create-walle 0.10.2 → 0.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/lib/release-schema.json +2 -2
- package/lib/runtime-closure-policy.json +8 -7
- package/package.json +1 -1
- package/template/coding-task-manager/api-prompts.js +45 -7
- package/template/coding-task-manager/bin/claude-hook.js +55 -1
- package/template/coding-task-manager/bin/ctm-relay-client.js +35 -12
- package/template/coding-task-manager/bin/land.js +2069 -0
- package/template/coding-task-manager/bin/memory-census.js +4 -2
- package/template/coding-task-manager/bin/memory-sync-verify.js +151 -0
- package/template/coding-task-manager/bin/probe-refusal-scope.js +530 -0
- package/template/coding-task-manager/db.js +156 -32
- package/template/coding-task-manager/lib/acp/acp-client.js +383 -0
- package/template/coding-task-manager/lib/acp/dsh-context-truth.js +176 -0
- package/template/coding-task-manager/lib/acp/dsh-launch-model.js +302 -0
- package/template/coding-task-manager/lib/acp/dsh-mcp-servers.js +194 -0
- package/template/coding-task-manager/lib/acp/dsh-prompt-lane.js +616 -0
- package/template/coding-task-manager/lib/acp/dsh-route-patch.js +308 -0
- package/template/coding-task-manager/lib/acp/dsh-session-runtime.js +522 -0
- package/template/coding-task-manager/lib/acp/dsh-transcript.js +395 -0
- package/template/coding-task-manager/lib/acp/dsh-usage-log.js +162 -0
- package/template/coding-task-manager/lib/acp/ndjson-stdio-transport.js +225 -0
- package/template/coding-task-manager/lib/adopted-host-redial.js +47 -11
- package/template/coding-task-manager/lib/adopted-launch-environment.js +123 -0
- package/template/coding-task-manager/lib/agent-auto-update-runtime.js +0 -3
- package/template/coding-task-manager/lib/agent-auto-update.js +26 -10
- package/template/coding-task-manager/lib/agent-bundle/build.js +657 -0
- package/template/coding-task-manager/lib/agent-bundle/codex-install.js +558 -0
- package/template/coding-task-manager/lib/agent-bundle/drift.js +120 -0
- package/template/coding-task-manager/lib/agent-bundle/gc.js +522 -0
- package/template/coding-task-manager/lib/agent-bundle/inventory.js +346 -0
- package/template/coding-task-manager/lib/agent-bundle/push.js +717 -0
- package/template/coding-task-manager/lib/agent-bundle/skill-transfer.js +724 -0
- package/template/coding-task-manager/lib/agent-bundle/spawn.js +119 -0
- package/template/coding-task-manager/lib/agent-bundle/store.js +568 -0
- package/template/coding-task-manager/lib/agent-capabilities.js +142 -12
- package/template/coding-task-manager/lib/agent-cli-cache.js +30 -5
- package/template/coding-task-manager/lib/agent-definitions.js +19 -0
- package/template/coding-task-manager/lib/agent-hooks-codex-toml.js +28 -16
- package/template/coding-task-manager/lib/agent-hooks-codex-workspace.js +9 -1
- package/template/coding-task-manager/lib/agent-hooks-installer.js +23 -8
- package/template/coding-task-manager/lib/agent-hooks-prune.js +37 -11
- package/template/coding-task-manager/lib/agent-hooks-shim.js +105 -0
- package/template/coding-task-manager/lib/agent-presets.js +84 -1
- package/template/coding-task-manager/lib/agent-upgrade-jobs.js +12 -1
- package/template/coding-task-manager/lib/agent-version-service.js +74 -11
- package/template/coding-task-manager/lib/all-sessions-query.js +6 -2
- package/template/coding-task-manager/lib/api/agent-bundles.js +436 -0
- package/template/coding-task-manager/lib/api/backup-retention.js +83 -0
- package/template/coding-task-manager/lib/api/conversation-sync-routes.js +124 -0
- package/template/coding-task-manager/lib/api/diagnostics.js +549 -69
- package/template/coding-task-manager/lib/api/fleet.js +118 -0
- package/template/coding-task-manager/lib/api/hub-bridge.js +243 -0
- package/template/coding-task-manager/lib/api/ingress.js +9 -1
- package/template/coding-task-manager/lib/api/landing.js +403 -0
- package/template/coding-task-manager/lib/api/machines.js +691 -2
- package/template/coding-task-manager/lib/api/memory-sync-routes.js +335 -0
- package/template/coding-task-manager/lib/api/misc-ops.js +8 -3
- package/template/coding-task-manager/lib/api/remote-sessions.js +1326 -0
- package/template/coding-task-manager/lib/api/session-attach.js +623 -0
- package/template/coding-task-manager/lib/api/session-messages.js +1384 -0
- package/template/coding-task-manager/lib/api/sessions.js +77 -51
- package/template/coding-task-manager/lib/api/setup-network.js +544 -16
- package/template/coding-task-manager/lib/api/setup-wizard-handlers.js +884 -0
- package/template/coding-task-manager/lib/api/setup.js +829 -10
- package/template/coding-task-manager/lib/api/walle-bridge.js +9 -1
- package/template/coding-task-manager/lib/attachment-reconciler-wiring.js +5 -2
- package/template/coding-task-manager/lib/auth-rate-limit.js +23 -0
- package/template/coding-task-manager/lib/auth-rules.js +292 -21
- package/template/coding-task-manager/lib/background-stop.js +369 -0
- package/template/coding-task-manager/lib/best-of-attempts.js +4 -1
- package/template/coding-task-manager/lib/best-of-launch-plan.js +52 -0
- package/template/coding-task-manager/lib/box-keep-awake.js +89 -0
- package/template/coding-task-manager/lib/broadcast-reason-stats.js +152 -0
- package/template/coding-task-manager/lib/broadcast-serialize.js +5 -0
- package/template/coding-task-manager/lib/build-info.js +4 -2
- package/template/coding-task-manager/lib/claude-model-truth.js +13 -2
- package/template/coding-task-manager/lib/claude-transcript-terminal-renderer.js +17 -13
- package/template/coding-task-manager/lib/client-diag-handler.js +64 -2
- package/template/coding-task-manager/lib/codex-app-server-client.js +14 -16
- package/template/coding-task-manager/lib/codex-configuration-history.js +55 -0
- package/template/coding-task-manager/lib/codex-harness-qualification.js +43 -20
- package/template/coding-task-manager/lib/codex-rollout-history.js +743 -0
- package/template/coding-task-manager/lib/coding-agent-models.js +27 -12
- package/template/coding-task-manager/lib/coding-cli-skill-installer.js +44 -14
- package/template/coding-task-manager/lib/coding-session-model-adapters.js +43 -2
- package/template/coding-task-manager/lib/coding-session-model-owner.js +1 -1
- package/template/coding-task-manager/lib/coding-session-model-public.js +7 -1
- package/template/coding-task-manager/lib/coding-session-model-runtime.js +23 -4
- package/template/coding-task-manager/lib/coding-session-model-scan.js +31 -0
- package/template/coding-task-manager/lib/coding-session-model-state.js +7 -0
- package/template/coding-task-manager/lib/conversation-freshness-diagnostics.js +280 -0
- package/template/coding-task-manager/lib/conversation-item-clock.js +261 -0
- package/template/coding-task-manager/lib/conversation-source-registry.js +5 -0
- package/template/coding-task-manager/lib/conversation-store-v2.js +44 -0
- package/template/coding-task-manager/lib/conversation-sync.js +355 -0
- package/template/coding-task-manager/lib/create/resolve-launch-params.js +96 -12
- package/template/coding-task-manager/lib/create/worker-role-profile.js +4 -4
- package/template/coding-task-manager/lib/create-intent-lease.js +51 -0
- package/template/coding-task-manager/lib/ctm-session-context-api.js +97 -6
- package/template/coding-task-manager/lib/db-domain-tables.js +1 -0
- package/template/coding-task-manager/lib/db-domains.js +1 -0
- package/template/coding-task-manager/lib/db-owner-dispatch-probe.js +32 -0
- package/template/coding-task-manager/lib/db-owner-request-counts.js +178 -0
- package/template/coding-task-manager/lib/db-owner-worker-client.js +68 -14
- package/template/coding-task-manager/lib/db-schema-source-files.js +5 -0
- package/template/coding-task-manager/lib/deferred-restore-attempt.js +6 -2
- package/template/coding-task-manager/lib/feature-manifest.js +4 -0
- package/template/coding-task-manager/lib/fleet-conversation-route.js +97 -0
- package/template/coding-task-manager/lib/fleet-fanout.js +78 -0
- package/template/coding-task-manager/lib/fleet-hub.js +762 -0
- package/template/coding-task-manager/lib/fleet-origin-follower.js +252 -0
- package/template/coding-task-manager/lib/fleet-resolver-effects.js +147 -0
- package/template/coding-task-manager/lib/fleet-resolver-tick.js +148 -0
- package/template/coding-task-manager/lib/fleet-resolver.js +454 -0
- package/template/coding-task-manager/lib/fleet-session-list-routes.js +114 -0
- package/template/coding-task-manager/lib/fleet-session-lists.js +672 -0
- package/template/coding-task-manager/lib/fleet-update.js +798 -0
- package/template/coding-task-manager/lib/fs-watcher-root-watch.js +141 -0
- package/template/coding-task-manager/lib/fs-watcher.js +58 -42
- package/template/coding-task-manager/lib/harness-history-transition.js +50 -9
- package/template/coding-task-manager/lib/headless-term-service.js +84 -8
- package/template/coding-task-manager/lib/hermetic-agent-launch-guard.js +10 -0
- package/template/coding-task-manager/lib/history-transition-read-op.js +175 -8
- package/template/coding-task-manager/lib/host-cpu-categories.js +310 -0
- package/template/coding-task-manager/lib/host-fseventsd-sampler.js +399 -0
- package/template/coding-task-manager/lib/host-handoff.js +16 -19
- package/template/coding-task-manager/lib/host-stats.js +150 -3
- package/template/coding-task-manager/lib/hosted-sync-worker.js +43 -320
- package/template/coding-task-manager/lib/hub-bridge-access.js +798 -0
- package/template/coding-task-manager/lib/hub-bridge-box.js +409 -0
- package/template/coding-task-manager/lib/hub-bridge-mcp.js +380 -0
- package/template/coding-task-manager/lib/hub-bridge-mesh.js +313 -0
- package/template/coding-task-manager/lib/hub-bridge-ops.js +721 -0
- package/template/coding-task-manager/lib/hub-bridge.js +398 -0
- package/template/coding-task-manager/lib/idle-notify.js +491 -0
- package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +8 -0
- package/template/coding-task-manager/lib/instance-manifest.js +5 -1
- package/template/coding-task-manager/lib/instance-sync-status.js +18 -8
- package/template/coding-task-manager/lib/landing/agent-rule.txt +1 -0
- package/template/coding-task-manager/lib/landing/batch-runner.js +366 -0
- package/template/coding-task-manager/lib/landing/config.js +536 -0
- package/template/coding-task-manager/lib/landing/ctm-view.js +256 -0
- package/template/coding-task-manager/lib/landing/gate.js +360 -0
- package/template/coding-task-manager/lib/landing/git.js +344 -0
- package/template/coding-task-manager/lib/landing/lease.js +247 -0
- package/template/coding-task-manager/lib/landing/merge-delta.js +380 -0
- package/template/coding-task-manager/lib/landing/proof.js +223 -0
- package/template/coding-task-manager/lib/landing/queue.js +998 -0
- package/template/coding-task-manager/lib/landing/radar.js +489 -0
- package/template/coding-task-manager/lib/landing/resolve-cli.js +424 -0
- package/template/coding-task-manager/lib/landing/state.js +263 -0
- package/template/coding-task-manager/lib/landing/stop.js +49 -0
- package/template/coding-task-manager/lib/landing/sync-cli.js +475 -0
- package/template/coding-task-manager/lib/landing/sync.js +456 -0
- package/template/coding-task-manager/lib/landing/train.js +885 -0
- package/template/coding-task-manager/lib/landing/window.js +57 -0
- package/template/coding-task-manager/lib/launch-environment.js +121 -0
- package/template/coding-task-manager/lib/launch-model-override.js +36 -10
- package/template/coding-task-manager/lib/launch-plan-pool.js +2 -1
- package/template/coding-task-manager/lib/launch-plan-warm-cache.js +6 -0
- package/template/coding-task-manager/lib/lead-model-observer.js +57 -1
- package/template/coding-task-manager/lib/lead-model-options.js +160 -3
- package/template/coding-task-manager/lib/lead-model-restore-reconciliation.js +106 -24
- package/template/coding-task-manager/lib/lead-model-switch-runtime.js +352 -68
- package/template/coding-task-manager/lib/lead-model-switch.js +114 -10
- package/template/coding-task-manager/lib/lead-model-transition-runtime.js +14 -2
- package/template/coding-task-manager/lib/lead-model-transition-store.js +24 -6
- package/template/coding-task-manager/lib/lead-route-preflight.js +179 -0
- package/template/coding-task-manager/lib/lead-switch-history-mode.js +110 -0
- package/template/coding-task-manager/lib/lead-wire-agent.js +7 -0
- package/template/coding-task-manager/lib/lifecycle-wake-experiment.js +9 -2
- package/template/coding-task-manager/lib/live-defer-announce.js +45 -0
- package/template/coding-task-manager/lib/machine-registry.js +750 -5
- package/template/coding-task-manager/lib/macos-capabilities.js +32 -15
- package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +433 -53
- package/template/coding-task-manager/lib/mainloop-observability.js +14 -9
- package/template/coding-task-manager/lib/media-draft-owner.js +16 -3
- package/template/coding-task-manager/lib/memory-join.js +337 -0
- package/template/coding-task-manager/lib/memory-sync-verify.js +393 -0
- package/template/coding-task-manager/lib/memory-sync.js +565 -0
- package/template/coding-task-manager/lib/migrations/v57-delegate-pref-mirror-opencode.js +68 -0
- package/template/coding-task-manager/lib/migrations/v58-subagent-launch-native-subagents.js +42 -0
- package/template/coding-task-manager/lib/migrations/v59-coding-session-model-state-dsh.js +73 -0
- package/template/coding-task-manager/lib/migrations/v60-delegate-pref-mirror-dsh.js +78 -0
- package/template/coding-task-manager/lib/migrations/v61-conversation-item-clock.js +71 -0
- package/template/coding-task-manager/lib/model-availability.js +727 -0
- package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +4 -6
- package/template/coding-task-manager/lib/model-host-receipt.js +6 -3
- package/template/coding-task-manager/lib/model-id-rule.js +19 -0
- package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
- package/template/coding-task-manager/lib/model-proxy/auto-mode-review.js +110 -23
- package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
- package/template/coding-task-manager/lib/model-proxy/generation-probe.js +60 -17
- package/template/coding-task-manager/lib/model-proxy/historical-tool-declarations.js +37 -0
- package/template/coding-task-manager/lib/model-proxy/lead-request-validation.js +1 -0
- package/template/coding-task-manager/lib/model-proxy/lead-token-admin.js +130 -0
- package/template/coding-task-manager/lib/model-proxy/refusal-scope.js +151 -0
- package/template/coding-task-manager/lib/model-proxy/request-budget-store.js +11 -2
- package/template/coding-task-manager/lib/model-proxy/tokens.js +69 -5
- package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
- package/template/coding-task-manager/lib/native-agent-model-args.js +25 -22
- package/template/coding-task-manager/lib/native-history-control.js +6 -4
- package/template/coding-task-manager/lib/native-lead-settings-client.js +44 -4
- package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +173 -36
- package/template/coding-task-manager/lib/online-backup-worker-client.js +2 -0
- package/template/coding-task-manager/lib/opencode-cli.js +480 -0
- package/template/coding-task-manager/lib/opencode-launch-model.js +471 -0
- package/template/coding-task-manager/lib/opencode-v1-plugin/model-carrier.mjs +35 -0
- package/template/coding-task-manager/lib/opencode-v2-plugin/server.js +118 -0
- package/template/coding-task-manager/lib/owner-action-manifest-data.json +1 -1
- package/template/coding-task-manager/lib/park-composer-draft.js +54 -0
- package/template/coding-task-manager/lib/park-fresh-relaunch.js +41 -10
- package/template/coding-task-manager/lib/perf-phase-trace.js +72 -0
- package/template/coding-task-manager/lib/perf-summary.js +7 -0
- package/template/coding-task-manager/lib/perf-tracker.js +63 -2
- package/template/coding-task-manager/lib/project-config.js +7 -0
- package/template/coding-task-manager/lib/prompt-purge-v2.js +52 -1
- package/template/coding-task-manager/lib/provider-login-broker.js +72 -15
- package/template/coding-task-manager/lib/provider-login-tab.js +10 -2
- package/template/coding-task-manager/lib/read-pool-client.js +89 -2
- package/template/coding-task-manager/lib/read-pool-worker-handle.js +29 -4
- package/template/coding-task-manager/lib/recent-catalog-query.js +15 -0
- package/template/coding-task-manager/lib/recent-session-catalog-response.js +67 -10
- package/template/coding-task-manager/lib/recovery-evidence.js +26 -11
- package/template/coding-task-manager/lib/relay-client.js +57 -3
- package/template/coding-task-manager/lib/relink-audit.js +34 -5
- package/template/coding-task-manager/lib/remote-machine-catalog.js +321 -26
- package/template/coding-task-manager/lib/remote-machine-create.js +576 -36
- package/template/coding-task-manager/lib/remote-machine-link.js +546 -59
- package/template/coding-task-manager/lib/remote-machine-proxy.js +143 -9
- package/template/coding-task-manager/lib/remote-session-forward.js +65 -7
- package/template/coding-task-manager/lib/remote-terminal-target.js +9 -2
- package/template/coding-task-manager/lib/restart-guard.js +18 -6
- package/template/coding-task-manager/lib/restore-seed-modes.js +87 -0
- package/template/coding-task-manager/lib/restored-scrollback-hydration-runtime.js +22 -2
- package/template/coding-task-manager/lib/resume-admission.js +17 -3
- package/template/coding-task-manager/lib/review-repo-registry.js +58 -19
- package/template/coding-task-manager/lib/root-delivery-runtime.js +2 -2
- package/template/coding-task-manager/lib/root-prompt-flow.js +1 -1
- package/template/coding-task-manager/lib/runtime-contract.js +3 -1
- package/template/coding-task-manager/lib/schema-indexes.js +1 -0
- package/template/coding-task-manager/lib/session-context-locator.js +742 -0
- package/template/coding-task-manager/lib/session-create/attachment.js +2 -2
- package/template/coding-task-manager/lib/session-create/create-phases.js +118 -0
- package/template/coding-task-manager/lib/session-create/delegate-worker.js +86 -16
- package/template/coding-task-manager/lib/session-create/dsh-session.js +930 -0
- package/template/coding-task-manager/lib/session-create/lead-route-launch.js +186 -13
- package/template/coding-task-manager/lib/session-create/native-lead-lifetime.js +55 -3
- package/template/coding-task-manager/lib/session-create/opencode-lead-provider.js +50 -0
- package/template/coding-task-manager/lib/session-create/prepared.js +162 -38
- package/template/coding-task-manager/lib/session-create/provider-process.js +160 -28
- package/template/coding-task-manager/lib/session-create/response.js +7 -7
- package/template/coding-task-manager/lib/session-create/structured-session.js +27 -0
- package/template/coding-task-manager/lib/session-create/subagent-pick-table.js +3 -1
- package/template/coding-task-manager/lib/session-create/subagent-pick.js +458 -9
- package/template/coding-task-manager/lib/session-create/transcript.js +1 -1
- package/template/coding-task-manager/lib/session-create/worker-budget-setup.js +21 -2
- package/template/coding-task-manager/lib/session-create/worker-continuation.js +7 -1
- package/template/coding-task-manager/lib/session-create/worker-launch-sandbox.js +39 -10
- package/template/coding-task-manager/lib/session-create/worker-observability.js +2 -2
- package/template/coding-task-manager/lib/session-empty.js +13 -10
- package/template/coding-task-manager/lib/session-host-manager.js +60 -0
- package/template/coding-task-manager/lib/session-jobs.js +3 -1
- package/template/coding-task-manager/lib/session-lifecycle.js +10 -4
- package/template/coding-task-manager/lib/session-model-action-preconditions.js +6 -2
- package/template/coding-task-manager/lib/session-model-intent-contract.js +11 -8
- package/template/coding-task-manager/lib/session-park.js +738 -0
- package/template/coding-task-manager/lib/session-path-grant.js +13 -4
- package/template/coding-task-manager/lib/session-status-ledger.js +106 -4
- package/template/coding-task-manager/lib/session-transport.js +17 -0
- package/template/coding-task-manager/lib/session-workspace-projection.js +54 -5
- package/template/coding-task-manager/lib/session-workspace-tracking-runtime.js +213 -1
- package/template/coding-task-manager/lib/skill-autocomplete-query.js +120 -3
- package/template/coding-task-manager/lib/skill-autocomplete.js +15 -1
- package/template/coding-task-manager/lib/sqlite-file-set.js +39 -0
- package/template/coding-task-manager/lib/standup-search-adapter.js +16 -2
- package/template/coding-task-manager/lib/state-sync/client-ledger.js +12 -0
- package/template/coding-task-manager/lib/state-sync/frame-emitter.js +319 -100
- package/template/coding-task-manager/lib/storage/coding-session-model-store.js +4 -1
- package/template/coding-task-manager/lib/storage/delegate-pref-mirror-store.js +32 -9
- package/template/coding-task-manager/lib/structured-session-boot-sweep.js +235 -0
- package/template/coding-task-manager/lib/submission-ack.js +90 -2
- package/template/coding-task-manager/lib/systemd-scope.js +115 -0
- package/template/coding-task-manager/lib/telemetry-receiver.js +39 -0
- package/template/coding-task-manager/lib/terminal-attach-geometry.js +985 -0
- package/template/coding-task-manager/lib/terminal-bootstrap.js +30 -0
- package/template/coding-task-manager/lib/terminal-draft-operation.js +357 -0
- package/template/coding-task-manager/lib/terminal-input-admission.js +17 -1
- package/template/coding-task-manager/lib/terminal-input-prepared.js +563 -0
- package/template/coding-task-manager/lib/transcript-cache-temps.js +171 -0
- package/template/coding-task-manager/lib/transcript-frame-cache.js +8 -3
- package/template/coding-task-manager/lib/transcript-source.js +36 -4
- package/template/coding-task-manager/lib/transcript-store.js +9 -0
- package/template/coding-task-manager/lib/typing-interaction-trace.js +106 -3
- package/template/coding-task-manager/lib/usage-ledger/backfill.js +40 -5
- package/template/coding-task-manager/lib/usage-ledger/client.js +4 -1
- package/template/coding-task-manager/lib/usage-ledger/dsh-parse.js +152 -0
- package/template/coding-task-manager/lib/usage-ledger/dsh-read.js +73 -0
- package/template/coding-task-manager/lib/usage-ledger/ingest.js +34 -6
- package/template/coding-task-manager/lib/usage-ledger/live.js +35 -8
- package/template/coding-task-manager/lib/usage-ledger/read.js +29 -20
- package/template/coding-task-manager/lib/usage-ledger/schema.js +61 -1
- package/template/coding-task-manager/lib/usage-ledger/task-coordinator.js +4 -2
- package/template/coding-task-manager/lib/wake-skip-reasons.js +1 -0
- package/template/coding-task-manager/lib/walle/turn.js +1 -1
- package/template/coding-task-manager/lib/walle-history-serve.js +838 -0
- package/template/coding-task-manager/lib/walle-kill-authority.js +2 -2
- package/template/coding-task-manager/lib/walle-log-rotation.js +164 -1
- package/template/coding-task-manager/lib/walle-mcp-auto-config.js +56 -2
- package/template/coding-task-manager/lib/walle-row-history.js +495 -0
- package/template/coding-task-manager/lib/walle-supervisor.js +33 -14
- package/template/coding-task-manager/lib/watch-worker-client.js +203 -0
- package/template/coding-task-manager/lib/worker-terminal-output.js +5 -2
- package/template/coding-task-manager/lib/ws/hub-bridge.js +35 -0
- package/template/coding-task-manager/lib/ws/lifecycle.js +6 -11
- package/template/coding-task-manager/lib/ws/misc.js +1 -1
- package/template/coding-task-manager/lib/ws/state-sync.js +106 -4
- package/template/coding-task-manager/lib/ws/terminal-attach.js +91 -0
- package/template/coding-task-manager/oauth-proxy.js +112 -115
- package/template/coding-task-manager/package-lock.json +10 -94
- package/template/coding-task-manager/package.json +4 -3
- package/template/coding-task-manager/protocol/message-table.json +10 -0
- package/template/coding-task-manager/protocol/messages.js +10 -0
- package/template/coding-task-manager/protocol/ws-schema.json +20 -0
- package/template/coding-task-manager/protocol/ws-schema.md +2 -0
- package/template/coding-task-manager/public/css/app.css +170 -0
- package/template/coding-task-manager/public/css/walle-session.css +4 -0
- package/template/coding-task-manager/public/index.html +473 -243
- package/template/coding-task-manager/public/js/agent-command.js +86 -0
- package/template/coding-task-manager/public/js/app/backups-panel.js +111 -5
- package/template/coding-task-manager/public/js/app/coding-agent-upgrade.js +103 -4
- package/template/coding-task-manager/public/js/app/machines-page.js +1328 -8
- package/template/coding-task-manager/public/js/app/models-tab.js +113 -38
- package/template/coding-task-manager/public/js/app/restart-banner.js +53 -0
- package/template/coding-task-manager/public/js/app/worktrees-panel.js +184 -2
- package/template/coding-task-manager/public/js/background-work.js +196 -27
- package/template/coding-task-manager/public/js/coding-session-model-presentation.js +138 -10
- package/template/coding-task-manager/public/js/core/active-session-groups.js +179 -0
- package/template/coding-task-manager/public/js/core/pending-create.js +238 -0
- package/template/coding-task-manager/public/js/core/recent-machine-source.js +228 -0
- package/template/coding-task-manager/public/js/core/session-commands.js +180 -14
- package/template/coding-task-manager/public/js/dashboard-client-function-order.json +8 -0
- package/template/coding-task-manager/public/js/document-review-links.js +9 -1
- package/template/coding-task-manager/public/js/file-resolution-client.js +52 -1
- package/template/coding-task-manager/public/js/history-app-seam.js +22 -2
- package/template/coding-task-manager/public/js/machine-pool-shared.js +558 -3
- package/template/coding-task-manager/public/js/message-renderer.js +4 -3
- package/template/coding-task-manager/public/js/model-picker.js +3 -1
- package/template/coding-task-manager/public/js/native-subagent-review.js +15 -2
- package/template/coding-task-manager/public/js/new-session-agent-grid.js +346 -0
- package/template/coding-task-manager/public/js/pasted-content.js +236 -0
- package/template/coding-task-manager/public/js/pe-private-content-lifetime.js +39 -7
- package/template/coding-task-manager/public/js/private-model-lifetime.js +6 -2
- package/template/coding-task-manager/public/js/prompts.js +11 -10
- package/template/coding-task-manager/public/js/protocol.js +10 -0
- package/template/coding-task-manager/public/js/restart-banner-policy.js +29 -1
- package/template/coding-task-manager/public/js/session-model-picker.js +447 -57
- package/template/coding-task-manager/public/js/session-search-utils.js +18 -2
- package/template/coding-task-manager/public/js/session-usage-view.js +2 -1
- package/template/coding-task-manager/public/js/session-workspace-tracking.js +51 -1
- package/template/coding-task-manager/public/js/state-sync-client.js +10 -1
- package/template/coding-task-manager/public/js/stream-view.js +6 -5
- package/template/coding-task-manager/public/js/terminal/conv-only-session.js +287 -0
- package/template/coding-task-manager/public/js/terminal/runtime-09.js +2 -0
- package/template/coding-task-manager/public/js/terminal/runtime-10.js +5 -5
- package/template/coding-task-manager/public/js/terminal/runtime-12.js +8 -1
- package/template/coding-task-manager/public/js/terminal/runtime-16.js +4 -0
- package/template/coding-task-manager/public/js/terminal/runtime-20.js +1 -1
- package/template/coding-task-manager/public/js/terminal-attachment-draft.js +67 -13
- package/template/coding-task-manager/public/js/terminal-image-orchestration.js +52 -6
- package/template/coding-task-manager/public/js/terminal-wheel.js +23 -3
- package/template/coding-task-manager/public/js/walle/chat-conversation.js +4 -4
- package/template/coding-task-manager/public/js/walle.js +6 -0
- package/template/coding-task-manager/public/js/ws/live-dispatch.js +10 -0
- package/template/coding-task-manager/public/m/app.css +140 -0
- package/template/coding-task-manager/public/m/app.js +507 -68
- package/template/coding-task-manager/public/m/index.html +17 -0
- package/template/coding-task-manager/public/m/machines.js +838 -4
- package/template/coding-task-manager/public/m/private-api.js +31 -1
- package/template/coding-task-manager/public/usage.html +1 -0
- package/template/coding-task-manager/scripts/dev-route-fixture/synthetic-route-upstream.js +104 -0
- package/template/coding-task-manager/scripts/dev-route-fixture.js +339 -0
- package/template/coding-task-manager/scripts/opencode-lead-route-probe.js +195 -0
- package/template/coding-task-manager/scripts/opencode-v2-contract-probe/fake-anthropic.js +168 -0
- package/template/coding-task-manager/scripts/opencode-v2-contract-probe/probe-plugin/server.js +95 -0
- package/template/coding-task-manager/scripts/opencode-v2-contract-probe.js +673 -0
- package/template/coding-task-manager/server.js +1950 -11679
- package/template/coding-task-manager/session-integrity.js +64 -4
- package/template/coding-task-manager/workers/db-owner-worker.js +19 -2
- package/template/coding-task-manager/workers/online-backup-worker.js +1 -0
- package/template/coding-task-manager/workers/read-pool-model-collector.js +51 -3
- package/template/coding-task-manager/workers/read-pool-worker.js +76 -6
- package/template/coding-task-manager/workers/session-host-pool-process.js +6 -6
- package/template/coding-task-manager/workers/session-host-process.js +5 -5
- package/template/coding-task-manager/workers/watch-worker.js +105 -0
- package/template/coding-task-manager/workers/worktree-worker.js +168 -33
- package/template/ctm-relay/lib/protocol.js +14 -1
- package/template/package-lock.json +2 -2
- package/template/package.json +1 -1
- package/template/shared/backup-retention.js +82 -0
- package/template/shared/conversation-admission.js +2 -0
- package/template/shared/conversation-provenance.js +3 -7
- package/template/shared/conversation-storage-v2-contract.js +5 -0
- package/template/shared/linked-worktree.js +51 -0
- package/template/shared/macos-fda-target.js +57 -0
- package/template/shared/orphan-parents.js +55 -0
- package/template/shared/release-schema.json +2 -2
- package/template/shared/storage/contract.js +3 -0
- package/template/wall-e/adapters/imessage.js +12 -0
- package/template/wall-e/agent.js +71 -4
- package/template/wall-e/api-walle.js +21 -1
- package/template/wall-e/bin/train-gemma-e4b-tooluse.js +2 -2
- package/template/wall-e/bin/walle-mcp-stdio.js +5 -5
- package/template/wall-e/brain/brain-join.js +859 -0
- package/template/wall-e/brain/brain-sync-membership.js +20 -0
- package/template/wall-e/brain/brain-sync-store.js +131 -77
- package/template/wall-e/brain/brain-sync-v2-schema.js +203 -0
- package/template/wall-e/brain/brain-sync-v2.js +578 -0
- package/template/wall-e/brain/delegate-model-prefs-store.js +13 -4
- package/template/wall-e/brain/deterministic-id.js +51 -0
- package/template/wall-e/brain/memories.js +2 -2
- package/template/wall-e/brain/migrations.js +40 -6
- package/template/wall-e/brain/model-identity-migration.js +1 -1
- package/template/wall-e/brain/owner-call-whitelist.js +23 -7
- package/template/wall-e/brain/schema.js +45 -85
- package/template/wall-e/brain.js +171 -35
- package/template/wall-e/chat.js +3 -3
- package/template/wall-e/coding/agent-shell-env.js +7 -2
- package/template/wall-e/coding/prompt-bundle.js +66 -21
- package/template/wall-e/config/main-loop-exemptions.json +2 -2
- package/template/wall-e/http/admin-api.js +37 -0
- package/template/wall-e/http/gws-api.js +4 -0
- package/template/wall-e/http/helper-rules-api.js +7 -1
- package/template/wall-e/http/model-admin.js +8 -1
- package/template/wall-e/http/model-delegate-pref-admin.js +3 -3
- package/template/wall-e/http/model-proxy-providers.js +174 -1
- package/template/wall-e/http/model-route-health.js +157 -0
- package/template/wall-e/http/sync-api.js +278 -464
- package/template/wall-e/jobs/job-runner.js +28 -1
- package/template/wall-e/lib/brain-owner-op-priority.js +9 -1
- package/template/wall-e/lib/ctm-data-dir.js +26 -0
- package/template/wall-e/lib/derivation-home.js +147 -0
- package/template/wall-e/lib/helper-rules/evaluate.js +50 -9
- package/template/wall-e/lib/helper-rules/grade.js +51 -8
- package/template/wall-e/lib/helper-rules/lead-call-hook.js +38 -5
- package/template/wall-e/lib/helper-rules/store.js +4 -2
- package/template/wall-e/lib/landing-mcp-tools.js +170 -0
- package/template/wall-e/lib/mcp-client-config.js +133 -14
- package/template/wall-e/lib/mcp-gateway-migrate.js +28 -64
- package/template/wall-e/lib/mcp-integration.js +58 -13
- package/template/wall-e/lib/parent-death.js +7 -1
- package/template/wall-e/lib/scheduler-worker-jobs.js +6 -4
- package/template/wall-e/llm/delegate-lead-prefs.js +16 -1
- package/template/wall-e/llm/delegate-vendor-default.js +12 -5
- package/template/wall-e/llm/model-roles.js +1 -1
- package/template/wall-e/llm/native-delegate-selection.js +56 -8
- package/template/wall-e/llm/worker-delegate-args.js +72 -7
- package/template/wall-e/llm/worker-mcp-routing.js +66 -17
- package/template/wall-e/loops/brain-custom-writes.js +19 -0
- package/template/wall-e/loops/ingest.js +17 -1
- package/template/wall-e/loops/think.js +19 -4
- package/template/wall-e/mcp-contract.js +222 -6
- package/template/wall-e/mcp-protocol-contract.js +40 -5
- package/template/wall-e/mcp-server.js +128 -29
- package/template/wall-e/mcp-worker-schemas.js +5 -5
- package/template/wall-e/memory/ctm-context-client.js +85 -0
- package/template/wall-e/memory/ctm-session-context.js +9 -3
- package/template/wall-e/memory/hub-bridge-client.js +388 -0
- package/template/wall-e/memory/replica-origin.js +56 -0
- package/template/wall-e/memory/session-pool.js +135 -0
- package/template/wall-e/runtime/live-capabilities.js +8 -3
- package/template/wall-e/server.js +4 -2
- package/template/wall-e/shared/release-schema.json +2 -2
- package/template/wall-e/shared/sqlite-file-set.js +39 -0
- package/template/wall-e/shared/storage/contract.js +3 -0
- package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
- package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +7 -1
- package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +4 -4
- package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +6 -6
- package/template/wall-e/skills/_templates/fleet-sync/VERSION +1 -0
- package/template/wall-e/skills/_templates/fleet-sync/released-sha256.json +16 -0
- package/template/wall-e/skills/_templates/fleet-sync/templates/claude/SKILL.md +64 -0
- package/template/wall-e/skills/_templates/fleet-sync/templates/codex/SKILL.md +64 -0
- package/template/wall-e/tools/chrome-automation.js +6 -0
- package/template/wall-e/training/setup-cuda-train-env.sh +2 -2
- package/template/wall-e/workers/brain-owner-worker.js +18 -4
- package/template/wall-e/workers/mcp-gateway-process.js +5 -5
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// One entry per (client, feature): the refusal wording whose MEASURED fallback scope is the
|
|
4
|
+
// narrowest the client offers. Evidence and re-measure procedure:
|
|
5
|
+
// docs/auto-mode-native-lead-subagent-latch-2026-10-30.html §1.2 / §7.
|
|
6
|
+
//
|
|
7
|
+
// Measured on Claude Code 2.1.314 (§1.2 P-2 vs P-3/P-4): a 400 whose message names any of the
|
|
8
|
+
// tokens in `forbiddenTokens` (case-insensitive) makes the client turn server-side review off
|
|
9
|
+
// for its WHOLE SESSION -- the host latch `thirdPartyServerClassifierRefused()` fires and the
|
|
10
|
+
// lead process keeps classifying locally after a single subagent was refused. The wording below
|
|
11
|
+
// names none of them, so the client falls back for that ONE CONVERSATION and the session-scoped
|
|
12
|
+
// latch is never set. Re-measure with the `reprobe` command after each Claude Code update; on
|
|
13
|
+
// PASS, bump `measuredOn` in an isolated commit that cites the probe output.
|
|
14
|
+
//
|
|
15
|
+
// `version` is only ever the `\d+\.\d+\.\d+` capture of `entry.userAgent`, never raw header text,
|
|
16
|
+
// so nothing but a version number can reach a log line.
|
|
17
|
+
|
|
18
|
+
const REFUSAL_SCOPE_UNVERIFIED_PREFIX = '[model-proxy] refusal_scope_unverified';
|
|
19
|
+
|
|
20
|
+
const REVIEW_UNAVAILABLE_MESSAGE =
|
|
21
|
+
'This translated route cannot run server-side action review for this request '
|
|
22
|
+
+ '(ctm: review_unavailable_on_route). Send it without that review.';
|
|
23
|
+
|
|
24
|
+
const REFUSAL_SCOPE_ENTRIES = Object.freeze([
|
|
25
|
+
Object.freeze({
|
|
26
|
+
id: 'claude-code/server-review',
|
|
27
|
+
client: 'claude-code',
|
|
28
|
+
feature: 'server-review',
|
|
29
|
+
userAgent: /^claude-cli\/(\d+\.\d+\.\d+)\b/,
|
|
30
|
+
measuredOn: '2.1.314',
|
|
31
|
+
scope: 'conversation',
|
|
32
|
+
status: 400,
|
|
33
|
+
errorType: 'invalid_request_error',
|
|
34
|
+
message: REVIEW_UNAVAILABLE_MESSAGE,
|
|
35
|
+
forbiddenTokens: Object.freeze(['safeguard', 'dangerous-tool-use', 'dangerous_tool_use', 'afk-mode']),
|
|
36
|
+
reprobe: 'node coding-task-manager/bin/probe-refusal-scope.js',
|
|
37
|
+
}),
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
const SCOPE_VERIFIED = 'verified';
|
|
41
|
+
const SCOPE_UNVERIFIED = 'unverified';
|
|
42
|
+
const SCOPE_UNKNOWN = 'unknown';
|
|
43
|
+
|
|
44
|
+
// Bound on the once-per-(entry.id, version) warn set: after this many distinct versions the
|
|
45
|
+
// module stops recording AND stops warning, so an oscillating client cannot grow the set or
|
|
46
|
+
// flood the log without limit.
|
|
47
|
+
const MAX_TRACKED_VERSIONS = 64;
|
|
48
|
+
|
|
49
|
+
// The versions already warned about, keyed `${entry.id}\u0000${version}`. Process-global by
|
|
50
|
+
// design: "once per process" is the contract.
|
|
51
|
+
const warnedVersions = new Set();
|
|
52
|
+
|
|
53
|
+
function _isPlainObject(value) {
|
|
54
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// One header value by case-insensitive name, or `undefined`. Node lower-cases incoming header
|
|
58
|
+
// names, but a caller-built map may spell them any way, so the lookup never relies on that.
|
|
59
|
+
function _headerValue(headers, name) {
|
|
60
|
+
if (!_isPlainObject(headers)) return undefined;
|
|
61
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
62
|
+
if (typeof key === 'string' && key.toLowerCase() === name) return value;
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The registered entry for a (client, feature) pair.
|
|
69
|
+
* @param {string} client
|
|
70
|
+
* @param {string} feature
|
|
71
|
+
* @returns {object|null} the frozen entry, or null when none is registered
|
|
72
|
+
*/
|
|
73
|
+
function refusalEntry(client, feature) {
|
|
74
|
+
for (const entry of REFUSAL_SCOPE_ENTRIES) {
|
|
75
|
+
if (entry.client === client && entry.feature === feature) return entry;
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The client version `X.Y.Z`, parsed with `entry.userAgent` out of the request's `user-agent`
|
|
82
|
+
* header (any case). Only the regex capture is ever returned, so a header that carries more
|
|
83
|
+
* than a version (e.g. `claude-cli/2.1.314<script>`) yields exactly `2.1.314`.
|
|
84
|
+
* @param {object|null} entry
|
|
85
|
+
* @param {unknown} headers
|
|
86
|
+
* @returns {string|null}
|
|
87
|
+
*/
|
|
88
|
+
function clientVersion(entry, headers) {
|
|
89
|
+
if (!entry || !(entry.userAgent instanceof RegExp)) return null;
|
|
90
|
+
const raw = _headerValue(headers, 'user-agent');
|
|
91
|
+
const value = Array.isArray(raw) ? raw.find((part) => typeof part === 'string') : raw;
|
|
92
|
+
if (typeof value !== 'string') return null;
|
|
93
|
+
const match = entry.userAgent.exec(value);
|
|
94
|
+
return match && typeof match[1] === 'string' ? match[1] : null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Whether the client's measured fallback scope for this entry still holds for the version seen.
|
|
99
|
+
* @param {object|null} entry
|
|
100
|
+
* @param {string|null|undefined} version
|
|
101
|
+
* @returns {'verified'|'unverified'|'unknown'} 'unknown' when no version could be read
|
|
102
|
+
*/
|
|
103
|
+
function scopeVerification(entry, version) {
|
|
104
|
+
if (!entry) return SCOPE_UNKNOWN;
|
|
105
|
+
if (typeof version !== 'string' || version === '') return SCOPE_UNKNOWN;
|
|
106
|
+
return version === entry.measuredOn ? SCOPE_VERIFIED : SCOPE_UNVERIFIED;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The refusal response for an entry, as fresh objects on every call (a caller may mutate them).
|
|
111
|
+
* @param {object|null} entry
|
|
112
|
+
* @returns {{status:number, body:{type:string, error:{type:string, message:string}}}|null}
|
|
113
|
+
*/
|
|
114
|
+
function refusalResponse(entry) {
|
|
115
|
+
if (!entry) return null;
|
|
116
|
+
return {
|
|
117
|
+
status: entry.status,
|
|
118
|
+
body: {
|
|
119
|
+
type: 'error',
|
|
120
|
+
error: { type: entry.errorType, message: entry.message },
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Warn ONCE per (entry.id, version) per process when the installed client version is not the one
|
|
127
|
+
* the entry was measured on. A no-op for 'verified'/'unknown' and for a missing entry.
|
|
128
|
+
* @param {object|null} entry
|
|
129
|
+
* @param {string|null|undefined} version
|
|
130
|
+
*/
|
|
131
|
+
function warnIfUnverified(entry, version) {
|
|
132
|
+
if (scopeVerification(entry, version) !== SCOPE_UNVERIFIED) return;
|
|
133
|
+
const key = `${entry.id}\u0000${version}`;
|
|
134
|
+
if (warnedVersions.has(key)) return;
|
|
135
|
+
if (warnedVersions.size >= MAX_TRACKED_VERSIONS) return; // bounded: stop recording and warning
|
|
136
|
+
warnedVersions.add(key);
|
|
137
|
+
console.warn(
|
|
138
|
+
`${REFUSAL_SCOPE_UNVERIFIED_PREFIX} entry=${entry.id} client_version=${version}`
|
|
139
|
+
+ ` measured_on=${entry.measuredOn} reprobe="${entry.reprobe}"`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = {
|
|
144
|
+
REVIEW_UNAVAILABLE_MESSAGE,
|
|
145
|
+
REFUSAL_SCOPE_ENTRIES,
|
|
146
|
+
refusalEntry,
|
|
147
|
+
clientVersion,
|
|
148
|
+
scopeVerification,
|
|
149
|
+
refusalResponse,
|
|
150
|
+
warnIfUnverified,
|
|
151
|
+
};
|
|
@@ -391,10 +391,19 @@ async function readTaskLedger({ ctmSessionId, dataDir = getDataDir(), fsImpl = f
|
|
|
391
391
|
const terminalRequests = terminal && Number.isSafeInteger(terminal.providerRequests) ? terminal.providerRequests : null;
|
|
392
392
|
const terminalOutput = terminal && Number.isSafeInteger(terminal.outputTokens) ? terminal.outputTokens : null;
|
|
393
393
|
const priced = completes.filter((record) => typeof record.costUsd === 'number' && Number.isFinite(record.costUsd));
|
|
394
|
+
// C7-A: the journal's own admits/completions are the floor, never a ceiling. CTM's terminal
|
|
395
|
+
// report is derived runtime telemetry: it can be stale (a runtime restart or a report written
|
|
396
|
+
// before the proxy actor's own appends land) and it can legitimately lag a completion that the
|
|
397
|
+
// writer journals after the terminal for an already-admitted id. The admit and known-completion
|
|
398
|
+
// records are append-only observations made by this store itself, so a smaller terminal snapshot
|
|
399
|
+
// cannot erase them. Math.max is a lower bound over two views of the SAME work, never an
|
|
400
|
+
// addition -- a larger legacy terminal snapshot stays the bound -- and a null terminal count is
|
|
401
|
+
// no bound at all, not a measured 0: usage no record observed stays unmeasured.
|
|
402
|
+
const knownCompletionOutputTokens = completes.reduce((sum, record) => sum + (record.outputTokens || 0), 0);
|
|
394
403
|
rows.push({
|
|
395
404
|
generation,
|
|
396
|
-
requests: terminalRequests
|
|
397
|
-
outputTokens:
|
|
405
|
+
requests: Math.max(terminalRequests ?? 0, admits.length),
|
|
406
|
+
outputTokens: Math.max(terminalOutput ?? 0, knownCompletionOutputTokens),
|
|
398
407
|
// WP-2: a v1 admit has no limits.startLease, so the fallback defaults to 1
|
|
399
408
|
// (a v1 journal's endLease is always a number).
|
|
400
409
|
endLease: leases.length ? Math.max(...leases.map((record) => record.lease)) : (admits.length ? admits[0].limits.startLease ?? 1 : 1),
|
|
@@ -19,6 +19,11 @@ const LEAD_TOKEN_RE = /^ctml_[0-9a-f]{32}$/;
|
|
|
19
19
|
const LEAD_HARNESSES = new Set(['codex-cli', 'claude-code']);
|
|
20
20
|
const SAFE_DEPLOYMENT_ID = /^[@A-Za-z0-9][A-Za-z0-9._:/@+-]{0,159}$/;
|
|
21
21
|
const LEAD_PURPOSES = new Set(['routed-primary', 'native-claude-passthrough']);
|
|
22
|
+
// WP-9 §9b L-2 (docs/deepseek-harness-acp-agent-2026-10-28.html D-19): an OPTIONAL mark on a lead
|
|
23
|
+
// token saying its requests must also be written to a per-request usage file. Only 'dsh' exists — the
|
|
24
|
+
// DeepSeek Harness lane, whose ACP wire carries no token counts — and a token without the field is
|
|
25
|
+
// byte-for-byte the binding it has always been (the proxy writes nothing for it).
|
|
26
|
+
const LEAD_USAGE_LOGS = new Set(['dsh']);
|
|
22
27
|
const NATIVE_CLAUDE_ALIAS = 'anthropic-oauth';
|
|
23
28
|
const SAFE_OWNER_ID = /^[A-Za-z0-9][A-Za-z0-9._:@/+\-]{0,255}$/;
|
|
24
29
|
// WP-0A (docs/claude-native-auth-mtls-and-routed-subagents-2026-10-15.html §6.2, D-2/D-7): the
|
|
@@ -170,6 +175,10 @@ function _sameLeadRegistration(entry, next) {
|
|
|
170
175
|
&& entry.harness === next.harness
|
|
171
176
|
&& entry.providerThreadId === next.providerThreadId
|
|
172
177
|
&& entry.transitionId === next.transitionId
|
|
178
|
+
// D-19: the usage mark is immutable identity like every other field here — an absent value reads as
|
|
179
|
+
// null, so a replay of the exact same registration stays idempotent while turning the mark on (or
|
|
180
|
+
// off) for a live token is a rebind, never an upgrade.
|
|
181
|
+
&& (entry.usageLog || null) === (next.usageLog || null)
|
|
173
182
|
// Carrier and fingerprint comparison uses the RESOLVED values: an owner registered before the
|
|
174
183
|
// fields existed (or one whose caller omits them) is on 'legacy-bearer' with no fingerprint, so
|
|
175
184
|
// replaying it as an explicit 'legacy-bearer' stays idempotent while a switch to 'mtls-v1' — or a
|
|
@@ -214,8 +223,14 @@ function _liveLeadEntry(token) {
|
|
|
214
223
|
|
|
215
224
|
function _normalizeLeadRegistration({ ctmSessionId, aliases, bindings, processGeneration, transitionId,
|
|
216
225
|
purpose = 'routed-primary', delegateScope = false, harness, providerThreadId, nativeTransport,
|
|
217
|
-
nativeCertFingerprint, nativeSettingsRef } = {}) {
|
|
226
|
+
nativeCertFingerprint, nativeSettingsRef, usageLog } = {}) {
|
|
218
227
|
if (!LEAD_PURPOSES.has(purpose)) throw new TypeError('registerLeadToken: unknown purpose');
|
|
228
|
+
// D-19/L-2: `usageLog` is an optional mark, and 'dsh' is the only value it has. `typeof`-first for
|
|
229
|
+
// the same reason the native fields are: an array- or object-wrapped value must be refused rather
|
|
230
|
+
// than coerced and then stored as something no downstream comparison could ever match.
|
|
231
|
+
if (usageLog != null && (typeof usageLog !== 'string' || !LEAD_USAGE_LOGS.has(usageLog))) {
|
|
232
|
+
throw new TypeError('registerLeadToken: unknown usageLog');
|
|
233
|
+
}
|
|
219
234
|
if (typeof delegateScope !== 'boolean') throw new TypeError('registerLeadToken: delegateScope must be boolean');
|
|
220
235
|
if (typeof ctmSessionId !== 'string' || !SAFE_OWNER_ID.test(ctmSessionId)) {
|
|
221
236
|
throw new TypeError('registerLeadToken: ctmSessionId required');
|
|
@@ -282,7 +297,8 @@ function _normalizeLeadRegistration({ ctmSessionId, aliases, bindings, processGe
|
|
|
282
297
|
// a settings locator is ever manufactured for it.
|
|
283
298
|
return { ctmSessionId, aliases: new Set(aliases), bindings: [], processGeneration,
|
|
284
299
|
purpose, delegateScope, harness, providerThreadId,
|
|
285
|
-
...(nativeTransport === NATIVE_TRANSPORT_MTLS ? { nativeTransport, nativeCertFingerprint, nativeSettingsRef } : {})
|
|
300
|
+
...(nativeTransport === NATIVE_TRANSPORT_MTLS ? { nativeTransport, nativeCertFingerprint, nativeSettingsRef } : {}),
|
|
301
|
+
...(usageLog ? { usageLog } : {}) };
|
|
286
302
|
}
|
|
287
303
|
if (providerThreadId != null && (typeof providerThreadId !== 'string' || !SAFE_OWNER_ID.test(providerThreadId))) {
|
|
288
304
|
throw new TypeError('registerLeadToken: providerThreadId must be a bounded identifier');
|
|
@@ -299,7 +315,8 @@ function _normalizeLeadRegistration({ ctmSessionId, aliases, bindings, processGe
|
|
|
299
315
|
const coherentHarness = harness || derivedHarness;
|
|
300
316
|
return { ctmSessionId, aliases: new Set(aliases), bindings: normalizedBindings,
|
|
301
317
|
processGeneration, purpose, delegateScope, ...(coherentHarness ? { harness: coherentHarness } : {}),
|
|
302
|
-
...(providerThreadId ? { providerThreadId } : {}), ...(transitionId != null ? { transitionId } : {})
|
|
318
|
+
...(providerThreadId ? { providerThreadId } : {}), ...(transitionId != null ? { transitionId } : {}),
|
|
319
|
+
...(usageLog ? { usageLog } : {}) };
|
|
303
320
|
}
|
|
304
321
|
|
|
305
322
|
function registerLeadToken(registration = {}) {
|
|
@@ -310,6 +327,11 @@ function registerLeadToken(registration = {}) {
|
|
|
310
327
|
return _commitLeadRegistration(validated.token, validated.entry);
|
|
311
328
|
}
|
|
312
329
|
|
|
330
|
+
// WP-1 (§5 D-4, §9 S5): the bounded code a REFUSED revocation replay carries. A static string -- never a
|
|
331
|
+
// token, a fingerprint or any other caller value -- so the native admin path can map it to a 409 without
|
|
332
|
+
// ever echoing input.
|
|
333
|
+
const LEAD_TOKEN_REVOKED_CODE = 'lead_token_revoked';
|
|
334
|
+
|
|
313
335
|
/**
|
|
314
336
|
* The PURE half of lead-token registration: the field allowlist, the token grammar, and every
|
|
315
337
|
* shape/normalization rule, including the immutable carrier/fingerprint/settings-ref pairing
|
|
@@ -329,7 +351,7 @@ function registerLeadToken(registration = {}) {
|
|
|
329
351
|
function validateLeadTokenRegistration(registration = {}) {
|
|
330
352
|
const allowedFields = new Set(['token', 'ctmSessionId', 'aliases', 'bindings', 'processGeneration',
|
|
331
353
|
'transitionId', 'purpose', 'delegateScope', 'harness', 'providerThreadId', 'nativeTransport',
|
|
332
|
-
'nativeCertFingerprint', 'nativeSettingsRef']);
|
|
354
|
+
'nativeCertFingerprint', 'nativeSettingsRef', 'usageLog']);
|
|
333
355
|
if (!registration || typeof registration !== 'object' || Array.isArray(registration)
|
|
334
356
|
|| Object.keys(registration).some((key) => !allowedFields.has(key))) {
|
|
335
357
|
throw new TypeError('registerLeadToken: unknown registration field');
|
|
@@ -348,6 +370,13 @@ function validateLeadTokenRegistration(registration = {}) {
|
|
|
348
370
|
function _commitLeadRegistration(token, next) {
|
|
349
371
|
const conflict = _leadRegistrationConflictMessage(token, next);
|
|
350
372
|
if (conflict) throw new TypeError(conflict);
|
|
373
|
+
// WP-1 (§5 D-4, §9 S5): a REVOKED owner is a tombstone, not an upgrade. Only an EXACT replay of a
|
|
374
|
+
// revoked registration reaches here (a differing one is already the rebind conflict above), and it is
|
|
375
|
+
// refused with the bounded code BEFORE the idempotent-replay short-circuit below: acknowledging it as
|
|
376
|
+
// a successful grant is exactly the lie this fix removes, because the revoke released this owner's
|
|
377
|
+
// certificate index row too, so the "granted" token resolved to no live owner at all.
|
|
378
|
+
const revokedRefusal = _revokedLeadRegistrationRefusal(token, next);
|
|
379
|
+
if (revokedRefusal) throw revokedRefusal;
|
|
351
380
|
// The idempotent-replay case writes the same entry again; that is what makes a replay a no-op.
|
|
352
381
|
const existing = _tokens.get(token);
|
|
353
382
|
if (existing && _sameLeadRegistration(existing, next)) return { token };
|
|
@@ -357,6 +386,28 @@ function _commitLeadRegistration(token, next) {
|
|
|
357
386
|
return { token };
|
|
358
387
|
}
|
|
359
388
|
|
|
389
|
+
/**
|
|
390
|
+
* The revocation half of the LIVE registration rules (§5 D-4): an EXACT replay of a revoked lead
|
|
391
|
+
* registration is refused, with a bounded code and no write. It reads and writes nothing else, so asking
|
|
392
|
+
* it before a ticket exists and again inside the commit is the same answer.
|
|
393
|
+
*
|
|
394
|
+
* A DIFFERING registration of a revoked token is NOT answered here -- the rebind conflict above owns it,
|
|
395
|
+
* so every immutable-field rule and the duplicate-certificate rule keep their existing refusal. And since
|
|
396
|
+
* a revoke releases the fingerprint in the same step, a different NEW token may still take that released
|
|
397
|
+
* certificate over (D-2): this refusal is about the revoked IDENTITY, never about the certificate.
|
|
398
|
+
*
|
|
399
|
+
* @returns {Error|null} the coded TypeError the caller must throw, or null when this is not a replay of a
|
|
400
|
+
* revoked registration
|
|
401
|
+
*/
|
|
402
|
+
function _revokedLeadRegistrationRefusal(token, next) {
|
|
403
|
+
const existing = _tokens.get(token);
|
|
404
|
+
if (!existing || existing.kind !== 'lead' || !existing.revoked) return null;
|
|
405
|
+
if (!_sameLeadRegistration(existing, next)) return null;
|
|
406
|
+
const error = new TypeError('registerLeadToken: a revoked token cannot be re-registered');
|
|
407
|
+
error.code = LEAD_TOKEN_REVOKED_CODE;
|
|
408
|
+
return error;
|
|
409
|
+
}
|
|
410
|
+
|
|
360
411
|
/**
|
|
361
412
|
* The read-only LIVE half of lead-token registration: everything that depends on the CURRENT registry.
|
|
362
413
|
*
|
|
@@ -454,6 +505,11 @@ function beginLeadTokenRegistration(registration = {}) {
|
|
|
454
505
|
// there — this early call is what keeps a doomed registration from ever loading a CA or binding a port.
|
|
455
506
|
const conflict = _leadRegistrationConflictMessage(validated.token, validated.entry);
|
|
456
507
|
if (conflict) throw new TypeError(conflict);
|
|
508
|
+
// WP-1 (§5 D-4): a revoked owner is refused HERE, before the ticket exists, so a doomed replay never
|
|
509
|
+
// loads a CA and never binds a port. The commit below asks the same question again, so a revocation
|
|
510
|
+
// that lands while the caller is awaiting is refused there too.
|
|
511
|
+
const revokedRefusal = _revokedLeadRegistrationRefusal(validated.token, validated.entry);
|
|
512
|
+
if (revokedRefusal) throw revokedRefusal;
|
|
457
513
|
if (_pendingLeadRegistrations.size >= MAX_PENDING_LEAD_REGISTRATIONS) {
|
|
458
514
|
throw new TypeError('beginLeadTokenRegistration: too many pending registrations');
|
|
459
515
|
}
|
|
@@ -474,10 +530,13 @@ function beginLeadTokenRegistration(registration = {}) {
|
|
|
474
530
|
const result = _commitLeadRegistration(ticket.token, ticket.entry);
|
|
475
531
|
ticket.state = 'committed';
|
|
476
532
|
return { ok: true, token: result.token };
|
|
477
|
-
} catch {
|
|
533
|
+
} catch (err) {
|
|
478
534
|
// The live checks refused (a concurrent owner took this certificate, or the token was rebound):
|
|
479
535
|
// the bounded code is all the caller learns — never the underlying message, which is internal.
|
|
480
536
|
ticket.state = 'refused';
|
|
537
|
+
// ...and a revocation refusal keeps its OWN bounded code, so the native admin path answers 409
|
|
538
|
+
// `lead_token_revoked` at commit time exactly as it does on the synchronous path (§9 S6).
|
|
539
|
+
if (err && err.code === LEAD_TOKEN_REVOKED_CODE) return { ok: false, code: LEAD_TOKEN_REVOKED_CODE };
|
|
481
540
|
return { ok: false, code: LEAD_REGISTRATION_TICKET_CODES.REFUSED };
|
|
482
541
|
}
|
|
483
542
|
},
|
|
@@ -552,6 +611,10 @@ function leadTokenBinding(token) {
|
|
|
552
611
|
...(entry.nativeTransport ? { nativeTransport: entry.nativeTransport } : {}),
|
|
553
612
|
...(entry.nativeCertFingerprint ? { nativeCertFingerprint: entry.nativeCertFingerprint } : {}),
|
|
554
613
|
...(entry.nativeSettingsRef ? { nativeSettingsRef: entry.nativeSettingsRef } : {}),
|
|
614
|
+
// D-19/L-2: the usage mark rides the binding projection exactly as supplied, so the proxy's
|
|
615
|
+
// completion path can tell a dsh lane from every other lead without a second registry. Absent on
|
|
616
|
+
// every registration that never set it, so those projections stay byte-for-byte unchanged.
|
|
617
|
+
...(entry.usageLog ? { usageLog: entry.usageLog } : {}),
|
|
555
618
|
};
|
|
556
619
|
}
|
|
557
620
|
|
|
@@ -869,6 +932,7 @@ module.exports = {
|
|
|
869
932
|
NATIVE_CERT_FINGERPRINT_RE,
|
|
870
933
|
NATIVE_SETTINGS_REF_RE,
|
|
871
934
|
LEAD_REGISTRATION_TICKET_CODES,
|
|
935
|
+
LEAD_TOKEN_REVOKED_CODE,
|
|
872
936
|
MAX_PENDING_LEAD_REGISTRATIONS,
|
|
873
937
|
registerLeadToken,
|
|
874
938
|
validateLeadTokenRegistration,
|
|
@@ -11,6 +11,7 @@ const SAFE_MESSAGES = Object.freeze({
|
|
|
11
11
|
server_error: 'The provider could not complete the request.',
|
|
12
12
|
model_not_invocable: 'This route cannot invoke this model id; it needs the model’s inference-profile id.',
|
|
13
13
|
model_access_denied: 'The route’s provider account denies access to this model.',
|
|
14
|
+
model_not_found: 'This route does not serve this model id.',
|
|
14
15
|
});
|
|
15
16
|
|
|
16
17
|
const SAFE_CODE = /^[A-Za-z0-9_.-]{1,64}$/;
|
|
@@ -45,6 +46,14 @@ function _classify(code, status, message) {
|
|
|
45
46
|
// rate_limit_exceeded) and before the generic invalid/4xx line. The provider's own prose is never copied.
|
|
46
47
|
if (status === 401 || status === 403 || code === '401' || code === '403') return 'model_access_denied';
|
|
47
48
|
if (/on-demand throughput|inference profile/.test(folded) && (status === 400 || status === 422 || code === '400' || code === '422')) return 'model_not_invocable';
|
|
49
|
+
// WP-1b (§12a rule 2): a 404, or a 400/422 whose prose says the model ID ITSELF is unknown or
|
|
50
|
+
// unreachable from this account, is the same verdict the document above describes — the route does
|
|
51
|
+
// not serve this model id — and the switch has to name it truthfully instead of "the request is
|
|
52
|
+
// invalid". Both run after the four message-driven classes and the two route/model-id lines, and
|
|
53
|
+
// before the generic invalid/4xx line (which would otherwise swallow a 404 as invalid_prompt).
|
|
54
|
+
if (status === 404 || code === '404') return 'model_not_found';
|
|
55
|
+
if ((status === 400 || status === 422 || code === '400' || code === '422')
|
|
56
|
+
&& /model identifier is invalid|model[_ -]?not[_ -]?found|not found or your project does not have access/.test(folded)) return 'model_not_found';
|
|
48
57
|
if (/invalid|model[_ -]?not[_ -]?found|history/.test(folded) || ['400', '404', '422'].includes(code) || [400, 404, 422].includes(status)) return 'invalid_prompt';
|
|
49
58
|
return 'server_error';
|
|
50
59
|
}
|
|
@@ -16,11 +16,6 @@ const NATIVE_AGENT_MODEL_FLAG = Object.freeze({
|
|
|
16
16
|
opencode: '--model',
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
-
// OpenCode model IDs include the provider ID (`provider/model`). Keep CTM's
|
|
20
|
-
// opinionated default here instead of mutating ~/.config/opencode so terminal
|
|
21
|
-
// launches outside CTM keep the user's own OpenCode defaults.
|
|
22
|
-
const DEFAULT_OPENCODE_MODEL = 'zai/glm-5.2';
|
|
23
|
-
|
|
24
19
|
function _normalizeProvider(value) {
|
|
25
20
|
const text = String(value || '').trim().toLowerCase().replace(/_/g, '-');
|
|
26
21
|
if (text === 'claude' || text === 'claude-code') return 'anthropic';
|
|
@@ -43,14 +38,6 @@ function _inferProviderFromModel(modelId) {
|
|
|
43
38
|
return '';
|
|
44
39
|
}
|
|
45
40
|
|
|
46
|
-
function _opencodeDefaultModel(env = process.env) {
|
|
47
|
-
return String(
|
|
48
|
-
env?.CTM_OPENCODE_DEFAULT_MODEL
|
|
49
|
-
|| env?.OPENCODE_DEFAULT_MODEL
|
|
50
|
-
|| DEFAULT_OPENCODE_MODEL
|
|
51
|
-
).trim();
|
|
52
|
-
}
|
|
53
|
-
|
|
54
41
|
function nativeProviderForAgent(agentType) {
|
|
55
42
|
return NATIVE_AGENT_PROVIDER[normalizeAgentType(agentType)] || '';
|
|
56
43
|
}
|
|
@@ -85,18 +72,18 @@ function selectNativeAgentLaunchModel({
|
|
|
85
72
|
explicitModelId,
|
|
86
73
|
explicitModelProvider,
|
|
87
74
|
defaultSelection,
|
|
88
|
-
env = process.env,
|
|
89
75
|
} = {}) {
|
|
90
76
|
const normalizedAgent = normalizeAgentType(agentType);
|
|
77
|
+
// OpenCode model ids are `provider/model`. Same rule as Claude/Codex: an explicit pick becomes
|
|
78
|
+
// `--model`; with no pick CTM passes none and the CLI's own default decides (observed on the
|
|
79
|
+
// first `opencode:message`). CTM never invents an OpenCode default (OpenCode round 2 §5 R-5).
|
|
91
80
|
if (normalizedAgent === 'opencode') {
|
|
92
81
|
const explicitModel = String(explicitModelId || '').trim();
|
|
93
|
-
|
|
94
|
-
if (!model) return null;
|
|
95
|
-
const modelProvider = _normalizeProvider(explicitModelProvider) || _inferProviderFromModel(model) || 'opencode';
|
|
82
|
+
if (!explicitModel) return null;
|
|
96
83
|
return {
|
|
97
|
-
model_id:
|
|
98
|
-
model_provider:
|
|
99
|
-
source:
|
|
84
|
+
model_id: explicitModel,
|
|
85
|
+
model_provider: _normalizeProvider(explicitModelProvider) || _inferProviderFromModel(explicitModel) || 'opencode',
|
|
86
|
+
source: 'explicit',
|
|
100
87
|
};
|
|
101
88
|
}
|
|
102
89
|
|
|
@@ -129,18 +116,34 @@ function selectNativeAgentLaunchModel({
|
|
|
129
116
|
return null;
|
|
130
117
|
}
|
|
131
118
|
|
|
132
|
-
|
|
119
|
+
// OpenCode 2's TUI rejects `--model` (it prints usage and exits); OpenCode 1.x
|
|
120
|
+
// accepts it. The launch path is synchronous, so the line comes from the last
|
|
121
|
+
// detection this process recorded (opencode-cli.peekOpencodeLine — sync, no fs, no
|
|
122
|
+
// spawn). R-6 (opencode-v2-model-switch §5.1): that detection is PRIMED by an
|
|
123
|
+
// `await primeOpencodeLine()` at the top of server.js's handleCreate, before any
|
|
124
|
+
// OpenCode argv is built, so an OpenCode launch never decides on an unknown line.
|
|
125
|
+
// The old background opencodeLine() warm-up is gone. Only OpenCode 2's line is
|
|
126
|
+
// treated specially: every other value (including a `null` we could not determine)
|
|
127
|
+
// keeps the pre-existing behaviour exactly.
|
|
128
|
+
function injectNativeAgentModelArgs({ agentType, args, modelId, env, lineOf } = {}) {
|
|
133
129
|
const normalizedAgent = normalizeAgentType(agentType);
|
|
134
130
|
const flag = NATIVE_AGENT_MODEL_FLAG[normalizedAgent];
|
|
135
131
|
const model = String(modelId || '').trim();
|
|
136
132
|
const list = Array.isArray(args) ? args.slice() : [];
|
|
137
133
|
if (!flag || !model) return { args: list, injected: false, reason: 'unsupported' };
|
|
134
|
+
if (normalizedAgent === 'opencode') {
|
|
135
|
+
const cli = require('./opencode-cli');
|
|
136
|
+
const resolveLine = lineOf || (() => cli.peekOpencodeLine());
|
|
137
|
+
const line = resolveLine(env || process.env);
|
|
138
|
+
if (line === 'v2') {
|
|
139
|
+
return { args: list, injected: false, reason: 'opencode-v2-no-tui-model-flag' };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
138
142
|
if (hasExplicitNativeModelArg(list)) return { args: list, injected: false, reason: 'explicit-model-arg' };
|
|
139
143
|
return { args: [flag, model, ...list], injected: true, reason: 'injected' };
|
|
140
144
|
}
|
|
141
145
|
|
|
142
146
|
module.exports = {
|
|
143
|
-
DEFAULT_OPENCODE_MODEL,
|
|
144
147
|
NATIVE_AGENT_PROVIDER,
|
|
145
148
|
hasExplicitNativeModelArg,
|
|
146
149
|
injectNativeAgentModelArgs,
|
|
@@ -4,8 +4,10 @@ const { spawn } = require('node:child_process');
|
|
|
4
4
|
const { StringDecoder } = require('node:string_decoder');
|
|
5
5
|
const path = require('node:path');
|
|
6
6
|
const { codexProxyProviderArgs } = require('./codex-launch-health');
|
|
7
|
-
// S-5: the adapter id and the
|
|
8
|
-
|
|
7
|
+
// S-5: the adapter id and the non-elevated cyber program set come from the one table. The Codex
|
|
8
|
+
// version is recorded provenance only (WP-0): the two validators below refuse a missing/blank
|
|
9
|
+
// version string but never compare it against a qualified-version table, range or floor.
|
|
10
|
+
const { CODEX_CONTROL_ADAPTER, isNonElevatedCyberAccessProgram } = require('./codex-harness-qualification');
|
|
9
11
|
|
|
10
12
|
const QUALIFIED_ADAPTER = CODEX_CONTROL_ADAPTER;
|
|
11
13
|
const MAX_FRAME_BYTES = 1024 * 1024;
|
|
@@ -52,7 +54,7 @@ function createCodexNativeHistoryControl({ sourceRecipe, targetRecipe } = {}) {
|
|
|
52
54
|
|
|
53
55
|
function validateSource(recipe) {
|
|
54
56
|
if (!isRecord(recipe) || recipe.controlAdapterVersion !== QUALIFIED_ADAPTER
|
|
55
|
-
|| !
|
|
57
|
+
|| !nonempty(recipe.programVersion) || !absolute(recipe.executablePath)
|
|
56
58
|
|| !absolute(recipe.codexHome) || !absolute(recipe.homeDir) || !absolute(recipe.workingDirectory)
|
|
57
59
|
|| (recipe.tmpDir != null && !absolute(recipe.tmpDir))
|
|
58
60
|
|| !validCyberAccessProgram(recipe.cyberAccessProgram)
|
|
@@ -67,7 +69,7 @@ function validateSource(recipe) {
|
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
function validateTarget(recipe) {
|
|
70
|
-
if (!isRecord(recipe) || recipe.harness !== 'codex-cli' || !
|
|
72
|
+
if (!isRecord(recipe) || recipe.harness !== 'codex-cli' || !nonempty(recipe.harnessVersion)
|
|
71
73
|
|| !nonempty(recipe.providerId) || !nonempty(recipe.providerName) || !nonempty(recipe.modelId)
|
|
72
74
|
|| !Number.isSafeInteger(recipe.proxyPort) || recipe.proxyPort < 1 || recipe.proxyPort > 65535
|
|
73
75
|
|| !validAlias(recipe.alias) || !validEnvKey(recipe.authEnvKey)
|
|
@@ -219,17 +219,34 @@ async function _ensureGeneratedDirAsync(dirPath, dataDir) {
|
|
|
219
219
|
// Exclusive creation, so a ref collision is a refusal rather than an adoption of someone else's
|
|
220
220
|
// container. The captured identity is what the later guarded cleanup and the worker's descriptor
|
|
221
221
|
// check both use.
|
|
222
|
-
|
|
222
|
+
//
|
|
223
|
+
// S-30: the reserving parent then holds a read-only directory FileHandle (stored in `sink[key]`) for
|
|
224
|
+
// as long as the identity is relied on. dev+ino alone is unsound on ext4 (a removed directory's inode
|
|
225
|
+
// number is reused by the next mkdir), but an inode with an open fd cannot be freed.
|
|
226
|
+
async function _reserveEmptyDirAsync(dirPath, sink, key) {
|
|
223
227
|
try {
|
|
224
228
|
await fs.promises.mkdir(dirPath, { mode: 0o700 });
|
|
225
229
|
} catch {
|
|
226
230
|
// EEXIST (or anything else): never adopt, never delete, never retry with a different name.
|
|
227
231
|
_fail(NATIVE_LEAD_SETTINGS_CLIENT_CODES.RESERVE_FAILED);
|
|
228
232
|
}
|
|
233
|
+
let handle = null;
|
|
234
|
+
try {
|
|
235
|
+
handle = await fs.promises.open(
|
|
236
|
+
dirPath,
|
|
237
|
+
fs.constants.O_RDONLY | fs.constants.O_DIRECTORY | (fs.constants.O_NOFOLLOW || 0),
|
|
238
|
+
);
|
|
239
|
+
} catch {
|
|
240
|
+
_fail(NATIVE_LEAD_SETTINGS_CLIENT_CODES.RESERVE_FAILED);
|
|
241
|
+
}
|
|
242
|
+
sink[key] = handle; // owned by the reservation from here, so every unwind path closes it
|
|
229
243
|
const detailed = await _lstatDetailed(dirPath);
|
|
244
|
+
const opened = await handle.stat();
|
|
230
245
|
if (detailed.status !== 'ok'
|
|
231
246
|
|| detailed.stat.isSymbolicLink()
|
|
232
247
|
|| !detailed.stat.isDirectory()
|
|
248
|
+
|| Number(opened.dev) !== Number(detailed.stat.dev)
|
|
249
|
+
|| Number(opened.ino) !== Number(detailed.stat.ino)
|
|
233
250
|
|| (typeof process.getuid === 'function' && Number(detailed.stat.uid) !== process.getuid())
|
|
234
251
|
|| (Number(detailed.stat.mode) & 0o077) !== 0) {
|
|
235
252
|
_fail(NATIVE_LEAD_SETTINGS_CLIENT_CODES.RESERVE_FAILED);
|
|
@@ -746,6 +763,7 @@ function createNativeLeadSettingsClient(options = {}) {
|
|
|
746
763
|
const tracked = new Set(); // every in-flight preparation, from its reservation onward
|
|
747
764
|
const adoptions = new Set(); // every in-flight adoption, joined by close() too (D-31)
|
|
748
765
|
const obligations = new Map(); // retained cleanup obligations, keyed for stable reporting
|
|
766
|
+
const reservations = new Set(); // every reservation holding directory handles, until closed
|
|
749
767
|
const obligationKeys = new Map(); // reservation -> Set<key>, so a LATER verified cleanup clears them
|
|
750
768
|
let obligationSequence = 0;
|
|
751
769
|
let closed = false;
|
|
@@ -794,12 +812,14 @@ function createNativeLeadSettingsClient(options = {}) {
|
|
|
794
812
|
attemptDir,
|
|
795
813
|
authorityCandidateDir,
|
|
796
814
|
descriptor: { attempt: null, authorityCandidate: null },
|
|
815
|
+
handles: { attempt: null, authorityCandidate: null }, // parent-side only, never serialized
|
|
797
816
|
};
|
|
817
|
+
reservations.add(reservation);
|
|
798
818
|
try {
|
|
799
819
|
await _ensureGeneratedDirAsync(path.dirname(attemptDir), dataDir);
|
|
800
820
|
await _ensureGeneratedDirAsync(nativeLeadTlsDir({ dataDir }), dataDir);
|
|
801
|
-
reservation.descriptor.attempt = await _reserveEmptyDirAsync(attemptDir);
|
|
802
|
-
reservation.descriptor.authorityCandidate = await _reserveEmptyDirAsync(authorityCandidateDir);
|
|
821
|
+
reservation.descriptor.attempt = await _reserveEmptyDirAsync(attemptDir, reservation.handles, 'attempt');
|
|
822
|
+
reservation.descriptor.authorityCandidate = await _reserveEmptyDirAsync(authorityCandidateDir, reservation.handles, 'authorityCandidate');
|
|
803
823
|
// The <= 4 KiB owner/ref stamp is written BEFORE dispatch, so a lost reply or a partial worker
|
|
804
824
|
// write still leaves a directory whose identity this client can clean by exact match.
|
|
805
825
|
const stamp = `${JSON.stringify({ version: 1, owner: { ...owner }, settingsRef, fingerprint: null })}\n`;
|
|
@@ -833,6 +853,17 @@ function createNativeLeadSettingsClient(options = {}) {
|
|
|
833
853
|
}
|
|
834
854
|
}
|
|
835
855
|
|
|
856
|
+
// Idempotent: each handle is detached before it is closed, so it is closed exactly once.
|
|
857
|
+
async function _closeReservationHandles(reservation) {
|
|
858
|
+
const handles = reservation && reservation.handles;
|
|
859
|
+
if (!handles) return;
|
|
860
|
+
for (const name of Object.keys(handles)) {
|
|
861
|
+
const handle = handles[name];
|
|
862
|
+
handles[name] = null;
|
|
863
|
+
if (handle) await handle.close().catch(() => { /* expected: a directory fd close cannot lose data; the handle is detached either way */ });
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
836
867
|
// The guarded cleanup of one reservation. The authority CANDIDATE directory is normally already
|
|
837
868
|
// gone (the worker removes its own link on success), so an absent candidate is not an obligation.
|
|
838
869
|
async function _cleanupReservation(reservation) {
|
|
@@ -843,7 +874,12 @@ function createNativeLeadSettingsClient(options = {}) {
|
|
|
843
874
|
const candidate = descriptor.authorityCandidate
|
|
844
875
|
? await _removeReservedDirGuarded(descriptor.authorityCandidate, NATIVE_LEAD_CANDIDATE_REMOVABLE)
|
|
845
876
|
: { removed: true, alreadyGone: true };
|
|
846
|
-
if (attempt.removed && candidate.removed)
|
|
877
|
+
if (attempt.removed && candidate.removed) {
|
|
878
|
+
// Closed only AFTER the verified removal: the open fd is what kept the guarded check honest.
|
|
879
|
+
await _closeReservationHandles(reservation);
|
|
880
|
+
reservations.delete(reservation);
|
|
881
|
+
return { cleaned: true };
|
|
882
|
+
}
|
|
847
883
|
return {
|
|
848
884
|
cleaned: false,
|
|
849
885
|
code: NATIVE_LEAD_SETTINGS_CLIENT_CODES.CLEANUP_DEFERRED,
|
|
@@ -1456,6 +1492,10 @@ function createNativeLeadSettingsClient(options = {}) {
|
|
|
1456
1492
|
// below — a single pass is therefore complete, and a committed host is never touched.
|
|
1457
1493
|
const uncommitted = Array.from(leases).filter((lease) => lease.kind === 'native-lead-settings' && !lease.isCommitted());
|
|
1458
1494
|
await Promise.all(uncommitted.map((lease) => lease.release().catch(() => undefined)));
|
|
1495
|
+
// Handles of reservations with no outstanding obligation are closed; retained obligations keep theirs.
|
|
1496
|
+
for (const reservation of reservations) {
|
|
1497
|
+
if (!obligationKeys.has(reservation)) await _closeReservationHandles(reservation);
|
|
1498
|
+
}
|
|
1459
1499
|
return { ok: obligations.size === 0, obligations: Array.from(obligations.values()) };
|
|
1460
1500
|
})();
|
|
1461
1501
|
return closePromise;
|