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
@@ -94,7 +94,11 @@ function _projectSessionParked(s, options) {
94
94
  ? `${idleMin ? 'Paused after ' + idleMin + ' minutes of inactivity. ' : 'Paused after inactivity. '}Your conversation and working directory are preserved.`
95
95
  : reason === 'cap' || reason === 'prewarm-cap' || reason === 'interactive-preempt'
96
96
  ? 'Paused to keep the live-session limit available. Your conversation and working directory are preserved.'
97
- : 'Paused to relieve system memory pressure. Your conversation and working directory are preserved.';
97
+ // Hub WP-6: a box-owned session the hub cannot reach shows its last known state; the box
98
+ // still owns (and may still be running) it.
99
+ : reason === 'remote-mirror'
100
+ ? `Runs on ${String(s.meta?.machineName || s.meta?.machine_name || '') || 'another machine'}, which is not reachable right now. This is its last known state; it updates when that machine is back.`
101
+ : 'Paused to relieve system memory pressure. Your conversation and working directory are preserved.';
98
102
  _projectSessionLifecycle(s, 'parked', { title: 'Session parked', detail, blocked });
99
103
  }
100
104
 
@@ -172,6 +172,11 @@
172
172
  // D-2 (transcript-history-fidelity WP-0): carry the server's human-readable refusal
173
173
  // cause alongside the code, so the toast can say WHY, not just which code refused.
174
174
  err.reason = (body && body.reason) || '';
175
+ // source-missing diagnosis: the raw-Codex lane reports WHY in `coverage.reason`/`meta.reason`
176
+ // (`base(state, generation, reason)` in lib/raw-codex-history-window.js), never in a top-level
177
+ // `reason`, so that cause used to reach the entry-failure beacon as nothing at all. Carried on
178
+ // its OWN field: `err.reason` drives the toast copy and stays exactly as it is.
179
+ err.coverageReason = String((body && ((body.coverage && body.coverage.reason) || (body.meta && body.meta.reason))) || '');
175
180
  throw err;
176
181
  }
177
182
  const Pages = historyTranscriptPages();
@@ -215,12 +220,12 @@
215
220
  if (c._continuousSegment && index === 0) {
216
221
  if (body.newerCursor) return rebaseSeamPage(c, rows.length);
217
222
  landSeamPage(c, body, { reason: (opts && opts.reason) || 'refetch', cursor: opts && opts.cursor }); // D-19: a seam refresh lands as 'refresh'
218
- if (direction === 'older' && (done || stuck)) c._transcriptDone = true;
223
+ if (direction === 'older' && (done || stuck)) { c._transcriptDone = true; if (stuck) c._transcriptModel.reachedStart = true; }
219
224
  return { ok: true, rows: rows.length, bytes: c._transcriptModel.bytes.get(index) || 0, shift: documentRows(c) - before };
220
225
  }
221
226
  const snap = hs.captureAnchor(c);
222
227
  Pages.insertPage(model, index, body);
223
- if (direction === 'older' && (done || stuck)) c._transcriptDone = true;
228
+ if (direction === 'older' && (done || stuck)) { c._transcriptDone = true; if (stuck) model.reachedStart = true; }
224
229
  const target = hs.anchoredScrollTop(c, snap);
225
230
  const win = hs.neededRowWindow(c, target);
226
231
  // Eviction floor = the fetch window itself (viewport +/- 1 pane is inside it): a page the
@@ -1183,13 +1188,27 @@
1183
1188
  }
1184
1189
 
1185
1190
  // One retry timer at most (a no-op while pending).
