@fitchmultz/pi-subagents 0.44.7 → 0.45.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 (947) hide show
  1. package/CHANGELOG.md +258 -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 +79 -0
  111. package/dist/pi-intercom/bridge-http.js +86 -0
  112. package/dist/pi-intercom/bridge-protocol.js +106 -0
  113. package/dist/pi-intercom/bridge-routes.js +64 -0
  114. package/dist/pi-intercom/bridge-session.js +416 -0
  115. package/dist/pi-intercom/bridge.js +182 -603
  116. package/dist/pi-intercom/broker/broker.js +236 -244
  117. package/dist/pi-intercom/broker/client.js +189 -304
  118. package/dist/pi-intercom/broker/connection.js +165 -0
  119. package/dist/pi-intercom/broker/framing.js +5 -3
  120. package/dist/pi-intercom/broker/paths.js +4 -1
  121. package/dist/pi-intercom/broker/pid.js +53 -25
  122. package/dist/pi-intercom/broker/snapshot-reader.js +78 -0
  123. package/dist/pi-intercom/broker/snapshots.js +188 -0
  124. package/dist/pi-intercom/broker/spawn.js +36 -25
  125. package/dist/pi-intercom/config.js +85 -71
  126. package/dist/pi-intercom/connection.js +297 -0
  127. package/dist/pi-intercom/identity.js +58 -0
  128. package/dist/pi-intercom/inbound-delivery.js +356 -0
  129. package/dist/pi-intercom/inbound-journal.js +303 -0
  130. package/dist/pi-intercom/inbound-record.js +113 -0
  131. package/dist/pi-intercom/inbound.js +179 -0
  132. package/dist/pi-intercom/index.js +79 -2933
  133. package/dist/pi-intercom/inspection.js +62 -0
  134. package/dist/pi-intercom/interview.js +236 -0
  135. package/dist/pi-intercom/lifecycle.js +168 -0
  136. package/dist/pi-intercom/message-format.js +69 -0
  137. package/dist/pi-intercom/outbound.js +304 -0
  138. package/dist/pi-intercom/peer-awareness.js +66 -0
  139. package/dist/pi-intercom/reply-tracker.js +62 -43
  140. package/dist/pi-intercom/reply-wait.js +201 -0
  141. package/dist/pi-intercom/runtime-types.js +3 -0
  142. package/dist/pi-intercom/session-events.js +180 -0
  143. package/dist/pi-intercom/session-targets.js +42 -34
  144. package/dist/pi-intercom/subagent-events.js +238 -0
  145. package/dist/pi-intercom/subagent-live-events.js +129 -86
  146. package/dist/pi-intercom/supervisor.js +282 -0
  147. package/dist/pi-intercom/tool-arguments.js +74 -0
  148. package/dist/pi-intercom/tool-registration.js +95 -0
  149. package/dist/pi-intercom/tool-results.js +38 -0
  150. package/dist/pi-intercom/tool-schemas.js +82 -0
  151. package/dist/pi-intercom/tools.js +51 -0
  152. package/dist/pi-intercom/topic-actions.js +177 -0
  153. package/dist/pi-intercom/topics.js +177 -92
  154. package/dist/pi-intercom/transport.js +1 -0
  155. package/dist/pi-intercom/types.js +151 -114
  156. package/dist/pi-intercom/ui/compose-paste.js +93 -0
  157. package/dist/pi-intercom/ui/compose.js +121 -143
  158. package/dist/pi-intercom/ui/inline-message.js +59 -59
  159. package/dist/pi-intercom/ui/message-renderers.js +147 -0
  160. package/dist/pi-intercom/ui/overlay.js +181 -0
  161. package/dist/pi-intercom/ui/session-format.js +124 -0
  162. package/dist/pi-intercom/ui/session-list.js +76 -43
  163. package/dist/pi-intercom/ui/tool-renderers.js +110 -0
  164. package/dist/pi-intercom/ui/topic-view.js +88 -0
  165. package/dist/pi-intercom/validation.js +59 -0
  166. package/dist/runs/background/async-chain-plan.js +265 -0
  167. package/dist/runs/background/async-control.js +134 -58
  168. package/dist/runs/background/async-execution.js +136 -633
  169. package/dist/runs/background/async-job-admission.js +40 -0
  170. package/dist/runs/background/async-job-projection.js +169 -0
  171. package/dist/runs/background/async-job-tracker.js +297 -327
  172. package/dist/runs/background/async-launch.js +216 -0
  173. package/dist/runs/background/async-plan.js +116 -0
  174. package/dist/runs/background/async-result-file.js +47 -23
  175. package/dist/runs/background/async-resume.js +195 -253
  176. package/dist/runs/background/async-run-discovery.js +213 -0
  177. package/dist/runs/background/async-run-location.js +135 -0
  178. package/dist/runs/background/async-run-record.js +1 -0
  179. package/dist/runs/background/async-run-repair.js +223 -0
  180. package/dist/runs/background/async-run-summary.js +1 -0
  181. package/dist/runs/background/async-single-plan.js +105 -0
  182. package/dist/runs/background/async-status-projection.js +155 -0
  183. package/dist/runs/background/async-status-render.js +117 -0
  184. package/dist/runs/background/async-status-validation.js +181 -0
  185. package/dist/runs/background/async-status.js +3 -442
  186. package/dist/runs/background/async-value.js +21 -0
  187. package/dist/runs/background/completion-dedupe.js +36 -12
  188. package/dist/runs/background/completion-delivery.js +211 -205
  189. package/dist/runs/background/completion-receipts.js +237 -0
  190. package/dist/runs/background/legacy-status-acceptance.js +34 -0
  191. package/dist/runs/background/notify.js +96 -48
  192. package/dist/runs/background/parallel-groups.js +36 -19
  193. package/dist/runs/background/result-payload.js +178 -0
  194. package/dist/runs/background/result-watcher.js +348 -339
  195. package/dist/runs/background/run-id-resolver.js +53 -33
  196. package/dist/runs/background/run-schemas.js +189 -0
  197. package/dist/runs/background/run-status-contracts.js +14 -0
  198. package/dist/runs/background/run-status-render.js +270 -0
  199. package/dist/runs/background/run-status.js +231 -339
  200. package/dist/runs/background/runner-attempt-output.js +155 -0
  201. package/dist/runs/background/runner-attempt.js +263 -0
  202. package/dist/runs/background/runner-child-executor.js +166 -0
  203. package/dist/runs/background/runner-child-observer.js +192 -0
  204. package/dist/runs/background/runner-completion-summary.js +71 -0
  205. package/dist/runs/background/runner-completion.js +211 -0
  206. package/dist/runs/background/runner-config.js +1 -0
  207. package/dist/runs/background/runner-contract.js +1 -0
  208. package/dist/runs/background/runner-dynamic.js +121 -0
  209. package/dist/runs/background/runner-finalization.js +149 -0
  210. package/dist/runs/background/runner-graph.js +68 -0
  211. package/dist/runs/background/runner-initial-status.js +60 -0
  212. package/dist/runs/background/runner-lifecycle.js +142 -0
  213. package/dist/runs/background/runner-monitor.js +309 -0
  214. package/dist/runs/background/runner-parallel.js +204 -0
  215. package/dist/runs/background/runner-prepare-step.js +86 -0
  216. package/dist/runs/background/runner-reporting.js +153 -0
  217. package/dist/runs/background/runner-status.js +148 -0
  218. package/dist/runs/background/runner-step-output.js +92 -0
  219. package/dist/runs/background/runner-step-record.js +45 -0
  220. package/dist/runs/background/runner-step.js +112 -0
  221. package/dist/runs/background/runner-streaming.js +173 -0
  222. package/dist/runs/background/runner-workflow.js +213 -0
  223. package/dist/runs/background/stale-run-reconciler.js +155 -292
  224. package/dist/runs/background/subagent-runner-launcher.js +33 -13
  225. package/dist/runs/background/subagent-runner.js +51 -1842
  226. package/dist/runs/background/top-level-async.js +2 -1
  227. package/dist/runs/foreground/chain-clarify.js +293 -1017
  228. package/dist/runs/foreground/clarification-draft.js +87 -0
  229. package/dist/runs/foreground/clarify-contracts.js +1 -0
  230. package/dist/runs/foreground/clarify-invocation.js +129 -38
  231. package/dist/runs/foreground/clarify-text-editor.js +210 -0
  232. package/dist/runs/foreground/clarify-view.js +167 -0
  233. package/dist/runs/foreground/execution-input.js +226 -353
  234. package/dist/runs/foreground/execution-routing.js +33 -0
  235. package/dist/runs/foreground/execution-timeout.js +81 -0
  236. package/dist/runs/foreground/foreground-control.js +222 -763
  237. package/dist/runs/foreground/foreground-memory.js +99 -0
  238. package/dist/runs/foreground/invocation-context.js +55 -0
  239. package/dist/runs/foreground/invocation-execution.js +331 -0
  240. package/dist/runs/foreground/invocation-expansion.js +48 -0
  241. package/dist/runs/foreground/management-actions.js +295 -0
  242. package/dist/runs/foreground/nested-control.js +179 -0
  243. package/dist/runs/foreground/question-continuation.js +78 -0
  244. package/dist/runs/foreground/question-control.js +155 -50
  245. package/dist/runs/foreground/resume-target.js +221 -0
  246. package/dist/runs/foreground/revival-launch-policy.js +58 -0
  247. package/dist/runs/foreground/run-async-path.js +190 -178
  248. package/dist/runs/foreground/run-interrupt.js +164 -0
  249. package/dist/runs/foreground/run-nudge.js +278 -0
  250. package/dist/runs/foreground/saved-revival.js +251 -0
  251. package/dist/runs/foreground/subagent-executor.js +135 -424
  252. package/dist/runs/foreground/subagent-params.js +67 -30
  253. package/dist/runs/foreground/wait-observation.js +104 -0
  254. package/dist/runs/foreground/wait-registration.js +17 -0
  255. package/dist/runs/foreground/wait-run.js +283 -123
  256. package/dist/runs/shared/acceptance-contract.js +193 -99
  257. package/dist/runs/shared/acceptance-defaults.js +1 -0
  258. package/dist/runs/shared/acceptance-evaluation.js +201 -181
  259. package/dist/runs/shared/acceptance-finalization.js +256 -180
  260. package/dist/runs/shared/acceptance-outcome.js +53 -0
  261. package/dist/runs/shared/acceptance-reports.js +137 -72
  262. package/dist/runs/shared/acceptance-submission.js +167 -0
  263. package/dist/runs/shared/acceptance-verification.js +100 -0
  264. package/dist/runs/shared/chain-outputs.js +80 -52
  265. package/dist/runs/shared/child-attempt-types.js +1 -0
  266. package/dist/runs/shared/child-attempt.js +314 -667
  267. package/dist/runs/shared/child-boundaries.js +173 -0
  268. package/dist/runs/shared/child-context-filter.js +44 -0
  269. package/dist/runs/shared/child-event-observer.js +390 -0
  270. package/dist/runs/shared/child-execution-cwd.js +73 -43
  271. package/dist/runs/shared/child-execution-outcome.js +72 -0
  272. package/dist/runs/shared/child-invocation.js +54 -0
  273. package/dist/runs/shared/child-message-validation.js +215 -0
  274. package/dist/runs/shared/child-native-accounting.js +106 -0
  275. package/dist/runs/shared/child-native-observation.js +69 -0
  276. package/dist/runs/shared/child-observation-spool.js +164 -0
  277. package/dist/runs/shared/child-observations.js +157 -76
  278. package/dist/runs/shared/child-presence.js +3 -0
  279. package/dist/runs/shared/child-route-env.js +119 -0
  280. package/dist/runs/shared/child-stream-observer.js +221 -0
  281. package/dist/runs/shared/child-stream-projection.js +137 -0
  282. package/dist/runs/shared/child-structured-tool.js +27 -0
  283. package/dist/runs/shared/claude-code.js +121 -158
  284. package/dist/runs/shared/claude-model.js +42 -0
  285. package/dist/runs/shared/claude-session.js +83 -0
  286. package/dist/runs/shared/claude-types.js +1 -0
  287. package/dist/runs/shared/completion-guard.js +18 -18
  288. package/dist/runs/shared/control-event-policy.js +8 -0
  289. package/dist/runs/shared/control-notification-owner.js +13 -0
  290. package/dist/runs/shared/dynamic-fanout.js +188 -173
  291. package/dist/runs/shared/dynamic-item.js +102 -0
  292. package/dist/runs/shared/history-index.js +62 -36
  293. package/dist/runs/shared/legacy-async-recovery.js +114 -0
  294. package/dist/runs/shared/legacy-output-recovery.js +148 -0
  295. package/dist/runs/shared/mcp-config-schema.js +36 -0
  296. package/dist/runs/shared/mcp-direct-tool-allowlist.js +147 -116
  297. package/dist/runs/shared/model-fallback.js +97 -38
  298. package/dist/runs/shared/mutating-tool-guard.js +128 -88
  299. package/dist/runs/shared/native-finalization-types.js +1 -0
  300. package/dist/runs/shared/native-finalization.js +155 -76
  301. package/dist/runs/shared/native-usage.js +238 -98
  302. package/dist/runs/shared/nested-control-records.js +128 -0
  303. package/dist/runs/shared/nested-events.js +8 -700
  304. package/dist/runs/shared/nested-path.js +32 -28
  305. package/dist/runs/shared/nested-projection.js +155 -0
  306. package/dist/runs/shared/nested-protocol.js +18 -0
  307. package/dist/runs/shared/nested-registry.js +191 -0
  308. package/dist/runs/shared/nested-render.js +106 -58
  309. package/dist/runs/shared/nested-route.js +220 -0
  310. package/dist/runs/shared/nested-validation.js +216 -0
  311. package/dist/runs/shared/owned-child-evidence.js +125 -0
  312. package/dist/runs/shared/owned-run-list.js +146 -0
  313. package/dist/runs/shared/owned-run-observation.js +267 -0
  314. package/dist/runs/shared/owned-run-progress.js +109 -0
  315. package/dist/runs/shared/owned-run-read-state.js +1 -0
  316. package/dist/runs/shared/owned-run-results.js +202 -0
  317. package/dist/runs/shared/owned-run-status.js +180 -0
  318. package/dist/runs/shared/owned-run-view.js +51 -0
  319. package/dist/runs/shared/owned-workflow-details.js +17 -0
  320. package/dist/runs/shared/owner-output.js +43 -0
  321. package/dist/runs/shared/parallel-utils.js +36 -28
  322. package/dist/runs/shared/parent-receipts.js +90 -32
  323. package/dist/runs/shared/parent-usage.js +169 -57
  324. package/dist/runs/shared/pi-args.js +108 -201
  325. package/dist/runs/shared/pi-launch-input.js +1 -0
  326. package/dist/runs/shared/pi-session-launch.js +86 -0
  327. package/dist/runs/shared/pi-spawn.js +35 -21
  328. package/dist/runs/shared/run-history-queries.js +126 -35
  329. package/dist/runs/shared/run-history.js +101 -35
  330. package/dist/runs/shared/run-metadata-paths.js +14 -0
  331. package/dist/runs/shared/run-metadata.js +158 -0
  332. package/dist/runs/shared/run-persistence.js +131 -0
  333. package/dist/runs/shared/run-records.js +9 -679
  334. package/dist/runs/shared/run-restoration.js +338 -0
  335. package/dist/runs/shared/run-state-owner.js +26 -0
  336. package/dist/runs/shared/session-cwd-preload.js +27 -18
  337. package/dist/runs/shared/single-output.js +148 -70
  338. package/dist/runs/shared/stable-value.js +15 -0
  339. package/dist/runs/shared/streaming-text.js +23 -12
  340. package/dist/runs/shared/structured-output.js +25 -13
  341. package/dist/runs/shared/subagent-control.js +167 -100
  342. package/dist/runs/shared/subagent-prompt-runtime.js +27 -130
  343. package/dist/runs/shared/subagent-tool-loop-guard.js +81 -52
  344. package/dist/runs/shared/supervisor-question-state.js +145 -0
  345. package/dist/runs/shared/supervisor-questions.js +185 -254
  346. package/dist/runs/shared/workflow-graph.js +259 -167
  347. package/dist/runs/shared/workflow-policy.js +120 -48
  348. package/dist/runs/shared/worktree-contract.js +1 -0
  349. package/dist/runs/shared/worktree-hook.js +138 -0
  350. package/dist/runs/shared/worktree-location.js +47 -0
  351. package/dist/runs/shared/worktree-preservation.js +8 -0
  352. package/dist/runs/shared/worktree-process.js +107 -0
  353. package/dist/runs/shared/worktree.js +183 -322
  354. package/dist/shared/agent-context-policy.js +34 -44
  355. package/dist/shared/agent-dir.js +14 -0
  356. package/dist/shared/artifacts.js +30 -12
  357. package/dist/shared/atomic-json.js +4 -1
  358. package/dist/shared/chain-files.js +72 -0
  359. package/dist/shared/concurrency.js +18 -0
  360. package/dist/shared/content-preview.js +115 -0
  361. package/dist/shared/cooperative.js +5 -2
  362. package/dist/shared/execution-cwd.js +13 -5
  363. package/dist/shared/file-coalescer.js +2 -1
  364. package/dist/shared/fork-context.js +10 -12
  365. package/dist/shared/formatters.js +45 -19
  366. package/dist/shared/journal-frames.js +164 -0
  367. package/dist/shared/journal-projections.js +121 -0
  368. package/dist/shared/journal-reader.js +6 -407
  369. package/dist/shared/json-projection.js +191 -0
  370. package/dist/shared/lazy-tools.js +19 -8
  371. package/dist/shared/message-output.js +116 -0
  372. package/dist/shared/model-info.js +31 -14
  373. package/dist/shared/native-import.js +20 -0
  374. package/dist/shared/native-journal.js +140 -0
  375. package/dist/shared/native-session.js +5 -2
  376. package/dist/shared/native-tui.js +7 -2
  377. package/dist/shared/native-typebox.js +15 -3
  378. package/dist/shared/output-page.js +63 -0
  379. package/dist/shared/post-exit-stdio-guard.js +128 -88
  380. package/dist/shared/prompt-sections.js +2 -0
  381. package/dist/shared/session-entries.js +70 -35
  382. package/dist/shared/session-identity.js +5 -3
  383. package/dist/shared/settings.js +62 -192
  384. package/dist/shared/status-format.js +97 -37
  385. package/dist/shared/task-file-policy.js +24 -0
  386. package/dist/shared/temp-root.js +44 -15
  387. package/dist/shared/types/acceptance.js +1 -0
  388. package/dist/shared/types/async.js +1 -0
  389. package/dist/shared/types/config.js +1 -0
  390. package/dist/shared/types/details.js +1 -0
  391. package/dist/shared/types/history.js +1 -0
  392. package/dist/shared/types/inputs.js +1 -0
  393. package/dist/shared/types/intercom.js +1 -0
  394. package/dist/shared/types/launch.js +1 -0
  395. package/dist/shared/types/messages.js +1 -0
  396. package/dist/shared/types/nested.js +1 -0
  397. package/dist/shared/types/owned-runs.js +1 -0
  398. package/dist/shared/types/progress.js +1 -0
  399. package/dist/shared/types/questions.js +1 -0
  400. package/dist/shared/types/results.js +1 -0
  401. package/dist/shared/types/state.js +1 -0
  402. package/dist/shared/types/usage.js +1 -0
  403. package/dist/shared/types/workflow.js +1 -0
  404. package/dist/shared/types.js +75 -30
  405. package/dist/shared/unknown.js +52 -0
  406. package/dist/shared/utils.js +54 -260
  407. package/dist/slash/command-discovery.js +82 -0
  408. package/dist/slash/command-input.js +190 -0
  409. package/dist/slash/prompt-template-bridge.js +94 -270
  410. package/dist/slash/prompt-template-contract.js +77 -0
  411. package/dist/slash/prompt-template-results.js +125 -0
  412. package/dist/slash/request-protocol.js +68 -0
  413. package/dist/slash/result-delivery.js +237 -0
  414. package/dist/slash/saved-chain.js +78 -0
  415. package/dist/slash/slash-bridge.js +123 -89
  416. package/dist/slash/slash-commands.js +101 -572
  417. package/dist/slash/slash-live-state.js +68 -47
  418. package/dist/tui/action-hints.js +50 -22
  419. package/dist/tui/activity-lines.js +32 -0
  420. package/dist/tui/agent-browser.js +290 -0
  421. package/dist/tui/agent-controls.js +289 -0
  422. package/dist/tui/agent-conversation.js +444 -0
  423. package/dist/tui/agent-dock.js +171 -0
  424. package/dist/tui/agent-history.js +50 -183
  425. package/dist/tui/agent-model.js +62 -0
  426. package/dist/tui/agent-picker.js +262 -0
  427. package/dist/tui/agent-view.js +232 -1564
  428. package/dist/tui/compact-results.js +193 -0
  429. package/dist/tui/conversation-details.js +190 -0
  430. package/dist/tui/conversation-frame.js +135 -0
  431. package/dist/tui/conversation-history.js +201 -0
  432. package/dist/tui/conversation-menu.js +78 -0
  433. package/dist/tui/conversation-notices.js +79 -0
  434. package/dist/tui/conversation-selection.js +58 -0
  435. package/dist/tui/conversation-viewport.js +129 -0
  436. package/dist/tui/direction-message.js +27 -0
  437. package/dist/tui/display.js +282 -0
  438. package/dist/tui/editor-viewport.js +18 -0
  439. package/dist/tui/expanded-content.js +87 -0
  440. package/dist/tui/expanded-multi.js +170 -0
  441. package/dist/tui/expanded-single.js +65 -0
  442. package/dist/tui/history-cards.js +216 -0
  443. package/dist/tui/history-display.js +238 -0
  444. package/dist/tui/history-json.js +16 -0
  445. package/dist/tui/history-metadata.js +152 -0
  446. package/dist/tui/history-record.js +251 -0
  447. package/dist/tui/history-text.js +34 -0
  448. package/dist/tui/indexed-history.js +116 -0
  449. package/dist/tui/nested-widget.js +120 -0
  450. package/dist/tui/picker-page.js +59 -0
  451. package/dist/tui/render.js +58 -1501
  452. package/dist/tui/result-facts.js +64 -0
  453. package/dist/tui/task-observation.js +59 -0
  454. package/dist/tui/task-store.js +366 -0
  455. package/dist/tui/text-values.js +16 -0
  456. package/dist/tui/view-connections.js +47 -0
  457. package/dist/tui/view-model.js +94 -0
  458. package/dist/tui/view-persistence.js +51 -0
  459. package/dist/tui/view-ports.js +1 -0
  460. package/dist/tui/view-session.js +37 -0
  461. package/dist/tui/widget-details.js +144 -0
  462. package/dist/tui/widget-status.js +245 -0
  463. package/dist/tui/widget.js +145 -0
  464. package/dist/tui/workflow-labels.js +223 -0
  465. package/dist/tui/workflow-outcomes.js +124 -0
  466. package/docs/code-quality.md +38 -0
  467. package/docs/development.md +99 -0
  468. package/docs/intercom-bridge.md +22 -17
  469. package/docs/intercom.md +132 -106
  470. package/docs/performance.md +19 -17
  471. package/docs/reference.md +1472 -0
  472. package/generated/RunValidators.d.ts +28 -0
  473. package/generated/RunValidators.js +2 -0
  474. package/package.json +27 -6
  475. package/prompts/parallel-cleanup.md +3 -0
  476. package/prompts/parallel-context-build.md +3 -0
  477. package/prompts/parallel-handoff-plan.md +6 -0
  478. package/prompts/parallel-research.md +4 -0
  479. package/prompts/parallel-review.md +2 -0
  480. package/prompts/review-loop.md +2 -0
  481. package/scripts/build.mjs +129 -85
  482. package/scripts/check-compat.mjs +48 -11
  483. package/scripts/compat-host.mjs +35 -7
  484. package/scripts/generate-run-schemas.mjs +210 -0
  485. package/scripts/intercom-bridge-credentials.mjs +155 -28
  486. package/scripts/intercom-remote.mjs +175 -50
  487. package/scripts/local-install-smoke.mjs +47 -37
  488. package/scripts/native-package-smoke.mjs +135 -25
  489. package/scripts/package-smoke.mjs +360 -140
  490. package/scripts/prepare.mjs +54 -34
  491. package/scripts/real-pi-processes.mjs +261 -0
  492. package/scripts/real-pi-smoke.mjs +432 -351
  493. package/scripts/run-tests.mjs +29 -15
  494. package/skills/pi-intercom/SKILL.md +11 -11
  495. package/skills/pi-intercom/references/peer-sessions.md +7 -2
  496. package/skills/pi-subagents/SKILL.md +1 -1
  497. package/skills/pi-subagents/references/full-orchestration-guide.md +254 -112
  498. package/src/agents/agent-defaults.ts +13 -0
  499. package/src/agents/agent-definition.ts +253 -0
  500. package/src/agents/agent-management.ts +111 -808
  501. package/src/agents/agent-scope.ts +7 -3
  502. package/src/agents/agent-selection.ts +26 -16
  503. package/src/agents/agent-serializer.ts +84 -93
  504. package/src/agents/agents.ts +199 -906
  505. package/src/agents/builtin-overrides.ts +297 -0
  506. package/src/agents/chain-serializer.ts +3 -329
  507. package/src/agents/config-values.ts +57 -0
  508. package/src/agents/discovery-paths.ts +134 -0
  509. package/src/agents/frontmatter.ts +35 -17
  510. package/src/agents/identity.ts +49 -18
  511. package/src/agents/json-chain.ts +305 -0
  512. package/src/agents/management-config.ts +342 -0
  513. package/src/agents/management-format.ts +243 -0
  514. package/src/agents/management-persistence.ts +408 -0
  515. package/src/agents/management-targets.ts +260 -0
  516. package/src/agents/management-warnings.ts +115 -0
  517. package/src/agents/markdown-chain.ts +237 -0
  518. package/src/agents/package-agents.ts +231 -0
  519. package/src/agents/skill-files.ts +182 -0
  520. package/src/agents/skill-search-paths.ts +289 -0
  521. package/src/agents/skill-types.ts +31 -0
  522. package/src/agents/skills.ts +152 -610
  523. package/src/extension/acceptance-schema.ts +88 -0
  524. package/src/extension/chain-schema.ts +276 -0
  525. package/src/extension/child-runtime.ts +302 -0
  526. package/src/extension/compact-tools.ts +259 -102
  527. package/src/extension/config-schema.ts +59 -0
  528. package/src/extension/config.ts +39 -17
  529. package/src/extension/control-notice-schema.ts +55 -0
  530. package/src/extension/control-notices.ts +57 -33
  531. package/src/extension/delegation-bridges.ts +59 -0
  532. package/src/extension/doctor.ts +171 -135
  533. package/src/extension/everyday-schemas.ts +252 -0
  534. package/src/extension/fanout-child.ts +29 -308
  535. package/src/extension/index.ts +17 -642
  536. package/src/extension/message-renderers.ts +334 -0
  537. package/src/extension/nested-control-inbox.ts +196 -0
  538. package/src/extension/parent-runtime.ts +405 -0
  539. package/src/extension/primary-tool.ts +130 -0
  540. package/src/extension/result-snapshot.ts +35 -0
  541. package/src/extension/runtime-reload.ts +72 -0
  542. package/src/extension/runtime-storage.ts +49 -0
  543. package/src/extension/schema-overrides.ts +114 -0
  544. package/src/extension/schemas.ts +451 -418
  545. package/src/extension/tool-input.ts +40 -13
  546. package/src/extension/tool-result.ts +45 -14
  547. package/src/history/answer-texts.ts +74 -0
  548. package/src/history/canonical-result.ts +127 -51
  549. package/src/history/canonical-selection.ts +54 -0
  550. package/src/history/database-generation.ts +86 -0
  551. package/src/history/database.ts +86 -48
  552. package/src/history/entry-ingestion.ts +129 -0
  553. package/src/history/index.ts +426 -148
  554. package/src/history/ingest.ts +372 -342
  555. package/src/history/output-digests.ts +41 -0
  556. package/src/history/preview.ts +235 -0
  557. package/src/history/prune.ts +48 -0
  558. package/src/history/queries.ts +122 -256
  559. package/src/history/query-scope.ts +278 -0
  560. package/src/history/rows.ts +118 -0
  561. package/src/history/run-projection.ts +164 -0
  562. package/src/history/run-queries.ts +199 -0
  563. package/src/history/schema.ts +34 -0
  564. package/src/history/search-queries.ts +216 -0
  565. package/src/history/selected-record.ts +140 -0
  566. package/src/history/source-file.ts +51 -0
  567. package/src/history/source-watches.ts +156 -0
  568. package/src/history/store.ts +310 -197
  569. package/src/history/text-chunks.ts +181 -0
  570. package/src/history/text.ts +43 -0
  571. package/src/history/transcript-queries.ts +251 -0
  572. package/src/history/types.ts +27 -164
  573. package/src/history/values.ts +36 -0
  574. package/src/history/wire-inputs.ts +141 -0
  575. package/src/history/wire-models.ts +51 -0
  576. package/src/history/worker-runtime.ts +502 -0
  577. package/src/history/worker.ts +94 -336
  578. package/src/intercom/intercom-bridge.ts +136 -69
  579. package/src/intercom/live-intercom.ts +197 -92
  580. package/src/intercom/nested-result-tree.ts +213 -0
  581. package/src/intercom/result-delivery.ts +56 -0
  582. package/src/intercom/result-intercom.ts +364 -382
  583. package/src/pi-intercom/agent-dir.ts +9 -3
  584. package/src/pi-intercom/async.ts +33 -0
  585. package/src/pi-intercom/bridge-ask.ts +64 -0
  586. package/src/pi-intercom/bridge-config.ts +101 -0
  587. package/src/pi-intercom/bridge-http.ts +97 -0
  588. package/src/pi-intercom/bridge-protocol.ts +123 -0
  589. package/src/pi-intercom/bridge-routes.ts +90 -0
  590. package/src/pi-intercom/bridge-session.ts +506 -0
  591. package/src/pi-intercom/bridge.ts +258 -547
  592. package/src/pi-intercom/broker/broker.ts +326 -263
  593. package/src/pi-intercom/broker/client.ts +273 -354
  594. package/src/pi-intercom/broker/connection.ts +192 -0
  595. package/src/pi-intercom/broker/framing.ts +11 -6
  596. package/src/pi-intercom/broker/paths.ts +28 -25
  597. package/src/pi-intercom/broker/pid.ts +68 -24
  598. package/src/pi-intercom/broker/snapshot-reader.ts +91 -0
  599. package/src/pi-intercom/broker/snapshots.ts +241 -0
  600. package/src/pi-intercom/broker/spawn.ts +58 -34
  601. package/src/pi-intercom/config.ts +113 -108
  602. package/src/pi-intercom/connection.ts +346 -0
  603. package/src/pi-intercom/identity.ts +71 -0
  604. package/src/pi-intercom/inbound-delivery.ts +450 -0
  605. package/src/pi-intercom/inbound-journal.ts +350 -0
  606. package/src/pi-intercom/inbound-record.ts +141 -0
  607. package/src/pi-intercom/inbound.ts +237 -0
  608. package/src/pi-intercom/index.ts +86 -2923
  609. package/src/pi-intercom/inspection.ts +82 -0
  610. package/src/pi-intercom/interview.ts +295 -0
  611. package/src/pi-intercom/lifecycle.ts +183 -0
  612. package/src/pi-intercom/message-format.ts +77 -0
  613. package/src/pi-intercom/outbound.ts +418 -0
  614. package/src/pi-intercom/peer-awareness.ts +84 -0
  615. package/src/pi-intercom/reply-tracker.ts +121 -59
  616. package/src/pi-intercom/reply-wait.ts +247 -0
  617. package/src/pi-intercom/runtime-types.ts +97 -0
  618. package/src/pi-intercom/session-events.ts +247 -0
  619. package/src/pi-intercom/session-targets.ts +124 -72
  620. package/src/pi-intercom/subagent-events.ts +299 -0
  621. package/src/pi-intercom/subagent-live-events.ts +212 -109
  622. package/src/pi-intercom/supervisor.ts +362 -0
  623. package/src/pi-intercom/tool-arguments.ts +120 -0
  624. package/src/pi-intercom/tool-registration.ts +121 -0
  625. package/src/pi-intercom/tool-results.ts +54 -0
  626. package/src/pi-intercom/tool-schemas.ts +134 -0
  627. package/src/pi-intercom/tools.ts +80 -0
  628. package/src/pi-intercom/topic-actions.ts +243 -0
  629. package/src/pi-intercom/topics.ts +267 -121
  630. package/src/pi-intercom/transport.ts +14 -0
  631. package/src/pi-intercom/types.ts +288 -190
  632. package/src/pi-intercom/ui/compose-paste.ts +109 -0
  633. package/src/pi-intercom/ui/compose.ts +173 -157
  634. package/src/pi-intercom/ui/inline-message.ts +80 -62
  635. package/src/pi-intercom/ui/message-renderers.ts +182 -0
  636. package/src/pi-intercom/ui/overlay.ts +238 -0
  637. package/src/pi-intercom/ui/session-format.ts +146 -0
  638. package/src/pi-intercom/ui/session-list.ts +145 -62
  639. package/src/pi-intercom/ui/tool-renderers.ts +148 -0
  640. package/src/pi-intercom/ui/topic-view.ts +107 -0
  641. package/src/pi-intercom/validation.ts +69 -0
  642. package/src/runs/background/async-chain-plan.ts +451 -0
  643. package/src/runs/background/async-control.ts +201 -71
  644. package/src/runs/background/async-execution.ts +206 -829
  645. package/src/runs/background/async-job-admission.ts +54 -0
  646. package/src/runs/background/async-job-projection.ts +225 -0
  647. package/src/runs/background/async-job-tracker.ts +416 -360
  648. package/src/runs/background/async-launch.ts +319 -0
  649. package/src/runs/background/async-plan.ts +285 -0
  650. package/src/runs/background/async-result-file.ts +79 -37
  651. package/src/runs/background/async-resume.ts +338 -285
  652. package/src/runs/background/async-run-discovery.ts +270 -0
  653. package/src/runs/background/async-run-location.ts +194 -0
  654. package/src/runs/background/async-run-record.ts +14 -0
  655. package/src/runs/background/async-run-repair.ts +307 -0
  656. package/src/runs/background/async-run-summary.ts +71 -0
  657. package/src/runs/background/async-single-plan.ts +168 -0
  658. package/src/runs/background/async-status-projection.ts +188 -0
  659. package/src/runs/background/async-status-render.ts +164 -0
  660. package/src/runs/background/async-status-validation.ts +238 -0
  661. package/src/runs/background/async-status.ts +9 -481
  662. package/src/runs/background/async-value.ts +29 -0
  663. package/src/runs/background/completion-dedupe.ts +103 -54
  664. package/src/runs/background/completion-delivery.ts +313 -187
  665. package/src/runs/background/completion-receipts.ts +309 -0
  666. package/src/runs/background/legacy-status-acceptance.ts +38 -0
  667. package/src/runs/background/notify.ts +167 -106
  668. package/src/runs/background/parallel-groups.ts +95 -46
  669. package/src/runs/background/result-payload.ts +268 -0
  670. package/src/runs/background/result-watcher.ts +484 -387
  671. package/src/runs/background/run-id-resolver.ts +126 -57
  672. package/src/runs/background/run-schemas.ts +240 -0
  673. package/src/runs/background/run-status-contracts.ts +46 -0
  674. package/src/runs/background/run-status-render.ts +364 -0
  675. package/src/runs/background/run-status.ts +338 -356
  676. package/src/runs/background/runner-attempt-output.ts +281 -0
  677. package/src/runs/background/runner-attempt.ts +354 -0
  678. package/src/runs/background/runner-child-executor.ts +232 -0
  679. package/src/runs/background/runner-child-observer.ts +270 -0
  680. package/src/runs/background/runner-completion-summary.ts +106 -0
  681. package/src/runs/background/runner-completion.ts +274 -0
  682. package/src/runs/background/runner-config.ts +52 -0
  683. package/src/runs/background/runner-contract.ts +51 -0
  684. package/src/runs/background/runner-dynamic.ts +185 -0
  685. package/src/runs/background/runner-finalization.ts +203 -0
  686. package/src/runs/background/runner-graph.ts +91 -0
  687. package/src/runs/background/runner-initial-status.ts +72 -0
  688. package/src/runs/background/runner-lifecycle.ts +159 -0
  689. package/src/runs/background/runner-monitor.ts +399 -0
  690. package/src/runs/background/runner-parallel.ts +280 -0
  691. package/src/runs/background/runner-prepare-step.ts +128 -0
  692. package/src/runs/background/runner-reporting.ts +192 -0
  693. package/src/runs/background/runner-status.ts +220 -0
  694. package/src/runs/background/runner-step-output.ts +158 -0
  695. package/src/runs/background/runner-step-record.ts +76 -0
  696. package/src/runs/background/runner-step.ts +150 -0
  697. package/src/runs/background/runner-streaming.ts +220 -0
  698. package/src/runs/background/runner-workflow.ts +284 -0
  699. package/src/runs/background/stale-run-reconciler.ts +279 -336
  700. package/src/runs/background/subagent-runner-launcher.ts +67 -35
  701. package/src/runs/background/subagent-runner.ts +80 -2048
  702. package/src/runs/background/top-level-async.ts +9 -7
  703. package/src/runs/foreground/chain-clarify.ts +499 -1334
  704. package/src/runs/foreground/clarification-draft.ts +92 -0
  705. package/src/runs/foreground/clarify-contracts.ts +33 -0
  706. package/src/runs/foreground/clarify-invocation.ts +192 -65
  707. package/src/runs/foreground/clarify-text-editor.ts +229 -0
  708. package/src/runs/foreground/clarify-view.ts +222 -0
  709. package/src/runs/foreground/execution-input.ts +358 -424
  710. package/src/runs/foreground/execution-routing.ts +41 -0
  711. package/src/runs/foreground/execution-timeout.ts +92 -0
  712. package/src/runs/foreground/foreground-control.ts +333 -812
  713. package/src/runs/foreground/foreground-memory.ts +132 -0
  714. package/src/runs/foreground/invocation-context.ts +88 -0
  715. package/src/runs/foreground/invocation-execution.ts +465 -0
  716. package/src/runs/foreground/invocation-expansion.ts +58 -0
  717. package/src/runs/foreground/management-actions.ts +360 -0
  718. package/src/runs/foreground/nested-control.ts +248 -0
  719. package/src/runs/foreground/question-continuation.ts +112 -0
  720. package/src/runs/foreground/question-control.ts +297 -70
  721. package/src/runs/foreground/resume-target.ts +330 -0
  722. package/src/runs/foreground/revival-launch-policy.ts +112 -0
  723. package/src/runs/foreground/run-async-path.ts +307 -223
  724. package/src/runs/foreground/run-interrupt.ts +202 -0
  725. package/src/runs/foreground/run-nudge.ts +355 -0
  726. package/src/runs/foreground/saved-revival.ts +356 -0
  727. package/src/runs/foreground/subagent-executor.ts +195 -493
  728. package/src/runs/foreground/subagent-params.ts +314 -242
  729. package/src/runs/foreground/wait-observation.ts +160 -0
  730. package/src/runs/foreground/wait-registration.ts +18 -0
  731. package/src/runs/foreground/wait-run.ts +361 -119
  732. package/src/runs/shared/acceptance-contract.ts +410 -209
  733. package/src/runs/shared/acceptance-defaults.ts +1 -0
  734. package/src/runs/shared/acceptance-evaluation.ts +314 -240
  735. package/src/runs/shared/acceptance-finalization.ts +447 -305
  736. package/src/runs/shared/acceptance-outcome.ts +69 -0
  737. package/src/runs/shared/acceptance-reports.ts +220 -126
  738. package/src/runs/shared/acceptance-submission.ts +251 -0
  739. package/src/runs/shared/acceptance-verification.ts +118 -0
  740. package/src/runs/shared/acceptance.ts +25 -25
  741. package/src/runs/shared/chain-outputs.ts +190 -98
  742. package/src/runs/shared/child-attempt-types.ts +118 -0
  743. package/src/runs/shared/child-attempt.ts +372 -608
  744. package/src/runs/shared/child-boundaries.ts +217 -0
  745. package/src/runs/shared/child-context-filter.ts +53 -0
  746. package/src/runs/shared/child-event-observer.ts +496 -0
  747. package/src/runs/shared/child-execution-cwd.ts +139 -68
  748. package/src/runs/shared/child-execution-outcome.ts +103 -0
  749. package/src/runs/shared/child-invocation.ts +74 -0
  750. package/src/runs/shared/child-message-validation.ts +233 -0
  751. package/src/runs/shared/child-native-accounting.ts +154 -0
  752. package/src/runs/shared/child-native-observation.ts +79 -0
  753. package/src/runs/shared/child-observation-spool.ts +188 -0
  754. package/src/runs/shared/child-observations.ts +219 -89
  755. package/src/runs/shared/child-presence.ts +6 -0
  756. package/src/runs/shared/child-route-env.ts +142 -0
  757. package/src/runs/shared/child-stream-observer.ts +302 -0
  758. package/src/runs/shared/child-stream-projection.ts +160 -0
  759. package/src/runs/shared/child-structured-tool.ts +38 -0
  760. package/src/runs/shared/claude-code.ts +201 -228
  761. package/src/runs/shared/claude-model.ts +67 -0
  762. package/src/runs/shared/claude-session.ts +124 -0
  763. package/src/runs/shared/claude-types.ts +31 -0
  764. package/src/runs/shared/completion-guard.ts +35 -26
  765. package/src/runs/shared/control-event-policy.ts +15 -0
  766. package/src/runs/shared/control-notification-owner.ts +20 -0
  767. package/src/runs/shared/dynamic-fanout.ts +402 -238
  768. package/src/runs/shared/dynamic-item.ts +114 -0
  769. package/src/runs/shared/history-index.ts +112 -51
  770. package/src/runs/shared/legacy-async-recovery.ts +153 -0
  771. package/src/runs/shared/legacy-output-recovery.ts +214 -0
  772. package/src/runs/shared/mcp-config-schema.ts +46 -0
  773. package/src/runs/shared/mcp-direct-tool-allowlist.ts +396 -307
  774. package/src/runs/shared/model-fallback.ts +302 -180
  775. package/src/runs/shared/mutating-tool-guard.ts +259 -158
  776. package/src/runs/shared/native-finalization-types.ts +29 -0
  777. package/src/runs/shared/native-finalization.ts +233 -109
  778. package/src/runs/shared/native-usage.ts +379 -96
  779. package/src/runs/shared/nested-control-records.ts +178 -0
  780. package/src/runs/shared/nested-events.ts +45 -752
  781. package/src/runs/shared/nested-path.ts +47 -34
  782. package/src/runs/shared/nested-projection.ts +230 -0
  783. package/src/runs/shared/nested-protocol.ts +67 -0
  784. package/src/runs/shared/nested-registry.ts +273 -0
  785. package/src/runs/shared/nested-render.ts +198 -100
  786. package/src/runs/shared/nested-route.ts +292 -0
  787. package/src/runs/shared/nested-validation.ts +291 -0
  788. package/src/runs/shared/owned-child-evidence.ts +184 -0
  789. package/src/runs/shared/owned-run-list.ts +187 -0
  790. package/src/runs/shared/owned-run-observation.ts +368 -0
  791. package/src/runs/shared/owned-run-progress.ts +172 -0
  792. package/src/runs/shared/owned-run-read-state.ts +7 -0
  793. package/src/runs/shared/owned-run-results.ts +289 -0
  794. package/src/runs/shared/owned-run-status.ts +239 -0
  795. package/src/runs/shared/owned-run-view.ts +65 -0
  796. package/src/runs/shared/owned-workflow-details.ts +24 -0
  797. package/src/runs/shared/owner-output.ts +73 -0
  798. package/src/runs/shared/parallel-utils.ts +93 -129
  799. package/src/runs/shared/parent-receipts.ts +177 -50
  800. package/src/runs/shared/parent-usage.ts +314 -91
  801. package/src/runs/shared/pi-args.ts +241 -311
  802. package/src/runs/shared/pi-launch-input.ts +48 -0
  803. package/src/runs/shared/pi-session-launch.ts +94 -0
  804. package/src/runs/shared/pi-spawn.ts +57 -33
  805. package/src/runs/shared/run-history-queries.ts +189 -38
  806. package/src/runs/shared/run-history.ts +197 -76
  807. package/src/runs/shared/run-id-ambiguity.ts +7 -3
  808. package/src/runs/shared/run-metadata-paths.ts +17 -0
  809. package/src/runs/shared/run-metadata.ts +240 -0
  810. package/src/runs/shared/run-persistence.ts +181 -0
  811. package/src/runs/shared/run-records.ts +19 -607
  812. package/src/runs/shared/run-restoration.ts +455 -0
  813. package/src/runs/shared/run-state-owner.ts +31 -0
  814. package/src/runs/shared/session-cwd-preload.ts +39 -19
  815. package/src/runs/shared/single-output.ts +358 -205
  816. package/src/runs/shared/stable-value.ts +15 -0
  817. package/src/runs/shared/streaming-text.ts +39 -12
  818. package/src/runs/shared/structured-output.ts +76 -46
  819. package/src/runs/shared/subagent-control.ts +330 -197
  820. package/src/runs/shared/subagent-prompt-runtime.ts +92 -174
  821. package/src/runs/shared/subagent-tool-loop-guard.ts +118 -68
  822. package/src/runs/shared/supervisor-question-state.ts +221 -0
  823. package/src/runs/shared/supervisor-questions.ts +396 -401
  824. package/src/runs/shared/workflow-graph.ts +400 -198
  825. package/src/runs/shared/workflow-policy.ts +245 -110
  826. package/src/runs/shared/worktree-contract.ts +29 -0
  827. package/src/runs/shared/worktree-hook.ts +181 -0
  828. package/src/runs/shared/worktree-location.ts +61 -0
  829. package/src/runs/shared/worktree-preservation.ts +10 -0
  830. package/src/runs/shared/worktree-process.ts +132 -0
  831. package/src/runs/shared/worktree.ts +459 -587
  832. package/src/shared/agent-context-policy.ts +176 -145
  833. package/src/shared/agent-dir.ts +15 -0
  834. package/src/shared/artifacts.ts +99 -68
  835. package/src/shared/atomic-json.ts +14 -11
  836. package/src/shared/chain-files.ts +72 -0
  837. package/src/shared/concurrency.ts +27 -0
  838. package/src/shared/content-preview.ts +117 -0
  839. package/src/shared/cooperative.ts +17 -13
  840. package/src/shared/execution-cwd.ts +34 -9
  841. package/src/shared/file-coalescer.ts +24 -22
  842. package/src/shared/fork-context.ts +52 -48
  843. package/src/shared/formatters.ts +88 -47
  844. package/src/shared/journal-frames.ts +197 -0
  845. package/src/shared/journal-projections.ts +133 -0
  846. package/src/shared/journal-reader.ts +18 -333
  847. package/src/shared/json-projection.ts +224 -0
  848. package/src/shared/lazy-tools.ts +76 -27
  849. package/src/shared/message-output.ts +146 -0
  850. package/src/shared/model-info.ts +94 -58
  851. package/src/shared/native-import.ts +25 -0
  852. package/src/shared/native-journal.ts +172 -0
  853. package/src/shared/native-session.ts +8 -3
  854. package/src/shared/native-tui.ts +13 -3
  855. package/src/shared/native-typebox.ts +18 -3
  856. package/src/shared/output-page.ts +76 -0
  857. package/src/shared/post-exit-stdio-guard.ts +232 -172
  858. package/src/shared/prompt-sections.ts +13 -7
  859. package/src/shared/session-entries.ts +103 -44
  860. package/src/shared/session-identity.ts +17 -10
  861. package/src/shared/settings.ts +182 -367
  862. package/src/shared/status-format.ts +207 -98
  863. package/src/shared/stream-json.d.ts +10 -0
  864. package/src/shared/task-file-policy.ts +35 -0
  865. package/src/shared/temp-root.ts +122 -71
  866. package/src/shared/types/acceptance.ts +149 -0
  867. package/src/shared/types/async.ts +247 -0
  868. package/src/shared/types/config.ts +139 -0
  869. package/src/shared/types/details.ts +104 -0
  870. package/src/shared/types/history.ts +217 -0
  871. package/src/shared/types/inputs.ts +4 -0
  872. package/src/shared/types/intercom.ts +57 -0
  873. package/src/shared/types/launch.ts +39 -0
  874. package/src/shared/types/messages.ts +104 -0
  875. package/src/shared/types/nested.ts +153 -0
  876. package/src/shared/types/owned-runs.ts +148 -0
  877. package/src/shared/types/progress.ts +106 -0
  878. package/src/shared/types/questions.ts +103 -0
  879. package/src/shared/types/results.ts +119 -0
  880. package/src/shared/types/state.ts +56 -0
  881. package/src/shared/types/usage.ts +36 -0
  882. package/src/shared/types/workflow.ts +187 -0
  883. package/src/shared/types.ts +222 -1161
  884. package/src/shared/ui-mode.ts +1 -1
  885. package/src/shared/unknown.ts +59 -0
  886. package/src/shared/utils.ts +111 -305
  887. package/src/slash/command-discovery.ts +109 -0
  888. package/src/slash/command-input.ts +232 -0
  889. package/src/slash/prompt-template-bridge.ts +153 -400
  890. package/src/slash/prompt-template-contract.ts +152 -0
  891. package/src/slash/prompt-template-results.ts +189 -0
  892. package/src/slash/request-protocol.ts +87 -0
  893. package/src/slash/result-delivery.ts +312 -0
  894. package/src/slash/saved-chain.ts +115 -0
  895. package/src/slash/slash-bridge.ts +202 -156
  896. package/src/slash/slash-commands.ts +176 -598
  897. package/src/slash/slash-live-state.ts +287 -229
  898. package/src/tui/action-hints.ts +147 -61
  899. package/src/tui/activity-lines.ts +40 -0
  900. package/src/tui/agent-browser.ts +354 -0
  901. package/src/tui/agent-controls.ts +355 -0
  902. package/src/tui/agent-conversation.ts +514 -0
  903. package/src/tui/agent-dock.ts +250 -0
  904. package/src/tui/agent-history.ts +78 -203
  905. package/src/tui/agent-model.ts +78 -0
  906. package/src/tui/agent-picker.ts +325 -0
  907. package/src/tui/agent-view.ts +391 -1333
  908. package/src/tui/compact-results.ts +342 -0
  909. package/src/tui/conversation-details.ts +206 -0
  910. package/src/tui/conversation-frame.ts +184 -0
  911. package/src/tui/conversation-history.ts +246 -0
  912. package/src/tui/conversation-menu.ts +119 -0
  913. package/src/tui/conversation-notices.ts +116 -0
  914. package/src/tui/conversation-selection.ts +66 -0
  915. package/src/tui/conversation-viewport.ts +169 -0
  916. package/src/tui/direction-message.ts +35 -0
  917. package/src/tui/display.ts +409 -0
  918. package/src/tui/editor-viewport.ts +19 -0
  919. package/src/tui/expanded-content.ts +134 -0
  920. package/src/tui/expanded-multi.ts +264 -0
  921. package/src/tui/expanded-single.ts +92 -0
  922. package/src/tui/history-cards.ts +316 -0
  923. package/src/tui/history-display.ts +334 -0
  924. package/src/tui/history-json.ts +17 -0
  925. package/src/tui/history-metadata.ts +177 -0
  926. package/src/tui/history-record.ts +321 -0
  927. package/src/tui/history-text.ts +34 -0
  928. package/src/tui/indexed-history.ts +149 -0
  929. package/src/tui/nested-widget.ts +192 -0
  930. package/src/tui/picker-page.ts +80 -0
  931. package/src/tui/render.ts +100 -1546
  932. package/src/tui/result-facts.ts +99 -0
  933. package/src/tui/task-observation.ts +96 -0
  934. package/src/tui/task-store.ts +432 -0
  935. package/src/tui/text-values.ts +20 -0
  936. package/src/tui/view-connections.ts +64 -0
  937. package/src/tui/view-model.ts +181 -0
  938. package/src/tui/view-persistence.ts +57 -0
  939. package/src/tui/view-ports.ts +55 -0
  940. package/src/tui/view-session.ts +42 -0
  941. package/src/tui/widget-details.ts +220 -0
  942. package/src/tui/widget-status.ts +331 -0
  943. package/src/tui/widget.ts +208 -0
  944. package/src/tui/workflow-labels.ts +340 -0
  945. package/src/tui/workflow-outcomes.ts +150 -0
  946. package/tsconfig.build.json +11 -10
  947. package/tsconfig.json +30 -17
