create-walle 0.10.1 → 0.10.2

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 (341) hide show
  1. package/README.md +4 -0
  2. package/lib/owner-write-safety.js +135 -0
  3. package/lib/public-runtime.js +13 -5
  4. package/lib/release-schema.json +3 -2
  5. package/lib/runtime-closure-policy.json +6 -4
  6. package/package.json +1 -1
  7. package/template/bin/deps-doctor.js +1 -1
  8. package/template/bin/node-bin.sh +11 -0
  9. package/template/coding-task-manager/api-prompts.js +10 -22
  10. package/template/coding-task-manager/bin/claude-hook.js +7 -3
  11. package/template/coding-task-manager/bin/claude-table-parity.js +307 -0
  12. package/template/coding-task-manager/bin/conversation-storage-auto-migration-lib.js +16 -1
  13. package/template/coding-task-manager/bin/conversation-storage-generation-evidence.js +9 -2
  14. package/template/coding-task-manager/bin/conversation-storage-service-entry-lib.js +16 -1
  15. package/template/coding-task-manager/bin/conversation-storage-walle-owner.js +247 -0
  16. package/template/coding-task-manager/bin/ctm-machine-pair.js +160 -0
  17. package/template/coding-task-manager/bin/lead-wait-census.js +1348 -0
  18. package/template/coding-task-manager/bin/repair-foreign-clone.js +564 -0
  19. package/template/coding-task-manager/bin/repair-resume-echo-titles.js +248 -0
  20. package/template/coding-task-manager/bin/write-lock-bench.js +111 -0
  21. package/template/coding-task-manager/db.js +705 -286
  22. package/template/coding-task-manager/git-utils.js +44 -0
  23. package/template/coding-task-manager/lib/adopted-host-redial.js +4 -0
  24. package/template/coding-task-manager/lib/agent-presets.js +15 -0
  25. package/template/coding-task-manager/lib/agent-title-apply.js +88 -0
  26. package/template/coding-task-manager/lib/agent-title-backfill-read.js +92 -0
  27. package/template/coding-task-manager/lib/api/host-stats.js +98 -0
  28. package/template/coding-task-manager/lib/api/ingress.js +35 -12
  29. package/template/coding-task-manager/lib/api/machine-pairing.js +285 -0
  30. package/template/coding-task-manager/lib/api/machine-pool.js +307 -0
  31. package/template/coding-task-manager/lib/api/machines.js +461 -0
  32. package/template/coding-task-manager/lib/api/misc-ops.js +20 -5
  33. package/template/coding-task-manager/lib/api/providers.js +53 -6
  34. package/template/coding-task-manager/lib/api/resume-from-store.js +25 -0
  35. package/template/coding-task-manager/lib/api/sessions.js +160 -8
  36. package/template/coding-task-manager/lib/api/transcript-history.js +17 -4
  37. package/template/coding-task-manager/lib/api/worktrees.js +38 -37
  38. package/template/coding-task-manager/lib/approval-drift.js +16 -1
  39. package/template/coding-task-manager/lib/approval-logical-frame.js +5 -1
  40. package/template/coding-task-manager/lib/approval-scope.js +68 -4
  41. package/template/coding-task-manager/lib/auth-context.js +32 -2
  42. package/template/coding-task-manager/lib/auth-rate-limit.js +41 -1
  43. package/template/coding-task-manager/lib/auth-rules.js +186 -45
  44. package/template/coding-task-manager/lib/broadcast-serialize.js +2 -1
  45. package/template/coding-task-manager/lib/browser-session-broker.js +9 -1
  46. package/template/coding-task-manager/lib/browser-use-adapter.js +33 -2
  47. package/template/coding-task-manager/lib/claude-markdown-ansi.js +27 -85
  48. package/template/coding-task-manager/lib/claude-session-title.js +6 -30
  49. package/template/coding-task-manager/lib/claude-table-layout.js +322 -0
  50. package/template/coding-task-manager/lib/client-diag-handler.js +5 -0
  51. package/template/coding-task-manager/lib/codex-config-guard.js +33 -3
  52. package/template/coding-task-manager/lib/codex-harness-qualification.js +12 -0
  53. package/template/coding-task-manager/lib/codex-launch-health.js +29 -2
  54. package/template/coding-task-manager/lib/codex-link-preparation.js +9 -8
  55. package/template/coding-task-manager/lib/codex-paths.js +2 -1
  56. package/template/coding-task-manager/lib/coding-agent-models.js +21 -30
  57. package/template/coding-task-manager/lib/coding-session-model-adapters.js +27 -2
  58. package/template/coding-task-manager/lib/coding-session-model-projection.js +5 -0
  59. package/template/coding-task-manager/lib/coding-session-model-runtime.js +1 -2
  60. package/template/coding-task-manager/lib/coding-session-model-state.js +10 -0
  61. package/template/coding-task-manager/lib/coding-session-model-sync-state.js +0 -1
  62. package/template/coding-task-manager/lib/conversation-import/cursor-guard-owner.js +10 -0
  63. package/template/coding-task-manager/lib/create/resolve-launch-params.js +8 -1
  64. package/template/coding-task-manager/lib/ctm-boot-lifecycle.js +17 -1
  65. package/template/coding-task-manager/lib/db-domain-tables.js +7 -0
  66. package/template/coding-task-manager/lib/db-domains.js +21 -2
  67. package/template/coding-task-manager/lib/db-owner-op-registry.js +7 -0
  68. package/template/coding-task-manager/lib/db-schema-source-files.js +1 -0
  69. package/template/coding-task-manager/lib/feature-manifest.js +1 -1
  70. package/template/coding-task-manager/lib/fs-watcher.js +3 -2
  71. package/template/coding-task-manager/lib/harness-history-transition.js +3 -3
  72. package/template/coding-task-manager/lib/headless-inflight-ops.js +203 -0
  73. package/template/coding-task-manager/lib/headless-missing-reply.js +42 -0
  74. package/template/coding-task-manager/lib/headless-term-service.js +55 -17
  75. package/template/coding-task-manager/lib/host-control-dial.js +167 -0
  76. package/template/coding-task-manager/lib/host-handoff-orchestrator.js +16 -2
  77. package/template/coding-task-manager/lib/host-handoff.js +51 -13
  78. package/template/coding-task-manager/lib/host-health-ledger.js +164 -12
  79. package/template/coding-task-manager/lib/host-self-truth-watchdog.js +4 -0
  80. package/template/coding-task-manager/lib/host-stats.js +328 -0
  81. package/template/coding-task-manager/lib/host-threadpool-evacuation.js +160 -0
  82. package/template/coding-task-manager/lib/host-threadpool-monitor.js +14 -1
  83. package/template/coding-task-manager/lib/hosted-input-dispatch.js +203 -0
  84. package/template/coding-task-manager/lib/hosted-sync-worker.js +113 -5
  85. package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +4 -0
  86. package/template/coding-task-manager/lib/instance-manifest-runtime.js +3 -0
  87. package/template/coding-task-manager/lib/instance-manifest.js +13 -1
  88. package/template/coding-task-manager/lib/instance-sync-status.js +15 -0
  89. package/template/coding-task-manager/lib/launch-model-override.js +160 -1
  90. package/template/coding-task-manager/lib/launch-plan-worker.js +59 -2
  91. package/template/coding-task-manager/lib/lead-model-switch-runtime.js +57 -4
  92. package/template/coding-task-manager/lib/lead-model-switch.js +3 -2
  93. package/template/coding-task-manager/lib/lead-model-transition-store.js +61 -3
  94. package/template/coding-task-manager/lib/legacy-draft-cancellation-recovery.js +111 -0
  95. package/template/coding-task-manager/lib/login-tripwire.js +1 -0
  96. package/template/coding-task-manager/lib/machine-pairing.js +238 -0
  97. package/template/coding-task-manager/lib/machine-pool.js +409 -0
  98. package/template/coding-task-manager/lib/machine-registry.js +749 -0
  99. package/template/coding-task-manager/lib/machine-self.js +66 -0
  100. package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +52 -4
  101. package/template/coding-task-manager/lib/media-journal-control-reader.js +473 -0
  102. package/template/coding-task-manager/lib/media-journal.js +84 -27
  103. package/template/coding-task-manager/lib/media-store-options.js +43 -15
  104. package/template/coding-task-manager/lib/migrations/v56-host-samples-and-machine-pool.js +76 -0
  105. package/template/coding-task-manager/lib/mirror-feed-sanitize.js +12 -3
  106. package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
  107. package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
  108. package/template/coding-task-manager/lib/model-proxy/handoff.js +494 -0
  109. package/template/coding-task-manager/lib/model-proxy/lead-worker-picks.js +25 -0
  110. package/template/coding-task-manager/lib/model-proxy/native-lead-listener.js +61 -7
  111. package/template/coding-task-manager/lib/model-proxy/proxy-cli.js +200 -0
  112. package/template/coding-task-manager/lib/model-proxy/proxy-instance-record.js +257 -0
  113. package/template/coding-task-manager/lib/model-proxy/stream-accounting.js +205 -0
  114. package/template/coding-task-manager/lib/model-proxy/tokens.js +78 -0
  115. package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
  116. package/template/coding-task-manager/lib/native-history-control.js +2 -2
  117. package/template/coding-task-manager/lib/native-lead-settings-client.js +2 -1
  118. package/template/coding-task-manager/lib/native-lead-tls.js +18 -4
  119. package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +628 -64
  120. package/template/coding-task-manager/lib/owner-action-manifest-data.json +14 -0
  121. package/template/coding-task-manager/lib/painted-row-recorder.js +9 -1
  122. package/template/coding-task-manager/lib/prompt-submission-contract.js +21 -1
  123. package/template/coding-task-manager/lib/provider-login-broker.js +192 -9
  124. package/template/coding-task-manager/lib/provider-login-capture.js +153 -0
  125. package/template/coding-task-manager/lib/queue-draft-status-overlay.js +36 -0
  126. package/template/coding-task-manager/lib/raw-codex-history-window.js +5 -3
  127. package/template/coding-task-manager/lib/read-pool-client.js +5 -2
  128. package/template/coding-task-manager/lib/read-pool-op-registry.js +10 -1
  129. package/template/coding-task-manager/lib/recent-catalog-query.js +26 -0
  130. package/template/coding-task-manager/lib/recent-session-catalog-response.js +9 -0
  131. package/template/coding-task-manager/lib/remote-command-context.js +12 -0
  132. package/template/coding-task-manager/lib/remote-command-runtime.js +13 -1
  133. package/template/coding-task-manager/lib/remote-machine-catalog.js +1062 -0
  134. package/template/coding-task-manager/lib/remote-machine-create.js +693 -0
  135. package/template/coding-task-manager/lib/remote-machine-link.js +939 -0
  136. package/template/coding-task-manager/lib/remote-machine-proxy.js +520 -0
  137. package/template/coding-task-manager/lib/remote-session-forward.js +306 -0
  138. package/template/coding-task-manager/lib/remote-submission-status.js +6 -0
  139. package/template/coding-task-manager/lib/restart-outcome-truth.js +20 -3
  140. package/template/coding-task-manager/lib/restart-preflight.js +9 -2
  141. package/template/coding-task-manager/lib/restore-backoff-gate.js +10 -2
  142. package/template/coding-task-manager/lib/restore-prepare-retry.js +12 -5
  143. package/template/coding-task-manager/lib/restore-resume-batch.js +22 -1
  144. package/template/coding-task-manager/lib/resume-blocked-wire.js +62 -1
  145. package/template/coding-task-manager/lib/resume-cwd.js +13 -0
  146. package/template/coding-task-manager/lib/resume-from-store.js +12 -1
  147. package/template/coding-task-manager/lib/resume-origin-facts.js +38 -1
  148. package/template/coding-task-manager/lib/resume-preflight.js +21 -16
  149. package/template/coding-task-manager/lib/review-repo-admission.js +39 -6
  150. package/template/coding-task-manager/lib/review-repo-registry.js +17 -0
  151. package/template/coding-task-manager/lib/root-delivery-runtime.js +72 -15
  152. package/template/coding-task-manager/lib/root-delivery-selection.js +37 -1
  153. package/template/coding-task-manager/lib/root-prompt-admission.js +33 -2
  154. package/template/coding-task-manager/lib/root-prompt-flow.js +167 -33
  155. package/template/coding-task-manager/lib/scrollback-archive-store.js +7 -4
  156. package/template/coding-task-manager/lib/session-analysis-memory.js +2 -0
  157. package/template/coding-task-manager/lib/session-create/codex-native-provider-gate.js +218 -0
  158. package/template/coding-task-manager/lib/session-create/codex-resume-exit-classify.js +52 -0
  159. package/template/coding-task-manager/lib/session-create/delegate-worker.js +95 -6
  160. package/template/coding-task-manager/lib/session-create/identity.js +20 -1
  161. package/template/coding-task-manager/lib/session-create/prepared.js +69 -1
  162. package/template/coding-task-manager/lib/session-create/provider-launch-context.js +77 -0
  163. package/template/coding-task-manager/lib/session-create/provider-process.js +34 -37
  164. package/template/coding-task-manager/lib/session-create/response.js +30 -12
  165. package/template/coding-task-manager/lib/session-create/transcript.js +13 -4
  166. package/template/coding-task-manager/lib/session-create/worker-continuation.js +43 -1
  167. package/template/coding-task-manager/lib/session-create/worker-notices.js +94 -5
  168. package/template/coding-task-manager/lib/session-create/worker-observability.js +99 -7
  169. package/template/coding-task-manager/lib/session-create/worker-stop.js +11 -3
  170. package/template/coding-task-manager/lib/session-create/worker-wait-watcher.js +315 -0
  171. package/template/coding-task-manager/lib/session-geometry-authority-handler.js +10 -1
  172. package/template/coding-task-manager/lib/session-host-manager.js +152 -206
  173. package/template/coding-task-manager/lib/session-jobs.js +17 -9
  174. package/template/coding-task-manager/lib/session-lifecycle.js +18 -0
  175. package/template/coding-task-manager/lib/session-model-action-preconditions.js +9 -0
  176. package/template/coding-task-manager/lib/session-resource-sampler.js +19 -9
  177. package/template/coding-task-manager/lib/session-standup.js +67 -4
  178. package/template/coding-task-manager/lib/session-status-ledger.js +42 -3
  179. package/template/coding-task-manager/lib/session-status-runtime.js +45 -1
  180. package/template/coding-task-manager/lib/session-title-mirror.js +25 -57
  181. package/template/coding-task-manager/lib/session-transcript-trash.js +8 -1
  182. package/template/coding-task-manager/lib/startup-pointer-write.js +57 -0
  183. package/template/coding-task-manager/lib/state-sync/painted-frame-field.js +11 -0
  184. package/template/coding-task-manager/lib/storage/coding-session-model-store.js +21 -22
  185. package/template/coding-task-manager/lib/surviving-owner-bootstrap.js +7 -2
  186. package/template/coding-task-manager/lib/surviving-owner-recovery.js +2 -1
  187. package/template/coding-task-manager/lib/terminal-draft-http-status.js +25 -0
  188. package/template/coding-task-manager/lib/transcript-archiver.js +229 -229
  189. package/template/coding-task-manager/lib/transcript-frame-cache.js +12 -2
  190. package/template/coding-task-manager/lib/transcript-guard-job.js +55 -4
  191. package/template/coding-task-manager/lib/transcript-link.js +77 -0
  192. package/template/coding-task-manager/lib/transcript-manifest-store.js +8 -4
  193. package/template/coding-task-manager/lib/transcript-source.js +8 -3
  194. package/template/coding-task-manager/lib/transcript-store.js +40 -0
  195. package/template/coding-task-manager/lib/tunnel-lane-governor.js +6 -3
  196. package/template/coding-task-manager/lib/typing-interaction-trace.js +92 -0
  197. package/template/coding-task-manager/lib/typing-worker-stages.js +60 -0
  198. package/template/coding-task-manager/lib/visible-revision-watchdog.js +8 -12
  199. package/template/coding-task-manager/lib/wake-skip-reasons.js +46 -0
  200. package/template/coding-task-manager/lib/walle-supervisor.js +98 -14
  201. package/template/coding-task-manager/lib/worker-failure-classify.js +13 -1
  202. package/template/coding-task-manager/lib/ws/state-sync.js +19 -2
  203. package/template/coding-task-manager/lib/ws/terminal-attach.js +32 -1
  204. package/template/coding-task-manager/lib/ws-router.js +68 -1
  205. package/template/coding-task-manager/lib/ws-send-admission.js +22 -4
  206. package/template/coding-task-manager/oauth-proxy.js +99 -55
  207. package/template/coding-task-manager/package-lock.json +24 -0
  208. package/template/coding-task-manager/package.json +3 -0
  209. package/template/coding-task-manager/providers/codex-mcp-extract.js +121 -19
  210. package/template/coding-task-manager/providers/codex-mcp.js +14 -5
  211. package/template/coding-task-manager/public/css/app.css +279 -1
  212. package/template/coding-task-manager/public/css/reviews.css +4 -0
  213. package/template/coding-task-manager/public/index.html +294 -223
  214. package/template/coding-task-manager/public/js/app/machines-page.js +1252 -0
  215. package/template/coding-task-manager/public/js/app/models-tab.js +546 -36
  216. package/template/coding-task-manager/public/js/app/worktrees-panel.js +7 -5
  217. package/template/coding-task-manager/public/js/background-work.js +13 -7
  218. package/template/coding-task-manager/public/js/coding-session-model-presentation.js +44 -4
  219. package/template/coding-task-manager/public/js/core/session-commands.js +676 -0
  220. package/template/coding-task-manager/public/js/core/session-selectors.js +5 -1
  221. package/template/coding-task-manager/public/js/history-app-seam.js +26 -2
  222. package/template/coding-task-manager/public/js/history-painted-rows.js +17 -2
  223. package/template/coding-task-manager/public/js/history-reader-park.js +13 -1
  224. package/template/coding-task-manager/public/js/history-scroller.js +187 -9
  225. package/template/coding-task-manager/public/js/history-transcript-pages.js +5 -5
  226. package/template/coding-task-manager/public/js/machine-pool-shared.js +436 -0
  227. package/template/coding-task-manager/public/js/private-state-scope.js +31 -3
  228. package/template/coding-task-manager/public/js/prompt-submission-client.js +173 -8
  229. package/template/coding-task-manager/public/js/prompt-submission-tray.js +281 -0
  230. package/template/coding-task-manager/public/js/queue-panel-submission-reconcile.js +174 -1
  231. package/template/coding-task-manager/public/js/reviews.js +7 -3
  232. package/template/coding-task-manager/public/js/root-draft-lifetime.js +139 -2
  233. package/template/coding-task-manager/public/js/root-queue-builder-lifetime.js +16 -0
  234. package/template/coding-task-manager/public/js/row-join.js +4 -2
  235. package/template/coding-task-manager/public/js/session-model-picker.js +1 -1
  236. package/template/coding-task-manager/public/js/session-search-utils.js +46 -19
  237. package/template/coding-task-manager/public/js/state-sync-client.js +4 -0
  238. package/template/coding-task-manager/public/js/terminal/runtime-02.js +16 -7
  239. package/template/coding-task-manager/public/js/terminal/runtime-03.js +41 -0
  240. package/template/coding-task-manager/public/js/terminal/runtime-04.js +41 -4
  241. package/template/coding-task-manager/public/js/terminal/runtime-12.js +52 -2
  242. package/template/coding-task-manager/public/js/terminal/runtime-16.js +12 -12
  243. package/template/coding-task-manager/public/js/terminal/runtime-18.js +14 -0
  244. package/template/coding-task-manager/public/js/terminal/runtime-19.js +8 -4
  245. package/template/coding-task-manager/public/js/terminal/runtime-20.js +210 -73
  246. package/template/coding-task-manager/public/js/terminal/runtime-21.js +19 -6
  247. package/template/coding-task-manager/public/js/terminal/runtime-22.js +86 -0
  248. package/template/coding-task-manager/public/js/terminal-history-viewport.js +1 -1
  249. package/template/coding-task-manager/public/js/terminal-wheel.js +39 -2
  250. package/template/coding-task-manager/public/js/walle/chat-conversation.js +11 -3
  251. package/template/coding-task-manager/public/js/walle-session.js +30 -7
  252. package/template/coding-task-manager/public/m/app.css +226 -8
  253. package/template/coding-task-manager/public/m/app.js +502 -9
  254. package/template/coding-task-manager/public/m/index.html +38 -1
  255. package/template/coding-task-manager/public/m/machines.js +1313 -0
  256. package/template/coding-task-manager/public/m/pool-failover.js +391 -0
  257. package/template/coding-task-manager/public/m/private-queue-actions.js +26 -7
  258. package/template/coding-task-manager/public/m/sw.js +5 -0
  259. package/template/coding-task-manager/queue-engine.js +13 -0
  260. package/template/coding-task-manager/server/approvals.js +5 -6
  261. package/template/coding-task-manager/server/boot.js +136 -17
  262. package/template/coding-task-manager/server.js +902 -239
  263. package/template/coding-task-manager/session-integrity.js +7 -9
  264. package/template/coding-task-manager/workers/db-owner-worker.js +53 -7
  265. package/template/coding-task-manager/workers/read-pool-worker.js +17 -6
  266. package/template/coding-task-manager/workers/session-host-pool-process.js +18 -4
  267. package/template/coding-task-manager/workers/session-host-process.js +14 -3
  268. package/template/coding-task-manager/workers/transcript-archive-worker.js +1 -1
  269. package/template/coding-task-manager/workers/worktree-worker.js +34 -0
  270. package/template/package-lock.json +2 -2
  271. package/template/package.json +1 -1
  272. package/template/scripts/conversation-storage-v2-migration.js +78 -11
  273. package/template/shared/conversation-storage-v2-contract.js +42 -6
  274. package/template/shared/fixtures/worker-status-payloads.json +392 -0
  275. package/template/shared/git-remote-key.js +83 -0
  276. package/template/shared/host-identity.js +98 -2
  277. package/template/shared/owner-write-safety.js +135 -0
  278. package/template/shared/public-runtime.js +13 -5
  279. package/template/shared/release-schema.json +3 -2
  280. package/template/shared/report-citations.js +14 -0
  281. package/template/shared/sqlite-write-lock.js +361 -223
  282. package/template/shared/storage/contract.js +35 -3
  283. package/template/wall-e/agent.js +21 -0
  284. package/template/wall-e/api-walle.js +5 -0
  285. package/template/wall-e/bin/brain-purge-legacy-ctm.js +3 -2
  286. package/template/wall-e/bin/walle-mcp-stdio.js +12 -6
  287. package/template/wall-e/brain/helper-rules-schema.js +129 -0
  288. package/template/wall-e/brain/migrations.js +51 -22
  289. package/template/wall-e/brain/model-deployment-facts.js +31 -1
  290. package/template/wall-e/brain/owner-call-whitelist.js +16 -0
  291. package/template/wall-e/brain/schema-ensures.js +22 -0
  292. package/template/wall-e/brain.js +1 -1
  293. package/template/wall-e/coding/journal-dispatcher.js +51 -0
  294. package/template/wall-e/coding/session-journal.js +28 -40
  295. package/template/wall-e/coding-orchestrator.js +23 -26
  296. package/template/wall-e/http/auth.js +18 -1
  297. package/template/wall-e/http/blog-views-api.js +318 -0
  298. package/template/wall-e/http/helper-rules-api.js +321 -0
  299. package/template/wall-e/http/model-proxy-providers.js +3 -1
  300. package/template/wall-e/http/telemetry-api.js +12 -0
  301. package/template/wall-e/lib/blog-view-policy.js +88 -0
  302. package/template/wall-e/lib/blog-view-store.js +169 -0
  303. package/template/wall-e/lib/client-config-lock.js +2 -1
  304. package/template/wall-e/lib/coding-journal-writer-child-client.js +67 -18
  305. package/template/wall-e/lib/error-kinds.js +10 -1
  306. package/template/wall-e/lib/feature-manifest.js +1 -1
  307. package/template/wall-e/lib/helper-rules/cell-state.js +123 -0
  308. package/template/wall-e/lib/helper-rules/evaluate.js +357 -0
  309. package/template/wall-e/lib/helper-rules/explore-rubric.md +12 -0
  310. package/template/wall-e/lib/helper-rules/grade.js +277 -0
  311. package/template/wall-e/lib/helper-rules/label.js +247 -0
  312. package/template/wall-e/lib/helper-rules/lead-call-hook.js +522 -0
  313. package/template/wall-e/lib/helper-rules/store.js +667 -0
  314. package/template/wall-e/lib/mcp-integration.js +5 -6
  315. package/template/wall-e/lib/runtime-worker-pool.js +1 -0
  316. package/template/wall-e/lib/scheduler-worker-jobs.js +6 -0
  317. package/template/wall-e/lib/telemetry-db-schema.js +37 -0
  318. package/template/wall-e/lib/telemetry-process-client.js +76 -1
  319. package/template/wall-e/llm/default-fallback.js +4 -0
  320. package/template/wall-e/llm/model-catalog.js +13 -4
  321. package/template/wall-e/llm/model-roles.js +6 -0
  322. package/template/wall-e/llm/portkey-http.js +150 -0
  323. package/template/wall-e/llm/portkey-sync.js +15 -145
  324. package/template/wall-e/llm/worker-mcp-routing.js +61 -0
  325. package/template/wall-e/llm/worker-route.js +43 -1
  326. package/template/wall-e/loops/brain-custom-writes.js +45 -0
  327. package/template/wall-e/mcp-protocol-contract.js +41 -1
  328. package/template/wall-e/mcp-server.js +13 -2
  329. package/template/wall-e/mcp-worker-schemas.js +20 -2
  330. package/template/wall-e/memory/ctm-context-client.js +25 -5
  331. package/template/wall-e/shared/release-schema.json +3 -2
  332. package/template/wall-e/shared/sqlite-write-lock.js +361 -223
  333. package/template/wall-e/shared/storage/contract.js +35 -3
  334. package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
  335. package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +6 -0
  336. package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +7 -2
  337. package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +7 -2
  338. package/template/wall-e/tools/local-tools.js +6 -4
  339. package/template/wall-e/workers/brain-owner-worker.js +9 -0
  340. package/template/wall-e/workers/coding-journal-writer-child.js +8 -1
  341. package/template/wall-e/workers/telemetry-worker.js +8 -0
