@north-light/crouter 0.3.173 → 0.3.174

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 (673) hide show
  1. package/bin/runtime-selector.mjs +8 -1
  2. package/dist/api/__tests__/{client.test.js → serial/client.test.js} +2 -135
  3. package/dist/api/client.d.ts +12 -21
  4. package/dist/api/client.js +22 -31
  5. package/dist/api/dto/broker-ops.d.ts +158 -0
  6. package/dist/api/dto/broker-ops.js +6 -0
  7. package/dist/api/dto/crons.d.ts +6 -0
  8. package/dist/api/dto/human.d.ts +7 -1
  9. package/dist/api/dto/inbox.d.ts +1 -71
  10. package/dist/api/dto/inbox.js +1 -9
  11. package/dist/api/dto/nodes.d.ts +6 -7
  12. package/dist/api/dto/reviews.d.ts +1 -11
  13. package/dist/api/routes.d.ts +7 -4
  14. package/dist/api/routes.js +8 -5
  15. package/dist/build-root.js +3 -3
  16. package/dist/builtin-memory/04-base-worker.md +1 -4
  17. package/dist/builtin-memory/05-kinds/advisor/00-base.md +1 -5
  18. package/dist/builtin-memory/05-kinds/advisor/01-orchestrator.md +2 -7
  19. package/dist/builtin-memory/05-kinds/design/00-base.md +1 -6
  20. package/dist/builtin-memory/05-kinds/developer/00-base.md +1 -6
  21. package/dist/builtin-memory/05-kinds/explore/00-base.md +1 -4
  22. package/dist/builtin-memory/05-kinds/explore/01-orchestrator.md +1 -2
  23. package/dist/builtin-memory/05-kinds/general/00-base.md +1 -3
  24. package/dist/builtin-memory/05-kinds/plan/00-base.md +1 -6
  25. package/dist/builtin-memory/05-kinds/plan/reviewers/00-base.md +1 -2
  26. package/dist/builtin-memory/05-kinds/plan/reviewers/architecture-fit.md +1 -3
  27. package/dist/builtin-memory/05-kinds/plan/reviewers/code-smells.md +1 -3
  28. package/dist/builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md +1 -3
  29. package/dist/builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md +1 -3
  30. package/dist/builtin-memory/05-kinds/plan/reviewers/security.md +1 -4
  31. package/dist/builtin-memory/05-kinds/review/companion/00-base.md +2 -3
  32. package/dist/builtin-memory/05-kinds/spec/00-base.md +1 -2
  33. package/dist/builtin-memory/advisor/council.md +1 -6
  34. package/dist/builtin-memory/design.md +2 -7
  35. package/dist/builtin-memory/development.md +9 -14
  36. package/dist/builtin-memory/internal/marketplaces.md +2 -6
  37. package/dist/builtin-memory/internal/nodes-and-canvas.md +1 -1
  38. package/dist/builtin-memory/internal/plugins.md +2 -8
  39. package/dist/builtin-memory/internal/storage-tiers.md +1 -2
  40. package/dist/builtin-memory/testing.md +41 -0
  41. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/{provider-rotation.test.ts → serial/provider-rotation.test.ts} +27 -179
  42. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/claude-plugin-commands.ts +5 -15
  43. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/index.ts +1 -28
  44. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/memory-slash-commands.ts +43 -88
  45. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.js +7 -1
  46. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/provider-rotation.ts +9 -1
  47. package/dist/cli.js +2 -2
  48. package/dist/clients/attach/__tests__/attach-remote-readonly.test.js +37 -131
  49. package/dist/clients/attach/__tests__/chat-view-snapshot-ordering.test.js +71 -152
  50. package/dist/clients/attach/__tests__/context-message.test.js +16 -174
  51. package/dist/clients/attach/__tests__/crtr-output.test.js +36 -2069
  52. package/dist/clients/attach/__tests__/diagram.test.js +2 -2
  53. package/dist/clients/attach/__tests__/edit-diff.test.js +0 -4
  54. package/dist/clients/attach/__tests__/file-review-focus.test.js +2 -1
  55. package/dist/clients/attach/__tests__/group-activity.test.js +40 -229
  56. package/dist/clients/attach/__tests__/oauth-dialog-lifecycle.test.js +26 -76
  57. package/dist/clients/attach/__tests__/prompt-preview.test.js +4 -70
  58. package/dist/clients/attach/__tests__/ref-autocomplete.test.js +11 -125
  59. package/dist/clients/attach/__tests__/titled-editor-preview.test.js +14 -205
  60. package/dist/clients/attach/command.d.ts +2 -1
  61. package/dist/clients/attach/command.js +81 -76
  62. package/dist/clients/attach/input/capabilities.d.ts +27 -0
  63. package/dist/clients/attach/input/capabilities.js +96 -0
  64. package/dist/clients/attach/input/controller.d.ts +20 -182
  65. package/dist/clients/attach/input/controller.js +128 -537
  66. package/dist/clients/attach/input/overlay-owner.d.ts +41 -0
  67. package/dist/clients/attach/input/overlay-owner.js +199 -0
  68. package/dist/clients/attach/overlays/file-review.d.ts +0 -4
  69. package/dist/clients/attach/overlays/file-review.js +13 -5
  70. package/dist/clients/attach/overlays/graph.js +2 -10
  71. package/dist/clients/attach/render/chat-view.d.ts +4 -3
  72. package/dist/clients/attach/render/chat-view.js +39 -33
  73. package/dist/clients/attach/render/group-activity.d.ts +1 -0
  74. package/dist/clients/attach/render/group-activity.js +20 -13
  75. package/dist/clients/attach/render/group-recap.js +12 -3
  76. package/dist/clients/attach/session/connection.js +9 -15
  77. package/dist/clients/attach/session/editor-inventory.d.ts +6 -5
  78. package/dist/clients/attach/session/editor-inventory.js +7 -8
  79. package/dist/clients/attach/session/file-links.d.ts +13 -0
  80. package/dist/clients/attach/session/file-links.js +107 -0
  81. package/dist/clients/attach/session/frame.d.ts +3 -0
  82. package/dist/clients/attach/session/frame.js +10 -1
  83. package/dist/clients/attach/session/frames.js +2 -3
  84. package/dist/clients/attach/session/input-wiring.d.ts +4 -1
  85. package/dist/clients/attach/session/input-wiring.js +15 -14
  86. package/dist/clients/attach/session/mouse.d.ts +6 -1
  87. package/dist/clients/attach/session/mouse.js +8 -5
  88. package/dist/clients/attach/session/profile-files.js +18 -12
  89. package/dist/clients/attach/session/reconnect.js +1 -1
  90. package/dist/clients/attach/session/state-sync.js +1 -2
  91. package/dist/clients/attach/session/surface-host.d.ts +3 -2
  92. package/dist/clients/attach/session/surface-host.js +4 -4
  93. package/dist/clients/attach/slash/dispatch.d.ts +8 -31
  94. package/dist/clients/attach/slash/dispatch.js +34 -81
  95. package/dist/clients/attach/viewer.js +820 -860
  96. package/dist/clients/conversation/projection.d.ts +79 -0
  97. package/dist/clients/conversation/projection.js +341 -0
  98. package/dist/clients/inbox/__tests__/serial/inbox-controller.test.js +159 -0
  99. package/dist/clients/inbox/__tests__/serial/mount-panel.test.js +151 -0
  100. package/dist/clients/inbox/controller.d.ts +0 -4
  101. package/dist/clients/inbox/controller.js +0 -2
  102. package/dist/clients/inbox/review/__tests__/review-core.test.js +4 -293
  103. package/dist/clients/inbox/review/__tests__/teardown-notice.test.js +100 -0
  104. package/dist/clients/inbox/review/comments-client.d.ts +3 -2
  105. package/dist/clients/inbox/review/comments-client.js +12 -12
  106. package/dist/clients/inbox/review/companion-pane.d.ts +30 -0
  107. package/dist/clients/inbox/review/companion-pane.js +86 -0
  108. package/dist/clients/inbox/review/document-surface.d.ts +182 -0
  109. package/dist/clients/inbox/review/{document-region.js → document-surface.js} +252 -48
  110. package/dist/clients/inbox/review/frame.js +5 -4
  111. package/dist/clients/inbox/review/launch.d.ts +8 -8
  112. package/dist/clients/inbox/review/launch.js +52 -92
  113. package/dist/clients/inbox/review/nvim.d.ts +2 -0
  114. package/dist/clients/inbox/review/nvim.js +14 -12
  115. package/dist/clients/inbox/review/review-client.d.ts +12 -8
  116. package/dist/clients/inbox/review/review-client.js +46 -35
  117. package/dist/clients/inbox/review-adapter.d.ts +3 -10
  118. package/dist/clients/inbox/review-adapter.js +27 -19
  119. package/dist/clients/inbox/surface.d.ts +0 -4
  120. package/dist/clients/inbox/surface.js +0 -2
  121. package/dist/clients/surfaces/host.d.ts +11 -3
  122. package/dist/commands/__tests__/api-client-cold-start-diagnostic.test.js +7 -34
  123. package/dist/commands/__tests__/human.test.js +11 -322
  124. package/dist/commands/api-client.d.ts +5 -5
  125. package/dist/commands/api-client.js +6 -85
  126. package/dist/commands/canvas-browse.js +3 -29
  127. package/dist/commands/canvas-config.js +1 -1
  128. package/dist/commands/canvas-use.js +3 -3
  129. package/dist/commands/canvas.js +1 -1
  130. package/dist/commands/cron.js +5 -1
  131. package/dist/commands/human/queue.d.ts +0 -4
  132. package/dist/commands/human/queue.js +7 -40
  133. package/dist/commands/memory/list.d.ts +14 -0
  134. package/dist/commands/memory/list.js +35 -31
  135. package/dist/commands/memory/read.d.ts +7 -0
  136. package/dist/commands/memory/read.js +14 -8
  137. package/dist/commands/node/bash.d.ts +1 -0
  138. package/dist/commands/node/bash.js +198 -0
  139. package/dist/commands/node/create.d.ts +23 -0
  140. package/dist/commands/node/create.js +207 -0
  141. package/dist/commands/node/inspect.d.ts +2 -0
  142. package/dist/commands/node/inspect.js +302 -0
  143. package/dist/commands/node/lifecycle.d.ts +4 -0
  144. package/dist/commands/node/lifecycle.js +415 -0
  145. package/dist/commands/node/message.d.ts +1 -0
  146. package/dist/commands/node/message.js +164 -0
  147. package/dist/commands/node/pyramid.d.ts +2 -0
  148. package/dist/commands/node/pyramid.js +79 -0
  149. package/dist/commands/node/subscription.d.ts +1 -0
  150. package/dist/commands/node/subscription.js +82 -0
  151. package/dist/commands/node/wait.d.ts +1 -0
  152. package/dist/commands/node/wait.js +159 -0
  153. package/dist/commands/node-lifecycle-revive.js +10 -57
  154. package/dist/commands/node.d.ts +0 -42
  155. package/dist/commands/node.js +11 -2065
  156. package/dist/commands/search.js +1 -1
  157. package/dist/commands/surface/node/cycle.d.ts +1 -0
  158. package/dist/commands/surface/node/cycle.js +83 -0
  159. package/dist/commands/surface/node/focus.d.ts +1 -0
  160. package/dist/commands/surface/node/focus.js +67 -0
  161. package/dist/commands/surface/node/id.d.ts +1 -0
  162. package/dist/commands/surface/node/id.js +38 -0
  163. package/dist/commands/surface/node/pin.d.ts +1 -0
  164. package/dist/commands/surface/node/pin.js +42 -0
  165. package/dist/commands/surface/node/placement.d.ts +46 -0
  166. package/dist/commands/surface/node/placement.js +124 -0
  167. package/dist/commands/surface-node.js +4 -1
  168. package/dist/commands/surface-reload.js +1 -2
  169. package/dist/commands/surface-tmux-spread.js +1 -1
  170. package/dist/commands/surface.js +5 -20
  171. package/dist/commands/sys/__tests__/setup-core.test.js +18 -271
  172. package/dist/commands/sys/config.js +6 -6
  173. package/dist/commands/sys/logs.js +1 -1
  174. package/dist/commands/sys/panels/broker-limits-panel.d.ts +17 -0
  175. package/dist/commands/sys/panels/broker-limits-panel.js +104 -0
  176. package/dist/commands/sys/panels/dependencies-panel.d.ts +14 -0
  177. package/dist/commands/sys/panels/dependencies-panel.js +80 -0
  178. package/dist/commands/sys/panels/keybindings-panel.d.ts +56 -0
  179. package/dist/commands/sys/panels/keybindings-panel.js +703 -0
  180. package/dist/commands/sys/panels/keys-panel.d.ts +16 -0
  181. package/dist/commands/sys/panels/keys-panel.js +60 -0
  182. package/dist/commands/sys/panels/models-panel.d.ts +77 -0
  183. package/dist/commands/sys/panels/models-panel.js +647 -0
  184. package/dist/commands/sys/panels/panel.d.ts +45 -0
  185. package/dist/commands/sys/panels/panel.js +137 -0
  186. package/dist/commands/sys/panels/plugins-panel.d.ts +16 -0
  187. package/dist/commands/sys/panels/plugins-panel.js +80 -0
  188. package/dist/commands/sys/panels/prefs-panel.d.ts +21 -0
  189. package/dist/commands/sys/panels/prefs-panel.js +85 -0
  190. package/dist/commands/sys/panels/provider-panel.d.ts +62 -0
  191. package/dist/commands/sys/panels/provider-panel.js +538 -0
  192. package/dist/commands/sys/settings-shell.d.ts +55 -0
  193. package/dist/commands/sys/settings-shell.js +400 -0
  194. package/dist/commands/sys/settings.d.ts +2 -0
  195. package/dist/commands/sys/settings.js +31 -0
  196. package/dist/commands/sys/setup-core.d.ts +3 -33
  197. package/dist/commands/sys/setup-core.js +27 -78
  198. package/dist/commands/sys/setup-settings-runner.d.ts +24 -0
  199. package/dist/commands/sys/setup-settings-runner.js +63 -0
  200. package/dist/commands/sys/setup-wizard.d.ts +26 -125
  201. package/dist/commands/sys/setup-wizard.js +209 -2473
  202. package/dist/commands/sys/setup.js +5 -7
  203. package/dist/commands/sys.js +3 -2
  204. package/dist/core/__tests__/broker-extension-canvas-db-boundary.test.js +50 -0
  205. package/dist/core/__tests__/broker-launch-candidates.test.js +20 -9
  206. package/dist/core/__tests__/broker-preflight.test.js +1 -1
  207. package/dist/core/__tests__/canvas-inbox-watcher-hold.test.js +108 -27
  208. package/dist/core/__tests__/canvas-inbox-watcher.test.js +14 -0
  209. package/dist/core/__tests__/canvas.test.js +2 -36
  210. package/dist/core/__tests__/close.test.js +14 -3
  211. package/dist/core/__tests__/context-intro.test.js +32 -143
  212. package/dist/core/__tests__/cron-broker-capacity.test.js +22 -3
  213. package/dist/core/__tests__/daemon-boot.test.js +2 -20
  214. package/dist/core/__tests__/daemon-wedge.test.js +1 -1
  215. package/dist/core/__tests__/dead-node-policy-table.test.js +1 -18
  216. package/dist/core/__tests__/fixtures/fake-engine.d.ts +1 -1
  217. package/dist/core/__tests__/fixtures/fake-engine.js +2 -2
  218. package/dist/core/__tests__/fixtures/fake-pi-host.js +2 -2
  219. package/dist/core/__tests__/focuses.test.js +4 -98
  220. package/dist/core/__tests__/helpers/broker-clients.d.ts +0 -3
  221. package/dist/core/__tests__/helpers/broker-clients.js +0 -14
  222. package/dist/core/__tests__/helpers/harness.d.ts +0 -8
  223. package/dist/core/__tests__/helpers/harness.js +7 -87
  224. package/dist/core/__tests__/helpers/viewer-panes.js +1 -1
  225. package/dist/core/__tests__/inline-memory-refs.test.js +5 -134
  226. package/dist/core/__tests__/kickoff.test.js +1 -1
  227. package/dist/core/__tests__/memory-resolver-precedence.test.js +29 -1
  228. package/dist/core/__tests__/migration.test.js +35 -48
  229. package/dist/core/__tests__/on-read-dedup-resume.test.js +22 -12
  230. package/dist/core/__tests__/passive-subscription.test.js +26 -49
  231. package/dist/core/__tests__/preview-registry-sync.test.js +1 -2
  232. package/dist/core/__tests__/prune.test.js +94 -1
  233. package/dist/core/__tests__/review-model-floor.test.js +0 -32
  234. package/dist/core/__tests__/seam/broker-attach-multiclient.test.js +255 -0
  235. package/dist/core/__tests__/{full → seam}/broker-attach-stream.test.js +39 -7
  236. package/dist/core/__tests__/{full → seam}/broker-crash-teardown.test.js +30 -29
  237. package/dist/core/__tests__/{full → seam}/broker-pane-resolution.test.js +54 -5
  238. package/dist/core/__tests__/{full → seam}/broker-provider-retry.test.js +134 -82
  239. package/dist/core/__tests__/{full → seam}/broker-whip-interrupt.test.js +15 -4
  240. package/dist/core/__tests__/{full/idle-release-settlement.test.js → seam/dormancy-release.test.js} +69 -17
  241. package/dist/core/__tests__/{full/yield-message-transaction.test.js → seam/yield-refresh-transaction.test.js} +31 -9
  242. package/dist/core/__tests__/secrets.test.js +1 -7
  243. package/dist/core/__tests__/{broker-fork-seam.test.js → serial/broker-fork-seam.test.js} +58 -7
  244. package/dist/core/__tests__/{broker-sdk-wiring.test.js → serial/broker-sdk-wiring.test.js} +18 -162
  245. package/dist/core/__tests__/{broker-snapshot-history.test.js → serial/broker-snapshot-history.test.js} +9 -9
  246. package/dist/core/__tests__/{command-plugins.test.js → serial/command-plugins.test.js} +105 -15
  247. package/dist/core/__tests__/{deferred-no-wake.test.js → serial/deferred-no-wake.test.js} +23 -20
  248. package/dist/core/__tests__/{flagship-lifecycle.test.js → serial/flagship-lifecycle.test.js} +7 -7
  249. package/dist/core/__tests__/{host-teardown-process-group.test.js → serial/host-teardown-process-group.test.js} +6 -6
  250. package/dist/core/__tests__/{human-deliver-e2e.test.js → serial/human-deliver-e2e.test.js} +12 -12
  251. package/dist/core/__tests__/{live-mutation.test.js → serial/live-mutation.test.js} +5 -5
  252. package/dist/core/__tests__/{refresh-stall-recycle.test.js → serial/refresh-stall-recycle.test.js} +5 -14
  253. package/dist/core/__tests__/{revive.test.js → serial/revive.test.js} +15 -100
  254. package/dist/core/__tests__/{spawn-root.test.js → serial/spawn-root.test.js} +32 -15
  255. package/dist/core/__tests__/serial/subscription-delivery.test.js +126 -0
  256. package/dist/core/__tests__/serial/tmux-surface.test.js +256 -0
  257. package/dist/core/__tests__/{worktree.test.js → serial/worktree.test.js} +15 -15
  258. package/dist/core/__tests__/session-model.test.js +0 -18
  259. package/dist/core/__tests__/spawn-worktree-id-conflict.test.js +9 -0
  260. package/dist/core/__tests__/watchdog-abort-arms-retry.test.js +50 -0
  261. package/dist/core/broker-client/__tests__/transport-relay.test.js +15 -16
  262. package/dist/core/broker-client/__tests__/transport-socket.test.js +28 -0
  263. package/dist/core/broker-client/client.d.ts +2 -2
  264. package/dist/core/broker-client/client.js +4 -7
  265. package/dist/core/broker-client/transport-relay.d.ts +13 -19
  266. package/dist/core/broker-client/transport-relay.js +19 -34
  267. package/dist/core/broker-client/transport-socket.d.ts +2 -2
  268. package/dist/core/broker-client/transport-socket.js +9 -2
  269. package/dist/core/broker-client/transport.d.ts +7 -3
  270. package/dist/core/broker-client/transport.js +4 -4
  271. package/dist/core/canvas/__tests__/attention.test.js +1 -18
  272. package/dist/core/canvas/__tests__/remote-canvas-source.test.js +7 -64
  273. package/dist/core/canvas/__tests__/remote-transport.test.js +2 -3
  274. package/dist/core/canvas/__tests__/render-remote.test.js +7 -52
  275. package/dist/core/canvas/browse/app.d.ts +2 -17
  276. package/dist/core/canvas/browse/app.js +16 -88
  277. package/dist/core/canvas/browse/model.d.ts +0 -4
  278. package/dist/core/canvas/browse/model.js +1 -12
  279. package/dist/core/canvas/browse/render.js +6 -3
  280. package/dist/core/canvas/crons.d.ts +8 -5
  281. package/dist/core/canvas/crons.js +7 -6
  282. package/dist/core/canvas/extensions.d.ts +7 -3
  283. package/dist/core/canvas/extensions.js +16 -4
  284. package/dist/core/canvas/nav-model.d.ts +0 -4
  285. package/dist/core/canvas/nav-model.js +2 -14
  286. package/dist/core/canvas/node-order.d.ts +3 -0
  287. package/dist/core/canvas/node-order.js +12 -0
  288. package/dist/core/canvas/remote-canvas-source.d.ts +3 -6
  289. package/dist/core/canvas/remote-canvas-source.js +8 -15
  290. package/dist/core/command.js +11 -4
  291. package/dist/core/config.js +3 -1
  292. package/dist/core/events/envelope.js +1 -1
  293. package/dist/core/events/read.js +7 -21
  294. package/dist/core/events/source.d.ts +0 -2
  295. package/dist/core/events/source.js +0 -16
  296. package/dist/core/events/types.d.ts +1 -1
  297. package/dist/core/fault-classifier.d.ts +1 -1
  298. package/dist/core/feed/passive.d.ts +0 -11
  299. package/dist/core/feed/passive.js +1 -62
  300. package/dist/core/human/__tests__/{inbox-core.test.js → serial/inbox-core.test.js} +6 -6
  301. package/dist/core/keybindings/__tests__/resolve.test.js +1 -96
  302. package/dist/core/keybindings/background-bash.js +3 -4
  303. package/dist/core/keybindings/catalog.js +1 -1
  304. package/dist/core/memory/inline-ref-grammar.js +3 -3
  305. package/dist/core/memory-resolver.d.ts +11 -25
  306. package/dist/core/memory-resolver.js +51 -33
  307. package/dist/core/model-routes.d.ts +15 -0
  308. package/dist/core/model-routes.js +12 -0
  309. package/dist/core/pool-credential-store.js +1 -1
  310. package/dist/core/preview-registry.js +8 -9
  311. package/dist/core/profiles/__tests__/root-profile.test.js +4 -14
  312. package/dist/core/review/realize.d.ts +0 -13
  313. package/dist/core/review/realize.js +0 -40
  314. package/dist/core/review/types.d.ts +1 -1
  315. package/dist/core/runtime/__tests__/engine-route.test.js +2 -2
  316. package/dist/core/runtime/bearings.js +1 -11
  317. package/dist/core/runtime/broker/client-registry.d.ts +33 -0
  318. package/dist/core/runtime/broker/client-registry.js +164 -0
  319. package/dist/core/runtime/broker/daemon-ops.d.ts +19 -0
  320. package/dist/core/runtime/broker/daemon-ops.js +49 -0
  321. package/dist/core/runtime/broker/event-projection.d.ts +42 -0
  322. package/dist/core/runtime/broker/event-projection.js +178 -0
  323. package/dist/core/runtime/broker/fault-retry.d.ts +39 -0
  324. package/dist/core/runtime/broker/fault-retry.js +277 -0
  325. package/dist/core/runtime/broker/frame-dispatch.d.ts +238 -0
  326. package/dist/core/runtime/broker/frame-dispatch.js +1537 -0
  327. package/dist/core/runtime/broker/inbox.d.ts +22 -0
  328. package/dist/core/runtime/broker/inbox.js +167 -0
  329. package/dist/core/runtime/broker/passive.d.ts +16 -0
  330. package/dist/core/runtime/broker/passive.js +44 -0
  331. package/dist/core/runtime/broker/read-ops.d.ts +23 -0
  332. package/dist/core/runtime/broker/read-ops.js +150 -0
  333. package/dist/core/runtime/broker/rebind.d.ts +74 -0
  334. package/dist/core/runtime/broker/rebind.js +211 -0
  335. package/dist/core/runtime/broker/tool-groups.d.ts +39 -0
  336. package/dist/core/runtime/broker/tool-groups.js +244 -0
  337. package/dist/core/runtime/broker-extension-render.d.ts +11 -0
  338. package/dist/core/runtime/broker-extension-render.js +181 -0
  339. package/dist/core/runtime/broker-persona-guidance.d.ts +13 -0
  340. package/dist/core/runtime/broker-persona-guidance.js +58 -0
  341. package/dist/core/runtime/broker-protocol.d.ts +1 -1
  342. package/dist/core/runtime/broker-protocol.js +1 -1
  343. package/dist/core/runtime/broker.d.ts +5 -196
  344. package/dist/core/runtime/broker.js +276 -3059
  345. package/dist/core/runtime/canvas-extensions.js +11 -2
  346. package/dist/core/runtime/fault.js +1 -1
  347. package/dist/core/runtime/model-selection.d.ts +9 -0
  348. package/dist/core/runtime/model-selection.js +12 -0
  349. package/dist/core/runtime/model-swap.d.ts +3 -3
  350. package/dist/core/runtime/model-swap.js +33 -25
  351. package/dist/core/runtime/placement-tmux.d.ts +3 -14
  352. package/dist/core/runtime/placement-tmux.js +11 -80
  353. package/dist/core/runtime/placement.d.ts +4 -2
  354. package/dist/core/runtime/placement.js +7 -12
  355. package/dist/core/runtime/promote.d.ts +14 -1
  356. package/dist/core/runtime/promote.js +1 -1
  357. package/dist/core/runtime/reset.d.ts +1 -1
  358. package/dist/core/runtime/roadmap.js +1 -1
  359. package/dist/core/runtime/spawn.d.ts +8 -0
  360. package/dist/core/runtime/spawn.js +36 -6
  361. package/dist/core/runtime/stop-guard.d.ts +4 -3
  362. package/dist/core/runtime/stop-guard.js +36 -18
  363. package/dist/core/runtime/stop-signals.d.ts +4 -1
  364. package/dist/core/runtime/stream-watchdog.js +4 -5
  365. package/dist/core/runtime/structured-output.d.ts +15 -2
  366. package/dist/core/runtime/structured-output.js +35 -12
  367. package/dist/core/runtime/tmux-bindings.d.ts +25 -0
  368. package/dist/core/runtime/tmux-bindings.js +682 -0
  369. package/dist/core/runtime/tmux-chrome.d.ts +2 -1
  370. package/dist/core/runtime/tmux-chrome.js +3 -2
  371. package/dist/core/runtime/{tmux.d.ts → tmux-driver.d.ts} +18 -26
  372. package/dist/core/runtime/tmux-driver.js +598 -0
  373. package/dist/core/runtime/view-socket.d.ts +14 -0
  374. package/dist/core/runtime/view-socket.js +83 -0
  375. package/dist/core/session-model/session-state.d.ts +1 -2
  376. package/dist/core/session-model/session-state.js +5 -8
  377. package/dist/core/spawn.d.ts +1 -1
  378. package/dist/core/spawn.js +2 -2
  379. package/dist/core/substrate/gate.d.ts +1 -1
  380. package/dist/core/substrate/on-read-pure.d.ts +7 -0
  381. package/dist/core/substrate/on-read-pure.js +272 -0
  382. package/dist/core/termrender/termrender.js +32 -2
  383. package/dist/core/user-settings.d.ts +23 -1
  384. package/dist/core/user-settings.js +18 -1
  385. package/dist/daemon/api/__tests__/node-model-validation.test.js +134 -0
  386. package/dist/daemon/api/__tests__/review-terminal-conflict.test.js +98 -0
  387. package/dist/daemon/api/__tests__/{full → seam}/api-server.test.js +6 -48
  388. package/dist/daemon/api/__tests__/{full → seam}/b10-attach-modelauth.test.js +17 -44
  389. package/dist/daemon/api/__tests__/{full/push-parity.test.js → seam/leaf-api-parity.test.js} +61 -42
  390. package/dist/daemon/api/bridge.js +40 -81
  391. package/dist/daemon/api/handlers/broker-ops.d.ts +2 -0
  392. package/dist/daemon/api/handlers/broker-ops.js +383 -0
  393. package/dist/daemon/api/handlers/crons.js +9 -4
  394. package/dist/daemon/api/handlers/files.js +6 -7
  395. package/dist/daemon/api/handlers/inbox.js +2 -340
  396. package/dist/daemon/api/handlers/nodes.d.ts +10 -1
  397. package/dist/daemon/api/handlers/nodes.js +74 -39
  398. package/dist/daemon/api/handlers/review-comments.js +5 -1
  399. package/dist/daemon/api/handlers/reviews.js +0 -19
  400. package/dist/daemon/api/map.js +25 -4
  401. package/dist/daemon/api/router.js +2 -3
  402. package/dist/daemon/api/server.js +5 -7
  403. package/dist/daemon/cron-run.d.ts +4 -0
  404. package/dist/daemon/cron-run.js +30 -13
  405. package/dist/daemon/crtrd.d.ts +3 -58
  406. package/dist/daemon/crtrd.js +28 -1244
  407. package/dist/daemon/manage.js +0 -1
  408. package/dist/daemon/reconcilers/broker-supervision.d.ts +50 -0
  409. package/dist/daemon/reconcilers/broker-supervision.js +549 -0
  410. package/dist/daemon/reconcilers/cron-lane.d.ts +10 -0
  411. package/dist/daemon/reconcilers/cron-lane.js +32 -0
  412. package/dist/daemon/reconcilers/dormant-inbox.d.ts +18 -0
  413. package/dist/daemon/reconcilers/dormant-inbox.js +143 -0
  414. package/dist/daemon/reconcilers/storage-maintenance.d.ts +22 -0
  415. package/dist/daemon/reconcilers/storage-maintenance.js +213 -0
  416. package/dist/daemon/review/finish.js +2 -5
  417. package/dist/pi-extensions/__tests__/canvas-context-intro.test.js +34 -53
  418. package/dist/pi-extensions/__tests__/canvas-recap.test.js +16 -113
  419. package/dist/pi-extensions/__tests__/canvas-stophook-agentend.test.js +72 -99
  420. package/dist/pi-extensions/__tests__/canvas-stophook-context-nudge.test.js +29 -12
  421. package/dist/pi-extensions/broker-local.d.ts +20 -0
  422. package/dist/pi-extensions/broker-local.js +89 -0
  423. package/dist/pi-extensions/canvas-context-intro.d.ts +3 -3
  424. package/dist/pi-extensions/canvas-context-intro.js +22 -11
  425. package/dist/pi-extensions/canvas-doc-substrate.js +17 -34
  426. package/dist/pi-extensions/canvas-goal-capture.js +7 -12
  427. package/dist/pi-extensions/canvas-inbox-watcher.js +124 -139
  428. package/dist/pi-extensions/canvas-passive-context.d.ts +3 -3
  429. package/dist/pi-extensions/canvas-passive-context.js +22 -18
  430. package/dist/pi-extensions/canvas-recap.d.ts +2 -2
  431. package/dist/pi-extensions/canvas-recap.js +48 -21
  432. package/dist/pi-extensions/canvas-review-boundary.js +10 -4
  433. package/dist/pi-extensions/canvas-stophook.d.ts +0 -5
  434. package/dist/pi-extensions/canvas-stophook.js +149 -431
  435. package/dist/pi-extensions/canvas-structured-output.js +52 -30
  436. package/dist/pi-extensions/envelope-merge.d.ts +13 -0
  437. package/dist/pi-extensions/envelope-merge.js +29 -0
  438. package/dist/shared/working-activity.d.ts +3 -2
  439. package/dist/shared/working-activity.js +3 -2
  440. package/dist/types.d.ts +6 -5
  441. package/dist/types.js +2 -2
  442. package/docs/compat/hearth-crtr-v1.md +4 -12
  443. package/docs/compat/hearth-crtr-v2.md +3 -11
  444. package/docs/compat/hearth-crtr-v3.md +4 -12
  445. package/docs/compat/hearth-crtr-v4.md +3 -11
  446. package/docs/compat/hearth-crtr-v5.md +3 -11
  447. package/docs/public-api.md +3 -7
  448. package/package.json +7 -49
  449. package/runtime.lock.json +2766 -14533
  450. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/claude-plugin-commands.test.ts +0 -49
  451. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/memory-slash-commands.test.ts +0 -194
  452. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/__tests__/statusline.test.ts +0 -68
  453. package/dist/builtin-pi-packages/pi-crtr-extensions/extensions/frontmatter-rules/test.ts +0 -119
  454. package/dist/clients/attach/__tests__/attach-chrome-remote.test.js +0 -439
  455. package/dist/clients/attach/__tests__/attach-keybindings.test.js +0 -170
  456. package/dist/clients/attach/__tests__/crtr-output-coverage.test.js +0 -57
  457. package/dist/clients/attach/__tests__/editor-frame-title.test.js +0 -19
  458. package/dist/clients/attach/__tests__/input-controller-extension-command.test.js +0 -32
  459. package/dist/clients/attach/__tests__/markdown-headings.test.js +0 -35
  460. package/dist/clients/attach/__tests__/session-identity-refresh.test.js +0 -32
  461. package/dist/clients/conversation/conversation.d.ts +0 -65
  462. package/dist/clients/conversation/conversation.js +0 -318
  463. package/dist/clients/conversation/frames.d.ts +0 -30
  464. package/dist/clients/conversation/frames.js +0 -52
  465. package/dist/clients/conversation/region.d.ts +0 -51
  466. package/dist/clients/conversation/region.js +0 -265
  467. package/dist/clients/inbox/__tests__/inbox-controller.test.js +0 -247
  468. package/dist/clients/inbox/__tests__/mount-panel.test.js +0 -776
  469. package/dist/clients/inbox/review/__tests__/remap.test.js +0 -196
  470. package/dist/clients/inbox/review/columns.d.ts +0 -22
  471. package/dist/clients/inbox/review/columns.js +0 -40
  472. package/dist/clients/inbox/review/composition.d.ts +0 -60
  473. package/dist/clients/inbox/review/composition.js +0 -278
  474. package/dist/clients/inbox/review/document-region.d.ts +0 -120
  475. package/dist/clients/surfaces/region.d.ts +0 -16
  476. package/dist/clients/web/__tests__/push-engine.test.js +0 -158
  477. package/dist/clients/web/__tests__/push-registry.test.d.ts +0 -1
  478. package/dist/clients/web/__tests__/push-registry.test.js +0 -177
  479. package/dist/clients/web/dev-server.d.ts +0 -7
  480. package/dist/clients/web/dev-server.js +0 -46
  481. package/dist/clients/web/events.d.ts +0 -19
  482. package/dist/clients/web/events.js +0 -163
  483. package/dist/clients/web/push-engine.d.ts +0 -44
  484. package/dist/clients/web/push-engine.js +0 -125
  485. package/dist/clients/web/push-registry.d.ts +0 -85
  486. package/dist/clients/web/push-registry.js +0 -373
  487. package/dist/clients/web/server.d.ts +0 -19
  488. package/dist/clients/web/server.js +0 -661
  489. package/dist/clients/web/web-client/shared/protocol.d.ts +0 -214
  490. package/dist/clients/web/web-client/shared/protocol.js +0 -8
  491. package/dist/clients/web/web-cmd.d.ts +0 -2
  492. package/dist/clients/web/web-cmd.js +0 -141
  493. package/dist/commands/__tests__/api-canvas-source.test.d.ts +0 -1
  494. package/dist/commands/__tests__/api-canvas-source.test.js +0 -37
  495. package/dist/commands/__tests__/canvas-config.test.d.ts +0 -1
  496. package/dist/commands/__tests__/canvas-config.test.js +0 -107
  497. package/dist/commands/__tests__/node-no-kickoff-flag.test.d.ts +0 -1
  498. package/dist/commands/__tests__/node-no-kickoff-flag.test.js +0 -73
  499. package/dist/commands/__tests__/node-worktree-flag.test.d.ts +0 -1
  500. package/dist/commands/__tests__/node-worktree-flag.test.js +0 -49
  501. package/dist/commands/__tests__/search-contents.test.d.ts +0 -1
  502. package/dist/commands/__tests__/search-contents.test.js +0 -52
  503. package/dist/commands/memory/__tests__/lint.test.d.ts +0 -1
  504. package/dist/commands/memory/__tests__/lint.test.js +0 -15
  505. package/dist/commands/sys/__tests__/config-broker-thresholds.test.d.ts +0 -1
  506. package/dist/commands/sys/__tests__/config-broker-thresholds.test.js +0 -95
  507. package/dist/commands/sys/__tests__/config-keybindings.test.d.ts +0 -1
  508. package/dist/commands/sys/__tests__/config-keybindings.test.js +0 -55
  509. package/dist/commands/sys/__tests__/helpers/setup-runtime.d.ts +0 -5
  510. package/dist/commands/sys/__tests__/helpers/setup-runtime.js +0 -19
  511. package/dist/commands/sys/__tests__/setup-front-door.test.d.ts +0 -1
  512. package/dist/commands/sys/__tests__/setup-front-door.test.js +0 -102
  513. package/dist/commands/sys/__tests__/setup-models.test.d.ts +0 -1
  514. package/dist/commands/sys/__tests__/setup-models.test.js +0 -205
  515. package/dist/commands/sys/__tests__/setup-providers-viewport.test.d.ts +0 -1
  516. package/dist/commands/sys/__tests__/setup-providers-viewport.test.js +0 -155
  517. package/dist/commands/sys/__tests__/sync-deps.test.d.ts +0 -1
  518. package/dist/commands/sys/__tests__/sync-deps.test.js +0 -179
  519. package/dist/commands/sys/__tests__/sync-import.test.d.ts +0 -1
  520. package/dist/commands/sys/__tests__/sync-import.test.js +0 -315
  521. package/dist/core/__tests__/base-worker-prompt.test.d.ts +0 -1
  522. package/dist/core/__tests__/base-worker-prompt.test.js +0 -57
  523. package/dist/core/__tests__/command-plugins-surfaces.test.d.ts +0 -1
  524. package/dist/core/__tests__/command-plugins-surfaces.test.js +0 -359
  525. package/dist/core/__tests__/connection-reconnect-resume.test.d.ts +0 -1
  526. package/dist/core/__tests__/connection-reconnect-resume.test.js +0 -36
  527. package/dist/core/__tests__/finalization-migration.test.d.ts +0 -1
  528. package/dist/core/__tests__/finalization-migration.test.js +0 -38
  529. package/dist/core/__tests__/fixtures/extra-extension.d.ts +0 -1
  530. package/dist/core/__tests__/fixtures/extra-extension.js +0 -3
  531. package/dist/core/__tests__/fixtures/spawn-proof-target.test.d.ts +0 -1
  532. package/dist/core/__tests__/fixtures/spawn-proof-target.test.js +0 -15
  533. package/dist/core/__tests__/fork.test.d.ts +0 -1
  534. package/dist/core/__tests__/fork.test.js +0 -108
  535. package/dist/core/__tests__/full/broker-attach-limits.test.d.ts +0 -1
  536. package/dist/core/__tests__/full/broker-attach-limits.test.js +0 -147
  537. package/dist/core/__tests__/full/broker-deliver-ack.test.d.ts +0 -1
  538. package/dist/core/__tests__/full/broker-deliver-ack.test.js +0 -73
  539. package/dist/core/__tests__/full/broker-dialogs.test.d.ts +0 -1
  540. package/dist/core/__tests__/full/broker-dialogs.test.js +0 -180
  541. package/dist/core/__tests__/full/broker-display-snapshot.test.d.ts +0 -1
  542. package/dist/core/__tests__/full/broker-display-snapshot.test.js +0 -75
  543. package/dist/core/__tests__/full/broker-dormant-wake.test.d.ts +0 -1
  544. package/dist/core/__tests__/full/broker-dormant-wake.test.js +0 -130
  545. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.d.ts +0 -1
  546. package/dist/core/__tests__/full/broker-model-changed-broadcast.test.js +0 -57
  547. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.d.ts +0 -1
  548. package/dist/core/__tests__/full/broker-navigate-tree-rewelcome.test.js +0 -80
  549. package/dist/core/__tests__/full/cascade-close.test.d.ts +0 -1
  550. package/dist/core/__tests__/full/cascade-close.test.js +0 -240
  551. package/dist/core/__tests__/full/detach-focus.test.d.ts +0 -1
  552. package/dist/core/__tests__/full/detach-focus.test.js +0 -126
  553. package/dist/core/__tests__/full/idle-release-settlement.test.d.ts +0 -1
  554. package/dist/core/__tests__/full/issue-146-yield-spawn-focus.test.d.ts +0 -1
  555. package/dist/core/__tests__/full/issue-146-yield-spawn-focus.test.js +0 -107
  556. package/dist/core/__tests__/full/park-clock-release.test.d.ts +0 -1
  557. package/dist/core/__tests__/full/park-clock-release.test.js +0 -56
  558. package/dist/core/__tests__/full/yield-message-transaction.test.d.ts +0 -1
  559. package/dist/core/__tests__/memory-index-render.test.d.ts +0 -1
  560. package/dist/core/__tests__/memory-index-render.test.js +0 -60
  561. package/dist/core/__tests__/model-routes.test.d.ts +0 -1
  562. package/dist/core/__tests__/model-routes.test.js +0 -41
  563. package/dist/core/__tests__/on-read-identity.test.d.ts +0 -1
  564. package/dist/core/__tests__/on-read-identity.test.js +0 -61
  565. package/dist/core/__tests__/phase4-review-migration.test.d.ts +0 -1
  566. package/dist/core/__tests__/phase4-review-migration.test.js +0 -100
  567. package/dist/core/__tests__/prune-to-limit.test.d.ts +0 -1
  568. package/dist/core/__tests__/prune-to-limit.test.js +0 -126
  569. package/dist/core/__tests__/reset.test.d.ts +0 -1
  570. package/dist/core/__tests__/reset.test.js +0 -69
  571. package/dist/core/__tests__/spawn-no-kickoff.test.d.ts +0 -1
  572. package/dist/core/__tests__/spawn-no-kickoff.test.js +0 -115
  573. package/dist/core/__tests__/steer-note.test.d.ts +0 -1
  574. package/dist/core/__tests__/steer-note.test.js +0 -41
  575. package/dist/core/__tests__/subcommand-tier.test.d.ts +0 -1
  576. package/dist/core/__tests__/subcommand-tier.test.js +0 -99
  577. package/dist/core/__tests__/subscription-delivery.test.js +0 -211
  578. package/dist/core/__tests__/test-runner-cheap-sharding.test.d.ts +0 -1
  579. package/dist/core/__tests__/test-runner-cheap-sharding.test.js +0 -97
  580. package/dist/core/__tests__/tmux-surface.test.js +0 -484
  581. package/dist/core/__tests__/wake-bearings.test.d.ts +0 -1
  582. package/dist/core/__tests__/wake-bearings.test.js +0 -102
  583. package/dist/core/canvas/__tests__/remote-event-stream.test.d.ts +0 -1
  584. package/dist/core/canvas/__tests__/remote-event-stream.test.js +0 -146
  585. package/dist/core/canvas/__tests__/source-resolve.test.d.ts +0 -1
  586. package/dist/core/canvas/__tests__/source-resolve.test.js +0 -77
  587. package/dist/core/canvas/browse/__tests__/model.test.d.ts +0 -1
  588. package/dist/core/canvas/browse/__tests__/model.test.js +0 -290
  589. package/dist/core/canvas/browse/__tests__/rebuild-coalescer.test.d.ts +0 -1
  590. package/dist/core/canvas/browse/__tests__/rebuild-coalescer.test.js +0 -134
  591. package/dist/core/canvas/browse/__tests__/render.test.d.ts +0 -1
  592. package/dist/core/canvas/browse/__tests__/render.test.js +0 -111
  593. package/dist/core/canvas/browse/rebuild-coalescer.d.ts +0 -7
  594. package/dist/core/canvas/browse/rebuild-coalescer.js +0 -61
  595. package/dist/core/canvas/remote-event-stream.d.ts +0 -24
  596. package/dist/core/canvas/remote-event-stream.js +0 -94
  597. package/dist/core/feed/__tests__/coalesce-human.test.d.ts +0 -1
  598. package/dist/core/feed/__tests__/coalesce-human.test.js +0 -36
  599. package/dist/core/feed/__tests__/empty-node-guard.test.d.ts +0 -1
  600. package/dist/core/feed/__tests__/empty-node-guard.test.js +0 -45
  601. package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.d.ts +0 -1
  602. package/dist/core/host-exports/__tests__/export-prunes-boot-skill.test.js +0 -84
  603. package/dist/core/human/__tests__/visible-paths.test.d.ts +0 -1
  604. package/dist/core/human/__tests__/visible-paths.test.js +0 -28
  605. package/dist/core/keybindings/__tests__/inbox-affordance.test.d.ts +0 -1
  606. package/dist/core/keybindings/__tests__/inbox-affordance.test.js +0 -38
  607. package/dist/core/review/__tests__/project.test.d.ts +0 -1
  608. package/dist/core/review/__tests__/project.test.js +0 -71
  609. package/dist/core/runtime/__tests__/command-expansion.test.d.ts +0 -1
  610. package/dist/core/runtime/__tests__/command-expansion.test.js +0 -25
  611. package/dist/core/runtime/__tests__/session-visibility.test.d.ts +0 -1
  612. package/dist/core/runtime/__tests__/session-visibility.test.js +0 -119
  613. package/dist/core/runtime/__tests__/spawn-at-given-id.test.d.ts +0 -1
  614. package/dist/core/runtime/__tests__/spawn-at-given-id.test.js +0 -34
  615. package/dist/core/runtime/tmux.js +0 -1314
  616. package/dist/daemon/api/__tests__/full/push-parity.test.d.ts +0 -1
  617. package/dist/daemon/api/__tests__/full/worktree-parity.test.d.ts +0 -1
  618. package/dist/daemon/api/__tests__/full/worktree-parity.test.js +0 -160
  619. package/dist/daemon/api/__tests__/inbox.test.d.ts +0 -1
  620. package/dist/daemon/api/__tests__/inbox.test.js +0 -489
  621. package/dist/web-client/apple-touch-icon.png +0 -0
  622. package/dist/web-client/assets/fragment-mono-latin-400-normal-BYwT3kSJ.woff +0 -0
  623. package/dist/web-client/assets/fragment-mono-latin-400-normal-yxdJ5AmL.woff2 +0 -0
  624. package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-BbKYyvR9.woff2 +0 -0
  625. package/dist/web-client/assets/fragment-mono-latin-ext-400-normal-CT4YFKeK.woff +0 -0
  626. package/dist/web-client/assets/index-CCPaLeW7.css +0 -2
  627. package/dist/web-client/assets/index-kyOWsnZK.js +0 -86
  628. package/dist/web-client/assets/martian-mono-cyrillic-wght-normal-B84CD5C_.woff2 +0 -0
  629. package/dist/web-client/assets/martian-mono-latin-ext-wght-normal-DlL6xMw5.woff2 +0 -0
  630. package/dist/web-client/assets/martian-mono-latin-wght-normal-5W32yIyr.woff2 +0 -0
  631. package/dist/web-client/assets/workbox-window.prod.es5-Bd17z0YL.js +0 -2
  632. package/dist/web-client/icon-192.png +0 -0
  633. package/dist/web-client/icon-512.png +0 -0
  634. package/dist/web-client/icon-maskable-512.png +0 -0
  635. package/dist/web-client/index.html +0 -17
  636. package/dist/web-client/manifest.webmanifest +0 -1
  637. package/dist/web-client/sw.js +0 -1
  638. /package/dist/api/__tests__/{client.test.d.ts → serial/client.test.d.ts} +0 -0
  639. /package/dist/clients/inbox/__tests__/{inbox-controller.test.d.ts → serial/inbox-controller.test.d.ts} +0 -0
  640. /package/dist/clients/inbox/__tests__/{mount-panel.test.d.ts → serial/mount-panel.test.d.ts} +0 -0
  641. /package/dist/clients/{attach/__tests__/attach-chrome-remote.test.d.ts → inbox/review/__tests__/teardown-notice.test.d.ts} +0 -0
  642. /package/dist/{clients/attach/__tests__/attach-keybindings.test.d.ts → core/__tests__/broker-extension-canvas-db-boundary.test.d.ts} +0 -0
  643. /package/dist/{clients/attach/__tests__/crtr-output-coverage.test.d.ts → core/__tests__/seam/broker-attach-multiclient.test.d.ts} +0 -0
  644. /package/dist/core/__tests__/{full → seam}/broker-attach-stream.test.d.ts +0 -0
  645. /package/dist/core/__tests__/{full → seam}/broker-crash-teardown.test.d.ts +0 -0
  646. /package/dist/core/__tests__/{full → seam}/broker-pane-resolution.test.d.ts +0 -0
  647. /package/dist/core/__tests__/{full → seam}/broker-provider-retry.test.d.ts +0 -0
  648. /package/dist/core/__tests__/{full → seam}/broker-whip-interrupt.test.d.ts +0 -0
  649. /package/dist/{clients/attach/__tests__/editor-frame-title.test.d.ts → core/__tests__/seam/dormancy-release.test.d.ts} +0 -0
  650. /package/dist/{clients/attach/__tests__/input-controller-extension-command.test.d.ts → core/__tests__/seam/yield-refresh-transaction.test.d.ts} +0 -0
  651. /package/dist/core/__tests__/{broker-fork-seam.test.d.ts → serial/broker-fork-seam.test.d.ts} +0 -0
  652. /package/dist/core/__tests__/{broker-sdk-wiring.test.d.ts → serial/broker-sdk-wiring.test.d.ts} +0 -0
  653. /package/dist/core/__tests__/{broker-snapshot-history.test.d.ts → serial/broker-snapshot-history.test.d.ts} +0 -0
  654. /package/dist/core/__tests__/{command-plugins.test.d.ts → serial/command-plugins.test.d.ts} +0 -0
  655. /package/dist/core/__tests__/{deferred-no-wake.test.d.ts → serial/deferred-no-wake.test.d.ts} +0 -0
  656. /package/dist/core/__tests__/{flagship-lifecycle.test.d.ts → serial/flagship-lifecycle.test.d.ts} +0 -0
  657. /package/dist/core/__tests__/{host-teardown-process-group.test.d.ts → serial/host-teardown-process-group.test.d.ts} +0 -0
  658. /package/dist/core/__tests__/{human-deliver-e2e.test.d.ts → serial/human-deliver-e2e.test.d.ts} +0 -0
  659. /package/dist/core/__tests__/{live-mutation.test.d.ts → serial/live-mutation.test.d.ts} +0 -0
  660. /package/dist/core/__tests__/{refresh-stall-recycle.test.d.ts → serial/refresh-stall-recycle.test.d.ts} +0 -0
  661. /package/dist/core/__tests__/{revive.test.d.ts → serial/revive.test.d.ts} +0 -0
  662. /package/dist/core/__tests__/{spawn-root.test.d.ts → serial/spawn-root.test.d.ts} +0 -0
  663. /package/dist/core/__tests__/{subscription-delivery.test.d.ts → serial/subscription-delivery.test.d.ts} +0 -0
  664. /package/dist/core/__tests__/{tmux-surface.test.d.ts → serial/tmux-surface.test.d.ts} +0 -0
  665. /package/dist/core/__tests__/{worktree.test.d.ts → serial/worktree.test.d.ts} +0 -0
  666. /package/dist/{clients/attach/__tests__/markdown-headings.test.d.ts → core/__tests__/watchdog-abort-arms-retry.test.d.ts} +0 -0
  667. /package/dist/{clients/attach/__tests__/session-identity-refresh.test.d.ts → core/broker-client/__tests__/transport-socket.test.d.ts} +0 -0
  668. /package/dist/core/human/__tests__/{inbox-core.test.d.ts → serial/inbox-core.test.d.ts} +0 -0
  669. /package/dist/{clients/inbox/review/__tests__/remap.test.d.ts → daemon/api/__tests__/node-model-validation.test.d.ts} +0 -0
  670. /package/dist/{clients/surfaces/region.js → daemon/api/__tests__/review-terminal-conflict.test.d.ts} +0 -0
  671. /package/dist/daemon/api/__tests__/{full → seam}/api-server.test.d.ts +0 -0
  672. /package/dist/daemon/api/__tests__/{full → seam}/b10-attach-modelauth.test.d.ts +0 -0
  673. /package/dist/{clients/web/__tests__/push-engine.test.d.ts → daemon/api/__tests__/seam/leaf-api-parity.test.d.ts} +0 -0
