macro-agent 0.1.5 → 0.1.7
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/.claude/settings.json +128 -1
- package/.sessionlog/settings.json +4 -0
- package/CLAUDE.md +125 -10
- package/README.md +93 -31
- package/dist/acp/macro-agent.d.ts.map +1 -1
- package/dist/acp/macro-agent.js +1 -3
- package/dist/acp/macro-agent.js.map +1 -1
- package/dist/boot-v2.d.ts +1 -0
- package/dist/boot-v2.d.ts.map +1 -1
- package/dist/boot-v2.js +1 -0
- package/dist/boot-v2.js.map +1 -1
- package/dist/cognitive/workspace-handler.d.ts +17 -9
- package/dist/cognitive/workspace-handler.d.ts.map +1 -1
- package/dist/cognitive/workspace-handler.js +10 -11
- package/dist/cognitive/workspace-handler.js.map +1 -1
- package/dist/map/coordination-handler.d.ts +7 -23
- package/dist/map/coordination-handler.d.ts.map +1 -1
- package/dist/map/coordination-handler.js +124 -100
- package/dist/map/coordination-handler.js.map +1 -1
- package/dist/map/server.d.ts.map +1 -1
- package/dist/map/server.js +13 -3
- package/dist/map/server.js.map +1 -1
- package/dist/map/sidecar.d.ts.map +1 -1
- package/dist/map/sidecar.js +13 -15
- package/dist/map/sidecar.js.map +1 -1
- package/dist/map/trajectory-reporter.d.ts +4 -9
- package/dist/map/trajectory-reporter.d.ts.map +1 -1
- package/dist/map/trajectory-reporter.js +15 -129
- package/dist/map/trajectory-reporter.js.map +1 -1
- package/dist/map/types.d.ts +39 -0
- package/dist/map/types.d.ts.map +1 -1
- package/package.json +2 -3
- package/src/__tests__/e2e/cognitive-workspace.e2e.test.ts +1 -1
- package/src/acp/macro-agent.ts +1 -4
- package/src/boot-v2.ts +2 -0
- package/src/cognitive/__tests__/workspace-handler.test.ts +2 -10
- package/src/cognitive/workspace-handler.ts +18 -15
- package/src/map/__tests__/trajectory-reporter.test.ts +2 -254
- package/src/map/coordination-handler.ts +137 -120
- package/src/map/server.ts +14 -2
- package/src/map/sidecar.ts +13 -20
- package/src/map/trajectory-reporter.ts +16 -154
- package/src/map/types.ts +43 -2
- package/src/__tests__/e2e/trajectory-content.e2e.test.ts +0 -708
- package/src/map/__tests__/coordination-handler.test.ts +0 -598
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for Trajectory Reporter — checkpoint
|
|
2
|
+
* Tests for Trajectory Reporter — checkpoint building & reporting.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { describe, it, expect, beforeEach,
|
|
6
|
-
import * as fs from "node:fs";
|
|
7
|
-
import * as path from "node:path";
|
|
8
|
-
import * as os from "node:os";
|
|
5
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
9
6
|
import {
|
|
10
7
|
createTrajectoryReporter,
|
|
11
8
|
type TrajectoryConnection,
|
|
@@ -174,252 +171,3 @@ describe("TrajectoryReporter", () => {
|
|
|
174
171
|
);
|
|
175
172
|
});
|
|
176
173
|
});
|
|
177
|
-
|
|
178
|
-
// =============================================================================
|
|
179
|
-
// Tests — Content Serving via sessionlog
|
|
180
|
-
// =============================================================================
|
|
181
|
-
|
|
182
|
-
describe("TrajectoryReporter — content serving", () => {
|
|
183
|
-
let conn: ReturnType<typeof mockConnection>;
|
|
184
|
-
let tmpDir: string;
|
|
185
|
-
|
|
186
|
-
beforeEach(() => {
|
|
187
|
-
conn = mockConnection();
|
|
188
|
-
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "trajectory-content-test-"));
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
afterEach(() => {
|
|
192
|
-
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
/** Write a sessionlog-compatible flat state file: <sessionsDir>/<sessionId>.json */
|
|
196
|
-
function writeSessionState(
|
|
197
|
-
sessionsDir: string,
|
|
198
|
-
sessionId: string,
|
|
199
|
-
state: Record<string, unknown>,
|
|
200
|
-
transcript?: string,
|
|
201
|
-
): string {
|
|
202
|
-
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
203
|
-
|
|
204
|
-
const transcriptPath = path.join(sessionsDir, `${sessionId}.jsonl`);
|
|
205
|
-
if (transcript) {
|
|
206
|
-
fs.writeFileSync(transcriptPath, transcript);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
fs.writeFileSync(
|
|
210
|
-
path.join(sessionsDir, `${sessionId}.json`),
|
|
211
|
-
JSON.stringify({
|
|
212
|
-
sessionID: sessionId,
|
|
213
|
-
phase: "active",
|
|
214
|
-
baseCommit: "abc123",
|
|
215
|
-
startedAt: new Date().toISOString(),
|
|
216
|
-
agentType: "claude",
|
|
217
|
-
transcriptPath: transcript ? transcriptPath : undefined,
|
|
218
|
-
...state,
|
|
219
|
-
}),
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
return transcriptPath;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
it("serves transcript from live session matching session ID", async () => {
|
|
226
|
-
const sessionsDir = path.join(tmpDir, "sessions");
|
|
227
|
-
const transcript = [
|
|
228
|
-
JSON.stringify({ type: "user", message: "Fix the bug" }),
|
|
229
|
-
JSON.stringify({ type: "assistant", message: "I'll look into it" }),
|
|
230
|
-
].join("\n");
|
|
231
|
-
|
|
232
|
-
writeSessionState(sessionsDir, "sess-abc", {
|
|
233
|
-
stepCount: 3,
|
|
234
|
-
filesTouched: ["src/main.ts"],
|
|
235
|
-
firstPrompt: "Fix the bug",
|
|
236
|
-
}, transcript);
|
|
237
|
-
|
|
238
|
-
createTrajectoryReporter(conn, {
|
|
239
|
-
trajectorySyncLevel: "full",
|
|
240
|
-
sessionDirs: [sessionsDir],
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
const handler = conn.onNotification.mock.calls[0][1];
|
|
244
|
-
await handler({ request_id: "req-1", checkpoint_id: "sess-abc-step2" });
|
|
245
|
-
|
|
246
|
-
expect(conn.sendNotification).toHaveBeenCalledWith(
|
|
247
|
-
"trajectory/content.response",
|
|
248
|
-
expect.objectContaining({
|
|
249
|
-
request_id: "req-1",
|
|
250
|
-
transcript: expect.stringContaining("Fix the bug"),
|
|
251
|
-
prompts: "Fix the bug",
|
|
252
|
-
metadata: expect.objectContaining({
|
|
253
|
-
sessionID: "sess-abc",
|
|
254
|
-
source: "live",
|
|
255
|
-
}),
|
|
256
|
-
}),
|
|
257
|
-
);
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
it("serves transcript matching checkpoint ID in turnCheckpointIDs", async () => {
|
|
261
|
-
const sessionsDir = path.join(tmpDir, "sessions");
|
|
262
|
-
const transcript = JSON.stringify({ type: "user", message: "Deploy it" }) + "\n";
|
|
263
|
-
|
|
264
|
-
writeSessionState(sessionsDir, "sess-xyz", {
|
|
265
|
-
turnCheckpointIDs: ["sess-xyz-step1", "sess-xyz-step2"],
|
|
266
|
-
firstPrompt: "Deploy it",
|
|
267
|
-
}, transcript);
|
|
268
|
-
|
|
269
|
-
createTrajectoryReporter(conn, {
|
|
270
|
-
trajectorySyncLevel: "full",
|
|
271
|
-
sessionDirs: [sessionsDir],
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
const handler = conn.onNotification.mock.calls[0][1];
|
|
275
|
-
await handler({ request_id: "req-2", checkpoint_id: "sess-xyz-step2" });
|
|
276
|
-
|
|
277
|
-
expect(conn.sendNotification).toHaveBeenCalledWith(
|
|
278
|
-
"trajectory/content.response",
|
|
279
|
-
expect.objectContaining({
|
|
280
|
-
request_id: "req-2",
|
|
281
|
-
transcript: expect.stringContaining("Deploy it"),
|
|
282
|
-
}),
|
|
283
|
-
);
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
it("uses promptAttributions for multi-prompt sessions", async () => {
|
|
287
|
-
const sessionsDir = path.join(tmpDir, "sessions");
|
|
288
|
-
const transcript = JSON.stringify({ type: "user", message: "First" }) + "\n"
|
|
289
|
-
+ JSON.stringify({ type: "user", message: "Second" }) + "\n";
|
|
290
|
-
|
|
291
|
-
writeSessionState(sessionsDir, "sess-multi", {
|
|
292
|
-
firstPrompt: "First",
|
|
293
|
-
promptAttributions: [
|
|
294
|
-
{ prompt: "First", timestamp: "2026-01-01T00:00:00Z", agentLines: 10 },
|
|
295
|
-
{ prompt: "Second", timestamp: "2026-01-01T00:01:00Z", agentLines: 5 },
|
|
296
|
-
],
|
|
297
|
-
}, transcript);
|
|
298
|
-
|
|
299
|
-
createTrajectoryReporter(conn, {
|
|
300
|
-
trajectorySyncLevel: "full",
|
|
301
|
-
sessionDirs: [sessionsDir],
|
|
302
|
-
});
|
|
303
|
-
|
|
304
|
-
const handler = conn.onNotification.mock.calls[0][1];
|
|
305
|
-
await handler({ request_id: "req-3", checkpoint_id: "sess-multi-step1" });
|
|
306
|
-
|
|
307
|
-
expect(conn.sendNotification).toHaveBeenCalledWith(
|
|
308
|
-
"trajectory/content.response",
|
|
309
|
-
expect.objectContaining({
|
|
310
|
-
prompts: "First\n---\nSecond",
|
|
311
|
-
}),
|
|
312
|
-
);
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
it("returns empty response when no session found", async () => {
|
|
316
|
-
createTrajectoryReporter(conn, {
|
|
317
|
-
trajectorySyncLevel: "full",
|
|
318
|
-
sessionDirs: [path.join(tmpDir, "nonexistent")],
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
const handler = conn.onNotification.mock.calls[0][1];
|
|
322
|
-
await handler({ request_id: "req-4", checkpoint_id: "unknown-session-step1" });
|
|
323
|
-
|
|
324
|
-
expect(conn.sendNotification).toHaveBeenCalledWith(
|
|
325
|
-
"trajectory/content.response",
|
|
326
|
-
expect.objectContaining({
|
|
327
|
-
request_id: "req-4",
|
|
328
|
-
transcript: "",
|
|
329
|
-
metadata: expect.objectContaining({ source: "macro-agent" }),
|
|
330
|
-
}),
|
|
331
|
-
);
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
it("serves transcripts from ended sessions (content is still valid)", async () => {
|
|
335
|
-
const sessionsDir = path.join(tmpDir, "sessions");
|
|
336
|
-
const transcript = JSON.stringify({ type: "user", message: "Old session" }) + "\n";
|
|
337
|
-
|
|
338
|
-
writeSessionState(sessionsDir, "sess-ended", {
|
|
339
|
-
phase: "ended",
|
|
340
|
-
}, transcript);
|
|
341
|
-
|
|
342
|
-
createTrajectoryReporter(conn, {
|
|
343
|
-
trajectorySyncLevel: "full",
|
|
344
|
-
sessionDirs: [sessionsDir],
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
const handler = conn.onNotification.mock.calls[0][1];
|
|
348
|
-
await handler({ request_id: "req-5", checkpoint_id: "sess-ended-step1" });
|
|
349
|
-
|
|
350
|
-
expect(conn.sendNotification).toHaveBeenCalledWith(
|
|
351
|
-
"trajectory/content.response",
|
|
352
|
-
expect.objectContaining({
|
|
353
|
-
request_id: "req-5",
|
|
354
|
-
transcript: expect.stringContaining("Old session"),
|
|
355
|
-
}),
|
|
356
|
-
);
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
it("skips sessions with missing transcript path", async () => {
|
|
360
|
-
const sessionsDir = path.join(tmpDir, "sessions");
|
|
361
|
-
|
|
362
|
-
// Write state without transcript file
|
|
363
|
-
writeSessionState(sessionsDir, "sess-no-file", {});
|
|
364
|
-
|
|
365
|
-
createTrajectoryReporter(conn, {
|
|
366
|
-
trajectorySyncLevel: "full",
|
|
367
|
-
sessionDirs: [sessionsDir],
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
const handler = conn.onNotification.mock.calls[0][1];
|
|
371
|
-
await handler({ request_id: "req-6", checkpoint_id: "sess-no-file-step1" });
|
|
372
|
-
|
|
373
|
-
const call = conn.sendNotification.mock.calls[0];
|
|
374
|
-
expect(call[1]).toHaveProperty("transcript", "");
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
it("sends error response when content handler throws", async () => {
|
|
378
|
-
conn.sendNotification
|
|
379
|
-
.mockRejectedValueOnce(new Error("network"))
|
|
380
|
-
.mockResolvedValueOnce(undefined);
|
|
381
|
-
|
|
382
|
-
createTrajectoryReporter(conn, {
|
|
383
|
-
trajectorySyncLevel: "full",
|
|
384
|
-
sessionDirs: [path.join(tmpDir, "nonexistent")],
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
const handler = conn.onNotification.mock.calls[0][1];
|
|
388
|
-
await handler({ request_id: "req-7", checkpoint_id: "any" });
|
|
389
|
-
|
|
390
|
-
// First call fails, second call sends error response
|
|
391
|
-
expect(conn.sendNotification).toHaveBeenCalledTimes(2);
|
|
392
|
-
expect(conn.sendNotification).toHaveBeenLastCalledWith(
|
|
393
|
-
"trajectory/content.response",
|
|
394
|
-
expect.objectContaining({
|
|
395
|
-
request_id: "req-7",
|
|
396
|
-
error: "Content serving failed",
|
|
397
|
-
}),
|
|
398
|
-
);
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
it("searches multiple session directories", async () => {
|
|
402
|
-
const dir1 = path.join(tmpDir, "dir1");
|
|
403
|
-
const dir2 = path.join(tmpDir, "dir2");
|
|
404
|
-
const transcript = JSON.stringify({ type: "user", message: "Found in dir2" }) + "\n";
|
|
405
|
-
|
|
406
|
-
// Only dir2 has the session
|
|
407
|
-
fs.mkdirSync(dir1, { recursive: true });
|
|
408
|
-
writeSessionState(dir2, "sess-multi-dir", {}, transcript);
|
|
409
|
-
|
|
410
|
-
createTrajectoryReporter(conn, {
|
|
411
|
-
trajectorySyncLevel: "full",
|
|
412
|
-
sessionDirs: [dir1, dir2],
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
const handler = conn.onNotification.mock.calls[0][1];
|
|
416
|
-
await handler({ request_id: "req-8", checkpoint_id: "sess-multi-dir-step1" });
|
|
417
|
-
|
|
418
|
-
expect(conn.sendNotification).toHaveBeenCalledWith(
|
|
419
|
-
"trajectory/content.response",
|
|
420
|
-
expect.objectContaining({
|
|
421
|
-
transcript: expect.stringContaining("Found in dir2"),
|
|
422
|
-
}),
|
|
423
|
-
);
|
|
424
|
-
});
|
|
425
|
-
});
|
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Coordination Handler — dispatches inbound coordination messages from the MAP hub.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* Context sharing and messaging use agent-inbox (not MAP).
|
|
7
|
-
* Workspace execution uses x-workspace/* notifications.
|
|
4
|
+
* Handles x-openhive/* JSON-RPC notifications for task assignment, status updates,
|
|
5
|
+
* context sharing, messaging, and workspace execution.
|
|
8
6
|
*
|
|
9
7
|
* @module map/coordination-handler
|
|
10
8
|
*/
|
|
11
9
|
|
|
12
|
-
import {
|
|
10
|
+
import type { AgentManager } from "../agent/agent-manager.js";
|
|
13
11
|
import type { InboxAdapter, TasksAdapter } from "../adapters/types.js";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
payload?: Record<string, unknown>;
|
|
22
|
-
meta?: Record<string, unknown>;
|
|
23
|
-
}
|
|
12
|
+
import type {
|
|
13
|
+
CoordinationTaskAssign,
|
|
14
|
+
CoordinationTaskStatus,
|
|
15
|
+
CoordinationContextShare,
|
|
16
|
+
CoordinationMessage,
|
|
17
|
+
TrajectoryReporter,
|
|
18
|
+
} from "./types.js";
|
|
24
19
|
|
|
25
20
|
export interface CoordinationConnection {
|
|
26
21
|
onNotification(
|
|
@@ -32,15 +27,14 @@ export interface CoordinationConnection {
|
|
|
32
27
|
handler: (params: unknown) => void | Promise<void>,
|
|
33
28
|
): void;
|
|
34
29
|
sendNotification(method: string, params: unknown): Promise<void>;
|
|
35
|
-
onMessage(handler: (message: MAPMessage) => void | Promise<void>): void;
|
|
36
|
-
offMessage(handler: (message: MAPMessage) => void | Promise<void>): void;
|
|
37
30
|
}
|
|
38
31
|
|
|
39
32
|
export interface CoordinationDeps {
|
|
40
33
|
connection: CoordinationConnection;
|
|
41
|
-
|
|
34
|
+
agentManager: AgentManager;
|
|
42
35
|
inboxAdapter: InboxAdapter;
|
|
43
36
|
tasksAdapter: TasksAdapter;
|
|
37
|
+
trajectoryReporter?: TrajectoryReporter;
|
|
44
38
|
/** Workspace handler from cognitive module (if available) */
|
|
45
39
|
workspaceHandler?: {
|
|
46
40
|
handleWorkspaceExecute(params: unknown): Promise<void>;
|
|
@@ -48,138 +42,164 @@ export interface CoordinationDeps {
|
|
|
48
42
|
};
|
|
49
43
|
}
|
|
50
44
|
|
|
51
|
-
/**
|
|
45
|
+
/** Coordination method constants */
|
|
52
46
|
const METHODS = {
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
TASK_ASSIGN: "x-openhive/task.assign",
|
|
48
|
+
TASK_STATUS: "x-openhive/task.status",
|
|
49
|
+
CONTEXT_SHARE: "x-openhive/context.share",
|
|
50
|
+
MESSAGE_SEND: "x-openhive/message.send",
|
|
51
|
+
WORKSPACE_EXECUTE: "x-openhive/learning.workspace.execute",
|
|
55
52
|
} as const;
|
|
56
53
|
|
|
57
54
|
/**
|
|
58
|
-
* Register coordination handlers on the MAP connection.
|
|
59
|
-
*
|
|
60
|
-
* Task operations are handled via MAP scope messages (onMessage).
|
|
61
|
-
* Context sharing and messaging are handled by agent-inbox (not here).
|
|
62
|
-
* Workspace execution uses x-workspace/* notifications.
|
|
55
|
+
* Register coordination notification handlers on the MAP connection.
|
|
63
56
|
* Returns a cleanup function that removes all handlers.
|
|
64
57
|
*/
|
|
65
58
|
export function setupCoordinationHandlers(
|
|
66
59
|
deps: CoordinationDeps,
|
|
67
60
|
): () => void {
|
|
68
|
-
const { connection, inboxAdapter, tasksAdapter } = deps;
|
|
69
|
-
const
|
|
61
|
+
const { connection, agentManager, inboxAdapter, tasksAdapter } = deps;
|
|
62
|
+
const handlers: Array<{ method: string; handler: (params: unknown) => void | Promise<void> }> = [];
|
|
70
63
|
|
|
71
64
|
const register = (
|
|
72
65
|
method: string,
|
|
73
66
|
handler: (params: unknown) => void | Promise<void>,
|
|
74
67
|
): void => {
|
|
75
68
|
connection.onNotification(method, handler);
|
|
76
|
-
|
|
69
|
+
handlers.push({ method, handler });
|
|
77
70
|
};
|
|
78
71
|
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// { type: "task.assigned", taskId, assignee }
|
|
84
|
-
// { type: "task.status", taskId, previous, current }
|
|
85
|
-
// =========================================================================
|
|
86
|
-
|
|
87
|
-
const messageHandler = async (message: MAPMessage): Promise<void> => {
|
|
88
|
-
const payload = message.payload;
|
|
89
|
-
if (!payload || typeof payload.type !== "string") return;
|
|
90
|
-
|
|
91
|
-
// Skip messages we originated (echo prevention)
|
|
92
|
-
const origin = payload._origin as string | undefined;
|
|
93
|
-
if (origin === "macro-agent") return;
|
|
72
|
+
// --- Task Assignment ---
|
|
73
|
+
register(METHODS.TASK_ASSIGN, async (params: unknown) => {
|
|
74
|
+
const p = params as CoordinationTaskAssign;
|
|
75
|
+
if (!p?.title) return;
|
|
94
76
|
|
|
95
77
|
try {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
78
|
+
// Create task in opentasks
|
|
79
|
+
const taskId = await tasksAdapter.createTask({
|
|
80
|
+
title: p.title,
|
|
81
|
+
content: p.description,
|
|
82
|
+
assignee: p.assigned_to,
|
|
83
|
+
priority: p.priority === "critical" ? 1 : p.priority === "high" ? 2 : p.priority === "low" ? 4 : 3,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Optionally spawn an agent to work on the task
|
|
87
|
+
if (p.assigned_to) {
|
|
88
|
+
try {
|
|
89
|
+
await inboxAdapter.send("system", p.assigned_to, {
|
|
90
|
+
type: "event",
|
|
91
|
+
event: "TASK_ASSIGNED",
|
|
92
|
+
data: { taskId, title: p.title, description: p.description },
|
|
107
93
|
});
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
await inboxAdapter
|
|
111
|
-
.send("system", task.assignee, {
|
|
112
|
-
type: "event",
|
|
113
|
-
event: "TASK_ASSIGNED",
|
|
114
|
-
data: { taskId, title: task.title },
|
|
115
|
-
})
|
|
116
|
-
.catch(() => {});
|
|
117
|
-
}
|
|
118
|
-
break;
|
|
94
|
+
} catch {
|
|
95
|
+
// Agent may not exist locally — that's fine
|
|
119
96
|
}
|
|
97
|
+
}
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.warn(
|
|
100
|
+
`[map-sidecar] Failed to handle task.assign: ${(err as Error).message}`,
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
120
104
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
await tasksAdapter.assignTask(taskId, assignee);
|
|
127
|
-
|
|
128
|
-
await inboxAdapter
|
|
129
|
-
.send("system", assignee, {
|
|
130
|
-
type: "event",
|
|
131
|
-
event: "TASK_ASSIGNED",
|
|
132
|
-
data: { taskId },
|
|
133
|
-
})
|
|
134
|
-
.catch(() => {});
|
|
135
|
-
break;
|
|
136
|
-
}
|
|
105
|
+
// --- Task Status ---
|
|
106
|
+
register(METHODS.TASK_STATUS, async (params: unknown) => {
|
|
107
|
+
const p = params as CoordinationTaskStatus;
|
|
108
|
+
if (!p?.task_id || !p?.status) return;
|
|
137
109
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
110
|
+
try {
|
|
111
|
+
const actionMap: Record<string, string> = {
|
|
112
|
+
in_progress: "start",
|
|
113
|
+
completed: "complete",
|
|
114
|
+
closed: "complete",
|
|
115
|
+
failed: "fail",
|
|
116
|
+
blocked: "block",
|
|
117
|
+
open: "reopen",
|
|
118
|
+
};
|
|
119
|
+
const action = actionMap[p.status];
|
|
120
|
+
if (action) {
|
|
121
|
+
await tasksAdapter.transitionTask(p.task_id, action as any);
|
|
122
|
+
}
|
|
123
|
+
} catch (err) {
|
|
124
|
+
console.warn(
|
|
125
|
+
`[map-sidecar] Failed to handle task.status: ${(err as Error).message}`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
157
129
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
130
|
+
// --- Context Share ---
|
|
131
|
+
register(METHODS.CONTEXT_SHARE, async (params: unknown) => {
|
|
132
|
+
const p = params as CoordinationContextShare;
|
|
133
|
+
if (!p?.context_type || !p?.data) return;
|
|
161
134
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
135
|
+
try {
|
|
136
|
+
// Deliver context to all running agents via inbox
|
|
137
|
+
const agents = agentManager.list()
|
|
138
|
+
.filter((a: any) => a.state === "running");
|
|
139
|
+
for (const agent of agents) {
|
|
140
|
+
await inboxAdapter
|
|
141
|
+
.send("system", agent.id, {
|
|
142
|
+
type: "event",
|
|
143
|
+
event: "CONTEXT_SHARED",
|
|
144
|
+
data: {
|
|
145
|
+
context_type: p.context_type,
|
|
146
|
+
data: p.data,
|
|
147
|
+
source: p.source_swarm_id,
|
|
148
|
+
},
|
|
149
|
+
})
|
|
150
|
+
.catch(() => {});
|
|
165
151
|
}
|
|
166
152
|
} catch (err) {
|
|
167
153
|
console.warn(
|
|
168
|
-
`[map-sidecar] Failed to handle
|
|
154
|
+
`[map-sidecar] Failed to handle context.share: ${(err as Error).message}`,
|
|
169
155
|
);
|
|
170
156
|
}
|
|
171
|
-
};
|
|
157
|
+
});
|
|
172
158
|
|
|
173
|
-
|
|
159
|
+
// --- Message Send ---
|
|
160
|
+
register(METHODS.MESSAGE_SEND, async (params: unknown) => {
|
|
161
|
+
const p = params as CoordinationMessage;
|
|
162
|
+
if (!p?.content) return;
|
|
174
163
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
164
|
+
try {
|
|
165
|
+
const agents = agentManager.list()
|
|
166
|
+
.filter((a: any) => a.state === "running");
|
|
167
|
+
if (agents.length === 0) return;
|
|
168
|
+
|
|
169
|
+
// Route to the best target:
|
|
170
|
+
// 1. If to_swarm_id matches a local agent ID, send directly
|
|
171
|
+
// 2. If metadata has a target_agent hint, use it
|
|
172
|
+
// 3. Otherwise, send to the coordinator/head manager (parentless agent)
|
|
173
|
+
// 4. Fallback: first running agent
|
|
174
|
+
const targetId = p.to_swarm_id;
|
|
175
|
+
const directTarget = targetId
|
|
176
|
+
? agents.find((a: any) => a.id === targetId)
|
|
177
|
+
: undefined;
|
|
178
|
+
const coordinator = agents.find((a: any) => !a.parent);
|
|
179
|
+
const target = directTarget ?? coordinator ?? agents[0];
|
|
180
|
+
|
|
181
|
+
await inboxAdapter.send("system", target.id, {
|
|
182
|
+
type: "event",
|
|
183
|
+
event: "EXTERNAL_MESSAGE",
|
|
184
|
+
data: {
|
|
185
|
+
from: p.from_swarm_id,
|
|
186
|
+
content_type: p.content_type,
|
|
187
|
+
content: p.content,
|
|
188
|
+
reply_to: p.reply_to,
|
|
189
|
+
metadata: p.metadata,
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
} catch (err) {
|
|
193
|
+
console.warn(
|
|
194
|
+
`[map-sidecar] Failed to handle message.send: ${(err as Error).message}`,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
178
198
|
|
|
179
199
|
// --- Workspace Execute (delegate to cognitive module) ---
|
|
180
200
|
if (deps.workspaceHandler) {
|
|
181
201
|
const wh = deps.workspaceHandler;
|
|
182
|
-
|
|
202
|
+
register(METHODS.WORKSPACE_EXECUTE, async (params: unknown) => {
|
|
183
203
|
try {
|
|
184
204
|
await wh.handleWorkspaceExecute(params);
|
|
185
205
|
} catch (err) {
|
|
@@ -187,17 +207,14 @@ export function setupCoordinationHandlers(
|
|
|
187
207
|
`[map-sidecar] Failed to handle workspace.execute: ${(err as Error).message}`,
|
|
188
208
|
);
|
|
189
209
|
}
|
|
190
|
-
};
|
|
191
|
-
register(METHODS.WORKSPACE_EXECUTE, workspaceHandler);
|
|
192
|
-
register(METHODS.WORKSPACE_EXECUTE_LEGACY, workspaceHandler);
|
|
210
|
+
});
|
|
193
211
|
}
|
|
194
212
|
|
|
195
213
|
// Return cleanup function
|
|
196
214
|
return () => {
|
|
197
|
-
|
|
198
|
-
for (const { method, handler } of notificationHandlers) {
|
|
215
|
+
for (const { method, handler } of handlers) {
|
|
199
216
|
connection.offNotification(method, handler);
|
|
200
217
|
}
|
|
201
|
-
|
|
218
|
+
handlers.length = 0;
|
|
202
219
|
};
|
|
203
220
|
}
|
package/src/map/server.ts
CHANGED
|
@@ -290,14 +290,26 @@ export function createMAPServerInstance(
|
|
|
290
290
|
const message = data?.message;
|
|
291
291
|
if (!message) return;
|
|
292
292
|
|
|
293
|
+
// Check if this is an ACP envelope — these should always be handled
|
|
294
|
+
// by the bridge, even if the target agent can't be resolved to a
|
|
295
|
+
// specific local agent (the bridge creates a head manager on demand).
|
|
296
|
+
const payload = message?.payload;
|
|
297
|
+
const isAcp = payload && typeof payload === 'object' &&
|
|
298
|
+
'acp' in payload && 'acpContext' in payload;
|
|
299
|
+
|
|
293
300
|
const toField = message.to;
|
|
294
301
|
const mapTargetId = data?.agentId ??
|
|
295
302
|
(typeof toField === "string" ? toField : toField?.agent ?? toField?.id);
|
|
296
303
|
if (!mapTargetId) return;
|
|
297
304
|
|
|
298
305
|
const localAgentId = mapIdToLocalId.get(mapTargetId) ?? mapTargetId;
|
|
299
|
-
|
|
300
|
-
|
|
306
|
+
|
|
307
|
+
// For ACP envelopes, always forward to bridge (it creates sessions on demand).
|
|
308
|
+
// For non-ACP messages, require a local agent to exist.
|
|
309
|
+
if (!isAcp) {
|
|
310
|
+
const localAgent = deps.agentManager.get(localAgentId);
|
|
311
|
+
if (!localAgent) return;
|
|
312
|
+
}
|
|
301
313
|
|
|
302
314
|
// Defer ACP processing to next tick so map/send response goes out first
|
|
303
315
|
setImmediate(() => {
|