@@ -20,7 +20,6 @@ const SAFE_ENV_KEYS = [
20
20
  'WALL_E_SQLITE_STORAGE_POLICY',
21
21
  'WALL_E_SQLITE_BUSY_TIMEOUT_MS',
22
22
  'WALL_E_SQLITE_WRITE_LOCK_TIMEOUT_MS',
23
- 'WALL_E_SQLITE_WRITE_LOCK_STALE_MS',
24
23
  'WALL_E_BRAIN_BOOT_LOCK_WAIT_MS',
25
24
  'WALL_E_SQLITE_SYNCHRONOUS',
26
25
  'WALL_E_SQLITE_WAL_AUTOCHECKPOINT_PAGES',
@@ -82,16 +81,48 @@ function writeJournalEntryInChild(entry, options = {}) {
82
81
  serialization: 'json',
83
82
  });
84
83
  } catch (error) {
84
+ // C7: a fork that threw synchronously returned no child, so this call owns
85
+ // nothing to watch and nothing to terminate — reject with the original value.
85
86
  return Promise.reject(error);
86
87
  }
87
88
 
88
89
  return new Promise((resolve, reject) => {
90
+ // C7: the application result (one reply envelope) and the native lifecycle
91
+ // (exit/close) are two separate facts about the same child. Only the child's
92
+ // own 'close' finalizes this operation — a reply envelope or 'exit' alone
93
+ // leaves the operation pending, because the child may still be running (or
94
+ // may still die badly) after either one.
95
+ let replied = false;
96
+ let replyResult = null;
97
+ let failed = false; // explicit Boolean: a falsy thrown value is still a failure
98
+ let failure = null;
99
+ let nativeExited = false; // 'exit' seen: the process is gone, its stdio is not necessarily closed
100
+ let nativeClosed = false; // 'close' seen: the only fact that finalizes this operation
101
+ let terminationRequested = false;
89
102
  let settled = false;
103
+
104
+ const recordFailure = (error) => {
105
+ // The FIRST failure wins, verbatim (identity and value): later errors, the
106
+ // deadline and a nonzero native close must never replace it.
107
+ if (failed) return;
108
+ failed = true;
109
+ failure = error;
110
+ };
111
+ const requestTermination = () => {
112
+ if (terminationRequested) return;
113
+ // Marked BEFORE kill: an 'error' emitted by the kill call itself must not
114
+ // re-enter here and signal the same child twice.
115
+ terminationRequested = true;
116
+ if (nativeExited || nativeClosed) return; // never signal after the native exit/close was observed
117
+ try { child.kill('SIGKILL'); }
118
+ catch { /* expected: the child may already be gone; a throwing kill neither relinquishes ownership nor proves closure — the operation still joins the child's actual close below */ }
119
+ };
90
120
  const cleanup = () => {
91
121
  clearTimeout(timer);
92
122
  child.off('message', onMessage);
93
123
  child.off('error', onError);
94
124
  child.off('exit', onExit);
125
+ child.off('close', onClose);
95
126
  };
96
127
  const settle = (fn, value) => {
97
128
  if (settled) return false;
@@ -101,39 +132,57 @@ function writeJournalEntryInChild(entry, options = {}) {
101
132
  return true;
102
133
  };
103
134
  const onMessage = (message) => {
104
- if (!message || message.type !== 'coding-journal-result') return;
105
- if (message.ok) settle(resolve, message.result || null);
106
- else {
107
- const error = new Error(String(message.error || 'One-shot journal writer failed').slice(0, 500));
108
- if (message.code) error.code = String(message.code).slice(0, 100);
109
- settle(reject, error);
110
- }
135
+ if (!message || message.type !== 'coding-journal-result') return; // unrelated envelope
136
+ if (replied) return; // first valid result wins; duplicate and late envelopes are ignored
137
+ replied = true;
138
+ if (message.ok) { replyResult = message.result || null; return; }
139
+ const error = new Error(String(message.error || 'One-shot journal writer failed').slice(0, 500));
140
+ if (message.code) error.code = String(message.code).slice(0, 100);
141
+ recordFailure(error);
142
+ };
143
+ const onError = (error) => {
144
+ // A process/IPC error is a failure but not a closure: the operation keeps
145
+ // waiting for the child's own close, and this first failure is preserved.
146
+ recordFailure(error);
147
+ requestTermination();
111
148
  };
112
- const onError = (error) => settle(reject, error);
113
- const onExit = (code, signal) => {
149
+ // 'exit' only records that the process is gone; the verdict reads the close
150
+ // event's own (code, signal), so exit and close can never be conflated.
151
+ const onExit = () => { nativeExited = true; };
152
+ const onClose = (code, signal) => {
153
+ nativeClosed = true;
154
+ // A falsy thrown value must not be laundered into a success: the boolean,
155
+ // not the value, says whether a failure happened.
156
+ if (failed) { settle(reject, failure); return; }
114
157
  const suffix = signal ? `signal ${signal}` : `code ${code}`;
115
- settle(reject, new Error(`One-shot journal writer exited before replying (${suffix})`));
158
+ if (!replied) { settle(reject, new Error(`One-shot journal writer exited before replying (${suffix})`)); return; }
159
+ if (code === 0 && signal === null) { settle(resolve, replyResult); return; }
160
+ // Replied, then died badly: the reply alone is not proof the write survived.
161
+ const error = new Error(`One-shot journal writer exited after replying (${suffix})`);
162
+ error.code = 'WALL_E_JOURNAL_WRITER_EXIT';
163
+ settle(reject, error);
116
164
  };
117
165
  const timer = setTimeout(() => {
118
166
  const error = new Error(`One-shot journal writer timed out after ${timeoutMs}ms`);
119
167
  error.code = 'WALL_E_JOURNAL_WRITER_TIMEOUT';
120
- if (!settle(reject, error)) return;
121
- try { child.kill('SIGKILL'); } catch {}
168
+ recordFailure(error); // only the first failure is kept; the deadline joins the close
169
+ requestTermination();
122
170
  }, timeoutMs);
123
171
 
124
172
  child.on('message', onMessage);
125
- child.once('error', onError);
126
- child.once('exit', onExit);
173
+ child.on('error', onError);
174
+ child.on('exit', onExit);
175
+ child.on('close', onClose);
127
176
  try {
128
177
  child.send({
129
178
  type: 'persist-coding-journal',
130
179
  dbPath: path.resolve(dbPath),
131
180
  entry: JSON.parse(entryJson),
132
181
  });
182
+ // A false return is IPC backpressure, never a failure or a cancellation.
133
183
  } catch (error) {
134
- if (settle(reject, error)) {
135
- try { child.kill('SIGKILL'); } catch {}
136
- }
184
+ recordFailure(error);
185
+ requestTermination();
137
186
  }
138
187
  });
139
188
  }
@@ -87,10 +87,19 @@ const CODE_KIND = Object.freeze({
87
87
  ANTHROPIC_WIRE_TOOL_NAME_INVALID: 'FATAL',
88
88
  // shared/sqlite-write-lock.js: process-liveness / lock-file-existence control-flow codes, not
89
89
  // genuine transient failures a retry loop should chase — the lock module already has its own
90
- // stale-window / foreign-hold handling around these.
90
+ // foreign-hold handling around these (protocol 2 has no stale window: a dead holder's kernel
91
+ // lock is dropped by the OS, D-4). EISDIR is the same family as EEXIST: an object at the record
92
+ // path that is not our record file (the fail-fast fixtures use a directory) is somebody else's
93
+ // claim, and SQLITE_WRITE_LOCK_STATE is the lock DB found inside a transaction — a bug state,
94
+ // never a race worth retrying.
91
95
  ESRCH: 'FATAL',
92
96
  ENOENT: 'FATAL',
93
97
  EEXIST: 'FATAL',
98
+ EISDIR: 'FATAL',
99
+ SQLITE_WRITE_LOCK_STATE: 'FATAL',
100
+ // BEGIN EXCLUSIVE on the lock DB lost the race for the EXCLUSIVE lock: contention, exactly like
101
+ // SQLITE_WRITE_LOCK_BUSY above, and won by the same later attempt.
102
+ SQLITE_BUSY: 'WRITE_LOCK_BUSY',
94
103
  CTM_MAIN_WRITE_ESCAPE: 'FATAL',
95
104
  CTM_MAIN_THREAD_WRITE_FORBIDDEN: 'FATAL',
96
105
  // D-7 (WP-2): a loop wrote directly to a brain handle this process opened
@@ -265,7 +265,7 @@ const FEATURES = [
265
265
  // numeric-tunable suffix/read shape; 1 site(s); first: wall-e/brain.js:1800
266
266
  { id: "walle_wall_e_sqlite_wal_autocheckpoint_pages", env: "WALL_E_SQLITE_WAL_AUTOCHECKPOINT_PAGES", owner: "wall-e-core-team", stage: "ops", tunable: true, default: 10, since: "2026-07-02", expires: null, note: "numeric tunable" },
267
267
  // numeric-tunable suffix/read shape; 1 site(s); first: wall-e/brain.js:1769
268
- { id: "walle_wall_e_sqlite_write_lock_stale_ms", env: "WALL_E_SQLITE_WRITE_LOCK_STALE_MS", owner: "wall-e-core-team", stage: "ops", tunable: true, default: 10, since: "2026-05-21", expires: null, note: "numeric tunable" },
268
+ { id: "walle_wall_e_sqlite_write_lock_stale_ms", env: "WALL_E_SQLITE_WRITE_LOCK_STALE_MS", owner: "wall-e-core-team", stage: "removed", defaultOn: false, since: "2026-05-21", expires: null, note: "retired 2026-10-23 (fsevents-churn WP-1, coding-task-manager/docs/fsevents-churn-kernel-write-lock-2026-10-23.html D-4): the SQLite write lock is a kernel lock on a .mutex lock DB released by the OS when its holder dies, so stale-claim recovery and its age knob were deleted; no read of this name survives." },
269
269
  // numeric-tunable suffix/read shape; 1 site(s); first: wall-e/skills/script-skill-runner.js:64
270
270
  { id: "walle_wall_e_sqlite_write_lock_timeout_ms", env: "WALL_E_SQLITE_WRITE_LOCK_TIMEOUT_MS", owner: "wall-e-core-team", stage: "ops", tunable: true, default: 0, since: "2026-05-21", expires: null, note: "numeric tunable" },
271
271
  // bare truthy opt-in read; 1 site(s); first: wall-e/agent.js:55
@@ -0,0 +1,123 @@
1
+ 'use strict';
2
+
3
+ // WP-4 (lead-cost-routing-round1-2026-10-21.html §6.4): the ONE reader of a lead harness's
4
+ // `explore` rule cell, plus the transport-facing plumbing both MCP transports need to gate the
5
+ // exploration hand-off block appended to the `initialize` instructions.
6
+ //
7
+ // The cell is the row `cell_key = 'explore|default|<agent>'` in helper_rule_cells. WP-5 owns the
8
+ // table's migration; this module NEVER creates it. A missing table, a missing row or any read
9
+ // error means "never switched off" -- the state is 'on' (fail-open, the same shape as an absent
10
+ // flag), so a brain built before WP-5 keeps serving today's guidance.
11
+ //
12
+ // States: 'on' (block appended), 'probe' (block appended, WP-5 owns what probe means) and 'off'
13
+ // (plain SERVER_INSTRUCTIONS). Only those three values are recognized; anything else reads 'on'.
14
+
15
+ const EXPLORE_STATES = Object.freeze(['on', 'off', 'probe']);
16
+ const DEFAULT_EXPLORE_STATE = 'on';
17
+ const GUIDANCE_PATH = '/api/wall-e/helper-rules/guidance';
18
+ // The stdio proxy blocks its `initialize` reply on this fetch, so the deadline is short and the
19
+ // failure mode is the fail-open default: a slow or dead daemon may not stall a client handshake.
20
+ const GUIDANCE_TIMEOUT_MS = 1500;
21
+
22
+ function readExploreCellState(db, agent = 'claude') {
23
+ if (!db || typeof db.prepare !== 'function') return DEFAULT_EXPLORE_STATE;
24
+ try {
25
+ const row = db.prepare('SELECT state FROM helper_rule_cells WHERE cell_key = ?')
26
+ .get(`explore|default|${agent}`);
27
+ const state = row && typeof row.state === 'string' ? row.state.trim() : '';
28
+ return EXPLORE_STATES.includes(state) ? state : DEFAULT_EXPLORE_STATE;
29
+ } catch {
30
+ // expected: helper_rule_cells is absent until WP-5's migration runs; a missing row means 'on'
31
+ return DEFAULT_EXPLORE_STATE;
32
+ }
33
+ }
34
+
35
+ function leadAgentFromClientInfo(clientInfo) {
36
+ return /codex/i.test(clientInfo?.name) ? 'codex' : 'claude';
37
+ }
38
+
39
+ // The daemon transport's one call: mcp-server.js already holds the brain module, and its getDb()
40
+ // throws before initDb() -- which must not break the initialize reply, so the read is guarded here
41
+ // rather than at the call site (the call site stays a single line, see mcp-server.js).
42
+ function leadGuidanceForMessage(message, brainModule) {
43
+ // Only `initialize` carries instructions; every other MCP message skips the read entirely
44
+ // (an undefined leadGuidance leaves handleProtocolMessage on its no-option path).
45
+ if (!message || message.method !== 'initialize') return undefined;
46
+ let db = null;
47
+ try {
48
+ db = brainModule && typeof brainModule.getDb === 'function' ? brainModule.getDb() : brainModule;
49
+ } catch {
50
+ // expected: brain not initialized yet (initialize can arrive before/without a DB) -- 'on'
51
+ db = null;
52
+ }
53
+ return { explore: readExploreCellState(db, leadAgentFromClientInfo(message?.params?.clientInfo)) };
54
+ }
55
+
56
+ function guidancePathFor(agent) {
57
+ return `${GUIDANCE_PATH}?agent=${encodeURIComponent(agent)}`;
58
+ }
59
+
60
+ // The read-only GET the stdio transport performs against the daemon (the stdio process does not
61
+ // own the brain). Kept here so bin/walle-mcp-stdio.js -- a frozen-ceiling file -- gains no logic.
62
+ // `host`/`port` arrive from the caller: the transport owns endpoint resolution (no env read here).
63
+ function httpJsonGet({ host = '127.0.0.1', port, path: requestPath, timeoutMs = GUIDANCE_TIMEOUT_MS } = {}) {
64
+ return new Promise((resolve, reject) => {
65
+ const req = require('node:http').request({ host, port, path: requestPath, method: 'GET', timeout: timeoutMs }, (res) => {
66
+ let raw = '';
67
+ res.setEncoding('utf8');
68
+ res.on('data', (chunk) => { raw += chunk; });
69
+ res.on('end', () => {
70
+ if (res.statusCode !== 200) {
71
+ reject(new Error(`helper-rules guidance returned HTTP ${res.statusCode}`));
72
+ return;
73
+ }
74
+ try {
75
+ resolve(JSON.parse(raw));
76
+ } catch (err) {
77
+ reject(err);
78
+ }
79
+ });
80
+ res.on('error', reject);
81
+ });
82
+ req.on('timeout', () => { req.destroy(new Error(`helper-rules guidance timed out after ${timeoutMs}ms`)); });
83
+ req.on('error', reject);
84
+ req.end();
85
+ });
86
+ }
87
+
88
+ // requestFn is the transport's own request machinery: it is handed the guidance path and returns
89
+ // the parsed JSON body (the stdio proxy passes its _executeDaemonRequest breaker wrapper around
90
+ // httpJsonGet). Every failure -- no requestFn, refused, non-2xx, malformed body, or the timeout --
91
+ // resolves to the fail-open default, so the transport always has an answer.
92
+ async function fetchExploreCellState({ requestFn, agent = 'claude', timeoutMs = GUIDANCE_TIMEOUT_MS } = {}) {
93
+ let timer = null;
94
+ try {
95
+ if (typeof requestFn !== 'function') return DEFAULT_EXPLORE_STATE;
96
+ const payload = await Promise.race([
97
+ Promise.resolve(requestFn(guidancePathFor(agent))),
98
+ new Promise((_, reject) => {
99
+ timer = setTimeout(() => reject(new Error(`helper-rules guidance fetch exceeded ${timeoutMs}ms`)), timeoutMs);
100
+ }),
101
+ ]);
102
+ const state = payload && typeof payload.explore === 'string' ? payload.explore.trim() : '';
103
+ return EXPLORE_STATES.includes(state) ? state : DEFAULT_EXPLORE_STATE;
104
+ } catch {
105
+ // expected: daemon unavailable/refusing/malformed -- 'on' keeps today's guidance served
106
+ return DEFAULT_EXPLORE_STATE;
107
+ } finally {
108
+ if (timer) clearTimeout(timer);
109
+ }
110
+ }
111
+
112
+ module.exports = {
113
+ DEFAULT_EXPLORE_STATE,
114
+ EXPLORE_STATES,
115
+ GUIDANCE_PATH,
116
+ GUIDANCE_TIMEOUT_MS,
117
+ fetchExploreCellState,
118
+ guidancePathFor,
119
+ httpJsonGet,
120
+ leadAgentFromClientInfo,
121
+ leadGuidanceForMessage,
122
+ readExploreCellState,
123
+ };
@@ -0,0 +1,357 @@
1
+ 'use strict';
2
+
3
+ // Lead-cost routing WP-5a (design §6.8): the PURE rule evaluator. One function, no I/O —
4
+ // the caller (lib/helper-rules/store.js) reads the cell row and the trailing window and
5
+ // owns every write. This is what makes "why did the cell turn off?" answerable in a test
6
+ // and the panel honest: same inputs, same verdict.
7
+
8
+ const { LABEL_VERSION } = require('./label');
9
+
10
+ const WINDOW = 20;
11
+ const MIN_LABELLED = 10;
12
+ const WATCH_OFF_BELOW = 0.5;
13
+ const WATCH_ON_ABOVE = 0.7;
14
+ const EXPLORE_FALLBACK_MAX = 0.3;
15
+ const EXPLORE_REREAD_MAX = 0.5;
16
+ const EXPLORE_SUFFICIENT_MIN = 0.7;
17
+ const EXPLORE_PROBE_AFTER_DAYS = 7;
18
+ const PROBE_N = 10;
19
+ const GRADED_MIN = 10;
20
+ const DAY_MS = 24 * 60 * 60 * 1000;
21
+
22
+ // Round 2 WP-2 (§5 D-7/D-10, §6.4): the two constants the measurability guard and the open-row
23
+ // sweep need. No round-1 threshold moves — D-7 is additive by design (the design's §5 note).
24
+ const EXPLORE_OPEN_TTL_MS = 4 * 60 * 60 * 1000; // D-10: worker deadline 1,800,000 ms × lease ceiling 4 = 2 h, doubled
25
+ const UNMEASURED_MAX_SHARE = 0.5; // D-7: an unmeasured/abandoned majority proves nothing
26
+
27
+ const CELL_STATES = Object.freeze(['on', 'off', 'probe']);
28
+
29
+ // The decisions an explore row may carry once it is no longer open. Anything else (a NULL from a
30
+ // round-1 write, or a value this rule has never produced) is unmeasured — D-7's "no data is not
31
+ // failure", applied to the row rather than to the window.
32
+ const EXPLORE_DECISIONS = Object.freeze(['open', 'handoff', 'fallback', 'abandoned', 'unmeasured']);
33
+
34
+ // Machine-readable reasons — short, stable strings the events table and the panel show.
35
+ const REASONS = Object.freeze({
36
+ USER_VETO: 'user_veto',
37
+ WATCH_PRECISION_BELOW: `precision_below_${WATCH_OFF_BELOW}`,
38
+ WATCH_SILENT_PRECISION_ABOVE: `silent_precision_above_${WATCH_ON_ABOVE}`,
39
+ EXPLORE_FALLBACK_ABOVE: `fallback_rate_above_${EXPLORE_FALLBACK_MAX}`,
40
+ EXPLORE_REREAD_ABOVE: `reread_rate_above_${EXPLORE_REREAD_MAX}`,
41
+ EXPLORE_HELPER_USD_AT_OR_ABOVE: 'helper_usd_at_or_above_lead_burst',
42
+ EXPLORE_SUFFICIENT_BELOW: `sufficient_rate_below_${EXPLORE_SUFFICIENT_MIN}`,
43
+ EXPLORE_PROBE_AFTER: `probe_after_${EXPLORE_PROBE_AFTER_DAYS}_days`,
44
+ EXPLORE_PROBE_PASSED: 'probe_passed',
45
+ EXPLORE_PROBE_FAILED: 'probe_failed',
46
+ // D-6: the audit row the one-time recovery pass writes on every cell it returns to `on` after
47
+ // the v1 labels it was demoted on turned out to be a labelling bug. Distinct from the reasons
48
+ // above so a query can tell "the evaluator demoted this" from "the record repaired itself".
49
+ LABEL_V2_RESET: 'label_v2_reset',
50
+ });
51
+
52
+ // D-6: the DEMOTION set — the reasons that mean "the evaluator (not the user) turned this cell off
53
+ // or into a probe on a window it could not measure". Only a cell whose LAST event carries one of
54
+ // these may be returned to `on` by the relabel recovery pass; a `user_veto` never is.
55
+ const EXPLORE_DEMOTION_REASONS = Object.freeze(new Set([
56
+ REASONS.EXPLORE_FALLBACK_ABOVE,
57
+ REASONS.EXPLORE_REREAD_ABOVE,
58
+ REASONS.EXPLORE_HELPER_USD_AT_OR_ABOVE,
59
+ REASONS.EXPLORE_SUFFICIENT_BELOW,
60
+ REASONS.EXPLORE_PROBE_FAILED,
61
+ REASONS.EXPLORE_PROBE_AFTER,
62
+ ]));
63
+
64
+ function _stateOf(cell) {
65
+ const state = cell && typeof cell.state === 'string' ? cell.state.trim() : '';
66
+ return CELL_STATES.includes(state) ? state : 'on';
67
+ }
68
+
69
+ function _vetoOf(cell) {
70
+ return Boolean(cell && Number(cell.user_veto) === 1);
71
+ }
72
+
73
+ function _toMs(value) {
74
+ if (value == null) return null;
75
+ if (value instanceof Date) return Number.isFinite(value.getTime()) ? value.getTime() : null;
76
+ if (typeof value === 'number') return Number.isFinite(value) ? value : null;
77
+ const text = String(value).trim();
78
+ if (!text) return null;
79
+ // SQLite's datetime('now') writes 'YYYY-MM-DD HH:MM:SS' in UTC.
80
+ const iso = /^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}$/.test(text) ? `${text.replace(' ', 'T')}Z` : text;
81
+ const ms = Date.parse(iso);
82
+ return Number.isFinite(ms) ? ms : null;
83
+ }
84
+
85
+ function _ratio(numerator, denominator) {
86
+ return denominator > 0 ? numerator / denominator : null;
87
+ }
88
+
89
+ function _median(values) {
90
+ const numbers = values.filter((value) => Number.isFinite(value)).sort((a, b) => a - b);
91
+ if (!numbers.length) return null;
92
+ const middle = Math.floor(numbers.length / 2);
93
+ return numbers.length % 2 === 1 ? numbers[middle] : (numbers[middle - 1] + numbers[middle]) / 2;
94
+ }
95
+
96
+ function _number(value) {
97
+ if (value == null || value === '') return null;
98
+ const number = Number(value);
99
+ return Number.isFinite(number) ? number : null;
100
+ }
101
+
102
+ function _verdict(state, previousState, metrics, reason) {
103
+ return { state, metrics, reason: state === previousState ? null : reason };
104
+ }
105
+
106
+ // --- wait-watch -------------------------------------------------------------------
107
+
108
+ /**
109
+ * D-9 (WP-4b): the watcher spend the window's cell actually accrued. The store reads it from
110
+ * `model_usage_ledger` and hands it over as ONE object, `{usd, rows}` — or null when the window
111
+ * names no worker or the ledger matched no row. These are REPORTED metrics: no threshold in this
112
+ * file reads them (the design's D-9 is a read, never a new rule).
113
+ */
114
+ function _watcherUsdWindow(watcherUsd) {
115
+ if (!watcherUsd || typeof watcherUsd !== 'object') return null;
116
+ if (Number.isFinite(watcherUsd.rows) && watcherUsd.rows <= 0) return null;
117
+ return typeof watcherUsd.usd === 'number' && Number.isFinite(watcherUsd.usd) ? watcherUsd.usd : null;
118
+ }
119
+
120
+ function _watchMetrics(outcomes, watcherUsd) {
121
+ const useful = outcomes.filter((row) => row.label === 'useful').length;
122
+ const empty = outcomes.filter((row) => row.label === 'empty').length;
123
+ const late = outcomes.filter((row) => row.label === 'late').length;
124
+ const silentUseful = outcomes.filter((row) => row.label === 'silent_useful').length;
125
+ const silentEmpty = outcomes.filter((row) => row.label === 'silent_empty').length;
126
+ const labelled = useful + empty;
127
+ const silentLabelled = silentUseful + silentEmpty;
128
+ const watcherUsdWindow = _watcherUsdWindow(watcherUsd);
129
+ return {
130
+ n_window: outcomes.length,
131
+ labelled,
132
+ useful,
133
+ empty,
134
+ late,
135
+ precision: _ratio(useful, labelled),
136
+ silent_labelled: silentLabelled,
137
+ silent_precision: _ratio(silentUseful, silentLabelled),
138
+ collecting: labelled < MIN_LABELLED,
139
+ watcher_usd_window: watcherUsdWindow,
140
+ // null (not 0) when there is no spend to divide or no useful wake to divide it by -- the panel
141
+ // must be able to say "not measured" rather than "$0.000 per wake".
142
+ usd_per_useful_wake: watcherUsdWindow !== null && useful > 0 ? watcherUsdWindow / useful : null,
143
+ };
144
+ }
145
+
146
+ function _evaluateWatch(cellState, outcomes, watcherUsd) {
147
+ const metrics = _watchMetrics(outcomes, watcherUsd);
148
+ if (cellState === 'on') {
149
+ if (metrics.labelled >= MIN_LABELLED && metrics.precision < WATCH_OFF_BELOW) {
150
+ return _verdict('off', cellState, metrics, REASONS.WATCH_PRECISION_BELOW);
151
+ }
152
+ return _verdict(cellState, cellState, metrics, null);
153
+ }
154
+ 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);
160
+ }
161
+ return _verdict(cellState, cellState, metrics, null);
162
+ }
163
+ return _verdict(cellState, cellState, metrics, null);
164
+ }
165
+
166
+ // --- explore ----------------------------------------------------------------------
167
+
168
+ /** The row's parsed `grade_json` object, or null when it carries nothing readable. */
169
+ function _parseGrade(row) {
170
+ if (row.grade_json == null || row.grade_json === '') return null;
171
+ let parsed = row.grade_json;
172
+ if (typeof parsed === 'string') {
173
+ try {
174
+ parsed = JSON.parse(parsed);
175
+ } catch {
176
+ // expected: an unparsable grade_json carries no verdict — the row counts as ungraded
177
+ return null;
178
+ }
179
+ }
180
+ if (!parsed || typeof parsed !== 'object') return null;
181
+ return parsed;
182
+ }
183
+
184
+ function _gradeOf(row) {
185
+ const parsed = _parseGrade(row);
186
+ if (!parsed || parsed.unavailable === true) return null;
187
+ return parsed;
188
+ }
189
+
190
+ /**
191
+ * D-1/D-7: a closed round-1 row stored no facts (label_version below the current version and
192
+ * facts_json empty), so its decision is a v1 label that cannot be recomputed -- the very labels
193
+ * that demoted the live cells. It is kept as evidence but measures nothing. A row without a
194
+ * label_version key (a bare test fixture) is read as current.
195
+ */
196
+ function _isFactlessOldRow(row) {
197
+ if (row.label_version == null || row.decision === 'open') return false;
198
+ return Number(row.label_version) < LABEL_VERSION && (row.facts_json == null || row.facts_json === '');
199
+ }
200
+
201
+ /** A handoff/fallback row the window may count as a measurement. */
202
+ function _isClosedRow(row) {
203
+ return (row.decision === 'handoff' || row.decision === 'fallback') && !_isFactlessOldRow(row);
204
+ }
205
+
206
+ /** D-7: a row that carries no measurement — an explicit `unmeasured`, a decision the rule never produced, or a factless round-1 label. */
207
+ function _isUnmeasuredRow(row) {
208
+ return row.decision === 'unmeasured'
209
+ || (row.decision !== 'open' && !EXPLORE_DECISIONS.includes(row.decision))
210
+ || _isFactlessOldRow(row);
211
+ }
212
+
213
+ function _exploreMetrics(outcomes, leadBurstUsd) {
214
+ const closed = outcomes.filter(_isClosedRow);
215
+ const handoff = closed.filter((row) => row.decision === 'handoff');
216
+ const fallback = closed.length - handoff.length;
217
+ const gradedRows = closed.map((row) => _gradeOf(row)).filter(Boolean);
218
+ const sufficient = gradedRows.filter((grade) => grade.sufficient === true).length;
219
+ // D-8: how many of the window's rows carry an unavailable grade — the grader failed, was never
220
+ // pinned, or never parsed. `_gradeOf` still excludes them from `graded`; this only makes them
221
+ // visible, and it is a metric, never a threshold.
222
+ const gradeUnavailable = outcomes.filter((row) => _parseGrade(row)?.unavailable === true).length;
223
+ const unmeasured = outcomes.filter(_isUnmeasuredRow).length;
224
+ const abandoned = outcomes.filter((row) => row.decision === 'abandoned').length;
225
+ const open = outcomes.filter((row) => row.decision === 'open').length;
226
+ // D-7 (S-14): a verdict needs 10 MEASURED rows and a window that is not mostly no-data. Round 1
227
+ // read every non-handoff row as a fallback, so an all-unmeasured window ("the status read came
228
+ // back without a citation count") demoted a healthy cell. `closed` is unchanged — handoff +
229
+ // fallback only; the guard is on measurability, never on a threshold.
230
+ const measurable = closed.length >= MIN_LABELLED
231
+ && (unmeasured + abandoned) <= outcomes.length * UNMEASURED_MAX_SHARE;
232
+ return {
233
+ n_window: outcomes.length,
234
+ closed: closed.length,
235
+ handoff: handoff.length,
236
+ fallback,
237
+ fallback_rate: _ratio(fallback, closed.length),
238
+ graded: gradedRows.length,
239
+ grade_unavailable: gradeUnavailable,
240
+ sufficient_rate: _ratio(sufficient, gradedRows.length),
241
+ median_helper_usd: _median(handoff.map((row) => _number(row.helper_usd)).filter((value) => value !== null)),
242
+ median_reread: _median(closed.map((row) => _number(row.reread_rate)).filter((value) => value !== null)),
243
+ lead_burst_usd: Number.isFinite(Number(leadBurstUsd)) ? Number(leadBurstUsd) : null,
244
+ unmeasured,
245
+ abandoned,
246
+ open,
247
+ measurable,
248
+ collecting: !measurable,
249
+ };
250
+ }
251
+
252
+ /** The first off-condition the window trips, or null when it trips none. */
253
+ function _exploreFailureReason(metrics, leadBurstUsd) {
254
+ // D-7: neither an `on → off` demotion nor a failed probe may rest on a window that is mostly
255
+ // no-data — "unmeasured" is not evidence that the helper failed.
256
+ if (!metrics.measurable) return null;
257
+ if (metrics.fallback_rate > EXPLORE_FALLBACK_MAX) return REASONS.EXPLORE_FALLBACK_ABOVE;
258
+ if (metrics.median_reread !== null && metrics.median_reread > EXPLORE_REREAD_MAX) return REASONS.EXPLORE_REREAD_ABOVE;
259
+ if (metrics.median_helper_usd !== null && Number.isFinite(leadBurstUsd) && metrics.median_helper_usd >= leadBurstUsd) {
260
+ return REASONS.EXPLORE_HELPER_USD_AT_OR_ABOVE;
261
+ }
262
+ if (metrics.graded >= GRADED_MIN && metrics.sufficient_rate < EXPLORE_SUFFICIENT_MIN) {
263
+ return REASONS.EXPLORE_SUFFICIENT_BELOW;
264
+ }
265
+ return null;
266
+ }
267
+
268
+ function _evaluateExplore(cell, outcomes, now, leadBurstUsd) {
269
+ const cellState = _stateOf(cell);
270
+ const metrics = _exploreMetrics(outcomes, leadBurstUsd);
271
+
272
+ if (cellState === 'on') {
273
+ const reason = _exploreFailureReason(metrics, leadBurstUsd);
274
+ if (reason) return _verdict('off', cellState, metrics, reason);
275
+ return _verdict(cellState, cellState, metrics, null);
276
+ }
277
+
278
+ if (cellState === 'off') {
279
+ // The 7-day probe timer: only the daily tick can move an off cell, and only when the
280
+ // user has not vetoed it.
281
+ const changedAt = _toMs(cell.state_changed_at);
282
+ const nowMs = _toMs(now) ?? Date.now();
283
+ const days = changedAt == null ? 0 : (nowMs - changedAt) / DAY_MS;
284
+ if (!_vetoOf(cell) && days >= EXPLORE_PROBE_AFTER_DAYS) {
285
+ return _verdict('probe', cellState, metrics, REASONS.EXPLORE_PROBE_AFTER);
286
+ }
287
+ return _verdict(cellState, cellState, metrics, null);
288
+ }
289
+
290
+ // probe: the next PROBE_N closed rows that landed AFTER the probe started decide it.
291
+ const changedAt = _toMs(cell.state_changed_at);
292
+ const probeWindow = outcomes
293
+ .filter(_isClosedRow)
294
+ .filter((row) => {
295
+ const createdMs = _toMs(row.created_at);
296
+ return changedAt == null || createdMs == null || createdMs >= changedAt;
297
+ })
298
+ .slice(0, PROBE_N);
299
+ metrics.probed = probeWindow.length;
300
+ if (probeWindow.length < PROBE_N) return _verdict(cellState, cellState, metrics, null);
301
+ const failed = _exploreFailureReason(_exploreMetrics(probeWindow, leadBurstUsd), leadBurstUsd);
302
+ if (failed) return _verdict('off', cellState, metrics, REASONS.EXPLORE_PROBE_FAILED);
303
+ return _verdict('on', cellState, metrics, REASONS.EXPLORE_PROBE_PASSED);
304
+ }
305
+
306
+ /**
307
+ * Pure. Decide the next state of one rule cell from its stored state and its trailing
308
+ * window of outcomes. `reason` is null whenever the state does not change.
309
+ * @param {{state?:string, user_veto?:number, state_changed_at?:string}} cell
310
+ * @param {Array<object>} trailingOutcomes most-recent-first window of helper_outcomes rows
311
+ * @param {{now?: number|string|Date, leadBurstUsd?: number, watcherUsd?: {usd:number, rows:number}|null}} [options]
312
+ * @returns {{state:'on'|'off'|'probe', metrics:object, reason:string|null}}
313
+ */
314
+ function evaluateCell(cell, trailingOutcomes, { now, leadBurstUsd, watcherUsd } = {}) {
315
+ const outcomes = Array.isArray(trailingOutcomes) ? trailingOutcomes : [];
316
+ const cellState = _stateOf(cell);
317
+ if (_vetoOf(cell)) {
318
+ // D-18: a vetoed cell is off, and it can never probe. Reason only when this is news.
319
+ return { state: 'off', metrics: _kindMetrics(cell, outcomes, leadBurstUsd, now, watcherUsd), reason: cellState === 'off' ? null : REASONS.USER_VETO };
320
+ }
321
+ const kind = String(cell?.kind || '').trim();
322
+ if (kind === 'explore' || String(cell?.cell_key || '').startsWith('explore|')) {
323
+ return _evaluateExplore(cell, outcomes, now, leadBurstUsd);
324
+ }
325
+ return _evaluateWatch(cellState, outcomes, watcherUsd);
326
+ }
327
+
328
+ /** The metrics a short-circuited (vetoed) cell reports — same shape as its kind's. */
329
+ function _kindMetrics(cell, outcomes, leadBurstUsd, now, watcherUsd) {
330
+ void now;
331
+ const kind = String(cell?.kind || '').trim();
332
+ if (kind === 'explore' || String(cell?.cell_key || '').startsWith('explore|')) {
333
+ return _exploreMetrics(outcomes, leadBurstUsd);
334
+ }
335
+ return _watchMetrics(outcomes, watcherUsd);
336
+ }
337
+
338
+ module.exports = {
339
+ CELL_STATES,
340
+ DAY_MS,
341
+ EXPLORE_DECISIONS,
342
+ EXPLORE_DEMOTION_REASONS,
343
+ EXPLORE_FALLBACK_MAX,
344
+ EXPLORE_OPEN_TTL_MS,
345
+ EXPLORE_PROBE_AFTER_DAYS,
346
+ EXPLORE_REREAD_MAX,
347
+ EXPLORE_SUFFICIENT_MIN,
348
+ GRADED_MIN,
349
+ MIN_LABELLED,
350
+ PROBE_N,
351
+ REASONS,
352
+ UNMEASURED_MAX_SHARE,
353
+ WATCH_OFF_BELOW,
354
+ WATCH_ON_ABOVE,
355
+ WINDOW,
356
+ evaluateCell,
357
+ };
@@ -0,0 +1,12 @@
1
+ You are a careful grader for an evaluation. A lead coding agent explored a codebase with several read-only steps, then made a decision. Separately, a helper model was given a short brief describing the same question and explored the same code on its own, then wrote a report. Your job: judge whether the helper's report would have served the lead as well as its own exploration did.
2
+
3
+ The card has: the brief given to the helper, the helper's report, and the list of tools and paths the helper touched.
4
+
5
+ Judge:
6
+ - coverage: did the report contain the facts the brief's "Stop when" condition needs? "all" | "most" | "little"
7
+ - inaccuracies: how many claims in the report are wrong or unsupported by a file:line the report cites? an integer
8
+ - sufficient: could the lead make its next decision from the report alone, without re-reading the files? true | false
9
+ - why: <= 40 words, concrete (name the missing or wrong fact if any).
10
+ Judge only facts the report itself supports with file:line; if the report marks something INFERENCE, do not count it as wrong. A "not found" row is valid when the brief's question has no answer in the code.
11
+
12
+ Answer with one JSON object only: {"sufficient": true|false, "coverage": "all"|"most"|"little", "inaccuracies": <integer>, "why": "<= 40 words"}