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
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
// D-2 (docs/lead-model-id-rule-at-tilde-prefix-2026-10-28.html): a routed wire model id obeys the ONE
|
|
4
|
+
// model-id rule; a stricter copy here silently dropped the recipe of a `~vendor/x-latest` lead.
|
|
5
|
+
const { isValidOverrideModel } = require('./model-id-rule');
|
|
3
6
|
const RECIPE_ENV_KEY = 'CTM_MODEL_EXECUTION_RECIPE';
|
|
4
7
|
const MAX_RECIPE_BYTES = 4096;
|
|
5
8
|
const RECIPE_KEYS = Object.freeze([
|
|
@@ -12,7 +15,6 @@ const RECIPE_KEYS = Object.freeze([
|
|
|
12
15
|
const OPTIONAL_RECIPE_KEYS = Object.freeze(['sourceEnvironmentReference', 'nativeConfigReceipt']);
|
|
13
16
|
const SOURCE_ENVIRONMENT_REFERENCES = new Set(['captured-empty', 'private-owner-required']);
|
|
14
17
|
const ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
15
|
-
const CANONICAL_MODEL_ID_PATTERN = /^[A-Za-z0-9@][A-Za-z0-9@._:/+-]{0,255}$/;
|
|
16
18
|
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
17
19
|
const SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
18
20
|
|
|
@@ -28,10 +30,6 @@ function boundedId(value) {
|
|
|
28
30
|
return typeof value === 'string' && ID_PATTERN.test(value);
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
function boundedCanonicalModelId(value) {
|
|
32
|
-
return typeof value === 'string' && CANONICAL_MODEL_ID_PATTERN.test(value);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
33
|
function validThreadId(value) {
|
|
36
34
|
return value === null || (typeof value === 'string' && Buffer.byteLength(value, 'utf8') <= 256
|
|
37
35
|
&& (UUID_PATTERN.test(value) || /^[A-Za-z0-9][A-Za-z0-9._:/+-]{0,255}$/.test(value)));
|
|
@@ -57,7 +55,7 @@ function sanitizedRecipe(value, minimumGeneration = 1) {
|
|
|
57
55
|
const native = value.routeId === null;
|
|
58
56
|
if (native) {
|
|
59
57
|
if (value.wireModelId !== null || value.profileFingerprint !== null || value.factsRevision !== null) return undefined;
|
|
60
|
-
} else if (!boundedId(value.routeId) || !
|
|
58
|
+
} else if (!boundedId(value.routeId) || !isValidOverrideModel(value.wireModelId)
|
|
61
59
|
|| typeof value.profileFingerprint !== 'string' || !SHA256_PATTERN.test(value.profileFingerprint)
|
|
62
60
|
|| typeof value.factsRevision !== 'string' || !SHA256_PATTERN.test(value.factsRevision)) return undefined;
|
|
63
61
|
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { modelProcessGenerationMetadata, modelStorageRootsMetadata } = require('./host-handoff');
|
|
3
|
+
const { modelProcessGenerationMetadata, modelStorageRootsMetadata, launchEnvironmentMetadata } = require('./host-handoff');
|
|
4
4
|
const { modelExecutionRecipeMetadata } = require('./model-execution-recipe-metadata');
|
|
5
5
|
|
|
6
|
-
function applyModelHostReceipt(target, { sessionId, modelProcessGeneration, modelStorageRoots, modelExecutionRecipe } = {}) {
|
|
6
|
+
function applyModelHostReceipt(target, { sessionId, modelProcessGeneration, modelStorageRoots, modelExecutionRecipe, launchEnvironment } = {}) {
|
|
7
7
|
if (!target || typeof target !== 'object') throw new TypeError('model host receipt target is required');
|
|
8
8
|
delete target.modelExecutionRecipe;
|
|
9
9
|
delete target._modelExecutionRecipe;
|
|
10
|
+
// WP-1 (S-12): a receipt without a valid record clears any stale one -- a host reply is authoritative.
|
|
11
|
+
delete target.launchEnvironment;
|
|
10
12
|
Object.assign(target,
|
|
11
13
|
modelProcessGenerationMetadata(modelProcessGeneration),
|
|
12
14
|
modelStorageRootsMetadata(modelStorageRoots),
|
|
13
|
-
modelExecutionRecipeMetadata(modelExecutionRecipe, { sessionId, processGeneration: modelProcessGeneration })
|
|
15
|
+
modelExecutionRecipeMetadata(modelExecutionRecipe, { sessionId, processGeneration: modelProcessGeneration }),
|
|
16
|
+
launchEnvironmentMetadata(launchEnvironment));
|
|
14
17
|
return target;
|
|
15
18
|
}
|
|
16
19
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The ONE model-id rule (docs/lead-model-id-rule-at-tilde-prefix-2026-10-28.html D-1/D-2). A model id
|
|
4
|
+
// rides argv as the operand of --model / -m, so the only argv hazard is a value the CLI would parse as
|
|
5
|
+
// an option: a leading '-' (CWE-88). The first character is a letter, a digit, '@' (Portkey model
|
|
6
|
+
// catalog: @slug/model) or '~' (OpenRouter latest alias: ~vendor/x-latest), never '-'. The agent is
|
|
7
|
+
// spawned with an argv array and no shell, so '~' is never tilde-expanded.
|
|
8
|
+
//
|
|
9
|
+
// A leaf module that requires nothing: the session host validates recipes with it, and its closure is
|
|
10
|
+
// bounded (tests-integration/host-socket-protocol.test.js), so it must not drag in launch-model-override's
|
|
11
|
+
// Claude argv helpers.
|
|
12
|
+
const MODEL_ID_RE = /^[A-Za-z0-9@~][A-Za-z0-9._:/@+~\-[\]]*$/;
|
|
13
|
+
const MAX_MODEL_ID_LENGTH = 200;
|
|
14
|
+
|
|
15
|
+
function isValidOverrideModel(model) {
|
|
16
|
+
return typeof model === 'string' && model.length > 0 && model.length <= MAX_MODEL_ID_LENGTH && MODEL_ID_RE.test(model);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = { isValidOverrideModel };
|
|
@@ -96,6 +96,10 @@ function anthropicErrorType(kind) {
|
|
|
96
96
|
// must not collapse into api_error -- a permission denial is not an upstream fault.
|
|
97
97
|
if (kind === 'model_not_invocable') return 'invalid_request_error';
|
|
98
98
|
if (kind === 'model_access_denied') return 'permission_error';
|
|
99
|
+
// WP-1b (§12a rule 2): `model_not_found` split out of `invalid_prompt`, and the HTTP status is
|
|
100
|
+
// still the upstream one (404), so the client-visible envelope must be the one `invalid_prompt`
|
|
101
|
+
// produced for that status -- type `invalid_request_error`, status 404.
|
|
102
|
+
if (kind === 'model_not_found') return 'invalid_request_error';
|
|
99
103
|
return 'api_error';
|
|
100
104
|
}
|
|
101
105
|
|
|
@@ -4,12 +4,17 @@
|
|
|
4
4
|
// the two protocol-boundary decisions the installed Claude client's server-side
|
|
5
5
|
// auto-mode review needs, kept as one cohesive, side-effect-free leaf.
|
|
6
6
|
//
|
|
7
|
-
// * The client
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
7
|
+
// * The client asks for server-side review with an own top-level `safeguards`
|
|
8
|
+
// field and/or a `dangerous-tool-use-` beta token. A route CTM is translating
|
|
9
|
+
// to a chat wire cannot carry that, so it negotiates honestly (D-2) instead of
|
|
10
|
+
// silently dropping it and letting the request reach an upstream that cannot run
|
|
11
|
+
// the review. The wording of that 400 is MEASURED (Claude Code 2.1.314,
|
|
12
|
+
// docs/auto-mode-native-lead-subagent-latch-2026-10-30.html §1.2): a message
|
|
13
|
+
// NAMING safeguards/dangerous-tool-use makes the client latch server review OFF
|
|
14
|
+
// for its whole SESSION (a subagent refusal poisons the lead process), while the
|
|
15
|
+
// registry's wording (lib/model-proxy/refusal-scope.js) is scoped to that one
|
|
16
|
+
// CONVERSATION. Never name those tokens here -- see refusal-scope.js and the doc.
|
|
17
|
+
// Native (passthrough) routes keep the original request untouched.
|
|
13
18
|
// * A caller-provided `anthropic-beta` and the route's configured `anthropic-beta`
|
|
14
19
|
// must be UNIONED, never replaced (D-5): a configured route could otherwise
|
|
15
20
|
// overwrite the client's dangerous-tool-use / future extension tokens.
|
|
@@ -29,9 +34,15 @@
|
|
|
29
34
|
// is checked BEFORE a chunk is retained. A chunk that does not fit is never held and
|
|
30
35
|
// immediately drops everything already held, so a crossing chunk can never be parsed
|
|
31
36
|
// and the observation stays "unobserved" instead of turning into a false negative.
|
|
37
|
+
//
|
|
38
|
+
// WP-0 (docs/auto-mode-native-lead-subagent-latch-2026-10-30.html §8) adds the REQUEST-side half of the
|
|
39
|
+
// same line: `class=` (what kind of Claude Code request this was, read from the client's own headers, so
|
|
40
|
+
// a lead that keeps sending subagent-class requests is visible) and the three fields later WPs fill
|
|
41
|
+
// (`review=`, `refusal=`, `scope=`). It also names the native-lead lane (`path=native-lead`) and the one
|
|
42
|
+
// predicate deciding whether a request asked for the server-side review at all (`serverReviewAsked`).
|
|
43
|
+
|
|
44
|
+
const { refusalEntry, refusalResponse } = require('./refusal-scope');
|
|
32
45
|
|
|
33
|
-
const SAFEGUARDS_UNSUPPORTED_MESSAGE =
|
|
34
|
-
'safeguards: server-side auto-mode review is unsupported by this translated route; use the client-side classifier.';
|
|
35
46
|
const BETA_HEADER = 'anthropic-beta';
|
|
36
47
|
|
|
37
48
|
function _isPlainObject(value) {
|
|
@@ -54,20 +65,18 @@ function reviewRequested(body) {
|
|
|
54
65
|
/**
|
|
55
66
|
* The honest negotiation for a translated route: `mode` is the transport
|
|
56
67
|
* routes.selectUpstream already selected for this exact request, so only
|
|
57
|
-
* 'chat-translate' (the mode that cannot represent the field) refuses.
|
|
68
|
+
* 'chat-translate' (the mode that cannot represent the field) refuses. The
|
|
69
|
+
* wording is the registry's conversation-scoped refusal (refusal-scope.js),
|
|
70
|
+
* never a literal here -- a 400 that names the review tokens makes the client
|
|
71
|
+
* latch server review off for its whole session (§1.2).
|
|
58
72
|
* @param {unknown} body
|
|
59
73
|
* @param {string|null|undefined} mode
|
|
74
|
+
* @param {unknown} headers
|
|
60
75
|
* @returns {{status:number, body:{type:string, error:{type:string, message:string}}}|null}
|
|
61
76
|
*/
|
|
62
|
-
function unsupportedSafeguards(body, mode) {
|
|
63
|
-
if (mode !== 'chat-translate' || !
|
|
64
|
-
return
|
|
65
|
-
status: 400,
|
|
66
|
-
body: {
|
|
67
|
-
type: 'error',
|
|
68
|
-
error: { type: 'invalid_request_error', message: SAFEGUARDS_UNSUPPORTED_MESSAGE },
|
|
69
|
-
},
|
|
70
|
-
};
|
|
77
|
+
function unsupportedSafeguards(body, mode, headers) {
|
|
78
|
+
if (mode !== 'chat-translate' || !serverReviewAsked(body, headers)) return null;
|
|
79
|
+
return refusalResponse(refusalEntry('claude-code', 'server-review'));
|
|
71
80
|
}
|
|
72
81
|
|
|
73
82
|
// One header map contributes only its `anthropic-beta` values (any spelling),
|
|
@@ -88,6 +97,49 @@ function _betaTokens(source) {
|
|
|
88
97
|
return tokens;
|
|
89
98
|
}
|
|
90
99
|
|
|
100
|
+
// One header value by case-insensitive name, or `undefined`. Node lower-cases incoming header names,
|
|
101
|
+
// but a caller-built map may spell them any way, so the lookup never relies on that.
|
|
102
|
+
function _headerValue(source, name) {
|
|
103
|
+
if (!_isPlainObject(source)) return undefined;
|
|
104
|
+
for (const [key, value] of Object.entries(source)) {
|
|
105
|
+
if (key.toLowerCase() === name) return value;
|
|
106
|
+
}
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The request class the CLIENT stated for itself, read only from the two headers Claude Code sets:
|
|
112
|
+
* `x-claude-code-request-class` (present only when the gateway hint headers are on) and
|
|
113
|
+
* `x-claude-code-agent-id` (always non-empty on a subagent request).
|
|
114
|
+
*
|
|
115
|
+
* Closed by construction: the stated class is returned only when it is EXACTLY a member of
|
|
116
|
+
* REQUEST_CLASSES, an agent-id-bearing request with no valid stated class is 'subagent', and anything
|
|
117
|
+
* else -- an unrecognized class, a whitespace-only agent id, a non-object header map -- is 'unknown'.
|
|
118
|
+
* No caller-supplied string is ever returned or interpolated.
|
|
119
|
+
* @param {unknown} headers
|
|
120
|
+
* @returns {string} one of REQUEST_CLASSES, or 'unknown'
|
|
121
|
+
*/
|
|
122
|
+
function requestClassFromHeaders(headers) {
|
|
123
|
+
const stated = _headerValue(headers, REQUEST_CLASS_HEADER);
|
|
124
|
+
if (_label(stated, REQUEST_CLASSES, null) !== null) return stated;
|
|
125
|
+
const agentId = _headerValue(headers, AGENT_ID_HEADER);
|
|
126
|
+
if (typeof agentId === 'string' && agentId.trim() !== '') return REQUEST_CLASS_SUBAGENT;
|
|
127
|
+
return REVIEW_LABEL_UNKNOWN;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Did this very request ask for the SERVER-SIDE review? Two client-side spellings mean the same thing
|
|
132
|
+
* and both are answered from this one predicate: the own top-level `safeguards` field (`reviewRequested`)
|
|
133
|
+
* and an `anthropic-beta` token in the `dangerous-tool-use-` family (`_betaTokens`). The nested,
|
|
134
|
+
* unrelated uses of the word -- a tool result, a message content block -- are not this feature.
|
|
135
|
+
* @param {unknown} body
|
|
136
|
+
* @param {unknown} headers
|
|
137
|
+
* @returns {boolean}
|
|
138
|
+
*/
|
|
139
|
+
function serverReviewAsked(body, headers) {
|
|
140
|
+
return reviewRequested(body) || _betaTokens(headers).some((token) => token.startsWith(DANGEROUS_TOOL_USE_BETA_PREFIX));
|
|
141
|
+
}
|
|
142
|
+
|
|
91
143
|
/**
|
|
92
144
|
* Union the beta tokens of every source map, in CALL ORDER, then keep any beta token the
|
|
93
145
|
* destination already carries that no source named (appended last, so an unnamed caller's
|
|
@@ -129,10 +181,24 @@ function mergeAnthropicBetas(headers, ...sources) {
|
|
|
129
181
|
// The observation vocabulary is CLOSED. Every value below is normalized through these
|
|
130
182
|
// lists before it can reach the log line, so an unknown/fallback/hostile value degrades to
|
|
131
183
|
// a fixed label instead of being interpolated.
|
|
132
|
-
const REVIEW_PATH_KINDS = ['legacy', 'routed'];
|
|
184
|
+
const REVIEW_PATH_KINDS = ['legacy', 'routed', 'native-lead'];
|
|
133
185
|
const REVIEW_TRANSPORTS = ['native', 'translated'];
|
|
134
186
|
const REVIEW_RESULTS = ['present', 'absent', 'unobserved', 'unsupported'];
|
|
135
187
|
const REVIEW_COMPLETIONS = ['complete', 'incomplete', 'unobserved'];
|
|
188
|
+
// WP-0 (docs/auto-mode-native-lead-subagent-latch-2026-10-30.html §8): the REQUEST-SIDE labels. A
|
|
189
|
+
// request class is what the Claude client itself stated (its own `x-claude-code-request-class`
|
|
190
|
+
// header, sent only when the gateway hint headers are on), never something inferred from a body; an
|
|
191
|
+
// agent-id-bearing request with no stated class is a subagent, and everything else is `unknown`.
|
|
192
|
+
const REQUEST_CLASSES = ['main', 'subagent', 'workflow', 'compaction', 'auxiliary', 'side_reply'];
|
|
193
|
+
const REQUEST_CLASS_SUBAGENT = 'subagent';
|
|
194
|
+
const REQUEST_CLASS_HEADER = 'x-claude-code-request-class';
|
|
195
|
+
const AGENT_ID_HEADER = 'x-claude-code-agent-id';
|
|
196
|
+
// The prefix of the `anthropic-beta` token the client sends when it wants the server-side review on
|
|
197
|
+
// its own request (the same capability the own top-level `safeguards` field asks for).
|
|
198
|
+
const DANGEROUS_TOOL_USE_BETA_PREFIX = 'dangerous-tool-use-';
|
|
199
|
+
const REVIEW_DECISIONS = ['requested', 'not_requested'];
|
|
200
|
+
const REVIEW_REFUSALS = ['conversation', 'none'];
|
|
201
|
+
const REVIEW_SCOPES = ['verified', 'unverified', 'unknown', 'none'];
|
|
136
202
|
const REVIEW_LABEL_UNKNOWN = 'unknown';
|
|
137
203
|
const REVIEW_LABEL_UNOBSERVED = 'unobserved';
|
|
138
204
|
// Same bound as the routed native usage side copy (§6, anthropic-face.js): one
|
|
@@ -165,15 +231,34 @@ function _boundedStatus(status) {
|
|
|
165
231
|
* A result is only ever present/absent together with completion=complete: the two axes are
|
|
166
232
|
* one claim about the observation, never a claim about a tool decision or a billing effect.
|
|
167
233
|
*
|
|
168
|
-
*
|
|
234
|
+
* The four REQUEST-side labels append after `completion=`, always, in this fixed order:
|
|
235
|
+
*
|
|
236
|
+
* class the client's own stated request class (see requestClassFromHeaders); 'unknown' when it
|
|
237
|
+
* stated none, or stated one outside the closed set
|
|
238
|
+
* review 'requested' the request itself asked for the server-side review (own `safeguards`
|
|
239
|
+
* field, or a `dangerous-tool-use-` beta token); 'not_requested' otherwise. The default
|
|
240
|
+
* is 'requested', so a caller that reports a review cycle without saying cannot
|
|
241
|
+
* under-report one.
|
|
242
|
+
* refusal 'conversation' a conversation-scoped refusal was applied; 'none' otherwise
|
|
243
|
+
* scope 'verified' | 'unverified' | 'unknown' | 'none' -- the scope a later WP fills
|
|
244
|
+
*
|
|
245
|
+
* Like every other field these are normalized through closed lists: a caller-supplied string is never
|
|
246
|
+
* echoed, only ever replaced by the documented default.
|
|
247
|
+
*
|
|
248
|
+
* @param {{pathKind?:string,transport?:string,status?:number,results?:string,completion?:string,
|
|
249
|
+
* requestClass?:string,review?:string,refusal?:string,scope?:string}} fields
|
|
169
250
|
* @returns {string} a string built only from the fixed labels above
|
|
170
251
|
*/
|
|
171
|
-
function reviewDiagnostic({ pathKind, transport, status, results, completion } = {}) {
|
|
252
|
+
function reviewDiagnostic({ pathKind, transport, status, results, completion, requestClass, review, refusal, scope } = {}) {
|
|
172
253
|
return `[model-proxy] auto_review path=${_label(pathKind, REVIEW_PATH_KINDS, REVIEW_LABEL_UNKNOWN)}`
|
|
173
254
|
+ ` transport=${_label(transport, REVIEW_TRANSPORTS, REVIEW_LABEL_UNKNOWN)}`
|
|
174
255
|
+ ` status=${_boundedStatus(status)}`
|
|
175
256
|
+ ` results=${_label(results, REVIEW_RESULTS, REVIEW_LABEL_UNOBSERVED)}`
|
|
176
|
-
+ ` completion=${_label(completion, REVIEW_COMPLETIONS, REVIEW_LABEL_UNOBSERVED)}
|
|
257
|
+
+ ` completion=${_label(completion, REVIEW_COMPLETIONS, REVIEW_LABEL_UNOBSERVED)}`
|
|
258
|
+
+ ` class=${_label(requestClass, REQUEST_CLASSES, REVIEW_LABEL_UNKNOWN)}`
|
|
259
|
+
+ ` review=${_label(review, REVIEW_DECISIONS, 'requested')}`
|
|
260
|
+
+ ` refusal=${_label(refusal, REVIEW_REFUSALS, 'none')}`
|
|
261
|
+
+ ` scope=${_label(scope, REVIEW_SCOPES, 'none')}`;
|
|
177
262
|
}
|
|
178
263
|
|
|
179
264
|
/**
|
|
@@ -332,8 +417,10 @@ function trackDownstreamClose(req, res) {
|
|
|
332
417
|
}
|
|
333
418
|
|
|
334
419
|
module.exports = {
|
|
335
|
-
SAFEGUARDS_UNSUPPORTED_MESSAGE,
|
|
336
420
|
reviewRequested,
|
|
421
|
+
serverReviewAsked,
|
|
422
|
+
requestClassFromHeaders,
|
|
423
|
+
REQUEST_CLASSES,
|
|
337
424
|
unsupportedSafeguards,
|
|
338
425
|
mergeAnthropicBetas,
|
|
339
426
|
MAX_OBSERVED_JSON_BYTES,
|
|
@@ -23,6 +23,9 @@ function createChatResponseTranslators({ normalizeUpstreamOutcome, firstString,
|
|
|
23
23
|
// WP-2 D-5: same two mappings as anthropic-face.js (HTTP 400 / 403 respectively).
|
|
24
24
|
if (kind === 'model_not_invocable') return 'invalid_request_error';
|
|
25
25
|
if (kind === 'model_access_denied') return 'permission_error';
|
|
26
|
+
// WP-1b (§12a rule 2): `model_not_found` maps exactly as `invalid_prompt` did, so a client that
|
|
27
|
+
// saw `invalid_request_error` on an upstream 404 still sees it.
|
|
28
|
+
if (kind === 'model_not_found') return 'invalid_request_error';
|
|
26
29
|
return 'api_error';
|
|
27
30
|
}
|
|
28
31
|
|
|
@@ -2,13 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
const upstreamHttp = require('./upstream-http');
|
|
4
4
|
const { classifyFailure } = require('../worker-failure-classify');
|
|
5
|
+
// WP-1b (docs/lead-route-model-invocability-preflight-2026-10-28.html §12a rule 1): a probe that
|
|
6
|
+
// names the wire by its own preference can contradict the wire the CALLER's harness will actually
|
|
7
|
+
// use (`selectUpstream(route, face, model)`), so a face-carrying probe asks the route table the
|
|
8
|
+
// same question the live request path does. `./routes` requires nothing from this module, so this
|
|
9
|
+
// stays a one-way edge.
|
|
10
|
+
const { selectUpstream } = require('./routes');
|
|
11
|
+
// WP-1 (docs/lead-route-model-invocability-preflight-2026-10-28.html §6.1): the probe's own
|
|
12
|
+
// verdict has to speak the proxy's classification, because that is the vocabulary that names a
|
|
13
|
+
// route/model pair as unable to run (`model_not_invocable` / `model_access_denied`). The worker
|
|
14
|
+
// taxonomy (`classifyFailure` above) is kept as-is for its existing consumers.
|
|
15
|
+
const { normalizeUpstreamOutcome } = require('./upstream-outcome');
|
|
5
16
|
|
|
6
17
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
7
18
|
const MAX_TIMEOUT_MS = 30_000;
|
|
8
19
|
const MAX_RESPONSE_BYTES = 64 * 1024;
|
|
9
20
|
const PROBE_PROMPT = 'Reply OK.';
|
|
10
21
|
|
|
11
|
-
|
|
22
|
+
// The two lead faces (§12a rule 1). Any other value, `undefined` included, keeps the legacy
|
|
23
|
+
// preference order below, so every existing caller dials exactly what it dials today.
|
|
24
|
+
const FACED_WIRES = new Set(['anthropic', 'responses']);
|
|
25
|
+
|
|
26
|
+
function _wireFor(route, face, model) {
|
|
27
|
+
if (FACED_WIRES.has(face)) {
|
|
28
|
+
// The face's own wire, chosen by the SAME function the request path uses. A route that cannot
|
|
29
|
+
// serve this face at all answers null, which the caller turns into `probe_unsupported_wire`
|
|
30
|
+
// (http_status null) rather than guessing at another wire.
|
|
31
|
+
const upstream = selectUpstream(route, face, String(model).trim());
|
|
32
|
+
return upstream && upstream.api ? upstream.api : null;
|
|
33
|
+
}
|
|
12
34
|
const apis = route && route.upstreamApis || [];
|
|
13
35
|
if (apis.includes('openai-chat')) return 'openai-chat';
|
|
14
36
|
if (apis.includes('anthropic-messages')) return 'anthropic-messages';
|
|
@@ -36,15 +58,32 @@ function _validResponse(wire, value) {
|
|
|
36
58
|
return false;
|
|
37
59
|
}
|
|
38
60
|
|
|
39
|
-
|
|
40
|
-
|
|
61
|
+
// The proxy's own classification for one probe response, reduced to the four fields a caller may
|
|
62
|
+
// act on. Provider prose (`message`/`error`) is deliberately NOT copied: `normalizeUpstreamOutcome`
|
|
63
|
+
// already redacts it, and this narrowing keeps that guarantee at the seam.
|
|
64
|
+
function _outcomeOf(status, value) {
|
|
65
|
+
const outcome = normalizeUpstreamOutcome({ body: value == null ? null : value, status });
|
|
41
66
|
return {
|
|
67
|
+
kind: outcome.kind,
|
|
68
|
+
upstreamStatus: outcome.upstreamStatus,
|
|
69
|
+
upstreamCode: outcome.upstreamCode,
|
|
70
|
+
retryable: outcome.retryable,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function _failure(status, observedText, fallbackCode = 'probe_failed', now = Date.now, value = null) {
|
|
75
|
+
const classified = classifyFailure({ http_status: status, message: observedText, now });
|
|
76
|
+
const failure = {
|
|
42
77
|
class: classified.class,
|
|
43
78
|
code: classified.code || fallbackCode,
|
|
44
79
|
http_status: status == null ? null : status,
|
|
45
80
|
retry_after_ms: classified.retry_after_ms,
|
|
46
81
|
message: 'Generation probe failed',
|
|
47
82
|
};
|
|
83
|
+
// A failure with an actual HTTP response carries the proxy's classification of it. A transport,
|
|
84
|
+
// timeout or unknown-wire failure (`http_status: null`) has no upstream outcome at all.
|
|
85
|
+
if (Number.isInteger(status)) failure.upstream_outcome = _outcomeOf(status, value);
|
|
86
|
+
return failure;
|
|
48
87
|
}
|
|
49
88
|
|
|
50
89
|
async function _readBodyBufferBounded(readable) {
|
|
@@ -75,10 +114,14 @@ function _requestFor(wire, model) {
|
|
|
75
114
|
return { suffix: '/responses', body: { model, input: PROBE_PROMPT, max_output_tokens: 16, stream: false } };
|
|
76
115
|
}
|
|
77
116
|
|
|
78
|
-
async function probeGeneration(route, { model, timeoutMs = DEFAULT_TIMEOUT_MS, now = Date.now } = {}) {
|
|
79
|
-
const wire = _wireFor(route);
|
|
117
|
+
async function probeGeneration(route, { model, timeoutMs = DEFAULT_TIMEOUT_MS, now = Date.now, face } = {}) {
|
|
118
|
+
const wire = _wireFor(route, face, model);
|
|
119
|
+
// Rule 4 needs both facts on EVERY result, success or failure: which wire this verdict is about,
|
|
120
|
+
// and how many wires the route offers (a verdict is only reportable when the route offers one).
|
|
121
|
+
const routeWireCount = route && Array.isArray(route.upstreamApis) ? route.upstreamApis.length : 0;
|
|
122
|
+
const withWire = (result) => ({ ...result, probe_wire: wire, route_wire_count: routeWireCount });
|
|
80
123
|
if (!wire) {
|
|
81
|
-
return {
|
|
124
|
+
return withWire({
|
|
82
125
|
generation_succeeded: false,
|
|
83
126
|
http_status: null,
|
|
84
127
|
failure: {
|
|
@@ -88,7 +131,7 @@ async function probeGeneration(route, { model, timeoutMs = DEFAULT_TIMEOUT_MS, n
|
|
|
88
131
|
retry_after_ms: null,
|
|
89
132
|
message: 'Generation probe failed',
|
|
90
133
|
},
|
|
91
|
-
};
|
|
134
|
+
});
|
|
92
135
|
}
|
|
93
136
|
const timeout = Math.max(1, Math.min(Number(timeoutMs) || DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS));
|
|
94
137
|
const request = _requestFor(wire, String(model).trim());
|
|
@@ -108,11 +151,11 @@ async function probeGeneration(route, { model, timeoutMs = DEFAULT_TIMEOUT_MS, n
|
|
|
108
151
|
caPem: route.caPem,
|
|
109
152
|
});
|
|
110
153
|
} catch {
|
|
111
|
-
return {
|
|
154
|
+
return withWire({
|
|
112
155
|
generation_succeeded: false,
|
|
113
156
|
http_status: null,
|
|
114
157
|
failure: _failure(null, '', 'probe_failed', now),
|
|
115
|
-
};
|
|
158
|
+
});
|
|
116
159
|
}
|
|
117
160
|
|
|
118
161
|
let raw;
|
|
@@ -120,11 +163,11 @@ async function probeGeneration(route, { model, timeoutMs = DEFAULT_TIMEOUT_MS, n
|
|
|
120
163
|
raw = (await _readBodyBufferBounded(resp.body)).toString('utf8');
|
|
121
164
|
} catch (err) {
|
|
122
165
|
const tooLarge = err && err.code === 'probe_response_too_large';
|
|
123
|
-
return {
|
|
166
|
+
return withWire({
|
|
124
167
|
generation_succeeded: false,
|
|
125
168
|
http_status: resp.status,
|
|
126
169
|
failure: _failure(resp.status, '', tooLarge ? 'probe_response_too_large' : 'probe_failed', now),
|
|
127
|
-
};
|
|
170
|
+
});
|
|
128
171
|
}
|
|
129
172
|
try {
|
|
130
173
|
let value = null;
|
|
@@ -133,19 +176,19 @@ async function probeGeneration(route, { model, timeoutMs = DEFAULT_TIMEOUT_MS, n
|
|
|
133
176
|
if (okStatus && _validResponse(wire, value)) {
|
|
134
177
|
const result = { generation_succeeded: true, http_status: resp.status };
|
|
135
178
|
if (typeof value.model === 'string' && value.model) result.upstream_model = value.model;
|
|
136
|
-
return result;
|
|
179
|
+
return withWire(result);
|
|
137
180
|
}
|
|
138
|
-
return {
|
|
181
|
+
return withWire({
|
|
139
182
|
generation_succeeded: false,
|
|
140
183
|
http_status: resp.status,
|
|
141
|
-
failure: _failure(resp.status, raw, okStatus ? 'probe_invalid_response' : 'probe_failed', now),
|
|
142
|
-
};
|
|
184
|
+
failure: _failure(resp.status, raw, okStatus ? 'probe_invalid_response' : 'probe_failed', now, value),
|
|
185
|
+
});
|
|
143
186
|
} catch {
|
|
144
|
-
return {
|
|
187
|
+
return withWire({
|
|
145
188
|
generation_succeeded: false,
|
|
146
189
|
http_status: null,
|
|
147
190
|
failure: _failure(null, '', 'probe_failed', now),
|
|
148
|
-
};
|
|
191
|
+
});
|
|
149
192
|
}
|
|
150
193
|
}
|
|
151
194
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const CODEX_HISTORICAL_TOOLS_FILTER_RECEIPT = 'ctm-proxy-historical-tools-v1';
|
|
4
|
+
|
|
5
|
+
function normalizeHistoricalAdditionalTools(body, { trustedRoutedCodexLead = false } = {}) {
|
|
6
|
+
if (!trustedRoutedCodexLead || !Array.isArray(body?.tools) || !Array.isArray(body?.input)) {
|
|
7
|
+
return { ok: true, body, removedCount: 0 };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let removedCount = 0;
|
|
11
|
+
const input = [];
|
|
12
|
+
for (const item of body.input) {
|
|
13
|
+
if (!item || typeof item !== 'object' || !Object.hasOwn(item, 'type') || item.type !== 'additional_tools') {
|
|
14
|
+
input.push(item);
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const keys = Reflect.ownKeys(item);
|
|
19
|
+
const allowed = new Set(['type', 'role', 'tools', 'id']);
|
|
20
|
+
if (keys.some((key) => typeof key !== 'string' || !allowed.has(key)) ||
|
|
21
|
+
!Object.hasOwn(item, 'role') || !Object.hasOwn(item, 'tools') ||
|
|
22
|
+
item.role !== 'developer' || !Array.isArray(item.tools) ||
|
|
23
|
+
(Object.hasOwn(item, 'id') && item.id !== null &&
|
|
24
|
+
(typeof item.id !== 'string' || item.id.length === 0))) {
|
|
25
|
+
return { ok: false, reason: 'historical_tool_declaration_invalid' };
|
|
26
|
+
}
|
|
27
|
+
removedCount += 1;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (removedCount === 0) return { ok: true, body, removedCount: 0 };
|
|
31
|
+
return { ok: true, body: { ...body, input }, removedCount };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = {
|
|
35
|
+
CODEX_HISTORICAL_TOOLS_FILTER_RECEIPT,
|
|
36
|
+
normalizeHistoricalAdditionalTools,
|
|
37
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-1 §6.4/§6.5 registration lifecycle, extracted out of oauth-proxy.js (review item 3): the one
|
|
4
|
+
// bounded transaction that answers `POST /_admin/tokens` with kind:'lead'. The refusal paths, the
|
|
5
|
+
// registry-owned ticket, the abort window and the 409 codes below are the ones oauth-proxy.js has
|
|
6
|
+
// always run, moved verbatim -- the transaction stays whole instead of being spread across the
|
|
7
|
+
// request dispatcher.
|
|
8
|
+
//
|
|
9
|
+
// The seam this cluster needs from its host is the lead-token registry in ./tokens
|
|
10
|
+
// (registerLeadToken, beginLeadTokenRegistration, LEAD_TOKEN_REVOKED_CODE, NATIVE_TRANSPORT_MTLS) and
|
|
11
|
+
// nothing else: the response `res`, the parsed `registration` and the per-instance `ctx` carrying
|
|
12
|
+
// this server's OWN native listener are all passed IN by the caller, so this module never requires
|
|
13
|
+
// oauth-proxy.js back -- there is no cycle.
|
|
14
|
+
|
|
15
|
+
const tokens = require('./tokens');
|
|
16
|
+
|
|
17
|
+
// ── WP-1 §6.4/§6.5: the TLS-ready acknowledgment of an mtls-v1 registration ─────────────────────
|
|
18
|
+
//
|
|
19
|
+
// A legacy (or routed-primary) registration is unchanged: it is synchronous, creates no listener and
|
|
20
|
+
// never loads the CA. An `mtls-v1` registration is the one thing that starts this transport, and it is
|
|
21
|
+
// acknowledged ONLY once BOTH of these are true:
|
|
22
|
+
//
|
|
23
|
+
// 1. the strict HTTPS listener is bound and ready, and
|
|
24
|
+
// 2. the pending registration has committed against the live registry.
|
|
25
|
+
//
|
|
26
|
+
// The order matters, and so does the ticket. Between "the registration is well formed" and "the
|
|
27
|
+
// listener is ready" the caller is awaiting, which is exactly when a `DELETE /_admin/tokens` can land:
|
|
28
|
+
// a plain revalidation would still see the token as absent (it was never live) and commit a grant the
|
|
29
|
+
// caller already gave up on. The registry-owned ticket fences that operation, so commit refuses with a
|
|
30
|
+
// bounded code and no token is created. A failed bind cancels the ticket and leaves no grant either.
|
|
31
|
+
//
|
|
32
|
+
// Review item 3 adds the OTHER half of that window: the caller can also simply GO AWAY. The supervisor's
|
|
33
|
+
// admin fetch has a 2s budget, and the child's TLS bind can outlive it — so an interrupted registration
|
|
34
|
+
// must not leave a live owner behind that the parent never learned about. The response is therefore
|
|
35
|
+
// watched from BEFORE the await (a `res.close` listener installed up front, not after the fact), the
|
|
36
|
+
// ticket is cancelled the moment the response can no longer be written, and the commit is guarded by a
|
|
37
|
+
// re-check on both sides of the await. Nothing is ever written to a closed response.
|
|
38
|
+
function _responseIsGone(res) {
|
|
39
|
+
if (!res) return true;
|
|
40
|
+
if (res.writableEnded) return true; // we already answered
|
|
41
|
+
if (res.destroyed) return true; // the socket went away
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function handleLeadTokenRegistration(res, registration, ctx) {
|
|
46
|
+
const sendJson = (status, payload) => {
|
|
47
|
+
// Never write on a response whose client is gone: a torn-down socket has no reader, and writing
|
|
48
|
+
// would surface as an unhandled error rather than as the honest "this registration was abandoned".
|
|
49
|
+
if (_responseIsGone(res)) return;
|
|
50
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
51
|
+
res.end(JSON.stringify(payload));
|
|
52
|
+
};
|
|
53
|
+
// §5 D-4 / §9 S6: the ONE refusal that is not a malformed registration is the revoked-identity
|
|
54
|
+
// tombstone, and it answers 409 with a static code (never the underlying message, never a caller
|
|
55
|
+
// value) from all three of this handler's exits: the synchronous branch, the ticket's entry check and
|
|
56
|
+
// the ticket's commit.
|
|
57
|
+
const sendRegistrationRefusal = (err) => {
|
|
58
|
+
if (err && err.code === tokens.LEAD_TOKEN_REVOKED_CODE) sendJson(409, { error: tokens.LEAD_TOKEN_REVOKED_CODE });
|
|
59
|
+
else sendJson(400, { error: err && err.message });
|
|
60
|
+
};
|
|
61
|
+
if (registration.nativeTransport !== tokens.NATIVE_TRANSPORT_MTLS) {
|
|
62
|
+
// Existing synchronous contract, byte-for-byte: no listener, no CA, no await.
|
|
63
|
+
try {
|
|
64
|
+
const result = tokens.registerLeadToken(registration);
|
|
65
|
+
sendJson(200, { token: result.token });
|
|
66
|
+
} catch (err) {
|
|
67
|
+
sendRegistrationRefusal(err);
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
let ticket;
|
|
72
|
+
try {
|
|
73
|
+
// Validated, conflict-checked read-only against the live registry, and captured BEFORE the first
|
|
74
|
+
// await: the caller's own object cannot change what commits, a malformed OR already-conflicting
|
|
75
|
+
// registration never reaches the listener (no CA load, no bind), and the ticket exists before any
|
|
76
|
+
// await can be interrupted.
|
|
77
|
+
ticket = tokens.beginLeadTokenRegistration(registration);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
sendRegistrationRefusal(err);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
let settled = false;
|
|
83
|
+
const cancelOwnTicket = () => {
|
|
84
|
+
if (settled) return;
|
|
85
|
+
settled = true;
|
|
86
|
+
ticket.cancel();
|
|
87
|
+
};
|
|
88
|
+
// Installed BEFORE the await, so a disconnect that happens while the listener is still binding is
|
|
89
|
+
// observed rather than discovered later as an orphaned owner.
|
|
90
|
+
const onClientGone = () => {
|
|
91
|
+
if (!res.writableEnded) cancelOwnTicket();
|
|
92
|
+
};
|
|
93
|
+
res.on('close', onClientGone);
|
|
94
|
+
try {
|
|
95
|
+
// Entry check: a caller that vanished before we even started has already given up on this attempt.
|
|
96
|
+
if (_responseIsGone(res)) return;
|
|
97
|
+
// This server's OWN controller, never another live server's.
|
|
98
|
+
if (!ctx.nativeListener) {
|
|
99
|
+
cancelOwnTicket();
|
|
100
|
+
sendJson(503, { error: 'native_lead_listener_unavailable' });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
await ctx.nativeListener.ensureReady();
|
|
104
|
+
// Re-check after the await: the interruption window is exactly the one the ticket exists for.
|
|
105
|
+
if (_responseIsGone(res)) return;
|
|
106
|
+
// The commit is synchronous and happens while the response is still writable, so the ticket is
|
|
107
|
+
// retired from the cancellation path here: from this point the grant is real and the only remaining
|
|
108
|
+
// hazard is a LOST acknowledgment, which the caller (the supervisor) owns via its pending-revoke
|
|
109
|
+
// obligation — the child cannot distinguish "the parent read it" from "the parent timed out".
|
|
110
|
+
settled = true;
|
|
111
|
+
const committed = ticket.commit();
|
|
112
|
+
if (!committed.ok) {
|
|
113
|
+
// A revoke/reset/cancel landed while the listener was starting, or the live revalidation found a
|
|
114
|
+
// concurrent owner: no grant, and the caller is told why by bounded code only.
|
|
115
|
+
sendJson(409, { error: committed.code });
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
sendJson(200, { token: committed.token });
|
|
119
|
+
} catch (err) {
|
|
120
|
+
cancelOwnTicket();
|
|
121
|
+
sendJson(503, { error: (err && err.code) || 'native_lead_listener_unavailable' });
|
|
122
|
+
} finally {
|
|
123
|
+
// The response is no longer ours to reason about, so the listener and any unsettled ticket are
|
|
124
|
+
// released here. An already-committed ticket is untouched (cancel() only moves a PENDING ticket).
|
|
125
|
+
res.removeListener('close', onClientGone);
|
|
126
|
+
cancelOwnTicket();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = { handleLeadTokenRegistration };
|