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,5 +1,8 @@
1
1
  'use strict';
2
2
 
3
+ // D-2 (docs/lead-model-id-rule-at-tilde-prefix-2026-10-28.html): a routed wire model id obeys the ONE
4
+ // model-id rule; a stricter copy here silently dropped the recipe of a `~vendor/x-latest` lead.
5
+ const { isValidOverrideModel } = require('./model-id-rule');
3
6
  const RECIPE_ENV_KEY = 'CTM_MODEL_EXECUTION_RECIPE';
4
7
  const MAX_RECIPE_BYTES = 4096;
5
8
  const RECIPE_KEYS = Object.freeze([
@@ -12,7 +15,6 @@ const RECIPE_KEYS = Object.freeze([
12
15
  const OPTIONAL_RECIPE_KEYS = Object.freeze(['sourceEnvironmentReference', 'nativeConfigReceipt']);
13
16
  const SOURCE_ENVIRONMENT_REFERENCES = new Set(['captured-empty', 'private-owner-required']);
14
17
  const ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
15
- const CANONICAL_MODEL_ID_PATTERN = /^[A-Za-z0-9@][A-Za-z0-9@._:/+-]{0,255}$/;
16
18
  const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
17
19
  const SHA256_PATTERN = /^[0-9a-f]{64}$/;
18
20
 
@@ -28,10 +30,6 @@ function boundedId(value) {
28
30
  return typeof value === 'string' && ID_PATTERN.test(value);
29
31
  }
30
32
 
31
- function boundedCanonicalModelId(value) {
32
- return typeof value === 'string' && CANONICAL_MODEL_ID_PATTERN.test(value);
33
- }
34
-
35
33
  function validThreadId(value) {
36
34
  return value === null || (typeof value === 'string' && Buffer.byteLength(value, 'utf8') <= 256
37
35
  && (UUID_PATTERN.test(value) || /^[A-Za-z0-9][A-Za-z0-9._:/+-]{0,255}$/.test(value)));
@@ -57,7 +55,7 @@ function sanitizedRecipe(value, minimumGeneration = 1) {
57
55
  const native = value.routeId === null;
58
56
  if (native) {
59
57
  if (value.wireModelId !== null || value.profileFingerprint !== null || value.factsRevision !== null) return undefined;
60
- } else if (!boundedId(value.routeId) || !boundedCanonicalModelId(value.wireModelId)
58
+ } else if (!boundedId(value.routeId) || !isValidOverrideModel(value.wireModelId)
61
59
  || typeof value.profileFingerprint !== 'string' || !SHA256_PATTERN.test(value.profileFingerprint)
62
60
  || typeof value.factsRevision !== 'string' || !SHA256_PATTERN.test(value.factsRevision)) return undefined;
63
61
 
@@ -1,16 +1,19 @@
1
1
  'use strict';
2
2
 
3
- const { modelProcessGenerationMetadata, modelStorageRootsMetadata } = require('./host-handoff');
3
+ const { modelProcessGenerationMetadata, modelStorageRootsMetadata, launchEnvironmentMetadata } = require('./host-handoff');
4
4
  const { modelExecutionRecipeMetadata } = require('./model-execution-recipe-metadata');
5
5
 
6
- function applyModelHostReceipt(target, { sessionId, modelProcessGeneration, modelStorageRoots, modelExecutionRecipe } = {}) {
6
+ function applyModelHostReceipt(target, { sessionId, modelProcessGeneration, modelStorageRoots, modelExecutionRecipe, launchEnvironment } = {}) {
7
7
  if (!target || typeof target !== 'object') throw new TypeError('model host receipt target is required');
8
8
  delete target.modelExecutionRecipe;
9
9
  delete target._modelExecutionRecipe;
10
+ // WP-1 (S-12): a receipt without a valid record clears any stale one -- a host reply is authoritative.
11
+ delete target.launchEnvironment;
10
12
  Object.assign(target,
11
13
  modelProcessGenerationMetadata(modelProcessGeneration),
12
14
  modelStorageRootsMetadata(modelStorageRoots),
13
- modelExecutionRecipeMetadata(modelExecutionRecipe, { sessionId, processGeneration: modelProcessGeneration }));
15
+ modelExecutionRecipeMetadata(modelExecutionRecipe, { sessionId, processGeneration: modelProcessGeneration }),
16
+ launchEnvironmentMetadata(launchEnvironment));
14
17
  return target;
15
18
  }
16
19
 
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ // The ONE model-id rule (docs/lead-model-id-rule-at-tilde-prefix-2026-10-28.html D-1/D-2). A model id
4
+ // rides argv as the operand of --model / -m, so the only argv hazard is a value the CLI would parse as
5
+ // an option: a leading '-' (CWE-88). The first character is a letter, a digit, '@' (Portkey model
6
+ // catalog: @slug/model) or '~' (OpenRouter latest alias: ~vendor/x-latest), never '-'. The agent is
7
+ // spawned with an argv array and no shell, so '~' is never tilde-expanded.
8
+ //
9
+ // A leaf module that requires nothing: the session host validates recipes with it, and its closure is
10
+ // bounded (tests-integration/host-socket-protocol.test.js), so it must not drag in launch-model-override's
11
+ // Claude argv helpers.
12
+ const MODEL_ID_RE = /^[A-Za-z0-9@~][A-Za-z0-9._:/@+~\-[\]]*$/;
13
+ const MAX_MODEL_ID_LENGTH = 200;
14
+
15
+ function isValidOverrideModel(model) {
16
+ return typeof model === 'string' && model.length > 0 && model.length <= MAX_MODEL_ID_LENGTH && MODEL_ID_RE.test(model);
17
+ }
18
+
19
+ module.exports = { isValidOverrideModel };
@@ -96,6 +96,10 @@ function anthropicErrorType(kind) {
96
96
  // must not collapse into api_error -- a permission denial is not an upstream fault.
97
97
  if (kind === 'model_not_invocable') return 'invalid_request_error';
98
98
  if (kind === 'model_access_denied') return 'permission_error';
99
+ // WP-1b (§12a rule 2): `model_not_found` split out of `invalid_prompt`, and the HTTP status is
100
+ // still the upstream one (404), so the client-visible envelope must be the one `invalid_prompt`
101
+ // produced for that status -- type `invalid_request_error`, status 404.
102
+ if (kind === 'model_not_found') return 'invalid_request_error';
99
103
  return 'api_error';
100
104
  }
101
105
 
@@ -4,12 +4,17 @@
4
4
  // the two protocol-boundary decisions the installed Claude client's server-side
5
5
  // auto-mode review needs, kept as one cohesive, side-effect-free leaf.
6
6
  //
7
- // * The client sends an own top-level `safeguards` field and, when a gateway
8
- // answers 400 with a message naming safeguards, retries through its own billed
9
- // client-side classifier. A route CTM is translating to a chat wire cannot
10
- // carry that field, so it negotiates honestly (D-2) instead of silently
11
- // dropping it and letting the request reach an upstream that cannot run the
12
- // review. Native (passthrough) routes keep the original request untouched.
7
+ // * The client asks for server-side review with an own top-level `safeguards`
8
+ // field and/or a `dangerous-tool-use-` beta token. A route CTM is translating
9
+ // to a chat wire cannot carry that, so it negotiates honestly (D-2) instead of
10
+ // silently dropping it and letting the request reach an upstream that cannot run
11
+ // the review. The wording of that 400 is MEASURED (Claude Code 2.1.314,
12
+ // docs/auto-mode-native-lead-subagent-latch-2026-10-30.html §1.2): a message
13
+ // NAMING safeguards/dangerous-tool-use makes the client latch server review OFF
14
+ // for its whole SESSION (a subagent refusal poisons the lead process), while the
15
+ // registry's wording (lib/model-proxy/refusal-scope.js) is scoped to that one
16
+ // CONVERSATION. Never name those tokens here -- see refusal-scope.js and the doc.
17
+ // Native (passthrough) routes keep the original request untouched.
13
18
  // * A caller-provided `anthropic-beta` and the route's configured `anthropic-beta`
14
19
  // must be UNIONED, never replaced (D-5): a configured route could otherwise
15
20
  // overwrite the client's dangerous-tool-use / future extension tokens.
@@ -29,9 +34,15 @@
29
34
  // is checked BEFORE a chunk is retained. A chunk that does not fit is never held and
30
35
  // immediately drops everything already held, so a crossing chunk can never be parsed
31
36
  // and the observation stays "unobserved" instead of turning into a false negative.
37
+ //
38
+ // WP-0 (docs/auto-mode-native-lead-subagent-latch-2026-10-30.html §8) adds the REQUEST-side half of the
39
+ // same line: `class=` (what kind of Claude Code request this was, read from the client's own headers, so
40
+ // a lead that keeps sending subagent-class requests is visible) and the three fields later WPs fill
41
+ // (`review=`, `refusal=`, `scope=`). It also names the native-lead lane (`path=native-lead`) and the one
42
+ // predicate deciding whether a request asked for the server-side review at all (`serverReviewAsked`).
43
+
44
+ const { refusalEntry, refusalResponse } = require('./refusal-scope');
32
45
 
33
- const SAFEGUARDS_UNSUPPORTED_MESSAGE =
34
- 'safeguards: server-side auto-mode review is unsupported by this translated route; use the client-side classifier.';
35
46
  const BETA_HEADER = 'anthropic-beta';
36
47
 
37
48
  function _isPlainObject(value) {
@@ -54,20 +65,18 @@ function reviewRequested(body) {
54
65
  /**
55
66
  * The honest negotiation for a translated route: `mode` is the transport
56
67
  * routes.selectUpstream already selected for this exact request, so only
57
- * 'chat-translate' (the mode that cannot represent the field) refuses.
68
+ * 'chat-translate' (the mode that cannot represent the field) refuses. The
69
+ * wording is the registry's conversation-scoped refusal (refusal-scope.js),
70
+ * never a literal here -- a 400 that names the review tokens makes the client
71
+ * latch server review off for its whole session (§1.2).
58
72
  * @param {unknown} body
59
73
  * @param {string|null|undefined} mode
74
+ * @param {unknown} headers
60
75
  * @returns {{status:number, body:{type:string, error:{type:string, message:string}}}|null}
61
76
  */
62
- function unsupportedSafeguards(body, mode) {
63
- if (mode !== 'chat-translate' || !reviewRequested(body)) return null;
64
- return {
65
- status: 400,
66
- body: {
67
- type: 'error',
68
- error: { type: 'invalid_request_error', message: SAFEGUARDS_UNSUPPORTED_MESSAGE },
69
- },
70
- };
77
+ function unsupportedSafeguards(body, mode, headers) {
78
+ if (mode !== 'chat-translate' || !serverReviewAsked(body, headers)) return null;
79
+ return refusalResponse(refusalEntry('claude-code', 'server-review'));
71
80
  }
72
81
 
73
82
  // One header map contributes only its `anthropic-beta` values (any spelling),
@@ -88,6 +97,49 @@ function _betaTokens(source) {
88
97
  return tokens;
89
98
  }
90
99
 
100
+ // One header value by case-insensitive name, or `undefined`. Node lower-cases incoming header names,
101
+ // but a caller-built map may spell them any way, so the lookup never relies on that.
102
+ function _headerValue(source, name) {
103
+ if (!_isPlainObject(source)) return undefined;
104
+ for (const [key, value] of Object.entries(source)) {
105
+ if (key.toLowerCase() === name) return value;
106
+ }
107
+ return undefined;
108
+ }
109
+
110
+ /**
111
+ * The request class the CLIENT stated for itself, read only from the two headers Claude Code sets:
112
+ * `x-claude-code-request-class` (present only when the gateway hint headers are on) and
113
+ * `x-claude-code-agent-id` (always non-empty on a subagent request).
114
+ *
115
+ * Closed by construction: the stated class is returned only when it is EXACTLY a member of
116
+ * REQUEST_CLASSES, an agent-id-bearing request with no valid stated class is 'subagent', and anything
117
+ * else -- an unrecognized class, a whitespace-only agent id, a non-object header map -- is 'unknown'.
118
+ * No caller-supplied string is ever returned or interpolated.
119
+ * @param {unknown} headers
120
+ * @returns {string} one of REQUEST_CLASSES, or 'unknown'
121
+ */
122
+ function requestClassFromHeaders(headers) {
123
+ const stated = _headerValue(headers, REQUEST_CLASS_HEADER);
124
+ if (_label(stated, REQUEST_CLASSES, null) !== null) return stated;
125
+ const agentId = _headerValue(headers, AGENT_ID_HEADER);
126
+ if (typeof agentId === 'string' && agentId.trim() !== '') return REQUEST_CLASS_SUBAGENT;
127
+ return REVIEW_LABEL_UNKNOWN;
128
+ }
129
+
130
+ /**
131
+ * Did this very request ask for the SERVER-SIDE review? Two client-side spellings mean the same thing
132
+ * and both are answered from this one predicate: the own top-level `safeguards` field (`reviewRequested`)
133
+ * and an `anthropic-beta` token in the `dangerous-tool-use-` family (`_betaTokens`). The nested,
134
+ * unrelated uses of the word -- a tool result, a message content block -- are not this feature.
135
+ * @param {unknown} body
136
+ * @param {unknown} headers
137
+ * @returns {boolean}
138
+ */
139
+ function serverReviewAsked(body, headers) {
140
+ return reviewRequested(body) || _betaTokens(headers).some((token) => token.startsWith(DANGEROUS_TOOL_USE_BETA_PREFIX));
141
+ }
142
+
91
143
  /**
92
144
  * Union the beta tokens of every source map, in CALL ORDER, then keep any beta token the
93
145
  * destination already carries that no source named (appended last, so an unnamed caller's
@@ -129,10 +181,24 @@ function mergeAnthropicBetas(headers, ...sources) {
129
181
  // The observation vocabulary is CLOSED. Every value below is normalized through these
130
182
  // lists before it can reach the log line, so an unknown/fallback/hostile value degrades to
131
183
  // a fixed label instead of being interpolated.
132
- const REVIEW_PATH_KINDS = ['legacy', 'routed'];
184
+ const REVIEW_PATH_KINDS = ['legacy', 'routed', 'native-lead'];
133
185
  const REVIEW_TRANSPORTS = ['native', 'translated'];
134
186
  const REVIEW_RESULTS = ['present', 'absent', 'unobserved', 'unsupported'];
135
187
  const REVIEW_COMPLETIONS = ['complete', 'incomplete', 'unobserved'];
188
+ // WP-0 (docs/auto-mode-native-lead-subagent-latch-2026-10-30.html §8): the REQUEST-SIDE labels. A
189
+ // request class is what the Claude client itself stated (its own `x-claude-code-request-class`
190
+ // header, sent only when the gateway hint headers are on), never something inferred from a body; an
191
+ // agent-id-bearing request with no stated class is a subagent, and everything else is `unknown`.
192
+ const REQUEST_CLASSES = ['main', 'subagent', 'workflow', 'compaction', 'auxiliary', 'side_reply'];
193
+ const REQUEST_CLASS_SUBAGENT = 'subagent';
194
+ const REQUEST_CLASS_HEADER = 'x-claude-code-request-class';
195
+ const AGENT_ID_HEADER = 'x-claude-code-agent-id';
196
+ // The prefix of the `anthropic-beta` token the client sends when it wants the server-side review on
197
+ // its own request (the same capability the own top-level `safeguards` field asks for).
198
+ const DANGEROUS_TOOL_USE_BETA_PREFIX = 'dangerous-tool-use-';
199
+ const REVIEW_DECISIONS = ['requested', 'not_requested'];
200
+ const REVIEW_REFUSALS = ['conversation', 'none'];
201
+ const REVIEW_SCOPES = ['verified', 'unverified', 'unknown', 'none'];
136
202
  const REVIEW_LABEL_UNKNOWN = 'unknown';
137
203
  const REVIEW_LABEL_UNOBSERVED = 'unobserved';
138
204
  // Same bound as the routed native usage side copy (§6, anthropic-face.js): one
@@ -165,15 +231,34 @@ function _boundedStatus(status) {
165
231
  * A result is only ever present/absent together with completion=complete: the two axes are
166
232
  * one claim about the observation, never a claim about a tool decision or a billing effect.
167
233
  *
168
- * @param {{pathKind?:string,transport?:string,status?:number,results?:string,completion?:string}} fields
234
+ * The four REQUEST-side labels append after `completion=`, always, in this fixed order:
235
+ *
236
+ * class the client's own stated request class (see requestClassFromHeaders); 'unknown' when it
237
+ * stated none, or stated one outside the closed set
238
+ * review 'requested' the request itself asked for the server-side review (own `safeguards`
239
+ * field, or a `dangerous-tool-use-` beta token); 'not_requested' otherwise. The default
240
+ * is 'requested', so a caller that reports a review cycle without saying cannot
241
+ * under-report one.
242
+ * refusal 'conversation' a conversation-scoped refusal was applied; 'none' otherwise
243
+ * scope 'verified' | 'unverified' | 'unknown' | 'none' -- the scope a later WP fills
244
+ *
245
+ * Like every other field these are normalized through closed lists: a caller-supplied string is never
246
+ * echoed, only ever replaced by the documented default.
247
+ *
248
+ * @param {{pathKind?:string,transport?:string,status?:number,results?:string,completion?:string,
249
+ * requestClass?:string,review?:string,refusal?:string,scope?:string}} fields
169
250
  * @returns {string} a string built only from the fixed labels above
170
251
  */
171
- function reviewDiagnostic({ pathKind, transport, status, results, completion } = {}) {
252
+ function reviewDiagnostic({ pathKind, transport, status, results, completion, requestClass, review, refusal, scope } = {}) {
172
253
  return `[model-proxy] auto_review path=${_label(pathKind, REVIEW_PATH_KINDS, REVIEW_LABEL_UNKNOWN)}`
173
254
  + ` transport=${_label(transport, REVIEW_TRANSPORTS, REVIEW_LABEL_UNKNOWN)}`
174
255
  + ` status=${_boundedStatus(status)}`
175
256
  + ` results=${_label(results, REVIEW_RESULTS, REVIEW_LABEL_UNOBSERVED)}`
176
- + ` completion=${_label(completion, REVIEW_COMPLETIONS, REVIEW_LABEL_UNOBSERVED)}`;
257
+ + ` completion=${_label(completion, REVIEW_COMPLETIONS, REVIEW_LABEL_UNOBSERVED)}`
258
+ + ` class=${_label(requestClass, REQUEST_CLASSES, REVIEW_LABEL_UNKNOWN)}`
259
+ + ` review=${_label(review, REVIEW_DECISIONS, 'requested')}`
260
+ + ` refusal=${_label(refusal, REVIEW_REFUSALS, 'none')}`
261
+ + ` scope=${_label(scope, REVIEW_SCOPES, 'none')}`;
177
262
  }
178
263
 
179
264
  /**
@@ -332,8 +417,10 @@ function trackDownstreamClose(req, res) {
332
417
  }
333
418
 
334
419
  module.exports = {
335
- SAFEGUARDS_UNSUPPORTED_MESSAGE,
336
420
  reviewRequested,
421
+ serverReviewAsked,
422
+ requestClassFromHeaders,
423
+ REQUEST_CLASSES,
337
424
  unsupportedSafeguards,
338
425
  mergeAnthropicBetas,
339
426
  MAX_OBSERVED_JSON_BYTES,
@@ -23,6 +23,9 @@ function createChatResponseTranslators({ normalizeUpstreamOutcome, firstString,
23
23
  // WP-2 D-5: same two mappings as anthropic-face.js (HTTP 400 / 403 respectively).
24
24
  if (kind === 'model_not_invocable') return 'invalid_request_error';
25
25
  if (kind === 'model_access_denied') return 'permission_error';
26
+ // WP-1b (§12a rule 2): `model_not_found` maps exactly as `invalid_prompt` did, so a client that
27
+ // saw `invalid_request_error` on an upstream 404 still sees it.
28
+ if (kind === 'model_not_found') return 'invalid_request_error';
26
29
  return 'api_error';
27
30
  }
28
31
 
@@ -2,13 +2,35 @@
2
2
 
3
3
  const upstreamHttp = require('./upstream-http');
4
4
  const { classifyFailure } = require('../worker-failure-classify');
5
+ // WP-1b (docs/lead-route-model-invocability-preflight-2026-10-28.html §12a rule 1): a probe that
6
+ // names the wire by its own preference can contradict the wire the CALLER's harness will actually
7
+ // use (`selectUpstream(route, face, model)`), so a face-carrying probe asks the route table the
8
+ // same question the live request path does. `./routes` requires nothing from this module, so this
9
+ // stays a one-way edge.
10
+ const { selectUpstream } = require('./routes');
11
+ // WP-1 (docs/lead-route-model-invocability-preflight-2026-10-28.html §6.1): the probe's own
12
+ // verdict has to speak the proxy's classification, because that is the vocabulary that names a
13
+ // route/model pair as unable to run (`model_not_invocable` / `model_access_denied`). The worker
14
+ // taxonomy (`classifyFailure` above) is kept as-is for its existing consumers.
15
+ const { normalizeUpstreamOutcome } = require('./upstream-outcome');
5
16
 
6
17
  const DEFAULT_TIMEOUT_MS = 30_000;
7
18
  const MAX_TIMEOUT_MS = 30_000;
8
19
  const MAX_RESPONSE_BYTES = 64 * 1024;
9
20
  const PROBE_PROMPT = 'Reply OK.';
10
21
 
11
- function _wireFor(route) {
22
+ // The two lead faces (§12a rule 1). Any other value, `undefined` included, keeps the legacy
23
+ // preference order below, so every existing caller dials exactly what it dials today.
24
+ const FACED_WIRES = new Set(['anthropic', 'responses']);
25
+
26
+ function _wireFor(route, face, model) {
27
+ if (FACED_WIRES.has(face)) {
28
+ // The face's own wire, chosen by the SAME function the request path uses. A route that cannot
29
+ // serve this face at all answers null, which the caller turns into `probe_unsupported_wire`
30
+ // (http_status null) rather than guessing at another wire.
31
+ const upstream = selectUpstream(route, face, String(model).trim());
32
+ return upstream && upstream.api ? upstream.api : null;
33
+ }
12
34
  const apis = route && route.upstreamApis || [];
13
35
  if (apis.includes('openai-chat')) return 'openai-chat';
14
36
  if (apis.includes('anthropic-messages')) return 'anthropic-messages';
@@ -36,15 +58,32 @@ function _validResponse(wire, value) {
36
58
  return false;
37
59
  }
38
60
 
39
- function _failure(status, observedText, fallbackCode = 'probe_failed', now = Date.now) {
40
- const classified = classifyFailure({ http_status: status, message: observedText, now });
61
+ // The proxy's own classification for one probe response, reduced to the four fields a caller may
62
+ // act on. Provider prose (`message`/`error`) is deliberately NOT copied: `normalizeUpstreamOutcome`
63
+ // already redacts it, and this narrowing keeps that guarantee at the seam.
64
+ function _outcomeOf(status, value) {
65
+ const outcome = normalizeUpstreamOutcome({ body: value == null ? null : value, status });
41
66
  return {
67
+ kind: outcome.kind,
68
+ upstreamStatus: outcome.upstreamStatus,
69
+ upstreamCode: outcome.upstreamCode,
70
+ retryable: outcome.retryable,
71
+ };
72
+ }
73
+
74
+ function _failure(status, observedText, fallbackCode = 'probe_failed', now = Date.now, value = null) {
75
+ const classified = classifyFailure({ http_status: status, message: observedText, now });
76
+ const failure = {
42
77
  class: classified.class,
43
78
  code: classified.code || fallbackCode,
44
79
  http_status: status == null ? null : status,
45
80
  retry_after_ms: classified.retry_after_ms,
46
81
  message: 'Generation probe failed',
47
82
  };
83
+ // A failure with an actual HTTP response carries the proxy's classification of it. A transport,
84
+ // timeout or unknown-wire failure (`http_status: null`) has no upstream outcome at all.
85
+ if (Number.isInteger(status)) failure.upstream_outcome = _outcomeOf(status, value);
86
+ return failure;
48
87
  }
49
88
 
50
89
  async function _readBodyBufferBounded(readable) {
@@ -75,10 +114,14 @@ function _requestFor(wire, model) {
75
114
  return { suffix: '/responses', body: { model, input: PROBE_PROMPT, max_output_tokens: 16, stream: false } };
76
115
  }
77
116
 
78
- async function probeGeneration(route, { model, timeoutMs = DEFAULT_TIMEOUT_MS, now = Date.now } = {}) {
79
- const wire = _wireFor(route);
117
+ async function probeGeneration(route, { model, timeoutMs = DEFAULT_TIMEOUT_MS, now = Date.now, face } = {}) {
118
+ const wire = _wireFor(route, face, model);
119
+ // Rule 4 needs both facts on EVERY result, success or failure: which wire this verdict is about,
120
+ // and how many wires the route offers (a verdict is only reportable when the route offers one).
121
+ const routeWireCount = route && Array.isArray(route.upstreamApis) ? route.upstreamApis.length : 0;
122
+ const withWire = (result) => ({ ...result, probe_wire: wire, route_wire_count: routeWireCount });
80
123
  if (!wire) {
81
- return {
124
+ return withWire({
82
125
  generation_succeeded: false,
83
126
  http_status: null,
84
127
  failure: {
@@ -88,7 +131,7 @@ async function probeGeneration(route, { model, timeoutMs = DEFAULT_TIMEOUT_MS, n
88
131
  retry_after_ms: null,
89
132
  message: 'Generation probe failed',
90
133
  },
91
- };
134
+ });
92
135
  }
93
136
  const timeout = Math.max(1, Math.min(Number(timeoutMs) || DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS));
94
137
  const request = _requestFor(wire, String(model).trim());
@@ -108,11 +151,11 @@ async function probeGeneration(route, { model, timeoutMs = DEFAULT_TIMEOUT_MS, n
108
151
  caPem: route.caPem,
109
152
  });
110
153
  } catch {
111
- return {
154
+ return withWire({
112
155
  generation_succeeded: false,
113
156
  http_status: null,
114
157
  failure: _failure(null, '', 'probe_failed', now),
115
- };
158
+ });
116
159
  }
117
160
 
118
161
  let raw;
@@ -120,11 +163,11 @@ async function probeGeneration(route, { model, timeoutMs = DEFAULT_TIMEOUT_MS, n
120
163
  raw = (await _readBodyBufferBounded(resp.body)).toString('utf8');
121
164
  } catch (err) {
122
165
  const tooLarge = err && err.code === 'probe_response_too_large';
123
- return {
166
+ return withWire({
124
167
  generation_succeeded: false,
125
168
  http_status: resp.status,
126
169
  failure: _failure(resp.status, '', tooLarge ? 'probe_response_too_large' : 'probe_failed', now),
127
- };
170
+ });
128
171
  }
129
172
  try {
130
173
  let value = null;
@@ -133,19 +176,19 @@ async function probeGeneration(route, { model, timeoutMs = DEFAULT_TIMEOUT_MS, n
133
176
  if (okStatus && _validResponse(wire, value)) {
134
177
  const result = { generation_succeeded: true, http_status: resp.status };
135
178
  if (typeof value.model === 'string' && value.model) result.upstream_model = value.model;
136
- return result;
179
+ return withWire(result);
137
180
  }
138
- return {
181
+ return withWire({
139
182
  generation_succeeded: false,
140
183
  http_status: resp.status,
141
- failure: _failure(resp.status, raw, okStatus ? 'probe_invalid_response' : 'probe_failed', now),
142
- };
184
+ failure: _failure(resp.status, raw, okStatus ? 'probe_invalid_response' : 'probe_failed', now, value),
185
+ });
143
186
  } catch {
144
- return {
187
+ return withWire({
145
188
  generation_succeeded: false,
146
189
  http_status: null,
147
190
  failure: _failure(null, '', 'probe_failed', now),
148
- };
191
+ });
149
192
  }
150
193
  }
151
194
 
@@ -0,0 +1,37 @@
1
+ 'use strict';
2
+
3
+ const CODEX_HISTORICAL_TOOLS_FILTER_RECEIPT = 'ctm-proxy-historical-tools-v1';
4
+
5
+ function normalizeHistoricalAdditionalTools(body, { trustedRoutedCodexLead = false } = {}) {
6
+ if (!trustedRoutedCodexLead || !Array.isArray(body?.tools) || !Array.isArray(body?.input)) {
7
+ return { ok: true, body, removedCount: 0 };
8
+ }
9
+
10
+ let removedCount = 0;
11
+ const input = [];
12
+ for (const item of body.input) {
13
+ if (!item || typeof item !== 'object' || !Object.hasOwn(item, 'type') || item.type !== 'additional_tools') {
14
+ input.push(item);
15
+ continue;
16
+ }
17
+
18
+ const keys = Reflect.ownKeys(item);
19
+ const allowed = new Set(['type', 'role', 'tools', 'id']);
20
+ if (keys.some((key) => typeof key !== 'string' || !allowed.has(key)) ||
21
+ !Object.hasOwn(item, 'role') || !Object.hasOwn(item, 'tools') ||
22
+ item.role !== 'developer' || !Array.isArray(item.tools) ||
23
+ (Object.hasOwn(item, 'id') && item.id !== null &&
24
+ (typeof item.id !== 'string' || item.id.length === 0))) {
25
+ return { ok: false, reason: 'historical_tool_declaration_invalid' };
26
+ }
27
+ removedCount += 1;
28
+ }
29
+
30
+ if (removedCount === 0) return { ok: true, body, removedCount: 0 };
31
+ return { ok: true, body: { ...body, input }, removedCount };
32
+ }
33
+
34
+ module.exports = {
35
+ CODEX_HISTORICAL_TOOLS_FILTER_RECEIPT,
36
+ normalizeHistoricalAdditionalTools,
37
+ };
@@ -28,6 +28,7 @@ const SAFE_LEAD_REFUSAL_REASONS = Object.freeze([
28
28
  'forged_identity_fields',
29
29
  'primary_binding_mismatch',
30
30
  'unsupported_token_purpose',
31
+ 'historical_tool_declaration_invalid',
31
32
  ]);
32
33
  const _SAFE_REASONS = new Set(SAFE_LEAD_REFUSAL_REASONS);
33
34
 
@@ -0,0 +1,130 @@
1
+ 'use strict';
2
+
3
+ // WP-1 §6.4/§6.5 registration lifecycle, extracted out of oauth-proxy.js (review item 3): the one
4
+ // bounded transaction that answers `POST /_admin/tokens` with kind:'lead'. The refusal paths, the
5
+ // registry-owned ticket, the abort window and the 409 codes below are the ones oauth-proxy.js has
6
+ // always run, moved verbatim -- the transaction stays whole instead of being spread across the
7
+ // request dispatcher.
8
+ //
9
+ // The seam this cluster needs from its host is the lead-token registry in ./tokens
10
+ // (registerLeadToken, beginLeadTokenRegistration, LEAD_TOKEN_REVOKED_CODE, NATIVE_TRANSPORT_MTLS) and
11
+ // nothing else: the response `res`, the parsed `registration` and the per-instance `ctx` carrying
12
+ // this server's OWN native listener are all passed IN by the caller, so this module never requires
13
+ // oauth-proxy.js back -- there is no cycle.
14
+
15
+ const tokens = require('./tokens');
16
+
17
+ // ── WP-1 §6.4/§6.5: the TLS-ready acknowledgment of an mtls-v1 registration ─────────────────────
18
+ //
19
+ // A legacy (or routed-primary) registration is unchanged: it is synchronous, creates no listener and
20
+ // never loads the CA. An `mtls-v1` registration is the one thing that starts this transport, and it is
21
+ // acknowledged ONLY once BOTH of these are true:
22
+ //
23
+ // 1. the strict HTTPS listener is bound and ready, and
24
+ // 2. the pending registration has committed against the live registry.
25
+ //
26
+ // The order matters, and so does the ticket. Between "the registration is well formed" and "the
27
+ // listener is ready" the caller is awaiting, which is exactly when a `DELETE /_admin/tokens` can land:
28
+ // a plain revalidation would still see the token as absent (it was never live) and commit a grant the
29
+ // caller already gave up on. The registry-owned ticket fences that operation, so commit refuses with a
30
+ // bounded code and no token is created. A failed bind cancels the ticket and leaves no grant either.
31
+ //
32
+ // Review item 3 adds the OTHER half of that window: the caller can also simply GO AWAY. The supervisor's
33
+ // admin fetch has a 2s budget, and the child's TLS bind can outlive it — so an interrupted registration
34
+ // must not leave a live owner behind that the parent never learned about. The response is therefore
35
+ // watched from BEFORE the await (a `res.close` listener installed up front, not after the fact), the
36
+ // ticket is cancelled the moment the response can no longer be written, and the commit is guarded by a
37
+ // re-check on both sides of the await. Nothing is ever written to a closed response.
38
+ function _responseIsGone(res) {
39
+ if (!res) return true;
40
+ if (res.writableEnded) return true; // we already answered
41
+ if (res.destroyed) return true; // the socket went away
42
+ return false;
43
+ }
44
+
45
+ async function handleLeadTokenRegistration(res, registration, ctx) {
46
+ const sendJson = (status, payload) => {
47
+ // Never write on a response whose client is gone: a torn-down socket has no reader, and writing
48
+ // would surface as an unhandled error rather than as the honest "this registration was abandoned".
49
+ if (_responseIsGone(res)) return;
50
+ res.writeHead(status, { 'Content-Type': 'application/json' });
51
+ res.end(JSON.stringify(payload));
52
+ };
53
+ // §5 D-4 / §9 S6: the ONE refusal that is not a malformed registration is the revoked-identity
54
+ // tombstone, and it answers 409 with a static code (never the underlying message, never a caller
55
+ // value) from all three of this handler's exits: the synchronous branch, the ticket's entry check and
56
+ // the ticket's commit.
57
+ const sendRegistrationRefusal = (err) => {
58
+ if (err && err.code === tokens.LEAD_TOKEN_REVOKED_CODE) sendJson(409, { error: tokens.LEAD_TOKEN_REVOKED_CODE });
59
+ else sendJson(400, { error: err && err.message });
60
+ };
61
+ if (registration.nativeTransport !== tokens.NATIVE_TRANSPORT_MTLS) {
62
+ // Existing synchronous contract, byte-for-byte: no listener, no CA, no await.
63
+ try {
64
+ const result = tokens.registerLeadToken(registration);
65
+ sendJson(200, { token: result.token });
66
+ } catch (err) {
67
+ sendRegistrationRefusal(err);
68
+ }
69
+ return;
70
+ }
71
+ let ticket;
72
+ try {
73
+ // Validated, conflict-checked read-only against the live registry, and captured BEFORE the first
74
+ // await: the caller's own object cannot change what commits, a malformed OR already-conflicting
75
+ // registration never reaches the listener (no CA load, no bind), and the ticket exists before any
76
+ // await can be interrupted.
77
+ ticket = tokens.beginLeadTokenRegistration(registration);
78
+ } catch (err) {
79
+ sendRegistrationRefusal(err);
80
+ return;
81
+ }
82
+ let settled = false;
83
+ const cancelOwnTicket = () => {
84
+ if (settled) return;
85
+ settled = true;
86
+ ticket.cancel();
87
+ };
88
+ // Installed BEFORE the await, so a disconnect that happens while the listener is still binding is
89
+ // observed rather than discovered later as an orphaned owner.
90
+ const onClientGone = () => {
91
+ if (!res.writableEnded) cancelOwnTicket();
92
+ };
93
+ res.on('close', onClientGone);
94
+ try {
95
+ // Entry check: a caller that vanished before we even started has already given up on this attempt.
96
+ if (_responseIsGone(res)) return;
97
+ // This server's OWN controller, never another live server's.
98
+ if (!ctx.nativeListener) {
99
+ cancelOwnTicket();
100
+ sendJson(503, { error: 'native_lead_listener_unavailable' });
101
+ return;
102
+ }
103
+ await ctx.nativeListener.ensureReady();
104
+ // Re-check after the await: the interruption window is exactly the one the ticket exists for.
105
+ if (_responseIsGone(res)) return;
106
+ // The commit is synchronous and happens while the response is still writable, so the ticket is
107
+ // retired from the cancellation path here: from this point the grant is real and the only remaining
108
+ // hazard is a LOST acknowledgment, which the caller (the supervisor) owns via its pending-revoke
109
+ // obligation — the child cannot distinguish "the parent read it" from "the parent timed out".
110
+ settled = true;
111
+ const committed = ticket.commit();
112
+ if (!committed.ok) {
113
+ // A revoke/reset/cancel landed while the listener was starting, or the live revalidation found a
114
+ // concurrent owner: no grant, and the caller is told why by bounded code only.
115
+ sendJson(409, { error: committed.code });
116
+ return;
117
+ }
118
+ sendJson(200, { token: committed.token });
119
+ } catch (err) {
120
+ cancelOwnTicket();
121
+ sendJson(503, { error: (err && err.code) || 'native_lead_listener_unavailable' });
122
+ } finally {
123
+ // The response is no longer ours to reason about, so the listener and any unsettled ticket are
124
+ // released here. An already-committed ticket is untouched (cancel() only moves a PENDING ticket).
125
+ res.removeListener('close', onClientGone);
126
+ cancelOwnTicket();
127
+ }
128
+ }
129
+
130
+ module.exports = { handleLeadTokenRegistration };