Closes the gap to Claude Code across four upgrade rounds (all verified green: typecheck, 388 tests, build 313 KB). Sub-agent orchestration: - Resumable sub-agents (send_message) + MaxIterations "continue" path - Configurable multi-depth sub-agent nesting - Parallel fan-out for pure agent/agent__* batches, each writable delegation in its own throwaway git worktree (no file collisions) - Named specialist fleet: explore, code-reviewer, planner, debugger, test-writer (read-only types skip worktree isolation) - Named teammates: agent tool `name` arg + list_teammates + send_message by name (synchronous, addressable handle — not true async background) Plan mode: - True in-turn pause/resume on approval (no synthetic proceed turn) - exit_plan_mode tool + side-by-side diff rendering (/diff) Persistent memory: - Typed file-per-fact memory under config dir, index folded into the system prompt (bounded), memory + memory_write tools, /memory command Toolset + UI: - multi_edit, notebook_edit, web_search/web_fetch, workflow orchestration primitive (agent/parallel/pipeline/phase/log + worktree isolation + best-effort structured output with recursive schema validation) - Structured task system (task_create/list/get/update with dependency graph + ownership) replacing flat todo_write - Cron/scheduled tasks (cron_create/list/delete, schedule_wakeup) with durable persistence and idle-gated ticking - Interactive worktree session (enter_worktree/exit_worktree) with a live-cwd UI switch that survives /model changes - AskUserQuestion tool + QuestionPrompt UI - Permission rule granularity + user/project settings merge - Color diffs, sub-agent activity panel, /undo, dashboard refinements Co-Authored-By: Claude <noreply@anthropic.com>
85 lines
3.7 KiB
TypeScript
85 lines
3.7 KiB
TypeScript
export function formatCallLabel(name: string, args: unknown): string {
|
|
const a = (args && typeof args === "object" ? args : {}) as Record<string, unknown>;
|
|
switch (name) {
|
|
case "read_file":
|
|
return `Read(${a.path ?? ""})`;
|
|
case "list_files":
|
|
return `List(${a.pattern ?? ""})`;
|
|
case "grep":
|
|
return `Grep(${a.pattern ?? ""})`;
|
|
case "web_search":
|
|
return `WebSearch(${a.query ?? ""})`;
|
|
case "web_fetch":
|
|
return `WebFetch(${a.url ?? ""})`;
|
|
case "agent":
|
|
return `Agent(${a.description ?? ""})`;
|
|
case "ask_user_question":
|
|
return `Ask(${(a.questions as any[])?.map((q) => q.header).join("/") ?? ""})`;
|
|
case "git_status":
|
|
case "git_commit":
|
|
return `Git(${a.operation ?? ""})`;
|
|
case "write_file":
|
|
return `Write(${a.path ?? ""})`;
|
|
case "edit_file":
|
|
return `Edit(${a.path ?? ""})`;
|
|
case "bash":
|
|
return `Bash(${a.command ?? ""})`;
|
|
default:
|
|
return `${name}(${JSON.stringify(args)})`;
|
|
}
|
|
}
|
|
|
|
export function summarizeToolResult(toolName: string, result: unknown): string {
|
|
if (result && typeof result === "object" && "error" in result) {
|
|
return String((result as { error: unknown }).error);
|
|
}
|
|
if (!result || typeof result !== "object") {
|
|
return "done";
|
|
}
|
|
const r = result as Record<string, unknown>;
|
|
switch (toolName) {
|
|
case "read_file":
|
|
if (r.image === true) {
|
|
const kb = typeof r.bytes === "number" ? `${(r.bytes / 1024).toFixed(0)} KB` : "";
|
|
return `Read image${r.mimeType ? ` (${r.mimeType}${kb ? `, ${kb}` : ""})` : ""}`;
|
|
}
|
|
if (r.truncated === true && typeof r.nextOffset === "number" && typeof r.totalLines === "number") {
|
|
return `Read ${r.nextOffset - 1} of ${r.totalLines} lines (truncated, next at offset ${r.nextOffset})`;
|
|
}
|
|
return typeof r.totalLines === "number" ? `Read ${r.totalLines} lines` : "Read file";
|
|
case "list_files":
|
|
return Array.isArray(r.matches) ? `Found ${r.matches.length} file(s)` : "Listed files";
|
|
case "grep":
|
|
return Array.isArray(r.matches) ? `Found ${r.matches.length} match(es)` : "Searched";
|
|
case "web_search":
|
|
return Array.isArray(r.results) ? `Found ${r.results.length} result(s)` : "Searched web";
|
|
case "web_fetch":
|
|
return typeof r.status === "number" ? `Fetched (HTTP ${r.status})` : "Fetched page";
|
|
case "agent":
|
|
return typeof r.result === "string" ? `Sub-agent finished (${r.result.length} chars)` : "Sub-agent finished";
|
|
case "ask_user_question": {
|
|
const ans = Array.isArray(r.answers) ? (r.answers as { question?: string; selected?: string[]; custom?: string }[]) : [];
|
|
const parts = ans.map((a) => {
|
|
const sel = a.custom ? `"${a.custom}"` : (a.selected ?? []).join(", ");
|
|
return `${a.question ?? "??"}: ${sel || "(none)"}`;
|
|
});
|
|
return parts.length ? `Answered — ${parts.join("; ")}` : "No answer";
|
|
}
|
|
case "git_status":
|
|
return typeof r.output === "string" ? `${r.output.split("\n").filter(Boolean).length} line(s)` : "Ran git query";
|
|
case "git_commit":
|
|
if (Array.isArray(r.staged)) return `Staged ${r.staged.length} path(s)`;
|
|
if (typeof r.created === "string") return `Created branch "${r.created}"`;
|
|
if (typeof r.switchedTo === "string") return `Switched to "${r.switchedTo}"`;
|
|
return "Done";
|
|
case "write_file":
|
|
return typeof r.bytesWritten === "number" ? `Wrote ${r.bytesWritten} bytes` : "Wrote file";
|
|
case "edit_file":
|
|
return typeof r.replacements === "number" ? `Applied ${r.replacements} replacement(s)` : "Edited file";
|
|
case "bash":
|
|
return typeof r.exitCode === "number" ? `Exit code ${r.exitCode}` : "Ran command";
|
|
default:
|
|
return "done";
|
|
}
|
|
}
|