agents-dojo 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -24,10 +24,14 @@ See `agents/_template_echo/` for a complete manifest reference.
24
24
 
25
25
  ## Monitor GUI
26
26
 
27
+ The Monitor GUI starts automatically when you run `agents-dojo`. It picks a free
28
+ port for the WebSocket and launches the Vite dev server (installing dependencies
29
+ on first run). The GUI URL is printed to the console.
30
+
27
31
  ```bash
28
- agents-dojo --monitor-port=41242
29
- cd monitor && npm install && npm run dev
30
- # Open http://localhost:5173
32
+ agents-dojo # monitor starts automatically
33
+ agents-dojo --monitor-port=9000 # use a specific WS port
34
+ agents-dojo --no-monitor # disable the monitor
31
35
  ```
32
36
 
33
37
  ## Architecture
@@ -204,8 +204,7 @@ function extractPreview(parts) {
204
204
  return '';
205
205
  for (const p of parts) {
206
206
  if (p && typeof p === 'object' && p.kind === 'text' && typeof p.text === 'string') {
207
- const t = p.text;
208
- return t.length > 80 ? t.slice(0, 80) + '…' : t;
207
+ return p.text;
209
208
  }
210
209
  }
211
210
  return '';
package/dist/cli.js CHANGED
@@ -1,7 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import { resolve, join } from 'path';
2
+ import { resolve, join, dirname } from 'path';
3
3
  import { existsSync, mkdirSync, writeFileSync } from 'fs';
4
+ import { fileURLToPath } from 'url';
5
+ import { spawn, execSync } from 'child_process';
4
6
  import { createServer } from './server.js';
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
5
9
  // ── init command ──────────────────────────────────────────
6
10
  const ECHO_MANIFEST = `{
7
11
  "id": "echo",
@@ -140,7 +144,7 @@ function installCallAgentSkill(agentDir) {
140
144
  export const DEFAULT_ARGS = {
141
145
  agentsDir: './agents',
142
146
  port: 41241,
143
- monitorPort: 41242,
147
+ monitorPort: 0, // 0 = OS picks a free port
144
148
  singleAgent: null,
145
149
  help: false,
146
150
  };
@@ -172,17 +176,149 @@ function printHelp() {
172
176
 
173
177
  Commands:
174
178
  init Create agents/echo/ example in the current directory
179
+ chat <agentId> <msg> One-shot chat with an agent (connects to a running server)
175
180
  (default) Start the A2A server
176
181
 
177
182
  Options:
178
183
  --agents-dir <path> Directory containing agent subdirectories (default: ${DEFAULT_ARGS.agentsDir})
179
184
  --port <port> A2A HTTP port (default: ${DEFAULT_ARGS.port})
180
- --monitor-port <port> Monitor WebSocket port (default: ${DEFAULT_ARGS.monitorPort})
181
- --no-monitor Disable Monitor WebSocket
185
+ --monitor-port <port> Monitor WebSocket port (default: auto)
186
+ --no-monitor Disable Monitor GUI
182
187
  --agent=<id> Single Agent mode: only load and serve the named agent
183
188
  -h, --help Show this help
184
189
  `);
185
190
  }
