@fitchmultz/pi-subagents 0.44.7 → 0.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (955) hide show
  1. package/CHANGELOG.md +264 -7
  2. package/LICENSE +1 -0
  3. package/README.md +58 -1483
  4. package/agents/context-builder.md +19 -1
  5. package/agents/debugger.md +10 -3
  6. package/agents/delegate.md +1 -0
  7. package/agents/fixer.md +4 -1
  8. package/agents/oracle.md +13 -2
  9. package/agents/planner.md +14 -3
  10. package/agents/researcher.md +11 -2
  11. package/agents/reviewer-claude.md +11 -2
  12. package/agents/reviewer-gpt.md +12 -2
  13. package/agents/reviewer-ponytail.md +11 -1
  14. package/agents/reviewer-security.md +13 -2
  15. package/agents/reviewer.md +12 -3
  16. package/agents/scout.md +12 -1
  17. package/agents/ui-designer.md +10 -3
  18. package/agents/watcher.md +1 -0
  19. package/agents/worker.md +8 -2
  20. package/agents/writer.md +6 -3
  21. package/dist/agents/agent-defaults.js +9 -0
  22. package/dist/agents/agent-definition.js +196 -0
  23. package/dist/agents/agent-management.js +64 -905
  24. package/dist/agents/agent-scope.js +4 -2
  25. package/dist/agents/agent-selection.js +10 -5
  26. package/dist/agents/agent-serializer.js +55 -67
  27. package/dist/agents/agents.js +75 -716
  28. package/dist/agents/builtin-overrides.js +218 -0
  29. package/dist/agents/chain-serializer.js +3 -367
  30. package/dist/agents/config-values.js +52 -0
  31. package/dist/agents/discovery-paths.js +91 -0
  32. package/dist/agents/frontmatter.js +15 -5
  33. package/dist/agents/identity.js +25 -8
  34. package/dist/agents/json-chain.js +254 -0
  35. package/dist/agents/management-config.js +332 -0
  36. package/dist/agents/management-format.js +231 -0
  37. package/dist/agents/management-persistence.js +287 -0
  38. package/dist/agents/management-targets.js +136 -0
  39. package/dist/agents/management-warnings.js +81 -0
  40. package/dist/agents/markdown-chain.js +210 -0
  41. package/dist/agents/package-agents.js +154 -0
  42. package/dist/agents/skill-files.js +157 -0
  43. package/dist/agents/skill-search-paths.js +246 -0
  44. package/dist/agents/skill-types.js +1 -0
  45. package/dist/agents/skills.js +54 -499
  46. package/dist/extension/acceptance-schema.js +55 -0
  47. package/dist/extension/build-info.js +1 -1
  48. package/dist/extension/chain-schema.js +196 -0
  49. package/dist/extension/child-runtime.js +237 -0
  50. package/dist/extension/compact-tools.js +122 -51
  51. package/dist/extension/config-schema.js +29 -0
  52. package/dist/extension/config.js +27 -11
  53. package/dist/extension/control-notice-schema.js +37 -0
  54. package/dist/extension/control-notices.js +13 -6
  55. package/dist/extension/delegation-bridges.js +43 -0
  56. package/dist/extension/doctor.js +48 -25
  57. package/dist/extension/everyday-schemas.js +207 -0
  58. package/dist/extension/fanout-child.js +18 -319
  59. package/dist/extension/index.js +8 -563
  60. package/dist/extension/message-renderers.js +225 -0
  61. package/dist/extension/nested-control-inbox.js +154 -0
  62. package/dist/extension/parent-runtime.js +339 -0
  63. package/dist/extension/primary-tool.js +69 -0
  64. package/dist/extension/result-snapshot.js +22 -0
  65. package/dist/extension/runtime-reload.js +62 -0
  66. package/dist/extension/runtime-storage.js +43 -0
  67. package/dist/extension/schema-overrides.js +75 -0
  68. package/dist/extension/schemas.js +265 -336
  69. package/dist/extension/tool-input.js +27 -12
  70. package/dist/extension/tool-result.js +19 -4
  71. package/dist/history/answer-texts.js +57 -0
  72. package/dist/history/canonical-result.js +76 -43
  73. package/dist/history/canonical-selection.js +40 -0
  74. package/dist/history/database-generation.js +82 -0
  75. package/dist/history/database.js +57 -33
  76. package/dist/history/entry-ingestion.js +61 -0
  77. package/dist/history/index.js +242 -97
  78. package/dist/history/ingest.js +168 -331
  79. package/dist/history/output-digests.js +40 -0
  80. package/dist/history/preview.js +210 -0
  81. package/dist/history/prune.js +33 -0
  82. package/dist/history/queries.js +60 -349
  83. package/dist/history/query-scope.js +190 -0
  84. package/dist/history/rows.js +77 -0
  85. package/dist/history/run-projection.js +137 -0
  86. package/dist/history/run-queries.js +153 -0
  87. package/dist/history/schema.js +31 -0
  88. package/dist/history/search-queries.js +163 -0
  89. package/dist/history/selected-record.js +108 -0
  90. package/dist/history/source-file.js +45 -0
  91. package/dist/history/source-watches.js +141 -0
  92. package/dist/history/store.js +164 -153
  93. package/dist/history/text-chunks.js +169 -0
  94. package/dist/history/text.js +35 -0
  95. package/dist/history/transcript-queries.js +177 -0
  96. package/dist/history/types.js +5 -1
  97. package/dist/history/values.js +36 -0
  98. package/dist/history/wire-inputs.js +122 -0
  99. package/dist/history/wire-models.js +38 -0
  100. package/dist/history/worker-runtime.js +441 -0
  101. package/dist/history/worker.js +66 -462
  102. package/dist/intercom/intercom-bridge.js +48 -20
  103. package/dist/intercom/live-intercom.js +80 -23
  104. package/dist/intercom/nested-result-tree.js +163 -0
  105. package/dist/intercom/result-delivery.js +40 -0
  106. package/dist/intercom/result-intercom.js +178 -261
  107. package/dist/pi-intercom/agent-dir.js +7 -3
  108. package/dist/pi-intercom/async.js +27 -0
  109. package/dist/pi-intercom/bridge-ask.js +57 -0
  110. package/dist/pi-intercom/bridge-config.js +89 -0
  111. package/dist/pi-intercom/bridge-http.js +86 -0
  112. package/dist/pi-intercom/bridge-launch-policy.js +88 -0
  113. package/dist/pi-intercom/bridge-launch.js +217 -0
  114. package/dist/pi-intercom/bridge-protocol.js +106 -0
  115. package/dist/pi-intercom/bridge-routes.js +72 -0
  116. package/dist/pi-intercom/bridge-rpc-process.js +264 -0
  117. package/dist/pi-intercom/bridge-session.js +420 -0
  118. package/dist/pi-intercom/bridge.js +221 -606
  119. package/dist/pi-intercom/broker/broker.js +236 -244
  120. package/dist/pi-intercom/broker/client.js +189 -304
  121. package/dist/pi-intercom/broker/connection.js +165 -0
  122. package/dist/pi-intercom/broker/framing.js +5 -3
  123. package/dist/pi-intercom/broker/paths.js +4 -1
  124. package/dist/pi-intercom/broker/pid.js +53 -25
  125. package/dist/pi-intercom/broker/snapshot-reader.js +78 -0
  126. package/dist/pi-intercom/broker/snapshots.js +188 -0
  127. package/dist/pi-intercom/broker/spawn.js +36 -25
  128. package/dist/pi-intercom/config.js +85 -71
  129. package/dist/pi-intercom/connection.js +297 -0
  130. package/dist/pi-intercom/identity.js +58 -0
  131. package/dist/pi-intercom/inbound-delivery.js +356 -0
  132. package/dist/pi-intercom/inbound-journal.js +303 -0
  133. package/dist/pi-intercom/inbound-record.js +113 -0
  134. package/dist/pi-intercom/inbound.js +179 -0
  135. package/dist/pi-intercom/index.js +79 -2933
  136. package/dist/pi-intercom/inspection.js +62 -0
  137. package/dist/pi-intercom/interview.js +236 -0
  138. package/dist/pi-intercom/lifecycle.js +168 -0
  139. package/dist/pi-intercom/message-format.js +69 -0
  140. package/dist/pi-intercom/outbound.js +304 -0
  141. package/dist/pi-intercom/peer-awareness.js +66 -0
  142. package/dist/pi-intercom/reply-tracker.js +62 -43
  143. package/dist/pi-intercom/reply-wait.js +201 -0
  144. package/dist/pi-intercom/runtime-types.js +3 -0
  145. package/dist/pi-intercom/session-events.js +180 -0
  146. package/dist/pi-intercom/session-targets.js +42 -34
  147. package/dist/pi-intercom/subagent-events.js +238 -0
  148. package/dist/pi-intercom/subagent-live-events.js +129 -86
  149. package/dist/pi-intercom/supervisor.js +282 -0
  150. package/dist/pi-intercom/tool-arguments.js +74 -0
  151. package/dist/pi-intercom/tool-registration.js +95 -0
  152. package/dist/pi-intercom/tool-results.js +38 -0
  153. package/dist/pi-intercom/tool-schemas.js +82 -0
  154. package/dist/pi-intercom/tools.js +51 -0
  155. package/dist/pi-intercom/topic-actions.js +177 -0
  156. package/dist/pi-intercom/topics.js +177 -92
  157. package/dist/pi-intercom/transport.js +1 -0
  158. package/dist/pi-intercom/types.js +151 -114
  159. package/dist/pi-intercom/ui/compose-paste.js +93 -0
  160. package/dist/pi-intercom/ui/compose.js +121 -143
  161. package/dist/pi-intercom/ui/inline-message.js +59 -59
  162. package/dist/pi-intercom/ui/message-renderers.js +147 -0
  163. package/dist/pi-intercom/ui/overlay.js +181 -0
  164. package/dist/pi-intercom/ui/session-format.js +124 -0
  165. package/dist/pi-intercom/ui/session-list.js +76 -43
  166. package/dist/pi-intercom/ui/tool-renderers.js +110 -0
  167. package/dist/pi-intercom/ui/topic-view.js +88 -0
  168. package/dist/pi-intercom/validation.js +59 -0
  169. package/dist/runs/background/async-chain-plan.js +265 -0
  170. package/dist/runs/background/async-control.js +134 -58
  171. package/dist/runs/background/async-execution.js +136 -633
  172. package/dist/runs/background/async-job-admission.js +40 -0
  173. package/dist/runs/background/async-job-projection.js +169 -0
  174. package/dist/runs/background/async-job-tracker.js +297 -327
  175. package/dist/runs/background/async-launch.js +216 -0
  176. package/dist/runs/background/async-plan.js +116 -0
  177. package/dist/runs/background/async-result-file.js +47 -23
  178. package/dist/runs/background/async-resume.js +195 -253
  179. package/dist/runs/background/async-run-discovery.js +213 -0
  180. package/dist/runs/background/async-run-location.js +135 -0
  181. package/dist/runs/background/async-run-record.js +1 -0
  182. package/dist/runs/background/async-run-repair.js +223 -0
  183. package/dist/runs/background/async-run-summary.js +1 -0
  184. package/dist/runs/background/async-single-plan.js +105 -0
  185. package/dist/runs/background/async-status-projection.js +155 -0
  186. package/dist/runs/background/async-status-render.js +117 -0
  187. package/dist/runs/background/async-status-validation.js +181 -0
  188. package/dist/runs/background/async-status.js +3 -442
  189. package/dist/runs/background/async-value.js +21 -0
  190. package/dist/runs/background/completion-dedupe.js +36 -12
  191. package/dist/runs/background/completion-delivery.js +211 -205
  192. package/dist/runs/background/completion-receipts.js +237 -0
  193. package/dist/runs/background/legacy-status-acceptance.js +34 -0
  194. package/dist/runs/background/notify.js +96 -48
  195. package/dist/runs/background/parallel-groups.js +36 -19
  196. package/dist/runs/background/result-payload.js +178 -0
  197. package/dist/runs/background/result-watcher.js +348 -339
  198. package/dist/runs/background/run-id-resolver.js +53 -33
  199. package/dist/runs/background/run-schemas.js +189 -0
  200. package/dist/runs/background/run-status-contracts.js +14 -0
  201. package/dist/runs/background/run-status-render.js +270 -0
  202. package/dist/runs/background/run-status.js +231 -339
  203. package/dist/runs/background/runner-attempt-output.js +155 -0
  204. package/dist/runs/background/runner-attempt.js +263 -0
  205. package/dist/runs/background/runner-child-executor.js +166 -0
  206. package/dist/runs/background/runner-child-observer.js +192 -0
  207. package/dist/runs/background/runner-completion-summary.js +71 -0
  208. package/dist/runs/background/runner-completion.js +211 -0
  209. package/dist/runs/background/runner-config.js +1 -0
  210. package/dist/runs/background/runner-contract.js +1 -0
  211. package/dist/runs/background/runner-dynamic.js +121 -0
  212. package/dist/runs/background/runner-finalization.js +149 -0
  213. package/dist/runs/background/runner-graph.js +68 -0
  214. package/dist/runs/background/runner-initial-status.js +60 -0
  215. package/dist/runs/background/runner-lifecycle.js +142 -0
  216. package/dist/runs/background/runner-monitor.js +309 -0
  217. package/dist/runs/background/runner-parallel.js +204 -0
  218. package/dist/runs/background/runner-prepare-step.js +86 -0
  219. package/dist/runs/background/runner-reporting.js +153 -0
  220. package/dist/runs/background/runner-status.js +148 -0
  221. package/dist/runs/background/runner-step-output.js +92 -0
  222. package/dist/runs/background/runner-step-record.js +45 -0
  223. package/dist/runs/background/runner-step.js +112 -0
  224. package/dist/runs/background/runner-streaming.js +173 -0
  225. package/dist/runs/background/runner-workflow.js +213 -0
  226. package/dist/runs/background/stale-run-reconciler.js +155 -292
  227. package/dist/runs/background/subagent-runner-launcher.js +33 -13
  228. package/dist/runs/background/subagent-runner.js +51 -1842
  229. package/dist/runs/background/top-level-async.js +2 -1
  230. package/dist/runs/foreground/chain-clarify.js +293 -1017
  231. package/dist/runs/foreground/clarification-draft.js +87 -0
  232. package/dist/runs/foreground/clarify-contracts.js +1 -0
  233. package/dist/runs/foreground/clarify-invocation.js +129 -38
  234. package/dist/runs/foreground/clarify-text-editor.js +210 -0
  235. package/dist/runs/foreground/clarify-view.js +167 -0
  236. package/dist/runs/foreground/execution-input.js +226 -353
  237. package/dist/runs/foreground/execution-routing.js +33 -0
  238. package/dist/runs/foreground/execution-timeout.js +81 -0
  239. package/dist/runs/foreground/foreground-control.js +222 -763
  240. package/dist/runs/foreground/foreground-memory.js +99 -0
  241. package/dist/runs/foreground/invocation-context.js +55 -0
  242. package/dist/runs/foreground/invocation-execution.js +331 -0
  243. package/dist/runs/foreground/invocation-expansion.js +48 -0
  244. package/dist/runs/foreground/management-actions.js +295 -0
  245. package/dist/runs/foreground/nested-control.js +179 -0
  246. package/dist/runs/foreground/question-continuation.js +78 -0
  247. package/dist/runs/foreground/question-control.js +155 -50
  248. package/dist/runs/foreground/resume-target.js +221 -0
  249. package/dist/runs/foreground/revival-launch-policy.js +58 -0
  250. package/dist/runs/foreground/run-async-path.js +190 -178
  251. package/dist/runs/foreground/run-interrupt.js +164 -0
  252. package/dist/runs/foreground/run-nudge.js +278 -0
  253. package/dist/runs/foreground/saved-revival.js +251 -0
  254. package/dist/runs/foreground/subagent-executor.js +135 -424
  255. package/dist/runs/foreground/subagent-params.js +67 -30
  256. package/dist/runs/foreground/wait-observation.js +104 -0
  257. package/dist/runs/foreground/wait-registration.js +17 -0
  258. package/dist/runs/foreground/wait-run.js +283 -123
  259. package/dist/runs/shared/acceptance-contract.js +193 -99
  260. package/dist/runs/shared/acceptance-defaults.js +1 -0
  261. package/dist/runs/shared/acceptance-evaluation.js +201 -181
  262. package/dist/runs/shared/acceptance-finalization.js +256 -180
  263. package/dist/runs/shared/acceptance-outcome.js +53 -0
  264. package/dist/runs/shared/acceptance-reports.js +137 -72
  265. package/dist/runs/shared/acceptance-submission.js +167 -0
  266. package/dist/runs/shared/acceptance-verification.js +100 -0
  267. package/dist/runs/shared/chain-outputs.js +80 -52
  268. package/dist/runs/shared/child-attempt-types.js +1 -0
  269. package/dist/runs/shared/child-attempt.js +314 -667
  270. package/dist/runs/shared/child-boundaries.js +173 -0
  271. package/dist/runs/shared/child-context-filter.js +44 -0
  272. package/dist/runs/shared/child-event-observer.js +390 -0
  273. package/dist/runs/shared/child-execution-cwd.js +73 -43
  274. package/dist/runs/shared/child-execution-outcome.js +72 -0
  275. package/dist/runs/shared/child-invocation.js +54 -0
  276. package/dist/runs/shared/child-message-validation.js +215 -0
  277. package/dist/runs/shared/child-native-accounting.js +106 -0
  278. package/dist/runs/shared/child-native-observation.js +69 -0
  279. package/dist/runs/shared/child-observation-spool.js +164 -0
  280. package/dist/runs/shared/child-observations.js +157 -76
  281. package/dist/runs/shared/child-presence.js +3 -0
  282. package/dist/runs/shared/child-route-env.js +119 -0
  283. package/dist/runs/shared/child-stream-observer.js +221 -0
  284. package/dist/runs/shared/child-stream-projection.js +137 -0
  285. package/dist/runs/shared/child-structured-tool.js +27 -0
  286. package/dist/runs/shared/claude-code.js +121 -158
  287. package/dist/runs/shared/claude-model.js +42 -0
  288. package/dist/runs/shared/claude-session.js +83 -0
  289. package/dist/runs/shared/claude-types.js +1 -0
  290. package/dist/runs/shared/completion-guard.js +18 -18
  291. package/dist/runs/shared/control-event-policy.js +8 -0
  292. package/dist/runs/shared/control-notification-owner.js +13 -0
  293. package/dist/runs/shared/dynamic-fanout.js +188 -173
  294. package/dist/runs/shared/dynamic-item.js +102 -0
  295. package/dist/runs/shared/history-index.js +62 -36
  296. package/dist/runs/shared/legacy-async-recovery.js +114 -0
  297. package/dist/runs/shared/legacy-output-recovery.js +148 -0
  298. package/dist/runs/shared/mcp-config-schema.js +36 -0
  299. package/dist/runs/shared/mcp-direct-tool-allowlist.js +147 -116
  300. package/dist/runs/shared/model-fallback.js +97 -38
  301. package/dist/runs/shared/mutating-tool-guard.js +128 -88
  302. package/dist/runs/shared/native-finalization-types.js +1 -0
  303. package/dist/runs/shared/native-finalization.js +155 -76
  304. package/dist/runs/shared/native-usage.js +238 -98
  305. package/dist/runs/shared/nested-control-records.js +128 -0
  306. package/dist/runs/shared/nested-events.js +8 -700
  307. package/dist/runs/shared/nested-path.js +32 -28
  308. package/dist/runs/shared/nested-projection.js +155 -0
  309. package/dist/runs/shared/nested-protocol.js +18 -0
  310. package/dist/runs/shared/nested-registry.js +191 -0
  311. package/dist/runs/shared/nested-render.js +106 -58
  312. package/dist/runs/shared/nested-route.js +220 -0
  313. package/dist/runs/shared/nested-validation.js +216 -0
  314. package/dist/runs/shared/owned-child-evidence.js +125 -0
  315. package/dist/runs/shared/owned-run-list.js +146 -0
  316. package/dist/runs/shared/owned-run-observation.js +267 -0
  317. package/dist/runs/shared/owned-run-progress.js +109 -0
  318. package/dist/runs/shared/owned-run-read-state.js +1 -0
  319. package/dist/runs/shared/owned-run-results.js +202 -0
  320. package/dist/runs/shared/owned-run-status.js +180 -0
  321. package/dist/runs/shared/owned-run-view.js +51 -0
  322. package/dist/runs/shared/owned-workflow-details.js +17 -0
  323. package/dist/runs/shared/owner-output.js +43 -0
  324. package/dist/runs/shared/parallel-utils.js +36 -28
  325. package/dist/runs/shared/parent-receipts.js +90 -32
  326. package/dist/runs/shared/parent-usage.js +169 -57
  327. package/dist/runs/shared/pi-args.js +108 -201
  328. package/dist/runs/shared/pi-launch-input.js +1 -0
  329. package/dist/runs/shared/pi-session-launch.js +86 -0
  330. package/dist/runs/shared/pi-spawn.js +35 -21
  331. package/dist/runs/shared/run-history-queries.js +126 -35
  332. package/dist/runs/shared/run-history.js +101 -35
  333. package/dist/runs/shared/run-metadata-paths.js +14 -0
  334. package/dist/runs/shared/run-metadata.js +158 -0
  335. package/dist/runs/shared/run-persistence.js +131 -0
  336. package/dist/runs/shared/run-records.js +9 -679
  337. package/dist/runs/shared/run-restoration.js +338 -0
  338. package/dist/runs/shared/run-state-owner.js +26 -0
  339. package/dist/runs/shared/session-cwd-preload.js +27 -18
  340. package/dist/runs/shared/single-output.js +148 -70
  341. package/dist/runs/shared/stable-value.js +15 -0
  342. package/dist/runs/shared/streaming-text.js +23 -12
  343. package/dist/runs/shared/structured-output.js +25 -13
  344. package/dist/runs/shared/subagent-control.js +167 -100
  345. package/dist/runs/shared/subagent-prompt-runtime.js +27 -130
  346. package/dist/runs/shared/subagent-tool-loop-guard.js +81 -52
  347. package/dist/runs/shared/supervisor-question-state.js +145 -0
  348. package/dist/runs/shared/supervisor-questions.js +185 -254
  349. package/dist/runs/shared/workflow-graph.js +259 -167
  350. package/dist/runs/shared/workflow-policy.js +120 -48
  351. package/dist/runs/shared/worktree-contract.js +1 -0
  352. package/dist/runs/shared/worktree-hook.js +138 -0
  353. package/dist/runs/shared/worktree-location.js +47 -0
  354. package/dist/runs/shared/worktree-preservation.js +8 -0
  355. package/dist/runs/shared/worktree-process.js +107 -0
  356. package/dist/runs/shared/worktree.js +183 -322
  357. package/dist/shared/agent-context-policy.js +34 -44
  358. package/dist/shared/agent-dir.js +14 -0
  359. package/dist/shared/artifacts.js +30 -12
  360. package/dist/shared/atomic-json.js +4 -1
  361. package/dist/shared/chain-files.js +72 -0
  362. package/dist/shared/concurrency.js +18 -0
  363. package/dist/shared/content-preview.js +115 -0
  364. package/dist/shared/cooperative.js +5 -2
  365. package/dist/shared/execution-cwd.js +13 -5
  366. package/dist/shared/file-coalescer.js +2 -1
  367. package/dist/shared/fork-context.js +10 -12
  368. package/dist/shared/formatters.js +45 -19
  369. package/dist/shared/journal-frames.js +164 -0
  370. package/dist/shared/journal-projections.js +121 -0
  371. package/dist/shared/journal-reader.js +6 -407
  372. package/dist/shared/json-projection.js +191 -0
  373. package/dist/shared/lazy-tools.js +19 -8
  374. package/dist/shared/message-output.js +116 -0
  375. package/dist/shared/model-info.js +31 -14
  376. package/dist/shared/native-import.js +20 -0
  377. package/dist/shared/native-journal.js +140 -0
  378. package/dist/shared/native-session-loader.js +17 -0
  379. package/dist/shared/native-session.js +2 -13
  380. package/dist/shared/native-tui.js +7 -2
  381. package/dist/shared/native-typebox.js +15 -3
  382. package/dist/shared/output-page.js +63 -0
  383. package/dist/shared/post-exit-stdio-guard.js +128 -88
  384. package/dist/shared/prompt-sections.js +2 -0
  385. package/dist/shared/session-entries.js +70 -35
  386. package/dist/shared/session-identity.js +5 -3
  387. package/dist/shared/settings.js +62 -192
  388. package/dist/shared/status-format.js +97 -37
  389. package/dist/shared/task-file-policy.js +24 -0
  390. package/dist/shared/temp-root.js +44 -15
  391. package/dist/shared/types/acceptance.js +1 -0
  392. package/dist/shared/types/async.js +1 -0
  393. package/dist/shared/types/config.js +1 -0
  394. package/dist/shared/types/details.js +1 -0
  395. package/dist/shared/types/history.js +1 -0
  396. package/dist/shared/types/inputs.js +1 -0
  397. package/dist/shared/types/intercom.js +1 -0
  398. package/dist/shared/types/launch.js +1 -0
  399. package/dist/shared/types/messages.js +1 -0
  400. package/dist/shared/types/nested.js +1 -0
  401. package/dist/shared/types/owned-runs.js +1 -0
  402. package/dist/shared/types/progress.js +1 -0
  403. package/dist/shared/types/questions.js +1 -0
  404. package/dist/shared/types/results.js +1 -0
  405. package/dist/shared/types/state.js +1 -0
  406. package/dist/shared/types/usage.js +1 -0
  407. package/dist/shared/types/workflow.js +1 -0
  408. package/dist/shared/types.js +75 -30
  409. package/dist/shared/unknown.js +52 -0
  410. package/dist/shared/utils.js +54 -260
  411. package/dist/slash/command-discovery.js +82 -0
  412. package/dist/slash/command-input.js +190 -0
  413. package/dist/slash/prompt-template-bridge.js +94 -270
  414. package/dist/slash/prompt-template-contract.js +77 -0
  415. package/dist/slash/prompt-template-results.js +125 -0
  416. package/dist/slash/request-protocol.js +68 -0
  417. package/dist/slash/result-delivery.js +237 -0
  418. package/dist/slash/saved-chain.js +78 -0
  419. package/dist/slash/slash-bridge.js +123 -89
  420. package/dist/slash/slash-commands.js +101 -572
  421. package/dist/slash/slash-live-state.js +68 -47
  422. package/dist/tui/action-hints.js +50 -22
  423. package/dist/tui/activity-lines.js +32 -0
  424. package/dist/tui/agent-browser.js +290 -0
  425. package/dist/tui/agent-controls.js +289 -0
  426. package/dist/tui/agent-conversation.js +444 -0
  427. package/dist/tui/agent-dock.js +171 -0
  428. package/dist/tui/agent-history.js +50 -183
  429. package/dist/tui/agent-model.js +62 -0
  430. package/dist/tui/agent-picker.js +262 -0
  431. package/dist/tui/agent-view.js +232 -1564
  432. package/dist/tui/compact-results.js +193 -0
  433. package/dist/tui/conversation-details.js +190 -0
  434. package/dist/tui/conversation-frame.js +135 -0
  435. package/dist/tui/conversation-history.js +201 -0
  436. package/dist/tui/conversation-menu.js +78 -0
  437. package/dist/tui/conversation-notices.js +79 -0
  438. package/dist/tui/conversation-selection.js +58 -0
  439. package/dist/tui/conversation-viewport.js +129 -0
  440. package/dist/tui/direction-message.js +27 -0
  441. package/dist/tui/display.js +282 -0
  442. package/dist/tui/editor-viewport.js +18 -0
  443. package/dist/tui/expanded-content.js +87 -0
  444. package/dist/tui/expanded-multi.js +170 -0
  445. package/dist/tui/expanded-single.js +65 -0
  446. package/dist/tui/history-cards.js +216 -0
  447. package/dist/tui/history-display.js +238 -0
  448. package/dist/tui/history-json.js +16 -0
  449. package/dist/tui/history-metadata.js +152 -0
  450. package/dist/tui/history-record.js +251 -0
  451. package/dist/tui/history-text.js +34 -0
  452. package/dist/tui/indexed-history.js +116 -0
  453. package/dist/tui/nested-widget.js +120 -0
  454. package/dist/tui/picker-page.js +59 -0
  455. package/dist/tui/render.js +58 -1501
  456. package/dist/tui/result-facts.js +64 -0
  457. package/dist/tui/task-observation.js +59 -0
  458. package/dist/tui/task-store.js +366 -0
  459. package/dist/tui/text-values.js +16 -0
  460. package/dist/tui/view-connections.js +47 -0
  461. package/dist/tui/view-model.js +94 -0
  462. package/dist/tui/view-persistence.js +51 -0
  463. package/dist/tui/view-ports.js +1 -0
  464. package/dist/tui/view-session.js +37 -0
  465. package/dist/tui/widget-details.js +144 -0
  466. package/dist/tui/widget-status.js +245 -0
  467. package/dist/tui/widget.js +145 -0
  468. package/dist/tui/workflow-labels.js +223 -0
  469. package/dist/tui/workflow-outcomes.js +124 -0
  470. package/docs/code-quality.md +38 -0
  471. package/docs/development.md +99 -0
  472. package/docs/intercom-bridge.md +77 -22
  473. package/docs/intercom.md +132 -106
  474. package/docs/performance.md +19 -17
  475. package/docs/reference.md +1472 -0
  476. package/generated/RunValidators.d.ts +28 -0
  477. package/generated/RunValidators.js +2 -0
  478. package/package.json +27 -6
  479. package/prompts/parallel-cleanup.md +3 -0
  480. package/prompts/parallel-context-build.md +3 -0
  481. package/prompts/parallel-handoff-plan.md +6 -0
  482. package/prompts/parallel-research.md +4 -0
  483. package/prompts/parallel-review.md +2 -0
  484. package/prompts/review-loop.md +2 -0
  485. package/scripts/build.mjs +129 -85
  486. package/scripts/check-compat.mjs +48 -11
  487. package/scripts/compat-host.mjs +35 -7
  488. package/scripts/generate-run-schemas.mjs +210 -0
  489. package/scripts/intercom-bridge-credentials.mjs +158 -29
  490. package/scripts/intercom-remote.mjs +213 -52
  491. package/scripts/local-install-smoke.mjs +47 -37
  492. package/scripts/native-package-smoke.mjs +135 -25
  493. package/scripts/package-smoke.mjs +360 -140
  494. package/scripts/prepare.mjs +54 -34
  495. package/scripts/real-pi-processes.mjs +261 -0
  496. package/scripts/real-pi-smoke.mjs +432 -351
  497. package/scripts/run-tests.mjs +29 -15
  498. package/skills/pi-intercom/SKILL.md +11 -11
  499. package/skills/pi-intercom/references/peer-sessions.md +7 -2
  500. package/skills/pi-subagents/SKILL.md +1 -1
  501. package/skills/pi-subagents/references/full-orchestration-guide.md +254 -112
  502. package/src/agents/agent-defaults.ts +13 -0
  503. package/src/agents/agent-definition.ts +253 -0
  504. package/src/agents/agent-management.ts +111 -808
  505. package/src/agents/agent-scope.ts +7 -3
  506. package/src/agents/agent-selection.ts +26 -16
  507. package/src/agents/agent-serializer.ts +84 -93
  508. package/src/agents/agents.ts +199 -906
  509. package/src/agents/builtin-overrides.ts +297 -0
  510. package/src/agents/chain-serializer.ts +3 -329
  511. package/src/agents/config-values.ts +57 -0
  512. package/src/agents/discovery-paths.ts +134 -0
  513. package/src/agents/frontmatter.ts +35 -17
  514. package/src/agents/identity.ts +49 -18
  515. package/src/agents/json-chain.ts +305 -0
  516. package/src/agents/management-config.ts +342 -0
  517. package/src/agents/management-format.ts +243 -0
  518. package/src/agents/management-persistence.ts +408 -0
  519. package/src/agents/management-targets.ts +260 -0
  520. package/src/agents/management-warnings.ts +115 -0
  521. package/src/agents/markdown-chain.ts +237 -0
  522. package/src/agents/package-agents.ts +231 -0
  523. package/src/agents/skill-files.ts +182 -0
  524. package/src/agents/skill-search-paths.ts +289 -0
  525. package/src/agents/skill-types.ts +31 -0
  526. package/src/agents/skills.ts +152 -610
  527. package/src/extension/acceptance-schema.ts +88 -0
  528. package/src/extension/chain-schema.ts +276 -0
  529. package/src/extension/child-runtime.ts +302 -0
  530. package/src/extension/compact-tools.ts +259 -102
  531. package/src/extension/config-schema.ts +59 -0
  532. package/src/extension/config.ts +39 -17
  533. package/src/extension/control-notice-schema.ts +55 -0
  534. package/src/extension/control-notices.ts +57 -33
  535. package/src/extension/delegation-bridges.ts +59 -0
  536. package/src/extension/doctor.ts +171 -135
  537. package/src/extension/everyday-schemas.ts +252 -0
  538. package/src/extension/fanout-child.ts +29 -308
  539. package/src/extension/index.ts +17 -642
  540. package/src/extension/message-renderers.ts +334 -0
  541. package/src/extension/nested-control-inbox.ts +196 -0
  542. package/src/extension/parent-runtime.ts +405 -0
  543. package/src/extension/primary-tool.ts +130 -0
  544. package/src/extension/result-snapshot.ts +35 -0
  545. package/src/extension/runtime-reload.ts +72 -0
  546. package/src/extension/runtime-storage.ts +49 -0
  547. package/src/extension/schema-overrides.ts +114 -0
  548. package/src/extension/schemas.ts +451 -418
  549. package/src/extension/tool-input.ts +40 -13
  550. package/src/extension/tool-result.ts +45 -14
  551. package/src/history/answer-texts.ts +74 -0
  552. package/src/history/canonical-result.ts +127 -51
  553. package/src/history/canonical-selection.ts +54 -0
  554. package/src/history/database-generation.ts +86 -0
  555. package/src/history/database.ts +86 -48
  556. package/src/history/entry-ingestion.ts +129 -0
  557. package/src/history/index.ts +426 -148
  558. package/src/history/ingest.ts +372 -342
  559. package/src/history/output-digests.ts +41 -0
  560. package/src/history/preview.ts +235 -0
  561. package/src/history/prune.ts +48 -0
  562. package/src/history/queries.ts +122 -256
  563. package/src/history/query-scope.ts +278 -0
  564. package/src/history/rows.ts +118 -0
  565. package/src/history/run-projection.ts +164 -0
  566. package/src/history/run-queries.ts +199 -0
  567. package/src/history/schema.ts +34 -0
  568. package/src/history/search-queries.ts +216 -0
  569. package/src/history/selected-record.ts +140 -0
  570. package/src/history/source-file.ts +51 -0
  571. package/src/history/source-watches.ts +156 -0
  572. package/src/history/store.ts +310 -197
  573. package/src/history/text-chunks.ts +181 -0
  574. package/src/history/text.ts +43 -0
  575. package/src/history/transcript-queries.ts +251 -0
  576. package/src/history/types.ts +27 -164
  577. package/src/history/values.ts +36 -0
  578. package/src/history/wire-inputs.ts +141 -0
  579. package/src/history/wire-models.ts +51 -0
  580. package/src/history/worker-runtime.ts +502 -0
  581. package/src/history/worker.ts +94 -336
  582. package/src/intercom/intercom-bridge.ts +136 -69
  583. package/src/intercom/live-intercom.ts +197 -92
  584. package/src/intercom/nested-result-tree.ts +213 -0
  585. package/src/intercom/result-delivery.ts +56 -0
  586. package/src/intercom/result-intercom.ts +364 -382
  587. package/src/pi-intercom/agent-dir.ts +9 -3
  588. package/src/pi-intercom/async.ts +33 -0
  589. package/src/pi-intercom/bridge-ask.ts +64 -0
  590. package/src/pi-intercom/bridge-config.ts +120 -0
  591. package/src/pi-intercom/bridge-http.ts +97 -0
  592. package/src/pi-intercom/bridge-launch-policy.ts +109 -0
  593. package/src/pi-intercom/bridge-launch.ts +317 -0
  594. package/src/pi-intercom/bridge-protocol.ts +123 -0
  595. package/src/pi-intercom/bridge-routes.ts +105 -0
  596. package/src/pi-intercom/bridge-rpc-process.ts +293 -0
  597. package/src/pi-intercom/bridge-session.ts +510 -0
  598. package/src/pi-intercom/bridge.ts +305 -549
  599. package/src/pi-intercom/broker/broker.ts +326 -263
  600. package/src/pi-intercom/broker/client.ts +273 -354
  601. package/src/pi-intercom/broker/connection.ts +192 -0
  602. package/src/pi-intercom/broker/framing.ts +11 -6
  603. package/src/pi-intercom/broker/paths.ts +28 -25
  604. package/src/pi-intercom/broker/pid.ts +68 -24
  605. package/src/pi-intercom/broker/snapshot-reader.ts +91 -0
  606. package/src/pi-intercom/broker/snapshots.ts +241 -0
  607. package/src/pi-intercom/broker/spawn.ts +58 -34
  608. package/src/pi-intercom/config.ts +113 -108
  609. package/src/pi-intercom/connection.ts +346 -0
  610. package/src/pi-intercom/identity.ts +71 -0
  611. package/src/pi-intercom/inbound-delivery.ts +450 -0
  612. package/src/pi-intercom/inbound-journal.ts +350 -0
  613. package/src/pi-intercom/inbound-record.ts +141 -0
  614. package/src/pi-intercom/inbound.ts +237 -0
  615. package/src/pi-intercom/index.ts +86 -2923
  616. package/src/pi-intercom/inspection.ts +82 -0
  617. package/src/pi-intercom/interview.ts +295 -0
  618. package/src/pi-intercom/lifecycle.ts +183 -0
  619. package/src/pi-intercom/message-format.ts +77 -0
  620. package/src/pi-intercom/outbound.ts +418 -0
  621. package/src/pi-intercom/peer-awareness.ts +84 -0
  622. package/src/pi-intercom/reply-tracker.ts +121 -59
  623. package/src/pi-intercom/reply-wait.ts +247 -0
  624. package/src/pi-intercom/runtime-types.ts +97 -0
  625. package/src/pi-intercom/session-events.ts +247 -0
  626. package/src/pi-intercom/session-targets.ts +124 -72
  627. package/src/pi-intercom/subagent-events.ts +299 -0
  628. package/src/pi-intercom/subagent-live-events.ts +212 -109
  629. package/src/pi-intercom/supervisor.ts +362 -0
  630. package/src/pi-intercom/tool-arguments.ts +120 -0
  631. package/src/pi-intercom/tool-registration.ts +121 -0
  632. package/src/pi-intercom/tool-results.ts +54 -0
  633. package/src/pi-intercom/tool-schemas.ts +134 -0
  634. package/src/pi-intercom/tools.ts +80 -0
  635. package/src/pi-intercom/topic-actions.ts +243 -0
  636. package/src/pi-intercom/topics.ts +267 -121
  637. package/src/pi-intercom/transport.ts +14 -0
  638. package/src/pi-intercom/types.ts +288 -190
  639. package/src/pi-intercom/ui/compose-paste.ts +109 -0
  640. package/src/pi-intercom/ui/compose.ts +173 -157
  641. package/src/pi-intercom/ui/inline-message.ts +80 -62
  642. package/src/pi-intercom/ui/message-renderers.ts +182 -0
  643. package/src/pi-intercom/ui/overlay.ts +238 -0
  644. package/src/pi-intercom/ui/session-format.ts +146 -0
  645. package/src/pi-intercom/ui/session-list.ts +145 -62
  646. package/src/pi-intercom/ui/tool-renderers.ts +148 -0
  647. package/src/pi-intercom/ui/topic-view.ts +107 -0
  648. package/src/pi-intercom/validation.ts +69 -0
  649. package/src/runs/background/async-chain-plan.ts +451 -0
  650. package/src/runs/background/async-control.ts +201 -71
  651. package/src/runs/background/async-execution.ts +206 -829
  652. package/src/runs/background/async-job-admission.ts +54 -0
  653. package/src/runs/background/async-job-projection.ts +225 -0
  654. package/src/runs/background/async-job-tracker.ts +416 -360
  655. package/src/runs/background/async-launch.ts +319 -0
  656. package/src/runs/background/async-plan.ts +285 -0
  657. package/src/runs/background/async-result-file.ts +79 -37
  658. package/src/runs/background/async-resume.ts +338 -285
  659. package/src/runs/background/async-run-discovery.ts +270 -0
  660. package/src/runs/background/async-run-location.ts +194 -0
  661. package/src/runs/background/async-run-record.ts +14 -0
  662. package/src/runs/background/async-run-repair.ts +307 -0
  663. package/src/runs/background/async-run-summary.ts +71 -0
  664. package/src/runs/background/async-single-plan.ts +168 -0
  665. package/src/runs/background/async-status-projection.ts +188 -0
  666. package/src/runs/background/async-status-render.ts +164 -0
  667. package/src/runs/background/async-status-validation.ts +238 -0
  668. package/src/runs/background/async-status.ts +9 -481
  669. package/src/runs/background/async-value.ts +29 -0
  670. package/src/runs/background/completion-dedupe.ts +103 -54
  671. package/src/runs/background/completion-delivery.ts +313 -187
  672. package/src/runs/background/completion-receipts.ts +309 -0
  673. package/src/runs/background/legacy-status-acceptance.ts +38 -0
  674. package/src/runs/background/notify.ts +167 -106
  675. package/src/runs/background/parallel-groups.ts +95 -46
  676. package/src/runs/background/result-payload.ts +268 -0
  677. package/src/runs/background/result-watcher.ts +484 -387
  678. package/src/runs/background/run-id-resolver.ts +126 -57
  679. package/src/runs/background/run-schemas.ts +240 -0
  680. package/src/runs/background/run-status-contracts.ts +46 -0
  681. package/src/runs/background/run-status-render.ts +364 -0
  682. package/src/runs/background/run-status.ts +338 -356
  683. package/src/runs/background/runner-attempt-output.ts +281 -0
  684. package/src/runs/background/runner-attempt.ts +354 -0
  685. package/src/runs/background/runner-child-executor.ts +232 -0
  686. package/src/runs/background/runner-child-observer.ts +270 -0
  687. package/src/runs/background/runner-completion-summary.ts +106 -0
  688. package/src/runs/background/runner-completion.ts +274 -0
  689. package/src/runs/background/runner-config.ts +52 -0
  690. package/src/runs/background/runner-contract.ts +51 -0
  691. package/src/runs/background/runner-dynamic.ts +185 -0
  692. package/src/runs/background/runner-finalization.ts +203 -0
  693. package/src/runs/background/runner-graph.ts +91 -0
  694. package/src/runs/background/runner-initial-status.ts +72 -0
  695. package/src/runs/background/runner-lifecycle.ts +159 -0
  696. package/src/runs/background/runner-monitor.ts +399 -0
  697. package/src/runs/background/runner-parallel.ts +280 -0
  698. package/src/runs/background/runner-prepare-step.ts +128 -0
  699. package/src/runs/background/runner-reporting.ts +192 -0
  700. package/src/runs/background/runner-status.ts +220 -0
  701. package/src/runs/background/runner-step-output.ts +158 -0
  702. package/src/runs/background/runner-step-record.ts +76 -0
  703. package/src/runs/background/runner-step.ts +150 -0
  704. package/src/runs/background/runner-streaming.ts +220 -0
  705. package/src/runs/background/runner-workflow.ts +284 -0
  706. package/src/runs/background/stale-run-reconciler.ts +279 -336
  707. package/src/runs/background/subagent-runner-launcher.ts +67 -35
  708. package/src/runs/background/subagent-runner.ts +80 -2048
  709. package/src/runs/background/top-level-async.ts +9 -7
  710. package/src/runs/foreground/chain-clarify.ts +499 -1334
  711. package/src/runs/foreground/clarification-draft.ts +92 -0
  712. package/src/runs/foreground/clarify-contracts.ts +33 -0
  713. package/src/runs/foreground/clarify-invocation.ts +192 -65
  714. package/src/runs/foreground/clarify-text-editor.ts +229 -0
  715. package/src/runs/foreground/clarify-view.ts +222 -0
  716. package/src/runs/foreground/execution-input.ts +358 -424
  717. package/src/runs/foreground/execution-routing.ts +41 -0
  718. package/src/runs/foreground/execution-timeout.ts +92 -0
  719. package/src/runs/foreground/foreground-control.ts +333 -812
  720. package/src/runs/foreground/foreground-memory.ts +132 -0
  721. package/src/runs/foreground/invocation-context.ts +88 -0
  722. package/src/runs/foreground/invocation-execution.ts +465 -0
  723. package/src/runs/foreground/invocation-expansion.ts +58 -0
  724. package/src/runs/foreground/management-actions.ts +360 -0
  725. package/src/runs/foreground/nested-control.ts +248 -0
  726. package/src/runs/foreground/question-continuation.ts +112 -0
  727. package/src/runs/foreground/question-control.ts +297 -70
  728. package/src/runs/foreground/resume-target.ts +330 -0
  729. package/src/runs/foreground/revival-launch-policy.ts +112 -0
  730. package/src/runs/foreground/run-async-path.ts +307 -223
  731. package/src/runs/foreground/run-interrupt.ts +202 -0
  732. package/src/runs/foreground/run-nudge.ts +355 -0
  733. package/src/runs/foreground/saved-revival.ts +356 -0
  734. package/src/runs/foreground/subagent-executor.ts +195 -493
  735. package/src/runs/foreground/subagent-params.ts +314 -242
  736. package/src/runs/foreground/wait-observation.ts +160 -0
  737. package/src/runs/foreground/wait-registration.ts +18 -0
  738. package/src/runs/foreground/wait-run.ts +361 -119
  739. package/src/runs/shared/acceptance-contract.ts +410 -209
  740. package/src/runs/shared/acceptance-defaults.ts +1 -0
  741. package/src/runs/shared/acceptance-evaluation.ts +314 -240
  742. package/src/runs/shared/acceptance-finalization.ts +447 -305
  743. package/src/runs/shared/acceptance-outcome.ts +69 -0
  744. package/src/runs/shared/acceptance-reports.ts +220 -126
  745. package/src/runs/shared/acceptance-submission.ts +251 -0
  746. package/src/runs/shared/acceptance-verification.ts +118 -0
  747. package/src/runs/shared/acceptance.ts +25 -25
  748. package/src/runs/shared/chain-outputs.ts +190 -98
  749. package/src/runs/shared/child-attempt-types.ts +118 -0
  750. package/src/runs/shared/child-attempt.ts +372 -608
  751. package/src/runs/shared/child-boundaries.ts +217 -0
  752. package/src/runs/shared/child-context-filter.ts +53 -0
  753. package/src/runs/shared/child-event-observer.ts +496 -0
  754. package/src/runs/shared/child-execution-cwd.ts +139 -68
  755. package/src/runs/shared/child-execution-outcome.ts +103 -0
  756. package/src/runs/shared/child-invocation.ts +74 -0
  757. package/src/runs/shared/child-message-validation.ts +233 -0
  758. package/src/runs/shared/child-native-accounting.ts +154 -0
  759. package/src/runs/shared/child-native-observation.ts +79 -0
  760. package/src/runs/shared/child-observation-spool.ts +188 -0
  761. package/src/runs/shared/child-observations.ts +219 -89
  762. package/src/runs/shared/child-presence.ts +6 -0
  763. package/src/runs/shared/child-route-env.ts +142 -0
  764. package/src/runs/shared/child-stream-observer.ts +302 -0
  765. package/src/runs/shared/child-stream-projection.ts +160 -0
  766. package/src/runs/shared/child-structured-tool.ts +38 -0
  767. package/src/runs/shared/claude-code.ts +201 -228
  768. package/src/runs/shared/claude-model.ts +67 -0
  769. package/src/runs/shared/claude-session.ts +124 -0
  770. package/src/runs/shared/claude-types.ts +31 -0
  771. package/src/runs/shared/completion-guard.ts +35 -26
  772. package/src/runs/shared/control-event-policy.ts +15 -0
  773. package/src/runs/shared/control-notification-owner.ts +20 -0
  774. package/src/runs/shared/dynamic-fanout.ts +402 -238
  775. package/src/runs/shared/dynamic-item.ts +114 -0
  776. package/src/runs/shared/history-index.ts +112 -51
  777. package/src/runs/shared/legacy-async-recovery.ts +153 -0
  778. package/src/runs/shared/legacy-output-recovery.ts +214 -0
  779. package/src/runs/shared/mcp-config-schema.ts +46 -0
  780. package/src/runs/shared/mcp-direct-tool-allowlist.ts +396 -307
  781. package/src/runs/shared/model-fallback.ts +302 -180
  782. package/src/runs/shared/mutating-tool-guard.ts +259 -158
  783. package/src/runs/shared/native-finalization-types.ts +29 -0
  784. package/src/runs/shared/native-finalization.ts +233 -109
  785. package/src/runs/shared/native-usage.ts +379 -96
  786. package/src/runs/shared/nested-control-records.ts +178 -0
  787. package/src/runs/shared/nested-events.ts +45 -752
  788. package/src/runs/shared/nested-path.ts +47 -34
  789. package/src/runs/shared/nested-projection.ts +230 -0
  790. package/src/runs/shared/nested-protocol.ts +67 -0
  791. package/src/runs/shared/nested-registry.ts +273 -0
  792. package/src/runs/shared/nested-render.ts +198 -100
  793. package/src/runs/shared/nested-route.ts +292 -0
  794. package/src/runs/shared/nested-validation.ts +291 -0
  795. package/src/runs/shared/owned-child-evidence.ts +184 -0
  796. package/src/runs/shared/owned-run-list.ts +187 -0
  797. package/src/runs/shared/owned-run-observation.ts +368 -0
  798. package/src/runs/shared/owned-run-progress.ts +172 -0
  799. package/src/runs/shared/owned-run-read-state.ts +7 -0
  800. package/src/runs/shared/owned-run-results.ts +289 -0
  801. package/src/runs/shared/owned-run-status.ts +239 -0
  802. package/src/runs/shared/owned-run-view.ts +65 -0
  803. package/src/runs/shared/owned-workflow-details.ts +24 -0
  804. package/src/runs/shared/owner-output.ts +73 -0
  805. package/src/runs/shared/parallel-utils.ts +93 -129
  806. package/src/runs/shared/parent-receipts.ts +177 -50
  807. package/src/runs/shared/parent-usage.ts +314 -91
  808. package/src/runs/shared/pi-args.ts +241 -311
  809. package/src/runs/shared/pi-launch-input.ts +48 -0
  810. package/src/runs/shared/pi-session-launch.ts +94 -0
  811. package/src/runs/shared/pi-spawn.ts +57 -33
  812. package/src/runs/shared/run-history-queries.ts +189 -38
  813. package/src/runs/shared/run-history.ts +197 -76
  814. package/src/runs/shared/run-id-ambiguity.ts +7 -3
  815. package/src/runs/shared/run-metadata-paths.ts +17 -0
  816. package/src/runs/shared/run-metadata.ts +240 -0
  817. package/src/runs/shared/run-persistence.ts +181 -0
  818. package/src/runs/shared/run-records.ts +19 -607
  819. package/src/runs/shared/run-restoration.ts +455 -0
  820. package/src/runs/shared/run-state-owner.ts +31 -0
  821. package/src/runs/shared/session-cwd-preload.ts +39 -19
  822. package/src/runs/shared/single-output.ts +358 -205
  823. package/src/runs/shared/stable-value.ts +15 -0
  824. package/src/runs/shared/streaming-text.ts +39 -12
  825. package/src/runs/shared/structured-output.ts +76 -46
  826. package/src/runs/shared/subagent-control.ts +330 -197
  827. package/src/runs/shared/subagent-prompt-runtime.ts +92 -174
  828. package/src/runs/shared/subagent-tool-loop-guard.ts +118 -68
  829. package/src/runs/shared/supervisor-question-state.ts +221 -0
  830. package/src/runs/shared/supervisor-questions.ts +396 -401
  831. package/src/runs/shared/workflow-graph.ts +400 -198
  832. package/src/runs/shared/workflow-policy.ts +245 -110
  833. package/src/runs/shared/worktree-contract.ts +29 -0
  834. package/src/runs/shared/worktree-hook.ts +181 -0
  835. package/src/runs/shared/worktree-location.ts +61 -0
  836. package/src/runs/shared/worktree-preservation.ts +10 -0
  837. package/src/runs/shared/worktree-process.ts +132 -0
  838. package/src/runs/shared/worktree.ts +459 -587
  839. package/src/shared/agent-context-policy.ts +176 -145
  840. package/src/shared/agent-dir.ts +15 -0
  841. package/src/shared/artifacts.ts +99 -68
  842. package/src/shared/atomic-json.ts +14 -11
  843. package/src/shared/chain-files.ts +72 -0
  844. package/src/shared/concurrency.ts +27 -0
  845. package/src/shared/content-preview.ts +117 -0
  846. package/src/shared/cooperative.ts +17 -13
  847. package/src/shared/execution-cwd.ts +34 -9
  848. package/src/shared/file-coalescer.ts +24 -22
  849. package/src/shared/fork-context.ts +52 -48
  850. package/src/shared/formatters.ts +88 -47
  851. package/src/shared/journal-frames.ts +197 -0
  852. package/src/shared/journal-projections.ts +133 -0
  853. package/src/shared/journal-reader.ts +18 -333
  854. package/src/shared/json-projection.ts +224 -0
  855. package/src/shared/lazy-tools.ts +76 -27
  856. package/src/shared/message-output.ts +146 -0
  857. package/src/shared/model-info.ts +94 -58
  858. package/src/shared/native-import.ts +25 -0
  859. package/src/shared/native-journal.ts +172 -0
  860. package/src/shared/native-session-loader.ts +13 -0
  861. package/src/shared/native-session.ts +3 -5
  862. package/src/shared/native-tui.ts +13 -3
  863. package/src/shared/native-typebox.ts +18 -3
  864. package/src/shared/output-page.ts +76 -0
  865. package/src/shared/post-exit-stdio-guard.ts +232 -172
  866. package/src/shared/prompt-sections.ts +13 -7
  867. package/src/shared/session-entries.ts +103 -44
  868. package/src/shared/session-identity.ts +17 -10
  869. package/src/shared/settings.ts +182 -367
  870. package/src/shared/status-format.ts +207 -98
  871. package/src/shared/stream-json.d.ts +10 -0
  872. package/src/shared/task-file-policy.ts +35 -0
  873. package/src/shared/temp-root.ts +122 -71
  874. package/src/shared/types/acceptance.ts +149 -0
  875. package/src/shared/types/async.ts +247 -0
  876. package/src/shared/types/config.ts +139 -0
  877. package/src/shared/types/details.ts +104 -0
  878. package/src/shared/types/history.ts +217 -0
  879. package/src/shared/types/inputs.ts +4 -0
  880. package/src/shared/types/intercom.ts +57 -0
  881. package/src/shared/types/launch.ts +39 -0
  882. package/src/shared/types/messages.ts +104 -0
  883. package/src/shared/types/nested.ts +153 -0
  884. package/src/shared/types/owned-runs.ts +148 -0
  885. package/src/shared/types/progress.ts +106 -0
  886. package/src/shared/types/questions.ts +103 -0
  887. package/src/shared/types/results.ts +119 -0
  888. package/src/shared/types/state.ts +56 -0
  889. package/src/shared/types/usage.ts +36 -0
  890. package/src/shared/types/workflow.ts +187 -0
  891. package/src/shared/types.ts +222 -1161
  892. package/src/shared/ui-mode.ts +1 -1
  893. package/src/shared/unknown.ts +59 -0
  894. package/src/shared/utils.ts +111 -305
  895. package/src/slash/command-discovery.ts +109 -0
  896. package/src/slash/command-input.ts +232 -0
  897. package/src/slash/prompt-template-bridge.ts +153 -400
  898. package/src/slash/prompt-template-contract.ts +152 -0
  899. package/src/slash/prompt-template-results.ts +189 -0
  900. package/src/slash/request-protocol.ts +87 -0
  901. package/src/slash/result-delivery.ts +312 -0
  902. package/src/slash/saved-chain.ts +115 -0
  903. package/src/slash/slash-bridge.ts +202 -156
  904. package/src/slash/slash-commands.ts +176 -598
  905. package/src/slash/slash-live-state.ts +287 -229
  906. package/src/tui/action-hints.ts +147 -61
  907. package/src/tui/activity-lines.ts +40 -0
  908. package/src/tui/agent-browser.ts +354 -0
  909. package/src/tui/agent-controls.ts +355 -0
  910. package/src/tui/agent-conversation.ts +514 -0
  911. package/src/tui/agent-dock.ts +250 -0
  912. package/src/tui/agent-history.ts +78 -203
  913. package/src/tui/agent-model.ts +78 -0
  914. package/src/tui/agent-picker.ts +325 -0
  915. package/src/tui/agent-view.ts +391 -1333
  916. package/src/tui/compact-results.ts +342 -0
  917. package/src/tui/conversation-details.ts +206 -0
  918. package/src/tui/conversation-frame.ts +184 -0
  919. package/src/tui/conversation-history.ts +246 -0
  920. package/src/tui/conversation-menu.ts +119 -0
  921. package/src/tui/conversation-notices.ts +116 -0
  922. package/src/tui/conversation-selection.ts +66 -0
  923. package/src/tui/conversation-viewport.ts +169 -0
  924. package/src/tui/direction-message.ts +35 -0
  925. package/src/tui/display.ts +409 -0
  926. package/src/tui/editor-viewport.ts +19 -0
  927. package/src/tui/expanded-content.ts +134 -0
  928. package/src/tui/expanded-multi.ts +264 -0
  929. package/src/tui/expanded-single.ts +92 -0
  930. package/src/tui/history-cards.ts +316 -0
  931. package/src/tui/history-display.ts +334 -0
  932. package/src/tui/history-json.ts +17 -0
  933. package/src/tui/history-metadata.ts +177 -0
  934. package/src/tui/history-record.ts +321 -0
  935. package/src/tui/history-text.ts +34 -0
  936. package/src/tui/indexed-history.ts +149 -0
  937. package/src/tui/nested-widget.ts +192 -0
  938. package/src/tui/picker-page.ts +80 -0
  939. package/src/tui/render.ts +100 -1546
  940. package/src/tui/result-facts.ts +99 -0
  941. package/src/tui/task-observation.ts +96 -0
  942. package/src/tui/task-store.ts +432 -0
  943. package/src/tui/text-values.ts +20 -0
  944. package/src/tui/view-connections.ts +64 -0
  945. package/src/tui/view-model.ts +181 -0
  946. package/src/tui/view-persistence.ts +57 -0
  947. package/src/tui/view-ports.ts +55 -0
  948. package/src/tui/view-session.ts +42 -0
  949. package/src/tui/widget-details.ts +220 -0
  950. package/src/tui/widget-status.ts +331 -0
  951. package/src/tui/widget.ts +208 -0
  952. package/src/tui/workflow-labels.ts +340 -0
  953. package/src/tui/workflow-outcomes.ts +150 -0
  954. package/tsconfig.build.json +11 -10
  955. package/tsconfig.json +30 -17
