agent-worker 0.1.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/backends-BklSbwcH.mjs +381 -0
- package/dist/backends-BqaAh6cC.mjs +4 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.mjs +867 -0
- package/dist/index.d.mts +572 -0
- package/dist/index.mjs +121 -0
- package/dist/models-FOOpWB91.mjs +182 -0
- package/dist/session-DQQAmPf-.mjs +419 -0
- package/package.json +85 -0
|
@@ -0,0 +1,867 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { a as getDefaultModel, t as FRONTIER_MODELS } from "../models-FOOpWB91.mjs";
|
|
3
|
+
import { t as AgentSession } from "../session-DQQAmPf-.mjs";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { createConnection, createServer } from "node:net";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
|
|
11
|
+
//#region src/cli/server.ts
|
|
12
|
+
const CONFIG_DIR = join(homedir(), ".agent-worker");
|
|
13
|
+
const SESSIONS_DIR = join(CONFIG_DIR, "sessions");
|
|
14
|
+
const REGISTRY_FILE = join(CONFIG_DIR, "registry.json");
|
|
15
|
+
let state = null;
|
|
16
|
+
const DEFAULT_IDLE_TIMEOUT = 1800 * 1e3;
|
|
17
|
+
function ensureDirs() {
|
|
18
|
+
if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
|
|
19
|
+
if (!existsSync(SESSIONS_DIR)) mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
function loadRegistry() {
|
|
22
|
+
ensureDirs();
|
|
23
|
+
if (!existsSync(REGISTRY_FILE)) return { sessions: {} };
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(readFileSync(REGISTRY_FILE, "utf-8"));
|
|
26
|
+
} catch {
|
|
27
|
+
return { sessions: {} };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function saveRegistry(registry) {
|
|
31
|
+
ensureDirs();
|
|
32
|
+
writeFileSync(REGISTRY_FILE, JSON.stringify(registry, null, 2));
|
|
33
|
+
}
|
|
34
|
+
function resetIdleTimer() {
|
|
35
|
+
if (!state) return;
|
|
36
|
+
state.lastActivity = Date.now();
|
|
37
|
+
if (state.idleTimer) {
|
|
38
|
+
clearTimeout(state.idleTimer);
|
|
39
|
+
state.idleTimer = void 0;
|
|
40
|
+
}
|
|
41
|
+
const timeout = state.info.idleTimeout ?? DEFAULT_IDLE_TIMEOUT;
|
|
42
|
+
if (timeout > 0) state.idleTimer = setTimeout(() => {
|
|
43
|
+
if (state && state.pendingRequests === 0) {
|
|
44
|
+
console.log(`\nSession idle for ${timeout / 1e3}s, shutting down...`);
|
|
45
|
+
gracefulShutdown();
|
|
46
|
+
} else resetIdleTimer();
|
|
47
|
+
}, timeout);
|
|
48
|
+
}
|
|
49
|
+
async function gracefulShutdown() {
|
|
50
|
+
if (!state) {
|
|
51
|
+
process.exit(0);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
state.server.close();
|
|
55
|
+
const maxWait = 1e4;
|
|
56
|
+
const start = Date.now();
|
|
57
|
+
while (state.pendingRequests > 0 && Date.now() - start < maxWait) await new Promise((resolve) => setTimeout(resolve, 100));
|
|
58
|
+
cleanup();
|
|
59
|
+
process.exit(0);
|
|
60
|
+
}
|
|
61
|
+
function registerSession(info) {
|
|
62
|
+
const registry = loadRegistry();
|
|
63
|
+
registry.sessions[info.id] = info;
|
|
64
|
+
if (info.name) registry.sessions[info.name] = info;
|
|
65
|
+
if (Object.keys(registry.sessions).length <= 2) registry.defaultSession = info.id;
|
|
66
|
+
saveRegistry(registry);
|
|
67
|
+
}
|
|
68
|
+
function unregisterSession(idOrName) {
|
|
69
|
+
const registry = loadRegistry();
|
|
70
|
+
const info = registry.sessions[idOrName];
|
|
71
|
+
if (info) {
|
|
72
|
+
delete registry.sessions[info.id];
|
|
73
|
+
if (info.name) delete registry.sessions[info.name];
|
|
74
|
+
if (registry.defaultSession === info.id) registry.defaultSession = Object.values(registry.sessions).filter((s) => s.id !== info.id)[0]?.id;
|
|
75
|
+
}
|
|
76
|
+
saveRegistry(registry);
|
|
77
|
+
}
|
|
78
|
+
function getSessionInfo(idOrName) {
|
|
79
|
+
const registry = loadRegistry();
|
|
80
|
+
if (!idOrName) {
|
|
81
|
+
if (registry.defaultSession) return registry.sessions[registry.defaultSession] || null;
|
|
82
|
+
const uniqueSessions = Object.values(registry.sessions).filter((s, i, arr) => arr.findIndex((x) => x.id === s.id) === i);
|
|
83
|
+
if (uniqueSessions.length === 1) return uniqueSessions[0];
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
return registry.sessions[idOrName] || null;
|
|
87
|
+
}
|
|
88
|
+
function listSessions() {
|
|
89
|
+
const registry = loadRegistry();
|
|
90
|
+
const seen = /* @__PURE__ */ new Set();
|
|
91
|
+
return Object.values(registry.sessions).filter((info) => {
|
|
92
|
+
if (seen.has(info.id)) return false;
|
|
93
|
+
seen.add(info.id);
|
|
94
|
+
return true;
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function setDefaultSession(idOrName) {
|
|
98
|
+
const registry = loadRegistry();
|
|
99
|
+
const info = registry.sessions[idOrName];
|
|
100
|
+
if (!info) return false;
|
|
101
|
+
registry.defaultSession = info.id;
|
|
102
|
+
saveRegistry(registry);
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
async function handleRequest(req) {
|
|
106
|
+
if (!state) return {
|
|
107
|
+
success: false,
|
|
108
|
+
error: "No active session"
|
|
109
|
+
};
|
|
110
|
+
state.pendingRequests++;
|
|
111
|
+
resetIdleTimer();
|
|
112
|
+
const { session } = state;
|
|
113
|
+
try {
|
|
114
|
+
switch (req.action) {
|
|
115
|
+
case "ping": return {
|
|
116
|
+
success: true,
|
|
117
|
+
data: {
|
|
118
|
+
id: session.id,
|
|
119
|
+
model: session.model,
|
|
120
|
+
name: state.info.name
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
case "send": {
|
|
124
|
+
const { message, options } = req.payload;
|
|
125
|
+
return {
|
|
126
|
+
success: true,
|
|
127
|
+
data: await session.send(message, options)
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
case "tool_add": {
|
|
131
|
+
const tool = req.payload;
|
|
132
|
+
session.addTool(tool);
|
|
133
|
+
return {
|
|
134
|
+
success: true,
|
|
135
|
+
data: { name: tool.name }
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
case "tool_mock": {
|
|
139
|
+
const { name, response } = req.payload;
|
|
140
|
+
session.setMockResponse(name, response);
|
|
141
|
+
return {
|
|
142
|
+
success: true,
|
|
143
|
+
data: { name }
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
case "tool_list": return {
|
|
147
|
+
success: true,
|
|
148
|
+
data: session.getTools()
|
|
149
|
+
};
|
|
150
|
+
case "tool_import": {
|
|
151
|
+
const { filePath } = req.payload;
|
|
152
|
+
if (!filePath || typeof filePath !== "string") return {
|
|
153
|
+
success: false,
|
|
154
|
+
error: "File path is required"
|
|
155
|
+
};
|
|
156
|
+
let module;
|
|
157
|
+
try {
|
|
158
|
+
module = await import(filePath);
|
|
159
|
+
} catch (importError) {
|
|
160
|
+
return {
|
|
161
|
+
success: false,
|
|
162
|
+
error: `Failed to import file: ${(importError instanceof Error ? importError.message : String(importError)).replace(filePath, "<file>")}`
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
let tools = [];
|
|
166
|
+
if (Array.isArray(module.default)) tools = module.default;
|
|
167
|
+
else if (typeof module.default === "function") try {
|
|
168
|
+
const result = await module.default();
|
|
169
|
+
tools = Array.isArray(result) ? result : [];
|
|
170
|
+
} catch (factoryError) {
|
|
171
|
+
return {
|
|
172
|
+
success: false,
|
|
173
|
+
error: `Factory function failed: ${factoryError instanceof Error ? factoryError.message : String(factoryError)}`
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
else if (Array.isArray(module.tools)) tools = module.tools;
|
|
177
|
+
else return {
|
|
178
|
+
success: false,
|
|
179
|
+
error: "No tools found. Export default array or named \"tools\" array."
|
|
180
|
+
};
|
|
181
|
+
const imported = [];
|
|
182
|
+
const skipped = [];
|
|
183
|
+
for (const tool of tools) {
|
|
184
|
+
if (!tool.name || typeof tool.name !== "string") {
|
|
185
|
+
skipped.push("(unnamed)");
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (!tool.description || !tool.parameters) {
|
|
189
|
+
skipped.push(tool.name);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
session.addTool(tool);
|
|
193
|
+
imported.push(tool.name);
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
success: true,
|
|
197
|
+
data: {
|
|
198
|
+
imported,
|
|
199
|
+
skipped: skipped.length > 0 ? skipped : void 0
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
case "history": return {
|
|
204
|
+
success: true,
|
|
205
|
+
data: session.history()
|
|
206
|
+
};
|
|
207
|
+
case "stats": return {
|
|
208
|
+
success: true,
|
|
209
|
+
data: session.stats()
|
|
210
|
+
};
|
|
211
|
+
case "export": return {
|
|
212
|
+
success: true,
|
|
213
|
+
data: session.export()
|
|
214
|
+
};
|
|
215
|
+
case "clear":
|
|
216
|
+
session.clear();
|
|
217
|
+
return { success: true };
|
|
218
|
+
case "pending": return {
|
|
219
|
+
success: true,
|
|
220
|
+
data: session.getPendingApprovals()
|
|
221
|
+
};
|
|
222
|
+
case "approve": {
|
|
223
|
+
const { id } = req.payload;
|
|
224
|
+
return {
|
|
225
|
+
success: true,
|
|
226
|
+
data: await session.approve(id)
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
case "deny": {
|
|
230
|
+
const { id, reason } = req.payload;
|
|
231
|
+
session.deny(id, reason);
|
|
232
|
+
return { success: true };
|
|
233
|
+
}
|
|
234
|
+
case "shutdown":
|
|
235
|
+
state.pendingRequests--;
|
|
236
|
+
setTimeout(() => gracefulShutdown(), 100);
|
|
237
|
+
return {
|
|
238
|
+
success: true,
|
|
239
|
+
data: "Shutting down"
|
|
240
|
+
};
|
|
241
|
+
default: return {
|
|
242
|
+
success: false,
|
|
243
|
+
error: `Unknown action: ${req.action}`
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
} catch (error) {
|
|
247
|
+
return {
|
|
248
|
+
success: false,
|
|
249
|
+
error: error instanceof Error ? error.message : String(error)
|
|
250
|
+
};
|
|
251
|
+
} finally {
|
|
252
|
+
if (state && req.action !== "shutdown") state.pendingRequests--;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function cleanup() {
|
|
256
|
+
if (state) {
|
|
257
|
+
if (state.idleTimer) clearTimeout(state.idleTimer);
|
|
258
|
+
if (existsSync(state.info.socketPath)) unlinkSync(state.info.socketPath);
|
|
259
|
+
if (existsSync(state.info.pidFile)) unlinkSync(state.info.pidFile);
|
|
260
|
+
if (existsSync(state.info.readyFile)) unlinkSync(state.info.readyFile);
|
|
261
|
+
unregisterSession(state.info.id);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function startServer(config) {
|
|
265
|
+
ensureDirs();
|
|
266
|
+
const session = new AgentSession({
|
|
267
|
+
model: config.model,
|
|
268
|
+
system: config.system
|
|
269
|
+
});
|
|
270
|
+
const socketPath = join(SESSIONS_DIR, `${session.id}.sock`);
|
|
271
|
+
const pidFile = join(SESSIONS_DIR, `${session.id}.pid`);
|
|
272
|
+
const readyFile = join(SESSIONS_DIR, `${session.id}.ready`);
|
|
273
|
+
if (existsSync(socketPath)) unlinkSync(socketPath);
|
|
274
|
+
const info = {
|
|
275
|
+
id: session.id,
|
|
276
|
+
name: config.name,
|
|
277
|
+
model: config.model,
|
|
278
|
+
socketPath,
|
|
279
|
+
pidFile,
|
|
280
|
+
readyFile,
|
|
281
|
+
pid: process.pid,
|
|
282
|
+
createdAt: session.createdAt,
|
|
283
|
+
idleTimeout: config.idleTimeout
|
|
284
|
+
};
|
|
285
|
+
const server = createServer((socket) => {
|
|
286
|
+
let buffer = "";
|
|
287
|
+
socket.on("data", async (data) => {
|
|
288
|
+
buffer += data.toString();
|
|
289
|
+
const lines = buffer.split("\n");
|
|
290
|
+
buffer = lines.pop() || "";
|
|
291
|
+
for (const line of lines) {
|
|
292
|
+
if (!line.trim()) continue;
|
|
293
|
+
try {
|
|
294
|
+
const res = await handleRequest(JSON.parse(line));
|
|
295
|
+
socket.write(JSON.stringify(res) + "\n");
|
|
296
|
+
} catch (error) {
|
|
297
|
+
socket.write(JSON.stringify({
|
|
298
|
+
success: false,
|
|
299
|
+
error: error instanceof Error ? error.message : "Parse error"
|
|
300
|
+
}) + "\n");
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
socket.on("error", () => {});
|
|
305
|
+
});
|
|
306
|
+
server.listen(socketPath, () => {
|
|
307
|
+
writeFileSync(pidFile, process.pid.toString());
|
|
308
|
+
registerSession(info);
|
|
309
|
+
state = {
|
|
310
|
+
session,
|
|
311
|
+
server,
|
|
312
|
+
info,
|
|
313
|
+
lastActivity: Date.now(),
|
|
314
|
+
pendingRequests: 0
|
|
315
|
+
};
|
|
316
|
+
writeFileSync(readyFile, session.id);
|
|
317
|
+
resetIdleTimer();
|
|
318
|
+
const nameStr = config.name ? ` (${config.name})` : "";
|
|
319
|
+
console.log(`Session started: ${session.id}${nameStr}`);
|
|
320
|
+
console.log(`Model: ${session.model}`);
|
|
321
|
+
});
|
|
322
|
+
server.on("error", (error) => {
|
|
323
|
+
console.error("Server error:", error);
|
|
324
|
+
cleanup();
|
|
325
|
+
process.exit(1);
|
|
326
|
+
});
|
|
327
|
+
process.on("SIGINT", () => {
|
|
328
|
+
console.log("\nShutting down...");
|
|
329
|
+
cleanup();
|
|
330
|
+
process.exit(0);
|
|
331
|
+
});
|
|
332
|
+
process.on("SIGTERM", () => {
|
|
333
|
+
cleanup();
|
|
334
|
+
process.exit(0);
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
function isSessionRunning(idOrName) {
|
|
338
|
+
const info = getSessionInfo(idOrName);
|
|
339
|
+
if (!info) return false;
|
|
340
|
+
try {
|
|
341
|
+
process.kill(info.pid, 0);
|
|
342
|
+
return true;
|
|
343
|
+
} catch {
|
|
344
|
+
if (existsSync(info.socketPath)) unlinkSync(info.socketPath);
|
|
345
|
+
if (existsSync(info.pidFile)) unlinkSync(info.pidFile);
|
|
346
|
+
if (info.readyFile && existsSync(info.readyFile)) unlinkSync(info.readyFile);
|
|
347
|
+
unregisterSession(info.id);
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Wait for a session to be ready (ready file exists)
|
|
353
|
+
* Returns session info if ready, null if timeout
|
|
354
|
+
*/
|
|
355
|
+
async function waitForReady(nameOrId, timeoutMs = 5e3) {
|
|
356
|
+
const start = Date.now();
|
|
357
|
+
const pollInterval = 50;
|
|
358
|
+
while (Date.now() - start < timeoutMs) {
|
|
359
|
+
const info = getSessionInfo(nameOrId);
|
|
360
|
+
if (info?.readyFile && existsSync(info.readyFile)) return info;
|
|
361
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
362
|
+
}
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
//#endregion
|
|
367
|
+
//#region src/cli/client.ts
|
|
368
|
+
/**
|
|
369
|
+
* Send a request to a specific session
|
|
370
|
+
* @param req - The request to send
|
|
371
|
+
* @param target - Session ID or name (optional, uses default if not specified)
|
|
372
|
+
*/
|
|
373
|
+
function sendRequest(req, target) {
|
|
374
|
+
return new Promise((resolve, reject) => {
|
|
375
|
+
const info = getSessionInfo(target);
|
|
376
|
+
if (!info) {
|
|
377
|
+
if (target) resolve({
|
|
378
|
+
success: false,
|
|
379
|
+
error: `Session not found: ${target}`
|
|
380
|
+
});
|
|
381
|
+
else resolve({
|
|
382
|
+
success: false,
|
|
383
|
+
error: "No active session. Start one with: agent-worker session start -m <model>"
|
|
384
|
+
});
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
if (!isSessionRunning(target)) {
|
|
388
|
+
resolve({
|
|
389
|
+
success: false,
|
|
390
|
+
error: `Session not running: ${target || info.id}`
|
|
391
|
+
});
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
const socket = createConnection(info.socketPath);
|
|
395
|
+
let buffer = "";
|
|
396
|
+
socket.on("connect", () => {
|
|
397
|
+
socket.write(JSON.stringify(req) + "\n");
|
|
398
|
+
});
|
|
399
|
+
socket.on("data", (data) => {
|
|
400
|
+
buffer += data.toString();
|
|
401
|
+
const lines = buffer.split("\n");
|
|
402
|
+
buffer = lines.pop() || "";
|
|
403
|
+
for (const line of lines) {
|
|
404
|
+
if (!line.trim()) continue;
|
|
405
|
+
try {
|
|
406
|
+
const res = JSON.parse(line);
|
|
407
|
+
socket.end();
|
|
408
|
+
resolve(res);
|
|
409
|
+
} catch (error) {
|
|
410
|
+
socket.end();
|
|
411
|
+
reject(error);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
socket.on("error", (error) => {
|
|
416
|
+
reject(error);
|
|
417
|
+
});
|
|
418
|
+
socket.on("timeout", () => {
|
|
419
|
+
socket.end();
|
|
420
|
+
reject(/* @__PURE__ */ new Error("Connection timeout"));
|
|
421
|
+
});
|
|
422
|
+
socket.setTimeout(6e4);
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Check if any session is active, or a specific session
|
|
427
|
+
*/
|
|
428
|
+
function isSessionActive(target) {
|
|
429
|
+
return isSessionRunning(target);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
//#endregion
|
|
433
|
+
//#region src/cli/index.ts
|
|
434
|
+
const program = new Command();
|
|
435
|
+
program.name("agent-worker").description("CLI for creating and testing agent workers").version("0.0.1");
|
|
436
|
+
const sessionCmd = program.command("session").description("Manage sessions");
|
|
437
|
+
sessionCmd.command("new").description("Create a new session").option("-m, --model <model>", `Model identifier (default: ${getDefaultModel()})`).option("-s, --system <prompt>", "System prompt", "You are a helpful assistant.").option("-f, --system-file <file>", "Read system prompt from file").option("-n, --name <name>", "Session name for easy reference").option("--idle-timeout <ms>", "Idle timeout in ms (0 = no timeout)", "1800000").option("--foreground", "Run in foreground").action(async (options) => {
|
|
438
|
+
let system = options.system;
|
|
439
|
+
if (options.systemFile) system = readFileSync(options.systemFile, "utf-8");
|
|
440
|
+
const model = options.model || getDefaultModel();
|
|
441
|
+
const idleTimeout = parseInt(options.idleTimeout, 10);
|
|
442
|
+
if (options.foreground) startServer({
|
|
443
|
+
model,
|
|
444
|
+
system,
|
|
445
|
+
name: options.name,
|
|
446
|
+
idleTimeout
|
|
447
|
+
});
|
|
448
|
+
else {
|
|
449
|
+
const args = [
|
|
450
|
+
process.argv[1],
|
|
451
|
+
"session",
|
|
452
|
+
"new",
|
|
453
|
+
"-m",
|
|
454
|
+
model,
|
|
455
|
+
"-s",
|
|
456
|
+
system,
|
|
457
|
+
"--foreground"
|
|
458
|
+
];
|
|
459
|
+
if (options.name) args.push("-n", options.name);
|
|
460
|
+
args.push("--idle-timeout", String(idleTimeout));
|
|
461
|
+
spawn(process.execPath, args, {
|
|
462
|
+
detached: true,
|
|
463
|
+
stdio: "ignore"
|
|
464
|
+
}).unref();
|
|
465
|
+
const info = await waitForReady(options.name, 5e3);
|
|
466
|
+
if (info) {
|
|
467
|
+
const nameStr = options.name ? ` (${options.name})` : "";
|
|
468
|
+
console.log(`Session started: ${info.id}${nameStr}`);
|
|
469
|
+
console.log(`Model: ${info.model}`);
|
|
470
|
+
} else {
|
|
471
|
+
console.error("Failed to start session");
|
|
472
|
+
process.exit(1);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
sessionCmd.command("list").description("List all sessions").action(() => {
|
|
477
|
+
const sessions = listSessions();
|
|
478
|
+
if (sessions.length === 0) {
|
|
479
|
+
console.log("No active sessions");
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
for (const s of sessions) {
|
|
483
|
+
const status = isSessionRunning(s.id) ? "running" : "stopped";
|
|
484
|
+
const nameStr = s.name ? ` (${s.name})` : "";
|
|
485
|
+
console.log(` ${s.id.slice(0, 8)}${nameStr} - ${s.model} [${status}]`);
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
sessionCmd.command("status [target]").description("Check session status").action(async (target) => {
|
|
489
|
+
if (!isSessionRunning(target)) {
|
|
490
|
+
console.log(target ? `Session not found: ${target}` : "No active session");
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
const res = await sendRequest({ action: "ping" }, target);
|
|
494
|
+
if (res.success && res.data) {
|
|
495
|
+
const { id, model, name } = res.data;
|
|
496
|
+
const nameStr = name ? ` (${name})` : "";
|
|
497
|
+
console.log(`Session: ${id}${nameStr}`);
|
|
498
|
+
console.log(`Model: ${model}`);
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
sessionCmd.command("use <target>").description("Set default session").action((target) => {
|
|
502
|
+
if (setDefaultSession(target)) console.log(`Default session set to: ${target}`);
|
|
503
|
+
else {
|
|
504
|
+
console.error(`Session not found: ${target}`);
|
|
505
|
+
process.exit(1);
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
sessionCmd.command("end [target]").description("End a session (or all with --all)").option("--all", "End all sessions").action(async (target, options) => {
|
|
509
|
+
if (options.all) {
|
|
510
|
+
const sessions = listSessions();
|
|
511
|
+
for (const s of sessions) if (isSessionRunning(s.id)) {
|
|
512
|
+
await sendRequest({ action: "shutdown" }, s.id);
|
|
513
|
+
console.log(`Ended: ${s.name || s.id}`);
|
|
514
|
+
}
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
if (!isSessionRunning(target)) {
|
|
518
|
+
console.log(target ? `Session not found: ${target}` : "No active session");
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const res = await sendRequest({ action: "shutdown" }, target);
|
|
522
|
+
if (res.success) console.log("Session ended");
|
|
523
|
+
else console.error("Error:", res.error);
|
|
524
|
+
});
|
|
525
|
+
program.command("send <message>").description("Send a message").option("--to <target>", "Target session (name or ID)").option("--json", "Output full JSON response").option("--auto-approve", "Auto-approve all tool calls (default)").option("--no-auto-approve", "Require manual approval").action(async (message, options) => {
|
|
526
|
+
const target = options.to;
|
|
527
|
+
if (!isSessionActive(target)) {
|
|
528
|
+
if (target) console.error(`Session not found: ${target}`);
|
|
529
|
+
else console.error("No active session. Create one with: agent-worker session new -m <model>");
|
|
530
|
+
process.exit(1);
|
|
531
|
+
}
|
|
532
|
+
const res = await sendRequest({
|
|
533
|
+
action: "send",
|
|
534
|
+
payload: {
|
|
535
|
+
message,
|
|
536
|
+
options: { autoApprove: options.autoApprove !== false }
|
|
537
|
+
}
|
|
538
|
+
}, target);
|
|
539
|
+
if (!res.success) {
|
|
540
|
+
console.error("Error:", res.error);
|
|
541
|
+
process.exit(1);
|
|
542
|
+
}
|
|
543
|
+
const response = res.data;
|
|
544
|
+
if (options.json) console.log(JSON.stringify(response, null, 2));
|
|
545
|
+
else {
|
|
546
|
+
console.log(response.content);
|
|
547
|
+
if (response.toolCalls?.length > 0) {
|
|
548
|
+
console.log("\n--- Tool Calls ---");
|
|
549
|
+
for (const tc of response.toolCalls) console.log(`${tc.name}(${JSON.stringify(tc.arguments)}) => ${JSON.stringify(tc.result)}`);
|
|
550
|
+
}
|
|
551
|
+
if (response.pendingApprovals?.length > 0) {
|
|
552
|
+
console.log("\n--- Pending Approvals ---");
|
|
553
|
+
for (const p of response.pendingApprovals) console.log(`[${p.id.slice(0, 8)}] ${p.toolName}(${JSON.stringify(p.arguments)})`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
const toolCmd = program.command("tool").description("Manage tools");
|
|
558
|
+
toolCmd.command("add <name>").description("Add a tool").option("--to <target>", "Target session").requiredOption("-d, --desc <description>", "Tool description").option("-p, --param <params...>", "Parameters (name:type:description)").option("--needs-approval", "Require approval").action(async (name, options) => {
|
|
559
|
+
const target = options.to;
|
|
560
|
+
if (!isSessionActive(target)) {
|
|
561
|
+
console.error(target ? `Session not found: ${target}` : "No active session");
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
564
|
+
const properties = {};
|
|
565
|
+
const required = [];
|
|
566
|
+
for (const param of options.param ?? []) {
|
|
567
|
+
const [paramName, type, ...descParts] = param.split(":");
|
|
568
|
+
properties[paramName] = {
|
|
569
|
+
type: type ?? "string",
|
|
570
|
+
description: descParts.join(":") ?? ""
|
|
571
|
+
};
|
|
572
|
+
required.push(paramName);
|
|
573
|
+
}
|
|
574
|
+
const res = await sendRequest({
|
|
575
|
+
action: "tool_add",
|
|
576
|
+
payload: {
|
|
577
|
+
name,
|
|
578
|
+
description: options.desc,
|
|
579
|
+
parameters: {
|
|
580
|
+
type: "object",
|
|
581
|
+
properties,
|
|
582
|
+
required
|
|
583
|
+
},
|
|
584
|
+
needsApproval: options.needsApproval ?? false
|
|
585
|
+
}
|
|
586
|
+
}, target);
|
|
587
|
+
if (res.success) {
|
|
588
|
+
const approvalNote = options.needsApproval ? " (needs approval)" : "";
|
|
589
|
+
console.log(`Tool added: ${name}${approvalNote}`);
|
|
590
|
+
} else {
|
|
591
|
+
console.error("Error:", res.error);
|
|
592
|
+
process.exit(1);
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
toolCmd.command("import <file>").description("Import tools from JS/TS file").option("--to <target>", "Target session").action(async (file, options) => {
|
|
596
|
+
const target = options.to;
|
|
597
|
+
if (!isSessionActive(target)) {
|
|
598
|
+
console.error(target ? `Session not found: ${target}` : "No active session");
|
|
599
|
+
process.exit(1);
|
|
600
|
+
}
|
|
601
|
+
const res = await sendRequest({
|
|
602
|
+
action: "tool_import",
|
|
603
|
+
payload: { filePath: file.startsWith("/") ? file : join(process.cwd(), file) }
|
|
604
|
+
}, target);
|
|
605
|
+
if (res.success) {
|
|
606
|
+
const data = res.data;
|
|
607
|
+
console.log(`Imported ${data.imported.length} tool(s):`);
|
|
608
|
+
for (const name of data.imported) console.log(` ${name}`);
|
|
609
|
+
if (data.skipped && data.skipped.length > 0) {
|
|
610
|
+
console.log(`Skipped ${data.skipped.length} invalid tool(s):`);
|
|
611
|
+
for (const name of data.skipped) console.log(` ${name}`);
|
|
612
|
+
}
|
|
613
|
+
} else {
|
|
614
|
+
console.error("Error:", res.error);
|
|
615
|
+
process.exit(1);
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
toolCmd.command("mock <name> <response>").description("Set mock response").option("--to <target>", "Target session").action(async (name, response, options) => {
|
|
619
|
+
const target = options.to;
|
|
620
|
+
if (!isSessionActive(target)) {
|
|
621
|
+
console.error(target ? `Session not found: ${target}` : "No active session");
|
|
622
|
+
process.exit(1);
|
|
623
|
+
}
|
|
624
|
+
try {
|
|
625
|
+
const res = await sendRequest({
|
|
626
|
+
action: "tool_mock",
|
|
627
|
+
payload: {
|
|
628
|
+
name,
|
|
629
|
+
response: JSON.parse(response)
|
|
630
|
+
}
|
|
631
|
+
}, target);
|
|
632
|
+
if (res.success) console.log(`Mock set for: ${name}`);
|
|
633
|
+
else {
|
|
634
|
+
console.error("Error:", res.error);
|
|
635
|
+
process.exit(1);
|
|
636
|
+
}
|
|
637
|
+
} catch {
|
|
638
|
+
console.error("Invalid JSON response");
|
|
639
|
+
process.exit(1);
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
toolCmd.command("list").description("List tools").option("--to <target>", "Target session").action(async (options) => {
|
|
643
|
+
const target = options.to;
|
|
644
|
+
if (!isSessionActive(target)) {
|
|
645
|
+
console.error(target ? `Session not found: ${target}` : "No active session");
|
|
646
|
+
process.exit(1);
|
|
647
|
+
}
|
|
648
|
+
const res = await sendRequest({ action: "tool_list" }, target);
|
|
649
|
+
if (!res.success) {
|
|
650
|
+
console.error("Error:", res.error);
|
|
651
|
+
process.exit(1);
|
|
652
|
+
}
|
|
653
|
+
const tools = res.data;
|
|
654
|
+
if (tools.length === 0) console.log("No tools");
|
|
655
|
+
else for (const t of tools) {
|
|
656
|
+
const approval = t.needsApproval ? " [needs approval]" : "";
|
|
657
|
+
const mock = t.mockResponse !== void 0 ? " [mocked]" : "";
|
|
658
|
+
console.log(` ${t.name}${approval}${mock} - ${t.description}`);
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
program.command("history").description("Show conversation history").option("--to <target>", "Target session").option("--json", "Output as JSON").option("-n, --last <count>", "Show last N messages", parseInt).action(async (options) => {
|
|
662
|
+
const target = options.to;
|
|
663
|
+
if (!isSessionActive(target)) {
|
|
664
|
+
console.error(target ? `Session not found: ${target}` : "No active session");
|
|
665
|
+
process.exit(1);
|
|
666
|
+
}
|
|
667
|
+
const res = await sendRequest({ action: "history" }, target);
|
|
668
|
+
if (!res.success) {
|
|
669
|
+
console.error("Error:", res.error);
|
|
670
|
+
process.exit(1);
|
|
671
|
+
}
|
|
672
|
+
let history = res.data;
|
|
673
|
+
if (options.last && options.last > 0) history = history.slice(-options.last);
|
|
674
|
+
if (options.json) console.log(JSON.stringify(history, null, 2));
|
|
675
|
+
else {
|
|
676
|
+
if (history.length === 0) {
|
|
677
|
+
console.log("No messages");
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
for (const msg of history) {
|
|
681
|
+
const role = msg.role === "user" ? "YOU" : msg.role.toUpperCase();
|
|
682
|
+
const status = msg.status === "responding" ? " (responding...)" : "";
|
|
683
|
+
console.log(`[${role}${status}] ${msg.content}`);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
});
|
|
687
|
+
program.command("stats").description("Show session statistics").option("--to <target>", "Target session").action(async (options) => {
|
|
688
|
+
const target = options.to;
|
|
689
|
+
if (!isSessionActive(target)) {
|
|
690
|
+
console.error(target ? `Session not found: ${target}` : "No active session");
|
|
691
|
+
process.exit(1);
|
|
692
|
+
}
|
|
693
|
+
const res = await sendRequest({ action: "stats" }, target);
|
|
694
|
+
if (!res.success) {
|
|
695
|
+
console.error("Error:", res.error);
|
|
696
|
+
process.exit(1);
|
|
697
|
+
}
|
|
698
|
+
const stats = res.data;
|
|
699
|
+
console.log(`Messages: ${stats.messageCount}`);
|
|
700
|
+
console.log(`Tokens: ${stats.usage.total} (in: ${stats.usage.input}, out: ${stats.usage.output})`);
|
|
701
|
+
});
|
|
702
|
+
program.command("export").description("Export session transcript").option("--to <target>", "Target session").action(async (options) => {
|
|
703
|
+
const target = options.to;
|
|
704
|
+
if (!isSessionActive(target)) {
|
|
705
|
+
console.error(target ? `Session not found: ${target}` : "No active session");
|
|
706
|
+
process.exit(1);
|
|
707
|
+
}
|
|
708
|
+
const res = await sendRequest({ action: "export" }, target);
|
|
709
|
+
if (!res.success) {
|
|
710
|
+
console.error("Error:", res.error);
|
|
711
|
+
process.exit(1);
|
|
712
|
+
}
|
|
713
|
+
console.log(JSON.stringify(res.data, null, 2));
|
|
714
|
+
});
|
|
715
|
+
program.command("clear").description("Clear conversation history").option("--to <target>", "Target session").action(async (options) => {
|
|
716
|
+
const target = options.to;
|
|
717
|
+
if (!isSessionActive(target)) {
|
|
718
|
+
console.error(target ? `Session not found: ${target}` : "No active session");
|
|
719
|
+
process.exit(1);
|
|
720
|
+
}
|
|
721
|
+
const res = await sendRequest({ action: "clear" }, target);
|
|
722
|
+
if (res.success) console.log("History cleared");
|
|
723
|
+
else console.error("Error:", res.error);
|
|
724
|
+
});
|
|
725
|
+
program.command("pending").description("List pending tool approvals").option("--to <target>", "Target session").option("--json", "Output as JSON").action(async (options) => {
|
|
726
|
+
const target = options.to;
|
|
727
|
+
if (!isSessionActive(target)) {
|
|
728
|
+
console.error(target ? `Session not found: ${target}` : "No active session");
|
|
729
|
+
process.exit(1);
|
|
730
|
+
}
|
|
731
|
+
const res = await sendRequest({ action: "pending" }, target);
|
|
732
|
+
if (!res.success) {
|
|
733
|
+
console.error("Error:", res.error);
|
|
734
|
+
process.exit(1);
|
|
735
|
+
}
|
|
736
|
+
const pending = res.data;
|
|
737
|
+
if (options.json) {
|
|
738
|
+
console.log(JSON.stringify(pending, null, 2));
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
if (pending.length === 0) {
|
|
742
|
+
console.log("No pending approvals");
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
for (const p of pending) {
|
|
746
|
+
console.log(`[${p.id.slice(0, 8)}] ${p.toolName}`);
|
|
747
|
+
console.log(` Arguments: ${JSON.stringify(p.arguments)}`);
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
program.command("approve <id>").description("Approve a pending tool call").option("--to <target>", "Target session").option("--json", "Output as JSON").action(async (id, options) => {
|
|
751
|
+
const target = options.to;
|
|
752
|
+
if (!isSessionActive(target)) {
|
|
753
|
+
console.error(target ? `Session not found: ${target}` : "No active session");
|
|
754
|
+
process.exit(1);
|
|
755
|
+
}
|
|
756
|
+
const res = await sendRequest({
|
|
757
|
+
action: "approve",
|
|
758
|
+
payload: { id }
|
|
759
|
+
}, target);
|
|
760
|
+
if (!res.success) {
|
|
761
|
+
console.error("Error:", res.error);
|
|
762
|
+
process.exit(1);
|
|
763
|
+
}
|
|
764
|
+
if (options.json) console.log(JSON.stringify({
|
|
765
|
+
approved: true,
|
|
766
|
+
result: res.data
|
|
767
|
+
}, null, 2));
|
|
768
|
+
else {
|
|
769
|
+
console.log("Approved");
|
|
770
|
+
console.log(`Result: ${JSON.stringify(res.data, null, 2)}`);
|
|
771
|
+
}
|
|
772
|
+
});
|
|
773
|
+
program.command("deny <id>").description("Deny a pending tool call").option("--to <target>", "Target session").option("-r, --reason <reason>", "Reason for denial").action(async (id, options) => {
|
|
774
|
+
const target = options.to;
|
|
775
|
+
if (!isSessionActive(target)) {
|
|
776
|
+
console.error(target ? `Session not found: ${target}` : "No active session");
|
|
777
|
+
process.exit(1);
|
|
778
|
+
}
|
|
779
|
+
const res = await sendRequest({
|
|
780
|
+
action: "deny",
|
|
781
|
+
payload: {
|
|
782
|
+
id,
|
|
783
|
+
reason: options.reason
|
|
784
|
+
}
|
|
785
|
+
}, target);
|
|
786
|
+
if (res.success) console.log("Denied");
|
|
787
|
+
else {
|
|
788
|
+
console.error("Error:", res.error);
|
|
789
|
+
process.exit(1);
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
const PROVIDER_API_KEYS = {
|
|
793
|
+
gateway: {
|
|
794
|
+
envVar: "AI_GATEWAY_API_KEY",
|
|
795
|
+
description: "Vercel AI Gateway (all providers)"
|
|
796
|
+
},
|
|
797
|
+
anthropic: {
|
|
798
|
+
envVar: "ANTHROPIC_API_KEY",
|
|
799
|
+
description: "Anthropic Claude"
|
|
800
|
+
},
|
|
801
|
+
openai: {
|
|
802
|
+
envVar: "OPENAI_API_KEY",
|
|
803
|
+
description: "OpenAI GPT"
|
|
804
|
+
},
|
|
805
|
+
deepseek: {
|
|
806
|
+
envVar: "DEEPSEEK_API_KEY",
|
|
807
|
+
description: "DeepSeek"
|
|
808
|
+
},
|
|
809
|
+
google: {
|
|
810
|
+
envVar: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
811
|
+
description: "Google Gemini"
|
|
812
|
+
},
|
|
813
|
+
groq: {
|
|
814
|
+
envVar: "GROQ_API_KEY",
|
|
815
|
+
description: "Groq"
|
|
816
|
+
},
|
|
817
|
+
mistral: {
|
|
818
|
+
envVar: "MISTRAL_API_KEY",
|
|
819
|
+
description: "Mistral"
|
|
820
|
+
},
|
|
821
|
+
xai: {
|
|
822
|
+
envVar: "XAI_API_KEY",
|
|
823
|
+
description: "xAI Grok"
|
|
824
|
+
},
|
|
825
|
+
minimax: {
|
|
826
|
+
envVar: "MINIMAX_API_KEY",
|
|
827
|
+
description: "MiniMax"
|
|
828
|
+
}
|
|
829
|
+
};
|
|
830
|
+
program.command("providers").description("Check provider availability").action(() => {
|
|
831
|
+
console.log("Provider Status:\n");
|
|
832
|
+
for (const [name, config] of Object.entries(PROVIDER_API_KEYS)) {
|
|
833
|
+
const isConfigured = !!process.env[config.envVar];
|
|
834
|
+
const status = isConfigured ? "✓" : "✗";
|
|
835
|
+
const statusText = isConfigured ? "" : " (not configured)";
|
|
836
|
+
const envHint = isConfigured ? "" : ` [${config.envVar}]`;
|
|
837
|
+
const defaultModel = name === "gateway" ? "" : ` → ${name}/${FRONTIER_MODELS[name]?.[0] || "?"}`;
|
|
838
|
+
console.log(` ${status} ${name.padEnd(10)} - ${config.description}${statusText}${envHint}${defaultModel}`);
|
|
839
|
+
}
|
|
840
|
+
const defaultModel = getDefaultModel();
|
|
841
|
+
const gatewayExample = `openai/${FRONTIER_MODELS.openai[0]}`;
|
|
842
|
+
const directExample = `deepseek:${FRONTIER_MODELS.deepseek[0]}`;
|
|
843
|
+
console.log("\nUsage:");
|
|
844
|
+
console.log(` Provider only: provider (e.g., openai → ${gatewayExample})`);
|
|
845
|
+
console.log(` Gateway format: provider/model (e.g., ${gatewayExample})`);
|
|
846
|
+
console.log(` Direct format: provider:model (e.g., ${directExample})`);
|
|
847
|
+
console.log(`\nDefault: ${defaultModel} (when no model specified)`);
|
|
848
|
+
});
|
|
849
|
+
program.command("backends").description("Check available backends (SDK, CLI tools)").action(async () => {
|
|
850
|
+
const { listBackends } = await import("../backends-BqaAh6cC.mjs");
|
|
851
|
+
const backends = await listBackends();
|
|
852
|
+
console.log("Backend Status:\n");
|
|
853
|
+
for (const backend of backends) {
|
|
854
|
+
const status = backend.available ? "✓" : "✗";
|
|
855
|
+
const statusText = backend.available ? "" : " (not installed)";
|
|
856
|
+
console.log(` ${status} ${backend.type.padEnd(8)} - ${backend.name}${statusText}`);
|
|
857
|
+
}
|
|
858
|
+
console.log("\nUsage:");
|
|
859
|
+
console.log(" SDK backend: agent-worker session new -m openai/gpt-5.2");
|
|
860
|
+
console.log(" Claude CLI: agent-worker session new --backend claude");
|
|
861
|
+
console.log(" Codex CLI: agent-worker session new --backend codex");
|
|
862
|
+
console.log(" Cursor CLI: agent-worker session new --backend cursor");
|
|
863
|
+
});
|
|
864
|
+
program.parse();
|
|
865
|
+
|
|
866
|
+
//#endregion
|
|
867
|
+
export { };
|