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
@@ -1,6 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  const { reduceModelObservation, validateModelState } = require('../coding-session-model-state');
4
+ // WP-8 (deepseek-harness-acp-agent §9b M-9): the owner agent-type list is the ONE declaration in
5
+ // lib/coding-session-model-owner.js — this module keeps no private copy of it.
6
+ const { OWNER_AGENT_TYPES } = require('../coding-session-model-owner');
4
7
  const { compatibilityModelFact } = require('../coding-session-model-projection');
5
8
  const { readLaunchGenerationRow, applyGenerationAdoption } = require('./coding-session-generation-store');
6
9
 
@@ -27,7 +30,7 @@ function createCodingSessionModelStore({ getCoreDb, tableExists, projectCompatib
27
30
  return decode(select(db, String(ctmSessionId || '').trim()));
28
31
  };
29
32
  const ownerValid = (owner) => owner && typeof owner.ctmSessionId === 'string' && owner.ctmSessionId.trim() === owner.ctmSessionId && Buffer.byteLength(owner.ctmSessionId, 'utf8') > 0 && Buffer.byteLength(owner.ctmSessionId, 'utf8') <= 256 &&
30
- ['claude', 'codex', 'gemini', 'opencode'].includes(owner.agentType) &&
33
+ OWNER_AGENT_TYPES.includes(owner.agentType) &&
31
34
  typeof owner.providerSessionId === 'string' && owner.providerSessionId.trim() === owner.providerSessionId && Buffer.byteLength(owner.providerSessionId, 'utf8') > 0 && Buffer.byteLength(owner.providerSessionId, 'utf8') <= 256 &&
32
35
  Number.isSafeInteger(owner.processGeneration) && owner.processGeneration >= 0;
33
36
  const ownerRow = (db, owner) => db.prepare(`
@@ -17,7 +17,7 @@
17
17
  const { isValidEffort } = require('../launch-model-override');
18
18
 
19
19
  const MAX_FIELD_LENGTH = 200;
20
- const AGENTS = new Set(['claude', 'codex']);
20
+ const AGENTS = new Set(['claude', 'codex', 'opencode']);
21
21
  const LANES = new Set(['native', 'routed', 'inherit']);
22
22
  const LAUNCH_KINDS = new Set(['none', 'native', 'inherit', 'routed', 'unservable']);
23
23
  const MIRROR_TABLE = 'delegate_pref_mirror';
@@ -44,6 +44,19 @@ function _normalizeEffort(effort) {
44
44
  throw new TypeError("effort must be null, '' or a bounded effort identifier");
45
45
  }
46
46
 
47
+ // v58 (WP-1, codex-native-subagent-pick-enforcement-2026-10-28.html §5 D-4, §6): the launch row also
48
+ // records what the spawn DID about Codex native sub-agents. Exactly two known states plus unknown:
49
+ // null = unknown (no fact recorded, i.e. every pre-v58 row)
50
+ // '' = this launch did NOT deny native sub-agents
51
+ // 'disabled' = this launch carried CODEX_NATIVE_SUBAGENT_DENY_ARGS
52
+ // A non-string or any other token is refused here rather than stored, so a read can never report a
53
+ // malformed value as the deny fact.
54
+ function _normalizeNativeSubagents(value) {
55
+ if (value === null || value === undefined) return null;
56
+ if (value === '' || value === 'disabled') return value;
57
+ throw new TypeError("nativeSubagents must be null, '' or 'disabled'");
58
+ }
59
+
47
60
  function createDelegatePrefMirrorStore({ getCoreDb, tableExists, flushWal = () => {}, now = () => new Date().toISOString() } = {}) {
48
61
  if (typeof getCoreDb !== 'function' || typeof tableExists !== 'function') throw new TypeError('getCoreDb and tableExists are required');
49
62
 
@@ -161,6 +174,9 @@ function createDelegatePrefMirrorStore({ getCoreDb, tableExists, flushWal = () =
161
174
  // Absent (pre-v50) is NULL = unknown; a stored '' is the separate "known NO role effort
162
175
  // override" state. Read verbatim -- the projection decides what a kind/agent may claim.
163
176
  effort: row.effort == null ? null : row.effort,
177
+ // v58 (WP-1): what this launch DID about Codex native sub-agents. Absent (pre-v58) is NULL =
178
+ // unknown; read verbatim, so a malformed stored value never becomes a claim.
179
+ native_subagents: row.native_subagents == null ? null : row.native_subagents,
164
180
  };
165
181
  }
166
182
 
@@ -168,18 +184,24 @@ function createDelegatePrefMirrorStore({ getCoreDb, tableExists, flushWal = () =
168
184
  const sessionId = _str(ctmSessionId);
169
185
  if (!sessionId || !tableExists(LAUNCH_TABLE)) return null;
170
186
  return toLaunchRow(getCoreDb().prepare(
171
- 'SELECT ctm_session_id, agent, kind, model, source, origin, reason, route_id, launched_at, process_generation, harness, provider_thread_id, effort FROM session_subagent_pick_launch WHERE ctm_session_id = ?'
187
+ 'SELECT ctm_session_id, agent, kind, model, source, origin, reason, route_id, launched_at, process_generation, harness, provider_thread_id, effort, native_subagents FROM session_subagent_pick_launch WHERE ctm_session_id = ?'
172
188
  ).get(sessionId));
173
189
  }
174
190
 
175
191
  function setLaunch({ ctmSessionId, agent, kind, model = '', source = '', origin = '', reason = '', routeId = '',
176
- processGeneration, harness, providerThreadId, launchedAt, effort = null } = {}) {
192
+ processGeneration, harness, providerThreadId, launchedAt, effort = null, nativeSubagents = null } = {}) {
177
193
  const sessionId = _str(ctmSessionId).trim();
178
194
  if (!sessionId) throw new TypeError('ctmSessionId is required');
179
- if (!AGENTS.has(agent)) throw new TypeError("agent must be 'claude' or 'codex'");
195
+ if (!AGENTS.has(agent)) throw new TypeError("agent must be 'claude', 'codex' or 'opencode'");
180
196
  if (!LAUNCH_KINDS.has(kind)) throw new TypeError('kind must be none, native, inherit, routed or unservable');
181
197
  const effortValue = _normalizeEffort(effort);
182
- const expectedHarness = agent === 'claude' ? 'claude-code' : 'codex-cli';
198
+ const nativeSubagentsValue = _normalizeNativeSubagents(nativeSubagents);
199
+ // Only Codex has its own harness label. An 'opencode' lead owns its launch row under the same
200
+ // non-Codex label Claude uses: CTM's owner tuple (generation/harness/thread, see
201
+ // subagent-pick-table._launchOwnerContext and deleteOwnedLaunch) has exactly two values, and an
202
+ // OpenCode lead has no lead-route token to conflict with either. Per-launch model projection for
203
+ // OpenCode is env-only (OPENCODE_CONFIG_CONTENT), so there is no third harness to add here.
204
+ const expectedHarness = agent === 'codex' ? 'codex-cli' : 'claude-code';
183
205
  if (!Number.isSafeInteger(processGeneration) || processGeneration < 1) {
184
206
  throw new TypeError('processGeneration must be a positive integer');
185
207
  }
@@ -213,15 +235,16 @@ function createDelegatePrefMirrorStore({ getCoreDb, tableExists, flushWal = () =
213
235
  db.prepare(
214
236
  `INSERT INTO session_subagent_pick_launch
215
237
  (ctm_session_id, agent, kind, model, source, origin, reason, route_id, launched_at,
216
- process_generation, harness, provider_thread_id, effort)
217
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
238
+ process_generation, harness, provider_thread_id, effort, native_subagents)
239
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
218
240
  ON CONFLICT(ctm_session_id) DO UPDATE SET agent = excluded.agent, kind = excluded.kind,
219
241
  model = excluded.model, source = excluded.source, origin = excluded.origin,
220
242
  reason = excluded.reason, route_id = excluded.route_id, launched_at = excluded.launched_at,
221
243
  process_generation = excluded.process_generation, harness = excluded.harness,
222
- provider_thread_id = excluded.provider_thread_id, effort = excluded.effort`
244
+ provider_thread_id = excluded.provider_thread_id, effort = excluded.effort,
245
+ native_subagents = excluded.native_subagents`
223
246
  ).run(sessionId, agent, kind, fields.model, fields.source, fields.origin, fields.reason,
224
- fields.routeId, launched, processGeneration, harness, threadId, effortValue);
247
+ fields.routeId, launched, processGeneration, harness, threadId, effortValue, nativeSubagentsValue);
225
248
  return getLaunch(sessionId);
