@fitchmultz/pi-subagents 0.0.0-stage → 0.44.5

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 (347) hide show
  1. package/CHANGELOG.md +1654 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1533 -2
  4. package/agents/context-builder.md +97 -0
  5. package/agents/debugger.md +51 -0
  6. package/agents/delegate.md +13 -0
  7. package/agents/fixer.md +37 -0
  8. package/agents/oracle.md +71 -0
  9. package/agents/planner.md +72 -0
  10. package/agents/researcher.md +51 -0
  11. package/agents/reviewer-claude.md +69 -0
  12. package/agents/reviewer-gpt.md +68 -0
  13. package/agents/reviewer-ponytail.md +82 -0
  14. package/agents/reviewer-security.md +81 -0
  15. package/agents/reviewer.md +69 -0
  16. package/agents/scout.md +66 -0
  17. package/agents/ui-designer.md +51 -0
  18. package/agents/watcher.md +26 -0
  19. package/agents/worker.md +46 -0
  20. package/agents/writer.md +36 -0
  21. package/dist/agents/agent-management.js +923 -0
  22. package/dist/agents/agent-scope.js +7 -0
  23. package/dist/agents/agent-selection.js +20 -0
  24. package/dist/agents/agent-serializer.js +100 -0
  25. package/dist/agents/agents.js +752 -0
  26. package/dist/agents/chain-serializer.js +367 -0
  27. package/dist/agents/frontmatter.js +21 -0
  28. package/dist/agents/identity.js +29 -0
  29. package/dist/agents/skills.js +570 -0
  30. package/dist/extension/build-info.js +1 -0
  31. package/dist/extension/compact-tools.js +112 -0
  32. package/dist/extension/config.js +22 -0
  33. package/dist/extension/control-notices.js +29 -0
  34. package/dist/extension/doctor.js +143 -0
  35. package/dist/extension/fanout-child.js +326 -0
  36. package/dist/extension/index.js +577 -0
  37. package/dist/extension/schemas.js +416 -0
  38. package/dist/extension/tool-input.js +20 -0
  39. package/dist/extension/tool-result.js +14 -0
  40. package/dist/history/canonical-result.js +81 -0
  41. package/dist/history/database.js +72 -0
  42. package/dist/history/index.js +199 -0
  43. package/dist/history/ingest.js +468 -0
  44. package/dist/history/queries.js +350 -0
  45. package/dist/history/store.js +216 -0
  46. package/dist/history/types.js +4 -0
  47. package/dist/history/worker.js +466 -0
  48. package/dist/intercom/intercom-bridge.js +112 -0
  49. package/dist/intercom/live-intercom.js +94 -0
  50. package/dist/intercom/result-intercom.js +373 -0
  51. package/dist/pi-intercom/agent-dir.js +10 -0
  52. package/dist/pi-intercom/bridge.js +750 -0
  53. package/dist/pi-intercom/broker/broker.js +426 -0
  54. package/dist/pi-intercom/broker/client.js +492 -0
  55. package/dist/pi-intercom/broker/framing.js +65 -0
  56. package/dist/pi-intercom/broker/paths.js +42 -0
  57. package/dist/pi-intercom/broker/pid.js +66 -0
  58. package/dist/pi-intercom/broker/spawn.js +221 -0
  59. package/dist/pi-intercom/config.js +102 -0
  60. package/dist/pi-intercom/index.js +2942 -0
  61. package/dist/pi-intercom/reply-tracker.js +182 -0
  62. package/dist/pi-intercom/session-targets.js +128 -0
  63. package/dist/pi-intercom/subagent-live-events.js +132 -0
  64. package/dist/pi-intercom/topics.js +157 -0
  65. package/dist/pi-intercom/types.js +163 -0
  66. package/dist/pi-intercom/ui/compose.js +269 -0
  67. package/dist/pi-intercom/ui/inline-message.js +80 -0
  68. package/dist/pi-intercom/ui/session-list.js +124 -0
  69. package/dist/runs/background/async-control.js +95 -0
  70. package/dist/runs/background/async-execution.js +676 -0
  71. package/dist/runs/background/async-job-tracker.js +383 -0
  72. package/dist/runs/background/async-result-file.js +58 -0
  73. package/dist/runs/background/async-resume.js +305 -0
  74. package/dist/runs/background/async-status.js +442 -0
  75. package/dist/runs/background/completion-dedupe.js +69 -0
  76. package/dist/runs/background/completion-delivery.js +245 -0
  77. package/dist/runs/background/notify.js +71 -0
  78. package/dist/runs/background/parallel-groups.js +55 -0
  79. package/dist/runs/background/result-watcher.js +426 -0
  80. package/dist/runs/background/run-id-resolver.js +64 -0
  81. package/dist/runs/background/run-status.js +382 -0
  82. package/dist/runs/background/stale-run-reconciler.js +335 -0
  83. package/dist/runs/background/subagent-runner-launcher.js +55 -0
  84. package/dist/runs/background/subagent-runner.js +1872 -0
  85. package/dist/runs/background/top-level-async.js +5 -0
  86. package/dist/runs/foreground/chain-clarify.js +1152 -0
  87. package/dist/runs/foreground/clarify-invocation.js +72 -0
  88. package/dist/runs/foreground/execution-input.js +423 -0
  89. package/dist/runs/foreground/foreground-control.js +817 -0
  90. package/dist/runs/foreground/question-control.js +88 -0
  91. package/dist/runs/foreground/run-async-path.js +205 -0
  92. package/dist/runs/foreground/subagent-executor.js +442 -0
  93. package/dist/runs/foreground/subagent-params.js +165 -0
  94. package/dist/runs/foreground/wait-run.js +141 -0
  95. package/dist/runs/shared/acceptance-contract.js +253 -0
  96. package/dist/runs/shared/acceptance-evaluation.js +256 -0
  97. package/dist/runs/shared/acceptance-finalization.js +280 -0
  98. package/dist/runs/shared/acceptance-reports.js +147 -0
  99. package/dist/runs/shared/acceptance.js +4 -0
  100. package/dist/runs/shared/chain-outputs.js +110 -0
  101. package/dist/runs/shared/child-attempt.js +693 -0
  102. package/dist/runs/shared/child-execution-cwd.js +96 -0
  103. package/dist/runs/shared/child-observations.js +114 -0
  104. package/dist/runs/shared/claude-code.js +199 -0
  105. package/dist/runs/shared/completion-guard.js +31 -0
  106. package/dist/runs/shared/dynamic-fanout.js +274 -0
  107. package/dist/runs/shared/history-index.js +72 -0
  108. package/dist/runs/shared/mcp-direct-tool-allowlist.js +334 -0
  109. package/dist/runs/shared/model-fallback.js +185 -0
  110. package/dist/runs/shared/mutating-tool-guard.js +170 -0
  111. package/dist/runs/shared/native-finalization.js +101 -0
  112. package/dist/runs/shared/native-usage.js +153 -0
  113. package/dist/runs/shared/nested-events.js +700 -0
  114. package/dist/runs/shared/nested-path.js +48 -0
  115. package/dist/runs/shared/nested-render.js +108 -0
  116. package/dist/runs/shared/parallel-utils.js +61 -0
  117. package/dist/runs/shared/parent-receipts.js +68 -0
  118. package/dist/runs/shared/parent-usage.js +121 -0
  119. package/dist/runs/shared/pi-args.js +310 -0
  120. package/dist/runs/shared/pi-spawn.js +52 -0
  121. package/dist/runs/shared/run-history-queries.js +45 -0
  122. package/dist/runs/shared/run-history.js +110 -0
  123. package/dist/runs/shared/run-id-ambiguity.js +4 -0
  124. package/dist/runs/shared/run-records.js +679 -0
  125. package/dist/runs/shared/session-cwd-preload.js +33 -0
  126. package/dist/runs/shared/single-output.js +222 -0
  127. package/dist/runs/shared/streaming-text.js +15 -0
  128. package/dist/runs/shared/structured-output.js +55 -0
  129. package/dist/runs/shared/subagent-control.js +179 -0
  130. package/dist/runs/shared/subagent-prompt-runtime.js +197 -0
  131. package/dist/runs/shared/subagent-tool-loop-guard.js +66 -0
  132. package/dist/runs/shared/supervisor-questions.js +350 -0
  133. package/dist/runs/shared/workflow-graph.js +214 -0
  134. package/dist/runs/shared/workflow-policy.js +84 -0
  135. package/dist/runs/shared/worktree.js +525 -0
  136. package/dist/shared/agent-context-policy.js +126 -0
  137. package/dist/shared/artifacts.js +96 -0
  138. package/dist/shared/atomic-json.js +13 -0
  139. package/dist/shared/cooperative.js +20 -0
  140. package/dist/shared/execution-cwd.js +14 -0
  141. package/dist/shared/file-coalescer.js +21 -0
  142. package/dist/shared/fork-context.js +49 -0
  143. package/dist/shared/formatters.js +85 -0
  144. package/dist/shared/journal-reader.js +407 -0
  145. package/dist/shared/lazy-tools.js +32 -0
  146. package/dist/shared/model-info.js +68 -0
  147. package/dist/shared/native-session.js +15 -0
  148. package/dist/shared/native-tui.js +18 -0
  149. package/dist/shared/native-typebox.js +20 -0
  150. package/dist/shared/post-exit-stdio-guard.js +197 -0
  151. package/dist/shared/prompt-sections.js +8 -0
  152. package/dist/shared/session-entries.js +52 -0
  153. package/dist/shared/session-identity.js +14 -0
  154. package/dist/shared/settings.js +254 -0
  155. package/dist/shared/status-format.js +111 -0
  156. package/dist/shared/temp-root.js +114 -0
  157. package/dist/shared/types.js +207 -0
  158. package/dist/shared/ui-mode.js +3 -0
  159. package/dist/shared/utils.js +334 -0
  160. package/dist/slash/prompt-template-bridge.js +298 -0
  161. package/dist/slash/slash-bridge.js +116 -0
  162. package/dist/slash/slash-commands.js +607 -0
  163. package/dist/slash/slash-live-state.js +246 -0
  164. package/dist/tui/action-hints.js +75 -0
  165. package/dist/tui/agent-history.js +197 -0
  166. package/dist/tui/agent-view.js +1656 -0
  167. package/dist/tui/render.js +1515 -0
  168. package/docs/intercom-bridge.md +212 -0
  169. package/docs/intercom.md +581 -0
  170. package/docs/performance.md +121 -0
  171. package/package.json +102 -4
  172. package/prompts/gather-context-and-clarify.md +14 -0
  173. package/prompts/parallel-cleanup.md +62 -0
  174. package/prompts/parallel-context-build.md +56 -0
  175. package/prompts/parallel-handoff-plan.md +62 -0
  176. package/prompts/parallel-research.md +51 -0
  177. package/prompts/parallel-review.md +59 -0
  178. package/prompts/review-loop.md +42 -0
  179. package/scripts/build.mjs +134 -0
  180. package/scripts/check-compat.mjs +48 -0
  181. package/scripts/compat-host.mjs +19 -0
  182. package/scripts/intercom-bridge-credentials.mjs +143 -0
  183. package/scripts/intercom-remote.mjs +103 -0
  184. package/scripts/linux-smoke.sh +51 -0
  185. package/scripts/local-install-smoke.mjs +66 -0
  186. package/scripts/native-package-smoke.mjs +82 -0
  187. package/scripts/package-smoke.mjs +209 -0
  188. package/scripts/prepare.mjs +67 -0
  189. package/scripts/real-pi-smoke.mjs +415 -0
  190. package/scripts/run-tests.mjs +149 -0
  191. package/skills/pi-intercom/SKILL.md +113 -0
  192. package/skills/pi-intercom/evals/evals.json +29 -0
  193. package/skills/pi-intercom/evals/trigger-evals.json +47 -0
  194. package/skills/pi-intercom/references/peer-sessions.md +69 -0
  195. package/skills/pi-subagents/SKILL.md +76 -0
  196. package/skills/pi-subagents/evals/evals.json +29 -0
  197. package/skills/pi-subagents/evals/trigger-evals.json +42 -0
  198. package/skills/pi-subagents/references/full-orchestration-guide.md +863 -0
  199. package/src/agents/agent-management.ts +813 -0
  200. package/src/agents/agent-scope.ts +7 -0
  201. package/src/agents/agent-selection.ts +23 -0
  202. package/src/agents/agent-serializer.ts +99 -0
  203. package/src/agents/agents.ts +916 -0
  204. package/src/agents/chain-serializer.ts +329 -0
  205. package/src/agents/frontmatter.ts +21 -0
  206. package/src/agents/identity.ts +30 -0
  207. package/src/agents/skills.ts +637 -0
  208. package/src/extension/build-info.ts +2 -0
  209. package/src/extension/compact-tools.ts +114 -0
  210. package/src/extension/config.ts +23 -0
  211. package/src/extension/control-notices.ts +47 -0
  212. package/src/extension/doctor.ts +171 -0
  213. package/src/extension/fanout-child.ts +312 -0
  214. package/src/extension/index.ts +649 -0
  215. package/src/extension/schemas.ts +444 -0
  216. package/src/extension/tool-input.ts +19 -0
  217. package/src/extension/tool-result.ts +16 -0
  218. package/src/history/canonical-result.ts +62 -0
  219. package/src/history/database.ts +59 -0
  220. package/src/history/index.ts +156 -0
  221. package/src/history/ingest.ts +349 -0
  222. package/src/history/queries.ts +241 -0
  223. package/src/history/store.ts +203 -0
  224. package/src/history/types.ts +166 -0
  225. package/src/history/worker.ts +321 -0
  226. package/src/intercom/intercom-bridge.ts +123 -0
  227. package/src/intercom/live-intercom.ts +104 -0
  228. package/src/intercom/result-intercom.ts +429 -0
  229. package/src/pi-intercom/agent-dir.ts +9 -0
  230. package/src/pi-intercom/bridge.ts +668 -0
  231. package/src/pi-intercom/broker/broker.ts +463 -0
  232. package/src/pi-intercom/broker/client.ts +558 -0
  233. package/src/pi-intercom/broker/framing.ts +77 -0
  234. package/src/pi-intercom/broker/paths.ts +47 -0
  235. package/src/pi-intercom/broker/pid.ts +59 -0
  236. package/src/pi-intercom/broker/spawn.ts +248 -0
  237. package/src/pi-intercom/config.ts +142 -0
  238. package/src/pi-intercom/index.ts +2929 -0
  239. package/src/pi-intercom/reply-tracker.ts +211 -0
  240. package/src/pi-intercom/session-targets.ts +172 -0
  241. package/src/pi-intercom/subagent-live-events.ts +133 -0
  242. package/src/pi-intercom/topics.ts +125 -0
  243. package/src/pi-intercom/types.ts +271 -0
  244. package/src/pi-intercom/ui/compose.ts +289 -0
  245. package/src/pi-intercom/ui/inline-message.ts +92 -0
  246. package/src/pi-intercom/ui/session-list.ts +139 -0
  247. package/src/runs/background/async-control.ts +89 -0
  248. package/src/runs/background/async-execution.ts +850 -0
  249. package/src/runs/background/async-job-tracker.ts +385 -0
  250. package/src/runs/background/async-result-file.ts +62 -0
  251. package/src/runs/background/async-resume.ts +333 -0
  252. package/src/runs/background/async-status.ts +481 -0
  253. package/src/runs/background/completion-dedupe.ts +79 -0
  254. package/src/runs/background/completion-delivery.ts +195 -0
  255. package/src/runs/background/notify.ts +123 -0
  256. package/src/runs/background/parallel-groups.ts +53 -0
  257. package/src/runs/background/result-watcher.ts +423 -0
  258. package/src/runs/background/run-id-resolver.ts +69 -0
  259. package/src/runs/background/run-status.ts +384 -0
  260. package/src/runs/background/stale-run-reconciler.ts +369 -0
  261. package/src/runs/background/subagent-runner-launcher.ts +56 -0
  262. package/src/runs/background/subagent-runner.ts +2053 -0
  263. package/src/runs/background/top-level-async.ts +13 -0
  264. package/src/runs/foreground/chain-clarify.ts +1340 -0
  265. package/src/runs/foreground/clarify-invocation.ts +77 -0
  266. package/src/runs/foreground/execution-input.ts +467 -0
  267. package/src/runs/foreground/foreground-control.ts +821 -0
  268. package/src/runs/foreground/question-control.ts +82 -0
  269. package/src/runs/foreground/run-async-path.ts +243 -0
  270. package/src/runs/foreground/subagent-executor.ts +501 -0
  271. package/src/runs/foreground/subagent-params.ts +320 -0
  272. package/src/runs/foreground/wait-run.ts +124 -0
  273. package/src/runs/shared/acceptance-contract.ts +257 -0
  274. package/src/runs/shared/acceptance-evaluation.ts +275 -0
  275. package/src/runs/shared/acceptance-finalization.ts +353 -0
  276. package/src/runs/shared/acceptance-reports.ts +152 -0
  277. package/src/runs/shared/acceptance.ts +33 -0
  278. package/src/runs/shared/chain-outputs.ts +114 -0
  279. package/src/runs/shared/child-attempt.ts +623 -0
  280. package/src/runs/shared/child-execution-cwd.ts +91 -0
  281. package/src/runs/shared/child-observations.ts +99 -0
  282. package/src/runs/shared/claude-code.ts +243 -0
  283. package/src/runs/shared/completion-guard.ts +35 -0
  284. package/src/runs/shared/dynamic-fanout.ts +295 -0
  285. package/src/runs/shared/history-index.ts +69 -0
  286. package/src/runs/shared/mcp-direct-tool-allowlist.ts +376 -0
  287. package/src/runs/shared/model-fallback.ts +219 -0
  288. package/src/runs/shared/mutating-tool-guard.ts +194 -0
  289. package/src/runs/shared/native-finalization.ts +128 -0
  290. package/src/runs/shared/native-usage.ts +113 -0
  291. package/src/runs/shared/nested-events.ts +752 -0
  292. package/src/runs/shared/nested-path.ts +52 -0
  293. package/src/runs/shared/nested-render.ts +119 -0
  294. package/src/runs/shared/parallel-utils.ts +153 -0
  295. package/src/runs/shared/parent-receipts.ts +59 -0
  296. package/src/runs/shared/parent-usage.ts +121 -0
  297. package/src/runs/shared/pi-args.ts +350 -0
  298. package/src/runs/shared/pi-spawn.ts +52 -0
  299. package/src/runs/shared/run-history-queries.ts +44 -0
  300. package/src/runs/shared/run-history.ts +100 -0
  301. package/src/runs/shared/run-id-ambiguity.ts +4 -0
  302. package/src/runs/shared/run-records.ts +607 -0
  303. package/src/runs/shared/session-cwd-preload.ts +27 -0
  304. package/src/runs/shared/single-output.ts +267 -0
  305. package/src/runs/shared/streaming-text.ts +18 -0
  306. package/src/runs/shared/structured-output.ts +68 -0
  307. package/src/runs/shared/subagent-control.ts +219 -0
  308. package/src/runs/shared/subagent-prompt-runtime.ts +201 -0
  309. package/src/runs/shared/subagent-tool-loop-guard.ts +89 -0
  310. package/src/runs/shared/supervisor-questions.ts +415 -0
  311. package/src/runs/shared/workflow-graph.ts +220 -0
  312. package/src/runs/shared/workflow-policy.ts +125 -0
  313. package/src/runs/shared/worktree.ts +635 -0
  314. package/src/shared/agent-context-policy.ts +184 -0
  315. package/src/shared/artifacts.ts +95 -0
  316. package/src/shared/atomic-json.ts +16 -0
  317. package/src/shared/cooperative.ts +21 -0
  318. package/src/shared/execution-cwd.ts +14 -0
  319. package/src/shared/file-coalescer.ts +29 -0
  320. package/src/shared/fork-context.ts +68 -0
  321. package/src/shared/formatters.ts +84 -0
  322. package/src/shared/journal-reader.ts +333 -0
  323. package/src/shared/lazy-tools.ts +36 -0
  324. package/src/shared/model-info.ts +81 -0
  325. package/src/shared/native-session.ts +8 -0
  326. package/src/shared/native-tui.ts +10 -0
  327. package/src/shared/native-typebox.ts +14 -0
  328. package/src/shared/post-exit-stdio-guard.ts +204 -0
  329. package/src/shared/prompt-sections.ts +10 -0
  330. package/src/shared/session-entries.ts +49 -0
  331. package/src/shared/session-identity.ts +18 -0
  332. package/src/shared/settings.ts +384 -0
  333. package/src/shared/status-format.ts +123 -0
  334. package/src/shared/temp-root.ts +107 -0
  335. package/src/shared/types.ts +1260 -0
  336. package/src/shared/ui-mode.ts +5 -0
  337. package/src/shared/utils.ts +342 -0
  338. package/src/slash/prompt-template-bridge.ts +408 -0
  339. package/src/slash/slash-bridge.ts +178 -0
  340. package/src/slash/slash-commands.ts +617 -0
  341. package/src/slash/slash-live-state.ts +292 -0
  342. package/src/tui/action-hints.ts +69 -0
  343. package/src/tui/agent-history.ts +210 -0
  344. package/src/tui/agent-view.ts +1323 -0
  345. package/src/tui/render.ts +1550 -0
  346. package/tsconfig.build.json +12 -0
  347. package/tsconfig.json +19 -0
