Fix runtime skill injection across adapters

This commit is contained in:
Dotta
2026-03-15 07:05:01 -05:00
parent 82f253c310
commit 7675fd0856
27 changed files with 506 additions and 222 deletions

View File

@@ -40,6 +40,8 @@ const PAPERCLIP_SKILL_ROOT_RELATIVE_CANDIDATES = [
export interface PaperclipSkillEntry {
name: string;
source: string;
required?: boolean;
requiredReason?: string | null;
}
function normalizePathSlashes(value: string): string {
@@ -306,12 +308,45 @@ export async function listPaperclipSkillEntries(
.map((entry) => ({
name: entry.name,
source: path.join(root, entry.name),
required: true,
requiredReason: "Bundled Paperclip skills are always available for local adapters.",
}));
} catch {
return [];
}
}
function normalizeConfiguredPaperclipRuntimeSkills(value: unknown): PaperclipSkillEntry[] {
if (!Array.isArray(value)) return [];
const out: PaperclipSkillEntry[] = [];
for (const rawEntry of value) {
const entry = parseObject(rawEntry);
const name = asString(entry.name, "").trim();
const source = asString(entry.source, "").trim();
if (!name || !source) continue;
out.push({
name,
source,
required: asBoolean(entry.required, false),
requiredReason:
typeof entry.requiredReason === "string" && entry.requiredReason.trim().length > 0
? entry.requiredReason.trim()
: null,
});
}
return out;
}
export async function readPaperclipRuntimeSkillEntries(
config: Record<string, unknown>,
moduleDir: string,
additionalCandidates: string[] = [],
): Promise<PaperclipSkillEntry[]> {
const configuredEntries = normalizeConfiguredPaperclipRuntimeSkills(config.paperclipRuntimeSkills);
if (configuredEntries.length > 0) return configuredEntries;
return listPaperclipSkillEntries(moduleDir, additionalCandidates);
}
export async function readPaperclipSkillMarkdown(
moduleDir: string,
skillName: string,
@@ -352,6 +387,20 @@ export function readPaperclipSkillSyncPreference(config: Record<string, unknown>
};
}
export function resolvePaperclipDesiredSkillNames(
config: Record<string, unknown>,
availableEntries: Array<{ name: string; required?: boolean }>,
): string[] {
const preference = readPaperclipSkillSyncPreference(config);
const requiredSkills = availableEntries
.filter((entry) => entry.required)
.map((entry) => entry.name);
if (!preference.explicit) {
return Array.from(new Set(requiredSkills));
}
return Array.from(new Set([...requiredSkills, ...preference.desiredSkills]));
}
export function writePaperclipSkillSyncPreference(
config: Record<string, unknown>,
desiredSkills: string[],

View File

@@ -152,6 +152,8 @@ export interface AdapterSkillEntry {
name: string;
desired: boolean;
managed: boolean;
required?: boolean;
requiredReason?: string | null;
state: AdapterSkillState;
sourcePath?: string | null;
targetPath?: string | null;