@@ -1,11 +1,12 @@
1
- // Run with: node --import tsx/esm --test src/core/__tests__/full/broker-attach-stream.test.ts
1
+ // Run with: node --import tsx/esm --test src/core/__tests__/seam/broker-attach-stream.test.ts
2
2
  //
3
- // FULL TIER (real-boot-bound): tmux-free, but it boots a REAL broker process
4
- // (~5s pi-SDK load), so it lives in full/ (CI), not the fast local loop.
3
+ // COMPILED SEAM (real-boot-bound): tmux-free, but it boots a REAL broker process
4
+ // (~5s pi-SDK load), so it lives in seam/ (CI), not the fast local loop.
5
5
  //
6
6
  // T8 — the `crtr surface attach` acceptance gate, gates G1/G1b/G2 (controller drive,
7
- // relay/coalescing, detach survival). Split out of
8
- // broker-lifecycle.test.ts; the gates are the original acceptance gate, unchanged.
7
+ // relay/coalescing, detach survival) plus the deliver ack's truthfulness (G3).
8
+ // Split out of broker-lifecycle.test.ts; the gates are the original acceptance
9
+ // gate, unchanged.
9
10
  //
10
11
  // 3-PART HEADER (headless-retarget):
11
12
  // (1) BUG IT LOCKS — the broker's client fan-out: a controller `prompt` must run
