diff --git a/.changeset/fix-triage-prompt-writer-tools.md b/.changeset/fix-triage-prompt-writer-tools.md new file mode 100644 index 0000000000..22dc635c19 --- /dev/null +++ b/.changeset/fix-triage-prompt-writer-tools.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Let task planning persist complete specifications before Plan Review starts. +category: fix +dev: Triage sessions now use the coding tool surface so `fn_task_prompt_write` remains available. diff --git a/packages/engine/src/__tests__/pi-create-fn-agent.test.ts b/packages/engine/src/__tests__/pi-create-fn-agent.test.ts index 66f5159693..5049568a46 100644 --- a/packages/engine/src/__tests__/pi-create-fn-agent.test.ts +++ b/packages/engine/src/__tests__/pi-create-fn-agent.test.ts @@ -2210,6 +2210,27 @@ describe("createFnAgent", () => { expect(createSessionArgs.customTools.map((tool) => tool.name)).toContain("fn_list_agents"); }); + it("keeps fn_task_prompt_write in coding session tools", async () => { + const promptWriter = { + name: "fn_task_prompt_write", + label: "Write PROMPT.md", + description: "Persist the task specification", + parameters: {}, + execute: vi.fn(), + }; + + const { createFnAgent } = await import("../pi.js"); + await createFnAgent({ + cwd: "/tmp", + systemPrompt: "test", + tools: "coding", + customTools: [promptWriter as any], + }); + + const createSessionArgs = createAgentSessionMock.mock.calls[0]?.[0] as { customTools: Array<{ name: string }> }; + expect(createSessionArgs.customTools.map((tool) => tool.name)).toContain("fn_task_prompt_write"); + }); + it("does not allow extra builtin tools in readonly sessions by default", async () => { const { createFnAgent } = await import("../pi.js"); diff --git a/packages/engine/src/__tests__/triage-planning-prompt-single-source.test.ts b/packages/engine/src/__tests__/triage-planning-prompt-single-source.test.ts index 17b60cf476..53b1b64da0 100644 --- a/packages/engine/src/__tests__/triage-planning-prompt-single-source.test.ts +++ b/packages/engine/src/__tests__/triage-planning-prompt-single-source.test.ts @@ -158,7 +158,7 @@ describe("triage planning prompt single source", () => { await new TriageProcessor(store, "/tmp/root").specifyTask(task); expect(customTools.map((tool) => tool.name)).toContain("fn_task_prompt_write"); - expect(sessionTools).toBe("readonly"); + expect(sessionTools).toBe("coding"); }); it("requires triage plans to use the durable prompt writer instead of generic filesystem writes", () => { diff --git a/packages/engine/src/triage.ts b/packages/engine/src/triage.ts index ea90032c41..0dca328ad5 100644 --- a/packages/engine/src/triage.ts +++ b/packages/engine/src/triage.ts @@ -1612,8 +1612,11 @@ export class TriageProcessor { : { provider: undefined, modelId: undefined }; /* - FNXC:TriagePromptPersistence 2026-07-21-16:30: - `tools: "readonly"` intentionally coexists with the custom prompt writer above: readonly governs general tools, while fn_task_prompt_write performs the one authorized synchronized task-artifact mutation. + FNXC:TriagePromptPersistence 2026-07-21-17:50: + Planning must use the coding tool surface. The shared readonly policy filters + mutation tools, including fn_task_prompt_write, before the model sees them; + advertising that writer in the prompt while running readonly stranded triage + on the original PROMPT.md stub and sent the stub into Plan Review. */ const { session } = await createResolvedAgentSession({ sessionPurpose: "triage", @@ -1622,7 +1625,7 @@ export class TriageProcessor { cwd: this.rootDir, systemPrompt: triageSystemPromptFinal, systemPromptLayers: triageLayers, - tools: "readonly", + tools: "coding", customTools, onText: agentLogger.onText, onThinking: agentLogger.onThinking,