@kolisachint/hoocode-agent 0.4.133 → 0.4.135
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 +4 -0
- package/dist/core/model-categories.d.ts +51 -12
- package/dist/core/model-categories.d.ts.map +1 -1
- package/dist/core/model-categories.js +89 -14
- package/dist/core/model-categories.js.map +1 -1
- package/dist/core/settings-types.d.ts +14 -2
- package/dist/core/settings-types.d.ts.map +1 -1
- package/dist/core/settings-types.js.map +1 -1
- package/dist/core/subagent-pool-instance.d.ts +10 -2
- package/dist/core/subagent-pool-instance.d.ts.map +1 -1
- package/dist/core/subagent-pool-instance.js +10 -2
- package/dist/core/subagent-pool-instance.js.map +1 -1
- package/dist/core/subagent-pool.d.ts +9 -0
- package/dist/core/subagent-pool.d.ts.map +1 -1
- package/dist/core/subagent-pool.js +6 -3
- package/dist/core/subagent-pool.js.map +1 -1
- package/dist/core/tools/subagent.d.ts.map +1 -1
- package/dist/core/tools/subagent.js +12 -4
- package/dist/core/tools/subagent.js.map +1 -1
- package/dist/core/warm-subagent-pool-instance.d.ts +9 -2
- package/dist/core/warm-subagent-pool-instance.d.ts.map +1 -1
- package/dist/core/warm-subagent-pool-instance.js +9 -3
- package/dist/core/warm-subagent-pool-instance.js.map +1 -1
- package/dist/core/warm-subagent-pool.d.ts +13 -2
- package/dist/core/warm-subagent-pool.d.ts.map +1 -1
- package/dist/core/warm-subagent-pool.js +16 -7
- package/dist/core/warm-subagent-pool.js.map +1 -1
- package/dist/modes/interactive/command-executor.d.ts.map +1 -1
- package/dist/modes/interactive/command-executor.js +1 -1
- package/dist/modes/interactive/command-executor.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
|
@@ -40,7 +40,7 @@ export class WarmSubagentWorker {
|
|
|
40
40
|
env;
|
|
41
41
|
client;
|
|
42
42
|
alive = true;
|
|
43
|
-
constructor(key, options, env, registry, skillPaths, settings,
|
|
43
|
+
constructor(key, options, env, registry, skillPaths, settings, availableModels,
|
|
44
44
|
/** Spawn command override (tests inject a fake RPC child); defaults to the real spawn command. */
|
|
45
45
|
spawnCommand) {
|
|
46
46
|
this.key = key;
|
|
@@ -51,7 +51,7 @@ export class WarmSubagentWorker {
|
|
|
51
51
|
prefixArgs,
|
|
52
52
|
cwd: options.cwd,
|
|
53
53
|
env: this.env,
|
|
54
|
-
args: buildWorkerArgs(options, registry, skillPaths, settings),
|
|
54
|
+
args: buildWorkerArgs(options, registry, skillPaths, settings, availableModels),
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
/** Boot the child. Throws (as WarmWorkerError) if it fails to come up. */
|
|
@@ -153,6 +153,7 @@ export class WarmSubagentPool {
|
|
|
153
153
|
cwd;
|
|
154
154
|
settings;
|
|
155
155
|
skillPaths;
|
|
156
|
+
availableModels;
|
|
156
157
|
maxPerKey;
|
|
157
158
|
idleTtlMs;
|
|
158
159
|
spawnCommand;
|
|
@@ -161,12 +162,18 @@ export class WarmSubagentPool {
|
|
|
161
162
|
liveCount = new Map();
|
|
162
163
|
disposed = false;
|
|
163
164
|
registry;
|
|
164
|
-
constructor(cwd, settings, skillPaths = [],
|
|
165
|
+
constructor(cwd, settings, skillPaths = [],
|
|
166
|
+
/**
|
|
167
|
+
* Available models used to derive default model-category mappings when a tier
|
|
168
|
+
* is not explicitly set in `settings.modelCategories` (snapshot at creation).
|
|
169
|
+
*/
|
|
170
|
+
availableModels = [], maxPerKey = 2, idleTtlMs = 30_000,
|
|
165
171
|
/** Spawn command override (tests inject a fake RPC child); defaults to the real spawn command. */
|
|
166
172
|
spawnCommand) {
|
|
167
173
|
this.cwd = cwd;
|
|
168
174
|
this.settings = settings;
|
|
169
175
|
this.skillPaths = skillPaths;
|
|
176
|
+
this.availableModels = availableModels;
|
|
170
177
|
this.maxPerKey = maxPerKey;
|
|
171
178
|
this.idleTtlMs = idleTtlMs;
|
|
172
179
|
this.spawnCommand = spawnCommand;
|
|
@@ -186,7 +193,9 @@ export class WarmSubagentPool {
|
|
|
186
193
|
}
|
|
187
194
|
/** Stable key for one worker configuration. */
|
|
188
195
|
keyFor(options) {
|
|
189
|
-
const resolved = options.model
|
|
196
|
+
const resolved = options.model
|
|
197
|
+
? resolveModelReference(options.model, this.settings, this.availableModels)
|
|
198
|
+
: undefined;
|
|
190
199
|
return `${options.agentType}::${resolved ?? "default"}::${options.provider ?? "default"}`;
|
|
191
200
|
}
|
|
192
201
|
/** Environment for a warm child: depth stamp + MCP skip, mirroring SubagentPool.childSpawnEnv. */
|
|
@@ -235,7 +244,7 @@ export class WarmSubagentPool {
|
|
|
235
244
|
this.decLive(key);
|
|
236
245
|
await worker.dispose();
|
|
237
246
|
}
|
|
238
|
-
const worker = new WarmSubagentWorker(key, options, this.childEnv(options.agentType), this.getRegistry(), this.skillPaths, this.settings, this.spawnCommand);
|
|
247
|
+
const worker = new WarmSubagentWorker(key, options, this.childEnv(options.agentType), this.getRegistry(), this.skillPaths, this.settings, this.availableModels, this.spawnCommand);
|
|
239
248
|
this.incLive(key);
|
|
240
249
|
try {
|
|
241
250
|
await worker.start();
|
|
@@ -331,7 +340,7 @@ export class WarmSubagentPool {
|
|
|
331
340
|
* cap, skills) but omits the one-shot `--mode json` / `--session` / `--task-id`
|
|
332
341
|
* bits, since the worker runs persistently in RPC mode and resets via new_session.
|
|
333
342
|
*/
|
|
334
|
-
function buildWorkerArgs(options, registry, skillPaths, settings) {
|
|
343
|
+
function buildWorkerArgs(options, registry, skillPaths, settings, availableModels) {
|
|
335
344
|
const args = [];
|
|
336
345
|
const def = registry.get(options.agentType);
|
|
337
346
|
if (def?.prompt)
|
|
@@ -358,7 +367,7 @@ function buildWorkerArgs(options, registry, skillPaths, settings) {
|
|
|
358
367
|
// wins, else the requested model/category, resolved to a concrete id.
|
|
359
368
|
const explicitModel = def?.model && def.model !== MODEL_INHERIT ? def.model : undefined;
|
|
360
369
|
const rawModel = explicitModel ?? options.model;
|
|
361
|
-
const modelToUse = rawModel ? resolveModelReference(rawModel, settings) : undefined;
|
|
370
|
+
const modelToUse = rawModel ? resolveModelReference(rawModel, settings, availableModels) : undefined;
|
|
362
371
|
if (modelToUse)
|
|
363
372
|
args.push("--model", modelToUse);
|
|
364
373
|
if (options.provider)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"warm-subagent-pool.js","sourceRoot":"","sources":["../../src/core/warm-subagent-pool.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Warm subagent worker pool (experimental, opt-in via `--warm-subagents` /\n * settings.warmSubagents; default off).\n *\n * The cold path (SubagentPool) re-execs the whole CLI for every dispatch, paying\n * a full module-load + resource-graph boot each time. A warm worker instead keeps\n * a long-lived child running in RPC mode and hands it one task at a time over the\n * existing JSON-line protocol: `new_session` resets its conversation between\n * tasks, `prompt` runs the task, and `agent_end` on the event stream signals\n * completion, after which the answer + usage are pulled inline (no result.json\n * disk round-trip, no OutputVerifier). The first task per worker still pays the\n * boot; every reuse after that skips it.\n *\n * Scope/limits (deliberately conservative — see the dispatch integration):\n * - Workers are pinned per agent type: RPC has no per-prompt system-prompt/tools\n * swap, so each (agentType, model, provider) is its own worker config.\n * - Only non-resume, non-fork dispatches are eligible; resume/fork need a\n * persisted/forked session the warm path does not own.\n * - Any worker/infra failure falls back to the cold pool, so enabling this can\n * only change latency, never whether a task can run.\n */\n\nimport type { AgentEvent } from \"@kolisachint/hoocode-agent-core\";\nimport { getSubagentSpawnCommand } from \"../config.js\";\nimport { RpcClient } from \"../modes/rpc/rpc-client.js\";\nimport { MODEL_INHERIT } from \"./agent-frontmatter.js\";\nimport { type AgentRegistry, loadAgentRegistry } from \"./agent-registry.js\";\nimport { resolveModelReference } from \"./model-categories.js\";\nimport type { Settings } from \"./settings-manager.js\";\nimport {\n\tcurrentSubagentDepth,\n\tDEFER_MCP_SCHEMAS_ENV,\n\tresolveMaxSubagentDepth,\n\tSUBAGENT_DEPTH_ENV,\n\tSUBAGENT_SKIP_MCP_ENV,\n\ttoolAllowlistNeedsMcp,\n} from \"./subagent-depth.js\";\nimport { DEFAULT_SUBAGENT_MAX_TURNS } from \"./subagent-pool.js\";\n\n/** Usage totals pulled from a worker after a task, shaped like SubagentResultFile.usage. */\nexport interface WarmUsage {\n\tinput: number;\n\toutput: number;\n\tcacheRead: number;\n\tcacheWrite: number;\n\tcost: number;\n}\n\n/** Outcome of running one task on a warm worker. */\nexport interface WarmRunResult {\n\tok: boolean;\n\tstatus: \"complete\" | \"failed\";\n\t/** The subagent's final assistant text (its answer to the caller). */\n\tsummary: string;\n\tusage?: WarmUsage;\n\terror?: string;\n}\n\n/** Per-dispatch inputs that select and configure a worker. */\nexport interface WarmDispatchOptions {\n\tagentType: string;\n\tcwd: string;\n\t/** Model id or category (fast/standard/capable); resolved to a concrete id. */\n\tmodel?: string;\n\tprovider?: string;\n}\n\n/** Reports the tool a warm worker is currently running (\"\" = idle between tools). */\nexport type WarmProgressCallback = (activity: string) => void;\n\n/** A run failed at the infrastructure level (worker crash, timeout, protocol error). */\nexport class WarmWorkerError extends Error {}\n\n/** Default per-task wait before declaring a warm run stalled and failing over to cold. */\nconst WARM_RUN_TIMEOUT_MS = 180_000;\n\n/**\n * One long-lived RPC child pinned to a single agent-type configuration. Reused\n * across tasks via reset(); a crash makes it not-alive so the pool discards it.\n */\nexport class WarmSubagentWorker {\n\tprivate readonly client: RpcClient;\n\tprivate alive = true;\n\n\tconstructor(\n\t\treadonly key: string,\n\t\toptions: WarmDispatchOptions,\n\t\tprivate readonly env: NodeJS.ProcessEnv,\n\t\tregistry: AgentRegistry,\n\t\tskillPaths: readonly string[],\n\t\tsettings: Settings | undefined,\n\t\t/** Spawn command override (tests inject a fake RPC child); defaults to the real spawn command. */\n\t\tspawnCommand?: { executable: string; prefixArgs: string[] },\n\t) {\n\t\tconst { executable, prefixArgs } = spawnCommand ?? getSubagentSpawnCommand();\n\t\tthis.client = new RpcClient({\n\t\t\texecutable,\n\t\t\tprefixArgs,\n\t\t\tcwd: options.cwd,\n\t\t\tenv: this.env as Record<string, string>,\n\t\t\targs: buildWorkerArgs(options, registry, skillPaths, settings),\n\t\t});\n\t}\n\n\t/** Boot the child. Throws (as WarmWorkerError) if it fails to come up. */\n\tasync start(): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.start();\n\t\t} catch (error) {\n\t\t\tthis.alive = false;\n\t\t\tthrow new WarmWorkerError(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\n\tisAlive(): boolean {\n\t\treturn this.alive;\n\t}\n\n\t/**\n\t * Run one task to completion and return its answer + usage. Throws\n\t * WarmWorkerError on an infra failure (crash/timeout/protocol) so the caller\n\t * can fall back to the cold pool; a task that ran but reported failure returns\n\t * `{ ok: false }` instead (no fall back — the work was actually done).\n\t */\n\tasync run(\n\t\tprompt: string,\n\t\tonActivity?: WarmProgressCallback,\n\t\ttimeoutMs = WARM_RUN_TIMEOUT_MS,\n\t): Promise<WarmRunResult> {\n\t\tif (!this.alive) throw new WarmWorkerError(\"worker is not alive\");\n\t\t// Mirror the cold pool's coarse progress: report the tool the child is\n\t\t// currently running so a warm dispatch's task row reads \"⋯ grep\" rather than a\n\t\t// static \"running…\". Cleared between tools and at turn end.\n\t\tconst detachProgress = onActivity ? this.tapProgress(onActivity) : undefined;\n\t\ttry {\n\t\t\tconst events = await this.client.promptAndWait(prompt, undefined, timeoutMs);\n\t\t\tconst failure = firstTurnError(events);\n\t\t\tconst summary = (await this.client.getLastAssistantText()) ?? \"\";\n\t\t\tconst usage = await this.readUsage();\n\t\t\tif (failure) {\n\t\t\t\treturn { ok: false, status: \"failed\", summary, usage, error: failure };\n\t\t\t}\n\t\t\treturn { ok: true, status: \"complete\", summary, usage };\n\t\t} catch (error) {\n\t\t\t// A prompt/await failure means the child is no longer trustworthy: mark it\n\t\t\t// dead so the pool discards it, and signal infra failure for fallback.\n\t\t\tthis.alive = false;\n\t\t\tthrow new WarmWorkerError(error instanceof Error ? error.message : String(error));\n\t\t} finally {\n\t\t\tdetachProgress?.();\n\t\t\tonActivity?.(\"\");\n\t\t}\n\t}\n\n\t/** Forward the child's coarse tool-lifecycle events to an activity callback. */\n\tprivate tapProgress(onActivity: WarmProgressCallback): () => void {\n\t\treturn this.client.onEvent((event) => {\n\t\t\tconst e = event as { type?: string; toolName?: string };\n\t\t\tif (e.type === \"tool_execution_start\") onActivity(typeof e.toolName === \"string\" ? e.toolName : \"\");\n\t\t\telse if (e.type === \"tool_execution_end\" || e.type === \"turn_end\") onActivity(\"\");\n\t\t});\n\t}\n\n\t/** Reset the worker's conversation so it can take the next task cleanly. */\n\tasync reset(): Promise<void> {\n\t\tif (!this.alive) return;\n\t\ttry {\n\t\t\tawait this.client.newSession();\n\t\t} catch (error) {\n\t\t\t// A reset failure leaves the worker in an unknown state — retire it.\n\t\t\tthis.alive = false;\n\t\t\tthrow new WarmWorkerError(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\tthis.alive = false;\n\t\tawait this.client.stop().catch(() => {});\n\t}\n\n\tprivate async readUsage(): Promise<WarmUsage | undefined> {\n\t\ttry {\n\t\t\tconst stats = await this.client.getSessionStats();\n\t\t\treturn {\n\t\t\t\tinput: stats.tokens.input,\n\t\t\t\toutput: stats.tokens.output,\n\t\t\t\tcacheRead: stats.tokens.cacheRead,\n\t\t\t\tcacheWrite: stats.tokens.cacheWrite,\n\t\t\t\tcost: stats.cost,\n\t\t\t};\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n}\n\n/**\n * Pool of warm workers keyed by agent-type configuration. Hands out an idle\n * worker (or boots a new one up to a per-key cap), and on release resets the\n * worker and returns it to the idle set with an idle-TTL reclaim timer.\n */\nexport class WarmSubagentPool {\n\tprivate idle = new Map<string, WarmSubagentWorker[]>();\n\tprivate reclaimTimers = new Map<WarmSubagentWorker, ReturnType<typeof setTimeout>>();\n\tprivate liveCount = new Map<string, number>();\n\tprivate disposed = false;\n\tprivate registry?: AgentRegistry;\n\n\tconstructor(\n\t\tprivate readonly cwd: string,\n\t\tprivate readonly settings: Settings | undefined,\n\t\tprivate skillPaths: string[] = [],\n\t\tprivate readonly maxPerKey = 2,\n\t\tprivate readonly idleTtlMs = 30_000,\n\t\t/** Spawn command override (tests inject a fake RPC child); defaults to the real spawn command. */\n\t\tprivate readonly spawnCommand?: { executable: string; prefixArgs: string[] },\n\t) {}\n\n\tupdateSkillPaths(paths: string[]): void {\n\t\tthis.skillPaths = [...paths];\n\t}\n\n\tprivate getRegistry(): AgentRegistry {\n\t\tif (!this.registry) this.registry = loadAgentRegistry({ cwd: this.cwd });\n\t\treturn this.registry;\n\t}\n\n\t/** A registry agent is poolable when it is not a fork agent (fork needs a forked session). */\n\tisPoolable(agentType: string): boolean {\n\t\tconst def = this.getRegistry().get(agentType);\n\t\treturn def !== undefined && def.fork !== true;\n\t}\n\n\t/** Stable key for one worker configuration. */\n\tprivate keyFor(options: WarmDispatchOptions): string {\n\t\tconst resolved = options.model ? resolveModelReference(options.model, this.settings) : undefined;\n\t\treturn `${options.agentType}::${resolved ?? \"default\"}::${options.provider ?? \"default\"}`;\n\t}\n\n\t/** Environment for a warm child: depth stamp + MCP skip, mirroring SubagentPool.childSpawnEnv. */\n\tprivate childEnv(agentType: string): NodeJS.ProcessEnv {\n\t\tconst env: NodeJS.ProcessEnv = {\n\t\t\t...process.env,\n\t\t\t[SUBAGENT_DEPTH_ENV]: String(currentSubagentDepth(process.env) + 1),\n\t\t};\n\t\tconst def = this.getRegistry().get(agentType);\n\t\tif (!toolAllowlistNeedsMcp(def?.tools)) env[SUBAGENT_SKIP_MCP_ENV] = \"1\";\n\t\t// A child never defers MCP schemas: if it needs MCP it eager-registers its\n\t\t// allowlisted tools at dispatch so they are immediately callable (spec §2).\n\t\tdelete env[DEFER_MCP_SCHEMAS_ENV];\n\t\treturn env;\n\t}\n\n\t/**\n\t * Run a task on a warm worker end to end: acquire (reuse or boot), run, then\n\t * release back to the pool. Throws WarmWorkerError on infra failure so the\n\t * caller can fall back to the cold pool.\n\t */\n\tasync dispatch(\n\t\tprompt: string,\n\t\toptions: WarmDispatchOptions,\n\t\tonActivity?: WarmProgressCallback,\n\t): Promise<WarmRunResult> {\n\t\tif (this.disposed) throw new WarmWorkerError(\"warm pool disposed\");\n\t\tconst worker = await this.acquire(options);\n\t\ttry {\n\t\t\tconst result = await worker.run(prompt, onActivity);\n\t\t\tawait this.release(worker);\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\t// Infra failure: the worker already marked itself dead; drop it entirely.\n\t\t\tawait this.discard(worker);\n\t\t\tthrow error instanceof WarmWorkerError ? error : new WarmWorkerError(String(error));\n\t\t}\n\t}\n\n\tprivate async acquire(options: WarmDispatchOptions): Promise<WarmSubagentWorker> {\n\t\tconst key = this.keyFor(options);\n\t\tconst pool = this.idle.get(key);\n\t\twhile (pool && pool.length > 0) {\n\t\t\tconst worker = pool.pop()!;\n\t\t\tthis.clearReclaim(worker);\n\t\t\tif (worker.isAlive()) return worker;\n\t\t\t// Dead idle worker (child exited while parked): drop and try the next.\n\t\t\tthis.decLive(key);\n\t\t\tawait worker.dispose();\n\t\t}\n\n\t\tconst worker = new WarmSubagentWorker(\n\t\t\tkey,\n\t\t\toptions,\n\t\t\tthis.childEnv(options.agentType),\n\t\t\tthis.getRegistry(),\n\t\t\tthis.skillPaths,\n\t\t\tthis.settings,\n\t\t\tthis.spawnCommand,\n\t\t);\n\t\tthis.incLive(key);\n\t\ttry {\n\t\t\tawait worker.start();\n\t\t} catch (error) {\n\t\t\tthis.decLive(key);\n\t\t\tthrow error;\n\t\t}\n\t\treturn worker;\n\t}\n\n\tprivate async release(worker: WarmSubagentWorker): Promise<void> {\n\t\tif (this.disposed || !worker.isAlive()) {\n\t\t\tawait this.discard(worker);\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait worker.reset();\n\t\t} catch {\n\t\t\tawait this.discard(worker);\n\t\t\treturn;\n\t\t}\n\t\tconst key = worker.key;\n\t\tconst pool = this.idle.get(key) ?? [];\n\t\tif (pool.length >= this.maxPerKey) {\n\t\t\tawait this.discard(worker);\n\t\t\treturn;\n\t\t}\n\t\tpool.push(worker);\n\t\tthis.idle.set(key, pool);\n\t\tthis.armReclaim(worker);\n\t}\n\n\tprivate async discard(worker: WarmSubagentWorker): Promise<void> {\n\t\tthis.clearReclaim(worker);\n\t\tthis.decLive(worker.key);\n\t\tawait worker.dispose();\n\t}\n\n\tprivate armReclaim(worker: WarmSubagentWorker): void {\n\t\tconst timer = setTimeout(() => {\n\t\t\tconst pool = this.idle.get(worker.key);\n\t\t\tif (pool) {\n\t\t\t\tconst idx = pool.indexOf(worker);\n\t\t\t\tif (idx !== -1) pool.splice(idx, 1);\n\t\t\t}\n\t\t\tvoid this.discard(worker);\n\t\t}, this.idleTtlMs);\n\t\ttimer.unref?.();\n\t\tthis.reclaimTimers.set(worker, timer);\n\t}\n\n\tprivate clearReclaim(worker: WarmSubagentWorker): void {\n\t\tconst timer = this.reclaimTimers.get(worker);\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\tthis.reclaimTimers.delete(worker);\n\t\t}\n\t}\n\n\tprivate incLive(key: string): void {\n\t\tthis.liveCount.set(key, (this.liveCount.get(key) ?? 0) + 1);\n\t}\n\n\tprivate decLive(key: string): void {\n\t\tconst n = (this.liveCount.get(key) ?? 1) - 1;\n\t\tif (n <= 0) this.liveCount.delete(key);\n\t\telse this.liveCount.set(key, n);\n\t}\n\n\t/** Number of currently idle (parked) workers — exposed for tests/diagnostics. */\n\tidleCount(): number {\n\t\tlet total = 0;\n\t\tfor (const pool of this.idle.values()) total += pool.length;\n\t\treturn total;\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\tif (this.disposed) return;\n\t\tthis.disposed = true;\n\t\tconst all: WarmSubagentWorker[] = [];\n\t\tfor (const pool of this.idle.values()) all.push(...pool);\n\t\tthis.idle.clear();\n\t\tfor (const timer of this.reclaimTimers.values()) clearTimeout(timer);\n\t\tthis.reclaimTimers.clear();\n\t\tthis.liveCount.clear();\n\t\tawait Promise.all(all.map((w) => w.dispose()));\n\t}\n}\n\n/**\n * Build the RPC child's CLI args for a warm worker. Mirrors the agent-config\n * portion of SubagentPool.buildArgs (system prompt, tools, model/provider, turn\n * cap, skills) but omits the one-shot `--mode json` / `--session` / `--task-id`\n * bits, since the worker runs persistently in RPC mode and resets via new_session.\n */\nfunction buildWorkerArgs(\n\toptions: WarmDispatchOptions,\n\tregistry: AgentRegistry,\n\tskillPaths: readonly string[],\n\tsettings: Settings | undefined,\n): string[] {\n\tconst args: string[] = [];\n\tconst def = registry.get(options.agentType);\n\n\tif (def?.prompt) args.push(\"--system-prompt\", def.prompt);\n\n\tconst childDepth = currentSubagentDepth(process.env) + 1;\n\tconst canChildDelegate = def?.delegate === true && childDepth < resolveMaxSubagentDepth(undefined, process.env);\n\n\tconst tools = def?.tools ? [...def.tools] : undefined;\n\tif (canChildDelegate && tools) {\n\t\tfor (const t of [\"Task\", \"TaskOutput\"]) if (!tools.includes(t)) tools.push(t);\n\t}\n\tif (tools && tools.length > 0) args.push(\"--tools\", tools.join(\",\"));\n\tif (def?.disallowedTools && def.disallowedTools.length > 0) {\n\t\targs.push(\"--disallowed-tools\", def.disallowedTools.join(\",\"));\n\t}\n\tif (canChildDelegate) {\n\t\targs.push(\"--enable-subagents\");\n\t\tif (def?.delegateTo && def.delegateTo.length > 0) args.push(\"--delegate-allow\", def.delegateTo.join(\",\"));\n\t}\n\n\t// Model precedence matches the cold path: a pinned (non-inherit) agent model\n\t// wins, else the requested model/category, resolved to a concrete id.\n\tconst explicitModel = def?.model && def.model !== MODEL_INHERIT ? def.model : undefined;\n\tconst rawModel = explicitModel ?? options.model;\n\tconst modelToUse = rawModel ? resolveModelReference(rawModel, settings) : undefined;\n\tif (modelToUse) args.push(\"--model\", modelToUse);\n\tif (options.provider) args.push(\"--provider\", options.provider);\n\n\tconst maxTurns = def?.maxTurns && def.maxTurns > 0 ? def.maxTurns : DEFAULT_SUBAGENT_MAX_TURNS;\n\targs.push(\"--max-turns\", String(maxTurns));\n\n\tfor (const skillPath of skillPaths) args.push(\"--skill\", skillPath);\n\n\treturn args;\n}\n\n/**\n * Scan a completed run's events for a turn that ended in error/abort, returning\n * its message (or a generic marker) so the caller can report a task failure\n * without re-reading the transcript. Returns undefined for a clean run.\n */\nfunction firstTurnError(events: readonly AgentEvent[]): string | undefined {\n\tfor (const event of events) {\n\t\tconst e = event as { type?: string; message?: { stopReason?: string; errorMessage?: string } };\n\t\tif (e.type === \"turn_end\" && (e.message?.stopReason === \"error\" || e.message?.stopReason === \"aborted\")) {\n\t\t\treturn e.message.errorMessage || `turn ${e.message.stopReason}`;\n\t\t}\n\t}\n\treturn undefined;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"warm-subagent-pool.js","sourceRoot":"","sources":["../../src/core/warm-subagent-pool.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Warm subagent worker pool (experimental, opt-in via `--warm-subagents` /\n * settings.warmSubagents; default off).\n *\n * The cold path (SubagentPool) re-execs the whole CLI for every dispatch, paying\n * a full module-load + resource-graph boot each time. A warm worker instead keeps\n * a long-lived child running in RPC mode and hands it one task at a time over the\n * existing JSON-line protocol: `new_session` resets its conversation between\n * tasks, `prompt` runs the task, and `agent_end` on the event stream signals\n * completion, after which the answer + usage are pulled inline (no result.json\n * disk round-trip, no OutputVerifier). The first task per worker still pays the\n * boot; every reuse after that skips it.\n *\n * Scope/limits (deliberately conservative — see the dispatch integration):\n * - Workers are pinned per agent type: RPC has no per-prompt system-prompt/tools\n * swap, so each (agentType, model, provider) is its own worker config.\n * - Only non-resume, non-fork dispatches are eligible; resume/fork need a\n * persisted/forked session the warm path does not own.\n * - Any worker/infra failure falls back to the cold pool, so enabling this can\n * only change latency, never whether a task can run.\n */\n\nimport type { AgentEvent } from \"@kolisachint/hoocode-agent-core\";\nimport type { Api, Model } from \"@kolisachint/hoocode-ai\";\nimport { getSubagentSpawnCommand } from \"../config.js\";\nimport { RpcClient } from \"../modes/rpc/rpc-client.js\";\nimport { MODEL_INHERIT } from \"./agent-frontmatter.js\";\nimport { type AgentRegistry, loadAgentRegistry } from \"./agent-registry.js\";\nimport { resolveModelReference } from \"./model-categories.js\";\nimport type { Settings } from \"./settings-manager.js\";\nimport {\n\tcurrentSubagentDepth,\n\tDEFER_MCP_SCHEMAS_ENV,\n\tresolveMaxSubagentDepth,\n\tSUBAGENT_DEPTH_ENV,\n\tSUBAGENT_SKIP_MCP_ENV,\n\ttoolAllowlistNeedsMcp,\n} from \"./subagent-depth.js\";\nimport { DEFAULT_SUBAGENT_MAX_TURNS } from \"./subagent-pool.js\";\n\n/** Usage totals pulled from a worker after a task, shaped like SubagentResultFile.usage. */\nexport interface WarmUsage {\n\tinput: number;\n\toutput: number;\n\tcacheRead: number;\n\tcacheWrite: number;\n\tcost: number;\n}\n\n/** Outcome of running one task on a warm worker. */\nexport interface WarmRunResult {\n\tok: boolean;\n\tstatus: \"complete\" | \"failed\";\n\t/** The subagent's final assistant text (its answer to the caller). */\n\tsummary: string;\n\tusage?: WarmUsage;\n\terror?: string;\n}\n\n/** Per-dispatch inputs that select and configure a worker. */\nexport interface WarmDispatchOptions {\n\tagentType: string;\n\tcwd: string;\n\t/** Model id or category (fast/standard/capable); resolved to a concrete id. */\n\tmodel?: string;\n\tprovider?: string;\n}\n\n/** Reports the tool a warm worker is currently running (\"\" = idle between tools). */\nexport type WarmProgressCallback = (activity: string) => void;\n\n/** A run failed at the infrastructure level (worker crash, timeout, protocol error). */\nexport class WarmWorkerError extends Error {}\n\n/** Default per-task wait before declaring a warm run stalled and failing over to cold. */\nconst WARM_RUN_TIMEOUT_MS = 180_000;\n\n/**\n * One long-lived RPC child pinned to a single agent-type configuration. Reused\n * across tasks via reset(); a crash makes it not-alive so the pool discards it.\n */\nexport class WarmSubagentWorker {\n\tprivate readonly client: RpcClient;\n\tprivate alive = true;\n\n\tconstructor(\n\t\treadonly key: string,\n\t\toptions: WarmDispatchOptions,\n\t\tprivate readonly env: NodeJS.ProcessEnv,\n\t\tregistry: AgentRegistry,\n\t\tskillPaths: readonly string[],\n\t\tsettings: Settings | undefined,\n\t\tavailableModels: readonly Model<Api>[],\n\t\t/** Spawn command override (tests inject a fake RPC child); defaults to the real spawn command. */\n\t\tspawnCommand?: { executable: string; prefixArgs: string[] },\n\t) {\n\t\tconst { executable, prefixArgs } = spawnCommand ?? getSubagentSpawnCommand();\n\t\tthis.client = new RpcClient({\n\t\t\texecutable,\n\t\t\tprefixArgs,\n\t\t\tcwd: options.cwd,\n\t\t\tenv: this.env as Record<string, string>,\n\t\t\targs: buildWorkerArgs(options, registry, skillPaths, settings, availableModels),\n\t\t});\n\t}\n\n\t/** Boot the child. Throws (as WarmWorkerError) if it fails to come up. */\n\tasync start(): Promise<void> {\n\t\ttry {\n\t\t\tawait this.client.start();\n\t\t} catch (error) {\n\t\t\tthis.alive = false;\n\t\t\tthrow new WarmWorkerError(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\n\tisAlive(): boolean {\n\t\treturn this.alive;\n\t}\n\n\t/**\n\t * Run one task to completion and return its answer + usage. Throws\n\t * WarmWorkerError on an infra failure (crash/timeout/protocol) so the caller\n\t * can fall back to the cold pool; a task that ran but reported failure returns\n\t * `{ ok: false }` instead (no fall back — the work was actually done).\n\t */\n\tasync run(\n\t\tprompt: string,\n\t\tonActivity?: WarmProgressCallback,\n\t\ttimeoutMs = WARM_RUN_TIMEOUT_MS,\n\t): Promise<WarmRunResult> {\n\t\tif (!this.alive) throw new WarmWorkerError(\"worker is not alive\");\n\t\t// Mirror the cold pool's coarse progress: report the tool the child is\n\t\t// currently running so a warm dispatch's task row reads \"⋯ grep\" rather than a\n\t\t// static \"running…\". Cleared between tools and at turn end.\n\t\tconst detachProgress = onActivity ? this.tapProgress(onActivity) : undefined;\n\t\ttry {\n\t\t\tconst events = await this.client.promptAndWait(prompt, undefined, timeoutMs);\n\t\t\tconst failure = firstTurnError(events);\n\t\t\tconst summary = (await this.client.getLastAssistantText()) ?? \"\";\n\t\t\tconst usage = await this.readUsage();\n\t\t\tif (failure) {\n\t\t\t\treturn { ok: false, status: \"failed\", summary, usage, error: failure };\n\t\t\t}\n\t\t\treturn { ok: true, status: \"complete\", summary, usage };\n\t\t} catch (error) {\n\t\t\t// A prompt/await failure means the child is no longer trustworthy: mark it\n\t\t\t// dead so the pool discards it, and signal infra failure for fallback.\n\t\t\tthis.alive = false;\n\t\t\tthrow new WarmWorkerError(error instanceof Error ? error.message : String(error));\n\t\t} finally {\n\t\t\tdetachProgress?.();\n\t\t\tonActivity?.(\"\");\n\t\t}\n\t}\n\n\t/** Forward the child's coarse tool-lifecycle events to an activity callback. */\n\tprivate tapProgress(onActivity: WarmProgressCallback): () => void {\n\t\treturn this.client.onEvent((event) => {\n\t\t\tconst e = event as { type?: string; toolName?: string };\n\t\t\tif (e.type === \"tool_execution_start\") onActivity(typeof e.toolName === \"string\" ? e.toolName : \"\");\n\t\t\telse if (e.type === \"tool_execution_end\" || e.type === \"turn_end\") onActivity(\"\");\n\t\t});\n\t}\n\n\t/** Reset the worker's conversation so it can take the next task cleanly. */\n\tasync reset(): Promise<void> {\n\t\tif (!this.alive) return;\n\t\ttry {\n\t\t\tawait this.client.newSession();\n\t\t} catch (error) {\n\t\t\t// A reset failure leaves the worker in an unknown state — retire it.\n\t\t\tthis.alive = false;\n\t\t\tthrow new WarmWorkerError(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\tthis.alive = false;\n\t\tawait this.client.stop().catch(() => {});\n\t}\n\n\tprivate async readUsage(): Promise<WarmUsage | undefined> {\n\t\ttry {\n\t\t\tconst stats = await this.client.getSessionStats();\n\t\t\treturn {\n\t\t\t\tinput: stats.tokens.input,\n\t\t\t\toutput: stats.tokens.output,\n\t\t\t\tcacheRead: stats.tokens.cacheRead,\n\t\t\t\tcacheWrite: stats.tokens.cacheWrite,\n\t\t\t\tcost: stats.cost,\n\t\t\t};\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t}\n}\n\n/**\n * Pool of warm workers keyed by agent-type configuration. Hands out an idle\n * worker (or boots a new one up to a per-key cap), and on release resets the\n * worker and returns it to the idle set with an idle-TTL reclaim timer.\n */\nexport class WarmSubagentPool {\n\tprivate idle = new Map<string, WarmSubagentWorker[]>();\n\tprivate reclaimTimers = new Map<WarmSubagentWorker, ReturnType<typeof setTimeout>>();\n\tprivate liveCount = new Map<string, number>();\n\tprivate disposed = false;\n\tprivate registry?: AgentRegistry;\n\n\tconstructor(\n\t\tprivate readonly cwd: string,\n\t\tprivate readonly settings: Settings | undefined,\n\t\tprivate skillPaths: string[] = [],\n\t\t/**\n\t\t * Available models used to derive default model-category mappings when a tier\n\t\t * is not explicitly set in `settings.modelCategories` (snapshot at creation).\n\t\t */\n\t\tprivate readonly availableModels: readonly Model<Api>[] = [],\n\t\tprivate readonly maxPerKey = 2,\n\t\tprivate readonly idleTtlMs = 30_000,\n\t\t/** Spawn command override (tests inject a fake RPC child); defaults to the real spawn command. */\n\t\tprivate readonly spawnCommand?: { executable: string; prefixArgs: string[] },\n\t) {}\n\n\tupdateSkillPaths(paths: string[]): void {\n\t\tthis.skillPaths = [...paths];\n\t}\n\n\tprivate getRegistry(): AgentRegistry {\n\t\tif (!this.registry) this.registry = loadAgentRegistry({ cwd: this.cwd });\n\t\treturn this.registry;\n\t}\n\n\t/** A registry agent is poolable when it is not a fork agent (fork needs a forked session). */\n\tisPoolable(agentType: string): boolean {\n\t\tconst def = this.getRegistry().get(agentType);\n\t\treturn def !== undefined && def.fork !== true;\n\t}\n\n\t/** Stable key for one worker configuration. */\n\tprivate keyFor(options: WarmDispatchOptions): string {\n\t\tconst resolved = options.model\n\t\t\t? resolveModelReference(options.model, this.settings, this.availableModels)\n\t\t\t: undefined;\n\t\treturn `${options.agentType}::${resolved ?? \"default\"}::${options.provider ?? \"default\"}`;\n\t}\n\n\t/** Environment for a warm child: depth stamp + MCP skip, mirroring SubagentPool.childSpawnEnv. */\n\tprivate childEnv(agentType: string): NodeJS.ProcessEnv {\n\t\tconst env: NodeJS.ProcessEnv = {\n\t\t\t...process.env,\n\t\t\t[SUBAGENT_DEPTH_ENV]: String(currentSubagentDepth(process.env) + 1),\n\t\t};\n\t\tconst def = this.getRegistry().get(agentType);\n\t\tif (!toolAllowlistNeedsMcp(def?.tools)) env[SUBAGENT_SKIP_MCP_ENV] = \"1\";\n\t\t// A child never defers MCP schemas: if it needs MCP it eager-registers its\n\t\t// allowlisted tools at dispatch so they are immediately callable (spec §2).\n\t\tdelete env[DEFER_MCP_SCHEMAS_ENV];\n\t\treturn env;\n\t}\n\n\t/**\n\t * Run a task on a warm worker end to end: acquire (reuse or boot), run, then\n\t * release back to the pool. Throws WarmWorkerError on infra failure so the\n\t * caller can fall back to the cold pool.\n\t */\n\tasync dispatch(\n\t\tprompt: string,\n\t\toptions: WarmDispatchOptions,\n\t\tonActivity?: WarmProgressCallback,\n\t): Promise<WarmRunResult> {\n\t\tif (this.disposed) throw new WarmWorkerError(\"warm pool disposed\");\n\t\tconst worker = await this.acquire(options);\n\t\ttry {\n\t\t\tconst result = await worker.run(prompt, onActivity);\n\t\t\tawait this.release(worker);\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\t// Infra failure: the worker already marked itself dead; drop it entirely.\n\t\t\tawait this.discard(worker);\n\t\t\tthrow error instanceof WarmWorkerError ? error : new WarmWorkerError(String(error));\n\t\t}\n\t}\n\n\tprivate async acquire(options: WarmDispatchOptions): Promise<WarmSubagentWorker> {\n\t\tconst key = this.keyFor(options);\n\t\tconst pool = this.idle.get(key);\n\t\twhile (pool && pool.length > 0) {\n\t\t\tconst worker = pool.pop()!;\n\t\t\tthis.clearReclaim(worker);\n\t\t\tif (worker.isAlive()) return worker;\n\t\t\t// Dead idle worker (child exited while parked): drop and try the next.\n\t\t\tthis.decLive(key);\n\t\t\tawait worker.dispose();\n\t\t}\n\n\t\tconst worker = new WarmSubagentWorker(\n\t\t\tkey,\n\t\t\toptions,\n\t\t\tthis.childEnv(options.agentType),\n\t\t\tthis.getRegistry(),\n\t\t\tthis.skillPaths,\n\t\t\tthis.settings,\n\t\t\tthis.availableModels,\n\t\t\tthis.spawnCommand,\n\t\t);\n\t\tthis.incLive(key);\n\t\ttry {\n\t\t\tawait worker.start();\n\t\t} catch (error) {\n\t\t\tthis.decLive(key);\n\t\t\tthrow error;\n\t\t}\n\t\treturn worker;\n\t}\n\n\tprivate async release(worker: WarmSubagentWorker): Promise<void> {\n\t\tif (this.disposed || !worker.isAlive()) {\n\t\t\tawait this.discard(worker);\n\t\t\treturn;\n\t\t}\n\t\ttry {\n\t\t\tawait worker.reset();\n\t\t} catch {\n\t\t\tawait this.discard(worker);\n\t\t\treturn;\n\t\t}\n\t\tconst key = worker.key;\n\t\tconst pool = this.idle.get(key) ?? [];\n\t\tif (pool.length >= this.maxPerKey) {\n\t\t\tawait this.discard(worker);\n\t\t\treturn;\n\t\t}\n\t\tpool.push(worker);\n\t\tthis.idle.set(key, pool);\n\t\tthis.armReclaim(worker);\n\t}\n\n\tprivate async discard(worker: WarmSubagentWorker): Promise<void> {\n\t\tthis.clearReclaim(worker);\n\t\tthis.decLive(worker.key);\n\t\tawait worker.dispose();\n\t}\n\n\tprivate armReclaim(worker: WarmSubagentWorker): void {\n\t\tconst timer = setTimeout(() => {\n\t\t\tconst pool = this.idle.get(worker.key);\n\t\t\tif (pool) {\n\t\t\t\tconst idx = pool.indexOf(worker);\n\t\t\t\tif (idx !== -1) pool.splice(idx, 1);\n\t\t\t}\n\t\t\tvoid this.discard(worker);\n\t\t}, this.idleTtlMs);\n\t\ttimer.unref?.();\n\t\tthis.reclaimTimers.set(worker, timer);\n\t}\n\n\tprivate clearReclaim(worker: WarmSubagentWorker): void {\n\t\tconst timer = this.reclaimTimers.get(worker);\n\t\tif (timer) {\n\t\t\tclearTimeout(timer);\n\t\t\tthis.reclaimTimers.delete(worker);\n\t\t}\n\t}\n\n\tprivate incLive(key: string): void {\n\t\tthis.liveCount.set(key, (this.liveCount.get(key) ?? 0) + 1);\n\t}\n\n\tprivate decLive(key: string): void {\n\t\tconst n = (this.liveCount.get(key) ?? 1) - 1;\n\t\tif (n <= 0) this.liveCount.delete(key);\n\t\telse this.liveCount.set(key, n);\n\t}\n\n\t/** Number of currently idle (parked) workers — exposed for tests/diagnostics. */\n\tidleCount(): number {\n\t\tlet total = 0;\n\t\tfor (const pool of this.idle.values()) total += pool.length;\n\t\treturn total;\n\t}\n\n\tasync dispose(): Promise<void> {\n\t\tif (this.disposed) return;\n\t\tthis.disposed = true;\n\t\tconst all: WarmSubagentWorker[] = [];\n\t\tfor (const pool of this.idle.values()) all.push(...pool);\n\t\tthis.idle.clear();\n\t\tfor (const timer of this.reclaimTimers.values()) clearTimeout(timer);\n\t\tthis.reclaimTimers.clear();\n\t\tthis.liveCount.clear();\n\t\tawait Promise.all(all.map((w) => w.dispose()));\n\t}\n}\n\n/**\n * Build the RPC child's CLI args for a warm worker. Mirrors the agent-config\n * portion of SubagentPool.buildArgs (system prompt, tools, model/provider, turn\n * cap, skills) but omits the one-shot `--mode json` / `--session` / `--task-id`\n * bits, since the worker runs persistently in RPC mode and resets via new_session.\n */\nfunction buildWorkerArgs(\n\toptions: WarmDispatchOptions,\n\tregistry: AgentRegistry,\n\tskillPaths: readonly string[],\n\tsettings: Settings | undefined,\n\tavailableModels: readonly Model<Api>[],\n): string[] {\n\tconst args: string[] = [];\n\tconst def = registry.get(options.agentType);\n\n\tif (def?.prompt) args.push(\"--system-prompt\", def.prompt);\n\n\tconst childDepth = currentSubagentDepth(process.env) + 1;\n\tconst canChildDelegate = def?.delegate === true && childDepth < resolveMaxSubagentDepth(undefined, process.env);\n\n\tconst tools = def?.tools ? [...def.tools] : undefined;\n\tif (canChildDelegate && tools) {\n\t\tfor (const t of [\"Task\", \"TaskOutput\"]) if (!tools.includes(t)) tools.push(t);\n\t}\n\tif (tools && tools.length > 0) args.push(\"--tools\", tools.join(\",\"));\n\tif (def?.disallowedTools && def.disallowedTools.length > 0) {\n\t\targs.push(\"--disallowed-tools\", def.disallowedTools.join(\",\"));\n\t}\n\tif (canChildDelegate) {\n\t\targs.push(\"--enable-subagents\");\n\t\tif (def?.delegateTo && def.delegateTo.length > 0) args.push(\"--delegate-allow\", def.delegateTo.join(\",\"));\n\t}\n\n\t// Model precedence matches the cold path: a pinned (non-inherit) agent model\n\t// wins, else the requested model/category, resolved to a concrete id.\n\tconst explicitModel = def?.model && def.model !== MODEL_INHERIT ? def.model : undefined;\n\tconst rawModel = explicitModel ?? options.model;\n\tconst modelToUse = rawModel ? resolveModelReference(rawModel, settings, availableModels) : undefined;\n\tif (modelToUse) args.push(\"--model\", modelToUse);\n\tif (options.provider) args.push(\"--provider\", options.provider);\n\n\tconst maxTurns = def?.maxTurns && def.maxTurns > 0 ? def.maxTurns : DEFAULT_SUBAGENT_MAX_TURNS;\n\targs.push(\"--max-turns\", String(maxTurns));\n\n\tfor (const skillPath of skillPaths) args.push(\"--skill\", skillPath);\n\n\treturn args;\n}\n\n/**\n * Scan a completed run's events for a turn that ended in error/abort, returning\n * its message (or a generic marker) so the caller can report a task failure\n * without re-reading the transcript. Returns undefined for a clean run.\n */\nfunction firstTurnError(events: readonly AgentEvent[]): string | undefined {\n\tfor (const event of events) {\n\t\tconst e = event as { type?: string; message?: { stopReason?: string; errorMessage?: string } };\n\t\tif (e.type === \"turn_end\" && (e.message?.stopReason === \"error\" || e.message?.stopReason === \"aborted\")) {\n\t\t\treturn e.message.errorMessage || `turn ${e.message.stopReason}`;\n\t\t}\n\t}\n\treturn undefined;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-executor.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/command-executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Extracted command handlers from InteractiveMode.\n * All methods receive dependencies via CommandContext rather than\n * reaching back into the parent class.\n */\n\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { Model } from \"@kolisachint/hoocode-ai\";\nimport type { EditorComponent, MarkdownTheme, TUI } from \"@kolisachint/hoocode-tui\";\nimport { type Container, Markdown, Spacer, Text, visibleWidth } from \"@kolisachint/hoocode-tui\";\nimport { spawn, spawnSync } from \"child_process\";\nimport { getDebugLogPath, getShareViewerUrl } from \"../../config.js\";\nimport { loadAgentRegistry } from \"../../core/agent-registry.js\";\nimport type { AgentSession } from \"../../core/agent-session.js\";\nimport type { AgentSessionRuntime } from \"../../core/agent-session-runtime.js\";\nimport { SessionImportFileNotFoundError } from \"../../core/agent-session-runtime.js\";\nimport type { KeybindingsManager } from \"../../core/keybindings.js\";\nimport { MissingSessionCwdError } from \"../../core/session-cwd.js\";\nimport type { SessionManager } from \"../../core/session-manager.js\";\nimport { getSubagentPool } from \"../../core/subagent-pool-instance.js\";\nimport type { SubagentResultFile } from \"../../core/subagent-result.js\";\nimport { getChangelogPath, parseChangelog } from \"../../utils/changelog.js\";\nimport { copyToClipboard } from \"../../utils/clipboard.js\";\nimport { BorderedLoader } from \"./components/bordered-loader.js\";\nimport { DynamicBorder } from \"./components/dynamic-border.js\";\nimport type { FooterComponent } from \"./components/footer.js\";\nimport { formatKeyText, keyDisplayText } from \"./components/keybinding-hints.js\";\nimport { theme } from \"./theme/theme.js\";\n\nexport interface CommandContext {\n\t// Core dependencies\n\tsession: AgentSession;\n\tsessionManager: SessionManager;\n\truntimeHost: AgentSessionRuntime;\n\tui: TUI;\n\teditor: EditorComponent;\n\teditorContainer: Container;\n\tchatContainer: Container;\n\tstatusContainer: Container;\n\tfooter: FooterComponent;\n\tkeybindings: KeybindingsManager;\n\n\t// UI callbacks\n\tshowStatus: (message: string) => void;\n\tshowError: (message: string) => void;\n\tshowWarning: (message: string) => void;\n\tupdateEditorBorderColor: () => void;\n\trenderCurrentSessionState: () => void;\n\trebuildChatFromMessages: () => void;\n\tgetMarkdownThemeWithSettings: () => MarkdownTheme;\n\tstopLoadingAnimation: () => void;\n\n\t// Auth/model helpers\n\tfindExactModelMatch: (searchTerm: string) => Promise<Model<any> | undefined>;\n\tmaybeWarnAboutAnthropicSubscriptionAuth: (model?: Model<any>) => Promise<void>;\n\n\t// Dialog callbacks\n\tshowModelSelector: (searchTerm?: string) => void;\n\tshowExtensionConfirm: (title: string, message: string) => Promise<boolean>;\n\tpromptForMissingSessionCwd: (error: MissingSessionCwdError) => Promise<string | undefined>;\n\n\t// Fatal error handler\n\thandleFatalRuntimeError: (prefix: string, error: unknown) => Promise<never>;\n}\n\nexport class CommandExecutor {\n\tconstructor(private readonly ctx: CommandContext) {}\n\n\t// =========================================================================\n\t// Slash command handlers\n\t// =========================================================================\n\n\tasync handleModel(searchTerm?: string): Promise<void> {\n\t\tif (!searchTerm) {\n\t\t\tthis.ctx.showModelSelector();\n\t\t\treturn;\n\t\t}\n\n\t\tconst model = await this.ctx.findExactModelMatch(searchTerm);\n\t\tif (model) {\n\t\t\ttry {\n\t\t\t\tawait this.ctx.session.setModel(model);\n\t\t\t\tthis.ctx.footer.invalidate();\n\t\t\t\tthis.ctx.updateEditorBorderColor();\n\t\t\t\tthis.ctx.showStatus(`Model: ${model.id}`);\n\t\t\t\tvoid this.ctx.maybeWarnAboutAnthropicSubscriptionAuth(model);\n\t\t\t} catch (error) {\n\t\t\t\tthis.ctx.showError(error instanceof Error ? error.message : String(error));\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tthis.ctx.showModelSelector(searchTerm);\n\t}\n\n\tasync handleClone(): Promise<void> {\n\t\tconst leafId = this.ctx.sessionManager.getLeafId();\n\t\tif (!leafId) {\n\t\t\tthis.ctx.showStatus(\"Nothing to clone yet\");\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = await this.ctx.runtimeHost.fork(leafId, { position: \"at\" });\n\t\t\tif (result.cancelled) {\n\t\t\t\tthis.ctx.ui.requestRender();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis.ctx.renderCurrentSessionState();\n\t\t\tthis.ctx.editor.setText(\"\");\n\t\t\tthis.ctx.showStatus(\"Cloned to new session\");\n\t\t} catch (error: unknown) {\n\t\t\tthis.ctx.showError(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\n\tasync handleSubagent(text: string): Promise<void> {\n\t\tconst prefix = \"/subagent \";\n\t\tconst args = text.startsWith(prefix) ? text.slice(prefix.length).trim() : \"\";\n\t\tif (!args) {\n\t\t\tthis.ctx.showStatus(\"Usage: /subagent <mode> <task>\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst firstSpace = args.indexOf(\" \");\n\t\tif (firstSpace === -1) {\n\t\t\tthis.ctx.showStatus(\"Usage: /subagent <mode> <task>\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst mode = args.slice(0, firstSpace).trim();\n\t\tconst task = args.slice(firstSpace + 1).trim();\n\t\tif (!task) {\n\t\t\tthis.ctx.showStatus(\"Usage: /subagent <mode> <task>\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst validModes = loadAgentRegistry({ cwd: this.ctx.sessionManager.getCwd() })\n\t\t\t.list()\n\t\t\t.map((a) => a.name);\n\t\tif (!validModes.includes(mode)) {\n\t\t\tthis.ctx.showStatus(`Unknown subagent_type: ${mode}. Available: ${validModes.join(\", \")}`);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.ctx.showStatus(`Spawning ${mode} subagent...`);\n\t\ttry {\n\t\t\tconst pool = getSubagentPool(this.ctx.sessionManager.getCwd());\n\t\t\tconst dispatchResult = await pool.dispatch(task, {\n\t\t\t\tforceAgent: mode,\n\t\t\t\tmodel: this.ctx.session.model?.id,\n\t\t\t\tprovider: this.ctx.session.model?.provider,\n\t\t\t});\n\t\t\tconst result = dispatchResult.result;\n\t\t\tconst resultData = result?.result_data as SubagentResultFile | undefined;\n\t\t\tif (result?.ok) {\n\t\t\t\tthis.ctx.showStatus(`${mode} subagent completed`);\n\t\t\t\t// Inject the subagent answer as a custom message so the user can see it in the chat\n\t\t\t\tthis.ctx.sessionManager.appendMessage({\n\t\t\t\t\trole: \"custom\",\n\t\t\t\t\tcustomType: \"subagent\",\n\t\t\t\t\tcontent: resultData?.summary || \"(no output)\",\n\t\t\t\t\tdisplay: true,\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthis.ctx.showError(`Subagent (${mode}) failed: ${result?.error ?? \"unknown error\"}`);\n\t\t\t}\n\t\t} catch (error: unknown) {\n\t\t\tthis.ctx.showError(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\n\tasync handleExport(text: string): Promise<void> {\n\t\tconst outputPath = this.getPathArgument(text, \"/export\");\n\n\t\ttry {\n\t\t\tif (outputPath?.endsWith(\".jsonl\")) {\n\t\t\t\tconst filePath = this.ctx.session.exportToJsonl(outputPath);\n\t\t\t\tthis.ctx.showStatus(`Session exported to: ${filePath}`);\n\t\t\t} else {\n\t\t\t\tconst filePath = await this.ctx.session.exportToHtml(outputPath);\n\t\t\t\tthis.ctx.showStatus(`Session exported to: ${filePath}`);\n\t\t\t}\n\t\t} catch (error: unknown) {\n\t\t\tthis.ctx.showError(`Failed to export session: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n\t\t}\n\t}\n\n\tprivate getPathArgument(text: string, command: \"/export\" | \"/import\"): string | undefined {\n\t\tif (text === command) {\n\t\t\treturn undefined;\n\t\t}\n\t\tif (!text.startsWith(`${command} `)) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst argsString = text.slice(command.length + 1).trimStart();\n\t\tif (!argsString) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst firstChar = argsString[0];\n\t\tif (firstChar === '\"' || firstChar === \"'\") {\n\t\t\tconst closingQuoteIndex = argsString.indexOf(firstChar, 1);\n\t\t\tif (closingQuoteIndex < 0) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\treturn argsString.slice(1, closingQuoteIndex);\n\t\t}\n\n\t\tconst firstWhitespaceIndex = argsString.search(/\\s/);\n\t\tif (firstWhitespaceIndex < 0) {\n\t\t\treturn argsString;\n\t\t}\n\t\treturn argsString.slice(0, firstWhitespaceIndex);\n\t}\n\n\tasync handleImport(text: string): Promise<void> {\n\t\tconst inputPath = this.getPathArgument(text, \"/import\");\n\t\tif (!inputPath) {\n\t\t\tthis.ctx.showError(\"Usage: /import <path.jsonl>\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst confirmed = await this.ctx.showExtensionConfirm(\n\t\t\t\"Import session\",\n\t\t\t`Replace current session with ${inputPath}?`,\n\t\t);\n\t\tif (!confirmed) {\n\t\t\tthis.ctx.showStatus(\"Import cancelled\");\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.ctx.stopLoadingAnimation();\n\t\t\tthis.ctx.statusContainer.clear();\n\t\t\tconst result = await this.ctx.runtimeHost.importFromJsonl(inputPath);\n\t\t\tif (result.cancelled) {\n\t\t\t\tthis.ctx.showStatus(\"Import cancelled\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.ctx.renderCurrentSessionState();\n\t\t\tthis.ctx.showStatus(`Session imported from: ${inputPath}`);\n\t\t} catch (error: unknown) {\n\t\t\tif (error instanceof MissingSessionCwdError) {\n\t\t\t\tconst selectedCwd = await this.ctx.promptForMissingSessionCwd(error);\n\t\t\t\tif (!selectedCwd) {\n\t\t\t\t\tthis.ctx.showStatus(\"Import cancelled\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst result = await this.ctx.runtimeHost.importFromJsonl(inputPath, selectedCwd);\n\t\t\t\tif (result.cancelled) {\n\t\t\t\t\tthis.ctx.showStatus(\"Import cancelled\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.ctx.renderCurrentSessionState();\n\t\t\t\tthis.ctx.showStatus(`Session imported from: ${inputPath}`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (error instanceof SessionImportFileNotFoundError) {\n\t\t\t\tthis.ctx.showError(`Failed to import session: ${error.message}`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait this.ctx.handleFatalRuntimeError(\"Failed to import session\", error);\n\t\t}\n\t}\n\n\tasync handleShare(): Promise<void> {\n\t\t// Check if gh is available and logged in\n\t\ttry {\n\t\t\tconst authResult = spawnSync(\"gh\", [\"auth\", \"status\"], { encoding: \"utf-8\" });\n\t\t\tif (authResult.status !== 0) {\n\t\t\t\tthis.ctx.showError(\"GitHub CLI is not logged in. Run 'gh auth login' first.\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t} catch {\n\t\t\tthis.ctx.showError(\"GitHub CLI (gh) is not installed. Install it from https://cli.github.com/\");\n\t\t\treturn;\n\t\t}\n\n\t\t// Export to a temp file\n\t\tconst tmpFile = path.join(os.tmpdir(), \"session.html\");\n\t\ttry {\n\t\t\tawait this.ctx.session.exportToHtml(tmpFile);\n\t\t} catch (error: unknown) {\n\t\t\tthis.ctx.showError(`Failed to export session: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n\t\t\treturn;\n\t\t}\n\n\t\t// Show cancellable loader, replacing the editor\n\t\tconst loader = new BorderedLoader(this.ctx.ui, theme, \"Creating gist...\");\n\t\tthis.ctx.editorContainer.clear();\n\t\tthis.ctx.editorContainer.addChild(loader);\n\t\tthis.ctx.ui.setFocus(loader);\n\t\tthis.ctx.ui.requestRender();\n\n\t\tconst restoreEditor = () => {\n\t\t\tloader.dispose();\n\t\t\tthis.ctx.editorContainer.clear();\n\t\t\tthis.ctx.editorContainer.addChild(this.ctx.editor);\n\t\t\tthis.ctx.ui.setFocus(this.ctx.editor);\n\t\t\ttry {\n\t\t\t\tfs.unlinkSync(tmpFile);\n\t\t\t} catch {\n\t\t\t\t// Ignore cleanup errors\n\t\t\t}\n\t\t};\n\n\t\t// Create a secret gist asynchronously\n\t\tlet proc: ReturnType<typeof spawn> | null = null;\n\n\t\tloader.onAbort = () => {\n\t\t\tproc?.kill();\n\t\t\trestoreEditor();\n\t\t\tthis.ctx.showStatus(\"Share cancelled\");\n\t\t};\n\n\t\ttry {\n\t\t\tconst result = await new Promise<{ stdout: string; stderr: string; code: number | null }>((resolve) => {\n\t\t\t\tproc = spawn(\"gh\", [\"gist\", \"create\", \"--public=false\", tmpFile]);\n\t\t\t\tlet stdout = \"\";\n\t\t\t\tlet stderr = \"\";\n\t\t\t\tproc.stdout?.on(\"data\", (data) => {\n\t\t\t\t\tstdout += data.toString();\n\t\t\t\t});\n\t\t\t\tproc.stderr?.on(\"data\", (data) => {\n\t\t\t\t\tstderr += data.toString();\n\t\t\t\t});\n\t\t\t\tproc.on(\"close\", (code) => resolve({ stdout, stderr, code }));\n\t\t\t});\n\n\t\t\tif (loader.signal.aborted) return;\n\n\t\t\trestoreEditor();\n\n\t\t\tif (result.code !== 0) {\n\t\t\t\tconst errorMsg = result.stderr?.trim() || \"Unknown error\";\n\t\t\t\tthis.ctx.showError(`Failed to create gist: ${errorMsg}`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Extract gist ID from the URL returned by gh\n\t\t\t// gh returns something like: https://gist.github.com/username/GIST_ID\n\t\t\tconst gistUrl = result.stdout?.trim();\n\t\t\tconst gistId = gistUrl?.split(\"/\").pop();\n\t\t\tif (!gistId) {\n\t\t\t\tthis.ctx.showError(\"Failed to parse gist ID from gh output\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Create the preview URL\n\t\t\tconst previewUrl = getShareViewerUrl(gistId);\n\t\t\tthis.ctx.showStatus(`Share URL: ${previewUrl}\\nGist: ${gistUrl}`);\n\t\t} catch (error: unknown) {\n\t\t\tif (!loader.signal.aborted) {\n\t\t\t\trestoreEditor();\n\t\t\t\tthis.ctx.showError(`Failed to create gist: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync handleCopy(): Promise<void> {\n\t\tconst text = this.ctx.session.getLastAssistantText();\n\t\tif (!text) {\n\t\t\tthis.ctx.showError(\"No agent messages to copy yet.\");\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tawait copyToClipboard(text);\n\t\t\tthis.ctx.showStatus(\"Copied last agent message to clipboard\");\n\t\t} catch (error) {\n\t\t\tthis.ctx.showError(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\n\thandleName(text: string): void {\n\t\tconst name = text.replace(/^\\/name\\s*/, \"\").trim();\n\t\tif (!name) {\n\t\t\tconst currentName = this.ctx.sessionManager.getSessionName();\n\t\t\tif (currentName) {\n\t\t\t\tthis.ctx.chatContainer.addChild(new Spacer(1));\n\t\t\t\tthis.ctx.chatContainer.addChild(new Text(theme.fg(\"dim\", `Session name: ${currentName}`), 1, 0));\n\t\t\t} else {\n\t\t\t\tthis.ctx.showWarning(\"Usage: /name <name>\");\n\t\t\t}\n\t\t\tthis.ctx.ui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.ctx.session.setSessionName(name);\n\t\tthis.ctx.chatContainer.addChild(new Spacer(1));\n\t\tthis.ctx.chatContainer.addChild(new Text(theme.fg(\"dim\", `Session name set: ${name}`), 1, 0));\n\t\tthis.ctx.ui.requestRender();\n\t}\n\n\thandleSession(): void {\n\t\tconst stats = this.ctx.session.getSessionStats();\n\t\tconst sessionName = this.ctx.sessionManager.getSessionName();\n\n\t\tlet info = `${theme.bold(\"Session Info\")}\\n\\n`;\n\t\tif (sessionName) {\n\t\t\tinfo += `${theme.fg(\"dim\", \"Name:\")} ${sessionName}\\n`;\n\t\t}\n\t\tinfo += `${theme.fg(\"dim\", \"File:\")} ${stats.sessionFile ?? \"In-memory\"}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", \"ID:\")} ${stats.sessionId}\\n\\n`;\n\t\tinfo += `${theme.bold(\"Messages\")}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", \"User:\")} ${stats.userMessages}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", \"Assistant:\")} ${stats.assistantMessages}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", \"Tool Calls:\")} ${stats.toolCalls}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", \"Tool Results:\")} ${stats.toolResults}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", \"Total:\")} ${stats.totalMessages}\\n\\n`;\n\t\tinfo += `${theme.bold(\"Tokens\")}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", \"Input:\")} ${stats.tokens.input.toLocaleString()}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", \"Output:\")} ${stats.tokens.output.toLocaleString()}\\n`;\n\t\tif (stats.tokens.cacheRead > 0) {\n\t\t\tinfo += `${theme.fg(\"dim\", \"Cache Read:\")} ${stats.tokens.cacheRead.toLocaleString()}\\n`;\n\t\t}\n\t\tif (stats.tokens.cacheWrite > 0) {\n\t\t\tinfo += `${theme.fg(\"dim\", \"Cache Write:\")} ${stats.tokens.cacheWrite.toLocaleString()}\\n`;\n\t\t}\n\t\tinfo += `${theme.fg(\"dim\", \"Total:\")} ${stats.tokens.total.toLocaleString()}\\n`;\n\n\t\tif (stats.cost > 0) {\n\t\t\tinfo += `\\n${theme.bold(\"Cost\")}\\n`;\n\t\t\tinfo += `${theme.fg(\"dim\", \"Total:\")} ${stats.cost.toFixed(4)}`;\n\t\t}\n\n\t\tthis.ctx.chatContainer.addChild(new Spacer(1));\n\t\tthis.ctx.chatContainer.addChild(new Text(info, 1, 0));\n\t\tthis.ctx.ui.requestRender();\n\t}\n\n\thandleChangelog(): void {\n\t\tconst changelogPath = getChangelogPath();\n\t\tconst allEntries = parseChangelog(changelogPath);\n\n\t\tif (allEntries.length === 0) {\n\t\t\tthis.ctx.chatContainer.addChild(new Spacer(1));\n\t\t\tthis.ctx.chatContainer.addChild(new Text(theme.fg(\"dim\", \"No changelog entries found.\"), 1, 0));\n\t\t\tthis.ctx.ui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tconst changelogMarkdown = allEntries\n\t\t\t.slice()\n\t\t\t.reverse()\n\t\t\t.map((e) => e.content)\n\t\t\t.join(\"\\n\\n\");\n\n\t\tthis.ctx.chatContainer.addChild(new Spacer(1));\n\t\tthis.ctx.chatContainer.addChild(new DynamicBorder());\n\t\tthis.ctx.chatContainer.addChild(new Text(theme.bold(theme.fg(\"accent\", \"What's New\")), 1, 0));\n\t\tthis.ctx.chatContainer.addChild(new Spacer(1));\n\t\tthis.ctx.chatContainer.addChild(new Markdown(changelogMarkdown, 1, 1, this.ctx.getMarkdownThemeWithSettings()));\n\t\tthis.ctx.chatContainer.addChild(new DynamicBorder());\n\t\tthis.ctx.ui.requestRender();\n\t}\n\n\thandleHotkeys(): void {\n\t\t// Navigation keybindings\n\t\tconst cursorUp = keyDisplayText(\"tui.editor.cursorUp\");\n\t\tconst cursorDown = keyDisplayText(\"tui.editor.cursorDown\");\n\t\tconst cursorLeft = keyDisplayText(\"tui.editor.cursorLeft\");\n\t\tconst cursorRight = keyDisplayText(\"tui.editor.cursorRight\");\n\t\tconst cursorWordLeft = keyDisplayText(\"tui.editor.cursorWordLeft\");\n\t\tconst cursorWordRight = keyDisplayText(\"tui.editor.cursorWordRight\");\n\t\tconst cursorLineStart = keyDisplayText(\"tui.editor.cursorLineStart\");\n\t\tconst cursorLineEnd = keyDisplayText(\"tui.editor.cursorLineEnd\");\n\t\tconst jumpForward = keyDisplayText(\"tui.editor.jumpForward\");\n\t\tconst jumpBackward = keyDisplayText(\"tui.editor.jumpBackward\");\n\t\tconst pageUp = keyDisplayText(\"tui.editor.pageUp\");\n\t\tconst pageDown = keyDisplayText(\"tui.editor.pageDown\");\n\n\t\t// Editing keybindings\n\t\tconst submit = keyDisplayText(\"tui.input.submit\");\n\t\tconst newLine = keyDisplayText(\"tui.input.newLine\");\n\t\tconst deleteWordBackward = keyDisplayText(\"tui.editor.deleteWordBackward\");\n\t\tconst deleteWordForward = keyDisplayText(\"tui.editor.deleteWordForward\");\n\t\tconst deleteToLineStart = keyDisplayText(\"tui.editor.deleteToLineStart\");\n\t\tconst deleteToLineEnd = keyDisplayText(\"tui.editor.deleteToLineEnd\");\n\t\tconst yank = keyDisplayText(\"tui.editor.yank\");\n\t\tconst yankPop = keyDisplayText(\"tui.editor.yankPop\");\n\t\tconst undo = keyDisplayText(\"tui.editor.undo\");\n\t\tconst tab = keyDisplayText(\"tui.input.tab\");\n\n\t\t// App keybindings\n\t\tconst interrupt = keyDisplayText(\"app.interrupt\");\n\t\tconst clear = keyDisplayText(\"app.clear\");\n\t\tconst exit = keyDisplayText(\"app.exit\");\n\t\tconst suspend = keyDisplayText(\"app.suspend\");\n\t\tconst cycleThinkingLevel = keyDisplayText(\"app.thinking.cycle\");\n\t\tconst cycleModelForward = keyDisplayText(\"app.model.cycleForward\");\n\t\tconst selectModel = keyDisplayText(\"app.model.select\");\n\t\tconst expandTools = keyDisplayText(\"app.tools.expand\");\n\t\tconst toggleThinking = keyDisplayText(\"app.thinking.toggle\");\n\t\tconst cycleTaskView = keyDisplayText(\"app.tasks.cycleView\");\n\t\tconst externalEditor = keyDisplayText(\"app.editor.external\");\n\t\tconst cycleModelBackward = keyDisplayText(\"app.model.cycleBackward\");\n\t\tconst followUp = keyDisplayText(\"app.message.followUp\");\n\t\tconst dequeue = keyDisplayText(\"app.message.dequeue\");\n\t\tconst pasteImage = keyDisplayText(\"app.clipboard.pasteImage\");\n\n\t\tlet hotkeys = `\n**Navigation**\n| Key | Action |\n|-----|--------|\n| \\`${cursorUp}\\` / \\`${cursorDown}\\` / \\`${cursorLeft}\\` / \\`${cursorRight}\\` | Move cursor / browse history (Up when empty) |\n| \\`${cursorWordLeft}\\` / \\`${cursorWordRight}\\` | Move by word |\n| \\`${cursorLineStart}\\` | Start of line |\n| \\`${cursorLineEnd}\\` | End of line |\n| \\`${jumpForward}\\` | Jump forward to character |\n| \\`${jumpBackward}\\` | Jump backward to character |\n| \\`${pageUp}\\` / \\`${pageDown}\\` | Scroll by page |\n\n**Editing**\n| Key | Action |\n|-----|--------|\n| \\`${submit}\\` | Send message |\n| \\`${newLine}\\` | New line${process.platform === \"win32\" ? \" (Ctrl+Enter on Windows Terminal)\" : \"\"} |\n| \\`${deleteWordBackward}\\` | Delete word backwards |\n| \\`${deleteWordForward}\\` | Delete word forwards |\n| \\`${deleteToLineStart}\\` | Delete to start of line |\n| \\`${deleteToLineEnd}\\` | Delete to end of line |\n| \\`${yank}\\` | Paste the most-recently-deleted text |\n| \\`${yankPop}\\` | Cycle through the deleted text after pasting |\n| \\`${undo}\\` | Undo |\n\n**Other**\n| Key | Action |\n|-----|--------|\n| \\`${tab}\\` | Path completion / accept autocomplete |\n| \\`${interrupt}\\` | Cancel autocomplete / abort streaming |\n| \\`${clear}\\` | Clear editor (first) / exit (second) |\n| \\`${exit}\\` | Exit (when editor is empty) |\n| \\`${suspend}\\` | Suspend to background |\n| \\`${cycleThinkingLevel}\\` | Cycle thinking level |\n| \\`${cycleModelForward}\\` / \\`${cycleModelBackward}\\` | Cycle models |\n| \\`${selectModel}\\` | Open model selector |\n| \\`${expandTools}\\` | Toggle tool output expansion |\n| \\`${toggleThinking}\\` | Toggle thinking block visibility |\n| \\`${cycleTaskView}\\` | Cycle task panel view (tasks → subagents → teams) |\n| \\`${externalEditor}\\` | Edit message in external editor |\n| \\`${followUp}\\` | Queue follow-up message |\n| \\`${dequeue}\\` | Restore queued messages |\n| \\`${pasteImage}\\` | Paste image from clipboard |\n| \\`/\\` | Slash commands |\n| \\`!\\` | Run bash command |\n| \\`!!\\` | Run bash command (excluded from context) |\n`;\n\n\t\t// Add extension-registered shortcuts\n\t\tconst extensionRunner = this.ctx.session.extensionRunner;\n\t\tconst shortcuts = extensionRunner.getShortcuts(this.ctx.keybindings.getEffectiveConfig());\n\t\tif (shortcuts.size > 0) {\n\t\t\thotkeys += `\n**Extensions**\n| Key | Action |\n|-----|--------|\n`;\n\t\t\tfor (const [key, shortcut] of shortcuts) {\n\t\t\t\tconst description = shortcut.description ?? shortcut.extensionPath;\n\t\t\t\tconst keyDisplay = formatKeyText(key, { capitalize: true });\n\t\t\t\thotkeys += `| \\`${keyDisplay}\\` | ${description} |\\n`;\n\t\t\t}\n\t\t}\n\n\t\tthis.ctx.chatContainer.addChild(new Spacer(1));\n\t\tthis.ctx.chatContainer.addChild(new DynamicBorder());\n\t\tthis.ctx.chatContainer.addChild(new Text(theme.bold(theme.fg(\"accent\", \"Keyboard Shortcuts\")), 1, 0));\n\t\tthis.ctx.chatContainer.addChild(new Spacer(1));\n\t\tthis.ctx.chatContainer.addChild(new Markdown(hotkeys.trim(), 1, 1, this.ctx.getMarkdownThemeWithSettings()));\n\t\tthis.ctx.chatContainer.addChild(new DynamicBorder());\n\t\tthis.ctx.ui.requestRender();\n\t}\n\n\tasync handleClear(): Promise<void> {\n\t\tthis.ctx.stopLoadingAnimation();\n\t\tthis.ctx.statusContainer.clear();\n\t\ttry {\n\t\t\tconst result = await this.ctx.runtimeHost.newSession();\n\t\t\tif (result.cancelled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.ctx.renderCurrentSessionState();\n\t\t\tthis.ctx.chatContainer.addChild(new Spacer(1));\n\t\t\tthis.ctx.chatContainer.addChild(new Text(`${theme.fg(\"accent\", \"✓ New session started\")}`, 1, 1));\n\t\t\tthis.ctx.ui.requestRender();\n\t\t} catch (error: unknown) {\n\t\t\tawait this.ctx.handleFatalRuntimeError(\"Failed to create session\", error);\n\t\t}\n\t}\n\n\thandleDebug(): void {\n\t\tconst width = this.ctx.ui.terminal.columns;\n\t\tconst height = this.ctx.ui.terminal.rows;\n\t\tconst allLines = this.ctx.ui.render(width);\n\n\t\tconst debugLogPath = getDebugLogPath();\n\t\tconst debugData = [\n\t\t\t`Debug output at ${new Date().toISOString()}`,\n\t\t\t`Terminal: ${width}x${height}`,\n\t\t\t`Total lines: ${allLines.length}`,\n\t\t\t\"\",\n\t\t\t\"=== All rendered lines with visible widths ===\",\n\t\t\t...allLines.map((line, idx) => {\n\t\t\t\tconst vw = visibleWidth(line);\n\t\t\t\tconst escaped = JSON.stringify(line);\n\t\t\t\treturn `[${idx}] (w=${vw}) ${escaped}`;\n\t\t\t}),\n\t\t\t\"\",\n\t\t\t\"=== Agent messages (JSONL) ===\",\n\t\t\t...this.ctx.session.messages.map((msg) => JSON.stringify(msg)),\n\t\t\t\"\",\n\t\t].join(\"\\n\");\n\n\t\tfs.mkdirSync(path.dirname(debugLogPath), { recursive: true });\n\t\tfs.writeFileSync(debugLogPath, debugData);\n\n\t\tthis.ctx.chatContainer.addChild(new Spacer(1));\n\t\tthis.ctx.chatContainer.addChild(\n\t\t\tnew Text(`${theme.fg(\"accent\", \"✓ Debug log written\")}\\n${theme.fg(\"muted\", debugLogPath)}`, 1, 1),\n\t\t);\n\t\tthis.ctx.ui.requestRender();\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"command-executor.d.ts","sourceRoot":"","sources":["../../../src/modes/interactive/command-executor.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Extracted command handlers from InteractiveMode.\n * All methods receive dependencies via CommandContext rather than\n * reaching back into the parent class.\n */\n\nimport * as fs from \"node:fs\";\nimport * as os from \"node:os\";\nimport * as path from \"node:path\";\nimport type { Model } from \"@kolisachint/hoocode-ai\";\nimport type { EditorComponent, MarkdownTheme, TUI } from \"@kolisachint/hoocode-tui\";\nimport { type Container, Markdown, Spacer, Text, visibleWidth } from \"@kolisachint/hoocode-tui\";\nimport { spawn, spawnSync } from \"child_process\";\nimport { getDebugLogPath, getShareViewerUrl } from \"../../config.js\";\nimport { loadAgentRegistry } from \"../../core/agent-registry.js\";\nimport type { AgentSession } from \"../../core/agent-session.js\";\nimport type { AgentSessionRuntime } from \"../../core/agent-session-runtime.js\";\nimport { SessionImportFileNotFoundError } from \"../../core/agent-session-runtime.js\";\nimport type { KeybindingsManager } from \"../../core/keybindings.js\";\nimport { MissingSessionCwdError } from \"../../core/session-cwd.js\";\nimport type { SessionManager } from \"../../core/session-manager.js\";\nimport { getSubagentPool } from \"../../core/subagent-pool-instance.js\";\nimport type { SubagentResultFile } from \"../../core/subagent-result.js\";\nimport { getChangelogPath, parseChangelog } from \"../../utils/changelog.js\";\nimport { copyToClipboard } from \"../../utils/clipboard.js\";\nimport { BorderedLoader } from \"./components/bordered-loader.js\";\nimport { DynamicBorder } from \"./components/dynamic-border.js\";\nimport type { FooterComponent } from \"./components/footer.js\";\nimport { formatKeyText, keyDisplayText } from \"./components/keybinding-hints.js\";\nimport { theme } from \"./theme/theme.js\";\n\nexport interface CommandContext {\n\t// Core dependencies\n\tsession: AgentSession;\n\tsessionManager: SessionManager;\n\truntimeHost: AgentSessionRuntime;\n\tui: TUI;\n\teditor: EditorComponent;\n\teditorContainer: Container;\n\tchatContainer: Container;\n\tstatusContainer: Container;\n\tfooter: FooterComponent;\n\tkeybindings: KeybindingsManager;\n\n\t// UI callbacks\n\tshowStatus: (message: string) => void;\n\tshowError: (message: string) => void;\n\tshowWarning: (message: string) => void;\n\tupdateEditorBorderColor: () => void;\n\trenderCurrentSessionState: () => void;\n\trebuildChatFromMessages: () => void;\n\tgetMarkdownThemeWithSettings: () => MarkdownTheme;\n\tstopLoadingAnimation: () => void;\n\n\t// Auth/model helpers\n\tfindExactModelMatch: (searchTerm: string) => Promise<Model<any> | undefined>;\n\tmaybeWarnAboutAnthropicSubscriptionAuth: (model?: Model<any>) => Promise<void>;\n\n\t// Dialog callbacks\n\tshowModelSelector: (searchTerm?: string) => void;\n\tshowExtensionConfirm: (title: string, message: string) => Promise<boolean>;\n\tpromptForMissingSessionCwd: (error: MissingSessionCwdError) => Promise<string | undefined>;\n\n\t// Fatal error handler\n\thandleFatalRuntimeError: (prefix: string, error: unknown) => Promise<never>;\n}\n\nexport class CommandExecutor {\n\tconstructor(private readonly ctx: CommandContext) {}\n\n\t// =========================================================================\n\t// Slash command handlers\n\t// =========================================================================\n\n\tasync handleModel(searchTerm?: string): Promise<void> {\n\t\tif (!searchTerm) {\n\t\t\tthis.ctx.showModelSelector();\n\t\t\treturn;\n\t\t}\n\n\t\tconst model = await this.ctx.findExactModelMatch(searchTerm);\n\t\tif (model) {\n\t\t\ttry {\n\t\t\t\tawait this.ctx.session.setModel(model);\n\t\t\t\tthis.ctx.footer.invalidate();\n\t\t\t\tthis.ctx.updateEditorBorderColor();\n\t\t\t\tthis.ctx.showStatus(`Model: ${model.id}`);\n\t\t\t\tvoid this.ctx.maybeWarnAboutAnthropicSubscriptionAuth(model);\n\t\t\t} catch (error) {\n\t\t\t\tthis.ctx.showError(error instanceof Error ? error.message : String(error));\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tthis.ctx.showModelSelector(searchTerm);\n\t}\n\n\tasync handleClone(): Promise<void> {\n\t\tconst leafId = this.ctx.sessionManager.getLeafId();\n\t\tif (!leafId) {\n\t\t\tthis.ctx.showStatus(\"Nothing to clone yet\");\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tconst result = await this.ctx.runtimeHost.fork(leafId, { position: \"at\" });\n\t\t\tif (result.cancelled) {\n\t\t\t\tthis.ctx.ui.requestRender();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis.ctx.renderCurrentSessionState();\n\t\t\tthis.ctx.editor.setText(\"\");\n\t\t\tthis.ctx.showStatus(\"Cloned to new session\");\n\t\t} catch (error: unknown) {\n\t\t\tthis.ctx.showError(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\n\tasync handleSubagent(text: string): Promise<void> {\n\t\tconst prefix = \"/subagent \";\n\t\tconst args = text.startsWith(prefix) ? text.slice(prefix.length).trim() : \"\";\n\t\tif (!args) {\n\t\t\tthis.ctx.showStatus(\"Usage: /subagent <mode> <task>\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst firstSpace = args.indexOf(\" \");\n\t\tif (firstSpace === -1) {\n\t\t\tthis.ctx.showStatus(\"Usage: /subagent <mode> <task>\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst mode = args.slice(0, firstSpace).trim();\n\t\tconst task = args.slice(firstSpace + 1).trim();\n\t\tif (!task) {\n\t\t\tthis.ctx.showStatus(\"Usage: /subagent <mode> <task>\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst validModes = loadAgentRegistry({ cwd: this.ctx.sessionManager.getCwd() })\n\t\t\t.list()\n\t\t\t.map((a) => a.name);\n\t\tif (!validModes.includes(mode)) {\n\t\t\tthis.ctx.showStatus(`Unknown subagent_type: ${mode}. Available: ${validModes.join(\", \")}`);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.ctx.showStatus(`Spawning ${mode} subagent...`);\n\t\ttry {\n\t\t\tconst pool = getSubagentPool(this.ctx.sessionManager.getCwd(), this.ctx.session.modelRegistry.getAvailable());\n\t\t\tconst dispatchResult = await pool.dispatch(task, {\n\t\t\t\tforceAgent: mode,\n\t\t\t\tmodel: this.ctx.session.model?.id,\n\t\t\t\tprovider: this.ctx.session.model?.provider,\n\t\t\t});\n\t\t\tconst result = dispatchResult.result;\n\t\t\tconst resultData = result?.result_data as SubagentResultFile | undefined;\n\t\t\tif (result?.ok) {\n\t\t\t\tthis.ctx.showStatus(`${mode} subagent completed`);\n\t\t\t\t// Inject the subagent answer as a custom message so the user can see it in the chat\n\t\t\t\tthis.ctx.sessionManager.appendMessage({\n\t\t\t\t\trole: \"custom\",\n\t\t\t\t\tcustomType: \"subagent\",\n\t\t\t\t\tcontent: resultData?.summary || \"(no output)\",\n\t\t\t\t\tdisplay: true,\n\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthis.ctx.showError(`Subagent (${mode}) failed: ${result?.error ?? \"unknown error\"}`);\n\t\t\t}\n\t\t} catch (error: unknown) {\n\t\t\tthis.ctx.showError(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\n\tasync handleExport(text: string): Promise<void> {\n\t\tconst outputPath = this.getPathArgument(text, \"/export\");\n\n\t\ttry {\n\t\t\tif (outputPath?.endsWith(\".jsonl\")) {\n\t\t\t\tconst filePath = this.ctx.session.exportToJsonl(outputPath);\n\t\t\t\tthis.ctx.showStatus(`Session exported to: ${filePath}`);\n\t\t\t} else {\n\t\t\t\tconst filePath = await this.ctx.session.exportToHtml(outputPath);\n\t\t\t\tthis.ctx.showStatus(`Session exported to: ${filePath}`);\n\t\t\t}\n\t\t} catch (error: unknown) {\n\t\t\tthis.ctx.showError(`Failed to export session: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n\t\t}\n\t}\n\n\tprivate getPathArgument(text: string, command: \"/export\" | \"/import\"): string | undefined {\n\t\tif (text === command) {\n\t\t\treturn undefined;\n\t\t}\n\t\tif (!text.startsWith(`${command} `)) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst argsString = text.slice(command.length + 1).trimStart();\n\t\tif (!argsString) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tconst firstChar = argsString[0];\n\t\tif (firstChar === '\"' || firstChar === \"'\") {\n\t\t\tconst closingQuoteIndex = argsString.indexOf(firstChar, 1);\n\t\t\tif (closingQuoteIndex < 0) {\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t\treturn argsString.slice(1, closingQuoteIndex);\n\t\t}\n\n\t\tconst firstWhitespaceIndex = argsString.search(/\\s/);\n\t\tif (firstWhitespaceIndex < 0) {\n\t\t\treturn argsString;\n\t\t}\n\t\treturn argsString.slice(0, firstWhitespaceIndex);\n\t}\n\n\tasync handleImport(text: string): Promise<void> {\n\t\tconst inputPath = this.getPathArgument(text, \"/import\");\n\t\tif (!inputPath) {\n\t\t\tthis.ctx.showError(\"Usage: /import <path.jsonl>\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst confirmed = await this.ctx.showExtensionConfirm(\n\t\t\t\"Import session\",\n\t\t\t`Replace current session with ${inputPath}?`,\n\t\t);\n\t\tif (!confirmed) {\n\t\t\tthis.ctx.showStatus(\"Import cancelled\");\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tthis.ctx.stopLoadingAnimation();\n\t\t\tthis.ctx.statusContainer.clear();\n\t\t\tconst result = await this.ctx.runtimeHost.importFromJsonl(inputPath);\n\t\t\tif (result.cancelled) {\n\t\t\t\tthis.ctx.showStatus(\"Import cancelled\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.ctx.renderCurrentSessionState();\n\t\t\tthis.ctx.showStatus(`Session imported from: ${inputPath}`);\n\t\t} catch (error: unknown) {\n\t\t\tif (error instanceof MissingSessionCwdError) {\n\t\t\t\tconst selectedCwd = await this.ctx.promptForMissingSessionCwd(error);\n\t\t\t\tif (!selectedCwd) {\n\t\t\t\t\tthis.ctx.showStatus(\"Import cancelled\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst result = await this.ctx.runtimeHost.importFromJsonl(inputPath, selectedCwd);\n\t\t\t\tif (result.cancelled) {\n\t\t\t\t\tthis.ctx.showStatus(\"Import cancelled\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tthis.ctx.renderCurrentSessionState();\n\t\t\t\tthis.ctx.showStatus(`Session imported from: ${inputPath}`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (error instanceof SessionImportFileNotFoundError) {\n\t\t\t\tthis.ctx.showError(`Failed to import session: ${error.message}`);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait this.ctx.handleFatalRuntimeError(\"Failed to import session\", error);\n\t\t}\n\t}\n\n\tasync handleShare(): Promise<void> {\n\t\t// Check if gh is available and logged in\n\t\ttry {\n\t\t\tconst authResult = spawnSync(\"gh\", [\"auth\", \"status\"], { encoding: \"utf-8\" });\n\t\t\tif (authResult.status !== 0) {\n\t\t\t\tthis.ctx.showError(\"GitHub CLI is not logged in. Run 'gh auth login' first.\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t} catch {\n\t\t\tthis.ctx.showError(\"GitHub CLI (gh) is not installed. Install it from https://cli.github.com/\");\n\t\t\treturn;\n\t\t}\n\n\t\t// Export to a temp file\n\t\tconst tmpFile = path.join(os.tmpdir(), \"session.html\");\n\t\ttry {\n\t\t\tawait this.ctx.session.exportToHtml(tmpFile);\n\t\t} catch (error: unknown) {\n\t\t\tthis.ctx.showError(`Failed to export session: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n\t\t\treturn;\n\t\t}\n\n\t\t// Show cancellable loader, replacing the editor\n\t\tconst loader = new BorderedLoader(this.ctx.ui, theme, \"Creating gist...\");\n\t\tthis.ctx.editorContainer.clear();\n\t\tthis.ctx.editorContainer.addChild(loader);\n\t\tthis.ctx.ui.setFocus(loader);\n\t\tthis.ctx.ui.requestRender();\n\n\t\tconst restoreEditor = () => {\n\t\t\tloader.dispose();\n\t\t\tthis.ctx.editorContainer.clear();\n\t\t\tthis.ctx.editorContainer.addChild(this.ctx.editor);\n\t\t\tthis.ctx.ui.setFocus(this.ctx.editor);\n\t\t\ttry {\n\t\t\t\tfs.unlinkSync(tmpFile);\n\t\t\t} catch {\n\t\t\t\t// Ignore cleanup errors\n\t\t\t}\n\t\t};\n\n\t\t// Create a secret gist asynchronously\n\t\tlet proc: ReturnType<typeof spawn> | null = null;\n\n\t\tloader.onAbort = () => {\n\t\t\tproc?.kill();\n\t\t\trestoreEditor();\n\t\t\tthis.ctx.showStatus(\"Share cancelled\");\n\t\t};\n\n\t\ttry {\n\t\t\tconst result = await new Promise<{ stdout: string; stderr: string; code: number | null }>((resolve) => {\n\t\t\t\tproc = spawn(\"gh\", [\"gist\", \"create\", \"--public=false\", tmpFile]);\n\t\t\t\tlet stdout = \"\";\n\t\t\t\tlet stderr = \"\";\n\t\t\t\tproc.stdout?.on(\"data\", (data) => {\n\t\t\t\t\tstdout += data.toString();\n\t\t\t\t});\n\t\t\t\tproc.stderr?.on(\"data\", (data) => {\n\t\t\t\t\tstderr += data.toString();\n\t\t\t\t});\n\t\t\t\tproc.on(\"close\", (code) => resolve({ stdout, stderr, code }));\n\t\t\t});\n\n\t\t\tif (loader.signal.aborted) return;\n\n\t\t\trestoreEditor();\n\n\t\t\tif (result.code !== 0) {\n\t\t\t\tconst errorMsg = result.stderr?.trim() || \"Unknown error\";\n\t\t\t\tthis.ctx.showError(`Failed to create gist: ${errorMsg}`);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Extract gist ID from the URL returned by gh\n\t\t\t// gh returns something like: https://gist.github.com/username/GIST_ID\n\t\t\tconst gistUrl = result.stdout?.trim();\n\t\t\tconst gistId = gistUrl?.split(\"/\").pop();\n\t\t\tif (!gistId) {\n\t\t\t\tthis.ctx.showError(\"Failed to parse gist ID from gh output\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Create the preview URL\n\t\t\tconst previewUrl = getShareViewerUrl(gistId);\n\t\t\tthis.ctx.showStatus(`Share URL: ${previewUrl}\\nGist: ${gistUrl}`);\n\t\t} catch (error: unknown) {\n\t\t\tif (!loader.signal.aborted) {\n\t\t\t\trestoreEditor();\n\t\t\t\tthis.ctx.showError(`Failed to create gist: ${error instanceof Error ? error.message : \"Unknown error\"}`);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync handleCopy(): Promise<void> {\n\t\tconst text = this.ctx.session.getLastAssistantText();\n\t\tif (!text) {\n\t\t\tthis.ctx.showError(\"No agent messages to copy yet.\");\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tawait copyToClipboard(text);\n\t\t\tthis.ctx.showStatus(\"Copied last agent message to clipboard\");\n\t\t} catch (error) {\n\t\t\tthis.ctx.showError(error instanceof Error ? error.message : String(error));\n\t\t}\n\t}\n\n\thandleName(text: string): void {\n\t\tconst name = text.replace(/^\\/name\\s*/, \"\").trim();\n\t\tif (!name) {\n\t\t\tconst currentName = this.ctx.sessionManager.getSessionName();\n\t\t\tif (currentName) {\n\t\t\t\tthis.ctx.chatContainer.addChild(new Spacer(1));\n\t\t\t\tthis.ctx.chatContainer.addChild(new Text(theme.fg(\"dim\", `Session name: ${currentName}`), 1, 0));\n\t\t\t} else {\n\t\t\t\tthis.ctx.showWarning(\"Usage: /name <name>\");\n\t\t\t}\n\t\t\tthis.ctx.ui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tthis.ctx.session.setSessionName(name);\n\t\tthis.ctx.chatContainer.addChild(new Spacer(1));\n\t\tthis.ctx.chatContainer.addChild(new Text(theme.fg(\"dim\", `Session name set: ${name}`), 1, 0));\n\t\tthis.ctx.ui.requestRender();\n\t}\n\n\thandleSession(): void {\n\t\tconst stats = this.ctx.session.getSessionStats();\n\t\tconst sessionName = this.ctx.sessionManager.getSessionName();\n\n\t\tlet info = `${theme.bold(\"Session Info\")}\\n\\n`;\n\t\tif (sessionName) {\n\t\t\tinfo += `${theme.fg(\"dim\", \"Name:\")} ${sessionName}\\n`;\n\t\t}\n\t\tinfo += `${theme.fg(\"dim\", \"File:\")} ${stats.sessionFile ?? \"In-memory\"}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", \"ID:\")} ${stats.sessionId}\\n\\n`;\n\t\tinfo += `${theme.bold(\"Messages\")}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", \"User:\")} ${stats.userMessages}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", \"Assistant:\")} ${stats.assistantMessages}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", \"Tool Calls:\")} ${stats.toolCalls}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", \"Tool Results:\")} ${stats.toolResults}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", \"Total:\")} ${stats.totalMessages}\\n\\n`;\n\t\tinfo += `${theme.bold(\"Tokens\")}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", \"Input:\")} ${stats.tokens.input.toLocaleString()}\\n`;\n\t\tinfo += `${theme.fg(\"dim\", \"Output:\")} ${stats.tokens.output.toLocaleString()}\\n`;\n\t\tif (stats.tokens.cacheRead > 0) {\n\t\t\tinfo += `${theme.fg(\"dim\", \"Cache Read:\")} ${stats.tokens.cacheRead.toLocaleString()}\\n`;\n\t\t}\n\t\tif (stats.tokens.cacheWrite > 0) {\n\t\t\tinfo += `${theme.fg(\"dim\", \"Cache Write:\")} ${stats.tokens.cacheWrite.toLocaleString()}\\n`;\n\t\t}\n\t\tinfo += `${theme.fg(\"dim\", \"Total:\")} ${stats.tokens.total.toLocaleString()}\\n`;\n\n\t\tif (stats.cost > 0) {\n\t\t\tinfo += `\\n${theme.bold(\"Cost\")}\\n`;\n\t\t\tinfo += `${theme.fg(\"dim\", \"Total:\")} ${stats.cost.toFixed(4)}`;\n\t\t}\n\n\t\tthis.ctx.chatContainer.addChild(new Spacer(1));\n\t\tthis.ctx.chatContainer.addChild(new Text(info, 1, 0));\n\t\tthis.ctx.ui.requestRender();\n\t}\n\n\thandleChangelog(): void {\n\t\tconst changelogPath = getChangelogPath();\n\t\tconst allEntries = parseChangelog(changelogPath);\n\n\t\tif (allEntries.length === 0) {\n\t\t\tthis.ctx.chatContainer.addChild(new Spacer(1));\n\t\t\tthis.ctx.chatContainer.addChild(new Text(theme.fg(\"dim\", \"No changelog entries found.\"), 1, 0));\n\t\t\tthis.ctx.ui.requestRender();\n\t\t\treturn;\n\t\t}\n\n\t\tconst changelogMarkdown = allEntries\n\t\t\t.slice()\n\t\t\t.reverse()\n\t\t\t.map((e) => e.content)\n\t\t\t.join(\"\\n\\n\");\n\n\t\tthis.ctx.chatContainer.addChild(new Spacer(1));\n\t\tthis.ctx.chatContainer.addChild(new DynamicBorder());\n\t\tthis.ctx.chatContainer.addChild(new Text(theme.bold(theme.fg(\"accent\", \"What's New\")), 1, 0));\n\t\tthis.ctx.chatContainer.addChild(new Spacer(1));\n\t\tthis.ctx.chatContainer.addChild(new Markdown(changelogMarkdown, 1, 1, this.ctx.getMarkdownThemeWithSettings()));\n\t\tthis.ctx.chatContainer.addChild(new DynamicBorder());\n\t\tthis.ctx.ui.requestRender();\n\t}\n\n\thandleHotkeys(): void {\n\t\t// Navigation keybindings\n\t\tconst cursorUp = keyDisplayText(\"tui.editor.cursorUp\");\n\t\tconst cursorDown = keyDisplayText(\"tui.editor.cursorDown\");\n\t\tconst cursorLeft = keyDisplayText(\"tui.editor.cursorLeft\");\n\t\tconst cursorRight = keyDisplayText(\"tui.editor.cursorRight\");\n\t\tconst cursorWordLeft = keyDisplayText(\"tui.editor.cursorWordLeft\");\n\t\tconst cursorWordRight = keyDisplayText(\"tui.editor.cursorWordRight\");\n\t\tconst cursorLineStart = keyDisplayText(\"tui.editor.cursorLineStart\");\n\t\tconst cursorLineEnd = keyDisplayText(\"tui.editor.cursorLineEnd\");\n\t\tconst jumpForward = keyDisplayText(\"tui.editor.jumpForward\");\n\t\tconst jumpBackward = keyDisplayText(\"tui.editor.jumpBackward\");\n\t\tconst pageUp = keyDisplayText(\"tui.editor.pageUp\");\n\t\tconst pageDown = keyDisplayText(\"tui.editor.pageDown\");\n\n\t\t// Editing keybindings\n\t\tconst submit = keyDisplayText(\"tui.input.submit\");\n\t\tconst newLine = keyDisplayText(\"tui.input.newLine\");\n\t\tconst deleteWordBackward = keyDisplayText(\"tui.editor.deleteWordBackward\");\n\t\tconst deleteWordForward = keyDisplayText(\"tui.editor.deleteWordForward\");\n\t\tconst deleteToLineStart = keyDisplayText(\"tui.editor.deleteToLineStart\");\n\t\tconst deleteToLineEnd = keyDisplayText(\"tui.editor.deleteToLineEnd\");\n\t\tconst yank = keyDisplayText(\"tui.editor.yank\");\n\t\tconst yankPop = keyDisplayText(\"tui.editor.yankPop\");\n\t\tconst undo = keyDisplayText(\"tui.editor.undo\");\n\t\tconst tab = keyDisplayText(\"tui.input.tab\");\n\n\t\t// App keybindings\n\t\tconst interrupt = keyDisplayText(\"app.interrupt\");\n\t\tconst clear = keyDisplayText(\"app.clear\");\n\t\tconst exit = keyDisplayText(\"app.exit\");\n\t\tconst suspend = keyDisplayText(\"app.suspend\");\n\t\tconst cycleThinkingLevel = keyDisplayText(\"app.thinking.cycle\");\n\t\tconst cycleModelForward = keyDisplayText(\"app.model.cycleForward\");\n\t\tconst selectModel = keyDisplayText(\"app.model.select\");\n\t\tconst expandTools = keyDisplayText(\"app.tools.expand\");\n\t\tconst toggleThinking = keyDisplayText(\"app.thinking.toggle\");\n\t\tconst cycleTaskView = keyDisplayText(\"app.tasks.cycleView\");\n\t\tconst externalEditor = keyDisplayText(\"app.editor.external\");\n\t\tconst cycleModelBackward = keyDisplayText(\"app.model.cycleBackward\");\n\t\tconst followUp = keyDisplayText(\"app.message.followUp\");\n\t\tconst dequeue = keyDisplayText(\"app.message.dequeue\");\n\t\tconst pasteImage = keyDisplayText(\"app.clipboard.pasteImage\");\n\n\t\tlet hotkeys = `\n**Navigation**\n| Key | Action |\n|-----|--------|\n| \\`${cursorUp}\\` / \\`${cursorDown}\\` / \\`${cursorLeft}\\` / \\`${cursorRight}\\` | Move cursor / browse history (Up when empty) |\n| \\`${cursorWordLeft}\\` / \\`${cursorWordRight}\\` | Move by word |\n| \\`${cursorLineStart}\\` | Start of line |\n| \\`${cursorLineEnd}\\` | End of line |\n| \\`${jumpForward}\\` | Jump forward to character |\n| \\`${jumpBackward}\\` | Jump backward to character |\n| \\`${pageUp}\\` / \\`${pageDown}\\` | Scroll by page |\n\n**Editing**\n| Key | Action |\n|-----|--------|\n| \\`${submit}\\` | Send message |\n| \\`${newLine}\\` | New line${process.platform === \"win32\" ? \" (Ctrl+Enter on Windows Terminal)\" : \"\"} |\n| \\`${deleteWordBackward}\\` | Delete word backwards |\n| \\`${deleteWordForward}\\` | Delete word forwards |\n| \\`${deleteToLineStart}\\` | Delete to start of line |\n| \\`${deleteToLineEnd}\\` | Delete to end of line |\n| \\`${yank}\\` | Paste the most-recently-deleted text |\n| \\`${yankPop}\\` | Cycle through the deleted text after pasting |\n| \\`${undo}\\` | Undo |\n\n**Other**\n| Key | Action |\n|-----|--------|\n| \\`${tab}\\` | Path completion / accept autocomplete |\n| \\`${interrupt}\\` | Cancel autocomplete / abort streaming |\n| \\`${clear}\\` | Clear editor (first) / exit (second) |\n| \\`${exit}\\` | Exit (when editor is empty) |\n| \\`${suspend}\\` | Suspend to background |\n| \\`${cycleThinkingLevel}\\` | Cycle thinking level |\n| \\`${cycleModelForward}\\` / \\`${cycleModelBackward}\\` | Cycle models |\n| \\`${selectModel}\\` | Open model selector |\n| \\`${expandTools}\\` | Toggle tool output expansion |\n| \\`${toggleThinking}\\` | Toggle thinking block visibility |\n| \\`${cycleTaskView}\\` | Cycle task panel view (tasks → subagents → teams) |\n| \\`${externalEditor}\\` | Edit message in external editor |\n| \\`${followUp}\\` | Queue follow-up message |\n| \\`${dequeue}\\` | Restore queued messages |\n| \\`${pasteImage}\\` | Paste image from clipboard |\n| \\`/\\` | Slash commands |\n| \\`!\\` | Run bash command |\n| \\`!!\\` | Run bash command (excluded from context) |\n`;\n\n\t\t// Add extension-registered shortcuts\n\t\tconst extensionRunner = this.ctx.session.extensionRunner;\n\t\tconst shortcuts = extensionRunner.getShortcuts(this.ctx.keybindings.getEffectiveConfig());\n\t\tif (shortcuts.size > 0) {\n\t\t\thotkeys += `\n**Extensions**\n| Key | Action |\n|-----|--------|\n`;\n\t\t\tfor (const [key, shortcut] of shortcuts) {\n\t\t\t\tconst description = shortcut.description ?? shortcut.extensionPath;\n\t\t\t\tconst keyDisplay = formatKeyText(key, { capitalize: true });\n\t\t\t\thotkeys += `| \\`${keyDisplay}\\` | ${description} |\\n`;\n\t\t\t}\n\t\t}\n\n\t\tthis.ctx.chatContainer.addChild(new Spacer(1));\n\t\tthis.ctx.chatContainer.addChild(new DynamicBorder());\n\t\tthis.ctx.chatContainer.addChild(new Text(theme.bold(theme.fg(\"accent\", \"Keyboard Shortcuts\")), 1, 0));\n\t\tthis.ctx.chatContainer.addChild(new Spacer(1));\n\t\tthis.ctx.chatContainer.addChild(new Markdown(hotkeys.trim(), 1, 1, this.ctx.getMarkdownThemeWithSettings()));\n\t\tthis.ctx.chatContainer.addChild(new DynamicBorder());\n\t\tthis.ctx.ui.requestRender();\n\t}\n\n\tasync handleClear(): Promise<void> {\n\t\tthis.ctx.stopLoadingAnimation();\n\t\tthis.ctx.statusContainer.clear();\n\t\ttry {\n\t\t\tconst result = await this.ctx.runtimeHost.newSession();\n\t\t\tif (result.cancelled) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis.ctx.renderCurrentSessionState();\n\t\t\tthis.ctx.chatContainer.addChild(new Spacer(1));\n\t\t\tthis.ctx.chatContainer.addChild(new Text(`${theme.fg(\"accent\", \"✓ New session started\")}`, 1, 1));\n\t\t\tthis.ctx.ui.requestRender();\n\t\t} catch (error: unknown) {\n\t\t\tawait this.ctx.handleFatalRuntimeError(\"Failed to create session\", error);\n\t\t}\n\t}\n\n\thandleDebug(): void {\n\t\tconst width = this.ctx.ui.terminal.columns;\n\t\tconst height = this.ctx.ui.terminal.rows;\n\t\tconst allLines = this.ctx.ui.render(width);\n\n\t\tconst debugLogPath = getDebugLogPath();\n\t\tconst debugData = [\n\t\t\t`Debug output at ${new Date().toISOString()}`,\n\t\t\t`Terminal: ${width}x${height}`,\n\t\t\t`Total lines: ${allLines.length}`,\n\t\t\t\"\",\n\t\t\t\"=== All rendered lines with visible widths ===\",\n\t\t\t...allLines.map((line, idx) => {\n\t\t\t\tconst vw = visibleWidth(line);\n\t\t\t\tconst escaped = JSON.stringify(line);\n\t\t\t\treturn `[${idx}] (w=${vw}) ${escaped}`;\n\t\t\t}),\n\t\t\t\"\",\n\t\t\t\"=== Agent messages (JSONL) ===\",\n\t\t\t...this.ctx.session.messages.map((msg) => JSON.stringify(msg)),\n\t\t\t\"\",\n\t\t].join(\"\\n\");\n\n\t\tfs.mkdirSync(path.dirname(debugLogPath), { recursive: true });\n\t\tfs.writeFileSync(debugLogPath, debugData);\n\n\t\tthis.ctx.chatContainer.addChild(new Spacer(1));\n\t\tthis.ctx.chatContainer.addChild(\n\t\t\tnew Text(`${theme.fg(\"accent\", \"✓ Debug log written\")}\\n${theme.fg(\"muted\", debugLogPath)}`, 1, 1),\n\t\t);\n\t\tthis.ctx.ui.requestRender();\n\t}\n}\n"]}
|
|
@@ -95,7 +95,7 @@ export class CommandExecutor {
|
|
|
95
95
|
}
|
|
96
96
|
this.ctx.showStatus(`Spawning ${mode} subagent...`);
|
|
97
97
|
try {
|
|
98
|
-
const pool = getSubagentPool(this.ctx.sessionManager.getCwd());
|
|
98
|
+
const pool = getSubagentPool(this.ctx.sessionManager.getCwd(), this.ctx.session.modelRegistry.getAvailable());
|
|
99
99
|
const dispatchResult = await pool.dispatch(task, {
|
|
100
100
|
forceAgent: mode,
|
|
101
101
|
model: this.ctx.session.model?.id,
|