@@ -1,450 +1,50 @@
1
- import { constants, closeSync, fstatSync, openSync, readFileSync, statSync } from "node:fs";
2
1
  import { createServer } from "node:https";
3
- import { isAbsolute, resolve } from "node:path";
4
- import { createSecureContext } from "node:tls";
2
+ import { resolve } from "node:path";
3
+ import { TLSSocket } from "node:tls";
5
4
  import { randomUUID } from "node:crypto";
6
- import { IntercomClient } from "./broker/client.js";
7
5
  import { getBrokerSocketPath } from "./broker/paths.js";
8
- import { formatSessionTarget, resolveSessionProjectId, resolveSessionTarget } from "./session-targets.js";
9
- const BODY_LIMIT = 64 * 1024;
10
- const SEND_TIMEOUT = 8000;
11
- const LIST_TIMEOUT = 5000;
12
- const ASK_TIMEOUT = 120000;
13
- const IDLE_TIMEOUT = 5 * 60 * 1000;
14
- const MAX_CLIENTS = 64;
6
+ import { loadConfig, sameListener } from "./bridge-config.js";
7
+ import { ASK_TIMEOUT, Fault, fingerprint, log } from "./bridge-protocol.js";
8
+ import { Session } from "./bridge-session.js";
9
+ import { operationFor, routeFor } from "./bridge-routes.js";
10
+ import { reply } from "./bridge-http.js";
11
+ import { Launcher, sameIdentity } from "./bridge-launch.js";
15
12
  const MAX_CONNECTIONS = 128;