@@ -0,0 +1,114 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { isSafeNestedPathId } from "../runs/shared/nested-path.js";
4
+ import { ASYNC_DIR, RESULTS_DIR, TEMP_ROOT_DIR } from "./types.js";
5
+ import { listRunQuestions, LEGACY_QUESTIONS_DIR, QUESTIONS_DIR } from "../runs/shared/supervisor-questions.js";
6
+ const MAX_RUN_AGE_MS = 7 * 24 * 60 * 60 * 1000;
7
+ export function ensureTempRoot() {
8
+ fs.mkdirSync(TEMP_ROOT_DIR, { recursive: true, mode: 0o700 });
9
+ const stat = fs.lstatSync(TEMP_ROOT_DIR);
10
+ if (!stat.isDirectory() || stat.isSymbolicLink())
11
+ throw new Error(`Unsafe pi-subagents temp root: ${TEMP_ROOT_DIR}`);
12
+ const uid = process.getuid?.();
13
+ if (uid !== undefined && stat.uid !== uid)
14
+ throw new Error(`pi-subagents temp root is owned by another user: ${TEMP_ROOT_DIR}`);
15
+ fs.chmodSync(TEMP_ROOT_DIR, 0o700);
16
+ }
17
+ export function ensureSafeTempPath(candidate) {
18
+ const root = path.resolve(TEMP_ROOT_DIR);
19
+ const resolved = path.resolve(candidate);
20
+ const relative = path.relative(root, resolved);
21
+ if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))
22
+ return;
23
+ ensureTempRoot();
24
+ let current = root;
25
+ for (const segment of relative.split(path.sep).filter(Boolean)) {
26
+ current = path.join(current, segment);
27
+ try {
28
+ const stat = fs.lstatSync(current);
29
+ if (stat.isSymbolicLink())
30
+ throw new Error(`Unsafe symlink in pi-subagents temp path: ${current}`);
31
+ }
32
+ catch (error) {
33
+ if (error.code === "ENOENT")
34
+ return;
35
+ throw error;
36
+ }
37
+ }
38
+ }
39
+ // Missing, malformed, or non-object JSON means the entry is incomplete garbage and safe
40
+ // to remove. Operational read failures throw so callers fail closed instead of deleting.
41
+ async function readJsonObject(file) {
42
+ try {
43
+ const parsed = JSON.parse(await fs.promises.readFile(file, "utf-8"));
44
+ return parsed !== null && typeof parsed === "object" ? parsed : undefined;
45
+ }
46
+ catch (error) {
47
+ if (error.code === "ENOENT" || error instanceof SyntaxError)
48
+ return undefined;
49
+ throw error;
50
+ }
51
+ }
52
+ async function activeStatus(statusFile) {
53
+ const status = await readJsonObject(statusFile);
54
+ return status?.state === "running" || status?.state === "queued";
55
+ }
56
+ async function removeOldEntries(root, now, keepActive) {
57
+ let entries;
58
+ try {
59
+ entries = await fs.promises.readdir(root, { withFileTypes: true });
60
+ }
61
+ catch {
62
+ return;
63
+ }
64
+ for (const entry of entries) {
65
+ const entryPath = path.join(root, entry.name);
66
+ try {
67
+ const stat = await fs.promises.lstat(entryPath);
68
+ if (now - stat.mtimeMs <= MAX_RUN_AGE_MS)
69
+ continue;
70
+ if (keepActive && entry.isDirectory() && await keepActive(entryPath))
71
+ continue;
72
+ await fs.promises.rm(entryPath, { recursive: entry.isDirectory(), force: true });
73
+ }
74
+ catch {
75
+ // Startup retention cleanup is best effort; entries that fail closed are skipped.
76
+ }
77
+ }
78
+ }
79
+ // A route is live while anything still writes into it (nested descendants can outlive a
80
+ // terminal root, and foreground roots have no async status at all) or while its root run
81
+ // reports an active state. Child writes land in the events/ and controls/ subdirs without
82
+ // bumping the route-root mtime; registry projection renames into routeRoot, which the
83
+ // caller's mtime gate already covers.
84
+ async function nestedRouteActive(routeRoot, now) {
85
+ for (const name of ["events", "controls"]) {
86
+ try {
87
+ if (now - (await fs.promises.stat(path.join(routeRoot, name))).mtimeMs <= MAX_RUN_AGE_MS)
88
+ return true;
89
+ }
90
+ catch (error) {
91
+ if (error.code !== "ENOENT")
92
+ throw error;
93
+ }
94
+ }
95
+ const metadata = await readJsonObject(path.join(routeRoot, "route.json"));
96
+ if (!metadata || !isSafeNestedPathId(metadata.rootRunId))
97
+ return false;
98
+ return activeStatus(path.join(ASYNC_DIR, metadata.rootRunId, "status.json"));
99
+ }
100
+ function pendingQuestion(runDir) {
101
+ return listRunQuestions(runDir).some((question) => question.state === "awaiting_input" || question.state === "answer_pending");
102
+ }
103
+ export async function cleanupOldRunStorage(now = Date.now()) {
104
+ for (const [dir, keepActive] of [
105
+ [ASYNC_DIR, async (entryPath) => await activeStatus(path.join(entryPath, "status.json")) || pendingQuestion(path.join(QUESTIONS_DIR, path.basename(entryPath)))],
106
+ [LEGACY_QUESTIONS_DIR, pendingQuestion],
107
+ [RESULTS_DIR, undefined],
108
+ [path.join(TEMP_ROOT_DIR, "nested-subagent-runs"), undefined],
109
+ [path.join(TEMP_ROOT_DIR, "nested-subagent-events"), (entryPath) => nestedRouteActive(entryPath, now)],
110
+ ]) {
111
+ ensureSafeTempPath(dir);
112
+ await removeOldEntries(dir, now, keepActive);
113
+ }
114
+ }
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Type definitions for the subagent extension
3
+ */
4
+ import * as os from "node:os";
5
+ import * as path from "node:path";
6
+ export const INTERCOM_DETACH_REQUEST_EVENT = "pi-intercom:detach-request";
7
+ export const INTERCOM_DETACH_RESPONSE_EVENT = "pi-intercom:detach-response";
8
+ export const SUBAGENT_ASYNC_STARTED_EVENT = "subagent:async-started";
9
+ export const SUBAGENT_ASYNC_COMPLETE_EVENT = "subagent:async-complete";
10
+ export const SUBAGENT_CONTROL_EVENT = "subagent:control-event";
11
+ export const SUBAGENT_CONTROL_INTERCOM_EVENT = "subagent:control-intercom";
12
+ export const SUBAGENT_RESULT_INTERCOM_EVENT = "subagent:result-intercom";
13
+ export const SUBAGENT_RESULT_INTERCOM_DELIVERY_EVENT = "subagent:result-intercom-delivery";
14
+ export const SUBAGENT_LIVE_INTERCOM_EVENT = "subagent:live-intercom";
15
+ export const SUBAGENT_LIVE_INTERCOM_DELIVERY_EVENT = "subagent:live-intercom-delivery";
16
+ export const SUBAGENT_INTERCOM_HEALTH_REQUEST_EVENT = "subagent:intercom-health-request";
17
+ export const SUBAGENT_INTERCOM_HEALTH_RESPONSE_EVENT = "subagent:intercom-health-response";
18
+ export const SUBAGENT_INTERCOM_IDENTITY_REQUEST_EVENT = "subagent:intercom-identity-request";
19
+ export const SUBAGENT_INTERCOM_IDENTITY_RESPONSE_EVENT = "subagent:intercom-identity-response";
20
+ // ============================================================================
21
+ // Constants
22
+ // ============================================================================
23
+ export const DEFAULT_MAX_OUTPUT = {
24
+ bytes: 200 * 1024,
25
+ lines: 5000,
26
+ };
27
+ function sanitizeTempScopeSegment(value) {
28
+ const sanitized = value
29
+ .trim()
30
+ .replace(/[^A-Za-z0-9._-]+/g, "-")
31
+ .replace(/^-+|-+$/g, "");
32
+ return sanitized || "unknown";
33
+ }
34
+ export function resolveTempScopeId() {
35
+ if (typeof process.getuid === "function")
36
+ return `uid-${process.getuid()}`;
37
+ for (const key of ["USERNAME", "USER", "LOGNAME"]) {
38
+ const value = process.env[key];
39
+ if (value)
40
+ return `user-${sanitizeTempScopeSegment(value)}`;
41
+ }
42
+ try {
43
+ const username = os.userInfo().username;
44
+ if (username)
45
+ return `user-${sanitizeTempScopeSegment(username)}`;
46
+ }
47
+ catch {
48
+ // Fall through to home-directory-based scoping.
49
+ }
50
+ const homedir = process.env.HOME;
51
+ if (homedir)
52
+ return `home-${sanitizeTempScopeSegment(homedir)}`;
53
+ try {
54
+ const fallbackHomedir = os.homedir();
55
+ if (fallbackHomedir)
56
+ return `home-${sanitizeTempScopeSegment(fallbackHomedir)}`;
57
+ }
58
+ catch {
59
+ // Fall through to the last-resort shared scope.
60
+ }
61
+ return "shared";
62
+ }
63
+ const MAX_PARALLEL = 8;
64
+ export const MAX_CONCURRENCY = 4;
65
+ export function resolveTempRootDir(configured = process.env.PI_SUBAGENT_TEMP_ROOT) {
66
+ const fallback = path.join(os.tmpdir(), `pi-subagents-${resolveTempScopeId()}`);
67
+ if (!configured?.trim())
68
+ return fallback;
69
+ const resolved = path.resolve(configured);
70
+ if (!path.basename(resolved).startsWith("pi-subagents-")) {
71
+ throw new Error("PI_SUBAGENT_TEMP_ROOT must name a dedicated 'pi-subagents-*' directory.");
72
+ }
73
+ return resolved;
74
+ }
75
+ export const TEMP_ROOT_DIR = resolveTempRootDir();
76
+ export const RESULTS_DIR = path.join(TEMP_ROOT_DIR, "async-subagent-results");
77
+ export const ASYNC_DIR = path.join(TEMP_ROOT_DIR, "async-subagent-runs");
78
+ export const RUNNER_ERROR_LOG_FILE = "runner-error.log";
79
+ export const CHAIN_RUNS_DIR = path.join(TEMP_ROOT_DIR, "chain-runs");
80
+ export const TEMP_ARTIFACTS_DIR = path.join(TEMP_ROOT_DIR, "artifacts");
81
+ export const WIDGET_KEY = "subagent-async";
82
+ export const SLASH_RESULT_TYPE = "subagent-slash-result";
83
+ export const SLASH_SUBAGENT_REQUEST_EVENT = "subagent:slash:request";
84
+ export const SLASH_SUBAGENT_STARTED_EVENT = "subagent:slash:started";
85
+ export const SLASH_SUBAGENT_RESPONSE_EVENT = "subagent:slash:response";
86
+ export const SLASH_SUBAGENT_UPDATE_EVENT = "subagent:slash:update";
87
+ export const SLASH_SUBAGENT_CANCEL_EVENT = "subagent:slash:cancel";
88
+ export const POLL_INTERVAL_MS = 1000;
89
+ export const MAX_WIDGET_JOBS = 4;
90
+ export const DEFAULT_SUBAGENT_MAX_DEPTH = 1;
91
+ export const SUBAGENT_ACTIONS = ["list", "get", "create", "update", "delete", "status", "history", "search", "interrupt", "extend", "resume", "nudge", "questions", "answer", "review", "doctor"];
92
+ export const DEFAULT_FORK_PREAMBLE = "You are a delegated subagent running from a fork of the parent session. " +
93
+ "Treat the inherited conversation as reference-only context, not a live thread to continue. " +
94
+ "Do not continue or answer prior messages as if they are waiting for a reply. " +
95
+ "Your sole job is to execute the task below and return a focused result for that task using your tools.";
96
+ function normalizeTopLevelParallelValue(value) {
97
+ const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
98
+ if (!Number.isInteger(parsed) || parsed < 1)
99
+ return undefined;
100
+ return parsed;
101
+ }
102
+ export function resolveTopLevelParallelMaxTasks(value) {
103
+ return normalizeTopLevelParallelValue(value) ?? MAX_PARALLEL;
104
+ }
105
+ export function resolveTopLevelParallelConcurrency(override, configValue) {
106
+ return normalizeTopLevelParallelValue(override)
107
+ ?? normalizeTopLevelParallelValue(configValue)
108
+ ?? MAX_CONCURRENCY;
109
+ }
110
+ export function getAsyncConfigPath(suffix) {
111
+ return path.join(TEMP_ROOT_DIR, `async-cfg-${suffix}.json`);
112
+ }
113
+ export function wrapForkTask(task, preamble) {
114
+ if (preamble === false)
115
+ return task;
116
+ const effectivePreamble = preamble ?? DEFAULT_FORK_PREAMBLE;
117
+ const wrappedPrefix = `${effectivePreamble}\n\nTask:\n`;
118
+ if (task.startsWith(wrappedPrefix))
119
+ return task;
120
+ return `${wrappedPrefix}${task}`;
121
+ }
122
+ // ============================================================================
123
+ // Recursion Depth Guard
124
+ // ============================================================================
125
+ export function normalizeMaxSubagentDepth(value) {
126
+ const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
127
+ if (!Number.isInteger(parsed) || parsed < 0)
128
+ return undefined;
129
+ return parsed;
130
+ }
131
+ export function resolveCurrentMaxSubagentDepth(configMaxDepth) {
132
+ return normalizeMaxSubagentDepth(process.env.PI_SUBAGENT_MAX_DEPTH)
133
+ ?? normalizeMaxSubagentDepth(configMaxDepth)
134
+ ?? DEFAULT_SUBAGENT_MAX_DEPTH;
135
+ }
136
+ export function resolveChildMaxSubagentDepth(parentMaxDepth, agentMaxDepth) {
137
+ const normalizedParent = normalizeMaxSubagentDepth(parentMaxDepth) ?? DEFAULT_SUBAGENT_MAX_DEPTH;
138
+ const normalizedAgent = normalizeMaxSubagentDepth(agentMaxDepth);
139
+ return normalizedAgent === undefined ? normalizedParent : Math.min(normalizedParent, normalizedAgent);
140
+ }
141
+ function parseSubagentDepth(value) {
142
+ if (value === undefined || value === null || value === "")
143
+ return 0;
144
+ const depth = Number(value);
145
+ return Number.isInteger(depth) && depth >= 0 ? depth : undefined;
146
+ }
147
+ export function checkSubagentDepth(configMaxDepth) {
148
+ const maxDepth = resolveCurrentMaxSubagentDepth(configMaxDepth);
149
+ const depth = parseSubagentDepth(process.env.PI_SUBAGENT_DEPTH);
150
+ if (depth === undefined)
151
+ return { blocked: true, depth: maxDepth, maxDepth };
152
+ return { blocked: depth >= maxDepth, depth, maxDepth };
153
+ }
154
+ export function getSubagentDepthEnv(maxDepth) {
155
+ const childMaxDepth = normalizeMaxSubagentDepth(maxDepth) ?? resolveCurrentMaxSubagentDepth();
156
+ const parentDepth = parseSubagentDepth(process.env.PI_SUBAGENT_DEPTH);
157
+ const nextDepth = parentDepth === undefined ? childMaxDepth : parentDepth + 1;
158
+ return {
159
+ PI_SUBAGENT_DEPTH: String(nextDepth),
160
+ PI_SUBAGENT_MAX_DEPTH: String(childMaxDepth),
161
+ };
162
+ }
163
+ // ============================================================================
164
+ // Utility Functions
165
+ // ============================================================================
166
+ function formatBytes(bytes) {
167
+ if (bytes < 1024)
168
+ return `${bytes}B`;
169
+ if (bytes < 1024 * 1024)
170
+ return `${(bytes / 1024).toFixed(1)}KB`;
171
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
172
+ }
173
+ export function truncateOutput(output, config, artifactPath) {
174
+ const lines = output.split("\n");
175
+ const bytes = Buffer.byteLength(output, "utf-8");
176
+ if (bytes <= config.bytes && lines.length <= config.lines) {
177
+ return { text: output, truncated: false };
178
+ }
179
+ let truncatedLines = lines;
180
+ if (lines.length > config.lines) {
181
+ truncatedLines = lines.slice(0, config.lines);
182
+ }
183
+ let result = truncatedLines.join("\n");
184
+ if (Buffer.byteLength(result, "utf-8") > config.bytes) {
185
+ let low = 0;
186
+ let high = result.length;
187
+ while (low < high) {
188
+ const mid = Math.floor((low + high + 1) / 2);
189
+ if (Buffer.byteLength(result.slice(0, mid), "utf-8") <= config.bytes) {
190
+ low = mid;
191
+ }
192
+ else {
193
+ high = mid - 1;
194
+ }
195
+ }
196
+ result = result.slice(0, low);
197
+ }
198
+ const keptLines = result.split("\n").length;
199
+ const marker = `[TRUNCATED: showing first ${keptLines} of ${lines.length} lines, ${formatBytes(Buffer.byteLength(result))} of ${formatBytes(bytes)}${artifactPath ? ` - full output at ${artifactPath}` : ""}]\n`;
200
+ return {
201
+ text: marker + result,
202
+ truncated: true,
203
+ originalBytes: bytes,
204
+ originalLines: lines.length,
205
+ artifactPath,
206
+ };
207
+ }
@@ -0,0 +1,3 @@
1
+ export function isTuiContext(ctx) {
2
+ return ctx.mode === "tui";
3
+ }
@@ -0,0 +1,334 @@
1
+ /**
2
+ * General utility functions for the subagent extension
3
+ */
4
+ import * as fs from "node:fs";
5
+ import * as os from "node:os";
6
+ import * as path from "node:path";
7
+ import { formatToolCall } from "./formatters.js";
8
+ // ============================================================================
9
+ // File System Utilities
10
+ // ============================================================================
11
+ export function getAgentDir() {
12
+ const configured = process.env.PI_CODING_AGENT_DIR;
13
+ if (configured === "~")
14
+ return os.homedir();
15
+ if (configured?.startsWith("~/"))
16
+ return path.join(os.homedir(), configured.slice(2));
17
+ return configured || path.join(os.homedir(), ".pi", "agent");
18
+ }
19
+ const statusCache = new Map();
20
+ function getErrorMessage(error) {
21
+ return error instanceof Error ? error.message : String(error);
22
+ }
23
+ export function resolveChildCwd(baseCwd, childCwd) {
24
+ if (!childCwd)
25
+ return baseCwd;
26
+ return path.isAbsolute(childCwd) ? childCwd : path.resolve(baseCwd, childCwd);
27
+ }
28
+ function isNotFoundError(error) {
29
+ return typeof error === "object"
30
+ && error !== null
31
+ && "code" in error
32
+ && error.code === "ENOENT";
33
+ }
34
+ /**
35
+ * Read async job status from disk, invalidating on replacement and in-place updates.
36
+ */
37
+ export function readStatus(asyncDir) {
38
+ const statusPath = path.join(asyncDir, "status.json");
39
+ let stat;
40
+ try {
41
+ stat = fs.statSync(statusPath, { bigint: true });
42
+ }
43
+ catch (error) {
44
+ if (isNotFoundError(error)) {
45
+ statusCache.delete(statusPath);
46
+ return null;
47
+ }
48
+ throw new Error(`Failed to inspect async status file '${statusPath}': ${getErrorMessage(error)}`, {
49
+ cause: error instanceof Error ? error : undefined,
50
+ });
51
+ }
52
+ const cached = statusCache.get(statusPath);
53
+ const version = `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
54
+ if (cached && cached.version === version) {
55
+ return cached.status;
56
+ }
57
+ let content;
58
+ try {
59
+ content = fs.readFileSync(statusPath, "utf-8");
60
+ }
61
+ catch (error) {
62
+ if (isNotFoundError(error))
63
+ return null;
64
+ throw new Error(`Failed to read async status file '${statusPath}': ${getErrorMessage(error)}`, {
65
+ cause: error instanceof Error ? error : undefined,
66
+ });
67
+ }
68
+ let status;
69
+ try {
70
+ status = JSON.parse(content);
71
+ }
72
+ catch (error) {
73
+ throw new Error(`Failed to parse async status file '${statusPath}': ${getErrorMessage(error)}`, {
74
+ cause: error instanceof Error ? error : undefined,
75
+ });
76
+ }
77
+ statusCache.set(statusPath, { version, status });
78
+ if (statusCache.size > 50) {
79
+ const firstKey = statusCache.keys().next().value;
80
+ if (firstKey)
81
+ statusCache.delete(firstKey);
82
+ }
83
+ return status;
84
+ }
85
+ /**
86
+ * Find the latest session file in a directory
87
+ */
88
+ export function findLatestSessionFile(sessionDir) {
89
+ try {
90
+ let latest;
91
+ for (const file of fs.readdirSync(sessionDir)) {
92
+ if (!file.endsWith(".jsonl"))
93
+ continue;
94
+ const filePath = path.join(sessionDir, file);
95
+ const mtime = fs.statSync(filePath).mtimeMs;
96
+ if (!latest || mtime > latest.mtime)
97
+ latest = { path: filePath, mtime };
98
+ }
99
+ return latest?.path ?? null;
100
+ }
101
+ catch {
102
+ return null;
103
+ }
104
+ }
105
+ // ============================================================================
106
+ // Message Parsing Utilities
107
+ // ============================================================================
108
+ /**
109
+ * Get the final text output from a list of messages
110
+ */
111
+ export function getFinalOutput(messages) {
112
+ for (let i = messages.length - 1; i >= 0; i--) {
113
+ const msg = messages[i];
114
+ if (msg.role === "assistant") {
115
+ const hasAssistantError = ("errorMessage" in msg && typeof msg.errorMessage === "string" && msg.errorMessage.length > 0)
116
+ || ("stopReason" in msg && msg.stopReason === "error");
117
+ if (hasAssistantError)
118
+ continue;
119
+ for (let j = msg.content.length - 1; j >= 0; j--) {
120
+ const part = msg.content[j];
121
+ if (part.type === "text" && part.text.trim().length > 0)
122
+ return part.text;
123
+ }
124
+ }
125
+ }
126
+ return "";
127
+ }
128
+ export function getSingleResultOutput(result) {
129
+ return result.finalOutput ?? getFinalOutput(result.messages ?? []);
130
+ }
131
+ export function formatResourceLimitExceeded(input) {
132
+ if (input.kind === "maxExecutionTimeMs") {
133
+ return `Resource limit exceeded for ${input.agent}: maxExecutionTimeMs ${input.limit}ms.`;
134
+ }
135
+ return `Resource limit exceeded for ${input.agent}: maxTokens ${input.limit}${input.observed !== undefined ? ` (observed ${input.observed})` : ""}.`;
136
+ }
137
+ /**
138
+ * Extract display items (text and tool calls) from messages
139
+ */
140
+ export function getDisplayItems(messages) {
141
+ if (!messages || messages.length === 0)
142
+ return [];
143
+ const items = [];
144
+ for (const msg of messages) {
145
+ if (msg.role === "assistant") {
146
+ for (const part of msg.content) {
147
+ if (part.type === "text")
148
+ items.push({ type: "text", text: part.text });
149
+ else if (part.type === "toolCall")
150
+ items.push({ type: "tool", name: part.name, args: part.arguments });
151
+ }
152
+ }
153
+ }
154
+ return items;
155
+ }
156
+ function extractToolCallSummaries(messages) {
157
+ if (!messages?.length)
158
+ return [];
159
+ const summaries = [];
160
+ for (const msg of messages) {
161
+ if (msg.role !== "assistant")
162
+ continue;
163
+ for (const part of msg.content) {
164
+ if (part.type !== "toolCall")
165
+ continue;
166
+ const args = typeof part.arguments === "object" && part.arguments !== null && !Array.isArray(part.arguments)
167
+ ? part.arguments
168
+ : {};
169
+ summaries.push({
170
+ text: formatToolCall(part.name, args),
171
+ expandedText: formatToolCall(part.name, args, true),
172
+ });
173
+ }
174
+ }
175
+ return summaries;
176
+ }
177
+ export function compactForegroundResult(result) {
178
+ if (result.progress?.status === "running")
179
+ return result;
180
+ const toolCalls = result.toolCalls?.length ? result.toolCalls : extractToolCallSummaries(result.messages);
181
+ return {
182
+ ...result,
183
+ messages: undefined,
184
+ progress: undefined,
185
+ toolCalls: toolCalls.length ? toolCalls : undefined,
186
+ };
187
+ }
188
+ /**
189
+ * Detect errors in subagent execution from messages (only errors with no subsequent success)
190
+ */
191
+ export function detectSubagentError(messages) {
192
+ let lastAssistantTextIndex = -1;
193
+ for (let i = messages.length - 1; i >= 0; i--) {
194
+ const msg = messages[i];
195
+ if (msg.role === "assistant") {
196
+ const hasText = Array.isArray(msg.content) && msg.content.some((c) => c.type === "text" && "text" in c && typeof c.text === "string" && c.text.trim().length > 0);
197
+ if (hasText) {
198
+ lastAssistantTextIndex = i;
199
+ break;
200
+ }
201
+ }
202
+ }
203
+ const scanStart = lastAssistantTextIndex >= 0 ? lastAssistantTextIndex + 1 : 0;
204
+ for (let i = messages.length - 1; i >= scanStart; i--) {
205
+ const msg = messages[i];
206
+ if (msg.role !== "toolResult")
207
+ continue;
208
+ const toolName = "toolName" in msg && typeof msg.toolName === "string" ? msg.toolName : undefined;
209
+ const isError = "isError" in msg && msg.isError === true;
210
+ const observedExit = msg.observedExitCode;
211
+ if (isError) {
212
+ const text = msg.content.find((c) => c.type === "text");
213
+ const details = text && "text" in text ? text.text : undefined;
214
+ const exitMatch = details?.match(/exit(?:ed)?\s*(?:with\s*)?(?:code|status)?\s*[:\s]?\s*(\d+)/i);
215
+ return {
216
+ hasError: true,
217
+ exitCode: observedExit ?? (exitMatch ? parseInt(exitMatch[1], 10) : 1),
218
+ errorType: toolName || "tool",
219
+ details: details?.slice(0, 200),
220
+ };
221
+ }
222
+ if (toolName !== "bash")
223
+ continue;
224
+ const text = msg.content.find((c) => c.type === "text");
225
+ if (!text || !("text" in text))
226
+ continue;
227
+ const output = text.text;
228
+ const exitMatch = output.match(/exit(?:ed)?\s*(?:with\s*)?(?:code|status)?\s*[:\s]?\s*(\d+)/i);
229
+ if (exitMatch || observedExit !== undefined) {
230
+ const code = observedExit ?? parseInt(exitMatch[1], 10);
231
+ if (code !== 0) {
232
+ return { hasError: true, exitCode: code, errorType: "bash", details: output.slice(0, 200) };
233
+ }
234
+ }
235
+ }
236
+ return { hasError: false };
237
+ }
238
+ /**
239
+ * Extract a preview of tool arguments for display
240
+ */
241
+ export function extractToolArgsPreview(args) {
242
+ const truncatePreview = (value, maxLength) => value.length > maxLength ? `${value.slice(0, maxLength - 3)}...` : value;
243
+ const stringifyPreviewValue = (value) => {
244
+ if (typeof value === "string" && value.trim().length > 0)
245
+ return value;
246
+ if (typeof value === "number" || typeof value === "boolean")
247
+ return String(value);
248
+ return undefined;
249
+ };
250
+ const previewArray = (value) => {
251
+ if (!Array.isArray(value) || value.length === 0)
252
+ return undefined;
253
+ const first = stringifyPreviewValue(value[0]);
254
+ if (!first)
255
+ return undefined;
256
+ const suffix = value.length > 1 ? ` (+${value.length - 1} more)` : "";
257
+ return `${first}${suffix}`;
258
+ };
259
+ // Handle MCP tool calls - show server/tool info
260
+ if (args.tool && typeof args.tool === "string") {
261
+ const server = args.server && typeof args.server === "string" ? `${args.server}/` : "";
262
+ const toolArgs = args.args && typeof args.args === "string" ? ` ${args.args.slice(0, 40)}` : "";
263
+ return `${server}${args.tool}${toolArgs}`;
264
+ }
265
+ const queriesPreview = previewArray(args.queries);
266
+ if (queriesPreview)
267
+ return truncatePreview(queriesPreview, 60);
268
+ if (typeof args.query === "string" && args.query.trim().length > 0)
269
+ return truncatePreview(args.query, 60);
270
+ if (typeof args.workflow === "string" && args.workflow.trim().length > 0)
271
+ return `workflow=${truncatePreview(args.workflow, 48)}`;
272
+ if (typeof args.url === "string" && args.url.trim().length > 0)
273
+ return truncatePreview(args.url, 60);
274
+ const urlsPreview = previewArray(args.urls);
275
+ if (urlsPreview)
276
+ return truncatePreview(urlsPreview, 60);
277
+ if (typeof args.prompt === "string" && args.prompt.trim().length > 0)
278
+ return truncatePreview(args.prompt, 60);
279
+ const previewKeys = ["command", "path", "file_path", "pattern", "query", "url", "task", "describe", "search"];
280
+ for (const key of previewKeys) {
281
+ if (args[key] && typeof args[key] === "string") {
282
+ const value = args[key];
283
+ return truncatePreview(value, 60);
284
+ }
285
+ }
286
+ // Fallback: show first string value found
287
+ for (const [key, value] of Object.entries(args)) {
288
+ const arrayPreview = previewArray(value);
289
+ if (arrayPreview)
290
+ return `${key}=${truncatePreview(arrayPreview, 50)}`;
291
+ if (typeof value === "string" && value.length > 0) {
292
+ const preview = truncatePreview(value, 50);
293
+ return `${key}=${preview}`;
294
+ }
295
+ }
296
+ return "";
297
+ }
298
+ /**
299
+ * Extract text content from various message content formats
300
+ */
301
+ export function extractTextFromContent(content) {
302
+ if (!content)
303
+ return "";
304
+ // Handle string content directly
305
+ if (typeof content === "string")
306
+ return content;
307
+ // Handle array content
308
+ if (!Array.isArray(content))
309
+ return "";
310
+ const texts = [];
311
+ for (const part of content) {
312
+ if (part && typeof part === "object") {
313
+ // Handle { type: "text", text: "..." }
314
+ if ("type" in part && part.type === "text" && "text" in part) {
315
+ texts.push(String(part.text));
316
+ }
317
+ // Handle { type: "tool_result", content: "..." }
318
+ else if ("type" in part && part.type === "tool_result" && "content" in part) {
319
+ const inner = extractTextFromContent(part.content);
320
+ if (inner)
321
+ texts.push(inner);
322
+ }
323
+ // Handle { text: "..." } without type
324
+ else if ("text" in part) {
325
+ texts.push(String(part.text));
326
+ }
327
+ }
328
+ }
329
+ return texts.join("\n");
330
+ }
331
+ // ============================================================================
332
+ // Concurrency Utilities
333
+ // ============================================================================
334
+ export { mapConcurrent } from "../runs/shared/parallel-utils.js";