1191
+ // Clock-free tests (D-2/D-4, WP-6): the ladder's own progress is readable from the page -- `window.TerminalHistoryViewport.get(id)`
1192
+ // is this `c` -- so a test waits on the loop's state instead of sleeping through a window at least as long as its period:
1193
+ // _seamRetryAttempts -- monotone: +1 per step whose timer FIRED (the loop's attempt to re-ask). Counted before the ask is
1194
+ // tried, so a step that skips the ask (a request is in flight, or the reader is gone) still counts as
1195
+ // an attempt; `_seamRefreshes` counts the requests that actually started (null/undefined before the
1196
+ // first landing that armed the ladder).
1197
+ // _seamRetryScheduled -- true while a step's timer is pending, false once it has fired or been cleared (also false/undefined
1198
+ // with no ladder).
1199
+ // _seamRetryStopped -- true once resetSeamFollow ran: a landing proved or overlapped the join (or the reader exited), so no
1200
+ // further attempt will be made. Set false again only by a landing that is still `none` and re-arms it.
1186
1201
  function armSeamBehindRetry(c) {
1187
1202
  const step = Number(c._seamBehindStep) || 0;
1188
1203
  if (c._seamRetryTimer) return;
1189
1204
  c._seamBehindStep = Math.min(step + 1, SEAM_BEHIND_RETRY_MS.length);
1205
+ c._seamRetryAttempts = Number(c._seamRetryAttempts) || 0; // a number from the first arm on, so a reader never has to tell 0 from "no ladder"
1206
+ c._seamRetryScheduled = true; c._seamRetryStopped = false;
1190
1207
  /* deadline-exempt: bounded transcript-behind retry (SEAM_BEHIND_RETRY_MS: 1, 2, 4 s, then 10 s) that re-asks for page 0; cleared by a landing that proves or overlaps and on exit by resetSeamFollow; not a recovery/activation deadline */
1191
1208
  c._seamRetryTimer = setTimeout(() => {
1192
1209
  c._seamRetryTimer = null;
1210
+ c._seamRetryScheduled = false;
1211
+ c._seamRetryAttempts = (Number(c._seamRetryAttempts) || 0) + 1; // the step fired: this is the attempt, whatever the ask below does
1193
1212
  if (!c.active || !c._hs) return;
1194
1213
  if (!refreshSeamPage(c)) armSeamBehindRetry(c); // a request is in flight: look again next step
1195
1214
  }, SEAM_BEHIND_RETRY_MS[Math.min(step, SEAM_BEHIND_RETRY_MS.length - 1)]);
@@ -1199,6 +1218,8 @@
1199
1218
  function resetSeamFollow(c) {
1200
1219
  if (c._seamRetryTimer) { clearTimeout(c._seamRetryTimer); c._seamRetryTimer = null; }
1201
1220
  c._seamBehindStep = 0;
1221
+ c._seamRetryScheduled = false;
1222
+ c._seamRetryStopped = true; // D-18/WP-6: a landing that proved or overlapped, or an exit: the ladder is stopped for good
1202
1223
  }
1203
1224
 
1204
1225
  // D-17: a page 0 that still has a newer neighbour is not the transcript's tail (the file grew past
@@ -1344,6 +1365,9 @@
1344
1365
  const err = new Error(code);
1345
1366
  err.code = code;
1346
1367
  err.reason = (body && body.reason) || '';
1368
+ // Same as applyTranscriptWindowBody: the server's own WHY (coverage.reason / meta.reason),
1369
+ // kept on its own field so the entry-failure beacon can report it without touching the toast.
1370
+ err.coverageReason = String((body && ((body.coverage && body.coverage.reason) || (body.meta && body.meta.reason))) || '');
1347
1371
  return failEntry(err);
1348
1372
  }
1349
1373
  if (!Array.isArray(body.rows) || body.rows.length === 0) {
@@ -79,6 +79,21 @@
79
79
  }
80
80
  }
81
81
 
82
+ // Clock-free tests (D-2/D-4, WP-6): the recorder's sample counter and the samples it is holding uncommitted travel next to
83
+ // `revision` on the frame's painted field, so a test waits on them instead of sleeping a window. `samples` is the recorder's
84
+ // monotone observe count (it moves on EVERY sample, including one that changes nothing). The frame carries the recorder's `hold`
85
+ // descriptor ({ kind, rows, top }: the samples it rejected and did not commit, null when it holds none -- the same fact as the
86
+ // recorder's boolean `held`, which does not travel on the wire); this store publishes it as `held`, its one store, so `held` here
87
+ // is unambiguously the descriptor. An older host (and today's /painted-rows route) sends neither: both read as null.
88
+ function observedOf(painted) {
89
+ const samples = Number.isSafeInteger(painted.samples) && painted.samples >= 0 ? painted.samples : null;
90
+ const h = painted.hold;
91
+ const held = h && typeof h === 'object'
92
+ ? { kind: typeof h.kind === 'string' ? h.kind : null, rows: Number.isSafeInteger(h.rows) && h.rows >= 0 ? h.rows : 0, top: Number.isSafeInteger(h.top) ? h.top : null }
93
+ : null;
94
+ return { samples, held };
95
+ }
96
+
82
97
  // `painted`: the validated frame field { epoch, top, pinned, floor, revision, breaks[, revisedFrom][, from, rows] }, or null (an older
