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
@@ -4,6 +4,31 @@
4
4
  })(typeof globalThis !== 'undefined' ? globalThis : this, function() {
5
5
  'use strict';
6
6
 
7
+ // WP-1 (C3, design §6): the same closed recovery vocabulary the server publishes. Send
8
+ // Next reads `lastAttempt.recoveryAction` directly, so this module normalizes
9
+ // independently of the manager: an unrecognized value never grants a fresh id, and a
10
+ // frame with no usable verdict is conservative (review -- restore + explicit confirm).
11
+ var RECOVERY_ACTIONS = new Set(['retry', 'resend', 'review', 'none']);
12
+
13
+ function normalizeRecoveryAction(value, retryable) {
14
+ var clean = typeof value === 'string' ? value.trim().toLowerCase() : '';
15
+ if (RECOVERY_ACTIONS.has(clean)) return clean;
16
+ return retryable === true ? 'retry' : 'review';
17
+ }
18
+
19
+ // R7 (AC-1e): the recovery policy a queue item's last failure published, so "Send Next" can
20
+ // never silently repeat an identity the server already refused.
21
+ // retry / none -> keep the admitted identity (a fresh id would be a second submission)
22
+ // resend -> certified no-effect recovery: drop the dead identity, one new attempt
23
+ // review -> uncertain: never dispatched, the user confirms first
24
+ // An item with no recorded attempt is an ordinary unsent draft and keeps the historical
25
+ // same-identity behavior.
26
+ function itemRecoveryPolicy(item) {
27
+ var lastAttempt = item && item.lastAttempt;
28
+ if (!lastAttempt) return 'retry';
29
+ return normalizeRecoveryAction(lastAttempt.recoveryAction, lastAttempt.retryable === true);
30
+ }
31
+
7
32
  function positiveTimestamp(value, fallback) {
8
33
  var timestamp = Number(value);
9
34
  if (Number.isFinite(timestamp) && timestamp > 0) return timestamp;
@@ -34,13 +59,20 @@
34
59
  var status = String(message && message.status || '').toLowerCase();
35
60
  if (status === 'sent') return { status: 'sent', message: '' };
36
61
  if (status === 'blocked' || status === 'failed' || status === 'needs_attention') {
62
+ var retryable = !!(message && message.retryable === true);
63
+ var reason = String((message && (message.reason || message.code)) || '');
37
64
  return {
38
65
  status: 'pending',
39
66
  lastAttempt: {
40
67
  at: Number(message && message.observedAt) || Date.now(),
41
68
  outcome: message && message.status,
42
69
  message: String((message && message.message) || ''),
43
- retryable: !!(message && message.retryable === true),
70
+ retryable: retryable,
71
+ // Additive for the recovery lanes: which action the server granted this
72
+ // failure (`recoveryAction`) and why (`reason`). Always derived, never
73
+ // invented from prose, so Send Next has a policy even for a legacy frame.
74
+ recoveryAction: normalizeRecoveryAction(message && message.recoveryAction, retryable),
75
+ ...(reason ? { reason: reason } : {}),
44
76
  },
45
77
  };
46
78
  }
@@ -135,10 +167,151 @@
135
167
  return params.toString();
136
168
  }
137
169
 
170
+ // R6 (AC-1d/AC-1f): the ONE route from a tray recovery gesture into the queue. It never mints an
171
+ // identity itself: when the panel still owns the exact submission, the owner-fenced resendItem
172
+ // (passed in as `resendItem`) is the only thing allowed to drop the old ID; otherwise the record
173
+ // stays with the manager, whose own rules decide whether a replacement is permitted at all.
174
+ function routeTrayRecovery(action, record, options, deps) {
175
+ deps = deps || {};
176
+ // Captured once, up front: the gesture is fenced by the manager that owned the record when the
177
+ // gesture started, and its own scope guard applies on completion. Never re-read mid-flight.
178
+ var manager = typeof deps.getManager === 'function' ? deps.getManager() : null;
179
+ var clientMessageId = String(record && record.clientMessageId || '');
180
+ if (action !== 'resend' || !manager || !clientMessageId) return Promise.resolve(false);
181
+ // AC-1e: the failure's own verdict decides whether a fresh attempt is permitted at all.
182
+ // `review` is uncertainty -- only this call being the explicit confirmation may proceed --
183
+ // `resend` is the certified no-effect recovery, and retry/none/unknown can never start a
184
+ // fresh send from here (a legacy frame with no verdict normalizes to `review`).
185
+ var policy = normalizeRecoveryAction(record.recoveryAction, record.retryable === true);
186
+ var confirmed = !!(options && options.confirmUncertain === true);
187
+ if (policy === 'review' ? !confirmed : policy !== 'resend') return Promise.resolve(false);
188
+ var sessionId = String(record && record.sessionId || '');
189
+ // Exact session AND exact item only: a tab switch between the gesture and this call must never
190
+ // touch another session's item, and an item that already moved to a newer identity is not this
191
+ // record's item any more.
192
+ var items = sessionId && typeof deps.getSessionId === 'function' && sessionId === deps.getSessionId()
193
+ && typeof deps.getItems === 'function' && Array.isArray(deps.getItems())
194
+ ? deps.getItems() : [];
195
+ var index = items.findIndex(function(item) {
196
+ return item && String(item._submissionClientMessageId || '') === clientMessageId;
197
+ });
198
+ if (index < 0) {
199
+ // A record the queue panel produced belongs to the panel: with no exact current item there is
200
+ // nothing this route may send, and it must NEVER fall back to a manager resend -- that would
201
+ // mint a fresh identity for a prompt the panel still owns (or send it into another session).
202
+ if (String(record.source || '') === 'queue-panel') return Promise.resolve(false);
203
+ var pending = manager.resend(clientMessageId, {
204
+ confirmUncertain: confirmed,
205
+ });
206
+ // resend() refuses outright, or registers its successor synchronously before returning. The
207
+ // original only disappears once that controlled successor really exists, so a refused
208
+ // preflight leaves the preserved text exactly where it was.
209
+ var superseded = Array.from(manager.records.values()).some(function(candidate) {
210
+ return candidate && String(candidate.supersedesClientMessageId || '') === clientMessageId;
211
+ });
212
+ if (!superseded) return pending;
213
+ manager.dismiss(clientMessageId);
214
+ return Promise.resolve(true);
215
+ }
216
+ return Promise.resolve(deps.resendItem(index)).then(function(dispatched) {
217
+ // The stale manager row retires only after the controlled successor has started.
218
+ if (dispatched) manager.dismiss(clientMessageId);
219
+ return dispatched === true;
220
+ });
221
+ }
222
+
223
+ // ---------------------------------------------------------------------------
224
+ // R7 (AC-1e, WP-1 fix): the review surface for a queue item whose last attempt is uncertain.
225
+ // "Send Next" used to open the edit box for such an item, which was a dead end: saving unchanged
226
+ // text does not clear lastAttempt/the dead id, so the next Send Next reopened it forever. The
227
+ // queue-owned review below is the explicit confirmation path instead, and it works for a
228
+ // preserved item after a reload -- the manager may hold no record for it at all.
229
+ // ---------------------------------------------------------------------------
230
+
231
+ var REVIEW_WARNING = 'CTM could not confirm what happened to this prompt.';
232
+
233
+ // The first pending item whose failure verdict is `review`, i.e. the first item Send Next must
234
+ // NOT dispatch. Scanned from the pending set so an auto/multi-item queue POST cannot bypass it.
235
+ function pendingReviewItem(items) {
236
+ if (!Array.isArray(items)) return null;
237
+ for (var index = 0; index < items.length; index += 1) {
238
+ var item = items[index];
239
+ if (!item || item._forceResend === true) continue;
240
+ if (!String(item._submissionClientMessageId || '')) continue;
241
+ if (String(item.status || 'pending').toLowerCase() === 'sent') continue;
242
+ if (itemRecoveryPolicy(item) !== 'review') continue;
243
+ return { index: index, item: item };
244
+ }
245
+ return null;
246
+ }
247
+
248
+ // A READ-ONLY snapshot of the exact live item: the prompt the user typed, its attachments and the
249
+ // server's own warning. Nothing is written back onto the item (no review-authority flag survives a
250
+ // reload) and nothing is dispatched from here.
251
+ function buildReviewRecord(item, deps) {
252
+ var lastAttempt = item && item.lastAttempt ? item.lastAttempt : null;
253
+ return {
254
+ clientMessageId: String(item && item._submissionClientMessageId || ''),
255
+ sessionId: deps && typeof deps.getSessionId === 'function' ? String(deps.getSessionId() || '') : '',
256
+ source: 'queue-panel',
257
+ status: 'needs_attention',
258
+ reason: String((lastAttempt && lastAttempt.reason) || 'review_required'),
259
+ message: String((lastAttempt && lastAttempt.message) || REVIEW_WARNING),
260
+ text: String(item && item.text || ''),
261
+ images: item && Array.isArray(item.images) ? item.images.slice() : [],
262
+ recoveryAction: 'review',
263
+ retryable: false,
264
+ reviewOnly: true,
265
+ };
266
+ }
267
+
268
+ // The surface's lifetime: the manager identity, the selected session, the exact items array and
269
+ // the exact original item all have to still be the ones the review was opened for. Captured here,
270
+ // never re-read from live state at click time -- and the id is never authority on its own.
271
+ function createReviewGuard(record, item, deps) {
272
+ var manager = typeof deps.getManager === 'function' ? deps.getManager() : null;
273
+ var items = typeof deps.getItems === 'function' ? deps.getItems() : null;
274
+ var sessionId = typeof deps.getSessionId === 'function' ? String(deps.getSessionId() || '') : '';
275
+ var originalId = String((record && record.clientMessageId) || '');
276
+ return function isCurrent(candidate) {
277
+ if (typeof deps.getManager === 'function' && deps.getManager() !== manager) return false;
278
+ if (typeof deps.getSessionId === 'function' && String(deps.getSessionId() || '') !== sessionId) return false;
279
+ if (typeof deps.getItems === 'function' && deps.getItems() !== items) return false;
280
+ if (String((candidate && candidate.clientMessageId) || '') !== originalId) return false;
281
+ if (!Array.isArray(items) || items.indexOf(item) < 0) return false;
282
+ return String(item._submissionClientMessageId || '') === originalId;
283
+ };
284
+ }
285
+
286
+ // Opens the explicit review surface for the first review-blocked pending item. `deps.renderReview`
287
+ // is the renderer (the tray module owns the DOM) and receives the read-only record plus the
288
+ // lifetime predicate above; its confirmation travels back through routeTrayRecovery, so the only
289
+ // thing that can drop the dead identity is the existing owner-fenced resendItem.
290
+ function openItemReview(items, deps) {
291
+ deps = deps || {};
292
+ var candidate = pendingReviewItem(items);
293
+ if (!candidate) return false;
294
+ var record = buildReviewRecord(candidate.item, deps);
295
+ var isCurrent = createReviewGuard(record, candidate.item, deps);
296
+ if (!isCurrent(record)) return false;
297
+ if (typeof deps.notify === 'function') {
298
+ deps.notify(String((candidate.item.lastAttempt && candidate.item.lastAttempt.message) || ''));
299
+ }
300
+ if (typeof deps.renderReview === 'function') deps.renderReview(record, isCurrent);
301
+ return true;
302
+ }
303
+
138
304
  return {
139
305
  buildSubmissionLookupQuery: buildSubmissionLookupQuery,
306
+ buildReviewRecord: buildReviewRecord,
307
+ createReviewGuard: createReviewGuard,
308
+ itemRecoveryPolicy: itemRecoveryPolicy,
140
309
  markItemSentById: markItemSentById,
310
+ normalizeRecoveryAction: normalizeRecoveryAction,
311
+ openItemReview: openItemReview,
312
+ pendingReviewItem: pendingReviewItem,
141
313
  reconcileSubmissionRows: reconcileSubmissionRows,
142
314
  reconcileSubmissionStatus: reconcileSubmissionStatus,
315
+ routeTrayRecovery: routeTrayRecovery,
143
316
  };
144
317
  });
