From fba6cfe63d491e3f9a06c0f70ae6145936b77a9c Mon Sep 17 00:00:00 2001 From: Viktorplus <36795799+viktorplus@users.noreply.github.com> Date: Sat, 6 Jun 2026 14:57:48 +0200 Subject: [PATCH] feat(03-02): install Kimi global agent artifacts - stage generated Kimi root and subagent YAML/prompt files under agents/ - copy and remove only GSD-owned Kimi agent files while preserving user files - print explicit kimi --agent-file launch hint --- bin/install.js | 31 +++++++++++++++++++- src/runtime-artifact-layout.cts | 52 +++++++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/bin/install.js b/bin/install.js index 121f47b57..1d89abdd0 100755 --- a/bin/install.js +++ b/bin/install.js @@ -6686,6 +6686,7 @@ function _applyRuntimeRewrites(content, runtime, pathPrefix) { * encodes the GSD namespace as its last segment (e.g. `commands/gsd`), in * which case write as `${stem}.md` (directory IS the namespace). * - agents: write as-is (files already carry their own `gsd-` prefix). + * For kimi-agents kind: recursively copy generated YAML/prompt files. */ function _copyStaged(stagedDir, destDir, kind) { if (!fs.existsSync(stagedDir)) return; @@ -6702,6 +6703,11 @@ function _copyStaged(stagedDir, destDir, kind) { return; } + if (kind.kind === 'kimi-agents') { + fs.cpSync(stagedDir, destDir, { recursive: true }); + return; + } + // commands or agents const entries = fs.readdirSync(stagedDir, { withFileTypes: true }); // For commands: apply prefix unless the destSubpath's last segment already @@ -6738,6 +6744,21 @@ function _copyStaged(stagedDir, destDir, kind) { */ function _removeGsdEntries(destDir, kind) { if (!fs.existsSync(destDir)) return; + if (kind.kind === 'kimi-agents') { + for (const fileName of ['gsd.yaml', 'gsd.md']) { + fs.rmSync(path.join(destDir, fileName), { force: true }); + } + const subagentsDir = path.join(destDir, 'subagents'); + if (fs.existsSync(subagentsDir)) { + for (const entry of fs.readdirSync(subagentsDir, { withFileTypes: true })) { + if (!entry.isFile()) continue; + if (!entry.name.startsWith('gsd-')) continue; + if (!entry.name.endsWith('.yaml') && !entry.name.endsWith('.md')) continue; + fs.rmSync(path.join(subagentsDir, entry.name), { force: true }); + } + } + return; + } if (kind.prefix === '') { // Whole-namespace removal (Hermes nested case — destSubpath is skills/gsd) // The directory itself is the GSD namespace, so remove it entirely. @@ -8779,6 +8800,7 @@ function install(isGlobal, runtime = 'claude', options = {}) { if (isKimi && isGlobal) { installRuntimeArtifacts(runtime, targetDir, 'global', _resolvedProfile); const skillsDir = path.join(targetDir, 'skills'); + const rootAgentPath = path.join(targetDir, 'agents', 'gsd.yaml'); const count = fs.existsSync(skillsDir) ? fs.readdirSync(skillsDir, { withFileTypes: true }) .filter(e => e.isDirectory() && e.name.startsWith('gsd-')).length @@ -8788,11 +8810,18 @@ function install(isGlobal, runtime = 'claude', options = {}) { } else { throw new Error('Kimi global install produced no skills/gsd-* entries'); } + if (fs.existsSync(rootAgentPath)) { + console.log(` ${green}✓${reset} Generated Kimi root agent: ${rootAgentPath}`); + console.log(` Launch with: kimi --agent-file ${rootAgentPath}`); + } else { + throw new Error('Kimi global install produced no agents/gsd.yaml'); + } return { runtime, skipped: true, - reason: 'kimi_global_skills_only', + reason: 'kimi_global_skills_and_agents', configDir: targetDir, + agentPath: rootAgentPath, settingsPath: null, settings: null, statuslineCommand: null, diff --git a/src/runtime-artifact-layout.cts b/src/runtime-artifact-layout.cts index dfe874a87..b68fb2a9c 100644 --- a/src/runtime-artifact-layout.cts +++ b/src/runtime-artifact-layout.cts @@ -64,6 +64,7 @@ function getInstallExports(): InstallExports { // --------------------------------------------------------------------------- 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. @@ -74,7 +75,7 @@ interface ResolvedProfile { } interface ArtifactKind { - kind: ArtifactKindName; + kind: KimiArtifactKindName; destSubpath: string; prefix: string; stage: (resolvedProfile: ResolvedProfile) => string; @@ -192,6 +193,50 @@ function agentsKind(destSubpath: string, prefix: string, configDir: string): Art }; } +function kimiAgentsKind(destSubpath: string, prefix: string, configDir: string): ArtifactKind { + return { + kind: 'kimi-agents', + destSubpath, + prefix, + stage: (resolved) => { + const installExports = getInstallExports(); + const buildKimiAgentArtifacts = installExports['buildKimiAgentArtifacts'] as (opts: { + rootAgent?: string; + subagents?: Array<{ path: string; content: string }>; + }) => { + root: { yaml: string; prompt: string }; + subagents: Array<{ name: string; yaml: string; prompt: string }>; + }; + const stagedAgents = stageAgentsForProfile(findAgentsSourceRoot(configDir), resolved); + const subagents: Array<{ path: string; content: string }> = []; + if (fs.existsSync(stagedAgents)) { + for (const entry of fs.readdirSync(stagedAgents, { withFileTypes: true })) { + if (!entry.isFile() || !entry.name.endsWith('.md')) continue; + const agentPath = path.join(stagedAgents, entry.name); + subagents.push({ + path: path.join('agents', entry.name).replace(/\\/g, '/'), + content: fs.readFileSync(agentPath, 'utf8'), + }); + } + } + + const rootAgent = `---\nname: gsd\ndescription: Run GSD workflows in Kimi CLI.\ntools: Agent\n---\n\n# GSD for Kimi CLI\n\nCoordinate installed /skill:gsd-* workflows and route work to generated GSD subagents when a workflow requires an agent handoff.\n`; + const artifacts = buildKimiAgentArtifacts({ rootAgent, subagents }); + const stageDir = fs.mkdtempSync(path.join(require('node:os').tmpdir(), 'gsd-kimi-agents-')); + fs.writeFileSync(path.join(stageDir, 'gsd.yaml'), artifacts.root.yaml); + fs.writeFileSync(path.join(stageDir, 'gsd.md'), artifacts.root.prompt); + const subagentsDir = path.join(stageDir, 'subagents'); + fs.mkdirSync(subagentsDir, { recursive: true }); + for (const artifact of artifacts.subagents) { + fs.writeFileSync(path.join(subagentsDir, `${artifact.name}.yaml`), artifact.yaml); + fs.writeFileSync(path.join(subagentsDir, `${artifact.name}.md`), artifact.prompt); + } + installProfiles.STAGED_DIRS.add(stageDir); + return stageDir; + }, + }; +} + /** * Build a skills kind descriptor. * @@ -306,7 +351,10 @@ function resolveRuntimeArtifactLayout(runtime: string, configDir: string, scope: case 'kimi': kinds = scope === 'global' - ? [skillsKind('skills', 'gsd-', 'convertClaudeCommandToKimiSkill', 'kimi', configDir)] + ? [ + skillsKind('skills', 'gsd-', 'convertClaudeCommandToKimiSkill', 'kimi', configDir), + kimiAgentsKind('agents', 'gsd', configDir), + ] : []; break;