create-walle 0.10.2 → 0.10.3

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 (488) hide show
  1. package/README.md +3 -1
  2. package/lib/release-schema.json +2 -2
  3. package/lib/runtime-closure-policy.json +8 -7
  4. package/package.json +1 -1
  5. package/template/coding-task-manager/api-prompts.js +45 -7
  6. package/template/coding-task-manager/bin/claude-hook.js +55 -1
  7. package/template/coding-task-manager/bin/ctm-relay-client.js +35 -12
  8. package/template/coding-task-manager/bin/land.js +2069 -0
  9. package/template/coding-task-manager/bin/memory-census.js +4 -2
  10. package/template/coding-task-manager/bin/memory-sync-verify.js +151 -0
  11. package/template/coding-task-manager/bin/probe-refusal-scope.js +530 -0
  12. package/template/coding-task-manager/db.js +156 -32
  13. package/template/coding-task-manager/lib/acp/acp-client.js +383 -0
  14. package/template/coding-task-manager/lib/acp/dsh-context-truth.js +176 -0
  15. package/template/coding-task-manager/lib/acp/dsh-launch-model.js +302 -0
  16. package/template/coding-task-manager/lib/acp/dsh-mcp-servers.js +194 -0
  17. package/template/coding-task-manager/lib/acp/dsh-prompt-lane.js +616 -0
  18. package/template/coding-task-manager/lib/acp/dsh-route-patch.js +308 -0
  19. package/template/coding-task-manager/lib/acp/dsh-session-runtime.js +522 -0
  20. package/template/coding-task-manager/lib/acp/dsh-transcript.js +395 -0
  21. package/template/coding-task-manager/lib/acp/dsh-usage-log.js +162 -0
  22. package/template/coding-task-manager/lib/acp/ndjson-stdio-transport.js +225 -0
  23. package/template/coding-task-manager/lib/adopted-host-redial.js +47 -11
  24. package/template/coding-task-manager/lib/adopted-launch-environment.js +123 -0
  25. package/template/coding-task-manager/lib/agent-auto-update-runtime.js +0 -3
  26. package/template/coding-task-manager/lib/agent-auto-update.js +26 -10
  27. package/template/coding-task-manager/lib/agent-bundle/build.js +657 -0
  28. package/template/coding-task-manager/lib/agent-bundle/codex-install.js +558 -0
  29. package/template/coding-task-manager/lib/agent-bundle/drift.js +120 -0
  30. package/template/coding-task-manager/lib/agent-bundle/gc.js +522 -0
  31. package/template/coding-task-manager/lib/agent-bundle/inventory.js +346 -0
  32. package/template/coding-task-manager/lib/agent-bundle/push.js +717 -0
  33. package/template/coding-task-manager/lib/agent-bundle/skill-transfer.js +724 -0
  34. package/template/coding-task-manager/lib/agent-bundle/spawn.js +119 -0
  35. package/template/coding-task-manager/lib/agent-bundle/store.js +568 -0
  36. package/template/coding-task-manager/lib/agent-capabilities.js +142 -12
  37. package/template/coding-task-manager/lib/agent-cli-cache.js +30 -5
  38. package/template/coding-task-manager/lib/agent-definitions.js +19 -0
  39. package/template/coding-task-manager/lib/agent-hooks-codex-toml.js +28 -16
  40. package/template/coding-task-manager/lib/agent-hooks-codex-workspace.js +9 -1
  41. package/template/coding-task-manager/lib/agent-hooks-installer.js +23 -8
  42. package/template/coding-task-manager/lib/agent-hooks-prune.js +37 -11
  43. package/template/coding-task-manager/lib/agent-hooks-shim.js +105 -0
  44. package/template/coding-task-manager/lib/agent-presets.js +84 -1
  45. package/template/coding-task-manager/lib/agent-upgrade-jobs.js +12 -1
  46. package/template/coding-task-manager/lib/agent-version-service.js +74 -11
  47. package/template/coding-task-manager/lib/all-sessions-query.js +6 -2
  48. package/template/coding-task-manager/lib/api/agent-bundles.js +436 -0
  49. package/template/coding-task-manager/lib/api/backup-retention.js +83 -0
  50. package/template/coding-task-manager/lib/api/conversation-sync-routes.js +124 -0
  51. package/template/coding-task-manager/lib/api/diagnostics.js +549 -69
  52. package/template/coding-task-manager/lib/api/fleet.js +118 -0
  53. package/template/coding-task-manager/lib/api/hub-bridge.js +243 -0
  54. package/template/coding-task-manager/lib/api/ingress.js +9 -1
  55. package/template/coding-task-manager/lib/api/landing.js +403 -0
  56. package/template/coding-task-manager/lib/api/machines.js +691 -2
  57. package/template/coding-task-manager/lib/api/memory-sync-routes.js +335 -0
  58. package/template/coding-task-manager/lib/api/misc-ops.js +8 -3
  59. package/template/coding-task-manager/lib/api/remote-sessions.js +1326 -0
  60. package/template/coding-task-manager/lib/api/session-attach.js +623 -0
  61. package/template/coding-task-manager/lib/api/session-messages.js +1384 -0
  62. package/template/coding-task-manager/lib/api/sessions.js +77 -51
  63. package/template/coding-task-manager/lib/api/setup-network.js +544 -16
  64. package/template/coding-task-manager/lib/api/setup-wizard-handlers.js +884 -0
  65. package/template/coding-task-manager/lib/api/setup.js +829 -10
  66. package/template/coding-task-manager/lib/api/walle-bridge.js +9 -1
  67. package/template/coding-task-manager/lib/attachment-reconciler-wiring.js +5 -2
  68. package/template/coding-task-manager/lib/auth-rate-limit.js +23 -0
  69. package/template/coding-task-manager/lib/auth-rules.js +292 -21
  70. package/template/coding-task-manager/lib/background-stop.js +369 -0
  71. package/template/coding-task-manager/lib/best-of-attempts.js +4 -1
  72. package/template/coding-task-manager/lib/best-of-launch-plan.js +52 -0
  73. package/template/coding-task-manager/lib/box-keep-awake.js +89 -0
  74. package/template/coding-task-manager/lib/broadcast-reason-stats.js +152 -0
  75. package/template/coding-task-manager/lib/broadcast-serialize.js +5 -0
  76. package/template/coding-task-manager/lib/build-info.js +4 -2
  77. package/template/coding-task-manager/lib/claude-model-truth.js +13 -2
  78. package/template/coding-task-manager/lib/claude-transcript-terminal-renderer.js +17 -13
  79. package/template/coding-task-manager/lib/client-diag-handler.js +64 -2
  80. package/template/coding-task-manager/lib/codex-app-server-client.js +14 -16
  81. package/template/coding-task-manager/lib/codex-configuration-history.js +55 -0
  82. package/template/coding-task-manager/lib/codex-harness-qualification.js +43 -20
  83. package/template/coding-task-manager/lib/codex-rollout-history.js +743 -0
  84. package/template/coding-task-manager/lib/coding-agent-models.js +27 -12
  85. package/template/coding-task-manager/lib/coding-cli-skill-installer.js +44 -14
  86. package/template/coding-task-manager/lib/coding-session-model-adapters.js +43 -2
  87. package/template/coding-task-manager/lib/coding-session-model-owner.js +1 -1
  88. package/template/coding-task-manager/lib/coding-session-model-public.js +7 -1
  89. package/template/coding-task-manager/lib/coding-session-model-runtime.js +23 -4
  90. package/template/coding-task-manager/lib/coding-session-model-scan.js +31 -0
  91. package/template/coding-task-manager/lib/coding-session-model-state.js +7 -0
  92. package/template/coding-task-manager/lib/conversation-freshness-diagnostics.js +280 -0
  93. package/template/coding-task-manager/lib/conversation-item-clock.js +261 -0
  94. package/template/coding-task-manager/lib/conversation-source-registry.js +5 -0
  95. package/template/coding-task-manager/lib/conversation-store-v2.js +44 -0
  96. package/template/coding-task-manager/lib/conversation-sync.js +355 -0
  97. package/template/coding-task-manager/lib/create/resolve-launch-params.js +96 -12
  98. package/template/coding-task-manager/lib/create/worker-role-profile.js +4 -4
  99. package/template/coding-task-manager/lib/create-intent-lease.js +51 -0
  100. package/template/coding-task-manager/lib/ctm-session-context-api.js +97 -6
  101. package/template/coding-task-manager/lib/db-domain-tables.js +1 -0
  102. package/template/coding-task-manager/lib/db-domains.js +1 -0
  103. package/template/coding-task-manager/lib/db-owner-dispatch-probe.js +32 -0
  104. package/template/coding-task-manager/lib/db-owner-request-counts.js +178 -0
  105. package/template/coding-task-manager/lib/db-owner-worker-client.js +68 -14
  106. package/template/coding-task-manager/lib/db-schema-source-files.js +5 -0
  107. package/template/coding-task-manager/lib/deferred-restore-attempt.js +6 -2
  108. package/template/coding-task-manager/lib/feature-manifest.js +4 -0
  109. package/template/coding-task-manager/lib/fleet-conversation-route.js +97 -0
  110. package/template/coding-task-manager/lib/fleet-fanout.js +78 -0
  111. package/template/coding-task-manager/lib/fleet-hub.js +762 -0
  112. package/template/coding-task-manager/lib/fleet-origin-follower.js +252 -0
  113. package/template/coding-task-manager/lib/fleet-resolver-effects.js +147 -0
  114. package/template/coding-task-manager/lib/fleet-resolver-tick.js +148 -0
  115. package/template/coding-task-manager/lib/fleet-resolver.js +454 -0
  116. package/template/coding-task-manager/lib/fleet-session-list-routes.js +114 -0
  117. package/template/coding-task-manager/lib/fleet-session-lists.js +672 -0
  118. package/template/coding-task-manager/lib/fleet-update.js +798 -0
  119. package/template/coding-task-manager/lib/fs-watcher-root-watch.js +141 -0
  120. package/template/coding-task-manager/lib/fs-watcher.js +58 -42
  121. package/template/coding-task-manager/lib/harness-history-transition.js +50 -9
  122. package/template/coding-task-manager/lib/headless-term-service.js +84 -8
  123. package/template/coding-task-manager/lib/hermetic-agent-launch-guard.js +10 -0
  124. package/template/coding-task-manager/lib/history-transition-read-op.js +175 -8
  125. package/template/coding-task-manager/lib/host-cpu-categories.js +310 -0
  126. package/template/coding-task-manager/lib/host-fseventsd-sampler.js +399 -0
  127. package/template/coding-task-manager/lib/host-handoff.js +16 -19
  128. package/template/coding-task-manager/lib/host-stats.js +150 -3
  129. package/template/coding-task-manager/lib/hosted-sync-worker.js +43 -320
  130. package/template/coding-task-manager/lib/hub-bridge-access.js +798 -0
  131. package/template/coding-task-manager/lib/hub-bridge-box.js +409 -0
  132. package/template/coding-task-manager/lib/hub-bridge-mcp.js +380 -0
  133. package/template/coding-task-manager/lib/hub-bridge-mesh.js +313 -0
  134. package/template/coding-task-manager/lib/hub-bridge-ops.js +721 -0
  135. package/template/coding-task-manager/lib/hub-bridge.js +398 -0
  136. package/template/coding-task-manager/lib/idle-notify.js +491 -0
  137. package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +8 -0
  138. package/template/coding-task-manager/lib/instance-manifest.js +5 -1
  139. package/template/coding-task-manager/lib/instance-sync-status.js +18 -8
  140. package/template/coding-task-manager/lib/landing/agent-rule.txt +1 -0
  141. package/template/coding-task-manager/lib/landing/batch-runner.js +366 -0
  142. package/template/coding-task-manager/lib/landing/config.js +536 -0
  143. package/template/coding-task-manager/lib/landing/ctm-view.js +256 -0
  144. package/template/coding-task-manager/lib/landing/gate.js +360 -0
  145. package/template/coding-task-manager/lib/landing/git.js +344 -0
  146. package/template/coding-task-manager/lib/landing/lease.js +247 -0
  147. package/template/coding-task-manager/lib/landing/merge-delta.js +380 -0
  148. package/template/coding-task-manager/lib/landing/proof.js +223 -0
  149. package/template/coding-task-manager/lib/landing/queue.js +998 -0
  150. package/template/coding-task-manager/lib/landing/radar.js +489 -0
  151. package/template/coding-task-manager/lib/landing/resolve-cli.js +424 -0
  152. package/template/coding-task-manager/lib/landing/state.js +263 -0
  153. package/template/coding-task-manager/lib/landing/stop.js +49 -0
  154. package/template/coding-task-manager/lib/landing/sync-cli.js +475 -0
  155. package/template/coding-task-manager/lib/landing/sync.js +456 -0
  156. package/template/coding-task-manager/lib/landing/train.js +885 -0
  157. package/template/coding-task-manager/lib/landing/window.js +57 -0
  158. package/template/coding-task-manager/lib/launch-environment.js +121 -0
  159. package/template/coding-task-manager/lib/launch-model-override.js +36 -10
  160. package/template/coding-task-manager/lib/launch-plan-pool.js +2 -1
  161. package/template/coding-task-manager/lib/launch-plan-warm-cache.js +6 -0
  162. package/template/coding-task-manager/lib/lead-model-observer.js +57 -1
  163. package/template/coding-task-manager/lib/lead-model-options.js +160 -3
  164. package/template/coding-task-manager/lib/lead-model-restore-reconciliation.js +106 -24
  165. package/template/coding-task-manager/lib/lead-model-switch-runtime.js +352 -68
  166. package/template/coding-task-manager/lib/lead-model-switch.js +114 -10
  167. package/template/coding-task-manager/lib/lead-model-transition-runtime.js +14 -2
  168. package/template/coding-task-manager/lib/lead-model-transition-store.js +24 -6
  169. package/template/coding-task-manager/lib/lead-route-preflight.js +179 -0
  170. package/template/coding-task-manager/lib/lead-switch-history-mode.js +110 -0
  171. package/template/coding-task-manager/lib/lead-wire-agent.js +7 -0
  172. package/template/coding-task-manager/lib/lifecycle-wake-experiment.js +9 -2
  173. package/template/coding-task-manager/lib/live-defer-announce.js +45 -0
  174. package/template/coding-task-manager/lib/machine-registry.js +750 -5
  175. package/template/coding-task-manager/lib/macos-capabilities.js +32 -15
  176. package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +433 -53
  177. package/template/coding-task-manager/lib/mainloop-observability.js +14 -9
  178. package/template/coding-task-manager/lib/media-draft-owner.js +16 -3
  179. package/template/coding-task-manager/lib/memory-join.js +337 -0
  180. package/template/coding-task-manager/lib/memory-sync-verify.js +393 -0
  181. package/template/coding-task-manager/lib/memory-sync.js +565 -0
  182. package/template/coding-task-manager/lib/migrations/v57-delegate-pref-mirror-opencode.js +68 -0
  183. package/template/coding-task-manager/lib/migrations/v58-subagent-launch-native-subagents.js +42 -0
  184. package/template/coding-task-manager/lib/migrations/v59-coding-session-model-state-dsh.js +73 -0
  185. package/template/coding-task-manager/lib/migrations/v60-delegate-pref-mirror-dsh.js +78 -0
  186. package/template/coding-task-manager/lib/migrations/v61-conversation-item-clock.js +71 -0
  187. package/template/coding-task-manager/lib/model-availability.js +727 -0
  188. package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +4 -6
  189. package/template/coding-task-manager/lib/model-host-receipt.js +6 -3
  190. package/template/coding-task-manager/lib/model-id-rule.js +19 -0
  191. package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
  192. package/template/coding-task-manager/lib/model-proxy/auto-mode-review.js +110 -23
  193. package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
  194. package/template/coding-task-manager/lib/model-proxy/generation-probe.js +60 -17
  195. package/template/coding-task-manager/lib/model-proxy/historical-tool-declarations.js +37 -0
  196. package/template/coding-task-manager/lib/model-proxy/lead-request-validation.js +1 -0
  197. package/template/coding-task-manager/lib/model-proxy/lead-token-admin.js +130 -0
  198. package/template/coding-task-manager/lib/model-proxy/refusal-scope.js +151 -0
  199. package/template/coding-task-manager/lib/model-proxy/request-budget-store.js +11 -2
  200. package/template/coding-task-manager/lib/model-proxy/tokens.js +69 -5
  201. package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
  202. package/template/coding-task-manager/lib/native-agent-model-args.js +25 -22
  203. package/template/coding-task-manager/lib/native-history-control.js +6 -4
  204. package/template/coding-task-manager/lib/native-lead-settings-client.js +44 -4
  205. package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +173 -36
  206. package/template/coding-task-manager/lib/online-backup-worker-client.js +2 -0
  207. package/template/coding-task-manager/lib/opencode-cli.js +480 -0
  208. package/template/coding-task-manager/lib/opencode-launch-model.js +471 -0
  209. package/template/coding-task-manager/lib/opencode-v1-plugin/model-carrier.mjs +35 -0
  210. package/template/coding-task-manager/lib/opencode-v2-plugin/server.js +118 -0
  211. package/template/coding-task-manager/lib/owner-action-manifest-data.json +1 -1
  212. package/template/coding-task-manager/lib/park-composer-draft.js +54 -0
  213. package/template/coding-task-manager/lib/park-fresh-relaunch.js +41 -10
  214. package/template/coding-task-manager/lib/perf-phase-trace.js +72 -0
  215. package/template/coding-task-manager/lib/perf-summary.js +7 -0
  216. package/template/coding-task-manager/lib/perf-tracker.js +63 -2
  217. package/template/coding-task-manager/lib/project-config.js +7 -0
  218. package/template/coding-task-manager/lib/prompt-purge-v2.js +52 -1
  219. package/template/coding-task-manager/lib/provider-login-broker.js +72 -15
  220. package/template/coding-task-manager/lib/provider-login-tab.js +10 -2
  221. package/template/coding-task-manager/lib/read-pool-client.js +89 -2
  222. package/template/coding-task-manager/lib/read-pool-worker-handle.js +29 -4
  223. package/template/coding-task-manager/lib/recent-catalog-query.js +15 -0
  224. package/template/coding-task-manager/lib/recent-session-catalog-response.js +67 -10
  225. package/template/coding-task-manager/lib/recovery-evidence.js +26 -11
  226. package/template/coding-task-manager/lib/relay-client.js +57 -3
  227. package/template/coding-task-manager/lib/relink-audit.js +34 -5
  228. package/template/coding-task-manager/lib/remote-machine-catalog.js +321 -26
  229. package/template/coding-task-manager/lib/remote-machine-create.js +576 -36
  230. package/template/coding-task-manager/lib/remote-machine-link.js +546 -59
  231. package/template/coding-task-manager/lib/remote-machine-proxy.js +143 -9
  232. package/template/coding-task-manager/lib/remote-session-forward.js +65 -7
  233. package/template/coding-task-manager/lib/remote-terminal-target.js +9 -2
  234. package/template/coding-task-manager/lib/restart-guard.js +18 -6
  235. package/template/coding-task-manager/lib/restore-seed-modes.js +87 -0
  236. package/template/coding-task-manager/lib/restored-scrollback-hydration-runtime.js +22 -2
  237. package/template/coding-task-manager/lib/resume-admission.js +17 -3
  238. package/template/coding-task-manager/lib/review-repo-registry.js +58 -19
  239. package/template/coding-task-manager/lib/root-delivery-runtime.js +2 -2
  240. package/template/coding-task-manager/lib/root-prompt-flow.js +1 -1
  241. package/template/coding-task-manager/lib/runtime-contract.js +3 -1
  242. package/template/coding-task-manager/lib/schema-indexes.js +1 -0
  243. package/template/coding-task-manager/lib/session-context-locator.js +742 -0
  244. package/template/coding-task-manager/lib/session-create/attachment.js +2 -2
  245. package/template/coding-task-manager/lib/session-create/create-phases.js +118 -0
  246. package/template/coding-task-manager/lib/session-create/delegate-worker.js +86 -16
  247. package/template/coding-task-manager/lib/session-create/dsh-session.js +930 -0
  248. package/template/coding-task-manager/lib/session-create/lead-route-launch.js +186 -13
  249. package/template/coding-task-manager/lib/session-create/native-lead-lifetime.js +55 -3
  250. package/template/coding-task-manager/lib/session-create/opencode-lead-provider.js +50 -0
  251. package/template/coding-task-manager/lib/session-create/prepared.js +162 -38
  252. package/template/coding-task-manager/lib/session-create/provider-process.js +160 -28
  253. package/template/coding-task-manager/lib/session-create/response.js +7 -7
  254. package/template/coding-task-manager/lib/session-create/structured-session.js +27 -0
  255. package/template/coding-task-manager/lib/session-create/subagent-pick-table.js +3 -1
  256. package/template/coding-task-manager/lib/session-create/subagent-pick.js +458 -9
  257. package/template/coding-task-manager/lib/session-create/transcript.js +1 -1
  258. package/template/coding-task-manager/lib/session-create/worker-budget-setup.js +21 -2
  259. package/template/coding-task-manager/lib/session-create/worker-continuation.js +7 -1
  260. package/template/coding-task-manager/lib/session-create/worker-launch-sandbox.js +39 -10
  261. package/template/coding-task-manager/lib/session-create/worker-observability.js +2 -2
  262. package/template/coding-task-manager/lib/session-empty.js +13 -10
  263. package/template/coding-task-manager/lib/session-host-manager.js +60 -0
  264. package/template/coding-task-manager/lib/session-jobs.js +3 -1
  265. package/template/coding-task-manager/lib/session-lifecycle.js +10 -4
  266. package/template/coding-task-manager/lib/session-model-action-preconditions.js +6 -2
  267. package/template/coding-task-manager/lib/session-model-intent-contract.js +11 -8
  268. package/template/coding-task-manager/lib/session-park.js +738 -0
  269. package/template/coding-task-manager/lib/session-path-grant.js +13 -4
  270. package/template/coding-task-manager/lib/session-status-ledger.js +106 -4
  271. package/template/coding-task-manager/lib/session-transport.js +17 -0
  272. package/template/coding-task-manager/lib/session-workspace-projection.js +54 -5
  273. package/template/coding-task-manager/lib/session-workspace-tracking-runtime.js +213 -1
  274. package/template/coding-task-manager/lib/skill-autocomplete-query.js +120 -3
  275. package/template/coding-task-manager/lib/skill-autocomplete.js +15 -1
  276. package/template/coding-task-manager/lib/sqlite-file-set.js +39 -0
  277. package/template/coding-task-manager/lib/standup-search-adapter.js +16 -2
  278. package/template/coding-task-manager/lib/state-sync/client-ledger.js +12 -0
  279. package/template/coding-task-manager/lib/state-sync/frame-emitter.js +319 -100
  280. package/template/coding-task-manager/lib/storage/coding-session-model-store.js +4 -1
  281. package/template/coding-task-manager/lib/storage/delegate-pref-mirror-store.js +32 -9
  282. package/template/coding-task-manager/lib/structured-session-boot-sweep.js +235 -0
  283. package/template/coding-task-manager/lib/submission-ack.js +90 -2
  284. package/template/coding-task-manager/lib/systemd-scope.js +115 -0
  285. package/template/coding-task-manager/lib/telemetry-receiver.js +39 -0
  286. package/template/coding-task-manager/lib/terminal-attach-geometry.js +985 -0
  287. package/template/coding-task-manager/lib/terminal-bootstrap.js +30 -0
  288. package/template/coding-task-manager/lib/terminal-draft-operation.js +357 -0
  289. package/template/coding-task-manager/lib/terminal-input-admission.js +17 -1
  290. package/template/coding-task-manager/lib/terminal-input-prepared.js +563 -0
  291. package/template/coding-task-manager/lib/transcript-cache-temps.js +171 -0
  292. package/template/coding-task-manager/lib/transcript-frame-cache.js +8 -3
  293. package/template/coding-task-manager/lib/transcript-source.js +36 -4
  294. package/template/coding-task-manager/lib/transcript-store.js +9 -0
  295. package/template/coding-task-manager/lib/typing-interaction-trace.js +106 -3
  296. package/template/coding-task-manager/lib/usage-ledger/backfill.js +40 -5
  297. package/template/coding-task-manager/lib/usage-ledger/client.js +4 -1
  298. package/template/coding-task-manager/lib/usage-ledger/dsh-parse.js +152 -0
  299. package/template/coding-task-manager/lib/usage-ledger/dsh-read.js +73 -0
  300. package/template/coding-task-manager/lib/usage-ledger/ingest.js +34 -6
  301. package/template/coding-task-manager/lib/usage-ledger/live.js +35 -8
  302. package/template/coding-task-manager/lib/usage-ledger/read.js +29 -20
  303. package/template/coding-task-manager/lib/usage-ledger/schema.js +61 -1
  304. package/template/coding-task-manager/lib/usage-ledger/task-coordinator.js +4 -2
  305. package/template/coding-task-manager/lib/wake-skip-reasons.js +1 -0
  306. package/template/coding-task-manager/lib/walle/turn.js +1 -1
  307. package/template/coding-task-manager/lib/walle-history-serve.js +838 -0
  308. package/template/coding-task-manager/lib/walle-kill-authority.js +2 -2
  309. package/template/coding-task-manager/lib/walle-log-rotation.js +164 -1
  310. package/template/coding-task-manager/lib/walle-mcp-auto-config.js +56 -2
  311. package/template/coding-task-manager/lib/walle-row-history.js +495 -0
  312. package/template/coding-task-manager/lib/walle-supervisor.js +33 -14
  313. package/template/coding-task-manager/lib/watch-worker-client.js +203 -0
  314. package/template/coding-task-manager/lib/worker-terminal-output.js +5 -2
  315. package/template/coding-task-manager/lib/ws/hub-bridge.js +35 -0
  316. package/template/coding-task-manager/lib/ws/lifecycle.js +6 -11
  317. package/template/coding-task-manager/lib/ws/misc.js +1 -1
  318. package/template/coding-task-manager/lib/ws/state-sync.js +106 -4
  319. package/template/coding-task-manager/lib/ws/terminal-attach.js +91 -0
  320. package/template/coding-task-manager/oauth-proxy.js +112 -115
  321. package/template/coding-task-manager/package-lock.json +10 -94
  322. package/template/coding-task-manager/package.json +4 -3
  323. package/template/coding-task-manager/protocol/message-table.json +10 -0
  324. package/template/coding-task-manager/protocol/messages.js +10 -0
  325. package/template/coding-task-manager/protocol/ws-schema.json +20 -0
  326. package/template/coding-task-manager/protocol/ws-schema.md +2 -0
  327. package/template/coding-task-manager/public/css/app.css +170 -0
  328. package/template/coding-task-manager/public/css/walle-session.css +4 -0
  329. package/template/coding-task-manager/public/index.html +473 -243
  330. package/template/coding-task-manager/public/js/agent-command.js +86 -0
  331. package/template/coding-task-manager/public/js/app/backups-panel.js +111 -5
  332. package/template/coding-task-manager/public/js/app/coding-agent-upgrade.js +103 -4
  333. package/template/coding-task-manager/public/js/app/machines-page.js +1328 -8
  334. package/template/coding-task-manager/public/js/app/models-tab.js +113 -38
  335. package/template/coding-task-manager/public/js/app/restart-banner.js +53 -0
  336. package/template/coding-task-manager/public/js/app/worktrees-panel.js +184 -2
  337. package/template/coding-task-manager/public/js/background-work.js +196 -27
  338. package/template/coding-task-manager/public/js/coding-session-model-presentation.js +138 -10
  339. package/template/coding-task-manager/public/js/core/active-session-groups.js +179 -0
  340. package/template/coding-task-manager/public/js/core/pending-create.js +238 -0
  341. package/template/coding-task-manager/public/js/core/recent-machine-source.js +228 -0
  342. package/template/coding-task-manager/public/js/core/session-commands.js +180 -14
  343. package/template/coding-task-manager/public/js/dashboard-client-function-order.json +8 -0
  344. package/template/coding-task-manager/public/js/document-review-links.js +9 -1
  345. package/template/coding-task-manager/public/js/file-resolution-client.js +52 -1
  346. package/template/coding-task-manager/public/js/history-app-seam.js +22 -2
  347. package/template/coding-task-manager/public/js/machine-pool-shared.js +558 -3
  348. package/template/coding-task-manager/public/js/message-renderer.js +4 -3
  349. package/template/coding-task-manager/public/js/model-picker.js +3 -1
  350. package/template/coding-task-manager/public/js/native-subagent-review.js +15 -2
  351. package/template/coding-task-manager/public/js/new-session-agent-grid.js +346 -0
  352. package/template/coding-task-manager/public/js/pasted-content.js +236 -0
  353. package/template/coding-task-manager/public/js/pe-private-content-lifetime.js +39 -7
  354. package/template/coding-task-manager/public/js/private-model-lifetime.js +6 -2
  355. package/template/coding-task-manager/public/js/prompts.js +11 -10
  356. package/template/coding-task-manager/public/js/protocol.js +10 -0
  357. package/template/coding-task-manager/public/js/restart-banner-policy.js +29 -1
  358. package/template/coding-task-manager/public/js/session-model-picker.js +447 -57
  359. package/template/coding-task-manager/public/js/session-search-utils.js +18 -2
  360. package/template/coding-task-manager/public/js/session-usage-view.js +2 -1
  361. package/template/coding-task-manager/public/js/session-workspace-tracking.js +51 -1
  362. package/template/coding-task-manager/public/js/state-sync-client.js +10 -1
  363. package/template/coding-task-manager/public/js/stream-view.js +6 -5
  364. package/template/coding-task-manager/public/js/terminal/conv-only-session.js +287 -0
  365. package/template/coding-task-manager/public/js/terminal/runtime-09.js +2 -0
  366. package/template/coding-task-manager/public/js/terminal/runtime-10.js +5 -5
  367. package/template/coding-task-manager/public/js/terminal/runtime-12.js +8 -1
  368. package/template/coding-task-manager/public/js/terminal/runtime-16.js +4 -0
  369. package/template/coding-task-manager/public/js/terminal/runtime-20.js +1 -1
  370. package/template/coding-task-manager/public/js/terminal-attachment-draft.js +67 -13
  371. package/template/coding-task-manager/public/js/terminal-image-orchestration.js +52 -6
  372. package/template/coding-task-manager/public/js/terminal-wheel.js +23 -3
  373. package/template/coding-task-manager/public/js/walle/chat-conversation.js +4 -4
  374. package/template/coding-task-manager/public/js/walle.js +6 -0
  375. package/template/coding-task-manager/public/js/ws/live-dispatch.js +10 -0
  376. package/template/coding-task-manager/public/m/app.css +140 -0
  377. package/template/coding-task-manager/public/m/app.js +507 -68
  378. package/template/coding-task-manager/public/m/index.html +17 -0
  379. package/template/coding-task-manager/public/m/machines.js +838 -4
  380. package/template/coding-task-manager/public/m/private-api.js +31 -1
  381. package/template/coding-task-manager/public/usage.html +1 -0
  382. package/template/coding-task-manager/scripts/dev-route-fixture/synthetic-route-upstream.js +104 -0
  383. package/template/coding-task-manager/scripts/dev-route-fixture.js +339 -0
  384. package/template/coding-task-manager/scripts/opencode-lead-route-probe.js +195 -0
  385. package/template/coding-task-manager/scripts/opencode-v2-contract-probe/fake-anthropic.js +168 -0
  386. package/template/coding-task-manager/scripts/opencode-v2-contract-probe/probe-plugin/server.js +95 -0
  387. package/template/coding-task-manager/scripts/opencode-v2-contract-probe.js +673 -0
  388. package/template/coding-task-manager/server.js +1950 -11679
  389. package/template/coding-task-manager/session-integrity.js +64 -4
  390. package/template/coding-task-manager/workers/db-owner-worker.js +19 -2
  391. package/template/coding-task-manager/workers/online-backup-worker.js +1 -0
  392. package/template/coding-task-manager/workers/read-pool-model-collector.js +51 -3
  393. package/template/coding-task-manager/workers/read-pool-worker.js +76 -6
  394. package/template/coding-task-manager/workers/session-host-pool-process.js +6 -6
  395. package/template/coding-task-manager/workers/session-host-process.js +5 -5
  396. package/template/coding-task-manager/workers/watch-worker.js +105 -0
  397. package/template/coding-task-manager/workers/worktree-worker.js +168 -33
  398. package/template/ctm-relay/lib/protocol.js +14 -1
  399. package/template/package-lock.json +2 -2
  400. package/template/package.json +1 -1
  401. package/template/shared/backup-retention.js +82 -0
  402. package/template/shared/conversation-admission.js +2 -0
  403. package/template/shared/conversation-provenance.js +3 -7
  404. package/template/shared/conversation-storage-v2-contract.js +5 -0
  405. package/template/shared/linked-worktree.js +51 -0
  406. package/template/shared/macos-fda-target.js +57 -0
  407. package/template/shared/orphan-parents.js +55 -0
  408. package/template/shared/release-schema.json +2 -2
  409. package/template/shared/storage/contract.js +3 -0
  410. package/template/wall-e/adapters/imessage.js +12 -0
  411. package/template/wall-e/agent.js +71 -4
  412. package/template/wall-e/api-walle.js +21 -1
  413. package/template/wall-e/bin/train-gemma-e4b-tooluse.js +2 -2
  414. package/template/wall-e/bin/walle-mcp-stdio.js +5 -5
  415. package/template/wall-e/brain/brain-join.js +859 -0
  416. package/template/wall-e/brain/brain-sync-membership.js +20 -0
  417. package/template/wall-e/brain/brain-sync-store.js +131 -77
  418. package/template/wall-e/brain/brain-sync-v2-schema.js +203 -0
  419. package/template/wall-e/brain/brain-sync-v2.js +578 -0
  420. package/template/wall-e/brain/delegate-model-prefs-store.js +13 -4
  421. package/template/wall-e/brain/deterministic-id.js +51 -0
  422. package/template/wall-e/brain/memories.js +2 -2
  423. package/template/wall-e/brain/migrations.js +40 -6
  424. package/template/wall-e/brain/model-identity-migration.js +1 -1
  425. package/template/wall-e/brain/owner-call-whitelist.js +23 -7
  426. package/template/wall-e/brain/schema.js +45 -85
  427. package/template/wall-e/brain.js +171 -35
  428. package/template/wall-e/chat.js +3 -3
  429. package/template/wall-e/coding/agent-shell-env.js +7 -2
  430. package/template/wall-e/coding/prompt-bundle.js +66 -21
  431. package/template/wall-e/config/main-loop-exemptions.json +2 -2
  432. package/template/wall-e/http/admin-api.js +37 -0
  433. package/template/wall-e/http/gws-api.js +4 -0
  434. package/template/wall-e/http/helper-rules-api.js +7 -1
  435. package/template/wall-e/http/model-admin.js +8 -1
  436. package/template/wall-e/http/model-delegate-pref-admin.js +3 -3
  437. package/template/wall-e/http/model-proxy-providers.js +174 -1
  438. package/template/wall-e/http/model-route-health.js +157 -0
  439. package/template/wall-e/http/sync-api.js +278 -464
  440. package/template/wall-e/jobs/job-runner.js +28 -1
  441. package/template/wall-e/lib/brain-owner-op-priority.js +9 -1
  442. package/template/wall-e/lib/ctm-data-dir.js +26 -0
  443. package/template/wall-e/lib/derivation-home.js +147 -0
  444. package/template/wall-e/lib/helper-rules/evaluate.js +50 -9
  445. package/template/wall-e/lib/helper-rules/grade.js +51 -8
  446. package/template/wall-e/lib/helper-rules/lead-call-hook.js +38 -5
  447. package/template/wall-e/lib/helper-rules/store.js +4 -2
  448. package/template/wall-e/lib/landing-mcp-tools.js +170 -0
  449. package/template/wall-e/lib/mcp-client-config.js +133 -14
  450. package/template/wall-e/lib/mcp-gateway-migrate.js +28 -64
  451. package/template/wall-e/lib/mcp-integration.js +58 -13
  452. package/template/wall-e/lib/parent-death.js +7 -1
  453. package/template/wall-e/lib/scheduler-worker-jobs.js +6 -4
  454. package/template/wall-e/llm/delegate-lead-prefs.js +16 -1
  455. package/template/wall-e/llm/delegate-vendor-default.js +12 -5
  456. package/template/wall-e/llm/model-roles.js +1 -1
  457. package/template/wall-e/llm/native-delegate-selection.js +56 -8
  458. package/template/wall-e/llm/worker-delegate-args.js +72 -7
  459. package/template/wall-e/llm/worker-mcp-routing.js +66 -17
  460. package/template/wall-e/loops/brain-custom-writes.js +19 -0
  461. package/template/wall-e/loops/ingest.js +17 -1
  462. package/template/wall-e/loops/think.js +19 -4
  463. package/template/wall-e/mcp-contract.js +222 -6
  464. package/template/wall-e/mcp-protocol-contract.js +40 -5
  465. package/template/wall-e/mcp-server.js +128 -29
  466. package/template/wall-e/mcp-worker-schemas.js +5 -5
  467. package/template/wall-e/memory/ctm-context-client.js +85 -0
  468. package/template/wall-e/memory/ctm-session-context.js +9 -3
  469. package/template/wall-e/memory/hub-bridge-client.js +388 -0
  470. package/template/wall-e/memory/replica-origin.js +56 -0
  471. package/template/wall-e/memory/session-pool.js +135 -0
  472. package/template/wall-e/runtime/live-capabilities.js +8 -3
  473. package/template/wall-e/server.js +4 -2
  474. package/template/wall-e/shared/release-schema.json +2 -2
  475. package/template/wall-e/shared/sqlite-file-set.js +39 -0
  476. package/template/wall-e/shared/storage/contract.js +3 -0
  477. package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
  478. package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +7 -1
  479. package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +4 -4
  480. package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +6 -6
  481. package/template/wall-e/skills/_templates/fleet-sync/VERSION +1 -0
  482. package/template/wall-e/skills/_templates/fleet-sync/released-sha256.json +16 -0
  483. package/template/wall-e/skills/_templates/fleet-sync/templates/claude/SKILL.md +64 -0
  484. package/template/wall-e/skills/_templates/fleet-sync/templates/codex/SKILL.md +64 -0
  485. package/template/wall-e/tools/chrome-automation.js +6 -0
  486. package/template/wall-e/training/setup-cuda-train-env.sh +2 -2
  487. package/template/wall-e/workers/brain-owner-worker.js +18 -4
  488. package/template/wall-e/workers/mcp-gateway-process.js +5 -5