83
98
  // host, a normal-buffer surface: no painted state; the rows already held stay until a state with another epoch or revision arrives).
84
99
  function noteFrame(session, painted) {
@@ -95,7 +110,7 @@
95
110
  else if (painted.revisedFrom !== undefined) dropFrom(store, painted.revisedFrom);
96
111
  else if (revision > store.revision) clearRows(store); // a keyframe or a missed delta: the rows revised are unknown
97
112
  store.revision = revision;
98
- store.state = { epoch: painted.epoch, top: painted.top, pinned: painted.pinned, floor: painted.floor, revision, breaks: painted.breaks || [], below: Number.isSafeInteger(painted.below) && painted.below > 0 ? painted.below : 0 }; // R-F2: rows the app holds below its screen; an older host sends none, and that is 0
113
+ store.state = { epoch: painted.epoch, top: painted.top, pinned: painted.pinned, floor: painted.floor, revision, breaks: painted.breaks || [], below: Number.isSafeInteger(painted.below) && painted.below > 0 ? painted.below : 0, ...observedOf(painted) }; // R-F2: rows the app holds below its screen; an older host sends none, and that is 0
99
114
  if (Array.isArray(painted.rows) && Number.isSafeInteger(painted.from)) putRows(store, painted.from, painted.rows);
100
115
  trim(store);
101
116
  }
@@ -106,7 +121,7 @@
106
121
  function stateOf(sessionId) {
107
122
  const store = stores.get(String(sessionId));
108
123
  const st = store && store.state;
109
- return st ? { epoch: st.epoch, top: st.top, pinned: st.pinned, floor: st.floor, revision: st.revision, breaks: st.breaks.map((b) => ({ at: b.at, reason: b.reason })), below: Number.isSafeInteger(st.below) ? st.below : 0 } : null;
124
+ return st ? { epoch: st.epoch, top: st.top, pinned: st.pinned, floor: st.floor, revision: st.revision, breaks: st.breaks.map((b) => ({ at: b.at, reason: b.reason })), below: Number.isSafeInteger(st.below) ? st.below : 0, samples: Number.isSafeInteger(st.samples) && st.samples >= 0 ? st.samples : null, held: st.held || null } : null;
110
125
  }
111
126
 
112
127
  // Rows [from, to) as stored; `undefined` where a row is not resident.
@@ -178,7 +178,19 @@
178
178
  c._renderTranscript();
179
179
  const rh = Number(c.rowHeight) > 0 ? Number(c.rowHeight) : 17;
180
180
  const offset = Pages.offsetOfPage(c._transcriptModel, index) + (Number(bookmark.rowOffsetInPage) || 0);
181
- if (c.scrollEl) c.scrollEl.scrollTop = offset * rh; // WP-2: one coordinate for every source (the native scroller included)
181
+ // reader-scroll-write beacon: a resume that lands more than one pane from where the reader was is a
182
+ // movement nobody scrolled, so it names itself (`resume`). The write is the same one it was.
183
+ if (c.scrollEl) {
184
+ const target = offset * rh; // WP-2: one coordinate for every source (the native scroller included)
185
+ const hs = scroller(); // resolved at call time, like resumePaintedRow's
186
+ const before = Number(c.scrollEl.scrollTop) || 0;
187
+ c.scrollEl.scrollTop = target;
188
+ const after = Number(c.scrollEl.scrollTop) || 0;
189
+ if (c._hs) c._hs.lastTop = after;
190
+ if (hs && typeof hs.noteBigScroll === 'function' && Math.abs(after - before) > (Number(c.scrollEl.clientHeight) || 0)) {
191
+ hs.noteBigScroll(c, 'resume', before, after, target);
192
+ }
193
+ }
182
194
  return true;
