create-walle 0.10.1 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. package/README.md +4 -0
  2. package/lib/owner-write-safety.js +135 -0
  3. package/lib/public-runtime.js +13 -5
  4. package/lib/release-schema.json +3 -2
  5. package/lib/runtime-closure-policy.json +6 -4
  6. package/package.json +1 -1
  7. package/template/bin/deps-doctor.js +1 -1
  8. package/template/bin/node-bin.sh +11 -0
  9. package/template/coding-task-manager/api-prompts.js +10 -22
  10. package/template/coding-task-manager/bin/claude-hook.js +7 -3
  11. package/template/coding-task-manager/bin/claude-table-parity.js +307 -0
  12. package/template/coding-task-manager/bin/conversation-storage-auto-migration-lib.js +16 -1
  13. package/template/coding-task-manager/bin/conversation-storage-generation-evidence.js +9 -2
  14. package/template/coding-task-manager/bin/conversation-storage-service-entry-lib.js +16 -1
  15. package/template/coding-task-manager/bin/conversation-storage-walle-owner.js +247 -0
  16. package/template/coding-task-manager/bin/ctm-machine-pair.js +160 -0
  17. package/template/coding-task-manager/bin/lead-wait-census.js +1348 -0
  18. package/template/coding-task-manager/bin/repair-foreign-clone.js +564 -0
  19. package/template/coding-task-manager/bin/repair-resume-echo-titles.js +248 -0
  20. package/template/coding-task-manager/bin/write-lock-bench.js +111 -0
  21. package/template/coding-task-manager/db.js +705 -286
  22. package/template/coding-task-manager/git-utils.js +44 -0
  23. package/template/coding-task-manager/lib/adopted-host-redial.js +4 -0
  24. package/template/coding-task-manager/lib/agent-presets.js +15 -0
  25. package/template/coding-task-manager/lib/agent-title-apply.js +88 -0
  26. package/template/coding-task-manager/lib/agent-title-backfill-read.js +92 -0
  27. package/template/coding-task-manager/lib/api/host-stats.js +98 -0
  28. package/template/coding-task-manager/lib/api/ingress.js +35 -12
  29. package/template/coding-task-manager/lib/api/machine-pairing.js +285 -0
  30. package/template/coding-task-manager/lib/api/machine-pool.js +307 -0
  31. package/template/coding-task-manager/lib/api/machines.js +461 -0
  32. package/template/coding-task-manager/lib/api/misc-ops.js +20 -5
  33. package/template/coding-task-manager/lib/api/providers.js +53 -6
  34. package/template/coding-task-manager/lib/api/resume-from-store.js +25 -0
  35. package/template/coding-task-manager/lib/api/sessions.js +160 -8
  36. package/template/coding-task-manager/lib/api/transcript-history.js +17 -4
  37. package/template/coding-task-manager/lib/api/worktrees.js +38 -37
  38. package/template/coding-task-manager/lib/approval-drift.js +16 -1
  39. package/template/coding-task-manager/lib/approval-logical-frame.js +5 -1
  40. package/template/coding-task-manager/lib/approval-scope.js +68 -4
  41. package/template/coding-task-manager/lib/auth-context.js +32 -2
  42. package/template/coding-task-manager/lib/auth-rate-limit.js +41 -1
  43. package/template/coding-task-manager/lib/auth-rules.js +186 -45
  44. package/template/coding-task-manager/lib/broadcast-serialize.js +2 -1
  45. package/template/coding-task-manager/lib/browser-session-broker.js +9 -1
  46. package/template/coding-task-manager/lib/browser-use-adapter.js +33 -2
  47. package/template/coding-task-manager/lib/claude-markdown-ansi.js +27 -85
  48. package/template/coding-task-manager/lib/claude-session-title.js +6 -30
  49. package/template/coding-task-manager/lib/claude-table-layout.js +322 -0
  50. package/template/coding-task-manager/lib/client-diag-handler.js +5 -0
  51. package/template/coding-task-manager/lib/codex-config-guard.js +33 -3
  52. package/template/coding-task-manager/lib/codex-harness-qualification.js +12 -0
  53. package/template/coding-task-manager/lib/codex-launch-health.js +29 -2
  54. package/template/coding-task-manager/lib/codex-link-preparation.js +9 -8
  55. package/template/coding-task-manager/lib/codex-paths.js +2 -1
  56. package/template/coding-task-manager/lib/coding-agent-models.js +21 -30
  57. package/template/coding-task-manager/lib/coding-session-model-adapters.js +27 -2
  58. package/template/coding-task-manager/lib/coding-session-model-projection.js +5 -0
  59. package/template/coding-task-manager/lib/coding-session-model-runtime.js +1 -2
  60. package/template/coding-task-manager/lib/coding-session-model-state.js +10 -0
  61. package/template/coding-task-manager/lib/coding-session-model-sync-state.js +0 -1
  62. package/template/coding-task-manager/lib/conversation-import/cursor-guard-owner.js +10 -0
  63. package/template/coding-task-manager/lib/create/resolve-launch-params.js +8 -1
  64. package/template/coding-task-manager/lib/ctm-boot-lifecycle.js +17 -1
  65. package/template/coding-task-manager/lib/db-domain-tables.js +7 -0
  66. package/template/coding-task-manager/lib/db-domains.js +21 -2
  67. package/template/coding-task-manager/lib/db-owner-op-registry.js +7 -0
  68. package/template/coding-task-manager/lib/db-schema-source-files.js +1 -0
  69. package/template/coding-task-manager/lib/feature-manifest.js +1 -1
  70. package/template/coding-task-manager/lib/fs-watcher.js +3 -2
  71. package/template/coding-task-manager/lib/harness-history-transition.js +3 -3
  72. package/template/coding-task-manager/lib/headless-inflight-ops.js +203 -0
  73. package/template/coding-task-manager/lib/headless-missing-reply.js +42 -0
  74. package/template/coding-task-manager/lib/headless-term-service.js +55 -17
  75. package/template/coding-task-manager/lib/host-control-dial.js +167 -0
  76. package/template/coding-task-manager/lib/host-handoff-orchestrator.js +16 -2
  77. package/template/coding-task-manager/lib/host-handoff.js +51 -13
  78. package/template/coding-task-manager/lib/host-health-ledger.js +164 -12
  79. package/template/coding-task-manager/lib/host-self-truth-watchdog.js +4 -0
  80. package/template/coding-task-manager/lib/host-stats.js +328 -0
  81. package/template/coding-task-manager/lib/host-threadpool-evacuation.js +160 -0
  82. package/template/coding-task-manager/lib/host-threadpool-monitor.js +14 -1
  83. package/template/coding-task-manager/lib/hosted-input-dispatch.js +203 -0
  84. package/template/coding-task-manager/lib/hosted-sync-worker.js +113 -5
  85. package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +4 -0
  86. package/template/coding-task-manager/lib/instance-manifest-runtime.js +3 -0
  87. package/template/coding-task-manager/lib/instance-manifest.js +13 -1
  88. package/template/coding-task-manager/lib/instance-sync-status.js +15 -0
  89. package/template/coding-task-manager/lib/launch-model-override.js +160 -1
  90. package/template/coding-task-manager/lib/launch-plan-worker.js +59 -2
  91. package/template/coding-task-manager/lib/lead-model-switch-runtime.js +57 -4
  92. package/template/coding-task-manager/lib/lead-model-switch.js +3 -2
  93. package/template/coding-task-manager/lib/lead-model-transition-store.js +61 -3
  94. package/template/coding-task-manager/lib/legacy-draft-cancellation-recovery.js +111 -0
  95. package/template/coding-task-manager/lib/login-tripwire.js +1 -0
  96. package/template/coding-task-manager/lib/machine-pairing.js +238 -0
  97. package/template/coding-task-manager/lib/machine-pool.js +409 -0
  98. package/template/coding-task-manager/lib/machine-registry.js +749 -0
  99. package/template/coding-task-manager/lib/machine-self.js +66 -0
  100. package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +52 -4
  101. package/template/coding-task-manager/lib/media-journal-control-reader.js +473 -0
  102. package/template/coding-task-manager/lib/media-journal.js +84 -27
  103. package/template/coding-task-manager/lib/media-store-options.js +43 -15
  104. package/template/coding-task-manager/lib/migrations/v56-host-samples-and-machine-pool.js +76 -0
  105. package/template/coding-task-manager/lib/mirror-feed-sanitize.js +12 -3
  106. package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
  107. package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
  108. package/template/coding-task-manager/lib/model-proxy/handoff.js +494 -0
  109. package/template/coding-task-manager/lib/model-proxy/lead-worker-picks.js +25 -0
  110. package/template/coding-task-manager/lib/model-proxy/native-lead-listener.js +61 -7
  111. package/template/coding-task-manager/lib/model-proxy/proxy-cli.js +200 -0
  112. package/template/coding-task-manager/lib/model-proxy/proxy-instance-record.js +257 -0
  113. package/template/coding-task-manager/lib/model-proxy/stream-accounting.js +205 -0
  114. package/template/coding-task-manager/lib/model-proxy/tokens.js +78 -0
  115. package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
  116. package/template/coding-task-manager/lib/native-history-control.js +2 -2
  117. package/template/coding-task-manager/lib/native-lead-settings-client.js +2 -1
  118. package/template/coding-task-manager/lib/native-lead-tls.js +18 -4
  119. package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +628 -64
  120. package/template/coding-task-manager/lib/owner-action-manifest-data.json +14 -0
  121. package/template/coding-task-manager/lib/painted-row-recorder.js +9 -1
  122. package/template/coding-task-manager/lib/prompt-submission-contract.js +21 -1
  123. package/template/coding-task-manager/lib/provider-login-broker.js +192 -9
  124. package/template/coding-task-manager/lib/provider-login-capture.js +153 -0
  125. package/template/coding-task-manager/lib/queue-draft-status-overlay.js +36 -0
  126. package/template/coding-task-manager/lib/raw-codex-history-window.js +5 -3
  127. package/template/coding-task-manager/lib/read-pool-client.js +5 -2
  128. package/template/coding-task-manager/lib/read-pool-op-registry.js +10 -1
  129. package/template/coding-task-manager/lib/recent-catalog-query.js +26 -0
  130. package/template/coding-task-manager/lib/recent-session-catalog-response.js +9 -0
  131. package/template/coding-task-manager/lib/remote-command-context.js +12 -0
  132. package/template/coding-task-manager/lib/remote-command-runtime.js +13 -1
  133. package/template/coding-task-manager/lib/remote-machine-catalog.js +1062 -0
  134. package/template/coding-task-manager/lib/remote-machine-create.js +693 -0
  135. package/template/coding-task-manager/lib/remote-machine-link.js +939 -0
  136. package/template/coding-task-manager/lib/remote-machine-proxy.js +520 -0
  137. package/template/coding-task-manager/lib/remote-session-forward.js +306 -0
  138. package/template/coding-task-manager/lib/remote-submission-status.js +6 -0
  139. package/template/coding-task-manager/lib/restart-outcome-truth.js +20 -3
  140. package/template/coding-task-manager/lib/restart-preflight.js +9 -2
  141. package/template/coding-task-manager/lib/restore-backoff-gate.js +10 -2
  142. package/template/coding-task-manager/lib/restore-prepare-retry.js +12 -5
  143. package/template/coding-task-manager/lib/restore-resume-batch.js +22 -1
  144. package/template/coding-task-manager/lib/resume-blocked-wire.js +62 -1
  145. package/template/coding-task-manager/lib/resume-cwd.js +13 -0
  146. package/template/coding-task-manager/lib/resume-from-store.js +12 -1
  147. package/template/coding-task-manager/lib/resume-origin-facts.js +38 -1
  148. package/template/coding-task-manager/lib/resume-preflight.js +21 -16
  149. package/template/coding-task-manager/lib/review-repo-admission.js +39 -6
  150. package/template/coding-task-manager/lib/review-repo-registry.js +17 -0
  151. package/template/coding-task-manager/lib/root-delivery-runtime.js +72 -15
  152. package/template/coding-task-manager/lib/root-delivery-selection.js +37 -1
  153. package/template/coding-task-manager/lib/root-prompt-admission.js +33 -2
  154. package/template/coding-task-manager/lib/root-prompt-flow.js +167 -33
  155. package/template/coding-task-manager/lib/scrollback-archive-store.js +7 -4
  156. package/template/coding-task-manager/lib/session-analysis-memory.js +2 -0
  157. package/template/coding-task-manager/lib/session-create/codex-native-provider-gate.js +218 -0
  158. package/template/coding-task-manager/lib/session-create/codex-resume-exit-classify.js +52 -0
  159. package/template/coding-task-manager/lib/session-create/delegate-worker.js +95 -6
  160. package/template/coding-task-manager/lib/session-create/identity.js +20 -1
  161. package/template/coding-task-manager/lib/session-create/prepared.js +69 -1
  162. package/template/coding-task-manager/lib/session-create/provider-launch-context.js +77 -0
  163. package/template/coding-task-manager/lib/session-create/provider-process.js +34 -37
  164. package/template/coding-task-manager/lib/session-create/response.js +30 -12
  165. package/template/coding-task-manager/lib/session-create/transcript.js +13 -4
  166. package/template/coding-task-manager/lib/session-create/worker-continuation.js +43 -1
  167. package/template/coding-task-manager/lib/session-create/worker-notices.js +94 -5
  168. package/template/coding-task-manager/lib/session-create/worker-observability.js +99 -7
  169. package/template/coding-task-manager/lib/session-create/worker-stop.js +11 -3
  170. package/template/coding-task-manager/lib/session-create/worker-wait-watcher.js +315 -0
  171. package/template/coding-task-manager/lib/session-geometry-authority-handler.js +10 -1
  172. package/template/coding-task-manager/lib/session-host-manager.js +152 -206
  173. package/template/coding-task-manager/lib/session-jobs.js +17 -9
  174. package/template/coding-task-manager/lib/session-lifecycle.js +18 -0
  175. package/template/coding-task-manager/lib/session-model-action-preconditions.js +9 -0
  176. package/template/coding-task-manager/lib/session-resource-sampler.js +19 -9
  177. package/template/coding-task-manager/lib/session-standup.js +67 -4
  178. package/template/coding-task-manager/lib/session-status-ledger.js +42 -3
  179. package/template/coding-task-manager/lib/session-status-runtime.js +45 -1
  180. package/template/coding-task-manager/lib/session-title-mirror.js +25 -57
  181. package/template/coding-task-manager/lib/session-transcript-trash.js +8 -1
  182. package/template/coding-task-manager/lib/startup-pointer-write.js +57 -0
  183. package/template/coding-task-manager/lib/state-sync/painted-frame-field.js +11 -0
  184. package/template/coding-task-manager/lib/storage/coding-session-model-store.js +21 -22
  185. package/template/coding-task-manager/lib/surviving-owner-bootstrap.js +7 -2
  186. package/template/coding-task-manager/lib/surviving-owner-recovery.js +2 -1
  187. package/template/coding-task-manager/lib/terminal-draft-http-status.js +25 -0
  188. package/template/coding-task-manager/lib/transcript-archiver.js +229 -229
  189. package/template/coding-task-manager/lib/transcript-frame-cache.js +12 -2
  190. package/template/coding-task-manager/lib/transcript-guard-job.js +55 -4
  191. package/template/coding-task-manager/lib/transcript-link.js +77 -0
  192. package/template/coding-task-manager/lib/transcript-manifest-store.js +8 -4
  193. package/template/coding-task-manager/lib/transcript-source.js +8 -3
  194. package/template/coding-task-manager/lib/transcript-store.js +40 -0
  195. package/template/coding-task-manager/lib/tunnel-lane-governor.js +6 -3
  196. package/template/coding-task-manager/lib/typing-interaction-trace.js +92 -0
  197. package/template/coding-task-manager/lib/typing-worker-stages.js +60 -0
  198. package/template/coding-task-manager/lib/visible-revision-watchdog.js +8 -12
  199. package/template/coding-task-manager/lib/wake-skip-reasons.js +46 -0
  200. package/template/coding-task-manager/lib/walle-supervisor.js +98 -14
  201. package/template/coding-task-manager/lib/worker-failure-classify.js +13 -1
  202. package/template/coding-task-manager/lib/ws/state-sync.js +19 -2
  203. package/template/coding-task-manager/lib/ws/terminal-attach.js +32 -1
  204. package/template/coding-task-manager/lib/ws-router.js +68 -1
  205. package/template/coding-task-manager/lib/ws-send-admission.js +22 -4
  206. package/template/coding-task-manager/oauth-proxy.js +99 -55
  207. package/template/coding-task-manager/package-lock.json +24 -0
  208. package/template/coding-task-manager/package.json +3 -0
  209. package/template/coding-task-manager/providers/codex-mcp-extract.js +121 -19
  210. package/template/coding-task-manager/providers/codex-mcp.js +14 -5
  211. package/template/coding-task-manager/public/css/app.css +279 -1
  212. package/template/coding-task-manager/public/css/reviews.css +4 -0
  213. package/template/coding-task-manager/public/index.html +294 -223
  214. package/template/coding-task-manager/public/js/app/machines-page.js +1252 -0
  215. package/template/coding-task-manager/public/js/app/models-tab.js +546 -36
  216. package/template/coding-task-manager/public/js/app/worktrees-panel.js +7 -5
  217. package/template/coding-task-manager/public/js/background-work.js +13 -7
  218. package/template/coding-task-manager/public/js/coding-session-model-presentation.js +44 -4
  219. package/template/coding-task-manager/public/js/core/session-commands.js +676 -0
  220. package/template/coding-task-manager/public/js/core/session-selectors.js +5 -1
  221. package/template/coding-task-manager/public/js/history-app-seam.js +26 -2
  222. package/template/coding-task-manager/public/js/history-painted-rows.js +17 -2
  223. package/template/coding-task-manager/public/js/history-reader-park.js +13 -1
  224. package/template/coding-task-manager/public/js/history-scroller.js +187 -9
  225. package/template/coding-task-manager/public/js/history-transcript-pages.js +5 -5
  226. package/template/coding-task-manager/public/js/machine-pool-shared.js +436 -0
  227. package/template/coding-task-manager/public/js/private-state-scope.js +31 -3
  228. package/template/coding-task-manager/public/js/prompt-submission-client.js +173 -8
  229. package/template/coding-task-manager/public/js/prompt-submission-tray.js +281 -0
  230. package/template/coding-task-manager/public/js/queue-panel-submission-reconcile.js +174 -1
  231. package/template/coding-task-manager/public/js/reviews.js +7 -3
  232. package/template/coding-task-manager/public/js/root-draft-lifetime.js +139 -2
  233. package/template/coding-task-manager/public/js/root-queue-builder-lifetime.js +16 -0
  234. package/template/coding-task-manager/public/js/row-join.js +4 -2
  235. package/template/coding-task-manager/public/js/session-model-picker.js +1 -1
  236. package/template/coding-task-manager/public/js/session-search-utils.js +46 -19
  237. package/template/coding-task-manager/public/js/state-sync-client.js +4 -0
  238. package/template/coding-task-manager/public/js/terminal/runtime-02.js +16 -7
  239. package/template/coding-task-manager/public/js/terminal/runtime-03.js +41 -0
  240. package/template/coding-task-manager/public/js/terminal/runtime-04.js +41 -4
  241. package/template/coding-task-manager/public/js/terminal/runtime-12.js +52 -2
  242. package/template/coding-task-manager/public/js/terminal/runtime-16.js +12 -12
  243. package/template/coding-task-manager/public/js/terminal/runtime-18.js +14 -0
  244. package/template/coding-task-manager/public/js/terminal/runtime-19.js +8 -4
  245. package/template/coding-task-manager/public/js/terminal/runtime-20.js +210 -73
  246. package/template/coding-task-manager/public/js/terminal/runtime-21.js +19 -6
  247. package/template/coding-task-manager/public/js/terminal/runtime-22.js +86 -0
  248. package/template/coding-task-manager/public/js/terminal-history-viewport.js +1 -1
  249. package/template/coding-task-manager/public/js/terminal-wheel.js +39 -2
  250. package/template/coding-task-manager/public/js/walle/chat-conversation.js +11 -3
  251. package/template/coding-task-manager/public/js/walle-session.js +30 -7
  252. package/template/coding-task-manager/public/m/app.css +226 -8
  253. package/template/coding-task-manager/public/m/app.js +502 -9
  254. package/template/coding-task-manager/public/m/index.html +38 -1
  255. package/template/coding-task-manager/public/m/machines.js +1313 -0
  256. package/template/coding-task-manager/public/m/pool-failover.js +391 -0
  257. package/template/coding-task-manager/public/m/private-queue-actions.js +26 -7
  258. package/template/coding-task-manager/public/m/sw.js +5 -0
  259. package/template/coding-task-manager/queue-engine.js +13 -0
  260. package/template/coding-task-manager/server/approvals.js +5 -6
  261. package/template/coding-task-manager/server/boot.js +136 -17
  262. package/template/coding-task-manager/server.js +902 -239
  263. package/template/coding-task-manager/session-integrity.js +7 -9
  264. package/template/coding-task-manager/workers/db-owner-worker.js +53 -7
  265. package/template/coding-task-manager/workers/read-pool-worker.js +17 -6
  266. package/template/coding-task-manager/workers/session-host-pool-process.js +18 -4
  267. package/template/coding-task-manager/workers/session-host-process.js +14 -3
  268. package/template/coding-task-manager/workers/transcript-archive-worker.js +1 -1
  269. package/template/coding-task-manager/workers/worktree-worker.js +34 -0
  270. package/template/package-lock.json +2 -2
  271. package/template/package.json +1 -1
  272. package/template/scripts/conversation-storage-v2-migration.js +78 -11
  273. package/template/shared/conversation-storage-v2-contract.js +42 -6
  274. package/template/shared/fixtures/worker-status-payloads.json +392 -0
  275. package/template/shared/git-remote-key.js +83 -0
  276. package/template/shared/host-identity.js +98 -2
  277. package/template/shared/owner-write-safety.js +135 -0
  278. package/template/shared/public-runtime.js +13 -5
  279. package/template/shared/release-schema.json +3 -2
  280. package/template/shared/report-citations.js +14 -0
  281. package/template/shared/sqlite-write-lock.js +361 -223
  282. package/template/shared/storage/contract.js +35 -3
  283. package/template/wall-e/agent.js +21 -0
  284. package/template/wall-e/api-walle.js +5 -0
  285. package/template/wall-e/bin/brain-purge-legacy-ctm.js +3 -2
  286. package/template/wall-e/bin/walle-mcp-stdio.js +12 -6
  287. package/template/wall-e/brain/helper-rules-schema.js +129 -0
  288. package/template/wall-e/brain/migrations.js +51 -22
  289. package/template/wall-e/brain/model-deployment-facts.js +31 -1
  290. package/template/wall-e/brain/owner-call-whitelist.js +16 -0
  291. package/template/wall-e/brain/schema-ensures.js +22 -0
  292. package/template/wall-e/brain.js +1 -1
  293. package/template/wall-e/coding/journal-dispatcher.js +51 -0
  294. package/template/wall-e/coding/session-journal.js +28 -40
  295. package/template/wall-e/coding-orchestrator.js +23 -26
  296. package/template/wall-e/http/auth.js +18 -1
  297. package/template/wall-e/http/blog-views-api.js +318 -0
  298. package/template/wall-e/http/helper-rules-api.js +321 -0
  299. package/template/wall-e/http/model-proxy-providers.js +3 -1
  300. package/template/wall-e/http/telemetry-api.js +12 -0
  301. package/template/wall-e/lib/blog-view-policy.js +88 -0
  302. package/template/wall-e/lib/blog-view-store.js +169 -0
  303. package/template/wall-e/lib/client-config-lock.js +2 -1
  304. package/template/wall-e/lib/coding-journal-writer-child-client.js +67 -18
  305. package/template/wall-e/lib/error-kinds.js +10 -1
  306. package/template/wall-e/lib/feature-manifest.js +1 -1
  307. package/template/wall-e/lib/helper-rules/cell-state.js +123 -0
  308. package/template/wall-e/lib/helper-rules/evaluate.js +357 -0
  309. package/template/wall-e/lib/helper-rules/explore-rubric.md +12 -0
  310. package/template/wall-e/lib/helper-rules/grade.js +277 -0
  311. package/template/wall-e/lib/helper-rules/label.js +247 -0
  312. package/template/wall-e/lib/helper-rules/lead-call-hook.js +522 -0
  313. package/template/wall-e/lib/helper-rules/store.js +667 -0
  314. package/template/wall-e/lib/mcp-integration.js +5 -6
  315. package/template/wall-e/lib/runtime-worker-pool.js +1 -0
  316. package/template/wall-e/lib/scheduler-worker-jobs.js +6 -0
  317. package/template/wall-e/lib/telemetry-db-schema.js +37 -0
  318. package/template/wall-e/lib/telemetry-process-client.js +76 -1
  319. package/template/wall-e/llm/default-fallback.js +4 -0
  320. package/template/wall-e/llm/model-catalog.js +13 -4
  321. package/template/wall-e/llm/model-roles.js +6 -0
  322. package/template/wall-e/llm/portkey-http.js +150 -0
  323. package/template/wall-e/llm/portkey-sync.js +15 -145
  324. package/template/wall-e/llm/worker-mcp-routing.js +61 -0
  325. package/template/wall-e/llm/worker-route.js +43 -1
  326. package/template/wall-e/loops/brain-custom-writes.js +45 -0
  327. package/template/wall-e/mcp-protocol-contract.js +41 -1
  328. package/template/wall-e/mcp-server.js +13 -2
  329. package/template/wall-e/mcp-worker-schemas.js +20 -2
  330. package/template/wall-e/memory/ctm-context-client.js +25 -5
  331. package/template/wall-e/shared/release-schema.json +3 -2
  332. package/template/wall-e/shared/sqlite-write-lock.js +361 -223
  333. package/template/wall-e/shared/storage/contract.js +35 -3
  334. package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
  335. package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +6 -0
  336. package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +7 -2
  337. package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +7 -2
  338. package/template/wall-e/tools/local-tools.js +6 -4
  339. package/template/wall-e/workers/brain-owner-worker.js +9 -0
  340. package/template/wall-e/workers/coding-journal-writer-child.js +8 -1
  341. package/template/wall-e/workers/telemetry-worker.js +8 -0
