agents 0.31.0 → 0.32.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.
Files changed (105) hide show
  1. package/dist/{agent-routing-wS1TimZ_.d.ts → agent-routing-vVMA7m8Y.d.ts} +6 -6
  2. package/dist/agent-routing.d.ts +1 -1
  3. package/dist/agent-tool-types.d.ts +1 -1
  4. package/dist/{agent-tools-Bamxxb6Y.d.ts → agent-tools-gGXXkf1z.d.ts} +2 -2
  5. package/dist/agent-tools.d.ts +1 -1
  6. package/dist/agent-tools.js +0 -1
  7. package/dist/agent-tools.js.map +1 -1
  8. package/dist/browser/ai.js +1 -1
  9. package/dist/browser/tools/ai-sdk.d.ts +66 -1
  10. package/dist/browser/tools/ai-sdk.js +2 -2
  11. package/dist/browser/tools/pi.js +2 -2
  12. package/dist/browser/tools/tanstack-ai.d.ts +2 -2
  13. package/dist/browser/tools/tanstack-ai.js +2 -2
  14. package/dist/browser/tools/tanstack-ai.js.map +1 -1
  15. package/dist/{browser-tool-lGnvEBLJ.js → browser-tool-DRan7Lk6.js} +2 -2
  16. package/dist/{browser-tool-lGnvEBLJ.js.map → browser-tool-DRan7Lk6.js.map} +1 -1
  17. package/dist/catalog-BpOejOsE.js +239 -0
  18. package/dist/catalog-BpOejOsE.js.map +1 -0
  19. package/dist/catalog-BpiVV1OF.d.ts +173 -0
  20. package/dist/chat/index.d.ts +2 -2
  21. package/dist/chat-sdk/index.d.ts +1 -1
  22. package/dist/chat-sdk/index.js +1 -1
  23. package/dist/{client-wn4-yqGz.d.ts → client-eSfgYvd3.d.ts} +2 -2
  24. package/dist/client.d.ts +1 -1
  25. package/dist/{cloudflare-Dzvc7V2N.js → cloudflare-CUUm_9q9.js} +26 -4
  26. package/dist/cloudflare-CUUm_9q9.js.map +1 -0
  27. package/dist/{do-oauth-client-provider-DfsUcpc3.d.ts → do-oauth-client-provider-DtNqv7oj.d.ts} +26 -2
  28. package/dist/{index-YSKgfgg9.d.ts → emitter-BJZtCVjQ.d.ts} +6 -140
  29. package/dist/emitter-DPOVcCln.js +15 -0
  30. package/dist/emitter-DPOVcCln.js.map +1 -0
  31. package/dist/experimental/channels/web/index.js +1 -1
  32. package/dist/harness/container/index.js +0 -1
  33. package/dist/harness/container/index.js.map +1 -1
  34. package/dist/harness/container/runtime/index.js +2 -2
  35. package/dist/harness/opencode/index.js +0 -1
  36. package/dist/harness/opencode/index.js.map +1 -1
  37. package/dist/harness/pi/index.d.ts +11 -4
  38. package/dist/harness/pi/index.js +43 -141
  39. package/dist/harness/pi/index.js.map +1 -1
  40. package/dist/index.d.ts +2 -3
  41. package/dist/index.js +2 -2
  42. package/dist/markdown-BZ-VLtA1.js +86 -0
  43. package/dist/markdown-BZ-VLtA1.js.map +1 -0
  44. package/dist/mcp/client/do-oauth-client-provider.d.ts +4 -2
  45. package/dist/mcp/client/do-oauth-client-provider.js +15 -1
  46. package/dist/mcp/client/do-oauth-client-provider.js.map +1 -1
  47. package/dist/mcp/client/index.d.ts +1 -1
  48. package/dist/mcp/index.d.ts +1 -1
  49. package/dist/mcp/index.js +1 -1
  50. package/dist/models/pi-ai/index.js +129 -2
  51. package/dist/models/pi-ai/index.js.map +1 -1
  52. package/dist/observability/ai/index.js +1 -2
  53. package/dist/observability/ai/index.js.map +1 -1
  54. package/dist/observability/index.d.ts +170 -9
  55. package/dist/observability/index.js +386 -11
  56. package/dist/observability/index.js.map +1 -1
  57. package/dist/react.d.ts +2 -2
  58. package/dist/resource-B59OV2a2.js +325 -0
  59. package/dist/resource-B59OV2a2.js.map +1 -0
  60. package/dist/routing/index.d.ts +1 -1
  61. package/dist/script-CFDLTk5b.d.ts +91 -0
  62. package/dist/script-workspace-BpdAYq1R.d.ts +41 -0
  63. package/dist/serializable.d.ts +1 -1
  64. package/dist/skill-DRBcIBjC.d.ts +221 -0
  65. package/dist/skills/index.d.ts +275 -36
  66. package/dist/skills/index.js +265 -1534
  67. package/dist/skills/index.js.map +1 -1
  68. package/dist/skills/runner.d.ts +37 -0
  69. package/dist/skills/runner.js +912 -0
  70. package/dist/skills/runner.js.map +1 -0
  71. package/dist/skills/tools/ai-sdk.d.ts +36 -0
  72. package/dist/skills/tools/ai-sdk.js +94 -0
  73. package/dist/skills/tools/ai-sdk.js.map +1 -0
  74. package/dist/source-9RQzlEhM.d.ts +112 -0
  75. package/dist/{src-BGolXG_H.js → src-DoGNJ4Ud.js} +4 -5
  76. package/dist/src-DoGNJ4Ud.js.map +1 -0
  77. package/dist/sub-routing.d.ts +1 -1
  78. package/dist/tool-CxKsvxjm.js.map +1 -1
  79. package/dist/tool-DkT_DJBm.js.map +1 -1
  80. package/dist/{tool-helpers-BodWi4gd.js → tool-helpers-BI7ifO2S.js} +1 -2
  81. package/dist/{tool-helpers-BodWi4gd.js.map → tool-helpers-BI7ifO2S.js.map} +1 -1
  82. package/dist/vite.d.ts +2 -2
  83. package/dist/vite.js +10 -258
  84. package/dist/vite.js.map +1 -1
  85. package/dist/voice/index.d.ts +1 -1
  86. package/dist/websockets/index.js +1 -1
  87. package/dist/{websockets-CyoeosdO.js → websockets-C6SJtWCJ.js} +1 -2
  88. package/dist/{websockets-CyoeosdO.js.map → websockets-C6SJtWCJ.js.map} +1 -1
  89. package/dist/workflow-types.d.ts +1 -1
  90. package/dist/workflows.d.ts +1 -1
  91. package/dist/workflows.js +0 -1
  92. package/dist/workflows.js.map +1 -1
  93. package/docs/harnesses/pi.md +9 -3
  94. package/docs/mcp-client.md +23 -1
  95. package/docs/mcp-servers.md +2 -2
  96. package/docs/observability.md +5 -3
  97. package/package.json +24 -15
  98. package/dist/ai-sdk-hW3vntnk.d.ts +0 -72
  99. package/dist/cloudflare-Dzvc7V2N.js.map +0 -1
  100. package/dist/index-iv9FTXX9.d.ts +0 -311
  101. package/dist/skills/compile.d.ts +0 -40
  102. package/dist/skills/compile.js +0 -65
  103. package/dist/skills/compile.js.map +0 -1
  104. package/dist/src-BGolXG_H.js.map +0 -1
  105. package/skills-module.d.ts +0 -22