@@ -0,0 +1,54 @@
1
+ 'use strict';
2
+
3
+ // WP-1 (docs/park-draft-truth-from-screen-2026-10-26.html §5 D-1/D-2/D-3) — the park decision's
4
+ // draft truth is the agent's own composer row, read from the server's headless mirror, not the
5
+ // browser's keystroke accounting. `lifecycle-draft` presence never produces a `draft` protection
6
+ // any more (that phantom refused parks and lead model switches on an empty composer).
7
+ //
8
+ // The verdict reuses `composerState` (lib/terminal-composer.js) unchanged: one glyph rule for the
9
+ // phone path and the park path alike (D-2). The verdict carries only a length, never the text.
10
+
11
+ const { composerState } = require('./terminal-composer');
12
+
13
+ /** @typedef {'text'|'empty'|'unknown'} ComposerDraftState */
14
+ /** @typedef {'composer-text'|'composer-empty'|'rows-unavailable'|'no-composer-row'|'rows-error'} ComposerDraftReason */
15
+ /** @typedef {{ state: ComposerDraftState, reason: ComposerDraftReason, chars: number, at: number }} ComposerDraftVerdict */
16
+
17
+ // Same bound as server.js's Submit-current-input probe (~16548).
18
+ const PARK_COMPOSER_READ_TIMEOUT_MS = 900;
19
+
20
+ /**
21
+ * Pure. `rows === null|undefined` (not a row list) is unreadable; a row list with no composer row
22
+ * is unreadable for a different reason. Only visible, non-dim composer text is a draft.
23
+ * @param {Array<{text: string, wrapped: boolean, dim: boolean}>|null|undefined} rows
24
+ * @param {number} [now]
25
+ * @returns {ComposerDraftVerdict}
26
+ */
27
+ function composerDraftVerdict(rows, now = Date.now()) {
28
+ if (!Array.isArray(rows)) return { state: 'unknown', reason: 'rows-unavailable', chars: 0, at: now };
29
+ const composer = composerState(rows);
30
+ if (composer.row === -1) return { state: 'unknown', reason: 'no-composer-row', chars: 0, at: now };
31
+ if (composer.empty) return { state: 'empty', reason: 'composer-empty', chars: 0, at: now };
32
+ return { state: 'text', reason: 'composer-text', chars: composer.text.length, at: now };
33
+ }
34
+
35
+ /**
36
+ * Never throws. Flushes the mirror's feed first (so text typed in the last milliseconds is in the
37
+ * rows) and reads them through the caller's bounded reader. Any failure — flush, worker, timeout —
38
+ * is the single `unknown` state, never a phantom draft.
39
+ * @param {{id?: string, _flushHeadlessFeed?: Function}|null|undefined} session
40
+ * @param {{getRows?: (sessionId: string, timeoutMs: number) => Promise<Array|null>, timeoutMs?: number, now?: () => number}} [options]
41
+ * @returns {Promise<ComposerDraftVerdict>}
42
+ */
43
+ async function readComposerDraft(session, { getRows, timeoutMs = PARK_COMPOSER_READ_TIMEOUT_MS, now = Date.now } = {}) {
44
+ try {
45
+ if (session && typeof session._flushHeadlessFeed === 'function') session._flushHeadlessFeed();
46
+ const rows = await getRows(session && session.id, timeoutMs);
47
+ return composerDraftVerdict(rows, now());
48
+ } catch {
49
+ // HANDLE: an unreadable screen is 'unknown' (D-4) — automatic parks wait, manual parks proceed.
50
+ return { state: 'unknown', reason: 'rows-error', chars: 0, at: now() };
51
+ }
52
+ }
53
+
54
+ module.exports = { composerDraftVerdict, readComposerDraft, PARK_COMPOSER_READ_TIMEOUT_MS };
@@ -9,6 +9,10 @@
9
9
  // already acts on (lib/session-restore.js). No flags.
