@oh-my-pi/pi-coding-agent 0.1.0

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 (337) hide show
  1. package/CHANGELOG.md +1629 -0
  2. package/README.md +1041 -0
  3. package/docs/compaction.md +403 -0
  4. package/docs/config-usage.md +113 -0
  5. package/docs/custom-tools.md +541 -0
  6. package/docs/extension-loading.md +1004 -0
  7. package/docs/hooks.md +867 -0
  8. package/docs/rpc.md +1040 -0
  9. package/docs/sdk.md +994 -0
  10. package/docs/session-tree-plan.md +441 -0
  11. package/docs/session.md +240 -0
  12. package/docs/skills.md +290 -0
  13. package/docs/theme.md +670 -0
  14. package/docs/tree.md +197 -0
  15. package/docs/tui.md +341 -0
  16. package/examples/README.md +21 -0
  17. package/examples/custom-tools/README.md +124 -0
  18. package/examples/custom-tools/hello/index.ts +20 -0
  19. package/examples/custom-tools/question/index.ts +84 -0
  20. package/examples/custom-tools/subagent/README.md +172 -0
  21. package/examples/custom-tools/subagent/agents/planner.md +37 -0
  22. package/examples/custom-tools/subagent/agents/scout.md +50 -0
  23. package/examples/custom-tools/subagent/agents/worker.md +24 -0
  24. package/examples/custom-tools/subagent/agents.ts +156 -0
  25. package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
  26. package/examples/custom-tools/subagent/commands/implement.md +10 -0
  27. package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
  28. package/examples/custom-tools/subagent/index.ts +1002 -0
  29. package/examples/custom-tools/todo/index.ts +212 -0
  30. package/examples/hooks/README.md +56 -0
  31. package/examples/hooks/auto-commit-on-exit.ts +49 -0
  32. package/examples/hooks/confirm-destructive.ts +59 -0
  33. package/examples/hooks/custom-compaction.ts +116 -0
  34. package/examples/hooks/dirty-repo-guard.ts +52 -0
  35. package/examples/hooks/file-trigger.ts +41 -0
  36. package/examples/hooks/git-checkpoint.ts +53 -0
  37. package/examples/hooks/handoff.ts +150 -0
  38. package/examples/hooks/permission-gate.ts +34 -0
  39. package/examples/hooks/protected-paths.ts +30 -0
  40. package/examples/hooks/qna.ts +119 -0
  41. package/examples/hooks/snake.ts +343 -0
  42. package/examples/hooks/status-line.ts +40 -0
  43. package/examples/sdk/01-minimal.ts +22 -0
  44. package/examples/sdk/02-custom-model.ts +49 -0
  45. package/examples/sdk/03-custom-prompt.ts +44 -0
  46. package/examples/sdk/04-skills.ts +44 -0
  47. package/examples/sdk/05-tools.ts +90 -0
  48. package/examples/sdk/06-hooks.ts +61 -0
  49. package/examples/sdk/07-context-files.ts +36 -0
  50. package/examples/sdk/08-slash-commands.ts +42 -0
  51. package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
  52. package/examples/sdk/10-settings.ts +38 -0
  53. package/examples/sdk/11-sessions.ts +48 -0
  54. package/examples/sdk/12-full-control.ts +95 -0
  55. package/examples/sdk/README.md +154 -0
  56. package/package.json +89 -0
  57. package/src/bun-imports.d.ts +16 -0
  58. package/src/capability/context-file.ts +40 -0
  59. package/src/capability/extension.ts +48 -0
  60. package/src/capability/hook.ts +40 -0
  61. package/src/capability/index.ts +616 -0
  62. package/src/capability/instruction.ts +37 -0
  63. package/src/capability/mcp.ts +52 -0
  64. package/src/capability/prompt.ts +35 -0
  65. package/src/capability/rule.ts +56 -0
  66. package/src/capability/settings.ts +35 -0
  67. package/src/capability/skill.ts +49 -0
  68. package/src/capability/slash-command.ts +40 -0
  69. package/src/capability/system-prompt.ts +35 -0
  70. package/src/capability/tool.ts +38 -0
  71. package/src/capability/types.ts +166 -0
  72. package/src/cli/args.ts +259 -0
  73. package/src/cli/file-processor.ts +121 -0
  74. package/src/cli/list-models.ts +104 -0
  75. package/src/cli/plugin-cli.ts +661 -0
  76. package/src/cli/session-picker.ts +41 -0
  77. package/src/cli/update-cli.ts +274 -0
  78. package/src/cli.ts +10 -0
  79. package/src/config.ts +391 -0
  80. package/src/core/agent-session.ts +2178 -0
  81. package/src/core/auth-storage.ts +258 -0
  82. package/src/core/bash-executor.ts +197 -0
  83. package/src/core/compaction/branch-summarization.ts +315 -0
  84. package/src/core/compaction/compaction.ts +664 -0
  85. package/src/core/compaction/index.ts +7 -0
  86. package/src/core/compaction/utils.ts +153 -0
  87. package/src/core/custom-commands/bundled/review/index.ts +156 -0
  88. package/src/core/custom-commands/index.ts +15 -0
  89. package/src/core/custom-commands/loader.ts +226 -0
  90. package/src/core/custom-commands/types.ts +112 -0
  91. package/src/core/custom-tools/index.ts +22 -0
  92. package/src/core/custom-tools/loader.ts +248 -0
  93. package/src/core/custom-tools/types.ts +185 -0
  94. package/src/core/custom-tools/wrapper.ts +29 -0
  95. package/src/core/exec.ts +139 -0
  96. package/src/core/export-html/index.ts +159 -0
  97. package/src/core/export-html/template.css +774 -0
  98. package/src/core/export-html/template.generated.ts +2 -0
  99. package/src/core/export-html/template.html +45 -0
  100. package/src/core/export-html/template.js +1185 -0
  101. package/src/core/export-html/template.macro.ts +24 -0
  102. package/src/core/file-mentions.ts +54 -0
  103. package/src/core/hooks/index.ts +16 -0
  104. package/src/core/hooks/loader.ts +288 -0
  105. package/src/core/hooks/runner.ts +434 -0
  106. package/src/core/hooks/tool-wrapper.ts +98 -0
  107. package/src/core/hooks/types.ts +770 -0
  108. package/src/core/index.ts +53 -0
  109. package/src/core/logger.ts +112 -0
  110. package/src/core/mcp/client.ts +185 -0
  111. package/src/core/mcp/config.ts +248 -0
  112. package/src/core/mcp/index.ts +45 -0
  113. package/src/core/mcp/loader.ts +99 -0
  114. package/src/core/mcp/manager.ts +235 -0
  115. package/src/core/mcp/tool-bridge.ts +156 -0
  116. package/src/core/mcp/transports/http.ts +316 -0
  117. package/src/core/mcp/transports/index.ts +6 -0
  118. package/src/core/mcp/transports/stdio.ts +252 -0
  119. package/src/core/mcp/types.ts +228 -0
  120. package/src/core/messages.ts +211 -0
  121. package/src/core/model-registry.ts +334 -0
  122. package/src/core/model-resolver.ts +494 -0
  123. package/src/core/plugins/doctor.ts +67 -0
  124. package/src/core/plugins/index.ts +38 -0
  125. package/src/core/plugins/installer.ts +189 -0
  126. package/src/core/plugins/loader.ts +339 -0
  127. package/src/core/plugins/manager.ts +672 -0
  128. package/src/core/plugins/parser.ts +105 -0
  129. package/src/core/plugins/paths.ts +37 -0
  130. package/src/core/plugins/types.ts +190 -0
  131. package/src/core/sdk.ts +900 -0
  132. package/src/core/session-manager.ts +1837 -0
  133. package/src/core/settings-manager.ts +860 -0
  134. package/src/core/skills.ts +352 -0
  135. package/src/core/slash-commands.ts +132 -0
  136. package/src/core/system-prompt.ts +442 -0
  137. package/src/core/timings.ts +25 -0
  138. package/src/core/title-generator.ts +110 -0
  139. package/src/core/tools/ask.ts +193 -0
  140. package/src/core/tools/bash-interceptor.ts +120 -0
  141. package/src/core/tools/bash.ts +91 -0
  142. package/src/core/tools/context.ts +32 -0
  143. package/src/core/tools/edit-diff.ts +487 -0
  144. package/src/core/tools/edit.ts +140 -0
  145. package/src/core/tools/exa/company.ts +59 -0
  146. package/src/core/tools/exa/index.ts +63 -0
  147. package/src/core/tools/exa/linkedin.ts +59 -0
  148. package/src/core/tools/exa/mcp-client.ts +368 -0
  149. package/src/core/tools/exa/render.ts +200 -0
  150. package/src/core/tools/exa/researcher.ts +90 -0
  151. package/src/core/tools/exa/search.ts +338 -0
  152. package/src/core/tools/exa/types.ts +167 -0
  153. package/src/core/tools/exa/websets.ts +248 -0
  154. package/src/core/tools/find.ts +244 -0
  155. package/src/core/tools/grep.ts +584 -0
  156. package/src/core/tools/index.ts +283 -0
  157. package/src/core/tools/ls.ts +142 -0
  158. package/src/core/tools/lsp/client.ts +767 -0
  159. package/src/core/tools/lsp/clients/biome-client.ts +207 -0
  160. package/src/core/tools/lsp/clients/index.ts +49 -0
  161. package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
  162. package/src/core/tools/lsp/config.ts +845 -0
  163. package/src/core/tools/lsp/edits.ts +110 -0
  164. package/src/core/tools/lsp/index.ts +1364 -0
  165. package/src/core/tools/lsp/render.ts +560 -0
  166. package/src/core/tools/lsp/rust-analyzer.ts +145 -0
  167. package/src/core/tools/lsp/types.ts +495 -0
  168. package/src/core/tools/lsp/utils.ts +526 -0
  169. package/src/core/tools/notebook.ts +182 -0
  170. package/src/core/tools/output.ts +198 -0
  171. package/src/core/tools/path-utils.ts +61 -0
  172. package/src/core/tools/read.ts +507 -0
  173. package/src/core/tools/renderers.ts +820 -0
  174. package/src/core/tools/review.ts +275 -0
  175. package/src/core/tools/rulebook.ts +124 -0
  176. package/src/core/tools/task/agents.ts +158 -0
  177. package/src/core/tools/task/artifacts.ts +114 -0
  178. package/src/core/tools/task/commands.ts +157 -0
  179. package/src/core/tools/task/discovery.ts +217 -0
  180. package/src/core/tools/task/executor.ts +531 -0
  181. package/src/core/tools/task/index.ts +548 -0
  182. package/src/core/tools/task/model-resolver.ts +176 -0
  183. package/src/core/tools/task/parallel.ts +38 -0
  184. package/src/core/tools/task/render.ts +502 -0
  185. package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
  186. package/src/core/tools/task/types.ts +142 -0
  187. package/src/core/tools/truncate.ts +265 -0
  188. package/src/core/tools/web-fetch.ts +2511 -0
  189. package/src/core/tools/web-search/auth.ts +199 -0
  190. package/src/core/tools/web-search/index.ts +583 -0
  191. package/src/core/tools/web-search/providers/anthropic.ts +198 -0
  192. package/src/core/tools/web-search/providers/exa.ts +196 -0
  193. package/src/core/tools/web-search/providers/perplexity.ts +195 -0
  194. package/src/core/tools/web-search/render.ts +372 -0
  195. package/src/core/tools/web-search/types.ts +180 -0
  196. package/src/core/tools/write.ts +63 -0
  197. package/src/core/ttsr.ts +211 -0
  198. package/src/core/utils.ts +187 -0
  199. package/src/discovery/agents-md.ts +75 -0
  200. package/src/discovery/builtin.ts +647 -0
  201. package/src/discovery/claude.ts +623 -0
  202. package/src/discovery/cline.ts +104 -0
  203. package/src/discovery/codex.ts +571 -0
  204. package/src/discovery/cursor.ts +266 -0
  205. package/src/discovery/gemini.ts +368 -0
  206. package/src/discovery/github.ts +120 -0
  207. package/src/discovery/helpers.test.ts +127 -0
  208. package/src/discovery/helpers.ts +249 -0
  209. package/src/discovery/index.ts +84 -0
  210. package/src/discovery/mcp-json.ts +127 -0
  211. package/src/discovery/vscode.ts +99 -0
  212. package/src/discovery/windsurf.ts +219 -0
  213. package/src/index.ts +192 -0
  214. package/src/main.ts +507 -0
  215. package/src/migrations.ts +156 -0
  216. package/src/modes/cleanup.ts +23 -0
  217. package/src/modes/index.ts +48 -0
  218. package/src/modes/interactive/components/armin.ts +382 -0
  219. package/src/modes/interactive/components/assistant-message.ts +86 -0
  220. package/src/modes/interactive/components/bash-execution.ts +199 -0
  221. package/src/modes/interactive/components/bordered-loader.ts +41 -0
  222. package/src/modes/interactive/components/branch-summary-message.ts +42 -0
  223. package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
  224. package/src/modes/interactive/components/custom-editor.ts +122 -0
  225. package/src/modes/interactive/components/diff.ts +147 -0
  226. package/src/modes/interactive/components/dynamic-border.ts +25 -0
  227. package/src/modes/interactive/components/extensions/extension-dashboard.ts +296 -0
  228. package/src/modes/interactive/components/extensions/extension-list.ts +479 -0
  229. package/src/modes/interactive/components/extensions/index.ts +9 -0
  230. package/src/modes/interactive/components/extensions/inspector-panel.ts +313 -0
  231. package/src/modes/interactive/components/extensions/state-manager.ts +558 -0
  232. package/src/modes/interactive/components/extensions/types.ts +191 -0
  233. package/src/modes/interactive/components/hook-editor.ts +117 -0
  234. package/src/modes/interactive/components/hook-input.ts +64 -0
  235. package/src/modes/interactive/components/hook-message.ts +96 -0
  236. package/src/modes/interactive/components/hook-selector.ts +91 -0
  237. package/src/modes/interactive/components/model-selector.ts +560 -0
  238. package/src/modes/interactive/components/oauth-selector.ts +136 -0
  239. package/src/modes/interactive/components/plugin-settings.ts +481 -0
  240. package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
  241. package/src/modes/interactive/components/session-selector.ts +220 -0
  242. package/src/modes/interactive/components/settings-defs.ts +597 -0
  243. package/src/modes/interactive/components/settings-selector.ts +545 -0
  244. package/src/modes/interactive/components/show-images-selector.ts +45 -0
  245. package/src/modes/interactive/components/status-line/index.ts +4 -0
  246. package/src/modes/interactive/components/status-line/presets.ts +94 -0
  247. package/src/modes/interactive/components/status-line/segments.ts +350 -0
  248. package/src/modes/interactive/components/status-line/separators.ts +55 -0
  249. package/src/modes/interactive/components/status-line/types.ts +81 -0
  250. package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
  251. package/src/modes/interactive/components/status-line.ts +384 -0
  252. package/src/modes/interactive/components/theme-selector.ts +62 -0
  253. package/src/modes/interactive/components/thinking-selector.ts +64 -0
  254. package/src/modes/interactive/components/tool-execution.ts +946 -0
  255. package/src/modes/interactive/components/tree-selector.ts +877 -0
  256. package/src/modes/interactive/components/ttsr-notification.ts +82 -0
  257. package/src/modes/interactive/components/user-message-selector.ts +159 -0
  258. package/src/modes/interactive/components/user-message.ts +18 -0
  259. package/src/modes/interactive/components/visual-truncate.ts +50 -0
  260. package/src/modes/interactive/components/welcome.ts +228 -0
  261. package/src/modes/interactive/interactive-mode.ts +2669 -0
  262. package/src/modes/interactive/theme/dark.json +102 -0
  263. package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
  264. package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
  265. package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
  266. package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
  267. package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
  268. package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
  269. package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
  270. package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
  271. package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
  272. package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
  273. package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
  274. package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
  275. package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
  276. package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
  277. package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
  278. package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
  279. package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
  280. package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
  281. package/src/modes/interactive/theme/defaults/index.ts +67 -0
  282. package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
  283. package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
  284. package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
  285. package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
  286. package/src/modes/interactive/theme/defaults/light-github.json +114 -0
  287. package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
  288. package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
  289. package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
  290. package/src/modes/interactive/theme/defaults/light-one.json +105 -0
  291. package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
  292. package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
  293. package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
  294. package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
  295. package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
  296. package/src/modes/interactive/theme/light.json +99 -0
  297. package/src/modes/interactive/theme/theme-schema.json +424 -0
  298. package/src/modes/interactive/theme/theme.ts +2211 -0
  299. package/src/modes/print-mode.ts +163 -0
  300. package/src/modes/rpc/rpc-client.ts +527 -0
  301. package/src/modes/rpc/rpc-mode.ts +494 -0
  302. package/src/modes/rpc/rpc-types.ts +203 -0
  303. package/src/prompts/architect-plan.md +10 -0
  304. package/src/prompts/branch-summary-preamble.md +3 -0
  305. package/src/prompts/branch-summary.md +28 -0
  306. package/src/prompts/browser.md +71 -0
  307. package/src/prompts/compaction-summary.md +34 -0
  308. package/src/prompts/compaction-turn-prefix.md +16 -0
  309. package/src/prompts/compaction-update-summary.md +41 -0
  310. package/src/prompts/explore.md +82 -0
  311. package/src/prompts/implement-with-critic.md +11 -0
  312. package/src/prompts/implement.md +11 -0
  313. package/src/prompts/init.md +30 -0
  314. package/src/prompts/plan.md +54 -0
  315. package/src/prompts/reviewer.md +81 -0
  316. package/src/prompts/summarization-system.md +3 -0
  317. package/src/prompts/system-prompt.md +27 -0
  318. package/src/prompts/task.md +56 -0
  319. package/src/prompts/title-system.md +8 -0
  320. package/src/prompts/tools/ask.md +24 -0
  321. package/src/prompts/tools/bash.md +23 -0
  322. package/src/prompts/tools/edit.md +9 -0
  323. package/src/prompts/tools/find.md +6 -0
  324. package/src/prompts/tools/grep.md +12 -0
  325. package/src/prompts/tools/lsp.md +14 -0
  326. package/src/prompts/tools/output.md +23 -0
  327. package/src/prompts/tools/read.md +25 -0
  328. package/src/prompts/tools/web-fetch.md +8 -0
  329. package/src/prompts/tools/web-search.md +10 -0
  330. package/src/prompts/tools/write.md +10 -0
  331. package/src/utils/changelog.ts +99 -0
  332. package/src/utils/clipboard.ts +265 -0
  333. package/src/utils/fuzzy.ts +108 -0
  334. package/src/utils/mime.ts +30 -0
  335. package/src/utils/shell-snapshot.ts +218 -0
  336. package/src/utils/shell.ts +364 -0
  337. package/src/utils/tools-manager.ts +265 -0