@@ -22,7 +23,7 @@
22
23
  // the lock holds headlessly (createHeadlessHarness, no tmux). It needs a REAL
23
24
  // broker PROCESS (not just model state) because it proves the cross-process
24
25
  // frame codec + fan-out + coalescer, which is why this file keeps ONE real
25
- // boot — shared across all three gates to stay off the fast-tier long pole.
26
+ // boot shared across all three gates.
26
27
  // (3) HOW THE HEADLESS DRIVE STILL FAILS ON REGRESSION — each gate attaches the
27
28
  // PRODUCTION ViewSocketClient over the real socket and asserts on the frames
28
29
  // the real broker relays (order, types, snapshot, pid stability). A broken
@@ -39,7 +40,8 @@ import { test, before, after, afterEach } from 'node:test';
39
40
  import assert from 'node:assert/strict';
40
41
  import { createHeadlessHarness } from '../helpers/harness.js';
41
42
  import { isPidAlive } from '../../canvas/pid.js';
42
- import { createAttachKit, delay, tok, frameHas } from '../helpers/broker-clients.js';
43
+ import { createAttachKit, brokerLogText, delay, tok, frameHas } from '../helpers/broker-clients.js';
44
+ import { REJECT_PROMPT_CALL } from '../fixtures/fake-engine.js';
43
45
  let h;
