macro-agent 0.1.12 → 0.2.1
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/acp/macro-agent.d.ts.map +1 -1
- package/dist/acp/macro-agent.js +18 -40
- 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 +241 -8
- package/dist/agent/agent-manager-v2.js.map +1 -1
- package/dist/agent/types.d.ts +47 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/boot-v2.d.ts +33 -0
- package/dist/boot-v2.d.ts.map +1 -1
- package/dist/boot-v2.js +144 -11
- package/dist/boot-v2.js.map +1 -1
- package/dist/cli/acp.js +0 -0
- package/dist/cli/inbox-mcp-proxy.d.ts +36 -0
- package/dist/cli/inbox-mcp-proxy.d.ts.map +1 -0
- package/dist/cli/inbox-mcp-proxy.js +51 -0
- package/dist/cli/inbox-mcp-proxy.js.map +1 -0
- package/dist/cli/index.js +0 -0
- package/dist/cli/mcp.js +0 -0
- package/dist/dispatch/loadout-translation.d.ts +100 -0
- package/dist/dispatch/loadout-translation.d.ts.map +1 -0
- package/dist/dispatch/loadout-translation.js +90 -0
- package/dist/dispatch/loadout-translation.js.map +1 -0
- package/dist/dispatch/mail-inbound-consumer.d.ts +136 -0
- package/dist/dispatch/mail-inbound-consumer.d.ts.map +1 -0
- package/dist/dispatch/mail-inbound-consumer.js +360 -0
- package/dist/dispatch/mail-inbound-consumer.js.map +1 -0
- package/dist/dispatch/mail-inbound-reuse-consumer.d.ts +75 -0
- package/dist/dispatch/mail-inbound-reuse-consumer.d.ts.map +1 -0
- package/dist/dispatch/mail-inbound-reuse-consumer.js +325 -0
- package/dist/dispatch/mail-inbound-reuse-consumer.js.map +1 -0
- package/dist/dispatch/permission-evaluator.d.ts +68 -0
- package/dist/dispatch/permission-evaluator.d.ts.map +1 -0
- package/dist/dispatch/permission-evaluator.js +159 -0
- package/dist/dispatch/permission-evaluator.js.map +1 -0
- package/dist/dispatch/permission-overlay.d.ts +64 -0
- package/dist/dispatch/permission-overlay.d.ts.map +1 -0
- package/dist/dispatch/permission-overlay.js +72 -0
- package/dist/dispatch/permission-overlay.js.map +1 -0
- package/dist/dispatch/permissions-handler.d.ts +71 -0
- package/dist/dispatch/permissions-handler.d.ts.map +1 -0
- package/dist/dispatch/permissions-handler.js +83 -0
- package/dist/dispatch/permissions-handler.js.map +1 -0
- package/dist/dispatch/spawn-agent-handler.d.ts +84 -0
- package/dist/dispatch/spawn-agent-handler.d.ts.map +1 -0
- package/dist/dispatch/spawn-agent-handler.js +85 -0
- package/dist/dispatch/spawn-agent-handler.js.map +1 -0
- package/dist/lifecycle/handlers-v2.d.ts +7 -0
- package/dist/lifecycle/handlers-v2.d.ts.map +1 -1
- package/dist/lifecycle/handlers-v2.js +27 -0
- package/dist/lifecycle/handlers-v2.js.map +1 -1
- package/dist/map/lifecycle-bridge.d.ts +18 -0
- package/dist/map/lifecycle-bridge.d.ts.map +1 -1
- package/dist/map/lifecycle-bridge.js +23 -1
- package/dist/map/lifecycle-bridge.js.map +1 -1
- package/dist/map/mail-bridge.d.ts +55 -0
- package/dist/map/mail-bridge.d.ts.map +1 -0
- package/dist/map/mail-bridge.js +115 -0
- package/dist/map/mail-bridge.js.map +1 -0
- package/dist/map/repo-workspace.d.ts +46 -0
- package/dist/map/repo-workspace.d.ts.map +1 -0
- package/dist/map/repo-workspace.js +39 -0
- package/dist/map/repo-workspace.js.map +1 -0
- package/dist/map/server.d.ts.map +1 -1
- package/dist/map/server.js +1 -0
- package/dist/map/server.js.map +1 -1
- package/dist/map/sidecar.d.ts.map +1 -1
- package/dist/map/sidecar.js +308 -1
- package/dist/map/sidecar.js.map +1 -1
- package/dist/map/types.d.ts +29 -0
- package/dist/map/types.d.ts.map +1 -1
- package/dist/mcp/tools/done-v2.d.ts.map +1 -1
- package/dist/mcp/tools/done-v2.js +1 -0
- package/dist/mcp/tools/done-v2.js.map +1 -1
- package/dist/teams/team-loader.d.ts.map +1 -1
- package/dist/teams/team-loader.js.map +1 -1
- package/dist/teams/team-runtime-v2.d.ts.map +1 -1
- package/dist/teams/team-runtime-v2.js +2 -0
- package/dist/teams/team-runtime-v2.js.map +1 -1
- package/package.json +7 -5
- package/src/acp/macro-agent.ts +20 -42
- package/src/agent/__tests__/agent-manager-v2.permission-interception.test.ts +296 -0
- package/src/agent/__tests__/agent-manager-v2.permissions.test.ts +233 -0
- package/src/agent/agent-manager-v2.ts +269 -8
- package/src/agent/types.ts +51 -0
- package/src/boot-v2.ts +192 -12
- package/src/cli/inbox-mcp-proxy.ts +56 -0
- package/src/dispatch/CLAUDE.md +129 -0
- package/src/dispatch/__tests__/loadout-translation.test.ts +141 -0
- package/src/dispatch/__tests__/mail-inbound-consumer.integration.test.ts +519 -0
- package/src/dispatch/__tests__/mail-inbound-consumer.test.ts +800 -0
- package/src/dispatch/__tests__/mail-inbound-reuse-consumer.test.ts +575 -0
- package/src/dispatch/__tests__/permission-evaluator.test.ts +196 -0
- package/src/dispatch/__tests__/permission-overlay.test.ts +56 -0
- package/src/dispatch/__tests__/permissions-handler.test.ts +168 -0
- package/src/dispatch/__tests__/spawn-agent-handler.test.ts +282 -0
- package/src/dispatch/loadout-translation.ts +138 -0
- package/src/dispatch/mail-inbound-consumer.ts +560 -0
- package/src/dispatch/mail-inbound-reuse-consumer.ts +479 -0
- package/src/dispatch/permission-evaluator.ts +191 -0
- package/src/dispatch/permission-overlay.ts +89 -0
- package/src/dispatch/permissions-handler.ts +112 -0
- package/src/dispatch/spawn-agent-handler.ts +160 -0
- package/src/lifecycle/handlers-v2.ts +34 -0
- package/src/map/__tests__/lifecycle-bridge.test.ts +64 -0
- package/src/map/__tests__/mail-bridge.test.ts +196 -0
- package/src/map/lifecycle-bridge.ts +48 -2
- package/src/map/mail-bridge.ts +203 -0
- package/src/map/repo-workspace.ts +82 -0
- package/src/map/server.ts +1 -0
- package/src/map/sidecar.ts +431 -1
- package/src/map/types.ts +34 -0
- package/src/mcp/tools/done-v2.ts +1 -0
- package/src/teams/team-loader.ts +3 -1
- package/src/teams/team-runtime-v2.ts +2 -0
package/src/map/sidecar.ts
CHANGED
|
@@ -23,6 +23,13 @@ import type {
|
|
|
23
23
|
TaskBridge,
|
|
24
24
|
} from "./types.js";
|
|
25
25
|
import type { AgentLifecycleCallback } from "../agent/types.js";
|
|
26
|
+
import {
|
|
27
|
+
REPO_PROTOCOL_VERSION,
|
|
28
|
+
RepoClient,
|
|
29
|
+
RepoManager,
|
|
30
|
+
type RepoClientTransport,
|
|
31
|
+
type WorkspaceCapability,
|
|
32
|
+
} from "./repo-workspace.js";
|
|
26
33
|
|
|
27
34
|
/**
|
|
28
35
|
* Create a MAP sidecar that connects macro-agent to an OpenHive MAP hub.
|
|
@@ -35,7 +42,7 @@ export function createMAPSidecar(
|
|
|
35
42
|
deps: MAPSidecarDeps,
|
|
36
43
|
config: MAPSidecarConfig,
|
|
37
44
|
): MAPSidecar {
|
|
38
|
-
const { agentManager, agentStore, inboxAdapter, tasksAdapter, getLocalMapId, gitCascadeAdapter } = deps;
|
|
45
|
+
const { agentManager, agentStore, inboxAdapter, tasksAdapter, getLocalMapId, gitCascadeAdapter, dispatcherAgentId } = deps;
|
|
39
46
|
const scope = config.scope ?? "swarm:macro-agent";
|
|
40
47
|
const agentName = config.agentName ?? "macro-agent-sidecar";
|
|
41
48
|
|
|
@@ -51,8 +58,29 @@ export function createMAPSidecar(
|
|
|
51
58
|
let taskBridge: TaskBridge | null = null;
|
|
52
59
|
let coordinationCleanup: (() => void) | null = null;
|
|
53
60
|
let cascadeBridgeCleanup: (() => void) | null = null;
|
|
61
|
+
let mailBridgeCleanup: (() => void) | null = null;
|
|
62
|
+
let dispatchSpawnHandlerCleanup: (() => void) | null = null;
|
|
63
|
+
let dispatchMessageHandlerCleanup: (() => void) | null = null;
|
|
64
|
+
let dispatchPermissionsHandlerCleanup: (() => void) | null = null;
|
|
65
|
+
let workspaceManager: RepoManager | null = null;
|
|
66
|
+
let workspaceTransport: RepoClientTransport | null = null;
|
|
54
67
|
let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
55
68
|
|
|
69
|
+
// Resolve the workspace capability from env vars. Setting OPENHIVE_WORKSPACE_DECLARE=off
|
|
70
|
+
// disables both explicit declare AND trajectory-handler bootstrap on the hub side.
|
|
71
|
+
const workspaceCapability: WorkspaceCapability = {
|
|
72
|
+
protocolVersion: REPO_PROTOCOL_VERSION,
|
|
73
|
+
declare: {
|
|
74
|
+
enabled: process.env.OPENHIVE_WORKSPACE_DECLARE !== "off",
|
|
75
|
+
defaultVisibility:
|
|
76
|
+
(process.env.OPENHIVE_WORKSPACE_VISIBILITY as
|
|
77
|
+
| "private"
|
|
78
|
+
| "hub_local"
|
|
79
|
+
| "federated") ?? "hub_local",
|
|
80
|
+
},
|
|
81
|
+
list: { enabled: true },
|
|
82
|
+
};
|
|
83
|
+
|
|
56
84
|
/**
|
|
57
85
|
* Build the MAP connection URL with auth token.
|
|
58
86
|
*/
|
|
@@ -83,10 +111,26 @@ export function createMAPSidecar(
|
|
|
83
111
|
coordinationCleanup();
|
|
84
112
|
coordinationCleanup = null;
|
|
85
113
|
}
|
|
114
|
+
if (mailBridgeCleanup) {
|
|
115
|
+
try { mailBridgeCleanup(); } catch { /* non-critical */ }
|
|
116
|
+
mailBridgeCleanup = null;
|
|
117
|
+
}
|
|
86
118
|
if (cascadeBridgeCleanup) {
|
|
87
119
|
try { cascadeBridgeCleanup(); } catch { /* non-critical */ }
|
|
88
120
|
cascadeBridgeCleanup = null;
|
|
89
121
|
}
|
|
122
|
+
if (dispatchMessageHandlerCleanup) {
|
|
123
|
+
try { dispatchMessageHandlerCleanup(); } catch { /* non-critical */ }
|
|
124
|
+
dispatchMessageHandlerCleanup = null;
|
|
125
|
+
}
|
|
126
|
+
if (dispatchSpawnHandlerCleanup) {
|
|
127
|
+
try { dispatchSpawnHandlerCleanup(); } catch { /* non-critical */ }
|
|
128
|
+
dispatchSpawnHandlerCleanup = null;
|
|
129
|
+
}
|
|
130
|
+
if (dispatchPermissionsHandlerCleanup) {
|
|
131
|
+
try { dispatchPermissionsHandlerCleanup(); } catch { /* non-critical */ }
|
|
132
|
+
dispatchPermissionsHandlerCleanup = null;
|
|
133
|
+
}
|
|
90
134
|
if (trajectoryReporter) {
|
|
91
135
|
trajectoryReporter.stop();
|
|
92
136
|
trajectoryReporter = null;
|
|
@@ -101,6 +145,8 @@ export function createMAPSidecar(
|
|
|
101
145
|
}
|
|
102
146
|
lifecycleCallback = null;
|
|
103
147
|
taskBridge = null;
|
|
148
|
+
workspaceManager = null;
|
|
149
|
+
workspaceTransport = null;
|
|
104
150
|
}
|
|
105
151
|
|
|
106
152
|
/**
|
|
@@ -127,6 +173,7 @@ export function createMAPSidecar(
|
|
|
127
173
|
canUpdate: true,
|
|
128
174
|
canList: true,
|
|
129
175
|
},
|
|
176
|
+
workspace: workspaceCapability,
|
|
130
177
|
},
|
|
131
178
|
metadata: {
|
|
132
179
|
systemId: config.systemId ?? "macro-agent",
|
|
@@ -278,6 +325,8 @@ export function createMAPSidecar(
|
|
|
278
325
|
);
|
|
279
326
|
lifecycleCallback = bridge.callback;
|
|
280
327
|
lifecycleCleanup = bridge.cleanup;
|
|
328
|
+
const awaitAcpRegistration = bridge.awaitRegistration;
|
|
329
|
+
const findLocalAgentByMapId = bridge.findLocalAgentByMapId;
|
|
281
330
|
lifecycleUnsubscribe = agentManager.onLifecycleEvent(lifecycleCallback);
|
|
282
331
|
|
|
283
332
|
// 3. Trajectory Reporter
|
|
@@ -294,6 +343,300 @@ export function createMAPSidecar(
|
|
|
294
343
|
trajectoryReporter,
|
|
295
344
|
});
|
|
296
345
|
|
|
346
|
+
// 4b. Mail Bridge — forwards `mail/turn.received` notifications from the
|
|
347
|
+
// hub into the local agent-inbox so swarm-dispatch's MessagePort can
|
|
348
|
+
// pick them up via its `inbox.events` subscription. Without this,
|
|
349
|
+
// hub-side mail never reaches the dispatcher.
|
|
350
|
+
const { setupMailBridge } = await import("./mail-bridge.js");
|
|
351
|
+
mailBridgeCleanup = await setupMailBridge({
|
|
352
|
+
connection,
|
|
353
|
+
inboxAdapter,
|
|
354
|
+
dispatcherAgentId,
|
|
355
|
+
log: (msg) => console.log(msg),
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// 4c. x-dispatch/spawn-agent handler — notification-pair pattern.
|
|
359
|
+
//
|
|
360
|
+
// The MAP SDK's AgentConnection doesn't expose setRequestHandler, so
|
|
361
|
+
// the hub→swarm spawn-agent "request" is sent as a notification
|
|
362
|
+
// with a correlation_id. We process and reply with a `.response`
|
|
363
|
+
// notification carrying the same correlation_id (or an error).
|
|
364
|
+
//
|
|
365
|
+
// Dual-listen: subscribe to BOTH the canonical
|
|
366
|
+
// `x-dispatch/spawn-agent.request` (Tier 2+, owned by swarm-dispatch)
|
|
367
|
+
// AND the legacy `dispatch/spawn-agent.request` for one release
|
|
368
|
+
// window. Reply on the matching channel — the hub's response
|
|
369
|
+
// dispatcher accepts both.
|
|
370
|
+
const { handleDispatchSpawnAgent } = await import(
|
|
371
|
+
"../dispatch/spawn-agent-handler.js"
|
|
372
|
+
);
|
|
373
|
+
const {
|
|
374
|
+
handleSpawnAgentRequest,
|
|
375
|
+
X_DISPATCH_METHODS: SPAWN_METHODS,
|
|
376
|
+
LEGACY_DISPATCH_SPAWN_AGENT_REQUEST,
|
|
377
|
+
LEGACY_DISPATCH_SPAWN_AGENT_RESPONSE,
|
|
378
|
+
} = await import("swarm-dispatch/client");
|
|
379
|
+
|
|
380
|
+
const makeSpawnHandler = (
|
|
381
|
+
responseMethod: string,
|
|
382
|
+
): ((params: unknown) => Promise<void>) =>
|
|
383
|
+
async (params) => {
|
|
384
|
+
await handleSpawnAgentRequest({
|
|
385
|
+
params,
|
|
386
|
+
runtime: {
|
|
387
|
+
async spawn(req) {
|
|
388
|
+
return handleDispatchSpawnAgent(
|
|
389
|
+
req as unknown as Parameters<typeof handleDispatchSpawnAgent>[0],
|
|
390
|
+
{
|
|
391
|
+
agentManager,
|
|
392
|
+
// Wait barrier: lifecycle-bridge resolves once
|
|
393
|
+
// `map/agents/register` completes, so the orchestrator's
|
|
394
|
+
// subsequent `findAcpAgentInfo` lookup doesn't race.
|
|
395
|
+
waitForAcpRegistration: awaitAcpRegistration,
|
|
396
|
+
log: (msg) => console.log(msg),
|
|
397
|
+
},
|
|
398
|
+
);
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
sendResponse: async (responseParams) => {
|
|
402
|
+
await connection.sendNotification(responseMethod, responseParams);
|
|
403
|
+
},
|
|
404
|
+
log: (msg) => console.log(msg),
|
|
405
|
+
});
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
const canonicalSpawnHandler = makeSpawnHandler(
|
|
409
|
+
SPAWN_METHODS.SPAWN_AGENT_RESPONSE,
|
|
410
|
+
);
|
|
411
|
+
const legacySpawnHandler = makeSpawnHandler(
|
|
412
|
+
LEGACY_DISPATCH_SPAWN_AGENT_RESPONSE,
|
|
413
|
+
);
|
|
414
|
+
|
|
415
|
+
connection.onNotification(
|
|
416
|
+
SPAWN_METHODS.SPAWN_AGENT_REQUEST,
|
|
417
|
+
canonicalSpawnHandler,
|
|
418
|
+
);
|
|
419
|
+
connection.onNotification(
|
|
420
|
+
LEGACY_DISPATCH_SPAWN_AGENT_REQUEST,
|
|
421
|
+
legacySpawnHandler,
|
|
422
|
+
);
|
|
423
|
+
dispatchSpawnHandlerCleanup = () => {
|
|
424
|
+
try {
|
|
425
|
+
if (typeof connection.offNotification === "function") {
|
|
426
|
+
connection.offNotification(
|
|
427
|
+
SPAWN_METHODS.SPAWN_AGENT_REQUEST,
|
|
428
|
+
canonicalSpawnHandler,
|
|
429
|
+
);
|
|
430
|
+
connection.offNotification(
|
|
431
|
+
LEGACY_DISPATCH_SPAWN_AGENT_REQUEST,
|
|
432
|
+
legacySpawnHandler,
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
} catch {
|
|
436
|
+
/* connection already torn down */
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
// 4c'. x-dispatch/permissions.{set,clear} handlers — notification-pair
|
|
441
|
+
// pattern, mirrors spawn-agent's shape. Used by hubs (e.g. OpenHive's
|
|
442
|
+
// ACP+reuse dispatch path) to apply per-dispatch loadout deny/allow
|
|
443
|
+
// rules to a long-lived agent's session at runtime via the
|
|
444
|
+
// permission-overlay registry. The prompt iterator in
|
|
445
|
+
// `agent-manager-v2.ts` enforces the overlay against
|
|
446
|
+
// `permission_request` ACP session updates. Pairs with the mail+reuse
|
|
447
|
+
// path's overlay set/clear in `mail-inbound-reuse-consumer.ts` —
|
|
448
|
+
// same registry, different transport.
|
|
449
|
+
const {
|
|
450
|
+
handlePermissionsSet,
|
|
451
|
+
handlePermissionsClear,
|
|
452
|
+
X_DISPATCH_PERMISSIONS_METHODS,
|
|
453
|
+
} = await import("../dispatch/permissions-handler.js");
|
|
454
|
+
|
|
455
|
+
// Response shape per swarm-dispatch's notification-rpc registry:
|
|
456
|
+
// { correlation_id, result } → resolve(result)
|
|
457
|
+
// { correlation_id, error: { code?, message? } } → reject
|
|
458
|
+
// The handler's `{ ok, error?: string }` is wrapped accordingly.
|
|
459
|
+
const sendPermissionsResponse = async (
|
|
460
|
+
method: string,
|
|
461
|
+
correlationId: string | undefined,
|
|
462
|
+
result: { ok: true } | { ok: false; error: string },
|
|
463
|
+
): Promise<void> => {
|
|
464
|
+
const body: Record<string, unknown> = {};
|
|
465
|
+
if (correlationId) body.correlation_id = correlationId;
|
|
466
|
+
if (result.ok) {
|
|
467
|
+
body.result = result;
|
|
468
|
+
} else {
|
|
469
|
+
body.error = { message: result.error };
|
|
470
|
+
}
|
|
471
|
+
try {
|
|
472
|
+
await connection.sendNotification(method, body);
|
|
473
|
+
} catch (err) {
|
|
474
|
+
console.warn(
|
|
475
|
+
`[${method}] response send failed: ${(err as Error).message}`,
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
const permissionsSetHandler = async (params: unknown): Promise<void> => {
|
|
481
|
+
const correlationId =
|
|
482
|
+
(params as { correlation_id?: string })?.correlation_id;
|
|
483
|
+
const result = handlePermissionsSet(
|
|
484
|
+
params as Parameters<typeof handlePermissionsSet>[0],
|
|
485
|
+
(msg) => console.log(msg),
|
|
486
|
+
);
|
|
487
|
+
await sendPermissionsResponse(
|
|
488
|
+
X_DISPATCH_PERMISSIONS_METHODS.SET_RESPONSE,
|
|
489
|
+
correlationId,
|
|
490
|
+
result,
|
|
491
|
+
);
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
const permissionsClearHandler = async (params: unknown): Promise<void> => {
|
|
495
|
+
const correlationId =
|
|
496
|
+
(params as { correlation_id?: string })?.correlation_id;
|
|
497
|
+
const result = handlePermissionsClear(
|
|
498
|
+
params as Parameters<typeof handlePermissionsClear>[0],
|
|
499
|
+
(msg) => console.log(msg),
|
|
500
|
+
);
|
|
501
|
+
await sendPermissionsResponse(
|
|
502
|
+
X_DISPATCH_PERMISSIONS_METHODS.CLEAR_RESPONSE,
|
|
503
|
+
correlationId,
|
|
504
|
+
result,
|
|
505
|
+
);
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
connection.onNotification(
|
|
509
|
+
X_DISPATCH_PERMISSIONS_METHODS.SET_REQUEST,
|
|
510
|
+
permissionsSetHandler,
|
|
511
|
+
);
|
|
512
|
+
connection.onNotification(
|
|
513
|
+
X_DISPATCH_PERMISSIONS_METHODS.CLEAR_REQUEST,
|
|
514
|
+
permissionsClearHandler,
|
|
515
|
+
);
|
|
516
|
+
dispatchPermissionsHandlerCleanup = () => {
|
|
517
|
+
try {
|
|
518
|
+
if (typeof connection.offNotification === "function") {
|
|
519
|
+
connection.offNotification(
|
|
520
|
+
X_DISPATCH_PERMISSIONS_METHODS.SET_REQUEST,
|
|
521
|
+
permissionsSetHandler,
|
|
522
|
+
);
|
|
523
|
+
connection.offNotification(
|
|
524
|
+
X_DISPATCH_PERMISSIONS_METHODS.CLEAR_REQUEST,
|
|
525
|
+
permissionsClearHandler,
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
} catch {
|
|
529
|
+
/* connection already torn down */
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
// 4d. map/dispatch/message handler — receives hub-routed envelopes
|
|
534
|
+
// addressed to a specific agent on this swarm via MAP scope. The hub
|
|
535
|
+
// takes this path (not mail/turn) when the target agent declares
|
|
536
|
+
// `messaging.canReceive: true` per-agent but not `mail.canJoin`,
|
|
537
|
+
// which is the default for long-lived workers/coordinators registered
|
|
538
|
+
// by the lifecycle bridge. Without this handler, mail+reuse dispatches
|
|
539
|
+
// are silently dropped on the swarm side.
|
|
540
|
+
//
|
|
541
|
+
// Translate the hub-assigned MAP ULID (`to_agent_id`) → local agent
|
|
542
|
+
// id and forward the envelope into the local inbox so the new
|
|
543
|
+
// `mail-inbound-reuse-consumer` picks it up.
|
|
544
|
+
const dispatchMessageHandler = async (params: unknown): Promise<void> => {
|
|
545
|
+
const p = params as
|
|
546
|
+
| (Record<string, unknown> & {
|
|
547
|
+
to_agent_id?: string;
|
|
548
|
+
envelope?: unknown;
|
|
549
|
+
from_agent_id?: string;
|
|
550
|
+
})
|
|
551
|
+
| undefined;
|
|
552
|
+
const toAgentId = p?.to_agent_id;
|
|
553
|
+
const envelope = p?.envelope;
|
|
554
|
+
if (!toAgentId || !envelope) {
|
|
555
|
+
console.warn(
|
|
556
|
+
"[sidecar] map/dispatch/message missing to_agent_id or envelope; ignoring",
|
|
557
|
+
);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
const localAgentId = findLocalAgentByMapId(toAgentId);
|
|
561
|
+
if (!localAgentId) {
|
|
562
|
+
console.warn(
|
|
563
|
+
`[sidecar] map/dispatch/message recipient ${toAgentId} not registered locally; dropping`,
|
|
564
|
+
);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
// Translate envelope { type, body } → { schema, data } shape that the
|
|
568
|
+
// mail-inbound-reuse-consumer expects (mirrors mail-bridge's
|
|
569
|
+
// translation for `mail/turn.received`).
|
|
570
|
+
//
|
|
571
|
+
// Hub-side mail-transport injects `body._conversationId` when sending
|
|
572
|
+
// via MAP scope (sendViaMapScope) — extract it here and surface it
|
|
573
|
+
// on the top-level content (alongside `data`) so the
|
|
574
|
+
// mail-inbound-reuse-consumer's reply path can `postMailTurn` to
|
|
575
|
+
// the right conversation. Without this, the consumer drops the
|
|
576
|
+
// reply with "No conversationId".
|
|
577
|
+
const env = envelope as { type?: string; body?: Record<string, unknown> };
|
|
578
|
+
const conversationId =
|
|
579
|
+
env.body && typeof env.body._conversationId === "string"
|
|
580
|
+
? (env.body._conversationId as string)
|
|
581
|
+
: undefined;
|
|
582
|
+
const content: Record<string, unknown> =
|
|
583
|
+
env.type && env.body
|
|
584
|
+
? { schema: env.type, data: env.body }
|
|
585
|
+
: (envelope as Record<string, unknown>);
|
|
586
|
+
const contentWithMarker: Record<string, unknown> = {
|
|
587
|
+
type: "data",
|
|
588
|
+
...content,
|
|
589
|
+
...(conversationId ? { _conversationId: conversationId } : {}),
|
|
590
|
+
};
|
|
591
|
+
try {
|
|
592
|
+
await inboxAdapter.send(
|
|
593
|
+
(p?.from_agent_id as string | undefined) ?? "openhive-hub",
|
|
594
|
+
localAgentId,
|
|
595
|
+
contentWithMarker as never,
|
|
596
|
+
{ importance: "normal" },
|
|
597
|
+
);
|
|
598
|
+
} catch (err) {
|
|
599
|
+
console.warn(
|
|
600
|
+
`[sidecar] map/dispatch/message inbox.send failed for ${localAgentId}: ` +
|
|
601
|
+
`${(err as Error).message}`,
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
// Dual-listen: subscribe to BOTH the canonical `x-dispatch/message`
|
|
606
|
+
// (the new method name owned by swarm-dispatch's protocol-constants
|
|
607
|
+
// module) AND the legacy `map/dispatch/message` alias for one
|
|
608
|
+
// release window. Older hub builds send under the legacy name; new
|
|
609
|
+
// builds send under the canonical name. Once the dual-listen window
|
|
610
|
+
// closes, drop the legacy registration.
|
|
611
|
+
const {
|
|
612
|
+
X_DISPATCH_METHODS,
|
|
613
|
+
LEGACY_MAP_DISPATCH_MESSAGE_METHOD,
|
|
614
|
+
} = await import("swarm-dispatch/client");
|
|
615
|
+
connection.onNotification(
|
|
616
|
+
X_DISPATCH_METHODS.MESSAGE,
|
|
617
|
+
dispatchMessageHandler,
|
|
618
|
+
);
|
|
619
|
+
connection.onNotification(
|
|
620
|
+
LEGACY_MAP_DISPATCH_MESSAGE_METHOD,
|
|
621
|
+
dispatchMessageHandler,
|
|
622
|
+
);
|
|
623
|
+
dispatchMessageHandlerCleanup = () => {
|
|
624
|
+
try {
|
|
625
|
+
if (typeof connection.offNotification === "function") {
|
|
626
|
+
connection.offNotification(
|
|
627
|
+
X_DISPATCH_METHODS.MESSAGE,
|
|
628
|
+
dispatchMessageHandler,
|
|
629
|
+
);
|
|
630
|
+
connection.offNotification(
|
|
631
|
+
LEGACY_MAP_DISPATCH_MESSAGE_METHOD,
|
|
632
|
+
dispatchMessageHandler,
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
} catch {
|
|
636
|
+
/* connection already torn down */
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
|
|
297
640
|
// 5. Cascade Bridge + Action Handler (optional — only when a GitCascadeAdapter is available)
|
|
298
641
|
if (gitCascadeAdapter) {
|
|
299
642
|
const { createCascadeBridge } = await import("./cascade-bridge.js");
|
|
@@ -308,6 +651,56 @@ export function createMAPSidecar(
|
|
|
308
651
|
actionCleanup();
|
|
309
652
|
};
|
|
310
653
|
}
|
|
654
|
+
|
|
655
|
+
// 6. Workspace (kinds/repo) — declare attached repos to the hub.
|
|
656
|
+
// Discovers repos from WORKSPACE_* env vars (set by openhive's swarm-spawn
|
|
657
|
+
// flow when spawning with a `repo_id`) plus OPENHIVE_WORKSPACE_REPOS for
|
|
658
|
+
// multi-repo declarations. Skipped entirely when capability.declare is off.
|
|
659
|
+
if (workspaceCapability.declare.enabled) {
|
|
660
|
+
try {
|
|
661
|
+
// OpenHive's MAP server registers x-workspace/repo.* as request handlers
|
|
662
|
+
// (additionalHandlers), not notification handlers — so route notify
|
|
663
|
+
// through callExtension and ignore the (void) response.
|
|
664
|
+
const repoTransport: RepoClientTransport = {
|
|
665
|
+
notify: async (method: string, params: unknown) => {
|
|
666
|
+
await connection.callExtension(method, params);
|
|
667
|
+
},
|
|
668
|
+
request: (method: string, params: unknown) =>
|
|
669
|
+
connection.callExtension(method, params),
|
|
670
|
+
};
|
|
671
|
+
const manager = new RepoManager();
|
|
672
|
+
const single =
|
|
673
|
+
process.env.WORKSPACE_REPO_URL && process.env.WORKSPACE_LOCAL_PATH
|
|
674
|
+
? [{
|
|
675
|
+
remoteUrl: process.env.WORKSPACE_REPO_URL,
|
|
676
|
+
localPath: process.env.WORKSPACE_LOCAL_PATH,
|
|
677
|
+
}]
|
|
678
|
+
: [];
|
|
679
|
+
const multi = process.env.OPENHIVE_WORKSPACE_REPOS
|
|
680
|
+
? (JSON.parse(process.env.OPENHIVE_WORKSPACE_REPOS) as Array<{
|
|
681
|
+
remoteUrl: string;
|
|
682
|
+
localPath: string;
|
|
683
|
+
}>)
|
|
684
|
+
: [];
|
|
685
|
+
for (const cfg of [...single, ...multi]) {
|
|
686
|
+
await manager.attach(cfg);
|
|
687
|
+
}
|
|
688
|
+
if (manager.list().length > 0) {
|
|
689
|
+
const client = new RepoClient(repoTransport);
|
|
690
|
+
await client.declare(RepoClient.snapshot(manager));
|
|
691
|
+
workspaceManager = manager;
|
|
692
|
+
workspaceTransport = repoTransport;
|
|
693
|
+
console.log(
|
|
694
|
+
`[map-sidecar] Declared ${manager.list().length} workspace(s) to hub`,
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
} catch (err) {
|
|
698
|
+
// Non-fatal — sidecar continues without workspace declarations
|
|
699
|
+
console.warn(
|
|
700
|
+
`[map-sidecar] Workspace declare failed: ${(err as Error).message}`,
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
311
704
|
}
|
|
312
705
|
|
|
313
706
|
return {
|
|
@@ -363,5 +756,42 @@ export function createMAPSidecar(
|
|
|
363
756
|
// Best effort — MAP hub may be temporarily unavailable
|
|
364
757
|
}
|
|
365
758
|
},
|
|
759
|
+
|
|
760
|
+
async postMailTurn(
|
|
761
|
+
conversationId: string,
|
|
762
|
+
participantId: string,
|
|
763
|
+
content: string,
|
|
764
|
+
): Promise<void> {
|
|
765
|
+
if (!connection || !isConnected) {
|
|
766
|
+
console.warn(
|
|
767
|
+
`[map-sidecar] postMailTurn skipped (connection=${!!connection} ` +
|
|
768
|
+
`isConnected=${isConnected}) conv=${conversationId}`,
|
|
769
|
+
);
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
try {
|
|
773
|
+
await connection.sendNotification("mail/turn", {
|
|
774
|
+
conversationId,
|
|
775
|
+
participantId,
|
|
776
|
+
contentType: "text/plain",
|
|
777
|
+
content,
|
|
778
|
+
});
|
|
779
|
+
} catch (err) {
|
|
780
|
+
// Best effort — hub may be temporarily unavailable. Log at warn
|
|
781
|
+
// so silent failures are visible during postmortem.
|
|
782
|
+
console.warn(
|
|
783
|
+
`[map-sidecar] postMailTurn failed for conv=${conversationId}: ` +
|
|
784
|
+
`${(err as Error).message ?? String(err)}`,
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
},
|
|
788
|
+
|
|
789
|
+
getWorkspaceManager() {
|
|
790
|
+
return workspaceManager;
|
|
791
|
+
},
|
|
792
|
+
|
|
793
|
+
getRepoTransport() {
|
|
794
|
+
return workspaceTransport;
|
|
795
|
+
},
|
|
366
796
|
};
|
|
367
797
|
}
|
package/src/map/types.ts
CHANGED
|
@@ -83,6 +83,15 @@ export interface MAPSidecarDeps {
|
|
|
83
83
|
* without hub observability).
|
|
84
84
|
*/
|
|
85
85
|
gitCascadeAdapter?: import("../workspace/git-cascade-adapter.js").GitCascadeAdapter;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* The swarm-dispatch dispatcher agent ID (e.g. "dispatcher:<claimantId>").
|
|
89
|
+
* When provided, the mail bridge delivers hub-forwarded mail turns directly
|
|
90
|
+
* to this inbox recipient so createAgentInboxPort.onIncoming fires
|
|
91
|
+
* correctly. Without it, bridged turns land in BRIDGE_RECIPIENT_ID and the
|
|
92
|
+
* MessagePort never sees them.
|
|
93
|
+
*/
|
|
94
|
+
dispatcherAgentId?: string;
|
|
86
95
|
}
|
|
87
96
|
|
|
88
97
|
// =============================================================================
|
|
@@ -106,6 +115,31 @@ export interface MAPSidecar {
|
|
|
106
115
|
|
|
107
116
|
/** Emit a custom event to the MAP hub scope (best-effort, no-op if disconnected) */
|
|
108
117
|
emitEvent?(event: Record<string, unknown>): Promise<void>;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Post a mail turn back to the hub via the `mail/turn` MAP notification.
|
|
121
|
+
* Used by the dispatch reply bridge to forward worker output into the hub's
|
|
122
|
+
* mail conversation after a mail-inbound task completes.
|
|
123
|
+
* No-op if disconnected.
|
|
124
|
+
*/
|
|
125
|
+
postMailTurn?(
|
|
126
|
+
conversationId: string,
|
|
127
|
+
participantId: string,
|
|
128
|
+
content: string,
|
|
129
|
+
): Promise<void>;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Access the sidecar's workspace RepoManager (if workspace declarations
|
|
133
|
+
* are enabled and the sidecar is connected). Used by mail-inbound-consumer
|
|
134
|
+
* for pre-spawn repo mount.
|
|
135
|
+
*/
|
|
136
|
+
getWorkspaceManager?(): unknown;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Access the sidecar's repo client transport for declaring newly-attached
|
|
140
|
+
* repos to the hub. Used by mail-inbound-consumer after pre-spawn clone.
|
|
141
|
+
*/
|
|
142
|
+
getRepoTransport?(): { notify(method: string, params: unknown): Promise<void>; request(method: string, params: unknown): Promise<unknown> } | null;
|
|
109
143
|
}
|
|
110
144
|
|
|
111
145
|
// =============================================================================
|
package/src/mcp/tools/done-v2.ts
CHANGED
package/src/teams/team-loader.ts
CHANGED
|
@@ -84,7 +84,9 @@ export async function loadTeam(
|
|
|
84
84
|
|
|
85
85
|
const manifest = template.manifest;
|
|
86
86
|
const communication = (manifest.communication ?? {}) as CommunicationConfig;
|
|
87
|
-
const macroAgent = parseMacroAgentExtensions(
|
|
87
|
+
const macroAgent = parseMacroAgentExtensions(
|
|
88
|
+
manifest.macro_agent as Record<string, unknown> | undefined,
|
|
89
|
+
);
|
|
88
90
|
|
|
89
91
|
// 2. Build enforcement-enriched roles
|
|
90
92
|
const resolvedRoles = new Map<string, ResolvedTeamRole>();
|
|
@@ -63,6 +63,8 @@ function manifestToResolved(manifest: TeamManifest): MacroResolvedTemplate {
|
|
|
63
63
|
roles: new Map(),
|
|
64
64
|
prompts: new Map(),
|
|
65
65
|
mcpServers: manifest._mcpServers,
|
|
66
|
+
mcpProviders: new Map(),
|
|
67
|
+
loadouts: new Map(),
|
|
66
68
|
sourcePath: "",
|
|
67
69
|
},
|
|
68
70
|
resolvedRoles: manifest._resolvedRoles,
|