@kolisachint/hoocode-agent 0.4.13 → 0.4.15

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.
Files changed (68) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/dist/cli/args.d.ts +2 -0
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +8 -0
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/config.d.ts +8 -0
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +12 -0
  9. package/dist/config.js.map +1 -1
  10. package/dist/core/agent-frontmatter.d.ts +107 -0
  11. package/dist/core/agent-frontmatter.d.ts.map +1 -0
  12. package/dist/core/agent-frontmatter.js +189 -0
  13. package/dist/core/agent-frontmatter.js.map +1 -0
  14. package/dist/core/agent-registry.d.ts +52 -0
  15. package/dist/core/agent-registry.d.ts.map +1 -0
  16. package/dist/core/agent-registry.js +131 -0
  17. package/dist/core/agent-registry.js.map +1 -0
  18. package/dist/core/dispatch-evaluator.d.ts +17 -0
  19. package/dist/core/dispatch-evaluator.d.ts.map +1 -1
  20. package/dist/core/dispatch-evaluator.js +44 -10
  21. package/dist/core/dispatch-evaluator.js.map +1 -1
  22. package/dist/core/lifeguard.d.ts.map +1 -1
  23. package/dist/core/lifeguard.js +5 -5
  24. package/dist/core/lifeguard.js.map +1 -1
  25. package/dist/core/output-verifier.d.ts.map +1 -1
  26. package/dist/core/output-verifier.js +2 -2
  27. package/dist/core/output-verifier.js.map +1 -1
  28. package/dist/core/subagent-pool.d.ts +54 -3
  29. package/dist/core/subagent-pool.d.ts.map +1 -1
  30. package/dist/core/subagent-pool.js +152 -62
  31. package/dist/core/subagent-pool.js.map +1 -1
  32. package/dist/core/subagent-result.d.ts +11 -2
  33. package/dist/core/subagent-result.d.ts.map +1 -1
  34. package/dist/core/subagent-result.js +17 -4
  35. package/dist/core/subagent-result.js.map +1 -1
  36. package/dist/core/task-store.d.ts +12 -7
  37. package/dist/core/task-store.d.ts.map +1 -1
  38. package/dist/core/task-store.js +23 -15
  39. package/dist/core/task-store.js.map +1 -1
  40. package/dist/core/token-budget.d.ts.map +1 -1
  41. package/dist/core/token-budget.js +17 -14
  42. package/dist/core/token-budget.js.map +1 -1
  43. package/dist/core/tools/subagent.d.ts +32 -15
  44. package/dist/core/tools/subagent.d.ts.map +1 -1
  45. package/dist/core/tools/subagent.js +236 -112
  46. package/dist/core/tools/subagent.js.map +1 -1
  47. package/dist/main.d.ts.map +1 -1
  48. package/dist/main.js +13 -5
  49. package/dist/main.js.map +1 -1
  50. package/dist/modes/interactive/components/task-panel.d.ts +1 -1
  51. package/dist/modes/interactive/components/task-panel.d.ts.map +1 -1
  52. package/dist/modes/interactive/components/task-panel.js +31 -12
  53. package/dist/modes/interactive/components/task-panel.js.map +1 -1
  54. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  55. package/dist/modes/interactive/components/tool-execution.js +4 -2
  56. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  57. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  58. package/dist/modes/interactive/interactive-mode.js +12 -7
  59. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  60. package/dist/modes/print-mode.d.ts +2 -0
  61. package/dist/modes/print-mode.d.ts.map +1 -1
  62. package/dist/modes/print-mode.js +29 -2
  63. package/dist/modes/print-mode.js.map +1 -1
  64. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  65. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  66. package/examples/extensions/sandbox/package.json +1 -1
  67. package/examples/extensions/with-deps/package.json +1 -1
  68. package/package.json +4 -4
@@ -21,6 +21,8 @@ export interface PrintModeOptions {
21
21
  initialImages?: ImageContent[];
22
22
  /** Internal: set when this process is a spawned subagent. Enables heartbeats and result.json. */
23
23
  taskId?: string;
24
+ /** Hard cap on assistant turns. Near the cap the agent is asked to wrap up; at the cap it is aborted. */
25
+ maxTurns?: number;
24
26
  }
