@yolo-labs/core-agent-runtime 1.0.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/chunk-OBRMNJP2.js +634 -0
- package/dist/chunk-OBRMNJP2.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +145 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +241 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
// src/headless-agent.ts
|
|
2
|
+
import { agentLoop, SessionManager } from "@yolo-labs/core-agent-service";
|
|
3
|
+
import { ToolRegistryImpl } from "@yolo-labs/core-tools";
|
|
4
|
+
var HeadlessAgent = class {
|
|
5
|
+
config;
|
|
6
|
+
transport;
|
|
7
|
+
provider;
|
|
8
|
+
toolRegistry;
|
|
9
|
+
sandbox;
|
|
10
|
+
taskQueue = [];
|
|
11
|
+
isProcessing = false;
|
|
12
|
+
isShuttingDown = false;
|
|
13
|
+
startTime = Date.now();
|
|
14
|
+
tasksCompleted = 0;
|
|
15
|
+
currentTaskPrompt;
|
|
16
|
+
injectedContext = {};
|
|
17
|
+
conversationHistory = [];
|
|
18
|
+
sessionManager;
|
|
19
|
+
sessionId;
|
|
20
|
+
currentAbortController;
|
|
21
|
+
constructor(config) {
|
|
22
|
+
this.config = config;
|
|
23
|
+
this.provider = config.provider;
|
|
24
|
+
this.sandbox = config.sandbox === "local" ? null : config.sandbox;
|
|
25
|
+
this.toolRegistry = new ToolRegistryImpl();
|
|
26
|
+
for (const tool of config.tools) {
|
|
27
|
+
this.toolRegistry.register(tool);
|
|
28
|
+
}
|
|
29
|
+
if (config.sessionStore) {
|
|
30
|
+
this.sessionManager = new SessionManager({ store: config.sessionStore });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Replace the sandbox provider (used by the factory when sandbox is 'local'). */
|
|
34
|
+
setSandbox(sandbox) {
|
|
35
|
+
this.sandbox = sandbox;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Binds a transport, begins listening for commands, and runs the initial task if configured.
|
|
39
|
+
*
|
|
40
|
+
* @param transport - The transport to use for command input and event output.
|
|
41
|
+
*/
|
|
42
|
+
async start(transport) {
|
|
43
|
+
this.transport = transport;
|
|
44
|
+
this.startTime = Date.now();
|
|
45
|
+
transport.onCommand((cmd) => this.handleCommand(cmd));
|
|
46
|
+
if (this.config.initialTask) {
|
|
47
|
+
this.enqueueTask({ prompt: this.config.initialTask });
|
|
48
|
+
}
|
|
49
|
+
this.emitStatus();
|
|
50
|
+
}
|
|
51
|
+
// 79b: Internal task queue
|
|
52
|
+
enqueueTask(task) {
|
|
53
|
+
this.taskQueue.push(task);
|
|
54
|
+
this.processQueue();
|
|
55
|
+
}
|
|
56
|
+
async processQueue() {
|
|
57
|
+
if (this.isProcessing || this.isShuttingDown) return;
|
|
58
|
+
if (this.taskQueue.length === 0) {
|
|
59
|
+
if (this.config.idlePolicy === "shutdown") {
|
|
60
|
+
await this.stop();
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.isProcessing = true;
|
|
65
|
+
const task = this.taskQueue.shift();
|
|
66
|
+
this.currentTaskPrompt = task.prompt;
|
|
67
|
+
this.emitStatus();
|
|
68
|
+
try {
|
|
69
|
+
const env = {};
|
|
70
|
+
for (const [k, v] of Object.entries({ ...this.injectedContext, ...task.context })) {
|
|
71
|
+
env[k] = String(v);
|
|
72
|
+
}
|
|
73
|
+
const context = {
|
|
74
|
+
sandbox: this.sandbox,
|
|
75
|
+
sessionId: `session_${Date.now()}`,
|
|
76
|
+
env
|
|
77
|
+
};
|
|
78
|
+
let systemPrompt;
|
|
79
|
+
if ("system" in this.config.prompts && typeof this.config.prompts.system === "string") {
|
|
80
|
+
systemPrompt = this.config.prompts.system;
|
|
81
|
+
}
|
|
82
|
+
this.currentAbortController = new AbortController();
|
|
83
|
+
const events = agentLoop(
|
|
84
|
+
{
|
|
85
|
+
prompt: task.prompt,
|
|
86
|
+
system: systemPrompt,
|
|
87
|
+
context: { conversationHistory: this.conversationHistory }
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
provider: this.provider,
|
|
91
|
+
toolRegistry: this.toolRegistry,
|
|
92
|
+
sandboxContext: context,
|
|
93
|
+
signal: this.currentAbortController.signal
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
let currentText = "";
|
|
97
|
+
const currentToolCalls = [];
|
|
98
|
+
const turnMessages = [];
|
|
99
|
+
let assistantTurnOpen = false;
|
|
100
|
+
turnMessages.push({ role: "user", content: task.prompt });
|
|
101
|
+
for await (const event of events) {
|
|
102
|
+
this.transport?.emit(event);
|
|
103
|
+
switch (event.type) {
|
|
104
|
+
case "text_delta":
|
|
105
|
+
currentText += event.text;
|
|
106
|
+
assistantTurnOpen = true;
|
|
107
|
+
break;
|
|
108
|
+
case "tool_call":
|
|
109
|
+
currentToolCalls.push({
|
|
110
|
+
type: "tool_use",
|
|
111
|
+
id: event.toolCallId,
|
|
112
|
+
name: event.name,
|
|
113
|
+
input: event.input
|
|
114
|
+
});
|
|
115
|
+
assistantTurnOpen = true;
|
|
116
|
+
break;
|
|
117
|
+
case "tool_result": {
|
|
118
|
+
if (assistantTurnOpen) {
|
|
119
|
+
const blocks = [];
|
|
120
|
+
if (currentText) {
|
|
121
|
+
blocks.push({ type: "text", text: currentText });
|
|
122
|
+
}
|
|
123
|
+
blocks.push(...currentToolCalls);
|
|
124
|
+
if (blocks.length > 0) {
|
|
125
|
+
turnMessages.push({ role: "assistant", content: blocks });
|
|
126
|
+
}
|
|
127
|
+
currentText = "";
|
|
128
|
+
currentToolCalls.length = 0;
|
|
129
|
+
assistantTurnOpen = false;
|
|
130
|
+
}
|
|
131
|
+
const toolResult = {
|
|
132
|
+
type: "tool_result",
|
|
133
|
+
tool_use_id: event.toolCallId,
|
|
134
|
+
content: typeof event.output === "string" ? event.output : JSON.stringify(event.output ?? event.error ?? ""),
|
|
135
|
+
...event.success ? {} : { is_error: true }
|
|
136
|
+
};
|
|
137
|
+
const lastMsg = turnMessages[turnMessages.length - 1];
|
|
138
|
+
if (lastMsg && lastMsg.role === "user" && Array.isArray(lastMsg.content) && lastMsg.content.length > 0 && lastMsg.content[0].type === "tool_result") {
|
|
139
|
+
lastMsg.content.push(toolResult);
|
|
140
|
+
} else {
|
|
141
|
+
turnMessages.push({ role: "user", content: [toolResult] });
|
|
142
|
+
}
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
case "agent_complete":
|
|
146
|
+
if (assistantTurnOpen) {
|
|
147
|
+
const blocks = [];
|
|
148
|
+
if (currentText) {
|
|
149
|
+
blocks.push({ type: "text", text: currentText });
|
|
150
|
+
}
|
|
151
|
+
blocks.push(...currentToolCalls);
|
|
152
|
+
if (blocks.length > 0) {
|
|
153
|
+
turnMessages.push({ role: "assistant", content: blocks });
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
break;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
this.conversationHistory.push(...turnMessages);
|
|
160
|
+
if (this.sessionManager) {
|
|
161
|
+
if (!this.sessionId) {
|
|
162
|
+
const session = await this.sessionManager.createSession({ prompt: task.prompt });
|
|
163
|
+
this.sessionId = session.id;
|
|
164
|
+
}
|
|
165
|
+
await this.sessionManager.updateConversationHistory(
|
|
166
|
+
this.sessionId,
|
|
167
|
+
this.conversationHistory
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
this.tasksCompleted++;
|
|
171
|
+
} catch (err) {
|
|
172
|
+
const errorEvent = {
|
|
173
|
+
type: "error",
|
|
174
|
+
error: err instanceof Error ? err.message : String(err),
|
|
175
|
+
code: "TASK_ERROR",
|
|
176
|
+
recoverable: false
|
|
177
|
+
};
|
|
178
|
+
this.transport?.emit(errorEvent);
|
|
179
|
+
}
|
|
180
|
+
this.isProcessing = false;
|
|
181
|
+
this.currentTaskPrompt = void 0;
|
|
182
|
+
this.currentAbortController = void 0;
|
|
183
|
+
this.emitStatus();
|
|
184
|
+
this.processQueue();
|
|
185
|
+
}
|
|
186
|
+
handleCommand(cmd) {
|
|
187
|
+
switch (cmd.type) {
|
|
188
|
+
case "task":
|
|
189
|
+
this.enqueueTask({ prompt: cmd.prompt, context: cmd.context });
|
|
190
|
+
break;
|
|
191
|
+
case "interrupt":
|
|
192
|
+
this.currentAbortController?.abort();
|
|
193
|
+
this.currentAbortController = void 0;
|
|
194
|
+
this.taskQueue.length = 0;
|
|
195
|
+
this.emitStatus();
|
|
196
|
+
break;
|
|
197
|
+
case "shutdown":
|
|
198
|
+
this.isShuttingDown = true;
|
|
199
|
+
if (!this.isProcessing) {
|
|
200
|
+
this.stop();
|
|
201
|
+
}
|
|
202
|
+
break;
|
|
203
|
+
case "abort":
|
|
204
|
+
this.stop();
|
|
205
|
+
break;
|
|
206
|
+
case "context_inject":
|
|
207
|
+
this.injectedContext[cmd.key] = cmd.value;
|
|
208
|
+
break;
|
|
209
|
+
case "context_clear":
|
|
210
|
+
this.clearHistory();
|
|
211
|
+
break;
|
|
212
|
+
case "status_query":
|
|
213
|
+
this.emitStatus();
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
emitStatus() {
|
|
218
|
+
const status = {
|
|
219
|
+
state: this.isShuttingDown ? "shutting_down" : this.isProcessing ? "working" : "idle",
|
|
220
|
+
currentTask: this.currentTaskPrompt,
|
|
221
|
+
queueDepth: this.taskQueue.length,
|
|
222
|
+
uptime: Math.floor((Date.now() - this.startTime) / 1e3),
|
|
223
|
+
tasksCompleted: this.tasksCompleted
|
|
224
|
+
};
|
|
225
|
+
this.transport?.emit(status);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Resets the conversation history, starting fresh for subsequent tasks.
|
|
229
|
+
*
|
|
230
|
+
* @remarks
|
|
231
|
+
* Can be triggered programmatically or via a `context_clear` command
|
|
232
|
+
* through the transport. If a session store is configured, the stored
|
|
233
|
+
* history is also cleared.
|
|
234
|
+
*/
|
|
235
|
+
clearHistory() {
|
|
236
|
+
this.conversationHistory = [];
|
|
237
|
+
if (this.sessionManager && this.sessionId) {
|
|
238
|
+
this.sessionManager.updateConversationHistory(this.sessionId, []);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Gracefully shuts down the agent, clears the task queue, and closes the transport.
|
|
243
|
+
*
|
|
244
|
+
* @remarks
|
|
245
|
+
* Called automatically when the idle policy is `'shutdown'` and the queue empties,
|
|
246
|
+
* or when an `abort` command is received.
|
|
247
|
+
*/
|
|
248
|
+
async stop() {
|
|
249
|
+
this.isShuttingDown = true;
|
|
250
|
+
this.taskQueue.length = 0;
|
|
251
|
+
await this.transport?.close();
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// src/local-sandbox.ts
|
|
256
|
+
import { readFile, writeFile, unlink, readdir, mkdir } from "fs/promises";
|
|
257
|
+
import { join, resolve } from "path";
|
|
258
|
+
import { spawn } from "child_process";
|
|
259
|
+
var LocalSandboxProvider = class {
|
|
260
|
+
rootDir;
|
|
261
|
+
cwd;
|
|
262
|
+
env;
|
|
263
|
+
timeout;
|
|
264
|
+
allowedCommands;
|
|
265
|
+
constructor(options = {}) {
|
|
266
|
+
this.rootDir = resolve(options.rootDir ?? process.cwd());
|
|
267
|
+
this.cwd = options.cwd ?? this.rootDir;
|
|
268
|
+
this.env = options.env ?? {};
|
|
269
|
+
this.timeout = options.timeout ?? 3e4;
|
|
270
|
+
this.allowedCommands = options.allowedCommands;
|
|
271
|
+
}
|
|
272
|
+
resolvePath(path) {
|
|
273
|
+
const resolved = resolve(this.rootDir, path);
|
|
274
|
+
if (!resolved.startsWith(this.rootDir)) {
|
|
275
|
+
throw new Error(`Path '${path}' is outside the root directory`);
|
|
276
|
+
}
|
|
277
|
+
return resolved;
|
|
278
|
+
}
|
|
279
|
+
/** Read a file relative to the sandbox root directory. */
|
|
280
|
+
async readFile(path) {
|
|
281
|
+
const fullPath = this.resolvePath(path);
|
|
282
|
+
return readFile(fullPath, "utf-8");
|
|
283
|
+
}
|
|
284
|
+
/** Write a file relative to the sandbox root, creating parent directories as needed. */
|
|
285
|
+
async writeFile(path, content) {
|
|
286
|
+
const fullPath = this.resolvePath(path);
|
|
287
|
+
const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
|
|
288
|
+
await mkdir(dir, { recursive: true });
|
|
289
|
+
await writeFile(fullPath, content, "utf-8");
|
|
290
|
+
}
|
|
291
|
+
/** Delete a file relative to the sandbox root directory. */
|
|
292
|
+
async deleteFile(path) {
|
|
293
|
+
const fullPath = this.resolvePath(path);
|
|
294
|
+
await unlink(fullPath);
|
|
295
|
+
}
|
|
296
|
+
/** List file names in a directory relative to the sandbox root. */
|
|
297
|
+
async listFiles(dir) {
|
|
298
|
+
const fullPath = this.resolvePath(dir);
|
|
299
|
+
const entries = await readdir(fullPath);
|
|
300
|
+
return entries.map((name) => join(dir, name));
|
|
301
|
+
}
|
|
302
|
+
/** Recursively create a directory relative to the sandbox root. */
|
|
303
|
+
async mkdir(path) {
|
|
304
|
+
const fullPath = this.resolvePath(path);
|
|
305
|
+
await mkdir(fullPath, { recursive: true });
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Executes a shell command via `child_process.spawn`.
|
|
309
|
+
*
|
|
310
|
+
* @param command - The shell command string to execute.
|
|
311
|
+
* @param options - Optional working directory, environment, timeout, and output callbacks.
|
|
312
|
+
* @returns A result with exit code, stdout, and stderr.
|
|
313
|
+
*
|
|
314
|
+
* @remarks
|
|
315
|
+
* If an `allowedCommands` list is configured, only the first token of the
|
|
316
|
+
* command is checked against the allowlist. Rejected commands return exit code 1.
|
|
317
|
+
*/
|
|
318
|
+
async executeCommand(command, options) {
|
|
319
|
+
if (this.allowedCommands) {
|
|
320
|
+
const cmd = command.split(" ")[0];
|
|
321
|
+
if (!this.allowedCommands.includes(cmd)) {
|
|
322
|
+
return { exitCode: 1, stdout: "", stderr: `Command '${cmd}' not allowed` };
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
const cwd = options?.cwd ? this.resolvePath(options.cwd) : this.cwd;
|
|
326
|
+
const timeout = options?.timeout ?? this.timeout;
|
|
327
|
+
const env = { ...process.env, ...this.env, ...options?.env };
|
|
328
|
+
return new Promise((resolvePromise) => {
|
|
329
|
+
const child = spawn("sh", ["-c", command], {
|
|
330
|
+
cwd,
|
|
331
|
+
env,
|
|
332
|
+
timeout
|
|
333
|
+
});
|
|
334
|
+
let stdout = "";
|
|
335
|
+
let stderr = "";
|
|
336
|
+
child.stdout.on("data", (data) => {
|
|
337
|
+
const text = data.toString();
|
|
338
|
+
stdout += text;
|
|
339
|
+
options?.onStdout?.(text);
|
|
340
|
+
});
|
|
341
|
+
child.stderr.on("data", (data) => {
|
|
342
|
+
const text = data.toString();
|
|
343
|
+
stderr += text;
|
|
344
|
+
options?.onStderr?.(text);
|
|
345
|
+
});
|
|
346
|
+
child.on("close", (code) => {
|
|
347
|
+
resolvePromise({ exitCode: code ?? 1, stdout, stderr });
|
|
348
|
+
});
|
|
349
|
+
child.on("error", (err) => {
|
|
350
|
+
resolvePromise({ exitCode: 1, stdout, stderr: err.message });
|
|
351
|
+
});
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
/** Ensure the sandbox root directory exists. */
|
|
355
|
+
async create() {
|
|
356
|
+
await mkdir(this.rootDir, { recursive: true });
|
|
357
|
+
}
|
|
358
|
+
/** No-op: does not delete the host root directory. */
|
|
359
|
+
async destroy() {
|
|
360
|
+
}
|
|
361
|
+
/** No-op for local sandbox. */
|
|
362
|
+
async reset() {
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
// src/factory.ts
|
|
367
|
+
import { getDefaultTools } from "@yolo-labs/core-tools";
|
|
368
|
+
function createHeadlessAgent(config) {
|
|
369
|
+
const effectiveConfig = { ...config };
|
|
370
|
+
if (effectiveConfig.tools.length === 0 && effectiveConfig.sandbox === "local") {
|
|
371
|
+
effectiveConfig.tools = getDefaultTools();
|
|
372
|
+
}
|
|
373
|
+
const agent = new HeadlessAgent(effectiveConfig);
|
|
374
|
+
if (config.sandbox === "local") {
|
|
375
|
+
const localSandbox = new LocalSandboxProvider();
|
|
376
|
+
agent.setSandbox(localSandbox);
|
|
377
|
+
}
|
|
378
|
+
return agent;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// src/stdio-transport.ts
|
|
382
|
+
import { createInterface } from "readline";
|
|
383
|
+
var StdioTransport = class {
|
|
384
|
+
commandHandler;
|
|
385
|
+
rl;
|
|
386
|
+
/** Start reading stdin line-by-line, parsing each line as a JSON AgentCommand. */
|
|
387
|
+
onCommand(handler) {
|
|
388
|
+
this.commandHandler = handler;
|
|
389
|
+
this.rl = createInterface({ input: process.stdin });
|
|
390
|
+
this.rl.on("line", (line) => {
|
|
391
|
+
const trimmed = line.trim();
|
|
392
|
+
if (!trimmed) return;
|
|
393
|
+
try {
|
|
394
|
+
const cmd = JSON.parse(trimmed);
|
|
395
|
+
this.commandHandler?.(cmd);
|
|
396
|
+
} catch {
|
|
397
|
+
this.emit({
|
|
398
|
+
type: "error",
|
|
399
|
+
error: `Malformed command: ${trimmed}`,
|
|
400
|
+
code: "PARSE_ERROR",
|
|
401
|
+
recoverable: true
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
const shutdownHandler = () => {
|
|
406
|
+
this.commandHandler?.({ type: "shutdown" });
|
|
407
|
+
};
|
|
408
|
+
process.on("SIGTERM", shutdownHandler);
|
|
409
|
+
process.on("SIGINT", shutdownHandler);
|
|
410
|
+
}
|
|
411
|
+
/** Serialize an event or status as a JSON line and write it to stdout. */
|
|
412
|
+
emit(event) {
|
|
413
|
+
process.stdout.write(JSON.stringify(event) + "\n");
|
|
414
|
+
}
|
|
415
|
+
/** Close the readline interface and stop reading from stdin. */
|
|
416
|
+
async close() {
|
|
417
|
+
this.rl?.close();
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
// src/http-transport.ts
|
|
422
|
+
import { createServer } from "http";
|
|
423
|
+
var HttpTransport = class {
|
|
424
|
+
server;
|
|
425
|
+
commandHandler;
|
|
426
|
+
sseClients = /* @__PURE__ */ new Set();
|
|
427
|
+
currentStatus = {
|
|
428
|
+
state: "idle",
|
|
429
|
+
queueDepth: 0,
|
|
430
|
+
uptime: 0,
|
|
431
|
+
tasksCompleted: 0
|
|
432
|
+
};
|
|
433
|
+
startTime = Date.now();
|
|
434
|
+
port;
|
|
435
|
+
host;
|
|
436
|
+
constructor(options = {}) {
|
|
437
|
+
this.port = options.port ?? 8080;
|
|
438
|
+
this.host = options.host ?? "0.0.0.0";
|
|
439
|
+
this.server = createServer((req, res) => this.handleRequest(req, res));
|
|
440
|
+
}
|
|
441
|
+
async handleRequest(req, res) {
|
|
442
|
+
const url = req.url ?? "/";
|
|
443
|
+
if (req.method === "POST" && url === "/command") {
|
|
444
|
+
const chunks = [];
|
|
445
|
+
for await (const chunk of req) {
|
|
446
|
+
chunks.push(chunk);
|
|
447
|
+
}
|
|
448
|
+
try {
|
|
449
|
+
const body = JSON.parse(Buffer.concat(chunks).toString());
|
|
450
|
+
this.commandHandler?.(body);
|
|
451
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
452
|
+
res.end(JSON.stringify({ ok: true }));
|
|
453
|
+
} catch {
|
|
454
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
455
|
+
res.end(JSON.stringify({ error: "Invalid JSON" }));
|
|
456
|
+
}
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (req.method === "GET" && url === "/events") {
|
|
460
|
+
res.writeHead(200, {
|
|
461
|
+
"Content-Type": "text/event-stream",
|
|
462
|
+
"Cache-Control": "no-cache",
|
|
463
|
+
Connection: "keep-alive"
|
|
464
|
+
});
|
|
465
|
+
this.sseClients.add(res);
|
|
466
|
+
req.on("close", () => {
|
|
467
|
+
this.sseClients.delete(res);
|
|
468
|
+
});
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
if (req.method === "GET" && url === "/status") {
|
|
472
|
+
this.currentStatus.uptime = Math.floor((Date.now() - this.startTime) / 1e3);
|
|
473
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
474
|
+
res.end(JSON.stringify(this.currentStatus));
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
478
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
479
|
+
}
|
|
480
|
+
/** Register the command handler and start the HTTP server. */
|
|
481
|
+
onCommand(handler) {
|
|
482
|
+
this.commandHandler = handler;
|
|
483
|
+
this.server.listen(this.port, this.host, () => {
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
/** Broadcast an event or status update to all connected SSE clients. */
|
|
487
|
+
emit(event) {
|
|
488
|
+
if ("state" in event && "queueDepth" in event) {
|
|
489
|
+
this.currentStatus = event;
|
|
490
|
+
}
|
|
491
|
+
const data = JSON.stringify(event);
|
|
492
|
+
for (const client of this.sseClients) {
|
|
493
|
+
client.write(`data: ${data}
|
|
494
|
+
|
|
495
|
+
`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
/** Close all SSE connections and shut down the HTTP server. */
|
|
499
|
+
async close() {
|
|
500
|
+
for (const client of this.sseClients) {
|
|
501
|
+
client.end();
|
|
502
|
+
}
|
|
503
|
+
this.sseClients.clear();
|
|
504
|
+
return new Promise((resolve2) => {
|
|
505
|
+
this.server.close(() => resolve2());
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
// src/interactive-transport.ts
|
|
511
|
+
import { createInterface as createInterface2 } from "readline";
|
|
512
|
+
var RESET = "\x1B[0m";
|
|
513
|
+
var DIM = "\x1B[2m";
|
|
514
|
+
var ITALIC = "\x1B[3m";
|
|
515
|
+
var BOLD = "\x1B[1m";
|
|
516
|
+
var GREEN = "\x1B[32m";
|
|
517
|
+
var RED = "\x1B[31m";
|
|
518
|
+
var CYAN = "\x1B[36m";
|
|
519
|
+
var InteractiveTransport = class {
|
|
520
|
+
commandHandler;
|
|
521
|
+
rl;
|
|
522
|
+
isWorking = false;
|
|
523
|
+
prompt;
|
|
524
|
+
constructor(prompt = "> ") {
|
|
525
|
+
this.prompt = prompt;
|
|
526
|
+
}
|
|
527
|
+
onCommand(handler) {
|
|
528
|
+
this.commandHandler = handler;
|
|
529
|
+
this.rl = createInterface2({
|
|
530
|
+
input: process.stdin,
|
|
531
|
+
output: process.stderr,
|
|
532
|
+
prompt: this.prompt
|
|
533
|
+
});
|
|
534
|
+
this.rl.on("line", (line) => {
|
|
535
|
+
const trimmed = line.trim();
|
|
536
|
+
if (!trimmed) {
|
|
537
|
+
this.showPrompt();
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
this.commandHandler?.({ type: "task", prompt: trimmed });
|
|
541
|
+
});
|
|
542
|
+
this.rl.on("close", () => {
|
|
543
|
+
this.commandHandler?.({ type: "shutdown" });
|
|
544
|
+
});
|
|
545
|
+
process.on("SIGINT", () => {
|
|
546
|
+
if (this.isWorking) {
|
|
547
|
+
process.stderr.write("\n");
|
|
548
|
+
this.commandHandler?.({ type: "interrupt" });
|
|
549
|
+
} else {
|
|
550
|
+
process.stderr.write("\nBye.\n");
|
|
551
|
+
this.commandHandler?.({ type: "shutdown" });
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
this.showPrompt();
|
|
555
|
+
}
|
|
556
|
+
emit(event) {
|
|
557
|
+
if ("state" in event && "queueDepth" in event) {
|
|
558
|
+
this.isWorking = event.state === "working";
|
|
559
|
+
if (event.state === "idle" && !this.isWorking) {
|
|
560
|
+
this.showPrompt();
|
|
561
|
+
}
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
const sessionEvent = event;
|
|
565
|
+
switch (sessionEvent.type) {
|
|
566
|
+
case "text_delta":
|
|
567
|
+
process.stdout.write(sessionEvent.text);
|
|
568
|
+
break;
|
|
569
|
+
case "thought_delta":
|
|
570
|
+
process.stderr.write(`${DIM}${ITALIC}${sessionEvent.text}${RESET}`);
|
|
571
|
+
break;
|
|
572
|
+
case "tool_call":
|
|
573
|
+
process.stderr.write(
|
|
574
|
+
`
|
|
575
|
+
${CYAN}${BOLD}[tool]${RESET} ${sessionEvent.name}(${formatInput(sessionEvent.input)})
|
|
576
|
+
`
|
|
577
|
+
);
|
|
578
|
+
break;
|
|
579
|
+
case "tool_result":
|
|
580
|
+
if (sessionEvent.success) {
|
|
581
|
+
const output = typeof sessionEvent.output === "string" ? sessionEvent.output : JSON.stringify(sessionEvent.output);
|
|
582
|
+
const summary = output && output.length > 200 ? output.slice(0, 200) + "..." : output;
|
|
583
|
+
process.stderr.write(`${GREEN}[ok]${RESET} ${summary ?? "(no output)"}
|
|
584
|
+
`);
|
|
585
|
+
} else {
|
|
586
|
+
process.stderr.write(`${RED}[error]${RESET} ${sessionEvent.error ?? "unknown error"}
|
|
587
|
+
`);
|
|
588
|
+
}
|
|
589
|
+
break;
|
|
590
|
+
case "error":
|
|
591
|
+
process.stderr.write(`
|
|
592
|
+
${RED}${BOLD}Error:${RESET} ${sessionEvent.error}
|
|
593
|
+
`);
|
|
594
|
+
break;
|
|
595
|
+
case "agent_start":
|
|
596
|
+
this.isWorking = true;
|
|
597
|
+
break;
|
|
598
|
+
case "agent_complete":
|
|
599
|
+
this.isWorking = false;
|
|
600
|
+
process.stdout.write("\n");
|
|
601
|
+
break;
|
|
602
|
+
// Ignore internal events in interactive mode
|
|
603
|
+
case "tool_input_delta":
|
|
604
|
+
case "tool_progress":
|
|
605
|
+
case "status_update":
|
|
606
|
+
break;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
async close() {
|
|
610
|
+
this.rl?.close();
|
|
611
|
+
}
|
|
612
|
+
showPrompt() {
|
|
613
|
+
this.rl?.prompt();
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
function formatInput(input) {
|
|
617
|
+
const entries = Object.entries(input);
|
|
618
|
+
if (entries.length === 0) return "";
|
|
619
|
+
const parts = entries.map(([k, v]) => {
|
|
620
|
+
const val = typeof v === "string" ? v.length > 50 ? v.slice(0, 50) + "..." : v : JSON.stringify(v);
|
|
621
|
+
return `${k}=${val}`;
|
|
622
|
+
});
|
|
623
|
+
return parts.join(", ");
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
export {
|
|
627
|
+
HeadlessAgent,
|
|
628
|
+
LocalSandboxProvider,
|
|
629
|
+
createHeadlessAgent,
|
|
630
|
+
StdioTransport,
|
|
631
|
+
HttpTransport,
|
|
632
|
+
InteractiveTransport
|
|
633
|
+
};
|
|
634
|
+
//# sourceMappingURL=chunk-OBRMNJP2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/headless-agent.ts","../src/local-sandbox.ts","../src/factory.ts","../src/stdio-transport.ts","../src/http-transport.ts","../src/interactive-transport.ts"],"sourcesContent":["// Task 79: HeadlessAgent class\n\nimport { agentLoop, SessionManager } from '@yolo-labs/core-agent-service';\nimport { ToolRegistryImpl } from '@yolo-labs/core-tools';\nimport type {\n HeadlessAgentConfig,\n AgentCommand,\n AgentStatus,\n AgentTransport,\n SessionEvent,\n SandboxContext,\n SandboxProvider,\n AIProvider,\n ToolRegistry,\n Message,\n ContentBlock,\n ToolUseBlock,\n ToolResultBlock,\n} from '@yolo-labs/core-types';\n\ninterface TaskEntry {\n prompt: string;\n context?: Record<string, unknown>;\n}\n\n/**\n * Self-contained agent that processes tasks via a pluggable transport.\n *\n * @remarks\n * Manages a task queue, executes tasks through {@link agentLoop}, and routes\n * all session events through the bound {@link AgentTransport}. Supports\n * commands: task submission, interrupt, shutdown, abort, context injection,\n * and status queries. Use {@link createHeadlessAgent} factory to instantiate.\n */\nexport class HeadlessAgent {\n private config: HeadlessAgentConfig;\n private transport?: AgentTransport;\n private provider: AIProvider;\n private toolRegistry: ToolRegistry;\n private sandbox: SandboxProvider;\n private taskQueue: TaskEntry[] = [];\n private isProcessing = false;\n private isShuttingDown = false;\n private startTime = Date.now();\n private tasksCompleted = 0;\n private currentTaskPrompt?: string;\n private injectedContext: Record<string, unknown> = {};\n private conversationHistory: Message[] = [];\n private sessionManager?: SessionManager;\n private sessionId?: string;\n private currentAbortController?: AbortController;\n\n constructor(config: HeadlessAgentConfig) {\n this.config = config;\n this.provider = config.provider;\n this.sandbox = config.sandbox === 'local'\n ? null as unknown as SandboxProvider // Will be set by factory\n : config.sandbox;\n\n // Set up tool registry\n this.toolRegistry = new ToolRegistryImpl();\n for (const tool of config.tools) {\n (this.toolRegistry as ToolRegistryImpl).register(tool);\n }\n\n // Task 116: Set up session manager if sessionStore is provided\n if (config.sessionStore) {\n this.sessionManager = new SessionManager({ store: config.sessionStore });\n }\n }\n\n /** Replace the sandbox provider (used by the factory when sandbox is 'local'). */\n setSandbox(sandbox: SandboxProvider): void {\n this.sandbox = sandbox;\n }\n\n /**\n * Binds a transport, begins listening for commands, and runs the initial task if configured.\n *\n * @param transport - The transport to use for command input and event output.\n */\n async start(transport: AgentTransport): Promise<void> {\n this.transport = transport;\n this.startTime = Date.now();\n\n // Listen for commands\n transport.onCommand((cmd) => this.handleCommand(cmd));\n\n // Execute initial task if provided\n if (this.config.initialTask) {\n this.enqueueTask({ prompt: this.config.initialTask });\n }\n\n this.emitStatus();\n }\n\n // 79b: Internal task queue\n private enqueueTask(task: TaskEntry): void {\n this.taskQueue.push(task);\n this.processQueue();\n }\n\n private async processQueue(): Promise<void> {\n if (this.isProcessing || this.isShuttingDown) return;\n if (this.taskQueue.length === 0) {\n // 80: Idle behavior\n if (this.config.idlePolicy === 'shutdown') {\n await this.stop();\n }\n return;\n }\n\n this.isProcessing = true;\n const task = this.taskQueue.shift()!;\n this.currentTaskPrompt = task.prompt;\n this.emitStatus();\n\n try {\n const env: Record<string, string> = {};\n for (const [k, v] of Object.entries({ ...this.injectedContext, ...task.context })) {\n env[k] = String(v);\n }\n\n const context: SandboxContext = {\n sandbox: this.sandbox,\n sessionId: `session_${Date.now()}`,\n env,\n };\n\n // Get system prompt\n let systemPrompt: string | undefined;\n if ('system' in this.config.prompts && typeof this.config.prompts.system === 'string') {\n systemPrompt = this.config.prompts.system;\n }\n\n // Task 117: Create an AbortController for this task so interrupt can cancel it\n this.currentAbortController = new AbortController();\n\n // Task 114: Pass conversationHistory in the generation request context\n const events = agentLoop(\n {\n prompt: task.prompt,\n system: systemPrompt,\n context: { conversationHistory: this.conversationHistory },\n },\n {\n provider: this.provider,\n toolRegistry: this.toolRegistry,\n sandboxContext: context,\n signal: this.currentAbortController.signal,\n },\n );\n\n // Task 113: Reconstruct conversation messages from emitted events\n let currentText = '';\n const currentToolCalls: ToolUseBlock[] = [];\n const turnMessages: Message[] = [];\n let assistantTurnOpen = false;\n\n // Add the user prompt as the first message of this task\n turnMessages.push({ role: 'user', content: task.prompt });\n\n for await (const event of events) {\n this.transport?.emit(event);\n\n // Reconstruct messages from events\n switch (event.type) {\n case 'text_delta':\n currentText += event.text;\n assistantTurnOpen = true;\n break;\n\n case 'tool_call':\n currentToolCalls.push({\n type: 'tool_use',\n id: event.toolCallId,\n name: event.name,\n input: event.input,\n });\n assistantTurnOpen = true;\n break;\n\n case 'tool_result': {\n // When we get tool results, flush the assistant message first\n if (assistantTurnOpen) {\n const blocks: ContentBlock[] = [];\n if (currentText) {\n blocks.push({ type: 'text', text: currentText });\n }\n blocks.push(...currentToolCalls);\n if (blocks.length > 0) {\n turnMessages.push({ role: 'assistant', content: blocks });\n }\n currentText = '';\n currentToolCalls.length = 0;\n assistantTurnOpen = false;\n }\n\n // Add tool result as user message\n const toolResult: ToolResultBlock = {\n type: 'tool_result',\n tool_use_id: event.toolCallId,\n content: typeof event.output === 'string'\n ? event.output\n : JSON.stringify(event.output ?? event.error ?? ''),\n ...(event.success ? {} : { is_error: true }),\n };\n // Accumulate tool results — they may be batched into one user message\n const lastMsg = turnMessages[turnMessages.length - 1];\n if (lastMsg && lastMsg.role === 'user' && Array.isArray(lastMsg.content)\n && lastMsg.content.length > 0 && (lastMsg.content[0] as ToolResultBlock).type === 'tool_result') {\n (lastMsg.content as ToolResultBlock[]).push(toolResult);\n } else {\n turnMessages.push({ role: 'user', content: [toolResult] });\n }\n break;\n }\n\n case 'agent_complete':\n // Flush any remaining assistant content\n if (assistantTurnOpen) {\n const blocks: ContentBlock[] = [];\n if (currentText) {\n blocks.push({ type: 'text', text: currentText });\n }\n blocks.push(...currentToolCalls);\n if (blocks.length > 0) {\n turnMessages.push({ role: 'assistant', content: blocks });\n }\n }\n break;\n }\n }\n\n // Append all turn messages to conversation history\n this.conversationHistory.push(...turnMessages);\n\n // Task 116: Persist conversation history to session store\n if (this.sessionManager) {\n if (!this.sessionId) {\n const session = await this.sessionManager.createSession({ prompt: task.prompt });\n this.sessionId = session.id;\n }\n await this.sessionManager.updateConversationHistory(\n this.sessionId,\n this.conversationHistory,\n );\n }\n\n this.tasksCompleted++;\n } catch (err) {\n const errorEvent: SessionEvent = {\n type: 'error',\n error: err instanceof Error ? err.message : String(err),\n code: 'TASK_ERROR',\n recoverable: false,\n };\n this.transport?.emit(errorEvent);\n }\n\n this.isProcessing = false;\n this.currentTaskPrompt = undefined;\n this.currentAbortController = undefined;\n this.emitStatus();\n\n // Process next task\n this.processQueue();\n }\n\n private handleCommand(cmd: AgentCommand): void {\n switch (cmd.type) {\n case 'task':\n this.enqueueTask({ prompt: cmd.prompt, context: cmd.context });\n break;\n\n case 'interrupt':\n // 79d: Cancel in-progress generation, clear queue\n // Task 117: Abort the in-flight agent loop via AbortController\n this.currentAbortController?.abort();\n this.currentAbortController = undefined;\n this.taskQueue.length = 0;\n this.emitStatus();\n break;\n\n case 'shutdown':\n // 79e: Finish current task, then close\n this.isShuttingDown = true;\n if (!this.isProcessing) {\n this.stop();\n }\n break;\n\n case 'abort':\n // Immediate exit\n this.stop();\n break;\n\n case 'context_inject':\n // 79f: Merge into active session context\n this.injectedContext[cmd.key] = cmd.value;\n break;\n\n case 'context_clear':\n // Task 115: Reset conversation history\n this.clearHistory();\n break;\n\n case 'status_query':\n // 79g: Emit current AgentStatus through transport\n this.emitStatus();\n break;\n }\n }\n\n private emitStatus(): void {\n const status: AgentStatus = {\n state: this.isShuttingDown\n ? 'shutting_down'\n : this.isProcessing\n ? 'working'\n : 'idle',\n currentTask: this.currentTaskPrompt,\n queueDepth: this.taskQueue.length,\n uptime: Math.floor((Date.now() - this.startTime) / 1000),\n tasksCompleted: this.tasksCompleted,\n };\n this.transport?.emit(status);\n }\n\n /**\n * Resets the conversation history, starting fresh for subsequent tasks.\n *\n * @remarks\n * Can be triggered programmatically or via a `context_clear` command\n * through the transport. If a session store is configured, the stored\n * history is also cleared.\n */\n clearHistory(): void {\n this.conversationHistory = [];\n // Task 116: Clear persisted history if session store is configured\n if (this.sessionManager && this.sessionId) {\n this.sessionManager.updateConversationHistory(this.sessionId, []);\n }\n }\n\n /**\n * Gracefully shuts down the agent, clears the task queue, and closes the transport.\n *\n * @remarks\n * Called automatically when the idle policy is `'shutdown'` and the queue empties,\n * or when an `abort` command is received.\n */\n async stop(): Promise<void> {\n this.isShuttingDown = true;\n this.taskQueue.length = 0;\n await this.transport?.close();\n }\n}\n","// Task 85: LocalSandboxProvider — operates directly on the host filesystem and shell\n\nimport { readFile, writeFile, unlink, readdir, mkdir, stat } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport { spawn } from 'node:child_process';\nimport type { SandboxProvider, CommandExecutionOptions } from '@yolo-labs/core-types';\n\n/** Configuration options for the local filesystem sandbox. */\nexport interface LocalSandboxProviderOptions {\n // 85a: Scoped to a configurable root directory for safety\n rootDir?: string;\n // 85c: Configurable settings\n cwd?: string;\n env?: Record<string, string>;\n timeout?: number;\n allowedCommands?: string[];\n}\n\n/**\n * {@link SandboxProvider} that operates directly on the host filesystem.\n *\n * @remarks\n * All file paths are resolved relative to a configurable root directory.\n * Path traversal outside the root is rejected. Command execution uses\n * `child_process.spawn` with an optional command allowlist for security.\n * Use this when the agent IS the sandbox occupant (e.g. inside a\n * WebContainer or Docker container).\n */\nexport class LocalSandboxProvider implements SandboxProvider {\n private rootDir: string;\n private cwd: string;\n private env: Record<string, string>;\n private timeout: number;\n private allowedCommands?: string[];\n\n constructor(options: LocalSandboxProviderOptions = {}) {\n this.rootDir = resolve(options.rootDir ?? process.cwd());\n this.cwd = options.cwd ?? this.rootDir;\n this.env = options.env ?? {};\n this.timeout = options.timeout ?? 30000;\n this.allowedCommands = options.allowedCommands;\n }\n\n private resolvePath(path: string): string {\n // 85a: Scope all file ops to rootDir\n const resolved = resolve(this.rootDir, path);\n if (!resolved.startsWith(this.rootDir)) {\n throw new Error(`Path '${path}' is outside the root directory`);\n }\n return resolved;\n }\n\n /** Read a file relative to the sandbox root directory. */\n async readFile(path: string): Promise<string> {\n const fullPath = this.resolvePath(path);\n return readFile(fullPath, 'utf-8');\n }\n\n /** Write a file relative to the sandbox root, creating parent directories as needed. */\n async writeFile(path: string, content: string): Promise<void> {\n const fullPath = this.resolvePath(path);\n // Ensure parent directory exists\n const dir = fullPath.substring(0, fullPath.lastIndexOf('/'));\n await mkdir(dir, { recursive: true });\n await writeFile(fullPath, content, 'utf-8');\n }\n\n /** Delete a file relative to the sandbox root directory. */\n async deleteFile(path: string): Promise<void> {\n const fullPath = this.resolvePath(path);\n await unlink(fullPath);\n }\n\n /** List file names in a directory relative to the sandbox root. */\n async listFiles(dir: string): Promise<string[]> {\n const fullPath = this.resolvePath(dir);\n const entries = await readdir(fullPath);\n return entries.map((name) => join(dir, name));\n }\n\n /** Recursively create a directory relative to the sandbox root. */\n async mkdir(path: string): Promise<void> {\n const fullPath = this.resolvePath(path);\n await mkdir(fullPath, { recursive: true });\n }\n\n /**\n * Executes a shell command via `child_process.spawn`.\n *\n * @param command - The shell command string to execute.\n * @param options - Optional working directory, environment, timeout, and output callbacks.\n * @returns A result with exit code, stdout, and stderr.\n *\n * @remarks\n * If an `allowedCommands` list is configured, only the first token of the\n * command is checked against the allowlist. Rejected commands return exit code 1.\n */\n async executeCommand(\n command: string,\n options?: CommandExecutionOptions,\n ): Promise<{ exitCode: number; stdout: string; stderr: string }> {\n // 85c: Check command allowlist if configured\n if (this.allowedCommands) {\n const cmd = command.split(' ')[0];\n if (!this.allowedCommands.includes(cmd)) {\n return { exitCode: 1, stdout: '', stderr: `Command '${cmd}' not allowed` };\n }\n }\n\n const cwd = options?.cwd ? this.resolvePath(options.cwd) : this.cwd;\n const timeout = options?.timeout ?? this.timeout;\n const env = { ...process.env, ...this.env, ...options?.env };\n\n return new Promise((resolvePromise) => {\n const child = spawn('sh', ['-c', command], {\n cwd,\n env,\n timeout,\n });\n\n let stdout = '';\n let stderr = '';\n\n child.stdout.on('data', (data: Buffer) => {\n const text = data.toString();\n stdout += text;\n options?.onStdout?.(text);\n });\n\n child.stderr.on('data', (data: Buffer) => {\n const text = data.toString();\n stderr += text;\n options?.onStderr?.(text);\n });\n\n child.on('close', (code) => {\n resolvePromise({ exitCode: code ?? 1, stdout, stderr });\n });\n\n child.on('error', (err) => {\n resolvePromise({ exitCode: 1, stdout, stderr: err.message });\n });\n });\n }\n\n /** Ensure the sandbox root directory exists. */\n async create(): Promise<void> {\n await mkdir(this.rootDir, { recursive: true });\n }\n\n /** No-op: does not delete the host root directory. */\n async destroy(): Promise<void> {\n // Don't delete the root dir — it's the host filesystem\n }\n\n /** No-op for local sandbox. */\n async reset(): Promise<void> {\n // No-op for local sandbox\n }\n}\n","// Task 78: createHeadlessAgent factory function\n\nimport type { HeadlessAgentConfig } from '@yolo-labs/core-types';\nimport { HeadlessAgent } from './headless-agent.js';\nimport { LocalSandboxProvider } from './local-sandbox.js';\nimport { getDefaultTools } from '@yolo-labs/core-tools';\n\n// 78a-c: Wire up provider, tools, prompts, sandbox from config\n/**\n * Factory function that creates and configures a {@link HeadlessAgent}.\n *\n * @param config - The headless agent configuration.\n * @returns A configured {@link HeadlessAgent} ready to be started with a transport.\n *\n * @remarks\n * When `config.sandbox` is `'local'`, a {@link LocalSandboxProvider} is\n * automatically created and injected. When `config.tools` is empty and\n * `config.sandbox` is `'local'`, default tools are auto-registered.\n */\nexport function createHeadlessAgent(config: HeadlessAgentConfig): HeadlessAgent {\n // Task 106: Auto-register default tools when tools is empty and sandbox is local\n const effectiveConfig = { ...config };\n if (effectiveConfig.tools.length === 0 && effectiveConfig.sandbox === 'local') {\n effectiveConfig.tools = getDefaultTools();\n }\n\n const agent = new HeadlessAgent(effectiveConfig);\n\n // If sandbox is 'local', create a LocalSandboxProvider\n if (config.sandbox === 'local') {\n const localSandbox = new LocalSandboxProvider();\n agent.setSandbox(localSandbox);\n }\n\n return agent;\n}\n","// Task 74: StdioTransport — reads JSON commands from stdin, writes events to stdout\n\nimport { createInterface } from 'node:readline';\nimport type { AgentCommand, AgentStatus, AgentTransport, SessionEvent } from '@yolo-labs/core-types';\n\n/**\n * {@link AgentTransport} using newline-delimited JSON over stdin/stdout.\n *\n * @remarks\n * Reads one JSON command per line from stdin and writes one JSON event per\n * line to stdout. SIGTERM and SIGINT are translated into `shutdown` commands.\n * Malformed input lines emit an `error` event rather than crashing.\n */\nexport class StdioTransport implements AgentTransport {\n private commandHandler?: (cmd: AgentCommand) => void;\n private rl?: ReturnType<typeof createInterface>;\n\n /** Start reading stdin line-by-line, parsing each line as a JSON AgentCommand. */\n onCommand(handler: (cmd: AgentCommand) => void): void {\n this.commandHandler = handler;\n\n this.rl = createInterface({ input: process.stdin });\n\n this.rl.on('line', (line: string) => {\n const trimmed = line.trim();\n if (!trimmed) return;\n\n try {\n const cmd = JSON.parse(trimmed) as AgentCommand;\n this.commandHandler?.(cmd);\n } catch {\n // Emit error for malformed input\n this.emit({\n type: 'error',\n error: `Malformed command: ${trimmed}`,\n code: 'PARSE_ERROR',\n recoverable: true,\n } as SessionEvent);\n }\n });\n\n // 74c: Handle SIGTERM/SIGINT as shutdown command\n const shutdownHandler = () => {\n this.commandHandler?.({ type: 'shutdown' });\n };\n process.on('SIGTERM', shutdownHandler);\n process.on('SIGINT', shutdownHandler);\n }\n\n /** Serialize an event or status as a JSON line and write it to stdout. */\n emit(event: SessionEvent | AgentStatus): void {\n process.stdout.write(JSON.stringify(event) + '\\n');\n }\n\n /** Close the readline interface and stop reading from stdin. */\n async close(): Promise<void> {\n this.rl?.close();\n }\n}\n","// Task 76: HttpTransport — minimal HTTP server for commands and event streaming\n\nimport { createServer, type Server, type IncomingMessage, type ServerResponse } from 'node:http';\nimport type { AgentCommand, AgentStatus, AgentTransport, SessionEvent } from '@yolo-labs/core-types';\n\n/** Configuration options for the HTTP transport server. */\nexport interface HttpTransportOptions {\n port?: number;\n host?: string;\n}\n\n/**\n * {@link AgentTransport} exposing a minimal HTTP server.\n *\n * @remarks\n * Endpoints:\n * - `POST /command` — accepts an {@link AgentCommand} as a JSON body\n * - `GET /events` — SSE stream of {@link SessionEvent} and {@link AgentStatus}\n * - `GET /status` — current {@link AgentStatus} as JSON\n *\n * Built on Node.js `http` module with no framework dependencies.\n */\nexport class HttpTransport implements AgentTransport {\n private server: Server;\n private commandHandler?: (cmd: AgentCommand) => void;\n private sseClients: Set<ServerResponse> = new Set();\n private currentStatus: AgentStatus = {\n state: 'idle',\n queueDepth: 0,\n uptime: 0,\n tasksCompleted: 0,\n };\n private startTime = Date.now();\n private port: number;\n private host: string;\n\n constructor(options: HttpTransportOptions = {}) {\n this.port = options.port ?? 8080;\n this.host = options.host ?? '0.0.0.0';\n\n // 76d: Uses Node.js built-in http module only\n this.server = createServer((req, res) => this.handleRequest(req, res));\n }\n\n private async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const url = req.url ?? '/';\n\n // 76a: POST /command — accepts AgentCommand as JSON body\n if (req.method === 'POST' && url === '/command') {\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n chunks.push(chunk as Buffer);\n }\n try {\n const body = JSON.parse(Buffer.concat(chunks).toString());\n this.commandHandler?.(body as AgentCommand);\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ ok: true }));\n } catch {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Invalid JSON' }));\n }\n return;\n }\n\n // 76b: GET /events — SSE stream of SessionEvent + AgentStatus\n if (req.method === 'GET' && url === '/events') {\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n });\n this.sseClients.add(res);\n\n req.on('close', () => {\n this.sseClients.delete(res);\n });\n return;\n }\n\n // 76c: GET /status — current AgentStatus as JSON\n if (req.method === 'GET' && url === '/status') {\n this.currentStatus.uptime = Math.floor((Date.now() - this.startTime) / 1000);\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(this.currentStatus));\n return;\n }\n\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Not found' }));\n }\n\n /** Register the command handler and start the HTTP server. */\n onCommand(handler: (cmd: AgentCommand) => void): void {\n this.commandHandler = handler;\n this.server.listen(this.port, this.host, () => {\n // Server is ready\n });\n }\n\n /** Broadcast an event or status update to all connected SSE clients. */\n emit(event: SessionEvent | AgentStatus): void {\n // Update internal status if this is a status event\n if ('state' in event && 'queueDepth' in event) {\n this.currentStatus = event as AgentStatus;\n }\n\n // Broadcast to SSE clients\n const data = JSON.stringify(event);\n for (const client of this.sseClients) {\n client.write(`data: ${data}\\n\\n`);\n }\n }\n\n /** Close all SSE connections and shut down the HTTP server. */\n async close(): Promise<void> {\n // Close all SSE connections\n for (const client of this.sseClients) {\n client.end();\n }\n this.sseClients.clear();\n\n // Close the server\n return new Promise((resolve) => {\n this.server.close(() => resolve());\n });\n }\n}\n","// Task 109: InteractiveTransport — human-friendly terminal mode\n\nimport { createInterface, type Interface } from 'node:readline';\nimport type { AgentCommand, AgentStatus, AgentTransport, SessionEvent } from '@yolo-labs/core-types';\n\n// ANSI escape codes\nconst RESET = '\\x1b[0m';\nconst DIM = '\\x1b[2m';\nconst ITALIC = '\\x1b[3m';\nconst BOLD = '\\x1b[1m';\nconst GREEN = '\\x1b[32m';\nconst RED = '\\x1b[31m';\nconst YELLOW = '\\x1b[33m';\nconst CYAN = '\\x1b[36m';\n\n/**\n * {@link AgentTransport} for interactive terminal sessions.\n *\n * @remarks\n * Renders agent events as formatted terminal output: streaming text to stdout,\n * dimmed/italic thinking text, tool call/result summaries, and error messages.\n * User input is read line-by-line from stdin and submitted as `task` commands.\n * Ctrl+C sends `interrupt` while working, `shutdown` while idle.\n */\nexport class InteractiveTransport implements AgentTransport {\n private commandHandler?: (cmd: AgentCommand) => void;\n private rl?: Interface;\n private isWorking = false;\n private prompt: string;\n\n constructor(prompt = '> ') {\n this.prompt = prompt;\n }\n\n onCommand(handler: (cmd: AgentCommand) => void): void {\n this.commandHandler = handler;\n\n this.rl = createInterface({\n input: process.stdin,\n output: process.stderr,\n prompt: this.prompt,\n });\n\n this.rl.on('line', (line: string) => {\n const trimmed = line.trim();\n if (!trimmed) {\n this.showPrompt();\n return;\n }\n this.commandHandler?.({ type: 'task', prompt: trimmed });\n });\n\n this.rl.on('close', () => {\n this.commandHandler?.({ type: 'shutdown' });\n });\n\n // Ctrl+C: interrupt if working, shutdown if idle\n process.on('SIGINT', () => {\n if (this.isWorking) {\n process.stderr.write('\\n');\n this.commandHandler?.({ type: 'interrupt' });\n } else {\n process.stderr.write('\\nBye.\\n');\n this.commandHandler?.({ type: 'shutdown' });\n }\n });\n\n this.showPrompt();\n }\n\n emit(event: SessionEvent | AgentStatus): void {\n // AgentStatus — track working state\n if ('state' in event && 'queueDepth' in event) {\n this.isWorking = event.state === 'working';\n if (event.state === 'idle' && !this.isWorking) {\n this.showPrompt();\n }\n return;\n }\n\n const sessionEvent = event as SessionEvent;\n\n switch (sessionEvent.type) {\n case 'text_delta':\n // Stream text in real-time\n process.stdout.write(sessionEvent.text);\n break;\n\n case 'thought_delta':\n // Dim/italic for thinking\n process.stderr.write(`${DIM}${ITALIC}${sessionEvent.text}${RESET}`);\n break;\n\n case 'tool_call':\n // Show tool name + input summary\n process.stderr.write(\n `\\n${CYAN}${BOLD}[tool]${RESET} ${sessionEvent.name}(${formatInput(sessionEvent.input)})\\n`,\n );\n break;\n\n case 'tool_result':\n if (sessionEvent.success) {\n const output = typeof sessionEvent.output === 'string'\n ? sessionEvent.output\n : JSON.stringify(sessionEvent.output);\n const summary = output && output.length > 200 ? output.slice(0, 200) + '...' : output;\n process.stderr.write(`${GREEN}[ok]${RESET} ${summary ?? '(no output)'}\\n`);\n } else {\n process.stderr.write(`${RED}[error]${RESET} ${sessionEvent.error ?? 'unknown error'}\\n`);\n }\n break;\n\n case 'error':\n process.stderr.write(`\\n${RED}${BOLD}Error:${RESET} ${sessionEvent.error}\\n`);\n break;\n\n case 'agent_start':\n this.isWorking = true;\n break;\n\n case 'agent_complete':\n this.isWorking = false;\n process.stdout.write('\\n');\n break;\n\n // Ignore internal events in interactive mode\n case 'tool_input_delta':\n case 'tool_progress':\n case 'status_update':\n break;\n }\n }\n\n async close(): Promise<void> {\n this.rl?.close();\n }\n\n private showPrompt(): void {\n this.rl?.prompt();\n }\n}\n\nfunction formatInput(input: Record<string, unknown>): string {\n const entries = Object.entries(input);\n if (entries.length === 0) return '';\n const parts = entries.map(([k, v]) => {\n const val = typeof v === 'string' ? (v.length > 50 ? v.slice(0, 50) + '...' : v) : JSON.stringify(v);\n return `${k}=${val}`;\n });\n return parts.join(', ');\n}\n"],"mappings":";AAEA,SAAS,WAAW,sBAAsB;AAC1C,SAAS,wBAAwB;AA+B1B,IAAM,gBAAN,MAAoB;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAyB,CAAC;AAAA,EAC1B,eAAe;AAAA,EACf,iBAAiB;AAAA,EACjB,YAAY,KAAK,IAAI;AAAA,EACrB,iBAAiB;AAAA,EACjB;AAAA,EACA,kBAA2C,CAAC;AAAA,EAC5C,sBAAiC,CAAC;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,QAA6B;AACvC,SAAK,SAAS;AACd,SAAK,WAAW,OAAO;AACvB,SAAK,UAAU,OAAO,YAAY,UAC9B,OACA,OAAO;AAGX,SAAK,eAAe,IAAI,iBAAiB;AACzC,eAAW,QAAQ,OAAO,OAAO;AAC/B,MAAC,KAAK,aAAkC,SAAS,IAAI;AAAA,IACvD;AAGA,QAAI,OAAO,cAAc;AACvB,WAAK,iBAAiB,IAAI,eAAe,EAAE,OAAO,OAAO,aAAa,CAAC;AAAA,IACzE;AAAA,EACF;AAAA;AAAA,EAGA,WAAW,SAAgC;AACzC,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,MAAM,WAA0C;AACpD,SAAK,YAAY;AACjB,SAAK,YAAY,KAAK,IAAI;AAG1B,cAAU,UAAU,CAAC,QAAQ,KAAK,cAAc,GAAG,CAAC;AAGpD,QAAI,KAAK,OAAO,aAAa;AAC3B,WAAK,YAAY,EAAE,QAAQ,KAAK,OAAO,YAAY,CAAC;AAAA,IACtD;AAEA,SAAK,WAAW;AAAA,EAClB;AAAA;AAAA,EAGQ,YAAY,MAAuB;AACzC,SAAK,UAAU,KAAK,IAAI;AACxB,SAAK,aAAa;AAAA,EACpB;AAAA,EAEA,MAAc,eAA8B;AAC1C,QAAI,KAAK,gBAAgB,KAAK,eAAgB;AAC9C,QAAI,KAAK,UAAU,WAAW,GAAG;AAE/B,UAAI,KAAK,OAAO,eAAe,YAAY;AACzC,cAAM,KAAK,KAAK;AAAA,MAClB;AACA;AAAA,IACF;AAEA,SAAK,eAAe;AACpB,UAAM,OAAO,KAAK,UAAU,MAAM;AAClC,SAAK,oBAAoB,KAAK;AAC9B,SAAK,WAAW;AAEhB,QAAI;AACF,YAAM,MAA8B,CAAC;AACrC,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,EAAE,GAAG,KAAK,iBAAiB,GAAG,KAAK,QAAQ,CAAC,GAAG;AACjF,YAAI,CAAC,IAAI,OAAO,CAAC;AAAA,MACnB;AAEA,YAAM,UAA0B;AAAA,QAC9B,SAAS,KAAK;AAAA,QACd,WAAW,WAAW,KAAK,IAAI,CAAC;AAAA,QAChC;AAAA,MACF;AAGA,UAAI;AACJ,UAAI,YAAY,KAAK,OAAO,WAAW,OAAO,KAAK,OAAO,QAAQ,WAAW,UAAU;AACrF,uBAAe,KAAK,OAAO,QAAQ;AAAA,MACrC;AAGA,WAAK,yBAAyB,IAAI,gBAAgB;AAGlD,YAAM,SAAS;AAAA,QACb;AAAA,UACE,QAAQ,KAAK;AAAA,UACb,QAAQ;AAAA,UACR,SAAS,EAAE,qBAAqB,KAAK,oBAAoB;AAAA,QAC3D;AAAA,QACA;AAAA,UACE,UAAU,KAAK;AAAA,UACf,cAAc,KAAK;AAAA,UACnB,gBAAgB;AAAA,UAChB,QAAQ,KAAK,uBAAuB;AAAA,QACtC;AAAA,MACF;AAGA,UAAI,cAAc;AAClB,YAAM,mBAAmC,CAAC;AAC1C,YAAM,eAA0B,CAAC;AACjC,UAAI,oBAAoB;AAGxB,mBAAa,KAAK,EAAE,MAAM,QAAQ,SAAS,KAAK,OAAO,CAAC;AAExD,uBAAiB,SAAS,QAAQ;AAChC,aAAK,WAAW,KAAK,KAAK;AAG1B,gBAAQ,MAAM,MAAM;AAAA,UAClB,KAAK;AACH,2BAAe,MAAM;AACrB,gCAAoB;AACpB;AAAA,UAEF,KAAK;AACH,6BAAiB,KAAK;AAAA,cACpB,MAAM;AAAA,cACN,IAAI,MAAM;AAAA,cACV,MAAM,MAAM;AAAA,cACZ,OAAO,MAAM;AAAA,YACf,CAAC;AACD,gCAAoB;AACpB;AAAA,UAEF,KAAK,eAAe;AAElB,gBAAI,mBAAmB;AACrB,oBAAM,SAAyB,CAAC;AAChC,kBAAI,aAAa;AACf,uBAAO,KAAK,EAAE,MAAM,QAAQ,MAAM,YAAY,CAAC;AAAA,cACjD;AACA,qBAAO,KAAK,GAAG,gBAAgB;AAC/B,kBAAI,OAAO,SAAS,GAAG;AACrB,6BAAa,KAAK,EAAE,MAAM,aAAa,SAAS,OAAO,CAAC;AAAA,cAC1D;AACA,4BAAc;AACd,+BAAiB,SAAS;AAC1B,kCAAoB;AAAA,YACtB;AAGA,kBAAM,aAA8B;AAAA,cAClC,MAAM;AAAA,cACN,aAAa,MAAM;AAAA,cACnB,SAAS,OAAO,MAAM,WAAW,WAC7B,MAAM,SACN,KAAK,UAAU,MAAM,UAAU,MAAM,SAAS,EAAE;AAAA,cACpD,GAAI,MAAM,UAAU,CAAC,IAAI,EAAE,UAAU,KAAK;AAAA,YAC5C;AAEA,kBAAM,UAAU,aAAa,aAAa,SAAS,CAAC;AACpD,gBAAI,WAAW,QAAQ,SAAS,UAAU,MAAM,QAAQ,QAAQ,OAAO,KAChE,QAAQ,QAAQ,SAAS,KAAM,QAAQ,QAAQ,CAAC,EAAsB,SAAS,eAAe;AACnG,cAAC,QAAQ,QAA8B,KAAK,UAAU;AAAA,YACxD,OAAO;AACL,2BAAa,KAAK,EAAE,MAAM,QAAQ,SAAS,CAAC,UAAU,EAAE,CAAC;AAAA,YAC3D;AACA;AAAA,UACF;AAAA,UAEA,KAAK;AAEH,gBAAI,mBAAmB;AACrB,oBAAM,SAAyB,CAAC;AAChC,kBAAI,aAAa;AACf,uBAAO,KAAK,EAAE,MAAM,QAAQ,MAAM,YAAY,CAAC;AAAA,cACjD;AACA,qBAAO,KAAK,GAAG,gBAAgB;AAC/B,kBAAI,OAAO,SAAS,GAAG;AACrB,6BAAa,KAAK,EAAE,MAAM,aAAa,SAAS,OAAO,CAAC;AAAA,cAC1D;AAAA,YACF;AACA;AAAA,QACJ;AAAA,MACF;AAGA,WAAK,oBAAoB,KAAK,GAAG,YAAY;AAG7C,UAAI,KAAK,gBAAgB;AACvB,YAAI,CAAC,KAAK,WAAW;AACnB,gBAAM,UAAU,MAAM,KAAK,eAAe,cAAc,EAAE,QAAQ,KAAK,OAAO,CAAC;AAC/E,eAAK,YAAY,QAAQ;AAAA,QAC3B;AACA,cAAM,KAAK,eAAe;AAAA,UACxB,KAAK;AAAA,UACL,KAAK;AAAA,QACP;AAAA,MACF;AAEA,WAAK;AAAA,IACP,SAAS,KAAK;AACZ,YAAM,aAA2B;AAAA,QAC/B,MAAM;AAAA,QACN,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACtD,MAAM;AAAA,QACN,aAAa;AAAA,MACf;AACA,WAAK,WAAW,KAAK,UAAU;AAAA,IACjC;AAEA,SAAK,eAAe;AACpB,SAAK,oBAAoB;AACzB,SAAK,yBAAyB;AAC9B,SAAK,WAAW;AAGhB,SAAK,aAAa;AAAA,EACpB;AAAA,EAEQ,cAAc,KAAyB;AAC7C,YAAQ,IAAI,MAAM;AAAA,MAChB,KAAK;AACH,aAAK,YAAY,EAAE,QAAQ,IAAI,QAAQ,SAAS,IAAI,QAAQ,CAAC;AAC7D;AAAA,MAEF,KAAK;AAGH,aAAK,wBAAwB,MAAM;AACnC,aAAK,yBAAyB;AAC9B,aAAK,UAAU,SAAS;AACxB,aAAK,WAAW;AAChB;AAAA,MAEF,KAAK;AAEH,aAAK,iBAAiB;AACtB,YAAI,CAAC,KAAK,cAAc;AACtB,eAAK,KAAK;AAAA,QACZ;AACA;AAAA,MAEF,KAAK;AAEH,aAAK,KAAK;AACV;AAAA,MAEF,KAAK;AAEH,aAAK,gBAAgB,IAAI,GAAG,IAAI,IAAI;AACpC;AAAA,MAEF,KAAK;AAEH,aAAK,aAAa;AAClB;AAAA,MAEF,KAAK;AAEH,aAAK,WAAW;AAChB;AAAA,IACJ;AAAA,EACF;AAAA,EAEQ,aAAmB;AACzB,UAAM,SAAsB;AAAA,MAC1B,OAAO,KAAK,iBACR,kBACA,KAAK,eACH,YACA;AAAA,MACN,aAAa,KAAK;AAAA,MAClB,YAAY,KAAK,UAAU;AAAA,MAC3B,QAAQ,KAAK,OAAO,KAAK,IAAI,IAAI,KAAK,aAAa,GAAI;AAAA,MACvD,gBAAgB,KAAK;AAAA,IACvB;AACA,SAAK,WAAW,KAAK,MAAM;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,eAAqB;AACnB,SAAK,sBAAsB,CAAC;AAE5B,QAAI,KAAK,kBAAkB,KAAK,WAAW;AACzC,WAAK,eAAe,0BAA0B,KAAK,WAAW,CAAC,CAAC;AAAA,IAClE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,OAAsB;AAC1B,SAAK,iBAAiB;AACtB,SAAK,UAAU,SAAS;AACxB,UAAM,KAAK,WAAW,MAAM;AAAA,EAC9B;AACF;;;ACnWA,SAAS,UAAU,WAAW,QAAQ,SAAS,aAAmB;AAClE,SAAS,MAAM,eAAe;AAC9B,SAAS,aAAa;AAwBf,IAAM,uBAAN,MAAsD;AAAA,EACnD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAY,UAAuC,CAAC,GAAG;AACrD,SAAK,UAAU,QAAQ,QAAQ,WAAW,QAAQ,IAAI,CAAC;AACvD,SAAK,MAAM,QAAQ,OAAO,KAAK;AAC/B,SAAK,MAAM,QAAQ,OAAO,CAAC;AAC3B,SAAK,UAAU,QAAQ,WAAW;AAClC,SAAK,kBAAkB,QAAQ;AAAA,EACjC;AAAA,EAEQ,YAAY,MAAsB;AAExC,UAAM,WAAW,QAAQ,KAAK,SAAS,IAAI;AAC3C,QAAI,CAAC,SAAS,WAAW,KAAK,OAAO,GAAG;AACtC,YAAM,IAAI,MAAM,SAAS,IAAI,iCAAiC;AAAA,IAChE;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,SAAS,MAA+B;AAC5C,UAAM,WAAW,KAAK,YAAY,IAAI;AACtC,WAAO,SAAS,UAAU,OAAO;AAAA,EACnC;AAAA;AAAA,EAGA,MAAM,UAAU,MAAc,SAAgC;AAC5D,UAAM,WAAW,KAAK,YAAY,IAAI;AAEtC,UAAM,MAAM,SAAS,UAAU,GAAG,SAAS,YAAY,GAAG,CAAC;AAC3D,UAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AACpC,UAAM,UAAU,UAAU,SAAS,OAAO;AAAA,EAC5C;AAAA;AAAA,EAGA,MAAM,WAAW,MAA6B;AAC5C,UAAM,WAAW,KAAK,YAAY,IAAI;AACtC,UAAM,OAAO,QAAQ;AAAA,EACvB;AAAA;AAAA,EAGA,MAAM,UAAU,KAAgC;AAC9C,UAAM,WAAW,KAAK,YAAY,GAAG;AACrC,UAAM,UAAU,MAAM,QAAQ,QAAQ;AACtC,WAAO,QAAQ,IAAI,CAAC,SAAS,KAAK,KAAK,IAAI,CAAC;AAAA,EAC9C;AAAA;AAAA,EAGA,MAAM,MAAM,MAA6B;AACvC,UAAM,WAAW,KAAK,YAAY,IAAI;AACtC,UAAM,MAAM,UAAU,EAAE,WAAW,KAAK,CAAC;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,eACJ,SACA,SAC+D;AAE/D,QAAI,KAAK,iBAAiB;AACxB,YAAM,MAAM,QAAQ,MAAM,GAAG,EAAE,CAAC;AAChC,UAAI,CAAC,KAAK,gBAAgB,SAAS,GAAG,GAAG;AACvC,eAAO,EAAE,UAAU,GAAG,QAAQ,IAAI,QAAQ,YAAY,GAAG,gBAAgB;AAAA,MAC3E;AAAA,IACF;AAEA,UAAM,MAAM,SAAS,MAAM,KAAK,YAAY,QAAQ,GAAG,IAAI,KAAK;AAChE,UAAM,UAAU,SAAS,WAAW,KAAK;AACzC,UAAM,MAAM,EAAE,GAAG,QAAQ,KAAK,GAAG,KAAK,KAAK,GAAG,SAAS,IAAI;AAE3D,WAAO,IAAI,QAAQ,CAAC,mBAAmB;AACrC,YAAM,QAAQ,MAAM,MAAM,CAAC,MAAM,OAAO,GAAG;AAAA,QACzC;AAAA,QACA;AAAA,QACA;AAAA,MACF,CAAC;AAED,UAAI,SAAS;AACb,UAAI,SAAS;AAEb,YAAM,OAAO,GAAG,QAAQ,CAAC,SAAiB;AACxC,cAAM,OAAO,KAAK,SAAS;AAC3B,kBAAU;AACV,iBAAS,WAAW,IAAI;AAAA,MAC1B,CAAC;AAED,YAAM,OAAO,GAAG,QAAQ,CAAC,SAAiB;AACxC,cAAM,OAAO,KAAK,SAAS;AAC3B,kBAAU;AACV,iBAAS,WAAW,IAAI;AAAA,MAC1B,CAAC;AAED,YAAM,GAAG,SAAS,CAAC,SAAS;AAC1B,uBAAe,EAAE,UAAU,QAAQ,GAAG,QAAQ,OAAO,CAAC;AAAA,MACxD,CAAC;AAED,YAAM,GAAG,SAAS,CAAC,QAAQ;AACzB,uBAAe,EAAE,UAAU,GAAG,QAAQ,QAAQ,IAAI,QAAQ,CAAC;AAAA,MAC7D,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,MAAM,SAAwB;AAC5B,UAAM,MAAM,KAAK,SAAS,EAAE,WAAW,KAAK,CAAC;AAAA,EAC/C;AAAA;AAAA,EAGA,MAAM,UAAyB;AAAA,EAE/B;AAAA;AAAA,EAGA,MAAM,QAAuB;AAAA,EAE7B;AACF;;;AC1JA,SAAS,uBAAuB;AAczB,SAAS,oBAAoB,QAA4C;AAE9E,QAAM,kBAAkB,EAAE,GAAG,OAAO;AACpC,MAAI,gBAAgB,MAAM,WAAW,KAAK,gBAAgB,YAAY,SAAS;AAC7E,oBAAgB,QAAQ,gBAAgB;AAAA,EAC1C;AAEA,QAAM,QAAQ,IAAI,cAAc,eAAe;AAG/C,MAAI,OAAO,YAAY,SAAS;AAC9B,UAAM,eAAe,IAAI,qBAAqB;AAC9C,UAAM,WAAW,YAAY;AAAA,EAC/B;AAEA,SAAO;AACT;;;ACjCA,SAAS,uBAAuB;AAWzB,IAAM,iBAAN,MAA+C;AAAA,EAC5C;AAAA,EACA;AAAA;AAAA,EAGR,UAAU,SAA4C;AACpD,SAAK,iBAAiB;AAEtB,SAAK,KAAK,gBAAgB,EAAE,OAAO,QAAQ,MAAM,CAAC;AAElD,SAAK,GAAG,GAAG,QAAQ,CAAC,SAAiB;AACnC,YAAM,UAAU,KAAK,KAAK;AAC1B,UAAI,CAAC,QAAS;AAEd,UAAI;AACF,cAAM,MAAM,KAAK,MAAM,OAAO;AAC9B,aAAK,iBAAiB,GAAG;AAAA,MAC3B,QAAQ;AAEN,aAAK,KAAK;AAAA,UACR,MAAM;AAAA,UACN,OAAO,sBAAsB,OAAO;AAAA,UACpC,MAAM;AAAA,UACN,aAAa;AAAA,QACf,CAAiB;AAAA,MACnB;AAAA,IACF,CAAC;AAGD,UAAM,kBAAkB,MAAM;AAC5B,WAAK,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAAA,IAC5C;AACA,YAAQ,GAAG,WAAW,eAAe;AACrC,YAAQ,GAAG,UAAU,eAAe;AAAA,EACtC;AAAA;AAAA,EAGA,KAAK,OAAyC;AAC5C,YAAQ,OAAO,MAAM,KAAK,UAAU,KAAK,IAAI,IAAI;AAAA,EACnD;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC3B,SAAK,IAAI,MAAM;AAAA,EACjB;AACF;;;ACxDA,SAAS,oBAA4E;AAoB9E,IAAM,gBAAN,MAA8C;AAAA,EAC3C;AAAA,EACA;AAAA,EACA,aAAkC,oBAAI,IAAI;AAAA,EAC1C,gBAA6B;AAAA,IACnC,OAAO;AAAA,IACP,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,gBAAgB;AAAA,EAClB;AAAA,EACQ,YAAY,KAAK,IAAI;AAAA,EACrB;AAAA,EACA;AAAA,EAER,YAAY,UAAgC,CAAC,GAAG;AAC9C,SAAK,OAAO,QAAQ,QAAQ;AAC5B,SAAK,OAAO,QAAQ,QAAQ;AAG5B,SAAK,SAAS,aAAa,CAAC,KAAK,QAAQ,KAAK,cAAc,KAAK,GAAG,CAAC;AAAA,EACvE;AAAA,EAEA,MAAc,cAAc,KAAsB,KAAoC;AACpF,UAAM,MAAM,IAAI,OAAO;AAGvB,QAAI,IAAI,WAAW,UAAU,QAAQ,YAAY;AAC/C,YAAM,SAAmB,CAAC;AAC1B,uBAAiB,SAAS,KAAK;AAC7B,eAAO,KAAK,KAAe;AAAA,MAC7B;AACA,UAAI;AACF,cAAM,OAAO,KAAK,MAAM,OAAO,OAAO,MAAM,EAAE,SAAS,CAAC;AACxD,aAAK,iBAAiB,IAAoB;AAC1C,YAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,YAAI,IAAI,KAAK,UAAU,EAAE,IAAI,KAAK,CAAC,CAAC;AAAA,MACtC,QAAQ;AACN,YAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,YAAI,IAAI,KAAK,UAAU,EAAE,OAAO,eAAe,CAAC,CAAC;AAAA,MACnD;AACA;AAAA,IACF;AAGA,QAAI,IAAI,WAAW,SAAS,QAAQ,WAAW;AAC7C,UAAI,UAAU,KAAK;AAAA,QACjB,gBAAgB;AAAA,QAChB,iBAAiB;AAAA,QACjB,YAAY;AAAA,MACd,CAAC;AACD,WAAK,WAAW,IAAI,GAAG;AAEvB,UAAI,GAAG,SAAS,MAAM;AACpB,aAAK,WAAW,OAAO,GAAG;AAAA,MAC5B,CAAC;AACD;AAAA,IACF;AAGA,QAAI,IAAI,WAAW,SAAS,QAAQ,WAAW;AAC7C,WAAK,cAAc,SAAS,KAAK,OAAO,KAAK,IAAI,IAAI,KAAK,aAAa,GAAI;AAC3E,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,KAAK,UAAU,KAAK,aAAa,CAAC;AAC1C;AAAA,IACF;AAEA,QAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,QAAI,IAAI,KAAK,UAAU,EAAE,OAAO,YAAY,CAAC,CAAC;AAAA,EAChD;AAAA;AAAA,EAGA,UAAU,SAA4C;AACpD,SAAK,iBAAiB;AACtB,SAAK,OAAO,OAAO,KAAK,MAAM,KAAK,MAAM,MAAM;AAAA,IAE/C,CAAC;AAAA,EACH;AAAA;AAAA,EAGA,KAAK,OAAyC;AAE5C,QAAI,WAAW,SAAS,gBAAgB,OAAO;AAC7C,WAAK,gBAAgB;AAAA,IACvB;AAGA,UAAM,OAAO,KAAK,UAAU,KAAK;AACjC,eAAW,UAAU,KAAK,YAAY;AACpC,aAAO,MAAM,SAAS,IAAI;AAAA;AAAA,CAAM;AAAA,IAClC;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,QAAuB;AAE3B,eAAW,UAAU,KAAK,YAAY;AACpC,aAAO,IAAI;AAAA,IACb;AACA,SAAK,WAAW,MAAM;AAGtB,WAAO,IAAI,QAAQ,CAACA,aAAY;AAC9B,WAAK,OAAO,MAAM,MAAMA,SAAQ,CAAC;AAAA,IACnC,CAAC;AAAA,EACH;AACF;;;AC7HA,SAAS,mBAAAC,wBAAuC;AAIhD,IAAM,QAAQ;AACd,IAAM,MAAM;AACZ,IAAM,SAAS;AACf,IAAM,OAAO;AACb,IAAM,QAAQ;AACd,IAAM,MAAM;AAEZ,IAAM,OAAO;AAWN,IAAM,uBAAN,MAAqD;AAAA,EAClD;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EAER,YAAY,SAAS,MAAM;AACzB,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,UAAU,SAA4C;AACpD,SAAK,iBAAiB;AAEtB,SAAK,KAAKC,iBAAgB;AAAA,MACxB,OAAO,QAAQ;AAAA,MACf,QAAQ,QAAQ;AAAA,MAChB,QAAQ,KAAK;AAAA,IACf,CAAC;AAED,SAAK,GAAG,GAAG,QAAQ,CAAC,SAAiB;AACnC,YAAM,UAAU,KAAK,KAAK;AAC1B,UAAI,CAAC,SAAS;AACZ,aAAK,WAAW;AAChB;AAAA,MACF;AACA,WAAK,iBAAiB,EAAE,MAAM,QAAQ,QAAQ,QAAQ,CAAC;AAAA,IACzD,CAAC;AAED,SAAK,GAAG,GAAG,SAAS,MAAM;AACxB,WAAK,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAAA,IAC5C,CAAC;AAGD,YAAQ,GAAG,UAAU,MAAM;AACzB,UAAI,KAAK,WAAW;AAClB,gBAAQ,OAAO,MAAM,IAAI;AACzB,aAAK,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAAA,MAC7C,OAAO;AACL,gBAAQ,OAAO,MAAM,UAAU;AAC/B,aAAK,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAAA,MAC5C;AAAA,IACF,CAAC;AAED,SAAK,WAAW;AAAA,EAClB;AAAA,EAEA,KAAK,OAAyC;AAE5C,QAAI,WAAW,SAAS,gBAAgB,OAAO;AAC7C,WAAK,YAAY,MAAM,UAAU;AACjC,UAAI,MAAM,UAAU,UAAU,CAAC,KAAK,WAAW;AAC7C,aAAK,WAAW;AAAA,MAClB;AACA;AAAA,IACF;AAEA,UAAM,eAAe;AAErB,YAAQ,aAAa,MAAM;AAAA,MACzB,KAAK;AAEH,gBAAQ,OAAO,MAAM,aAAa,IAAI;AACtC;AAAA,MAEF,KAAK;AAEH,gBAAQ,OAAO,MAAM,GAAG,GAAG,GAAG,MAAM,GAAG,aAAa,IAAI,GAAG,KAAK,EAAE;AAClE;AAAA,MAEF,KAAK;AAEH,gBAAQ,OAAO;AAAA,UACb;AAAA,EAAK,IAAI,GAAG,IAAI,SAAS,KAAK,IAAI,aAAa,IAAI,IAAI,YAAY,aAAa,KAAK,CAAC;AAAA;AAAA,QACxF;AACA;AAAA,MAEF,KAAK;AACH,YAAI,aAAa,SAAS;AACxB,gBAAM,SAAS,OAAO,aAAa,WAAW,WAC1C,aAAa,SACb,KAAK,UAAU,aAAa,MAAM;AACtC,gBAAM,UAAU,UAAU,OAAO,SAAS,MAAM,OAAO,MAAM,GAAG,GAAG,IAAI,QAAQ;AAC/E,kBAAQ,OAAO,MAAM,GAAG,KAAK,OAAO,KAAK,IAAI,WAAW,aAAa;AAAA,CAAI;AAAA,QAC3E,OAAO;AACL,kBAAQ,OAAO,MAAM,GAAG,GAAG,UAAU,KAAK,IAAI,aAAa,SAAS,eAAe;AAAA,CAAI;AAAA,QACzF;AACA;AAAA,MAEF,KAAK;AACH,gBAAQ,OAAO,MAAM;AAAA,EAAK,GAAG,GAAG,IAAI,SAAS,KAAK,IAAI,aAAa,KAAK;AAAA,CAAI;AAC5E;AAAA,MAEF,KAAK;AACH,aAAK,YAAY;AACjB;AAAA,MAEF,KAAK;AACH,aAAK,YAAY;AACjB,gBAAQ,OAAO,MAAM,IAAI;AACzB;AAAA;AAAA,MAGF,KAAK;AAAA,MACL,KAAK;AAAA,MACL,KAAK;AACH;AAAA,IACJ;AAAA,EACF;AAAA,EAEA,MAAM,QAAuB;AAC3B,SAAK,IAAI,MAAM;AAAA,EACjB;AAAA,EAEQ,aAAmB;AACzB,SAAK,IAAI,OAAO;AAAA,EAClB;AACF;AAEA,SAAS,YAAY,OAAwC;AAC3D,QAAM,UAAU,OAAO,QAAQ,KAAK;AACpC,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,QAAM,QAAQ,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM;AACpC,UAAM,MAAM,OAAO,MAAM,WAAY,EAAE,SAAS,KAAK,EAAE,MAAM,GAAG,EAAE,IAAI,QAAQ,IAAK,KAAK,UAAU,CAAC;AACnG,WAAO,GAAG,CAAC,IAAI,GAAG;AAAA,EACpB,CAAC;AACD,SAAO,MAAM,KAAK,IAAI;AACxB;","names":["resolve","createInterface","createInterface"]}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
HttpTransport,
|
|
4
|
+
InteractiveTransport,
|
|
5
|
+
StdioTransport,
|
|
6
|
+
createHeadlessAgent
|
|
7
|
+
} from "./chunk-OBRMNJP2.js";
|
|
8
|
+
|
|
9
|
+
// src/cli.ts
|
|
10
|
+
import { readFile } from "fs/promises";
|
|
11
|
+
import { resolve } from "path";
|
|
12
|
+
import { parse as parseYaml } from "yaml";
|
|
13
|
+
import { getDefaultTools } from "@yolo-labs/core-tools";
|
|
14
|
+
function parseArgs() {
|
|
15
|
+
const args = process.argv.slice(2);
|
|
16
|
+
const result = {
|
|
17
|
+
config: void 0,
|
|
18
|
+
// Task 110: Default to interactive when stdin is a TTY, stdio otherwise
|
|
19
|
+
transport: process.stdin.isTTY ? "interactive" : "stdio",
|
|
20
|
+
port: 8080,
|
|
21
|
+
task: void 0
|
|
22
|
+
};
|
|
23
|
+
for (let i = 0; i < args.length; i++) {
|
|
24
|
+
switch (args[i]) {
|
|
25
|
+
case "--config":
|
|
26
|
+
result.config = args[++i];
|
|
27
|
+
break;
|
|
28
|
+
case "--transport":
|
|
29
|
+
result.transport = args[++i];
|
|
30
|
+
break;
|
|
31
|
+
case "--port":
|
|
32
|
+
result.port = parseInt(args[++i], 10);
|
|
33
|
+
break;
|
|
34
|
+
case "--task":
|
|
35
|
+
result.task = args[++i];
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
async function loadConfig(configPath) {
|
|
42
|
+
const content = await readFile(resolve(configPath), "utf-8");
|
|
43
|
+
const parsed = configPath.endsWith(".json") ? JSON.parse(content) : parseYaml(content);
|
|
44
|
+
return substituteEnvVars(parsed);
|
|
45
|
+
}
|
|
46
|
+
function substituteEnvVars(obj) {
|
|
47
|
+
if (typeof obj === "string") {
|
|
48
|
+
return obj.replace(/\$([A-Z_][A-Z0-9_]*)/g, (_, name) => process.env[name] ?? "");
|
|
49
|
+
}
|
|
50
|
+
if (Array.isArray(obj)) {
|
|
51
|
+
return obj.map(substituteEnvVars);
|
|
52
|
+
}
|
|
53
|
+
if (obj && typeof obj === "object") {
|
|
54
|
+
const result = {};
|
|
55
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
56
|
+
result[key] = substituteEnvVars(value);
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
return obj;
|
|
61
|
+
}
|
|
62
|
+
async function resolveProvider(config) {
|
|
63
|
+
const providerConfig = config.provider;
|
|
64
|
+
const type = providerConfig?.type;
|
|
65
|
+
switch (type) {
|
|
66
|
+
case "anthropic": {
|
|
67
|
+
const { AnthropicProvider } = await import("@yolo-labs/core-providers/anthropic");
|
|
68
|
+
return new AnthropicProvider({
|
|
69
|
+
apiKey: providerConfig.apiKey,
|
|
70
|
+
defaultModel: providerConfig.model
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
case "openai": {
|
|
74
|
+
const { OpenAIProvider } = await import("@yolo-labs/core-providers/openai");
|
|
75
|
+
return new OpenAIProvider({
|
|
76
|
+
apiKey: providerConfig.apiKey,
|
|
77
|
+
defaultModel: providerConfig.model
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
case "google": {
|
|
81
|
+
const { GoogleProvider } = await import("@yolo-labs/core-providers/google");
|
|
82
|
+
return new GoogleProvider({
|
|
83
|
+
apiKey: providerConfig.apiKey,
|
|
84
|
+
defaultModel: providerConfig.model
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
default:
|
|
88
|
+
throw new Error(`Unknown provider type: ${type}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function resolveTools(config) {
|
|
92
|
+
const toolsConfig = config.tools;
|
|
93
|
+
if (toolsConfig === "default") {
|
|
94
|
+
return getDefaultTools();
|
|
95
|
+
}
|
|
96
|
+
if (Array.isArray(toolsConfig)) {
|
|
97
|
+
return toolsConfig;
|
|
98
|
+
}
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
async function main() {
|
|
102
|
+
const args = parseArgs();
|
|
103
|
+
if (!args.config) {
|
|
104
|
+
console.error("Usage: yolo-agent --config <path> [--transport stdio|http] [--port <n>] [--task <string>]");
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
const rawConfig = await loadConfig(args.config);
|
|
108
|
+
const provider = await resolveProvider(rawConfig);
|
|
109
|
+
const tools = resolveTools(rawConfig);
|
|
110
|
+
const agentConfig = {
|
|
111
|
+
provider,
|
|
112
|
+
tools,
|
|
113
|
+
prompts: {
|
|
114
|
+
system: rawConfig.system ?? void 0
|
|
115
|
+
},
|
|
116
|
+
sandbox: "local",
|
|
117
|
+
initialTask: args.task ?? rawConfig.initialTask,
|
|
118
|
+
idlePolicy: rawConfig.idlePolicy ?? "wait"
|
|
119
|
+
};
|
|
120
|
+
const agent = createHeadlessAgent(agentConfig);
|
|
121
|
+
let transport;
|
|
122
|
+
if (args.transport === "http") {
|
|
123
|
+
transport = new HttpTransport({ port: args.port });
|
|
124
|
+
console.error(`Starting HTTP transport on port ${args.port}`);
|
|
125
|
+
} else if (args.transport === "interactive") {
|
|
126
|
+
transport = new InteractiveTransport();
|
|
127
|
+
console.error("Starting interactive transport");
|
|
128
|
+
} else {
|
|
129
|
+
transport = new StdioTransport();
|
|
130
|
+
console.error("Starting stdio transport");
|
|
131
|
+
}
|
|
132
|
+
await agent.start(transport);
|
|
133
|
+
const shutdown = async () => {
|
|
134
|
+
console.error("Shutting down...");
|
|
135
|
+
await agent.stop();
|
|
136
|
+
process.exit(0);
|
|
137
|
+
};
|
|
138
|
+
process.on("SIGTERM", shutdown);
|
|
139
|
+
process.on("SIGINT", shutdown);
|
|
140
|
+
}
|
|
141
|
+
main().catch((err) => {
|
|
142
|
+
console.error("Fatal error:", err);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
});
|
|
145
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n// Task 82: CLI entry point — boot a headless agent from config file\n\nimport { readFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport { createHeadlessAgent } from './factory.js';\nimport { StdioTransport } from './stdio-transport.js';\nimport { HttpTransport } from './http-transport.js';\nimport { InteractiveTransport } from './interactive-transport.js';\nimport { getDefaultTools } from '@yolo-labs/core-tools';\nimport type { HeadlessAgentConfig, Tool, AIProvider } from '@yolo-labs/core-types';\n\n// 82a: Parse CLI args\nfunction parseArgs(): {\n config?: string;\n transport: 'stdio' | 'http' | 'interactive';\n port: number;\n task?: string;\n} {\n const args = process.argv.slice(2);\n const result = {\n config: undefined as string | undefined,\n // Task 110: Default to interactive when stdin is a TTY, stdio otherwise\n transport: (process.stdin.isTTY ? 'interactive' : 'stdio') as 'stdio' | 'http' | 'interactive',\n port: 8080,\n task: undefined as string | undefined,\n };\n\n for (let i = 0; i < args.length; i++) {\n switch (args[i]) {\n case '--config':\n result.config = args[++i];\n break;\n case '--transport':\n result.transport = args[++i] as 'stdio' | 'http' | 'interactive';\n break;\n case '--port':\n result.port = parseInt(args[++i], 10);\n break;\n case '--task':\n result.task = args[++i];\n break;\n }\n }\n\n return result;\n}\n\n// 82b: Load config file with env var substitution\nasync function loadConfig(configPath: string): Promise<Record<string, unknown>> {\n const content = await readFile(resolve(configPath), 'utf-8');\n const parsed = configPath.endsWith('.json') ? JSON.parse(content) : parseYaml(content);\n\n // Substitute $ENV_VAR patterns\n return substituteEnvVars(parsed);\n}\n\nfunction substituteEnvVars(obj: unknown): Record<string, unknown> {\n if (typeof obj === 'string') {\n return obj.replace(/\\$([A-Z_][A-Z0-9_]*)/g, (_, name) => process.env[name] ?? '') as unknown as Record<string, unknown>;\n }\n if (Array.isArray(obj)) {\n return obj.map(substituteEnvVars) as unknown as Record<string, unknown>;\n }\n if (obj && typeof obj === 'object') {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n result[key] = substituteEnvVars(value);\n }\n return result;\n }\n return obj as Record<string, unknown>;\n}\n\n// 82b: Resolve provider from config\nasync function resolveProvider(config: Record<string, unknown>): Promise<AIProvider> {\n const providerConfig = config.provider as Record<string, unknown>;\n const type = providerConfig?.type as string;\n\n switch (type) {\n case 'anthropic': {\n const { AnthropicProvider } = await import('@yolo-labs/core-providers/anthropic');\n return new AnthropicProvider({\n apiKey: providerConfig.apiKey as string,\n defaultModel: providerConfig.model as string,\n });\n }\n case 'openai': {\n const { OpenAIProvider } = await import('@yolo-labs/core-providers/openai');\n return new OpenAIProvider({\n apiKey: providerConfig.apiKey as string,\n defaultModel: providerConfig.model as string,\n });\n }\n case 'google': {\n const { GoogleProvider } = await import('@yolo-labs/core-providers/google');\n return new GoogleProvider({\n apiKey: providerConfig.apiKey as string,\n defaultModel: providerConfig.model as string,\n });\n }\n default:\n throw new Error(`Unknown provider type: ${type}`);\n }\n}\n\n// Task 107: Resolve tools from config\nfunction resolveTools(config: Record<string, unknown>): Tool[] {\n const toolsConfig = config.tools;\n\n // tools: 'default' → load all 5 reference tools\n if (toolsConfig === 'default') {\n return getDefaultTools();\n }\n\n // tools: [...] → custom tool definitions (future use)\n if (Array.isArray(toolsConfig)) {\n return toolsConfig as Tool[];\n }\n\n // No tools config → empty (factory will auto-register defaults for local sandbox)\n return [];\n}\n\n// Main\nasync function main() {\n const args = parseArgs();\n\n if (!args.config) {\n console.error('Usage: yolo-agent --config <path> [--transport stdio|http] [--port <n>] [--task <string>]');\n process.exit(1);\n }\n\n const rawConfig = await loadConfig(args.config);\n const provider = await resolveProvider(rawConfig);\n\n // Task 107: Load tool configuration from config file\n const tools = resolveTools(rawConfig);\n\n const agentConfig: HeadlessAgentConfig = {\n provider,\n tools,\n prompts: {\n system: (rawConfig.system as string) ?? undefined,\n },\n sandbox: 'local',\n initialTask: args.task ?? (rawConfig.initialTask as string),\n idlePolicy: (rawConfig.idlePolicy as 'wait' | 'shutdown') ?? 'wait',\n };\n\n // 82c: Instantiate agent and start with selected transport\n const agent = createHeadlessAgent(agentConfig);\n\n let transport;\n if (args.transport === 'http') {\n transport = new HttpTransport({ port: args.port });\n console.error(`Starting HTTP transport on port ${args.port}`);\n } else if (args.transport === 'interactive') {\n transport = new InteractiveTransport();\n console.error('Starting interactive transport');\n } else {\n transport = new StdioTransport();\n console.error('Starting stdio transport');\n }\n\n await agent.start(transport);\n\n // 82d: Graceful shutdown on SIGTERM/SIGINT\n const shutdown = async () => {\n console.error('Shutting down...');\n await agent.stop();\n process.exit(0);\n };\n\n process.on('SIGTERM', shutdown);\n process.on('SIGINT', shutdown);\n}\n\nmain().catch((err) => {\n console.error('Fatal error:', err);\n process.exit(1);\n});\n"],"mappings":";;;;;;;;;AAGA,SAAS,gBAAgB;AACzB,SAAS,eAAe;AACxB,SAAS,SAAS,iBAAiB;AAKnC,SAAS,uBAAuB;AAIhC,SAAS,YAKP;AACA,QAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AACjC,QAAM,SAAS;AAAA,IACb,QAAQ;AAAA;AAAA,IAER,WAAY,QAAQ,MAAM,QAAQ,gBAAgB;AAAA,IAClD,MAAM;AAAA,IACN,MAAM;AAAA,EACR;AAEA,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,YAAQ,KAAK,CAAC,GAAG;AAAA,MACf,KAAK;AACH,eAAO,SAAS,KAAK,EAAE,CAAC;AACxB;AAAA,MACF,KAAK;AACH,eAAO,YAAY,KAAK,EAAE,CAAC;AAC3B;AAAA,MACF,KAAK;AACH,eAAO,OAAO,SAAS,KAAK,EAAE,CAAC,GAAG,EAAE;AACpC;AAAA,MACF,KAAK;AACH,eAAO,OAAO,KAAK,EAAE,CAAC;AACtB;AAAA,IACJ;AAAA,EACF;AAEA,SAAO;AACT;AAGA,eAAe,WAAW,YAAsD;AAC9E,QAAM,UAAU,MAAM,SAAS,QAAQ,UAAU,GAAG,OAAO;AAC3D,QAAM,SAAS,WAAW,SAAS,OAAO,IAAI,KAAK,MAAM,OAAO,IAAI,UAAU,OAAO;AAGrF,SAAO,kBAAkB,MAAM;AACjC;AAEA,SAAS,kBAAkB,KAAuC;AAChE,MAAI,OAAO,QAAQ,UAAU;AAC3B,WAAO,IAAI,QAAQ,yBAAyB,CAAC,GAAG,SAAS,QAAQ,IAAI,IAAI,KAAK,EAAE;AAAA,EAClF;AACA,MAAI,MAAM,QAAQ,GAAG,GAAG;AACtB,WAAO,IAAI,IAAI,iBAAiB;AAAA,EAClC;AACA,MAAI,OAAO,OAAO,QAAQ,UAAU;AAClC,UAAM,SAAkC,CAAC;AACzC,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,GAA8B,GAAG;AACzE,aAAO,GAAG,IAAI,kBAAkB,KAAK;AAAA,IACvC;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGA,eAAe,gBAAgB,QAAsD;AACnF,QAAM,iBAAiB,OAAO;AAC9B,QAAM,OAAO,gBAAgB;AAE7B,UAAQ,MAAM;AAAA,IACZ,KAAK,aAAa;AAChB,YAAM,EAAE,kBAAkB,IAAI,MAAM,OAAO,qCAAqC;AAChF,aAAO,IAAI,kBAAkB;AAAA,QAC3B,QAAQ,eAAe;AAAA,QACvB,cAAc,eAAe;AAAA,MAC/B,CAAC;AAAA,IACH;AAAA,IACA,KAAK,UAAU;AACb,YAAM,EAAE,eAAe,IAAI,MAAM,OAAO,kCAAkC;AAC1E,aAAO,IAAI,eAAe;AAAA,QACxB,QAAQ,eAAe;AAAA,QACvB,cAAc,eAAe;AAAA,MAC/B,CAAC;AAAA,IACH;AAAA,IACA,KAAK,UAAU;AACb,YAAM,EAAE,eAAe,IAAI,MAAM,OAAO,kCAAkC;AAC1E,aAAO,IAAI,eAAe;AAAA,QACxB,QAAQ,eAAe;AAAA,QACvB,cAAc,eAAe;AAAA,MAC/B,CAAC;AAAA,IACH;AAAA,IACA;AACE,YAAM,IAAI,MAAM,0BAA0B,IAAI,EAAE;AAAA,EACpD;AACF;AAGA,SAAS,aAAa,QAAyC;AAC7D,QAAM,cAAc,OAAO;AAG3B,MAAI,gBAAgB,WAAW;AAC7B,WAAO,gBAAgB;AAAA,EACzB;AAGA,MAAI,MAAM,QAAQ,WAAW,GAAG;AAC9B,WAAO;AAAA,EACT;AAGA,SAAO,CAAC;AACV;AAGA,eAAe,OAAO;AACpB,QAAM,OAAO,UAAU;AAEvB,MAAI,CAAC,KAAK,QAAQ;AAChB,YAAQ,MAAM,2FAA2F;AACzG,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,YAAY,MAAM,WAAW,KAAK,MAAM;AAC9C,QAAM,WAAW,MAAM,gBAAgB,SAAS;AAGhD,QAAM,QAAQ,aAAa,SAAS;AAEpC,QAAM,cAAmC;AAAA,IACvC;AAAA,IACA;AAAA,IACA,SAAS;AAAA,MACP,QAAS,UAAU,UAAqB;AAAA,IAC1C;AAAA,IACA,SAAS;AAAA,IACT,aAAa,KAAK,QAAS,UAAU;AAAA,IACrC,YAAa,UAAU,cAAsC;AAAA,EAC/D;AAGA,QAAM,QAAQ,oBAAoB,WAAW;AAE7C,MAAI;AACJ,MAAI,KAAK,cAAc,QAAQ;AAC7B,gBAAY,IAAI,cAAc,EAAE,MAAM,KAAK,KAAK,CAAC;AACjD,YAAQ,MAAM,mCAAmC,KAAK,IAAI,EAAE;AAAA,EAC9D,WAAW,KAAK,cAAc,eAAe;AAC3C,gBAAY,IAAI,qBAAqB;AACrC,YAAQ,MAAM,gCAAgC;AAAA,EAChD,OAAO;AACL,gBAAY,IAAI,eAAe;AAC/B,YAAQ,MAAM,0BAA0B;AAAA,EAC1C;AAEA,QAAM,MAAM,MAAM,SAAS;AAG3B,QAAM,WAAW,YAAY;AAC3B,YAAQ,MAAM,kBAAkB;AAChC,UAAM,MAAM,KAAK;AACjB,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,UAAQ,GAAG,WAAW,QAAQ;AAC9B,UAAQ,GAAG,UAAU,QAAQ;AAC/B;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,UAAQ,MAAM,gBAAgB,GAAG;AACjC,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { HeadlessAgentConfig, SandboxProvider, AgentTransport, AgentCommand, SessionEvent, AgentStatus, CommandExecutionOptions } from '@yolo-labs/core-types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Self-contained agent that processes tasks via a pluggable transport.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* Manages a task queue, executes tasks through {@link agentLoop}, and routes
|
|
8
|
+
* all session events through the bound {@link AgentTransport}. Supports
|
|
9
|
+
* commands: task submission, interrupt, shutdown, abort, context injection,
|
|
10
|
+
* and status queries. Use {@link createHeadlessAgent} factory to instantiate.
|
|
11
|
+
*/
|
|
12
|
+
declare class HeadlessAgent {
|
|
13
|
+
private config;
|
|
14
|
+
private transport?;
|
|
15
|
+
private provider;
|
|
16
|
+
private toolRegistry;
|
|
17
|
+
private sandbox;
|
|
18
|
+
private taskQueue;
|
|
19
|
+
private isProcessing;
|
|
20
|
+
private isShuttingDown;
|
|
21
|
+
private startTime;
|
|
22
|
+
private tasksCompleted;
|
|
23
|
+
private currentTaskPrompt?;
|
|
24
|
+
private injectedContext;
|
|
25
|
+
private conversationHistory;
|
|
26
|
+
private sessionManager?;
|
|
27
|
+
private sessionId?;
|
|
28
|
+
private currentAbortController?;
|
|
29
|
+
constructor(config: HeadlessAgentConfig);
|
|
30
|
+
/** Replace the sandbox provider (used by the factory when sandbox is 'local'). */
|
|
31
|
+
setSandbox(sandbox: SandboxProvider): void;
|
|
32
|
+
/**
|
|
33
|
+
* Binds a transport, begins listening for commands, and runs the initial task if configured.
|
|
34
|
+
*
|
|
35
|
+
* @param transport - The transport to use for command input and event output.
|
|
36
|
+
*/
|
|
37
|
+
start(transport: AgentTransport): Promise<void>;
|
|
38
|
+
private enqueueTask;
|
|
39
|
+
private processQueue;
|
|
40
|
+
private handleCommand;
|
|
41
|
+
private emitStatus;
|
|
42
|
+
/**
|
|
43
|
+
* Resets the conversation history, starting fresh for subsequent tasks.
|
|
44
|
+
*
|
|
45
|
+
* @remarks
|
|
46
|
+
* Can be triggered programmatically or via a `context_clear` command
|
|
47
|
+
* through the transport. If a session store is configured, the stored
|
|
48
|
+
* history is also cleared.
|
|
49
|
+
*/
|
|
50
|
+
clearHistory(): void;
|
|
51
|
+
/**
|
|
52
|
+
* Gracefully shuts down the agent, clears the task queue, and closes the transport.
|
|
53
|
+
*
|
|
54
|
+
* @remarks
|
|
55
|
+
* Called automatically when the idle policy is `'shutdown'` and the queue empties,
|
|
56
|
+
* or when an `abort` command is received.
|
|
57
|
+
*/
|
|
58
|
+
stop(): Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Factory function that creates and configures a {@link HeadlessAgent}.
|
|
63
|
+
*
|
|
64
|
+
* @param config - The headless agent configuration.
|
|
65
|
+
* @returns A configured {@link HeadlessAgent} ready to be started with a transport.
|
|
66
|
+
*
|
|
67
|
+
* @remarks
|
|
68
|
+
* When `config.sandbox` is `'local'`, a {@link LocalSandboxProvider} is
|
|
69
|
+
* automatically created and injected. When `config.tools` is empty and
|
|
70
|
+
* `config.sandbox` is `'local'`, default tools are auto-registered.
|
|
71
|
+
*/
|
|
72
|
+
declare function createHeadlessAgent(config: HeadlessAgentConfig): HeadlessAgent;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* {@link AgentTransport} using newline-delimited JSON over stdin/stdout.
|
|
76
|
+
*
|
|
77
|
+
* @remarks
|
|
78
|
+
* Reads one JSON command per line from stdin and writes one JSON event per
|
|
79
|
+
* line to stdout. SIGTERM and SIGINT are translated into `shutdown` commands.
|
|
80
|
+
* Malformed input lines emit an `error` event rather than crashing.
|
|
81
|
+
*/
|
|
82
|
+
declare class StdioTransport implements AgentTransport {
|
|
83
|
+
private commandHandler?;
|
|
84
|
+
private rl?;
|
|
85
|
+
/** Start reading stdin line-by-line, parsing each line as a JSON AgentCommand. */
|
|
86
|
+
onCommand(handler: (cmd: AgentCommand) => void): void;
|
|
87
|
+
/** Serialize an event or status as a JSON line and write it to stdout. */
|
|
88
|
+
emit(event: SessionEvent | AgentStatus): void;
|
|
89
|
+
/** Close the readline interface and stop reading from stdin. */
|
|
90
|
+
close(): Promise<void>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Minimal interface for any object that supports postMessage-based communication. */
|
|
94
|
+
interface MessagePortLike {
|
|
95
|
+
postMessage(data: unknown): void;
|
|
96
|
+
on?(event: 'message', handler: (data: unknown) => void): void;
|
|
97
|
+
addEventListener?(event: 'message', handler: (event: {
|
|
98
|
+
data: unknown;
|
|
99
|
+
}) => void): void;
|
|
100
|
+
removeEventListener?(event: 'message', handler: (event: {
|
|
101
|
+
data: unknown;
|
|
102
|
+
}) => void): void;
|
|
103
|
+
close?(): void;
|
|
104
|
+
terminate?(): void;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* {@link AgentTransport} using `postMessage` for Web Workers, worker_threads, and WebContainers.
|
|
108
|
+
*
|
|
109
|
+
* @remarks
|
|
110
|
+
* Accepts any object matching {@link MessagePortLike} (Node.js `MessagePort`,
|
|
111
|
+
* Web API `MessagePort`, or a `Worker` instance). Automatically detects
|
|
112
|
+
* the messaging API (Node.js `on('message')` vs Web `addEventListener`).
|
|
113
|
+
*/
|
|
114
|
+
declare class MessagePortTransport implements AgentTransport {
|
|
115
|
+
private port;
|
|
116
|
+
private messageHandler?;
|
|
117
|
+
constructor(port: MessagePortLike);
|
|
118
|
+
/** Subscribe to incoming messages on the port, dispatching each as an AgentCommand. */
|
|
119
|
+
onCommand(handler: (cmd: AgentCommand) => void): void;
|
|
120
|
+
/** Send an event or status to the other end of the port via postMessage. */
|
|
121
|
+
emit(event: SessionEvent | AgentStatus): void;
|
|
122
|
+
/** Detach the message listener and close or terminate the port. */
|
|
123
|
+
close(): Promise<void>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Configuration options for the HTTP transport server. */
|
|
127
|
+
interface HttpTransportOptions {
|
|
128
|
+
port?: number;
|
|
129
|
+
host?: string;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* {@link AgentTransport} exposing a minimal HTTP server.
|
|
133
|
+
*
|
|
134
|
+
* @remarks
|
|
135
|
+
* Endpoints:
|
|
136
|
+
* - `POST /command` — accepts an {@link AgentCommand} as a JSON body
|
|
137
|
+
* - `GET /events` — SSE stream of {@link SessionEvent} and {@link AgentStatus}
|
|
138
|
+
* - `GET /status` — current {@link AgentStatus} as JSON
|
|
139
|
+
*
|
|
140
|
+
* Built on Node.js `http` module with no framework dependencies.
|
|
141
|
+
*/
|
|
142
|
+
declare class HttpTransport implements AgentTransport {
|
|
143
|
+
private server;
|
|
144
|
+
private commandHandler?;
|
|
145
|
+
private sseClients;
|
|
146
|
+
private currentStatus;
|
|
147
|
+
private startTime;
|
|
148
|
+
private port;
|
|
149
|
+
private host;
|
|
150
|
+
constructor(options?: HttpTransportOptions);
|
|
151
|
+
private handleRequest;
|
|
152
|
+
/** Register the command handler and start the HTTP server. */
|
|
153
|
+
onCommand(handler: (cmd: AgentCommand) => void): void;
|
|
154
|
+
/** Broadcast an event or status update to all connected SSE clients. */
|
|
155
|
+
emit(event: SessionEvent | AgentStatus): void;
|
|
156
|
+
/** Close all SSE connections and shut down the HTTP server. */
|
|
157
|
+
close(): Promise<void>;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* {@link AgentTransport} for interactive terminal sessions.
|
|
162
|
+
*
|
|
163
|
+
* @remarks
|
|
164
|
+
* Renders agent events as formatted terminal output: streaming text to stdout,
|
|
165
|
+
* dimmed/italic thinking text, tool call/result summaries, and error messages.
|
|
166
|
+
* User input is read line-by-line from stdin and submitted as `task` commands.
|
|
167
|
+
* Ctrl+C sends `interrupt` while working, `shutdown` while idle.
|
|
168
|
+
*/
|
|
169
|
+
declare class InteractiveTransport implements AgentTransport {
|
|
170
|
+
private commandHandler?;
|
|
171
|
+
private rl?;
|
|
172
|
+
private isWorking;
|
|
173
|
+
private prompt;
|
|
174
|
+
constructor(prompt?: string);
|
|
175
|
+
onCommand(handler: (cmd: AgentCommand) => void): void;
|
|
176
|
+
emit(event: SessionEvent | AgentStatus): void;
|
|
177
|
+
close(): Promise<void>;
|
|
178
|
+
private showPrompt;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Configuration options for the local filesystem sandbox. */
|
|
182
|
+
interface LocalSandboxProviderOptions {
|
|
183
|
+
rootDir?: string;
|
|
184
|
+
cwd?: string;
|
|
185
|
+
env?: Record<string, string>;
|
|
186
|
+
timeout?: number;
|
|
187
|
+
allowedCommands?: string[];
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* {@link SandboxProvider} that operates directly on the host filesystem.
|
|
191
|
+
*
|
|
192
|
+
* @remarks
|
|
193
|
+
* All file paths are resolved relative to a configurable root directory.
|
|
194
|
+
* Path traversal outside the root is rejected. Command execution uses
|
|
195
|
+
* `child_process.spawn` with an optional command allowlist for security.
|
|
196
|
+
* Use this when the agent IS the sandbox occupant (e.g. inside a
|
|
197
|
+
* WebContainer or Docker container).
|
|
198
|
+
*/
|
|
199
|
+
declare class LocalSandboxProvider implements SandboxProvider {
|
|
200
|
+
private rootDir;
|
|
201
|
+
private cwd;
|
|
202
|
+
private env;
|
|
203
|
+
private timeout;
|
|
204
|
+
private allowedCommands?;
|
|
205
|
+
constructor(options?: LocalSandboxProviderOptions);
|
|
206
|
+
private resolvePath;
|
|
207
|
+
/** Read a file relative to the sandbox root directory. */
|
|
208
|
+
readFile(path: string): Promise<string>;
|
|
209
|
+
/** Write a file relative to the sandbox root, creating parent directories as needed. */
|
|
210
|
+
writeFile(path: string, content: string): Promise<void>;
|
|
211
|
+
/** Delete a file relative to the sandbox root directory. */
|
|
212
|
+
deleteFile(path: string): Promise<void>;
|
|
213
|
+
/** List file names in a directory relative to the sandbox root. */
|
|
214
|
+
listFiles(dir: string): Promise<string[]>;
|
|
215
|
+
/** Recursively create a directory relative to the sandbox root. */
|
|
216
|
+
mkdir(path: string): Promise<void>;
|
|
217
|
+
/**
|
|
218
|
+
* Executes a shell command via `child_process.spawn`.
|
|
219
|
+
*
|
|
220
|
+
* @param command - The shell command string to execute.
|
|
221
|
+
* @param options - Optional working directory, environment, timeout, and output callbacks.
|
|
222
|
+
* @returns A result with exit code, stdout, and stderr.
|
|
223
|
+
*
|
|
224
|
+
* @remarks
|
|
225
|
+
* If an `allowedCommands` list is configured, only the first token of the
|
|
226
|
+
* command is checked against the allowlist. Rejected commands return exit code 1.
|
|
227
|
+
*/
|
|
228
|
+
executeCommand(command: string, options?: CommandExecutionOptions): Promise<{
|
|
229
|
+
exitCode: number;
|
|
230
|
+
stdout: string;
|
|
231
|
+
stderr: string;
|
|
232
|
+
}>;
|
|
233
|
+
/** Ensure the sandbox root directory exists. */
|
|
234
|
+
create(): Promise<void>;
|
|
235
|
+
/** No-op: does not delete the host root directory. */
|
|
236
|
+
destroy(): Promise<void>;
|
|
237
|
+
/** No-op for local sandbox. */
|
|
238
|
+
reset(): Promise<void>;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export { HeadlessAgent, HttpTransport, type HttpTransportOptions, InteractiveTransport, LocalSandboxProvider, type LocalSandboxProviderOptions, type MessagePortLike, MessagePortTransport, StdioTransport, createHeadlessAgent };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HeadlessAgent,
|
|
3
|
+
HttpTransport,
|
|
4
|
+
InteractiveTransport,
|
|
5
|
+
LocalSandboxProvider,
|
|
6
|
+
StdioTransport,
|
|
7
|
+
createHeadlessAgent
|
|
8
|
+
} from "./chunk-OBRMNJP2.js";
|
|
9
|
+
|
|
10
|
+
// src/message-port-transport.ts
|
|
11
|
+
var MessagePortTransport = class {
|
|
12
|
+
port;
|
|
13
|
+
messageHandler;
|
|
14
|
+
// 75a: Accept a MessagePort or Worker in constructor
|
|
15
|
+
constructor(port) {
|
|
16
|
+
this.port = port;
|
|
17
|
+
}
|
|
18
|
+
/** Subscribe to incoming messages on the port, dispatching each as an AgentCommand. */
|
|
19
|
+
onCommand(handler) {
|
|
20
|
+
if (this.port.on) {
|
|
21
|
+
this.port.on("message", (data) => {
|
|
22
|
+
handler(data);
|
|
23
|
+
});
|
|
24
|
+
} else if (this.port.addEventListener) {
|
|
25
|
+
this.messageHandler = (event) => {
|
|
26
|
+
handler(event.data);
|
|
27
|
+
};
|
|
28
|
+
this.port.addEventListener("message", this.messageHandler);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Send an event or status to the other end of the port via postMessage. */
|
|
32
|
+
emit(event) {
|
|
33
|
+
this.port.postMessage(event);
|
|
34
|
+
}
|
|
35
|
+
/** Detach the message listener and close or terminate the port. */
|
|
36
|
+
async close() {
|
|
37
|
+
if (this.messageHandler && this.port.removeEventListener) {
|
|
38
|
+
this.port.removeEventListener("message", this.messageHandler);
|
|
39
|
+
}
|
|
40
|
+
if (this.port.close) {
|
|
41
|
+
this.port.close();
|
|
42
|
+
} else if (this.port.terminate) {
|
|
43
|
+
this.port.terminate();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
export {
|
|
48
|
+
HeadlessAgent,
|
|
49
|
+
HttpTransport,
|
|
50
|
+
InteractiveTransport,
|
|
51
|
+
LocalSandboxProvider,
|
|
52
|
+
MessagePortTransport,
|
|
53
|
+
StdioTransport,
|
|
54
|
+
createHeadlessAgent
|
|
55
|
+
};
|
|
56
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/message-port-transport.ts"],"sourcesContent":["// Task 75: MessagePortTransport — for Web Workers / WebContainers using postMessage\n\nimport type { AgentCommand, AgentStatus, AgentTransport, SessionEvent } from '@yolo-labs/core-types';\n\n/** Minimal interface for any object that supports postMessage-based communication. */\nexport interface MessagePortLike {\n postMessage(data: unknown): void;\n on?(event: 'message', handler: (data: unknown) => void): void;\n addEventListener?(event: 'message', handler: (event: { data: unknown }) => void): void;\n removeEventListener?(event: 'message', handler: (event: { data: unknown }) => void): void;\n close?(): void;\n terminate?(): void;\n}\n\n/**\n * {@link AgentTransport} using `postMessage` for Web Workers, worker_threads, and WebContainers.\n *\n * @remarks\n * Accepts any object matching {@link MessagePortLike} (Node.js `MessagePort`,\n * Web API `MessagePort`, or a `Worker` instance). Automatically detects\n * the messaging API (Node.js `on('message')` vs Web `addEventListener`).\n */\nexport class MessagePortTransport implements AgentTransport {\n private port: MessagePortLike;\n private messageHandler?: (event: { data: unknown }) => void;\n\n // 75a: Accept a MessagePort or Worker in constructor\n constructor(port: MessagePortLike) {\n this.port = port;\n }\n\n /** Subscribe to incoming messages on the port, dispatching each as an AgentCommand. */\n onCommand(handler: (cmd: AgentCommand) => void): void {\n if (this.port.on) {\n // Node.js worker_threads MessagePort\n this.port.on('message', (data: unknown) => {\n handler(data as AgentCommand);\n });\n } else if (this.port.addEventListener) {\n // Web API MessagePort / Worker\n this.messageHandler = (event: { data: unknown }) => {\n handler(event.data as AgentCommand);\n };\n this.port.addEventListener('message', this.messageHandler);\n }\n }\n\n /** Send an event or status to the other end of the port via postMessage. */\n emit(event: SessionEvent | AgentStatus): void {\n this.port.postMessage(event);\n }\n\n /** Detach the message listener and close or terminate the port. */\n async close(): Promise<void> {\n if (this.messageHandler && this.port.removeEventListener) {\n this.port.removeEventListener('message', this.messageHandler);\n }\n if (this.port.close) {\n this.port.close();\n } else if (this.port.terminate) {\n this.port.terminate();\n }\n }\n}\n"],"mappings":";;;;;;;;;;AAsBO,IAAM,uBAAN,MAAqD;AAAA,EAClD;AAAA,EACA;AAAA;AAAA,EAGR,YAAY,MAAuB;AACjC,SAAK,OAAO;AAAA,EACd;AAAA;AAAA,EAGA,UAAU,SAA4C;AACpD,QAAI,KAAK,KAAK,IAAI;AAEhB,WAAK,KAAK,GAAG,WAAW,CAAC,SAAkB;AACzC,gBAAQ,IAAoB;AAAA,MAC9B,CAAC;AAAA,IACH,WAAW,KAAK,KAAK,kBAAkB;AAErC,WAAK,iBAAiB,CAAC,UAA6B;AAClD,gBAAQ,MAAM,IAAoB;AAAA,MACpC;AACA,WAAK,KAAK,iBAAiB,WAAW,KAAK,cAAc;AAAA,IAC3D;AAAA,EACF;AAAA;AAAA,EAGA,KAAK,OAAyC;AAC5C,SAAK,KAAK,YAAY,KAAK;AAAA,EAC7B;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC3B,QAAI,KAAK,kBAAkB,KAAK,KAAK,qBAAqB;AACxD,WAAK,KAAK,oBAAoB,WAAW,KAAK,cAAc;AAAA,IAC9D;AACA,QAAI,KAAK,KAAK,OAAO;AACnB,WAAK,KAAK,MAAM;AAAA,IAClB,WAAW,KAAK,KAAK,WAAW;AAC9B,WAAK,KAAK,UAAU;AAAA,IACtB;AAAA,EACF;AACF;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yolo-labs/core-agent-runtime",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"import": "./dist/index.js"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"yolo-agent": "./dist/cli.js"
|
|
13
|
+
},
|
|
14
|
+
"main": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"yaml": "^2.4.0",
|
|
21
|
+
"@yolo-labs/core-types": "1.0.0",
|
|
22
|
+
"@yolo-labs/core-agent-service": "1.0.0",
|
|
23
|
+
"@yolo-labs/core-tools": "1.0.0"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"@yolo-labs/core-providers": "1.0.0"
|
|
27
|
+
},
|
|
28
|
+
"peerDependenciesMeta": {
|
|
29
|
+
"@yolo-core/providers": {
|
|
30
|
+
"optional": true
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"tsup": "^8.0.0",
|
|
35
|
+
"typescript": "^5.5.0",
|
|
36
|
+
"@yolo-labs/core-providers": "1.0.0"
|
|
37
|
+
},
|
|
38
|
+
"description": "Headless agent runtime with pluggable transports",
|
|
39
|
+
"license": "MIT",
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "git+https://github.com/yolo-labs-hq/monorepo.git",
|
|
43
|
+
"directory": "yolo-core/packages/agent-runtime"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public",
|
|
47
|
+
"registry": "https://registry.npmjs.org/"
|
|
48
|
+
},
|
|
49
|
+
"homepage": "https://github.com/yolo-labs-hq/monorepo/tree/main/yolo-core#readme",
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/yolo-labs-hq/monorepo/issues"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsup",
|
|
55
|
+
"clean": "rm -rf dist *.tsbuildinfo"
|
|
56
|
+
}
|
|
57
|
+
}
|