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
@@ -76,6 +76,7 @@ const { createErrorSink } = require('./error-sink');
76
76
  const { createPendingClientRegistry } = require('./state-sync/pending-client-registry');
77
77
  const { createApprovalTrailingRecheck } = require('./approval-trailing-recheck');
78
78
  const { composeTerminalBootstrapResult, truncateBootstrapAnsi } = require('./terminal-bootstrap');
79
+ const { createInFlightOps } = require('./headless-inflight-ops'); // WP-0 (D-6): per-session in-flight op table
79
80
 
80
81
  let postMessage = () => {};
81
82
  const errorSink = createErrorSink({ name: 'worker:headless-terminal' });
@@ -87,6 +88,8 @@ const errorSink = createErrorSink({ name: 'worker:headless-terminal' });
87
88
  // is a complete no-op, so behaviour is unchanged everywhere else.
88
89
  let _opStampBuffer = null;
89
90
  let _opTable = null;
91
+ // WP-0 (D-6): the per-session truth the single SAB slot pair above can never hold.
92
+ const _inFlightOps = createInFlightOps();
90
93
 
91
94
  function setOpStampBuffer(shared, opTable) {
92
95
  _opStampBuffer = shared ? new BigInt64Array(shared) : null;
@@ -106,18 +109,31 @@ function _sid32(sessionId) {
106
109
  return Number.isFinite(n) ? BigInt(n >>> 0) : 0n;
107
110
  }
108
111
 
112
+ // Returns the packed (sid32<<32)|opCode this call wrote (or null with no buffer)
113
+ // so the op's own finisher can compare-and-clear exactly its own stamp.
114
+ // asIntN(64, ...) is NOT cosmetic: a BigInt64Array wraps on store, so the value
115
+ // read back (and hence what compareExchange must be given) is the signed 64-bit
116
+ // one -- the unwrapped positive BigInt would never match and the clear would
117
+ // silently become a no-op.
109
118
  function _stampOp(sessionId, opType) {
110
- if (!_opStampBuffer) return;
119
+ if (!_opStampBuffer) return null;
111
120
  const sid32 = _sid32(sessionId);
112
121
  const opCode = BigInt(_opCode(opType));
122
+ const packed = BigInt.asIntN(64, (sid32 << 32n) | opCode);
113
123
  Atomics.store(_opStampBuffer, 2, BigInt(Date.now()));
114
- Atomics.store(_opStampBuffer, 3, (sid32 << 32n) | opCode);
115
- }
116
-
117
- function _clearOp() {
118
- if (!_opStampBuffer) return;
119
- Atomics.store(_opStampBuffer, 2, 0n);
120
- Atomics.store(_opStampBuffer, 3, 0n);
124
+ Atomics.store(_opStampBuffer, 3, packed);
125
+ return packed;
126
+ }
127
+
128
+ // WP-0 (D-6, S-12): compare-and-clear. Slots [2]/[3] are ONE shared pair per
129
+ // process, so an unconditional zero would erase a DIFFERENT session's op that
130
+ // stamped after this one. Clear [2] only when slot [3] still holds this op's
131
+ // own packed value.
132
+ function _clearOp(packed) {
133
+ if (!_opStampBuffer || packed == null) return;
134
+ if (Atomics.compareExchange(_opStampBuffer, 3, packed, 0n) === packed) {
135
+ Atomics.store(_opStampBuffer, 2, 0n);
136
+ }
121
137
  }
122
138
 
123
139
  function postTransport(message) {
@@ -730,20 +746,21 @@ function _stateSyncOnMirrorWrite(sessionId, bytes, opts) {
730
746
  _stateSyncEmitter.setFlooding(sessionId, _stateSyncIsFlooding(flood, _stateSyncClock.now(), {}));
731
747
  const clearScrollback = _stateSyncPendingClearScrollback.delete(sessionId);
732
748
  const currentEntry = terminals.get(sessionId);
733
- _noteWorkerTypingStagePending(sessionId, currentEntry, 'emit-offered');
749
+ const offeredEchoSeq = Number(currentEntry?.state?.echoedThroughInputSeq || 0);
750
+ _noteWorkerTypingStagePending(sessionId, currentEntry, 'emit-offered', { offeredEchoSeq });
734
751
  const sent = _stateSyncEmitter.onMirrorUpdated(sessionId, {
735
752
  bypassRate: true,
736
753
  clearScrollback,
737
- echoedThroughSeq: Number(currentEntry?.state?.echoedThroughInputSeq || 0),
754
+ echoedThroughSeq: offeredEchoSeq,
738
755
  synchronizedOutputOpen: _stateSyncSynchronizedOutputOpen(sessionId),
739
756
  });
740
757
  if (sent && currentEntry?.state) {
741
758
  currentEntry.state.lastStateSyncEmittedInputSeq = Number(
742
759
  currentEntry.state.echoedThroughInputSeq || 0
743
760
  );
744
- // A frame actually shipped for these interactions -- clear the pending set so a LATER
745
- // refusal (a different write's gate) never double-attributes to an already-delivered one.
746
- currentEntry.state.pendingTypingInteractionIds = [];
761
+ // A frame actually shipped for these interactions -- clear only the ids whose echo it
762
+ // covers (WP-4 R-7); a LATER refusal (a different write's gate) never double-attributes.
763
+ currentEntry.state.pendingTypingInteractionIds = _typingWorkerStages.noteFramePostedForEchoed(postTransport, sessionId, currentEntry, offeredEchoSeq);
747
764
  }
748
765
  }
749
766
  };
@@ -1265,12 +1282,14 @@ const opQueues = new Map(); // sessionId -> Promise chain for feed/serialize/des
1265
1282
 
1266
1283
  function enqueueSessionOp(sessionId, fn, opType) {
1267
1284
  const prev = opQueues.get(sessionId) || Promise.resolve();
1285
+ _inFlightOps.noteQueued(sessionId, 1); // WP-0: depth behind the running op
1268
1286
  let next;
1269
1287
  next = prev
1270
1288
  .catch(() => {})
1271
1289
  .then(() => {
1272
- _stampOp(sessionId, opType);
1273
- return Promise.resolve().then(fn).finally(_clearOp);
1290
+ const seq = _inFlightOps.begin(sessionId, opType);
1291
+ const packed = _stampOp(sessionId, opType);
1292
+ return Promise.resolve().then(fn).finally(() => { _clearOp(packed); _inFlightOps.end(sessionId, seq); });
1274
1293
  })
1275
1294
  .catch((err) => {
1276
1295
  postTransport({
@@ -1320,6 +1339,18 @@ async function handleMessage(msg) {
1320
1339
  if (feedEntry.state.typingIdsAwaitingFeed && feedEntry.state.pendingTypingInteractionIds.length) {
1321
1340
  feedEntry.state.typingIdsAwaitingFeed = false;
1322
1341
  _noteWorkerTypingStage(msg.sessionId, feedEntry.state.pendingTypingInteractionIds, 'worker-received');
1342
+ // WP-4 (clock-free-tests-followups-r4-2026-10-24.html §5 R-9, §6.5): the SAME ids, in
1343
+ // the same call pattern, gain a second stamp at the instant the host's mirror-feed
1344
+ // coalescer took the batch in (`hostReceivedAt`, carried from the deliver callback) --
1345
+ // 'worker-received' above is the parse leg, this one is the host's receipt leg, and the
1346
+ // delta between them is what R-9 exists to measure. Only a finite host time stamps
1347
+ // anything: a sender that predates the field leaves the stage absent, never at 0.
1348
+ if (Number.isFinite(msg.hostReceivedAt)) {
1349
+ _noteWorkerTypingStage(
1350
+ msg.sessionId, feedEntry.state.pendingTypingInteractionIds, 'host-pty-data',
1351
+ { at: msg.hostReceivedAt },
1352
+ );
1353
+ }
1323
1354
  }
1324
1355
  }
1325
1356
  const byteCount = Buffer.byteLength(String(msg.data || ''));
@@ -1345,6 +1376,8 @@ async function handleMessage(msg) {
1345
1376
  // WP-0b (§6): the xterm write callback for THIS feed -- exactly where the brief says
1346
1377
  // `mirror-written` belongs.
1347
1378
  _noteWorkerTypingStagePending(msg.sessionId, entry, 'mirror-written');
1379
+ // WP-3 (D-C2): per-key shadow echo matchers -- see lib/typing-worker-stages.js.
1380
+ _typingWorkerStages.matchEchoPredictions(postTransport, msg.sessionId, entry, msg.data);
1348
1381
  entry.state.liveOutputSeq += 1;
1349
1382
  entry.state.liveBytesSinceSeed += byteCount;
1350
1383
  if (entry.state.pendingInputSeq > entry.state.echoedThroughInputSeq) {
@@ -1470,12 +1503,9 @@ async function handleMessage(msg) {
1470
1503
  // every keystroke (HeadlessTerminalRouter.postMessage, lib/session-host-manager.js
1471
1504
  // :1815-1834, forwards by sessionId to the pool host when one owns the session, else the
1472
1505
  // legacy worker thread) -- carry the pending typing-interaction ids here, not on 'feed'.
1473
- if (Array.isArray(msg.interactionIds) && msg.interactionIds.length) {
1474
- entry.state.pendingTypingInteractionIds = _typingWorkerStages.mergePendingInteractionIds(
1475
- entry.state.pendingTypingInteractionIds, msg.interactionIds,
1476
- );
1477
- entry.state.typingIdsAwaitingFeed = true;
1478
- }
1506
+ // WP-3 (D-C2): the pending-id merge above moved into armEchoPrediction, which also arms
1507
+ // this keystroke's shadow echo prediction when the message carries one.
1508
+ _typingWorkerStages.armEchoPrediction(entry, msg);
1479
1509
  break;
1480
1510
  }
1481
1511
 
@@ -2368,8 +2398,12 @@ async function handleMessage(msg) {
2368
2398
  function dispatchHeadlessMessage(msg) {
2369
2399
  const sessionId = msg && msg.sessionId;
2370
2400
  if (sessionId && msg.type === 'getViewportText') {
2371
- _stampOp(sessionId, msg.type);
2372
- return handleMessage(msg).finally(_clearOp);
2401
+ // WP-0: SAB stamp only. This op is outside the per-session chain and cannot wedge it,
2402
+ // and the in-flight table holds ONE row per session -- registering here would overwrite
2403
+ // (and its end() would then delete) a wedged chain op for the same session, hiding the
2404
+ // very wedge WP-0 exists to name.
2405
+ const packed = _stampOp(sessionId, msg.type);
2406
+ return handleMessage(msg).finally(() => _clearOp(packed));
2373
2407
  }
2374
2408
  if (!sessionId) {
2375
2409
  return handleMessage(msg).catch((err) => {
@@ -2392,6 +2426,9 @@ function bindHeadlessTerminalService(options = {}) {
2392
2426
  return {
2393
2427
  handleMessage: dispatchHeadlessMessage,
2394
2428
  hasTerminal,
2429
+ // WP-0 (D-6): report-only views of the per-session in-flight op table.
2430
+ getInFlightOps: () => _inFlightOps.list(),
2431
+ oldestInFlightOps: (n) => _inFlightOps.oldest(n),
2395
2432
  getStats() {
2396
2433
  const pendingStats = pendingStateSyncClients.snapshot();
2397
2434
  const warningStats = _stalledSessionWarnings.snapshot();
@@ -7,17 +7,23 @@ const transcriptSource = require('./transcript-source');
7
7
  const sessionHistory = require('./session-history');
8
8
  const codexPaths = require('./codex-paths');
9
9
  const { readNativeHistoryPrefix, findCanonicalRolloutsByFilenameId } = require('./codex-native-history-prefix');
10
+ // S-4: one definition of the portable history kinds, in the planner. This module only reads it.
11
+ const { PORTABLE_HISTORY_KINDS } = require('./harness-history-transition');
10
12
 
11
13
  const DEFAULT_SCAN_BYTES = 64 * 1024 * 1024;
12
14
  const MAX_SCAN_BYTES = 64 * 1024 * 1024;
13
15
  const MAX_RECORDS = 200_000;
14
16
  const THREAD_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
17
+ // D-5/D-8 (docs/codex-lead-routed-switch-history-handoff-2026-10-21.html §6.1): the plaintext
18
+ // handoff a source GPT model writes before the switch, exactly as lib/native-history-control.js
19
+ // injects it. Only the marker sha is parsed here; the summary text itself never leaves the digest.
20
+ const HANDOFF_MARKER = /^<ctm_handoff compaction_sha256="([0-9a-f]{64})"/;
21
+ const COMPACTION_ITEM_KEYS = Object.freeze(['replacement_history', 'replacementHistory', 'summary']);
15
22
  const TOOL_PAIRS = Object.freeze({
16
23
  function_call: 'function_call_output', custom_tool_call: 'custom_tool_call_output',
17
24
  computer_call: 'computer_call_output', local_shell_call: 'local_shell_call_output',
18
25
  });
19
26
  const TOOL_OUTPUTS = new Set(Object.values(TOOL_PAIRS));
20
- const PORTABLE_HISTORY_KINDS = new Set(['text', 'tool-pair', 'native-world-state', 'native-lifecycle']);
21
27
  const SAFE_EVENT_TYPES = new Set([
22
28
  'task_started', 'task_complete', 'turn_aborted', 'token_count', 'user_message',
23
29
  'agent_message_delta', 'agent_reasoning_delta', 'agent_reasoning_section_break', 'item_completed',
@@ -70,7 +76,12 @@ function summarizeCodexHistoryRecords(records, { threadId, generation, lineageTh
70
76
  active.kinds.add('text');
71
77
  collectHashes(record, active.hashes);
72
78
  if (containsFlag(payload, /^opaque$/i)) active.opaque = true;
73
- if (containsFlag(payload, /^(encrypted|ciphertext|encrypted_content)$/i)) active.encrypted = true;
79
+ // S-9/D-5: a `compaction` item inside the replacement history is the ONLY ciphertext that a
80
+ // source-written plaintext handoff can cover, so it is tracked by digest instead of being
81
+ // collapsed into the blanket `encrypted` flag. The record-level flag is evaluated on a copy
82
+ // with those items removed, so every OTHER ciphertext still marks the window non-portable.
83
+ const withoutSealed = withoutSealedCompactionItems(payload);
84
+ if (containsFlag(withoutSealed, /^(encrypted|ciphertext|encrypted_content)$/i)) active.encrypted = true;
74
85
  const replacement = payload.replacement_history ?? payload.replacementHistory ?? payload.summary;
75
86
  if (replacement !== undefined) {
76
87
  const status = classifyReplacement(replacement, active);
@@ -189,12 +200,25 @@ function summarizeCodexHistoryRecords(records, { threadId, generation, lineageTh
189
200
  }
190
201
  if (pairKind === 'output') active.estimateValid = false;
191
202
  } else if (itemType === 'message' || itemType === 'agent_message' || itemType === 'reasoning') {
192
- if (itemType !== 'reasoning' && !portableTextOnly(payload)) return unsupported('unsupported_native_history_item_content');
193
- active.kinds.add('text');
203
+ // D-2: a `message` whose only non-text content parts are input_image parts is native media,
204
+ // not a refusal -- Codex strips an image the target cannot accept
205
+ // (core/src/context_manager/normalize.rs:346 @ rust-v0.163.0) and CTM declares
206
+ // input_modalities:['text'] for routed models (lib/harness-model-profile.js:85). Any other
207
+ // modality (input_audio, file, ...) and an agent_message carrying media still refuse.
208
+ if (itemType !== 'reasoning' && !portableTextOnly(payload)) {
209
+ if (itemType !== 'message' || !imageOnlyNonText(payload)) return unsupported('unsupported_native_history_item_content');
210
+ active.kinds.add('native-media');
211
+ } else {
212
+ active.kinds.add('text');
213
+ }
194
214
  if (itemType === 'agent_message') active.estimateValid = false;
195
215
  if (payload.role === 'user' || payload.role === 'developer' || payload.role === 'system') active.estimateValid = false;
196
216
  }
197
217
  if (itemType === 'reasoning' && ownsTopLevelFlag(payload, 'encrypted_content')) active.droppableReasoningCount += 1;
218
+ // D-8: the covering proof for a sealed compaction is a developer message the source model wrote
219
+ // after it. Only its digests are recorded; the summary text stays out of this object.
220
+ const marker = handoffMarkerOf(payload);
221
+ if (marker) active.handoffMarkers.push(marker);
198
222
  // D-1: a reasoning item's own ciphertext is droppable (the issuer omits incompatible reasoning
199
223
  // itself); nested ciphertext in its content/summary parts still marks the window non-portable.
200
224
  const ciphertextScope = itemType === 'reasoning' ? withoutTopLevelFlag(payload, 'encrypted_content') : payload;
@@ -261,6 +285,11 @@ function summarizeCodexHistoryRecords(records, { threadId, generation, lineageTh
261
285
  const nativeCurrentPolicyStatus = !active.currentThreadSettings ? 'not-present'
262
286
  : !completedContext?.nativePolicyFingerprint || !active.currentThreadSettings.policyFingerprint ? 'unknown'
263
287
  : completedContext.nativePolicyFingerprint === active.currentThreadSettings.policyFingerprint ? 'match' : 'mismatch';
288
+ // D-8: coverage is proven from the readback itself -- every sealed compaction digest must have a
289
+ // marker at or after it, and the marker covering the first one identifies the exact text.
290
+ const encryptedCompactions = [...new Set(active.encryptedCompactions)];
291
+ const coveredShas = new Set(active.handoffMarkers.map((marker) => marker.sha));
292
+ const coveringMarker = active.handoffMarkers.find((marker) => marker.sha === encryptedCompactions[0]) || null;
264
293
  return {
265
294
  complete: true, sourceThreadId: threadId, generation,
266
295
  compacted: sawCompaction,
@@ -270,6 +299,9 @@ function summarizeCodexHistoryRecords(records, { threadId, generation, lineageTh
270
299
  historyKinds: [...active.kinds].sort().length ? [...active.kinds].sort() : [sawCompaction ? 'text' : 'unknown'],
271
300
  toolPairsValid: active.calls.size === active.outputs.size && [...active.calls].every((id) => active.outputs.has(id)),
272
301
  opaque: active.opaque, encrypted: active.encrypted,
302
+ encryptedCompactions,
303
+ handoffCovers: encryptedCompactions.every((sha) => coveredShas.has(sha)),
304
+ handoffTextSha256: coveringMarker ? coveringMarker.textSha256 : null,
273
305
  droppableReasoningCount: active.droppableReasoningCount, compactionHashes: hashSet,
274
306
  previousSettings, nativePolicyControl: completedContext?.nativePolicyControl || null,
275
307
  nativeCurrentPolicyStatus, currentThreadSettingsModel: active.currentThreadSettings?.model || null,
@@ -278,6 +310,59 @@ function summarizeCodexHistoryRecords(records, { threadId, generation, lineageTh
278
310
  };
279
311
  }
280
312
 
313
+ // D-5: the digest of a `compaction` replacement item's ciphertext, or '' when the item is not one.
314
+ // `type` is read the same way classifyReplacement reads it, so the two can never disagree.
315
+ function sealedCompactionSha(item) {
316
+ if (!isRecord(item) || nonempty(item.type || item.kind) !== 'compaction') return '';
317
+ const content = item.encrypted_content;
318
+ return typeof content === 'string' && content.length > 0 ? digest(content) : '';
319
+ }
320
+
321
+ // A copy of a `compacted` payload without the ciphertext this scanner already accounts for, so the
322
+ // record-level probe sees every OTHER encrypted value:
323
+ // - a sealed `compaction` replacement item, which a source-written handoff can cover (D-5);
324
+ // - a `reasoning` item's own top-level encrypted_content, droppable exactly as for a top-level
325
+ // reasoning response_item (D-1). Codex >= 0.163 checkpoints the auto-reviewer's transcript as
326
+ // `guardian_history` (core/src/session/guardian_checkpoint.rs @ rust-v0.163.0), and its
327
+ // reasoning items carry their own ciphertext; nested ciphertext inside them still counts.
328
+ const CHECKPOINT_ITEM_KEYS = Object.freeze([...COMPACTION_ITEM_KEYS, 'guardian_history']);
329
+
330
+ function withoutSealedCompactionItems(payload) {
331
+ if (typeof payload !== 'object' || payload === null) return payload;
332
+ let copy = null;
333
+ for (const key of CHECKPOINT_ITEM_KEYS) {
334
+ const value = payload[key];
335
+ if (!Array.isArray(value)) continue;
336
+ let changed = false;
337
+ const kept = [];
338
+ for (const item of value) {
339
+ if (sealedCompactionSha(item)) { changed = true; continue; }
340
+ if (isRecord(item) && item.type === 'reasoning' && ownsTopLevelFlag(item, 'encrypted_content')) {
341
+ changed = true;
342
+ kept.push(withoutTopLevelFlag(item, 'encrypted_content'));
343
+ continue;
344
+ }
345
+ kept.push(item);
346
+ }
347
+ if (!changed) continue;
348
+ if (!copy) copy = { ...payload };
349
+ copy[key] = kept;
350
+ }
351
+ return copy || payload;
352
+ }
353
+
354
+ // The covering proof: a developer message whose first content part is input_text starting at the
355
+ // handoff marker. Returns digests only -- never the summary text.
356
+ function handoffMarkerOf(payload) {
357
+ if (!isRecord(payload) || payload.type !== 'message' || payload.role !== 'developer') return null;
358
+ const content = payload.content;
359
+ if (!Array.isArray(content) || content.length === 0) return null;
360
+ const first = content[0];
361
+ if (!isRecord(first) || first.type !== 'input_text' || typeof first.text !== 'string') return null;
362
+ const match = HANDOFF_MARKER.exec(first.text);
363
+ return match ? { sha: match[1], textSha256: digest(first.text) } : null;
364
+ }
365
+
281
366
  function readCodexHistoryTransitionSnapshot({ db, ctmSessionId, sourceThreadId, processGeneration, signal, assertLive,
282
367
  maxBytes = DEFAULT_SCAN_BYTES, storageRootReceipt } = {}) {
283
368
  if (cancelled(signal)) return unsupported('transition_cancelled');
@@ -311,7 +396,7 @@ function readCodexHistoryTransitionSnapshot({ db, ctmSessionId, sourceThreadId,
311
396
 
312
397
  function verifyCodexTransitionFork({ db, transitionId, expectedRevision, sessionId, sourceThreadId,
313
398
  sourceGeneration, targetGeneration, sourceCheckpointId, sourceContentDigest, targetThreadId,
314
- signal, assertLive, storageRootReceipt } = {}) {
399
+ expectedHandoff, signal, assertLive, storageRootReceipt } = {}) {
315
400
  if (cancelled(signal)) return unsupported('transition_cancelled');
316
401
  assertLive?.();
317
402
  if (!db || !nonempty(transitionId) || !nonempty(sessionId) || !THREAD_ID.test(String(sourceThreadId || ''))
@@ -355,6 +440,18 @@ function verifyCodexTransitionFork({ db, transitionId, expectedRevision, session
355
440
  if (!target.complete || target.sourceThreadId !== targetThreadId || target.checkpoint?.id !== sourceCheckpointId) {
356
441
  return unsupported('fork_target_checkpoint_mismatch');
357
442
  }
443
+ // S-10/D-8: a sealed compaction in the FORKED history is accepted only when the readback itself
444
+ // proves the exact plaintext handoff that covers it. Trusting the inject call would accept a lost
445
+ // or partial inject, so the coverage digests are compared against what the controller wrote.
446
+ const sealedTarget = Array.isArray(target.encryptedCompactions) ? target.encryptedCompactions : [];
447
+ if (sealedTarget.length && (target.handoffCovers !== true
448
+ || !isRecord(expectedHandoff)
449
+ || !/^[a-f0-9]{64}$/.test(String(expectedHandoff.compactionSha256 || ''))
450
+ || !/^[a-f0-9]{64}$/.test(String(expectedHandoff.textSha256 || ''))
451
+ || sealedTarget[0] !== expectedHandoff.compactionSha256
452
+ || target.handoffTextSha256 !== expectedHandoff.textSha256)) {
453
+ return unsupported('fork_target_handoff_missing');
454
+ }
358
455
  if (target.opaque || target.encrypted || target.toolPairsValid !== true
359
456
  || !Array.isArray(target.historyKinds) || target.historyKinds.length === 0
360
457
  || target.historyKinds.some((kind) => !PORTABLE_HISTORY_KINDS.has(kind))) return unsupported('fork_target_history_unsafe');
@@ -416,12 +513,20 @@ function classifyReplacement(value, active) {
416
513
  else if (type === 'compaction') {
417
514
  // Codex 0.162.1 keeps the pre-window digest in a `compaction` item. It is a known shape,
418
515
  // but its ciphertext is the only record of earlier context, so it is never droppable.
516
+ // D-5: the SOURCE model can write a plaintext handoff covering exactly this ciphertext, so
517
+ // the item is tracked by digest; the coverage decision belongs to the planner.
419
518
  active.kinds.add('text');
420
- if (nonempty(item.encrypted_content)) active.encrypted = true;
519
+ const sealed = sealedCompactionSha(item);
520
+ if (sealed) {
521
+ if (!active.encryptedCompactions.includes(sealed)) active.encryptedCompactions.push(sealed);
522
+ continue;
523
+ }
421
524
  }
422
525
  else if (type === 'message') {
423
- if (!portableTextOnly(item)) return 'compaction_replacement_unknown';
424
- active.kinds.add('text');
526
+ // D-2 applies inside a compaction replacement too: an image-only message is native media.
527
+ if (portableTextOnly(item)) active.kinds.add('text');
528
+ else if (imageOnlyNonText(item)) active.kinds.add('native-media');
529
+ else return 'compaction_replacement_unknown';
425
530
  }
426
531
  else if (type === 'function_call' || type === 'custom_tool_call' || type === 'computer_call' || type === 'local_shell_call') {
427
532
  const id = nonempty(item.call_id || item.id);
@@ -544,17 +649,30 @@ function inspectCompletedItemEvent(payload, lineage) {
544
649
  return { ok: inspection.ok, opaque: inspection.opaque, encrypted: inspection.encrypted };
545
650
  }
546
651
 
652
+ // D-1: exactly the field set of Codex ThreadSettingsSnapshot
653
+ // (codex-rs/protocol/src/protocol.rs:2335-2362 @ rust-v0.163.0). The fields below are the ones the
654
+ // struct requires; the rest carry a serde default (runtime_workspace_roots, disabled_plugin_ids,
655
+ // active_permission_profile) or are Option. The payload's own thread_id is Option<ThreadId>
656
+ // (protocol.rs:2329), so it is optional but must be in the lineage when present.
657
+ const THREAD_SETTINGS_REQUIRED = Object.freeze(['model', 'model_provider_id', 'approval_policy',
658
+ 'approvals_reviewer', 'permission_profile', 'cwd', 'collaboration_mode']);
659
+ const THREAD_SETTINGS_OPTIONAL = Object.freeze(['service_tier', 'active_permission_profile',
660
+ 'runtime_workspace_roots', 'reasoning_effort', 'reasoning_summary', 'personality', 'disabled_plugin_ids']);
661
+ const THREAD_SETTINGS_KEYS = new Set([...THREAD_SETTINGS_REQUIRED, ...THREAD_SETTINGS_OPTIONAL]);
662
+ const THREAD_SETTINGS_PAYLOAD_KEYS = new Set(['type', 'thread_id', 'thread_settings']);
663
+
547
664
  function inspectThreadSettingsApplied(payload, lineage) {
548
665
  const settings = payload.thread_settings;
549
- const keys = new Set(['model', 'model_provider_id', 'approval_policy', 'approvals_reviewer',
550
- 'permission_profile', 'cwd', 'runtime_workspace_roots', 'collaboration_mode', 'disabled_plugin_ids']);
551
666
  const invalid = () => ({ ok: false, opaque: false, encrypted: false });
552
- if (!hasOnlyKeys(payload, new Set(['type', 'thread_id', 'thread_settings']))
667
+ if (!hasOnlyKeys(payload, THREAD_SETTINGS_PAYLOAD_KEYS)
553
668
  || payload.type !== 'thread_settings_applied'
554
- || !lineage.has(String(payload.thread_id || '').toLowerCase())
555
- || !isRecord(settings) || Object.keys(settings).some((key) => !keys.has(key))) return invalid();
669
+ || (payload.thread_id !== undefined && payload.thread_id !== null
670
+ && !lineage.has(String(payload.thread_id).toLowerCase()))
671
+ || !isRecord(settings) || Object.keys(settings).some((key) => !THREAD_SETTINGS_KEYS.has(key))) return invalid();
556
672
  const boundedString = (value, max, pattern = null) => typeof value === 'string' && value.length > 0
557
673
  && value.length <= max && (!pattern || pattern.test(value));
674
+ const optionalBoundedString = (value, max, pattern) => value === undefined || value === null
675
+ || boundedString(value, max, pattern);
558
676
  const collaboration = settings.collaboration_mode;
559
677
  const collaborationSettings = collaboration?.settings;
560
678
  const permissionKeys = settings.permission_profile && isRecord(settings.permission_profile)
@@ -566,14 +684,25 @@ function inspectThreadSettingsApplied(payload, lineage) {
566
684
  : profileType === 'external'
567
685
  ? permissionKeys.length === 2 && permissionKeys.includes('network') && ['enabled', 'restricted'].includes(settings.permission_profile.network)
568
686
  : profileType === 'disabled' && permissionKeys.length === 1;
687
+ // A serde-defaulted Vec is absent in real records with an empty list; the two are the same value.
688
+ const disabledPluginIds = settings.disabled_plugin_ids ?? [];
689
+ const runtimeWorkspaceRoots = settings.runtime_workspace_roots;
690
+ const activePermissionProfile = settings.active_permission_profile;
569
691
  if (!boundedString(settings.model, 256, /^[A-Za-z0-9@][A-Za-z0-9@._:/+-]*$/)
570
692
  || !boundedString(settings.model_provider_id, 128, /^[A-Za-z0-9][A-Za-z0-9._-]*$/)
571
693
  || !['never', 'on-request', 'on-failure', 'untrusted'].includes(settings.approval_policy)
572
694
  || !['user', 'auto_review', 'guardian_subagent'].includes(settings.approvals_reviewer)
573
695
  || !validPermissionProfile
574
696
  || !boundedString(settings.cwd, 4096) || !path.isAbsolute(settings.cwd)
575
- || !Array.isArray(settings.runtime_workspace_roots) || settings.runtime_workspace_roots.length > 256
576
- || settings.runtime_workspace_roots.some((value) => !boundedString(value, 4096) || !path.isAbsolute(value))
697
+ || !optionalBoundedString(settings.service_tier, 64, /^[a-z0-9_-]{1,64}$/)
698
+ || !optionalBoundedString(settings.reasoning_effort, 32, /^[a-z]{1,32}$/)
699
+ || !optionalBoundedString(settings.reasoning_summary, 32, /^[a-z]{1,32}$/)
700
+ || !optionalBoundedString(settings.personality, 64, /^[a-z0-9_-]{1,64}$/)
701
+ || (activePermissionProfile !== undefined && activePermissionProfile !== null
702
+ && !isRecord(activePermissionProfile))
703
+ || (runtimeWorkspaceRoots !== undefined && runtimeWorkspaceRoots !== null
704
+ && (!Array.isArray(runtimeWorkspaceRoots) || runtimeWorkspaceRoots.length > 256
705
+ || runtimeWorkspaceRoots.some((value) => !boundedString(value, 4096) || !path.isAbsolute(value))))
577
706
  || !isRecord(collaboration) || Object.keys(collaboration).some((key) => !['mode', 'settings'].includes(key))
578
707
  || !['default', 'plan'].includes(collaboration.mode)
579
708
  || !isRecord(collaborationSettings)
@@ -583,25 +712,30 @@ function inspectThreadSettingsApplied(payload, lineage) {
583
712
  || boundedString(collaborationSettings.reasoning_effort, 128, /^[A-Za-z0-9._-]+$/))
584
713
  || !(collaborationSettings.developer_instructions === undefined || collaborationSettings.developer_instructions === null
585
714
  || boundedString(collaborationSettings.developer_instructions, 64 * 1024))
586
- || !Array.isArray(settings.disabled_plugin_ids) || settings.disabled_plugin_ids.length > 256
587
- || settings.disabled_plugin_ids.some((value) => !boundedString(value, 256, /^[A-Za-z0-9][A-Za-z0-9._:-]*$/))) return invalid();
715
+ || !Array.isArray(disabledPluginIds) || disabledPluginIds.length > 256
716
+ || disabledPluginIds.some((value) => !boundedString(value, 256, /^[A-Za-z0-9][A-Za-z0-9._:-]*$/))) return invalid();
588
717
  const permission = inspectNativeState(settings.permission_profile);
589
718
  const collaborationInspection = inspectNativeState(collaboration);
590
- if (!permission.ok || !collaborationInspection.ok) return invalid();
719
+ const activeInspection = activePermissionProfile === undefined || activePermissionProfile === null
720
+ ? { ok: true, opaque: false, encrypted: false } : inspectNativeState(activePermissionProfile);
721
+ if (!permission.ok || !collaborationInspection.ok || !activeInspection.ok) return invalid();
591
722
  return { ok: true, model: settings.model, policyFingerprint: nativePolicyFingerprint(settings),
592
- opaque: permission.opaque || collaborationInspection.opaque,
593
- encrypted: permission.encrypted || collaborationInspection.encrypted };
723
+ opaque: permission.opaque || collaborationInspection.opaque || activeInspection.opaque,
724
+ encrypted: permission.encrypted || collaborationInspection.encrypted || activeInspection.encrypted };
594
725
  }
595
726
 
596
727
  function nativePolicyFingerprint(value) {
728
+ // An absent (serde-default) disabled_plugin_ids is the same policy as an explicit empty list.
729
+ const disabledPluginIds = value?.disabled_plugin_ids === undefined || value?.disabled_plugin_ids === null
730
+ ? [] : (Array.isArray(value.disabled_plugin_ids) ? value.disabled_plugin_ids : null);
597
731
  if (!isRecord(value) || !isRecord(value.permission_profile) || !nonempty(value.cwd)
598
732
  || !nonempty(value.approval_policy) || !nonempty(value.approvals_reviewer)
599
- || !isRecord(value.collaboration_mode) || !Array.isArray(value.disabled_plugin_ids)) return null;
733
+ || !isRecord(value.collaboration_mode) || disabledPluginIds === null) return null;
600
734
  return digest(JSON.stringify({ permissionProfile: stableValue(value.permission_profile), cwd: value.cwd,
601
735
  approvalPolicy: value.approval_policy, approvalsReviewer: value.approvals_reviewer,
602
736
  collaborationMode: { mode: value.collaboration_mode.mode,
603
737
  developerInstructions: value.collaboration_mode.settings?.developer_instructions ?? null },
604
- disabledPluginIds: [...value.disabled_plugin_ids].sort() }));
738
+ disabledPluginIds: [...disabledPluginIds].sort() }));
605
739
  }
606
740
 
607
741
  function stableValue(value) {
@@ -678,13 +812,14 @@ function optionalSafeInteger(value) { return value === undefined || value === nu
678
812
 
679
813
  function freshActive() {
680
814
  return { kinds: new Set(), calls: new Set(), outputs: new Set(), hashes: [], opaque: false, encrypted: false,
681
- droppableReasoningCount: 0,
815
+ droppableReasoningCount: 0, encryptedCompactions: [], handoffMarkers: [],
682
816
  latestContext: null, lastCompletedContext: null, currentThreadSettings: null, compactTurnId: '', estimatedTokens: null, estimateValid: false, programVersion: '', modelProvider: '' };
683
817
  }
684
818
 
685
819
  function resetActive(active) {
686
820
  active.kinds.clear(); active.calls.clear(); active.outputs.clear(); active.hashes.length = 0;
687
821
  active.opaque = false; active.encrypted = false; active.droppableReasoningCount = 0;
822
+ active.encryptedCompactions.length = 0; active.handoffMarkers.length = 0;
688
823
  active.latestContext = null; active.lastCompletedContext = null;
689
824
  active.currentThreadSettings = null;
690
825
  active.compactTurnId = ''; active.estimatedTokens = null; active.estimateValid = false;
@@ -750,6 +885,27 @@ function portableTextOnly(value) {
750
885
  return visit(value);
751
886
  }
752
887
 
888
+ // D-2: a `message` item whose content carries at least one Codex InputImage part and no other
889
+ // non-text modality. A part is an image part only when its key set is exactly type/image_url/detail;
890
+ // anything else (input_audio, file, nested media, extra keys) falls back to portableTextOnly and
891
+ // therefore refuses, exactly as before.
892
+ function imageOnlyNonText(value) {
893
+ if (!isRecord(value) || !Array.isArray(value.content) || value.content.length === 0) return false;
894
+ let images = 0;
895
+ for (const part of value.content) {
896
+ if (isInputImagePart(part)) { images += 1; continue; }
897
+ if (!portableTextOnly(part)) return false;
898
+ }
899
+ return images > 0;
900
+ }
901
+
902
+ function isInputImagePart(part) {
903
+ return isRecord(part) && part.type === 'input_image'
904
+ && typeof part.image_url === 'string' && part.image_url.length > 0
905
+ && Object.keys(part).every((key) => ['type', 'image_url', 'detail'].includes(key))
906
+ && (part.detail === undefined || part.detail === null || typeof part.detail === 'string');
907
+ }
908
+
753
909
  function tableExists(db, name) {
754
910
  try { return !!db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=?").get(name); } catch { return false; }
755
911
  }