feat(#2093): migrate Kilo onto EoS imperative adapter + hook-bus/model/MCP/dispatch upgrades (ADR-1239)
Fold remaining isKilo logic branches into descriptor-driven reads: finishPermissionWriter (uninstall cleanup), skipSharedHooksInstall (hooks copy), and a skills converter-name registry (the artifactLayout.converter field is now load-bearing, not decorative). frontmatterDialect stays the documented dispatch key for frontmatter (no descriptor field for it). Dead isKilo destructure bindings removed. Byte-identical golden parity for all 16 runtimes (opencode, which shares kilo's combined-family path, verified clean). UPGRADE 1 (hook bus): install .kilo/plugins/gsd-core.js native plugin + extensionEvents:"kilo" + EXTENSION_EVENT_SURFACES.kilo (OpenCode-fork bus). UPGRADE 2 (active model): populate runtimeTierDefaults.kilo + thread modelOverride through convertClaudeToKiloFrontmatter — model no longer stripped from agents. UPGRADE 3 (MCP): document the gsd-core MCP companion under kilo's mcp config key. UPGRADE 4 (named dispatch): agents/*.md mode:subagent roster is the Task-tool dispatch surface (tested); subagentToolkit stays 'undocumented' per AC so dispatch degrades to 'degraded' by design. Model-catalog single-source edit ripples the shared model-catalog.json hash into all 16 golden fixtures (expected). Inline defect fixes (no-defer): stale- bake-guard resolveAgentDir 'agent'->'agents' (was a silent no-op for opencode/ codex), hardcoded 'Removed OpenCode plugin' uninstall log -> generic, and the connect-gsd-mcp-server.md OpenCode mcpServers->mcp doc error. Tests: kilo-imperative-reference (adapter/axes/fail-closed/degradation/ hostBehaviors + widened isKilo source-grep across 4 modules) + kilo-upgrades (plugin parity+load, model-override converter, agents dispatch surface, MCP doc). Matrix + how-to + config docs updated; changeset added. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1548,6 +1548,7 @@ const capabilities = {
|
||||
},
|
||||
"commandStyle": "slash-hyphen",
|
||||
"hooksSurface": "none",
|
||||
"extensionEvents": "kilo",
|
||||
"sandboxTier": "none",
|
||||
"supportTier": 2,
|
||||
"installSurface": "settings-json",
|
||||
@@ -1577,7 +1578,13 @@ const capabilities = {
|
||||
"flatCommandDir": "command",
|
||||
"combinedFamilyInstall": true,
|
||||
"frontmatterDialect": "kilo",
|
||||
"skipUpdateBannerCommand": true
|
||||
"nativePlugin": {
|
||||
"dir": "plugins",
|
||||
"file": "gsd-core.js",
|
||||
"source": ".kilo/plugins/gsd-core.js"
|
||||
},
|
||||
"skipUpdateBannerCommand": true,
|
||||
"skipSharedHooksInstall": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4669,6 +4676,7 @@ const runtimes = {
|
||||
},
|
||||
"commandStyle": "slash-hyphen",
|
||||
"hooksSurface": "none",
|
||||
"extensionEvents": "kilo",
|
||||
"sandboxTier": "none",
|
||||
"supportTier": 2,
|
||||
"installSurface": "settings-json",
|
||||
@@ -4698,7 +4706,13 @@ const runtimes = {
|
||||
"flatCommandDir": "command",
|
||||
"combinedFamilyInstall": true,
|
||||
"frontmatterDialect": "kilo",
|
||||
"skipUpdateBannerCommand": true
|
||||
"nativePlugin": {
|
||||
"dir": "plugins",
|
||||
"file": "gsd-core.js",
|
||||
"source": ".kilo/plugins/gsd-core.js"
|
||||
},
|
||||
"skipUpdateBannerCommand": true,
|
||||
"skipSharedHooksInstall": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -711,7 +711,7 @@ const VALID_HOOK_EVENTS = new Set(['claude', 'gemini']);
|
||||
// DISTINCT from hookEvents (managed-hook dialect): extensionEvents describes the
|
||||
// plugin-owned event subset imperative hosts expose (opencode / pi); 'none' = the
|
||||
// host exposes no extension surface (engine owns the bus, e.g. VS Code).
|
||||
const VALID_EXTENSION_EVENTS = new Set(['opencode', 'pi', 'hermes', 'none']);
|
||||
const VALID_EXTENSION_EVENTS = new Set(['opencode', 'pi', 'hermes', 'kilo', 'none']);
|
||||
const VALID_SANDBOX_TIERS = new Set(['none', 'codex-agent-sandbox']);
|
||||
const VALID_ARTIFACT_KIND_NAMES = new Set(['commands', 'agents', 'skills', 'kimi-agents']);
|
||||
const VALID_ARTIFACT_NESTINGS = new Set(['flat', 'nested']);
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
/**
|
||||
* Stale-bake guard for static-frontmatter runtimes (#1688, follow-up to #1650).
|
||||
*
|
||||
* Runtimes `codex` and `opencode` bake the resolved model ID into each agent's
|
||||
* static config at install time (bin/install.js ~5667-5767 for codex,
|
||||
* ~10008-10026 for opencode). Their task/spawn_agent interfaces do not accept
|
||||
* Runtimes `codex`, `opencode`, and (since #2093) `kilo` bake the resolved
|
||||
* model ID into each agent's static config at install time (bin/install.js
|
||||
* ~5667-5767 for codex, ~10008-10026 for opencode, and the adjacent kilo
|
||||
* branch added by #2093). Their task/spawn_agent interfaces do not accept
|
||||
* an inline `model` parameter, so editing `model_overrides` in
|
||||
* `.planning/config.json` or `~/.gsd/defaults.json` has NO effect until the
|
||||
* user re-runs `gsd install <runtime>` (or `gsd update`). The failure is
|
||||
@@ -25,9 +26,17 @@ const os = require('os');
|
||||
* Runtimes whose agent config is static frontmatter/TOML baked at install time.
|
||||
* MUST stay in sync with the bake paths in bin/install.js. The parity test in
|
||||
* tests/stale-bake-guard.test.cjs asserts this matches the runtimes that
|
||||
* actually emit a baked model: line id #2256 (opencode) and #49/#2256 (codex).
|
||||
* actually emit a baked model: line id #2256 (opencode), #49/#2256 (codex),
|
||||
* and #2093 (kilo).
|
||||
*/
|
||||
const STATIC_FRONTMATTER_RUNTIMES = Object.freeze(['codex', 'opencode']);
|
||||
const STATIC_FRONTMATTER_RUNTIMES = Object.freeze(['codex', 'kilo', 'opencode']);
|
||||
|
||||
/** Per-runtime `gsd install` flag, for the remediation hint in the warning. */
|
||||
const INSTALL_FLAG_BY_RUNTIME = Object.freeze({
|
||||
codex: '--codex',
|
||||
opencode: '--opencode',
|
||||
kilo: '--kilo',
|
||||
});
|
||||
|
||||
const _warnedKeys = new Set();
|
||||
|
||||
@@ -55,7 +64,7 @@ function formatStaleBakeWarning({ runtime, configPath, configMtimeMs, agentMtime
|
||||
const signal = detectStaleBake({ runtime, configMtimeMs, agentMtimeMs });
|
||||
if (!signal) return '';
|
||||
const configDate = new Date(configMtimeMs).toISOString();
|
||||
const installFlag = runtime === 'opencode' ? '--opencode' : '--codex';
|
||||
const installFlag = INSTALL_FLAG_BY_RUNTIME[runtime] || `--${runtime}`;
|
||||
return [
|
||||
`gsd: model config in ${configPath} changed since agents were last baked (${configDate}).`,
|
||||
` Static-frontmatter runtime '${runtime}' ignores the new model_overrides`,
|
||||
@@ -79,13 +88,24 @@ function resolveRuntimeFromConfig(config) {
|
||||
|
||||
/**
|
||||
* Resolve the install root for a runtime's agent files, honoring the same env
|
||||
* vars the installer does (CODEX_HOME, OPENCODE_CONFIG_DIR). Returns the
|
||||
* absolute directory or `null` for unsupported runtimes.
|
||||
* vars the installer does (CODEX_HOME, OPENCODE_CONFIG_DIR, KILO_CONFIG_DIR).
|
||||
* Returns the absolute directory or `null` for unsupported runtimes.
|
||||
*/
|
||||
function resolveAgentDir(runtime, { env = process.env, homedir = os.homedir } = {}) {
|
||||
if (runtime === 'opencode') {
|
||||
const base = (env.OPENCODE_CONFIG_DIR && String(env.OPENCODE_CONFIG_DIR).trim()) || path.join(homedir(), '.config', 'opencode');
|
||||
return path.join(base, 'agent');
|
||||
// #2093 fix: the installer writes agents to `<base>/agents` (plural — see
|
||||
// bin/install.js's universal `agentsDest = path.join(targetDir, 'agents')`,
|
||||
// verified against a live `--opencode --global` install). The prior
|
||||
// singular `agent` never matched the real install output, so this guard's
|
||||
// `findOldestAgentMtime` always hit ENOENT and warnIfStaleBake was a
|
||||
// silent no-op for opencode in production — discovered while wiring the
|
||||
// parallel kilo entry below (same bake mechanism, #2093).
|
||||
return path.join(base, 'agents');
|
||||
}
|
||||
if (runtime === 'kilo') {
|
||||
const base = (env.KILO_CONFIG_DIR && String(env.KILO_CONFIG_DIR).trim()) || path.join(homedir(), '.config', 'kilo');
|
||||
return path.join(base, 'agents');
|
||||
}
|
||||
if (runtime === 'codex') {
|
||||
const base = (env.CODEX_HOME && String(env.CODEX_HOME).trim()) || path.join(homedir(), '.codex');
|
||||
@@ -148,7 +168,7 @@ function findOldestAgentMtime(runtime, { env = process.env, homedir = os.homedir
|
||||
for (const entry of entries) {
|
||||
if (!entry.isFile()) continue;
|
||||
if (!entry.name.startsWith('gsd-')) continue;
|
||||
const isAgentFile = (runtime === 'opencode' && entry.name.endsWith('.md'))
|
||||
const isAgentFile = ((runtime === 'opencode' || runtime === 'kilo') && entry.name.endsWith('.md'))
|
||||
|| (runtime === 'codex' && (entry.name.endsWith('.toml') || entry.name.endsWith('.md')));
|
||||
if (!isAgentFile) continue;
|
||||
try {
|
||||
|
||||
@@ -38,9 +38,9 @@
|
||||
"haiku": { "model": "anthropic/claude-haiku-4-5" }
|
||||
},
|
||||
"kilo": {
|
||||
"opus": null,
|
||||
"sonnet": null,
|
||||
"haiku": null
|
||||
"opus": { "model": "anthropic/claude-opus-4-8" },
|
||||
"sonnet": { "model": "anthropic/claude-sonnet-5" },
|
||||
"haiku": { "model": "anthropic/claude-haiku-4-5" }
|
||||
},
|
||||
"cline": {
|
||||
"opus": null,
|
||||
|
||||
@@ -360,7 +360,8 @@ Built-in tier defaults by runtime:
|
||||
| `opencode` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` |
|
||||
| `copilot` | `claude-opus-4-8` | `claude-sonnet-5` | `claude-haiku-4-5` |
|
||||
| `hermes` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` |
|
||||
| Group B (`kilo`, `cline`, `cursor`, `windsurf`, `augment`, `trae`, `codebuddy`, `antigravity`) | (no built-in default — your runtime handles model selection) | | |
|
||||
| `kilo` | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` |
|
||||
| Group B (`cline`, `cursor`, `windsurf`, `augment`, `trae`, `codebuddy`, `antigravity`) | (no built-in default — your runtime handles model selection) | | |
|
||||
|
||||
Display a table to the user showing the effective configuration:
|
||||
|
||||
@@ -387,8 +388,8 @@ AskUserQuestion([
|
||||
options: [
|
||||
{ label: "Keep current (<runtime>)", description: "Configure overrides for the current runtime." },
|
||||
{ label: "Common runtimes", description: "claude, codex, gemini, qwen" },
|
||||
{ label: "Additional runtimes", description: "opencode, copilot, hermes" },
|
||||
{ label: "Other (Group B or custom)", description: "kilo, cline, cursor, windsurf, augment, trae, codebuddy, antigravity, or a custom runtime string." }
|
||||
{ label: "Additional runtimes", description: "opencode, copilot, hermes, kilo" },
|
||||
{ label: "Other (Group B or custom)", description: "cline, cursor, windsurf, augment, trae, codebuddy, antigravity, or a custom runtime string." }
|
||||
]
|
||||
}
|
||||
])
|
||||
@@ -423,7 +424,8 @@ AskUserQuestion([
|
||||
options: [
|
||||
{ label: "opencode", description: "OpenCode (uses anthropic/ prefix)." },
|
||||
{ label: "copilot", description: "GitHub Copilot." },
|
||||
{ label: "hermes", description: "Hermes (uses anthropic/ prefix)." }
|
||||
{ label: "hermes", description: "Hermes (uses anthropic/ prefix)." },
|
||||
{ label: "kilo", description: "Kilo Code (uses anthropic/ prefix)." }
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
Reference in New Issue
Block a user