25
27
  /**
26
28
  * Run in print (single-shot) mode.
@@ -1 +1 @@
1
- {"version":3,"file":"print-mode.d.ts","sourceRoot":"","sources":["../../src/modes/print-mode.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAoB,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC9E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAQ5E;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,yEAAyE;IACzE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8CAA8C;IAC9C,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC;IAC/B,iGAAiG;IACjG,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,WAAW,EAAE,mBAAmB,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAwJ/G","sourcesContent":["/**\n * Print mode (single-shot): Send prompts, output result, exit.\n *\n * Used for:\n * - `hoocode -p \"prompt\"` - text output\n * - `hoocode --mode json \"prompt\"` - JSON event stream\n */\n\nimport type { AssistantMessage, ImageContent } from \"@kolisachint/hoocode-ai\";\nimport type { AgentSessionRuntime } from \"../core/agent-session-runtime.js\";\nimport { flushRawStdout, writeRawStdout } from \"../core/output-guard.js\";\nimport { buildSubagentResult, writeSubagentResult } from \"../core/subagent-result.js\";\nimport { killTrackedDetachedChildren } from \"../utils/shell.js\";\n\n/** Heartbeat cadence for spawned subagents (parent lifeguard stalls after 60s of silence). */\nconst SUBAGENT_HEARTBEAT_MS = 30000;\n\n/**\n * Options for print mode.\n */\nexport interface PrintModeOptions {\n\t/** Output mode: \"text\" for final response only, \"json\" for all events */\n\tmode: \"text\" | \"json\";\n\t/** Array of additional prompts to send after initialMessage */\n\tmessages?: string[];\n\t/** First message to send (may contain @file content) */\n\tinitialMessage?: string;\n\t/** Images to attach to the initial message */\n\tinitialImages?: ImageContent[];\n\t/** Internal: set when this process is a spawned subagent. Enables heartbeats and result.json. */\n\ttaskId?: string;\n}\n\n/**\n * Run in print (single-shot) mode.\n * Sends prompts to the agent and outputs the result.\n */\nexport async function runPrintMode(runtimeHost: AgentSessionRuntime, options: PrintModeOptions): Promise<number> {\n\tconst { mode, messages = [], initialMessage, initialImages, taskId } = options;\n\tlet exitCode = 0;\n\tlet session = runtimeHost.session;\n\tlet unsubscribe: (() => void) | undefined;\n\tlet disposed = false;\n\n\t// Spawned subagents (json mode + task id) emit a periodic heartbeat so the\n\t// parent lifeguard does not treat a long-thinking turn as a stall.\n\tconst isSubagent = mode === \"json\" && typeof taskId === \"string\" && taskId.length > 0;\n\tlet heartbeat: NodeJS.Timeout | undefined;\n\tif (isSubagent) {\n\t\theartbeat = setInterval(() => {\n\t\t\twriteRawStdout(`${JSON.stringify({ ping: true })}\\n`);\n\t\t}, SUBAGENT_HEARTBEAT_MS);\n\t\theartbeat.unref();\n\t}\n\tconst signalCleanupHandlers: Array<() => void> = [];\n\n\tconst disposeRuntime = async (): Promise<void> => {\n\t\tif (disposed) return;\n\t\tdisposed = true;\n\t\tunsubscribe?.();\n\t\tawait runtimeHost.dispose();\n\t};\n\n\tconst registerSignalHandlers = (): void => {\n\t\tconst signals: NodeJS.Signals[] = [\"SIGTERM\"];\n\t\tif (process.platform !== \"win32\") {\n\t\t\tsignals.push(\"SIGHUP\");\n\t\t}\n\n\t\tfor (const signal of signals) {\n\t\t\tconst handler = () => {\n\t\t\t\tkillTrackedDetachedChildren();\n\t\t\t\tvoid disposeRuntime().finally(() => {\n\t\t\t\t\tprocess.exit(signal === \"SIGHUP\" ? 129 : 143);\n\t\t\t\t});\n\t\t\t};\n\t\t\tprocess.on(signal, handler);\n\t\t\tsignalCleanupHandlers.push(() => process.off(signal, handler));\n\t\t}\n\t};\n\n\tregisterSignalHandlers();\n\n\truntimeHost.setRebindSession(async () => {\n\t\tawait rebindSession();\n\t});\n\n\tconst rebindSession = async (): Promise<void> => {\n\t\tsession = runtimeHost.session;\n\t\tawait session.bindExtensions({\n\t\t\tcommandContextActions: {\n\t\t\t\twaitForIdle: () => session.agent.waitForIdle(),\n\t\t\t\tnewSession: async (newSessionOptions) => runtimeHost.newSession(newSessionOptions),\n\t\t\t\tfork: async (entryId, forkOptions) => {\n\t\t\t\t\tconst result = await runtimeHost.fork(entryId, forkOptions);\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tnavigateTree: async (targetId, navigateOptions) => {\n\t\t\t\t\tconst result = await session.navigateTree(targetId, {\n\t\t\t\t\t\tsummarize: navigateOptions?.summarize,\n\t\t\t\t\t\tcustomInstructions: navigateOptions?.customInstructions,\n\t\t\t\t\t\treplaceInstructions: navigateOptions?.replaceInstructions,\n\t\t\t\t\t\tlabel: navigateOptions?.label,\n\t\t\t\t\t});\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tswitchSession: async (sessionPath, switchOptions) => {\n\t\t\t\t\treturn runtimeHost.switchSession(sessionPath, switchOptions);\n\t\t\t\t},\n\t\t\t\treload: async () => {\n\t\t\t\t\tawait session.reload();\n\t\t\t\t},\n\t\t\t},\n\t\t\tonError: (err) => {\n\t\t\t\tconsole.error(`Extension error (${err.extensionPath}): ${err.error}`);\n\t\t\t},\n\t\t});\n\n\t\tunsubscribe?.();\n\t\tunsubscribe = session.subscribe((event) => {\n\t\t\tif (mode === \"json\") {\n\t\t\t\twriteRawStdout(`${JSON.stringify(event)}\\n`);\n\t\t\t}\n\t\t});\n\t};\n\n\ttry {\n\t\tif (mode === \"json\") {\n\t\t\tconst header = session.sessionManager.getHeader();\n\t\t\tif (header) {\n\t\t\t\twriteRawStdout(`${JSON.stringify(header)}\\n`);\n\t\t\t}\n\t\t}\n\n\t\tawait rebindSession();\n\n\t\tif (initialMessage) {\n\t\t\tawait session.prompt(initialMessage, { images: initialImages });\n\t\t}\n\n\t\tfor (const message of messages) {\n\t\t\tawait session.prompt(message);\n\t\t}\n\n\t\t// Spawned subagents write the audit file the parent pool verifies.\n\t\tif (isSubagent && taskId) {\n\t\t\tconst stats = session.getSessionStats();\n\t\t\tconst result = buildSubagentResult(session.state.messages, {\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\twriteSubagentResult(session.sessionManager.getCwd(), taskId, result);\n\t\t\tif (result.status === \"failed\") exitCode = 1;\n\t\t}\n\n\t\tif (mode === \"text\") {\n\t\t\tconst state = session.state;\n\t\t\tconst lastMessage = state.messages[state.messages.length - 1];\n\n\t\t\tif (lastMessage?.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = lastMessage as AssistantMessage;\n\t\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\t\tconsole.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);\n\t\t\t\t\texitCode = 1;\n\t\t\t\t} else {\n\t\t\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\t\t\tif (content.type === \"text\") {\n\t\t\t\t\t\t\twriteRawStdout(`${content.text}\\n`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn exitCode;\n\t} catch (error: unknown) {\n\t\tconsole.error(error instanceof Error ? error.message : String(error));\n\t\treturn 1;\n\t} finally {\n\t\tif (heartbeat) clearInterval(heartbeat);\n\t\tfor (const cleanup of signalCleanupHandlers) {\n\t\t\tcleanup();\n\t\t}\n\t\tawait disposeRuntime();\n\t\tawait flushRawStdout();\n\t}\n}\n"]}
1
+ {"version":3,"file":"print-mode.d.ts","sourceRoot":"","sources":["../../src/modes/print-mode.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAoB,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC9E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAQ5E;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,yEAAyE;IACzE,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8CAA8C;IAC9C,aAAa,CAAC,EAAE,YAAY,EAAE,CAAC;IAC/B,iGAAiG;IACjG,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yGAAyG;IACzG,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,WAAW,EAAE,mBAAmB,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAyL/G","sourcesContent":["/**\n * Print mode (single-shot): Send prompts, output result, exit.\n *\n * Used for:\n * - `hoocode -p \"prompt\"` - text output\n * - `hoocode --mode json \"prompt\"` - JSON event stream\n */\n\nimport type { AssistantMessage, ImageContent } from \"@kolisachint/hoocode-ai\";\nimport type { AgentSessionRuntime } from \"../core/agent-session-runtime.js\";\nimport { flushRawStdout, writeRawStdout } from \"../core/output-guard.js\";\nimport { buildSubagentResult, writeSubagentResult } from \"../core/subagent-result.js\";\nimport { killTrackedDetachedChildren } from \"../utils/shell.js\";\n\n/** Heartbeat cadence for spawned subagents (parent lifeguard stalls after 60s of silence). */\nconst SUBAGENT_HEARTBEAT_MS = 30000;\n\n/**\n * Options for print mode.\n */\nexport interface PrintModeOptions {\n\t/** Output mode: \"text\" for final response only, \"json\" for all events */\n\tmode: \"text\" | \"json\";\n\t/** Array of additional prompts to send after initialMessage */\n\tmessages?: string[];\n\t/** First message to send (may contain @file content) */\n\tinitialMessage?: string;\n\t/** Images to attach to the initial message */\n\tinitialImages?: ImageContent[];\n\t/** Internal: set when this process is a spawned subagent. Enables heartbeats and result.json. */\n\ttaskId?: string;\n\t/** Hard cap on assistant turns. Near the cap the agent is asked to wrap up; at the cap it is aborted. */\n\tmaxTurns?: number;\n}\n\n/**\n * Run in print (single-shot) mode.\n * Sends prompts to the agent and outputs the result.\n */\nexport async function runPrintMode(runtimeHost: AgentSessionRuntime, options: PrintModeOptions): Promise<number> {\n\tconst { mode, messages = [], initialMessage, initialImages, taskId, maxTurns } = options;\n\tlet exitCode = 0;\n\tlet session = runtimeHost.session;\n\tlet unsubscribe: (() => void) | undefined;\n\tlet disposed = false;\n\n\t// Spawned subagents (json mode + task id) emit a periodic heartbeat so the\n\t// parent lifeguard does not treat a long-thinking turn as a stall.\n\tconst isSubagent = mode === \"json\" && typeof taskId === \"string\" && taskId.length > 0;\n\tlet heartbeat: NodeJS.Timeout | undefined;\n\tif (isSubagent) {\n\t\theartbeat = setInterval(() => {\n\t\t\twriteRawStdout(`${JSON.stringify({ ping: true })}\\n`);\n\t\t}, SUBAGENT_HEARTBEAT_MS);\n\t\theartbeat.unref();\n\t}\n\t// Turn-limit enforcement for spawned subagents: ask the agent to wrap up near\n\t// the cap, then hard-stop at the cap so a runaway agent always terminates.\n\tlet reachedMaxTurns = false;\n\tlet turnLimitUnsub: (() => void) | undefined;\n\tconst signalCleanupHandlers: Array<() => void> = [];\n\n\tconst disposeRuntime = async (): Promise<void> => {\n\t\tif (disposed) return;\n\t\tdisposed = true;\n\t\tunsubscribe?.();\n\t\tawait runtimeHost.dispose();\n\t};\n\n\tconst registerSignalHandlers = (): void => {\n\t\tconst signals: NodeJS.Signals[] = [\"SIGTERM\"];\n\t\tif (process.platform !== \"win32\") {\n\t\t\tsignals.push(\"SIGHUP\");\n\t\t}\n\n\t\tfor (const signal of signals) {\n\t\t\tconst handler = () => {\n\t\t\t\tkillTrackedDetachedChildren();\n\t\t\t\tvoid disposeRuntime().finally(() => {\n\t\t\t\t\tprocess.exit(signal === \"SIGHUP\" ? 129 : 143);\n\t\t\t\t});\n\t\t\t};\n\t\t\tprocess.on(signal, handler);\n\t\t\tsignalCleanupHandlers.push(() => process.off(signal, handler));\n\t\t}\n\t};\n\n\tregisterSignalHandlers();\n\n\truntimeHost.setRebindSession(async () => {\n\t\tawait rebindSession();\n\t});\n\n\tconst rebindSession = async (): Promise<void> => {\n\t\tsession = runtimeHost.session;\n\t\tawait session.bindExtensions({\n\t\t\tcommandContextActions: {\n\t\t\t\twaitForIdle: () => session.agent.waitForIdle(),\n\t\t\t\tnewSession: async (newSessionOptions) => runtimeHost.newSession(newSessionOptions),\n\t\t\t\tfork: async (entryId, forkOptions) => {\n\t\t\t\t\tconst result = await runtimeHost.fork(entryId, forkOptions);\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tnavigateTree: async (targetId, navigateOptions) => {\n\t\t\t\t\tconst result = await session.navigateTree(targetId, {\n\t\t\t\t\t\tsummarize: navigateOptions?.summarize,\n\t\t\t\t\t\tcustomInstructions: navigateOptions?.customInstructions,\n\t\t\t\t\t\treplaceInstructions: navigateOptions?.replaceInstructions,\n\t\t\t\t\t\tlabel: navigateOptions?.label,\n\t\t\t\t\t});\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tswitchSession: async (sessionPath, switchOptions) => {\n\t\t\t\t\treturn runtimeHost.switchSession(sessionPath, switchOptions);\n\t\t\t\t},\n\t\t\t\treload: async () => {\n\t\t\t\t\tawait session.reload();\n\t\t\t\t},\n\t\t\t},\n\t\t\tonError: (err) => {\n\t\t\t\tconsole.error(`Extension error (${err.extensionPath}): ${err.error}`);\n\t\t\t},\n\t\t});\n\n\t\tunsubscribe?.();\n\t\tunsubscribe = session.subscribe((event) => {\n\t\t\tif (mode === \"json\") {\n\t\t\t\twriteRawStdout(`${JSON.stringify(event)}\\n`);\n\t\t\t}\n\t\t});\n\t};\n\n\ttry {\n\t\tif (mode === \"json\") {\n\t\t\tconst header = session.sessionManager.getHeader();\n\t\t\tif (header) {\n\t\t\t\twriteRawStdout(`${JSON.stringify(header)}\\n`);\n\t\t\t}\n\t\t}\n\n\t\tawait rebindSession();\n\n\t\tif (isSubagent && typeof maxTurns === \"number\" && maxTurns > 0) {\n\t\t\tconst cap = maxTurns;\n\t\t\tconst wrapUpAt = Math.floor(cap * 0.9);\n\t\t\tlet turns = 0;\n\t\t\tlet warned = false;\n\t\t\tturnLimitUnsub = session.subscribe((event) => {\n\t\t\t\tif (event.type !== \"turn_end\") return;\n\t\t\t\tturns += 1;\n\t\t\t\tif (turns >= cap) {\n\t\t\t\t\tif (!reachedMaxTurns) {\n\t\t\t\t\t\treachedMaxTurns = true;\n\t\t\t\t\t\tvoid session.abort();\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!warned && wrapUpAt >= 1 && wrapUpAt < cap && turns >= wrapUpAt) {\n\t\t\t\t\twarned = true;\n\t\t\t\t\tvoid session.steer(\n\t\t\t\t\t\t`You are at turn ${turns} of your ${cap}-turn limit. Stop investigating or making changes now and write your final summary of findings and results in your next message.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tif (initialMessage) {\n\t\t\tawait session.prompt(initialMessage, { images: initialImages });\n\t\t}\n\n\t\tfor (const message of messages) {\n\t\t\tawait session.prompt(message);\n\t\t}\n\n\t\t// Spawned subagents write the audit file the parent pool verifies.\n\t\tif (isSubagent && taskId) {\n\t\t\tconst stats = session.getSessionStats();\n\t\t\tconst result = buildSubagentResult(\n\t\t\t\tsession.state.messages,\n\t\t\t\t{\n\t\t\t\t\tinput: stats.tokens.input,\n\t\t\t\t\toutput: stats.tokens.output,\n\t\t\t\t\tcacheRead: stats.tokens.cacheRead,\n\t\t\t\t\tcacheWrite: stats.tokens.cacheWrite,\n\t\t\t\t\tcost: stats.cost,\n\t\t\t\t},\n\t\t\t\t{ reachedMaxTurns },\n\t\t\t);\n\t\t\twriteSubagentResult(session.sessionManager.getCwd(), taskId, result);\n\t\t\tif (result.status === \"failed\") exitCode = 1;\n\t\t}\n\n\t\tif (mode === \"text\") {\n\t\t\tconst state = session.state;\n\t\t\tconst lastMessage = state.messages[state.messages.length - 1];\n\n\t\t\tif (lastMessage?.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = lastMessage as AssistantMessage;\n\t\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\t\tconsole.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);\n\t\t\t\t\texitCode = 1;\n\t\t\t\t} else {\n\t\t\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\t\t\tif (content.type === \"text\") {\n\t\t\t\t\t\t\twriteRawStdout(`${content.text}\\n`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn exitCode;\n\t} catch (error: unknown) {\n\t\tconsole.error(error instanceof Error ? error.message : String(error));\n\t\treturn 1;\n\t} finally {\n\t\tif (heartbeat) clearInterval(heartbeat);\n\t\tturnLimitUnsub?.();\n\t\tfor (const cleanup of signalCleanupHandlers) {\n\t\t\tcleanup();\n\t\t}\n\t\tawait disposeRuntime();\n\t\tawait flushRawStdout();\n\t}\n}\n"]}
@@ -15,7 +15,7 @@ const SUBAGENT_HEARTBEAT_MS = 30000;
15
15
  * Sends prompts to the agent and outputs the result.
16
16
  */
17
17
  export async function runPrintMode(runtimeHost, options) {
18
- const { mode, messages = [], initialMessage, initialImages, taskId } = options;
18
+ const { mode, messages = [], initialMessage, initialImages, taskId, maxTurns } = options;
19
19
  let exitCode = 0;
20
20
  let session = runtimeHost.session;
21
21
  let unsubscribe;
@@ -30,6 +30,10 @@ export async function runPrintMode(runtimeHost, options) {
30
30
  }, SUBAGENT_HEARTBEAT_MS);
31
31
  heartbeat.unref();
32
32
  }
