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,436 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-2 (docs/remote-agent-skills-bundles-2026-10-25.html §5 D-1/D-2/D-5, §6.2, §6.3): the BOX-side
|
|
4
|
+
// bundle ingress — the three routes a hub calls with its peer bearer to hand this machine the skills
|
|
5
|
+
// and plugins it must run a forwarded session with.
|
|
6
|
+
//
|
|
7
|
+
// POST /api/agent-bundles/missing { blobs: [hex…] } -> 200 { missing }
|
|
8
|
+
// POST /api/agent-bundles/blobs { blobs: [{hash, data(base64)}] }
|
|
9
|
+
// -> 200 { stored, present } (WP-4/§6.2, D-6)
|
|
10
|
+
// PUT /api/agent-bundles/blobs/<hex> raw bytes -> 201 { stored:true } | 200 { stored:false }
|
|
11
|
+
// PUT /api/agent-bundles/manifests/<treeHash> JSON -> 201 { treeHash, materialized:true }
|
|
12
|
+
// | 200 { treeHash, materialized:false }
|
|
13
|
+
// GET /api/agent-skills/inventory the hub's read -> 200 { platform, checkedAt, roots,
|
|
14
|
+
// skills, base } (WP-7/§6.2, D-11)
|
|
15
|
+
// GET /api/agent-skills/export ?agent&name -> 200 { agent, name, itemHash, files }
|
|
16
|
+
// POST /api/agent-skills/retire { agent, name, expectHash } -> 200 { retiredTo }
|
|
17
|
+
// (WP-8/§6.2, D-12/D-13)
|
|
18
|
+
//
|
|
19
|
+
// Storage and verification are lib/agent-bundle/store.js's (blob re-hash, manifest tree-hash check,
|
|
20
|
+
// materialization); this module owns only the wire: body limits, the status table, and the peer
|
|
21
|
+
// identity read.
|
|
22
|
+
//
|
|
23
|
+
// Like lib/api/host-stats.js and lib/api/machines.js (and unlike the lib/api/*.js extraction slices)
|
|
24
|
+
// this is NEW surface: the handler bodies live here, so there is no legacy handleApi branch to delete
|
|
25
|
+
// and tests-integration/api-router-parity.test.js's no-dual-dispatch rule is satisfied by
|
|
26
|
+
// construction. Auth is NOT evaluated here: lib/auth-rules.js carries the three `admin` +
|
|
27
|
+
// `peerControl: true` rows, and a route with no rule is 403 auth_rule_missing by design — so the rule
|
|
28
|
+
// rows and these registrations must land together.
|
|
29
|
+
//
|
|
30
|
+
// IDENTITY: every route refuses a caller with no authenticated peer identity (403
|
|
31
|
+
// `agent_bundle_peer_unidentified`) before it reads a body. The peer is `req.ctmAuth.peerInstanceId`
|
|
32
|
+
// (the same read server.js's sync routes use) — never a field from the request — so a bundle can
|
|
33
|
+
// only ever be written under the identity that actually authenticated.
|
|
34
|
+
const os = require('os');
|
|
35
|
+
const { registerApiRoutes } = require('../api-router');
|
|
36
|
+
const store = require('../agent-bundle/store');
|
|
37
|
+
// WP-5 (docs/remote-agent-skills-bundles-2026-10-25.html §8 WP-5 row): a manifest PUT that
|
|
38
|
+
// MATERIALIZED a tree is one of exactly two moments the box store grows, so it is one of exactly
|
|
39
|
+
// two moments a sweep is scheduled (the other is boot, in server.js). Event-driven, never a timer.
|
|
40
|
+
const bundleGc = require('../agent-bundle/gc');
|
|
41
|
+
// WP-6 (docs/remote-agent-skills-bundles-2026-10-25.html §5 D-14, §8 WP-6 row): the same PUT is also
|
|
42
|
+
// the moment the box's `$HOME/.agents/skills` copies are brought in line with the tree it just
|
|
43
|
+
// pointed at — on EVERY successful PUT, because the hub pointer moves on a re-PUT too.
|
|
44
|
+
const bundleCodexInstall = require('../agent-bundle/codex-install');
|
|
45
|
+
// WP-7 (docs/remote-agent-skills-bundles-2026-10-25.html §5 D-11, §6.2): the RETURN path — the box
|
|
46
|
+
// reports the skills it already holds so the hub can classify them instead of copying anything.
|
|
47
|
+
const bundleInventory = require('../agent-bundle/inventory');
|
|
48
|
+
// WP-8 (docs/remote-agent-skills-bundles-2026-10-25.html §5 D-12/D-13, §6.2): the ONE-SKILL
|
|
49
|
+
// exchange — `export` ships one box item's bytes to the hub, `retire` MOVES that item aside on the box
|
|
50
|
+
// when the hub now owns the copy. Policy lives in the lib; this module owns only the wire.
|
|
51
|
+
const skillTransfer = require('../agent-bundle/skill-transfer');
|
|
52
|
+
const { MAX_ENTRIES } = require('../agent-bundle/build');
|
|
53
|
+
|
|
54
|
+
const { AgentBundleError } = store;
|
|
55
|
+
|
|
56
|
+
// §6.2 body limits. MAX_ENTRIES mirrors build.js: the hub cannot produce a longer list, so the box
|
|
57
|
+
// answers the same way it would to an unparseable body rather than reading unbounded input.
|
|
58
|
+
const MAX_MISSING_BODY_BYTES = 1024 * 1024;
|
|
59
|
+
// WP-4/§6.2/D-6: the batch body is JSON + base64, so it is capped at its own limit rather than
|
|
60
|
+
// MAX_BLOB_BYTES. A 4 MiB raw batch (D-7) base64-grows to ~5.4 MiB, under this cap.
|
|
61
|
+
const MAX_BATCH_BODY_BYTES = 8 * 1024 * 1024;
|
|
62
|
+
const MAX_BLOB_BYTES = 25 * 1024 * 1024;
|
|
63
|
+
const MAX_MANIFEST_BODY_BYTES = 4 * 1024 * 1024;
|
|
64
|
+
|
|
65
|
+
const ROUTE_MISSING = 'POST /api/agent-bundles/missing';
|
|
66
|
+
const ROUTE_BLOB_BATCH = 'POST /api/agent-bundles/blobs';
|
|
67
|
+
const ROUTE_BLOB = 'PUT /api/agent-bundles/blobs/:sha256';
|
|
68
|
+
const ROUTE_MANIFEST = 'PUT /api/agent-bundles/manifests/:treeHash';
|
|
69
|
+
const ROUTE_INVENTORY = 'GET /api/agent-skills/inventory';
|
|
70
|
+
const ROUTE_SKILL_EXPORT = 'GET /api/agent-skills/export';
|
|
71
|
+
const ROUTE_SKILL_RETIRE = 'POST /api/agent-skills/retire';
|
|
72
|
+
|
|
73
|
+
// One table: the store's error codes are the wire's codes (D-5 wording), and nothing else maps to a
|
|
74
|
+
// 4xx. An unrecognized error is a 500 `internal_error`, server.js's own global shape.
|
|
75
|
+
const STATUS_BY_CODE = {
|
|
76
|
+
agent_bundle_path_invalid: 422,
|
|
77
|
+
agent_bundle_blob_hash_mismatch: 422,
|
|
78
|
+
agent_bundle_tree_hash_mismatch: 422,
|
|
79
|
+
agent_bundle_blobs_missing: 409,
|
|
80
|
+
agent_bundle_peer_unidentified: 403,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
let _deps = null;
|
|
84
|
+
|
|
85
|
+
function configure({
|
|
86
|
+
dataDir,
|
|
87
|
+
// WP-8: the box user's home, injected rather than `os.homedir()`-at-call-site so a route test can
|
|
88
|
+
// point the export/retire legs at a temp HOME. In production it is the process home, which the
|
|
89
|
+
// spawner already sets to the box user's.
|
|
90
|
+
homeDir = null,
|
|
91
|
+
readJsonRequestBody,
|
|
92
|
+
sendJsonResponse,
|
|
93
|
+
errorSink = null,
|
|
94
|
+
// WP-5: both are injected rather than required at call time so the route test can observe the
|
|
95
|
+
// trigger without a timer, a flag, or a spy on the store.
|
|
96
|
+
scheduleGc = null, // (dataDir, deps) => Promise<result>; defaults to the real single-flight sweep
|
|
97
|
+
sessionExists = null, // (ctmSessionId) => Promise<boolean>; server.js wires the box's DB read
|
|
98
|
+
// WP-6: same seam shape as scheduleGc — the route test observes the trigger without a timer.
|
|
99
|
+
scheduleCodexInstall = null, // (dataDir, deps) => Promise<result>; defaults to the real single-flight run
|
|
100
|
+
// WP-3 (agent-bundle-darwin-readonly-rename-2026-10-27.html §16 D-8 T1): the hub pointer for this
|
|
101
|
+
// peer just moved (or a tree materialized), so any live session still running the OLD tree is now
|
|
102
|
+
// drifted. Injected, never required at call time — the module stays wire-only and testable.
|
|
103
|
+
onBundlePointerMoved = null, // (peerInstanceId) => void
|
|
104
|
+
} = {}) {
|
|
105
|
+
_deps = { dataDir, homeDir, readJsonRequestBody, sendJsonResponse, errorSink, scheduleGc, sessionExists, scheduleCodexInstall, onBundlePointerMoved };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The box user's home, for the WP-8 export/retire legs. */
|
|
109
|
+
function _homeDir() {
|
|
110
|
+
if (_deps && _deps.homeDir) return _deps.homeDir;
|
|
111
|
+
return os.homedir();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function _send(res, status, payload) {
|
|
115
|
+
if (_deps && typeof _deps.sendJsonResponse === 'function') return _deps.sendJsonResponse(res, status, payload);
|
|
116
|
+
const body = JSON.stringify(payload || {});
|
|
117
|
+
res.writeHead(status, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) });
|
|
118
|
+
res.end(body);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function _dataDir() {
|
|
122
|
+
if (_deps && _deps.dataDir) return _deps.dataDir;
|
|
123
|
+
return require('../data-dir').getDataDir();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** The authenticated hub, or '' — the same read server.js:52696 does for its sync routes. */
|
|
127
|
+
function _peerInstanceId(req) {
|
|
128
|
+
return String((req && req.ctmAuth && req.ctmAuth.peerInstanceId) || '').trim();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function _payloadTooLarge(res) {
|
|
132
|
+
return _send(res, 413, { error: 'payload_too_large', code: 'PAYLOAD_TOO_LARGE' });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* WP-5: fire-and-forget sweep after the response is out. The scheduler is single-flight and never
|
|
137
|
+
* rejects, so `void` here cannot produce an unhandled rejection; a sweep fault reaches the error sink
|
|
138
|
+
* through the `report` this passes in.
|
|
139
|
+
*/
|
|
140
|
+
function _scheduleGc() {
|
|
141
|
+
const schedule = (_deps && _deps.scheduleGc) || bundleGc.scheduleCollectGarbage;
|
|
142
|
+
const report = (error) => {
|
|
143
|
+
if (_deps && _deps.errorSink && typeof _deps.errorSink.report === 'function') {
|
|
144
|
+
_deps.errorSink.report('agent-bundle-gc', error);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
schedule(_dataDir(), { sessionExists: (_deps && _deps.sessionExists) || null, report });
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* WP-6/D-14: fire-and-forget Codex install after the response is out — the box's `$HOME/.agents/skills`
|
|
152
|
+
* copies are brought in line with the tree this PUT just pointed at. The scheduler is single-flight and
|
|
153
|
+
* never rejects, so `void` here cannot produce an unhandled rejection; a fault reaches the error sink
|
|
154
|
+
* through the `report` this passes in. The hub identity is the AUTHENTICATED one, never a body field.
|
|
155
|
+
*/
|
|
156
|
+
function _scheduleCodexInstall(hubPeerId) {
|
|
157
|
+
const schedule = (_deps && _deps.scheduleCodexInstall) || bundleCodexInstall.scheduleBundleCodexInstall;
|
|
158
|
+
const report = (error) => {
|
|
159
|
+
if (_deps && _deps.errorSink && typeof _deps.errorSink.report === 'function') {
|
|
160
|
+
_deps.errorSink.report('agent-bundle-codex-install', error);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
schedule(_dataDir(), { hubPeerId, report });
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* WP-3/D-8 T1: tell the server the hub pointer just moved, so it can reconcile bundle drift for the
|
|
168
|
+
* sessions that are still running an older tree. Void by construction — the reconciler is
|
|
169
|
+
* single-flight and reports its own faults through `errorSink`; a manifest PUT never waits on it.
|
|
170
|
+
*/
|
|
171
|
+
function _onBundlePointerMoved(hubPeerId) {
|
|
172
|
+
if (_deps && typeof _deps.onBundlePointerMoved === 'function') _deps.onBundlePointerMoved(hubPeerId);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The bounded raw-body reader. api-prompts.js:684 has the same shape for image uploads but does not
|
|
177
|
+
* export it, and its 499/abort bookkeeping is image-specific; this is the ~20-line subset the blob
|
|
178
|
+
* PUT needs (accumulate to a hard cap, reject with the tagged 413 the readers share).
|
|
179
|
+
*/
|
|
180
|
+
function _readRawBody(req, limit) {
|
|
181
|
+
return new Promise((resolve, reject) => {
|
|
182
|
+
const chunks = [];
|
|
183
|
+
let size = 0;
|
|
184
|
+
let settled = false;
|
|
185
|
+
const fail = (error) => {
|
|
186
|
+
if (settled) return;
|
|
187
|
+
settled = true;
|
|
188
|
+
reject(error);
|
|
189
|
+
};
|
|
190
|
+
req.on('data', (chunk) => {
|
|
191
|
+
if (settled) return;
|
|
192
|
+
size += chunk.length;
|
|
193
|
+
if (size > limit) {
|
|
194
|
+
const error = new Error('Body too large');
|
|
195
|
+
error.code = 'PAYLOAD_TOO_LARGE';
|
|
196
|
+
error.error = 'payload_too_large';
|
|
197
|
+
error.status = 413;
|
|
198
|
+
fail(error);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
chunks.push(chunk);
|
|
202
|
+
});
|
|
203
|
+
req.on('end', () => {
|
|
204
|
+
if (settled) return;
|
|
205
|
+
settled = true;
|
|
206
|
+
resolve(Buffer.concat(chunks));
|
|
207
|
+
});
|
|
208
|
+
req.on('aborted', () => {
|
|
209
|
+
const error = new Error('Client aborted agent-bundle upload');
|
|
210
|
+
error.code = 'CTM_UPLOAD_ABORTED';
|
|
211
|
+
error.status = 499;
|
|
212
|
+
fail(error);
|
|
213
|
+
});
|
|
214
|
+
req.on('error', fail);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Every route's failure path: store codes → their status, an over-cap body → 413, else 500. */
|
|
219
|
+
function _fail(res, error, route) {
|
|
220
|
+
if (error instanceof AgentBundleError) {
|
|
221
|
+
const status = STATUS_BY_CODE[error.code] || 500;
|
|
222
|
+
const payload = { code: error.code };
|
|
223
|
+
if (Array.isArray(error.missing)) payload.missing = error.missing;
|
|
224
|
+
return _send(res, status, payload);
|
|
225
|
+
}
|
|
226
|
+
// WP-8: the skill transfer's own typed failure already carries its wire status (404/409/413/422/502),
|
|
227
|
+
// so the box's answer and the hub's relay of it are the same shape for the same fault.
|
|
228
|
+
if (error instanceof skillTransfer.AgentSkillError) {
|
|
229
|
+
const payload = { code: error.code };
|
|
230
|
+
if (error.detail) payload.detail = error.detail;
|
|
231
|
+
return _send(res, error.status, payload);
|
|
232
|
+
}
|
|
233
|
+
const code = String((error && error.code) || '');
|
|
234
|
+
const message = String((error && error.message) || '');
|
|
235
|
+
if (code === 'PAYLOAD_TOO_LARGE' || message === 'request_body_too_large') return _payloadTooLarge(res);
|
|
236
|
+
if (code === 'CTM_UPLOAD_ABORTED') return _send(res, 499, { error: 'client_aborted' });
|
|
237
|
+
if (_deps && _deps.errorSink && typeof _deps.errorSink.report === 'function') {
|
|
238
|
+
// REPORT: an unexpected failure is a real fault, not a client mistake — surface it before the 500.
|
|
239
|
+
_deps.errorSink.report(`agent-bundles:${route}`, error);
|
|
240
|
+
}
|
|
241
|
+
return _send(res, 500, { error: 'internal_error' });
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function handleMissing(req, res) {
|
|
245
|
+
const peerInstanceId = _peerInstanceId(req);
|
|
246
|
+
if (!peerInstanceId) return _send(res, 403, { code: 'agent_bundle_peer_unidentified' });
|
|
247
|
+
try {
|
|
248
|
+
const body = await _deps.readJsonRequestBody(req, { limitBytes: MAX_MISSING_BODY_BYTES });
|
|
249
|
+
const blobs = body && !Array.isArray(body) ? body.blobs : null;
|
|
250
|
+
if (!Array.isArray(blobs)) return _send(res, 400, { error: 'invalid_body' });
|
|
251
|
+
if (blobs.length > MAX_ENTRIES) return _payloadTooLarge(res);
|
|
252
|
+
return _send(res, 200, { missing: await store.missingBlobs(_dataDir(), blobs) });
|
|
253
|
+
} catch (error) {
|
|
254
|
+
return _fail(res, error, ROUTE_MISSING);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function handleBlobPut(req, res, url, params) {
|
|
259
|
+
const peerInstanceId = _peerInstanceId(req);
|
|
260
|
+
if (!peerInstanceId) return _send(res, 403, { code: 'agent_bundle_peer_unidentified' });
|
|
261
|
+
try {
|
|
262
|
+
const hex = String((params && params['1']) || '');
|
|
263
|
+
const body = await _readRawBody(req, MAX_BLOB_BYTES);
|
|
264
|
+
const result = await store.putBlob(_dataDir(), hex, body);
|
|
265
|
+
return _send(res, result.stored ? 201 : 200, { stored: result.stored });
|
|
266
|
+
} catch (error) {
|
|
267
|
+
return _fail(res, error, ROUTE_BLOB);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* WP-4/§6.2/D-6 (docs/remote-skills-hub-peer-identity-2026-10-26.html rev 2): the BATCH blob leg —
|
|
273
|
+
* many blobs in one request, so a first push costs a constant number of requests instead of one per
|
|
274
|
+
* blob (the box's `peer` rate bucket is per-hub and shared with the live relay; §1.3). Body:
|
|
275
|
+
* `{ blobs: [{ hash: <64 hex>, data: <base64> }] }`, at most MAX_BATCH_BODY_BYTES of JSON and
|
|
276
|
+
* MAX_ENTRIES items. Items go through the SAME `store.putBlob` (re-hash + atomic write) IN ORDER, so
|
|
277
|
+
* a bad hash is the store's own 422 and an already-present blob is left untouched. Content-addressed,
|
|
278
|
+
* so a stop mid-list is exactly a partial per-blob run. Same identity rule as the per-blob PUT: the
|
|
279
|
+
* peer refusal comes first, before the body is read.
|
|
280
|
+
*/
|
|
281
|
+
async function handleBlobBatch(req, res) {
|
|
282
|
+
const peerInstanceId = _peerInstanceId(req);
|
|
283
|
+
if (!peerInstanceId) return _send(res, 403, { code: 'agent_bundle_peer_unidentified' });
|
|
284
|
+
try {
|
|
285
|
+
const body = await _deps.readJsonRequestBody(req, { limitBytes: MAX_BATCH_BODY_BYTES });
|
|
286
|
+
const blobs = body && !Array.isArray(body) ? body.blobs : null;
|
|
287
|
+
if (!Array.isArray(blobs)) return _send(res, 400, { error: 'invalid_body' });
|
|
288
|
+
if (blobs.length > MAX_ENTRIES) return _payloadTooLarge(res);
|
|
289
|
+
let stored = 0;
|
|
290
|
+
let present = 0;
|
|
291
|
+
for (const item of blobs) {
|
|
292
|
+
const hash = item && typeof item.hash === 'string' ? item.hash : '';
|
|
293
|
+
const data = item && typeof item.data === 'string' ? item.data : null;
|
|
294
|
+
if (data === null) return _send(res, 400, { error: 'invalid_body' });
|
|
295
|
+
// A bad hash (or bytes that do not match it) is the store's own code; it stops the batch, and
|
|
296
|
+
// every item already written stays — content-addressed, so that is a partial per-blob run.
|
|
297
|
+
const result = await store.putBlob(_dataDir(), hash, Buffer.from(data, 'base64'));
|
|
298
|
+
if (result.stored) stored += 1; else present += 1;
|
|
299
|
+
}
|
|
300
|
+
return _send(res, 200, { stored, present });
|
|
301
|
+
} catch (error) {
|
|
302
|
+
return _fail(res, error, ROUTE_BLOB_BATCH);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function handleManifestPut(req, res, url, params) {
|
|
307
|
+
const peerInstanceId = _peerInstanceId(req);
|
|
308
|
+
if (!peerInstanceId) return _send(res, 403, { code: 'agent_bundle_peer_unidentified' });
|
|
309
|
+
try {
|
|
310
|
+
const treeHash = String((params && params['1']) || '');
|
|
311
|
+
const manifest = await _deps.readJsonRequestBody(req, { limitBytes: MAX_MANIFEST_BODY_BYTES });
|
|
312
|
+
const result = await store.putManifest(_dataDir(), treeHash, manifest, { peerInstanceId });
|
|
313
|
+
_send(res, result.materialized ? 201 : 200, {
|
|
314
|
+
treeHash: result.treeHash,
|
|
315
|
+
materialized: result.materialized,
|
|
316
|
+
});
|
|
317
|
+
// WP-6/D-14: on EVERY successful PUT — the hub pointer moves on a re-PUT too, and re-applying an
|
|
318
|
+
// unchanged tree is a cheap `noop` sweep. The install resolves the tree through the hub pointer
|
|
319
|
+
// this PUT just wrote, and the GC keeps a hub-pointer tree (gc.js's `_referencedTrees` reads
|
|
320
|
+
// `hubs/*.json`), so scheduling the sweep for a re-PUT cannot delete what the install needs.
|
|
321
|
+
void _scheduleCodexInstall(peerInstanceId);
|
|
322
|
+
// WP-3/D-8 T1: on EVERY 2xx PUT — the pointer moves on a re-PUT too, so a live session on the
|
|
323
|
+
// old tree is drifted either way. Distinct from the GC below, which only a NEW tree can feed.
|
|
324
|
+
void _onBundlePointerMoved(peerInstanceId);
|
|
325
|
+
// Only a NEW tree can have added blobs/trees worth sweeping: a re-PUT changed nothing on disk.
|
|
326
|
+
if (result.materialized) void _scheduleGc();
|
|
327
|
+
return undefined;
|
|
328
|
+
} catch (error) {
|
|
329
|
+
return _fail(res, error, ROUTE_MANIFEST);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* WP-7/§6.2/AC-7a: `GET /api/agent-skills/inventory` — the box's own skill truth for the CALLING hub.
|
|
335
|
+
*
|
|
336
|
+
* Read-only and side-effect free: it enumerates the box user's skill roots and hashes them, and it
|
|
337
|
+
* never writes anything (D-1: the bundle only ever lives under the box's data dir). The peer check
|
|
338
|
+
* comes first, exactly like the three ingress routes, so an unidentified caller cannot even learn the
|
|
339
|
+
* shape of this machine's skill roots.
|
|
340
|
+
*
|
|
341
|
+
* The `base` in the answer is the tree THIS hub last handed the box (`store.currentHubTree` keyed by
|
|
342
|
+
* the AUTHENTICATED peer id), which is what makes classification survive a hub restart — the hub's
|
|
343
|
+
* own push bookkeeping is in-process only.
|
|
344
|
+
*/
|
|
345
|
+
async function handleSkillsInventory(req, res) {
|
|
346
|
+
const peerInstanceId = _peerInstanceId(req);
|
|
347
|
+
if (!peerInstanceId) return _send(res, 403, { code: 'agent_bundle_peer_unidentified' });
|
|
348
|
+
try {
|
|
349
|
+
const inventory = await bundleInventory.readBoxInventory({ dataDir: _dataDir(), peerInstanceId });
|
|
350
|
+
return _send(res, 200, inventory);
|
|
351
|
+
} catch (error) {
|
|
352
|
+
return _fail(res, error, ROUTE_INVENTORY);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* WP-8/§6.2: `GET /api/agent-skills/export?agent=claude|codex&name=<name>` — the bytes of ONE box item.
|
|
358
|
+
*
|
|
359
|
+
* A `ctm-managed` copy (the managed-skill installer's own `delegate`) is a 404, never a 409: this
|
|
360
|
+
* release's copy is simply not an item that can be proposed for import, and "not found" keeps the hub
|
|
361
|
+
* from ever rendering it as one. The ref is validated first (a name that could escape a root is a 422
|
|
362
|
+
* before any path is built), then the item is located exactly as the WP-7 inventory locates it.
|
|
363
|
+
*/
|
|
364
|
+
async function handleSkillExport(req, res, url) {
|
|
365
|
+
const peerInstanceId = _peerInstanceId(req);
|
|
366
|
+
if (!peerInstanceId) return _send(res, 403, { code: 'agent_bundle_peer_unidentified' });
|
|
367
|
+
try {
|
|
368
|
+
const agent = url && url.searchParams ? url.searchParams.get('agent') : '';
|
|
369
|
+
const name = url && url.searchParams ? url.searchParams.get('name') : '';
|
|
370
|
+
const exported = await skillTransfer.exportBoxSkill({ agent, name, homeDir: _homeDir(), env: process.env });
|
|
371
|
+
return _send(res, 200, exported);
|
|
372
|
+
} catch (error) {
|
|
373
|
+
return _fail(res, error, ROUTE_SKILL_EXPORT);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* WP-8/§6.5: `POST /api/agent-skills/retire { agent, name, expectHash }` — D-13's "moves, never
|
|
379
|
+
* deletes". The box re-hashes the copy NOW and refuses one that changed since review, then MOVES it
|
|
380
|
+
* under `<data dir>/agent-bundles/retired/`. Both failures are the human's to see, so both carry a code.
|
|
381
|
+
*/
|
|
382
|
+
async function handleSkillRetire(req, res) {
|
|
383
|
+
const peerInstanceId = _peerInstanceId(req);
|
|
384
|
+
if (!peerInstanceId) return _send(res, 403, { code: 'agent_bundle_peer_unidentified' });
|
|
385
|
+
try {
|
|
386
|
+
const body = await _deps.readJsonRequestBody(req, { limitBytes: MAX_MISSING_BODY_BYTES });
|
|
387
|
+
const ref = body && !Array.isArray(body) ? body : {};
|
|
388
|
+
const result = await skillTransfer.retireBoxSkill({
|
|
389
|
+
agent: ref.agent,
|
|
390
|
+
name: ref.name,
|
|
391
|
+
expectHash: ref.expectHash,
|
|
392
|
+
dataDir: _dataDir(),
|
|
393
|
+
homeDir: _homeDir(),
|
|
394
|
+
env: process.env,
|
|
395
|
+
});
|
|
396
|
+
return _send(res, 200, result);
|
|
397
|
+
} catch (error) {
|
|
398
|
+
return _fail(res, error, ROUTE_SKILL_RETIRE);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function register() {
|
|
403
|
+
registerApiRoutes('agent-bundles', [
|
|
404
|
+
{ method: 'POST', path: '/api/agent-bundles/missing', handler: handleMissing },
|
|
405
|
+
// WP-4/§6.2/D-6: the batch leg, so a first push is a constant number of requests. Same auth row
|
|
406
|
+
// tuple as the per-blob PUT (lib/auth-rules.js), same peer-identity refusal first.
|
|
407
|
+
{ method: 'POST', path: '/api/agent-bundles/blobs', handler: handleBlobBatch },
|
|
408
|
+
// The regex is byte-identical to lib/auth-rules.js's blob row on purpose: routing and auth must
|
|
409
|
+
// agree on what "this route" is, or a malformed hash would be answered by the auth wall with a
|
|
410
|
+
// different code than the handler's own 422.
|
|
411
|
+
{ method: 'PUT', regex: /^\/api\/agent-bundles\/blobs\/([0-9a-f]{64})$/, handler: handleBlobPut },
|
|
412
|
+
// `[^/]+` (not a hash pattern): the tree hash is URL-encoded in the URL, and store.js decodes
|
|
413
|
+
// and validates it into the one canonical `sha256:<hex>` form — a bad one is its 422, not a 404.
|
|
414
|
+
{ method: 'PUT', regex: /^\/api\/agent-bundles\/manifests\/([^/]+)$/, handler: handleManifestPut },
|
|
415
|
+
// WP-7/§6.2: the return path. Same identity rule as the three above (an unauthenticated caller is
|
|
416
|
+
// 403 agent_bundle_peer_unidentified), same group; the base it reports is the caller's own.
|
|
417
|
+
{ method: 'GET', path: '/api/agent-skills/inventory', handler: handleSkillsInventory },
|
|
418
|
+
// WP-8/§6.2: the one-skill exchange. `export` is a GET with query ref fields (a name is not
|
|
419
|
+
// path-safe in general); `retire` is a POST whose only body field that matters is the reviewed
|
|
420
|
+
// hash — the box identity is the authenticated peer, never a field, exactly as above.
|
|
421
|
+
{ method: 'GET', path: '/api/agent-skills/export', handler: handleSkillExport },
|
|
422
|
+
{ method: 'POST', path: '/api/agent-skills/retire', handler: handleSkillRetire },
|
|
423
|
+
]);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
module.exports = {
|
|
427
|
+
configure,
|
|
428
|
+
register,
|
|
429
|
+
handleMissing,
|
|
430
|
+
handleBlobBatch,
|
|
431
|
+
handleBlobPut,
|
|
432
|
+
handleManifestPut,
|
|
433
|
+
handleSkillsInventory,
|
|
434
|
+
handleSkillExport,
|
|
435
|
+
handleSkillRetire,
|
|
436
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Backup retention setting (design: docs/backup-retention-days-setting-2026-10-31.html §6/WP-1).
|
|
4
|
+
// GET /api/storage/backup-retention -> the effective value, where it came from, and the
|
|
5
|
+
// backup names the next prune will remove.
|
|
6
|
+
// PUT /api/storage/backup-retention -> validate + persist `backup_retention_days`.
|
|
7
|
+
//
|
|
8
|
+
// Neither handler deletes anything: pruning still runs only at the end of a successful backup
|
|
9
|
+
// (db.js cleanOldBackups). A PUT just changes the number the next prune reads.
|
|
10
|
+
|
|
11
|
+
const { registerApiRoutes } = require('../api-router');
|
|
12
|
+
const backupRetention = require('../../../shared/backup-retention');
|
|
13
|
+
|
|
14
|
+
let _deps = null;
|
|
15
|
+
|
|
16
|
+
function configure(deps = {}) {
|
|
17
|
+
_deps = deps;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function _statusPayload() {
|
|
21
|
+
const db = _deps.db;
|
|
22
|
+
const retentionDays = db.getBackupRetentionDays();
|
|
23
|
+
let source = 'default';
|
|
24
|
+
try {
|
|
25
|
+
// Production main thread: the published control snapshot (direct sync DB reads are refused
|
|
26
|
+
// in the request zone). Tests and worker-local callers fall back to db.getSetting.
|
|
27
|
+
const read = typeof _deps.getSetting === 'function' ? _deps.getSetting : db.getSetting.bind(db);
|
|
28
|
+
const stored = await read(backupRetention.BACKUP_RETENTION_SETTING_KEY, null);
|
|
29
|
+
if (stored !== null && stored !== undefined) source = 'settings';
|
|
30
|
+
} catch (e) {
|
|
31
|
+
// A read failure must never break the page: report and answer with the default source.
|
|
32
|
+
console.error('[api] Backup retention setting read failed:', e.message);
|
|
33
|
+
source = 'default';
|
|
34
|
+
}
|
|
35
|
+
const expiring = backupRetention
|
|
36
|
+
.selectExpiredBackups(await db.listBackupsAsync(), retentionDays)
|
|
37
|
+
.map((b) => b.name);
|
|
38
|
+
return {
|
|
39
|
+
retention_days: retentionDays,
|
|
40
|
+
default_retention_days: backupRetention.DEFAULT_BACKUP_RETENTION_DAYS,
|
|
41
|
+
min: backupRetention.MIN_BACKUP_RETENTION_DAYS,
|
|
42
|
+
max: backupRetention.MAX_BACKUP_RETENTION_DAYS,
|
|
43
|
+
source,
|
|
44
|
+
expiring,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function handleGet(req, res) {
|
|
49
|
+
void req;
|
|
50
|
+
_deps.sendJsonResponse(res, 200, await _statusPayload());
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function handlePut(req, res) {
|
|
54
|
+
let body;
|
|
55
|
+
try {
|
|
56
|
+
body = await _deps.readBody(req);
|
|
57
|
+
} catch {
|
|
58
|
+
// Malformed JSON, empty body, or an over-limit body: nothing is written.
|
|
59
|
+
_deps.sendJsonResponse(res, 400, { error: 'invalid_body' });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const parsed = backupRetention.parseRetentionDaysInput(body && body.retention_days);
|
|
63
|
+
if (!parsed.ok) {
|
|
64
|
+
_deps.sendJsonResponse(res, 400, {
|
|
65
|
+
error: parsed.error,
|
|
66
|
+
min: parsed.min,
|
|
67
|
+
max: parsed.max,
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
_deps.db.setBackupRetentionDays(parsed.days);
|
|
72
|
+
await _deps.db.setSetting(backupRetention.BACKUP_RETENTION_SETTING_KEY, parsed.days);
|
|
73
|
+
_deps.sendJsonResponse(res, 200, await _statusPayload());
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function register() {
|
|
77
|
+
registerApiRoutes('backup-retention', [
|
|
78
|
+
{ method: 'GET', path: '/api/storage/backup-retention', handler: handleGet },
|
|
79
|
+
{ method: 'PUT', path: '/api/storage/backup-retention', handler: handlePut },
|
|
80
|
+
]);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = { configure, handleGet, handlePut, register };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-5 (docs/shared-memory-every-machine-2026-10-31.html §5 D-3, D-12, §6.8): the CTM-served
|
|
4
|
+
// conversation-history feed. Unlike the brain/v2 family (lib/api/memory-sync-routes.js), these
|
|
5
|
+
// three routes are NOT proxies: transcription history lives in CTM's own task-manager.db, so the
|
|
6
|
+
// hub speaks to a member's CTM directly and no Wall-E hop is involved.
|
|
7
|
+
//
|
|
8
|
+
// GET /api/sync/conversation/v2/vector -> { ok, instanceId, vector, seedDone }
|
|
9
|
+
// POST /api/sync/conversation/v2/changes { since, limit } -> { ok, entries, more, seedDone }
|
|
10
|
+
// POST /api/sync/conversation/v2/apply { sourceInstanceId, entries } -> { ok, applied, skipped, vector }
|
|
11
|
+
//
|
|
12
|
+
// Auth is NOT evaluated here — lib/auth-rules.js carries the three rows (scope `sync`, no step-up,
|
|
13
|
+
// NOT peerControl: the pool credential the hub already dials `/api/sync/brain/v2/*` with authorizes
|
|
14
|
+
// them), and a route with no rule is 403 auth_rule_missing by design, so the rule rows and these
|
|
15
|
+
// registrations land together. A body over the 8 MiB cap is 413 before any DB work.
|
|
16
|
+
//
|
|
17
|
+
// The clock seed (D-3 / §6.8) is driven from here rather than at boot: each `/vector` and
|
|
18
|
+
// `/changes` call advances THIS node's seed by one bounded page, so a member with pre-existing
|
|
19
|
+
// history finishes seeding over the first few hub ticks and boot stays free of the work.
|
|
20
|
+
//
|
|
21
|
+
// WP-3 (docs/join-sync-followups-2-2026-11-01.html §5 R-10/R-11, §8 WP-3): that seed page, the
|
|
22
|
+
// store DDL and every read/write below run in the DB-OWNER WORKER, never on this thread. Each
|
|
23
|
+
// handler awaits one closed owner action through the injected `runMutation`
|
|
24
|
+
// (`runMutation -> server.js _runCtmDbWorkerOp('runDbMutation', …) -> db.js
|
|
25
|
+
// DB_OWNER_MUTATION_ACTIONS -> lib/conversation-sync.js owner*`). Doing this work here is what
|
|
26
|
+
// made every member's /vector answer 500: the main-thread request-zone guard refuses a main-DB
|
|
27
|
+
// op inside a request (lib/main-db-census.js, CTM_MAIN_DB_REQUEST_FORBIDDEN). The three reply
|
|
28
|
+
// shapes are unchanged (R-11) — only where the work runs changed.
|
|
29
|
+
|
|
30
|
+
const { registerApiRoutes } = require('../api-router');
|
|
31
|
+
|
|
32
|
+
const MAX_BODY_BYTES = 8 * 1024 * 1024;
|
|
33
|
+
const BODY_TOO_LARGE = 'request_body_too_large';
|
|
34
|
+
const OWNER_UNAVAILABLE = 'conversation_sync_owner_unavailable';
|
|
35
|
+
|
|
36
|
+
let _deps = null;
|
|
37
|
+
|
|
38
|
+
function configure({ dbModule, readJsonRequestBody, sendJsonResponse, seedPageLimit, runMutation } = {}) {
|
|
39
|
+
_deps = { dbModule, readJsonRequestBody, sendJsonResponse, seedPageLimit, runMutation };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// The one seam to the DB-owner worker. Unconfigured (or configured with a non-function) means the
|
|
43
|
+
// feed cannot run at all, so every route answers 503 rather than doing the work in-zone.
|
|
44
|
+
function _ownerRun() {
|
|
45
|
+
const run = _deps && _deps.runMutation;
|
|
46
|
+
return typeof run === 'function' ? run : null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function _send(res, status, payload) {
|
|
50
|
+
if (_deps && typeof _deps.sendJsonResponse === 'function') return _deps.sendJsonResponse(res, status, payload || {});
|
|
51
|
+
const body = JSON.stringify(payload || {});
|
|
52
|
+
res.writeHead(status, { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) });
|
|
53
|
+
res.end(body);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function _readBody(req) {
|
|
57
|
+
const reader = _deps && _deps.readJsonRequestBody;
|
|
58
|
+
if (typeof reader !== 'function') return { ok: true, body: undefined };
|
|
59
|
+
try {
|
|
60
|
+
return { ok: true, body: await reader(req, { limitBytes: MAX_BODY_BYTES }) };
|
|
61
|
+
} catch (err) {
|
|
62
|
+
return { ok: false, tooLarge: !!(err && err.message === BODY_TOO_LARGE) };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function _handleVector(req, res) {
|
|
67
|
+
const run = _ownerRun();
|
|
68
|
+
if (!run) return _send(res, 503, { ok: false, error: OWNER_UNAVAILABLE });
|
|
69
|
+
try {
|
|
70
|
+
const vector = await run('conversationSyncVector', [{ seedPageLimit: _deps.seedPageLimit }]);
|
|
71
|
+
return _send(res, 200, { ok: true, ...vector });
|
|
72
|
+
} catch (err) {
|
|
73
|
+
return _send(res, 500, { ok: false, error: 'conversation_vector_failed', detail: String(err?.message || err).slice(0, 200) });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function _handleChanges(req, res) {
|
|
78
|
+
const run = _ownerRun();
|
|
79
|
+
if (!run) return _send(res, 503, { ok: false, error: OWNER_UNAVAILABLE });
|
|
80
|
+
const read = await _readBody(req);
|
|
81
|
+
if (!read.ok) return _send(res, 413, { ok: false, error: 'payload_too_large' });
|
|
82
|
+
const body = (read.body && typeof read.body === 'object') ? read.body : {};
|
|
83
|
+
try {
|
|
84
|
+
const page = await run('conversationSyncChanges', [{
|
|
85
|
+
since: body.since,
|
|
86
|
+
limit: body.limit,
|
|
87
|
+
seedPageLimit: _deps.seedPageLimit,
|
|
88
|
+
}]);
|
|
89
|
+
return _send(res, 200, { ok: true, entries: page.entries, more: page.more, seedDone: page.seedDone });
|
|
90
|
+
} catch (err) {
|
|
91
|
+
return _send(res, 500, { ok: false, error: 'conversation_changes_failed', detail: String(err?.message || err).slice(0, 200) });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function _handleApply(req, res) {
|
|
96
|
+
const run = _ownerRun();
|
|
97
|
+
if (!run) return _send(res, 503, { ok: false, error: OWNER_UNAVAILABLE });
|
|
98
|
+
const read = await _readBody(req);
|
|
99
|
+
if (!read.ok) return _send(res, 413, { ok: false, error: 'payload_too_large' });
|
|
100
|
+
const body = (read.body && typeof read.body === 'object') ? read.body : {};
|
|
101
|
+
const sourceInstanceId = String(body.sourceInstanceId || '').trim();
|
|
102
|
+
if (!sourceInstanceId) return _send(res, 400, { ok: false, error: 'source_instance_required' });
|
|
103
|
+
try {
|
|
104
|
+
const result = await run('conversationSyncApply', [{
|
|
105
|
+
sourceInstanceId,
|
|
106
|
+
entries: Array.isArray(body.entries) ? body.entries : [],
|
|
107
|
+
}]);
|
|
108
|
+
return _send(res, 200, { ok: true, applied: result.applied, skipped: result.skipped, vector: result.vector });
|
|
109
|
+
} catch (err) {
|
|
110
|
+
const code = err && err.code;
|
|
111
|
+
if (code === 'CONVERSATION_SYNC_ENTRIES_TOO_LARGE') return _send(res, 413, { ok: false, error: 'too_many_entries' });
|
|
112
|
+
return _send(res, 500, { ok: false, error: 'conversation_apply_failed', detail: String(err?.message || err).slice(0, 200) });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function register() {
|
|
117
|
+
registerApiRoutes('conversation-sync', [
|
|
118
|
+
{ method: 'GET', path: '/api/sync/conversation/v2/vector', handler: (req, res, url) => _handleVector(req, res, url) },
|
|
119
|
+
{ method: 'POST', path: '/api/sync/conversation/v2/changes', handler: (req, res, url) => _handleChanges(req, res, url) },
|
|
120
|
+
{ method: 'POST', path: '/api/sync/conversation/v2/apply', handler: (req, res, url) => _handleApply(req, res, url) },
|
|
121
|
+
]);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
module.exports = { configure, register };
|