@@ -1,437 +1,14 @@
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";
15
11
  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
12
  class Bridge {
436
13
  config;
437
14
  configPath;
@@ -441,10 +18,31 @@ class Bridge {
441
18
  connections = new Map();
442
19
  server;
443
20
  stopping = false;
21
+ handlers = new Set();
444
22
  constructor(config, configPath) {
445
23
  this.config = config;
446
24
  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); });
25
+ this.server = createServer({
26
+ ...config.tls,
27
+ minVersion: "TLSv1.2",
28
+ requestCert: true,
29
+ rejectUnauthorized: true,
30
+ handshakeTimeout: 10000,
31
+ }, (req, res) => {
32
+ const handler = this.handle(req, res).catch(() => {
33
+ // A response write can fail after route handling; close transport and retain metadata only.
34
+ res.destroy();
35
+ log("request", { result: "transport_failed" });
36
+ });
37
+ this.handlers.add(handler);
38
+ handler
39
+ .finally(() => {
40
+ this.handlers.delete(handler);
41
+ })
42
+ .catch(() => {
43
+ res.destroy();
44
+ });
45
+ });
448
46
  this.server.maxConnections = MAX_CONNECTIONS;
449
47
  this.server.requestTimeout = 10000;
450
48
  this.server.headersTimeout = 10000;
