@compilr-dev/sdk 0.24.2 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.js +29 -0
- package/dist/config.d.ts +13 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/skills/catalog.d.ts +34 -0
- package/dist/skills/catalog.js +57 -0
- package/dist/skills/index.d.ts +6 -2
- package/dist/skills/index.js +4 -2
- package/dist/skills/load-tool.d.ts +30 -0
- package/dist/skills/load-tool.js +53 -0
- package/dist/skills/loader.d.ts +16 -0
- package/dist/skills/loader.js +59 -1
- package/dist/skills/resolver.d.ts +26 -0
- package/dist/skills/resolver.js +27 -0
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -15,6 +15,8 @@ import { CapabilityManager } from './capabilities/manager.js';
|
|
|
15
15
|
import { CapabilityContext } from './capabilities/context.js';
|
|
16
16
|
import { createCapabilityHook } from './capabilities/hook.js';
|
|
17
17
|
import { createLoadCapabilityTool } from './capabilities/load-tool.js';
|
|
18
|
+
import { createLoadSkillTool } from './skills/load-tool.js';
|
|
19
|
+
import { generateSkillCatalog, toCatalogEntry } from './skills/catalog.js';
|
|
18
20
|
import { resolveProfileGroups, resolveUpfrontGroups } from './capabilities/profile-resolver.js';
|
|
19
21
|
import { MetaToolsRegistry, createMetaTools } from './meta-tools/registry.js';
|
|
20
22
|
/**
|
|
@@ -116,13 +118,40 @@ class CompilrAgentImpl {
|
|
|
116
118
|
else {
|
|
117
119
|
systemPrompt = preset.systemPrompt;
|
|
118
120
|
}
|
|
121
|
+
/*
|
|
122
|
+
Skills the model may choose from. The host has already filtered these for this agent — see
|
|
123
|
+
`CompilrAgentConfig.skills`. Two things follow: a catalogue of names and DESCRIPTIONS in the
|
|
124
|
+
prompt, and `load_skill` to fetch a body on demand. Bodies never go in the prompt; `design`
|
|
125
|
+
alone is 3,724 characters.
|
|
126
|
+
*/
|
|
127
|
+
const eligibleSkills = config?.skills ?? [];
|
|
128
|
+
const loadedSkillNames = [];
|
|
119
129
|
// Assemble all tools from preset + config
|
|
120
130
|
let allTools = deduplicateTools(assembleTools(preset, config?.tools));
|
|
131
|
+
if (eligibleSkills.length > 0) {
|
|
132
|
+
allTools = [
|
|
133
|
+
...allTools,
|
|
134
|
+
createLoadSkillTool({
|
|
135
|
+
// Re-read on every call: an edit rebuilds the agent, so this closure is rebuilt too,
|
|
136
|
+
// but reading through the array keeps the tool honest if that ever stops being true.
|
|
137
|
+
available: () => eligibleSkills,
|
|
138
|
+
onLoaded: (name) => {
|
|
139
|
+
if (!loadedSkillNames.includes(name))
|
|
140
|
+
loadedSkillNames.push(name);
|
|
141
|
+
},
|
|
142
|
+
}),
|
|
143
|
+
];
|
|
144
|
+
}
|
|
121
145
|
// Replace default suggest tool with callback-wired version when onSuggest is provided
|
|
122
146
|
if (config?.onSuggest) {
|
|
123
147
|
const wiredSuggest = createSuggestTool({ onSuggest: config.onSuggest });
|
|
124
148
|
allTools = allTools.map((t) => t.definition.name === 'suggest' ? wiredSuggest : t);
|
|
125
149
|
}
|
|
150
|
+
if (eligibleSkills.length > 0) {
|
|
151
|
+
const catalog = generateSkillCatalog(eligibleSkills.map(toCatalogEntry), loadedSkillNames);
|
|
152
|
+
if (catalog)
|
|
153
|
+
systemPrompt = systemPrompt ? `${systemPrompt}\n\n${catalog}` : catalog;
|
|
154
|
+
}
|
|
126
155
|
// Build context manager if configured
|
|
127
156
|
let contextManager;
|
|
128
157
|
const extendedContext = config?.context?.extendedContext ?? false;
|
package/dist/config.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { LLMProvider, Message, Tool, ToolPermission, HooksConfig, AnchorInp
|
|
|
5
5
|
import type { Preset } from './presets/types.js';
|
|
6
6
|
import type { ToolProfile } from './team/tool-config.js';
|
|
7
7
|
import type { ConditionalModule } from './capabilities/hook.js';
|
|
8
|
+
import type { CustomSkill } from './skills/types.js';
|
|
8
9
|
/**
|
|
9
10
|
* Dynamic capability loading configuration.
|
|
10
11
|
*
|
|
@@ -298,6 +299,18 @@ export interface CompilrAgentConfig {
|
|
|
298
299
|
* Omit or set enabled: false for the legacy all-tools-upfront mode.
|
|
299
300
|
*/
|
|
300
301
|
capabilities?: CapabilitiesConfig;
|
|
302
|
+
/**
|
|
303
|
+
* Skills this agent may load, ALREADY filtered for eligibility by the host.
|
|
304
|
+
*
|
|
305
|
+
* ⚠️ THE HOST FILTERS, THE SDK PRESENTS — the same split as MCP grants. The host owns the
|
|
306
|
+
* skill directories and knows which agent it is building (the factory receives the TeamAgent),
|
|
307
|
+
* so it applies `resolveSkillsForAgent`; the SDK turns the result into a catalogue the model
|
|
308
|
+
* reads and a `load_skill` tool it can call. Passing an unfiltered pool here would hand every
|
|
309
|
+
* agent every skill, which is the defect this whole line of work started from.
|
|
310
|
+
*
|
|
311
|
+
* Omit for no skills — the catalogue is then absent rather than empty.
|
|
312
|
+
*/
|
|
313
|
+
skills?: CustomSkill[];
|
|
301
314
|
}
|
|
302
315
|
/**
|
|
303
316
|
* High-level agent interface
|
package/dist/index.d.ts
CHANGED
|
@@ -68,9 +68,11 @@ export { createPlatformTools, createProjectTools, createWorkItemTools, createDoc
|
|
|
68
68
|
export type { ProjectAnchorStoreConfig, FileArtifactServiceConfig, ImageToolsConfig, ImageResizer, } from './platform/index.js';
|
|
69
69
|
export { STEP_ORDER, GUIDED_STEP_CRITERIA, getNextStep, isValidTransition, getStepCriteria, formatStepDisplay, getStepNumber, } from './platform/index.js';
|
|
70
70
|
export type { CustomSkill, CompilrSkillExtension, ForkedFromMarker, SkillEligibilityContext, SkillCollision, SkillDiffLine, SkillValidationIssue, ScopeConfig, SkillResolution, } from './skills/index.js';
|
|
71
|
-
export { RESERVED_SKILL_NAMES, isReservedSkillName, parseSkillMarkdown, loadSkillsFromDir, resolveLayeredSkills, resolveSkillsForAgent, detectCollisions, formatCollisionWarnings, diffForkVsUpstream, buildForkContent, buildNewSkillContent, validateSkill as validateSkillQuality, getSkillsDir, getSkillFolder, getSkillFile, ensureSkillsDir, isValidSkillName, getScopeConfigPath, readSkillScopeConfig, readSkillScopeConfigSync, writeSkillScopeConfig, getSkillBindings, resolveSkillBinding, } from './skills/index.js';
|
|
71
|
+
export { RESERVED_SKILL_NAMES, isReservedSkillName, parseSkillMarkdown, loadSkillsFromDir, loadInstalledSkills, resolveLayeredSkills, resolveSkillsForAgent, resolveSkillsForTeamAgent, detectCollisions, formatCollisionWarnings, diffForkVsUpstream, buildForkContent, buildNewSkillContent, validateSkill as validateSkillQuality, getSkillsDir, getSkillFolder, getSkillFile, ensureSkillsDir, isValidSkillName, getScopeConfigPath, readSkillScopeConfig, readSkillScopeConfigSync, writeSkillScopeConfig, getSkillBindings, resolveSkillBinding, } from './skills/index.js';
|
|
72
72
|
export type { SkillScope, SkillPromptResolution, AvailableSkillEntry, SkillSources, } from './skills/index.js';
|
|
73
73
|
export { resolveSkillPrompt, getAllAvailableSkills } from './skills/index.js';
|
|
74
|
+
export { generateSkillCatalog, toCatalogEntry, createLoadSkillTool } from './skills/index.js';
|
|
75
|
+
export type { SkillCatalogEntry, SkillSource } from './skills/index.js';
|
|
74
76
|
export { platformSkills, designSkill, sketchSkill, prdSkill, refineSkill, refineItemSkill, architectureSkill, sessionNotesSkill, buildSkill, scaffoldSkill, outlineSkill, literatureReviewSkill, draftSectionSkill, peerReviewSkill, researchScaffoldSkill, businessVisionSkill, marketAnalysisSkill, competitorAnalysisSkill, financialModelSkill, pitchOutlineSkill, businessReviewSkill, brandSetupSkill, contentStrategySkill, contentCalendarSkill, createContentSkill, contentReviewSkill, curriculumDesignSkill, lessonPlanSkill, assessmentDesignSkill, courseReviewSkill, bookOutlineSkill, characterDesignSkill, plotThreadsSkill, sceneBreakdownSkill, bookReviewSkill, } from './skills/index.js';
|
|
75
77
|
export { ACTION_REGISTRY, getActionsForContext, getActionById, resolveActionPrompt, buildContextSummary, getSuggestedRole, } from './actions/index.js';
|
|
76
78
|
export type { ActionContext, ActionDefinition } from './actions/index.js';
|
package/dist/index.js
CHANGED
|
@@ -152,8 +152,9 @@ export { createPlatformTools, createProjectTools, createWorkItemTools, createDoc
|
|
|
152
152
|
// Platform Workflow (pure step-criteria logic)
|
|
153
153
|
// =============================================================================
|
|
154
154
|
export { STEP_ORDER, GUIDED_STEP_CRITERIA, getNextStep, isValidTransition, getStepCriteria, formatStepDisplay, getStepNumber, } from './platform/index.js';
|
|
155
|
-
export { RESERVED_SKILL_NAMES, isReservedSkillName, parseSkillMarkdown, loadSkillsFromDir, resolveLayeredSkills, resolveSkillsForAgent, detectCollisions, formatCollisionWarnings, diffForkVsUpstream, buildForkContent, buildNewSkillContent, validateSkill as validateSkillQuality, getSkillsDir, getSkillFolder, getSkillFile, ensureSkillsDir, isValidSkillName, getScopeConfigPath, readSkillScopeConfig, readSkillScopeConfigSync, writeSkillScopeConfig, getSkillBindings, resolveSkillBinding, } from './skills/index.js';
|
|
155
|
+
export { RESERVED_SKILL_NAMES, isReservedSkillName, parseSkillMarkdown, loadSkillsFromDir, loadInstalledSkills, resolveLayeredSkills, resolveSkillsForAgent, resolveSkillsForTeamAgent, detectCollisions, formatCollisionWarnings, diffForkVsUpstream, buildForkContent, buildNewSkillContent, validateSkill as validateSkillQuality, getSkillsDir, getSkillFolder, getSkillFile, ensureSkillsDir, isValidSkillName, getScopeConfigPath, readSkillScopeConfig, readSkillScopeConfigSync, writeSkillScopeConfig, getSkillBindings, resolveSkillBinding, } from './skills/index.js';
|
|
156
156
|
export { resolveSkillPrompt, getAllAvailableSkills } from './skills/index.js';
|
|
157
|
+
export { generateSkillCatalog, toCatalogEntry, createLoadSkillTool } from './skills/index.js';
|
|
157
158
|
export { platformSkills, designSkill, sketchSkill, prdSkill, refineSkill, refineItemSkill, architectureSkill, sessionNotesSkill, buildSkill, scaffoldSkill, outlineSkill, literatureReviewSkill, draftSectionSkill, peerReviewSkill, researchScaffoldSkill, businessVisionSkill, marketAnalysisSkill, competitorAnalysisSkill, financialModelSkill, pitchOutlineSkill, businessReviewSkill, brandSetupSkill, contentStrategySkill, contentCalendarSkill, createContentSkill, contentReviewSkill, curriculumDesignSkill, lessonPlanSkill, assessmentDesignSkill, courseReviewSkill, bookOutlineSkill, characterDesignSkill, plotThreadsSkill, sceneBreakdownSkill, bookReviewSkill, } from './skills/index.js';
|
|
158
159
|
// =============================================================================
|
|
159
160
|
// Contextual Actions (skill invocations with context)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill catalogue — what the model reads to decide which skill to load.
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ THIS IS THE PIECE THAT MAKES A SKILL A SKILL. Until it existed, `SKILL.md` files were
|
|
5
|
+
* user-invoked prompt macros: the host pasted a body into the chat when someone typed a slash
|
|
6
|
+
* command, the model never saw a list, and `description` — the field the Anthropic format exists
|
|
7
|
+
* to make the model read — was decoration. Every per-agent skill setting in the product was
|
|
8
|
+
* therefore inert, because there was nothing for it to restrict.
|
|
9
|
+
*
|
|
10
|
+
* Modelled deliberately on `capabilities/catalog.ts`: the same shape already works for tools
|
|
11
|
+
* (`## Available Capabilities` + `load_capability`). Skills are that pattern with prompts.
|
|
12
|
+
*
|
|
13
|
+
* ⚠️ DESCRIPTIONS ONLY, NEVER BODIES. A body is what `load_skill` returns. Putting bodies here
|
|
14
|
+
* would be the mistake the capability system was built to avoid — measured on the same codebase,
|
|
15
|
+
* making language packs lazy saved ~8.4K tokens per turn. `design` alone is 3,724 characters.
|
|
16
|
+
*/
|
|
17
|
+
import type { CustomSkill } from './types.js';
|
|
18
|
+
/** A skill as the model sees it before loading: what it is for, not what it says. */
|
|
19
|
+
export interface SkillCatalogEntry {
|
|
20
|
+
name: string;
|
|
21
|
+
description: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Build the system-prompt section listing the skills this agent may load.
|
|
25
|
+
*
|
|
26
|
+
* @param skills - the skills this agent is eligible for, already filtered by
|
|
27
|
+
* `resolveSkillsForAgent` (grants, targeting, project type, tool guards)
|
|
28
|
+
* @param loadedNames - skills already loaded this session, so the model does not reload them
|
|
29
|
+
* @returns the section, or '' when there is nothing to offer — an empty heading reads as a
|
|
30
|
+
* capability the agent has and cannot use
|
|
31
|
+
*/
|
|
32
|
+
export declare function generateSkillCatalog(skills: readonly SkillCatalogEntry[], loadedNames?: readonly string[]): string;
|
|
33
|
+
/** Narrow a resolved skill to what the catalogue publishes. */
|
|
34
|
+
export declare function toCatalogEntry(skill: CustomSkill): SkillCatalogEntry;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill catalogue — what the model reads to decide which skill to load.
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ THIS IS THE PIECE THAT MAKES A SKILL A SKILL. Until it existed, `SKILL.md` files were
|
|
5
|
+
* user-invoked prompt macros: the host pasted a body into the chat when someone typed a slash
|
|
6
|
+
* command, the model never saw a list, and `description` — the field the Anthropic format exists
|
|
7
|
+
* to make the model read — was decoration. Every per-agent skill setting in the product was
|
|
8
|
+
* therefore inert, because there was nothing for it to restrict.
|
|
9
|
+
*
|
|
10
|
+
* Modelled deliberately on `capabilities/catalog.ts`: the same shape already works for tools
|
|
11
|
+
* (`## Available Capabilities` + `load_capability`). Skills are that pattern with prompts.
|
|
12
|
+
*
|
|
13
|
+
* ⚠️ DESCRIPTIONS ONLY, NEVER BODIES. A body is what `load_skill` returns. Putting bodies here
|
|
14
|
+
* would be the mistake the capability system was built to avoid — measured on the same codebase,
|
|
15
|
+
* making language packs lazy saved ~8.4K tokens per turn. `design` alone is 3,724 characters.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Build the system-prompt section listing the skills this agent may load.
|
|
19
|
+
*
|
|
20
|
+
* @param skills - the skills this agent is eligible for, already filtered by
|
|
21
|
+
* `resolveSkillsForAgent` (grants, targeting, project type, tool guards)
|
|
22
|
+
* @param loadedNames - skills already loaded this session, so the model does not reload them
|
|
23
|
+
* @returns the section, or '' when there is nothing to offer — an empty heading reads as a
|
|
24
|
+
* capability the agent has and cannot use
|
|
25
|
+
*/
|
|
26
|
+
export function generateSkillCatalog(skills, loadedNames = []) {
|
|
27
|
+
if (skills.length === 0)
|
|
28
|
+
return '';
|
|
29
|
+
const lines = [
|
|
30
|
+
'## Available Skills',
|
|
31
|
+
'',
|
|
32
|
+
'Procedures you can load when a task calls for one. Read the descriptions and call ' +
|
|
33
|
+
'`load_skill` with the name when it fits what the user is asking for — do not load one ' +
|
|
34
|
+
'speculatively, and do not describe a skill to the user instead of using it.',
|
|
35
|
+
'',
|
|
36
|
+
];
|
|
37
|
+
for (const skill of skills) {
|
|
38
|
+
lines.push(`- **${skill.name}** — ${collapse(skill.description)}`);
|
|
39
|
+
}
|
|
40
|
+
if (loadedNames.length > 0) {
|
|
41
|
+
lines.push('');
|
|
42
|
+
lines.push(`Already loaded: ${loadedNames.join(', ')}`);
|
|
43
|
+
}
|
|
44
|
+
return lines.join('\n');
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Anthropic-format descriptions are deliberately long and often multi-line — the format asks for
|
|
48
|
+
* trigger conditions and example phrasings. A newline inside a list item breaks the list, so the
|
|
49
|
+
* catalogue reads as prose and the model loses the one-skill-per-line structure it is scanning.
|
|
50
|
+
*/
|
|
51
|
+
function collapse(description) {
|
|
52
|
+
return description.replace(/\s*\n+\s*/g, ' ').trim();
|
|
53
|
+
}
|
|
54
|
+
/** Narrow a resolved skill to what the catalogue publishes. */
|
|
55
|
+
export function toCatalogEntry(skill) {
|
|
56
|
+
return { name: skill.name, description: skill.description };
|
|
57
|
+
}
|
package/dist/skills/index.d.ts
CHANGED
|
@@ -3,9 +3,13 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export type { CustomSkill, CompilrSkillExtension, ForkedFromMarker } from './types.js';
|
|
5
5
|
export { RESERVED_SKILL_NAMES, isReservedSkillName } from './types.js';
|
|
6
|
-
export { parseSkillMarkdown, loadSkillsFromDir } from './loader.js';
|
|
6
|
+
export { parseSkillMarkdown, loadSkillsFromDir, loadInstalledSkills } from './loader.js';
|
|
7
7
|
export type { SkillEligibilityContext } from './resolver.js';
|
|
8
|
-
export { resolveLayeredSkills, resolveSkillsForAgent } from './resolver.js';
|
|
8
|
+
export { resolveLayeredSkills, resolveSkillsForAgent, resolveSkillsForTeamAgent, } from './resolver.js';
|
|
9
|
+
export { generateSkillCatalog, toCatalogEntry } from './catalog.js';
|
|
10
|
+
export type { SkillCatalogEntry } from './catalog.js';
|
|
11
|
+
export { createLoadSkillTool } from './load-tool.js';
|
|
12
|
+
export type { SkillSource } from './load-tool.js';
|
|
9
13
|
export type { SkillCollision, SkillDiffLine, SkillValidationIssue, ScopeConfig, SkillResolution, } from './operations.js';
|
|
10
14
|
export { detectCollisions, formatCollisionWarnings, diffForkVsUpstream, buildForkContent, buildNewSkillContent, validateSkill, } from './operations.js';
|
|
11
15
|
export type { SkillScope } from './paths.js';
|
package/dist/skills/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { RESERVED_SKILL_NAMES, isReservedSkillName } from './types.js';
|
|
2
|
-
export { parseSkillMarkdown, loadSkillsFromDir } from './loader.js';
|
|
3
|
-
export { resolveLayeredSkills, resolveSkillsForAgent } from './resolver.js';
|
|
2
|
+
export { parseSkillMarkdown, loadSkillsFromDir, loadInstalledSkills } from './loader.js';
|
|
3
|
+
export { resolveLayeredSkills, resolveSkillsForAgent, resolveSkillsForTeamAgent, } from './resolver.js';
|
|
4
|
+
export { generateSkillCatalog, toCatalogEntry } from './catalog.js';
|
|
5
|
+
export { createLoadSkillTool } from './load-tool.js';
|
|
4
6
|
export { detectCollisions, formatCollisionWarnings, diffForkVsUpstream, buildForkContent, buildNewSkillContent, validateSkill, } from './operations.js';
|
|
5
7
|
export { getSkillsDir, getSkillFolder, getSkillFile, ensureSkillsDir, isValidSkillName, getScopeConfigPath, readScopeConfig as readSkillScopeConfig, readScopeConfigSync as readSkillScopeConfigSync, writeScopeConfig as writeSkillScopeConfig, getAllBindings as getSkillBindings, resolveBinding as resolveSkillBinding, } from './paths.js';
|
|
6
8
|
export { resolveSkillPrompt, getAllAvailableSkills } from './prompt-resolver.js';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* load_skill — how an agent reaches a skill body.
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ BEFORE THIS TOOL THERE WAS NO PATH FROM AN AGENT TO A SKILL. Skills were delivered only by
|
|
5
|
+
* the HOST, when a user typed a slash command; the body arrived as a user message and the model
|
|
6
|
+
* had no way to ask for one. `canvas_guide` exists precisely as a hand-built escape hatch around
|
|
7
|
+
* that gap, exposing 8 of ~48 bodies, and its own comment says so. Meanwhile ~30 platform prompts
|
|
8
|
+
* instruct the model to "use the refine skill" — advice it could only relay to the user.
|
|
9
|
+
*
|
|
10
|
+
* Deliberately unlike `load_capability`, which loads a pack and lets the NEXT turn see new tools:
|
|
11
|
+
* a skill body is content, not capability, so it is returned directly and is usable in the same
|
|
12
|
+
* turn. Nothing needs to be rebuilt.
|
|
13
|
+
*/
|
|
14
|
+
import type { Tool } from '@compilr-dev/agents';
|
|
15
|
+
interface LoadSkillInput {
|
|
16
|
+
skill: string;
|
|
17
|
+
}
|
|
18
|
+
/** What `load_skill` can reach: the skills this agent is eligible for, resolved per turn. */
|
|
19
|
+
export interface SkillSource {
|
|
20
|
+
/** Eligible skills — already filtered by grants, targeting, project type and tool guards. */
|
|
21
|
+
available: () => {
|
|
22
|
+
name: string;
|
|
23
|
+
description: string;
|
|
24
|
+
prompt: string;
|
|
25
|
+
}[];
|
|
26
|
+
/** Called when a skill is successfully loaded, so the catalogue can mark it. */
|
|
27
|
+
onLoaded?: (name: string) => void;
|
|
28
|
+
}
|
|
29
|
+
export declare function createLoadSkillTool(source: SkillSource): Tool<LoadSkillInput>;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* load_skill — how an agent reaches a skill body.
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ BEFORE THIS TOOL THERE WAS NO PATH FROM AN AGENT TO A SKILL. Skills were delivered only by
|
|
5
|
+
* the HOST, when a user typed a slash command; the body arrived as a user message and the model
|
|
6
|
+
* had no way to ask for one. `canvas_guide` exists precisely as a hand-built escape hatch around
|
|
7
|
+
* that gap, exposing 8 of ~48 bodies, and its own comment says so. Meanwhile ~30 platform prompts
|
|
8
|
+
* instruct the model to "use the refine skill" — advice it could only relay to the user.
|
|
9
|
+
*
|
|
10
|
+
* Deliberately unlike `load_capability`, which loads a pack and lets the NEXT turn see new tools:
|
|
11
|
+
* a skill body is content, not capability, so it is returned directly and is usable in the same
|
|
12
|
+
* turn. Nothing needs to be rebuilt.
|
|
13
|
+
*/
|
|
14
|
+
import { defineTool, createSuccessResult, createErrorResult } from '@compilr-dev/agents';
|
|
15
|
+
export function createLoadSkillTool(source) {
|
|
16
|
+
return defineTool({
|
|
17
|
+
name: 'load_skill',
|
|
18
|
+
description: 'Load a skill: returns the full procedure for a task. ' +
|
|
19
|
+
'See the "Available Skills" section in your instructions for names and what each is for. ' +
|
|
20
|
+
'Load one when the user asks for something it covers, then follow it.',
|
|
21
|
+
inputSchema: {
|
|
22
|
+
type: 'object',
|
|
23
|
+
properties: {
|
|
24
|
+
skill: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
description: 'Skill name exactly as listed in "Available Skills".',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
required: ['skill'],
|
|
30
|
+
},
|
|
31
|
+
execute: (input) => {
|
|
32
|
+
const { skill } = input;
|
|
33
|
+
const available = source.available();
|
|
34
|
+
const found = available.find((s) => s.name === skill);
|
|
35
|
+
if (!found) {
|
|
36
|
+
/*
|
|
37
|
+
⚠️ NAME WHAT IS REACHABLE. A bare "not found" leaves the model guessing between "typo",
|
|
38
|
+
"not granted to me" and "does not exist" — and the usual recovery from guessing is to
|
|
39
|
+
invent a plausible name and try again. The eligible list is short by construction.
|
|
40
|
+
*/
|
|
41
|
+
const names = available.map((s) => s.name);
|
|
42
|
+
return Promise.resolve(createErrorResult(names.length === 0
|
|
43
|
+
? `No skills are available to you. Do not retry — proceed without one.`
|
|
44
|
+
: `No skill named "${skill}" is available to you. Available: ${names.join(', ')}.`));
|
|
45
|
+
}
|
|
46
|
+
source.onLoaded?.(found.name);
|
|
47
|
+
return Promise.resolve(createSuccessResult({
|
|
48
|
+
skill: found.name,
|
|
49
|
+
instructions: found.prompt,
|
|
50
|
+
}));
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
package/dist/skills/loader.d.ts
CHANGED
|
@@ -28,3 +28,19 @@ export declare function parseSkillMarkdown(content: string, sourcePath?: string,
|
|
|
28
28
|
* Returns [] if the directory doesn't exist.
|
|
29
29
|
*/
|
|
30
30
|
export declare function loadSkillsFromDir(dir: string, source?: CustomSkill['source'], onWarn?: (msg: string) => void): Promise<CustomSkill[]>;
|
|
31
|
+
/**
|
|
32
|
+
* Every installed skill an agent could be granted: the user's and the project's, project first.
|
|
33
|
+
*
|
|
34
|
+
* ⚠️ PLATFORM SKILLS ARE NOT IN HERE, DELIBERATELY. The 42 shipped prompts are Commands — modes a
|
|
35
|
+
* user forces (`You are in DESIGN MODE`), not capabilities a model selects. Putting them in the
|
|
36
|
+
* catalogue would invite the model to load a mode nobody asked for, and would cost catalogue
|
|
37
|
+
* tokens for 42 entries that are already reachable by typing. See the Commands/Skills split.
|
|
38
|
+
*
|
|
39
|
+
* ⚠️ SYNC, BECAUSE AGENT CONSTRUCTION IS. Both hosts build agents inside a factory callback that
|
|
40
|
+
* is not a good place to await disk I/O on every rebuild. The directories are small and the
|
|
41
|
+
* result is intended to be cached by the host and refreshed when skills change.
|
|
42
|
+
*
|
|
43
|
+
* Project scope wins on a name clash, matching `resolveSkillPrompt`'s precedence — a project can
|
|
44
|
+
* override a personal skill, and the two hosts must agree about that.
|
|
45
|
+
*/
|
|
46
|
+
export declare function loadInstalledSkills(projectDir?: string): CustomSkill[];
|
package/dist/skills/loader.js
CHANGED
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Spec: /workspace/project-docs/00-requirements/compilr-dev-sdk/custom-skills-spec.md
|
|
13
13
|
*/
|
|
14
|
-
import { promises as fs } from 'node:fs';
|
|
14
|
+
import { promises as fs, readdirSync, readFileSync } from 'node:fs';
|
|
15
15
|
import { join } from 'node:path';
|
|
16
|
+
import { getSkillsDir } from './paths.js';
|
|
16
17
|
import { parse as parseYamlReal } from 'yaml';
|
|
17
18
|
/**
|
|
18
19
|
* Parse a SKILL.md string into a CustomSkill.
|
|
@@ -131,3 +132,60 @@ function parseYaml(text) {
|
|
|
131
132
|
return null;
|
|
132
133
|
}
|
|
133
134
|
}
|
|
135
|
+
/**
|
|
136
|
+
* Every installed skill an agent could be granted: the user's and the project's, project first.
|
|
137
|
+
*
|
|
138
|
+
* ⚠️ PLATFORM SKILLS ARE NOT IN HERE, DELIBERATELY. The 42 shipped prompts are Commands — modes a
|
|
139
|
+
* user forces (`You are in DESIGN MODE`), not capabilities a model selects. Putting them in the
|
|
140
|
+
* catalogue would invite the model to load a mode nobody asked for, and would cost catalogue
|
|
141
|
+
* tokens for 42 entries that are already reachable by typing. See the Commands/Skills split.
|
|
142
|
+
*
|
|
143
|
+
* ⚠️ SYNC, BECAUSE AGENT CONSTRUCTION IS. Both hosts build agents inside a factory callback that
|
|
144
|
+
* is not a good place to await disk I/O on every rebuild. The directories are small and the
|
|
145
|
+
* result is intended to be cached by the host and refreshed when skills change.
|
|
146
|
+
*
|
|
147
|
+
* Project scope wins on a name clash, matching `resolveSkillPrompt`'s precedence — a project can
|
|
148
|
+
* override a personal skill, and the two hosts must agree about that.
|
|
149
|
+
*/
|
|
150
|
+
export function loadInstalledSkills(projectDir) {
|
|
151
|
+
const seen = new Map();
|
|
152
|
+
const scopes = projectDir
|
|
153
|
+
? [
|
|
154
|
+
{ scope: 'project', dir: projectDir },
|
|
155
|
+
{ scope: 'user', dir: undefined },
|
|
156
|
+
]
|
|
157
|
+
: [{ scope: 'user', dir: undefined }];
|
|
158
|
+
for (const { scope, dir } of scopes) {
|
|
159
|
+
for (const skill of loadSkillsFromDirSync(getSkillsDir(scope, dir), scope)) {
|
|
160
|
+
if (!seen.has(skill.name))
|
|
161
|
+
seen.set(skill.name, skill);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return [...seen.values()];
|
|
165
|
+
}
|
|
166
|
+
/** Synchronous sibling of `loadSkillsFromDir`, for the construction path. */
|
|
167
|
+
function loadSkillsFromDirSync(dir, scope) {
|
|
168
|
+
const source = scope;
|
|
169
|
+
let entries;
|
|
170
|
+
try {
|
|
171
|
+
entries = readdirSync(dir, { withFileTypes: true })
|
|
172
|
+
.filter((e) => e.isDirectory())
|
|
173
|
+
.map((e) => e.name);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return []; // no skills directory is the normal case, not an error
|
|
177
|
+
}
|
|
178
|
+
const skills = [];
|
|
179
|
+
for (const name of entries) {
|
|
180
|
+
try {
|
|
181
|
+
const parsed = parseSkillMarkdown(readFileSync(join(dir, name, 'SKILL.md'), 'utf-8'));
|
|
182
|
+
if (!parsed)
|
|
183
|
+
continue; // malformed frontmatter — skipped, as the async loader does
|
|
184
|
+
skills.push({ ...parsed, source, compilr: { ...parsed.compilr, scope } });
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
continue; // no SKILL.md in this folder
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return skills;
|
|
191
|
+
}
|
|
@@ -53,3 +53,29 @@ export declare function resolveLayeredSkills(layers: {
|
|
|
53
53
|
* activation flow and lives at the call site, not here.
|
|
54
54
|
*/
|
|
55
55
|
export declare function resolveSkillsForAgent(skills: CustomSkill[], context: SkillEligibilityContext): CustomSkill[];
|
|
56
|
+
/**
|
|
57
|
+
* Eligibility for a TeamAgent — the one call both hosts make.
|
|
58
|
+
*
|
|
59
|
+
* ⚠️ SHARED SO THE HOSTS CANNOT DISAGREE. Desktop and the CLI each own their skill directories
|
|
60
|
+
* and each build agents through a factory, so each could assemble this context itself — and then
|
|
61
|
+
* drift, which is how the same skill came to be offered in one host and refused in the other more
|
|
62
|
+
* than once. Every judgement lives here: which fields of the agent matter, and what an absent
|
|
63
|
+
* allowlist means.
|
|
64
|
+
*
|
|
65
|
+
* ⚠️ `enabledSkills: []` IS NOT `undefined`, exactly as with MCP grants. `undefined` is "never
|
|
66
|
+
* asked" — every agent created before skills were choosable, which must keep reaching everything
|
|
67
|
+
* eligible. `[]` is "asked, and granted nothing". Collapsing them either strips capability from
|
|
68
|
+
* working agents or hands new ones the lot.
|
|
69
|
+
*
|
|
70
|
+
* @param pool every skill installed for this machine/project, unfiltered
|
|
71
|
+
* @param agent the agent being built — role, id and its allowlist come from here
|
|
72
|
+
* @param opts context the agent does not carry: the project type, and the tools it ended up with
|
|
73
|
+
*/
|
|
74
|
+
export declare function resolveSkillsForTeamAgent(pool: CustomSkill[], agent: {
|
|
75
|
+
id: string;
|
|
76
|
+
role?: string;
|
|
77
|
+
enabledSkills?: readonly string[];
|
|
78
|
+
}, opts?: {
|
|
79
|
+
projectType?: string;
|
|
80
|
+
toolNames?: readonly string[];
|
|
81
|
+
}): CustomSkill[];
|
package/dist/skills/resolver.js
CHANGED
|
@@ -78,3 +78,30 @@ export function resolveSkillsForAgent(skills, context) {
|
|
|
78
78
|
return true;
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Eligibility for a TeamAgent — the one call both hosts make.
|
|
83
|
+
*
|
|
84
|
+
* ⚠️ SHARED SO THE HOSTS CANNOT DISAGREE. Desktop and the CLI each own their skill directories
|
|
85
|
+
* and each build agents through a factory, so each could assemble this context itself — and then
|
|
86
|
+
* drift, which is how the same skill came to be offered in one host and refused in the other more
|
|
87
|
+
* than once. Every judgement lives here: which fields of the agent matter, and what an absent
|
|
88
|
+
* allowlist means.
|
|
89
|
+
*
|
|
90
|
+
* ⚠️ `enabledSkills: []` IS NOT `undefined`, exactly as with MCP grants. `undefined` is "never
|
|
91
|
+
* asked" — every agent created before skills were choosable, which must keep reaching everything
|
|
92
|
+
* eligible. `[]` is "asked, and granted nothing". Collapsing them either strips capability from
|
|
93
|
+
* working agents or hands new ones the lot.
|
|
94
|
+
*
|
|
95
|
+
* @param pool every skill installed for this machine/project, unfiltered
|
|
96
|
+
* @param agent the agent being built — role, id and its allowlist come from here
|
|
97
|
+
* @param opts context the agent does not carry: the project type, and the tools it ended up with
|
|
98
|
+
*/
|
|
99
|
+
export function resolveSkillsForTeamAgent(pool, agent, opts = {}) {
|
|
100
|
+
return resolveSkillsForAgent(pool, {
|
|
101
|
+
role: agent.role,
|
|
102
|
+
agentId: agent.id,
|
|
103
|
+
enabledSkills: agent.enabledSkills,
|
|
104
|
+
projectType: opts.projectType,
|
|
105
|
+
toolNames: opts.toolNames,
|
|
106
|
+
});
|
|
107
|
+
}
|