mixdog 1.2.6 → 1.3.1
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/LICENSES/simple-icons-CC0-1.0.txt +30 -0
- package/NOTICE.md +71 -9
- package/package.json +4 -1
- package/scripts/check-model-recommendations.mjs +89 -0
- package/scripts/gate-local.mjs +7 -0
- package/scripts/lib/isolated-test-env.mjs +16 -0
- package/src/agents/reviewer/AGENT.md +4 -2
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +10 -0
- package/src/defaults/skills/browser-use/SKILL.md +5 -0
- package/src/defaults/skills/memory-management/SKILL.md +4 -1
- package/src/defaults/skills/setup/references/actions.md +14 -4
- package/src/defaults/skills/setup-guide/SKILL.md +178 -0
- package/src/defaults/skills/setup-guide/model-recommendations.json +333 -0
- package/src/lib/rules-builder.cjs +55 -6
- package/src/output-styles/common.md +13 -8
- package/src/rules/agent/AGENT.md +4 -0
- package/src/rules/agent-mode/AGENT_MODE.md +24 -0
- package/src/rules/agent-mode/MIXY.md +28 -0
- package/src/rules/agent-mode/ROUTINE.md +6 -0
- package/src/rules/lead/LEAD.md +7 -3
- package/src/rules/shared/70-delivery.md +3 -0
- package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +86 -0
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +77 -14
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +241 -14
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +4 -2
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +216 -0
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -4
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +10 -1
- package/src/runtime/agent/orchestrator/mcp/client.mjs +115 -11
- package/src/runtime/agent/orchestrator/mcp/oauth-account.mjs +97 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +49 -0
- package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +59 -9
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +32 -7
- package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +45 -6
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +195 -6
- package/src/runtime/agent/orchestrator/mcp/server-connectors.mjs +17 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +7 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +10 -3
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +7 -8
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/lib/tool-call-wire-ids.mjs +65 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +8 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +17 -12
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -0
- package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +14 -2
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +17 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +96 -1
- package/src/runtime/agent/orchestrator/runtime-core/context-status-shape.mjs +4 -0
- package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +6 -4
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +14 -0
- package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +12 -6
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +32 -5
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +21 -5
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +40 -7
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +52 -20
- package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/agent-home.d.mts +4 -0
- package/src/runtime/agent/orchestrator/session/agent-home.mjs +33 -0
- package/src/runtime/agent/orchestrator/session/agent-mode.mjs +62 -0
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +31 -0
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +241 -0
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +166 -0
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +650 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +192 -0
- package/src/runtime/agent/orchestrator/session/human-gate/held-message.mjs +95 -0
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +221 -0
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +195 -0
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +167 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +108 -6
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +74 -15
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +9 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +5 -4
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +13 -3
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +49 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +18 -8
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-lifecycle-epoch.mjs +0 -8
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +44 -16
- package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +50 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +22 -5
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +39 -4
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +33 -24
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +58 -8
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/mixy-name.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +32 -5
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +86 -0
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +329 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/store-record-strict-scan.mjs +28 -9
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +16 -2
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -12
- package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +46 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +0 -4
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +58 -10
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +92 -15
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +12 -0
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +113 -5
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +4 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +25 -1
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +6 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +3 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +75 -57
- package/src/runtime/attachments/store.mjs +6 -0
- package/src/runtime/browser-bridge/client.mjs +24 -0
- package/src/runtime/channels/lib/scheduler.mjs +40 -12
- package/src/runtime/channels/lib/schedules-db.mjs +71 -12
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +36 -16
- package/src/runtime/channels/lib/webhook.mjs +0 -1
- package/src/runtime/channels/lib/webhooks-db.mjs +100 -10
- package/src/runtime/channels/lib/worker-main.mjs +17 -20
- package/src/runtime/computer-bridge/client.mjs +28 -1
- package/src/runtime/connectors/gmail-app-password.mjs +690 -0
- package/src/runtime/connectors/google-mcp.mjs +446 -0
- package/src/runtime/effort-judge/judge-client.mjs +1 -1
- package/src/runtime/effort-judge/model-manifest.json +7 -7
- package/src/runtime/memory/lib/core-memory-access.mjs +69 -0
- package/src/runtime/memory/lib/core-memory-file.mjs +16 -8
- package/src/runtime/memory/lib/core-memory-index.mjs +15 -9
- package/src/runtime/memory/lib/core-memory-management.mjs +10 -4
- package/src/runtime/memory/lib/core-memory-store.mjs +53 -30
- package/src/runtime/memory/lib/core-memory-uniqueness.mjs +10 -6
- package/src/runtime/memory/lib/http-router/session-routes.mjs +3 -2
- package/src/runtime/memory/lib/memory-action-handlers/core-actions.mjs +44 -14
- package/src/runtime/memory/lib/memory-schema/core-and-meta.mjs +11 -0
- package/src/runtime/memory/lib/memory.mjs +7 -1
- package/src/runtime/memory/lib/query-core-recall.mjs +9 -1
- package/src/runtime/memory/lib/query-hybrid-search.mjs +2 -0
- package/src/runtime/memory/lib/tool-call-handler.mjs +2 -0
- package/src/runtime/memory/tool-defs.mjs +5 -0
- package/src/runtime/shared/agent-avatar-sprites.mjs +445 -0
- package/src/runtime/shared/agent-avatar.d.mts +64 -0
- package/src/runtime/shared/agent-avatar.mjs +320 -0
- package/src/runtime/shared/agent-kind.mjs +152 -0
- package/src/runtime/shared/agent-route-config.d.mts +4 -0
- package/src/runtime/shared/agent-route-config.mjs +30 -0
- package/src/runtime/shared/agent-store.mjs +132 -0
- package/src/runtime/shared/atomic-file.mjs +14 -1
- package/src/runtime/shared/bot-split-migration.mjs +457 -0
- package/src/runtime/shared/config-patch.mjs +20 -1
- package/src/runtime/shared/config.mjs +8 -1
- package/src/runtime/shared/connector-accounts.mjs +15 -0
- package/src/runtime/shared/connector-catalog.d.mts +51 -0
- package/src/runtime/shared/connector-catalog.mjs +228 -0
- package/src/runtime/shared/connector-triggers.d.mts +3 -0
- package/src/runtime/shared/connector-triggers.mjs +45 -0
- package/src/runtime/shared/err-text.mjs +3 -1
- package/src/runtime/shared/error-presentation.mjs +91 -6
- package/src/runtime/shared/google-oauth-client.mjs +24 -0
- package/src/runtime/shared/handoff-artifacts.d.mts +20 -0
- package/src/runtime/shared/handoff-artifacts.mjs +61 -0
- package/src/runtime/shared/held-request-host.d.mts +12 -0
- package/src/runtime/shared/held-request-host.mjs +71 -0
- package/src/runtime/shared/llm/provider-model-identities.mjs +6 -1
- package/src/runtime/shared/llm/usage-accounting.mjs +7 -1
- package/src/runtime/shared/model-route-fallback.mjs +79 -7
- package/src/runtime/shared/plain-preview.d.mts +1 -0
- package/src/runtime/shared/plain-preview.mjs +33 -0
- package/src/runtime/shared/routine-limits.d.mts +11 -0
- package/src/runtime/shared/routine-limits.mjs +21 -0
- package/src/runtime/shared/routine-trigger.d.mts +10 -0
- package/src/runtime/shared/routine-trigger.mjs +44 -0
- package/src/runtime/shared/schedule-model-ref.mjs +9 -10
- package/src/runtime/shared/sensitive-path-rules.d.mts +15 -0
- package/src/runtime/shared/sensitive-path-rules.mjs +256 -0
- package/src/runtime/shared/slow-span.mjs +4 -12
- package/src/runtime/shared/statusline/statusline-segments.mjs +13 -1
- package/src/runtime/shared/transcript-metadata.mjs +10 -0
- package/src/runtime/shared/turn-changes.mjs +0 -8
- package/src/runtime/shared/usage-readout-totals.mjs +58 -0
- package/src/runtime/shared/user-data-guard.mjs +119 -38
- package/src/session-runtime/agent-home-delivery.mjs +826 -0
- package/src/session-runtime/agent-profile-changes.mjs +21 -0
- package/src/session-runtime/agent-room-delivery.mjs +133 -0
- package/src/session-runtime/agent-task-sessions.mjs +460 -0
- package/src/session-runtime/boot/apis.mjs +63 -18
- package/src/session-runtime/boot/facade.mjs +5 -8
- package/src/session-runtime/boot/infrastructure.mjs +28 -17
- package/src/session-runtime/boot/providers.mjs +14 -4
- package/src/session-runtime/boot/tools.mjs +3 -0
- package/src/session-runtime/channel-config-api.mjs +53 -5
- package/src/session-runtime/config-lifecycle.mjs +43 -2
- package/src/session-runtime/cwd-plugins/core-memory-context.mjs +36 -20
- package/src/session-runtime/cwd-plugins/plugin-status.mjs +6 -1
- package/src/session-runtime/handoff-attachments.mjs +593 -0
- package/src/session-runtime/held-requests.mjs +228 -0
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +121 -0
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +5 -1
- package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +31 -2
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +56 -3
- package/src/session-runtime/internal-tool-executor.mjs +2 -0
- package/src/session-runtime/lifecycle/session-catalog.mjs +9 -0
- package/src/session-runtime/lifecycle/session-switch/resume.mjs +4 -0
- package/src/session-runtime/lifecycle/shared.mjs +6 -0
- package/src/session-runtime/mcp-glue/server-catalog.mjs +8 -0
- package/src/session-runtime/mcp-glue/server-input.mjs +3 -0
- package/src/session-runtime/mcp-oauth-login.mjs +112 -15
- package/src/session-runtime/model-route/route-persist.mjs +2 -2
- package/src/session-runtime/model-route/route-tuning.mjs +7 -1
- package/src/session-runtime/model-route/set-route.mjs +35 -4
- package/src/session-runtime/native-web-search.mjs +2 -2
- package/src/session-runtime/new-session-config.mjs +6 -1
- package/src/session-runtime/owned-automation-run.mjs +229 -0
- package/src/session-runtime/resource-api.mjs +5 -0
- package/src/session-runtime/resource-mcp-api.mjs +107 -13
- package/src/session-runtime/resource-skills-api.mjs +12 -0
- package/src/session-runtime/room-member-gate.mjs +48 -0
- package/src/session-runtime/route-resolution.mjs +5 -11
- package/src/session-runtime/routine-home-turn.mjs +156 -0
- package/src/session-runtime/routine-owner.mjs +48 -0
- package/src/session-runtime/schedule-session-run.mjs +30 -64
- package/src/session-runtime/services/agent-tool/helpers.mjs +30 -1
- package/src/session-runtime/services/agent-tool/send-flow.mjs +11 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +19 -4
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +2 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +5 -8
- package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
- package/src/session-runtime/services/channel-admin.mjs +93 -24
- package/src/session-runtime/services/memory-access.mjs +37 -0
- package/src/session-runtime/services/provider-admin.mjs +74 -39
- package/src/session-runtime/services/schedule-access.mjs +91 -0
- package/src/session-runtime/session-create/session-options.mjs +34 -7
- package/src/session-runtime/session-hooks.mjs +16 -8
- package/src/session-runtime/setup-tool/executor.mjs +79 -6
- package/src/session-runtime/setup-tool/extended-actions.mjs +90 -20
- package/src/session-runtime/setup-tool/settings-contract.mjs +14 -6
- package/src/session-runtime/setup-tool/tool-defs.mjs +8 -0
- package/src/session-runtime/skills-api.mjs +16 -1
- package/src/session-runtime/tool-policy-refresh.mjs +249 -29
- package/src/session-runtime/turn/session-ops.mjs +8 -1
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +10 -2
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +14 -0
- package/src/session-runtime/webhook-session-run.mjs +49 -67
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +239 -0
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +650 -25
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +67 -7
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +11 -4
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +11 -1
- package/src/session-runtime/workflow-agents-api.mjs +4 -1
- package/src/standalone/daemon-agent-control.mjs +1 -8
- package/src/standalone/daemon-session-control.mjs +21 -4
- package/src/standalone/daemon.mjs +31 -5
- package/src/standalone/session-protocol.mjs +27 -0
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +46 -3
- package/src/standalone/session-service/agent-tree.mjs +4 -3
- package/src/standalone/session-service/session-calls/session-turns.mjs +28 -0
- package/src/standalone/session-service.mjs +19 -0
- package/src/tui/app/slash-commands.mjs +20 -0
- package/src/tui/dist/index.mjs +234 -95
- package/src/tui/session/context-state.mjs +6 -1
- package/src/tui/session/notice-surface.mjs +34 -4
- package/src/tui/session/queue-helpers.mjs +2 -1
- package/src/tui/session/session-api/extensions.mjs +4 -0
- package/src/tui/session/session-api/intake/submission.mjs +5 -0
- package/src/tui/session/session-api/integrations.mjs +8 -0
- package/src/tui/session/session-api/lifecycle/session-reset.mjs +13 -3
- package/src/tui/session/session-api/lifecycle.mjs +17 -1
- package/src/tui/session/session-api/routes.mjs +61 -5
- package/src/tui/session/session-flow/drain.mjs +12 -3
- package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -1
- package/src/tui/session/session-flow/queue/take-entries.mjs +3 -2
- package/src/tui/session/session-flow/steering.mjs +58 -24
- package/src/tui/session/tool-approval.mjs +26 -5
- package/src/tui/session/transcript-intake.mjs +8 -2
- package/src/tui/session/transcript-spill.mjs +5 -1
- package/src/tui/session/tui-steering-persist.mjs +65 -42
- package/src/tui/session/turn-live-call.mjs +64 -0
- package/src/tui/session/turn.mjs +34 -8
- package/src/session-runtime/automation-agents.mjs +0 -94
- package/src/session-runtime/automation-workflow.mjs +0 -59
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// The built-in connector catalog: services a user connects with one click
|
|
2
|
+
// from Desktop (Extensions → 커넥터) or from a chat card the setup tool's
|
|
3
|
+
// `connect_connector` action surfaces. Each entry declares its backend
|
|
4
|
+
// explicitly:
|
|
5
|
+
// oauth — a remote MCP server that signs in with MCP OAuth;
|
|
6
|
+
// apiKey — an MCP server that takes one key (an HTTP header or a stdio
|
|
7
|
+
// environment variable).
|
|
8
|
+
// GitHub is not a connector: it is the signed-in GitHub CLI (github tool).
|
|
9
|
+
// A connected catalog service is an ordinary user MCP server whose config
|
|
10
|
+
// carries `connector: <id>`. An existing custom server is never adopted into
|
|
11
|
+
// the catalog row.
|
|
12
|
+
|
|
13
|
+
import { connectorTrigger } from './connector-triggers.mjs';
|
|
14
|
+
|
|
15
|
+
// `oauthClient` names a pre-registered client the runtime supplies
|
|
16
|
+
// (mcp/oauth-client.mjs: 'google') in place of dynamic client registration;
|
|
17
|
+
// `scopes` are then the ones requested.
|
|
18
|
+
const oauth = (url, client = {}) => Object.freeze({ kind: 'oauth', url, ...client });
|
|
19
|
+
|
|
20
|
+
const GOOGLE_SCOPE = 'https://www.googleapis.com/auth';
|
|
21
|
+
// The Google access each built-in connector asks for: the one place to widen
|
|
22
|
+
// it (e.g. gmail.readonly). openid + email name the signed-in account.
|
|
23
|
+
export const GOOGLE_SCOPES = Object.freeze({
|
|
24
|
+
gmail: Object.freeze(['openid', 'email', `${GOOGLE_SCOPE}/gmail.send`]),
|
|
25
|
+
calendar: Object.freeze(['openid', 'email', `${GOOGLE_SCOPE}/calendar.events`]),
|
|
26
|
+
drive: Object.freeze(['openid', 'email', `${GOOGLE_SCOPE}/drive.file`]),
|
|
27
|
+
});
|
|
28
|
+
// Gmail / Calendar / Drive are Mixdog's built-in Google connectors
|
|
29
|
+
// (runtime/connectors/google-mcp.mjs), answered in-process at a reserved
|
|
30
|
+
// address and signed in with Mixdog's Google client. `askTools` ask before
|
|
31
|
+
// they run unless the agent set them.
|
|
32
|
+
const google = (service, scopes, askTools) =>
|
|
33
|
+
oauth(`https://${service}.mixdog.invalid/mcp`, { oauthClient: 'google', scopes, askTools });
|
|
34
|
+
// Gmail can also connect with a Google app password instead of the sign-in:
|
|
35
|
+
// IMAP and SMTP over TLS (runtime/connectors/gmail-app-password.mjs), answered
|
|
36
|
+
// in-process at its own reserved address. Its server is still the Gmail
|
|
37
|
+
// connector (`connector: 'gmail'`, so Gmail's askTools apply to its tools of
|
|
38
|
+
// the same names); the account rides in a header, the app password lives only
|
|
39
|
+
// in the keychain.
|
|
40
|
+
export const GMAIL_APP_PASSWORD = Object.freeze({
|
|
41
|
+
url: 'https://gmail-imap.mixdog.invalid/mcp',
|
|
42
|
+
accountHeader: 'X-Mixdog-Gmail-Account',
|
|
43
|
+
helpUrl: 'https://myaccount.google.com/apppasswords',
|
|
44
|
+
});
|
|
45
|
+
const apiKey = (spec) => Object.freeze({ kind: 'apiKey', ...spec });
|
|
46
|
+
|
|
47
|
+
export const CONNECTOR_CATALOG = Object.freeze(
|
|
48
|
+
[
|
|
49
|
+
{ id: 'airtable', name: 'Airtable', backend: oauth('https://mcp.airtable.com/mcp') },
|
|
50
|
+
{ id: 'confluence', name: 'Confluence', backend: oauth('https://mcp.atlassian.com/v1/mcp/authv2') },
|
|
51
|
+
{ id: 'dropbox', name: 'Dropbox', backend: oauth('https://mcp.dropbox.com/mcp') },
|
|
52
|
+
{
|
|
53
|
+
id: 'gmail',
|
|
54
|
+
name: 'Gmail',
|
|
55
|
+
backend: google('gmail', GOOGLE_SCOPES.gmail, ['send_email']),
|
|
56
|
+
},
|
|
57
|
+
// Calendar and Drive are offered only to a user who entered their own
|
|
58
|
+
// Google OAuth client (Connectors → add custom → 내 Google 키): `ownKey`.
|
|
59
|
+
// Existing connections keep working; Mixy and the chat cards never offer them.
|
|
60
|
+
{
|
|
61
|
+
id: 'google-calendar',
|
|
62
|
+
name: 'Google Calendar',
|
|
63
|
+
ownKey: true,
|
|
64
|
+
backend: google('calendar', GOOGLE_SCOPES.calendar, ['delete_event']),
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'google-drive',
|
|
68
|
+
name: 'Google Drive',
|
|
69
|
+
ownKey: true,
|
|
70
|
+
backend: google('drive', GOOGLE_SCOPES.drive, []),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'hugging-face',
|
|
74
|
+
name: 'Hugging Face',
|
|
75
|
+
backend: apiKey({
|
|
76
|
+
url: 'https://huggingface.co/mcp',
|
|
77
|
+
header: 'Authorization',
|
|
78
|
+
prefix: 'Bearer ',
|
|
79
|
+
keyUrl: 'https://huggingface.co/settings/tokens',
|
|
80
|
+
}),
|
|
81
|
+
},
|
|
82
|
+
{ id: 'jira', name: 'Jira', backend: oauth('https://mcp.atlassian.com/v1/mcp/authv2') },
|
|
83
|
+
{ id: 'linear', name: 'Linear', backend: oauth('https://mcp.linear.app/mcp') },
|
|
84
|
+
{ id: 'notion', name: 'Notion', backend: oauth('https://mcp.notion.com/mcp') },
|
|
85
|
+
{
|
|
86
|
+
id: 'postgres',
|
|
87
|
+
name: 'Postgres',
|
|
88
|
+
backend: apiKey({
|
|
89
|
+
command: 'npx',
|
|
90
|
+
args: ['-y', '@microsoft/postgres-mcp@0.2.0', 'run'],
|
|
91
|
+
env: 'POSTGRES_MCP_CONNECTION_STRING',
|
|
92
|
+
keyUrl: 'https://github.com/microsoft/postgres-mcp/blob/main/USAGE.md',
|
|
93
|
+
placeholder: 'postgresql://user:password@host:5432/db',
|
|
94
|
+
}),
|
|
95
|
+
},
|
|
96
|
+
{ id: 'sentry', name: 'Sentry', backend: oauth('https://mcp.sentry.dev/mcp') },
|
|
97
|
+
{
|
|
98
|
+
id: 'stripe',
|
|
99
|
+
name: 'Stripe',
|
|
100
|
+
backend: apiKey({
|
|
101
|
+
url: 'https://mcp.stripe.com',
|
|
102
|
+
header: 'Authorization',
|
|
103
|
+
prefix: 'Bearer ',
|
|
104
|
+
keyUrl: 'https://dashboard.stripe.com/apikeys',
|
|
105
|
+
placeholder: 'rk_live_…',
|
|
106
|
+
}),
|
|
107
|
+
},
|
|
108
|
+
{ id: 'supabase', name: 'Supabase', backend: oauth('https://mcp.supabase.com/mcp') },
|
|
109
|
+
{ id: 'trello', name: 'Trello', backend: oauth('https://mcp.trello.com/v1') },
|
|
110
|
+
{ id: 'vercel', name: 'Vercel', backend: oauth('https://mcp.vercel.com') },
|
|
111
|
+
// The ids are the deferred-tool manifest's curated keys: a server named by
|
|
112
|
+
// its id gets that one-line trigger (connector-triggers.mjs).
|
|
113
|
+
].map((entry) => Object.freeze({ ...entry, trigger: connectorTrigger(entry.id) }))
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
/** The ids offered to everyone (the setup tool's `connect_connector`): the
|
|
117
|
+
* catalog minus the own-key-only Google services. */
|
|
118
|
+
export const CONNECTOR_IDS = Object.freeze(CONNECTOR_CATALOG.filter((entry) => !entry.ownKey).map((entry) => entry.id));
|
|
119
|
+
|
|
120
|
+
/** The catalog entry for `id`, or null. */
|
|
121
|
+
export function connectorById(id) {
|
|
122
|
+
return CONNECTOR_CATALOG.find((entry) => entry.id === id) || null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const urlKey = (url) =>
|
|
126
|
+
String(url || '')
|
|
127
|
+
.trim()
|
|
128
|
+
.replace(/\/+$/, '')
|
|
129
|
+
.toLowerCase();
|
|
130
|
+
|
|
131
|
+
/** The catalog id a configured MCP server connects: its `connector` marker,
|
|
132
|
+
* '' for none (or a marker the catalog no longer knows). */
|
|
133
|
+
export function connectorOfServer(cfg) {
|
|
134
|
+
const marker = typeof cfg?.connector === 'string' ? cfg.connector : '';
|
|
135
|
+
return connectorById(marker) ? marker : '';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** The permission a catalog connector's tool has when nothing set its own:
|
|
139
|
+
* `needs_approval` for the high-risk tools it names, else `auto`. */
|
|
140
|
+
export function connectorToolDefault(connectorId, tool) {
|
|
141
|
+
const backend = connectorById(connectorId)?.backend;
|
|
142
|
+
return backend?.askTools?.includes(tool) ? 'needs_approval' : 'auto';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const EMAIL = /^[^\s@<>(),;:"]+@[^\s@<>(),;:"]+\.[^\s@<>(),;:".]+$/;
|
|
146
|
+
|
|
147
|
+
/** A Gmail address as typed (trimmed, lowercased), or '' when it is none. */
|
|
148
|
+
export function gmailAddress(value) {
|
|
149
|
+
const address = String(value ?? '')
|
|
150
|
+
.trim()
|
|
151
|
+
.toLowerCase();
|
|
152
|
+
return EMAIL.test(address) ? address : '';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** A Google app password as typed, its spaces removed; '' unless it then
|
|
156
|
+
* has the 16 characters Google issues. */
|
|
157
|
+
export function gmailAppPasswordValue(value) {
|
|
158
|
+
const password = String(value ?? '').replace(/\s+/g, '');
|
|
159
|
+
return password.length === 16 ? password : '';
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** The account of a Gmail app-password server config, '' for any other. */
|
|
163
|
+
export function gmailAppPasswordAccount(cfg) {
|
|
164
|
+
if (urlKey(cfg?.url) !== urlKey(GMAIL_APP_PASSWORD.url)) return '';
|
|
165
|
+
const headers = cfg?.headers && typeof cfg.headers === 'object' ? cfg.headers : {};
|
|
166
|
+
const header = GMAIL_APP_PASSWORD.accountHeader.toLowerCase();
|
|
167
|
+
const key = Object.keys(headers).find((name) => name.toLowerCase() === header);
|
|
168
|
+
return key ? gmailAddress(headers[key]) : '';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** The MCP server input that connects Gmail under `name` as `email` with an
|
|
172
|
+
* app password. `appPassword` only travels to the host, which checks it
|
|
173
|
+
* with Gmail and keeps it in the keychain; it never reaches the config. */
|
|
174
|
+
export function gmailAppPasswordServerInput(name, email, password) {
|
|
175
|
+
const address = gmailAddress(email);
|
|
176
|
+
if (!address) throw new Error('GMAIL_APP_PASSWORD_ADDRESS: Enter a Gmail address.');
|
|
177
|
+
const appPassword = gmailAppPasswordValue(password);
|
|
178
|
+
if (!appPassword) throw new Error('GMAIL_APP_PASSWORD_FORMAT: An app password has 16 characters.');
|
|
179
|
+
return {
|
|
180
|
+
name,
|
|
181
|
+
type: 'http',
|
|
182
|
+
url: GMAIL_APP_PASSWORD.url,
|
|
183
|
+
headers: { [GMAIL_APP_PASSWORD.accountHeader]: address },
|
|
184
|
+
connector: 'gmail',
|
|
185
|
+
appPassword,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Whether several accounts of catalog connector `entry` can be connected at
|
|
190
|
+
* once: Gmail (also by app password), Calendar and Drive with the Google
|
|
191
|
+
* sign-in. Other remote servers keep one (their tokens and URL are shared). */
|
|
192
|
+
export function connectorMultiAccount(entry) {
|
|
193
|
+
return entry?.backend?.kind === 'oauth' && entry.backend.oauthClient === 'google';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** The MCP server input that connects catalog entry `entry` under `name`
|
|
197
|
+
* (oauth / apiKey); `key` fills an apiKey backend; `extraAccount`
|
|
198
|
+
* makes it one more account of a multi-account connector. */
|
|
199
|
+
export function connectorServerInput(entry, name, key = '', { extraAccount = false } = {}) {
|
|
200
|
+
const { backend } = entry;
|
|
201
|
+
if (backend.kind === 'oauth') {
|
|
202
|
+
// Tokens are stored per exact server URL, so another account of the same
|
|
203
|
+
// connector needs its own URL; the in-process Google servers ignore the query.
|
|
204
|
+
const url =
|
|
205
|
+
extraAccount && connectorMultiAccount(entry) ? `${backend.url}?account=${encodeURIComponent(name)}` : backend.url;
|
|
206
|
+
return { name, type: 'http', url, connector: entry.id };
|
|
207
|
+
}
|
|
208
|
+
if (backend.kind !== 'apiKey') throw new Error(`${entry.name} is not an MCP connector`);
|
|
209
|
+
const value = String(key || '').trim();
|
|
210
|
+
if (!value) throw new Error(`${entry.name} needs a key`);
|
|
211
|
+
if (backend.url) {
|
|
212
|
+
return {
|
|
213
|
+
name,
|
|
214
|
+
type: 'http',
|
|
215
|
+
url: backend.url,
|
|
216
|
+
headers: { [backend.header]: `${backend.prefix || ''}${value}` },
|
|
217
|
+
connector: entry.id,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
name,
|
|
222
|
+
type: 'stdio',
|
|
223
|
+
command: backend.command,
|
|
224
|
+
args: [...backend.args],
|
|
225
|
+
env: { [backend.env]: value },
|
|
226
|
+
connector: entry.id,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Curated selection triggers for catalog connectors (MCP servers). A catalog
|
|
2
|
+
// entry carries its line as `trigger`; the deferred-tool manifest lists each
|
|
3
|
+
// connected server once as `- mcp__<server id>: <trigger>`, so one line must
|
|
4
|
+
// say what the connector is for (plus a close negative where useful), in
|
|
5
|
+
// English, at most CONNECTOR_TRIGGER_MAX_CHARS characters.
|
|
6
|
+
export const CONNECTOR_TRIGGER_MAX_CHARS = 100;
|
|
7
|
+
|
|
8
|
+
export const CONNECTOR_TRIGGERS = Object.freeze({
|
|
9
|
+
// Not a catalog connector (Mixdog uses the signed-in GitHub CLI), but a
|
|
10
|
+
// user-added `github` MCP server still gets this line.
|
|
11
|
+
github: 'GitHub repos, issues, pull requests, Actions and code search; not local Git history or diffs.',
|
|
12
|
+
notion: 'Notion pages, databases and workspace search; create or update team docs and notes.',
|
|
13
|
+
linear: 'Linear issues, projects, cycles and comments; create, triage or update tickets.',
|
|
14
|
+
sentry: 'Sentry issues, events, stack traces and releases; triage production errors.',
|
|
15
|
+
gmail: 'Gmail: send email; with an app password also search, read, archive, label, star, trash.',
|
|
16
|
+
'google-calendar': 'Google Calendar events on the primary calendar; list, search, create, update or delete.',
|
|
17
|
+
'google-drive': 'Google Drive files Mixdog created or was given; search, read or create; not local files.',
|
|
18
|
+
jira: 'Jira issues, projects, sprints and comments; JQL search, create or transition tickets.',
|
|
19
|
+
confluence: 'Confluence spaces and pages; search, read or edit the team wiki; not Jira tickets.',
|
|
20
|
+
dropbox: 'Dropbox files and folders; search, read, upload or share cloud files; not local files.',
|
|
21
|
+
trello: 'Trello boards, lists and cards; create, move or update cards.',
|
|
22
|
+
airtable: 'Airtable bases, tables and records; query, create or update rows.',
|
|
23
|
+
stripe: 'Stripe customers, payments, invoices, subscriptions and refunds; billing data and actions.',
|
|
24
|
+
vercel: 'Vercel projects, deployments, domains and logs; deploy status and build errors.',
|
|
25
|
+
supabase: 'Supabase projects: Postgres tables and SQL, auth, storage and edge functions.',
|
|
26
|
+
postgres: 'PostgreSQL schemas, tables and SQL queries on a connected database; not local files.',
|
|
27
|
+
'hugging-face': 'Hugging Face Hub models, datasets, Spaces and papers; not local GGUF model setup.',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Server ids differ only in separators and case (google_calendar, huggingface).
|
|
31
|
+
const triggerKey = (id) =>
|
|
32
|
+
String(id || '')
|
|
33
|
+
.toLowerCase()
|
|
34
|
+
.replace(/[^a-z0-9]/g, '');
|
|
35
|
+
const TRIGGERS_BY_KEY = new Map(Object.entries(CONNECTOR_TRIGGERS).map(([id, line]) => [triggerKey(id), line]));
|
|
36
|
+
|
|
37
|
+
/** The curated trigger of the catalog connector `serverId`, or ''. */
|
|
38
|
+
export function connectorTrigger(serverId) {
|
|
39
|
+
// Another account of a connector is named `<id>-2`, `<id>-3`…
|
|
40
|
+
return (
|
|
41
|
+
TRIGGERS_BY_KEY.get(triggerKey(serverId)) ||
|
|
42
|
+
TRIGGERS_BY_KEY.get(triggerKey(String(serverId || '').replace(/[-_]\d+$/, ''))) ||
|
|
43
|
+
''
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -237,7 +237,9 @@ function typedStreamFailureText(error, text) {
|
|
|
237
237
|
|
|
238
238
|
export function presentErrorText(error, options = {}) {
|
|
239
239
|
const presentation = describeError(error);
|
|
240
|
-
if (['image-size', 'payload-size', 'request'].includes(presentation.kind))
|
|
240
|
+
if (['image-size', 'payload-size', 'request', 'model-unavailable', 'tool-schema'].includes(presentation.kind)) {
|
|
241
|
+
return presentation.summary;
|
|
242
|
+
}
|
|
241
243
|
const kindLine = hasTypedEvidence(error) ? KIND_LINES[presentation.providerKind] : null;
|
|
242
244
|
if (kindLine && presentation.kind !== 'unknown') {
|
|
243
245
|
return typeof kindLine === 'function' ? kindLine(presentation) : kindLine;
|
|
@@ -138,6 +138,77 @@ function resetLabel(resetAt) {
|
|
|
138
138
|
return reset.toDateString() === new Date().toDateString() ? time : `${reset.getMonth() + 1}/${reset.getDate()} ${time}`;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
+
// A refused request that names its model as unknown, retired or not offered
|
|
142
|
+
// to this account. Read only for request-refusal kinds, so a typed overload,
|
|
143
|
+
// quota or auth failure never reads as a missing model.
|
|
144
|
+
const MODEL_REFUSAL_KINDS = new Set(['invalid_request', 'permission', 'unknown']);
|
|
145
|
+
const MODEL_UNAVAILABLE_CODES = new Set(['model_not_found', 'model_not_supported']);
|
|
146
|
+
// The model must be the subject of the refusal, at the start of a line or
|
|
147
|
+
// sentence ("The model `x` does not exist", "models/x is not found", Ollama's
|
|
148
|
+
// `model "x" not found`), or the refusal names it ("Unknown model", "… is not
|
|
149
|
+
// a valid model ID", Anthropic's bare "model: x"). A model merely mentioned
|
|
150
|
+
// beside some other missing thing ("For model x, tool lookup: route not
|
|
151
|
+
// found") does not count.
|
|
152
|
+
const MODEL_REFUSAL_PREDICATE =
|
|
153
|
+
"(?:(?:is|was|has been)\\s+)?(?:not found|does not exist|doesn'?t exist|not (?:supported|available)|(?:currently\\s+)?unavailable|no longer (?:available|supported)|deprecated|retired|removed)\\b";
|
|
154
|
+
const SENTENCE_START = '(?:^|[.:;!]\\s+)';
|
|
155
|
+
const MODEL_UNAVAILABLE_RE = new RegExp(
|
|
156
|
+
[
|
|
157
|
+
// "The model `x` does not exist", "models/x is not found", Vertex
|
|
158
|
+
// "Publisher Model `…` was not found", Ollama `model "x" not found`.
|
|
159
|
+
`${SENTENCE_START}(?:(?:the|this|that|requested|selected|publisher)\\s+)*models?(?:\\/\\S+|\\s+[\`'"]?[\\w./:@-]+[\`'"]?)?\\s+${MODEL_REFUSAL_PREDICATE}`,
|
|
160
|
+
// OpenAI "The 'gpt-x' model is not supported when using Codex …".
|
|
161
|
+
`${SENTENCE_START}(?:the\\s+)?[\`'"][^\`'"\\n]+[\`'"]\\s+model\\s+${MODEL_REFUSAL_PREDICATE}`,
|
|
162
|
+
`${SENTENCE_START}(?:unknown|invalid|unsupported) model\\b`,
|
|
163
|
+
// OpenRouter: no provider serves this model id.
|
|
164
|
+
`${SENTENCE_START}no endpoints found for [\\w.-]+\\/[\\w.:-]+`,
|
|
165
|
+
'\\bis not a valid model\\b',
|
|
166
|
+
'^model: \\S+$',
|
|
167
|
+
].join('|'),
|
|
168
|
+
'im'
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* True when `error` (or its text) says the requested model is unknown,
|
|
173
|
+
* retired or not offered to this account: a typed model code, or provider
|
|
174
|
+
* wording with the model as the subject of the refusal.
|
|
175
|
+
*/
|
|
176
|
+
export function namesUnavailableModel(error) {
|
|
177
|
+
if (!error) return false;
|
|
178
|
+
if (typeof error === 'string') return MODEL_UNAVAILABLE_RE.test(error);
|
|
179
|
+
const codes = [error.code, error.providerErrorCode, error.error?.code, error.providerError?.code];
|
|
180
|
+
if (codes.some((code) => MODEL_UNAVAILABLE_CODES.has(String(code || '').toLowerCase()))) return true;
|
|
181
|
+
return [error.message, error.error?.message, error.providerError?.message, error.detail]
|
|
182
|
+
.filter((text) => typeof text === 'string' && text)
|
|
183
|
+
.some((text) => MODEL_UNAVAILABLE_RE.test(text));
|
|
184
|
+
}
|
|
185
|
+
const MODEL_UNAVAILABLE_COPY = [
|
|
186
|
+
'model-unavailable',
|
|
187
|
+
"This model isn't available for this account.",
|
|
188
|
+
'Choose another model, or check the model and endpoint in Providers.',
|
|
189
|
+
];
|
|
190
|
+
const NOT_FOUND_SUMMARY = "The provider couldn't find the requested model or endpoint.";
|
|
191
|
+
// A tool definition the provider's schema validator refused (an MCP or custom
|
|
192
|
+
// tool schema), named by the field path or wording each API uses.
|
|
193
|
+
const TOOL_SCHEMA_RE =
|
|
194
|
+
/invalid schema for (?:function|tool)\b|\btools\.\d+\.(?:custom\.)?input_schema\b|\bfunction_declarations\[\d+\]\.parameters\b|\btools\[\d+\]\.function\.parameters\b|\binvalid (?:tool|function) (?:schema|parameters|definition)\b/i;
|
|
195
|
+
const TOOL_SCHEMA_COPY = [
|
|
196
|
+
'tool-schema',
|
|
197
|
+
'The provider rejected a tool definition.',
|
|
198
|
+
'Disable the tool or MCP server named in the details, then try again.',
|
|
199
|
+
];
|
|
200
|
+
|
|
201
|
+
// The first line of a provider message worth showing as-is; '' for a stack
|
|
202
|
+
// frame, a markup page or a raw JSON body.
|
|
203
|
+
function firstReadableLine(clean) {
|
|
204
|
+
const lines = clean
|
|
205
|
+
.split('\n')
|
|
206
|
+
.map((line) => line.trim())
|
|
207
|
+
.filter(Boolean);
|
|
208
|
+
const first = lines.find((line) => !/^at\s|^\{|^<!doctype|^<html/i.test(line)) || '';
|
|
209
|
+
return first && !/^[{[]|^<|(?:API|HTTP)\s+[45]\d\d.*[<{]/i.test(first) ? first : '';
|
|
210
|
+
}
|
|
211
|
+
|
|
141
212
|
// Only legacy items (no persisted descriptor) and untyped strings reach this.
|
|
142
213
|
const LEGACY_TEXT_KINDS = [
|
|
143
214
|
[/The provider usage limit was reached|quota.*(?:hit|exceed)|usage limit/i, 'quota_exhausted'],
|
|
@@ -241,6 +312,25 @@ export function describeError(value) {
|
|
|
241
312
|
kind = 'image-size';
|
|
242
313
|
summary = 'An attached image exceeds the provider limit.';
|
|
243
314
|
recovery = 'Reduce the image dimensions or number of images, then try again.';
|
|
315
|
+
} else if (
|
|
316
|
+
clean === MODEL_UNAVAILABLE_COPY[1] ||
|
|
317
|
+
(MODEL_REFUSAL_KINDS.has(descriptor.kind) &&
|
|
318
|
+
(MODEL_UNAVAILABLE_CODES.has(code) || MODEL_UNAVAILABLE_RE.test(clean)))
|
|
319
|
+
) {
|
|
320
|
+
[kind, summary, recovery] = MODEL_UNAVAILABLE_COPY;
|
|
321
|
+
} else if (
|
|
322
|
+
clean === TOOL_SCHEMA_COPY[1] ||
|
|
323
|
+
(['invalid_request', 'unknown'].includes(descriptor.kind) && TOOL_SCHEMA_RE.test(clean))
|
|
324
|
+
) {
|
|
325
|
+
[kind, summary, recovery] = TOOL_SCHEMA_COPY;
|
|
326
|
+
} else if (
|
|
327
|
+
clean === NOT_FOUND_SUMMARY ||
|
|
328
|
+
(MODEL_REFUSAL_KINDS.has(descriptor.kind) && (status === 404 || status === 410))
|
|
329
|
+
) {
|
|
330
|
+
// The provider's own words name what is missing; the next step is the same.
|
|
331
|
+
kind = 'not-found';
|
|
332
|
+
summary = firstReadableLine(clean) || NOT_FOUND_SUMMARY;
|
|
333
|
+
recovery = MODEL_UNAVAILABLE_COPY[2];
|
|
244
334
|
} else if (
|
|
245
335
|
descriptor.kind === 'unknown' &&
|
|
246
336
|
/payload too large|request (?:body )?too large|The request exceeds the size limit/i.test(clean)
|
|
@@ -272,12 +362,7 @@ export function describeError(value) {
|
|
|
272
362
|
summary = 'The provider rejected this request.';
|
|
273
363
|
recovery = 'Review the request details before trying again.';
|
|
274
364
|
} else {
|
|
275
|
-
|
|
276
|
-
.split('\n')
|
|
277
|
-
.map((line) => line.trim())
|
|
278
|
-
.filter(Boolean);
|
|
279
|
-
const first = lines.find((line) => !/^at\s|^\{|^<!doctype|^<html/i.test(line)) || '';
|
|
280
|
-
summary = first && !/^[{[]|^<|(?:API|HTTP)\s+[45]\d\d.*[<{]/i.test(first) ? first : 'Something went wrong.';
|
|
365
|
+
summary = firstReadableLine(clean) || 'Something went wrong.';
|
|
281
366
|
}
|
|
282
367
|
summary = summary.replace(/\s+/g, ' ').trim() || 'Something went wrong.';
|
|
283
368
|
if (summary.length > SUMMARY_LIMIT) summary = `${summary.slice(0, SUMMARY_LIMIT - 1).trimEnd()}…`;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Mixdog's own Google OAuth client (a "Desktop app" client, marked "Use this
|
|
2
|
+
// client for an AI-powered agent"), used by the Gmail, Google Calendar and
|
|
3
|
+
// Google Drive catalog connectors instead of dynamic client registration,
|
|
4
|
+
// which Google does not offer. Google treats desktop clients as public: the
|
|
5
|
+
// id and its non-confidential secret ship with the app, and every sign-in
|
|
6
|
+
// uses PKCE (S256), state and a loopback redirect
|
|
7
|
+
// (developers.google.com/identity/protocols/oauth2/native-app).
|
|
8
|
+
//
|
|
9
|
+
// The values come only from the environment; the source ships none, so a
|
|
10
|
+
// build without them offers no built-in Google sign-in (the user's own
|
|
11
|
+
// Google client still works).
|
|
12
|
+
|
|
13
|
+
const value = (name) => String(process.env[name] || '').trim();
|
|
14
|
+
|
|
15
|
+
/** `{ clientId, clientSecret }` of Mixdog's Google client, or null when this
|
|
16
|
+
* build has none. */
|
|
17
|
+
export function googleOAuthClient() {
|
|
18
|
+
const clientId = value('MIXDOG_GOOGLE_OAUTH_CLIENT_ID');
|
|
19
|
+
if (!clientId) return null;
|
|
20
|
+
return { clientId, clientSecret: value('MIXDOG_GOOGLE_OAUTH_CLIENT_SECRET') };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Google issues a refresh token only for `access_type=offline`. */
|
|
24
|
+
export const GOOGLE_AUTH_PARAMS = Object.freeze({ access_type: 'offline' });
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface HandoffFile {
|
|
2
|
+
name: string;
|
|
3
|
+
path: string;
|
|
4
|
+
kind: 'image' | 'file' | 'text';
|
|
5
|
+
mimeType: string;
|
|
6
|
+
size: number;
|
|
7
|
+
/** Attachment-store reference when the file was stored. */
|
|
8
|
+
id?: string;
|
|
9
|
+
}
|
|
10
|
+
export function normalizeHandoffFiles(files: unknown): HandoffFile[];
|
|
11
|
+
export function artifactLines(files: unknown, omitted?: number, options?: { ids?: boolean }): string[];
|
|
12
|
+
export interface HandoffRecord {
|
|
13
|
+
id: string;
|
|
14
|
+
attachmentRef: string;
|
|
15
|
+
kind: 'image' | 'file' | 'text';
|
|
16
|
+
name: string;
|
|
17
|
+
mimeType: string;
|
|
18
|
+
size: number;
|
|
19
|
+
}
|
|
20
|
+
export function artifactRecords(files: unknown): HandoffRecord[];
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// The files a bot's or agent's turn handed back (session-runtime/
|
|
2
|
+
// handoff-attachments.mjs collectTurnArtifacts) as the rows that carry them
|
|
3
|
+
// show them: one model-facing line per file — the attachment id that forwards
|
|
4
|
+
// it, or its path when it is too large or of a type no prompt carries — and
|
|
5
|
+
// the message attachment records of the stored ones. Only those records
|
|
6
|
+
// authorize forwarding by id; the lines are information for the model.
|
|
7
|
+
import { escapeHarnessTags } from './shell-output-summary.mjs';
|
|
8
|
+
import { normalizeMessageAttachments } from './transcript-metadata.mjs';
|
|
9
|
+
|
|
10
|
+
const sizeLabel = (size) => {
|
|
11
|
+
const bytes = Number(size) || 0;
|
|
12
|
+
if (bytes >= 1_048_576) return `${(bytes / 1_048_576).toFixed(1)} MB`;
|
|
13
|
+
return bytes >= 1024 ? `${Math.round(bytes / 1024)} KB` : `${bytes} B`;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const oneLine = (value) => escapeHarnessTags(String(value ?? '').replace(/[\r\n]+/g, ' '));
|
|
17
|
+
|
|
18
|
+
/** `files` ({ name, mimeType, size, path, id? }) normalized: bounded strings,
|
|
19
|
+
* ids only of the attachment-store shape. */
|
|
20
|
+
export function normalizeHandoffFiles(files) {
|
|
21
|
+
return (Array.isArray(files) ? files : [])
|
|
22
|
+
.filter((file) => file && typeof file === 'object' && typeof file.path === 'string' && file.path)
|
|
23
|
+
.slice(0, 8)
|
|
24
|
+
.map((file) => ({
|
|
25
|
+
name: String(file.name || 'file').slice(0, 200),
|
|
26
|
+
path: file.path.slice(0, 1024),
|
|
27
|
+
kind: ['image', 'file', 'text'].includes(file.kind) ? file.kind : 'file',
|
|
28
|
+
mimeType: String(file.mimeType || 'application/octet-stream').slice(0, 100),
|
|
29
|
+
size: Number(file.size) >= 0 ? Number(file.size) : 0,
|
|
30
|
+
...(/^[a-f0-9]{64}$/.test(String(file.id ?? '')) ? { id: file.id } : {}),
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The model-facing list of `files`, empty when there are none. `ids`: the
|
|
35
|
+
* row also records the stored files as its attachments (a task report), so
|
|
36
|
+
* their ids can be forwarded; a plain-text handoff (an agent completion)
|
|
37
|
+
* lists paths only — an id named in text is never trusted. */
|
|
38
|
+
export function artifactLines(files, omitted = 0, { ids = true } = {}) {
|
|
39
|
+
const list = normalizeHandoffFiles(files);
|
|
40
|
+
if (!list.length) return [];
|
|
41
|
+
const attachable = (file) => (ids ? ` [attachment:${file.id}]` : ' (attachable: forward it by path)');
|
|
42
|
+
return [
|
|
43
|
+
`Files from this turn (forward one with attachments: [${ids ? 'id or path' : 'path'}]; link a path to show it):`,
|
|
44
|
+
...list.map(
|
|
45
|
+
(file) =>
|
|
46
|
+
`- ${oneLine(file.name)} (${oneLine(file.mimeType)}, ${sizeLabel(file.size)}) — ${oneLine(file.path)}${
|
|
47
|
+
file.id ? attachable(file) : ' (too large or unsupported to attach; link the path)'
|
|
48
|
+
}`
|
|
49
|
+
),
|
|
50
|
+
...(omitted > 0 ? [`- …and ${omitted} more file(s)`] : []),
|
|
51
|
+
];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The message attachment records of `files` (the stored ones). */
|
|
55
|
+
export function artifactRecords(files) {
|
|
56
|
+
return normalizeMessageAttachments(
|
|
57
|
+
normalizeHandoffFiles(files)
|
|
58
|
+
.filter((file) => file.id)
|
|
59
|
+
.map((file) => ({ ...file, attachmentRef: file.id }))
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface HeldRequestPushEvent {
|
|
2
|
+
sessionId: string;
|
|
3
|
+
agentId: string;
|
|
4
|
+
agentName: string;
|
|
5
|
+
requestId: string;
|
|
6
|
+
}
|
|
7
|
+
export function registerHeldRequestResolver(resolver: (id: string, decision: unknown) => boolean): () => void;
|
|
8
|
+
export function resolveHeldRequest(id: string, decision: unknown): boolean;
|
|
9
|
+
export function registerHeldRequestNotifier(notifier: (event: HeldRequestPushEvent) => void): () => void;
|
|
10
|
+
export function notifyHeldRequest(event: Partial<HeldRequestPushEvent>): void;
|
|
11
|
+
export function recordRunOwner(sessionId: string, agentId: string): void;
|
|
12
|
+
export function runOwnerOf(sessionId: string): string;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Runtime ↔ desktop-service channel for held requests of unattended runs
|
|
2
|
+
// (session-runtime/held-requests.mjs). The daemon's held-request registry
|
|
3
|
+
// registers the resolver; the desktop service hosted in this daemon registers
|
|
4
|
+
// a notifier that sends the sanitized phone push. The session API answers a
|
|
5
|
+
// held request through resolveHeldRequest, whatever session it was sent from.
|
|
6
|
+
//
|
|
7
|
+
// Deliberately dependency-free: the desktop service bundle imports this file
|
|
8
|
+
// too, and both copies share one registry through a global symbol.
|
|
9
|
+
|
|
10
|
+
const KEY = Symbol.for('mixdog.heldRequestHost.v1');
|
|
11
|
+
|
|
12
|
+
function registry() {
|
|
13
|
+
globalThis[KEY] ??= { resolver: null, notifiers: [], runOwners: new Map() };
|
|
14
|
+
return globalThis[KEY];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const RUN_OWNER_LIMIT = 500;
|
|
18
|
+
/** Remember which agent an unattended run session belongs to, so its pushes
|
|
19
|
+
* follow that agent's notification mute after the run ends (bounded, newest kept). */
|
|
20
|
+
export function recordRunOwner(sessionId, agentId) {
|
|
21
|
+
const owners = registry().runOwners;
|
|
22
|
+
owners.delete(sessionId);
|
|
23
|
+
owners.set(String(sessionId), String(agentId));
|
|
24
|
+
while (owners.size > RUN_OWNER_LIMIT) owners.delete(owners.keys().next().value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function runOwnerOf(sessionId) {
|
|
28
|
+
return registry().runOwners.get(String(sessionId || '')) || '';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** `resolver(id, decision) → boolean` (true when `id` was a pending held request). */
|
|
32
|
+
export function registerHeldRequestResolver(resolver) {
|
|
33
|
+
registry().resolver = resolver;
|
|
34
|
+
return () => {
|
|
35
|
+
if (registry().resolver === resolver) registry().resolver = null;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function resolveHeldRequest(id, decision) {
|
|
40
|
+
const { resolver } = registry();
|
|
41
|
+
return typeof resolver === 'function' && String(id || '').startsWith('held_')
|
|
42
|
+
? resolver(id, decision) === true
|
|
43
|
+
: false;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** `notifier({ sessionId, agentId, agentName, requestId })`: one per desktop service. */
|
|
47
|
+
export function registerHeldRequestNotifier(notifier) {
|
|
48
|
+
const entry = { notifier };
|
|
49
|
+
registry().notifiers.push(entry);
|
|
50
|
+
return () => {
|
|
51
|
+
const list = registry().notifiers;
|
|
52
|
+
const index = list.indexOf(entry);
|
|
53
|
+
if (index >= 0) list.splice(index, 1);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Hand one held request to every registered notifier. Carries ids and the
|
|
58
|
+
* agent's name only: never the tool, its arguments or any text. */
|
|
59
|
+
export function notifyHeldRequest({ sessionId, agentId, agentName, requestId }) {
|
|
60
|
+
const event = {
|
|
61
|
+
sessionId: String(sessionId || ''),
|
|
62
|
+
agentId: String(agentId || ''),
|
|
63
|
+
agentName: String(agentName || ''),
|
|
64
|
+
requestId: String(requestId || ''),
|
|
65
|
+
};
|
|
66
|
+
for (const { notifier } of registry().notifiers) {
|
|
67
|
+
try {
|
|
68
|
+
notifier(event);
|
|
69
|
+
} catch {}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -28,7 +28,12 @@ export const ANTIGRAVITY_MODELS = Object.freeze([
|
|
|
28
28
|
low: 'gemini-3.1-pro-low',
|
|
29
29
|
high: 'gemini-3.1-pro-high',
|
|
30
30
|
}),
|
|
31
|
-
|
|
31
|
+
// The gateway serves Argon only through per-tier ids; bare `gemini-4-argon` is a 404.
|
|
32
|
+
fallbackModel('gemini-4-argon', 'Gemini 4 Argon', 'gemini-pro', 1048576, {
|
|
33
|
+
low: 'gemini-4-argon-low',
|
|
34
|
+
medium: 'gemini-4-argon-medium',
|
|
35
|
+
high: 'gemini-4-argon-high',
|
|
36
|
+
}),
|
|
32
37
|
fallbackModel('claude-opus-4-6-thinking', 'Claude Opus 4.6 (Thinking)', null, 250000, 'claude-opus-4-6-thinking', [
|
|
33
38
|
'low',
|
|
34
39
|
'medium',
|
|
@@ -63,6 +63,12 @@ export async function accountProviderSend(provider, instance, send, model, opts
|
|
|
63
63
|
if (!ledger) return;
|
|
64
64
|
recorded.add(owner);
|
|
65
65
|
const usage = result.usage || UNMEASURED_USAGE;
|
|
66
|
+
const durationMs = timed ? Date.now() - startedAt : 0;
|
|
67
|
+
// The one measured request time: the loop's speed figures read it, so
|
|
68
|
+
// the composer and the ledger show the same span.
|
|
69
|
+
if (timed && result && typeof result === 'object') {
|
|
70
|
+
Object.defineProperty(result, 'usageDurationMs', { value: durationMs, configurable: true, writable: true });
|
|
71
|
+
}
|
|
66
72
|
const row = makeUsageRecord({
|
|
67
73
|
id: result.responseId ? undefined : id,
|
|
68
74
|
ts: Date.now(),
|
|
@@ -88,7 +94,7 @@ export async function accountProviderSend(provider, instance, send, model, opts
|
|
|
88
94
|
// The account this send is bound to, so quota history can tell one
|
|
89
95
|
// connected subscription's records from another's.
|
|
90
96
|
account: ACCOUNT_PROVIDERS.includes(provider) ? currentProviderAccountId(provider) : '',
|
|
91
|
-
durationMs
|
|
97
|
+
durationMs,
|
|
92
98
|
});
|
|
93
99
|
// Committed by the ledger worker (batched with concurrent sends); the
|
|
94
100
|
// send still settles only once its row is durable, as before, but the
|