mixdog 1.4.1 → 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/gate-local.mjs +172 -61
- package/scripts/lib/gate-scheduler.mjs +138 -32
- package/scripts/lib/gate-timing-reporter.mjs +16 -0
- package/scripts/lib/run-node-tests.mjs +54 -29
- package/scripts/lib/scoped-temp.mjs +1 -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/pptx-kit-index.mjs +5 -6
- package/scripts/suite-health.mjs +110 -28
- 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/cli.mjs +7 -0
- package/src/defaults/agents.json +7 -0
- package/src/defaults/shipped-history.json +18 -2
- package/src/defaults/skills/browser-use/SKILL.md +4 -1
- 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 +12 -2
- 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 +6 -30
- package/src/rules/lead/LEAD.md +3 -3
- package/src/rules/shared/20-research.md +3 -6
- package/src/rules/shared/40-editing.md +1 -0
- package/src/rules/shared/60-verification.md +10 -9
- package/src/rules/shared/70-delivery.md +4 -3
- 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 +130 -21
- 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/config.mjs +12 -7
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +7 -7
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +5 -26
- 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 +83 -52
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +42 -26
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +43 -8
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +30 -28
- 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/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.mjs +1 -1
- 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/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 +48 -31
- 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.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-oauth-login.mjs +26 -28
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +1 -0
- 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 +33 -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/builtin-features.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +2 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +3 -0
- 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/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-warm.mjs +94 -10
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +10 -5
- package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +5 -5
- package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +11 -1
- 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/fresh-context.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +21 -2
- 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/turn-limits.mjs +13 -8
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +50 -28
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +3 -4
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +2 -0
- 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 +25 -14
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +17 -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 +7 -3
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +47 -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/restart-prefix-parity.fixture.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +243 -38
- 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/tools/builtin/bash-tool/completed-result.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +36 -26
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +3 -5
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +58 -20
- 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/graph-manifest.json +13 -13
- 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 +2 -0
- 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/whisper-port.mjs +19 -10
- package/src/runtime/channels/lib/whisper-server.mjs +10 -1
- 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/memory/index.mjs +4 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +4 -1
- package/src/runtime/memory/lib/embedding-worker.mjs +4 -0
- package/src/runtime/office/quality/assurance-structure-docx.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/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/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/llm/cost.mjs +6 -1
- 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/provider-accounts.mjs +52 -2
- package/src/runtime/shared/staged-install-worker.mjs +5 -0
- package/src/runtime/shared/staged-update.mjs +47 -2
- package/src/runtime/shared/tool-surface.mjs +110 -36
- package/src/runtime/shared/tool-work-units.mjs +4 -0
- 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/git.mjs +57 -0
- package/src/runtime/shared/worktrees/manager.mjs +205 -137
- 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 +4 -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-task-sessions.mjs +410 -353
- package/src/session-runtime/boot/tools.mjs +2 -0
- package/src/session-runtime/bot-status.mjs +103 -103
- package/src/session-runtime/connector-merge-migration.mjs +18 -15
- package/src/session-runtime/conversation-import/discovery.mjs +159 -40
- package/src/session-runtime/conversation-import/service.mjs +9 -3
- package/src/session-runtime/handoff-attachments.mjs +1 -1
- package/src/session-runtime/held-requests.mjs +3 -1
- 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/workspace-tools.mjs +143 -116
- 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/provider-auth/login.mjs +2 -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/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-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 +281 -10
- 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/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-route.mjs +10 -1
- 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 -1066
- 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 +54 -54
- package/src/standalone/daemon-desktop-runtime.mjs +2 -0
- package/src/standalone/daemon-session-control.mjs +151 -136
- 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.mjs +45 -9
- package/src/standalone/session-service/projection/transcript-window.mjs +700 -64
- package/src/standalone/session-service/session-calls/session-views.mjs +160 -104
- package/src/standalone/session-service/stored-reader.mjs +127 -40
- package/src/tui/App.jsx +154 -164
- package/src/tui/app/app-view/floating-panel.jsx +3 -0
- package/src/tui/app/app-view/text-entry-labels.mjs +1 -0
- package/src/tui/app/clipboard.mjs +21 -10
- 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 +25 -6
- package/src/tui/app/provider-setup-picker.mjs +20 -13
- 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/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 +1896 -1341
- 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/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/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
|
+
}
|
|
@@ -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) => {
|
|
@@ -6,8 +6,13 @@
|
|
|
6
6
|
* quitting mid-install. Does its work and exits; the swap happens on a later
|
|
7
7
|
* clean launch via performPendingSwap().
|
|
8
8
|
*/
|
|
9
|
+
import { adoptSystemProxy } from './llm/system-proxy.mjs';
|
|
9
10
|
import { runStagedInstall } from './staged-update.mjs';
|
|
10
11
|
|
|
12
|
+
// Consume the system proxy handed over by the launcher (removing it from this
|
|
13
|
+
// env); only the npm install below receives it, as npm settings.
|
|
14
|
+
adoptSystemProxy();
|
|
15
|
+
|
|
11
16
|
const version = process.argv[2] || process.env.MIXDOG_STAGE_VERSION || '';
|
|
12
17
|
runStagedInstall(version)
|
|
13
18
|
.then((r) => process.exit(r?.ok || r?.alreadyStaged || r?.inProgress ? 0 : 1))
|
|
@@ -46,6 +46,7 @@ import { dirname, basename, join } from 'node:path';
|
|
|
46
46
|
import { fileURLToPath } from 'node:url';
|
|
47
47
|
import { spawn } from 'node:child_process';
|
|
48
48
|
import { resolvePluginData } from './plugin-paths.mjs';
|
|
49
|
+
import { systemProxyHandoffEnv, systemProxyNpmEnv } from './llm/system-proxy.mjs';
|
|
49
50
|
// Same probe every discovery/lock reader uses; `process.pid` is trivially alive.
|
|
50
51
|
import { isPidAlive as pidAlive } from './pid-liveness.mjs';
|
|
51
52
|
import { sleepSync } from './sleep.mjs';
|
|
@@ -222,6 +223,38 @@ function releaseStagingLock(verDir) {
|
|
|
222
223
|
}
|
|
223
224
|
}
|
|
224
225
|
|
|
226
|
+
const DEFAULT_NPM_REGISTRY = 'https://registry.npmjs.org/';
|
|
227
|
+
|
|
228
|
+
/** The registry npm installs from (its .npmrc files included); npm's default when npm cannot say. */
|
|
229
|
+
function npmRegistry(cliJs) {
|
|
230
|
+
return new Promise((resolve) => {
|
|
231
|
+
let out = '';
|
|
232
|
+
let child;
|
|
233
|
+
try {
|
|
234
|
+
child = spawn(process.execPath, [cliJs, 'config', 'get', 'registry'], {
|
|
235
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
236
|
+
shell: false,
|
|
237
|
+
timeout: 10_000,
|
|
238
|
+
...hiddenSpawnOpts,
|
|
239
|
+
});
|
|
240
|
+
} catch {
|
|
241
|
+
resolve(DEFAULT_NPM_REGISTRY);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
child.stdout?.on('data', (chunk) => {
|
|
245
|
+
out += chunk;
|
|
246
|
+
});
|
|
247
|
+
child.once('error', () => resolve(DEFAULT_NPM_REGISTRY));
|
|
248
|
+
child.once('close', (code) => {
|
|
249
|
+
try {
|
|
250
|
+
resolve(code === 0 ? new URL(out.trim()).href : DEFAULT_NPM_REGISTRY);
|
|
251
|
+
} catch {
|
|
252
|
+
resolve(DEFAULT_NPM_REGISTRY);
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
225
258
|
/**
|
|
226
259
|
* runStagedInstall(version) — perform the full staging install for one version
|
|
227
260
|
* into ~/.mixdog/data/staging/<version>. Runs to completion (awaits the npm
|
|
@@ -259,10 +292,17 @@ export async function runStagedInstall(version) {
|
|
|
259
292
|
// shell spawn (AV constraint — no shell spawns).
|
|
260
293
|
const cliJs = npmCliJsPath();
|
|
261
294
|
if (!cliJs) return { ok: false, error: 'npm-cli.js unresolved — staging skipped' };
|
|
295
|
+
// The system proxy of npm's registry, as npm's own settings for this one install.
|
|
296
|
+
const npmProxyEnv = await systemProxyNpmEnv(() => npmRegistry(cliJs));
|
|
262
297
|
const code = await new Promise((res) => {
|
|
263
298
|
let child;
|
|
264
299
|
try {
|
|
265
|
-
child = spawn(process.execPath, [cliJs, ...installArgs], {
|
|
300
|
+
child = spawn(process.execPath, [cliJs, ...installArgs], {
|
|
301
|
+
stdio: 'ignore',
|
|
302
|
+
shell: false,
|
|
303
|
+
env: { ...process.env, ...npmProxyEnv },
|
|
304
|
+
...hiddenSpawnOpts,
|
|
305
|
+
});
|
|
266
306
|
} catch {
|
|
267
307
|
res(-1);
|
|
268
308
|
return;
|
|
@@ -319,7 +359,12 @@ export function spawnStagedInstall(version) {
|
|
|
319
359
|
if (isStagedComplete(v)) return false;
|
|
320
360
|
try {
|
|
321
361
|
const worker = join(_MODULE_DIR, 'staged-install-worker.mjs');
|
|
322
|
-
|
|
362
|
+
// The worker gets the system proxy decision privately (it consumes it).
|
|
363
|
+
const child = spawn(process.execPath, [worker, v], {
|
|
364
|
+
stdio: 'ignore',
|
|
365
|
+
env: { ...process.env, ...systemProxyHandoffEnv() },
|
|
366
|
+
...detachedSpawnOpts,
|
|
367
|
+
});
|
|
323
368
|
child.once?.('error', () => {});
|
|
324
369
|
child.unref?.();
|
|
325
370
|
return true;
|