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,141 @@
1
+ 'use strict';
2
+ /**
3
+ * WP-1 root-dir watch — design doc cross-machine-session-context-2026-10-31.html
4
+ * §1.3 C-6, §5 R-10/D-9, §8 WP-1, §9 S-4.
5
+ *
6
+ * The watcher watched each *project* directory but never a provider ROOT, so a brand-new
7
+ * project dir (a fresh worktree: ~/.claude/projects/<new-slug>/) stayed unreadable until the
8
+ * 60 s scan. This module owns the ONE extra non-recursive fs.watch per canonical root that
9
+ * closes that gap — a new dir is attached in ~one debounce window instead of a scan tick.
10
+ *
11
+ * Extracted out of lib/fs-watcher.js purely to keep that file inside its file-size ratchet
12
+ * (bin/file-size-lint.js). Behaviour is identical to the in-file version, including the
13
+ * per-layout rules, the symlink/containment refusals via the watcher's own
14
+ * `_warnRefusalOnce` + the caller's `isInside`, the error→reattach after `reattachDelayMs`,
15
+ * and the `closeHandles` contract. WP-3 (§9 S-5) moved the handle itself onto the caller's
16
+ * watch client (`watcher._watch`, the watch worker thread) so open/close never blocks the main
17
+ * loop; a sync attach failure now arrives as an `'error'` with `syncAttach` instead of a throw.
18
+ * The scan is untouched and stays the backstop: a dir
19
+ * created while this watch is closed/errored is still attached by `_scanAndWatchAsync`.
20
+ *
21
+ * createRootWatch(watcher, { isInside, reattachDelayMs }) →
22
+ * { ensure(root), close(root), closeAll({ closeHandles }), handleChild(root, filename), handles,
23
+ * events }
24
+ *
25
+ * isInside — the caller's canonical containment predicate (isInside in fs-watcher.js)
26
+ * reattachDelayMs — the caller's REATTACH_DELAY_MS (same delay the per-dir watchers use)
27
+ * watcher — the JsonlWatcher this watch belongs to; this module reads `_stopped`
28
+ * and calls `emit`, `_warnRefusalOnce`, `_refreshRootAsync`,
29
+ * `_ensureWatchedDirAsync` and `_scanDatedRootStepsAsync` on it.
30
+ * events — read-only, monotonically increasing count of raw fs.watch callbacks
31
+ * (every event, before any filtering). It is the liveness probe for the
32
+ * tests: on macOS libuv keeps ONE FSEventStream per process and restarts it
33
+ * whenever a watcher is added or removed, so a test that wants to measure
34
+ * the root watch has to prove the stream is delivering — and proving it by
35
+ * attaching a probe directory would itself add a watcher and restart the
36
+ * stream. Reading this counter for a probe FILE under the root is watcher-free.
37
+ */
38
+ const fs = require('fs');
39
+ const path = require('path');
40
+
41
+ function createRootWatch(watcher, { isInside, reattachDelayMs }) {
42
+ // root object → FSWatcher. Deliberately separate from the watcher's `_watchers`/
43
+ // `_watchedDirs`, so `watchedDirsSnapshot()`/`rootHealthSnapshot()` keep meaning
44
+ // "session dirs we read from".
45
+ const handles = new Map();
46
+ // Monotonic count of raw fs.watch callbacks, bumped BEFORE any filtering so it counts every
47
+ // event the OS stream delivered for any canonical root. Exposed read-only as `events`.
48
+ let events = 0;
49
+
50
+ function close(root) {
51
+ const handle = handles.get(root);
52
+ if (!handle) return;
53
+ handles.delete(root);
54
+ try { handle.close(); } catch { /* expected: the watcher may already be closed (its own 'error' handler) */ }
55
+ }
56
+
57
+ function ensure(root) {
58
+ if (watcher._stopped || !root.canonical || handles.has(root)) return;
59
+ // WP-3 §9 S-5: the handle comes from the caller's watch client (the watch worker), so its
60
+ // close() posts instead of blocking this loop on the libuv FSEvents semaphore.
61
+ const handle = watcher._watch(root.canonical, {}, (eventType, filename) => {
62
+ events += 1; // WP-1c: count first — the callback firing is the liveness signal itself.
63
+ if (!filename || watcher._stopped) return;
64
+ handleChild(root, filename).catch((e) => watcher.emit('error', e));
65
+ });
66
+ handle.on('error', (err) => {
67
+ // WP-3 §9 S-5: a sync attach failure (root vanished) now arrives here, not as a throw —
68
+ // report it with no reattach timer, exactly like the try/catch this replaced.
69
+ if (err && err.syncAttach) { watcher.emit('error', err); return; }
70
+ // Same shape as the per-dir watchers: drop the handle, then re-resolve the root (its
71
+ // canonical target may have moved while we were down) and reattach.
72
+ close(root);
73
+ if (watcher._stopped) return;
74
+ setTimeout(() => {
75
+ if (watcher._stopped) return;
76
+ watcher._refreshRootAsync(root).then(() => ensure(root)).catch((e) => watcher.emit('error', e));
77
+ }, reattachDelayMs);
78
+ });
79
+ handles.set(root, handle);
80
+ }
81
+
82
+ // A direct child of a canonical root appeared. Per layout (mirrors what `_scanTickSteps`
83
+ // derives for that layout — see `_scanProjectsRootStepsAsync`/`_scanDatedRootStepsAsync`):
84
+ // - 'projects': attach it through the SAME path the scan and the fs.watch reattach use,
85
+ // with the same watchKey/projectEntry (`entry`, prefixed by root name for non-claude
86
+ // roots) and the same symlink + isInside containment refusals.
87
+ // - 'dated': the immediate child of the root is a YEAR dir, never a leaf day dir, so
88
+ // there is nothing to attach here; re-run the (idempotent) dated step for the root so
89
+ // a brand-new year attaches its newest leaf dirs on this tick instead of 60 s later.
90
+ // NOTE: a new day dir under an EXISTING year is not visible to a root watcher; it still
91
+ // rides the 60 s scan.
92
+ // - 'flat': the root itself is the watched dir and its *.jsonl files are already covered
93
+ // by that per-dir watcher — a sub-directory is not a session location, so nothing to do
94
+ // (and the non-directory early return above means no duplicate file events).
95
+ async function handleChild(root, filename) {
96
+ if (filename.includes(path.sep)) return; // direct children only
97
+ const child = path.join(root.canonical, filename);
98
+ let lstat;
99
+ try { lstat = await fs.promises.lstat(child); } catch { return; } // vanished again
100
+ if (lstat.isSymbolicLink()) {
101
+ watcher._warnRefusalOnce('project-symlink', child, `[jsonl-watcher] skipping symlinked project dir: ${filename}`);
102
+ return;
103
+ }
104
+ if (!lstat.isDirectory()) return;
105
+ let canonical;
106
+ try { canonical = await fs.promises.realpath(child); } catch { return; }
107
+ if (!isInside(root.canonical, canonical)) {
108
+ watcher._warnRefusalOnce('project-escape', canonical, `[jsonl-watcher] skipping project dir that escapes root: ${filename}`);
109
+ return;
110
+ }
111
+ if (root.layout === 'projects') {
112
+ const watchKey = root.name === 'claude' ? filename : `${root.name}:${filename}`;
113
+ await watcher._ensureWatchedDirAsync(root, child, watchKey, filename);
114
+ } else if (root.layout === 'dated' && /^\d{4}$/.test(filename)) {
115
+ // eslint-disable-next-line no-unused-vars
116
+ for await (const _step of watcher._scanDatedRootStepsAsync(root)) { /* no-op: drain without a tick budget */ }
117
+ }
118
+ }
119
+
120
+ // Rides the same closeHandles contract as the per-dir watchers: fast-exit callers leave the
121
+ // OS handles for the process to reclaim. The map is always cleared.
122
+ function closeAll({ closeHandles = true } = {}) {
123
+ if (closeHandles) {
124
+ for (const [root, handle] of handles) {
125
+ try { handle.close(); } catch (e) { console.error(`[jsonl-watcher] close error for root ${root.name}:`, e.message); }
126
+ }
127
+ }
128
+ handles.clear();
129
+ }
130
+
131
+ return {
132
+ ensure,
133
+ close,
134
+ closeAll,
135
+ handleChild,
136
+ handles,
137
+ get events() { return events; },
138
+ };
139
+ }
140
+
141
+ module.exports = { createRootWatch };
@@ -5,6 +5,8 @@ const EventEmitter = require('events');
5
5
  const { namedInterval, runSync, wrap } = require('./perf-tracker');