@@ -1,471 +1,153 @@
1
- import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "../classPrivateFieldGet2-DZBYAB34.js";
2
- import { t as _classPrivateMethodInitSpec } from "../classPrivateMethodInitSpec-qMjJ6sHQ.js";
3
- import { tool } from "ai";
4
- import { RpcTarget } from "cloudflare:workers";
5
- import { z } from "zod";
6
- import { parse } from "yaml";
7
- import { DynamicWorkerExecutor, resolveProvider } from "@cloudflare/codemode";
8
- import { Bash, defineCommand } from "just-bash";
9
- //#region src/skills/frontmatter.ts
10
- function parseSkillFrontmatter(raw) {
11
- const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
12
- if (!match) return {
13
- data: {},
14
- body: raw
15
- };
16
- const parsed = parse(match[1] ?? "");
17
- return {
18
- data: parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {},
19
- body: match[2] ?? ""
20
- };
21
- }
22
- function optionalString(value) {
23
- return typeof value === "string" && value.trim() ? value.trim() : void 0;
24
- }
25
- function optionalRecord(value) {
26
- return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
27
- }
28
- function parseSkillMarkdown(raw) {
29
- const { data, body } = parseSkillFrontmatter(raw);
30
- const name = optionalString(data.name);
31
- const description = optionalString(data.description);
32
- if (!name || !description) return null;
33
- return {
34
- name,
35
- description,
36
- body,
37
- compatibility: optionalString(data.compatibility),
38
- license: optionalString(data.license),
39
- allowedTools: optionalString(data["allowed-tools"]),
40
- metadata: optionalRecord(data.metadata)
41
- };
42
- }
43
- //#endregion
44
- //#region src/skills/types.ts
45
- function validateSkillResourcePath(path) {
46
- if (path.startsWith("/") || path.includes("\0") || path.split("/").some((part) => part === "" || part === "." || part === "..")) return `Skill resource path must be a normalized relative path: ${path}`;
47
- return null;
48
- }
49
- //#endregion
50
- //#region src/skills/manifest.ts
51
- function descriptorFromEntry(sourceId, entry) {
52
- return {
53
- name: entry.name,
54
- description: entry.description,
55
- compatibility: entry.compatibility,
56
- license: entry.license,
57
- allowedTools: entry.allowedTools,
58
- metadata: entry.metadata,
59
- sourceId,
60
- version: entry.version
61
- };
62
- }
63
- function contentFromEntry(sourceId, entry) {
64
- return {
65
- ...descriptorFromEntry(sourceId, entry),
66
- body: entry.body,
67
- rawContent: entry.rawContent,
68
- resources: entry.resources?.filter((resource) => validateSkillResourcePath(resource.path) === null).map(({ content: _content, ...resource }) => ({ ...resource }))
69
- };
70
- }
71
- function fromManifest(manifest) {
72
- const byName = new Map(manifest.skills.map((skill) => [skill.name, skill]));
73
- return {
74
- id: manifest.id,
75
- fingerprint: manifest.fingerprint,
76
- async list() {
77
- return manifest.skills.map((skill) => descriptorFromEntry(manifest.id, skill));
78
- },
79
- async load(name) {
80
- const skill = byName.get(name);
81
- return skill ? contentFromEntry(manifest.id, skill) : null;
82
- },
83
- async readResource(name, path) {
84
- const skill = byName.get(name);
85
- if (validateSkillResourcePath(path) !== null) return null;
86
- const resource = skill?.resources?.find((entry) => entry.path === path);
87
- if (resource && validateSkillResourcePath(resource.path) !== null) return null;
88
- return resource ? { ...resource } : null;
1
+ import { a as isIgnoredSkillPath, c as isModelInvocable, d as err, f as ok, l as parseSkillFrontmatter, n as describeSkillResource, o as parseSkillResourcePath, r as encodeSkillResourceContent, s as SkillParseError, t as InvalidSkillResourcePathError, u as parseSkillName } from "../resource-B59OV2a2.js";
2
+ import { n as parseSkill, t as formatSkill } from "../markdown-BZ-VLtA1.js";
3
+ import { a as SkillResourceNotFoundError, i as renderSkillResource, n as formatSkillDiagnostic, o as SkillSourceError, r as loadSkillCatalog, t as UnknownSkillError } from "../catalog-BpOejOsE.js";
4
+ //#region src/skills/tree.ts
5
+ /**
6
+ * @internal Turn a flat list of files under a skills root into a listing.
7
+ *
8
+ * Each `<directory>/SKILL.md` one level below the root is a skill; every
9
+ * other file in that directory is one of its resources. Workspace and R2
10
+ * sources share this so both read the same layout the same way.
11
+ *
12
+ * @param files - Every file under the root.
13
+ * @param readSkillFile - Read a root-relative `SKILL.md` as text.
14
+ * @param location - Absolute directory for a skill, when the source has one.
15
+ * @throws Whatever `readSkillFile` throws; callers wrap it as a source error.
16
+ */
17
+ async function indexSkillTree(files, readSkillFile, location) {
18
+ const byDirectory = /* @__PURE__ */ new Map();
19
+ for (const file of files) {
20
+ const slash = file.path.indexOf("/");
21
+ if (slash <= 0) continue;
22
+ const directory = file.path.slice(0, slash);
23
+ const entries = byDirectory.get(directory) ?? [];
24
+ entries.push(file);
25
+ byDirectory.set(directory, entries);
26
+ }
27
+ const skills = [];
28
+ const invalid = [];
29
+ const directories = /* @__PURE__ */ new Map();
30
+ for (const directory of [...byDirectory.keys()].sort()) {
31
+ const entries = byDirectory.get(directory) ?? [];
32
+ const skillPath = `${directory}/SKILL.md`;
33
+ if (!entries.some((entry) => entry.path === skillPath)) continue;
34
+ const markdown = await readSkillFile(skillPath);
35
+ if (markdown === null) continue;
36
+ const parsed = parseSkill(markdown, { directory });
37
+ if (!parsed.ok) {
38
+ invalid.push({
39
+ location: skillPath,
40
+ error: parsed.error
41
+ });
42
+ continue;
89
43
  }
90
- };
91
- }
92
- //#endregion
93
- //#region src/skills/r2.ts
94
- function normalizePrefix(prefix) {
95
- if (!prefix) return "";
96
- return prefix.endsWith("/") ? prefix : `${prefix}/`;
97
- }
98
- function resourceKind(path) {
99
- if (path.startsWith("references/")) return "reference";
100
- if (path.startsWith("scripts/")) return "script";
101
- if (path.startsWith("assets/")) return "asset";
102
- return "file";
103
- }
104
- const TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
105
- ".bash",
106
- ".css",
107
- ".csv",
108
- ".html",
109
- ".js",
110
- ".json",
111
- ".jsx",
112
- ".md",
113
- ".mjs",
114
- ".py",
115
- ".sh",
116
- ".svg",
117
- ".ts",
118
- ".tsx",
119
- ".txt",
120
- ".xml",
121
- ".yaml",
122
- ".yml"
123
- ]);
124
- const MIME_TYPES = /* @__PURE__ */ new Map([
125
- [".css", "text/css"],
126
- [".gif", "image/gif"],
127
- [".html", "text/html"],
128
- [".jpg", "image/jpeg"],
129
- [".jpeg", "image/jpeg"],
130
- [".js", "text/javascript"],
131
- [".json", "application/json"],
132
- [".md", "text/markdown"],
133
- [".mjs", "text/javascript"],
134
- [".pdf", "application/pdf"],
135
- [".png", "image/png"],
136
- [".py", "text/x-python"],
137
- [".sh", "text/x-shellscript"],
138
- [".svg", "image/svg+xml"],
139
- [".ts", "text/typescript"],
140
- [".tsx", "text/typescript"],
141
- [".txt", "text/plain"],
142
- [".webp", "image/webp"],
143
- [".woff", "font/woff"],
144
- [".woff2", "font/woff2"],
145
- [".xml", "application/xml"],
146
- [".yaml", "application/yaml"],
147
- [".yml", "application/yaml"]
148
- ]);
149
- function extensionOf$1(path) {
150
- const file = path.split("/").at(-1) ?? path;
151
- const index = file.lastIndexOf(".");
152
- return index === -1 ? "" : file.slice(index).toLowerCase();
153
- }
154
- function resourceEncoding(path) {
155
- return TEXT_EXTENSIONS.has(extensionOf$1(path)) ? "text" : "base64";
156
- }
157
- function resourceMimeType(path) {
158
- return MIME_TYPES.get(extensionOf$1(path));
159
- }
160
- function base64Encode(bytes) {
161
- let binary = "";
162
- const view = new Uint8Array(bytes);
163
- for (let i = 0; i < view.length; i++) binary += String.fromCharCode(view[i]);
164
- return btoa(binary);
165
- }
166
- async function readObjectFingerprint(bucket, key, path) {
167
- const object = await bucket.get(key);
168
- if (!object) return null;
169
- const encoding = resourceEncoding(path);
170
- return `${encoding}:${encoding === "base64" ? base64Encode(await object.arrayBuffer()) : await object.text()}`;
171
- }
172
- function stableHash(parts) {
173
- let hash = 2166136261;
174
- for (const part of parts) {
175
- for (let i = 0; i < part.length; i++) {
176
- hash ^= part.charCodeAt(i);
177
- hash = Math.imul(hash, 16777619);
44
+ const resources = [];
45
+ for (const entry of entries) {
46
+ if (entry.path === skillPath) continue;
47
+ const relative = entry.path.slice(directory.length + 1);
48
+ if (isIgnoredSkillPath(relative)) continue;
49
+ const path = parseSkillResourcePath(relative);
50
+ if (path.ok) resources.push(describeSkillResource(path.value, entry.size));
178
51
  }
179
- hash ^= 255;
180
- hash = Math.imul(hash, 16777619);
181
- }
182
- return (hash >>> 0).toString(36);
183
- }
184
- function objectFingerprintPart(object) {
185
- return [
186
- object.key,
187
- String(object.size),
188
- object.etag,
189
- object.uploaded?.toISOString() ?? ""
190
- ].join(":");
191
- }
192
- async function listAllObjects(bucket, prefix) {
193
- const objects = [];
194
- let cursor;
195
- let truncated = true;
196
- while (truncated) {
197
- const listed = await bucket.list({
198
- prefix,
199
- cursor
52
+ resources.sort((a, b) => a.path.localeCompare(b.path));
53
+ skills.push({
54
+ skill: parsed.value,
55
+ resources,
56
+ ...location === void 0 ? {} : { location: location(directory) },
57
+ ...parsed.warnings.length === 0 ? {} : { warnings: parsed.warnings }
200
58
  });
201
- objects.push(...listed.objects);
202
- truncated = listed.truncated;
203
- cursor = listed.truncated ? listed.cursor : void 0;
59
+ if (!directories.has(parsed.value.name)) directories.set(parsed.value.name, directory);
204
60
  }
205
- return objects.sort((a, b) => a.key.localeCompare(b.key));
206
- }
207
- async function readObject(bucket, key) {
208
- const object = await bucket.get(key);
209
- return object ? object.text() : null;
210
- }
211
- async function readResourceObject(bucket, key, descriptor) {
212
- const object = await bucket.get(key);
213
- if (!object) return null;
214
- const encoding = descriptor.encoding ?? resourceEncoding(descriptor.path);
215
- const content = encoding === "base64" ? base64Encode(await object.arrayBuffer()) : await object.text();
216
61
  return {
217
- ...descriptor,
218
- encoding,
219
- content
220
- };
221
- }
222
- function r2(bucket, options = {}) {
223
- const prefix = normalizePrefix(options.prefix);
224
- const id = options.id ?? `r2:${prefix || "/"}`;
225
- const allowedSkills = options.skills?.length ? new Set(options.skills) : null;
226
- const fingerprintMode = options.fingerprint ?? "metadata";
227
- const refreshIntervalMs = options.refreshIntervalMs ?? 6e4;
228
- let fingerprint = id;
229
- let loaded = false;
230
- let indexedAt = 0;
231
- let byName = /* @__PURE__ */ new Map();
232
- let resourcesByName = /* @__PURE__ */ new Map();
233
- async function refreshIndex(force = false) {
234
- if (loaded && !force && Date.now() - indexedAt < refreshIntervalMs) return;
235
- const objects = await listAllObjects(bucket, prefix);
236
- const objectsByKey = new Map(objects.map((object) => [object.key, object]));
237
- const skillDirectories = objects.map((object) => object.key.slice(prefix.length)).filter((key) => key.endsWith("/SKILL.md")).map((key) => key.slice(0, -9)).filter((directory) => directory && !directory.includes("/"));
238
- const nextByName = /* @__PURE__ */ new Map();
239
- const nextResourcesByName = /* @__PURE__ */ new Map();
240
- const fingerprintParts = [];
241
- for (const directory of skillDirectories) {
242
- const skillKey = `${prefix}${directory}/SKILL.md`;
243
- const rawContent = await readObject(bucket, skillKey);
244
- if (!rawContent) continue;
245
- const parsed = parseSkillMarkdown(rawContent);
246
- if (!parsed || allowedSkills?.has(parsed.name) === false) continue;
247
- const resourceKeys = objects.map((object) => object.key).filter((key) => key.startsWith(`${prefix}${directory}/`) && key !== skillKey).filter((key) => validateSkillResourcePath(key.slice(`${prefix}${directory}/`.length)) === null);
248
- const resources = [];
249
- for (const key of resourceKeys) {
250
- const path = key.slice(`${prefix}${directory}/`.length);
251
- const listedResource = objectsByKey.get(key);
252
- resources.push({
253
- path,
254
- kind: resourceKind(path),
255
- size: listedResource?.size,
256
- encoding: resourceEncoding(path),
257
- mimeType: resourceMimeType(path)
258
- });
259
- }
260
- const descriptor = {
261
- name: parsed.name,
262
- description: parsed.description,
263
- compatibility: parsed.compatibility,
264
- license: parsed.license,
265
- allowedTools: parsed.allowedTools,
266
- metadata: parsed.metadata,
267
- sourceId: id
268
- };
269
- const content = {
270
- ...descriptor,
271
- body: parsed.body,
272
- rawContent,
273
- resources: resources.map((resource) => ({ ...resource }))
274
- };
275
- if (!nextByName.has(parsed.name)) {
276
- nextByName.set(parsed.name, {
277
- descriptor,
278
- content,
279
- directory
280
- });
281
- nextResourcesByName.set(parsed.name, resources);
282
- }
283
- const skillObjects = [skillKey, ...resourceKeys].map((key) => objectsByKey.get(key)).filter((object) => Boolean(object));
284
- if (fingerprintMode === "content") {
285
- fingerprintParts.push(rawContent);
286
- for (const key of resourceKeys) {
287
- const path = key.slice(`${prefix}${directory}/`.length);
288
- fingerprintParts.push(await readObjectFingerprint(bucket, key, path) ?? "");
289
- }
290
- } else fingerprintParts.push(...skillObjects.map(objectFingerprintPart));
291
- }
292
- byName = nextByName;
293
- resourcesByName = nextResourcesByName;
294
- fingerprint = `${id}:${stableHash(fingerprintParts)}`;
295
- loaded = true;
296
- indexedAt = Date.now();
297
- }
298
- return {
299
- id,
300
- get fingerprint() {
301
- return fingerprint;
62
+ listing: {
63
+ skills,
64
+ invalid
302
65
  },
303
- async list() {
304
- await refreshIndex();
305
- return [...byName.values()].map(({ descriptor }) => ({ ...descriptor }));
306
- },
307
- async load(name) {
308
- await refreshIndex();
309
- const skill = byName.get(name);
310
- return skill ? { ...skill.content } : null;
311
- },
312
- async readResource(name, path) {
313
- if (validateSkillResourcePath(path) !== null) return null;
314
- await refreshIndex();
315
- const skill = byName.get(name);
316
- if (!skill) return null;
317
- const resource = resourcesByName.get(name)?.find((entry) => entry.path === path);
318
- if (!resource) return null;
319
- return readResourceObject(bucket, `${prefix}${skill.directory}/${path}`, resource);
320
- },
321
- async refresh() {
322
- await refreshIndex();
323
- }
66
+ directories
324
67
  };
325
68
  }
326
69
  //#endregion
327
70
  //#region src/skills/workspace.ts
328
- /** Directory skill scripts and relative paths resolve against. */
329
- const WORKSPACE_ROOT = "/workspace";
330
- const TEXT_ENCODER = new TextEncoder();
331
- const TEXT_DECODER = new TextDecoder();
332
- /** @internal Adapt a Workspace for projecting skills into it. */
333
- function createSkillWorkspaceFiles(workspace, rootOverride) {
334
- const root = parseRoot(rootOverride ?? `${WORKSPACE_ROOT}/.agents/skills`);
335
- const access = createSkillWorkspaceAccess(workspace);
336
- const readBytes = async (path) => {
71
+ /** Default skills directory inside a Workspace. */
72
+ const DEFAULT_WORKSPACE_SKILLS_ROOT = "/workspace/.agents/skills";
73
+ /**
74
+ * Create a source that reads skills from a Computer Workspace.
75
+ *
76
+ * Skills are plain files: every `<root>/<skill-name>/SKILL.md` is a skill,
77
+ * and the files beside it are its resources. The Workspace is read on every
78
+ * listing, so skills the agent or a user adds, edits, or deletes take effect
79
+ * the next time the catalog loads. A missing root lists no skills.
80
+ *
81
+ * @param target - The Workspace to read.
82
+ * @param options - Skills root and source id.
83
+ * @returns A source backed by the Workspace.
84
+ * @throws TypeError when `root` is not a normalized absolute path.
85
+ */
86
+ function workspace(target, options = {}) {
87
+ const root = parseRoot(options.root ?? "/workspace/.agents/skills");
88
+ const id = options.id ?? `workspace:${root}`;
89
+ const { fs } = target;
90
+ let directories = /* @__PURE__ */ new Map();
91
+ async function readBytes(path) {
337
92
  try {
338
- return await drain(await workspace.fs.readFile(path));
93
+ return await drain(await fs.readFile(path));
339
94
  } catch (error) {
340
95
  if (isNotFound(error)) return null;
341
96
  throw error;
342
97
  }
343
- };
98
+ }
344
99
  return {
345
- root,
346
- path(name, relativePath) {
347
- return `${root}/${parseSkillDirectoryName(name)}/${parseRelativePath(relativePath)}`;
348
- },
349
- async exists(path) {
350
- return await access.stat(path) !== null;
351
- },
352
- readText: (path) => access.readFile(path),
353
- async readResource(path, descriptor) {
354
- if ((descriptor.encoding ?? "text") === "text") {
355
- const content = await access.readFile(path);
356
- return content === null ? null : {
357
- ...descriptor,
358
- content
359
- };
360
- }
361
- const bytes = await readBytes(path);
362
- return bytes === null ? null : {
363
- ...descriptor,
364
- content: encodeBase64(bytes)
365
- };
366
- },
367
- writeText: (path, content) => access.writeFile(path, content),
368
- async writeResource(path, resource) {
369
- await access.writeFile(path, (resource.encoding ?? "text") === "text" ? resource.content : decodeBase64(resource.content));
370
- }
371
- };
372
- }
373
- /**
374
- * @internal Adapt a Workspace for skill scripts. Relative paths resolve
375
- * against `/workspace`.
376
- */
377
- function createSkillWorkspaceAccess(workspace) {
378
- const { fs } = workspace;
379
- const access = {
380
- async readFile(path) {
381
- try {
382
- return TEXT_DECODER.decode(await drain(await fs.readFile(resolvePath(path))));
383
- } catch (error) {
384
- if (isNotFound(error)) return null;
385
- throw error;
386
- }
387
- },
388
- async listFiles(path) {
389
- const directory = resolvePath(path);
390
- return (await fs.readdir(directory)).map((entry) => ({
391
- path: `${directory.replace(/\/$/, "")}/${entry.name}`,
392
- name: entry.name,
393
- type: entry.isDirectory ? "directory" : "file",
394
- size: entry.size
395
- }));
396
- },
397
- async glob(pattern) {
398
- const segments = resolvePath(pattern).split("/").filter(Boolean);
399
- const firstGlob = segments.findIndex((part) => /[*?[\]{}]/.test(part));
400
- if (firstGlob === -1) {
401
- const path = `/${segments.join("/")}`;
402
- const found = await access.stat(path);
403
- return found === null ? [] : [{
404
- path,
405
- ...found
406
- }];
407
- }
408
- let matches;
100
+ id,
101
+ async list() {
409
102
  try {
410
- matches = await fs.find(`/${segments.slice(0, firstGlob).join("/")}`, segments.slice(firstGlob).join("/"));
411
- } catch (error) {
412
- if (isNotFound(error)) return [];
413
- throw error;
103
+ let entries;
104
+ try {
105
+ entries = await fs.find(root, void 0, { exclude: ["*/node_modules/**", "*/.git/**"] });
106
+ } catch (error) {
107
+ if (isNotFound(error)) return ok({
108
+ skills: [],
109
+ invalid: []
110
+ });
111
+ throw error;
112
+ }
113
+ const tree = await indexSkillTree(entries.filter((entry) => entry.type === "file").map((entry) => ({ path: entry.path.slice(root.length + 1) })), async (path) => {
114
+ const bytes = await readBytes(`${root}/${path}`);
115
+ return bytes === null ? null : new TextDecoder().decode(bytes);
116
+ }, (directory) => `${root}/${directory}`);
117
+ directories = tree.directories;
118
+ return ok(tree.listing);
119
+ } catch (cause) {
120
+ return err(new SkillSourceError(id, "list", cause));
414
121
  }
415
- return Promise.all(matches.map(async (match) => match.type === "dir" ? {
416
- path: match.path,
417
- type: "directory",
418
- size: 0
419
- } : {
420
- path: match.path,
421
- type: "file",
422
- size: (await fs.stat(match.path)).size
423
- }));
424
122
  },
425
- async stat(path) {
123
+ async readResource(skill, path) {
124
+ const info = describeSkillResource(path);
426
125
  try {
427
- const info = await fs.stat(resolvePath(path));
428
- return {
429
- type: info.isDirectory ? "directory" : "file",
430
- size: info.size
431
- };
432
- } catch (error) {
433
- if (isNotFound(error)) return null;
434
- throw error;
126
+ const bytes = await readBytes(`${root}/${directories.get(skill) ?? skill}/${path}`);
127
+ if (bytes === null) return err(new SkillResourceNotFoundError(skill, path));
128
+ return ok({
129
+ ...info,
130
+ size: bytes.byteLength,
131
+ content: encodeSkillResourceContent(bytes, info.encoding)
132
+ });
133
+ } catch (cause) {
134
+ return err(new SkillSourceError(id, "read-resource", cause));
435
135
  }
436
- },
437
- async writeFile(path, content) {
438
- const absolute = resolvePath(path);
439
- const parent = absolute.slice(0, absolute.lastIndexOf("/"));
440
- if (parent) await fs.mkdir(parent, { recursive: true });
441
- await fs.writeFile(absolute, typeof content === "string" ? TEXT_ENCODER.encode(content) : content);
442
136
  }
443
137
  };
444
- return access;
445
- }
446
- function resolvePath(path) {
447
- if (path.startsWith("/")) return path;
448
- const relative = path.replace(/^\.(\/|$)/, "");
449
- return relative === "" ? WORKSPACE_ROOT : `${WORKSPACE_ROOT}/${relative}`;
450
138
  }
451
139
  function parseRoot(root) {
452
- if (!root.startsWith("/") || root.includes("\0") || root.split("/").some((part) => part === "." || part === "..")) throw new Error(`Skill Workspace root must be a normalized absolute path: ${root}`);
453
- const normalized = root.replace(/\/{2,}/g, "/").replace(/\/$/, "");
454
- if (!normalized) throw new Error("Skill Workspace root cannot be /");
140
+ const normalized = root.replace(/\/+$/, "");
141
+ if (!normalized.startsWith("/") || normalized.includes("\0") || normalized.slice(1).split("/").some((part) => part === "" || part === "." || part === "..")) throw new TypeError(`Skills root must be a normalized absolute path: ${root}`);
455
142
  return normalized;
456
143
  }
457
- function parseSkillDirectoryName(name) {
458
- if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name)) throw new Error(`Skill name must be a safe Workspace directory name: ${name}`);
459
- return name;
460
- }
461
- function parseRelativePath(path) {
462
- if (path.startsWith("/") || path.includes("\0") || path.split("/").some((part) => part === "" || part === "." || part === "..")) throw new Error(`Skill file path must be normalized and relative: ${path}`);
463
- return path;
464
- }
144
+ /**
145
+ * Computer raises `ENOENT` errors with a `code`. Over RPC only the message
146
+ * may survive, so match that too.
147
+ */
465
148
  function isNotFound(error) {
466
- if (!error || typeof error !== "object") return false;
467
- const record = error;
468
- return record.code === "ENOENT" || typeof record.message === "string" && /ENOENT|no such file|not found/i.test(record.message);
149
+ if (!(error instanceof Error)) return false;
150
+ return "code" in error && error.code === "ENOENT" || /ENOENT|no such (file|path)/i.test(error.message);
469
151
  }
470
152
  async function drain(stream) {
471
153
  const parts = [];
@@ -475,14 +157,12 @@ async function drain(stream) {
475
157
  while (true) {
476
158
  const { value, done } = await reader.read();
477
159
  if (done) break;
478
- if (!value || value.byteLength === 0) continue;
479
160
  parts.push(value);
480
161
  total += value.byteLength;
481
162
  }
482
163
  } finally {
483
164
  reader.releaseLock();
484
165
  }
485
- if (parts.length === 1) return parts[0];
486
166
  const bytes = new Uint8Array(total);
487
167
  let offset = 0;
488
168
  for (const part of parts) {
@@ -491,1122 +171,173 @@ async function drain(stream) {
491
171
  }
492
172
  return bytes;
493
173
  }
494
- function decodeBase64(value) {
495
- const binary = atob(value);
496
- const bytes = new Uint8Array(binary.length);
497
- for (let index = 0; index < binary.length; index++) bytes[index] = binary.charCodeAt(index);
498
- return bytes;
499
- }
500
- function encodeBase64(bytes) {
501
- const parts = [];
502
- for (let offset = 0; offset < bytes.byteLength; offset += 32768) parts.push(String.fromCharCode(...bytes.subarray(offset, offset + 32768)));
503
- return btoa(parts.join(""));
504
- }
505
174
  //#endregion
506
- //#region src/skills/runner.ts
507
- const DEFAULT_SCRIPT_TIMEOUT_MS = 3e4;
508
- const MAX_OUTPUT_ARTIFACT_BYTES = 64e3;
509
- const MAX_OUTPUT_ARTIFACTS = 20;
510
- const SUPPORTED_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([
511
- ".js",
512
- ".mjs",
513
- ".ts",
514
- ".tsx",
515
- ".py",
516
- ".sh",
517
- ".bash"
518
- ]);
519
- let runnerExperimentalWarned = false;
520
- function extensionOf(path) {
521
- const file = path.split("/").at(-1) ?? path;
522
- const index = file.lastIndexOf(".");
523
- return index === -1 ? "" : file.slice(index).toLowerCase();
524
- }
525
- function effectiveTimeout(options) {
526
- return options.timeout ?? DEFAULT_SCRIPT_TIMEOUT_MS;
527
- }
528
- function effectiveWorkspaceAccess(options) {
529
- if (options.workspace) return options.workspace;
530
- return options.workspaceInstance ? "read" : "none";
531
- }
532
- function validateSkillScriptPath(path) {
533
- if (!path.startsWith("scripts/")) return {
534
- ok: false,
535
- error: `Skill script path must start with "scripts/": ${path}`
536
- };
537
- if (path.startsWith("/") || path.includes("\0") || path.split("/").some((part) => part === "" || part === "." || part === "..")) return {
538
- ok: false,
539
- error: `Skill script path must be a normalized relative path under "scripts/": ${path}`
540
- };
541
- const extension = extensionOf(path);
542
- if (!SUPPORTED_SCRIPT_EXTENSIONS.has(extension)) return {
543
- ok: false,
544
- error: `Unsupported skill script extension "${extension || "(none)"}" for ${path}. Supported extensions: ${[...SUPPORTED_SCRIPT_EXTENSIONS].join(", ")}`
545
- };
546
- if (extension === ".sh" || extension === ".bash") return {
547
- ok: true,
548
- runtime: "bash"
549
- };
550
- if (extension === ".py") return {
551
- ok: true,
552
- runtime: "python"
553
- };
554
- if (extension === ".ts" || extension === ".tsx") return {
555
- ok: true,
556
- runtime: "typescript"
557
- };
558
- return {
559
- ok: true,
560
- runtime: "javascript"
561
- };
562
- }
563
- function validateMountedResourcePaths(request) {
564
- for (const resource of request.resources ?? []) {
565
- const pathError = validateSkillResourcePath(resource.path);
566
- if (pathError) throw new Error(pathError);
567
- }
568
- }
569
- function skillScriptContext(request) {
570
- return { skill: {
571
- name: request.skill.name,
572
- description: request.skill.description,
573
- compatibility: request.skill.compatibility,
574
- license: request.skill.license,
575
- allowedTools: request.skill.allowedTools,
576
- metadata: request.skill.metadata,
577
- sourceId: request.skill.sourceId,
578
- version: request.skill.version
579
- } };
580
- }
581
- /**
582
- * Text bundled resources exposed to function-style JS/TS scripts via
583
- * `ctx.files`. Binary resources are omitted in v1.
584
- */
585
- function textFilesMap(request) {
586
- const files = {};
587
- for (const resource of request.resources ?? []) if ((resource.encoding ?? "text") === "text") files[resource.path] = resource.content;
588
- return files;
589
- }
590
- function base64ToBytes(value) {
591
- const binary = atob(value);
592
- const bytes = new Uint8Array(binary.length);
593
- for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
594
- return bytes;
595
- }
596
- function stdinText(stdin) {
597
- return typeof stdin === "string" ? stdin : String(stdin ?? "");
598
- }
599
- function mountedFiles(request) {
600
- const files = {
601
- "/input.json": {
602
- content: JSON.stringify(request.input),
603
- encoding: "text"
604
- },
605
- "/context.json": {
606
- content: JSON.stringify(skillScriptContext(request)),
607
- encoding: "text"
608
- },
609
- "/skill/SKILL.md": {
610
- content: request.skill.rawContent ?? request.skill.body,
611
- encoding: "text"
612
- }
613
- };
614
- for (const resource of request.resources ?? []) {
615
- const pathError = validateSkillResourcePath(resource.path);
616
- if (pathError) throw new Error(pathError);
617
- files[`/skill/${resource.path}`] = {
618
- content: resource.content,
619
- encoding: resource.encoding ?? "text"
620
- };
621
- }
622
- files[`/skill/${request.path}`] = {
623
- content: request.source,
624
- encoding: "text"
625
- };
626
- return files;
627
- }
628
- function bashFiles(request) {
629
- const files = {
630
- "/input.json": JSON.stringify(request.input),
631
- "/context.json": JSON.stringify(skillScriptContext(request)),
632
- "/skill-script.sh": request.source,
633
- "/skill/SKILL.md": request.skill.rawContent ?? request.skill.body,
634
- [`/skill/${request.path}`]: request.source
635
- };
636
- for (const resource of request.resources ?? []) {
637
- const pathError = validateSkillResourcePath(resource.path);
638
- if (pathError) throw new Error(pathError);
639
- files[`/skill/${resource.path}`] = (resource.encoding ?? "text") === "base64" ? base64ToBytes(resource.content) : resource.content;
640
- }
641
- return files;
642
- }
643
- /**
644
- * Wrap a function-style JS/TS skill module so it runs inside the codemode
645
- * sandbox. The module must `export default` an async `run(input, ctx)`; we
646
- * rewrite the default export to a local binding, build the `ctx` capability
647
- * object from the host bridge proxy (`__host`), and invoke it.
648
- */
649
- function scriptModule(source, request) {
650
- const runnableSource = stripStrayExports(source.replace(/^\s*export\s+default\s+/m, "const __skillRun = "));
651
- const skillMeta = skillScriptContext(request).skill;
652
- return [
653
- "async () => {",
654
- ` const input = ${JSON.stringify(request.input)};`,
655
- ` const __skill = ${JSON.stringify(skillMeta)};`,
656
- ` const __files = ${JSON.stringify(textFilesMap(request))};`,
657
- " const workspace = {",
658
- " readFile: (path) => __host.readFile(path),",
659
- " listFiles: (path = \".\") => __host.listFiles(path),",
660
- " glob: (pattern) => __host.glob(pattern),",
661
- " stat: (path) => __host.stat(path),",
662
- " writeFile: (path, content) => __host.writeFile({ path, content })",
663
- " };",
664
- " const tools = new Proxy(",
665
- " { call: (name, input) => __host.callTool({ name, input }) },",
666
- " {",
667
- " get: (target, prop) =>",
668
- " prop in target",
669
- " ? target[prop]",
670
- " : (input) => __host.callTool({ name: String(prop), input })",
671
- " }",
672
- " );",
673
- " const output = {",
674
- " writeFile: (name, content) => __host.writeOutput({ name, content })",
675
- " };",
676
- " const ctx = { skill: __skill, files: __files, workspace, tools, output };",
677
- "",
678
- runnableSource,
679
- "",
680
- " if (typeof __skillRun !== \"function\") {",
681
- " throw new Error(\"Skill script default export must be a function (input, ctx).\");",
682
- " }",
683
- " return await __skillRun(input, ctx);",
684
- "}"
685
- ].join("\n");
686
- }
687
- /**
688
- * Whether a script declares a default export, in either the raw author form
689
- * (`export default ...`) or the bundled form esbuild emits
690
- * (`export { run as default }`).
691
- */
692
- function hasDefaultExport(source) {
693
- return /^\s*export\s+default\s+/m.test(source) || /export\s*\{[^}]*\bas\s+default\b[^}]*\}/m.test(source);
694
- }
695
- /**
696
- * Remove `export { ... }` blocks, which are illegal inside the function wrapper
697
- * the runner builds around skill scripts.
698
- */
699
- function stripStrayExports(source) {
700
- return source.replace(/\n?export\s*\{[\s\S]*?\};?/g, "");
701
- }
702
- function rewriteBundledSource(source) {
703
- const defaultBinding = source.match(/\bexport\s*\{[^}]*\b([A-Za-z_$][\w$]*)\s+as\s+default\b[^}]*\}/m);
704
- const stripped = stripStrayExports(source);
705
- if (defaultBinding) return `${stripped}\nconst __skillRun = ${defaultBinding[1]};`;
706
- return stripped;
707
- }
708
- async function prepareJavaScriptSource(request, runtime) {
709
- if ((request.resources ?? []).some((resource) => resource.path === request.path && resource.precompiled === true)) return rewriteBundledSource(request.source);
710
- let scriptFileCount = 1;
711
- for (const resource of request.resources ?? []) {
712
- if (resource.path === request.path) continue;
713
- const extension = extensionOf(resource.path);
714
- if (resource.kind === "script" && (resource.encoding ?? "text") === "text" && [
715
- ".js",
716
- ".mjs",
717
- ".ts",
718
- ".tsx"
719
- ].includes(extension)) scriptFileCount++;
720
- }
721
- if (runtime === "javascript" && scriptFileCount === 1) return request.source;
722
- throw new Error(`Skill script "${request.path}" must be compiled to a self-contained JavaScript module before it can run. Bundled skills are compiled automatically by the Agents Vite plugin. Skills served from R2 or other dynamic sources must be bundled ahead of time (e.g. with \`compileSkillScript\` from "agents/skills/compile") before upload.`);
723
- }
724
- async function executeToolFromSet(tools, name, input) {
725
- const target = tools?.[name];
726
- const execute = target && "execute" in target ? target.execute : void 0;
727
- if (!execute) throw new Error(`Tool not available: ${name}`);
728
- return execute(input);
729
- }
730
- function stringifyHostResult(result) {
731
- return JSON.stringify({ result });
732
- }
733
- function stringifyHostError(error) {
734
- return JSON.stringify({ error: error instanceof Error ? error.message : String(error) });
735
- }
736
- var _tools = /* @__PURE__ */ new WeakMap();
737
- var _workspace = /* @__PURE__ */ new WeakMap();
738
- var _workspaceAccess = /* @__PURE__ */ new WeakMap();
739
- var _outputs = /* @__PURE__ */ new WeakMap();
740
- var _SkillScriptHostBridge_brand = /* @__PURE__ */ new WeakSet();
175
+ //#region src/skills/r2.ts
741
176
  /**
742
- * The single source of truth for skill-script capabilities and permission
743
- * enforcement. Every runtime delegates here:
177
+ * Create a read-only source backed by an R2 bucket.
744
178
  *
745
- * - JavaScript/TypeScript reach it through a codemode `ToolProvider`.
746
- * - Python receives it as an RPC `RpcTarget` (JSON-marshalling methods).
747
- * - Bash calls it from `just-bash` custom commands.
179
+ * The bucket holds the Agent Skills layout under `prefix`:
180
+ * `<prefix><skill-name>/SKILL.md`, plus any bundled files beside it.
748
181
  *
749
- * Construct a fresh bridge per `run()` so the per-invocation `/output`
750
- * artifact buffer is never shared between concurrent script runs.
182
+ * @param bucket - The R2 binding.
183
+ * @param options - Prefix, id, name allowlist, and listing cache interval.
184
+ * @returns A source that reads skills from the bucket.
751
185
  */
752
- var SkillScriptHostBridge = class extends RpcTarget {
753
- constructor(tools, workspace, workspaceAccess) {
754
- super();
755
- _classPrivateMethodInitSpec(this, _SkillScriptHostBridge_brand);
756
- _classPrivateFieldInitSpec(this, _tools, void 0);
757
- _classPrivateFieldInitSpec(this, _workspace, void 0);
758
- _classPrivateFieldInitSpec(this, _workspaceAccess, void 0);
759
- _classPrivateFieldInitSpec(this, _outputs, /* @__PURE__ */ new Map());
760
- _classPrivateFieldSet2(_tools, this, tools);
761
- _classPrivateFieldSet2(_workspace, this, workspace === void 0 ? void 0 : createSkillWorkspaceAccess(workspace));
762
- _classPrivateFieldSet2(_workspaceAccess, this, workspaceAccess);
763
- }
764
- get workspaceAccess() {
765
- return _classPrivateFieldGet2(_workspaceAccess, this);
766
- }
767
- hasTools() {
768
- return Boolean(_classPrivateFieldGet2(_tools, this) && Object.keys(_classPrivateFieldGet2(_tools, this)).length > 0);
769
- }
770
- async callTool(name, input) {
771
- return executeToolFromSet(_classPrivateFieldGet2(_tools, this), name, input);
772
- }
773
- async readFile(path) {
774
- return _assertClassBrand(_SkillScriptHostBridge_brand, this, _requireWorkspace).call(this, "read").readFile(path);
775
- }
776
- async listFiles(path = ".") {
777
- return _assertClassBrand(_SkillScriptHostBridge_brand, this, _requireWorkspace).call(this, "read").listFiles(path);
778
- }
779
- async glob(pattern) {
780
- return _assertClassBrand(_SkillScriptHostBridge_brand, this, _requireWorkspace).call(this, "read").glob(pattern);
781
- }
782
- async stat(path) {
783
- return _assertClassBrand(_SkillScriptHostBridge_brand, this, _requireWorkspace).call(this, "read").stat(path);
784
- }
785
- async writeFile(path, content) {
786
- await _assertClassBrand(_SkillScriptHostBridge_brand, this, _requireWorkspace).call(this, "read-write").writeFile(path, content);
787
- }
788
- writeOutput(name, content) {
789
- const key = String(name);
790
- const text = typeof content === "string" ? content : String(content ?? "");
791
- if (new TextEncoder().encode(text).byteLength > MAX_OUTPUT_ARTIFACT_BYTES) throw new Error(`Output artifact "${key}" exceeds ${MAX_OUTPUT_ARTIFACT_BYTES} bytes.`);
792
- if (!_classPrivateFieldGet2(_outputs, this).has(key) && _classPrivateFieldGet2(_outputs, this).size >= MAX_OUTPUT_ARTIFACTS) throw new Error(`Too many skill output artifacts (max ${MAX_OUTPUT_ARTIFACTS}).`);
793
- _classPrivateFieldGet2(_outputs, this).set(key, {
794
- path: key,
795
- encoding: "text",
796
- content: text
797
- });
798
- }
799
- getOutputFiles() {
800
- return [..._classPrivateFieldGet2(_outputs, this).values()];
801
- }
802
- async tool(name, inputJson = "{}") {
803
- try {
804
- const input = inputJson.trim() ? JSON.parse(inputJson) : {};
805
- return stringifyHostResult(await this.callTool(name, input));
806
- } catch (error) {
807
- return stringifyHostError(error);
808
- }
809
- }
810
- async workspaceReadFile(path) {
811
- try {
812
- return stringifyHostResult(await this.readFile(path));
813
- } catch (error) {
814
- return stringifyHostError(error);
815
- }
816
- }
817
- async workspaceListFiles(path = ".") {
818
- try {
819
- return stringifyHostResult(await this.listFiles(path));
820
- } catch (error) {
821
- return stringifyHostError(error);
822
- }
823
- }
824
- async workspaceGlob(pattern) {
825
- try {
826
- return stringifyHostResult(await this.glob(pattern));
827
- } catch (error) {
828
- return stringifyHostError(error);
829
- }
830
- }
831
- async workspaceWriteFile(path, content) {
186
+ function r2(bucket, options = {}) {
187
+ const prefix = normalizePrefix(options.prefix);
188
+ const id = options.id ?? `r2:${prefix || "/"}`;
189
+ const allowed = options.skills?.length ? new Set(options.skills) : null;
190
+ const refreshIntervalMs = options.refreshIntervalMs ?? 6e4;
191
+ let cached = null;
192
+ async function list() {
193
+ if (cached && Date.now() - cached.at < refreshIntervalMs) return ok(cached.listing);
832
194
  try {
833
- await this.writeFile(path, content);
834
- return stringifyHostResult(null);
835
- } catch (error) {
836
- return stringifyHostError(error);
195
+ const { listing, directories } = await indexSkillTree((await listAll(bucket, prefix)).map((object) => ({
196
+ path: object.key.slice(prefix.length),
197
+ size: object.size
198
+ })), async (path) => {
199
+ const object = await bucket.get(`${prefix}${path}`);
200
+ return object ? object.text() : null;
201
+ });
202
+ const filtered = allowed ? {
203
+ skills: listing.skills.filter(({ skill }) => allowed.has(skill.name)),
204
+ invalid: listing.invalid
205
+ } : listing;
206
+ cached = {
207
+ listing: filtered,
208
+ directories,
209
+ at: Date.now()
210
+ };
211
+ return ok(filtered);
212
+ } catch (cause) {
213
+ return err(new SkillSourceError(id, "list", cause));
837
214
  }
838
215
  }
839
- };
840
- function _requireWorkspace(access) {
841
- if (!_classPrivateFieldGet2(_workspace, this) || _classPrivateFieldGet2(_workspaceAccess, this) === "none") throw new Error("Workspace access is not available.");
842
- if (access === "read-write" && _classPrivateFieldGet2(_workspaceAccess, this) !== "read-write") throw new Error("Workspace write access is not available.");
843
- return _classPrivateFieldGet2(_workspace, this);
844
- }
845
- /**
846
- * Expose the bridge to JS/TS scripts as a single codemode provider namespace
847
- * (`__host`). The sandbox `ctx` object wraps these calls into the friendly
848
- * `workspace` / `tools` / `output` surface (see {@link scriptModule}).
849
- */
850
- function hostProvider(bridge) {
851
216
  return {
852
- name: "__host",
853
- tools: {
854
- callTool: { execute: (a) => {
855
- const { name, input } = a;
856
- return bridge.callTool(name, input);
857
- } },
858
- readFile: { execute: (a) => bridge.readFile(String(a)) },
859
- listFiles: { execute: (a) => bridge.listFiles(typeof a === "string" ? a : ".") },
860
- glob: { execute: (a) => bridge.glob(String(a)) },
861
- stat: { execute: (a) => bridge.stat(String(a)) },
862
- writeFile: { execute: (a) => {
863
- const { path, content } = a;
864
- return bridge.writeFile(path, content);
865
- } },
866
- writeOutput: { execute: async (a) => {
867
- const { name, content } = a;
868
- bridge.writeOutput(name, content);
869
- return null;
870
- } }
217
+ id,
218
+ list,
219
+ async readResource(skill, path) {
220
+ if (allowed && !allowed.has(skill)) return err(new SkillResourceNotFoundError(skill, path));
221
+ try {
222
+ const directory = cached?.directories.get(skill) ?? skill;
223
+ const object = await bucket.get(`${prefix}${directory}/${path}`);
224
+ if (!object) return err(new SkillResourceNotFoundError(skill, path));
225
+ const info = describeSkillResource(path, object.size);
226
+ const bytes = new Uint8Array(await object.arrayBuffer());
227
+ return ok({
228
+ ...info,
229
+ content: encodeSkillResourceContent(bytes, info.encoding)
230
+ });
231
+ } catch (cause) {
232
+ return err(new SkillSourceError(id, "read-resource", cause));
233
+ }
871
234
  }
872
235
  };
873
236
  }
874
- function pythonScriptModule(request) {
875
- const source = request.source;
876
- const sourceLiteral = JSON.stringify(source);
877
- const filesLiteral = JSON.stringify(mountedFiles(request));
878
- return String.raw`
879
- import asyncio
880
- import base64
881
- import contextlib
882
- import inspect
883
- import io
884
- import json
885
- import os
886
- import sys
887
- import time
888
- import types
889
- from js import Object
890
- from pyodide.ffi import to_js as pyodide_to_js
891
- from workers import WorkerEntrypoint
892
-
893
- SKILL_SOURCE = ${sourceLiteral}
894
- SKILL_FILES = ${filesLiteral}
895
-
896
- async def maybe_await(value):
897
- if inspect.isawaitable(value):
898
- return await value
899
- return value
900
-
901
- async def decode_host_response(raw):
902
- data = json.loads(str(raw))
903
- if "error" in data:
904
- raise Exception(data["error"])
905
- return data.get("result")
906
-
907
- def to_js(obj):
908
- return pyodide_to_js(obj, dict_converter=Object.fromEntries)
909
-
910
- def materialize_files():
911
- os.makedirs("/output", exist_ok=True)
912
- for path, file in SKILL_FILES.items():
913
- directory = os.path.dirname(path)
914
- if directory:
915
- os.makedirs(directory, exist_ok=True)
916
- mode = "wb" if file.get("encoding") == "base64" else "w"
917
- with open(path, mode) as handle:
918
- if file.get("encoding") == "base64":
919
- handle.write(base64.b64decode(file.get("content", "")))
920
- else:
921
- handle.write(file.get("content", ""))
922
-
923
- def collect_output_files():
924
- output_files = []
925
- if not os.path.isdir("/output"):
926
- return output_files
927
-
928
- for root, _dirs, files in os.walk("/output"):
929
- for name in sorted(files):
930
- path = os.path.join(root, name)
931
- with open(path, "rb") as handle:
932
- content = handle.read()
933
- if len(content) > ${MAX_OUTPUT_ARTIFACT_BYTES}:
934
- raise Exception(f"Output artifact exceeds ${MAX_OUTPUT_ARTIFACT_BYTES} bytes: {path}")
935
- try:
936
- output_files.append({
937
- "path": path,
938
- "encoding": "text",
939
- "content": content.decode("utf-8")
940
- })
941
- except UnicodeDecodeError:
942
- output_files.append({
943
- "path": path,
944
- "encoding": "base64",
945
- "content": base64.b64encode(content).decode("ascii")
946
- })
947
-
948
- return sorted(output_files, key=lambda file: file["path"])
949
-
950
- def looks_function_style(source):
951
- return "def run(" in source or "async def run(" in source
952
-
953
- def timeout_trace(deadline):
954
- def trace(frame, event, arg):
955
- if time.monotonic() > deadline:
956
- raise TimeoutError("Python script execution timed out")
957
- return trace
958
- return trace
959
-
960
- class ToolNamespace:
961
- def __init__(self, host):
962
- self.host = host
963
-
964
- async def call(self, name, input=None):
965
- raw = await self.host.tool(name, json.dumps(input if input is not None else {}))
966
- return await decode_host_response(raw)
967
-
968
- def __getattr__(self, name):
969
- async def call_tool(input=None):
970
- return await self.call(name, input)
971
- return call_tool
972
-
973
- class WorkspaceNamespace:
974
- def __init__(self, host):
975
- self.host = host
976
-
977
- async def read_file(self, path):
978
- raw = await self.host.workspaceReadFile(path)
979
- return await decode_host_response(raw)
980
-
981
- async def list_files(self, path="."):
982
- raw = await self.host.workspaceListFiles(path)
983
- return await decode_host_response(raw)
984
-
985
- async def glob(self, pattern):
986
- raw = await self.host.workspaceGlob(pattern)
987
- return await decode_host_response(raw)
988
-
989
- async def write_file(self, path, content):
990
- raw = await self.host.workspaceWriteFile(path, content)
991
- return await decode_host_response(raw)
992
-
993
- class Default(WorkerEntrypoint):
994
- async def evaluate(self, input, ctx, host, timeout_ms=None):
995
- materialize_files()
996
- try:
997
- if looks_function_style(SKILL_SOURCE):
998
- skill_module = types.ModuleType("skill_script")
999
- skill_module.tools = ToolNamespace(host)
1000
- skill_module.workspace = WorkspaceNamespace(host)
1001
- exec(SKILL_SOURCE, skill_module.__dict__)
1002
- if not hasattr(skill_module, "run") or not callable(skill_module.run):
1003
- raise Exception("Python function-style skill script must define a callable run(input, ctx).")
1004
- execution = maybe_await(skill_module.run(input, ctx))
1005
- previous_trace = sys.gettrace()
1006
- if timeout_ms is not None:
1007
- sys.settrace(timeout_trace(time.monotonic() + (timeout_ms / 1000)))
1008
- try:
1009
- if timeout_ms is not None:
1010
- result = await asyncio.wait_for(execution, timeout_ms / 1000)
1011
- else:
1012
- result = await execution
1013
- finally:
1014
- sys.settrace(previous_trace)
1015
- return to_js({
1016
- "result": result,
1017
- "logs": [],
1018
- "mode": "function",
1019
- "outputFiles": collect_output_files()
1020
- })
1021
-
1022
- stdout = io.StringIO()
1023
- stderr = io.StringIO()
1024
- previous_stdin = sys.stdin
1025
- previous_trace = sys.gettrace()
1026
- if timeout_ms is not None:
1027
- sys.settrace(timeout_trace(time.monotonic() + (timeout_ms / 1000)))
1028
- sys.stdin = io.StringIO(json.dumps(input))
1029
- try:
1030
- namespace = {"__name__": "__main__", "__file__": "/skill/script.py"}
1031
- with contextlib.redirect_stdout(stdout), contextlib.redirect_stderr(stderr):
1032
- exec(SKILL_SOURCE, namespace)
1033
- finally:
1034
- sys.stdin = previous_stdin
1035
- sys.settrace(previous_trace)
1036
- return to_js({
1037
- "result": {
1038
- "stdout": stdout.getvalue(),
1039
- "stderr": stderr.getvalue(),
1040
- "exitCode": 0
1041
- },
1042
- "logs": [],
1043
- "mode": "cli",
1044
- "outputFiles": collect_output_files()
1045
- })
1046
- except TimeoutError:
1047
- return to_js({"error": "Python script execution timed out", "logs": []})
1048
- except SystemExit as err:
1049
- return to_js({
1050
- "result": {
1051
- "stdout": stdout.getvalue() if "stdout" in locals() else "",
1052
- "stderr": stderr.getvalue() if "stderr" in locals() else "",
1053
- "exitCode": int(err.code) if isinstance(err.code, int) else 1
1054
- },
1055
- "logs": [],
1056
- "mode": "cli",
1057
- "outputFiles": collect_output_files()
1058
- })
1059
- except asyncio.TimeoutError:
1060
- return to_js({"error": "Python script execution timed out", "logs": []})
1061
- except Exception as err:
1062
- return to_js({"error": str(err), "logs": []})
1063
- `;
1064
- }
1065
- async function runPythonScript(request, options, bridge) {
1066
- const execution = options.loader.get(`skill-python-${crypto.randomUUID()}`, () => ({
1067
- compatibilityDate: "2026-05-23",
1068
- compatibilityFlags: ["python_workers", "disable_python_external_sdk"],
1069
- mainModule: "skill_runner.py",
1070
- modules: { "skill_runner.py": pythonScriptModule(request) },
1071
- globalOutbound: options.network ? void 0 : null
1072
- })).getEntrypoint().evaluate(request.input, skillScriptContext(request), bridge, effectiveTimeout(options));
1073
- let timeout = null;
1074
- const timeoutPromise = new Promise((_, reject) => {
1075
- timeout = setTimeout(() => reject(/* @__PURE__ */ new Error("Python script execution timed out")), effectiveTimeout(options));
1076
- });
1077
- try {
1078
- const response = await Promise.race([execution, timeoutPromise]);
1079
- if (response.error) throw new Error(response.error);
1080
- const outputFiles = response.outputFiles ?? [];
1081
- if (response.mode === "cli") {
1082
- if (typeof response.result === "object" && response.result !== null && outputFiles.length > 0) return {
1083
- ...response.result,
1084
- outputFiles
1085
- };
1086
- return response.result;
1087
- }
1088
- if (response.logs?.length || outputFiles.length > 0) return {
1089
- result: response.result,
1090
- ...response.logs?.length ? { logs: response.logs } : {},
1091
- ...outputFiles.length > 0 ? { outputFiles } : {}
1092
- };
1093
- return response.result;
1094
- } finally {
1095
- if (timeout) clearTimeout(timeout);
1096
- }
237
+ function normalizePrefix(prefix) {
238
+ if (!prefix) return "";
239
+ return prefix.endsWith("/") ? prefix : `${prefix}/`;
1097
240
  }
1098
- async function runBashScript(request, options, bridge) {
1099
- const customCommands = [];
1100
- if (bridge.workspaceAccess !== "none") {
1101
- customCommands.push(defineCommand("workspace-read", async (args) => {
1102
- const path = args[0];
1103
- if (!path) return {
1104
- stdout: "",
1105
- stderr: "Missing path\n",
1106
- exitCode: 2
1107
- };
1108
- try {
1109
- return {
1110
- stdout: await bridge.readFile(path) ?? "",
1111
- stderr: "",
1112
- exitCode: 0
1113
- };
1114
- } catch (error) {
1115
- return {
1116
- stdout: "",
1117
- stderr: `${error instanceof Error ? error.message : String(error)}\n`,
1118
- exitCode: 1
1119
- };
1120
- }
1121
- }), defineCommand("workspace-list", async (args) => {
1122
- const path = args[0] ?? ".";
1123
- try {
1124
- return {
1125
- stdout: JSON.stringify(await bridge.listFiles(path)) + "\n",
1126
- stderr: "",
1127
- exitCode: 0
1128
- };
1129
- } catch (error) {
1130
- return {
1131
- stdout: "",
1132
- stderr: `${error instanceof Error ? error.message : String(error)}\n`,
1133
- exitCode: 1
1134
- };
1135
- }
1136
- }), defineCommand("workspace-glob", async (args) => {
1137
- const pattern = args[0];
1138
- if (!pattern) return {
1139
- stdout: "",
1140
- stderr: "Missing pattern\n",
1141
- exitCode: 2
1142
- };
1143
- try {
1144
- return {
1145
- stdout: JSON.stringify(await bridge.glob(pattern)) + "\n",
1146
- stderr: "",
1147
- exitCode: 0
1148
- };
1149
- } catch (error) {
1150
- return {
1151
- stdout: "",
1152
- stderr: `${error instanceof Error ? error.message : String(error)}\n`,
1153
- exitCode: 1
1154
- };
1155
- }
1156
- }));
1157
- if (bridge.workspaceAccess === "read-write") customCommands.push(defineCommand("workspace-write", async (args, ctx) => {
1158
- const path = args[0];
1159
- if (!path) return {
1160
- stdout: "",
1161
- stderr: "Missing path\n",
1162
- exitCode: 2
1163
- };
1164
- try {
1165
- await bridge.writeFile(path, stdinText(ctx.stdin));
1166
- return {
1167
- stdout: "",
1168
- stderr: "",
1169
- exitCode: 0
1170
- };
1171
- } catch (error) {
1172
- return {
1173
- stdout: "",
1174
- stderr: `${error instanceof Error ? error.message : String(error)}\n`,
1175
- exitCode: 1
1176
- };
1177
- }
1178
- }));
1179
- }
1180
- if (bridge.hasTools()) customCommands.push(defineCommand("tool", async (args, ctx) => {
1181
- const name = args[0];
1182
- if (!name) return {
1183
- stdout: "",
1184
- stderr: "Missing tool name\n",
1185
- exitCode: 2
1186
- };
1187
- try {
1188
- const rawInput = args[1] ?? stdinText(ctx.stdin) ?? "{}";
1189
- const input = rawInput.trim() ? JSON.parse(rawInput) : {};
1190
- const result = await bridge.callTool(name, input);
1191
- return {
1192
- stdout: JSON.stringify(result) + "\n",
1193
- stderr: "",
1194
- exitCode: 0
1195
- };
1196
- } catch (error) {
1197
- return {
1198
- stdout: "",
1199
- stderr: `${error instanceof Error ? error.message : String(error)}\n`,
1200
- exitCode: 1
1201
- };
1202
- }
1203
- }));
1204
- const controller = new AbortController();
1205
- const timeout = setTimeout(() => controller.abort(), effectiveTimeout(options));
1206
- try {
1207
- const result = await new Bash({
1208
- files: bashFiles(request),
1209
- customCommands,
1210
- defenseInDepth: true,
1211
- network: options.network ? {} : void 0
1212
- }).exec("bash /skill-script.sh", {
1213
- signal: controller.signal,
1214
- stdin: JSON.stringify(request.input)
241
+ async function listAll(bucket, prefix) {
242
+ const objects = [];
243
+ let cursor;
244
+ do {
245
+ const page = await bucket.list({
246
+ prefix,
247
+ cursor
1215
248
  });
1216
- return {
1217
- stdout: result.stdout,
1218
- stderr: result.stderr,
1219
- exitCode: result.exitCode
1220
- };
1221
- } finally {
1222
- if (timeout) clearTimeout(timeout);
1223
- }
1224
- }
1225
- async function runJavaScriptScript(request, options, bridge, runtime) {
1226
- if (!hasDefaultExport(request.source)) throw new Error("JS/TS skill scripts must `export default` an async run(input, ctx) function.");
1227
- const source = await prepareJavaScriptSource(request, runtime);
1228
- const result = await new DynamicWorkerExecutor({
1229
- loader: options.loader,
1230
- timeout: effectiveTimeout(options),
1231
- globalOutbound: options.network ? void 0 : null
1232
- }).execute(scriptModule(source, request), [resolveProvider(hostProvider(bridge))]);
1233
- if (result.error) {
1234
- const logs = result.logs?.length ? `\n\nConsole output:\n${result.logs.join("\n")}` : "";
1235
- throw new Error(`${result.error}${logs}`);
1236
- }
1237
- const outputFiles = bridge.getOutputFiles();
1238
- if (result.logs?.length || outputFiles.length > 0) return {
1239
- result: result.result,
1240
- ...result.logs?.length ? { logs: result.logs } : {},
1241
- ...outputFiles.length > 0 ? { outputFiles } : {}
1242
- };
1243
- return result.result;
249
+ objects.push(...page.objects.map((object) => ({
250
+ key: object.key,
251
+ size: object.size
252
+ })));
253
+ cursor = page.truncated ? page.cursor : void 0;
254
+ } while (cursor);
255
+ return objects;
1244
256
  }
257
+ //#endregion
258
+ //#region src/skills/manifest.ts
1245
259
  /**
1246
- * Create a skill script runner backed by a Worker Loader.
260
+ * Create a source from skills defined in code.
1247
261
  *
1248
- * Capabilities are opt-in and enforced by a single host bridge: no network and
1249
- * no tools by default, read-only workspace access when `workspaceInstance` is
1250
- * provided. JS/TS scripts are function-style (`export default run(input, ctx)`)
1251
- * and receive `ctx = { skill, files, workspace, tools, output }`. Python and
1252
- * Bash use the path-based `/skill`, `/input.json`, `/output` contract.
262
+ * Entries pass the same checks as `SKILL.md` files from other sources:
263
+ * broken spec rules become warnings, and entries without a usable
264
+ * name or description are reported as invalid. Useful for tests and for small fixed skill sets
265
+ * that do not need a Workspace or bucket.
1253
266
  *
1254
- * @experimental Skill script execution is experimental and may change before
1255
- * stabilizing.
267
+ * @param definition - The skills to serve.
268
+ * @returns A source that always lists the same skills.
1256
269
  */
1257
- function runner(options) {
1258
- return { async run(request) {
1259
- if (!runnerExperimentalWarned) {
1260
- runnerExperimentalWarned = true;
1261
- console.warn("[think] skills.runner script execution is experimental; the API and capabilities may change.");
1262
- }
1263
- const tools = typeof options.tools === "function" ? await options.tools() : options.tools;
1264
- const validation = validateSkillScriptPath(request.path);
1265
- if (!validation.ok) throw new Error(validation.error);
1266
- validateMountedResourcePaths(request);
1267
- const bridge = new SkillScriptHostBridge(tools, options.workspaceInstance, effectiveWorkspaceAccess(options));
1268
- if (validation.runtime === "bash") return await runBashScript(request, options, bridge);
1269
- if (validation.runtime === "python") return await runPythonScript(request, options, bridge);
1270
- return await runJavaScriptScript(request, options, bridge, validation.runtime);
1271
- } };
1272
- }
1273
- //#endregion
1274
- //#region src/skills/registry.ts
1275
- const SKILL_CONTEXT_LABEL = "think_skills";
1276
- function stableSourceFingerprint(sources) {
1277
- return sources.map((source) => `${source.id}:${source.fingerprint}`).join("|");
1278
- }
1279
- function renderSkillMarkdown(skill) {
1280
- const lines = [
1281
- "---",
1282
- `name: ${JSON.stringify(skill.name)}`,
1283
- `description: ${JSON.stringify(skill.description)}`
1284
- ];
1285
- if (skill.compatibility) lines.push(`compatibility: ${JSON.stringify(skill.compatibility)}`);
1286
- if (skill.license) lines.push(`license: ${JSON.stringify(skill.license)}`);
1287
- if (skill.allowedTools) lines.push(`allowed-tools: ${JSON.stringify(skill.allowedTools)}`);
1288
- if (skill.metadata) lines.push(`metadata: ${JSON.stringify(skill.metadata)}`);
1289
- lines.push("---", "", skill.body);
1290
- return lines.join("\n");
1291
- }
1292
- function wrapSkillContent(skill) {
1293
- const version = skill.version ? ` version="${skill.version}"` : "";
1294
- const resourceList = skill.resources?.length ? [
1295
- "",
1296
- "<skill_resources>",
1297
- ...skill.resources.map((resource) => ` <file kind="${resource.kind}" encoding="${resource.encoding ?? "text"}"${resource.size === void 0 ? "" : ` size="${resource.size}"`}>${resource.path}</file>`),
1298
- "</skill_resources>"
1299
- ].join("\n") : "";
1300
- return [
1301
- `<skill_content name="${skill.name}"${version}>`,
1302
- skill.body.trim(),
1303
- resourceList,
1304
- "</skill_content>"
1305
- ].join("\n");
1306
- }
1307
- function renderResourceList(resources) {
1308
- if (!resources?.length) return "No bundled resources.";
1309
- return resources.map((resource) => {
1310
- const encoding = resource.encoding ?? "text";
1311
- const size = resource.size === void 0 ? "" : `, ${resource.size} bytes`;
1312
- const mimeType = resource.mimeType ? `, ${resource.mimeType}` : "";
1313
- return `- ${resource.path} (${resource.kind}, ${encoding}${mimeType}${size})`;
1314
- }).join("\n");
1315
- }
1316
- function validateResourcePath(path) {
1317
- if (path.startsWith("../")) return `Resource paths cannot use "../". To read from another skill, use a qualified path like "other-skill/references/file.md".`;
1318
- return validateSkillResourcePath(path);
1319
- }
1320
- var SkillRegistry = class {
1321
- constructor(sources, scriptRunner = null) {
1322
- this.contextLabel = SKILL_CONTEXT_LABEL;
1323
- this.warnings = [];
1324
- this.descriptors = /* @__PURE__ */ new Map();
1325
- this.sourceBySkill = /* @__PURE__ */ new Map();
1326
- this.loaded = false;
1327
- this.workspaceFiles = null;
1328
- this.workspaceSkillNames = /* @__PURE__ */ new Set();
1329
- this.workspaceResources = /* @__PURE__ */ new Map();
1330
- this.sources = sources;
1331
- this.scriptRunner = scriptRunner;
1332
- }
1333
- get fingerprint() {
1334
- return stableSourceFingerprint(this.sources);
1335
- }
1336
- async load() {
1337
- if (this.loaded) return;
1338
- this.descriptors.clear();
1339
- this.sourceBySkill.clear();
1340
- this.warnings.length = 0;
1341
- for (const source of this.sources) {
1342
- let descriptors;
1343
- try {
1344
- descriptors = await source.list();
1345
- } catch (error) {
1346
- this.warnings.push(`Skill source "${source.id}" failed to list skills and was skipped: ${error instanceof Error ? error.message : String(error)}`);
1347
- continue;
1348
- }
1349
- for (const descriptor of descriptors) {
1350
- const existing = this.descriptors.get(descriptor.name);
1351
- if (existing) {
1352
- this.warnings.push(`Duplicate skill "${descriptor.name}" from ${source.id} ignored; already registered from ${existing.sourceId}.`);
1353
- continue;
1354
- }
1355
- this.descriptors.set(descriptor.name, {
1356
- ...descriptor,
1357
- sourceId: descriptor.sourceId ?? source.id
1358
- });
1359
- this.sourceBySkill.set(descriptor.name, source);
1360
- }
1361
- }
1362
- this.loaded = true;
1363
- }
1364
- async refresh() {
1365
- const refreshErrors = [];
1366
- await Promise.all(this.sources.map(async (source) => {
1367
- try {
1368
- await source.refresh?.();
1369
- } catch (error) {
1370
- refreshErrors.push(`Skill source "${source.id}" failed to refresh: ${error instanceof Error ? error.message : String(error)}`);
1371
- }
1372
- }));
1373
- this.loaded = false;
1374
- await this.load();
1375
- this.warnings.push(...refreshErrors);
1376
- }
1377
- /**
1378
- * Seed resolved Agent Skills into a Workspace.
1379
- * Existing files are preserved by default, and become authoritative for
1380
- * activation, resource reads, and script execution.
1381
- */
1382
- async seedWorkspace(workspace, options = {}) {
1383
- await this.load();
1384
- const files = createSkillWorkspaceFiles(workspace, options.root);
1385
- const result = {
1386
- written: 0,
1387
- preserved: 0,
1388
- skipped: 0,
1389
- warnings: [],
1390
- root: files.root
1391
- };
1392
- const seeded = /* @__PURE__ */ new Set();
1393
- const resources = /* @__PURE__ */ new Map();
1394
- for (const descriptor of this.descriptors.values()) {
1395
- const source = this.sourceBySkill.get(descriptor.name);
1396
- let skill = null;
1397
- try {
1398
- skill = await source?.load(descriptor.name) ?? null;
1399
- } catch (error) {
1400
- result.skipped++;
1401
- result.warnings.push(`Could not load skill "${descriptor.name}" for Workspace seeding: ${error instanceof Error ? error.message : String(error)}`);
1402
- continue;
1403
- }
1404
- if (!skill) {
1405
- result.skipped++;
1406
- result.warnings.push(`Could not load skill "${descriptor.name}" for Workspace seeding.`);
1407
- continue;
1408
- }
1409
- resources.set(descriptor.name, new Map((skill.resources ?? []).map((resource) => [resource.path, resource])));
1410
- const skillPath = files.path(descriptor.name, "SKILL.md");
1411
- if (await this.writeSeedFile(files, skillPath, skill.rawContent ?? renderSkillMarkdown(skill), options, result)) seeded.add(descriptor.name);
1412
- }
1413
- this.workspaceFiles = files;
1414
- this.workspaceSkillNames = seeded;
1415
- this.workspaceResources = resources;
1416
- return result;
1417
- }
1418
- /**
1419
- * Route skill reads through an already-seeded Workspace without probing or
1420
- * rewriting its files. Hosts use this when the durable source fingerprint
1421
- * matches the recorded projection fingerprint.
1422
- */
1423
- async useWorkspace(workspace, options = {}) {
1424
- await this.load();
1425
- this.workspaceFiles = createSkillWorkspaceFiles(workspace, options.root);
1426
- this.workspaceSkillNames = new Set(this.descriptors.keys());
1427
- this.workspaceResources.clear();
1428
- }
1429
- async writeSeedFile(files, path, content, options, result) {
1430
- try {
1431
- if (options.onConflict !== "replace" && await files.exists(path)) {
1432
- result.preserved++;
1433
- return true;
1434
- }
1435
- await files.writeText(path, content);
1436
- result.written++;
1437
- return true;
1438
- } catch {
1439
- result.skipped++;
1440
- return false;
270
+ function manifest(definition) {
271
+ const contents = /* @__PURE__ */ new Map();
272
+ const skills = [];
273
+ const invalid = [];
274
+ for (const entry of definition.skills) {
275
+ const parsed = parseSkillFrontmatter({
276
+ name: entry.name,
277
+ description: entry.description,
278
+ license: entry.license,
279
+ compatibility: entry.compatibility,
280
+ metadata: entry.metadata,
281
+ "allowed-tools": entry.allowedTools
282
+ }, entry.body);
283
+ if (!parsed.ok) {
284
+ invalid.push({
285
+ location: `${definition.id}:${entry.name}`,
286
+ error: parsed.error
287
+ });
288
+ continue;
1441
289
  }
1442
- }
1443
- async snapshot() {
1444
- await this.load();
1445
- const catalog = [];
1446
- for (const descriptor of this.descriptors.values()) catalog.push(`- ${descriptor.name}: ${descriptor.description}`);
1447
- return {
1448
- fingerprint: this.fingerprint,
1449
- catalogPrompt: catalog.length ? [
1450
- "Available skills. When a task matches a skill, use activate_skill with its name before proceeding.",
1451
- ...this.workspaceFiles ? [`Skill files are available at ${this.workspaceFiles.root}. Workspace edits affect later activations and resource reads.`] : [],
1452
- "",
1453
- ...catalog
1454
- ].join("\n") : null
1455
- };
1456
- }
1457
- async systemPrompt() {
1458
- return (await this.snapshot()).catalogPrompt;
1459
- }
1460
- async loadSkill(name) {
1461
- await this.load();
1462
- const source = this.sourceBySkill.get(name);
1463
- if (!source) return null;
1464
- const original = await source.load(name);
1465
- if (!original) return null;
1466
- this.workspaceResources.set(name, new Map((original.resources ?? []).map((resource) => [resource.path, resource])));
1467
- if (!this.workspaceSkillNames.has(name)) return original;
1468
- const rawContent = await this.workspaceFiles?.readText(this.workspaceFiles.path(name, "SKILL.md"));
1469
- if (rawContent === null || rawContent === void 0) return null;
1470
- const parsed = parseSkillMarkdown(rawContent);
1471
- if (!parsed) return null;
1472
- return {
1473
- ...original,
1474
- ...parsed,
1475
- rawContent,
1476
- sourceId: original.sourceId,
1477
- version: original.version,
1478
- resources: original.resources
1479
- };
1480
- }
1481
- resolveResourceTarget(name, path) {
1482
- const pathError = validateResourcePath(path);
1483
- if (pathError) return {
1484
- ok: false,
1485
- error: pathError
1486
- };
1487
- if (name) return {
1488
- ok: true,
1489
- name,
1490
- path
1491
- };
1492
- const [candidateName, ...rest] = path.split("/");
1493
- if (!candidateName || rest.length === 0) return {
1494
- ok: false,
1495
- error: "Resource path must include a skill name when name is omitted, for example: cloudflare-brand/references/tokens.md"
1496
- };
1497
- if (!this.descriptors.has(candidateName)) return {
1498
- ok: false,
1499
- error: `Unknown skill in qualified resource path: ${candidateName}`
1500
- };
1501
- return {
1502
- ok: true,
1503
- name: candidateName,
1504
- path: rest.join("/")
1505
- };
1506
- }
1507
- async readResource(name, path) {
1508
- const source = this.sourceBySkill.get(name);
1509
- if (this.workspaceSkillNames.has(name) && this.workspaceFiles) {
1510
- let descriptor = this.workspaceResources.get(name)?.get(path);
1511
- if (!descriptor) {
1512
- const skill = await source?.load(name);
1513
- const resources = new Map((skill?.resources ?? []).map((resource) => [resource.path, resource]));
1514
- this.workspaceResources.set(name, resources);
1515
- descriptor = resources.get(path);
1516
- }
1517
- if (!descriptor) return `Resource not found: ${name}/${path}`;
1518
- const workspacePath = this.workspaceFiles.path(name, path);
1519
- const existing = await this.workspaceFiles.readResource(workspacePath, descriptor);
1520
- if (existing) return existing;
1521
- if (!source?.readResource) return `Skill "${name}" has no readable resources.`;
1522
- const resource = await source.readResource(name, path);
1523
- if (!resource) return `Resource not found: ${name}/${path}`;
1524
- await this.workspaceFiles.writeResource(workspacePath, resource);
1525
- return resource;
1526
- }
1527
- if (!source?.readResource) return `Skill "${name}" has no readable resources.`;
1528
- return await source.readResource(name, path) ?? `Resource not found: ${name}/${path}`;
1529
- }
1530
- async readSkillResources(skill) {
290
+ if (contents.has(parsed.value.name)) continue;
291
+ const files = /* @__PURE__ */ new Map();
1531
292
  const resources = [];
1532
- for (const descriptor of skill.resources ?? []) {
1533
- const resource = await this.readResource(skill.name, descriptor.path);
1534
- if (typeof resource !== "string") resources.push(resource);
293
+ for (const resource of entry.resources ?? []) {
294
+ const path = parseSkillResourcePath(resource.path);
295
+ if (!path.ok) continue;
296
+ const inferred = describeSkillResource(path.value);
297
+ const info = {
298
+ ...inferred,
299
+ kind: resource.kind ?? inferred.kind,
300
+ encoding: resource.encoding ?? inferred.encoding,
301
+ ...resource.mimeType === void 0 ? {} : { mimeType: resource.mimeType }
302
+ };
303
+ resources.push(info);
304
+ files.set(path.value, {
305
+ ...info,
306
+ content: resource.content
307
+ });
1535
308
  }
1536
- return resources;
1537
- }
1538
- tools() {
1539
- const modelSkillNames = [...this.descriptors.values()].map((skill) => skill.name);
1540
- const tools = {};
1541
- if (modelSkillNames.length > 0) tools.activate_skill = tool({
1542
- description: "Activate a skill by name. Use this when the user's task matches one of the available skills.",
1543
- inputSchema: z.object({ name: z.enum(modelSkillNames) }),
1544
- execute: async ({ name }) => {
1545
- const skill = await this.loadSkill(name);
1546
- if (!skill) return `Skill not found: ${name}`;
1547
- return [
1548
- wrapSkillContent(skill),
1549
- "",
1550
- "Bundled resources:",
1551
- renderResourceList(skill.resources)
1552
- ].join("\n");
1553
- }
1554
- });
1555
- if (modelSkillNames.length > 0) tools.read_skill_resource = tool({
1556
- description: "Read a bundled resource from an available skill by relative path. Pass name and path, or use a qualified path like skill-name/references/file.md.",
1557
- inputSchema: z.object({
1558
- name: z.enum(modelSkillNames).optional(),
1559
- path: z.string().min(1)
1560
- }),
1561
- execute: async ({ name, path }) => {
1562
- const target = this.resolveResourceTarget(name, path);
1563
- if (!target.ok) return target.error;
1564
- const resource = await this.readResource(target.name, target.path);
1565
- if (typeof resource === "string") return resource;
1566
- const encoding = resource.encoding ?? "text";
1567
- const mimeType = resource.mimeType ? ` mimeType="${resource.mimeType}"` : "";
1568
- return [
1569
- `<skill_resource name="${target.name}" path="${resource.path}" kind="${resource.kind}" encoding="${encoding}"${mimeType}>`,
1570
- resource.content,
1571
- "</skill_resource>"
1572
- ].join("\n");
1573
- }
1574
- });
1575
- if (modelSkillNames.length > 0 && this.scriptRunner) tools.run_skill_script = tool({
1576
- description: "Run a bundled script resource from an available skill. Use only when a skill instructs you to run a script.",
1577
- inputSchema: z.object({
1578
- name: z.enum(modelSkillNames),
1579
- path: z.string().min(1),
1580
- input: z.unknown().default({})
1581
- }),
1582
- execute: async ({ name, path, input = {} }) => {
1583
- const validation = validateSkillScriptPath(path);
1584
- if (!validation.ok) return validation.error;
1585
- const skill = await this.loadSkill(name);
1586
- if (!skill) return `Skill not found: ${name}`;
1587
- const script = skill.resources?.find((resource) => resource.path === path);
1588
- if (!script) return `Script not found: ${name}/${path}`;
1589
- if (script.kind !== "script") return `Resource is not a script: ${name}/${path}`;
1590
- const resource = await this.readResource(name, path);
1591
- if (typeof resource === "string") return resource;
1592
- if ((resource.encoding ?? "text") !== "text") return `Script resource must be text, got ${resource.encoding}: ${name}/${path}`;
1593
- try {
1594
- return await this.scriptRunner.run({
1595
- skill,
1596
- path,
1597
- source: resource.content,
1598
- input,
1599
- resources: await this.readSkillResources(skill)
1600
- });
1601
- } catch (error) {
1602
- return `Skill script failed: ${error instanceof Error ? error.message : String(error)}`;
1603
- }
1604
- }
309
+ resources.sort((a, b) => a.path.localeCompare(b.path));
310
+ contents.set(parsed.value.name, files);
311
+ skills.push({
312
+ skill: parsed.value,
313
+ resources,
314
+ ...parsed.warnings.length === 0 ? {} : { warnings: parsed.warnings }
1605
315
  });
1606
- return tools;
1607
316
  }
1608
- };
317
+ const listing = {
318
+ skills,
319
+ invalid
320
+ };
321
+ return {
322
+ id: definition.id,
323
+ async list() {
324
+ return ok(listing);
325
+ },
326
+ async readResource(skill, path) {
327
+ const resource = contents.get(skill)?.get(path);
328
+ return resource ? ok(resource) : err(new SkillResourceNotFoundError(skill, path));
329
+ }
330
+ };
331
+ }
332
+ /**
333
+ * Create a source from skills defined in code.
334
+ *
335
+ * @deprecated Use {@link manifest}.
336
+ * @param definition - The skills to serve.
337
+ * @returns A source that always lists the same skills.
338
+ */
339
+ const fromManifest = manifest;
1609
340
  //#endregion
1610
- export { SkillRegistry, fromManifest, parseSkillFrontmatter, parseSkillMarkdown, r2, runner };
341
+ export { DEFAULT_WORKSPACE_SKILLS_ROOT, InvalidSkillResourcePathError, SkillParseError, SkillResourceNotFoundError, SkillSourceError, UnknownSkillError, formatSkill, formatSkillDiagnostic, fromManifest, isModelInvocable, loadSkillCatalog, manifest, parseSkill, parseSkillName, parseSkillResourcePath, r2, renderSkillResource, workspace };
1611
342
 
1612
343
  //# sourceMappingURL=index.js.map