@@ -457,20 +55,46 @@ class Bridge {
457
55
  return;
458
56
  }
459
57
  this.sockets.add(socket);
460
- socket.once("close", () => this.sockets.delete(socket));
58
+ socket.once("close", () => {
59
+ this.sockets.delete(socket);
60
+ });
461
61
  });
462
62
  this.server.on("secureConnection", (socket) => this.authenticate(socket));
463
63
  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(); });
64
+ this.server.on("clientError", (_error, socket) => {
65
+ socket.destroy();
66
+ });
67
+ this.server.on("error", () => {
68
+ log("server_error", { result: "listener_failed" });
69
+ process.exitCode = 1;
70
+ this.stop();
71
+ });
466
72
  }
467
73
  allowed(identity) {
468
74
  const current = this.config.clients.get(identity.fingerprint256);
469
75
  return !this.stopping && current?.name === identity.name && current.cwd === identity.cwd;
470
76
  }
471
77
  lost(fp, count = 1) {
472
- if (count && this.config.clients.has(fp))
78
+ if (count > 0 && this.config.clients.has(fp)) {
473
79
  this.losses.set(fp, Math.min(Number.MAX_SAFE_INTEGER, (this.losses.get(fp) ?? 0) + count));
80
+ }
81
+ }
82
+ session(identity, expires) {
83
+ let session = this.sessions.get(identity.fingerprint256);
84
+ if (!session) {
85
+ session = new Session({
86
+ allowed: (candidate) => this.allowed(candidate),
87
+ lost: (fp, count) => this.lost(fp, count),
88
+ losses: (fp) => this.losses.get(fp) ?? 0,
89
+ retired: (fp, generation) => {
90
+ if (this.sessions.get(fp)?.generation === generation) {
91
+ this.sessions.delete(fp);
92
+ }
93
+ },
94
+ }, identity, expires);
95
+ this.sessions.set(identity.fingerprint256, session);
96
+ }
97
+ return session;
474
98
  }