10
10
 
11
11
  const CLAUDE_RESUME_FORMS = new Set(['--resume', '-r', '--continue', '-c']);
12
+ // OpenCode 1.x's own resume selectors (docs/opencode-lead-and-delegate-model-selection-2026-10-28.html
13
+ // §9 S-7, R-2). A stored argv that already names a conversation is NOT a fresh launch: relaunching it
14
+ // with `--model` would be ignored by TUI 1.x (the last user message wins, §5 R-4), so it is refused.
15
+ const OPENCODE_RESUME_FORMS = new Set(['--session', '-s', '--continue', '-c']);
12
16
 
13
17
  /**
14
18
  * The launch args a fresh relaunch runs, or null when today's resume path must stand.
@@ -21,6 +25,13 @@ function freshRelaunchArgs({ agentType, storedTask, sessionCmd, sessionId, resum
21
25
  const args = storedTask.args.slice();
22
26
  if (agentType === 'codex') {
23
27
  if (args[0] === 'resume') return null;
28
+ } else if (agentType === 'opencode') {
29
+ // No `--session-id` injection for OpenCode: CTM cannot mint an OpenCode session id on 1.x
30
+ // (probe P-10), so the stored args are copied EXACTLY.
31
+ for (const arg of args) {
32
+ const text = String(arg);
33
+ if (OPENCODE_RESUME_FORMS.has(text) || text.startsWith('--session=')) return null;
34
+ }
24
35
  } else {
25
36
  for (const arg of args) {
26
37
  if (CLAUDE_RESUME_FORMS.has(arg)) return null;
@@ -35,17 +46,41 @@ function freshRelaunchArgs({ agentType, storedTask, sessionCmd, sessionId, resum
35
46
  /**
36
47
  * Whether park should relaunch fresh: the off-thread ladder says 'fresh' when nothing exists to
37
48
  * resume. False for any other provider; for Claude an empty jsonlPath is nothing to look up.
38
- * @param {{ agentType: string, ctmSessionId: string, jsonlPath: string,
49
+ * OpenCode 1.x has no ladder to consult at all: it creates its session on the first prompt (probe
50
+ * P-9), so "nothing to resume" is exactly "no captured resume id".
51
+ * @param {{ agentType: string, ctmSessionId: string, jsonlPath: string, resumeId?: string,
39
52
  * resolveRung: (input: { ctmSessionId: string, provider: string, jsonlPath: string }) => Promise<{ rung: string }> }} input
40
53
  * @returns {Promise<boolean>}
41
54
  */
