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:
@@ -24,7 +24,7 @@ import os from 'node:os';
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import installFsAdapter = require('./install-fs-adapter.cjs');
|
||||
import { tryWithinRootLexical } from './security.cjs';
|
||||
const { installFs, mkInstallTempDir } = installFsAdapter;
|
||||
const { installFs } = installFsAdapter;
|
||||
// Reuse the install manifest's existing parser and streamed SHA-256
|
||||
// classification instead of deriving a second integrity implementation here.
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
@@ -43,7 +43,7 @@ const conversionExports = runtimeArtifactConversion as Record<string, unknown> &
|
||||
readMsdCommandNames?: () => string[];
|
||||
};
|
||||
// #2875 Part 2 (J8): shared model-override precedence resolver — see its
|
||||
// module doc for why kilo/opencode MUST resolve through this ONE function
|
||||
// module doc for why opencode MUST resolve through this ONE function
|
||||
// rather than re-deriving the chain per runtime.
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import installModelOverrideResolver = require('./install-model-override-resolver.cjs');
|
||||
@@ -71,7 +71,6 @@ const _require: NodeRequire = require;
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
type ArtifactKindName = 'commands' | 'agents' | 'skills';
|
||||
type KimiArtifactKindName = ArtifactKindName | 'kimi-agents';
|
||||
|
||||
// Mirrors the (unexported) ResolvedProfile in install-profiles.cts.
|
||||
// Must stay in sync if that shape changes.
|
||||
@@ -105,7 +104,7 @@ interface AgentCtx {
|
||||
pathPrefix: string;
|
||||
attribution: string | null | undefined;
|
||||
/** #2875 Part 2 (row I1-I3): install root, threaded through to the
|
||||
* frontmatter-extensions step and (for kilo/opencode's converters) the
|
||||
* frontmatter-extensions step and (for opencode's converter) the
|
||||
* per-agent model-override resolution below. Mirrors install-profiles.cts's
|
||||
* identically-named AgentCtx field — see its doc comment. */
|
||||
targetDir?: string | null;
|
||||
@@ -114,7 +113,7 @@ interface AgentCtx {
|
||||
}
|
||||
|
||||
interface ArtifactKind {
|
||||
kind: KimiArtifactKindName;
|
||||
kind: ArtifactKindName;
|
||||
destSubpath: string;
|
||||
prefix: string;
|
||||
/** For agent kinds, accepts optional pre-converter cross-cutting context
|
||||
@@ -152,7 +151,7 @@ function requiredRuntimeSurfaceSourceClasses(kinds: Iterable<{ kind?: string }>)
|
||||
const required = new Set<RuntimeSurfaceSourceClass>();
|
||||
for (const kind of kinds) {
|
||||
if (kind.kind === 'commands' || kind.kind === 'skills') required.add('commands');
|
||||
if (kind.kind === 'agents' || kind.kind === 'kimi-agents') required.add('agents');
|
||||
if (kind.kind === 'agents') required.add('agents');
|
||||
}
|
||||
return required;
|
||||
}
|
||||
@@ -524,7 +523,7 @@ function _resolveNamedConverter(converterName: string, kindLabel: string): (...a
|
||||
|
||||
/**
|
||||
* Build a converted-agents kind descriptor for runtimes whose agent `.md` files
|
||||
* need runtime-specific frontmatter/body conversion (e.g. Copilot, Cursor, Codex).
|
||||
* need runtime-specific frontmatter/body conversion (e.g. Cursor, Codex).
|
||||
*
|
||||
* Unlike `agentsKind` (which raw-copies source files), this kind applies
|
||||
* `converterName` from Runtime Artifact Conversion exports to each agent file
|
||||
@@ -538,8 +537,8 @@ function _resolveNamedConverter(converterName: string, kindLabel: string): (...a
|
||||
*
|
||||
* Of the four blockers this comment used to name for wiring `bin/install.js`'s
|
||||
* inline agent loop against this resolver, THREE were already stale by the
|
||||
* time #2875 measured them and are not re-litigated here: Copilot's
|
||||
* `.agent.md` rename (the loop's own `destName = entry.name` comment records
|
||||
* time #2875 measured them and are not re-litigated here: the agent-file
|
||||
* extension rename (the loop's own `destName = entry.name` comment records
|
||||
* the ternary dropped in #2099; the descriptor fold applies it via
|
||||
* `hostBehaviors.agentFileExtension`), the cross-cutting path-prefix rewrite +
|
||||
* attribution (`stageAgentsForRuntimeWithConverter` already applies
|
||||
@@ -557,10 +556,8 @@ function _resolveNamedConverter(converterName: string, kindLabel: string): (...a
|
||||
* `disallowedTools` injection) and (b) let THIS function resolve a per-agent
|
||||
* model override (`installModelOverrideResolver.resolveAgentModelOverride`,
|
||||
* `model_overrides[agent]` > `model_profile_overrides.<rt>.<tier>` > omit)
|
||||
* before invoking a converter that needs it (kilo/opencode). Both pieces —
|
||||
* plus a data-driven Hermes branding converter
|
||||
* (`convertClaudeAgentToHermesAgent`, reading `hostBehaviors.brandingRewrites`
|
||||
* rather than a hardcoded string table) — are single-sourced: `bin/install.js`
|
||||
* before invoking a converter that needs it (opencode). Both pieces are
|
||||
* single-sourced: `bin/install.js`
|
||||
* requires the SAME functions this module does, so its inline loop and the
|
||||
* descriptor path can no longer independently drift (the CLAUDE.md
|
||||
* "Generative Fix Divergence" class the prior duplication risked).
|
||||
@@ -568,15 +565,14 @@ function _resolveNamedConverter(converterName: string, kindLabel: string): (...a
|
||||
* `tests/agent-descriptor-parity.test.cjs` proves byte-identical output
|
||||
* between the inline loop and a SYNTHETIC descriptor registry (the same
|
||||
* override seam `resolveRuntimeArtifactLayoutFromRegistry` exposes) for all
|
||||
* six runtimes the inline loop still served: claude, cline, codex, hermes,
|
||||
* kilo, opencode.
|
||||
* runtimes the inline loop still served (claude, codex, opencode).
|
||||
*
|
||||
* Both findings the prior revision of this comment named as STILL deferred
|
||||
* are now CLOSED (#2875 Part 2 Task A/B/C), measured against the real
|
||||
* `capability.json` entries and the real production entry points, not
|
||||
* argued from this module alone:
|
||||
*
|
||||
* 1. **kilo/opencode reaching `layout.kinds`.** `installEngine.
|
||||
* 1. **opencode reaching `layout.kinds`.** `installEngine.
|
||||
* installAgentsKindStandalone` (install-engine.cts) is called from inside
|
||||
* `installOpencodeFamilyArtifacts` and resolves the agents kind through
|
||||
* THIS SAME `resolveRuntimeArtifactLayout`/`convertedAgentsKind` path —
|
||||
@@ -589,18 +585,15 @@ function _resolveNamedConverter(converterName: string, kindLabel: string): (...a
|
||||
* install-tree golden fixture (`tests/fixtures/install-tree/claude-local.json`)
|
||||
* is what caught the gap when it was first missed.
|
||||
* 2. **`/msd:surface` / `applySurface` activation.** Confirmed convergent,
|
||||
* not merely non-broken: for all six runtimes (claude, cline, codex,
|
||||
* hermes, kilo, opencode), staging via `applySurface` into a freshly
|
||||
* not merely non-broken: for every runtime (claude, codex, opencode),
|
||||
* staging via `applySurface` into a freshly
|
||||
* wiped `agents/` directory produces byte-identical output (including
|
||||
* filenames) to `installRuntimeArtifacts`'s own write — verified directly
|
||||
* against the built registry, not inferred.
|
||||
*
|
||||
* The inline loop (`_DESCRIPTOR_AGENTS_RUNTIMES` and the `bin/install.js`
|
||||
* agent-staging block it gated) is DELETED — every runtime the registry
|
||||
* declares an `agents` kind for is descriptor-driven now, including a
|
||||
* seventh runtime (`kimi-code`) this comment's own prior measurement missed
|
||||
* (it fell through the inline loop's generic `else if` branch, same as
|
||||
* claude, with no dedicated dialect arm — caught by the same golden fixture).
|
||||
* declares an `agents` kind for is descriptor-driven now.
|
||||
*
|
||||
* Codex's `config.toml [agents.msd-*]` strip (`bin/install.js`, under
|
||||
* `isMinimalMode` + `hostBehaviors.tomlConfigInstall`) remains the one
|
||||
@@ -636,16 +629,16 @@ function convertedAgentsKind(
|
||||
const rawConverter = _resolveNamedConverter(converterName, 'agents') as
|
||||
(content: string, arg2?: boolean | { isAgent?: boolean; modelOverride?: string | null; variant?: string | null }) => string;
|
||||
|
||||
// #2875 Part 2 (J5-J8): kilo/opencode agent converters take an options
|
||||
// #2875 Part 2 (J5-J8): the opencode agent converter takes an options
|
||||
// bag (`{isAgent, modelOverride}`), not the `isGlobal` boolean every
|
||||
// other agent converter's 2nd positional arg means — mirrors the
|
||||
// inline loop's per-runtime `frontmatterDialect === 'opencode' | 'kilo'`
|
||||
// branches (bin/install.js), which resolve model_overrides[agent] >
|
||||
// inline loop's per-runtime `frontmatterDialect === 'opencode'`
|
||||
// branch (bin/install.js), which resolves model_overrides[agent] >
|
||||
// model_profile_overrides.<runtime>.<tier> > omit BEFORE calling the
|
||||
// converter. Resolved ONCE per stage() call (not per file — a pure
|
||||
// function of configDir/targetDir) via the single shared precedence
|
||||
// resolver so kilo and opencode can never diverge (J8).
|
||||
const needsModelOverride = converterName === 'convertClaudeToOpencodeFrontmatter' || converterName === 'convertClaudeToKiloFrontmatter';
|
||||
// resolver (J8).
|
||||
const needsModelOverride = converterName === 'convertClaudeToOpencodeFrontmatter';
|
||||
let converter: (content: string, isGlobal?: boolean, meta?: { agentName: string }) => string;
|
||||
if (needsModelOverride) {
|
||||
const overrideTargetDir = agentCtx?.targetDir ?? configDir;
|
||||
@@ -665,10 +658,7 @@ function convertedAgentsKind(
|
||||
// the risk this gate avoids. #1156's rule for `model: inherit` is the
|
||||
// precedent: do not emit a key the runtime may not understand.
|
||||
//
|
||||
// OpenCode only; the kilo converter ignores the field (no EFFORT_ARGV.kilo).
|
||||
const effortConfig = converterName === 'convertClaudeToOpencodeFrontmatter'
|
||||
? installEffortResolver.readMsdEffectiveEffortConfig(overrideTargetDir)
|
||||
: null;
|
||||
const effortConfig = installEffortResolver.readMsdEffectiveEffortConfig(overrideTargetDir);
|
||||
converter = (content, _isGlobal, meta) => {
|
||||
const modelOverride = meta
|
||||
? installModelOverrideResolver.resolveAgentModelOverride(meta.agentName, modelOverrides, runtimeResolver)
|
||||
@@ -687,7 +677,7 @@ function convertedAgentsKind(
|
||||
return rawConverter(content, { isAgent: true, modelOverride, variant });
|
||||
};
|
||||
} else {
|
||||
// isGlobal is threaded so scope-aware agent converters (copilot, antigravity)
|
||||
// isGlobal is threaded so scope-aware agent converters (antigravity)
|
||||
// choose global-home vs workspace-relative paths; converters that only take
|
||||
// (content) ignore the extra positional arg. Mirrors skillsKind's scope
|
||||
// threading (#1173).
|
||||
@@ -707,68 +697,17 @@ function convertedAgentsKind(
|
||||
};
|
||||
}
|
||||
|
||||
function kimiAgentsKind(destSubpath: string, prefix: string, configDir: string, sourceContext: SourceResolutionContext): ArtifactKind {
|
||||
return {
|
||||
kind: 'kimi-agents',
|
||||
destSubpath,
|
||||
prefix,
|
||||
stage: (resolved, agentCtx) => {
|
||||
const buildKimiAgentArtifacts = conversionExports['buildKimiAgentArtifacts'] as (opts: {
|
||||
rootAgent?: string;
|
||||
subagents?: Array<{ path: string; content: string }>;
|
||||
}) => {
|
||||
root: { yaml: string; prompt: string };
|
||||
subagents: Array<{ name: string; yaml: string; prompt: string }>;
|
||||
};
|
||||
// #2995: compose at staging (identity converter) so the readFileSync below
|
||||
// sees marker-free content — same single composing stager as agentsKind.
|
||||
const stagedAgents = stageAgentsForRuntimeWithConverter(
|
||||
sourceRootFor(sourceContext, 'agents'),
|
||||
resolved,
|
||||
(content: string) => content,
|
||||
false,
|
||||
agentCtx,
|
||||
);
|
||||
const subagents: Array<{ path: string; content: string }> = [];
|
||||
if (installFs().existsSync(stagedAgents)) {
|
||||
for (const entry of installFs().readdirSync(stagedAgents, { withFileTypes: true })) {
|
||||
if (!entry.isFile() || !entry.name.endsWith('.md')) continue;
|
||||
const agentPath = path.join(stagedAgents, entry.name);
|
||||
subagents.push({
|
||||
path: posixNormalize(path.join('agents', entry.name)),
|
||||
content: installFs().readFileSync(agentPath, 'utf8'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const rootAgent = `---\nname: msd\ndescription: Run MSD workflows in Kimi CLI.\ntools: Agent\n---\n\n# MSD for Kimi CLI\n\nCoordinate installed /skill:msd-* workflows and route work to generated MSD subagents when a workflow requires an agent handoff.\n`;
|
||||
const artifacts = buildKimiAgentArtifacts({ rootAgent, subagents });
|
||||
const stageDir = mkInstallTempDir('msd-kimi-agents-');
|
||||
installProfiles.STAGED_DIRS.add(stageDir);
|
||||
installFs().writeFileSync(path.join(stageDir, 'msd.yaml'), artifacts.root.yaml);
|
||||
installFs().writeFileSync(path.join(stageDir, 'msd.md'), artifacts.root.prompt);
|
||||
const subagentsDir = path.join(stageDir, 'subagents');
|
||||
installFs().mkdirSync(subagentsDir, { recursive: true });
|
||||
for (const artifact of artifacts.subagents) {
|
||||
installFs().writeFileSync(path.join(subagentsDir, `${artifact.name}.yaml`), artifact.yaml);
|
||||
installFs().writeFileSync(path.join(subagentsDir, `${artifact.name}.md`), artifact.prompt);
|
||||
}
|
||||
return stageDir;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a skills kind descriptor.
|
||||
*
|
||||
* @param destSubpath
|
||||
* @param prefix
|
||||
* @param converterName name of converter function in Runtime Artifact Conversion exports
|
||||
* @param runtime canonical runtime ID (gates Hermes/Qwen branding in converter)
|
||||
* @param runtime canonical runtime ID (passed through to the converter)
|
||||
* @param configDir runtime config dir (for .msd-source marker resolution)
|
||||
* @param nested if true, nest concrete skills under their ns-* routers (#69)
|
||||
* @param scope install scope; converted to isGlobal and passed as 5th positional
|
||||
* arg so scope-aware converters (antigravity, copilot) can choose
|
||||
* arg so scope-aware converters (antigravity) can choose
|
||||
* between global home paths and workspace-relative paths without
|
||||
* colliding with the `runtime` string at position 3.
|
||||
* @param capabilityRegistry #2322: optional capability registry — captured in the
|
||||
@@ -797,8 +736,8 @@ function skillsKind(
|
||||
// Compute cmdNames once per stage call for performance (#3583).
|
||||
// Extra trailing args are ignored by converters that don't need them. The
|
||||
// isGlobal flag is the 5th positional (NOT the 3rd): the 3rd positional is
|
||||
// `runtime` for the claude/kimi/cline converters, so the scope-aware
|
||||
// converters (antigravity, copilot) read isGlobal from position 5 to avoid
|
||||
// `runtime` for the claude converter, so the scope-aware
|
||||
// converters (antigravity) read isGlobal from position 5 to avoid
|
||||
// colliding with `runtime` and always taking the global branch.
|
||||
const cmdNames = conversionExports.readMsdCommandNames
|
||||
? conversionExports.readMsdCommandNames()
|
||||
@@ -874,17 +813,10 @@ function convertedCommandsKind(
|
||||
// flat conservatively. Verified June 2026:
|
||||
//
|
||||
// NEST (confirmed non-recursive / one-level scan):
|
||||
// cline — cline/cline skills.ts scanSkillsDirectory uses flat fs.readdir
|
||||
// qwen — QwenLM/qwen-code skill-load.ts flat readdir ("depth 2 enough")
|
||||
// hermes — hermes-agent.nousresearch.com/docs/user-guide/features/skills
|
||||
// (single-level subdir probe of the tap path)
|
||||
// augment — https://docs.augmentcode.com/cli/skills (flat single-level)
|
||||
// trae — docs.trae.ai/ide/skills + Trae-AI/TRAE#2253 (flat; nesting errors)
|
||||
// Trae IDE (trae.ai), not trae-agent — see runtime-homes.cts header note
|
||||
// (none of the currently supported runtimes)
|
||||
// FLAT (recursive loader → nesting gives no saving):
|
||||
// cursor — https://cursor.com/docs/skills (walks skills root recursively)
|
||||
// opencode — sst/opencode skill/index.ts glob "skills/**/SKILL.md"
|
||||
// kilo — Kilo-Org/kilocode (opencode fork, same ** glob)
|
||||
//
|
||||
// FLAT (one-level scan, but concrete skills must be directly discoverable):
|
||||
// antigravity— https://antigravity.google/docs/skills + /docs/cli-plugins
|
||||
@@ -899,9 +831,6 @@ function convertedCommandsKind(
|
||||
//
|
||||
// FLAT (nested-scan behaviour unconfirmed → conservative):
|
||||
// codex — developers.openai.com/codex/skills/
|
||||
// copilot — docs.github.com/en/copilot/concepts/agents/about-agent-skills
|
||||
// windsurf — docs.devin.ai/desktop/cascade/skills
|
||||
// codebuddy — codebuddy.ai/docs/cli/skills
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Descriptor-driven dispatch helpers (ADR-857 phase 5d)
|
||||
@@ -967,10 +896,6 @@ function dispatchKindEntry(entry: ArtifactKindDescriptor, runtime: string, confi
|
||||
result = skillsKind(destSubpath, prefix, converter, runtime, configDir, nested, scope, sourceContext, capabilityRegistry);
|
||||
break;
|
||||
|
||||
case 'kimi-agents':
|
||||
result = kimiAgentsKind(destSubpath, prefix, configDir, sourceContext);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new TypeError(
|
||||
`resolveRuntimeArtifactLayout: unknown kind '${kind}' in descriptor for runtime '${runtime}'`,
|
||||
@@ -1060,12 +985,12 @@ function resolveRuntimeArtifactLayoutFromRegistry(
|
||||
// rather than "where does a file land". A new function, not a widened
|
||||
// signature — see .msd/phase/feat-2871-trigger-resolution/40-design.md.
|
||||
//
|
||||
// Only `commands` and `skills` are trigger-bearing. `agents` / `kimi-agents`
|
||||
// Only `commands` and `skills` are trigger-bearing. `agents`
|
||||
// are a SEPARATE dispatch interface point (subagent invocation via
|
||||
// `subagent_type` / named dispatch, never a `/msd-<name>` a user types) — see
|
||||
// 40-design.md's "agents are not trigger-bearing" correction to ADR-2866.
|
||||
// Excluding them here is deliberate, not an oversight: including `agents`
|
||||
// would misreport windsurf (whose global scope emits agents only) as fully
|
||||
// would misreport a runtime whose global scope emits agents only as fully
|
||||
// shadowing its local `/msd-*` surface, when in fact nothing shadows it.
|
||||
|
||||
/** The trigger-bearing subset of ArtifactKindName — mirrors
|
||||
@@ -1275,7 +1200,7 @@ function resolveTriggerSurface(runtime: string, scopes: InstallScope[], opts: Tr
|
||||
if (!scopeSet.has(scope)) continue;
|
||||
const entries = layout[scope] ?? [];
|
||||
for (const entry of entries) {
|
||||
if (entry.kind !== 'commands' && entry.kind !== 'skills') continue; // excludes agents/kimi-agents
|
||||
if (entry.kind !== 'commands' && entry.kind !== 'skills') continue; // excludes agents
|
||||
const kind = entry.kind;
|
||||
const destSubpath = posixNormalize(entry.destSubpath);
|
||||
const namespacedByDir = isNamespacedByDir(kind, entry.destSubpath, entry.prefix);
|
||||
|
||||
Reference in New Issue
Block a user