33
+ // Turn-limit enforcement for spawned subagents: ask the agent to wrap up near
34
+ // the cap, then hard-stop at the cap so a runaway agent always terminates.
35
+ let reachedMaxTurns = false;
36
+ let turnLimitUnsub;
33
37
  const signalCleanupHandlers = [];
34
38
  const disposeRuntime = async () => {
35
39
  if (disposed)
@@ -103,6 +107,28 @@ export async function runPrintMode(runtimeHost, options) {
103
107
  }
104
108
  }
105
109
  await rebindSession();
110
+ if (isSubagent && typeof maxTurns === "number" && maxTurns > 0) {
111
+ const cap = maxTurns;
112
+ const wrapUpAt = Math.floor(cap * 0.9);
113
+ let turns = 0;
114
+ let warned = false;
115
+ turnLimitUnsub = session.subscribe((event) => {
116
+ if (event.type !== "turn_end")
117
+ return;
118
+ turns += 1;
119
+ if (turns >= cap) {
120
+ if (!reachedMaxTurns) {
121
+ reachedMaxTurns = true;
122
+ void session.abort();
123
+ }
124
+ return;
125
+ }
126
+ if (!warned && wrapUpAt >= 1 && wrapUpAt < cap && turns >= wrapUpAt) {
127
+ warned = true;
128
+ void session.steer(`You are at turn ${turns} of your ${cap}-turn limit. Stop investigating or making changes now and write your final summary of findings and results in your next message.`);
129
+ }
130
+ });
131
+ }
106
132
  if (initialMessage) {
107
133
  await session.prompt(initialMessage, { images: initialImages });
108
134
  }