6
6
  const envFlags = require('./env-flags');
7
7
  const { isWritableOnlyByOwner } = require('../../shared/owner-write-safety');
8
+ const { createRootWatch } = require('./fs-watcher-root-watch');
9
+ const { getDefaultWatchClient } = require('./watch-worker-client');
8
10
 
9
11
  // Design doc mainloop-composer-freeze-2026-08-20.html §5 D-1, §6, §9 S-1/S-4. The 60s
10
12
  // scan tick and the attach/reattach directory walk ran completely unwrapped, so the
@@ -228,8 +230,12 @@ class JsonlWatcher extends EventEmitter {
228
230
  extraRoots = [],
229
231
  scanBudgetMs = DEFAULT_SCAN_BUDGET_MS,
230
232
  scanContinuationJitterMs = DEFAULT_SCAN_CONTINUATION_JITTER_MS,
233
+ watch = null,
231
234
  } = {}) {
232
235
  super();
236
+ // WP-3 (§5 D-4, §9 S-3): directory watchers are created in ONE worker thread
237
+ // (./watch-worker-client.js); `watch` is the injection seam (tests needing a real fs.watch).
238
+ this._watch = typeof watch === 'function' ? watch : getDefaultWatchClient().watch;
233
239
  this._rescanDirsPerTick = Math.max(1, Number(rescanDirsPerTick) || DEFAULT_RESCAN_DIRS_PER_TICK);
234
240
  this._rescanCursor = 0;
235
241
  // WP-1 async scan budget/continuation state (§9 S-1..S-4). `_scanBudgetMs` is
@@ -277,6 +283,7 @@ class JsonlWatcher extends EventEmitter {
277
283
  configuredPath: r.configuredPath || r.dir,
278
284
  enrolled: r.enrolled === true,
279
285
  allowRootSymlink: r.allowRootSymlink === true,
286
+ ensureDir: r.ensureDir === true, // a CTM-owned root its writer creates lazily (dsh): the scan tick mkdirs it
280
287
  dev: 0,
281
288
  ino: 0,
282
289
  validationStatus: r.validationStatus || 'missing',
@@ -288,6 +295,10 @@ class JsonlWatcher extends EventEmitter {
288
295
  ];
289
296
  this._watchers = new Map(); // watchKey → FSWatcher
290
297
  this._watchedDirs = new Map(); // watchKey → { dir, root, projectEntry }
298
+ // WP-1 root-dir watch: one extra fs.watch per canonical root (./fs-watcher-root-watch.js).
299
+ this._rootWatch = createRootWatch(this, { isInside, reattachDelayMs: REATTACH_DELAY_MS });
300
+ // WP-1b: watchKeys with an attach in flight — reserved before the first await (see below).
301
+ this._attaching = new Set();
291
302
  this._debounceTimers = new Map(); // filePath → timer
292
303
  this._knownFiles = new Map(); // filePath → { size, mtimeMs }
293
304
  this._refusalWarnings = new Set();
@@ -323,6 +334,7 @@ class JsonlWatcher extends EventEmitter {
323
334
  }
324
335
 
325
336
  _detachRoot(root, previousCanonical = root.canonical) {
337
+ this._rootWatch.close(root);
326
338
  for (const [watchKey, rec] of this._watchedDirs) {
327
339
  if (rec.root !== root) continue;
328
340
  const watcher = this._watchers.get(watchKey);
@@ -354,6 +366,7 @@ class JsonlWatcher extends EventEmitter {
354
366
  || previousDev !== root.dev
355
367
  || previousIno !== root.ino
356
368
  )) this._detachRoot(root, previousCanonical);
369
+ this._rootWatch.ensure(root); // WP-1: (re)attach the root-dir watcher for this canonical root
357
370
  return !!root.canonical;
358
371
  }
359
372
 
@@ -395,10 +408,10 @@ class JsonlWatcher extends EventEmitter {
395
408
  /**
396
409
  * Stop scanning and event delivery.
397
410
  * @param {{ closeHandles?: boolean }} [options]
398
- * closeHandles (default true): close every fs.watch handle.
399
- * false: leave the OS handles open for the process to reclaim at exit. ONLY for callers that
400
- * are about to exit the process (libuv fsevents.c:888: each close() blocks on a semaphore while
401
- * the shared FSEventStream is rebuilt; 417 closes took 41-360 s). _watchers/_watchedDirs are
411
+ * closeHandles (default true): close every directory watch handle. WP-3 posts those closes to
412
+ * the watch worker, so the libuv FSEvents semaphore wait no longer blocks this loop.
413
+ * false: leave the OS handles open for the process to reclaim at exit. ONLY for callers about to
414
+ * exit (each close rebuilds the shared FSEventStream; 417 closes took 41-360 s). The maps are
402
415
  * still cleared so getKnownFiles()/introspection stay consistent.
403
416
  */
404
417
  stop({ closeHandles = true } = {}) {
@@ -412,6 +425,7 @@ class JsonlWatcher extends EventEmitter {
412
425
  try { watcher.close(); } catch (e) { console.error(`[jsonl-watcher] close error for ${entry}:`, e.message); }
413
426
  }
414
427
  }
428
+ this._rootWatch.closeAll({ closeHandles }); // WP-1 root watchers: same closeHandles contract
415
429
  this._watchers.clear();
416
430
  this._watchedDirs.clear();
417
431
  }
@@ -542,6 +556,7 @@ class JsonlWatcher extends EventEmitter {
542
556
  if (this._stopped) return;
543
557
  // Late canonicalization mirrors the sync path: a root whose directory
544
558
  // didn't exist at start() attaches on the first tick that sees it.
559
+ if (root.ensureDir && !root.canonical) await fs.promises.mkdir(root.dir, { recursive: true }).catch((e) => this.emit('error', e));
545
560
  const attached = await this._refreshRootAsync(root);
546
561
  if (!attached) {
547
562
  await this._refuseRootOnceAsync(root);
@@ -577,6 +592,7 @@ class JsonlWatcher extends EventEmitter {
577
592
  || previousDev !== root.dev
578
593
  || previousIno !== root.ino
579
594
  )) this._detachRoot(root, previousCanonical);
595
+ this._rootWatch.ensure(root); // WP-1: covers roots that appear/retarget after start()
580
596
  return !!root.canonical;
581
597
  }
582
598
 
@@ -712,28 +728,32 @@ class JsonlWatcher extends EventEmitter {
712
728
  // are NOT on the budgeted periodic path (the fs.watch reattach-on-error handler
713
729
  // below) drain it to completion via `_ensureWatchedDirAsync`.
714
730
  async *_ensureWatchedDirAsyncSteps(root, dir, watchKey, projectEntry) {
715
- if (this._stopped || this._watchers.has(watchKey)) return;
716
- const rec = { dir, root, projectEntry };
731
+ if (this._stopped || this._watchers.has(watchKey) || this._attaching.has(watchKey)) return;
732
+ this._attaching.add(watchKey); // reserve synchronously: the walk below awaits before fs.watch
733
+ try {
734
+ const rec = { dir, root, projectEntry };
717
735
 
718
- // Reconcile against prior _knownFiles entries for this dir (fresh attach: none;
719
- // reattach: catches files that appeared/grew/vanished while unwatched).
720
- yield* this._attachWalkStepsAsync(dir, root, projectEntry);
736
+ // Reconcile against prior _knownFiles entries for this dir (fresh attach: none;
737
+ // reattach: catches files that appeared/grew/vanished while unwatched).
738
+ yield* this._attachWalkStepsAsync(dir, root, projectEntry);
721
739
 
722
- // Set up fs.watch (non-recursive). Symlink checks happen in _handleFileEvent.
723
- try {
740
+ // Set up the watcher (non-recursive). Symlink checks happen in _handleFileEvent.
724
741
  this._watchedDirs.set(watchKey, rec);
725
- const watcher = fs.watch(dir, (eventType, filename) => {
742
+ const watcher = this._watch(dir, {}, (eventType, filename) => {
726
743
  if (!filename || this._stopped) return;
727
744
  if (!filename.endsWith('.jsonl') && !filename.endsWith('.jsonl.bak')) return;
728
745
  const filePath = path.join(dir, filename);
729
746
  this._debouncedHandle(filePath, dir, watchKey);
730
747
  });
731
748
 
732
- watcher.on('error', () => {
733
- // Reattach on error with delay — same delay/shape as the sync path, but
734
- // resumes via the async attach so it never touches sync fs.
749
+ watcher.on('error', (err) => {
735
750
  this._watchers.delete(watchKey);
736
751
  this._watchedDirs.set(watchKey, rec);
752
+ // WP-3 §9 S-3: the sync attach throw (missing dir) is now this async 'error' — same shape
753
+ // as the catch it replaced: keep the dir recorded and report, with NO reattach timer.
754
+ if (err && err.syncAttach) { this.emit('error', err); return; }
755
+ // Reattach on error with delay — same delay/shape as the sync path, but
756
+ // resumes via the async attach so it never touches sync fs.
737
757
  try { watcher.close(); } catch { /* expected: the watcher already errored, close() may throw again harmlessly */ }
738
758
  if (!this._stopped) {
739
759
  setTimeout(() => {
@@ -743,9 +763,8 @@ class JsonlWatcher extends EventEmitter {
743
763
  });
744
764
 
745
765
  this._watchers.set(watchKey, watcher);
746
- } catch (e) {
747
- this._watchedDirs.set(watchKey, rec);
748
- this.emit('error', e);
766
+ } finally {
767
+ this._attaching.delete(watchKey);
749
768
  }
750
769
  }
751
770
 
@@ -832,7 +851,7 @@ class JsonlWatcher extends EventEmitter {
832
851
  }
833
852
 
834
853
  _ensureWatchedDir(root, dir, watchKey, projectEntry) {
835
- if (this._stopped || this._watchers.has(watchKey)) return;
854
+ if (this._stopped || this._watchers.has(watchKey) || this._attaching.has(watchKey)) return; // WP-1b: an async attach owns it
836
855
  const rec = { dir, root, projectEntry };
837
856
 
838
857
  // Reconcile against any prior _knownFiles entries for this dir. On a
@@ -899,33 +918,30 @@ class JsonlWatcher extends EventEmitter {
899
918
  if (MAINLOOP_JOB_SPANS_ENABLED) runSync('fs-watcher:attach-walk', attachWalk);
900
919
  else attachWalk();
901
920
 
902
- // Set up fs.watch (non-recursive — one watcher per directory). The
921
+ // Set up the watcher (non-recursive — one watcher per directory). The
903
922
  // actual symlink check happens in _handleFileEvent so that a file that
904
923
  // is later replaced by a symlink is still filtered.
905
- try {
906
- this._watchedDirs.set(watchKey, rec);
907
- const watcher = fs.watch(dir, (eventType, filename) => {
908
- if (!filename || this._stopped) return;
909
- if (!filename.endsWith('.jsonl') && !filename.endsWith('.jsonl.bak')) return;
910
- const filePath = path.join(dir, filename);
911
- this._debouncedHandle(filePath, dir, watchKey);
912
- });
913
-
914
- watcher.on('error', (err) => {
915
- // Reattach on error with delay
916
- this._watchers.delete(watchKey);
917
- this._watchedDirs.set(watchKey, rec);
918
- try { watcher.close(); } catch {}
919
- if (!this._stopped) {
920
- setTimeout(() => this._ensureWatchedDir(root, dir, watchKey, projectEntry), REATTACH_DELAY_MS);
921
- }
922
- });
924
+ this._watchedDirs.set(watchKey, rec);
925
+ const watcher = this._watch(dir, {}, (eventType, filename) => {
926
+ if (!filename || this._stopped) return;
927
+ if (!filename.endsWith('.jsonl') && !filename.endsWith('.jsonl.bak')) return;
928
+ const filePath = path.join(dir, filename);
929
+ this._debouncedHandle(filePath, dir, watchKey);
930
+ });
923
931
 
924
- this._watchers.set(watchKey, watcher);
925
- } catch (e) {
932
+ watcher.on('error', (err) => {
933
+ this._watchers.delete(watchKey);
926
934
  this._watchedDirs.set(watchKey, rec);
927
- this.emit('error', e);
928
- }
935
+ // WP-3 §9 S-4: the sync attach throw arrives here now — the try/catch it replaced, no timer.
936
+ if (err && err.syncAttach) { this.emit('error', err); return; }
937
+ // Reattach on error with delay
938
+ try { watcher.close(); } catch { /* expected: the watcher already errored, close() may throw again harmlessly */ }
939
+ if (!this._stopped) {
940
+ setTimeout(() => this._ensureWatchedDir(root, dir, watchKey, projectEntry), REATTACH_DELAY_MS);
941
+ }
942
+ });
943
+
944
+ this._watchers.set(watchKey, watcher);
929
945
  }
930
946
 
931
947
  // Async so the readdir/lstat run on libuv's threadpool, NOT the main event loop. On a primary
@@ -2,12 +2,28 @@
2
2
 
3
3
  const NATIVE_ORIGINS = new Set(['native-codex', 'native-claude', 'native-opencode', 'native-gemini']);
4
4
  const KNOWN_ORIGINS = new Set([...NATIVE_ORIGINS, 'routed-proxy', 'portable-transcript']);
5
- // S-3: the adapter/schema ids and the version membership test come from the one qualified table.
5
+ // S-3: the adapter/schema ids and the non-elevated cyber program set come from the one table. A
6
+ // Codex version is recorded provenance only (WP-0): the gates below refuse a missing/blank version
7
+ // string but never compare it against a qualified-version table, range or floor.
6
8
  const { CODEX_CONTROL_ADAPTER, CODEX_COMPACTION_SCHEMA_RECEIPT, CODEX_PRE_TURN_COMPACTION_SCHEMA,
7
- isQualifiedCodexVersion, isNonElevatedCyberAccessProgram } = require('./codex-harness-qualification');
9
+ isNonElevatedCyberAccessProgram } = require('./codex-harness-qualification');
10
+ // D-4 (docs/codex-0164-historical-tools-safe-handoff-2026-10-27.html §5): the ONE versioned
11
+ // proxy-filter receipt. A cross-provider Codex target may carry historical tool declarations only
12
+ // when its durable recipe proves the local authenticated proxy launch path issued this exact receipt.
13
+ const { CODEX_HISTORICAL_TOOLS_FILTER_RECEIPT } = require('./model-proxy/historical-tool-declarations');
8
14
  // S-4: the ONE portable-kind set. `native-media` is a message whose only non-text content parts are
9
15
  // input_image parts (D-2); it is portable only to a Codex target, which is the planner's job below.
10
- const PORTABLE_HISTORY_KINDS = new Set(['text', 'tool-pair', 'native-world-state', 'native-lifecycle', 'native-media']);
16
+ // `native-tool-declarations` is listed because its portability is CONDITIONAL, and the condition is
17
+ // evaluated below (routed Codex target plus the exact durable receipt); membership here only stops
18
+ // the generic `history_kind_unsupported` refusal from firing first.
19
+ // D-7 (docs/codex-upgrades-without-version-approval-2026-10-28.html §configuration-followup):
20
+ // `native-configuration-update` is the harness-authored reasoning-configuration control record.
21
+ // Codex keeps it for the native OpenAI models that advertise support and drops it from the outgoing
22
+ // request copy for non-OpenAI / unsupported-override targets -- not for all targets -- so it travels
23
+ // only to a Codex target; membership here keeps the generic refusal from firing first and the
24
+ // condition is evaluated below, exactly like `native-media`.
25
+ const PORTABLE_HISTORY_KINDS = new Set(['text', 'tool-pair', 'native-world-state', 'native-lifecycle',
26
+ 'native-media', 'native-tool-declarations', 'native-configuration-update']);
11
27
  // D-5/D-6 (docs/codex-lead-routed-switch-history-handoff-2026-10-21.html §6.2): the plaintext handoff
12
28
  // the source model writes is injected into the forked target, so it consumes target context that the
13
29
  // source estimate does not count. 32 KiB of summary text is 8_192 tokens at four characters each.
@@ -23,9 +39,10 @@ function inspectTransition({ sourceRecipe, targetRecipe, snapshot, signal } = {}
23
39
  const targetProviderId = nonempty(targetRecipe.providerId);
24
40
  const origin = nonempty(sourceRecipe.origin);
25
41
  const codexHistorySource = origin === 'native-codex' || sourceRecipe.harness === 'codex-cli';
26
- // D-3: qualification is per record (D-1) plus the installed binary that performs the fork. The
27
- // rollout's CREATOR version (session_meta.cli_version, written once at creation) is not the writer
28
- // of its later records, so it is not a gate here.
42
+ // D-3/WP-0: admission is per record (D-1) plus the adapter identity of the binary that performs
43
+ // the fork. The rollout's CREATOR version (session_meta.cli_version, written once at creation) is
44
+ // not the writer of its later records, so it is never a gate; the target wire version below is
45
+ // required provenance only, not membership in a qualified-version table.
29
46
  const qualifiedCodexHistorySource = codexHistorySource
30
47
  && sourceRecipe.controlAdapterVersion === CODEX_CONTROL_ADAPTER;
31
48
  if (!origin) return refuse('source_origin_unknown');
@@ -50,7 +67,7 @@ function inspectTransition({ sourceRecipe, targetRecipe, snapshot, signal } = {}
50
67
  && (!qualifiedCodexHistorySource
51
68
  || !isNonElevatedCyberAccessProgram(sourceRecipe.cyberAccessProgram)
52
69
  || !isNonElevatedCyberAccessProgram(snapshot.previousSettings?.cyberAccessProgram)
53
- || targetRecipe.harness !== 'codex-cli' || !isQualifiedCodexVersion(targetRecipe.harnessVersion))) {
70
+ || targetRecipe.harness !== 'codex-cli' || !nonempty(targetRecipe.harnessVersion))) {
54
71
  return refuse('native_fork_adapter_unqualified', { sourceCheckpoint });
55
72
  }
56
73
  if (qualifiedCodexHistorySource) {
@@ -75,12 +92,36 @@ function inspectTransition({ sourceRecipe, targetRecipe, snapshot, signal } = {}
75
92
  }
76
93
  }
77
94
  if (snapshot.opaque === true || has(snapshot.historyKinds, 'opaque')) return refuse('opaque_history_not_portable', { sourceCheckpoint });
95
+ // D-4: a persisted AdditionalTools declaration is its own kind and is portable ONLY to the local
96
+ // authenticated proxy Codex target whose durable recipe carries the exact versioned filter receipt
97
+ // (the target that proved it strips the source declaration before the upstream request). Anything
98
+ // else -- a non-Codex target, a route-less native target, or a missing/foreign receipt -- keeps a
99
+ // fixed refusal instead of forwarding an unexamined tool schema across providers. Same-provider
100
+ // native reuse was already decided above, and this never widens the generic opaque allowance.
101
+ // The name is deliberately in the `target_` refusal family so the existing user-facing sentence map
102
+ // (lib/session-model-action-preconditions.js, out of this package's file list) already names it.
103
+ if (has(snapshot.historyKinds, 'native-tool-declarations')
104
+ && !(targetRecipe.harness === 'codex-cli' && nonempty(targetRecipe.routeId)
105
+ && targetRecipe.historyToolFilterReceipt === CODEX_HISTORICAL_TOOLS_FILTER_RECEIPT)) {
106
+ return refuse('target_tool_declarations_filter_unqualified', { sourceCheckpoint });
107
+ }
78
108
  // D-2: native media is portable exactly when the target is Codex, which strips images the target
79
109
  // cannot accept (core/src/context_manager/normalize.rs:346 @ rust-v0.163.0). A non-Codex target
80
110
  // gets a plain refusal instead of a silent rewrite of the source history.
81
111
  if (has(snapshot.historyKinds, 'native-media') && targetRecipe.harness !== 'codex-cli') {
82
112
  return refuse('history_media_not_portable', { sourceCheckpoint });
83
113
  }
114
+ // D-7: the harness-authored reasoning-configuration control record is portable exactly when the
115
+ // target is Codex: the only harness that produced it, and the one whose request copy drops this
116
+ // item before the provider on non-OpenAI / unsupported-override targets
117
+ // (core/src/client.rs:596-599,935-939 @ rust-v0.165.0), so a routed Codex target never forwards
118
+ // it upstream. Any other target keeps a named refusal rather than a silent rewrite of the source
119
+ // history, and no replacement-history form of this record is accepted: no producer of one has been
120
+ // observed. The same-provider native reuse above already decided the matching case, and the
121
+ // earlier source/harness gates are untouched, so they can still refuse first.
122
+ if (has(snapshot.historyKinds, 'native-configuration-update') && targetRecipe.harness !== 'codex-cli') {
123
+ return refuse('history_configuration_not_portable', { sourceCheckpoint });
124
+ }
84
125
  // D-5/D-6: a sealed `compaction` item is carried by a plaintext handoff the SOURCE model writes
85
126
  // before the switch. Exactly one sealed digest can be covered, and only the native openai source
86
127
  // (whose own model can be asked to write it) can produce one; everything else keeps a named refusal.
@@ -114,12 +155,12 @@ function inspectTransition({ sourceRecipe, targetRecipe, snapshot, signal } = {}
114
155
  && targetRecipe.compactHashStatus === 'known-absent-with-native-schema-receipt'
115
156
  && targetRecipe.compactHashSchemaReceipt === CODEX_COMPACTION_SCHEMA_RECEIPT
116
157
  && /^[a-f0-9]{64}$/.test(String(targetRecipe.catalogSha256 || ''))
117
- && targetRecipe.harness === 'codex-cli' && isQualifiedCodexVersion(targetRecipe.harnessVersion);
158
+ && targetRecipe.harness === 'codex-cli' && !!nonempty(targetRecipe.harnessVersion);
118
159
  const targetHashKnownPresent = targetHash !== ''
119
160
  && targetRecipe.compactHashStatus === 'known-present-with-native-schema-receipt'
120
161
  && targetRecipe.compactHashSchemaReceipt === CODEX_COMPACTION_SCHEMA_RECEIPT
121
162
  && /^[a-f0-9]{64}$/.test(String(targetRecipe.catalogSha256 || ''))
122
- && targetRecipe.harness === 'codex-cli' && isQualifiedCodexVersion(targetRecipe.harnessVersion);
163
+ && targetRecipe.harness === 'codex-cli' && !!nonempty(targetRecipe.harnessVersion);
123
164
  if (!targetHashKnownAbsent && !targetHashKnownPresent) return refuse('target_compaction_hash_unknown', { sourceCheckpoint });
124
165
  // In pinned Codex 0.162.1, compaction for a hash change is triggered only
125
166
  // when both turns declare hashes and those hashes differ. A qualified
@@ -54,7 +54,7 @@ const {
54
54
  MIRROR_RIGHTSIZE_ENABLED,
55
55
  normalizeSnapshotScrollbackRows,
56
56
  } = require('./terminal-snapshot-options');
57
- const { createFrameEmitter, EMPTY_GRID } = require('./state-sync/frame-emitter'); const { computeAnsiPatch } = require('./state-sync/cell-diff');
57
+ const { createFrameEmitter } = require('./state-sync/frame-emitter'); // WP-1: EMPTY_GRID/computeAnsiPatch moved into terminal-bootstrap.js's keyframeBootstrapAnsi (one diff core, one empty grid)
58
58
  const HEADLESS_VIEWPORT_CONTRACT_VERSION = 2; // WP-1/D-1: getViewportText wire-contract version
59
59
  const { noteOutput: _stateSyncNoteOutput, isFlooding: _stateSyncIsFlooding, settleDelay: _stateSyncSettleDelay } = require('./state-sync/frame-rate');
60
60
  const { serializeRow, serializeRowState, wrapRowAnsi, ROW_DIGEST_ALGORITHM } = require('./state-sync/row-serializer');
@@ -75,7 +75,7 @@ const { composerSnapshotFromTerm, boundedBufferTokens } = require('./image-compo
75
75
  const { createErrorSink } = require('./error-sink');
76
76
  const { createPendingClientRegistry } = require('./state-sync/pending-client-registry');
77
77
  const { createApprovalTrailingRecheck } = require('./approval-trailing-recheck');
78
- const { composeTerminalBootstrapResult, truncateBootstrapAnsi } = require('./terminal-bootstrap');
78
+ const { composeTerminalBootstrapResult, truncateBootstrapAnsi, keyframeBootstrapAnsi } = require('./terminal-bootstrap');
79
79
  const { createInFlightOps } = require('./headless-inflight-ops'); // WP-0 (D-6): per-session in-flight op table
80
80
 
81
81
  let postMessage = () => {};
@@ -214,6 +214,10 @@ function _deliverKeyframeRequest(msg) {
214
214
  requestToken: String(msg.requestToken || '').slice(0, 128),
215
215
  contentRecoveryEpoch: Math.max(0, Number(msg.contentRecoveryEpoch || 0)),
216
216
  ...(serverHealAttempt >= 1 ? { serverHealAttempt } : {}),
217
+ // WP-2 (D-6, §9 S-8): thread the client's committed baseline seq to the emitter when the relay
218
+ // carried a valid one; an absent haveSeq stays absent (the emitter then serves every request,
219
+ // exactly as before this WP).
220
+ ...(Number.isSafeInteger(msg.haveSeq) && msg.haveSeq >= 0 ? { haveSeq: msg.haveSeq } : {}),
217
221
  };
218
222
  const delivered = _stateSyncEmitter.onKeyframeReq(msg.sessionId, reqCtx);
219
223
  // WP-2 (state-sync-delivery-path-reset-2026-09-18.html §5): onKeyframeReq never refuses and
@@ -438,6 +442,19 @@ const _stateSyncEmitter = createFrameEmitter({
438
442
  if (!ids) return;
439
443
  _noteWorkerTypingStage(id, ids, 'emit-refused', { gate, ...fields });
440
444
  },
445
+ // WP-0 (S-11, docs/state-sync-pipelined-deltas-echo-never-waits-for-ack-2026-10-31.html §5 D-5,
446
+ // §9 S-11): report ONE matched ack's in-flight round trip -- the host leg measured in the emitter,
447
+ // the main leg passed through from the server's ledger lookup -- against whichever interactions
448
+ // are still pending on this session's mirror. `inflight-acked` is an OPTIONAL trace stage, so it
449
+ // can never move an interaction's completed boundary (lib/typing-interaction-trace.js).
450
+ onAcked: (id, info) => {
451
+ const ids = _pendingTypingInteractionIds(terminals.get(id));
452
+ if (!ids) return;
453
+ _noteWorkerTypingStage(id, ids, 'inflight-acked', {
454
+ inFlightSeq: info.seq, hostRttMs: info.hostRttMs, mainRttMs: info.mainRttMs,
455
+ windowFrames: info.windowFrames,
456
+ });
457
+ },
441
458
  // WP-5 (§9 S-8/S-9): surface the no-audience stand-down latch in ctm.log the same way the W5c
442
459
  // ack-stall warning below does (console.warn from this worker is captured like any other log line).
443
460
  log: (event, data) => {
@@ -477,25 +494,81 @@ const _stateSyncPendingClearScrollback = new Set(); // sessionIds whose next fra
477
494
  // Lost-ack watchdog: the emitter's diff base only advances on a client ACK, so a frame whose ACK never
478
495
  // reaches the worker would pin the session in-flight forever and freeze its terminal. Periodically sweep
479
496
  // for frames stuck in flight past STATE_SYNC_INFLIGHT_STALL_MS and force a fresh keyframe (full repaint).
497
+ // WP-2 (D-5, docs/remote-relay-ack-rate-limit-typing-delay-2026-10-28.html §5 D-5, §9 S-10): the stall
498
+ // is now PER DELIVERY — each delivery's own measured ack RTT sizes its own watchdog (`stallMsFor`,
499
+ // RFC 6298 §2 SRTT/RTTVAR). This constant is now the CEILING (unchanged value, unchanged env
500
+ // override), and the floor is the private-low-RTT-path bound below (mosh MIN_RTO intuition).
480
501
  const STATE_SYNC_INFLIGHT_STALL_MS = Math.max(
481
502
  STATE_SYNC_FRAME_MAX_MS * 4,
482
503
  Number(process.env.CTM_STATE_SYNC_INFLIGHT_STALL_MS) || 3000,
483
504
  );
484
- const STATE_SYNC_INFLIGHT_SWEEP_MS = Math.max(250, Math.floor(STATE_SYNC_INFLIGHT_STALL_MS / 3));
505
+ const STATE_SYNC_INFLIGHT_STALL_FLOOR_MS = Math.max(50, Number(require('./env-flags').intFlag('CTM_STATE_SYNC_INFLIGHT_STALL_FLOOR_MS', 250)));
506
+ // The recovery sweep runs every 100 ms (was max(250, stall/3) = 1000 ms), so an adaptive stall at the
507
+ // 250 ms floor still recovers within 350 ms of the send instead of 3–4 s.
508
+ const STATE_SYNC_INFLIGHT_SWEEP_MS = 100;
485
509
  // W5c: a session whose client ack stream is dead gets NO benefit from sweepStalledInFlight's
486
510
  // per-frame recovery (it keeps re-arming inFlightAt every sweep, forever masking the stall) — the
487
511
  // browser mirror can freeze for minutes with zero console output (production incident 2026-07-12).
488
- // Query listStalledSessions alongside the recovery sweep and surface a throttled warning.
512
+ // Query listStalledSessions on its own (unchanged) 1000 ms cadence and surface a throttled warning.
513
+ const STATE_SYNC_STALL_LIST_MS = 1000;
489
514
  const _stalledSessionWarnings = createStallWarningRegistry();
490
515
  const STATE_SYNC_STALL_WARN_THROTTLE_MS = 60000;
516
+
517
+ // WP-0 (D-4c, docs/remote-relay-ack-rate-limit-typing-delay-2026-10-28.html §5 D-4c, §9 S-10):
518
+ // every lost-ack recovery the sweep performs is reported to main, throttled per session per 30 s
519
+ // with the count accumulated in the window (the first recovery reports immediately, mirroring
520
+ // lib/remote-machine-link.js's _noteUndelivered). Diagnostics only — the recovery itself has
521
+ // already happened when this runs.
522
+ const STATE_SYNC_INFLIGHT_RECOVERED_THROTTLE_MS = 30000;
523
+ const _inflightRecovered = new Map(); // sessionId -> { count, hiddenCount, inFlightKind, lastInFlightAgeMs, stallMs, lastReportMs }
524
+ function _noteInflightRecovered(sessionId, info) {
525
+ if (!sessionId) return;
526
+ const now = Date.now();
527
+ let entry = _inflightRecovered.get(sessionId);
528
+ if (!entry) {
529
+ entry = { count: 0, hiddenCount: 0, inFlightKind: '', lastInFlightAgeMs: 0, stallMs: 0, lastReportMs: null };
530
+ _inflightRecovered.set(sessionId, entry);
531
+ }
532
+ entry.count += 1;
533
+ entry.hiddenCount += info && info.hidden ? 1 : 0;
534
+ entry.inFlightKind = String((info && info.inFlightKind) || '');
535
+ entry.lastInFlightAgeMs = Number((info && info.inFlightAgeMs) || 0);
536
+ entry.stallMs = Number((info && info.stallMs) || 0);
537
+ if (entry.lastReportMs != null && now - entry.lastReportMs < STATE_SYNC_INFLIGHT_RECOVERED_THROTTLE_MS) return;
538
+ postMessage({
539
+ type: 'state-sync-inflight-recovered',
540
+ sessionId,
541
+ count: entry.count,
542
+ inFlightKind: entry.inFlightKind,
543
+ lastInFlightAgeMs: entry.lastInFlightAgeMs,
544
+ stallMs: entry.stallMs,
545
+ hiddenCount: entry.hiddenCount,
546
+ });
547
+ entry.count = 0;
548
+ entry.hiddenCount = 0;
549
+ entry.lastReportMs = now;
550
+ }
551
+
491
552
  if (_stateSyncEmitter) {
492
- const _stalledSweep = setInterval(() => {
553
+ // WP-2 (D-5, §9 S-10): the recovery sweep runs on its own 100 ms cadence so a per-delivery stall at
554
+ // the 250 ms floor is noticed within one sweep of elapsing (recovered ≤ 350 ms after send), not on
555
+ // the old 1000 ms tick. `stallMs` is the ceiling; `floorMs` bounds a fast delivery from below.
556
+ const _recoverySweep = setInterval(() => {
493
557
  try {
494
558
  _stateSyncEmitter.sweepStalledInFlight(Date.now(), {
495
559
  stallMs: STATE_SYNC_INFLIGHT_STALL_MS,
560
+ floorMs: STATE_SYNC_INFLIGHT_STALL_FLOOR_MS,
496
561
  hasViewers: (id) => _mirrorVisibility(id) === 'visible',
562
+ onRecovered: (id, info) => _noteInflightRecovered(id, info),
497
563
  });
498
564
  } catch {}
565
+ }, STATE_SYNC_INFLIGHT_SWEEP_MS);
566
+ if (typeof _recoverySweep.unref === 'function') _recoverySweep.unref();
567
+
568
+ // W5c: listStalledSessions keeps its own (unchanged) 1000 ms cadence and 3×-ceiling warning
569
+ // semantics. A dead ack stream gets no benefit from the recovery sweep above (it keeps re-arming
570
+ // inFlightAt every sweep, forever masking the stall) — this is the separate, louder signal.
571
+ const _stalledList = setInterval(() => {
499
572
  try {
500
573
  const stalls = _stateSyncEmitter.listStalledSessions(Date.now(), { stallMs: STATE_SYNC_INFLIGHT_STALL_MS });
501
574
  const now = Date.now();
@@ -540,8 +613,8 @@ if (_stateSyncEmitter) {
540
613
  }
541
614
  _stalledSessionWarnings.reconcile(activeWarningKeys);
542
615
  } catch {}
543
- }, STATE_SYNC_INFLIGHT_SWEEP_MS);
544
- if (typeof _stalledSweep.unref === 'function') _stalledSweep.unref();
616
+ }, STATE_SYNC_STALL_LIST_MS);
617
+ if (typeof _stalledList.unref === 'function') _stalledList.unref();
545
618
  }
546
619
 
547
620
  // Sessions mid-"repaint nudge": the server resizes the PTY+mirror to cols-1 (to force a SIGWINCH so a
@@ -1630,6 +1703,9 @@ async function handleMessage(msg) {
1630
1703
  _stateSyncEmitter.onAck(msg.sessionId, Number(msg.seq || 0), {
1631
1704
  clientId: msg.clientId || '', socketGeneration: Number(msg.socketGeneration || 0),
1632
1705
  holdCatchUp,
1706
+ // WP-0 (S-11): the main process measured this ack's main-side round trip on the ledger's
1707
+ // own sentAt; -1 when it had no evidence. Report-only, forwarded to the typing trace.
1708
+ mainRttMs: Number.isFinite(Number(msg.mainRttMs)) ? Number(msg.mainRttMs) : -1,
1633
1709
  });
1634
1710
  if (holdCatchUp) _stateSyncScheduleHeldEmit(msg.sessionId);
1635
1711
  }
@@ -1895,7 +1971,7 @@ async function handleMessage(msg) {
1895
1971
  const keyframe = Number(msg.clientBufferLength || 0) > Number(entry.term.rows || 0);
1896
1972
  if (keyframe) bootstrapMode = 'keyframe';
1897
1973
  const sourceAnsi = keyframe
1898
- ? computeAnsiPatch(EMPTY_GRID, extractGrid(msg.sessionId, { scrollbackRows: STATE_SYNC_SCROLLBACK_ROWS }), { keyframe: true }).ansi
1974
+ ? keyframeBootstrapAnsi(extractGrid(msg.sessionId, { scrollbackRows: STATE_SYNC_SCROLLBACK_ROWS }))
1899
1975
  : data;
1900
1976
  // WP-3: byte-budget probe only (server.js builds the wire result from `bootstrap` below);
1901
1977
  // gridRevision/surfaceEpoch are required by the schema, so 0 placeholders satisfy it.