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
|
@@ -67,6 +67,10 @@ const NOT_RESUMABLE_PARK_CODES = new Set(['session_not_resumable']);
|
|
|
67
67
|
// this module re-exports the history message projector so no import path changes.
|
|
68
68
|
const DEFAULT_RELAUNCH_DEADLINE_MS = 180_000;
|
|
69
69
|
const ACTIVE_JOURNAL_STATES = new Set(['queued', 'preparing', 'compacting', 'verified', 'handing_off', 'awaiting_turn']);
|
|
70
|
+
// WP-1/D-2: provider statuses that mean "this target can never serve a turn as configured" (model
|
|
71
|
+
// access, bad request, auth). Everything else -- 408/409/429/5xx/529, or no status at all -- is
|
|
72
|
+
// transient, so the switch keeps waiting for a later successful turn instead of undoing the pick.
|
|
73
|
+
const PERMANENT_TURN_FAILURE_STATUSES = new Set([400, 401, 403, 404, 422]);
|
|
70
74
|
|
|
71
75
|
// historyPreparationMessage / parkRefusalDetail / PARK_REFUSAL_SENTENCES: see the approved helper.
|
|
72
76
|
|
|
@@ -141,6 +145,7 @@ function createLeadModelSwitch(deps) {
|
|
|
141
145
|
revalidateHistoryBeforePark = async () => ({ ok: false, reason: 'history_revalidation_unavailable' }),
|
|
142
146
|
cleanupStagedTarget = async () => true,
|
|
143
147
|
prepareTargetPlan = async () => null,
|
|
148
|
+
resolveSourceHistoryMode = null, // WP-0 D-1; absent -> 'present' (pre-change behaviour)
|
|
144
149
|
stageTarget = async () => null,
|
|
145
150
|
getLeadRoute = () => null,
|
|
146
151
|
persistLeadRoute = () => {},
|
|
@@ -386,12 +391,25 @@ function createLeadModelSwitch(deps) {
|
|
|
386
391
|
entry.req.cancellable = false;
|
|
387
392
|
entry.req.detail = 'Restoring the captured source provider lifetime.';
|
|
388
393
|
emit(entry, 'recovering');
|
|
394
|
+
entry.recoveryRunning = true;
|
|
389
395
|
entry.recoveryPromise = Promise.resolve().then(() => cleanupStaged(entry)).then(() => recoverSource({
|
|
390
396
|
sessionId: entry.req.ctm_session_id, source: entry.sourceInfo, transition: entry.transition,
|
|
391
397
|
targetStarted: !!entry.targetStarted, targetOwner: entry.targetOwner || null,
|
|
392
398
|
})).then((result) => {
|
|
393
399
|
entry.recovered = !!result?.ok;
|
|
394
|
-
if (!entry.recovered) {
|
|
400
|
+
if (!entry.recovered && recoveryRefusalIsTransient(result)) {
|
|
401
|
+
// Live 10-28 (textbook 3ae5a121, transition 79c30333): the target's first turn was refused
|
|
402
|
+
// and this recovery ran while the server still called the session `running` (it went idle
|
|
403
|
+
// 15 s later); the park refused it and recovery ended for good, leaving the session on a
|
|
404
|
+
// target that cannot serve a turn. A busy refusal waits for the next idle edge instead,
|
|
405
|
+
// exactly like the forward park (REQUEUE_PARK_CODES).
|
|
406
|
+
entry.recoveryPromise = null;
|
|
407
|
+
entry.recoveryAwaitingIdle = true;
|
|
408
|
+
const cause = entry.failureOutcome?.detail || 'The requested target was not applied.';
|
|
409
|
+
entry.req.detail = `${cause} The captured source provider is restored once the session is idle.`;
|
|
410
|
+
diagnostic(entry.req.ctm_session_id, 'lead-transition-source-recovery-deferred', { protections: result.protections || [] });
|
|
411
|
+
armRecoveryRetry(entry, Number(result.retryAt));
|
|
412
|
+
} else if (!entry.recovered) {
|
|
395
413
|
entry.req.recovery_reason = 'source_recovery_failed';
|
|
396
414
|
diagnostic(entry.req.ctm_session_id, 'lead-transition-source-recovery-failed', { reason: String(result?.reason || 'unknown').slice(0, 120) });
|
|
397
415
|
const cause = entry.failureOutcome?.detail || 'The requested target was not applied.';
|
|
@@ -412,8 +430,47 @@ function createLeadModelSwitch(deps) {
|
|
|
412
430
|
diagnostic(entry.req.ctm_session_id, 'lead-source-recovery-failed', { message: recoveryDetail });
|
|
413
431
|
emit(entry, entry.req.state);
|
|
414
432
|
return { ok: false, reason: 'source_recovery_failed' };
|
|
415
|
-
});
|
|
416
|
-
return entry.recoveryPromise;
|
|
433
|
+
}).finally(() => { entry.recoveryRunning = false; });
|
|
434
|
+
return entry.recoveryPromise || Promise.resolve(null);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function recoveryRefusalIsTransient(result) {
|
|
438
|
+
const protections = Array.isArray(result?.protections) ? result.protections : [];
|
|
439
|
+
return REQUEUE_PARK_CODES.has(String(result?.reason || '')) && !protections.some((p) => NON_TRANSIENT_PROTECTIONS.has(p));
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function armRecoveryRetry(entry, at) {
|
|
443
|
+
if (!Number.isFinite(at) || at <= now()) return;
|
|
444
|
+
if (entry.recoveryRetryTimer) clearTimeoutFn(entry.recoveryRetryTimer);
|
|
445
|
+
entry.recoveryRetryTimer = setTimeoutFn(() => {
|
|
446
|
+
entry.recoveryRetryTimer = null;
|
|
447
|
+
void notifyStatus(entry.req.ctm_session_id);
|
|
448
|
+
}, at - now());
|
|
449
|
+
if (entry.recoveryRetryTimer && typeof entry.recoveryRetryTimer.unref === 'function') entry.recoveryRetryTimer.unref();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// A recovery waiting for idle re-runs on the next idle edge, only while its request is still the
|
|
453
|
+
// session's latest one: a new pick replaces the entry, and that pick's own park moves the target.
|
|
454
|
+
function retryRecoveryWhenIdle(entry) {
|
|
455
|
+
const id = entry.req.ctm_session_id;
|
|
456
|
+
if (entry.recoveryCheck) { entry.recoveryEdgePending = true; return entry.recoveryCheck; }
|
|
457
|
+
entry.recoveryCheck = Promise.resolve().then(async () => {
|
|
458
|
+
for (let checkCount = 0; checkCount < 2; checkCount += 1) {
|
|
459
|
+
entry.recoveryEdgePending = false;
|
|
460
|
+
let idle = false;
|
|
461
|
+
try { idle = (await isIdle(id)) === true; }
|
|
462
|
+
catch (error) { diagnostic(id, 'lead-model-idle-check-failed', { message: String(error && error.message || error).slice(0, 200) }); }
|
|
463
|
+
if (entries.get(id) !== entry || !entry.recoveryAwaitingIdle) return;
|
|
464
|
+
if (idle) {
|
|
465
|
+
entry.recoveryAwaitingIdle = false;
|
|
466
|
+
if (entry.recoveryRetryTimer) { clearTimeoutFn(entry.recoveryRetryTimer); entry.recoveryRetryTimer = null; }
|
|
467
|
+
await recoverAbandonedSource(entry);
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (!entry.recoveryEdgePending) return;
|
|
471
|
+
}
|
|
472
|
+
}).finally(() => { entry.recoveryCheck = null; });
|
|
473
|
+
return entry.recoveryCheck;
|
|
417
474
|
}
|
|
418
475
|
|
|
419
476
|
function isPending(entry) {
|
|
@@ -468,8 +525,8 @@ function createLeadModelSwitch(deps) {
|
|
|
468
525
|
if (!routeId && !effortOnly && !isSwitchableModel(info.agent, modelId)) return refuse(400, 'lead_model_not_switchable', { model_id: modelId });
|
|
469
526
|
if (scope === 'session+default' && info.agent !== 'codex') return refuse(400, 'lead_default_unsupported', { agent: info.agent });
|
|
470
527
|
if (info.launchEnvironment?.state !== 'captured') {
|
|
471
|
-
|
|
472
|
-
|
|
528
|
+
try { diagnostic(id, 'lead-model-refused', { reason: 'source_launch_environment_unavailable', cause: info.launchEnvironment?.reason ?? null }); } catch (error) { process.stderr.write(`[lead-transition] diagnostic sink failed code=${String(error?.code || 'sink_failed').slice(0, 64)}\n`); }
|
|
529
|
+
return refuse(409, 'lead_switch_unsupported', { reason: 'source_launch_environment_unavailable', cause: info.launchEnvironment?.reason ?? null, message: 'The source launch environment cannot be recovered safely, so its process was left running.' });
|
|
473
530
|
}
|
|
474
531
|
|
|
475
532
|
const existing = entries.get(id);
|
|
@@ -481,7 +538,10 @@ function createLeadModelSwitch(deps) {
|
|
|
481
538
|
return refuse(409, 'client_request_conflict');
|
|
482
539
|
}
|
|
483
540
|
if (existing?.confirmationPromise) return refuse(409, 'lead_transition_committing', { request: publicRequest(existing.req) });
|
|
484
|
-
|
|
541
|
+
// Only a recovery that is RUNNING blocks a pick. One waiting for idle is superseded by the pick
|
|
542
|
+
// (whose own park waits for idle), and one that ended without restoring the source must not
|
|
543
|
+
// refuse every later pick until a CTM restart (live 10-28, textbook 3ae5a121).
|
|
544
|
+
if (existing?.recoveryRunning && ['cancelled', 'failed'].includes(existing.req.state)) {
|
|
485
545
|
return refuse(409, 'lead_switch_recovery_in_flight', { request: publicRequest(existing.req) });
|
|
486
546
|
}
|
|
487
547
|
if (existing?.req.state === 'awaiting_turn' && existing.handoffStarted && !existing.recovered) {
|
|
@@ -611,10 +671,22 @@ function createLeadModelSwitch(deps) {
|
|
|
611
671
|
};
|
|
612
672
|
entries.set(id, entry);
|
|
613
673
|
emit(entry, 'preparing');
|
|
674
|
+
// WP-0 D-1: resolve the source history mode ONCE, before the plan, the claim and any park; both
|
|
675
|
+
// recipes carry it ('none' = never-prompted source, so a resume-less target is legitimate).
|
|
676
|
+
let historyMode = 'present';
|
|
677
|
+
if (typeof resolveSourceHistoryMode === 'function') {
|
|
678
|
+
try { historyMode = (await resolveSourceHistoryMode(id)) === 'none' ? 'none' : 'present'; }
|
|
679
|
+
catch (error) { // HANDLE: D-2 -- a broken resolver is never evidence the conversation is gone.
|
|
680
|
+
diagnostic(id, 'lead-history-mode-failed', { code: String(error?.code || 'history_mode_failed').slice(0, 64) });
|
|
681
|
+
}
|
|
682
|
+
if (!ownsRequest()) return superseded();
|
|
683
|
+
if (!sourceStillMatches()) return refuse(409, 'model_owner_changed');
|
|
684
|
+
}
|
|
685
|
+
entry.sourceInfo.historyMode = historyMode;
|
|
614
686
|
try {
|
|
615
687
|
targetPlan = await prepareTargetPlan({
|
|
616
688
|
sessionId: id, transitionId: requestId, revision: requestRevision,
|
|
617
|
-
source: { ...info, processGeneration: sourceGeneration, providerThreadId: sourceThreadId },
|
|
689
|
+
source: { ...info, processGeneration: sourceGeneration, providerThreadId: sourceThreadId, historyMode },
|
|
618
690
|
target: { agent: info.agent, routeId: routeId || null, modelId }, signal: controller.signal,
|
|
619
691
|
});
|
|
620
692
|
} catch (error) {
|
|
@@ -702,7 +774,7 @@ function createLeadModelSwitch(deps) {
|
|
|
702
774
|
const pendingClaim = Promise.resolve(beginTransition({
|
|
703
775
|
transitionId: requestId, revision: requestRevision, sessionId: id, agent: info.agent,
|
|
704
776
|
source: { ...info, processGeneration: sourceGeneration, providerThreadId: sourceThreadId,
|
|
705
|
-
leadRoute: sourceLeadRoute },
|
|
777
|
+
leadRoute: sourceLeadRoute, historyMode },
|
|
706
778
|
target: { agent: info.agent, routeId: routeId || null, modelId, deployment: routed?.deployment || null,
|
|
707
779
|
profileFingerprint: routed?.profileFingerprint || null, factsRevision: routed?.deployment?.factsRevision || null,
|
|
708
780
|
preparedProfile: routed?.preparedProfile || null, resolvedRoute: routed?.resolvedRoute || null },
|
|
@@ -768,7 +840,7 @@ function createLeadModelSwitch(deps) {
|
|
|
768
840
|
return refuse(500, 'lead_transition_persist_failed', { message: String(error?.message || error).slice(0, 200) });
|
|
769
841
|
}
|
|
770
842
|
}
|
|
771
|
-
if (providerTransition && info.agent !== 'claude') {
|
|
843
|
+
if (providerTransition && info.agent !== 'claude' && entry.sourceInfo.historyMode !== 'none') {
|
|
772
844
|
if (!entry.transition) { requestControllers.delete(id); fail(entry, 'lead_transition_owner_unavailable', 'The transition owner is not available.'); return refuse(409, 'lead_transition_owner_unavailable'); }
|
|
773
845
|
try {
|
|
774
846
|
historyPreparation = await prepareHistory({
|
|
@@ -1012,6 +1084,7 @@ function createLeadModelSwitch(deps) {
|
|
|
1012
1084
|
function notifyStatus(sessionId) {
|
|
1013
1085
|
const id = String(sessionId || '');
|
|
1014
1086
|
const entry = entries.get(id);
|
|
1087
|
+
if (entry?.recoveryAwaitingIdle) return retryRecoveryWhenIdle(entry);
|
|
1015
1088
|
if (!entry || !['queued', 'relaunching'].includes(entry.req.state)) return Promise.resolve();
|
|
1016
1089
|
if (entry.check || entry.req.state === 'relaunching') { entry.edgePending = true; return entry.check || entry.tail; }
|
|
1017
1090
|
return tryFire(id);
|
|
@@ -1338,6 +1411,21 @@ function createLeadModelSwitch(deps) {
|
|
|
1338
1411
|
}
|
|
1339
1412
|
const at = Number(obs.observedAtMs);
|
|
1340
1413
|
if (!Number.isFinite(at) || at <= Number(req.relaunched_at)) return { settled: false, ignored: 'not_after_relaunch' };
|
|
1414
|
+
/*
|
|
1415
|
+
* WP-1/D-2. A Claude first turn the API REFUSED is a positive failure the transcript already
|
|
1416
|
+
* records (an `isApiErrorMessage` assistant row), so it is decided HERE -- after the owner checks
|
|
1417
|
+
* and the post-relaunch check, and BEFORE `no_model`, because a refusal carries no model name by
|
|
1418
|
+
* construction and used to be swallowed as "no observation yet". A permanent status ends the
|
|
1419
|
+
* switch and the existing failure path restores the captured source; a transient one leaves the
|
|
1420
|
+
* request in `awaiting_turn` for the next successful turn to commit.
|
|
1421
|
+
*/
|
|
1422
|
+
if (req.agent === 'claude' && obs.outcome === 'failed') {
|
|
1423
|
+
if (PERMANENT_TURN_FAILURE_STATUSES.has(Number(obs.apiErrorStatus))) {
|
|
1424
|
+
fail(entry, 'turn_failed', `Claude rejected the first turn after the restart: API Error ${obs.apiErrorStatus}. Pick another model.`, { observed_source: obs.source });
|
|
1425
|
+
return { settled: true, state: 'failed' };
|
|
1426
|
+
}
|
|
1427
|
+
return { settled: false, ignored: 'turn_failed_transient' };
|
|
1428
|
+
}
|
|
1341
1429
|
const model = String(obs.model || '').trim();
|
|
1342
1430
|
if (!model) return { settled: false, ignored: 'no_model' };
|
|
1343
1431
|
const observedEffort = typeof obs.effort === 'string' && obs.effort.trim() ? obs.effort.trim().toLowerCase() : null;
|
|
@@ -1466,6 +1554,21 @@ function createLeadModelSwitch(deps) {
|
|
|
1466
1554
|
return !!entry && entry.req.state === 'awaiting_turn';
|
|
1467
1555
|
}
|
|
1468
1556
|
|
|
1557
|
+
/**
|
|
1558
|
+
* WP-1/D-4 (presumed abort). A restore found a transition this controller still holds in memory in
|
|
1559
|
+
* `awaiting_turn`: the target was never confirmed by a turn, so the switch is abandoned and the
|
|
1560
|
+
* ordinary failure path restores the captured source. Only an in-memory `awaiting_turn` entry is a
|
|
1561
|
+
* side effect; anything else is a typed no-op so the caller can fall back to the journal.
|
|
1562
|
+
* @param {string} sessionId
|
|
1563
|
+
* @param {string} reason
|
|
1564
|
+
*/
|
|
1565
|
+
function abandonUnconfirmedTarget(sessionId, reason = 'target_unconfirmed_presumed_abort') {
|
|
1566
|
+
const entry = entries.get(String(sessionId || ''));
|
|
1567
|
+
if (!entry || entry.req.state !== 'awaiting_turn') return { ok: false, reason: 'not_awaiting' };
|
|
1568
|
+
fail(entry, reason, 'The switch was never confirmed by a turn before the session stopped; the original model is restored. Pick the model again to retry.');
|
|
1569
|
+
return { ok: true, state: 'failed' };
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1469
1572
|
// Resolves when the in-flight relaunch (if any) has finished. For tests and shutdown.
|
|
1470
1573
|
async function settled(sessionId) {
|
|
1471
1574
|
const entry = entries.get(String(sessionId || ''));
|
|
@@ -1495,7 +1598,8 @@ function createLeadModelSwitch(deps) {
|
|
|
1495
1598
|
}
|
|
1496
1599
|
|
|
1497
1600
|
return { request, get, cancel, notifyStatus, observe, observeSelection, wantsObservation, settled,
|
|
1498
|
-
adoptAwaitingTransition, recoverUnownedTransition, reconcileUnownedTransition,
|
|
1601
|
+
abandonUnconfirmedTarget, adoptAwaitingTransition, recoverUnownedTransition, reconcileUnownedTransition,
|
|
1602
|
+
_effortPlanCount: () => effortPlans.size };
|
|
1499
1603
|
}
|
|
1500
1604
|
|
|
1501
1605
|
module.exports = {
|
|
@@ -12,8 +12,10 @@ const { rebindCodexForkLaunchPlan } = require('./launch-model-override');
|
|
|
12
12
|
const codexPaths = require('./codex-paths');
|
|
13
13
|
const { _probeVersion } = require('./harness-model-profile');
|
|
14
14
|
// S-6: the receipt id and the default control-adapter id are qualified-table constants.
|
|
15
|
-
const { CODEX_COMPACTION_SCHEMA_RECEIPT, CODEX_CONTROL_ADAPTER
|
|
15
|
+
const { CODEX_COMPACTION_SCHEMA_RECEIPT, CODEX_CONTROL_ADAPTER } =
|
|
16
16
|
require('./codex-harness-qualification');
|
|
17
|
+
// WP-2 S-9: the ONE versioned proxy-filter receipt the planner gates the tool-declaration kind on.
|
|
18
|
+
const { CODEX_HISTORICAL_TOOLS_FILTER_RECEIPT } = require('./model-proxy/historical-tool-declarations');
|
|
17
19
|
const MAX_PROFILE_CATALOG_BYTES = 1024 * 1024;
|
|
18
20
|
|
|
19
21
|
// WP-2A (generation-zero review): the canonical SOURCE generation is the SAME injected canonical
|
|
@@ -167,6 +169,16 @@ function createLeadModelTransitionRuntime(host = {}) {
|
|
|
167
169
|
factsRevision: target.factsRevision || target.deployment.factsRevision,
|
|
168
170
|
origin: 'routed-proxy',
|
|
169
171
|
controlAdapterVersion: transitionControlAdapterVersion,
|
|
172
|
+
// WP-2 D-4: the planner is handed the receipt the DURABLE target recipe carried -- read back
|
|
173
|
+
// from the store on a restart exactly as on the first pass -- and nothing else: any value that
|
|
174
|
+
// is not the one exact constant is normalized to null before the portability gate sees it, so a
|
|
175
|
+
// forged/foreign spelling can never qualify the historical tool-declaration kind. A recipe that
|
|
176
|
+
// never carried the key (a row written before this receipt existed) keeps its exact shape and is
|
|
177
|
+
// therefore treated as having no receipt proof at all, never as an implicit one.
|
|
178
|
+
...(targetRecipe && Object.hasOwn(targetRecipe, 'historyToolFilterReceipt')
|
|
179
|
+
? { historyToolFilterReceipt: targetRecipe.historyToolFilterReceipt === CODEX_HISTORICAL_TOOLS_FILTER_RECEIPT
|
|
180
|
+
? CODEX_HISTORICAL_TOOLS_FILTER_RECEIPT : null }
|
|
181
|
+
: {}),
|
|
170
182
|
// S-7: the measured binary's version, never a constant. A null profile already refuses in
|
|
171
183
|
// validateTargetCatalogCompactionProfile before this recipe is built.
|
|
172
184
|
harnessVersion: target.preparedProfile?.cliVersion,
|
|
@@ -535,7 +547,7 @@ async function validateTargetCatalogCompactionProfile({ profile, modelId, profil
|
|
|
535
547
|
&& profile.compactionHash === null;
|
|
536
548
|
const present = profile?.compactionHashStatus === 'known-present-with-native-schema-receipt'
|
|
537
549
|
&& typeof profile.compactionHash === 'string' && profile.compactionHash.length > 0;
|
|
538
|
-
if (!profile || profile.harness !== 'codex-cli' || !
|
|
550
|
+
if (!profile || profile.harness !== 'codex-cli' || !nonempty(profile.cliVersion)
|
|
539
551
|
|| profileFingerprint !== profile.fingerprint || factsRevision !== profile.factsRevision
|
|
540
552
|
|| (!absent && !present)
|
|
541
553
|
|| profile.compactionHashSchemaReceipt !== CODEX_COMPACTION_SCHEMA_RECEIPT
|
|
@@ -15,8 +15,13 @@ const REVISION_CHANGED_CODE = 'model_revision_changed';
|
|
|
15
15
|
const OWNER_CHANGED_CODE = 'model_owner_changed';
|
|
16
16
|
const EFFORT_CHANGED_CODE = 'model_effort_changed';
|
|
17
17
|
// S-9: the receipt id is one qualified-table constant, never a local literal.
|
|
18
|
-
const { CODEX_COMPACTION_SCHEMA_RECEIPT
|
|
19
|
-
|
|
18
|
+
const { CODEX_COMPACTION_SCHEMA_RECEIPT } = require('./codex-harness-qualification');
|
|
19
|
+
// S-9/WP-2: the SAME one versioned proxy-filter receipt the proxy filter and the planner use.
|
|
20
|
+
const { CODEX_HISTORICAL_TOOLS_FILTER_RECEIPT } = require('./model-proxy/historical-tool-declarations');
|
|
21
|
+
// WP-0 D-1: the journaled history mode's two spellings come from the ONE history-mode module, so the
|
|
22
|
+
// store can never accept a third one the gates would read as 'present'.
|
|
23
|
+
const { HISTORY_MODES, MODEL_ORIGINS } = require('./lead-switch-history-mode');
|
|
24
|
+
const RECIPE_KEYS = new Set(['id', 'sessionId', 'providerThreadId', 'harness', 'providerId', 'routeId', 'wireModelId', 'sourceModelId', 'processGeneration', 'profileFingerprint', 'authReference', 'approvalPolicyReference', 'compactHash', 'compactHashStatus', 'compactHashSchemaReceipt', 'catalogSha256', 'factsRevision', 'metadataRevision', 'origin', 'controlAdapterVersion', 'programVersion', 'cyberAccessProgram', 'contextTokens', 'usableContextTokens', 'nativeCompactThresholdTokens', 'inputTokens', 'plannedRequestOutputTokens', 'outputCapSource', 'approvalPolicy', 'sandboxPolicy', 'harnessVersion', 'routeTransitionId', 'sourceEffort', 'sourceEnvironmentReference', 'historyToolFilterReceipt', 'historyMode', 'modelOrigin']);
|
|
20
25
|
const PHASE_EDGES = new Map([
|
|
21
26
|
['queued', new Set(['preparing', 'failed', 'cancelled'])],
|
|
22
27
|
['preparing', new Set(['compacting', 'verified', 'failed', 'cancelled'])],
|
|
@@ -55,13 +60,27 @@ function validateRecipe(value, sessionId, generation, label) {
|
|
|
55
60
|
if (!copy.id || !copy.sessionId || copy.sessionId !== sessionId || !copy.providerThreadId) throw new TypeError(`${label} is missing its session/thread reference`);
|
|
56
61
|
if (!Number.isSafeInteger(copy.processGeneration) || copy.processGeneration !== generation) throw new TypeError(`${label} generation mismatch`);
|
|
57
62
|
if (copy.profileFingerprint != null && !/^[a-f0-9]{64}$/.test(copy.profileFingerprint)) throw new TypeError(`${label} profile fingerprint is invalid`);
|
|
58
|
-
if (copy.sourceModelId != null &&
|
|
63
|
+
if (copy.sourceModelId != null && !isValidOverrideModel(copy.sourceModelId)) throw new TypeError(`${label} source model id is invalid`);
|
|
59
64
|
if (copy.sourceEffort != null && (typeof copy.sourceEffort !== 'string' || !/^[a-z]{2,12}$/.test(copy.sourceEffort))) throw new TypeError(`${label} source effort is invalid`);
|
|
60
65
|
if (copy.sourceEnvironmentReference != null && !['captured-empty', 'private-owner-required'].includes(copy.sourceEnvironmentReference)) {
|
|
61
66
|
throw new TypeError(`${label} source environment reference is invalid`);
|
|
62
67
|
}
|
|
68
|
+
// WP-2: the filter receipt is a fixed, versioned capability claim, never free text. It is either
|
|
69
|
+
// absent/null (no such proof) or EXACTLY the one constant the local proxy filter issues; any other
|
|
70
|
+
// value or shape is refused here so a persisted row can never invent one.
|
|
71
|
+
if (copy.historyToolFilterReceipt != null && copy.historyToolFilterReceipt !== CODEX_HISTORICAL_TOOLS_FILTER_RECEIPT) {
|
|
72
|
+
throw new TypeError(`${label}.historyToolFilterReceipt is not a known receipt`);
|
|
73
|
+
}
|
|
63
74
|
if (copy.approvalPolicy != null && !['never', 'on-request', 'on-failure', 'untrusted'].includes(copy.approvalPolicy)) throw new TypeError(`${label}.approvalPolicy is invalid`);
|
|
64
75
|
if (copy.origin != null && !['native-codex', 'native-claude', 'routed-proxy', 'portable-transcript', 'unknown'].includes(copy.origin)) throw new TypeError(`${label}.origin is invalid`);
|
|
76
|
+
// WP-0 D-1: the journaled history mode is one of exactly two spellings. ABSENT is valid and reads
|
|
77
|
+
// as 'present' (openHistoryMode), so every recipe written before this change stays loadable; an
|
|
78
|
+
// unknown spelling is refused here rather than being silently read back as 'present'.
|
|
79
|
+
if (copy.historyMode != null && !HISTORY_MODES.includes(copy.historyMode)) throw new TypeError(`${label}.historyMode is invalid`);
|
|
80
|
+
// WP-2 D-6 (§5/§6): the source recipe records WHERE the source model came from, in exactly two
|
|
81
|
+
// spellings. ABSENT stays valid (a recipe written before this change reads as 'observed'); a third
|
|
82
|
+
// spelling is refused here rather than being read back as 'observed'.
|
|
83
|
+
if (copy.modelOrigin != null && !MODEL_ORIGINS.includes(copy.modelOrigin)) throw new TypeError(`${label}.modelOrigin is invalid`);
|
|
65
84
|
if (copy.routeTransitionId != null && !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(copy.routeTransitionId)) throw new TypeError(`${label}.routeTransitionId is invalid`);
|
|
66
85
|
if (copy.cyberAccessProgram != null && (typeof copy.cyberAccessProgram !== 'string' || copy.cyberAccessProgram.length > 128)) throw new TypeError(`${label}.cyberAccessProgram is invalid`);
|
|
67
86
|
for (const key of ['factsRevision', 'metadataRevision']) {
|
|
@@ -79,7 +98,7 @@ function validateRecipe(value, sessionId, generation, label) {
|
|
|
79
98
|
if (compactionMetadataPresent && (!qualifiedCompactionValue
|
|
80
99
|
|| copy.compactHashSchemaReceipt !== CODEX_COMPACTION_SCHEMA_RECEIPT
|
|
81
100
|
|| !/^[a-f0-9]{64}$/.test(String(copy.catalogSha256 || ''))
|
|
82
|
-
|| copy.harness !== 'codex-cli' || !
|
|
101
|
+
|| copy.harness !== 'codex-cli' || !(typeof copy.harnessVersion === 'string' && copy.harnessVersion.trim())
|
|
83
102
|
|| !/^[a-f0-9]{64}$/.test(String(copy.profileFingerprint || '')))) {
|
|
84
103
|
throw new TypeError(`${label} compaction absence lacks a qualified generated-catalog receipt`);
|
|
85
104
|
}
|
|
@@ -116,8 +135,7 @@ function validateTransitionRequest(value) {
|
|
|
116
135
|
}
|
|
117
136
|
const modelId = value.modelId;
|
|
118
137
|
const effort = value.effort;
|
|
119
|
-
if (
|
|
120
|
-
|| !/^[A-Za-z0-9][A-Za-z0-9._:/@+\-\[\]]*$/.test(modelId)) throw new TypeError('transition request modelId is invalid');
|
|
138
|
+
if (!isValidOverrideModel(modelId)) throw new TypeError('transition request modelId is invalid');
|
|
121
139
|
if (effort !== null && effort !== undefined && effort !== ''
|
|
122
140
|
&& (typeof effort !== 'string' || !/^[a-z]{2,12}$/.test(effort))) throw new TypeError('transition request effort is invalid');
|
|
123
141
|
if (!['session', 'session+default'].includes(value.scope) || typeof value.effortOnly !== 'boolean') {
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-1 (docs/lead-route-model-invocability-preflight-2026-10-28.html §5 D-1/D-2, §6.2).
|
|
4
|
+
//
|
|
5
|
+
// A routed Lead switch used to validate only that the route resolves and the model is in the
|
|
6
|
+
// catalog, then parked the running session and relaunched it — learning on the FIRST turn that the
|
|
7
|
+
// route cannot run the model (live 10-28: proxy log
|
|
8
|
+
// `upstream_outcome=model_not_invocable upstream_status=400 retryable=0`). This module runs the
|
|
9
|
+
// proxy's EXISTING one-message generation probe against (route, model) while the switch is still
|
|
10
|
+
// being validated, before any park, and refuses the switch on a non-retryable verdict.
|
|
11
|
+
//
|
|
12
|
+
// The readiness steps mirror lib/session-create/worker-continuation.js's probeWorkerRoute (health
|
|
13
|
+
// wait, route push, then the probe) because that is the one proven order: a freshly-(re)started
|
|
14
|
+
// proxy has an empty route table, so the route push has to precede the probe.
|
|
15
|
+
//
|
|
16
|
+
// Fail-open by construction (D-2): a transport failure, a timeout, a retryable verdict, an
|
|
17
|
+
// admin-channel error, a thrown probe or an unready proxy all return `{ refusal: null }` and the
|
|
18
|
+
// switch proceeds exactly as today — the existing first-turn path still guards it.
|
|
19
|
+
//
|
|
20
|
+
// Dependency-injected, like the other *-runtime factories: nothing is looked up by name at module
|
|
21
|
+
// load, so requiring this module stays inert and a test can supply its own stubs.
|
|
22
|
+
|
|
23
|
+
const { normalizeUpstreamOutcome, SAFE_MESSAGES } = require('./model-proxy/upstream-outcome');
|
|
24
|
+
const { READINESS_BUDGET_MS } = require('./session-create/proxy-route-readiness');
|
|
25
|
+
|
|
26
|
+
const PROBE_TIMEOUT_MS = 30_000;
|
|
27
|
+
// One bounded diagnostic vocabulary: every string a diagnostic carries is capped here, so a
|
|
28
|
+
// provider-supplied value can never grow the diagnostic sink.
|
|
29
|
+
const DIAGNOSTIC_TEXT_MAX = 120;
|
|
30
|
+
const REPORT_ERROR_MAX = 200;
|
|
31
|
+
const PICK_ANOTHER = ' Pick another route or model.';
|
|
32
|
+
|
|
33
|
+
// The switch's reason vocabulary (§6.2). Every non-retryable kind maps to a reason; only an access
|
|
34
|
+
// denial gets its own (the user's fix differs: ask the account owner, not "try a different id").
|
|
35
|
+
const REFUSAL_REASONS = Object.freeze({
|
|
36
|
+
model_access_denied: 'target_model_access_denied',
|
|
37
|
+
model_not_invocable: 'target_model_not_invocable',
|
|
38
|
+
model_not_found: 'target_model_not_invocable',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// §12a rule 3: refuse ONLY on a kind that proves the pair cannot run the model. Every other kind --
|
|
42
|
+
// `invalid_prompt` above all -- is refused by nothing, because the probe's own fixed 16-token
|
|
43
|
+
// request can be what the upstream disliked; the live first turn is the authority for those.
|
|
44
|
+
const REFUSING_KINDS = new Set(Object.keys(REFUSAL_REASONS));
|
|
45
|
+
|
|
46
|
+
function _bound(value) {
|
|
47
|
+
return String(value == null ? '' : value).slice(0, DIAGNOSTIC_TEXT_MAX);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function createLeadRoutePreflight({ getSupervisor, getReadiness, report, diagnostic, now = Date.now } = {}) {
|
|
51
|
+
// The D-3 report is FIRE-AND-FORGET by contract: it is a health hint for the picker, never an
|
|
52
|
+
// input to the switch. It is never awaited, a synchronous throw is swallowed the same way as a
|
|
53
|
+
// rejection, and a failure is DIAGNOSED (REPORT disposition) rather than raised.
|
|
54
|
+
function reportHealth(sessionId, body) {
|
|
55
|
+
if (typeof report !== 'function') return;
|
|
56
|
+
const failed = (error) => {
|
|
57
|
+
try {
|
|
58
|
+
if (typeof diagnostic !== 'function') return;
|
|
59
|
+
diagnostic(sessionId, 'lead-route-health-report-failed', {
|
|
60
|
+
message: String((error && error.message) || error).slice(0, REPORT_ERROR_MAX),
|
|
61
|
+
});
|
|
62
|
+
} catch { /* expected: a diagnostic sink failure must never change the switch outcome */ }
|
|
63
|
+
};
|
|
64
|
+
let pending;
|
|
65
|
+
try {
|
|
66
|
+
pending = report(body);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
failed(error);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (pending && typeof pending.then === 'function') Promise.resolve(pending).catch(failed);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Exactly ONE diagnostic per invocation, with the bounded vocabulary §6.2 names.
|
|
75
|
+
function recordDiagnostic(sessionId, detail) {
|
|
76
|
+
try {
|
|
77
|
+
if (typeof diagnostic !== 'function') return;
|
|
78
|
+
diagnostic(sessionId, 'lead-route-preflight', {
|
|
79
|
+
route_id: _bound(detail.routeId),
|
|
80
|
+
model: _bound(detail.modelId),
|
|
81
|
+
ok: detail.ok === true,
|
|
82
|
+
kind: detail.kind == null ? null : _bound(detail.kind),
|
|
83
|
+
// §12a rule 1: which wire the verdict is about -- a verdict on the wrong wire is not a
|
|
84
|
+
// verdict, and the one line has to say which one it was taken on.
|
|
85
|
+
wire: detail.wire == null ? null : _bound(detail.wire),
|
|
86
|
+
http_status: Number.isInteger(detail.httpStatus) ? detail.httpStatus : null,
|
|
87
|
+
probe_ms: Number.isFinite(detail.probeMs) ? Math.max(0, Math.round(detail.probeMs)) : null,
|
|
88
|
+
...(detail.skipped ? { skipped: _bound(detail.skipped) } : {}),
|
|
89
|
+
});
|
|
90
|
+
} catch { /* expected: a diagnostic sink failure must never change the switch outcome */ }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// A skipped preflight is not a verdict: the switch proceeds and the skip is diagnosed so the
|
|
94
|
+
// missing evidence is visible (the same disposition the delegate path gives an unready proxy).
|
|
95
|
+
function skip(sessionId, routeId, modelId, skipped) {
|
|
96
|
+
recordDiagnostic(sessionId, { routeId, modelId, ok: false, skipped, probeMs: null });
|
|
97
|
+
return { refusal: null };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return async function preflight({ routeId, modelId, sessionId, signal, face } = {}) {
|
|
101
|
+
// 1. The proxy supervisor is the only process that can run the probe (it holds the route's
|
|
102
|
+
// credentials and CA). A host without one has no probe to run and no verdict to give.
|
|
103
|
+
let supervisor = null;
|
|
104
|
+
try {
|
|
105
|
+
supervisor = typeof getSupervisor === 'function' ? getSupervisor() : null;
|
|
106
|
+
} catch { return { refusal: null }; /* expected: an unreadable supervisor is no probe */ }
|
|
107
|
+
if (!supervisor || typeof supervisor.probeRouteGeneration !== 'function') return { refusal: null };
|
|
108
|
+
|
|
109
|
+
// 2/3. The same readiness order the delegate path uses: healthy proxy, then route push.
|
|
110
|
+
try {
|
|
111
|
+
if (typeof supervisor.isRunning === 'function' && !supervisor.isRunning()) {
|
|
112
|
+
const started = supervisor.start();
|
|
113
|
+
if (!started || started.ok === false) return skip(sessionId, routeId, modelId, 'proxy_unready');
|
|
114
|
+
}
|
|
115
|
+
const readiness = typeof getReadiness === 'function' ? getReadiness() : null;
|
|
116
|
+
await readiness.waitForProxyHealth(READINESS_BUDGET_MS, signal);
|
|
117
|
+
await readiness.pushRoutesIfNeeded(routeId, signal);
|
|
118
|
+
} catch { return skip(sessionId, routeId, modelId, 'proxy_unready'); }
|
|
119
|
+
|
|
120
|
+
// 4. The probe itself, honouring the request's own cancellation.
|
|
121
|
+
const startedAt = now();
|
|
122
|
+
let result;
|
|
123
|
+
try {
|
|
124
|
+
result = await supervisor.probeRouteGeneration(routeId, { model: modelId, timeoutMs: PROBE_TIMEOUT_MS, face });
|
|
125
|
+
} catch { return skip(sessionId, routeId, modelId, 'probe_threw'); }
|
|
126
|
+
const probeMs = now() - startedAt;
|
|
127
|
+
const httpStatus = Number.isInteger(result && result.http_status) ? result.http_status : null;
|
|
128
|
+
const succeeded = !!(result && result.generation_succeeded === true);
|
|
129
|
+
const outcome = (result && result.failure && result.failure.upstream_outcome) || null;
|
|
130
|
+
const kind = outcome ? outcome.kind : null;
|
|
131
|
+
const wire = (result && typeof result.probe_wire === 'string') ? result.probe_wire : null;
|
|
132
|
+
const diagnostic = { routeId, modelId, ok: succeeded, kind, wire, httpStatus, probeMs };
|
|
133
|
+
// §12a rule 4: a verdict is only wire-independent when the route offers exactly one wire -- on a
|
|
134
|
+
// multi-wire route the same pair can be invocable on the wire the probe dialled and dead on the
|
|
135
|
+
// one another harness uses, so this switch keeps the refusal local and reports nothing.
|
|
136
|
+
const reportable = !!result && result.route_wire_count === 1;
|
|
137
|
+
|
|
138
|
+
// A superseded request is the caller's to refuse: this path never reports and never refuses on its
|
|
139
|
+
// own. The probe DID run and DID produce a verdict, so the one diagnostic is still recorded (D-2
|
|
140
|
+
// "always record ONE diagnostic") — the verdict is evidence even when nothing acts on it.
|
|
141
|
+
if (signal && signal.aborted) {
|
|
142
|
+
recordDiagnostic(sessionId, diagnostic);
|
|
143
|
+
return { refusal: null };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (succeeded) {
|
|
147
|
+
if (reportable) reportHealth(sessionId, { route_id: routeId, model: modelId, ok: true, outcome: null, http_status: httpStatus });
|
|
148
|
+
recordDiagnostic(sessionId, diagnostic);
|
|
149
|
+
return { refusal: null };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (kind && REFUSING_KINDS.has(kind)) {
|
|
153
|
+
// §12a rule 3: only a kind that proves the pair cannot run the model refuses. A non-retryable
|
|
154
|
+
// `invalid_prompt` (the probe's own 16-token shape, an upstream that dislikes it) does NOT:
|
|
155
|
+
// it falls through to the proceed path below.
|
|
156
|
+
if (reportable) {
|
|
157
|
+
// §12a rule 4: the pair-class verdict is recorded with the status and the kind, exactly as
|
|
158
|
+
// before -- but only when it holds for every harness the route serves.
|
|
159
|
+
reportHealth(sessionId, { route_id: routeId, model: modelId, ok: false, outcome: _bound(kind), http_status: httpStatus });
|
|
160
|
+
}
|
|
161
|
+
recordDiagnostic(sessionId, diagnostic);
|
|
162
|
+
return {
|
|
163
|
+
refusal: {
|
|
164
|
+
ok: false,
|
|
165
|
+
reason: REFUSAL_REASONS[kind],
|
|
166
|
+
message: (SAFE_MESSAGES[kind] || SAFE_MESSAGES.invalid_prompt) + PICK_ANOTHER,
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Retryable, no upstream outcome, `http_status: null`, an HTTP-level admin error
|
|
172
|
+
// ({error:'route_unknown'}), or a non-retryable kind that only indicts the probe's own request:
|
|
173
|
+
// no report (a probe is not a load signal) and no refusal.
|
|
174
|
+
recordDiagnostic(sessionId, diagnostic);
|
|
175
|
+
return { refusal: null };
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
module.exports = { createLeadRoutePreflight };
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WP-0/WP-1 (docs/lead-switch-never-prompted-session-2026-10-30.html §5 D-1..D-4, §6): ONE history
|
|
4
|
+
// mode per transition — 'none' (this source lifetime has nothing to carry) or 'present' (today's
|
|
5
|
+
// behaviour) — decided ONCE before the switch claims its transition, carried on both recipes, and
|
|
6
|
+
// read back by every gate that used to re-derive (or silently assume) the answer.
|
|
7
|
+
//
|
|
8
|
+
// D-2, the rule that keeps this safe: 'none' is returned ONLY for a DEFINITE absence. The oracle is
|
|
9
|
+
// the same off-thread resume ladder park already consults (lib/park-fresh-relaunch.js
|
|
10
|
+
// `parkFindsNothingToResume`); a false answer, a thrown error, a timeout or an agent with no ladder
|
|
11
|
+
// all mean 'present'. "An unreadable store is not evidence that the conversation is gone"
|
|
12
|
+
// (superset-sh/superset#7144, adopted in §3 P-3), so a flaky ladder can only cost a refusal, never a
|
|
13
|
+
// lost conversation. A recipe written before this change carries no `historyMode` key and reads as
|
|
14
|
+
// 'present' — byte-for-byte today's path.
|
|
15
|
+
|
|
16
|
+
const { extractResumeTarget } = require('./session-history');
|
|
17
|
+
const { parkFindsNothingToResume } = require('./park-fresh-relaunch');
|
|
18
|
+
|
|
19
|
+
const HISTORY_MODES = Object.freeze(['none', 'present']);
|
|
20
|
+
// WP-2 D-6 (§5/§6): the only two spellings a source recipe's `modelOrigin` may carry.
|
|
21
|
+
const MODEL_ORIGINS = Object.freeze(['observed', 'cli-default']);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the SOURCE lifetime's history mode. NEVER throws (D-2): any error, rejection or timeout is
|
|
25
|
+
* 'present'.
|
|
26
|
+
*
|
|
27
|
+
* @param {object} input
|
|
28
|
+
* @param {'claude'|'codex'|string} input.agentType
|
|
29
|
+
* @param {string} input.ctmSessionId
|
|
30
|
+
* @param {string} input.jsonlPath claude: the transcript path park would stat; codex: the rollout path or ''
|
|
31
|
+
* @param {string} [input.resumeId] the source provider thread id
|
|
32
|
+
* @param {boolean} [input.resumeEvidence] true when park's own inputs already found a transcript/rollout ON DISK
|
|
33
|
+
* @param {(query: object) => Promise<{ rung?: string }>} input.resolveRung resolveResumeRungOffThread bound to dbPath
|
|
34
|
+
* @returns {Promise<'none'|'present'>}
|
|
35
|
+
*/
|
|
36
|
+
async function resolveSourceHistoryMode({ agentType, ctmSessionId, jsonlPath, resumeId, resumeEvidence = false, resolveRung } = {}) {
|
|
37
|
+
try {
|
|
38
|
+
// A transcript/rollout file already on disk is proof there IS something to resume, so the ladder
|
|
39
|
+
// is never consulted for it (park short-circuits the same way, lib/park-fresh-relaunch.js).
|
|
40
|
+
if (resumeEvidence === true) return 'present';
|
|
41
|
+
const nothingToResume = await parkFindsNothingToResume({ agentType, ctmSessionId, jsonlPath, resumeId, resolveRung });
|
|
42
|
+
return nothingToResume === true ? 'none' : 'present';
|
|
43
|
+
} catch {
|
|
44
|
+
// HANDLE: D-2 — an unreadable store, a rejected ladder or a timeout is never evidence of absence,
|
|
45
|
+
// so the refusal side of every gate keeps today's behaviour.
|
|
46
|
+
return 'present';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Recipe read with the D-2 default: a missing or unknown key is 'present'. */
|
|
51
|
+
function recipeHistoryMode(recipe) {
|
|
52
|
+
return recipe?.historyMode === 'none' ? 'none' : 'present';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* WP-2 D-6 (§5/§6): WHERE the source lifetime's model came from — recorded, never guessed.
|
|
57
|
+
*
|
|
58
|
+
* 'cli-default' means this source never named a model of its own: the mode is 'none' (nothing was
|
|
59
|
+
* ever prompted) AND `cliDefaultModel` (captured by the runtime's `getSessionInfo`) proved an empty
|
|
60
|
+
* `session.model_id`, no `--model`/`-m`/`--model=` in the captured launch argv and no
|
|
61
|
+
* `session_lead_model` pin row. Rolling such a source back must relaunch the ORIGINAL argv and pass
|
|
62
|
+
* NO one-shot model override at all, so the CLI's own default applies by construction (D-6 rejected
|
|
63
|
+
* both substituting `cli_default` as `--model` and re-reading the CLI's config file).
|
|
64
|
+
*
|
|
65
|
+
* Everything else is 'observed' — today's meaning: the model was read from the session/argv, and a
|
|
66
|
+
* rollback passes it as the one-shot override exactly as before. A recipe written before this change
|
|
67
|
+
* carries no `cliDefaultModel`, so it reads 'observed'.
|
|
68
|
+
*/
|
|
69
|
+
function sourceModelOrigin(source) {
|
|
70
|
+
return recipeHistoryMode(source) === 'none' && source?.cliDefaultModel === true ? 'cli-default' : 'observed';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The value of a Claude argv's `--session-id <id>` / `--session-id=<id>`, or '' when absent. */
|
|
74
|
+
function claudeSessionIdArg(args) {
|
|
75
|
+
const list = Array.isArray(args) ? args.map(String) : [];
|
|
76
|
+
for (let i = 0; i < list.length; i += 1) {
|
|
77
|
+
if (list[i] === '--session-id') return String(list[i + 1] || '');
|
|
78
|
+
if (list[i].startsWith('--session-id=')) return list[i].slice('--session-id='.length);
|
|
79
|
+
}
|
|
80
|
+
return '';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The single thread clause shared by the pre-park planner check (D-4) and authorizeLiveTargetRestore
|
|
85
|
+
* (D-3) — one definition, so the two gates can never drift apart (§7).
|
|
86
|
+
*
|
|
87
|
+
* For 'present' it is EXACTLY the historical conjunct: the plan must resume the target recipe's own
|
|
88
|
+
* provider thread. For 'none' the plan must carry no resume selector at all, and a Claude plan's own
|
|
89
|
+
* `--session-id` must name the target recipe's thread: that argv is what the fresh process is given,
|
|
90
|
+
* so it is the thread its canonical owner will report.
|
|
91
|
+
*
|
|
92
|
+
* @returns {boolean}
|
|
93
|
+
*/
|
|
94
|
+
function targetThreadClause({ source, target, launchPlan } = {}) {
|
|
95
|
+
const planThread = String(launchPlan?.resume?.sessionId || '');
|
|
96
|
+
const targetThread = String(target?.providerThreadId || '');
|
|
97
|
+
if (recipeHistoryMode(target) === 'present') return targetThread.length > 0 && planThread === targetThread;
|
|
98
|
+
// A 'none' target recipe is only meaningful with a 'none' SOURCE: the mode is one fact per
|
|
99
|
+
// transition, recorded twice.
|
|
100
|
+
if (recipeHistoryMode(source) !== 'none') return false;
|
|
101
|
+
if (planThread !== '') return false;
|
|
102
|
+
if (extractResumeTarget(launchPlan?.agentType, launchPlan?.args || [])) return false;
|
|
103
|
+
if (launchPlan?.agentType === 'claude') return targetThread.length > 0 && claudeSessionIdArg(launchPlan.args) === targetThread;
|
|
104
|
+
// codex: the new thread id is unknown until the thread links, so the owner check after launch
|
|
105
|
+
// binds it instead.
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = { HISTORY_MODES, MODEL_ORIGINS, resolveSourceHistoryMode, recipeHistoryMode,
|
|
110
|
+
sourceModelOrigin, claudeSessionIdArg, targetThreadClause };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// The CLI wire a lead agent speaks to CTM's model proxy. OpenCode leads and OpenCode's routed
|
|
3
|
+
// workers both use the Anthropic Messages face, so route servability is asked of 'claude'.
|
|
4
|
+
// This is the ONE rule: public/js/session-model-picker.js mirrors it as `leadWireAgent` for the
|
|
5
|
+
// Lead AND Delegate tabs, and tests pin the two bodies equal (docs/opencode-model-selection-round-2-shared-route-logic-2026-10-30.html §5 R-1, §6.1, §7).
|
|
6
|
+
function leadWireAgent(agent) { return agent === 'opencode' ? 'claude' : agent; }
|
|
7
|
+
module.exports = { leadWireAgent };
|