diff --git a/src/reinject.ts b/src/reinject.ts index 174ae66..16a7988 100644 --- a/src/reinject.ts +++ b/src/reinject.ts @@ -5,6 +5,7 @@ import type { SessionCompactEvent, Skill, } from "@earendil-works/pi-coding-agent"; +import { existsSync } from "node:fs"; import { expandSkill } from "./expand.js"; import { filterSkillsNeedingReinject, @@ -16,6 +17,13 @@ import type { ExtensionState } from "./state.js"; export const DEFERRED_REINJECT_CUSTOM_TYPE = "skill-reinject:inject"; +function notifySkippedSkill(ctx: ExtensionContext | undefined, skillName: string, reason: string): void { + if (!ctx?.hasUI) { + return; + } + ctx.ui.notify(`skill-reinject: skipped "${skillName}" — ${reason}`, "warning"); +} + /** Names still registered in resourceLoader (SPEC §5.2). */ export function registeredSkillNames(skills: readonly Pick[]): ReadonlySet { return new Set(skills.map((skill) => skill.name)); @@ -66,25 +74,38 @@ export function buildReinjectBlocks( state: ExtensionState, settings: SkillReinjectSettings, registeredSkills: readonly Pick[], + ctx?: ExtensionContext, ): string[] { const registeredByName = new Map(registeredSkills.map((skill) => [skill.name, skill])); const blocks: string[] = []; for (const name of skillNames) { const tracked = state.skills.find((skill) => skill.name === name); - const registered = registeredByName.get(name); - if (!tracked || !registered) { + if (!tracked) { continue; } - blocks.push( - expandSkill( - { - name: tracked.name, - filePath: registered.filePath, - baseDir: registered.baseDir, - }, - settings.suffix, - ), - ); + const registered = registeredByName.get(name); + if (!registered) { + notifySkippedSkill(ctx, name, "no longer registered"); + continue; + } + if (!existsSync(registered.filePath)) { + notifySkippedSkill(ctx, name, "SKILL.md not found on disk"); + continue; + } + try { + blocks.push( + expandSkill( + { + name: tracked.name, + filePath: registered.filePath, + baseDir: registered.baseDir, + }, + settings.suffix, + ), + ); + } catch { + notifySkippedSkill(ctx, name, "SKILL.md not readable"); + } } return blocks; } @@ -95,8 +116,9 @@ export function buildDeferredReinjectContent( state: ExtensionState, settings: SkillReinjectSettings, registeredSkills: readonly Pick[], + ctx?: ExtensionContext, ): string { - return buildReinjectBlocks(pendingNames, state, settings, registeredSkills).join("\n\n"); + return buildReinjectBlocks(pendingNames, state, settings, registeredSkills, ctx).join("\n\n"); } /** Immediate path when agent is idle: first block triggers turn, rest queue as followUp (SPEC §6.5.2). */ @@ -106,8 +128,9 @@ export function sendImmediateReinjectIdle( state: ExtensionState, settings: SkillReinjectSettings, registeredSkills: readonly Pick[], + ctx?: ExtensionContext, ): void { - const blocks = buildReinjectBlocks(skillNames, state, settings, registeredSkills); + const blocks = buildReinjectBlocks(skillNames, state, settings, registeredSkills, ctx); blocks.forEach((block, index) => { pi.sendUserMessage(block, index === 0 ? undefined : { deliverAs: "followUp" }); }); @@ -120,8 +143,9 @@ export function sendImmediateReinjectAllFollowUp( state: ExtensionState, settings: SkillReinjectSettings, registeredSkills: readonly Pick[], + ctx?: ExtensionContext, ): void { - for (const block of buildReinjectBlocks(skillNames, state, settings, registeredSkills)) { + for (const block of buildReinjectBlocks(skillNames, state, settings, registeredSkills, ctx)) { pi.sendUserMessage(block, { deliverAs: "followUp" }); } } @@ -134,12 +158,13 @@ export function tryConsumeDeferredReinject( state: ExtensionState, settings: SkillReinjectSettings, registeredSkills: readonly Pick[], + ctx?: ExtensionContext, ): BeforeAgentStartEventResult | undefined { if (state.pendingReinject.length === 0) { return undefined; } const pendingNames = [...state.pendingReinject]; - const content = buildDeferredReinjectContent(pendingNames, state, settings, registeredSkills); + const content = buildDeferredReinjectContent(pendingNames, state, settings, registeredSkills, ctx); if (!content) { state.pendingReinject = []; return undefined;