mixdog 1.4.0 → 1.4.2
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/package.json +3 -2
- package/scripts/build-shipped-history.mjs +115 -33
- package/scripts/check-voice-runtime-inputs.mjs +25 -0
- package/scripts/gate-local.mjs +183 -63
- package/scripts/latest-guard.mjs +21 -9
- package/scripts/lib/gate-scheduler.mjs +138 -32
- package/scripts/lib/gate-timing-reporter.mjs +16 -0
- package/scripts/lib/isolated-test-env.mjs +10 -5
- package/scripts/lib/run-node-tests.mjs +91 -66
- package/scripts/lib/scoped-temp.d.mts +16 -0
- package/scripts/lib/scoped-temp.mjs +124 -0
- package/scripts/lib/stale-temp.mjs +44 -0
- package/scripts/lib/test-failure-records.mjs +14 -1
- package/scripts/lib/test-failure-summary.mjs +16 -4
- package/scripts/lib/test-spawn-ledger.mjs +12 -3
- package/scripts/post-release-check.mjs +1 -6
- package/scripts/pptx-kit-index.mjs +17 -5
- package/scripts/suite-health.mjs +115 -24
- package/scripts/test-data-dir.mjs +10 -7
- package/scripts/test.mjs +9 -14
- package/src/agents/advisor/AGENT.md +4 -4
- package/src/agents/advisor/agent.json +1 -1
- package/src/agents/explore/AGENT.md +22 -0
- package/src/agents/explore/agent.json +6 -0
- package/src/app.mjs +4 -0
- package/src/cli.mjs +7 -0
- package/src/defaults/agents.json +7 -0
- package/src/defaults/shipped-history.json +23 -2
- package/src/defaults/skills/browser-use/SKILL.md +10 -5
- package/src/defaults/skills/code-tidy/SKILL.md +71 -102
- package/src/defaults/skills/code-tidy/references/agent-cleanup.md +66 -114
- package/src/defaults/skills/code-tidy/references/cleanup-report.md +19 -35
- package/src/defaults/skills/code-tidy/references/dead-code.md +3 -9
- package/src/defaults/skills/code-tidy/references/test-suite-slop.md +16 -27
- package/src/defaults/skills/computer-use/SKILL.md +16 -4
- package/src/defaults/skills/goal-management/SKILL.md +1 -1
- package/src/defaults/skills/setup/references/actions.md +12 -6
- package/src/defaults/skills/setup/references/surfaces.md +2 -2
- package/src/defaults/skills/setup-guide/SKILL.md +5 -2
- package/src/defaults/skills/setup-guide/model-recommendations.json +13 -0
- package/src/defaults/skills/skill-creator/SKILL.md +85 -190
- package/src/defaults/skills/skill-creator/references/design.md +70 -162
- package/src/defaults/skills/skill-creator/references/evaluation.md +33 -92
- package/src/defaults/skills/test-suite/SKILL.md +194 -0
- package/src/defaults/skills/test-suite/references/case-management.md +51 -0
- package/src/defaults/skills/test-suite/references/flaky-policy.md +39 -0
- package/src/defaults/skills/test-suite/references/reliability.md +49 -0
- package/src/defaults/skills/test-suite/references/suite-design.md +78 -0
- package/src/defaults/skills/test-suite/scripts/inventory.mjs +335 -0
- package/src/defaults/skills/visualize/SKILL.md +6 -2
- package/src/lib/rules-builder.cjs +35 -42
- package/src/output-styles/common.md +2 -2
- package/src/output-styles/simple.md +1 -1
- package/src/rules/agent/AGENT.md +5 -4
- package/src/rules/lead/LEAD.md +12 -7
- package/src/rules/shared/10-tool-workflow.md +3 -1
- package/src/rules/shared/20-research.md +3 -6
- package/src/rules/shared/40-editing.md +1 -0
- package/src/rules/shared/45-destructive.md +0 -2
- package/src/rules/shared/50-execution.md +2 -0
- package/src/rules/shared/60-verification.md +10 -9
- package/src/rules/shared/70-delivery.md +8 -9
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/abort-link.mjs +5 -1
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +10 -1
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +156 -24
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +13 -0
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +4 -1
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +2 -0
- package/src/runtime/agent/orchestrator/config.mjs +12 -7
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +8 -8
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +5 -26
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +3 -1
- package/src/runtime/agent/orchestrator/internal-agents.mjs +3 -14
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +122 -12
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -3
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +13 -2
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +152 -58
- package/src/runtime/agent/orchestrator/mcp/client.mjs +51 -18
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +88 -54
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +42 -26
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +54 -8
- package/src/runtime/agent/orchestrator/providers/anthropic-api-recovery.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +37 -31
- package/src/runtime/agent/orchestrator/providers/anthropic-api-transport.mjs +8 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens-recovery.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +31 -21
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/initial-status.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +14 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/command-code-oauth-login.mjs +1 -10
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +40 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider-keys.mjs +34 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +14 -7
- package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/devin.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +248 -41
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -11
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +45 -6
- package/src/runtime/agent/orchestrator/providers/lib/first-output-window.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/lib/listen-on.mjs +14 -0
- package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +0 -18
- package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +138 -6
- package/src/runtime/agent/orchestrator/providers/mimo-oauth-login.mjs +1 -10
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/model-cache.mjs +100 -33
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/oauth-secret-fields.fixtures.mjs +45 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +14 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream-common.mjs +5 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +16 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/output-items.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +13 -3
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +13 -2
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-watchdogs.mjs +81 -42
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +160 -31
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +22 -12
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +49 -22
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/registry.mjs +45 -2
- package/src/runtime/agent/orchestrator/providers/vertex-ai-auth.mjs +622 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai-maas.mjs +185 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai-models.mjs +445 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +567 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +41 -13
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +8 -6
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +7 -3
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +14 -5
- package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -0
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -5
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +171 -37
- package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/cache-warm.mjs +102 -12
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +1 -8
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +23 -0
- package/src/runtime/agent/orchestrator/session/compact/observation-supersession.mjs +103 -23
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +160 -36
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +164 -23
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +139 -19
- package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +38 -7
- package/src/runtime/agent/orchestrator/session/conversation-import/imported-session.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +100 -22
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -6
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +33 -3
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +9 -2
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +13 -5
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +23 -4
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +23 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +39 -21
- package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +13 -8
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +50 -28
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +30 -13
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +63 -6
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +35 -13
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +24 -14
- package/src/runtime/agent/orchestrator/session/manager/session-scratch-environment.mjs +70 -0
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +0 -3
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +10 -6
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-context.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +56 -29
- package/src/runtime/agent/orchestrator/session/manager/turn-reap.mjs +125 -0
- package/src/runtime/agent/orchestrator/session/mixy-name.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +30 -7
- package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +154 -0
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/store-summary-fields.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +37 -8
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +252 -39
- package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +163 -64
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +164 -4
- package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +142 -18
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +61 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +23 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/invocation-plan.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +46 -20
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +67 -31
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +25 -13
- package/src/runtime/agent/orchestrator/tools/builtin/lib/scoped-search-flight.mjs +9 -5
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +58 -20
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-stream.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-dispatch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-publication.mjs +23 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-snapshot.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +1 -8
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +20 -15
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +96 -20
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +59 -10
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch/federation.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/lib/isolated-test-env.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +99 -2
- package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +103 -13
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +49 -15
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +45 -27
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +34 -10
- package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +28 -4
- package/src/runtime/agent/orchestrator/tools/lib/wait-until-fixture.mjs +26 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -2
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
- package/src/runtime/channels/lib/webhook/signature.mjs +4 -1
- package/src/runtime/channels/lib/whisper-port.mjs +29 -11
- package/src/runtime/channels/lib/whisper-server.mjs +10 -1
- package/src/runtime/computer-bridge/action-schema.mjs +2 -2
- package/src/runtime/computer-bridge/core-actions.mjs +2 -2
- package/src/runtime/computer-bridge/error-recovery.mjs +5 -4
- package/src/runtime/computer-bridge/linux-action-capabilities.mjs +24 -5
- package/src/runtime/computer-bridge/linux-readiness.d.mts +7 -2
- package/src/runtime/connectors/connector-accounts-mcp.mjs +236 -103
- package/src/runtime/connectors/gmail-app-password.mjs +3 -3
- package/src/runtime/explore/explorer.mjs +93 -0
- package/src/runtime/explore/tool-defs.mjs +35 -0
- package/src/runtime/explore/tool.mjs +123 -0
- package/src/runtime/local-provider/server-process.mjs +2 -1
- package/src/runtime/media/adapters/gemini-video.mjs +4 -1
- package/src/runtime/media/adapters/xai-media.mjs +2 -1
- package/src/runtime/memory/index.mjs +4 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +4 -1
- package/src/runtime/memory/lib/embedding-worker.mjs +10 -3
- package/src/runtime/memory/lib/memory-action-handlers/cycle-backfill-action.mjs +1 -7
- package/src/runtime/memory/lib/memory-chunk-quality.mjs +1 -6
- package/src/runtime/office/authoring/pdf-author-action.mjs +1 -6
- package/src/runtime/office/design/design-art-direction.mjs +29 -27
- package/src/runtime/office/pdf/pdf-writer.mjs +6 -1
- package/src/runtime/office/portable/portable-snapshot-docx.mjs +7 -1
- package/src/runtime/office/portable/portable-snapshot-shared.mjs +6 -1
- package/src/runtime/office/quality/assurance-rendered.mjs +9 -2
- package/src/runtime/office/quality/assurance-structure-docx.mjs +4 -2
- package/src/runtime/office/quality/design-review.mjs +1 -5
- package/src/runtime/office/quality/document-brief.mjs +1 -5
- package/src/runtime/office/quality/forced-break-pages.mjs +9 -6
- package/src/runtime/office/quality/quality-pipeline.mjs +1 -1
- package/src/runtime/shared/agent-route-config.mjs +17 -2
- package/src/runtime/shared/atomic-file.mjs +103 -2
- package/src/runtime/shared/background-tasks.mjs +15 -6
- package/src/runtime/shared/bot-owned-work.mjs +56 -0
- package/src/runtime/shared/connector-accounts.mjs +5 -5
- package/src/runtime/shared/connector-catalog.d.mts +11 -4
- package/src/runtime/shared/connector-catalog.mjs +60 -52
- package/src/runtime/shared/custom-provider-keys.mjs +20 -0
- package/src/runtime/shared/defaults-separation.mjs +1 -4
- package/src/runtime/shared/fetch-safe-listen.d.mts +16 -0
- package/src/runtime/shared/fetch-safe-listen.mjs +50 -0
- package/src/runtime/shared/file-has-data.mjs +26 -2
- package/src/runtime/shared/file-lock.mjs +13 -15
- package/src/runtime/shared/git-launch.d.mts +9 -0
- package/src/runtime/shared/git-launch.mjs +213 -0
- package/src/runtime/shared/git-runner-worker.mjs +2 -1
- package/src/runtime/shared/handoff-artifacts.d.mts +2 -1
- package/src/runtime/shared/handoff-artifacts.mjs +18 -8
- package/src/runtime/shared/idle-auto-clear-gate.mjs +29 -7
- package/src/runtime/shared/llm/cost.mjs +11 -2
- package/src/runtime/shared/llm/http-agent.mjs +313 -29
- package/src/runtime/shared/llm/model-catalog-projection.mjs +3 -2
- package/src/runtime/shared/llm/model-catalog.mjs +46 -6
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +3 -0
- package/src/runtime/shared/llm/system-proxy.mjs +452 -0
- package/src/runtime/shared/loopback-listener.mjs +25 -2
- package/src/runtime/shared/model-route-chain.d.mts +7 -2
- package/src/runtime/shared/model-route-chain.mjs +22 -4
- package/src/runtime/shared/open-url.mjs +10 -0
- package/src/runtime/shared/provider-accounts.mjs +52 -2
- package/src/runtime/shared/session-activity-signal.mjs +14 -0
- package/src/runtime/shared/session-scratch.mjs +37 -0
- package/src/runtime/shared/staged-install-worker.mjs +5 -0
- package/src/runtime/shared/staged-update.mjs +47 -2
- package/src/runtime/shared/tool-card-model/agent-surface.mjs +1 -3
- package/src/runtime/shared/tool-card-model/card-status.mjs +15 -2
- package/src/runtime/shared/tool-result-summary.mjs +1 -1
- package/src/runtime/shared/tool-surface.mjs +113 -36
- package/src/runtime/shared/tool-work-units.mjs +6 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +13 -6
- package/src/runtime/shared/turn-worktree-snapshot.mjs +414 -54
- package/src/runtime/shared/worktrees/activity-gate.mjs +215 -318
- package/src/runtime/shared/worktrees/activity-ledger-worker.mjs +56 -0
- package/src/runtime/shared/worktrees/activity-ledger.mjs +391 -0
- package/src/runtime/shared/worktrees/branch-management.mjs +9 -3
- package/src/runtime/shared/worktrees/contract.mjs +4 -1
- package/src/runtime/shared/worktrees/git.mjs +72 -0
- package/src/runtime/shared/worktrees/manager.mjs +285 -168
- package/src/runtime/shared/worktrees/test-support.mjs +6 -10
- package/src/runtime/tidy/install.mjs +95 -15
- package/src/runtime/tidy/languages.mjs +17 -15
- package/src/runtime/tidy/process.mjs +7 -4
- package/src/runtime/tidy/resolve.mjs +19 -16
- package/src/runtime/tidy/runners/biome.mjs +3 -3
- package/src/runtime/tidy/tool.mjs +1 -1
- package/src/runtime/tidy/worktree.mjs +10 -6
- package/src/runtime/visualize-bridge/client.mjs +6 -1
- package/src/runtime/visualize-bridge/tool-defs.mjs +7 -2
- package/src/runtime/web-search/index.mjs +1 -1
- package/src/session-runtime/agent-home-delivery.mjs +41 -0
- package/src/session-runtime/agent-task-sessions.mjs +458 -312
- package/src/session-runtime/agent-tool-routing.mjs +8 -41
- package/src/session-runtime/boot/tools.mjs +7 -1
- package/src/session-runtime/bot-status.mjs +107 -101
- package/src/session-runtime/bot-target.mjs +38 -0
- package/src/session-runtime/channel-config-api.mjs +6 -1
- package/src/session-runtime/connector-merge-migration.mjs +18 -15
- package/src/session-runtime/conversation-import/codex-titles.mjs +88 -0
- package/src/session-runtime/conversation-import/discovery.mjs +401 -36
- package/src/session-runtime/conversation-import/service.mjs +35 -8
- package/src/session-runtime/handoff-attachments.mjs +82 -31
- package/src/session-runtime/held-requests.mjs +3 -1
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +12 -13
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +1 -0
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -0
- package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +8 -0
- package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +4 -1
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +145 -112
- package/src/session-runtime/lifecycle/cold-heir.mjs +11 -6
- package/src/session-runtime/lifecycle/session-catalog.mjs +9 -1
- package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -1
- package/src/session-runtime/lifecycle/teardown.mjs +1 -1
- package/src/session-runtime/mcp-glue/server-catalog.mjs +16 -10
- package/src/session-runtime/mcp-glue/server-input.mjs +4 -1
- package/src/session-runtime/mixy-heartbeat.mjs +1 -1
- package/src/session-runtime/model-route/set-route.mjs +11 -2
- package/src/session-runtime/provider-auth/login.mjs +2 -2
- package/src/session-runtime/provider-catalog-cache.mjs +82 -36
- package/src/session-runtime/provider-models/catalog-load.mjs +23 -6
- package/src/session-runtime/provider-models.mjs +4 -2
- package/src/session-runtime/resource-mcp-api.mjs +98 -35
- package/src/session-runtime/resource-mcp-connector-accounts.mjs +44 -31
- package/src/session-runtime/resource-mcp-secrets.mjs +28 -11
- package/src/session-runtime/resource-mcp-servers.mjs +5 -4
- package/src/session-runtime/resource-recall-api.mjs +6 -1
- package/src/session-runtime/runtime-feature-gates.mjs +5 -1
- package/src/session-runtime/services/agent-tool/execute.mjs +14 -9
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +20 -4
- package/src/session-runtime/services/agent-tool/model-select.mjs +48 -7
- package/src/session-runtime/services/agent-tool/render.mjs +71 -17
- package/src/session-runtime/services/agent-tool/send-flow.mjs +10 -2
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +8 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +6 -2
- package/src/session-runtime/services/agent-tool/tool-def.mjs +9 -9
- package/src/session-runtime/services/agent-watchdog-registry.mjs +32 -4
- package/src/session-runtime/services/channel-worker/daemon-spawn.mjs +2 -0
- package/src/session-runtime/services/daemon-candidate.mjs +4 -1
- package/src/session-runtime/services/memory-runtime-proxy/daemon-spawn.mjs +4 -0
- package/src/session-runtime/services/plugin-admin.mjs +10 -6
- package/src/session-runtime/services/provider-admin.mjs +283 -11
- package/src/session-runtime/session-turn-api.mjs +2 -0
- package/src/session-runtime/settings-builtin-tools-api.mjs +4 -1
- package/src/session-runtime/settings-system-api.mjs +1 -1
- package/src/session-runtime/setup-tool/agent-settings-actions.mjs +13 -3
- package/src/session-runtime/setup-tool/connector-widen-approval.mjs +96 -0
- package/src/session-runtime/setup-tool/executor.mjs +6 -2
- package/src/session-runtime/setup-tool/tool-defs.mjs +2 -1
- package/src/session-runtime/tool-policy/policy-stage.mjs +1 -0
- package/src/session-runtime/turn/agent-ops.mjs +23 -0
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +49 -11
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +5 -1
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +25 -5
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +3 -3
- package/src/session-runtime/workspace/activity-guards.mjs +176 -0
- package/src/session-runtime/workspace/session-view.mjs +149 -0
- package/src/session-runtime/workspace/side-checkouts.mjs +203 -0
- package/src/session-runtime/workspace/transition-runner.mjs +188 -0
- package/src/session-runtime/workspace/transitions.mjs +108 -1041
- package/src/session-runtime/workspace/workspace-merge.mjs +132 -0
- package/src/session-runtime/workspace/worktree-intents.mjs +242 -0
- package/src/session-runtime/workspace/worktree-maintenance.mjs +80 -0
- package/src/session-runtime/workspace/worktree-setup.mjs +85 -0
- package/src/session-runtime/workspace/worktree-tool.mjs +73 -56
- package/src/standalone/daemon-desktop-runtime.mjs +2 -0
- package/src/standalone/daemon-session-control.mjs +169 -138
- package/src/standalone/daemon.mjs +7 -0
- package/src/standalone/desktop-service-registry.mjs +9 -0
- package/src/standalone/local-session-runtime.mjs +5 -1
- package/src/standalone/session-client/daemon-unavailable.mjs +3 -0
- package/src/standalone/session-client.mjs +5 -1
- package/src/standalone/session-frame-stream.mjs +83 -11
- package/src/standalone/session-protocol.mjs +4 -0
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +16 -12
- package/src/standalone/session-service/agent-tree.mjs +45 -9
- package/src/standalone/session-service/projection/transcript-window.mjs +700 -64
- package/src/standalone/session-service/session-calls/session-action.mjs +10 -3
- package/src/standalone/session-service/session-calls/session-views.mjs +160 -104
- package/src/standalone/session-service/stored-reader.mjs +127 -40
- package/src/standalone/session-transport/client-registry/client-grace.mjs +12 -9
- package/src/tui/App.jsx +154 -164
- package/src/tui/app/app-view/floating-panel.jsx +10 -4
- package/src/tui/app/app-view/text-entry-labels.mjs +1 -0
- package/src/tui/app/clipboard.mjs +21 -10
- package/src/tui/app/model-picker/route-selection/fast-parameters.mjs +1 -3
- package/src/tui/app/model-picker/route-selection.mjs +5 -6
- package/src/tui/app/prompt-handlers/use-prompt-history-nav.mjs +1 -1
- package/src/tui/app/prompt-handlers/use-prompt-interrupt.mjs +3 -0
- package/src/tui/app/prompt-submit/provider-prompts.mjs +3 -1
- package/src/tui/app/provider-setup/api-key-actions.mjs +31 -1
- package/src/tui/app/provider-setup/oauth-actions.mjs +58 -21
- package/src/tui/app/provider-setup/provider-items.mjs +8 -2
- package/src/tui/app/provider-setup-picker.mjs +29 -18
- package/src/tui/app/shell-layout/row-budgets.mjs +12 -10
- package/src/tui/app/slash-commands.mjs +1 -0
- package/src/tui/app/slash-dispatch/session-commands.mjs +46 -0
- package/src/tui/app/slash-dispatch.mjs +1 -1
- package/src/tui/app/use-btw-panel.mjs +29 -0
- package/src/tui/app/use-context-panel-refresh.mjs +31 -0
- package/src/tui/app/use-model-catalog-cache.mjs +13 -10
- package/src/tui/app/use-mouse-input.mjs +11 -0
- package/src/tui/app/use-onboarding-start.mjs +8 -3
- package/src/tui/app/use-panel-transition.mjs +10 -1
- package/src/tui/app/use-pasted-buffers.mjs +28 -20
- package/src/tui/app/use-prompt-draft-flow.mjs +7 -0
- package/src/tui/app/use-prompt-layout-rows.mjs +3 -3
- package/src/tui/app/use-prompt-queue-history.mjs +3 -2
- package/src/tui/app/use-selection-focus.mjs +11 -2
- package/src/tui/app/use-selection-geometry.mjs +18 -9
- package/src/tui/app/use-selection-paint.mjs +4 -4
- package/src/tui/app/use-selection-stitch.mjs +3 -3
- package/src/tui/app/use-slash-palette.mjs +62 -0
- package/src/tui/app/use-terminal-chrome.mjs +2 -2
- package/src/tui/app/use-transcript-activity.mjs +47 -32
- package/src/tui/app/use-transcript-follow.mjs +12 -4
- package/src/tui/app/use-transcript-scroll.mjs +46 -17
- package/src/tui/app/use-transcript-swap-reset.mjs +27 -0
- package/src/tui/app/use-transcript-window/geometry.mjs +19 -5
- package/src/tui/app/use-transcript-window/instance-state.mjs +4 -0
- package/src/tui/app/use-transcript-window/selection-shift.mjs +5 -1
- package/src/tui/app/use-transcript-window.mjs +21 -11
- package/src/tui/app/use-tui-ready.mjs +18 -0
- package/src/tui/app/use-welcome-prompt-hint.mjs +5 -0
- package/src/tui/app/use-workflow-tab-cycle.mjs +5 -3
- package/src/tui/components/Picker.jsx +27 -3
- package/src/tui/components/QuestionPanel.jsx +13 -4
- package/src/tui/components/SignInUrl.jsx +68 -0
- package/src/tui/components/TextEntryPanel.jsx +76 -46
- package/src/tui/components/sign-in-url-layout.mjs +24 -0
- package/src/tui/dist/index.mjs +1963 -1361
- package/src/tui/session/goal-turn-state.mjs +6 -2
- package/src/tui/session/session-api/controls.mjs +4 -0
- package/src/tui/session/session-api/extensions.mjs +8 -0
- package/src/tui/session/session-api/intake/abort.mjs +2 -1
- package/src/tui/session/session-api/intake.mjs +2 -0
- package/src/tui/session/session-api/integrations.mjs +2 -2
- package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
- package/src/tui/session/session-api/settings.mjs +2 -2
- package/src/tui/session/session-flow/queue/take-entries.mjs +11 -0
- package/src/tui/session/transcript-spill/spill-writer.mjs +15 -0
- package/src/tui/session/transcript-spill.mjs +92 -4
- package/src/tui/session/transcript-store.mjs +2 -0
- package/src/ui/statusline-agents.mjs +1 -3
- package/src/defaults/skills/skill-creator/references/source-review.md +0 -94
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The OS proxy for Mixdog's own network stack, which Node does not make follow
|
|
3
|
+
* it by itself, decided per origin.
|
|
4
|
+
*
|
|
5
|
+
* While a desktop app is attached (attachSystemProxyResolver, from the desktop
|
|
6
|
+
* service this daemon hosts), each origin is routed by Chromium's answer for
|
|
7
|
+
* it — PAC, WPAD, OS exceptions and DIRECT alike — asked once per origin,
|
|
8
|
+
* cached, and bounded: a lookup that fails or is slow goes DIRECT. Without one
|
|
9
|
+
* the fallback decision applies: the OS static proxy (the one Clash or Windows
|
|
10
|
+
* Settings writes) with its exact bypass rules, or DIRECT.
|
|
11
|
+
*
|
|
12
|
+
* A proxy that came from the OS is Mixdog's private state, never environment:
|
|
13
|
+
* no HTTPS_PROXY / HTTP_PROXY / NO_PROXY / NODE_USE_ENV_PROXY is written, so the
|
|
14
|
+
* shells, tools and CLIs Mixdog spawns do not inherit it. It is applied to the
|
|
15
|
+
* global fetch dispatcher (http-agent.mjs adoptPrivateProxy), and the fallback
|
|
16
|
+
* decision reaches Mixdog's own daemon and workers through one explicit
|
|
17
|
+
* handoff variable that the receiver reads and removes at startup. Proxy
|
|
18
|
+
* variables the user set keep their usual behaviour and win outright.
|
|
19
|
+
*
|
|
20
|
+
* Windows discovery reads the registry once at startup. The proxy is used only
|
|
21
|
+
* when the registry answer is exactly representable: a PAC script
|
|
22
|
+
* (AutoConfigURL), SOCKS-only entries and exceptions that have no exact rule
|
|
23
|
+
* leave routing untouched. "Automatically detect settings" (WPAD) is on by
|
|
24
|
+
* default in Windows and is not consulted: an enabled static proxy is used.
|
|
25
|
+
* Any read failure means no system proxy.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { execFileSync } from 'node:child_process';
|
|
29
|
+
import { isIP } from 'node:net';
|
|
30
|
+
import { join } from 'node:path';
|
|
31
|
+
import { adoptPrivateProxy, isLocalHost, normalizeHost } from './http-agent.mjs';
|
|
32
|
+
|
|
33
|
+
const INTERNET_SETTINGS_KEY = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings';
|
|
34
|
+
const USER_PROXY_VARS = [
|
|
35
|
+
'HTTPS_PROXY',
|
|
36
|
+
'https_proxy',
|
|
37
|
+
'HTTP_PROXY',
|
|
38
|
+
'http_proxy',
|
|
39
|
+
'ALL_PROXY',
|
|
40
|
+
'all_proxy',
|
|
41
|
+
'NODE_USE_ENV_PROXY',
|
|
42
|
+
];
|
|
43
|
+
/**
|
|
44
|
+
* The handoff to a Mixdog process this one starts: JSON `{ proxy, bypass }`,
|
|
45
|
+
* `proxy` null for "decided: direct". Consumed (and removed from the env) by
|
|
46
|
+
* adoptSystemProxy, so nothing downstream of that process ever sees it.
|
|
47
|
+
*/
|
|
48
|
+
export const HANDOFF_VAR = 'MIXDOG_SYSTEM_PROXY_HANDOFF';
|
|
49
|
+
/** Must match SYSTEM_PROXY_REQUEST_EVENT in apps/desktop/src/main/system-proxy.ts. */
|
|
50
|
+
export const SYSTEM_PROXY_REQUEST_EVENT = 'system-proxy-request';
|
|
51
|
+
const RESOLVE_TIMEOUT_MS = 3_000;
|
|
52
|
+
const ROUTE_TTL_MS = 5 * 60_000;
|
|
53
|
+
// A failed lookup is asked again sooner, but not on every request of a burst.
|
|
54
|
+
const FAILED_ROUTE_TTL_MS = 30_000;
|
|
55
|
+
const DIRECT = Object.freeze({ proxy: null });
|
|
56
|
+
|
|
57
|
+
/** The fallback decision this process holds, or null when it made none. */
|
|
58
|
+
let active = null;
|
|
59
|
+
/** Installs this process's router; null when the user's variables or MIXDOG_SYSTEM_PROXY=0 decide. */
|
|
60
|
+
let installRouting = null;
|
|
61
|
+
let routingInstalled = false;
|
|
62
|
+
let log = (line) => process.stderr.write(`${line}\n`);
|
|
63
|
+
/** Attached resolvers, newest last; the newest answers. */
|
|
64
|
+
const resolvers = [];
|
|
65
|
+
/** origin → { route, expiresAt } */
|
|
66
|
+
const routes = new Map();
|
|
67
|
+
/** origin → the in-flight lookup */
|
|
68
|
+
const lookups = new Map();
|
|
69
|
+
let socksReported = false;
|
|
70
|
+
let failureReported = false;
|
|
71
|
+
|
|
72
|
+
/** The rows of `reg query <key>` output as a name → data map; null when the query failed. */
|
|
73
|
+
function queryRegistryKey(key) {
|
|
74
|
+
try {
|
|
75
|
+
const exe = join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'reg.exe');
|
|
76
|
+
const out = execFileSync(exe, ['query', key], {
|
|
77
|
+
encoding: 'utf8',
|
|
78
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
79
|
+
timeout: 2000,
|
|
80
|
+
maxBuffer: 256 * 1024,
|
|
81
|
+
windowsHide: true,
|
|
82
|
+
});
|
|
83
|
+
const values = new Map();
|
|
84
|
+
for (const row of out.split(/\r?\n/)) {
|
|
85
|
+
// " Name REG_TYPE data" (data is absent for an empty string)
|
|
86
|
+
const match = row.match(/^ {4}(\S.*?) {4}(REG_[A-Z_]+)(?: {4}(.*))?$/);
|
|
87
|
+
if (match) values.set(match[1].toLowerCase(), (match[3] ?? '').trim());
|
|
88
|
+
}
|
|
89
|
+
return values;
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Raw registry values: `{ proxyEnable, proxyServer, proxyOverride, autoConfigUrl }`
|
|
97
|
+
* (strings as `reg query` prints them, null when absent), or null when the
|
|
98
|
+
* Internet Settings key cannot be read.
|
|
99
|
+
*/
|
|
100
|
+
function readWindowsProxyRegistry() {
|
|
101
|
+
const settings = queryRegistryKey(INTERNET_SETTINGS_KEY);
|
|
102
|
+
if (!settings) return null;
|
|
103
|
+
return {
|
|
104
|
+
proxyEnable: settings.get('proxyenable') ?? null,
|
|
105
|
+
proxyServer: settings.get('proxyserver') ?? null,
|
|
106
|
+
proxyOverride: settings.get('proxyoverride') ?? null,
|
|
107
|
+
autoConfigUrl: settings.get('autoconfigurl') ?? null,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const PROXY_ADDRESS = /^(https?:\/\/)?((?:[a-z0-9_-]+\.)*[a-z0-9_-]+|\[[0-9a-f:]+\]):(\d{1,5})$/i;
|
|
112
|
+
|
|
113
|
+
function proxyUrl(address) {
|
|
114
|
+
const match = PROXY_ADDRESS.exec(address.trim());
|
|
115
|
+
if (!match || Number(match[3]) > 65535) return null;
|
|
116
|
+
return `${(match[1] ?? 'http://').toLowerCase()}${match[2]}:${match[3]}`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* `ProxyServer` is a bare `host:port` (every scheme) or `scheme=host:port;...`.
|
|
121
|
+
* Returns `{ https, http }` proxy URLs, or `{ reason }`. HTTPS is required:
|
|
122
|
+
* Mixdog's traffic is https, and an http-only entry does not say how it goes.
|
|
123
|
+
*/
|
|
124
|
+
export function parseProxyServer(value) {
|
|
125
|
+
const text = String(value ?? '').trim();
|
|
126
|
+
if (!text) return { reason: 'no proxy server' };
|
|
127
|
+
if (!text.includes('=')) {
|
|
128
|
+
const url = proxyUrl(text);
|
|
129
|
+
return url ? { https: url, http: url } : { reason: 'unsupported proxy server' };
|
|
130
|
+
}
|
|
131
|
+
const entries = new Map();
|
|
132
|
+
for (const part of text.split(';')) {
|
|
133
|
+
if (!part.trim()) continue;
|
|
134
|
+
const eq = part.indexOf('=');
|
|
135
|
+
if (eq <= 0) return { reason: 'unsupported proxy server' };
|
|
136
|
+
entries.set(part.slice(0, eq).trim().toLowerCase(), part.slice(eq + 1).trim());
|
|
137
|
+
}
|
|
138
|
+
const https = entries.has('https') ? proxyUrl(entries.get('https')) : null;
|
|
139
|
+
const http = entries.has('http') ? proxyUrl(entries.get('http')) : null;
|
|
140
|
+
if (entries.has('https') && !https) return { reason: 'unsupported proxy server' };
|
|
141
|
+
if (entries.has('http') && !http) return { reason: 'unsupported proxy server' };
|
|
142
|
+
if (!https) return { reason: 'no HTTPS proxy (HTTP-only, SOCKS-only or other entries)' };
|
|
143
|
+
return { https, ...(http && { http }) };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function ipv4Wildcard(entry) {
|
|
147
|
+
const match = /^(\d{1,3}(?:\.\d{1,3}){0,2})\.\*$/.exec(entry);
|
|
148
|
+
if (!match) return null;
|
|
149
|
+
const octets = match[1].split('.').map(Number);
|
|
150
|
+
if (octets.some((octet) => octet > 255)) return null;
|
|
151
|
+
const pad = (fill) => [...octets, ...Array(4 - octets.length).fill(fill)].join('.');
|
|
152
|
+
return { start: pad(0), end: pad(255) };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** The form a URL hostname takes for an IP literal (IPv6 compressed, no brackets). */
|
|
156
|
+
function canonicalIp(address, family) {
|
|
157
|
+
return new URL(`http://${family === 6 ? `[${address}]` : address}`).hostname.replace(/^\[(.*)\]$/, '$1');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* `ProxyOverride` as bypass rules `{ hosts, suffixes }` (exact hosts and IP
|
|
162
|
+
* literals; `*.domain` as the bare domain, subdomains only), or null when any
|
|
163
|
+
* entry has no exact rule. `<local>` (dotless names) and IPv4 wildcards inside
|
|
164
|
+
* the loopback / private ranges need no rule: the dispatcher already connects
|
|
165
|
+
* to every local host directly (isLocalHost).
|
|
166
|
+
*/
|
|
167
|
+
export function translateProxyOverride(value) {
|
|
168
|
+
const hosts = [];
|
|
169
|
+
const suffixes = [];
|
|
170
|
+
for (const raw of String(value ?? '').split(';')) {
|
|
171
|
+
const entry = raw.trim().toLowerCase();
|
|
172
|
+
if (!entry || entry === '<local>') continue;
|
|
173
|
+
const bare = entry.replace(/^\[(.*)\]$/, '$1');
|
|
174
|
+
const family = isIP(bare);
|
|
175
|
+
if (family) {
|
|
176
|
+
hosts.push(canonicalIp(bare, family));
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const range = ipv4Wildcard(entry);
|
|
180
|
+
if (range) {
|
|
181
|
+
if (isLocalHost(range.start) && isLocalHost(range.end)) continue;
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
const wildcard = /^\*\.((?:[a-z0-9_-]+\.)*[a-z0-9_-]+)$/.exec(entry);
|
|
185
|
+
if (wildcard) suffixes.push(wildcard[1]);
|
|
186
|
+
else if (/^(?:[a-z0-9_-]+\.)*[a-z0-9_-]+$/.test(entry) && !/^[\d.]+$/.test(entry)) hosts.push(entry);
|
|
187
|
+
else return null;
|
|
188
|
+
}
|
|
189
|
+
return { hosts, suffixes };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* The decision for registry `values`: `{ proxy, bypass }`, or `{ reason }` when
|
|
194
|
+
* the system proxy must not be used. `silent` marks reasons that are the
|
|
195
|
+
* user's plain "no proxy" rather than a proxy we declined.
|
|
196
|
+
*/
|
|
197
|
+
export function systemProxyFromRegistry(values) {
|
|
198
|
+
if (!values) return { reason: 'registry unreadable', silent: true };
|
|
199
|
+
if (!/^(0x)?0*1$/i.test((values.proxyEnable ?? '').trim()) || !values.proxyServer) {
|
|
200
|
+
return { reason: 'disabled', silent: true };
|
|
201
|
+
}
|
|
202
|
+
if (values.autoConfigUrl) return { reason: 'a PAC script is configured' };
|
|
203
|
+
const server = parseProxyServer(values.proxyServer);
|
|
204
|
+
if (server.reason) return { reason: server.reason };
|
|
205
|
+
const bypass = translateProxyOverride(values.proxyOverride);
|
|
206
|
+
if (!bypass) return { reason: 'proxy exceptions cannot be translated exactly' };
|
|
207
|
+
return { proxy: { https: server.https, ...(server.http && { http: server.http }) }, bypass };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* `value` when it is an http(s) proxy URL the dispatcher can be built from:
|
|
212
|
+
* parsable, with a host, and credentials that decode (the dispatcher decodes
|
|
213
|
+
* them and throws on a malformed escape). null otherwise.
|
|
214
|
+
*/
|
|
215
|
+
function usableProxyUrl(value) {
|
|
216
|
+
try {
|
|
217
|
+
const { protocol, hostname, username, password } = new URL(value);
|
|
218
|
+
decodeURIComponent(username);
|
|
219
|
+
decodeURIComponent(password);
|
|
220
|
+
return (protocol === 'http:' || protocol === 'https:') && hostname ? value : null;
|
|
221
|
+
} catch {
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function stringList(value) {
|
|
227
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === 'string') : [];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* A handoff value as a decision `{ proxy, bypass }`; null when there is none;
|
|
232
|
+
* `{ invalid: true }` when it is malformed (any proxy URL that does not fully
|
|
233
|
+
* validate included).
|
|
234
|
+
*/
|
|
235
|
+
function parseHandoff(text) {
|
|
236
|
+
if (text === undefined) return null;
|
|
237
|
+
try {
|
|
238
|
+
const { proxy, bypass } = JSON.parse(text);
|
|
239
|
+
if (proxy === null) return { proxy: null };
|
|
240
|
+
const https = usableProxyUrl(proxy?.https);
|
|
241
|
+
const http = proxy.http === undefined ? null : usableProxyUrl(proxy.http);
|
|
242
|
+
if (!https || (proxy.http !== undefined && !http)) return { invalid: true };
|
|
243
|
+
return {
|
|
244
|
+
proxy: { https, ...(http && { http }) },
|
|
245
|
+
bypass: { hosts: stringList(bypass?.hosts), suffixes: stringList(bypass?.suffixes) },
|
|
246
|
+
};
|
|
247
|
+
} catch {
|
|
248
|
+
return { invalid: true };
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function discover(reader, log) {
|
|
253
|
+
let found;
|
|
254
|
+
try {
|
|
255
|
+
found = systemProxyFromRegistry(reader());
|
|
256
|
+
} catch {
|
|
257
|
+
return { proxy: null };
|
|
258
|
+
}
|
|
259
|
+
if (!found.proxy) {
|
|
260
|
+
if (!found.silent) log(`[mixdog] system proxy not used: ${found.reason}`);
|
|
261
|
+
return { proxy: null };
|
|
262
|
+
}
|
|
263
|
+
return found;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Chromium's proxy answer for one URL, such as
|
|
268
|
+
* `PROXY 127.0.0.1:7890; SOCKS5 127.0.0.1:7891; DIRECT`, read in fallback
|
|
269
|
+
* order: the first HTTP(S) proxy wins and DIRECT ends the list. `{ proxy }`
|
|
270
|
+
* with a proxy URL or null; `socks` when only SOCKS entries were skipped
|
|
271
|
+
* (fetch here cannot use them, so that origin goes direct).
|
|
272
|
+
*/
|
|
273
|
+
export function routeFromChromium(answer) {
|
|
274
|
+
let socks = false;
|
|
275
|
+
for (const entry of String(answer ?? '').split(';')) {
|
|
276
|
+
const [scheme = '', hostPort = ''] = entry.trim().split(/\s+/);
|
|
277
|
+
const kind = scheme.toUpperCase();
|
|
278
|
+
if (kind === 'DIRECT') break;
|
|
279
|
+
if (kind.startsWith('SOCKS')) {
|
|
280
|
+
socks = true;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
const url = (kind === 'PROXY' || kind === 'HTTPS') && proxyUrl(`${kind === 'HTTPS' ? 'https' : 'http'}://${hostPort}`);
|
|
284
|
+
if (url) return { proxy: url, socks: false };
|
|
285
|
+
}
|
|
286
|
+
return { proxy: null, socks };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** The fallback decision's route for `origin`: its proxy for the scheme unless the host is excepted. */
|
|
290
|
+
function fallbackRoute(origin) {
|
|
291
|
+
const proxy = active?.proxy;
|
|
292
|
+
if (!proxy) return DIRECT;
|
|
293
|
+
const { protocol, hostname } = new URL(origin);
|
|
294
|
+
const host = normalizeHost(hostname);
|
|
295
|
+
const { hosts, suffixes } = active.bypass;
|
|
296
|
+
if (hosts.includes(host) || suffixes.some((suffix) => host.endsWith(`.${suffix}`))) return DIRECT;
|
|
297
|
+
return { proxy: (protocol === 'https:' ? proxy.https : proxy.http) ?? null };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function resetRoutes() {
|
|
301
|
+
routes.clear();
|
|
302
|
+
lookups.clear();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** Logs never quote the answer, a URL or an error: any of them may carry credentials. */
|
|
306
|
+
function reportLookup(found) {
|
|
307
|
+
if (!found && !failureReported) {
|
|
308
|
+
failureReported = true;
|
|
309
|
+
log('[mixdog] system proxy lookup failed or timed out; connecting directly');
|
|
310
|
+
} else if (found?.socks && !found.proxy && !socksReported) {
|
|
311
|
+
socksReported = true;
|
|
312
|
+
log('[mixdog] system proxy is SOCKS only for a host; connecting to it directly');
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function lookup(origin, entry) {
|
|
317
|
+
let timer;
|
|
318
|
+
const pending = Promise.race([
|
|
319
|
+
Promise.resolve()
|
|
320
|
+
.then(() => entry.resolve(origin))
|
|
321
|
+
.then(routeFromChromium),
|
|
322
|
+
new Promise((resolve) => {
|
|
323
|
+
timer = setTimeout(() => resolve(null), RESOLVE_TIMEOUT_MS);
|
|
324
|
+
timer.unref?.();
|
|
325
|
+
}),
|
|
326
|
+
])
|
|
327
|
+
.catch(() => null)
|
|
328
|
+
.then((found) => {
|
|
329
|
+
clearTimeout(timer);
|
|
330
|
+
const route = found ? { proxy: found.proxy } : DIRECT;
|
|
331
|
+
// Not ours any more when the resolver changed meanwhile: not cached.
|
|
332
|
+
if (lookups.get(origin) === pending) {
|
|
333
|
+
lookups.delete(origin);
|
|
334
|
+
routes.set(origin, { route, expiresAt: Date.now() + (found ? ROUTE_TTL_MS : FAILED_ROUTE_TTL_MS) });
|
|
335
|
+
}
|
|
336
|
+
reportLookup(found);
|
|
337
|
+
return route;
|
|
338
|
+
});
|
|
339
|
+
lookups.set(origin, pending);
|
|
340
|
+
return pending;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* The route for `origin` (`scheme://host[:port]`): `{ proxy }`, a proxy URL or
|
|
345
|
+
* null for DIRECT. From the attached resolver's cached answer, or a promise
|
|
346
|
+
* while it is asked (one lookup per origin at a time); from the fallback
|
|
347
|
+
* decision when none is attached.
|
|
348
|
+
*/
|
|
349
|
+
export function systemProxyRoute(origin) {
|
|
350
|
+
const entry = resolvers.at(-1);
|
|
351
|
+
if (!entry) return fallbackRoute(origin);
|
|
352
|
+
const cached = routes.get(origin);
|
|
353
|
+
if (cached && cached.expiresAt > Date.now()) return cached.route;
|
|
354
|
+
return lookups.get(origin) ?? lookup(origin, entry);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function ensureRouting() {
|
|
358
|
+
if (routingInstalled) return true;
|
|
359
|
+
try {
|
|
360
|
+
installRouting();
|
|
361
|
+
} catch {
|
|
362
|
+
// The dispatcher is built before anything is installed, so nothing is half-applied.
|
|
363
|
+
log('[mixdog] system proxy not used: could not be applied');
|
|
364
|
+
active = { proxy: null };
|
|
365
|
+
installRouting = null;
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
routingInstalled = true;
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Settle this process's fallback system-proxy decision and route its own fetch
|
|
374
|
+
* by it. A handoff from the launching Mixdog process (always removed from
|
|
375
|
+
* `env`) is the decision; without one, Windows reads the registry. Nothing
|
|
376
|
+
* happens, nothing is handed on and no resolver is ever used when the user set
|
|
377
|
+
* a proxy variable or MIXDOG_SYSTEM_PROXY=0 (which also disables discovery).
|
|
378
|
+
* Returns true when a proxy was applied.
|
|
379
|
+
*/
|
|
380
|
+
export function adoptSystemProxy(
|
|
381
|
+
env = process.env,
|
|
382
|
+
{
|
|
383
|
+
platform = process.platform,
|
|
384
|
+
reader = readWindowsProxyRegistry,
|
|
385
|
+
log: logLine = (line) => process.stderr.write(`${line}\n`),
|
|
386
|
+
apply = adoptPrivateProxy,
|
|
387
|
+
} = {}
|
|
388
|
+
) {
|
|
389
|
+
const handoff = parseHandoff(env[HANDOFF_VAR]);
|
|
390
|
+
delete env[HANDOFF_VAR];
|
|
391
|
+
active = null;
|
|
392
|
+
installRouting = null;
|
|
393
|
+
routingInstalled = false;
|
|
394
|
+
log = logLine;
|
|
395
|
+
resolvers.length = 0;
|
|
396
|
+
resetRoutes();
|
|
397
|
+
if (env.MIXDOG_SYSTEM_PROXY === '0' || USER_PROXY_VARS.some((name) => env[name] !== undefined)) return false;
|
|
398
|
+
installRouting = () => apply(systemProxyRoute);
|
|
399
|
+
if (handoff?.invalid) {
|
|
400
|
+
log('[mixdog] system proxy not used: invalid handoff');
|
|
401
|
+
active = { proxy: null };
|
|
402
|
+
return false;
|
|
403
|
+
}
|
|
404
|
+
active = handoff ?? (platform === 'win32' ? discover(reader, log) : null);
|
|
405
|
+
if (!active?.proxy) return false;
|
|
406
|
+
return ensureRouting();
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Route this process by `resolve(origin)` — a promise of Chromium's proxy
|
|
411
|
+
* answer for that origin — until the returned detach is called. The newest
|
|
412
|
+
* attached resolver answers; every attach and detach drops the cached routes.
|
|
413
|
+
* A no-op when this process routes nothing (the user's variables or
|
|
414
|
+
* MIXDOG_SYSTEM_PROXY=0 decide).
|
|
415
|
+
*/
|
|
416
|
+
export function attachSystemProxyResolver(resolve) {
|
|
417
|
+
if (!installRouting || !ensureRouting()) return () => {};
|
|
418
|
+
const entry = { resolve };
|
|
419
|
+
resolvers.push(entry);
|
|
420
|
+
failureReported = false;
|
|
421
|
+
resetRoutes();
|
|
422
|
+
return () => {
|
|
423
|
+
const index = resolvers.indexOf(entry);
|
|
424
|
+
if (index < 0) return;
|
|
425
|
+
resolvers.splice(index, 1);
|
|
426
|
+
resetRoutes();
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* npm's own proxy settings for the one Mixdog-owned npm spawn (the self-update
|
|
432
|
+
* install): the route of the registry npm uses (`registry()`, asked only when
|
|
433
|
+
* this process routes at all); empty for DIRECT or without a decision. Meant
|
|
434
|
+
* for that spawn's env only, never for the process env.
|
|
435
|
+
*/
|
|
436
|
+
export async function systemProxyNpmEnv(registry) {
|
|
437
|
+
if (!installRouting) return {};
|
|
438
|
+
const { origin, hostname } = new URL(await registry());
|
|
439
|
+
if (isLocalHost(hostname)) return {};
|
|
440
|
+
const { proxy } = await systemProxyRoute(origin);
|
|
441
|
+
if (!proxy) return {};
|
|
442
|
+
return {
|
|
443
|
+
npm_config_proxy: proxy,
|
|
444
|
+
npm_config_https_proxy: proxy,
|
|
445
|
+
npm_config_noproxy: 'localhost,127.0.0.1,::1,.local',
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/** The handoff for a Mixdog process this one starts; empty when no decision was made. */
|
|
450
|
+
export function systemProxyHandoffEnv() {
|
|
451
|
+
return active ? { [HANDOFF_VAR]: JSON.stringify(active) } : {};
|
|
452
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { FETCH_BLOCKED_PORTS, FETCH_SAFE_ATTEMPTS } from './fetch-safe-listen.mjs';
|
|
2
|
+
|
|
1
3
|
// One loopback listener owns each bind attempt, its event handlers, and stop.
|
|
2
4
|
export function createLoopbackListener({
|
|
3
5
|
server,
|
|
@@ -24,7 +26,15 @@ export function createLoopbackListener({
|
|
|
24
26
|
|
|
25
27
|
const { promise, resolve, reject } = Promise.withResolvers();
|
|
26
28
|
const controller = new AbortController();
|
|
27
|
-
const attempt = {
|
|
29
|
+
const attempt = {
|
|
30
|
+
promise,
|
|
31
|
+
reject,
|
|
32
|
+
controller,
|
|
33
|
+
port: basePort,
|
|
34
|
+
bound: false,
|
|
35
|
+
blockedRetries: FETCH_SAFE_ATTEMPTS,
|
|
36
|
+
detach,
|
|
37
|
+
};
|
|
28
38
|
current = attempt;
|
|
29
39
|
|
|
30
40
|
function detach() {
|
|
@@ -35,6 +45,19 @@ export function createLoopbackListener({
|
|
|
35
45
|
function onListening() {
|
|
36
46
|
if (current !== attempt) return;
|
|
37
47
|
const boundPort = server.address().port;
|
|
48
|
+
// An OS-assigned port can be one fetch clients refuse ("bad port"):
|
|
49
|
+
// close it and take another, a bounded number of times.
|
|
50
|
+
if (attempt.port === 0 && FETCH_BLOCKED_PORTS.has(boundPort)) {
|
|
51
|
+
const exhausted = attempt.blockedRetries <= 0;
|
|
52
|
+
attempt.blockedRetries -= 1;
|
|
53
|
+
server.close(() => {
|
|
54
|
+
if (current !== attempt) return;
|
|
55
|
+
if (exhausted) onError(new Error('no fetch-safe loopback port was assigned'));
|
|
56
|
+
else listen();
|
|
57
|
+
});
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
server.off('listening', onListening);
|
|
38
61
|
attempt.bound = true;
|
|
39
62
|
reportListening(boundPort);
|
|
40
63
|
resolve(boundPort);
|
|
@@ -67,7 +90,7 @@ export function createLoopbackListener({
|
|
|
67
90
|
}
|
|
68
91
|
|
|
69
92
|
server.on('error', onError);
|
|
70
|
-
server.
|
|
93
|
+
server.on('listening', onListening);
|
|
71
94
|
listen();
|
|
72
95
|
return promise;
|
|
73
96
|
}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
export function canonicalRouteProvider(value: unknown): string;
|
|
2
2
|
export function routeChain(route: Record<string, unknown>): Record<string, unknown>[];
|
|
3
|
-
export function
|
|
3
|
+
export function executionFallbacks(route: Record<string, unknown> | null | undefined): Record<string, unknown>[];
|
|
4
|
+
export function validateRouteChainProviders(
|
|
5
|
+
routes: Record<string, unknown>[],
|
|
6
|
+
options?: { allowDuplicates?: boolean }
|
|
7
|
+
): void;
|
|
4
8
|
export function normalizeRouteChain(
|
|
5
9
|
route: Record<string, unknown>,
|
|
6
10
|
normalizeLeaf: (route: Record<string, unknown>, fallback: Record<string, unknown>) => Record<string, unknown> | null,
|
|
7
|
-
fallback?: Record<string, unknown
|
|
11
|
+
fallback?: Record<string, unknown>,
|
|
12
|
+
options?: { strict?: boolean }
|
|
8
13
|
): Record<string, unknown> | null;
|
|
@@ -22,18 +22,36 @@ export function routeChain(route) {
|
|
|
22
22
|
return [primary, ...fallbacks];
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
/** The fallbacks a chain runs: those on a provider not already used by the
|
|
26
|
+
* primary or an earlier fallback. The stored list is never changed; a fallback
|
|
27
|
+
* on the main model's own provider (left there by a later main-model change)
|
|
28
|
+
* is simply inactive. */
|
|
29
|
+
export function executionFallbacks(route) {
|
|
30
|
+
const [primary, ...fallbacks] = routeChain(route);
|
|
31
|
+
if (!primary) return [];
|
|
32
|
+
const seen = new Set([canonicalRouteProvider(primary.provider)]);
|
|
33
|
+
return fallbacks.filter((entry) => {
|
|
34
|
+
const provider = canonicalRouteProvider(entry.provider);
|
|
35
|
+
if (seen.has(provider)) return false;
|
|
36
|
+
seen.add(provider);
|
|
37
|
+
return true;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** `allowDuplicates` accepts a stored chain whose fallbacks repeat a provider
|
|
42
|
+
* (see executionFallbacks); an explicit save validates without it. */
|
|
43
|
+
export function validateRouteChainProviders(routes, { allowDuplicates = false } = {}) {
|
|
26
44
|
const providers = new Set();
|
|
27
45
|
for (const route of routes) {
|
|
28
46
|
const provider = canonicalRouteProvider(route.provider);
|
|
29
47
|
if (!provider || !String(route.model || '').trim()) throw new Error('route requires provider and model');
|
|
30
|
-
if (providers.has(provider)) throw new Error(`duplicate route provider "${provider}"`);
|
|
48
|
+
if (!allowDuplicates && providers.has(provider)) throw new Error(`duplicate route provider "${provider}"`);
|
|
31
49
|
if (providers.size && provider === 'default') throw new Error('follow Main is only allowed as the primary route');
|
|
32
50
|
providers.add(provider);
|
|
33
51
|
}
|
|
34
52
|
}
|
|
35
53
|
|
|
36
|
-
export function normalizeRouteChain(route, normalizeLeaf, fallback = {}) {
|
|
54
|
+
export function normalizeRouteChain(route, normalizeLeaf, fallback = {}, { strict = true } = {}) {
|
|
37
55
|
const raw = routeChain({ ...fallback, ...route });
|
|
38
56
|
const primary = normalizeLeaf(route, fallback);
|
|
39
57
|
if (!primary) return null;
|
|
@@ -42,7 +60,7 @@ export function normalizeRouteChain(route, normalizeLeaf, fallback = {}) {
|
|
|
42
60
|
if (!normalized) throw new Error('fallback route requires provider and model');
|
|
43
61
|
return { ...normalized, provider: canonicalRouteProvider(normalized.provider) };
|
|
44
62
|
});
|
|
45
|
-
validateRouteChainProviders(routes);
|
|
63
|
+
validateRouteChainProviders(routes, { allowDuplicates: !strict });
|
|
46
64
|
const [first, ...rest] = routes;
|
|
47
65
|
return rest.length ? { ...first, fallbacks: rest } : first;
|
|
48
66
|
}
|
|
@@ -2,6 +2,16 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { isWSL } from './wsl.mjs';
|
|
3
3
|
import { detachedSpawnOpts } from './spawn-flags.mjs';
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Whether a browser can plausibly open on this machine: not inside an SSH
|
|
7
|
+
* session, and on non-WSL Linux a DISPLAY or WAYLAND_DISPLAY exists.
|
|
8
|
+
*/
|
|
9
|
+
export function canOpenBrowser(env = process.env, platform = process.platform, wsl = isWSL()) {
|
|
10
|
+
if (env.SSH_TTY || env.SSH_CONNECTION) return false;
|
|
11
|
+
if (platform !== 'linux' || wsl) return true;
|
|
12
|
+
return Boolean(env.DISPLAY || env.WAYLAND_DISPLAY);
|
|
13
|
+
}
|
|
14
|
+
|
|
5
15
|
/**
|
|
6
16
|
* Open a URL in the user's default browser. Best-effort and non-blocking.
|
|
7
17
|
* Callers also print the URL, so opener failure should never block OAuth.
|
|
@@ -20,8 +20,17 @@ export const MAX_ACCOUNTS = 20;
|
|
|
20
20
|
// the host's dir explicitly while its own MIXDOG_DATA_DIR points elsewhere.
|
|
21
21
|
const file = (dataDir = resolvePluginData()) => join(dataDir, 'provider-accounts.json');
|
|
22
22
|
|
|
23
|
+
// A custom provider (`custom-<id>`) pools its API keys in the same roster: its
|
|
24
|
+
// primary key is the `default` account and each additional key one more
|
|
25
|
+
// account (`setCustomProviderKeyIds`). A single-key provider has no roster.
|
|
26
|
+
export function isCustomKeyProvider(provider) {
|
|
27
|
+
return typeof provider === 'string' && /^custom-./.test(provider);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const isAccountProvider = (provider) => ACCOUNT_PROVIDERS.includes(provider) || isCustomKeyProvider(provider);
|
|
31
|
+
|
|
23
32
|
function requireAccountProvider(provider) {
|
|
24
|
-
if (!
|
|
33
|
+
if (!isAccountProvider(provider)) throw new TypeError('Unknown OAuth provider.');
|
|
25
34
|
return provider;
|
|
26
35
|
}
|
|
27
36
|
|
|
@@ -45,7 +54,7 @@ function validate(raw) {
|
|
|
45
54
|
for (const [provider, pool] of Object.entries(raw.providers)) {
|
|
46
55
|
// A provider a newer build added is carried as is: this build never reads
|
|
47
56
|
// or writes its pool, and treating it as damage would discard it.
|
|
48
|
-
if (!
|
|
57
|
+
if (!isAccountProvider(provider)) continue;
|
|
49
58
|
if (!Array.isArray(pool?.accounts) || pool.accounts.length > MAX_ACCOUNTS) {
|
|
50
59
|
throw new Error('Invalid provider account list.');
|
|
51
60
|
}
|
|
@@ -259,6 +268,47 @@ export async function selectProviderAccountIf(provider, expectedSelectedId, id,
|
|
|
259
268
|
return moved ? pool : null;
|
|
260
269
|
}
|
|
261
270
|
|
|
271
|
+
/** The additional API key ids of custom provider `provider`, in use order. */
|
|
272
|
+
export function customProviderKeyIds(provider, dataDir = resolvePluginData()) {
|
|
273
|
+
if (!isCustomKeyProvider(provider)) throw new TypeError('Unknown custom provider.');
|
|
274
|
+
return readProviderAccountPool(provider, dataDir)
|
|
275
|
+
.accounts.map((row) => row.id)
|
|
276
|
+
.filter((id) => id !== 'default');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Sets custom provider `provider`'s additional API keys to `ids`, in use
|
|
280
|
+
// order after its primary key (`default`). A kept key keeps its cooldown and a
|
|
281
|
+
// selection still listed stays. Switching is always automatic and API keys
|
|
282
|
+
// have no plan windows to respect, so the roster switches on limits with
|
|
283
|
+
// credit use on. Without an additional key the roster is removed, so a
|
|
284
|
+
// single-key provider stores nothing here.
|
|
285
|
+
export async function setCustomProviderKeyIds(provider, ids) {
|
|
286
|
+
if (!isCustomKeyProvider(provider)) throw new TypeError('Unknown custom provider.');
|
|
287
|
+
if (!Array.isArray(ids) || ids.length >= MAX_ACCOUNTS || new Set(ids).size !== ids.length) {
|
|
288
|
+
throw new TypeError(`At most ${MAX_ACCOUNTS} API keys can be used.`);
|
|
289
|
+
}
|
|
290
|
+
for (const id of ids) if (requireAccountId(id) === 'default') throw new TypeError('Invalid provider account id.');
|
|
291
|
+
read();
|
|
292
|
+
await updateJsonAtomic(
|
|
293
|
+
file(),
|
|
294
|
+
(raw) => {
|
|
295
|
+
const state = validate(raw);
|
|
296
|
+
const pool = state.providers[provider];
|
|
297
|
+
if (!ids.length) {
|
|
298
|
+
delete state.providers[provider];
|
|
299
|
+
return validate(state);
|
|
300
|
+
}
|
|
301
|
+
const rows = pool?.accounts || [];
|
|
302
|
+
const accounts = ['default', ...ids].map((id) => rows.find((row) => row.id === id) || { id });
|
|
303
|
+
const selectedId = accounts.some((row) => row.id === pool?.selectedId) ? pool.selectedId : 'default';
|
|
304
|
+
state.providers[provider] = { accounts, selectedId, auto: true, policy: 'limit', useCredits: true };
|
|
305
|
+
return validate(state);
|
|
306
|
+
},
|
|
307
|
+
{ mode: 0o600, secret: true, fsyncDir: true, backup: true }
|
|
308
|
+
);
|
|
309
|
+
return customProviderKeyIds(provider);
|
|
310
|
+
}
|
|
311
|
+
|
|
262
312
|
export async function removeProviderAccount(provider, id) {
|
|
263
313
|
requireAccountId(id);
|
|
264
314
|
return update(provider, (pool) => {
|
|
@@ -45,6 +45,20 @@ export function emitSessionApprovalCardChange(sessionId = '') {
|
|
|
45
45
|
emit(approvalCardListeners, sessionId);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
// The bots whose sends are in flight in a session changed (bot-owned-work.mjs):
|
|
49
|
+
// the session catalog stamps them on each row (`handedBy`) and re-reads.
|
|
50
|
+
const botWorkListeners = new Set();
|
|
51
|
+
|
|
52
|
+
export function onSessionBotWorkChange(listener) {
|
|
53
|
+
if (typeof listener !== 'function') return () => {};
|
|
54
|
+
botWorkListeners.add(listener);
|
|
55
|
+
return () => botWorkListeners.delete(listener);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function emitSessionBotWorkChange(sessionId = '') {
|
|
59
|
+
emit(botWorkListeners, sessionId);
|
|
60
|
+
}
|
|
61
|
+
|
|
48
62
|
function emit(targets, sessionId) {
|
|
49
63
|
for (const listener of [...targets]) {
|
|
50
64
|
try {
|