agent-worker 0.9.0 → 0.11.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/README.md +4 -4
- package/dist/backends-DG5igQii.mjs +3 -0
- package/dist/{backends-BOAkfYyL.mjs → backends-DLaP0rMW.mjs} +45 -30
- package/dist/cli/index.mjs +1408 -1505
- package/dist/context-CKL-RY7a.mjs +4 -0
- package/dist/{display-pretty-BL9H2ocr.mjs → display-pretty-BsCsnPqs.mjs} +35 -14
- package/dist/index.d.mts +42 -30
- package/dist/index.mjs +561 -5
- package/dist/{logger-C3ekEOzi.mjs → logger-CCFaMMV7.mjs} +2 -2
- package/dist/memory-provider-CBlKMdbJ.mjs +70 -0
- package/dist/worker-CJ5_b2_q.mjs +446 -0
- package/dist/{workflow-CoZnnJ3O.mjs → workflow-CfITD7TN.mjs} +36 -37
- package/package.json +3 -1
- package/dist/backends-e6gCxRZ9.mjs +0 -3
- package/dist/context-dgI2YCGG.mjs +0 -4
- package/dist/mcp-server-BQCQxv2v.mjs +0 -573
- package/dist/skills-xNmQZf8e.mjs +0 -1002
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { A as createModelAsync, D as FRONTIER_MODELS, O as SUPPORTED_PROVIDERS, a as createMockBackend, c as CodexBackend, i as MockAIBackend, k as createModel, l as ClaudeCodeBackend, n as createBackend, o as SdkBackend, r as listBackends, s as CursorBackend, t as checkBackends } from "./backends-DLaP0rMW.mjs";
|
|
2
|
+
import { t as AgentWorker } from "./worker-CJ5_b2_q.mjs";
|
|
3
3
|
import { jsonSchema, tool } from "ai";
|
|
4
4
|
import { createBashTool } from "bash-tool";
|
|
5
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
6
|
+
import { join, normalize } from "node:path";
|
|
7
|
+
import { parse } from "yaml";
|
|
8
|
+
import { mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { tmpdir } from "node:os";
|
|
11
|
+
import { spawn } from "node:child_process";
|
|
5
12
|
|
|
6
13
|
//#region src/agent/tools/bash.ts
|
|
7
14
|
/**
|
|
@@ -10,7 +17,7 @@ import { createBashTool } from "bash-tool";
|
|
|
10
17
|
* Provides bash, readFile, writeFile tools for AI agents in a sandboxed environment
|
|
11
18
|
*/
|
|
12
19
|
/**
|
|
13
|
-
* Create bash tools as AI SDK tool() objects for use with
|
|
20
|
+
* Create bash tools as AI SDK tool() objects for use with AgentWorker
|
|
14
21
|
*
|
|
15
22
|
* @example
|
|
16
23
|
* ```typescript
|
|
@@ -18,7 +25,7 @@ import { createBashTool } from "bash-tool";
|
|
|
18
25
|
* files: { 'src/index.ts': 'console.log("hello")' }
|
|
19
26
|
* })
|
|
20
27
|
*
|
|
21
|
-
* const session = new
|
|
28
|
+
* const session = new AgentWorker({
|
|
22
29
|
* model: 'anthropic/claude-sonnet-4-5',
|
|
23
30
|
* system: 'You are a coding assistant.',
|
|
24
31
|
* tools
|
|
@@ -108,4 +115,553 @@ async function createBashToolsFromFiles(files, options = {}) {
|
|
|
108
115
|
}
|
|
109
116
|
|
|
110
117
|
//#endregion
|
|
111
|
-
|
|
118
|
+
//#region src/agent/tools/feedback.ts
|
|
119
|
+
/**
|
|
120
|
+
* Feedback tool — lets agents surface workflow improvement needs
|
|
121
|
+
*
|
|
122
|
+
* When enabled, agents can report what's missing or inconvenient during work:
|
|
123
|
+
* a tool they wished they had, a step that felt unnecessarily slow, or a
|
|
124
|
+
* capability gap. The purpose is workflow improvement, not bug reporting.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* const { tool, getFeedback } = createFeedbackTool();
|
|
129
|
+
*
|
|
130
|
+
* const session = new AgentWorker({
|
|
131
|
+
* model: 'anthropic/claude-sonnet-4-5',
|
|
132
|
+
* system: FEEDBACK_PROMPT + '\nYou are a coding assistant.',
|
|
133
|
+
* tools: { feedback: tool, bash: bashTool },
|
|
134
|
+
* });
|
|
135
|
+
*
|
|
136
|
+
* await session.send('...');
|
|
137
|
+
* console.log(getFeedback()); // review what the agent reported
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
/**
|
|
141
|
+
* Append this to the system prompt when the feedback tool is enabled.
|
|
142
|
+
* Tells the agent the tool exists and when to use it.
|
|
143
|
+
*/
|
|
144
|
+
const FEEDBACK_PROMPT = `
|
|
145
|
+
## Feedback
|
|
146
|
+
|
|
147
|
+
You have a \`feedback\` tool. If you run into something inconvenient during your work — a tool you wish you had, a workflow step that feels unnecessarily slow, a capability gap — use it to report what you needed.
|
|
148
|
+
|
|
149
|
+
The purpose is to improve the workflow for future runs. Don't force feedback; only call it when you genuinely hit a pain point.
|
|
150
|
+
`.trim();
|
|
151
|
+
function createFeedbackTool(options = {}) {
|
|
152
|
+
const { onFeedback, maxEntries = 50 } = options;
|
|
153
|
+
const entries = [];
|
|
154
|
+
return {
|
|
155
|
+
tool: tool({
|
|
156
|
+
description: "Report a workflow improvement need. Use when you hit something inconvenient — a missing tool, an awkward step, or a capability you wished you had.",
|
|
157
|
+
inputSchema: jsonSchema({
|
|
158
|
+
type: "object",
|
|
159
|
+
properties: {
|
|
160
|
+
target: {
|
|
161
|
+
type: "string",
|
|
162
|
+
description: "The area this is about — a tool name (e.g. bash, readFile), a workflow step, or a general area (e.g. file search, code review)."
|
|
163
|
+
},
|
|
164
|
+
type: {
|
|
165
|
+
type: "string",
|
|
166
|
+
enum: [
|
|
167
|
+
"missing",
|
|
168
|
+
"friction",
|
|
169
|
+
"suggestion"
|
|
170
|
+
],
|
|
171
|
+
description: "missing: a tool or capability you needed but didn't have. friction: something that works but is awkward or slow. suggestion: a concrete improvement idea."
|
|
172
|
+
},
|
|
173
|
+
description: {
|
|
174
|
+
type: "string",
|
|
175
|
+
description: "What you needed or what could be improved. Be specific."
|
|
176
|
+
},
|
|
177
|
+
context: {
|
|
178
|
+
type: "string",
|
|
179
|
+
description: "Optional: what you were trying to do when you hit this."
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
required: [
|
|
183
|
+
"target",
|
|
184
|
+
"type",
|
|
185
|
+
"description"
|
|
186
|
+
]
|
|
187
|
+
}),
|
|
188
|
+
execute: async (args) => {
|
|
189
|
+
const validTypes = [
|
|
190
|
+
"missing",
|
|
191
|
+
"friction",
|
|
192
|
+
"suggestion"
|
|
193
|
+
];
|
|
194
|
+
const rawType = args.type;
|
|
195
|
+
const type = validTypes.includes(rawType) ? rawType : "suggestion";
|
|
196
|
+
const entry = {
|
|
197
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
198
|
+
target: args.target,
|
|
199
|
+
type,
|
|
200
|
+
description: args.description,
|
|
201
|
+
...args.context ? { context: args.context } : {}
|
|
202
|
+
};
|
|
203
|
+
if (entries.length >= maxEntries) entries.shift();
|
|
204
|
+
entries.push(entry);
|
|
205
|
+
onFeedback?.(entry);
|
|
206
|
+
return { recorded: true };
|
|
207
|
+
}
|
|
208
|
+
}),
|
|
209
|
+
getFeedback: () => [...entries],
|
|
210
|
+
clearFeedback: () => {
|
|
211
|
+
entries.length = 0;
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
//#endregion
|
|
217
|
+
//#region src/agent/skills/provider.ts
|
|
218
|
+
const frontmatterSchema = z.object({
|
|
219
|
+
name: z.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/).max(64),
|
|
220
|
+
description: z.string().min(1).max(1024),
|
|
221
|
+
license: z.string().optional(),
|
|
222
|
+
compatibility: z.string().max(500).optional(),
|
|
223
|
+
metadata: z.record(z.string()).optional(),
|
|
224
|
+
"allowed-tools": z.string().optional()
|
|
225
|
+
});
|
|
226
|
+
var SkillsProvider = class {
|
|
227
|
+
skills = /* @__PURE__ */ new Map();
|
|
228
|
+
/**
|
|
229
|
+
* Add a single skill directory
|
|
230
|
+
*/
|
|
231
|
+
async addSkill(skillPath) {
|
|
232
|
+
const skillMdPath = join(skillPath, "SKILL.md");
|
|
233
|
+
try {
|
|
234
|
+
await stat(skillMdPath);
|
|
235
|
+
} catch {
|
|
236
|
+
throw new Error(`SKILL.md not found in ${skillPath}`);
|
|
237
|
+
}
|
|
238
|
+
const frontmatter = await this.parseFrontmatter(skillMdPath);
|
|
239
|
+
this.skills.set(frontmatter.name, {
|
|
240
|
+
name: frontmatter.name,
|
|
241
|
+
description: frontmatter.description,
|
|
242
|
+
path: skillPath
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Scan a directory and add all valid skills found
|
|
247
|
+
*/
|
|
248
|
+
async scanDirectory(dir) {
|
|
249
|
+
const resolved = this.resolvePath(dir);
|
|
250
|
+
try {
|
|
251
|
+
const entries = await readdir(resolved, { withFileTypes: true });
|
|
252
|
+
for (const entry of entries) if (entry.isDirectory()) {
|
|
253
|
+
const skillPath = join(resolved, entry.name);
|
|
254
|
+
try {
|
|
255
|
+
await this.addSkill(skillPath);
|
|
256
|
+
} catch {}
|
|
257
|
+
}
|
|
258
|
+
} catch {}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* List all available skills (metadata only)
|
|
262
|
+
*/
|
|
263
|
+
list() {
|
|
264
|
+
return Array.from(this.skills.values()).map(({ name, description }) => ({
|
|
265
|
+
name,
|
|
266
|
+
description
|
|
267
|
+
}));
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* View the full SKILL.md content
|
|
271
|
+
*/
|
|
272
|
+
async view(skillName) {
|
|
273
|
+
const skill = this.skills.get(skillName);
|
|
274
|
+
if (!skill) throw new Error(`Skill "${skillName}" not found`);
|
|
275
|
+
return await readFile(join(skill.path, "SKILL.md"), "utf-8");
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Read a file within a skill directory (relative path)
|
|
279
|
+
*/
|
|
280
|
+
async readFile(skillName, relativePath) {
|
|
281
|
+
const skill = this.skills.get(skillName);
|
|
282
|
+
if (!skill) throw new Error(`Skill "${skillName}" not found`);
|
|
283
|
+
const normalized = normalize(relativePath);
|
|
284
|
+
if (normalized.startsWith("..")) throw new Error("Path traversal not allowed");
|
|
285
|
+
return await readFile(join(skill.path, normalized), "utf-8");
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Parse YAML frontmatter from SKILL.md
|
|
289
|
+
*/
|
|
290
|
+
async parseFrontmatter(skillMdPath) {
|
|
291
|
+
const match = (await readFile(skillMdPath, "utf-8")).match(/^---\n([\s\S]+?)\n---/);
|
|
292
|
+
if (!match || !match[1]) throw new Error(`Invalid SKILL.md: missing frontmatter in ${skillMdPath}`);
|
|
293
|
+
try {
|
|
294
|
+
return frontmatterSchema.parse(parse(match[1]));
|
|
295
|
+
} catch (error) {
|
|
296
|
+
throw new Error(`Invalid frontmatter in ${skillMdPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Resolve tilde and relative paths
|
|
301
|
+
*/
|
|
302
|
+
resolvePath(path) {
|
|
303
|
+
if (path.startsWith("~/")) {
|
|
304
|
+
const home = process.env.HOME || process.env.USERPROFILE;
|
|
305
|
+
if (!home) throw new Error("Cannot resolve ~/ without HOME environment variable");
|
|
306
|
+
return join(home, path.slice(2));
|
|
307
|
+
}
|
|
308
|
+
return path;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Synchronous version: Add a single skill directory
|
|
312
|
+
*/
|
|
313
|
+
addSkillSync(skillPath) {
|
|
314
|
+
const skillMdPath = join(skillPath, "SKILL.md");
|
|
315
|
+
try {
|
|
316
|
+
statSync(skillMdPath);
|
|
317
|
+
} catch {
|
|
318
|
+
throw new Error(`SKILL.md not found in ${skillPath}`);
|
|
319
|
+
}
|
|
320
|
+
const frontmatter = this.parseFrontmatterSync(skillMdPath);
|
|
321
|
+
this.skills.set(frontmatter.name, {
|
|
322
|
+
name: frontmatter.name,
|
|
323
|
+
description: frontmatter.description,
|
|
324
|
+
path: skillPath
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Synchronous version: Scan a directory and add all valid skills found
|
|
329
|
+
*/
|
|
330
|
+
scanDirectorySync(dir) {
|
|
331
|
+
const resolved = this.resolvePath(dir);
|
|
332
|
+
try {
|
|
333
|
+
const entries = readdirSync(resolved, { withFileTypes: true });
|
|
334
|
+
for (const entry of entries) if (entry.isDirectory()) {
|
|
335
|
+
const skillPath = join(resolved, entry.name);
|
|
336
|
+
try {
|
|
337
|
+
this.addSkillSync(skillPath);
|
|
338
|
+
} catch {}
|
|
339
|
+
}
|
|
340
|
+
} catch {}
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Synchronous version: Parse YAML frontmatter from SKILL.md
|
|
344
|
+
*/
|
|
345
|
+
parseFrontmatterSync(skillMdPath) {
|
|
346
|
+
const match = readFileSync(skillMdPath, "utf-8").match(/^---\n([\s\S]+?)\n---/);
|
|
347
|
+
if (!match || !match[1]) throw new Error(`Invalid SKILL.md: missing frontmatter in ${skillMdPath}`);
|
|
348
|
+
try {
|
|
349
|
+
return frontmatterSchema.parse(parse(match[1]));
|
|
350
|
+
} catch (error) {
|
|
351
|
+
throw new Error(`Invalid frontmatter in ${skillMdPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Add skills from a SkillImporter (async)
|
|
356
|
+
* Used for temporary imported skills during session lifecycle
|
|
357
|
+
*/
|
|
358
|
+
async addImportedSkills(importer) {
|
|
359
|
+
const skillPaths = importer.getAllImportedSkillPaths();
|
|
360
|
+
for (const skillPath of skillPaths) await this.addSkill(skillPath);
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Add skills from a SkillImporter (sync)
|
|
364
|
+
*/
|
|
365
|
+
addImportedSkillsSync(importer) {
|
|
366
|
+
const skillPaths = importer.getAllImportedSkillPaths();
|
|
367
|
+
for (const skillPath of skillPaths) this.addSkillSync(skillPath);
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
//#endregion
|
|
372
|
+
//#region src/agent/tools/skills.ts
|
|
373
|
+
/**
|
|
374
|
+
* Create a Skills tool as an AI SDK tool() object
|
|
375
|
+
*/
|
|
376
|
+
function createSkillsTool(provider) {
|
|
377
|
+
return tool({
|
|
378
|
+
description: "Interact with available agent skills. Use \"list\" to see all skills with their descriptions, \"view\" to read a complete SKILL.md file, \"readFile\" to read files within a skill directory (e.g., references/, scripts/, assets/).",
|
|
379
|
+
inputSchema: jsonSchema({
|
|
380
|
+
type: "object",
|
|
381
|
+
properties: {
|
|
382
|
+
operation: {
|
|
383
|
+
type: "string",
|
|
384
|
+
enum: [
|
|
385
|
+
"list",
|
|
386
|
+
"view",
|
|
387
|
+
"readFile"
|
|
388
|
+
],
|
|
389
|
+
description: "Operation to perform"
|
|
390
|
+
},
|
|
391
|
+
skillName: {
|
|
392
|
+
type: "string",
|
|
393
|
+
description: "Skill name (required for view and readFile operations)"
|
|
394
|
+
},
|
|
395
|
+
filePath: {
|
|
396
|
+
type: "string",
|
|
397
|
+
description: "Relative file path within the skill directory (required for readFile operation, e.g., \"references/search-strategies.md\")"
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
required: ["operation"]
|
|
401
|
+
}),
|
|
402
|
+
execute: async (args) => {
|
|
403
|
+
const operation = args.operation;
|
|
404
|
+
const skillName = args.skillName;
|
|
405
|
+
const filePath = args.filePath;
|
|
406
|
+
switch (operation) {
|
|
407
|
+
case "list": {
|
|
408
|
+
const skills = provider.list();
|
|
409
|
+
if (skills.length === 0) return { message: "No skills available" };
|
|
410
|
+
return { skills: skills.map((s) => ({
|
|
411
|
+
name: s.name,
|
|
412
|
+
description: s.description
|
|
413
|
+
})) };
|
|
414
|
+
}
|
|
415
|
+
case "view":
|
|
416
|
+
if (!skillName) throw new Error("skillName is required for view operation");
|
|
417
|
+
return { content: await provider.view(skillName) };
|
|
418
|
+
case "readFile":
|
|
419
|
+
if (!skillName || !filePath) throw new Error("skillName and filePath are required for readFile operation");
|
|
420
|
+
return { content: await provider.readFile(skillName, filePath) };
|
|
421
|
+
default: throw new Error(`Unknown operation: ${operation}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
//#endregion
|
|
428
|
+
//#region src/agent/skills/import-spec.ts
|
|
429
|
+
const providerUrls = {
|
|
430
|
+
github: "https://github.com",
|
|
431
|
+
gitlab: "https://gitlab.com",
|
|
432
|
+
gitee: "https://gitee.com"
|
|
433
|
+
};
|
|
434
|
+
/**
|
|
435
|
+
* Parse import spec: [provider:]owner/repo[@ref]:{skill1,skill2,...}
|
|
436
|
+
*
|
|
437
|
+
* Examples:
|
|
438
|
+
* vercel-labs/agent-skills:react-best-practices
|
|
439
|
+
* vercel-labs/agent-skills:{react,web}
|
|
440
|
+
* vercel-labs/agent-skills@v1.0.0:react
|
|
441
|
+
* github:vercel-labs/agent-skills@main:react
|
|
442
|
+
* vercel-labs/agent-skills (imports all)
|
|
443
|
+
*/
|
|
444
|
+
function parseImportSpec(spec) {
|
|
445
|
+
const match = spec.match(/^(?:([a-z]+):)?([^/@:]+)\/([^/@:]+)(?:@([^:]+))?(?::(.+))?$/);
|
|
446
|
+
if (!match) throw new Error(`Invalid import spec: ${spec}\nFormat: [provider:]owner/repo[@ref]:{skill1,skill2,...}`);
|
|
447
|
+
const [, providerMatch = "github", ownerMatch, repoMatch, refMatch = "main", skillsStr] = match;
|
|
448
|
+
if (!ownerMatch || !repoMatch) throw new Error(`Invalid import spec: ${spec}\nFormat: [provider:]owner/repo[@ref]:{skill1,skill2,...}`);
|
|
449
|
+
const provider = providerMatch;
|
|
450
|
+
const owner = ownerMatch;
|
|
451
|
+
const repo = repoMatch;
|
|
452
|
+
const ref = refMatch;
|
|
453
|
+
if (![
|
|
454
|
+
"github",
|
|
455
|
+
"gitlab",
|
|
456
|
+
"gitee"
|
|
457
|
+
].includes(provider)) throw new Error(`Unsupported provider: ${provider}. Supported: github, gitlab, gitee`);
|
|
458
|
+
const safeNamePattern = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
459
|
+
if (!safeNamePattern.test(owner)) throw new Error(`Invalid owner: "${owner}". Must start with alphanumeric and only contain alphanumeric, hyphen, underscore, or dot`);
|
|
460
|
+
if (!safeNamePattern.test(repo)) throw new Error(`Invalid repo: "${repo}". Must start with alphanumeric and only contain alphanumeric, hyphen, underscore, or dot`);
|
|
461
|
+
if (!safeNamePattern.test(ref)) throw new Error(`Invalid ref: "${ref}". Must start with alphanumeric and only contain alphanumeric, hyphen, underscore, or dot`);
|
|
462
|
+
let skills = "all";
|
|
463
|
+
if (skillsStr) if (skillsStr.startsWith("{") && skillsStr.endsWith("}")) {
|
|
464
|
+
const skillList = skillsStr.slice(1, -1).split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
465
|
+
if (skillList.length === 0) throw new Error("Empty skill list in braces");
|
|
466
|
+
skills = skillList;
|
|
467
|
+
} else skills = [skillsStr.trim()];
|
|
468
|
+
return {
|
|
469
|
+
provider,
|
|
470
|
+
owner,
|
|
471
|
+
repo,
|
|
472
|
+
ref,
|
|
473
|
+
skills,
|
|
474
|
+
rawSpec: spec
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Build Git URL from import spec
|
|
479
|
+
*/
|
|
480
|
+
function buildGitUrl(spec) {
|
|
481
|
+
return `${providerUrls[spec.provider]}/${spec.owner}/${spec.repo}.git`;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Get display name for import spec
|
|
485
|
+
*/
|
|
486
|
+
function getSpecDisplayName(spec) {
|
|
487
|
+
const skillsDisplay = spec.skills === "all" ? "all skills" : spec.skills.length === 1 ? spec.skills[0] : `${spec.skills.length} skills`;
|
|
488
|
+
return `${spec.owner}/${spec.repo}@${spec.ref} (${skillsDisplay})`;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
//#endregion
|
|
492
|
+
//#region src/agent/skills/importer.ts
|
|
493
|
+
/**
|
|
494
|
+
* Temporary skill importer for session lifecycle
|
|
495
|
+
* Clones Git repos to temp directory and manages imported skills
|
|
496
|
+
*/
|
|
497
|
+
var SkillImporter = class {
|
|
498
|
+
tempDir;
|
|
499
|
+
imported = /* @__PURE__ */ new Map();
|
|
500
|
+
constructor(sessionId) {
|
|
501
|
+
this.tempDir = join(tmpdir(), `agent-worker-skills-${sessionId}`);
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Import skills from a Git repository
|
|
505
|
+
*/
|
|
506
|
+
async import(spec) {
|
|
507
|
+
const parsed = parseImportSpec(spec);
|
|
508
|
+
console.log(`Importing: ${getSpecDisplayName(parsed)}`);
|
|
509
|
+
const repoDir = await this.cloneRepo(parsed);
|
|
510
|
+
const skillNames = await this.extractSkills(repoDir, parsed);
|
|
511
|
+
console.log(`✓ Imported ${skillNames.length} skill(s): ${skillNames.join(", ")}`);
|
|
512
|
+
return skillNames;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Import skills from multiple specs
|
|
516
|
+
*/
|
|
517
|
+
async importMultiple(specs) {
|
|
518
|
+
const allSkillNames = [];
|
|
519
|
+
for (const spec of specs) try {
|
|
520
|
+
const skillNames = await this.import(spec);
|
|
521
|
+
allSkillNames.push(...skillNames);
|
|
522
|
+
} catch (error) {
|
|
523
|
+
console.error(`Failed to import ${spec}:`, error);
|
|
524
|
+
}
|
|
525
|
+
return allSkillNames;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Get path for an imported skill
|
|
529
|
+
*/
|
|
530
|
+
getImportedSkillPath(skillName) {
|
|
531
|
+
return this.imported.get(skillName)?.tempPath || null;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Get all imported skill paths
|
|
535
|
+
*/
|
|
536
|
+
getAllImportedSkillPaths() {
|
|
537
|
+
return Array.from(this.imported.values()).map((s) => s.tempPath);
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Get all imported skills metadata
|
|
541
|
+
*/
|
|
542
|
+
getImportedSkills() {
|
|
543
|
+
return Array.from(this.imported.values());
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Get temporary directory path
|
|
547
|
+
*/
|
|
548
|
+
getTempDir() {
|
|
549
|
+
return this.tempDir;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Cleanup temporary directory
|
|
553
|
+
*/
|
|
554
|
+
async cleanup() {
|
|
555
|
+
if (existsSync(this.tempDir)) await rm(this.tempDir, {
|
|
556
|
+
recursive: true,
|
|
557
|
+
force: true
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Clone Git repository (shallow clone)
|
|
562
|
+
*/
|
|
563
|
+
async cloneRepo(spec) {
|
|
564
|
+
await mkdir(this.tempDir, { recursive: true });
|
|
565
|
+
const repoDir = join(this.tempDir, `${spec.owner}-${spec.repo}`);
|
|
566
|
+
if (existsSync(repoDir)) return repoDir;
|
|
567
|
+
const gitUrl = buildGitUrl(spec);
|
|
568
|
+
await this.execGit([
|
|
569
|
+
"clone",
|
|
570
|
+
"--depth",
|
|
571
|
+
"1",
|
|
572
|
+
"--branch",
|
|
573
|
+
spec.ref,
|
|
574
|
+
"--single-branch",
|
|
575
|
+
gitUrl,
|
|
576
|
+
repoDir
|
|
577
|
+
]);
|
|
578
|
+
return repoDir;
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Extract skills from cloned repository
|
|
582
|
+
*/
|
|
583
|
+
async extractSkills(repoDir, spec) {
|
|
584
|
+
const skillsDir = await this.findSkillsDirectory(repoDir);
|
|
585
|
+
const skillsToImport = spec.skills === "all" ? await this.findAllSkills(skillsDir) : spec.skills;
|
|
586
|
+
const importedSkills = [];
|
|
587
|
+
for (const skillName of skillsToImport) {
|
|
588
|
+
const skillPath = join(skillsDir, skillName);
|
|
589
|
+
const skillMdPath = join(skillPath, "SKILL.md");
|
|
590
|
+
try {
|
|
591
|
+
await stat(skillMdPath);
|
|
592
|
+
this.imported.set(skillName, {
|
|
593
|
+
name: skillName,
|
|
594
|
+
source: spec.rawSpec,
|
|
595
|
+
tempPath: skillPath
|
|
596
|
+
});
|
|
597
|
+
importedSkills.push(skillName);
|
|
598
|
+
} catch {
|
|
599
|
+
console.warn(`Skipping ${skillName}: SKILL.md not found`);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
if (importedSkills.length === 0) throw new Error(`No valid skills found in ${spec.owner}/${spec.repo}`);
|
|
603
|
+
return importedSkills;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Find skills directory in repository
|
|
607
|
+
* Tries: skills/, agent-skills/, . (root)
|
|
608
|
+
*/
|
|
609
|
+
async findSkillsDirectory(repoDir) {
|
|
610
|
+
for (const candidate of [
|
|
611
|
+
"skills",
|
|
612
|
+
"agent-skills",
|
|
613
|
+
"."
|
|
614
|
+
]) {
|
|
615
|
+
const dir = join(repoDir, candidate);
|
|
616
|
+
try {
|
|
617
|
+
if ((await stat(dir)).isDirectory()) {
|
|
618
|
+
if ((await readdir(dir, { withFileTypes: true })).some((entry) => entry.isDirectory() && existsSync(join(dir, entry.name, "SKILL.md")))) return dir;
|
|
619
|
+
}
|
|
620
|
+
} catch {
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
throw new Error(`No skills directory found in ${repoDir}`);
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Find all skills in a directory
|
|
628
|
+
*/
|
|
629
|
+
async findAllSkills(skillsDir) {
|
|
630
|
+
const entries = await readdir(skillsDir, { withFileTypes: true });
|
|
631
|
+
const skills = [];
|
|
632
|
+
for (const entry of entries) if (entry.isDirectory()) {
|
|
633
|
+
if (existsSync(join(skillsDir, entry.name, "SKILL.md"))) skills.push(entry.name);
|
|
634
|
+
}
|
|
635
|
+
return skills;
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Execute git command
|
|
639
|
+
*/
|
|
640
|
+
async execGit(args) {
|
|
641
|
+
return new Promise((resolve, reject) => {
|
|
642
|
+
const git = spawn("git", args, { stdio: [
|
|
643
|
+
"ignore",
|
|
644
|
+
"pipe",
|
|
645
|
+
"pipe"
|
|
646
|
+
] });
|
|
647
|
+
let stdout = "";
|
|
648
|
+
let stderr = "";
|
|
649
|
+
git.stdout?.on("data", (data) => {
|
|
650
|
+
stdout += data.toString();
|
|
651
|
+
});
|
|
652
|
+
git.stderr?.on("data", (data) => {
|
|
653
|
+
stderr += data.toString();
|
|
654
|
+
});
|
|
655
|
+
git.on("close", (code) => {
|
|
656
|
+
if (code === 0) resolve();
|
|
657
|
+
else reject(/* @__PURE__ */ new Error(`Git command failed (exit ${code}): ${stderr || stdout}`));
|
|
658
|
+
});
|
|
659
|
+
git.on("error", (error) => {
|
|
660
|
+
reject(/* @__PURE__ */ new Error(`Failed to spawn git: ${error.message}`));
|
|
661
|
+
});
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
//#endregion
|
|
667
|
+
export { AgentWorker as AgentSession, AgentWorker, ClaudeCodeBackend, CodexBackend, CursorBackend, FEEDBACK_PROMPT, FRONTIER_MODELS, MockAIBackend, SUPPORTED_PROVIDERS, SdkBackend, SkillImporter, SkillsProvider, buildGitUrl, checkBackends, createBackend, createBashTool, createBashTools, createBashToolsFromDirectory, createBashToolsFromFiles, createFeedbackTool, createMockBackend, createModel, createModelAsync, createSkillsTool, getSpecDisplayName, listBackends, parseImportSpec };
|
|
@@ -16,7 +16,7 @@ function createSilentLogger() {
|
|
|
16
16
|
/**
|
|
17
17
|
* Create a logger that writes to the channel.
|
|
18
18
|
*
|
|
19
|
-
* - info/warn/error → channel entry with kind="
|
|
19
|
+
* - info/warn/error → channel entry with kind="system" (always shown to user)
|
|
20
20
|
* - debug → channel entry with kind="debug" (only shown with --debug)
|
|
21
21
|
*
|
|
22
22
|
* The display layer handles formatting and filtering.
|
|
@@ -31,7 +31,7 @@ function createChannelLogger(config) {
|
|
|
31
31
|
};
|
|
32
32
|
const write = (level, message, args) => {
|
|
33
33
|
const content = formatContent(level, message, args);
|
|
34
|
-
const kind = level === "debug" ? "debug" : "
|
|
34
|
+
const kind = level === "debug" ? "debug" : "system";
|
|
35
35
|
provider.appendChannel(from, content, { kind }).catch(() => {});
|
|
36
36
|
};
|
|
37
37
|
return {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { o as MemoryStorage, s as ContextProviderImpl } from "./cli/index.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/workflow/context/memory-provider.ts
|
|
4
|
+
/**
|
|
5
|
+
* In-memory ContextProvider for testing.
|
|
6
|
+
* All domain logic is inherited from ContextProviderImpl;
|
|
7
|
+
* this class adds test helpers for inspection and cleanup.
|
|
8
|
+
*/
|
|
9
|
+
var MemoryContextProvider = class extends ContextProviderImpl {
|
|
10
|
+
memoryStorage;
|
|
11
|
+
constructor(validAgents) {
|
|
12
|
+
const storage = new MemoryStorage();
|
|
13
|
+
super(storage, validAgents);
|
|
14
|
+
this.memoryStorage = storage;
|
|
15
|
+
}
|
|
16
|
+
/** Get underlying MemoryStorage (for testing) */
|
|
17
|
+
getStorage() {
|
|
18
|
+
return this.memoryStorage;
|
|
19
|
+
}
|
|
20
|
+
/** Get all channel messages (for testing, unfiltered) */
|
|
21
|
+
async getMessages() {
|
|
22
|
+
return this.readChannel();
|
|
23
|
+
}
|
|
24
|
+
/** Clear all data (for testing) */
|
|
25
|
+
clear() {
|
|
26
|
+
this.memoryStorage.clear();
|
|
27
|
+
}
|
|
28
|
+
/** Get all resources (for testing) */
|
|
29
|
+
async getResources() {
|
|
30
|
+
const keys = await this.memoryStorage.list("resources/");
|
|
31
|
+
const map = /* @__PURE__ */ new Map();
|
|
32
|
+
for (const key of keys) {
|
|
33
|
+
const content = await this.memoryStorage.read(`resources/${key}`);
|
|
34
|
+
if (content !== null) {
|
|
35
|
+
const id = key.replace(/\.[^.]+$/, "");
|
|
36
|
+
map.set(id, content);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return map;
|
|
40
|
+
}
|
|
41
|
+
/** Get inbox state for an agent (for testing) */
|
|
42
|
+
async getInboxState(agent) {
|
|
43
|
+
const raw = await this.memoryStorage.read("_state/inbox.json");
|
|
44
|
+
if (!raw) return void 0;
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(raw).readCursors?.[agent];
|
|
47
|
+
} catch {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Get all documents (for testing) */
|
|
52
|
+
async getDocuments() {
|
|
53
|
+
const files = await this.memoryStorage.list("documents/");
|
|
54
|
+
const map = /* @__PURE__ */ new Map();
|
|
55
|
+
for (const file of files) {
|
|
56
|
+
const content = await this.memoryStorage.read(`documents/${file}`);
|
|
57
|
+
if (content !== null) map.set(file, content);
|
|
58
|
+
}
|
|
59
|
+
return map;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Create a memory context provider
|
|
64
|
+
*/
|
|
65
|
+
function createMemoryContextProvider(validAgents) {
|
|
66
|
+
return new MemoryContextProvider(validAgents);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//#endregion
|
|
70
|
+
export { createMemoryContextProvider as n, MemoryContextProvider as t };
|