macro-agent 0.1.4 → 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/dist/cognitive/workspace-handler.js +2 -2
- package/dist/cognitive/workspace-handler.js.map +1 -1
- package/dist/map/coordination-handler.d.ts +23 -7
- package/dist/map/coordination-handler.d.ts.map +1 -1
- package/dist/map/coordination-handler.js +93 -121
- package/dist/map/coordination-handler.js.map +1 -1
- package/dist/map/sidecar.d.ts.map +1 -1
- package/dist/map/sidecar.js +0 -2
- package/dist/map/sidecar.js.map +1 -1
- package/dist/map/trajectory-reporter.d.ts +9 -4
- package/dist/map/trajectory-reporter.d.ts.map +1 -1
- package/dist/map/trajectory-reporter.js +129 -15
- package/dist/map/trajectory-reporter.js.map +1 -1
- package/dist/map/types.d.ts +0 -37
- package/dist/map/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/e2e/cognitive-workspace.e2e.test.ts +1 -1
- package/src/__tests__/e2e/trajectory-content.e2e.test.ts +708 -0
- package/src/cognitive/__tests__/workspace-handler.test.ts +10 -2
- package/src/cognitive/workspace-handler.ts +2 -2
- package/src/map/__tests__/coordination-handler.test.ts +598 -0
- package/src/map/__tests__/trajectory-reporter.test.ts +254 -2
- package/src/map/coordination-handler.ts +113 -134
- package/src/map/sidecar.ts +0 -2
- package/src/map/trajectory-reporter.ts +154 -16
- package/src/map/types.ts +2 -40
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Tests for Trajectory Reporter — checkpoint
|
|
2
|
+
* Tests for Trajectory Reporter — checkpoint reporting & content serving.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
6
|
+
import * as fs from "node:fs";
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
import * as os from "node:os";
|
|
6
9
|
import {
|
|
7
10
|
createTrajectoryReporter,
|
|
8
11
|
type TrajectoryConnection,
|
|
@@ -171,3 +174,252 @@ describe("TrajectoryReporter", () => {
|
|
|
171
174
|
);
|
|
172
175
|
});
|
|
173
176
|
});
|
|
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,22 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Coordination Handler — dispatches inbound coordination messages from the MAP hub.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* status
|
|
4
|
+
* Task operations use generic MAP scope messages (task.created, task.assigned,
|
|
5
|
+
* task.status) matching the wire format used by cc-swarm and opentasks.
|
|
6
|
+
* Context sharing and messaging use agent-inbox (not MAP).
|
|
7
|
+
* Workspace execution uses x-workspace/* notifications.
|
|
6
8
|
*
|
|
7
9
|
* @module map/coordination-handler
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
12
|
import { WORKSPACE_METHODS, WORKSPACE_METHODS_LEGACY } from "agent-workspace";
|
|
11
|
-
import type { AgentManager } from "../agent/agent-manager.js";
|
|
12
13
|
import type { InboxAdapter, TasksAdapter } from "../adapters/types.js";
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
|
|
15
|
+
/** MAP Message shape (subset of @multi-agent-protocol/sdk Message) */
|
|
16
|
+
export interface MAPMessage {
|
|
17
|
+
id: string;
|
|
18
|
+
from: string;
|
|
19
|
+
to: string | { scope: string };
|
|
20
|
+
timestamp: string;
|
|
21
|
+
payload?: Record<string, unknown>;
|
|
22
|
+
meta?: Record<string, unknown>;
|
|
23
|
+
}
|
|
20
24
|
|
|
21
25
|
export interface CoordinationConnection {
|
|
22
26
|
onNotification(
|
|
@@ -28,14 +32,15 @@ export interface CoordinationConnection {
|
|
|
28
32
|
handler: (params: unknown) => void | Promise<void>,
|
|
29
33
|
): void;
|
|
30
34
|
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;
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
export interface CoordinationDeps {
|
|
34
40
|
connection: CoordinationConnection;
|
|
35
|
-
|
|
41
|
+
/** Used by task handlers to notify assignees */
|
|
36
42
|
inboxAdapter: InboxAdapter;
|
|
37
43
|
tasksAdapter: TasksAdapter;
|
|
38
|
-
trajectoryReporter?: TrajectoryReporter;
|
|
39
44
|
/** Workspace handler from cognitive module (if available) */
|
|
40
45
|
workspaceHandler?: {
|
|
41
46
|
handleWorkspaceExecute(params: unknown): Promise<void>;
|
|
@@ -43,160 +48,133 @@ export interface CoordinationDeps {
|
|
|
43
48
|
};
|
|
44
49
|
}
|
|
45
50
|
|
|
46
|
-
/**
|
|
51
|
+
/** Notification method constants (workspace only — task/context/message use MAP messages) */
|
|
47
52
|
const METHODS = {
|
|
48
|
-
TASK_ASSIGN: "x-openhive/task.assign",
|
|
49
|
-
TASK_STATUS: "x-openhive/task.status",
|
|
50
|
-
CONTEXT_SHARE: "x-openhive/context.share",
|
|
51
|
-
MESSAGE_SEND: "x-openhive/message.send",
|
|
52
53
|
WORKSPACE_EXECUTE: WORKSPACE_METHODS.EXECUTE,
|
|
53
54
|
WORKSPACE_EXECUTE_LEGACY: WORKSPACE_METHODS_LEGACY.EXECUTE,
|
|
54
55
|
} as const;
|
|
55
56
|
|
|
56
57
|
/**
|
|
57
|
-
* Register coordination
|
|
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.
|
|
58
63
|
* Returns a cleanup function that removes all handlers.
|
|
59
64
|
*/
|
|
60
65
|
export function setupCoordinationHandlers(
|
|
61
66
|
deps: CoordinationDeps,
|
|
62
67
|
): () => void {
|
|
63
|
-
const { connection,
|
|
64
|
-
const
|
|
68
|
+
const { connection, inboxAdapter, tasksAdapter } = deps;
|
|
69
|
+
const notificationHandlers: Array<{ method: string; handler: (params: unknown) => void | Promise<void> }> = [];
|
|
65
70
|
|
|
66
71
|
const register = (
|
|
67
72
|
method: string,
|
|
68
73
|
handler: (params: unknown) => void | Promise<void>,
|
|
69
74
|
): void => {
|
|
70
75
|
connection.onNotification(method, handler);
|
|
71
|
-
|
|
76
|
+
notificationHandlers.push({ method, handler });
|
|
72
77
|
};
|
|
73
78
|
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
// =========================================================================
|
|
80
|
+
// Task operations — generic MAP scope messages
|
|
81
|
+
// Wire format matches cc-swarm / opentasks MAP Event Bridge:
|
|
82
|
+
// { type: "task.created", task: { id, title, status, assignee } }
|
|
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;
|
|
78
94
|
|
|
79
95
|
try {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
await inboxAdapter.send("system", p.assigned_to, {
|
|
92
|
-
type: "event",
|
|
93
|
-
event: "TASK_ASSIGNED",
|
|
94
|
-
data: { taskId, title: p.title, description: p.description },
|
|
96
|
+
switch (payload.type) {
|
|
97
|
+
case "task.created": {
|
|
98
|
+
const task = payload.task as
|
|
99
|
+
| { id?: string; title?: string; status?: string; assignee?: string }
|
|
100
|
+
| undefined;
|
|
101
|
+
if (!task?.title) return;
|
|
102
|
+
|
|
103
|
+
const taskId = await tasksAdapter.createTask({
|
|
104
|
+
title: task.title,
|
|
105
|
+
content: (task as Record<string, unknown>).description as string | undefined,
|
|
106
|
+
assignee: task.assignee,
|
|
95
107
|
});
|
|
96
|
-
|
|
97
|
-
|
|
108
|
+
|
|
109
|
+
if (task.assignee) {
|
|
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;
|
|
98
119
|
}
|
|
99
|
-
}
|
|
100
|
-
} catch (err) {
|
|
101
|
-
console.warn(
|
|
102
|
-
`[map-sidecar] Failed to handle task.assign: ${(err as Error).message}`,
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
120
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
121
|
+
case "task.assigned": {
|
|
122
|
+
const taskId = payload.taskId as string | undefined;
|
|
123
|
+
const assignee = payload.assignee as string | undefined;
|
|
124
|
+
if (!taskId || !assignee) return;
|
|
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
|
+
}
|
|
111
137
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
138
|
+
case "task.status": {
|
|
139
|
+
const taskId = payload.taskId as string | undefined;
|
|
140
|
+
const current = payload.current as string | undefined;
|
|
141
|
+
if (!taskId || !current) return;
|
|
142
|
+
|
|
143
|
+
const actionMap: Record<string, string> = {
|
|
144
|
+
in_progress: "start",
|
|
145
|
+
completed: "complete",
|
|
146
|
+
closed: "complete",
|
|
147
|
+
failed: "fail",
|
|
148
|
+
blocked: "block",
|
|
149
|
+
open: "reopen",
|
|
150
|
+
};
|
|
151
|
+
const action = actionMap[current];
|
|
152
|
+
if (action) {
|
|
153
|
+
await tasksAdapter.transitionTask(taskId, action as any);
|
|
154
|
+
}
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
131
157
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (!p?.context_type || !p?.data) return;
|
|
158
|
+
// Context sharing and messaging are handled by agent-inbox directly
|
|
159
|
+
// (not through MAP scope messages). See InboxAdapter for broadcast
|
|
160
|
+
// scope delivery and agent-to-agent messaging.
|
|
136
161
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
.filter((a: any) => a.state === "running");
|
|
141
|
-
for (const agent of agents) {
|
|
142
|
-
await inboxAdapter
|
|
143
|
-
.send("system", agent.id, {
|
|
144
|
-
type: "event",
|
|
145
|
-
event: "CONTEXT_SHARED",
|
|
146
|
-
data: {
|
|
147
|
-
context_type: p.context_type,
|
|
148
|
-
data: p.data,
|
|
149
|
-
source: p.source_swarm_id,
|
|
150
|
-
},
|
|
151
|
-
})
|
|
152
|
-
.catch(() => {});
|
|
162
|
+
// Ignore other message types (e.g., task.completed is informational)
|
|
163
|
+
default:
|
|
164
|
+
break;
|
|
153
165
|
}
|
|
154
166
|
} catch (err) {
|
|
155
167
|
console.warn(
|
|
156
|
-
`[map-sidecar] Failed to handle
|
|
168
|
+
`[map-sidecar] Failed to handle ${payload.type}: ${(err as Error).message}`,
|
|
157
169
|
);
|
|
158
170
|
}
|
|
159
|
-
}
|
|
171
|
+
};
|
|
160
172
|
|
|
161
|
-
|
|
162
|
-
register(METHODS.MESSAGE_SEND, async (params: unknown) => {
|
|
163
|
-
const p = params as CoordinationMessage;
|
|
164
|
-
if (!p?.content) return;
|
|
173
|
+
connection.onMessage(messageHandler);
|
|
165
174
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
if (agents.length === 0) return;
|
|
170
|
-
|
|
171
|
-
// Route to the best target:
|
|
172
|
-
// 1. If to_swarm_id matches a local agent ID, send directly
|
|
173
|
-
// 2. If metadata has a target_agent hint, use it
|
|
174
|
-
// 3. Otherwise, send to the coordinator/head manager (parentless agent)
|
|
175
|
-
// 4. Fallback: first running agent
|
|
176
|
-
const targetId = p.to_swarm_id;
|
|
177
|
-
const directTarget = targetId
|
|
178
|
-
? agents.find((a: any) => a.id === targetId)
|
|
179
|
-
: undefined;
|
|
180
|
-
const coordinator = agents.find((a: any) => !a.parent);
|
|
181
|
-
const target = directTarget ?? coordinator ?? agents[0];
|
|
182
|
-
|
|
183
|
-
await inboxAdapter.send("system", target.id, {
|
|
184
|
-
type: "event",
|
|
185
|
-
event: "EXTERNAL_MESSAGE",
|
|
186
|
-
data: {
|
|
187
|
-
from: p.from_swarm_id,
|
|
188
|
-
content_type: p.content_type,
|
|
189
|
-
content: p.content,
|
|
190
|
-
reply_to: p.reply_to,
|
|
191
|
-
metadata: p.metadata,
|
|
192
|
-
},
|
|
193
|
-
});
|
|
194
|
-
} catch (err) {
|
|
195
|
-
console.warn(
|
|
196
|
-
`[map-sidecar] Failed to handle message.send: ${(err as Error).message}`,
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
});
|
|
175
|
+
// =========================================================================
|
|
176
|
+
// Workspace — JSON-RPC notifications (x-workspace protocol)
|
|
177
|
+
// =========================================================================
|
|
200
178
|
|
|
201
179
|
// --- Workspace Execute (delegate to cognitive module) ---
|
|
202
180
|
if (deps.workspaceHandler) {
|
|
@@ -216,9 +194,10 @@ export function setupCoordinationHandlers(
|
|
|
216
194
|
|
|
217
195
|
// Return cleanup function
|
|
218
196
|
return () => {
|
|
219
|
-
|
|
197
|
+
connection.offMessage(messageHandler);
|
|
198
|
+
for (const { method, handler } of notificationHandlers) {
|
|
220
199
|
connection.offNotification(method, handler);
|
|
221
200
|
}
|
|
222
|
-
|
|
201
|
+
notificationHandlers.length = 0;
|
|
223
202
|
};
|
|
224
203
|
}
|
package/src/map/sidecar.ts
CHANGED
|
@@ -275,10 +275,8 @@ export function createMAPSidecar(
|
|
|
275
275
|
const { setupCoordinationHandlers } = await import("./coordination-handler.js");
|
|
276
276
|
coordinationCleanup = setupCoordinationHandlers({
|
|
277
277
|
connection,
|
|
278
|
-
agentManager,
|
|
279
278
|
inboxAdapter,
|
|
280
279
|
tasksAdapter,
|
|
281
|
-
trajectoryReporter,
|
|
282
280
|
workspaceHandler,
|
|
283
281
|
});
|
|
284
282
|
}
|