@@ -0,0 +1,531 @@
1
+ /**
2
+ * Subprocess execution for subagents.
3
+ *
4
+ * Spawns `omp` in JSON mode to execute tasks with isolated context.
5
+ * Parses JSON events for progress tracking.
6
+ */
7
+
8
+ import { spawn } from "node:child_process";
9
+ import * as fs from "node:fs";
10
+ import * as os from "node:os";
11
+ import * as path from "node:path";
12
+ import * as readline from "node:readline";
13
+ import { ensureArtifactsDir, getArtifactPaths } from "./artifacts";
14
+ import { resolveModelPattern } from "./model-resolver";
15
+ import { subprocessToolRegistry } from "./subprocess-tool-registry";
16
+ import {
17
+ type AgentDefinition,
18
+ type AgentProgress,
19
+ MAX_OUTPUT_BYTES,
20
+ MAX_OUTPUT_LINES,
21
+ OMP_BLOCKED_AGENT_ENV,
22
+ OMP_SPAWNS_ENV,
23
+ type SingleResult,
24
+ } from "./types";
25
+
26
+ /** omp command: 'omp.cmd' on Windows, 'omp' elsewhere */
27
+ const OMP_CMD = process.platform === "win32" ? "omp.cmd" : "omp";
28
+
29
+ /** Windows shell option for spawn */
30
+ const OMP_SHELL_OPT = process.platform === "win32";
31
+
32
+ /** Options for subprocess execution */
33
+ export interface ExecutorOptions {
34
+ cwd: string;
35
+ agent: AgentDefinition;
36
+ task: string;
37
+ index: number;
38
+ context?: string;
39
+ modelOverride?: string;
40
+ signal?: AbortSignal;
41
+ onProgress?: (progress: AgentProgress) => void;
42
+ sessionFile?: string | null;
43
+ persistArtifacts?: boolean;
44
+ artifactsDir?: string;
45
+ }
46
+
47
+ /**
48
+ * Truncate output to byte and line limits.
49
+ */
50
+ function truncateOutput(output: string): { text: string; truncated: boolean } {
51
+ let truncated = false;
52
+ let byteBudget = MAX_OUTPUT_BYTES;
53
+ let lineBudget = MAX_OUTPUT_LINES;
54
+
55
+ let i = 0;
56
+ let lastNewlineIndex = -1;
57
+ while (i < output.length && byteBudget > 0) {
58
+ const ch = output.charCodeAt(i);
59
+ byteBudget--;
60
+
61
+ if (ch === 10 /* \n */) {
62
+ lineBudget--;
63
+ lastNewlineIndex = i;
64
+ if (lineBudget <= 0) {
65
+ truncated = true;
66
+ break;
67
+ }
68
+ }
69
+
70
+ i++;
71
+ }
72
+
73
+ if (i < output.length) {
74
+ truncated = true;
75
+ }
76
+
77
+ if (truncated && lineBudget <= 0 && lastNewlineIndex >= 0) {
78
+ output = output.slice(0, lastNewlineIndex);
79
+ } else {
80
+ output = output.slice(0, i);
81
+ }
82
+
83
+ return { text: output, truncated };
84
+ }
85
+
86
+ /**
87
+ * Extract a short preview from tool args for display.
88
+ */
89
+ function extractToolArgsPreview(args: Record<string, unknown>): string {
90
+ // Priority order for preview
91
+ const previewKeys = ["command", "file_path", "path", "pattern", "query", "url", "task", "prompt"];
92
+
93
+ for (const key of previewKeys) {
94
+ if (args[key] && typeof args[key] === "string") {
95
+ const value = args[key] as string;
96
+ return value.length > 60 ? `${value.slice(0, 57)}...` : value;
97
+ }
98
+ }
99
+
100
+ return "";
101
+ }
102
+
103
+ function getNumberField(record: Record<string, unknown>, key: string): number | undefined {
104
+ if (!Object.hasOwn(record, key)) return undefined;
105
+ const value = record[key];
106
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
107
+ }
108
+
109
+ function firstNumberField(record: Record<string, unknown>, keys: string[]): number | undefined {
110
+ for (const key of keys) {
111
+ const value = getNumberField(record, key);
112
+ if (value !== undefined) return value;
113
+ }
114
+ return undefined;
115
+ }
116
+
117
+ /**
118
+ * Normalize usage objects from different event formats.
119
+ */
120
+ function getUsageTokens(usage: unknown): number {
121
+ if (!usage || typeof usage !== "object") return 0;
122
+ const record = usage as Record<string, unknown>;
123
+
124
+ const totalTokens = firstNumberField(record, ["totalTokens", "total_tokens"]);
125
+ if (totalTokens !== undefined && totalTokens > 0) return totalTokens;
126
+
127
+ const input = firstNumberField(record, ["input", "input_tokens", "inputTokens"]) ?? 0;
128
+ const output = firstNumberField(record, ["output", "output_tokens", "outputTokens"]) ?? 0;
129
+ const cacheRead = firstNumberField(record, ["cacheRead", "cache_read", "cacheReadTokens"]) ?? 0;
130
+ const cacheWrite = firstNumberField(record, ["cacheWrite", "cache_write", "cacheWriteTokens"]) ?? 0;
131
+
132
+ return input + output + cacheRead + cacheWrite;
133
+ }
134
+
135
+ /**
136
+ * Run a single agent as a subprocess.
137
+ */
138
+ export async function runSubprocess(options: ExecutorOptions): Promise<SingleResult> {
139
+ const { cwd, agent, task, index, context, modelOverride, signal, onProgress } = options;
140
+ const startTime = Date.now();
141
+
142
+ // Initialize progress
143
+ const progress: AgentProgress = {
144
+ index,
145
+ agent: agent.name,
146
+ agentSource: agent.source,
147
+ status: "running",
148
+ task,
149
+ recentTools: [],
150
+ recentOutput: [],
151
+ toolCount: 0,
152
+ tokens: 0,
153
+ durationMs: 0,
154
+ modelOverride,
155
+ };
156
+
157
+ // Check if already aborted
158
+ if (signal?.aborted) {
159
+ return {
160
+ index,
161
+ agent: agent.name,
162
+ agentSource: agent.source,
163
+ task,
164
+ exitCode: 1,
165
+ output: "",
166
+ stderr: "Aborted before start",
167
+ truncated: false,
168
+ durationMs: 0,
169
+ tokens: 0,
170
+ modelOverride,
171
+ error: "Aborted",
172
+ };
173
+ }
174
+
175
+ // Write system prompt to temp file
176
+ const tempDir = os.tmpdir();
177
+ const promptFile = path.join(
178
+ tempDir,
179
+ `omp-agent-${agent.name}-${Date.now()}-${Math.random().toString(36).slice(2)}.md`,
180
+ );
181
+
182
+ try {
183
+ fs.writeFileSync(promptFile, agent.systemPrompt, "utf-8");
184
+ } catch (err) {
185
+ return {
186
+ index,
187
+ agent: agent.name,
188
+ agentSource: agent.source,
189
+ task,
190
+ exitCode: 1,
191
+ output: "",
192
+ stderr: `Failed to write prompt file: ${err}`,
193
+ truncated: false,
194
+ durationMs: Date.now() - startTime,
195
+ tokens: 0,
196
+ modelOverride,
197
+ error: `Failed to write prompt file: ${err}`,
198
+ };
199
+ }
200
+
201
+ // Build full task with context
202
+ const fullTask = context ? `${context}\n\n${task}` : task;
203
+
204
+ // Set up artifact paths and write input file upfront if artifacts dir provided
205
+ let artifactPaths: { inputPath: string; outputPath: string; jsonlPath: string } | undefined;
206
+ let subtaskSessionFile: string | undefined;
207
+
208
+ if (options.artifactsDir) {
209
+ ensureArtifactsDir(options.artifactsDir);
210
+ artifactPaths = getArtifactPaths(options.artifactsDir, agent.name, index);
211
+ subtaskSessionFile = artifactPaths.jsonlPath;
212
+
213
+ // Write input file immediately (real-time visibility)
214
+ try {
215
+ fs.writeFileSync(artifactPaths.inputPath, fullTask, "utf-8");
216
+ } catch {
217
+ // Non-fatal, continue without input artifact
218
+ }
219
+ }
220
+
221
+ // Build args
222
+ const args: string[] = ["--mode", "json", "--non-interactive"];
223
+
224
+ // Add system prompt
225
+ args.push("--append-system-prompt", promptFile);
226
+
227
+ // Add tools if specified
228
+ if (agent.tools && agent.tools.length > 0) {
229
+ let toolList = agent.tools;
230
+ // Auto-include task tool if spawns defined but task not in tools
231
+ if (agent.spawns !== undefined && !toolList.includes("task")) {
232
+ toolList = [...toolList, "task"];
233
+ }
234
+ args.push("--tools", toolList.join(","));
235
+ }
236
+
237
+ // Resolve and add model
238
+ const resolvedModel = resolveModelPattern(modelOverride || agent.model);
239
+ if (resolvedModel) {
240
+ args.push("--model", resolvedModel);
241
+ }
242
+
243
+ // Add session options - use subtask-specific session file for real-time streaming
244
+ if (subtaskSessionFile) {
245
+ args.push("--session", subtaskSessionFile);
246
+ } else if (options.sessionFile) {
247
+ args.push("--session", options.sessionFile);
248
+ } else {
249
+ args.push("--no-session");
250
+ }
251
+
252
+ // Add task as prompt
253
+ args.push("--prompt", fullTask);
254
+
255
+ // Set up environment - block same-agent recursion unless explicitly recursive
256
+ const env = { ...process.env };
257
+ if (!agent.recursive) {
258
+ env[OMP_BLOCKED_AGENT_ENV] = agent.name;
259
+ }
260
+
261
+ // Propagate spawn restrictions to subprocess
262
+ if (agent.spawns === undefined) {
263
+ env[OMP_SPAWNS_ENV] = ""; // No spawns = deny all
264
+ } else if (agent.spawns === "*") {
265
+ env[OMP_SPAWNS_ENV] = "*";
266
+ } else {
267
+ env[OMP_SPAWNS_ENV] = agent.spawns.join(",");
268
+ }
269
+
270
+ // Spawn subprocess
271
+ const proc = spawn(OMP_CMD, args, {
272
+ cwd,
273
+ stdio: ["ignore", "pipe", "pipe"],
274
+ shell: OMP_SHELL_OPT,
275
+ env,
276
+ });
277
+
278
+ let output = "";
279
+ let stderr = "";
280
+ let finalOutput = "";
281
+ let resolved = false;
282
+ let pendingTermination = false; // Set when shouldTerminate fires, wait for message_end
283
+ const jsonlEvents: string[] = [];
284
+
285
+ // Handle abort signal
286
+ const onAbort = () => {
287
+ if (!resolved) {
288
+ proc.kill("SIGTERM");
289
+ }
290
+ };
291
+ if (signal) {
292
+ signal.addEventListener("abort", onAbort, { once: true });
293
+ }
294
+
295
+ // Parse JSON events from stdout
296
+ const rl = readline.createInterface({ input: proc.stdout! });
297
+
298
+ rl.on("line", (line) => {
299
+ if (resolved) return;
300
+
301
+ try {
302
+ const event = JSON.parse(line);
303
+ jsonlEvents.push(line);
304
+ const now = Date.now();
305
+
306
+ switch (event.type) {
307
+ case "tool_execution_start":
308
+ progress.toolCount++;
309
+ progress.currentTool = event.toolName;
310
+ progress.currentToolArgs = extractToolArgsPreview(event.toolArgs || event.args || {});
311
+ progress.currentToolStartMs = now;
312
+ break;
313
+
314
+ case "tool_execution_end": {
315
+ if (progress.currentTool) {
316
+ progress.recentTools.unshift({
317
+ tool: progress.currentTool,
318
+ args: progress.currentToolArgs || "",
319
+ endMs: now,
320
+ });
321
+ // Keep only last 5
322
+ if (progress.recentTools.length > 5) {
323
+ progress.recentTools.pop();
324
+ }
325
+ }
326
+ progress.currentTool = undefined;
327
+ progress.currentToolArgs = undefined;
328
+ progress.currentToolStartMs = undefined;
329
+
330
+ // Check for registered subprocess tool handler
331
+ const handler = subprocessToolRegistry.getHandler(event.toolName);
332
+ if (handler) {
333
+ // Extract data using handler
334
+ if (handler.extractData) {
335
+ const data = handler.extractData({
336
+ toolName: event.toolName,
337
+ toolCallId: event.toolCallId,
338
+ args: event.args,
339
+ result: event.result,
340
+ isError: event.isError,
341
+ });
342
+ if (data !== undefined) {
343
+ progress.extractedToolData = progress.extractedToolData || {};
344
+ progress.extractedToolData[event.toolName] = progress.extractedToolData[event.toolName] || [];
345
+ progress.extractedToolData[event.toolName].push(data);
346
+ }
347
+ }
348
+
349
+ // Check if handler wants to terminate subprocess
350
+ if (
351
+ handler.shouldTerminate?.({
352
+ toolName: event.toolName,
353
+ toolCallId: event.toolCallId,
354
+ args: event.args,
355
+ result: event.result,
356
+ isError: event.isError,
357
+ })
358
+ ) {
359
+ // Don't kill immediately - wait for message_end to get token counts
360
+ pendingTermination = true;
361
+ // Safety timeout in case message_end never arrives
362
+ setTimeout(() => {
363
+ if (!resolved) {
364
+ resolved = true;
365
+ proc.kill("SIGTERM");
366
+ }
367
+ }, 2000);
368
+ }
369
+ }
370
+ break;
371
+ }
372
+
373
+ case "message_update": {
374
+ // Extract text for progress display only (replace, don't accumulate)
375
+ const updateContent = event.message?.content || event.content;
376
+ if (updateContent && Array.isArray(updateContent)) {
377
+ const allText: string[] = [];
378
+ for (const block of updateContent) {
379
+ if (block.type === "text" && block.text) {
380
+ const lines = block.text.split("\n").filter((l: string) => l.trim());
381
+ allText.push(...lines);
382
+ }
383
+ }
384
+ // Show last 8 lines from current state (not accumulated)
385
+ progress.recentOutput = allText.slice(-8).reverse();
386
+ }
387
+ break;
388
+ }
389
+
390
+ case "message_end": {
391
+ // Extract final text content from completed message
392
+ const messageContent = event.message?.content || event.content;
393
+ if (messageContent && Array.isArray(messageContent)) {
394
+ for (const block of messageContent) {
395
+ if (block.type === "text" && block.text) {
396
+ output += block.text;
397
+ }
398
+ }
399
+ }
400
+ // Extract usage (prefer message.usage, fallback to event.usage)
401
+ const messageUsage = event.message?.usage || event.usage;
402
+ if (messageUsage) {
403
+ // Accumulate tokens across messages (not overwrite)
404
+ progress.tokens += getUsageTokens(messageUsage);
405
+ }
406
+ // If pending termination, now we have tokens - terminate
407
+ if (pendingTermination && !resolved) {
408
+ resolved = true;
409
+ proc.kill("SIGTERM");
410
+ }
411
+ break;
412
+ }
413
+
414
+ case "agent_end":
415
+ // Extract final content from messages array
416
+ if (event.messages && Array.isArray(event.messages)) {
417
+ for (const msg of event.messages) {
418
+ if (msg.content && Array.isArray(msg.content)) {
419
+ for (const block of msg.content) {
420
+ if (block.type === "text" && block.text) {
421
+ finalOutput += block.text;
422
+ }
423
+ }
424
+ }
425
+ }
426
+ }
427
+ break;
428
+ }
429
+
430
+ progress.durationMs = now - startTime;
431
+ // Clone progress object before passing to callback to prevent mutation during render
432
+ onProgress?.({ ...progress });
433
+ } catch {
434
+ // Ignore non-JSON lines
435
+ }
436
+ });
437
+
438
+ // Capture stderr
439
+ const stderrDecoder = new TextDecoder();
440
+ proc.stderr?.on("data", (chunk: Buffer) => {
441
+ stderr += stderrDecoder.decode(chunk, { stream: true });
442
+ });
443
+
444
+ // Wait for readline to finish BEFORE resolving
445
+ const exitCode = await new Promise<number>((resolve) => {
446
+ let code: number | null = null;
447
+ let rlClosed = false;
448
+ let procClosed = false;
449
+
450
+ const maybeResolve = () => {
451
+ if (rlClosed && procClosed) {
452
+ resolved = true;
453
+ resolve(code ?? 1);
454
+ }
455
+ };
456
+
457
+ rl.on("close", () => {
458
+ rlClosed = true;
459
+ maybeResolve();
460
+ });
461
+
462
+ proc.on("close", (c) => {
463
+ code = c;
464
+ procClosed = true;
465
+ maybeResolve();
466
+ });
467
+
468
+ proc.on("error", (err) => {
469
+ stderr += `\nProcess error: ${err.message}`;
470
+ code = 1;
471
+ procClosed = true;
472
+ maybeResolve();
473
+ });
474
+ });
475
+
476
+ // Cleanup
477
+ if (signal) {
478
+ signal.removeEventListener("abort", onAbort);
479
+ }
480
+
481
+ try {
482
+ fs.unlinkSync(promptFile);
483
+ } catch {
484
+ // Ignore cleanup errors
485
+ }
486
+
487
+ // Use final output if available, otherwise accumulated output
488
+ const rawOutput = finalOutput || output;
489
+ const { text: truncatedOutput, truncated } = truncateOutput(rawOutput);
490
+
491
+ // Write output artifact (input and jsonl already written in real-time)
492
+ // Compute output metadata for Output tool integration
493
+ let outputMeta: { lineCount: number; charCount: number } | undefined;
494
+ if (artifactPaths) {
495
+ try {
496
+ fs.writeFileSync(artifactPaths.outputPath, rawOutput, "utf-8");
497
+ outputMeta = {
498
+ lineCount: rawOutput.split("\n").length,
499
+ charCount: rawOutput.length,
500
+ };
501
+ } catch {
502
+ // Non-fatal
503
+ }
504
+ }
505
+
506
+ // Update final progress
507
+ const wasAborted = signal?.aborted ?? false;
508
+ progress.status = wasAborted ? "aborted" : exitCode === 0 ? "completed" : "failed";
509
+ progress.durationMs = Date.now() - startTime;
510
+ onProgress?.(progress);
511
+
512
+ return {
513
+ index,
514
+ agent: agent.name,
515
+ agentSource: agent.source,
516
+ task,
517
+ exitCode,
518
+ output: truncatedOutput,
519
+ stderr,
520
+ truncated,
521
+ durationMs: Date.now() - startTime,
522
+ tokens: progress.tokens,
523
+ modelOverride,
524
+ error: exitCode !== 0 && stderr ? stderr : undefined,
525
+ aborted: wasAborted,
526
+ jsonlEvents,
527
+ artifactPaths,
528
+ extractedToolData: progress.extractedToolData,
529
+ outputMeta,
530
+ };
531
+ }