16
- const MAX_REQUESTS = 8;
17
- const INBOX_COUNT = 256;
18
- const INBOX_BYTES = 4 * 1024 * 1024;
19
- const LABEL_CONTROLS = /[\u0000-\u001f\u007f-\u009f]/;
20
- const TEXT_CONTROLS = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/;
21
- class Fault extends Error {
22
- status;
23
- code;
24
- details;
25
- constructor(status, code, message, details = {}) {
26
- super(message);
27
- this.status = status;
28
- this.code = code;
29
- this.details = details;
30
- }
31
- }
32
- function object(value, fields) {
33
- if (!value || typeof value !== "object" || Array.isArray(value))
34
- throw new Fault(400, "invalid_input", "Expected a JSON object.");
35
- const result = value;
36
- if (Object.keys(result).some((key) => !fields.includes(key)))
37
- throw new Fault(400, "invalid_input", "Unknown field.");
38
- return result;
39
- }
40
- function label(value, field, max = 512) {
41
- if (typeof value !== "string" || !value.trim() || value.length > max || LABEL_CONTROLS.test(value)) {
42
- throw new Fault(400, "invalid_input", `Invalid ${field}.`);
43
- }
44
- return value;
45
- }
46
- function text(value) {
47
- if (typeof value !== "string" || !value.trim() || TEXT_CONTROLS.test(value))
48
- throw new Fault(400, "invalid_input", "Invalid message.");
49
- return value;
50
- }
51
- function fingerprint(value) {
52
- if (typeof value !== "string" || !/^(?:[a-fA-F0-9]{64}|(?:[a-fA-F0-9]{2}:){31}[a-fA-F0-9]{2})$/.test(value)) {
53
- throw new Fault(400, "invalid_input", "Invalid SHA256 certificate fingerprint.");
54
- }
55
- return value.replaceAll(":", "").toUpperCase();
56
- }
57
- function readFile(path, privateFile, limit) {
58
- const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
59
- try {
60
- const stat = fstatSync(fd);
61
- if (!stat.isFile() || stat.size > limit)
62
- throw new Error("Configuration/TLS file must be a bounded regular file.");
63
- if (privateFile && (stat.uid !== process.getuid?.() || (stat.mode & 0o777) !== 0o600)) {
64
- throw new Error("Config and TLS key must be owned by the current user with mode 0600.");
65
- }
66
- const data = readFileSync(fd);
67
- if (data.length > limit)
68
- throw new Error("Configuration/TLS file exceeds its size limit.");
69
- return data;
70
- }
71
- finally {
72
- closeSync(fd);
73
- }
74
- }
75
- function loadConfig(path) {
76
- const input = object(JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(readFile(path, true, BODY_LIMIT))), ["port", "ca", "cert", "key", "clients"]);
77
- const port = input.port === undefined ? 9443 : input.port;
78
- if (!Number.isInteger(port) || typeof port !== "number" || port < 0 || port > 65535)
79
- throw new Error("Invalid HTTPS port.");
80
- const ca = label(input.ca, "ca path", 4096);
81
- const cert = label(input.cert, "cert path", 4096);
82
- const key = label(input.key, "key path", 4096);
83
- if (![ca, cert, key].every(isAbsolute))
84
- throw new Error("TLS paths must be absolute.");
85
- if (!Array.isArray(input.clients) || input.clients.length > MAX_CLIENTS)
86
- throw new Error(`clients must be an array of at most ${MAX_CLIENTS} identities.`);
87
- const clients = new Map();
88
- const names = new Set();
89
- for (const raw of input.clients) {
90
- const entry = object(raw, ["fingerprint256", "name", "cwd"]);
91
- const fingerprint256 = fingerprint(entry.fingerprint256);
92
- const name = label(entry.name, "name", 128).trim();
93
- const cwd = label(entry.cwd, "cwd", 4096);
94
- if (!isAbsolute(cwd) || !statSync(cwd).isDirectory())
95
- throw new Error("Client cwd must be an existing absolute directory.");
96
- if (clients.has(fingerprint256) || names.has(name.toLowerCase()))
97
- throw new Error("Client fingerprints and names must be unique.");
98
- clients.set(fingerprint256, { fingerprint256, name, cwd });
99
- names.add(name.toLowerCase());
100
- }
101
- const tls = { ca: readFile(ca, false, 1024 * 1024), cert: readFile(cert, false, 1024 * 1024), key: readFile(key, true, 1024 * 1024) };
102
- createSecureContext({ ...tls, minVersion: "TLSv1.2" });
103
- return { port, ca, cert, key, clients, tls };
104
- }
105
- let logBackpressure = false;
106
- let lostLogEvents = 0;
107
- function log(event, fields = {}) {
108
- if (logBackpressure) {
109
- lostLogEvents = Math.min(Number.MAX_SAFE_INTEGER, lostLogEvents + 1);
110
- return;
111
- }
112
- logBackpressure = !process.stdout.write(`${JSON.stringify({ timestamp: new Date().toISOString(), event, ...fields })}\n`);
113
- if (logBackpressure)
114
- process.stdout.once("drain", () => {
115
- logBackpressure = false;
116
- if (lostLogEvents) {
117
- const lostEvents = lostLogEvents;
118
- lostLogEvents = 0;
119
- log("log_overflow", { result: "metadata_lost", lostEvents });
120
- }
121
- });
122
- }
123
- function cancelled(signal) {
124
- if (signal.aborted)
125
- throw signal.reason;
126
- }
127
- function wait(promise, signal) {
128
- if (signal.aborted) {
129
- void promise.catch(() => { });
130
- throw signal.reason;
131
- }
132
- return new Promise((resolve, reject) => {
133
- const abort = () => { cleanup(); reject(signal.reason); };
134
- const cleanup = () => signal.removeEventListener("abort", abort);
135
- signal.addEventListener("abort", abort, { once: true });
136
- promise.then((value) => { cleanup(); resolve(value); }, (error) => { cleanup(); reject(error); });
137
- });
138
- }
139
- async function body(req, signal) {
140
- const type = req.headers["content-type"];
141
- if (typeof type !== "string" || !/^application\/json(?:\s*;\s*charset=utf-8)?$/i.test(type))
142
- throw new Fault(415, "content_type", "Use application/json.");
143
- const length = req.headers["content-length"];
144
- if (length !== undefined && (!/^\d+$/.test(length) || Number(length) > BODY_LIMIT))
145
- throw new Fault(413, "body_too_large", "JSON body exceeds 64 KiB.");
146
- const data = await new Promise((resolve, reject) => {
147
- const chunks = [];
148
- let size = 0;
149
- const cleanup = () => {
150
- clearTimeout(timer);
151
- req.off("data", onData);
152
- req.off("end", onEnd);
153
- req.off("error", onError);
154
- signal.removeEventListener("abort", onAbort);
155
- };
156
- const fail = (error) => { cleanup(); req.pause(); reject(error); };
157
- const onAbort = () => fail(signal.reason);
158
- const onError = () => fail(new Fault(400, "invalid_body", "Could not read JSON body."));
159
- const onData = (chunk) => {
160
- size += chunk.length;
161
- if (size > BODY_LIMIT) {
162
- fail(new Fault(413, "body_too_large", "JSON body exceeds 64 KiB."));
163
- return;
164
- }
165
- chunks.push(chunk);
166
- };
167
- const onEnd = () => { cleanup(); resolve(Buffer.concat(chunks)); };
168
- const timer = setTimeout(() => fail(new Fault(408, "body_timeout", "JSON body deadline exceeded.")), 10000);
169
- signal.addEventListener("abort", onAbort, { once: true });
170
- req.on("data", onData);
171
- req.once("end", onEnd);
172
- req.once("error", onError);
173
- if (signal.aborted)
174
- onAbort();
175
- });
176
- try {
177
- return JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(data));
178
- }
179
- catch {
180
- throw new Fault(400, "invalid_json", "Invalid UTF-8 JSON body.");
181
- }
182
- }
183
- class Session {
184
- bridge;
185
- identity;
186
- expires;
187
- client = new IntercomClient({ sendTimeoutMs: SEND_TIMEOUT, listTimeoutMs: LIST_TIMEOUT });
188
- requests = new Set();
189
- inbox = new Map();
190
- inboxBytes = 0;
191
- pending;
192
- closed = false;
193
- connecting;
194
- disconnecting;
195
- lease;
196
- closeReason;
197
- constructor(bridge, identity, expires) {
198
- this.bridge = bridge;
199
- this.identity = identity;
200
- this.expires = expires;
201
- this.client.on("error", () => this.close(new Fault(503, "broker_unavailable", "Broker connection failed.")));
202
- this.client.on("disconnected", () => this.close(new Fault(503, "broker_disconnected", "Broker disconnected.")));
203
- this.client.on("_registered", () => {
204
- // IntercomClient cannot abort socket acquisition; retire a cancelled generation before any application write.
205
- if (this.closed)
206
- void this.disconnect();
207
- });
208
- this.client.on("message", (from, message) => this.receive(from, message));
209
- this.client.on("session_left", (id) => {
210
- if (this.pending?.peer === id && !this.pending.settled)
211
- this.pending.reject(new Fault(409, "peer_offline", "Ask recipient disconnected.", { id: this.pending.id }));
212
- for (const [messageId, entry] of this.inbox) {
213
- if (entry.from.id === id && entry.message.expectsReply) {
214
- this.remove(messageId);
215
- this.bridge.lost(this.identity.fingerprint256);
216
- }
217
- }
218
- this.presence();
219
- });
220
- this.touch();
221
- }
222
- guard(signal) {
223
- if (signal)
224
- cancelled(signal);
225
- if (this.closed)
226
- throw this.closeReason;
227
- if (!this.bridge.allowed(this.identity))
228
- throw new Fault(403, "revoked", "Identity is no longer authorized.");
229
- if (this.expires <= Date.now()) {
230
- const error = new Fault(403, "certificate_expired", "Client certificate expired.");
231
- this.close(error);
232
- throw error;
233
- }
234
- }
235
- touch() {
236
- clearTimeout(this.lease);
237
- this.lease = setTimeout(() => this.close(this.expires <= Date.now()
238
- ? new Fault(403, "certificate_expired", "Client certificate expired.")
239
- : new Fault(410, "lease_expired", "Bridge session idle lease expired.")), Math.max(1, Math.min(IDLE_TIMEOUT, this.expires - Date.now())));
240
- }
241
- async connect(signal) {
242
- this.guard(signal);
243
- if (!this.connecting) {
244
- this.connecting = (async () => {
245
- const projectId = await resolveSessionProjectId(this.identity.cwd);
246
- this.guard();
247
- await this.client.connect({ name: this.identity.name, cwd: this.identity.cwd, model: "remote-bridge", projectId, status: "idle", acceptsAsks: true, pendingAsks: 0 }, `remote-${this.identity.fingerprint256.toLowerCase()}`);
248
- if (this.closed) {
249
- await this.disconnect();
250
- return;
251
- }
252
- if (this.client.sessionId !== `remote-${this.identity.fingerprint256.toLowerCase()}`) {
253
- this.close(new Fault(409, "identity_in_use", "This remote identity is already registered by another connection."));
254
- throw new Fault(409, "identity_in_use", "This remote identity is already registered by another connection.");
255
- }
256
- log("registered", this.fields());
257
- })().catch((error) => {
258
- if (!this.closed)
259
- this.close(error instanceof Fault ? error : new Fault(503, "broker_unavailable", "Local broker is unavailable; bridge does not start it."));
260
- throw error;
261
- }).finally(() => { if (this.closed)
262
- void this.disconnect(); });
263
- }
264
- await wait(this.connecting, signal);
265
- this.guard(signal);
266
- }
267
- fields() { return { name: this.identity.name, fingerprint: this.identity.fingerprint256 }; }
268
- disconnect() {
269
- if (!this.disconnecting) {
270
- this.disconnecting = this.client.disconnect().finally(() => { this.disconnecting = undefined; });
271
- }
272
- return this.disconnecting;
273
- }
274
- close(error) {
275
- if (this.closed)
276
- return;
277
- this.closed = true;
278
- this.closeReason = error;
279
- clearTimeout(this.lease);
280
- this.pending?.reject(error);
281
- for (const controller of this.requests)
282
- controller.abort(error);
283
- this.bridge.lost(this.identity.fingerprint256, this.inbox.size);
284
- this.inbox.clear();
285
- this.inboxBytes = 0;
286
- // Keep the retiring generation in the map until socket acquisition/registration has settled.
287
- // Otherwise rapid aborts could create an unbounded number of unfinished broker connects.
288
- const disconnect = this.disconnect();
289
- void (async () => {
290
- await disconnect;
291
- try {
292
- await this.connecting;
293
- }
294
- catch { /* The original request receives the connect failure. */ }
295
- await this.disconnect();
296
- if (this.bridge.sessions.get(this.identity.fingerprint256) === this)
297
- this.bridge.sessions.delete(this.identity.fingerprint256);
298
- log("session_closed", { ...this.fields(), result: error.code });
299
- })();
300
- }
301
- receive(from, message) {
302
- if (this.closed)
303
- return;
304
- try {
305
- this.guard();
306
- }
307
- catch (error) {
308
- this.close(error instanceof Fault ? error : new Fault(403, "revoked", "Identity is no longer authorized."));
309
- return;
310
- }
311
- log("received", { ...this.fields(), peer: from.id, messageId: message.id, result: "received" });
312
- const pending = this.pending;
313
- if (pending && !pending.settled && pending.id === message.replyTo && pending.peer === from.id) {
314
- pending.resolve({ from, message });
315
- return;
316
- }
317
- if (this.inbox.has(message.id)) {
318
- this.bridge.lost(this.identity.fingerprint256);
319
- return;
320
- }
321
- const bytes = Buffer.byteLength(JSON.stringify({ from, message }));
322
- if (this.inbox.size >= INBOX_COUNT || this.inboxBytes + bytes > INBOX_BYTES) {
323
- this.bridge.lost(this.identity.fingerprint256);
324
- log("inbox_overflow", { ...this.fields(), peer: from.id, messageId: message.id, result: "lost" });
325
- return;
326
- }
327
- // ponytail: inbox is bounded and volatile; use a private durable journal if restart/lease-loss recovery becomes required.
328
- this.inbox.set(message.id, { from, message, bytes, replying: false });
329
- this.inboxBytes += bytes;
330
- this.presence();
331
- }
332
- remove(id) {
333
- const entry = this.inbox.get(id);
334
- if (entry) {
335
- this.inboxBytes -= entry.bytes;
336
- this.inbox.delete(id);
337
- }
338
- }
339
- presence() {
340
- if (this.closed)
341
- return;
342
- const pendingAsks = [...this.inbox.values()].filter((entry) => entry.message.expectsReply).length;
343
- this.client.updatePresence({ pendingAsks, acceptsAsks: this.inbox.size < INBOX_COUNT && this.inboxBytes < INBOX_BYTES });
344
- }
345
- async peers(signal) {
346
- this.guard(signal);
347
- let peers;
348
- try {
349
- peers = await wait(this.client.listSessions(), signal);
350
- }
351
- catch (error) {
352
- if (signal.aborted)
353
- throw signal.reason;
354
- if (error instanceof Error && error.message === "List sessions timeout")
355
- throw new Fault(504, "list_timeout", "Broker session list deadline exceeded.");
356
- throw new Fault(503, "broker_unavailable", "Broker session list failed.");
357
- }
358
- this.guard(signal);
359
- return peers;
360
- }
361
- async target(to, signal) {
362
- const resolution = resolveSessionTarget(await this.peers(signal), to);
363
- if (resolution.status === "none")
364
- throw new Fault(404, "peer_offline", "Peer is not connected.");
365
- if (resolution.status !== "found" || !resolution.target)
366
- throw new Fault(409, "ambiguous_target", "Use an unambiguous peer name or session ID (prefixes need at least 8 characters).");
367
- if (resolution.target.id === this.client.sessionId)
368
- throw new Fault(400, "self_target", "Choose another peer.");
369
- return resolution.target;
370
- }
371
- async send(peer, message, signal, requestId, options = {}) {
372
- this.guard(signal);
373
- const id = options.messageId ?? randomUUID();
374
- log("send", { ...this.fields(), requestId, peer, messageId: id, result: "attempt" });
375
- let result;
376
- try {
377
- result = await wait(this.client.send(peer, { text: message, delivery: "steer", ...options, messageId: id }), signal);
378
- }
379
- catch (error) {
380
- if (signal.aborted)
381
- throw signal.reason;
382
- const fault = new Fault(504, "send_unconfirmed", "Broker acknowledgement was not received; delivery is unknown. Do not automatically retry.", { id, deliveryUnknown: true });
383
- this.close(fault);
384
- throw fault;
385
- }
386
- this.guard(signal);
387
- log("send_result", { ...this.fields(), requestId, peer, messageId: id, result: result.accepted ? "accepted" : "rejected" });
388
- if (!result.accepted)
389
- throw new Fault(409, "delivery_failed", result.reason ?? "Broker rejected delivery.", { id, accepted: false, delivered: false });
390
- return result;
391
- }
392
- async ask(to, message, timeoutMs, signal, requestId) {
393
- if (this.pending)
394
- throw new Fault(409, "ask_in_progress", "Only one concurrent ask is allowed per identity.");
395
- const id = randomUUID();
396
- let waiting;
397
- let timer;
398
- let receipt;
399
- const reply = new Promise((resolve, reject) => {
400
- waiting = { id, settled: false,
401
- resolve: (answer) => { waiting.settled = true; clearTimeout(timer); resolve(answer); },
402
- reject: (error) => { waiting.settled = true; clearTimeout(timer); reject(error); },
403
- };
404
- });
405
- // A disconnect may reject the reply while target lookup or send acknowledgement is still pending.
406
- void reply.catch(() => { });
407
- this.pending = waiting;
408
- const abort = () => waiting.reject(signal.reason);
409
- signal.addEventListener("abort", abort, { once: true });
410
- try {
411
- await this.connect(signal);
412
- const peer = await this.target(to, signal);
413
- this.guard(signal);
414
- waiting.peer = peer.id;
415
- timer = setTimeout(() => {
416
- const fault = new Fault(504, "ask_timeout", "Peer did not reply before the ask deadline.", { id, ...(receipt ? { accepted: receipt.accepted, delivered: receipt.delivered } : { deliveryUnknown: true }) });
417
- if (receipt)
418
- waiting.reject(fault);
419
- else
420
- this.close(fault);
421
- }, timeoutMs);
422
- receipt = await this.send(peer.id, message, signal, requestId, { messageId: id, expectsReply: true });
423
- const answer = await wait(reply, signal);
424
- this.guard(signal);
425
- return { ...receipt, replied: true, reply: answer };
426
- }
427
- finally {
428
- clearTimeout(timer);
429
- signal.removeEventListener("abort", abort);
430
- if (this.pending === waiting)
431
- this.pending = undefined;
432
- }
433
- }
434
- }
435
13
  class Bridge {
436
14
  config;
437
15
  configPath;
438
16
  sessions = new Map();
439
17
  losses = new Map();
18
+ launcher = new Launcher();
440
19
  sockets = new Set();
441
20
  connections = new Map();
442
21
  server;
443
22
  stopping = false;
23
+ handlers = new Set();
444
24
  constructor(config, configPath) {
445
25
  this.config = config;
446
26
  this.configPath = configPath;
447
- this.server = createServer({ ...config.tls, minVersion: "TLSv1.2", requestCert: true, rejectUnauthorized: true, handshakeTimeout: 10000 }, (req, res) => { void this.handle(req, res); });
27
+ this.server = createServer({
28
+ ...config.tls,
29
+ minVersion: "TLSv1.2",
30
+ requestCert: true,
31
+ rejectUnauthorized: true,
32
+ handshakeTimeout: 10000,
33
+ }, (req, res) => {
34
+ const handler = this.handle(req, res).catch(() => {
35
+ // A response write can fail after route handling; close transport and retain metadata only.
36
+ res.destroy();
37
+ log("request", { result: "transport_failed" });
38
+ });
39
+ this.handlers.add(handler);
40
+ handler
41
+ .finally(() => {
42
+ this.handlers.delete(handler);
43
+ })
44
+ .catch(() => {
45
+ res.destroy();
46
+ });
47
+ });
448
48
  this.server.maxConnections = MAX_CONNECTIONS;
449
49
  this.server.requestTimeout = 10000;
450
50
  this.server.headersTimeout = 10000;
@@ -457,20 +57,46 @@ class Bridge {
457
57
  return;
458
58
  }
459
59
  this.sockets.add(socket);
460
- socket.once("close", () => this.sockets.delete(socket));
60
+ socket.once("close", () => {
61
+ this.sockets.delete(socket);
62
+ });
461
63
  });