475
99
  authenticate(socket) {
476
100
  const cert = socket.getPeerCertificate();
@@ -485,12 +109,17 @@ class Bridge {
485
109
  const identity = this.config.clients.get(fp);
486
110
  const expires = Date.parse(cert.valid_to);
487
111
  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()) {
112
+ if (!socket.authorized ||
113
+ !identity ||
114
+ this.stopping ||
115
+ !Number.isFinite(expires) ||
116
+ !Number.isFinite(starts) ||
117
+ starts > Date.now() ||
118
+ expires <= Date.now()) {
489
119
  log("connection_rejected", { fingerprint: fp, result: "unauthorized" });
490
120
  socket.destroy();
491
121
  return;
492
122
  }
493
- // Timers longer than INT32_MAX fire immediately in Node; recheck long-lived certificates in bounded slices.
494
123
  const expiry = () => {
495
124
  if (expires <= Date.now()) {
496
125
  this.drop(fp, new Fault(403, "certificate_expired", "Client certificate expired."));
@@ -498,68 +127,124 @@ class Bridge {
498
127
  }
499
128
  connection.timer = setTimeout(expiry, Math.min(expires - Date.now(), 0x7fffffff));
500
129
  };
501
- const connection = { fingerprint: fp, expires, timer: setTimeout(expiry, Math.min(expires - Date.now(), 0x7fffffff)) };
130
+ // Node timers must recheck long-lived certificates in INT32_MAX-bounded slices.
131
+ const connection = {
132
+ fingerprint: fp,
133
+ expires,
134
+ timer: setTimeout(expiry, Math.min(expires - Date.now(), 0x7fffffff)),
135
+ };
502
136
  this.connections.set(socket, connection);
503
- socket.once("close", () => { clearTimeout(connection.timer); this.connections.delete(socket); });
137
+ socket.once("close", () => {
138
+ clearTimeout(connection.timer);
139
+ this.connections.delete(socket);
140
+ });
504
141
  log("connected", { name: identity.name, fingerprint: fp, result: "authenticated" });
505
142
  }
506
143
  drop(fp, error) {
507
144
  this.sessions.get(fp)?.close(error);
508
- for (const [socket, connection] of this.connections)
509
- if (connection.fingerprint === fp)
145
+ for (const [socket, connection] of this.connections) {
146
+ if (connection.fingerprint === fp) {
510
147
  socket.destroy();
148
+ }
149
+ }
511
150
  }
512
151
  start() {
513
152
  this.server.listen(this.config.port, "127.0.0.1", () => {
514
153
  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() });
154
+ log("listening", {
155
+ result: "ready",
156
+ host: "127.0.0.1",
157
+ pid: process.pid,
158
+ port: typeof address === "object" && address ? address.port : this.config.port,
159
+ brokerSocket: getBrokerSocketPath(),
160
+ });
516
161
  });
517
162
  process.on("SIGHUP", () => this.reload());
518
- process.once("SIGINT", () => this.shutdown());
519
- process.once("SIGTERM", () => this.shutdown());
163
+ process.once("SIGINT", () => this.stop());
164
+ process.once("SIGTERM", () => this.stop());
520
165
  }