183
195
  }
184
196
 
@@ -126,10 +126,7 @@
126
126
  function setSpacerHeightAnchored(c, nextHeightPx, deltaAbovePx) {
127
127
  if (c && c.spacerEl) c.spacerEl.style.height = (Number(nextHeightPx) || 0) + 'px';
128
128
  const delta = Number(deltaAbovePx) || 0;
129
- if (delta && c && c.scrollEl) {
130
- c.scrollEl.scrollTop = (Number(c.scrollEl.scrollTop) || 0) + delta;
131
- if (c._hs) c._hs.lastTop = Number(c.scrollEl.scrollTop) || 0;
132
- }
129
+ if (delta && c && c.scrollEl) writeScrollTop(c, 'spacer', (Number(c.scrollEl.scrollTop) || 0) + delta);
133
130
  }
134
131
 
135
132
  // D-3 (unified-scrollbar track wheel, keys): deltaMode 0 = px, 1 = lines (x row height),
@@ -144,11 +141,166 @@
144
141
  const scroller = c && c.scrollEl;
145
142
  if (!scroller) return false;
146
143
  const top = deltaPx(c, deltaY, deltaMode);
144
+ const before = Number(scroller.scrollTop) || 0;
147
145
  if (typeof scroller.scrollBy === 'function') scroller.scrollBy({ top });
148
- else scroller.scrollTop = (Number(scroller.scrollTop) || 0) + top;
146
+ else scroller.scrollTop = before + top;
147
+ const after = Number(scroller.scrollTop) || 0;
148
+ noteBigScrollIfPane(c, 'scrollBy', before, after, after); // scrollBy clamps the delta itself: there is no separate target to report
149
149
  return true;
150
150
  }
151
151
 