226
249
  });
227
250
  const result = run();
@@ -0,0 +1,235 @@
1
+ 'use strict';
2
+
3
+ // Boot sweep for CTM's non-PTY ("structured") sessions. Extracted verbatim from server.js (design
4
+ // doc docs/deepseek-harness-acp-agent-2026-10-28.html §7 offset (3), §9 S-42) so server.js keeps its
5
+ // zero-headroom line count while the same sweep can later also close out an interrupted DeepSeek
6
+ // Harness turn (WP-5 adds that strategy next to the Wall-E one below).
7
+ //
8
+ // WP-V2 A4 — boot sweep. Incident class B: a Wall-E turn that died mid-retry across a restart writes
9
+ // NO terminal record, so the durable transcript permanently looks "running"
10
+ // (walle-interrupted-visibility-resume-design-2026-07-17.html §2.2 V6). Closes those out at the next
11
+ // boot: bounded to walle sessions active in the last 7 days, off the main thread for both the recency
12
+ // check and the jsonl read (reuses the SAME read-pool ops the restore-interruption detector already
13
+ // uses), never a synchronous DB query, and scheduled by server.js via the existing
14
+ // _scheduleAfterStartupMaintenanceQuiet deferral so it never competes with the restore storm on the
15
+ // boot critical path.
16
+ //
17
+ // Every server global the two functions use arrives as a dep; the two limit values (one of them
18
+ // env-derived) are read in server.js because the flag lint forbids new raw process.env reads here.
19
+ //
20
+ // WP-5 (§5 D-9, §9 S-42) adds the DeepSeek Harness strategy beside the Wall-E one. A dsh turn that was
21
+ // in flight when CTM went down wrote its user row and nothing after it, so the durable transcript
22
+ // permanently looks "running". At the next boot the dsh strategy re-reads that transcript off-thread,
23
+ // and — when the last row is a `user` row — appends exactly ONE `dsh_turn_interrupted` row through the
24
+ // SAME writer the live path uses (append-only, async, ordered). Since WP-7d that row is
25
+ // `assistant`-shaped (an API-error row) so Conv renders it. It is naturally
26
+ // idempotent: the row it writes is itself terminal, so a second sweep finds nothing open.
27
+
28
+ const { createDshTranscriptWriter, dshTranscriptDir, findOpenDshTurn } = require('./acp/dsh-transcript');
29
+ const path = require('node:path');
30
+
31
+ /**
32
+ * @param {object} deps
33
+ * @param {Map} deps.sessions
34
+ * @param {Function} deps.offthreadRead // server.js _offthreadRead
35
+ * @param {object} deps.restoreInterruptionBreaker
36
+ * @param {Function} deps.findLastWalleTurnTerminal
37
+ * @param {Function} deps.detectOpenWalleTurn
38
+ * @param {Function} deps.appendWalleTurnTerminal // server.js _appendWalleTurnTerminal
39
+ * @param {object} deps.runtimeWorkRegistry
40
+ * @param {Function} deps.statFilesOffThread // server.js _statFilesOffThread
41
+ * @param {number} deps.maxSessions
42
+ * @param {number} deps.maxAgeMs
43
+ * @param {Function} [deps.getDataDir] // WP-5: lib/data-dir.js's getDataDir()
44
+ * @param {Function} [deps.log]
45
+ * @param {Function} [deps.warn]
46
+ * @param {Function} [deps.error]
47
+ */
48
+ function createStructuredSessionBootSweep(deps = {}) {
49
+ const {
50
+ sessions,
51
+ offthreadRead,
52
+ restoreInterruptionBreaker,
53
+ findLastWalleTurnTerminal,
54
+ detectOpenWalleTurn,
55
+ appendWalleTurnTerminal,
56
+ runtimeWorkRegistry,
57
+ statFilesOffThread,
58
+ maxSessions,
59
+ maxAgeMs,
60
+ getDataDir,
61
+ log = (...args) => console.log(...args),
62
+ warn = (...args) => console.warn(...args),
63
+ error = (...args) => console.error(...args),
64
+ } = deps;
65
+
66
+ async function sweepWalleSessionOpenTurn(session) {
67
+ if (!session || !session.jsonlPath || session.abortController) return null;
68
+ const tail = await offthreadRead({
69
+ breaker: restoreInterruptionBreaker,
70
+ op: 'restoreInterruptionTail',
71
+ payload: { path: session.jsonlPath },
72
+ timeoutMs: 15000,
73
+ onSuccess: (res) => res || null,
74
+ onFail: () => null,
75
+ });
76
+ const entries = tail && Array.isArray(tail.entries) ? tail.entries : null;
77
+ if (!entries || !entries.length) return null;
78
+ const lastTerminal = findLastWalleTurnTerminal(entries);
79
+ if (lastTerminal) {
80
+ session._walleLastTurnTerminal = lastTerminal;
81
+ session._walleTurnTerminalWrittenFor = lastTerminal.turnId || '';
82
+ }
83
+ const openTurn = detectOpenWalleTurn(entries);
84
+ if (!openTurn.open) return null;
85
+ // Re-check liveness right before writing — this session's off-thread tail read may
86
+ // have raced a fresh turn starting (auto-resume, a queued send) while it was in
87
+ // flight. Never slam the door on a turn that's actually running.
88
+ if (session.abortController) return null;
89
+ const appended = appendWalleTurnTerminal(session, openTurn.turnId, {
90
+ status: 'interrupted_infra',
91
+ reason: 'boot_sweep_open_turn',
92
+ source: 'boot_sweep',
93
+ detectedBy: 'boot_sweep',
94
+ message: 'Wall-E restarted before this turn finished — nothing is running now.',
95
+ lastActivityAt: openTurn.lastActivityAt || '',
96
+ });
97
+ if (!appended) return null;
98
+ const retired = runtimeWorkRegistry.retireTurnWithoutHydration({
99
+ sessionId: session.id,
100
+ turnId: openTurn.turnId,
101
+ status: 'stopped',
102
+ summary: 'Interrupted by CTM restart',
103
+ });
104
+ if (retired?.persisted && typeof retired.persisted.then === 'function') {
105
+ await retired.persisted;
106
+ }
107
+ return appended;
108
+ }
109
+
110
+ async function runWalleBootTurnSweep() {
111
+ try {
112
+ const candidates = [];
113
+ for (const session of sessions.values()) {
114
+ if (session && session.type === 'walle' && session.jsonlPath) candidates.push(session);
115
+ if (candidates.length >= maxSessions) break;
116
+ }
117
+ if (!candidates.length) return;
118
+ const stats = await statFilesOffThread(candidates.map((s) => s.jsonlPath), 20000);
119
+ if (!stats) {
120
+ warn('[walle-boot-sweep] off-thread stat unavailable — skipping this boot (next boot retries).');
121
+ return;
122
+ }
123
+ const cutoff = Date.now() - maxAgeMs;
124
+ const recent = candidates.filter((session) => {
125
+ const st = stats.get(session.jsonlPath);
126
+ return st && st.exists && Number(st.mtimeMs || 0) >= cutoff;
127
+ });
128
+ let closed = 0;
129
+ for (const session of recent) {
130
+ try {
131
+ const result = await sweepWalleSessionOpenTurn(session);
132
+ if (result) closed += 1;
133
+ } catch (e) {
134
+ error('[walle-boot-sweep] session sweep failed:', session.id, e && e.message);
135
+ }
136
+ }
137
+ if (recent.length) {
138
+ log(` [walle-boot-sweep] checked ${recent.length} recent Wall-E session(s); closed ${closed} silently-open turn(s).`);
139
+ }
140
+ } catch (e) {
141
+ error('[walle-boot-sweep] failed:', e && e.message);
142
+ }
143
+ }
144
+
145
+ // The transcript a dsh session's rows live in: the placeholder's own `jsonlPath` when the restore
146
+ // loop set one, else the D-8 path derived from the stored ACP session id.
147
+ function transcriptPathForDsh(session) {
148
+ if (!session) return '';
149
+ if (session.jsonlPath) return session.jsonlPath;
150
+ const acpSessionId = String(session.agentSessionId || (session._restoreTask && session._restoreTask.chat_session_id) || '');
151
+ if (!acpSessionId || typeof getDataDir !== 'function') return '';
152
+ return path.join(dshTranscriptDir(getDataDir()), `${acpSessionId}.jsonl`);
153
+ }
154
+
155
+ // One dsh session: read the transcript tail, and close a silently-open turn with ONE interrupted row.
156
+ async function sweepDshSessionOpenTurn(session) {
157
+ const jsonlPath = transcriptPathForDsh(session);
158
+ if (!jsonlPath) return null;
159
+ // A session whose child is LIVE right now owns its own writer and an actually-running turn; the
160
+ // sweep only ever closes a turn that died with the CTM process.
161
+ if (session._dshTranscript) return null;
162
+ const tail = await offthreadRead({
163
+ breaker: restoreInterruptionBreaker,
164
+ op: 'restoreInterruptionTail',
165
+ payload: { path: jsonlPath },
166
+ timeoutMs: 15000,
167
+ onSuccess: (res) => res || null,
168
+ onFail: () => null,
169
+ });
170
+ const entries = tail && Array.isArray(tail.entries) ? tail.entries : null;
171
+ if (!entries || !entries.length) return null;
172
+ if (!findOpenDshTurn(entries)) return null;
173
+ // Re-check liveness right before writing: this session's off-thread tail read may have raced a
174
+ // fresh wake (attach, queued Send) while it was in flight. Never slam a running turn shut.
175
+ if (session._dshTranscript) return null;
176
+ const acpSessionId = String(session.agentSessionId || (session._restoreTask && session._restoreTask.chat_session_id) || '');
177
+ if (!acpSessionId) return null;
178
+ // Reopen the SAME file append-only. This writer is throwaway: it exists only to land the one row,
179
+ // and it re-seeds its parentUuid from the file's own tail so the chain stays unbroken.
180
+ const writer = createDshTranscriptWriter({
181
+ dataDir: typeof getDataDir === 'function' ? getDataDir() : '',
182
+ acpSessionId,
183
+ cwd: session.cwd || '',
184
+ });
185
+ if (typeof writer.appendTurnInterrupted !== 'function') return null;
186
+ const row = writer.appendTurnInterrupted();
187
+ if (typeof writer.whenIdle === 'function') await writer.whenIdle();
188
+ return row || null;
189
+ }
190
+
191
+ async function runDshBootTurnSweep() {
192
+ try {
193
+ const candidates = [];
194
+ for (const session of sessions.values()) {
195
+ if (session && session.type === 'dsh' && transcriptPathForDsh(session)) candidates.push(session);
196
+ if (candidates.length >= maxSessions) break;
197
+ }
198
+ if (!candidates.length) return;
199
+ const stats = await statFilesOffThread(candidates.map((s) => transcriptPathForDsh(s)), 20000);
200
+ if (!stats) {
201
+ warn('[dsh-boot-sweep] off-thread stat unavailable — skipping this boot (next boot retries).');
202
+ return;
203
+ }
204
+ const cutoff = Date.now() - maxAgeMs;
205
+ const recent = candidates.filter((session) => {
206
+ const st = stats.get(transcriptPathForDsh(session));
207
+ return st && st.exists && Number(st.mtimeMs || 0) >= cutoff;
208
+ });
209
+ let closed = 0;
210
+ for (const session of recent) {
211
+ try {
212
+ const result = await sweepDshSessionOpenTurn(session);
213
+ if (result) closed += 1;
214
+ } catch (e) {
215
+ error('[dsh-boot-sweep] session sweep failed:', session.id, e && e.message);
216
+ }
217
+ }
218
+ if (recent.length) {
219
+ log(` [dsh-boot-sweep] checked ${recent.length} recent DeepSeek Harness session(s); closed ${closed} silently-open turn(s).`);
220
+ }
221
+ } catch (e) {
222
+ error('[dsh-boot-sweep] failed:', e && e.message);
223
+ }
224
+ }
225
+
226
+ // The one call server.js makes at boot: both non-PTY strategies, Wall-E first (unchanged order).
227
+ async function runStructuredBootTurnSweep() {
228
+ await runWalleBootTurnSweep();
229
+ await runDshBootTurnSweep();
230
+ }
231
+
232
+ return { runStructuredBootTurnSweep, runWalleBootTurnSweep, sweepWalleSessionOpenTurn, runDshBootTurnSweep, sweepDshSessionOpenTurn };
233
+ }
234
+
235
+ module.exports = { createStructuredSessionBootSweep };
@@ -101,6 +101,85 @@ function queuedWalleRuntimeInputId(item, fallbackInputId = '') {
101
101
  return String(fallbackInputId || '');
102
102
  }
