@provos/ironcurtain 0.11.0 → 0.13.0
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 +23 -19
- package/dist/cli-help.d.ts +12 -0
- package/dist/cli-help.js +21 -0
- package/dist/cli-help.js.map +1 -1
- package/dist/cli.js +14 -6
- package/dist/cli.js.map +1 -1
- package/dist/config/agent-model-guard.d.ts +14 -0
- package/dist/config/agent-model-guard.js +24 -0
- package/dist/config/agent-model-guard.js.map +1 -0
- package/dist/config/config-command.d.ts +33 -1
- package/dist/config/config-command.js +723 -11
- package/dist/config/config-command.js.map +1 -1
- package/dist/config/first-start.js +2 -8
- package/dist/config/first-start.js.map +1 -1
- package/dist/config/model-provider.d.ts +9 -0
- package/dist/config/model-provider.js +13 -0
- package/dist/config/model-provider.js.map +1 -1
- package/dist/config/openrouter-catalog.d.ts +46 -0
- package/dist/config/openrouter-catalog.js +111 -0
- package/dist/config/openrouter-catalog.js.map +1 -0
- package/dist/config/openrouter-models-fallback.d.ts +15 -0
- package/dist/config/openrouter-models-fallback.js +49 -0
- package/dist/config/openrouter-models-fallback.js.map +1 -0
- package/dist/config/paths.d.ts +48 -6
- package/dist/config/paths.js +63 -8
- package/dist/config/paths.js.map +1 -1
- package/dist/config/types.d.ts +8 -0
- package/dist/config/user-config.d.ts +179 -6
- package/dist/config/user-config.js +282 -5
- package/dist/config/user-config.js.map +1 -1
- package/dist/daemon/daemon-command.js +14 -5
- package/dist/daemon/daemon-command.js.map +1 -1
- package/dist/daemon/ironcurtain-daemon.d.ts +18 -0
- package/dist/daemon/ironcurtain-daemon.js +28 -1
- package/dist/daemon/ironcurtain-daemon.js.map +1 -1
- package/dist/daemon-client/daemon-client.d.ts +123 -0
- package/dist/daemon-client/daemon-client.js +324 -0
- package/dist/daemon-client/daemon-client.js.map +1 -0
- package/dist/docker/adapters/claude-code.js +109 -1
- package/dist/docker/adapters/claude-code.js.map +1 -1
- package/dist/docker/adapters/codex.d.ts +18 -0
- package/dist/docker/adapters/codex.js +376 -0
- package/dist/docker/adapters/codex.js.map +1 -0
- package/dist/docker/adapters/goose.js +38 -2
- package/dist/docker/adapters/goose.js.map +1 -1
- package/dist/docker/agent-adapter.d.ts +34 -6
- package/dist/docker/agent-adapter.js +2 -0
- package/dist/docker/agent-adapter.js.map +1 -1
- package/dist/docker/agent-registry.js +5 -0
- package/dist/docker/agent-registry.js.map +1 -1
- package/dist/docker/apple-container-manager.d.ts +49 -0
- package/dist/docker/apple-container-manager.js +486 -0
- package/dist/docker/apple-container-manager.js.map +1 -0
- package/dist/docker/ca.js +9 -2
- package/dist/docker/ca.js.map +1 -1
- package/dist/docker/code-mode-proxy.d.ts +11 -0
- package/dist/docker/code-mode-proxy.js +3 -1
- package/dist/docker/code-mode-proxy.js.map +1 -1
- package/dist/docker/container-lifecycle.d.ts +2 -2
- package/dist/docker/container-lifecycle.js +9 -0
- package/dist/docker/container-lifecycle.js.map +1 -1
- package/dist/docker/container-runtime.d.ts +29 -0
- package/dist/docker/container-runtime.js +76 -0
- package/dist/docker/container-runtime.js.map +1 -0
- package/dist/docker/docker-agent-session.d.ts +31 -0
- package/dist/docker/docker-agent-session.js +97 -7
- package/dist/docker/docker-agent-session.js.map +1 -1
- package/dist/docker/docker-infrastructure.d.ts +232 -15
- package/dist/docker/docker-infrastructure.js +762 -118
- package/dist/docker/docker-infrastructure.js.map +1 -1
- package/dist/docker/docker-manager.d.ts +25 -5
- package/dist/docker/docker-manager.js +388 -21
- package/dist/docker/docker-manager.js.map +1 -1
- package/dist/docker/docker-probe.d.ts +45 -0
- package/dist/docker/docker-probe.js +86 -0
- package/dist/docker/docker-probe.js.map +1 -0
- package/dist/docker/docker-progress-sink.d.ts +1 -1
- package/dist/docker/docker-resource-lifecycle.d.ts +71 -0
- package/dist/docker/docker-resource-lifecycle.js +454 -0
- package/dist/docker/docker-resource-lifecycle.js.map +1 -0
- package/dist/docker/gc-command.d.ts +2 -0
- package/dist/docker/gc-command.js +37 -0
- package/dist/docker/gc-command.js.map +1 -0
- package/dist/docker/mitm-proxy.d.ts +58 -5
- package/dist/docker/mitm-proxy.js +338 -26
- package/dist/docker/mitm-proxy.js.map +1 -1
- package/dist/docker/network-topology.d.ts +88 -0
- package/dist/docker/network-topology.js +167 -0
- package/dist/docker/network-topology.js.map +1 -0
- package/dist/docker/oauth-credentials.d.ts +13 -0
- package/dist/docker/oauth-credentials.js +125 -0
- package/dist/docker/oauth-credentials.js.map +1 -1
- package/dist/docker/openrouter.d.ts +107 -0
- package/dist/docker/openrouter.js +236 -0
- package/dist/docker/openrouter.js.map +1 -0
- package/dist/docker/orientation.js +1 -1
- package/dist/docker/orientation.js.map +1 -1
- package/dist/docker/package-types.d.ts +1 -1
- package/dist/docker/platform.d.ts +3 -1
- package/dist/docker/platform.js +3 -1
- package/dist/docker/platform.js.map +1 -1
- package/dist/docker/provider-config.d.ts +35 -1
- package/dist/docker/provider-config.js +93 -3
- package/dist/docker/provider-config.js.map +1 -1
- package/dist/docker/provision-lock.d.ts +46 -0
- package/dist/docker/provision-lock.js +152 -0
- package/dist/docker/provision-lock.js.map +1 -0
- package/dist/docker/pty-session.d.ts +22 -0
- package/dist/docker/pty-session.js +238 -84
- package/dist/docker/pty-session.js.map +1 -1
- package/dist/docker/pty-types.d.ts +15 -0
- package/dist/docker/pty-types.js +8 -0
- package/dist/docker/pty-types.js.map +1 -1
- package/dist/docker/registry-proxy.d.ts +46 -0
- package/dist/docker/registry-proxy.js +255 -1
- package/dist/docker/registry-proxy.js.map +1 -1
- package/dist/docker/resource-limits.d.ts +14 -5
- package/dist/docker/resource-limits.js +11 -8
- package/dist/docker/resource-limits.js.map +1 -1
- package/dist/docker/sse-extractor.js +55 -5
- package/dist/docker/sse-extractor.js.map +1 -1
- package/dist/docker/stream-delay.d.ts +86 -0
- package/dist/docker/stream-delay.js +218 -0
- package/dist/docker/stream-delay.js.map +1 -0
- package/dist/docker/token-stream-types.d.ts +15 -0
- package/dist/docker/trajectory-capture.d.ts +79 -0
- package/dist/docker/trajectory-capture.js +521 -0
- package/dist/docker/trajectory-capture.js.map +1 -0
- package/dist/docker/trajectory-reassembler.d.ts +286 -0
- package/dist/docker/trajectory-reassembler.js +1010 -0
- package/dist/docker/trajectory-reassembler.js.map +1 -0
- package/dist/docker/trajectory-tap.d.ts +100 -0
- package/dist/docker/trajectory-tap.js +410 -0
- package/dist/docker/trajectory-tap.js.map +1 -0
- package/dist/docker/trajectory-types.d.ts +187 -0
- package/dist/docker/trajectory-types.js +43 -0
- package/dist/docker/trajectory-types.js.map +1 -0
- package/dist/docker/types.d.ts +116 -12
- package/dist/doctor/checks.d.ts +39 -6
- package/dist/doctor/checks.js +129 -25
- package/dist/doctor/checks.js.map +1 -1
- package/dist/doctor/doctor-command.js +39 -9
- package/dist/doctor/doctor-command.js.map +1 -1
- package/dist/doctor/mcp-liveness.d.ts +1 -1
- package/dist/doctor/mcp-liveness.js +12 -4
- package/dist/doctor/mcp-liveness.js.map +1 -1
- package/dist/escalation/escalation-watcher.js +6 -0
- package/dist/escalation/escalation-watcher.js.map +1 -1
- package/dist/{mux → escalation}/trusted-input.d.ts +7 -4
- package/dist/{mux → escalation}/trusted-input.js +8 -5
- package/dist/escalation/trusted-input.js.map +1 -0
- package/dist/hash.js.map +1 -1
- package/dist/index.js +65 -16
- package/dist/index.js.map +1 -1
- package/dist/mux/mux-app.d.ts +9 -0
- package/dist/mux/mux-app.js +18 -16
- package/dist/mux/mux-app.js.map +1 -1
- package/dist/mux/mux-command.js +26 -8
- package/dist/mux/mux-command.js.map +1 -1
- package/dist/mux/mux-input-handler.d.ts +26 -2
- package/dist/mux/mux-input-handler.js +93 -11
- package/dist/mux/mux-input-handler.js.map +1 -1
- package/dist/mux/mux-renderer.d.ts +2 -1
- package/dist/mux/mux-renderer.js +67 -0
- package/dist/mux/mux-renderer.js.map +1 -1
- package/dist/mux/provider-profile-snapshot.d.ts +44 -0
- package/dist/mux/provider-profile-snapshot.js +56 -0
- package/dist/mux/provider-profile-snapshot.js.map +1 -0
- package/dist/mux/types.d.ts +3 -2
- package/dist/mux/types.js +6 -2
- package/dist/mux/types.js.map +1 -1
- package/dist/observe/observe-command.js +112 -125
- package/dist/observe/observe-command.js.map +1 -1
- package/dist/persona/broad-policy-validator.d.ts +95 -0
- package/dist/persona/broad-policy-validator.js +171 -0
- package/dist/persona/broad-policy-validator.js.map +1 -0
- package/dist/persona/compile-persona-policy.d.ts +54 -2
- package/dist/persona/compile-persona-policy.js +29 -4
- package/dist/persona/compile-persona-policy.js.map +1 -1
- package/dist/persona/event-bus-progress-reporter.d.ts +57 -0
- package/dist/persona/event-bus-progress-reporter.js +66 -0
- package/dist/persona/event-bus-progress-reporter.js.map +1 -0
- package/dist/persona/persona-command.js +46 -76
- package/dist/persona/persona-command.js.map +1 -1
- package/dist/persona/persona-compile-orchestrator.d.ts +101 -0
- package/dist/persona/persona-compile-orchestrator.js +504 -0
- package/dist/persona/persona-compile-orchestrator.js.map +1 -0
- package/dist/persona/persona-service.d.ts +137 -0
- package/dist/persona/persona-service.js +263 -0
- package/dist/persona/persona-service.js.map +1 -0
- package/dist/persona/policy-mutation-audit.d.ts +109 -0
- package/dist/persona/policy-mutation-audit.js +179 -0
- package/dist/persona/policy-mutation-audit.js.map +1 -0
- package/dist/persona/rule-delta.d.ts +30 -0
- package/dist/persona/rule-delta.js +82 -0
- package/dist/persona/rule-delta.js.map +1 -0
- package/dist/persona/types.d.ts +12 -0
- package/dist/pipeline/annotate.js +10 -2
- package/dist/pipeline/annotate.js.map +1 -1
- package/dist/pipeline/compile.js +14 -2
- package/dist/pipeline/compile.js.map +1 -1
- package/dist/pipeline/constitution-compiler.js.map +1 -1
- package/dist/pipeline/generate-with-repair.js.map +1 -1
- package/dist/pipeline/pipeline-runner.d.ts +63 -4
- package/dist/pipeline/pipeline-runner.js +159 -50
- package/dist/pipeline/pipeline-runner.js.map +1 -1
- package/dist/pipeline/pipeline-shared.d.ts +32 -3
- package/dist/pipeline/pipeline-shared.js +76 -8
- package/dist/pipeline/pipeline-shared.js.map +1 -1
- package/dist/pipeline/refresh-lists.js +3 -5
- package/dist/pipeline/refresh-lists.js.map +1 -1
- package/dist/pipeline/scenario-schema-validator.js.map +1 -1
- package/dist/{mux → pty}/pty-bridge.d.ts +41 -3
- package/dist/{mux → pty}/pty-bridge.js +71 -18
- package/dist/pty/pty-bridge.js.map +1 -0
- package/dist/pty/resolve-ironcurtain-bin.d.ts +17 -0
- package/dist/pty/resolve-ironcurtain-bin.js +22 -0
- package/dist/pty/resolve-ironcurtain-bin.js.map +1 -0
- package/dist/sandbox/index.d.ts +19 -4
- package/dist/sandbox/index.js +31 -18
- package/dist/sandbox/index.js.map +1 -1
- package/dist/servers/fetch-server.js +2 -0
- package/dist/servers/fetch-server.js.map +1 -1
- package/dist/session/index.d.ts +1 -1
- package/dist/session/index.js +20 -2
- package/dist/session/index.js.map +1 -1
- package/dist/session/preflight.d.ts +20 -35
- package/dist/session/preflight.js +92 -109
- package/dist/session/preflight.js.map +1 -1
- package/dist/session/resource-budget-tracker.js +8 -0
- package/dist/session/resource-budget-tracker.js.map +1 -1
- package/dist/session/session-manager.d.ts +7 -0
- package/dist/session/session-manager.js.map +1 -1
- package/dist/session/types.d.ts +23 -0
- package/dist/signal/bot-command.js +6 -1
- package/dist/signal/bot-command.js.map +1 -1
- package/dist/signal/setup-signal.js +1 -1
- package/dist/signal/setup-signal.js.map +1 -1
- package/dist/signal/signal-bot-daemon.d.ts +1 -0
- package/dist/signal/signal-bot-daemon.js +9 -2
- package/dist/signal/signal-bot-daemon.js.map +1 -1
- package/dist/signal/signal-container.d.ts +3 -3
- package/dist/signal/signal-container.js +2 -2
- package/dist/signal/signal-container.js.map +1 -1
- package/dist/trusted-process/call-circuit-breaker.d.ts +6 -0
- package/dist/trusted-process/call-circuit-breaker.js +22 -3
- package/dist/trusted-process/call-circuit-breaker.js.map +1 -1
- package/dist/trusted-process/container-command.d.ts +35 -0
- package/dist/trusted-process/container-command.js +49 -0
- package/dist/trusted-process/container-command.js.map +1 -0
- package/dist/trusted-process/mcp-error-utils.js +4 -1
- package/dist/trusted-process/mcp-error-utils.js.map +1 -1
- package/dist/trusted-process/mcp-proxy-server.js +37 -3
- package/dist/trusted-process/mcp-proxy-server.js.map +1 -1
- package/dist/trusted-process/policy-engine.js +13 -2
- package/dist/trusted-process/policy-engine.js.map +1 -1
- package/dist/trusted-process/tcp-server-transport.d.ts +19 -1
- package/dist/trusted-process/tcp-server-transport.js +56 -10
- package/dist/trusted-process/tcp-server-transport.js.map +1 -1
- package/dist/trusted-process/tool-call-coordinator.d.ts +22 -8
- package/dist/trusted-process/tool-call-coordinator.js +149 -65
- package/dist/trusted-process/tool-call-coordinator.js.map +1 -1
- package/dist/trusted-process/tool-call-pipeline.d.ts +8 -0
- package/dist/trusted-process/tool-call-pipeline.js +68 -34
- package/dist/trusted-process/tool-call-pipeline.js.map +1 -1
- package/dist/util/atomic-write.d.ts +31 -0
- package/dist/util/atomic-write.js +43 -0
- package/dist/util/atomic-write.js.map +1 -0
- package/dist/utils/preflight-checks.js +1 -3
- package/dist/utils/preflight-checks.js.map +1 -1
- package/dist/utils/stdio-guard.d.ts +1 -0
- package/dist/utils/stdio-guard.js +27 -0
- package/dist/utils/stdio-guard.js.map +1 -0
- package/dist/web-ui/__tests__/config-dispatch.test.d.ts +13 -0
- package/dist/web-ui/__tests__/config-dispatch.test.js +383 -0
- package/dist/web-ui/__tests__/config-dispatch.test.js.map +1 -0
- package/dist/web-ui/__tests__/json-rpc-dispatch.test.js +6 -0
- package/dist/web-ui/__tests__/json-rpc-dispatch.test.js.map +1 -1
- package/dist/web-ui/conn-registry.d.ts +14 -0
- package/dist/web-ui/conn-registry.js +25 -0
- package/dist/web-ui/conn-registry.js.map +1 -0
- package/dist/web-ui/dispatch/config-dispatch.d.ts +15 -0
- package/dist/web-ui/dispatch/config-dispatch.js +277 -0
- package/dist/web-ui/dispatch/config-dispatch.js.map +1 -0
- package/dist/web-ui/dispatch/escalation-dispatch.js +11 -2
- package/dist/web-ui/dispatch/escalation-dispatch.js.map +1 -1
- package/dist/web-ui/dispatch/persona-dispatch.d.ts +46 -2
- package/dist/web-ui/dispatch/persona-dispatch.js +209 -56
- package/dist/web-ui/dispatch/persona-dispatch.js.map +1 -1
- package/dist/web-ui/dispatch/pty-dispatch.d.ts +15 -0
- package/dist/web-ui/dispatch/pty-dispatch.js +66 -0
- package/dist/web-ui/dispatch/pty-dispatch.js.map +1 -0
- package/dist/web-ui/dispatch/session-dispatch.js +89 -6
- package/dist/web-ui/dispatch/session-dispatch.js.map +1 -1
- package/dist/web-ui/dispatch/types.d.ts +35 -0
- package/dist/web-ui/dispatch/types.js +42 -1
- package/dist/web-ui/dispatch/types.js.map +1 -1
- package/dist/web-ui/dispatch/workflow-dispatch.js +116 -5
- package/dist/web-ui/dispatch/workflow-dispatch.js.map +1 -1
- package/dist/web-ui/json-rpc-dispatch.js +4 -1
- package/dist/web-ui/json-rpc-dispatch.js.map +1 -1
- package/dist/web-ui/pty-session-manager.d.ts +234 -0
- package/dist/web-ui/pty-session-manager.js +599 -0
- package/dist/web-ui/pty-session-manager.js.map +1 -0
- package/dist/web-ui/web-event-bus.d.ts +34 -1
- package/dist/web-ui/web-event-bus.js.map +1 -1
- package/dist/web-ui/web-session-transport.js.map +1 -1
- package/dist/web-ui/web-ui-server.d.ts +22 -0
- package/dist/web-ui/web-ui-server.js +41 -0
- package/dist/web-ui/web-ui-server.js.map +1 -1
- package/dist/web-ui/web-ui-types.d.ts +212 -5
- package/dist/web-ui/web-ui-types.js.map +1 -1
- package/dist/web-ui-static/assets/index-CCVI4nEK.css +1 -0
- package/dist/web-ui-static/assets/index-Dm012gvY.js +114 -0
- package/dist/web-ui-static/index.html +2 -2
- package/dist/workflow/cli-shared.d.ts +62 -0
- package/dist/workflow/cli-shared.js +117 -0
- package/dist/workflow/cli-shared.js.map +1 -0
- package/dist/workflow/cli-support.js +0 -2
- package/dist/workflow/cli-support.js.map +1 -1
- package/dist/workflow/container-snapshots.d.ts +29 -0
- package/dist/workflow/container-snapshots.js +137 -0
- package/dist/workflow/container-snapshots.js.map +1 -0
- package/dist/workflow/daemon-gate-commands.d.ts +21 -0
- package/dist/workflow/daemon-gate-commands.js +595 -0
- package/dist/workflow/daemon-gate-commands.js.map +1 -0
- package/dist/workflow/discovery.d.ts +29 -0
- package/dist/workflow/discovery.js +67 -7
- package/dist/workflow/discovery.js.map +1 -1
- package/dist/workflow/lane-template.d.ts +89 -0
- package/dist/workflow/lane-template.js +141 -0
- package/dist/workflow/lane-template.js.map +1 -0
- package/dist/workflow/lint.d.ts +7 -1
- package/dist/workflow/lint.js +186 -3
- package/dist/workflow/lint.js.map +1 -1
- package/dist/workflow/machine-builder.d.ts +90 -2
- package/dist/workflow/machine-builder.js +222 -29
- package/dist/workflow/machine-builder.js.map +1 -1
- package/dist/workflow/message-log.d.ts +15 -1
- package/dist/workflow/message-log.js.map +1 -1
- package/dist/workflow/orchestrator.d.ts +505 -1
- package/dist/workflow/orchestrator.js +1725 -69
- package/dist/workflow/orchestrator.js.map +1 -1
- package/dist/workflow/prompt-builder.js +2 -1
- package/dist/workflow/prompt-builder.js.map +1 -1
- package/dist/workflow/run-state-command.js +1 -1
- package/dist/workflow/run-state-command.js.map +1 -1
- package/dist/workflow/shutdown-signals.d.ts +7 -0
- package/dist/workflow/shutdown-signals.js +40 -0
- package/dist/workflow/shutdown-signals.js.map +1 -0
- package/dist/workflow/status-parser.js +19 -4
- package/dist/workflow/status-parser.js.map +1 -1
- package/dist/workflow/types.d.ts +123 -23
- package/dist/workflow/types.js +2 -0
- package/dist/workflow/types.js.map +1 -1
- package/dist/workflow/validate.d.ts +23 -1
- package/dist/workflow/validate.js +311 -22
- package/dist/workflow/validate.js.map +1 -1
- package/dist/workflow/workflow-command.d.ts +0 -17
- package/dist/workflow/workflow-command.js +118 -94
- package/dist/workflow/workflow-command.js.map +1 -1
- package/dist/workflow/workflow-discovery.d.ts +2 -0
- package/dist/workflow/workflow-discovery.js +22 -1
- package/dist/workflow/workflow-discovery.js.map +1 -1
- package/dist/workflow/workflow-manager.d.ts +34 -0
- package/dist/workflow/workflow-manager.js +52 -1
- package/dist/workflow/workflow-manager.js.map +1 -1
- package/dist/workflow/workflows/design-and-code/README.md +50 -0
- package/dist/workflow/workflows/deterministic-eval-smoke/scripts/format_report.js +29 -0
- package/dist/workflow/workflows/deterministic-eval-smoke/scripts/package.json +5 -0
- package/dist/workflow/workflows/deterministic-eval-smoke/scripts/requirements.txt +1 -0
- package/dist/workflow/workflows/deterministic-eval-smoke/scripts/run_eval.py +41 -0
- package/dist/workflow/workflows/deterministic-eval-smoke/workflow.yaml +57 -0
- package/dist/workflow/workflows/deterministic-verdict-smoke/scripts/classify.py +18 -0
- package/dist/workflow/workflows/deterministic-verdict-smoke/workflow.yaml +51 -0
- package/dist/workflow/workflows/evolve/README.md +66 -0
- package/dist/workflow/workflows/evolve/scripts/LICENSE +201 -0
- package/dist/workflow/workflows/evolve/scripts/README.md +314 -0
- package/dist/workflow/workflows/evolve/scripts/evolve-brief +16 -0
- package/dist/workflow/workflows/evolve/scripts/evolve-cognition +16 -0
- package/dist/workflow/workflows/evolve/scripts/evolve-db +16 -0
- package/dist/workflow/workflows/evolve/scripts/evolve-eval +16 -0
- package/dist/workflow/workflows/evolve/scripts/evolve-files +16 -0
- package/dist/workflow/workflows/evolve/scripts/evolve-summary +16 -0
- package/dist/workflow/workflows/evolve/scripts/evolve_core/__init__.py +14 -0
- package/dist/workflow/workflows/evolve/scripts/evolve_core/algorithms/__init__.py +17 -0
- package/dist/workflow/workflows/evolve/scripts/evolve_core/algorithms/base.py +21 -0
- package/dist/workflow/workflows/evolve/scripts/evolve_core/algorithms/factory.py +90 -0
- package/dist/workflow/workflows/evolve/scripts/evolve_core/algorithms/greedy.py +17 -0
- package/dist/workflow/workflows/evolve/scripts/evolve_core/algorithms/island.py +316 -0
- package/dist/workflow/workflows/evolve/scripts/evolve_core/algorithms/random.py +18 -0
- package/dist/workflow/workflows/evolve/scripts/evolve_core/algorithms/ucb1.py +55 -0
- package/dist/workflow/workflows/evolve/scripts/evolve_core/best_snapshot.py +62 -0
- package/dist/workflow/workflows/evolve/scripts/evolve_core/cli.py +752 -0
- package/dist/workflow/workflows/evolve/scripts/evolve_core/cognition.py +127 -0
- package/dist/workflow/workflows/evolve/scripts/evolve_core/database.py +218 -0
- package/dist/workflow/workflows/evolve/scripts/evolve_core/diff.py +57 -0
- package/dist/workflow/workflows/evolve/scripts/evolve_core/embedding.py +84 -0
- package/dist/workflow/workflows/evolve/scripts/evolve_core/file_lock.py +45 -0
- package/dist/workflow/workflows/evolve/scripts/evolve_core/run_state.py +343 -0
- package/dist/workflow/workflows/evolve/scripts/evolve_core/sampling_config.py +173 -0
- package/dist/workflow/workflows/evolve/scripts/evolve_core/structures.py +93 -0
- package/dist/workflow/workflows/evolve/scripts/evolve_core/vector_index.py +191 -0
- package/dist/workflow/workflows/evolve/scripts/evolve_result.py +1503 -0
- package/dist/workflow/workflows/evolve/scripts/requirements.txt +2 -0
- package/dist/workflow/workflows/evolve/workflow.yaml +646 -0
- package/dist/workflow/workflows/test-email-summary/workflow.yaml +1 -0
- package/dist/workflow/workflows/vuln-discovery/README.md +54 -0
- package/dist/workflow/workflows/vuln-discovery/skills/harness-design-fuzzing/SKILL.md +6 -1
- package/dist/workflow/workflows/vuln-discovery/skills/memory-safety-c-cpp/SKILL.md +1 -0
- package/dist/workflow/workflows/vuln-discovery/skills/vulnerability-triage/SKILL.md +4 -0
- package/dist/workflow/workflows/vuln-discovery/workflow.yaml +13 -8
- package/docker/Dockerfile.base +22 -0
- package/docker/Dockerfile.base.arm64 +18 -0
- package/docker/Dockerfile.claude-code +3 -1
- package/docker/Dockerfile.codex +17 -0
- package/docker/Dockerfile.goose +3 -1
- package/docker/entrypoint-claude-code.sh +28 -45
- package/docker/entrypoint-codex.sh +42 -0
- package/docker/entrypoint-goose.sh +5 -24
- package/docker/entrypoint-uid-remap.sh +93 -0
- package/package.json +39 -30
- package/src/workflow/workflows/design-and-code/README.md +50 -0
- package/src/workflow/workflows/deterministic-eval-smoke/scripts/format_report.js +29 -0
- package/src/workflow/workflows/deterministic-eval-smoke/scripts/package.json +5 -0
- package/src/workflow/workflows/deterministic-eval-smoke/scripts/requirements.txt +1 -0
- package/src/workflow/workflows/deterministic-eval-smoke/scripts/run_eval.py +41 -0
- package/src/workflow/workflows/deterministic-eval-smoke/workflow.yaml +57 -0
- package/src/workflow/workflows/deterministic-verdict-smoke/scripts/classify.py +18 -0
- package/src/workflow/workflows/deterministic-verdict-smoke/workflow.yaml +51 -0
- package/src/workflow/workflows/evolve/README.md +66 -0
- package/src/workflow/workflows/evolve/scripts/LICENSE +201 -0
- package/src/workflow/workflows/evolve/scripts/README.md +314 -0
- package/src/workflow/workflows/evolve/scripts/evolve-brief +16 -0
- package/src/workflow/workflows/evolve/scripts/evolve-cognition +16 -0
- package/src/workflow/workflows/evolve/scripts/evolve-db +16 -0
- package/src/workflow/workflows/evolve/scripts/evolve-eval +16 -0
- package/src/workflow/workflows/evolve/scripts/evolve-files +16 -0
- package/src/workflow/workflows/evolve/scripts/evolve-summary +16 -0
- package/src/workflow/workflows/evolve/scripts/evolve_core/__init__.py +14 -0
- package/src/workflow/workflows/evolve/scripts/evolve_core/algorithms/__init__.py +17 -0
- package/src/workflow/workflows/evolve/scripts/evolve_core/algorithms/base.py +21 -0
- package/src/workflow/workflows/evolve/scripts/evolve_core/algorithms/factory.py +90 -0
- package/src/workflow/workflows/evolve/scripts/evolve_core/algorithms/greedy.py +17 -0
- package/src/workflow/workflows/evolve/scripts/evolve_core/algorithms/island.py +316 -0
- package/src/workflow/workflows/evolve/scripts/evolve_core/algorithms/random.py +18 -0
- package/src/workflow/workflows/evolve/scripts/evolve_core/algorithms/ucb1.py +55 -0
- package/src/workflow/workflows/evolve/scripts/evolve_core/best_snapshot.py +62 -0
- package/src/workflow/workflows/evolve/scripts/evolve_core/cli.py +752 -0
- package/src/workflow/workflows/evolve/scripts/evolve_core/cognition.py +127 -0
- package/src/workflow/workflows/evolve/scripts/evolve_core/database.py +218 -0
- package/src/workflow/workflows/evolve/scripts/evolve_core/diff.py +57 -0
- package/src/workflow/workflows/evolve/scripts/evolve_core/embedding.py +84 -0
- package/src/workflow/workflows/evolve/scripts/evolve_core/file_lock.py +45 -0
- package/src/workflow/workflows/evolve/scripts/evolve_core/run_state.py +343 -0
- package/src/workflow/workflows/evolve/scripts/evolve_core/sampling_config.py +173 -0
- package/src/workflow/workflows/evolve/scripts/evolve_core/structures.py +93 -0
- package/src/workflow/workflows/evolve/scripts/evolve_core/vector_index.py +191 -0
- package/src/workflow/workflows/evolve/scripts/evolve_result.py +1503 -0
- package/src/workflow/workflows/evolve/scripts/requirements.txt +2 -0
- package/src/workflow/workflows/evolve/workflow.yaml +646 -0
- package/src/workflow/workflows/test-email-summary/workflow.yaml +1 -0
- package/src/workflow/workflows/vuln-discovery/README.md +54 -0
- package/src/workflow/workflows/vuln-discovery/skills/harness-design-fuzzing/SKILL.md +6 -1
- package/src/workflow/workflows/vuln-discovery/skills/memory-safety-c-cpp/SKILL.md +1 -0
- package/src/workflow/workflows/vuln-discovery/skills/vulnerability-triage/SKILL.md +4 -0
- package/src/workflow/workflows/vuln-discovery/workflow.yaml +13 -8
- package/dist/mux/pty-bridge.js.map +0 -1
- package/dist/mux/trusted-input.js.map +0 -1
- package/dist/web-ui-static/assets/index-BFO5z1js.js +0 -76
- package/dist/web-ui-static/assets/index-CCUwrkIL.css +0 -1
- package/packages/web-ui/README.md +0 -113
|
@@ -9,8 +9,9 @@ import * as p from '@clack/prompts';
|
|
|
9
9
|
import { readFileSync } from 'node:fs';
|
|
10
10
|
import { resolve, dirname } from 'node:path';
|
|
11
11
|
import { fileURLToPath } from 'node:url';
|
|
12
|
-
import { loadUserConfig, saveUserConfig, validateModelId, ESCALATION_TIMEOUT_MIN, ESCALATION_TIMEOUT_MAX, WEB_SEARCH_PROVIDERS, WEB_SEARCH_PROVIDER_LABELS, WEB_SEARCH_PROVIDER_URLS, GOOSE_PROVIDERS, DOCKER_AGENTS, SESSION_MODES, } from './user-config.js';
|
|
12
|
+
import { loadUserConfig, saveUserConfig, validateModelId, ESCALATION_TIMEOUT_MIN, ESCALATION_TIMEOUT_MAX, WEB_SEARCH_PROVIDERS, WEB_SEARCH_PROVIDER_LABELS, WEB_SEARCH_PROVIDER_URLS, GOOSE_PROVIDERS, DOCKER_AGENTS, SESSION_MODES, CONTAINER_RUNTIMES, NATIVE_PROFILE_NAME, DEFAULT_GLM_SLUG, maskApiKey, cloneProviderPreference, } from './user-config.js';
|
|
13
13
|
import { getUserConfigPath } from './paths.js';
|
|
14
|
+
import { listOpenrouterModels, catalogEnforces, } from './openrouter-catalog.js';
|
|
14
15
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
16
|
/** Known model options for selection prompts. */
|
|
16
17
|
const KNOWN_MODELS = [
|
|
@@ -57,17 +58,97 @@ export function formatCost(n) {
|
|
|
57
58
|
return 'disabled';
|
|
58
59
|
return `$${n.toFixed(2)}`;
|
|
59
60
|
}
|
|
60
|
-
export function maskApiKey(key) {
|
|
61
|
-
if (!key)
|
|
62
|
-
return 'none';
|
|
63
|
-
if (key.length <= 6)
|
|
64
|
-
return '***';
|
|
65
|
-
return key.slice(0, 3) + '...' + key.slice(-3);
|
|
66
|
-
}
|
|
67
61
|
function formatModelShort(id) {
|
|
68
62
|
const known = KNOWN_MODELS.find((m) => m.value === id);
|
|
69
63
|
return known ? known.label : id;
|
|
70
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* The current editor view of the `profiles` record, in input shape (openrouter
|
|
67
|
+
* profiles carry the fields the editor mutates). A pending `profiles` record,
|
|
68
|
+
* when present, is already the whole record (read-modify-write) and replaces the
|
|
69
|
+
* resolved view; otherwise the resolved profiles are converted to input shape.
|
|
70
|
+
* The implicit `native` profile is intentionally excluded — it is never
|
|
71
|
+
* persisted and is always shown separately as read-only.
|
|
72
|
+
*/
|
|
73
|
+
function currentProfiles(resolved, pending) {
|
|
74
|
+
if (pending.modelProviders?.profiles) {
|
|
75
|
+
return { ...pending.modelProviders.profiles };
|
|
76
|
+
}
|
|
77
|
+
const view = {};
|
|
78
|
+
for (const [name, profile] of Object.entries(resolved.modelProviders.profiles)) {
|
|
79
|
+
if (name === NATIVE_PROFILE_NAME)
|
|
80
|
+
continue;
|
|
81
|
+
view[name] = resolvedProfileToInput(profile);
|
|
82
|
+
}
|
|
83
|
+
return view;
|
|
84
|
+
}
|
|
85
|
+
/** Converts a resolved profile back to its input shape (drops resolution-only defaults where empty). */
|
|
86
|
+
function resolvedProfileToInput(profile) {
|
|
87
|
+
if (profile.type === 'native')
|
|
88
|
+
return { type: 'native' };
|
|
89
|
+
const input = { type: 'openrouter' };
|
|
90
|
+
if (profile.apiKey)
|
|
91
|
+
input.apiKey = profile.apiKey;
|
|
92
|
+
// A default-tracking profile OMITS `modelMap` so an edit round-trip re-persists
|
|
93
|
+
// the omission rather than pinning today's materialized DEFAULT_MODEL_MAP.
|
|
94
|
+
if (!profile.usesDefaultMap)
|
|
95
|
+
input.modelMap = profile.modelMap.map((r) => ({ match: r.match, model: r.model }));
|
|
96
|
+
const perAgent = {};
|
|
97
|
+
for (const agent of DOCKER_AGENTS) {
|
|
98
|
+
const slug = profile.perAgent[agent];
|
|
99
|
+
if (slug)
|
|
100
|
+
perAgent[agent] = slug;
|
|
101
|
+
}
|
|
102
|
+
if (Object.keys(perAgent).length > 0)
|
|
103
|
+
input.perAgent = perAgent;
|
|
104
|
+
if (profile.providerPreference) {
|
|
105
|
+
input.providerPreference = cloneProviderPreference(profile.providerPreference);
|
|
106
|
+
}
|
|
107
|
+
input.sessionAffinity = profile.sessionAffinity;
|
|
108
|
+
return input;
|
|
109
|
+
}
|
|
110
|
+
/** The current default profile name (pending override wins over the resolved value). */
|
|
111
|
+
function currentDefault(resolved, pending) {
|
|
112
|
+
return pending.modelProviders?.default ?? resolved.modelProviders.default;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* F10 — re-point a dangling `default` to `native`. Returns `NATIVE_PROFILE_NAME`
|
|
116
|
+
* when the current default no longer names a profile in `remainingNames` (and is
|
|
117
|
+
* not itself `native`), otherwise returns the current default unchanged. Pure so
|
|
118
|
+
* the delete-repoints-default invariant is unit-testable without the interactive
|
|
119
|
+
* flow. Persisting a `default` that names a missing profile would make the next
|
|
120
|
+
* `loadUserConfig` a HARD error (the Zod `.refine`), so callers must apply this
|
|
121
|
+
* in the same pending write that deletes a profile.
|
|
122
|
+
*/
|
|
123
|
+
export function repointDefaultAfterDelete(currentDefaultName, remainingNames) {
|
|
124
|
+
if (currentDefaultName === NATIVE_PROFILE_NAME)
|
|
125
|
+
return currentDefaultName;
|
|
126
|
+
return remainingNames.includes(currentDefaultName) ? currentDefaultName : NATIVE_PROFILE_NAME;
|
|
127
|
+
}
|
|
128
|
+
/** Compact one-line summary of an openrouter profile for list/hint rendering. */
|
|
129
|
+
function summarizeOpenrouterProfile(profile) {
|
|
130
|
+
const parts = [];
|
|
131
|
+
const map = profile.modelMap;
|
|
132
|
+
if (map && map.length === 0) {
|
|
133
|
+
parts.push('per-agent only');
|
|
134
|
+
}
|
|
135
|
+
else if (map && map.length > 0) {
|
|
136
|
+
parts.push(map.length === 1 ? `-> ${map[0].model}` : `${map.length} map rules`);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
parts.push('default map');
|
|
140
|
+
}
|
|
141
|
+
parts.push(`key: ${maskApiKey(profile.apiKey)}`);
|
|
142
|
+
return parts.join(', ');
|
|
143
|
+
}
|
|
144
|
+
/** Compact rendering of a modelMap for diffs: `*sonnet*->z-ai/glm-5.2; *opus*->…`. */
|
|
145
|
+
function formatModelMap(map) {
|
|
146
|
+
if (!map)
|
|
147
|
+
return 'default';
|
|
148
|
+
if (map.length === 0)
|
|
149
|
+
return 'per-agent only (empty map)';
|
|
150
|
+
return map.map((r) => `${r.match}->${r.model}`).join('; ');
|
|
151
|
+
}
|
|
71
152
|
export function computeDiff(resolved, pending) {
|
|
72
153
|
const diffs = [];
|
|
73
154
|
const topLevelKeys = [
|
|
@@ -79,6 +160,7 @@ export function computeDiff(resolved, pending) {
|
|
|
79
160
|
'gooseModel',
|
|
80
161
|
'preferredDockerAgent',
|
|
81
162
|
'preferredMode',
|
|
163
|
+
'containerRuntime',
|
|
82
164
|
];
|
|
83
165
|
for (const key of topLevelKeys) {
|
|
84
166
|
if (key in pending && pending[key] !== undefined && pending[key] !== resolved[key]) {
|
|
@@ -92,6 +174,7 @@ export function computeDiff(resolved, pending) {
|
|
|
92
174
|
'auditRedaction',
|
|
93
175
|
'memory',
|
|
94
176
|
'dockerResources',
|
|
177
|
+
'snapshot',
|
|
95
178
|
];
|
|
96
179
|
for (const section of nestedSections) {
|
|
97
180
|
const pendingSection = pending[section];
|
|
@@ -140,8 +223,77 @@ export function computeDiff(resolved, pending) {
|
|
|
140
223
|
}
|
|
141
224
|
}
|
|
142
225
|
}
|
|
226
|
+
diffModelProviders(resolved, pending, diffs);
|
|
143
227
|
return diffs;
|
|
144
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Dedicated `modelProviders` diff branch (not the generic nestedSections loop,
|
|
231
|
+
* which compares by reference and would spuriously diff freshly-materialized
|
|
232
|
+
* profile objects — m14). Deep-compares each profile field via JSON.stringify so
|
|
233
|
+
* a no-op read-modify-write yields an EMPTY diff; masks every `apiKey`; renders
|
|
234
|
+
* modelMap rows compactly.
|
|
235
|
+
*/
|
|
236
|
+
function diffModelProviders(resolved, pending, diffs) {
|
|
237
|
+
const pendingMp = pending.modelProviders;
|
|
238
|
+
if (!pendingMp)
|
|
239
|
+
return;
|
|
240
|
+
// default selector
|
|
241
|
+
const resolvedDefault = resolved.modelProviders.default;
|
|
242
|
+
if (pendingMp.default !== undefined && pendingMp.default !== resolvedDefault) {
|
|
243
|
+
diffs.push(['modelProviders.default', { from: resolvedDefault, to: pendingMp.default }]);
|
|
244
|
+
}
|
|
245
|
+
if (!pendingMp.profiles)
|
|
246
|
+
return;
|
|
247
|
+
const resolvedProfiles = resolved.modelProviders.profiles;
|
|
248
|
+
const pendingProfiles = pendingMp.profiles;
|
|
249
|
+
// Added / changed profiles.
|
|
250
|
+
for (const [name, pendingProfile] of Object.entries(pendingProfiles)) {
|
|
251
|
+
if (name === NATIVE_PROFILE_NAME)
|
|
252
|
+
continue;
|
|
253
|
+
const before = name in resolvedProfiles ? resolvedProfileToInput(resolvedProfiles[name]) : undefined;
|
|
254
|
+
diffOneProfile(name, before, pendingProfile, diffs);
|
|
255
|
+
}
|
|
256
|
+
// Removed profiles (present in resolved, absent in the whole-record pending write).
|
|
257
|
+
for (const name of Object.keys(resolvedProfiles)) {
|
|
258
|
+
if (name === NATIVE_PROFILE_NAME)
|
|
259
|
+
continue;
|
|
260
|
+
if (!(name in pendingProfiles)) {
|
|
261
|
+
diffs.push([`modelProviders.profiles.${name}`, { from: 'configured', to: 'removed' }]);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
/** Field-by-field diff of one profile, masking apiKey and rendering modelMap compactly. */
|
|
266
|
+
function diffOneProfile(name, before, after, diffs) {
|
|
267
|
+
const prefix = `modelProviders.profiles.${name}`;
|
|
268
|
+
if (!before) {
|
|
269
|
+
diffs.push([prefix, { from: 'none', to: `added (${after.type})` }]);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (before.type !== after.type) {
|
|
273
|
+
diffs.push([`${prefix}.type`, { from: before.type, to: after.type }]);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (before.type === 'native' || after.type === 'native')
|
|
277
|
+
return;
|
|
278
|
+
if (before.apiKey !== after.apiKey) {
|
|
279
|
+
diffs.push([`${prefix}.apiKey`, { from: maskApiKey(before.apiKey), to: maskApiKey(after.apiKey) }]);
|
|
280
|
+
}
|
|
281
|
+
if (JSON.stringify(before.modelMap) !== JSON.stringify(after.modelMap)) {
|
|
282
|
+
diffs.push([`${prefix}.modelMap`, { from: formatModelMap(before.modelMap), to: formatModelMap(after.modelMap) }]);
|
|
283
|
+
}
|
|
284
|
+
if (JSON.stringify(before.perAgent) !== JSON.stringify(after.perAgent)) {
|
|
285
|
+
diffs.push([`${prefix}.perAgent`, { from: before.perAgent ?? {}, to: after.perAgent ?? {} }]);
|
|
286
|
+
}
|
|
287
|
+
if (JSON.stringify(before.providerPreference) !== JSON.stringify(after.providerPreference)) {
|
|
288
|
+
diffs.push([
|
|
289
|
+
`${prefix}.providerPreference`,
|
|
290
|
+
{ from: before.providerPreference ?? 'default', to: after.providerPreference ?? 'default' },
|
|
291
|
+
]);
|
|
292
|
+
}
|
|
293
|
+
if (before.sessionAffinity !== after.sessionAffinity) {
|
|
294
|
+
diffs.push([`${prefix}.sessionAffinity`, { from: before.sessionAffinity, to: after.sessionAffinity }]);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
145
297
|
function formatDiffValue(key, value) {
|
|
146
298
|
if (value === null)
|
|
147
299
|
return 'disabled';
|
|
@@ -155,7 +307,11 @@ function formatDiffValue(key, value) {
|
|
|
155
307
|
return formatSeconds(value);
|
|
156
308
|
if (key.includes('Cost'))
|
|
157
309
|
return formatCost(value);
|
|
158
|
-
|
|
310
|
+
if (typeof value === 'string')
|
|
311
|
+
return value;
|
|
312
|
+
if (typeof value === 'number')
|
|
313
|
+
return String(value);
|
|
314
|
+
return JSON.stringify(value);
|
|
159
315
|
}
|
|
160
316
|
// ─── Model prompt ────────────────────────────────────────────
|
|
161
317
|
async function promptModelId(message, current) {
|
|
@@ -365,6 +521,76 @@ async function handleMemory(resolved, pending) {
|
|
|
365
521
|
}
|
|
366
522
|
}
|
|
367
523
|
}
|
|
524
|
+
async function handleSnapshot(resolved, pending) {
|
|
525
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- interactive loop exited via return
|
|
526
|
+
while (true) {
|
|
527
|
+
const currentEnabled = pending.snapshot?.enabled ?? resolved.snapshot.enabled;
|
|
528
|
+
const currentMaxAge = pending.snapshot?.maxAgeDays ?? resolved.snapshot.maxAgeDays;
|
|
529
|
+
const currentSweep = pending.snapshot?.sweepIntervalHours ?? resolved.snapshot.sweepIntervalHours;
|
|
530
|
+
const field = await p.select({
|
|
531
|
+
message: 'Container Snapshots',
|
|
532
|
+
options: [
|
|
533
|
+
{
|
|
534
|
+
value: 'enabled',
|
|
535
|
+
label: 'Enabled (kill switch — snapshot capture + GC for all workflows)',
|
|
536
|
+
hint: currentEnabled ? 'on' : 'off',
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
value: 'maxAgeDays',
|
|
540
|
+
label: 'Max snapshot age before GC (days)',
|
|
541
|
+
hint: currentMaxAge === null ? 'unbounded' : String(currentMaxAge),
|
|
542
|
+
},
|
|
543
|
+
{ value: 'sweepIntervalHours', label: 'GC sweep interval (hours)', hint: String(currentSweep) },
|
|
544
|
+
{ value: 'back', label: 'Back' },
|
|
545
|
+
],
|
|
546
|
+
});
|
|
547
|
+
if (isCancelled(field) || field === 'back')
|
|
548
|
+
return;
|
|
549
|
+
if (field === 'enabled') {
|
|
550
|
+
const enabled = await p.confirm({
|
|
551
|
+
message: 'Enable workflow container snapshots globally? ' +
|
|
552
|
+
'(off disables snapshot capture on stop and GC for all workflows)',
|
|
553
|
+
initialValue: currentEnabled,
|
|
554
|
+
});
|
|
555
|
+
if (isCancelled(enabled))
|
|
556
|
+
continue;
|
|
557
|
+
if (enabled !== currentEnabled) {
|
|
558
|
+
pending.snapshot = { ...pending.snapshot, enabled: enabled };
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
else if (field === 'maxAgeDays') {
|
|
562
|
+
const next = await promptNullableNumber({
|
|
563
|
+
message: 'Max snapshot age before automatic GC',
|
|
564
|
+
current: currentMaxAge,
|
|
565
|
+
validate: (n) => (n > 0 ? undefined : 'Must be a positive number of days'),
|
|
566
|
+
format: (n) => (n === null ? 'unbounded' : `${n} day(s)`),
|
|
567
|
+
});
|
|
568
|
+
if (next !== undefined && next !== currentMaxAge) {
|
|
569
|
+
pending.snapshot = { ...pending.snapshot, maxAgeDays: next };
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
else if (field === 'sweepIntervalHours') {
|
|
573
|
+
const input = await p.text({
|
|
574
|
+
message: 'GC sweep interval (hours):',
|
|
575
|
+
placeholder: String(currentSweep),
|
|
576
|
+
validate: (val) => {
|
|
577
|
+
if (!val || val.trim() === '')
|
|
578
|
+
return 'Must be a positive number';
|
|
579
|
+
const n = Number(val);
|
|
580
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
581
|
+
return 'Must be positive';
|
|
582
|
+
return undefined;
|
|
583
|
+
},
|
|
584
|
+
});
|
|
585
|
+
if (isCancelled(input))
|
|
586
|
+
continue;
|
|
587
|
+
const newVal = Number(input);
|
|
588
|
+
if (newVal !== currentSweep) {
|
|
589
|
+
pending.snapshot = { ...pending.snapshot, sweepIntervalHours: newVal };
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
368
594
|
async function handleResourceLimits(resolved, pending) {
|
|
369
595
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- interactive loop exited via return
|
|
370
596
|
while (true) {
|
|
@@ -576,6 +802,437 @@ async function handleWebSearch(resolved, pending) {
|
|
|
576
802
|
};
|
|
577
803
|
}
|
|
578
804
|
}
|
|
805
|
+
// ─── Model Providers ─────────────────────────────────────────
|
|
806
|
+
/** Returns a shallow copy of `obj` without the given key (avoids `delete obj[dynamicKey]`). */
|
|
807
|
+
function omitKey(obj, key) {
|
|
808
|
+
const copy = { ...obj };
|
|
809
|
+
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete -- key is intentionally dynamic here
|
|
810
|
+
delete copy[key];
|
|
811
|
+
return copy;
|
|
812
|
+
}
|
|
813
|
+
/**
|
|
814
|
+
* Writes the whole `profiles` record + `default` into pending (read-modify-write,
|
|
815
|
+
* required by the shallow `deepMergeConfig`). Callers always pass the complete
|
|
816
|
+
* view so no unmentioned profile is dropped unintentionally.
|
|
817
|
+
*/
|
|
818
|
+
function commitModelProviders(pending, profiles, defaultName) {
|
|
819
|
+
pending.modelProviders = { default: defaultName, profiles };
|
|
820
|
+
}
|
|
821
|
+
async function handleModelProviders(resolved, pending) {
|
|
822
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- interactive loop exited via return
|
|
823
|
+
while (true) {
|
|
824
|
+
const profiles = currentProfiles(resolved, pending);
|
|
825
|
+
const defaultName = currentDefault(resolved, pending);
|
|
826
|
+
const options = [
|
|
827
|
+
{
|
|
828
|
+
value: `profile:${NATIVE_PROFILE_NAME}`,
|
|
829
|
+
label: NATIVE_PROFILE_NAME,
|
|
830
|
+
hint: defaultName === NATIVE_PROFILE_NAME ? 'native routing (default, not editable)' : 'native routing',
|
|
831
|
+
},
|
|
832
|
+
];
|
|
833
|
+
for (const [name, profile] of Object.entries(profiles)) {
|
|
834
|
+
const summary = profile.type === 'openrouter' ? summarizeOpenrouterProfile(profile) : 'native';
|
|
835
|
+
options.push({
|
|
836
|
+
value: `profile:${name}`,
|
|
837
|
+
label: name,
|
|
838
|
+
hint: `${profile.type}, ${summary}${name === defaultName ? ' (default)' : ''}`,
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
options.push({ value: 'add', label: 'Add profile...' });
|
|
842
|
+
options.push({ value: 'default', label: 'Set default', hint: defaultName });
|
|
843
|
+
options.push({ value: 'back', label: 'Back' });
|
|
844
|
+
const action = await p.select({ message: 'Model Providers', options });
|
|
845
|
+
if (isCancelled(action) || action === 'back')
|
|
846
|
+
return;
|
|
847
|
+
if (action === 'add') {
|
|
848
|
+
await addProfile(resolved, pending);
|
|
849
|
+
}
|
|
850
|
+
else if (action === 'default') {
|
|
851
|
+
await setDefaultProfile(resolved, pending);
|
|
852
|
+
}
|
|
853
|
+
else if (typeof action === 'string' && action.startsWith('profile:')) {
|
|
854
|
+
const name = action.slice('profile:'.length);
|
|
855
|
+
if (name === NATIVE_PROFILE_NAME) {
|
|
856
|
+
p.note('The native profile uses today’s canonical routing and cannot be edited or deleted.', 'native');
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
await editProfile(resolved, pending, name);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
async function addProfile(resolved, pending) {
|
|
864
|
+
const profiles = currentProfiles(resolved, pending);
|
|
865
|
+
const name = await p.text({
|
|
866
|
+
message: 'Profile name:',
|
|
867
|
+
placeholder: 'e.g. glm-5.2',
|
|
868
|
+
validate: (val) => {
|
|
869
|
+
if (!val || val.trim() === '')
|
|
870
|
+
return 'Name is required';
|
|
871
|
+
if (val === NATIVE_PROFILE_NAME)
|
|
872
|
+
return `"${NATIVE_PROFILE_NAME}" is a reserved profile name.`;
|
|
873
|
+
if (val in profiles)
|
|
874
|
+
return `Profile "${val}" already exists.`;
|
|
875
|
+
return undefined;
|
|
876
|
+
},
|
|
877
|
+
});
|
|
878
|
+
if (isCancelled(name))
|
|
879
|
+
return;
|
|
880
|
+
// v1 supports only the 'openrouter' type; native is implicit and never user-defined.
|
|
881
|
+
p.note('OpenRouter routes Docker agents through openrouter.ai with a bound model map + key.\n' +
|
|
882
|
+
'Paste an sk-or-v1-... key; defaults map *sonnet*/*opus*/*haiku* -> ' +
|
|
883
|
+
`${DEFAULT_GLM_SLUG} with a soft z-ai cache pin.`, 'openrouter');
|
|
884
|
+
const apiKey = await p.text({
|
|
885
|
+
message: 'OpenRouter API key (sk-or-v1-...):',
|
|
886
|
+
placeholder: 'set via OPENROUTER_API_KEY env, or leave blank',
|
|
887
|
+
validate: () => undefined,
|
|
888
|
+
});
|
|
889
|
+
if (isCancelled(apiKey))
|
|
890
|
+
return;
|
|
891
|
+
const profile = { type: 'openrouter' };
|
|
892
|
+
if (apiKey)
|
|
893
|
+
profile.apiKey = apiKey;
|
|
894
|
+
profiles[name] = profile;
|
|
895
|
+
commitModelProviders(pending, profiles, currentDefault(resolved, pending));
|
|
896
|
+
}
|
|
897
|
+
async function setDefaultProfile(resolved, pending) {
|
|
898
|
+
const profiles = currentProfiles(resolved, pending);
|
|
899
|
+
const currentName = currentDefault(resolved, pending);
|
|
900
|
+
const options = [
|
|
901
|
+
{
|
|
902
|
+
value: NATIVE_PROFILE_NAME,
|
|
903
|
+
label: NATIVE_PROFILE_NAME,
|
|
904
|
+
hint: currentName === NATIVE_PROFILE_NAME ? '(current)' : undefined,
|
|
905
|
+
},
|
|
906
|
+
...Object.keys(profiles).map((name) => ({
|
|
907
|
+
value: name,
|
|
908
|
+
label: name,
|
|
909
|
+
hint: name === currentName ? '(current)' : undefined,
|
|
910
|
+
})),
|
|
911
|
+
];
|
|
912
|
+
const selected = await p.select({
|
|
913
|
+
message: 'Default profile (applies when no per-session choice is made):',
|
|
914
|
+
options,
|
|
915
|
+
});
|
|
916
|
+
if (isCancelled(selected))
|
|
917
|
+
return;
|
|
918
|
+
const name = selected;
|
|
919
|
+
if (name !== currentName) {
|
|
920
|
+
commitModelProviders(pending, profiles, name);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
async function editProfile(resolved, pending, name) {
|
|
924
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- interactive loop exited via return
|
|
925
|
+
while (true) {
|
|
926
|
+
const profiles = currentProfiles(resolved, pending);
|
|
927
|
+
if (!(name in profiles))
|
|
928
|
+
return;
|
|
929
|
+
const profile = profiles[name];
|
|
930
|
+
if (profile.type !== 'openrouter')
|
|
931
|
+
return;
|
|
932
|
+
const field = await p.select({
|
|
933
|
+
message: `Profile: ${name}`,
|
|
934
|
+
options: [
|
|
935
|
+
{ value: 'apiKey', label: 'API key', hint: maskApiKey(profile.apiKey) },
|
|
936
|
+
{ value: 'modelMap', label: 'Model map (glob -> slug)', hint: formatModelMap(profile.modelMap) },
|
|
937
|
+
{ value: 'perAgent', label: 'Per-agent model overrides', hint: perAgentSummary(profile) },
|
|
938
|
+
{
|
|
939
|
+
value: 'providerPreference',
|
|
940
|
+
label: 'Provider preference (cache pinning)',
|
|
941
|
+
hint: providerPreferenceSummary(profile.providerPreference),
|
|
942
|
+
},
|
|
943
|
+
{
|
|
944
|
+
value: 'sessionAffinity',
|
|
945
|
+
label: 'Session affinity (GLM cache)',
|
|
946
|
+
hint: (profile.sessionAffinity ?? true) ? 'on' : 'off',
|
|
947
|
+
},
|
|
948
|
+
{ value: 'delete', label: 'Delete profile' },
|
|
949
|
+
{ value: 'back', label: 'Back' },
|
|
950
|
+
],
|
|
951
|
+
});
|
|
952
|
+
if (isCancelled(field) || field === 'back')
|
|
953
|
+
return;
|
|
954
|
+
if (field === 'delete') {
|
|
955
|
+
const confirmed = await p.confirm({ message: `Delete profile "${name}"?`, initialValue: false });
|
|
956
|
+
if (isCancelled(confirmed) || !confirmed)
|
|
957
|
+
continue;
|
|
958
|
+
const remaining = omitKey(profiles, name);
|
|
959
|
+
// F10: never persist a `default` that names the just-deleted profile.
|
|
960
|
+
const nextDefault = repointDefaultAfterDelete(currentDefault(resolved, pending), Object.keys(remaining));
|
|
961
|
+
commitModelProviders(pending, remaining, nextDefault);
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
const updated = await editProfileField(field, profile);
|
|
965
|
+
if (updated) {
|
|
966
|
+
profiles[name] = updated;
|
|
967
|
+
commitModelProviders(pending, profiles, currentDefault(resolved, pending));
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
/** Edits one field of an openrouter profile, returning the updated profile or undefined (no change). */
|
|
972
|
+
async function editProfileField(field, profile) {
|
|
973
|
+
if (field === 'apiKey') {
|
|
974
|
+
const apiKey = await p.text({
|
|
975
|
+
message: 'OpenRouter API key (sk-or-v1-...):',
|
|
976
|
+
placeholder: profile.apiKey ? '(keep current)' : 'leave blank to use OPENROUTER_API_KEY env',
|
|
977
|
+
validate: () => undefined,
|
|
978
|
+
});
|
|
979
|
+
if (isCancelled(apiKey))
|
|
980
|
+
return undefined;
|
|
981
|
+
const next = { ...profile };
|
|
982
|
+
if (apiKey)
|
|
983
|
+
next.apiKey = apiKey;
|
|
984
|
+
else
|
|
985
|
+
delete next.apiKey;
|
|
986
|
+
return next;
|
|
987
|
+
}
|
|
988
|
+
if (field === 'modelMap')
|
|
989
|
+
return editModelMap(profile);
|
|
990
|
+
if (field === 'perAgent')
|
|
991
|
+
return editPerAgent(profile);
|
|
992
|
+
if (field === 'providerPreference')
|
|
993
|
+
return editProviderPreference(profile);
|
|
994
|
+
if (field === 'sessionAffinity') {
|
|
995
|
+
const enabled = await p.confirm({
|
|
996
|
+
message: 'Inject a stable session_id for GLM cache affinity?',
|
|
997
|
+
initialValue: profile.sessionAffinity ?? true,
|
|
998
|
+
});
|
|
999
|
+
if (isCancelled(enabled))
|
|
1000
|
+
return undefined;
|
|
1001
|
+
return { ...profile, sessionAffinity: enabled };
|
|
1002
|
+
}
|
|
1003
|
+
return undefined;
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Builds the option list for a model-slug autocomplete from the resolved catalog.
|
|
1007
|
+
*
|
|
1008
|
+
* - Every catalog slug becomes a `{ value, label }` option.
|
|
1009
|
+
* - `allowNone` prepends a `(none)` sentinel (value `''`) FIRST — the only way a
|
|
1010
|
+
* selection-only prompt can clear a per-agent override.
|
|
1011
|
+
* - A non-empty `current` absent from the catalog is appended as a grandfather
|
|
1012
|
+
* option, so editing a legacy/delisted slug never traps the user.
|
|
1013
|
+
*
|
|
1014
|
+
* There is intentionally NO refresh sentinel: `ironcurtain config` is short-lived
|
|
1015
|
+
* and fetches the catalog once, so re-running it IS the refresh path.
|
|
1016
|
+
*/
|
|
1017
|
+
export function buildSlugOptions(catalog, current, opts) {
|
|
1018
|
+
const options = catalog.models.map((slug) => ({ value: slug, label: slug }));
|
|
1019
|
+
if (opts.allowNone) {
|
|
1020
|
+
options.unshift({ value: '', label: '(none — use model map)' });
|
|
1021
|
+
}
|
|
1022
|
+
if (current !== '' && !catalog.models.includes(current)) {
|
|
1023
|
+
options.push({ value: current, label: `${current} (current, unverified)` });
|
|
1024
|
+
}
|
|
1025
|
+
return options;
|
|
1026
|
+
}
|
|
1027
|
+
/** Autocomplete (hard-block) for an authoritative catalog; free text for the bundled floor. */
|
|
1028
|
+
export function slugPromptMode(source) {
|
|
1029
|
+
return catalogEnforces(source) ? 'autocomplete' : 'freetext';
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Prompts for one OpenRouter model slug. Fetches the catalog lazily (module-cached)
|
|
1033
|
+
* so apiKey / provider-preference-only edits never pay the network cost.
|
|
1034
|
+
*
|
|
1035
|
+
* Authoritative catalog (`live`/`cache`): a selection-only `autocomplete` whose
|
|
1036
|
+
* options ARE the allowlist — a structural hard-block — with `validate` as
|
|
1037
|
+
* belt-and-suspenders. For an UNSET field the `(none)` sentinel is first, so a
|
|
1038
|
+
* no-op Enter keeps it unset — clack auto-preselects the first option and collapses
|
|
1039
|
+
* a falsy `initialValue` to undefined; a non-empty `current` is passed as
|
|
1040
|
+
* `initialValue` so editing an existing slug starts on it.
|
|
1041
|
+
*
|
|
1042
|
+
* Bundled floor (offline): the list is known-incomplete, so free-text entry with a
|
|
1043
|
+
* one-line warning — the value is accepted as-is.
|
|
1044
|
+
*
|
|
1045
|
+
* Returns the chosen slug (`''` clears when `allowNone`), or `undefined` on cancel.
|
|
1046
|
+
*/
|
|
1047
|
+
async function promptSlug(message, opts) {
|
|
1048
|
+
const { current, allowNone } = opts;
|
|
1049
|
+
const catalog = await listOpenrouterModels();
|
|
1050
|
+
if (slugPromptMode(catalog.source) === 'autocomplete') {
|
|
1051
|
+
// Allowlist = catalog slugs, plus `current` (grandfather a legacy/delisted slug)
|
|
1052
|
+
// and '' ONLY when the field is clearable (per-agent). A required field
|
|
1053
|
+
// (map-row model, allowNone:false) never admits an empty submit.
|
|
1054
|
+
const allowlist = new Set([...catalog.models, ...(current ? [current] : []), ...(allowNone ? [''] : [])]);
|
|
1055
|
+
const picked = await p.autocomplete({
|
|
1056
|
+
message,
|
|
1057
|
+
options: buildSlugOptions(catalog, current, { allowNone }),
|
|
1058
|
+
initialValue: current || '',
|
|
1059
|
+
initialUserInput: current || undefined,
|
|
1060
|
+
maxItems: 8,
|
|
1061
|
+
validate: (v) => (typeof v === 'string' && !allowlist.has(v) ? `Unknown model: ${v}` : undefined),
|
|
1062
|
+
});
|
|
1063
|
+
if (isCancelled(picked))
|
|
1064
|
+
return undefined;
|
|
1065
|
+
return picked;
|
|
1066
|
+
}
|
|
1067
|
+
p.note("Model catalog unavailable — slug can't be verified; entered value accepted as-is.");
|
|
1068
|
+
const typed = await p.text({
|
|
1069
|
+
message,
|
|
1070
|
+
placeholder: current || DEFAULT_GLM_SLUG,
|
|
1071
|
+
validate: allowNone ? () => undefined : (val) => (!val || !val.trim() ? 'Target slug is required' : undefined),
|
|
1072
|
+
});
|
|
1073
|
+
if (isCancelled(typed))
|
|
1074
|
+
return undefined;
|
|
1075
|
+
// Trim: a free-typed slug is persisted as-is, and " " would pass the schema's
|
|
1076
|
+
// min(1) yet break routing (and defeat "blank to clear" for per-agent). The web
|
|
1077
|
+
// path already trims (editableToDto / validateSlugs); keep the CLI consistent.
|
|
1078
|
+
return typed.trim();
|
|
1079
|
+
}
|
|
1080
|
+
async function editModelMap(profile) {
|
|
1081
|
+
const rows = [...(profile.modelMap ?? [])];
|
|
1082
|
+
p.note('Ordered glob -> slug rules; first match wins (matched against the requested model id).\n' +
|
|
1083
|
+
'An EMPTY map means "per-agent-only mode": no glob mapping, rely on per-agent overrides.\n' +
|
|
1084
|
+
'Leaving the map unset uses the built-in defaults (*sonnet*/*opus*/*haiku* -> ' +
|
|
1085
|
+
`${DEFAULT_GLM_SLUG}).`, 'Model map');
|
|
1086
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- interactive loop exited via return
|
|
1087
|
+
while (true) {
|
|
1088
|
+
const options = rows.map((row, i) => ({
|
|
1089
|
+
value: `row:${i}`,
|
|
1090
|
+
label: `${row.match} -> ${row.model}`,
|
|
1091
|
+
hint: 'select to remove',
|
|
1092
|
+
}));
|
|
1093
|
+
options.push({ value: 'add', label: 'Add rule...' });
|
|
1094
|
+
options.push({ value: 'done', label: 'Done', hint: rows.length === 0 ? 'empty = per-agent-only mode' : undefined });
|
|
1095
|
+
const action = await p.select({ message: `Model map (${rows.length} rule(s))`, options });
|
|
1096
|
+
if (isCancelled(action) || action === 'done')
|
|
1097
|
+
break;
|
|
1098
|
+
if (action === 'add') {
|
|
1099
|
+
const match = await p.text({
|
|
1100
|
+
message: 'Glob to match (e.g. *sonnet*):',
|
|
1101
|
+
validate: (val) => (!val ? 'Match glob is required' : undefined),
|
|
1102
|
+
});
|
|
1103
|
+
if (isCancelled(match))
|
|
1104
|
+
continue;
|
|
1105
|
+
const model = await promptSlug('Target OpenRouter slug (e.g. z-ai/glm-5.2):', {
|
|
1106
|
+
current: '',
|
|
1107
|
+
allowNone: false,
|
|
1108
|
+
});
|
|
1109
|
+
if (model === undefined)
|
|
1110
|
+
continue;
|
|
1111
|
+
rows.push({ match: match, model });
|
|
1112
|
+
}
|
|
1113
|
+
else if (typeof action === 'string' && action.startsWith('row:')) {
|
|
1114
|
+
const index = Number(action.slice('row:'.length));
|
|
1115
|
+
rows.splice(index, 1);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
return { ...profile, modelMap: rows };
|
|
1119
|
+
}
|
|
1120
|
+
async function editPerAgent(profile) {
|
|
1121
|
+
let perAgent = { ...(profile.perAgent ?? {}) };
|
|
1122
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- interactive loop exited via return
|
|
1123
|
+
while (true) {
|
|
1124
|
+
const options = DOCKER_AGENTS.map((agent) => ({
|
|
1125
|
+
value: agent,
|
|
1126
|
+
label: DOCKER_AGENT_LABELS[agent],
|
|
1127
|
+
hint: perAgent[agent] ?? 'none (use model map)',
|
|
1128
|
+
}));
|
|
1129
|
+
const selected = await p.select({
|
|
1130
|
+
message: 'Per-agent model overrides (wins over the model map)',
|
|
1131
|
+
options: [...options, { value: 'back', label: 'Back', hint: '' }],
|
|
1132
|
+
});
|
|
1133
|
+
if (isCancelled(selected) || selected === 'back')
|
|
1134
|
+
break;
|
|
1135
|
+
const agent = selected;
|
|
1136
|
+
const slug = await promptSlug(`${DOCKER_AGENT_LABELS[agent]} model slug (blank to clear):`, {
|
|
1137
|
+
current: perAgent[agent] ?? '',
|
|
1138
|
+
allowNone: true,
|
|
1139
|
+
});
|
|
1140
|
+
if (slug === undefined)
|
|
1141
|
+
continue;
|
|
1142
|
+
if (slug)
|
|
1143
|
+
perAgent[agent] = slug;
|
|
1144
|
+
else
|
|
1145
|
+
perAgent = omitKey(perAgent, agent);
|
|
1146
|
+
}
|
|
1147
|
+
const next = { ...profile };
|
|
1148
|
+
if (Object.keys(perAgent).length > 0)
|
|
1149
|
+
next.perAgent = perAgent;
|
|
1150
|
+
else
|
|
1151
|
+
delete next.perAgent;
|
|
1152
|
+
return next;
|
|
1153
|
+
}
|
|
1154
|
+
async function editProviderPreference(profile) {
|
|
1155
|
+
const pref = { ...(profile.providerPreference ?? {}) };
|
|
1156
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- interactive loop exited via return
|
|
1157
|
+
while (true) {
|
|
1158
|
+
const field = await p.select({
|
|
1159
|
+
message: 'Provider preference (leave unset for the default soft z-ai pin on z-ai/* slugs)',
|
|
1160
|
+
options: [
|
|
1161
|
+
{ value: 'order', label: 'order (soft pin)', hint: pref.order?.join(', ') ?? 'none' },
|
|
1162
|
+
{ value: 'only', label: 'only (strict pin)', hint: pref.only?.join(', ') ?? 'none' },
|
|
1163
|
+
{
|
|
1164
|
+
value: 'allowFallbacks',
|
|
1165
|
+
label: 'allowFallbacks',
|
|
1166
|
+
hint: pref.allowFallbacks === undefined ? 'default (true)' : pref.allowFallbacks ? 'true' : 'false',
|
|
1167
|
+
},
|
|
1168
|
+
{ value: 'clear', label: 'Clear (revert to default pin)' },
|
|
1169
|
+
{ value: 'back', label: 'Back' },
|
|
1170
|
+
],
|
|
1171
|
+
});
|
|
1172
|
+
if (isCancelled(field) || field === 'back')
|
|
1173
|
+
break;
|
|
1174
|
+
if (field === 'clear') {
|
|
1175
|
+
return { ...profile, providerPreference: undefined };
|
|
1176
|
+
}
|
|
1177
|
+
if (field === 'order' || field === 'only') {
|
|
1178
|
+
const input = await p.text({
|
|
1179
|
+
message: `${field} — comma-separated provider slugs (e.g. z-ai), blank to clear:`,
|
|
1180
|
+
placeholder: pref[field]?.join(', ') ?? 'z-ai',
|
|
1181
|
+
validate: () => undefined,
|
|
1182
|
+
});
|
|
1183
|
+
if (isCancelled(input))
|
|
1184
|
+
continue;
|
|
1185
|
+
const list = input
|
|
1186
|
+
.split(',')
|
|
1187
|
+
.map((s) => s.trim())
|
|
1188
|
+
.filter((s) => s.length > 0);
|
|
1189
|
+
const next = list.length > 0 ? list : undefined;
|
|
1190
|
+
if (field === 'order')
|
|
1191
|
+
pref.order = next;
|
|
1192
|
+
else
|
|
1193
|
+
pref.only = next;
|
|
1194
|
+
}
|
|
1195
|
+
else if (field === 'allowFallbacks') {
|
|
1196
|
+
const enabled = await p.confirm({
|
|
1197
|
+
message: 'Allow OpenRouter to fall back to other providers?',
|
|
1198
|
+
initialValue: pref.allowFallbacks ?? true,
|
|
1199
|
+
});
|
|
1200
|
+
if (isCancelled(enabled))
|
|
1201
|
+
continue;
|
|
1202
|
+
pref.allowFallbacks = enabled;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
const cleaned = {};
|
|
1206
|
+
if (pref.order !== undefined)
|
|
1207
|
+
cleaned.order = pref.order;
|
|
1208
|
+
if (pref.only !== undefined)
|
|
1209
|
+
cleaned.only = pref.only;
|
|
1210
|
+
if (pref.allowFallbacks !== undefined)
|
|
1211
|
+
cleaned.allowFallbacks = pref.allowFallbacks;
|
|
1212
|
+
const hasAny = Object.keys(cleaned).length > 0;
|
|
1213
|
+
return { ...profile, providerPreference: hasAny ? cleaned : undefined };
|
|
1214
|
+
}
|
|
1215
|
+
function perAgentSummary(profile) {
|
|
1216
|
+
const perAgent = profile.perAgent;
|
|
1217
|
+
if (!perAgent)
|
|
1218
|
+
return 'none';
|
|
1219
|
+
const entries = Object.entries(perAgent).filter(([, v]) => v);
|
|
1220
|
+
if (entries.length === 0)
|
|
1221
|
+
return 'none';
|
|
1222
|
+
return entries.map(([agent, slug]) => `${agent}=${slug}`).join(', ');
|
|
1223
|
+
}
|
|
1224
|
+
function providerPreferenceSummary(pref) {
|
|
1225
|
+
if (!pref)
|
|
1226
|
+
return 'default (soft z-ai pin)';
|
|
1227
|
+
const parts = [];
|
|
1228
|
+
if (pref.only)
|
|
1229
|
+
parts.push(`only: ${pref.only.join(', ')}`);
|
|
1230
|
+
if (pref.order)
|
|
1231
|
+
parts.push(`order: ${pref.order.join(', ')}`);
|
|
1232
|
+
if (pref.allowFallbacks !== undefined)
|
|
1233
|
+
parts.push(`fallbacks: ${pref.allowFallbacks ? 'on' : 'off'}`);
|
|
1234
|
+
return parts.length > 0 ? parts.join(', ') : 'default';
|
|
1235
|
+
}
|
|
579
1236
|
// ─── Server Credentials ──────────────────────────────────────
|
|
580
1237
|
/** Loads server names from mcp-servers.json for the credential editor. */
|
|
581
1238
|
function loadServerNames() {
|
|
@@ -688,18 +1345,25 @@ async function handleServerCredentials(resolved, pending) {
|
|
|
688
1345
|
// ─── Session Mode ─────────────────────────────────────────────
|
|
689
1346
|
/** Human-readable labels for session modes. */
|
|
690
1347
|
const SESSION_MODE_LABELS = {
|
|
691
|
-
|
|
1348
|
+
container: 'Container (recommended)',
|
|
692
1349
|
builtin: 'Builtin (V8 sandbox)',
|
|
693
1350
|
};
|
|
694
1351
|
/** Short labels used in hints (no parenthetical). */
|
|
695
1352
|
const SESSION_MODE_SHORT_LABELS = {
|
|
696
|
-
|
|
1353
|
+
container: 'Container',
|
|
697
1354
|
builtin: 'Builtin',
|
|
698
1355
|
};
|
|
1356
|
+
/** Human-readable labels for container runtime backends. */
|
|
1357
|
+
const CONTAINER_RUNTIME_LABELS = {
|
|
1358
|
+
auto: 'Auto (prefer Apple container when available)',
|
|
1359
|
+
docker: 'Docker',
|
|
1360
|
+
'apple-container': 'Apple container (macOS 26+, Apple silicon)',
|
|
1361
|
+
};
|
|
699
1362
|
async function handleSessionMode(resolved, pending) {
|
|
700
1363
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- interactive loop exited via return
|
|
701
1364
|
while (true) {
|
|
702
1365
|
const currentMode = pending.preferredMode ?? resolved.preferredMode;
|
|
1366
|
+
const currentRuntime = pending.containerRuntime ?? resolved.containerRuntime;
|
|
703
1367
|
const field = await p.select({
|
|
704
1368
|
message: 'Session Mode',
|
|
705
1369
|
options: [
|
|
@@ -708,11 +1372,35 @@ async function handleSessionMode(resolved, pending) {
|
|
|
708
1372
|
label: 'Preferred mode',
|
|
709
1373
|
hint: SESSION_MODE_LABELS[currentMode],
|
|
710
1374
|
},
|
|
1375
|
+
{
|
|
1376
|
+
value: 'containerRuntime',
|
|
1377
|
+
label: 'Container runtime',
|
|
1378
|
+
hint: CONTAINER_RUNTIME_LABELS[currentRuntime],
|
|
1379
|
+
},
|
|
711
1380
|
{ value: 'back', label: 'Back' },
|
|
712
1381
|
],
|
|
713
1382
|
});
|
|
714
1383
|
if (isCancelled(field) || field === 'back')
|
|
715
1384
|
return;
|
|
1385
|
+
if (field === 'containerRuntime') {
|
|
1386
|
+
const runtimeOptions = CONTAINER_RUNTIMES.map((runtime) => ({
|
|
1387
|
+
value: runtime,
|
|
1388
|
+
label: CONTAINER_RUNTIME_LABELS[runtime],
|
|
1389
|
+
hint: runtime === currentRuntime ? '(current)' : undefined,
|
|
1390
|
+
}));
|
|
1391
|
+
const selected = await p.select({
|
|
1392
|
+
message: 'Select container runtime backend:',
|
|
1393
|
+
options: runtimeOptions,
|
|
1394
|
+
initialValue: currentRuntime,
|
|
1395
|
+
});
|
|
1396
|
+
if (isCancelled(selected))
|
|
1397
|
+
continue;
|
|
1398
|
+
const runtime = selected;
|
|
1399
|
+
if (runtime !== currentRuntime) {
|
|
1400
|
+
pending.containerRuntime = runtime;
|
|
1401
|
+
}
|
|
1402
|
+
continue;
|
|
1403
|
+
}
|
|
716
1404
|
if (field === 'preferredMode') {
|
|
717
1405
|
const modeOptions = SESSION_MODES.map((mode) => ({
|
|
718
1406
|
value: mode,
|
|
@@ -744,6 +1432,7 @@ const GOOSE_PROVIDER_LABELS = {
|
|
|
744
1432
|
const DOCKER_AGENT_LABELS = {
|
|
745
1433
|
'claude-code': 'Claude Code',
|
|
746
1434
|
goose: 'Goose',
|
|
1435
|
+
codex: 'Codex CLI',
|
|
747
1436
|
};
|
|
748
1437
|
async function handleDockerAgent(resolved, pending) {
|
|
749
1438
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- interactive loop exited via return
|
|
@@ -948,6 +1637,14 @@ function webSearchHint(resolved, pending) {
|
|
|
948
1637
|
const provider = pending.webSearch?.provider ?? resolved.webSearch.provider;
|
|
949
1638
|
return provider ? WEB_SEARCH_PROVIDER_LABELS[provider] : 'not configured';
|
|
950
1639
|
}
|
|
1640
|
+
function modelProvidersHint(resolved, pending) {
|
|
1641
|
+
const profiles = currentProfiles(resolved, pending);
|
|
1642
|
+
const count = Object.keys(profiles).length;
|
|
1643
|
+
const defaultName = currentDefault(resolved, pending);
|
|
1644
|
+
if (count === 0)
|
|
1645
|
+
return `default: ${defaultName}`;
|
|
1646
|
+
return `${count} profile${count > 1 ? 's' : ''}, default: ${defaultName}`;
|
|
1647
|
+
}
|
|
951
1648
|
function serverCredentialsHint(resolved, pending) {
|
|
952
1649
|
const creds = { ...resolved.serverCredentials, ...pending.serverCredentials };
|
|
953
1650
|
const configured = Object.entries(creds).filter(([, v]) => Object.keys(v).length > 0);
|
|
@@ -962,6 +1659,13 @@ function memoryHint(resolved, pending) {
|
|
|
962
1659
|
return 'off (kill switch)';
|
|
963
1660
|
return `on, auto-save: ${autoSave ? 'on' : 'off'}`;
|
|
964
1661
|
}
|
|
1662
|
+
function snapshotHint(resolved, pending) {
|
|
1663
|
+
const enabled = pending.snapshot?.enabled ?? resolved.snapshot.enabled;
|
|
1664
|
+
const maxAge = pending.snapshot?.maxAgeDays ?? resolved.snapshot.maxAgeDays;
|
|
1665
|
+
if (!enabled)
|
|
1666
|
+
return 'off (kill switch)';
|
|
1667
|
+
return `on, max age: ${maxAge === null ? 'unbounded' : `${maxAge}d`}`;
|
|
1668
|
+
}
|
|
965
1669
|
function dockerAgentHint(resolved, pending) {
|
|
966
1670
|
const agent = DOCKER_AGENT_LABELS[pending.preferredDockerAgent ?? resolved.preferredDockerAgent];
|
|
967
1671
|
return `${agent}, ${dockerResourcesSummary(resolved, pending)}`;
|
|
@@ -1003,8 +1707,10 @@ export async function runConfigCommand() {
|
|
|
1003
1707
|
{ value: 'resources', label: `Resource Limits (${resourceHint(resolved, pending)})` },
|
|
1004
1708
|
{ value: 'compact', label: `Auto-Compact (${autoCompactHint(resolved, pending)})` },
|
|
1005
1709
|
{ value: 'websearch', label: `Web Search (${webSearchHint(resolved, pending)})` },
|
|
1710
|
+
{ value: 'modelProviders', label: `Model Providers (${modelProvidersHint(resolved, pending)})` },
|
|
1006
1711
|
{ value: 'credentials', label: `Server Credentials (${serverCredentialsHint(resolved, pending)})` },
|
|
1007
1712
|
{ value: 'memory', label: `Memory (${memoryHint(resolved, pending)})` },
|
|
1713
|
+
{ value: 'snapshots', label: `Container Snapshots (${snapshotHint(resolved, pending)})` },
|
|
1008
1714
|
{ value: 'sessionMode', label: `Session Mode (${sessionModeHint(resolved, pending)})` },
|
|
1009
1715
|
{ value: 'dockerAgent', label: `Docker Agent (${dockerAgentHint(resolved, pending)})` },
|
|
1010
1716
|
{ value: 'save', label: 'Save & Exit', hint: changeCount(resolved, pending) },
|
|
@@ -1031,12 +1737,18 @@ export async function runConfigCommand() {
|
|
|
1031
1737
|
case 'websearch':
|
|
1032
1738
|
await handleWebSearch(resolved, pending);
|
|
1033
1739
|
break;
|
|
1740
|
+
case 'modelProviders':
|
|
1741
|
+
await handleModelProviders(resolved, pending);
|
|
1742
|
+
break;
|
|
1034
1743
|
case 'credentials':
|
|
1035
1744
|
await handleServerCredentials(resolved, pending);
|
|
1036
1745
|
break;
|
|
1037
1746
|
case 'memory':
|
|
1038
1747
|
await handleMemory(resolved, pending);
|
|
1039
1748
|
break;
|
|
1749
|
+
case 'snapshots':
|
|
1750
|
+
await handleSnapshot(resolved, pending);
|
|
1751
|
+
break;
|
|
1040
1752
|
case 'sessionMode':
|
|
1041
1753
|
await handleSessionMode(resolved, pending);
|
|
1042
1754
|
break;
|