@@ -859,6 +859,8 @@ CR.openSessionForProject = function() {
859
859
  args: [],
860
860
  cwd: projectPath,
861
861
  label: projectPath.replace(/^.*\//, '') + ' (review)',
862
+ // D-13: a review session's name is composed here, so it is a default (tier 3), not a rename.
863
+ titleIntent: 'default',
862
864
  });
863
865
  if (typeof window.toast === 'function') {
864
866
  window.toast('Starting Claude session at ' + projectPath.replace(/^.*\//, ''), { type: 'success' });
@@ -1425,9 +1427,10 @@ function _crumbDir(relPath) {
1425
1427
 
1426
1428
  // R-5: the review chat is no longer a sidebar — it is the Wall-E chat's review
1427
1429
  // thread (`review-<ctmSessionId>`). One button opens it on the WALL-E tab; the
1428
- // server ensures the thread row on the first message.
1430
+ // server ensures the thread row on the first message. The label is its own span
1431
+ // so the one-line doc header can show the icon alone (reviews.css).
1429
1432
  function _chatToggleHTML() {
1430
- return `<button class="cr-chat-toggle" onclick="CR.toggleChat()" title="Discuss this review in the Wall-E chat (Cmd+Shift+C)" id="cr-chat-toggle-btn">&#x1F4AC; Discuss in Wall-E chat</button>`;
1433
+ return `<button class="cr-chat-toggle" onclick="CR.toggleChat()" title="Discuss this review in the Wall-E chat (Cmd+Shift+C)" aria-label="Discuss in Wall-E chat" id="cr-chat-toggle-btn">&#x1F4AC;<span class="cr-chat-toggle-label"> Discuss in Wall-E chat</span></button>`;
1431
1434
  }
1432
1435
 
1433
1436
  function renderDocHeader() {
@@ -3013,7 +3016,8 @@ function _createSessionAndSendReview(prompt) {
3013
3016
  window.toast('Creating new Claude session...', { type: 'info' });
3014
3017
  }
3015
3018
 
3016
- window.send({ type: 'create', id: sessionId, cmd: 'claude', args: [], cwd, label: label.slice(0, 60) });
3019
+ // D-13: `Code Review:`/`Doc Review:` is CTM-composed — mark it default so it lands tier 3.
3020
+ window.send({ type: 'create', id: sessionId, cmd: 'claude', args: [], cwd, label: label.slice(0, 60), titleIntent: 'default' });
3017
3021
 
3018
3022
  setTimeout(() => {
3019
3023
  if (_pendingReviewSend?.sessionId === sessionId) {
@@ -14,6 +14,10 @@
14
14
  const requests = new WeakMap();
15
15
  const contexts = new WeakMap();
16
16
  const imageUploads = new WeakMap();
17
+ const unmatchedPublications = new WeakMap();
18
+ // Bounded like every other retained-evidence store here: newest version per durable attachment
19
+ // id, oldest entry evicted past the cap. A session has at most a handful of drafts in flight.
20
+ const UNMATCHED_PUBLICATION_LIMIT = 16;
17
21
  const activeById = new Map();
18
22
 
19
23
  // Same rule as Draft.stateVersionOf (§6: a lifecycle integer is never coerced from a
@@ -92,6 +96,11 @@
92
96
  for (const reservation of [...uploads.values()]) disposeImageUpload(reservation);
93
97
  uploads.clear();
94
98
  }
99
+ // Same rule for a publication retained for an attachment this context had no row for: an
100
+ // in-app retirement is revocation, not a licence to keep applying server truth to a dead
101
+ // draft (the map is keyed by the context, so this also drops the last reference early).
102
+ const retainedUnmatched = unmatchedPublications.get(context);
103
+ if (retainedUnmatched) retainedUnmatched.clear();
95
104
  const attachments = context.draft && Array.isArray(context.draft.attachments)
96
105
  ? context.draft.attachments : [];
97
106
  for (const attachment of attachments) {
@@ -269,6 +278,89 @@
269
278
  return true;
270
279
  }
271
280
 
281
+ function unmatchedPublicationsOf(context) {
282
+ let map = unmatchedPublications.get(context);
283
+ if (!map) { map = new Map(); unmatchedPublications.set(context, map); }
284
+ return map;
285
+ }
286
+
287
+ // A client that has never seen this draft before -- the normal shape after a page reload, or a
288
+ // second tab opened on a session with a durable draft -- learns its rows from the restore LIST,
289
+ // but the server starts publishing the moment the row commits. A publication that arrives
290
+ // before that list has been applied therefore has NO row to join (`onTerminalImageDraftState`'s
291
+ // correlation finds nothing), and dropping it there left the restored chip frozen on the list's
292
+ // OLDER state -- typically `delivery_pending` -> 'attaching' -- while the terminal publication
293
+ // had in fact already arrived, so the chip showed "Attached ✓" (from the local client hint) over
294
+ // `data-status="attaching"` forever. Retain it instead, fenced by the CAPTURED context (session)
295
+ // and the publication's own durable attachment id, and consume it exactly once from the restore
296
+ // that creates the row.
297
+ //
298
+ // The list stays the EXISTENCE authority: an attachment the server never lists never gains a
299
+ // row, so a retained publication can never resurrect a chip the user dismissed or one that was
300
+ // retired by a submitted snapshot. Identity is validated here with what is honestly known
301
+ // without a row (session + durable id); the restore re-validates the full identity (owner,
302
+ // and for a local row its request id) before adopting.
303
+ function retainUnmatchedPublication(context, snapshot, hint) {
304
+ if (!current(context) || !snapshot || typeof snapshot !== 'object') return false;
305
+ const version = lifecycleVersion(snapshot.stateVersion);
306
+ if (version === null || !isCanonicalState(String(snapshot.state || ''))) return false;
307
+ const attachmentId = String(snapshot.attachmentId || '');
308
+ if (!attachmentId) return false;
309
+ if (!DraftApi || !DraftApi.validateServerSnapshot(snapshot, {
310
+ sessionId: context.id, attachmentId, ownerHostId: String(snapshot.ownerHostId || ''),
311
+ }).ok) return false;
312
+ const map = unmatchedPublicationsOf(context);
313
+ const retained = map.get(attachmentId);
314
+ if (retained && version < retained.version) return true;
315
+ if (retained && version === retained.version
316
+ && DraftApi.canonicalSnapshotsEqual(retained.snapshot, snapshot)) return true;
317
+ const copy = {};
318
+ for (const key of Object.keys(snapshot)) {
319
+ if (key === 'hint' || key === 'type' || key === 'timestamp') continue;
320
+ copy[key] = snapshot[key];
321
+ }
322
+ map.delete(attachmentId);
323
+ map.set(attachmentId, {
324
+ snapshot: copy,
325
+ hint: _retainedHint(null, hint, version, String(snapshot.deliveryAttemptId || '')),
326
+ version,
327
+ });
328
+ while (map.size > UNMATCHED_PUBLICATION_LIMIT) {
329
+ const oldest = map.keys().next();
330
+ if (oldest.done) break;
331
+ map.delete(oldest.value);
332
+ }
333
+ return true;
334
+ }
335
+
336
+ // Read-only observability for the bounded-work contract: never allocates, reports 0 for an
337
+ // unknown/retired context.
338
+ function unmatchedPublicationCount(context) {
339
+ if (!current(context)) return 0;
340
+ const map = unmatchedPublications.get(context);
341
+ return map ? map.size : 0;
342
+ }
343
+
344
+ // The consuming half, called from `mergeServerSnapshot` once the restore list has given the
345
+ // attachment a row: the retained publication is newer server truth for that durable id, so it
346
+ // is ADOPTED (never a local transition) under the restore's own identity envelope. Consumed
347
+ // exactly once -- the entry is dropped whether or not it was newer than the list row -- so a
348
+ // later restore cannot replay it. Returns whether the row changed.
349
+ function applyUnmatchedPublication(context, attachment, identity) {
350
+ if (!current(context) || !attachment) return false;
351
+ const attachmentId = String(attachment.attachmentId || '');
352
+ if (!attachmentId) return false;
353
+ const map = unmatchedPublications.get(context);
354
+ const retained = map && map.get(attachmentId);
355
+ if (!retained) return false;
356
+ map.delete(attachmentId);
357
+ const rowVersion = DraftApi && typeof DraftApi.stateVersionOf === 'function'
358
+ ? DraftApi.stateVersionOf(attachment.stateVersion) : null;
359
+ if (rowVersion !== null && retained.version <= rowVersion) return false;
360
+ return DraftApi.adoptServerSnapshot(context.draft, attachment.clientId, retained.snapshot,
361
+ retained.hint, { ...identity, attachmentId }) === true;
362
+ }
363
+
272
364
  // §6/AC-3 + review finding 1: choose the newest VALID canonical snapshot among the verified
273
365
  // HTTP metadata, the retained publication and the already-adopted watermark; bind the verified
274
366
  // durable ID and adopt atomically. Outcomes (callers never guess whether a rejection is the
@@ -395,6 +487,43 @@
395
487
  throw lastError || new Error('Request failed');
396
488
  }
397
489
 
490
+ // A DELETE is owned by its captured root. Aborting it does not undo server-side work.
491
+ async function deleteServerDraft({ id, attachment, options = {}, context, token, retry, isCurrent }) {
492
+ const live = () => Boolean(context) && contexts.get(context.draft) === context
493
+ && current(context) && context.id === id && (typeof isCurrent !== 'function' || isCurrent() === true);
494
+ if (!live()) return false;
495
+ const clientId = String(attachment?.clientId || '');
496
+ const attachmentId = String(attachment?.attachmentId || '');
497
+ const etag = String(attachment?.etag || '');
498
+ const base = '/api/sessions/' + encodeURIComponent(id) + '/image-drafts';
499
+ const requestToken = encodeURIComponent(String(token || ''));
500
+ let url;
501
+ let headers;
502
+ if (!attachmentId) {
503
+ if (!options.cancelRequest || !clientId) return true;
504
+ url = base + '?request_id=' + encodeURIComponent(clientId) + '&purge=1&token=' + requestToken;
505
+ } else {
506
+ url = base + '/' + encodeURIComponent(attachmentId) + '?token=' + requestToken
507
+ + (options.purge === true ? '&purge=1' : '');
508
+ headers = etag ? { 'If-Match': etag } : {};
509
+ }
510
+ if (typeof retry !== 'function') return false;
511
+ try {
512
+ const result = await request(context, async signal => {
513
+ if (!live()) return false;
514
+ const response = await retry(url, { method: 'DELETE', ...(headers ? { headers } : {}), signal },
515
+ { attempts: 3, isCurrent: live });
516
+ if (!response || !live()) return false;
517
+ if (!response.ok && response.status !== 404) throw new Error('HTTP ' + response.status);
518
+ return true;
519
+ });
520
+ return result === true && live();
521
+ } catch (error) {
522
+ if (!live()) return false;
523
+ throw error;
524
+ }
525
+ }
526
+
398
527
  async function completeDelivery({ id, attachment, attemptId, outcome, reason, providerOrdinal, diagnostics,
399
528
  context, token, getToken, fetchFn, retry }) {
400
529
  const isCurrent = () => attachmentCurrent(context, id, context && context.draft, attachment);
@@ -528,6 +657,10 @@
528
657
  Draft.adoptServerSnapshot(draft, existing.clientId, item, null, identity);
529
658
  if (existing._serverSnapshotInvalid || existing._serverSnapshotConflict
530
659
  || existing._serverIdentityMismatch || existing._serverSnapshotStale !== undefined) continue;
660
+ // B9: a publication that arrived before this restore gave the attachment a row is
661
+ // NEWER server truth for the same durable id, so it is adopted before the row is
662
+ // decorated/published rather than being lost to the ordering.
663
+ applyUnmatchedPublication(context, existing, identity);
531
664
  const rawPreview = existing.canonicalPreviewUrl;
532
665
  if (typeof rawPreview === 'string') existing.previewUrl = previewUrlFor({ previewUrl: rawPreview });
533
666
  Draft.reactivateWriteProof(existing, { ...identity, deliveryAttemptId: String(existing.deliveryAttemptId || '') });
@@ -541,6 +674,9 @@
541
674
  Draft.removeAttachment(draft, added.clientId);
542
675
  continue;
543
676
  }
677
+ // The restore is the ONLY writer of a `server_` row, and the row now exists: this is
678
+ // where a publication retained while it was missing is consumed (B9).
679
+ applyUnmatchedPublication(context, added, identity);
544
680
  added.previewUrl = previewUrlFor({ previewUrl: added.canonicalPreviewUrl });
545
681
  byServerId.set(serverId, added);
546
682
  if (added.status === 'ready' && item.state === 'ready') attachAfterRestore.push(added);
@@ -595,9 +731,10 @@
595
731
  }
596
732
 
597
733
  return Object.freeze({ capture, createDraft, current, attachmentCurrent, retire: dispose, request,
598
- fetchWithRetry, completeDelivery, readDraftImage, schedule, clear, mergeServerSnapshot,
734
+ fetchWithRetry, completeDelivery, readDraftImage, schedule, clear, mergeServerSnapshot, deleteServerDraft,
599
735
  registerRequest, releaseRequest, reserveImageUpload, imageUploadReservation,
600
- retainImagePublication, finishImageUpload, resetImageHints, disposeImageUpload, imageUploadCount });
736
+ retainImagePublication, finishImageUpload, resetImageHints, disposeImageUpload, imageUploadCount,
737
+ retainUnmatchedPublication, applyUnmatchedPublication, unmatchedPublicationCount });
601
738
  }
602
739
 
603
740
  return Object.freeze({ createRootDraftLifetime });
@@ -17,6 +17,14 @@
17
17
  const leaseRegistries = new WeakMap();
18
18
  const cleanupFailures = [];
19
19
  let droppedCleanupFailures = 0;
20
+ // WP-1 (C4, AC-1d): exactly ONE replacement gesture per session at a time. Both the
21
+ // row's ↻ and the tray's recovery reach resendItem, and a double click runs it twice
22
+ // before either await returns. The second run must be refused HERE -- before the
23
+ // async verify and before the ID deletion below -- because sendQueueFromPanel's
24
+ // _qpManualSendInFlight is only consulted after the old identity is already gone, so
25
+ // it cannot stop the second gesture from minting a second replacement. A lock whose
26
+ // owner has retired or gone stale is reclaimed instead of blocking forever.
27
+ let resendGesture = null;
20
28
 
21
29
  function rememberCleanupFailure(phase) {
22
30
  if (cleanupFailures.length < 8) cleanupFailures.push(phase);
@@ -482,6 +490,13 @@
482
490
  });
483
491
  if (!captured) return false;
484
492
  const { view, action } = captured;
493
+ if (resendGesture && resendGesture.sessionId === sessionId && current(resendGesture.action)) {
494
+ finish(action);
495
+ finish(view);
496
+ return false;
497
+ }
498
+ const gesture = { sessionId, action };
499
+ resendGesture = gesture;
485
500
  try {
486
501
  if (!await verify(action) || !current(action) || action.loadingAtClick === true
487
502
  || action.visibleSessionAtClick !== sessionId || !Array.isArray(action.items)
@@ -500,6 +515,7 @@
500
515
  if (current(action) && dependencies.getVisibleSession?.() === sessionId) dependencies.onError?.();
501
516
  return false;
502
517
  } finally {
518
+ if (resendGesture === gesture) resendGesture = null;
503
519
  finish(action);
504
520
  finish(view);
505
521
  }
@@ -24,12 +24,14 @@
24
24
  // first row in the transcript, never for what gets painted. R-11: a turn-end STATUS row (`✻ Worked for 18s · done 4:35 PM`)
25
25
  // carries NO words at all -- its verb is one Claude picks at random (`Worked`, `Brewed`, ...) and its elapsed seconds are the
26
26
  // terminal's own clock (observed live `16s` against the transcript's `17s`), so the transcript cannot hold them; only a row
27
- // that is ENTIRELY that shape is dropped, and every other row keeps its words.
27
+ // that is ENTIRELY that shape is dropped, and every other row keeps its words. A blockquote's bar is decoration too: Claude's
28
+ // live screen draws it `▎` (U+258E) where the transcript's own rendering draws `│` (observed 2026-10-24: a kept `▎` made the
29
+ // quoted rows owe a word the transcript never holds, so the proof refused and 21 live rows stayed duplicated as an overlap).
28
30
  function cutWordsOf(text) {
29
31
  const plain = stripForCompare(text);
30
32
  if (/^\s*[^\s\p{L}\p{N}]\s+\p{L}+\s+for\s+(?:\d+[hms]\s*)+(?:·\s*done\s+\d{1,2}:\d{2}\s*[AP]M)?\s*$/u.test(plain)) return [];
31
33
  return plain
32
- .replace(/[|\u2500\u2501\u2502\u250c\u2510\u2514\u2518\u251c\u2524\u252c\u2534\u253c\u2550\u2026]/g, ' ')
34
+ .replace(/[|\u2500\u2501\u2502\u250c\u2510\u2514\u2518\u251c\u2524\u252c\u2534\u253c\u2550\u2026\u258e]/g, ' ')
33
35
  .split(/\s+/)
34
36
  .flatMap((token) => token.split(/(?<=[\p{L}\p{N}]-)(?=[\p{L}\p{N}])/u))
35
37
  .filter(Boolean);
@@ -801,6 +801,7 @@
801
801
  }
802
802
 
803
803
  function leadNote(agent) {
804
+ if (agent === 'claude') return 'Same Claude session: your history continues here, and the next completed turn confirms this model.';
804
805
  return 'History moves only after source and target compatibility is verified. A native fork uses a new provider thread; the next completed root turn confirms the target.';
805
806
  }
806
807
 
@@ -1497,7 +1498,6 @@
1497
1498
  if (typeof row.data.context_window === 'number' && row.data.context_window > 0) {
1498
1499
  pane.appendChild(textLine('walle-model-effort-note', 'Context window: ' + row.data.context_window.toLocaleString('en-US') + ' tokens'));
1499
1500
  }
1500
- pane.appendChild(textLine('walle-model-effort-note', 'History is carried forward only after source and target compatibility is verified. A native fork uses a new provider thread.'));
1501
1501
  // R-6: the same Route line the Delegate pane has — the routed row's route is choosable.
1502
1502
  appendRouteLine(pane, row.data, { onRoutes: lead && lead.onRoutes });
1503
1503
  } else {
@@ -105,6 +105,18 @@
105
105
  return normalizedValue ? normalizedValue.split(/\s+/).filter(Boolean) : [];
106
106
  }
107
107
 
108
+ // D-1/D-3 of the separator-ranking design (docs/session-search-separator-
109
+ // ranking-2026-10-24.html §6): slash and backslash are component boundaries
110
+ // for the non-exact evidence grades, so "resume/hung" contributes whole-word
111
+ // evidence for "resume" instead of one stem-exempt token. Hyphens,
112
+ // underscores, dots and digits stay inside a component. Contract: ordered
113
+ // string[], duplicates and order retained, no empty strings. The raw
114
+ // whitespace representation above is deliberately NOT changed -- it still
115
+ // decides the exact / stem-exact / legacy-command branches.
116
+ function titleComponentTokens(normalizedValue) {
117
+ return String(normalizedValue || '').split(/[\s/\\]+/u).filter(Boolean);
118
+ }
119
+
108
120
  const TITLE_GRADE_NONE_RESULT = Object.freeze({
109
121
  grade: TITLE_GRADE.NONE,
110
122
  rank: TITLE_GRADE_RANK.none,
@@ -171,48 +183,63 @@
171
183
  };
172
184
  }
173
185
 
186
+ // D-1/D-3: only after both raw equality branches have failed do the
187
+ // evidence grades run, and they run on separator components (the same
188
+ // helper for title and query) rather than on the raw whitespace tokens.
189
+ // Component arrays keep repetition and order; the score denominator is the
190
+ // component count only, never raw tokens plus components.
191
+ const titleComponents = titleComponentTokens(normalizedTitle);
192
+ const queryComponents = titleComponentTokens(normalizedQuery);
193
+ if (!titleComponents.length || !queryComponents.length) return TITLE_GRADE_NONE_RESULT;
194
+
195
+ const componentCount = titleComponents.length;
196
+ const queryComponentCount = queryComponents.length;
197
+ const titleComponentForms = titleComponents.map(titleTokenForm);
198
+ const queryComponentForms = queryComponents.map(titleTokenForm);
199
+
174
200
  const minPrefixLen = TITLE_SCORE_WEIGHTS.MIN_PREFIX_LEN;
175
- const wordHit = (queryForm) => titleForms.includes(queryForm);
176
- const prefixHit = (queryTok) => queryTok.length >= minPrefixLen && titleToks.some(t => t.startsWith(queryTok));
177
- const containsHit = (queryTok) => titleToks.some(t => t.includes(queryTok));
201
+ const wordHit = (queryForm) => titleComponentForms.includes(queryForm);
202
+ const prefixHit = (queryTok) => queryTok.length >= minPrefixLen
203
+ && titleComponents.some(t => t.startsWith(queryTok));
204
+ const containsHit = (queryTok) => titleComponents.some(t => t.includes(queryTok));
178
205
 
179
- const wordAll = queryForms.every(wordHit);
180
- const prefixAll = !wordAll && queryToks.every(prefixHit);
181
- const containsAll = !wordAll && !prefixAll && queryToks.every(containsHit);
206
+ const wordAll = queryComponentForms.every(wordHit);
207
+ const prefixAll = !wordAll && queryComponents.every(prefixHit);
208
+ const containsAll = !wordAll && !prefixAll && queryComponents.every(containsHit);
182
209
 
183
210
  let grade;
184
211
  let matchTest;
185
212
  if (wordAll) {
186
213
  grade = TITLE_GRADE.WORD;
187
- matchTest = (token, form) => queryForms.includes(form);
214
+ matchTest = (token, form) => queryComponentForms.includes(form);
188
215
  } else if (prefixAll) {
189
216
  grade = TITLE_GRADE.PREFIX;
190
- matchTest = (token) => queryToks.some(q => q.length >= minPrefixLen && token.startsWith(q));
217
+ matchTest = (token) => queryComponents.some(q => q.length >= minPrefixLen && token.startsWith(q));
191
218
  } else if (containsAll) {
192
219
  grade = TITLE_GRADE.CONTAINS;
193
- matchTest = (token) => queryToks.some(q => token.includes(q));
220
+ matchTest = (token) => queryComponents.some(q => token.includes(q));
194
221
  } else {
195
- const anyPartial = queryToks.some((queryTok, index) => (
196
- wordHit(queryForms[index]) || prefixHit(queryTok) || containsHit(queryTok)
222
+ const anyPartial = queryComponents.some((queryTok, index) => (
223
+ wordHit(queryComponentForms[index]) || prefixHit(queryTok) || containsHit(queryTok)
197
224
  ));
198
225
  grade = anyPartial ? TITLE_GRADE.PARTIAL : TITLE_GRADE.NONE;
199
- matchTest = (token, form) => queryForms.includes(form)
200
- || queryToks.some(q => (q.length >= minPrefixLen && token.startsWith(q)) || token.includes(q));
226
+ matchTest = (token, form) => queryComponentForms.includes(form)
227
+ || queryComponents.some(q => (q.length >= minPrefixLen && token.startsWith(q)) || token.includes(q));
201
228
  }
202
229
 
203
230
  let matchedTitleTokens = 0;
204
231
  let firstMatchedIndex = -1;
205
232
  if (grade !== TITLE_GRADE.NONE) {
206
- titleToks.forEach((token, index) => {
207
- if (matchTest(token, titleForms[index])) {
233
+ titleComponents.forEach((token, index) => {
234
+ if (matchTest(token, titleComponentForms[index])) {
208
235
  matchedTitleTokens += 1;
209
236
  if (firstMatchedIndex === -1) firstMatchedIndex = index;
210
237
  }
211
238
  });
212
239
  }
213
240
 
214
- const coverage = titleTokenCount ? matchedTitleTokens / titleTokenCount : 0;
215
- const position = firstMatchedIndex === -1 ? 0 : 1 - (firstMatchedIndex / titleTokenCount);
241
+ const coverage = componentCount ? matchedTitleTokens / componentCount : 0;
242
+ const position = firstMatchedIndex === -1 ? 0 : 1 - (firstMatchedIndex / componentCount);
216
243
  const score = TITLE_GRADE_BASE[grade] + TITLE_SCORE_WEIGHTS.COVERAGE * coverage + TITLE_SCORE_WEIGHTS.POSITION * position;
217
244
 
218
245
  return {
@@ -222,8 +249,8 @@
222
249
  position,
223
250
  score,
224
251
  matchedTitleTokens,
225
- titleTokens: titleTokenCount,
226
- queryTokens: queryTokenCount,
252
+ titleTokens: componentCount,
253
+ queryTokens: queryComponentCount,
227
254
  };
228
255
  }
229
256
 
@@ -125,6 +125,10 @@
125
125
  if (!ok) return { painted: null, invalid: true };
126
126
  const base = { epoch: p.epoch, top: p.top, pinned: p.pinned, floor: p.floor, revision: p.revision, breaks: Object.freeze(breaks.map((b) => Object.freeze({ at: b.at, reason: b.reason }))), below: Number.isSafeInteger(p.below) && p.below > 0 ? p.below : 0 }; // R-F2: rows the app holds below its screen; an older host omits it (0), and it is never a rejection reason
127
127
  if (p.revisedFrom !== undefined) base.revisedFrom = p.revisedFrom;
128
+ // Clock-free tests (WP-6): the recorder's observe count and its uncommitted hold pass through (HistoryPaintedRows publishes them);
129
+ // an older host sends neither, and a malformed value is dropped, never a rejection reason.
130
+ if (integer(p.samples) && p.samples >= 0) base.samples = p.samples;
131
+ if (p.hold && typeof p.hold === 'object' && integer(p.hold.rows)) base.hold = Object.freeze({ kind: typeof p.hold.kind === 'string' ? p.hold.kind : null, rows: p.hold.rows, top: integer(p.hold.top) ? p.hold.top : null });
128
132
  return { painted: Object.freeze(withRows ? { ...base, from: p.from, rows: Object.freeze(p.rows.slice()) } : base), invalid: false };
129
133
  }
130
134