@@ -118,7 +144,7 @@ export async function runPrintMode(runtimeHost, options) {
118
144
  cacheRead: stats.tokens.cacheRead,
119
145
  cacheWrite: stats.tokens.cacheWrite,
120
146
  cost: stats.cost,
121
- });
147
+ }, { reachedMaxTurns });
122
148
  writeSubagentResult(session.sessionManager.getCwd(), taskId, result);
123
149
  if (result.status === "failed")
124
150
  exitCode = 1;
@@ -150,6 +176,7 @@ export async function runPrintMode(runtimeHost, options) {
150
176
  finally {
151
177
  if (heartbeat)
152
178
  clearInterval(heartbeat);
179
+ turnLimitUnsub?.();
153
180
  for (const cleanup of signalCleanupHandlers) {
154
181
  cleanup();
155
182
  }
@@ -1 +1 @@
1
- {"version":3,"file":"print-mode.js","sourceRoot":"","sources":["../../src/modes/print-mode.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Print mode (single-shot): Send prompts, output result, exit.\n *\n * Used for:\n * - `hoocode -p \"prompt\"` - text output\n * - `hoocode --mode json \"prompt\"` - JSON event stream\n */\n\nimport type { AssistantMessage, ImageContent } from \"@kolisachint/hoocode-ai\";\nimport type { AgentSessionRuntime } from \"../core/agent-session-runtime.js\";\nimport { flushRawStdout, writeRawStdout } from \"../core/output-guard.js\";\nimport { buildSubagentResult, writeSubagentResult } from \"../core/subagent-result.js\";\nimport { killTrackedDetachedChildren } from \"../utils/shell.js\";\n\n/** Heartbeat cadence for spawned subagents (parent lifeguard stalls after 60s of silence). */\nconst SUBAGENT_HEARTBEAT_MS = 30000;\n\n/**\n * Options for print mode.\n */\nexport interface PrintModeOptions {\n\t/** Output mode: \"text\" for final response only, \"json\" for all events */\n\tmode: \"text\" | \"json\";\n\t/** Array of additional prompts to send after initialMessage */\n\tmessages?: string[];\n\t/** First message to send (may contain @file content) */\n\tinitialMessage?: string;\n\t/** Images to attach to the initial message */\n\tinitialImages?: ImageContent[];\n\t/** Internal: set when this process is a spawned subagent. Enables heartbeats and result.json. */\n\ttaskId?: string;\n}\n\n/**\n * Run in print (single-shot) mode.\n * Sends prompts to the agent and outputs the result.\n */\nexport async function runPrintMode(runtimeHost: AgentSessionRuntime, options: PrintModeOptions): Promise<number> {\n\tconst { mode, messages = [], initialMessage, initialImages, taskId } = options;\n\tlet exitCode = 0;\n\tlet session = runtimeHost.session;\n\tlet unsubscribe: (() => void) | undefined;\n\tlet disposed = false;\n\n\t// Spawned subagents (json mode + task id) emit a periodic heartbeat so the\n\t// parent lifeguard does not treat a long-thinking turn as a stall.\n\tconst isSubagent = mode === \"json\" && typeof taskId === \"string\" && taskId.length > 0;\n\tlet heartbeat: NodeJS.Timeout | undefined;\n\tif (isSubagent) {\n\t\theartbeat = setInterval(() => {\n\t\t\twriteRawStdout(`${JSON.stringify({ ping: true })}\\n`);\n\t\t}, SUBAGENT_HEARTBEAT_MS);\n\t\theartbeat.unref();\n\t}\n\tconst signalCleanupHandlers: Array<() => void> = [];\n\n\tconst disposeRuntime = async (): Promise<void> => {\n\t\tif (disposed) return;\n\t\tdisposed = true;\n\t\tunsubscribe?.();\n\t\tawait runtimeHost.dispose();\n\t};\n\n\tconst registerSignalHandlers = (): void => {\n\t\tconst signals: NodeJS.Signals[] = [\"SIGTERM\"];\n\t\tif (process.platform !== \"win32\") {\n\t\t\tsignals.push(\"SIGHUP\");\n\t\t}\n\n\t\tfor (const signal of signals) {\n\t\t\tconst handler = () => {\n\t\t\t\tkillTrackedDetachedChildren();\n\t\t\t\tvoid disposeRuntime().finally(() => {\n\t\t\t\t\tprocess.exit(signal === \"SIGHUP\" ? 129 : 143);\n\t\t\t\t});\n\t\t\t};\n\t\t\tprocess.on(signal, handler);\n\t\t\tsignalCleanupHandlers.push(() => process.off(signal, handler));\n\t\t}\n\t};\n\n\tregisterSignalHandlers();\n\n\truntimeHost.setRebindSession(async () => {\n\t\tawait rebindSession();\n\t});\n\n\tconst rebindSession = async (): Promise<void> => {\n\t\tsession = runtimeHost.session;\n\t\tawait session.bindExtensions({\n\t\t\tcommandContextActions: {\n\t\t\t\twaitForIdle: () => session.agent.waitForIdle(),\n\t\t\t\tnewSession: async (newSessionOptions) => runtimeHost.newSession(newSessionOptions),\n\t\t\t\tfork: async (entryId, forkOptions) => {\n\t\t\t\t\tconst result = await runtimeHost.fork(entryId, forkOptions);\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tnavigateTree: async (targetId, navigateOptions) => {\n\t\t\t\t\tconst result = await session.navigateTree(targetId, {\n\t\t\t\t\t\tsummarize: navigateOptions?.summarize,\n\t\t\t\t\t\tcustomInstructions: navigateOptions?.customInstructions,\n\t\t\t\t\t\treplaceInstructions: navigateOptions?.replaceInstructions,\n\t\t\t\t\t\tlabel: navigateOptions?.label,\n\t\t\t\t\t});\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tswitchSession: async (sessionPath, switchOptions) => {\n\t\t\t\t\treturn runtimeHost.switchSession(sessionPath, switchOptions);\n\t\t\t\t},\n\t\t\t\treload: async () => {\n\t\t\t\t\tawait session.reload();\n\t\t\t\t},\n\t\t\t},\n\t\t\tonError: (err) => {\n\t\t\t\tconsole.error(`Extension error (${err.extensionPath}): ${err.error}`);\n\t\t\t},\n\t\t});\n\n\t\tunsubscribe?.();\n\t\tunsubscribe = session.subscribe((event) => {\n\t\t\tif (mode === \"json\") {\n\t\t\t\twriteRawStdout(`${JSON.stringify(event)}\\n`);\n\t\t\t}\n\t\t});\n\t};\n\n\ttry {\n\t\tif (mode === \"json\") {\n\t\t\tconst header = session.sessionManager.getHeader();\n\t\t\tif (header) {\n\t\t\t\twriteRawStdout(`${JSON.stringify(header)}\\n`);\n\t\t\t}\n\t\t}\n\n\t\tawait rebindSession();\n\n\t\tif (initialMessage) {\n\t\t\tawait session.prompt(initialMessage, { images: initialImages });\n\t\t}\n\n\t\tfor (const message of messages) {\n\t\t\tawait session.prompt(message);\n\t\t}\n\n\t\t// Spawned subagents write the audit file the parent pool verifies.\n\t\tif (isSubagent && taskId) {\n\t\t\tconst stats = session.getSessionStats();\n\t\t\tconst result = buildSubagentResult(session.state.messages, {\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\twriteSubagentResult(session.sessionManager.getCwd(), taskId, result);\n\t\t\tif (result.status === \"failed\") exitCode = 1;\n\t\t}\n\n\t\tif (mode === \"text\") {\n\t\t\tconst state = session.state;\n\t\t\tconst lastMessage = state.messages[state.messages.length - 1];\n\n\t\t\tif (lastMessage?.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = lastMessage as AssistantMessage;\n\t\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\t\tconsole.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);\n\t\t\t\t\texitCode = 1;\n\t\t\t\t} else {\n\t\t\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\t\t\tif (content.type === \"text\") {\n\t\t\t\t\t\t\twriteRawStdout(`${content.text}\\n`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn exitCode;\n\t} catch (error: unknown) {\n\t\tconsole.error(error instanceof Error ? error.message : String(error));\n\t\treturn 1;\n\t} finally {\n\t\tif (heartbeat) clearInterval(heartbeat);\n\t\tfor (const cleanup of signalCleanupHandlers) {\n\t\t\tcleanup();\n\t\t}\n\t\tawait disposeRuntime();\n\t\tawait flushRawStdout();\n\t}\n}\n"]}
1
+ {"version":3,"file":"print-mode.js","sourceRoot":"","sources":["../../src/modes/print-mode.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Print mode (single-shot): Send prompts, output result, exit.\n *\n * Used for:\n * - `hoocode -p \"prompt\"` - text output\n * - `hoocode --mode json \"prompt\"` - JSON event stream\n */\n\nimport type { AssistantMessage, ImageContent } from \"@kolisachint/hoocode-ai\";\nimport type { AgentSessionRuntime } from \"../core/agent-session-runtime.js\";\nimport { flushRawStdout, writeRawStdout } from \"../core/output-guard.js\";\nimport { buildSubagentResult, writeSubagentResult } from \"../core/subagent-result.js\";\nimport { killTrackedDetachedChildren } from \"../utils/shell.js\";\n\n/** Heartbeat cadence for spawned subagents (parent lifeguard stalls after 60s of silence). */\nconst SUBAGENT_HEARTBEAT_MS = 30000;\n\n/**\n * Options for print mode.\n */\nexport interface PrintModeOptions {\n\t/** Output mode: \"text\" for final response only, \"json\" for all events */\n\tmode: \"text\" | \"json\";\n\t/** Array of additional prompts to send after initialMessage */\n\tmessages?: string[];\n\t/** First message to send (may contain @file content) */\n\tinitialMessage?: string;\n\t/** Images to attach to the initial message */\n\tinitialImages?: ImageContent[];\n\t/** Internal: set when this process is a spawned subagent. Enables heartbeats and result.json. */\n\ttaskId?: string;\n\t/** Hard cap on assistant turns. Near the cap the agent is asked to wrap up; at the cap it is aborted. */\n\tmaxTurns?: number;\n}\n\n/**\n * Run in print (single-shot) mode.\n * Sends prompts to the agent and outputs the result.\n */\nexport async function runPrintMode(runtimeHost: AgentSessionRuntime, options: PrintModeOptions): Promise<number> {\n\tconst { mode, messages = [], initialMessage, initialImages, taskId, maxTurns } = options;\n\tlet exitCode = 0;\n\tlet session = runtimeHost.session;\n\tlet unsubscribe: (() => void) | undefined;\n\tlet disposed = false;\n\n\t// Spawned subagents (json mode + task id) emit a periodic heartbeat so the\n\t// parent lifeguard does not treat a long-thinking turn as a stall.\n\tconst isSubagent = mode === \"json\" && typeof taskId === \"string\" && taskId.length > 0;\n\tlet heartbeat: NodeJS.Timeout | undefined;\n\tif (isSubagent) {\n\t\theartbeat = setInterval(() => {\n\t\t\twriteRawStdout(`${JSON.stringify({ ping: true })}\\n`);\n\t\t}, SUBAGENT_HEARTBEAT_MS);\n\t\theartbeat.unref();\n\t}\n\t// Turn-limit enforcement for spawned subagents: ask the agent to wrap up near\n\t// the cap, then hard-stop at the cap so a runaway agent always terminates.\n\tlet reachedMaxTurns = false;\n\tlet turnLimitUnsub: (() => void) | undefined;\n\tconst signalCleanupHandlers: Array<() => void> = [];\n\n\tconst disposeRuntime = async (): Promise<void> => {\n\t\tif (disposed) return;\n\t\tdisposed = true;\n\t\tunsubscribe?.();\n\t\tawait runtimeHost.dispose();\n\t};\n\n\tconst registerSignalHandlers = (): void => {\n\t\tconst signals: NodeJS.Signals[] = [\"SIGTERM\"];\n\t\tif (process.platform !== \"win32\") {\n\t\t\tsignals.push(\"SIGHUP\");\n\t\t}\n\n\t\tfor (const signal of signals) {\n\t\t\tconst handler = () => {\n\t\t\t\tkillTrackedDetachedChildren();\n\t\t\t\tvoid disposeRuntime().finally(() => {\n\t\t\t\t\tprocess.exit(signal === \"SIGHUP\" ? 129 : 143);\n\t\t\t\t});\n\t\t\t};\n\t\t\tprocess.on(signal, handler);\n\t\t\tsignalCleanupHandlers.push(() => process.off(signal, handler));\n\t\t}\n\t};\n\n\tregisterSignalHandlers();\n\n\truntimeHost.setRebindSession(async () => {\n\t\tawait rebindSession();\n\t});\n\n\tconst rebindSession = async (): Promise<void> => {\n\t\tsession = runtimeHost.session;\n\t\tawait session.bindExtensions({\n\t\t\tcommandContextActions: {\n\t\t\t\twaitForIdle: () => session.agent.waitForIdle(),\n\t\t\t\tnewSession: async (newSessionOptions) => runtimeHost.newSession(newSessionOptions),\n\t\t\t\tfork: async (entryId, forkOptions) => {\n\t\t\t\t\tconst result = await runtimeHost.fork(entryId, forkOptions);\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tnavigateTree: async (targetId, navigateOptions) => {\n\t\t\t\t\tconst result = await session.navigateTree(targetId, {\n\t\t\t\t\t\tsummarize: navigateOptions?.summarize,\n\t\t\t\t\t\tcustomInstructions: navigateOptions?.customInstructions,\n\t\t\t\t\t\treplaceInstructions: navigateOptions?.replaceInstructions,\n\t\t\t\t\t\tlabel: navigateOptions?.label,\n\t\t\t\t\t});\n\t\t\t\t\treturn { cancelled: result.cancelled };\n\t\t\t\t},\n\t\t\t\tswitchSession: async (sessionPath, switchOptions) => {\n\t\t\t\t\treturn runtimeHost.switchSession(sessionPath, switchOptions);\n\t\t\t\t},\n\t\t\t\treload: async () => {\n\t\t\t\t\tawait session.reload();\n\t\t\t\t},\n\t\t\t},\n\t\t\tonError: (err) => {\n\t\t\t\tconsole.error(`Extension error (${err.extensionPath}): ${err.error}`);\n\t\t\t},\n\t\t});\n\n\t\tunsubscribe?.();\n\t\tunsubscribe = session.subscribe((event) => {\n\t\t\tif (mode === \"json\") {\n\t\t\t\twriteRawStdout(`${JSON.stringify(event)}\\n`);\n\t\t\t}\n\t\t});\n\t};\n\n\ttry {\n\t\tif (mode === \"json\") {\n\t\t\tconst header = session.sessionManager.getHeader();\n\t\t\tif (header) {\n\t\t\t\twriteRawStdout(`${JSON.stringify(header)}\\n`);\n\t\t\t}\n\t\t}\n\n\t\tawait rebindSession();\n\n\t\tif (isSubagent && typeof maxTurns === \"number\" && maxTurns > 0) {\n\t\t\tconst cap = maxTurns;\n\t\t\tconst wrapUpAt = Math.floor(cap * 0.9);\n\t\t\tlet turns = 0;\n\t\t\tlet warned = false;\n\t\t\tturnLimitUnsub = session.subscribe((event) => {\n\t\t\t\tif (event.type !== \"turn_end\") return;\n\t\t\t\tturns += 1;\n\t\t\t\tif (turns >= cap) {\n\t\t\t\t\tif (!reachedMaxTurns) {\n\t\t\t\t\t\treachedMaxTurns = true;\n\t\t\t\t\t\tvoid session.abort();\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!warned && wrapUpAt >= 1 && wrapUpAt < cap && turns >= wrapUpAt) {\n\t\t\t\t\twarned = true;\n\t\t\t\t\tvoid session.steer(\n\t\t\t\t\t\t`You are at turn ${turns} of your ${cap}-turn limit. Stop investigating or making changes now and write your final summary of findings and results in your next message.`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tif (initialMessage) {\n\t\t\tawait session.prompt(initialMessage, { images: initialImages });\n\t\t}\n\n\t\tfor (const message of messages) {\n\t\t\tawait session.prompt(message);\n\t\t}\n\n\t\t// Spawned subagents write the audit file the parent pool verifies.\n\t\tif (isSubagent && taskId) {\n\t\t\tconst stats = session.getSessionStats();\n\t\t\tconst result = buildSubagentResult(\n\t\t\t\tsession.state.messages,\n\t\t\t\t{\n\t\t\t\t\tinput: stats.tokens.input,\n\t\t\t\t\toutput: stats.tokens.output,\n\t\t\t\t\tcacheRead: stats.tokens.cacheRead,\n\t\t\t\t\tcacheWrite: stats.tokens.cacheWrite,\n\t\t\t\t\tcost: stats.cost,\n\t\t\t\t},\n\t\t\t\t{ reachedMaxTurns },\n\t\t\t);\n\t\t\twriteSubagentResult(session.sessionManager.getCwd(), taskId, result);\n\t\t\tif (result.status === \"failed\") exitCode = 1;\n\t\t}\n\n\t\tif (mode === \"text\") {\n\t\t\tconst state = session.state;\n\t\t\tconst lastMessage = state.messages[state.messages.length - 1];\n\n\t\t\tif (lastMessage?.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = lastMessage as AssistantMessage;\n\t\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\t\tconsole.error(assistantMsg.errorMessage || `Request ${assistantMsg.stopReason}`);\n\t\t\t\t\texitCode = 1;\n\t\t\t\t} else {\n\t\t\t\t\tfor (const content of assistantMsg.content) {\n\t\t\t\t\t\tif (content.type === \"text\") {\n\t\t\t\t\t\t\twriteRawStdout(`${content.text}\\n`);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn exitCode;\n\t} catch (error: unknown) {\n\t\tconsole.error(error instanceof Error ? error.message : String(error));\n\t\treturn 1;\n\t} finally {\n\t\tif (heartbeat) clearInterval(heartbeat);\n\t\tturnLimitUnsub?.();\n\t\tfor (const cleanup of signalCleanupHandlers) {\n\t\t\tcleanup();\n\t\t}\n\t\tawait disposeRuntime();\n\t\tawait flushRawStdout();\n\t}\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-custom-provider-anthropic",
3
3
  "private": true,
4
- "version": "0.2.12",
4
+ "version": "0.2.14",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-custom-provider-gitlab-duo",
3
3
  "private": true,
4
- "version": "0.2.12",
4
+ "version": "0.2.14",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-sandbox",
3
3
  "private": true,
4
- "version": "0.2.12",
4
+ "version": "0.2.14",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-extension-with-deps",
3
3
  "private": true,
4
- "version": "0.2.12",
4
+ "version": "0.2.14",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "bun": ">=1.0.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolisachint/hoocode-agent",
3
- "version": "0.4.13",
3
+ "version": "0.4.15",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "hoocodeConfig": {
@@ -44,9 +44,9 @@
44
44
  "prepublishOnly": "npm run clean && npm run build"
45
45
  },
46
46
  "dependencies": {
47
- "@kolisachint/hoocode-agent-core": "^0.4.13",
48
- "@kolisachint/hoocode-ai": "^0.4.13",
49
- "@kolisachint/hoocode-tui": "^0.4.13",
47
+ "@kolisachint/hoocode-agent-core": "^0.4.15",
48
+ "@kolisachint/hoocode-ai": "^0.4.15",
49
+ "@kolisachint/hoocode-tui": "^0.4.15",
50
50
  "@silvia-odwyer/photon-node": "^0.3.4",
51
51
  "chalk": "^5.5.0",
52
52
  "cli-highlight": "^2.1.11",