create-walle 0.10.0 → 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 (381) hide show
  1. package/README.md +88 -41
  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 +7 -5
  6. package/package.json +10 -4
  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/capture-codex-rollout-records.js +417 -0
  11. package/template/coding-task-manager/bin/census-codex-lead-history.js +325 -0
  12. package/template/coding-task-manager/bin/claude-hook.js +7 -3
  13. package/template/coding-task-manager/bin/claude-table-parity.js +307 -0
  14. package/template/coding-task-manager/bin/conversation-storage-auto-migration-lib.js +16 -1
  15. package/template/coding-task-manager/bin/conversation-storage-generation-evidence.js +9 -2
  16. package/template/coding-task-manager/bin/conversation-storage-service-entry-lib.js +16 -1
  17. package/template/coding-task-manager/bin/conversation-storage-walle-owner.js +247 -0
  18. package/template/coding-task-manager/bin/ctm-machine-pair.js +160 -0
  19. package/template/coding-task-manager/bin/lead-wait-census.js +1348 -0
  20. package/template/coding-task-manager/bin/repair-foreign-clone.js +564 -0
  21. package/template/coding-task-manager/bin/repair-resume-echo-titles.js +248 -0
  22. package/template/coding-task-manager/bin/seam-replay-codex.js +301 -0
  23. package/template/coding-task-manager/bin/write-lock-bench.js +111 -0
  24. package/template/coding-task-manager/db.js +705 -286
  25. package/template/coding-task-manager/git-utils.js +44 -0
  26. package/template/coding-task-manager/lib/adopted-host-redial.js +4 -0
  27. package/template/coding-task-manager/lib/agent-presets.js +15 -0
  28. package/template/coding-task-manager/lib/agent-title-apply.js +88 -0
  29. package/template/coding-task-manager/lib/agent-title-backfill-read.js +92 -0
  30. package/template/coding-task-manager/lib/api/host-stats.js +98 -0
  31. package/template/coding-task-manager/lib/api/ingress.js +35 -12
  32. package/template/coding-task-manager/lib/api/machine-pairing.js +285 -0
  33. package/template/coding-task-manager/lib/api/machine-pool.js +307 -0
  34. package/template/coding-task-manager/lib/api/machines.js +461 -0
  35. package/template/coding-task-manager/lib/api/misc-ops.js +20 -5
  36. package/template/coding-task-manager/lib/api/providers.js +53 -6
  37. package/template/coding-task-manager/lib/api/resume-from-store.js +25 -0
  38. package/template/coding-task-manager/lib/api/sessions.js +160 -8
  39. package/template/coding-task-manager/lib/api/transcript-history.js +17 -4
  40. package/template/coding-task-manager/lib/api/worktrees.js +38 -37
  41. package/template/coding-task-manager/lib/approval-drift.js +16 -1
  42. package/template/coding-task-manager/lib/approval-logical-frame.js +5 -1
  43. package/template/coding-task-manager/lib/approval-scope.js +68 -4
  44. package/template/coding-task-manager/lib/auth-context.js +32 -2
  45. package/template/coding-task-manager/lib/auth-rate-limit.js +41 -1
  46. package/template/coding-task-manager/lib/auth-rules.js +186 -45
  47. package/template/coding-task-manager/lib/broadcast-serialize.js +2 -1
  48. package/template/coding-task-manager/lib/browser-session-broker.js +9 -1
  49. package/template/coding-task-manager/lib/browser-use-adapter.js +33 -2
  50. package/template/coding-task-manager/lib/claude-markdown-ansi.js +27 -85
  51. package/template/coding-task-manager/lib/claude-session-title.js +6 -30
  52. package/template/coding-task-manager/lib/claude-table-layout.js +322 -0
  53. package/template/coding-task-manager/lib/client-diag-handler.js +25 -0
  54. package/template/coding-task-manager/lib/codex-config-guard.js +33 -3
  55. package/template/coding-task-manager/lib/codex-harness-qualification.js +12 -0
  56. package/template/coding-task-manager/lib/codex-history-terminal-renderer.js +33 -33
  57. package/template/coding-task-manager/lib/codex-launch-health.js +29 -2
  58. package/template/coding-task-manager/lib/codex-link-preparation.js +9 -8
  59. package/template/coding-task-manager/lib/codex-local-links.js +267 -0
  60. package/template/coding-task-manager/lib/codex-markdown-lists.js +160 -0
  61. package/template/coding-task-manager/lib/codex-native-history-prefix.js +4 -0
  62. package/template/coding-task-manager/lib/codex-paths.js +2 -1
  63. package/template/coding-task-manager/lib/codex-source-handoff.js +186 -0
  64. package/template/coding-task-manager/lib/coding-agent-models.js +21 -30
  65. package/template/coding-task-manager/lib/coding-session-model-adapters.js +27 -2
  66. package/template/coding-task-manager/lib/coding-session-model-projection.js +5 -0
  67. package/template/coding-task-manager/lib/coding-session-model-runtime.js +1 -2
  68. package/template/coding-task-manager/lib/coding-session-model-state.js +10 -0
  69. package/template/coding-task-manager/lib/coding-session-model-sync-state.js +0 -1
  70. package/template/coding-task-manager/lib/conversation-import/cursor-guard-owner.js +10 -0
  71. package/template/coding-task-manager/lib/create/resolve-launch-params.js +8 -1
  72. package/template/coding-task-manager/lib/ctm-boot-lifecycle.js +17 -1
  73. package/template/coding-task-manager/lib/db-domain-tables.js +7 -0
  74. package/template/coding-task-manager/lib/db-domains.js +21 -2
  75. package/template/coding-task-manager/lib/db-owner-op-registry.js +7 -0
  76. package/template/coding-task-manager/lib/db-owner-worker-client.js +22 -0
  77. package/template/coding-task-manager/lib/db-schema-source-files.js +1 -0
  78. package/template/coding-task-manager/lib/feature-manifest.js +1 -3
  79. package/template/coding-task-manager/lib/fs-watcher.js +3 -2
  80. package/template/coding-task-manager/lib/harness-history-transition.js +41 -14
  81. package/template/coding-task-manager/lib/headless-inflight-ops.js +203 -0
  82. package/template/coding-task-manager/lib/headless-missing-reply.js +42 -0
  83. package/template/coding-task-manager/lib/headless-term-service.js +60 -23
  84. package/template/coding-task-manager/lib/history-transition-read-op.js +179 -23
  85. package/template/coding-task-manager/lib/host-control-dial.js +167 -0
  86. package/template/coding-task-manager/lib/host-handoff-orchestrator.js +16 -2
  87. package/template/coding-task-manager/lib/host-handoff.js +51 -13
  88. package/template/coding-task-manager/lib/host-health-ledger.js +164 -12
  89. package/template/coding-task-manager/lib/host-self-truth-watchdog.js +4 -0
  90. package/template/coding-task-manager/lib/host-stats.js +328 -0
  91. package/template/coding-task-manager/lib/host-threadpool-evacuation.js +160 -0
  92. package/template/coding-task-manager/lib/host-threadpool-monitor.js +14 -1
  93. package/template/coding-task-manager/lib/hosted-input-dispatch.js +203 -0
  94. package/template/coding-task-manager/lib/hosted-sync-worker.js +113 -5
  95. package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +4 -0
  96. package/template/coding-task-manager/lib/instance-manifest-runtime.js +3 -0
  97. package/template/coding-task-manager/lib/instance-manifest.js +13 -1
  98. package/template/coding-task-manager/lib/instance-sync-status.js +15 -0
  99. package/template/coding-task-manager/lib/kernel/diag-log.js +12 -0
  100. package/template/coding-task-manager/lib/launch-model-override.js +160 -1
  101. package/template/coding-task-manager/lib/launch-plan-worker.js +59 -2
  102. package/template/coding-task-manager/lib/lead-model-observer.js +17 -2
  103. package/template/coding-task-manager/lib/lead-model-switch-runtime.js +57 -4
  104. package/template/coding-task-manager/lib/lead-model-switch.js +11 -2
  105. package/template/coding-task-manager/lib/lead-model-transition-runtime.js +42 -4
  106. package/template/coding-task-manager/lib/lead-model-transition-store.js +61 -3
  107. package/template/coding-task-manager/lib/legacy-draft-cancellation-recovery.js +111 -0
  108. package/template/coding-task-manager/lib/login-tripwire.js +18 -0
  109. package/template/coding-task-manager/lib/machine-pairing.js +238 -0
  110. package/template/coding-task-manager/lib/machine-pool.js +409 -0
  111. package/template/coding-task-manager/lib/machine-registry.js +749 -0
  112. package/template/coding-task-manager/lib/machine-self.js +66 -0
  113. package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +56 -4
  114. package/template/coding-task-manager/lib/media-journal-control-reader.js +473 -0
  115. package/template/coding-task-manager/lib/media-journal.js +84 -27
  116. package/template/coding-task-manager/lib/media-store-options.js +43 -15
  117. package/template/coding-task-manager/lib/migrations/v56-host-samples-and-machine-pool.js +76 -0
  118. package/template/coding-task-manager/lib/mirror-feed-sanitize.js +12 -3
  119. package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +16 -3
  120. package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
  121. package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
  122. package/template/coding-task-manager/lib/model-proxy/collaboration-codec.js +94 -0
  123. package/template/coding-task-manager/lib/model-proxy/handoff.js +494 -0
  124. package/template/coding-task-manager/lib/model-proxy/lead-worker-picks.js +25 -0
  125. package/template/coding-task-manager/lib/model-proxy/native-lead-listener.js +61 -7
  126. package/template/coding-task-manager/lib/model-proxy/proxy-cli.js +200 -0
  127. package/template/coding-task-manager/lib/model-proxy/proxy-instance-record.js +257 -0
  128. package/template/coding-task-manager/lib/model-proxy/responses-face.js +24 -2
  129. package/template/coding-task-manager/lib/model-proxy/stream-accounting.js +205 -0
  130. package/template/coding-task-manager/lib/model-proxy/tokens.js +78 -0
  131. package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
  132. package/template/coding-task-manager/lib/native-history-control.js +62 -14
  133. package/template/coding-task-manager/lib/native-lead-settings-client.js +2 -1
  134. package/template/coding-task-manager/lib/native-lead-tls.js +18 -4
  135. package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +628 -64
  136. package/template/coding-task-manager/lib/owner-action-manifest-data.json +14 -0
  137. package/template/coding-task-manager/lib/painted-row-recorder.js +9 -1
  138. package/template/coding-task-manager/lib/prompt-admission-trace.js +69 -0
  139. package/template/coding-task-manager/lib/prompt-submission-contract.js +21 -1
  140. package/template/coding-task-manager/lib/provider-login-broker.js +192 -9
  141. package/template/coding-task-manager/lib/provider-login-capture.js +153 -0
  142. package/template/coding-task-manager/lib/queue-draft-status-overlay.js +36 -0
  143. package/template/coding-task-manager/lib/raw-codex-history-window.js +5 -3
  144. package/template/coding-task-manager/lib/raw-history-block.js +3 -1
  145. package/template/coding-task-manager/lib/read-pool-client.js +36 -24
  146. package/template/coding-task-manager/lib/read-pool-op-registry.js +10 -1
  147. package/template/coding-task-manager/lib/read-pool-telemetry.js +14 -5
  148. package/template/coding-task-manager/lib/recent-catalog-query.js +26 -0
  149. package/template/coding-task-manager/lib/recent-session-catalog-response.js +9 -0
  150. package/template/coding-task-manager/lib/remote-command-context.js +12 -0
  151. package/template/coding-task-manager/lib/remote-command-runtime.js +18 -5
  152. package/template/coding-task-manager/lib/remote-machine-catalog.js +1062 -0
  153. package/template/coding-task-manager/lib/remote-machine-create.js +693 -0
  154. package/template/coding-task-manager/lib/remote-machine-link.js +939 -0
  155. package/template/coding-task-manager/lib/remote-machine-proxy.js +520 -0
  156. package/template/coding-task-manager/lib/remote-session-forward.js +306 -0
  157. package/template/coding-task-manager/lib/remote-submission-status.js +6 -0
  158. package/template/coding-task-manager/lib/restart-outcome-truth.js +20 -3
  159. package/template/coding-task-manager/lib/restart-preflight.js +9 -2
  160. package/template/coding-task-manager/lib/restore-backoff-gate.js +10 -2
  161. package/template/coding-task-manager/lib/restore-prepare-retry.js +12 -5
  162. package/template/coding-task-manager/lib/restore-resume-batch.js +22 -1
  163. package/template/coding-task-manager/lib/resume-blocked-wire.js +62 -1
  164. package/template/coding-task-manager/lib/resume-cwd.js +13 -0
  165. package/template/coding-task-manager/lib/resume-from-store.js +12 -1
  166. package/template/coding-task-manager/lib/resume-origin-facts.js +38 -1
  167. package/template/coding-task-manager/lib/resume-preflight.js +21 -16
  168. package/template/coding-task-manager/lib/review-repo-admission.js +39 -6
  169. package/template/coding-task-manager/lib/review-repo-registry.js +17 -0
  170. package/template/coding-task-manager/lib/root-delivery-runtime.js +72 -15
  171. package/template/coding-task-manager/lib/root-delivery-selection.js +37 -1
  172. package/template/coding-task-manager/lib/root-prompt-admission.js +43 -11
  173. package/template/coding-task-manager/lib/root-prompt-flow.js +175 -38
  174. package/template/coding-task-manager/lib/scrollback-archive-store.js +7 -4
  175. package/template/coding-task-manager/lib/server-memory-sampler-runtime.js +10 -0
  176. package/template/coding-task-manager/lib/server-memory-sampler.js +41 -2
  177. package/template/coding-task-manager/lib/session-analysis-memory.js +2 -0
  178. package/template/coding-task-manager/lib/session-create/codex-native-provider-gate.js +218 -0
  179. package/template/coding-task-manager/lib/session-create/codex-resume-exit-classify.js +52 -0
  180. package/template/coding-task-manager/lib/session-create/delegate-worker.js +95 -6
  181. package/template/coding-task-manager/lib/session-create/identity.js +20 -1
  182. package/template/coding-task-manager/lib/session-create/prepared.js +69 -1
  183. package/template/coding-task-manager/lib/session-create/provider-launch-context.js +77 -0
  184. package/template/coding-task-manager/lib/session-create/provider-process.js +38 -39
  185. package/template/coding-task-manager/lib/session-create/response.js +30 -12
  186. package/template/coding-task-manager/lib/session-create/transcript.js +13 -4
  187. package/template/coding-task-manager/lib/session-create/worker-continuation.js +43 -1
  188. package/template/coding-task-manager/lib/session-create/worker-notices.js +94 -5
  189. package/template/coding-task-manager/lib/session-create/worker-observability.js +99 -7
  190. package/template/coding-task-manager/lib/session-create/worker-stop.js +11 -3
  191. package/template/coding-task-manager/lib/session-create/worker-wait-watcher.js +315 -0
  192. package/template/coding-task-manager/lib/session-geometry-authority-handler.js +10 -1
  193. package/template/coding-task-manager/lib/session-host-manager.js +152 -206
  194. package/template/coding-task-manager/lib/session-jobs.js +17 -9
  195. package/template/coding-task-manager/lib/session-lifecycle.js +20 -50
  196. package/template/coding-task-manager/lib/session-model-action-preconditions.js +68 -3
  197. package/template/coding-task-manager/lib/session-resource-sampler.js +67 -9
  198. package/template/coding-task-manager/lib/session-standup.js +67 -4
  199. package/template/coding-task-manager/lib/session-status-ledger.js +42 -3
  200. package/template/coding-task-manager/lib/session-status-runtime.js +45 -1
  201. package/template/coding-task-manager/lib/session-status.js +2 -2
  202. package/template/coding-task-manager/lib/session-title-mirror.js +25 -57
  203. package/template/coding-task-manager/lib/session-transcript-trash.js +8 -1
  204. package/template/coding-task-manager/lib/startup-pointer-write.js +57 -0
  205. package/template/coding-task-manager/lib/state-sync/painted-frame-field.js +11 -0
  206. package/template/coding-task-manager/lib/storage/coding-session-model-store.js +21 -22
  207. package/template/coding-task-manager/lib/surviving-owner-bootstrap.js +7 -2
  208. package/template/coding-task-manager/lib/surviving-owner-recovery.js +2 -1
  209. package/template/coding-task-manager/lib/terminal-draft-http-status.js +25 -0
  210. package/template/coding-task-manager/lib/transcript-archiver.js +229 -229
  211. package/template/coding-task-manager/lib/transcript-frame-cache.js +12 -2
  212. package/template/coding-task-manager/lib/transcript-guard-job.js +55 -4
  213. package/template/coding-task-manager/lib/transcript-link.js +77 -0
  214. package/template/coding-task-manager/lib/transcript-manifest-store.js +8 -4
  215. package/template/coding-task-manager/lib/transcript-source.js +8 -3
  216. package/template/coding-task-manager/lib/transcript-store.js +40 -0
  217. package/template/coding-task-manager/lib/tunnel-lane-governor.js +6 -3
  218. package/template/coding-task-manager/lib/typing-interaction-trace.js +136 -2
  219. package/template/coding-task-manager/lib/typing-worker-stages.js +208 -1
  220. package/template/coding-task-manager/lib/usage-ledger/claude-parse.js +53 -2
  221. package/template/coding-task-manager/lib/usage-ledger/ingest.js +13 -4
  222. package/template/coding-task-manager/lib/usage-ledger/read.js +14 -0
  223. package/template/coding-task-manager/lib/usage-ledger/schema.js +49 -1
  224. package/template/coding-task-manager/lib/usage-ledger/store.js +337 -6
  225. package/template/coding-task-manager/lib/usage-ledger/task-read.js +123 -5
  226. package/template/coding-task-manager/lib/visible-revision-watchdog.js +8 -12
  227. package/template/coding-task-manager/lib/wake-skip-reasons.js +46 -0
  228. package/template/coding-task-manager/lib/walle-supervisor.js +98 -14
  229. package/template/coding-task-manager/lib/worker-failure-classify.js +13 -1
  230. package/template/coding-task-manager/lib/ws/input.js +1 -0
  231. package/template/coding-task-manager/lib/ws/state-sync.js +19 -2
  232. package/template/coding-task-manager/lib/ws/terminal-attach.js +32 -1
  233. package/template/coding-task-manager/lib/ws-router.js +68 -1
  234. package/template/coding-task-manager/lib/ws-send-admission.js +22 -4
  235. package/template/coding-task-manager/oauth-proxy.js +99 -55
  236. package/template/coding-task-manager/package-lock.json +24 -0
  237. package/template/coding-task-manager/package.json +3 -0
  238. package/template/coding-task-manager/protocol/message-table.json +0 -10
  239. package/template/coding-task-manager/protocol/messages.js +0 -10
  240. package/template/coding-task-manager/protocol/ws-schema.json +0 -20
  241. package/template/coding-task-manager/protocol/ws-schema.md +0 -2
  242. package/template/coding-task-manager/providers/codex-mcp-extract.js +121 -19
  243. package/template/coding-task-manager/providers/codex-mcp.js +14 -5
  244. package/template/coding-task-manager/public/css/app.css +279 -2
  245. package/template/coding-task-manager/public/css/reviews.css +4 -0
  246. package/template/coding-task-manager/public/index.html +297 -240
  247. package/template/coding-task-manager/public/js/app/machines-page.js +1252 -0
  248. package/template/coding-task-manager/public/js/app/models-tab.js +546 -36
  249. package/template/coding-task-manager/public/js/app/worktrees-panel.js +7 -5
  250. package/template/coding-task-manager/public/js/background-work.js +13 -7
  251. package/template/coding-task-manager/public/js/client-health-core.js +81 -0
  252. package/template/coding-task-manager/public/js/client-health-main.js +61 -0
  253. package/template/coding-task-manager/public/js/coding-session-model-presentation.js +44 -4
  254. package/template/coding-task-manager/public/js/core/session-commands.js +676 -0
  255. package/template/coding-task-manager/public/js/core/session-selectors.js +5 -1
  256. package/template/coding-task-manager/public/js/history-app-seam.js +26 -2
  257. package/template/coding-task-manager/public/js/history-painted-rows.js +17 -2
  258. package/template/coding-task-manager/public/js/history-reader-park.js +13 -1
  259. package/template/coding-task-manager/public/js/history-scroller.js +187 -9
  260. package/template/coding-task-manager/public/js/history-transcript-pages.js +5 -5
  261. package/template/coding-task-manager/public/js/machine-pool-shared.js +436 -0
  262. package/template/coding-task-manager/public/js/private-state-scope.js +31 -3
  263. package/template/coding-task-manager/public/js/prompt-submission-client.js +173 -4
  264. package/template/coding-task-manager/public/js/prompt-submission-tray.js +281 -0
  265. package/template/coding-task-manager/public/js/protocol.js +0 -10
  266. package/template/coding-task-manager/public/js/queue-panel-submission-reconcile.js +174 -1
  267. package/template/coding-task-manager/public/js/reviews.js +7 -3
  268. package/template/coding-task-manager/public/js/root-draft-lifetime.js +139 -2
  269. package/template/coding-task-manager/public/js/root-queue-builder-lifetime.js +16 -0
  270. package/template/coding-task-manager/public/js/row-join.js +18 -3
  271. package/template/coding-task-manager/public/js/session-model-picker.js +1 -1
  272. package/template/coding-task-manager/public/js/session-search-utils.js +46 -19
  273. package/template/coding-task-manager/public/js/session-usage-view.js +1 -0
  274. package/template/coding-task-manager/public/js/state-sync-client.js +4 -0
  275. package/template/coding-task-manager/public/js/terminal/runtime-02.js +16 -7
  276. package/template/coding-task-manager/public/js/terminal/runtime-03.js +41 -0
  277. package/template/coding-task-manager/public/js/terminal/runtime-04.js +41 -4
  278. package/template/coding-task-manager/public/js/terminal/runtime-12.js +52 -2
  279. package/template/coding-task-manager/public/js/terminal/runtime-16.js +12 -12
  280. package/template/coding-task-manager/public/js/terminal/runtime-18.js +14 -0
  281. package/template/coding-task-manager/public/js/terminal/runtime-19.js +8 -4
  282. package/template/coding-task-manager/public/js/terminal/runtime-20.js +210 -73
  283. package/template/coding-task-manager/public/js/terminal/runtime-21.js +19 -6
  284. package/template/coding-task-manager/public/js/terminal/runtime-22.js +86 -0
  285. package/template/coding-task-manager/public/js/terminal-history-viewport.js +1 -3
  286. package/template/coding-task-manager/public/js/terminal-wheel.js +39 -2
  287. package/template/coding-task-manager/public/js/walle/chat-conversation.js +11 -3
  288. package/template/coding-task-manager/public/js/walle-session.js +30 -7
  289. package/template/coding-task-manager/public/m/app.css +226 -8
  290. package/template/coding-task-manager/public/m/app.js +502 -9
  291. package/template/coding-task-manager/public/m/index.html +38 -1
  292. package/template/coding-task-manager/public/m/machines.js +1313 -0
  293. package/template/coding-task-manager/public/m/pool-failover.js +391 -0
  294. package/template/coding-task-manager/public/m/private-queue-actions.js +26 -7
  295. package/template/coding-task-manager/public/m/sw.js +5 -0
  296. package/template/coding-task-manager/queue-engine.js +13 -0
  297. package/template/coding-task-manager/server/approvals.js +5 -6
  298. package/template/coding-task-manager/server/boot.js +136 -17
  299. package/template/coding-task-manager/server.js +964 -425
  300. package/template/coding-task-manager/session-integrity.js +7 -9
  301. package/template/coding-task-manager/workers/db-owner-worker.js +53 -7
  302. package/template/coding-task-manager/workers/read-pool-worker.js +17 -6
  303. package/template/coding-task-manager/workers/session-host-pool-process.js +18 -4
  304. package/template/coding-task-manager/workers/session-host-process.js +14 -3
  305. package/template/coding-task-manager/workers/transcript-archive-worker.js +1 -1
  306. package/template/coding-task-manager/workers/worktree-worker.js +34 -0
  307. package/template/package-lock.json +2 -2
  308. package/template/package.json +1 -1
  309. package/template/scripts/conversation-storage-v2-migration.js +78 -11
  310. package/template/shared/conversation-storage-v2-contract.js +42 -6
  311. package/template/shared/fixtures/worker-status-payloads.json +392 -0
  312. package/template/shared/git-remote-key.js +83 -0
  313. package/template/shared/host-identity.js +98 -2
  314. package/template/shared/owner-write-safety.js +135 -0
  315. package/template/shared/public-runtime.js +13 -5
  316. package/template/shared/release-schema.json +3 -2
  317. package/template/shared/report-citations.js +14 -0
  318. package/template/shared/sqlite-write-lock.js +361 -223
  319. package/template/shared/storage/contract.js +35 -3
  320. package/template/wall-e/agent.js +21 -0
  321. package/template/wall-e/api-walle.js +5 -0
  322. package/template/wall-e/bin/brain-purge-legacy-ctm.js +3 -2
  323. package/template/wall-e/bin/walle-mcp-stdio.js +12 -6
  324. package/template/wall-e/brain/helper-rules-schema.js +129 -0
  325. package/template/wall-e/brain/migrations.js +51 -22
  326. package/template/wall-e/brain/model-deployment-facts.js +31 -1
  327. package/template/wall-e/brain/owner-call-whitelist.js +16 -0
  328. package/template/wall-e/brain/schema-ensures.js +22 -0
  329. package/template/wall-e/brain.js +1 -1
  330. package/template/wall-e/coding/host-capabilities.js +2 -2
  331. package/template/wall-e/coding/journal-dispatcher.js +51 -0
  332. package/template/wall-e/coding/session-journal.js +28 -40
  333. package/template/wall-e/coding-orchestrator.js +23 -26
  334. package/template/wall-e/fly.toml +1 -1
  335. package/template/wall-e/http/auth.js +18 -1
  336. package/template/wall-e/http/blog-views-api.js +318 -0
  337. package/template/wall-e/http/helper-rules-api.js +321 -0
  338. package/template/wall-e/http/model-proxy-providers.js +3 -1
  339. package/template/wall-e/http/telemetry-api.js +12 -0
  340. package/template/wall-e/lib/blog-view-policy.js +88 -0
  341. package/template/wall-e/lib/blog-view-store.js +169 -0
  342. package/template/wall-e/lib/client-config-lock.js +2 -1
  343. package/template/wall-e/lib/coding-journal-writer-child-client.js +67 -18
  344. package/template/wall-e/lib/error-kinds.js +10 -1
  345. package/template/wall-e/lib/feature-manifest.js +1 -1
  346. package/template/wall-e/lib/helper-rules/cell-state.js +123 -0
  347. package/template/wall-e/lib/helper-rules/evaluate.js +357 -0
  348. package/template/wall-e/lib/helper-rules/explore-rubric.md +12 -0
  349. package/template/wall-e/lib/helper-rules/grade.js +277 -0
  350. package/template/wall-e/lib/helper-rules/label.js +247 -0
  351. package/template/wall-e/lib/helper-rules/lead-call-hook.js +522 -0
  352. package/template/wall-e/lib/helper-rules/store.js +667 -0
  353. package/template/wall-e/lib/mcp-integration.js +5 -6
  354. package/template/wall-e/lib/runtime-worker-pool.js +1 -0
  355. package/template/wall-e/lib/scheduler-worker-jobs.js +6 -0
  356. package/template/wall-e/lib/telemetry-db-schema.js +37 -0
  357. package/template/wall-e/lib/telemetry-process-client.js +76 -1
  358. package/template/wall-e/llm/default-fallback.js +4 -0
  359. package/template/wall-e/llm/model-catalog.js +13 -4
  360. package/template/wall-e/llm/model-roles.js +6 -0
  361. package/template/wall-e/llm/portkey-http.js +150 -0
  362. package/template/wall-e/llm/portkey-sync.js +15 -145
  363. package/template/wall-e/llm/worker-mcp-routing.js +61 -0
  364. package/template/wall-e/llm/worker-route.js +43 -1
  365. package/template/wall-e/loops/brain-custom-writes.js +45 -0
  366. package/template/wall-e/mcp-protocol-contract.js +41 -1
  367. package/template/wall-e/mcp-server.js +13 -2
  368. package/template/wall-e/mcp-worker-schemas.js +20 -2
  369. package/template/wall-e/memory/ctm-context-client.js +25 -5
  370. package/template/wall-e/shared/release-schema.json +3 -2
  371. package/template/wall-e/shared/sqlite-write-lock.js +361 -223
  372. package/template/wall-e/shared/storage/contract.js +35 -3
  373. package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
  374. package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +6 -0
  375. package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +7 -2
  376. package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +7 -2
  377. package/template/wall-e/tools/local-tools.js +12 -5
  378. package/template/wall-e/workers/brain-owner-worker.js +9 -0
  379. package/template/wall-e/workers/coding-journal-writer-child.js +8 -1
  380. package/template/wall-e/workers/telemetry-worker.js +8 -0
  381. package/template/coding-task-manager/lib/giant-freeze-park.js +0 -31
