@kolisachint/hoocode-agent 0.4.10 → 0.4.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/dist/core/lifeguard.d.ts +1 -0
- package/dist/core/lifeguard.d.ts.map +1 -1
- package/dist/core/lifeguard.js +6 -0
- package/dist/core/lifeguard.js.map +1 -1
- package/dist/core/subagent-pool.d.ts.map +1 -1
- package/dist/core/subagent-pool.js +5 -4
- package/dist/core/subagent-pool.js.map +1 -1
- package/dist/core/task-store.d.ts +3 -2
- package/dist/core/task-store.d.ts.map +1 -1
- package/dist/core/task-store.js +3 -2
- package/dist/core/task-store.js.map +1 -1
- package/dist/core/tools/subagent.d.ts.map +1 -1
- package/dist/core/tools/subagent.js +15 -5
- package/dist/core/tools/subagent.js.map +1 -1
- package/dist/modes/interactive/components/task-panel.d.ts +4 -3
- package/dist/modes/interactive/components/task-panel.d.ts.map +1 -1
- package/dist/modes/interactive/components/task-panel.js +52 -21
- package/dist/modes/interactive/components/task-panel.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +5 -3
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-store.js","sourceRoot":"","sources":["../../src/core/task-store.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA8BH,MAAM,SAAS;IACN,KAAK,GAAW,EAAE,CAAC;IACnB,MAAM,GAAG,CAAC,CAAC;IACF,SAAS,GAAG,IAAI,GAAG,EAAY,CAAC;IAEjD,MAAM,CAAC,KAAa,EAAE,OAAO,GAAsB,EAAE,EAAQ;QAC5D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,IAAI,GAAS;YAClB,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE;YACjB,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,iBAAiB;YACxC,MAAM,EAAE,SAAS;YACjB,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,SAAS,EAAE,GAAG;YACd,SAAS,EAAE,GAAG;SACd,CAAC;QACF,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC;IAAA,CACZ;IAED,MAAM,CAAC,EAAU,EAAE,KAAgB,EAAQ;QAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACjD,IAAI,CAAC,IAAI;YAAE,OAAO;QAClB,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS;YAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;QACxD,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;QAC3D,IAAI,KAAK,CAAC,YAAY,KAAK,SAAS;YAAE,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC;QAC7E,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS;YAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;QACxD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,EAAE,CAAC;IAAA,CACZ;IAED,MAAM,CAAC,EAAU,EAAQ;QACxB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACrD,IAAI,GAAG,KAAK,CAAC,CAAC;YAAE,OAAO;QACvB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC1B,IAAI,CAAC,IAAI,EAAE,CAAC;IAAA,CACZ;IAED
|
|
1
|
+
{"version":3,"file":"task-store.js","sourceRoot":"","sources":["../../src/core/task-store.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA8BH,MAAM,SAAS;IACN,KAAK,GAAW,EAAE,CAAC;IACnB,MAAM,GAAG,CAAC,CAAC;IACF,SAAS,GAAG,IAAI,GAAG,EAAY,CAAC;IAEjD,MAAM,CAAC,KAAa,EAAE,OAAO,GAAsB,EAAE,EAAQ;QAC5D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,IAAI,GAAS;YAClB,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE;YACjB,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,iBAAiB;YACxC,MAAM,EAAE,SAAS;YACjB,YAAY,EAAE,OAAO,CAAC,YAAY;YAClC,SAAS,EAAE,GAAG;YACd,SAAS,EAAE,GAAG;SACd,CAAC;QACF,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC;IAAA,CACZ;IAED,MAAM,CAAC,EAAU,EAAE,KAAgB,EAAQ;QAC1C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACjD,IAAI,CAAC,IAAI;YAAE,OAAO;QAClB,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS;YAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;QACxD,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;QAC3D,IAAI,KAAK,CAAC,YAAY,KAAK,SAAS;YAAE,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC;QAC7E,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS;YAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;QACxD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,EAAE,CAAC;IAAA,CACZ;IAED,MAAM,CAAC,EAAU,EAAQ;QACxB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACrD,IAAI,GAAG,KAAK,CAAC,CAAC;YAAE,OAAO;QACvB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC1B,IAAI,CAAC,IAAI,EAAE,CAAC;IAAA,CACZ;IAED;;;;;;OAMG;IACH,cAAc,GAAS;QACtB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;QACjC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,MAAM,IAAI,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC;QACpF,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM;YAAE,IAAI,CAAC,IAAI,EAAE,CAAC;IAAA,CAC9C;IAED,IAAI,GAAoB;QACvB,OAAO,IAAI,CAAC,KAAK,CAAC;IAAA,CAClB;IAED,SAAS,CAAC,QAAkB,EAAc;QACzC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC7B,OAAO,GAAG,EAAE,CAAC;YACZ,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAAA,CAChC,CAAC;IAAA,CACF;IAED,yDAAyD;IACzD,KAAK,GAAS;QACb,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;QAChB,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;QAChB,IAAI,CAAC,IAAI,EAAE,CAAC;IAAA,CACZ;IAEO,IAAI,GAAS;QACpB,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACvC,QAAQ,EAAE,CAAC;QACZ,CAAC;IAAA,CACD;CACD;AAED,uCAAuC;AACvC,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,SAAS,EAAE,CAAC","sourcesContent":["/**\n * Minimal in-process task store.\n *\n * Tracks short-lived tasks (e.g. subagent delegations) so the TUI task panel can\n * display active work. It is a process-level singleton because the tool that\n * creates tasks and the footer that renders them live in the same process and\n * there is no cross-process boundary to cross.\n */\n\nexport type TaskStatus = \"pending\" | \"in_progress\" | \"done\" | \"failed\";\n\nexport interface Task {\n\treadonly id: number;\n\ttitle: string;\n\tstatus: TaskStatus;\n\t/** Subagent mode when this task is owned by a subagent (e.g. \"explore\"). */\n\tsubagentMode?: string;\n\treadonly createdAt: number;\n\tupdatedAt: number;\n\t/** Token and cost usage attributed to this task (e.g. from a subagent session). */\n\tusage?: {\n\t\tinput: number;\n\t\toutput: number;\n\t\tcacheRead: number;\n\t\tcacheWrite: number;\n\t\tcost: number;\n\t};\n}\n\nexport interface CreateTaskOptions {\n\tsubagentMode?: string;\n}\n\nexport type TaskPatch = Partial<Pick<Task, \"title\" | \"status\" | \"subagentMode\" | \"usage\">>;\n\ntype Listener = () => void;\n\nclass TaskStore {\n\tprivate tasks: Task[] = [];\n\tprivate nextId = 1;\n\tprivate readonly listeners = new Set<Listener>();\n\n\tcreate(title: string, options: CreateTaskOptions = {}): Task {\n\t\tconst now = Date.now();\n\t\tconst task: Task = {\n\t\t\tid: this.nextId++,\n\t\t\ttitle: title.trim() || \"(untitled task)\",\n\t\t\tstatus: \"pending\",\n\t\t\tsubagentMode: options.subagentMode,\n\t\t\tcreatedAt: now,\n\t\t\tupdatedAt: now,\n\t\t};\n\t\tthis.tasks.push(task);\n\t\tthis.emit();\n\t\treturn task;\n\t}\n\n\tupdate(id: number, patch: TaskPatch): void {\n\t\tconst task = this.tasks.find((t) => t.id === id);\n\t\tif (!task) return;\n\t\tif (patch.title !== undefined) task.title = patch.title;\n\t\tif (patch.status !== undefined) task.status = patch.status;\n\t\tif (patch.subagentMode !== undefined) task.subagentMode = patch.subagentMode;\n\t\tif (patch.usage !== undefined) task.usage = patch.usage;\n\t\ttask.updatedAt = Date.now();\n\t\tthis.emit();\n\t}\n\n\tremove(id: number): void {\n\t\tconst idx = this.tasks.findIndex((t) => t.id === id);\n\t\tif (idx === -1) return;\n\t\tthis.tasks.splice(idx, 1);\n\t\tthis.emit();\n\t}\n\n\t/**\n\t * Remove all finished tasks (done or failed), keeping pending/in_progress ones.\n\t *\n\t * Called when a new user message arrives: finished subagent tasks stay visible\n\t * (with their final status) for the whole turn and only retire when the user\n\t * starts the next turn, so their outcome remains glanceable until then.\n\t */\n\tretireFinished(): void {\n\t\tconst before = this.tasks.length;\n\t\tthis.tasks = this.tasks.filter((t) => t.status !== \"done\" && t.status !== \"failed\");\n\t\tif (this.tasks.length !== before) this.emit();\n\t}\n\n\tlist(): readonly Task[] {\n\t\treturn this.tasks;\n\t}\n\n\tsubscribe(listener: Listener): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => {\n\t\t\tthis.listeners.delete(listener);\n\t\t};\n\t}\n\n\t/** Clear all tasks. Intended for test isolation only. */\n\tclear(): void {\n\t\tthis.tasks = [];\n\t\tthis.nextId = 1;\n\t\tthis.emit();\n\t}\n\n\tprivate emit(): void {\n\t\tfor (const listener of this.listeners) {\n\t\t\tlistener();\n\t\t}\n\t}\n}\n\n/** Shared, process-wide task store. */\nexport const taskStore = new TaskStore();\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subagent.d.ts","sourceRoot":"","sources":["../../../src/core/tools/subagent.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;0EAC0E;AAC1E,eAAO,MAAM,oBAAoB,82DA0BkD,CAAC;AAKpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAmChE,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,YAAY,CAAC;IACnB,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAQD,sDAAsD;AACtD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAG3D;AAOD,oFAAoF;AACpF,wBAAgB,4BAA4B,IAAI,cAAc,CAuF7D","sourcesContent":["/**\n * Subagent tool: delegate a focused task to a fresh, isolated agent loop.\n *\n * The tool registers a task in the shared task store (visible in the task panel),\n * runs the subagent to completion, and returns ONLY the subagent's final\n * answer. It is an optional, opt-in tool (enabled via --subagent or the\n * `enableSubagent` setting); see buildSessionOptions in main.ts.\n */\n\n/** System prompt appendix for the main session when subagent tooling is enabled.\n * Instructs the parent agent on when and how to delegate effectively. */\nexport const SUBAGENT_MAIN_PROMPT = `You have access to the **subagent** tool. Use it to delegate self-contained tasks to isolated subagent loops that run with their own context and return only their final answer.\n\nAvailable subagent modes:\n- explore: read-only investigation (read, grep, find, ls, bash).\n- edit: make a focused code change (read, edit, write, grep, find, ls, bash).\n- test: run tests and report (read, bash, grep, find, ls).\n- fix: diagnose and fix a failure (read, edit, write, bash, grep, find, ls).\n- review: read-only code review (read, grep, find, ls, bash).\n- doc: write documentation, README, or comments (read, write, edit, grep, find, ls, bash).\n\nWhen to delegate:\n1. The work is self-contained and you only need the final result, not intermediate steps.\n2. You want to investigate or edit something in parallel without losing your current context or reasoning chain.\n3. The task is a discrete unit (explore one module, run one test file, review one PR, fix one isolated bug).\n4. You need to run a long command or test suite and wait for its output without blocking your own reasoning.\n\nGuidelines:\n- Make every task specific and self-contained. The subagent cannot see this conversation.\n- Pass all necessary context (files, constraints, prior findings) via the \\`context\\` parameter.\n- Do NOT delegate tasks that require tight back-and-forth with your current reasoning.\n- Do NOT delegate edits to files you are actively reasoning about.\n- The subagent returns ONLY its final answer. Intermediate reasoning, tool calls, and output are hidden from you.\n\nDispatch evaluator:\n- The dispatch evaluator determines if a subagent is needed. Do not spawn subagents directly unless the user explicitly requests it.\n- For simple single-file changes (<50 lines, read-only or trivial edit), handle them inline.\n- Use force=true to bypass evaluation when you are certain a subagent is required.`;\n\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { type Static, Type } from \"typebox\";\nimport { type AgentType, DispatchEvaluator } from \"../dispatch-evaluator.js\";\nimport type { ToolDefinition } from \"../extensions/types.js\";\nimport { defineTool } from \"../extensions/types.js\";\nimport { runSubagent, type SubagentMode } from \"../subagent.js\";\nimport { taskStore } from \"../task-store.js\";\n\nconst subagentParams = Type.Object({\n\ttask: Type.String({\n\t\tdescription:\n\t\t\t\"The task to delegate. Make it specific and self-contained; the subagent cannot see this conversation.\",\n\t}),\n\tcontext: Type.String({\n\t\tdescription:\n\t\t\t'Context distilled from the conversation the subagent needs (files, constraints, prior findings). Pass \"\" if none.',\n\t}),\n\tmode: Type.Union(\n\t\t[\n\t\t\tType.Literal(\"explore\"),\n\t\t\tType.Literal(\"edit\"),\n\t\t\tType.Literal(\"test\"),\n\t\t\tType.Literal(\"fix\"),\n\t\t\tType.Literal(\"review\"),\n\t\t\tType.Literal(\"doc\"),\n\t\t],\n\t\t{\n\t\t\tdescription:\n\t\t\t\t\"explore: read-only investigation. edit: make a focused code change. test: run tests and report. fix: diagnose and fix a failure. review: read-only code review. doc: write documentation.\",\n\t\t},\n\t),\n\tforce: Type.Boolean({\n\t\tdescription:\n\t\t\t\"Bypass dispatch evaluation and spawn the subagent directly. Use when you are certain a subagent is required.\",\n\t\tdefault: false,\n\t}),\n});\n\ntype SubagentParams = Static<typeof subagentParams>;\n\nexport interface SubagentToolDetails {\n\tmode: SubagentMode;\n\tok: boolean;\n\terror?: string;\n\ttaskId: number;\n\t/** True when the evaluator handled the task inline instead of delegating. */\n\tinline?: boolean;\n}\n\n/** First line of the task, trimmed to a short summary for the task list/footer. */\nfunction summarize(task: string): string {\n\tconst firstLine = task.trim().split(\"\\n\")[0] ?? \"\";\n\treturn firstLine.length > 60 ? `${firstLine.slice(0, 57)}...` : firstLine || \"(task)\";\n}\n\n/** Quick check: should this task go to a subagent? */\nexport function isSubagentRecommended(task: string): boolean {\n\tconst evaluator = new DispatchEvaluator();\n\treturn evaluator.evaluate(task).should_delegate;\n}\n\n/** Map evaluator AgentType to SubagentMode (they are compatible strings). */\nfunction toSubagentMode(agentType: AgentType | null): SubagentMode {\n\treturn (agentType as SubagentMode) ?? \"explore\";\n}\n\n/** Create the subagent tool definition. Registered as a customTool when enabled. */\nexport function createSubagentToolDefinition(): ToolDefinition {\n\treturn defineTool<typeof subagentParams, SubagentToolDetails>({\n\t\tname: \"subagent\",\n\t\tlabel: \"Subagent\",\n\t\tdescription: [\n\t\t\t\"Delegate a focused task to a subagent that runs in a fresh, isolated context (it cannot see this conversation).\",\n\t\t\t\"Pass everything it needs via `context`. The subagent returns only its final answer.\",\n\t\t\t\"Modes: explore, edit, test, fix, review, doc.\",\n\t\t\t\"WHEN TO USE: (1) The work is self-contained and you do not need to see intermediate steps — only the final result.\",\n\t\t\t\"(2) You want to investigate or edit something in parallel without losing your current context or reasoning chain.\",\n\t\t\t\"(3) The task is a discrete unit (explore one module, run one test file, review one PR, fix one isolated bug, write docs).\",\n\t\t\t\"(4) You need to run a long command or test suite and wait for its output without blocking your own reasoning.\",\n\t\t\t\"Do NOT use for tasks that require tight back-and-forth with your current reasoning or that change files you are actively reasoning about.\",\n\t\t\t\"Use force=true to bypass dispatch evaluation when you are certain a subagent is required.\",\n\t\t].join(\" \"),\n\t\tpromptSnippet: \"delegate a self-contained task to an isolated subagent (modes: explore/edit/test/fix/review/doc)\",\n\t\tparameters: subagentParams,\n\n\t\tasync execute(_toolCallId, params: SubagentParams, signal, _onUpdate, ctx) {\n\t\t\tconst forcedMode = params.mode as SubagentMode;\n\n\t\t\t// Dispatch evaluation\n\t\t\tif (!params.force) {\n\t\t\t\tconst evaluator = new DispatchEvaluator();\n\t\t\t\tconst analysis = evaluator.evaluate(params.task);\n\t\t\t\tif (!analysis.should_delegate) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\ttext: `Task is simple enough for inline handling. Reason: ${analysis.reason}. Use force=true for subagent override.`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tdetails: { mode: forcedMode, ok: true, taskId: 0, inline: true },\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst mode = params.force\n\t\t\t\t? forcedMode\n\t\t\t\t: toSubagentMode(new DispatchEvaluator().evaluate(params.task).agent_type);\n\t\t\tconst summary = summarize(params.task);\n\n\t\t\tconst task = taskStore.create(summary, { subagentMode: mode });\n\t\t\ttaskStore.update(task.id, { status: \"in_progress\" });\n\t\t\ttry {\n\t\t\t\tconst result = await runSubagent({\n\t\t\t\t\ttask: params.task,\n\t\t\t\t\tcontext: params.context,\n\t\t\t\t\tmode,\n\t\t\t\t\tcwd: ctx.cwd,\n\t\t\t\t\tmodel: ctx.model,\n\t\t\t\t\tmodelRegistry: ctx.modelRegistry,\n\t\t\t\t\tsignal: signal ?? ctx.signal,\n\t\t\t\t});\n\n\t\t\t\tif (!result.ok) {\n\t\t\t\t\t// Signal failure by throwing: the agent loop derives a tool's error\n\t\t\t\t\t// state from a thrown error, not from a returned flag.\n\t\t\t\t\ttaskStore.update(task.id, { status: \"failed\", usage: result.usage });\n\t\t\t\t\tthrow new Error(`Subagent (${mode}) failed: ${result.error ?? \"unknown error\"}`);\n\t\t\t\t}\n\n\t\t\t\t// Leave the task in the store with its final status. It stays visible in\n\t\t\t\t// the task panel until the main agent moves on (retireFinished is called\n\t\t\t\t// when the main agent starts its next turn).\n\t\t\t\ttaskStore.update(task.id, { status: \"done\", usage: result.usage });\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: result.answer || \"(subagent returned no output)\" }],\n\t\t\t\t\tdetails: { mode, ok: true, taskId: task.id },\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\ttaskStore.update(task.id, { status: \"failed\" });\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\n\t\trenderCall(args, theme) {\n\t\t\tconst mode = args.mode ?? \"explore\";\n\t\t\tconst preview = summarize(args.task ?? \"\");\n\t\t\tconst text =\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"subagent \")) +\n\t\t\t\ttheme.fg(\"accent\", `[${mode}]`) +\n\t\t\t\ttheme.fg(\"dim\", ` ${preview}`);\n\t\t\treturn new Text(text, 0, 0);\n\t\t},\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"subagent.d.ts","sourceRoot":"","sources":["../../../src/core/tools/subagent.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;0EAC0E;AAC1E,eAAO,MAAM,oBAAoB,82DA0BkD,CAAC;AAKpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAmChE,MAAM,WAAW,mBAAmB;IACnC,IAAI,EAAE,YAAY,CAAC;IACnB,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB;AAgBD,sDAAsD;AACtD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAG3D;AAOD,oFAAoF;AACpF,wBAAgB,4BAA4B,IAAI,cAAc,CAuF7D","sourcesContent":["/**\n * Subagent tool: delegate a focused task to a fresh, isolated agent loop.\n *\n * The tool registers a task in the shared task store (visible in the task panel),\n * runs the subagent to completion, and returns ONLY the subagent's final\n * answer. It is an optional, opt-in tool (enabled via --subagent or the\n * `enableSubagent` setting); see buildSessionOptions in main.ts.\n */\n\n/** System prompt appendix for the main session when subagent tooling is enabled.\n * Instructs the parent agent on when and how to delegate effectively. */\nexport const SUBAGENT_MAIN_PROMPT = `You have access to the **subagent** tool. Use it to delegate self-contained tasks to isolated subagent loops that run with their own context and return only their final answer.\n\nAvailable subagent modes:\n- explore: read-only investigation (read, grep, find, ls, bash).\n- edit: make a focused code change (read, edit, write, grep, find, ls, bash).\n- test: run tests and report (read, bash, grep, find, ls).\n- fix: diagnose and fix a failure (read, edit, write, bash, grep, find, ls).\n- review: read-only code review (read, grep, find, ls, bash).\n- doc: write documentation, README, or comments (read, write, edit, grep, find, ls, bash).\n\nWhen to delegate:\n1. The work is self-contained and you only need the final result, not intermediate steps.\n2. You want to investigate or edit something in parallel without losing your current context or reasoning chain.\n3. The task is a discrete unit (explore one module, run one test file, review one PR, fix one isolated bug).\n4. You need to run a long command or test suite and wait for its output without blocking your own reasoning.\n\nGuidelines:\n- Make every task specific and self-contained. The subagent cannot see this conversation.\n- Pass all necessary context (files, constraints, prior findings) via the \\`context\\` parameter.\n- Do NOT delegate tasks that require tight back-and-forth with your current reasoning.\n- Do NOT delegate edits to files you are actively reasoning about.\n- The subagent returns ONLY its final answer. Intermediate reasoning, tool calls, and output are hidden from you.\n\nDispatch evaluator:\n- The dispatch evaluator determines if a subagent is needed. Do not spawn subagents directly unless the user explicitly requests it.\n- For simple single-file changes (<50 lines, read-only or trivial edit), handle them inline.\n- Use force=true to bypass evaluation when you are certain a subagent is required.`;\n\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { type Static, Type } from \"typebox\";\nimport { type AgentType, DispatchEvaluator } from \"../dispatch-evaluator.js\";\nimport type { ToolDefinition } from \"../extensions/types.js\";\nimport { defineTool } from \"../extensions/types.js\";\nimport { runSubagent, type SubagentMode } from \"../subagent.js\";\nimport { taskStore } from \"../task-store.js\";\n\nconst subagentParams = Type.Object({\n\ttask: Type.String({\n\t\tdescription:\n\t\t\t\"The task to delegate. Make it specific and self-contained; the subagent cannot see this conversation.\",\n\t}),\n\tcontext: Type.String({\n\t\tdescription:\n\t\t\t'Context distilled from the conversation the subagent needs (files, constraints, prior findings). Pass \"\" if none.',\n\t}),\n\tmode: Type.Union(\n\t\t[\n\t\t\tType.Literal(\"explore\"),\n\t\t\tType.Literal(\"edit\"),\n\t\t\tType.Literal(\"test\"),\n\t\t\tType.Literal(\"fix\"),\n\t\t\tType.Literal(\"review\"),\n\t\t\tType.Literal(\"doc\"),\n\t\t],\n\t\t{\n\t\t\tdescription:\n\t\t\t\t\"explore: read-only investigation. edit: make a focused code change. test: run tests and report. fix: diagnose and fix a failure. review: read-only code review. doc: write documentation.\",\n\t\t},\n\t),\n\tforce: Type.Boolean({\n\t\tdescription:\n\t\t\t\"Bypass dispatch evaluation and spawn the subagent directly. Use when you are certain a subagent is required.\",\n\t\tdefault: false,\n\t}),\n});\n\ntype SubagentParams = Static<typeof subagentParams>;\n\nexport interface SubagentToolDetails {\n\tmode: SubagentMode;\n\tok: boolean;\n\terror?: string;\n\ttaskId: number;\n\t/** True when the evaluator handled the task inline instead of delegating. */\n\tinline?: boolean;\n}\n\n/**\n * A short, human-readable task name for the task panel: the first line of the\n * task limited to ~4–8 words so it stays glanceable in the pane. A character cap\n * guards against a single very long word.\n */\nfunction summarize(task: string): string {\n\tconst firstLine = (task.trim().split(\"\\n\")[0] ?? \"\").trim();\n\tif (!firstLine) return \"(task)\";\n\tconst words = firstLine.split(/\\s+/);\n\tlet name = words.length > 8 ? `${words.slice(0, 8).join(\" \")}…` : firstLine;\n\tif (name.length > 60) name = `${name.slice(0, 59)}…`;\n\treturn name;\n}\n\n/** Quick check: should this task go to a subagent? */\nexport function isSubagentRecommended(task: string): boolean {\n\tconst evaluator = new DispatchEvaluator();\n\treturn evaluator.evaluate(task).should_delegate;\n}\n\n/** Map evaluator AgentType to SubagentMode (they are compatible strings). */\nfunction toSubagentMode(agentType: AgentType | null): SubagentMode {\n\treturn (agentType as SubagentMode) ?? \"explore\";\n}\n\n/** Create the subagent tool definition. Registered as a customTool when enabled. */\nexport function createSubagentToolDefinition(): ToolDefinition {\n\treturn defineTool<typeof subagentParams, SubagentToolDetails>({\n\t\tname: \"subagent\",\n\t\tlabel: \"Subagent\",\n\t\tdescription: [\n\t\t\t\"Delegate a focused task to a subagent that runs in a fresh, isolated context (it cannot see this conversation).\",\n\t\t\t\"Pass everything it needs via `context`. The subagent returns only its final answer.\",\n\t\t\t\"Modes: explore, edit, test, fix, review, doc.\",\n\t\t\t\"WHEN TO USE: (1) The work is self-contained and you do not need to see intermediate steps — only the final result.\",\n\t\t\t\"(2) You want to investigate or edit something in parallel without losing your current context or reasoning chain.\",\n\t\t\t\"(3) The task is a discrete unit (explore one module, run one test file, review one PR, fix one isolated bug, write docs).\",\n\t\t\t\"(4) You need to run a long command or test suite and wait for its output without blocking your own reasoning.\",\n\t\t\t\"Do NOT use for tasks that require tight back-and-forth with your current reasoning or that change files you are actively reasoning about.\",\n\t\t\t\"Use force=true to bypass dispatch evaluation when you are certain a subagent is required.\",\n\t\t].join(\" \"),\n\t\tpromptSnippet: \"delegate a self-contained task to an isolated subagent (modes: explore/edit/test/fix/review/doc)\",\n\t\tparameters: subagentParams,\n\n\t\tasync execute(_toolCallId, params: SubagentParams, signal, _onUpdate, ctx) {\n\t\t\tconst forcedMode = params.mode as SubagentMode;\n\n\t\t\t// Dispatch evaluation\n\t\t\tif (!params.force) {\n\t\t\t\tconst evaluator = new DispatchEvaluator();\n\t\t\t\tconst analysis = evaluator.evaluate(params.task);\n\t\t\t\tif (!analysis.should_delegate) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\ttext: `Task is simple enough for inline handling. Reason: ${analysis.reason}. Use force=true for subagent override.`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tdetails: { mode: forcedMode, ok: true, taskId: 0, inline: true },\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst mode = params.force\n\t\t\t\t? forcedMode\n\t\t\t\t: toSubagentMode(new DispatchEvaluator().evaluate(params.task).agent_type);\n\t\t\tconst summary = summarize(params.task);\n\n\t\t\tconst task = taskStore.create(summary, { subagentMode: mode });\n\t\t\ttaskStore.update(task.id, { status: \"in_progress\" });\n\t\t\ttry {\n\t\t\t\tconst result = await runSubagent({\n\t\t\t\t\ttask: params.task,\n\t\t\t\t\tcontext: params.context,\n\t\t\t\t\tmode,\n\t\t\t\t\tcwd: ctx.cwd,\n\t\t\t\t\tmodel: ctx.model,\n\t\t\t\t\tmodelRegistry: ctx.modelRegistry,\n\t\t\t\t\tsignal: signal ?? ctx.signal,\n\t\t\t\t});\n\n\t\t\t\tif (!result.ok) {\n\t\t\t\t\t// Signal failure by throwing: the agent loop derives a tool's error\n\t\t\t\t\t// state from a thrown error, not from a returned flag.\n\t\t\t\t\ttaskStore.update(task.id, { status: \"failed\", usage: result.usage });\n\t\t\t\t\tthrow new Error(`Subagent (${mode}) failed: ${result.error ?? \"unknown error\"}`);\n\t\t\t\t}\n\n\t\t\t\t// Leave the task in the store with its final status. It stays visible in\n\t\t\t\t// the task panel until the next user message arrives (retireFinished is\n\t\t\t\t// called when the user starts the next turn).\n\t\t\t\ttaskStore.update(task.id, { status: \"done\", usage: result.usage });\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: result.answer || \"(subagent returned no output)\" }],\n\t\t\t\t\tdetails: { mode, ok: true, taskId: task.id },\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\ttaskStore.update(task.id, { status: \"failed\" });\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\n\t\trenderCall(args, theme) {\n\t\t\tconst mode = args.mode ?? \"explore\";\n\t\t\tconst preview = summarize(args.task ?? \"\");\n\t\t\tconst text =\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"subagent \")) +\n\t\t\t\ttheme.fg(\"accent\", `[${mode}]`) +\n\t\t\t\ttheme.fg(\"dim\", ` ${preview}`);\n\t\t\treturn new Text(text, 0, 0);\n\t\t},\n\t});\n}\n"]}
|
|
@@ -63,10 +63,20 @@ const subagentParams = Type.Object({
|
|
|
63
63
|
default: false,
|
|
64
64
|
}),
|
|
65
65
|
});
|
|
66
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* A short, human-readable task name for the task panel: the first line of the
|
|
68
|
+
* task limited to ~4–8 words so it stays glanceable in the pane. A character cap
|
|
69
|
+
* guards against a single very long word.
|
|
70
|
+
*/
|
|
67
71
|
function summarize(task) {
|
|
68
|
-
const firstLine = task.trim().split("\n")[0] ?? "";
|
|
69
|
-
|
|
72
|
+
const firstLine = (task.trim().split("\n")[0] ?? "").trim();
|
|
73
|
+
if (!firstLine)
|
|
74
|
+
return "(task)";
|
|
75
|
+
const words = firstLine.split(/\s+/);
|
|
76
|
+
let name = words.length > 8 ? `${words.slice(0, 8).join(" ")}…` : firstLine;
|
|
77
|
+
if (name.length > 60)
|
|
78
|
+
name = `${name.slice(0, 59)}…`;
|
|
79
|
+
return name;
|
|
70
80
|
}
|
|
71
81
|
/** Quick check: should this task go to a subagent? */
|
|
72
82
|
export function isSubagentRecommended(task) {
|
|
@@ -136,8 +146,8 @@ export function createSubagentToolDefinition() {
|
|
|
136
146
|
throw new Error(`Subagent (${mode}) failed: ${result.error ?? "unknown error"}`);
|
|
137
147
|
}
|
|
138
148
|
// Leave the task in the store with its final status. It stays visible in
|
|
139
|
-
// the task panel until the
|
|
140
|
-
// when the
|
|
149
|
+
// the task panel until the next user message arrives (retireFinished is
|
|
150
|
+
// called when the user starts the next turn).
|
|
141
151
|
taskStore.update(task.id, { status: "done", usage: result.usage });
|
|
142
152
|
return {
|
|
143
153
|
content: [{ type: "text", text: result.answer || "(subagent returned no output)" }],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subagent.js","sourceRoot":"","sources":["../../../src/core/tools/subagent.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Subagent tool: delegate a focused task to a fresh, isolated agent loop.\n *\n * The tool registers a task in the shared task store (visible in the task panel),\n * runs the subagent to completion, and returns ONLY the subagent's final\n * answer. It is an optional, opt-in tool (enabled via --subagent or the\n * `enableSubagent` setting); see buildSessionOptions in main.ts.\n */\n\n/** System prompt appendix for the main session when subagent tooling is enabled.\n * Instructs the parent agent on when and how to delegate effectively. */\nexport const SUBAGENT_MAIN_PROMPT = `You have access to the **subagent** tool. Use it to delegate self-contained tasks to isolated subagent loops that run with their own context and return only their final answer.\n\nAvailable subagent modes:\n- explore: read-only investigation (read, grep, find, ls, bash).\n- edit: make a focused code change (read, edit, write, grep, find, ls, bash).\n- test: run tests and report (read, bash, grep, find, ls).\n- fix: diagnose and fix a failure (read, edit, write, bash, grep, find, ls).\n- review: read-only code review (read, grep, find, ls, bash).\n- doc: write documentation, README, or comments (read, write, edit, grep, find, ls, bash).\n\nWhen to delegate:\n1. The work is self-contained and you only need the final result, not intermediate steps.\n2. You want to investigate or edit something in parallel without losing your current context or reasoning chain.\n3. The task is a discrete unit (explore one module, run one test file, review one PR, fix one isolated bug).\n4. You need to run a long command or test suite and wait for its output without blocking your own reasoning.\n\nGuidelines:\n- Make every task specific and self-contained. The subagent cannot see this conversation.\n- Pass all necessary context (files, constraints, prior findings) via the \\`context\\` parameter.\n- Do NOT delegate tasks that require tight back-and-forth with your current reasoning.\n- Do NOT delegate edits to files you are actively reasoning about.\n- The subagent returns ONLY its final answer. Intermediate reasoning, tool calls, and output are hidden from you.\n\nDispatch evaluator:\n- The dispatch evaluator determines if a subagent is needed. Do not spawn subagents directly unless the user explicitly requests it.\n- For simple single-file changes (<50 lines, read-only or trivial edit), handle them inline.\n- Use force=true to bypass evaluation when you are certain a subagent is required.`;\n\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { type Static, Type } from \"typebox\";\nimport { type AgentType, DispatchEvaluator } from \"../dispatch-evaluator.js\";\nimport type { ToolDefinition } from \"../extensions/types.js\";\nimport { defineTool } from \"../extensions/types.js\";\nimport { runSubagent, type SubagentMode } from \"../subagent.js\";\nimport { taskStore } from \"../task-store.js\";\n\nconst subagentParams = Type.Object({\n\ttask: Type.String({\n\t\tdescription:\n\t\t\t\"The task to delegate. Make it specific and self-contained; the subagent cannot see this conversation.\",\n\t}),\n\tcontext: Type.String({\n\t\tdescription:\n\t\t\t'Context distilled from the conversation the subagent needs (files, constraints, prior findings). Pass \"\" if none.',\n\t}),\n\tmode: Type.Union(\n\t\t[\n\t\t\tType.Literal(\"explore\"),\n\t\t\tType.Literal(\"edit\"),\n\t\t\tType.Literal(\"test\"),\n\t\t\tType.Literal(\"fix\"),\n\t\t\tType.Literal(\"review\"),\n\t\t\tType.Literal(\"doc\"),\n\t\t],\n\t\t{\n\t\t\tdescription:\n\t\t\t\t\"explore: read-only investigation. edit: make a focused code change. test: run tests and report. fix: diagnose and fix a failure. review: read-only code review. doc: write documentation.\",\n\t\t},\n\t),\n\tforce: Type.Boolean({\n\t\tdescription:\n\t\t\t\"Bypass dispatch evaluation and spawn the subagent directly. Use when you are certain a subagent is required.\",\n\t\tdefault: false,\n\t}),\n});\n\ntype SubagentParams = Static<typeof subagentParams>;\n\nexport interface SubagentToolDetails {\n\tmode: SubagentMode;\n\tok: boolean;\n\terror?: string;\n\ttaskId: number;\n\t/** True when the evaluator handled the task inline instead of delegating. */\n\tinline?: boolean;\n}\n\n/** First line of the task, trimmed to a short summary for the task list/footer. */\nfunction summarize(task: string): string {\n\tconst firstLine = task.trim().split(\"\\n\")[0] ?? \"\";\n\treturn firstLine.length > 60 ? `${firstLine.slice(0, 57)}...` : firstLine || \"(task)\";\n}\n\n/** Quick check: should this task go to a subagent? */\nexport function isSubagentRecommended(task: string): boolean {\n\tconst evaluator = new DispatchEvaluator();\n\treturn evaluator.evaluate(task).should_delegate;\n}\n\n/** Map evaluator AgentType to SubagentMode (they are compatible strings). */\nfunction toSubagentMode(agentType: AgentType | null): SubagentMode {\n\treturn (agentType as SubagentMode) ?? \"explore\";\n}\n\n/** Create the subagent tool definition. Registered as a customTool when enabled. */\nexport function createSubagentToolDefinition(): ToolDefinition {\n\treturn defineTool<typeof subagentParams, SubagentToolDetails>({\n\t\tname: \"subagent\",\n\t\tlabel: \"Subagent\",\n\t\tdescription: [\n\t\t\t\"Delegate a focused task to a subagent that runs in a fresh, isolated context (it cannot see this conversation).\",\n\t\t\t\"Pass everything it needs via `context`. The subagent returns only its final answer.\",\n\t\t\t\"Modes: explore, edit, test, fix, review, doc.\",\n\t\t\t\"WHEN TO USE: (1) The work is self-contained and you do not need to see intermediate steps — only the final result.\",\n\t\t\t\"(2) You want to investigate or edit something in parallel without losing your current context or reasoning chain.\",\n\t\t\t\"(3) The task is a discrete unit (explore one module, run one test file, review one PR, fix one isolated bug, write docs).\",\n\t\t\t\"(4) You need to run a long command or test suite and wait for its output without blocking your own reasoning.\",\n\t\t\t\"Do NOT use for tasks that require tight back-and-forth with your current reasoning or that change files you are actively reasoning about.\",\n\t\t\t\"Use force=true to bypass dispatch evaluation when you are certain a subagent is required.\",\n\t\t].join(\" \"),\n\t\tpromptSnippet: \"delegate a self-contained task to an isolated subagent (modes: explore/edit/test/fix/review/doc)\",\n\t\tparameters: subagentParams,\n\n\t\tasync execute(_toolCallId, params: SubagentParams, signal, _onUpdate, ctx) {\n\t\t\tconst forcedMode = params.mode as SubagentMode;\n\n\t\t\t// Dispatch evaluation\n\t\t\tif (!params.force) {\n\t\t\t\tconst evaluator = new DispatchEvaluator();\n\t\t\t\tconst analysis = evaluator.evaluate(params.task);\n\t\t\t\tif (!analysis.should_delegate) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\ttext: `Task is simple enough for inline handling. Reason: ${analysis.reason}. Use force=true for subagent override.`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tdetails: { mode: forcedMode, ok: true, taskId: 0, inline: true },\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst mode = params.force\n\t\t\t\t? forcedMode\n\t\t\t\t: toSubagentMode(new DispatchEvaluator().evaluate(params.task).agent_type);\n\t\t\tconst summary = summarize(params.task);\n\n\t\t\tconst task = taskStore.create(summary, { subagentMode: mode });\n\t\t\ttaskStore.update(task.id, { status: \"in_progress\" });\n\t\t\ttry {\n\t\t\t\tconst result = await runSubagent({\n\t\t\t\t\ttask: params.task,\n\t\t\t\t\tcontext: params.context,\n\t\t\t\t\tmode,\n\t\t\t\t\tcwd: ctx.cwd,\n\t\t\t\t\tmodel: ctx.model,\n\t\t\t\t\tmodelRegistry: ctx.modelRegistry,\n\t\t\t\t\tsignal: signal ?? ctx.signal,\n\t\t\t\t});\n\n\t\t\t\tif (!result.ok) {\n\t\t\t\t\t// Signal failure by throwing: the agent loop derives a tool's error\n\t\t\t\t\t// state from a thrown error, not from a returned flag.\n\t\t\t\t\ttaskStore.update(task.id, { status: \"failed\", usage: result.usage });\n\t\t\t\t\tthrow new Error(`Subagent (${mode}) failed: ${result.error ?? \"unknown error\"}`);\n\t\t\t\t}\n\n\t\t\t\t// Leave the task in the store with its final status. It stays visible in\n\t\t\t\t// the task panel until the main agent moves on (retireFinished is called\n\t\t\t\t// when the main agent starts its next turn).\n\t\t\t\ttaskStore.update(task.id, { status: \"done\", usage: result.usage });\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: result.answer || \"(subagent returned no output)\" }],\n\t\t\t\t\tdetails: { mode, ok: true, taskId: task.id },\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\ttaskStore.update(task.id, { status: \"failed\" });\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\n\t\trenderCall(args, theme) {\n\t\t\tconst mode = args.mode ?? \"explore\";\n\t\t\tconst preview = summarize(args.task ?? \"\");\n\t\t\tconst text =\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"subagent \")) +\n\t\t\t\ttheme.fg(\"accent\", `[${mode}]`) +\n\t\t\t\ttheme.fg(\"dim\", ` ${preview}`);\n\t\t\treturn new Text(text, 0, 0);\n\t\t},\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"subagent.js","sourceRoot":"","sources":["../../../src/core/tools/subagent.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Subagent tool: delegate a focused task to a fresh, isolated agent loop.\n *\n * The tool registers a task in the shared task store (visible in the task panel),\n * runs the subagent to completion, and returns ONLY the subagent's final\n * answer. It is an optional, opt-in tool (enabled via --subagent or the\n * `enableSubagent` setting); see buildSessionOptions in main.ts.\n */\n\n/** System prompt appendix for the main session when subagent tooling is enabled.\n * Instructs the parent agent on when and how to delegate effectively. */\nexport const SUBAGENT_MAIN_PROMPT = `You have access to the **subagent** tool. Use it to delegate self-contained tasks to isolated subagent loops that run with their own context and return only their final answer.\n\nAvailable subagent modes:\n- explore: read-only investigation (read, grep, find, ls, bash).\n- edit: make a focused code change (read, edit, write, grep, find, ls, bash).\n- test: run tests and report (read, bash, grep, find, ls).\n- fix: diagnose and fix a failure (read, edit, write, bash, grep, find, ls).\n- review: read-only code review (read, grep, find, ls, bash).\n- doc: write documentation, README, or comments (read, write, edit, grep, find, ls, bash).\n\nWhen to delegate:\n1. The work is self-contained and you only need the final result, not intermediate steps.\n2. You want to investigate or edit something in parallel without losing your current context or reasoning chain.\n3. The task is a discrete unit (explore one module, run one test file, review one PR, fix one isolated bug).\n4. You need to run a long command or test suite and wait for its output without blocking your own reasoning.\n\nGuidelines:\n- Make every task specific and self-contained. The subagent cannot see this conversation.\n- Pass all necessary context (files, constraints, prior findings) via the \\`context\\` parameter.\n- Do NOT delegate tasks that require tight back-and-forth with your current reasoning.\n- Do NOT delegate edits to files you are actively reasoning about.\n- The subagent returns ONLY its final answer. Intermediate reasoning, tool calls, and output are hidden from you.\n\nDispatch evaluator:\n- The dispatch evaluator determines if a subagent is needed. Do not spawn subagents directly unless the user explicitly requests it.\n- For simple single-file changes (<50 lines, read-only or trivial edit), handle them inline.\n- Use force=true to bypass evaluation when you are certain a subagent is required.`;\n\nimport { Text } from \"@kolisachint/hoocode-tui\";\nimport { type Static, Type } from \"typebox\";\nimport { type AgentType, DispatchEvaluator } from \"../dispatch-evaluator.js\";\nimport type { ToolDefinition } from \"../extensions/types.js\";\nimport { defineTool } from \"../extensions/types.js\";\nimport { runSubagent, type SubagentMode } from \"../subagent.js\";\nimport { taskStore } from \"../task-store.js\";\n\nconst subagentParams = Type.Object({\n\ttask: Type.String({\n\t\tdescription:\n\t\t\t\"The task to delegate. Make it specific and self-contained; the subagent cannot see this conversation.\",\n\t}),\n\tcontext: Type.String({\n\t\tdescription:\n\t\t\t'Context distilled from the conversation the subagent needs (files, constraints, prior findings). Pass \"\" if none.',\n\t}),\n\tmode: Type.Union(\n\t\t[\n\t\t\tType.Literal(\"explore\"),\n\t\t\tType.Literal(\"edit\"),\n\t\t\tType.Literal(\"test\"),\n\t\t\tType.Literal(\"fix\"),\n\t\t\tType.Literal(\"review\"),\n\t\t\tType.Literal(\"doc\"),\n\t\t],\n\t\t{\n\t\t\tdescription:\n\t\t\t\t\"explore: read-only investigation. edit: make a focused code change. test: run tests and report. fix: diagnose and fix a failure. review: read-only code review. doc: write documentation.\",\n\t\t},\n\t),\n\tforce: Type.Boolean({\n\t\tdescription:\n\t\t\t\"Bypass dispatch evaluation and spawn the subagent directly. Use when you are certain a subagent is required.\",\n\t\tdefault: false,\n\t}),\n});\n\ntype SubagentParams = Static<typeof subagentParams>;\n\nexport interface SubagentToolDetails {\n\tmode: SubagentMode;\n\tok: boolean;\n\terror?: string;\n\ttaskId: number;\n\t/** True when the evaluator handled the task inline instead of delegating. */\n\tinline?: boolean;\n}\n\n/**\n * A short, human-readable task name for the task panel: the first line of the\n * task limited to ~4–8 words so it stays glanceable in the pane. A character cap\n * guards against a single very long word.\n */\nfunction summarize(task: string): string {\n\tconst firstLine = (task.trim().split(\"\\n\")[0] ?? \"\").trim();\n\tif (!firstLine) return \"(task)\";\n\tconst words = firstLine.split(/\\s+/);\n\tlet name = words.length > 8 ? `${words.slice(0, 8).join(\" \")}…` : firstLine;\n\tif (name.length > 60) name = `${name.slice(0, 59)}…`;\n\treturn name;\n}\n\n/** Quick check: should this task go to a subagent? */\nexport function isSubagentRecommended(task: string): boolean {\n\tconst evaluator = new DispatchEvaluator();\n\treturn evaluator.evaluate(task).should_delegate;\n}\n\n/** Map evaluator AgentType to SubagentMode (they are compatible strings). */\nfunction toSubagentMode(agentType: AgentType | null): SubagentMode {\n\treturn (agentType as SubagentMode) ?? \"explore\";\n}\n\n/** Create the subagent tool definition. Registered as a customTool when enabled. */\nexport function createSubagentToolDefinition(): ToolDefinition {\n\treturn defineTool<typeof subagentParams, SubagentToolDetails>({\n\t\tname: \"subagent\",\n\t\tlabel: \"Subagent\",\n\t\tdescription: [\n\t\t\t\"Delegate a focused task to a subagent that runs in a fresh, isolated context (it cannot see this conversation).\",\n\t\t\t\"Pass everything it needs via `context`. The subagent returns only its final answer.\",\n\t\t\t\"Modes: explore, edit, test, fix, review, doc.\",\n\t\t\t\"WHEN TO USE: (1) The work is self-contained and you do not need to see intermediate steps — only the final result.\",\n\t\t\t\"(2) You want to investigate or edit something in parallel without losing your current context or reasoning chain.\",\n\t\t\t\"(3) The task is a discrete unit (explore one module, run one test file, review one PR, fix one isolated bug, write docs).\",\n\t\t\t\"(4) You need to run a long command or test suite and wait for its output without blocking your own reasoning.\",\n\t\t\t\"Do NOT use for tasks that require tight back-and-forth with your current reasoning or that change files you are actively reasoning about.\",\n\t\t\t\"Use force=true to bypass dispatch evaluation when you are certain a subagent is required.\",\n\t\t].join(\" \"),\n\t\tpromptSnippet: \"delegate a self-contained task to an isolated subagent (modes: explore/edit/test/fix/review/doc)\",\n\t\tparameters: subagentParams,\n\n\t\tasync execute(_toolCallId, params: SubagentParams, signal, _onUpdate, ctx) {\n\t\t\tconst forcedMode = params.mode as SubagentMode;\n\n\t\t\t// Dispatch evaluation\n\t\t\tif (!params.force) {\n\t\t\t\tconst evaluator = new DispatchEvaluator();\n\t\t\t\tconst analysis = evaluator.evaluate(params.task);\n\t\t\t\tif (!analysis.should_delegate) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tcontent: [\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttype: \"text\",\n\t\t\t\t\t\t\t\ttext: `Task is simple enough for inline handling. Reason: ${analysis.reason}. Use force=true for subagent override.`,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t],\n\t\t\t\t\t\tdetails: { mode: forcedMode, ok: true, taskId: 0, inline: true },\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst mode = params.force\n\t\t\t\t? forcedMode\n\t\t\t\t: toSubagentMode(new DispatchEvaluator().evaluate(params.task).agent_type);\n\t\t\tconst summary = summarize(params.task);\n\n\t\t\tconst task = taskStore.create(summary, { subagentMode: mode });\n\t\t\ttaskStore.update(task.id, { status: \"in_progress\" });\n\t\t\ttry {\n\t\t\t\tconst result = await runSubagent({\n\t\t\t\t\ttask: params.task,\n\t\t\t\t\tcontext: params.context,\n\t\t\t\t\tmode,\n\t\t\t\t\tcwd: ctx.cwd,\n\t\t\t\t\tmodel: ctx.model,\n\t\t\t\t\tmodelRegistry: ctx.modelRegistry,\n\t\t\t\t\tsignal: signal ?? ctx.signal,\n\t\t\t\t});\n\n\t\t\t\tif (!result.ok) {\n\t\t\t\t\t// Signal failure by throwing: the agent loop derives a tool's error\n\t\t\t\t\t// state from a thrown error, not from a returned flag.\n\t\t\t\t\ttaskStore.update(task.id, { status: \"failed\", usage: result.usage });\n\t\t\t\t\tthrow new Error(`Subagent (${mode}) failed: ${result.error ?? \"unknown error\"}`);\n\t\t\t\t}\n\n\t\t\t\t// Leave the task in the store with its final status. It stays visible in\n\t\t\t\t// the task panel until the next user message arrives (retireFinished is\n\t\t\t\t// called when the user starts the next turn).\n\t\t\t\ttaskStore.update(task.id, { status: \"done\", usage: result.usage });\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [{ type: \"text\", text: result.answer || \"(subagent returned no output)\" }],\n\t\t\t\t\tdetails: { mode, ok: true, taskId: task.id },\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\ttaskStore.update(task.id, { status: \"failed\" });\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t},\n\n\t\trenderCall(args, theme) {\n\t\t\tconst mode = args.mode ?? \"explore\";\n\t\t\tconst preview = summarize(args.task ?? \"\");\n\t\t\tconst text =\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"subagent \")) +\n\t\t\t\ttheme.fg(\"accent\", `[${mode}]`) +\n\t\t\t\ttheme.fg(\"dim\", ` ${preview}`);\n\t\t\treturn new Text(text, 0, 0);\n\t\t},\n\t});\n}\n"]}
|
|
@@ -4,10 +4,11 @@ import type { Component } from "@kolisachint/hoocode-tui";
|
|
|
4
4
|
*
|
|
5
5
|
* - A ledger header (watched/reviewed stamp + done/total count) tops the list.
|
|
6
6
|
* - Shows all tasks with all statuses (pending / in_progress / done / failed).
|
|
7
|
+
* - Subagent mode is intentionally NOT shown here (e.g. no "[explore]" tag) — the
|
|
8
|
+
* task title is the meaningful label; the mode adds noise in the pane.
|
|
7
9
|
* - LIFO within the window: newest tasks appear at the bottom (closest to the prompt).
|
|
8
|
-
* - Finished tasks carry their wall-clock cost and
|
|
9
|
-
*
|
|
10
|
-
* not the moment they finish.
|
|
10
|
+
* - Finished tasks carry their wall-clock cost and stay visible until the next
|
|
11
|
+
* user message arrives (see taskStore.retireFinished()), not the moment they finish.
|
|
11
12
|
* - Collapses to zero lines when there are no tasks.
|
|
12
13
|
*/
|
|
13
14
|
export declare class TaskPanelComponent implements Component {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-panel.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/task-panel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"task-panel.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/task-panel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAsI1D;;;;;;;;;;;GAWG;AACH,qBAAa,kBAAmB,YAAW,SAAS;IACnD,UAAU,IAAI,IAAI,CAEjB;IAED,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAW9B;CACD","sourcesContent":["import type { Component } from \"@kolisachint/hoocode-tui\";\nimport { truncateToWidth, visibleWidth } from \"@kolisachint/hoocode-tui\";\nimport type { Task, TaskStatus } from \"../../../core/task-store.js\";\nimport { taskStore } from \"../../../core/task-store.js\";\nimport { theme } from \"../theme/theme.js\";\n\nconst TASK_STATUS_ICON: Record<TaskStatus, string> = {\n\tpending: \"●\",\n\tin_progress: \"◐\",\n\tdone: \"✓\",\n\tfailed: \"✗\",\n};\n\nfunction taskStatusColor(status: TaskStatus): \"dim\" | \"warning\" | \"success\" | \"error\" {\n\tswitch (status) {\n\t\tcase \"in_progress\":\n\t\t\treturn \"warning\";\n\t\tcase \"done\":\n\t\t\treturn \"success\";\n\t\tcase \"failed\":\n\t\t\treturn \"error\";\n\t\tdefault:\n\t\t\treturn \"dim\";\n\t}\n}\n\n/** Wall-clock time a task occupied, derived from its create/update stamps. */\nfunction formatElapsed(task: Task): string {\n\tconst secs = Math.max(0, (task.updatedAt - task.createdAt) / 1000);\n\tif (secs < 10) return `${secs.toFixed(1)}s`;\n\tif (secs < 60) return `${Math.round(secs)}s`;\n\tconst mins = Math.floor(secs / 60);\n\tconst rem = Math.round(secs % 60);\n\treturn `${mins}m${rem.toString().padStart(2, \"0\")}s`;\n}\n\n/** Sum the token + cost usage reported by the tasks shown this turn. */\nfunction sumTurnUsage(tasks: readonly Task[]): { input: number; output: number; cost: number } | null {\n\tlet input = 0;\n\tlet output = 0;\n\tlet cost = 0;\n\tfor (const task of tasks) {\n\t\tif (!task.usage) continue;\n\t\tinput += task.usage.input;\n\t\toutput += task.usage.output;\n\t\tcost += task.usage.cost;\n\t}\n\tif (input === 0 && output === 0 && cost === 0) return null;\n\treturn { input, output, cost };\n}\n\n/**\n * Ledger header: a watched/reviewed stamp plus done/total count on the left, and\n * the per-turn token + cost delta (summed across the tasks below) on the right.\n * The panel is an audit trail — the stamp makes the deterministic \"every task\n * watched & reviewed\" state glanceable, and the delta surfaces what the turn cost.\n */\nfunction formatHeader(tasks: readonly Task[], width: number): string {\n\tconst total = tasks.length;\n\tconst done = tasks.filter((t) => t.status === \"done\").length;\n\tconst watching = tasks.some((t) => t.status === \"in_progress\" || t.status === \"pending\");\n\n\tconst stampPlain = watching ? \"⟳ watching\" : \"reviewed ✓ · deterministic\";\n\tconst stamp = watching\n\t\t? theme.fg(\"warning\", \"⟳ watching\")\n\t\t: theme.fg(\"success\", \"reviewed ✓\") + theme.fg(\"dim\", \" · deterministic\");\n\n\tconst countPlain = `${done}/${total} done`;\n\tconst leftPlain = `${stampPlain} ${countPlain}`;\n\tconst left = `${stamp} ${theme.fg(\"dim\", countPlain)}`;\n\n\tconst turn = sumTurnUsage(tasks);\n\tif (!turn) {\n\t\treturn truncateToWidth(left, width, \"…\");\n\t}\n\n\t// Cost is omitted when zero (e.g. subscription/untracked) — still show tokens.\n\tconst showCost = turn.cost > 0;\n\tconst costPlain = showCost ? ` $${turn.cost.toFixed(3)}` : \"\";\n\tconst turnPlain = `turn ↑${formatTokens(turn.input)} ↓${formatTokens(turn.output)}${costPlain}`;\n\tlet turnText =\n\t\ttheme.fg(\"dim\", \"turn ↑\") +\n\t\ttheme.bold(formatTokens(turn.input)) +\n\t\ttheme.fg(\"dim\", \" ↓\") +\n\t\ttheme.bold(formatTokens(turn.output));\n\tif (showCost) {\n\t\tturnText += theme.fg(\"dim\", \" \") + theme.bold(`$${turn.cost.toFixed(3)}`);\n\t}\n\n\tif (visibleWidth(leftPlain) + 2 + visibleWidth(turnPlain) > width) {\n\t\t// Too narrow for both — keep the stamp + count.\n\t\treturn truncateToWidth(left, width, \"…\");\n\t}\n\tconst pad = Math.max(2, width - visibleWidth(leftPlain) - visibleWidth(turnPlain));\n\treturn left + \" \".repeat(pad) + turnText;\n}\n\nfunction formatTokens(count: number): string {\n\tif (count < 1000) return count.toString();\n\tif (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n\tif (count < 1000000) return `${Math.round(count / 1000)}k`;\n\treturn `${(count / 1000000).toFixed(1)}M`;\n}\n\nfunction formatTaskLine(task: Task, width: number): string {\n\tconst icon = theme.fg(taskStatusColor(task.status), TASK_STATUS_ICON[task.status]);\n\tconst idLabel = `#${task.id}`;\n\tconst title = task.title;\n\n\t// Finished tasks carry an audit stamp: total tokens used + elapsed time.\n\tconst settled = task.status === \"done\" || task.status === \"failed\";\n\tlet rightPlain = \"\";\n\tif (settled) {\n\t\tconst parts: string[] = [];\n\t\tif (task.usage) {\n\t\t\tconst total = task.usage.input + task.usage.output;\n\t\t\tif (total > 0) parts.push(formatTokens(total));\n\t\t}\n\t\tparts.push(formatElapsed(task));\n\t\trightPlain = parts.join(\" · \");\n\t}\n\tconst rightWidth = rightPlain ? visibleWidth(rightPlain) + 1 : 0;\n\tconst leftWidth = Math.max(0, width - rightWidth);\n\n\tconst plainText = `${TASK_STATUS_ICON[task.status]} ${idLabel} ${title}`;\n\tconst available = Math.max(0, leftWidth - visibleWidth(plainText) + visibleWidth(title));\n\tconst left = truncateToWidth(`${icon} ${idLabel} ${title}`, available, \"…\");\n\n\tif (!rightPlain) return left;\n\n\tconst pad = Math.max(1, width - visibleWidth(left) - visibleWidth(rightPlain));\n\treturn left + \" \".repeat(pad) + theme.fg(\"dim\", rightPlain);\n}\n\n/**\n * Task panel rendered just above the editor prompt.\n *\n * - A ledger header (watched/reviewed stamp + done/total count) tops the list.\n * - Shows all tasks with all statuses (pending / in_progress / done / failed).\n * - Subagent mode is intentionally NOT shown here (e.g. no \"[explore]\" tag) — the\n * task title is the meaningful label; the mode adds noise in the pane.\n * - LIFO within the window: newest tasks appear at the bottom (closest to the prompt).\n * - Finished tasks carry their wall-clock cost and stay visible until the next\n * user message arrives (see taskStore.retireFinished()), not the moment they finish.\n * - Collapses to zero lines when there are no tasks.\n */\nexport class TaskPanelComponent implements Component {\n\tinvalidate(): void {\n\t\t// No cached rendering state.\n\t}\n\n\trender(width: number): string[] {\n\t\tconst tasks = taskStore.list();\n\t\tif (tasks.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst lines: string[] = [formatHeader(tasks, width)];\n\t\tfor (const task of tasks) {\n\t\t\tlines.push(formatTaskLine(task, width));\n\t\t}\n\t\treturn lines;\n\t}\n}\n"]}
|
|
@@ -30,10 +30,27 @@ function formatElapsed(task) {
|
|
|
30
30
|
const rem = Math.round(secs % 60);
|
|
31
31
|
return `${mins}m${rem.toString().padStart(2, "0")}s`;
|
|
32
32
|
}
|
|
33
|
+
/** Sum the token + cost usage reported by the tasks shown this turn. */
|
|
34
|
+
function sumTurnUsage(tasks) {
|
|
35
|
+
let input = 0;
|
|
36
|
+
let output = 0;
|
|
37
|
+
let cost = 0;
|
|
38
|
+
for (const task of tasks) {
|
|
39
|
+
if (!task.usage)
|
|
40
|
+
continue;
|
|
41
|
+
input += task.usage.input;
|
|
42
|
+
output += task.usage.output;
|
|
43
|
+
cost += task.usage.cost;
|
|
44
|
+
}
|
|
45
|
+
if (input === 0 && output === 0 && cost === 0)
|
|
46
|
+
return null;
|
|
47
|
+
return { input, output, cost };
|
|
48
|
+
}
|
|
33
49
|
/**
|
|
34
|
-
* Ledger header: a watched/reviewed stamp
|
|
35
|
-
* the
|
|
36
|
-
*
|
|
50
|
+
* Ledger header: a watched/reviewed stamp plus done/total count on the left, and
|
|
51
|
+
* the per-turn token + cost delta (summed across the tasks below) on the right.
|
|
52
|
+
* The panel is an audit trail — the stamp makes the deterministic "every task
|
|
53
|
+
* watched & reviewed" state glanceable, and the delta surfaces what the turn cost.
|
|
37
54
|
*/
|
|
38
55
|
function formatHeader(tasks, width) {
|
|
39
56
|
const total = tasks.length;
|
|
@@ -44,13 +61,29 @@ function formatHeader(tasks, width) {
|
|
|
44
61
|
? theme.fg("warning", "⟳ watching")
|
|
45
62
|
: theme.fg("success", "reviewed ✓") + theme.fg("dim", " · deterministic");
|
|
46
63
|
const countPlain = `${done}/${total} done`;
|
|
47
|
-
const
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return
|
|
64
|
+
const leftPlain = `${stampPlain} ${countPlain}`;
|
|
65
|
+
const left = `${stamp} ${theme.fg("dim", countPlain)}`;
|
|
66
|
+
const turn = sumTurnUsage(tasks);
|
|
67
|
+
if (!turn) {
|
|
68
|
+
return truncateToWidth(left, width, "…");
|
|
69
|
+
}
|
|
70
|
+
// Cost is omitted when zero (e.g. subscription/untracked) — still show tokens.
|
|
71
|
+
const showCost = turn.cost > 0;
|
|
72
|
+
const costPlain = showCost ? ` $${turn.cost.toFixed(3)}` : "";
|
|
73
|
+
const turnPlain = `turn ↑${formatTokens(turn.input)} ↓${formatTokens(turn.output)}${costPlain}`;
|
|
74
|
+
let turnText = theme.fg("dim", "turn ↑") +
|
|
75
|
+
theme.bold(formatTokens(turn.input)) +
|
|
76
|
+
theme.fg("dim", " ↓") +
|
|
77
|
+
theme.bold(formatTokens(turn.output));
|
|
78
|
+
if (showCost) {
|
|
79
|
+
turnText += theme.fg("dim", " ") + theme.bold(`$${turn.cost.toFixed(3)}`);
|
|
80
|
+
}
|
|
81
|
+
if (visibleWidth(leftPlain) + 2 + visibleWidth(turnPlain) > width) {
|
|
82
|
+
// Too narrow for both — keep the stamp + count.
|
|
83
|
+
return truncateToWidth(left, width, "…");
|
|
52
84
|
}
|
|
53
|
-
|
|
85
|
+
const pad = Math.max(2, width - visibleWidth(leftPlain) - visibleWidth(turnPlain));
|
|
86
|
+
return left + " ".repeat(pad) + turnText;
|
|
54
87
|
}
|
|
55
88
|
function formatTokens(count) {
|
|
56
89
|
if (count < 1000)
|
|
@@ -63,29 +96,26 @@ function formatTokens(count) {
|
|
|
63
96
|
}
|
|
64
97
|
function formatTaskLine(task, width) {
|
|
65
98
|
const icon = theme.fg(taskStatusColor(task.status), TASK_STATUS_ICON[task.status]);
|
|
66
|
-
const modeTag = task.subagentMode ? theme.fg("accent", ` [${task.subagentMode}]`) : "";
|
|
67
|
-
const plainModeTag = task.subagentMode ? ` [${task.subagentMode}]` : "";
|
|
68
99
|
const idLabel = `#${task.id}`;
|
|
69
100
|
const title = task.title;
|
|
70
|
-
// Finished tasks carry an audit stamp: tokens + elapsed time.
|
|
101
|
+
// Finished tasks carry an audit stamp: total tokens used + elapsed time.
|
|
71
102
|
const settled = task.status === "done" || task.status === "failed";
|
|
72
103
|
let rightPlain = "";
|
|
73
104
|
if (settled) {
|
|
74
105
|
const parts = [];
|
|
75
106
|
if (task.usage) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
parts.push(`↓${formatTokens(task.usage.output)}`);
|
|
107
|
+
const total = task.usage.input + task.usage.output;
|
|
108
|
+
if (total > 0)
|
|
109
|
+
parts.push(formatTokens(total));
|
|
80
110
|
}
|
|
81
111
|
parts.push(formatElapsed(task));
|
|
82
112
|
rightPlain = parts.join(" · ");
|
|
83
113
|
}
|
|
84
114
|
const rightWidth = rightPlain ? visibleWidth(rightPlain) + 1 : 0;
|
|
85
115
|
const leftWidth = Math.max(0, width - rightWidth);
|
|
86
|
-
const plainText = `${TASK_STATUS_ICON[task.status]} ${idLabel} ${title}
|
|
116
|
+
const plainText = `${TASK_STATUS_ICON[task.status]} ${idLabel} ${title}`;
|
|
87
117
|
const available = Math.max(0, leftWidth - visibleWidth(plainText) + visibleWidth(title));
|
|
88
|
-
const left = truncateToWidth(`${icon} ${idLabel} ${title}`, available, "
|
|
118
|
+
const left = truncateToWidth(`${icon} ${idLabel} ${title}`, available, "…");
|
|
89
119
|
if (!rightPlain)
|
|
90
120
|
return left;
|
|
91
121
|
const pad = Math.max(1, width - visibleWidth(left) - visibleWidth(rightPlain));
|
|
@@ -96,10 +126,11 @@ function formatTaskLine(task, width) {
|
|
|
96
126
|
*
|
|
97
127
|
* - A ledger header (watched/reviewed stamp + done/total count) tops the list.
|
|
98
128
|
* - Shows all tasks with all statuses (pending / in_progress / done / failed).
|
|
129
|
+
* - Subagent mode is intentionally NOT shown here (e.g. no "[explore]" tag) — the
|
|
130
|
+
* task title is the meaningful label; the mode adds noise in the pane.
|
|
99
131
|
* - LIFO within the window: newest tasks appear at the bottom (closest to the prompt).
|
|
100
|
-
* - Finished tasks carry their wall-clock cost and
|
|
101
|
-
*
|
|
102
|
-
* not the moment they finish.
|
|
132
|
+
* - Finished tasks carry their wall-clock cost and stay visible until the next
|
|
133
|
+
* user message arrives (see taskStore.retireFinished()), not the moment they finish.
|
|
103
134
|
* - Collapses to zero lines when there are no tasks.
|
|
104
135
|
*/
|
|
105
136
|
export class TaskPanelComponent {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-panel.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/task-panel.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Component } from \"@kolisachint/hoocode-tui\";\nimport { truncateToWidth, visibleWidth } from \"@kolisachint/hoocode-tui\";\nimport type { Task, TaskStatus } from \"../../../core/task-store.js\";\nimport { taskStore } from \"../../../core/task-store.js\";\nimport { theme } from \"../theme/theme.js\";\n\nconst TASK_STATUS_ICON: Record<TaskStatus, string> = {\n\tpending: \"●\",\n\tin_progress: \"◐\",\n\tdone: \"✓\",\n\tfailed: \"✗\",\n};\n\nfunction taskStatusColor(status: TaskStatus): \"dim\" | \"warning\" | \"success\" | \"error\" {\n\tswitch (status) {\n\t\tcase \"in_progress\":\n\t\t\treturn \"warning\";\n\t\tcase \"done\":\n\t\t\treturn \"success\";\n\t\tcase \"failed\":\n\t\t\treturn \"error\";\n\t\tdefault:\n\t\t\treturn \"dim\";\n\t}\n}\n\n/** Wall-clock time a task occupied, derived from its create/update stamps. */\nfunction formatElapsed(task: Task): string {\n\tconst secs = Math.max(0, (task.updatedAt - task.createdAt) / 1000);\n\tif (secs < 10) return `${secs.toFixed(1)}s`;\n\tif (secs < 60) return `${Math.round(secs)}s`;\n\tconst mins = Math.floor(secs / 60);\n\tconst rem = Math.round(secs % 60);\n\treturn `${mins}m${rem.toString().padStart(2, \"0\")}s`;\n}\n\n/**\n * Ledger header: a watched/reviewed stamp on the left, the done/total count on\n * the right. The panel is an audit trail — the stamp makes the deterministic\n * \"every task watched & reviewed\" state glanceable.\n */\nfunction formatHeader(tasks: readonly Task[], width: number): string {\n\tconst total = tasks.length;\n\tconst done = tasks.filter((t) => t.status === \"done\").length;\n\tconst watching = tasks.some((t) => t.status === \"in_progress\" || t.status === \"pending\");\n\n\tconst stampPlain = watching ? \"⟳ watching\" : \"reviewed ✓ · deterministic\";\n\tconst stamp = watching\n\t\t? theme.fg(\"warning\", \"⟳ watching\")\n\t\t: theme.fg(\"success\", \"reviewed ✓\") + theme.fg(\"dim\", \" · deterministic\");\n\n\tconst countPlain = `${done}/${total} done`;\n\tconst count = theme.fg(\"dim\", countPlain);\n\n\tconst pad = Math.max(2, width - visibleWidth(stampPlain) - visibleWidth(countPlain));\n\tif (visibleWidth(stampPlain) + 2 + visibleWidth(countPlain) > width) {\n\t\t// Too narrow for both — keep just the stamp.\n\t\treturn stamp;\n\t}\n\treturn stamp + \" \".repeat(pad) + count;\n}\n\nfunction formatTokens(count: number): string {\n\tif (count < 1000) return count.toString();\n\tif (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n\tif (count < 1000000) return `${Math.round(count / 1000)}k`;\n\treturn `${(count / 1000000).toFixed(1)}M`;\n}\n\nfunction formatTaskLine(task: Task, width: number): string {\n\tconst icon = theme.fg(taskStatusColor(task.status), TASK_STATUS_ICON[task.status]);\n\tconst modeTag = task.subagentMode ? theme.fg(\"accent\", ` [${task.subagentMode}]`) : \"\";\n\tconst plainModeTag = task.subagentMode ? ` [${task.subagentMode}]` : \"\";\n\tconst idLabel = `#${task.id}`;\n\tconst title = task.title;\n\n\t// Finished tasks carry an audit stamp: tokens + elapsed time.\n\tconst settled = task.status === \"done\" || task.status === \"failed\";\n\tlet rightPlain = \"\";\n\tif (settled) {\n\t\tconst parts: string[] = [];\n\t\tif (task.usage) {\n\t\t\tif (task.usage.input) parts.push(`↑${formatTokens(task.usage.input)}`);\n\t\t\tif (task.usage.output) parts.push(`↓${formatTokens(task.usage.output)}`);\n\t\t}\n\t\tparts.push(formatElapsed(task));\n\t\trightPlain = parts.join(\" · \");\n\t}\n\tconst rightWidth = rightPlain ? visibleWidth(rightPlain) + 1 : 0;\n\tconst leftWidth = Math.max(0, width - rightWidth);\n\n\tconst plainText = `${TASK_STATUS_ICON[task.status]} ${idLabel} ${title}${plainModeTag}`;\n\tconst available = Math.max(0, leftWidth - visibleWidth(plainText) + visibleWidth(title));\n\tconst left = truncateToWidth(`${icon} ${idLabel} ${title}`, available, \"...\") + modeTag;\n\n\tif (!rightPlain) return left;\n\n\tconst pad = Math.max(1, width - visibleWidth(left) - visibleWidth(rightPlain));\n\treturn left + \" \".repeat(pad) + theme.fg(\"dim\", rightPlain);\n}\n\n/**\n * Task panel rendered just above the editor prompt.\n *\n * - A ledger header (watched/reviewed stamp + done/total count) tops the list.\n * - Shows all tasks with all statuses (pending / in_progress / done / failed).\n * - LIFO within the window: newest tasks appear at the bottom (closest to the prompt).\n * - Finished tasks carry their wall-clock cost and retire only when the main\n * agent moves on after a parallel subagent spawn (see taskStore.retireFinished()),\n * not the moment they finish.\n * - Collapses to zero lines when there are no tasks.\n */\nexport class TaskPanelComponent implements Component {\n\tinvalidate(): void {\n\t\t// No cached rendering state.\n\t}\n\n\trender(width: number): string[] {\n\t\tconst tasks = taskStore.list();\n\t\tif (tasks.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst lines: string[] = [formatHeader(tasks, width)];\n\t\tfor (const task of tasks) {\n\t\t\tlines.push(formatTaskLine(task, width));\n\t\t}\n\t\treturn lines;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"task-panel.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/task-panel.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Component } from \"@kolisachint/hoocode-tui\";\nimport { truncateToWidth, visibleWidth } from \"@kolisachint/hoocode-tui\";\nimport type { Task, TaskStatus } from \"../../../core/task-store.js\";\nimport { taskStore } from \"../../../core/task-store.js\";\nimport { theme } from \"../theme/theme.js\";\n\nconst TASK_STATUS_ICON: Record<TaskStatus, string> = {\n\tpending: \"●\",\n\tin_progress: \"◐\",\n\tdone: \"✓\",\n\tfailed: \"✗\",\n};\n\nfunction taskStatusColor(status: TaskStatus): \"dim\" | \"warning\" | \"success\" | \"error\" {\n\tswitch (status) {\n\t\tcase \"in_progress\":\n\t\t\treturn \"warning\";\n\t\tcase \"done\":\n\t\t\treturn \"success\";\n\t\tcase \"failed\":\n\t\t\treturn \"error\";\n\t\tdefault:\n\t\t\treturn \"dim\";\n\t}\n}\n\n/** Wall-clock time a task occupied, derived from its create/update stamps. */\nfunction formatElapsed(task: Task): string {\n\tconst secs = Math.max(0, (task.updatedAt - task.createdAt) / 1000);\n\tif (secs < 10) return `${secs.toFixed(1)}s`;\n\tif (secs < 60) return `${Math.round(secs)}s`;\n\tconst mins = Math.floor(secs / 60);\n\tconst rem = Math.round(secs % 60);\n\treturn `${mins}m${rem.toString().padStart(2, \"0\")}s`;\n}\n\n/** Sum the token + cost usage reported by the tasks shown this turn. */\nfunction sumTurnUsage(tasks: readonly Task[]): { input: number; output: number; cost: number } | null {\n\tlet input = 0;\n\tlet output = 0;\n\tlet cost = 0;\n\tfor (const task of tasks) {\n\t\tif (!task.usage) continue;\n\t\tinput += task.usage.input;\n\t\toutput += task.usage.output;\n\t\tcost += task.usage.cost;\n\t}\n\tif (input === 0 && output === 0 && cost === 0) return null;\n\treturn { input, output, cost };\n}\n\n/**\n * Ledger header: a watched/reviewed stamp plus done/total count on the left, and\n * the per-turn token + cost delta (summed across the tasks below) on the right.\n * The panel is an audit trail — the stamp makes the deterministic \"every task\n * watched & reviewed\" state glanceable, and the delta surfaces what the turn cost.\n */\nfunction formatHeader(tasks: readonly Task[], width: number): string {\n\tconst total = tasks.length;\n\tconst done = tasks.filter((t) => t.status === \"done\").length;\n\tconst watching = tasks.some((t) => t.status === \"in_progress\" || t.status === \"pending\");\n\n\tconst stampPlain = watching ? \"⟳ watching\" : \"reviewed ✓ · deterministic\";\n\tconst stamp = watching\n\t\t? theme.fg(\"warning\", \"⟳ watching\")\n\t\t: theme.fg(\"success\", \"reviewed ✓\") + theme.fg(\"dim\", \" · deterministic\");\n\n\tconst countPlain = `${done}/${total} done`;\n\tconst leftPlain = `${stampPlain} ${countPlain}`;\n\tconst left = `${stamp} ${theme.fg(\"dim\", countPlain)}`;\n\n\tconst turn = sumTurnUsage(tasks);\n\tif (!turn) {\n\t\treturn truncateToWidth(left, width, \"…\");\n\t}\n\n\t// Cost is omitted when zero (e.g. subscription/untracked) — still show tokens.\n\tconst showCost = turn.cost > 0;\n\tconst costPlain = showCost ? ` $${turn.cost.toFixed(3)}` : \"\";\n\tconst turnPlain = `turn ↑${formatTokens(turn.input)} ↓${formatTokens(turn.output)}${costPlain}`;\n\tlet turnText =\n\t\ttheme.fg(\"dim\", \"turn ↑\") +\n\t\ttheme.bold(formatTokens(turn.input)) +\n\t\ttheme.fg(\"dim\", \" ↓\") +\n\t\ttheme.bold(formatTokens(turn.output));\n\tif (showCost) {\n\t\tturnText += theme.fg(\"dim\", \" \") + theme.bold(`$${turn.cost.toFixed(3)}`);\n\t}\n\n\tif (visibleWidth(leftPlain) + 2 + visibleWidth(turnPlain) > width) {\n\t\t// Too narrow for both — keep the stamp + count.\n\t\treturn truncateToWidth(left, width, \"…\");\n\t}\n\tconst pad = Math.max(2, width - visibleWidth(leftPlain) - visibleWidth(turnPlain));\n\treturn left + \" \".repeat(pad) + turnText;\n}\n\nfunction formatTokens(count: number): string {\n\tif (count < 1000) return count.toString();\n\tif (count < 10000) return `${(count / 1000).toFixed(1)}k`;\n\tif (count < 1000000) return `${Math.round(count / 1000)}k`;\n\treturn `${(count / 1000000).toFixed(1)}M`;\n}\n\nfunction formatTaskLine(task: Task, width: number): string {\n\tconst icon = theme.fg(taskStatusColor(task.status), TASK_STATUS_ICON[task.status]);\n\tconst idLabel = `#${task.id}`;\n\tconst title = task.title;\n\n\t// Finished tasks carry an audit stamp: total tokens used + elapsed time.\n\tconst settled = task.status === \"done\" || task.status === \"failed\";\n\tlet rightPlain = \"\";\n\tif (settled) {\n\t\tconst parts: string[] = [];\n\t\tif (task.usage) {\n\t\t\tconst total = task.usage.input + task.usage.output;\n\t\t\tif (total > 0) parts.push(formatTokens(total));\n\t\t}\n\t\tparts.push(formatElapsed(task));\n\t\trightPlain = parts.join(\" · \");\n\t}\n\tconst rightWidth = rightPlain ? visibleWidth(rightPlain) + 1 : 0;\n\tconst leftWidth = Math.max(0, width - rightWidth);\n\n\tconst plainText = `${TASK_STATUS_ICON[task.status]} ${idLabel} ${title}`;\n\tconst available = Math.max(0, leftWidth - visibleWidth(plainText) + visibleWidth(title));\n\tconst left = truncateToWidth(`${icon} ${idLabel} ${title}`, available, \"…\");\n\n\tif (!rightPlain) return left;\n\n\tconst pad = Math.max(1, width - visibleWidth(left) - visibleWidth(rightPlain));\n\treturn left + \" \".repeat(pad) + theme.fg(\"dim\", rightPlain);\n}\n\n/**\n * Task panel rendered just above the editor prompt.\n *\n * - A ledger header (watched/reviewed stamp + done/total count) tops the list.\n * - Shows all tasks with all statuses (pending / in_progress / done / failed).\n * - Subagent mode is intentionally NOT shown here (e.g. no \"[explore]\" tag) — the\n * task title is the meaningful label; the mode adds noise in the pane.\n * - LIFO within the window: newest tasks appear at the bottom (closest to the prompt).\n * - Finished tasks carry their wall-clock cost and stay visible until the next\n * user message arrives (see taskStore.retireFinished()), not the moment they finish.\n * - Collapses to zero lines when there are no tasks.\n */\nexport class TaskPanelComponent implements Component {\n\tinvalidate(): void {\n\t\t// No cached rendering state.\n\t}\n\n\trender(width: number): string[] {\n\t\tconst tasks = taskStore.list();\n\t\tif (tasks.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst lines: string[] = [formatHeader(tasks, width)];\n\t\tfor (const task of tasks) {\n\t\t\tlines.push(formatTaskLine(task, width));\n\t\t}\n\t\treturn lines;\n\t}\n}\n"]}
|