agent-worker 0.18.0 → 0.19.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 +49 -38
- package/dist/cli/index.mjs +208 -4320
- package/dist/client-DAKkzdOn.mjs +171 -0
- package/dist/daemon-CwaHgxs6.mjs +1071 -0
- package/dist/index.d.mts +249 -849
- package/dist/index.mjs +27 -1102
- package/dist/output-B0mwPqjv.mjs +20 -0
- package/dist/rolldown-runtime-wcPFST8Q.mjs +13 -0
- package/dist/target-9yiBRXxa.mjs +105 -0
- package/package.json +25 -37
- package/dist/backends-D7DT0uox.mjs +0 -1484
- package/dist/backends-DUvcm-ce.mjs +0 -3
- package/dist/context-CoRTddGx.mjs +0 -4
- package/dist/create-tool-gcUuI1FD.mjs +0 -32
- package/dist/display-pretty-Kyd40DEF.mjs +0 -190
- package/dist/memory-provider-Z9D8NdwS.mjs +0 -75
- package/dist/runner-BmT0Y8MD.mjs +0 -690
- package/dist/workflow-LOZUlaDo.mjs +0 -744
package/dist/index.mjs
CHANGED
|
@@ -1,1111 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import { createBashTool } from "bash-tool";
|
|
5
|
-
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
6
|
-
import { join, normalize } from "node:path";
|
|
7
|
-
import { mkdir, readFile, readdir, rm, stat } from "node:fs/promises";
|
|
8
|
-
import { parse } from "yaml";
|
|
9
|
-
import { z } from "zod";
|
|
10
|
-
import { tmpdir } from "node:os";
|
|
11
|
-
import { spawn } from "node:child_process";
|
|
12
|
-
|
|
13
|
-
//#region src/agent/worker.ts
|
|
1
|
+
import { a as isDaemonRunning, c as writeDaemonInfo, d as AgentHandle, i as DEFAULT_PORT, l as MemoryStateStore, n as startDaemon, o as readDaemonInfo, r as DaemonEventLog, s as removeDaemonInfo, u as AgentRegistry } from "./daemon-CwaHgxs6.mjs";
|
|
2
|
+
import { AgentWorker, AgentWorker as AgentWorker$1, SkillImporter, createBackend, createSkillTool } from "@moniro/agent-loop";
|
|
3
|
+
//#region src/agent/handle.ts
|
|
14
4
|
/**
|
|
15
|
-
*
|
|
5
|
+
* LocalWorker — In-process execution via AgentWorker.
|
|
16
6
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* enabling improvisational testing where you observe responses
|
|
20
|
-
* and decide next actions.
|
|
21
|
-
*
|
|
22
|
-
* Tools are AI SDK tool() objects passed as Record<name, tool()>.
|
|
23
|
-
* Approval is configured separately via Record<name, check>.
|
|
7
|
+
* Wraps an AgentWorker instance. State lives in the AgentWorker's memory.
|
|
8
|
+
* This is the default for single-machine deployments.
|
|
24
9
|
*/
|
|
25
|
-
var
|
|
26
|
-
|
|
27
|
-
model;
|
|
28
|
-
system;
|
|
29
|
-
createdAt;
|
|
30
|
-
tools;
|
|
31
|
-
approval;
|
|
32
|
-
maxTokens;
|
|
33
|
-
maxSteps;
|
|
34
|
-
messages = [];
|
|
35
|
-
totalUsage = {
|
|
36
|
-
input: 0,
|
|
37
|
-
output: 0,
|
|
38
|
-
total: 0
|
|
39
|
-
};
|
|
40
|
-
pendingApprovals = [];
|
|
41
|
-
backend;
|
|
42
|
-
provider;
|
|
43
|
-
cachedAgent = null;
|
|
44
|
-
toolsChanged = false;
|
|
45
|
-
/**
|
|
46
|
-
* Whether this session supports tool management (SDK backend only)
|
|
47
|
-
*/
|
|
48
|
-
get supportsTools() {
|
|
49
|
-
return this.backend === null;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Convert AgentMessage[] to ModelMessage[] for AI SDK
|
|
53
|
-
*/
|
|
54
|
-
toModelMessages() {
|
|
55
|
-
return this.messages.filter((m) => m.status !== "responding").map((m) => ({
|
|
56
|
-
role: m.role,
|
|
57
|
-
content: m.content
|
|
58
|
-
}));
|
|
59
|
-
}
|
|
10
|
+
var LocalWorker = class {
|
|
11
|
+
engine;
|
|
60
12
|
constructor(config, restore) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
this.
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
this.
|
|
78
|
-
this.provider = config.provider;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Check if a tool needs approval for given arguments
|
|
82
|
-
*/
|
|
83
|
-
checkApproval(name, args) {
|
|
84
|
-
const check = this.approval[name];
|
|
85
|
-
if (!check) return false;
|
|
86
|
-
if (typeof check === "function") return check(args);
|
|
87
|
-
return check;
|
|
88
|
-
}
|
|
89
|
-
/**
|
|
90
|
-
* Build tools with approval wrapping for ToolLoopAgent
|
|
91
|
-
*/
|
|
92
|
-
buildTools(autoApprove) {
|
|
93
|
-
if (Object.keys(this.tools).length === 0) return void 0;
|
|
94
|
-
if (autoApprove || Object.keys(this.approval).length === 0) return this.tools;
|
|
95
|
-
const wrapped = {};
|
|
96
|
-
for (const [name, t] of Object.entries(this.tools)) {
|
|
97
|
-
if (!this.approval[name]) {
|
|
98
|
-
wrapped[name] = t;
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
wrapped[name] = {
|
|
102
|
-
...t,
|
|
103
|
-
execute: async (args, options) => {
|
|
104
|
-
if (this.checkApproval(name, args)) {
|
|
105
|
-
const approval = {
|
|
106
|
-
id: crypto.randomUUID(),
|
|
107
|
-
toolName: name,
|
|
108
|
-
toolCallId: crypto.randomUUID(),
|
|
109
|
-
arguments: args,
|
|
110
|
-
requestedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
111
|
-
status: "pending"
|
|
112
|
-
};
|
|
113
|
-
this.pendingApprovals.push(approval);
|
|
114
|
-
return {
|
|
115
|
-
__approvalRequired: true,
|
|
116
|
-
approvalId: approval.id
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
return t.execute?.(args, options);
|
|
120
|
-
}
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
return wrapped;
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Get or create cached agent, rebuild if tools changed
|
|
127
|
-
*/
|
|
128
|
-
async getAgent(autoApprove) {
|
|
129
|
-
if (!this.cachedAgent || this.toolsChanged || !autoApprove) {
|
|
130
|
-
this.cachedAgent = new ToolLoopAgent({
|
|
131
|
-
model: this.provider ? await createModelWithProvider(this.model, this.provider) : await createModelAsync(this.model),
|
|
132
|
-
instructions: this.system,
|
|
133
|
-
tools: this.buildTools(autoApprove),
|
|
134
|
-
maxOutputTokens: this.maxTokens,
|
|
135
|
-
stopWhen: stepCountIs(this.maxSteps)
|
|
136
|
-
});
|
|
137
|
-
if (autoApprove) this.toolsChanged = false;
|
|
138
|
-
}
|
|
139
|
-
return this.cachedAgent;
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Send a message via CLI backend (non-SDK path)
|
|
143
|
-
*/
|
|
144
|
-
async sendViaBackend(content) {
|
|
145
|
-
const startTime = performance.now();
|
|
146
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
147
|
-
this.messages.push({
|
|
148
|
-
role: "user",
|
|
149
|
-
content,
|
|
150
|
-
status: "complete",
|
|
151
|
-
timestamp
|
|
152
|
-
});
|
|
153
|
-
const result = await this.backend.send(content, { system: this.system });
|
|
154
|
-
const latency = Math.round(performance.now() - startTime);
|
|
155
|
-
this.messages.push({
|
|
156
|
-
role: "assistant",
|
|
157
|
-
content: result.content,
|
|
158
|
-
status: "complete",
|
|
159
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
160
|
-
});
|
|
161
|
-
const usage = {
|
|
162
|
-
input: result.usage?.input ?? 0,
|
|
163
|
-
output: result.usage?.output ?? 0,
|
|
164
|
-
total: result.usage?.total ?? 0
|
|
165
|
-
};
|
|
166
|
-
this.totalUsage.input += usage.input;
|
|
167
|
-
this.totalUsage.output += usage.output;
|
|
168
|
-
this.totalUsage.total += usage.total;
|
|
169
|
-
const toolCalls = (result.toolCalls ?? []).map((tc) => ({
|
|
170
|
-
name: tc.name,
|
|
171
|
-
arguments: tc.arguments,
|
|
172
|
-
result: tc.result,
|
|
173
|
-
timing: 0
|
|
174
|
-
}));
|
|
175
|
-
return {
|
|
176
|
-
content: result.content,
|
|
177
|
-
toolCalls,
|
|
178
|
-
pendingApprovals: [],
|
|
179
|
-
usage,
|
|
180
|
-
latency
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Send a message and get the agent's response
|
|
185
|
-
*/
|
|
186
|
-
async send(content, options = {}) {
|
|
187
|
-
if (this.backend) return this.sendViaBackend(content);
|
|
188
|
-
const { autoApprove = true, onStepFinish } = options;
|
|
189
|
-
const startTime = performance.now();
|
|
190
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
191
|
-
this.messages.push({
|
|
192
|
-
role: "user",
|
|
193
|
-
content,
|
|
194
|
-
status: "complete",
|
|
195
|
-
timestamp
|
|
196
|
-
});
|
|
197
|
-
const agent = await this.getAgent(autoApprove);
|
|
198
|
-
const allToolCalls = [];
|
|
199
|
-
let stepNumber = 0;
|
|
200
|
-
const result = await agent.generate({
|
|
201
|
-
messages: this.toModelMessages(),
|
|
202
|
-
onStepFinish: async ({ usage, toolCalls, toolResults }) => {
|
|
203
|
-
stepNumber++;
|
|
204
|
-
const stepToolCalls = [];
|
|
205
|
-
if (toolCalls) for (const tc of toolCalls) {
|
|
206
|
-
const toolResult = toolResults?.find((tr) => tr.toolCallId === tc.toolCallId);
|
|
207
|
-
const toolCall = {
|
|
208
|
-
name: tc.toolName,
|
|
209
|
-
arguments: tc.input,
|
|
210
|
-
result: toolResult?.output ?? null,
|
|
211
|
-
timing: 0
|
|
212
|
-
};
|
|
213
|
-
stepToolCalls.push(toolCall);
|
|
214
|
-
allToolCalls.push(toolCall);
|
|
215
|
-
}
|
|
216
|
-
if (onStepFinish) {
|
|
217
|
-
const stepUsage = {
|
|
218
|
-
input: usage?.inputTokens ?? 0,
|
|
219
|
-
output: usage?.outputTokens ?? 0,
|
|
220
|
-
total: (usage?.inputTokens ?? 0) + (usage?.outputTokens ?? 0)
|
|
221
|
-
};
|
|
222
|
-
await onStepFinish({
|
|
223
|
-
stepNumber,
|
|
224
|
-
toolCalls: stepToolCalls,
|
|
225
|
-
usage: stepUsage
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
const latency = Math.round(performance.now() - startTime);
|
|
231
|
-
this.messages.push({
|
|
232
|
-
role: "assistant",
|
|
233
|
-
content: result.text,
|
|
234
|
-
status: "complete",
|
|
235
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
236
|
-
});
|
|
237
|
-
const usage = {
|
|
238
|
-
input: result.usage?.inputTokens ?? 0,
|
|
239
|
-
output: result.usage?.outputTokens ?? 0,
|
|
240
|
-
total: (result.usage?.inputTokens ?? 0) + (result.usage?.outputTokens ?? 0)
|
|
241
|
-
};
|
|
242
|
-
this.totalUsage.input += usage.input;
|
|
243
|
-
this.totalUsage.output += usage.output;
|
|
244
|
-
this.totalUsage.total += usage.total;
|
|
245
|
-
if (this.maxSteps > 0 && stepNumber >= this.maxSteps && allToolCalls.length > 0) console.warn(`⚠️ Agent reached maxSteps limit (${this.maxSteps}) but wanted to continue. Consider increasing maxSteps or removing the limit.`);
|
|
246
|
-
const currentPending = this.pendingApprovals.filter((p) => p.status === "pending");
|
|
247
|
-
return {
|
|
248
|
-
content: result.text,
|
|
249
|
-
toolCalls: allToolCalls,
|
|
250
|
-
pendingApprovals: currentPending,
|
|
251
|
-
usage,
|
|
252
|
-
latency
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* Send a message and stream the response
|
|
257
|
-
*/
|
|
258
|
-
async *sendStream(content, options = {}) {
|
|
259
|
-
if (this.backend) {
|
|
260
|
-
const response = await this.sendViaBackend(content);
|
|
261
|
-
yield response.content;
|
|
262
|
-
return response;
|
|
263
|
-
}
|
|
264
|
-
const { autoApprove = true, onStepFinish } = options;
|
|
265
|
-
const startTime = performance.now();
|
|
266
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
267
|
-
this.messages.push({
|
|
268
|
-
role: "user",
|
|
269
|
-
content,
|
|
270
|
-
status: "complete",
|
|
271
|
-
timestamp
|
|
272
|
-
});
|
|
273
|
-
const assistantMsg = {
|
|
274
|
-
role: "assistant",
|
|
275
|
-
content: "",
|
|
276
|
-
status: "responding",
|
|
277
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
278
|
-
};
|
|
279
|
-
this.messages.push(assistantMsg);
|
|
280
|
-
const agent = await this.getAgent(autoApprove);
|
|
281
|
-
const allToolCalls = [];
|
|
282
|
-
let stepNumber = 0;
|
|
283
|
-
const result = await agent.stream({
|
|
284
|
-
messages: this.toModelMessages(),
|
|
285
|
-
onStepFinish: async ({ usage, toolCalls, toolResults }) => {
|
|
286
|
-
stepNumber++;
|
|
287
|
-
const stepToolCalls = [];
|
|
288
|
-
if (toolCalls) for (const tc of toolCalls) {
|
|
289
|
-
const toolResult = toolResults?.find((tr) => tr.toolCallId === tc.toolCallId);
|
|
290
|
-
const toolCall = {
|
|
291
|
-
name: tc.toolName,
|
|
292
|
-
arguments: tc.input,
|
|
293
|
-
result: toolResult?.output ?? null,
|
|
294
|
-
timing: 0
|
|
295
|
-
};
|
|
296
|
-
stepToolCalls.push(toolCall);
|
|
297
|
-
allToolCalls.push(toolCall);
|
|
298
|
-
}
|
|
299
|
-
if (onStepFinish) {
|
|
300
|
-
const stepUsage = {
|
|
301
|
-
input: usage?.inputTokens ?? 0,
|
|
302
|
-
output: usage?.outputTokens ?? 0,
|
|
303
|
-
total: (usage?.inputTokens ?? 0) + (usage?.outputTokens ?? 0)
|
|
304
|
-
};
|
|
305
|
-
await onStepFinish({
|
|
306
|
-
stepNumber,
|
|
307
|
-
toolCalls: stepToolCalls,
|
|
308
|
-
usage: stepUsage
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
for await (const chunk of result.textStream) {
|
|
314
|
-
assistantMsg.content += chunk;
|
|
315
|
-
yield chunk;
|
|
316
|
-
}
|
|
317
|
-
const latency = Math.round(performance.now() - startTime);
|
|
318
|
-
const text = await result.text;
|
|
319
|
-
assistantMsg.content = text;
|
|
320
|
-
assistantMsg.status = "complete";
|
|
321
|
-
const finalUsage = await result.usage;
|
|
322
|
-
const usage = {
|
|
323
|
-
input: finalUsage?.inputTokens ?? 0,
|
|
324
|
-
output: finalUsage?.outputTokens ?? 0,
|
|
325
|
-
total: (finalUsage?.inputTokens ?? 0) + (finalUsage?.outputTokens ?? 0)
|
|
326
|
-
};
|
|
327
|
-
this.totalUsage.input += usage.input;
|
|
328
|
-
this.totalUsage.output += usage.output;
|
|
329
|
-
this.totalUsage.total += usage.total;
|
|
330
|
-
return {
|
|
331
|
-
content: text,
|
|
332
|
-
toolCalls: allToolCalls,
|
|
333
|
-
pendingApprovals: this.pendingApprovals.filter((p) => p.status === "pending"),
|
|
334
|
-
usage,
|
|
335
|
-
latency
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Add an AI SDK tool
|
|
340
|
-
* Only supported for SDK backends (ToolLoopAgent)
|
|
341
|
-
*/
|
|
342
|
-
addTool(name, t) {
|
|
343
|
-
if (this.backend) throw new Error("Tool management not supported for CLI backends");
|
|
344
|
-
this.tools[name] = t;
|
|
345
|
-
this.toolsChanged = true;
|
|
346
|
-
this.cachedAgent = null;
|
|
347
|
-
}
|
|
348
|
-
/**
|
|
349
|
-
* Set approval requirement for a tool
|
|
350
|
-
*/
|
|
351
|
-
setApproval(name, check) {
|
|
352
|
-
this.approval[name] = check;
|
|
353
|
-
}
|
|
354
|
-
/**
|
|
355
|
-
* Replace a tool's execute function (for testing)
|
|
356
|
-
*/
|
|
357
|
-
mockTool(name, mockFn) {
|
|
358
|
-
if (this.backend) throw new Error("Tool management not supported for CLI backends");
|
|
359
|
-
const t = this.tools[name];
|
|
360
|
-
if (!t) throw new Error(`Tool not found: ${name}`);
|
|
361
|
-
this.tools[name] = {
|
|
362
|
-
...t,
|
|
363
|
-
execute: mockFn
|
|
364
|
-
};
|
|
365
|
-
this.toolsChanged = true;
|
|
366
|
-
this.cachedAgent = null;
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Set a static mock response for an existing tool
|
|
370
|
-
*/
|
|
371
|
-
setMockResponse(name, response) {
|
|
372
|
-
if (this.backend) throw new Error("Tool management not supported for CLI backends");
|
|
373
|
-
const t = this.tools[name];
|
|
374
|
-
if (!t) throw new Error(`Tool not found: ${name}`);
|
|
375
|
-
this.tools[name] = {
|
|
376
|
-
...t,
|
|
377
|
-
execute: () => response
|
|
378
|
-
};
|
|
379
|
-
this.toolsChanged = true;
|
|
380
|
-
this.cachedAgent = null;
|
|
381
|
-
}
|
|
382
|
-
/**
|
|
383
|
-
* Get tool info (names, descriptions, approval status)
|
|
384
|
-
*/
|
|
385
|
-
getTools() {
|
|
386
|
-
return Object.entries(this.tools).map(([name, t]) => {
|
|
387
|
-
return {
|
|
388
|
-
name,
|
|
389
|
-
description: t?.description,
|
|
390
|
-
needsApproval: !!this.approval[name]
|
|
391
|
-
};
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
history() {
|
|
395
|
-
return [...this.messages];
|
|
396
|
-
}
|
|
397
|
-
stats() {
|
|
398
|
-
return {
|
|
399
|
-
messageCount: this.messages.length,
|
|
400
|
-
usage: { ...this.totalUsage }
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
export() {
|
|
404
|
-
return {
|
|
405
|
-
sessionId: this.id,
|
|
406
|
-
model: this.model,
|
|
407
|
-
system: this.system,
|
|
408
|
-
messages: [...this.messages],
|
|
409
|
-
totalUsage: { ...this.totalUsage },
|
|
410
|
-
createdAt: this.createdAt
|
|
411
|
-
};
|
|
13
|
+
const backend = config.backend !== "default" ? createBackend({
|
|
14
|
+
type: config.backend,
|
|
15
|
+
model: config.model
|
|
16
|
+
}) : void 0;
|
|
17
|
+
this.engine = new AgentWorker$1({
|
|
18
|
+
model: config.model,
|
|
19
|
+
system: config.system,
|
|
20
|
+
tools: {},
|
|
21
|
+
backend,
|
|
22
|
+
provider: config.provider
|
|
23
|
+
}, restore);
|
|
24
|
+
}
|
|
25
|
+
send(input, options) {
|
|
26
|
+
return this.engine.send(input, options);
|
|
27
|
+
}
|
|
28
|
+
sendStream(input, options) {
|
|
29
|
+
return this.engine.sendStream(input, options);
|
|
412
30
|
}
|
|
413
31
|
getState() {
|
|
414
|
-
return
|
|
415
|
-
id: this.id,
|
|
416
|
-
createdAt: this.createdAt,
|
|
417
|
-
messages: [...this.messages],
|
|
418
|
-
totalUsage: { ...this.totalUsage },
|
|
419
|
-
pendingApprovals: [...this.pendingApprovals]
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
getPendingApprovals() {
|
|
423
|
-
return this.pendingApprovals.filter((p) => p.status === "pending");
|
|
424
|
-
}
|
|
425
|
-
async approve(approvalId) {
|
|
426
|
-
const approval = this.pendingApprovals.find((p) => p.id === approvalId);
|
|
427
|
-
if (!approval) throw new Error(`Approval not found: ${approvalId}`);
|
|
428
|
-
if (approval.status !== "pending") throw new Error(`Approval already ${approval.status}: ${approvalId}`);
|
|
429
|
-
const t = this.tools[approval.toolName];
|
|
430
|
-
if (!t) throw new Error(`Tool not found: ${approval.toolName}`);
|
|
431
|
-
let result;
|
|
432
|
-
const tool = t;
|
|
433
|
-
if (typeof tool.execute === "function") result = await tool.execute(approval.arguments);
|
|
434
|
-
else result = { error: "No implementation provided" };
|
|
435
|
-
approval.status = "approved";
|
|
436
|
-
return result;
|
|
437
|
-
}
|
|
438
|
-
deny(approvalId, reason) {
|
|
439
|
-
const approval = this.pendingApprovals.find((p) => p.id === approvalId);
|
|
440
|
-
if (!approval) throw new Error(`Approval not found: ${approvalId}`);
|
|
441
|
-
if (approval.status !== "pending") throw new Error(`Approval already ${approval.status}: ${approvalId}`);
|
|
442
|
-
approval.status = "denied";
|
|
443
|
-
approval.denyReason = reason;
|
|
444
|
-
}
|
|
445
|
-
clear() {
|
|
446
|
-
this.messages = [];
|
|
447
|
-
this.totalUsage = {
|
|
448
|
-
input: 0,
|
|
449
|
-
output: 0,
|
|
450
|
-
total: 0
|
|
451
|
-
};
|
|
452
|
-
this.pendingApprovals = [];
|
|
453
|
-
}
|
|
454
|
-
};
|
|
455
|
-
|
|
456
|
-
//#endregion
|
|
457
|
-
//#region src/agent/tools/bash.ts
|
|
458
|
-
/**
|
|
459
|
-
* Integration with Vercel's bash-tool for file system operations
|
|
460
|
-
*
|
|
461
|
-
* Provides bash, readFile, writeFile tools for AI agents in a sandboxed environment
|
|
462
|
-
*/
|
|
463
|
-
/**
|
|
464
|
-
* Create bash tools as AI SDK tool() objects for use with AgentWorker
|
|
465
|
-
*
|
|
466
|
-
* @example
|
|
467
|
-
* ```typescript
|
|
468
|
-
* const { tools } = await createBashTools({
|
|
469
|
-
* files: { 'src/index.ts': 'console.log("hello")' }
|
|
470
|
-
* })
|
|
471
|
-
*
|
|
472
|
-
* const session = new AgentWorker({
|
|
473
|
-
* model: 'anthropic/claude-sonnet-4-5',
|
|
474
|
-
* system: 'You are a coding assistant.',
|
|
475
|
-
* tools
|
|
476
|
-
* })
|
|
477
|
-
* ```
|
|
478
|
-
*/
|
|
479
|
-
async function createBashTools(options = {}) {
|
|
480
|
-
const { includeReadFile = true, includeWriteFile = true, ...bashOptions } = options;
|
|
481
|
-
const toolkit = await createBashTool(bashOptions);
|
|
482
|
-
const tools = {};
|
|
483
|
-
tools.bash = createTool({
|
|
484
|
-
description: "Execute bash commands in a sandboxed environment. Returns stdout, stderr, and exit code.",
|
|
485
|
-
schema: {
|
|
486
|
-
type: "object",
|
|
487
|
-
properties: { command: {
|
|
488
|
-
type: "string",
|
|
489
|
-
description: "The bash command to execute"
|
|
490
|
-
} },
|
|
491
|
-
required: ["command"]
|
|
492
|
-
},
|
|
493
|
-
execute: async (args) => {
|
|
494
|
-
const bashTool = toolkit.tools.bash;
|
|
495
|
-
if (!bashTool?.execute) throw new Error("Bash tool not available");
|
|
496
|
-
return bashTool.execute(args, {});
|
|
497
|
-
}
|
|
498
|
-
});
|
|
499
|
-
if (includeReadFile) tools.readFile = createTool({
|
|
500
|
-
description: "Read the contents of a file from the sandbox filesystem.",
|
|
501
|
-
schema: {
|
|
502
|
-
type: "object",
|
|
503
|
-
properties: { path: {
|
|
504
|
-
type: "string",
|
|
505
|
-
description: "The path to the file to read"
|
|
506
|
-
} },
|
|
507
|
-
required: ["path"]
|
|
508
|
-
},
|
|
509
|
-
execute: async (args) => {
|
|
510
|
-
const readFileTool = toolkit.tools.readFile;
|
|
511
|
-
if (!readFileTool?.execute) throw new Error("ReadFile tool not available");
|
|
512
|
-
return readFileTool.execute(args, {});
|
|
513
|
-
}
|
|
514
|
-
});
|
|
515
|
-
if (includeWriteFile) tools.writeFile = createTool({
|
|
516
|
-
description: "Write content to a file in the sandbox filesystem. Creates parent directories if needed.",
|
|
517
|
-
schema: {
|
|
518
|
-
type: "object",
|
|
519
|
-
properties: {
|
|
520
|
-
path: {
|
|
521
|
-
type: "string",
|
|
522
|
-
description: "The path to the file to write"
|
|
523
|
-
},
|
|
524
|
-
content: {
|
|
525
|
-
type: "string",
|
|
526
|
-
description: "The content to write to the file"
|
|
527
|
-
}
|
|
528
|
-
},
|
|
529
|
-
required: ["path", "content"]
|
|
530
|
-
},
|
|
531
|
-
execute: async (args) => {
|
|
532
|
-
const writeFileTool = toolkit.tools.writeFile;
|
|
533
|
-
if (!writeFileTool?.execute) throw new Error("WriteFile tool not available");
|
|
534
|
-
return writeFileTool.execute(args, {});
|
|
535
|
-
}
|
|
536
|
-
});
|
|
537
|
-
return {
|
|
538
|
-
tools,
|
|
539
|
-
toolkit
|
|
540
|
-
};
|
|
541
|
-
}
|
|
542
|
-
/**
|
|
543
|
-
* Quick helper to create bash tools with a directory
|
|
544
|
-
*/
|
|
545
|
-
async function createBashToolsFromDirectory(source, options = {}) {
|
|
546
|
-
return createBashTools({
|
|
547
|
-
...options,
|
|
548
|
-
uploadDirectory: { source }
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
/**
|
|
552
|
-
* Quick helper to create bash tools with inline files
|
|
553
|
-
*/
|
|
554
|
-
async function createBashToolsFromFiles(files, options = {}) {
|
|
555
|
-
return createBashTools({
|
|
556
|
-
...options,
|
|
557
|
-
files
|
|
558
|
-
});
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
//#endregion
|
|
562
|
-
//#region src/agent/tools/feedback.ts
|
|
563
|
-
/**
|
|
564
|
-
* Feedback tool — lets agents surface workflow improvement needs
|
|
565
|
-
*
|
|
566
|
-
* When enabled, agents can report what's missing or inconvenient during work:
|
|
567
|
-
* a tool they wished they had, a step that felt unnecessarily slow, or a
|
|
568
|
-
* capability gap. The purpose is workflow improvement, not bug reporting.
|
|
569
|
-
*
|
|
570
|
-
* @example
|
|
571
|
-
* ```typescript
|
|
572
|
-
* const { tool, getFeedback } = createFeedbackTool();
|
|
573
|
-
*
|
|
574
|
-
* const session = new AgentWorker({
|
|
575
|
-
* model: 'anthropic/claude-sonnet-4-5',
|
|
576
|
-
* system: FEEDBACK_PROMPT + '\nYou are a coding assistant.',
|
|
577
|
-
* tools: { feedback: tool, bash: bashTool },
|
|
578
|
-
* });
|
|
579
|
-
*
|
|
580
|
-
* await session.send('...');
|
|
581
|
-
* console.log(getFeedback()); // review what the agent reported
|
|
582
|
-
* ```
|
|
583
|
-
*/
|
|
584
|
-
/**
|
|
585
|
-
* Append this to the system prompt when the feedback tool is enabled.
|
|
586
|
-
* Tells the agent the tool exists and when to use it.
|
|
587
|
-
*/
|
|
588
|
-
const FEEDBACK_PROMPT = `
|
|
589
|
-
## Feedback
|
|
590
|
-
|
|
591
|
-
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.
|
|
592
|
-
|
|
593
|
-
The purpose is to improve the workflow for future runs. Don't force feedback; only call it when you genuinely hit a pain point.
|
|
594
|
-
`.trim();
|
|
595
|
-
function createFeedbackTool(options = {}) {
|
|
596
|
-
const { onFeedback, maxEntries = 50 } = options;
|
|
597
|
-
const entries = [];
|
|
598
|
-
return {
|
|
599
|
-
tool: createTool({
|
|
600
|
-
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.",
|
|
601
|
-
schema: {
|
|
602
|
-
type: "object",
|
|
603
|
-
properties: {
|
|
604
|
-
target: {
|
|
605
|
-
type: "string",
|
|
606
|
-
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)."
|
|
607
|
-
},
|
|
608
|
-
type: {
|
|
609
|
-
type: "string",
|
|
610
|
-
enum: [
|
|
611
|
-
"missing",
|
|
612
|
-
"friction",
|
|
613
|
-
"suggestion"
|
|
614
|
-
],
|
|
615
|
-
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."
|
|
616
|
-
},
|
|
617
|
-
description: {
|
|
618
|
-
type: "string",
|
|
619
|
-
description: "What you needed or what could be improved. Be specific."
|
|
620
|
-
},
|
|
621
|
-
context: {
|
|
622
|
-
type: "string",
|
|
623
|
-
description: "Optional: what you were trying to do when you hit this."
|
|
624
|
-
}
|
|
625
|
-
},
|
|
626
|
-
required: [
|
|
627
|
-
"target",
|
|
628
|
-
"type",
|
|
629
|
-
"description"
|
|
630
|
-
]
|
|
631
|
-
},
|
|
632
|
-
execute: async (args) => {
|
|
633
|
-
const validTypes = [
|
|
634
|
-
"missing",
|
|
635
|
-
"friction",
|
|
636
|
-
"suggestion"
|
|
637
|
-
];
|
|
638
|
-
const rawType = args.type;
|
|
639
|
-
const type = validTypes.includes(rawType) ? rawType : "suggestion";
|
|
640
|
-
const entry = {
|
|
641
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
642
|
-
target: args.target,
|
|
643
|
-
type,
|
|
644
|
-
description: args.description,
|
|
645
|
-
...args.context ? { context: args.context } : {}
|
|
646
|
-
};
|
|
647
|
-
if (entries.length >= maxEntries) entries.shift();
|
|
648
|
-
entries.push(entry);
|
|
649
|
-
onFeedback?.(entry);
|
|
650
|
-
return { recorded: true };
|
|
651
|
-
}
|
|
652
|
-
}),
|
|
653
|
-
getFeedback: () => [...entries],
|
|
654
|
-
clearFeedback: () => {
|
|
655
|
-
entries.length = 0;
|
|
656
|
-
}
|
|
657
|
-
};
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
//#endregion
|
|
661
|
-
//#region src/agent/skills/provider.ts
|
|
662
|
-
const frontmatterSchema = z.object({
|
|
663
|
-
name: z.string().regex(/^[a-z0-9]+(-[a-z0-9]+)*$/).max(64),
|
|
664
|
-
description: z.string().min(1).max(1024),
|
|
665
|
-
license: z.string().optional(),
|
|
666
|
-
compatibility: z.string().max(500).optional(),
|
|
667
|
-
metadata: z.record(z.string(), z.string()).optional(),
|
|
668
|
-
"allowed-tools": z.string().optional()
|
|
669
|
-
});
|
|
670
|
-
var SkillsProvider = class {
|
|
671
|
-
skills = /* @__PURE__ */ new Map();
|
|
672
|
-
/**
|
|
673
|
-
* Add a single skill directory
|
|
674
|
-
*/
|
|
675
|
-
async addSkill(skillPath) {
|
|
676
|
-
const skillMdPath = join(skillPath, "SKILL.md");
|
|
677
|
-
try {
|
|
678
|
-
await stat(skillMdPath);
|
|
679
|
-
} catch {
|
|
680
|
-
throw new Error(`SKILL.md not found in ${skillPath}`);
|
|
681
|
-
}
|
|
682
|
-
const frontmatter = await this.parseFrontmatter(skillMdPath);
|
|
683
|
-
this.skills.set(frontmatter.name, {
|
|
684
|
-
name: frontmatter.name,
|
|
685
|
-
description: frontmatter.description,
|
|
686
|
-
path: skillPath
|
|
687
|
-
});
|
|
688
|
-
}
|
|
689
|
-
/**
|
|
690
|
-
* Scan a directory and add all valid skills found
|
|
691
|
-
*/
|
|
692
|
-
async scanDirectory(dir) {
|
|
693
|
-
const resolved = this.resolvePath(dir);
|
|
694
|
-
try {
|
|
695
|
-
const entries = await readdir(resolved, { withFileTypes: true });
|
|
696
|
-
for (const entry of entries) if (entry.isDirectory()) {
|
|
697
|
-
const skillPath = join(resolved, entry.name);
|
|
698
|
-
try {
|
|
699
|
-
await this.addSkill(skillPath);
|
|
700
|
-
} catch {}
|
|
701
|
-
}
|
|
702
|
-
} catch {}
|
|
703
|
-
}
|
|
704
|
-
/**
|
|
705
|
-
* List all available skills (metadata only)
|
|
706
|
-
*/
|
|
707
|
-
list() {
|
|
708
|
-
return Array.from(this.skills.values()).map(({ name, description }) => ({
|
|
709
|
-
name,
|
|
710
|
-
description
|
|
711
|
-
}));
|
|
712
|
-
}
|
|
713
|
-
/**
|
|
714
|
-
* View the full SKILL.md content
|
|
715
|
-
*/
|
|
716
|
-
async view(skillName) {
|
|
717
|
-
const skill = this.skills.get(skillName);
|
|
718
|
-
if (!skill) throw new Error(`Skill "${skillName}" not found`);
|
|
719
|
-
return await readFile(join(skill.path, "SKILL.md"), "utf-8");
|
|
720
|
-
}
|
|
721
|
-
/**
|
|
722
|
-
* Read a file within a skill directory (relative path)
|
|
723
|
-
*/
|
|
724
|
-
async readFile(skillName, relativePath) {
|
|
725
|
-
const skill = this.skills.get(skillName);
|
|
726
|
-
if (!skill) throw new Error(`Skill "${skillName}" not found`);
|
|
727
|
-
const normalized = normalize(relativePath);
|
|
728
|
-
if (normalized.startsWith("..")) throw new Error("Path traversal not allowed");
|
|
729
|
-
return await readFile(join(skill.path, normalized), "utf-8");
|
|
730
|
-
}
|
|
731
|
-
/**
|
|
732
|
-
* Parse YAML frontmatter from SKILL.md
|
|
733
|
-
*/
|
|
734
|
-
async parseFrontmatter(skillMdPath) {
|
|
735
|
-
const match = (await readFile(skillMdPath, "utf-8")).match(/^---\n([\s\S]+?)\n---/);
|
|
736
|
-
if (!match || !match[1]) throw new Error(`Invalid SKILL.md: missing frontmatter in ${skillMdPath}`);
|
|
737
|
-
try {
|
|
738
|
-
return frontmatterSchema.parse(parse(match[1]));
|
|
739
|
-
} catch (error) {
|
|
740
|
-
throw new Error(`Invalid frontmatter in ${skillMdPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
/**
|
|
744
|
-
* Resolve tilde and relative paths
|
|
745
|
-
*/
|
|
746
|
-
resolvePath(path) {
|
|
747
|
-
if (path.startsWith("~/")) {
|
|
748
|
-
const home = process.env.HOME || process.env.USERPROFILE;
|
|
749
|
-
if (!home) throw new Error("Cannot resolve ~/ without HOME environment variable");
|
|
750
|
-
return join(home, path.slice(2));
|
|
751
|
-
}
|
|
752
|
-
return path;
|
|
753
|
-
}
|
|
754
|
-
/**
|
|
755
|
-
* Synchronous version: Add a single skill directory
|
|
756
|
-
*/
|
|
757
|
-
addSkillSync(skillPath) {
|
|
758
|
-
const skillMdPath = join(skillPath, "SKILL.md");
|
|
759
|
-
try {
|
|
760
|
-
statSync(skillMdPath);
|
|
761
|
-
} catch {
|
|
762
|
-
throw new Error(`SKILL.md not found in ${skillPath}`);
|
|
763
|
-
}
|
|
764
|
-
const frontmatter = this.parseFrontmatterSync(skillMdPath);
|
|
765
|
-
this.skills.set(frontmatter.name, {
|
|
766
|
-
name: frontmatter.name,
|
|
767
|
-
description: frontmatter.description,
|
|
768
|
-
path: skillPath
|
|
769
|
-
});
|
|
770
|
-
}
|
|
771
|
-
/**
|
|
772
|
-
* Synchronous version: Scan a directory and add all valid skills found
|
|
773
|
-
*/
|
|
774
|
-
scanDirectorySync(dir) {
|
|
775
|
-
const resolved = this.resolvePath(dir);
|
|
776
|
-
try {
|
|
777
|
-
const entries = readdirSync(resolved, { withFileTypes: true });
|
|
778
|
-
for (const entry of entries) if (entry.isDirectory()) {
|
|
779
|
-
const skillPath = join(resolved, entry.name);
|
|
780
|
-
try {
|
|
781
|
-
this.addSkillSync(skillPath);
|
|
782
|
-
} catch {}
|
|
783
|
-
}
|
|
784
|
-
} catch {}
|
|
785
|
-
}
|
|
786
|
-
/**
|
|
787
|
-
* Synchronous version: Parse YAML frontmatter from SKILL.md
|
|
788
|
-
*/
|
|
789
|
-
parseFrontmatterSync(skillMdPath) {
|
|
790
|
-
const match = readFileSync(skillMdPath, "utf-8").match(/^---\n([\s\S]+?)\n---/);
|
|
791
|
-
if (!match || !match[1]) throw new Error(`Invalid SKILL.md: missing frontmatter in ${skillMdPath}`);
|
|
792
|
-
try {
|
|
793
|
-
return frontmatterSchema.parse(parse(match[1]));
|
|
794
|
-
} catch (error) {
|
|
795
|
-
throw new Error(`Invalid frontmatter in ${skillMdPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
/**
|
|
799
|
-
* Add skills from a SkillImporter (async)
|
|
800
|
-
* Used for temporary imported skills during session lifecycle
|
|
801
|
-
*/
|
|
802
|
-
async addImportedSkills(importer) {
|
|
803
|
-
const skillPaths = importer.getAllImportedSkillPaths();
|
|
804
|
-
for (const skillPath of skillPaths) await this.addSkill(skillPath);
|
|
805
|
-
}
|
|
806
|
-
/**
|
|
807
|
-
* Add skills from a SkillImporter (sync)
|
|
808
|
-
*/
|
|
809
|
-
addImportedSkillsSync(importer) {
|
|
810
|
-
const skillPaths = importer.getAllImportedSkillPaths();
|
|
811
|
-
for (const skillPath of skillPaths) this.addSkillSync(skillPath);
|
|
812
|
-
}
|
|
813
|
-
};
|
|
814
|
-
|
|
815
|
-
//#endregion
|
|
816
|
-
//#region src/agent/tools/skills.ts
|
|
817
|
-
/**
|
|
818
|
-
* Create a Skills tool as an AI SDK tool() object
|
|
819
|
-
*/
|
|
820
|
-
function createSkillsTool(provider) {
|
|
821
|
-
return createTool({
|
|
822
|
-
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/).",
|
|
823
|
-
schema: {
|
|
824
|
-
type: "object",
|
|
825
|
-
properties: {
|
|
826
|
-
operation: {
|
|
827
|
-
type: "string",
|
|
828
|
-
enum: [
|
|
829
|
-
"list",
|
|
830
|
-
"view",
|
|
831
|
-
"readFile"
|
|
832
|
-
],
|
|
833
|
-
description: "Operation to perform"
|
|
834
|
-
},
|
|
835
|
-
skillName: {
|
|
836
|
-
type: "string",
|
|
837
|
-
description: "Skill name (required for view and readFile operations)"
|
|
838
|
-
},
|
|
839
|
-
filePath: {
|
|
840
|
-
type: "string",
|
|
841
|
-
description: "Relative file path within the skill directory (required for readFile operation, e.g., \"references/search-strategies.md\")"
|
|
842
|
-
}
|
|
843
|
-
},
|
|
844
|
-
required: ["operation"]
|
|
845
|
-
},
|
|
846
|
-
execute: async (args) => {
|
|
847
|
-
const operation = args.operation;
|
|
848
|
-
const skillName = args.skillName;
|
|
849
|
-
const filePath = args.filePath;
|
|
850
|
-
switch (operation) {
|
|
851
|
-
case "list": {
|
|
852
|
-
const skills = provider.list();
|
|
853
|
-
if (skills.length === 0) return { message: "No skills available" };
|
|
854
|
-
return { skills: skills.map((s) => ({
|
|
855
|
-
name: s.name,
|
|
856
|
-
description: s.description
|
|
857
|
-
})) };
|
|
858
|
-
}
|
|
859
|
-
case "view":
|
|
860
|
-
if (!skillName) throw new Error("skillName is required for view operation");
|
|
861
|
-
return { content: await provider.view(skillName) };
|
|
862
|
-
case "readFile":
|
|
863
|
-
if (!skillName || !filePath) throw new Error("skillName and filePath are required for readFile operation");
|
|
864
|
-
return { content: await provider.readFile(skillName, filePath) };
|
|
865
|
-
default: throw new Error(`Unknown operation: ${operation}`);
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
});
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
//#endregion
|
|
872
|
-
//#region src/agent/skills/import-spec.ts
|
|
873
|
-
const providerUrls = {
|
|
874
|
-
github: "https://github.com",
|
|
875
|
-
gitlab: "https://gitlab.com",
|
|
876
|
-
gitee: "https://gitee.com"
|
|
877
|
-
};
|
|
878
|
-
/**
|
|
879
|
-
* Parse import spec: [provider:]owner/repo[@ref]:{skill1,skill2,...}
|
|
880
|
-
*
|
|
881
|
-
* Examples:
|
|
882
|
-
* vercel-labs/agent-skills:react-best-practices
|
|
883
|
-
* vercel-labs/agent-skills:{react,web}
|
|
884
|
-
* vercel-labs/agent-skills@v1.0.0:react
|
|
885
|
-
* github:vercel-labs/agent-skills@main:react
|
|
886
|
-
* vercel-labs/agent-skills (imports all)
|
|
887
|
-
*/
|
|
888
|
-
function parseImportSpec(spec) {
|
|
889
|
-
const match = spec.match(/^(?:([a-z]+):)?([^/@:]+)\/([^/@:]+)(?:@([^:]+))?(?::(.+))?$/);
|
|
890
|
-
if (!match) throw new Error(`Invalid import spec: ${spec}\nFormat: [provider:]owner/repo[@ref]:{skill1,skill2,...}`);
|
|
891
|
-
const [, providerMatch = "github", ownerMatch, repoMatch, refMatch = "main", skillsStr] = match;
|
|
892
|
-
if (!ownerMatch || !repoMatch) throw new Error(`Invalid import spec: ${spec}\nFormat: [provider:]owner/repo[@ref]:{skill1,skill2,...}`);
|
|
893
|
-
const provider = providerMatch;
|
|
894
|
-
const owner = ownerMatch;
|
|
895
|
-
const repo = repoMatch;
|
|
896
|
-
const ref = refMatch;
|
|
897
|
-
if (![
|
|
898
|
-
"github",
|
|
899
|
-
"gitlab",
|
|
900
|
-
"gitee"
|
|
901
|
-
].includes(provider)) throw new Error(`Unsupported provider: ${provider}. Supported: github, gitlab, gitee`);
|
|
902
|
-
const safeNamePattern = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
|
|
903
|
-
if (!safeNamePattern.test(owner)) throw new Error(`Invalid owner: "${owner}". Must start with alphanumeric and only contain alphanumeric, hyphen, underscore, or dot`);
|
|
904
|
-
if (!safeNamePattern.test(repo)) throw new Error(`Invalid repo: "${repo}". Must start with alphanumeric and only contain alphanumeric, hyphen, underscore, or dot`);
|
|
905
|
-
if (!safeNamePattern.test(ref)) throw new Error(`Invalid ref: "${ref}". Must start with alphanumeric and only contain alphanumeric, hyphen, underscore, or dot`);
|
|
906
|
-
let skills = "all";
|
|
907
|
-
if (skillsStr) if (skillsStr.startsWith("{") && skillsStr.endsWith("}")) {
|
|
908
|
-
const skillList = skillsStr.slice(1, -1).split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
909
|
-
if (skillList.length === 0) throw new Error("Empty skill list in braces");
|
|
910
|
-
skills = skillList;
|
|
911
|
-
} else skills = [skillsStr.trim()];
|
|
912
|
-
return {
|
|
913
|
-
provider,
|
|
914
|
-
owner,
|
|
915
|
-
repo,
|
|
916
|
-
ref,
|
|
917
|
-
skills,
|
|
918
|
-
rawSpec: spec
|
|
919
|
-
};
|
|
920
|
-
}
|
|
921
|
-
/**
|
|
922
|
-
* Build Git URL from import spec
|
|
923
|
-
*/
|
|
924
|
-
function buildGitUrl(spec) {
|
|
925
|
-
return `${providerUrls[spec.provider]}/${spec.owner}/${spec.repo}.git`;
|
|
926
|
-
}
|
|
927
|
-
/**
|
|
928
|
-
* Get display name for import spec
|
|
929
|
-
*/
|
|
930
|
-
function getSpecDisplayName(spec) {
|
|
931
|
-
const skillsDisplay = spec.skills === "all" ? "all skills" : spec.skills.length === 1 ? spec.skills[0] : `${spec.skills.length} skills`;
|
|
932
|
-
return `${spec.owner}/${spec.repo}@${spec.ref} (${skillsDisplay})`;
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
//#endregion
|
|
936
|
-
//#region src/agent/skills/importer.ts
|
|
937
|
-
/**
|
|
938
|
-
* Temporary skill importer for session lifecycle
|
|
939
|
-
* Clones Git repos to temp directory and manages imported skills
|
|
940
|
-
*/
|
|
941
|
-
var SkillImporter = class {
|
|
942
|
-
tempDir;
|
|
943
|
-
imported = /* @__PURE__ */ new Map();
|
|
944
|
-
constructor(sessionId) {
|
|
945
|
-
this.tempDir = join(tmpdir(), `agent-worker-skills-${sessionId}`);
|
|
946
|
-
}
|
|
947
|
-
/**
|
|
948
|
-
* Import skills from a Git repository
|
|
949
|
-
*/
|
|
950
|
-
async import(spec) {
|
|
951
|
-
const parsed = parseImportSpec(spec);
|
|
952
|
-
console.log(`Importing: ${getSpecDisplayName(parsed)}`);
|
|
953
|
-
const repoDir = await this.cloneRepo(parsed);
|
|
954
|
-
const skillNames = await this.extractSkills(repoDir, parsed);
|
|
955
|
-
console.log(`✓ Imported ${skillNames.length} skill(s): ${skillNames.join(", ")}`);
|
|
956
|
-
return skillNames;
|
|
957
|
-
}
|
|
958
|
-
/**
|
|
959
|
-
* Import skills from multiple specs
|
|
960
|
-
*/
|
|
961
|
-
async importMultiple(specs) {
|
|
962
|
-
const allSkillNames = [];
|
|
963
|
-
for (const spec of specs) try {
|
|
964
|
-
const skillNames = await this.import(spec);
|
|
965
|
-
allSkillNames.push(...skillNames);
|
|
966
|
-
} catch (error) {
|
|
967
|
-
console.error(`Failed to import ${spec}:`, error);
|
|
968
|
-
}
|
|
969
|
-
return allSkillNames;
|
|
970
|
-
}
|
|
971
|
-
/**
|
|
972
|
-
* Get path for an imported skill
|
|
973
|
-
*/
|
|
974
|
-
getImportedSkillPath(skillName) {
|
|
975
|
-
return this.imported.get(skillName)?.tempPath || null;
|
|
976
|
-
}
|
|
977
|
-
/**
|
|
978
|
-
* Get all imported skill paths
|
|
979
|
-
*/
|
|
980
|
-
getAllImportedSkillPaths() {
|
|
981
|
-
return Array.from(this.imported.values()).map((s) => s.tempPath);
|
|
982
|
-
}
|
|
983
|
-
/**
|
|
984
|
-
* Get all imported skills metadata
|
|
985
|
-
*/
|
|
986
|
-
getImportedSkills() {
|
|
987
|
-
return Array.from(this.imported.values());
|
|
988
|
-
}
|
|
989
|
-
/**
|
|
990
|
-
* Get temporary directory path
|
|
991
|
-
*/
|
|
992
|
-
getTempDir() {
|
|
993
|
-
return this.tempDir;
|
|
994
|
-
}
|
|
995
|
-
/**
|
|
996
|
-
* Cleanup temporary directory
|
|
997
|
-
*/
|
|
998
|
-
async cleanup() {
|
|
999
|
-
if (existsSync(this.tempDir)) await rm(this.tempDir, {
|
|
1000
|
-
recursive: true,
|
|
1001
|
-
force: true
|
|
1002
|
-
});
|
|
1003
|
-
}
|
|
1004
|
-
/**
|
|
1005
|
-
* Clone Git repository (shallow clone)
|
|
1006
|
-
*/
|
|
1007
|
-
async cloneRepo(spec) {
|
|
1008
|
-
await mkdir(this.tempDir, { recursive: true });
|
|
1009
|
-
const repoDir = join(this.tempDir, `${spec.owner}-${spec.repo}`);
|
|
1010
|
-
if (existsSync(repoDir)) return repoDir;
|
|
1011
|
-
const gitUrl = buildGitUrl(spec);
|
|
1012
|
-
await this.execGit([
|
|
1013
|
-
"clone",
|
|
1014
|
-
"--depth",
|
|
1015
|
-
"1",
|
|
1016
|
-
"--branch",
|
|
1017
|
-
spec.ref,
|
|
1018
|
-
"--single-branch",
|
|
1019
|
-
gitUrl,
|
|
1020
|
-
repoDir
|
|
1021
|
-
]);
|
|
1022
|
-
return repoDir;
|
|
1023
|
-
}
|
|
1024
|
-
/**
|
|
1025
|
-
* Extract skills from cloned repository
|
|
1026
|
-
*/
|
|
1027
|
-
async extractSkills(repoDir, spec) {
|
|
1028
|
-
const skillsDir = await this.findSkillsDirectory(repoDir);
|
|
1029
|
-
const skillsToImport = spec.skills === "all" ? await this.findAllSkills(skillsDir) : spec.skills;
|
|
1030
|
-
const importedSkills = [];
|
|
1031
|
-
for (const skillName of skillsToImport) {
|
|
1032
|
-
const skillPath = join(skillsDir, skillName);
|
|
1033
|
-
const skillMdPath = join(skillPath, "SKILL.md");
|
|
1034
|
-
try {
|
|
1035
|
-
await stat(skillMdPath);
|
|
1036
|
-
this.imported.set(skillName, {
|
|
1037
|
-
name: skillName,
|
|
1038
|
-
source: spec.rawSpec,
|
|
1039
|
-
tempPath: skillPath
|
|
1040
|
-
});
|
|
1041
|
-
importedSkills.push(skillName);
|
|
1042
|
-
} catch {
|
|
1043
|
-
console.warn(`Skipping ${skillName}: SKILL.md not found`);
|
|
1044
|
-
}
|
|
1045
|
-
}
|
|
1046
|
-
if (importedSkills.length === 0) throw new Error(`No valid skills found in ${spec.owner}/${spec.repo}`);
|
|
1047
|
-
return importedSkills;
|
|
1048
|
-
}
|
|
1049
|
-
/**
|
|
1050
|
-
* Find skills directory in repository
|
|
1051
|
-
* Tries: skills/, agent-skills/, . (root)
|
|
1052
|
-
*/
|
|
1053
|
-
async findSkillsDirectory(repoDir) {
|
|
1054
|
-
for (const candidate of [
|
|
1055
|
-
"skills",
|
|
1056
|
-
"agent-skills",
|
|
1057
|
-
"."
|
|
1058
|
-
]) {
|
|
1059
|
-
const dir = join(repoDir, candidate);
|
|
1060
|
-
try {
|
|
1061
|
-
if ((await stat(dir)).isDirectory()) {
|
|
1062
|
-
if ((await readdir(dir, { withFileTypes: true })).some((entry) => entry.isDirectory() && existsSync(join(dir, entry.name, "SKILL.md")))) return dir;
|
|
1063
|
-
}
|
|
1064
|
-
} catch {
|
|
1065
|
-
continue;
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
throw new Error(`No skills directory found in ${repoDir}`);
|
|
1069
|
-
}
|
|
1070
|
-
/**
|
|
1071
|
-
* Find all skills in a directory
|
|
1072
|
-
*/
|
|
1073
|
-
async findAllSkills(skillsDir) {
|
|
1074
|
-
const entries = await readdir(skillsDir, { withFileTypes: true });
|
|
1075
|
-
const skills = [];
|
|
1076
|
-
for (const entry of entries) if (entry.isDirectory()) {
|
|
1077
|
-
if (existsSync(join(skillsDir, entry.name, "SKILL.md"))) skills.push(entry.name);
|
|
1078
|
-
}
|
|
1079
|
-
return skills;
|
|
1080
|
-
}
|
|
1081
|
-
/**
|
|
1082
|
-
* Execute git command
|
|
1083
|
-
*/
|
|
1084
|
-
async execGit(args) {
|
|
1085
|
-
return new Promise((resolve, reject) => {
|
|
1086
|
-
const git = spawn("git", args, { stdio: [
|
|
1087
|
-
"ignore",
|
|
1088
|
-
"pipe",
|
|
1089
|
-
"pipe"
|
|
1090
|
-
] });
|
|
1091
|
-
let stdout = "";
|
|
1092
|
-
let stderr = "";
|
|
1093
|
-
git.stdout?.on("data", (data) => {
|
|
1094
|
-
stdout += data.toString();
|
|
1095
|
-
});
|
|
1096
|
-
git.stderr?.on("data", (data) => {
|
|
1097
|
-
stderr += data.toString();
|
|
1098
|
-
});
|
|
1099
|
-
git.on("close", (code) => {
|
|
1100
|
-
if (code === 0) resolve();
|
|
1101
|
-
else reject(/* @__PURE__ */ new Error(`Git command failed (exit ${code}): ${stderr || stdout}`));
|
|
1102
|
-
});
|
|
1103
|
-
git.on("error", (error) => {
|
|
1104
|
-
reject(/* @__PURE__ */ new Error(`Failed to spawn git: ${error.message}`));
|
|
1105
|
-
});
|
|
1106
|
-
});
|
|
32
|
+
return this.engine.getState();
|
|
1107
33
|
}
|
|
1108
34
|
};
|
|
1109
|
-
|
|
1110
35
|
//#endregion
|
|
1111
|
-
export {
|
|
36
|
+
export { AgentHandle, AgentRegistry, AgentWorker, DEFAULT_PORT, DaemonEventLog, LocalWorker, MemoryStateStore, SkillImporter, createSkillTool, isDaemonRunning, readDaemonInfo, removeDaemonInfo, startDaemon, writeDaemonInfo };
|