mixdog 0.9.151 → 0.9.152
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/README.md +110 -68
- package/package.json +3 -2
- package/scripts/tool-stress.mjs +0 -2
- package/src/defaults/skills/setup/SKILL.md +18 -27
- package/src/headless-exec.mjs +3 -10
- package/src/headless-exec.test.mjs +2 -7
- package/src/runtime/agent/orchestrator/config.mjs +6 -66
- package/src/runtime/agent/orchestrator/context/collect-skills.test.mjs +153 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +49 -45
- package/src/runtime/agent/orchestrator/mcp/client.mjs +242 -15
- package/src/runtime/agent/orchestrator/mcp/features.test.mjs +66 -0
- package/src/runtime/agent/orchestrator/mcp/security.test.mjs +36 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +54 -33
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +3 -8
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +10 -2
- package/src/runtime/agent/orchestrator/providers/lib/provider-replay.mjs +50 -0
- package/src/runtime/agent/orchestrator/providers/lib/sse-framing.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +3 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +25 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +35 -4
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +22 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +0 -2
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +4 -7
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/provider-replay.test.mjs +235 -0
- package/src/runtime/agent/orchestrator/providers/sse-framing.test.mjs +719 -0
- package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +399 -39
- package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +26 -0
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +14 -4
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +24 -1
- package/src/runtime/agent/orchestrator/session/compaction-read-reset.test.mjs +161 -0
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +14 -4
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +12 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +31 -18
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/message-sanitize.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +6 -1
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +556 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.test.mjs +457 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +105 -186
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +115 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +23 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool-cwd-env.test.mjs +14 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -10
- package/src/runtime/agent/orchestrator/tools/builtin/find-search-budget.test.mjs +30 -14
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +15 -4
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +73 -15
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +76 -2
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +24 -683
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +64 -72
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-runner.mjs +2 -5
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.mjs +75 -0
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.test.mjs +24 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +12 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +24 -5
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.test.mjs +44 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +12 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +35 -21
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +28 -13
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.test.mjs +54 -0
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +11 -11
- package/src/runtime/browser-bridge/client.mjs +103 -0
- package/src/runtime/browser-bridge/tool-defs.mjs +42 -0
- package/src/runtime/computer-bridge/client.mjs +101 -0
- package/src/runtime/computer-bridge/tool-defs.mjs +53 -0
- package/src/runtime/media/store.mjs +18 -8
- package/src/runtime/media/store.test.mjs +14 -0
- package/src/runtime/memory/index.mjs +1 -1
- package/src/runtime/memory/lib/transcript-ingest.mjs +8 -6
- package/src/runtime/memory/lib/transcript-ingest.test.mjs +52 -0
- package/src/runtime/shared/child-spawn-gate.mjs +17 -0
- package/src/runtime/shared/child-spawn-remote.mjs +69 -7
- package/src/runtime/shared/child-spawn-remote.test.mjs +124 -0
- package/src/runtime/shared/pristine-execution-contract.json +2 -1
- package/src/runtime/shared/provider-api-key.mjs +1 -0
- package/src/runtime/shared/session-runtime-health.mjs +91 -0
- package/src/runtime/shared/session-runtime-health.test.mjs +53 -0
- package/src/runtime/shared/skill-document.mjs +86 -0
- package/src/runtime/shared/skill-document.test.mjs +81 -0
- package/src/runtime/shared/tool-surface.mjs +18 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +1 -0
- package/src/runtime/shared/turn-snapshot.mjs +32 -9
- package/src/runtime/shared/user-cwd.mjs +33 -14
- package/src/runtime/shared/user-cwd.test.mjs +43 -0
- package/src/session-runtime/cwd-plugins.mjs +26 -26
- package/src/session-runtime/cwd-plugins.test.mjs +75 -0
- package/src/session-runtime/cwd-tool-routing.test.mjs +5 -0
- package/src/session-runtime/global-extensions.mjs +26 -0
- package/src/session-runtime/global-extensions.test.mjs +28 -0
- package/src/session-runtime/goal-runtime.mjs +721 -0
- package/src/session-runtime/goal-runtime.test.mjs +108 -0
- package/src/session-runtime/lifecycle-api.mjs +18 -4
- package/src/session-runtime/lifecycle-api.test.mjs +21 -0
- package/src/session-runtime/mcp-glue.mjs +49 -47
- package/src/session-runtime/plugin-mcp-standard.test.mjs +177 -0
- package/src/session-runtime/plugin-mcp.mjs +96 -22
- package/src/session-runtime/prewarm.mjs +1 -10
- package/src/session-runtime/resource-api-global.test.mjs +69 -0
- package/src/session-runtime/resource-api.mjs +173 -58
- package/src/session-runtime/runtime-core.mjs +148 -20
- package/src/session-runtime/runtime-tunables.mjs +6 -8
- package/src/session-runtime/session-turn-api.mjs +22 -11
- package/src/session-runtime/skills-api.mjs +79 -25
- package/src/session-runtime/skills-api.test.mjs +99 -0
- package/src/session-runtime/tool-catalog-schema.mjs +0 -5
- package/src/session-runtime/tool-catalog.mjs +4 -13
- package/src/session-runtime/tool-policy-surface.test.mjs +33 -11
- package/src/session-runtime/tool-surface.mjs +12 -0
- package/src/standalone/agent-dispatch-broker.mjs +5 -9
- package/src/standalone/agent-tool/notify.mjs +6 -8
- package/src/standalone/agent-tool/notify.test.mjs +37 -0
- package/src/standalone/daemon.mjs +31 -15
- package/src/standalone/hook-bus/config.mjs +1 -0
- package/src/standalone/plugin-admin.mjs +17 -0
- package/src/standalone/plugin-admin.test.mjs +7 -0
- package/src/standalone/provider-admin.mjs +1 -0
- package/src/standalone/session-protocol.mjs +5 -0
- package/src/standalone/session-runtime-agent-control-client.mjs +133 -0
- package/src/standalone/session-runtime-agent-control-client.test.mjs +71 -0
- package/src/standalone/session-runtime-dispatch-cancel.test.mjs +139 -0
- package/src/standalone/session-runtime-host-factory.mjs +17 -0
- package/src/standalone/session-runtime-host-factory.test.mjs +31 -0
- package/src/standalone/session-runtime-host-health.test.mjs +5 -0
- package/src/standalone/session-runtime-host.mjs +696 -58
- package/src/standalone/session-runtime-inline-host.mjs +351 -0
- package/src/standalone/session-runtime-inline-host.test.mjs +83 -0
- package/src/standalone/session-runtime-provider-cooldown.mjs +78 -0
- package/src/standalone/session-runtime-provider-cooldown.test.mjs +80 -0
- package/src/standalone/session-runtime-shard-host.test.mjs +569 -0
- package/src/standalone/session-runtime-shard-router.mjs +149 -0
- package/src/standalone/session-runtime-shard-router.test.mjs +101 -0
- package/src/standalone/session-runtime-worker.mjs +476 -18
- package/src/standalone/session-service.mjs +81 -6
- package/src/tui/app/app-view.jsx +1 -0
- package/src/tui/app/core-memory-picker.mjs +2 -0
- package/src/tui/app/extension-pickers.mjs +1 -1
- package/src/tui/app/maintenance-pickers.mjs +3 -3
- package/src/tui/app/model-options.mjs +2 -0
- package/src/tui/app/model-picker.mjs +14 -4
- package/src/tui/app/panel-handoff.test.mjs +311 -0
- package/src/tui/app/project-picker.mjs +1 -0
- package/src/tui/app/route-pickers.mjs +21 -6
- package/src/tui/app/settings-picker.mjs +26 -3
- package/src/tui/app/slash-commands.mjs +1 -0
- package/src/tui/app/slash-dispatch.mjs +73 -22
- package/src/tui/app/theme-effort-pickers.mjs +7 -2
- package/src/tui/components/Picker.jsx +10 -1
- package/src/tui/dist/index.mjs +163 -44
- package/src/tui/session/context-state.mjs +1 -0
- package/src/tui/session/goal-continuation.mjs +110 -0
- package/src/tui/session/goal-continuation.test.mjs +84 -0
- package/src/tui/session/queue-helpers.mjs +5 -2
- package/src/tui/session/session-action-surface.test.mjs +108 -0
- package/src/tui/session/session-api-ext.mjs +39 -8
- package/src/tui/session/session-api.mjs +58 -0
- package/src/tui/session/session-flow.mjs +6 -0
- package/src/tui/session/turn.mjs +1 -0
- package/src/tui/session-local.mjs +15 -1
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +0 -301
|
@@ -133,28 +133,19 @@ export function createPrewarmSchedulers({
|
|
|
133
133
|
const start = () => void (async () => {
|
|
134
134
|
timers.searchRuntimeWarmupTimer = null;
|
|
135
135
|
if (isCloseRequested()) return;
|
|
136
|
-
const root = getCurrentCwd();
|
|
137
136
|
const nativeSearchWarm = (async () => {
|
|
138
137
|
const { warmNativeSearchServer } = await import('../runtime/agent/orchestrator/tools/builtin/native-search-client.mjs');
|
|
139
138
|
bootProfile('native-search:warm', { up: await warmNativeSearchServer() === true });
|
|
140
139
|
})().catch((error) => {
|
|
141
140
|
bootProfile('native-search:warm-failed', { error: error?.message || String(error) });
|
|
142
141
|
});
|
|
143
|
-
const findIndexWarm = (async () => {
|
|
144
|
-
const { prewarmFindEnumeration } = await import('../runtime/agent/orchestrator/tools/builtin/list-tool.mjs');
|
|
145
|
-
bootProfile('find-index:prewarm-scheduled', { cwd: root });
|
|
146
|
-
await prewarmFindEnumeration(root);
|
|
147
|
-
bootProfile('find-index:prewarm-complete', { cwd: root });
|
|
148
|
-
})().catch((error) => {
|
|
149
|
-
bootProfile('find-index:prewarm-failed', { error: error?.message || String(error) });
|
|
150
|
-
});
|
|
151
142
|
const nativeSpawnWarm = (async () => {
|
|
152
143
|
const { warmNativeSpawnServer } = await import('../runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs');
|
|
153
144
|
bootProfile('native-spawn:warm', { up: await warmNativeSpawnServer() === true });
|
|
154
145
|
})().catch((error) => {
|
|
155
146
|
bootProfile('native-spawn:warm-failed', { error: error?.message || String(error) });
|
|
156
147
|
});
|
|
157
|
-
await Promise.all([nativeSearchWarm,
|
|
148
|
+
await Promise.all([nativeSearchWarm, nativeSpawnWarm]);
|
|
158
149
|
});
|
|
159
150
|
if (delayMs <= 0) {
|
|
160
151
|
start();
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { createResourceApi } from './resource-api.mjs';
|
|
5
|
+
|
|
6
|
+
function resourceApi(overrides = {}) {
|
|
7
|
+
return createResourceApi({
|
|
8
|
+
getConfig: () => ({}),
|
|
9
|
+
getSession: () => null,
|
|
10
|
+
getCurrentCwd: () => process.cwd(),
|
|
11
|
+
cfgMod: {},
|
|
12
|
+
mgr: {},
|
|
13
|
+
hooks: {},
|
|
14
|
+
saveConfigAndAdopt: () => {},
|
|
15
|
+
connectConfiguredMcp: async () => ({ servers: [] }),
|
|
16
|
+
invalidatePreSessionToolSurface: () => {},
|
|
17
|
+
recreateCurrentSessionIfReady: async () => {},
|
|
18
|
+
normalizeMcpServerInput: () => {
|
|
19
|
+
throw new Error('not used');
|
|
20
|
+
},
|
|
21
|
+
mcpStatus: () => ({ servers: [] }),
|
|
22
|
+
skillsStatus: () => ({ skills: [] }),
|
|
23
|
+
pluginsStatus: () => ({ plugins: [] }),
|
|
24
|
+
reloadFullConfig: () => {},
|
|
25
|
+
getActiveTurnCount: () => 0,
|
|
26
|
+
...overrides,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
test('a global MCP toggle reloads and reconnects peer session runtimes', async () => {
|
|
31
|
+
let persisted = {
|
|
32
|
+
mcpServers: {
|
|
33
|
+
demo: { type: 'stdio', command: 'demo', enabled: true },
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
let writerConfig = structuredClone(persisted);
|
|
37
|
+
let peerConfig = structuredClone(persisted);
|
|
38
|
+
let peerReloads = 0;
|
|
39
|
+
let peerConnects = 0;
|
|
40
|
+
const writer = resourceApi({
|
|
41
|
+
getConfig: () => writerConfig,
|
|
42
|
+
saveConfigAndAdopt: (next) => {
|
|
43
|
+
writerConfig = structuredClone(next);
|
|
44
|
+
persisted = structuredClone(next);
|
|
45
|
+
},
|
|
46
|
+
mcpStatus: () => ({ servers: [{ name: 'demo', source: 'config' }] }),
|
|
47
|
+
});
|
|
48
|
+
const peer = resourceApi({
|
|
49
|
+
getConfig: () => peerConfig,
|
|
50
|
+
reloadFullConfig: () => {
|
|
51
|
+
peerReloads += 1;
|
|
52
|
+
peerConfig = structuredClone(persisted);
|
|
53
|
+
},
|
|
54
|
+
connectConfiguredMcp: async () => {
|
|
55
|
+
peerConnects += 1;
|
|
56
|
+
return { servers: [] };
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
try {
|
|
60
|
+
await writer.setMcpServerEnabled('demo', false);
|
|
61
|
+
assert.equal(writerConfig.mcpServers.demo.enabled, false);
|
|
62
|
+
assert.equal(peerConfig.mcpServers.demo.enabled, false);
|
|
63
|
+
assert.equal(peerReloads, 1);
|
|
64
|
+
assert.equal(peerConnects, 1);
|
|
65
|
+
} finally {
|
|
66
|
+
writer.disposeGlobalExtensionSubscription();
|
|
67
|
+
peer.disposeGlobalExtensionSubscription();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
@@ -7,9 +7,14 @@ import {
|
|
|
7
7
|
currentSessionRecallRows,
|
|
8
8
|
tombstoneOnClose,
|
|
9
9
|
} from './session-text.mjs';
|
|
10
|
+
import {
|
|
11
|
+
publishGlobalExtensionChange,
|
|
12
|
+
subscribeGlobalExtensionChanges,
|
|
13
|
+
} from './global-extensions.mjs';
|
|
10
14
|
import {
|
|
11
15
|
addPlugin as registryAddPlugin,
|
|
12
16
|
removePlugin as registryRemovePlugin,
|
|
17
|
+
setPluginEnabled as registrySetPluginEnabled,
|
|
13
18
|
updatePlugin as registryUpdatePlugin,
|
|
14
19
|
} from '../standalone/plugin-admin.mjs';
|
|
15
20
|
import {
|
|
@@ -18,10 +23,8 @@ import {
|
|
|
18
23
|
pluginRawMcpServers,
|
|
19
24
|
pluginMcpEnableScript,
|
|
20
25
|
resolveContainedPluginPath,
|
|
21
|
-
|
|
22
|
-
normalizeMcpProjectPathKey,
|
|
26
|
+
mergeMcpServerConfig,
|
|
23
27
|
} from './plugin-mcp.mjs';
|
|
24
|
-
import { invalidateProjectMcpCache } from './mcp-glue.mjs';
|
|
25
28
|
|
|
26
29
|
// MCP servers, skills, plugins, hooks, and memory/recall surfaces. Extracted
|
|
27
30
|
// verbatim from the runtime API object; stateless helpers are imported directly
|
|
@@ -31,9 +34,10 @@ export function createResourceApi(deps) {
|
|
|
31
34
|
getConfig, getSession, getCurrentCwd,
|
|
32
35
|
cfgMod, mgr, hooks, STANDALONE_DATA_DIR,
|
|
33
36
|
saveConfigAndAdopt, connectConfiguredMcp, invalidatePreSessionToolSurface,
|
|
34
|
-
recreateCurrentSessionIfReady, normalizeMcpServerInput, mcpStatus,
|
|
35
|
-
skillsStatus, skillContent,
|
|
36
|
-
|
|
37
|
+
recreateCurrentSessionIfReady, normalizeMcpServerInput, mcpStatus, getMcpServerConfig,
|
|
38
|
+
skillsStatus, skillContent, addGlobalSkill, saveSkillDocument, invalidateSkills,
|
|
39
|
+
getDisabledSkills, setDisabledSkills, pluginsStatus, getMemoryModule,
|
|
40
|
+
reloadFullConfig, flushSkillsSave, awaitKeychainPrewarm, getActiveTurnCount,
|
|
37
41
|
} = deps;
|
|
38
42
|
// Per-server MCP toggle serialization. The synchronous config adopt in
|
|
39
43
|
// setMcpServerEnabled has already made the intent durable; the heavy
|
|
@@ -42,6 +46,39 @@ export function createResourceApi(deps) {
|
|
|
42
46
|
// update `desired` and ride the in-flight chain so it converges to the last
|
|
43
47
|
// requested state, closing/recreating the session only once at the end.
|
|
44
48
|
const mcpToggleChains = new Map(); // name -> { desired, running }
|
|
49
|
+
let globalRefreshTimer = null;
|
|
50
|
+
function applyGlobalExtensionRecreate(kind) {
|
|
51
|
+
if (typeof getActiveTurnCount === 'function' && getActiveTurnCount() > 0) {
|
|
52
|
+
if (!globalRefreshTimer) {
|
|
53
|
+
globalRefreshTimer = setTimeout(() => {
|
|
54
|
+
globalRefreshTimer = null;
|
|
55
|
+
applyGlobalExtensionRecreate(kind);
|
|
56
|
+
}, 250);
|
|
57
|
+
globalRefreshTimer.unref?.();
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
invalidatePreSessionToolSurface();
|
|
62
|
+
const session = getSession();
|
|
63
|
+
if (session?.id) {
|
|
64
|
+
mgr.closeSession(session.id, `global-${kind}-refresh`, { tombstone: tombstoneOnClose(session) });
|
|
65
|
+
}
|
|
66
|
+
void recreateCurrentSessionIfReady().catch((err) => {
|
|
67
|
+
process.stderr.write(`[extensions] session recreate failed: ${err?.message || err}\n`);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
async function refreshGlobalExtensionState(kind) {
|
|
71
|
+
reloadFullConfig?.();
|
|
72
|
+
if (kind === 'skills' || kind === 'plugins') invalidateSkills?.();
|
|
73
|
+
if (kind === 'mcp' || kind === 'plugins') {
|
|
74
|
+
await connectConfiguredMcp({ reset: true });
|
|
75
|
+
}
|
|
76
|
+
applyGlobalExtensionRecreate(kind);
|
|
77
|
+
}
|
|
78
|
+
const globalExtensionSubscription = subscribeGlobalExtensionChanges(refreshGlobalExtensionState);
|
|
79
|
+
const publishGlobalChange = (kind) => (
|
|
80
|
+
publishGlobalExtensionChange(kind, globalExtensionSubscription.id)
|
|
81
|
+
);
|
|
45
82
|
// Close/recreate the live session only at a turn boundary: a background
|
|
46
83
|
// toggle must never abort an in-flight turn. If a turn is active, poll until
|
|
47
84
|
// it ends, then swap the session so it picks up the new tool surface.
|
|
@@ -105,6 +142,9 @@ export function createResourceApi(deps) {
|
|
|
105
142
|
mcpStatus() {
|
|
106
143
|
return mcpStatus();
|
|
107
144
|
},
|
|
145
|
+
getMcpServerConfig(name) {
|
|
146
|
+
return getMcpServerConfig(name);
|
|
147
|
+
},
|
|
108
148
|
async reconnectMcp() {
|
|
109
149
|
await awaitKeychainPrewarm();
|
|
110
150
|
reloadFullConfig();
|
|
@@ -113,11 +153,13 @@ export function createResourceApi(deps) {
|
|
|
113
153
|
const session = getSession();
|
|
114
154
|
if (session?.id) mgr.closeSession(session.id, 'cli-mcp-reconnect', { tombstone: tombstoneOnClose(session) });
|
|
115
155
|
await recreateCurrentSessionIfReady();
|
|
156
|
+
await publishGlobalChange('mcp');
|
|
116
157
|
return status;
|
|
117
158
|
},
|
|
118
159
|
async addMcpServer(input = {}) {
|
|
119
160
|
const { name, config: serverConfig } = normalizeMcpServerInput(input);
|
|
120
161
|
const nextConfig = { ...getConfig() };
|
|
162
|
+
delete nextConfig.mcpProjectOverrides;
|
|
121
163
|
nextConfig.mcpServers = {
|
|
122
164
|
...(nextConfig.mcpServers || {}),
|
|
123
165
|
[name]: serverConfig,
|
|
@@ -128,8 +170,40 @@ export function createResourceApi(deps) {
|
|
|
128
170
|
const session = getSession();
|
|
129
171
|
if (session?.id) mgr.closeSession(session.id, 'cli-mcp-add', { tombstone: tombstoneOnClose(session) });
|
|
130
172
|
await recreateCurrentSessionIfReady();
|
|
173
|
+
await publishGlobalChange('mcp');
|
|
131
174
|
return { name, status };
|
|
132
175
|
},
|
|
176
|
+
async saveMcpServer(input = {}) {
|
|
177
|
+
const originalName = clean(input.originalName);
|
|
178
|
+
const normalizedInput = normalizeMcpServerInput(input);
|
|
179
|
+
const name = originalName && clean(input.name) === originalName
|
|
180
|
+
? originalName
|
|
181
|
+
: normalizedInput.name;
|
|
182
|
+
const normalized = normalizedInput.config;
|
|
183
|
+
const nextConfig = { ...getConfig() };
|
|
184
|
+
delete nextConfig.mcpProjectOverrides;
|
|
185
|
+
const servers = nextConfig.mcpServers && typeof nextConfig.mcpServers === 'object'
|
|
186
|
+
? { ...nextConfig.mcpServers }
|
|
187
|
+
: {};
|
|
188
|
+
if (originalName && !Object.prototype.hasOwnProperty.call(servers, originalName)) {
|
|
189
|
+
throw new Error(`MCP server not configured: ${originalName}`);
|
|
190
|
+
}
|
|
191
|
+
if (name !== originalName && Object.prototype.hasOwnProperty.call(servers, name)) {
|
|
192
|
+
throw new Error(`MCP server already exists: ${name}`);
|
|
193
|
+
}
|
|
194
|
+
const existing = originalName ? servers[originalName] : {};
|
|
195
|
+
if (originalName && originalName !== name) delete servers[originalName];
|
|
196
|
+
servers[name] = mergeMcpServerConfig(existing, normalized);
|
|
197
|
+
nextConfig.mcpServers = servers;
|
|
198
|
+
saveConfigAndAdopt(nextConfig);
|
|
199
|
+
const status = await connectConfiguredMcp({ reset: true });
|
|
200
|
+
invalidatePreSessionToolSurface();
|
|
201
|
+
const session = getSession();
|
|
202
|
+
if (session?.id) mgr.closeSession(session.id, 'cli-mcp-save', { tombstone: tombstoneOnClose(session) });
|
|
203
|
+
await recreateCurrentSessionIfReady();
|
|
204
|
+
await publishGlobalChange('mcp');
|
|
205
|
+
return { name, source: 'config', status };
|
|
206
|
+
},
|
|
133
207
|
async removeMcpServer(name) {
|
|
134
208
|
const serverName = clean(name);
|
|
135
209
|
if (!serverName) throw new Error('MCP server name is required');
|
|
@@ -141,73 +215,36 @@ export function createResourceApi(deps) {
|
|
|
141
215
|
throw new Error(`MCP server not configured: ${serverName}`);
|
|
142
216
|
}
|
|
143
217
|
delete current[serverName];
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
: {};
|
|
147
|
-
const mcpProjectOverrides = {};
|
|
148
|
-
for (const [projectKey, serverOverrides] of Object.entries(currentOverrides)) {
|
|
149
|
-
if (!serverOverrides || typeof serverOverrides !== 'object' || Array.isArray(serverOverrides)) continue;
|
|
150
|
-
const nextServerOverrides = { ...serverOverrides };
|
|
151
|
-
delete nextServerOverrides[serverName];
|
|
152
|
-
if (Object.keys(nextServerOverrides).length > 0) {
|
|
153
|
-
mcpProjectOverrides[projectKey] = nextServerOverrides;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
saveConfigAndAdopt({ ...nextConfig, mcpServers: current, mcpProjectOverrides });
|
|
218
|
+
delete nextConfig.mcpProjectOverrides;
|
|
219
|
+
saveConfigAndAdopt({ ...nextConfig, mcpServers: current });
|
|
157
220
|
const status = await connectConfiguredMcp({ reset: true });
|
|
158
221
|
invalidatePreSessionToolSurface();
|
|
159
222
|
const session = getSession();
|
|
160
223
|
if (session?.id) mgr.closeSession(session.id, 'cli-mcp-remove', { tombstone: tombstoneOnClose(session) });
|
|
161
224
|
await recreateCurrentSessionIfReady();
|
|
225
|
+
await publishGlobalChange('mcp');
|
|
162
226
|
return status;
|
|
163
227
|
},
|
|
164
|
-
setMcpServerEnabled(name, enabled) {
|
|
228
|
+
async setMcpServerEnabled(name, enabled) {
|
|
165
229
|
const serverName = clean(name);
|
|
166
230
|
if (!serverName) throw new Error('MCP server name is required');
|
|
167
231
|
const want = enabled !== false;
|
|
168
|
-
// A project-local `.mcp.json` entry WINS over config for this name, so the
|
|
169
|
-
// durable toggle must land in whichever file actually drives the server.
|
|
170
|
-
// For project-sourced servers, persist the `enabled` flag into `.mcp.json`
|
|
171
|
-
// then explicitly invalidate the project cache (mtime granularity is not
|
|
172
|
-
// reliable for same-tick writes), before running the same
|
|
173
|
-
// background connect/recreate chain used for config servers.
|
|
174
232
|
const shadowRow = mcpStatus().servers.find((s) => s.name === serverName);
|
|
175
|
-
if (shadowRow
|
|
176
|
-
setProjectMcpServerEnabled(getCurrentCwd(), serverName, want);
|
|
177
|
-
invalidateProjectMcpCache(getCurrentCwd());
|
|
178
|
-
return scheduleMcpToggle(serverName, want);
|
|
179
|
-
}
|
|
233
|
+
if (!shadowRow) throw new Error(`MCP server not configured: ${serverName}`);
|
|
180
234
|
const nextConfig = { ...getConfig() };
|
|
181
|
-
const
|
|
235
|
+
const servers = nextConfig.mcpServers && typeof nextConfig.mcpServers === 'object'
|
|
182
236
|
? { ...nextConfig.mcpServers }
|
|
183
237
|
: {};
|
|
184
|
-
|
|
238
|
+
const current = servers[serverName];
|
|
239
|
+
if (!current || typeof current !== 'object' || Array.isArray(current)) {
|
|
185
240
|
throw new Error(`MCP server not configured: ${serverName}`);
|
|
186
241
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const currentOverrides = nextConfig.mcpProjectOverrides && typeof nextConfig.mcpProjectOverrides === 'object'
|
|
194
|
-
? nextConfig.mcpProjectOverrides
|
|
195
|
-
: {};
|
|
196
|
-
const projectOverrides = currentOverrides[projectKey] && typeof currentOverrides[projectKey] === 'object'
|
|
197
|
-
? currentOverrides[projectKey]
|
|
198
|
-
: {};
|
|
199
|
-
cfgMod.markMcpProjectOverrideDirty(projectKey, serverName, want);
|
|
200
|
-
saveConfigAndAdopt({
|
|
201
|
-
...nextConfig,
|
|
202
|
-
mcpProjectOverrides: {
|
|
203
|
-
...currentOverrides,
|
|
204
|
-
[projectKey]: {
|
|
205
|
-
...projectOverrides,
|
|
206
|
-
[serverName]: { enabled: want },
|
|
207
|
-
},
|
|
208
|
-
},
|
|
209
|
-
});
|
|
210
|
-
return scheduleMcpToggle(serverName, want);
|
|
242
|
+
servers[serverName] = { ...current, enabled: want };
|
|
243
|
+
delete nextConfig.mcpProjectOverrides;
|
|
244
|
+
saveConfigAndAdopt({ ...nextConfig, mcpServers: servers });
|
|
245
|
+
const status = await scheduleMcpToggle(serverName, want);
|
|
246
|
+
await publishGlobalChange('mcp');
|
|
247
|
+
return status;
|
|
211
248
|
},
|
|
212
249
|
skillsStatus() {
|
|
213
250
|
return skillsStatus();
|
|
@@ -216,16 +253,44 @@ export function createResourceApi(deps) {
|
|
|
216
253
|
return skillContent(name);
|
|
217
254
|
},
|
|
218
255
|
async addSkill(input = {}) {
|
|
219
|
-
const skill =
|
|
256
|
+
const skill = addGlobalSkill(input);
|
|
257
|
+
invalidateSkills?.();
|
|
220
258
|
const session = getSession();
|
|
221
259
|
if (session?.id) mgr.closeSession(session.id, 'cli-skill-add', { tombstone: tombstoneOnClose(session) });
|
|
222
260
|
await recreateCurrentSessionIfReady();
|
|
261
|
+
await publishGlobalChange('skills');
|
|
223
262
|
return { skill, status: skillsStatus() };
|
|
224
263
|
},
|
|
264
|
+
async saveSkill(input = {}) {
|
|
265
|
+
const skill = saveSkillDocument(input);
|
|
266
|
+
if (skill.originalName !== skill.name) {
|
|
267
|
+
const disabled = getDisabledSkills?.().disabled;
|
|
268
|
+
if (Array.isArray(disabled) && disabled.includes(skill.originalName)) {
|
|
269
|
+
setDisabledSkills?.(disabled.map((name) =>
|
|
270
|
+
name === skill.originalName ? skill.name : name));
|
|
271
|
+
flushSkillsSave?.();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const session = getSession();
|
|
275
|
+
if (session?.id) mgr.closeSession(session.id, 'cli-skill-save', { tombstone: tombstoneOnClose(session) });
|
|
276
|
+
await recreateCurrentSessionIfReady();
|
|
277
|
+
await publishGlobalChange('skills');
|
|
278
|
+
return { skill, status: skillsStatus() };
|
|
279
|
+
},
|
|
280
|
+
async setDisabledSkills(names = []) {
|
|
281
|
+
const result = setDisabledSkills?.(names);
|
|
282
|
+
flushSkillsSave?.();
|
|
283
|
+
invalidateSkills?.();
|
|
284
|
+
applyGlobalExtensionRecreate('skills');
|
|
285
|
+
await publishGlobalChange('skills');
|
|
286
|
+
return result;
|
|
287
|
+
},
|
|
225
288
|
async reloadSkills() {
|
|
289
|
+
invalidateSkills?.();
|
|
226
290
|
const session = getSession();
|
|
227
291
|
if (session?.id) mgr.closeSession(session.id, 'cli-skills-reload', { tombstone: tombstoneOnClose(session) });
|
|
228
292
|
await recreateCurrentSessionIfReady();
|
|
293
|
+
await publishGlobalChange('skills');
|
|
229
294
|
return skillsStatus();
|
|
230
295
|
},
|
|
231
296
|
pluginsStatus() {
|
|
@@ -235,23 +300,63 @@ export function createResourceApi(deps) {
|
|
|
235
300
|
const session = getSession();
|
|
236
301
|
if (session?.id) mgr.closeSession(session.id, 'cli-plugins-reload', { tombstone: tombstoneOnClose(session) });
|
|
237
302
|
await recreateCurrentSessionIfReady();
|
|
303
|
+
await publishGlobalChange('plugins');
|
|
238
304
|
return pluginsStatus();
|
|
239
305
|
},
|
|
240
306
|
async addPlugin(source) {
|
|
241
307
|
const dataDir = cfgMod.getPluginData?.();
|
|
242
308
|
const plugin = registryAddPlugin(source, { dataDir });
|
|
309
|
+
invalidateSkills?.();
|
|
243
310
|
const session = getSession();
|
|
244
311
|
if (session?.id) mgr.closeSession(session.id, 'cli-plugin-add', { tombstone: tombstoneOnClose(session) });
|
|
245
312
|
await recreateCurrentSessionIfReady();
|
|
313
|
+
await publishGlobalChange('plugins');
|
|
246
314
|
return { plugin, status: pluginsStatus() };
|
|
247
315
|
},
|
|
248
316
|
async updatePlugin(plugin = {}) {
|
|
249
317
|
const key = clean(plugin.id || plugin.name || plugin);
|
|
250
318
|
const dataDir = cfgMod.getPluginData?.();
|
|
251
319
|
const updated = registryUpdatePlugin(key, { dataDir });
|
|
320
|
+
invalidateSkills?.();
|
|
252
321
|
const session = getSession();
|
|
253
322
|
if (session?.id) mgr.closeSession(session.id, 'cli-plugin-update', { tombstone: tombstoneOnClose(session) });
|
|
254
323
|
await recreateCurrentSessionIfReady();
|
|
324
|
+
await publishGlobalChange('plugins');
|
|
325
|
+
return { plugin: updated, status: pluginsStatus() };
|
|
326
|
+
},
|
|
327
|
+
async setPluginEnabled(plugin = {}, enabled = true) {
|
|
328
|
+
const key = clean(plugin.id || plugin.name || plugin);
|
|
329
|
+
const dataDir = cfgMod.getPluginData?.();
|
|
330
|
+
const updated = registrySetPluginEnabled(key, enabled, { dataDir });
|
|
331
|
+
const nextConfig = { ...getConfig() };
|
|
332
|
+
const serverName = pluginMcpServerName(plugin);
|
|
333
|
+
const prefix = `${serverName}--`;
|
|
334
|
+
let changedMcp = false;
|
|
335
|
+
const mcpServers = {};
|
|
336
|
+
for (const [name, value] of Object.entries(nextConfig.mcpServers || {})) {
|
|
337
|
+
const config = value && typeof value === 'object' && !Array.isArray(value)
|
|
338
|
+
? { ...value }
|
|
339
|
+
: value;
|
|
340
|
+
if (name === serverName || name.startsWith(prefix)) {
|
|
341
|
+
changedMcp = true;
|
|
342
|
+
if (config && typeof config === 'object' && !Array.isArray(config)) {
|
|
343
|
+
if (enabled === false) config._mixdogPluginDisabled = true;
|
|
344
|
+
else delete config._mixdogPluginDisabled;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
mcpServers[name] = config;
|
|
348
|
+
}
|
|
349
|
+
if (changedMcp) {
|
|
350
|
+
nextConfig.mcpServers = mcpServers;
|
|
351
|
+
saveConfigAndAdopt(nextConfig);
|
|
352
|
+
await connectConfiguredMcp({ reset: true });
|
|
353
|
+
invalidatePreSessionToolSurface();
|
|
354
|
+
}
|
|
355
|
+
invalidateSkills?.();
|
|
356
|
+
const session = getSession();
|
|
357
|
+
if (session?.id) mgr.closeSession(session.id, 'cli-plugin-toggle', { tombstone: tombstoneOnClose(session) });
|
|
358
|
+
await recreateCurrentSessionIfReady();
|
|
359
|
+
await publishGlobalChange('plugins');
|
|
255
360
|
return { plugin: updated, status: pluginsStatus() };
|
|
256
361
|
},
|
|
257
362
|
async removePlugin(plugin = {}) {
|
|
@@ -273,9 +378,11 @@ export function createResourceApi(deps) {
|
|
|
273
378
|
await connectConfiguredMcp({ reset: true });
|
|
274
379
|
invalidatePreSessionToolSurface();
|
|
275
380
|
}
|
|
381
|
+
invalidateSkills?.();
|
|
276
382
|
const session = getSession();
|
|
277
383
|
if (session?.id) mgr.closeSession(session.id, 'cli-plugin-remove', { tombstone: tombstoneOnClose(session) });
|
|
278
384
|
await recreateCurrentSessionIfReady();
|
|
385
|
+
await publishGlobalChange('plugins');
|
|
279
386
|
return { plugin: removed, status: pluginsStatus() };
|
|
280
387
|
},
|
|
281
388
|
async enablePluginMcp(plugin = {}) {
|
|
@@ -304,6 +411,7 @@ export function createResourceApi(deps) {
|
|
|
304
411
|
MIXDOG_PLUGIN_ROOT: root,
|
|
305
412
|
MIXDOG_PLUGIN_DATA: join(cfgMod.getPluginData?.() || STANDALONE_DATA_DIR, 'plugins', 'data', clean(plugin.id || plugin.name || serverName)),
|
|
306
413
|
};
|
|
414
|
+
if (plugin.enabled === false) cfg._mixdogPluginDisabled = true;
|
|
307
415
|
const key = keys.length === 1 ? serverName : `${serverName}--${serverKey}`;
|
|
308
416
|
nextServers[key] = cfg;
|
|
309
417
|
}
|
|
@@ -321,6 +429,7 @@ export function createResourceApi(deps) {
|
|
|
321
429
|
MIXDOG_PLUGIN_ROOT: root,
|
|
322
430
|
MIXDOG_PLUGIN_DATA: join(cfgMod.getPluginData?.() || STANDALONE_DATA_DIR, 'plugins', 'data', clean(plugin.id || plugin.name || serverName)),
|
|
323
431
|
},
|
|
432
|
+
...(plugin.enabled === false ? { _mixdogPluginDisabled: true } : {}),
|
|
324
433
|
},
|
|
325
434
|
};
|
|
326
435
|
}
|
|
@@ -330,8 +439,14 @@ export function createResourceApi(deps) {
|
|
|
330
439
|
const session = getSession();
|
|
331
440
|
if (session?.id) mgr.closeSession(session.id, 'cli-plugin-mcp-enable', { tombstone: tombstoneOnClose(session) });
|
|
332
441
|
await recreateCurrentSessionIfReady();
|
|
442
|
+
await publishGlobalChange('mcp');
|
|
333
443
|
return { serverName, status };
|
|
334
444
|
},
|
|
445
|
+
disposeGlobalExtensionSubscription() {
|
|
446
|
+
if (globalRefreshTimer) clearTimeout(globalRefreshTimer);
|
|
447
|
+
globalRefreshTimer = null;
|
|
448
|
+
globalExtensionSubscription.unsubscribe();
|
|
449
|
+
},
|
|
335
450
|
hooksStatus() {
|
|
336
451
|
return hooks.status();
|
|
337
452
|
},
|