2059f6033b
Builds one injected custom message from pendingReinject via expandSkill, clears the queue only after content is built successfully. Co-authored-by: Cursor <cursoragent@cursor.com>
118 lines
3.5 KiB
TypeScript
118 lines
3.5 KiB
TypeScript
import type {
|
|
BeforeAgentStartEventResult,
|
|
ExtensionContext,
|
|
SessionCompactEvent,
|
|
Skill,
|
|
} from "@earendil-works/pi-coding-agent";
|
|
import { expandSkill } from "./expand.js";
|
|
import {
|
|
filterSkillsNeedingReinject,
|
|
getKeptEntries,
|
|
skillsPresentInKeptWindow,
|
|
} from "./kept.js";
|
|
import type { SkillReinjectSettings } from "./settings.js";
|
|
import type { ExtensionState } from "./state.js";
|
|
|
|
export const DEFERRED_REINJECT_CUSTOM_TYPE = "skill-reinject:inject";
|
|
|
|
/** Names still registered in resourceLoader (SPEC §5.2). */
|
|
export function registeredSkillNames(skills: readonly Pick<Skill, "name">[]): ReadonlySet<string> {
|
|
return new Set(skills.map((skill) => skill.name));
|
|
}
|
|
|
|
/**
|
|
* Skill names to re-inject after compaction: tracked, absent from kept window, still registered (SPEC §5.2).
|
|
* `registeredSkills` comes from resourceLoader — ExtensionContext has no getSkills(); wired in index.ts.
|
|
*/
|
|
export function planReinject(
|
|
state: ExtensionState,
|
|
_settings: SkillReinjectSettings,
|
|
ctx: ExtensionContext,
|
|
compactionEvent: SessionCompactEvent,
|
|
registeredSkills: readonly Pick<Skill, "name">[],
|
|
): string[] {
|
|
const branch = ctx.sessionManager.getBranch();
|
|
const keptEntries = getKeptEntries(branch, compactionEvent.compactionEntry.firstKeptEntryId);
|
|
const trackedNames = state.skills.map((skill) => skill.name);
|
|
const keptPresent = skillsPresentInKeptWindow(keptEntries, trackedNames);
|
|
return filterSkillsNeedingReinject(
|
|
state.skills,
|
|
keptPresent,
|
|
registeredSkillNames(registeredSkills),
|
|
);
|
|
}
|
|
|
|
/** Defer path on session_compact: queue planned skills without sendUserMessage (SPEC §6.5.1, §16.2). */
|
|
export function enqueueDeferredReinjectFromCompact(
|
|
state: ExtensionState,
|
|
settings: SkillReinjectSettings,
|
|
ctx: ExtensionContext,
|
|
compactionEvent: SessionCompactEvent,
|
|
registeredSkills: readonly Pick<Skill, "name">[],
|
|
): void {
|
|
state.pendingReinject = planReinject(
|
|
state,
|
|
settings,
|
|
ctx,
|
|
compactionEvent,
|
|
registeredSkills,
|
|
);
|
|
}
|
|
|
|
/** Combined skill-block user text for pending names in queue order (SPEC §5.3). */
|
|
export function buildDeferredReinjectContent(
|
|
pendingNames: readonly string[],
|
|
state: ExtensionState,
|
|
settings: SkillReinjectSettings,
|
|
registeredSkills: readonly Pick<Skill, "name" | "filePath" | "baseDir">[],
|
|
): string {
|
|
const registeredByName = new Map(registeredSkills.map((skill) => [skill.name, skill]));
|
|
const blocks: string[] = [];
|
|
for (const name of pendingNames) {
|
|
const tracked = state.skills.find((skill) => skill.name === name);
|
|
const registered = registeredByName.get(name);
|
|
if (!tracked || !registered) {
|
|
continue;
|
|
}
|
|
blocks.push(
|
|
expandSkill(
|
|
{
|
|
name: tracked.name,
|
|
filePath: registered.filePath,
|
|
baseDir: registered.baseDir,
|
|
},
|
|
settings.suffix,
|
|
),
|
|
);
|
|
}
|
|
return blocks.join("\n\n");
|
|
}
|
|
|
|
/**
|
|
* Defer path on before_agent_start: inject one combined message, then clear queue (SPEC §6.5.1).
|
|
* Returns undefined when pendingReinject is empty.
|
|
*/
|
|
export function tryConsumeDeferredReinject(
|
|
state: ExtensionState,
|
|
settings: SkillReinjectSettings,
|
|
registeredSkills: readonly Pick<Skill, "name" | "filePath" | "baseDir">[],
|
|
): BeforeAgentStartEventResult | undefined {
|
|
if (state.pendingReinject.length === 0) {
|
|
return undefined;
|
|
}
|
|
const pendingNames = [...state.pendingReinject];
|
|
const content = buildDeferredReinjectContent(pendingNames, state, settings, registeredSkills);
|
|
if (!content) {
|
|
state.pendingReinject = [];
|
|
return undefined;
|
|
}
|
|
state.pendingReinject = [];
|
|
return {
|
|
message: {
|
|
customType: DEFERRED_REINJECT_CUSTOM_TYPE,
|
|
content,
|
|
display: true,
|
|
},
|
|
};
|
|
}
|