macro-agent 0.1.6 → 0.1.8
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.md +14 -6
- 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/agent/agent-manager-v2.d.ts.map +1 -1
- package/dist/agent/agent-manager-v2.js +0 -28
- package/dist/agent/agent-manager-v2.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 +19 -36
- package/dist/boot-v2.js.map +1 -1
- package/dist/map/lifecycle-bridge.d.ts +0 -7
- package/dist/map/lifecycle-bridge.d.ts.map +1 -1
- package/dist/map/lifecycle-bridge.js +28 -9
- package/dist/map/lifecycle-bridge.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 +6 -2
- package/dist/map/sidecar.js.map +1 -1
- package/dist/map/types.d.ts +2 -0
- package/dist/map/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/acp/macro-agent.ts +1 -4
- package/src/agent/__tests__/agent-manager-v2.test.ts +5 -11
- package/src/agent/agent-manager-v2.ts +0 -29
- package/src/boot-v2.ts +21 -35
- package/src/cognitive/__tests__/macro-agent-backend.test.ts +47 -5
- package/src/map/__tests__/lifecycle-bridge.test.ts +84 -17
- package/src/map/lifecycle-bridge.ts +31 -16
- package/src/map/server.ts +14 -2
- package/src/map/sidecar.ts +6 -2
- package/src/map/types.ts +3 -0
- package/dist/boot-v2-DMTHPQ4i.d.ts +0 -2672
|
@@ -12,11 +12,9 @@ import type { TaskBridge } from "../types.js";
|
|
|
12
12
|
import type { Agent } from "../../store/types/index.js";
|
|
13
13
|
|
|
14
14
|
function mockConnection(): LifecycleBridgeConnection & {
|
|
15
|
-
spawn: ReturnType<typeof vi.fn>;
|
|
16
15
|
callExtension: ReturnType<typeof vi.fn>;
|
|
17
16
|
} {
|
|
18
17
|
return {
|
|
19
|
-
spawn: vi.fn().mockResolvedValue({}),
|
|
20
18
|
callExtension: vi.fn().mockResolvedValue({}),
|
|
21
19
|
get isConnected() {
|
|
22
20
|
return true;
|
|
@@ -70,16 +68,61 @@ describe("LifecycleBridge", () => {
|
|
|
70
68
|
agent: mockAgent({ id: "agent-1", name: "worker-1", role: "worker" }),
|
|
71
69
|
});
|
|
72
70
|
|
|
73
|
-
expect(conn.
|
|
71
|
+
expect(conn.callExtension).toHaveBeenCalledWith(
|
|
72
|
+
"map/agents/register",
|
|
74
73
|
expect.objectContaining({
|
|
75
|
-
agentId: "agent-1",
|
|
76
74
|
name: "worker-1",
|
|
77
75
|
role: "worker",
|
|
78
|
-
scopes: [scope],
|
|
79
76
|
}),
|
|
80
77
|
);
|
|
81
78
|
});
|
|
82
79
|
|
|
80
|
+
it("includes per-agent ACP capabilities for coordinators", () => {
|
|
81
|
+
const { callback } = createLifecycleBridge(
|
|
82
|
+
conn,
|
|
83
|
+
{} as AgentStore,
|
|
84
|
+
scope,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
callback({
|
|
88
|
+
type: "spawned",
|
|
89
|
+
agent: mockAgent({ id: "coord-1", name: "coordinator-1", role: "coordinator" }),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(conn.callExtension).toHaveBeenCalledWith(
|
|
93
|
+
"map/agents/register",
|
|
94
|
+
expect.objectContaining({
|
|
95
|
+
name: "coordinator-1",
|
|
96
|
+
role: "coordinator",
|
|
97
|
+
capabilities: expect.objectContaining({
|
|
98
|
+
protocols: ["acp"],
|
|
99
|
+
acp: { version: "2024-10-07" },
|
|
100
|
+
messaging: { canReceive: true },
|
|
101
|
+
}),
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("does not include ACP capabilities for workers", () => {
|
|
107
|
+
const { callback } = createLifecycleBridge(
|
|
108
|
+
conn,
|
|
109
|
+
{} as AgentStore,
|
|
110
|
+
scope,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
callback({
|
|
114
|
+
type: "spawned",
|
|
115
|
+
agent: mockAgent({ id: "worker-1", name: "worker-1", role: "worker" }),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const call = conn.callExtension.mock.calls[0];
|
|
119
|
+
const params = call[1] as Record<string, unknown>;
|
|
120
|
+
const caps = params.capabilities as Record<string, unknown>;
|
|
121
|
+
expect(caps.protocols).toBeUndefined();
|
|
122
|
+
expect(caps.acp).toBeUndefined();
|
|
123
|
+
expect(caps.messaging).toEqual({ canReceive: true });
|
|
124
|
+
});
|
|
125
|
+
|
|
83
126
|
it("unregisters agent from MAP hub on stop event", () => {
|
|
84
127
|
const { callback } = createLifecycleBridge(
|
|
85
128
|
conn,
|
|
@@ -98,12 +141,39 @@ describe("LifecycleBridge", () => {
|
|
|
98
141
|
expect(conn.callExtension).toHaveBeenCalledWith(
|
|
99
142
|
"map/agents/unregister",
|
|
100
143
|
expect.objectContaining({
|
|
101
|
-
agentId: "agent-1",
|
|
102
144
|
reason: "completed",
|
|
103
145
|
}),
|
|
104
146
|
);
|
|
105
147
|
});
|
|
106
148
|
|
|
149
|
+
it("uses MAP-assigned ID for unregistration when available", async () => {
|
|
150
|
+
conn.callExtension.mockResolvedValueOnce({ agent: { id: "map-ulid-1" } });
|
|
151
|
+
|
|
152
|
+
const { callback } = createLifecycleBridge(
|
|
153
|
+
conn,
|
|
154
|
+
{} as AgentStore,
|
|
155
|
+
scope,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
callback({ type: "spawned", agent: mockAgent({ id: "agent-1" }) });
|
|
159
|
+
|
|
160
|
+
// Wait for the .then() that stores mapId
|
|
161
|
+
await new Promise(r => setTimeout(r, 10));
|
|
162
|
+
|
|
163
|
+
callback({
|
|
164
|
+
type: "stopped",
|
|
165
|
+
agent: mockAgent({ id: "agent-1" }),
|
|
166
|
+
reason: "completed",
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
expect(conn.callExtension).toHaveBeenLastCalledWith(
|
|
170
|
+
"map/agents/unregister",
|
|
171
|
+
expect.objectContaining({
|
|
172
|
+
agentId: "map-ulid-1",
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
|
|
107
177
|
it("does nothing when disconnected", () => {
|
|
108
178
|
const disconnected = {
|
|
109
179
|
...conn,
|
|
@@ -119,7 +189,7 @@ describe("LifecycleBridge", () => {
|
|
|
119
189
|
|
|
120
190
|
callback({ type: "spawned", agent: mockAgent() });
|
|
121
191
|
|
|
122
|
-
expect(conn.
|
|
192
|
+
expect(conn.callExtension).not.toHaveBeenCalled();
|
|
123
193
|
});
|
|
124
194
|
|
|
125
195
|
it("bridges task creation on spawn when agent has task_id", () => {
|
|
@@ -178,19 +248,15 @@ describe("LifecycleBridge", () => {
|
|
|
178
248
|
|
|
179
249
|
await cleanup();
|
|
180
250
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
"map/agents/unregister",
|
|
184
|
-
expect.objectContaining({ agentId: "a1" }),
|
|
185
|
-
);
|
|
186
|
-
expect(conn.callExtension).toHaveBeenCalledWith(
|
|
187
|
-
"map/agents/unregister",
|
|
188
|
-
expect.objectContaining({ agentId: "a2" }),
|
|
251
|
+
// 2 register calls + 2 unregister calls
|
|
252
|
+
const unregisterCalls = conn.callExtension.mock.calls.filter(
|
|
253
|
+
(c: any[]) => c[0] === "map/agents/unregister",
|
|
189
254
|
);
|
|
255
|
+
expect(unregisterCalls).toHaveLength(2);
|
|
190
256
|
});
|
|
191
257
|
|
|
192
258
|
it("silently handles MAP call failures", () => {
|
|
193
|
-
conn.
|
|
259
|
+
conn.callExtension.mockRejectedValue(new Error("network error"));
|
|
194
260
|
|
|
195
261
|
const { callback } = createLifecycleBridge(
|
|
196
262
|
conn,
|
|
@@ -216,7 +282,8 @@ describe("LifecycleBridge", () => {
|
|
|
216
282
|
agent: mockAgent({ id: "agent-99", name: undefined }),
|
|
217
283
|
});
|
|
218
284
|
|
|
219
|
-
expect(conn.
|
|
285
|
+
expect(conn.callExtension).toHaveBeenCalledWith(
|
|
286
|
+
"map/agents/register",
|
|
220
287
|
expect.objectContaining({ name: "agent-99" }),
|
|
221
288
|
);
|
|
222
289
|
});
|
|
@@ -13,13 +13,6 @@ import type { TaskBridge } from "./types.js";
|
|
|
13
13
|
|
|
14
14
|
/** Minimal interface for the MAP connection methods we need */
|
|
15
15
|
export interface LifecycleBridgeConnection {
|
|
16
|
-
spawn(options: {
|
|
17
|
-
agentId?: string | undefined;
|
|
18
|
-
name?: string | undefined;
|
|
19
|
-
role?: string | undefined;
|
|
20
|
-
scopes?: string[];
|
|
21
|
-
metadata?: Record<string, unknown>;
|
|
22
|
-
}): Promise<unknown>;
|
|
23
16
|
callExtension(method: string, params?: unknown): Promise<unknown>;
|
|
24
17
|
get isConnected(): boolean;
|
|
25
18
|
}
|
|
@@ -28,6 +21,8 @@ interface RegisteredAgent {
|
|
|
28
21
|
id: string;
|
|
29
22
|
name: string;
|
|
30
23
|
role: string;
|
|
24
|
+
/** MAP-assigned agent ID (ULID) from the hub, used for unregistration */
|
|
25
|
+
mapId?: string;
|
|
31
26
|
}
|
|
32
27
|
|
|
33
28
|
/**
|
|
@@ -55,19 +50,37 @@ export function createLifecycleBridge(
|
|
|
55
50
|
const entry: RegisteredAgent = { id: agent.id, name, role };
|
|
56
51
|
registered.set(agent.id, entry);
|
|
57
52
|
|
|
58
|
-
//
|
|
53
|
+
// Build per-agent capabilities.
|
|
54
|
+
// Coordinators (head managers) support ACP for interactive chat.
|
|
55
|
+
const capabilities: Record<string, unknown> = {
|
|
56
|
+
messaging: { canReceive: true },
|
|
57
|
+
};
|
|
58
|
+
if (role === "coordinator") {
|
|
59
|
+
capabilities.protocols = ["acp"];
|
|
60
|
+
capabilities.acp = { version: "2024-10-07" };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Register agent with MAP hub (use map/agents/register to preserve
|
|
64
|
+
// per-agent capabilities; map/agents/spawn drops them)
|
|
59
65
|
connection
|
|
60
|
-
.
|
|
61
|
-
agentId: agent.id,
|
|
66
|
+
.callExtension("map/agents/register", {
|
|
62
67
|
name,
|
|
63
68
|
role,
|
|
64
|
-
|
|
69
|
+
capabilities,
|
|
65
70
|
metadata: {
|
|
71
|
+
localAgentId: agent.id,
|
|
66
72
|
parent: (agent as any).parent_id ?? undefined,
|
|
67
73
|
team: (agent as any).team ?? undefined,
|
|
68
74
|
cwd: (agent as any).cwd ?? undefined,
|
|
69
75
|
},
|
|
70
76
|
})
|
|
77
|
+
.then((result: any) => {
|
|
78
|
+
// Track the MAP-assigned agent ID for unregistration
|
|
79
|
+
const mapId = result?.agent?.id ?? result?.id;
|
|
80
|
+
if (mapId) {
|
|
81
|
+
entry.mapId = mapId;
|
|
82
|
+
}
|
|
83
|
+
})
|
|
71
84
|
.catch(() => {
|
|
72
85
|
// Silent — MAP hub may be temporarily unavailable
|
|
73
86
|
});
|
|
@@ -88,12 +101,14 @@ export function createLifecycleBridge(
|
|
|
88
101
|
|
|
89
102
|
case "stopped": {
|
|
90
103
|
const agent = event.agent;
|
|
104
|
+
const entry = registered.get(agent.id);
|
|
91
105
|
registered.delete(agent.id);
|
|
92
106
|
|
|
93
|
-
// Unregister agent from MAP hub
|
|
107
|
+
// Unregister agent from MAP hub (use MAP-assigned ID if available)
|
|
108
|
+
const unregId = entry?.mapId ?? agent.id;
|
|
94
109
|
connection
|
|
95
110
|
.callExtension("map/agents/unregister", {
|
|
96
|
-
agentId:
|
|
111
|
+
agentId: unregId,
|
|
97
112
|
reason: event.reason ?? "stopped",
|
|
98
113
|
})
|
|
99
114
|
.catch(() => {
|
|
@@ -123,11 +138,11 @@ export function createLifecycleBridge(
|
|
|
123
138
|
registered.clear();
|
|
124
139
|
return;
|
|
125
140
|
}
|
|
126
|
-
// Unregister all tracked agents
|
|
127
|
-
const promises = Array.from(registered.
|
|
141
|
+
// Unregister all tracked agents (use MAP-assigned IDs)
|
|
142
|
+
const promises = Array.from(registered.values()).map((entry) =>
|
|
128
143
|
connection
|
|
129
144
|
.callExtension("map/agents/unregister", {
|
|
130
|
-
agentId,
|
|
145
|
+
agentId: entry.mapId ?? entry.id,
|
|
131
146
|
reason: "sidecar_shutdown",
|
|
132
147
|
})
|
|
133
148
|
.catch(() => {}),
|
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(() => {
|
package/src/map/sidecar.ts
CHANGED
|
@@ -64,6 +64,12 @@ export function createMAPSidecar(
|
|
|
64
64
|
if (config.token) {
|
|
65
65
|
parsed.searchParams.set("token", config.token);
|
|
66
66
|
}
|
|
67
|
+
// Include swarm_id for stable identity across reconnections.
|
|
68
|
+
// When set, the hub reuses the pre-registered swarm record instead
|
|
69
|
+
// of auto-generating a new one on each connection.
|
|
70
|
+
if (config.swarmId) {
|
|
71
|
+
parsed.searchParams.set("swarm_id", config.swarmId);
|
|
72
|
+
}
|
|
67
73
|
return parsed.toString();
|
|
68
74
|
}
|
|
69
75
|
|
|
@@ -109,8 +115,6 @@ export function createMAPSidecar(
|
|
|
109
115
|
capabilities: {
|
|
110
116
|
messaging: { canSend: true, canReceive: true },
|
|
111
117
|
mail: { canCreate: true, canJoin: true, canViewHistory: true },
|
|
112
|
-
protocols: ['acp'],
|
|
113
|
-
acp: { version: '2024-10-07' },
|
|
114
118
|
trajectory: { canReport: true, canServeContent: false },
|
|
115
119
|
tasks: {
|
|
116
120
|
canCreate: true,
|
package/src/map/types.ts
CHANGED
|
@@ -35,6 +35,9 @@ export interface MAPSidecarConfig {
|
|
|
35
35
|
/** Agent name for MAP registration (default: "macro-agent-sidecar") */
|
|
36
36
|
agentName?: string;
|
|
37
37
|
|
|
38
|
+
/** Swarm ID for stable identity across reconnections */
|
|
39
|
+
swarmId?: string;
|
|
40
|
+
|
|
38
41
|
/** Trajectory sync level */
|
|
39
42
|
trajectorySyncLevel?: "off" | "lifecycle" | "metrics" | "full";
|
|
40
43
|
|