@@ -5,7 +5,6 @@ const { dispatchOwnedInputAck } = require('./host-input-ack');
5
5
  const { EventEmitter } = require('node:events');
6
6
  const { fork } = require('node:child_process');
7
7
  const fs = require('node:fs');
8
- const net = require('node:net');
9
8
  const path = require('node:path');
10
9
  const crypto = require('node:crypto');
11
10
  const { resolveRealNode } = require('./real-node'); const { hostDiagnosticFrame } = require('./host-diagnostic-frame');
@@ -18,13 +17,14 @@ const {
18
17
  isProcessAlive,
19
18
  probeHostAliveDetailed,
20
19
  } = require('./host-registry');
21
- const { CONTROL_CONNECTION_ROLE, encodeFrame, createFrameDecoder, PROTOCOL_VERSION, FLOOR_VERSION, HOST_HANDOFF_EXIT_CODE } = require('./host-control-protocol');
20
+ const { HOST_HANDOFF_EXIT_CODE } = require('./host-control-protocol');
21
+ const { dialControlSocketWithRetry: _dialControlSocketWithRetry } = require('./host-control-dial');
22
+ const { postSocketFrameObserved, postIpcFrameObserved, postSelectedSocket, notePostRefusal, withAdapterPostObservation, forwardPostObservation, observeAdapterWrite } = require('./hosted-input-dispatch');
22
23
  const { isHostedAdapterLive, isHostedHeadlessLive, isHostedSessionStateAdoptable } = require('./hosted-adapter-liveness');
