create-walle 0.10.0 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (381) hide show
  1. package/README.md +88 -41
  2. package/lib/owner-write-safety.js +135 -0
  3. package/lib/public-runtime.js +13 -5
  4. package/lib/release-schema.json +3 -2
  5. package/lib/runtime-closure-policy.json +7 -5
  6. package/package.json +10 -4
  7. package/template/bin/deps-doctor.js +1 -1
  8. package/template/bin/node-bin.sh +11 -0
  9. package/template/coding-task-manager/api-prompts.js +10 -22
  10. package/template/coding-task-manager/bin/capture-codex-rollout-records.js +417 -0
  11. package/template/coding-task-manager/bin/census-codex-lead-history.js +325 -0
  12. package/template/coding-task-manager/bin/claude-hook.js +7 -3
  13. package/template/coding-task-manager/bin/claude-table-parity.js +307 -0
  14. package/template/coding-task-manager/bin/conversation-storage-auto-migration-lib.js +16 -1
  15. package/template/coding-task-manager/bin/conversation-storage-generation-evidence.js +9 -2
  16. package/template/coding-task-manager/bin/conversation-storage-service-entry-lib.js +16 -1
  17. package/template/coding-task-manager/bin/conversation-storage-walle-owner.js +247 -0
  18. package/template/coding-task-manager/bin/ctm-machine-pair.js +160 -0
  19. package/template/coding-task-manager/bin/lead-wait-census.js +1348 -0
  20. package/template/coding-task-manager/bin/repair-foreign-clone.js +564 -0
  21. package/template/coding-task-manager/bin/repair-resume-echo-titles.js +248 -0
  22. package/template/coding-task-manager/bin/seam-replay-codex.js +301 -0
  23. package/template/coding-task-manager/bin/write-lock-bench.js +111 -0
  24. package/template/coding-task-manager/db.js +705 -286
  25. package/template/coding-task-manager/git-utils.js +44 -0
  26. package/template/coding-task-manager/lib/adopted-host-redial.js +4 -0
  27. package/template/coding-task-manager/lib/agent-presets.js +15 -0
  28. package/template/coding-task-manager/lib/agent-title-apply.js +88 -0
  29. package/template/coding-task-manager/lib/agent-title-backfill-read.js +92 -0
  30. package/template/coding-task-manager/lib/api/host-stats.js +98 -0
  31. package/template/coding-task-manager/lib/api/ingress.js +35 -12
  32. package/template/coding-task-manager/lib/api/machine-pairing.js +285 -0
  33. package/template/coding-task-manager/lib/api/machine-pool.js +307 -0
  34. package/template/coding-task-manager/lib/api/machines.js +461 -0
  35. package/template/coding-task-manager/lib/api/misc-ops.js +20 -5
  36. package/template/coding-task-manager/lib/api/providers.js +53 -6
  37. package/template/coding-task-manager/lib/api/resume-from-store.js +25 -0
  38. package/template/coding-task-manager/lib/api/sessions.js +160 -8
  39. package/template/coding-task-manager/lib/api/transcript-history.js +17 -4
  40. package/template/coding-task-manager/lib/api/worktrees.js +38 -37
  41. package/template/coding-task-manager/lib/approval-drift.js +16 -1
  42. package/template/coding-task-manager/lib/approval-logical-frame.js +5 -1
  43. package/template/coding-task-manager/lib/approval-scope.js +68 -4
  44. package/template/coding-task-manager/lib/auth-context.js +32 -2
  45. package/template/coding-task-manager/lib/auth-rate-limit.js +41 -1
  46. package/template/coding-task-manager/lib/auth-rules.js +186 -45
  47. package/template/coding-task-manager/lib/broadcast-serialize.js +2 -1
  48. package/template/coding-task-manager/lib/browser-session-broker.js +9 -1
  49. package/template/coding-task-manager/lib/browser-use-adapter.js +33 -2
  50. package/template/coding-task-manager/lib/claude-markdown-ansi.js +27 -85
  51. package/template/coding-task-manager/lib/claude-session-title.js +6 -30
  52. package/template/coding-task-manager/lib/claude-table-layout.js +322 -0
  53. package/template/coding-task-manager/lib/client-diag-handler.js +25 -0
  54. package/template/coding-task-manager/lib/codex-config-guard.js +33 -3
  55. package/template/coding-task-manager/lib/codex-harness-qualification.js +12 -0
  56. package/template/coding-task-manager/lib/codex-history-terminal-renderer.js +33 -33
  57. package/template/coding-task-manager/lib/codex-launch-health.js +29 -2
  58. package/template/coding-task-manager/lib/codex-link-preparation.js +9 -8
  59. package/template/coding-task-manager/lib/codex-local-links.js +267 -0
  60. package/template/coding-task-manager/lib/codex-markdown-lists.js +160 -0
  61. package/template/coding-task-manager/lib/codex-native-history-prefix.js +4 -0
  62. package/template/coding-task-manager/lib/codex-paths.js +2 -1
  63. package/template/coding-task-manager/lib/codex-source-handoff.js +186 -0
  64. package/template/coding-task-manager/lib/coding-agent-models.js +21 -30
  65. package/template/coding-task-manager/lib/coding-session-model-adapters.js +27 -2
  66. package/template/coding-task-manager/lib/coding-session-model-projection.js +5 -0
  67. package/template/coding-task-manager/lib/coding-session-model-runtime.js +1 -2
  68. package/template/coding-task-manager/lib/coding-session-model-state.js +10 -0
  69. package/template/coding-task-manager/lib/coding-session-model-sync-state.js +0 -1
  70. package/template/coding-task-manager/lib/conversation-import/cursor-guard-owner.js +10 -0
  71. package/template/coding-task-manager/lib/create/resolve-launch-params.js +8 -1
  72. package/template/coding-task-manager/lib/ctm-boot-lifecycle.js +17 -1
  73. package/template/coding-task-manager/lib/db-domain-tables.js +7 -0
  74. package/template/coding-task-manager/lib/db-domains.js +21 -2
  75. package/template/coding-task-manager/lib/db-owner-op-registry.js +7 -0
  76. package/template/coding-task-manager/lib/db-owner-worker-client.js +22 -0
  77. package/template/coding-task-manager/lib/db-schema-source-files.js +1 -0
  78. package/template/coding-task-manager/lib/feature-manifest.js +1 -3
  79. package/template/coding-task-manager/lib/fs-watcher.js +3 -2
  80. package/template/coding-task-manager/lib/harness-history-transition.js +41 -14
  81. package/template/coding-task-manager/lib/headless-inflight-ops.js +203 -0
  82. package/template/coding-task-manager/lib/headless-missing-reply.js +42 -0
  83. package/template/coding-task-manager/lib/headless-term-service.js +60 -23
  84. package/template/coding-task-manager/lib/history-transition-read-op.js +179 -23
  85. package/template/coding-task-manager/lib/host-control-dial.js +167 -0
  86. package/template/coding-task-manager/lib/host-handoff-orchestrator.js +16 -2
  87. package/template/coding-task-manager/lib/host-handoff.js +51 -13
  88. package/template/coding-task-manager/lib/host-health-ledger.js +164 -12
  89. package/template/coding-task-manager/lib/host-self-truth-watchdog.js +4 -0
  90. package/template/coding-task-manager/lib/host-stats.js +328 -0
  91. package/template/coding-task-manager/lib/host-threadpool-evacuation.js +160 -0
  92. package/template/coding-task-manager/lib/host-threadpool-monitor.js +14 -1
  93. package/template/coding-task-manager/lib/hosted-input-dispatch.js +203 -0
  94. package/template/coding-task-manager/lib/hosted-sync-worker.js +113 -5
  95. package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +4 -0
  96. package/template/coding-task-manager/lib/instance-manifest-runtime.js +3 -0
  97. package/template/coding-task-manager/lib/instance-manifest.js +13 -1
  98. package/template/coding-task-manager/lib/instance-sync-status.js +15 -0
  99. package/template/coding-task-manager/lib/kernel/diag-log.js +12 -0
  100. package/template/coding-task-manager/lib/launch-model-override.js +160 -1
  101. package/template/coding-task-manager/lib/launch-plan-worker.js +59 -2
  102. package/template/coding-task-manager/lib/lead-model-observer.js +17 -2
  103. package/template/coding-task-manager/lib/lead-model-switch-runtime.js +57 -4
  104. package/template/coding-task-manager/lib/lead-model-switch.js +11 -2
  105. package/template/coding-task-manager/lib/lead-model-transition-runtime.js +42 -4
  106. package/template/coding-task-manager/lib/lead-model-transition-store.js +61 -3
  107. package/template/coding-task-manager/lib/legacy-draft-cancellation-recovery.js +111 -0
  108. package/template/coding-task-manager/lib/login-tripwire.js +18 -0
  109. package/template/coding-task-manager/lib/machine-pairing.js +238 -0
  110. package/template/coding-task-manager/lib/machine-pool.js +409 -0
  111. package/template/coding-task-manager/lib/machine-registry.js +749 -0
  112. package/template/coding-task-manager/lib/machine-self.js +66 -0
  113. package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +56 -4
  114. package/template/coding-task-manager/lib/media-journal-control-reader.js +473 -0
  115. package/template/coding-task-manager/lib/media-journal.js +84 -27
  116. package/template/coding-task-manager/lib/media-store-options.js +43 -15
  117. package/template/coding-task-manager/lib/migrations/v56-host-samples-and-machine-pool.js +76 -0
  118. package/template/coding-task-manager/lib/mirror-feed-sanitize.js +12 -3
  119. package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +16 -3
  120. package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
  121. package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
  122. package/template/coding-task-manager/lib/model-proxy/collaboration-codec.js +94 -0
  123. package/template/coding-task-manager/lib/model-proxy/handoff.js +494 -0
  124. package/template/coding-task-manager/lib/model-proxy/lead-worker-picks.js +25 -0
  125. package/template/coding-task-manager/lib/model-proxy/native-lead-listener.js +61 -7
  126. package/template/coding-task-manager/lib/model-proxy/proxy-cli.js +200 -0
  127. package/template/coding-task-manager/lib/model-proxy/proxy-instance-record.js +257 -0
  128. package/template/coding-task-manager/lib/model-proxy/responses-face.js +24 -2
  129. package/template/coding-task-manager/lib/model-proxy/stream-accounting.js +205 -0
  130. package/template/coding-task-manager/lib/model-proxy/tokens.js +78 -0
  131. package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
  132. package/template/coding-task-manager/lib/native-history-control.js +62 -14
  133. package/template/coding-task-manager/lib/native-lead-settings-client.js +2 -1
  134. package/template/coding-task-manager/lib/native-lead-tls.js +18 -4
  135. package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +628 -64
  136. package/template/coding-task-manager/lib/owner-action-manifest-data.json +14 -0
  137. package/template/coding-task-manager/lib/painted-row-recorder.js +9 -1
  138. package/template/coding-task-manager/lib/prompt-admission-trace.js +69 -0
  139. package/template/coding-task-manager/lib/prompt-submission-contract.js +21 -1
  140. package/template/coding-task-manager/lib/provider-login-broker.js +192 -9
  141. package/template/coding-task-manager/lib/provider-login-capture.js +153 -0
  142. package/template/coding-task-manager/lib/queue-draft-status-overlay.js +36 -0
  143. package/template/coding-task-manager/lib/raw-codex-history-window.js +5 -3
  144. package/template/coding-task-manager/lib/raw-history-block.js +3 -1
  145. package/template/coding-task-manager/lib/read-pool-client.js +36 -24
  146. package/template/coding-task-manager/lib/read-pool-op-registry.js +10 -1
  147. package/template/coding-task-manager/lib/read-pool-telemetry.js +14 -5
  148. package/template/coding-task-manager/lib/recent-catalog-query.js +26 -0
  149. package/template/coding-task-manager/lib/recent-session-catalog-response.js +9 -0
  150. package/template/coding-task-manager/lib/remote-command-context.js +12 -0
  151. package/template/coding-task-manager/lib/remote-command-runtime.js +18 -5
  152. package/template/coding-task-manager/lib/remote-machine-catalog.js +1062 -0
  153. package/template/coding-task-manager/lib/remote-machine-create.js +693 -0
  154. package/template/coding-task-manager/lib/remote-machine-link.js +939 -0
  155. package/template/coding-task-manager/lib/remote-machine-proxy.js +520 -0
  156. package/template/coding-task-manager/lib/remote-session-forward.js +306 -0
  157. package/template/coding-task-manager/lib/remote-submission-status.js +6 -0
  158. package/template/coding-task-manager/lib/restart-outcome-truth.js +20 -3
  159. package/template/coding-task-manager/lib/restart-preflight.js +9 -2
  160. package/template/coding-task-manager/lib/restore-backoff-gate.js +10 -2
  161. package/template/coding-task-manager/lib/restore-prepare-retry.js +12 -5
  162. package/template/coding-task-manager/lib/restore-resume-batch.js +22 -1
  163. package/template/coding-task-manager/lib/resume-blocked-wire.js +62 -1
  164. package/template/coding-task-manager/lib/resume-cwd.js +13 -0
  165. package/template/coding-task-manager/lib/resume-from-store.js +12 -1
  166. package/template/coding-task-manager/lib/resume-origin-facts.js +38 -1
  167. package/template/coding-task-manager/lib/resume-preflight.js +21 -16
  168. package/template/coding-task-manager/lib/review-repo-admission.js +39 -6
  169. package/template/coding-task-manager/lib/review-repo-registry.js +17 -0
  170. package/template/coding-task-manager/lib/root-delivery-runtime.js +72 -15
  171. package/template/coding-task-manager/lib/root-delivery-selection.js +37 -1
  172. package/template/coding-task-manager/lib/root-prompt-admission.js +43 -11
  173. package/template/coding-task-manager/lib/root-prompt-flow.js +175 -38
  174. package/template/coding-task-manager/lib/scrollback-archive-store.js +7 -4
  175. package/template/coding-task-manager/lib/server-memory-sampler-runtime.js +10 -0
  176. package/template/coding-task-manager/lib/server-memory-sampler.js +41 -2
  177. package/template/coding-task-manager/lib/session-analysis-memory.js +2 -0
  178. package/template/coding-task-manager/lib/session-create/codex-native-provider-gate.js +218 -0
  179. package/template/coding-task-manager/lib/session-create/codex-resume-exit-classify.js +52 -0
  180. package/template/coding-task-manager/lib/session-create/delegate-worker.js +95 -6
  181. package/template/coding-task-manager/lib/session-create/identity.js +20 -1
  182. package/template/coding-task-manager/lib/session-create/prepared.js +69 -1
  183. package/template/coding-task-manager/lib/session-create/provider-launch-context.js +77 -0
  184. package/template/coding-task-manager/lib/session-create/provider-process.js +38 -39
  185. package/template/coding-task-manager/lib/session-create/response.js +30 -12
  186. package/template/coding-task-manager/lib/session-create/transcript.js +13 -4
  187. package/template/coding-task-manager/lib/session-create/worker-continuation.js +43 -1
  188. package/template/coding-task-manager/lib/session-create/worker-notices.js +94 -5
  189. package/template/coding-task-manager/lib/session-create/worker-observability.js +99 -7
  190. package/template/coding-task-manager/lib/session-create/worker-stop.js +11 -3
  191. package/template/coding-task-manager/lib/session-create/worker-wait-watcher.js +315 -0
  192. package/template/coding-task-manager/lib/session-geometry-authority-handler.js +10 -1
  193. package/template/coding-task-manager/lib/session-host-manager.js +152 -206
  194. package/template/coding-task-manager/lib/session-jobs.js +17 -9
  195. package/template/coding-task-manager/lib/session-lifecycle.js +20 -50
  196. package/template/coding-task-manager/lib/session-model-action-preconditions.js +68 -3
  197. package/template/coding-task-manager/lib/session-resource-sampler.js +67 -9
  198. package/template/coding-task-manager/lib/session-standup.js +67 -4
  199. package/template/coding-task-manager/lib/session-status-ledger.js +42 -3
  200. package/template/coding-task-manager/lib/session-status-runtime.js +45 -1
  201. package/template/coding-task-manager/lib/session-status.js +2 -2
  202. package/template/coding-task-manager/lib/session-title-mirror.js +25 -57
  203. package/template/coding-task-manager/lib/session-transcript-trash.js +8 -1
  204. package/template/coding-task-manager/lib/startup-pointer-write.js +57 -0
  205. package/template/coding-task-manager/lib/state-sync/painted-frame-field.js +11 -0
  206. package/template/coding-task-manager/lib/storage/coding-session-model-store.js +21 -22
  207. package/template/coding-task-manager/lib/surviving-owner-bootstrap.js +7 -2
  208. package/template/coding-task-manager/lib/surviving-owner-recovery.js +2 -1
  209. package/template/coding-task-manager/lib/terminal-draft-http-status.js +25 -0
  210. package/template/coding-task-manager/lib/transcript-archiver.js +229 -229
  211. package/template/coding-task-manager/lib/transcript-frame-cache.js +12 -2
  212. package/template/coding-task-manager/lib/transcript-guard-job.js +55 -4
  213. package/template/coding-task-manager/lib/transcript-link.js +77 -0
  214. package/template/coding-task-manager/lib/transcript-manifest-store.js +8 -4
  215. package/template/coding-task-manager/lib/transcript-source.js +8 -3
  216. package/template/coding-task-manager/lib/transcript-store.js +40 -0
  217. package/template/coding-task-manager/lib/tunnel-lane-governor.js +6 -3
  218. package/template/coding-task-manager/lib/typing-interaction-trace.js +136 -2
  219. package/template/coding-task-manager/lib/typing-worker-stages.js +208 -1
  220. package/template/coding-task-manager/lib/usage-ledger/claude-parse.js +53 -2
  221. package/template/coding-task-manager/lib/usage-ledger/ingest.js +13 -4
  222. package/template/coding-task-manager/lib/usage-ledger/read.js +14 -0
  223. package/template/coding-task-manager/lib/usage-ledger/schema.js +49 -1
  224. package/template/coding-task-manager/lib/usage-ledger/store.js +337 -6
  225. package/template/coding-task-manager/lib/usage-ledger/task-read.js +123 -5
  226. package/template/coding-task-manager/lib/visible-revision-watchdog.js +8 -12
  227. package/template/coding-task-manager/lib/wake-skip-reasons.js +46 -0
  228. package/template/coding-task-manager/lib/walle-supervisor.js +98 -14
  229. package/template/coding-task-manager/lib/worker-failure-classify.js +13 -1
  230. package/template/coding-task-manager/lib/ws/input.js +1 -0
  231. package/template/coding-task-manager/lib/ws/state-sync.js +19 -2
  232. package/template/coding-task-manager/lib/ws/terminal-attach.js +32 -1
  233. package/template/coding-task-manager/lib/ws-router.js +68 -1
  234. package/template/coding-task-manager/lib/ws-send-admission.js +22 -4
  235. package/template/coding-task-manager/oauth-proxy.js +99 -55
  236. package/template/coding-task-manager/package-lock.json +24 -0
  237. package/template/coding-task-manager/package.json +3 -0
  238. package/template/coding-task-manager/protocol/message-table.json +0 -10
  239. package/template/coding-task-manager/protocol/messages.js +0 -10
  240. package/template/coding-task-manager/protocol/ws-schema.json +0 -20
  241. package/template/coding-task-manager/protocol/ws-schema.md +0 -2
  242. package/template/coding-task-manager/providers/codex-mcp-extract.js +121 -19
  243. package/template/coding-task-manager/providers/codex-mcp.js +14 -5
  244. package/template/coding-task-manager/public/css/app.css +279 -2
  245. package/template/coding-task-manager/public/css/reviews.css +4 -0
  246. package/template/coding-task-manager/public/index.html +297 -240
  247. package/template/coding-task-manager/public/js/app/machines-page.js +1252 -0
  248. package/template/coding-task-manager/public/js/app/models-tab.js +546 -36
  249. package/template/coding-task-manager/public/js/app/worktrees-panel.js +7 -5
  250. package/template/coding-task-manager/public/js/background-work.js +13 -7
  251. package/template/coding-task-manager/public/js/client-health-core.js +81 -0
  252. package/template/coding-task-manager/public/js/client-health-main.js +61 -0
  253. package/template/coding-task-manager/public/js/coding-session-model-presentation.js +44 -4
  254. package/template/coding-task-manager/public/js/core/session-commands.js +676 -0
  255. package/template/coding-task-manager/public/js/core/session-selectors.js +5 -1
  256. package/template/coding-task-manager/public/js/history-app-seam.js +26 -2
  257. package/template/coding-task-manager/public/js/history-painted-rows.js +17 -2
  258. package/template/coding-task-manager/public/js/history-reader-park.js +13 -1
  259. package/template/coding-task-manager/public/js/history-scroller.js +187 -9
  260. package/template/coding-task-manager/public/js/history-transcript-pages.js +5 -5
  261. package/template/coding-task-manager/public/js/machine-pool-shared.js +436 -0
  262. package/template/coding-task-manager/public/js/private-state-scope.js +31 -3
  263. package/template/coding-task-manager/public/js/prompt-submission-client.js +173 -4
  264. package/template/coding-task-manager/public/js/prompt-submission-tray.js +281 -0
  265. package/template/coding-task-manager/public/js/protocol.js +0 -10
  266. package/template/coding-task-manager/public/js/queue-panel-submission-reconcile.js +174 -1
  267. package/template/coding-task-manager/public/js/reviews.js +7 -3
  268. package/template/coding-task-manager/public/js/root-draft-lifetime.js +139 -2
  269. package/template/coding-task-manager/public/js/root-queue-builder-lifetime.js +16 -0
  270. package/template/coding-task-manager/public/js/row-join.js +18 -3
  271. package/template/coding-task-manager/public/js/session-model-picker.js +1 -1
  272. package/template/coding-task-manager/public/js/session-search-utils.js +46 -19
  273. package/template/coding-task-manager/public/js/session-usage-view.js +1 -0
  274. package/template/coding-task-manager/public/js/state-sync-client.js +4 -0
  275. package/template/coding-task-manager/public/js/terminal/runtime-02.js +16 -7
  276. package/template/coding-task-manager/public/js/terminal/runtime-03.js +41 -0
  277. package/template/coding-task-manager/public/js/terminal/runtime-04.js +41 -4
  278. package/template/coding-task-manager/public/js/terminal/runtime-12.js +52 -2
  279. package/template/coding-task-manager/public/js/terminal/runtime-16.js +12 -12
  280. package/template/coding-task-manager/public/js/terminal/runtime-18.js +14 -0
  281. package/template/coding-task-manager/public/js/terminal/runtime-19.js +8 -4
  282. package/template/coding-task-manager/public/js/terminal/runtime-20.js +210 -73
  283. package/template/coding-task-manager/public/js/terminal/runtime-21.js +19 -6
  284. package/template/coding-task-manager/public/js/terminal/runtime-22.js +86 -0
  285. package/template/coding-task-manager/public/js/terminal-history-viewport.js +1 -3
  286. package/template/coding-task-manager/public/js/terminal-wheel.js +39 -2
  287. package/template/coding-task-manager/public/js/walle/chat-conversation.js +11 -3
  288. package/template/coding-task-manager/public/js/walle-session.js +30 -7
  289. package/template/coding-task-manager/public/m/app.css +226 -8
  290. package/template/coding-task-manager/public/m/app.js +502 -9
  291. package/template/coding-task-manager/public/m/index.html +38 -1
  292. package/template/coding-task-manager/public/m/machines.js +1313 -0
  293. package/template/coding-task-manager/public/m/pool-failover.js +391 -0
  294. package/template/coding-task-manager/public/m/private-queue-actions.js +26 -7
  295. package/template/coding-task-manager/public/m/sw.js +5 -0
  296. package/template/coding-task-manager/queue-engine.js +13 -0
  297. package/template/coding-task-manager/server/approvals.js +5 -6
  298. package/template/coding-task-manager/server/boot.js +136 -17
  299. package/template/coding-task-manager/server.js +964 -425
  300. package/template/coding-task-manager/session-integrity.js +7 -9
  301. package/template/coding-task-manager/workers/db-owner-worker.js +53 -7
  302. package/template/coding-task-manager/workers/read-pool-worker.js +17 -6
  303. package/template/coding-task-manager/workers/session-host-pool-process.js +18 -4
  304. package/template/coding-task-manager/workers/session-host-process.js +14 -3
  305. package/template/coding-task-manager/workers/transcript-archive-worker.js +1 -1
  306. package/template/coding-task-manager/workers/worktree-worker.js +34 -0
  307. package/template/package-lock.json +2 -2
  308. package/template/package.json +1 -1
  309. package/template/scripts/conversation-storage-v2-migration.js +78 -11
  310. package/template/shared/conversation-storage-v2-contract.js +42 -6
  311. package/template/shared/fixtures/worker-status-payloads.json +392 -0
  312. package/template/shared/git-remote-key.js +83 -0
  313. package/template/shared/host-identity.js +98 -2
  314. package/template/shared/owner-write-safety.js +135 -0
  315. package/template/shared/public-runtime.js +13 -5
  316. package/template/shared/release-schema.json +3 -2
  317. package/template/shared/report-citations.js +14 -0
  318. package/template/shared/sqlite-write-lock.js +361 -223
  319. package/template/shared/storage/contract.js +35 -3
  320. package/template/wall-e/agent.js +21 -0
  321. package/template/wall-e/api-walle.js +5 -0
  322. package/template/wall-e/bin/brain-purge-legacy-ctm.js +3 -2
  323. package/template/wall-e/bin/walle-mcp-stdio.js +12 -6
  324. package/template/wall-e/brain/helper-rules-schema.js +129 -0
  325. package/template/wall-e/brain/migrations.js +51 -22
  326. package/template/wall-e/brain/model-deployment-facts.js +31 -1
  327. package/template/wall-e/brain/owner-call-whitelist.js +16 -0
  328. package/template/wall-e/brain/schema-ensures.js +22 -0
  329. package/template/wall-e/brain.js +1 -1
  330. package/template/wall-e/coding/host-capabilities.js +2 -2
  331. package/template/wall-e/coding/journal-dispatcher.js +51 -0
  332. package/template/wall-e/coding/session-journal.js +28 -40
  333. package/template/wall-e/coding-orchestrator.js +23 -26
  334. package/template/wall-e/fly.toml +1 -1
  335. package/template/wall-e/http/auth.js +18 -1
  336. package/template/wall-e/http/blog-views-api.js +318 -0
  337. package/template/wall-e/http/helper-rules-api.js +321 -0
  338. package/template/wall-e/http/model-proxy-providers.js +3 -1
  339. package/template/wall-e/http/telemetry-api.js +12 -0
  340. package/template/wall-e/lib/blog-view-policy.js +88 -0
  341. package/template/wall-e/lib/blog-view-store.js +169 -0
  342. package/template/wall-e/lib/client-config-lock.js +2 -1
  343. package/template/wall-e/lib/coding-journal-writer-child-client.js +67 -18
  344. package/template/wall-e/lib/error-kinds.js +10 -1
  345. package/template/wall-e/lib/feature-manifest.js +1 -1
  346. package/template/wall-e/lib/helper-rules/cell-state.js +123 -0
  347. package/template/wall-e/lib/helper-rules/evaluate.js +357 -0
  348. package/template/wall-e/lib/helper-rules/explore-rubric.md +12 -0
  349. package/template/wall-e/lib/helper-rules/grade.js +277 -0
  350. package/template/wall-e/lib/helper-rules/label.js +247 -0
  351. package/template/wall-e/lib/helper-rules/lead-call-hook.js +522 -0
  352. package/template/wall-e/lib/helper-rules/store.js +667 -0
  353. package/template/wall-e/lib/mcp-integration.js +5 -6
  354. package/template/wall-e/lib/runtime-worker-pool.js +1 -0
  355. package/template/wall-e/lib/scheduler-worker-jobs.js +6 -0
  356. package/template/wall-e/lib/telemetry-db-schema.js +37 -0
  357. package/template/wall-e/lib/telemetry-process-client.js +76 -1
  358. package/template/wall-e/llm/default-fallback.js +4 -0
  359. package/template/wall-e/llm/model-catalog.js +13 -4
  360. package/template/wall-e/llm/model-roles.js +6 -0
  361. package/template/wall-e/llm/portkey-http.js +150 -0
  362. package/template/wall-e/llm/portkey-sync.js +15 -145
  363. package/template/wall-e/llm/worker-mcp-routing.js +61 -0
  364. package/template/wall-e/llm/worker-route.js +43 -1
  365. package/template/wall-e/loops/brain-custom-writes.js +45 -0
  366. package/template/wall-e/mcp-protocol-contract.js +41 -1
  367. package/template/wall-e/mcp-server.js +13 -2
  368. package/template/wall-e/mcp-worker-schemas.js +20 -2
  369. package/template/wall-e/memory/ctm-context-client.js +25 -5
  370. package/template/wall-e/shared/release-schema.json +3 -2
  371. package/template/wall-e/shared/sqlite-write-lock.js +361 -223
  372. package/template/wall-e/shared/storage/contract.js +35 -3
  373. package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
  374. package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +6 -0
  375. package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +7 -2
  376. package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +7 -2
  377. package/template/wall-e/tools/local-tools.js +12 -5
  378. package/template/wall-e/workers/brain-owner-worker.js +9 -0
  379. package/template/wall-e/workers/coding-journal-writer-child.js +8 -1
  380. package/template/wall-e/workers/telemetry-worker.js +8 -0
  381. package/template/coding-task-manager/lib/giant-freeze-park.js +0 -31
