@fitchmultz/pi-subagents 0.0.0-stage → 0.44.4

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 (342) hide show
  1. package/CHANGELOG.md +1645 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1529 -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 +194 -0
  43. package/dist/history/ingest.js +447 -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 +461 -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/broker/broker.js +426 -0
  53. package/dist/pi-intercom/broker/client.js +492 -0
  54. package/dist/pi-intercom/broker/framing.js +65 -0
  55. package/dist/pi-intercom/broker/paths.js +42 -0
  56. package/dist/pi-intercom/broker/pid.js +66 -0
  57. package/dist/pi-intercom/broker/spawn.js +221 -0
  58. package/dist/pi-intercom/config.js +102 -0
  59. package/dist/pi-intercom/index.js +2942 -0
  60. package/dist/pi-intercom/reply-tracker.js +182 -0
  61. package/dist/pi-intercom/session-targets.js +128 -0
  62. package/dist/pi-intercom/subagent-live-events.js +132 -0
  63. package/dist/pi-intercom/topics.js +157 -0
  64. package/dist/pi-intercom/types.js +163 -0
  65. package/dist/pi-intercom/ui/compose.js +269 -0
  66. package/dist/pi-intercom/ui/inline-message.js +80 -0
  67. package/dist/pi-intercom/ui/session-list.js +124 -0
  68. package/dist/runs/background/async-control.js +95 -0
  69. package/dist/runs/background/async-execution.js +676 -0
  70. package/dist/runs/background/async-job-tracker.js +383 -0
  71. package/dist/runs/background/async-result-file.js +58 -0
  72. package/dist/runs/background/async-resume.js +305 -0
  73. package/dist/runs/background/async-status.js +442 -0
  74. package/dist/runs/background/completion-dedupe.js +69 -0
  75. package/dist/runs/background/completion-delivery.js +245 -0
  76. package/dist/runs/background/notify.js +71 -0
  77. package/dist/runs/background/parallel-groups.js +55 -0
  78. package/dist/runs/background/result-watcher.js +426 -0
  79. package/dist/runs/background/run-id-resolver.js +64 -0
  80. package/dist/runs/background/run-status.js +382 -0
  81. package/dist/runs/background/stale-run-reconciler.js +335 -0
  82. package/dist/runs/background/subagent-runner-launcher.js +55 -0
  83. package/dist/runs/background/subagent-runner.js +1872 -0
  84. package/dist/runs/background/top-level-async.js +5 -0
  85. package/dist/runs/foreground/chain-clarify.js +1152 -0
  86. package/dist/runs/foreground/clarify-invocation.js +72 -0
  87. package/dist/runs/foreground/execution-input.js +423 -0
  88. package/dist/runs/foreground/foreground-control.js +817 -0
  89. package/dist/runs/foreground/question-control.js +77 -0
  90. package/dist/runs/foreground/run-async-path.js +205 -0
  91. package/dist/runs/foreground/subagent-executor.js +442 -0
  92. package/dist/runs/foreground/subagent-params.js +165 -0
  93. package/dist/runs/foreground/wait-run.js +139 -0
  94. package/dist/runs/shared/acceptance-contract.js +253 -0
  95. package/dist/runs/shared/acceptance-evaluation.js +256 -0
  96. package/dist/runs/shared/acceptance-finalization.js +280 -0
  97. package/dist/runs/shared/acceptance-reports.js +147 -0
  98. package/dist/runs/shared/acceptance.js +4 -0
  99. package/dist/runs/shared/chain-outputs.js +110 -0
  100. package/dist/runs/shared/child-attempt.js +693 -0
  101. package/dist/runs/shared/child-execution-cwd.js +96 -0
  102. package/dist/runs/shared/child-observations.js +114 -0
  103. package/dist/runs/shared/claude-code.js +199 -0
  104. package/dist/runs/shared/completion-guard.js +31 -0
  105. package/dist/runs/shared/dynamic-fanout.js +274 -0
  106. package/dist/runs/shared/history-index.js +72 -0
  107. package/dist/runs/shared/mcp-direct-tool-allowlist.js +334 -0
  108. package/dist/runs/shared/model-fallback.js +185 -0
  109. package/dist/runs/shared/mutating-tool-guard.js +170 -0
  110. package/dist/runs/shared/native-finalization.js +101 -0
  111. package/dist/runs/shared/native-usage.js +153 -0
  112. package/dist/runs/shared/nested-events.js +700 -0
  113. package/dist/runs/shared/nested-path.js +48 -0
  114. package/dist/runs/shared/nested-render.js +108 -0
  115. package/dist/runs/shared/parallel-utils.js +61 -0
  116. package/dist/runs/shared/parent-receipts.js +68 -0
  117. package/dist/runs/shared/parent-usage.js +121 -0
  118. package/dist/runs/shared/pi-args.js +310 -0
  119. package/dist/runs/shared/pi-spawn.js +52 -0
  120. package/dist/runs/shared/run-history-queries.js +45 -0
  121. package/dist/runs/shared/run-history.js +103 -0
  122. package/dist/runs/shared/run-id-ambiguity.js +4 -0
  123. package/dist/runs/shared/run-records.js +679 -0
  124. package/dist/runs/shared/session-cwd-preload.js +33 -0
  125. package/dist/runs/shared/single-output.js +222 -0
  126. package/dist/runs/shared/streaming-text.js +15 -0
  127. package/dist/runs/shared/structured-output.js +55 -0
  128. package/dist/runs/shared/subagent-control.js +179 -0
  129. package/dist/runs/shared/subagent-prompt-runtime.js +197 -0
  130. package/dist/runs/shared/subagent-tool-loop-guard.js +66 -0
  131. package/dist/runs/shared/supervisor-questions.js +320 -0
  132. package/dist/runs/shared/workflow-graph.js +214 -0
  133. package/dist/runs/shared/workflow-policy.js +84 -0
  134. package/dist/runs/shared/worktree.js +525 -0
  135. package/dist/shared/agent-context-policy.js +126 -0
  136. package/dist/shared/artifacts.js +96 -0
  137. package/dist/shared/atomic-json.js +13 -0
  138. package/dist/shared/cooperative.js +20 -0
  139. package/dist/shared/execution-cwd.js +14 -0
  140. package/dist/shared/file-coalescer.js +21 -0
  141. package/dist/shared/fork-context.js +49 -0
  142. package/dist/shared/formatters.js +85 -0
  143. package/dist/shared/journal-reader.js +407 -0
  144. package/dist/shared/lazy-tools.js +32 -0
  145. package/dist/shared/model-info.js +68 -0
  146. package/dist/shared/native-session.js +15 -0
  147. package/dist/shared/native-tui.js +18 -0
  148. package/dist/shared/native-typebox.js +20 -0
  149. package/dist/shared/post-exit-stdio-guard.js +197 -0
  150. package/dist/shared/prompt-sections.js +8 -0
  151. package/dist/shared/session-entries.js +52 -0
  152. package/dist/shared/session-identity.js +14 -0
  153. package/dist/shared/settings.js +254 -0
  154. package/dist/shared/status-format.js +111 -0
  155. package/dist/shared/temp-root.js +114 -0
  156. package/dist/shared/types.js +207 -0
  157. package/dist/shared/ui-mode.js +3 -0
  158. package/dist/shared/utils.js +334 -0
  159. package/dist/slash/prompt-template-bridge.js +298 -0
  160. package/dist/slash/slash-bridge.js +116 -0
  161. package/dist/slash/slash-commands.js +607 -0
  162. package/dist/slash/slash-live-state.js +246 -0
  163. package/dist/tui/action-hints.js +75 -0
  164. package/dist/tui/agent-history.js +197 -0
  165. package/dist/tui/agent-view.js +1656 -0
  166. package/dist/tui/render.js +1515 -0
  167. package/docs/intercom.md +579 -0
  168. package/docs/performance.md +121 -0
  169. package/package.json +101 -4
  170. package/prompts/gather-context-and-clarify.md +14 -0
  171. package/prompts/parallel-cleanup.md +62 -0
  172. package/prompts/parallel-context-build.md +56 -0
  173. package/prompts/parallel-handoff-plan.md +62 -0
  174. package/prompts/parallel-research.md +51 -0
  175. package/prompts/parallel-review.md +59 -0
  176. package/prompts/review-loop.md +42 -0
  177. package/scripts/build.mjs +134 -0
  178. package/scripts/check-compat.mjs +48 -0
  179. package/scripts/compat-host.mjs +19 -0
  180. package/scripts/linux-smoke.sh +51 -0
  181. package/scripts/local-install-smoke.mjs +66 -0
  182. package/scripts/native-package-smoke.mjs +82 -0
  183. package/scripts/package-smoke.mjs +205 -0
  184. package/scripts/prepare.mjs +67 -0
  185. package/scripts/real-pi-smoke.mjs +415 -0
  186. package/scripts/run-tests.mjs +149 -0
  187. package/skills/pi-intercom/SKILL.md +113 -0
  188. package/skills/pi-intercom/evals/evals.json +29 -0
  189. package/skills/pi-intercom/evals/trigger-evals.json +47 -0
  190. package/skills/pi-intercom/references/peer-sessions.md +69 -0
  191. package/skills/pi-subagents/SKILL.md +76 -0
  192. package/skills/pi-subagents/evals/evals.json +29 -0
  193. package/skills/pi-subagents/evals/trigger-evals.json +42 -0
  194. package/skills/pi-subagents/references/full-orchestration-guide.md +863 -0
  195. package/src/agents/agent-management.ts +813 -0
  196. package/src/agents/agent-scope.ts +7 -0
  197. package/src/agents/agent-selection.ts +23 -0
  198. package/src/agents/agent-serializer.ts +99 -0
  199. package/src/agents/agents.ts +916 -0
  200. package/src/agents/chain-serializer.ts +329 -0
  201. package/src/agents/frontmatter.ts +21 -0
  202. package/src/agents/identity.ts +30 -0
  203. package/src/agents/skills.ts +637 -0
  204. package/src/extension/build-info.ts +2 -0
  205. package/src/extension/compact-tools.ts +114 -0
  206. package/src/extension/config.ts +23 -0
  207. package/src/extension/control-notices.ts +47 -0
  208. package/src/extension/doctor.ts +171 -0
  209. package/src/extension/fanout-child.ts +312 -0
  210. package/src/extension/index.ts +649 -0
  211. package/src/extension/schemas.ts +444 -0
  212. package/src/extension/tool-input.ts +19 -0
  213. package/src/extension/tool-result.ts +16 -0
  214. package/src/history/canonical-result.ts +62 -0
  215. package/src/history/database.ts +59 -0
  216. package/src/history/index.ts +152 -0
  217. package/src/history/ingest.ts +332 -0
  218. package/src/history/queries.ts +241 -0
  219. package/src/history/store.ts +203 -0
  220. package/src/history/types.ts +166 -0
  221. package/src/history/worker.ts +317 -0
  222. package/src/intercom/intercom-bridge.ts +123 -0
  223. package/src/intercom/live-intercom.ts +104 -0
  224. package/src/intercom/result-intercom.ts +429 -0
  225. package/src/pi-intercom/agent-dir.ts +9 -0
  226. package/src/pi-intercom/broker/broker.ts +463 -0
  227. package/src/pi-intercom/broker/client.ts +558 -0
  228. package/src/pi-intercom/broker/framing.ts +77 -0
  229. package/src/pi-intercom/broker/paths.ts +47 -0
  230. package/src/pi-intercom/broker/pid.ts +59 -0
  231. package/src/pi-intercom/broker/spawn.ts +248 -0
  232. package/src/pi-intercom/config.ts +142 -0
  233. package/src/pi-intercom/index.ts +2929 -0
  234. package/src/pi-intercom/reply-tracker.ts +211 -0
  235. package/src/pi-intercom/session-targets.ts +172 -0
  236. package/src/pi-intercom/subagent-live-events.ts +133 -0
  237. package/src/pi-intercom/topics.ts +125 -0
  238. package/src/pi-intercom/types.ts +271 -0
  239. package/src/pi-intercom/ui/compose.ts +289 -0
  240. package/src/pi-intercom/ui/inline-message.ts +92 -0
  241. package/src/pi-intercom/ui/session-list.ts +139 -0
  242. package/src/runs/background/async-control.ts +89 -0
  243. package/src/runs/background/async-execution.ts +850 -0
  244. package/src/runs/background/async-job-tracker.ts +385 -0
  245. package/src/runs/background/async-result-file.ts +62 -0
  246. package/src/runs/background/async-resume.ts +333 -0
  247. package/src/runs/background/async-status.ts +481 -0
  248. package/src/runs/background/completion-dedupe.ts +79 -0
  249. package/src/runs/background/completion-delivery.ts +195 -0
  250. package/src/runs/background/notify.ts +123 -0
  251. package/src/runs/background/parallel-groups.ts +53 -0
  252. package/src/runs/background/result-watcher.ts +423 -0
  253. package/src/runs/background/run-id-resolver.ts +69 -0
  254. package/src/runs/background/run-status.ts +384 -0
  255. package/src/runs/background/stale-run-reconciler.ts +369 -0
  256. package/src/runs/background/subagent-runner-launcher.ts +56 -0
  257. package/src/runs/background/subagent-runner.ts +2053 -0
  258. package/src/runs/background/top-level-async.ts +13 -0
  259. package/src/runs/foreground/chain-clarify.ts +1340 -0
  260. package/src/runs/foreground/clarify-invocation.ts +77 -0
  261. package/src/runs/foreground/execution-input.ts +467 -0
  262. package/src/runs/foreground/foreground-control.ts +821 -0
  263. package/src/runs/foreground/question-control.ts +70 -0
  264. package/src/runs/foreground/run-async-path.ts +243 -0
  265. package/src/runs/foreground/subagent-executor.ts +501 -0
  266. package/src/runs/foreground/subagent-params.ts +320 -0
  267. package/src/runs/foreground/wait-run.ts +121 -0
  268. package/src/runs/shared/acceptance-contract.ts +257 -0
  269. package/src/runs/shared/acceptance-evaluation.ts +275 -0
  270. package/src/runs/shared/acceptance-finalization.ts +353 -0
  271. package/src/runs/shared/acceptance-reports.ts +152 -0
  272. package/src/runs/shared/acceptance.ts +33 -0
  273. package/src/runs/shared/chain-outputs.ts +114 -0
  274. package/src/runs/shared/child-attempt.ts +623 -0
  275. package/src/runs/shared/child-execution-cwd.ts +91 -0
  276. package/src/runs/shared/child-observations.ts +99 -0
  277. package/src/runs/shared/claude-code.ts +243 -0
  278. package/src/runs/shared/completion-guard.ts +35 -0
  279. package/src/runs/shared/dynamic-fanout.ts +295 -0
  280. package/src/runs/shared/history-index.ts +69 -0
  281. package/src/runs/shared/mcp-direct-tool-allowlist.ts +376 -0
  282. package/src/runs/shared/model-fallback.ts +219 -0
  283. package/src/runs/shared/mutating-tool-guard.ts +194 -0
  284. package/src/runs/shared/native-finalization.ts +128 -0
  285. package/src/runs/shared/native-usage.ts +113 -0
  286. package/src/runs/shared/nested-events.ts +752 -0
  287. package/src/runs/shared/nested-path.ts +52 -0
  288. package/src/runs/shared/nested-render.ts +119 -0
  289. package/src/runs/shared/parallel-utils.ts +153 -0
  290. package/src/runs/shared/parent-receipts.ts +59 -0
  291. package/src/runs/shared/parent-usage.ts +121 -0
  292. package/src/runs/shared/pi-args.ts +350 -0
  293. package/src/runs/shared/pi-spawn.ts +52 -0
  294. package/src/runs/shared/run-history-queries.ts +44 -0
  295. package/src/runs/shared/run-history.ts +93 -0
  296. package/src/runs/shared/run-id-ambiguity.ts +4 -0
  297. package/src/runs/shared/run-records.ts +607 -0
  298. package/src/runs/shared/session-cwd-preload.ts +27 -0
  299. package/src/runs/shared/single-output.ts +267 -0
  300. package/src/runs/shared/streaming-text.ts +18 -0
  301. package/src/runs/shared/structured-output.ts +68 -0
  302. package/src/runs/shared/subagent-control.ts +219 -0
  303. package/src/runs/shared/subagent-prompt-runtime.ts +201 -0
  304. package/src/runs/shared/subagent-tool-loop-guard.ts +89 -0
  305. package/src/runs/shared/supervisor-questions.ts +372 -0
  306. package/src/runs/shared/workflow-graph.ts +220 -0
  307. package/src/runs/shared/workflow-policy.ts +125 -0
  308. package/src/runs/shared/worktree.ts +635 -0
  309. package/src/shared/agent-context-policy.ts +184 -0
  310. package/src/shared/artifacts.ts +95 -0
  311. package/src/shared/atomic-json.ts +16 -0
  312. package/src/shared/cooperative.ts +21 -0
  313. package/src/shared/execution-cwd.ts +14 -0
  314. package/src/shared/file-coalescer.ts +29 -0
  315. package/src/shared/fork-context.ts +68 -0
  316. package/src/shared/formatters.ts +84 -0
  317. package/src/shared/journal-reader.ts +333 -0
  318. package/src/shared/lazy-tools.ts +36 -0
  319. package/src/shared/model-info.ts +81 -0
  320. package/src/shared/native-session.ts +8 -0
  321. package/src/shared/native-tui.ts +10 -0
  322. package/src/shared/native-typebox.ts +14 -0
  323. package/src/shared/post-exit-stdio-guard.ts +204 -0
  324. package/src/shared/prompt-sections.ts +10 -0
  325. package/src/shared/session-entries.ts +49 -0
  326. package/src/shared/session-identity.ts +18 -0
  327. package/src/shared/settings.ts +384 -0
  328. package/src/shared/status-format.ts +123 -0
  329. package/src/shared/temp-root.ts +107 -0
  330. package/src/shared/types.ts +1259 -0
  331. package/src/shared/ui-mode.ts +5 -0
  332. package/src/shared/utils.ts +342 -0
  333. package/src/slash/prompt-template-bridge.ts +408 -0
  334. package/src/slash/slash-bridge.ts +178 -0
  335. package/src/slash/slash-commands.ts +617 -0
  336. package/src/slash/slash-live-state.ts +292 -0
  337. package/src/tui/action-hints.ts +69 -0
  338. package/src/tui/agent-history.ts +210 -0
  339. package/src/tui/agent-view.ts +1323 -0
  340. package/src/tui/render.ts +1550 -0
  341. package/tsconfig.build.json +12 -0
  342. package/tsconfig.json +19 -0
