create-walle 0.10.2 → 0.10.3

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 (488) hide show
  1. package/README.md +3 -1
  2. package/lib/release-schema.json +2 -2
  3. package/lib/runtime-closure-policy.json +8 -7
  4. package/package.json +1 -1
  5. package/template/coding-task-manager/api-prompts.js +45 -7
  6. package/template/coding-task-manager/bin/claude-hook.js +55 -1
  7. package/template/coding-task-manager/bin/ctm-relay-client.js +35 -12
  8. package/template/coding-task-manager/bin/land.js +2069 -0
  9. package/template/coding-task-manager/bin/memory-census.js +4 -2
  10. package/template/coding-task-manager/bin/memory-sync-verify.js +151 -0
  11. package/template/coding-task-manager/bin/probe-refusal-scope.js +530 -0
  12. package/template/coding-task-manager/db.js +156 -32
  13. package/template/coding-task-manager/lib/acp/acp-client.js +383 -0
  14. package/template/coding-task-manager/lib/acp/dsh-context-truth.js +176 -0
  15. package/template/coding-task-manager/lib/acp/dsh-launch-model.js +302 -0
  16. package/template/coding-task-manager/lib/acp/dsh-mcp-servers.js +194 -0
  17. package/template/coding-task-manager/lib/acp/dsh-prompt-lane.js +616 -0
  18. package/template/coding-task-manager/lib/acp/dsh-route-patch.js +308 -0
  19. package/template/coding-task-manager/lib/acp/dsh-session-runtime.js +522 -0
  20. package/template/coding-task-manager/lib/acp/dsh-transcript.js +395 -0
  21. package/template/coding-task-manager/lib/acp/dsh-usage-log.js +162 -0
  22. package/template/coding-task-manager/lib/acp/ndjson-stdio-transport.js +225 -0
  23. package/template/coding-task-manager/lib/adopted-host-redial.js +47 -11
  24. package/template/coding-task-manager/lib/adopted-launch-environment.js +123 -0
  25. package/template/coding-task-manager/lib/agent-auto-update-runtime.js +0 -3
  26. package/template/coding-task-manager/lib/agent-auto-update.js +26 -10
  27. package/template/coding-task-manager/lib/agent-bundle/build.js +657 -0
  28. package/template/coding-task-manager/lib/agent-bundle/codex-install.js +558 -0
  29. package/template/coding-task-manager/lib/agent-bundle/drift.js +120 -0
  30. package/template/coding-task-manager/lib/agent-bundle/gc.js +522 -0
  31. package/template/coding-task-manager/lib/agent-bundle/inventory.js +346 -0
  32. package/template/coding-task-manager/lib/agent-bundle/push.js +717 -0
  33. package/template/coding-task-manager/lib/agent-bundle/skill-transfer.js +724 -0
  34. package/template/coding-task-manager/lib/agent-bundle/spawn.js +119 -0
  35. package/template/coding-task-manager/lib/agent-bundle/store.js +568 -0
  36. package/template/coding-task-manager/lib/agent-capabilities.js +142 -12
  37. package/template/coding-task-manager/lib/agent-cli-cache.js +30 -5
  38. package/template/coding-task-manager/lib/agent-definitions.js +19 -0
  39. package/template/coding-task-manager/lib/agent-hooks-codex-toml.js +28 -16
  40. package/template/coding-task-manager/lib/agent-hooks-codex-workspace.js +9 -1
  41. package/template/coding-task-manager/lib/agent-hooks-installer.js +23 -8
  42. package/template/coding-task-manager/lib/agent-hooks-prune.js +37 -11
  43. package/template/coding-task-manager/lib/agent-hooks-shim.js +105 -0
  44. package/template/coding-task-manager/lib/agent-presets.js +84 -1
  45. package/template/coding-task-manager/lib/agent-upgrade-jobs.js +12 -1
  46. package/template/coding-task-manager/lib/agent-version-service.js +74 -11
  47. package/template/coding-task-manager/lib/all-sessions-query.js +6 -2
  48. package/template/coding-task-manager/lib/api/agent-bundles.js +436 -0
  49. package/template/coding-task-manager/lib/api/backup-retention.js +83 -0
  50. package/template/coding-task-manager/lib/api/conversation-sync-routes.js +124 -0
  51. package/template/coding-task-manager/lib/api/diagnostics.js +549 -69
  52. package/template/coding-task-manager/lib/api/fleet.js +118 -0
  53. package/template/coding-task-manager/lib/api/hub-bridge.js +243 -0
  54. package/template/coding-task-manager/lib/api/ingress.js +9 -1
  55. package/template/coding-task-manager/lib/api/landing.js +403 -0
  56. package/template/coding-task-manager/lib/api/machines.js +691 -2
  57. package/template/coding-task-manager/lib/api/memory-sync-routes.js +335 -0
  58. package/template/coding-task-manager/lib/api/misc-ops.js +8 -3
  59. package/template/coding-task-manager/lib/api/remote-sessions.js +1326 -0
  60. package/template/coding-task-manager/lib/api/session-attach.js +623 -0
  61. package/template/coding-task-manager/lib/api/session-messages.js +1384 -0
  62. package/template/coding-task-manager/lib/api/sessions.js +77 -51
  63. package/template/coding-task-manager/lib/api/setup-network.js +544 -16
  64. package/template/coding-task-manager/lib/api/setup-wizard-handlers.js +884 -0
  65. package/template/coding-task-manager/lib/api/setup.js +829 -10
  66. package/template/coding-task-manager/lib/api/walle-bridge.js +9 -1
  67. package/template/coding-task-manager/lib/attachment-reconciler-wiring.js +5 -2
  68. package/template/coding-task-manager/lib/auth-rate-limit.js +23 -0
  69. package/template/coding-task-manager/lib/auth-rules.js +292 -21
  70. package/template/coding-task-manager/lib/background-stop.js +369 -0
  71. package/template/coding-task-manager/lib/best-of-attempts.js +4 -1
  72. package/template/coding-task-manager/lib/best-of-launch-plan.js +52 -0
  73. package/template/coding-task-manager/lib/box-keep-awake.js +89 -0
  74. package/template/coding-task-manager/lib/broadcast-reason-stats.js +152 -0
  75. package/template/coding-task-manager/lib/broadcast-serialize.js +5 -0
  76. package/template/coding-task-manager/lib/build-info.js +4 -2
  77. package/template/coding-task-manager/lib/claude-model-truth.js +13 -2
  78. package/template/coding-task-manager/lib/claude-transcript-terminal-renderer.js +17 -13
  79. package/template/coding-task-manager/lib/client-diag-handler.js +64 -2
  80. package/template/coding-task-manager/lib/codex-app-server-client.js +14 -16
  81. package/template/coding-task-manager/lib/codex-configuration-history.js +55 -0
  82. package/template/coding-task-manager/lib/codex-harness-qualification.js +43 -20
  83. package/template/coding-task-manager/lib/codex-rollout-history.js +743 -0
  84. package/template/coding-task-manager/lib/coding-agent-models.js +27 -12
  85. package/template/coding-task-manager/lib/coding-cli-skill-installer.js +44 -14
  86. package/template/coding-task-manager/lib/coding-session-model-adapters.js +43 -2
  87. package/template/coding-task-manager/lib/coding-session-model-owner.js +1 -1
  88. package/template/coding-task-manager/lib/coding-session-model-public.js +7 -1
  89. package/template/coding-task-manager/lib/coding-session-model-runtime.js +23 -4
  90. package/template/coding-task-manager/lib/coding-session-model-scan.js +31 -0
  91. package/template/coding-task-manager/lib/coding-session-model-state.js +7 -0
  92. package/template/coding-task-manager/lib/conversation-freshness-diagnostics.js +280 -0
  93. package/template/coding-task-manager/lib/conversation-item-clock.js +261 -0
  94. package/template/coding-task-manager/lib/conversation-source-registry.js +5 -0
  95. package/template/coding-task-manager/lib/conversation-store-v2.js +44 -0
  96. package/template/coding-task-manager/lib/conversation-sync.js +355 -0
  97. package/template/coding-task-manager/lib/create/resolve-launch-params.js +96 -12
  98. package/template/coding-task-manager/lib/create/worker-role-profile.js +4 -4
  99. package/template/coding-task-manager/lib/create-intent-lease.js +51 -0
  100. package/template/coding-task-manager/lib/ctm-session-context-api.js +97 -6
  101. package/template/coding-task-manager/lib/db-domain-tables.js +1 -0
  102. package/template/coding-task-manager/lib/db-domains.js +1 -0
  103. package/template/coding-task-manager/lib/db-owner-dispatch-probe.js +32 -0
  104. package/template/coding-task-manager/lib/db-owner-request-counts.js +178 -0
  105. package/template/coding-task-manager/lib/db-owner-worker-client.js +68 -14
  106. package/template/coding-task-manager/lib/db-schema-source-files.js +5 -0
  107. package/template/coding-task-manager/lib/deferred-restore-attempt.js +6 -2
  108. package/template/coding-task-manager/lib/feature-manifest.js +4 -0
  109. package/template/coding-task-manager/lib/fleet-conversation-route.js +97 -0
  110. package/template/coding-task-manager/lib/fleet-fanout.js +78 -0
  111. package/template/coding-task-manager/lib/fleet-hub.js +762 -0
  112. package/template/coding-task-manager/lib/fleet-origin-follower.js +252 -0
  113. package/template/coding-task-manager/lib/fleet-resolver-effects.js +147 -0
  114. package/template/coding-task-manager/lib/fleet-resolver-tick.js +148 -0
  115. package/template/coding-task-manager/lib/fleet-resolver.js +454 -0
  116. package/template/coding-task-manager/lib/fleet-session-list-routes.js +114 -0
  117. package/template/coding-task-manager/lib/fleet-session-lists.js +672 -0
  118. package/template/coding-task-manager/lib/fleet-update.js +798 -0
  119. package/template/coding-task-manager/lib/fs-watcher-root-watch.js +141 -0
  120. package/template/coding-task-manager/lib/fs-watcher.js +58 -42
  121. package/template/coding-task-manager/lib/harness-history-transition.js +50 -9
  122. package/template/coding-task-manager/lib/headless-term-service.js +84 -8
  123. package/template/coding-task-manager/lib/hermetic-agent-launch-guard.js +10 -0
  124. package/template/coding-task-manager/lib/history-transition-read-op.js +175 -8
  125. package/template/coding-task-manager/lib/host-cpu-categories.js +310 -0
  126. package/template/coding-task-manager/lib/host-fseventsd-sampler.js +399 -0
  127. package/template/coding-task-manager/lib/host-handoff.js +16 -19
  128. package/template/coding-task-manager/lib/host-stats.js +150 -3
  129. package/template/coding-task-manager/lib/hosted-sync-worker.js +43 -320
  130. package/template/coding-task-manager/lib/hub-bridge-access.js +798 -0
  131. package/template/coding-task-manager/lib/hub-bridge-box.js +409 -0
  132. package/template/coding-task-manager/lib/hub-bridge-mcp.js +380 -0
  133. package/template/coding-task-manager/lib/hub-bridge-mesh.js +313 -0
  134. package/template/coding-task-manager/lib/hub-bridge-ops.js +721 -0
  135. package/template/coding-task-manager/lib/hub-bridge.js +398 -0
  136. package/template/coding-task-manager/lib/idle-notify.js +491 -0
  137. package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +8 -0
  138. package/template/coding-task-manager/lib/instance-manifest.js +5 -1
  139. package/template/coding-task-manager/lib/instance-sync-status.js +18 -8
  140. package/template/coding-task-manager/lib/landing/agent-rule.txt +1 -0
  141. package/template/coding-task-manager/lib/landing/batch-runner.js +366 -0
  142. package/template/coding-task-manager/lib/landing/config.js +536 -0
  143. package/template/coding-task-manager/lib/landing/ctm-view.js +256 -0
  144. package/template/coding-task-manager/lib/landing/gate.js +360 -0
  145. package/template/coding-task-manager/lib/landing/git.js +344 -0
  146. package/template/coding-task-manager/lib/landing/lease.js +247 -0
  147. package/template/coding-task-manager/lib/landing/merge-delta.js +380 -0
  148. package/template/coding-task-manager/lib/landing/proof.js +223 -0
  149. package/template/coding-task-manager/lib/landing/queue.js +998 -0
  150. package/template/coding-task-manager/lib/landing/radar.js +489 -0
  151. package/template/coding-task-manager/lib/landing/resolve-cli.js +424 -0
  152. package/template/coding-task-manager/lib/landing/state.js +263 -0
  153. package/template/coding-task-manager/lib/landing/stop.js +49 -0
  154. package/template/coding-task-manager/lib/landing/sync-cli.js +475 -0
  155. package/template/coding-task-manager/lib/landing/sync.js +456 -0
  156. package/template/coding-task-manager/lib/landing/train.js +885 -0
  157. package/template/coding-task-manager/lib/landing/window.js +57 -0
  158. package/template/coding-task-manager/lib/launch-environment.js +121 -0
  159. package/template/coding-task-manager/lib/launch-model-override.js +36 -10
  160. package/template/coding-task-manager/lib/launch-plan-pool.js +2 -1
  161. package/template/coding-task-manager/lib/launch-plan-warm-cache.js +6 -0
  162. package/template/coding-task-manager/lib/lead-model-observer.js +57 -1
  163. package/template/coding-task-manager/lib/lead-model-options.js +160 -3
  164. package/template/coding-task-manager/lib/lead-model-restore-reconciliation.js +106 -24
  165. package/template/coding-task-manager/lib/lead-model-switch-runtime.js +352 -68
  166. package/template/coding-task-manager/lib/lead-model-switch.js +114 -10
  167. package/template/coding-task-manager/lib/lead-model-transition-runtime.js +14 -2
  168. package/template/coding-task-manager/lib/lead-model-transition-store.js +24 -6
  169. package/template/coding-task-manager/lib/lead-route-preflight.js +179 -0
  170. package/template/coding-task-manager/lib/lead-switch-history-mode.js +110 -0
  171. package/template/coding-task-manager/lib/lead-wire-agent.js +7 -0
  172. package/template/coding-task-manager/lib/lifecycle-wake-experiment.js +9 -2
  173. package/template/coding-task-manager/lib/live-defer-announce.js +45 -0
  174. package/template/coding-task-manager/lib/machine-registry.js +750 -5
  175. package/template/coding-task-manager/lib/macos-capabilities.js +32 -15
  176. package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +433 -53
  177. package/template/coding-task-manager/lib/mainloop-observability.js +14 -9
  178. package/template/coding-task-manager/lib/media-draft-owner.js +16 -3
  179. package/template/coding-task-manager/lib/memory-join.js +337 -0
  180. package/template/coding-task-manager/lib/memory-sync-verify.js +393 -0
  181. package/template/coding-task-manager/lib/memory-sync.js +565 -0
  182. package/template/coding-task-manager/lib/migrations/v57-delegate-pref-mirror-opencode.js +68 -0
  183. package/template/coding-task-manager/lib/migrations/v58-subagent-launch-native-subagents.js +42 -0
  184. package/template/coding-task-manager/lib/migrations/v59-coding-session-model-state-dsh.js +73 -0
  185. package/template/coding-task-manager/lib/migrations/v60-delegate-pref-mirror-dsh.js +78 -0
  186. package/template/coding-task-manager/lib/migrations/v61-conversation-item-clock.js +71 -0
  187. package/template/coding-task-manager/lib/model-availability.js +727 -0
  188. package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +4 -6
  189. package/template/coding-task-manager/lib/model-host-receipt.js +6 -3
  190. package/template/coding-task-manager/lib/model-id-rule.js +19 -0
  191. package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
  192. package/template/coding-task-manager/lib/model-proxy/auto-mode-review.js +110 -23
  193. package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
  194. package/template/coding-task-manager/lib/model-proxy/generation-probe.js +60 -17
  195. package/template/coding-task-manager/lib/model-proxy/historical-tool-declarations.js +37 -0
  196. package/template/coding-task-manager/lib/model-proxy/lead-request-validation.js +1 -0
  197. package/template/coding-task-manager/lib/model-proxy/lead-token-admin.js +130 -0
  198. package/template/coding-task-manager/lib/model-proxy/refusal-scope.js +151 -0
  199. package/template/coding-task-manager/lib/model-proxy/request-budget-store.js +11 -2
  200. package/template/coding-task-manager/lib/model-proxy/tokens.js +69 -5
  201. package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
  202. package/template/coding-task-manager/lib/native-agent-model-args.js +25 -22
  203. package/template/coding-task-manager/lib/native-history-control.js +6 -4
  204. package/template/coding-task-manager/lib/native-lead-settings-client.js +44 -4
  205. package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +173 -36
  206. package/template/coding-task-manager/lib/online-backup-worker-client.js +2 -0
  207. package/template/coding-task-manager/lib/opencode-cli.js +480 -0
  208. package/template/coding-task-manager/lib/opencode-launch-model.js +471 -0
  209. package/template/coding-task-manager/lib/opencode-v1-plugin/model-carrier.mjs +35 -0
  210. package/template/coding-task-manager/lib/opencode-v2-plugin/server.js +118 -0
  211. package/template/coding-task-manager/lib/owner-action-manifest-data.json +1 -1
  212. package/template/coding-task-manager/lib/park-composer-draft.js +54 -0
  213. package/template/coding-task-manager/lib/park-fresh-relaunch.js +41 -10
  214. package/template/coding-task-manager/lib/perf-phase-trace.js +72 -0
  215. package/template/coding-task-manager/lib/perf-summary.js +7 -0
  216. package/template/coding-task-manager/lib/perf-tracker.js +63 -2
  217. package/template/coding-task-manager/lib/project-config.js +7 -0
  218. package/template/coding-task-manager/lib/prompt-purge-v2.js +52 -1
  219. package/template/coding-task-manager/lib/provider-login-broker.js +72 -15
  220. package/template/coding-task-manager/lib/provider-login-tab.js +10 -2
  221. package/template/coding-task-manager/lib/read-pool-client.js +89 -2
  222. package/template/coding-task-manager/lib/read-pool-worker-handle.js +29 -4
  223. package/template/coding-task-manager/lib/recent-catalog-query.js +15 -0
  224. package/template/coding-task-manager/lib/recent-session-catalog-response.js +67 -10
  225. package/template/coding-task-manager/lib/recovery-evidence.js +26 -11
  226. package/template/coding-task-manager/lib/relay-client.js +57 -3
  227. package/template/coding-task-manager/lib/relink-audit.js +34 -5
  228. package/template/coding-task-manager/lib/remote-machine-catalog.js +321 -26
  229. package/template/coding-task-manager/lib/remote-machine-create.js +576 -36
  230. package/template/coding-task-manager/lib/remote-machine-link.js +546 -59
  231. package/template/coding-task-manager/lib/remote-machine-proxy.js +143 -9
  232. package/template/coding-task-manager/lib/remote-session-forward.js +65 -7
  233. package/template/coding-task-manager/lib/remote-terminal-target.js +9 -2
  234. package/template/coding-task-manager/lib/restart-guard.js +18 -6
  235. package/template/coding-task-manager/lib/restore-seed-modes.js +87 -0
  236. package/template/coding-task-manager/lib/restored-scrollback-hydration-runtime.js +22 -2
  237. package/template/coding-task-manager/lib/resume-admission.js +17 -3
  238. package/template/coding-task-manager/lib/review-repo-registry.js +58 -19
  239. package/template/coding-task-manager/lib/root-delivery-runtime.js +2 -2
  240. package/template/coding-task-manager/lib/root-prompt-flow.js +1 -1
  241. package/template/coding-task-manager/lib/runtime-contract.js +3 -1
  242. package/template/coding-task-manager/lib/schema-indexes.js +1 -0
  243. package/template/coding-task-manager/lib/session-context-locator.js +742 -0
  244. package/template/coding-task-manager/lib/session-create/attachment.js +2 -2
  245. package/template/coding-task-manager/lib/session-create/create-phases.js +118 -0
  246. package/template/coding-task-manager/lib/session-create/delegate-worker.js +86 -16
  247. package/template/coding-task-manager/lib/session-create/dsh-session.js +930 -0
  248. package/template/coding-task-manager/lib/session-create/lead-route-launch.js +186 -13
  249. package/template/coding-task-manager/lib/session-create/native-lead-lifetime.js +55 -3
  250. package/template/coding-task-manager/lib/session-create/opencode-lead-provider.js +50 -0
  251. package/template/coding-task-manager/lib/session-create/prepared.js +162 -38
  252. package/template/coding-task-manager/lib/session-create/provider-process.js +160 -28
  253. package/template/coding-task-manager/lib/session-create/response.js +7 -7
  254. package/template/coding-task-manager/lib/session-create/structured-session.js +27 -0
  255. package/template/coding-task-manager/lib/session-create/subagent-pick-table.js +3 -1
  256. package/template/coding-task-manager/lib/session-create/subagent-pick.js +458 -9
  257. package/template/coding-task-manager/lib/session-create/transcript.js +1 -1
  258. package/template/coding-task-manager/lib/session-create/worker-budget-setup.js +21 -2
  259. package/template/coding-task-manager/lib/session-create/worker-continuation.js +7 -1
  260. package/template/coding-task-manager/lib/session-create/worker-launch-sandbox.js +39 -10
  261. package/template/coding-task-manager/lib/session-create/worker-observability.js +2 -2
  262. package/template/coding-task-manager/lib/session-empty.js +13 -10
  263. package/template/coding-task-manager/lib/session-host-manager.js +60 -0
  264. package/template/coding-task-manager/lib/session-jobs.js +3 -1
  265. package/template/coding-task-manager/lib/session-lifecycle.js +10 -4
  266. package/template/coding-task-manager/lib/session-model-action-preconditions.js +6 -2
  267. package/template/coding-task-manager/lib/session-model-intent-contract.js +11 -8
  268. package/template/coding-task-manager/lib/session-park.js +738 -0
  269. package/template/coding-task-manager/lib/session-path-grant.js +13 -4
  270. package/template/coding-task-manager/lib/session-status-ledger.js +106 -4
  271. package/template/coding-task-manager/lib/session-transport.js +17 -0
  272. package/template/coding-task-manager/lib/session-workspace-projection.js +54 -5
  273. package/template/coding-task-manager/lib/session-workspace-tracking-runtime.js +213 -1
  274. package/template/coding-task-manager/lib/skill-autocomplete-query.js +120 -3
  275. package/template/coding-task-manager/lib/skill-autocomplete.js +15 -1
  276. package/template/coding-task-manager/lib/sqlite-file-set.js +39 -0
  277. package/template/coding-task-manager/lib/standup-search-adapter.js +16 -2
  278. package/template/coding-task-manager/lib/state-sync/client-ledger.js +12 -0
  279. package/template/coding-task-manager/lib/state-sync/frame-emitter.js +319 -100
  280. package/template/coding-task-manager/lib/storage/coding-session-model-store.js +4 -1
  281. package/template/coding-task-manager/lib/storage/delegate-pref-mirror-store.js +32 -9
  282. package/template/coding-task-manager/lib/structured-session-boot-sweep.js +235 -0
  283. package/template/coding-task-manager/lib/submission-ack.js +90 -2
  284. package/template/coding-task-manager/lib/systemd-scope.js +115 -0
  285. package/template/coding-task-manager/lib/telemetry-receiver.js +39 -0
  286. package/template/coding-task-manager/lib/terminal-attach-geometry.js +985 -0
  287. package/template/coding-task-manager/lib/terminal-bootstrap.js +30 -0
  288. package/template/coding-task-manager/lib/terminal-draft-operation.js +357 -0
  289. package/template/coding-task-manager/lib/terminal-input-admission.js +17 -1
  290. package/template/coding-task-manager/lib/terminal-input-prepared.js +563 -0
  291. package/template/coding-task-manager/lib/transcript-cache-temps.js +171 -0
  292. package/template/coding-task-manager/lib/transcript-frame-cache.js +8 -3
  293. package/template/coding-task-manager/lib/transcript-source.js +36 -4
  294. package/template/coding-task-manager/lib/transcript-store.js +9 -0
  295. package/template/coding-task-manager/lib/typing-interaction-trace.js +106 -3
  296. package/template/coding-task-manager/lib/usage-ledger/backfill.js +40 -5
  297. package/template/coding-task-manager/lib/usage-ledger/client.js +4 -1
  298. package/template/coding-task-manager/lib/usage-ledger/dsh-parse.js +152 -0
  299. package/template/coding-task-manager/lib/usage-ledger/dsh-read.js +73 -0
  300. package/template/coding-task-manager/lib/usage-ledger/ingest.js +34 -6
  301. package/template/coding-task-manager/lib/usage-ledger/live.js +35 -8
  302. package/template/coding-task-manager/lib/usage-ledger/read.js +29 -20
  303. package/template/coding-task-manager/lib/usage-ledger/schema.js +61 -1
  304. package/template/coding-task-manager/lib/usage-ledger/task-coordinator.js +4 -2
  305. package/template/coding-task-manager/lib/wake-skip-reasons.js +1 -0
  306. package/template/coding-task-manager/lib/walle/turn.js +1 -1
  307. package/template/coding-task-manager/lib/walle-history-serve.js +838 -0
  308. package/template/coding-task-manager/lib/walle-kill-authority.js +2 -2
  309. package/template/coding-task-manager/lib/walle-log-rotation.js +164 -1
  310. package/template/coding-task-manager/lib/walle-mcp-auto-config.js +56 -2
  311. package/template/coding-task-manager/lib/walle-row-history.js +495 -0
  312. package/template/coding-task-manager/lib/walle-supervisor.js +33 -14
  313. package/template/coding-task-manager/lib/watch-worker-client.js +203 -0
  314. package/template/coding-task-manager/lib/worker-terminal-output.js +5 -2
  315. package/template/coding-task-manager/lib/ws/hub-bridge.js +35 -0
  316. package/template/coding-task-manager/lib/ws/lifecycle.js +6 -11
  317. package/template/coding-task-manager/lib/ws/misc.js +1 -1
  318. package/template/coding-task-manager/lib/ws/state-sync.js +106 -4
  319. package/template/coding-task-manager/lib/ws/terminal-attach.js +91 -0
  320. package/template/coding-task-manager/oauth-proxy.js +112 -115
  321. package/template/coding-task-manager/package-lock.json +10 -94
  322. package/template/coding-task-manager/package.json +4 -3
  323. package/template/coding-task-manager/protocol/message-table.json +10 -0
  324. package/template/coding-task-manager/protocol/messages.js +10 -0
  325. package/template/coding-task-manager/protocol/ws-schema.json +20 -0
  326. package/template/coding-task-manager/protocol/ws-schema.md +2 -0
  327. package/template/coding-task-manager/public/css/app.css +170 -0
  328. package/template/coding-task-manager/public/css/walle-session.css +4 -0
  329. package/template/coding-task-manager/public/index.html +473 -243
  330. package/template/coding-task-manager/public/js/agent-command.js +86 -0
  331. package/template/coding-task-manager/public/js/app/backups-panel.js +111 -5
  332. package/template/coding-task-manager/public/js/app/coding-agent-upgrade.js +103 -4
  333. package/template/coding-task-manager/public/js/app/machines-page.js +1328 -8
  334. package/template/coding-task-manager/public/js/app/models-tab.js +113 -38
  335. package/template/coding-task-manager/public/js/app/restart-banner.js +53 -0
  336. package/template/coding-task-manager/public/js/app/worktrees-panel.js +184 -2
  337. package/template/coding-task-manager/public/js/background-work.js +196 -27
  338. package/template/coding-task-manager/public/js/coding-session-model-presentation.js +138 -10
  339. package/template/coding-task-manager/public/js/core/active-session-groups.js +179 -0
  340. package/template/coding-task-manager/public/js/core/pending-create.js +238 -0
  341. package/template/coding-task-manager/public/js/core/recent-machine-source.js +228 -0
  342. package/template/coding-task-manager/public/js/core/session-commands.js +180 -14
  343. package/template/coding-task-manager/public/js/dashboard-client-function-order.json +8 -0
  344. package/template/coding-task-manager/public/js/document-review-links.js +9 -1
  345. package/template/coding-task-manager/public/js/file-resolution-client.js +52 -1
  346. package/template/coding-task-manager/public/js/history-app-seam.js +22 -2
  347. package/template/coding-task-manager/public/js/machine-pool-shared.js +558 -3
  348. package/template/coding-task-manager/public/js/message-renderer.js +4 -3
  349. package/template/coding-task-manager/public/js/model-picker.js +3 -1
  350. package/template/coding-task-manager/public/js/native-subagent-review.js +15 -2
  351. package/template/coding-task-manager/public/js/new-session-agent-grid.js +346 -0
  352. package/template/coding-task-manager/public/js/pasted-content.js +236 -0
  353. package/template/coding-task-manager/public/js/pe-private-content-lifetime.js +39 -7
  354. package/template/coding-task-manager/public/js/private-model-lifetime.js +6 -2
  355. package/template/coding-task-manager/public/js/prompts.js +11 -10
  356. package/template/coding-task-manager/public/js/protocol.js +10 -0
  357. package/template/coding-task-manager/public/js/restart-banner-policy.js +29 -1
  358. package/template/coding-task-manager/public/js/session-model-picker.js +447 -57
  359. package/template/coding-task-manager/public/js/session-search-utils.js +18 -2
  360. package/template/coding-task-manager/public/js/session-usage-view.js +2 -1
  361. package/template/coding-task-manager/public/js/session-workspace-tracking.js +51 -1
  362. package/template/coding-task-manager/public/js/state-sync-client.js +10 -1
  363. package/template/coding-task-manager/public/js/stream-view.js +6 -5
  364. package/template/coding-task-manager/public/js/terminal/conv-only-session.js +287 -0
  365. package/template/coding-task-manager/public/js/terminal/runtime-09.js +2 -0
  366. package/template/coding-task-manager/public/js/terminal/runtime-10.js +5 -5
  367. package/template/coding-task-manager/public/js/terminal/runtime-12.js +8 -1
  368. package/template/coding-task-manager/public/js/terminal/runtime-16.js +4 -0
  369. package/template/coding-task-manager/public/js/terminal/runtime-20.js +1 -1
  370. package/template/coding-task-manager/public/js/terminal-attachment-draft.js +67 -13
  371. package/template/coding-task-manager/public/js/terminal-image-orchestration.js +52 -6
  372. package/template/coding-task-manager/public/js/terminal-wheel.js +23 -3
  373. package/template/coding-task-manager/public/js/walle/chat-conversation.js +4 -4
  374. package/template/coding-task-manager/public/js/walle.js +6 -0
  375. package/template/coding-task-manager/public/js/ws/live-dispatch.js +10 -0
  376. package/template/coding-task-manager/public/m/app.css +140 -0
  377. package/template/coding-task-manager/public/m/app.js +507 -68
  378. package/template/coding-task-manager/public/m/index.html +17 -0
  379. package/template/coding-task-manager/public/m/machines.js +838 -4
  380. package/template/coding-task-manager/public/m/private-api.js +31 -1
  381. package/template/coding-task-manager/public/usage.html +1 -0
  382. package/template/coding-task-manager/scripts/dev-route-fixture/synthetic-route-upstream.js +104 -0
  383. package/template/coding-task-manager/scripts/dev-route-fixture.js +339 -0
  384. package/template/coding-task-manager/scripts/opencode-lead-route-probe.js +195 -0
  385. package/template/coding-task-manager/scripts/opencode-v2-contract-probe/fake-anthropic.js +168 -0
  386. package/template/coding-task-manager/scripts/opencode-v2-contract-probe/probe-plugin/server.js +95 -0
  387. package/template/coding-task-manager/scripts/opencode-v2-contract-probe.js +673 -0
  388. package/template/coding-task-manager/server.js +1950 -11679
  389. package/template/coding-task-manager/session-integrity.js +64 -4
  390. package/template/coding-task-manager/workers/db-owner-worker.js +19 -2
  391. package/template/coding-task-manager/workers/online-backup-worker.js +1 -0
  392. package/template/coding-task-manager/workers/read-pool-model-collector.js +51 -3
  393. package/template/coding-task-manager/workers/read-pool-worker.js +76 -6
  394. package/template/coding-task-manager/workers/session-host-pool-process.js +6 -6
  395. package/template/coding-task-manager/workers/session-host-process.js +5 -5
  396. package/template/coding-task-manager/workers/watch-worker.js +105 -0
  397. package/template/coding-task-manager/workers/worktree-worker.js +168 -33
  398. package/template/ctm-relay/lib/protocol.js +14 -1
  399. package/template/package-lock.json +2 -2
  400. package/template/package.json +1 -1
  401. package/template/shared/backup-retention.js +82 -0
  402. package/template/shared/conversation-admission.js +2 -0
  403. package/template/shared/conversation-provenance.js +3 -7
  404. package/template/shared/conversation-storage-v2-contract.js +5 -0
  405. package/template/shared/linked-worktree.js +51 -0
  406. package/template/shared/macos-fda-target.js +57 -0
  407. package/template/shared/orphan-parents.js +55 -0
  408. package/template/shared/release-schema.json +2 -2
  409. package/template/shared/storage/contract.js +3 -0
  410. package/template/wall-e/adapters/imessage.js +12 -0
  411. package/template/wall-e/agent.js +71 -4
  412. package/template/wall-e/api-walle.js +21 -1
  413. package/template/wall-e/bin/train-gemma-e4b-tooluse.js +2 -2
  414. package/template/wall-e/bin/walle-mcp-stdio.js +5 -5
  415. package/template/wall-e/brain/brain-join.js +859 -0
  416. package/template/wall-e/brain/brain-sync-membership.js +20 -0
  417. package/template/wall-e/brain/brain-sync-store.js +131 -77
  418. package/template/wall-e/brain/brain-sync-v2-schema.js +203 -0
  419. package/template/wall-e/brain/brain-sync-v2.js +578 -0
  420. package/template/wall-e/brain/delegate-model-prefs-store.js +13 -4
  421. package/template/wall-e/brain/deterministic-id.js +51 -0
  422. package/template/wall-e/brain/memories.js +2 -2
  423. package/template/wall-e/brain/migrations.js +40 -6
  424. package/template/wall-e/brain/model-identity-migration.js +1 -1
  425. package/template/wall-e/brain/owner-call-whitelist.js +23 -7
  426. package/template/wall-e/brain/schema.js +45 -85
  427. package/template/wall-e/brain.js +171 -35
  428. package/template/wall-e/chat.js +3 -3
  429. package/template/wall-e/coding/agent-shell-env.js +7 -2
  430. package/template/wall-e/coding/prompt-bundle.js +66 -21
  431. package/template/wall-e/config/main-loop-exemptions.json +2 -2
  432. package/template/wall-e/http/admin-api.js +37 -0
  433. package/template/wall-e/http/gws-api.js +4 -0
  434. package/template/wall-e/http/helper-rules-api.js +7 -1
  435. package/template/wall-e/http/model-admin.js +8 -1
  436. package/template/wall-e/http/model-delegate-pref-admin.js +3 -3
  437. package/template/wall-e/http/model-proxy-providers.js +174 -1
  438. package/template/wall-e/http/model-route-health.js +157 -0
  439. package/template/wall-e/http/sync-api.js +278 -464
  440. package/template/wall-e/jobs/job-runner.js +28 -1
  441. package/template/wall-e/lib/brain-owner-op-priority.js +9 -1
  442. package/template/wall-e/lib/ctm-data-dir.js +26 -0
  443. package/template/wall-e/lib/derivation-home.js +147 -0
  444. package/template/wall-e/lib/helper-rules/evaluate.js +50 -9
  445. package/template/wall-e/lib/helper-rules/grade.js +51 -8
  446. package/template/wall-e/lib/helper-rules/lead-call-hook.js +38 -5
  447. package/template/wall-e/lib/helper-rules/store.js +4 -2
  448. package/template/wall-e/lib/landing-mcp-tools.js +170 -0
  449. package/template/wall-e/lib/mcp-client-config.js +133 -14
  450. package/template/wall-e/lib/mcp-gateway-migrate.js +28 -64
  451. package/template/wall-e/lib/mcp-integration.js +58 -13
  452. package/template/wall-e/lib/parent-death.js +7 -1
  453. package/template/wall-e/lib/scheduler-worker-jobs.js +6 -4
  454. package/template/wall-e/llm/delegate-lead-prefs.js +16 -1
  455. package/template/wall-e/llm/delegate-vendor-default.js +12 -5
  456. package/template/wall-e/llm/model-roles.js +1 -1
  457. package/template/wall-e/llm/native-delegate-selection.js +56 -8
  458. package/template/wall-e/llm/worker-delegate-args.js +72 -7
  459. package/template/wall-e/llm/worker-mcp-routing.js +66 -17
  460. package/template/wall-e/loops/brain-custom-writes.js +19 -0
  461. package/template/wall-e/loops/ingest.js +17 -1
  462. package/template/wall-e/loops/think.js +19 -4
  463. package/template/wall-e/mcp-contract.js +222 -6
  464. package/template/wall-e/mcp-protocol-contract.js +40 -5
  465. package/template/wall-e/mcp-server.js +128 -29
  466. package/template/wall-e/mcp-worker-schemas.js +5 -5
  467. package/template/wall-e/memory/ctm-context-client.js +85 -0
  468. package/template/wall-e/memory/ctm-session-context.js +9 -3
  469. package/template/wall-e/memory/hub-bridge-client.js +388 -0
  470. package/template/wall-e/memory/replica-origin.js +56 -0
  471. package/template/wall-e/memory/session-pool.js +135 -0
  472. package/template/wall-e/runtime/live-capabilities.js +8 -3
  473. package/template/wall-e/server.js +4 -2
  474. package/template/wall-e/shared/release-schema.json +2 -2
  475. package/template/wall-e/shared/sqlite-file-set.js +39 -0
  476. package/template/wall-e/shared/storage/contract.js +3 -0
  477. package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
  478. package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +7 -1
  479. package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +4 -4
  480. package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +6 -6
  481. package/template/wall-e/skills/_templates/fleet-sync/VERSION +1 -0
  482. package/template/wall-e/skills/_templates/fleet-sync/released-sha256.json +16 -0
  483. package/template/wall-e/skills/_templates/fleet-sync/templates/claude/SKILL.md +64 -0
  484. package/template/wall-e/skills/_templates/fleet-sync/templates/codex/SKILL.md +64 -0
  485. package/template/wall-e/tools/chrome-automation.js +6 -0
  486. package/template/wall-e/training/setup-cuda-train-env.sh +2 -2
  487. package/template/wall-e/workers/brain-owner-worker.js +18 -4
  488. package/template/wall-e/workers/mcp-gateway-process.js +5 -5