23
- const { hostRuntimeCompatibilityDecision } = require('./host-runtime-compatibility');
24
24
  const { hashClosureFiles } = require('./host-closure-hash');
25
25
  const { headlessMissingReplies } = require('./headless-missing-reply');
26
- const { resolveDialRetryDeadline, DIAL_MAX_STALL_EXTENSION_RATIO } = require('./host-dial-stall-clock');
27
26
  const { redialAdoptedHost } = require('./adopted-host-redial');
27
+ const { sanitizeSessionOps, createOverdueEdgeTrigger } = require('./headless-inflight-ops'); // WP-0 (D-6/S-12): frame sessionOps + one-shot overdue edge
28
28
  const { dialHostState, shutdownHostSession } = require('./host-adopt-dial');
29
29
  const handoffOrchestrator = require('./host-handoff-orchestrator');
30
30
  const { applyModelHostReceipt, applyStartedModelHostReceipt } = require('./model-host-receipt');
@@ -49,162 +49,10 @@ function _openHostLogFd(dataDir, hostId) {
49
49
  }
50
50
  }
51
51
 
52
+ // A1: the wire leaf (guard, encode, write, swallowed-failure boundary) moved to
53
+ // lib/hosted-input-dispatch.js, where it observes its own outcome; this stays VOID for callers.
52
54
  function _writeFrame(conn, obj) {
53
- if (!conn || conn.destroyed) return;
54
- try { conn.write(encodeFrame(obj)); } catch { /* expected: connection may already be gone */ }
55
- }
56
-
57
- // Dial a host control socket within a stall-aware deadline. The first state frame
58
- // is judged by hostRuntimeCompatibilityDecision (a differing closure hash is adoptable); later frames
59
- // route to onFrame, while onState exposes adoptable session metadata.
60
- function _dialControlSocketWithRetry(socketPath, {
61
- serverBootId, onFrame, onState, timeoutMs = 5000, retryDelayMs = 200,
62
- protocolVersion = PROTOCOL_VERSION,
63
- } = {}) {
64
- return new Promise((resolve, reject) => {
65
- const budgetMs = Math.max(0, Number(timeoutMs) || 5000);
66
- // Stall-aware, not fixed wall clock: see lib/host-dial-stall-clock.js for the incident where
67
- // this deadline drained during the server's OWN boot freeze and a healthy pooled host was
68
- // SIGTERM'd (taking its live sessions with it). `deadline` is reassigned as stall is credited
69
- // back; `_stallExtendedMs` is the running total and is capped, so a permanently stalled
70
- // server still times out rather than waiting forever.
71
- let deadline = Date.now() + budgetMs;
72
- let _stallExtendedMs = 0;
73
- const maxStallExtensionMs = budgetMs * DIAL_MAX_STALL_EXTENSION_RATIO;
74
- let settled = false;
75
- let retryTimer = null, deadlineTimer = null, activeConn = null;
76
- function finishOk(conn) {
77
- if (settled) return;
78
- settled = true;
79
- clearTimeout(deadlineTimer);
80
- resolve(conn);
81
- }
82
- function finishFail(err) {
83
- if (settled) return;
84
- settled = true;
85
- clearTimeout(retryTimer); clearTimeout(deadlineTimer);
86
- try { activeConn?.destroy(); } catch { /* expected: connection may already be gone */ }
87
- reject(err || new Error('control socket dial failed'));
88
- }
89
- function armDeadlineWatchdog(scheduledAt = Date.now()) {
90
- const delayMs = Math.max(1, deadline - scheduledAt);
91
- deadlineTimer = setTimeout(() => {
92
- const firedAt = Date.now();
93
- const credited = resolveDialRetryDeadline({ deadline, scheduledAt, firedAt, retryDelayMs: delayMs,
94
- extendedMs: _stallExtendedMs, maxExtensionMs: maxStallExtensionMs });
95
- deadline = credited.deadline; _stallExtendedMs = credited.extendedMs;
96
- if (firedAt < deadline) armDeadlineWatchdog(firedAt); else finishFail(new Error(`control socket handshake timed out after ${timeoutMs}ms`));
97
- }, delayMs);
98
- deadlineTimer.unref?.();
99
- }
100
- function scheduleRetry() {
101
- if (settled) return;
102
- if (Date.now() >= deadline) {
103
- finishFail(new Error(
104
- `control socket dial timed out after ${timeoutMs}ms`
105
- + (_stallExtendedMs > 0 ? ` (+${_stallExtendedMs}ms credited for main-loop stall)` : ''),
106
- ));
107
- return;
108
- }
109
- const scheduledAt = Date.now();
110
- retryTimer = setTimeout(() => {
111
- // A retry that fires far later than it was armed for means OUR event loop was starved --
112
- // that time is not evidence about the child, so give it back before re-testing the
113
- // deadline above. Bounded by maxStallExtensionMs.
114
- const credited = resolveDialRetryDeadline({
115
- deadline,
116
- scheduledAt,
117
- firedAt: Date.now(),
118
- retryDelayMs,
119
- extendedMs: _stallExtendedMs,
120
- maxExtensionMs: maxStallExtensionMs,
121
- });
122
- deadline = credited.deadline;
123
- _stallExtendedMs = credited.extendedMs;
124
- attempt();
125
- }, retryDelayMs);
126
- retryTimer.unref?.();
127
- }
128
- function attempt() {
129
- if (settled) return;
130
- // Do NOT dial before the host has created its socket. A connect to a missing unix socket
131
- // makes node build an ExceptionWithHostPort and capture its stack, and this loop runs every
132
- // retryDelayMs across every concurrent dial (pool + per-session + registry probes). Measured
133
- // 2026-08-23 with the server's own auto-CPU-profiler, during a boot where a pooled host was
134
- // being started:
135
- // top self-time frames: ExceptionWithHostPort (errors:765) 3290ms
136
- // | defaultPrepareStackTrace (errors:95) 3256ms
137
- // | (garbage collector) 752ms | checkError (destroy:33) 512ms
138
- // | scheduleRetry (session-host-manager.js:89) 192ms
139
- // i.e. ~6.5s of main-loop CPU spent building the retry loop's OWN connection errors. That
140
- // block (`[perf-lag] Event loop blocked 6102ms`) is what then starved the very handshake it
141
- // was polling for, until the dial "timed out" and the manager SIGTERM'd a healthy pooled
142
- // host, killing its live sessions. A self-inflicted starvation loop.
143
- //
144
- // fs.access (async -- never sync I/O on the main loop) is a cheap stat that produces no
145
- // exception object. This is a fast-path skip only: once the socket exists the dial below is
146
- // byte-identical to before, and a socket that appears between this check and the connect
147
- // still takes the ordinary error path.
148
- fs.access(socketPath, fs.constants.F_OK, (missing) => {
149
- if (settled) return;
150
- if (missing) { scheduleRetry(); return; }
151
- _connect();
152
- });
153
- }
154
- function _connect() {
155
- if (settled) return;
156
- let conn;
157
- let helloAcked = false;
158
- try {
159
- conn = net.createConnection(socketPath);
160
- activeConn = conn;
161
- } catch {
162
- scheduleRetry();
163
- return;
164
- }
165
- const decoder = createFrameDecoder({
166
- onFrame: (msg) => {
167
- if (!helloAcked) {
168
- if (msg && msg.type === 'state') {
169
- const diskClosureHash = hashClosureFiles(Array.isArray(msg.closureFiles) ? msg.closureFiles : []), hostClosureHash = String(msg.closureHash || '');
170
- const compatibility = { ...hostRuntimeCompatibilityDecision({
171
- expectedProtocol: protocolVersion, actualProtocol: msg.protocolVersion,
172
- expectedFloor: FLOOR_VERSION, actualFloor: msg.floorVersion, diskClosureHash, hostClosureHash,
173
- }), hostClosureHash, diskClosureHash };
174
- if (!compatibility.compatible) {
175
- helloAcked = true;
176
- try { conn.destroy(); } catch { /* expected: already gone */ }
177
- finishFail(Object.assign(
178
- new Error(`control socket host compatibility mismatch (${compatibility.reason})`),
179
- { code: 'HOST_INCOMPATIBLE', compatibilityReason: compatibility.reason },
180
- ));
181
- return;
182
- }
183
- helloAcked = true;
184
- try { onState?.(msg, compatibility); } catch { /* expected: caller's onState must not wedge the dial */ }
185
- finishOk(conn);
186
- }
187
- return;
188
- }
189
- if (typeof onFrame === 'function') onFrame(msg);
190
- },
191
- onError: () => { try { conn.destroy(); } catch { /* expected: already gone */ } },
192
- });
193
- conn.on('data', (chunk) => decoder.push(chunk));
194
- conn.on('connect', () => {
195
- try {
196
- conn.write(encodeFrame({ type: 'hello', serverBootId, connectionRole: CONTROL_CONNECTION_ROLE.CONTROLLER, lastSeq: 0, protocolVersion, floorVersion: FLOOR_VERSION }));
197
- } catch {
198
- try { conn.destroy(); } catch { /* expected: already gone */ }
199
- }
200
- });
201
- conn.on('error', () => { /* expected: close follows error and owns the sole retry */ });
202
- conn.on('close', () => { if (!helloAcked) scheduleRetry(); });
203
- }
204
-
205
- attempt();
206
- armDeadlineWatchdog();
207
- });
55
+ postSocketFrameObserved(conn, obj);
208
56
  }
