* fix(#4211): materialize Kimi's agent tree recursively during surface apply kimiAgentsKind stages `gsd.yaml` + `gsd.md` + `subagents/gsd-*.{yaml,md}`, and install copies that tree recursively (_copyStaged). Surface apply fell through to _syncGsdDir's flat command/agent branch, which reads only top-level `*.md`: the YAML half and the whole subagents/ subtree were ignored, and `gsd.md` was written as `gsdgsd.md` because the flat branch re-applies kind.prefix to a name that already carries it. A surface change could therefore corrupt Kimi's installed artifacts while still reporting success. Three divergences from the install path, all in src/surface.cts: - _syncGsdDir gains a kimi-agents branch: recursive copy, then a prune scoped to exactly what install's _removeGsdEntries owns for this kind (the two root files, and gsd-*.{yaml,md} under subagents/). Everything else is user-owned and preserved. - applySurface stages kimi-agents WITH agentCtx and the `skills: '*'` rule for an unmodified full profile, as it already does for the agents kind and as createRuntimeArtifactInstallPlan does for every kind — without it Kimi's generated subagents lost their path-prefix rewrites and attribution trailer, and an unmodified full profile staged only the skill-referenced subset. - applySurface runs rewriteStagedSkillBodies for kimi-agents, which the install plan routes through it alongside skills. * chore: add changeset for #4211 --------- Co-authored-by: Tom Boucher <trekkie@nomorestars.com>
This commit is contained in:
@@ -435,13 +435,24 @@ function applySurface(runtimeConfigDir: string, layout: Layout, manifest: Map<st
|
||||
try {
|
||||
for (const kind of layout.kinds) {
|
||||
let staged: string;
|
||||
if (kind.kind === 'agents') {
|
||||
// #4211: kimi-agents is an AGENT kind — kimiAgentsKind.stage() forwards
|
||||
// agentCtx into stageAgentsForRuntimeWithConverter exactly as agentsKind
|
||||
// does, and createRuntimeArtifactInstallPlan hands every kind the
|
||||
// context. Staging it bare here dropped the path-prefix rewrites and the
|
||||
// attribution trailer from Kimi's generated subagents, and (under an
|
||||
// unmodified `full` profile) staged only the skill-referenced subset the
|
||||
// install path stages with `skills: '*'`.
|
||||
if (kind.kind === 'agents' || kind.kind === 'kimi-agents') {
|
||||
const agentProfile = _isUnmodifiedFull ? { ...resolved, skills: '*' as const } : resolved;
|
||||
staged = kind.stage(agentProfile, agentCtx);
|
||||
} else {
|
||||
staged = kind.stage(resolved);
|
||||
}
|
||||
if (kind.kind === 'skills') {
|
||||
// #4211: kimi-agents takes the skill-body rewrite too —
|
||||
// createRuntimeArtifactInstallPlan routes `skills` and `kimi-agents`
|
||||
// through rewriteStagedSkillBodies together, so omitting it here left
|
||||
// Kimi's surface-materialized prompts with unrewritten paths.
|
||||
if (kind.kind === 'skills' || kind.kind === 'kimi-agents') {
|
||||
runtimeArtifactConversion.rewriteStagedSkillBodies(staged, {
|
||||
runtime: layout.runtime,
|
||||
configDir: layout.configDir,
|
||||
@@ -638,6 +649,45 @@ function _syncGsdDir(stagedDir: string, destDir: string, kind: ArtifactKind | st
|
||||
const _agentExt = runtime ? runtimeArtifactConversion.agentFileExtensionFor(runtime) : undefined;
|
||||
const isRenamedAgents = !!_agentExt && kindName === 'agents';
|
||||
|
||||
if (kindName === 'kimi-agents') {
|
||||
// #4211: Kimi's managed tree is `gsd.yaml` + `gsd.md` + `subagents/gsd-*.{yaml,md}`
|
||||
// (runtime-artifact-layout.cts kimiAgentsKind), and install copies it
|
||||
// RECURSIVELY (_copyStaged in src/install-engine.cts). Surface apply fell
|
||||
// through to the flat command/agent branch below, which reads only `*.md`
|
||||
// at the top level: the YAML half and the whole subagents/ subtree were
|
||||
// dropped, and `gsd.md` was written as `gsdgsd.md` (the flat branch
|
||||
// re-applies kind.prefix to a name that already carries it). A surface
|
||||
// change could therefore corrupt Kimi's installed artifacts while still
|
||||
// reporting success.
|
||||
fs.cpSync(stagedDir, destDir, { recursive: true });
|
||||
|
||||
// Prune GSD-owned files the new surface no longer stages, with exactly the
|
||||
// ownership rule install's _removeGsdEntries applies to this kind: the two
|
||||
// root files, and `gsd-`-prefixed .yaml/.md under subagents/. Everything
|
||||
// else in the directory is user-owned and is preserved.
|
||||
const _rootStaged = new Set(fs.readdirSync(stagedDir));
|
||||
for (const fileName of ['gsd.yaml', 'gsd.md']) {
|
||||
if (!_rootStaged.has(fileName)) {
|
||||
try { fs.rmSync(path.join(destDir, fileName), { force: true }); } catch { /* ignore */ }
|
||||
}
|
||||
}
|
||||
const _stagedSubagentsDir = path.join(stagedDir, 'subagents');
|
||||
const _destSubagentsDir = path.join(destDir, 'subagents');
|
||||
const _stagedSubagents = fs.existsSync(_stagedSubagentsDir)
|
||||
? new Set(fs.readdirSync(_stagedSubagentsDir))
|
||||
: new Set<string>();
|
||||
if (fs.existsSync(_destSubagentsDir)) {
|
||||
for (const entry of fs.readdirSync(_destSubagentsDir, { withFileTypes: true })) {
|
||||
if (!entry.isFile()) continue;
|
||||
if (!entry.name.startsWith('gsd-')) continue;
|
||||
if (!entry.name.endsWith('.yaml') && !entry.name.endsWith('.md')) continue;
|
||||
if (_stagedSubagents.has(entry.name)) continue;
|
||||
try { fs.rmSync(path.join(_destSubagentsDir, entry.name), { force: true }); } catch { /* ignore */ }
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (kindName === 'skills') {
|
||||
// Skills kind: work with directories, not files.
|
||||
// Each staged entry is a directory named ${prefix}${stem}.
|
||||
|
||||
Reference in New Issue
Block a user