agents-dojo 0.1.4 → 0.1.6
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/a2a-server.d.ts +2 -0
- package/dist/a2a-server.js +3 -0
- package/dist/agent-executor.d.ts +7 -0
- package/dist/agent-executor.js +36 -3
- package/dist/claude-bridge.d.ts +4 -0
- package/dist/claude-bridge.js +41 -18
- package/dist/cli.js +5 -46
- package/dist/event-translator.js +1 -1
- package/dist/monitor-ws.d.ts +3 -2
- package/dist/monitor-ws.js +5 -49
- package/dist/server.js +22 -1
- package/package.json +1 -1
package/dist/a2a-server.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type Express } from 'express';
|
|
2
|
+
import { DojoAgentExecutor } from './agent-executor.js';
|
|
2
3
|
import type { AgentRegistry } from './agent-registry.js';
|
|
3
4
|
import type { MonitorBus } from './monitor-bus.js';
|
|
4
5
|
export interface A2AServerOptions {
|
|
@@ -9,6 +10,7 @@ export interface A2AServerOptions {
|
|
|
9
10
|
}
|
|
10
11
|
export interface A2AServerHandle {
|
|
11
12
|
app: Express;
|
|
13
|
+
executors: Map<string, DojoAgentExecutor>;
|
|
12
14
|
close: () => Promise<void>;
|
|
13
15
|
}
|
|
14
16
|
export declare function createA2AServer(opts: A2AServerOptions): A2AServerHandle;
|
package/dist/a2a-server.js
CHANGED
|
@@ -10,6 +10,7 @@ export function createA2AServer(opts) {
|
|
|
10
10
|
const agents = opts.singleAgent
|
|
11
11
|
? [opts.registry.get(opts.singleAgent)].filter((a) => a !== undefined)
|
|
12
12
|
: opts.registry.list().map((id) => opts.registry.get(id)).filter((a) => a !== undefined);
|
|
13
|
+
const executors = new Map();
|
|
13
14
|
for (const loaded of agents) {
|
|
14
15
|
const card = {
|
|
15
16
|
name: loaded.manifest.name,
|
|
@@ -28,6 +29,7 @@ export function createA2AServer(opts) {
|
|
|
28
29
|
};
|
|
29
30
|
const taskStore = new InMemoryTaskStore();
|
|
30
31
|
const executor = new DojoAgentExecutor(loaded, { monitorBus: opts.monitorBus });
|
|
32
|
+
executors.set(loaded.manifest.id, executor);
|
|
31
33
|
const requestHandler = new DefaultRequestHandler(card, taskStore, executor);
|
|
32
34
|
const userBuilder = UserBuilder.noAuthentication;
|
|
33
35
|
app.use(`/a2a/${loaded.manifest.id}`, jsonRpcHandler({ requestHandler, userBuilder }));
|
|
@@ -36,6 +38,7 @@ export function createA2AServer(opts) {
|
|
|
36
38
|
}
|
|
37
39
|
return {
|
|
38
40
|
app,
|
|
41
|
+
executors,
|
|
39
42
|
close: () => new Promise((resolve) => {
|
|
40
43
|
// No actual server here; createServer() handles listening
|
|
41
44
|
resolve();
|
package/dist/agent-executor.d.ts
CHANGED
|
@@ -9,7 +9,14 @@ export declare class DojoAgentExecutor implements AgentExecutor {
|
|
|
9
9
|
private options;
|
|
10
10
|
private controllers;
|
|
11
11
|
private contextIds;
|
|
12
|
+
/** Persistent session ID for this agent — enables cross-call memory. */
|
|
13
|
+
private sessionId;
|
|
12
14
|
constructor(agent: LoadedAgent, options?: AgentExecutorOptions);
|
|
13
15
|
cancelTask: (taskId: string, eventBus: ExecutionEventBus) => Promise<void>;
|
|
14
16
|
execute(requestContext: RequestContext, eventBus: ExecutionEventBus): Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* btw-style one-shot chat: forks the agent's current session, disables all tools,
|
|
19
|
+
* gets a text-only reply, then discards the fork. Does not affect the main session.
|
|
20
|
+
*/
|
|
21
|
+
executeBtw(message: string): Promise<string>;
|
|
15
22
|
}
|
package/dist/agent-executor.js
CHANGED
|
@@ -29,6 +29,8 @@ export class DojoAgentExecutor {
|
|
|
29
29
|
options;
|
|
30
30
|
controllers = new Map();
|
|
31
31
|
contextIds = new Map();
|
|
32
|
+
/** Persistent session ID for this agent — enables cross-call memory. */
|
|
33
|
+
sessionId;
|
|
32
34
|
constructor(agent, options = {}) {
|
|
33
35
|
this.agent = agent;
|
|
34
36
|
this.options = options;
|
|
@@ -133,11 +135,16 @@ export class DojoAgentExecutor {
|
|
|
133
135
|
agent: this.agent,
|
|
134
136
|
contentBlocks,
|
|
135
137
|
contextId,
|
|
138
|
+
resume: this.sessionId,
|
|
136
139
|
onEvent: (m) => {
|
|
137
140
|
translator.onSdkEvent(m);
|
|
138
141
|
sdkLogger.onSdkMessage(m);
|
|
139
|
-
// Capture final text for summary log
|
|
140
142
|
const msg = m;
|
|
143
|
+
// Capture session_id for cross-call continuity
|
|
144
|
+
if (msg.session_id && typeof msg.session_id === 'string') {
|
|
145
|
+
this.sessionId = msg.session_id;
|
|
146
|
+
}
|
|
147
|
+
// Capture final text for summary log
|
|
141
148
|
if (msg.type === 'assistant' && msg.message?.content) {
|
|
142
149
|
for (const block of msg.message.content) {
|
|
143
150
|
if (block.type === 'text')
|
|
@@ -198,14 +205,40 @@ export class DojoAgentExecutor {
|
|
|
198
205
|
});
|
|
199
206
|
}
|
|
200
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* btw-style one-shot chat: forks the agent's current session, disables all tools,
|
|
210
|
+
* gets a text-only reply, then discards the fork. Does not affect the main session.
|
|
211
|
+
*/
|
|
212
|
+
async executeBtw(message) {
|
|
213
|
+
// btw is only meaningful when the agent already has a session to fork from.
|
|
214
|
+
// Without a session, there's no context to branch — just run a plain ephemeral query.
|
|
215
|
+
let reply = '';
|
|
216
|
+
for await (const sdkMsg of runClaude({
|
|
217
|
+
agent: this.agent,
|
|
218
|
+
contentBlocks: [{ type: 'text', text: message }],
|
|
219
|
+
contextId: 'btw-' + Date.now(),
|
|
220
|
+
resume: this.sessionId, // undefined if no session yet — SDK creates a fresh one
|
|
221
|
+
btw: this.sessionId !== undefined, // only fork if there's a session to fork from
|
|
222
|
+
onEvent: (m) => {
|
|
223
|
+
const msg = m;
|
|
224
|
+
// Do NOT capture session_id — btw must never affect the main session
|
|
225
|
+
if (msg.type === 'assistant' && msg.message?.content) {
|
|
226
|
+
for (const block of msg.message.content) {
|
|
227
|
+
if (block.type === 'text')
|
|
228
|
+
reply = block.text;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
})) { /* consume */ }
|
|
233
|
+
return reply || '(no response)';
|
|
234
|
+
}
|
|
201
235
|
}
|
|
202
236
|
function extractPreview(parts) {
|
|
203
237
|
if (!Array.isArray(parts))
|
|
204
238
|
return '';
|
|
205
239
|
for (const p of parts) {
|
|
206
240
|
if (p && typeof p === 'object' && p.kind === 'text' && typeof p.text === 'string') {
|
|
207
|
-
|
|
208
|
-
return t.length > 80 ? t.slice(0, 80) + '…' : t;
|
|
241
|
+
return p.text;
|
|
209
242
|
}
|
|
210
243
|
}
|
|
211
244
|
return '';
|
package/dist/claude-bridge.d.ts
CHANGED
|
@@ -7,5 +7,9 @@ export interface RunClaudeParams {
|
|
|
7
7
|
contextId: string;
|
|
8
8
|
onEvent: (event: SDKMessage) => void;
|
|
9
9
|
abortController?: AbortController;
|
|
10
|
+
/** Resume an existing session (pass the session UUID from a previous query). */
|
|
11
|
+
resume?: string;
|
|
12
|
+
/** btw mode: fork the session, disable all tools, don't persist the fork. */
|
|
13
|
+
btw?: boolean;
|
|
10
14
|
}
|
|
11
15
|
export declare function runClaude(params: RunClaudeParams): AsyncGenerator<SDKMessage>;
|
package/dist/claude-bridge.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/claude-bridge.ts
|
|
2
2
|
import { join } from 'path';
|
|
3
|
+
import { existsSync, mkdirSync, symlinkSync } from 'fs';
|
|
3
4
|
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
4
5
|
export async function* runClaude(params) {
|
|
5
6
|
const { agent, contentBlocks, onEvent, abortController } = params;
|
|
@@ -12,23 +13,42 @@ export async function* runClaude(params) {
|
|
|
12
13
|
PATH: process.env.PATH,
|
|
13
14
|
...(m.env ?? {}),
|
|
14
15
|
};
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
// Each agent stores sessions/config in its own .claude directory by default.
|
|
17
|
+
// This keeps session data co-located with the agent rather than polluting ~/.claude.
|
|
18
|
+
const agentConfigDir = m.configDir
|
|
19
|
+
? (m.configDir.startsWith('/') ? m.configDir : join(agent.agentDir, m.configDir))
|
|
20
|
+
: join(agent.agentDir, '.claude');
|
|
21
|
+
env.CLAUDE_CONFIG_DIR = agentConfigDir;
|
|
22
|
+
// Ensure the agent's .claude dir exists and symlink global settings (auth, permissions).
|
|
23
|
+
// Symlink keeps all agents in sync with the user's global config automatically.
|
|
24
|
+
mkdirSync(agentConfigDir, { recursive: true });
|
|
25
|
+
const globalSettings = join(process.env.HOME ?? '', '.claude', 'settings.json');
|
|
26
|
+
const localSettings = join(agentConfigDir, 'settings.json');
|
|
27
|
+
if (existsSync(globalSettings) && !existsSync(localSettings)) {
|
|
28
|
+
try {
|
|
29
|
+
symlinkSync(globalSettings, localSettings);
|
|
30
|
+
}
|
|
31
|
+
catch { /* race or permission — ignore */ }
|
|
21
32
|
}
|
|
22
|
-
// Build options. Note: we do NOT pass `resume: contextId` here — the Claude
|
|
23
|
-
// SDK treats resume as a previously-issued session ID, and A2A's contextId
|
|
24
|
-
// is a fresh UUID with no corresponding SDK session. Treating every send
|
|
25
|
-
// as a brand-new Claude session is the safe default. (Future: persist a
|
|
26
|
-
// contextId→sessionId mapping if cross-call continuity is required.)
|
|
27
33
|
const options = {
|
|
28
34
|
systemPrompt,
|
|
29
35
|
cwd: agent.agentDir,
|
|
30
36
|
env,
|
|
31
37
|
};
|
|
38
|
+
// Session continuity: resume an existing session if provided
|
|
39
|
+
if (params.resume) {
|
|
40
|
+
options.resume = params.resume;
|
|
41
|
+
}
|
|
42
|
+
// btw mode: disable tools, don't persist. Fork only if resuming an existing session.
|
|
43
|
+
if (params.btw) {
|
|
44
|
+
options.tools = [];
|
|
45
|
+
options.allowedTools = [];
|
|
46
|
+
options.disallowedTools = [];
|
|
47
|
+
options.persistSession = false;
|
|
48
|
+
if (params.resume) {
|
|
49
|
+
options.forkSession = true;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
32
52
|
if (abortController) {
|
|
33
53
|
options.abortController = abortController;
|
|
34
54
|
}
|
|
@@ -44,12 +64,15 @@ export async function* runClaude(params) {
|
|
|
44
64
|
options.pathToClaudeCodeExecutable = m.pathToClaudeCodeExecutable;
|
|
45
65
|
if (m.extraArgs)
|
|
46
66
|
options.extraArgs = m.extraArgs;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
67
|
+
// In btw mode, tools are already disabled — don't let manifest override
|
|
68
|
+
if (!params.btw) {
|
|
69
|
+
if (m.tools)
|
|
70
|
+
options.tools = m.tools;
|
|
71
|
+
if (m.allowedTools)
|
|
72
|
+
options.allowedTools = m.allowedTools;
|
|
73
|
+
if (m.disallowedTools)
|
|
74
|
+
options.disallowedTools = m.disallowedTools;
|
|
75
|
+
}
|
|
53
76
|
if (m.toolAliases)
|
|
54
77
|
options.toolAliases = m.toolAliases;
|
|
55
78
|
if (m.permissionMode)
|
|
@@ -86,7 +109,7 @@ export async function* runClaude(params) {
|
|
|
86
109
|
options.betas = m.betas;
|
|
87
110
|
if (m.outputFormat)
|
|
88
111
|
options.outputFormat = m.outputFormat;
|
|
89
|
-
if (m.forkSession !== undefined)
|
|
112
|
+
if (m.forkSession !== undefined && !params.btw)
|
|
90
113
|
options.forkSession = m.forkSession;
|
|
91
114
|
if (agent.agentsPath)
|
|
92
115
|
options.agents = agent.agentsPath;
|
package/dist/cli.js
CHANGED
|
@@ -188,63 +188,22 @@ Options:
|
|
|
188
188
|
-h, --help Show this help
|
|
189
189
|
`);
|
|
190
190
|
}
|
|
191
|
-
// ── chat command
|
|
191
|
+
// ── chat command (btw mode) ───────────────────────────────
|
|
192
192
|
async function runChat(agentId, message, port) {
|
|
193
|
-
const
|
|
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}`, {
|
|
193
|
+
const res = await fetch(`http://localhost:${port}/btw/${agentId}`, {
|
|
208
194
|
method: 'POST',
|
|
209
195
|
headers: { 'Content-Type': 'application/json' },
|
|
210
|
-
body: JSON.stringify(
|
|
196
|
+
body: JSON.stringify({ message }),
|
|
211
197
|
});
|
|
212
198
|
if (!res.ok) {
|
|
213
|
-
console.error(`Error:
|
|
199
|
+
console.error(`Error: server returned ${res.status}`);
|
|
214
200
|
const text = await res.text();
|
|
215
201
|
if (text)
|
|
216
202
|
console.error(text);
|
|
217
203
|
process.exit(1);
|
|
218
204
|
}
|
|
219
205
|
const json = await res.json();
|
|
220
|
-
|
|
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
|
-
}
|
|
206
|
+
console.log(json.reply ?? '(no response)');
|
|
248
207
|
}
|
|
249
208
|
// ── monitor GUI launcher ─────────────────────────────────
|
|
250
209
|
/** Resolve the monitor/ directory shipped alongside the package. */
|
package/dist/event-translator.js
CHANGED
package/dist/monitor-ws.d.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
2
2
|
import type { Server } from 'http';
|
|
3
3
|
import type { AgentRegistry } from './agent-registry.js';
|
|
4
|
+
import type { DojoAgentExecutor } from './agent-executor.js';
|
|
4
5
|
import type { MonitorBus } from './monitor-bus.js';
|
|
5
6
|
export interface MonitorWsOptions {
|
|
6
7
|
server: Server;
|
|
7
8
|
bus: MonitorBus;
|
|
8
9
|
path: string;
|
|
9
10
|
registry: AgentRegistry;
|
|
10
|
-
/**
|
|
11
|
-
|
|
11
|
+
/** Agent executors — needed for btw-style chat. */
|
|
12
|
+
executors: Map<string, DojoAgentExecutor>;
|
|
12
13
|
}
|
|
13
14
|
export type MonitorCommand = {
|
|
14
15
|
type: 'reload';
|
package/dist/monitor-ws.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// src/monitor-ws.ts
|
|
2
2
|
import { WebSocketServer, WebSocket } from 'ws';
|
|
3
|
-
import { v4 as uuidv4 } from 'uuid';
|
|
4
3
|
export function createMonitorWs(opts) {
|
|
5
4
|
const clients = new Set();
|
|
6
5
|
const wss = new WebSocketServer({ server: opts.server, path: opts.path });
|
|
@@ -37,56 +36,13 @@ export function createMonitorWs(opts) {
|
|
|
37
36
|
});
|
|
38
37
|
}
|
|
39
38
|
}
|
|
40
|
-
/**
|
|
39
|
+
/** btw-style chat: fork agent session, get text-only reply, discard fork. */
|
|
41
40
|
async function handleChat(cmd, senderWs) {
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
}
|
|
41
|
+
const executor = opts.executors.get(cmd.agentId);
|
|
42
|
+
if (!executor) {
|
|
43
|
+
throw new Error(`Agent "${cmd.agentId}" not found`);
|
|
86
44
|
}
|
|
87
|
-
|
|
88
|
-
replyText = '(no response)';
|
|
89
|
-
// Send the response only to the requesting client
|
|
45
|
+
const replyText = await executor.executeBtw(cmd.message);
|
|
90
46
|
const event = {
|
|
91
47
|
type: 'chat_response',
|
|
92
48
|
chatId: cmd.chatId,
|
package/dist/server.js
CHANGED
|
@@ -38,6 +38,27 @@ export async function createServer(opts) {
|
|
|
38
38
|
app.get('/logs/dates', (_req, res) => {
|
|
39
39
|
res.json({ dates: listLogDates(logDir) });
|
|
40
40
|
});
|
|
41
|
+
// btw-style one-shot chat endpoint (forks session, no tools, ephemeral)
|
|
42
|
+
app.post('/btw/:agentId', async (req, res) => {
|
|
43
|
+
const { agentId } = req.params;
|
|
44
|
+
const { message } = req.body;
|
|
45
|
+
if (!message) {
|
|
46
|
+
res.status(400).json({ error: 'message required' });
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const executor = a2a.executors.get(agentId);
|
|
50
|
+
if (!executor) {
|
|
51
|
+
res.status(404).json({ error: `agent "${agentId}" not found` });
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const reply = await executor.executeBtw(message);
|
|
56
|
+
res.json({ reply });
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
41
62
|
const httpServer = createHttpServer(app);
|
|
42
63
|
// Listen on main server first so we know the actual port (important when port=0)
|
|
43
64
|
await new Promise((r) => httpServer.listen(opts.port ?? 41241, r));
|
|
@@ -47,7 +68,7 @@ export async function createServer(opts) {
|
|
|
47
68
|
if (opts.monitorPort !== undefined) {
|
|
48
69
|
// Bind monitor on a separate server (port 0 = OS picks a free port)
|
|
49
70
|
monitorHttp = createHttpServer();
|
|
50
|
-
createMonitorWs({ server: monitorHttp, bus, path: '/monitor', registry,
|
|
71
|
+
createMonitorWs({ server: monitorHttp, bus, path: '/monitor', registry, executors: a2a.executors });
|
|
51
72
|
await new Promise((r) => monitorHttp.listen(opts.monitorPort, r));
|
|
52
73
|
actualMonitorPort = monitorHttp.address().port;
|
|
53
74
|
}
|