191
+ // ── chat command ─────────────────────────────────────────
192
+ async function runChat(agentId, message, port) {
193
+ const { v4: uuidv4 } = await import('uuid');
194
+ const body = {
195
+ jsonrpc: '2.0',
196
+ id: 1,
197
+ method: 'message/send',
198
+ params: {
199
+ message: {
200
+ messageId: uuidv4(),
201
+ kind: 'message',
202
+ role: 'user',
203
+ parts: [{ kind: 'text', text: message }],
204
+ },
205
+ },
206
+ };
207
+ const res = await fetch(`http://localhost:${port}/a2a/${agentId}`, {
208
+ method: 'POST',
209
+ headers: { 'Content-Type': 'application/json' },
210
+ body: JSON.stringify(body),
211
+ });
212
+ if (!res.ok) {
213
+ console.error(`Error: A2A server returned ${res.status}`);
214
+ const text = await res.text();
215
+ if (text)
216
+ console.error(text);
217
+ process.exit(1);
218
+ }
219
+ const json = await res.json();
220
+ const result = json.result;
221
+ if (!result) {
222
+ console.error('Error: no result in response');
223
+ process.exit(1);
224
+ }
225
+ // Extract reply text
226
+ let replyText = '';
227
+ for (const a of (result.artifacts ?? [])) {
228
+ for (const p of (a.parts ?? [])) {
229
+ if (p.kind === 'text')
230
+ replyText += p.text;
231
+ }
232
+ }
233
+ if (!replyText) {
234
+ const statusMsg = result.status?.message;
235
+ if (statusMsg) {
236
+ for (const p of (statusMsg.parts ?? [])) {
237
+ if (p.kind === 'text')
238
+ replyText += p.text;
239
+ }
240
+ }
241
+ }
242
+ if (replyText) {
243
+ console.log(replyText);
244
+ }
245
+ else {
246
+ console.log('(no response)');
247
+ }
248
+ }
249
+ // ── monitor GUI launcher ─────────────────────────────────
250
+ /** Resolve the monitor/ directory shipped alongside the package. */
251
+ function resolveMonitorDir() {
252
+ // __dirname is dist/ after build, so monitor/ is one level up
253
+ return resolve(__dirname, '..', 'monitor');
254
+ }
255
+ function startMonitorGui(monitorWsPort) {
256
+ const monitorDir = resolveMonitorDir();
257
+ if (!existsSync(join(monitorDir, 'package.json'))) {
258
+ console.warn('[agents-dojo] Monitor GUI not found — skipping.');
259
+ return null;
260
+ }
261
+ // Auto-install monitor deps if missing
262
+ if (!existsSync(join(monitorDir, 'node_modules'))) {
263
+ console.log('[agents-dojo] Installing monitor dependencies…');
264
+ try {
265
+ execSync('npm install --no-audit --no-fund', { cwd: monitorDir, stdio: 'inherit' });
266
+ }
267
+ catch {
268
+ console.warn('[agents-dojo] Failed to install monitor dependencies — skipping GUI.');
269
+ return null;
270
+ }
271
+ }
272
+ const wsUrl = `ws://localhost:${monitorWsPort}/monitor`;
273
+ const child = spawn(join(monitorDir, 'node_modules', '.bin', 'vite'), [], {
274
+ cwd: monitorDir,
275
+ stdio: ['ignore', 'pipe', 'pipe'],
276
+ env: {
277
+ ...process.env,
278
+ VITE_MONITOR_WS_URL: wsUrl,
279
+ },
280
+ });
281
+ child.stdout?.on('data', (data) => {
282
+ const line = data.toString().trim();
283
+ if (line) {
284
+ // Extract and print the local URL from vite output
285
+ const urlMatch = line.match(/Local:\s+(https?:\/\/\S+)/);
286
+ if (urlMatch) {
287
+ console.log(`[agents-dojo] Monitor GUI: ${urlMatch[1]}`);
288
+ }
289
+ else if (line.includes('ready in') || line.includes('VITE')) {
290
+ // Print vite ready messages
291
+ console.log(`[agents-dojo] ${line}`);
292
+ }
293
+ }
294
+ });
295
+ child.stderr?.on('data', (data) => {
296
+ const line = data.toString().trim();
297
+ if (line)
298
+ console.error(`[monitor] ${line}`);
299
+ });
300
+ child.on('error', (err) => {
301
+ console.warn(`[agents-dojo] Failed to start Monitor GUI: ${err.message}`);
302
+ });
303
+ return child;
304
+ }
305
+ /** Ensure child processes are killed on exit. */
306
+ function registerCleanup(child) {
307
+ const kill = () => {
308
+ if (!child.killed) {
309
+ child.kill('SIGTERM');
310
+ // Force kill after 2s if SIGTERM didn't work
311
+ setTimeout(() => {
312
+ if (!child.killed)
313
+ child.kill('SIGKILL');
314
+ }, 2000).unref();
315
+ }
316
+ };
317
+ process.on('exit', kill);
318
+ process.on('SIGINT', () => { kill(); process.exit(0); });
319
+ process.on('SIGTERM', () => { kill(); process.exit(0); });
320
+ }
321
+ // ── main ─────────────────────────────────────────────────
186
322
  async function main() {
187
323
  const rawArgs = process.argv.slice(2);
188
324
  // Handle `agents-dojo init [dir]`
@@ -190,6 +326,24 @@ async function main() {
190
326
  runInit(resolve(rawArgs[1] ?? '.'));
191
327
  return;
192
328
  }
329
+ // Handle `agents-dojo chat <agentId> <message...>`
330
+ if (rawArgs[0] === 'chat') {
331
+ const agentId = rawArgs[1];
332
+ const message = rawArgs.slice(2).join(' ');
333
+ if (!agentId || !message) {
334
+ console.error('Usage: agents-dojo chat <agentId> <message>');
335
+ process.exit(1);
336
+ }
337
+ // Check for --port flag in remaining args
338
+ let port = DEFAULT_ARGS.port;
339
+ for (let i = 2; i < rawArgs.length; i++) {
340
+ if (rawArgs[i] === '--port' && rawArgs[i + 1]) {
341
+ port = parseInt(rawArgs[++i], 10);
342
+ }
343
+ }
344
+ await runChat(agentId, message, port);
345
+ return;
346
+ }
193
347
  const args = parseArgs(rawArgs);
194
348
  if (args.help) {
195
349
  printHelp();
@@ -197,9 +351,6 @@ async function main() {
197
351
  }
198
352
  console.log(`[agents-dojo] agents-dir: ${args.agentsDir}`);
199
353
  console.log(`[agents-dojo] A2A port: ${args.port}`);
200
- if (args.monitorPort !== null) {
201
- console.log(`[agents-dojo] Monitor port: ${args.monitorPort}`);
202
- }
203
354
  if (args.singleAgent) {
204
355
  console.log(`[agents-dojo] Single agent mode: ${args.singleAgent}`);
205
356
  }
@@ -210,6 +361,17 @@ async function main() {
210
361
  singleAgent: args.singleAgent ?? undefined,
211
362
  });
212
363
  console.log(`[agents-dojo] ${server.registry.list().length} agents loaded: ${server.registry.list().join(', ')}`);
364
+ // Auto-start monitor GUI
365
+ if (server.monitorPort) {
366
+ console.log(`[agents-dojo] Monitor WS port: ${server.monitorPort}`);
367
+ const monitorChild = startMonitorGui(server.monitorPort);
368
+ if (monitorChild) {
369
+ registerCleanup(monitorChild);
370
+ }
371
+ }
372
+ else {
373
+ console.log(`[agents-dojo] Monitor disabled.`);
374
+ }
213
375
  console.log(`[agents-dojo] Ready.`);
214
376
  }