462
64
  this.server.on("secureConnection", (socket) => this.authenticate(socket));
463
65
  this.server.on("tlsClientError", () => log("connection_rejected", { result: "tls_authentication_failed" }));
464
- this.server.on("clientError", (_error, socket) => socket.destroy());
465
- this.server.on("error", () => { log("server_error", { result: "listener_failed" }); process.exitCode = 1; this.shutdown(); });
66
+ this.server.on("clientError", (_error, socket) => {
67
+ socket.destroy();
68
+ });
69
+ this.server.on("error", () => {
70
+ log("server_error", { result: "listener_failed" });
71
+ process.exitCode = 1;
72
+ this.stop();
73
+ });
466
74
  }
467
75
  allowed(identity) {
468
76
  const current = this.config.clients.get(identity.fingerprint256);
469
- return !this.stopping && current?.name === identity.name && current.cwd === identity.cwd;
77
+ return !this.stopping && sameIdentity(identity, current);
470
78
  }
471
79
  lost(fp, count = 1) {
472
- if (count && this.config.clients.has(fp))
80
+ if (count > 0 && this.config.clients.has(fp)) {
473
81
  this.losses.set(fp, Math.min(Number.MAX_SAFE_INTEGER, (this.losses.get(fp) ?? 0) + count));
82
+ }
83
+ }
84
+ session(identity, expires) {
85
+ let session = this.sessions.get(identity.fingerprint256);
86
+ if (!session) {
87
+ session = new Session({
88
+ allowed: (candidate) => this.allowed(candidate),
89
+ lost: (fp, count) => this.lost(fp, count),
90
+ losses: (fp) => this.losses.get(fp) ?? 0,
91
+ retired: (fp, generation) => {
92
+ if (this.sessions.get(fp)?.generation === generation) {
93
+ this.sessions.delete(fp);
94
+ }
95
+ },
96
+ }, identity, expires);
97
+ this.sessions.set(identity.fingerprint256, session);
98
+ }
99
+ return session;
474
100
  }
