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/dist/index.mjs CHANGED
@@ -1,1111 +1,36 @@
1
- import { F as createModelWithProvider, M as SUPPORTED_PROVIDERS, N as createModel, P as createModelAsync, a as createMockBackend, d as CodexBackend, f as ClaudeCodeBackend, i as MockAIBackend, j as FRONTIER_MODELS, n as createBackend, o as SdkBackend, r as listBackends, t as checkBackends, u as CursorBackend } from "./backends-D7DT0uox.mjs";
2
- import { t as createTool } from "./create-tool-gcUuI1FD.mjs";
3
- import { ToolLoopAgent, stepCountIs } from "ai";
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
- * AgentWorker - Stateful worker for controlled agent execution
5
+ * LocalWorker — In-process execution via AgentWorker.
16
6
  *
17
- * Uses ToolLoopAgent internally for multi-step reasoning loops.
18
- * Maintains conversation state across multiple send() calls,
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 AgentWorker = class {
26
- id;
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
- if (restore) {
62
- this.id = restore.id;
63
- this.createdAt = restore.createdAt;
64
- this.messages = [...restore.messages];
65
- this.totalUsage = { ...restore.totalUsage };
66
- this.pendingApprovals = [...restore.pendingApprovals ?? []];
67
- } else {
68
- this.id = crypto.randomUUID();
69
- this.createdAt = (/* @__PURE__ */ new Date()).toISOString();
70
- }
71
- this.model = config.model;
72
- this.system = config.system;
73
- this.tools = config.tools ? { ...config.tools } : {};
74
- this.approval = config.approval ? { ...config.approval } : {};
75
- this.maxTokens = config.maxTokens ?? 4096;
76
- this.maxSteps = config.maxSteps ?? 200;
77
- this.backend = config.backend ?? null;
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 { 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 };
36
+ export { AgentHandle, AgentRegistry, AgentWorker, DEFAULT_PORT, DaemonEventLog, LocalWorker, MemoryStateStore, SkillImporter, createSkillTool, isDaemonRunning, readDaemonInfo, removeDaemonInfo, startDaemon, writeDaemonInfo };