44
46
  let id; // ONE shared broker, reused across G1/G1b/G2 (1 real boot, not 3)
45
47
  const kit = createAttachKit(() => h);
@@ -131,3 +133,33 @@ test('G2 — detach (bye+close) leaves the broker alive + the engine still emitt
131
133
  assert.ok(o.welcome.snapshot.messages.length > 0, 'G2: the engine produced a message after the detach');
132
134
  assert.equal(isPidAlive(pid), true, 'G2: broker still alive after the post-detach turn');
133
135
  });
136
+ // ---------------------------------------------------------------------------
137
+ // G3 — the interactive `deliver` ack reports the ENGINE CALL's real outcome, not
138
+ // the fact that a call was routed. A `session.prompt()` that REJECTS outright —
139
+ // the shape a broker whose model resolved but whose provider has no installed
140
+ // credential produces ("No API key found for anthropic") — used to still ack
141
+ // ok:true, so crtrd returned `delivered:true, delivered_via:engine` for a message
142
+ // the engine had refused and the one-shot client closed before the error frame
143
+ // arrived: the message vanished with no trace. The ack now races acceptance (the
144
+ // engine's own agent_start) against rejection and records
145
+ // `broker.prompt.deliver_failed` in the node's job/log.jsonl. Shares this file's
146
+ // one broker: the contract is two frames on an attached controller.
147
+ // ---------------------------------------------------------------------------
148
+ test('G3 — a deliver whose prompt rejects acks ok:false and records deliver_failed; a succeeding deliver acks ok:true via prompt', async () => {
149
+ // Both delivers must route as `prompt` (idle), never `steer` — the steer route
150
+ // legitimately acks at routing time — so attach only once the engine is idle.
151
+ const c = await attachUntil(id, 'controller', 'g3-ctrl', (a) => a.welcome.role === 'controller' && a.welcome.snapshot.state.isStreaming === false, 'G3 controller admitted while the engine is idle');
152
+ // FAILURE: the engine refuses the call itself. Before the fix this acked
153
+ // ok:true and the rejection left no trace in the node's log.
154
+ c.send({ type: 'deliver', id: 'd-fail', text: `do a thing ${REJECT_PROMPT_CALL}` });
155
+ const failAck = await c.waitFrame((f) => f.type === 'ack' && f.for === 'deliver' && f.id === 'd-fail', 'G3 rejected deliver ack');
156
+ assert.equal(failAck.type === 'ack' && failAck.ok, false, 'G3: a rejected prompt must not ack delivered:true');
157
+ assert.match((failAck.type === 'ack' ? failAck.detail : undefined) ?? '', /No API key found/, 'G3: the ack detail carries the engine rejection');
158
+ await h.waitFor(() => (brokerLogText(h, id).includes('broker.prompt.deliver_failed') ? true : null), { label: 'G3 broker.prompt.deliver_failed recorded in job/log.jsonl' });
159
+ // POSITIVE CONTROL: an ordinary deliver still acks ok:true from acceptance
160
+ // (the engine's agent_start), detail naming the prompt route.
161
+ c.send({ type: 'deliver', id: 'd-ok', text: 'an ordinary interactive message' });
162
+ const okAck = await c.waitFrame((f) => f.type === 'ack' && f.for === 'deliver' && f.id === 'd-ok', 'G3 accepted deliver ack');
163
+ assert.equal(okAck.type === 'ack' && okAck.ok, true, 'G3: an accepted prompt acks delivered:true');
164
+ assert.equal(okAck.type === 'ack' ? okAck.detail : undefined, 'prompt');
165
+ });
@@ -1,4 +1,4 @@
1
- // Run with: node --import tsx/esm --test src/core/__tests__/full/broker-crash-teardown.test.ts
1
+ // Run with: node --import tsx/esm --test src/core/__tests__/seam/broker-crash-teardown.test.ts
2
2
  //