152
+ // ---------------------------------------------------------------------------
153
+ // reader-scroll-write (2026-10-25 incident: a light wheel-up jumped the reader ~5,800 rows to the
154
+ // top of its document and the D-2 chain then fetched 11 pages; telemetry named no write). INSTRUMENT
155
+ // ONLY: every write below is the same write it was before, the only additions are the two beacons
156
+ // `history-reader-scroll-write` (a movement bigger than one pane, with its site) and
157
+ // `history-reader-chain-advance` (a burst of chain advances). Both coalesce into bounded integers --
158
+ // no transcript text, no URLs, no cursors -- and every failure is swallowed: nothing in the scroll,
159
+ // anchor, fetch or park path reads any of this.
160
+ // ---------------------------------------------------------------------------
161
+ const SCROLL_WRITE_EVENT = 'history-reader-scroll-write';
162
+ const CHAIN_ADVANCE_EVENT = 'history-reader-chain-advance';
163
+ /* deadline-exempt: the scroll-write beacon's fixed coalescing window (one row per incident burst), not a recovery/activation deadline */
164
+ const SCROLL_WRITE_COALESCE_MS = 1100;
165
+ const CHAIN_WINDOW_MS = 1000; // a chain-advance burst is this many advances inside this sliding window
166
+ const CHAIN_BURST = 4;
167
+ const CHAIN_STAMP_CAP = 16;
168
+ const SCROLL_WRITE_ENTRY_CAP = 4; // the first four entries name the burst; the rest are counted
169
+ const SCROLL_WRITE_ENTRY_CHARS = 40; // the server keeps 40 chars per entry
170
+ const TARGET_EPSILON_PX = 1; // above this the browser did not land where it was told (it clamped)
171
+
172
+ // The ONE write path: the same scrollTop assignment every instrumented site made before, plus the
173
+ // measurement that names it. `target` is what was asked for, the return value what the browser
174
+ // clamped to (a clamped write is itself the interesting case).
175
+ function writeScrollTop(c, site, target) {
176
+ const scroller = c && c.scrollEl;
177
+ if (!scroller) return 0;
178
+ const before = Number(scroller.scrollTop) || 0;
179
+ scroller.scrollTop = target;
180
+ const after = Number(scroller.scrollTop) || 0;
181
+ if (c._hs) c._hs.lastTop = after; // programmatic: never counted as travel
182
+ noteBigScrollIfPane(c, site, before, after, target);
183
+ return after;
184
+ }
185
+ function noteBigScrollIfPane(c, site, before, after, target) {
186
+ const scroller = c && c.scrollEl;
187
+ if (!scroller) return;
188
+ if (Math.abs(Number(after) - Number(before)) > (Number(scroller.clientHeight) || 0)) noteBigScroll(c, site, before, after, target);
189
+ }
190
+
191
+ function scrollWriteState(c) {
192
+ if (!c._bigScrollDiag) c._bigScrollDiag = { count: 0, entries: [], firstSite: '', lastSite: '', before: 0, after: 0, timer: null };
193
+ return c._bigScrollDiag;
194
+ }
195
+ // `tag.class` (first class only) of whatever holds the keyboard: the one hint a `native` movement cannot
196
+ // otherwise carry. Never an id, a value or any text the element holds.
197
+ function activeElementTagClass() {
198
+ const el = typeof document !== 'undefined' ? document.activeElement : null;
199
+ if (!el) return '';
200
+ const tag = String(el.tagName || '').toLowerCase();
201
+ if (!tag) return '';
202
+ const cls = (typeof el.className === 'string' ? el.className.trim().split(/\s+/)[0] : '') || '';
203
+ return '@' + (cls ? tag + '.' + cls : tag);
204
+ }
205
+
206
+ // Coalesced: the first 4 entries (in order) plus the count and the geometry that places them, one
207
+ // beacon per episode. Called per big write -- never per scroll event.
208
+ function noteBigScroll(c, site, before, after, target) {
209
+ if (!c || typeof c.sendDiag !== 'function') return; // no diag channel: nothing to record, and no timer to arm for it
210
+ try {
211
+ const state = scrollWriteState(c);
212
+ const b = Math.round(Number(before) || 0);
213
+ const a = Math.round(Number(after) || 0);
214
+ let entry = site + ':' + b + '>' + a;
215
+ if (Math.abs((Number(target) || 0) - (Number(after) || 0)) > TARGET_EPSILON_PX) entry += '(t=' + Math.round(Number(target) || 0) + ')';
216
+ if (site === 'native') entry += activeElementTagClass();
217
+ if (state.count === 0) { state.before = b; state.firstSite = site; }
218
+ state.count += 1;
219
+ state.after = a;
220
+ state.lastSite = site;
221
+ if (state.entries.length < SCROLL_WRITE_ENTRY_CAP) state.entries.push(entry.slice(0, SCROLL_WRITE_ENTRY_CHARS));
222
+ if (state.timer == null) state.timer = setTimeout(function () { flushBigScroll(c); }, SCROLL_WRITE_COALESCE_MS);
223
+ } catch { /* expected: diag is best-effort */ }
224
+ }
225
+
226
+ // The geometry every beacon carries, read once at flush time (`_hs` is gone after a park, so restPx/
227
+ // spacerPx/sinceMountMs read 0 then -- the entries still name the movement).
228
+ function scrollDiagFacts(c) {
229
+ const hint = c && c._hs;
230
+ const model = c && c._transcriptModel;
231
+ const Pages = historyTranscriptPages();
232
+ const scroller = c && c.scrollEl;
233
+ let totalRows = 0;
234
+ let oldest = -1;
235
+ if (Pages && model) {
236
+ totalRows = Math.round(Number(Pages.totalRows(model, { separator: true })) || 0);
237
+ const known = Pages.oldestKnownIndex(model);
238
+ if (known !== null && known !== undefined) oldest = known;
239
+ }
240
+ return {
241
+ scrollTopPx: Math.round(Number(scroller && scroller.scrollTop) || 0),
242
+ panePx: Math.round(Number(scroller && scroller.clientHeight) || 0),
243
+ restPx: hint ? Math.round(restOf(c)) : 0,
244
+ spacerPx: hint ? Math.round(spacerHeightOf(c)) : 0,
245
+ totalRows,
246
+ oldestPage: oldest,
247
+ residentPages: model && model.pages ? model.pages.size : 0,
248
+ sinceMountMs: hint && Number(hint.mountedAt) ? Math.max(0, Math.round(Date.now() - Number(hint.mountedAt))) : 0,
249
+ };
250
+ }
251
+
252
+ // Sends the one beacon for the episode just ended and clears it (synchronously, from unmountScroller
253
+ // too, so a jump right before a park/exit is not lost). The state is cleared BEFORE the send: a
254
+ // re-entrant write from inside sendDiag starts a fresh episode rather than extending this one.
255
+ function flushBigScroll(c) {
256
+ const state = c && c._bigScrollDiag;
257
+ if (!state) return;
258
+ if (state.timer != null) clearTimeout(state.timer);
259
+ c._bigScrollDiag = null;
260
+ if (typeof c.sendDiag !== 'function') return;
261
+ try {
262
+ c.sendDiag(Object.assign({
263
+ count: state.count, entries: state.entries, firstSite: state.firstSite, lastSite: state.lastSite,
264
+ before: state.before, after: state.after,
265
+ chainAdv1s: chainAdvancesWithin(c, Date.now()), // was the D-2 chain running while the viewport moved?
266
+ }, scrollDiagFacts(c)), SCROLL_WRITE_EVENT);
267
+ } catch { /* expected: diag is best-effort */ }
268
+ }
269
+
270
+ // The chain advances inside the last CHAIN_WINDOW_MS, as of `nowMs` (pure; Beacon 1 reads it).
271
+ function chainAdvancesWithin(c, nowMs) {
272
+ const stamps = c && Array.isArray(c._chainAdvanceAt) ? c._chainAdvanceAt : [];
273
+ const now = Number(nowMs) || 0;
274
+ let count = 0;
275
+ for (let i = stamps.length - 1; i >= 0; i -= 1) {
276
+ if (now - (Number(stamps[i]) || 0) > CHAIN_WINDOW_MS) break;
277
+ count += 1;
278
+ }
279
+ return count;
280
+ }
281
+
282
+ // Beacon 2: one chain advance started a fetch (the stamps are a sliding window, so an old burst never
283
+ // inflates a new one). The fourth inside the window reports once; the flag clears when the window has
284
+ // fully aged out, so a later burst reports again. No change to WHEN the chain advances.
285
+ function noteChainAdvance(c, where) {
286
+ if (!c) return;
287
+ try {
288
+ const now = Date.now();
289
+ const kept = (Array.isArray(c._chainAdvanceAt) ? c._chainAdvanceAt : []).filter((at) => now - (Number(at) || 0) <= CHAIN_WINDOW_MS);
290
+ if (kept.length === 0) c._chainAdvanceBurstSent = false; // the previous burst's window has cleared
291
+ kept.push(now);
292
+ c._chainAdvanceAt = kept.slice(-CHAIN_STAMP_CAP);
293
+ if (kept.length < CHAIN_BURST || c._chainAdvanceBurstSent || typeof c.sendDiag !== 'function') return;
294
+ c._chainAdvanceBurstSent = true; // set before the send: a throwing sendDiag must not re-report
295
+ c.sendDiag(Object.assign({
296
+ count: kept.length,
297
+ topRow: Math.round(Number(where && where.topRow) || 0),
298
+ oldestPage: where && where.oldestPage !== null && where.oldestPage !== undefined ? where.oldestPage : -1,
299
+ oldestFrom: Math.round(Number(where && where.oldestFrom) || 0),
300
+ }, scrollDiagFacts(c)), CHAIN_ADVANCE_EVENT);
301
+ } catch { /* expected: diag is best-effort */ }
302
+ }
303
+
152
304
  function liveRowsOf(c) {
153
305
  const live = c && c.getLive && c.getLive();
154
306
  return Math.max(1, Number(live && live.term && live.term.rows) || 1);
@@ -208,8 +360,9 @@
208
360
  function applyAnchor(c, target) {
209
361
  const scroller = c && c.scrollEl;
210
362
  if (!scroller) return false;
211
- if (Math.abs((Number(scroller.scrollTop) || 0) - target) > 0.01) scroller.scrollTop = target;
212
- if (c._hs) c._hs.lastTop = Number(scroller.scrollTop) || 0; // programmatic: never counted as travel
363
+ // The 0.01 px guard is the write-skip that was always here: a sub-pixel "move" is not a write at all.
364
+ if (Math.abs((Number(scroller.scrollTop) || 0) - target) > 0.01) writeScrollTop(c, 'anchor', target);
365
+ else if (c._hs) c._hs.lastTop = Number(scroller.scrollTop) || 0; // programmatic: never counted as travel
213
366
  return true;
214
367
  }
215
368
 
@@ -304,6 +457,24 @@
304
457
  seam.fetchTranscriptPage(c, index, plan.cursor, plan.direction, { attempt: 1, maxRows: plan.maxRows });
305
458
  slots -= 1; started += 1;
306
459
  }
