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
@@ -48,6 +48,7 @@
48
48
  // `../brain/job-store` here.
49
49
 
50
50
  const { writeRunArtifact, pruneRunArtifacts, classifySkillError } = require('../lib/run-artifacts');
51
+ const { resolvePlacement } = require('../lib/derivation-home');
51
52
 
52
53
  // The per-run execution budget constants and the prompt builder live in the
53
54
  // require-free leaf `jobs/job-defaults.js` (moved there verbatim by fix round 2);
@@ -65,6 +66,13 @@ const {
65
66
  // §4: default concurrency is 1 — run jobs one at a time per tick, no flag.
66
67
  const DEFAULT_JOB_CONCURRENCY = 1;
67
68
 
69
+ // D-5/S-16 (shared-memory WP-2): job kinds whose INPUT is replicated brain rows, so the
70
+ // derived rows they mint (skill-executor's timestamped `source_id`, a prompt job's model
71
+ // output) would multiply across N replicas. Only the derivation home selects them;
72
+ // `builtin` jobs (e.g. `slack.inbound`) act on data this machine has and always run.
73
+ const DERIVED_JOB_ACTION_KINDS = new Set(['skill', 'prompt']);
74
+ const NOT_DERIVATION_HOME = 'not_derivation_home';
75
+
68
76
  // §6.1 retention: keep the newest 200 runs per job.
69
77
  const DEFAULT_RUN_KEEP = 200;
70
78
 
@@ -337,9 +345,13 @@ async function runJob(job, { trigger = 'schedule', now = Date.now, executors, ac
337
345
  *
338
346
  * @returns {Promise<{ran: number, skipped: number, considered: number, decisions: Array}>}
339
347
  */
340
- async function runDueJobs({ now = Date.now, executors, brain, deps } = {}) {
348
+ async function runDueJobs({ now = Date.now, executors, brain, deps, placement } = {}) {
341
349
  const io = resolveAccess(brain);
342
350
  const at = typeof now === 'function' ? now() : now;
351
+ // D-5/S-16 (WP-2): the `jobs` table is host-local and stays untouched; the placement gate
352
+ // lives HERE, at the selection point, so a non-home node simply never selects a derived job.
353
+ // `placement` is the test seam; production resolves it from CTM (cached 60 s).
354
+ const resolvedPlacement = placement || await resolvePlacement();
343
355
 
344
356
  const jobs = await io.read('listJobs', [{ includeDisabled: true }]);
345
357
  const decisions = [];
@@ -350,6 +362,19 @@ async function runDueJobs({ now = Date.now, executors, brain, deps } = {}) {
350
362
  for (const job of jobs || []) {
351
363
  if (ran >= DEFAULT_JOB_CONCURRENCY) break;
352
364
  considered += 1;
365
+ // WP-2: a derived job on a non-home node is skipped with its own reason, reported in the
366
+ // same `decisions` array every other skip uses (no row is disabled, no run is recorded).
367
+ if (!resolvedPlacement.home && DERIVED_JOB_ACTION_KINDS.has(job.action_kind)) {
368
+ skipped += 1;
369
+ decisions.push({
370
+ job_id: job.id,
371
+ name: job.name,
372
+ decision: 'skip',
373
+ reason: NOT_DERIVATION_HOME,
374
+ details: { action_kind: job.action_kind, placement: resolvedPlacement.reason },
375
+ });
376
+ continue;
377
+ }
353
378
  // WP-5a (D-12): the connection record is the authority — a job that declares
354
379
  // a requirement is judged by its kind's row, and a job that declares none is
355
380
  // 'ok'. Only reconnect_required/disabled gate dispatch (§4/D-6).
@@ -527,4 +552,6 @@ module.exports = {
527
552
  MIN_TASK_EXECUTION_TIMEOUT_MS,
528
553
  DEFAULT_JOB_CONCURRENCY,
529
554
  DEFAULT_RUN_KEEP,
555
+ DERIVED_JOB_ACTION_KINDS,
556
+ NOT_DERIVATION_HOME,
530
557
  };
@@ -13,7 +13,15 @@ const OWNER_METHOD_PRIORITY = Object.freeze({
13
13
  // background — scheduled, resumable
14
14
  markTurnLedgerOutboxDelivered: 'background',
15
15
  recordTurnLedgerOutboxFailure: 'background',
16
- backfillEntityLinkBatch: 'background', backfillClassifyBatch: 'background',
16
+ backfillEntityLinkBatch: 'background', backfillClassifyBatch: 'background', brainSyncClockSeed: 'background',
17
+ // WP-1 §6.4: the v2 feed/apply pair and the hub bookkeeping — peer-driven, no user waits on
18
+ // them locally (the hub's own tick owns the latency budget), so they share the seed walk's
19
+ // class rather than competing with an interactive turn.
20
+ brainSyncV2Vector: 'background', brainSyncV2Changes: 'background', brainSyncV2Apply: 'background',
21
+ brainSyncV2MemberVector: 'background', brainSyncV2PruneTombstones: 'background', brainSyncV2Digest: 'background',
22
+ // WP-4 §6.7: the join trio + the import job's step — peer/job-driven, no local user waits on them.
23
+ brainSyncV2JoinSnapshot: 'background', brainSyncV2JoinImport: 'background',
24
+ brainSyncV2JoinStatus: 'background', brainSyncV2JoinStep: 'background',
17
25
  backfillStripNoiseBatch: 'background', backfillInsertMemoryIndexBatch: 'background',
18
26
  backfillMarkEmbedFailed: 'background', embeddingsUpsertBatch: 'background',
19
27
  commitThinkExtraction: 'background', ingestInsertMemoryBatch: 'background',
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ // R-6/R-7 (§5, D-4) — "CTM's data dir", computed the way CTM computes it for itself
4
+ // (coding-task-manager/lib/data-dir.js:17-21) but from a GIVEN env object.
5
+ //
6
+ // Call it in the Wall-E DAEMON (the process that serves POST /api/walle/sync/v2/join/import),
7
+ // NEVER in the brain-owner worker: the owner's env is a deliberate whitelist
8
+ // (lib/brain-owner-env.js) that sets WALL_E_DATA_DIR to the brain's own directory and drops
9
+ // CTM_DATA_DIR (I-5), so resolving the inbox root there names the wrong directory — which is
10
+ // exactly the Spark fault (every join import refused JOIN_PATH_INVALID while CTM wrote its
11
+ // snapshots to the CTM inbox). The daemon resolves the root and passes it down as `dataDir`
12
+ // (http/sync-api.js _handleV2JoinImport → brain/brain-join.js isInsideJoinInbox).
13
+
14
+ const os = require('node:os');
15
+ const path = require('node:path');
16
+
17
+ // CTM_DATA_DIR, then WALL_E_DATA_DIR (wall-e's own override, honoured by CTM's resolver too so a
18
+ // single value works for both processes), then <HOME>/.walle/data. An empty — or
19
+ // whitespace-only — value counts as unset, matching CTM's truthiness test on the raw value.
20
+ function resolveCtmDataDir(env = process.env) {
21
+ const override = String(env.CTM_DATA_DIR || env.WALL_E_DATA_DIR || '').trim();
22
+ if (override) return path.resolve(override);
23
+ return path.resolve(env.HOME || os.homedir(), '.walle', 'data');
24
+ }
25
+
26
+ module.exports = { resolveCtmDataDir };
@@ -0,0 +1,147 @@
1
+ 'use strict';
2
+
3
+ // shared-memory WP-2 (design `wall-e/docs/shared-memory-every-machine-2026-10-31.html`
4
+ // §5 D-5, §9 S-16..S-21): WHERE derived work runs.
5
+ //
6
+ // After replication every machine holds the same brain rows. A producer whose INPUT is
7
+ // replicated rows would then run N times and mint N different rows (e.g.
8
+ // `skills/skill-executor.js` minting `skill-${skill.id}-${Date.now()}-${count}`), so D-5
9
+ // single-homes *derived* production on the "derivation home": the Wall-E whose CTM does
10
+ // not report `role:'box'`. Host-local ingest keeps running wherever the data lives; only
11
+ // the derived step is gated.
12
+ //
13
+ // The role fact comes from CTM (`memory/hub-bridge-client.js:fetchStatus` ->
14
+ // `GET /api/hub-bridge/status` -> `{ role, hubs, poolMembers }`), which answers null when
15
+ // CTM is unreachable. Three placements:
16
+ //
17
+ // role 'box' -> { home: false, reason: 'box' } (a box skips derived work)
18
+ // null / no string role -> { home: false, reason: 'role_unknown' } (D-5: skip, never guess)
19
+ // any other role -> { home: true, reason: 'not_box' } (hub, standalone installs)
20
+ //
21
+ // The answer is cached for CACHE_TTL_MS in BOTH directions: a probe per scheduler tick per
22
+ // producer would be a CTM round-trip storm, and a negative answer must not flap back to
23
+ // "home" mid-run. Tests never hit a real CTM — `resolvePlacement({ fetchStatus })` is the seam.
24
+
25
+ const hubBridgeClient = require('../memory/hub-bridge-client');
26
+
27
+ const CACHE_TTL_MS = 60 * 1000;
28
+
29
+ const REASON_NOT_BOX = 'not_box';
30
+ const REASON_BOX = 'box';
31
+ const REASON_ROLE_UNKNOWN = 'role_unknown';
32
+ const REASON_MEMBER = 'member';
33
+
34
+ // `{ at, value }` — one process-wide memo (positive AND negative), reset by
35
+ // `_resetPlacementCacheForTests()`.
36
+ let _cache = null;
37
+
38
+ function _classify(status) {
39
+ if (status == null || typeof status !== 'object') {
40
+ return { home: false, role: null, reason: REASON_ROLE_UNKNOWN };
41
+ }
42
+ const role = status.role;
43
+ // A missing key, an explicit null, and an empty string all mean "CTM did not tell us its
44
+ // role" — D-5 says skip derived work for that run rather than guess (fail closed).
45
+ if (typeof role !== 'string' || role === '') {
46
+ return { home: false, role: null, reason: REASON_ROLE_UNKNOWN };
47
+ }
48
+ if (role === 'box') return { home: false, role, reason: REASON_BOX };
49
+ return { home: true, role, reason: REASON_NOT_BOX };
50
+ }
51
+
52
+ /**
53
+ * Resolve where this Wall-E sits. Never throws: an unreachable CTM is the named
54
+ * `role_unknown`, not a crash.
55
+ *
56
+ * @param {Object} [opts]
57
+ * @param {Object} [opts.env=process.env] env handed to the CTM client
58
+ * @param {Function} [opts.fetchStatus] test seam (defaults to the hub-bridge client)
59
+ * @param {Function|number} [opts.now=Date.now] clock (injectable for the cache TTL)
60
+ * @returns {Promise<{home: boolean, role: string|null, reason: string}>}
61
+ */
62
+ // The durable membership marker (brain/brain-sync-v2.js markMembership): a Wall-E a home has synced
63
+ // is a member. Read locally, before any network: CTM's role:'box' is only reported while a hub link is
64
+ // open (coding-task-manager/lib/hub-bridge-box.js:186), so it cannot be the only signal.
65
+ function _defaultReadMembership() {
66
+ try {
67
+ const brain = require('../brain');
68
+ const db = typeof brain.getDb === 'function' ? brain.getDb() : null;
69
+ if (!db) return null;
70
+ return require('../brain/brain-sync-membership').readMembership(db);
71
+ } catch {
72
+ return null; // HANDLE: no brain handle in this process yet — fall through to the CTM role
73
+ }
74
+ }
75
+
76
+ async function resolvePlacement({ env = process.env, fetchStatus, readMembership, now = Date.now } = {}) {
77
+ const clock = typeof now === 'function' ? now : () => now;
78
+ const at = clock();
79
+ if (_cache && (at - _cache.at) < CACHE_TTL_MS) return _cache.value;
80
+ const membership = (typeof readMembership === 'function' ? readMembership : _defaultReadMembership)();
81
+ if (membership && membership.role === 'member') {
82
+ const value = { home: false, role: 'member', reason: REASON_MEMBER };
83
+ _cache = { at, value };
84
+ return value;
85
+ }
86
+
87
+ const fetch = typeof fetchStatus === 'function' ? fetchStatus : hubBridgeClient.fetchStatus;
88
+ let status = null;
89
+ try {
90
+ status = await fetch({ env });
91
+ } catch {
92
+ // REPORT: a thrown probe is exactly the unreachable-CTM case the caller must treat as
93
+ // 'role_unknown' (hub-bridge-client.fetchStatus itself already answers null; this guards
94
+ // an injected seam that throws instead).
95
+ status = null;
96
+ }
97
+
98
+ const value = _classify(status);
99
+ _cache = { at, value };
100
+ return value;
101
+ }
102
+
103
+ /** Convenience boolean: is this Wall-E the derivation home? */
104
+ async function isDerivationHome(opts) {
105
+ return (await resolvePlacement(opts)).home;
106
+ }
107
+
108
+ /**
109
+ * The skip record a gated producer returns instead of running. Same shape the loops already
110
+ * use for "did no work" (`{ skipped: true, reason, ... }`) so `onResult` needs no new branch.
111
+ */
112
+ function placementSkip(name, placement) {
113
+ return {
114
+ skipped: true,
115
+ reason: (placement && placement.reason) || REASON_ROLE_UNKNOWN,
116
+ job: name,
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Run `fn` only on the derivation home; otherwise answer the skip record. This is the wrapper
122
+ * agent.js puts around a `placement: 'home'` registration's `run`:
123
+ *
124
+ * run: () => runIfHome('reflect', () => reflect.runOnce())
125
+ */
126
+ async function runIfHome(name, fn, opts = {}) {
127
+ const placement = await resolvePlacement(opts);
128
+ if (!placement.home) return placementSkip(name, placement);
129
+ return fn();
130
+ }
131
+
132
+ /** Test seam: clear the 60 s memo (both directions). */
133
+ function _resetPlacementCacheForTests() {
134
+ _cache = null;
135
+ }
136
+
137
+ module.exports = {
138
+ resolvePlacement,
139
+ isDerivationHome,
140
+ placementSkip,
141
+ runIfHome,
142
+ _resetPlacementCacheForTests,
143
+ CACHE_TTL_MS,
144
+ REASON_NOT_BOX,
145
+ REASON_BOX,
146
+ REASON_ROLE_UNKNOWN,
147
+ };
@@ -124,6 +124,9 @@ function _watchMetrics(outcomes, watcherUsd) {
124
124
  const silentUseful = outcomes.filter((row) => row.label === 'silent_useful').length;
125
125
  const silentEmpty = outcomes.filter((row) => row.label === 'silent_empty').length;
126
126
  const labelled = useful + empty;
127
+ // Legacy (WP-2): Wall-E no longer produces `silent_*` rows — nothing reads these fields any
128
+ // more — but events and rows written before the silent mode was retired still carry them, so
129
+ // the metrics shape stays for the panel.
127
130
  const silentLabelled = silentUseful + silentEmpty;
128
131
  const watcherUsdWindow = _watcherUsdWindow(watcherUsd);
129
132
  return {
@@ -143,24 +146,62 @@ function _watchMetrics(outcomes, watcherUsd) {
143
146
  };
144
147
  }
145
148
 
146
- function _evaluateWatch(cellState, outcomes, watcherUsd) {
147
- const metrics = _watchMetrics(outcomes, watcherUsd);
149
+ /** A labelled row: a wake the lead answered in time (`useful`) or did not need (`empty`). */
150
+ function _isLabelledRow(row) {
151
+ return row.label === 'useful' || row.label === 'empty';
152
+ }
153
+
154
+ /**
155
+ * WP-2 (§6, lead ruling): the rows this cell's CURRENT regime produced — the ones at or after
156
+ * `state_changed_at`. A cell that just passed its probe still has up to PROBE_N pre-probe rows in
157
+ * the 20-row trailing window; measuring the whole window would read 7 probe-useful rows next to 9
158
+ * old empty ones as ~0.44 and turn the cell straight back off. resilience4j starts a fresh metrics
159
+ * window on every state transition for the same reason. Rows with no readable timestamp are kept
160
+ * (the same predicate `_evaluateExplore`'s probe filter uses).
161
+ */
162
+ function _regimeRows(cell, outcomes) {
163
+ const changedAt = _toMs(cell.state_changed_at);
164
+ return outcomes.filter((row) => {
165
+ const createdMs = _toMs(row.created_at);
166
+ return changedAt == null || createdMs == null || createdMs >= changedAt;
167
+ });
168
+ }
169
+
170
+ function _evaluateWatch(cell, outcomes, watcherUsd, now) {
171
+ const cellState = _stateOf(cell);
172
+
148
173
  if (cellState === 'on') {
174
+ // Only the current regime is evidence: an on-cell demotes on its own rows, never on the
175
+ // pre-probe rows its last transition left in the window.
176
+ const metrics = _watchMetrics(_regimeRows(cell, outcomes), watcherUsd);
149
177
  if (metrics.labelled >= MIN_LABELLED && metrics.precision < WATCH_OFF_BELOW) {
150
178
  return _verdict('off', cellState, metrics, REASONS.WATCH_PRECISION_BELOW);
151
179
  }
152
180
  return _verdict(cellState, cellState, metrics, null);
153
181
  }
182
+
154
183
  if (cellState === 'off') {
155
- // While off, the silent ticks are the window: the lead's own next call is the only
156
- // evidence left that the wake was not needed.
157
- metrics.collecting = metrics.silent_labelled < MIN_LABELLED;
158
- if (metrics.silent_labelled >= MIN_LABELLED && metrics.silent_precision >= WATCH_ON_ABOVE) {
159
- return _verdict('on', cellState, metrics, REASONS.WATCH_SILENT_PRECISION_ABOVE);
184
+ // Rows never move an off cell — not the retired silent ticks, not any other label. Only
185
+ // elapsed time does, and only when the user has not vetoed the route.
186
+ const metrics = _watchMetrics(outcomes, watcherUsd);
187
+ const changedAt = _toMs(cell.state_changed_at);
188
+ const nowMs = _toMs(now) ?? Date.now();
189
+ const days = changedAt == null ? 0 : (nowMs - changedAt) / DAY_MS;
190
+ if (!_vetoOf(cell) && days >= EXPLORE_PROBE_AFTER_DAYS) {
191
+ return _verdict('probe', cellState, metrics, REASONS.EXPLORE_PROBE_AFTER);
160
192
  }
161
193
  return _verdict(cellState, cellState, metrics, null);
162
194
  }
163
- return _verdict(cellState, cellState, metrics, null);
195
+
196
+ // probe: the next PROBE_N labelled rows that landed AFTER the probe started decide it.
197
+ const regime = _regimeRows(cell, outcomes);
198
+ const metrics = _watchMetrics(regime, watcherUsd);
199
+ const probeWindow = regime.filter(_isLabelledRow).slice(0, PROBE_N);
200
+ metrics.probed = probeWindow.length;
201
+ if (probeWindow.length < PROBE_N) return _verdict(cellState, cellState, metrics, null);
202
+ const precision = _ratio(probeWindow.filter((row) => row.label === 'useful').length, probeWindow.length);
203
+ if (precision >= WATCH_ON_ABOVE) return _verdict('on', cellState, metrics, REASONS.EXPLORE_PROBE_PASSED);
204
+ return _verdict('off', cellState, metrics, REASONS.EXPLORE_PROBE_FAILED);
164
205
  }
165
206
 
166
207
  // --- explore ----------------------------------------------------------------------
@@ -322,7 +363,7 @@ function evaluateCell(cell, trailingOutcomes, { now, leadBurstUsd, watcherUsd }
322
363
  if (kind === 'explore' || String(cell?.cell_key || '').startsWith('explore|')) {
323
364
  return _evaluateExplore(cell, outcomes, now, leadBurstUsd);
324
365
  }
325
- return _evaluateWatch(cellState, outcomes, watcherUsd);
366
+ return _evaluateWatch(cell, outcomes, watcherUsd, now);
326
367
  }
327
368
 
328
369
  /** The metrics a short-circuited (vetoed) cell reports — same shape as its kind's. */
@@ -25,13 +25,22 @@
25
25
  // * an unparsed first reply earns ONE re-ask of the same pinned model with the parse error
26
26
  // appended, and a second failure stores the raw reply so the failure is visible on the panel.
27
27
  // A cheaper judge is still never substituted (D-19).
28
+ //
29
+ // Round 3 WP-1 (§4 D-1..D-5): the budget, and the truth of a cut-off reply.
30
+ // * the output ceiling is the role API's own MAX_COMPLETION_TOKENS, not a hand-picked number
31
+ // (D-1) and no thinking/effort knob is configured (D-2);
32
+ // * a `max_tokens` stop is `grade_truncated`: the text is cut off, so it is never parsed and
33
+ // never re-asked (D-3);
34
+ // * an empty reply is `grade_empty` and earns NO re-ask, so an empty assistant turn can never
35
+ // reach the provider guard (D-4);
36
+ // * every unavailable sample carries the stop reason, and the call carries the explore row's
37
+ // worker id as `sessionId` so grader spend joins the cell (D-5).
28
38
 
29
39
  const fs = require('node:fs');
30
40
  const path = require('node:path');
31
41
 
32
42
  const EXPLORE_GRADE_EVERY = 5; // D-12: one in five completed hand-offs per cell
33
43
  const GRADE_TASK = 'explore-handoff-grade';
34
- const GRADE_MAX_TOKENS = 400;
35
44
  const GRADE_TIMEOUT_MS = 120000;
36
45
  const CARD_MAX_CHARS = 12000;
37
46
  const BRIEF_MAX_CHARS = 4000; // the brief's share of the card; the report keeps the rest
@@ -138,6 +147,11 @@ function _replyText(reply) {
138
147
  return _str(reply && reply.content);
139
148
  }
140
149
 
150
+ /** D-3: `completeForRole` carries the provider's stop reason; a reply without one has none. */
151
+ function _stopReasonOf(reply) {
152
+ return _str(reply && reply.stopReason) || null;
153
+ }
154
+
141
155
  /**
142
156
  * D-8: a grader that wraps its JSON in one Markdown fence still yields its verdict. Only a fence
143
157
  * the reply STARTS with is stripped, and only its matching close — prose around the block is left
@@ -170,12 +184,19 @@ function _parseReply(reply) {
170
184
 
171
185
  /**
172
186
  * Grade one exploration hand-off. Never throws; returns { ok:false, error } on any failure, and
173
- * re-asks the same pinned model once when the first reply does not parse (D-8).
187
+ * re-asks the same pinned model once when the first reply does not parse (D-8). A truncated reply
188
+ * (round 3 D-3) is its own outcome and is never parsed or re-asked.
174
189
  * @param {{ brief?:string, brief_path?:string, report?:string, report_path?:string, last_message_path?:string, toolLog:[{tool:string, path:string}] }} card clipped to CARD_MAX_CHARS (brief text, or read from brief_path; report text, or read from report_path/last_message_path)
175
- * @param {{ completeForRole: Function, graderModel: string, brain?: object }} deps
176
- * @returns {Promise<{ ok:true, sufficient:boolean, coverage:'all'|'most'|'little', inaccuracies:number, why:string } | { ok:false, error:string, raw?:string }>}
190
+ * @param {{ completeForRole: Function, graderModel: string, brain?: object, sessionId?: string|null }} deps
191
+ * @returns {Promise<{ ok:true, sufficient:boolean, coverage:'all'|'most'|'little', inaccuracies:number, why:string } | { ok:false, error:'grader_model_unset'|'grade_truncated'|'grade_empty'|'grade_unparsed'|string, stop_reason?:string|null, raw?:string }>}
177
192
  */
178
193
  async function gradeHandoff(card, deps = {}) {
194
+ // Round 3 D-1: the grader's output budget IS the role API's ceiling. The pinned judge always
195
+ // thinks, and thinking counts inside max_tokens, so the old 400 was consumed before the verdict
196
+ // was written (14/19 live calls stopped on `max_tokens`). Billing is per token used, so the
197
+ // higher ceiling costs only what the judge actually thinks. Required here, not at module top:
198
+ // lib/ holds no load-time reference into llm/ (lead-call-hook.js's lazy-seam rule).
199
+ const { MAX_COMPLETION_TOKENS } = require('../../llm/model-roles');
179
200
  const graderModel = _str(deps && deps.graderModel).trim();
180
201
  // An unset pin is not a reason to grade with something else (D-0): the sample is unavailable.
181
202
  if (!graderModel) return { ok: false, error: 'grader_model_unset' };
@@ -184,9 +205,12 @@ async function gradeHandoff(card, deps = {}) {
184
205
  task: GRADE_TASK,
185
206
  model: graderModel,
186
207
  system: RUBRIC,
187
- maxTokens: GRADE_MAX_TOKENS,
208
+ maxTokens: MAX_COMPLETION_TOKENS,
188
209
  timeoutMs: GRADE_TIMEOUT_MS,
189
210
  temperature: 0,
211
+ // D-5: the explore row's worker id, so the grader's ledger row joins to the same cell the
212
+ // watcher spend is keyed by. A caller with no identity keeps today's NULL.
213
+ sessionId: deps.sessionId || null,
190
214
  };
191
215
  const userMessage = { role: 'user', content: _cardText(card) };
192
216
  let reply;
@@ -197,10 +221,21 @@ async function gradeHandoff(card, deps = {}) {
197
221
  // A call that never answered has no reply to re-ask about, so this is not retried.
198
222
  return { ok: false, error: _errorCode(err) };
199
223
  }
224
+ const firstStop = _stopReasonOf(reply);
225
+ // D-3: a `max_tokens` stop is a cut-off reply, not a verdict -- the text ends mid-object, so
226
+ // parsing it would fabricate a label and a re-ask at the same ceiling only truncates again.
227
+ if (firstStop === 'max_tokens') {
228
+ return { ok: false, error: 'grade_truncated', stop_reason: 'max_tokens', raw: _replyText(reply).slice(0, RAW_MAX_CHARS) };
229
+ }
200
230
  const first = _parseReply(reply);
201
231
  if (first.ok) return first;
202
232
  const firstRaw = _replyText(reply);
233
+ // D-4: an empty first reply earns NO re-ask. The D-8 retry re-sends the raw reply as the
234
+ // assistant turn, and an empty one is rejected locally (WALLE_EMPTY_PROVIDER_MESSAGES) before
235
+ // any network call -- so the sample is stored as empty, with the stop reason that explains it.
236
+ if (!firstRaw.trim()) return { ok: false, error: 'grade_empty', stop_reason: firstStop };
203
237
  let raw = firstRaw;
238
+ let stopReason = firstStop;
204
239
  try {
205
240
  // D-8: ONE bounded retry, the SAME pinned model and parameters, with the raw reply and the
206
241
  // parse error on the wire (the MLflow "single retry with a tightened prompt" form).
@@ -212,15 +247,21 @@ async function gradeHandoff(card, deps = {}) {
212
247
  { role: 'user', content: `Reply with the JSON object only. Parse error: missing ${first.missing.join(', ')}` },
213
248
  ],
214
249
  });
250
+ const retryStop = _stopReasonOf(retried);
251
+ if (retryStop === 'max_tokens') {
252
+ return { ok: false, error: 'grade_truncated', stop_reason: 'max_tokens', raw: _replyText(retried).slice(0, RAW_MAX_CHARS) };
253
+ }
215
254
  const parsed = _parseReply(retried);
216
255
  if (parsed.ok) return parsed;
217
256
  raw = _replyText(retried);
257
+ stopReason = retryStop;
218
258
  } catch {
219
259
  // REPORT: the retry itself failed -- the sample is still stored as an unparsed grade, carrying
220
260
  // the first raw reply, so the failure is visible rather than a silent hole in the rate.
221
261
  raw = firstRaw;
262
+ stopReason = firstStop;
222
263
  }
223
- return { ok: false, error: 'grade_unparsed', raw: raw.slice(0, RAW_MAX_CHARS) };
264
+ return { ok: false, error: 'grade_unparsed', stop_reason: stopReason, raw: raw.slice(0, RAW_MAX_CHARS) };
224
265
  }
225
266
 
226
267
  /** D-12: every EXPLORE_GRADE_EVERY-th completed hand-off of a cell (its `helper_outcomes.seq`). */
@@ -231,11 +272,14 @@ function shouldGrade(seq) {
231
272
  /**
232
273
  * The persisted `helper_outcomes.grade_json` value: the four verdict fields, or `unavailable`.
233
274
  * D-8: an unparsed sample keeps the raw reply (≤ RAW_MAX_CHARS) so the panel can show WHY it
234
- * failed instead of an empty cell.
275
+ * failed instead of an empty cell. D-4: the stop reason is kept too (`max_tokens` reads as
276
+ * "budget", `end_turn` with no text as "the judge said nothing"), when the reply had one.
235
277
  */
236
278
  function gradeToJson(result) {
237
279
  if (!result || result.ok !== true) {
238
280
  const json = { unavailable: true, error: _str(result && result.error) || 'grade_failed' };
281
+ const stopReason = _str(result && result.stop_reason);
282
+ if (stopReason) json.stop_reason = stopReason;
239
283
  const raw = _str(result && result.raw);
240
284
  if (raw) json.raw = raw.slice(0, RAW_MAX_CHARS);
241
285
  return json;
@@ -265,7 +309,6 @@ module.exports = {
265
309
  CARD_MAX_CHARS,
266
310
  DEFAULT_GRADER_MODEL_KV,
267
311
  EXPLORE_GRADE_EVERY,
268
- GRADE_MAX_TOKENS,
269
312
  GRADE_TASK,
270
313
  GRADE_TIMEOUT_MS,
271
314
  RAW_MAX_CHARS,
@@ -61,6 +61,13 @@ let openHandoffs = new Map(); // ctm_session_id -> { id, opened_at, brief_path }
61
61
  // repeated terminal reads of one worker from re-sending the same close.
62
62
  let closedWorkers = new Set();
63
63
  let lastWarnAt = 0;
64
+ // D-8 (round 3): the three reasons watchModeFor (llm/worker-mcp-routing.js) returns null on. A null
65
+ // means CTM's default `on` -- a full-rate watcher -- so the reason a worker with an off cell still
66
+ // spent 40 watcher ticks has to be nameable, not inferred.
67
+ const WATCH_FALLBACK_REASONS = Object.freeze(['unknown_worker', 'no_lead_workers', 'hook_error']);
68
+ // Counters are per process since boot; `since` is the ISO time this module loaded (or last reset).
69
+ let watchFallbackTally = new Map(); // reason -> count
70
+ let watchFallbackSince = new Date().toISOString();
64
71
 
65
72
  // --- small helpers -----------------------------------------------------------------
66
73
 
@@ -102,6 +109,22 @@ function _warn(message) {
102
109
  console.warn(`[helper-rules] ${message}`);
103
110
  }
104
111
 
112
+ // D-8: one fail-open fallback of the wait-watch decision, counted and named. A reason outside the
113
+ // list is not a fallback this module knows about, so it is ignored (never silently added as a key).
114
+ function noteWatchFallback(reason, workerId = null) {
115
+ if (!WATCH_FALLBACK_REASONS.includes(reason)) return;
116
+ watchFallbackTally.set(reason, (watchFallbackTally.get(reason) || 0) + 1);
117
+ _warn(`watch_mode_fallback reason=${reason} worker=${workerId || '-'}`);
118
+ }
119
+
120
+ /** D-8: a fresh copy of the per-reason fallback counts plus the window's start time. */
121
+ function watchFallbackCounts() {
122
+ const counts = {};
123
+ for (const reason of WATCH_FALLBACK_REASONS) counts[reason] = watchFallbackTally.get(reason) || 0;
124
+ counts.since = watchFallbackSince;
125
+ return counts;
126
+ }
127
+
105
128
  function _write(method, ...args) {
106
129
  const fail = (err) => _warn(`${method} failed: ${err && err.message ? err.message : err}`);
107
130
  try {
@@ -153,6 +176,8 @@ function _resetForTests() {
153
176
  openHandoffs = new Map();
154
177
  closedWorkers = new Set();
155
178
  lastWarnAt = 0;
179
+ watchFallbackTally = new Map();
180
+ watchFallbackSince = new Date().toISOString();
156
181
  }
157
182
 
158
183
  // --- the classification of one watcher decision --------------------------------------
@@ -354,7 +379,7 @@ function _toolLogOf(payload) {
354
379
  return Object.entries(counts).map(([tool, count]) => ({ tool, path: String(count) }));
355
380
  }
356
381
 
357
- function _trackHandoff(call, res, clientInfo, nowMs) {
382
+ function _trackHandoff(call, res, clientInfo, nowMs, lead) {
358
383
  if (!res) return;
359
384
  if (call.tool === 'walle_delegate_implementation') {
360
385
  const ctmSessionId = _string(res.ctm_session_id).trim();
@@ -378,6 +403,9 @@ function _trackHandoff(call, res, clientInfo, nowMs) {
378
403
  opened_at: nowMs,
379
404
  harness: res.harness == null ? null : res.harness,
380
405
  brief_path: res.rendered_brief_path || null,
406
+ // D-5: the lead that owns this explore row, so a later lead-cost measurement can join the
407
+ // cell on it (helper_outcomes.lead_usd stays NULL).
408
+ lead_session_id: lead || null,
381
409
  }),
382
410
  label_version: LABEL_VERSION,
383
411
  });
@@ -409,7 +437,7 @@ function _trackHandoff(call, res, clientInfo, nowMs) {
409
437
  // The grade card is built HERE, while the terminal payload is in scope; the grade itself waits
410
438
  // for the row's seq, which only the close write can return.
411
439
  const toolLog = _toolLogOf(payload);
412
- _writeThen('helperRulesCloseOutcomeByWorker', [workerId, { facts }], (result) => _gradeIfSampled(result, facts, toolLog));
440
+ _writeThen('helperRulesCloseOutcomeByWorker', [workerId, { facts }], (result) => _gradeIfSampled(result, facts, toolLog, workerId));
413
441
  }
414
442
 
415
443
  // --- the 1-in-5 spot check (WP-6, §6.5/D-19) -------------------------------------------
@@ -437,7 +465,7 @@ function _gradeDeps() {
437
465
  // Fire-and-forget: the close already landed, so a grader outage costs a sample, never the write.
438
466
  // S-3 (round 2): the store derives the label and returns it, so "was this a hand-off?" is read
439
467
  // from `result.labelled`, and the card carries the report/log PATHS (grade.js reads them).
440
- function _gradeIfSampled(result, facts, toolLog) {
468
+ function _gradeIfSampled(result, facts, toolLog, workerId) {
441
469
  if (!result || result.closed !== true) return;
442
470
  if (!result.labelled || result.labelled.decision !== 'handoff') return; // only an accepted hand-off
443
471
  if (!shouldGrade(result.seq)) return; // D-12: every EXPLORE_GRADE_EVERY-th, in every cell state
@@ -459,7 +487,9 @@ function _gradeIfSampled(result, facts, toolLog) {
459
487
  }
460
488
  // REPORT: gradeHandoff turns a failed call into an unavailable sample, so the tail below only
461
489
  // covers a throw from the write itself -- both are fire-and-forget by contract.
462
- gradeHandoff(card, { completeForRole: deps.completeForRole, graderModel: readGraderModel(deps.db), brain: deps.brain })
490
+ // D-5: the closing worker's id rides the grader call as `sessionId`, so the grader's ledger row
491
+ // joins the same cell the watcher spend is keyed by (round 2 WP-4a's convention).
492
+ gradeHandoff(card, { completeForRole: deps.completeForRole, graderModel: readGraderModel(deps.db), brain: deps.brain, sessionId: workerId })
463
493
  .then((grade) => _write('helperRulesSetGrade', id, gradeToJson(grade)))
464
494
  .catch((err) => _warn(`helperRulesSetGrade failed: ${err && err.message ? err.message : err}`));
465
495
  }
@@ -482,7 +512,7 @@ function _record(options) {
482
512
  _rememberDelegation(lead, call, res);
483
513
  _openWatcherOutcomes(lead, call, res, nowMs);
484
514
  _recordLateEnd(call, res, nowMs);
485
- _trackHandoff(call, res, clientInfo, nowMs);
515
+ _trackHandoff(call, res, clientInfo, nowMs, lead);
486
516
 
487
517
  const ring = callsByLead.get(lead) || [];
488
518
  ring.push(call);
@@ -515,8 +545,11 @@ module.exports = {
515
545
  TERMINAL_STATUSES,
516
546
  WAIT_TOOLS,
517
547
  WARN_THROTTLE_MS,
548
+ WATCH_FALLBACK_REASONS,
518
549
  _resetForTests,
519
550
  leadWorkersOf,
551
+ noteWatchFallback,
520
552
  recordLeadToolCall,
553
+ watchFallbackCounts,
521
554
  workerRouteOf,
522
555
  };
@@ -625,14 +625,16 @@ function setCellState(d, cellKey, { state, user_veto, reason, evidence } = {}) {
625
625
  * 1. `sweepAbandoned` — D-10's 4 h TTL, so a row no lead ever closed stops occupying the window;
626
626
  * 2. `relabel` — D-5's recompute + D-6's one-time recovery (a no-op once the kv watermark is
627
627
  * current, which is why it can ride the daily tick at all);
628
- * 3. the evaluator over every explore cell, as before — this is what advances `off → probe`.
628
+ * 3. the evaluator over every explore and wait-watch cell — this is what advances `off → probe`
629
+ * for both kinds (WP-2: the wait-watch cell's probe timer needs the same daily clock the
630
+ * explore cell's already had).
629
631
  * @returns {{cells: Array<{cell_key:string, state:string, changed:boolean}>, abandoned:number,
630
632
  * relabelled:number}}
631
633
  */
632
634
  function dailyTick(d) {
633
635
  const abandoned = sweepAbandoned(d, {});
634
636
  const relabelled = relabel(d, {});
635
- const keys = d.prepare("SELECT cell_key FROM helper_rule_cells WHERE kind = 'explore' OR cell_key LIKE 'explore|%'")
637
+ const keys = d.prepare("SELECT cell_key FROM helper_rule_cells WHERE kind IN ('explore','wait-watch') OR cell_key LIKE 'explore|%' OR cell_key LIKE 'wait-watch|%'")
636
638
  .all()
637
639
  .map((row) => row.cell_key);
638
640
  const run = d.transaction(() => keys.map((key) => evaluateAndApply(d, key)));