3
3
  // Daemon-epoch broker ownership, real-process crash and teardown. The broker's
4
4
  // exit event is the sole death authority: a SIGTERM of a streaming broker
@@ -14,9 +14,9 @@
14
14
  // broker and reboots the node through THIS process's reviveNode, so every
15
15
  // broker below is harness-fleet-owned and every SIGKILL's REAL ChildProcess
16
16
  // exit drives policy automatically — no synthetic delivery, no tick, no grace.
17
- // Short-lived exits pay the real respawn backoff (5s, then 10s), so the one
18
- // broker lifecycle is split across two serial tests to fit the tier's
19
- // per-test budget.
17
+ // Each crash has its own real broker setup, so either top-level test remains a
18
+ // complete journey under name filtering and pays only its own first respawn
19
+ // backoff.
20
20
  import { test, before, after } from 'node:test';
21
21
  import assert from 'node:assert/strict';
22
22
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
@@ -28,10 +28,6 @@ import { RUNTIME_RESTART_CONTINUATION } from '../../runtime/kickoff.js';
28
28
  import { jobDir } from '../../canvas/paths.js';
29
29
  let h;
30
30
  let root;
31
- // One broker lifecycle threaded through the two serial tests below.
32
- let id;
33
- let boots;
34
- let idleRevivedPid;
35
31
  let priorHoldTurn;
