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
|
@@ -48,22 +48,31 @@ function runOwnershipQuery(command, args) {
|
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
// Get-NetTCPConnection prints one OwningProcess number per line: no localized words.
|
|
52
|
+
export function listenerPidsInclude(stdout, pid) {
|
|
53
|
+
return stdout.split(/\r?\n/).some((line) => line.trim() === String(pid));
|
|
54
|
+
}
|
|
55
|
+
|
|
51
56
|
export async function whisperListenerOwned(host, port, pid) {
|
|
52
57
|
if (!Number.isInteger(pid) || pid <= 0 || !Number.isInteger(port)) return false;
|
|
53
58
|
try {
|
|
54
59
|
if (process.platform === 'win32') {
|
|
55
60
|
const root = process.env.SystemRoot || 'C:\\Windows';
|
|
56
|
-
|
|
61
|
+
// netstat omits listeners when the TCP table is large; the CIM-backed cmdlet does not.
|
|
62
|
+
// The State filter is skipped (it can report an unknown state for live listeners);
|
|
63
|
+
// a listening row is the one with no remote endpoint (RemotePort 0).
|
|
64
|
+
if (!/^[0-9a-fA-F:.]+$/.test(host)) return false;
|
|
65
|
+
const stdout = await runOwnershipQuery(
|
|
66
|
+
path.join(root, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe'),
|
|
67
|
+
[
|
|
68
|
+
'-NoProfile',
|
|
69
|
+
'-NonInteractive',
|
|
70
|
+
'-Command',
|
|
71
|
+
`(Get-NetTCPConnection -LocalAddress '${host}' -LocalPort ${port} -ErrorAction SilentlyContinue | Where-Object RemotePort -eq 0).OwningProcess`,
|
|
72
|
+
]
|
|
73
|
+
);
|
|
57
74
|
if (stdout === null) return false;
|
|
58
|
-
return stdout
|
|
59
|
-
const fields = line.trim().split(/\s+/);
|
|
60
|
-
return (
|
|
61
|
-
fields[0] === 'TCP' &&
|
|
62
|
-
fields[1] === `${host}:${port}` &&
|
|
63
|
-
fields[3] === 'LISTENING' &&
|
|
64
|
-
fields[4] === String(pid)
|
|
65
|
-
);
|
|
66
|
-
});
|
|
75
|
+
return listenerPidsInclude(stdout, pid);
|
|
67
76
|
}
|
|
68
77
|
if (process.platform === 'linux') {
|
|
69
78
|
const stdout = await runOwnershipQuery('ss', ['-H', '-ltnp', `sport = :${port}`]);
|
|
@@ -80,6 +80,9 @@ const mgr = {
|
|
|
80
80
|
child: null, // ChildProcess handle (non-detached)
|
|
81
81
|
port: null, // selected port; must belong to the current child
|
|
82
82
|
host: null,
|
|
83
|
+
// Ownership proven for this child+port. A live child keeps its bound port, so
|
|
84
|
+
// no other process can take it; cleared whenever the child goes away.
|
|
85
|
+
proof: null, // { child, port }
|
|
83
86
|
runtimeKey: null, // exact contract fingerprint
|
|
84
87
|
contract: null, // { serverCmd, modelPath, threadCount, host }
|
|
85
88
|
startPromise: null, // in-flight ensureReady() start
|
|
@@ -330,6 +333,7 @@ async function teardown(reason) {
|
|
|
330
333
|
}
|
|
331
334
|
}
|
|
332
335
|
mgr.child = null;
|
|
336
|
+
mgr.proof = null;
|
|
333
337
|
mgr.port = null;
|
|
334
338
|
mgr.host = null;
|
|
335
339
|
mgr.runtimeKey = null;
|
|
@@ -357,6 +361,7 @@ function wireChildExit() {
|
|
|
357
361
|
detachChildHandlers();
|
|
358
362
|
clearPidMeta();
|
|
359
363
|
mgr.child = null;
|
|
364
|
+
mgr.proof = null;
|
|
360
365
|
mgr.port = null;
|
|
361
366
|
mgr.runtimeKey = null;
|
|
362
367
|
mgr.contract = null;
|
|
@@ -435,6 +440,7 @@ async function startServer(contract) {
|
|
|
435
440
|
mgr.state === STATE.STARTING
|
|
436
441
|
) {
|
|
437
442
|
mgr.port = port;
|
|
443
|
+
mgr.proof = { child, port };
|
|
438
444
|
mgr.state = STATE.READY;
|
|
439
445
|
return;
|
|
440
446
|
}
|
|
@@ -509,7 +515,10 @@ export async function transcribe(wavPath, { language } = {}) {
|
|
|
509
515
|
try {
|
|
510
516
|
const [data, owned] = await Promise.all([
|
|
511
517
|
fs.promises.readFile(wavPath),
|
|
512
|
-
|
|
518
|
+
// Proven at readiness for this live child; re-prove only without that proof.
|
|
519
|
+
mgr.proof?.child === child && mgr.proof.port === port && child.exitCode === null
|
|
520
|
+
? true
|
|
521
|
+
: whisperListenerOwned(host, port, child.pid),
|
|
513
522
|
]);
|
|
514
523
|
// Re-check after the awaits: a replacement listener (or child) must never
|
|
515
524
|
// receive audio. Nothing awaits between this check and the request.
|
|
@@ -1,20 +1,32 @@
|
|
|
1
|
-
// The one MCP server of a
|
|
2
|
-
// connectorAccountsOf):
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
1
|
+
// The one MCP server of a catalog connector (connector-catalog.mjs
|
|
2
|
+
// connectorAccountsOf): every connected account of Gmail, Notion, Postgres…
|
|
3
|
+
// behind one tool set, answered in-process. Underneath, each account is its
|
|
4
|
+
// own ordinary MCP connection with its own credentials (client-transport.mjs
|
|
5
|
+
// connectorAccountClient), and its tools are the ones its backend lists. This
|
|
6
|
+
// server lists their union (a tool whose schema differs between accounts is
|
|
7
|
+
// left out), each with one more optional argument, `account` (an account's
|
|
8
|
+
// address or name), which never reaches the account.
|
|
9
|
+
//
|
|
10
|
+
// The account a call uses: the one it names; else the one last used in its
|
|
11
|
+
// conversation, else the connector's last-used account (connectorAccountInUse),
|
|
12
|
+
// else the topmost connected account. A named or remembered account that is
|
|
13
|
+
// switched off, removed or needs a sign-in fails the call with the usable
|
|
14
|
+
// accounts listed; no other account ever stands in for it. A conversation's
|
|
15
|
+
// call (`_meta[CONVERSATION_META]`) makes its account the last used, for the
|
|
16
|
+
// conversation and the connector. Every result of a call made on an account,
|
|
17
|
+
// errors included, says which account answered. A name binds a call to one
|
|
18
|
+
// account; an address or label two accounts share binds none.
|
|
19
|
+
// An account that fails stays listed with why (connectorAccountHealth); the
|
|
20
|
+
// server only fails to start when no account can be used.
|
|
21
|
+
import { join } from 'node:path';
|
|
22
|
+
import { readJsonStateSync, updateJsonAtomicSync } from '../shared/atomic-file.mjs';
|
|
12
23
|
import { accountsOfInstructions, connectorAccountsInstructions } from '../shared/connector-accounts.mjs';
|
|
13
24
|
import { connectorById } from '../shared/connector-catalog.mjs';
|
|
14
|
-
import {
|
|
15
|
-
import { googleMcpTools } from './google-mcp.mjs';
|
|
25
|
+
import { resolvePluginData } from '../shared/plugin-paths.mjs';
|
|
16
26
|
|
|
17
27
|
const PROTOCOL_VERSION = '2025-06-18';
|
|
28
|
+
/** The `_meta` key of a tools/call that names the conversation it is made in. */
|
|
29
|
+
export const CONVERSATION_META = 'mixdog/conversation';
|
|
18
30
|
|
|
19
31
|
const json = (status, body) =>
|
|
20
32
|
new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' } });
|
|
@@ -36,66 +48,158 @@ export function forgetConnectorAccountHealth(name) {
|
|
|
36
48
|
health.delete(name);
|
|
37
49
|
}
|
|
38
50
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
// ── The last-used account ───────────────────────────────────────────────────
|
|
52
|
+
// Per connector, in the data dir (every runtime reads the same one); per
|
|
53
|
+
// conversation, in memory (the newest conversations kept).
|
|
54
|
+
const ACCOUNTS_IN_USE_FILE = 'connector-accounts-in-use.json';
|
|
55
|
+
const inUseFile = () => join(resolvePluginData(), ACCOUNTS_IN_USE_FILE);
|
|
56
|
+
const isRecord = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
57
|
+
const validInUse = (value) => {
|
|
58
|
+
if (!isRecord(value)) throw new Error('the accounts in use must be an object');
|
|
59
|
+
return value;
|
|
60
|
+
};
|
|
44
61
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
function connectorTools(connector, accounts, { hidden = false } = {}) {
|
|
51
|
-
const tools =
|
|
52
|
-
connector === 'gmail' && accounts.some((account) => account.appPassword)
|
|
53
|
-
? gmailAppPasswordTools({ hidden })
|
|
54
|
-
: googleMcpTools(SERVICE_OF[connector]);
|
|
55
|
-
return tools.map((tool) => ({
|
|
56
|
-
...tool,
|
|
57
|
-
inputSchema: {
|
|
58
|
-
...tool.inputSchema,
|
|
59
|
-
properties: { ...(tool.inputSchema?.properties || {}), account: ACCOUNT_PROPERTY },
|
|
60
|
-
},
|
|
61
|
-
}));
|
|
62
|
+
/** The name of the account catalog connector `connector` used last, '' when
|
|
63
|
+
* none was recorded. */
|
|
64
|
+
export function connectorAccountInUse(connector) {
|
|
65
|
+
const stored = readJsonStateSync(inUseFile(), { validate: validInUse, fallback: {} });
|
|
66
|
+
return typeof stored[connector] === 'string' ? stored[connector] : '';
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
|
|
69
|
+
function rememberAccountInUse(connector, name) {
|
|
70
|
+
if (connectorAccountInUse(connector) === name) return;
|
|
71
|
+
updateJsonAtomicSync(inUseFile(), (current) => ({ ...(isRecord(current) ? current : {}), [connector]: name }));
|
|
72
|
+
}
|
|
65
73
|
|
|
66
|
-
const
|
|
74
|
+
const CONVERSATIONS_KEPT = 500;
|
|
75
|
+
// conversation → (connector → account name), oldest first.
|
|
76
|
+
const conversations = new Map();
|
|
67
77
|
|
|
68
|
-
/**
|
|
69
|
-
*
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
/** Account `name` of `connector` was removed: wherever it is the last used it
|
|
79
|
+
* stays a removed account, so a call that would default to it keeps failing
|
|
80
|
+
* closed, even once a new account is given the same name. */
|
|
81
|
+
export function retireConnectorAccount(connector, name) {
|
|
82
|
+
const removed = `${name} (removed)`;
|
|
83
|
+
for (const own of conversations.values()) if (own.get(connector) === name) own.set(connector, removed);
|
|
84
|
+
if (connectorAccountInUse(connector) === name) rememberAccountInUse(connector, removed);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function rememberConversationAccount(conversation, connector, name) {
|
|
88
|
+
const own = conversations.get(conversation) || new Map();
|
|
89
|
+
conversations.delete(conversation);
|
|
90
|
+
own.set(connector, name);
|
|
91
|
+
conversations.set(conversation, own);
|
|
92
|
+
if (conversations.size > CONVERSATIONS_KEPT) conversations.delete(conversations.keys().next().value);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const ACCOUNT_PROPERTY = Object.freeze({
|
|
96
|
+
type: 'string',
|
|
97
|
+
description: 'The account to use (its email address or account name). Left out: the account used last.',
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
/** `value` as JSON with every object's keys sorted, so equal schemas compare
|
|
101
|
+
* equal whatever order their keys were written in. */
|
|
102
|
+
const canonical = (value) => {
|
|
103
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`;
|
|
104
|
+
if (isRecord(value)) {
|
|
105
|
+
const keys = Object.keys(value).filter((key) => value[key] !== undefined).sort();
|
|
106
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`;
|
|
107
|
+
}
|
|
108
|
+
return JSON.stringify(value) ?? 'null';
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/** The tools every account lists, once each, with `account` added; a tool
|
|
112
|
+
* two accounts list with different schemas is left out. */
|
|
113
|
+
function unionTools(states) {
|
|
114
|
+
const first = new Map();
|
|
115
|
+
const differs = new Set();
|
|
116
|
+
for (const { tools = [] } of states) {
|
|
117
|
+
for (const tool of tools) {
|
|
118
|
+
const seen = first.get(tool.name);
|
|
119
|
+
if (!seen) first.set(tool.name, tool);
|
|
120
|
+
else if (canonical(seen.inputSchema) !== canonical(tool.inputSchema)) differs.add(tool.name);
|
|
121
|
+
}
|
|
79
122
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
123
|
+
return [...first.values()]
|
|
124
|
+
.filter((tool) => !differs.has(tool.name))
|
|
125
|
+
.map((tool) => ({
|
|
126
|
+
...tool,
|
|
127
|
+
inputSchema: {
|
|
128
|
+
type: 'object',
|
|
129
|
+
...tool.inputSchema,
|
|
130
|
+
properties: { ...(tool.inputSchema?.properties || {}), account: ACCOUNT_PROPERTY },
|
|
131
|
+
},
|
|
132
|
+
}));
|
|
84
133
|
}
|
|
85
134
|
|
|
86
135
|
const noteOf = (state) => (state.authRequired ? 'needs sign-in' : 'unavailable');
|
|
136
|
+
const usable = (account) => health.get(account.name)?.authRequired !== true;
|
|
137
|
+
|
|
138
|
+
/** `result` saying which account answered it (`label`). */
|
|
139
|
+
const withAccountNote = (result, service, label) => ({
|
|
140
|
+
...result,
|
|
141
|
+
content: [
|
|
142
|
+
...(Array.isArray(result?.content) ? result.content : []),
|
|
143
|
+
{ type: 'text', text: `(${service} account: ${label})` },
|
|
144
|
+
],
|
|
145
|
+
});
|
|
87
146
|
|
|
88
147
|
/**
|
|
89
148
|
* A `fetch` answering the accounts server of `connector` in-process. Each of
|
|
90
|
-
* `accounts` is `{ name, email,
|
|
91
|
-
* `
|
|
92
|
-
*
|
|
93
|
-
*
|
|
149
|
+
* `accounts` is `{ name, email, connect(signal), callTool(params, { signal, onprogress }) }`:
|
|
150
|
+
* `connect` resolves `{ instructions, tools }` of that account's own MCP
|
|
151
|
+
* connection, `callTool` sends it one tools/call (reporting its progress to
|
|
152
|
+
* `onprogress`, when given) and resolves the result; both
|
|
153
|
+
* throw when the account cannot be reached (an `authRequired` error when it
|
|
154
|
+
* needs a sign-in). The fetch carries `connectorAccounts`: `{ connector,
|
|
155
|
+
* choose({ tool, wanted, conversation }) }`, `{ name, label }` of the account
|
|
156
|
+
* such a call would use (it throws the refusal otherwise), for binding a call
|
|
157
|
+
* to its account (by its unique name) before it is approved.
|
|
94
158
|
*/
|
|
95
159
|
export function withConnectorAccountsMcp({ connector, accounts }) {
|
|
96
160
|
const service = connectorById(connector)?.name || connector;
|
|
97
|
-
|
|
98
|
-
const
|
|
161
|
+
// account name → the names of the tools it lists (set once it answered).
|
|
162
|
+
const toolsOf = new Map();
|
|
163
|
+
let listed = [];
|
|
164
|
+
const same = (a, b) => Boolean(a) && a.toLowerCase() === b.toLowerCase();
|
|
165
|
+
/** What an account is called to the model: its address, with its name when
|
|
166
|
+
* another account shares that address. */
|
|
167
|
+
const labelOf = (account) => {
|
|
168
|
+
if (!account.email) return account.name;
|
|
169
|
+
const shared = accounts.some((other) => other !== account && same(other.email, account.email));
|
|
170
|
+
return shared ? `${account.email} (${account.name})` : account.email;
|
|
171
|
+
};
|
|
172
|
+
/** The account a call uses (see the header); throws the refusal. */
|
|
173
|
+
const choose = ({ tool, wanted, conversation }) => {
|
|
174
|
+
const refuse = (why) => {
|
|
175
|
+
const list = accounts.filter(usable).map(labelOf).join(', ') || 'none';
|
|
176
|
+
return new Error(`${why} Usable ${service} accounts: ${list}. Pass one as \`account\`.`);
|
|
177
|
+
};
|
|
178
|
+
const named = text(wanted);
|
|
179
|
+
let account;
|
|
180
|
+
if (named) {
|
|
181
|
+
// Its unique name, else the one account its address or label is.
|
|
182
|
+
let matches = accounts.filter((one) => same(one.name, named));
|
|
183
|
+
if (!matches.length) matches = accounts.filter((one) => same(one.email, named) || same(labelOf(one), named));
|
|
184
|
+
if (!matches.length) throw refuse(`No connected ${service} account is "${named}".`);
|
|
185
|
+
if (matches.length > 1) {
|
|
186
|
+
const names = matches.map((one) => one.name).join(', ');
|
|
187
|
+
throw refuse(`"${named}" is more than one ${service} account (${names}); name one.`);
|
|
188
|
+
}
|
|
189
|
+
[account] = matches;
|
|
190
|
+
} else {
|
|
191
|
+
const remembered =
|
|
192
|
+
(conversation && conversations.get(conversation)?.get(connector)) || connectorAccountInUse(connector);
|
|
193
|
+
account = remembered ? accounts.find((one) => one.name === remembered) : accounts[0];
|
|
194
|
+
if (!account) throw refuse(`The ${service} account used last (${remembered}) is not connected now.`);
|
|
195
|
+
}
|
|
196
|
+
if (!usable(account)) throw refuse(`${labelOf(account)} needs a sign-in in Connectors.`);
|
|
197
|
+
const has = toolsOf.get(account.name);
|
|
198
|
+
if (has && !has.has(tool) && listed.some((one) => one.name === tool)) {
|
|
199
|
+
throw refuse(`${labelOf(account)} has no ${tool} tool.`);
|
|
200
|
+
}
|
|
201
|
+
return account;
|
|
202
|
+
};
|
|
99
203
|
const record = (account, state) => {
|
|
100
204
|
if (state.email) account.email = state.email;
|
|
101
205
|
health.set(account.name, {
|
|
@@ -109,13 +213,13 @@ export function withConnectorAccountsMcp({ connector, accounts }) {
|
|
|
109
213
|
record(account, state);
|
|
110
214
|
return state;
|
|
111
215
|
};
|
|
112
|
-
// Each account's own
|
|
216
|
+
// Each account's own connection: whether it can be used, its address and tools.
|
|
113
217
|
const probe = async (account, signal) => {
|
|
114
218
|
try {
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
const [email = ''] = accountsOfInstructions(
|
|
118
|
-
const state = { email: account.email || email };
|
|
219
|
+
const { instructions, tools } = await account.connect(signal);
|
|
220
|
+
toolsOf.set(account.name, new Set(tools.map((tool) => tool.name)));
|
|
221
|
+
const [email = ''] = accountsOfInstructions(instructions);
|
|
222
|
+
const state = { email: account.email || email, instructions, tools };
|
|
119
223
|
record(account, state);
|
|
120
224
|
return state;
|
|
121
225
|
} catch (error) {
|
|
@@ -125,7 +229,9 @@ export function withConnectorAccountsMcp({ connector, accounts }) {
|
|
|
125
229
|
// The calls in flight by JSON-RPC id, so a `notifications/cancelled` for one
|
|
126
230
|
// aborts its downstream request.
|
|
127
231
|
const inflight = new Map();
|
|
128
|
-
|
|
232
|
+
/** One JSON-RPC request's reply; `notify` sends a notification ahead of it
|
|
233
|
+
* (a tools/call's progress). */
|
|
234
|
+
async function rpc(message, transportSignal, notify = null) {
|
|
129
235
|
const { id, method, params } = message || {};
|
|
130
236
|
const reply = (result) => ({ jsonrpc: '2.0', id, result });
|
|
131
237
|
if (method === 'initialize') {
|
|
@@ -138,55 +244,69 @@ export function withConnectorAccountsMcp({ connector, accounts }) {
|
|
|
138
244
|
error: { code: -32001, message: first?.error || `No ${service} account is connected` },
|
|
139
245
|
};
|
|
140
246
|
}
|
|
247
|
+
listed = unionTools(states);
|
|
248
|
+
// What the accounts' own servers tell the model (an in-process Google
|
|
249
|
+
// account's only names its address, which the account list says).
|
|
250
|
+
const own = [
|
|
251
|
+
...new Set(
|
|
252
|
+
states.map((state) => text(state.instructions)).filter((one) => one && !accountsOfInstructions(one).length)
|
|
253
|
+
),
|
|
254
|
+
];
|
|
255
|
+
const accountsLine = connectorAccountsInstructions(
|
|
256
|
+
accounts.map((account, index) => ({
|
|
257
|
+
email: labelOf(account),
|
|
258
|
+
...(states[index].error ? { note: noteOf(states[index]) } : {}),
|
|
259
|
+
}))
|
|
260
|
+
);
|
|
141
261
|
return reply({
|
|
142
262
|
protocolVersion: text(params?.protocolVersion) || PROTOCOL_VERSION,
|
|
143
263
|
capabilities: { tools: { listChanged: false } },
|
|
144
264
|
serverInfo: { name: `mixdog-${connector}`, version: '1.0.0' },
|
|
145
|
-
instructions:
|
|
146
|
-
accounts.map((account, index) => ({
|
|
147
|
-
email: labelOf(account),
|
|
148
|
-
...(states[index].error ? { note: noteOf(states[index]) } : {}),
|
|
149
|
-
}))
|
|
150
|
-
),
|
|
265
|
+
instructions: [accountsLine, ...own].filter(Boolean).join('\n\n') || undefined,
|
|
151
266
|
});
|
|
152
267
|
}
|
|
153
268
|
if (method === 'ping') return reply({});
|
|
154
269
|
if (method === 'tools/list') return reply({ tools: listed });
|
|
155
270
|
if (method === 'tools/call') {
|
|
156
271
|
const name = params?.name;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
const { account: chosen, ...args } = params?.arguments || {};
|
|
272
|
+
const { account: wanted, ...args } = params?.arguments || {};
|
|
273
|
+
const conversation = text(params?._meta?.[CONVERSATION_META]);
|
|
161
274
|
let account;
|
|
162
275
|
try {
|
|
163
|
-
account =
|
|
276
|
+
account = choose({ tool: name, wanted, conversation });
|
|
277
|
+
if (conversation) {
|
|
278
|
+
rememberConversationAccount(conversation, connector, account.name);
|
|
279
|
+
rememberAccountInUse(connector, account.name);
|
|
280
|
+
}
|
|
164
281
|
} catch (error) {
|
|
165
|
-
return reply(errorResult(error
|
|
282
|
+
return reply(errorResult(error?.message || String(error)));
|
|
166
283
|
}
|
|
167
284
|
const cancel = new AbortController();
|
|
168
285
|
const signal = transportSignal ? AbortSignal.any([transportSignal, cancel.signal]) : cancel.signal;
|
|
169
286
|
inflight.set(id, cancel);
|
|
287
|
+
// The account's progress reaches the caller under the caller's own token,
|
|
288
|
+
// so it resets the call's deadline as a direct server's progress does.
|
|
289
|
+
const progressToken = params?._meta?.progressToken;
|
|
290
|
+
const onprogress =
|
|
291
|
+
notify && progressToken !== undefined
|
|
292
|
+
? (progress) => notify({ jsonrpc: '2.0', method: 'notifications/progress', params: { ...progress, progressToken } })
|
|
293
|
+
: undefined;
|
|
170
294
|
try {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
{ signal }
|
|
174
|
-
);
|
|
175
|
-
if (answer?.error) return reply(errorResult(`${labelOf(account)}: ${answer.error.message}`));
|
|
295
|
+
// An unlisted tool (one only the approval gate calls) is the account's to answer.
|
|
296
|
+
const result = await account.callTool({ name, arguments: args }, { signal, onprogress });
|
|
176
297
|
if (health.get(account.name)?.error) record(account, {});
|
|
177
|
-
return reply(
|
|
298
|
+
return reply(withAccountNote(result, service, labelOf(account)));
|
|
178
299
|
} catch (error) {
|
|
179
300
|
// A cancelled call says nothing about the account's health.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
return reply(errorResult(`${labelOf(account)}: ${state.error}`));
|
|
301
|
+
const why = signal.aborted ? 'cancelled' : failure(account, error).error;
|
|
302
|
+
return reply(withAccountNote(errorResult(why), service, labelOf(account)));
|
|
183
303
|
} finally {
|
|
184
304
|
inflight.delete(id);
|
|
185
305
|
}
|
|
186
306
|
}
|
|
187
307
|
return { jsonrpc: '2.0', id, error: { code: -32601, message: `Method not found: ${method}` } };
|
|
188
308
|
}
|
|
189
|
-
|
|
309
|
+
const fetchImpl = async (input, init) => {
|
|
190
310
|
const request = input instanceof Request ? input : null;
|
|
191
311
|
if (String(request?.method || init?.method || 'GET').toUpperCase() !== 'POST') {
|
|
192
312
|
return new Response(null, { status: 405, headers: { allow: 'POST' } });
|
|
@@ -203,28 +323,41 @@ export function withConnectorAccountsMcp({ connector, accounts }) {
|
|
|
203
323
|
for (const one of batch) {
|
|
204
324
|
if (one?.method === 'notifications/cancelled') inflight.get(one.params?.requestId)?.abort();
|
|
205
325
|
}
|
|
326
|
+
// A call asking for progress is answered as an event stream: its progress
|
|
327
|
+
// notifications, then its reply.
|
|
328
|
+
if (!Array.isArray(message) && message?.id !== undefined && message.params?._meta?.progressToken !== undefined) {
|
|
329
|
+
const encoder = new TextEncoder();
|
|
330
|
+
let open = true;
|
|
331
|
+
const stream = new ReadableStream({
|
|
332
|
+
start(controller) {
|
|
333
|
+
const send = (payload) => {
|
|
334
|
+
if (open) controller.enqueue(encoder.encode(`event: message\ndata: ${JSON.stringify(payload)}\n\n`));
|
|
335
|
+
};
|
|
336
|
+
void rpc(message, signal, send).then((answer) => {
|
|
337
|
+
send(answer);
|
|
338
|
+
if (open) controller.close();
|
|
339
|
+
open = false;
|
|
340
|
+
});
|
|
341
|
+
},
|
|
342
|
+
// The caller stopped reading (its call was aborted).
|
|
343
|
+
cancel() {
|
|
344
|
+
open = false;
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
return new Response(stream, { status: 200, headers: { 'content-type': 'text/event-stream' } });
|
|
348
|
+
}
|
|
206
349
|
const replies = (await Promise.all(batch.map((one) => (one?.id === undefined ? null : rpc(one, signal))))).filter(
|
|
207
350
|
Boolean
|
|
208
351
|
);
|
|
209
352
|
if (!replies.length) return new Response(null, { status: 202 });
|
|
210
353
|
return json(200, Array.isArray(message) ? replies : replies[0]);
|
|
211
354
|
};
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const response = await fetchImpl(url, {
|
|
219
|
-
method: 'POST',
|
|
220
|
-
headers: { 'content-type': 'application/json', accept: 'application/json, text/event-stream', ...headers },
|
|
221
|
-
body: JSON.stringify(message),
|
|
222
|
-
signal,
|
|
223
|
-
});
|
|
224
|
-
if (!response.ok) {
|
|
225
|
-
await response.body?.cancel();
|
|
226
|
-
throw new Error(`The account's server answered ${response.status}`);
|
|
227
|
-
}
|
|
228
|
-
return response.json();
|
|
355
|
+
fetchImpl.connectorAccounts = {
|
|
356
|
+
connector,
|
|
357
|
+
choose: (call) => {
|
|
358
|
+
const account = choose(call);
|
|
359
|
+
return { name: account.name, label: labelOf(account) };
|
|
360
|
+
},
|
|
229
361
|
};
|
|
362
|
+
return fetchImpl;
|
|
230
363
|
}
|
|
@@ -723,9 +723,9 @@ const TOOLS = [
|
|
|
723
723
|
];
|
|
724
724
|
|
|
725
725
|
/** The tool definitions (name, description, schema, annotations) the server
|
|
726
|
-
* lists
|
|
727
|
-
export function gmailAppPasswordTools(
|
|
728
|
-
return TOOLS.filter((tool) =>
|
|
726
|
+
* lists: never the ones only the approval gate calls. */
|
|
727
|
+
export function gmailAppPasswordTools() {
|
|
728
|
+
return TOOLS.filter((tool) => !tool.hidden).map(({ run, hidden: _hidden, ...tool }) => tool);
|
|
729
729
|
}
|
|
730
730
|
|
|
731
731
|
// ── The MCP server at the reserved address ──────────────────────────────────
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// One explorer: the agent loop driven directly with one fixed system prompt
|
|
2
|
+
// and the read-only filesystem tools — no session record, rules, skills,
|
|
3
|
+
// profile context or persistence.
|
|
4
|
+
import { agentLoop } from '../agent/orchestrator/session/agent-loop.mjs';
|
|
5
|
+
import { readOnlyFilesystemTools } from '../agent/orchestrator/session/manager/tool-resolution.mjs';
|
|
6
|
+
import { sharedPrefixProviderCacheOpts } from '../agent/orchestrator/agent-runtime/cache-strategy.mjs';
|
|
7
|
+
import { partialHandoffTextFromSession } from '../agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs';
|
|
8
|
+
|
|
9
|
+
// Tool-calling responses before the explorer must answer from what it has.
|
|
10
|
+
export const EXPLORER_MAX_TOOL_ROUNDS = 10;
|
|
11
|
+
|
|
12
|
+
export const EXPLORER_SYSTEM_PROMPT = [
|
|
13
|
+
'You explore the working directory read-only to answer one question. You cannot edit, run commands or delegate.',
|
|
14
|
+
'',
|
|
15
|
+
'Search:',
|
|
16
|
+
`- You have ${EXPLORER_MAX_TOOL_ROUNDS} tool rounds: batch independent searches in one turn (parallel calls or array arguments).`,
|
|
17
|
+
"- Search the question's own nouns, and match the subsystem it names, not a similarly named one.",
|
|
18
|
+
'- Locate first, then read only that window.',
|
|
19
|
+
'- A location a tool returned is final; never reconfirm it.',
|
|
20
|
+
'- Before answering, list the parts the question asks about; each needs its own tool-returned `path:line`.',
|
|
21
|
+
'- Cite implementation, not tests, docs or changelogs, unless asked.',
|
|
22
|
+
'- A part without a `path:line`: search again with different terms, or use `code_graph` on a found symbol (definition, callers, writers). Call it missing only after that search, and name what you searched.',
|
|
23
|
+
'- Stop once every part has its location.',
|
|
24
|
+
'',
|
|
25
|
+
'Answer shape, hard limit 1,500 characters, nothing else:',
|
|
26
|
+
'- One short answer line.',
|
|
27
|
+
'- One evidence line per hop of each part (entry, callee, writer, reader), at most 10 lines: `path:line` from tool output, the exact function or symbol name there, a few words.',
|
|
28
|
+
'- No intro, summary, extra locations, notes about unread code or "Open:" lines.',
|
|
29
|
+
].join('\n');
|
|
30
|
+
|
|
31
|
+
// One list for the process: identical schema bytes for every explorer.
|
|
32
|
+
const EXPLORER_TOOLS = readOnlyFilesystemTools();
|
|
33
|
+
export const EXPLORER_TOOL_NAMES = Object.freeze(EXPLORER_TOOLS.map((tool) => tool.name));
|
|
34
|
+
|
|
35
|
+
/** Exact provider prefix inputs, for cache-stability checks. */
|
|
36
|
+
export function explorerPrefix() {
|
|
37
|
+
return { system: EXPLORER_SYSTEM_PROMPT, tools: EXPLORER_TOOLS };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Answer `query` on `route` with `provider`. Resolves the answer text (on a
|
|
42
|
+
* salvage-flagged abort, the text written so far); rejects on failure.
|
|
43
|
+
*/
|
|
44
|
+
export async function runExplorer({ query, route, provider, cwd = null, signal, ownerSessionId = null }) {
|
|
45
|
+
const tools = EXPLORER_TOOLS.slice();
|
|
46
|
+
const messages = [
|
|
47
|
+
{ role: 'system', content: EXPLORER_SYSTEM_PROMPT },
|
|
48
|
+
{ role: 'user', content: query },
|
|
49
|
+
];
|
|
50
|
+
// In-memory policy only: the schema allowlist is also the execution
|
|
51
|
+
// allowlist (pre-dispatch-deny.mjs), and the owner ids attribute spend in
|
|
52
|
+
// the usage ledger. No session id, so nothing session-keyed is created.
|
|
53
|
+
const policy = {
|
|
54
|
+
provider: route.provider,
|
|
55
|
+
model: route.model,
|
|
56
|
+
cwd,
|
|
57
|
+
tools,
|
|
58
|
+
schemaAllowedTools: EXPLORER_TOOL_NAMES,
|
|
59
|
+
compaction: { auto: false },
|
|
60
|
+
ownerSessionId,
|
|
61
|
+
parentSessionId: ownerSessionId,
|
|
62
|
+
};
|
|
63
|
+
// Text of the request in flight, for salvage when the deadline cuts it.
|
|
64
|
+
let streamed = '';
|
|
65
|
+
try {
|
|
66
|
+
const response = await agentLoop(provider, messages, route.model, tools, null, cwd, {
|
|
67
|
+
signal,
|
|
68
|
+
session: policy,
|
|
69
|
+
...(route.effort ? { effort: route.effort } : {}),
|
|
70
|
+
fast: route.fast === true,
|
|
71
|
+
modelParameters: route.modelParameters || {},
|
|
72
|
+
turnLimits: { maxToolRounds: EXPLORER_MAX_TOOL_ROUNDS },
|
|
73
|
+
...sharedPrefixProviderCacheOpts(route.provider),
|
|
74
|
+
onProviderSendStarted: () => {
|
|
75
|
+
streamed = '';
|
|
76
|
+
},
|
|
77
|
+
onTextDelta: (chunk) => {
|
|
78
|
+
streamed += typeof chunk === 'string' ? chunk : '';
|
|
79
|
+
},
|
|
80
|
+
onTextReset: ({ chars } = {}) => {
|
|
81
|
+
streamed = streamed.slice(0, Math.max(0, streamed.length - (Number(chars) || 0)));
|
|
82
|
+
return true;
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
return String(response?.content || '').trim();
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (signal?.reason?.salvagePartial === true) {
|
|
88
|
+
const partial = [partialHandoffTextFromSession({ messages }, 2), streamed.trim()].filter(Boolean).join('\n\n');
|
|
89
|
+
if (partial) return partial;
|
|
90
|
+
}
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|