@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,197 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { readLinuxProcess } from "../pi-intercom/broker/pid.js";
3
+ /** Capture the detached group leader's birth identity; Pi may replace its command-line title. */
4
+ export function readChildProcessIdentity(pid) {
5
+ if (!Number.isSafeInteger(pid) || pid <= 0)
6
+ return undefined;
7
+ if (process.platform === "linux")
8
+ return readLinuxProcess(pid)?.identity;
9
+ try {
10
+ const identity = execFileSync("ps", ["-p", String(pid), "-o", "lstart=", "-o", "pgid="], {
11
+ encoding: "utf8", timeout: 1000, stdio: ["ignore", "pipe", "ignore"],
12
+ env: { ...process.env, LC_ALL: "C" },
13
+ }).trim().replace(/\s+/g, " ");
14
+ return Number(identity.split(" ").at(-1)) === pid ? identity : undefined;
15
+ }
16
+ catch {
17
+ return undefined;
18
+ }
19
+ }
20
+ export function trySignalChild(child, signal) {
21
+ try {
22
+ return child.kill(signal);
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ }
28
+ export function isChildTreeAlive(child) {
29
+ if (child.pid) {
30
+ try {
31
+ process.kill(-child.pid, 0);
32
+ return true;
33
+ }
34
+ catch (error) {
35
+ if (error.code === "EPERM")
36
+ return true;
37
+ }
38
+ }
39
+ try {
40
+ return child.kill(0);
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ }
46
+ /** Signal the detached child's process group, falling back if it is not a group leader. */
47
+ export function trySignalChildTree(child, signal) {
48
+ if (child.pid) {
49
+ try {
50
+ process.kill(-child.pid, signal);
51
+ return true;
52
+ }
53
+ catch {
54
+ // Fall back when the child is not a process-group leader.
55
+ }
56
+ }
57
+ return trySignalChild(child, signal);
58
+ }
59
+ /** One lifetime for the owned process group, including descendants holding its pipes open. */
60
+ export function attachChildProcessLifecycle(child) {
61
+ let closed = false;
62
+ let exited = false;
63
+ let stopping = false;
64
+ let settledCleanup = false;
65
+ let agentProcessExit;
66
+ let drainTimer;
67
+ let escalationTimer;
68
+ const clearDrain = () => {
69
+ clearTimeout(drainTimer);
70
+ drainTimer = undefined;
71
+ };
72
+ const clearTimers = () => {
73
+ clearDrain();
74
+ clearTimeout(escalationTimer);
75
+ escalationTimer = undefined;
76
+ };
77
+ const signalTree = () => {
78
+ // Pi print mode uses SIGTERM to dispose its own detached tool processes.
79
+ const sent = trySignalChildTree(child, "SIGTERM");
80
+ escalationTimer = setTimeout(() => {
81
+ if (isChildTreeAlive(child))
82
+ trySignalChildTree(child, "SIGKILL");
83
+ }, 3000);
84
+ escalationTimer.unref();
85
+ return sent;
86
+ };
87
+ const terminate = () => {
88
+ // Cancellation overrides a pending or already-started successful settlement cleanup.
89
+ settledCleanup = false;
90
+ clearDrain();
91
+ if (closed || stopping)
92
+ return;
93
+ stopping = true;
94
+ signalTree();
95
+ };
96
+ const clearStdioGuard = attachPostExitStdioGuard(child, { idleMs: 2000, hardMs: 8000 });
97
+ child.on("exit", (code, signal) => {
98
+ exited = true;
99
+ agentProcessExit = { pid: child.pid, code, signal, at: Date.now() };
100
+ clearTimers();
101
+ // The leader may exit before resistant descendants release inherited stdio.
102
+ trySignalChildTree(child, "SIGKILL");
103
+ });
104
+ const dispose = () => {
105
+ closed = true;
106
+ clearTimers();
107
+ clearStdioGuard();
108
+ };
109
+ child.on("close", dispose);
110
+ child.on("error", dispose);
111
+ return {
112
+ terminate,
113
+ get stopping() { return stopping; },
114
+ get agentProcessExit() { return agentProcessExit; },
115
+ get settledCleanup() { return settledCleanup; },
116
+ observeEvent(type) {
117
+ if (closed || exited || stopping)
118
+ return;
119
+ if (type === "agent_settled") {
120
+ if (drainTimer)
121
+ return;
122
+ drainTimer = setTimeout(() => {
123
+ drainTimer = undefined;
124
+ stopping = true;
125
+ settledCleanup = signalTree();
126
+ }, 1000);
127
+ drainTimer.unref();
128
+ }
129
+ else if (type === "agent_start" || type === "turn_start" || type?.startsWith("message_") || type?.startsWith("tool_execution_")) {
130
+ clearDrain();
131
+ }
132
+ },
133
+ };
134
+ }
135
+ export function attachPostExitStdioGuard(child, options) {
136
+ const { idleMs, hardMs } = options;
137
+ let exited = false;
138
+ let stdoutEnded = false;
139
+ let stderrEnded = false;
140
+ let idleTimer;
141
+ let hardTimer;
142
+ const destroyUnendedStdio = () => {
143
+ if (!stdoutEnded) {
144
+ try {
145
+ child.stdout?.destroy();
146
+ }
147
+ catch { }
148
+ }
149
+ if (!stderrEnded) {
150
+ try {
151
+ child.stderr?.destroy();
152
+ }
153
+ catch { }
154
+ }
155
+ };
156
+ const clearTimers = () => {
157
+ if (idleTimer) {
158
+ clearTimeout(idleTimer);
159
+ idleTimer = undefined;
160
+ }
161
+ if (hardTimer) {
162
+ clearTimeout(hardTimer);
163
+ hardTimer = undefined;
164
+ }
165
+ };
166
+ const armIdleTimer = () => {
167
+ if (!exited)
168
+ return;
169
+ if (idleTimer)
170
+ clearTimeout(idleTimer);
171
+ idleTimer = setTimeout(destroyUnendedStdio, idleMs);
172
+ idleTimer.unref?.();
173
+ };
174
+ child.stdout?.on("data", armIdleTimer);
175
+ child.stderr?.on("data", armIdleTimer);
176
+ child.stdout?.on("end", () => {
177
+ stdoutEnded = true;
178
+ if (stdoutEnded && stderrEnded)
179
+ clearTimers();
180
+ });
181
+ child.stderr?.on("end", () => {
182
+ stderrEnded = true;
183
+ if (stdoutEnded && stderrEnded)
184
+ clearTimers();
185
+ });
186
+ child.on("exit", () => {
187
+ exited = true;
188
+ armIdleTimer();
189
+ if (hardTimer)
190
+ return;
191
+ hardTimer = setTimeout(destroyUnendedStdio, hardMs);
192
+ hardTimer.unref?.();
193
+ });
194
+ child.on("close", clearTimers);
195
+ child.on("error", clearTimers);
196
+ return clearTimers;
197
+ }
@@ -0,0 +1,8 @@
1
+ export function setPromptSection(options, name, content) {
2
+ options.sections[name] = content;
3
+ // Pi projects an earlier exact override instead of sections. Preserve its content;
4
+ // this compatibility path replaces the leading prompt and cannot preserve its cache prefix.
5
+ if (options.forceSystemPrompt !== undefined) {
6
+ options.forceSystemPrompt += `\n\n<${name}>\n${content}\n</${name}>`;
7
+ }
8
+ }
@@ -0,0 +1,52 @@
1
+ /** Follow append-only parent links; rescan when entries were replaced or appended off the active branch. */
2
+ export class SessionEntryCursor {
3
+ manager;
4
+ sessionId;
5
+ file;
6
+ known = new Map();
7
+ anchor;
8
+ reset() {
9
+ this.manager = undefined;
10
+ this.known.clear();
11
+ this.anchor = undefined;
12
+ }
13
+ read(manager) {
14
+ const sessionId = manager.getSessionId(), file = manager.getSessionFile?.(), count = manager.getEntryCount?.();
15
+ let reset = this.manager !== manager || this.sessionId !== sessionId || this.file !== file
16
+ || count !== undefined && count < this.known.size
17
+ || Boolean(this.anchor && manager.getEntry && manager.getEntry(this.anchor.id) !== this.known.get(this.anchor.id));
18
+ let entries;
19
+ if (!reset && count !== undefined && manager.getEntry && manager.getLeafId) {
20
+ const appended = [], visited = new Set();
21
+ let id = manager.getLeafId();
22
+ while (id && !this.known.has(id) && !visited.has(id)) {
23
+ visited.add(id);
24
+ const entry = manager.getEntry(id);
25
+ if (!entry)
26
+ break;
27
+ appended.push(entry);
28
+ id = entry.parentId;
29
+ }
30
+ if (count === this.known.size + appended.length && (!id || this.known.has(id)))
31
+ entries = appended.reverse();
32
+ }
33
+ if (!entries) {
34
+ const all = manager.getEntries();
35
+ entries = all.filter((entry) => !this.known.has(entry.id));
36
+ if (all.length !== this.known.size + entries.length
37
+ || all.some((entry) => this.known.has(entry.id) && this.known.get(entry.id) !== entry))
38
+ reset = true;
39
+ if (reset) {
40
+ this.known.clear();
41
+ entries = all;
42
+ }
43
+ }
44
+ for (const entry of entries)
45
+ this.known.set(entry.id, entry);
46
+ this.anchor = entries.at(-1) ?? (reset ? undefined : this.anchor);
47
+ this.manager = manager;
48
+ this.sessionId = sessionId;
49
+ this.file = file;
50
+ return { entries, reset };
51
+ }
52
+ }
@@ -0,0 +1,14 @@
1
+ // Only delegation supplies this identity; Pi's ordinary fork/clone ancestry does not.
2
+ export function resolveRootSessionId(sessionManager, env = process.env) {
3
+ const rootId = env.PI_SUBAGENT_CHILD === "1" ? env.PI_SUBAGENT_ROOT_SESSION_ID : undefined;
4
+ const sessionId = rootId || sessionManager.getSessionId();
5
+ if (!sessionId)
6
+ throw new Error("Root session identity is unavailable.");
7
+ return sessionId;
8
+ }
9
+ export function resolveCurrentSessionId(sessionManager) {
10
+ const sessionId = sessionManager.getSessionId();
11
+ if (!sessionId)
12
+ throw new Error("Current session identity is unavailable.");
13
+ return sessionId;
14
+ }
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Chain behavior, template resolution, and directory management
3
+ */
4
+ import * as fs from "node:fs";
5
+ import * as path from "node:path";
6
+ import { normalizeSkillInput } from "../agents/skills.js";
7
+ import { CHAIN_RUNS_DIR } from "./types.js";
8
+ import { ensureSafeTempPath, ensureTempRoot } from "./temp-root.js";
9
+ const CHAIN_DIR_MAX_AGE_MS = 24 * 60 * 60 * 1000; // 24 hours
10
+ const INITIAL_PROGRESS_CONTENT = "# Progress\n\n## Status\nIn Progress\n\n## Tasks\n\n## Files Changed\n\n## Notes\n";
11
+ function normalizeOutputOverride(output) {
12
+ return output === "false" ? false : output;
13
+ }
14
+ // =============================================================================
15
+ // Type Guards
16
+ // =============================================================================
17
+ export function isParallelStep(step) {
18
+ return "parallel" in step && Array.isArray(step.parallel);
19
+ }
20
+ export function isDynamicParallelStep(step) {
21
+ return "expand" in step && "collect" in step && "parallel" in step && !Array.isArray(step.parallel);
22
+ }
23
+ /** Get all agent names in a step (single for sequential, multiple for parallel) */
24
+ export function getStepAgents(step) {
25
+ if (isParallelStep(step)) {
26
+ return step.parallel.map((t) => t.agent);
27
+ }
28
+ if (isDynamicParallelStep(step)) {
29
+ return [step.parallel.agent];
30
+ }
31
+ return [step.agent];
32
+ }
33
+ export function collectInvocationAgentNames(params) {
34
+ const names = [];
35
+ if (params.agent)
36
+ names.push(params.agent);
37
+ for (const task of params.tasks ?? [])
38
+ names.push(task.agent);
39
+ for (const step of params.chain ?? [])
40
+ names.push(...getStepAgents(step));
41
+ return names;
42
+ }
43
+ // =============================================================================
44
+ // Chain Directory Management
45
+ // =============================================================================
46
+ export function createChainDir(runId, baseDir, cwd = process.cwd()) {
47
+ if (!baseDir)
48
+ ensureTempRoot();
49
+ const chainDir = path.join(baseDir ? path.resolve(cwd, baseDir) : CHAIN_RUNS_DIR, runId);
50
+ if (!baseDir)
51
+ ensureSafeTempPath(chainDir);
52
+ fs.mkdirSync(chainDir, { recursive: true });
53
+ return chainDir;
54
+ }
55
+ export function removeChainDir(chainDir) {
56
+ try {
57
+ fs.rmSync(chainDir, { recursive: true });
58
+ }
59
+ catch {
60
+ // Chain cleanup is best-effort. Runs can already have cleaned their temp dir.
61
+ }
62
+ }
63
+ export async function cleanupOldChainDirs() {
64
+ ensureSafeTempPath(CHAIN_RUNS_DIR);
65
+ const now = Date.now();
66
+ let dirs;
67
+ try {
68
+ dirs = await fs.promises.readdir(CHAIN_RUNS_DIR);
69
+ }
70
+ catch {
71
+ // Startup cleanup is best-effort. If the scoped temp root is unreadable,
72
+ // skip cleanup instead of failing extension startup.
73
+ return;
74
+ }
75
+ for (const dir of dirs) {
76
+ try {
77
+ const dirPath = path.join(CHAIN_RUNS_DIR, dir);
78
+ const stat = await fs.promises.lstat(dirPath);
79
+ if (stat.isDirectory() && now - stat.mtimeMs > CHAIN_DIR_MAX_AGE_MS) {
80
+ await fs.promises.rm(dirPath, { recursive: true });
81
+ }
82
+ }
83
+ catch {
84
+ // Skip directories that can't be processed; continue with others
85
+ }
86
+ }
87
+ }
88
+ /**
89
+ * Resolve templates for a chain with parallel step support.
90
+ * Returns string for sequential steps, string[] for parallel steps.
91
+ */
92
+ export function resolveChainTemplates(steps) {
93
+ return steps.map((step, i) => {
94
+ if (isParallelStep(step)) {
95
+ // Parallel step: resolve each task's template
96
+ return step.parallel.map((task) => {
97
+ if (task.task)
98
+ return task.task;
99
+ // Default for parallel tasks is {previous}
100
+ return "{previous}";
101
+ });
102
+ }
103
+ if (isDynamicParallelStep(step)) {
104
+ return step.parallel.task ?? "{previous}";
105
+ }
106
+ // Sequential step: existing logic
107
+ const seq = step;
108
+ if (seq.task)
109
+ return seq.task;
110
+ // Default: first step uses {task}, others use {previous}
111
+ return i === 0 ? "{task}" : "{previous}";
112
+ });
113
+ }
114
+ // =============================================================================
115
+ // Behavior Resolution
116
+ // =============================================================================
117
+ /**
118
+ * Resolve effective chain behavior per step.
119
+ * Priority: step override > agent frontmatter > false (disabled)
120
+ */
121
+ export function resolveStepBehavior(agentConfig, stepOverrides, chainSkills) {
122
+ // Output: step override > frontmatter > false (no output)
123
+ const stepOutput = normalizeOutputOverride(stepOverrides.output);
124
+ const output = stepOutput !== undefined
125
+ ? stepOutput
126
+ : normalizeOutputOverride(agentConfig.output) ?? false;
127
+ // Reads: step override > frontmatter defaultReads > false (no reads)
128
+ const reads = stepOverrides.reads !== undefined
129
+ ? stepOverrides.reads
130
+ : agentConfig.defaultReads ?? false;
131
+ // Progress: step override > frontmatter defaultProgress > false
132
+ const progress = stepOverrides.progress !== undefined
133
+ ? stepOverrides.progress
134
+ : agentConfig.defaultProgress ?? false;
135
+ let skills;
136
+ if (chainSkills === false || stepOverrides.skills === false) {
137
+ skills = false;
138
+ }
139
+ else if (stepOverrides.skills !== undefined) {
140
+ skills = [...stepOverrides.skills];
141
+ if (chainSkills && chainSkills.length > 0) {
142
+ skills = [...new Set([...skills, ...chainSkills])];
143
+ }
144
+ }
145
+ else {
146
+ skills = agentConfig.skills ? [...agentConfig.skills] : [];
147
+ if (chainSkills && chainSkills.length > 0) {
148
+ skills = [...new Set([...skills, ...chainSkills])];
149
+ }
150
+ }
151
+ const outputMode = stepOverrides.outputMode ?? "inline";
152
+ const model = stepOverrides.model ?? agentConfig.model;
153
+ return { output, outputMode, reads, progress, skills, model };
154
+ }
155
+ export function resolveTaskTextForFileUpdatePolicy(task, originalTask) {
156
+ if (!task)
157
+ return originalTask;
158
+ return originalTask ? task.replaceAll("{task}", () => originalTask) : task;
159
+ }
160
+ export function taskDisallowsFileUpdates(task) {
161
+ if (!task)
162
+ return false;
163
+ return /\breview[- ]only\b/i.test(task)
164
+ || /\bread[- ]only\s+(?:review|audit|inspection|pass)\b/i.test(task)
165
+ || /\b(?:no|without)\s+(?:file\s+)?edits?\b/i.test(task)
166
+ || /\b(?:do not|don't|must not)\s+(?:edit|modify|write|touch)\b/i.test(task)
167
+ || /\bleave\s+files?\s+unchanged\b/i.test(task);
168
+ }
169
+ export function suppressProgressForReadOnlyTask(behavior, task, originalTask) {
170
+ const policyTask = resolveTaskTextForFileUpdatePolicy(task, originalTask);
171
+ return behavior.progress && taskDisallowsFileUpdates(policyTask) ? { ...behavior, progress: false } : behavior;
172
+ }
173
+ // =============================================================================
174
+ // Chain Instruction Injection
175
+ // =============================================================================
176
+ /**
177
+ * Resolve a file path: absolute paths pass through, relative paths get chainDir prepended.
178
+ */
179
+ function resolveChainPath(filePath, chainDir) {
180
+ return path.isAbsolute(filePath) ? filePath : path.join(chainDir, filePath);
181
+ }
182
+ /**
183
+ * Build chain instructions from resolved behavior.
184
+ * These are appended to the task to tell the agent what to read/write.
185
+ */
186
+ export function writeInitialProgressFile(progressDir) {
187
+ fs.mkdirSync(progressDir, { recursive: true });
188
+ try {
189
+ fs.writeFileSync(path.join(progressDir, "progress.md"), INITIAL_PROGRESS_CONTENT, { encoding: "utf-8", flag: "wx" });
190
+ }
191
+ catch (error) {
192
+ if (error.code !== "EEXIST")
193
+ throw error;
194
+ }
195
+ }
196
+ export function buildChainInstructions(behavior, chainDir, isFirstProgressAgent) {
197
+ const prefixParts = [];
198
+ const suffixParts = [];
199
+ // READS - prepend to override any hardcoded filenames in task text
200
+ if (behavior.reads && behavior.reads.length > 0) {
201
+ const files = behavior.reads.map((f) => resolveChainPath(f, chainDir));
202
+ prefixParts.push(`[Read from: ${files.join(", ")}]`);
203
+ }
204
+ // OUTPUT - prepend so agent knows where to write
205
+ if (behavior.output) {
206
+ const outputPath = resolveChainPath(behavior.output, chainDir);
207
+ prefixParts.push(`[Write to: ${outputPath}]`);
208
+ }
209
+ // Progress instructions in suffix (less critical)
210
+ if (behavior.progress) {
211
+ const progressPath = path.join(chainDir, "progress.md");
212
+ if (isFirstProgressAgent) {
213
+ suffixParts.push(`Create and maintain progress at: ${progressPath}`);
214
+ }
215
+ else {
216
+ suffixParts.push(`Update progress at: ${progressPath}`);
217
+ }
218
+ }
219
+ const prefix = prefixParts.length > 0
220
+ ? prefixParts.join("\n") + "\n\n"
221
+ : "";
222
+ const suffix = suffixParts.length > 0
223
+ ? "\n\n---\n" + suffixParts.join("\n")
224
+ : "";
225
+ return { prefix, suffix };
226
+ }
227
+ // =============================================================================
228
+ // Parallel Step Support
229
+ // =============================================================================
230
+ /**
231
+ * Resolve behaviors for all tasks in a parallel step.
232
+ * Creates namespaced output paths to avoid collisions.
233
+ */
234
+ export function resolveParallelBehaviors(tasks, agentConfigs, stepIndex, chainSkills) {
235
+ return tasks.map((task, taskIndex) => {
236
+ const config = agentConfigs.find((a) => a.name === task.agent);
237
+ if (!config) {
238
+ throw new Error(`Unknown agent: ${task.agent}`);
239
+ }
240
+ const behavior = resolveStepBehavior(config, {
241
+ output: task.output,
242
+ outputMode: task.outputMode,
243
+ reads: task.reads,
244
+ progress: task.progress,
245
+ skills: normalizeSkillInput(task.skill),
246
+ model: task.model,
247
+ }, chainSkills);
248
+ return { ...behavior, output: namespaceParallelOutput(behavior.output, task.agent, stepIndex, taskIndex) };
249
+ });
250
+ }
251
+ export function namespaceParallelOutput(output, agent, stepIndex, taskIndex) {
252
+ return output ? path.isAbsolute(output) ? output : path.join(`parallel-${stepIndex}`, `${taskIndex}-${agent}`, output) : false;
253
+ }
254
+ export { aggregateParallelOutputs } from "../runs/shared/parallel-utils.js";
@@ -0,0 +1,111 @@
1
+ import { loadConfig } from "../extension/config.js";
2
+ export function formatAgentProcessExit(exit) {
3
+ return exit ? `Agent process exited (${exit.signal ? `signal ${exit.signal}` : `code ${exit.code ?? "unrecorded"}`}). Command and descendant exit are not implied.`
4
+ : "Agent process exit not recorded. Command exit is unconfirmed.";
5
+ }
6
+ export function formatRunAction(action, id, fields = {}, childSafe = false) {
7
+ const legacyChild = childSafe && loadConfig().compactChildTools === false;
8
+ const parentAction = { status: "inspect", resume: "continue", interrupt: "stop" };
9
+ const name = legacyChild ? action : parentAction[action] ?? action;
10
+ const args = Object.entries({ action: name, ...(id !== undefined ? { id } : {}), ...fields }).map(([key, value]) => `${key}: ${JSON.stringify(value)}`).join(", ");
11
+ const call = `${legacyChild || action === "extend" ? "subagent" : "agent_runs"}({ ${args} })`;
12
+ return action === "extend" && !legacyChild ? `load_subagent({}), then ${call}` : call;
13
+ }
14
+ export function buildManagementControl(input) {
15
+ const capabilities = ["status"];
16
+ if (input.state === "live") {
17
+ if (input.canNudge)
18
+ capabilities.push("nudge");
19
+ if (input.canResume)
20
+ capabilities.push("resume");
21
+ if (input.canInterrupt)
22
+ capabilities.push("interrupt");
23
+ }
24
+ else if (input.canResume)
25
+ capabilities.push("resume");
26
+ if (input.state === "live" && input.canExtend)
27
+ capabilities.push("extend");
28
+ if (input.canReview)
29
+ capabilities.push("review");
30
+ return {
31
+ state: input.state,
32
+ runId: input.runId,
33
+ capabilities,
34
+ nextActions: capabilities.map((action) => ({
35
+ action,
36
+ runId: input.runId,
37
+ ...(input.index !== undefined && (action === "nudge" || action === "resume") ? { index: input.index } : {}),
38
+ ...(input.intercomTarget && (action === "nudge" || action === "resume") ? { intercomTarget: input.intercomTarget } : {}),
39
+ })),
40
+ ...(input.unavailableActions ? { unavailableActions: input.unavailableActions } : {}),
41
+ ...(input.revivedFromRunId ? { revivedFromRunId: input.revivedFromRunId, pendingReplyContextValid: false } : {}),
42
+ };
43
+ }
44
+ function formatActivityAge(ms) {
45
+ if (ms < 1000)
46
+ return "now";
47
+ if (ms < 60000)
48
+ return `${Math.floor(ms / 1000)}s`;
49
+ return `${Math.floor(ms / 60000)}m`;
50
+ }
51
+ export function formatActivityLabel(lastActivityAt, activityState, now = Date.now()) {
52
+ if (lastActivityAt === undefined) {
53
+ if (activityState === "needs_attention")
54
+ return "needs attention";
55
+ return undefined;
56
+ }
57
+ const age = formatActivityAge(Math.max(0, now - lastActivityAt));
58
+ if (activityState === "needs_attention")
59
+ return `no activity for ${age}`;
60
+ return age === "now" ? "active now" : `active ${age} ago`;
61
+ }
62
+ function isCompletedStepStatus(status) {
63
+ return status === "complete" || status === "completed";
64
+ }
65
+ export function aggregateStepStatus(steps) {
66
+ if (steps.some((step) => step.status === "running"))
67
+ return "running";
68
+ if (steps.some((step) => step.status === "failed"))
69
+ return "failed";
70
+ if (steps.some((step) => step.status === "blocked"))
71
+ return "blocked";
72
+ if (steps.some((step) => step.status === "paused"))
73
+ return "paused";
74
+ if (steps.length > 0 && steps.every((step) => isCompletedStepStatus(step.status)))
75
+ return "complete";
76
+ return "pending";
77
+ }
78
+ export function formatAgentRunningLabel(count) {
79
+ return count === 1 ? "1 agent running" : `${count} agents running`;
80
+ }
81
+ export function formatLiveIntercomActionLines(input) {
82
+ const fields = { ...(input.index !== undefined ? { index: input.index } : {}), message: "What are you blocked on?" };
83
+ const healthText = !input.health
84
+ ? "unknown"
85
+ : input.health.status === "registered"
86
+ ? `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}` : ""}`
87
+ : input.health.status.replace(/_/g, " ");
88
+ const indent = input.indent ?? "";
89
+ return [
90
+ `${indent}Intercom: ${healthText} (${input.target})`,
91
+ `${indent}Nudge (preferred live coordination): ${formatRunAction("nudge", input.runId, fields, input.childSafe)}`,
92
+ `${indent}Ask (blocking wait only; parent must remain alive): intercom({ action: "ask", to: "${input.target}", delivery: "steer", message: "What are you blocked on?" })`,
93
+ ];
94
+ }
95
+ export function formatParallelOutcome(steps, total, options = {}) {
96
+ const running = steps.filter((step) => step.status === "running").length;
97
+ const succeeded = steps.filter((step) => isCompletedStepStatus(step.status)).length;
98
+ const failed = steps.filter((step) => step.status === "failed").length;
99
+ const paused = steps.filter((step) => step.status === "paused").length;
100
+ const blocked = steps.filter((step) => step.status === "blocked").length;
101
+ const parts = [`${succeeded}/${total} succeeded`];
102
+ if (options.showRunning !== false && running > 0)
103
+ parts.unshift(formatAgentRunningLabel(running));
104
+ if (failed > 0)
105
+ parts.push(`${failed} failed`);
106
+ if (paused > 0)
107
+ parts.push(`${paused} paused`);
108
+ if (blocked > 0)
109
+ parts.push(`${blocked} need human action`);
110
+ return parts.join(" · ");
111
+ }