@alvin0/ai-agent-sdk-skill-filesystem 0.1.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/LICENSE +21 -0
- package/README.md +35 -0
- package/dist/index.d.mts +50 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +595 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +75 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 alvin0 (chaulamdinhai) <chaulamdinhai@gmail.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# @alvin0/ai-agent-sdk-skill-filesystem
|
|
2
|
+
|
|
3
|
+
The package directly owns exact `yaml@2.9.0` for bounded, fail-closed parsing of
|
|
4
|
+
`agents/openai.yaml` invocation policy. Aliases and duplicate keys are rejected.
|
|
5
|
+
|
|
6
|
+
Runtime: **Node 22.12+**.
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pnpm add @alvin0/ai-agent-sdk-core @alvin0/ai-agent-sdk-skill-filesystem
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Lazy Node filesystem discovery and activation for `SKILL.md` bundles.
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { createAgentRuntime } from '@alvin0/ai-agent-sdk-core'
|
|
16
|
+
import { fileSystemSkillProviderPlugin } from '@alvin0/ai-agent-sdk-skill-filesystem'
|
|
17
|
+
|
|
18
|
+
const skills = fileSystemSkillProviderPlugin({ roots: ['./skills'] })
|
|
19
|
+
const runtime = await createAgentRuntime({ providers: [modelProvider] })
|
|
20
|
+
const agent = runtime.agent({
|
|
21
|
+
id: 'coding-agent',
|
|
22
|
+
instructions: 'Use the available skills when relevant.',
|
|
23
|
+
skills: [skills],
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The package is Node-only. The returned versioned provider is borrowed by the
|
|
28
|
+
runtime: creating it performs no filesystem I/O, skill metadata is discovered at
|
|
29
|
+
the start of a run, and bodies/resources are loaded lazily with bounded reads.
|
|
30
|
+
Close the runtime; the provider itself has no owned resource to close.
|
|
31
|
+
|
|
32
|
+
`fileSystemSkills()` remains the marker-free advanced compatibility API.
|
|
33
|
+
|
|
34
|
+
Composition: `runtime-agent.skills`. Lifecycle: `borrowed-caller-owned`; the
|
|
35
|
+
provider stays lazy and the runtime never invents an independent close action.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { SkillDefinition, SkillProvider, SkillProviderListOptions, SkillProviderPlugin } from "@alvin0/ai-agent-sdk-core/skills";
|
|
2
|
+
//#region src/provider/filesystem-provider.d.ts
|
|
3
|
+
interface FileSystemSkillRoot {
|
|
4
|
+
readonly path: string;
|
|
5
|
+
readonly source?: string;
|
|
6
|
+
}
|
|
7
|
+
type FileSystemSkillIoPhase = 'discovery' | 'activation' | 'resource';
|
|
8
|
+
/** Optional byte-level telemetry for acceptance tests and host diagnostics. */
|
|
9
|
+
interface FileSystemSkillIoEvent {
|
|
10
|
+
readonly phase: FileSystemSkillIoPhase;
|
|
11
|
+
readonly operation: 'read' | 'scan';
|
|
12
|
+
readonly path: string;
|
|
13
|
+
readonly skillId?: string;
|
|
14
|
+
readonly bytesRead: number;
|
|
15
|
+
readonly entriesScanned?: number;
|
|
16
|
+
}
|
|
17
|
+
interface FileSystemSkillsOptions {
|
|
18
|
+
/** Provider identity used in catalogs and diagnostics. Defaults to filesystem. */
|
|
19
|
+
readonly id?: string;
|
|
20
|
+
/** Exact ordered roots. Earlier roots win duplicate ids. */
|
|
21
|
+
readonly roots?: readonly (string | FileSystemSkillRoot)[];
|
|
22
|
+
/** Base for automatic project discovery. Defaults to lookup cwd, then process.cwd(). */
|
|
23
|
+
readonly cwd?: string;
|
|
24
|
+
/** Scan `.agents/skills` from cwd through the git root. Defaults to true when roots are omitted. */
|
|
25
|
+
readonly includeProjectAgents?: boolean;
|
|
26
|
+
/** Also scan `.dsh/skills` along the same project chain. Defaults to false. */
|
|
27
|
+
readonly includeProjectDsh?: boolean;
|
|
28
|
+
/** Add `$HOME/.agents/skills` after project roots. Defaults to false for hermetic SDK usage. */
|
|
29
|
+
readonly includeUserAgents?: boolean;
|
|
30
|
+
/** Hard cap for metadata candidates retained by one provider. Defaults to 1024. */
|
|
31
|
+
readonly maxCandidates?: number;
|
|
32
|
+
/** Hard cap for directory entries scanned per discovery root. Defaults to 4,096. */
|
|
33
|
+
readonly maxRootEntries?: number;
|
|
34
|
+
/** Observe bounded filesystem I/O without changing discovery semantics. */
|
|
35
|
+
readonly onIo?: (event: FileSystemSkillIoEvent) => void;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Create a lazy filesystem provider. Discovery runs before each agent turn, so
|
|
39
|
+
* a CLI sees newly added or edited skill folders without rebuilding the agent.
|
|
40
|
+
*/
|
|
41
|
+
declare function fileSystemSkills(options?: FileSystemSkillsOptions): SkillProvider;
|
|
42
|
+
/** Eager convenience for CLIs that want definitions rather than a live provider. */
|
|
43
|
+
declare function discoverFileSystemSkills(options?: FileSystemSkillsOptions & SkillProviderListOptions): Promise<readonly SkillDefinition[]>;
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/provider/plugin.d.ts
|
|
46
|
+
/** Versioned, lazy adapter used by the high-level runtime composition slot. */
|
|
47
|
+
declare function fileSystemSkillProviderPlugin(options?: FileSystemSkillsOptions): SkillProviderPlugin;
|
|
48
|
+
//#endregion
|
|
49
|
+
export { type FileSystemSkillIoEvent, type FileSystemSkillIoPhase, type FileSystemSkillRoot, type FileSystemSkillsOptions, discoverFileSystemSkills, fileSystemSkillProviderPlugin, fileSystemSkills };
|
|
50
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/provider/filesystem-provider.ts","../src/provider/plugin.ts"],"mappings":";;UA4CiB;WACN;WACA;;KAGC;;UAGK;WACN,OAAO;WACP;WACA;WACA;WACA;WACA;;UAGM;;WAEN;;WAEA,2BAA2B;;WAE3B;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA;;WAEA,QAAQ,OAAO;;;;;;iBAqBV,iBAAiB,UAAS,0BAA+B;;iBAmDnD,yBACpB,UAAS,0BAA0B,2BAClC,iBAAiB;;;;iBCzIJ,8BACd,UAAS,0BACR"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
import { access, open, opendir, realpath, stat } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { parseDocument } from "yaml";
|
|
5
|
+
import { MAX_SKILL_INSTRUCTIONS_CHARS, MAX_SKILL_RESOURCE_CHARS, defineSkill, defineSkillProvider, defineSkillProviderPlugin, validateCandidate, validateSkillResourcePath } from "@alvin0/ai-agent-sdk-core/skills";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
|
|
8
|
+
//#region src/provider/filesystem-provider.ts
|
|
9
|
+
const TEXT_RESOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
10
|
+
".md",
|
|
11
|
+
".txt",
|
|
12
|
+
".json",
|
|
13
|
+
".csv",
|
|
14
|
+
".yaml",
|
|
15
|
+
".yml",
|
|
16
|
+
".ts",
|
|
17
|
+
".tsx",
|
|
18
|
+
".js",
|
|
19
|
+
".mjs",
|
|
20
|
+
".cjs",
|
|
21
|
+
".py",
|
|
22
|
+
".sh",
|
|
23
|
+
".ps1"
|
|
24
|
+
]);
|
|
25
|
+
const MAX_RESOURCES = 256;
|
|
26
|
+
const DEFAULT_MAX_CANDIDATES = 1024;
|
|
27
|
+
const DEFAULT_MAX_ROOT_ENTRIES = 4096;
|
|
28
|
+
const MAX_MANIFEST_ENTRIES = 4096;
|
|
29
|
+
const MAX_MANIFEST_DEPTH = 16;
|
|
30
|
+
const IGNORED_RESOURCE_DIRECTORIES = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
31
|
+
const READ_CHUNK_BYTES = 8192;
|
|
32
|
+
const MAX_FRONT_MATTER_BYTES = 65536;
|
|
33
|
+
const MAX_OPENAI_METADATA_BYTES = 65536;
|
|
34
|
+
const MAX_OPENAI_METADATA_DEPTH = 16;
|
|
35
|
+
const MAX_OPENAI_METADATA_NODES = 4096;
|
|
36
|
+
const MAX_SKILL_FILE_BYTES = MAX_FRONT_MATTER_BYTES + MAX_SKILL_INSTRUCTIONS_CHARS * 4;
|
|
37
|
+
const MAX_RESOURCE_FILE_BYTES = MAX_SKILL_RESOURCE_CHARS * 4;
|
|
38
|
+
/**
|
|
39
|
+
* Create a lazy filesystem provider. Discovery runs before each agent turn, so
|
|
40
|
+
* a CLI sees newly added or edited skill folders without rebuilding the agent.
|
|
41
|
+
*/
|
|
42
|
+
function fileSystemSkills(options = {}) {
|
|
43
|
+
const id = options.id ?? "filesystem";
|
|
44
|
+
const maxCandidates = boundedInteger(options.maxCandidates, DEFAULT_MAX_CANDIDATES, 1, 1e4, "maxCandidates");
|
|
45
|
+
const maxRootEntries = boundedInteger(options.maxRootEntries, DEFAULT_MAX_ROOT_ENTRIES, 1, 1e5, "maxRootEntries");
|
|
46
|
+
const issued = /* @__PURE__ */ new WeakSet();
|
|
47
|
+
return defineSkillProvider({
|
|
48
|
+
kind: "skill-provider",
|
|
49
|
+
id,
|
|
50
|
+
async list(lookup) {
|
|
51
|
+
const allowed = lookup.allowedSkillIds === void 0 ? void 0 : new Set(lookup.allowedSkillIds);
|
|
52
|
+
if (allowed?.size === 0) return Object.freeze([]);
|
|
53
|
+
const roots = await resolveRoots(options, lookup);
|
|
54
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
55
|
+
for (const root of roots) for (const locator of await discoverRoot(root.path, maxRootEntries, lookup.signal, options.onIo)) {
|
|
56
|
+
const candidate = await parseSkillMetadata(locator, root.source, id, lookup.signal, options.onIo);
|
|
57
|
+
if (allowed !== void 0 && !allowed.has(candidate.id)) continue;
|
|
58
|
+
if (candidates.has(candidate.id)) continue;
|
|
59
|
+
if (candidates.size >= maxCandidates) throw new RangeError(`skill provider '${id}' discovered more than ${maxCandidates} candidates`);
|
|
60
|
+
candidates.set(candidate.id, candidate);
|
|
61
|
+
issued.add(candidate);
|
|
62
|
+
}
|
|
63
|
+
return Object.freeze([...candidates.values()]);
|
|
64
|
+
},
|
|
65
|
+
async load(candidate, lookup) {
|
|
66
|
+
assertIssuedCandidate(issued, candidate, id);
|
|
67
|
+
return (await parseSkillFile(asLocator(candidate.locator, candidate.id), candidate.id, candidate.source, id, lookup.signal, options.onIo)).input;
|
|
68
|
+
},
|
|
69
|
+
async readResource(candidate, path, lookup) {
|
|
70
|
+
assertIssuedCandidate(issued, candidate, id);
|
|
71
|
+
return await readResource(asLocator(candidate.locator, candidate.id), candidate.id, path, lookup.signal, options.onIo);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/** Eager convenience for CLIs that want definitions rather than a live provider. */
|
|
76
|
+
async function discoverFileSystemSkills(options = {}) {
|
|
77
|
+
const provider = fileSystemSkills(options);
|
|
78
|
+
const lookup = {
|
|
79
|
+
...options.cwd === void 0 ? {} : { cwd: options.cwd },
|
|
80
|
+
...options.signal === void 0 ? {} : { signal: options.signal },
|
|
81
|
+
...options.allowedSkillIds === void 0 ? {} : { allowedSkillIds: options.allowedSkillIds }
|
|
82
|
+
};
|
|
83
|
+
const candidates = await provider.list(lookup);
|
|
84
|
+
const definitions = [];
|
|
85
|
+
for (const candidate of candidates) {
|
|
86
|
+
const input = await provider.load(candidate, lookup);
|
|
87
|
+
if (input !== void 0) definitions.push(defineSkill(input));
|
|
88
|
+
}
|
|
89
|
+
return Object.freeze(definitions);
|
|
90
|
+
}
|
|
91
|
+
async function resolveRoots(options, lookup) {
|
|
92
|
+
if (options.roots !== void 0) return Object.freeze(options.roots.map((root, index) => typeof root === "string" ? {
|
|
93
|
+
path: resolve(root),
|
|
94
|
+
source: `custom-${index + 1}`
|
|
95
|
+
} : {
|
|
96
|
+
path: resolve(root.path),
|
|
97
|
+
source: root.source ?? `custom-${index + 1}`
|
|
98
|
+
}));
|
|
99
|
+
const cwd = resolve(lookup.cwd ?? options.cwd ?? process.cwd());
|
|
100
|
+
const projectRoot = await findProjectRoot(cwd);
|
|
101
|
+
const roots = [];
|
|
102
|
+
if (options.includeProjectAgents ?? true) roots.push(...ancestorSkillRoots(cwd, projectRoot, ".agents/skills", "project-agents"));
|
|
103
|
+
if (options.includeProjectDsh ?? false) roots.push(...ancestorSkillRoots(cwd, projectRoot, ".dsh/skills", "project-dsh"));
|
|
104
|
+
if (options.includeUserAgents ?? false) roots.push({
|
|
105
|
+
path: join(homedir(), ".agents", "skills"),
|
|
106
|
+
source: "user-agents"
|
|
107
|
+
});
|
|
108
|
+
return Object.freeze(roots);
|
|
109
|
+
}
|
|
110
|
+
function ancestorSkillRoots(cwd, projectRoot, suffix, source) {
|
|
111
|
+
const roots = [];
|
|
112
|
+
let current = cwd;
|
|
113
|
+
while (true) {
|
|
114
|
+
roots.push({
|
|
115
|
+
path: join(current, suffix),
|
|
116
|
+
source
|
|
117
|
+
});
|
|
118
|
+
if (samePath(current, projectRoot)) break;
|
|
119
|
+
const parent = dirname(current);
|
|
120
|
+
if (parent === current || !isInside(projectRoot, parent)) break;
|
|
121
|
+
current = parent;
|
|
122
|
+
}
|
|
123
|
+
return roots;
|
|
124
|
+
}
|
|
125
|
+
async function findProjectRoot(cwd) {
|
|
126
|
+
let current = cwd;
|
|
127
|
+
while (true) {
|
|
128
|
+
try {
|
|
129
|
+
await access(join(current, ".git"));
|
|
130
|
+
return current;
|
|
131
|
+
} catch {}
|
|
132
|
+
const parent = dirname(current);
|
|
133
|
+
if (parent === current) return cwd;
|
|
134
|
+
current = parent;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
async function discoverRoot(root, maxEntries, signal, onIo) {
|
|
138
|
+
throwIfAborted(signal);
|
|
139
|
+
let canonicalRoot;
|
|
140
|
+
try {
|
|
141
|
+
canonicalRoot = await realpath(root);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
if (isMissing(error)) return [];
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
const entries = await readDirectoryBounded(canonicalRoot, maxEntries, signal, "skill discovery root");
|
|
147
|
+
emitIo(onIo, {
|
|
148
|
+
phase: "discovery",
|
|
149
|
+
operation: "scan",
|
|
150
|
+
path: canonicalRoot,
|
|
151
|
+
bytesRead: 0,
|
|
152
|
+
entriesScanned: entries.length
|
|
153
|
+
});
|
|
154
|
+
const locators = [];
|
|
155
|
+
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
156
|
+
throwIfAborted(signal);
|
|
157
|
+
const entryPath = join(canonicalRoot, entry.name);
|
|
158
|
+
let directory;
|
|
159
|
+
try {
|
|
160
|
+
const info = entry.isDirectory() ? void 0 : await stat(entryPath);
|
|
161
|
+
if (!entry.isDirectory() && info?.isDirectory() !== true) continue;
|
|
162
|
+
directory = await realpath(entryPath);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
if (isMissing(error)) continue;
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
const skillFile = join(directory, "SKILL.md");
|
|
168
|
+
try {
|
|
169
|
+
await access(skillFile);
|
|
170
|
+
} catch {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
locators.push({
|
|
174
|
+
skillFile,
|
|
175
|
+
directory
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return locators;
|
|
179
|
+
}
|
|
180
|
+
async function parseSkillFile(locator, skillId, source, provider, signal, onIo) {
|
|
181
|
+
throwIfAborted(signal);
|
|
182
|
+
const { fields, body, metadata } = parseFrontMatter(normalize(await readTextFileBounded(locator.skillFile, MAX_SKILL_FILE_BYTES, `skill '${locator.skillFile}'`, signal, onIo, {
|
|
183
|
+
phase: "activation",
|
|
184
|
+
skillId
|
|
185
|
+
})), locator.skillFile);
|
|
186
|
+
const id = fields.get("name") ?? "";
|
|
187
|
+
const description = fields.get("description") ?? "";
|
|
188
|
+
const whenToUse = fields.get("metadata.when_to_use");
|
|
189
|
+
const allowImplicit = await readImplicitPolicy(locator.directory, signal, "activation", onIo, skillId);
|
|
190
|
+
const invocation = {
|
|
191
|
+
modelInvocable: booleanField(fields, "disable-model-invocation", false) !== true && allowImplicit !== false,
|
|
192
|
+
userInvocable: booleanField(fields, "user-invocable", true)
|
|
193
|
+
};
|
|
194
|
+
const resourceManifest = await discoverResourceManifest(locator, signal, onIo, skillId);
|
|
195
|
+
return { input: {
|
|
196
|
+
id,
|
|
197
|
+
name: fields.get("display-name") ?? id,
|
|
198
|
+
description,
|
|
199
|
+
...whenToUse === void 0 ? {} : { whenToUse },
|
|
200
|
+
instructions: body,
|
|
201
|
+
resourceManifest,
|
|
202
|
+
invocation,
|
|
203
|
+
source,
|
|
204
|
+
provider,
|
|
205
|
+
resourceBase: {
|
|
206
|
+
kind: "directory",
|
|
207
|
+
value: locator.directory
|
|
208
|
+
},
|
|
209
|
+
path: locator.skillFile,
|
|
210
|
+
metadata
|
|
211
|
+
} };
|
|
212
|
+
}
|
|
213
|
+
async function parseSkillMetadata(locator, source, provider, signal, onIo) {
|
|
214
|
+
const frontMatter = await readFrontMatter(locator.skillFile, signal, onIo);
|
|
215
|
+
const fileRevision = await stat(locator.skillFile);
|
|
216
|
+
const { fields } = parseFrontMatter(frontMatter, locator.skillFile);
|
|
217
|
+
const id = fields.get("name") ?? "";
|
|
218
|
+
const description = fields.get("description") ?? "";
|
|
219
|
+
const whenToUse = fields.get("metadata.when_to_use");
|
|
220
|
+
const allowImplicit = await readImplicitPolicy(locator.directory, signal, "discovery", onIo, id);
|
|
221
|
+
const candidate = Object.freeze({
|
|
222
|
+
id,
|
|
223
|
+
name: fields.get("display-name") ?? id,
|
|
224
|
+
description,
|
|
225
|
+
...whenToUse === void 0 ? {} : { whenToUse },
|
|
226
|
+
invocation: Object.freeze({
|
|
227
|
+
modelInvocable: booleanField(fields, "disable-model-invocation", false) !== true && allowImplicit !== false,
|
|
228
|
+
userInvocable: booleanField(fields, "user-invocable", true)
|
|
229
|
+
}),
|
|
230
|
+
source,
|
|
231
|
+
provider,
|
|
232
|
+
resourceBase: Object.freeze({
|
|
233
|
+
kind: "directory",
|
|
234
|
+
value: locator.directory
|
|
235
|
+
}),
|
|
236
|
+
locator: Object.freeze({
|
|
237
|
+
...locator,
|
|
238
|
+
revision: Object.freeze({
|
|
239
|
+
sizeBytes: fileRevision.size,
|
|
240
|
+
modifiedMs: fileRevision.mtimeMs
|
|
241
|
+
})
|
|
242
|
+
}),
|
|
243
|
+
path: locator.skillFile
|
|
244
|
+
});
|
|
245
|
+
validateCandidate(candidate, provider);
|
|
246
|
+
return candidate;
|
|
247
|
+
}
|
|
248
|
+
function parseFrontMatter(contents, path) {
|
|
249
|
+
const match = /^---\n([\s\S]*?)\n---\n?/.exec(contents);
|
|
250
|
+
if (match === null) throw new Error(`skill '${path}' must start with YAML front matter`);
|
|
251
|
+
const fields = /* @__PURE__ */ new Map();
|
|
252
|
+
let section = "";
|
|
253
|
+
for (const line of (match[1] ?? "").split("\n")) {
|
|
254
|
+
if (line.trim().length === 0 || line.trimStart().startsWith("#")) continue;
|
|
255
|
+
const separator = line.indexOf(":");
|
|
256
|
+
if (separator < 0) continue;
|
|
257
|
+
const indented = /^\s/.test(line);
|
|
258
|
+
const key = line.slice(0, separator).trim();
|
|
259
|
+
const raw = line.slice(separator + 1).trim();
|
|
260
|
+
if (!indented && raw.length === 0) {
|
|
261
|
+
section = key;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
fields.set(indented && section.length > 0 ? `${section}.${key}` : key, unquote(raw));
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
fields,
|
|
268
|
+
body: contents.slice(match[0].length).trim(),
|
|
269
|
+
metadata: Object.freeze(Object.fromEntries(fields))
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
async function readImplicitPolicy(directory, signal, phase, onIo, skillId) {
|
|
273
|
+
throwIfAborted(signal);
|
|
274
|
+
try {
|
|
275
|
+
const path = join(directory, "agents", "openai.yaml");
|
|
276
|
+
const contents = normalize(await readTextFileBounded(path, MAX_OPENAI_METADATA_BYTES, `skill metadata '${path}'`, signal, onIo, {
|
|
277
|
+
phase,
|
|
278
|
+
skillId
|
|
279
|
+
}));
|
|
280
|
+
const document = parseDocument(contents, {
|
|
281
|
+
strict: true,
|
|
282
|
+
uniqueKeys: true
|
|
283
|
+
});
|
|
284
|
+
if (document.errors.length > 0) throw new TypeError(`skill metadata '${path}' is invalid YAML: ${document.errors[0]?.message ?? "parse failed"}`);
|
|
285
|
+
let value;
|
|
286
|
+
try {
|
|
287
|
+
value = document.toJS({ maxAliasCount: 0 });
|
|
288
|
+
} catch (error) {
|
|
289
|
+
throw new TypeError(`skill metadata '${path}' contains unsupported YAML aliases`, { cause: error });
|
|
290
|
+
}
|
|
291
|
+
assertMetadataBounds(value, path);
|
|
292
|
+
if (value === null || value === void 0) return void 0;
|
|
293
|
+
if (!isStringRecord(value)) throw new TypeError(`skill metadata '${path}' must be a YAML mapping`);
|
|
294
|
+
const policy = value.policy;
|
|
295
|
+
if (policy === void 0) {
|
|
296
|
+
if (containsImplicitPolicyKey(value)) throw new TypeError(`skill metadata '${path}' must place allow_implicit_invocation under policy`);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (!isStringRecord(policy)) throw new TypeError(`skill metadata '${path}' policy must be a YAML mapping`);
|
|
300
|
+
const allowImplicit = policy.allow_implicit_invocation;
|
|
301
|
+
if (allowImplicit === void 0) {
|
|
302
|
+
if (containsImplicitPolicyKey(value)) throw new TypeError(`skill metadata '${path}' has an invalid allow_implicit_invocation policy`);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (typeof allowImplicit !== "boolean") throw new TypeError(`skill metadata '${path}' policy.allow_implicit_invocation must be true or false`);
|
|
306
|
+
return allowImplicit;
|
|
307
|
+
} catch (error) {
|
|
308
|
+
if (isMissing(error)) return void 0;
|
|
309
|
+
throw error;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function assertMetadataBounds(value, path) {
|
|
313
|
+
let nodes = 0;
|
|
314
|
+
const visit = (current, depth) => {
|
|
315
|
+
nodes++;
|
|
316
|
+
if (nodes > MAX_OPENAI_METADATA_NODES) throw new RangeError(`skill metadata '${path}' exceeds ${MAX_OPENAI_METADATA_NODES} YAML nodes`);
|
|
317
|
+
if (depth > MAX_OPENAI_METADATA_DEPTH) throw new RangeError(`skill metadata '${path}' exceeds YAML depth ${MAX_OPENAI_METADATA_DEPTH}`);
|
|
318
|
+
if (Array.isArray(current)) {
|
|
319
|
+
for (const item of current) visit(item, depth + 1);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
if (isStringRecord(current)) for (const item of Object.values(current)) visit(item, depth + 1);
|
|
323
|
+
};
|
|
324
|
+
visit(value, 0);
|
|
325
|
+
}
|
|
326
|
+
function containsImplicitPolicyKey(value) {
|
|
327
|
+
if (Array.isArray(value)) return value.some(containsImplicitPolicyKey);
|
|
328
|
+
if (!isStringRecord(value)) return false;
|
|
329
|
+
return Object.prototype.hasOwnProperty.call(value, "allow_implicit_invocation") || Object.values(value).some(containsImplicitPolicyKey);
|
|
330
|
+
}
|
|
331
|
+
function isStringRecord(value) {
|
|
332
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
333
|
+
}
|
|
334
|
+
async function discoverResourceManifest(locator, signal, onIo, skillId) {
|
|
335
|
+
const resources = [];
|
|
336
|
+
const pending = [{
|
|
337
|
+
directory: locator.directory,
|
|
338
|
+
depth: 0
|
|
339
|
+
}];
|
|
340
|
+
let scannedEntries = 0;
|
|
341
|
+
try {
|
|
342
|
+
while (pending.length > 0) {
|
|
343
|
+
throwIfAborted(signal);
|
|
344
|
+
const current = pending.pop();
|
|
345
|
+
if (current === void 0) break;
|
|
346
|
+
const { directory, depth } = current;
|
|
347
|
+
const entries = await readDirectoryBounded(directory, MAX_MANIFEST_ENTRIES - scannedEntries, signal, `skill '${basename(locator.directory)}' resource tree`);
|
|
348
|
+
for (const entry of entries.sort((left, right) => right.name.localeCompare(left.name))) {
|
|
349
|
+
throwIfAborted(signal);
|
|
350
|
+
scannedEntries++;
|
|
351
|
+
if (scannedEntries > MAX_MANIFEST_ENTRIES) throw new RangeError(`skill '${basename(locator.directory)}' resource tree exceeds ${MAX_MANIFEST_ENTRIES} entries`);
|
|
352
|
+
const path = join(directory, entry.name);
|
|
353
|
+
if (entry.isDirectory()) {
|
|
354
|
+
if (IGNORED_RESOURCE_DIRECTORIES.has(entry.name)) continue;
|
|
355
|
+
if (depth >= MAX_MANIFEST_DEPTH) throw new RangeError(`skill '${basename(locator.directory)}' resource tree exceeds depth ${MAX_MANIFEST_DEPTH}`);
|
|
356
|
+
pending.push({
|
|
357
|
+
directory: path,
|
|
358
|
+
depth: depth + 1
|
|
359
|
+
});
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
if (!entry.isFile() || !TEXT_RESOURCE_EXTENSIONS.has(extname(entry.name).toLocaleLowerCase())) continue;
|
|
363
|
+
const key = relative(locator.directory, path).split(sep).join("/");
|
|
364
|
+
if (key === "SKILL.md" || key === "agents/openai.yaml") continue;
|
|
365
|
+
if (resources.length >= MAX_RESOURCES) throw new RangeError(`skill '${basename(locator.directory)}' has more than ${MAX_RESOURCES} text resources`);
|
|
366
|
+
const info = await stat(path);
|
|
367
|
+
resources.push(Object.freeze({
|
|
368
|
+
path: key,
|
|
369
|
+
sizeBytes: info.size
|
|
370
|
+
}));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
} finally {
|
|
374
|
+
emitIo(onIo, {
|
|
375
|
+
phase: "activation",
|
|
376
|
+
operation: "scan",
|
|
377
|
+
path: locator.directory,
|
|
378
|
+
skillId,
|
|
379
|
+
bytesRead: 0,
|
|
380
|
+
entriesScanned: scannedEntries
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
return Object.freeze(resources.sort((left, right) => left.path.localeCompare(right.path)));
|
|
384
|
+
}
|
|
385
|
+
async function readDirectoryBounded(directory, maxEntries, signal, label) {
|
|
386
|
+
if (maxEntries < 1) throw new RangeError(`${label} exceeds its entry limit`);
|
|
387
|
+
const handle = await opendir(directory);
|
|
388
|
+
const entries = [];
|
|
389
|
+
try {
|
|
390
|
+
for await (const entry of handle) {
|
|
391
|
+
throwIfAborted(signal);
|
|
392
|
+
if (entries.length >= maxEntries) throw new RangeError(`${label} exceeds ${maxEntries} entries`);
|
|
393
|
+
entries.push(entry);
|
|
394
|
+
}
|
|
395
|
+
} finally {
|
|
396
|
+
await handle.close().catch(() => void 0);
|
|
397
|
+
}
|
|
398
|
+
return entries;
|
|
399
|
+
}
|
|
400
|
+
async function readResource(locator, skillId, resourcePath, signal, onIo) {
|
|
401
|
+
validateSkillResourcePath(resourcePath, skillId);
|
|
402
|
+
if (!TEXT_RESOURCE_EXTENSIONS.has(extname(resourcePath).toLocaleLowerCase())) return void 0;
|
|
403
|
+
const requested = resolve(locator.directory, ...resourcePath.split("/"));
|
|
404
|
+
if (!isInside(locator.directory, requested)) return void 0;
|
|
405
|
+
let canonical;
|
|
406
|
+
try {
|
|
407
|
+
canonical = await realpath(requested);
|
|
408
|
+
} catch (error) {
|
|
409
|
+
if (isMissing(error)) return void 0;
|
|
410
|
+
throw error;
|
|
411
|
+
}
|
|
412
|
+
if (!isInside(locator.directory, canonical)) return void 0;
|
|
413
|
+
if (!(await stat(canonical)).isFile()) return void 0;
|
|
414
|
+
return normalize(await readTextFileBounded(canonical, MAX_RESOURCE_FILE_BYTES, `skill resource '${resourcePath}'`, signal, onIo, {
|
|
415
|
+
phase: "resource",
|
|
416
|
+
skillId
|
|
417
|
+
})).trim();
|
|
418
|
+
}
|
|
419
|
+
async function readFrontMatter(path, signal, onIo) {
|
|
420
|
+
throwIfAborted(signal);
|
|
421
|
+
const handle = await open(path, "r");
|
|
422
|
+
const chunks = [];
|
|
423
|
+
let total = 0;
|
|
424
|
+
try {
|
|
425
|
+
while (total < MAX_FRONT_MATTER_BYTES) {
|
|
426
|
+
throwIfAborted(signal);
|
|
427
|
+
const buffer = Buffer.allocUnsafe(Math.min(READ_CHUNK_BYTES, MAX_FRONT_MATTER_BYTES - total));
|
|
428
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, total);
|
|
429
|
+
if (bytesRead === 0) break;
|
|
430
|
+
chunks.push(buffer.subarray(0, bytesRead));
|
|
431
|
+
total += bytesRead;
|
|
432
|
+
const prefix = normalize(Buffer.concat(chunks, total).toString("utf8"));
|
|
433
|
+
const match = /^---\n[\s\S]*?\n---(?:\n|$)/.exec(prefix);
|
|
434
|
+
if (match !== null) {
|
|
435
|
+
emitIo(onIo, {
|
|
436
|
+
phase: "discovery",
|
|
437
|
+
operation: "read",
|
|
438
|
+
path,
|
|
439
|
+
bytesRead: total
|
|
440
|
+
});
|
|
441
|
+
return match[0];
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
} finally {
|
|
445
|
+
await handle.close();
|
|
446
|
+
}
|
|
447
|
+
emitIo(onIo, {
|
|
448
|
+
phase: "discovery",
|
|
449
|
+
operation: "read",
|
|
450
|
+
path,
|
|
451
|
+
bytesRead: total
|
|
452
|
+
});
|
|
453
|
+
throw new RangeError(`skill '${path}' front matter is missing its closing delimiter within ${MAX_FRONT_MATTER_BYTES} bytes`);
|
|
454
|
+
}
|
|
455
|
+
async function readTextFileBounded(path, maxBytes, label, signal, onIo, context) {
|
|
456
|
+
throwIfAborted(signal);
|
|
457
|
+
const handle = await open(path, "r");
|
|
458
|
+
const chunks = [];
|
|
459
|
+
let total = 0;
|
|
460
|
+
try {
|
|
461
|
+
while (true) {
|
|
462
|
+
throwIfAborted(signal);
|
|
463
|
+
const buffer = Buffer.allocUnsafe(READ_CHUNK_BYTES);
|
|
464
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, total);
|
|
465
|
+
if (bytesRead === 0) break;
|
|
466
|
+
total += bytesRead;
|
|
467
|
+
if (total > maxBytes) throw new RangeError(`${label} exceeds ${maxBytes} bytes`);
|
|
468
|
+
chunks.push(buffer.subarray(0, bytesRead));
|
|
469
|
+
}
|
|
470
|
+
return Buffer.concat(chunks, total).toString("utf8");
|
|
471
|
+
} finally {
|
|
472
|
+
await handle.close();
|
|
473
|
+
if (context !== void 0) emitIo(onIo, {
|
|
474
|
+
phase: context.phase,
|
|
475
|
+
operation: "read",
|
|
476
|
+
path,
|
|
477
|
+
...context.skillId === void 0 ? {} : { skillId: context.skillId },
|
|
478
|
+
bytesRead: total
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function emitIo(listener, event) {
|
|
483
|
+
try {
|
|
484
|
+
listener?.(Object.freeze(event));
|
|
485
|
+
} catch {}
|
|
486
|
+
}
|
|
487
|
+
function asLocator(value, skillId) {
|
|
488
|
+
if (typeof value !== "object" || value === null || typeof value.skillFile !== "string" || typeof value.directory !== "string") throw new TypeError(`filesystem skill '${skillId}' has an invalid locator`);
|
|
489
|
+
return value;
|
|
490
|
+
}
|
|
491
|
+
function assertIssuedCandidate(issued, candidate, providerId) {
|
|
492
|
+
if (!issued.has(candidate)) throw new TypeError(`skill provider '${providerId}' received a candidate it did not issue`);
|
|
493
|
+
}
|
|
494
|
+
function booleanField(fields, key, fallback) {
|
|
495
|
+
const value = fields.get(key);
|
|
496
|
+
if (value === void 0) return fallback;
|
|
497
|
+
if (value === "true") return true;
|
|
498
|
+
if (value === "false") return false;
|
|
499
|
+
throw new TypeError(`skill front matter '${key}' must be true or false`);
|
|
500
|
+
}
|
|
501
|
+
function normalize(value) {
|
|
502
|
+
return value.replace(/^\uFEFF/, "").replace(/\r\n/g, "\n");
|
|
503
|
+
}
|
|
504
|
+
function unquote(value) {
|
|
505
|
+
return /^(["'])([\s\S]*)\1$/.exec(value)?.[2] ?? value;
|
|
506
|
+
}
|
|
507
|
+
function samePath(left, right) {
|
|
508
|
+
return process.platform === "win32" ? left.toLocaleLowerCase() === right.toLocaleLowerCase() : left === right;
|
|
509
|
+
}
|
|
510
|
+
function isInside(root, target) {
|
|
511
|
+
const path = relative(root, target);
|
|
512
|
+
return path.length === 0 || !path.startsWith("..") && !isAbsolute(path);
|
|
513
|
+
}
|
|
514
|
+
function isMissing(error) {
|
|
515
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
516
|
+
}
|
|
517
|
+
function throwIfAborted(signal) {
|
|
518
|
+
if (signal?.aborted === true) throw signal.reason ?? /* @__PURE__ */ new Error("skill discovery aborted");
|
|
519
|
+
}
|
|
520
|
+
function boundedInteger(value, fallback, min, max, name) {
|
|
521
|
+
const resolved = value ?? fallback;
|
|
522
|
+
if (!Number.isSafeInteger(resolved) || resolved < min || resolved > max) throw new RangeError(`${name} must be an integer between ${min} and ${max}`);
|
|
523
|
+
return resolved;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
//#endregion
|
|
527
|
+
//#region src/provider/plugin.ts
|
|
528
|
+
/** Versioned, lazy adapter used by the high-level runtime composition slot. */
|
|
529
|
+
function fileSystemSkillProviderPlugin(options = {}) {
|
|
530
|
+
const provider = fileSystemSkills(options);
|
|
531
|
+
return defineSkillProviderPlugin({
|
|
532
|
+
id: provider.id,
|
|
533
|
+
async list(lookup) {
|
|
534
|
+
const candidates = await provider.list(markerFreeLookup(lookup));
|
|
535
|
+
const rows = Object.freeze(candidates.map((candidate) => runtimeCandidate(candidate)));
|
|
536
|
+
return Object.freeze({
|
|
537
|
+
revision: catalogRevision(rows),
|
|
538
|
+
candidates: rows
|
|
539
|
+
});
|
|
540
|
+
},
|
|
541
|
+
async load(reference, lookup) {
|
|
542
|
+
const candidate = await resolveReference(provider, reference, lookup);
|
|
543
|
+
return candidate === void 0 ? void 0 : provider.load(candidate, markerFreeLookup(lookup));
|
|
544
|
+
},
|
|
545
|
+
async readResource(reference, path, lookup) {
|
|
546
|
+
const candidate = await resolveReference(provider, reference, lookup);
|
|
547
|
+
return candidate === void 0 || provider.readResource === void 0 ? void 0 : provider.readResource(candidate, path, markerFreeLookup(lookup));
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
function runtimeCandidate(candidate) {
|
|
552
|
+
return Object.freeze({
|
|
553
|
+
id: candidate.id,
|
|
554
|
+
name: candidate.name,
|
|
555
|
+
description: candidate.description,
|
|
556
|
+
...candidate.whenToUse === void 0 ? {} : { whenToUse: candidate.whenToUse },
|
|
557
|
+
invocation: candidate.invocation,
|
|
558
|
+
source: candidate.source,
|
|
559
|
+
provider: candidate.provider,
|
|
560
|
+
...candidate.locator === void 0 ? {} : { locator: jsonLocator(candidate.locator) }
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
async function resolveReference(provider, reference, lookup) {
|
|
564
|
+
if (reference.provider !== provider.id) return void 0;
|
|
565
|
+
return (await provider.list({
|
|
566
|
+
...markerFreeLookup(lookup),
|
|
567
|
+
allowedSkillIds: [reference.id]
|
|
568
|
+
})).find((candidate) => candidate.id === reference.id && candidate.source === reference.source && sameJson(candidate.locator, reference.locator));
|
|
569
|
+
}
|
|
570
|
+
function markerFreeLookup(lookup) {
|
|
571
|
+
return {
|
|
572
|
+
...lookup.cwd === void 0 ? {} : { cwd: lookup.cwd },
|
|
573
|
+
signal: lookup.signal,
|
|
574
|
+
...lookup.allowedSkillIds === void 0 ? {} : { allowedSkillIds: lookup.allowedSkillIds }
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
function catalogRevision(candidates) {
|
|
578
|
+
return createHash("sha256").update(JSON.stringify(candidates)).digest("hex");
|
|
579
|
+
}
|
|
580
|
+
function jsonLocator(value) {
|
|
581
|
+
const serialized = JSON.stringify(value);
|
|
582
|
+
if (serialized === void 0) throw new TypeError("filesystem skill locator must be JSON serializable");
|
|
583
|
+
return JSON.parse(serialized);
|
|
584
|
+
}
|
|
585
|
+
function sameJson(left, right) {
|
|
586
|
+
try {
|
|
587
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
588
|
+
} catch {
|
|
589
|
+
return false;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
//#endregion
|
|
594
|
+
export { discoverFileSystemSkills, fileSystemSkillProviderPlugin, fileSystemSkills };
|
|
595
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/provider/filesystem-provider.ts","../src/provider/plugin.ts"],"sourcesContent":["/** Node-only Agent Skills folder discovery. */\n\nimport type { Dirent } from 'node:fs'\nimport { access, open, opendir, realpath, stat } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from 'node:path'\nimport { parseDocument } from 'yaml'\nimport {\n MAX_SKILL_RESOURCE_CHARS,\n MAX_SKILL_INSTRUCTIONS_CHARS,\n defineSkill,\n defineSkillProvider,\n type SkillCandidate,\n type SkillDefinition,\n type SkillDefinitionInput,\n type SkillInvocationPolicy,\n type SkillLookupOptions,\n type SkillProvider,\n type SkillProviderListOptions,\n type SkillResourceSummary,\n} from '@alvin0/ai-agent-sdk-core/skills'\nimport {\n validateCandidate,\n validateSkillResourcePath,\n} from '@alvin0/ai-agent-sdk-core/skills'\n\nconst TEXT_RESOURCE_EXTENSIONS = new Set([\n '.md', '.txt', '.json', '.csv', '.yaml', '.yml', '.ts', '.tsx', '.js', '.mjs', '.cjs',\n '.py', '.sh', '.ps1',\n])\nconst MAX_RESOURCES = 256\nconst DEFAULT_MAX_CANDIDATES = 1_024\nconst DEFAULT_MAX_ROOT_ENTRIES = 4_096\nconst MAX_MANIFEST_ENTRIES = 4_096\nconst MAX_MANIFEST_DEPTH = 16\nconst IGNORED_RESOURCE_DIRECTORIES = new Set(['.git', 'node_modules'])\nconst READ_CHUNK_BYTES = 8 * 1024\nconst MAX_FRONT_MATTER_BYTES = 64 * 1024\nconst MAX_OPENAI_METADATA_BYTES = 64 * 1024\nconst MAX_OPENAI_METADATA_DEPTH = 16\nconst MAX_OPENAI_METADATA_NODES = 4_096\nconst MAX_SKILL_FILE_BYTES = MAX_FRONT_MATTER_BYTES + MAX_SKILL_INSTRUCTIONS_CHARS * 4\nconst MAX_RESOURCE_FILE_BYTES = MAX_SKILL_RESOURCE_CHARS * 4\n\nexport interface FileSystemSkillRoot {\n readonly path: string\n readonly source?: string\n}\n\nexport type FileSystemSkillIoPhase = 'discovery' | 'activation' | 'resource'\n\n/** Optional byte-level telemetry for acceptance tests and host diagnostics. */\nexport interface FileSystemSkillIoEvent {\n readonly phase: FileSystemSkillIoPhase\n readonly operation: 'read' | 'scan'\n readonly path: string\n readonly skillId?: string\n readonly bytesRead: number\n readonly entriesScanned?: number\n}\n\nexport interface FileSystemSkillsOptions {\n /** Provider identity used in catalogs and diagnostics. Defaults to filesystem. */\n readonly id?: string\n /** Exact ordered roots. Earlier roots win duplicate ids. */\n readonly roots?: readonly (string | FileSystemSkillRoot)[]\n /** Base for automatic project discovery. Defaults to lookup cwd, then process.cwd(). */\n readonly cwd?: string\n /** Scan `.agents/skills` from cwd through the git root. Defaults to true when roots are omitted. */\n readonly includeProjectAgents?: boolean\n /** Also scan `.dsh/skills` along the same project chain. Defaults to false. */\n readonly includeProjectDsh?: boolean\n /** Add `$HOME/.agents/skills` after project roots. Defaults to false for hermetic SDK usage. */\n readonly includeUserAgents?: boolean\n /** Hard cap for metadata candidates retained by one provider. Defaults to 1024. */\n readonly maxCandidates?: number\n /** Hard cap for directory entries scanned per discovery root. Defaults to 4,096. */\n readonly maxRootEntries?: number\n /** Observe bounded filesystem I/O without changing discovery semantics. */\n readonly onIo?: (event: FileSystemSkillIoEvent) => void\n}\n\ninterface ResolvedRoot {\n readonly path: string\n readonly source: string\n}\n\ninterface FileLocator {\n readonly skillFile: string\n readonly directory: string\n}\n\ninterface ParsedSkillFile {\n readonly input: SkillDefinitionInput\n}\n\n/**\n * Create a lazy filesystem provider. Discovery runs before each agent turn, so\n * a CLI sees newly added or edited skill folders without rebuilding the agent.\n */\nexport function fileSystemSkills(options: FileSystemSkillsOptions = {}): SkillProvider {\n const id = options.id ?? 'filesystem'\n const maxCandidates = boundedInteger(\n options.maxCandidates, DEFAULT_MAX_CANDIDATES, 1, 10_000, 'maxCandidates',\n )\n const maxRootEntries = boundedInteger(\n options.maxRootEntries, DEFAULT_MAX_ROOT_ENTRIES, 1, 100_000, 'maxRootEntries',\n )\n const issued = new WeakSet<object>()\n return defineSkillProvider({\n kind: 'skill-provider',\n id,\n async list(lookup) {\n const allowed = lookup.allowedSkillIds === undefined\n ? undefined\n : new Set(lookup.allowedSkillIds)\n if (allowed?.size === 0) return Object.freeze([])\n const roots = await resolveRoots(options, lookup)\n const candidates = new Map<string, SkillCandidate>()\n for (const root of roots) {\n for (const locator of await discoverRoot(root.path, maxRootEntries, lookup.signal, options.onIo)) {\n const candidate = await parseSkillMetadata(\n locator, root.source, id, lookup.signal, options.onIo,\n )\n if (allowed !== undefined && !allowed.has(candidate.id)) continue\n if (candidates.has(candidate.id)) continue\n if (candidates.size >= maxCandidates) {\n throw new RangeError(`skill provider '${id}' discovered more than ${maxCandidates} candidates`)\n }\n candidates.set(candidate.id, candidate)\n issued.add(candidate)\n }\n }\n return Object.freeze([...candidates.values()])\n },\n async load(candidate, lookup) {\n assertIssuedCandidate(issued, candidate, id)\n const locator = asLocator(candidate.locator, candidate.id)\n return (await parseSkillFile(\n locator, candidate.id, candidate.source, id, lookup.signal, options.onIo,\n )).input\n },\n async readResource(candidate, path, lookup) {\n assertIssuedCandidate(issued, candidate, id)\n const locator = asLocator(candidate.locator, candidate.id)\n return await readResource(locator, candidate.id, path, lookup.signal, options.onIo)\n },\n })\n}\n\n/** Eager convenience for CLIs that want definitions rather than a live provider. */\nexport async function discoverFileSystemSkills(\n options: FileSystemSkillsOptions & SkillProviderListOptions = {},\n): Promise<readonly SkillDefinition[]> {\n const provider = fileSystemSkills(options)\n const lookup = {\n ...(options.cwd === undefined ? {} : { cwd: options.cwd }),\n ...(options.signal === undefined ? {} : { signal: options.signal }),\n ...(options.allowedSkillIds === undefined ? {} : { allowedSkillIds: options.allowedSkillIds }),\n }\n const candidates = await provider.list(lookup)\n const definitions: SkillDefinition[] = []\n for (const candidate of candidates) {\n const input = await provider.load(candidate, lookup)\n if (input !== undefined) definitions.push(defineSkill(input))\n }\n return Object.freeze(definitions)\n}\n\nasync function resolveRoots(\n options: FileSystemSkillsOptions,\n lookup: SkillLookupOptions,\n): Promise<readonly ResolvedRoot[]> {\n if (options.roots !== undefined) {\n return Object.freeze(options.roots.map((root, index) => typeof root === 'string'\n ? { path: resolve(root), source: `custom-${index + 1}` }\n : { path: resolve(root.path), source: root.source ?? `custom-${index + 1}` }))\n }\n const cwd = resolve(lookup.cwd ?? options.cwd ?? process.cwd())\n const projectRoot = await findProjectRoot(cwd)\n const roots: ResolvedRoot[] = []\n if (options.includeProjectAgents ?? true) {\n roots.push(...ancestorSkillRoots(cwd, projectRoot, '.agents/skills', 'project-agents'))\n }\n if (options.includeProjectDsh ?? false) {\n roots.push(...ancestorSkillRoots(cwd, projectRoot, '.dsh/skills', 'project-dsh'))\n }\n if (options.includeUserAgents ?? false) {\n roots.push({ path: join(homedir(), '.agents', 'skills'), source: 'user-agents' })\n }\n return Object.freeze(roots)\n}\n\nfunction ancestorSkillRoots(\n cwd: string,\n projectRoot: string,\n suffix: string,\n source: string,\n): ResolvedRoot[] {\n const roots: ResolvedRoot[] = []\n let current = cwd\n while (true) {\n roots.push({ path: join(current, suffix), source })\n if (samePath(current, projectRoot)) break\n const parent = dirname(current)\n if (parent === current || !isInside(projectRoot, parent)) break\n current = parent\n }\n return roots\n}\n\nasync function findProjectRoot(cwd: string): Promise<string> {\n let current = cwd\n while (true) {\n try { await access(join(current, '.git')); return current }\n catch { /* keep walking */ }\n const parent = dirname(current)\n if (parent === current) return cwd\n current = parent\n }\n}\n\nasync function discoverRoot(\n root: string,\n maxEntries: number,\n signal: AbortSignal | undefined,\n onIo: FileSystemSkillsOptions['onIo'],\n): Promise<FileLocator[]> {\n throwIfAborted(signal)\n let canonicalRoot: string\n try { canonicalRoot = await realpath(root) }\n catch (error: unknown) {\n if (isMissing(error)) return []\n throw error\n }\n const entries = await readDirectoryBounded(canonicalRoot, maxEntries, signal, 'skill discovery root')\n emitIo(onIo, {\n phase: 'discovery', operation: 'scan', path: canonicalRoot,\n bytesRead: 0, entriesScanned: entries.length,\n })\n const locators: FileLocator[] = []\n for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {\n throwIfAborted(signal)\n const entryPath = join(canonicalRoot, entry.name)\n let directory: string\n try {\n const info = entry.isDirectory() ? undefined : await stat(entryPath)\n if (!entry.isDirectory() && info?.isDirectory() !== true) continue\n directory = await realpath(entryPath)\n } catch (error: unknown) {\n if (isMissing(error)) continue\n throw error\n }\n const skillFile = join(directory, 'SKILL.md')\n try { await access(skillFile) }\n catch { continue }\n locators.push({ skillFile, directory })\n }\n return locators\n}\n\nasync function parseSkillFile(\n locator: FileLocator,\n skillId: string,\n source: string,\n provider: string,\n signal: AbortSignal | undefined,\n onIo: FileSystemSkillsOptions['onIo'],\n): Promise<ParsedSkillFile> {\n throwIfAborted(signal)\n const contents = normalize(await readTextFileBounded(\n locator.skillFile, MAX_SKILL_FILE_BYTES, `skill '${locator.skillFile}'`, signal,\n onIo, { phase: 'activation', skillId },\n ))\n const { fields, body, metadata } = parseFrontMatter(contents, locator.skillFile)\n const id = fields.get('name') ?? ''\n const description = fields.get('description') ?? ''\n const whenToUse = fields.get('metadata.when_to_use')\n const allowImplicit = await readImplicitPolicy(\n locator.directory, signal, 'activation', onIo, skillId,\n )\n const invocation: SkillInvocationPolicy = {\n modelInvocable: booleanField(fields, 'disable-model-invocation', false) !== true\n && allowImplicit !== false,\n userInvocable: booleanField(fields, 'user-invocable', true),\n }\n const resourceManifest = await discoverResourceManifest(locator, signal, onIo, skillId)\n return {\n input: {\n id,\n name: fields.get('display-name') ?? id,\n description,\n ...(whenToUse === undefined ? {} : { whenToUse }),\n instructions: body,\n resourceManifest,\n invocation,\n source,\n provider,\n resourceBase: { kind: 'directory', value: locator.directory },\n path: locator.skillFile,\n metadata,\n },\n }\n}\n\nasync function parseSkillMetadata(\n locator: FileLocator,\n source: string,\n provider: string,\n signal: AbortSignal | undefined,\n onIo: FileSystemSkillsOptions['onIo'],\n): Promise<SkillCandidate> {\n const frontMatter = await readFrontMatter(locator.skillFile, signal, onIo)\n const fileRevision = await stat(locator.skillFile)\n const { fields } = parseFrontMatter(frontMatter, locator.skillFile)\n const id = fields.get('name') ?? ''\n const description = fields.get('description') ?? ''\n const whenToUse = fields.get('metadata.when_to_use')\n const allowImplicit = await readImplicitPolicy(\n locator.directory, signal, 'discovery', onIo, id,\n )\n const candidate: SkillCandidate = Object.freeze({\n id,\n name: fields.get('display-name') ?? id,\n description,\n ...(whenToUse === undefined ? {} : { whenToUse }),\n invocation: Object.freeze({\n modelInvocable: booleanField(fields, 'disable-model-invocation', false) !== true\n && allowImplicit !== false,\n userInvocable: booleanField(fields, 'user-invocable', true),\n }),\n source,\n provider,\n resourceBase: Object.freeze({ kind: 'directory' as const, value: locator.directory }),\n locator: Object.freeze({\n ...locator,\n revision: Object.freeze({ sizeBytes: fileRevision.size, modifiedMs: fileRevision.mtimeMs }),\n }),\n path: locator.skillFile,\n })\n validateCandidate(candidate, provider)\n return candidate\n}\n\nfunction parseFrontMatter(\n contents: string,\n path: string,\n): { fields: Map<string, string>; body: string; metadata: Readonly<Record<string, unknown>> } {\n const match = /^---\\n([\\s\\S]*?)\\n---\\n?/.exec(contents)\n if (match === null) throw new Error(`skill '${path}' must start with YAML front matter`)\n const fields = new Map<string, string>()\n let section = ''\n for (const line of (match[1] ?? '').split('\\n')) {\n if (line.trim().length === 0 || line.trimStart().startsWith('#')) continue\n const separator = line.indexOf(':')\n if (separator < 0) continue\n const indented = /^\\s/.test(line)\n const key = line.slice(0, separator).trim()\n const raw = line.slice(separator + 1).trim()\n if (!indented && raw.length === 0) { section = key; continue }\n fields.set(indented && section.length > 0 ? `${section}.${key}` : key, unquote(raw))\n }\n return {\n fields,\n body: contents.slice(match[0].length).trim(),\n metadata: Object.freeze(Object.fromEntries(fields)),\n }\n}\n\nasync function readImplicitPolicy(\n directory: string,\n signal: AbortSignal | undefined,\n phase: Exclude<FileSystemSkillIoPhase, 'resource'>,\n onIo: FileSystemSkillsOptions['onIo'],\n skillId: string,\n): Promise<boolean | undefined> {\n throwIfAborted(signal)\n try {\n const path = join(directory, 'agents', 'openai.yaml')\n const contents = normalize(await readTextFileBounded(\n path, MAX_OPENAI_METADATA_BYTES, `skill metadata '${path}'`, signal,\n onIo, { phase, skillId },\n ))\n const document = parseDocument(contents, { strict: true, uniqueKeys: true })\n if (document.errors.length > 0) {\n throw new TypeError(`skill metadata '${path}' is invalid YAML: ${document.errors[0]?.message ?? 'parse failed'}`)\n }\n let value: unknown\n try { value = document.toJS({ maxAliasCount: 0 }) }\n catch (error: unknown) {\n throw new TypeError(`skill metadata '${path}' contains unsupported YAML aliases`, { cause: error })\n }\n assertMetadataBounds(value, path)\n if (value === null || value === undefined) return undefined\n if (!isStringRecord(value)) throw new TypeError(`skill metadata '${path}' must be a YAML mapping`)\n const policy = value.policy\n if (policy === undefined) {\n if (containsImplicitPolicyKey(value)) {\n throw new TypeError(`skill metadata '${path}' must place allow_implicit_invocation under policy`)\n }\n return undefined\n }\n if (!isStringRecord(policy)) throw new TypeError(`skill metadata '${path}' policy must be a YAML mapping`)\n const allowImplicit = policy.allow_implicit_invocation\n if (allowImplicit === undefined) {\n if (containsImplicitPolicyKey(value)) {\n throw new TypeError(`skill metadata '${path}' has an invalid allow_implicit_invocation policy`)\n }\n return undefined\n }\n if (typeof allowImplicit !== 'boolean') {\n throw new TypeError(`skill metadata '${path}' policy.allow_implicit_invocation must be true or false`)\n }\n return allowImplicit\n } catch (error: unknown) {\n if (isMissing(error)) return undefined\n throw error\n }\n}\n\nfunction assertMetadataBounds(value: unknown, path: string): void {\n let nodes = 0\n const visit = (current: unknown, depth: number): void => {\n nodes++\n if (nodes > MAX_OPENAI_METADATA_NODES) {\n throw new RangeError(`skill metadata '${path}' exceeds ${MAX_OPENAI_METADATA_NODES} YAML nodes`)\n }\n if (depth > MAX_OPENAI_METADATA_DEPTH) {\n throw new RangeError(`skill metadata '${path}' exceeds YAML depth ${MAX_OPENAI_METADATA_DEPTH}`)\n }\n if (Array.isArray(current)) { for (const item of current) visit(item, depth + 1); return }\n if (isStringRecord(current)) { for (const item of Object.values(current)) visit(item, depth + 1) }\n }\n visit(value, 0)\n}\n\nfunction containsImplicitPolicyKey(value: unknown): boolean {\n if (Array.isArray(value)) return value.some(containsImplicitPolicyKey)\n if (!isStringRecord(value)) return false\n return Object.prototype.hasOwnProperty.call(value, 'allow_implicit_invocation')\n || Object.values(value).some(containsImplicitPolicyKey)\n}\n\nfunction isStringRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nasync function discoverResourceManifest(\n locator: FileLocator,\n signal: AbortSignal | undefined,\n onIo: FileSystemSkillsOptions['onIo'],\n skillId: string,\n): Promise<readonly SkillResourceSummary[]> {\n const resources: SkillResourceSummary[] = []\n const pending = [{ directory: locator.directory, depth: 0 }]\n let scannedEntries = 0\n try {\n while (pending.length > 0) {\n throwIfAborted(signal)\n const current = pending.pop()\n if (current === undefined) break\n const { directory, depth } = current\n const entries = await readDirectoryBounded(\n directory,\n MAX_MANIFEST_ENTRIES - scannedEntries,\n signal,\n `skill '${basename(locator.directory)}' resource tree`,\n )\n for (const entry of entries.sort((left, right) => right.name.localeCompare(left.name))) {\n throwIfAborted(signal)\n scannedEntries++\n if (scannedEntries > MAX_MANIFEST_ENTRIES) {\n throw new RangeError(\n `skill '${basename(locator.directory)}' resource tree exceeds ${MAX_MANIFEST_ENTRIES} entries`,\n )\n }\n const path = join(directory, entry.name)\n if (entry.isDirectory()) {\n if (IGNORED_RESOURCE_DIRECTORIES.has(entry.name)) continue\n if (depth >= MAX_MANIFEST_DEPTH) {\n throw new RangeError(\n `skill '${basename(locator.directory)}' resource tree exceeds depth ${MAX_MANIFEST_DEPTH}`,\n )\n }\n pending.push({ directory: path, depth: depth + 1 })\n continue\n }\n if (!entry.isFile() || !TEXT_RESOURCE_EXTENSIONS.has(extname(entry.name).toLocaleLowerCase())) continue\n const key = relative(locator.directory, path).split(sep).join('/')\n if (key === 'SKILL.md' || key === 'agents/openai.yaml') continue\n if (resources.length >= MAX_RESOURCES) {\n throw new RangeError(`skill '${basename(locator.directory)}' has more than ${MAX_RESOURCES} text resources`)\n }\n const info = await stat(path)\n resources.push(Object.freeze({ path: key, sizeBytes: info.size }))\n }\n }\n } finally {\n emitIo(onIo, {\n phase: 'activation', operation: 'scan', path: locator.directory,\n skillId, bytesRead: 0, entriesScanned: scannedEntries,\n })\n }\n return Object.freeze(resources.sort((left, right) => left.path.localeCompare(right.path)))\n}\n\nasync function readDirectoryBounded(\n directory: string,\n maxEntries: number,\n signal: AbortSignal | undefined,\n label: string,\n): Promise<Dirent[]> {\n if (maxEntries < 1) throw new RangeError(`${label} exceeds its entry limit`)\n const handle = await opendir(directory)\n const entries: Dirent[] = []\n try {\n for await (const entry of handle) {\n throwIfAborted(signal)\n if (entries.length >= maxEntries) {\n throw new RangeError(`${label} exceeds ${maxEntries} entries`)\n }\n entries.push(entry)\n }\n } finally {\n // Async iteration closes the handle. Some runtimes also reject a second\n // explicit close, so only close when iteration was interrupted and ignore\n // the already-closed case.\n await handle.close().catch(() => undefined)\n }\n return entries\n}\n\nasync function readResource(\n locator: FileLocator,\n skillId: string,\n resourcePath: string,\n signal: AbortSignal | undefined,\n onIo: FileSystemSkillsOptions['onIo'],\n): Promise<string | undefined> {\n validateSkillResourcePath(resourcePath, skillId)\n if (!TEXT_RESOURCE_EXTENSIONS.has(extname(resourcePath).toLocaleLowerCase())) return undefined\n const requested = resolve(locator.directory, ...resourcePath.split('/'))\n if (!isInside(locator.directory, requested)) return undefined\n let canonical: string\n try { canonical = await realpath(requested) }\n catch (error: unknown) {\n if (isMissing(error)) return undefined\n throw error\n }\n if (!isInside(locator.directory, canonical)) return undefined\n const info = await stat(canonical)\n if (!info.isFile()) return undefined\n return normalize(await readTextFileBounded(\n canonical, MAX_RESOURCE_FILE_BYTES, `skill resource '${resourcePath}'`, signal,\n onIo, { phase: 'resource', skillId },\n )).trim()\n}\n\nasync function readFrontMatter(\n path: string,\n signal: AbortSignal | undefined,\n onIo: FileSystemSkillsOptions['onIo'],\n): Promise<string> {\n throwIfAborted(signal)\n const handle = await open(path, 'r')\n const chunks: Buffer[] = []\n let total = 0\n try {\n while (total < MAX_FRONT_MATTER_BYTES) {\n throwIfAborted(signal)\n const buffer = Buffer.allocUnsafe(Math.min(READ_CHUNK_BYTES, MAX_FRONT_MATTER_BYTES - total))\n const { bytesRead } = await handle.read(buffer, 0, buffer.length, total)\n if (bytesRead === 0) break\n chunks.push(buffer.subarray(0, bytesRead))\n total += bytesRead\n const prefix = normalize(Buffer.concat(chunks, total).toString('utf8'))\n const match = /^---\\n[\\s\\S]*?\\n---(?:\\n|$)/.exec(prefix)\n if (match !== null) {\n emitIo(onIo, { phase: 'discovery', operation: 'read', path, bytesRead: total })\n return match[0]\n }\n }\n } finally {\n await handle.close()\n }\n emitIo(onIo, { phase: 'discovery', operation: 'read', path, bytesRead: total })\n throw new RangeError(\n `skill '${path}' front matter is missing its closing delimiter within ${MAX_FRONT_MATTER_BYTES} bytes`,\n )\n}\n\nasync function readTextFileBounded(\n path: string,\n maxBytes: number,\n label: string,\n signal: AbortSignal | undefined,\n onIo?: FileSystemSkillsOptions['onIo'],\n context?: { readonly phase: FileSystemSkillIoPhase; readonly skillId?: string },\n): Promise<string> {\n throwIfAborted(signal)\n const handle = await open(path, 'r')\n const chunks: Buffer[] = []\n let total = 0\n try {\n while (true) {\n throwIfAborted(signal)\n const buffer = Buffer.allocUnsafe(READ_CHUNK_BYTES)\n const { bytesRead } = await handle.read(buffer, 0, buffer.length, total)\n if (bytesRead === 0) break\n total += bytesRead\n if (total > maxBytes) throw new RangeError(`${label} exceeds ${maxBytes} bytes`)\n chunks.push(buffer.subarray(0, bytesRead))\n }\n return Buffer.concat(chunks, total).toString('utf8')\n } finally {\n await handle.close()\n if (context !== undefined) {\n emitIo(onIo, {\n phase: context.phase, operation: 'read', path,\n ...(context.skillId === undefined ? {} : { skillId: context.skillId }),\n bytesRead: total,\n })\n }\n }\n}\n\nfunction emitIo(\n listener: FileSystemSkillsOptions['onIo'],\n event: FileSystemSkillIoEvent,\n): void {\n try { listener?.(Object.freeze(event)) }\n catch { /* Diagnostics must not change skill-loading behavior. */ }\n}\n\nfunction asLocator(value: unknown, skillId: string): FileLocator {\n if (typeof value !== 'object' || value === null\n || typeof (value as FileLocator).skillFile !== 'string'\n || typeof (value as FileLocator).directory !== 'string') {\n throw new TypeError(`filesystem skill '${skillId}' has an invalid locator`)\n }\n return value as FileLocator\n}\n\nfunction assertIssuedCandidate(\n issued: WeakSet<object>, candidate: SkillCandidate, providerId: string,\n): void {\n if (!issued.has(candidate)) {\n throw new TypeError(`skill provider '${providerId}' received a candidate it did not issue`)\n }\n}\n\nfunction booleanField(fields: ReadonlyMap<string, string>, key: string, fallback: boolean): boolean {\n const value = fields.get(key)\n if (value === undefined) return fallback\n if (value === 'true') return true\n if (value === 'false') return false\n throw new TypeError(`skill front matter '${key}' must be true or false`)\n}\n\nfunction normalize(value: string): string { return value.replace(/^\\uFEFF/, '').replace(/\\r\\n/g, '\\n') }\nfunction unquote(value: string): string {\n const match = /^([\"'])([\\s\\S]*)\\1$/.exec(value)\n return match?.[2] ?? value\n}\nfunction samePath(left: string, right: string): boolean {\n return process.platform === 'win32'\n ? left.toLocaleLowerCase() === right.toLocaleLowerCase()\n : left === right\n}\nfunction isInside(root: string, target: string): boolean {\n const path = relative(root, target)\n return path.length === 0 || (!path.startsWith('..') && !isAbsolute(path))\n}\nfunction isMissing(error: unknown): boolean {\n return typeof error === 'object' && error !== null && 'code' in error\n && (error as { code?: unknown }).code === 'ENOENT'\n}\nfunction throwIfAborted(signal: AbortSignal | undefined): void {\n if (signal?.aborted === true) throw signal.reason ?? new Error('skill discovery aborted')\n}\n\nfunction boundedInteger(\n value: number | undefined, fallback: number, min: number, max: number, name: string,\n): number {\n const resolved = value ?? fallback\n if (!Number.isSafeInteger(resolved) || resolved < min || resolved > max) {\n throw new RangeError(`${name} must be an integer between ${min} and ${max}`)\n }\n return resolved\n}\n","import { createHash } from 'node:crypto'\nimport type { JsonValue } from '@alvin0/ai-agent-sdk-core'\nimport {\n defineSkillProviderPlugin,\n type RuntimeSkillCandidate,\n type RuntimeSkillLookupOptions,\n type SkillCandidate,\n type SkillProviderPlugin,\n type SkillReference,\n} from '@alvin0/ai-agent-sdk-core/skills'\nimport {\n fileSystemSkills,\n type FileSystemSkillsOptions,\n} from './filesystem-provider.ts'\n\n/** Versioned, lazy adapter used by the high-level runtime composition slot. */\nexport function fileSystemSkillProviderPlugin(\n options: FileSystemSkillsOptions = {},\n): SkillProviderPlugin {\n const provider = fileSystemSkills(options)\n return defineSkillProviderPlugin({\n id: provider.id,\n async list(lookup) {\n const candidates = await provider.list(markerFreeLookup(lookup))\n const rows = Object.freeze(candidates.map(candidate => runtimeCandidate(candidate)))\n return Object.freeze({ revision: catalogRevision(rows), candidates: rows })\n },\n async load(reference, lookup) {\n const candidate = await resolveReference(provider, reference, lookup)\n return candidate === undefined ? undefined : provider.load(candidate, markerFreeLookup(lookup))\n },\n async readResource(reference, path, lookup) {\n const candidate = await resolveReference(provider, reference, lookup)\n return candidate === undefined || provider.readResource === undefined\n ? undefined\n : provider.readResource(candidate, path, markerFreeLookup(lookup))\n },\n })\n}\n\nfunction runtimeCandidate(candidate: SkillCandidate): RuntimeSkillCandidate {\n return Object.freeze({\n id: candidate.id,\n name: candidate.name,\n description: candidate.description,\n ...(candidate.whenToUse === undefined ? {} : { whenToUse: candidate.whenToUse }),\n invocation: candidate.invocation,\n source: candidate.source,\n provider: candidate.provider,\n ...(candidate.locator === undefined ? {} : { locator: jsonLocator(candidate.locator) }),\n })\n}\n\nasync function resolveReference(\n provider: ReturnType<typeof fileSystemSkills>,\n reference: SkillReference,\n lookup: RuntimeSkillLookupOptions,\n): Promise<SkillCandidate | undefined> {\n if (reference.provider !== provider.id) return undefined\n const candidates = await provider.list({\n ...markerFreeLookup(lookup), allowedSkillIds: [reference.id],\n })\n return candidates.find(candidate => candidate.id === reference.id\n && candidate.source === reference.source\n && sameJson(candidate.locator, reference.locator))\n}\n\nfunction markerFreeLookup(\n lookup: RuntimeSkillLookupOptions & { readonly allowedSkillIds?: readonly string[] },\n) {\n return {\n ...(lookup.cwd === undefined ? {} : { cwd: lookup.cwd }),\n signal: lookup.signal,\n ...(lookup.allowedSkillIds === undefined ? {} : { allowedSkillIds: lookup.allowedSkillIds }),\n }\n}\n\nfunction catalogRevision(candidates: readonly RuntimeSkillCandidate[]): string {\n return createHash('sha256').update(JSON.stringify(candidates)).digest('hex')\n}\n\nfunction jsonLocator(value: unknown): JsonValue {\n const serialized = JSON.stringify(value)\n if (serialized === undefined) throw new TypeError('filesystem skill locator must be JSON serializable')\n return JSON.parse(serialized) as JsonValue\n}\n\nfunction sameJson(left: unknown, right: unknown): boolean {\n try { return JSON.stringify(left) === JSON.stringify(right) }\n catch { return false }\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alvin0/ai-agent-sdk-skill-filesystem",
|
|
3
|
+
"author": {
|
|
4
|
+
"name": "alvin0 - chaulamdinhai",
|
|
5
|
+
"email": "chaulamdinhai@gmail.com"
|
|
6
|
+
},
|
|
7
|
+
"version": "0.1.0",
|
|
8
|
+
"description": "Node filesystem-backed skill provider for ai-agent-sdk",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/alvin0/ai-agent-sdk.git",
|
|
13
|
+
"directory": "packages/skill-filesystem"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/alvin0/ai-agent-sdk/tree/main/packages/skill-filesystem#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/alvin0/ai-agent-sdk/issues"
|
|
18
|
+
},
|
|
19
|
+
"type": "module",
|
|
20
|
+
"sideEffects": false,
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"main": "./dist/index.mjs",
|
|
27
|
+
"types": "./dist/index.d.mts",
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/index.d.mts",
|
|
31
|
+
"import": "./dist/index.mjs",
|
|
32
|
+
"default": "./dist/index.mjs"
|
|
33
|
+
},
|
|
34
|
+
"./package.json": "./package.json"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public",
|
|
38
|
+
"provenance": true
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@alvin0/ai-agent-sdk-core": "^0.1.0"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"yaml": "2.9.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@alvin0/ai-agent-sdk-core": "^0.1.0",
|
|
48
|
+
"@arethetypeswrong/cli": "0.18.5",
|
|
49
|
+
"@types/node": "26.4.0",
|
|
50
|
+
"publint": "0.3.24",
|
|
51
|
+
"tsdown": "0.22.14",
|
|
52
|
+
"typescript": "7.0.2",
|
|
53
|
+
"vitest": "4.1.11"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=22.12"
|
|
57
|
+
},
|
|
58
|
+
"aiAgentSdk": {
|
|
59
|
+
"runtime": "node",
|
|
60
|
+
"coreApi": 1,
|
|
61
|
+
"roles": [
|
|
62
|
+
"skill-provider"
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
"scripts": {
|
|
66
|
+
"build": "tsdown",
|
|
67
|
+
"clean": "node -e \"for(const p of ['dist','artifacts'])require('node:fs').rmSync(p,{recursive:true,force:true})\"",
|
|
68
|
+
"typecheck": "tsc --noEmit",
|
|
69
|
+
"test": "vitest run --config vitest.config.ts",
|
|
70
|
+
"pack": "pnpm pack --pack-destination artifacts",
|
|
71
|
+
"test:pack": "node ../../scripts/test-packed-node-capability.mts skill-filesystem",
|
|
72
|
+
"check:publint": "publint",
|
|
73
|
+
"check:types": "attw --profile esm-only --pack ."
|
|
74
|
+
}
|
|
75
|
+
}
|