FN-7903: wire thinkingLevel into AI session creation for automation steps
Threads the persisted per-step Thinking Level (from FN-7900) into runtime AI session creation and task spawning, so scheduled, routine, and manual automation runs actually apply the chosen reasoning effort instead of only storing it. - CronRunner passes step.thinkingLevel through AiPromptExecutor to createFnAgent's defaultThinkingLevel for scheduled AI-prompt steps - RoutineRunner forwards step.thinkingLevel to the shared AiPromptExecutor seam for routine AI-prompt steps - Cron/routine create-task steps map step.thinkingLevel onto TaskCreateInput.thinkingLevel so spawned tasks inherit the configured reasoning effort - Dashboard's inline/manual AI-prompt and create-task automation routes apply the same defaultThinkingLevel / TaskCreateInput.thinkingLevel behavior - Updated docs (dashboard-guide.md, settings-reference.md) to describe the now-active runtime behavior - Added a changeset for @runfusion/fusion (minor) and expanded cron-runner/routine-runner/routes-automation test coverage Files changed: .changeset/fn-7903-automation-thinking-level.md | 7 ++ docs/dashboard-guide.md | 5 +- docs/settings-reference.md | 2 +- .../src/__tests__/routes-automation.test.ts | 67 +++++++++++++++++++ packages/dashboard/src/routes.ts | 10 +++ packages/engine/src/__tests__/cron-runner.test.ts | 75 +++++++++++++++++++++- .../engine/src/__tests__/routine-runner.test.ts | 68 +++++++++++++++++++- packages/engine/src/cron-runner.ts | 21 +++++- packages/engine/src/routine-runner.ts | 11 +++- 9 files changed, 255 insertions(+), 11 deletions(-) Fusion-Task-Id: FN-7903 Fusion-Task-Lineage: c7eb4660-975d-4c5d-820e-0f1a3ac8b6a6 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-7903-automation-thinking-level.md
Normal file
7
.changeset/fn-7903-automation-thinking-level.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": minor
|
||||
---
|
||||
|
||||
summary: Schedule and routine AI steps now apply the chosen thinking level at run time.
|
||||
category: feature
|
||||
dev: Threads AutomationStep.thinkingLevel into createFnAgent (defaultThinkingLevel) across cron-runner, routine-runner, and the dashboard inline ai-prompt path, and maps it onto create-task steps' task thinkingLevel (FN-7903, follow-up to FN-7900).
|
||||
@@ -142,9 +142,10 @@ On mobile viewports, the Right Dock never renders. The compact Header actions an
|
||||
|
||||
<!-- FNXC:AutomationTools 2026-06-26-00:00: Automation AI-prompt steps now default to the full coding tool set and expose per-step restrictions so operators can intentionally narrow tool access without breaking legacy schedules. -->
|
||||
<!-- FNXC:AutomationLiveOutput 2026-06-26-00:00: Manual automation runs stream step, text, and tool activity into the Automations card while preserving the final run-result history after completion. -->
|
||||
<!-- FNXC:Automations 2026-07-12-19:14: Schedule and routine AI-capable model selectors persist an optional Thinking Level on each step. Default/inherit stays empty, concrete off..xhigh values are stored with the JSON step configuration; runtime application is tracked separately. -->
|
||||
<!-- FNXC:Automations 2026-07-12-19:14: Schedule and routine AI-capable model selectors persist an optional Thinking Level on each step. Default/inherit stays empty, concrete off..xhigh values are stored with the JSON step configuration. -->
|
||||
<!-- FNXC:Automations 2026-07-12-20:30: FN-7903 makes the persisted step Thinking Level runtime-active for scheduled, routine, and manual AI Prompt runs, and maps Create Task step choices onto the spawned task. -->
|
||||
|
||||
Open **Automations** from the left sidebar (or the mobile More surfaces) to create cron, webhook, API, or manual routines. AI Prompt steps now run with all selectable coding tools by default: **Read**, **Bash**, **Edit**, **Write**, **Grep**, **Find**, and **Ls**. In the routine editor, use **Allowed tools** on a simple AI Prompt action or any multi-step AI Prompt step to clear or re-select tools. Leaving every tool selected stores the legacy default, so existing schedules continue to run with full tool access; clearing every box is an explicit no-tools configuration. AI Prompt and Create Task action model selectors also include **Thinking Level**: leave it on **Default** to inherit the project setting, or choose a concrete reasoning effort to save it with that step.
|
||||
Open **Automations** from the left sidebar (or the mobile More surfaces) to create cron, webhook, API, or manual routines. AI Prompt steps now run with all selectable coding tools by default: **Read**, **Bash**, **Edit**, **Write**, **Grep**, **Find**, and **Ls**. In the routine editor, use **Allowed tools** on a simple AI Prompt action or any multi-step AI Prompt step to clear or re-select tools. Leaving every tool selected stores the legacy default, so existing schedules continue to run with full tool access; clearing every box is an explicit no-tools configuration. AI Prompt and Create Task action model selectors also include **Thinking Level**: leave it on **Default** to inherit the project setting, or choose a concrete reasoning effort. Scheduled, routine, and manual AI Prompt runs apply that saved reasoning effort at session creation; Create Task steps copy it onto the task they create.
|
||||
|
||||
When you choose **Run now**, the routine card opens a **Live output** panel while the manual run is active. The panel appends step status, AI text deltas, and tool start/finish activity as the run executes, then the card falls back to the persisted final run output and run history once the server records the result. The same `RoutineCard` surface is used by the floating modal and embedded Automations view, so live output appears in both presentations and collapses into a single-column card layout on mobile.
|
||||
|
||||
|
||||
@@ -1018,7 +1018,7 @@ The three GPT-5.6 codenamed OpenAI Codex variants (`gpt-5.6-luna`, `gpt-5.6-sol`
|
||||
6. Assigned durable agent runtime model (`runtimeConfig.model` or `runtimeConfig.modelProvider` + `runtimeConfig.modelId`) when both provider and model ID are set and no task/lane/default pair is configured
|
||||
7. Automatic provider/model resolution
|
||||
|
||||
Workflow prompt steps and scheduled/manual AI-prompt automation steps use the same executor lane before falling back to project/global defaults; explicit step-level `modelProvider` + `modelId` values still take precedence for that individual step. If a non-mock, non-test-mode session still reaches runtime creation without a complete provider/model pair, Fusion logs a warning and records `noModelResolved` plus `runtimeBuiltInFallbackModel` on `session:runtime-resolved` so the runtime's built-in fallback model is observable.
|
||||
Workflow prompt steps and scheduled/manual AI-prompt automation steps use the same executor lane before falling back to project/global defaults; explicit step-level `modelProvider` + `modelId` values still take precedence for that individual step. Automation AI Prompt steps also apply an explicit step `thinkingLevel` at session creation, while Create Task automation steps copy that reasoning-effort value onto the spawned task; leaving it empty preserves the lane/default thinking-level inheritance. If a non-mock, non-test-mode session still reaches runtime creation without a complete provider/model pair, Fusion logs a warning and records `noModelResolved` plus `runtimeBuiltInFallbackModel` on `session:runtime-resolved` so the runtime's built-in fallback model is observable.
|
||||
|
||||
### Heartbeat model (durable agents)
|
||||
|
||||
|
||||
@@ -978,6 +978,41 @@ describe("Automation routes", () => {
|
||||
}));
|
||||
});
|
||||
|
||||
it("forwards manual ai-prompt thinkingLevel into session creation and leaves omitted level unset", async () => {
|
||||
vi.mocked(createFnAgent).mockClear();
|
||||
const mockStore = createMockAutomationStore();
|
||||
mockStore.getSchedule.mockResolvedValue({
|
||||
...FAKE_SCHEDULE,
|
||||
command: "",
|
||||
steps: [
|
||||
{
|
||||
id: "step-ai-high",
|
||||
type: "ai-prompt",
|
||||
name: "High thinking AI",
|
||||
prompt: "Summarize deeply",
|
||||
thinkingLevel: " high ",
|
||||
},
|
||||
{
|
||||
id: "step-ai-default",
|
||||
type: "ai-prompt",
|
||||
name: "Default thinking AI",
|
||||
prompt: "Summarize normally",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const { app } = buildApp(mockStore);
|
||||
const res = await REQUEST(app, "POST", "/api/automations/sched-001/run");
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(vi.mocked(createFnAgent)).toHaveBeenNthCalledWith(1, expect.objectContaining({
|
||||
defaultThinkingLevel: "high",
|
||||
}));
|
||||
expect(vi.mocked(createFnAgent)).toHaveBeenNthCalledWith(2, expect.objectContaining({
|
||||
defaultThinkingLevel: undefined,
|
||||
}));
|
||||
});
|
||||
|
||||
it("streams buffered live events for a completed manual AI prompt run", async () => {
|
||||
vi.mocked(createFnAgent).mockClear();
|
||||
const mockStore = createMockAutomationStore();
|
||||
@@ -1083,6 +1118,7 @@ describe("Automation routes", () => {
|
||||
expect(vi.mocked(createFnAgent)).toHaveBeenCalledWith(expect.objectContaining({
|
||||
tools: "coding",
|
||||
toolsAllowlist: undefined,
|
||||
defaultThinkingLevel: undefined,
|
||||
}));
|
||||
});
|
||||
|
||||
@@ -1099,6 +1135,7 @@ describe("Automation routes", () => {
|
||||
taskTitle: "Weekly report",
|
||||
taskDescription: "Create weekly maintenance report",
|
||||
taskColumn: "todo",
|
||||
thinkingLevel: " high ",
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -1118,6 +1155,7 @@ describe("Automation routes", () => {
|
||||
title: "Weekly report",
|
||||
description: "Create weekly maintenance report",
|
||||
column: "todo",
|
||||
thinkingLevel: "high",
|
||||
}),
|
||||
);
|
||||
expect(res.body.result.stepResults[0]).toEqual(
|
||||
@@ -1129,6 +1167,35 @@ describe("Automation routes", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("leaves manual create-task thinkingLevel unset when the step omits it", async () => {
|
||||
const mockStore = createMockAutomationStore();
|
||||
mockStore.getSchedule.mockResolvedValue({
|
||||
...FAKE_SCHEDULE,
|
||||
command: "",
|
||||
steps: [
|
||||
{
|
||||
id: "step-task-default",
|
||||
type: "create-task",
|
||||
name: "Create default follow-up",
|
||||
taskDescription: "Create default maintenance report",
|
||||
},
|
||||
],
|
||||
});
|
||||
const { app, store } = buildApp(mockStore);
|
||||
(store.createTask as ReturnType<typeof vi.fn>).mockResolvedValue({
|
||||
id: "FN-9005",
|
||||
title: "",
|
||||
description: "Create default maintenance report",
|
||||
});
|
||||
|
||||
const res = await REQUEST(app, "POST", "/api/automations/sched-001/run");
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(store.createTask).toHaveBeenCalledWith(expect.objectContaining({
|
||||
thinkingLevel: undefined,
|
||||
}));
|
||||
});
|
||||
|
||||
it("create-task automation step remains successful without explicit tracking issue creation", async () => {
|
||||
const createIssueSpy = vi.spyOn(GitHubClient.prototype, "createIssue").mockResolvedValue({
|
||||
owner: "task",
|
||||
|
||||
@@ -5387,8 +5387,12 @@ async function executeAiPromptStep(
|
||||
/*
|
||||
* FNXC:McpConfig 2026-06-26-00:00:
|
||||
* Manual AI-prompt workflow runs are operator-triggered coding-agent sessions, so they must receive the task-store resolved MCP set just like task executor lanes. Do not log resolved MCP payloads because env/header values may contain materialized secrets.
|
||||
*
|
||||
* FNXC:Automations 2026-07-12-20:30:
|
||||
* Manual/inline automation AI runs bypass CronRunner's executor seam, so they must pass the persisted step thinking level directly as createFnAgent.defaultThinkingLevel. Undefined or blank values preserve inherited defaults.
|
||||
*/
|
||||
const mcpServers = await resolveManualAiPromptMcpServers(taskStore);
|
||||
const defaultThinkingLevel = step.thinkingLevel?.trim() || undefined;
|
||||
|
||||
const { session } = await createFnAgent({
|
||||
cwd: process.cwd(),
|
||||
@@ -5397,6 +5401,7 @@ async function executeAiPromptStep(
|
||||
toolsAllowlist: step.allowedTools,
|
||||
defaultProvider: modelProvider,
|
||||
defaultModelId: modelId,
|
||||
defaultThinkingLevel,
|
||||
mcpServers,
|
||||
onText: (delta: string) => {
|
||||
responseText += delta;
|
||||
@@ -5464,12 +5469,17 @@ async function executeCreateTaskStep(
|
||||
}
|
||||
|
||||
try {
|
||||
/*
|
||||
FNXC:Automations 2026-07-12-20:30:
|
||||
Manual/inline create-task automation runs map the persisted step thinking level onto the created task so manual execution matches scheduled and routine behavior.
|
||||
*/
|
||||
const task = await taskStore.createTask({
|
||||
title: step.taskTitle?.trim() || undefined,
|
||||
description: step.taskDescription.trim(),
|
||||
column: (step.taskColumn as import("@fusion/core").Column) || "triage",
|
||||
modelProvider: step.modelProvider?.trim() || undefined,
|
||||
modelId: step.modelId?.trim() || undefined,
|
||||
thinkingLevel: (step.thinkingLevel?.trim() || undefined) as import("@fusion/core").TaskCreateInput["thinkingLevel"],
|
||||
source: {
|
||||
sourceType: "workflow_step",
|
||||
sourceMetadata: { stepId: step.id },
|
||||
|
||||
@@ -223,6 +223,21 @@ describe("CronRunner", () => {
|
||||
expect(capturedOptions.toolsAllowlist).toBeUndefined();
|
||||
});
|
||||
|
||||
it("forwards thinkingLevel as createFnAgent defaultThinkingLevel and omits blank values", async () => {
|
||||
const capturedOptions: any[] = [];
|
||||
piModuleMocks.createFnAgent.mockImplementation(async (options: any) => {
|
||||
capturedOptions.push(options);
|
||||
return { session: { dispose: vi.fn() } };
|
||||
});
|
||||
|
||||
const executor = await createAiPromptExecutor("/test/project");
|
||||
await executor("Think deeply", undefined, undefined, undefined, "high");
|
||||
await executor("Inherit defaults", undefined, undefined, undefined, " ");
|
||||
|
||||
expect(capturedOptions[0].defaultThinkingLevel).toBe("high");
|
||||
expect(capturedOptions[1].defaultThinkingLevel).toBeUndefined();
|
||||
});
|
||||
|
||||
it("forwards store-resolved MCP servers to scheduled AI prompt sessions", async () => {
|
||||
let capturedOptions: any;
|
||||
piModuleMocks.createFnAgent.mockImplementation(async (options: any) => {
|
||||
@@ -1034,6 +1049,7 @@ describe("CronRunner", () => {
|
||||
"anthropic",
|
||||
"claude-sonnet-4-5",
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1224,7 +1240,37 @@ describe("CronRunner", () => {
|
||||
|
||||
await runner.executeSchedule(schedule);
|
||||
|
||||
expect(mockExecutor).toHaveBeenCalledWith("Use selected tools", "anthropic", "claude-sonnet-4-5", ["Read", "Grep"]);
|
||||
expect(mockExecutor).toHaveBeenCalledWith("Use selected tools", "anthropic", "claude-sonnet-4-5", ["Read", "Grep"], undefined);
|
||||
});
|
||||
|
||||
it("passes explicit step thinkingLevel to executor and leaves omitted level unset", async () => {
|
||||
const store = createMockStore();
|
||||
const mockExecutor = createAiMockExecutor("response");
|
||||
const schedule = createMockSchedule({
|
||||
command: "",
|
||||
steps: [
|
||||
makeStep({
|
||||
type: "ai-prompt",
|
||||
name: "High thinking",
|
||||
prompt: "Think deeply",
|
||||
thinkingLevel: "high",
|
||||
command: undefined,
|
||||
}),
|
||||
makeStep({
|
||||
type: "ai-prompt",
|
||||
name: "Default thinking",
|
||||
prompt: "Use default",
|
||||
command: undefined,
|
||||
}),
|
||||
],
|
||||
});
|
||||
const automationStore = createMockAutomationStore([schedule]);
|
||||
runner = new CronRunner(store, automationStore, { aiPromptExecutor: mockExecutor });
|
||||
|
||||
await runner.executeSchedule(schedule);
|
||||
|
||||
expect(mockExecutor).toHaveBeenNthCalledWith(1, "Think deeply", "anthropic", "claude-sonnet-4-5", undefined, "high");
|
||||
expect(mockExecutor).toHaveBeenNthCalledWith(2, "Use default", "anthropic", "claude-sonnet-4-5", undefined, undefined);
|
||||
});
|
||||
|
||||
it("passes step model provider and model ID to executor", async () => {
|
||||
@@ -1248,7 +1294,7 @@ describe("CronRunner", () => {
|
||||
|
||||
await runner.executeSchedule(schedule);
|
||||
|
||||
expect(mockExecutor).toHaveBeenCalledWith("Do something", "openai", "gpt-4o", undefined);
|
||||
expect(mockExecutor).toHaveBeenCalledWith("Do something", "openai", "gpt-4o", undefined, undefined);
|
||||
});
|
||||
|
||||
it("falls back to settings defaults when step has no model", async () => {
|
||||
@@ -1273,7 +1319,7 @@ describe("CronRunner", () => {
|
||||
|
||||
await runner.executeSchedule(schedule);
|
||||
|
||||
expect(mockExecutor).toHaveBeenCalledWith("Use defaults", "anthropic", "claude-sonnet-4-5", undefined);
|
||||
expect(mockExecutor).toHaveBeenCalledWith("Use defaults", "anthropic", "claude-sonnet-4-5", undefined, undefined);
|
||||
});
|
||||
|
||||
it("returns configuration error when no executor is provided", async () => {
|
||||
@@ -1514,6 +1560,7 @@ describe("CronRunner", () => {
|
||||
column: "todo",
|
||||
modelProvider: "anthropic",
|
||||
modelId: "claude-sonnet-4-5",
|
||||
thinkingLevel: undefined,
|
||||
source: {
|
||||
sourceType: "cron",
|
||||
sourceMetadata: { scheduleId: "test-schedule-id", stepId: expect.any(String) },
|
||||
@@ -1521,6 +1568,28 @@ describe("CronRunner", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("maps explicit create-task thinkingLevel and leaves omitted level unset", async () => {
|
||||
const mockTask = { id: "FN-7788", title: "", description: "Some task" };
|
||||
const createTaskMock = vi.fn().mockResolvedValue(mockTask);
|
||||
const store = createMockStore({} as any);
|
||||
(store as any).createTask = createTaskMock;
|
||||
|
||||
const schedule = createMockSchedule({
|
||||
command: "",
|
||||
steps: [
|
||||
makeCreateTaskStep({ taskDescription: "High effort task", thinkingLevel: "high" }),
|
||||
makeCreateTaskStep({ taskDescription: "Inherited effort task" }),
|
||||
],
|
||||
});
|
||||
const automationStore = createMockAutomationStore([schedule]);
|
||||
runner = new CronRunner(store, automationStore);
|
||||
|
||||
await runner.executeSchedule(schedule);
|
||||
|
||||
expect(createTaskMock).toHaveBeenNthCalledWith(1, expect.objectContaining({ thinkingLevel: "high" }));
|
||||
expect(createTaskMock).toHaveBeenNthCalledWith(2, expect.objectContaining({ thinkingLevel: undefined }));
|
||||
});
|
||||
|
||||
it("defaults column to triage when taskColumn is not set", async () => {
|
||||
const mockTask = { id: "FN-9999", title: "", description: "Some task" };
|
||||
const createTaskMock = vi.fn().mockResolvedValue(mockTask);
|
||||
|
||||
@@ -346,11 +346,77 @@ describe("RoutineRunner", () => {
|
||||
const result = await runner.executeRoutine("routine-ai", "api", undefined, liveCallbacks);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(aiPromptExecutor).toHaveBeenCalledWith("Analyze this", undefined, undefined, ["Read", "Grep"], liveCallbacks);
|
||||
expect(aiPromptExecutor).toHaveBeenCalledWith("Analyze this", undefined, undefined, ["Read", "Grep"], undefined, liveCallbacks);
|
||||
expect(liveCallbacks.onStep).toHaveBeenCalledWith(expect.objectContaining({ stepId: "step-ai", status: "started" }));
|
||||
expect(liveCallbacks.onStep).toHaveBeenCalledWith(expect.objectContaining({ stepId: "step-ai", status: "completed", success: true }));
|
||||
});
|
||||
|
||||
it("forwards explicit and omitted ai-prompt thinking levels to the AI executor", async () => {
|
||||
const routine = createMockRoutine({
|
||||
id: "routine-ai-thinking",
|
||||
agentId: undefined,
|
||||
steps: [
|
||||
{
|
||||
id: "step-ai-high",
|
||||
type: "ai-prompt",
|
||||
name: "Analyze deeply",
|
||||
prompt: "Analyze deeply",
|
||||
thinkingLevel: "high",
|
||||
},
|
||||
{
|
||||
id: "step-ai-default",
|
||||
type: "ai-prompt",
|
||||
name: "Analyze normally",
|
||||
prompt: "Analyze normally",
|
||||
},
|
||||
],
|
||||
});
|
||||
const routineStore = createMockRoutineStore([routine]);
|
||||
const aiPromptExecutor = vi.fn().mockResolvedValue("ai output");
|
||||
const runner = createRoutineRunner({ routineStore, aiPromptExecutor });
|
||||
|
||||
const result = await runner.executeRoutine("routine-ai-thinking", "api");
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(aiPromptExecutor).toHaveBeenNthCalledWith(1, "Analyze deeply", undefined, undefined, undefined, "high", undefined);
|
||||
expect(aiPromptExecutor).toHaveBeenNthCalledWith(2, "Analyze normally", undefined, undefined, undefined, undefined, undefined);
|
||||
});
|
||||
|
||||
it("maps explicit and omitted create-task thinking levels onto spawned task input", async () => {
|
||||
const routine = createMockRoutine({
|
||||
id: "routine-task-thinking",
|
||||
agentId: undefined,
|
||||
steps: [
|
||||
{
|
||||
id: "step-task-high",
|
||||
type: "create-task",
|
||||
name: "Create high effort task",
|
||||
taskDescription: "Investigate deeply",
|
||||
thinkingLevel: "high",
|
||||
},
|
||||
{
|
||||
id: "step-task-default",
|
||||
type: "create-task",
|
||||
name: "Create default task",
|
||||
taskDescription: "Investigate normally",
|
||||
},
|
||||
],
|
||||
});
|
||||
const routineStore = createMockRoutineStore([routine]);
|
||||
const createTask = vi
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ id: "FN-7001", title: "", description: "Investigate deeply" })
|
||||
.mockResolvedValueOnce({ id: "FN-7002", title: "", description: "Investigate normally" });
|
||||
const taskStore = { ...createMockTaskStore(), createTask } as unknown as TaskStore;
|
||||
const runner = createRoutineRunner({ routineStore, taskStore });
|
||||
|
||||
const result = await runner.executeRoutine("routine-task-thinking", "api");
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(createTask).toHaveBeenNthCalledWith(1, expect.objectContaining({ thinkingLevel: "high" }));
|
||||
expect(createTask).toHaveBeenNthCalledWith(2, expect.objectContaining({ thinkingLevel: undefined }));
|
||||
});
|
||||
|
||||
it("cleans up inFlightExecutions map even on error", async () => {
|
||||
const routine = createMockRoutine({ id: "routine-error-cleanup", enabled: false });
|
||||
const routineStore = createMockRoutineStore([routine]);
|
||||
|
||||
@@ -208,11 +208,16 @@ export type AiPromptLiveCallbacks = {
|
||||
onToolEnd?: (name: string, isError: boolean, result?: unknown) => void;
|
||||
};
|
||||
|
||||
/*
|
||||
FNXC:Automations 2026-07-12-20:30:
|
||||
FN-7903 applies FN-7900's persisted per-step reasoning effort at runtime. Scheduled and routine AI steps pass `thinkingLevel` before live callbacks so undefined keeps inheriting the resolved default while explicit values become createFnAgent's defaultThinkingLevel.
|
||||
*/
|
||||
export type AiPromptExecutor = (
|
||||
prompt: string,
|
||||
modelProvider?: string,
|
||||
modelId?: string,
|
||||
allowedTools?: string[],
|
||||
thinkingLevel?: string,
|
||||
liveCallbacks?: AiPromptLiveCallbacks,
|
||||
) => Promise<string>;
|
||||
|
||||
@@ -885,6 +890,7 @@ export class CronRunner {
|
||||
const defaultModel = resolveExecutionSettingsModel(settings);
|
||||
const modelProvider = step.modelProvider?.trim() || defaultModel.provider;
|
||||
const modelId = step.modelId?.trim() || defaultModel.modelId;
|
||||
const thinkingLevel = step.thinkingLevel?.trim() || undefined;
|
||||
|
||||
const model = modelProvider && modelId
|
||||
? `${modelProvider}/${modelId}`
|
||||
@@ -894,7 +900,7 @@ export class CronRunner {
|
||||
|
||||
try {
|
||||
// Race between executor and timeout
|
||||
const resultPromise = this.aiPromptExecutor(step.prompt, modelProvider, modelId, step.allowedTools);
|
||||
const resultPromise = this.aiPromptExecutor(step.prompt, modelProvider, modelId, step.allowedTools, thinkingLevel);
|
||||
const timeoutPromise = new Promise<never>((_resolve, reject) => {
|
||||
setTimeout(() => reject(new Error(`AI prompt step timed out after ${timeoutMs / 1000}s`)), timeoutMs);
|
||||
});
|
||||
@@ -959,13 +965,17 @@ export class CronRunner {
|
||||
};
|
||||
}
|
||||
|
||||
// Build TaskCreateInput from step fields
|
||||
/*
|
||||
FNXC:Automations 2026-07-12-20:30:
|
||||
Create-task automation steps spawn normal Fusion tasks, so the persisted per-step thinking level must map onto TaskCreateInput.thinkingLevel. Empty values stay unset to preserve task/settings inheritance.
|
||||
*/
|
||||
const taskInput: TaskCreateInput = {
|
||||
title: step.taskTitle?.trim() || undefined,
|
||||
description: step.taskDescription.trim(),
|
||||
column: (step.taskColumn as Column) || "triage",
|
||||
modelProvider: step.modelProvider?.trim() || undefined,
|
||||
modelId: step.modelId?.trim() || undefined,
|
||||
thinkingLevel: (step.thinkingLevel?.trim() || undefined) as TaskCreateInput["thinkingLevel"],
|
||||
source: {
|
||||
sourceType: "cron",
|
||||
sourceMetadata: { scheduleId, stepId: step.id },
|
||||
@@ -1029,7 +1039,7 @@ const AI_AUTOMATION_SYSTEM_PROMPT = [
|
||||
export async function createAiPromptExecutor(cwd: string, store?: TaskStore): Promise<AiPromptExecutor> {
|
||||
const disposeLog = createLogger("cron-runner");
|
||||
|
||||
return async (prompt: string, modelProvider?: string, modelId?: string, allowedTools?: string[], liveCallbacks?: AiPromptLiveCallbacks): Promise<string> => {
|
||||
return async (prompt: string, modelProvider?: string, modelId?: string, allowedTools?: string[], thinkingLevel?: string, liveCallbacks?: AiPromptLiveCallbacks): Promise<string> => {
|
||||
let responseText = "";
|
||||
const skillContext = buildSessionSkillContextSync(null, "executor", cwd, undefined);
|
||||
|
||||
@@ -1039,8 +1049,12 @@ export async function createAiPromptExecutor(cwd: string, store?: TaskStore): Pr
|
||||
|
||||
FNXC:McpConfig 2026-06-26-00:00:
|
||||
Scheduled AI automations are coding-agent work surfaces. ProjectEngine passes the TaskStore so configured MCP servers are forwarded; lightweight in-process runtime callers may omit the store and keep the pre-existing empty-MCP behavior.
|
||||
|
||||
FNXC:Automations 2026-07-12-20:30:
|
||||
Scheduled/routine automation sessions forward the persisted per-step thinking level as createFnAgent's defaultThinkingLevel. Undefined or whitespace-only values intentionally omit the option so settings-level inheritance remains unchanged.
|
||||
*/
|
||||
const mcpServers = store ? (await resolveMcpServersForStore(store)).servers : undefined;
|
||||
const defaultThinkingLevel = thinkingLevel?.trim() || undefined;
|
||||
const { session } = await createFnAgent({
|
||||
cwd,
|
||||
systemPrompt: AI_AUTOMATION_SYSTEM_PROMPT,
|
||||
@@ -1051,6 +1065,7 @@ export async function createAiPromptExecutor(cwd: string, store?: TaskStore): Pr
|
||||
...(skillContext.additionalSkillPaths.length > 0 ? { additionalSkillPaths: skillContext.additionalSkillPaths } : {}),
|
||||
defaultProvider: modelProvider,
|
||||
defaultModelId: modelId,
|
||||
defaultThinkingLevel,
|
||||
mcpServers,
|
||||
onText: (delta: string) => {
|
||||
responseText += delta;
|
||||
|
||||
@@ -458,8 +458,12 @@ export class RoutineRunner {
|
||||
return { stepId: step.id, stepName: step.name, stepIndex, success: false, output: "", error: "AI execution is not configured", startedAt, completedAt: new Date().toISOString() };
|
||||
}
|
||||
try {
|
||||
/*
|
||||
FNXC:Automations 2026-07-12-20:30:
|
||||
Routine AI-prompt steps share the CronRunner AiPromptExecutor seam. Pass the persisted per-step thinking level before live callbacks so explicit reasoning effort applies and omitted/blank values inherit defaults.
|
||||
*/
|
||||
const output = await Promise.race([
|
||||
this.options.aiPromptExecutor(step.prompt, step.modelProvider, step.modelId, step.allowedTools, liveCallbacks),
|
||||
this.options.aiPromptExecutor(step.prompt, step.modelProvider, step.modelId, step.allowedTools, step.thinkingLevel?.trim() || undefined, liveCallbacks),
|
||||
new Promise<never>((_resolve, reject) => setTimeout(() => reject(new Error(`AI prompt step timed out after ${timeoutMs / 1000}s`)), timeoutMs)),
|
||||
]);
|
||||
return { stepId: step.id, stepName: step.name, stepIndex, success: true, output: truncateOutput(output, ""), startedAt, completedAt: new Date().toISOString() };
|
||||
@@ -476,12 +480,17 @@ export class RoutineRunner {
|
||||
return { stepId: step.id, stepName: step.name, stepIndex, success: false, output: "", error: "Create-task step has no task description specified", startedAt, completedAt: new Date().toISOString() };
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:Automations 2026-07-12-20:30:
|
||||
Routine create-task steps map their persisted thinking level onto the spawned task. Blank values remain undefined to keep the task's normal thinking-level inheritance.
|
||||
*/
|
||||
const taskInput: TaskCreateInput = {
|
||||
title: step.taskTitle?.trim() || undefined,
|
||||
description: step.taskDescription.trim(),
|
||||
column: (step.taskColumn as Column) || "triage",
|
||||
modelProvider: step.modelProvider?.trim() || undefined,
|
||||
modelId: step.modelId?.trim() || undefined,
|
||||
thinkingLevel: (step.thinkingLevel?.trim() || undefined) as TaskCreateInput["thinkingLevel"],
|
||||
source: {
|
||||
sourceType: "automation",
|
||||
sourceMetadata: { routineId: routine.id, stepId: step.id },
|
||||
|
||||
Reference in New Issue
Block a user