feat(FN-3097): wire plugin prompt contributions into execution surfaces
- Add plugin prompt contribution support across executor, triage, reviewer, and heartbeat instruction builders - Thread plugin runner context into execution prompt assembly and preserve source issue commit reference hints - Expand engine tests to cover prompt contribution injection behavior and regression scenarios - Update architecture and plugin authoring docs with prompt surface integration details Fusion-Task-Id: FN-3097
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
resolveAgentInstructionsWithRatings,
|
||||
buildAgentChatPrompt,
|
||||
buildSystemPromptWithInstructions,
|
||||
buildPluginPromptSection,
|
||||
ensureDefaultHeartbeatProcedureFile,
|
||||
resolveAgentHeartbeatProcedure,
|
||||
} from "../agent-instructions.js";
|
||||
@@ -555,6 +556,38 @@ describe("buildSystemPromptWithInstructions", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildPluginPromptSection", () => {
|
||||
it("returns empty string when pluginRunner is undefined", () => {
|
||||
expect(buildPluginPromptSection("triage", undefined)).toBe("");
|
||||
});
|
||||
|
||||
it("returns empty string when no contributions match", () => {
|
||||
const pluginRunner = {
|
||||
getPromptContributionsForSurface: vi.fn().mockReturnValue([]),
|
||||
};
|
||||
|
||||
expect(buildPluginPromptSection("triage", pluginRunner as any)).toBe("");
|
||||
});
|
||||
|
||||
it("formats grouped plugin sections and prepend-before-append ordering", () => {
|
||||
const pluginRunner = {
|
||||
getPromptContributionsForSurface: vi.fn().mockReturnValue([
|
||||
{ pluginId: "plugin-b", contribution: { surface: "triage", content: "append B1" }, config: {} },
|
||||
{ pluginId: "plugin-a", contribution: { surface: "triage", content: "prepend A1", position: "prepend" }, config: {} },
|
||||
{ pluginId: "plugin-a", contribution: { surface: "triage", content: "prepend A2", position: "prepend" }, config: {} },
|
||||
{ pluginId: "plugin-c", contribution: { surface: "triage", content: "append C1", position: "append" }, config: {} },
|
||||
]),
|
||||
};
|
||||
|
||||
const result = buildPluginPromptSection("triage", pluginRunner as any);
|
||||
|
||||
expect(result).toContain("## Plugin: plugin-a\n\nprepend A1\n\nprepend A2");
|
||||
expect(result).toContain("## Plugin: plugin-b\n\nappend B1");
|
||||
expect(result).toContain("## Plugin: plugin-c\n\nappend C1");
|
||||
expect(result.indexOf("## Plugin: plugin-a")).toBeLessThan(result.indexOf("## Plugin: plugin-b"));
|
||||
});
|
||||
});
|
||||
|
||||
describe("diagnostics logging", () => {
|
||||
let testDir: string;
|
||||
|
||||
|
||||
@@ -146,6 +146,47 @@ describe("reviewStep — spec review type", () => {
|
||||
expect(opts.systemPrompt).toContain("Mission clarity");
|
||||
});
|
||||
|
||||
it("appends reviewer plugin prompt contributions when provided", async () => {
|
||||
mockedCreateFnAgent.mockResolvedValue(
|
||||
createMockSession("### Verdict: APPROVE\n### Summary\nGood spec."),
|
||||
);
|
||||
|
||||
const pluginRunner = {
|
||||
getPromptContributionsForSurface: vi.fn().mockReturnValue([
|
||||
{ pluginId: "plugin-review", contribution: { content: "Follow plugin reviewer rubric." } },
|
||||
]),
|
||||
};
|
||||
|
||||
await reviewStep(
|
||||
"/tmp/worktree", "FN-050", 0, "Spec Review", "spec", "# Task: KB-050",
|
||||
undefined,
|
||||
{ pluginRunner: pluginRunner as any },
|
||||
);
|
||||
|
||||
const opts = mockedCreateFnAgent.mock.calls[0][0];
|
||||
expect(opts.systemPrompt).toContain("## Plugin: plugin-review");
|
||||
expect(opts.systemPrompt).toContain("Follow plugin reviewer rubric.");
|
||||
});
|
||||
|
||||
it("keeps reviewer system prompt unchanged when no reviewer plugin contributions exist", async () => {
|
||||
mockedCreateFnAgent.mockResolvedValue(
|
||||
createMockSession("### Verdict: APPROVE\n### Summary\nGood spec."),
|
||||
);
|
||||
|
||||
const pluginRunner = {
|
||||
getPromptContributionsForSurface: vi.fn().mockReturnValue([]),
|
||||
};
|
||||
|
||||
await reviewStep(
|
||||
"/tmp/worktree", "FN-050", 0, "Spec Review", "spec", "# Task: KB-050",
|
||||
undefined,
|
||||
{ pluginRunner: pluginRunner as any },
|
||||
);
|
||||
|
||||
const opts = mockedCreateFnAgent.mock.calls[0][0];
|
||||
expect(opts.systemPrompt).not.toContain("## Plugin:");
|
||||
});
|
||||
|
||||
it("injects read-only memory instructions and tools when project memory is enabled", async () => {
|
||||
mockedCreateFnAgent.mockResolvedValue(
|
||||
createMockSession("### Verdict: APPROVE\n### Summary\nGood spec."),
|
||||
|
||||
@@ -697,6 +697,103 @@ describe("fast-mode triage", () => {
|
||||
expect(capturedSystemPrompt).toContain("## Review Level");
|
||||
});
|
||||
|
||||
it("includes triage plugin contributions when provided", async () => {
|
||||
const task = createTriageTask({ id: "FN-FAST-PLUGIN-001", executionMode: "standard" });
|
||||
const store = createMockStore({
|
||||
getTask: vi.fn().mockResolvedValue({ ...mockTaskDetail, id: task.id, attachments: [], comments: [] }),
|
||||
});
|
||||
const pluginRunner = {
|
||||
getPromptContributionsForSurface: vi.fn().mockImplementation((surface: string) => {
|
||||
if (surface !== "triage") return [];
|
||||
return [{ pluginId: "plugin-triage", contribution: { surface: "triage", content: "Use plugin triage policy." }, config: {} }];
|
||||
}),
|
||||
getPluginSkills: vi.fn().mockReturnValue([]),
|
||||
};
|
||||
|
||||
let capturedSystemPrompt = "";
|
||||
mockCreateFnAgent.mockImplementationOnce(async (opts: any) => {
|
||||
capturedSystemPrompt = opts.systemPrompt;
|
||||
return {
|
||||
session: {
|
||||
state: {},
|
||||
sessionManager: { getLeafId: vi.fn().mockReturnValue(null) },
|
||||
prompt: vi.fn().mockResolvedValue(undefined),
|
||||
dispose: vi.fn(),
|
||||
navigateTree: vi.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const processor = new TriageProcessor(store, "/tmp/root", { pluginRunner: pluginRunner as any });
|
||||
await processor.specifyTask(task);
|
||||
|
||||
expect(capturedSystemPrompt).toContain("## Plugin: plugin-triage");
|
||||
expect(capturedSystemPrompt).toContain("Use plugin triage policy.");
|
||||
});
|
||||
|
||||
it("keeps triage prompt unchanged when no triage plugin contributions exist", async () => {
|
||||
const task = createTriageTask({ id: "FN-FAST-PLUGIN-002", executionMode: "standard" });
|
||||
const store = createMockStore({
|
||||
getTask: vi.fn().mockResolvedValue({ ...mockTaskDetail, id: task.id, attachments: [], comments: [] }),
|
||||
});
|
||||
const pluginRunner = {
|
||||
getPromptContributionsForSurface: vi.fn().mockReturnValue([]),
|
||||
getPluginSkills: vi.fn().mockReturnValue([]),
|
||||
};
|
||||
|
||||
let capturedSystemPrompt = "";
|
||||
mockCreateFnAgent.mockImplementationOnce(async (opts: any) => {
|
||||
capturedSystemPrompt = opts.systemPrompt;
|
||||
return {
|
||||
session: {
|
||||
state: {},
|
||||
sessionManager: { getLeafId: vi.fn().mockReturnValue(null) },
|
||||
prompt: vi.fn().mockResolvedValue(undefined),
|
||||
dispose: vi.fn(),
|
||||
navigateTree: vi.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const processor = new TriageProcessor(store, "/tmp/root", { pluginRunner: pluginRunner as any });
|
||||
await processor.specifyTask(task);
|
||||
|
||||
expect(capturedSystemPrompt).not.toContain("## Plugin:");
|
||||
});
|
||||
|
||||
it("applies triage plugin contributions in fast mode too", async () => {
|
||||
const task = createTriageTask({ id: "FN-FAST-PLUGIN-003", executionMode: "fast" });
|
||||
const store = createMockStore({
|
||||
getTask: vi.fn().mockResolvedValue({ ...mockTaskDetail, id: task.id, attachments: [], comments: [] }),
|
||||
});
|
||||
const pluginRunner = {
|
||||
getPromptContributionsForSurface: vi.fn().mockReturnValue([
|
||||
{ pluginId: "plugin-fast", contribution: { surface: "triage", content: "Fast mode plugin note." }, config: {} },
|
||||
]),
|
||||
getPluginSkills: vi.fn().mockReturnValue([]),
|
||||
};
|
||||
|
||||
let capturedSystemPrompt = "";
|
||||
mockCreateFnAgent.mockImplementationOnce(async (opts: any) => {
|
||||
capturedSystemPrompt = opts.systemPrompt;
|
||||
return {
|
||||
session: {
|
||||
state: {},
|
||||
sessionManager: { getLeafId: vi.fn().mockReturnValue(null) },
|
||||
prompt: vi.fn().mockResolvedValue(undefined),
|
||||
dispose: vi.fn(),
|
||||
navigateTree: vi.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const processor = new TriageProcessor(store, "/tmp/root", { pluginRunner: pluginRunner as any });
|
||||
await processor.specifyTask(task);
|
||||
|
||||
expect(capturedSystemPrompt).toContain("This task is running in **fast mode**");
|
||||
expect(capturedSystemPrompt).toContain("## Plugin: plugin-fast");
|
||||
});
|
||||
|
||||
it("auto-approves fn_review_spec in fast mode without calling reviewer", async () => {
|
||||
const rootDir = await createTriageFixtureRoot("fusion-triage-fast-review-");
|
||||
try {
|
||||
|
||||
@@ -24,7 +24,12 @@ import { Type, type Static } from "@mariozechner/pi-ai";
|
||||
import { createHash } from "node:crypto";
|
||||
import { createTaskCreateTool, createTaskLogToolWithContext, createTaskDocumentWriteTool, createTaskDocumentReadTool, createListAgentsTool, createDelegateTaskTool, createGetAgentConfigTool, createUpdateAgentConfigTool, createSendMessageTool, createReadMessagesTool, createMemoryTools, createReadEvaluationsTool, createUpdateIdentityTool, createReflectOnPerformanceTool, taskCreateParams } from "./agent-tools.js";
|
||||
import { AgentLogger } from "./agent-logger.js";
|
||||
import { resolveAgentInstructionsWithRatings, buildSystemPromptWithInstructions, resolveAgentHeartbeatProcedure } from "./agent-instructions.js";
|
||||
import {
|
||||
resolveAgentInstructionsWithRatings,
|
||||
buildSystemPromptWithInstructions,
|
||||
buildPluginPromptSection,
|
||||
resolveAgentHeartbeatProcedure,
|
||||
} from "./agent-instructions.js";
|
||||
import { heartbeatLog, formatError } from "./logger.js";
|
||||
import { createRunAuditor, type EngineRunContext } from "./run-audit.js";
|
||||
import { promptWithFallback } from "./pi.js";
|
||||
@@ -1689,6 +1694,19 @@ export class HeartbeatMonitor {
|
||||
baseHeartbeatSystemPrompt,
|
||||
[resolvedInstructionsForIdentity, memoryInstructions, selfImprovePrompt].filter((part) => part.trim()).join("\n\n"),
|
||||
);
|
||||
const heartbeatContributions = this.pluginRunner
|
||||
?.getPromptContributionsForSurface("heartbeat")
|
||||
?? [];
|
||||
if (heartbeatContributions.length > 0) {
|
||||
heartbeatLog.log(`applied ${heartbeatContributions.length} plugin prompt contributions for heartbeat surface`);
|
||||
}
|
||||
const heartbeatPluginContributions = buildPluginPromptSection(
|
||||
"heartbeat",
|
||||
this.pluginRunner,
|
||||
);
|
||||
const systemPromptFinal = heartbeatPluginContributions
|
||||
? `${systemPrompt}\n\n${heartbeatPluginContributions}`
|
||||
: systemPrompt;
|
||||
|
||||
// fn_heartbeat_done must be the last tool in the array (stable terminal signal)
|
||||
heartbeatTools.push(heartbeatDoneTool);
|
||||
@@ -1717,7 +1735,7 @@ export class HeartbeatMonitor {
|
||||
runtimeHint: extractRuntimeHint(agent.runtimeConfig),
|
||||
pluginRunner: this.pluginRunner,
|
||||
cwd: rootDir,
|
||||
systemPrompt,
|
||||
systemPrompt: systemPromptFinal,
|
||||
tools: "coding",
|
||||
customTools: heartbeatTools,
|
||||
...(() => {
|
||||
@@ -1956,7 +1974,7 @@ export class HeartbeatMonitor {
|
||||
try {
|
||||
const runWithPrompts: AgentHeartbeatRun = {
|
||||
...run,
|
||||
systemPrompt: truncatePrompt(systemPrompt, 100_000),
|
||||
systemPrompt: truncatePrompt(systemPromptFinal, 100_000),
|
||||
executionPrompt: truncatePrompt(executionPrompt, 100_000),
|
||||
heartbeatProcedureSource: customProcedure ? "custom" : "default",
|
||||
};
|
||||
|
||||
@@ -6,7 +6,9 @@ import {
|
||||
type Agent,
|
||||
type AgentRatingSummary,
|
||||
type AgentStore,
|
||||
type PluginPromptSurface,
|
||||
} from "@fusion/core";
|
||||
import type { PluginRunner } from "./plugin-runner.js";
|
||||
import { createLogger } from "./logger.js";
|
||||
|
||||
const log = createLogger("agent-instructions");
|
||||
@@ -383,3 +385,38 @@ export function buildSystemPromptWithInstructions(
|
||||
if (!instructions.trim()) return basePrompt;
|
||||
return `${basePrompt}\n\n## Custom Instructions\n\n${instructions}`;
|
||||
}
|
||||
|
||||
export function buildPluginPromptSection(
|
||||
surface: PluginPromptSurface,
|
||||
pluginRunner: PluginRunner | undefined,
|
||||
): string {
|
||||
if (!pluginRunner) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const contributions = pluginRunner.getPromptContributionsForSurface(surface);
|
||||
if (contributions.length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const prependByPlugin = new Map<string, string[]>();
|
||||
const appendByPlugin = new Map<string, string[]>();
|
||||
|
||||
for (const { pluginId, contribution } of contributions) {
|
||||
const target = contribution.position === "prepend" ? prependByPlugin : appendByPlugin;
|
||||
const existing = target.get(pluginId) ?? [];
|
||||
existing.push(contribution.content);
|
||||
target.set(pluginId, existing);
|
||||
}
|
||||
|
||||
const toSections = (group: Map<string, string[]>): string[] => {
|
||||
return Array.from(group.entries()).map(([pluginId, contents]) => {
|
||||
return `## Plugin: ${pluginId}\n\n${contents.join("\n\n")}`;
|
||||
});
|
||||
};
|
||||
|
||||
const sections = [...toSections(prependByPlugin), ...toSections(appendByPlugin)];
|
||||
|
||||
log.log(`Applied ${contributions.length} prompt contributions for surface '${surface}'`);
|
||||
return sections.join("\n\n");
|
||||
}
|
||||
|
||||
@@ -42,7 +42,11 @@ import type { StuckTaskDetector, StuckTaskEvent } from "./stuck-task-detector.js
|
||||
import type { PluginRunner } from "./plugin-runner.js";
|
||||
import { isContextLimitError } from "./context-limit-detector.js";
|
||||
import { StepSessionExecutor } from "./step-session-executor.js";
|
||||
import { resolveAgentInstructions, buildSystemPromptWithInstructions } from "./agent-instructions.js";
|
||||
import {
|
||||
resolveAgentInstructions,
|
||||
buildSystemPromptWithInstructions,
|
||||
buildPluginPromptSection,
|
||||
} from "./agent-instructions.js";
|
||||
import type { AgentReflectionService } from "./agent-reflection.js";
|
||||
import { createRunAuditor, generateSyntheticRunId, type EngineRunContext } from "./run-audit.js";
|
||||
import { evaluateSpecStaleness, getPromptPath } from "./spec-staleness.js";
|
||||
@@ -2884,6 +2888,17 @@ export class TaskExecutor {
|
||||
getExecutorSystemPrompt(settings),
|
||||
executorInstructions,
|
||||
);
|
||||
const executorSystemContributions = this.options.pluginRunner?.getPromptContributionsForSurface("executor-system") ?? [];
|
||||
if (executorSystemContributions.length > 0) {
|
||||
executorLog.log(`${task.id}: applied ${executorSystemContributions.length} plugin prompt contributions for executor-system surface`);
|
||||
}
|
||||
const executorPluginContributions = buildPluginPromptSection(
|
||||
"executor-system",
|
||||
this.options.pluginRunner,
|
||||
);
|
||||
const executorSystemPromptFinal = executorPluginContributions
|
||||
? `${executorSystemPrompt}\n\n${executorPluginContributions}`
|
||||
: executorSystemPrompt;
|
||||
|
||||
// sessionFile must be let because it's destructured alongside session which is reassigned
|
||||
// eslint-disable-next-line prefer-const
|
||||
@@ -2892,7 +2907,7 @@ export class TaskExecutor {
|
||||
runtimeHint: executorRuntimeHint,
|
||||
pluginRunner: this.options.pluginRunner,
|
||||
cwd: worktreePath,
|
||||
systemPrompt: executorSystemPrompt,
|
||||
systemPrompt: executorSystemPromptFinal,
|
||||
tools: "coding",
|
||||
customTools,
|
||||
onText: agentLogger.onText,
|
||||
@@ -2970,7 +2985,13 @@ export class TaskExecutor {
|
||||
"Review the current state of your worktree and proceed with the next pending step.",
|
||||
].join("\n"));
|
||||
} else {
|
||||
const agentPrompt = buildExecutionPrompt(detail, this.rootDir, settings, worktreePath);
|
||||
const agentPrompt = buildExecutionPrompt(
|
||||
detail,
|
||||
this.rootDir,
|
||||
settings,
|
||||
worktreePath,
|
||||
this.options.pluginRunner,
|
||||
);
|
||||
await promptWithFallback(session, agentPrompt);
|
||||
}
|
||||
|
||||
@@ -3196,7 +3217,7 @@ export class TaskExecutor {
|
||||
runtimeHint: executorRuntimeHint,
|
||||
pluginRunner: this.options.pluginRunner,
|
||||
cwd: worktreePath,
|
||||
systemPrompt: executorSystemPrompt,
|
||||
systemPrompt: executorSystemPromptFinal,
|
||||
tools: "coding",
|
||||
customTools,
|
||||
onText: agentLogger.onText,
|
||||
@@ -3248,7 +3269,7 @@ export class TaskExecutor {
|
||||
"Do NOT ask for permission. Do NOT write a summary. Just call a tool and keep working.",
|
||||
"",
|
||||
"Original task:",
|
||||
buildExecutionPrompt(detail, this.rootDir, settings, worktreePath),
|
||||
buildExecutionPrompt(detail, this.rootDir, settings, worktreePath, this.options.pluginRunner),
|
||||
].join("\n");
|
||||
} else {
|
||||
retryPrompt = [
|
||||
@@ -3258,7 +3279,7 @@ export class TaskExecutor {
|
||||
"2. If there is remaining work, finish it and then call fn_task_done.",
|
||||
"",
|
||||
"Original task:",
|
||||
buildExecutionPrompt(detail, this.rootDir, settings, worktreePath),
|
||||
buildExecutionPrompt(detail, this.rootDir, settings, worktreePath, this.options.pluginRunner),
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
@@ -6926,7 +6947,13 @@ function buildSourceIssueRef(sourceIssue: TaskDetail["sourceIssue"]): string {
|
||||
return `${sourceIssue.repository}#${issueNumber}`;
|
||||
}
|
||||
|
||||
export function buildExecutionPrompt(task: TaskDetail, rootDir?: string, settings?: Settings, worktreePath?: string): string {
|
||||
export function buildExecutionPrompt(
|
||||
task: TaskDetail,
|
||||
rootDir?: string,
|
||||
settings?: Settings,
|
||||
worktreePath?: string,
|
||||
pluginRunner?: PluginRunner,
|
||||
): string {
|
||||
const prompt = scopePromptToWorktree(task.prompt, rootDir, worktreePath);
|
||||
const reviewMatch = prompt.match(/##\s*Review Level[:\s]*(\d)/);
|
||||
const reviewLevel = reviewMatch ? parseInt(reviewMatch[1], 10) : 0;
|
||||
@@ -7020,6 +7047,12 @@ git log --oneline
|
||||
steeringSection = lines.join("\n");
|
||||
}
|
||||
|
||||
const taskPromptContributions = pluginRunner?.getPromptContributionsForSurface("executor-task") ?? [];
|
||||
if (taskPromptContributions.length > 0) {
|
||||
executorLog.log(`${task.id}: applied ${taskPromptContributions.length} plugin prompt contributions for executor-task surface`);
|
||||
}
|
||||
const pluginTaskContributions = buildPluginPromptSection("executor-task", pluginRunner);
|
||||
|
||||
return `Execute this task.
|
||||
|
||||
## Task: ${task.id}
|
||||
@@ -7038,6 +7071,10 @@ ${reviewLevel >= 1 ? `Before implementing each step (except Step 0 and the final
|
||||
${reviewLevel >= 2 ? `After implementing + committing each step, call:
|
||||
\`fn_review_step(step=N, type="code", step_name="...", baseline="<SHA from before step>")\`` : ""}
|
||||
${reviewLevel >= 3 ? `After tests, also call fn_review_step with type="code" for test review.` : ""}
|
||||
${pluginTaskContributions ? `
|
||||
|
||||
${pluginTaskContributions}
|
||||
` : ""}
|
||||
|
||||
## Worktree Boundaries
|
||||
|
||||
|
||||
@@ -16,7 +16,11 @@ import { buildSessionSkillContext } from "./session-skill-context.js";
|
||||
import { AgentLogger } from "./agent-logger.js";
|
||||
import { reviewerLog } from "./logger.js";
|
||||
import { checkSessionError } from "./usage-limit-detector.js";
|
||||
import { resolveAgentInstructions, buildSystemPromptWithInstructions } from "./agent-instructions.js";
|
||||
import {
|
||||
resolveAgentInstructions,
|
||||
buildSystemPromptWithInstructions,
|
||||
buildPluginPromptSection,
|
||||
} from "./agent-instructions.js";
|
||||
import { createFallbackModelObserver } from "./fallback-model-observer.js";
|
||||
import { createMemoryGetTool, createMemorySearchTool } from "./agent-tools.js";
|
||||
|
||||
@@ -408,6 +412,19 @@ export async function reviewStep(
|
||||
reviewerBasePrompt + memorySection,
|
||||
reviewerInstructions,
|
||||
);
|
||||
const reviewerContributions = options.pluginRunner
|
||||
?.getPromptContributionsForSurface("reviewer")
|
||||
?? [];
|
||||
if (reviewerContributions.length > 0) {
|
||||
reviewerLog.log(`applied ${reviewerContributions.length} plugin prompt contributions for reviewer surface`);
|
||||
}
|
||||
const reviewerPluginContributions = buildPluginPromptSection(
|
||||
"reviewer",
|
||||
options.pluginRunner,
|
||||
);
|
||||
const reviewerSystemPromptFinal = reviewerPluginContributions
|
||||
? `${reviewerSystemPrompt}\n\n${reviewerPluginContributions}`
|
||||
: reviewerSystemPrompt;
|
||||
|
||||
// Build skill selection context (assigned agent skills take precedence over role fallback)
|
||||
let skillContext = undefined;
|
||||
@@ -477,7 +494,7 @@ export async function reviewStep(
|
||||
runtimeHint: extractRuntimeHint(memoryAgent?.runtimeConfig),
|
||||
pluginRunner: options.pluginRunner,
|
||||
cwd,
|
||||
systemPrompt: reviewerSystemPrompt,
|
||||
systemPrompt: reviewerSystemPromptFinal,
|
||||
tools: "readonly",
|
||||
customTools: memoryTools,
|
||||
onText: agentLogger ? agentLogger.onText : (delta) => options.onText?.(delta),
|
||||
|
||||
@@ -23,7 +23,11 @@ import { reviewStep, type ReviewVerdict } from "./reviewer.js";
|
||||
import { buildSessionSkillContext } from "./session-skill-context.js";
|
||||
import { PRIORITY_SPECIFY, type AgentSemaphore } from "./concurrency.js";
|
||||
import { AgentLogger } from "./agent-logger.js";
|
||||
import { resolveAgentInstructions, buildSystemPromptWithInstructions } from "./agent-instructions.js";
|
||||
import {
|
||||
resolveAgentInstructions,
|
||||
buildSystemPromptWithInstructions,
|
||||
buildPluginPromptSection,
|
||||
} from "./agent-instructions.js";
|
||||
import { createFallbackModelObserver } from "./fallback-model-observer.js";
|
||||
import { planLog, reviewerLog, formatError } from "./logger.js";
|
||||
import {
|
||||
@@ -975,6 +979,19 @@ export class TriageProcessor {
|
||||
|| (isFast ? FAST_TRIAGE_SYSTEM_PROMPT : TRIAGE_SYSTEM_PROMPT),
|
||||
triageInstructions,
|
||||
);
|
||||
const triageContributions = this.options.pluginRunner
|
||||
?.getPromptContributionsForSurface("triage")
|
||||
?? [];
|
||||
if (triageContributions.length > 0) {
|
||||
planLog.log(`${task.id}: applied ${triageContributions.length} plugin prompt contributions for triage surface`);
|
||||
}
|
||||
const triagePluginContributions = buildPluginPromptSection(
|
||||
"triage",
|
||||
this.options.pluginRunner,
|
||||
);
|
||||
const triageSystemPromptFinal = triagePluginContributions
|
||||
? `${triageSystemPrompt}\n\n${triagePluginContributions}`
|
||||
: triageSystemPrompt;
|
||||
|
||||
// Build skill selection context (assigned agent skills take precedence over role fallback)
|
||||
const skillContext = await buildSessionSkillContext({
|
||||
@@ -990,7 +1007,7 @@ export class TriageProcessor {
|
||||
runtimeHint: triageRuntimeHint,
|
||||
pluginRunner: this.options.pluginRunner,
|
||||
cwd: this.rootDir,
|
||||
systemPrompt: triageSystemPrompt,
|
||||
systemPrompt: triageSystemPromptFinal,
|
||||
tools: "coding",
|
||||
customTools,
|
||||
onText: agentLogger.onText,
|
||||
@@ -1238,7 +1255,7 @@ export class TriageProcessor {
|
||||
runtimeHint: triageRuntimeHint,
|
||||
pluginRunner: this.options.pluginRunner,
|
||||
cwd: this.rootDir,
|
||||
systemPrompt: triageSystemPrompt,
|
||||
systemPrompt: triageSystemPromptFinal,
|
||||
tools: "coding",
|
||||
customTools,
|
||||
onText: agentLogger.onText,
|
||||
|
||||
Reference in New Issue
Block a user