refactor(coding-agent): renamed 'complete' tool to 'submit_result' for clarity and consistency
- Renamed 'complete' tool to 'submit_result' throughout codebase for clarity and consistency. - Updated all tool references, function names, and variable names from 'complete' to 'submit_result' in executor, render, and tools modules. - Renamed CompleteTool class to SubmitResultTool and CompleteDetails interface to SubmitResultDetails. - Updated agent prompts and documentation to reference the new 'submit_result' tool name. - Reorganized task.md prompt to move critical guidance to the top and restructure instructions for better clarity.
This commit is contained in:
@@ -183,7 +183,7 @@ READ-ONLY if applicable — list prohibited actions explicitly.
|
||||
|
||||
<output>
|
||||
What to return. Schema requirements.
|
||||
Call `complete` with findings when done.
|
||||
Call `submit_result` with findings when done.
|
||||
</output>
|
||||
|
||||
<critical>
|
||||
@@ -541,11 +541,11 @@ READ-ONLY. You are STRICTLY PROHIBITED from:
|
||||
2. Read key sections (not entire files)
|
||||
3. Identify types, interfaces, key functions
|
||||
4. Note dependencies between files
|
||||
5. Call `complete` with findings
|
||||
5. Call `submit_result` with findings
|
||||
</procedure>
|
||||
|
||||
<critical>
|
||||
Read-only. Call `complete` when done. This matters.
|
||||
Read-only. Call `submit_result` when done. This matters.
|
||||
</critical>
|
||||
```
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
### Added
|
||||
- Added `/fork` command to create a new session with the exact same state (entries and artifacts) as the current session
|
||||
|
||||
### Changed
|
||||
- Renamed the `complete` tool to `submit_result` for subagent result submission
|
||||
|
||||
## [8.6.0] - 2026-01-27
|
||||
|
||||
### Added
|
||||
|
||||
@@ -268,7 +268,7 @@ src/
|
||||
│ ├── bash.ts # Bash command execution
|
||||
│ ├── bash-interceptor.ts # Bash command interception
|
||||
│ ├── calculator.ts # Calculator tool
|
||||
│ ├── complete.ts # Completion tool
|
||||
│ ├── submit-result.ts # Submit result tool
|
||||
│ ├── context.ts # Tool context utilities
|
||||
│ ├── fetch.ts # URL content fetching
|
||||
│ ├── find.ts # File search by glob
|
||||
|
||||
@@ -19,4 +19,4 @@ Return a concise JSON object with:
|
||||
Consider how this file's changes relate to the above files.
|
||||
{{/if}}
|
||||
|
||||
Call the complete tool with the JSON payload.
|
||||
Call the submit_result tool with the JSON payload.
|
||||
@@ -117,5 +117,5 @@ Infer from task, default medium:
|
||||
</procedure>
|
||||
|
||||
<critical>
|
||||
Read-only; no file modifications. Call `complete` with your findings when done.
|
||||
Read-only; no file modifications. Call `submit_result` with your findings when done.
|
||||
</critical>
|
||||
@@ -61,7 +61,7 @@ output:
|
||||
2. Read modified files for full context
|
||||
3. For large changes, spawn parallel `task` agents (one per module/concern)
|
||||
4. Call `report_finding` for each issue
|
||||
5. Call `complete` with your verdict — **review is incomplete until `complete` is called**
|
||||
5. Call `submit_result` with your verdict — **review is incomplete until `submit_result` is called**
|
||||
|
||||
Bash is read-only here: `git diff`, `git log`, `git show`, `gh pr diff`. No file modifications or builds.
|
||||
</procedure>
|
||||
@@ -109,17 +109,17 @@ Each `report_finding` requires:
|
||||
- `file_path`: Absolute path
|
||||
- `line_start`, `line_end`: Range ≤10 lines, must overlap the diff
|
||||
|
||||
Final `complete` call (payload goes under `data`):
|
||||
Final `submit_result` call (payload goes under `data`):
|
||||
- `data.overall_correctness`: "correct" (no bugs/blockers) or "incorrect"
|
||||
- `data.explanation`: Plain text, 1-3 sentences summarizing your verdict. Do NOT include JSON, do NOT repeat findings here (they're already captured via `report_finding`).
|
||||
- `data.confidence`: 0.0-1.0
|
||||
- `data.findings`: Optional; MUST omit (it is populated from `report_finding` calls)
|
||||
|
||||
Do not output JSON or code blocks. You must call the `complete` tool.
|
||||
Do not output JSON or code blocks. You must call the `submit_result` tool.
|
||||
|
||||
Correctness judgment ignores non-blocking issues (style, docs, nits).
|
||||
</output>
|
||||
|
||||
<critical>
|
||||
Every finding must be anchored to the patch and evidence-backed. Before submitting, verify each finding is not speculative. Then call `complete`.
|
||||
Every finding must be anchored to the patch and evidence-backed. Before submitting, verify each finding is not speculative. Then call `submit_result`.
|
||||
</critical>
|
||||
@@ -41,7 +41,7 @@ Each reviewer agent should:
|
||||
2. {{#if skipDiff}}Run `git diff` or `git show` to get the diff for assigned files{{else}}Use the diff hunks provided below (don't re-run git diff){{/if}}
|
||||
3. Read full file context as needed via the `read` tool
|
||||
4. Call `report_finding` for each issue found
|
||||
5. Call `complete` with verdict when done
|
||||
5. Call `submit_result` with verdict when done
|
||||
|
||||
{{#if skipDiff}}
|
||||
### Diff Previews
|
||||
|
||||
@@ -2,6 +2,21 @@
|
||||
|
||||
Launch a new agent to handle complex, multi-step tasks autonomously. Each agent type has specific capabilities and tools available to it.
|
||||
|
||||
<critical>
|
||||
This matters. Get it right.
|
||||
|
||||
Subagents have NO access to conversation history. They only see:
|
||||
1. Their agent-specific system prompt
|
||||
2. The `context` string you provide
|
||||
3. The `task` string you provide
|
||||
|
||||
Use a single Task call with multiple `tasks` entries when parallelizing. Multiple concurrent Task calls bypass coordination.
|
||||
|
||||
For code changes, have subagents write files directly with Edit/Write. Do not ask them to return patches for you to apply.
|
||||
|
||||
Agents with `output="structured"` enforce their own schema; the `output` parameter is ignored for those agents.
|
||||
</critical>
|
||||
|
||||
<agents>
|
||||
{{#list agents join="\n"}}
|
||||
<agent name="{{name}}"{{#if output}} output="structured"{{/if}}>
|
||||
@@ -9,20 +24,17 @@ Launch a new agent to handle complex, multi-step tasks autonomously. Each agent
|
||||
<tools>{{default (join tools ", ") "All tools"}}</tools>
|
||||
</agent>
|
||||
{{/list}}
|
||||
|
||||
Agents with `output="structured"` have a fixed schema enforced via frontmatter; your `output` parameter will be ignored for these agents.
|
||||
</agents>
|
||||
|
||||
<instruction>
|
||||
- Always include a short description of the task in the task parameter
|
||||
- **Plan-then-execute**: Put shared constraints in `context`, keep each task focused, specify acceptance criteria; **always provide an `output` schema unless the task explicitly does not require structured output**
|
||||
- **Minimize tool chatter**: Avoid repeating large context; use `read agent://<id>` for full logs
|
||||
- **Parallelize**: Launch multiple agents whenever possible. You MUST use a single Task call with multiple entries in the `tasks` array to do this.
|
||||
- **Isolate file scopes**: Assign each task distinct files or directories so agents don't conflict
|
||||
- **Results are intermediate data**: Agent findings provide context for YOU to perform actual work. Do not treat agent reports as "task complete" signals.
|
||||
- **Trust outputs**: Agent results should generally be trusted
|
||||
- **Clarify intent**: Tell the agent whether you expect code changes or just research (search, file reads, web fetches)
|
||||
- **Proactive use**: If an agent description says to use it proactively, do so without waiting for explicit user request
|
||||
This matters. Be thorough.
|
||||
|
||||
1. Plan before acting. Define the goal, acceptance criteria, and scope per task.
|
||||
2. Put shared constraints and decisions in `context`; keep each task request short and unambiguous.
|
||||
3. State whether each task is research-only or should modify files.
|
||||
4. Provide an `output` schema whenever possible. Do not repeat the schema in `context`; the agent does not need it there.
|
||||
5. Assign distinct file scopes per task to avoid conflicts.
|
||||
6. Trust the returned data, then verify with tools when correctness matters.
|
||||
</instruction>
|
||||
|
||||
<parameters>
|
||||
@@ -34,7 +46,7 @@ Agents with `output="structured"` have a fixed schema enforced via frontmatter;
|
||||
- `description`: Short human-readable description of what the task does
|
||||
- `args`: Object with keys matching `\{{placeholders}}` in context (always include this, even if empty)
|
||||
- `skills`: (optional) Array of skill names to preload into this task's system prompt. When set, the skills index section is omitted and the full SKILL.md contents are embedded.
|
||||
- `output`: (optional) JTD schema for structured subagent output (used by the complete tool)
|
||||
- `output`: (optional) JTD schema for structured subagent output (used by the submit_result tool). Do not duplicate this schema in `context`.
|
||||
</parameters>
|
||||
|
||||
<output>
|
||||
@@ -46,17 +58,6 @@ Returns task results for each spawned agent:
|
||||
Results are keyed by task `id` (e.g., "AuthProvider", "AuthApi").
|
||||
</output>
|
||||
|
||||
<critical>
|
||||
**Subagents have NO access to conversation history.** They only see:
|
||||
1. Their agent-specific system prompt
|
||||
2. The `context` string you provide
|
||||
3. The `task` string you provide
|
||||
|
||||
If you discussed requirements, plans, schemas, or decisions with the user, you MUST include that information in `context`. Subagents cannot see prior messages—they start fresh with only what you explicitly pass them.
|
||||
**Never call Task multiple times in parallel.** Use a single Task call with multiple entries in the `tasks` array. Parallel Task calls waste resources and bypass coordination.
|
||||
**For code changes, subagents write files directly.** Never ask an agent to "return the changes" for you to apply—they have Edit and Write tools. Their context window holds the work; asking them to report back wastes it.
|
||||
</critical>
|
||||
|
||||
<example>
|
||||
user: "Looks good, execute the plan"
|
||||
assistant: I'll execute the refactoring plan.
|
||||
@@ -80,10 +81,10 @@ assistant: Uses the Task tool:
|
||||
</example>
|
||||
|
||||
<avoid>
|
||||
- Confirmation bias: avoid yes/no exploration prompts; ask for factual discovery instead
|
||||
- Confirmation bias: ask for factual discovery instead of yes/no exploration prompts
|
||||
- Reading a specific file path → Use Read tool instead
|
||||
- Finding files by pattern/name → Use Find tool instead
|
||||
- Searching for a specific class/function definition → Use Grep tool instead
|
||||
- Searching code within 2-3 specific files → Use Read tool instead
|
||||
- Tasks unrelated to the agent descriptions above
|
||||
</avoid>
|
||||
</avoid>
|
||||
|
||||
@@ -178,8 +178,8 @@ export interface CreateAgentSessionOptions {
|
||||
|
||||
/** Output schema for structured completion (subagents) */
|
||||
outputSchema?: unknown;
|
||||
/** Whether to include the complete tool by default */
|
||||
requireCompleteTool?: boolean;
|
||||
/** Whether to include the submit_result tool by default */
|
||||
requireSubmitResultTool?: boolean;
|
||||
|
||||
/** Session manager. Default: SessionManager.create(cwd) */
|
||||
sessionManager?: SessionManager;
|
||||
@@ -746,7 +746,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
||||
skills,
|
||||
eventBus,
|
||||
outputSchema: options.outputSchema,
|
||||
requireCompleteTool: options.requireCompleteTool,
|
||||
requireSubmitResultTool: options.requireSubmitResultTool,
|
||||
getSessionFile: () => sessionManager.getSessionFile() ?? null,
|
||||
getSessionId: () => sessionManager.getSessionId?.() ?? null,
|
||||
getSessionSpawns: () => options.spawns ?? "*",
|
||||
|
||||
@@ -155,7 +155,7 @@ function resolveModelOverride(
|
||||
return {};
|
||||
}
|
||||
|
||||
function buildCompleteToolChoice(model?: Model<Api>): ToolChoice | undefined {
|
||||
function buildSubmitResultToolChoice(model?: Model<Api>): ToolChoice | undefined {
|
||||
if (!model) return undefined;
|
||||
if (
|
||||
model.api === "openai-codex-responses" ||
|
||||
@@ -163,10 +163,10 @@ function buildCompleteToolChoice(model?: Model<Api>): ToolChoice | undefined {
|
||||
model.api === "openai-completions" ||
|
||||
model.api === "azure-openai-responses"
|
||||
) {
|
||||
return { type: "function", name: "complete" };
|
||||
return { type: "function", name: "submit_result" };
|
||||
}
|
||||
if (model.api === "anthropic-messages" || model.api === "bedrock-converse-stream") {
|
||||
return { type: "tool", name: "complete" };
|
||||
return { type: "tool", name: "submit_result" };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -545,7 +545,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
||||
const abortSignal = abortController.signal;
|
||||
let activeSession: AgentSession | null = null;
|
||||
let unsubscribe: (() => void) | null = null;
|
||||
let completeCalled = false;
|
||||
let submitResultCalled = false;
|
||||
|
||||
// Accumulate usage incrementally from message_end events (no memory for streaming events)
|
||||
const accumulatedUsage = {
|
||||
@@ -921,7 +921,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
||||
const enableMCP = !options.mcpManager;
|
||||
|
||||
const completionInstruction =
|
||||
"When finished, call the complete tool exactly once. Do not output JSON or code blocks. Do not end with a plain-text final answer.";
|
||||
"When finished, call the submit_result tool exactly once. Do not output JSON or code blocks. Do not end with a plain-text final answer.";
|
||||
const worktreeNotice = worktree
|
||||
? `You will work under this working tree: ${worktree}. CRITICAL: Do not touch the original repository; only make changes inside this worktree.`
|
||||
: "";
|
||||
@@ -935,7 +935,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
||||
thinkingLevel: effectiveThinkingLevel,
|
||||
toolNames,
|
||||
outputSchema,
|
||||
requireCompleteTool: true,
|
||||
requireSubmitResultTool: true,
|
||||
contextFiles: options.contextFiles,
|
||||
skills: options.skills,
|
||||
preloadedSkills: options.preloadedSkills,
|
||||
@@ -1027,10 +1027,10 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
||||
await extensionRunner.emit({ type: "session_start" });
|
||||
}
|
||||
|
||||
const MAX_COMPLETE_RETRIES = 3;
|
||||
const MAX_SUBMIT_RESULT_RETRIES = 3;
|
||||
unsubscribe = session.subscribe(event => {
|
||||
if (event.type === "tool_execution_end" && event.toolName === "complete") {
|
||||
completeCalled = true;
|
||||
if (event.type === "tool_execution_end" && event.toolName === "submit_result") {
|
||||
submitResultCalled = true;
|
||||
}
|
||||
if (isAgentEvent(event)) {
|
||||
try {
|
||||
@@ -1046,28 +1046,28 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
||||
|
||||
await session.prompt(fullTask);
|
||||
|
||||
const reminderToolChoice = buildCompleteToolChoice(session.model);
|
||||
const reminderToolChoice = buildSubmitResultToolChoice(session.model);
|
||||
|
||||
let retryCount = 0;
|
||||
let previousTools: string[] | null = null;
|
||||
try {
|
||||
while (!completeCalled && retryCount < MAX_COMPLETE_RETRIES && !abortSignal.aborted) {
|
||||
while (!submitResultCalled && retryCount < MAX_SUBMIT_RESULT_RETRIES && !abortSignal.aborted) {
|
||||
retryCount++;
|
||||
if (!previousTools) {
|
||||
previousTools = session.getActiveToolNames();
|
||||
await session.setActiveToolsByName(["complete"]);
|
||||
await session.setActiveToolsByName(["submit_result"]);
|
||||
}
|
||||
const reminder = `<system-reminder>
|
||||
CRITICAL: You stopped without calling the complete tool. This is reminder ${retryCount} of ${MAX_COMPLETE_RETRIES}.
|
||||
CRITICAL: You stopped without calling the submit_result tool. This is reminder ${retryCount} of ${MAX_SUBMIT_RESULT_RETRIES}.
|
||||
|
||||
You MUST call the complete tool to finish your task. Options:
|
||||
1. Call complete with your result data if you have completed the task
|
||||
2. Call complete with status="aborted" and an error message if you cannot complete the task
|
||||
You MUST call the submit_result tool to finish your task. Options:
|
||||
1. Call submit_result with your result data if you have completed the task
|
||||
2. Call submit_result with status="aborted" and an error message if you cannot complete the task
|
||||
|
||||
Failure to call complete after ${MAX_COMPLETE_RETRIES} reminders will result in task failure.
|
||||
Failure to call submit_result after ${MAX_SUBMIT_RESULT_RETRIES} reminders will result in task failure.
|
||||
</system-reminder>
|
||||
|
||||
Call complete now.`;
|
||||
Call submit_result now.`;
|
||||
|
||||
await session.prompt(reminder, reminderToolChoice ? { toolChoice: reminderToolChoice } : undefined);
|
||||
}
|
||||
@@ -1137,32 +1137,32 @@ Call complete now.`;
|
||||
|
||||
// Use final output if available, otherwise accumulated output
|
||||
let rawOutput = finalOutputChunks.length > 0 ? finalOutputChunks.join("") : outputChunks.join("");
|
||||
let abortedViaComplete = false;
|
||||
const completeItems = progress.extractedToolData?.complete as
|
||||
let abortedViaSubmitResult = false;
|
||||
const submitResultItems = progress.extractedToolData?.submit_result as
|
||||
| Array<{ data?: unknown; status?: "success" | "aborted"; error?: string }>
|
||||
| undefined;
|
||||
const reportFindings = progress.extractedToolData?.report_finding as ReviewFinding[] | undefined;
|
||||
const hasComplete = Array.isArray(completeItems) && completeItems.length > 0;
|
||||
if (hasComplete) {
|
||||
const lastComplete = completeItems[completeItems.length - 1];
|
||||
if (lastComplete?.status === "aborted") {
|
||||
// Agent explicitly aborted via complete tool - clean exit with error info
|
||||
abortedViaComplete = true;
|
||||
const hasSubmitResult = Array.isArray(submitResultItems) && submitResultItems.length > 0;
|
||||
if (hasSubmitResult) {
|
||||
const lastSubmitResult = submitResultItems[submitResultItems.length - 1];
|
||||
if (lastSubmitResult?.status === "aborted") {
|
||||
// Agent explicitly aborted via submit_result tool - clean exit with error info
|
||||
abortedViaSubmitResult = true;
|
||||
exitCode = 0;
|
||||
stderr = lastComplete.error || "Subagent aborted task";
|
||||
stderr = lastSubmitResult.error || "Subagent aborted task";
|
||||
try {
|
||||
rawOutput = JSON.stringify({ aborted: true, error: lastComplete.error }, null, 2);
|
||||
rawOutput = JSON.stringify({ aborted: true, error: lastSubmitResult.error }, null, 2);
|
||||
} catch {
|
||||
rawOutput = `{"aborted":true,"error":"${lastComplete.error || "Unknown error"}"}`;
|
||||
rawOutput = `{"aborted":true,"error":"${lastSubmitResult.error || "Unknown error"}"}`;
|
||||
}
|
||||
} else {
|
||||
// Normal successful completion
|
||||
const completeData = normalizeCompleteData(lastComplete?.data ?? null, reportFindings);
|
||||
const completeData = normalizeCompleteData(lastSubmitResult?.data ?? null, reportFindings);
|
||||
try {
|
||||
rawOutput = JSON.stringify(completeData, null, 2) ?? "null";
|
||||
} catch (err) {
|
||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||
rawOutput = `{"error":"Failed to serialize complete data: ${errorMessage}"}`;
|
||||
rawOutput = `{"error":"Failed to serialize submit_result data: ${errorMessage}"}`;
|
||||
}
|
||||
exitCode = 0;
|
||||
stderr = "";
|
||||
@@ -1186,7 +1186,7 @@ Call complete now.`;
|
||||
exitCode = 0;
|
||||
stderr = "";
|
||||
} else {
|
||||
const warning = "SYSTEM WARNING: Subagent exited without calling complete tool after 3 reminders.";
|
||||
const warning = "SYSTEM WARNING: Subagent exited without calling submit_result tool after 3 reminders.";
|
||||
rawOutput = rawOutput ? `${warning}\n\n${rawOutput}` : warning;
|
||||
}
|
||||
}
|
||||
@@ -1210,7 +1210,7 @@ Call complete now.`;
|
||||
}
|
||||
|
||||
// Update final progress
|
||||
const wasAborted = abortedViaComplete || (!hasComplete && (done.aborted || signal?.aborted || false));
|
||||
const wasAborted = abortedViaSubmitResult || (!hasSubmitResult && (done.aborted || signal?.aborted || false));
|
||||
progress.status = wasAborted ? "aborted" : exitCode === 0 ? "completed" : "failed";
|
||||
scheduleProgress(true);
|
||||
|
||||
|
||||
@@ -77,12 +77,12 @@ function formatJsonScalar(value: unknown, theme: Theme): string {
|
||||
return "";
|
||||
}
|
||||
|
||||
const MISSING_COMPLETE_WARNING_PREFIX = "SYSTEM WARNING: Subagent exited without calling complete tool";
|
||||
const MISSING_SUBMIT_RESULT_WARNING_PREFIX = "SYSTEM WARNING: Subagent exited without calling submit_result tool";
|
||||
|
||||
function extractMissingCompleteWarning(output: string): { warning?: string; rest: string } {
|
||||
function extractMissingSubmitResultWarning(output: string): { warning?: string; rest: string } {
|
||||
const lines = output.split("\n");
|
||||
const firstLine = lines[0]?.trim() ?? "";
|
||||
if (!firstLine.startsWith(MISSING_COMPLETE_WARNING_PREFIX)) {
|
||||
if (!firstLine.startsWith(MISSING_SUBMIT_RESULT_WARNING_PREFIX)) {
|
||||
return { rest: output };
|
||||
}
|
||||
const rest = lines
|
||||
@@ -574,9 +574,9 @@ function renderAgentProgress(
|
||||
|
||||
// Render extracted tool data inline (e.g., review findings)
|
||||
if (progress.extractedToolData) {
|
||||
// For completed tasks, check for review verdict from complete tool
|
||||
// For completed tasks, check for review verdict from submit_result tool
|
||||
if (progress.status === "completed") {
|
||||
const completeData = progress.extractedToolData.complete as Array<{ data: unknown }> | undefined;
|
||||
const completeData = progress.extractedToolData.submit_result as Array<{ data: unknown }> | undefined;
|
||||
const reportFindingData = progress.extractedToolData.report_finding as ReportFindingDetails[] | undefined;
|
||||
const reviewData = completeData
|
||||
?.map(c => c.data as SubmitReviewDetails)
|
||||
@@ -732,7 +732,8 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
||||
const prefix = isLast ? theme.fg("dim", theme.tree.last) : theme.fg("dim", theme.tree.branch);
|
||||
const continuePrefix = isLast ? " " : `${theme.fg("dim", theme.tree.vertical)} `;
|
||||
|
||||
const { warning: missingCompleteWarning, rest: outputWithoutWarning } = extractMissingCompleteWarning(result.output);
|
||||
const { warning: missingCompleteWarning, rest: outputWithoutWarning } =
|
||||
extractMissingSubmitResultWarning(result.output);
|
||||
const aborted = result.aborted ?? false;
|
||||
const success = !aborted && result.exitCode === 0;
|
||||
const needsWarning = Boolean(missingCompleteWarning) && success;
|
||||
@@ -766,11 +767,11 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
||||
lines.push(statusLine);
|
||||
lines.push(...renderArgsSection(result.args, continuePrefix, expanded, theme));
|
||||
|
||||
// Check for review result (complete with review schema + report_finding)
|
||||
const completeData = result.extractedToolData?.complete as Array<{ data: unknown }> | undefined;
|
||||
// Check for review result (submit_result with review schema + report_finding)
|
||||
const completeData = result.extractedToolData?.submit_result as Array<{ data: unknown }> | undefined;
|
||||
const reportFindingData = result.extractedToolData?.report_finding as ReportFindingDetails[] | undefined;
|
||||
|
||||
// Extract review verdict from complete tool's data field if it matches SubmitReviewDetails
|
||||
// Extract review verdict from submit_result tool's data field if it matches SubmitReviewDetails
|
||||
const reviewData = completeData
|
||||
?.map(c => c.data as SubmitReviewDetails)
|
||||
.filter(d => d && typeof d === "object" && "overall_correctness" in d);
|
||||
@@ -787,7 +788,7 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
||||
const hasCompleteData = completeData && completeData.length > 0;
|
||||
const message = hasCompleteData
|
||||
? "Review verdict missing expected fields"
|
||||
: "Review incomplete (complete not called)";
|
||||
: "Review incomplete (submit_result not called)";
|
||||
lines.push(`${continuePrefix}${theme.fg("warning", theme.status.warning)} ${theme.fg("dim", message)}`);
|
||||
lines.push(`${continuePrefix}${formatFindingSummary(reportFindingData, theme)}`);
|
||||
lines.push(...renderFindings(reportFindingData, continuePrefix, expanded, theme));
|
||||
@@ -799,7 +800,7 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
||||
if (result.extractedToolData) {
|
||||
for (const [toolName, dataArray] of Object.entries(result.extractedToolData)) {
|
||||
// Skip review tools - handled above
|
||||
if (toolName === "complete" || toolName === "report_finding") continue;
|
||||
if (toolName === "submit_result" || toolName === "report_finding") continue;
|
||||
|
||||
const handler = subprocessToolRegistry.getHandler(toolName);
|
||||
if (handler?.renderFinal && (dataArray as unknown[]).length > 0) {
|
||||
|
||||
@@ -19,7 +19,7 @@ import { WebSearchTool } from "../web/search";
|
||||
import { AskTool } from "./ask";
|
||||
import { BashTool } from "./bash";
|
||||
import { CalculatorTool } from "./calculator";
|
||||
import { CompleteTool } from "./complete";
|
||||
import { SubmitResultTool } from "./submit-result";
|
||||
import { ExitPlanModeTool } from "./exit-plan-mode";
|
||||
import { FetchTool } from "./fetch";
|
||||
import { FindTool } from "./find";
|
||||
@@ -72,7 +72,7 @@ export {
|
||||
export { AskTool, type AskToolDetails } from "./ask";
|
||||
export { BashTool, type BashToolDetails, type BashToolOptions } from "./bash";
|
||||
export { CalculatorTool, type CalculatorToolDetails } from "./calculator";
|
||||
export { CompleteTool } from "./complete";
|
||||
export { SubmitResultTool } from "./submit-result";
|
||||
export { type ExitPlanModeDetails, ExitPlanModeTool } from "./exit-plan-mode";
|
||||
export { FetchTool, type FetchToolDetails } from "./fetch";
|
||||
export { type FindOperations, FindTool, type FindToolDetails, type FindToolOptions } from "./find";
|
||||
@@ -126,8 +126,8 @@ export interface ToolSession {
|
||||
eventBus?: EventBus;
|
||||
/** Output schema for structured completion (subagents) */
|
||||
outputSchema?: unknown;
|
||||
/** Whether to include the complete tool by default */
|
||||
requireCompleteTool?: boolean;
|
||||
/** Whether to include the submit_result tool by default */
|
||||
requireSubmitResultTool?: boolean;
|
||||
/** Get session file */
|
||||
getSessionFile: () => string | null;
|
||||
/** Get session ID */
|
||||
@@ -198,7 +198,7 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
||||
};
|
||||
|
||||
export const HIDDEN_TOOLS: Record<string, ToolFactory> = {
|
||||
complete: s => new CompleteTool(s),
|
||||
submit_result: s => new SubmitResultTool(s),
|
||||
report_finding: () => reportFindingTool,
|
||||
exit_plan_mode: s => new ExitPlanModeTool(s),
|
||||
};
|
||||
@@ -240,7 +240,7 @@ function getPythonModeFromEnv(): PythonToolMode | null {
|
||||
*/
|
||||
export async function createTools(session: ToolSession, toolNames?: string[]): Promise<Tool[]> {
|
||||
time("createTools:start");
|
||||
const includeComplete = session.requireCompleteTool === true;
|
||||
const includeSubmitResult = session.requireSubmitResultTool === true;
|
||||
const enableLsp = session.enableLsp ?? true;
|
||||
const requestedTools = toolNames && toolNames.length > 0 ? [...new Set(toolNames)] : undefined;
|
||||
if (requestedTools && !requestedTools.includes("exit_plan_mode")) {
|
||||
@@ -296,8 +296,8 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
||||
if (name === "python") return allowPython;
|
||||
return true;
|
||||
};
|
||||
if (includeComplete && requestedTools && !requestedTools.includes("complete")) {
|
||||
requestedTools.push("complete");
|
||||
if (includeSubmitResult && requestedTools && !requestedTools.includes("submit_result")) {
|
||||
requestedTools.push("submit_result");
|
||||
}
|
||||
|
||||
const filteredRequestedTools = requestedTools?.filter(name => name in allTools && isToolAllowed(name));
|
||||
@@ -307,7 +307,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
||||
? filteredRequestedTools.map(name => [name, allTools[name]] as const)
|
||||
: [
|
||||
...Object.entries(BUILTIN_TOOLS).filter(([name]) => isToolAllowed(name)),
|
||||
...(includeComplete ? ([["complete", HIDDEN_TOOLS.complete]] as const) : []),
|
||||
...(includeSubmitResult ? ([["submit_result", HIDDEN_TOOLS.submit_result]] as const) : []),
|
||||
...([["exit_plan_mode", HIDDEN_TOOLS.exit_plan_mode]] as const),
|
||||
];
|
||||
time("createTools:beforeFactories");
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* Used by the reviewer agent to report findings in a structured way.
|
||||
* Hidden by default - only enabled when explicitly listed in agent's tools.
|
||||
* Reviewers finish via `complete` tool with SubmitReviewDetails schema.
|
||||
* Reviewers finish via `submit_result` tool with SubmitReviewDetails schema.
|
||||
*/
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Subprocess tool handlers - registered for extraction/rendering in task tool
|
||||
@@ -85,7 +85,7 @@ interface ReportFindingDetails {
|
||||
export const reportFindingTool: AgentTool<typeof ReportFindingParams, ReportFindingDetails, Theme> = {
|
||||
name: "report_finding",
|
||||
label: "Report Finding",
|
||||
description: "Report a code review finding. Use this for each issue found. Call complete when done.",
|
||||
description: "Report a code review finding. Use this for each issue found. Call submit_result when done.",
|
||||
parameters: ReportFindingParams,
|
||||
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
||||
const { title, body, priority, confidence, file_path, line_start, line_end } = params;
|
||||
@@ -140,7 +140,7 @@ export const reportFindingTool: AgentTool<typeof ReportFindingParams, ReportFind
|
||||
},
|
||||
};
|
||||
|
||||
/** SubmitReviewDetails - used for rendering review results from complete tool */
|
||||
/** SubmitReviewDetails - used for rendering review results from submit_result tool */
|
||||
export interface SubmitReviewDetails {
|
||||
overall_correctness: "correct" | "incorrect";
|
||||
explanation: string;
|
||||
|
||||
+10
-10
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Complete tool for structured subagent output.
|
||||
* Submit result tool for structured subagent output.
|
||||
*
|
||||
* Subagents must call this tool to finish and return structured JSON output.
|
||||
*/
|
||||
@@ -12,7 +12,7 @@ import { subprocessToolRegistry } from "../task/subprocess-tool-registry";
|
||||
import type { ToolSession } from ".";
|
||||
import { jtdToJsonSchema } from "./jtd-to-json-schema";
|
||||
|
||||
export interface CompleteDetails {
|
||||
export interface SubmitResultDetails {
|
||||
data: unknown;
|
||||
status: "success" | "aborted";
|
||||
error?: string;
|
||||
@@ -52,9 +52,9 @@ function formatAjvErrors(errors: ErrorObject[] | null | undefined): string {
|
||||
.join("; ");
|
||||
}
|
||||
|
||||
export class CompleteTool implements AgentTool<TObject, CompleteDetails> {
|
||||
public readonly name = "complete";
|
||||
public readonly label = "Complete";
|
||||
export class SubmitResultTool implements AgentTool<TObject, SubmitResultDetails> {
|
||||
public readonly name = "submit_result";
|
||||
public readonly label = "Submit Result";
|
||||
public readonly description =
|
||||
"Finish the task with structured JSON output. Call exactly once at the end of the task.\n\n" +
|
||||
"If you cannot complete the task, call with status='aborted' and an error message.";
|
||||
@@ -106,9 +106,9 @@ export class CompleteTool implements AgentTool<TObject, CompleteDetails> {
|
||||
_toolCallId: string,
|
||||
params: Static<TObject>,
|
||||
_signal?: AbortSignal,
|
||||
_onUpdate?: AgentToolUpdateCallback<CompleteDetails>,
|
||||
_onUpdate?: AgentToolUpdateCallback<SubmitResultDetails>,
|
||||
_context?: AgentToolContext,
|
||||
): Promise<AgentToolResult<CompleteDetails>> {
|
||||
): Promise<AgentToolResult<SubmitResultDetails>> {
|
||||
const status = (params.status ?? "success") as "success" | "aborted";
|
||||
|
||||
// Skip validation when aborting - data is optional for aborts
|
||||
@@ -125,7 +125,7 @@ export class CompleteTool implements AgentTool<TObject, CompleteDetails> {
|
||||
}
|
||||
|
||||
const responseText =
|
||||
status === "aborted" ? `Task aborted: ${params.error || "No reason provided"}` : "Completion recorded.";
|
||||
status === "aborted" ? `Task aborted: ${params.error || "No reason provided"}` : "Result submitted.";
|
||||
|
||||
return {
|
||||
content: [{ type: "text", text: responseText }],
|
||||
@@ -135,7 +135,7 @@ export class CompleteTool implements AgentTool<TObject, CompleteDetails> {
|
||||
}
|
||||
|
||||
// Register subprocess tool handler for extraction + termination.
|
||||
subprocessToolRegistry.register<CompleteDetails>("complete", {
|
||||
extractData: event => event.result?.details as CompleteDetails | undefined,
|
||||
subprocessToolRegistry.register<SubmitResultDetails>("submit_result", {
|
||||
extractData: event => event.result?.details as SubmitResultDetails | undefined,
|
||||
shouldTerminate: () => true,
|
||||
});
|
||||
@@ -112,12 +112,12 @@ describe("createTools", () => {
|
||||
expect(names).toEqual(["report_finding", "exit_plan_mode"]);
|
||||
});
|
||||
|
||||
it("includes complete tool when required", async () => {
|
||||
const session = createTestSession({ requireCompleteTool: true });
|
||||
it("includes submit_result tool when required", async () => {
|
||||
const session = createTestSession({ requireSubmitResultTool: true });
|
||||
const tools = await createTools(session);
|
||||
const names = tools.map(t => t.name);
|
||||
|
||||
expect(names).toContain("complete");
|
||||
expect(names).toContain("submit_result");
|
||||
});
|
||||
|
||||
it("excludes ask tool when hasUI is false", async () => {
|
||||
@@ -166,6 +166,6 @@ describe("createTools", () => {
|
||||
});
|
||||
|
||||
it("HIDDEN_TOOLS contains review tools", () => {
|
||||
expect(Object.keys(HIDDEN_TOOLS).sort()).toEqual(["complete", "exit_plan_mode", "report_finding"]);
|
||||
expect(Object.keys(HIDDEN_TOOLS).sort()).toEqual(["exit_plan_mode", "report_finding", "submit_result"]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user