refactor: drop 12 runtimes, keep Claude, Codex, OpenCode, Cursor, ZCode, Antigravity
Removes kilo, kimi, kimi-code, copilot, windsurf, augment, trae, qwen, hermes, cline, codebuddy and pi end to end: capability descriptors, installer branches and converters (bin/install.js 14.9k -> 11.2k lines), TypeScript converters, hook surfaces and runtime homes, review lanes qwen/kimi-code, the two pi migrations, Kimi payload normalization in the hook guards, dead hostBehaviors vocabulary, launcher home probes, fixtures, runtime-specific tests and the prose that presented them as supported. Installer output for the six kept runtimes is byte-identical to before the prune. The Kimi tool-vocabulary tests in workflow-guard, read-guard and read-injection-scanner are left in place pending a decision.
This commit is contained in:
@@ -14,7 +14,7 @@ const { installerEnv, RUNTIME_META } = require('./helpers/install-shared.cjs');
|
||||
const { INSTALL_TIMEOUT_MS } = require('./helpers/timeouts.cjs');
|
||||
|
||||
const REPO_ROOT = path.join(__dirname, '..');
|
||||
const { appendAgentTools, buildKimiAgentArtifacts, convertClaudeAgentToQwenAgent, convertClaudeAgentToZcodeAgent, _decodeToolScalar } = require('../msd-core/bin/lib/runtime-artifact-conversion.cjs');
|
||||
const { appendAgentTools, convertClaudeAgentToZcodeAgent, _decodeToolScalar } = require('../msd-core/bin/lib/runtime-artifact-conversion.cjs');
|
||||
const { parseFrontmatter } = require('../msd-core/bin/lib/frontmatter.cjs');
|
||||
|
||||
function parseTools(content) {
|
||||
@@ -209,13 +209,6 @@ test('host converters receive canonical grants without changing their omissions
|
||||
assert.ok(artifacts.some((artifact) => artifact.includes('mcp__configured__grant')),
|
||||
`${runtime} must expose the configured canonical grant in its existing host form`);
|
||||
}
|
||||
const kiloArtifacts = emittedAgentArtifacts(installRuntime(t, 'kilo', { defaults }), 'msd-executor');
|
||||
assert.ok(kiloArtifacts.some((artifact) => {
|
||||
const configured = artifact.indexOf(' configured_grant: allow');
|
||||
const wildcard = artifact.indexOf(' first_*: allow');
|
||||
return configured >= 0 && wildcard > configured;
|
||||
}),
|
||||
'Kilo must translate safe canonical MCP grants into native permission keys in first-seen order');
|
||||
for (const runtime of ['zcode', 'opencode']) {
|
||||
const artifacts = emittedAgentArtifacts(installRuntime(t, runtime, { defaults }), 'msd-executor');
|
||||
assert.ok(artifacts.every((artifact) => !artifact.includes('mcp__configured__grant')),
|
||||
@@ -223,71 +216,6 @@ test('host converters receive canonical grants without changing their omissions
|
||||
}
|
||||
});
|
||||
|
||||
test('Kimi receives canonical grants before its existing mapper runs (#4032)', (t) => {
|
||||
const install = installRuntime(t, 'kimi', {
|
||||
defaults: { agent_tools: { 'msd-executor': ['WebFetch'] } },
|
||||
scope: 'global',
|
||||
});
|
||||
const artifacts = emittedAgentArtifacts(install, 'msd-executor');
|
||||
assert.ok(artifacts.some((artifact) => artifact.includes('kimi_cli.tools.web:FetchURL')),
|
||||
'Kimi must map a configured canonical WebFetch tool through its existing converter');
|
||||
});
|
||||
|
||||
test('Kimi project selectors override matching global selectors (#4032)', (t) => {
|
||||
const install = installRuntime(t, 'kimi', {
|
||||
defaults: { agent_tools: { 'msd-executor': ['WebFetch'] } },
|
||||
projectConfig: { agent_tools: { 'msd-executor': ['WebSearch'] } },
|
||||
scope: 'global',
|
||||
});
|
||||
const artifacts = emittedAgentArtifacts(install, 'msd-executor');
|
||||
assert.ok(artifacts.some((artifact) => artifact.includes('kimi_cli.tools.web:SearchWeb')),
|
||||
'Kimi must map the project grant through its existing converter');
|
||||
assert.ok(artifacts.every((artifact) => !artifact.includes('kimi_cli.tools.web:FetchURL')),
|
||||
'the matching global selector must not survive the project override');
|
||||
});
|
||||
|
||||
test('Kimi neutralizes ~/.claude/msd-core references instead of leaking a repointed path (#4032)', (t) => {
|
||||
// Routing Kimi through the ADR-1235 pre-converter pipeline (needed so
|
||||
// project-scoped agent_tools selectors reach it, see the test above) also
|
||||
// runs _applyAgentPathRewrites. Kimi's own neutralizeKimiAgentPrompt expects
|
||||
// to see the ORIGINAL `~/.claude/msd-core` text; kimi/kimi-code capability.json
|
||||
// now opt out via hostBehaviors.noPathRewrite so that still holds.
|
||||
const install = installRuntime(t, 'kimi', { scope: 'global' });
|
||||
// Inspect generated prompts, not the installation-owned raw source corpus.
|
||||
const artifacts = emittedAgentArtifacts(install, 'msd-executor', path.join(install.configDir, 'agents'));
|
||||
assert.ok(artifacts.some((artifact) => artifact.includes('MSD core')),
|
||||
'a ~/.claude/msd-core reference must neutralize to prose, not a repointed Kimi path');
|
||||
assert.ok(artifacts.every((artifact) =>
|
||||
!artifact.includes('~/.claude/msd-core') &&
|
||||
!artifact.includes('$HOME/.claude/msd-core') &&
|
||||
!artifact.includes('config/agents/msd-core/references')),
|
||||
'no raw Claude- or Kimi-prefixed msd-core path may leak into the Kimi prompt body');
|
||||
});
|
||||
|
||||
test('Kilo global install resolves project agent_tools from the working directory (#4032)', (t) => {
|
||||
const install = installRuntime(t, 'kilo', {
|
||||
defaults: { agent_tools: { 'msd-executor': ['mcp__global__loser'] } },
|
||||
projectConfig: { agent_tools: { 'msd-executor': ['mcp__project__winner'] } },
|
||||
scope: 'global',
|
||||
});
|
||||
const artifacts = emittedAgentArtifacts(install, 'msd-executor');
|
||||
assert.ok(artifacts.some((artifact) => artifact.includes(' project_winner: allow')),
|
||||
'the combined-family path must discover project config from cwd during a global install');
|
||||
assert.ok(artifacts.every((artifact) => !artifact.includes('global_loser')),
|
||||
'the matching global selector must not survive the project override');
|
||||
});
|
||||
|
||||
test('Kilo permission-key collision resolves deterministically to first-seen (#4032)', (t) => {
|
||||
// mcp__a_b__c and mcp__a__b_c both derive Kilo's native "a_b_c" key — the
|
||||
// `{server}_{tool}` format is Kilo's own fixed contract, not ours to widen.
|
||||
const install = installRuntime(t, 'kilo', {
|
||||
defaults: { agent_tools: { 'msd-executor': ['mcp__a_b__c', 'mcp__a__b_c'] } },
|
||||
});
|
||||
const artifacts = emittedAgentArtifacts(install, 'msd-executor');
|
||||
assert.ok(artifacts.some((artifact) => (artifact.match(/^ {2}a_b_c: allow$/gm) || []).length === 1),
|
||||
'a colliding pair must still emit exactly one permission line');
|
||||
});
|
||||
|
||||
test('every installable runtime accepts a configured MCP grant without crashing (#4032)', (t) => {
|
||||
// Shallow, broad: appendAgentTools runs pre-converter for every runtime, but
|
||||
// only 6 have deep per-runtime assertions elsewhere in this file. This locks
|
||||
@@ -297,17 +225,14 @@ test('every installable runtime accepts a configured MCP grant without crashing
|
||||
// support varies per runtime, global does not). Search from `install.home`,
|
||||
// not `install.configDir`: a nested-home runtime (e.g. antigravity) places
|
||||
// agents in a sibling directory outside its own configDir subtree.
|
||||
const NO_SUBAGENT_TOOLKIT = new Set(['pi']); // programmatic dispatch, no named-dispatch agent files
|
||||
// Runtimes empirically verified (see PR #4238 remediation) to pass an
|
||||
// arbitrary mcp__ grant through recognizably — either verbatim or via
|
||||
// Kilo's {server}_{tool} transform. Every other runtime filters unknown
|
||||
// arbitrary mcp__ grant through recognizably (verbatim). Every other runtime filters unknown
|
||||
// tool names through its own built-in vocabulary (a legitimate, unrelated
|
||||
// per-runtime design choice, not an agent_tools omission) and is checked
|
||||
// for a clean, non-crashing install only. This is an allowlist, not a
|
||||
// guess-based omit-list, so it can't silently drift as runtimes are added.
|
||||
const GRANT_SURVIVES_RECOGNIZABLY = new Set(['claude', 'codex', 'copilot', 'hermes', 'kimi-code', 'kilo', 'qwen']);
|
||||
const GRANT_SURVIVES_RECOGNIZABLY = new Set(['claude', 'codex']);
|
||||
for (const runtime of Object.keys(RUNTIME_META)) {
|
||||
if (NO_SUBAGENT_TOOLKIT.has(runtime)) continue;
|
||||
const install = installRuntime(t, runtime, {
|
||||
defaults: { agent_tools: { 'msd-executor': ['mcp__smoke__probe'] } },
|
||||
scope: 'global',
|
||||
@@ -315,8 +240,8 @@ test('every installable runtime accepts a configured MCP grant without crashing
|
||||
const artifacts = emittedAgentArtifacts(install, 'msd-executor', install.home);
|
||||
assert.ok(artifacts.every((artifact) => artifact.length > 0), `${runtime} must emit non-empty msd-executor artifact(s)`);
|
||||
if (!GRANT_SURVIVES_RECOGNIZABLY.has(runtime)) continue;
|
||||
assert.ok(artifacts.some((artifact) => artifact.includes('mcp__smoke__probe') || artifact.includes('smoke_probe')),
|
||||
`${runtime} must carry the configured grant (raw or Kilo-style transformed) — this must fail if agent_tools is reverted`);
|
||||
assert.ok(artifacts.some((artifact) => artifact.includes('mcp__smoke__probe')),
|
||||
`${runtime} must carry the configured grant — this must fail if agent_tools is reverted`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -358,7 +283,7 @@ test('reinstall remains idempotent and preserves Claude read-only restrictions (
|
||||
'the existing Claude read-only deny-list must survive augmentation');
|
||||
});
|
||||
|
||||
test('quoted scalar identity is shared by append, Kimi, and Qwen (#4191)', () => {
|
||||
test('quoted scalar identity is shared by append (#4191)', () => {
|
||||
for (const [raw, expected] of [
|
||||
['"\\x6dcp__server__tool"', 'mcp__server__tool'],
|
||||
['"\\u006dcp__server__tool"', 'mcp__server__tool'],
|
||||
@@ -374,16 +299,6 @@ test('quoted scalar identity is shared by append, Kimi, and Qwen (#4191)', () =>
|
||||
for (const content of [inline, block]) {
|
||||
const once = appendAgentTools(content, ['WebFetch', 'WebSearch']);
|
||||
assert.strictEqual(once, content, 'quoted values must already satisfy append idempotency');
|
||||
|
||||
const kimi = buildKimiAgentArtifacts({ subagents: [{ path: 'agents/msd-test.md', content }] });
|
||||
assert.ok(kimi.subagents[0].yaml.includes('kimi_cli.tools.web:FetchURL'));
|
||||
assert.ok(kimi.subagents[0].yaml.includes('kimi_cli.tools.web:SearchWeb'));
|
||||
assert.ok(!kimi.subagents[0].yaml.includes('unterminated'));
|
||||
|
||||
const qwen = convertClaudeAgentToQwenAgent(content);
|
||||
assert.match(qwen, /^ {2}- WebFetch$/m);
|
||||
assert.match(qwen, /^ {2}- WebSearch$/m);
|
||||
assert.doesNotMatch(qwen, /unterminated/);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -476,27 +391,6 @@ test('appendAgentTools recognizes an existing block item with a trailing comment
|
||||
assert.match(once, /^ {2}- "Write"$/m, 'the genuinely new grant must still be appended');
|
||||
});
|
||||
|
||||
test('parseFrontmatterTools (Qwen conversion) does not silently drop a block list under a comment-only header (#4032)', () => {
|
||||
// Sibling of the appendAgentTools fix (finding 4): parseFrontmatterTools's
|
||||
// own comment-only-header check (`if (value) {...}` treated `# note` as
|
||||
// truthy content instead of falling through to `collecting = true`) is a
|
||||
// separate parser reached by Kimi and Qwen conversion, downstream of
|
||||
// appendAgentTools's own output.
|
||||
const agent = '---\nname: msd-executor\ndescription: test\ntools: # note\n - Read\n - Write\n---\nbody\n';
|
||||
const qwen = convertClaudeAgentToQwenAgent(agent);
|
||||
assert.match(qwen, /- Read/);
|
||||
assert.match(qwen, /- Write/);
|
||||
});
|
||||
|
||||
test('parseFrontmatterTools (Qwen conversion) does not tear a quoted scalar containing a literal comma (#4032)', () => {
|
||||
const agent = appendAgentTools(
|
||||
'---\nname: msd-executor\ndescription: test\ntools: Read, "mcp__x, y"\n---\nbody\n',
|
||||
[],
|
||||
);
|
||||
const qwen = convertClaudeAgentToQwenAgent(agent);
|
||||
assert.match(qwen, /- "mcp__x, y"/, 'the quoted scalar must survive as one tool, not torn on its internal comma');
|
||||
});
|
||||
|
||||
test('ZCode strips an undecodable mcp__ scalar instead of keeping it (fail-closed) (#4032)', () => {
|
||||
// An unterminated quote makes decodeToolScalar return null. ZCode's contract
|
||||
// is "never emit mcp__*" (a required-MCP-server hard-fail otherwise), so a
|
||||
|
||||
Reference in New Issue
Block a user