42
- async function parkFindsNothingToResume({ agentType, ctmSessionId, jsonlPath, resolveRung }) {
55
+ async function parkFindsNothingToResume({ agentType, ctmSessionId, jsonlPath, resumeId, resolveRung }) {
56
+ if (agentType === 'opencode') return !resumeId;
43
57
  if (agentType !== 'claude' && agentType !== 'codex') return false;
44
58
  if (agentType === 'claude' && !jsonlPath) return false;
45
59
  const result = await resolveRung({ ctmSessionId, provider: agentType, jsonlPath: jsonlPath || '' });
46
60
  return result?.rung === 'fresh';
47
61
  }
48
62
 
63
+ /**
64
+ * D-5 in ONE place: the resume-evidence flag and the jsonlPath the ladder is asked about, derived
65
+ * from the prepared transcript/codex rollout. Shared with lib/lead-switch-history-mode.js, which
66
+ * must never grow a second copy of this rule (WP-0/§6 "one definition").
67
+ * @param {{ agentType: string, transcript?: object|null, codexRolloutPath?: string }} input
68
+ * @returns {{ resumeEvidence: boolean, jsonlPath: string }}
69
+ */
70
+ function freshEvidenceInputs({ agentType, transcript, codexRolloutPath }) {
71
+ // A transcript/rollout file on disk means there is something to resume (and Claude refuses a
72
+ // `--session-id` whose .jsonl exists), so the ladder is consulted only when none exists.
73
+ // OpenCode has neither: its "something to resume" question is answered from the captured resume
74
+ // id alone (see parkFindsNothingToResume), so a null/undefined transcript is NOT evidence here.
75
+ const resumeEvidence = agentType === 'opencode'
76
+ ? false
77
+ : agentType === 'codex' ? !!codexRolloutPath : !!(transcript && transcript.missing === false);
78
+ const jsonlPath = agentType === 'codex'
79
+ ? (codexRolloutPath || '')
80
+ : (transcript && (transcript.filePath || (Array.isArray(transcript.candidates) ? transcript.candidates[0] : '')));
81
+ return { resumeEvidence, jsonlPath: jsonlPath || '' };
82
+ }
83
+
49
84
  /**
50
85
  * Compose D-5 (jsonlPath from the transcript/codex rollout), the ladder check, and the fresh args.
51
86
  * Never throws: a ladder or DB error is reported through `diagnostic` and yields null.
@@ -57,13 +92,9 @@ async function parkFindsNothingToResume({ agentType, ctmSessionId, jsonlPath, re
57
92
  */
58
93
  async function planFreshRelaunch({ session, agentType, resumeId, transcript, codexRolloutPath, storedTask, resolveRung, diagnostic }) {
59
94
  try {
60
- // A transcript/rollout file on disk means there is something to resume (and Claude refuses a
61
- // `--session-id` whose .jsonl exists), so the ladder is consulted only when none exists.
62
- if (agentType === 'codex' ? !!codexRolloutPath : !!(transcript && transcript.missing === false)) return null;
63
- const jsonlPath = agentType === 'codex'
64
- ? (codexRolloutPath || '')
65
- : (transcript && (transcript.filePath || (Array.isArray(transcript.candidates) ? transcript.candidates[0] : '')));
66
- const nothingToResume = await parkFindsNothingToResume({ agentType, ctmSessionId: session.id, jsonlPath, resolveRung });
95
+ const { resumeEvidence, jsonlPath } = freshEvidenceInputs({ agentType, transcript, codexRolloutPath });
96
+ if (resumeEvidence) return null;
97
+ const nothingToResume = await parkFindsNothingToResume({ agentType, ctmSessionId: session.id, jsonlPath, resumeId, resolveRung });
67
98
  if (!nothingToResume) return null;
68
99
  return freshRelaunchArgs({ agentType, storedTask, sessionCmd: session.cmd || '', sessionId: session.id, resumeId });
69
100
  } catch (err) {
@@ -73,4 +104,4 @@ async function planFreshRelaunch({ session, agentType, resumeId, transcript, cod
73
104
  }
74
105
  }
75
106
 
76
- module.exports = { freshRelaunchArgs, parkFindsNothingToResume, planFreshRelaunch };
107
+ module.exports = { freshRelaunchArgs, parkFindsNothingToResume, freshEvidenceInputs, planFreshRelaunch };
@@ -0,0 +1,72 @@
1
+ 'use strict';
2
+
3
+ // Phase trace for one synchronous scan run (WP-0 / M-3, measurement only —
4
+ // docs/hub-local-cpu-memory-burn-2026-10-31.html §7). Used by session-integrity.js
5
+ // (detectMismatches) and lib/relink-audit.js (auditRelink): on the hub the
6
+ // sessionIntegrityDetect op outlives its caller's timeout and the result is discarded, so the
7
+ // `[session-integrity] phases {...}` line each scan logs at its end is the only record.
8
+ //
9
+ // Gated by CTM_PERF_ATTRIBUTION (on by default; '0'/'false'/'no'/'off' → createPerfTrace()
10
+ // returns null and callers record nothing). Cost: one performance.now() per lap. Memory is
11
+ // bounded by the caller's fixed set of phase/counter names, never by row count.
12
+
13
+ const { boolFlag } = require('./env-flags');
14
+
15
+ const NOOP_LAP = () => {};
16
+
17
+ function perfNow() {
18
+ return performance.now();
19
+ }
20
+
21
+ function createPerfTrace() {
22
+ if (!boolFlag('CTM_PERF_ATTRIBUTION', true)) return null;
23
+ const start = perfNow();
24
+ const trace = { t0: start, t: start, phases: Object.create(null), counters: Object.create(null) };
25
+ // lap(name, n): closes the open segment and charges it to `name`, then opens the next one —
26
+ // so the laps of a run partition its wall time and sum(phases.ms) == totalMs.
27
+ trace.lap = (name, n = 1) => {
28
+ const now = perfNow();
29
+ const phase = trace.phases[name] || (trace.phases[name] = { ms: 0, n: 0 });
30
+ phase.ms += now - trace.t;
31
+ phase.n += n;
32
+ trace.t = now;
33
+ };
34
+ // count(name, ms, n): an overlapping sub-measurement of a call already inside a lap
35
+ // (e.g. findCodexSessionFiles). Kept apart so it can never distort the phase sum.
36
+ trace.count = (name, ms, n = 1) => {
37
+ const counter = trace.counters[name] || (trace.counters[name] = { ms: 0, n: 0 });
38
+ counter.ms += ms;
39
+ counter.n += n;
40
+ };
41
+ // timed(name, fn): runs fn and records it under counters[name].
42
+ trace.timed = (name, fn) => {
43
+ const started = perfNow();
44
+ try {
45
+ return fn();
46
+ } finally {
47
+ trace.count(name, perfNow() - started, 1);
48
+ }
49
+ };
50
+ return trace;
51
+ }
52
+
53
+ function _round(value) {
54
+ return Math.round(value * 10) / 10;
55
+ }
56
+
57
+ function _roundMap(map) {
58
+ const out = {};
59
+ for (const [name, entry] of Object.entries(map)) out[name] = { ms: _round(entry.ms), n: entry.n };
60
+ return out;
61
+ }
62
+
63
+ function perfPhasesJson(scope, trace) {
64
+ return JSON.stringify({
65
+ scope,
66
+ totalMs: _round(perfNow() - trace.t0),
67
+ phases: _roundMap(trace.phases),
68
+ counters: _roundMap(trace.counters),
69
+ });
70
+ }
71
+
72
+ module.exports = { NOOP_LAP, createPerfTrace, perfPhasesJson };
@@ -194,6 +194,13 @@ function buildPerfSummary(input = {}) {
194
194
  syncIo,
195
195
  wal: input.wal || null,
196
196
  workers: { dbOwner, readPool },
197
+ // WP-0 M-1 (§7): CPU per main-thread timer, { [namedInterval]: { count, userMs, systemMs } }.
198
+ // Empty object when CTM_PERF_ATTRIBUTION=0 or no named timer has ticked yet.
199
+ mainTimers: { cpuByTimer: input.mainTimers?.cpuByTimer || {} },
200
+ // WP-0 M-2 (§3.2/§7): per-reason session-list broadcast counters, { [reason]: { calls,
201
+ // suppressed, coalesced, flushes, serialized, elided } } — see lib/broadcast-reason-stats.js.
202
+ // Empty object when CTM_PERF_ATTRIBUTION=0.
203
+ broadcast: { byReason: input.broadcast?.byReason || {} },
197
204
  insertChunkHoldMs: dbOwner?.insertChunkHoldMs || null,
198
205
  writeLock: input.writeLock || null,
199
206
  mainWriteEscapes: input.mainWriteEscapes || null,
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { AsyncLocalStorage } = require('node:async_hooks');
4
4
  const path = require('node:path');
5
+ const { boolFlag } = require('./env-flags');
5
6
 
6
7
  // Tiny shared module for tagging the current synchronous operation. The
7
8
  // always-on event-loop lag detector reads `getOp()` to identify what was
@@ -296,6 +297,11 @@ function _hostCores() {
296
297
 
297
298
  function buildEventLoopBlockEvidence(previous = {}, current = {}, options = {}) {
298
299
  const blockedMs = Math.max(1, Number(options.blockedMs) || 1);
300
+ // WP-2 (design §5 D-6 / §9 S-5..S-7): the watchdog worker's own measured suspension
301
+ // overlap. It is the one observation a suspended process cannot fake — the worker thread
302
+ // keeps ticking on its own scheduler while the whole process is frozen — so when most of
303
+ // the block is suspension, that is the label, not an inference from CPU or run queue.
304
+ const suspendedMs = Math.max(0, Number(options.suspendedMs) || 0);
299
305
  const mainThreadCpuMs = Math.max(0, Number(options.mainThreadCpuDeltaMs ?? options.cpuDeltaMs) || 0);
300
306
  const processCpuMs = Math.max(0, Number(options.processCpuDeltaMs ?? options.cpuDeltaMs) || 0);
301
307
  let memory = options.memory;
@@ -369,6 +375,13 @@ function buildEventLoopBlockEvidence(previous = {}, current = {}, options = {})
369
375
  else if (mainThreadCpuPct >= 50) signal = 'main_cpu';
370
376
  else if (loadPerCore !== null && loadPerCore >= _OVERSUBSCRIBED_LOAD_PER_CORE) signal = 'host-oversubscribed';
371
377
 
378
+ // WP-2 (D-6): the watchdog's measured suspension beats every inferential host signal. A
379
+ // system sleep or task_suspend freezes the whole process, so CPU, sync-I/O and run-queue
380
+ // evidence all read "nothing ran" and the honest label is the suspension itself. The
381
+ // 1000 ms floor keeps a short tick stall from being restated as machine sleep, and the
382
+ // 50% majority keeps a suspension that merely overlapped a real block from relabelling it.
383
+ if (blockedMs >= 1000 && suspendedMs >= 0.5 * blockedMs) signal = 'process-suspended';
384
+
372
385
  return {
373
386
  signal,
374
387
  // D-2: append the sampler's last per-worker/zstd numbers (null/absent provider adds nothing) so
@@ -405,6 +418,8 @@ function buildEventLoopBlockEvidence(previous = {}, current = {}, options = {})
405
418
  offCpuMs,
406
419
  offCpuUnexplainedMs,
407
420
  offCpuUnexplainedPct,
421
+ // WP-2: the measured suspension this verdict was made from (0 when nothing was measured).
422
+ suspendedMs,
408
423
  hostFreeBytes,
409
424
  hostTotalBytes,
410
425
  freePagesPct,
@@ -866,11 +881,57 @@ function _callerOwner() {
866
881
  return 'unknown';
867
882
  }
868
883
 
884
+ // WP-0 M-1 (docs/hub-local-cpu-memory-burn-2026-10-31.html §7): per-timer CPU attribution for
885
+ // the main-thread intervals, so a boot-time CPU burn in the 3 s / 30 s / 60 s sweeps can be
886
+ // named instead of guessed. Same gate as the read-pool side (CTM_PERF_ATTRIBUTION, on in
887
+ // source): when off, nothing is sampled and getTimerCpuStats() is an empty object.
888
+ const _timerCpu = new Map(); // name -> { count, userUs, systemUs }; bounded by the named timers
889
+ const _perfAttributionOn = () => boolFlag('CTM_PERF_ATTRIBUTION', true);
890
+ const _threadCpu = typeof process.threadCpuUsage === 'function'
891
+ ? () => process.threadCpuUsage()
892
+ : () => ({ user: 0, system: 0 });
893
+
894
+ // Only the SYNCHRONOUS part of a tick is measured: the sample is taken around the fn.apply()
895
+ // call and computed in `finally`, so a callback that returns a promise reports the CPU its
896
+ // synchronous prefix spent and nothing that happens after it returns. Nothing is awaited here --
897
+ // the timer's own behaviour (return value, thrown error, cadence) is unchanged.
898
+ function _timedIntervalCallback(key, fn) {
899
+ return function timedIntervalTick(...args) {
900
+ if (!_perfAttributionOn()) return fn.apply(this, args);
901
+ const cpu0 = _threadCpu();
902
+ try {
903
+ return fn.apply(this, args);
904
+ } finally {
905
+ const cpu = _threadCpu();
906
+ const totals = _timerCpu.get(key) || { count: 0, userUs: 0, systemUs: 0 };
907
+ totals.count += 1;
908
+ totals.userUs += Math.max(0, cpu.user - cpu0.user);
909
+ totals.systemUs += Math.max(0, cpu.system - cpu0.system);
910
+ _timerCpu.set(key, totals);
911
+ }
912
+ };
913
+ }
914
+
915
+ // { [timer]: { count, userMs, systemMs } } -- ms rounded to 0.1, summed from µs so repeated
916
+ // reads cannot drift. Read by perf-summary (mainTimers.cpuByTimer).
917
+ function getTimerCpuStats() {
918
+ if (!_perfAttributionOn()) return {};
919
+ const out = {};
920
+ for (const [name, totals] of _timerCpu) {
921
+ out[name] = {
922
+ count: totals.count,
923
+ userMs: Math.round((totals.userUs / 1000) * 10) / 10,
924
+ systemMs: Math.round((totals.systemUs / 1000) * 10) / 10,
925
+ };
926
+ }
927
+ return out;
928
+ }
929
+
869
930
  function namedInterval(name, fn, ms, ...args) {
870
931
  if (typeof fn !== 'function') throw new TypeError('namedInterval requires a function');
871
932
  const key = String(name || 'timer');
872
933
  _namedIntervalRegistry.set(key, { name: key, ms, owner: _callerOwner(), registeredAt: Date.now() });
873
- return setInterval(wrap(key, fn), ms, ...args);
934
+ return setInterval(_timedIntervalCallback(key, wrap(key, fn)), ms, ...args);
874
935
  }
875
936
 
876
937
  // Exposed for GET /api/diagnostics/main-watchdog (AC-5b) and tests. A registration-time
@@ -1030,4 +1091,4 @@ function loadPerCore() {
1030
1091
  } catch { return null; }
1031
1092
  }
1032
1093
 
1033
- module.exports = { eventLoopBlockMs, getOp, getActiveOpName, getAttributionState, getRecentSpans, getRecentEventLoopBlocks, eventLoopBlockOverlap, _EVENT_LOOP_BLOCK_RING_MAX, recordCompletedSpan, recordExternalSpan, recordSyncIoCall, recordMainDbCall, recordEventLoopBlock, recordEventLoopLagSample, eventLoopLagProfileSuffix, getEventLoopStats, captureEventLoopEvidenceBaseline, buildEventLoopBlockEvidence, shouldReportLag, wrap, wrapAsync, namedInterval, listNamedIntervals, runSync, runSyncProbed, findRecentSynchronousBlockEvidence, isSuspectedSleepBlock, isOffCpuSpan, loadPerCore, setMemoryExtrasProvider, hostCores: _hostCores, OVERSUBSCRIBED_LOAD_PER_CORE: _OVERSUBSCRIBED_LOAD_PER_CORE, SLEEP_BLOCK_MS: _SLEEP_BLOCK_MS, FREEZE_PROBE_MS, perfLogsEnabled, shouldLogPerf, PERF_CRITICAL_MS, isWorkerThread: _isWorkerThread, MAINLOOP_DESCHED_LOAD, MAINLOOP_DESCHED_INVOLCTX, runOutsideAttributionForTests };
1094
+ module.exports = { eventLoopBlockMs, getOp, getActiveOpName, getAttributionState, getRecentSpans, getRecentEventLoopBlocks, eventLoopBlockOverlap, _EVENT_LOOP_BLOCK_RING_MAX, recordCompletedSpan, recordExternalSpan, recordSyncIoCall, recordMainDbCall, recordEventLoopBlock, recordEventLoopLagSample, eventLoopLagProfileSuffix, getEventLoopStats, captureEventLoopEvidenceBaseline, buildEventLoopBlockEvidence, shouldReportLag, wrap, wrapAsync, namedInterval, listNamedIntervals, getTimerCpuStats, runSync, runSyncProbed, findRecentSynchronousBlockEvidence, isSuspectedSleepBlock, isOffCpuSpan, loadPerCore, setMemoryExtrasProvider, hostCores: _hostCores, OVERSUBSCRIBED_LOAD_PER_CORE: _OVERSUBSCRIBED_LOAD_PER_CORE, SLEEP_BLOCK_MS: _SLEEP_BLOCK_MS, FREEZE_PROBE_MS, perfLogsEnabled, shouldLogPerf, PERF_CRITICAL_MS, isWorkerThread: _isWorkerThread, MAINLOOP_DESCHED_LOAD, MAINLOOP_DESCHED_INVOLCTX, runOutsideAttributionForTests };
@@ -114,6 +114,13 @@ function _normalize(data) {
114
114
  }
115
115
  if (Object.keys(worker).length) out.worker = worker;
116
116
  }
117
+ // WP-0 (local-landing-queue-multi-session-integration-2026-10-26.html §5 D-1, §6.1): the
118
+ // landing queue's configuration object. _normalize only passes a plain object through — the
119
+ // landing validator (lib/landing/config.js) owns its contents, including the rejection of
120
+ // unknown keys, so nothing is whitelisted or rewritten here. Never merged with the keys above.
121
+ if (data.landing && typeof data.landing === 'object' && !Array.isArray(data.landing)) {
122
+ out.landing = data.landing;
123
+ }
117
124
  return out;
118
125
  }
119
126
 
@@ -41,6 +41,13 @@ function purgeConversationV2(db, sessionId, sourceIds, eventKeys, nowMs = Date.n
41
41
  .map((row) => row.stable_key);
42
42
  let deleted = 0;
43
43
  let canonicalChanges = 0;
44
+ // WP-5 (shared-memory-every-machine §5 D-3, §6.8): a purge must stay addressable on the wire —
45
+ // stamp a tombstone (deleted=1, fresh local seq) for every purged key BEFORE the row goes, so
46
+ // the conversation clock feed can tell a peer to delete it too. No outbox row: the delete feed
47
+ // (conversation_projection_deletes_v2, below) is the local Wall-E subscriber's channel.
48
+ if (stableKeys.length && _tableExists(db, 'conversation_item_clock')) {
49
+ require('./conversation-item-clock').stampConversationTombstonesV2(db, { sessionId, stableKeys, nowMs });
50
+ }
44
51
  // WP-0 (one-transcript-ingestor §6.2, D-6b): the deletes below remove the purged
45
52
  // keys' outbox rows, so the change feed must FIRST learn exactly which stable
46
53
  // keys are going away — same transaction, before the deletes, best-effort when
@@ -99,4 +106,48 @@ function purgeConversationV2(db, sessionId, sourceIds, eventKeys, nowMs = Date.n
99
106
  return deleted;
100
107
  }
101
108
 
102
- module.exports = { purgeConversationV2 };
109
+ // WP-5 §6.8 (shared-memory-every-machine-2026-10-31.html §5 D-3): the replica RECEIVER's delete —
110
+ // the same work purgeConversationV2 does per key, but scoped to an explicit stable-key list the sync
111
+ // feed handed over (a peer's tombstone). One transaction, and the caller (`db.transaction` around it)
112
+ // may already be inside one, so this uses `.immediate()` like the purge path. It lives here rather
113
+ // than in lib/conversation-item-clock.js because it IS a writer of the conversation item tables, and
114
+ // this module is already the pinned writer (tests/conversation-store-v2.test.js's writer gate).
115
+ function deleteConversationItemsByStableKeyV2(db, { sessionId, stableKeys = [], nowMs = Date.now() } = {}) {
116
+ const requested = [...new Set((Array.isArray(stableKeys) ? stableKeys : []).map((k) => String(k)).filter(Boolean))];
117
+ if (!requested.length) return 0;
118
+ return db.transaction(() => {
119
+ // Only keys this store actually still holds count: a re-offered tombstone (or one for an item
120
+ // this node never had) must leave NO second projection-delete row — the subscriber's delete feed
121
+ // is a log, and replaying a delete into it would grow it without bound.
122
+ const marks = _marks(requested);
123
+ const present = new Set(db.prepare(`SELECT stable_key FROM conversation_text_items_v2
124
+ WHERE session_id=? AND stable_key IN (${marks})`).all(sessionId, ...requested).map((r) => r.stable_key));
125
+ if (_tableExists(db, 'conversation_item_placement_v2')) {
126
+ for (const row of db.prepare(`SELECT stable_key FROM conversation_item_placement_v2
127
+ WHERE session_id=? AND stable_key IN (${marks})`).all(sessionId, ...requested)) {
128
+ present.add(row.stable_key);
129
+ }
130
+ }
131
+ const keys = requested.filter((key) => present.has(key));
132
+ if (!keys.length) return 0;
133
+ const mark = _marks(keys);
134
+ let deleted = 0;
135
+ if (_tableExists(db, 'conversation_projection_deletes_v2')) {
136
+ recordProjectionDeletes(db, { sessionId, stableKeys: keys, nowMs });
137
+ }
138
+ if (_tableExists(db, 'conversation_projection_outbox_v2')) {
139
+ deleted += Number(db.prepare(`DELETE FROM conversation_projection_outbox_v2
140
+ WHERE session_id=? AND stable_key IN (${mark})`).run(sessionId, ...keys).changes || 0);
141
+ }
142
+ if (_tableExists(db, 'conversation_item_placement_v2')) {
143
+ deleted += Number(db.prepare(`DELETE FROM conversation_item_placement_v2
144
+ WHERE session_id=? AND stable_key IN (${mark})`).run(sessionId, ...keys).changes || 0);
145
+ }
146
+ deleted += Number(db.prepare(`DELETE FROM conversation_text_items_v2
147
+ WHERE session_id=? AND stable_key IN (${mark})`).run(sessionId, ...keys).changes || 0);
148
+ rebuildTurnIndexForSessions(db, [sessionId], { nowMs });
149
+ return deleted;
150
+ }).immediate();
151
+ }
152
+
153
+ module.exports = { purgeConversationV2, deleteConversationItemsByStableKeyV2 };
@@ -134,6 +134,10 @@ class ProviderLoginBroker {
134
134
  // WP-1: the login tab is a real CTM terminal session, created through the
135
135
  // server's own prepared-create entry point (server.js wires both in).
136
136
  this.createSession = options.createSession || null;
137
+ // WP-1 (D-2): has the session restore finished? Until it has, a persisted open flow whose
138
+ // session is not in the registry may simply not be restored yet, and settling it then would
139
+ // kill a live sign-in. server.js wires `() => _restoreCompletedAt > 0`.
140
+ this.sessionsRestored = options.sessionsRestored || (() => true);
137
141
  this.getSession = options.getSession || ((sessionId) => {
138
142
  try {
139
143
  return require('../server-state').getSession(sessionId) || null;
@@ -316,9 +320,13 @@ class ProviderLoginBroker {
316
320
  // stat is information only — on macOS Claude keeps its credential in the Keychain and
317
321
  // Codex may keep it in the keyring, so a present/absent file proves nothing.
318
322
  async getState() {
323
+ const accounts = await this._probeAccounts();
324
+ // WP-1 (D-1): a restart ADOPTS an open flow's tab, but the exit watcher and the capture
325
+ // lived only in memory — reconcile them before answering, so THIS read reflects a settle
326
+ // the reconcile just made (the card's 2 s poll is the only reader a stuck flow has).
327
+ this._reconcileOpenFlows(accounts);
319
328
  const state = this._readState();
320
329
  const browserUrls = this._latestBrowserUrls();
321
- const accounts = await this._probeAccounts();
322
330
  const providers = {};
323
331
  for (const id of Object.keys(DEFAULT_PROVIDERS)) {
324
332
  const provider = this._provider(id);
@@ -369,6 +377,68 @@ class ProviderLoginBroker {
369
377
  }
370
378
  }
371
379
 
380
+ // WP-1 (D-1): the ONE state transition that ends a flow — the exit watcher and the
381
+ // login-state reconcile both come here. `status` overrides the exit-code derivation for a
382
+ // tab the restore can no longer find (its account probe decides). Pure with respect to the
383
+ // in-memory maps: the caller owns the latch and the watcher/capture disposal.
384
+ _settleFlow(provider, sessionId, { exitCode = null, signal = '', error = '', status = null } = {}) {
385
+ const next = this._readState();
386
+ const current = next[provider.id]?.flow;
387
+ // Superseded (a newer flow for this provider) or already final: nothing to say.
388
+ if (!current || current.sessionId !== sessionId) return;
389
+ if (current.status === 'completed' || current.status === 'failed') return;
390
+ const code = typeof exitCode === 'number' ? exitCode : null;
391
+ current.status = status || (code === 0 ? 'completed' : 'failed');
392
+ current.exitCode = code;
393
+ current.signal = signal || '';
394
+ // An empty error keeps whatever the flow already carried (the exit path says nothing new).
395
+ if (error) current.error = error;
396
+ current.exitedAt = _nowIso();
397
+ current.updatedAt = current.exitedAt;
398
+ next[provider.id] = { flow: current, status: current.status };
399
+ this._writeState(next);
400
+ this._invalidate(provider.agentId);
401
+ }
402
+
403
+ // WP-1 (D-1..D-3): the persisted open flows are reconciled against the live session
404
+ // registry on every login-state read, because a restart ADOPTS a login tab (its PTY host
405
+ // survives) while the watcher/capture that end the flow were attached only inside start().
406
+ // Idempotent: a flow already watched and captured attaches nothing more.
407
+ _reconcileOpenFlows(accounts) {
408
+ const state = this._readState();
409
+ for (const id of Object.keys(DEFAULT_PROVIDERS)) {
410
+ const saved = state[id];
411
+ const flow = saved && saved.flow;
412
+ if (!flow || !OPEN_FLOW_STATUSES.includes(flow.status) || !flow.sessionId) continue;
413
+ const provider = this._provider(id);
414
+ const sessionId = flow.sessionId;
415
+ const session = this.getSession ? this.getSession(sessionId) : null;
416
+ if (loginTabAlive(session)) {
417
+ if (!this.subscriptions.has(sessionId)) this._watchLoginTab(provider, session, sessionId);
418
+ if (!this.captures.has(sessionId)) this._attachCapture(provider, session, flow);
419
+ continue;
420
+ }
421
+ // The tab is gone: its own exit stamp (when the host still holds it) is the truth.
422
+ if (loginTabExited(session)) {
423
+ this._settleFlow(provider, sessionId, {
424
+ exitCode: loginTabExitCode(session),
425
+ signal: (session && session._exitSignal) || '',
426
+ });
427
+ continue;
428
+ }
429
+ // No session at all: only a FINISHED restore may read that as "the tab is gone" —
430
+ // before it, the session simply may not be restored yet and the flow must stay open.
431
+ if (!session && this.sessionsRestored()) {
432
+ const account = provider.agentId ? (accounts || {})[provider.agentId] : null;
433
+ this._settleFlow(provider, sessionId, {
434
+ exitCode: null,
435
+ error: 'login_tab_gone',
436
+ status: account && account.signedIn === true ? 'completed' : 'failed',
437
+ });
438
+ }
439
+ }
440
+ }
441
+
372
442
  // Records the exit outcome once the tab is gone, and drops the cached probe so
373
443
  // the next login-state read re-runs `claude auth status` / `codex login status`.
374
444
  _watchLoginTab(provider, session, sessionId) {
@@ -382,20 +452,7 @@ class ProviderLoginBroker {
382
452
  if (typeof capture.dispose === 'function') capture.dispose();
383
453
  this.captures.delete(sessionId);
384
454
  }
385
- const next = this._readState();
386
- const current = next[provider.id]?.flow;
387
- // Superseded (a newer flow for this provider) or already final: nothing to say.
388
- if (!current || current.sessionId !== sessionId) return;
389
- if (current.status === 'completed' || current.status === 'failed') return;
390
- const code = typeof exitCode === 'number' ? exitCode : null;
391
- current.status = code === 0 ? 'completed' : 'failed';
392
- current.exitCode = code;
393
- current.signal = signal || '';
394
- current.exitedAt = _nowIso();
395
- current.updatedAt = current.exitedAt;
396
- next[provider.id] = { flow: current, status: current.status };
397
- this._writeState(next);
398
- this._invalidate(provider.agentId);
455
+ this._settleFlow(provider, sessionId, { exitCode, signal });
399
456
  };
400
457
  const stamp = () => settle({ exitCode: loginTabExitCode(session), signal: (session && session._exitSignal) || '' });
401
458
  // A tab that died between createSession() and this line would never reach a fresh
@@ -23,6 +23,7 @@
23
23
  // A login shell that `exec`s the resolved CLI keeps the agent type `shell` and
24
24
  // hands the CLI the login argv byte-exact.
25
25
 
26
+ const os = require('os');
26
27
  const path = require('path');
27
28
 
28
29
  const BROWSER_SHIM_PATH = path.resolve(__dirname, '..', '..', 'bin', 'open-url-headless.sh');
@@ -74,9 +75,16 @@ function loginTabArgs(spec, cliPath) {
74
75
  return ['-l', '-c', loginTabExecLine(spec, cliPath)];
75
76
  }
76
77
 
77
- function loginTabShell(env = process.env) {
78
+ // SHELL is often unset for a service-launched CTM (and is in the hermetic test env); then the
79
+ // user's login shell comes from the passwd record, never a hard-coded /bin/zsh -- which does not
80
+ // exist on a default Ubuntu host, where the tab died with exit 1 and no output (design
81
+ // nightly-linux-host-portability-2026-10-27 U-7). /bin/zsh stays the last resort (macOS default).
82
+ function loginTabShell(env = process.env, { userShell = () => os.userInfo().shell } = {}) {
78
83
  const shell = String((env && env.SHELL) || '').trim();
79
- return shell || '/bin/zsh';
84
+ if (shell) return shell;
85
+ let account = '';
86
+ try { account = String(userShell() || '').trim(); } catch { /* expected: no passwd entry (some containers) -- fall through */ }
87
+ return account || '/bin/zsh';
80
88
  }
81
89
 
82
90
  function loginTabEnv({ env = process.env, providerId, urlSink }) {