521
166
  reload() {
522
- if (this.stopping)
167
+ if (this.stopping) {
523
168
  return;
169
+ }
524
170
  try {
525
171
  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)) {
172
+ if (!sameListener(next, this.config)) {
528
173
  throw new Error("TLS/listener rotation requires restarting the helper.");
529
174
  }
530
175
  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);
176
+ this.revokeRemoved();
540
177
  log("allowlist_reloaded", { result: "accepted", clients: next.clients.size });
541
178
  }
542
179
  catch {
543
- this.config.clients.clear();
544
- for (const session of this.sessions.values())
180
+ this.config = { ...this.config, clients: new Map() };
181
+ for (const session of this.sessions.values()) {
545
182
  session.close(new Fault(503, "configuration_invalid", "Configuration reload failed; all identities denied."));
546
- for (const socket of this.sockets)
183
+ }
184
+ for (const socket of this.sockets) {
547
185
  socket.destroy();
186
+ }
548
187
  this.losses.clear();
549
188
  log("allowlist_reloaded", { result: "failed_closed" });
550
189
  }
551
190
  }
552
- shutdown() {
553
- if (this.stopping)
191
+ revokeRemoved() {
192
+ for (const [fp, session] of this.sessions) {
193
+ if (!this.allowed(session.identity)) {
194
+ this.drop(fp, new Fault(403, "revoked", "Identity revoked or changed by configuration reload."));
195
+ }
196
+ }
197
+ for (const connection of this.connections.values()) {
198
+ if (!this.config.clients.has(connection.fingerprint)) {
199
+ this.drop(connection.fingerprint, new Fault(403, "revoked", "Identity revoked."));
200
+ }
201
+ }
202
+ for (const fp of this.losses.keys()) {
203
+ if (!this.config.clients.has(fp)) {
204
+ this.losses.delete(fp);
205
+ }
206
+ }
207
+ }
208
+ stop() {
209
+ this.shutdown().catch(() => {
210
+ process.exitCode = 1;
211
+ log("shutdown", { result: "cleanup_failed" });
212
+ });
213
+ }
214
+ async shutdown() {
215
+ if (this.stopping) {
554
216
  return;
217
+ }
555
218
  this.stopping = true;
556
219
  this.server.close();
557
- for (const session of this.sessions.values())
220
+ const sessions = [...this.sessions.values()];
221
+ for (const session of sessions) {
558
222
  session.close(new Fault(503, "shutting_down", "Bridge is shutting down."));
559
- for (const socket of this.sockets)
223
+ }
224
+ for (const socket of this.sockets) {
560
225
  socket.destroy();
226
+ }
227
+ await Promise.all([...this.handlers, ...sessions.map((session) => session.settled())]);
561
228
  log("shutdown", { result: "closed" });
562
229
  }
230
+ admission(req) {
231
+ const socket = req.socket;
232
+ const connection = socket instanceof TLSSocket ? this.connections.get(socket) : undefined;
233
+ const identity = connection && this.config.clients.get(connection.fingerprint);
234
+ if (!connection ||
235
+ !identity ||
236
+ !(socket instanceof TLSSocket) ||
237
+ !socket.authorized ||
238
+ this.stopping) {
239
+ throw new Fault(403, "unauthorized", "Client certificate is not authorized.");
240
+ }
241
+ if (connection.expires <= Date.now()) {
242
+ const fault = new Fault(403, "certificate_expired", "Client certificate expired.");
243
+ this.drop(connection.fingerprint, fault);
244
+ throw fault;
245
+ }
246
+ return { identity, expires: connection.expires };
247
+ }
563
248
  async handle(req, res) {
564
249
  const requestId = randomUUID();
565
250
  const started = Date.now();
@@ -568,162 +253,56 @@ class Bridge {
568
253
  let deadline;
569
254
  let peer;
570
255
  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);
256
+ const onClose = () => {
257
+ if (!res.writableFinished) {
258
+ abort();
259
+ }
260
+ };
261
+ const setDeadline = (ms) => {
262
+ clearTimeout(deadline);
263
+ 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
264
  };
580
265
  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();
266
+ const { identity, expires } = this.admission(req);
267
+ const route = routeFor(req);
268
+ session = this.session(identity, expires);
269
+ controller = session.begin();
606
270
  const signal = controller.signal;
607
- session.requests.add(controller);
608
- session.touch();
609
271
  req.once("aborted", abort);
610
272
  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
273
  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.");
274
+ const operation = await operationFor(req, route, signal);
275
+ if (operation.route === "POST /v1/ask") {
276
+ setDeadline(operation.timeoutMs + 15000);
711
277
  }
278
+ const data = await session.execute(operation, {
279
+ signal,
280
+ requestId,
281
+ onPeer: (id) => {
282
+ peer = id;
283
+ },
284
+ });
712
285
  session.guard(signal);
713
286
  log("request", { ...session.fields(), requestId, peer, result: "ok" });
714
- reply(200, { ok: true, ...data });
287
+ reply(res, requestId, 200, { ok: true, ...data });
715
288
  }
716
289
  catch (error) {
717
- const fault = error instanceof Fault ? error : new Fault(503, "broker_unavailable", "Local broker operation failed.");
290
+ const fault = error instanceof Fault
291
+ ? error
292
+ : new Fault(503, "broker_unavailable", "Local broker operation failed.");
718
293
  log("request", { ...session?.fields(), requestId, peer, result: fault.code });
719
- reply(fault.status, { ok: false, error: { code: fault.code, message: fault.message, ...fault.details } });
294
+ reply(res, requestId, fault.status, {
295
+ ok: false,
296
+ error: { code: fault.code, message: fault.message, ...fault.details },
297
+ });
720
298
  }
721
299
  finally {
722
300
  clearTimeout(deadline);
723
301
  req.off("aborted", abort);
724
302
  res.off("close", onClose);
725
- if (controller)
726
- session?.requests.delete(controller);
303
+ if (controller && session) {
304
+ session.end(controller);
305
+ }
727
306
  }
728
307
  }
729
308
  }
@@ -733,7 +312,7 @@ function main() {
733
312
  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
313
  return;
735
314
  }
736
- if (args.length !== 2 || args[0] !== "--config" || !args[1]) {
315
+ if (args.length !== 2 || args[0] !== "--config" || args[1] === "") {
737
316
  process.stderr.write("Usage: node dist/pi-intercom/bridge.js --config PATH (or --help)\n");
738
317
  process.exitCode = 2;
739
318
  return;