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,558 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * WP-6 (docs/remote-agent-skills-bundles-2026-10-25.html §5 D-14, the D-14 decision row, §6.3, §8
5
+ * WP-6 row, AC-6a..AC-6c): CODEX INSTALL-MODE — the one place the box writes into its own home.
6
+ *
7
+ * Codex has no session-scoped skills root (`--add-dir` is a Claude-only flag, and `CODEX_HOME` also
8
+ * holds the user's sign-in, so it cannot be swapped per session — D-14's decision row, read
9
+ * codex-rs/ext/skills/src/host_roots.rs:48-185), so the bundle's `codex/<name>/` items are
10
+ * INSTALLED into `$HOME/.agents/skills/<name>/` with the SAME dpkg-conffile rule the box's own
11
+ * managed-skill installer already uses (lib/coding-cli-skill-installer.js): a pristine copy is
12
+ * created/updated, an edited copy is kept and reported, a folder CTM never wrote is untouched.
13
+ *
14
+ * REPLACE/REUSE, never a parallel installer: the marker/record constants, the root-record reader,
15
+ * the root-record writer and D-8's `writeAllowed` all come from lib/coding-cli-skill-installer.js.
16
+ * A directory-source plan is a genuinely different shape from that module's one-file template plan
17
+ * (its `buildPlan` classifies a single SKILL.md against a known-hash SET; here the unit is a whole
18
+ * directory compared hash-for-hash against the tree copy), so the planner lives here and calls the
19
+ * shared helpers; folding the two into one function would make the single-file path read worse.
20
+ *
21
+ * Classification (first match wins), per name:
22
+ * 1. target dir missing -> `removed` when the root record remembers an `agent-bundle`
23
+ * entry (the user deleted it — never resurrect), else `create`.
24
+ * EXCEPTION: a leftover `.<name>.ctm-retired-*` (crash
25
+ * mid-swap) means `create`.
26
+ * 2. no marker, or `source !== 'agent-bundle'` -> `conflict` (user-authored, or another
27
+ * installer's copy — untouched, not even hashed).
28
+ * 3. `diskDirHash !== marker.sha256` -> `edited` (kept; WP-7 reports "changed on box").
29
+ * 4. `diskDirHash === bundleDirHash` -> `noop`.
30
+ * 5. else -> `update`.
31
+ * retire: an installed `agent-bundle` copy of THIS hub, pristine, ABSENT from the current tree ->
32
+ * `retire` (the hub deleted that skill). Edited or other-hub copies are never retired.
33
+ *
34
+ * Write order per skill: staging dir (0644/0755 — the tree's read-only modes must NOT be copied, the
35
+ * user has to be able to edit) holding the copied bytes + the marker, then the swap (rename old to
36
+ * `.<name>.ctm-retired-*`, rename staging into place, remove the retired dir), then the root-record
37
+ * entry (atomic writes throughout, mirroring the installer's D-6 order).
38
+ *
39
+ * EVENT-DRIVEN, never a timer (stabilization lint): the store only changes at the two moments
40
+ * lib/agent-bundle/gc.js already names (boot, and a manifest PUT), so the codex install is scheduled
41
+ * from the manifest-PUT seam and is single-flight exactly like `scheduleCollectGarbage`.
42
+ */
43
+
44
+ const fsp = require('node:fs/promises');
45
+ const path = require('node:path');
46
+ const os = require('node:os');
47
+ const crypto = require('node:crypto');
48
+
49
+ const { atomicWriteFile } = require('../../atomic-write');
50
+ const store = require('./store');
51
+ const installer = require('../coding-cli-skill-installer');
52
+
53
+ /** D-14: the box-home write target. NOT `$CODEX_HOME/skills` — Codex marks that root deprecated. */
54
+ const AGENTS_SKILLS_DIR = '.agents';
55
+ const SKILLS_DIR = 'skills';
56
+ /** The id shape an item name must have to be usable as a directory name (store.js's ID_RE). */
57
+ const NAME_RE = /^[A-Za-z0-9._-]{1,128}$/;
58
+ const STAGING_RE = /^\.(.+)\.ctm-staging-[0-9a-f]+$/;
59
+ const RETIRED_RE = /^\.(.+)\.ctm-retired-[0-9a-f]+$/;
60
+ /** The marker `source` THIS module writes (the installer's own copies carry its own source string). */
61
+ const SOURCE = 'agent-bundle';
62
+
63
+ const { writeAllowed, MANAGED_SKILLS } = installer;
64
+ const { MARKER_FILE, ROOT_RECORD_FILE, readMarker, readRootRecord, writeRootRecord } = installer._internals;
65
+
66
+ /** REPORT sink: a throwing reporting backend must never turn a per-skill failure into a throw. */
67
+ function _report(report, error) {
68
+ if (typeof report !== 'function') return;
69
+ try {
70
+ report(error);
71
+ } catch {
72
+ /* expected: the report sink is best-effort telemetry; its own failure is not an install failure */
73
+ }
74
+ }
75
+
76
+ function _rand() {
77
+ return crypto.randomBytes(6).toString('hex');
78
+ }
79
+
80
+ function _sha256(data) {
81
+ return crypto.createHash('sha256').update(data).digest('hex');
82
+ }
83
+
84
+ /** The install root (D-14). Absolute; `homeDir` is injected so a test never reads the real home. */
85
+ function skillsRoot(homeDir) {
86
+ return path.join(homeDir, AGENTS_SKILLS_DIR, SKILLS_DIR);
87
+ }
88
+
89
+ async function _statOrNull(target) {
90
+ try {
91
+ return await fsp.lstat(target);
92
+ } catch (error) {
93
+ if (error && (error.code === 'ENOENT' || error.code === 'ENOTDIR')) return null;
94
+ throw error; // RETHROW: an unreadable path is a real fault the caller REPORTs
95
+ }
96
+ }
97
+
98
+ async function _isDir(target) {
99
+ const stat = await _statOrNull(target);
100
+ return stat !== null && stat.isDirectory();
101
+ }
102
+
103
+ /** readdir of a directory that may not exist yet (an empty root is not an error). */
104
+ async function _readdirOrEmpty(dir) {
105
+ try {
106
+ return await fsp.readdir(dir, { withFileTypes: true });
107
+ } catch (error) {
108
+ if (error && (error.code === 'ENOENT' || error.code === 'ENOTDIR')) return []; /* expected: no root yet */
109
+ throw error; // RETHROW: an unreadable directory is a real fault
110
+ }
111
+ }
112
+
113
+ /** Recursive remove that tolerates a vanished path (a concurrent run, or the rename that consumed it). */
114
+ async function _removeQuiet(target) {
115
+ try {
116
+ await fsp.rm(target, { recursive: true, force: true });
117
+ } catch (error) {
118
+ if (error && error.code === 'ENOENT') return; /* expected: already gone */
119
+ throw error; // RETHROW: a failed removal is a real fault (the caller REPORTs it)
120
+ }
121
+ }
122
+
123
+ /**
124
+ * sha256 over the sorted `{relPath, exec, sha256}` of every regular file under `dir`, EXCLUDING
125
+ * dot-names at any depth (so a `.ctm-managed` marker or a `.SKILL.md.edited-*.bak` backup never
126
+ * counts) and never following a symlink out of the directory (symlinks and other non-regular files
127
+ * are skipped — the hub builder already excludes them from the tree, so the tree copy and the
128
+ * installed copy hash equal iff their bytes match).
129
+ *
130
+ * @returns {Promise<string>} 64-hex digest
131
+ */
132
+ async function dirHash(dir) {
133
+ const files = [];
134
+ await _walk(dir, '', files);
135
+ files.sort((a, b) => (a.relPath < b.relPath ? -1 : a.relPath > b.relPath ? 1 : 0));
136
+ const payload = files.map((file) => `${file.relPath}\0${file.exec ? 'x' : '-'}\0${file.sha256}\n`).join('');
137
+ return _sha256(payload);
138
+ }
139
+
140
+ async function _walk(base, rel, out) {
141
+ const current = rel ? path.join(base, rel) : base;
142
+ for (const dirent of await _readdirOrEmpty(current)) {
143
+ if (dirent.name.startsWith('.')) continue; // markers/backups are never content
144
+ const childRel = rel ? `${rel}/${dirent.name}` : dirent.name;
145
+ if (dirent.isDirectory()) {
146
+ await _walk(base, childRel, out);
147
+ continue;
148
+ }
149
+ if (!dirent.isFile()) continue; // symlink/socket/fifo: never followed, never hashed
150
+ const full = path.join(base, ...childRel.split('/'));
151
+ const stat = await fsp.lstat(full);
152
+ const content = await fsp.readFile(full);
153
+ out.push({ relPath: childRel, exec: (stat.mode & 0o111) !== 0, sha256: _sha256(content) });
154
+ }
155
+ }
156
+
157
+ /** Copy a tree item into `destDir` with USER-writable modes: files 0644 (the exec bit preserved as
158
+ * 0755), dirs 0755. The tree's 0444/0555 read-only modes must never be reproduced — the user edits
159
+ * these copies, and a 0444 SKILL.md would make that a chmod first. */
160
+ async function _copyDir(srcDir, destDir) {
161
+ await fsp.mkdir(destDir, { recursive: true });
162
+ await fsp.chmod(destDir, 0o755);
163
+ for (const dirent of await fsp.readdir(srcDir, { withFileTypes: true })) {
164
+ if (dirent.name.startsWith('.')) continue; // a tree never carries one, but a marker never travels
165
+ const src = path.join(srcDir, dirent.name);
166
+ const dest = path.join(destDir, dirent.name);
167
+ if (dirent.isDirectory()) {
168
+ await _copyDir(src, dest);
169
+ continue;
170
+ }
171
+ if (!dirent.isFile()) continue;
172
+ const stat = await fsp.lstat(src);
173
+ await fsp.copyFile(src, dest);
174
+ await fsp.chmod(dest, (stat.mode & 0o111) !== 0 ? 0o755 : 0o644);
175
+ }
176
+ }
177
+
178
+ /** The bundle's codex item names (directories under `<treeDir>/codex`), sorted, dot-names excluded. */
179
+ async function _bundleItemNames(treeDir) {
180
+ const dirents = await _readdirOrEmpty(path.join(treeDir, 'codex'));
181
+ return dirents
182
+ .filter((dirent) => dirent.isDirectory() && !dirent.name.startsWith('.') && NAME_RE.test(dirent.name))
183
+ .map((dirent) => dirent.name)
184
+ .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
185
+ }
186
+
187
+ /** The names under `root` a hub copy could own: real directories that are not our bookkeeping dirs. */
188
+ function _installedNames(dirents) {
189
+ return dirents
190
+ .filter((dirent) => dirent.isDirectory() && !dirent.name.startsWith('.') && NAME_RE.test(dirent.name))
191
+ .map((dirent) => dirent.name)
192
+ .sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
193
+ }
194
+
195
+ /** The names a leftover DIRECTORY in `root` belongs to — the fingerprint of a crash between the two
196
+ * renames of a swap (or of a retire that never finished). */
197
+ function _leftoverPrefixes(dirents) {
198
+ const names = new Set();
199
+ for (const dirent of dirents) {
200
+ if (!dirent.isDirectory()) continue;
201
+ const match = RETIRED_RE.exec(dirent.name) || STAGING_RE.exec(dirent.name);
202
+ if (match) names.add(match[1]);
203
+ }
204
+ return names;
205
+ }
206
+
207
+ function _recordEntry(record, name) {
208
+ const entry = record && record.skills && record.skills[name];
209
+ return entry && typeof entry === 'object' ? entry : null;
210
+ }
211
+
212
+ /**
213
+ * Plan the install. Read-only: not a single write happens here.
214
+ *
215
+ * @param {string} treeDir absolute materialized tree dir (`trees/<hex>`)
216
+ * @param {object} [options]
217
+ * @param {string} [options.hubPeerId] the hub whose copy this is (marker + retire ownership)
218
+ * @param {string} [options.homeDir=os.homedir()] the box user's home (a test passes a temp HOME)
219
+ * @param {boolean} [options.isPrimary=false] D-8: only a primary may write the real home
220
+ * @param {string} [options.realHome=os.userInfo().homedir] the password-database home (ignores $HOME)
221
+ * @param {Function} [options.log]
222
+ * @returns {Promise<{root: string, writeAllowed: boolean, plans: object[]}>} plus the re-plan inputs
223
+ * an apply needs (`treeDir`, `hubPeerId`, `treeHash`, `homeDir`, `isPrimary`, `realHome`).
224
+ */
225
+ async function planBundleCodexInstall(treeDir, {
226
+ hubPeerId = '', homeDir = os.homedir(), isPrimary = false, realHome = os.userInfo().homedir, log,
227
+ // Apply's per-name re-plan passes the one name it is about to write, so a run hashes each
228
+ // directory a bounded number of times instead of re-hashing every skill once per skill.
229
+ onlyName = null,
230
+ } = {}) {
231
+ const root = skillsRoot(homeDir);
232
+ const hub = String(hubPeerId == null ? '' : hubPeerId).trim();
233
+ const baseName = path.basename(treeDir);
234
+ const treeHash = /^[0-9a-f]{64}$/.test(baseName) ? `sha256:${baseName}` : null;
235
+
236
+ const [allowed, record, bundleNames, rootDirents] = await Promise.all([
237
+ writeAllowed(root, { isPrimary, realHome }),
238
+ readRootRecord(root, { log }),
239
+ _bundleItemNames(treeDir),
240
+ _readdirOrEmpty(root),
241
+ ]);
242
+ const leftovers = _leftoverPrefixes(rootDirents);
243
+
244
+ const plans = [];
245
+ for (const name of bundleNames) {
246
+ if (onlyName !== null && name !== onlyName) continue;
247
+ const targetDir = path.join(root, name);
248
+ const bundleDirHash = await dirHash(path.join(treeDir, 'codex', name));
249
+ if (Object.hasOwn(MANAGED_SKILLS, name)) {
250
+ // The box installs its OWN `delegate` from its own release; a hub copy must never shadow it.
251
+ plans.push({ name, action: 'skipped_managed', targetDir, bundleDirHash, diskDirHash: null, marker: null });
252
+ continue;
253
+ }
254
+ let action;
255
+ let diskDirHash = null;
256
+ let marker = null;
257
+ if (!(await _isDir(targetDir))) {
258
+ // Rule 1. The leftover-retired exception is checked FIRST: a name the tree still HAS must be
259
+ // restored even though a record entry (which the crashed run never dropped) would say `removed`.
260
+ if (leftovers.has(name)) action = 'create';
261
+ else {
262
+ const entry = _recordEntry(record, name);
263
+ action = entry && entry.source === SOURCE ? 'removed' : 'create';
264
+ }
265
+ } else {
266
+ marker = await readMarker(targetDir, name);
267
+ if (!marker || marker.source !== SOURCE) {
268
+ action = 'conflict'; // user-authored, or another installer's copy — not even hashed
269
+ } else {
270
+ diskDirHash = await dirHash(targetDir);
271
+ if (marker.sha256 !== diskDirHash) action = 'edited';
272
+ else if (diskDirHash === bundleDirHash) action = 'noop';
273
+ else action = 'update';
274
+ }
275
+ }
276
+ plans.push({ name, action, targetDir, bundleDirHash, diskDirHash, marker });
277
+ }
278
+
279
+ // Retire: installed agent-bundle copies of THIS hub that the current tree no longer has.
280
+ const inTree = new Set(bundleNames);
281
+ for (const name of _installedNames(rootDirents)) {
282
+ if (inTree.has(name)) continue;
283
+ if (onlyName !== null && name !== onlyName) continue;
284
+ const targetDir = path.join(root, name);
285
+ const marker = await readMarker(targetDir, name);
286
+ if (!marker || marker.source !== SOURCE || marker.hubPeerId !== hub) continue; // not ours: never retired
287
+ const diskDirHash = await dirHash(targetDir);
288
+ if (marker.sha256 !== diskDirHash) continue; // edited on the box: kept, never retired
289
+ plans.push({ name, action: 'retire', targetDir, bundleDirHash: null, diskDirHash, marker });
290
+ }
291
+
292
+ return { root, writeAllowed: allowed, plans, treeDir, hubPeerId: hub, treeHash, homeDir, isPrimary, realHome };
293
+ }
294
+
295
+ /**
296
+ * Dirs a crashed run left behind, removed once they are older than this run (nothing of OURS is
297
+ * younger than `runStartMs`).
298
+ *
299
+ * `wanted(name)` splits the job in two, and that split is load-bearing: a `.<name>.ctm-retired-*` dir
300
+ * is the ONLY evidence that a swap crashed mid-flight (rule 1's exception), so it must still be there
301
+ * when the apply-time re-plan classifies the name — hence the start-of-apply sweep skips the names
302
+ * this run is about to act on, and those get their leftovers removed after the run's own swaps.
303
+ */
304
+ async function _sweepLeftovers(root, runStartMs, wanted = () => true) {
305
+ for (const dirent of await _readdirOrEmpty(root)) {
306
+ if (!dirent.isDirectory()) continue;
307
+ const match = RETIRED_RE.exec(dirent.name) || STAGING_RE.exec(dirent.name);
308
+ if (!match || !wanted(match[1])) continue;
309
+ const target = path.join(root, dirent.name);
310
+ const stat = await _statOrNull(target);
311
+ if (!stat || stat.mtimeMs > runStartMs) continue; // younger than this run: not ours to touch
312
+ await _removeQuiet(target);
313
+ }
314
+ }
315
+
316
+ /** Remove the root-record entry for `name`. `writeRootRecord` only merges, so a drop reads and writes
317
+ * through the shared reader/format — a re-add by the hub must then classify `create`, not `removed`. */
318
+ async function _dropRecordEntry(root, name, log) {
319
+ const current = await readRootRecord(root, { log });
320
+ if (!current || !current.skills || !Object.hasOwn(current.skills, name)) return;
321
+ delete current.skills[name];
322
+ await atomicWriteFile(path.join(root, ROOT_RECORD_FILE), JSON.stringify(current, null, 2) + '\n');
323
+ }
324
+
325
+ /**
326
+ * Apply a plan. Never throws for a per-skill failure — that skill's result carries `skipped:'error'`
327
+ * and the fault reaches `report`; the run continues (REPORT + continue, like syncManagedSkills).
328
+ *
329
+ * @param {object} plan from planBundleCodexInstall
330
+ * @param {object} [options]
331
+ * @param {string} [options.hubPeerId] falls back to the plan's
332
+ * @param {string} [options.treeHash] falls back to the plan's (derived from the tree dir name)
333
+ * @param {() => Date} [options.now]
334
+ * @param {Function} [options.report]
335
+ * @param {Function} [options.log]
336
+ * @returns {Promise<{results: Array<{name: string, action: string, written: boolean, skipped: string|null}>}>}
337
+ */
338
+ async function applyBundleCodexInstall(plan, {
339
+ hubPeerId = null, treeHash = null, now = () => new Date(), report = null, log,
340
+ } = {}) {
341
+ const root = plan.root;
342
+ const hub = String((hubPeerId == null ? plan.hubPeerId : hubPeerId) || '').trim();
343
+ const hash = treeHash == null ? plan.treeHash : treeHash;
344
+
345
+ if (!plan.writeAllowed) {
346
+ // D-8: a non-primary instance never writes the real home. Every result says so; nothing is touched.
347
+ return {
348
+ results: plan.plans.map((entry) => ({
349
+ name: entry.name, action: entry.action, written: false, skipped: 'write_blocked_real_home',
350
+ })),
351
+ };
352
+ }
353
+
354
+ const stamp = now();
355
+ const plannedNames = new Set(plan.plans.map((entry) => entry.name));
356
+ const results = [];
357
+ try {
358
+ // Start of apply: everything older than this run EXCEPT the names below — their leftover is the
359
+ // crash fingerprint the re-plan still has to read (see _sweepLeftovers).
360
+ await _sweepLeftovers(root, stamp.getTime(), (name) => !plannedNames.has(name));
361
+ } catch (error) {
362
+ _report(report, error); // REPORT: a failed cleanup must not stop this run's real work
363
+ }
364
+
365
+ for (const entry of plan.plans) {
366
+ const result = { name: entry.name, action: entry.action, written: false, skipped: null };
367
+ try {
368
+ // Re-plan THIS name and refuse if its action moved (a lost race): never write against a stale plan.
369
+ const fresh = await planBundleCodexInstall(plan.treeDir, {
370
+ hubPeerId: hub, homeDir: plan.homeDir, isPrimary: plan.isPrimary, realHome: plan.realHome, log,
371
+ onlyName: entry.name,
372
+ });
373
+ const current = fresh.plans.find((candidate) => candidate.name === entry.name) || null;
374
+ if (!current || current.action !== entry.action) {
375
+ result.skipped = 'plan_changed';
376
+ results.push(result);
377
+ continue;
378
+ }
379
+ if (current.action === 'create' || current.action === 'update') {
380
+ await _installOne(root, current, fresh, { hub, hash, stamp, report, log });
381
+ result.written = true;
382
+ } else if (current.action === 'retire') {
383
+ await _retireOne(root, current, log);
384
+ result.written = true;
385
+ }
386
+ // noop / conflict / edited / removed / skipped_managed: nothing is written.
387
+ } catch (error) {
388
+ // REPORT: one skill's failure never aborts the others (and never rejects the caller).
389
+ result.skipped = 'error';
390
+ _report(report, error);
391
+ }
392
+ results.push(result);
393
+ }
394
+
395
+ try {
396
+ // End of apply: the names the start sweep had to leave alone (no swap of ours survives this).
397
+ await _sweepLeftovers(root, stamp.getTime(), (name) => plannedNames.has(name));
398
+ } catch (error) {
399
+ _report(report, error); // REPORT: leftover cleanup is best-effort and retried by the next run
400
+ }
401
+ return { results };
402
+ }
403
+
404
+ async function _installOne(root, current, fresh, { hub, hash, stamp, report, log }) {
405
+ const name = current.name;
406
+ const installedAt = (current.marker && current.marker.installedAt) || stamp.toISOString();
407
+ const staging = path.join(root, `.${name}.ctm-staging-${_rand()}`);
408
+ try {
409
+ await _copyDir(path.join(fresh.treeDir, 'codex', name), staging);
410
+ await atomicWriteFile(path.join(staging, MARKER_FILE), JSON.stringify({
411
+ schema: 2,
412
+ name,
413
+ source: SOURCE,
414
+ hubPeerId: hub,
415
+ treeHash: hash,
416
+ sha256: current.bundleDirHash,
417
+ installedAt,
418
+ updatedAt: stamp.toISOString(),
419
+ }, null, 2) + '\n');
420
+ if (current.action === 'update') {
421
+ // Crash window: between these two renames the target is missing and `.<name>.ctm-retired-*`
422
+ // is present — which is exactly what the planner's rule-1 exception turns back into `create`.
423
+ const retired = path.join(root, `.${name}.ctm-retired-${_rand()}`);
424
+ await fsp.rename(current.targetDir, retired);
425
+ try {
426
+ await fsp.rename(staging, current.targetDir);
427
+ } catch (error) {
428
+ await _removeQuiet(retired); // then RETHROW: never leave a half-swapped install behind
429
+ throw error;
430
+ }
431
+ await _removeQuiet(retired);
432
+ } else {
433
+ await fsp.rename(staging, current.targetDir);
434
+ }
435
+ } catch (error) {
436
+ await _removeQuiet(staging); // RETHROW: the caller REPORTs it and the original directory survives
437
+ throw error;
438
+ }
439
+ await writeRootRecord(root, name, {
440
+ source: SOURCE,
441
+ hubPeerId: hub,
442
+ lastSha256: current.bundleDirHash,
443
+ lastWrittenAt: stamp.toISOString(),
444
+ firstInstalledAt: installedAt,
445
+ }, log);
446
+ }
447
+
448
+ async function _retireOne(root, current, log) {
449
+ const retired = path.join(root, `.${current.name}.ctm-retired-${_rand()}`);
450
+ await fsp.rename(current.targetDir, retired);
451
+ await _removeQuiet(retired);
452
+ // Drop the record entry: a later re-add by the hub is `create`, never `removed`.
453
+ await _dropRecordEntry(root, current.name, log);
454
+ }
455
+
456
+ let _inFlight = null;
457
+ // hubPeerId -> { dataDir, deps } for calls that arrived while a run was in flight. Unlike the GC
458
+ // (one sweep covers everything), a run installs ONE hub's tree as of when it read the pointer, so a
459
+ // call for another hub — or the same hub after a newer PUT — must get a run of its own afterwards.
460
+ const _pending = new Map();
461
+
462
+ /**
463
+ * Single-flight trigger for the manifest-PUT seam (and any future boot hook). A call while an install
464
+ * is running returns THAT run's promise and queues one follow-up run for its hub (coalesced: the
465
+ * latest deps per hub win), started as soon as the current run settles; a call after it settles
466
+ * starts a new one. Never rejects: the returned promise always resolves (a fault reaches `report`).
467
+ * The tree is resolved through the hub's CURRENT pointer, so the caller needs nothing but the hub
468
+ * identity.
469
+ *
470
+ * Deliberately not `async` — an async wrapper would hand every caller a fresh promise and break the
471
+ * "same promise" contract the single-flight test asserts (gc.js's own reasoning).
472
+ *
473
+ * @param {string} dataDir
474
+ * @param {object} [deps]
475
+ * @param {string} [deps.hubPeerId] the hub whose CURRENT tree to install
476
+ * @param {boolean} [deps.isPrimary]
477
+ * @param {string} [deps.homeDir]
478
+ * @param {string} [deps.realHome]
479
+ * @param {() => Date} [deps.now]
480
+ * @param {Function} [deps.report]
481
+ * @param {Function} [deps.log]
482
+ * @returns {Promise<{results: object[]}>}
483
+ */
484
+ function scheduleBundleCodexInstall(dataDir, deps = {}) {
485
+ if (_inFlight) {
486
+ _pending.set(String((deps && deps.hubPeerId) || ''), { dataDir, deps });
487
+ return _inFlight;
488
+ }
489
+ const run = Promise.resolve()
490
+ .then(() => _runCodexInstall(dataDir, deps))
491
+ .catch((error) => {
492
+ // REPORT: a run that fails as a whole (not per skill) must not reject into a `void` caller.
493
+ _report(deps && deps.report, error);
494
+ return { results: [] };
495
+ });
496
+ _inFlight = run;
497
+ // `run` never rejects, so this settlement tail cannot itself create an unhandled rejection.
498
+ const settle = () => {
499
+ if (_inFlight !== run) return;
500
+ _inFlight = null;
501
+ if (!_pending.size) return;
502
+ const [key, next] = _pending.entries().next().value;
503
+ _pending.delete(key);
504
+ scheduleBundleCodexInstall(next.dataDir, next.deps); // never rejects (see above)
505
+ };
506
+ run.then(settle, settle);
507
+ return run;
508
+ }
509
+
510
+ async function _runCodexInstall(dataDir, deps) {
511
+ const hubPeerId = String((deps && deps.hubPeerId) == null ? '' : deps.hubPeerId).trim();
512
+ if (!hubPeerId) return { results: [] }; // no hub, no install (D-14: there is nothing to install)
513
+ const treeHash = await store.currentHubTree(dataDir, hubPeerId);
514
+ if (!treeHash) return { results: [] };
515
+ const treeDir = path.join(store.storeRoot(dataDir), 'trees', treeHash.replace(/^sha256:/, ''));
516
+ if (!(await _isDir(treeDir))) return { results: [] }; // the tree is gone: nothing to install, not a fault
517
+ const plan = await planBundleCodexInstall(treeDir, {
518
+ hubPeerId,
519
+ homeDir: deps.homeDir,
520
+ isPrimary: deps.isPrimary,
521
+ realHome: deps.realHome,
522
+ log: deps.log,
523
+ });
524
+ return applyBundleCodexInstall(plan, {
525
+ hubPeerId,
526
+ treeHash,
527
+ now: deps.now,
528
+ report: deps.report,
529
+ log: deps.log,
530
+ });
531
+ }
532
+
533
+ /** Test-only: drop the single-flight latch so each test observes its own run. */
534
+ function _resetForTests() {
535
+ _inFlight = null;
536
+ _pending.clear();
537
+ }
538
+
539
+ /** Test-only: resolve once no run is in flight and none is queued. `settle` starts the queued run
540
+ * synchronously before any later continuation, so awaiting the current run and re-checking
541
+ * observes every follow-up without a timer. */
542
+ async function _whenIdleForTests() {
543
+ while (_inFlight) await _inFlight;
544
+ }
545
+
546
+ module.exports = {
547
+ planBundleCodexInstall,
548
+ applyBundleCodexInstall,
549
+ scheduleBundleCodexInstall,
550
+ dirHash,
551
+ // WP-8: `retireBoxSkill` moves an edited agent-bundle copy aside and must ALSO drop its root-record
552
+ // entry, or the hub's next push would read the missing target as a user DELETION (`removed`) and
553
+ // never re-install the pristine bundle copy. Both helpers are named here rather than re-derived.
554
+ skillsRoot,
555
+ dropRecordEntry: _dropRecordEntry,
556
+ _resetForTests,
557
+ _whenIdleForTests,
558
+ };
@@ -0,0 +1,120 @@
1
+ 'use strict';
2
+
3
+ // WP-3 (docs/agent-bundle-darwin-readonly-rename-2026-10-27.html §16 D-6/D-9/D-13, §17): the PURE
4
+ // bundle-drift rules. No fs, no db, no net — the process argv arrives as a string (or an argv
5
+ // array), the store's answer arrives as an already-resolved tree hash, and a session arrives as a
6
+ // plain view object. Everything the reconciler needs to decide is decidable here, which is why the
7
+ // whole decision surface is unit-testable without a server.
8
+ //
9
+ // The contract (D-6): `applied` is the tree hex the RUNNING process was spawned with — null when it
10
+ // was spawned with none, the string 'unknown' when the process could not be read. `expected` is what
11
+ // `store.resolveSpawnBundle(dataDir, sessionId)` answers right now, as hex (null when the box has no
12
+ // hub tree). Drift is `expected !== null && applied !== expected`; 'unknown' is NEVER drift, because
13
+ // an unreadable process is not evidence that the process is wrong.
14
+
15
+ /** D-13: at most this many relaunch attempts per (session, expected tree) pair. */
16
+ const DRIFT_MAX_ATTEMPTS = 2;
17
+
18
+ // The spawn's own shape: `--add-dir <...>/trees/<64 hex>/claude`, followed by whitespace or the end
19
+ // of the line. Anchored on the literal `--add-dir` so a `/trees/<hex>/claude` path that appears in
20
+ // some OTHER token (a cwd, a log line, a `--settings` value) is not mistaken for the bundle.
21
+ const ADD_DIR_TREE_RE = /--add-dir (\S+?)\/trees\/([0-9a-f]{64})\/claude(?=\s|$)/;
22
+
23
+ /**
24
+ * Group 2 of the `--add-dir <dir>/trees/<hex>/claude` token in an argv (array) or a `ps` line
25
+ * (string), or null when the spawn carried no bundle. A `ps -o args=` line is the argv joined by
26
+ * single spaces, so both forms are read the same way.
27
+ *
28
+ * @param {string[]|string|null|undefined} argvOrLine
29
+ * @returns {string|null} 64 lowercase hex characters
30
+ */
31
+ function treeHashFromArgv(argvOrLine) {
32
+ const line = Array.isArray(argvOrLine)
33
+ ? argvOrLine.map((value) => String(value == null ? '' : value)).join(' ')
34
+ : String(argvOrLine == null ? '' : argvOrLine);
35
+ if (!line) return null;
36
+ const match = ADD_DIR_TREE_RE.exec(line);
37
+ return match ? match[2] : null;
38
+ }
39
+
40
+ /**
41
+ * The hex of `'sha256:<hex>'` or a bare `'<hex>'`; null for anything else (including null,
42
+ * undefined and the 'unknown' sentinel, which is not a tree).
43
+ *
44
+ * @param {string|null|undefined} treeHash
45
+ * @returns {string|null}
46
+ */
47
+ function treeHex(treeHash) {
48
+ if (typeof treeHash !== 'string') return null;
49
+ const value = treeHash.trim();
50
+ if (/^sha256:[0-9a-f]{64}$/.test(value)) return value.slice('sha256:'.length);
51
+ if (/^[0-9a-f]{64}$/.test(value)) return value;
52
+ return null;
53
+ }
54
+
55
+ /**
56
+ * D-6: does the RUNNING process need to be respawned to carry the tree the box expects now?
57
+ *
58
+ * (null, X) -> no-bundle the process was spawned before a tree existed (deepseek)
59
+ * (X, Y≠X) -> stale the hub pushed a different tree
60
+ * (X, X) -> none
61
+ * (*, null) -> none the box has no hub tree: never force a relaunch onto nothing
62
+ * ('unknown', X) -> unknown-applied, drift:false
63
+ *
64
+ * @returns {{drift: boolean, case: 'none'|'no-bundle'|'stale'|'unknown-applied'}}
65
+ */
66
+ function computeDrift({ applied = null, expected = null } = {}) {
67
+ if (expected === null || expected === undefined) return { drift: false, case: 'none' };
68
+ if (applied === 'unknown') return { drift: false, case: 'unknown-applied' };
69
+ if (applied === null || applied === undefined) return { drift: true, case: 'no-bundle' };
70
+ if (applied === expected) return { drift: false, case: 'none' };
71
+ return { drift: true, case: 'stale' };
72
+ }
73
+
74
+ /**
75
+ * D-9: the manual-park gate (`evaluateManualPark`, so an unattended relaunch is exactly as
76
+ * acceptable as a park the user's own model switch performs) PLUS three drift-only refusals:
77
+ *
78
+ * - `composer-unknown`: manual park treats an unreadable composer as "no draft" because a user
79
+ * asked. Nobody asked here, so an unreadable composer protects (the opposite of D-4).
80
+ * - `waiting-input`: manual park also rewrites `waiting_input`/`resuming` to `idle` (the user who
81
+ * asked their own model switch is present to answer). An unattended relaunch would kill a turn
82
+ * that is waiting for input, so the drift gate keeps the protection the raw gate carries.
83
+ * - `input-in-flight`: a remote submission is mid-delivery (`_remoteTerminalSubmissionInFlight`).
84
+ * - `transition`: a deferred restore or a lead-model transition is already in flight.
85
+ * - `create-in-flight` (D-10): a new-session create is in flight on this box
86
+ * (`host.createInFlight`: lib/session-create/prepared.js `inflightCreateCount() > 0`). On
87
+ * 2026-11-01 15:33-15:35Z this pass parked and cold-relaunched three live leads while a
88
+ * create was in flight; the relaunch now waits until no create is in progress (not the
89
+ * 120 s restart lease, which outlives the create).
90
+ *
91
+ * A refusal DEFERS the relaunch (the caller records `drift-pending` and retries on the next sweep);
92
+ * it never force-parks.
93
+ *
94
+ * @returns {{ok: boolean, protections: string[]}} the shape §17 names
95
+ */
96
+ function evaluateDriftRelaunch(session, host, config, { evaluateManualPark } = {}) {
97
+ if (typeof evaluateManualPark !== 'function') throw new TypeError('evaluateDriftRelaunch requires evaluateManualPark');
98
+ // AC-3c: `isActiveTab` / `isMru` are already dropped by evaluateManualPark's own view transform;
99
+ // `pinned` is NOT — it refuses there (tests/session-lifecycle.test.js:385) because a lifecycle pin
100
+ // is the user telling CTM not to RECLAIM the session's memory. A drift relaunch reclaims nothing:
101
+ // it respawns the same tab onto the tree the box already owes it, so pinning must not strand a
102
+ // session without skills. Cleared here rather than in evaluateManualPark (whose other callers rely
103
+ // on the refusal).
104
+ const base = evaluateManualPark({ ...session, pinned: false }, host, config) || { protections: [] };
105
+ const protections = Array.isArray(base.protections) ? base.protections.slice() : [];
106
+ if (session?.liveStatus === 'waiting_input' || session?.liveStatus === 'resuming') protections.push('waiting-input');
107
+ if (session?.composerDraft === 'unknown' && !protections.includes('composer-unknown')) protections.push('composer-unknown');
108
+ if (session?._remoteTerminalSubmissionInFlight) protections.push('input-in-flight');
109
+ if (session?._restoreDeferred === true || session?._activationStartPromise) protections.push('transition');
110
+ if (host?.createInFlight === true) protections.push('create-in-flight');
111
+ return { ok: protections.length === 0, protections };
112
+ }
113
+
114
+ module.exports = {
115
+ DRIFT_MAX_ATTEMPTS,
116
+ treeHashFromArgv,
117
+ treeHex,
118
+ computeDrift,
119
+ evaluateDriftRelaunch,
120
+ };