mixdog 0.9.1 → 0.9.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 +8 -1
- package/scripts/_bench-cwc.json +20 -0
- package/scripts/agent-loop-policy-test.mjs +37 -0
- package/scripts/agent-parallel-smoke.mjs +54 -10
- package/scripts/background-task-meta-smoke.mjs +1 -1
- package/scripts/bench-run.mjs +262 -0
- package/scripts/compact-smoke.mjs +12 -0
- package/scripts/compact-trigger-migration-smoke.mjs +67 -1
- package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
- package/scripts/internal-comms-bench.mjs +727 -0
- package/scripts/internal-comms-smoke.mjs +75 -0
- package/scripts/lead-workflow-smoke.mjs +4 -4
- package/scripts/live-worker-smoke.mjs +9 -9
- package/scripts/output-style-bench.mjs +285 -0
- package/scripts/output-style-smoke.mjs +13 -10
- package/scripts/patch-replay.mjs +90 -0
- package/scripts/provider-stream-stall-test.mjs +276 -0
- package/scripts/provider-toolcall-test.mjs +599 -1
- package/scripts/routing-corpus.mjs +281 -0
- package/scripts/session-bench.mjs +1526 -0
- package/scripts/session-diag.mjs +595 -0
- package/scripts/task-bench.mjs +207 -0
- package/scripts/tool-failures.mjs +6 -6
- package/scripts/tool-smoke.mjs +306 -66
- package/scripts/toolcall-args-test.mjs +81 -0
- package/src/agents/debugger/AGENT.md +4 -4
- package/src/agents/heavy-worker/AGENT.md +4 -2
- package/src/agents/reviewer/AGENT.md +4 -4
- package/src/agents/worker/AGENT.md +4 -2
- package/src/app.mjs +10 -6
- package/src/defaults/{hidden-roles.json → agents.json} +7 -7
- package/src/examples/schedules/SCHEDULE.example.md +32 -0
- package/src/examples/webhooks/WEBHOOK.example.md +40 -0
- package/src/headless-role.mjs +14 -14
- package/src/help.mjs +1 -0
- package/src/lib/rules-builder.cjs +32 -54
- package/src/mixdog-session-runtime.mjs +710 -318
- package/src/output-styles/default.md +12 -7
- package/src/output-styles/minimal.md +25 -0
- package/src/output-styles/oneline.md +21 -0
- package/src/output-styles/simple.md +10 -9
- package/src/repl.mjs +12 -2
- package/src/rules/agent/00-common.md +7 -5
- package/src/rules/agent/30-explorer.md +7 -8
- package/src/rules/lead/01-general.md +3 -1
- package/src/rules/lead/lead-tool.md +7 -0
- package/src/rules/shared/01-tool.md +17 -12
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
- package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
- package/src/runtime/agent/orchestrator/config.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +131 -67
- package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
- package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
- package/src/runtime/agent/orchestrator/mcp/client.mjs +94 -16
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +332 -57
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +59 -32
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +27 -20
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +210 -21
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +78 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +202 -98
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +183 -20
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +11 -5
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +15 -9
- package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
- package/src/runtime/agent/orchestrator/session/loop.mjs +394 -132
- package/src/runtime/agent/orchestrator/session/manager.mjs +217 -170
- package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -24
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +0 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +13 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +18 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -1
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4076 -3985
- package/src/runtime/agent/orchestrator/tools/patch.mjs +116 -2
- package/src/runtime/channels/backends/discord.mjs +99 -9
- package/src/runtime/channels/backends/telegram.mjs +501 -0
- package/src/runtime/channels/index.mjs +441 -1224
- package/src/runtime/channels/lib/config.mjs +54 -2
- package/src/runtime/channels/lib/format.mjs +4 -2
- package/src/runtime/channels/lib/output-forwarder.mjs +80 -67
- package/src/runtime/channels/lib/runtime-paths.mjs +29 -0
- package/src/runtime/channels/lib/scheduler.mjs +1 -1
- package/src/runtime/channels/lib/telegram-format.mjs +283 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +19 -1
- package/src/runtime/channels/lib/webhook.mjs +59 -31
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/memory/index.mjs +184 -19
- package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
- package/src/runtime/memory/lib/core-memory-store.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2.mjs +9 -6
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
- package/src/runtime/memory/lib/memory.mjs +101 -4
- package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
- package/src/runtime/memory/lib/session-ingest.mjs +107 -0
- package/src/runtime/memory/lib/trace-store.mjs +69 -22
- package/src/runtime/memory/tool-defs.mjs +6 -3
- package/src/runtime/shared/channel-notification-routing.mjs +12 -0
- package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
- package/src/runtime/shared/config.mjs +9 -0
- package/src/runtime/shared/llm/http-agent.mjs +12 -5
- package/src/runtime/shared/schedules-store.mjs +21 -19
- package/src/runtime/shared/tool-surface.mjs +98 -13
- package/src/runtime/shared/transcript-writer.mjs +129 -0
- package/src/runtime/shared/update-checker.mjs +214 -0
- package/src/standalone/agent-tool.mjs +255 -109
- package/src/standalone/channel-admin.mjs +133 -40
- package/src/standalone/channel-worker.mjs +8 -291
- package/src/standalone/explore-tool.mjs +2 -2
- package/src/standalone/memory-runtime-proxy.mjs +3 -1
- package/src/standalone/provider-admin.mjs +11 -0
- package/src/standalone/usage-dashboard.mjs +1 -1
- package/src/tui/App.jsx +2096 -732
- package/src/tui/components/ConfirmBar.jsx +47 -0
- package/src/tui/components/ContextPanel.jsx +5 -3
- package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
- package/src/tui/components/Markdown.jsx +22 -98
- package/src/tui/components/Message.jsx +14 -35
- package/src/tui/components/Picker.jsx +87 -12
- package/src/tui/components/PromptInput.jsx +83 -7
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +8 -5
- package/src/tui/components/Spinner.jsx +7 -7
- package/src/tui/components/StatusLine.jsx +40 -21
- package/src/tui/components/TextEntryPanel.jsx +51 -7
- package/src/tui/components/ToolExecution.jsx +170 -98
- package/src/tui/components/TurnDone.jsx +4 -4
- package/src/tui/components/UsagePanel.jsx +1 -1
- package/src/tui/components/tool-output-format.mjs +159 -21
- package/src/tui/components/tool-output-format.test.mjs +87 -0
- package/src/tui/display-width.mjs +69 -0
- package/src/tui/display-width.test.mjs +35 -0
- package/src/tui/dist/index.mjs +6965 -2391
- package/src/tui/engine.mjs +287 -126
- package/src/tui/index.jsx +117 -7
- package/src/tui/keyboard-protocol.mjs +42 -0
- package/src/tui/lib/voice-recorder.mjs +453 -0
- package/src/tui/markdown/format-token.mjs +129 -76
- package/src/tui/markdown/format-token.test.mjs +61 -19
- package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
- package/src/tui/markdown/render-ansi.test.mjs +1 -1
- package/src/tui/markdown/streaming-markdown.mjs +167 -0
- package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
- package/src/tui/markdown/table-layout.mjs +9 -9
- package/src/tui/paste-attachments.mjs +0 -11
- package/src/tui/prompt-history-store.mjs +129 -0
- package/src/tui/prompt-history-store.test.mjs +52 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
- package/src/tui/theme.mjs +41 -657
- package/src/tui/themes/base.mjs +86 -0
- package/src/tui/themes/basic.mjs +85 -0
- package/src/tui/themes/catppuccin.mjs +72 -0
- package/src/tui/themes/dracula.mjs +70 -0
- package/src/tui/themes/everforest.mjs +71 -0
- package/src/tui/themes/gruvbox.mjs +71 -0
- package/src/tui/themes/index.mjs +71 -0
- package/src/tui/themes/indigo.mjs +78 -0
- package/src/tui/themes/kanagawa.mjs +80 -0
- package/src/tui/themes/light.mjs +81 -0
- package/src/tui/themes/nord.mjs +72 -0
- package/src/tui/themes/onedark.mjs +16 -0
- package/src/tui/themes/rosepine.mjs +70 -0
- package/src/tui/themes/teal.mjs +81 -0
- package/src/tui/themes/tokyonight.mjs +79 -0
- package/src/tui/themes/utils.mjs +106 -0
- package/src/tui/themes/warm.mjs +79 -0
- package/src/tui/transcript-tool-failures.mjs +13 -2
- package/src/ui/markdown.mjs +1 -1
- package/src/ui/model-display.mjs +2 -2
- package/src/ui/statusline.mjs +26 -27
- package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
- package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
- package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
- package/src/workflows/default/WORKFLOW.md +39 -12
- package/src/workflows/sequential/WORKFLOW.md +46 -0
- package/src/workflows/solo/WORKFLOW.md +7 -0
- package/vendor/ink/build/display-width.js +62 -0
- package/vendor/ink/build/ink.js +100 -12
- package/vendor/ink/build/measure-text.js +4 -1
- package/vendor/ink/build/output.js +115 -9
- package/vendor/ink/build/render-node-to-output.js +4 -1
- package/vendor/ink/build/render.js +4 -0
- package/src/output-styles/extreme-simple.md +0 -20
- package/src/rules/lead/04-workflow.md +0 -51
- package/src/workflows/default/workflow.json +0 -13
- package/src/workflows/solo/workflow.json +0 -7
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
* no-tool role guard) — not a permission check. No gating is needed here.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
+
import { isToolEnvelope, makeToolEnvelope } from './session/tool-envelope.mjs';
|
|
19
|
+
|
|
18
20
|
let _executor = null;
|
|
19
21
|
let _tools = [];
|
|
20
22
|
let _names = new Set();
|
|
@@ -24,35 +26,14 @@ let _bootResolver = null;
|
|
|
24
26
|
const _bootPromise = new Promise((r) => { _bootResolver = r; });
|
|
25
27
|
export function markBootReady() { if (_bootReady) return; _bootReady = true; _bootResolver(); }
|
|
26
28
|
|
|
27
|
-
// Per-tool executor overrides. Populated by addInternalTools() for extra
|
|
28
|
-
// internal tools that bypass the main dispatch (tools.json + dispatchTool).
|
|
29
|
-
const _overrides = new Map();
|
|
30
|
-
|
|
31
29
|
export function setInternalToolsProvider({ executor, tools }) {
|
|
32
30
|
if (typeof executor !== 'function') throw new Error('internal-tools: executor must be a function');
|
|
33
31
|
_executor = executor;
|
|
34
32
|
const base = Array.isArray(tools) ? [...tools] : [];
|
|
35
|
-
// Re-registration (handleToolCall idempotent fallback) must preserve any
|
|
36
|
-
// override-backed tools previously registered via addInternalTools.
|
|
37
|
-
if (_overrides.size > 0) {
|
|
38
|
-
const baseNames = new Set(base.map(t => t?.name).filter(Boolean));
|
|
39
|
-
for (const [name] of _overrides) {
|
|
40
|
-
if (baseNames.has(name)) continue;
|
|
41
|
-
const existing = _tools.find(t => t?.name === name);
|
|
42
|
-
if (existing) base.push(existing);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
33
|
_tools = base;
|
|
46
34
|
_names = new Set(_tools.map(t => t?.name).filter(Boolean));
|
|
47
35
|
}
|
|
48
36
|
|
|
49
|
-
/**
|
|
50
|
-
* Register additional tools that aren't declared in tools.json — each comes
|
|
51
|
-
* with its own executor.
|
|
52
|
-
*
|
|
53
|
-
* Re-registration is idempotent; later calls overwrite earlier entries with
|
|
54
|
-
* the same name.
|
|
55
|
-
*/
|
|
56
37
|
export function getInternalTools() {
|
|
57
38
|
return _tools;
|
|
58
39
|
}
|
|
@@ -63,11 +44,6 @@ export function isInternalTool(name) {
|
|
|
63
44
|
|
|
64
45
|
export async function executeInternalTool(name, args, callerCtx = {}) {
|
|
65
46
|
if (!_names.has(name)) throw new Error(`internal-tools: "${name}" is not registered`);
|
|
66
|
-
const override = _overrides.get(name);
|
|
67
|
-
if (override) {
|
|
68
|
-
const result = await override(args ?? {}, callerCtx);
|
|
69
|
-
return _normalize(result);
|
|
70
|
-
}
|
|
71
47
|
if (!_executor) throw new Error(`internal-tools: executor not initialized (tool=${name})`);
|
|
72
48
|
const result = await _executor(name, args ?? {}, callerCtx);
|
|
73
49
|
return _normalize(result);
|
|
@@ -77,6 +53,17 @@ export async function executeInternalTool(name, args, callerCtx = {}) {
|
|
|
77
53
|
// plain string either way. Worker/module handlers return the MCP-shaped
|
|
78
54
|
// `{ content: [{type:'text', text}] }` envelope directly.
|
|
79
55
|
function _normalize(result) {
|
|
56
|
+
// General newMessages tool-result channel: a tool may return a
|
|
57
|
+
// `{ __toolEnvelope, result, newMessages }` envelope (e.g. Skill, whose
|
|
58
|
+
// body rides ONE injected user message). Preserve the envelope shape, only
|
|
59
|
+
// normalizing its inner `result` to a string, so the agent loop's central
|
|
60
|
+
// normalizeToolEnvelope still splits it into stub + injected user body.
|
|
61
|
+
// Without this guard the envelope object would fall through to
|
|
62
|
+
// JSON.stringify below and the loop would see the stringified envelope as
|
|
63
|
+
// the tool_result (body inlined + duplicated, no newMessages).
|
|
64
|
+
if (isToolEnvelope(result)) {
|
|
65
|
+
return makeToolEnvelope(_normalize(result.result), result.newMessages);
|
|
66
|
+
}
|
|
80
67
|
if (result && typeof result === 'object' && Array.isArray(result.content)) {
|
|
81
68
|
return result.content
|
|
82
69
|
.map((c) => (c?.type === 'text' ? c.text || '' : JSON.stringify(c)))
|
|
@@ -37,13 +37,58 @@ async function loadMcpSdk() {
|
|
|
37
37
|
import('@modelcontextprotocol/sdk/client/index.js'),
|
|
38
38
|
import('@modelcontextprotocol/sdk/client/stdio.js'),
|
|
39
39
|
import('@modelcontextprotocol/sdk/client/streamableHttp.js'),
|
|
40
|
-
|
|
40
|
+
import('@modelcontextprotocol/sdk/client/sse.js'),
|
|
41
|
+
import('@modelcontextprotocol/sdk/client/websocket.js'),
|
|
42
|
+
]).then(([clientMod, stdioMod, httpMod, sseMod, wsMod]) => ({
|
|
41
43
|
Client: clientMod.Client,
|
|
42
44
|
StdioClientTransport: stdioMod.StdioClientTransport,
|
|
43
45
|
StreamableHTTPClientTransport: httpMod.StreamableHTTPClientTransport,
|
|
46
|
+
SSEClientTransport: sseMod.SSEClientTransport,
|
|
47
|
+
WebSocketClientTransport: wsMod.WebSocketClientTransport,
|
|
44
48
|
}));
|
|
45
49
|
return mcpSdkPromise;
|
|
46
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Expand `${VAR}` and `${env:VAR}` references in string values using the
|
|
53
|
+
* provided env map (defaults to process.env). Recurses into arrays/objects.
|
|
54
|
+
* Unknown vars expand to an empty string. No shell execution.
|
|
55
|
+
*/
|
|
56
|
+
export function expandEnvVars(value, env = process.env) {
|
|
57
|
+
if (typeof value === 'string') {
|
|
58
|
+
return value.replace(/\$\{(?:env:)?([A-Za-z_][A-Za-z0-9_]*)\}/g, (_m, name) => {
|
|
59
|
+
const v = env?.[name];
|
|
60
|
+
return v == null ? '' : String(v);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (Array.isArray(value)) {
|
|
64
|
+
return value.map((v) => expandEnvVars(v, env));
|
|
65
|
+
}
|
|
66
|
+
if (value && typeof value === 'object') {
|
|
67
|
+
const out = {};
|
|
68
|
+
for (const [k, v] of Object.entries(value)) {
|
|
69
|
+
out[k] = expandEnvVars(v, env);
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Resolve the canonical transport kind for an MCP server config entry.
|
|
77
|
+
* Returns one of: 'autoDetect' | 'stdio' | 'http' | 'sse' | 'ws'.
|
|
78
|
+
* Throws when no transport can be determined.
|
|
79
|
+
*/
|
|
80
|
+
export function resolveMcpTransportKind(cfg) {
|
|
81
|
+
if (cfg?.autoDetect) return 'autoDetect';
|
|
82
|
+
if (cfg?.type != null && cfg.type !== '') {
|
|
83
|
+
let t = String(cfg.type).toLowerCase();
|
|
84
|
+
if (t === 'streamable-http' || t === 'streamablehttp') t = 'http';
|
|
85
|
+
if (t === 'stdio' || t === 'http' || t === 'sse' || t === 'ws') return t;
|
|
86
|
+
}
|
|
87
|
+
if (cfg?.transport === 'http') return 'http';
|
|
88
|
+
if (cfg?.command) return 'stdio';
|
|
89
|
+
if (cfg?.url) return 'http';
|
|
90
|
+
throw new Error(`Invalid config: need autoDetect, type (stdio/http/sse/ws), url (http), or command (stdio)`);
|
|
91
|
+
}
|
|
47
92
|
// --- Public API ---
|
|
48
93
|
/**
|
|
49
94
|
* Connect to MCP servers defined in config.
|
|
@@ -92,11 +137,13 @@ export function getMcpServerStatus() {
|
|
|
92
137
|
name: tool.name,
|
|
93
138
|
description: tool.description || '',
|
|
94
139
|
})),
|
|
95
|
-
transport:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
140
|
+
transport: (() => {
|
|
141
|
+
try {
|
|
142
|
+
return resolveMcpTransportKind(server.cfg);
|
|
143
|
+
} catch {
|
|
144
|
+
return 'stdio';
|
|
145
|
+
}
|
|
146
|
+
})(),
|
|
100
147
|
}));
|
|
101
148
|
}
|
|
102
149
|
/**
|
|
@@ -275,11 +322,18 @@ export async function disconnectAll() {
|
|
|
275
322
|
_invalidateMcpToolFieldMemo();
|
|
276
323
|
}
|
|
277
324
|
async function connectServer(name, cfg) {
|
|
278
|
-
const {
|
|
325
|
+
const {
|
|
326
|
+
Client,
|
|
327
|
+
StdioClientTransport,
|
|
328
|
+
StreamableHTTPClientTransport,
|
|
329
|
+
SSEClientTransport,
|
|
330
|
+
WebSocketClientTransport,
|
|
331
|
+
} = await loadMcpSdk();
|
|
279
332
|
const client = new Client({ name: `mixdog-agent/${name}`, version: '1.0.0' });
|
|
280
333
|
let transport;
|
|
334
|
+
const kind = resolveMcpTransportKind(cfg);
|
|
281
335
|
// Auto-detect: read port from a running service's port file
|
|
282
|
-
if (
|
|
336
|
+
if (kind === 'autoDetect') {
|
|
283
337
|
const spec = AUTO_DETECT_PORTS[cfg.autoDetect];
|
|
284
338
|
if (!spec)
|
|
285
339
|
throw new Error(`Unknown autoDetect target: "${cfg.autoDetect}"`);
|
|
@@ -314,16 +368,40 @@ async function connectServer(name, cfg) {
|
|
|
314
368
|
transport = new StreamableHTTPClientTransport(new URL(url));
|
|
315
369
|
mcpLog(`[mcp-client] Connecting "${name}" via autoDetect HTTP: ${url}\n`);
|
|
316
370
|
}
|
|
317
|
-
else if (
|
|
318
|
-
|
|
319
|
-
|
|
371
|
+
else if (kind === 'http') {
|
|
372
|
+
const url = expandEnvVars(String(cfg.url ?? ''));
|
|
373
|
+
const headers = expandEnvVars(cfg.headers && typeof cfg.headers === 'object' ? cfg.headers : {});
|
|
374
|
+
const opts = (headers && Object.keys(headers).length > 0)
|
|
375
|
+
? { requestInit: { headers } }
|
|
376
|
+
: undefined;
|
|
377
|
+
transport = opts
|
|
378
|
+
? new StreamableHTTPClientTransport(new URL(url), opts)
|
|
379
|
+
: new StreamableHTTPClientTransport(new URL(url));
|
|
380
|
+
mcpLog(`[mcp-client] Connecting "${name}" via HTTP: ${url}\n`);
|
|
381
|
+
}
|
|
382
|
+
else if (kind === 'sse') {
|
|
383
|
+
const url = expandEnvVars(String(cfg.url ?? ''));
|
|
384
|
+
const headers = expandEnvVars(cfg.headers && typeof cfg.headers === 'object' ? cfg.headers : {});
|
|
385
|
+
const opts = (headers && Object.keys(headers).length > 0)
|
|
386
|
+
? { requestInit: { headers } }
|
|
387
|
+
: undefined;
|
|
388
|
+
transport = opts
|
|
389
|
+
? new SSEClientTransport(new URL(url), opts)
|
|
390
|
+
: new SSEClientTransport(new URL(url));
|
|
391
|
+
mcpLog(`[mcp-client] Connecting "${name}" via SSE: ${url}\n`);
|
|
392
|
+
}
|
|
393
|
+
else if (kind === 'ws') {
|
|
394
|
+
// WebSocketClientTransport ctor takes only a URL; headers are ignored.
|
|
395
|
+
const url = expandEnvVars(String(cfg.url ?? ''));
|
|
396
|
+
transport = new WebSocketClientTransport(new URL(url));
|
|
397
|
+
mcpLog(`[mcp-client] Connecting "${name}" via WebSocket: ${url}\n`);
|
|
320
398
|
}
|
|
321
|
-
else if (
|
|
399
|
+
else if (kind === 'stdio') {
|
|
322
400
|
transport = new StdioClientTransport({
|
|
323
|
-
command: cfg.command,
|
|
324
|
-
args: cfg.args,
|
|
401
|
+
command: expandEnvVars(String(cfg.command ?? '')),
|
|
402
|
+
args: Array.isArray(cfg.args) ? expandEnvVars(cfg.args) : cfg.args,
|
|
325
403
|
cwd: cfg.cwd,
|
|
326
|
-
env: { ...process.env, ...cfg.env },
|
|
404
|
+
env: { ...process.env, ...expandEnvVars(cfg.env && typeof cfg.env === 'object' ? cfg.env : {}) },
|
|
327
405
|
stderr: cfg.stderr ?? 'pipe',
|
|
328
406
|
});
|
|
329
407
|
transport.stderr?.on?.('data', (chunk) => {
|
|
@@ -331,7 +409,7 @@ async function connectServer(name, cfg) {
|
|
|
331
409
|
});
|
|
332
410
|
}
|
|
333
411
|
else {
|
|
334
|
-
throw new Error(`Invalid config for "${name}": need autoDetect, url (http), or command (stdio)`);
|
|
412
|
+
throw new Error(`Invalid config for "${name}": need autoDetect, type (stdio/http/sse/ws), url (http), or command (stdio)`);
|
|
335
413
|
}
|
|
336
414
|
await client.connect(transport);
|
|
337
415
|
const toolsResult = await client.listTools();
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { EFFORT_BETA_HEADER } from './anthropic-effort.mjs';
|
|
2
|
+
|
|
1
3
|
export const EXTENDED_CACHE_TTL_BETA_HEADER = 'extended-cache-ttl-2025-04-11';
|
|
2
4
|
export const INTERLEAVED_THINKING_BETA_HEADER = 'interleaved-thinking-2025-05-14';
|
|
3
5
|
export const FAST_MODE_BETA_HEADER = 'fast-mode-2026-02-01';
|
|
4
6
|
export const TOOL_SEARCH_BETA_HEADER = 'advanced-tool-use-2025-11-20';
|
|
7
|
+
export { EFFORT_BETA_HEADER };
|
|
5
8
|
|
|
6
9
|
export function supportsAnthropicFastMode(model) {
|
|
7
10
|
const id = String(model || '').toLowerCase().replace(/\./g, '-');
|
|
@@ -12,6 +15,7 @@ export function buildAnthropicBetaHeaders({
|
|
|
12
15
|
base = `${INTERLEAVED_THINKING_BETA_HEADER},${EXTENDED_CACHE_TTL_BETA_HEADER}`,
|
|
13
16
|
fastMode = false,
|
|
14
17
|
toolSearch = false,
|
|
18
|
+
effort = false,
|
|
15
19
|
} = {}) {
|
|
16
20
|
const headers = String(base || '')
|
|
17
21
|
.split(',')
|
|
@@ -23,5 +27,8 @@ export function buildAnthropicBetaHeaders({
|
|
|
23
27
|
if (toolSearch && !headers.includes(TOOL_SEARCH_BETA_HEADER)) {
|
|
24
28
|
headers.push(TOOL_SEARCH_BETA_HEADER);
|
|
25
29
|
}
|
|
30
|
+
if (effort && !headers.includes(EFFORT_BETA_HEADER)) {
|
|
31
|
+
headers.push(EFFORT_BETA_HEADER);
|
|
32
|
+
}
|
|
26
33
|
return headers.join(',');
|
|
27
34
|
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Anthropic effort API alignment (Claude Code effort.ts + configureEffortParams).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const EFFORT_LEVELS = Object.freeze(['low', 'medium', 'high', 'max']);
|
|
6
|
+
|
|
7
|
+
export const EFFORT_BETA_HEADER = 'effort-2025-11-24';
|
|
8
|
+
|
|
9
|
+
export const LEGACY_EFFORT_BUDGET = Object.freeze({
|
|
10
|
+
low: 1024,
|
|
11
|
+
medium: 4096,
|
|
12
|
+
high: 16384,
|
|
13
|
+
max: 32768,
|
|
14
|
+
});
|
|
15
|
+
const _LOGGED_EFFORT_NORMALIZATION = new Set();
|
|
16
|
+
const _LOGGED_UNKNOWN_EFFORT = new Set();
|
|
17
|
+
|
|
18
|
+
function normalizeModelId(model) {
|
|
19
|
+
return String(model || '').toLowerCase().replace(/\./g, '-');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseClaudeVersion(model) {
|
|
23
|
+
const m = normalizeModelId(model);
|
|
24
|
+
const triple = m.match(/^claude-(haiku|sonnet|opus|fable)-(\d+)-(\d+)/);
|
|
25
|
+
if (triple) {
|
|
26
|
+
return { family: triple[1], major: Number(triple[2]), minor: Number(triple[3]) };
|
|
27
|
+
}
|
|
28
|
+
const pair = m.match(/^claude-(sonnet|fable)-(\d+)(?:$|[-@])/);
|
|
29
|
+
if (pair) {
|
|
30
|
+
return { family: pair[1], major: Number(pair[2]), minor: null };
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isLegacyAnthropicReasoningModel(model) {
|
|
36
|
+
const parsed = parseClaudeVersion(model);
|
|
37
|
+
if (!parsed) return false;
|
|
38
|
+
if (parsed.family === 'haiku') return true;
|
|
39
|
+
if (parsed.family === 'fable') return false;
|
|
40
|
+
if (parsed.family === 'sonnet' || parsed.family === 'opus') {
|
|
41
|
+
if (parsed.major < 4) return true;
|
|
42
|
+
if (parsed.major === 4 && parsed.minor !== null && parsed.minor < 6) return true;
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// @[MODEL LAUNCH]: extend allowlist when new Claude models ship with effort support.
|
|
49
|
+
export function modelSupportsEffort(model) {
|
|
50
|
+
if (isEnvTruthy(process.env.MIXDOG_ANTHROPIC_ALWAYS_ENABLE_EFFORT)) return true;
|
|
51
|
+
const m = normalizeModelId(model);
|
|
52
|
+
if (!m.includes('claude')) return false;
|
|
53
|
+
if (isLegacyAnthropicReasoningModel(model)) return false;
|
|
54
|
+
if (m.includes('opus-4-6') || m.includes('sonnet-4-6')) return true;
|
|
55
|
+
if (m.includes('sonnet-5') || m.includes('fable-5')) return true;
|
|
56
|
+
if (/^claude-opus-4-(6|7|8)(?:$|[-@])/.test(m)) return true;
|
|
57
|
+
if (/^claude-opus-5-/.test(m)) return true;
|
|
58
|
+
if (m.startsWith('claude-')) return true;
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// @[MODEL LAUNCH]: extend when new Opus models support max effort.
|
|
63
|
+
export function modelSupportsMaxEffort(model) {
|
|
64
|
+
const m = normalizeModelId(model);
|
|
65
|
+
if (/^claude-opus-4-(6|7|8)(?:$|[-@])/.test(m)) return true;
|
|
66
|
+
if (/^claude-opus-5-/.test(m)) return true;
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isEnvTruthy(value) {
|
|
71
|
+
const s = String(value ?? '').trim().toLowerCase();
|
|
72
|
+
return s === '1' || s === 'true' || s === 'yes' || s === 'on';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function logOnce(key, write) {
|
|
76
|
+
if (_LOGGED_EFFORT_NORMALIZATION.has(key)) return;
|
|
77
|
+
_LOGGED_EFFORT_NORMALIZATION.add(key);
|
|
78
|
+
try { write(); } catch { /* ignore */ }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Normalize user/config effort to API levels (low/medium/high/max).
|
|
83
|
+
* Maps legacy xhigh → max (Opus 4.6+) or high; clamps max on non-Opus models.
|
|
84
|
+
*/
|
|
85
|
+
export function normalizeAnthropicEffortInput(raw, model, logTag = 'anthropic') {
|
|
86
|
+
if (raw === undefined || raw === null || raw === '') return undefined;
|
|
87
|
+
let level = String(raw).trim().toLowerCase();
|
|
88
|
+
if (level === 'extra-high' || level === 'very-high') level = 'xhigh';
|
|
89
|
+
|
|
90
|
+
if (level === 'xhigh') {
|
|
91
|
+
const mapped = modelSupportsMaxEffort(model) ? 'max' : 'high';
|
|
92
|
+
logOnce(`xhigh:${mapped}`, () => {
|
|
93
|
+
process.stderr.write(
|
|
94
|
+
`[${logTag}] effort "xhigh" normalized to "${mapped}" (official levels: ${EFFORT_LEVELS.join(', ')})\n`,
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
return mapped;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (level === 'max' && !modelSupportsMaxEffort(model)) {
|
|
101
|
+
logOnce('max:high', () => {
|
|
102
|
+
process.stderr.write(`[${logTag}] effort "max" downgraded to "high" for model ${model}\n`);
|
|
103
|
+
});
|
|
104
|
+
return 'high';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (EFFORT_LEVELS.includes(level)) return level;
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function anthropicEffortUsesOutputConfig(model, opts = {}) {
|
|
112
|
+
const thinkingBudgetTokens = Number(opts.thinkingBudgetTokens);
|
|
113
|
+
if (Number.isFinite(thinkingBudgetTokens) && thinkingBudgetTokens > 0) return false;
|
|
114
|
+
return modelSupportsEffort(model);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function shouldIncludeEffortBeta(model, opts = {}) {
|
|
118
|
+
return anthropicEffortUsesOutputConfig(model, opts);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Apply effort / thinking to a Messages API body or SDK params object (mutates).
|
|
123
|
+
*/
|
|
124
|
+
export function applyAnthropicEffortToBody(
|
|
125
|
+
body,
|
|
126
|
+
{
|
|
127
|
+
model,
|
|
128
|
+
opts = {},
|
|
129
|
+
maxTokens,
|
|
130
|
+
clampThinkingBudgetTokens,
|
|
131
|
+
logTag = 'anthropic',
|
|
132
|
+
},
|
|
133
|
+
) {
|
|
134
|
+
if (!body || typeof body !== 'object') return;
|
|
135
|
+
|
|
136
|
+
const thinkingBudgetTokens = Number(opts.thinkingBudgetTokens);
|
|
137
|
+
if (Number.isFinite(thinkingBudgetTokens) && thinkingBudgetTokens > 0) {
|
|
138
|
+
const budgetTokens = clampThinkingBudgetTokens(thinkingBudgetTokens, maxTokens);
|
|
139
|
+
if (budgetTokens) {
|
|
140
|
+
body.thinking = { type: 'enabled', budget_tokens: budgetTokens };
|
|
141
|
+
}
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const normalized = normalizeAnthropicEffortInput(opts.effort, model, logTag);
|
|
146
|
+
|
|
147
|
+
if (modelSupportsEffort(model)) {
|
|
148
|
+
if (normalized) {
|
|
149
|
+
const existing = body.output_config && typeof body.output_config === 'object'
|
|
150
|
+
? body.output_config
|
|
151
|
+
: {};
|
|
152
|
+
body.output_config = { ...existing, effort: normalized };
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (normalized && LEGACY_EFFORT_BUDGET[normalized]) {
|
|
158
|
+
const budgetTokens = clampThinkingBudgetTokens(LEGACY_EFFORT_BUDGET[normalized], maxTokens);
|
|
159
|
+
if (budgetTokens) {
|
|
160
|
+
body.thinking = { type: 'enabled', budget_tokens: budgetTokens };
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (opts.effort && normalized === undefined && !_LOGGED_UNKNOWN_EFFORT.has(opts.effort)) {
|
|
166
|
+
_LOGGED_UNKNOWN_EFFORT.add(opts.effort);
|
|
167
|
+
try {
|
|
168
|
+
process.stderr.write(
|
|
169
|
+
`[${logTag}] unknown effort=${opts.effort} ignored (known legacy: ${Object.keys(LEGACY_EFFORT_BUDGET).join(', ')})\n`,
|
|
170
|
+
);
|
|
171
|
+
} catch { /* ignore */ }
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function effortValuesForModel(capabilities, modelId) {
|
|
176
|
+
const effort = capabilities?.effort;
|
|
177
|
+
let levels = [...EFFORT_LEVELS];
|
|
178
|
+
if (!modelSupportsMaxEffort(modelId)) {
|
|
179
|
+
levels = levels.filter((level) => level !== 'max');
|
|
180
|
+
}
|
|
181
|
+
if (!effort) return [];
|
|
182
|
+
if (effort === true) return levels;
|
|
183
|
+
const values = levels.filter(
|
|
184
|
+
(level) => effort?.[level] === true || effort?.[level]?.supported === true,
|
|
185
|
+
);
|
|
186
|
+
if (values.length) return values;
|
|
187
|
+
return effort.supported === true ? levels : [];
|
|
188
|
+
}
|