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
@@ -0,0 +1,717 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * WP-3 (docs/remote-agent-skills-bundles-2026-10-25.html §5 D-4/D-6, §6.5): the HUB side of the
5
+ * agent-skill push — build the bundle for the box's platform, ask which blobs it lacks, upload
6
+ * those, then PUT the manifest.
7
+ *
8
+ * Three rules drive every decision here:
9
+ * D-4 push once per hash, only missing blobs. The `POST /missing` round trip is the authority (the
10
+ * box may have garbage-collected a tree WP-5 dropped), so it runs on EVERY push — including a
11
+ * push whose tree this process has already delivered.
12
+ * D-6 skills NEVER block a session. `budgetMs` (10 s by default) bounds the CALLER'S WAIT — not
13
+ * the push (rev 3, D-11 below) — and any failure (4xx/5xx, a build error, an unreadable blob)
14
+ * comes back as `{ok:false, code, detail}`: the caller records it on the machine row and
15
+ * forwards the create exactly as it would have without skills.
16
+ * D-5 the manifest carries ONLY `{schema, treeHash, entries}`. `blobsByHash` is hub-local by
17
+ * construction (build.js says so) and `items` / `heldBack` / `warnings` are hub-side reporting
18
+ * that must never ride the wire — they are not part of the box's manifest contract.
19
+ *
20
+ * Rev 3 (docs/remote-skills-hub-peer-identity-2026-10-26.html §5 D-11/D-12/D-13, §6.3) — a push
21
+ * outlives the create's wait, one push per box, and every result says which phase cost what:
22
+ * D-11 the push's lifetime is decoupled from the caller's wait. On budget expiry `pushBundleToBox`
23
+ * resolves `{ok:false, code:'agent_bundle_push_timeout', continuing:true, phase, timings}`
24
+ * and the push is NOT aborted — it runs on to completion or to `BACKGROUND_CEILING_MS`
25
+ * (120 s, `options.ceilingMs` in tests), whichever is first. The push's final result goes to
26
+ * `options.onLateResult(result)` EXACTLY ONCE, and only when THIS call timed out and THIS call
27
+ * STARTED the push (a joined caller never gets a late result — the starter's caller records the
28
+ * outcome once). A throwing `onLateResult` is REPORTED with `console.warn`, never left as an
29
+ * unhandled rejection. The work promise never rejects.
30
+ * D-12 single-flight per box: a call while a push to the same `instanceId` is in flight starts
31
+ * nothing — it waits on that push with its OWN budget and returns its result (or its own
32
+ * timeout) with `joined:true`. At most one upload per box at a time; a joiner costs zero
33
+ * requests. Configuration failures (no credential / fetch / url) return before any of this.
34
+ * D-13 every result (ok, failure, timeout snapshot, late, ceiling) carries
35
+ * `timings: {buildMs, missingMs, readMs, uploadMs, manifestMs, totalMs, batches, batchesDone}`
36
+ * measured with `options.monotonicNow` (default `performance.now`), rounded to integer ms and
37
+ * summed across the D-4 409 retry. A batch POST or an oversize PUT is ONE batch.
38
+ *
39
+ * Error codes (the closed set; `detail` is a short message and never carries a token, a bearer or
40
+ * any file contents):
41
+ * agent_bundle_build_failed the bundle could not be built (or built into something unusable)
42
+ * agent_bundle_push_timeout the caller's budget expired (D-11: the push keeps running until its
43
+ * ceiling) — `continuing:true` on the budget result, `ceiling:true` on
44
+ * the ceiling result
45
+ * agent_bundle_push_http_<n> the box answered a non-2xx status
46
+ * agent_bundle_push_network the transport failed, or nothing could be dialled
47
+ * agent_bundle_push_no_credential no peer credential is configured (a configuration error)
48
+ *
49
+ * A non-2xx push also carries the BOX's own reason, when it named one, as `boxCode` on the failure
50
+ * result (D-4, docs/remote-skills-hub-peer-identity-2026-10-26.html §5 D-4/§6). `code` is unchanged —
51
+ * a `403 {"code":"agent_bundle_peer_unidentified"}` is still `agent_bundle_push_http_403` — and the
52
+ * `boxCode` key is ABSENT when the body had no code, or a code that is not a snake_case token.
53
+ *
54
+ * Request count, not concurrency, is the scarce resource. The box charges every request to one
55
+ * `peer` rate bucket keyed by the HUB (lib/auth-rate-limit.js:16-24: burst 120, 600/min), and that
56
+ * same key carries the hub's live keystroke/prompt relay for every box session — so a per-blob upload
57
+ * of a real ~585-blob home (S-7, docs/remote-skills-hub-peer-identity-2026-10-26.html §1.3, rev 2)
58
+ * answers 429 at request 125 and starves the relay while it runs. WP-4 uploads in BATCHES instead
59
+ * (D-6/D-7, this doc rev 2): blobs are packed in order into groups of at most BATCH_MAX_RAW_BYTES
60
+ * (4 MiB) raw bytes and POSTed one group at a time — the REAPI `FindMissingBlobs` + `BatchUpdateBlobs`
61
+ * shape, with the existing per-blob `PUT /blobs/<hex>` kept as the ByteStream analogue for a single
62
+ * oversized blob. A first push therefore costs `2 + ⌈bytes/4 MiB⌉ + (oversize blobs)` requests; the
63
+ * per-process cache is described at `_pushedTrees` below.
64
+ */
65
+
66
+ const fsp = require('fs/promises');
67
+ const os = require('os');
68
+
69
+ const { buildAgentBundle } = require('./build');
70
+
71
+ const DEFAULT_BUDGET_MS = 10_000;
72
+ /**
73
+ * D-7: the raw-byte budget of ONE batch request. Base64 grows it to ~5.4 MiB, under the box's D-6
74
+ * `MAX_BATCH_BODY_BYTES` of 8 MiB. A single blob larger than this goes alone through the existing
75
+ * per-blob PUT (the REAPI ByteStream analogue).
76
+ */
77
+ const BATCH_MAX_RAW_BYTES = 4 * 1024 * 1024;
78
+ const MAX_DETAIL_CHARS = 200;
79
+ const HEX_RE = /^[0-9a-f]{64}$/;
80
+ /**
81
+ * D-4 (docs/remote-skills-hub-peer-identity-2026-10-26.html §6): the shape a box's own reason may
82
+ * take. The box answers a non-2xx with `{code}` — a snake_case token — and `code` alone cannot say
83
+ * WHY (a 403 is "peer unidentified" or a dozen other refusals). Kept only if it matches this form,
84
+ * so nothing arbitrary (a path, a sentence, an uppercase word) can ride into the machine row.
85
+ */
86
+ const BOX_CODE_RE = /^[a-z][a-z0-9_]{0,79}$/;
87
+ /** build.js's `computeTreeHash` form. A hash the box would reject on format is a local build failure. */
88
+ const TREE_HASH_RE = /^sha256:[0-9a-f]{64}$/;
89
+
90
+ const CODE_BUILD_FAILED = 'agent_bundle_build_failed';
91
+ const CODE_TIMEOUT = 'agent_bundle_push_timeout';
92
+ const CODE_NETWORK = 'agent_bundle_push_network';
93
+ const CODE_NO_CREDENTIAL = 'agent_bundle_push_no_credential';
94
+
95
+ /**
96
+ * Per-process push cache: instanceId -> Set<treeHash> of trees this process has successfully
97
+ * delivered to that box.
98
+ *
99
+ * CHOICE (the brief leaves this open): a cache HIT still runs the `POST /missing` check — that is
100
+ * the box's own truth and the only thing that can reveal a garbage-collected tree — and it skips
101
+ * the manifest PUT ONLY when the check came back EMPTY. The reasoning: a box's hub pointer moves
102
+ * only when THIS hub successfully PUTs a manifest for that peer, and `(instanceId, treeHash)` is the
103
+ * cache key, so "we pushed T to box B in this process and B is still missing nothing" means B's
104
+ * pointer already names T and its tree directory is protected by WP-5's pointer rule. That is
105
+ * exactly AC-3a ("the second create with no change uploads 0 blobs and 0 manifest bytes beyond the
106
+ * missing check"). A box that lost the tree or moved its pointer is a non-empty answer, and then the
107
+ * full push runs as usual — the skip can never hide a missing tree.
108
+ */
109
+ const _pushedTrees = new Map();
110
+
111
+ /**
112
+ * D-11 (rev 3): how long a push may run on after the caller stopped waiting, before its
113
+ * AbortController is aborted and the push reports a ceiling failure. 11.3 MB on the wire / 120 s ≈
114
+ * 94 KB/s, so this covers any link the relay itself is usable on; `options.ceilingMs` overrides it.
115
+ */
116
+ const BACKGROUND_CEILING_MS = 120_000;
117
+
118
+ /**
119
+ * D-12 (rev 3): single-flight per box — `instanceId -> {promise, progress, controller}` for every push
120
+ * this process has running. A second call for the same box JOINS the running push instead of sending
121
+ * a second copy of the bundle through the one `peer` bucket the relay shares.
122
+ */
123
+ const _inFlight = new Map();
124
+
125
+ /** Test seam: the cache is module state and must never leak between tests. D-12: in-flight pushes too. */
126
+ function _resetPushCacheForTests() {
127
+ _pushedTrees.clear();
128
+ for (const entry of _inFlight.values()) {
129
+ try {
130
+ entry.controller.abort();
131
+ } catch {
132
+ // expected: an already-aborted controller
133
+ }
134
+ }
135
+ _inFlight.clear();
136
+ }
137
+
138
+ /** D-13: the eight numbers every result carries, in one place. */
139
+ function _newTimings() {
140
+ return {
141
+ buildMs: 0, missingMs: 0, readMs: 0, uploadMs: 0, manifestMs: 0, totalMs: 0, batches: 0, batchesDone: 0,
142
+ };
143
+ }
144
+
145
+ function _defaultMonotonicNow() {
146
+ return performance.now();
147
+ }
148
+
149
+ /**
150
+ * D-13 (rev 3): the per-push progress — the phase the push is in, the batch counters, and the timings —
151
+ * read by a budget/ceiling snapshot while the push is still running. `measure` is the only way a phase
152
+ * is entered, so `phase` is always the phase whose work is in flight.
153
+ */
154
+ function _newProgress(monotonicNow) {
155
+ const started = monotonicNow();
156
+ const timings = _newTimings();
157
+ const progress = {
158
+ phase: 'build',
159
+ batches: 0,
160
+ batchesDone: 0,
161
+ timings,
162
+ /** Run `fn` with `phase` current; charge the elapsed wall time to `timings[phaseKey]` (summed). */
163
+ async measure(phase, phaseKey, fn) {
164
+ progress.phase = phase;
165
+ const t0 = monotonicNow();
166
+ try {
167
+ return await fn();
168
+ } finally {
169
+ timings[phaseKey] += Math.max(0, Math.round(monotonicNow() - t0));
170
+ }
171
+ },
172
+ /** One planned group (a batch POST or an oversize PUT). */
173
+ plan(count) {
174
+ timings.batches += Math.max(0, Number(count) || 0);
175
+ progress.batches = timings.batches;
176
+ },
177
+ /** One acknowledged group (its 2xx came back). */
178
+ planned() {
179
+ timings.batchesDone += 1;
180
+ progress.batchesDone = timings.batchesDone;
181
+ },
182
+ /** A copy, safe to hand out while the push keeps writing. */
183
+ snapshot() {
184
+ timings.totalMs = Math.max(0, Math.round(monotonicNow() - started));
185
+ return { ...timings };
186
+ },
187
+ };
188
+ return progress;
189
+ }
190
+
191
+ /** D-11: the result the caller gets when its own wait (not the push) ran out. */
192
+ function _budgetExpired(budgetMs, progress) {
193
+ return {
194
+ ok: false,
195
+ code: CODE_TIMEOUT,
196
+ detail: `push exceeded ${budgetMs}ms in ${progress.phase}; continuing in background`,
197
+ phase: progress.phase,
198
+ timings: progress.snapshot(),
199
+ continuing: true,
200
+ };
201
+ }
202
+
203
+ /** D-11: the result every waiter gets when the push itself ran past BACKGROUND_CEILING_MS. */
204
+ function _ceilingExpired(ceilingMs, progress) {
205
+ return {
206
+ ok: false,
207
+ code: CODE_TIMEOUT,
208
+ detail: `push exceeded ceiling ${ceilingMs}ms in ${progress.phase}`,
209
+ phase: progress.phase,
210
+ ceiling: true,
211
+ timings: progress.snapshot(),
212
+ };
213
+ }
214
+
215
+ /**
216
+ * D-11: bound the CALLER'S WAIT — never the push. Resolves `{timedOut:false, result}` when the push
217
+ * settles first, or `{timedOut:true, result:<snapshot>}` when `budgetMs` expires first. It owns no
218
+ * controller and aborts nothing: the deadline here is Node's own unref'd `AbortSignal.timeout`.
219
+ */
220
+ function _budgetWait(promise, budgetMs, progress) {
221
+ return new Promise((resolve) => {
222
+ let settled = false;
223
+ const deadline = AbortSignal.timeout(Math.max(1, Number(budgetMs) || DEFAULT_BUDGET_MS));
224
+ deadline.addEventListener('abort', () => {
225
+ if (settled) return;
226
+ settled = true;
227
+ resolve({ timedOut: true, result: _budgetExpired(budgetMs, progress) });
228
+ }, { once: true });
229
+ promise.then((result) => {
230
+ if (settled) return;
231
+ settled = true;
232
+ resolve({ timedOut: false, result });
233
+ }, (error) => {
234
+ // Defensive: the work promise never rejects, but a regression here must still not hang a create.
235
+ if (settled) return;
236
+ settled = true;
237
+ resolve({ timedOut: false, result: _fail(CODE_NETWORK, error) });
238
+ });
239
+ });
240
+ }
241
+
242
+ function _string(value, max = 1000) {
243
+ return String(value == null ? '' : value).trim().slice(0, max);
244
+ }
245
+
246
+ /** A short, safe `detail`: bounded, single-line, and never a credential. */
247
+ function _safeDetail(value) {
248
+ let text = value instanceof Error
249
+ ? String(value.message || value.name || 'error')
250
+ : String(value == null ? '' : value);
251
+ // Defensive: a transport error message is caller-supplied text, and the only secret this module
252
+ // holds is the bearer in `peerHeaders` — so redact the shape a bearer takes before it can escape.
253
+ text = text.replace(/bearer\s+[^\s,;]+/gi, 'bearer <redacted>');
254
+ text = text.replace(/[\r\n\t]+/g, ' ').trim();
255
+ return text.slice(0, MAX_DETAIL_CHARS);
256
+ }
257
+
258
+ function _fail(code, detail, boxCode) {
259
+ const result = { ok: false, code, detail: _safeDetail(detail) };
260
+ // D-4: `boxCode` is present ONLY when the box really named a code — an absent key, never an
261
+ // empty string, so a reader can tell "the box said nothing" from "the box said ''".
262
+ const reason = _boxCodeString(boxCode);
263
+ if (reason) result.boxCode = reason;
264
+ return result;
265
+ }
266
+
267
+ /** The box's own reason, kept only when it is a plain snake_case token (BOX_CODE_RE). */
268
+ function _boxCodeString(value) {
269
+ const text = _string(value, 80);
270
+ return BOX_CODE_RE.test(text) ? text : '';
271
+ }
272
+
273
+ /**
274
+ * D-9: read a refusal body with the bounded `_readJson` and keep `body.code` if it matches
275
+ * BOX_CODE_RE, else `body.error` if THAT matches, else ''. The box's own route handlers answer
276
+ * `{code}` (a snake_case token, D-4), but the auth WALL answers `{error}` (server.js:11791) — so a
277
+ * 429 reads as `rate_limited` and an old box with no batch route reads as `auth_rule_missing`.
278
+ * Never throws: a body this hub cannot read is "the box said nothing", and the push still reports by
279
+ * status.
280
+ */
281
+ async function _boxCodeOf(response) {
282
+ const body = await _readJson(response);
283
+ return _boxCodeString(body && body.code) || _boxCodeString(body && body.error);
284
+ }
285
+
286
+ /** `http_<status>` for every non-2xx the box answers, including its 409/413/422 refusals. */
287
+ function _httpFail(status, detail, boxCode) {
288
+ return _fail(`agent_bundle_push_http_${Number(status) || 0}`, detail, boxCode);
289
+ }
290
+
291
+ /** The bundle's unique entry hashes, in entry order, deduped — the `/missing` request body. */
292
+ function _uniqueHashes(entries) {
293
+ const hashes = [];
294
+ const seen = new Set();
295
+ for (const entry of entries) {
296
+ const hex = _string(entry && entry.sha256, 64);
297
+ if (seen.has(hex)) continue;
298
+ seen.add(hex);
299
+ hashes.push(hex);
300
+ }
301
+ return hashes;
302
+ }
303
+
304
+ /** Every entry's hash must be a 64-hex digest: the box would 422 anything else, so fail locally. */
305
+ function _entriesAreWellFormed(entries) {
306
+ if (!Array.isArray(entries)) return false;
307
+ for (const entry of entries) {
308
+ if (!entry || typeof entry !== 'object') return false;
309
+ if (!HEX_RE.test(_string(entry.sha256, 64))) return false;
310
+ }
311
+ return true;
312
+ }
313
+
314
+ function _joinUrl(baseUrl, pathname) {
315
+ const base = _string(baseUrl, 1000).replace(/\/+$/, '');
316
+ if (!base) return '';
317
+ try {
318
+ return new URL(pathname, base).toString();
319
+ } catch {
320
+ // expected: an unparseable machine url is a dial failure, never a throw out of a create
321
+ return '';
322
+ }
323
+ }
324
+
325
+ async function _readJson(response) {
326
+ try {
327
+ return await response.json();
328
+ } catch {
329
+ // expected: a non-JSON body is a refusal we report by STATUS, not a parse failure of our own
330
+ return null;
331
+ }
332
+ }
333
+
334
+ /**
335
+ * Push `targetPlatform`'s bundle to `machine` = `{instanceId, url}`.
336
+ *
337
+ * @param {{instanceId: string, url: string}} machine
338
+ * @param {object} options
339
+ * @param {Function} [options.fetchImpl] the fetch implementation (injectable for tests)
340
+ * @param {object} [options.peerHeaders] the Authorization + X-CTM-Sync-Peer headers the dial uses
341
+ * @param {string} options.targetPlatform 'darwin' | 'linux' | 'win32' — the BOX's os
342
+ * @param {string[]} [options.excludePlugins] plugin names never shipped to this box (WP-8 wires it)
343
+ * @param {number} [options.budgetMs] the CALLER's wait (D-11; the push itself runs on to the ceiling)
344
+ * @param {number} [options.ceilingMs] how long the push may run past the wait (D-11, default
345
+ * BACKGROUND_CEILING_MS)
346
+ * @param {Function} [options.onLateResult] `(result) => void` — the push's final result, exactly once,
347
+ * and only for a caller that timed out AND started the push (D-11)
348
+ * @param {Function} [options.monotonicNow] the monotonic clock the timings are measured with (D-13)
349
+ * @param {Function} [options.build] the bundle builder (injectable for tests)
350
+ * @param {Function} [options.now] the clock for the cache stamp
351
+ * @param {object} [options.env] the env the build reads (WP-1)
352
+ * @param {string} [options.homeDir] the home the build reads (WP-1)
353
+ * @returns {Promise<{ok: true, treeHash: string, uploadedBlobs: number, uploadedBytes: number,
354
+ * sentCount: number, heldBack: Array, timings: object}|{ok: false, code: string, detail: string,
355
+ * boxCode?: string, phase?: string, continuing?: true, ceiling?: true, timings: object}>}
356
+ * `boxCode` is the box's own refusal reason (D-4), present ONLY when it named a well-formed one.
357
+ * `timings` (D-13) is on EVERY result; `joined` marks a caller that waited on someone else's push (D-12).
358
+ */
359
+ async function pushBundleToBox(machine, options = {}) {
360
+ const opts = options && typeof options === 'object' ? options : {};
361
+ const fetchImpl = opts.fetchImpl || (typeof fetch === 'function' ? fetch : null);
362
+ const budgetMs = Number(opts.budgetMs) || DEFAULT_BUDGET_MS;
363
+ const build = typeof opts.build === 'function' ? opts.build : buildAgentBundle;
364
+ const targetPlatform = _string(opts.targetPlatform, 40);
365
+ const excludePlugins = Array.isArray(opts.excludePlugins) ? opts.excludePlugins.map((name) => _string(name, 200)).filter(Boolean) : [];
366
+ const headers = opts.peerHeaders && typeof opts.peerHeaders === 'object' ? opts.peerHeaders : null;
367
+ const env = opts.env && typeof opts.env === 'object' ? opts.env : process.env;
368
+ const homeDir = _string(opts.homeDir, 1000) || os.homedir();
369
+ const instanceId = _string(machine && machine.instanceId, 160);
370
+ const baseUrl = _string(machine && machine.url, 1000);
371
+ const monotonicNow = typeof opts.monotonicNow === 'function' ? opts.monotonicNow : _defaultMonotonicNow;
372
+ const ceilingMs = Math.max(1, Math.round(Number(opts.ceilingMs) || BACKGROUND_CEILING_MS));
373
+ const onLateResult = typeof opts.onLateResult === 'function' ? opts.onLateResult : null;
374
+
375
+ // Configuration failures, named as such: a push is skipped, never attempted anonymously — and
376
+ // never enters _inFlight (D-12: a joiner must never wait on a push that was never started).
377
+ if (!headers) return _fail(CODE_NO_CREDENTIAL, 'no peer credential is configured for this machine');
378
+ if (!fetchImpl) return _fail(CODE_NETWORK, 'no fetch implementation is available');
379
+ if (!baseUrl) return _fail(CODE_NETWORK, 'machine url is missing');
380
+ const missingUrl = _joinUrl(baseUrl, '/api/agent-bundles/missing');
381
+ if (!missingUrl) return _fail(CODE_NETWORK, 'machine url is not a valid url');
382
+
383
+ // D-12: single-flight per box. A push to this instanceId is already in flight: start NOTHING, wait
384
+ // on it with this caller's own budget, and hand back its result (`joined:true`). A joiner never
385
+ // calls its own `onLateResult` — the starter's caller records the outcome exactly once.
386
+ const running = _inFlight.get(instanceId);
387
+ if (running) {
388
+ const waited = await _budgetWait(running.promise, budgetMs, running.progress);
389
+ return { ...waited.result, joined: true };
390
+ }
391
+
392
+ const controller = new AbortController();
393
+ const progress = _newProgress(monotonicNow);
394
+ // D-11: the CEILING — and only the ceiling — aborts the push. The budget above never does.
395
+ const ceilingResult = new Promise((resolve) => {
396
+ AbortSignal.timeout(ceilingMs).addEventListener('abort', () => {
397
+ try {
398
+ controller.abort();
399
+ } catch {
400
+ // expected: an already-aborted controller
401
+ }
402
+ resolve(_ceilingExpired(ceilingMs, progress));
403
+ }, { once: true });
404
+ });
405
+
406
+ // D-11: the work promise NEVER rejects; every failure is a result. Every result carries `timings`.
407
+ const push = _runPush({
408
+ build, targetPlatform, excludePlugins, env, homeDir, headers, fetchImpl, baseUrl, missingUrl, instanceId, controller, progress, opts,
409
+ }).then(
410
+ (result) => (result && result.timings ? result : { ...result, timings: progress.snapshot() }),
411
+ // Defensive: `_runPush` resolves its own failures, so a rejection here is a bug — never a hang.
412
+ (error) => ({ ..._fail(CODE_NETWORK, error), timings: progress.snapshot() }),
413
+ );
414
+ const work = Promise.race([push, ceilingResult]);
415
+
416
+ const entry = { promise: work, progress, controller };
417
+ _inFlight.set(instanceId, entry);
418
+ // D-12: the entry is deleted when the push settles — and only if the map still holds THIS entry.
419
+ const release = () => {
420
+ if (_inFlight.get(instanceId) === entry) _inFlight.delete(instanceId);
421
+ };
422
+ work.then(release, release);
423
+
424
+ const waited = await _budgetWait(work, budgetMs, progress);
425
+ if (!waited.timedOut) return waited.result;
426
+
427
+ // D-11: this call timed out AND this call started the push, so it — and only it — owns the late
428
+ // result. A throwing callback is REPORTED, never an unhandled rejection.
429
+ if (onLateResult) {
430
+ work.then((late) => {
431
+ try {
432
+ onLateResult(late);
433
+ } catch (error) {
434
+ console.warn(`[agent-bundle] onLateResult threw for ${instanceId}: ${_safeDetail(error)}`);
435
+ }
436
+ }, () => { /* unreachable: the work promise never rejects */ });
437
+ }
438
+ return waited.result;
439
+ }
440
+
441
+ /**
442
+ * The push itself (D-4/D-5/D-7, and the D-13 phase bookkeeping): build, ask what is missing, upload,
443
+ * PUT the manifest, with the D-4 409 retry. Resolves a result — it never rejects — and reads its
444
+ * `progress` to say which phase is in flight when a budget/ceiling snapshot is taken.
445
+ */
446
+ async function _runPush(ctx) {
447
+ const {
448
+ build, targetPlatform, excludePlugins, env, homeDir, headers, fetchImpl, baseUrl, missingUrl,
449
+ instanceId, controller, progress, opts,
450
+ } = ctx;
451
+ const signal = controller.signal;
452
+ try {
453
+ let bundle = null;
454
+ try {
455
+ bundle = await progress.measure('build', 'buildMs', () => build({ targetPlatform, excludePlugins, env, homeDir }));
456
+ } catch (error) {
457
+ return _fail(CODE_BUILD_FAILED, error);
458
+ }
459
+ const treeHash = _string(bundle && bundle.treeHash, 80);
460
+ const entries = bundle && bundle.entries;
461
+ const blobsByHash = bundle && bundle.blobsByHash;
462
+ if (!TREE_HASH_RE.test(treeHash) || !_entriesAreWellFormed(entries) || !(blobsByHash instanceof Map)) {
463
+ // REPORT-grade: the builder handed us a bundle the box could never accept. Same code as a
464
+ // build error — nothing left this hub, and the caller treats it identically (D-6).
465
+ return _fail(CODE_BUILD_FAILED, 'the bundle is not usable');
466
+ }
467
+
468
+ const jsonInit = (body) => ({
469
+ method: 'POST',
470
+ headers: { ...headers, 'content-type': 'application/json' },
471
+ body: JSON.stringify(body),
472
+ signal,
473
+ });
474
+ const manifestUrl = _joinUrl(baseUrl, `/api/agent-bundles/manifests/${encodeURIComponent(treeHash)}`);
475
+
476
+ const missingCheck = () => progress.measure('missing', 'missingMs', async () => {
477
+ let response;
478
+ try {
479
+ response = await fetchImpl(missingUrl, jsonInit({ blobs: hashes }));
480
+ } catch (error) {
481
+ throw Object.assign(new Error(_safeDetail(error)), { code: CODE_NETWORK });
482
+ }
483
+ const status = Number(response && response.status) || 0;
484
+ if (status < 200 || status >= 300) {
485
+ throw Object.assign(new Error(`missing check answered ${status}`), {
486
+ code: `agent_bundle_push_http_${status}`,
487
+ // S-2: the box's reason rides the throw so the outer catch can keep it (D-4).
488
+ boxCode: await _boxCodeOf(response),
489
+ });
490
+ }
491
+ const body = await _readJson(response);
492
+ const raw = body && Array.isArray(body.missing) ? body.missing : [];
493
+ // The box is the authority on WHICH blobs it lacks; it is not trusted for how many times a
494
+ // hash appears (a repeat would otherwise be uploaded, and counted, twice).
495
+ const wanted = [];
496
+ const seen = new Set();
497
+ for (const value of raw) {
498
+ const hex = _string(value, 64);
499
+ if (!HEX_RE.test(hex) || seen.has(hex)) continue;
500
+ seen.add(hex);
501
+ wanted.push(hex);
502
+ }
503
+ return wanted;
504
+ });
505
+
506
+ /** The REAPI ByteStream analogue: ONE blob over the existing per-blob PUT. */
507
+ const putOneBlob = async (hex, buffer) => {
508
+ let response;
509
+ try {
510
+ response = await fetchImpl(_joinUrl(baseUrl, `/api/agent-bundles/blobs/${hex}`), {
511
+ method: 'PUT',
512
+ headers: { ...headers, 'content-type': 'application/octet-stream' },
513
+ body: buffer,
514
+ signal,
515
+ });
516
+ } catch (error) {
517
+ throw Object.assign(new Error(_safeDetail(error)), { code: CODE_NETWORK });
518
+ }
519
+ const status = Number(response && response.status) || 0;
520
+ if (status < 200 || status >= 300) {
521
+ throw Object.assign(new Error(`blob ${hex.slice(0, 12)} answered ${status}`), {
522
+ code: `agent_bundle_push_http_${status}`,
523
+ // S-3: a 409/413/422 here is the box refusing for a reason (blobs_missing, too big, hash
524
+ // mismatch) — keep it exactly as the missing check does.
525
+ boxCode: await _boxCodeOf(response),
526
+ });
527
+ }
528
+ return buffer.length;
529
+ };
530
+
531
+ /** One D-6 `POST /api/agent-bundles/blobs` carrying a group of already-read blobs. */
532
+ const postBatch = async (items) => {
533
+ const payload = items.map(({ hex, buffer }) => ({ hash: hex, data: buffer.toString('base64') }));
534
+ let response;
535
+ try {
536
+ response = await fetchImpl(_joinUrl(baseUrl, '/api/agent-bundles/blobs'), jsonInit({ blobs: payload }));
537
+ } catch (error) {
538
+ throw Object.assign(new Error(_safeDetail(error)), { code: CODE_NETWORK });
539
+ }
540
+ const status = Number(response && response.status) || 0;
541
+ if (status < 200 || status >= 300) {
542
+ throw Object.assign(new Error(`blob batch answered ${status}`), {
543
+ code: `agent_bundle_push_http_${status}`,
544
+ boxCode: await _boxCodeOf(response),
545
+ });
546
+ }
547
+ return items.reduce((total, { buffer }) => total + buffer.length, 0);
548
+ };
549
+
550
+ /**
551
+ * D-7: read EVERY blob first (an unreadable one is a local build failure, D-6), then pack the
552
+ * buffers IN ORDER into groups whose raw total is at most BATCH_MAX_RAW_BYTES and send them one
553
+ * at a time. A single blob over the cap goes alone through the per-blob PUT. Request count is
554
+ * the scarce resource (see the header note), so batches are strictly sequential — never two in
555
+ * flight — and this returns the raw bytes sent (uploadedBytes' meaning).
556
+ *
557
+ * D-13: the reads are the `read` phase and the sends are the `upload` phase; one group — a batch
558
+ * POST or an oversize PUT — is one batch, counted as planned before the sends and as done after
559
+ * each 2xx.
560
+ */
561
+ const uploadBlobs = async (hexes) => {
562
+ const pending = await progress.measure('read', 'readMs', async () => {
563
+ const read = [];
564
+ for (const hex of hexes) {
565
+ const source = blobsByHash.get(hex);
566
+ let buffer;
567
+ try {
568
+ buffer = await fsp.readFile(source);
569
+ } catch (error) {
570
+ // A blob the builder named but we cannot read: the push cannot be completed, and D-6
571
+ // says the session is unaffected.
572
+ throw Object.assign(new Error(`blob unreadable: ${_safeDetail(error)}`), { code: CODE_BUILD_FAILED });
573
+ }
574
+ read.push({ hex, buffer });
575
+ }
576
+ return read;
577
+ });
578
+
579
+ const groups = [];
580
+ let current = [];
581
+ let currentBytes = 0;
582
+ for (const item of pending) {
583
+ if (item.buffer.length > BATCH_MAX_RAW_BYTES) {
584
+ if (current.length) {
585
+ groups.push({ items: current });
586
+ current = [];
587
+ currentBytes = 0;
588
+ }
589
+ groups.push({ oversize: item });
590
+ continue;
591
+ }
592
+ if (currentBytes + item.buffer.length > BATCH_MAX_RAW_BYTES) {
593
+ groups.push({ items: current });
594
+ current = [];
595
+ currentBytes = 0;
596
+ }
597
+ current.push(item);
598
+ currentBytes += item.buffer.length;
599
+ }
600
+ if (current.length) groups.push({ items: current });
601
+ progress.plan(groups.length);
602
+
603
+ let bytes = 0;
604
+ await progress.measure('upload', 'uploadMs', async () => {
605
+ for (const group of groups) {
606
+ bytes += group.oversize
607
+ ? await putOneBlob(group.oversize.hex, group.oversize.buffer)
608
+ : await postBatch(group.items);
609
+ progress.planned();
610
+ }
611
+ });
612
+ return bytes;
613
+ };
614
+
615
+ const putManifest = () => progress.measure('manifest', 'manifestMs', async () => {
616
+ let response;
617
+ try {
618
+ response = await fetchImpl(manifestUrl, {
619
+ // D-5: only the manifest's own three fields cross — never blobsByHash, items, heldBack
620
+ // or warnings.
621
+ method: 'PUT',
622
+ headers: { ...headers, 'content-type': 'application/json' },
623
+ body: JSON.stringify({ schema: 1, treeHash, entries }),
624
+ signal,
625
+ });
626
+ } catch (error) {
627
+ throw Object.assign(new Error(_safeDetail(error)), { code: CODE_NETWORK });
628
+ }
629
+ const status = Number(response && response.status) || 0;
630
+ // S-3: the manifest PUT reports its status rather than throwing (the 409 retry lives at the
631
+ // call site), so carry the box's reason alongside it for that call site to keep.
632
+ const boxCode = (status < 200 || status >= 300) ? await _boxCodeOf(response) : '';
633
+ return { status, boxCode };
634
+ });
635
+
636
+ const hashes = _uniqueHashes(entries);
637
+ let uploadedBlobs = 0;
638
+ let uploadedBytes = 0;
639
+
640
+ const missing = await missingCheck();
641
+ if (missing.length) {
642
+ uploadedBytes += await uploadBlobs(missing);
643
+ uploadedBlobs = missing.length;
644
+ }
645
+
646
+ const delivered = _pushedTrees.get(instanceId);
647
+ const alreadyDelivered = !!(delivered && delivered.has(treeHash));
648
+ if (alreadyDelivered && !missing.length) {
649
+ progress.phase = 'done';
650
+ return _success(treeHash, 0, 0, bundle, opts);
651
+ }
652
+
653
+ let manifest = await putManifest();
654
+ if (manifest.status === 409) {
655
+ // D-4's one retry: the box named blobs it wants after all (a GC, or a first push that raced
656
+ // another hub). Re-upload exactly what it asks for and PUT the manifest once more; a second
657
+ // 409 is reported, not looped on. D-13: the retry re-enters missing → read → upload → manifest.
658
+ const lateMissing = await missingCheck();
659
+ if (lateMissing.length) {
660
+ uploadedBytes += await uploadBlobs(lateMissing);
661
+ uploadedBlobs += lateMissing.length;
662
+ }
663
+ manifest = await putManifest();
664
+ }
665
+ if (manifest.status < 200 || manifest.status >= 300) {
666
+ return _httpFail(manifest.status, `manifest answered ${manifest.status}`, manifest.boxCode);
667
+ }
668
+
669
+ const entry = _pushedTrees.get(instanceId) || new Set();
670
+ entry.add(treeHash);
671
+ _pushedTrees.set(instanceId, entry);
672
+ progress.phase = 'done';
673
+ return _success(treeHash, uploadedBlobs, uploadedBytes, bundle, opts);
674
+ } catch (error) {
675
+ const code = _string(error && error.code, 80) || CODE_NETWORK;
676
+ // With D-11 nothing here times the push out (only the ceiling aborts, and its result wins the
677
+ // race), but a tagged CODE_TIMEOUT still reports as one.
678
+ if (code === CODE_TIMEOUT) return _fail(CODE_TIMEOUT, `push timed out in ${progress.phase}`);
679
+ // Any other tagged code is already one of this module's closed set; anything untagged is a
680
+ // transport/unexpected fault and reads as a network failure. S-4: the box's reason, when a
681
+ // non-2xx site attached one, rides out unchanged — `code` keeps its `agent_bundle_push_http_<n>`
682
+ // value, so nothing that branches on it changes.
683
+ return _fail(
684
+ code.startsWith('agent_bundle_push_http_') ? code : (code === CODE_BUILD_FAILED ? CODE_BUILD_FAILED : CODE_NETWORK),
685
+ error,
686
+ error && error.boxCode,
687
+ );
688
+ }
689
+ }
690
+
691
+ function _success(treeHash, uploadedBlobs, uploadedBytes, bundle, opts) {
692
+ return {
693
+ ok: true,
694
+ treeHash,
695
+ uploadedBlobs: Number(uploadedBlobs) || 0,
696
+ uploadedBytes: Number(uploadedBytes) || 0,
697
+ sentCount: Array.isArray(bundle.items) ? bundle.items.length : 0,
698
+ heldBack: Array.isArray(bundle.heldBack) ? bundle.heldBack : [],
699
+ // WP-9 (§9 S-9): the plugin names this push carried — the machine row's Review checklist reads them
700
+ // (machine-registry.js `_pluginsFromResult`). Names only; the item hashes stay inside the bundle.
701
+ plugins: (Array.isArray(bundle.items) ? bundle.items : [])
702
+ .filter((item) => item && item.kind === 'plugin' && item.name).map((item) => String(item.name)),
703
+ at: typeof opts.now === 'function' ? Number(opts.now()) || 0 : Date.now(),
704
+ };
705
+ }
706
+
707
+ module.exports = {
708
+ pushBundleToBox,
709
+ DEFAULT_BUDGET_MS,
710
+ BACKGROUND_CEILING_MS,
711
+ BATCH_MAX_RAW_BYTES,
712
+ CODE_BUILD_FAILED,
713
+ CODE_TIMEOUT,
714
+ CODE_NETWORK,
715
+ CODE_NO_CREDENTIAL,
716
+ _resetPushCacheForTests,
717
+ };