macro-agent 0.0.7 → 0.0.9
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/cli/acp.d.ts.map +1 -1
- package/dist/cli/acp.js +101 -3
- package/dist/cli/acp.js.map +1 -1
- package/dist/map/adapter/acp-over-map.d.ts +77 -0
- package/dist/map/adapter/acp-over-map.d.ts.map +1 -0
- package/dist/map/adapter/acp-over-map.js +372 -0
- package/dist/map/adapter/acp-over-map.js.map +1 -0
- package/dist/map/adapter/index.d.ts +1 -0
- package/dist/map/adapter/index.d.ts.map +1 -1
- package/dist/map/adapter/index.js +2 -0
- package/dist/map/adapter/index.js.map +1 -1
- package/dist/map/adapter/interface.d.ts +22 -0
- package/dist/map/adapter/interface.d.ts.map +1 -1
- package/dist/map/adapter/map-adapter.d.ts +25 -1
- package/dist/map/adapter/map-adapter.d.ts.map +1 -1
- package/dist/map/adapter/map-adapter.js +147 -21
- package/dist/map/adapter/map-adapter.js.map +1 -1
- package/dist/map/adapter/rpc-handler.d.ts.map +1 -1
- package/dist/map/adapter/rpc-handler.js +4 -0
- package/dist/map/adapter/rpc-handler.js.map +1 -1
- package/dist/map/adapter/subscription-manager.js.map +1 -1
- package/dist/server/combined-server.d.ts.map +1 -1
- package/dist/server/combined-server.js +8 -1
- package/dist/server/combined-server.js.map +1 -1
- package/dist/store/event-store.js +7 -1
- package/dist/store/event-store.js.map +1 -1
- package/package.json +2 -2
- package/src/cli/acp.ts +107 -3
- package/src/map/adapter/acp-over-map.ts +516 -0
- package/src/map/adapter/index.ts +8 -0
- package/src/map/adapter/interface.ts +30 -6
- package/src/map/adapter/map-adapter.ts +325 -76
- package/src/map/adapter/rpc-handler.ts +4 -0
- package/src/map/adapter/subscription-manager.ts +10 -10
- package/src/server/combined-server.ts +8 -1
- package/src/store/event-store.ts +8 -2
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACP-over-MAP Handler
|
|
3
|
+
*
|
|
4
|
+
* Handles ACP protocol messages that arrive via MAP instead of direct WebSocket.
|
|
5
|
+
* Provides a bridge between MAP messaging and the MacroAgent ACP implementation.
|
|
6
|
+
*
|
|
7
|
+
* This allows external clients to communicate with macro-agent agents using
|
|
8
|
+
* ACP protocol semantics over the MAP transport layer.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { AgentManager } from "../../agent/agent-manager.js";
|
|
12
|
+
import type { EventStore } from "../../store/event-store.js";
|
|
13
|
+
import type { TaskManager } from "../../task/task-manager.js";
|
|
14
|
+
import type { AgentId } from "../../store/types/index.js";
|
|
15
|
+
import { SessionMapper } from "../../acp/session-mapper.js";
|
|
16
|
+
import type { ACPSessionId } from "../../acp/types.js";
|
|
17
|
+
|
|
18
|
+
// ─────────────────────────────────────────────────────────────────
|
|
19
|
+
// Types
|
|
20
|
+
// ─────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
export interface ACPEnvelope {
|
|
23
|
+
acp: {
|
|
24
|
+
jsonrpc: string;
|
|
25
|
+
id?: string | number;
|
|
26
|
+
method?: string;
|
|
27
|
+
params?: unknown;
|
|
28
|
+
result?: unknown;
|
|
29
|
+
error?: { code: number; message: string; data?: unknown };
|
|
30
|
+
};
|
|
31
|
+
acpContext: {
|
|
32
|
+
streamId: string;
|
|
33
|
+
sessionId?: string;
|
|
34
|
+
direction: string;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ACPOverMAPConfig {
|
|
39
|
+
agentManager: AgentManager;
|
|
40
|
+
eventStore: EventStore;
|
|
41
|
+
taskManager: TaskManager;
|
|
42
|
+
defaultCwd?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Callback for emitting ACP notifications during request processing.
|
|
47
|
+
* Used to stream session updates back to the client.
|
|
48
|
+
*/
|
|
49
|
+
export type ACPNotificationEmitter = (notification: ACPEnvelope) => void;
|
|
50
|
+
|
|
51
|
+
interface StreamState {
|
|
52
|
+
initialized: boolean;
|
|
53
|
+
streamId: string;
|
|
54
|
+
sessionId?: string;
|
|
55
|
+
agentId?: AgentId;
|
|
56
|
+
abortController: AbortController;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ─────────────────────────────────────────────────────────────────
|
|
60
|
+
// ACP-over-MAP Handler
|
|
61
|
+
// ─────────────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
export class ACPOverMAPHandler {
|
|
64
|
+
private agentManager: AgentManager;
|
|
65
|
+
private eventStore: EventStore;
|
|
66
|
+
private taskManager: TaskManager;
|
|
67
|
+
private defaultCwd: string;
|
|
68
|
+
|
|
69
|
+
/** Stream states by streamId */
|
|
70
|
+
private streams: Map<string, StreamState> = new Map();
|
|
71
|
+
|
|
72
|
+
/** Session mapper for ACP session -> Agent mapping */
|
|
73
|
+
private sessionMapper: SessionMapper = new SessionMapper();
|
|
74
|
+
|
|
75
|
+
constructor(config: ACPOverMAPConfig) {
|
|
76
|
+
this.agentManager = config.agentManager;
|
|
77
|
+
this.eventStore = config.eventStore;
|
|
78
|
+
this.taskManager = config.taskManager;
|
|
79
|
+
this.defaultCwd = config.defaultCwd ?? process.cwd();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Process an ACP request and return the response.
|
|
84
|
+
* @param targetAgentId - Target agent for the request
|
|
85
|
+
* @param envelope - ACP request envelope
|
|
86
|
+
* @param emitNotification - Optional callback to emit notifications (for streaming updates)
|
|
87
|
+
*/
|
|
88
|
+
async processRequest(
|
|
89
|
+
targetAgentId: AgentId,
|
|
90
|
+
envelope: ACPEnvelope,
|
|
91
|
+
emitNotification?: ACPNotificationEmitter,
|
|
92
|
+
): Promise<ACPEnvelope> {
|
|
93
|
+
const { acp, acpContext } = envelope;
|
|
94
|
+
const { streamId, sessionId } = acpContext;
|
|
95
|
+
const method = acp.method;
|
|
96
|
+
|
|
97
|
+
console.error(`[ACP-over-MAP] Processing - streamId=${streamId} method=${method}`);
|
|
98
|
+
|
|
99
|
+
// Get or create stream state
|
|
100
|
+
let streamState = this.streams.get(streamId);
|
|
101
|
+
if (!streamState) {
|
|
102
|
+
streamState = {
|
|
103
|
+
initialized: false,
|
|
104
|
+
streamId,
|
|
105
|
+
abortController: new AbortController(),
|
|
106
|
+
};
|
|
107
|
+
this.streams.set(streamId, streamState);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let result: unknown;
|
|
111
|
+
let error: { code: number; message: string; data?: unknown } | undefined;
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
switch (method) {
|
|
115
|
+
case "initialize":
|
|
116
|
+
result = await this.handleInitialize(streamState, acp.params);
|
|
117
|
+
break;
|
|
118
|
+
|
|
119
|
+
case "session/new":
|
|
120
|
+
result = await this.handleNewSession(streamState, acp.params);
|
|
121
|
+
break;
|
|
122
|
+
|
|
123
|
+
case "session/load":
|
|
124
|
+
result = await this.handleLoadSession(streamState, acp.params);
|
|
125
|
+
break;
|
|
126
|
+
|
|
127
|
+
case "authenticate":
|
|
128
|
+
result = await this.handleAuthenticate(acp.params);
|
|
129
|
+
break;
|
|
130
|
+
|
|
131
|
+
case "session/prompt":
|
|
132
|
+
result = await this.handlePrompt(streamState, acp.params, sessionId, emitNotification);
|
|
133
|
+
break;
|
|
134
|
+
|
|
135
|
+
case "session/cancel":
|
|
136
|
+
result = await this.handleCancel(streamState, acp.params, sessionId);
|
|
137
|
+
break;
|
|
138
|
+
|
|
139
|
+
default:
|
|
140
|
+
// Check for extension methods
|
|
141
|
+
if (method?.startsWith("_")) {
|
|
142
|
+
result = await this.handleExtension(streamState, method, acp.params);
|
|
143
|
+
} else {
|
|
144
|
+
throw new Error(`Unknown ACP method: ${method}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
} catch (e) {
|
|
148
|
+
console.error(`[ACP-over-MAP] Error processing ${method}:`, e);
|
|
149
|
+
error = {
|
|
150
|
+
code: -32603,
|
|
151
|
+
message: e instanceof Error ? e.message : String(e),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Build response envelope
|
|
156
|
+
return {
|
|
157
|
+
acp: {
|
|
158
|
+
jsonrpc: "2.0",
|
|
159
|
+
id: acp.id,
|
|
160
|
+
...(error ? { error } : { result }),
|
|
161
|
+
},
|
|
162
|
+
acpContext: {
|
|
163
|
+
streamId,
|
|
164
|
+
sessionId: streamState.sessionId,
|
|
165
|
+
direction: "agent-to-client",
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ─────────────────────────────────────────────────────────────────
|
|
171
|
+
// Core ACP Methods
|
|
172
|
+
// ─────────────────────────────────────────────────────────────────
|
|
173
|
+
|
|
174
|
+
private async handleInitialize(
|
|
175
|
+
streamState: StreamState,
|
|
176
|
+
params: unknown,
|
|
177
|
+
): Promise<unknown> {
|
|
178
|
+
if (streamState.initialized) {
|
|
179
|
+
throw new Error("Stream already initialized");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
streamState.initialized = true;
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
protocolVersion: 1,
|
|
186
|
+
agentCapabilities: {
|
|
187
|
+
loadSession: true,
|
|
188
|
+
_meta: {
|
|
189
|
+
extensions: [
|
|
190
|
+
"_macro/spawnAgent",
|
|
191
|
+
"_macro/getHierarchy",
|
|
192
|
+
"_macro/getTask",
|
|
193
|
+
],
|
|
194
|
+
agentType: "macro-agent",
|
|
195
|
+
transport: "acp-over-map",
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private async handleNewSession(
|
|
202
|
+
streamState: StreamState,
|
|
203
|
+
params: unknown,
|
|
204
|
+
): Promise<unknown> {
|
|
205
|
+
if (!streamState.initialized) {
|
|
206
|
+
throw new Error("Must call initialize before newSession");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const { cwd, mcpServers } = (params as { cwd?: string; mcpServers?: unknown[] }) ?? {};
|
|
210
|
+
const workingDir = cwd ?? this.defaultCwd;
|
|
211
|
+
|
|
212
|
+
// Spawn a new head manager for this session
|
|
213
|
+
const spawned = await this.agentManager.getOrCreateHeadManager({
|
|
214
|
+
cwd: workingDir,
|
|
215
|
+
forceNew: true,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
const sessionId = spawned.session_id;
|
|
219
|
+
streamState.sessionId = sessionId;
|
|
220
|
+
streamState.agentId = spawned.id;
|
|
221
|
+
|
|
222
|
+
// Create session mapping
|
|
223
|
+
this.sessionMapper.createMapping(sessionId as ACPSessionId, spawned.id);
|
|
224
|
+
|
|
225
|
+
console.error(`[ACP-over-MAP] Created session ${sessionId} -> agent ${spawned.id}`);
|
|
226
|
+
|
|
227
|
+
return { sessionId };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private async handleLoadSession(
|
|
231
|
+
streamState: StreamState,
|
|
232
|
+
params: unknown,
|
|
233
|
+
): Promise<unknown> {
|
|
234
|
+
if (!streamState.initialized) {
|
|
235
|
+
throw new Error("Must call initialize before loadSession");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const { sessionId, cwd } = (params as { sessionId: string; cwd?: string }) ?? {};
|
|
239
|
+
if (!sessionId) {
|
|
240
|
+
throw new Error("sessionId required");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const workingDir = cwd ?? this.defaultCwd;
|
|
244
|
+
|
|
245
|
+
// Try to find an existing head manager with this session ID
|
|
246
|
+
const headManagers = this.agentManager.listHeadManagers();
|
|
247
|
+
const existing = headManagers.find((hm) => hm.session_id === sessionId);
|
|
248
|
+
|
|
249
|
+
if (existing) {
|
|
250
|
+
// Check if the agent already has an active session
|
|
251
|
+
if (this.agentManager.hasActiveSession(existing.id)) {
|
|
252
|
+
console.error(`[ACP-over-MAP] loadSession: Reusing existing session for agent ${existing.id}`);
|
|
253
|
+
streamState.sessionId = sessionId;
|
|
254
|
+
streamState.agentId = existing.id;
|
|
255
|
+
this.sessionMapper.createMapping(sessionId as ACPSessionId, existing.id);
|
|
256
|
+
return {};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Agent exists but no active session - resume it
|
|
260
|
+
console.error(`[ACP-over-MAP] loadSession: Resuming stopped agent ${existing.id}`);
|
|
261
|
+
const spawned = await this.agentManager.resume(existing.id);
|
|
262
|
+
streamState.sessionId = sessionId;
|
|
263
|
+
streamState.agentId = spawned.id;
|
|
264
|
+
this.sessionMapper.createMapping(sessionId as ACPSessionId, spawned.id);
|
|
265
|
+
return {};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// No existing agent found - create new with the specified session ID
|
|
269
|
+
console.error(`[ACP-over-MAP] loadSession: Creating new agent for session ${sessionId}`);
|
|
270
|
+
const spawned = await this.agentManager.getOrCreateHeadManager({
|
|
271
|
+
cwd: workingDir,
|
|
272
|
+
sessionId,
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
streamState.sessionId = sessionId;
|
|
276
|
+
streamState.agentId = spawned.id;
|
|
277
|
+
this.sessionMapper.createMapping(sessionId as ACPSessionId, spawned.id);
|
|
278
|
+
|
|
279
|
+
return {};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
private async handleAuthenticate(_params: unknown): Promise<unknown> {
|
|
283
|
+
// No authentication required
|
|
284
|
+
return {};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private async handlePrompt(
|
|
288
|
+
streamState: StreamState,
|
|
289
|
+
params: unknown,
|
|
290
|
+
sessionIdFromContext?: string,
|
|
291
|
+
emitNotification?: ACPNotificationEmitter,
|
|
292
|
+
): Promise<unknown> {
|
|
293
|
+
const { prompt, sessionId: paramSessionId } = (params as {
|
|
294
|
+
prompt?: Array<{ type: string; text?: string }>;
|
|
295
|
+
sessionId?: string;
|
|
296
|
+
messages?: Array<{ role: string; content: string }>;
|
|
297
|
+
}) ?? {};
|
|
298
|
+
|
|
299
|
+
const sessionId = paramSessionId ?? sessionIdFromContext ?? streamState.sessionId;
|
|
300
|
+
if (!sessionId) {
|
|
301
|
+
throw new Error("No session - call newSession or loadSession first");
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Get the mapped agent
|
|
305
|
+
const agentId = this.sessionMapper.getAgentId(sessionId as ACPSessionId);
|
|
306
|
+
if (!agentId) {
|
|
307
|
+
throw new Error(`No agent mapped for session ${sessionId}`);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Reset abort controller if needed
|
|
311
|
+
if (streamState.abortController.signal.aborted) {
|
|
312
|
+
streamState.abortController = new AbortController();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Extract message content
|
|
316
|
+
let messageContent: string;
|
|
317
|
+
if (prompt && Array.isArray(prompt)) {
|
|
318
|
+
messageContent = prompt
|
|
319
|
+
.filter((block) => block.type === "text" && block.text)
|
|
320
|
+
.map((block) => block.text)
|
|
321
|
+
.join("\n");
|
|
322
|
+
} else if ((params as { messages?: Array<{ content: string }> })?.messages) {
|
|
323
|
+
// Handle messages format (role/content array)
|
|
324
|
+
const messages = (params as { messages: Array<{ role: string; content: string }> }).messages;
|
|
325
|
+
messageContent = messages.map((m) => m.content).join("\n");
|
|
326
|
+
} else {
|
|
327
|
+
messageContent = JSON.stringify(params);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
console.error(`[ACP-over-MAP] Prompting agent ${agentId} with: ${messageContent.slice(0, 100)}...`);
|
|
331
|
+
|
|
332
|
+
// Mark session as processing
|
|
333
|
+
this.sessionMapper.setProcessing(sessionId as ACPSessionId, true);
|
|
334
|
+
|
|
335
|
+
// Helper to emit session update notifications
|
|
336
|
+
const emitSessionUpdate = (update: unknown) => {
|
|
337
|
+
if (!emitNotification) return;
|
|
338
|
+
|
|
339
|
+
const notification: ACPEnvelope = {
|
|
340
|
+
acp: {
|
|
341
|
+
jsonrpc: "2.0",
|
|
342
|
+
method: "session/update",
|
|
343
|
+
params: {
|
|
344
|
+
sessionId: sessionId,
|
|
345
|
+
update: update,
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
acpContext: {
|
|
349
|
+
streamId: streamState.streamId,
|
|
350
|
+
sessionId: sessionId,
|
|
351
|
+
direction: "agent-to-client",
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
emitNotification(notification);
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
try {
|
|
358
|
+
// Stream responses from the agent
|
|
359
|
+
let updateCount = 0;
|
|
360
|
+
for await (const update of this.agentManager.prompt(agentId, messageContent)) {
|
|
361
|
+
// Check for cancellation
|
|
362
|
+
if (streamState.abortController.signal.aborted) {
|
|
363
|
+
return { stopReason: "cancelled" };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Stream the update back to the client
|
|
367
|
+
emitSessionUpdate(update);
|
|
368
|
+
updateCount++;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
console.error(`[ACP-over-MAP] Prompt completed for agent ${agentId}, ${updateCount} updates`);
|
|
372
|
+
|
|
373
|
+
return { stopReason: "end_turn" };
|
|
374
|
+
} catch (error) {
|
|
375
|
+
console.error(`[ACP-over-MAP] Prompt error:`, error);
|
|
376
|
+
return {
|
|
377
|
+
stopReason: "end_turn",
|
|
378
|
+
error: error instanceof Error ? error.message : String(error),
|
|
379
|
+
};
|
|
380
|
+
} finally {
|
|
381
|
+
this.sessionMapper.setProcessing(sessionId as ACPSessionId, false);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
private async handleCancel(
|
|
386
|
+
streamState: StreamState,
|
|
387
|
+
params: unknown,
|
|
388
|
+
sessionIdFromContext?: string,
|
|
389
|
+
): Promise<unknown> {
|
|
390
|
+
const { sessionId: paramSessionId } = (params as { sessionId?: string }) ?? {};
|
|
391
|
+
const sessionId = paramSessionId ?? sessionIdFromContext ?? streamState.sessionId;
|
|
392
|
+
|
|
393
|
+
// Signal cancellation
|
|
394
|
+
streamState.abortController.abort();
|
|
395
|
+
|
|
396
|
+
if (!sessionId) {
|
|
397
|
+
return { cancelled: true };
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Get the mapped agent
|
|
401
|
+
const agentId = this.sessionMapper.getAgentId(sessionId as ACPSessionId);
|
|
402
|
+
if (!agentId) {
|
|
403
|
+
return { cancelled: true };
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Terminate the agent
|
|
407
|
+
try {
|
|
408
|
+
await this.agentManager.terminate(agentId, "cancelled");
|
|
409
|
+
} catch (error) {
|
|
410
|
+
console.warn(`[ACP-over-MAP] Error terminating agent ${agentId}:`, error);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Clean up
|
|
414
|
+
this.sessionMapper.removeMapping(sessionId as ACPSessionId);
|
|
415
|
+
|
|
416
|
+
return { cancelled: true };
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// ─────────────────────────────────────────────────────────────────
|
|
420
|
+
// Extension Methods
|
|
421
|
+
// ─────────────────────────────────────────────────────────────────
|
|
422
|
+
|
|
423
|
+
private async handleExtension(
|
|
424
|
+
streamState: StreamState,
|
|
425
|
+
method: string,
|
|
426
|
+
params: unknown,
|
|
427
|
+
): Promise<unknown> {
|
|
428
|
+
const methodParams = params as Record<string, unknown> ?? {};
|
|
429
|
+
|
|
430
|
+
switch (method) {
|
|
431
|
+
case "_macro/spawnAgent": {
|
|
432
|
+
const { task, cwd, topics, config, parentId } = methodParams as {
|
|
433
|
+
task: string;
|
|
434
|
+
cwd?: string;
|
|
435
|
+
topics?: string[];
|
|
436
|
+
config?: Record<string, unknown>;
|
|
437
|
+
parentId?: string;
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
// Use the stream's agent as parent, or find head manager
|
|
441
|
+
let parent = streamState.agentId;
|
|
442
|
+
if (parentId) {
|
|
443
|
+
parent = parentId as AgentId;
|
|
444
|
+
} else if (!parent) {
|
|
445
|
+
const headManagers = this.agentManager.listHeadManagers();
|
|
446
|
+
if (headManagers.length > 0) {
|
|
447
|
+
parent = headManagers[0].id;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (!parent) {
|
|
452
|
+
throw new Error("No parent agent available for spawning");
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const spawned = await this.agentManager.spawn({
|
|
456
|
+
parent,
|
|
457
|
+
task,
|
|
458
|
+
cwd: cwd ?? this.defaultCwd,
|
|
459
|
+
role: "worker",
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
return {
|
|
463
|
+
agentId: spawned.id,
|
|
464
|
+
sessionId: spawned.session_id,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
case "_macro/getHierarchy": {
|
|
469
|
+
const agents = this.agentManager.list();
|
|
470
|
+
return {
|
|
471
|
+
agents: agents.map((a) => ({
|
|
472
|
+
id: a.id,
|
|
473
|
+
role: a.role,
|
|
474
|
+
state: a.state,
|
|
475
|
+
parent: a.parent,
|
|
476
|
+
createdAt: a.created_at,
|
|
477
|
+
})),
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
case "_macro/getTask": {
|
|
482
|
+
const { taskId } = methodParams as { taskId: string };
|
|
483
|
+
const task = await this.taskManager.get(taskId);
|
|
484
|
+
return { task };
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
default:
|
|
488
|
+
throw new Error(`Unknown extension method: ${method}`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// ─────────────────────────────────────────────────────────────────
|
|
493
|
+
// Cleanup
|
|
494
|
+
// ─────────────────────────────────────────────────────────────────
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Clean up a stream when it's closed.
|
|
498
|
+
*/
|
|
499
|
+
closeStream(streamId: string): void {
|
|
500
|
+
const state = this.streams.get(streamId);
|
|
501
|
+
if (state) {
|
|
502
|
+
state.abortController.abort();
|
|
503
|
+
this.streams.delete(streamId);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Clean up all streams.
|
|
509
|
+
*/
|
|
510
|
+
closeAll(): void {
|
|
511
|
+
for (const [streamId, state] of this.streams) {
|
|
512
|
+
state.abortController.abort();
|
|
513
|
+
}
|
|
514
|
+
this.streams.clear();
|
|
515
|
+
}
|
|
516
|
+
}
|
package/src/map/adapter/index.ts
CHANGED
|
@@ -76,6 +76,14 @@ export {
|
|
|
76
76
|
type MAPAdapterServices,
|
|
77
77
|
} from "./map-adapter.js";
|
|
78
78
|
|
|
79
|
+
// ACP-over-MAP support
|
|
80
|
+
export {
|
|
81
|
+
ACPOverMAPHandler,
|
|
82
|
+
type ACPEnvelope,
|
|
83
|
+
type ACPOverMAPConfig,
|
|
84
|
+
type ACPNotificationEmitter,
|
|
85
|
+
} from "./acp-over-map.js";
|
|
86
|
+
|
|
79
87
|
// Event translation
|
|
80
88
|
export {
|
|
81
89
|
translateEvent,
|
|
@@ -75,7 +75,7 @@ export interface ExtensionContext {
|
|
|
75
75
|
*/
|
|
76
76
|
export type ExtensionHandler = (
|
|
77
77
|
context: ExtensionContext,
|
|
78
|
-
params: unknown
|
|
78
|
+
params: unknown,
|
|
79
79
|
) => Promise<unknown>;
|
|
80
80
|
|
|
81
81
|
// =============================================================================
|
|
@@ -154,7 +154,7 @@ export interface AdapterLimits {
|
|
|
154
154
|
*/
|
|
155
155
|
export type AuthenticateHandler = (
|
|
156
156
|
participantType: ParticipantType,
|
|
157
|
-
credentials: AuthCredentials
|
|
157
|
+
credentials: AuthCredentials,
|
|
158
158
|
) => Promise<AuthResult>;
|
|
159
159
|
|
|
160
160
|
/**
|
|
@@ -232,6 +232,28 @@ export interface AgentFilter {
|
|
|
232
232
|
parent?: AgentId;
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
+
/**
|
|
236
|
+
* ACP capability advertisement (matches MAP SDK's ACPCapability).
|
|
237
|
+
*/
|
|
238
|
+
export interface ACPCapability {
|
|
239
|
+
/** ACP protocol version supported (e.g., '2024-10-07') */
|
|
240
|
+
version?: string;
|
|
241
|
+
/** ACP features supported by this agent */
|
|
242
|
+
features?: string[];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Agent capabilities advertised via MAP (matches MAP SDK's ParticipantCapabilities).
|
|
247
|
+
*/
|
|
248
|
+
export interface AgentCapabilities {
|
|
249
|
+
/** Protocols supported by this agent (e.g., ["acp"]) */
|
|
250
|
+
protocols?: string[];
|
|
251
|
+
/** ACP capability details (present if 'acp' is in protocols array) */
|
|
252
|
+
acp?: ACPCapability;
|
|
253
|
+
/** Additional capability flags */
|
|
254
|
+
[key: string]: unknown;
|
|
255
|
+
}
|
|
256
|
+
|
|
235
257
|
/**
|
|
236
258
|
* Agent info returned by queries.
|
|
237
259
|
*/
|
|
@@ -244,6 +266,8 @@ export interface AgentInfo {
|
|
|
244
266
|
scopes: ScopeId[];
|
|
245
267
|
metadata?: Record<string, unknown>;
|
|
246
268
|
createdAt: number;
|
|
269
|
+
/** Agent capabilities (protocols, features) */
|
|
270
|
+
capabilities?: AgentCapabilities;
|
|
247
271
|
}
|
|
248
272
|
|
|
249
273
|
/**
|
|
@@ -368,7 +392,7 @@ export interface MAPAdapter {
|
|
|
368
392
|
participantId: ParticipantId,
|
|
369
393
|
to: Address,
|
|
370
394
|
payload: MessagePayload,
|
|
371
|
-
options?: SendOptions
|
|
395
|
+
options?: SendOptions,
|
|
372
396
|
): Promise<SendResult>;
|
|
373
397
|
|
|
374
398
|
// ===========================================================================
|
|
@@ -388,7 +412,7 @@ export interface MAPAdapter {
|
|
|
388
412
|
*/
|
|
389
413
|
createSubscription(
|
|
390
414
|
participantId: ParticipantId,
|
|
391
|
-
filter?: SubscriptionFilter
|
|
415
|
+
filter?: SubscriptionFilter,
|
|
392
416
|
): Promise<SubscriptionId>;
|
|
393
417
|
|
|
394
418
|
/**
|
|
@@ -440,7 +464,7 @@ export interface MAPAdapter {
|
|
|
440
464
|
*/
|
|
441
465
|
getAgent(
|
|
442
466
|
participantId: ParticipantId,
|
|
443
|
-
agentId: AgentId
|
|
467
|
+
agentId: AgentId,
|
|
444
468
|
): AgentInfo | undefined;
|
|
445
469
|
|
|
446
470
|
/**
|
|
@@ -460,7 +484,7 @@ export interface MAPAdapter {
|
|
|
460
484
|
*/
|
|
461
485
|
getScope(
|
|
462
486
|
participantId: ParticipantId,
|
|
463
|
-
scopeId: ScopeId
|
|
487
|
+
scopeId: ScopeId,
|
|
464
488
|
): ScopeInfo | undefined;
|
|
465
489
|
|
|
466
490
|
// ===========================================================================
|