103
103
 
104
+ // IR2 (design doc walle-inflight-readiness-and-replay-2026-10-27.html §5): a caller
105
+ // already joined to another in-process owner must not re-enter admission just because
106
+ // the stored decision still says proceed. Process readiness decides whether re-entry
107
+ // is permitted; the recorded phase decides the reply. Neither outcome certifies a SQL
108
+ // commit, a provider no-post boundary or cancellation authority.
109
+ function classifyInFlightSubmissionDecision(storedDecision, readiness) {
110
+ // Sent/conflict/read-error decisions are strictly further along than any local
111
+ // readiness observation and are returned untouched.
112
+ if (!storedDecision || storedDecision.action !== 'proceed') return storedDecision;
113
+ // Only a claim given up before its readiness checkpoint preserves the ordinary retry policy.
114
+ if (readiness === 'released_unready') return storedDecision;
115
+ // Unknown readiness supplies neither acceptance nor release authority.
116
+ if (readiness !== 'owner_ready') return { action: 'error', reason: 'dedup_unavailable' };
117
+ // Exact machine codes from the classifier's own inputs; never a display message,
118
+ // a suffix parse or a blanket proceed->accepted. Any other phase (pending,
119
+ // received, orphaned, missing, future) is a reconciliation-unavailable error.
120
+ switch (storedDecision.reason) {
121
+ case 'accepted_retry':
122
+ return { action: 'replay', replayStatus: 'accepted', reason: 'duplicate_replay' };
123
+ case 'rejected_retry':
124
+ case 'cancelled_retry':
125
+ return { action: 'replay', replayStatus: 'failed', reason: 'duplicate_replay' };
126
+ case 'deferred_retry':
127
+ return { action: 'replay', replayStatus: 'blocked', reason: 'duplicate_replay', retryable: true };
128
+ default:
129
+ return { action: 'error', reason: 'dedup_unavailable' };
130
+ }
131
+ }
132
+
133
+ // IR5: the pure stored-response presentation lives here (the frame module), not in
134
+ // server.js -- server.js is at its line ceiling and must not be compressed. None of
135
+ // these messages claim rollback, undo, provider liveness or a safe effect repetition.
136
+ const STORED_SUBMISSION_REPLAY_MESSAGES = Object.freeze({
137
+ sent: 'Sent (already delivered)',
138
+ accepted: 'Already accepted - still confirming...',
139
+ failed: 'The original submission has a recorded unsuccessful outcome. This replay did not start another attempt.',
140
+ blocked: 'The original submission is recorded as deferred. This replay did not start another attempt.',
141
+ });
142
+ const STORED_SUBMISSION_UNAVAILABLE_MESSAGE =
143
+ 'CTM cannot yet reconcile the original request. This replay has not started an additional attempt.';
144
+ const STORED_SUBMISSION_CONFLICT_MESSAGE =
145
+ 'This submission id was already used for different prompt text.';
146
+
147
+ function walleStoredSubmissionResponse(decision, source) {
148
+ const action = decision && typeof decision === 'object' ? decision.action : '';
149
+ if (
150
+ action === 'replay'
151
+ && Object.prototype.hasOwnProperty.call(STORED_SUBMISSION_REPLAY_MESSAGES, String(decision.replayStatus || ''))
152
+ ) {
153
+ const status = String(decision.replayStatus || '');
154
+ return {
155
+ status,
156
+ details: {
157
+ reason: decision.reason,
158
+ message: STORED_SUBMISSION_REPLAY_MESSAGES[status],
159
+ signal: status === 'sent' ? 'durable_row' : '',
160
+ source,
161
+ retryable: status === 'blocked',
162
+ },
163
+ result: { ok: true, deduplicated: true },
164
+ };
165
+ }
166
+ // Conflict keeps its own message; everything else (read error, unreadable phase,
167
+ // an unknown replay status) is reconciliation-unavailable and stays retryable.
168
+ const conflict = action === 'conflict';
169
+ const reason = conflict ? decision.reason : 'dedup_unavailable';
170
+ return {
171
+ status: 'blocked',
172
+ details: {
173
+ reason,
174
+ message: conflict ? STORED_SUBMISSION_CONFLICT_MESSAGE : STORED_SUBMISSION_UNAVAILABLE_MESSAGE,
175
+ signal: '',
176
+ source,
177
+ retryable: !conflict,
178
+ },
179
+ result: { ok: false, code: reason },
180
+ };
181
+ }
182
+
104
183
  function createInFlightSubmissionRegistry() {
105
184
  const records = new Map();
106
185
 
@@ -123,7 +202,7 @@ function createInFlightSubmissionRegistry() {
123
202
  const record = records.get(String(key || ''));
124
203
  if (!record || record.token !== token || record.resolved) return false;
125
204
  record.resolved = true;
126
- record.resolveReady();
205
+ record.resolveReady('owner_ready');
127
206
  return true;
128
207
  }
129
208
 
@@ -131,7 +210,14 @@ function createInFlightSubmissionRegistry() {
131
210
  const cleanKey = String(key || '');
132
211
  const record = records.get(cleanKey);
133
212
  if (!record || record.token !== token) return false;
134
- markReady(cleanKey, token);
213
+ // A claim released before its readiness checkpoint is the ONLY readiness that
214
+ // permits the caller's ordinary retry policy. A claim already settled as
215
+ // owner_ready keeps that first settlement (Promise single-assignment); this
216
+ // deletes its own record and nothing else.
217
+ if (!record.resolved) {
218
+ record.resolved = true;
219
+ record.resolveReady('released_unready');
220
+ }
135
221
  records.delete(cleanKey);
136
222
  return true;
137
223
  }
@@ -172,11 +258,13 @@ module.exports = {
172
258
  CTM_SUBMIT_ACK_TRUTH,
173
259
  REMOTE_SUBMISSION_OBSERVE_DELAYS_MS,
174
260
  classifyDuplicateSubmission,
261
+ classifyInFlightSubmissionDecision,
175
262
  createInFlightSubmissionRegistry,
176
263
  markerEvidence,
177
264
  queuedWalleRuntimeInputId,
178
265
  redactSubmissionPreview,
179
266
  replayStatusForEnvelopeResult,
180
267
  walleSubmissionStatusFrame,
268
+ walleStoredSubmissionResponse,
181
269
  walleStoredTurnIdForPrompt,
182
270
  };
@@ -0,0 +1,115 @@
1
+ 'use strict';
2
+
3
+ // Session hosts survive a systemd CTM restart (docs/session-host-cgroup-survival-and-durable-jobs-2026-10-31.html
4
+ // §5 D-1..D-6, §6.1). Under ctm.service (KillMode=control-group) a `detached: true` child is still in the
5
+ // unit's cgroup and is reaped when the unit stops. tmux/podman fix this by moving the child into its own
6
+ // transient .scope right after fork (StartTransientUnit with PIDs). We issue the same call through busctl
7
+ // (no Node D-Bus binding is installed) with an argv array -- no shell.
8
+
9
+ const fs = require('node:fs');
10
+ const { execFile } = require('node:child_process');
11
+ const { registerFlag, boolFlag, createEnvFlags } = require('./env-flags');
12
+
13
+ registerFlag('CTM_HOST_OWN_SCOPE', {
14
+ kind: 'bool',
15
+ default: true,
16
+ desc: 'On a systemd service host, move each session-host/pool/proxy process into its own transient scope so a CTM restart does not reap it.',
17
+ expires: null,
18
+ });
19
+
20
+ // The process-wide singleton reads process.env; a caller-injected env (tests) gets a throwaway reader.
21
+ function _ownScopeFlag(env) {
22
+ if (env === process.env || !env) return boolFlag('CTM_HOST_OWN_SCOPE', true);
23
+ return createEnvFlags({ env, warn: () => {} }).boolFlag('CTM_HOST_OWN_SCOPE', true);
24
+ }
25
+
26
+ const SCOPE_MOVE_TIMEOUT_MS = 5000; // busctl is a local bus call; the probe measured 6 ms
27
+ const SCOPE_PREFIX = 'ctm-host-'; // unit = `${SCOPE_PREFIX}${sanitize(label)}-${pid}.scope`
28
+
29
+ /** True iff this process is a systemd service (INVOCATION_ID is set by systemd for a unit's own processes). */
30
+ function isSystemdService({ platform = process.platform, env = process.env } = {}) {
31
+ return platform === 'linux' && !!(env && env.INVOCATION_ID);
32
+ }
33
+
34
+ /** True iff the scope move is enabled: a systemd service and CTM_HOST_OWN_SCOPE not explicitly falsy. */
35
+ function scopeMoveEnabled({ platform = process.platform, env = process.env } = {}) {
36
+ return isSystemdService({ platform, env }) && _ownScopeFlag(env);
37
+ }
38
+
39
+ function _sanitizeLabel(label) {
40
+ const cleaned = String(label || 'host').replace(/[^A-Za-z0-9_.:-]/g, '-').replace(/^-+|-+$/g, '').slice(0, 60);
41
+ return cleaned || 'host';
42
+ }
43
+
44
+ function _scopeArgv(unit, pid, label) {
45
+ return ['--user', 'call', 'org.freedesktop.systemd1', '/org/freedesktop/systemd1',
46
+ 'org.freedesktop.systemd1.Manager', 'StartTransientUnit', 'ssa(sv)a(sa(sv))',
47
+ unit, 'fail', '3', 'PIDs', 'au', '1', String(pid), 'CollectMode', 's', 'inactive-or-failed',
48
+ 'Description', 's', `CTM survivor: ${label}`, '0'];
49
+ }
50
+
51
+ /**
52
+ * Move `pid` into a new transient user scope. Never throws.
53
+ * @returns {Promise<{ moved: boolean, unit: string|null, reason: string|null, ms: number }>}
54
+ * reason: 'disabled' | 'not-systemd' | 'bad-pid' | 'timeout' | `exit-${code}: ${stderr}` | 'ENOENT' | `error: ...`
55
+ */
56
+ async function moveToOwnScope(pid, {
57
+ label = 'host', platform = process.platform, env = process.env,
58
+ execFileImpl = execFile, timeoutMs = SCOPE_MOVE_TIMEOUT_MS,
59
+ } = {}) {
60
+ const started = Date.now();
61
+ const fail = (reason) => ({ moved: false, unit: null, reason, ms: Date.now() - started });
62
+ try {
63
+ if (!isSystemdService({ platform, env })) return fail('not-systemd');
64
+ if (!_ownScopeFlag(env)) return fail('disabled');
65
+ const n = Number(pid);
66
+ if (!Number.isInteger(n) || n <= 1) return fail('bad-pid');
67
+ const safeLabel = _sanitizeLabel(label);
68
+ const unit = `${SCOPE_PREFIX}${safeLabel}-${n}.scope`;
69
+ return await new Promise((resolve) => {
70
+ let settled = false;
71
+ const done = (r) => { if (!settled) { settled = true; resolve(r); } };
72
+ try {
73
+ execFileImpl('busctl', _scopeArgv(unit, n, safeLabel), { timeout: timeoutMs }, (error, _stdout, stderr) => {
74
+ if (!error) return done({ moved: true, unit, reason: null, ms: Date.now() - started });
75
+ if (error.code === 'ENOENT') return done(fail('ENOENT'));
76
+ if (error.killed || error.signal === 'SIGTERM' || error.code === 'ETIMEDOUT') return done(fail('timeout'));
77
+ if (Number.isInteger(error.code)) return done(fail(`exit-${error.code}: ${String(stderr || error.message || '').trim().slice(0, 200)}`));
78
+ return done(fail(`error: ${String(error.message || error).slice(0, 200)}`));
79
+ });
80
+ } catch (err) {
81
+ // HANDLE: a synchronous spawn failure is classified like an async one; the caller reports it.
82
+ done(fail(err && err.code === 'ENOENT' ? 'ENOENT' : `error: ${String(err && err.message || err).slice(0, 200)}`));
83
+ }
84
+ });
85
+ } catch (err) {
86
+ // HANDLE: moveToOwnScope never throws; the classified reason is REPORTED by the caller.
87
+ return fail(`error: ${String(err && err.message || err).slice(0, 200)}`);
88
+ }
89
+ }
90
+
91
+ function _cgroupLine(text) {
92
+ const line = String(text || '').split('\n').find((l) => l.startsWith('0::'));
93
+ return line ? line.trim() : null;
94
+ }
95
+
96
+ /**
97
+ * Synchronous truth for the restart guard (D-5). True when not a systemd service (launchd semantics: proven),
98
+ * or when /proc/<pid>/cgroup's '0::' line differs from /proc/self/cgroup's. False on any read error.
99
+ */
100
+ function pidSurvivesServiceStop(pid, { platform = process.platform, env = process.env, readFileSync = fs.readFileSync } = {}) {
101
+ if (!isSystemdService({ platform, env })) return true;
102
+ const n = Number(pid);
103
+ if (!Number.isInteger(n) || n <= 0) return false;
104
+ try {
105
+ const mine = _cgroupLine(readFileSync('/proc/self/cgroup', 'utf8'));
106
+ const theirs = _cgroupLine(readFileSync(`/proc/${n}/cgroup`, 'utf8'));
107
+ if (!mine || !theirs) return false;
108
+ return mine !== theirs;
109
+ } catch {
110
+ // HANDLE: a dead/unreadable pid cannot survive anything -- the guard treats it as not surviving.
111
+ return false;
112
+ }
113
+ }
114
+
115
+ module.exports = { isSystemdService, scopeMoveEnabled, moveToOwnScope, pidSurvivesServiceStop, SCOPE_MOVE_TIMEOUT_MS, SCOPE_PREFIX };
@@ -213,6 +213,29 @@ function markAuthoritative(id, source) {
213
213
  authoritative.set(id, source);
214
214
  }
215
215
 
216
+ // R4 (WP-1): a duration in the owner's own units -- "<h> h <m> m" from an hour up, "<m> m" below it.
217
+ // Floored to whole minutes and never negative, so the line reads the same at 30 m as at 29.9 m.
218
+ function bgNudgeDuration(ms) {
219
+ const minutes = Math.max(0, Math.floor((Number(ms) || 0) / 60000));
220
+ return minutes >= 60 ? `${Math.floor(minutes / 60)} h ${minutes % 60} m` : `${minutes} m`;
221
+ }
222
+
223
+ /*
224
+ * R4 (WP-1): one line of evidence per quiet declared task, delivered as a Stop notice. Pure: it reads the
225
+ * entry a ledger reader produced and names the owner's own remedy (TaskStop) -- CTM itself never signals.
226
+ * The entry's label is already capped at BG_LABEL_MAX; its newlines are folded so the notice stays one line.
227
+ */
228
+ function renderBackgroundNudge(d, now = Date.now()) {
229
+ const label = String(d.label || '').replace(/\s*[\r\n]+\s*/g, ' ').trim();
230
+ const deadlineMs = Number(d.deadlineMs) > 0 ? Number(d.deadlineMs) : 0;
231
+ const firstSeenAt = Number(d.firstSeenAt) || 0;
232
+ const written = Number(d.outMtimeMs) > 0 ? `${bgNudgeDuration(now - Number(d.outMtimeMs))} ago` : 'never';
233
+ const deadlinePart = deadlineMs && firstSeenAt + deadlineMs > now
234
+ ? `Its own deadline ends it in ${bgNudgeDuration(firstSeenAt + deadlineMs - now)}.`
235
+ : 'It has no declared deadline.';
236
+ return `Background task ${d.id} has been quiet for ${bgNudgeDuration(d.quietForMs)} (started ${bgNudgeDuration(now - firstSeenAt)} ago; output ${Number(d.outSize) || 0} bytes, last written ${written}; no CPU activity seen since): ${label}. ${deadlinePart} If it is no longer needed, run TaskStop ${d.id}. If it must keep running, say why in one line.`;
237
+ }
238
+
216
239
  // `ledger` = { kind, meta } is the typed status-ledger evidence for this broadcast (session-status-truth
217
240
  // D-3/D-12). It rides the in-process message as a NON-enumerable property so the WS wire shape is unchanged.
218
241
  function broadcastStatus(id, working, source, ledger) {
@@ -660,6 +683,21 @@ function hookClaude(route, payload) {
660
683
  // After approval menu, Claude can already be idle before Stop hook arrives.
661
684
  // Force a terminal.capture so the UI gets a fresh snapshot of post-menu state.
662
685
  setTimeout(() => broadcastFn?.({ type: 'terminal.capture', id }), 500);
686
+ /*
687
+ * R4 (WP-1): the owner is asked, never killed. Once the Stop's declared set is on the ledger, list the
688
+ * entries backgroundQuiet already calls quiet and hand one evidence line per task back as a notice --
689
+ * except while the CLI itself says this Stop is already inside a continuation (stop_hook_active), where
690
+ * a nudge would feed the platform's own loop. No timer, no signal, no process inspection: it is a read.
691
+ */
692
+ const nudgedAt = Date.now();
693
+ const ledger = sessForTranscript?._statusLedger;
694
+ if (ledger && hookJson(payload, 'stop_hook_active') !== true) {
695
+ const due = ledger.backgroundNudges(nudgedAt);
696
+ if (due.length) {
697
+ ledger.noteBackgroundNudged(due.map((d) => d.id), nudgedAt);
698
+ return { matched: true, backgroundNudges: due.map((d) => ({ id: d.id, text: renderBackgroundNudge(d, nudgedAt) })) };
699
+ }
700
+ }
663
701
  return { matched: true };
664
702
  }
665
703
  if (route === 'idle' || route === 'waiting') {
@@ -862,6 +900,7 @@ module.exports = {
862
900
  getAuthoritativeSource,
863
901
  getReceiptStats,
864
902
  // exposed for tests
903
+ renderBackgroundNudge,
865
904
  _test: {
866
905
  parseAttrs, resolveSessionId, codexPendingStop, codexPendingTools,
867
906
  resolveSessionIdFromAgentToken, resolveSessionIdForRecord,