@@ -9,8 +9,10 @@
9
9
  //
10
10
  // Nothing here touches fs or sqlite: every read is injected through `deps`, so the whole
11
11
  // short-circuit order is decidable in a pure unit test. The order is the design's:
12
- // D-3 canonical file (realpath; missing / directory / non-regular → not-found)
13
- // D-4 deny list beats the grant, on the realpath AND the lexical expanded path
12
+ // D-4a the lexical deny runs FIRST, before any filesystem call: a sensitive expanded path
13
+ // returns `sensitive-path` without a realpath/stat (2026-10-27 D-1, the Cortex fix)
14
+ // D-3 canonical file (realpath; missing / directory / non-regular → not-found)
15
+ // D-4b the deny list beats the grant on the canonical realpath too (the symlink case)
14
16
  // D-5 only link-detector extensions
15
17
  // E1 admitted Review Hub worktree → root = that worktree
16
18
  // E2 the session's stored text mentions the path → root = dirname(real)
@@ -121,6 +123,12 @@ async function grantForAbsolutePath(p, deps) {
121
123
  const sessionId = String(p?.sessionId || '');
122
124
  if (!expanded || !path.isAbsolute(expanded) || !sessionId) return { ok: false, code: 'not-printed' };
123
125
 
126
+ // D-4: deny beats grant — the lexical form the user typed, decided BEFORE any filesystem
127
+ // call. A request for `~/.ssh/anything` is refused without a realpath/stat, so the server
128
+ // never touches a credential path on behalf of a link (and a test can assert the deny on a
129
+ // path that does not exist). The canonical form is re-checked after realpath below.
130
+ if (isSensitive(expanded)) return { ok: false, code: 'sensitive-path' };
131
+
124
132
  // D-3: decide on the canonical file, never on the request string.
125
133
  let real;
126
134
  try {
@@ -137,8 +145,9 @@ async function grantForAbsolutePath(p, deps) {
137
145
  }
138
146
  if (!stat || typeof stat.isFile !== 'function' || !stat.isFile()) return { ok: false, code: 'not-found' };
139
147
 
140
- // D-4: deny beats grant — the canonical target AND the lexical form the user typed.
141
- if (isSensitive(real) || isSensitive(expanded)) return { ok: false, code: 'sensitive-path' };
148
+ // D-4: deny beats grant — the canonical target (the lexical form was checked above, before
149
+ // realpath, so a symlink whose target is a credential path is still refused here).
150
+ if (isSensitive(real)) return { ok: false, code: 'sensitive-path' };
142
151
 
143
152
  // D-5: only what the terminal could have linked.
144
153
  if (!GRANT_EXTENSIONS.includes(path.extname(real).slice(1).toLowerCase())) {
@@ -91,6 +91,14 @@ function normalizeBackgroundTask(task) {
91
91
 
92
92
  function bgCapFor(type) { return type === 'shell' ? BG_SHELL_MAX_MS : BG_DECLARED_MAX_MS; }
93
93
 
94
+ // R4/WP-1: the lifetime a launcher declared inside its own command line (`bin/bounded.js --deadline-ms N -- ...`).
95
+ // Read from the label only; nothing here parses, signals or inspects a process.
96
+ const BG_DEADLINE_RE = /\bbin\/bounded\.js\s+--deadline-ms\s+(\d+)/;
97
+ function bgDeadlineMsOf(label) {
98
+ const m = BG_DEADLINE_RE.exec(String(label || ''));
99
+ return m ? Number(m[1]) : null;
100
+ }
101
+
94
102
  class SessionStatusLedger {
95
103
  constructor({
96
104
  sessionId = '', providerId = '',
@@ -158,7 +166,18 @@ class SessionStatusLedger {
158
166
  if (now < e.expiresAt) continue;
159
167
  this.background.delete(id);
160
168
  if (!e.leased) {
161
- this._bgPending.push({ kind: 'status-background-expired', payload: { id: e.id, type: e.type, ageMs: Math.max(0, now - e.firstSeenAt) } });
169
+ // WP-4 (M1/M5): the expired row carries the terminal spell (quietForMs) and the declared deadline, so the
170
+ // census can count quiet hours and deadline coverage without re-deriving either from the entry.
171
+ const firstSeenAt = Number(e.firstSeenAt) || 0;
172
+ this._bgPending.push({
173
+ kind: 'status-background-expired',
174
+ payload: {
175
+ id: e.id, type: e.type,
176
+ ageMs: Math.max(0, now - firstSeenAt),
177
+ quietForMs: Math.max(0, now - (Number(e.activeAt) || firstSeenAt)),
178
+ deadlineMs: bgDeadlineMsOf(e.label),
179
+ },
180
+ });
162
181
  }
163
182
  }
164
183
  if (!this.background.size) this._bgEvent = null;
@@ -169,6 +188,8 @@ class SessionStatusLedger {
169
188
  this.backgroundObservedAt = at;
170
189
  if (!Array.isArray(tasks)) { // D-6: absent is not empty
171
190
  const hadDeclared = this.backgroundDeclared;
191
+ // R4/WP-1: an absent set is NOT an empty one (D-6) -- the CLI did not report the set, so the owner
192
+ // retired nothing and no entry here reports a retirement. Only a set that omits an entry does that.
172
193
  this.background.clear();
173
194
  this.backgroundDeclared = false;
174
195
  // Noisy on older Claude Code (every Stop is null): only a declared->absent flip, or the ledger's first null, is a row.
@@ -191,8 +212,11 @@ class SessionStatusLedger {
191
212
  ...n, firstSeenAt, expiresAt: keep ? Math.max(Number(prev.expiresAt) || 0, cap) : cap, source: 'hook', count: 1,
192
213
  renewedAt: keep ? (prev.renewedAt || 0) : 0, renewals: keep ? (prev.renewals || 0) : 0, capCrossedAt: keep ? (prev.capCrossedAt || 0) : 0,
193
214
  activeAt: keep ? (prev.activeAt || firstSeenAt) : firstSeenAt, outSize: keep ? (prev.outSize || 0) : 0, outMtimeMs: keep ? (prev.outMtimeMs || 0) : 0,
215
+ nudgedAt: keep ? (prev.nudgedAt || 0) : 0, nudgeCount: keep ? (prev.nudgeCount || 0) : 0, firstNudgedAt: keep ? (prev.firstNudgedAt || 0) : 0,
194
216
  });
195
217
  }
218
+ // R4/WP-1: an entry the new set no longer names has left the set (M2 evidence), whether it ended or was omitted.
219
+ for (const [id, e] of this.background) if (!next.has(id)) this._bgRetire(e, 'snapshot', at);
196
220
  let changed = next.size !== before || !this.backgroundDeclared;
197
221
  if (!changed) for (const [id, e] of next) { const o = this.background.get(id); if (!o || o.type !== e.type || o.label !== e.label) { changed = true; break; } }
198
222
  this.background = next;
@@ -200,6 +224,28 @@ class SessionStatusLedger {
200
224
  if (changed) this._bgNote('snapshot', source || 'hook', before);
201
225
  }
202
226
 
227
+ // R4/WP-1 (M2) + WP-4 (M1/M5): one row for every entry that leaves the set and is either a hook-declared shell
228
+ // (the class M1/M3/M5 measure) or was ever nudged. The payload carries the whole terminal spell — the age, the
229
+ // quiet spell, the declared deadline and the nudge tally — so a census computes M1/M2/M5 from diagnostics alone.
230
+ // Expiry is not a retirement (_bgExpire keeps its own row); only the three callers that delete an entry report here.
231
+ _bgRetire(entry, via, now) {
232
+ const firstSeenAt = Number(entry.firstSeenAt) || 0;
233
+ const firstNudgedAt = Number(entry.firstNudgedAt) || 0;
234
+ const hookShell = entry.type === 'shell' && entry.source === 'hook';
235
+ if (!hookShell && !(firstNudgedAt > 0)) return;
236
+ this._bgPending.push({
237
+ kind: 'status-background-retired',
238
+ payload: {
239
+ id: entry.id, type: entry.type, via,
240
+ ageMs: Math.max(0, now - firstSeenAt),
241
+ quietForMs: Math.max(0, now - (Number(entry.activeAt) || firstSeenAt)),
242
+ deadlineMs: bgDeadlineMsOf(entry.label),
243
+ nudgeCount: entry.nudgeCount || 0,
244
+ sinceFirstNudgeMs: firstNudgedAt > 0 ? Math.max(0, now - firstNudgedAt) : null,
245
+ },
246
+ });
247
+ }
248
+
203
249
  _bgTask(meta, at, source) {
204
250
  const op = meta.op;
205
251
  const n = normalizeBackgroundTask(meta);
@@ -207,7 +253,10 @@ class SessionStatusLedger {
207
253
  const before = this.background.size;
208
254
  const src = meta.source || source || 'hook';
209
255
  if (op === 'remove') {
210
- if (!this.background.delete(n.id)) return;
256
+ const gone = this.background.get(n.id);
257
+ if (!gone || !this.background.delete(n.id)) return;
258
+ // R4/WP-1 (M2): a removed entry that was ever nudged reports its retirement.
259
+ this._bgRetire(gone, 'remove', at);
211
260
  this.backgroundObservedAt = at;
212
261
  this._bgNote('remove', src, before);
213
262
  return;
@@ -225,6 +274,7 @@ class SessionStatusLedger {
225
274
  source: src, count,
226
275
  renewedAt: keep ? (prev.renewedAt || 0) : 0, renewals: keep ? (prev.renewals || 0) : 0, capCrossedAt: keep ? (prev.capCrossedAt || 0) : 0,
227
276
  activeAt: keep ? (prev.activeAt || firstSeenAt) : firstSeenAt, outSize: keep ? (prev.outSize || 0) : 0, outMtimeMs: keep ? (prev.outMtimeMs || 0) : 0,
277
+ nudgedAt: keep ? (prev.nudgedAt || 0) : 0, nudgeCount: keep ? (prev.nudgeCount || 0) : 0, firstNudgedAt: keep ? (prev.firstNudgedAt || 0) : 0,
228
278
  };
229
279
  if (leaseMs) entry.leased = true;
230
280
  this.background.set(n.id, entry);
@@ -247,6 +297,51 @@ class SessionStatusLedger {
247
297
  return [...this.background.values()].some(e => !this.backgroundQuiet(e, now));
248
298
  }
249
299
 
300
+ // R4 (WP-1): the entries the owner should be asked about at this Stop, oldest quiet spell first. Pure reader:
301
+ // it signals nothing, schedules nothing and mutates nothing (noteBackgroundNudged records the emission).
302
+ // Nudge n for one task fires once its quiet spell reaches BG_QUIET_MS * 2**n (30 min, 1 h, 2 h, 4 h ...), and
303
+ // activity after a nudge starts a fresh spell whose count is 0 again.
304
+ backgroundNudges(now = Date.now(), { max = 3 } = {}) {
305
+ this._bgExpire(now);
306
+ const due = [];
307
+ for (const e of this.background.values()) {
308
+ if (e.leased || !this.backgroundQuiet(e, now)) continue;
309
+ const activeAt = e.activeAt || 0;
310
+ const quietForMs = Math.max(0, now - activeAt);
311
+ // a fresh quiet spell (activity after the last nudge) resets the ladder
312
+ const count = (e.nudgedAt && activeAt > e.nudgedAt) ? 0 : (e.nudgeCount || 0);
313
+ if (quietForMs < BG_QUIET_MS * 2 ** count) continue;
314
+ if (count > 0 && now - e.nudgedAt < BG_QUIET_MS * 2 ** (count - 1)) continue;
315
+ due.push({
316
+ id: e.id, label: e.label, type: e.type, firstSeenAt: e.firstSeenAt, activeAt,
317
+ quietForMs, outSize: e.outSize || 0, outMtimeMs: e.outMtimeMs || 0, nudgeCount: count,
318
+ deadlineMs: bgDeadlineMsOf(e.label),
319
+ });
320
+ }
321
+ due.sort((a, b) => a.activeAt - b.activeAt);
322
+ return due.slice(0, Math.max(0, Number(max) || 0));
323
+ }
324
+
325
+ // R4 (WP-1): record that the owner was asked about these ids. Bookkeeping only — no timer, no signal, no
326
+ // onBackgroundChange (the set itself did not move). Returns how many ids were noted.
327
+ noteBackgroundNudged(ids, now = Date.now()) {
328
+ let noted = 0;
329
+ for (const id of Array.isArray(ids) ? ids : []) {
330
+ const e = this.background.get(id);
331
+ if (!e) continue;
332
+ if (e.nudgedAt && (e.activeAt || 0) > e.nudgedAt) e.nudgeCount = 0; // a fresh spell restarts the ladder
333
+ e.nudgeCount = (e.nudgeCount || 0) + 1;
334
+ e.nudgedAt = now;
335
+ e.firstNudgedAt ||= now;
336
+ this._bgPending.push({
337
+ kind: 'status-background-nudged',
338
+ payload: { id: e.id, nudgeCount: e.nudgeCount, quietForMs: Math.max(0, now - (e.activeAt || 0)), ageMs: Math.max(0, now - e.firstSeenAt) },
339
+ });
340
+ noted += 1;
341
+ }
342
+ return noted;
343
+ }
344
+
250
345
  backgroundView(now = Date.now()) {
251
346
  this._bgExpire(now);
252
347
  if (!this.background.size) return null;
@@ -273,6 +368,7 @@ class SessionStatusLedger {
273
368
  id: e.id, type: e.type, label: e.label, firstSeenAt: e.firstSeenAt, expiresAt: e.expiresAt, source: e.source, count: e.count,
274
369
  renewedAt: e.renewedAt || 0, renewals: e.renewals || 0, capCrossedAt: e.capCrossedAt || 0,
275
370
  activeAt: e.activeAt || 0, outSize: e.outSize || 0, outMtimeMs: e.outMtimeMs || 0,
371
+ nudgedAt: Number(e.nudgedAt) || 0, nudgeCount: Number(e.nudgeCount) || 0, firstNudgedAt: Number(e.firstNudgedAt) || 0,
276
372
  });
277
373
  }
278
374
  return { declared: this.backgroundDeclared, observedAt: this.backgroundObservedAt, entries };
@@ -296,6 +392,7 @@ class SessionStatusLedger {
296
392
  source: String(raw.source || 'hook'), count: Math.max(1, Number(raw.count) || 1),
297
393
  renewedAt: Number(raw.renewedAt) || 0, renewals: Math.max(0, Number(raw.renewals) || 0), capCrossedAt: Number(raw.capCrossedAt) || 0,
298
394
  activeAt: Number(raw.activeAt) > 0 ? Number(raw.activeAt) : now, outSize: Number(raw.outSize) || 0, outMtimeMs: Number(raw.outMtimeMs) || 0,
395
+ nudgedAt: Number(raw.nudgedAt) || 0, nudgeCount: Number(raw.nudgeCount) || 0, firstNudgedAt: Number(raw.firstNudgedAt) || 0,
299
396
  });
300
397
  n += 1;
301
398
  }
@@ -311,7 +408,12 @@ class SessionStatusLedger {
311
408
  dropBackground(predicate, now = Date.now(), reason = '') {
312
409
  this._bgExpire(now);
313
410
  const before = this.background.size;
314
- for (const [id, e] of this.background) if (predicate(e)) this.background.delete(id);
411
+ for (const [id, e] of this.background) {
412
+ if (!predicate(e)) continue;
413
+ this.background.delete(id);
414
+ // R4/WP-1 (M2): a dropped entry that was ever nudged reports its retirement.
415
+ this._bgRetire(e, 'drop', now);
416
+ }
315
417
  const removed = before - this.background.size;
316
418
  if (removed) {
317
419
  this.backgroundObservedAt = now;
@@ -618,4 +720,4 @@ class SessionStatusLedger {
618
720
  }
619
721
  }
620
722
 
621
- module.exports = { SessionStatusLedger, EVIDENCE, AGENT_OUTPUT, PROVIDER_STATUS_POLICY, BG_END_STATUSES, BG_SHELL_MAX_MS, BG_QUIET_MS, BG_DECLARED_MAX_MS, BG_LABEL_MAX, normalizeBackgroundTask };
723
+ module.exports = { SessionStatusLedger, EVIDENCE, AGENT_OUTPUT, PROVIDER_STATUS_POLICY, BG_END_STATUSES, BG_SHELL_MAX_MS, BG_QUIET_MS, BG_DECLARED_MAX_MS, BG_LABEL_MAX, normalizeBackgroundTask, bgDeadlineMsOf };
@@ -0,0 +1,17 @@
1
+ 'use strict';
2
+
3
+ // WP-4 of docs/deepseek-harness-acp-agent-2026-10-28.html (§5 D-4).
4
+ //
5
+ // The one predicate for "this session speaks the Agent Client Protocol on pipes and has no PTY"
6
+ // (today the DeepSeek Harness). Required from the prompt lane (lib/acp/dsh-prompt-lane.js), the
7
+ // snapshot redirect (lib/ws/state-sync.js, S-39) and server.js's approval-executor seam.
8
+ //
9
+ // PTY-shaped status code (lib/session-status-runtime.js) deliberately does NOT skip ACP sessions:
10
+ // they own a status ledger fed by turn evidence, which is how their chip reads running/needs-you/idle.
11
+
12
+ /** A session driven over the Agent Client Protocol (child on pipes, no terminal). */
13
+ function isAcpSession(session) {
14
+ return session?.transport === 'acp';
15
+ }
16
+
17
+ module.exports = { isAcpSession };
@@ -2,6 +2,29 @@
2
2
 
3
3
  const { parseWorkspaceIncarnation } = require('../../shared/storage/session-workspace-activity');
4
4
 
5
+ // Sorted-key stable JSON — the same 6-line function as lib/standup-incremental.js:5-10 (copied,
6
+ // not imported: that module is unrelated). Keys are sorted recursively so two structurally equal
7
+ // values compare equal as strings; arrays keep their order.
8
+ function stableStringify(value) {
9
+ if (value === null || typeof value !== 'object') return JSON.stringify(value);
10
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`;
11
+ const keys = Object.keys(value).sort();
12
+ return `{${keys.map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(',')}}`;
13
+ }
14
+
15
+ // WP-1 (§5 D-1/D-2 of docs/typing-delay-session-list-broadcast-storm-2026-11-01.html): the git
16
+ // FACTS of an observation, excluding exactly the two per-probe fields — `snapshotVersion` (the
17
+ // ordering counter incremented on every probe, runtime.js:137) and `collectedAt` (the probe
18
+ // time). Every other field stays in, because projectSessionWorkspaceTracking spreads the whole
19
+ // snapshot into the row (`git = { ...gitSnapshot, … }`), so any other field is visible on the
20
+ // wire and must count as a change. Null/non-object input has the empty signature, which is what
21
+ // makes the first installation of a session's snapshot a change.
22
+ function gitFactsSignature(git) {
23
+ if (!git || typeof git !== 'object') return '';
24
+ const { snapshotVersion, collectedAt, ...facts } = git;
25
+ return stableStringify(facts);
26
+ }
27
+
5
28
  const GIT_STATES = ['checking', 'known', 'unknown', 'unavailable'];
6
29
  const PROVENANCE = ['current-snapshot', 'last-observed', 'configured', 'unavailable'];
7
30
  const UNAVAILABLE_CODES = ['WORKSPACE_IDENTITY_CHANGED', 'WORKSPACE_IDENTITY_UNAVAILABLE'];
@@ -41,7 +64,13 @@ function scopedErrorCode(gitSnapshot, tracking, selection) {
41
64
  // so no consumer can read it as "still checking" or join it to another workspace.
42
65
  // D-6: `checkoutProvenance` scopes the branch that may be shown; counts are only ever joined to
43
66
  // a matching fresh snapshot, so one workspace's numbers can never accompany another selection.
44
- function projectSessionWorkspaceTracking(tracking, gitSnapshot) {
67
+ //
68
+ // WP-3 (§6.3): the third argument is this session's landing-queue view, already computed by the
69
+ // caller (lib/landing/ctm-view.js is pure; the runtime owns the per-repo probe). It is gated on
70
+ // `git.freshness === 'fresh'` for the same reason every other count is: a stale or refused
71
+ // snapshot describes a workspace we can no longer prove this session is on, and a landing view
72
+ // quoting a branch from an unproven workspace is worse than no view at all.
73
+ function projectSessionWorkspaceTracking(tracking, gitSnapshot, landing = null) {
45
74
  const selection = tracking?.selection || null;
46
75
  if (!selection) return null;
47
76
  const sameWorkspace = !!gitSnapshot && gitSnapshot.workspaceKey === selection.workspaceKey
@@ -94,14 +123,34 @@ function projectSessionWorkspaceTracking(tracking, gitSnapshot) {
94
123
  workspaceIncarnation: selection.workspaceIncarnation,
95
124
  selection,
96
125
  git,
126
+ landing: git.freshness === 'fresh' && landing ? landing : null,
97
127
  recentWorkspaceCount: (tracking.history || []).filter(item => item.actorRole === 'foreground').length,
98
128
  history: (tracking.history || []).filter(item => item.actorRole === 'foreground').slice(0, 12),
99
129
  };
100
130
  }
101
- function workspaceTrackingMemoSignature(tracking, git) {
131
+
132
+ // WP-3 (census 1e): the broadcast memo key. A landing-only change (the queue drained, a new pair
133
+ // appeared) moves nothing else in the row, so without this tuple the sessions frame would keep
134
+ // serving the previous memo and the chip would freeze. The view is built by a pure function with a
135
+ // fixed key order and a sorted `with` list, so this derived tuple — never the object itself — is
136
+ // what gets compared; it is stable across two runs over equal data.
137
+ function landingSignature(landing) {
138
+ if (!landing || typeof landing !== 'object') return null;
139
+ const radar = landing.radar && typeof landing.radar === 'object' ? landing.radar : {};
140
+ const with_ = Array.isArray(radar.with) ? radar.with : [];
141
+ return [landing.jobId ?? null, landing.state ?? null, landing.position ?? null, landing.total ?? null,
142
+ landing.eta_s ?? null, radar.max ?? null,
143
+ with_.map(entry => [entry?.session ?? null, entry?.branch ?? null, entry?.level ?? null])];
144
+ }
145
+
146
+ // WP-1 (§5 D-2): the git part of the key is the FACTS signature, not `snapshotVersion`. With the
147
+ // per-probe counter in the key, every 30 s probe of an unchanged worktree missed the memo and
148
+ // rebuilt all 105 rows; the same signature the change test uses is what lets an unchanged row
149
+ // reuse its memo (the 1 s memo TTL still bounds clock-only fields).
150
+ function workspaceTrackingMemoSignature(tracking, git, landing) {
102
151
  return tracking?.selection ? JSON.stringify([tracking.sessionEpoch, tracking.selectionVersion,
103
- tracking.selection.workspaceKey, tracking.selection.workspaceIncarnation, git?.snapshotVersion,
104
- git?.freshness, git?.state, git?.errorCode, git?.checkoutProvenance]) : '';
152
+ tracking.selection.workspaceKey, tracking.selection.workspaceIncarnation, gitFactsSignature(git),
153
+ git?.freshness, git?.state, git?.errorCode, git?.checkoutProvenance, landingSignature(landing)]) : '';
105
154
  }
106
155
 
107
156
  // D-6/AC-3c: a checkout is presented as CURRENT only from a matching fresh snapshot. A
@@ -175,4 +224,4 @@ function workspaceTrackingCompatibility(tracking) {
175
224
  };
176
225
  }
177
226
 
178
- module.exports = { projectSessionWorkspaceTracking, workspaceTrackingMemoSignature, workspaceTrackingCompatibility };
227
+ module.exports = { projectSessionWorkspaceTracking, workspaceTrackingMemoSignature, workspaceTrackingCompatibility, landingSignature, gitFactsSignature };
@@ -2,6 +2,12 @@
2
2
 
3
3
  const path = require('node:path');
4
4
  const { parseWorkspaceIncarnation } = require('../../shared/storage/session-workspace-activity');
5
+ // WP-1 (§5 D-1): the change test compares git FACTS, not the per-probe ordering counter.
6
+ // No require cycle: session-workspace-projection.js requires only ../../shared/storage/*.
7
+ const { gitFactsSignature } = require('./session-workspace-projection');
8
+ // WP-3 (§6.3): the per-session landing view is a PURE function over the `land` CLI's own JSON —
9
+ // this runtime owns the probe that produces that JSON, the view module owns nothing but the shape.
10
+ const { landingForSession, landingStatusForRepo } = require('./landing/ctm-view');
5
11
 
6
12
  // Activity moves only after the durable owner commits. Git reconciliation has its own version
7
13
  // and never changes last-used activity.
@@ -56,6 +62,26 @@ function createSessionWorkspaceTrackingRuntime(deps = {}) {
56
62
  const nativeDrain = new WeakMap();
57
63
  const probes = new Map();
58
64
  let snapshotVersion = 0;
65
+ // WP-3 (§5 D-1/D-2, §6.3, §8 WP-3): ONE landing probe per repository. The landing queue is
66
+ // per-repository state that only `bin/land.js` can read, so the runtime does not parse it — it
67
+ // hands a `{ repo, branches }` request to the injected probe (server.js wires the real async CLI
68
+ // child) and keeps the last payload. A probe for a repository that is already running is never
69
+ // started again: the in-flight promise is returned, so the request path and the sweep share one
70
+ // child per repository instead of racing two.
71
+ const runLandingProbe = typeof deps.runLandingProbe === 'function' ? deps.runLandingProbe : null;
72
+ const onLandingChanged = typeof deps.onLandingChanged === 'function' ? deps.onLandingChanged : null;
73
+ const landingSlots = new Map();
74
+ // A worktree path -> the key its probe actually resolved to (the git common dir the CLI printed).
75
+ // Sessions whose snapshot has no common dir (an unvalidated hydration) key on their worktree path
76
+ // until the first probe resolves it, and this alias is what keeps that first probe from becoming a
77
+ // second, parallel cache entry for the same repository.
78
+ const landingAliases = new Map();
79
+ const LANDING_PROBE_CAP = 64;
80
+ let landingSessions = [];
81
+ // repo key -> { branch -> [session id] }, rebuilt once per sweep so the per-session view is a map
82
+ // lookup instead of a scan of every live session (the payload projection runs once per session per
83
+ // broadcast, and a scan there would be quadratic in the session count).
84
+ let landingBranchIndex = new Map();
59
85
 
60
86
  // §6: `git.errorCode` is a closed set. Raw Git/worker codes are mapped onto it so the
61
87
  // projection never publishes an unbounded string.
@@ -132,7 +158,13 @@ function createSessionWorkspaceTrackingRuntime(deps = {}) {
132
158
  const installed = { ...observation };
133
159
  delete installed.refusedSelection;
134
160
  state.gitSnapshot = installed;
135
- const changed = previous?.snapshotVersion !== observation.snapshotVersion || previous?.freshness !== observation.freshness;
161
+ // WP-1 (§5 D-1/D-3): `snapshotVersion` is the ORDERING key (the fence above), never the change
162
+ // key — it advances on every probe, so comparing it made every 30 s probe of an unchanged
163
+ // worktree a change and re-broadcast the whole session list. Compare the git facts instead
164
+ // (everything but `snapshotVersion`/`collectedAt`), plus `freshness`, which is probe-local
165
+ // state rather than an observation fact. The observation itself is still installed above.
166
+ const changed = gitFactsSignature(previous) !== gitFactsSignature(installed)
167
+ || previous?.freshness !== observation.freshness;
136
168
  if (changed) onChange(session);
137
169
  return changed;
138
170
  }
@@ -523,8 +555,188 @@ function createSessionWorkspaceTrackingRuntime(deps = {}) {
523
555
  return reconcile(session, state, options);
524
556
  }
525
557
 
558
+ // --- WP-3 (§5 D-1/D-2, §6.3, §8 WP-3): the landing probe cache --------------------------------
559
+
560
+ /**
561
+ * What CTM knows about this session's repository, or `null` when it cannot be PROVEN. A session
562
+ * whose git freshness is not `fresh` contributes nothing — the same gate the projection applies to
563
+ * the view itself, so the probe and the chip can never disagree about whose branch this is. No I/O:
564
+ * every field comes from the snapshot the sweep already installed.
565
+ */
566
+ function describeSession(session) {
567
+ const state = session && typeof session === 'object' ? contexts.get(session) : null;
568
+ const git = state ? state.gitSnapshot : null;
569
+ if (!git || git.freshness !== 'fresh') return null;
570
+ const checkout = git.checkout || null;
571
+ const branch = checkout && typeof checkout.branch === 'string' ? checkout.branch : '';
572
+ if (!branch) return null;
573
+ const workspace = git.workspace || {};
574
+ const root = (typeof workspace.root === 'string' && workspace.root)
575
+ || (typeof session.worktree_path === 'string' && session.worktree_path)
576
+ || (typeof session.cwd === 'string' && session.cwd)
577
+ || null;
578
+ if (!root) return null;
579
+ const commonDir = typeof workspace.commonDir === 'string' && workspace.commonDir ? workspace.commonDir : null;
580
+ return { id: String(session.id), branch, repo: commonDir || landingAliases.get(root) || root, path: root };
581
+ }
582
+
583
+ const landingKeyFor = (repo) => landingAliases.get(repo) || repo;
584
+
585
+ function landingSlotFor(key) {
586
+ const existing = landingSlots.get(key);
587
+ if (existing) return existing;
588
+ if (landingSlots.size >= LANDING_PROBE_CAP) {
589
+ const idle = [...landingSlots].find(([, entry]) => !entry.pending);
590
+ // Every slot is in flight: keep the results already held and start nothing new. A probe that
591
+ // cannot be started is not a failure — the last payload stays, exactly like a failed probe's.
592
+ if (!idle) return null;
593
+ landingSlots.delete(idle[0]);
594
+ }
595
+ const slot = { path: key, root: null, branches: [], payload: null, error: null, at: 0, pending: null };
596
+ landingSlots.set(key, slot);
597
+ return slot;
598
+ }
599
+
600
+ function startLandingProbe(key, repoPath, branches) {
601
+ const slot = landingSlotFor(key);
602
+ if (!slot) {
603
+ return Promise.resolve({ ok: false, payload: null, error: { code: 'landing_probe_capacity', message: 'every landing probe slot is in flight' } });
604
+ }
605
+ // `key` is the repository IDENTITY (the git common dir, so every worktree of a repository shares
606
+ // one slot) while `repoPath` is the WORK TREE the CLI is run in. They are deliberately different
607
+ // values: the CLI refuses a bare git directory (`git rev-parse --show-toplevel` inside `.git`
608
+ // exits non-zero => `not_a_repo`), so probing the common dir would leave every chip permanently
609
+ // dark. Any work tree of the repository answers identically — the queue state lives under the
610
+ // common dir and the radar policy is read from the target branch tip.
611
+ slot.path = repoPath;
612
+ slot.root = repoPath;
613
+ slot.branches = branches;
614
+ // SINGLE-FLIGHT: a repository whose probe is still running is never probed again — the request
615
+ // path and the sweep join the child already in flight, and its result is the one that lands.
616
+ if (slot.pending) return slot.pending;
617
+ const pending = Promise.resolve()
618
+ .then(() => runLandingProbe({ repo: repoPath, branches }))
619
+ .then((result) => {
620
+ slot.at = now();
621
+ if (result && result.ok === true) {
622
+ const changed = JSON.stringify(slot.payload) !== JSON.stringify(result.payload || null);
623
+ slot.payload = result.payload || null;
624
+ // The sweep does not await the probe, so its broadcast went out with the previous payload;
625
+ // a changed payload must ask for one more (the memo still filters unchanged sessions).
626
+ if (changed && onLandingChanged) onLandingChanged(key);
627
+ slot.error = null;
628
+ const resolved = result.payload && typeof result.payload.repo === 'string' ? result.payload.repo : null;
629
+ // The CLI prints the repository's REAL common dir, which may differ from the key we probed
630
+ // with (a worktree path, or a snapshot that had no common dir yet): alias both directions so
631
+ // the next sweep keys on the same slot instead of starting a second cache entry.
632
+ if (resolved && resolved !== key) landingAliases.set(resolved, key);
633
+ if (repoPath !== key) landingAliases.set(repoPath, key);
634
+ } else {
635
+ // A failed probe says nothing new about the queue and keeps the last payload: an empty chip
636
+ // is worse than a slightly old one. The error is retained for the status route to report.
637
+ slot.error = (result && result.error) || { code: 'landing_probe_failed', message: 'the landing probe returned no result' };
638
+ }
639
+ return { ok: !!slot.payload, payload: slot.payload, error: slot.error };
640
+ })
641
+ .catch((error) => {
642
+ // The probe is injected; a rejection here is a wiring bug, not a CLI refusal. Record it and
643
+ // keep the previous payload rather than dropping the repository out of the cache.
644
+ slot.error = { code: (error && error.code) || 'landing_probe_failed', message: (error && error.message) || String(error) };
645
+ return { ok: !!slot.payload, payload: slot.payload, error: slot.error };
646
+ })
647
+ .finally(() => { if (slot.pending === pending) slot.pending = null; });
648
+ slot.pending = pending;
649
+ return pending;
650
+ }
651
+
652
+ /**
653
+ * The WORK TREE to run the CLI in for a repository keyed by `key`. A sweep always supplies the
654
+ * live session's own worktree root; the request path may be handed the git common dir instead
655
+ * (`?repo=<commonDir>`), and `dirname(<repo>/.git)` is that repository's primary work tree — the
656
+ * same rule lib/review-repo-admission.js uses to name a repository's root from its administrative
657
+ * directory. No such directory means the CLI refuses and the route answers `repo_unknown`.
658
+ */
659
+ function landingRepoFor(key, repo) {
660
+ const slot = landingSlots.get(key);
661
+ if (slot && slot.root) return slot.root;
662
+ return path.basename(repo) === '.git' ? path.dirname(repo) : repo;
663
+ }
664
+
665
+ function landingBranchesFor(key) {
666
+ const branches = new Set();
667
+ for (const entry of landingSessions) {
668
+ if (landingKeyFor(entry.repo) === key) branches.add(entry.branch);
669
+ }
670
+ return [...branches];
671
+ }
672
+
673
+ /**
674
+ * One probe per repository, driven by the SAME sweep that just refreshed the per-session git
675
+ * snapshots — no new timer and no idle loop, so a repository with no live session on any branch is
676
+ * never probed at all. Errors are handled inside `startLandingProbe`; nothing here is awaited.
677
+ */
678
+ function refreshLanding(sessions) {
679
+ const described = [];
680
+ for (const session of Array.isArray(sessions) ? sessions : []) {
681
+ const entry = describeSession(session);
682
+ if (entry) described.push(entry);
683
+ }
684
+ landingSessions = described;
685
+ const index = new Map();
686
+ const groups = new Map();
687
+ for (const entry of described) {
688
+ const key = landingKeyFor(entry.repo);
689
+ let byBranch = index.get(key);
690
+ if (!byBranch) { byBranch = {}; index.set(key, byBranch); }
691
+ if (!byBranch[entry.branch]) byBranch[entry.branch] = [];
692
+ byBranch[entry.branch].push(entry.id);
693
+ let group = groups.get(key);
694
+ if (!group) {
695
+ group = { key, repo: entry.path || entry.repo, branches: new Set() };
696
+ groups.set(key, group);
697
+ }
698
+ group.branches.add(entry.branch);
699
+ }
700
+ landingBranchIndex = index;
701
+ if (!runLandingProbe) return;
702
+ for (const group of groups.values()) startLandingProbe(group.key, group.repo, [...group.branches]);
703
+ }
704
+
705
+ /**
706
+ * The request path's probe: awaited, and single-flight with the sweep (same slot). `sessions`
707
+ * (optional) re-describes the live sessions first, so a route call cannot answer with a `with` list
708
+ * from a stale session set.
709
+ */
710
+ function probeLanding(args = {}) {
711
+ if (Array.isArray(args.sessions)) refreshLanding(args.sessions);
712
+ const repo = typeof args.repo === 'string' && args.repo ? args.repo : null;
713
+ if (!repo) return Promise.resolve({ ok: false, payload: null, error: { code: 'repo_required', message: 'probeLanding needs a repository path' } });
714
+ if (!runLandingProbe) return Promise.resolve({ ok: false, payload: null, error: { code: 'landing_unavailable', message: 'no landing probe is wired on this instance' } });
715
+ const key = landingKeyFor(repo);
716
+ return startLandingProbe(key, landingRepoFor(key, repo), landingBranchesFor(key));
717
+ }
718
+
719
+ /**
720
+ * The per-session view (§6.3) over the last payload for this session's repository. `null` when the
721
+ * repository has nothing to say OR its probe has not landed yet — the projection turns that into an
722
+ * absent chip, which is the honest state for "we have not read this queue".
723
+ */
724
+ function landingView(session) {
725
+ const described = describeSession(session);
726
+ if (!described) return null;
727
+ const key = landingKeyFor(described.repo);
728
+ const slot = landingSlots.get(key);
729
+ if (!slot || !slot.payload) return null;
730
+ return landingForSession(slot.payload, {
731
+ sessionId: described.id,
732
+ branch: described.branch,
733
+ sessionsByBranch: landingBranchIndex.get(key) || {},
734
+ });
735
+ }
736
+
526
737
  return { observe, observeNativeActivity, refresh, stateFor, snapshot: session => contexts.get(session)?.tracking || null,
527
738
  gitSnapshot: session => contexts.get(session)?.gitSnapshot || null,
739
+ describeSession, refreshLanding, probeLanding, landingView,
528
740
  bindSource: bindSource ? (input, options) => bindSource(input, options) : null };
529
741
  }
530
742