460
+ // D-2: the window lies above what the chain can serve (`pagesForRows` named only "virtual" indices past
461
+ // `oldest + 1`, whose plan is null by construction; nothing landed, so no scroll event follows either --
462
+ // the anchor holds the viewport's distance invariant). Advance the chain one page toward the window; each
463
+ // landing schedules the next frame, and `planPageChain` answers null once reachedStart/_transcriptDone is set.
464
+ if (started === 0 && slots > 0) {
465
+ const oldest = Pages.oldestKnownIndex(model);
466
+ const range = oldest === null ? null : Pages.rangeOfPage(model, oldest);
467
+ const next = oldest === null ? null : oldest + 1;
468
+ if (range && win.topRow < range.from && !model.pages.has(next) && !c._transcriptAborters.has(next) && !seam.pageRetryPending(c, next)) {
469
+ const plan = seam.planPageFetch(c, next);
470
+ if (plan) {
471
+ seam.clearFailedPage(c, next);
472
+ seam.fetchTranscriptPage(c, next, plan.cursor, plan.direction, { attempt: 1, maxRows: plan.maxRows });
473
+ noteChainAdvance(c, { topRow: win.topRow, oldestPage: oldest, oldestFrom: range.from }); // the burst beacon; the advance itself is unchanged
474
+ slots -= 1; started += 1;
475
+ }
476
+ }
477
+ }
307
478
  return started + fetchPaintedHoles(c, win, viewTop, slots);