209
57
 
210
58
  const DEFAULT_START_TIMEOUT_MS = 5000;
@@ -248,6 +96,26 @@ function _sleep(ms) {
248
96
 
249
97
  const TERMINATE_ACK_TIMEOUT_MS = 1500;
250
98
 
99
+ // WP-4 (clock-free-tests-followups-r4-2026-10-24.html §5 R-7, §6.5): stamp the FIRST
100
+ // main-thread touch of a headless message with the receiving thread's own clock, so server.js
101
+ // can price the receiver-loop leg (`frame-posted` -> `frame-arrived`) separately from the work
102
+ // it does after (`mirror-revision`). Read-only instrumentation: the property is NON-enumerable, so
103
+ // the message's shape is unchanged for every consumer that compares, spreads or serializes it (a
104
+ // pool host's `serialized` missing reply is deep-equal to the shared headlessMissingReplies table),
105
+ // and no decision anywhere reads it; server.js reads it off the same object. One helper for all three
106
+ // transports (the dedicated host's IPC channel and control socket both funnel through
107
+ // HostedPtyAdapter._handleChildMessage; the pool's fallback and the legacy worker path call it
108
+ // themselves), so the value means the same thing whichever carried the frame. Returns the
109
+ // message so a call site can chain it into its own emit.
110
+ function _stampMainArrival(message) {
111
+ if (message && typeof message === 'object') {
112
+ Object.defineProperty(message, '_mainArrivedAt', {
113
+ value: Date.now(), enumerable: false, writable: true, configurable: true,
114
+ });
115
+ }
116
+ return message;
117
+ }
118
+
251
119
  // WP-3: bound for adoptHost's dial. Plain const, not a flag -- a boot reconcile
252
120
  // pass adopting a handful of hosts is not a hot path.
253
121
  //
@@ -405,6 +273,15 @@ class HostedPtyAdapter {
405
273
  this.closed = true;
406
274
  this._manager?._unregister(this.sessionId, this);
407
275
  if (this._hostId) this._manager?._removeHostRecordAsync(this._hostId);
276
+ // WP-3 (session-stops-followups-2026-10-24.html, D-5): a dedicated host's
277
+ // death announces itself exactly like the pool's and an adopted host's, so
278
+ // the constructor's single 'host-exit' listener drops this hostId's frame
279
+ // clock and server.js's listener drops its readiness-ledger entry. Without it
280
+ // both outlived the host -- and since a dedicated hostId IS the sessionId (set
281
+ // in _startDedicated above), the next host for that session inherited the dead
282
+ // one's ledger misses (final-review fix I6, pool-only until now). Sits AFTER
283
+ // the `if (this.closed) return;` guard, so it fires once per host.
284
+ if (this._hostId) this._manager?.emit?.('host-exit', { hostId: this._hostId, kind: 'dedicated' });
408
285
  this._emitExitOnce({
409
286
  exitCode: Number.isFinite(code) ? code : 1,
410
287
  signal: signal || null,
@@ -465,32 +342,30 @@ class HostedPtyAdapter {
465
342
 
466
343
  _post(message) {
467
344
  if (this._pooled) {
468
- return this._manager?._postToPool({ ...message, sessionId: message.sessionId || this.sessionId }) || false;
345
+ // A1: only a truly ABSENT manager refuses here; a present manager keeps its COPY + forwarded scope.
346
+ if (this._manager === undefined || this._manager === null) { notePostRefusal(message); return false; }
347
+ const forwarded = { ...message, sessionId: message.sessionId || this.sessionId };
348
+ return forwardPostObservation(message, forwarded, () => this._manager?._postToPool(forwarded)) || false;
469
349
  }
470
- if (this.closed) return false;
350
+ if (this.closed) { notePostRefusal(message); return false; }
471
351
  // WP-1 (deliverable 2): once the control flip has succeeded, ALL control +
472
352
  // data goes over the socket -- IPC is no longer used to send.
473
353
  if (this._transport === 'socket' && this._socketConn && !this._socketConn.destroyed) {
474
- try {
475
- _writeFrame(this._socketConn, message);
476
- return true;
477
- } catch (err) {
478
- this._events.emit('host-error', err);
479
- return false;
480
- }
481
- }
482
- if (!this._child || !this._child.connected) return false;
483
- try {
484
- this._child.send(message);
485
- return true;
486
- } catch (err) {
487
- this._events.emit('host-error', err);
488
- return false;
354
+ return postSelectedSocket(() => this._socketConn, message, (err) => this._events.emit('host-error', err));
489
355
  }
356
+ // The helper owns the ONE missing/disconnected refusal AND the legacy try/send/host-error boundary.
357
+ return postIpcFrameObserved(this._child, message, (err) => this._events.emit('host-error', err)).posted;
490
358
  }
491
359
 
492
360
  _handleChildMessage(msg) {
493
361
  if (!msg || typeof msg !== 'object') return;
362
+ // WP-3 (session-stops-mid-job-2026-10-24.html, D-10): every frame a host
363
+ // sends -- PTY data, exit, threadpool report, serialize reply, control
364
+ // replies -- proves it alive. Recorded under the SAME hostId the readiness
365
+ // ledger keys on (lib/ws/state-sync.js derives it via
366
+ // SessionHostManager.hostIdForSession), so a pooled session records under
367
+ // its pool's `pool-<pid>`, not under its own sessionId. Cheap: one Map.set.
368
+ this._manager?._noteHostFrame?.(this._manager?.hostIdForSession?.(this.sessionId) || this._hostId);
494
369
  switch (msg.type) {
495
370
  case 'ready':
496
371
  if (this._hostId) {
@@ -536,7 +411,10 @@ class HostedPtyAdapter {
536
411
  });
537
412
  return;
538
413
  case 'headless-message':
539
- this._manager?._emitHeadlessMessage(msg.message || {});
414
+ // WP-4 (R-7): the first main-thread touch of this frame on BOTH dedicated-host
415
+ // transports -- the IPC channel's child.on('message') and the control socket's
416
+ // onFrame --- converge on this switch.
417
+ this._manager?._emitHeadlessMessage(_stampMainArrival(msg.message || {}));
540
418
  return;
541
419
  case 'snapshot':
542
420
  this._headlessReady = true;
@@ -616,7 +494,19 @@ class HostedPtyAdapter {
616
494
  // exists once the host's 'input-ack' frame comes back (see _handleChildMessage's
617
495
  // 'input-ack' case and the manager's onInputAck hook). `inputSeq` correlates the two.
618
496
  write(data, { inputSeq } = {}) {
619
- return this._post({ type: 'input', sessionId: this.sessionId, data: String(data || ''), inputSeq: Number(inputSeq) || 0 }) === true;
497
+ // A1: the frame is built here exactly as before, then handed to the ACTUAL selected `_post`
498
+ // through the observation scope, so an observed write can report what THIS route did while the
499
+ // legacy `=== true` Boolean keeps its exact meaning.
500
+ const message = { type: 'input', sessionId: this.sessionId, data: String(data || ''), inputSeq: Number(inputSeq) || 0 };
501
+ return withAdapterPostObservation(this, message, () => this._post(message)) === true;
502
+ }
503
+
504
+ // A1: observe ONE dispatch through the ACTUAL selected writer (never a prototype bypass). The
505
+ // result is diagnostic data -- `posted` mirrors the selected writer's own legacy result and the
506
+ // disposition says only how much this module actually saw. It is not byte-write, ack or
507
+ // no-post proof, and it grants no caller any withdrawal authority.
508
+ writeObserved(data, { inputSeq } = {}) {
509
+ return observeAdapterWrite(this, () => this.write(data, { inputSeq }));
620
510
  }
621
511
 
622
512
  resize(cols, rows) {
@@ -793,10 +683,28 @@ class SessionHostManager extends EventEmitter {
793
683
  // only, same seam shape as _onDiagnostic -- never a path that kills/respawns/migrates
794
684
  // anything on 'stalled' (see _handleThreadpoolFrame below and its AC-0c test).
795
685
  this._threadpoolStates = new Map();
686
+ // WP-3 (session-stops-mid-job-2026-10-24.html, D-10): hostId -> the last
687
+ // instant ANY frame arrived from that host. This is the readiness ledger's
688
+ // ONE liveness input (server.js injects `lastFrameAt` into it): a hard
689
+ // serialize miss on a host whose lastFrameAt is newer than the request was
690
+ // issued is 'degraded' (readiness), never a kill. Written by
691
+ // HostedPtyAdapter._handleChildMessage and _handleThreadpoolFrame below on
692
+ // every received frame; dropped when the manager announces 'host-exit'.
693
+ this._hostFrameAt = new Map();
694
+ // WP-3 (session-stops-followups-2026-10-24.html, D-6): the ONE place a host's
695
+ // frame clock is dropped. Every death path announces itself as
696
+ // 'host-exit {hostId, kind}' -- pool, adopted (lib/adopted-host-redial.js) and
697
+ // dedicated (HostedPtyAdapter's child 'exit' handler below) -- so a future death
698
+ // path is covered by emitting that event alone, and no call site can forget the
699
+ // clock (the pre-D-6 inline delete at the pool exit left the adopted and
700
+ // dedicated paths leaking it).
701
+ this.on('host-exit', ({ hostId } = {}) => { if (hostId) this._hostFrameAt.delete(String(hostId)); });
796
702
  this._onThreadpoolReport = typeof options.onThreadpoolReport === 'function' ? options.onThreadpoolReport : null;
797
703
  // D-2 (pty-host-threadpool-stranded-waiters-2026-09-25.html): report-only seam for
798
704
  // input-ack frames -- server.js's _onHostInputAck. Same shape as onThreadpoolReport.
799
705
  this._onInputAck = typeof options.onInputAck === 'function' ? options.onInputAck : null; this._onSessionDiagnostic = typeof options.onSessionDiagnostic === 'function' ? options.onSessionDiagnostic : null; // probe: host-diagnostic rows
706
+ // WP-0 (D-6): at most one report-only headless-op-overdue diagnostic per (hostId, sid8, seq).
707
+ this._headlessOpOverdue = createOverdueEdgeTrigger();
800
708
  }
801
709
 
802
710
  createPty(options = {}) {
@@ -1152,12 +1060,18 @@ class SessionHostManager extends EventEmitter {
1152
1060
  // (never a per-tick echo -- the monitor itself already only frames on transition or once
1153
1061
  // per its own report interval, but this dedupes defensively against redelivery), and
1154
1062
  // forwards every frame to the optional onThreadpoolReport hook (server.js's __host__
1155
- // diagnostic-row writer + golden-signals feed) -- report-only, exactly like _onDiagnostic;
1156
- // nothing here ever kills/respawns/migrates a host (AC-0c).
1063
+ // diagnostic-row writer + golden-signals feed) -- report-only, exactly like _onDiagnostic; nothing
1064
+ // here kills/respawns/migrates a host (09-25 AC-0c). WP-3 supersedes that for the SUPERVISOR only: server.js's onThreadpoolReport -> hostHealth.recordThreadpool -> evacuateStalledHost evacuates a stalled host (docs/stale-pty-host-evacuation-2026-10-24.html §5 D-3, §9 S-6).
1157
1065
  _handleThreadpoolFrame(hostId, msg) {
1158
1066
  if (!msg || typeof msg !== 'object') return;
1067
+ // WP-3 (D-10): a threadpool report is a frame like any other -- the host's
1068
+ // main thread is alive and talking even when its threadpool is wedged (the
1069
+ // exact 2026-10-24 shape: stalled for 68 min while PTY I/O kept flowing).
1070
+ this._noteHostFrame(hostId);
1159
1071
  const state = msg.state === 'stalled' ? 'stalled' : 'ok';
1160
1072
  const prev = this._threadpoolStates.get(hostId);
1073
+ // WP-0 (D-6): the frame's per-session view, sanitized to strings/numbers and capped at 5.
1074
+ const sessionOps = sanitizeSessionOps(msg.sessionOps, 5);
1161
1075
  const snapshot = {
1162
1076
  hostId,
1163
1077
  pid: Number.isFinite(msg.pid) ? msg.pid : (prev?.pid ?? null),
@@ -1167,12 +1081,22 @@ class SessionHostManager extends EventEmitter {
1167
1081
  requestsPerMin: Number(msg.requestsPerMin) || 0,
1168
1082
  byType: msg.byType && typeof msg.byType === 'object' ? { ...msg.byType } : {},
1169
1083
  probeMs: Number.isFinite(msg.probeMs) ? msg.probeMs : null,
1084
+ sessionOps,
1170
1085
  receivedAt: Date.now(),
1171
1086
  };
1172
1087
  this._threadpoolStates.set(hostId, snapshot);
1173
1088
  if (!prev || prev.state !== state) {
1174
1089
  this.logger.warn?.(`[session-host-pool] threadpool-${state} hostId=${hostId} pid=${snapshot.pid} pendingRequests=${snapshot.pendingRequests} oldestPendingMs=${snapshot.oldestPendingMs}`);
1175
1090
  }
1091
+ // WP-0 (D-6/AC-0c): one report-only diagnostic per (hostId, sid8, seq) at >= 30 s. Never
1092
+ // rejects/times out/reorders an op, and never touches host or session state.
1093
+ for (const hit of this._headlessOpOverdue.next(sessionOps, hostId, (sid8) => {
1094
+ for (const sid of this.hosts.keys()) if (sid.startsWith(sid8)) return sid;
1095
+ return sid8;
1096
+ })) {
1097
+ try { this._onSessionDiagnostic?.(hit.sessionId, 'headless-op-overdue', hit.details); }
1098
+ catch (err) { this.logger.warn?.(`[session-host-pool] onSessionDiagnostic hook threw: ${err?.message || err}`); }
1099
+ }
1176
1100
  if (this._onThreadpoolReport) {
1177
1101
  try { this._onThreadpoolReport(snapshot); }
1178
1102
  catch (err) { this.logger.warn?.(`[session-host-pool] onThreadpoolReport hook threw: ${err?.message || err}`); }
@@ -1236,6 +1160,23 @@ class SessionHostManager extends EventEmitter {
1236
1160
  return null;
1237
1161
  }
1238
1162
 
1163
+ // WP-3 (session-stops-mid-job-2026-10-24.html, D-10): the per-host frame clock.
1164
+ // `_noteHostFrame` is called for every frame a host sends (see
1165
+ // HostedPtyAdapter._handleChildMessage and _handleThreadpoolFrame); `lastFrameAt`
1166
+ // is the public read server.js injects into lib/host-health-ledger.js. One
1167
+ // Map.set per frame -- no allocation beyond that, no event emitted per frame.
1168
+ _noteHostFrame(hostId) {
1169
+ const id = String(hostId || '');
1170
+ if (!id) return;
1171
+ this._hostFrameAt.set(id, Date.now());
1172
+ }
1173
+
1174
+ lastFrameAt(hostId) {
1175
+ const id = String(hostId || '');
1176
+ if (!id) return 0;
1177
+ return this._hostFrameAt.get(id) || 0;
1178
+ }
1179
+
1239
1180
  // WP-1: reconstructs the same shape of record `_shutdownOrphanHost` (server.js)
1240
1181
  // expects -- {hostId, kind, pid, socketPath, sessionIds} -- from this manager's
1241
1182
  // own live routing state, so the revive path never has to block on an async
@@ -1384,11 +1325,17 @@ class SessionHostManager extends EventEmitter {
1384
1325
  // prototype's IPC fallback can never fire) -- redial on write failure
1385
1326
  // (lib/adopted-host-redial.js) instead of failing forever.
1386
1327
  adapter._post = (message) => {
1387
- if (adapter.closed) return false;
1328
+ if (adapter.closed) { notePostRefusal(message); return false; }
1329
+ // A1: this per-instance override stays the selected route for an adopted host. The helper
1330
+ // owns the WHOLE second-lookup/write/error boundary, so a caught failure falls through to
1331
+ // this adapter's ORIGINAL redial (never lost, never doubled) and only a genuinely missing
1332
+ // socket refuses here.
1388
1333
  if (adapter._socketConn && !adapter._socketConn.destroyed) {
1389
- try { _writeFrame(adapter._socketConn, message); return true; }
1390
- catch (err) { adapter._events.emit('host-error', err); } // HANDLE: fall through to redial below
1334
+ if (postSelectedSocket(() => adapter._socketConn, message, (err) => adapter._events.emit('host-error', err))) return true;
1335
+ this._redialAdoptedHost(hostId, socketPath);
1336
+ return false;
1391
1337
  }
1338
+ notePostRefusal(message);
1392
1339
  this._redialAdoptedHost(hostId, socketPath);
1393
1340
  return false;
1394
1341
  };
@@ -1495,12 +1442,12 @@ class SessionHostManager extends EventEmitter {
1495
1442
 
1496
1443
  // WP-3 (fix round 1, P1-2): dispatch for frames arriving on an ADOPTED host's
1497
1444
  // connection -- pooled OR dedicated, both now route here rather than through
1498
- // _handlePoolMessage's manager-global pool special cases. Scoped by hostId so a
1499
- // manager that has adopted more than one host at once routes each connection's
1500
- // frames only to its own adapter(s).
1445
+ // _handlePoolMessage's manager-global pool special cases. Scoped by hostId so a manager that has adopted more
1446
+ // than one host at once routes each connection's frames only to its own adapter(s).
1501
1447
  _routeAdoptedFrame(hostId, msg, originConnection) {
1502
1448
  if (!msg || typeof msg !== 'object') return;
1503
1449
  const sessionId = String(msg.sessionId || '');
1450
+ if (msg.type === 'host-threadpool') { this._handleThreadpoolFrame(hostId, msg); return; } // stale-pty-host-evacuation S-7: host-wide (no sessionId) -- an adopted stalled host must reach the ledger
1504
1451
  if (msg.type === 'shutdown-session-ack') { this._shutdownSessionWaiters.get(sessionId)?.(true); return; }
1505
1452
  if (String(msg.type).startsWith('handoff-')) { handoffOrchestrator.routeFrame(this, hostId, msg); return; }
1506
1453
  const adapter = sessionId ? this.hosts.get(sessionId) : null;
@@ -1778,7 +1725,13 @@ class SessionHostManager extends EventEmitter {
1778
1725
  // drops this hostId. Without it the per-hostId misses outlived the host, and
1779
1726
  // since a hostId is `pool-<pid>`, a LATER host reusing that pid inherited the
1780
1727
  // dead one's miss count and could be killed on its first deadline miss.
1781
- if (exitingPoolHostId) this.emit('host-exit', { hostId: exitingPoolHostId, kind: 'pool' });
1728
+ if (exitingPoolHostId) {
1729
+ // WP-3 (session-stops-followups-2026-10-24.html, D-5/D-6): announce only --
1730
+ // the constructor's single 'host-exit' listener owns dropping this hostId's
1731
+ // frame clock (with its ledger entry), so a later host reusing the same
1732
+ // `pool-<pid>` id can never inherit it. No inline delete here.
1733
+ this.emit('host-exit', { hostId: exitingPoolHostId, kind: 'pool' });
1734
+ }
1782
1735
  for (const host of Array.from(this.hosts.values())) {
1783
1736
  if (!host._pooled || host._adopted || host.closed || host._hostId !== exitingPoolHostId && host._hostId) continue; // WP-4: a re-pointed adapter no longer belongs to this exiting pool
1784
1737
  host.closed = true;
@@ -1797,26 +1750,14 @@ class SessionHostManager extends EventEmitter {
1797
1750
 
1798
1751
  _postToPool(message) {
1799
1752
  const child = this._ensurePool();
1800
- if (!child || this._poolClosed) return false;
1753
+ // A1: an absent/closed pool posts nothing -- recorded as a refusal, joined CONSERVATIVELY below.
1754
+ if (!child || this._poolClosed) { notePostRefusal(message); return false; }
1801
1755
  // WP-1 (deliverable 2): once the pool's control flip has succeeded, ALL
1802
1756
  // control + data goes over the socket -- IPC is no longer used to send.
1803
1757
  if (this._poolTransport === 'socket' && this._poolSocketConn && !this._poolSocketConn.destroyed) {
1804
- try {
1805
- _writeFrame(this._poolSocketConn, message);
1806
- return true;
1807
- } catch (err) {
1808
- this._emitHostError(message?.sessionId || '', err);
1809
- return false;
1810
- }
1811
- }
1812
- if (!child.connected) return false;
1813
- try {
1814
- child.send(message);
1815
- return true;
1816
- } catch (err) {
1817
- this._emitHostError(message?.sessionId || '', err);
1818
- return false;
1758
+ return postSelectedSocket(() => this._poolSocketConn, message, (err) => this._emitHostError(message?.sessionId || '', err));
1819
1759
  }
1760
+ return postIpcFrameObserved(child, message, (err) => this._emitHostError(message?.sessionId || '', err)).posted;
1820
1761
  }
1821
1762
  _handlePoolMessage(msg, sourceChild = null) {
1822
1763
  if (sourceChild && sourceChild !== this._poolChild) return;
@@ -1893,7 +1834,9 @@ class SessionHostManager extends EventEmitter {
1893
1834
  return;
1894
1835
  }
1895
1836
  }
1896
- this._emitHeadlessMessage(inner);
1837
+ // WP-4 (R-7): the pool's own arrival stamp for a frame with no routed adapter above --
1838
+ // same helper, so a pooled frame and a dedicated one answer the same question.
1839
+ this._emitHeadlessMessage(_stampMainArrival(inner));
1897
1840
  } else if (msg.type === 'host-diagnostic') { hostDiagnosticFrame(this, sessionId, msg); // no adapter routed yet (successor restore)
1898
1841
  } else if (msg.type === 'host-error') {
1899
1842
  const err = new Error(msg.message || 'session host pool error');
@@ -1934,7 +1877,10 @@ class HeadlessTerminalRouter extends EventEmitter {
1934
1877
  return;
1935
1878
  }
1936
1879
  if (msg?.type === 'worker-error') this._scheduleStatusRefresh();
1937
- this.emit('message', msg);
1880
+ // WP-4 (R-7): the legacy worker transport is a third arrival path into the same 'message'
1881
+ // event the two hosted ones feed -- stamp it here so a frame's `_mainArrivedAt` means the
1882
+ // same thing regardless of which transport delivered it.
1883
+ this.emit('message', _stampMainArrival(msg));
1938
1884
  });
1939
1885
  this.legacyWorker.on('error', (err) => this.emit('error', err));
1940
1886
  this.sessionHostManager.on('headless-message', (msg) => this.emit('message', msg));
@@ -2042,5 +1988,5 @@ module.exports = {
2042
1988
  HeadlessTerminalRouter,
2043
1989
  createSessionHostManager,
2044
1990
  createHeadlessTerminalRouter,
2045
- _internals: { ADOPT_DIAL_TIMEOUT_MS },
1991
+ _internals: { ADOPT_DIAL_TIMEOUT_MS, _stampMainArrival },
2046
1992
  };
@@ -175,35 +175,38 @@ function registerSessionJobs(scheduler, deps) {
175
175
  }
176
176
  return false;
177
177
  }
178
+ // WP-6: the window reports WHY it is quiet -- a short reason string, '' when maintenance may run. Its only
179
+ // caller uses the value for truthiness, so this is a naming change, not a behaviour change; the transcript
180
+ // archive job passes the reason through to its own `deferred` log line.
178
181
  function _isInStartupMaintenanceQuietWindow() {
179
- if (typeof isRestoreInProgress === 'function' && isRestoreInProgress()) return true;
182
+ if (typeof isRestoreInProgress === 'function' && isRestoreInProgress()) return 'restore_in_progress';
180
183
  // Defer while the single serial db-owner worker is degraded under load: piling more
181
184
  // maintenance onto a struggling worker deepens the contention cascade that wedges the
182
185
  // cold-boot storm. The scheduler re-runs the job once the worker recovers.
183
- if (typeof isDbWorkerDegraded === 'function' && isDbWorkerDegraded()) return true;
186
+ if (typeof isDbWorkerDegraded === 'function' && isDbWorkerDegraded()) return 'db_worker_degraded';
184
187
  // Just woke from a process suspension (laptop sleep): the wake backlog (imports/restore/harvest)
185
188
  // fires at once and saturates the serial db-owner worker → main-loop freezes. Hold heavy
186
189
  // maintenance for a short grace window so the burst drains before we pile more on (mirrors the
187
190
  // degraded-worker defer above, but catches the INITIAL wake wave before degradation sets in).
188
- if (typeof recentlyResumedFromSleep === 'function' && recentlyResumedFromSleep()) return true;
191
+ if (typeof recentlyResumedFromSleep === 'function' && recentlyResumedFromSleep()) return 'resumed_from_sleep';
189
192
  // isDbWarming() is currently a retired compatibility gate; keep honoring it
190
193
  // here so any future SQLite-owned warmup can defer maintenance safely. The
191
194
  // fixed startup grace below remains the active maintenance quiet window.
192
- if (typeof isDbWarming === 'function' && isDbWarming()) return true;
193
- if (typeof canAdmitTierB === 'function' && !canAdmitTierB()) return true;
195
+ if (typeof isDbWarming === 'function' && isDbWarming()) return 'db_warming';
196
+ if (typeof canAdmitTierB === 'function' && !canAdmitTierB()) return 'tier_b_refused';
194
197
  if (typeof getServerStartedAt === 'function') {
195
198
  const startedAt = Number(getServerStartedAt());
196
199
  if (Number.isFinite(startedAt) && startedAt > 0 && (Date.now() - startedAt) < STARTUP_MAINTENANCE_GRACE_MS) {
197
- return true;
200
+ return 'startup_grace';
198
201
  }
199
202
  }
200
203
  if (typeof getRestoreCompletedAt === 'function') {
201
204
  const completedAt = Number(getRestoreCompletedAt());
202
205
  if (Number.isFinite(completedAt) && completedAt > 0 && (Date.now() - completedAt) < STARTUP_MAINTENANCE_GRACE_MS) {
203
- return true;
206
+ return 'restore_grace';
204
207
  }
205
208
  }
206
- return false;
209
+ return '';
207
210
  }
208
211
  // Interactive guard: heavy maintenance jobs (the io-pool scans) must not START
209
212
  // their synchronous DB/fs work within INTERACTIVE_DEFER_MS of a user keystroke.
@@ -231,6 +234,11 @@ function registerSessionJobs(scheduler, deps) {
231
234
  _interactiveDeferSince = 0;
232
235
  return true;
233
236
  }
237
+ // WP-6: why `_canRunStartupMaintenance` is holding a heavy job back, in the same words the transcript
238
+ // archive job logs. '' means nothing is holding it back.
239
+ function _maintenanceDeferReason() {
240
+ return _isInStartupMaintenanceQuietWindow() || (_userInteractingNow() ? 'user_typing' : '');
241
+ }
234
242
 
235
243
  let sessionMetadataSyncCursor = 0;
236
244
  let sessionMetadataSyncTick = 0;
@@ -1104,7 +1112,7 @@ function registerSessionJobs(scheduler, deps) {
1104
1112
  });
1105
1113
  }
1106
1114
 
1107
- if (deps.runConversationSummaryBackfill) require('./conversation-summary-backfill').registerJob(scheduler, deps.runConversationSummaryBackfill, _canRunStartupMaintenance); if (deps.transcriptGuard && deps.transcriptArchiveEnv) require('./transcript-guard-job').registerArchiveJob(scheduler, deps.transcriptGuard, deps.transcriptArchiveEnv, _canRunStartupMaintenance); // D-19 transcript archive
1115
+ if (deps.runConversationSummaryBackfill) require('./conversation-summary-backfill').registerJob(scheduler, deps.runConversationSummaryBackfill, _canRunStartupMaintenance); if (deps.transcriptGuard && deps.transcriptArchiveEnv) require('./transcript-guard-job').registerArchiveJob(scheduler, deps.transcriptGuard, deps.transcriptArchiveEnv, _canRunStartupMaintenance, _maintenanceDeferReason); // D-19 transcript archive
1108
1116
  // --- message-backfill: maintain legacy session_messages FTS from canonical rows ---
1109
1117
  if (deps.backfillSessionMessages) {
1110
1118
  scheduler.register({