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:
can1357
2026-01-28 03:28:03 +01:00
parent 70a2c30177
commit a4bfb5795c
15 changed files with 114 additions and 109 deletions
+3 -3
View File
@@ -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>
```
+3
View File
@@ -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
+1 -1
View File
@@ -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
+26 -25
View File
@@ -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>
+3 -3
View File
@@ -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 ?? "*",
+33 -33
View File
@@ -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);
+12 -11
View File
@@ -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) {
+9 -9
View File
@@ -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 -3
View File
@@ -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;
@@ -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"]);
});
});