@@ -128,9 +128,6 @@ function lifecycleConfigFromEnv(env = process.env) {
128
128
  prewarm: envBool(env, 'CTM_PREWARM', false),
129
129
  prewarmTtlMs: envInt(env, 'CTM_PREWARM_TTL_MS', 60_000, 1_000),
130
130
  prewarmTopK: envInt(env, 'CTM_PREWARM_TOP_K', 1, 1),
131
- freezeTier: idlePark && envBool(env, 'CTM_FREEZE_TIER', false),
132
- freezeAfterMs: envInt(env, 'CTM_FREEZE_AFTER_MS', 15 * 60_000, 1_000),
133
- freezeParkAfterMs: 2 * 60 * 60_000,
134
131
  // WP-3: default is derived from host RAM (see defaultSessionRssBudgetMB), so the
135
132
  // MemoryBudgetController is live out of the box. `CTM_SESSION_RSS_BUDGET_MB=0`
136
133
  // remains the explicit disable — a 0 budget makes observe() inert again.
@@ -147,11 +144,7 @@ function lifecycleConfigFromEnv(env = process.env) {
147
144
 
148
145
  function automaticParkSweepPolicy(config, options = {}) {
149
146
  const pressure = ['warn', 'critical'].includes(options.pressure) ? options.pressure : 'normal';
150
- const freezeTier = options.freezeTier === true;
151
- const configuredThresholdMs = Math.max(0, Number(config?.idleParkAfterMs) || 0);
152
- const idleParkAfterMs = freezeTier
153
- ? Math.max(configuredThresholdMs, Number(config?.freezeParkAfterMs) || 0)
154
- : configuredThresholdMs;
147
+ const idleParkAfterMs = Math.max(0, Number(config?.idleParkAfterMs) || 0);
155
148
  const disconnectCollapseMs = Math.max(
156
149
  idleParkAfterMs,
157
150
  Number(config?.disconnectCollapseMs) || 0,
@@ -223,7 +216,7 @@ function normalizeClientKind(value) {
223
216
  function evaluatePark(session, host, config) {
224
217
  const now = Number(host?.now) || Date.now();
225
218
  const protections = [];
226
- if (!session || !['live', 'frozen'].includes(session.state)) protections.push('not-live');
219
+ if (!session || session.state !== 'live') protections.push('not-live');
227
220
  if (session?.resumable === false) protections.push('not-resumable'); if (session?.parkable === false) protections.push('not-parkable-class');
228
221
  if (session?.liveStatus === 'running' || session?.liveStatus === 'generating' || session?.liveStatus === 'working') protections.push('running');
229
222
  if (session?.liveStatus === 'waiting_input' || session?.liveStatus === 'resuming') protections.push('waiting-input');
@@ -490,37 +483,6 @@ function describePressureCause(signals = {}, thresholds = {}, effectiveLevel = '
490
483
  || { kind: 'recovering', level, value: null };
491
484
  }
492
485
 
493
- function evaluateFreeze(session, host, config) {
494
- if (config?.freezeTier !== true) {
495
- return { eligible: false, protections: ['disabled'], idleMs: 0, thresholdMs: Number(config?.freezeAfterMs) || 0 };
496
- }
497
- if (!session || session.state !== 'live') {
498
- return { eligible: false, protections: ['not-live'], idleMs: 0, thresholdMs: Number(config?.freezeAfterMs) || 0 };
499
- }
500
- return evaluatePark(session, host, {
501
- ...config,
502
- idleParkAfterMs: Number(config.freezeAfterMs) || 15 * 60_000,
503
- disconnectCollapseMs: Number(config.freezeAfterMs) || 15 * 60_000,
504
- });
505
- }
506
-
507
- function selectFreezeVictim(sessions, host, config) {
508
- const eligible = [];
509
- for (const session of sessions || []) {
510
- const result = evaluateFreeze(session, host, config);
511
- if (result.eligible) eligible.push({ session, result });
512
- }
513
- eligible.sort((a, b) => {
514
- const activityDelta = (Number(a.session.lastMeaningfulActivityAt) || 0)
515
- - (Number(b.session.lastMeaningfulActivityAt) || 0);
516
- if (activityDelta !== 0) return activityDelta;
517
- const rssDelta = sessionMemoryMB(b.session) - sessionMemoryMB(a.session);
518
- if (rssDelta !== 0) return rssDelta;
519
- return String(a.session.id).localeCompare(String(b.session.id));
520
- });
521
- return eligible[0]?.session || null;
522
- }
523
-
524
486
  class PressureController {
525
487
  constructor() {
526
488
  this.level = 'normal';
@@ -766,6 +728,10 @@ function percentile(values, ratio) {
766
728
  return sorted[Math.min(sorted.length - 1, Math.max(0, Math.ceil(sorted.length * ratio) - 1))];
767
729
  }
768
730
 
731
+ // WP-3 (§5 D-8): the skip-reason vocabulary lives in its own module (so server.js can name tokens
732
+ // without growing); re-exported here for the existing importers.
733
+ const { WAKE_SKIP_REASONS } = require('./wake-skip-reasons');
734
+
769
735
  class LifecycleDiagnostics {
770
736
  constructor({ maxEvents = 200 } = {}) {
771
737
  this.maxEvents = Math.max(1, maxEvents);
@@ -773,12 +739,15 @@ class LifecycleDiagnostics {
773
739
  this.activationByClient = {};
774
740
  this.parkByReason = {};
775
741
  this.prewarmByOutcome = {};
776
- this.freezeByAction = {};
777
742
  this.shadow = { candidates: 0, falsePositives: 0, projectedReclaimMB: 0 };
778
743
  this.pressureTransitions = 0;
779
744
  this.events = [];
780
745
  this.wake = new Map();
781
746
  this.wakeOutcomes = {};
747
+ // WP-3 (§5 D-6): skip-reason counter for the pre-gate deferred-restore bails -- the ones that
748
+ // precede _wakeStartedAt and so can never be counted by wakeOutcomes ("65 deferred, zero
749
+ // wakes" was invisible for exactly this reason).
750
+ this.wakeSkips = {};
782
751
  }
783
752
 
784
753
  _count(target, key) { target[key] = (target[key] || 0) + 1; }
@@ -813,12 +782,6 @@ class LifecycleDiagnostics {
813
782
  this._event({ type: 'prewarm', id, outcome: normalized, reason: String(reason || '').slice(0, 48) });
814
783
  }
815
784
 
816
- recordFreeze({ id, action, idleMs = 0, reason = '' }) {
817
- const normalized = String(action || 'unknown').slice(0, 32);
818
- this._count(this.freezeByAction, normalized);
819
- this._event({ type: 'freeze', id, action: normalized, idleMs: Math.max(0, idleMs), reason: String(reason || '').slice(0, 48) });
820
- }
821
-
822
785
  recordShadow({ id, action = 'candidate', rssMB = 0, activityAfterMs = 0, reason = '', signals = {} }) {
823
786
  if (action === 'candidate') {
824
787
  this.shadow.candidates += 1;
@@ -863,6 +826,14 @@ class LifecycleDiagnostics {
863
826
  this._event({ type: 'wake-outcome', id, provider, outcome, phases: { ...phases } });
864
827
  }
865
828
 
829
+ // WP-3 (§5 D-6/D-8): the sibling of recordWakeOutcome for the pre-gate bails. `reason` is a
830
+ // WAKE_SKIP_REASONS token (CamelCase, machine-readable — the K8s-condition convention); a
831
+ // caller that still passes a legacy human string is recorded verbatim rather than dropped.
832
+ recordWakeSkip({ id = '', provider = 'unknown', reason = 'Unclassified' } = {}) {
833
+ this._count(this.wakeSkips, reason);
834
+ this._event({ type: 'wake-skip', id, provider, reason });
835
+ }
836
+
866
837
  snapshot(extra = {}) {
867
838
  const wakeLatency = {};
868
839
  for (const [provider, entry] of this.wake) {
@@ -876,10 +847,10 @@ class LifecycleDiagnostics {
876
847
  activationByClient: { ...this.activationByClient },
877
848
  parkByReason: { ...this.parkByReason },
878
849
  prewarmByOutcome: { ...this.prewarmByOutcome },
879
- freezeByAction: { ...this.freezeByAction },
880
850
  shadow: { ...this.shadow },
881
851
  pressureTransitions: this.pressureTransitions,
882
852
  wakeOutcomes: { ...this.wakeOutcomes },
853
+ wakeSkips: { ...this.wakeSkips },
883
854
  wakeLatency,
884
855
  events: [...this.events],
885
856
  ...extra,
@@ -890,6 +861,7 @@ class LifecycleDiagnostics {
890
861
  module.exports = {
891
862
  ActivationGate,
892
863
  LifecycleDiagnostics,
864
+ WAKE_SKIP_REASONS,
893
865
  MemoryBudgetController,
894
866
  PressureController,
895
867
  automaticParkSweepPolicy,
@@ -901,11 +873,9 @@ module.exports = {
901
873
  classifyWakeOutcome,
902
874
  derivePressure,
903
875
  describePressureCause,
904
- evaluateFreeze,
905
876
  evaluateManualPark,
906
877
  evaluatePark,
907
878
  summarizeParkEligibility,
908
- selectFreezeVictim,
909
879
  selectParkVictim,
910
880
  sessionMemoryMB,
911
881
  };
@@ -72,6 +72,28 @@ function parkRefusalDetail(protections) {
72
72
  return 'The session could not be restarted right now. Try again in a moment.';
73
73
  }
74
74
 
75
+ // WP-0 D-10: a refusal code with no hand-written sentence still gets a family sentence that NAMES
76
+ // the code, so no refusal can reach the person as a generic line. Checked after the explicit map and
77
+ // before any caller-supplied message.
78
+ const HISTORY_REFUSAL_FAMILIES = Object.freeze([
79
+ [/^unsupported_native_/, 'This history carries a record type that has not been qualified for transfer.'],
80
+ [/^native_world_state_/, 'The native Codex world state could not be replayed safely.'],
81
+ [/^native_history_/, 'The native Codex history could not be prepared for transfer.'],
82
+ [/^tool_call_/, 'This history has an incomplete tool exchange, so it cannot be transferred safely.'],
83
+ [/^history_/, 'The source history could not be verified for this provider change.'],
84
+ [/^source_/, 'The source session could not be verified for this provider change.'],
85
+ [/^target_/, 'The target session could not be verified for this provider change.'],
86
+ [/^fork_/, 'The native fork could not be verified in Codex history.'],
87
+ [/^native_(?:rpc|process|fork|adapter|readback|initialize)_/, 'The native Codex process could not complete the handoff.'],
88
+ ]);
89
+
90
+ function historyRefusalFamilyMessage(reason, tail) {
91
+ for (const [pattern, sentence] of HISTORY_REFUSAL_FAMILIES) {
92
+ if (pattern.test(reason)) return `${sentence} (code: ${reason}) ${tail}`;
93
+ }
94
+ return '';
95
+ }
96
+
75
97
  // History checks run before the source is parked. Keep their bounded reason codes useful to the
76
98
  // person making the change without exposing scanner records, paths, or provider credentials.
77
99
  function historyPreparationMessage(reason, suppliedMessage = '', { postPark = false } = {}) {
@@ -83,10 +105,13 @@ function historyPreparationMessage(reason, suppliedMessage = '', { postPark = fa
83
105
  source_checkpoint_changed_after_park: `The source checkpoint changed during handoff verification. ${recovery}`,
84
106
  source_config_policy_unqualified: `The captured Codex configuration changed during handoff verification. ${recovery}`,
85
107
  native_history_control_failed: `Codex could not create the native history checkpoint. ${recovery}`,
108
+ native_handoff_inject_failed: `The handoff summary could not be written into the new Codex thread. ${recovery}`,
109
+ fork_target_handoff_missing: `The new Codex thread does not prove it received the handoff summary for its encrypted history. ${recovery}`,
86
110
  fork_readback_unverified: `The native fork could not be verified in Codex history. ${recovery}`,
87
111
  fork_journal_owner_changed: `The transition owner changed during handoff. ${recovery}`,
88
112
  };
89
- return messages[reason] || `The target handoff could not be verified. ${recovery}`;
113
+ return messages[reason] || historyRefusalFamilyMessage(reason, recovery)
114
+ || `The target handoff could not be verified. ${recovery}`;
90
115
  }
91
116
  const messages = {
92
117
  native_target_profile_unavailable: 'The native Codex model does not publish the context limits needed to verify this history move. The current session is still running unchanged.',
@@ -100,7 +125,6 @@ function historyPreparationMessage(reason, suppliedMessage = '', { postPark = fa
100
125
  // WP-2 (docs/codex-encrypted-history-lead-switch-2026-10-17.html §5/§8): the name stays; the copy
101
126
  // now says what the user actually has (an encrypted summary) and what did NOT happen to them.
102
127
  encrypted_history_not_portable: 'Earlier history exists only as an encrypted summary that the target model cannot read, so it cannot be carried over. The current session is still running unchanged.',
103
- compacted_source_fork_unqualified: 'This source history was compacted, and its active checkpoint cannot yet be verified across providers. The current session is still running unchanged.',
104
128
  source_compaction_unqualified: 'The source history is too large for the target, and safely compacting it is not supported yet. The current session is still running unchanged.',
105
129
  history_estimate_unknown: 'The source history size could not be verified safely. The current session is still running unchanged.',
106
130
  target_compaction_hash_unknown: 'The target profile cannot verify the source history compaction settings. The current session is still running unchanged.',
@@ -109,9 +133,41 @@ function historyPreparationMessage(reason, suppliedMessage = '', { postPark = fa
109
133
  unpaired_tool_history: 'This history has an incomplete tool exchange, so it cannot be transferred safely. The current session is still running unchanged.',
110
134
  source_origin_unknown: 'The source provider could not be verified. The current session is still running unchanged.',
111
135
  source_origin_unsupported: 'This source provider has no qualified history handoff yet. The current session is still running unchanged.',
136
+ transition_cancelled: 'The history check was cancelled before it finished. The current session is still running unchanged.',
137
+ history_scan_incomplete: 'The source history scan did not finish, so nothing was moved. The current session is still running unchanged.',
138
+ source_checkpoint_unknown: 'The source history has no verified checkpoint to carry over. The current session is still running unchanged.',
139
+ source_previous_model_mismatch: 'The model recorded on the source history does not match the captured source model. The current session is still running unchanged.',
140
+ source_model_changed: 'The source model changed while the history was being prepared. The current session is still running unchanged.',
141
+ lead_source_changed: 'The source session this switch started from is no longer the live one. The current session is still running unchanged.',
142
+ provider_identity_unknown: 'The source or target provider for this history move could not be identified. The current session is still running unchanged.',
143
+ target_budget_unknown: 'The target model does not publish the context budget needed to carry this history over. The current session is still running unchanged.',
144
+ history_estimate_evidence_unknown: 'The source history size was measured, but not in a way that can be verified for this move. The current session is still running unchanged.',
145
+ compaction_hash_mismatch: 'The source and target history compaction settings disagree, so the move cannot be verified safely. The current session is still running unchanged.',
146
+ native_transition_policy_unknown: 'The Codex permission policy for the source or the target could not be read. The current session is still running unchanged.',
147
+ native_transition_policy_mismatch: 'The Codex permission policy on the target differs from the source, so the history cannot be moved safely. The current session is still running unchanged.',
148
+ native_fork_adapter_unqualified: 'This Codex installation or adapter is not qualified to carry this history over. The current session is still running unchanged.',
149
+ native_policy_override_unqualified: 'This source session uses a Codex policy override that cannot yet be reproduced safely for history transfer. The current session is still running unchanged.',
150
+ native_token_usage_record_invalid: 'The Codex usage record for this session could not be verified, so the history size cannot be trusted. The current session is still running unchanged.',
151
+ history_media_not_portable: 'This history contains an image, and the target model cannot receive images. The current session is still running unchanged.',
152
+ // WP-2 (docs/codex-lead-routed-switch-history-handoff-2026-10-21.html §5 D-5/D-6, §6.5): the
153
+ // encrypted-compaction path. The source model writes a plaintext handoff before the switch, and
154
+ // each way that handoff can fail says which one it was.
155
+ encrypted_compaction_multiple: 'This history holds more than one encrypted compaction, and only one of them can be replaced by a handoff summary. The current session is still running unchanged.',
156
+ encrypted_compaction_handoff_unavailable: 'This history exists only as an encrypted summary, and the model that wrote it cannot be asked to write a plaintext handoff for this provider change. The current session is still running unchanged.',
157
+ handoff_fork_failed: 'The handoff summary could not be started from this session\'s history. The current session is still running unchanged.',
158
+ handoff_turn_failed: 'The source model did not finish writing the handoff summary. The current session is still running unchanged.',
159
+ handoff_empty: 'The source model returned a handoff summary that is too short to carry this history over. The current session is still running unchanged.',
160
+ handoff_too_long: 'The source model returned a handoff summary that is too long to carry this history over. The current session is still running unchanged.',
161
+ handoff_marker_in_text: 'The handoff summary contains text reserved for this move, so it cannot be trusted. The current session is still running unchanged.',
162
+ handoff_timeout: 'The source model did not finish writing the handoff summary in time. The current session is still running unchanged.',
163
+ // Raised while the forked target is being written, but the code is enumerated from its own
164
+ // source file, so it carries a sentence on both paths rather than falling back to the generic one.
165
+ native_handoff_inject_failed: 'The handoff summary could not be written into the new Codex thread. The current session is still running unchanged.',
112
166
  };
113
167
  const safe = typeof suppliedMessage === 'string' ? suppliedMessage.trim().slice(0, 240) : '';
114
- return messages[reason] || safe || 'History could not be verified for this provider change. The current session is still running unchanged.';
168
+ return messages[reason]
169
+ || historyRefusalFamilyMessage(reason, 'The current session is still running unchanged.')
170
+ || safe || 'History could not be verified for this provider change. The current session is still running unchanged.';
115
171
  }
116
172
 
117
173
  function refusal(status, code, extra) {
@@ -371,6 +427,15 @@ function reconstructSessionModelTransitionSource({ sessionId, source, sourceLaun
371
427
  const rebound = require('./launch-model-override').ensureCodexResumeTarget(args, source.providerThreadId);
372
428
  if (!rebound.ok) return null;
373
429
  args = rebound.args;
430
+ } else if (agent === 'claude') {
431
+ // The SAME canonical normalization for a native Claude source: the recorded capture may name its
432
+ // own thread through an explicit `--session-id` (the identity CTM injected at startup) and may
433
+ // still carry that launch's original positional prompt. Only the canonical owner's own
434
+ // `--resume <source thread>` is the source recipe, so the existing extractor below reads a
435
+ // rebound argv instead of rejecting the capture outright.
436
+ const rebound = require('./launch-model-override').ensureClaudeResumeTarget(args, source.providerThreadId);
437
+ if (!rebound.ok) return null;
438
+ args = rebound.args;
374
439
  }
375
440
  if ((require('./session-history').extractResumeTarget(agent, args)?.sessionId || '') !== String(source.providerThreadId)) return null;
376
441
  return { ...context, args };
@@ -79,14 +79,14 @@ function splitAgentDescendantRows(rows, agentPid) {
79
79
  const agentRssMB = agentRow ? Math.round(((Number(agentRow.rssKB) || 0) / 1024) * 100) / 100 : 0;
80
80
  const pending = [...(byParent.get(agentPid) || [])];
81
81
  const seen = new Set();
82
- let descendantRssKB = 0, descendantGroupLeaderCount = 0; // D-10: a descendant leading its own process group (a background shell's wrapper)
82
+ let descendantRssKB = 0, descendantCpuTimeMs = 0, descendantGroupLeaderCount = 0; // D-10: a descendant leading its own process group (a background shell's wrapper)
83
83
  while (pending.length) {
84
84
  const pid = pending.pop();
85
85
  if (seen.has(pid)) continue;
86
86
  seen.add(pid);
87
87
  const row = byPid.get(pid);
88
88
  if (!row) continue;
89
- descendantRssKB += Number(row.rssKB) || 0; if (row.pgid === row.pid) descendantGroupLeaderCount += 1;
89
+ descendantRssKB += Number(row.rssKB) || 0; descendantCpuTimeMs += Number(row.cpuTimeMs) || 0; if (row.pgid === row.pid) descendantGroupLeaderCount += 1;
90
90
  pending.push(...(byParent.get(pid) || []));
91
91
  }
92
92
  return {
@@ -99,7 +99,7 @@ function splitAgentDescendantRows(rows, agentPid) {
99
99
  // invisible to a pgid-only rollup).
100
100
  descendantPids: [...seen],
101
101
  descendantGroupLeaderCount,
102
- descendantRssMB: Math.round((descendantRssKB / 1024) * 100) / 100,
102
+ descendantRssMB: Math.round((descendantRssKB / 1024) * 100) / 100, descendantCpuTimeMs, // WP-0 (bg-shell-liveness): Σ cpuTimeMs of every visited descendant
103
103
  };
104
104
  }
105
105
 
@@ -126,6 +126,33 @@ function parseFootprintJson(text) {
126
126
  return results;
127
127
  }
128
128
 
129
+ // WP-A P-1 (freeze-probes): the CATEGORIZED `footprint -j` document (no `--noCategories`) --
130
+ // { unit, 'bytes per unit', processes: [{ pid, footprint, categories: { name: { dirty, swapped, ... } } }] }.
131
+ // The 4<->9 GB server swing measured 2026-10-21 lives entirely in the "Malloc Large" category, which
132
+ // the --noCategories summary above cannot name. Pure; returns { totalMB, top: [{name,dirtyMB,swappedMB}] }
133
+ // for the FIRST process, null for unparseable input or a non-byte unit (same rules as parseFootprintJson).
134
+ function parseFootprintRegions(text) {
135
+ let doc;
136
+ try { doc = JSON.parse(String(text ?? '')); } catch { return null; } // not JSON at all (empty stdout on a failed run)
137
+ if (!doc || typeof doc !== 'object' || Array.isArray(doc)) return null;
138
+ if (doc.unit !== 'byte') return null;
139
+ const bytesPerUnit = Number(doc['bytes per unit']) || 1;
140
+ const proc = (Array.isArray(doc.processes) ? doc.processes : []).find(entry => entry && typeof entry === 'object');
141
+ if (!proc) return null;
142
+ const mb = (bytes) => Math.round((bytes / 1048576) * 10) / 10;
143
+ const rows = Object.entries(proc.categories && typeof proc.categories === 'object' ? proc.categories : {})
144
+ .map(([name, region]) => {
145
+ const dirty = (Number(region?.dirty) || 0) * bytesPerUnit;
146
+ const swapped = (Number(region?.swapped) || 0) * bytesPerUnit;
147
+ return { name: String(name).slice(0, 40), dirtyMB: mb(dirty), swappedMB: mb(swapped), total: dirty + swapped };
148
+ })
149
+ .sort((a, b) => b.total - a.total);
150
+ return {
151
+ totalMB: Number.isFinite(proc.footprint) ? mb(proc.footprint * bytesPerUnit) : null,
152
+ top: rows.slice(0, 6).map(({ name, dirtyMB, swappedMB }) => ({ name, dirtyMB, swappedMB })),
153
+ };
154
+ }
155
+
129
156
  // Cleanup never turns an already-decided sample (number or D-3 null) into a rejected sweep.
130
157
  function removeTempFileQuietly(file) {
131
158
  return fs.promises.rm(file, { force: true }).then(() => true, () => false);
@@ -441,18 +468,28 @@ class SessionResourceSampler {
441
468
  return { agentPid: Number(agentPid), descendantPids: split.descendantPids, descendantGroupLeaderCount: split.descendantGroupLeaderCount };
442
469
  }
443
470
 
471
+ // D-9 (WP-6, clock-free-tests-followups-2026-10-23 §5 D-9/§8 WP-6): ONE whole-table read a caller
472
+ // can share across every rollup in one sweep. Same command/args/limits as sampleProcessGroup's own
473
+ // read, and the same maxBuffer: `ps -axo …` prints ~1.37 MB on this host, above Node's 1 MiB default.
474
+ async sampleProcessTable() {
475
+ const psCommand = this.platform === 'darwin' ? '/bin/ps' : 'ps';
476
+ const { stdout } = await this.exec(psCommand, PS_ARGS, { timeout: 10_000, maxBuffer: 8 * 1024 * 1024 });
477
+ return parseProcessTable(stdout);
478
+ }
479
+
444
480
  // D-4: the post-kill census. One ps read; the pgid rollup as before, plus every row whose pid is in
445
481
  // `descendantPidsBefore` (and not already in the group). Survivors are merged into pids/procCount/rssMB/cpuPct.
446
482
  // A call with no second argument (every pre-existing caller) behaves exactly as before.
447
- async sampleProcessGroup(pgid, descendantPidsBefore = []) {
448
- const psCommand = this.platform === 'darwin' ? '/bin/ps' : 'ps';
449
- const { stdout } = await this.exec(psCommand, PS_ARGS, { timeout: 10_000, maxBuffer: 8 * 1024 * 1024 });
450
- const rows = parseProcessTable(stdout);
451
- const group = rollupProcessGroup(rows, pgid);
483
+ // D-9: `{ rows }` lets a sweep hand in the ONE table it already read (sampleProcessTable above) —
484
+ // then this call execs nothing. The rollup/survivor logic below is shared verbatim by both paths,
485
+ // so a caller that supplies rows cannot diverge from one that does not.
486
+ async sampleProcessGroup(pgid, descendantPidsBefore = [], { rows = null } = {}) {
487
+ const table = Array.isArray(rows) ? rows : await this.sampleProcessTable();
488
+ const group = rollupProcessGroup(table, pgid);
452
489
  const before = new Set((descendantPidsBefore || []).map(Number));
453
490
  if (!before.size) return group;
454
491
  const have = new Set(group.pids);
455
- const survivors = rows.filter(row => before.has(Number(row.pid)) && !have.has(row.pid)).sort((a, b) => a.pid - b.pid);
492
+ const survivors = table.filter(row => before.has(Number(row.pid)) && !have.has(row.pid)).sort((a, b) => a.pid - b.pid);
456
493
  if (!survivors.length) return group;
457
494
  const rssKB = survivors.reduce((sum, row) => sum + (Number(row.rssKB) || 0), 0);
458
495
  const cpuPct = survivors.reduce((sum, row) => sum + (Number(row.cpuPct) || 0), 0);
@@ -540,6 +577,26 @@ class SessionResourceSampler {
540
577
  return results;
541
578
  }
542
579
 
580
+ // WP-A P-1: ONE fresh categorized `/usr/bin/footprint -j <tmp> <pid>` for the MAIN pid, every call.
581
+ // Deliberately NOT the TTL-cached sampleFootprints() path: a stale cache entry cannot name the region
582
+ // that just grew. Same gate/tmp/limits/cleanup mechanics as _sampleFootprintBatch, minus the cache.
583
+ // Never rejects: null on non-darwin, on the primary-only gate, or on any failure. Categorized
584
+ // `footprint` does not suspend its target (measured 2026-10-21), unlike vmmap/sample/leaks.
585
+ async sampleFootprintRegions(pid) {
586
+ const key = Number(pid);
587
+ if (this.platform !== 'darwin' || !(key > 0)) return null;
588
+ if (this.exec === execFileAsync && !hostIdentityAccessAllowed({ env: this.env })) return null;
589
+ const tmp = path.join(os.tmpdir(), `ctm-footprint-regions-${process.pid}-${crypto.randomUUID()}.json`);
590
+ try {
591
+ await this.exec(FOOTPRINT_BIN, ['-j', tmp, String(key)], { timeout: 10_000, maxBuffer: 4 * 1024 * 1024 });
592
+ return parseFootprintRegions(await fs.promises.readFile(tmp, 'utf8'));
593
+ } catch {
594
+ return null; // footprint missing/denied or temp file unreadable (D-3: null, never a throw)
595
+ } finally {
596
+ await removeTempFileQuietly(tmp);
597
+ }
598
+ }
599
+
543
600
  // D-1: sweep counterpart to sampleFootprints() (same batch and TTL cache), resolving to
544
601
  // `{ footprintMB, sampledAt }` per pid so a consumer can tell how fresh an entry is.
545
602
  async sampleFootprintSnapshots({ pids, now } = {}) {
@@ -558,6 +615,7 @@ module.exports = {
558
615
  FOOTPRINT_SAMPLE_TTL_MS,
559
616
  hostMemoryFromVmStat,
560
617
  parseFootprintJson,
618
+ parseFootprintRegions,
561
619
  parseMemoryPressure,
562
620
  parsePageouts,
563
621
  parseProcessTable,
@@ -882,6 +882,15 @@ function classifySessionStandup(session, signals = {}, now = Date.now()) {
882
882
  resume: session?.resume?.schemaVersion === 2 ? session.resume : null,
883
883
  priority: LANE_ORDER.get(lane) ?? 99, backgroundWork: session?.backgroundWork || null, statusReason: session?.statusReason || '',
884
884
  verdictAt: Number(session?.verdictAt) || Number(session?.liveStatusAt) || 0,
885
+ // WP-3 (remote-hub-followups D-9): the machine tag rides the card IFF the input session carries
886
+ // it. `_buildStandupSnapshot` stamps local standup sessions EMPTY and remote (box) sessions with
887
+ // the registry values, but ONLY when at least one machine is registered — so a zero-machine hub
888
+ // adds no key here and its snapshot stays byte-identical to the pre-WP-3 output (AC-3a).
889
+ ...(session && Object.prototype.hasOwnProperty.call(session, 'machine') ? {
890
+ machine: String(session.machine == null ? '' : session.machine).trim(),
891
+ machineName: String(session.machineName == null ? '' : session.machineName).trim(),
892
+ machineHealth: String(session.machineHealth == null ? '' : session.machineHealth).trim(),
893
+ } : {}),
885
894
  };
886
895
  // WP1C: the card's bounded own/null authority fields and its shared model label, applied to THIS card
887
896
  // object after it is built (see applyStandupModelAuthority in ./coding-session-model-public).
@@ -970,13 +979,13 @@ function isStandupStartupTaskSeedEnabled(env = process.env) {
970
979
  // existing classifier can run through unmodified, so a "seeded" card gets exactly the same
971
980
  // lane/priority logic as a live one — just starting from persisted metadata instead of a
972
981
  // live stream.
973
- function startupTaskPlaceholderSession(task) {
982
+ function startupTaskPlaceholderSession(task, titleInfo) {
974
983
  if (!task) return null;
975
984
  const id = String(task.session_id || task.ctm_session_id || '').trim();
976
985
  if (!id) return null;
977
986
  const placeholder = {
978
987
  id,
979
- label: task.label || '',
988
+ label: String(titleInfo?.title || '').trim() || task.label || '', // D-13/S-26: durable ctm_sessions title; echo only if no row
980
989
  cmd: task.cmd || '',
981
990
  cwd: task.cwd || '',
982
991
  project: task.cwd || '',
@@ -1015,15 +1024,16 @@ function startupTaskPlaceholderSession(task) {
1015
1024
  // a startup task appearing more than once). Callers union the result onto their live session
1016
1025
  // list; kept separate (rather than mutating/returning a merged array) so the server can push
1017
1026
  // each placeholder through the same standupPairs/attention wiring live sessions use.
1018
- function selectUnmatchedStartupTaskPlaceholders(existingSessions, startupTasks) {
1027
+ function selectUnmatchedStartupTaskPlaceholders(existingSessions, startupTasks, titlesForIds) {
1019
1028
  const seen = new Set();
1020
1029
  for (const session of existingSessions || []) {
1021
1030
  const id = String(session?.id || session?.sessionId || '').trim();
1022
1031
  if (id) seen.add(id);
1023
1032
  }
1033
+ const titlesById = typeof titlesForIds === 'function' ? titlesForIds((startupTasks || []).map((t) => t?.session_id || t?.ctm_session_id).filter(Boolean)) : null; // D-13/S-26: ONE batched read, never per row
1024
1034
  const placeholders = [];
1025
1035
  for (const task of startupTasks || []) {
1026
- const placeholder = startupTaskPlaceholderSession(task);
1036
+ const placeholder = startupTaskPlaceholderSession(task, titlesById?.get?.(task?.session_id || task?.ctm_session_id));
1027
1037
  if (!placeholder || seen.has(placeholder.id)) continue;
1028
1038
  seen.add(placeholder.id);
1029
1039
  placeholders.push(placeholder);
@@ -1031,6 +1041,57 @@ function selectUnmatchedStartupTaskPlaceholders(existingSessions, startupTasks)
1031
1041
  return placeholders;
1032
1042
  }
1033
1043
 
1044
+ // WP-3 (D-9): the statuses a REMOTE (box) session row can carry that mean its PTY is live. A row
1045
+ // whose box reports a parked/exited/stopped look (the sync-mirror or last-good degradation) is not
1046
+ // live. The hub never consults a LOCAL PTY helper for these rows — they are not on this host.
1047
+ const REMOTE_ROW_LIVE_STATUSES = new Set([
1048
+ 'running', 'busy', 'active', 'working', 'thinking',
1049
+ 'waiting_input', 'waiting_for_input', 'waiting',
1050
+ 'resuming', 'restoring', 'restarting',
1051
+ ]);
1052
+
1053
+ /** The box's own liveness for a remote catalog row (never a local PTY check). */
1054
+ function remoteRowTerminalLive(row) {
1055
+ // The row's OWN status decides when it carries one (a live reachable box row, or the at-rest
1056
+ // mirror's 'parked'); the boolean flags are only a fallback for a row that has no status.
1057
+ const status = String(row?.standupStatus || row?.liveStatus || row?.status || row?.lifecycleState || '')
1058
+ .trim().toLowerCase().replace(/[-\s]+/g, '_');
1059
+ if (status) return REMOTE_ROW_LIVE_STATUSES.has(status);
1060
+ return row?.terminalLive === true || row?.terminal_live === true
1061
+ || row?.activeCtmSession === true || row?.active_ctm_session === true
1062
+ || row?.runtimeActive === true || row?.runtime_active === true;
1063
+ }
1064
+
1065
+ /**
1066
+ * WP-3 (D-9): a session-shaped object for one row of `remoteMachineCatalog.remoteRows()`, ready for
1067
+ * the same `classifySessionStandup` the local sessions map goes through (`summary: null` — a box row
1068
+ * has no local stream summary). Liveness comes from the row's OWN status; no local PTY helper runs.
1069
+ *
1070
+ * @param {object} row a remote catalog row (already machine-tagged by remoteRows())
1071
+ * @param {{instanceId?: string, displayName?: string, health?: string}} [machineFacts] fallback tag
1072
+ */
1073
+ function remoteStandupSession(row, machineFacts = {}) {
1074
+ const source = row && typeof row === 'object' ? row : {};
1075
+ const terminalLive = remoteRowTerminalLive(source);
1076
+ const rawStatus = String(source.standupStatus || source.liveStatus || source.status || '').trim();
1077
+ const standupStatus = rawStatus
1078
+ ? rawStatus.toLowerCase().replace(/[-\s]+/g, '_')
1079
+ : (terminalLive ? 'running' : 'parked');
1080
+ return {
1081
+ ...source,
1082
+ machine: String(source.machine || machineFacts.instanceId || '').trim(),
1083
+ machineName: String(source.machineName || machineFacts.displayName || '').trim(),
1084
+ machineHealth: String(source.machineHealth || machineFacts.health || '').trim(),
1085
+ terminalLive,
1086
+ terminal_live: terminalLive,
1087
+ activeCtmSession: terminalLive,
1088
+ active_ctm_session: terminalLive,
1089
+ runtimeActive: terminalLive,
1090
+ runtime_active: terminalLive,
1091
+ standupStatus,
1092
+ };
1093
+ }
1094
+
1034
1095
  function buildSessionStandupSnapshot({ sessions = [], summaries = [], statuses = [], now = Date.now() } = {}) {
1035
1096
  const summaryIndex = buildIndex(summaries);
1036
1097
  const statusIndex = buildIndex(statuses);
@@ -1070,6 +1131,8 @@ module.exports = {
1070
1131
  startupTaskPlaceholderSession,
1071
1132
  selectUnmatchedStartupTaskPlaceholders,
1072
1133
  isStandupStartupTaskSeedEnabled,
1134
+ remoteRowTerminalLive,
1135
+ remoteStandupSession,
1073
1136
  _private: {
1074
1137
  normalizeStatus,
1075
1138
  normalizeProvider,
@@ -72,6 +72,7 @@ const BOUNDARY_CLOSERS = new Set(['end_turn', 'interrupt']);
72
72
  const BG_END_STATUSES = new Set(['completed', 'failed', 'killed', 'stopped']);
73
73
  // renewal span, and the backstop expiry when no process sample arrives (busy-background-shell design D-3); the CLI has no local cap since 2.1.288
74
74
  const BG_SHELL_MAX_MS = 2 * 3600_000 + 5 * 60_000;
75
+ const BG_QUIET_MS = 30 * 60_000;
75
76
  const BG_DECLARED_MAX_MS = 6 * 3600_000;
76
77
  const BG_LABEL_MAX = 160;
77
78
  const BG_VIEW_TASKS_MAX = 8;
@@ -189,6 +190,7 @@ class SessionStatusLedger {
189
190
  next.set(n.id, {
190
191
  ...n, firstSeenAt, expiresAt: keep ? Math.max(Number(prev.expiresAt) || 0, cap) : cap, source: 'hook', count: 1,
191
192
  renewedAt: keep ? (prev.renewedAt || 0) : 0, renewals: keep ? (prev.renewals || 0) : 0, capCrossedAt: keep ? (prev.capCrossedAt || 0) : 0,
193
+ activeAt: keep ? (prev.activeAt || firstSeenAt) : firstSeenAt, outSize: keep ? (prev.outSize || 0) : 0, outMtimeMs: keep ? (prev.outMtimeMs || 0) : 0,
192
194
  });
193
195
  }
194
196
  let changed = next.size !== before || !this.backgroundDeclared;
@@ -222,6 +224,7 @@ class SessionStatusLedger {
222
224
  expiresAt: keep ? Math.max(Number(prev.expiresAt) || 0, computed) : computed,
223
225
  source: src, count,
224
226
  renewedAt: keep ? (prev.renewedAt || 0) : 0, renewals: keep ? (prev.renewals || 0) : 0, capCrossedAt: keep ? (prev.capCrossedAt || 0) : 0,
227
+ activeAt: keep ? (prev.activeAt || firstSeenAt) : firstSeenAt, outSize: keep ? (prev.outSize || 0) : 0, outMtimeMs: keep ? (prev.outMtimeMs || 0) : 0,
225
228
  };
226
229
  if (leaseMs) entry.leased = true;
227
230
  this.background.set(n.id, entry);
@@ -234,16 +237,28 @@ class SessionStatusLedger {
234
237
  return this.background.size > 0;
235
238
  }
236
239
 
240
+ // D-1: quiet = a hook-declared shell with no activity evidence for BG_QUIET_MS. Never kills; only drops park protection.
241
+ backgroundQuiet(entry, now) {
242
+ return entry.type === 'shell' && entry.source === 'hook' && now - (entry.activeAt || 0) >= BG_QUIET_MS;
243
+ }
244
+
245
+ backgroundActive(now = Date.now()) {
246
+ this._bgExpire(now);
247
+ return [...this.background.values()].some(e => !this.backgroundQuiet(e, now));
248
+ }
249
+
237
250
  backgroundView(now = Date.now()) {
238
251
  this._bgExpire(now);
239
252
  if (!this.background.size) return null;
240
253
  const live = [...this.background.values()].sort((a, b) => a.firstSeenAt - b.firstSeenAt);
241
254
  const types = {};
242
255
  let count = 0;
256
+ let quiet = 0;
257
+ for (const e of live) { if (this.backgroundQuiet(e, now)) quiet += 1; }
243
258
  for (const e of live) { types[e.type] = (types[e.type] || 0) + e.count; count += e.count; }
244
259
  return {
245
- count, types,
246
- tasks: live.slice(0, BG_VIEW_TASKS_MAX).map(e => ({ id: e.id, type: e.type, label: e.label, since: e.firstSeenAt })),
260
+ count, types, quiet,
261
+ tasks: live.slice(0, BG_VIEW_TASKS_MAX).map(e => ({ id: e.id, type: e.type, label: e.label, since: e.firstSeenAt, activeAt: e.activeAt || 0, quiet: this.backgroundQuiet(e, now) })),
247
262
  observedAt: this.backgroundObservedAt, declared: this.backgroundDeclared,
248
263
  };
249
264
  }
@@ -257,6 +272,7 @@ class SessionStatusLedger {
257
272
  entries.push({
258
273
  id: e.id, type: e.type, label: e.label, firstSeenAt: e.firstSeenAt, expiresAt: e.expiresAt, source: e.source, count: e.count,
259
274
  renewedAt: e.renewedAt || 0, renewals: e.renewals || 0, capCrossedAt: e.capCrossedAt || 0,
275
+ activeAt: e.activeAt || 0, outSize: e.outSize || 0, outMtimeMs: e.outMtimeMs || 0,
260
276
  });
261
277
  }
262
278
  return { declared: this.backgroundDeclared, observedAt: this.backgroundObservedAt, entries };
@@ -279,6 +295,7 @@ class SessionStatusLedger {
279
295
  firstSeenAt, expiresAt: Math.min(Number(raw.expiresAt), now + bgCapFor(type)),
280
296
  source: String(raw.source || 'hook'), count: Math.max(1, Number(raw.count) || 1),
281
297
  renewedAt: Number(raw.renewedAt) || 0, renewals: Math.max(0, Number(raw.renewals) || 0), capCrossedAt: Number(raw.capCrossedAt) || 0,
298
+ activeAt: Number(raw.activeAt) > 0 ? Number(raw.activeAt) : now, outSize: Number(raw.outSize) || 0, outMtimeMs: Number(raw.outMtimeMs) || 0,
282
299
  });
283
300
  n += 1;
284
301
  }
@@ -325,6 +342,28 @@ class SessionStatusLedger {
325
342
  return renewed;
326
343
  }
327
344
 
345
+ // D-4: silent activity evidence (a process-sample CPU delta). Returns the number of entries whose activeAt moved.
346
+ markBackgroundActive(predicate, at = Date.now()) {
347
+ let moved = 0;
348
+ for (const e of this.background.values()) {
349
+ if (!predicate(e)) continue;
350
+ if (at > (e.activeAt || 0)) { e.activeAt = at; moved += 1; }
351
+ }
352
+ return moved;
353
+ }
354
+
355
+ // D-4: output-file growth evidence; a never-seen entry counts as growth only when size > 0.
356
+ observeBackgroundOutput(id, { size = 0, mtimeMs = 0 } = {}, at = Date.now()) {
357
+ const e = this.background.get(id);
358
+ if (!e) return false;
359
+ const seen = (e.outSize || 0) > 0 || (e.outMtimeMs || 0) > 0;
360
+ const grew = seen ? (size > (e.outSize || 0) || mtimeMs > (e.outMtimeMs || 0)) : size > 0;
361
+ if (!grew) return false;
362
+ e.activeAt = Math.max(e.activeAt || 0, at);
363
+ e.outSize = size; e.outMtimeMs = mtimeMs;
364
+ return true;
365
+ }
366
+
328
367
  record(kind, { at = Date.now(), source = '', meta = {} } = {}) {
329
368
  const prev = this.verdict(at);
330
369
  const bgBefore = this._bgPending.length;
@@ -579,4 +618,4 @@ class SessionStatusLedger {
579
618
  }
580
619
  }
581
620
 
582
- module.exports = { SessionStatusLedger, EVIDENCE, AGENT_OUTPUT, PROVIDER_STATUS_POLICY, BG_END_STATUSES, BG_SHELL_MAX_MS, BG_DECLARED_MAX_MS, BG_LABEL_MAX, normalizeBackgroundTask };
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 };