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.
- package/README.md +3 -1
- package/lib/release-schema.json +2 -2
- package/lib/runtime-closure-policy.json +8 -7
- package/package.json +1 -1
- package/template/coding-task-manager/api-prompts.js +45 -7
- package/template/coding-task-manager/bin/claude-hook.js +55 -1
- package/template/coding-task-manager/bin/ctm-relay-client.js +35 -12
- package/template/coding-task-manager/bin/land.js +2069 -0
- package/template/coding-task-manager/bin/memory-census.js +4 -2
- package/template/coding-task-manager/bin/memory-sync-verify.js +151 -0
- package/template/coding-task-manager/bin/probe-refusal-scope.js +530 -0
- package/template/coding-task-manager/db.js +156 -32
- package/template/coding-task-manager/lib/acp/acp-client.js +383 -0
- package/template/coding-task-manager/lib/acp/dsh-context-truth.js +176 -0
- package/template/coding-task-manager/lib/acp/dsh-launch-model.js +302 -0
- package/template/coding-task-manager/lib/acp/dsh-mcp-servers.js +194 -0
- package/template/coding-task-manager/lib/acp/dsh-prompt-lane.js +616 -0
- package/template/coding-task-manager/lib/acp/dsh-route-patch.js +308 -0
- package/template/coding-task-manager/lib/acp/dsh-session-runtime.js +522 -0
- package/template/coding-task-manager/lib/acp/dsh-transcript.js +395 -0
- package/template/coding-task-manager/lib/acp/dsh-usage-log.js +162 -0
- package/template/coding-task-manager/lib/acp/ndjson-stdio-transport.js +225 -0
- package/template/coding-task-manager/lib/adopted-host-redial.js +47 -11
- package/template/coding-task-manager/lib/adopted-launch-environment.js +123 -0
- package/template/coding-task-manager/lib/agent-auto-update-runtime.js +0 -3
- package/template/coding-task-manager/lib/agent-auto-update.js +26 -10
- package/template/coding-task-manager/lib/agent-bundle/build.js +657 -0
- package/template/coding-task-manager/lib/agent-bundle/codex-install.js +558 -0
- package/template/coding-task-manager/lib/agent-bundle/drift.js +120 -0
- package/template/coding-task-manager/lib/agent-bundle/gc.js +522 -0
- package/template/coding-task-manager/lib/agent-bundle/inventory.js +346 -0
- package/template/coding-task-manager/lib/agent-bundle/push.js +717 -0
- package/template/coding-task-manager/lib/agent-bundle/skill-transfer.js +724 -0
- package/template/coding-task-manager/lib/agent-bundle/spawn.js +119 -0
- package/template/coding-task-manager/lib/agent-bundle/store.js +568 -0
- package/template/coding-task-manager/lib/agent-capabilities.js +142 -12
- package/template/coding-task-manager/lib/agent-cli-cache.js +30 -5
- package/template/coding-task-manager/lib/agent-definitions.js +19 -0
- package/template/coding-task-manager/lib/agent-hooks-codex-toml.js +28 -16
- package/template/coding-task-manager/lib/agent-hooks-codex-workspace.js +9 -1
- package/template/coding-task-manager/lib/agent-hooks-installer.js +23 -8
- package/template/coding-task-manager/lib/agent-hooks-prune.js +37 -11
- package/template/coding-task-manager/lib/agent-hooks-shim.js +105 -0
- package/template/coding-task-manager/lib/agent-presets.js +84 -1
- package/template/coding-task-manager/lib/agent-upgrade-jobs.js +12 -1
- package/template/coding-task-manager/lib/agent-version-service.js +74 -11
- package/template/coding-task-manager/lib/all-sessions-query.js +6 -2
- package/template/coding-task-manager/lib/api/agent-bundles.js +436 -0
- package/template/coding-task-manager/lib/api/backup-retention.js +83 -0
- package/template/coding-task-manager/lib/api/conversation-sync-routes.js +124 -0
- package/template/coding-task-manager/lib/api/diagnostics.js +549 -69
- package/template/coding-task-manager/lib/api/fleet.js +118 -0
- package/template/coding-task-manager/lib/api/hub-bridge.js +243 -0
- package/template/coding-task-manager/lib/api/ingress.js +9 -1
- package/template/coding-task-manager/lib/api/landing.js +403 -0
- package/template/coding-task-manager/lib/api/machines.js +691 -2
- package/template/coding-task-manager/lib/api/memory-sync-routes.js +335 -0
- package/template/coding-task-manager/lib/api/misc-ops.js +8 -3
- package/template/coding-task-manager/lib/api/remote-sessions.js +1326 -0
- package/template/coding-task-manager/lib/api/session-attach.js +623 -0
- package/template/coding-task-manager/lib/api/session-messages.js +1384 -0
- package/template/coding-task-manager/lib/api/sessions.js +77 -51
- package/template/coding-task-manager/lib/api/setup-network.js +544 -16
- package/template/coding-task-manager/lib/api/setup-wizard-handlers.js +884 -0
- package/template/coding-task-manager/lib/api/setup.js +829 -10
- package/template/coding-task-manager/lib/api/walle-bridge.js +9 -1
- package/template/coding-task-manager/lib/attachment-reconciler-wiring.js +5 -2
- package/template/coding-task-manager/lib/auth-rate-limit.js +23 -0
- package/template/coding-task-manager/lib/auth-rules.js +292 -21
- package/template/coding-task-manager/lib/background-stop.js +369 -0
- package/template/coding-task-manager/lib/best-of-attempts.js +4 -1
- package/template/coding-task-manager/lib/best-of-launch-plan.js +52 -0
- package/template/coding-task-manager/lib/box-keep-awake.js +89 -0
- package/template/coding-task-manager/lib/broadcast-reason-stats.js +152 -0
- package/template/coding-task-manager/lib/broadcast-serialize.js +5 -0
- package/template/coding-task-manager/lib/build-info.js +4 -2
- package/template/coding-task-manager/lib/claude-model-truth.js +13 -2
- package/template/coding-task-manager/lib/claude-transcript-terminal-renderer.js +17 -13
- package/template/coding-task-manager/lib/client-diag-handler.js +64 -2
- package/template/coding-task-manager/lib/codex-app-server-client.js +14 -16
- package/template/coding-task-manager/lib/codex-configuration-history.js +55 -0
- package/template/coding-task-manager/lib/codex-harness-qualification.js +43 -20
- package/template/coding-task-manager/lib/codex-rollout-history.js +743 -0
- package/template/coding-task-manager/lib/coding-agent-models.js +27 -12
- package/template/coding-task-manager/lib/coding-cli-skill-installer.js +44 -14
- package/template/coding-task-manager/lib/coding-session-model-adapters.js +43 -2
- package/template/coding-task-manager/lib/coding-session-model-owner.js +1 -1
- package/template/coding-task-manager/lib/coding-session-model-public.js +7 -1
- package/template/coding-task-manager/lib/coding-session-model-runtime.js +23 -4
- package/template/coding-task-manager/lib/coding-session-model-scan.js +31 -0
- package/template/coding-task-manager/lib/coding-session-model-state.js +7 -0
- package/template/coding-task-manager/lib/conversation-freshness-diagnostics.js +280 -0
- package/template/coding-task-manager/lib/conversation-item-clock.js +261 -0
- package/template/coding-task-manager/lib/conversation-source-registry.js +5 -0
- package/template/coding-task-manager/lib/conversation-store-v2.js +44 -0
- package/template/coding-task-manager/lib/conversation-sync.js +355 -0
- package/template/coding-task-manager/lib/create/resolve-launch-params.js +96 -12
- package/template/coding-task-manager/lib/create/worker-role-profile.js +4 -4
- package/template/coding-task-manager/lib/create-intent-lease.js +51 -0
- package/template/coding-task-manager/lib/ctm-session-context-api.js +97 -6
- package/template/coding-task-manager/lib/db-domain-tables.js +1 -0
- package/template/coding-task-manager/lib/db-domains.js +1 -0
- package/template/coding-task-manager/lib/db-owner-dispatch-probe.js +32 -0
- package/template/coding-task-manager/lib/db-owner-request-counts.js +178 -0
- package/template/coding-task-manager/lib/db-owner-worker-client.js +68 -14
- package/template/coding-task-manager/lib/db-schema-source-files.js +5 -0
- package/template/coding-task-manager/lib/deferred-restore-attempt.js +6 -2
- package/template/coding-task-manager/lib/feature-manifest.js +4 -0
- package/template/coding-task-manager/lib/fleet-conversation-route.js +97 -0
- package/template/coding-task-manager/lib/fleet-fanout.js +78 -0
- package/template/coding-task-manager/lib/fleet-hub.js +762 -0
- package/template/coding-task-manager/lib/fleet-origin-follower.js +252 -0
- package/template/coding-task-manager/lib/fleet-resolver-effects.js +147 -0
- package/template/coding-task-manager/lib/fleet-resolver-tick.js +148 -0
- package/template/coding-task-manager/lib/fleet-resolver.js +454 -0
- package/template/coding-task-manager/lib/fleet-session-list-routes.js +114 -0
- package/template/coding-task-manager/lib/fleet-session-lists.js +672 -0
- package/template/coding-task-manager/lib/fleet-update.js +798 -0
- package/template/coding-task-manager/lib/fs-watcher-root-watch.js +141 -0
- package/template/coding-task-manager/lib/fs-watcher.js +58 -42
- package/template/coding-task-manager/lib/harness-history-transition.js +50 -9
- package/template/coding-task-manager/lib/headless-term-service.js +84 -8
- package/template/coding-task-manager/lib/hermetic-agent-launch-guard.js +10 -0
- package/template/coding-task-manager/lib/history-transition-read-op.js +175 -8
- package/template/coding-task-manager/lib/host-cpu-categories.js +310 -0
- package/template/coding-task-manager/lib/host-fseventsd-sampler.js +399 -0
- package/template/coding-task-manager/lib/host-handoff.js +16 -19
- package/template/coding-task-manager/lib/host-stats.js +150 -3
- package/template/coding-task-manager/lib/hosted-sync-worker.js +43 -320
- package/template/coding-task-manager/lib/hub-bridge-access.js +798 -0
- package/template/coding-task-manager/lib/hub-bridge-box.js +409 -0
- package/template/coding-task-manager/lib/hub-bridge-mcp.js +380 -0
- package/template/coding-task-manager/lib/hub-bridge-mesh.js +313 -0
- package/template/coding-task-manager/lib/hub-bridge-ops.js +721 -0
- package/template/coding-task-manager/lib/hub-bridge.js +398 -0
- package/template/coding-task-manager/lib/idle-notify.js +491 -0
- package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +8 -0
- package/template/coding-task-manager/lib/instance-manifest.js +5 -1
- package/template/coding-task-manager/lib/instance-sync-status.js +18 -8
- package/template/coding-task-manager/lib/landing/agent-rule.txt +1 -0
- package/template/coding-task-manager/lib/landing/batch-runner.js +366 -0
- package/template/coding-task-manager/lib/landing/config.js +536 -0
- package/template/coding-task-manager/lib/landing/ctm-view.js +256 -0
- package/template/coding-task-manager/lib/landing/gate.js +360 -0
- package/template/coding-task-manager/lib/landing/git.js +344 -0
- package/template/coding-task-manager/lib/landing/lease.js +247 -0
- package/template/coding-task-manager/lib/landing/merge-delta.js +380 -0
- package/template/coding-task-manager/lib/landing/proof.js +223 -0
- package/template/coding-task-manager/lib/landing/queue.js +998 -0
- package/template/coding-task-manager/lib/landing/radar.js +489 -0
- package/template/coding-task-manager/lib/landing/resolve-cli.js +424 -0
- package/template/coding-task-manager/lib/landing/state.js +263 -0
- package/template/coding-task-manager/lib/landing/stop.js +49 -0
- package/template/coding-task-manager/lib/landing/sync-cli.js +475 -0
- package/template/coding-task-manager/lib/landing/sync.js +456 -0
- package/template/coding-task-manager/lib/landing/train.js +885 -0
- package/template/coding-task-manager/lib/landing/window.js +57 -0
- package/template/coding-task-manager/lib/launch-environment.js +121 -0
- package/template/coding-task-manager/lib/launch-model-override.js +36 -10
- package/template/coding-task-manager/lib/launch-plan-pool.js +2 -1
- package/template/coding-task-manager/lib/launch-plan-warm-cache.js +6 -0
- package/template/coding-task-manager/lib/lead-model-observer.js +57 -1
- package/template/coding-task-manager/lib/lead-model-options.js +160 -3
- package/template/coding-task-manager/lib/lead-model-restore-reconciliation.js +106 -24
- package/template/coding-task-manager/lib/lead-model-switch-runtime.js +352 -68
- package/template/coding-task-manager/lib/lead-model-switch.js +114 -10
- package/template/coding-task-manager/lib/lead-model-transition-runtime.js +14 -2
- package/template/coding-task-manager/lib/lead-model-transition-store.js +24 -6
- package/template/coding-task-manager/lib/lead-route-preflight.js +179 -0
- package/template/coding-task-manager/lib/lead-switch-history-mode.js +110 -0
- package/template/coding-task-manager/lib/lead-wire-agent.js +7 -0
- package/template/coding-task-manager/lib/lifecycle-wake-experiment.js +9 -2
- package/template/coding-task-manager/lib/live-defer-announce.js +45 -0
- package/template/coding-task-manager/lib/machine-registry.js +750 -5
- package/template/coding-task-manager/lib/macos-capabilities.js +32 -15
- package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +433 -53
- package/template/coding-task-manager/lib/mainloop-observability.js +14 -9
- package/template/coding-task-manager/lib/media-draft-owner.js +16 -3
- package/template/coding-task-manager/lib/memory-join.js +337 -0
- package/template/coding-task-manager/lib/memory-sync-verify.js +393 -0
- package/template/coding-task-manager/lib/memory-sync.js +565 -0
- package/template/coding-task-manager/lib/migrations/v57-delegate-pref-mirror-opencode.js +68 -0
- package/template/coding-task-manager/lib/migrations/v58-subagent-launch-native-subagents.js +42 -0
- package/template/coding-task-manager/lib/migrations/v59-coding-session-model-state-dsh.js +73 -0
- package/template/coding-task-manager/lib/migrations/v60-delegate-pref-mirror-dsh.js +78 -0
- package/template/coding-task-manager/lib/migrations/v61-conversation-item-clock.js +71 -0
- package/template/coding-task-manager/lib/model-availability.js +727 -0
- package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +4 -6
- package/template/coding-task-manager/lib/model-host-receipt.js +6 -3
- package/template/coding-task-manager/lib/model-id-rule.js +19 -0
- package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
- package/template/coding-task-manager/lib/model-proxy/auto-mode-review.js +110 -23
- package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
- package/template/coding-task-manager/lib/model-proxy/generation-probe.js +60 -17
- package/template/coding-task-manager/lib/model-proxy/historical-tool-declarations.js +37 -0
- package/template/coding-task-manager/lib/model-proxy/lead-request-validation.js +1 -0
- package/template/coding-task-manager/lib/model-proxy/lead-token-admin.js +130 -0
- package/template/coding-task-manager/lib/model-proxy/refusal-scope.js +151 -0
- package/template/coding-task-manager/lib/model-proxy/request-budget-store.js +11 -2
- package/template/coding-task-manager/lib/model-proxy/tokens.js +69 -5
- package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
- package/template/coding-task-manager/lib/native-agent-model-args.js +25 -22
- package/template/coding-task-manager/lib/native-history-control.js +6 -4
- package/template/coding-task-manager/lib/native-lead-settings-client.js +44 -4
- package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +173 -36
- package/template/coding-task-manager/lib/online-backup-worker-client.js +2 -0
- package/template/coding-task-manager/lib/opencode-cli.js +480 -0
- package/template/coding-task-manager/lib/opencode-launch-model.js +471 -0
- package/template/coding-task-manager/lib/opencode-v1-plugin/model-carrier.mjs +35 -0
- package/template/coding-task-manager/lib/opencode-v2-plugin/server.js +118 -0
- package/template/coding-task-manager/lib/owner-action-manifest-data.json +1 -1
- package/template/coding-task-manager/lib/park-composer-draft.js +54 -0
- package/template/coding-task-manager/lib/park-fresh-relaunch.js +41 -10
- package/template/coding-task-manager/lib/perf-phase-trace.js +72 -0
- package/template/coding-task-manager/lib/perf-summary.js +7 -0
- package/template/coding-task-manager/lib/perf-tracker.js +63 -2
- package/template/coding-task-manager/lib/project-config.js +7 -0
- package/template/coding-task-manager/lib/prompt-purge-v2.js +52 -1
- package/template/coding-task-manager/lib/provider-login-broker.js +72 -15
- package/template/coding-task-manager/lib/provider-login-tab.js +10 -2
- package/template/coding-task-manager/lib/read-pool-client.js +89 -2
- package/template/coding-task-manager/lib/read-pool-worker-handle.js +29 -4
- package/template/coding-task-manager/lib/recent-catalog-query.js +15 -0
- package/template/coding-task-manager/lib/recent-session-catalog-response.js +67 -10
- package/template/coding-task-manager/lib/recovery-evidence.js +26 -11
- package/template/coding-task-manager/lib/relay-client.js +57 -3
- package/template/coding-task-manager/lib/relink-audit.js +34 -5
- package/template/coding-task-manager/lib/remote-machine-catalog.js +321 -26
- package/template/coding-task-manager/lib/remote-machine-create.js +576 -36
- package/template/coding-task-manager/lib/remote-machine-link.js +546 -59
- package/template/coding-task-manager/lib/remote-machine-proxy.js +143 -9
- package/template/coding-task-manager/lib/remote-session-forward.js +65 -7
- package/template/coding-task-manager/lib/remote-terminal-target.js +9 -2
- package/template/coding-task-manager/lib/restart-guard.js +18 -6
- package/template/coding-task-manager/lib/restore-seed-modes.js +87 -0
- package/template/coding-task-manager/lib/restored-scrollback-hydration-runtime.js +22 -2
- package/template/coding-task-manager/lib/resume-admission.js +17 -3
- package/template/coding-task-manager/lib/review-repo-registry.js +58 -19
- package/template/coding-task-manager/lib/root-delivery-runtime.js +2 -2
- package/template/coding-task-manager/lib/root-prompt-flow.js +1 -1
- package/template/coding-task-manager/lib/runtime-contract.js +3 -1
- package/template/coding-task-manager/lib/schema-indexes.js +1 -0
- package/template/coding-task-manager/lib/session-context-locator.js +742 -0
- package/template/coding-task-manager/lib/session-create/attachment.js +2 -2
- package/template/coding-task-manager/lib/session-create/create-phases.js +118 -0
- package/template/coding-task-manager/lib/session-create/delegate-worker.js +86 -16
- package/template/coding-task-manager/lib/session-create/dsh-session.js +930 -0
- package/template/coding-task-manager/lib/session-create/lead-route-launch.js +186 -13
- package/template/coding-task-manager/lib/session-create/native-lead-lifetime.js +55 -3
- package/template/coding-task-manager/lib/session-create/opencode-lead-provider.js +50 -0
- package/template/coding-task-manager/lib/session-create/prepared.js +162 -38
- package/template/coding-task-manager/lib/session-create/provider-process.js +160 -28
- package/template/coding-task-manager/lib/session-create/response.js +7 -7
- package/template/coding-task-manager/lib/session-create/structured-session.js +27 -0
- package/template/coding-task-manager/lib/session-create/subagent-pick-table.js +3 -1
- package/template/coding-task-manager/lib/session-create/subagent-pick.js +458 -9
- package/template/coding-task-manager/lib/session-create/transcript.js +1 -1
- package/template/coding-task-manager/lib/session-create/worker-budget-setup.js +21 -2
- package/template/coding-task-manager/lib/session-create/worker-continuation.js +7 -1
- package/template/coding-task-manager/lib/session-create/worker-launch-sandbox.js +39 -10
- package/template/coding-task-manager/lib/session-create/worker-observability.js +2 -2
- package/template/coding-task-manager/lib/session-empty.js +13 -10
- package/template/coding-task-manager/lib/session-host-manager.js +60 -0
- package/template/coding-task-manager/lib/session-jobs.js +3 -1
- package/template/coding-task-manager/lib/session-lifecycle.js +10 -4
- package/template/coding-task-manager/lib/session-model-action-preconditions.js +6 -2
- package/template/coding-task-manager/lib/session-model-intent-contract.js +11 -8
- package/template/coding-task-manager/lib/session-park.js +738 -0
- package/template/coding-task-manager/lib/session-path-grant.js +13 -4
- package/template/coding-task-manager/lib/session-status-ledger.js +106 -4
- package/template/coding-task-manager/lib/session-transport.js +17 -0
- package/template/coding-task-manager/lib/session-workspace-projection.js +54 -5
- package/template/coding-task-manager/lib/session-workspace-tracking-runtime.js +213 -1
- package/template/coding-task-manager/lib/skill-autocomplete-query.js +120 -3
- package/template/coding-task-manager/lib/skill-autocomplete.js +15 -1
- package/template/coding-task-manager/lib/sqlite-file-set.js +39 -0
- package/template/coding-task-manager/lib/standup-search-adapter.js +16 -2
- package/template/coding-task-manager/lib/state-sync/client-ledger.js +12 -0
- package/template/coding-task-manager/lib/state-sync/frame-emitter.js +319 -100
- package/template/coding-task-manager/lib/storage/coding-session-model-store.js +4 -1
- package/template/coding-task-manager/lib/storage/delegate-pref-mirror-store.js +32 -9
- package/template/coding-task-manager/lib/structured-session-boot-sweep.js +235 -0
- package/template/coding-task-manager/lib/submission-ack.js +90 -2
- package/template/coding-task-manager/lib/systemd-scope.js +115 -0
- package/template/coding-task-manager/lib/telemetry-receiver.js +39 -0
- package/template/coding-task-manager/lib/terminal-attach-geometry.js +985 -0
- package/template/coding-task-manager/lib/terminal-bootstrap.js +30 -0
- package/template/coding-task-manager/lib/terminal-draft-operation.js +357 -0
- package/template/coding-task-manager/lib/terminal-input-admission.js +17 -1
- package/template/coding-task-manager/lib/terminal-input-prepared.js +563 -0
- package/template/coding-task-manager/lib/transcript-cache-temps.js +171 -0
- package/template/coding-task-manager/lib/transcript-frame-cache.js +8 -3
- package/template/coding-task-manager/lib/transcript-source.js +36 -4
- package/template/coding-task-manager/lib/transcript-store.js +9 -0
- package/template/coding-task-manager/lib/typing-interaction-trace.js +106 -3
- package/template/coding-task-manager/lib/usage-ledger/backfill.js +40 -5
- package/template/coding-task-manager/lib/usage-ledger/client.js +4 -1
- package/template/coding-task-manager/lib/usage-ledger/dsh-parse.js +152 -0
- package/template/coding-task-manager/lib/usage-ledger/dsh-read.js +73 -0
- package/template/coding-task-manager/lib/usage-ledger/ingest.js +34 -6
- package/template/coding-task-manager/lib/usage-ledger/live.js +35 -8
- package/template/coding-task-manager/lib/usage-ledger/read.js +29 -20
- package/template/coding-task-manager/lib/usage-ledger/schema.js +61 -1
- package/template/coding-task-manager/lib/usage-ledger/task-coordinator.js +4 -2
- package/template/coding-task-manager/lib/wake-skip-reasons.js +1 -0
- package/template/coding-task-manager/lib/walle/turn.js +1 -1
- package/template/coding-task-manager/lib/walle-history-serve.js +838 -0
- package/template/coding-task-manager/lib/walle-kill-authority.js +2 -2
- package/template/coding-task-manager/lib/walle-log-rotation.js +164 -1
- package/template/coding-task-manager/lib/walle-mcp-auto-config.js +56 -2
- package/template/coding-task-manager/lib/walle-row-history.js +495 -0
- package/template/coding-task-manager/lib/walle-supervisor.js +33 -14
- package/template/coding-task-manager/lib/watch-worker-client.js +203 -0
- package/template/coding-task-manager/lib/worker-terminal-output.js +5 -2
- package/template/coding-task-manager/lib/ws/hub-bridge.js +35 -0
- package/template/coding-task-manager/lib/ws/lifecycle.js +6 -11
- package/template/coding-task-manager/lib/ws/misc.js +1 -1
- package/template/coding-task-manager/lib/ws/state-sync.js +106 -4
- package/template/coding-task-manager/lib/ws/terminal-attach.js +91 -0
- package/template/coding-task-manager/oauth-proxy.js +112 -115
- package/template/coding-task-manager/package-lock.json +10 -94
- package/template/coding-task-manager/package.json +4 -3
- package/template/coding-task-manager/protocol/message-table.json +10 -0
- package/template/coding-task-manager/protocol/messages.js +10 -0
- package/template/coding-task-manager/protocol/ws-schema.json +20 -0
- package/template/coding-task-manager/protocol/ws-schema.md +2 -0
- package/template/coding-task-manager/public/css/app.css +170 -0
- package/template/coding-task-manager/public/css/walle-session.css +4 -0
- package/template/coding-task-manager/public/index.html +473 -243
- package/template/coding-task-manager/public/js/agent-command.js +86 -0
- package/template/coding-task-manager/public/js/app/backups-panel.js +111 -5
- package/template/coding-task-manager/public/js/app/coding-agent-upgrade.js +103 -4
- package/template/coding-task-manager/public/js/app/machines-page.js +1328 -8
- package/template/coding-task-manager/public/js/app/models-tab.js +113 -38
- package/template/coding-task-manager/public/js/app/restart-banner.js +53 -0
- package/template/coding-task-manager/public/js/app/worktrees-panel.js +184 -2
- package/template/coding-task-manager/public/js/background-work.js +196 -27
- package/template/coding-task-manager/public/js/coding-session-model-presentation.js +138 -10
- package/template/coding-task-manager/public/js/core/active-session-groups.js +179 -0
- package/template/coding-task-manager/public/js/core/pending-create.js +238 -0
- package/template/coding-task-manager/public/js/core/recent-machine-source.js +228 -0
- package/template/coding-task-manager/public/js/core/session-commands.js +180 -14
- package/template/coding-task-manager/public/js/dashboard-client-function-order.json +8 -0
- package/template/coding-task-manager/public/js/document-review-links.js +9 -1
- package/template/coding-task-manager/public/js/file-resolution-client.js +52 -1
- package/template/coding-task-manager/public/js/history-app-seam.js +22 -2
- package/template/coding-task-manager/public/js/machine-pool-shared.js +558 -3
- package/template/coding-task-manager/public/js/message-renderer.js +4 -3
- package/template/coding-task-manager/public/js/model-picker.js +3 -1
- package/template/coding-task-manager/public/js/native-subagent-review.js +15 -2
- package/template/coding-task-manager/public/js/new-session-agent-grid.js +346 -0
- package/template/coding-task-manager/public/js/pasted-content.js +236 -0
- package/template/coding-task-manager/public/js/pe-private-content-lifetime.js +39 -7
- package/template/coding-task-manager/public/js/private-model-lifetime.js +6 -2
- package/template/coding-task-manager/public/js/prompts.js +11 -10
- package/template/coding-task-manager/public/js/protocol.js +10 -0
- package/template/coding-task-manager/public/js/restart-banner-policy.js +29 -1
- package/template/coding-task-manager/public/js/session-model-picker.js +447 -57
- package/template/coding-task-manager/public/js/session-search-utils.js +18 -2
- package/template/coding-task-manager/public/js/session-usage-view.js +2 -1
- package/template/coding-task-manager/public/js/session-workspace-tracking.js +51 -1
- package/template/coding-task-manager/public/js/state-sync-client.js +10 -1
- package/template/coding-task-manager/public/js/stream-view.js +6 -5
- package/template/coding-task-manager/public/js/terminal/conv-only-session.js +287 -0
- package/template/coding-task-manager/public/js/terminal/runtime-09.js +2 -0
- package/template/coding-task-manager/public/js/terminal/runtime-10.js +5 -5
- package/template/coding-task-manager/public/js/terminal/runtime-12.js +8 -1
- package/template/coding-task-manager/public/js/terminal/runtime-16.js +4 -0
- package/template/coding-task-manager/public/js/terminal/runtime-20.js +1 -1
- package/template/coding-task-manager/public/js/terminal-attachment-draft.js +67 -13
- package/template/coding-task-manager/public/js/terminal-image-orchestration.js +52 -6
- package/template/coding-task-manager/public/js/terminal-wheel.js +23 -3
- package/template/coding-task-manager/public/js/walle/chat-conversation.js +4 -4
- package/template/coding-task-manager/public/js/walle.js +6 -0
- package/template/coding-task-manager/public/js/ws/live-dispatch.js +10 -0
- package/template/coding-task-manager/public/m/app.css +140 -0
- package/template/coding-task-manager/public/m/app.js +507 -68
- package/template/coding-task-manager/public/m/index.html +17 -0
- package/template/coding-task-manager/public/m/machines.js +838 -4
- package/template/coding-task-manager/public/m/private-api.js +31 -1
- package/template/coding-task-manager/public/usage.html +1 -0
- package/template/coding-task-manager/scripts/dev-route-fixture/synthetic-route-upstream.js +104 -0
- package/template/coding-task-manager/scripts/dev-route-fixture.js +339 -0
- package/template/coding-task-manager/scripts/opencode-lead-route-probe.js +195 -0
- package/template/coding-task-manager/scripts/opencode-v2-contract-probe/fake-anthropic.js +168 -0
- package/template/coding-task-manager/scripts/opencode-v2-contract-probe/probe-plugin/server.js +95 -0
- package/template/coding-task-manager/scripts/opencode-v2-contract-probe.js +673 -0
- package/template/coding-task-manager/server.js +1950 -11679
- package/template/coding-task-manager/session-integrity.js +64 -4
- package/template/coding-task-manager/workers/db-owner-worker.js +19 -2
- package/template/coding-task-manager/workers/online-backup-worker.js +1 -0
- package/template/coding-task-manager/workers/read-pool-model-collector.js +51 -3
- package/template/coding-task-manager/workers/read-pool-worker.js +76 -6
- package/template/coding-task-manager/workers/session-host-pool-process.js +6 -6
- package/template/coding-task-manager/workers/session-host-process.js +5 -5
- package/template/coding-task-manager/workers/watch-worker.js +105 -0
- package/template/coding-task-manager/workers/worktree-worker.js +168 -33
- package/template/ctm-relay/lib/protocol.js +14 -1
- package/template/package-lock.json +2 -2
- package/template/package.json +1 -1
- package/template/shared/backup-retention.js +82 -0
- package/template/shared/conversation-admission.js +2 -0
- package/template/shared/conversation-provenance.js +3 -7
- package/template/shared/conversation-storage-v2-contract.js +5 -0
- package/template/shared/linked-worktree.js +51 -0
- package/template/shared/macos-fda-target.js +57 -0
- package/template/shared/orphan-parents.js +55 -0
- package/template/shared/release-schema.json +2 -2
- package/template/shared/storage/contract.js +3 -0
- package/template/wall-e/adapters/imessage.js +12 -0
- package/template/wall-e/agent.js +71 -4
- package/template/wall-e/api-walle.js +21 -1
- package/template/wall-e/bin/train-gemma-e4b-tooluse.js +2 -2
- package/template/wall-e/bin/walle-mcp-stdio.js +5 -5
- package/template/wall-e/brain/brain-join.js +859 -0
- package/template/wall-e/brain/brain-sync-membership.js +20 -0
- package/template/wall-e/brain/brain-sync-store.js +131 -77
- package/template/wall-e/brain/brain-sync-v2-schema.js +203 -0
- package/template/wall-e/brain/brain-sync-v2.js +578 -0
- package/template/wall-e/brain/delegate-model-prefs-store.js +13 -4
- package/template/wall-e/brain/deterministic-id.js +51 -0
- package/template/wall-e/brain/memories.js +2 -2
- package/template/wall-e/brain/migrations.js +40 -6
- package/template/wall-e/brain/model-identity-migration.js +1 -1
- package/template/wall-e/brain/owner-call-whitelist.js +23 -7
- package/template/wall-e/brain/schema.js +45 -85
- package/template/wall-e/brain.js +171 -35
- package/template/wall-e/chat.js +3 -3
- package/template/wall-e/coding/agent-shell-env.js +7 -2
- package/template/wall-e/coding/prompt-bundle.js +66 -21
- package/template/wall-e/config/main-loop-exemptions.json +2 -2
- package/template/wall-e/http/admin-api.js +37 -0
- package/template/wall-e/http/gws-api.js +4 -0
- package/template/wall-e/http/helper-rules-api.js +7 -1
- package/template/wall-e/http/model-admin.js +8 -1
- package/template/wall-e/http/model-delegate-pref-admin.js +3 -3
- package/template/wall-e/http/model-proxy-providers.js +174 -1
- package/template/wall-e/http/model-route-health.js +157 -0
- package/template/wall-e/http/sync-api.js +278 -464
- package/template/wall-e/jobs/job-runner.js +28 -1
- package/template/wall-e/lib/brain-owner-op-priority.js +9 -1
- package/template/wall-e/lib/ctm-data-dir.js +26 -0
- package/template/wall-e/lib/derivation-home.js +147 -0
- package/template/wall-e/lib/helper-rules/evaluate.js +50 -9
- package/template/wall-e/lib/helper-rules/grade.js +51 -8
- package/template/wall-e/lib/helper-rules/lead-call-hook.js +38 -5
- package/template/wall-e/lib/helper-rules/store.js +4 -2
- package/template/wall-e/lib/landing-mcp-tools.js +170 -0
- package/template/wall-e/lib/mcp-client-config.js +133 -14
- package/template/wall-e/lib/mcp-gateway-migrate.js +28 -64
- package/template/wall-e/lib/mcp-integration.js +58 -13
- package/template/wall-e/lib/parent-death.js +7 -1
- package/template/wall-e/lib/scheduler-worker-jobs.js +6 -4
- package/template/wall-e/llm/delegate-lead-prefs.js +16 -1
- package/template/wall-e/llm/delegate-vendor-default.js +12 -5
- package/template/wall-e/llm/model-roles.js +1 -1
- package/template/wall-e/llm/native-delegate-selection.js +56 -8
- package/template/wall-e/llm/worker-delegate-args.js +72 -7
- package/template/wall-e/llm/worker-mcp-routing.js +66 -17
- package/template/wall-e/loops/brain-custom-writes.js +19 -0
- package/template/wall-e/loops/ingest.js +17 -1
- package/template/wall-e/loops/think.js +19 -4
- package/template/wall-e/mcp-contract.js +222 -6
- package/template/wall-e/mcp-protocol-contract.js +40 -5
- package/template/wall-e/mcp-server.js +128 -29
- package/template/wall-e/mcp-worker-schemas.js +5 -5
- package/template/wall-e/memory/ctm-context-client.js +85 -0
- package/template/wall-e/memory/ctm-session-context.js +9 -3
- package/template/wall-e/memory/hub-bridge-client.js +388 -0
- package/template/wall-e/memory/replica-origin.js +56 -0
- package/template/wall-e/memory/session-pool.js +135 -0
- package/template/wall-e/runtime/live-capabilities.js +8 -3
- package/template/wall-e/server.js +4 -2
- package/template/wall-e/shared/release-schema.json +2 -2
- package/template/wall-e/shared/sqlite-file-set.js +39 -0
- package/template/wall-e/shared/storage/contract.js +3 -0
- package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
- package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +7 -1
- package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +4 -4
- package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +6 -6
- package/template/wall-e/skills/_templates/fleet-sync/VERSION +1 -0
- package/template/wall-e/skills/_templates/fleet-sync/released-sha256.json +16 -0
- package/template/wall-e/skills/_templates/fleet-sync/templates/claude/SKILL.md +64 -0
- package/template/wall-e/skills/_templates/fleet-sync/templates/codex/SKILL.md +64 -0
- package/template/wall-e/tools/chrome-automation.js +6 -0
- package/template/wall-e/training/setup-cuda-train-env.sh +2 -2
- package/template/wall-e/workers/brain-owner-worker.js +18 -4
- 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
|
+
};
|