@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,123 @@
1
+ import { loadConfig } from "../extension/config.ts";
2
+ import type { AgentProcessExit, ActivityState, AsyncJobStep, ManagementAction, ManagementControl, ManagementRunState, SubagentLiveIntercomHealth } from "./types.ts";
3
+
4
+ export function formatAgentProcessExit(exit: AgentProcessExit | undefined): string {
5
+ return exit ? `Agent process exited (${exit.signal ? `signal ${exit.signal}` : `code ${exit.code ?? "unrecorded"}`}). Command and descendant exit are not implied.`
6
+ : "Agent process exit not recorded. Command exit is unconfirmed.";
7
+ }
8
+
9
+ export function formatRunAction(action: ManagementAction | "questions" | "answer", id: string | undefined, fields: Record<string, string | number | boolean> = {}, childSafe = false): string {
10
+ const legacyChild = childSafe && loadConfig().compactChildTools === false;
11
+ const parentAction = { status: "inspect", resume: "continue", interrupt: "stop" };
12
+ const name = legacyChild ? action : parentAction[action as keyof typeof parentAction] ?? action;
13
+ const args = Object.entries({ action: name, ...(id !== undefined ? { id } : {}), ...fields }).map(([key, value]) => `${key}: ${JSON.stringify(value)}`).join(", ");
14
+ const call = `${legacyChild || action === "extend" ? "subagent" : "agent_runs"}({ ${args} })`;
15
+ return action === "extend" && !legacyChild ? `load_subagent({}), then ${call}` : call;
16
+ }
17
+
18
+ export function buildManagementControl(input: {
19
+ state: ManagementRunState;
20
+ runId: string;
21
+ index?: number;
22
+ intercomTarget?: string;
23
+ canExtend?: boolean;
24
+ canResume?: boolean;
25
+ canNudge?: boolean;
26
+ canInterrupt?: boolean;
27
+ canReview?: boolean;
28
+ unavailableActions?: Partial<Record<ManagementAction, string>>;
29
+ revivedFromRunId?: string;
30
+ }): ManagementControl {
31
+ const capabilities: ManagementAction[] = ["status"];
32
+ if (input.state === "live") {
33
+ if (input.canNudge) capabilities.push("nudge");
34
+ if (input.canResume) capabilities.push("resume");
35
+ if (input.canInterrupt) capabilities.push("interrupt");
36
+ } else if (input.canResume) capabilities.push("resume");
37
+ if (input.state === "live" && input.canExtend) capabilities.push("extend");
38
+ if (input.canReview) capabilities.push("review");
39
+ return {
40
+ state: input.state,
41
+ runId: input.runId,
42
+ capabilities,
43
+ nextActions: capabilities.map((action) => ({
44
+ action,
45
+ runId: input.runId,
46
+ ...(input.index !== undefined && (action === "nudge" || action === "resume") ? { index: input.index } : {}),
47
+ ...(input.intercomTarget && (action === "nudge" || action === "resume") ? { intercomTarget: input.intercomTarget } : {}),
48
+ })),
49
+ ...(input.unavailableActions ? { unavailableActions: input.unavailableActions } : {}),
50
+ ...(input.revivedFromRunId ? { revivedFromRunId: input.revivedFromRunId, pendingReplyContextValid: false } : {}),
51
+ };
52
+ }
53
+
54
+ type StepStatusLike = Pick<AsyncJobStep, "status">;
55
+
56
+ function formatActivityAge(ms: number): string {
57
+ if (ms < 1000) return "now";
58
+ if (ms < 60000) return `${Math.floor(ms / 1000)}s`;
59
+ return `${Math.floor(ms / 60000)}m`;
60
+ }
61
+
62
+ export function formatActivityLabel(lastActivityAt: number | undefined, activityState?: ActivityState, now = Date.now()): string | undefined {
63
+ if (lastActivityAt === undefined) {
64
+ if (activityState === "needs_attention") return "needs attention";
65
+ return undefined;
66
+ }
67
+ const age = formatActivityAge(Math.max(0, now - lastActivityAt));
68
+ if (activityState === "needs_attention") return `no activity for ${age}`;
69
+ return age === "now" ? "active now" : `active ${age} ago`;
70
+ }
71
+
72
+ function isCompletedStepStatus(status: AsyncJobStep["status"]): boolean {
73
+ return status === "complete" || status === "completed";
74
+ }
75
+
76
+ export function aggregateStepStatus(steps: StepStatusLike[]): AsyncJobStep["status"] {
77
+ if (steps.some((step) => step.status === "running")) return "running";
78
+ if (steps.some((step) => step.status === "failed")) return "failed";
79
+ if (steps.some((step) => step.status === "blocked")) return "blocked";
80
+ if (steps.some((step) => step.status === "paused")) return "paused";
81
+ if (steps.length > 0 && steps.every((step) => isCompletedStepStatus(step.status))) return "complete";
82
+ return "pending";
83
+ }
84
+
85
+ export function formatAgentRunningLabel(count: number): string {
86
+ return count === 1 ? "1 agent running" : `${count} agents running`;
87
+ }
88
+
89
+ export function formatLiveIntercomActionLines(input: {
90
+ runId: string;
91
+ target: string;
92
+ index?: number;
93
+ health?: SubagentLiveIntercomHealth;
94
+ indent?: string;
95
+ childSafe?: boolean;
96
+ }): string[] {
97
+ const fields = { ...(input.index !== undefined ? { index: input.index } : {}), message: "What are you blocked on?" };
98
+ const healthText = !input.health
99
+ ? "unknown"
100
+ : input.health.status === "registered"
101
+ ? `registered${input.health.sessionStatus ? `, ${input.health.sessionStatus}` : ""}${input.health.acceptsAsks !== undefined ? `, accepts_asks:${input.health.acceptsAsks}` : ""}${input.health.pendingAsks !== undefined ? `, pending_asks:${input.health.pendingAsks}` : ""}`
102
+ : input.health.status.replace(/_/g, " ");
103
+ const indent = input.indent ?? "";
104
+ return [
105
+ `${indent}Intercom: ${healthText} (${input.target})`,
106
+ `${indent}Nudge (preferred live coordination): ${formatRunAction("nudge", input.runId, fields, input.childSafe)}`,
107
+ `${indent}Ask (blocking wait only; parent must remain alive): intercom({ action: "ask", to: "${input.target}", delivery: "steer", message: "What are you blocked on?" })`,
108
+ ];
109
+ }
110
+
111
+ export function formatParallelOutcome(steps: StepStatusLike[], total: number, options: { showRunning?: boolean } = {}): string {
112
+ const running = steps.filter((step) => step.status === "running").length;
113
+ const succeeded = steps.filter((step) => isCompletedStepStatus(step.status)).length;
114
+ const failed = steps.filter((step) => step.status === "failed").length;
115
+ const paused = steps.filter((step) => step.status === "paused").length;
116
+ const blocked = steps.filter((step) => step.status === "blocked").length;
117
+ const parts = [`${succeeded}/${total} succeeded`];
118
+ if (options.showRunning !== false && running > 0) parts.unshift(formatAgentRunningLabel(running));
119
+ if (failed > 0) parts.push(`${failed} failed`);
120
+ if (paused > 0) parts.push(`${paused} paused`);
121
+ if (blocked > 0) parts.push(`${blocked} need human action`);
122
+ return parts.join(" · ");
123
+ }
@@ -0,0 +1,107 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { isSafeNestedPathId } from "../runs/shared/nested-path.ts";
4
+ import { ASYNC_DIR, RESULTS_DIR, TEMP_ROOT_DIR } from "./types.ts";
5
+ import { listRunQuestions, LEGACY_QUESTIONS_DIR, QUESTIONS_DIR } from "../runs/shared/supervisor-questions.ts";
6
+
7
+ const MAX_RUN_AGE_MS = 7 * 24 * 60 * 60 * 1000;
8
+
9
+ export function ensureTempRoot(): void {
10
+ fs.mkdirSync(TEMP_ROOT_DIR, { recursive: true, mode: 0o700 });
11
+ const stat = fs.lstatSync(TEMP_ROOT_DIR);
12
+ if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error(`Unsafe pi-subagents temp root: ${TEMP_ROOT_DIR}`);
13
+ const uid = process.getuid?.();
14
+ if (uid !== undefined && stat.uid !== uid) throw new Error(`pi-subagents temp root is owned by another user: ${TEMP_ROOT_DIR}`);
15
+ fs.chmodSync(TEMP_ROOT_DIR, 0o700);
16
+ }
17
+
18
+ export function ensureSafeTempPath(candidate: string): void {
19
+ const root = path.resolve(TEMP_ROOT_DIR);
20
+ const resolved = path.resolve(candidate);
21
+ const relative = path.relative(root, resolved);
22
+ if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) 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()) throw new Error(`Unsafe symlink in pi-subagents temp path: ${current}`);
30
+ } catch (error) {
31
+ if ((error as NodeJS.ErrnoException).code === "ENOENT") return;
32
+ throw error;
33
+ }
34
+ }
35
+ }
36
+
37
+ // Missing, malformed, or non-object JSON means the entry is incomplete garbage and safe
38
+ // to remove. Operational read failures throw so callers fail closed instead of deleting.
39
+ async function readJsonObject(file: string): Promise<Record<string, unknown> | undefined> {
40
+ try {
41
+ const parsed: unknown = JSON.parse(await fs.promises.readFile(file, "utf-8"));
42
+ return parsed !== null && typeof parsed === "object" ? parsed as Record<string, unknown> : undefined;
43
+ } catch (error) {
44
+ if ((error as NodeJS.ErrnoException).code === "ENOENT" || error instanceof SyntaxError) return undefined;
45
+ throw error;
46
+ }
47
+ }
48
+
49
+ async function activeStatus(statusFile: string): Promise<boolean> {
50
+ const status = await readJsonObject(statusFile);
51
+ return status?.state === "running" || status?.state === "queued";
52
+ }
53
+
54
+ async function removeOldEntries(root: string, now: number, keepActive?: (entryPath: string) => boolean | Promise<boolean>): Promise<void> {
55
+ let entries: fs.Dirent[];
56
+ try {
57
+ entries = await fs.promises.readdir(root, { withFileTypes: true });
58
+ } catch {
59
+ return;
60
+ }
61
+ for (const entry of entries) {
62
+ const entryPath = path.join(root, entry.name);
63
+ try {
64
+ const stat = await fs.promises.lstat(entryPath);
65
+ if (now - stat.mtimeMs <= MAX_RUN_AGE_MS) continue;
66
+ if (keepActive && entry.isDirectory() && await keepActive(entryPath)) continue;
67
+ await fs.promises.rm(entryPath, { recursive: entry.isDirectory(), force: true });
68
+ } catch {
69
+ // Startup retention cleanup is best effort; entries that fail closed are skipped.
70
+ }
71
+ }
72
+ }
73
+
74
+ // A route is live while anything still writes into it (nested descendants can outlive a
75
+ // terminal root, and foreground roots have no async status at all) or while its root run
76
+ // reports an active state. Child writes land in the events/ and controls/ subdirs without
77
+ // bumping the route-root mtime; registry projection renames into routeRoot, which the
78
+ // caller's mtime gate already covers.
79
+ async function nestedRouteActive(routeRoot: string, now: number): Promise<boolean> {
80
+ for (const name of ["events", "controls"]) {
81
+ try {
82
+ if (now - (await fs.promises.stat(path.join(routeRoot, name))).mtimeMs <= MAX_RUN_AGE_MS) return true;
83
+ } catch (error) {
84
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
85
+ }
86
+ }
87
+ const metadata = await readJsonObject(path.join(routeRoot, "route.json"));
88
+ if (!metadata || !isSafeNestedPathId(metadata.rootRunId)) return false;
89
+ return activeStatus(path.join(ASYNC_DIR, metadata.rootRunId, "status.json"));
90
+ }
91
+
92
+ function pendingQuestion(runDir: string): boolean {
93
+ return listRunQuestions(runDir).some((question) => question.state === "awaiting_input" || question.state === "answer_pending");
94
+ }
95
+
96
+ export async function cleanupOldRunStorage(now = Date.now()): Promise<void> {
97
+ for (const [dir, keepActive] of [
98
+ [ASYNC_DIR, async (entryPath: string) => await activeStatus(path.join(entryPath, "status.json")) || pendingQuestion(path.join(QUESTIONS_DIR, path.basename(entryPath)))],
99
+ [LEGACY_QUESTIONS_DIR, pendingQuestion],
100
+ [RESULTS_DIR, undefined],
101
+ [path.join(TEMP_ROOT_DIR, "nested-subagent-runs"), undefined],
102
+ [path.join(TEMP_ROOT_DIR, "nested-subagent-events"), (entryPath: string) => nestedRouteActive(entryPath, now)],
103
+ ] as const) {
104
+ ensureSafeTempPath(dir);
105
+ await removeOldEntries(dir, now, keepActive);
106
+ }
107
+ }