@@ -0,0 +1,245 @@
1
+ import * as fs from "node:fs";
2
+ import { RESULTS_DIR, SUBAGENT_ASYNC_COMPLETE_EVENT } from "../../shared/types.js";
3
+ import { SessionEntryCursor } from "../../shared/session-entries.js";
4
+ import { createParentReceiptReader } from "../shared/parent-receipts.js";
5
+ import { finalizedChildUsage } from "../shared/parent-usage.js";
6
+ import { ownedRunView, rememberOwnedRun, repairOwnedRunAccounting } from "../shared/run-records.js";
7
+ import registerSubagentNotify from "./notify.js";
8
+ import { createResultWatcher } from "./result-watcher.js";
9
+ /** Completion authority is the published parent receipt, never queue/send acceptance. */
10
+ export function createCompletionDelivery(pi, state, parentUsage) {
11
+ const store = globalThis, queueKey = "__pi_subagents_queued_notifications__";
12
+ // Native custom queues survive extension reload, so their admission authority must too.
13
+ const queued = store[queueKey] instanceof Map ? store[queueKey] : new Map();
14
+ store[queueKey] = queued;
15
+ pi.on("turn_end", (event, ctx) => {
16
+ // ponytail: this is the next batch, not the whole queue. Retain ambiguous
17
+ // admissions until publication or empty queues; selective removal needs a public full-queue snapshot.
18
+ const pending = event.context.pendingMessages.length > 0;
19
+ for (const [key, admission] of queued) {
20
+ if (admission.channel !== "notification" || admission.sessionId !== ctx.sessionManager.getSessionId())
21
+ continue;
22
+ admission.pending = pending;
23
+ if (!admission.pending) {
24
+ queued.delete(key);
25
+ state.completionSeen.delete(key);
26
+ }
27
+ }
28
+ });
29
+ const publishedReceipts = createParentReceiptReader("live");
30
+ const indexReceipts = (saved) => {
31
+ const completed = new Map(), singles = new Map();
32
+ const completions = new Map(), legacy = new Map();
33
+ for (const entry of saved.values()) {
34
+ const details = entry.type === "message" && entry.message.role === "toolResult" ? entry.message.details
35
+ : entry.type === "custom_message" ? entry.details : undefined;
36
+ const data = details;
37
+ const wait = entry.type === "message" ? data?.wait : data?.result?.details?.wait;
38
+ if (wait?.status === "completed") {
39
+ const target = wait.index === undefined ? completed : wait.index === 0 ? singles : undefined;
40
+ if (target && !target.has(wait.runId))
41
+ target.set(wait.runId, entry);
42
+ }
43
+ if (entry.type !== "custom_message")
44
+ continue;
45
+ const completion = data?.completion ?? data?.subagentCompletion;
46
+ if (!completion?.runId)
47
+ continue;
48
+ for (const key of [completion.key, completion.completionId && `completion:${completion.completionId}`]) {
49
+ const identity = `${completion.runId}\0${key}`;
50
+ if (key && !completions.has(identity))
51
+ completions.set(identity, entry);
52
+ }
53
+ if (!completion.completionId && (completion.ownerSessionId === undefined || completion.ownerSessionId === state.currentSessionId)
54
+ && !legacy.has(completion.runId))
55
+ legacy.set(completion.runId, entry);
56
+ }
57
+ return { saved, completed, singles, completions, legacy };
58
+ };
59
+ let batching = false;
60
+ let snapshot;
61
+ const readReceipts = () => {
62
+ const manager = state.lastUiContext?.sessionManager, file = manager?.getSessionFile();
63
+ const leaf = manager?.getLeafId?.(), count = manager?.getEntryCount?.();
64
+ if (batching && count !== undefined && snapshot && snapshot.manager === manager && snapshot.file === file && snapshot.leaf === leaf && snapshot.count === count)
65
+ return snapshot.index;
66
+ const index = indexReceipts(publishedReceipts.read(file));
67
+ if (batching)
68
+ snapshot = { manager, file, leaf, count, index };
69
+ return index;
70
+ };
71
+ const withReceiptBatch = (work) => {
72
+ // Proof is scoped to this synchronous stack, never async delivery. Every
73
+ // parent append changes count/leaf and forces another SHA-verified read.
74
+ batching = true;
75
+ snapshot = undefined;
76
+ try {
77
+ work();
78
+ }
79
+ finally {
80
+ batching = false;
81
+ snapshot = undefined;
82
+ }
83
+ };
84
+ const consumedReceipt = (index, runId) => index.completed.get(runId)
85
+ ?? (state.ownedRuns?.get(runId)?.mode === "single" ? index.singles.get(runId) : undefined);
86
+ const publishedReceipt = (index, runId, key) => consumedReceipt(index, runId)
87
+ ?? index.completions.get(`${runId}\0${key}`) ?? (key.startsWith("completion:legacy:") ? index.legacy.get(runId) : undefined);
88
+ const completedCursor = new SessionEntryCursor();
89
+ const receiptEntryIds = new Set();
90
+ const readParentChanges = () => {
91
+ const ctx = state.lastUiContext;
92
+ if (!ctx)
93
+ return;
94
+ const changes = completedCursor.read(ctx.sessionManager);
95
+ if (changes.reset)
96
+ receiptEntryIds.clear();
97
+ for (const entry of changes.entries) {
98
+ if (entry.type === "message" && entry.message.role === "toolResult") {
99
+ if (["subagent", "delegate", "agent_runs"].includes(entry.message.toolName))
100
+ receiptEntryIds.add(entry.id);
101
+ }
102
+ else if (entry.type === "custom_message" && ["subagent-notify", "intercom_message", "subagent-slash-result"].includes(entry.customType))
103
+ receiptEntryIds.add(entry.id);
104
+ }
105
+ };
106
+ const consumed = (entry, runId) => {
107
+ const wait = entry.type === "message" ? entry.message?.details?.wait : entry.details?.result?.details?.wait;
108
+ const run = state.ownedRuns?.get(runId);
109
+ return wait?.runId === runId && wait.status === "completed" && (wait.index === undefined || run?.mode === "single" && wait.index === 0);
110
+ };
111
+ state.isRunResultConsumed = (runId) => Boolean(consumedReceipt(readReceipts(), runId));
112
+ const matches = (entry, runId, key) => {
113
+ if (consumed(entry, runId))
114
+ return true;
115
+ if (entry.type !== "custom_message")
116
+ return false;
117
+ const completion = entry.details?.completion ?? entry.details?.subagentCompletion;
118
+ return completion?.runId === runId && (completion.key === key || completion.completionId && `completion:${completion.completionId}` === key
119
+ || key.startsWith("completion:legacy:") && !completion.completionId && (completion.ownerSessionId === undefined || completion.ownerSessionId === state.currentSessionId));
120
+ };
121
+ const recordAccounting = (runId) => {
122
+ const run = state.ownedRuns?.get(runId), ctx = state.lastUiContext;
123
+ if (!run || !ctx)
124
+ return;
125
+ try {
126
+ repairOwnedRunAccounting(run);
127
+ const children = ownedRunView(run, state, { readConfiguration: false }).children;
128
+ const incomplete = children.find((child) => child.result?.accounting?.state === "incomplete");
129
+ const accounting = incomplete ? { state: "incomplete", error: incomplete.result?.accounting?.error }
130
+ : { state: parentUsage.isRecorded(finalizedChildUsage(children), ctx, readReceipts().saved) ? "complete" : "pending" };
131
+ rememberOwnedRun(state, { ...state.ownedRuns.get(runId), accounting });
132
+ }
133
+ catch (error) {
134
+ try {
135
+ rememberOwnedRun(state, { ...state.ownedRuns.get(runId), accounting: { state: "incomplete", error: String(error) } });
136
+ }
137
+ catch (saveError) {
138
+ console.error(`Could not save accounting projection for ${runId}:`, saveError);
139
+ }
140
+ console.error(`Subagent ${runId} accounting remains incomplete:`, error);
141
+ }
142
+ };
143
+ const reconcileDelivery = (runId, key, accounting = true) => {
144
+ const run = state.ownedRuns?.get(runId);
145
+ const index = readReceipts(), saved = index.saved;
146
+ const receipt = publishedReceipt(index, runId, key);
147
+ if (receipt) {
148
+ queued.delete(key);
149
+ state.completionSeen.delete(key);
150
+ // Save delivery before optional accounting. Even a failed owner append cannot
151
+ // make this published identity eligible for another notification.
152
+ try {
153
+ if (run)
154
+ rememberOwnedRun(state, { ...run, completion: { id: key, state: "journaled", entryId: receipt.id },
155
+ delivery: { notifiedAt: Date.parse(receipt.timestamp), intercomDelivered: receipt.type === "custom_message" && receipt.customType === "intercom_message", completionId: key, entryId: receipt.id } });
156
+ }
157
+ catch (error) {
158
+ console.error(`Could not save delivery projection for ${runId}:`, error);
159
+ }
160
+ if (accounting)
161
+ recordAccounting(runId);
162
+ return true;
163
+ }
164
+ const ctx = state.lastUiContext;
165
+ if (!ctx)
166
+ return true;
167
+ // An accepted but unflushed receipt or a native pending queue is ambiguous:
168
+ // keep pending rather than enqueueing a second copy. Official hosts expose
169
+ // queue occupancy, not individual queue contents.
170
+ readParentChanges();
171
+ if ([...receiptEntryIds].some((id) => {
172
+ if (saved.has(id))
173
+ return false; // Published candidates were checked above.
174
+ const entry = ctx.sessionManager.getEntry(id);
175
+ return entry !== undefined && matches(entry, runId, key);
176
+ })) {
177
+ queued.delete(key);
178
+ state.completionSeen.delete(key);
179
+ return true;
180
+ }
181
+ if (queued.has(key) || run?.completion?.id === key && run.completion.state === "queued") {
182
+ // The durable Intercom inbox owns its own queue/restart reconciliation.
183
+ // A remote acknowledgement exposes no parent commit/queue boundary.
184
+ if (run?.completion?.channel === "intercom")
185
+ return true;
186
+ // Settlement after abort need not drain the custom queue. Its actual
187
+ // turn boundary remains authoritative when coarse occupancy says idle.
188
+ if (queued.get(key)?.pending || !ctx.isIdle() || ctx.hasPendingMessages())
189
+ return true;
190
+ queued.delete(key);
191
+ state.completionSeen.delete(key);
192
+ if (run)
193
+ rememberOwnedRun(state, { ...run, completion: { id: key, state: "dropped" } });
194
+ }
195
+ return false;
196
+ };
197
+ const watcher = createResultWatcher(pi, state, RESULTS_DIR, { reconcileDelivery, withReceiptBatch,
198
+ isCompletionPublished: (runId, key) => Boolean(publishedReceipt(readReceipts(), runId, key)) });
199
+ let unsubscribe, unsubscribeNotify;
200
+ const markQueued = (runId, key, channel = "notification") => {
201
+ const run = state.ownedRuns?.get(runId);
202
+ queued.set(key, { sessionId: state.currentSessionId, pending: false, channel });
203
+ if (run)
204
+ rememberOwnedRun(state, { ...run, completion: { id: key, state: "queued", channel, queuedAt: Date.now() } });
205
+ };
206
+ const start = () => {
207
+ if (!unsubscribe) {
208
+ unsubscribeNotify = registerSubagentNotify(pi, (key, runId) => { if (runId)
209
+ markQueued(runId, key); });
210
+ unsubscribe = pi.events.on(SUBAGENT_ASYNC_COMPLETE_EVENT, (data) => {
211
+ const result = data;
212
+ const run = state.ownedRuns?.get(result.runId ?? result.id ?? "");
213
+ if (!run || result.sessionId !== state.currentSessionId && run.ownerSessionId !== state.currentSessionId)
214
+ return;
215
+ if (result.completionKey) {
216
+ if (result.intercomResultDelivered)
217
+ markQueued(run.runId, result.completionKey, "intercom");
218
+ reconcileDelivery(run.runId, result.completionKey);
219
+ }
220
+ if (result.suppressNotification)
221
+ recordAccounting(run.runId);
222
+ });
223
+ }
224
+ fs.mkdirSync(RESULTS_DIR, { recursive: true });
225
+ watcher.startResultWatcher();
226
+ watcher.primeExistingResults();
227
+ };
228
+ const stopListening = () => {
229
+ unsubscribe?.();
230
+ unsubscribe = undefined;
231
+ unsubscribeNotify?.();
232
+ unsubscribeNotify = undefined;
233
+ publishedReceipts.clear();
234
+ completedCursor.reset();
235
+ receiptEntryIds.clear();
236
+ };
237
+ const stop = () => { watcher.stopResultWatcher(); stopListening(); };
238
+ const stopAndJoin = async (options = {}) => {
239
+ // Finish accepted delivery while the old owner and its listeners are valid.
240
+ watcher.stopResultWatcher({ ...options, joinInFlight: true });
241
+ await watcher.joinInFlight();
242
+ stopListening();
243
+ };
244
+ return { start, stop, stopAndJoin };
245
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Subagent completion notifications.
3
+ */
4
+ import { buildCompletionKey, getGlobalSeenMap, markSeenWithTtl } from "./completion-dedupe.js";
5
+ import { SUBAGENT_ASYNC_COMPLETE_EVENT } from "../../shared/types.js";
6
+ export default function registerSubagentNotify(pi, onQueued) {
7
+ const unsubscribeStoreKey = "__pi_subagents_notify_unsubscribe__";
8
+ const globalStore = globalThis;
9
+ const previousUnsubscribe = globalStore[unsubscribeStoreKey];
10
+ if (typeof previousUnsubscribe === "function") {
11
+ try {
12
+ previousUnsubscribe();
13
+ }
14
+ catch {
15
+ // Best effort cleanup for stale handlers from an older reload.
16
+ }
17
+ }
18
+ const seen = getGlobalSeenMap("__pi_subagents_notify_seen__");
19
+ const ttlMs = 10 * 60 * 1000;
20
+ const handleComplete = (data) => {
21
+ const result = data;
22
+ if (result.intercomResultDelivered === true || result.suppressNotification === true)
23
+ return;
24
+ const now = Date.now();
25
+ const key = buildCompletionKey(result, "notify");
26
+ // Owned completion keys are governed by the shared queue/journal lifecycle, not a TTL.
27
+ if ((!onQueued || !result.completionKey) && markSeenWithTtl(seen, key, now, ttlMs))
28
+ return;
29
+ const agent = result.agent ?? "unknown";
30
+ const summary = typeof result.summary === "string" ? result.summary : "";
31
+ const paused = !result.success && (result.exitCode === 0
32
+ || result.state === "paused"
33
+ || summary.startsWith("Paused after interrupt."));
34
+ const status = result.state === "blocked" ? "blocked" : paused ? "paused" : result.success ? "completed" : "failed";
35
+ const taskInfo = result.taskIndex !== undefined && result.totalTasks !== undefined
36
+ ? ` (${result.taskIndex + 1}/${result.totalTasks})`
37
+ : "";
38
+ const sessionLine = result.shareUrl
39
+ ? `Session: ${result.shareUrl}`
40
+ : result.shareError
41
+ ? `Session share error: ${result.shareError}`
42
+ : result.sessionFile
43
+ ? `Session file: ${result.sessionFile}`
44
+ : undefined;
45
+ const displaySummary = summary.trim() ? summary : "(no output)";
46
+ const content = [
47
+ `Background task ${status}: **${agent}**${taskInfo}`,
48
+ "",
49
+ displaySummary,
50
+ sessionLine ? "" : undefined,
51
+ sessionLine,
52
+ ]
53
+ .filter((line) => line !== undefined)
54
+ .join("\n");
55
+ pi.sendMessage({
56
+ customType: "subagent-notify",
57
+ content,
58
+ display: true,
59
+ ...(result.runId && result.completionKey ? { details: {
60
+ agent, status, taskInfo: taskInfo.trim(), resultPreview: displaySummary.trim(),
61
+ ...(sessionLine ? { sessionLabel: sessionLine.slice(0, sessionLine.indexOf(":")).toLowerCase(), sessionValue: sessionLine.slice(sessionLine.indexOf(":") + 1).trim() } : {}),
62
+ completion: { runId: result.runId, key: result.completionKey },
63
+ } } : {}),
64
+ }, { triggerTurn: true });
65
+ if (result.completionKey)
66
+ onQueued?.(result.completionKey, result.runId);
67
+ };
68
+ const unsubscribe = pi.events.on(SUBAGENT_ASYNC_COMPLETE_EVENT, handleComplete);
69
+ globalStore[unsubscribeStoreKey] = unsubscribe;
70
+ return unsubscribe;
71
+ }
@@ -0,0 +1,55 @@
1
+ function isValidParallelGroup(group, stepCount, chainStepCount) {
2
+ if (typeof group !== "object" || group === null)
3
+ return false;
4
+ const { start, count, stepIndex } = group;
5
+ return typeof start === "number"
6
+ && typeof count === "number"
7
+ && typeof stepIndex === "number"
8
+ && Number.isInteger(start)
9
+ && Number.isInteger(count)
10
+ && Number.isInteger(stepIndex)
11
+ && start >= 0
12
+ && count >= 0
13
+ && stepIndex >= 0
14
+ && stepIndex < chainStepCount
15
+ && start + count <= stepCount;
16
+ }
17
+ export function normalizeParallelGroups(groups, stepCount, chainStepCount) {
18
+ if (!Array.isArray(groups))
19
+ return [];
20
+ const sorted = groups
21
+ .filter((group) => isValidParallelGroup(group, stepCount, chainStepCount))
22
+ .sort((left, right) => left.stepIndex - right.stepIndex || left.start - right.start);
23
+ const normalized = [];
24
+ const logicalSteps = new Set();
25
+ for (const group of sorted) {
26
+ if (logicalSteps.has(group.stepIndex) || normalized.some((existing) => group.start < existing.start + existing.count && existing.start < group.start + group.count))
27
+ continue;
28
+ logicalSteps.add(group.stepIndex);
29
+ normalized.push(group);
30
+ }
31
+ return normalized;
32
+ }
33
+ export function flatToLogicalStepIndex(flatIndex, chainStepCount, groups) {
34
+ let logicalIndex = 0;
35
+ let cursor = 0;
36
+ for (const group of groups) {
37
+ while (cursor < group.start && logicalIndex < chainStepCount) {
38
+ if (cursor === flatIndex)
39
+ return logicalIndex;
40
+ cursor++;
41
+ logicalIndex++;
42
+ }
43
+ if (flatIndex >= group.start && flatIndex < group.start + group.count)
44
+ return group.stepIndex;
45
+ cursor = group.start + group.count;
46
+ logicalIndex = group.stepIndex + 1;
47
+ }
48
+ while (cursor <= flatIndex && logicalIndex < chainStepCount) {
49
+ if (cursor === flatIndex)
50
+ return logicalIndex;
51
+ cursor++;
52
+ logicalIndex++;
53
+ }
54
+ return Math.max(0, chainStepCount - 1);
55
+ }