@@ -185,6 +185,13 @@
185
185
  "human",
186
186
  false
187
187
  ],
188
+ [
189
+ "DELETE",
190
+ "/api/sessions/:p1/image-drafts",
191
+ "respond",
192
+ "human",
193
+ false
194
+ ],
188
195
  [
189
196
  "DELETE",
190
197
  "/api/sessions/:p1/image-drafts/:p2",
@@ -3280,6 +3287,13 @@
3280
3287
  "human",
3281
3288
  false
3282
3289
  ],
3290
+ [
3291
+ "POST",
3292
+ "/api/providers/:p1/login/code",
3293
+ "admin",
3294
+ "human",
3295
+ false
3296
+ ],
3283
3297
  [
3284
3298
  "POST",
3285
3299
  "/api/queues",
@@ -900,9 +900,17 @@ function createPaintedRowRecorder(options) {
900
900
  // The floor never exceeds top: after a move back down (or rows revealed above the stretch start) nothing below top is servable.
901
901
  // R-F2: `below` = how many rows the app holds BELOW its screen, measured at the screen BOTTOM (`bot`) so a pin change cannot move it.
902
902
  // `top` is the first content row, so it moves when the pin count changes; the screen's bottom edge does not.
903
+ // Clock-free tests (D-2/D-4, WP-6): `samples` (the same monotone count as `stats().samples`, +1 on EVERY observe at the top of
904
+ // observeSample -- a sample that changes nothing counts too) and `hold` ride this object, so they travel wherever the recorder's
905
+ // state travels (the frame's `painted` field, mirror-status). `hold` describes the samples the recorder has REJECTED and not
906
+ // committed -- the same fact as the `held` flag beside it (non-null exactly when `held` is true: the samples since the last
907
+ // adopted screen, cleared by an adoption and by the seal that opens the next stretch). `kind` is the last rejection's kind
908
+ // ('ambiguous' | 'inPlace'), `rows` how many samples are outstanding, `top` the index they would have been adopted at. A reader
909
+ // can then wait on "this write has been sampled" (and on the hold it produced) instead of sleeping a window.
903
910
  function state() {
904
911
  const f = docFloor();
905
- return { epoch, top, pinned, floor: f, recorded: top - f, held: holds > 0, revision: revisionSeq, revisedFrom, breaks: breaks.map((b) => ({ at: b.at, reason: b.reason })), below: botMax === null ? 0 : Math.max(0, botMax - bot) }; // R-F2: rows below the screen bottom edge (0 at rest, after a seal or a jump)
912
+ const heldLast = holds > 0 && held.length ? held[held.length - 1] : null;
913
+ return { epoch, top, pinned, floor: f, recorded: top - f, held: holds > 0, hold: heldLast ? { kind: heldLast.kind || null, rows: held.length, top } : null, samples: stats.samples, revision: revisionSeq, revisedFrom, breaks: breaks.map((b) => ({ at: b.at, reason: b.reason })), below: botMax === null ? 0 : Math.max(0, botMax - bot) }; // R-F2: rows below the screen bottom edge (0 at rest, after a seal or a jump)
906
914
  }
907
915
 
908
916
  // The lowest index revised by an observe after revision `seq`; null when none. When the bounded event list no longer reaches back to
@@ -22,12 +22,27 @@ const SUBMISSION_SOURCES = new Set([
22
22
  'skill',
23
23
  'unknown',
24
24
  ]);
25
+ // WP-0 (queue-delivery-recovery): the closed vocabulary of recovery actions a
26
+ // client may offer next to a status frame. It is additive to the historical
27
+ // `retryable` boolean, which stays exactly as it was (including its legacy
28
+ // `retryable: true` => `retry` meaning).
29
+ const RECOVERY_ACTIONS = new Set(['retry', 'resend', 'review', 'none']);
25
30
 
26
31
  function normalizeSubmissionSource(source) {
27
32
  const clean = String(source || '').trim().toLowerCase();
28
33
  return SUBMISSION_SOURCES.has(clean) ? clean : 'unknown';
29
34
  }
30
35
 
36
+ // Missing or unrecognized -> 'none' ("nothing for you to do"), except the
37
+ // historical `retryable: true` verdict, which has always meant "resending the
38
+ // same text can succeed once the condition clears" -> 'retry'. A value is never
39
+ // invented from a code or a message string.
40
+ function normalizeRecoveryAction(value, retryable) {
41
+ const clean = typeof value === 'string' ? value.trim().toLowerCase() : '';
42
+ if (RECOVERY_ACTIONS.has(clean)) return clean;
43
+ return retryable === true ? 'retry' : 'none';
44
+ }
45
+
31
46
  function publicSubmissionStatus(remoteStatus, result = {}) {
32
47
  const knownStatus = Object.prototype.hasOwnProperty.call(TERMINAL_STATUS_MAP, String(remoteStatus || ''));
33
48
  const mapped = knownStatus ? TERMINAL_STATUS_MAP[String(remoteStatus || '')] : { status: 'needs_attention' };
@@ -37,12 +52,16 @@ function publicSubmissionStatus(remoteStatus, result = {}) {
37
52
  reason: String(result.reason || (knownStatus ? '' : 'unknown_status')),
38
53
  message: String(result.message || ''),
39
54
  source: String(result.source || ''),
40
- // Whether re-sending the SAME text can succeed once the condition clears
55
+ // Whether retrying the SAME admitted request can succeed once the condition clears
41
56
  // (agent busy, another prompt in flight, session still restoring) versus
42
57
  // needing the user to act first (answer an approval, revive the session).
43
58
  // Carried here so EVERY status frame agrees — the direct WS reply and the
44
59
  // broadcast both reach the client and the later one overwrites the record.
45
60
  retryable: result.retryable === true,
61
+ // Additive recovery verdict, same reasoning as `retryable`: the direct reply,
62
+ // the broadcast, the persisted result and the stored projection all run
63
+ // through this one function so they cannot disagree.
64
+ recoveryAction: normalizeRecoveryAction(result.recoveryAction, result.retryable === true),
46
65
  };
47
66
  }
48
67
 
@@ -151,6 +170,7 @@ module.exports = {
151
170
  createSubmissionCounters,
152
171
  executeTerminalWriteSequence,
153
172
  isConfirmedSubmissionRecord,
173
+ normalizeRecoveryAction,
154
174
  normalizeSubmissionSource,
155
175
  publicSubmissionStatus,
156
176
  runtimeResultForSubmissionStatus,
@@ -4,6 +4,8 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const crypto = require('crypto');
6
6
 
7
+ const { createLoginCapture, isLoopbackUrl, loopbackPortOf } = require('./provider-login-capture');
8
+
7
9
  const {
8
10
  browserShimPath,
9
11
  loginTabSpec,
@@ -46,6 +48,45 @@ const DEFAULT_PROVIDERS = Object.freeze({
46
48
  },
47
49
  });
48
50
 
51
+ // WP-1 (design §6, D-6): the ONE accepted shape of a returned sign-in code —
52
+ // `<code>#<state>`, both halves drawn from the vendor's own URL-safe alphabet. The
53
+ // code half is capped at 512 chars and the state half at 256 so a hostile body cannot
54
+ // smuggle a multi-kilobyte line into the tab's stdin.
55
+ const LOGIN_CODE_RE = /^([A-Za-z0-9._~-]{8,512})#([A-Za-z0-9._~-]{8,256})$/;
56
+ // The whole input is bounded before anything else runs: the route reads at most 16 KiB,
57
+ // and no legitimate paste (a bare code or a full callback URL with scopes) approaches this.
58
+ const LOGIN_CODE_MAX_CHARS = 4096;
59
+
60
+ // D-6 input normalisation, pure and exported for the unit table (AC-1a/AC-1b).
61
+ // Shape A `<code>#<state>` -> { code, state }
62
+ // Shape B an http(s) URL whose query has `code`/`state` -> { code, state }
63
+ // A loopback callback URL is its OWN refusal: its code was issued for the automatic
64
+ // `http://localhost:<port>/callback` redirect, while a pasted code is always exchanged
65
+ // with the manual redirect — the CLI would answer "Invalid code" for a code the card
66
+ // could have refused up front (§5 D-5).
67
+ function normalizeLoginCode(raw) {
68
+ const text = String(raw == null ? '' : raw).trim();
69
+ if (!text || text.length > LOGIN_CODE_MAX_CHARS) return { error: 'code_format_invalid' };
70
+ const direct = LOGIN_CODE_RE.exec(text);
71
+ if (direct) return { code: direct[1], state: direct[2] };
72
+ let parsed = null;
73
+ try {
74
+ parsed = new URL(text);
75
+ } catch {
76
+ /* expected: the trimmed string is neither a `code#state` pair nor a URL */
77
+ }
78
+ if (!parsed || (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')) {
79
+ return { error: 'code_format_invalid' };
80
+ }
81
+ const code = parsed.searchParams.get('code') || '';
82
+ const state = parsed.searchParams.get('state') || '';
83
+ if (!code || !state) return { error: 'code_format_invalid' };
84
+ if (isLoopbackUrl(text)) return { error: 'code_from_loopback_redirect' };
85
+ const split = LOGIN_CODE_RE.exec(`${code}#${state}`);
86
+ if (split) return { code: split[1], state: split[2] };
87
+ return { error: 'code_format_invalid' };
88
+ }
89
+
49
90
  function _nowIso() {
50
91
  return new Date().toISOString();
51
92
  }
@@ -105,7 +146,13 @@ class ProviderLoginBroker {
105
146
  // cache invalidation (lib/coding-agent-auth.invalidate).
106
147
  this.resolveCli = options.resolveCli || null;
107
148
  this.invalidateAccount = options.invalidateAccount || null;
149
+ // WP-1 (D-4): the ONE PTY write primitive a returned sign-in code travels through —
150
+ // server.js wires `(session, data) => _writeLifecyclePty(session, data, 'provider-login-code')`.
151
+ // Null in a pure unit context, where submitCode then answers 503 rather than writing.
152
+ this.writeToLoginTab = options.writeToLoginTab || null;
108
153
  this.subscriptions = new Map();
154
+ // WP-0: one output capture subscriber per open flow, keyed by sessionId, disposed on exit.
155
+ this.captures = new Map();
109
156
  }
110
157
 
111
158
  // One probeAll for the whole state read (the lib caches per agent for CACHE_MS).
@@ -140,9 +187,12 @@ class ProviderLoginBroker {
140
187
  _provider(id) {
141
188
  const provider = DEFAULT_PROVIDERS[String(id || '').trim().toLowerCase()];
142
189
  if (!provider) return null;
190
+ // WP-0 test seam (D-10): CTM_PROVIDER_LOGIN_<ID>_CLI pins the exact CLI path the login
191
+ // shell `exec`s, so a fixture CLI never depends on the agent-cli-cache/PATH order.
192
+ const cliOverride = String(this.env[`CTM_PROVIDER_LOGIN_${provider.id.toUpperCase()}_CLI`] || '').trim();
143
193
  return {
144
194
  ...provider,
145
- cli: this._resolveCli((provider.command || [])[0]),
195
+ cli: cliOverride || this._resolveCli((provider.command || [])[0]),
146
196
  credentialPath: this.env[`CTM_PROVIDER_LOGIN_${provider.id.toUpperCase()}_CREDENTIAL_PATH`] || provider.credentialPath,
147
197
  };
148
198
  }
@@ -198,19 +248,27 @@ class ProviderLoginBroker {
198
248
 
199
249
  _savedFlow(providerId, saved, browserUrl) {
200
250
  if (!saved.flow) {
201
- return browserUrl ? {
251
+ // D-1: a loopback-only sink row (the CLI's automatic URL, which no remote browser can
252
+ // complete) must not fabricate an awaiting_user flow out of thin air.
253
+ const synthetic = browserUrl && !isLoopbackUrl(browserUrl.url) ? browserUrl : null;
254
+ return synthetic ? {
202
255
  id: `${providerId}-browser-url`,
203
256
  sessionId: '',
204
257
  status: 'awaiting_user',
205
- verificationUrl: browserUrl.url,
258
+ verificationUrl: synthetic.url,
206
259
  userCode: '',
207
- startedAt: browserUrl.at,
208
- updatedAt: browserUrl.at,
260
+ state: '',
261
+ callbackPort: loopbackPortOf(synthetic.url),
262
+ codeEntry: '',
263
+ verificationUrlSource: '',
264
+ startedAt: synthetic.at,
265
+ updatedAt: synthetic.at,
209
266
  exitedAt: '',
210
267
  exitCode: null,
211
268
  signal: '',
212
269
  error: '',
213
270
  outputPreview: '',
271
+ codeSubmittedAt: '',
214
272
  } : null;
215
273
  }
216
274
  // The sink keeps the LAST URL per provider across flows, so a URL recorded before
@@ -218,12 +276,23 @@ class ProviderLoginBroker {
218
276
  // card would offer a dead "Open sign-in page" before the CLI printed anything.
219
277
  const startedAt = saved.flow.startedAt || '';
220
278
  const urlForThisFlow = browserUrl && (!startedAt || String(browserUrl.at || '') >= startedAt) ? browserUrl : null;
279
+ const sinkUrl = urlForThisFlow ? String(urlForThisFlow.url || '') : '';
280
+ // D-1: a loopback sink URL (own host or redirect_uri) never becomes the link — it is the
281
+ // port evidence the card explains the manual paste with. A non-loopback sink URL keeps
282
+ // today's back-fill behaviour for the case where the capture found nothing.
283
+ const loopbackSink = sinkUrl ? isLoopbackUrl(sinkUrl) : false;
221
284
  const flow = {
222
285
  id: saved.flow.id,
223
286
  sessionId: saved.flow.sessionId || '',
224
287
  status: saved.flow.status,
225
- verificationUrl: saved.flow.verificationUrl || urlForThisFlow?.url || '',
288
+ verificationUrl: saved.flow.verificationUrl || (loopbackSink ? '' : sinkUrl),
226
289
  userCode: saved.flow.userCode || '',
290
+ state: saved.flow.state || '',
291
+ callbackPort: typeof saved.flow.callbackPort === 'number'
292
+ ? saved.flow.callbackPort
293
+ : (loopbackSink ? loopbackPortOf(sinkUrl) : null),
294
+ codeEntry: saved.flow.codeEntry || '',
295
+ verificationUrlSource: saved.flow.verificationUrlSource || '',
227
296
  startedAt,
228
297
  updatedAt: saved.flow.updatedAt || urlForThisFlow?.at || '',
229
298
  exitedAt: saved.flow.exitedAt || '',
@@ -231,10 +300,13 @@ class ProviderLoginBroker {
231
300
  signal: saved.flow.signal || '',
232
301
  error: saved.flow.error || '',
233
302
  outputPreview: _redact(saved.flow.outputPreview || '').slice(-1200),
303
+ // WP-1: WHEN the user handed the code back. A timestamp only — the code and its
304
+ // state are never persisted (§5 D-5).
305
+ codeSubmittedAt: saved.flow.codeSubmittedAt || '',
234
306
  };
235
- // A URL in the sink is proof the CLI is waiting for the human — the tab tells
236
- // the truth about the flow's status until its exit handler settles it.
237
- if (flow.verificationUrl && OPEN_FLOW_STATUSES.includes(flow.status) && flow.status !== 'awaiting_user') {
307
+ // A captured link or a Codex code is proof the CLI is waiting for the human — the tab
308
+ // tells the truth about the flow's status until its exit handler settles it.
309
+ if ((flow.verificationUrl || flow.userCode) && OPEN_FLOW_STATUSES.includes(flow.status) && flow.status !== 'awaiting_user') {
238
310
  flow.status = 'awaiting_user';
239
311
  }
240
312
  return flow;
@@ -305,6 +377,11 @@ class ProviderLoginBroker {
305
377
  if (settled) return;
306
378
  settled = true;
307
379
  this.subscriptions.delete(sessionId);
380
+ const capture = this.captures.get(sessionId);
381
+ if (capture) {
382
+ if (typeof capture.dispose === 'function') capture.dispose();
383
+ this.captures.delete(sessionId);
384
+ }
308
385
  const next = this._readState();
309
386
  const current = next[provider.id]?.flow;
310
387
  // Superseded (a newer flow for this provider) or already final: nothing to say.
@@ -335,6 +412,50 @@ class ProviderLoginBroker {
335
412
  this._writeState(next);
336
413
  }
337
414
 
415
+ // WP-0 (D-2): the login tab's OWN output is where the CLI prints the link and the code.
416
+ // One subscriber per flow, seeded with the session's flushed tail (the CLI may have
417
+ // printed before createSession() resolved — §5 OQ-1), disposed by _watchLoginTab.settle.
418
+ _attachCapture(provider, session, flow) {
419
+ const host = session && (session.ptyProcess || session.pty);
420
+ if (!host || typeof host.onData !== 'function') return;
421
+ let subscription = null;
422
+ try {
423
+ const capture = createLoginCapture({
424
+ providerId: provider.id,
425
+ seed: String(session._textTail || ''),
426
+ onUpdate: (fields) => this._recordCaptured(provider, flow.sessionId, fields),
427
+ });
428
+ subscription = host.onData((data) => capture.feed(data));
429
+ } catch {
430
+ /* expected: a host that refuses a data subscriber — the flow still settles on exit */
431
+ return;
432
+ }
433
+ this.captures.set(flow.sessionId, subscription);
434
+ }
435
+
436
+ // Merges what the capture froze into the persisted flow. A flow that is gone, superseded
437
+ // or already final is left alone: capture is evidence, never a state resurrection.
438
+ _recordCaptured(provider, sessionId, fields) {
439
+ const state = this._readState();
440
+ const current = state[provider.id]?.flow;
441
+ if (!current || current.sessionId !== sessionId) return;
442
+ if (current.status === 'completed' || current.status === 'failed') return;
443
+ const next = { ...current };
444
+ if (fields.verificationUrl && !isLoopbackUrl(fields.verificationUrl)) {
445
+ next.verificationUrl = fields.verificationUrl;
446
+ next.verificationUrlSource = 'cli-print';
447
+ }
448
+ if (fields.state) next.state = fields.state;
449
+ if (fields.userCode) next.userCode = fields.userCode;
450
+ if (typeof fields.callbackPort === 'number') next.callbackPort = fields.callbackPort;
451
+ if (fields.codeEntry) next.codeEntry = fields.codeEntry;
452
+ if ((next.status === 'starting' || next.status === 'running') && (next.verificationUrl || next.userCode)) {
453
+ next.status = 'awaiting_user';
454
+ }
455
+ next.updatedAt = _nowIso();
456
+ this._writeFlow(provider, next);
457
+ }
458
+
338
459
  // POST /api/providers/:id/login — opens the provider's login tab.
339
460
  async start(id, opts = {}) {
340
461
  const provider = this._provider(id);
@@ -369,6 +490,10 @@ class ProviderLoginBroker {
369
490
  status: 'starting',
370
491
  verificationUrl: '',
371
492
  userCode: '',
493
+ state: '',
494
+ callbackPort: null,
495
+ codeEntry: '',
496
+ verificationUrlSource: '',
372
497
  startedAt,
373
498
  updatedAt: startedAt,
374
499
  exitedAt: '',
@@ -376,6 +501,7 @@ class ProviderLoginBroker {
376
501
  signal: '',
377
502
  error: '',
378
503
  outputPreview: '',
504
+ codeSubmittedAt: '',
379
505
  };
380
506
  this._writeFlow(provider, flow);
381
507
 
@@ -407,8 +533,64 @@ class ProviderLoginBroker {
407
533
  const running = { ...flow, status: 'running', updatedAt: _nowIso() };
408
534
  this._writeFlow(provider, running);
409
535
  this._watchLoginTab(provider, session, sessionId);
536
+ this._attachCapture(provider, session, running);
410
537
  return { ok: true, provider: provider.id, sessionId, flow: running };
411
538
  }
539
+
540
+ // POST /api/providers/:id/login/code — hands a code the user pasted on the card back to
541
+ // the flow's OWN login tab (§5 D-4/D-5). Every refusal is checked BEFORE the write, and the
542
+ // write itself happens EXACTLY ONCE, here: `<code>#<state>\r` through the injected
543
+ // `writeToLoginTab` (server.js binds it to `_writeLifecyclePty(session, data,
544
+ // 'provider-login-code')`, so the user-recency clocks stay untouched). No keystroke replay, no
545
+ // retry, no write to any other session, and the code/state never enter a response, a log line
546
+ // or the flow record.
547
+ async submitCode(id, { code } = {}) {
548
+ const provider = this._provider(id);
549
+ if (!provider) return { ok: false, status: 404, error: 'provider_not_found' };
550
+ const saved = this._readState()[provider.id] || {};
551
+ const flow = saved.flow || null;
552
+ if (!flow || !OPEN_FLOW_STATUSES.includes(flow.status)) {
553
+ return { ok: false, status: 409, error: 'provider_login_not_open', provider: provider.id };
554
+ }
555
+ const session = this.getSession(flow.sessionId);
556
+ if (!loginTabAlive(session)) {
557
+ return { ok: false, status: 409, error: 'login_tab_gone', provider: provider.id, sessionId: flow.sessionId };
558
+ }
559
+ // D-3: only a provider whose code travels browser -> card -> tab can accept one. Codex
560
+ // ('browser') and a not-yet-captured flow ('') are refused with their own answer.
561
+ if (flow.codeEntry !== 'paste') {
562
+ return {
563
+ ok: false,
564
+ status: 409,
565
+ error: 'code_entry_not_supported',
566
+ provider: provider.id,
567
+ codeEntry: flow.codeEntry || '',
568
+ };
569
+ }
570
+ const normalized = normalizeLoginCode(code);
571
+ if (normalized.error) return { ok: false, status: 400, error: normalized.error };
572
+ if (flow.state && normalized.state !== flow.state) {
573
+ return { ok: false, status: 400, error: 'code_state_mismatch' };
574
+ }
575
+ if (!this.writeToLoginTab) {
576
+ return { ok: false, status: 503, error: 'provider_login_unavailable', provider: provider.id };
577
+ }
578
+ const written = this.writeToLoginTab(session, `${normalized.code}#${normalized.state}\r`);
579
+ if (!written || written.posted === false) {
580
+ return { ok: false, status: 409, error: 'login_tab_gone', provider: provider.id, sessionId: flow.sessionId };
581
+ }
582
+ const next = { ...flow };
583
+ next.updatedAt = _nowIso();
584
+ next.codeSubmittedAt = next.updatedAt;
585
+ this._writeFlow(provider, next);
586
+ return {
587
+ ok: true,
588
+ status: 200,
589
+ provider: provider.id,
590
+ sessionId: flow.sessionId,
591
+ flow: this._savedFlow(provider.id, { flow: next }, null),
592
+ };
593
+ }
412
594
  }
413
595
 
414
596
  module.exports = {
@@ -416,5 +598,6 @@ module.exports = {
416
598
  DEFAULT_PROVIDERS,
417
599
  OPEN_FLOW_STATUSES,
418
600
  browserShimPath,
601
+ normalizeLoginCode,
419
602
  _deriveStatus,
420
603
  };
@@ -0,0 +1,153 @@
1
+ 'use strict';
2
+
3
+ // WP-0 (design docs/remote-agent-sign-in-from-hub-browser-2026-10-25.html §5 D-1/D-2/D-3,
4
+ // §6, §9 S-1): what a provider sign-in CLI PRINTS on its own PTY is the truth for the
5
+ // card's link and code. `claude auth login` prints its manual authorize URL (the automatic
6
+ // one carries a loopback `redirect_uri` and is useless to a remote browser); `codex login
7
+ // --device-auth` prints a device URL plus a one-time code and never calls a browser.
8
+ //
9
+ // Pure by contract: no fs, no timers, no process.env -- the rolling window is the only
10
+ // state, so a URL split across PTY chunks still matches once both halves are in it.
11
+ //
12
+ // A loopback URL (`localhost` / `127.0.0.1` / `::1`, as the URL's own host or as its
13
+ // `redirect_uri` host) is NEVER a verification URL (D-1); it contributes the callback port
14
+ // only, which the card uses to explain why the automatic link cannot sign a remote user in.
15
+
16
+ const { stripAnsi } = require('./login-tripwire');
17
+
18
+ const CAPTURE_WINDOW_CHARS = 8192;
19
+ const AUTHORIZE_URL_RE = /https:\/\/[^\s"'<>]+\/oauth\/authorize\?[^\s"'<>]+/g; // host-agnostic on purpose
20
+ const DEVICE_URL_RE = /https:\/\/[^\s"'<>]+\/codex\/device\b[^\s"'<>]*/;
21
+ // The code must be followed by whitespace (Codex prints a newline after it): a PTY chunk that
22
+ // ends inside the code (`A12B-C`) must not yield its first group as a complete code.
23
+ const DEVICE_CODE_LINE_RE = /Enter this one-time code[^\n]*\n\s*([A-Z0-9]{4,}(?:-[A-Z0-9]{4,})*)(?=\s)/;
24
+ const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '::1', '[::1]']);
25
+ const CAPTURE_FIELDS = Object.freeze(['verificationUrl', 'state', 'userCode', 'callbackPort', 'codeEntry']);
26
+
27
+ function _urlOf(value) {
28
+ try {
29
+ return new URL(String(value));
30
+ } catch {
31
+ /* expected: a relative or truncated string is not a URL */
32
+ return null;
33
+ }
34
+ }
35
+
36
+ // `new URL('[::1]')` keeps the brackets in hostname; the set carries both spellings, and the
37
+ // comparison is case-insensitive (hostnames are).
38
+ function _hostOf(parsed) {
39
+ return String(parsed.hostname || '').replace(/^\[|\]$/g, '').toLowerCase();
40
+ }
41
+
42
+ function _isLoopbackHost(parsed) {
43
+ return parsed ? LOOPBACK_HOSTS.has(_hostOf(parsed)) : false;
44
+ }
45
+
46
+ function isLoopbackUrl(value) {
47
+ const parsed = _urlOf(value);
48
+ if (!parsed) return false;
49
+ if (_isLoopbackHost(parsed)) return true;
50
+ const redirect = parsed.searchParams.get('redirect_uri');
51
+ return redirect ? _isLoopbackHost(_urlOf(redirect)) : false;
52
+ }
53
+
54
+ function loopbackPortOf(value) {
55
+ const parsed = _urlOf(value);
56
+ if (!parsed) return null;
57
+ const redirect = parsed.searchParams.get('redirect_uri');
58
+ const redirectParsed = redirect ? _urlOf(redirect) : null;
59
+ if (_isLoopbackHost(redirectParsed) && redirectParsed.port) return Number(redirectParsed.port);
60
+ if (_isLoopbackHost(parsed) && parsed.port) return Number(parsed.port);
61
+ return null;
62
+ }
63
+
64
+ function _stateParamOf(value) {
65
+ const parsed = _urlOf(value);
66
+ return parsed ? (parsed.searchParams.get('state') || '') : '';
67
+ }
68
+
69
+ // A match that runs to the very end of what we have may be truncated by the chunk boundary
70
+ // (a PTY chunk is arbitrary bytes, and stripAnsi cannot know an OSC hyperlink finished).
71
+ // feed() re-parses the window on every chunk, so waiting one chunk for the rest loses
72
+ // nothing -- and it keeps a half-received URL from being frozen as the card's link.
73
+ function _isCompleteMatch(match, source) {
74
+ return match.index + match[0].length < source.length;
75
+ }
76
+
77
+ // text is ALREADY stripped (use stripAnsi first). providerId only decides precedence when
78
+ // both shapes appear: claude -> the authorize URL wins, codex -> the device URL wins.
79
+ function parseLoginOutput(text, providerId) {
80
+ const source = String(text == null ? '' : text);
81
+ const provider = String(providerId == null ? '' : providerId).trim().toLowerCase();
82
+ const result = { verificationUrl: '', state: '', userCode: '', callbackPort: null, codeEntry: '' };
83
+
84
+ let authorizeUrl = '';
85
+ for (const match of source.matchAll(AUTHORIZE_URL_RE)) {
86
+ if (!_isCompleteMatch(match, source)) continue;
87
+ const url = match[0];
88
+ // The automatic URL the CLI hands to BROWSER is loopback-redirected: port evidence only (D-1).
89
+ if (isLoopbackUrl(url)) {
90
+ if (result.callbackPort === null) result.callbackPort = loopbackPortOf(url);
91
+ continue;
92
+ }
93
+ if (!authorizeUrl) authorizeUrl = url;
94
+ }
95
+
96
+ const deviceMatch = DEVICE_URL_RE.exec(source);
97
+ const deviceUrl = deviceMatch && _isCompleteMatch(deviceMatch, source) ? deviceMatch[0] : '';
98
+ const codeMatch = DEVICE_CODE_LINE_RE.exec(source);
99
+ const userCode = codeMatch ? codeMatch[1] : '';
100
+
101
+ const chosen = provider === 'codex' ? (deviceUrl || authorizeUrl) : (authorizeUrl || deviceUrl);
102
+ result.verificationUrl = chosen;
103
+ result.state = chosen ? _stateParamOf(chosen) : '';
104
+ result.codeEntry = !chosen ? '' : (chosen === authorizeUrl ? 'paste' : 'browser');
105
+ result.userCode = userCode;
106
+ return result;
107
+ }
108
+
109
+ /**
110
+ * createLoginCapture({ providerId, seed, onUpdate }) -> { feed(chunk), snapshot() }
111
+ * feed() appends to the rolling window and re-parses it; every field that changes
112
+ * from ''/null to a value is FROZEN (a later, different value never overwrites it) and the
113
+ * whole set of fields changed by this feed() is passed to onUpdate() exactly once.
114
+ * `seed` (the session's `_textTail`) is fed synchronously at creation, closing the
115
+ * attach-after-first-bytes race (§5 OQ-1).
116
+ *
117
+ * The window keeps RAW bytes and is stripped at parse time, not per chunk: a PTY chunk is
118
+ * arbitrary bytes, so an OSC-8 hyperlink (which the CLI wraps its URL in) can be split
119
+ * across two chunks -- stripping the halves separately would eat the `ESC ]` but leave
120
+ * `8;;` glued in front of the payload and the payload glued to the following text, and the
121
+ * card would get `…&state=<43>If`. Stripping the reassembled window removes the whole
122
+ * sequence, payload included (proved by the AC-0d chunk-split test).
123
+ */
124
+ function createLoginCapture({ providerId, seed = '', onUpdate } = {}) {
125
+ let window = '';
126
+ const frozen = { verificationUrl: '', state: '', userCode: '', callbackPort: null, codeEntry: '' };
127
+ const notify = typeof onUpdate === 'function' ? onUpdate : null;
128
+
129
+ const feed = (chunk) => {
130
+ window = `${window}${String(chunk == null ? '' : chunk)}`.slice(-CAPTURE_WINDOW_CHARS);
131
+ const parsed = parseLoginOutput(stripAnsi(window), providerId);
132
+ const changed = {};
133
+ for (const field of CAPTURE_FIELDS) {
134
+ if (frozen[field] !== '' && frozen[field] !== null) continue; // frozen once set
135
+ const next = parsed[field];
136
+ if (next === '' || next === null || next === undefined) continue;
137
+ frozen[field] = next;
138
+ changed[field] = next;
139
+ }
140
+ if (notify && Object.keys(changed).length) notify(changed);
141
+ };
142
+
143
+ if (seed) feed(seed);
144
+ return { feed, snapshot: () => ({ ...frozen }) };
145
+ }
146
+
147
+ module.exports = {
148
+ CAPTURE_WINDOW_CHARS,
149
+ isLoopbackUrl,
150
+ loopbackPortOf,
151
+ parseLoginOutput,
152
+ createLoginCapture,
153
+ };
@@ -0,0 +1,36 @@
1
+ 'use strict';
2
+
3
+ // R-1/R-2 (docs/clock-free-tests-followups-r5-2026-10-25.html §5, §6):
4
+ // the queue-draft GET overlays the durable, in-memory queue-engine item status
5
+ // onto the stored draft snapshot, so every reader (desktop, phone) sees the
6
+ // same truth without the GET writing anything back (D-4). Pure: no requires,
7
+ // no I/O, never mutates the draft it is handed.
8
+
9
+ // Only durable statuses that mean "the engine owns this item's fate" overlay a
10
+ // draft. A durable `pending` is deliberately excluded: the draft's
11
+ // queued/pending/confirming vocabulary stays client-owned (R-2, D-5).
12
+ const OVERLAY_STATUSES = new Set(['sending', 'sent', 'skipped', 'failed']);
13
+
14
+ // Missing / non-numeric / non-positive timestamps count as 0 (R-2).
15
+ function _draftStatusTimestamp(value) {
16
+ const n = Number(value);
17
+ return Number.isFinite(n) && n > 0 ? n : 0;
18
+ }
19
+
20
+ function overlayQueueDraftStatuses(draft, statusMap) {
21
+ if (!draft || typeof draft !== 'object' || !Array.isArray(draft.items)) return draft;
22
+ if (!statusMap || statusMap.size === 0) return { ...draft, items: draft.items.slice() };
23
+ const items = draft.items.map((item) => {
24
+ if (!item || typeof item !== 'object' || !item.id) return item;
25
+ const durable = statusMap.get(item.id);
26
+ if (!durable || !OVERLAY_STATUSES.has(durable.status)) return item;
27
+ const durableAt = _draftStatusTimestamp(durable.statusUpdatedAt);
28
+ const draftAt = _draftStatusTimestamp(item.statusUpdatedAt);
29
+ // Newest timestamp wins (A5), tie goes to the durable status (AC-1a).
30
+ if (durableAt < draftAt) return item;
31
+ return { ...item, status: durable.status, statusUpdatedAt: durable.statusUpdatedAt };
32
+ });
33
+ return { ...draft, items };
34
+ }
35
+
36
+ module.exports = { overlayQueueDraftStatuses, OVERLAY_STATUSES };
@@ -23,6 +23,7 @@ const MAX_CURSOR_BYTES = 16 * 1024;
23
23
  // A request keeps consuming blocks until it holds maxRows rows or has read maxBytes; this bounds
24
24
  // the work when many consecutive blocks hold only hidden record kinds.
25
25
  const MAX_FILL_BLOCKS = 16;
26
+ const MAX_EMPTY_RUN_BLOCKS = 256; // D-1: while no row is collected a fill keeps consuming up to 256 × BLOCK_BYTES = 8 MiB of hidden records before answering an empty page
26
27
  // leadPrompt: when no loaded unit before the page holds a prompt, scan this many EARLIER blocks (2 MiB) for one.
27
28
  const LEAD_SCAN_MAX_BLOCKS = 64;
28
29
  // Per-process LRU of scan answers. A page inside one long turn would otherwise re-scan the same blocks on every
@@ -323,7 +324,7 @@ function getRawCodexHistoryWindow(sources, options = {}, fileOps = transcriptSou
323
324
  // maxBytes budgets the source bytes CONSUMED (the logical blocks); readRawHistoryBlock also reads a
324
325
  // block of look-around each side to resolve record ownership, so counting raw reads would stop at ~3 blocks.
325
326
  const consumedBytes = () => segments.reduce((sum, one) => sum + (one.block.end - one.block.start), 0);
326
- while (all.length < maxRows && !(all.length && consumedBytes() >= maxBytes) && segments.length < MAX_FILL_BLOCKS) {
327
+ while (all.length < maxRows && !(all.length && consumedBytes() >= maxBytes) && segments.length < (all.length ? MAX_FILL_BLOCKS : MAX_EMPTY_RUN_BLOCKS)) {
327
328
  const at = forward ? nextBlockOf(seg, all.at(-1)) : prevBlockOf(seg);
328
329
  if (!at) break;
329
330
  seg = loadBlock(at.x, at.b);
@@ -466,7 +467,8 @@ function getRawCodexHistoryWindow(sources, options = {}, fileOps = transcriptSou
466
467
  // A block with only non-display records must still advance rather than claim an edge.
467
468
  if (!items.length && all.length) return base('retryable-error', generation, 'response-budget-too-small');
468
469
  if (!items.length) {
469
- // Every block consumed was hidden-only: hand back cursors just past the oldest/newest one read.
470
+ // Every block consumed (up to MAX_EMPTY_RUN_BLOCKS while no row was collected) was hidden-only: hand back
471
+ // cursors just past the oldest/newest one read, so the next request resumes after the hidden run it walked.
470
472
  const oldest = forward ? segments[0] : segments.at(-1); const newest = forward ? segments.at(-1) : segments[0];
471
473
  const older = prevBlockOf(oldest); const newer = nextBlockOf(newest, null);
472
474
  response.olderCursor = cursorAt(older && { ...older, u: Number.MAX_SAFE_INTEGER, f: Number.MAX_SAFE_INTEGER });
@@ -484,4 +486,4 @@ function getRawCodexHistoryWindow(sources, options = {}, fileOps = transcriptSou
484
486
  return response;
485
487
  }
486
488
 
487
- module.exports = { FAMILY, BLOCK_BYTES, encodeRawCodexHistoryCursor, decodeRawCodexHistoryCursor, isRawCodexHistoryCursor, getRawCodexHistoryWindow, historyRowKindOfRole };
489
+ module.exports = { FAMILY, BLOCK_BYTES, MAX_EMPTY_RUN_BLOCKS, encodeRawCodexHistoryCursor, decodeRawCodexHistoryCursor, isRawCodexHistoryCursor, getRawCodexHistoryWindow, historyRowKindOfRole };
@@ -1105,8 +1105,11 @@ class CtmReadPoolClient {
1105
1105
  const timer = setTimeout(resolve, 500);
1106
1106
  timer.unref?.();
1107
1107
  });
1108
- const results = await Promise.all(this._workers.map(worker => worker.requestReadonlyRecycle(reason)));
1109
- return { requested: this._workers.length, recycled: results.filter(Boolean).length };
1108
+ // Only a slot with an isolate can hold a read connection; a retired / never-started slot is
1109
+ // already in a reader gap (sqlite.org/wal.html "reader gaps").
1110
+ const live = this._workers.filter((worker) => worker.isLive);
1111
+ const results = await Promise.all(live.map((worker) => worker.requestReadonlyRecycle(reason)));
1112
+ return { requested: live.length, recycled: results.filter(Boolean).length, skippedNoIsolate: this._workers.length - live.length };
1110
1113
  }
1111
1114
 
1112
1115
  // `idleMs` (WP-4): when set, only workers idle (no completed op) at least this long