475
101
  authenticate(socket) {
476
102
  const cert = socket.getPeerCertificate();
@@ -485,12 +111,17 @@ class Bridge {
485
111
  const identity = this.config.clients.get(fp);
486
112
  const expires = Date.parse(cert.valid_to);
487
113
  const starts = Date.parse(cert.valid_from);
488
- if (!socket.authorized || !identity || this.stopping || !Number.isFinite(expires) || !Number.isFinite(starts) || starts > Date.now() || expires <= Date.now()) {
114
+ if (!socket.authorized ||
115
+ !identity ||
116
+ this.stopping ||
117
+ !Number.isFinite(expires) ||
118
+ !Number.isFinite(starts) ||
119
+ starts > Date.now() ||
120
+ expires <= Date.now()) {
489
121
  log("connection_rejected", { fingerprint: fp, result: "unauthorized" });
490
122
  socket.destroy();
491
123
  return;
492
124
  }
493
- // Timers longer than INT32_MAX fire immediately in Node; recheck long-lived certificates in bounded slices.
494
125
  const expiry = () => {
495
126
  if (expires <= Date.now()) {
496
127
  this.drop(fp, new Fault(403, "certificate_expired", "Client certificate expired."));
@@ -498,68 +129,133 @@ class Bridge {
498
129
  }
499
130
  connection.timer = setTimeout(expiry, Math.min(expires - Date.now(), 0x7fffffff));
500
131
  };
501
- const connection = { fingerprint: fp, expires, timer: setTimeout(expiry, Math.min(expires - Date.now(), 0x7fffffff)) };
132
+ // Node timers must recheck long-lived certificates in INT32_MAX-bounded slices.
133
+ const connection = {
134
+ fingerprint: fp,
135
+ expires,
136
+ timer: setTimeout(expiry, Math.min(expires - Date.now(), 0x7fffffff)),
137
+ };
502
138
  this.connections.set(socket, connection);
503
- socket.once("close", () => { clearTimeout(connection.timer); this.connections.delete(socket); });
139
+ socket.once("close", () => {
140
+ clearTimeout(connection.timer);
141
+ this.connections.delete(socket);
142
+ });
504
143
  log("connected", { name: identity.name, fingerprint: fp, result: "authenticated" });
505
144
  }
506
145
  drop(fp, error) {
507
146
  this.sessions.get(fp)?.close(error);
508
- for (const [socket, connection] of this.connections)
509
- if (connection.fingerprint === fp)
147
+ for (const [socket, connection] of this.connections) {
148
+ if (connection.fingerprint === fp) {
510
149
  socket.destroy();
150
+ }
151
+ }
511
152
  }
512
153
  start() {
513
154
  this.server.listen(this.config.port, "127.0.0.1", () => {
514
155
  const address = this.server.address();
515
- log("listening", { result: "ready", host: "127.0.0.1", pid: process.pid, port: typeof address === "object" && address ? address.port : this.config.port, brokerSocket: getBrokerSocketPath() });
156
+ log("listening", {
157
+ result: "ready",
158
+ host: "127.0.0.1",
159
+ pid: process.pid,
160
+ port: typeof address === "object" && address ? address.port : this.config.port,
161
+ brokerSocket: getBrokerSocketPath(),
162
+ });
516
163
  });
517
164
  process.on("SIGHUP", () => this.reload());
518
- process.once("SIGINT", () => this.shutdown());
519
- process.once("SIGTERM", () => this.shutdown());
165
+ // Retain listeners during dispatch: selected SDK dependencies use signal-exit's listener check.
166
+ process.on("SIGINT", () => this.stop());
167
+ process.on("SIGTERM", () => this.stop());
520
168
  }
521
169
  reload() {
522
- if (this.stopping)
170
+ if (this.stopping) {
523
171
  return;
172
+ }
524
173
  try {
525
174
  const next = loadConfig(this.configPath);
526
- if (next.port !== this.config.port || next.ca !== this.config.ca || next.cert !== this.config.cert || next.key !== this.config.key
527
- || !next.tls.ca.equals(this.config.tls.ca) || !next.tls.cert.equals(this.config.tls.cert) || !next.tls.key.equals(this.config.tls.key)) {
175
+ if (!sameListener(next, this.config)) {
528
176
  throw new Error("TLS/listener rotation requires restarting the helper.");
529
177
  }
530
178
  this.config = next;
531
- for (const [fp, session] of this.sessions)
532
- if (!this.allowed(session.identity))
533
- this.drop(fp, new Fault(403, "revoked", "Identity revoked or changed by configuration reload."));
534
- for (const connection of this.connections.values())
535
- if (!next.clients.has(connection.fingerprint))
536
- this.drop(connection.fingerprint, new Fault(403, "revoked", "Identity revoked."));
537
- for (const fp of this.losses.keys())
538
- if (!next.clients.has(fp))
539
- this.losses.delete(fp);
179
+ this.revokeRemoved();
180
+ this.launcher.revoke(next.clients).catch(() => log("launch_cleanup", { result: "failed" }));
540
181
  log("allowlist_reloaded", { result: "accepted", clients: next.clients.size });
541
182
  }
542
183
  catch {
543
- this.config.clients.clear();
544
- for (const session of this.sessions.values())
184
+ this.config = { ...this.config, clients: new Map() };
185
+ this.launcher
186
+ .revoke(this.config.clients)
187
+ .catch(() => log("launch_cleanup", { result: "failed" }));
188
+ for (const session of this.sessions.values()) {
545
189
  session.close(new Fault(503, "configuration_invalid", "Configuration reload failed; all identities denied."));
546
- for (const socket of this.sockets)
190
+ }
191
+ for (const socket of this.sockets) {
547
192
  socket.destroy();
193
+ }
548
194
  this.losses.clear();
549
195
  log("allowlist_reloaded", { result: "failed_closed" });
550
196
  }
551
197
  }
552
- shutdown() {
553
- if (this.stopping)
198
+ revokeRemoved() {
199
+ for (const [fp, session] of this.sessions) {
200
+ if (!this.allowed(session.identity)) {
201
+ this.drop(fp, new Fault(403, "revoked", "Identity revoked or changed by configuration reload."));
202
+ }
203
+ }
204
+ for (const connection of this.connections.values()) {
205
+ if (!this.config.clients.has(connection.fingerprint)) {
206
+ this.drop(connection.fingerprint, new Fault(403, "revoked", "Identity revoked."));
207
+ }
208
+ }
209
+ for (const fp of this.losses.keys()) {
210
+ if (!this.config.clients.has(fp)) {
211
+ this.losses.delete(fp);
212
+ }
213
+ }
214
+ }
215
+ stop() {
216
+ this.shutdown().catch(() => {
217
+ process.exitCode = 1;
218
+ log("shutdown", { result: "cleanup_failed" });
219
+ });
220
+ }
221
+ async shutdown() {
222
+ if (this.stopping) {
554
223
  return;
224
+ }
555
225
  this.stopping = true;
556
226
  this.server.close();
557
- for (const session of this.sessions.values())
227
+ const sessions = [...this.sessions.values()];
228
+ for (const session of sessions) {
558
229
  session.close(new Fault(503, "shutting_down", "Bridge is shutting down."));
559
- for (const socket of this.sockets)
230
+ }
231
+ for (const socket of this.sockets) {
560
232
  socket.destroy();
233
+ }
234
+ await Promise.all([
235
+ ...this.handlers,
236
+ ...sessions.map((session) => session.settled()),
237
+ this.launcher.shutdown(),
238
+ ]);
561
239
  log("shutdown", { result: "closed" });
562
240
  }
241
+ admission(req) {
242
+ const socket = req.socket;
243
+ const connection = socket instanceof TLSSocket ? this.connections.get(socket) : undefined;
244
+ const identity = connection && this.config.clients.get(connection.fingerprint);
245
+ if (!connection ||
246
+ !identity ||
247
+ !(socket instanceof TLSSocket) ||
248
+ !socket.authorized ||
249
+ this.stopping) {
250
+ throw new Fault(403, "unauthorized", "Client certificate is not authorized.");
251
+ }
252
+ if (connection.expires <= Date.now()) {
253
+ const fault = new Fault(403, "certificate_expired", "Client certificate expired.");
254
+ this.drop(connection.fingerprint, fault);
255
+ throw fault;
256
+ }
257
+ return { identity, expires: connection.expires };
258
+ }
563
259
  async handle(req, res) {
564
260
  const requestId = randomUUID();
565
261
  const started = Date.now();
@@ -568,162 +264,81 @@ class Bridge {
568
264
  let deadline;
569
265
  let peer;
570
266
  const abort = () => session?.close(new Fault(499, "client_aborted", "HTTP client disconnected; delivery may be unknown."));
571
- const onClose = () => { if (!res.writableFinished)
572
- abort(); };
573
- const reply = (status, data) => {
574
- if (res.destroyed || res.writableEnded)
575
- return;
576
- const payload = JSON.stringify({ ...data, requestId });
577
- res.writeHead(status, { "Content-Type": "application/json; charset=utf-8", "Content-Length": Buffer.byteLength(payload), "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff", "Connection": "close" });
578
- res.end(payload);
267
+ const onClose = () => {
268
+ if (!res.writableFinished) {
269
+ abort();
270
+ }
271
+ };
272
+ const setDeadline = (ms) => {
273
+ clearTimeout(deadline);
274
+ deadline = setTimeout(() => session?.close(new Fault(504, "request_timeout", "HTTP operation deadline exceeded; delivery may be unknown.")), Math.max(1, ms - (Date.now() - started)));
579
275
  };
580
276
  try {
581
- const socket = req.socket;
582
- const connection = this.connections.get(socket);
583
- const identity = connection && this.config.clients.get(connection.fingerprint);
584
- if (!connection || !identity || !socket.authorized || this.stopping)
585
- throw new Fault(403, "unauthorized", "Client certificate is not authorized.");
586
- if (connection.expires <= Date.now()) {
587
- this.drop(connection.fingerprint, new Fault(403, "certificate_expired", "Client certificate expired."));
588
- throw new Fault(403, "certificate_expired", "Client certificate expired.");
589
- }
590
- if (req.headers.origin !== undefined || Object.keys(req.headers).some((key) => key.startsWith("sec-fetch-")))
591
- throw new Fault(403, "browser_forbidden", "Browser access is not supported.");
592
- const route = `${req.method} ${req.url}`;
593
- if (!["GET /v1/list", "GET /v1/inbox", "POST /v1/register", "POST /v1/send", "POST /v1/ask", "POST /v1/reply", "POST /v1/ack"].includes(route))
594
- throw new Fault(404, "not_found", "Unknown method or path.");
595
- if (req.method === "GET" && (req.headers["transfer-encoding"] !== undefined || req.headers["content-length"] !== undefined && req.headers["content-length"] !== "0"))
596
- throw new Fault(400, "invalid_input", "GET requests must not have a body.");
597
- session = this.sessions.get(identity.fingerprint256);
598
- if (!session) {
599
- session = new Session(this, identity, connection.expires);
600
- this.sessions.set(identity.fingerprint256, session);
601
- }
602
- session.guard();
603
- if (session.requests.size >= MAX_REQUESTS)
604
- throw new Fault(429, "too_many_requests", "Too many concurrent requests for this identity.");
605
- controller = new AbortController();
277
+ const { identity, expires } = this.admission(req);
278
+ const route = routeFor(req);
279
+ session = this.session(identity, expires);
280
+ controller = session.begin();
606
281
  const signal = controller.signal;
607
- session.requests.add(controller);
608
- session.touch();
609
282
  req.once("aborted", abort);
610
283
  res.once("close", onClose);
611
- const setDeadline = (ms) => {
612
- clearTimeout(deadline);
613
- deadline = setTimeout(() => session?.close(new Fault(504, "request_timeout", "HTTP operation deadline exceeded; delivery may be unknown.")), Math.max(1, ms - (Date.now() - started)));
614
- };
615
- setDeadline(route === "POST /v1/ask" ? ASK_TIMEOUT + 15000 : 15000);
616
- let input = {};
617
- if (req.method === "POST") {
618
- const fields = route === "POST /v1/register" ? [] : route === "POST /v1/ack" ? ["ids"] : route === "POST /v1/reply" ? ["replyTo", "message"] : route === "POST /v1/ask" ? ["to", "message", "timeoutMs"] : ["to", "message"];
619
- input = object(await body(req, signal), fields);
620
- }
621
- let message;
622
- let to;
623
- let replyTo;
624
- let ids = [];
625
- let timeoutMs = ASK_TIMEOUT;
626
- if (["POST /v1/send", "POST /v1/ask", "POST /v1/reply"].includes(route))
627
- message = text(input.message);
628
- if (["POST /v1/send", "POST /v1/ask"].includes(route))
629
- to = label(input.to, "to").trim();
630
- if (route === "POST /v1/reply")
631
- replyTo = label(input.replyTo, "replyTo");
632
- if (route === "POST /v1/ask") {
633
- const timeout = input.timeoutMs === undefined ? ASK_TIMEOUT : input.timeoutMs;
634
- if (typeof timeout !== "number" || !Number.isInteger(timeout) || timeout < 100 || timeout > ASK_TIMEOUT)
635
- throw new Fault(400, "invalid_input", "timeoutMs must be an integer from 100 to 120000.");
636
- timeoutMs = timeout;
637
- setDeadline(timeoutMs + 15000);
638
- }
639
- if (route === "POST /v1/ack") {
640
- if (!Array.isArray(input.ids) || input.ids.length > INBOX_COUNT)
641
- throw new Fault(400, "invalid_input", "ids must be an array of at most 256 message IDs.");
642
- ids = [...new Set(input.ids.map((id) => label(id, "message ID")))];
643
- }
644
- session.guard(signal);
645
- if (route !== "POST /v1/ask")
646
- await session.connect(signal);
647
- let data;
648
- switch (route) {
649
- case "POST /v1/register":
650
- data = { sessionId: session.client.sessionId, name: identity.name };
651
- break;
652
- case "GET /v1/list": {
653
- const sessions = await session.peers(signal);
654
- data = { sessionId: session.client.sessionId, sessions: sessions.map(({ topics: _topics, subscriptions: _subscriptions, ...entry }) => ({ ...entry, target: formatSessionTarget(entry, sessions) })) };
655
- break;
656
- }
657
- case "GET /v1/inbox": {
658
- const lostMessages = this.losses.get(identity.fingerprint256) ?? 0;
659
- data = { messages: [...session.inbox.values()].map(({ from, message }) => ({ from, message })), overflow: lostMessages > 0, lostMessages };
660
- break;
661
- }
662
- case "POST /v1/ack": {
663
- const acked = [];
664
- const retained = [];
665
- for (const id of ids) {
666
- const entry = session.inbox.get(id);
667
- if (entry?.message.expectsReply)
668
- retained.push(id);
669
- else if (entry) {
670
- session.remove(id);
671
- acked.push(id);
672
- }
673
- }
674
- session.presence();
675
- data = { acked, retained };
676
- break;
677
- }
678
- case "POST /v1/send": {
679
- peer = (await session.target(to, signal)).id;
680
- data = { ...await session.send(peer, message, signal, requestId) };
681
- break;
682
- }
683
- case "POST /v1/ask":
684
- data = await session.ask(to, message, timeoutMs, signal, requestId);
685
- break;
686
- case "POST /v1/reply": {
687
- const entry = session.inbox.get(replyTo);
688
- if (!entry?.message.expectsReply)
689
- throw new Fault(404, "unknown_ask", "replyTo must identify an unanswered inbound ask.");
690
- if (entry.replying)
691
- throw new Fault(409, "reply_in_progress", "This ask already has a reply in flight.");
692
- entry.replying = true;
693
- peer = entry.from.id;
694
- try {
695
- const peers = await session.peers(signal);
696
- if (!peers.some((candidate) => candidate.id === peer))
697
- throw new Fault(409, "peer_offline", "Original ask sender is disconnected.");
698
- session.guard(signal);
699
- if (session.inbox.get(replyTo) !== entry)
700
- throw new Fault(409, "ask_retired", "Inbound ask is no longer replyable.");
701
- data = { ...await session.send(peer, message, signal, requestId, { replyTo }) };
702
- session.remove(replyTo);
703
- session.presence();
704
- }
705
- finally {
706
- entry.replying = false;
707
- }
708
- break;
709
- }
710
- default: throw new Fault(404, "not_found", "Unknown method or path.");
284
+ const duration = route === "POST /v1/ask" ? ASK_TIMEOUT + 15000 : 15000;
285
+ setDeadline(route === "POST /v1/start" ? 75000 : duration);
286
+ const operation = await operationFor(req, route, signal);
287
+ if (operation.route === "POST /v1/ask") {
288
+ setDeadline(operation.timeoutMs + 15000);
711
289
  }
290
+ const data = await this.execute(operation, session, {
291
+ signal,
292
+ requestId,
293
+ expires,
294
+ onPeer: (id) => {
295
+ peer = id;
296
+ },
297
+ });
712
298
  session.guard(signal);
713
299
  log("request", { ...session.fields(), requestId, peer, result: "ok" });
714
- reply(200, { ok: true, ...data });
300
+ reply(res, requestId, 200, { ok: true, ...data });
715
301
  }
716
302
  catch (error) {
717
- const fault = error instanceof Fault ? error : new Fault(503, "broker_unavailable", "Local broker operation failed.");
303
+ const fault = error instanceof Fault
304
+ ? error
305
+ : new Fault(503, "broker_unavailable", "Local broker operation failed.");
718
306
  log("request", { ...session?.fields(), requestId, peer, result: fault.code });
719
- reply(fault.status, { ok: false, error: { code: fault.code, message: fault.message, ...fault.details } });
307
+ reply(res, requestId, fault.status, {
308
+ ok: false,
309
+ error: { code: fault.code, message: fault.message, ...fault.details },
310
+ });
720
311
  }
721
312
  finally {
722
313
  clearTimeout(deadline);
723
314
  req.off("aborted", abort);
724
315
  res.off("close", onClose);
725
- if (controller)
726
- session?.requests.delete(controller);
316
+ if (controller && session) {
317
+ session.end(controller);
318
+ }
319
+ }
320
+ }
321
+ async execute(operation, session, context) {
322
+ switch (operation.route) {
323
+ case "POST /v1/start":
324
+ return this.launcher.start(operation.name, {
325
+ identity: session.identity,
326
+ expires: context.expires,
327
+ signal: context.signal,
328
+ peers: (signal) => session.connectedPeers(signal),
329
+ });
330
+ case "GET /v1/sessions":
331
+ return this.launcher.list(session.identity);
332
+ case "POST /v1/stop":
333
+ return this.launcher.stop(session.identity, operation.sessionId);
334
+ case "GET /v1/list":
335
+ case "GET /v1/inbox":
336
+ case "POST /v1/register":
337
+ case "POST /v1/ask":
338
+ case "POST /v1/send":
339
+ case "POST /v1/reply":
340
+ case "POST /v1/ack":
341
+ return session.execute(operation, context);
727
342
  }
728
343
  }
729
344
  }
@@ -733,7 +348,7 @@ function main() {
733
348
  process.stdout.write("Usage: node dist/pi-intercom/bridge.js --config PATH\n\nOptional loopback HTTPS/mTLS bridge for the local pi-intercom broker.\nConfig and TLS key must be owned by you with mode 0600. Never starts the broker.\nSIGHUP reloads the client allowlist (invalid reload denies all clients).\nRestart this helper for TLS/listener rotation. SIGINT/SIGTERM disconnect peers.\n\nExample: node dist/pi-intercom/bridge.js --config ~/.pi/agent/intercom/bridge.json\n");
734
349
  return;
735
350
  }
736
- if (args.length !== 2 || args[0] !== "--config" || !args[1]) {
351
+ if (args.length !== 2 || args[0] !== "--config" || args[1] === "") {
737
352
  process.stderr.write("Usage: node dist/pi-intercom/bridge.js --config PATH (or --help)\n");
738
353
  process.exitCode = 2;
739
354
  return;
@@ -743,7 +358,7 @@ function main() {
743
358
  new Bridge(loadConfig(path), path).start();
744
359
  }
745
360
  catch {
746
- process.stderr.write("Bridge startup failed: check config schema, file ownership/mode 0600, absolute TLS/cwd paths, and TLS credentials.\n");
361
+ process.stderr.write("Bridge startup failed: check config schema, file ownership/mode 0600, absolute TLS/cwd paths, TLS credentials, and launch policy (0700 sessionDir, Pi packageRoot, extension paths).\n");
747
362
  process.exitCode = 1;
748
363
  }
749
364
  }