215
377
  main().catch((err) => {
@@ -30,7 +30,7 @@ export function createTranslator(ctx) {
30
30
  type: 'task_status',
31
31
  taskId: ctx.taskId,
32
32
  state: 'working',
33
- message: text.length > 80 ? text.slice(0, 77) + '...' : text,
33
+ message: text,
34
34
  });
35
35
  }
36
36
  function publishCompleted() {
@@ -30,6 +30,12 @@ export type MonitorEvent = {
30
30
  taskId: string;
31
31
  toolName: string;
32
32
  success: boolean;
33
+ } | {
34
+ type: 'chat_response';
35
+ chatId: string;
36
+ agentId: string;
37
+ text: string;
38
+ final: boolean;
33
39
  };
34
40
  export type MonitorSubscriber = (event: MonitorEvent) => void;
35
41
  export type Unsubscribe = () => void;
@@ -7,6 +7,8 @@ export interface MonitorWsOptions {
7
7
  bus: MonitorBus;
8
8
  path: string;
9
9
  registry: AgentRegistry;
10
+ /** A2A server port — needed to proxy chat commands. */
11
+ a2aPort: number;
10
12
  }
11
13
  export type MonitorCommand = {
12
14
  type: 'reload';
@@ -16,6 +18,11 @@ export type MonitorCommand = {
16
18
  agentId: string;
17
19
  x: number;
18
20
  y: number;
21
+ } | {
22
+ type: 'chat';
23
+ chatId: string;
24
+ agentId: string;
25
+ message: string;
19
26
  };
20
27
  export type MonitorCommandHandler = (cmd: MonitorCommand) => void | Promise<void>;
21
28
  export interface MonitorWsHandle {
@@ -1,9 +1,10 @@
1
1
  // src/monitor-ws.ts
2
2
  import { WebSocketServer, WebSocket } from 'ws';
3
+ import { v4 as uuidv4 } from 'uuid';
3
4
  export function createMonitorWs(opts) {
4
5
  const clients = new Set();
5
6
  const wss = new WebSocketServer({ server: opts.server, path: opts.path });
6
- function handleCommand(cmd) {
7
+ function handleCommand(cmd, senderWs) {
7
8
  if (cmd.type === 'reload') {
8
9
  const result = opts.registry.reload(cmd.agentId);
9
10
  // WS protocol is fire-and-forget for commands; on failure, log so the
@@ -18,6 +19,83 @@ export function createMonitorWs(opts) {
18
19
  if (!agent)
19
20
  return;
20
21
  agent.manifest.monitor = { ...agent.manifest.monitor, position: { x: cmd.x, y: cmd.y } };
22
+ return;
23
+ }
24
+ if (cmd.type === 'chat') {
25
+ handleChat(cmd, senderWs).catch((err) => {
26
+ console.error(`[monitor-ws] chat error for "${cmd.agentId}":`, err);
27
+ const errEvent = {
28
+ type: 'chat_response',
29
+ chatId: cmd.chatId,
30
+ agentId: cmd.agentId,
31
+ text: `Error: ${err instanceof Error ? err.message : String(err)}`,
32
+ final: true,
33
+ };
34
+ if (senderWs.readyState === WebSocket.OPEN) {
35
+ senderWs.send(JSON.stringify(errEvent));
36
+ }
37
+ });
38
+ }
39
+ }
40
+ /** Proxy a chat message to the A2A endpoint and stream the reply back. */
41
+ async function handleChat(cmd, senderWs) {
42
+ const body = {
43
+ jsonrpc: '2.0',
44
+ id: 1,
45
+ method: 'message/send',
46
+ params: {
47
+ message: {
48
+ messageId: uuidv4(),
49
+ kind: 'message',
50
+ role: 'user',
51
+ parts: [{ kind: 'text', text: cmd.message }],
52
+ },
53
+ },
54
+ };
55
+ const res = await fetch(`http://localhost:${opts.a2aPort}/a2a/${cmd.agentId}`, {
56
+ method: 'POST',
57
+ headers: { 'Content-Type': 'application/json' },
58
+ body: JSON.stringify(body),
59
+ });
60
+ if (!res.ok) {
61
+ throw new Error(`A2A returned ${res.status}: ${await res.text()}`);
62
+ }
63
+ const json = await res.json();
64
+ // Extract the agent's reply text from the A2A JSON-RPC response
65
+ let replyText = '';
66
+ const result = json.result;
67
+ if (result) {
68
+ // Try artifacts first (main response content)
69
+ const artifacts = result.artifacts ?? [];
70
+ for (const a of artifacts) {
71
+ for (const p of (a.parts ?? [])) {
72
+ if (p.kind === 'text')
73
+ replyText += p.text;
74
+ }
75
+ }
76
+ // Fall back to status message
77
+ if (!replyText) {
78
+ const statusMsg = result.status?.message;
79
+ if (statusMsg) {
80
+ for (const p of (statusMsg.parts ?? [])) {
81
+ if (p.kind === 'text')
82
+ replyText += p.text;
83
+ }
84
+ }
85
+ }
86
+ }
87
+ if (!replyText)
88
+ replyText = '(no response)';
89
+ // Send the response only to the requesting client
90
+ const event = {
91
+ type: 'chat_response',
92
+ chatId: cmd.chatId,
93
+ agentId: cmd.agentId,
94
+ text: replyText,
95
+ final: true,
96
+ };
97
+ if (senderWs.readyState === WebSocket.OPEN) {
98
+ senderWs.send(JSON.stringify(event));
21
99
  }
22
100
  }
23
101
  wss.on('connection', (ws) => {
@@ -40,8 +118,8 @@ export function createMonitorWs(opts) {
40
118
  const parsed = JSON.parse(data.toString());
41
119
  if (parsed &&
42
120
  typeof parsed === 'object' &&
43
- (parsed.type === 'reload' || parsed.type === 'set_position')) {
44
- handleCommand(parsed);
121
+ (parsed.type === 'reload' || parsed.type === 'set_position' || parsed.type === 'chat')) {
122
+ handleCommand(parsed, ws);
45
123
  }
46
124
  }
47
125
  catch {
package/dist/server.d.ts CHANGED
@@ -11,6 +11,8 @@ export interface DojoServer {
11
11
  registry: AgentRegistry;
12
12
  bus: ReturnType<typeof createMonitorBus>;
13
13
  httpServer: ReturnType<typeof createHttpServer>;
14
+ /** Actual monitor WebSocket port (undefined when --no-monitor). */
15
+ monitorPort?: number;
14
16
  close: () => Promise<void>;
15
17
  }
16
18
  export declare function createServer(opts: CreateServerOptions): Promise<DojoServer>;
package/dist/server.js CHANGED
@@ -39,18 +39,28 @@ export async function createServer(opts) {
39
39
  res.json({ dates: listLogDates(logDir) });
40
40
  });
41
41
  const httpServer = createHttpServer(app);
42
- if (opts.monitorPort) {
43
- const monitorHttp = createHttpServer();
44
- createMonitorWs({ server: monitorHttp, bus, path: '/monitor', registry });
42
+ // Listen on main server first so we know the actual port (important when port=0)
43
+ await new Promise((r) => httpServer.listen(opts.port ?? 41241, r));
44
+ const actualA2APort = httpServer.address().port;
45
+ let monitorHttp;
46
+ let actualMonitorPort;
47
+ if (opts.monitorPort !== undefined) {
48
+ // Bind monitor on a separate server (port 0 = OS picks a free port)
49
+ monitorHttp = createHttpServer();
50
+ createMonitorWs({ server: monitorHttp, bus, path: '/monitor', registry, a2aPort: actualA2APort });
45
51
  await new Promise((r) => monitorHttp.listen(opts.monitorPort, r));
52
+ actualMonitorPort = monitorHttp.address().port;
46
53
  }
47
- await new Promise((r) => httpServer.listen(opts.port ?? 41241, r));
48
54
  return {
49
55
  registry,
50
56
  bus,
51
57
  httpServer,
58
+ monitorPort: actualMonitorPort,
52
59
  close: () => new Promise((resolve) => {
53
- httpServer.close(() => resolve());
60
+ const closeMonitor = monitorHttp
61
+ ? new Promise((r) => monitorHttp.close(() => r()))
62
+ : Promise.resolve();
63
+ httpServer.close(() => closeMonitor.then(resolve));
54
64
  }),
55
65
  };
56
66
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-dojo",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "A2A-compatible Agent framework built on Claude Code SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",