36
32
  before(async () => {
37
33
  priorHoldTurn = process.env['PI_FAKE_ENGINE_HOLD_TURN'];
@@ -48,12 +44,12 @@ after(async () => {
48
44
  process.env['PI_FAKE_ENGINE_HOLD_TURN'] = priorHoldTurn;
49
45
  });
50
46
  // ===========================================================================
51
- // Items 4 + 5 + 6 — one real broker lifecycle:
52
- // graceful busy teardown → strict resume + continuation → dirty interrupted-turn
53
- // crash → recovery cycle → close.
47
+ // Items 4 + 5 + 6 — two independent real-process journeys:
48
+ // graceful busy teardown → strict resume + continuation;
49
+ // dirty interrupted-turn crash → recovery cycle → close.
54
50
  // ===========================================================================
55
51
  test('SIGTERM of a busy broker aborts cleanly and resumes with a continuation', { timeout: 30_000 }, async () => {
56
- id = await h.spawnHeadlessChild(root, 'headless worker — crash+teardown');
52
+ const id = await h.spawnHeadlessChild(root, 'headless worker — graceful crash recovery');
57
53
  const boot1 = await h.awaitBoot(id);
58
54
  const oldPid = h.node(id).pi_pid;
59
55
  assert.equal(oldPid, boot1.pid, 'canvas pid identifies the broker that booted the session');
@@ -61,7 +57,7 @@ test('SIGTERM of a busy broker aborts cleanly and resumes with a continuation',
61
57
  await h.waitFor(() => existsSync(h.brokerSock(id)), { label: 'broker socket bound' });
62
58
  await h.waitFor(() => existsSync(join(jobDir(id), 'busy')), { label: 'fixture turn is active before SIGTERM' });
63
59
  assert.equal(h.node(id).intent ?? null, null, 'fresh broker has a null intent (not refresh/idle-release)');
64
- boots = h.bootCount(id);
60
+ const boots = h.bootCount(id);
65
61
  // SIGTERM exercises broker.ts's bounded session.abort() path. Its REAL child
66
62
  // exit sees the broker-written clean-abort certificate and resumes only after
67
63
  // the first-failure backoff; no synthetic fleet delivery is involved.
@@ -70,37 +66,42 @@ test('SIGTERM of a busy broker aborts cleanly and resumes with a continuation',
70
66
  assert.equal(existsSync(join(jobDir(id), 'clean-abort')), true, 'settled abort leaves durable clean-abort evidence before exit policy respawns');
71
67
  // This is the file's SECOND real broker boot (exit-event recovery), behind a
72
68
  // 5s short-lived-exit backoff, on an oversubscribed CI runner sharing the
73
- // host with the other full-tier files' own broker spawns. Give it a wide,
69
+ // host with the other compiled-seam files' own broker spawns. Give it a wide,
74
70
  // still-bounded ceiling.
75
71
  const boot2 = await h.awaitBoot(id, { minCount: boots + 1, timeoutMs: 25_000 });
76
72
  assert.equal(boot2.resuming, true, 'clean-abort exit-event recovery RESUMES the saved .jsonl');
77
73
  assert.equal(boot2.prompt, RUNTIME_RESTART_CONTINUATION, 'the strict resume injects the runtime-restart continuation');
78
74
  assert.ok(!boot2.rawArgv.includes('--crtr-new-cycle'), 'clean abort never starts a recovery cycle');
79
- idleRevivedPid = h.node(id).pi_pid;
80
- assert.ok(idleRevivedPid != null && isPidAlive(idleRevivedPid), 'the revived broker pid is alive');
81
- assert.notEqual(idleRevivedPid, oldPid, 'the revived pid is distinct from the crashed one');
75
+ const revivedPid = h.node(id).pi_pid;
76
+ assert.ok(revivedPid != null && isPidAlive(revivedPid), 'the revived broker pid is alive');
77
+ assert.notEqual(revivedPid, oldPid, 'the revived pid is distinct from the crashed one');
82
78
  assert.equal(isPidAlive(oldPid), false, 'one-writer: the crashed pid never resurrected');
83
79
  });
84
80
  test('SIGKILL of a busy broker stays a dirty recovery cycle; close tears down the replacement', { timeout: 30_000 }, async () => {
85
- // A second crash occurs with only the durable busy marker — SIGKILL cannot run
86
- // the abort handler or write clean-abort evidence. Strict resume would only
87
- // reopen the transcript at an idle prompt; the policy must select resume:false
88
- // and inject the normal fresh-cycle kickoff. This second short-lived exit pays
89
- // the 10s backoff before the recovery cycle.
81
+ const id = await h.spawnHeadlessChild(root, 'headless worker — dirty crash+teardown');
82
+ const boot1 = await h.awaitBoot(id);
83
+ const boots = h.bootCount(id);
84
+ const busyPid = h.node(id).pi_pid;
85
+ assert.equal(busyPid, boot1.pid, 'canvas pid identifies the broker that owns the dirty-crash session');
86
+ assert.equal(isPidAlive(busyPid), true, 'busy broker is alive before SIGKILL');
87
+ // This broker has only the durable busy marker — SIGKILL cannot run the abort
88
+ // handler or write clean-abort evidence. Strict resume would only reopen the
89
+ // transcript at an idle prompt; policy must select resume:false and inject the
90
+ // normal fresh-cycle kickoff after this journey's first 5s backoff.
90
91
  markBusy(id);
91
- process.kill(idleRevivedPid, 'SIGKILL');
92
- await h.waitFor(() => !isPidAlive(idleRevivedPid), { label: 'mid-turn crashed broker pid is dead' });
93
- const boot3 = await h.awaitBoot(id, { minCount: boots + 2, timeoutMs: 25_000 });
92
+ process.kill(busyPid, 'SIGKILL');
93
+ await h.waitFor(() => !isPidAlive(busyPid), { label: 'mid-turn crashed broker pid is dead' });
94
+ const boot2 = await h.awaitBoot(id, { minCount: boots + 1, timeoutMs: 25_000 });
94
95
  // The fake SessionManager reports `resuming=true` for ANY existing session
95
96
  // path, including a cycle; production distinguishes cycling with the argv
96
97
  // token and non-null kickoff. Assert those load-bearing inputs directly.
97
- const recoveryArgv = boot3.rawArgv;
98
+ const recoveryArgv = boot2.rawArgv;
98
99
  assert.ok(recoveryArgv.includes('--crtr-new-cycle'), 'a surviving busy marker selects --crtr-new-cycle');
99
- assert.match(boot3.prompt ?? '', /You have been revived fresh after a context refresh/, 'the recovery cycle receives a real roadmap\/feed\/context kickoff');
100
+ assert.match(boot2.prompt ?? '', /You have been revived fresh after a context refresh/, 'the recovery cycle receives a real roadmap\/feed\/context kickoff');
100
101
  const recoveryPid = h.node(id).pi_pid;
101
102
  assert.ok(recoveryPid != null && isPidAlive(recoveryPid), 'the recovery-cycle broker pid is alive');
102
- assert.notEqual(recoveryPid, idleRevivedPid, 'the recovery cycle uses a distinct broker pid');
103
- assert.equal(isPidAlive(idleRevivedPid), false, 'one-writer: the interrupted pid never resurrected');
103
+ assert.notEqual(recoveryPid, busyPid, 'the recovery cycle uses a distinct broker pid');
104
+ assert.equal(isPidAlive(busyPid), false, 'one-writer: the interrupted pid never resurrected');
104
105
  // --- Item 5: clean teardown of the (revived, live) broker. node lifecycle close marks
105
106
  // canceled BEFORE teardown (crash-safe order), then hostFor(broker).teardown
106
107
  // sends the `shutdown` frame → the broker disposes + exits, socket unlinked. ---
@@ -1,10 +1,10 @@
1
- // Run with: node --import tsx/esm --test src/core/__tests__/full/broker-pane-resolution.test.ts
1
+ // Run with: node --import tsx/esm --test src/core/__tests__/seam/broker-pane-resolution.test.ts
2
2
  //
3
3
  // REGRESSION — broker-hosted nodes were second-class in the tmux command
4
4
  // surface (audit: findings-headless-default.md, Gaps 1 + 3). Both regressions
5
- // are observable only with a REAL tmux pane, so this is a FULL-tier test.
5
+ // are observable only with a REAL tmux pane, so this is a compiled-seam test.
6
6
  //
7
- // (1) Gap 1 — `nodeInPane()` (src/commands/node.ts) resolved a pane via
7
+ // (1) Gap 1 — `nodeInPane()` (src/commands/surface/node/placement.ts) resolved a pane via
8
8
  // window→node ONLY. A broker engine runs DETACHED with window=null, and its
9
9
  // on-screen presence is a `crtr surface attach` VIEWER pane, so the window lookup
10
10
  // never found it: viewer navigation and node lifecycle operations all
@@ -22,13 +22,23 @@
22
22
  // node, the second reused IN PLACE (in_place=true). A focus() that always
23
23
  // opened a fresh pane would yield two tagged panes (or crash on the UNIQUE
24
24
  // constraint) — either way this goes RED.
25
+ //
26
+ // (4) `node lifecycle demote --detach` (the Alt+C → D menu path) CLOSES the
27
+ // node's viewer pane and its `@crtr_node` tag while the broker ENGINE keeps
28
+ // running untouched, reconnectable by a later focus. Only the pane-closing
29
+ // half needs tmux — "detach leaves the broker running" is already proved
30
+ // headlessly by broker-attach-stream G2 — so it rides this file's pane, tag
31
+ // helpers and budget instead of its own harness.
25
32
  import { test, before, after } from 'node:test';
26
33
  import assert from 'node:assert/strict';
27
34
  import { spawnSync } from 'node:child_process';
35
+ import { mkdirSync, writeFileSync } from 'node:fs';
36
+ import { join } from 'node:path';
28
37
  import { createHarness, hasTmux, TMUX_TIMEOUT_MS } from '../helpers/harness.js';
29
- import { taggedViewerPanes, waitForOneTaggedViewer } from '../helpers/viewer-panes.js';
38
+ import { paneTag, taggedViewerPanes, waitForOneTaggedViewer } from '../helpers/viewer-panes.js';
30
39
  import { closeDb } from '../../canvas/db.js';
31
- import { nodeInPane } from '../../../commands/node.js';
40
+ import { isPidAlive } from '../../canvas/pid.js';
41
+ import { nodeInPane } from '../../../commands/surface/node/placement.js';
32
42
  let h;
33
43
  let root;
34
44
  before(async () => {
@@ -153,3 +163,42 @@ test('surface node focus reuses a viewer in another session instead of reopening
153
163
  spawnSync('tmux', ['kill-session', '-t', otherSession], { stdio: 'ignore', timeout: TMUX_TIMEOUT_MS });
154
164
  }
155
165
  });
166
+ // 30s for the same reason as the two tests above: the viewer self-tags
167
+ // asynchronously on connect, and this test waits for that tag before detaching.
168
+ test('node lifecycle demote --detach closes the viewer pane + tag while the broker engine keeps running', { timeout: 30_000 }, async (t) => {
169
+ if (!hasTmux())
170
+ return t.skip('tmux unavailable');
171
+ const broker = await h.spawnHeadlessChild(root, 'detach while the broker keeps running');
172
+ closeDb();
173
+ // Make the node NON-EMPTY (it produced an assistant message) so `--detach`
174
+ // exercises the "close the viewer, broker survives" contract and NOT the
175
+ // reap-empty path, which tears an output-less shell down.
176
+ const sessionFile = h.node(broker).pi_session_file;
177
+ assert.ok(sessionFile != null, 'the node has a session file');
178
+ mkdirSync(join(sessionFile, '..'), { recursive: true });
179
+ writeFileSync(sessionFile, JSON.stringify({ type: 'message', message: { role: 'assistant', content: [{ type: 'text', text: 'hi' }] } }) + '\n');
180
+ // Put the node genuinely on screen: one viewer pane beside a caller pane.
181
+ const caller = makePane();
182
+ const focused = h.cli(broker, ['surface', 'node', 'focus', broker, '--pane', caller]);
183
+ assert.equal(focused.code, 0, `surface node focus exit 0\n${focused.stderr}`);
184
+ const viewerPane = await waitForOneTaggedViewer(h.session, broker);
185
+ // The engine the detach must NOT disturb is the one serving the viewer that is
186
+ // now on screen, so read its pid here — a headless child that idle-released
187
+ // after its kickoff turn is revived BY the focus, under a new pid.
188
+ closeDb();
189
+ const brokerPid = h.node(broker).pi_pid;
190
+ assert.ok(isPidAlive(brokerPid), 'broker engine alive before the detach');
191
+ // Drive the REAL verb on the focused node (the Alt+C → D menu path).
192
+ const res = h.cli(broker, ['node', 'lifecycle', 'demote', '--node', broker, '--pane', viewerPane, '--detach']);
193
+ assert.equal(res.code, 0, `node lifecycle demote --detach exit 0\n${res.stderr}`);
194
+ assert.match(res.stdout, /viewer closed \(broker still running off-screen\)/, `the verb reports the viewer closed with the broker alive\n${res.stdout}`);
195
+ closeDb();
196
+ const after = h.node(broker);
197
+ assert.equal(after.status, 'active', 'detach does NOT end the node');
198
+ assert.equal(after.lifecycle, 'terminal', 'lifecycle flips terminal IN PLACE');
199
+ assert.equal(after.pi_pid, brokerPid, 'the broker pid is UNCHANGED (engine not killed or respawned)');
200
+ assert.equal(isPidAlive(brokerPid), true, 'the broker engine keeps running off-screen');
201
+ // The viewer pane is CLOSED and untagged — no orphaned/phantom viewport.
202
+ await h.waitFor(() => (taggedViewerPanes(h.session, broker).length === 0 ? true : null), { label: 'the detached viewer pane is gone from the tag registry' });
203
+ assert.equal(paneTag(viewerPane), '', 'NO phantom @crtr_node tag resolves on the closed viewer %id');
204
+ });
@@ -1,8 +1,10 @@
1
- // Run with: node --import tsx/esm --test src/core/__tests__/full/broker-provider-retry.test.ts
1
+ // Run with: node --import tsx/esm --test src/core/__tests__/seam/broker-provider-retry.test.ts
2
2
  //
3
- // Broker-level regression coverage for turn-time provider rotation. The fake
4
- // engine emits pi's actual auto_retry_start event over session.subscribe, so this
5
- // exercises runBroker's live retry handler rather than its helpers in isolation.
3
+ // Broker-level regression coverage for turn-time provider rotation, context
4
+ // overflow settlement, model-choice durability, and the model-less launch
5
+ // re-drive. The fake engine emits pi's actual lifecycle events over
6
+ // session.subscribe, so this exercises runBroker's live handlers rather than
7
+ // its helpers in isolation.
6
8
  import { test, before, after, afterEach } from 'node:test';
7
9
  import assert from 'node:assert/strict';
8
10
  import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
@@ -26,6 +28,7 @@ function resolvedModel(spec) {
26
28
  const OPENAI_STRONG = resolvedModel('openai/strong');
27
29
  let h;
28
30
  const kit = createAttachKit(() => h);
31
+ const { attachUntil } = kit;
29
32
  function poolPath(provider) {
30
33
  const name = provider === 'anthropic' ? 'anthropic-subscriptions.json' : 'openai-codex-subscriptions.json';
31
34
  return join(h.agentHome, '.pi', 'agent', name);
@@ -38,9 +41,24 @@ function seedPool(provider, cooling) {
38
41
  rateLimitedUntil: cooling ? Date.now() + 60_000 : 0, lastAttemptAt: 0, lastRateLimitedAt: 0,
39
42
  }]));
40
43
  }
44
+ // The node's recorded model (fake-pi.model.json, written by the fake's
45
+ // recordModel) — `null` for a model-less boot. A disk read, so it survives the
46
+ // broker exiting after its kickoff turn idle-releases.
41
47
  function model(id) {
42
48
  return JSON.parse(readFileSync(join(h.home, 'nodes', id, 'fake-pi.model.json'), 'utf8'));
43
49
  }
50
+ // The fake engine records each settled overflow episode's outcome in
51
+ // fake-pi.overflow.json, OVERWRITING it per episode. Two episodes on one broker
52
+ // must therefore gate on the parsed `outcome`, never on file existence.
53
+ function overflowOutcome(id) {
54
+ try {
55
+ const raw = readFileSync(join(h.home, 'nodes', id, 'fake-pi.overflow.json'), 'utf8');
56
+ return JSON.parse(raw).outcome ?? null;
57
+ }
58
+ catch {
59
+ return null;
60
+ }
61
+ }
44
62
  // Every fired lifecycle event is appended to fake-pi.events.jsonl. A broker
45
63
  // re-drive is a fresh session.prompt → emitTurn, which fires one 'agent_start';
46
64
  // counting them is the observable proof of how many times the turn was re-driven.
@@ -178,35 +196,20 @@ test('reload_auth leaves a parked auth fault with its original session when navi
178
196
  assert.equal(agentStartCount(child), baselineStarts, 'the replacement session receives no stale recovery prompt');
179
197
  controller.close();
180
198
  });
181
- test('successful compact-and-retry settles without a fatal fault or terminal transition', { timeout: 30_000 }, async () => {
182
- const parent = h.spawnRoot('recovered overflow parent');
199
+ // Both overflow outcomes on ONE broker, in order: Pi's compact-and-retry
200
+ // recovers (nothing is committed, the child lives), and only then does a
201
+ // definitive failure make the episode terminal.
202
+ test('a recovered compact-and-retry commits nothing, while a definitive failure publishes one structured overflow fault and terminal notice', { timeout: 30_000 }, async () => {
203
+ const parent = h.spawnRoot('overflow parent');
183
204
  seedPool('anthropic', false);
184
205
  seedPool('openai-codex', false);
185
- const child = await h.spawnHeadlessChild(parent, 'recovered overflow child', { model: OPENAI_STRONG.spec });
206
+ const child = await h.spawnHeadlessChild(parent, 'overflow child', { model: OPENAI_STRONG.spec });
186
207
  h.fakeCmd(child, { cmd: 'overflow', outcome: 'recover' });
187
- await h.waitFor(() => existsSync(join(h.home, 'nodes', child, 'fake-pi.overflow.json')), { label: 'recovered overflow reached agent_settled' });
208
+ await h.waitFor(() => overflowOutcome(child) === 'recover' ? true : null, { label: 'recovered overflow reached agent_settled' });
188
209
  await h.tick();
189
210
  assert.equal(readFault(child), null, 'raw overflow is never committed before Pi recovery settles');
190
211
  assert.notEqual(h.status(child), 'dead', 'a recovered child remains live');
191
212
  assert.doesNotMatch(h.inbox(parent).map((entry) => entry.label).join('\n'), /context window|will not recover/i, 'recovery publishes no fatal parent notice');
192
- });
193
- test('queued continuation success supersedes an earlier definitive compaction failure', { timeout: 30_000 }, async () => {
194
- const parent = h.spawnRoot('continued overflow parent');
195
- seedPool('anthropic', false);
196
- seedPool('openai-codex', false);
197
- const child = await h.spawnHeadlessChild(parent, 'continued overflow child', { model: OPENAI_STRONG.spec });
198
- h.fakeCmd(child, { cmd: 'overflow', outcome: 'failure-continued' });
199
- await h.waitFor(() => existsSync(join(h.home, 'nodes', child, 'fake-pi.overflow.json')), { label: 'post-failure continuation reached agent_settled' });
200
- await h.tick();
201
- assert.equal(readFault(child), null, 'the final successful continuation owns the settled provider outcome');
202
- assert.notEqual(h.status(child), 'dead', 'an earlier compaction failure cannot reap a recovered child');
203
- assert.doesNotMatch(h.inbox(parent).map((entry) => entry.label).join('\n'), /context window|will not recover/i, 'the superseded compaction failure publishes no parent notice');
204
- });
205
- test('failed compact-and-retry publishes one structured overflow fault and terminal notice', { timeout: 30_000 }, async () => {
206
- const parent = h.spawnRoot('failed overflow parent');
207
- seedPool('anthropic', false);
208
- seedPool('openai-codex', false);
209
- const child = await h.spawnHeadlessChild(parent, 'failed overflow child', { model: OPENAI_STRONG.spec });
210
213
  // A settled ordinary provider failure leaves an active daemon-owned retry
211
214
  // marker in place while the next turn runs. The later terminal overflow must
212
215
  // supersede that marker rather than lose to its priority in fault storage.
@@ -222,7 +225,7 @@ test('failed compact-and-retry publishes one structured overflow fault and termi
222
225
  }, { label: 'ordinary settled provider fault published' });
223
226
  assert.equal(autoFault.kind, 'rate-limit', 'ordinary settled provider failures retain broker-owned retry behavior');
224
227
  h.fakeCmd(child, { cmd: 'overflow', outcome: 'fail' });
225
- await h.waitFor(() => existsSync(join(h.home, 'nodes', child, 'fake-pi.overflow.json')), { label: 'failed overflow reached agent_settled' });
228
+ await h.waitFor(() => overflowOutcome(child) === 'fail' ? true : null, { label: 'failed overflow reached agent_settled' });
226
229
  const fault = readFault(child);
227
230
  assert.equal(fault?.kind, 'context-overflow');
228
231
  assert.equal(fault?.op, 'context overflow recovery');
@@ -233,65 +236,53 @@ test('failed compact-and-retry publishes one structured overflow fault and termi
233
236
  const notices = h.inbox(parent).filter((entry) => /exceeded its model context window/i.test(entry.label));
234
237
  assert.equal(notices.length, 1, 'the parent receives exactly one structured overflow notice');
235
238
  });
236
- test('raw overflow settling without compaction terminates instead of hanging', { timeout: 30_000 }, async () => {
237
- const parent = h.spawnRoot('uncompacted overflow parent');
238
- seedPool('anthropic', false);
239
- seedPool('openai-codex', false);
240
- const child = await h.spawnHeadlessChild(parent, 'uncompacted overflow child', { model: OPENAI_STRONG.spec });
241
- h.fakeCmd(child, { cmd: 'overflow', outcome: 'uncompacted' });
242
- await h.waitFor(() => existsSync(join(h.home, 'nodes', child, 'fake-pi.overflow.json')), { label: 'uncompacted overflow reached agent_settled' });
243
- const fault = readFault(child);
244
- assert.equal(fault?.kind, 'context-overflow', 'settlement makes a raw overflow terminal when recovery never began');
245
- assert.equal(fault?.retry.disposition, 'fatal');
246
- await h.tick();
247
- assert.equal(h.status(child), 'dead', 'settled raw overflow cannot leave the child hanging');
248
- });
249
- test('an explicit picker choice pins and clears prior logical routing; an internal no-raw-token path preserves that decision', { timeout: 30_000 }, async () => {
250
- const parent = h.spawnRoot('picker frame parent');
251
- seedPool('anthropic', false);
252
- seedPool('openai-codex', false);
253
- const child = await h.spawnHeadlessChild(parent, 'picker frame child', { model: 'openai/strong' }); // logical route boot
254
- const ctrl = await kit.attach(child, 'controller', 'picker-test');
255
- ctrl.send({ type: 'set_model', model: OPENAI_STRONG.spec }); // exactly what buildModelPicker sends — no `pinned`
256
- await ctrl.waitFrame((f) => f.type === 'ack' && f.for === 'set_model', 'picker set_model acked');
257
- let after = h.node(child);
258
- assert.equal(after.launch?.modelProviderPinned, true, 'the omitted-pinned frame derives pinned from the real openai-codex registry namespace');
259
- assert.equal(after.launch?.modelIntent, undefined, 'the explicit concrete choice clears the logical launch intent');
260
- assert.equal(after.launch?.env.CRTR_MODEL_INTENT, undefined, 'a revive cannot restore the stale logical route intent');
261
- // `cycle_ladder` — an internal no-raw-token path (no `model`/`pinned` on the
262
- // frame at all) — reuses the SAME resolve+setModel machinery `set_model` does
263
- // (real `nextLadderModel`/`resolveModelQuery`, the fake engine's real
264
- // `session.setModel`), unlike `cycle_model` which the fake engine's session
265
- // does not implement. `broadcastModelChanged()` is called here with no
266
- // override, so the existing pin must survive verbatim.
267
- ctrl.send({ type: 'cycle_ladder', direction: 'forward' });
268
- await ctrl.waitFrame((f) => f.type === 'ack' && f.for === 'cycle_ladder', 'cycle_ladder acked');
269
- after = h.node(child);
270
- assert.equal(after.launch?.modelProviderPinned, true, 'an internal no-raw-token change (cycle_ladder) preserves the existing pin verbatim');
271
- ctrl.close();
272
- });
273
- test("a user's Alt+M and Shift+Tab choices replace stale launch intent for restart replay", { timeout: 30_000 }, async () => {
274
- const parent = h.spawnRoot('session choice durability parent');
239
+ // Two real logical launches keep both user paths non-vacuous: the picker first
240
+ // proves an explicit concrete choice pins and clears routing, while Alt+M runs
241
+ // directly against still-present logical intent before Shift+Tab and replay.
242
+ test("picker and Alt+M choices each clear logical routing, and Alt+M / Shift+Tab become the durable restart recipe", { timeout: 30_000 }, async () => {
243
+ const parent = h.spawnRoot('model durability parent');
275
244
  seedPool('anthropic', false);
276
245
  seedPool('openai-codex', false);
277
- const child = await h.spawnHeadlessChild(parent, 'session choice durability child', { model: 'openai/strong' });
278
- const before = h.node(child);
279
- assert.deepEqual(before.launch?.modelIntent, { family: 'openai', strength: 'strong' }, 'fixture starts from a logical launch preference');
280
- assert.equal(before.launch?.env.CRTR_MODEL_INTENT, 'openai/strong');
281
- const ctrl = await kit.attach(child, 'controller', 'session-choice-durability');
282
- ctrl.send({ type: 'cycle_ladder', direction: 'forward' });
283
- await ctrl.waitFrame((f) => f.type === 'ack' && f.for === 'cycle_ladder', 'Alt+M cycle acked');
284
- let after = h.node(child);
285
- assert.equal(after.launch?.modelIntent, undefined, 'Alt+M replaces the old logical route intent');
286
- assert.equal(after.launch?.env.CRTR_MODEL_INTENT, undefined, 'a restarted broker cannot resolve the old route over the selected rung');
246
+ const pickerNode = await h.spawnHeadlessChild(parent, 'picker durability child', { model: 'openai/strong' });
247
+ const pickerBefore = h.node(pickerNode);
248
+ assert.deepEqual(pickerBefore.launch?.modelIntent, { family: 'openai', strength: 'strong' }, 'picker fixture starts from a logical launch preference');
249
+ assert.equal(pickerBefore.launch?.env.CRTR_MODEL_INTENT, 'openai/strong');
250
+ const pickerCtrl = await kit.attach(pickerNode, 'controller', 'picker-durability-test');
251
+ pickerCtrl.send({ type: 'set_model', model: OPENAI_STRONG.spec }); // exactly what buildModelPicker sends — no `pinned`
252
+ await pickerCtrl.waitFrame((f) => f.type === 'ack' && f.for === 'set_model', 'picker set_model acked');
253
+ let pickerState = h.node(pickerNode);
254
+ assert.equal(pickerState.launch?.modelProviderPinned, true, 'the omitted-pinned frame derives pinned from the real openai-codex registry namespace');
255
+ assert.equal(pickerState.launch?.modelIntent, undefined, 'the explicit concrete choice clears the logical launch intent');
256
+ assert.equal(pickerState.launch?.env.CRTR_MODEL_INTENT, undefined, 'a revive cannot restore the stale logical route intent');
257
+ // `cycle_ladder` carries no raw model/pin token. Running it after the picker
258
+ // proves that this internal change preserves the concrete provider pin.
259
+ pickerCtrl.send({ type: 'cycle_ladder', direction: 'forward' });
260
+ await pickerCtrl.waitFrame((f) => f.type === 'ack' && f.for === 'cycle_ladder', 'post-picker Alt+M cycle_ladder acked');
261
+ pickerState = h.node(pickerNode);
262
+ assert.equal(pickerState.launch?.modelProviderPinned, true, 'an internal no-raw-token ladder change preserves the explicit picker pin');
263
+ pickerCtrl.close();
264
+ // A separate logical launch reaches Alt+M with BOTH intent representations
265
+ // still populated. This is the regression path: a user-selected ladder rung
266
+ // must clear them before a restart can resolve the stale route over it.
267
+ const ladderNode = await h.spawnHeadlessChild(parent, 'ladder durability child', { model: 'openai/strong' });
268
+ const ladderBefore = h.node(ladderNode);
269
+ assert.deepEqual(ladderBefore.launch?.modelIntent, { family: 'openai', strength: 'strong' }, 'Alt+M starts with durable logical modelIntent present');
270
+ assert.equal(ladderBefore.launch?.env.CRTR_MODEL_INTENT, 'openai/strong', 'Alt+M starts with the launch environment intent present');
271
+ const ladderCtrl = await kit.attach(ladderNode, 'controller', 'ladder-durability-test');
272
+ ladderCtrl.send({ type: 'cycle_ladder', direction: 'forward' });
273
+ await ladderCtrl.waitFrame((f) => f.type === 'ack' && f.for === 'cycle_ladder', 'logical-launch Alt+M cycle_ladder acked');
274
+ let after = h.node(ladderNode);
275
+ assert.equal(after.launch?.modelIntent, undefined, 'Alt+M clears the durable logical launch intent');
276
+ assert.equal(after.launch?.env.CRTR_MODEL_INTENT, undefined, 'Alt+M clears the logical intent from the restart environment');
277
+ assert.equal(after.launch?.modelProviderPinned, ladderBefore.launch?.modelProviderPinned, 'Alt+M preserves the existing pin state verbatim');
287
278
  assert.equal(after.launch?.model, after.model_override, 'the selected model and thinking rung is the durable revive recipe');
288
279
  const rung = after.launch?.model;
289
280
  assert.ok(rung, 'Alt+M persisted a concrete model rung');
290
- const thinkingFrameOffset = ctrl.frames.length;
291
- ctrl.send({ type: 'cycle_thinking' });
292
- const thinkingChanged = await h.waitFor(() => ctrl.frames.slice(thinkingFrameOffset).find((f) => f.type === 'thinking_level_changed') ?? null, { label: 'Shift+Tab thinking change relayed' });
293
- await ctrl.waitFrame((f) => f.type === 'ack' && f.for === 'cycle_thinking', 'Shift+Tab cycle acked');
294
- after = h.node(child);
281
+ const thinkingFrameOffset = ladderCtrl.frames.length;
282
+ ladderCtrl.send({ type: 'cycle_thinking' });
283
+ const thinkingChanged = await h.waitFor(() => ladderCtrl.frames.slice(thinkingFrameOffset).find((f) => f.type === 'thinking_level_changed') ?? null, { label: 'Shift+Tab thinking change relayed' });
284
+ await ladderCtrl.waitFrame((f) => f.type === 'ack' && f.for === 'cycle_thinking', 'Shift+Tab cycle acked');
285
+ after = h.node(ladderNode);
295
286
  const level = thinkingChanged.type === 'thinking_level_changed' ? thinkingChanged.level : 'off';
296
287
  const bareRung = rung.replace(/:(?:off|minimal|low|medium|high|xhigh|max)$/, '');
297
288
  const expected = `${bareRung}${level === 'off' ? '' : `:${level}`}`;
@@ -302,7 +293,7 @@ test("a user's Alt+M and Shift+Tab choices replace stale launch intent for resta
302
293
  assert.notEqual(modelArg, -1, 'a daemon revive replays a model argument');
303
294
  assert.equal(replay.argv[modelArg + 1], expected, 'the replayed model argument carries both choices');
304
295
  assert.equal(replay.env.CRTR_MODEL_INTENT, undefined, 'the stale logical intent is absent from the restarted broker environment');
305
- ctrl.close();
296
+ ladderCtrl.close();
306
297
  });
307
298
  test('reload_auth restores a newly authenticated personal route before the next idle prompt', { timeout: 30_000 }, async () => {
308
299
  const personal = { provider: 'personal-anthropic', id: 'claude-personal' };
@@ -354,3 +345,64 @@ test('reload_auth restores a newly authenticated personal route before the next
354
345
  rmSync(userConfigPath, { force: true });
355
346
  }
356
347
  });
348
+ // MODEL-LESS LAUNCH RE-DRIVE — MUST REMAIN THE LAST TEST IN THIS FILE. Its
349
+ // zero-auth seam, <CRTR_HOME>/fake-pi.no-auth, is HOME-GLOBAL: while it exists
350
+ // the fake engine's modelRegistry reports a genuine zero-auth boot, so no other
351
+ // boot may overlap it. Arming it before the spawn is race-free against the
352
+ // broker's boot-time registry read, and deleting it mid-test is a live auth-on
353
+ // flip that reload_auth's modelRegistry.refresh() re-reads (fake-engine.ts,
354
+ // createAgentSessionServices).
355
+ //
356
+ // Locks two broker.ts launch-model fixes:
357
+ // 0b unknown-stub detection — the SDK hands a model-less session an
358
+ // unknown-stub model (never `undefined`), so sessionHasNoModel() must defer
359
+ // the kickoff instead of prompting a keyless engine.
360
+ // 0a self-healing model-less registry — the construction-time mask that
361
+ // suppresses the SDK's findInitialModel must reveal() the instant
362
+ // construction returns, or list_models stays broken forever for a
363
+ // model-less-booted node even after a /login. The auth-on list_models
364
+ // assertion below is the ONLY one that discriminates that bug: with the
365
+ // mask never revealed, getAvailable() returns [] forever.
366
+ //
367
+ // The request is LOGICAL (`anthropic/medium`), deliberately. A CONCRETE pin can
368
+ // never reach a model-less boot: both `node new`'s assertLaunchModelRegistered
369
+ // and resolveLaunchModel's own unregisteredConcreteModel gate treat a concrete
370
+ // spec absent from the serving registry as a FATAL model-not-found, and under
371
+ // the zero-auth sentinel every spec is absent. A logical request skips both
372
+ // gates by design, so the zero-auth window produces the tier-3 model-less boot
373
+ // this test exists to cover. After the flip its candidate tail includes the
374
+ // fake registry's non-managed `fake/fake-model`, whose hasConfiguredAuth() the
375
+ // sentinel controls — so the re-drive resolves without needing managed
376
+ // subscription-pool occupancy (broker-sdk-wiring's scope).
377
+ test('login re-drive → list_models self-heals (0a) and reload_auth resolves a model + fires the deferred kickoff', { timeout: 30_000 }, async () => {
378
+ const noAuthSentinel = join(h.home, 'fake-pi.no-auth');
379
+ writeFileSync(noAuthSentinel, '1');
380
+ try {
381
+ const parent = h.spawnRoot('login re-drive parent');
382
+ const child = await h.spawnHeadlessChild(parent, 'login re-drive', { model: 'anthropic/medium' });
383
+ assert.equal(model(child), null, 'boots model-less under zero-auth');
384
+ assert.equal(agentStartCount(child), 0, 'kickoff deferred at boot');
385
+ const c = await attachUntil(child, 'controller', 'redrive-ctrl', (a) => a.welcome.role === 'controller', 'redrive controller admitted');
386
+ // A read-op reply is a correlated `data` frame the client swallows into
387
+ // request()'s by-id resolver (NEVER a generic 'frame'), so we MUST use
388
+ // client.request(), not send()+waitFrame.
389
+ // Baseline: under zero-auth the registry reports nothing available.
390
+ const lm0 = await c.client.request({ type: 'list_models' });
391
+ assert.ok(lm0.kind === 'list_models' && lm0.availableIds.length === 0, 'zero-auth: no models available');
392
+ // FLIP AUTH ON, then re-query while the broker is still alive (no turn has run
393
+ // yet). getAvailable() flows through the revealed construction proxy →
394
+ // NON-empty. With the mask never revealed it would still be [].
395
+ rmSync(noAuthSentinel, { force: true });
396
+ const lm1 = await c.client.request({ type: 'list_models' });
397
+ assert.ok(lm1.kind === 'list_models' && lm1.availableIds.length > 0, 'auth on: list_models reports an available model (0a self-heal — the construction mask revealed)');
398
+ // reload_auth re-drives the launch policy → resolves a model and fires the
399
+ // deferred kickoff. Disk reads, so they survive the broker idle-releasing
400
+ // once that kickoff turn ends.
401
+ c.send({ type: 'reload_auth' });
402
+ await h.waitFor(() => model(child) !== null, { label: 're-drive selected a model' });
403
+ await h.waitFor(() => agentStartCount(child) >= 1, { label: 'deferred kickoff fired after re-drive' });
404
+ }
405
+ finally {
406
+ rmSync(noAuthSentinel, { force: true });
407
+ }
408
+ });
@@ -1,4 +1,4 @@
1
- // Run with: node --import tsx/esm --test src/core/__tests__/full/broker-whip-interrupt.test.ts
1
+ // Run with: node --import tsx/esm --test src/core/__tests__/seam/broker-whip-interrupt.test.ts
2
2
  //
3
3
  // A whip is a transport-level abort-then-prompt, not an ordinary steering queue
4
4
  // entry. This needs a real broker because the guarantee crosses the socket frame,
@@ -31,9 +31,20 @@ test('a whip aborts the active turn before starting its prompt', { timeout: 20_0
31
31
  const id = await h.spawnHeadlessChild(root, 'headless worker — held turn');
32
32
  const controller = await kit.attach(id, 'controller', 'whip-controller');
33
33
  assert.equal(controller.welcome.snapshot.state.isStreaming, true, 'the original turn is held active');
34
- const startsBeforeWhip = controller.frames.filter((frame) => frame.type === 'agent_start').length;
34
+ const frameOffset = controller.frames.length;
35
35
  const message = 'Whip command must start after the active turn is interrupted';
36
36
  controller.send({ type: 'prompt', text: message, interrupt: true });
37
- await h.waitFor(() => controller.frames.filter((frame) => frame.type === 'agent_start').length > startsBeforeWhip || null, { label: 'whip prompt started a new turn after the abort' });
38
- assert.ok(controller.frames.some((frame) => frame.type === 'message_start' && JSON.stringify(frame).includes(message)), 'the whip message starts a fresh prompt rather than entering the steering queue');
37
+ const whipFrames = await h.waitFor(() => {
38
+ const frames = controller.frames.slice(frameOffset);
39
+ return frames.some((frame) => frame.type === 'agent_start') &&
40
+ frames.some((frame) => frame.type === 'message_start' && JSON.stringify(frame).includes(message))
41
+ ? frames
42
+ : null;
43
+ }, { label: 'whip prompt started a new turn after the abort' });
44
+ const settledIndex = whipFrames.findIndex((frame) => frame.type === 'agent_settled');
45
+ const whipStartIndex = whipFrames.findIndex((frame) => frame.type === 'agent_start');
46
+ const whipMessageIndex = whipFrames.findIndex((frame) => frame.type === 'message_start' && JSON.stringify(frame).includes(message));
47
+ assert.ok(settledIndex >= 0, 'abort releases the held original turn through agent_settled');
48
+ assert.ok(whipStartIndex > settledIndex, 'the original held turn settles before the whip turn starts');
49
+ assert.ok(whipMessageIndex > settledIndex, 'the whip message starts only after the original turn settles');
39
50
  });