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
@@ -2,6 +2,8 @@
2
2
  const { execFile, spawn } = require('child_process');
3
3
  const path = require('path');
4
4
  const { createSemaphore } = require('./lib/async-semaphore');
5
+ // WP-4: one normal form for a git remote, shared with wall-e and the hub (never re-implemented).
6
+ const { normalizeGitRemote } = require('../shared/git-remote-key');
5
7
 
6
8
  const DEFAULT_GIT_TIMEOUT_MS = _positiveInt(process.env.CTM_GIT_TIMEOUT_MS, 15000);
7
9
  const DEFAULT_WORKTREE_CREATE_TIMEOUT_MS = Math.max(
@@ -628,6 +630,46 @@ function _invalidateGitFactCache() {
628
630
  async function resolveMainBranch(cwd) {
629
631
  return _mainBranchCache(cwd || '', () => _resolveMainBranchUncached(cwd));
630
632
  }
633
+
634
+ // WP-4 (§6.5 step 2, §9 S-9): the box-side `gitRemoteKey` for /api/projects rows.
635
+ //
636
+ // The SAME cached-promise pattern as resolveMainBranch above — async (never a sync spawn on the
637
+ // main thread), one `git remote get-url origin` per checkout per TTL, and a rejection drops the
638
+ // cache entry instead of pinning an empty key for the whole window. Everything about WHICH string
639
+ // is the key lives in shared/git-remote-key.js (one implementation, both ends); this is only the
640
+ // git read. A non-repo / no-origin / detached cwd resolves to '' — the caller emits the field
641
+ // with an empty value rather than omitting it, so a client can tell "asked and there is no remote"
642
+ // from "this instance is too old to answer".
643
+ const _GIT_REMOTE_KEY_TTL_MS = 30000; // fixed on purpose: no new env var (WP-4 hard rule)
644
+ const _remoteKeyCache = _ttlPromiseCache(_GIT_REMOTE_KEY_TTL_MS);
645
+
646
+ async function gitRemoteKey(cwd) {
647
+ const repoPath = String(cwd || '');
648
+ if (!repoPath) return '';
649
+ return _remoteKeyCache(repoPath, async () => {
650
+ const url = await _gitSafe(repoPath, ['remote', 'get-url', 'origin']);
651
+ return normalizeGitRemote(url);
652
+ });
653
+ }
654
+
655
+ /**
656
+ * Attach `gitRemoteKey` to every project row (in place, returning the same array).
657
+ *
658
+ * Bounded concurrency: a projects list is normally < 100 rows and each miss costs one `git`
659
+ * process, but a shared box can hold hundreds — the pool keeps the fan-out flat instead of
660
+ * spawning one git per row at once.
661
+ */
662
+ async function attachGitRemoteKeys(projects) {
663
+ const rows = Array.isArray(projects) ? projects : [];
664
+ const concurrency = 8;
665
+ for (let i = 0; i < rows.length; i += concurrency) {
666
+ await Promise.all(rows.slice(i, i + concurrency).map(async (row) => {
667
+ if (!row || typeof row !== 'object') return;
668
+ row.gitRemoteKey = await gitRemoteKey(row.path);
669
+ }));
670
+ }
671
+ return rows;
672
+ }
631
673
  async function _resolveMainBranchUncached(cwd) {
632
674
  const originHead = await _gitSafe(cwd, ['symbolic-ref', '--quiet', '--short', 'refs/remotes/origin/HEAD']);
633
675
  if (originHead) {
@@ -2303,4 +2345,6 @@ module.exports = {
2303
2345
  _invalidateGitFactCache,
2304
2346
  _gitProbeEnv,
2305
2347
  _mapWorktreesBounded, _getGitProbeMetrics, _resetGitProbeMetrics,
2348
+ // WP-4: gitRemoteKey on /api/projects rows (S-9) — the key itself is normalizeGitRemote.
2349
+ gitRemoteKey, attachGitRemoteKeys, normalizeGitRemote,
2306
2350
  };
@@ -60,6 +60,10 @@ function _declareHostDead(manager, hostId, attemptNumber) {
60
60
  adapter._emitExitOnce({ exitCode: 1, signal: null, hostClosed: true, reason: 'adopted-redial-exhausted' });
61
61
  }
62
62
  manager._removeHostRecordAsync(hostId);
63
+ // D-8 (stale-pty-host-evacuation design): a dead host leaves no retry behind --
64
+ // without its compat entry handoffStale() returns null and never re-arms a
65
+ // stale retry for a process that is already gone.
66
+ manager.hostCompatibility?.delete(hostId);
63
67
  // I6 (host-readiness design): announce the death so the primary's readiness
64
68
  // ledger forgets this hostId -- the ONE terminal path for an adopted host.
65
69
  manager.emit?.('host-exit', { hostId, kind: 'adopted' });
@@ -188,11 +188,25 @@ const INHERITED_AGENT_ENV_KEYS = [
188
188
  // lead, it never drives CTM; interactive sessions (explicitWorkerAgent undefined) keep all four.
189
189
  const WORKER_STRIPPED_TOKEN_KEYS = Object.freeze(['WALLE_CTM_TOKEN', 'WALL_E_API_TOKEN', 'CTM_TOKEN', 'CTM_API_TOKEN']);
190
190
 
191
+ // D-7 (session-stops-followups-2026-10-24.html §1.3, §5 D-7, §8 WP-4): the same worker branch also
192
+ // must not hold a CTM API ADDRESS. On 2026-10-24 a delegated worker's script loaded Wall-E's CTM
193
+ // client with no base URL; resolveApiBaseUrl (wall-e/memory/ctm-context-client.js:12-17) fell back
194
+ // to the inherited CTM_PORT=3456 and reached PRIMARY. These three names are every alias that
195
+ // resolver reads, so a worker keeps none of them and carries the resolver's existing
196
+ // `WALLE_CTM_API='0'` switch instead: with no explicit URL it resolves '' and calls throw
197
+ // ctm_api_unavailable. buildTelemetryEnv still sets CTM_PORT (the shared session env; interactive
198
+ // hooks read it, a default worker runs none); the address is what must not travel. Least privilege
199
+ // by inheritance (P-5): the orchestrator's addressability is not a worker capability. Interactive
200
+ // sessions keep all of it.
201
+ const WORKER_STRIPPED_CTM_API_KEYS = Object.freeze(['CTM_SESSION_MEMORY_API_BASE_URL', 'CTM_API_BASE_URL', 'CTM_BASE_URL']);
202
+
191
203
  function sanitizeInheritedAgentEnv(env, explicitWorkerAgent) {
192
204
  if (!env || typeof env !== 'object') return env;
193
205
  for (const key of INHERITED_AGENT_ENV_KEYS) delete env[key];
194
206
  if (explicitWorkerAgent === 'claude' || explicitWorkerAgent === 'codex') {
195
207
  for (const key of WORKER_STRIPPED_TOKEN_KEYS) delete env[key];
208
+ for (const key of WORKER_STRIPPED_CTM_API_KEYS) delete env[key];
209
+ env.WALLE_CTM_API = '0';
196
210
  env.CTM_WORKER_AGENT = explicitWorkerAgent;
197
211
  }
198
212
  return env;
@@ -238,4 +252,5 @@ module.exports = {
238
252
  buildTelemetryEnv,
239
253
  sanitizeInheritedAgentEnv,
240
254
  WORKER_STRIPPED_TOKEN_KEYS,
255
+ WORKER_STRIPPED_CTM_API_KEYS,
241
256
  };
@@ -0,0 +1,88 @@
1
+ 'use strict';
2
+
3
+ // WP-2 (design doc session-name-agent-title-precedence §5 D-4/D-5/D-9, §6): the inbound applier
4
+ // for a name the CODING AGENT wrote into its own transcript — Claude's `ai-title` / `custom-title`
5
+ // records, captured during ingest (lib/transcript-store.js claudeMessagesFromEntry).
6
+ //
7
+ // Pure SQL on the caller's better-sqlite3 handle. This module is loaded on the ingest path
8
+ // (transcript-store → the db-owner worker, the read pool and the main thread), so it must never
9
+ // require lib/db.js — the domain router owns the handles and passes one in.
10
+ //
11
+ // Contract:
12
+ // * Tier 1 (`user_renamed=1`) is never rewritten. A drifted agent name only asks for ONE
13
+ // re-assert outward (`reassert: true`; the caller runs mirrorSessionTitle({force:true})).
14
+ // `ai-title` alone is never drift — Claude keeps writing ai-titles under a custom title (§1e).
15
+ // * `custom-title` equal to `title_mirror_value` is CTM's own echo of what it pushed into the
16
+ // agent, not the agent's name (D-5), so the `ai-title` wins instead.
17
+ // * A tier-2 write touches ctm_sessions only. The caller (server.js _runTranscriptDeltaPipeline)
18
+ // moves `startup_tasks.label` through db.updateStartupTaskLabel, which also enqueues the
19
+ // multi-machine sync outbox row this db.js-free module cannot reach (D-13).
20
+
21
+ const { normalizeSessionTitle } = require('./session-title-signals');
22
+ const { isUuidLike } = require('./session-title-mirror');
23
+
24
+ // D-9: these two states mean "a push was attempted and did not land" — a row already sitting at
25
+ // that outcome for the CURRENT title is not re-driven from inbound (exit and relink keep retrying).
26
+ const MIRROR_RETRY_STATES = new Set(['failed', 'unsupported']);
27
+
28
+ /**
29
+ * @param {import('better-sqlite3').Database} coreDb handle carrying ctm_sessions + startup_tasks
30
+ * (the caller's CORE domain handle — `options.coreDb || db` at the ingest choke point).
31
+ * @param {{ ctmSessionId: string, provider?: string, customTitle?: string, aiTitle?: string }} o
32
+ * `provider` is accepted for call-site symmetry; the provider gate (titleSpec.readsAiTitle) is
33
+ * the caller's, because the reason string and the SQL are provider-independent.
34
+ * @returns {{ applied: 'agent'|'none', reason: string, title?: string, reassert: boolean }}
35
+ */
36
+ function applyAgentTitleSignals(coreDb, { ctmSessionId, customTitle = '', aiTitle = '' } = {}) {
37
+ const id = String(ctmSessionId || '').trim();
38
+ if (!id) return { applied: 'none', reason: 'no_session', reassert: false };
39
+ const row = coreDb.prepare(
40
+ 'SELECT title, user_renamed, title_mirror_state, title_mirror_value FROM ctm_sessions WHERE id = ?',
41
+ ).get(id);
42
+ if (!row) return { applied: 'none', reason: 'no_row', reassert: false };
43
+
44
+ const title = normalizeSessionTitle(row.title);
45
+ const custom = normalizeSessionTitle(customTitle);
46
+ const ai = normalizeSessionTitle(aiTitle);
47
+ const mirrorValue = normalizeSessionTitle(row.title_mirror_value);
48
+ const mirrorState = String(row.title_mirror_state || '');
49
+
50
+ if (row.user_renamed) {
51
+ // C-5/D-9: a tier-1 row nobody has ever tried to push gets exactly one attempt on its first
52
+ // observation. Checked before the drift vocabulary below — nothing was pushed yet, so the
53
+ // agent cannot have drifted FROM anything, and naming the real condition is what a diagnosis
54
+ // should report. Either way the row is never rewritten.
55
+ if (mirrorState === '') return { applied: 'none', reason: 'tier1_unmirrored', reassert: true };
56
+ if (custom && custom !== title) {
57
+ if (MIRROR_RETRY_STATES.has(mirrorState) && mirrorValue === title) {
58
+ return { applied: 'none', reason: 'tier1_drift', reassert: false };
59
+ }
60
+ // Drift is recorded either way: a later batch carrying the CTM name back converges
61
+ // (`mirrorSessionTitle` persists state='mirrored') and the next observation is quiet.
62
+ coreDb.prepare('UPDATE ctm_sessions SET title_mirror_state = ? WHERE id = ?').run('drifted', id);
63
+ return { applied: 'none', reason: 'tier1_drift', reassert: true };
64
+ }
65
+ return { applied: 'none', reason: 'tier1', reassert: false };
66
+ }
67
+
68
+ // D-5: the agent's name wins, unless the custom-title is CTM's own echo of title_mirror_value.
69
+ const name = (custom && custom !== mirrorValue) ? custom : ai;
70
+ if (!name) return { applied: 'none', reason: 'unchanged', reassert: false };
71
+ // A provider name that is just the provider's opaque session id names nothing.
72
+ if (isUuidLike(name)) return { applied: 'none', reason: 'uuid_like', reassert: false };
73
+ if (name === title) return { applied: 'none', reason: 'unchanged', reassert: false };
74
+
75
+ const reason = name === custom ? 'provider_custom_title' : 'provider_ai_title';
76
+ const updatedAt = new Date().toISOString();
77
+ // The tier guard lives in the WHERE, so a concurrent rename is never overwritten.
78
+ const changes = coreDb.prepare(`
79
+ UPDATE ctm_sessions
80
+ SET title = ?, title_source = 'provider_title', title_status = 'imported',
81
+ title_status_reason = ?, title_updated_at = ?, updated_at = datetime('now')
82
+ WHERE id = ? AND COALESCE(user_renamed, 0) = 0
83
+ `).run(name, reason, updatedAt, id).changes;
84
+ if (!changes) return { applied: 'none', reason: 'tier1', reassert: false };
85
+ return { applied: 'agent', title: name, reason, reassert: false };
86
+ }
87
+
88
+ module.exports = { applyAgentTitleSignals };
@@ -0,0 +1,92 @@
1
+ 'use strict';
2
+
3
+ // WP-6 (design doc session-name-agent-title-precedence §§1e/6/8): the READ half of the one-shot
4
+ // agent-title backfill — one bounded page of Claude candidates CTM has never been given an agent
5
+ // name for, plus each row's transcript TAIL (last <=64 KiB, one positional range; never a
6
+ // whole-file read). The apply is the db-owner's (db.js applyAgentTitleBackfillRows ->
7
+ // lib/agent-title-apply.js), so this stays a pure read on the caller's read-only WAL handle.
8
+ // Called by the read-pool worker op 'backfillAgentTitles'; the caller (server/boot.js) owns the
9
+ // page loop and the once-per-install marker.
10
+
11
+ const transcriptSource = require('./transcript-source');
12
+
13
+ function readAgentTitleBackfillPage(conn, payload = {}) {
14
+ const { extractTitleSignalFromRecord } = require('./session-title-signals');
15
+ // §1e measured the EOF→last title-record distance at p50 3,293 B, p90 19,166 B, max 36,778 B
16
+ // over n=43; 64 KiB is that bound with headroom and is the hard per-row ceiling.
17
+ const MAX_ROWS = 20;
18
+ const TAIL_BYTES = 64 * 1024;
19
+ const explicitIds = (Array.isArray(payload.ids) ? payload.ids : [])
20
+ .map((id) => String(id || '').trim()).filter(Boolean).slice(0, MAX_ROWS);
21
+ const limit = Math.max(1, Math.min(MAX_ROWS, Number(payload.limit) || MAX_ROWS));
22
+ const tailBytes = Math.max(1024, Math.min(TAIL_BYTES, Number(payload.tailBytes) || TAIL_BYTES));
23
+ // An explicit id list (bin/repair-resume-echo-titles.js) has already re-stated the row's
24
+ // tier itself, so those pages drop the tier filters; the default page keeps them.
25
+ const tierFilter = explicitIds.length ? '' : `
26
+ AND COALESCE(c.user_renamed, 0) = 0
27
+ AND COALESCE(c.title_source, '') <> 'provider_title'`;
28
+ const idFilter = explicitIds.length ? ` AND c.id IN (${explicitIds.map(() => '?').join(',')})` : '';
29
+ // Run-scoped walk cursor: the caller accumulates the ids this page ALREADY read and hands
30
+ // them back, so a row whose transcript turns out to carry no title record cannot make the
31
+ // walk loop forever (rows that DO get a title also leave the candidate set on their own).
32
+ const skipIds = (Array.isArray(payload.skipIds) ? payload.skipIds : [])
33
+ .map((id) => String(id || '').trim()).filter(Boolean).slice(0, 4000);
34
+ const skipFilter = skipIds.length ? ` AND c.id NOT IN (${skipIds.map(() => '?').join(',')})` : '';
35
+ // "The row's transcript" is the LATEST agent_sessions row, the same rule the display path
36
+ // uses (db.js getSessionDisplayTitleInfo's ROW_NUMBER() OVER … rn = 1), not MAX(jsonl_path):
37
+ // a stitched compaction continuation adds a second row, and the freshest file is the one
38
+ // Claude is still appending ai-titles to.
39
+ const candidates = conn.prepare(`
40
+ SELECT c.id AS id, a.jsonl_path AS jsonl_path
41
+ FROM ctm_sessions c
42
+ JOIN (
43
+ SELECT ctm_session_id, jsonl_path, modified_at, updated_at, created_at,
44
+ ROW_NUMBER() OVER (
45
+ PARTITION BY ctm_session_id
46
+ ORDER BY COALESCE(NULLIF(modified_at, ''), updated_at, created_at) DESC,
47
+ updated_at DESC, created_at DESC
48
+ ) AS rn
49
+ FROM agent_sessions
50
+ ) a ON a.ctm_session_id = c.id AND a.rn = 1
51
+ WHERE lower(COALESCE(c.provider, '')) = 'claude'
52
+ AND COALESCE(a.jsonl_path, '') <> ''${tierFilter}${idFilter}${skipFilter}
53
+ ORDER BY COALESCE(NULLIF(a.modified_at, ''), a.updated_at, a.created_at, '') DESC
54
+ LIMIT ?
55
+ `).all(...explicitIds, ...skipIds, limit);
56
+ const rows = [];
57
+ const scannedIds = [];
58
+ for (const candidate of candidates) {
59
+ const id = String(candidate.id || '');
60
+ scannedIds.push(id);
61
+ const jsonlPath = String(candidate.jsonl_path || '');
62
+ let view = null;
63
+ try { view = transcriptSource.statTranscriptSync(jsonlPath, { db: conn }); } catch { view = null; /* expected: a transcript rotated away between the row read and the stat — skip the row, never fatal */ }
64
+ if (!view || view.state === 'missing' || !(Number(view.size) > 0)) continue;
65
+ const size = Number(view.size);
66
+ const from = Math.max(0, size - tailBytes);
67
+ let buf = null;
68
+ try { buf = transcriptSource.readTranscriptRangeSync(jsonlPath, from, size, { db: conn }); } catch { continue; /* expected: an unreadable/rotating transcript — skip the row */ }
69
+ // Both records are kept independently (D-4); which one wins is the applier's D-5 decision,
70
+ // because Claude keeps re-appending ai-titles under a custom title (§1e).
71
+ const lines = buf.toString('utf8').split('\n');
72
+ if (from > 0) lines.shift(); // the window started mid-record
73
+ let customTitle = '';
74
+ let aiTitle = '';
75
+ for (const line of lines) {
76
+ const text = line.trim();
77
+ if (!text) continue;
78
+ let entry = null;
79
+ try { entry = JSON.parse(text); } catch { continue; /* expected: a window-edge partial line or a non-record line */ }
80
+ const signal = extractTitleSignalFromRecord(entry);
81
+ if (signal.userTitle) customTitle = signal.userTitle;
82
+ else if (signal.providerTitle) aiTitle = signal.providerTitle;
83
+ }
84
+ if (!customTitle && !aiTitle) continue;
85
+ // `jsonlPath` is the file this page actually read — the repair script prints it instead of
86
+ // re-deriving "the row's transcript" and risking a different answer.
87
+ rows.push({ id, jsonlPath, customTitle, aiTitle, tailBytes: size - from });
88
+ }
89
+ return { rows, scanned: candidates.length, scannedIds, limit };
90
+ }
91
+
92
+ module.exports = { readAgentTitleBackfillPage };
@@ -0,0 +1,98 @@
1
+ 'use strict';
2
+
3
+ // WP-1 (machine-pool-picker-pairing-dashboard-2026-10-24.html §6.1, D-11/D-13): the host-stats
4
+ // read surface. Every CTM samples its own host on the existing 60 s resource tick (no new timer —
5
+ // the record call rides the tick server.js already runs) and keeps 24 h of one-per-minute rows;
6
+ // this route serves them to its own picker and, through the ?machine= proxy, to a hub.
7
+ //
8
+ // GET /api/host/stats?latest=1 -> { ok:true, instanceId, latest: HostSample|null }
9
+ // GET /api/host/stats?since=<ms> -> { ok:true, instanceId, latest, samples: HostSample[] }
10
+ // `since` is clamped to now-24h (the retention window), so
11
+ // `?since=0` means "everything retained", never "everything
12
+ // ever" — a caller cannot ask for a row the purge dropped.
13
+ //
14
+ // Like lib/api/machines.js (and unlike the lib/api/*.js extraction slices) this is NEW surface
15
+ // added by WP-1: the handler body lives here, so there is no legacy handleApi branch to delete and
16
+ // tests-integration/api-router-parity.test.js's no-dual-dispatch rule is satisfied by construction.
17
+ // Auth is NOT evaluated here: lib/auth-rules.js carries the row — read, NOT loopbackOnly (a phone
18
+ // picker reads another machine through the hub), peerControl:true (D-13) — and a route with no rule
19
+ // is 403 auth_rule_missing by design, so the rule row and this registration must land together.
20
+ const { registerApiRoutes } = require('../api-router');
21
+ const remoteMachineProxy = require('../remote-machine-proxy');
22
+
23
+ // Mirrors lib/host-stats.js's DEFAULT_RETENTION_MS. It is the clamp floor here, so a caller asking
24
+ // for a wider window than the sampler keeps is answered with the rows that actually exist.
25
+ const RETENTION_MS = 24 * 60 * 60 * 1000;
26
+ const ROUTE = 'GET /api/host/stats';
27
+
28
+ let _deps = null;
29
+
30
+ function configure({
31
+ hostStats,
32
+ sendJsonResponse,
33
+ } = {}) {
34
+ _deps = { hostStats, sendJsonResponse };
35
+ }
36
+
37
+ function _send(res, status, payload) {
38
+ if (_deps && typeof _deps.sendJsonResponse === 'function') return _deps.sendJsonResponse(res, status, payload);
39
+ const body = JSON.stringify(payload || {});
40
+ res.writeHead(status, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) });
41
+ res.end(body);
42
+ }
43
+
44
+ // This machine's own minted id — the instanceId a hub keys the response by. Read through the
45
+ // accessor (never re-derived here), and '' rather than a throw before boot has primed identity: a
46
+ // 200 with an empty id is a truthful "sampler is up, identity is not yet" answer.
47
+ function _instanceId() {
48
+ try {
49
+ const access = require('../instance-identity-access');
50
+ const identity = access.getInstanceIdentity({ required: false });
51
+ return (identity && identity.instanceId) || '';
52
+ } catch {
53
+ return ''; // expected: instance identity is not ready yet (early boot / pre-initDb)
54
+ }
55
+ }
56
+
57
+ // Self-contained on purpose: no countTasks/lastCreateAt here. Those two are injected into
58
+ // lib/host-stats.js by server.js (it owns the live session map and the status bindings), so this
59
+ // handler only reads what the sampler already wrote.
60
+ async function handleHostStats(req, res, url) {
61
+ const stats = _deps && _deps.hostStats;
62
+ // ORDER MATTERS: the history read is the slow leg (a DB-owner round trip, ~1 ms alone but seconds
63
+ // under load), and `latest` is the in-memory sample. Reading `latest` FIRST lets a tick land
64
+ // mid-read, so the answer carries a row NEWER than the `latest` object beside it — which is exactly
65
+ // how this file's own assertion `latest === samples[last]` failed once on a saturated host.
66
+ // Reading history first makes `latest.at >= samples[last].at` hold for the whole answer.
67
+ const payload = { ok: true, instanceId: _instanceId(), latest: null };
68
+
69
+ const sinceRaw = url && url.searchParams ? url.searchParams.get('since') : null;
70
+ if (sinceRaw != null && sinceRaw !== '') {
71
+ const now = Date.now();
72
+ const floor = now - RETENTION_MS;
73
+ const requested = Number(sinceRaw);
74
+ const since = Math.min(Math.max(Number.isFinite(requested) ? Math.trunc(requested) : floor, floor), now);
75
+ payload.samples = stats && typeof stats.history === 'function' ? await stats.history({ sinceMs: since }) : [];
76
+ }
77
+ payload.latest = stats && typeof stats.latest === 'function' ? (stats.latest() || null) : null;
78
+ return _send(res, 200, payload);
79
+ }
80
+
81
+ // Remote machine hub (§6.1 / S-5): the same wrapper lib/api/misc-ops.js:41 uses. A request naming
82
+ // another machine (?machine= or X-CTM-Machine) is forwarded to that box with the peer bearer and
83
+ // this handler never runs; every other request is answered locally. `url` is passed through so the
84
+ // proxied box sees the caller's own `since`.
85
+ function _machineRouted(route, local) {
86
+ return (req, res, url) => (remoteMachineProxy.isMachineRouted(req, url)
87
+ ? remoteMachineProxy.maybeProxyMachineRequest(req, res, url, { route })
88
+ .then((handled) => (handled ? undefined : local(req, res, url)))
89
+ : local(req, res, url));
90
+ }
91
+
92
+ function register() {
93
+ registerApiRoutes('host-stats', [
94
+ { method: 'GET', path: '/api/host/stats', handler: _machineRouted(ROUTE, (req, res, url) => handleHostStats(req, res, url)) },
95
+ ]);
96
+ }
97
+
98
+ module.exports = { configure, register, handleHostStats };
@@ -69,27 +69,50 @@ function collectHook(req, res, onPayload) {
69
69
  });
70
70
  }
71
71
 
72
- // A lead hears its delegated worker stopped on its next PostToolUse / UserPromptSubmit hook.
72
+ // A lead hears its delegated worker stopped on its next PostToolUse / UserPromptSubmit hook, and --
73
+ // RC-1 (D-1..D-4/D-6) -- on its Stop hook, which is answered synchronously with the drained notices
74
+ // plus a nudge when the lead still owns a live worker and nothing pending will wake it.
73
75
  // A notice counts as delivered only once the response carrying it finished (was handed to the
74
76
  // socket); a response that closes or errors first re-queues it in memory for the next hook.
75
- function workerNoticeBody(route, payload, res) {
77
+ function workerNoticeBody(route, payload, res, verdict) {
76
78
  const isPrompt = route === 'start' && payload.hook_event_name === 'UserPromptSubmit';
79
+ const isToolUse = route === 'PostToolUse';
80
+ const isStop = route === 'stop';
77
81
  const leadId = typeof payload.ctm_id === 'string' ? payload.ctm_id : '';
78
- if ((route !== 'PostToolUse' && !isPrompt) || !leadId) return undefined;
82
+ if ((!isToolUse && !isPrompt && !isStop) || !leadId) return undefined;
83
+ // D-4: the loop guard counts TOOL CALLS, so every PostToolUse advances the epoch even when the
84
+ // lead has no notice waiting.
85
+ if (isToolUse) workerNotices.noteLeadToolUse(leadId);
86
+ // D-6: a Stop that arrived after a newer UserPromptSubmit is stale -- no notices and no nudge.
87
+ if (isStop && verdict && verdict.stale) return undefined;
79
88
  const drained = workerNotices.drainNotices(leadId, { now: Date.now(), dbModule: _deps.dbModule || null, delegateWorker: _deps.delegateWorker || null });
80
- if (!drained.notices.length) return undefined;
81
- if (res && typeof res.once === 'function') {
82
- res.once('finish', () => drained.commit());
83
- res.once('close', () => drained.abandon()); // after 'finish' this is a no-op
84
- res.once('error', () => drained.abandon());
85
- } else drained.commit();
86
- return { notices: drained.notices.map((n) => ({ receipt_id: n.receipt_id, text: n.text })) };
89
+ const nudge = isStop
90
+ ? workerNotices.stopNudge(leadId, { backgroundTasks: payload.background_tasks, sessionCrons: payload.session_crons })
91
+ : null;
92
+ if (!drained.notices.length && !nudge) return undefined;
93
+ // §7: one session_diagnostics row per nudge, so the RC-1 census can count leads that were held.
94
+ if (nudge && typeof _deps.recordSessionDiagnostic === 'function') {
95
+ try { _deps.recordSessionDiagnostic(leadId, 'lead-stop-nudge', { workers: nudge.workers, background_running: false }); }
96
+ catch (err) { console.error('[worker-notices] lead-stop-nudge diagnostic failed:', err && err.message); }
97
+ }
98
+ if (drained.notices.length) {
99
+ if (res && typeof res.once === 'function') {
100
+ res.once('finish', () => drained.commit());
101
+ res.once('close', () => drained.abandon()); // after 'finish' this is a no-op
102
+ res.once('error', () => drained.abandon());
103
+ } else drained.commit();
104
+ }
105
+ const notices = drained.notices.map((n) => ({ receipt_id: n.receipt_id, text: n.text }));
106
+ // A nudge is not a receipt-backed notice, so it carries no receipt_id.
107
+ if (nudge) notices.push({ receipt_id: null, text: nudge.text });
108
+ return { notices };
87
109
  }
88
110
 
89
111
  function handleClaudeHook(req, res, url) {
90
112
  const route = url.pathname.slice('/hook/claude/'.length);
91
113
  collectHook(req, res, (payload) => {
92
- _deps.telemetryReceiver.hookClaude(route, payload);
114
+ // D-1/D-6: the Stop verdict says whether this Stop is stale; the notice body needs it.
115
+ const verdict = _deps.telemetryReceiver.hookClaude(route, payload);
93
116
  // WP-1 (native-subagent-sidebar-rows-2026-10-12 §5 D-4, §9 S-8): feed the lead's native
94
117
  // subagent lifecycle from the same hook stream. Best-effort -- a runtime failure never fails
95
118
  // the hook (the telemetry receiver above already owns the status/background writes).
@@ -100,7 +123,7 @@ function handleClaudeHook(req, res, url) {
100
123
  // go through the separate activity adapter and never rewrite resume cwd.
101
124
  if (!['PostToolUse', 'PostToolUseFailure', 'CwdChanged'].includes(route)) _deps.observeHookCwd(payload, `claude.${route}`);
102
125
  if (typeof _deps.observeWorkspaceActivity === 'function') _deps.observeWorkspaceActivity('claude', route, payload);
103
- try { return workerNoticeBody(route, payload, res); } catch { return undefined; /* expected: a notice failure never fails the hook */ }
126
+ try { return workerNoticeBody(route, payload, res, verdict); } catch { return undefined; /* expected: a notice failure never fails the hook */ }
104
127
  });
105
128
  }
106
129