feat(#2505): Phase 3 — agent-skills fallback for non-dispatchable runtimes (#2521)

* feat(#2454): PR 2 — cmdAgentSkills fallback reads installed agent prompt

When no agent_skills config entry exists for a given agent type (the common
case on AGENTS-native runtimes), cmdAgentSkills previously returned empty
output. Workflows that inject ${AGENT_SKILLS_*} into subagent dispatch
prompts then carried nothing — the persona was lost.

The fallback: resolve the runtime's agents directory via checkAgentsInstalled
and read <agentsDir>/<agentType>.md. The installed agent prompt content
(now present for kimi-code via the flat-skills install layout) flows into
the dispatch prompt so the persona survives even without explicit config
opt-in. This is the reporter's suggested fix #2 from #2454.

The fallback triggers for ALL runtimes (not just kimi-code) when no config
entry exists — it is strictly additive (returns content the previous empty
path could not). If the agent file is not found on disk, the block stays
empty (same as before).

* docs(changeset): Phase 3 agent-skills fallback Added (#2510)

* docs(changeset): backfill PR #2521 for Phase 3 (#2510)
This commit is contained in:
Tom Boucher
2026-07-22 01:25:41 -04:00
committed by GitHub
parent c2a305c44d
commit 936a345381
2 changed files with 38 additions and 1 deletions

View File

@@ -0,0 +1,6 @@
---
type: Added
pr: 2521
---
<!-- docs-exempt: Phase 3 is an internal behavior fallback for `gsd-tools query agent-skills` on non-Claude runtimes — no user-facing command surface or config to document. -->
**`gsd-tools query agent-skills <name>` returns the installed agent's prompt content on non-Claude runtimes** — previously, when a non-Claude runtime (kimi, kimi-code, opencode, kilo, etc.) had no explicit `agent_skills` config entry, `buildAgentSkillsBlock` returned empty and the `${AGENT_SKILLS_*}` workflow injection carried no persona. Phase 3 adds a fallback in `cmdAgentSkills`: on non-Claude runtimes, when the configured block is empty, resolve the runtime's agents directory via `checkAgentsInstalled(runtime)` and read `<agentsDir>/<agentType>.md` as the block. Gated to `runtime !== 'claude'` (Claude supports named dispatch and its `${AGENT_SKILLS_*}` contract is a skills-injection path, not a persona fallback). (#2510)

View File

@@ -2258,13 +2258,44 @@ function cmdAgentSkills(
const projectRoot = findProjectRoot(cwd);
const { config, source, degraded } = loadConfigResolved(projectRoot);
const diagnostics = { warnings: [] as string[] };
const block = buildAgentSkillsBlock(
let block = buildAgentSkillsBlock(
config,
agentType,
projectRoot,
diagnostics,
);
// #2454: Agent prompt fallback for AGENTS-native runtimes where named
// subagents are NOT dispatchable (kimi-code, kimi, opencode, kilo, etc.).
// On these runtimes, workflows inject ${AGENT_SKILLS_*} into the dispatch
// prompt of a built-in subagent (coder/explore/plan). If no
// model_profile_overrides or agent_skills config entry exists, the block
// is empty — but the agent's prompt CONTENT is installed on disk at the
// runtime's agents directory. Read it as a fallback so the persona survives
// the dispatch even without explicit config opt-in.
//
// GATED to non-claude runtimes: Claude Code supports named subagent dispatch
// and its ${AGENT_SKILLS_*} contract is a skills-injection path, not a
// persona fallback. Triggering the fallback for claude would change the
// documented "unconfigured → empty block" contract that agent-skills tests
// pin.
if (!block) {
const runtime = (config && (config['runtime'] as string)) || process.env['GSD_RUNTIME'] || 'claude';
if (runtime !== 'claude') {
const agentCheck = checkAgentsInstalled(runtime) as unknown as { agents_dir?: string } | null;
const agentsDir = agentCheck?.agents_dir;
if (typeof agentsDir === 'string' && agentsDir.length > 0) {
const agentFile = path.join(agentsDir, `${agentType}.md`);
try {
const content = platformReadSync(agentFile);
if (content && content.length > 0) {
block = content;
}
} catch { /* agent file not found — fall through to empty block */ }
}
}
}
// Compute configured + reason for diagnostic output.
const agentSkillsMap = (config && config['agent_skills'] && typeof config['agent_skills'] === 'object')
? config['agent_skills'] as Record<string, unknown>