308
479
  }
309
480
 
@@ -457,7 +628,7 @@
457
628
  const rh = rowHeightOf(c);
458
629
  if (e.key === 'Home') {
459
630
  e.preventDefault();
460
- if (typeof scroller.scrollTo === 'function') scroller.scrollTo({ top: 0 }); else scroller.scrollTop = 0;
631
+ writeScrollTop(c, 'home', 0); // the reader spells Home the same way it wrote it before (`scrollTo({top:0})` on a scroller whose scroll-behavior is the default `auto`)
461
632
  return true;
462
633
  }
463
634
  const by = { PageUp: -page, PageDown: page, ArrowUp: -rh, ArrowDown: rh }[e.key];
@@ -543,6 +714,8 @@
543
714
  };
544
715
  c._hs = state;
545
716
  c._skeletonMs = 0; c._skelSince = 0; c._travelPx = 0; c._winPx = null; c._lastFetchTop = undefined; c._seamRefreshes = 0; // WP-3 D-15 / D-20 counters, per open
717
+ c._bigScrollDiag = null; c._chainAdvanceAt = []; c._chainAdvanceBurstSent = false; // the two beacons' state is per open
718
+ state.mountedAt = Date.now(); // sinceMountMs places a jump in the open's own timeline
546
719
  c._seamBehindStep = 0; // D-19: the transcript-behind retry ladder, per open
