FN-8538: give Planning Mode a dedicated collaborative prompt
Planning Mode now uses a standalone collaborative prompt rather than inherited task-triage instructions. - Resolve the Planning Mode default independently of workflows and triage assignments. - Preserve explicit full-system prompt overrides and update prompt-setting documentation. - Cover dedicated prompt behavior and add the published-package changeset. Files changed: .changeset/fn-8538-planning-prompt.md | 7 +++ docs/dashboard-guide.md | 3 +- .../core/src/__tests__/prompt-overrides.test.ts | 7 +++ packages/core/src/prompt-overrides.ts | 8 ++- packages/dashboard/app/utils/builtinPrompts.ts | 8 ++- .../__tests__/planning-infinite-interview.test.ts | 60 +++++++++++++++++++++- .../__tests__/planning-prompt-resolution.test.ts | 47 ++++++++++------- packages/dashboard/src/planning.ts | 46 +++++------------ 8 files changed, 130 insertions(+), 56 deletions(-) Fusion-Task-Id: FN-8538 Fusion-Task-Lineage: 36e0665d-7995-4bc5-9c73-948f7f4e9131 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
7
.changeset/fn-8538-planning-prompt.md
Normal file
7
.changeset/fn-8538-planning-prompt.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
summary: Give Planning Mode a dedicated collaborative prompt instead of task-triage instructions.
|
||||
category: fix
|
||||
dev: The planning-system override remains a full system-prompt replacement.
|
||||
@@ -534,7 +534,8 @@ When an active Planning AI generation appears stuck, Planning Mode automatically
|
||||
<!-- FNXC:PlanningMode 2026-07-19-15:55: FN-8400 replaces the duplicate prompt-recovery controls with a focused three-pane interview; restarting remains a deliberate New session action. -->
|
||||
Use **New session** to restart planning with a different idea.
|
||||
|
||||
Planning Mode uses the selected workflow's `planning` seam—the same triage template used for newly added tasks—as its quality bar, then layers the user-controlled interview adapter on top. An explicitly configured `planning-system` prompt override replaces that full system prompt.
|
||||
<!-- FNXC:PlanningMode 2026-07-23-11:50: Planning Mode must preserve a collaborative discovery identity instead of inheriting triage/workflow execution specifications. -->
|
||||
Planning Mode uses a dedicated collaborative system prompt. It investigates relevant repository and board context, explores assumptions and tradeoffs, considers decomposition choices without automatically creating child tasks, and sharpens concrete deliverables with observable acceptance criteria. This is separate from task triage and the executor-ready **PROMPT.md** generated after task creation. An explicitly configured nonblank `planning-system` prompt override replaces the complete dedicated system prompt; blank or absent values retain the default interview.
|
||||
|
||||
<!-- FNXC:PlanningMode 2026-07-20-15:45: FN-8442 replaces the simultaneous three-pane interview with a sequential plan-review and question loop. -->
|
||||
Planning Mode is a single-surface sequence: enter an idea, wait while Fusion generates a concrete initial plan, then review that evolving work product before deciding whether clarification is needed. Plan review shows the title, description, explicit **What to change** and **Acceptance criteria** sections, and deliverables, plus model-suggested **Focus the next question** choices and **Write your own focus**. Choose **Refine** to ask one high-impact question or **Validate** to accept the current plan. Answering a question shows **Updating plan…** and returns to plan review rather than automatically starting another question. The active generation purpose and running plan are persisted, so refreshing or navigating away during generation restores the correct progress state and eventual review.
|
||||
|
||||
@@ -73,6 +73,13 @@ describe("prompt-overrides", () => {
|
||||
expect(meta?.name).toBe("Executor Welcome");
|
||||
});
|
||||
|
||||
it("describes Planning Mode as a dedicated prompt with an explicit full replacement", () => {
|
||||
const meta = getPromptKeyMetadata("planning-system");
|
||||
expect(meta?.description).toMatch(/dedicated collaborative prompt/i);
|
||||
expect(meta?.defaultContent).toMatch(/dedicated collaborative Planning Mode prompt/i);
|
||||
expect(meta?.defaultContent).not.toMatch(/workflow planning seam|triage template/i);
|
||||
});
|
||||
|
||||
it("should return undefined for invalid keys", () => {
|
||||
expect(getPromptKeyMetadata("invalid-key" as PromptKey)).toBeUndefined();
|
||||
expect(getPromptKeyMetadata("" as PromptKey)).toBeUndefined();
|
||||
|
||||
@@ -256,12 +256,16 @@ The prompt should:
|
||||
|
||||
Output ONLY the prompt text (no markdown, no explanations).`,
|
||||
},
|
||||
/*
|
||||
FNXC:PlanningMode 2026-07-23-11:40:
|
||||
Planning Mode has a dedicated collaborative default rather than triage or workflow prompt composition. This setting remains an explicit full replacement so operators can intentionally provide a different complete system prompt.
|
||||
*/
|
||||
"planning-system": {
|
||||
key: "planning-system",
|
||||
name: "Planning System",
|
||||
roles: ["triage"],
|
||||
description: "Explicit full system-prompt replacement for Planning Mode; otherwise it uses the workflow planning seam plus interview adapter",
|
||||
defaultContent: "Runtime default: the selected workflow planning seam (the same triage template used for new tasks) plus the user-validated JSON interview adapter. Set an explicit override to replace the full system prompt.",
|
||||
description: "Explicit full system-prompt replacement for Planning Mode's dedicated collaborative prompt",
|
||||
defaultContent: "Runtime default: a dedicated collaborative Planning Mode prompt with a user-validated JSON interview. Set an explicit override to replace the full system prompt.",
|
||||
},
|
||||
/**
|
||||
* FNXC:AgentOnboardingRuntime 2026-07-15-15:25:
|
||||
|
||||
@@ -149,12 +149,16 @@ export const PROMPT_KEY_CATALOG: Record<PromptKey, PromptKeyMetadata> = {
|
||||
description: "System prompt for refining workflow step descriptions",
|
||||
defaultContent: "You are an expert at creating detailed agent prompts...",
|
||||
},
|
||||
/*
|
||||
FNXC:PlanningMode 2026-07-23-11:40:
|
||||
Keep the browser catalog synchronized with core: Planning Mode's dedicated collaborative default is independent from triage and workflow seams, while this override replaces that complete prompt.
|
||||
*/
|
||||
"planning-system": {
|
||||
key: "planning-system",
|
||||
name: "Planning System",
|
||||
roles: ["triage"],
|
||||
description: "Explicit full system-prompt replacement for Planning Mode; otherwise it uses the workflow planning seam plus interview adapter",
|
||||
defaultContent: "Runtime default: selected workflow planning seam plus the user-validated JSON interview adapter.",
|
||||
description: "Explicit full system-prompt replacement for Planning Mode's dedicated collaborative prompt",
|
||||
defaultContent: "Runtime default: a dedicated collaborative Planning Mode prompt with a user-validated JSON interview.",
|
||||
},
|
||||
"subtask-breakdown-system": {
|
||||
key: "subtask-breakdown-system",
|
||||
|
||||
@@ -60,10 +60,14 @@ function completePayload(): string {
|
||||
});
|
||||
}
|
||||
|
||||
/** System prompts used by agent constructions in the current test. */
|
||||
let agentSystemPrompts: string[] = [];
|
||||
|
||||
/** A scripted planning agent that records every prompt sent through the live session seam. */
|
||||
function installScriptedAgent(responses: string[]) {
|
||||
const prompts: string[] = [];
|
||||
__setCreateFnAgent(vi.fn(async () => {
|
||||
__setCreateFnAgent(vi.fn(async ({ systemPrompt }: { systemPrompt: string }) => {
|
||||
agentSystemPrompts.push(systemPrompt);
|
||||
const messages: Array<{ role: string; content: string }> = [];
|
||||
return {
|
||||
session: {
|
||||
@@ -101,6 +105,7 @@ const SECOND_QUESTION = {
|
||||
describe("reactive Planning Mode question contract", () => {
|
||||
beforeEach(() => {
|
||||
__resetPlanningState();
|
||||
agentSystemPrompts = [];
|
||||
});
|
||||
|
||||
it("preserves every valid suggested refinement category", () => {
|
||||
@@ -167,6 +172,18 @@ describe("reactive Planning Mode question contract", () => {
|
||||
expect(PLANNING_SYSTEM_PROMPT).toMatch(/Proceed with plan serializes the plan as plan\.md/i);
|
||||
});
|
||||
|
||||
it("defines a collaborative planning contract rather than an execution-task specification", () => {
|
||||
expect(PLANNING_SYSTEM_PROMPT).toMatch(/investigate relevant repository and active-board context/i);
|
||||
expect(PLANNING_SYSTEM_PROMPT).toMatch(/assumptions, unknowns, constraints/i);
|
||||
expect(PLANNING_SYSTEM_PROMPT).toMatch(/Compare viable approaches and their trade-offs/i);
|
||||
expect(PLANNING_SYSTEM_PROMPT).toMatch(/Discuss decomposition/i);
|
||||
expect(PLANNING_SYSTEM_PROMPT).toMatch(/concrete deliverables.*observable acceptance criteria/i);
|
||||
expect(PLANNING_SYSTEM_PROMPT).toMatch(/not an executor-ready task specification/i);
|
||||
expect(PLANNING_SYSTEM_PROMPT).toMatch(/do not automatically split work/i);
|
||||
expect(PLANNING_SYSTEM_PROMPT).toMatch(/Do not produce task-specification bookkeeping.*commit guidance.*no-code-change caveats.*task-creation directives/i);
|
||||
expect(PLANNING_SYSTEM_PROMPT).toMatch(/respond only with JSON/i);
|
||||
});
|
||||
|
||||
it("repairs malformed select options and appends one localized Other option", () => {
|
||||
const question = normalizePlanningQuestion({
|
||||
id: "security",
|
||||
@@ -407,6 +424,7 @@ describe("reactive Planning Mode question contract", () => {
|
||||
expect(prompts[1]).toContain("Use audit logs");
|
||||
expect(prompts[3]).toContain("Use audit logs");
|
||||
expect(prompts[3]).toContain("Add a staged rollout.");
|
||||
expect(agentSystemPrompts).toEqual([PLANNING_SYSTEM_PROMPT, PLANNING_SYSTEM_PROMPT]);
|
||||
expect((await getSession(created.sessionId))?.pendingContextualComments).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -491,6 +509,46 @@ describe("reactive Planning Mode question contract", () => {
|
||||
expect(created.summary.description).not.toBe(created.firstQuestion.question);
|
||||
});
|
||||
|
||||
it("uses the dedicated prompt in both initial and streaming agent creation paths", async () => {
|
||||
const systemPrompts: string[] = [];
|
||||
__setCreateFnAgent(vi.fn(async ({ systemPrompt }: { systemPrompt: string }) => {
|
||||
systemPrompts.push(systemPrompt);
|
||||
const messages: Array<{ role: string; content: string }> = [];
|
||||
return {
|
||||
session: {
|
||||
state: { messages },
|
||||
prompt: vi.fn(async () => {
|
||||
messages.push({ role: "assistant", content: payload({ ...FIRST_QUESTION, runningPlan: normalizePlanningSummaryPayload({ title: "Dedicated prompt plan" }) }) });
|
||||
}),
|
||||
dispose: vi.fn(),
|
||||
},
|
||||
};
|
||||
}) as never);
|
||||
const leakingStore = {
|
||||
...MOCK_TASK_STORE,
|
||||
getSettings: vi.fn(async () => ({
|
||||
agentPrompts: {
|
||||
roleAssignments: { triage: "execution-triage" },
|
||||
templates: [{ id: "execution-triage", role: "triage", prompt: "PROMPT.md NO-CODE CREATE CHILD TASK" }],
|
||||
},
|
||||
})),
|
||||
} as unknown as TaskStore;
|
||||
|
||||
await createSession("127.0.0.21", "Plan a safer sign-in flow", leakingStore, "/tmp/project");
|
||||
const streamingSessionId = await createSessionWithAgent(
|
||||
"127.0.0.22", "Plan a safer sign-in flow", "/tmp/project", leakingStore,
|
||||
undefined, undefined, undefined, { workflowId: "WF-custom" },
|
||||
);
|
||||
planningStreamManager.consumeInitialTurn(streamingSessionId)?.();
|
||||
await vi.waitFor(() => expect(systemPrompts).toHaveLength(2));
|
||||
|
||||
for (const systemPrompt of systemPrompts) {
|
||||
expect(systemPrompt).toBe(PLANNING_SYSTEM_PROMPT);
|
||||
expect(systemPrompt).not.toContain("PROMPT.md NO-CODE CREATE CHILD TASK");
|
||||
expect(systemPrompt).toContain('"type":"question"');
|
||||
}
|
||||
});
|
||||
|
||||
it("uses a model-authored initial plan on the streaming first turn and exposes one question", async () => {
|
||||
installScriptedAgent([payload({
|
||||
...FIRST_QUESTION,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { PROMPT_KEY_CATALOG, type TaskStore } from "@fusion/core";
|
||||
import { resolvePlanningModeSystemPrompt } from "../planning.js";
|
||||
import { type TaskStore } from "@fusion/core";
|
||||
import { PLANNING_SYSTEM_PROMPT, resolvePlanningModeSystemPrompt } from "../planning.js";
|
||||
|
||||
function store(settings: Record<string, unknown> = {}, workflowPrompt?: string): TaskStore {
|
||||
return {
|
||||
@@ -12,29 +12,40 @@ function store(settings: Record<string, unknown> = {}, workflowPrompt?: string):
|
||||
}
|
||||
|
||||
describe("resolvePlanningModeSystemPrompt", () => {
|
||||
it("composes the selected workflow planning seam with the interview adapter", async () => {
|
||||
const prompt = await resolvePlanningModeSystemPrompt(store({}, "CUSTOM WORKFLOW PLANNING SEAM"), undefined, "WF-custom");
|
||||
expect(prompt).toContain("CUSTOM WORKFLOW PLANNING SEAM");
|
||||
it("uses only the dedicated planning prompt for custom and built-in workflows", async () => {
|
||||
const executionMarkers = "PROMPT.md NO-CODE TASK CAVEAT CREATE CHILD TASK";
|
||||
const customWorkflow = await resolvePlanningModeSystemPrompt(store({}, executionMarkers), undefined, "WF-custom");
|
||||
const builtInWorkflow = await resolvePlanningModeSystemPrompt(store({}, executionMarkers), undefined, "builtin:coding");
|
||||
|
||||
expect(customWorkflow).toBe(PLANNING_SYSTEM_PROMPT);
|
||||
expect(builtInWorkflow).toBe(PLANNING_SYSTEM_PROMPT);
|
||||
expect(customWorkflow).not.toContain(executionMarkers);
|
||||
expect(builtInWorkflow).not.toContain(executionMarkers);
|
||||
});
|
||||
|
||||
it("cannot inherit execution-only markers from an assigned triage prompt", async () => {
|
||||
const prompt = await resolvePlanningModeSystemPrompt(store({
|
||||
agentPrompts: {
|
||||
roleAssignments: { triage: "custom-triage" },
|
||||
templates: [{ id: "custom-triage", role: "triage", prompt: "TRIAGE PROMPT.md NO-CODE CREATE CHILD TASK" }],
|
||||
},
|
||||
}));
|
||||
|
||||
expect(prompt).toBe(PLANNING_SYSTEM_PROMPT);
|
||||
expect(prompt).not.toMatch(/TRIAGE PROMPT\.md NO-CODE CREATE CHILD TASK/);
|
||||
expect(prompt).toContain('"type":"question"');
|
||||
expect(prompt).toContain("exactly one");
|
||||
expect(prompt).toContain("Only the user can validate");
|
||||
});
|
||||
|
||||
it("does not mistake the catalog fallback for an explicit override", async () => {
|
||||
const prompt = await resolvePlanningModeSystemPrompt(store({}, "WORKFLOW SEAM MARKER"), undefined, "WF-custom");
|
||||
expect(prompt).toContain("WORKFLOW SEAM MARKER");
|
||||
expect(prompt).not.toBe(PROMPT_KEY_CATALOG["planning-system"].defaultContent);
|
||||
});
|
||||
|
||||
it("lets an explicit planning-system override replace the full prompt", async () => {
|
||||
it("lets a nonblank planning-system override replace the full prompt", async () => {
|
||||
await expect(resolvePlanningModeSystemPrompt(store(), { "planning-system": "OPERATOR REPLACEMENT" }))
|
||||
.resolves.toBe("OPERATOR REPLACEMENT");
|
||||
});
|
||||
|
||||
it("prefers the configured triage assignment and fails soft to a builtin seam", async () => {
|
||||
const assigned = await resolvePlanningModeSystemPrompt(store({ agentPrompts: { roleAssignments: { triage: "custom-triage" }, templates: [{ id: "custom-triage", role: "triage", prompt: "TRIAGE ASSIGNMENT MARKER" }] } }));
|
||||
expect(assigned).toContain("TRIAGE ASSIGNMENT MARKER");
|
||||
const fallback = await resolvePlanningModeSystemPrompt({ getSettings: vi.fn().mockRejectedValue(new Error("broken")), getWorkflowDefinition: vi.fn().mockRejectedValue(new Error("broken")) } as unknown as TaskStore);
|
||||
expect(fallback).toContain("task specification agent");
|
||||
it("uses the dedicated prompt for absent or blank overrides and when settings fail", async () => {
|
||||
await expect(resolvePlanningModeSystemPrompt(store())).resolves.toBe(PLANNING_SYSTEM_PROMPT);
|
||||
await expect(resolvePlanningModeSystemPrompt(store({ promptOverrides: { "planning-system": " " } }))).resolves.toBe(PLANNING_SYSTEM_PROMPT);
|
||||
await expect(resolvePlanningModeSystemPrompt({ getSettings: vi.fn().mockRejectedValue(new Error("broken")) } as unknown as TaskStore))
|
||||
.resolves.toBe(PLANNING_SYSTEM_PROMPT);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -28,12 +28,6 @@ import {
|
||||
TASK_PRIORITIES,
|
||||
THINKING_LEVELS,
|
||||
summarizeTitle,
|
||||
builtinSeamPrompt,
|
||||
renderTriagePolicyPlaceholders,
|
||||
resolveAgentPrompt,
|
||||
resolveEffectivePlannerHeartbeatPatrolEnabled,
|
||||
resolvePlanningPromptFromIr,
|
||||
resolveWorkflowIrById,
|
||||
type PromptOverrideMap,
|
||||
} from "@fusion/core";
|
||||
import type { SubtaskItem } from "./subtask-breakdown.js";
|
||||
@@ -233,29 +227,32 @@ async function ensureNtfyHelpersReady(): Promise<void> {
|
||||
// ── Constants ───────────────────────────────────────────────────────────────
|
||||
|
||||
/*
|
||||
FNXC:PlanningMode 2026-07-20-00:55:
|
||||
Planning Mode is user-terminated: each answered turn must produce one consequential, novel question with alternatives and trade-offs.
|
||||
FNXC:PlanningMode 2026-07-23-11:30:
|
||||
Planning Mode is a collaborative, user-terminated discovery session, not task triage. Its dedicated prompt must not inherit workflow or assigned-triage execution instructions, because those instructions can turn exploratory responses into executor specifications and child-task directives.
|
||||
|
||||
The model may update the running plan but must never infer completion; only the visible Proceed with plan action can make a session terminal.
|
||||
*/
|
||||
/** Planning system prompt for the AI agent */
|
||||
export const PLANNING_SYSTEM_PROMPT = `## Planning Mode interaction adapter
|
||||
/** Self-contained system prompt for the separate collaborative Planning Mode. */
|
||||
export const PLANNING_SYSTEM_PROMPT = `## Collaborative Planning Mode
|
||||
|
||||
First analyze the codebase and active board with the available readonly tools, fn_task_list, and fn_task_show. Treat the workflow planning template above as the quality bar and PROMPT.md structure for the evolving plan, but do not write PROMPT.md or use write tools during this interview.
|
||||
Help the operator iteratively turn an idea into a clear, useful plan. First investigate relevant repository and active-board context with the available readonly tools, fn_task_list, and fn_task_show when that context can reduce uncertainty. Explore the problem before assuming a solution: identify assumptions, unknowns, constraints, affected surfaces, and meaningful risks. Compare viable approaches and their trade-offs. Discuss decomposition when it clarifies scope, sequencing, ownership, or deliverables, but do not automatically split work or direct the creation of child tasks.
|
||||
|
||||
Start by producing a concrete initial plan and exactly one high-impact question. Author the operator-facing plan in Markdown: write the description as concise GitHub-flavored Markdown, while the structured change, acceptance, dependency, and deliverable fields become its Markdown sections and lists. After every answer, regenerate the plan and ask exactly one consequential next question. A refine turn uses the selected or free-text focus to choose that next question. The model never validates or terminates the session. Only the user can validate it through the visible Proceed with plan action.
|
||||
Build an evolving operator-facing plan, not an executor-ready task specification. Focus on intended outcomes, concrete deliverables, alternatives considered, and observable acceptance criteria. Do not produce task-specification bookkeeping or execution-process instructions such as task-size policy, commit guidance, no-code-change caveats, or task-creation directives. Author the operator-facing plan in Markdown: write the description as concise GitHub-flavored Markdown, while the structured change, acceptance, dependency, and deliverable fields become its Markdown sections and lists.
|
||||
|
||||
Start by producing a concrete initial plan and exactly one high-impact question. After every answer, regenerate the plan and ask exactly one consequential next question. A refine turn uses the selected or free-text focus to choose that next question. The model never validates or terminates the session. Only the user can validate it through the visible Proceed with plan action.
|
||||
|
||||
For every initial, answer, or refine turn respond only with JSON: {"type":"question","data":{"id":"unique-id","type":"single_select|multi_select","question":"...","description":"...","options":[{"id":"option-a","label":"...","description":"...","pros":["..."],"cons":["..."]},{"id":"option-b","label":"...","description":"...","pros":["..."],"cons":["..."]},{"id":"other","label":"...","isOther":true}],"runningPlan":{"title":"...","description":"...","proposedChanges":["specific change"],"acceptanceCriteria":["observable outcome"],"suggestedSize":"S|M|L","priority":"normal","suggestedDependencies":[],"keyDeliverables":["concrete work item"],"suggestedRefinements":["next focus 1","next focus 2"]}}}.
|
||||
|
||||
Every turn must include the running-plan fields: only title, description, concrete proposedChanges, observable acceptanceCriteria, suggestedSize, optional priority, suggestedDependencies, concrete keyDeliverables, and concise suggestedRefinements informed by the idea and answers so far. Include every distinct, high-value unresolved refinement area; do not cap the list at three. Never use interview question text as a deliverable. Do not put PROMPT.md sections (Mission, Before → After, Steps, File Scope, Review Level, Completion Criteria, or Do NOT) in runningPlan or free text: triage writes PROMPT.md only after the operator proceeds with the plan. Proceed with plan serializes the plan as plan.md without priority or suggestedRefinements; priority remains a task field. Every question must provide at least two alternatives, each with non-empty pros and cons, plus exactly one Other/write-your-own option. Write every label, option, and Other label in the language of the user's original input. Incorporate free-text Other answers verbatim as steering context for the following question.`;
|
||||
Every turn must include the running-plan fields: only title, description, concrete proposedChanges, observable acceptanceCriteria, suggestedSize, optional priority, suggestedDependencies, concrete keyDeliverables, and concise suggestedRefinements informed by the idea and answers so far. Include every distinct, high-value unresolved refinement area; do not cap the list at three. Never use interview question text as a deliverable. Proceed with plan serializes the plan as plan.md without priority or suggestedRefinements; priority remains a task field. Every question must provide at least two alternatives, each with non-empty pros and cons, plus exactly one Other/write-your-own option. Write every label, option, and Other label in the language of the user's original input. Incorporate free-text Other answers verbatim as steering context for the following question.`;
|
||||
|
||||
/*
|
||||
FNXC:PlanningMode 2026-07-20-14:30:
|
||||
Planning Mode must use the same workflow planning seam as triage for a newly added task, then layer its infinite JSON interview contract over that template. A catalog defaultContent is Settings UI documentation, not proof that an operator explicitly replaced the full system prompt.
|
||||
FNXC:PlanningMode 2026-07-23-11:35:
|
||||
The separate Planning Mode prompt has sole default authority. Workflow selection still resolves models and task creation, but workflow planning seams and triage assignments remain execution-task concerns and must never leak into collaborative planning turns.
|
||||
*/
|
||||
export async function resolvePlanningModeSystemPrompt(
|
||||
store: TaskStore,
|
||||
promptOverrides?: PromptOverrideMap,
|
||||
workflowId?: string,
|
||||
_workflowId?: string,
|
||||
): Promise<string> {
|
||||
const settings: Partial<Settings> = (await store.getSettings().catch(() => ({}))) ?? {};
|
||||
const overrides = promptOverrides ?? settings.promptOverrides;
|
||||
@@ -264,23 +261,8 @@ export async function resolvePlanningModeSystemPrompt(
|
||||
&& overrides["planning-system"].trim().length > 0
|
||||
? overrides["planning-system"]
|
||||
: undefined;
|
||||
if (explicitOverride) return explicitOverride;
|
||||
|
||||
const plannerHeartbeatPatrolEnabled = resolveEffectivePlannerHeartbeatPatrolEnabled(settings);
|
||||
const assignedTriagePrompt = settings.agentPrompts?.roleAssignments?.triage
|
||||
? resolveAgentPrompt("triage", settings.agentPrompts, { plannerHeartbeatPatrolEnabled })
|
||||
: "";
|
||||
let workflowPrompt: string | undefined;
|
||||
try {
|
||||
const ir = await resolveWorkflowIrById(store, workflowId || settings.defaultWorkflowId || "builtin:coding");
|
||||
workflowPrompt = resolvePlanningPromptFromIr(ir);
|
||||
} catch {
|
||||
// Resolve fail-soft below so a missing custom workflow cannot prevent planning.
|
||||
}
|
||||
const fallback = builtinSeamPrompt("planning")
|
||||
|| resolveAgentPrompt("triage", undefined, { plannerHeartbeatPatrolEnabled });
|
||||
const base = assignedTriagePrompt || workflowPrompt || fallback;
|
||||
return `${renderTriagePolicyPlaceholders(base, settings)}\n\n${PLANNING_SYSTEM_PROMPT}`;
|
||||
return explicitOverride ?? PLANNING_SYSTEM_PROMPT;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user