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,623 @@
1
+ 'use strict';
2
+
3
+ // Landing-throughput WP-5 shard S6 (design doc §5 D-10 "move-only", §6 "Shard
4
+ // module seam", §8 WP-6, §9 S-21..S-27): the HTTP attach handler
5
+ // (apiAttachSession, POST /api/sessions/attach) moves out of server.js VERBATIM
6
+ // into this module behind the same configure(deps) seam as
7
+ // lib/api/session-messages.js (S3). It registers NO routes -- the route stays in
8
+ // lib/api/sessions.js, whose configure() call in server.js now receives
9
+ // sessionAttachApi.apiAttachSession. The body is identical to server.js's
10
+ // declaration except for the `_deps.` prefix on every identifier that used to resolve
11
+ // to a server.js top-level binding (D-10 rule 1), proven by
12
+ // `node bin/move-only-check.js`. Four inline `require('./lib/...')` calls are rebased
13
+ // to this module's own `require('../...')` (S1/S3 precedent: injecting server.js's
14
+ // `require` is a loader-value transfer the public runtime closure guard refuses) -- the
15
+ // one deviation from a pure move-only diff.
16
+ //
17
+ // Injected deps, per function:
18
+ // apiAttachSession: CTM_FLUSH_BYTE_CAP, OUTPUT_FLUSH_BULK_INPUT_MAX_BYTES,
19
+ // OUTPUT_FLUSH_MAX_BYTES, PORT, PROJECT_ENTRY_RE, WALLE_PORT, WorkerTurnCounter,
20
+ // _answerPtyDeviceQueries, _appendScrollbackOnDbOwner, _clearRestoreInterruption,
21
+ // _clearScrollbackOnDbOwner, _codingSessionGenerationRuntime, _destroySession,
22
+ // _ensureSessionOutputGapWatchdog, _executeGeometryRevokeGraceForSession,
23
+ // _executionLocalityEnv, _forceFlowResume, _hasBufferedBusyStatusPtyEvidence,
24
+ // _isActivePtyChunk, _isBusyStatusPtyChunk, _isPtyUserInputEchoActivitySuppressed,
25
+ // _isServerWaitingForInput, _isShuttingDown, _isStatusOnlyPtyChunk, _mainTranscriptReads,
26
+ // _markHeartbeatDirty, _markProviderBusyStatusEvidence, _markServerSessionResumedFromPty,
27
+ // _markServerSessionTurnClosed, _markServerSessionTurnEvidence, _noteTerminalBell,
28
+ // _parseSessionMetaBatchOffThread, _perfWrap, _prependWalleBinToSessionPath,
29
+ // _providerIdFromCmd, _recordPtyChunkStatus, _retireStartupTaskOnDbOwner,
30
+ // _samplePtyDetectionData, _scheduleSecondaryPush, _sessionHostOwnsLiveHeadless,
31
+ // _spawnSessionPty, _stampSpawnAgentBundle, _stripPtyOutputReplies,
32
+ // _suppressCtmUiRefreshActivity, _useSessionHostProcessForPty, _wheelRttSampler, _wsSend,
33
+ // agentSessionKernel, applyAgentBundleToSpawn, applyClaudeRestoreBakDecision,
34
+ // applyLaunchEffortOverride, applyLaunchModelPin, broadcastSessionList,
35
+ // buildClaudeRendererLaunchEnv, buildTelemetryEnv, checkAutoApproval, checkIdleNotify,
36
+ // cleanAutoApprovalBuffer, cleanIdleNotify, createBoundedFlushScheduler,
37
+ // createSessionWithStartupTaskWithRetry, crypto, dbModule, delegateWorker,
38
+ // ensureWalleMcpForAgentSession, formatWorkerTerminalOutput, handleCreatePrepared,
39
+ // headlessWorker, oauthProxySupervisor, path, planCappedOutputAppend,
40
+ // ptyRuntimeActivityDecision, queueEngine, recordSessionDiagnostic, resolveAgentCliCommand,
41
+ // resolveSpawnBundleForSession, restoreRuntime, sanitizeAnthropicAuth,
42
+ // sanitizeInheritedAgentEnv, serverConfig, sessionDiagnostics, sessionEvents,
43
+ // sessionInputAuthority, sessionPolicy, sessions, setupQueueForSession,
44
+ // splitMirrorAndBrowserChunks, terminalOutputFlushDelay, treeHex
45
+ //
46
+ // Mutable module state is injected as a GETTER (D-10 rule 2): `_isShuttingDown` is a
47
+ // server.js `let` binding reassigned at runtime, so server.js injects
48
+ // `get _isShuttingDown() { return _isShuttingDown; }` rather than a frozen copy.
49
+ // No moved body assigns server.js state.
50
+
51
+ let _deps = null;
52
+
53
+ function configure(deps = {}) {
54
+ _deps = deps;
55
+ }
56
+
57
+ function apiAttachSession(req, res) {
58
+ let body = '';
59
+ req.on('data', chunk => { body += chunk; if (body.length > 1e6) req.destroy(); });
60
+ req.on('end', async () => {
61
+ try {
62
+ const { session_id, cwd: rawCwd } = JSON.parse(body);
63
+ if (!session_id) {
64
+ res.writeHead(400, { 'Content-Type': 'application/json' });
65
+ res.end(JSON.stringify({ error: 'session_id required' }));
66
+ return;
67
+ }
68
+
69
+ if (typeof session_id !== 'string' || !_deps.PROJECT_ENTRY_RE.test(session_id) || session_id === '.' || session_id === '..') {
70
+ res.writeHead(400, { 'Content-Type': 'application/json' });
71
+ res.end(JSON.stringify({ error: 'Invalid session_id format' }));
72
+ return;
73
+ }
74
+
75
+ const tabId = _deps.crypto.randomUUID();
76
+ const cwdDir = (rawCwd || _deps.serverConfig.HOME).replace(/^~/, _deps.serverConfig.HOME);
77
+ const env = { ...process.env, PWD: cwdDir };
78
+ _deps._prependWalleBinToSessionPath(env);
79
+ _deps.sanitizeInheritedAgentEnv(env);
80
+ const claudeCmd = _deps.resolveAgentCliCommand('claude');
81
+ _deps.sanitizeAnthropicAuth(env, claudeCmd);
82
+ Object.assign(env, _deps._executionLocalityEnv());
83
+ // Resume path: inject CTM telemetry env the same way as the create path.
84
+ Object.assign(env, _deps.buildTelemetryEnv(tabId, claudeCmd, _deps.PORT, env));
85
+ Object.assign(env, _deps.buildClaudeRendererLaunchEnv(claudeCmd, env)); // restore-seed-alt-screen-trap R-4
86
+ _deps.ensureWalleMcpForAgentSession({
87
+ cmd: claudeCmd,
88
+ wallePort: _deps.WALLE_PORT,
89
+ homeDir: env.HOME || _deps.serverConfig.HOME,
90
+ env,
91
+ });
92
+
93
+ // Restore .jsonl from .bak if needed (Claude Code migrates sessions to
94
+ // directory format, and an interrupted compaction can leave a live
95
+ // metadata-only stub while the conversation survives in .bak).
96
+ const claudeProjectsDir = _deps.path.join(_deps.serverConfig.HOME, '.claude', 'projects');
97
+ const unavailable = () => Object.assign(new Error('Session preparation is unavailable; retry shortly'), { code: 'READ_POOL_UNAVAILABLE', statusCode: 503 });
98
+ const entries = await _deps._mainTranscriptReads.listClaudeProjectDirectoryNames();
99
+ const candidates = entries.map((entry) => {
100
+ const livePath = _deps.path.join(claudeProjectsDir, entry, `${session_id}.jsonl`);
101
+ return { livePath, bakPath: `${livePath}.bak` };
102
+ });
103
+ const decisions = await _deps._mainTranscriptReads.prepareRestoreBackups(candidates, { requireAvailable: true });
104
+ const stats = await _deps._mainTranscriptReads.statPaths(candidates.flatMap((candidate) => [candidate.livePath, candidate.bakPath]), { requireAvailable: true });
105
+ if (!decisions || !stats || candidates.some((candidate) => [candidate.livePath, candidate.bakPath]
106
+ .some((p) => !stats.get(p) || stats.get(p).state === 'error'))) throw unavailable();
107
+ let chosen = null;
108
+ for (let i = 0; i < decisions.length; i++) {
109
+ const decision = decisions[i];
110
+ if (decision.reason === 'unavailable' || decision.live?.state === 'unavailable' || decision.bak?.state === 'unavailable') throw unavailable();
111
+ if (decision.eligible) {
112
+ const applied = await _deps.applyClaudeRestoreBakDecision(decision, { db: _deps.dbModule.getDb() });
113
+ // A failed apply can have renamed the stub already. Never reuse pre-apply exists=true.
114
+ if (!applied.restored) {
115
+ console.error('[attach] backup apply failed:', {
116
+ reason: applied.reason, errorCode: applied.errorCode || '', stubPath: applied.stubPath || '',
117
+ });
118
+ throw unavailable();
119
+ }
120
+ chosen = { filePath: decision.livePath, projectEntry: entries[i] };
121
+ } else if (stats.get(decision.livePath)?.exists) {
122
+ chosen = { filePath: decision.livePath, projectEntry: entries[i] };
123
+ }
124
+ if (chosen) break;
125
+ }
126
+
127
+ // Guard: `claude --resume <id>` exits 1 if the JSONL doesn't exist, which
128
+ // triggers an auto-restart loop with the same missing ID. Surface a clear 404
129
+ // instead of letting Claude Code crash. The identity row is KEPT (design D-1):
130
+ // vendor retention deletes transcripts, the conversation may still be stored.
131
+ if (!chosen) {
132
+ res.writeHead(404, { 'Content-Type': 'application/json' });
133
+ res.end(JSON.stringify({ error: 'Session JSONL not found on disk', session_id, identityKept: true }));
134
+ return;
135
+ }
136
+
137
+ // Prepare the existing DB/worker title before spawn: the new metadata await must not delay
138
+ // registration of the PTY's first output handler. Only the already-chosen path is parsed.
139
+ let label = '';
140
+ try {
141
+ const titleInfo = _deps.dbModule.getSessionTitleNew(session_id);
142
+ if (titleInfo && titleInfo.title) label = titleInfo.title;
143
+ } catch (e) { console.error('[attach] getSessionTitleNew error:', e.message); }
144
+ if (!label) {
145
+ try {
146
+ const results = await _deps._parseSessionMetaBatchOffThread([chosen], { metadataProfile: 'display' });
147
+ label = results?.find((row) => row?.filePath === chosen.filePath)?.parsed?.title || '';
148
+ } catch (e) { console.error('[attach] label preparation error:', e.message); }
149
+ }
150
+ if (!label) label = 'Attached: ' + session_id.slice(0, 8) + '...';
151
+
152
+ let ptyProcess;
153
+ const terminalOutputEpoch = _deps.crypto.randomUUID();
154
+ // WP-0b (design §9 S2, AC-0g): reserve BEFORE the PTY exists — a refused reservation leaves zero processes.
155
+ const attachLaunch = _deps._codingSessionGenerationRuntime.revalidateReservation(await _deps._codingSessionGenerationRuntime.prepareAttachedLaunch({ sessionId: tabId, providerThreadId: session_id, env }));
156
+ if (!attachLaunch.ok) { res.writeHead(503, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Launch generation unavailable', session_id })); return; }
157
+ // WP-3 (docs/claude-resume-managed-model-pin-2026-10-16.html §6 D-7, §9 S-7): the attach path
158
+ // spawns outside createProviderSession, so the session's stored Claude lead-model pin is replayed
159
+ // here on THIS spawn's argv only. An adopted external session has no CTM row yet, so the read
160
+ // returns null and the argv is today's (design R-6). The `session.args` below and the startup
161
+ // task stay clean -- the pin is never stored.
162
+ // WP-2 (docs/claude-lead-model-pin-followups-2026-10-17.html §5.1 D-6b): the same spawn also
163
+ // replays the session's stored lead effort (`session_lead_effort`), because a plain `claude
164
+ // --resume` keeps neither the chosen model nor the chosen effort. The effort read has its OWN
165
+ // failure mode: an unreadable effort records lead-effort-read-failed and the spawn keeps the
166
+ // pinned argv. Neither value is persisted -- both reach this spawn's argv only.
167
+ // WP-4 (docs/agent-bundle-darwin-readonly-rename-2026-10-27.html §16 D-11/D-12, §17): the attach
168
+ // path spawns outside createProviderSession, so it applied no bundle and stamped no tree — an
169
+ // attached session silently ran without the hub's skills (census gap D-12). The SAME resolver,
170
+ // transform and diagnostic the create path uses (provider-process.js:687/:940), applied to THIS
171
+ // spawn's argv/env only: `session.args` below and the startup task stay clean, so a later restore
172
+ // re-resolves the hub's CURRENT pointer (D-7). No hub tree → null → today's argv/env byte for byte.
173
+ const attachBundle = await _deps.resolveSpawnBundleForSession(tabId, { report: (event, details) => _deps.recordSessionDiagnostic?.(tabId, event, details) });
174
+ let attachSpawnEnv = attachLaunch.env;
175
+ const attachSpawnArgs = (() => {
176
+ let args = ['--resume', session_id];
177
+ try {
178
+ if (typeof _deps.dbModule.getSessionLeadModel === 'function') {
179
+ args = _deps.applyLaunchModelPin({ agentType: 'claude', args: ['--resume', session_id],
180
+ pin: _deps.dbModule.getSessionLeadModel(tabId), env: attachLaunch.env }).args;
181
+ }
182
+ } catch (error) {
183
+ if (typeof _deps.recordSessionDiagnostic === 'function') _deps.recordSessionDiagnostic(tabId, 'lead-model-pin-read-failed', { message: String(error && error.message || error).slice(0, 200) });
184
+ /* REPORT: an unreadable pin never blocks an attach; the spawn keeps today's argv */
185
+ }
186
+ try {
187
+ const effort = _deps.dbModule.getSessionLeadEffort?.(tabId)?.effort;
188
+ if (typeof effort === 'string') args = _deps.applyLaunchEffortOverride(args, 'claude', effort).args;
189
+ } catch (error) {
190
+ if (typeof _deps.recordSessionDiagnostic === 'function') _deps.recordSessionDiagnostic(tabId, 'lead-effort-read-failed', { message: String(error && error.message || error).slice(0, 200) });
191
+ /* REPORT: an unreadable effort never blocks an attach; the spawn keeps the pinned argv */
192
+ }
193
+ // WP-4: pinned argv + the bundle's --add-dir is the ONE argv this spawn receives (last, after pin and effort).
194
+ const bundled = _deps.applyAgentBundleToSpawn({ launchArgs: args, spawnEnv: attachLaunch.env, agentType: 'claude', bundle: attachBundle }); attachSpawnEnv = bundled.spawnEnv; args = bundled.launchArgs;
195
+ if (bundled.applied) _deps.recordSessionDiagnostic?.(tabId, 'agent-bundle-applied', { treeHash: attachBundle.treeHash, hubPeerId: attachBundle.hubPeerId });
196
+ return args;
197
+ })();
198
+ try {
199
+ ptyProcess = _deps._spawnSessionPty({
200
+ sessionId: tabId,
201
+ cmd: claudeCmd,
202
+ args: attachSpawnArgs,
203
+ cols: 120,
204
+ rows: 30,
205
+ cwd: cwdDir,
206
+ env: attachSpawnEnv,
207
+ outputEpoch: terminalOutputEpoch,
208
+ });
209
+ } catch (e) {
210
+ res.writeHead(500, { 'Content-Type': 'application/json' });
211
+ res.end(JSON.stringify({ error: 'Failed to spawn: ' + e.message }));
212
+ return;
213
+ }
214
+
215
+ const attachedSessionSpawnedAt = Date.now();
216
+ const session = {
217
+ id: tabId,
218
+ ...attachLaunch.owner,
219
+ label,
220
+ cmd: 'claude',
221
+ args: ['--resume', session_id],
222
+ cwd: cwdDir,
223
+ pid: ptyProcess.pid,
224
+ ptyProcess,
225
+ // WI-3 (resume-grid-mismatch-render-churn): see the primary spawn site's comment.
226
+ _ptySpawnedAt: attachedSessionSpawnedAt,
227
+ clients: [],
228
+ // scrollback removed — headless terminal is source of truth (Phase 3D)
229
+ createdAt: attachedSessionSpawnedAt,
230
+ lastActivity: attachedSessionSpawnedAt,
231
+ lastPtyActivity: 0,
232
+ _ptyCols: 120,
233
+ _ptyRows: 30,
234
+ model_id: null,
235
+ model_provider: null,
236
+ _autoRestartCount: 0,
237
+ _terminalOutputEpoch: terminalOutputEpoch,
238
+ };
239
+ // WP-3/D-11, applied here by WP-4: the SAME spawn stamp the create path applies, so an attached
240
+ // session's bundle truth reaches the drift reconciler and `_sessionPayload.agentBundle` (and the
241
+ // store GC's live-set) exactly like a created one's. `treeHex` of a null bundle is null.
242
+ _deps._stampSpawnAgentBundle({ session, agentBundleTreeHash: _deps.treeHex(attachBundle?.treeHash) });
243
+
244
+ _deps.sessions.set(tabId, session);
245
+ _deps.sessionInputAuthority.observeProvider({ sessionId: tabId, status: 'ready' });
246
+ if (typeof ptyProcess.onStarted === 'function') {
247
+ ptyProcess.onStarted((started = {}) => {
248
+ session.pid = started.pid || session.pid || ptyProcess.pid;
249
+ session._hostPid = ptyProcess.hostPid || null;
250
+ _deps.broadcastSessionList(true, { reason: 'attach-started' });
251
+ });
252
+ }
253
+
254
+ // Create headless terminal for legacy PTYs. Hosted PTYs create their
255
+ // embedded headless terminal inside the child process before the PTY
256
+ // starts, so the primary process must not send a second create that could
257
+ // reset early output.
258
+ if (!_deps._sessionHostOwnsLiveHeadless(tabId)) {
259
+ _deps.headlessWorker.postMessage({ type: 'create', sessionId: tabId, cols: 120, rows: 30, outputEpoch: terminalOutputEpoch });
260
+ }
261
+
262
+ // Durable restore identity for crash-safe restore: the startup_tasks row AND the
263
+ // ctm_sessions/agent_sessions rows in ONE transaction, through the same helper the ordinary
264
+ // create path uses (lib/session-create/provider-process.js:1394). The attach route used to
265
+ // write ONLY the startup task, so an API-only attached tab had no agent_sessions owner row for
266
+ // the provider thread it actually resumed and every owner-keyed durable store refused it on its
267
+ // own read (lib/storage/coding-session-model-store.js's bind -> ownerRow requires the exact
268
+ // ctm_session_id + agent_session_id + provider tuple) -- the attached session could never
269
+ // publish durable model state.
270
+ // The identity is the REAL provider thread this child was launched with (`--resume session_id`),
271
+ // never the CTM tab UUID: the tab id is only the ctm_session_id. Fire-and-forget on purpose --
272
+ // the promise is exposed on the live session exactly like the create path's
273
+ // `session._startupPersistence`, and nothing is awaited between the spawn above and the
274
+ // onData/onExit registration below, which would risk losing early PTY bytes.
275
+ session._startupPersistence = _deps.createSessionWithStartupTaskWithRetry({
276
+ sessionId: tabId,
277
+ label,
278
+ cmd: 'claude',
279
+ args: ['--resume', session_id],
280
+ cwd: cwdDir,
281
+ sessionData: {
282
+ agentSessionId: session_id,
283
+ provider: 'claude',
284
+ cwd: cwdDir,
285
+ title: label,
286
+ projectEntry: chosen.projectEntry,
287
+ projectPath: cwdDir,
288
+ },
289
+ });
290
+
291
+ // Output batching (same as main session path)
292
+ let outputBuf2 = '';
293
+ let outputBufBytes2 = 0;
294
+ let outputFlushTimer2 = null;
295
+ let headlessFeedBuf2 = '';
296
+ let headlessFeedTimer2 = null;
297
+ let _sb2Seq = 0, _sb2Pending = [], _sb2Timer = null;
298
+ function _flushSb2() { _sb2Timer = null; if (!_sb2Pending.length) return; const w = _sb2Pending; _sb2Pending = []; _deps._appendScrollbackOnDbOwner(tabId, w); }
299
+ session._flushScrollbackToDb = _flushSb2;
300
+ function flushHeadlessFeed2() {
301
+ if (headlessFeedTimer2) {
302
+ clearTimeout(headlessFeedTimer2);
303
+ headlessFeedTimer2 = null;
304
+ }
305
+ if (!headlessFeedBuf2) return;
306
+ const batch = headlessFeedBuf2;
307
+ headlessFeedBuf2 = '';
308
+ if (!_deps.sessions.has(tabId) || _deps.sessions.get(tabId) !== session) return;
309
+ if (_deps._sessionHostOwnsLiveHeadless(tabId)) return;
310
+ _deps.headlessWorker.postMessage({ type: 'feed', sessionId: tabId, data: batch });
311
+ }
312
+ function enqueueHeadlessFeed2(data) {
313
+ if (!data) return;
314
+ headlessFeedBuf2 += data;
315
+ if (!headlessFeedTimer2) headlessFeedTimer2 = setTimeout(flushHeadlessFeed2, 16);
316
+ }
317
+ session._flushHeadlessFeed = flushHeadlessFeed2;
318
+ function flushOutput2() {
319
+ if (_deps.CTM_FLUSH_BYTE_CAP && outputFlushTimer2) clearTimeout(outputFlushTimer2);
320
+ outputFlushTimer2 = null;
321
+ if (!outputBuf2) return;
322
+ const batch = outputBuf2;
323
+ outputBuf2 = '';
324
+ outputBufBytes2 = 0;
325
+ _sb2Pending.push({ seq: _sb2Seq++, data: batch }); _deps.sessionEvents.emit('session:tail-dirty', { id: tabId });
326
+ if (!_sb2Timer) _sb2Timer = setTimeout(_flushSb2, 1000);
327
+ // State-sync delivers these bytes via the next 'frame' (the worker mirror, fed via
328
+ // enqueueHeadlessFeed2 / the session host, already holds them). Scrollback is persisted above;
329
+ // secondary viewers get a reflowed snapshot below.
330
+ // Render secondary viewers at their own width from a debounced reflowed
331
+ // snapshot (no-op + free when there are no secondaries).
332
+ if (_deps.serverConfig.CTM_DEBUG_SECPUSH) console.warn(`[flushout] ${tabId.slice(0,6)} hasSecondary=${!!session._hasSecondaryViewers} clients=${session.clients.length}`);
333
+ if (session._hasSecondaryViewers) _deps._scheduleSecondaryPush(session, tabId, 'output');
334
+ }
335
+ function flushForcedOutput2(batch) {
336
+ if (!batch) return;
337
+ const pending = outputBuf2;
338
+ const pendingBytes = outputBufBytes2;
339
+ const pendingTimer = outputFlushTimer2;
340
+ outputBuf2 = batch;
341
+ outputBufBytes2 = Buffer.byteLength(batch, 'utf8');
342
+ outputFlushTimer2 = null;
343
+ try {
344
+ flushOutput2();
345
+ } finally {
346
+ outputBuf2 = pending;
347
+ outputBufBytes2 = pendingBytes;
348
+ outputFlushTimer2 = pendingTimer;
349
+ }
350
+ }
351
+ const cappedFlushScheduler2 = _deps.createBoundedFlushScheduler({
352
+ flush: flushForcedOutput2,
353
+ onIdle: () => {
354
+ if (outputBuf2 && !outputFlushTimer2) {
355
+ outputFlushTimer2 = setTimeout(
356
+ flushOutput2,
357
+ _deps.terminalOutputFlushDelay(outputBuf2, session),
358
+ );
359
+ }
360
+ },
361
+ });
362
+
363
+ const attachedPtyDataHandler = _deps._perfWrap(`pty:onData:${tabId.slice(0, 8)}`, (data) => {
364
+ _deps._wheelRttSampler.onOutput(tabId);
365
+ const now = Date.now();
366
+ // RAW output recency for the mid-stream resize guard (_ptyOutputInFlight) —
367
+ // unfiltered on purpose; see the matching stamp in the queued onData path.
368
+ session._lastRawPtyOutputAt = now;
369
+ _deps._ensureSessionOutputGapWatchdog(session, tabId)?.notePtyOutput(
370
+ now,
371
+ Buffer.byteLength(String(data || ''), 'utf8'),
372
+ );
373
+ _deps._markHeartbeatDirty(session);
374
+ _deps._answerPtyDeviceQueries(session, tabId, data, 'feed-attached');
375
+ const rawTerminalData = _deps._stripPtyOutputReplies(session, tabId, data, 'feed-attached');
376
+ // Count raw worker turns before projection, then schedule progress checkpoints
377
+ // (spark-worker-latency-prompt-diet §8 WP-3).
378
+ if (session.agentRole === 'worker') {
379
+ session._workerTurnCounter ||= new _deps.WorkerTurnCounter({ agent: session._workerAgent });
380
+ if (session._workerTurnCounter.feed(rawTerminalData)) session._workerTurnCount = session._workerTurnCounter.count;
381
+ _deps.delegateWorker.scheduleWorkerObservation(session.id, session);
382
+ }
383
+ const terminalData = _deps.formatWorkerTerminalOutput(session, rawTerminalData);
384
+ // A BEL that terminates a stripped color reply is not an approval signal.
385
+ _deps._noteTerminalBell(tabId, session, terminalData);
386
+ const detectionData = _deps._samplePtyDetectionData(terminalData);
387
+ const activeChunk = _deps._isActivePtyChunk(session, detectionData);
388
+ const statusOnlyChunk = _deps._isStatusOnlyPtyChunk(session, detectionData);
389
+ const busyStatusChunk = _deps._isBusyStatusPtyChunk(session, detectionData);
390
+ const busyStatusEvidenceChunk = busyStatusChunk || _deps._hasBufferedBusyStatusPtyEvidence(session, detectionData, now);
391
+ const waitingForInput = _deps._isServerWaitingForInput(tabId, session);
392
+ const suppressedUiRefreshChunk = _deps._suppressCtmUiRefreshActivity(session, statusOnlyChunk);
393
+ const restoreActivitySuppressed = _deps.restoreRuntime.restoreActivitySuppressed(session, now);
394
+ const suppressedInputEchoChunk = _deps._isPtyUserInputEchoActivitySuppressed(session, detectionData, {
395
+ statusOnlyChunk,
396
+ busyStatusChunk,
397
+ });
398
+ if (busyStatusEvidenceChunk && !restoreActivitySuppressed && !suppressedInputEchoChunk) {
399
+ _deps._markProviderBusyStatusEvidence(
400
+ tabId,
401
+ session,
402
+ now,
403
+ suppressedUiRefreshChunk ? 'attached-pty-ui-refresh' : 'attached-pty'
404
+ );
405
+ }
406
+ const { activityChunk, runtimeActivityChunk } = _deps.ptyRuntimeActivityDecision({
407
+ activeChunk,
408
+ statusOnlyChunk,
409
+ busyStatusChunk,
410
+ waitingForInput,
411
+ suppressedUiRefreshChunk,
412
+ suppressedInputEchoChunk,
413
+ restoreActivitySuppressed,
414
+ });
415
+ if (busyStatusChunk && waitingForInput && !restoreActivitySuppressed && !suppressedInputEchoChunk) {
416
+ const providerId = session?._providerId || _deps._providerIdFromCmd(session?.cmd || '') || 'provider';
417
+ _deps._markServerSessionResumedFromPty(tabId, session, `${providerId}-busy-status`);
418
+ }
419
+ _deps._recordPtyChunkStatus(session, detectionData, {
420
+ now, waitingForInput, suppressedUiRefreshChunk, suppressedInputEchoChunk, restoreActivitySuppressed,
421
+ });
422
+ if (runtimeActivityChunk) {
423
+ session.lastActivity = now;
424
+ session.lastPtyActivity = now;
425
+ if (session._activeTurnOpen) _deps._markServerSessionTurnEvidence(session, now, 'attached-pty-activity', { ledger: false });
426
+ _deps.restoreRuntime.clearRestoreState(session);
427
+ _deps._clearRestoreInterruption(session, now, 'attached-pty-activity');
428
+ }
429
+ // TWO feeds, one chunk -- the same shared split as the primary feed site in
430
+ // lib/session-create/response.js. `mirrorData` feeds the headless MIRROR, the authority for
431
+ // the app's mouse-tracking mode (R-2) and therefore the one copy that must keep the
432
+ // ?1000/?1002/?1003/?1006/?1015 enables; `cleanData` feeds the BROWSER and is byte-identical
433
+ // to the pre-change computation (R-10). See splitMirrorAndBrowserChunks for why the
434
+ // transform order matters.
435
+ const { mirror: mirrorData, browser: cleanData } = _deps.splitMirrorAndBrowserChunks(
436
+ terminalData,
437
+ session.cmd || '',
438
+ );
439
+ if (!_deps.CTM_FLUSH_BYTE_CAP) {
440
+ outputBuf2 += cleanData;
441
+ if (!outputFlushTimer2) {
442
+ outputFlushTimer2 = setTimeout(flushOutput2, _deps.terminalOutputFlushDelay(cleanData, session));
443
+ }
444
+ } else if (cleanData) {
445
+ const bulkInputActive = Number(session._codexComposerBulkInputUntil || 0) > now;
446
+ const byteCap = bulkInputActive
447
+ ? _deps.OUTPUT_FLUSH_BULK_INPUT_MAX_BYTES
448
+ : _deps.OUTPUT_FLUSH_MAX_BYTES;
449
+ const appendPlan = _deps.planCappedOutputAppend(outputBuf2, cleanData, byteCap, outputBufBytes2);
450
+ if (appendPlan.flushes.length) {
451
+ if (outputFlushTimer2) clearTimeout(outputFlushTimer2);
452
+ outputFlushTimer2 = null;
453
+ cappedFlushScheduler2.enqueue(appendPlan.flushes);
454
+ }
455
+ outputBuf2 = appendPlan.pending;
456
+ outputBufBytes2 = appendPlan.pendingBytes;
457
+ if (outputBuf2 && !cappedFlushScheduler2.pending && !outputFlushTimer2) {
458
+ outputFlushTimer2 = setTimeout(flushOutput2, _deps.terminalOutputFlushDelay(cleanData, session));
459
+ }
460
+ }
461
+
462
+ if (!_deps._sessionHostOwnsLiveHeadless(tabId)) enqueueHeadlessFeed2(mirrorData);
463
+ // Mark cache stale on output (matches the primary feed site).
464
+ if (cleanData && cleanData.length) session._cachedSnapshotDirty = true;
465
+
466
+ // Feed output to queue engine for completion detection
467
+ _deps.queueEngine.feedOutput(tabId, detectionData);
468
+
469
+ // Feed output to auto-approval engine
470
+ _deps.checkAutoApproval(tabId, session, detectionData);
471
+
472
+ // Feed output to idle/waiting-for-input detection
473
+ _deps.checkIdleNotify(tabId, session, detectionData);
474
+ });
475
+ ptyProcess.onData(attachedPtyDataHandler);
476
+
477
+ // WP-1 §6.6 item 2: the owner + cleanup FACTORY are captured BEFORE this handler is installed
478
+ // (see lib/session-create/native-lead-server-wiring.js); the handler passes its FULL event through it.
479
+ const exitLeadOwner = require('../session-create/native-lead-server-wiring').captureExitLeadOwner(session);
480
+ const leadRouteCleanup = require('../session-create/native-lead-server-wiring').createLeadRouteCleanupForSession({ session, sessionId: session.id, ownership: session._leadRouteTokenOwnership, hosted: () => _deps._useSessionHostProcessForPty() === true });
481
+ ptyProcess.onExit(async (exitEvent) => {
482
+ const { exitCode, signal } = exitEvent || {};
483
+ _deps.sessionInputAuthority.observeProvider({ sessionId: tabId, status: 'exited' });
484
+ if (session.agentRole === 'worker') attachedPtyDataHandler('\n');
485
+ if (outputFlushTimer2) { clearTimeout(outputFlushTimer2); flushOutput2(); }
486
+ if (_sb2Timer) { clearTimeout(_sb2Timer); _flushSb2(); }
487
+ _deps._forceFlowResume(session, tabId, 'pty-exit');
488
+ // WP-2 (D-1, S-16): don't let a grace-deferred geometry revoke outlive the PTY it was
489
+ // guarding ownership of.
490
+ _deps._executeGeometryRevokeGraceForSession(tabId);
491
+ // WP-2 (delegated-implementation-workers-2026-09-24.html §5 D-7, §9 S-13): revoke a
492
+ // worker session's proxy token on PTY exit -- best-effort, never blocks teardown.
493
+ if (session.agentRole === 'worker' && session._workerToken) {
494
+ _deps.oauthProxySupervisor.revokeWorkerToken(session._workerToken).catch(() => { /* expected: best-effort token revoke on PTY exit -- an unreachable/already-down proxy must never block session teardown */ });
495
+ } else if (session.agentRole !== 'worker') {
496
+ // §6.6 item 2/§6.11: the FULL event goes through the captured factory BEFORE the park/shutdown
497
+ // early returns below; a hosted synthetic/unknown event revokes the exact owner and keeps files.
498
+ await require('../session-create/native-lead-lifetime').runLeadRouteLifetimeCleanup(leadRouteCleanup, exitEvent, { reason: 'pty-exit' });
499
+ // WP-4 D-8: drop the exited lead from the pick table (inline require: may run before the module-level const).
500
+ require('../session-create/subagent-pick-table').forget(session.id, exitLeadOwner)
501
+ .catch(() => { /* expected: subagent-pick-table forget never rejects -- it reports its own failures */ });
502
+ }
503
+ // Lifecycle WP-4 (delegated-worker-lifecycle-identity-observability-2026-09-27.html
504
+ // §5 D-7, §9 S-20): release this worker's admission slot -- and, on a 429 ending,
505
+ // record the throttle timestamp D-7 keys admission decisions off -- on every worker
506
+ // exit, attached-path or create-path (response.js has the twin call), regardless of
507
+ // `_isShuttingDown` below (a shutdown-time exit must still free the slot for the next
508
+ // process to admit correctly).
509
+ _deps.delegateWorker.releaseAdmissionOnExit(session, exitCode);
510
+ // Lifecycle WP-5 (delegated-worker-lifecycle-identity-observability-2026-09-27.html
511
+ // §5 D-10, §9 S-16): stamp terminal_at/terminal_status/failure_json (once) and arm
512
+ // the D-10 retirement timer -- right after the existing token revoke + admission
513
+ // release above, on every worker exit (attached path; response.js's create-path
514
+ // exit handler has the twin call), regardless of `_isShuttingDown` below (a
515
+ // shutdown-time exit must still schedule the hide so a worker that exited just
516
+ // before a restart isn't retained forever by a lost in-memory timer -- the boot
517
+ // re-arm below picks it back up from the persisted hide_at either way).
518
+ // getWorkerStatus derives its status off session._exitCode, not this closure's own
519
+ // `exitCode` param, so that has to be stamped here too -- harmless: the SAME
520
+ // assignment happens again, unconditionally, a few lines further down (line ~39135)
521
+ // once this early-return window has passed.
522
+ session._exitCode = exitCode; session._exitSignal = signal ?? null;
523
+ _deps.delegateWorker.retireWorkerOnExit(session);
524
+ if (_deps._isShuttingDown) return;
525
+
526
+ // A delegated worker's exit is terminal, never an interactive --resume auto-restart: a worker
527
+ // continuation does launch `-p --resume` / `exec resume`, but relaunching it here would start an
528
+ // INTERACTIVE session under the worker's identity, stuck at worker-status `starting`
529
+ // (lib/session-class.js: relaunchFromRestore=false). Stamp session._exitCode unconditionally
530
+ // (every exit path benefits) so delegate-worker.js's getWorkerStatus can derive 'failed' from it.
531
+ const isWorkerSession = session.agentRole === 'worker';
532
+ session._exitCode = exitCode; session._exitSignal = signal ?? null;
533
+
534
+ // Crash recovery for attached sessions (always Claude --resume)
535
+ const isCrash = !session._intentionalKill && exitCode !== 0;
536
+ const maxAutoRestarts = 3;
537
+ const restartCount = session._autoRestartCount || 0;
538
+ const shouldAutoRestart = _deps.sessionPolicy(session).relaunchFromRestore && isCrash && restartCount < maxAutoRestarts;
539
+
540
+ const payload = JSON.stringify({ type: shouldAutoRestart ? 'restarting' : 'exit', id: tabId, exitCode, signal });
541
+ for (const client of session.clients) {
542
+ if (client.readyState === 1) _deps._wsSend(client, payload);
543
+ }
544
+
545
+ if (shouldAutoRestart) {
546
+ console.log(` Attached session ${tabId.slice(0, 8)} crashed (exit ${exitCode}) — auto-restarting (attempt ${restartCount + 1}/${maxAutoRestarts})...`);
547
+ const delay = Math.min(2000 * Math.pow(2, restartCount), 15000);
548
+ session._restartTimer = setTimeout(async () => {
549
+ // C-4: this teardown is immediately followed by a relaunch of the SAME
550
+ // tabId (handleCreatePrepared below) -- unlike the final-exit branch further
551
+ // down, the session-runtime aggregate must survive so the crashed session's
552
+ // attached viewers stay attached across the auto-restart instead of being
553
+ // dropped until they reconnect from scratch.
554
+ await _deps._destroySession(tabId, 'process-teardown', { retireRuntime: false });
555
+ _deps.queueEngine.onSessionExit(tabId);
556
+ _deps.cleanAutoApprovalBuffer(tabId);
557
+ _deps.cleanIdleNotify(tabId);
558
+ try {
559
+ // WP-2 (docs/claude-lead-model-pin-followups-2026-10-17.html §5.1 D-6a, §9 WP-2): this
560
+ // relaunch of an EXISTING session is a RESTORE, not a create -- the same shape the in-session
561
+ // auto-restart uses (lib/session-create/response.js:1180-1181). `_isRestore: true` makes
562
+ // provider-process replay the stored Claude lead-model pin AND the stored session lead effort
563
+ // on THIS spawn's argv (lib/session-create/provider-process.js:813, :826) and skips the
564
+ // startup_tasks re-insert whose created_at would otherwise be reset (provider-process.js:1373).
565
+ // Neither value is persisted: the ladder rebinds launchArgs only, never `args`.
566
+ await _deps.handleCreatePrepared(null, {
567
+ id: tabId,
568
+ title: session.title || session.label,
569
+ label: session.title || session.label,
570
+ cmd: 'claude',
571
+ args: ['--resume', session_id],
572
+ cwd: cwdDir,
573
+ _isRestore: true,
574
+ _ctmOriginalCreatedAt: session._ctmOriginalCreatedAt || session.createdAt,
575
+ _autoRestartCount: restartCount + 1,
576
+ });
577
+ console.log(` ✓ Restarted attached session ${tabId.slice(0, 8)}`);
578
+ } catch (e) {
579
+ console.log(` ✗ Restart failed: ${e.message}`);
580
+ _deps.broadcastSessionList(true, { reason: 'attach-restart-failed' });
581
+ }
582
+ }, delay);
583
+ return;
584
+ }
585
+
586
+ _deps._markServerSessionTurnClosed(session, Date.now(), 'attached-session-exit');
587
+ // A worker session keeps its in-memory row (deleteSession:false) so a caller
588
+ // polling worker-status right after this exit still finds session._exitCode --
589
+ // deleting it here would collapse getWorkerStatus's `!session` branch back to
590
+ // 'exited'/row-existence, which can't distinguish a genuine failure from a
591
+ // clean teardown. Cleanup of a stale worker session's memory footprint is the
592
+ // same tradeoff the crash-restart branch above already makes with
593
+ // retireRuntime:false; a worker session's lifetime is bounded (one trial run).
594
+ await _deps._destroySession(tabId, 'process-teardown', isWorkerSession ? { deleteSession: false } : undefined);
595
+ _deps.agentSessionKernel.forgetSession(tabId);
596
+ _deps.sessionDiagnostics.clear(tabId); // WP-3/S2: session fully retired -- ring must not outlive it
597
+ _deps._retireStartupTaskOnDbOwner(tabId, 'attached-session-exit').catch(() => {});
598
+ _deps._clearScrollbackOnDbOwner(tabId).catch(() => {});
599
+ _deps.queueEngine.onSessionExit(tabId);
600
+ _deps.cleanAutoApprovalBuffer(tabId);
601
+ _deps.cleanIdleNotify(tabId);
602
+ _deps.broadcastSessionList(true, { reason: 'attach-exit' });
603
+ });
604
+
605
+ // Wire up queue engine hooks if a queue exists for this session
606
+ _deps.setupQueueForSession(tabId);
607
+
608
+ _deps.broadcastSessionList(true, { reason: 'attach-created' });
609
+
610
+ res.writeHead(200, { 'Content-Type': 'application/json' });
611
+ res.end(JSON.stringify({ ok: true, tab_id: tabId, session_id }));
612
+ } catch (e) {
613
+ const status = e.statusCode || 500;
614
+ res.writeHead(status, { 'Content-Type': 'application/json', ...(status === 503 ? { 'Retry-After': '2' } : {}) });
615
+ res.end(JSON.stringify({ error: e.message, ...(status === 503 ? { retry: true } : {}) }));
616
+ }
617
+ });
618
+ }
619
+
620
+ module.exports = {
621
+ configure,
622
+ apiAttachSession,
623
+ };