547
720
  try {
548
721
  const viewportModuleRef = viewportModule();
@@ -594,6 +767,9 @@
594
767
  state.onScroll = function onReaderScroll() {
595
768
  const top = Number(scroller.scrollTop) || 0;
596
769
  c._travelPx = (Number(c._travelPx) || 0) + Math.abs(top - state.lastTop); // D-15 travelPx
770
+ // Every instrumented write sets lastTop itself, so a movement past one pane here is one none of
771
+ // them made: the browser moved the scroller (wheel, focus, CSS anchoring, a reflow).
772
+ noteBigScrollIfPane(c, 'native', state.lastTop, top, top);
597
773
  state.lastTop = top;
598
774
  if (isAtRest(c)) { exitReader(c, 'scroll'); return; } // D-5: the only scroll-driven exit
599
775
  syncSkeleton(c);
@@ -615,7 +791,7 @@
615
791
  paintSkeletonTile(c);
616
792
  applyClip(c); // the pinned rows are a count of rows: their px follow the row height
617
793
  c._renderTranscript();
618
- scroller.scrollTop = next.scrollTop;
794
+ writeScrollTop(c, 'remeasure', next.scrollTop);
619
795
  });
620
796
  c.ro.observe(scroller);
621
797
  }
@@ -632,6 +808,7 @@
632
808
  function unmountScroller(c) {
633
809
  const state = c && c._hs;
634
810
  if (!state) return false;
811
+ flushBigScroll(c); // a jump right before a park/exit is not lost; the episode's timer is cleared here, never left pending
635
812
  if (c._skelSince) { c._skeletonMs = (Number(c._skeletonMs) || 0) + Date.now() - c._skelSince; c._skelSince = 0; } // D-15: kept on `c` for the exit beacon
636
813
  c._hs = null;
637
814
  cancelFetchAhead(c);
@@ -657,6 +834,7 @@
657
834
  return Object.freeze({
658
835
  ensureTermScroller, mountScroller, unmountScroller, isMounted, isMountedFor, isAtRest, clipPx, restOf, applyClip, commitPainted,
659
836
  setSpacerHeightAnchored, scrollByPx, neededRowWindow, scheduleFetchAhead, scheduleRender, cancelRender, scheduleFrame, cancelFrame, RENDER_FALLBACK_MS, runFetchAhead, liveWheelVeto,
837
+ noteBigScroll, chainAdvancesWithin, // reader-scroll-write beacons: the park/resume path and the tests call these
660
838
  handleKeydown, typedKeyData, leaveAndType, captureAnchor, anchoredScrollTop, applyAnchor, refreshLiveRows, syncSkeleton, skeletonMs, REST_EPSILON, MAX_IN_FLIGHT,
661
839
  });
662
840
  });
@@ -313,10 +313,11 @@
313
313
  return Math.min(MAX_ESTIMATE_ROWS, Math.max(live, est));
314
314
  }
315
315
 
316
+ // D-8/R-2 (WP-2): the OLDEST known page index in `rowCounts` (index k+1 is older than k), resident or not, or null while nothing is known.
317
+ const oldestKnownIndex = (model) => { let oldest = null; for (const index of model.rowCounts.keys()) if (oldest === null || index > oldest) oldest = index; return oldest; };
316
318
  function _estimate(model, liveRows) {
317
319
  if (model.rowCounts.size === 0 || model.reachedStart) return 0;
318
- let oldest = null;
319
- for (const index of model.rowCounts.keys()) if (oldest === null || index > oldest) oldest = index;
320
+ const oldest = oldestKnownIndex(model);
320
321
  const cursor = model.cursors.get(oldest);
321
322
  if (!cursor || cursor.olderCursor == null) return 0;
322
323
  // Pages of the same source chunk report identical (start, end): count each distinct byte range
@@ -354,8 +355,7 @@
354
355
  function _layout(model, liveRows) {
355
356
  const live = Math.max(1, Math.floor(Number(liveRows) || Number(model.liveRows) || 1));
356
357
  const est = _estimate(model, live);
357
- let oldest = null;
358
- for (const index of model.rowCounts.keys()) if (oldest === null || index > oldest) oldest = index;
358
+ const oldest = oldestKnownIndex(model);
359
359
  const entries = [];
360
360
  const byIndex = new Map();
361
361
  let cursor = est;
@@ -495,7 +495,7 @@
495
495
  }
496
496
 
497
497
  return Object.freeze({
498
- createModel,
498
+ createModel, oldestKnownIndex,
499
499
  promptAtOrAbove,
500
500
  insertPage,
501
501
  pageKinds,