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
|
@@ -128,12 +128,113 @@ export function renameWithRetrySync(src, dst, opts = {}) {
|
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
// Windows refuses to rename over a file while any handle on it is open, so
|
|
132
|
+
// this process's own reads of a busy state file (a delivery loop polling it)
|
|
133
|
+
// could fail every attempt of its writer's rename until the retries ran out.
|
|
134
|
+
// Here a rename attempt waits for the reads of its target in flight and holds
|
|
135
|
+
// new ones off only while it runs; other processes are what the retries cover.
|
|
136
|
+
// A read still open after RENAME_READ_WAIT_MS is left behind with its path's
|
|
137
|
+
// entry (a stuck read must not hold the writer, its lock, or later readers),
|
|
138
|
+
// and the attempt goes ahead under those same retries. Likewise a rename not
|
|
139
|
+
// returned by then: readers held off read anyway, as without it. A reader's
|
|
140
|
+
// bound counts from the rename call, not from the attempt's own (bounded)
|
|
141
|
+
// wait for reads, so a rename that just started is never read across.
|
|
142
|
+
export const RENAME_READ_WAIT_MS = 1_000;
|
|
143
|
+
const targetUse = new Map();
|
|
144
|
+
const targetKey = (path) => {
|
|
145
|
+
const full = resolve(path);
|
|
146
|
+
return process.platform === 'win32' ? full.toLowerCase() : full;
|
|
147
|
+
};
|
|
148
|
+
function useOf(key) {
|
|
149
|
+
let use = targetUse.get(key);
|
|
150
|
+
if (!use) targetUse.set(key, (use = { reads: 0, renaming: null, draining: null, drained: [] }));
|
|
151
|
+
return use;
|
|
152
|
+
}
|
|
153
|
+
function forgetIfIdle(key, use) {
|
|
154
|
+
if (use.reads === 0 && !use.renaming && targetUse.get(key) === use) targetUse.delete(key);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Waits for the rename `use` runs; one that has not returned within the bound
|
|
158
|
+
// is left behind with the entry, and the caller goes on as without it.
|
|
159
|
+
async function outwaitRename(key, use) {
|
|
160
|
+
await use.draining;
|
|
161
|
+
if (use.renaming && !(await settlesWithin(use.renaming, RENAME_READ_WAIT_MS)) && targetUse.get(key) === use) {
|
|
162
|
+
targetUse.delete(key);
|
|
163
|
+
}
|
|
164
|
+
return useOf(key);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function settlesWithin(promise, ms) {
|
|
168
|
+
let timer;
|
|
169
|
+
const bound = new Promise((resolve) => {
|
|
170
|
+
timer = setTimeout(() => resolve(false), ms);
|
|
171
|
+
});
|
|
172
|
+
return Promise.race([promise.then(() => true), bound]).finally(() => clearTimeout(timer));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function readTarget(path, read) {
|
|
176
|
+
const key = targetKey(path);
|
|
177
|
+
let use = useOf(key);
|
|
178
|
+
while (use.renaming) use = await outwaitRename(key, use);
|
|
179
|
+
use.reads += 1;
|
|
180
|
+
try {
|
|
181
|
+
return await read();
|
|
182
|
+
} finally {
|
|
183
|
+
use.reads -= 1;
|
|
184
|
+
if (use.reads === 0) for (const done of use.drained.splice(0)) done();
|
|
185
|
+
forgetIfIdle(key, use);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function renameOver(src, dst) {
|
|
190
|
+
const key = targetKey(dst);
|
|
191
|
+
let use = useOf(key);
|
|
192
|
+
while (use.renaming) use = await outwaitRename(key, use);
|
|
193
|
+
let finish;
|
|
194
|
+
use.renaming = new Promise((resolve) => {
|
|
195
|
+
finish = resolve;
|
|
196
|
+
});
|
|
197
|
+
use.draining = use.reads > 0 ? readsDrained(use, RENAME_READ_WAIT_MS) : null;
|
|
198
|
+
try {
|
|
199
|
+
if (use.draining && !(await use.draining) && targetUse.get(key) === use) targetUse.delete(key);
|
|
200
|
+
await renameAsync(src, dst);
|
|
201
|
+
} finally {
|
|
202
|
+
use.renaming = null;
|
|
203
|
+
use.draining = null;
|
|
204
|
+
finish();
|
|
205
|
+
forgetIfIdle(key, use);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// True once `use` has no read in flight, false after `ms` (its waiter removed).
|
|
210
|
+
function readsDrained(use, ms) {
|
|
211
|
+
return new Promise((resolve) => {
|
|
212
|
+
const done = () => {
|
|
213
|
+
clearTimeout(timer);
|
|
214
|
+
resolve(true);
|
|
215
|
+
};
|
|
216
|
+
const timer = setTimeout(() => {
|
|
217
|
+
const at = use.drained.indexOf(done);
|
|
218
|
+
if (at >= 0) use.drained.splice(at, 1);
|
|
219
|
+
resolve(false);
|
|
220
|
+
}, ms);
|
|
221
|
+
use.drained.push(done);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Test seam: the per-path coordination left (entries, drain waiters). */
|
|
226
|
+
export function _renameCoordinationForTest() {
|
|
227
|
+
let waiters = 0;
|
|
228
|
+
for (const use of targetUse.values()) waiters += use.drained.length;
|
|
229
|
+
return { entries: targetUse.size, waiters };
|
|
230
|
+
}
|
|
231
|
+
|
|
131
232
|
async function renameWithRetry(src, dst, opts = {}) {
|
|
132
233
|
const backoffs = renameBackoffs(opts);
|
|
133
234
|
let lastErr = null;
|
|
134
235
|
for (let attempt = 0; attempt <= backoffs.length; attempt++) {
|
|
135
236
|
try {
|
|
136
|
-
await
|
|
237
|
+
await renameOver(src, dst);
|
|
137
238
|
return true;
|
|
138
239
|
} catch (err) {
|
|
139
240
|
lastErr = err;
|
|
@@ -433,7 +534,7 @@ function readContentOrNullSync(filePath) {
|
|
|
433
534
|
|
|
434
535
|
async function readContentOrNullAsync(filePath) {
|
|
435
536
|
try {
|
|
436
|
-
return await readFileAsync(filePath);
|
|
537
|
+
return await readTarget(filePath, () => readFileAsync(filePath));
|
|
437
538
|
} catch (err) {
|
|
438
539
|
if (err?.code === 'ENOENT') return null;
|
|
439
540
|
throw err;
|
|
@@ -310,7 +310,9 @@ export function cleanupBackgroundTasks(options = {}) {
|
|
|
310
310
|
function cancelRunningTask(task, reason, notify, terminalReason = null) {
|
|
311
311
|
try {
|
|
312
312
|
task.cancel?.();
|
|
313
|
-
} catch {
|
|
313
|
+
} catch {
|
|
314
|
+
// A failing cancel hook must not stop the task from being marked cancelled.
|
|
315
|
+
}
|
|
314
316
|
completeBackgroundTask(task.taskId, { status: 'cancelled', error: reason, notify: notify === true, terminalReason });
|
|
315
317
|
if (task.notifyOnCancel !== true || notify === true || task.status !== 'cancelled' || task.cancelNotify) return;
|
|
316
318
|
if (notify === false) {
|
|
@@ -370,9 +372,12 @@ export function cancelBackgroundTasks(options = {}) {
|
|
|
370
372
|
const wanted = clean(options.surface);
|
|
371
373
|
const executionSessionId = clean(options.executionSessionId);
|
|
372
374
|
const scope = normalizeTaskScope(options);
|
|
373
|
-
|
|
375
|
+
// Optional: only tasks started at/after this time (a turn's own work).
|
|
376
|
+
const startedSinceMs = Number(options.startedSinceMs) || 0;
|
|
377
|
+
const taskIds = [];
|
|
374
378
|
for (const task of [...tasks.values()]) {
|
|
375
379
|
if (wanted && task.surface !== wanted) continue;
|
|
380
|
+
if (startedSinceMs > 0 && !(task.startedAtMs >= startedSinceMs)) continue;
|
|
376
381
|
// Scoped teardown must be exact. Legacy tasks without ownership stay
|
|
377
382
|
// visible/recoverable, but may not be claimed by whichever session happens
|
|
378
383
|
// to close first.
|
|
@@ -386,9 +391,9 @@ export function cancelBackgroundTasks(options = {}) {
|
|
|
386
391
|
continue;
|
|
387
392
|
}
|
|
388
393
|
cancelRunningTask(task, reason, options.notify, terminalReason);
|
|
389
|
-
|
|
394
|
+
taskIds.push(task.taskId);
|
|
390
395
|
}
|
|
391
|
-
return { cancelled };
|
|
396
|
+
return { cancelled: taskIds.length, taskIds };
|
|
392
397
|
}
|
|
393
398
|
|
|
394
399
|
export function acknowledgeBackgroundTaskCompletion(taskId, options = {}) {
|
|
@@ -415,13 +420,17 @@ function resultTextForTask(task) {
|
|
|
415
420
|
// fallback below so the completion notification still carries a body.
|
|
416
421
|
try {
|
|
417
422
|
process.stderr.write(`[background-${task.surface}] renderResult failed: ${err?.message || err}\n`);
|
|
418
|
-
} catch {
|
|
423
|
+
} catch {
|
|
424
|
+
// A closed stderr must not lose the completion body.
|
|
425
|
+
}
|
|
419
426
|
}
|
|
420
427
|
}
|
|
421
428
|
if (typeof task.result === 'string') return task.result;
|
|
422
429
|
try {
|
|
423
430
|
return JSON.stringify(task.result, null, 2);
|
|
424
|
-
} catch {
|
|
431
|
+
} catch {
|
|
432
|
+
// Unserializable result (circular, BigInt): fall back to the empty body.
|
|
433
|
+
}
|
|
425
434
|
}
|
|
426
435
|
return '';
|
|
427
436
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Bot-owned work: the live work in a session that a bot sent. A bot's accepted
|
|
2
|
+
// `session` send or create (session-runtime/agent-task-sessions.mjs) is the
|
|
3
|
+
// bot's work in that session from its acceptance until it settles or is
|
|
4
|
+
// cancelled — whether or not it reports back (`notify: false`, a report watch
|
|
5
|
+
// that could not be stored). A turn nobody sent (the user typed it in the
|
|
6
|
+
// task session) is owned by no bot, so it never reads as a bot being busy.
|
|
7
|
+
// Process-wide and dependency-free like delegated-approval-host.mjs. The
|
|
8
|
+
// registry is only this process's cache: what keeps the work owned across a
|
|
9
|
+
// restart is stored apart (a reporting send's watch, any other send's record
|
|
10
|
+
// of its work, both in agent-home-delivery.mjs) and re-noted by
|
|
11
|
+
// agent-task-sessions.mjs recover. A change of which bots own a session's work
|
|
12
|
+
// is announced (session-activity-signal.mjs onSessionBotWorkChange), so the
|
|
13
|
+
// session catalog, which stamps them on each row, re-lists.
|
|
14
|
+
import { emitSessionBotWorkChange } from './session-activity-signal.mjs';
|
|
15
|
+
|
|
16
|
+
const KEY = Symbol.for('mixdog.botOwnedWork.v1');
|
|
17
|
+
|
|
18
|
+
/** Map run id → `{ sessionId, agentId }`. */
|
|
19
|
+
function registry() {
|
|
20
|
+
globalThis[KEY] ??= new Map();
|
|
21
|
+
return globalThis[KEY];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** How many owned works of `agentId` run in `sessionId`. */
|
|
25
|
+
function worksOf(sessionId, agentId) {
|
|
26
|
+
let count = 0;
|
|
27
|
+
for (const work of registry().values()) if (work.sessionId === sessionId && work.agentId === agentId) count += 1;
|
|
28
|
+
return count;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Every session with owned work and the bots owning it (each once, in noting
|
|
32
|
+
* order): Map session → agent ids. */
|
|
33
|
+
export function botWorkBySession() {
|
|
34
|
+
const bySession = new Map();
|
|
35
|
+
for (const { sessionId, agentId } of registry().values()) {
|
|
36
|
+
const agents = bySession.get(sessionId) ?? [];
|
|
37
|
+
if (!agents.includes(agentId)) agents.push(agentId);
|
|
38
|
+
bySession.set(sessionId, agents);
|
|
39
|
+
}
|
|
40
|
+
return bySession;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Agent `agentId`'s submission `runId` to `sessionId` was accepted. */
|
|
44
|
+
export function noteBotWork({ runId, sessionId, agentId }) {
|
|
45
|
+
if (!runId || !sessionId || !agentId || registry().has(String(runId))) return;
|
|
46
|
+
registry().set(String(runId), { sessionId: String(sessionId), agentId: String(agentId) });
|
|
47
|
+
if (worksOf(String(sessionId), String(agentId)) === 1) emitSessionBotWorkChange(sessionId);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Submission `runId` settled or was cancelled: its work is nobody's now. */
|
|
51
|
+
export function endBotWork(runId) {
|
|
52
|
+
const work = registry().get(String(runId));
|
|
53
|
+
if (!work) return;
|
|
54
|
+
registry().delete(String(runId));
|
|
55
|
+
if (worksOf(work.sessionId, work.agentId) === 0) emitSessionBotWorkChange(work.sessionId);
|
|
56
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// What a
|
|
1
|
+
// What a connector server tells the model about its accounts (MCP
|
|
2
2
|
// `instructions`), in one line: the connected accounts, and that each tool
|
|
3
|
-
// takes `account` (
|
|
4
|
-
//
|
|
3
|
+
// takes `account` (left out, the account used last). An account that cannot
|
|
4
|
+
// be used right now is listed with why.
|
|
5
5
|
|
|
6
6
|
/** The MCP instructions naming `accounts` (`{ email, note? }`, `note` a short
|
|
7
7
|
* reason it is unusable such as "needs sign-in"; one without an address is
|
|
@@ -12,12 +12,12 @@ export function connectorAccountsInstructions(accounts) {
|
|
|
12
12
|
const list = named.map(({ email, note }) => (note ? `${email} (${note})` : email)).join(', ');
|
|
13
13
|
return named.length === 1
|
|
14
14
|
? `Accounts: ${list}. \`account\` may be left out.`
|
|
15
|
-
: `Accounts: ${list}.
|
|
15
|
+
: `Accounts: ${list}. \`account\` picks one; left out, a call uses the account used last, and its result names it.`;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/** The account addresses instructions from connectorAccountsInstructions name. */
|
|
19
19
|
export function accountsOfInstructions(instructions) {
|
|
20
|
-
const list = /^Accounts: (.+?)\.
|
|
20
|
+
const list = /^Accounts: (.+?)\. `account`/.exec(String(instructions || ''))?.[1] || '';
|
|
21
21
|
return list
|
|
22
22
|
.split(', ')
|
|
23
23
|
.map((part) => part.replace(/ \(.*\)$/, '').trim())
|
|
@@ -40,8 +40,7 @@ export function connectorToolDefault(connectorId: string, tool: string): 'auto'
|
|
|
40
40
|
export function connectorServerInput(
|
|
41
41
|
entry: ConnectorCatalogEntry,
|
|
42
42
|
name: string,
|
|
43
|
-
key?: string
|
|
44
|
-
options?: { extraAccount?: boolean }
|
|
43
|
+
key?: string
|
|
45
44
|
): Record<string, unknown>;
|
|
46
45
|
export function connectorAppPasswordOnly(entry: ConnectorCatalogEntry | null | undefined): boolean;
|
|
47
46
|
export function connectorUsesGoogleClient(entry: ConnectorCatalogEntry | null | undefined): boolean;
|
|
@@ -52,12 +51,19 @@ export function gmailAppPasswordValue(value: unknown): string;
|
|
|
52
51
|
export function gmailAppPasswordAccount(cfg: unknown): string;
|
|
53
52
|
export function gmailAppPasswordServerInput(name: string, email: string, password: string): Record<string, unknown>;
|
|
54
53
|
|
|
55
|
-
/** One stored account of a
|
|
54
|
+
/** One stored account of a connector server. */
|
|
56
55
|
export interface ConnectorAccountEntry {
|
|
57
56
|
name: string;
|
|
58
|
-
|
|
57
|
+
/** Absent: http. */
|
|
58
|
+
type?: string;
|
|
59
|
+
url?: string;
|
|
59
60
|
headers?: Record<string, string>;
|
|
60
61
|
oauthClientId?: string;
|
|
62
|
+
/** The account's own sign-in key, when another account signs in at its URL. */
|
|
63
|
+
oauthKey?: string;
|
|
64
|
+
command?: string;
|
|
65
|
+
args?: string[];
|
|
66
|
+
env?: Record<string, string>;
|
|
61
67
|
/** `false`: the account is kept but neither connected nor called. */
|
|
62
68
|
enabled?: boolean;
|
|
63
69
|
}
|
|
@@ -76,6 +82,7 @@ export function withConnectorAccountEnabled(
|
|
|
76
82
|
enabled: boolean
|
|
77
83
|
): Record<string, unknown>;
|
|
78
84
|
export function connectorAccountEntry(name: string, cfg: Record<string, unknown>): ConnectorAccountEntry;
|
|
85
|
+
export function connectorServerSettings(cfg: Record<string, unknown>): Record<string, unknown>;
|
|
79
86
|
export function mergeConnectorAccountServers(servers: Record<string, unknown>): {
|
|
80
87
|
servers: Record<string, unknown>;
|
|
81
88
|
merged: { connector: string; name: string; from: string[] }[];
|
|
@@ -184,25 +184,27 @@ export function gmailAppPasswordServerInput(name, email, password) {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
/** Whether several accounts of catalog connector `entry` can be connected at
|
|
187
|
-
* once:
|
|
188
|
-
* sign-in. Other remote servers keep one (their tokens and URL are shared). */
|
|
187
|
+
* once: every catalog connector holds its accounts in one server. */
|
|
189
188
|
export function connectorMultiAccount(entry) {
|
|
190
|
-
return entry?.
|
|
189
|
+
return Boolean(entry?.id) && connectorById(entry.id) === entry;
|
|
191
190
|
}
|
|
192
191
|
|
|
193
|
-
// ── One server per
|
|
194
|
-
// A
|
|
195
|
-
//
|
|
192
|
+
// ── One server per connector ────────────────────────────────────────────────
|
|
193
|
+
// A catalog connector is ONE MCP server (`gmail`, `notion`, `postgres`…)
|
|
194
|
+
// holding its accounts:
|
|
196
195
|
// { type: 'http', url: connectorAccountsUrl(id), connector: id,
|
|
197
|
-
// accounts: [{ name, url
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
// in
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
|
|
196
|
+
// accounts: [{ name, type?, url?, headers?, oauthClientId?, oauthKey?,
|
|
197
|
+
// command?, args?, env? }, …] }
|
|
198
|
+
// Each account keeps the transport it had as a server of its own (its remote
|
|
199
|
+
// URL and sign-in, its key header, its app-password address, or its local
|
|
200
|
+
// command and environment), so its keychain entries are untouched. A remote
|
|
201
|
+
// sign-in is stored under the account's URL, or under its own `oauthKey` when
|
|
202
|
+
// another account of the connector already signs in at that URL. Account names
|
|
203
|
+
// share the server-name space (`notion`, `notion-2`…): a name-based call
|
|
204
|
+
// (remove, sign in, sign out) that names an account acts on that account. The
|
|
205
|
+
// server is answered in-process (connectors/connector-accounts-mcp.mjs): one
|
|
206
|
+
// tool set with an `account` argument.
|
|
207
|
+
const ACCOUNT_FIELDS = ['type', 'url', 'headers', 'oauthClientId', 'oauthKey', 'command', 'args', 'env'];
|
|
206
208
|
|
|
207
209
|
/** The reserved (never resolvable) address of connector `id`'s accounts server. */
|
|
208
210
|
export function connectorAccountsUrl(id) {
|
|
@@ -211,14 +213,16 @@ export function connectorAccountsUrl(id) {
|
|
|
211
213
|
|
|
212
214
|
const isRecord = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
213
215
|
|
|
214
|
-
/** The stored accounts of a
|
|
215
|
-
*
|
|
216
|
+
/** The stored accounts of a connector server, [] for any other config (a
|
|
217
|
+
* legacy one-account server included). */
|
|
216
218
|
export function connectorAccountsOf(cfg) {
|
|
217
|
-
if (!isRecord(cfg) || !Array.isArray(cfg.accounts)) return [];
|
|
218
|
-
if (!connectorMultiAccount(connectorById(connectorOfServer(cfg)))) return [];
|
|
219
|
+
if (!isRecord(cfg) || !Array.isArray(cfg.accounts) || !connectorOfServer(cfg)) return [];
|
|
219
220
|
return cfg.accounts.filter(
|
|
220
221
|
(account) =>
|
|
221
|
-
isRecord(account) &&
|
|
222
|
+
isRecord(account) &&
|
|
223
|
+
typeof account.name === 'string' &&
|
|
224
|
+
account.name &&
|
|
225
|
+
(typeof account.url === 'string' || typeof account.command === 'string')
|
|
222
226
|
);
|
|
223
227
|
}
|
|
224
228
|
|
|
@@ -231,6 +235,12 @@ export function connectorAccountServer(cfg, account) {
|
|
|
231
235
|
return out;
|
|
232
236
|
}
|
|
233
237
|
|
|
238
|
+
/** `cfg` without what is an account's own connection: the settings of the
|
|
239
|
+
* server holding its accounts (timeouts, enabled…). */
|
|
240
|
+
export function connectorServerSettings(cfg) {
|
|
241
|
+
return Object.fromEntries(Object.entries(cfg).filter(([key]) => key !== 'accounts' && !ACCOUNT_FIELDS.includes(key)));
|
|
242
|
+
}
|
|
243
|
+
|
|
234
244
|
/** Every account of `cfg` as its one-account server config. */
|
|
235
245
|
export function connectorAccountServers(cfg) {
|
|
236
246
|
return connectorAccountsOf(cfg).map((account) => connectorAccountServer(cfg, account));
|
|
@@ -264,33 +274,39 @@ export function withConnectorAccountEnabled(cfg, name, enabled) {
|
|
|
264
274
|
return connectorAccountsOf(next).some(connectorAccountEnabled) ? next : { ...next, enabled: false };
|
|
265
275
|
}
|
|
266
276
|
|
|
267
|
-
/** The stored account entry of one-account config `cfg` under `name
|
|
277
|
+
/** The stored account entry of one-account config `cfg` under `name` (an
|
|
278
|
+
* http transport is the default and is not stored). */
|
|
268
279
|
export function connectorAccountEntry(name, cfg) {
|
|
269
280
|
const out = { name };
|
|
270
281
|
for (const field of ACCOUNT_FIELDS) if (cfg?.[field] !== undefined) out[field] = cfg[field];
|
|
282
|
+
if (out.type === 'http') delete out.type;
|
|
271
283
|
return out;
|
|
272
284
|
}
|
|
273
285
|
|
|
274
286
|
const naturalOrder = (a, b) => a.localeCompare(b, 'en', { numeric: true });
|
|
275
287
|
|
|
288
|
+
/** What makes an account the same connection as another: its transport
|
|
289
|
+
* (URL, sign-in key, headers, command and environment), whatever its name. */
|
|
290
|
+
const connectionOf = (cfg, account) => JSON.stringify({ ...connectorAccountServer(cfg, account), name: '' });
|
|
291
|
+
|
|
276
292
|
/**
|
|
277
|
-
* Merges every
|
|
278
|
-
* accounts: legacy one-account servers (`
|
|
279
|
-
* Drive alike) and any second accounts server. The server keeps the name
|
|
280
|
-
* an existing accounts server, else the connector id when a server has it,
|
|
293
|
+
* Merges every catalog connector's servers into one server holding the
|
|
294
|
+
* accounts: legacy one-account servers (`notion`, `notion-2`…, Gmail, Calendar
|
|
295
|
+
* and Drive alike) and any second accounts server. The server keeps the name
|
|
296
|
+
* of an existing accounts server, else the connector id when a server has it,
|
|
281
297
|
* else the first name; its other fields come from that server. Accounts keep
|
|
282
298
|
* their names, transports and order (that server's first); a second entry
|
|
283
|
-
* for the same name
|
|
284
|
-
* off only when every merged one was; otherwise the accounts
|
|
285
|
-
* server that was off stay off (`enabled: false` on the account),
|
|
286
|
-
* account's own `enabled: false` is kept. A server left with no
|
|
287
|
-
* account is off. Idempotent: `merged` is empty when
|
|
288
|
-
*
|
|
299
|
+
* for the same name, app-password address or connection is dropped. The
|
|
300
|
+
* merged server is off only when every merged one was; otherwise the accounts
|
|
301
|
+
* of a merged server that was off stay off (`enabled: false` on the account),
|
|
302
|
+
* and an account's own `enabled: false` is kept. A server left with no
|
|
303
|
+
* switched-on account is off. Idempotent: `merged` is empty when nothing
|
|
304
|
+
* changed. Returns `{ servers, merged: [{ connector, name, from }] }`.
|
|
289
305
|
*/
|
|
290
306
|
export function mergeConnectorAccountServers(input) {
|
|
291
307
|
const servers = isRecord(input) ? { ...input } : {};
|
|
292
308
|
const merged = [];
|
|
293
|
-
for (const entry of CONNECTOR_CATALOG
|
|
309
|
+
for (const entry of CONNECTOR_CATALOG) {
|
|
294
310
|
const names = Object.keys(servers)
|
|
295
311
|
.filter((name) => isRecord(servers[name]) && connectorOfServer(servers[name]) === entry.id)
|
|
296
312
|
.sort(naturalOrder);
|
|
@@ -303,6 +319,7 @@ export function mergeConnectorAccountServers(input) {
|
|
|
303
319
|
const accounts = [];
|
|
304
320
|
const seenNames = new Set();
|
|
305
321
|
const seenAddresses = new Set();
|
|
322
|
+
const seenConnections = new Set();
|
|
306
323
|
for (const name of order) {
|
|
307
324
|
const cfg = servers[name];
|
|
308
325
|
const own = Array.isArray(cfg.accounts) ? connectorAccountsOf(cfg) : [connectorAccountEntry(name, cfg)];
|
|
@@ -311,21 +328,17 @@ export function mergeConnectorAccountServers(input) {
|
|
|
311
328
|
const serverOff = !off && cfg.enabled === false;
|
|
312
329
|
for (const account of own) {
|
|
313
330
|
const address = gmailAppPasswordAccount(connectorAccountServer(cfg, account));
|
|
314
|
-
|
|
331
|
+
const connection = connectionOf(cfg, account);
|
|
332
|
+
if (seenNames.has(account.name) || (address && seenAddresses.has(address)) || seenConnections.has(connection)) {
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
315
335
|
seenNames.add(account.name);
|
|
316
336
|
if (address) seenAddresses.add(address);
|
|
337
|
+
seenConnections.add(connection);
|
|
317
338
|
accounts.push(serverOff ? { ...account, enabled: false } : account);
|
|
318
339
|
}
|
|
319
340
|
}
|
|
320
|
-
const {
|
|
321
|
-
type: _type,
|
|
322
|
-
url: _url,
|
|
323
|
-
headers: _headers,
|
|
324
|
-
oauthClientId: _client,
|
|
325
|
-
accounts: _accounts,
|
|
326
|
-
enabled: _enabled,
|
|
327
|
-
...rest
|
|
328
|
-
} = servers[target];
|
|
341
|
+
const { enabled: _enabled, ...rest } = connectorServerSettings(servers[target]);
|
|
329
342
|
for (const name of order) delete servers[name];
|
|
330
343
|
servers[target] = {
|
|
331
344
|
...rest,
|
|
@@ -352,17 +365,12 @@ export function connectorUsesGoogleClient(entry) {
|
|
|
352
365
|
}
|
|
353
366
|
|
|
354
367
|
/** The MCP server input that connects catalog entry `entry` under `name`
|
|
355
|
-
* (oauth / apiKey); `key` fills an apiKey backend
|
|
356
|
-
*
|
|
357
|
-
|
|
368
|
+
* (oauth / apiKey); `key` fills an apiKey backend. Every account keeps the
|
|
369
|
+
* service's real URL; another account at that URL gets its own sign-in key
|
|
370
|
+
* (resource-mcp-connector-accounts.mjs). */
|
|
371
|
+
export function connectorServerInput(entry, name, key = '') {
|
|
358
372
|
const { backend } = entry;
|
|
359
|
-
if (backend.kind === 'oauth') {
|
|
360
|
-
// Tokens are stored per exact server URL, so another account of the same
|
|
361
|
-
// connector needs its own URL; the in-process Google servers ignore the query.
|
|
362
|
-
const url =
|
|
363
|
-
extraAccount && connectorMultiAccount(entry) ? `${backend.url}?account=${encodeURIComponent(name)}` : backend.url;
|
|
364
|
-
return { name, type: 'http', url, connector: entry.id };
|
|
365
|
-
}
|
|
373
|
+
if (backend.kind === 'oauth') return { name, type: 'http', url: backend.url, connector: entry.id };
|
|
366
374
|
if (backend.kind !== 'apiKey') throw new Error(`${entry.name} is not an MCP connector`);
|
|
367
375
|
const value = String(key || '').trim();
|
|
368
376
|
if (!value) throw new Error(`${entry.name} needs a key`);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
|
|
3
|
+
const require = createRequire(import.meta.url);
|
|
4
|
+
const { getSecret } = require('../../lib/keychain-cjs.cjs');
|
|
5
|
+
|
|
6
|
+
// A custom provider's additional API keys are keychain secrets beside its
|
|
7
|
+
// primary key (`agent.<id>.apiKey`), one per key id of its roster
|
|
8
|
+
// (provider-accounts.mjs `setCustomProviderKeyIds`). Never config values.
|
|
9
|
+
export function customProviderKeyAccount(providerId, keyId) {
|
|
10
|
+
return `agent.${providerId}.apiKey.${keyId}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** The stored value of additional key `keyId`, or '' when it is not readable. */
|
|
14
|
+
export function readCustomProviderKey(providerId, keyId) {
|
|
15
|
+
try {
|
|
16
|
+
return getSecret(customProviderKeyAccount(providerId, keyId)) || '';
|
|
17
|
+
} catch {
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -176,10 +176,7 @@ function removeLegacyKeys(root, dataDir) {
|
|
|
176
176
|
* not set the marker. `stripRouteDefaults` drops shipped presets and
|
|
177
177
|
* maintenance routes (their defaults live with the agent config).
|
|
178
178
|
*/
|
|
179
|
-
function separateDefaultsInConfig(
|
|
180
|
-
root,
|
|
181
|
-
{ stripRouteDefaults = (agent) => agent, dataDir = resolvePluginData() } = {}
|
|
182
|
-
) {
|
|
179
|
+
function separateDefaultsInConfig(root, { stripRouteDefaults = (agent) => agent, dataDir = resolvePluginData() } = {}) {
|
|
183
180
|
const from = Number(root?.[DEFAULTS_VERSION_KEY]) || 0;
|
|
184
181
|
let next = { ...(root || {}) };
|
|
185
182
|
if (from < 1) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Server } from 'node:net';
|
|
2
|
+
|
|
3
|
+
export const FETCH_BLOCKED_PORTS: ReadonlySet<number>;
|
|
4
|
+
|
|
5
|
+
export const FETCH_SAFE_ATTEMPTS: number;
|
|
6
|
+
|
|
7
|
+
export function listenOnFetchSafePort(
|
|
8
|
+
server: Server,
|
|
9
|
+
host: string,
|
|
10
|
+
onListening: () => void,
|
|
11
|
+
options?: {
|
|
12
|
+
attempts?: number;
|
|
13
|
+
isCurrent?: () => boolean;
|
|
14
|
+
portOf?: (server: Server) => number;
|
|
15
|
+
}
|
|
16
|
+
): void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Ports the WHATWG Fetch spec blocks ("bad port"): fetch (undici) and Chromium
|
|
2
|
+
// refuse to connect to them, so a loopback server that a fetch client or a
|
|
3
|
+
// browser context must reach cannot live on one. https://fetch.spec.whatwg.org/#port-blocking
|
|
4
|
+
export const FETCH_BLOCKED_PORTS = new Set([
|
|
5
|
+
0, 1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 69, 77, 79, 87, 95, 101, 102, 103, 104, 109, 110,
|
|
6
|
+
111, 113, 115, 117, 119, 123, 135, 137, 139, 143, 161, 179, 389, 427, 465, 512, 513, 514, 515, 526, 530, 531, 532,
|
|
7
|
+
540, 548, 554, 556, 563, 587, 601, 636, 989, 990, 993, 995, 1719, 1720, 1723, 2049, 3659, 4045, 4190, 5060, 5061,
|
|
8
|
+
6000, 6566, 6665, 6666, 6667, 6668, 6669, 6679, 6697, 10080,
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
/** How many more OS-assigned ports a listener takes after a blocked one. */
|
|
12
|
+
export const FETCH_SAFE_ATTEMPTS = 16;
|
|
13
|
+
|
|
14
|
+
const assignedPort = (server) => {
|
|
15
|
+
const address = server.address();
|
|
16
|
+
return address && typeof address === 'object' ? address.port : 0;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* `server.listen(0, host)` that never settles on a fetch-blocked port: when the
|
|
21
|
+
* OS assigns one, the server is closed and listened again, at most `attempts`
|
|
22
|
+
* more times. `onListening` runs once, on a safe port. When the attempts run
|
|
23
|
+
* out the server is closed and an 'error' is emitted, like a failed listen.
|
|
24
|
+
* `isCurrent` lets a caller that stopped the server meanwhile cancel the retry;
|
|
25
|
+
* `portOf` reads the assigned port.
|
|
26
|
+
*/
|
|
27
|
+
export function listenOnFetchSafePort(
|
|
28
|
+
server,
|
|
29
|
+
host,
|
|
30
|
+
onListening,
|
|
31
|
+
{ attempts = FETCH_SAFE_ATTEMPTS, isCurrent = () => true, portOf = assignedPort } = {}
|
|
32
|
+
) {
|
|
33
|
+
let retries = attempts;
|
|
34
|
+
const attempt = () => {
|
|
35
|
+
server.listen(0, host, () => {
|
|
36
|
+
if (!FETCH_BLOCKED_PORTS.has(portOf(server))) {
|
|
37
|
+
onListening();
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const exhausted = retries <= 0;
|
|
41
|
+
retries -= 1;
|
|
42
|
+
server.close(() => {
|
|
43
|
+
if (!isCurrent()) return;
|
|
44
|
+
if (exhausted) server.emit('error', new Error('no fetch-safe loopback port was assigned'));
|
|
45
|
+
else attempt();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
attempt();
|
|
50
|
+
}
|
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
import { closeSync, openSync, readSync } from 'node:fs';
|
|
2
|
+
import { open } from 'node:fs/promises';
|
|
2
3
|
import { holdsNoData } from './atomic-file.mjs';
|
|
3
4
|
|
|
4
5
|
const PROBE_BYTES = 4096;
|
|
5
6
|
|
|
7
|
+
// Only these errno codes mean the path names no file. Every other failure
|
|
8
|
+
// leaves the file present (a scanner's or the running process's lock, EIO, ...):
|
|
9
|
+
// this is an install check on settings and status paths, so it never throws.
|
|
10
|
+
const ABSENT_CODES = new Set(['ENOENT', 'ENOTDIR']);
|
|
11
|
+
const presentDespite = (err) => !ABSENT_CODES.has(err?.code);
|
|
12
|
+
|
|
6
13
|
/**
|
|
7
14
|
* Install check for a downloaded binary or model: false when `path` is absent
|
|
8
15
|
* or its first bytes hold no data (empty, or NUL-filled by a crash that
|
|
9
16
|
* committed the size but not the bytes). Cheaper than hashing a multi-GB file
|
|
10
17
|
* on every use. A file that exists but cannot be opened right now (locked by a
|
|
11
|
-
* scanner or by the process running it) still counts as present
|
|
18
|
+
* scanner or by the process running it) still counts as present, as does any
|
|
19
|
+
* other failure.
|
|
12
20
|
*/
|
|
13
21
|
export function fileHasData(path) {
|
|
14
22
|
let fd;
|
|
@@ -17,7 +25,7 @@ export function fileHasData(path) {
|
|
|
17
25
|
const head = Buffer.alloc(PROBE_BYTES);
|
|
18
26
|
return !holdsNoData(head.subarray(0, readSync(fd, head, 0, PROBE_BYTES, 0)));
|
|
19
27
|
} catch (err) {
|
|
20
|
-
return err
|
|
28
|
+
return presentDespite(err);
|
|
21
29
|
} finally {
|
|
22
30
|
if (fd !== undefined) {
|
|
23
31
|
try {
|
|
@@ -26,3 +34,19 @@ export function fileHasData(path) {
|
|
|
26
34
|
}
|
|
27
35
|
}
|
|
28
36
|
}
|
|
37
|
+
|
|
38
|
+
/** Same verdict as `fileHasData`, without blocking the event loop on the open
|
|
39
|
+
* (an antivirus scan of a binary can stall a synchronous open for seconds). */
|
|
40
|
+
export async function fileHasDataAsync(path) {
|
|
41
|
+
let handle;
|
|
42
|
+
try {
|
|
43
|
+
handle = await open(path, 'r');
|
|
44
|
+
const head = Buffer.alloc(PROBE_BYTES);
|
|
45
|
+
const { bytesRead } = await handle.read(head, 0, PROBE_BYTES, 0);
|
|
46
|
+
return !holdsNoData(head.subarray(0, bytesRead));
|
|
47
|
+
} catch (err) {
|
|
48
|
+
return presentDespite(err);
|
|
49
|
+
} finally {
|
|
50
|
+
await handle?.close().catch(() => {});
|
|
51
|
+
}
|
|
52
|
+
}
|