macro-agent 0.1.8 → 0.1.11
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 +263 -33
- package/README.md +781 -131
- package/dist/acp/claude-code-replay.d.ts +11 -0
- package/dist/acp/claude-code-replay.d.ts.map +1 -0
- package/dist/acp/claude-code-replay.js +190 -0
- package/dist/acp/claude-code-replay.js.map +1 -0
- package/dist/acp/macro-agent.d.ts.map +1 -1
- package/dist/acp/macro-agent.js +192 -7
- package/dist/acp/macro-agent.js.map +1 -1
- package/dist/acp/types.d.ts +9 -0
- package/dist/acp/types.d.ts.map +1 -1
- package/dist/acp/types.js.map +1 -1
- package/dist/adapters/tasks-adapter.d.ts.map +1 -1
- package/dist/adapters/tasks-adapter.js +3 -0
- package/dist/adapters/tasks-adapter.js.map +1 -1
- package/dist/adapters/types.d.ts +1 -0
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/agent/agent-manager-v2.d.ts +21 -0
- package/dist/agent/agent-manager-v2.d.ts.map +1 -1
- package/dist/agent/agent-manager-v2.js +308 -54
- package/dist/agent/agent-manager-v2.js.map +1 -1
- package/dist/agent/agent-manager.d.ts +12 -0
- package/dist/agent/agent-manager.d.ts.map +1 -1
- package/dist/agent/agent-manager.js.map +1 -1
- package/dist/agent/agent-store.d.ts +10 -0
- package/dist/agent/agent-store.d.ts.map +1 -1
- package/dist/agent/agent-store.js +22 -0
- package/dist/agent/agent-store.js.map +1 -1
- package/dist/agent/types.d.ts +15 -2
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/boot-v2.d.ts +129 -1
- package/dist/boot-v2.d.ts.map +1 -1
- package/dist/boot-v2.js +359 -8
- package/dist/boot-v2.js.map +1 -1
- package/dist/cli/acp.js +4 -0
- package/dist/cli/acp.js.map +1 -1
- package/dist/cli/index.js +56 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cognitive/macro-agent-backend.d.ts.map +1 -1
- package/dist/cognitive/macro-agent-backend.js +40 -22
- package/dist/cognitive/macro-agent-backend.js.map +1 -1
- package/dist/integrations/skilltree.d.ts.map +1 -1
- package/dist/integrations/skilltree.js +1 -0
- package/dist/integrations/skilltree.js.map +1 -1
- package/dist/lifecycle/cascade.d.ts +25 -2
- package/dist/lifecycle/cascade.d.ts.map +1 -1
- package/dist/lifecycle/cascade.js +70 -2
- package/dist/lifecycle/cascade.js.map +1 -1
- package/dist/lifecycle/cleanup.d.ts +33 -2
- package/dist/lifecycle/cleanup.d.ts.map +1 -1
- package/dist/lifecycle/cleanup.js +28 -6
- package/dist/lifecycle/cleanup.js.map +1 -1
- 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 +28 -2
- package/dist/lifecycle/handlers-v2.js.map +1 -1
- package/dist/lifecycle/types.d.ts +11 -0
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lifecycle/types.js.map +1 -1
- package/dist/map/acp-bridge.d.ts +9 -0
- package/dist/map/acp-bridge.d.ts.map +1 -1
- package/dist/map/acp-bridge.js +15 -2
- package/dist/map/acp-bridge.js.map +1 -1
- package/dist/map/cascade-action-handler.d.ts +24 -0
- package/dist/map/cascade-action-handler.d.ts.map +1 -0
- package/dist/map/cascade-action-handler.js +170 -0
- package/dist/map/cascade-action-handler.js.map +1 -0
- package/dist/map/cascade-bridge.d.ts +44 -0
- package/dist/map/cascade-bridge.d.ts.map +1 -0
- package/dist/map/cascade-bridge.js +294 -0
- package/dist/map/cascade-bridge.js.map +1 -0
- package/dist/map/coordination-handler.d.ts.map +1 -1
- package/dist/map/coordination-handler.js +12 -1
- package/dist/map/coordination-handler.js.map +1 -1
- package/dist/map/lifecycle-bridge.d.ts +1 -1
- package/dist/map/lifecycle-bridge.d.ts.map +1 -1
- package/dist/map/lifecycle-bridge.js +58 -23
- package/dist/map/lifecycle-bridge.js.map +1 -1
- package/dist/map/server.d.ts.map +1 -1
- package/dist/map/server.js +219 -7
- package/dist/map/server.js.map +1 -1
- package/dist/map/sidecar.d.ts.map +1 -1
- package/dist/map/sidecar.js +49 -2
- package/dist/map/sidecar.js.map +1 -1
- package/dist/map/types.d.ts +22 -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 +8 -0
- package/dist/mcp/tools/done-v2.js.map +1 -1
- package/dist/teams/team-manager-v2.d.ts.map +1 -1
- package/dist/teams/team-manager-v2.js +26 -0
- package/dist/teams/team-manager-v2.js.map +1 -1
- package/dist/teams/team-runtime-v2.d.ts.map +1 -1
- package/dist/teams/team-runtime-v2.js +16 -3
- package/dist/teams/team-runtime-v2.js.map +1 -1
- package/dist/workspace/config.d.ts +10 -10
- package/dist/workspace/config.d.ts.map +1 -1
- package/dist/workspace/config.js +4 -4
- package/dist/workspace/config.js.map +1 -1
- package/dist/workspace/git-cascade-adapter.d.ts +510 -0
- package/dist/workspace/git-cascade-adapter.d.ts.map +1 -0
- package/dist/workspace/git-cascade-adapter.js +934 -0
- package/dist/workspace/git-cascade-adapter.js.map +1 -0
- package/dist/workspace/index.d.ts +3 -3
- package/dist/workspace/index.d.ts.map +1 -1
- package/dist/workspace/index.js +4 -4
- package/dist/workspace/index.js.map +1 -1
- package/dist/workspace/landing/direct-push.d.ts +20 -0
- package/dist/workspace/landing/direct-push.d.ts.map +1 -0
- package/dist/workspace/landing/direct-push.js +74 -0
- package/dist/workspace/landing/direct-push.js.map +1 -0
- package/dist/workspace/landing/index.d.ts +29 -0
- package/dist/workspace/landing/index.d.ts.map +1 -0
- package/dist/workspace/landing/index.js +37 -0
- package/dist/workspace/landing/index.js.map +1 -0
- package/dist/workspace/landing/merge-to-parent.d.ts +41 -0
- package/dist/workspace/landing/merge-to-parent.d.ts.map +1 -0
- package/dist/workspace/landing/merge-to-parent.js +186 -0
- package/dist/workspace/landing/merge-to-parent.js.map +1 -0
- package/dist/workspace/landing/optimistic-push.d.ts +16 -0
- package/dist/workspace/landing/optimistic-push.d.ts.map +1 -0
- package/dist/workspace/landing/optimistic-push.js +27 -0
- package/dist/workspace/landing/optimistic-push.js.map +1 -0
- package/dist/workspace/landing/queue-to-branch.d.ts +24 -0
- package/dist/workspace/landing/queue-to-branch.d.ts.map +1 -0
- package/dist/workspace/landing/queue-to-branch.js +79 -0
- package/dist/workspace/landing/queue-to-branch.js.map +1 -0
- package/dist/workspace/merge-queue/merge-queue.d.ts +10 -0
- package/dist/workspace/merge-queue/merge-queue.d.ts.map +1 -1
- package/dist/workspace/merge-queue/merge-queue.js +10 -0
- package/dist/workspace/merge-queue/merge-queue.js.map +1 -1
- package/dist/workspace/merge-queue/types.d.ts +16 -2
- package/dist/workspace/merge-queue/types.d.ts.map +1 -1
- package/dist/workspace/merge-queue/types.js +9 -0
- package/dist/workspace/merge-queue/types.js.map +1 -1
- package/dist/workspace/pool/types.d.ts +1 -0
- package/dist/workspace/pool/types.d.ts.map +1 -1
- package/dist/workspace/pool/worktree-pool.d.ts.map +1 -1
- package/dist/workspace/pool/worktree-pool.js +1 -0
- package/dist/workspace/pool/worktree-pool.js.map +1 -1
- package/dist/workspace/recovery/abandon.d.ts +15 -0
- package/dist/workspace/recovery/abandon.d.ts.map +1 -0
- package/dist/workspace/recovery/abandon.js +45 -0
- package/dist/workspace/recovery/abandon.js.map +1 -0
- package/dist/workspace/recovery/auto-resolve.d.ts +27 -0
- package/dist/workspace/recovery/auto-resolve.d.ts.map +1 -0
- package/dist/workspace/recovery/auto-resolve.js +99 -0
- package/dist/workspace/recovery/auto-resolve.js.map +1 -0
- package/dist/workspace/recovery/defer.d.ts +15 -0
- package/dist/workspace/recovery/defer.d.ts.map +1 -0
- package/dist/workspace/recovery/defer.js +16 -0
- package/dist/workspace/recovery/defer.js.map +1 -0
- package/dist/workspace/recovery/escalate.d.ts +16 -0
- package/dist/workspace/recovery/escalate.d.ts.map +1 -0
- package/dist/workspace/recovery/escalate.js +24 -0
- package/dist/workspace/recovery/escalate.js.map +1 -0
- package/dist/workspace/recovery/index.d.ts +32 -0
- package/dist/workspace/recovery/index.d.ts.map +1 -0
- package/dist/workspace/recovery/index.js +45 -0
- package/dist/workspace/recovery/index.js.map +1 -0
- package/dist/workspace/recovery/spawn-resolver.d.ts +45 -0
- package/dist/workspace/recovery/spawn-resolver.d.ts.map +1 -0
- package/dist/workspace/recovery/spawn-resolver.js +118 -0
- package/dist/workspace/recovery/spawn-resolver.js.map +1 -0
- package/dist/workspace/recovery/types.d.ts +63 -0
- package/dist/workspace/recovery/types.d.ts.map +1 -0
- package/dist/workspace/recovery/types.js +12 -0
- package/dist/workspace/recovery/types.js.map +1 -0
- package/dist/workspace/topology/index.d.ts +9 -0
- package/dist/workspace/topology/index.d.ts.map +1 -0
- package/dist/workspace/topology/index.js +8 -0
- package/dist/workspace/topology/index.js.map +1 -0
- package/dist/workspace/topology/no-workspace.d.ts +18 -0
- package/dist/workspace/topology/no-workspace.d.ts.map +1 -0
- package/dist/workspace/topology/no-workspace.js +25 -0
- package/dist/workspace/topology/no-workspace.js.map +1 -0
- package/dist/workspace/topology/types.d.ts +97 -0
- package/dist/workspace/topology/types.d.ts.map +1 -0
- package/dist/workspace/topology/types.js +20 -0
- package/dist/workspace/topology/types.js.map +1 -0
- package/dist/workspace/topology/yaml-driven.d.ts +69 -0
- package/dist/workspace/topology/yaml-driven.d.ts.map +1 -0
- package/dist/workspace/topology/yaml-driven.js +273 -0
- package/dist/workspace/topology/yaml-driven.js.map +1 -0
- package/dist/workspace/types-v3.d.ts +117 -0
- package/dist/workspace/types-v3.d.ts.map +1 -0
- package/dist/workspace/types-v3.js +20 -0
- package/dist/workspace/types-v3.js.map +1 -0
- package/dist/workspace/types.d.ts +162 -17
- package/dist/workspace/types.d.ts.map +1 -1
- package/dist/workspace/workspace-manager.d.ts +101 -13
- package/dist/workspace/workspace-manager.d.ts.map +1 -1
- package/dist/workspace/workspace-manager.js +416 -13
- package/dist/workspace/workspace-manager.js.map +1 -1
- package/dist/workspace/yaml-schema.d.ts +254 -0
- package/dist/workspace/yaml-schema.d.ts.map +1 -0
- package/dist/workspace/yaml-schema.js +170 -0
- package/dist/workspace/yaml-schema.js.map +1 -0
- package/docs/conflict-recovery.md +472 -0
- package/docs/design/task-dispatcher.md +880 -0
- package/docs/git-cascade-integration-gaps.md +678 -0
- package/docs/workspace-interfaces.md +731 -0
- package/docs/workspace-redesign-plan.md +302 -0
- package/package.json +6 -5
- package/src/__tests__/boot-v2.test.ts +435 -0
- package/src/__tests__/e2e/acp-over-map.e2e.test.ts +92 -0
- package/src/__tests__/e2e/auto-sync.e2e.test.ts +257 -0
- package/src/__tests__/e2e/bootstrap.e2e.test.ts +319 -0
- package/src/__tests__/e2e/cascade-rebase.e2e.test.ts +254 -0
- package/src/__tests__/e2e/cli-run.e2e.test.ts +167 -0
- package/src/__tests__/e2e/dispatch-coordination.e2e.test.ts +495 -0
- package/src/__tests__/e2e/dispatch-live.e2e.test.ts +564 -0
- package/src/__tests__/e2e/dispatch-opentasks.e2e.test.ts +496 -0
- package/src/__tests__/e2e/dispatch-phase2-live.e2e.test.ts +456 -0
- package/src/__tests__/e2e/dispatch-phase2.e2e.test.ts +386 -0
- package/src/__tests__/e2e/dispatch.e2e.test.ts +376 -0
- package/src/__tests__/e2e/self-driving-v3.e2e.test.ts +197 -0
- package/src/__tests__/e2e/spawn-resolver.e2e.test.ts +200 -0
- package/src/__tests__/e2e/workspace-lifecycle.e2e.test.ts +30 -22
- package/src/__tests__/e2e/workspace-v3.e2e.test.ts +413 -0
- package/src/acp/__tests__/claude-code-replay.test.ts +225 -0
- package/src/acp/__tests__/macro-agent.test.ts +39 -1
- package/src/acp/claude-code-replay.ts +208 -0
- package/src/acp/macro-agent.ts +203 -10
- package/src/acp/types.ts +10 -0
- package/src/adapters/__tests__/tasks-adapter.test.ts +1 -0
- package/src/adapters/tasks-adapter.ts +3 -0
- package/src/adapters/types.ts +1 -0
- package/src/agent/__tests__/agent-manager-topology.test.ts +73 -0
- package/src/agent/__tests__/agent-manager-v2.test.ts +66 -0
- package/src/agent/__tests__/agent-store.test.ts +52 -0
- package/src/agent/__tests__/task-ref-resolution.test.ts +231 -0
- package/src/agent/agent-manager-v2.ts +372 -59
- package/src/agent/agent-manager.ts +14 -0
- package/src/agent/agent-store.ts +24 -0
- package/src/agent/types.ts +16 -2
- package/src/boot-v2.ts +589 -35
- package/src/cli/acp.ts +4 -0
- package/src/cli/index.ts +61 -0
- package/src/cognitive/macro-agent-backend.ts +45 -29
- package/src/integrations/skilltree.ts +1 -0
- package/src/lifecycle/__tests__/cascade-consolidation.test.ts +240 -0
- package/src/lifecycle/cascade.ts +77 -2
- package/src/lifecycle/cleanup.ts +52 -3
- package/src/lifecycle/handlers-v2.ts +40 -3
- package/src/lifecycle/types.ts +12 -0
- package/src/map/__tests__/cascade-bridge.test.ts +229 -0
- package/src/map/__tests__/emit-event.test.ts +71 -0
- package/src/map/__tests__/lifecycle-bridge.test.ts +86 -10
- package/src/map/acp-bridge.ts +26 -3
- package/src/map/cascade-action-handler.ts +205 -0
- package/src/map/cascade-bridge.ts +339 -0
- package/src/map/coordination-handler.ts +13 -1
- package/src/map/lifecycle-bridge.ts +52 -17
- package/src/map/server.ts +225 -7
- package/src/map/sidecar.ts +48 -1
- package/src/map/types.ts +23 -0
- package/src/mcp/tools/done-v2.ts +9 -0
- package/src/teams/team-manager-v2.ts +37 -0
- package/src/teams/team-runtime-v2.ts +23 -3
- package/src/workspace/__tests__/{dataplane-adapter.test.ts → git-cascade-adapter.test.ts} +209 -14
- package/src/workspace/__tests__/land-dispatch.test.ts +214 -0
- package/src/workspace/__tests__/self-driving-yaml.test.ts +114 -0
- package/src/workspace/__tests__/shared-worktree-refcount.test.ts +154 -0
- package/src/workspace/__tests__/standalone-mode.test.ts +118 -0
- package/src/workspace/__tests__/workspace-manager-v3.test.ts +245 -0
- package/src/workspace/__tests__/yaml-schema.test.ts +210 -0
- package/src/workspace/config.ts +11 -11
- package/src/workspace/git-cascade-adapter.ts +1213 -0
- package/src/workspace/index.ts +11 -11
- package/src/workspace/landing/__tests__/strategies.test.ts +184 -0
- package/src/workspace/landing/direct-push.ts +91 -0
- package/src/workspace/landing/index.ts +40 -0
- package/src/workspace/landing/merge-to-parent.ts +229 -0
- package/src/workspace/landing/optimistic-push.ts +36 -0
- package/src/workspace/landing/queue-to-branch.ts +108 -0
- package/src/workspace/merge-queue/merge-queue.ts +10 -0
- package/src/workspace/merge-queue/types.ts +16 -2
- package/src/workspace/pool/__tests__/worktree-pool.integration.test.ts +5 -5
- package/src/workspace/pool/types.ts +1 -0
- package/src/workspace/pool/worktree-pool.ts +1 -0
- package/src/workspace/recovery/__tests__/auto-resolve-integration.test.ts +127 -0
- package/src/workspace/recovery/__tests__/spawn-resolver.test.ts +139 -0
- package/src/workspace/recovery/__tests__/strategies.test.ts +145 -0
- package/src/workspace/recovery/abandon.ts +51 -0
- package/src/workspace/recovery/auto-resolve.ts +119 -0
- package/src/workspace/recovery/defer.ts +23 -0
- package/src/workspace/recovery/escalate.ts +30 -0
- package/src/workspace/recovery/index.ts +58 -0
- package/src/workspace/recovery/spawn-resolver.ts +152 -0
- package/src/workspace/recovery/types.ts +54 -0
- package/src/workspace/topology/__tests__/yaml-driven.test.ts +345 -0
- package/src/workspace/topology/index.ts +18 -0
- package/src/workspace/topology/no-workspace.ts +39 -0
- package/src/workspace/topology/types.ts +116 -0
- package/src/workspace/topology/yaml-driven.ts +316 -0
- package/src/workspace/types-v3.ts +162 -0
- package/src/workspace/types.ts +211 -20
- package/src/workspace/workspace-manager.ts +533 -19
- package/src/workspace/yaml-schema.ts +216 -0
- package/dist/workspace/dataplane-adapter.d.ts +0 -260
- package/dist/workspace/dataplane-adapter.d.ts.map +0 -1
- package/dist/workspace/dataplane-adapter.js +0 -416
- package/dist/workspace/dataplane-adapter.js.map +0 -1
- package/src/workspace/dataplane-adapter.ts +0 -546
package/src/map/server.ts
CHANGED
|
@@ -74,6 +74,14 @@ export function createMAPServerInstance(
|
|
|
74
74
|
const clientWebSockets = new Map<string, WebSocket>(); // participant/agent ID → WebSocket
|
|
75
75
|
// Track subscription IDs by client agent ID for ACP response delivery
|
|
76
76
|
const clientSubscriptions = new Map<string, string[]>(); // agent ID → subscription IDs
|
|
77
|
+
/**
|
|
78
|
+
* Per-subscription monotonic event counter. The MAP SDK's Subscription
|
|
79
|
+
* checks `sequenceNumber !== lastSequenceNumber + 1` and warns on gaps —
|
|
80
|
+
* using `Date.now()` (millisecond timestamp) breaks that assumption since
|
|
81
|
+
* each event becomes a "gap". Track a per-subscription counter starting
|
|
82
|
+
* at 1 and increment per event.
|
|
83
|
+
*/
|
|
84
|
+
const subscriptionSequence = new Map<string, number>(); // subscription ID → next sequence number
|
|
77
85
|
// Track original ws.send for each WebSocket (before interception)
|
|
78
86
|
const originalSends = new Map<WebSocket, Function>();
|
|
79
87
|
|
|
@@ -90,11 +98,21 @@ export function createMAPServerInstance(
|
|
|
90
98
|
|
|
91
99
|
// ── Agent extensions ──────────────────────────────────────────
|
|
92
100
|
handlers["_macro/spawnAgent"] = async (params, ctx) => {
|
|
101
|
+
// Forward the full SpawnAgentOptions surface so callers can set
|
|
102
|
+
// permission mode, agent type, custom prompt, model config, etc.
|
|
103
|
+
// Previously this handler dropped everything except role/cwd/task,
|
|
104
|
+
// making _macro/spawnAgent useless for any non-default agent.
|
|
93
105
|
const spawned = await agentManager.spawn({
|
|
94
106
|
task: params.task ?? "Spawned via MAP",
|
|
95
107
|
parent: params.parent ?? null,
|
|
96
108
|
cwd: params.cwd,
|
|
97
109
|
role: params.role ?? "worker",
|
|
110
|
+
permissionMode: params.permissionMode,
|
|
111
|
+
agentType: params.agentType,
|
|
112
|
+
customPrompt: params.customPrompt,
|
|
113
|
+
topics: params.topics,
|
|
114
|
+
config: params.config,
|
|
115
|
+
taskRef: params.taskRef,
|
|
98
116
|
});
|
|
99
117
|
|
|
100
118
|
// Ensure agent is registered in MAPServer's registry.
|
|
@@ -107,7 +125,7 @@ export function createMAPServerInstance(
|
|
|
107
125
|
role: params.role ?? "worker",
|
|
108
126
|
state: "idle",
|
|
109
127
|
sessionId: ctx?.session?.id,
|
|
110
|
-
metadata: {
|
|
128
|
+
metadata: { peerAgentId: spawned.id, task: params.task },
|
|
111
129
|
});
|
|
112
130
|
if (registered?.id) {
|
|
113
131
|
mapIdToLocalId.set(registered.id, spawned.id);
|
|
@@ -145,6 +163,160 @@ export function createMAPServerInstance(
|
|
|
145
163
|
return { agent: { id: spawned.id } };
|
|
146
164
|
};
|
|
147
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Resume an agent session with full routing + session info returned.
|
|
168
|
+
*
|
|
169
|
+
* Session-first resolution: given a Claude Code `providerSessionId` (the
|
|
170
|
+
* session UUID persisted on the session record), reverse-look-up the
|
|
171
|
+
* owning agent. Falls back to `agentId` (either the MAP ULID or the
|
|
172
|
+
* local store id) when no providerSessionId is given or the reverse
|
|
173
|
+
* lookup misses.
|
|
174
|
+
*
|
|
175
|
+
* Behavior:
|
|
176
|
+
* 1. Resolve the local agent id.
|
|
177
|
+
* 2. Call `agentManager.resume(localId)` — idempotent; the manager
|
|
178
|
+
* re-spawns the coordinator/head-manager if its process isn't live,
|
|
179
|
+
* otherwise returns the existing handle.
|
|
180
|
+
* 3. Ensure the agent is registered in the MAPServer's registry so
|
|
181
|
+
* ACP streams can target it via the returned peerMapId.
|
|
182
|
+
* 4. Return `{ agent: { id: peerMapId, localId, name }, acpSessionId,
|
|
183
|
+
* providerSessionId }` — the caller needs peerMapId to open the
|
|
184
|
+
* ACP stream and providerSessionId to pass into `session/load` so
|
|
185
|
+
* Claude Code replays its on-disk transcript.
|
|
186
|
+
*
|
|
187
|
+
* Used by OpenHive's POST /sessions/:id/resume to revive a session whose
|
|
188
|
+
* swarm has been offline for longer than the hub's stale-grace window.
|
|
189
|
+
*/
|
|
190
|
+
handlers["_macro/resumeAgent"] = async (params, ctx) => {
|
|
191
|
+
const providerSessionIdParam = params.providerSessionId as string | undefined;
|
|
192
|
+
const agentIdParam = params.agentId as string | undefined;
|
|
193
|
+
|
|
194
|
+
let localId: string | undefined;
|
|
195
|
+
let providerSessionId: string | undefined;
|
|
196
|
+
|
|
197
|
+
if (providerSessionIdParam) {
|
|
198
|
+
const session = agentStore.findSessionByProviderSessionId(providerSessionIdParam);
|
|
199
|
+
if (session) {
|
|
200
|
+
localId = session.agent_id;
|
|
201
|
+
providerSessionId = session.provider_session_id;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (!localId && agentIdParam) {
|
|
206
|
+
localId = mapIdToLocalId.get(agentIdParam) ?? agentIdParam;
|
|
207
|
+
const session = agentStore.getSession(localId);
|
|
208
|
+
providerSessionId = session?.provider_session_id;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (!localId) {
|
|
212
|
+
return {
|
|
213
|
+
success: false,
|
|
214
|
+
error: "providerSessionId or agentId required",
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const agentRec = agentStore.getAgent(localId);
|
|
219
|
+
if (!agentRec) {
|
|
220
|
+
return { success: false, error: `Agent not found: ${localId}` };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Already-running case: skip resume() (which rejects with ALREADY_RUNNING)
|
|
224
|
+
// and return the live agent's session info straight from the store.
|
|
225
|
+
// This makes the call idempotent — callers don't need to pre-check.
|
|
226
|
+
let resumedId: string;
|
|
227
|
+
let resumedSessionId: string;
|
|
228
|
+
let resumedName: string | undefined;
|
|
229
|
+
if (agentManager.hasActiveSession(localId as any)) {
|
|
230
|
+
resumedId = localId;
|
|
231
|
+
resumedName = agentRec.name;
|
|
232
|
+
const liveSession = agentStore.getSession(localId);
|
|
233
|
+
if (!liveSession) {
|
|
234
|
+
return {
|
|
235
|
+
success: false,
|
|
236
|
+
error: `Agent ${localId} is active but has no session record`,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
resumedSessionId = liveSession.session_id;
|
|
240
|
+
} else {
|
|
241
|
+
try {
|
|
242
|
+
const resumed = await agentManager.resume(localId as any);
|
|
243
|
+
resumedId = resumed.id;
|
|
244
|
+
resumedSessionId = resumed.session_id;
|
|
245
|
+
resumedName = (resumed as any).name;
|
|
246
|
+
} catch (err) {
|
|
247
|
+
return { success: false, error: (err as Error).message };
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Ensure agent is registered in MAPServer's registry. resume() fires
|
|
252
|
+
// the spawned lifecycle event, which the lifecycle bridge handles —
|
|
253
|
+
// but we also register here for subscription routing context on the
|
|
254
|
+
// current MAP session (mirrors _macro/spawnAgent).
|
|
255
|
+
if (mapServer && !localIdToMapId.has(resumedId)) {
|
|
256
|
+
try {
|
|
257
|
+
const registered = mapServer.agents.register({
|
|
258
|
+
name: resumedName ?? resumedId,
|
|
259
|
+
role: agentRec.role,
|
|
260
|
+
state: "idle",
|
|
261
|
+
sessionId: ctx?.session?.id,
|
|
262
|
+
metadata: { peerAgentId: resumedId },
|
|
263
|
+
});
|
|
264
|
+
if (registered?.id) {
|
|
265
|
+
mapIdToLocalId.set(registered.id, resumedId);
|
|
266
|
+
localIdToMapId.set(resumedId, registered.id);
|
|
267
|
+
}
|
|
268
|
+
} catch {
|
|
269
|
+
// Best effort; lifecycle bridge will register on spawned event
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const peerMapId = localIdToMapId.get(resumedId) ?? resumedId;
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
success: true,
|
|
277
|
+
agent: {
|
|
278
|
+
id: peerMapId,
|
|
279
|
+
localId: resumedId,
|
|
280
|
+
name: resumedName,
|
|
281
|
+
role: agentRec.role,
|
|
282
|
+
},
|
|
283
|
+
acpSessionId: resumedSessionId,
|
|
284
|
+
providerSessionId,
|
|
285
|
+
};
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Terminate a running agent. Accepts either the agent's local ID or the
|
|
290
|
+
* MAP-assigned ULID (we resolve back to local via mapIdToLocalId).
|
|
291
|
+
* Reason defaults to "stopped"; use "cancelled" for user-initiated stops.
|
|
292
|
+
*/
|
|
293
|
+
handlers["_macro/terminateAgent"] = async (params) => {
|
|
294
|
+
const agentIdParam = params.agentId as string | undefined;
|
|
295
|
+
const reason = (params.reason as string) ?? "cancelled";
|
|
296
|
+
if (!agentIdParam) {
|
|
297
|
+
return { success: false, error: "agentId is required" };
|
|
298
|
+
}
|
|
299
|
+
// Resolve either a MAP ULID or a local agent ID to our internal ID.
|
|
300
|
+
const localId = mapIdToLocalId.get(agentIdParam) ?? agentIdParam;
|
|
301
|
+
try {
|
|
302
|
+
await agentManager.terminate(localId as any, reason as any);
|
|
303
|
+
return { success: true };
|
|
304
|
+
} catch (err) {
|
|
305
|
+
return { success: false, error: (err as Error).message };
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Inspect ACP stream → peer agent bindings on this MAP server.
|
|
311
|
+
* Each stream carries the peer agent id (macro-agent's internal store id)
|
|
312
|
+
* it was opened against, set by the bridge from MAP routing. Useful for
|
|
313
|
+
* routing tests and debugging multi-coordinator scenarios.
|
|
314
|
+
*/
|
|
315
|
+
handlers["_macro/getAcpStreamBindings"] = async () => {
|
|
316
|
+
if (!acpBridge) return { bindings: [] };
|
|
317
|
+
return { bindings: acpBridge.getStreamBindings() };
|
|
318
|
+
};
|
|
319
|
+
|
|
148
320
|
// ── Task extensions ───────────────────────────────────────────
|
|
149
321
|
handlers["_macro/task/list"] = async () => {
|
|
150
322
|
if (!tasksAdapter.connected) return { tasks: [] };
|
|
@@ -256,15 +428,21 @@ export function createMAPServerInstance(
|
|
|
256
428
|
|
|
257
429
|
// Send as subscription event notification (what ACPStreamConnection expects).
|
|
258
430
|
// The _pushEvent method expects: { subscriptionId, sequenceNumber, eventId, timestamp, event }
|
|
431
|
+
//
|
|
432
|
+
// sequenceNumber must be a per-subscription monotonic counter that
|
|
433
|
+
// increments by exactly 1 — the SDK warns on any gap. Don't use
|
|
434
|
+
// Date.now() here (breaks the contract on every event).
|
|
259
435
|
for (const subId of subIds) {
|
|
260
436
|
const event = rawEvent.params?.event ?? rawEvent;
|
|
261
437
|
const eventId = event.id ?? `acp-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
438
|
+
const nextSeq = (subscriptionSequence.get(subId) ?? 0) + 1;
|
|
439
|
+
subscriptionSequence.set(subId, nextSeq);
|
|
262
440
|
const notification = JSON.stringify({
|
|
263
441
|
jsonrpc: "2.0",
|
|
264
442
|
method: "map/event",
|
|
265
443
|
params: {
|
|
266
444
|
subscriptionId: subId,
|
|
267
|
-
sequenceNumber:
|
|
445
|
+
sequenceNumber: nextSeq,
|
|
268
446
|
eventId,
|
|
269
447
|
timestamp: Date.now(),
|
|
270
448
|
event,
|
|
@@ -381,6 +559,32 @@ export function createMAPServerInstance(
|
|
|
381
559
|
return originalSend(data, ...args);
|
|
382
560
|
} as any;
|
|
383
561
|
|
|
562
|
+
// Observe incoming messages so we drop subscription IDs from our
|
|
563
|
+
// routing array when the client unsubscribes. Without this, closed
|
|
564
|
+
// ACP streams keep receiving events ("MAP: Event for unknown
|
|
565
|
+
// subscription" warnings on the client). We don't intercept the
|
|
566
|
+
// SDK's processing — this listener runs alongside it.
|
|
567
|
+
ws.on("message", (data: any) => {
|
|
568
|
+
try {
|
|
569
|
+
const text = typeof data === "string"
|
|
570
|
+
? data
|
|
571
|
+
: Buffer.isBuffer(data)
|
|
572
|
+
? data.toString("utf-8")
|
|
573
|
+
: String(data);
|
|
574
|
+
const msg = JSON.parse(text);
|
|
575
|
+
if (msg?.method === "map/unsubscribe") {
|
|
576
|
+
const subId = msg?.params?.subscriptionId;
|
|
577
|
+
if (typeof subId === "string") {
|
|
578
|
+
const idx = subscriptionIds.indexOf(subId);
|
|
579
|
+
if (idx >= 0) subscriptionIds.splice(idx, 1);
|
|
580
|
+
subscriptionSequence.delete(subId);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
} catch {
|
|
584
|
+
// Non-JSON or parse failure — ignore
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
|
|
384
588
|
const stream = websocketStream(ws as unknown as globalThis.WebSocket);
|
|
385
589
|
const router = mapServer.accept(stream, {
|
|
386
590
|
role: "client",
|
|
@@ -390,6 +594,11 @@ export function createMAPServerInstance(
|
|
|
390
594
|
|
|
391
595
|
ws.on("close", () => {
|
|
392
596
|
connectionCount--;
|
|
597
|
+
// Clear sequence counters for any subscriptions belonging to this
|
|
598
|
+
// connection. Use a copy of subscriptionIds since we don't mutate it.
|
|
599
|
+
for (const subId of subscriptionIds) {
|
|
600
|
+
subscriptionSequence.delete(subId);
|
|
601
|
+
}
|
|
393
602
|
if (clientAgentId) {
|
|
394
603
|
clientWebSockets.delete(clientAgentId);
|
|
395
604
|
clientSubscriptions.delete(clientAgentId);
|
|
@@ -404,16 +613,21 @@ export function createMAPServerInstance(
|
|
|
404
613
|
try {
|
|
405
614
|
if (event.type === "spawned" || event.type === "started") {
|
|
406
615
|
const agent = event.agent;
|
|
407
|
-
// Register agent
|
|
408
|
-
//
|
|
409
|
-
//
|
|
616
|
+
// Register agent ONCE. spawn() fires "spawned" immediately followed
|
|
617
|
+
// by "started", so without this guard the listener re-registers
|
|
618
|
+
// on the second event — generating a fresh MAP ULID and overwriting
|
|
619
|
+
// localIdToMapId. Consumers racing against that overwrite (like the
|
|
620
|
+
// sidecar's lifecycle bridge, which snapshots peerMapId into hub
|
|
621
|
+
// metadata) end up disagreeing with _macro/spawnAgent's return
|
|
622
|
+
// value on which ULID refers to this agent.
|
|
623
|
+
if (localIdToMapId.has(agent.id)) return;
|
|
410
624
|
try {
|
|
411
625
|
const registered = mapServer.agents.register({
|
|
412
626
|
name: agent.name ?? agent.id,
|
|
413
627
|
role: agent.role ?? "worker",
|
|
414
628
|
state: "idle",
|
|
415
629
|
metadata: {
|
|
416
|
-
|
|
630
|
+
peerAgentId: agent.id, // macro-agent's internal store id
|
|
417
631
|
parent: (agent as any).parent ?? null,
|
|
418
632
|
task: (agent as any).task ?? null,
|
|
419
633
|
cwd: (agent as any).cwd ?? null,
|
|
@@ -454,7 +668,7 @@ export function createMAPServerInstance(
|
|
|
454
668
|
role: agent.role ?? "worker",
|
|
455
669
|
state: agent.state === "running" ? "busy" : "idle",
|
|
456
670
|
metadata: {
|
|
457
|
-
|
|
671
|
+
peerAgentId: agent.id,
|
|
458
672
|
parent: agent.parent ?? null,
|
|
459
673
|
task: agent.task ?? null,
|
|
460
674
|
},
|
|
@@ -535,5 +749,9 @@ export function createMAPServerInstance(
|
|
|
535
749
|
getConnectionCount(): number {
|
|
536
750
|
return connectionCount;
|
|
537
751
|
},
|
|
752
|
+
|
|
753
|
+
getLocalMapId(localAgentId: string): string | undefined {
|
|
754
|
+
return localIdToMapId.get(localAgentId);
|
|
755
|
+
},
|
|
538
756
|
};
|
|
539
757
|
}
|
package/src/map/sidecar.ts
CHANGED
|
@@ -35,7 +35,7 @@ export function createMAPSidecar(
|
|
|
35
35
|
deps: MAPSidecarDeps,
|
|
36
36
|
config: MAPSidecarConfig,
|
|
37
37
|
): MAPSidecar {
|
|
38
|
-
const { agentManager, agentStore, inboxAdapter, tasksAdapter } = deps;
|
|
38
|
+
const { agentManager, agentStore, inboxAdapter, tasksAdapter, getLocalMapId, gitCascadeAdapter } = deps;
|
|
39
39
|
const scope = config.scope ?? "swarm:macro-agent";
|
|
40
40
|
const agentName = config.agentName ?? "macro-agent-sidecar";
|
|
41
41
|
|
|
@@ -50,6 +50,7 @@ export function createMAPSidecar(
|
|
|
50
50
|
let trajectoryReporter: TrajectoryReporter | null = null;
|
|
51
51
|
let taskBridge: TaskBridge | null = null;
|
|
52
52
|
let coordinationCleanup: (() => void) | null = null;
|
|
53
|
+
let cascadeBridgeCleanup: (() => void) | null = null;
|
|
53
54
|
let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
54
55
|
|
|
55
56
|
/**
|
|
@@ -82,6 +83,10 @@ export function createMAPSidecar(
|
|
|
82
83
|
coordinationCleanup();
|
|
83
84
|
coordinationCleanup = null;
|
|
84
85
|
}
|
|
86
|
+
if (cascadeBridgeCleanup) {
|
|
87
|
+
try { cascadeBridgeCleanup(); } catch { /* non-critical */ }
|
|
88
|
+
cascadeBridgeCleanup = null;
|
|
89
|
+
}
|
|
85
90
|
if (trajectoryReporter) {
|
|
86
91
|
trajectoryReporter.stop();
|
|
87
92
|
trajectoryReporter = null;
|
|
@@ -126,6 +131,10 @@ export function createMAPSidecar(
|
|
|
126
131
|
metadata: {
|
|
127
132
|
systemId: config.systemId ?? "macro-agent",
|
|
128
133
|
type: "macro-agent-sidecar",
|
|
134
|
+
// Signals that this swarm can spawn ACP-capable coordinators on demand,
|
|
135
|
+
// even before any coordinator has registered. The hub's /sessions/create-acp
|
|
136
|
+
// endpoint handles the spawn via _macro/spawnAgent when no ACP agent exists.
|
|
137
|
+
canHostAcp: true,
|
|
129
138
|
},
|
|
130
139
|
reconnection: {
|
|
131
140
|
enabled: config.reconnection?.enabled ?? true,
|
|
@@ -175,6 +184,19 @@ export function createMAPSidecar(
|
|
|
175
184
|
isConnected = true;
|
|
176
185
|
} // end if (!isConnected)
|
|
177
186
|
|
|
187
|
+
// Publish sidecar metadata to the hub. The MAP SDK's connect()/register()
|
|
188
|
+
// does not propagate the `metadata` field from connect options — it only
|
|
189
|
+
// forwards name/role/capabilities/scopes. Call updateMetadata explicitly
|
|
190
|
+
// so the hub sees canHostAcp (and any other metadata the UI relies on).
|
|
191
|
+
try {
|
|
192
|
+
const metadata = (connectOpts.metadata as Record<string, unknown>) ?? {};
|
|
193
|
+
if (typeof connection.updateMetadata === "function") {
|
|
194
|
+
await connection.updateMetadata(metadata);
|
|
195
|
+
}
|
|
196
|
+
} catch {
|
|
197
|
+
// Non-fatal — metadata is advisory
|
|
198
|
+
}
|
|
199
|
+
|
|
178
200
|
// Monitor connection state
|
|
179
201
|
connection.onStateChange(
|
|
180
202
|
(newState: string, _oldState: string) => {
|
|
@@ -252,6 +274,7 @@ export function createMAPSidecar(
|
|
|
252
274
|
agentStore,
|
|
253
275
|
scope,
|
|
254
276
|
taskBridge,
|
|
277
|
+
getLocalMapId,
|
|
255
278
|
);
|
|
256
279
|
lifecycleCallback = bridge.callback;
|
|
257
280
|
lifecycleCleanup = bridge.cleanup;
|
|
@@ -270,6 +293,21 @@ export function createMAPSidecar(
|
|
|
270
293
|
tasksAdapter,
|
|
271
294
|
trajectoryReporter,
|
|
272
295
|
});
|
|
296
|
+
|
|
297
|
+
// 5. Cascade Bridge + Action Handler (optional — only when a GitCascadeAdapter is available)
|
|
298
|
+
if (gitCascadeAdapter) {
|
|
299
|
+
const { createCascadeBridge } = await import("./cascade-bridge.js");
|
|
300
|
+
const cascadeBridge = createCascadeBridge(connection, gitCascadeAdapter);
|
|
301
|
+
|
|
302
|
+
// 5b. Inbound action handler — receives x-cascade/request.* from hub
|
|
303
|
+
const { setupCascadeActionHandlers } = await import("./cascade-action-handler.js");
|
|
304
|
+
const actionCleanup = setupCascadeActionHandlers(connection, gitCascadeAdapter);
|
|
305
|
+
|
|
306
|
+
cascadeBridgeCleanup = () => {
|
|
307
|
+
cascadeBridge.dispose();
|
|
308
|
+
actionCleanup();
|
|
309
|
+
};
|
|
310
|
+
}
|
|
273
311
|
}
|
|
274
312
|
|
|
275
313
|
return {
|
|
@@ -316,5 +354,14 @@ export function createMAPSidecar(
|
|
|
316
354
|
if (!trajectoryReporter) return null;
|
|
317
355
|
return trajectoryReporter.reportCheckpoint(checkpoint);
|
|
318
356
|
},
|
|
357
|
+
|
|
358
|
+
async emitEvent(event: Record<string, unknown>): Promise<void> {
|
|
359
|
+
if (!connection || !isConnected) return;
|
|
360
|
+
try {
|
|
361
|
+
await connection.send({ scope }, { ...event, _origin: "macro-agent" });
|
|
362
|
+
} catch {
|
|
363
|
+
// Best effort — MAP hub may be temporarily unavailable
|
|
364
|
+
}
|
|
365
|
+
},
|
|
319
366
|
};
|
|
320
367
|
}
|
package/src/map/types.ts
CHANGED
|
@@ -68,6 +68,21 @@ export interface MAPSidecarDeps {
|
|
|
68
68
|
agentStore: AgentStore;
|
|
69
69
|
inboxAdapter: InboxAdapter;
|
|
70
70
|
tasksAdapter: TasksAdapter;
|
|
71
|
+
/**
|
|
72
|
+
* Optional lookup for the local MAP server's ULID for a given local agent ID.
|
|
73
|
+
* When provided, the lifecycle bridge includes this ID in hub registration
|
|
74
|
+
* metadata so clients (e.g., SwarmCraft) can target the agent correctly on
|
|
75
|
+
* the macro-agent's own MAP server.
|
|
76
|
+
*/
|
|
77
|
+
getLocalMapId?: (localAgentId: string) => string | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* Optional GitCascadeAdapter. When provided, the sidecar wires a cascade
|
|
80
|
+
* bridge that forwards the adapter's event stream to the hub as
|
|
81
|
+
* `x-cascade/*` MAP notifications. Leave undefined to disable cascade
|
|
82
|
+
* event forwarding (macro-agent will still use cascade internally, just
|
|
83
|
+
* without hub observability).
|
|
84
|
+
*/
|
|
85
|
+
gitCascadeAdapter?: import("../workspace/git-cascade-adapter.js").GitCascadeAdapter;
|
|
71
86
|
}
|
|
72
87
|
|
|
73
88
|
// =============================================================================
|
|
@@ -88,6 +103,9 @@ export interface MAPSidecar {
|
|
|
88
103
|
reportCheckpoint(
|
|
89
104
|
checkpoint: TrajectoryCheckpointPayload,
|
|
90
105
|
): Promise<TrajectoryCheckpointResult | null>;
|
|
106
|
+
|
|
107
|
+
/** Emit a custom event to the MAP hub scope (best-effort, no-op if disconnected) */
|
|
108
|
+
emitEvent?(event: Record<string, unknown>): Promise<void>;
|
|
91
109
|
}
|
|
92
110
|
|
|
93
111
|
// =============================================================================
|
|
@@ -244,4 +262,9 @@ export interface MAPServerInstance {
|
|
|
244
262
|
getUrl(): string;
|
|
245
263
|
/** Get number of active connections */
|
|
246
264
|
getConnectionCount(): number;
|
|
265
|
+
/**
|
|
266
|
+
* Resolve a local agent ID (macro-agent internal) to its MAP server-assigned ULID.
|
|
267
|
+
* Returns undefined if the agent is not registered on the MAP server yet.
|
|
268
|
+
*/
|
|
269
|
+
getLocalMapId(localAgentId: string): string | undefined;
|
|
247
270
|
}
|
package/src/mcp/tools/done-v2.ts
CHANGED
|
@@ -106,6 +106,15 @@ function buildLifecycleContext(
|
|
|
106
106
|
streamId: record?.workspace_stream_id ?? process.env.MACRO_STREAM_ID,
|
|
107
107
|
};
|
|
108
108
|
|
|
109
|
+
// Pull task_ref out of agent metadata if it was stashed there at spawn
|
|
110
|
+
// time. Validates the shape — bad data is silently dropped rather than
|
|
111
|
+
// pushed downstream into cascade payloads.
|
|
112
|
+
const meta = record?.metadata as Record<string, unknown> | undefined;
|
|
113
|
+
const tr = meta?.task_ref as { resource_id?: unknown; node_id?: unknown } | undefined;
|
|
114
|
+
if (tr && typeof tr.resource_id === "string" && typeof tr.node_id === "string") {
|
|
115
|
+
ctx.taskRef = { resource_id: tr.resource_id, node_id: tr.node_id };
|
|
116
|
+
}
|
|
117
|
+
|
|
109
118
|
// Resolve capabilities for dispatch
|
|
110
119
|
if (roleRegistry) {
|
|
111
120
|
try {
|
|
@@ -142,6 +142,43 @@ export class TeamManagerV2 {
|
|
|
142
142
|
basePath ?? process.cwd()
|
|
143
143
|
);
|
|
144
144
|
|
|
145
|
+
// V3: auto-wire TopologyPolicy when the team declares
|
|
146
|
+
// `macro_agent.workspace`. Requires a WorkspaceManager to be present.
|
|
147
|
+
if (workspaceManager) {
|
|
148
|
+
try {
|
|
149
|
+
const { extractWorkspaceConfig } = await import(
|
|
150
|
+
"../workspace/yaml-schema.js"
|
|
151
|
+
);
|
|
152
|
+
const workspaceConfig = extractWorkspaceConfig(
|
|
153
|
+
manifest as unknown as { macro_agent?: Record<string, unknown> }
|
|
154
|
+
);
|
|
155
|
+
if (workspaceConfig) {
|
|
156
|
+
const { YamlDrivenTopology } = await import(
|
|
157
|
+
"../workspace/topology/yaml-driven.js"
|
|
158
|
+
);
|
|
159
|
+
const policy = new YamlDrivenTopology(workspaceConfig);
|
|
160
|
+
agentManager.setTopologyPolicy(policy);
|
|
161
|
+
|
|
162
|
+
// Kick the topology's onTeamStart so team-root streams get
|
|
163
|
+
// created before any agents spawn.
|
|
164
|
+
await policy.onTeamStart({
|
|
165
|
+
teamName: name,
|
|
166
|
+
teamInstanceId: `${name}-${this.instanceCounter + 1}`,
|
|
167
|
+
workspaceConfig,
|
|
168
|
+
workspaceManager,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
} catch (err) {
|
|
172
|
+
// Non-fatal: topology wiring is a progressive enhancement. Log and
|
|
173
|
+
// fall through to legacy capability-based dispatch.
|
|
174
|
+
console.warn(
|
|
175
|
+
`[TeamManagerV2] topology auto-wire skipped for team "${name}": ${
|
|
176
|
+
err instanceof Error ? err.message : String(err)
|
|
177
|
+
}`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
145
182
|
// Create runtime
|
|
146
183
|
const runtimeServices: TeamServicesV2 = {
|
|
147
184
|
agentManager,
|
|
@@ -506,12 +506,12 @@ export class TeamRuntimeV2 {
|
|
|
506
506
|
const capabilities = resolved.capabilities;
|
|
507
507
|
let streamId = options.streamId;
|
|
508
508
|
let streamConfig = options.streamConfig;
|
|
509
|
-
let
|
|
509
|
+
let gitCascadeTaskId = options.gitCascadeTaskId;
|
|
510
510
|
|
|
511
511
|
if (this.teamStreamId && capabilities) {
|
|
512
512
|
if (capabilities.includes(WORKSPACE_CAPABILITIES.WORKTREE)) {
|
|
513
513
|
streamId = streamId ?? this.teamStreamId;
|
|
514
|
-
|
|
514
|
+
gitCascadeTaskId = gitCascadeTaskId ?? `worker-${Date.now()}`;
|
|
515
515
|
} else if (capabilities.includes(WORKSPACE_CAPABILITIES.INTEGRATE)) {
|
|
516
516
|
streamId = streamId ?? this.teamStreamId;
|
|
517
517
|
}
|
|
@@ -521,7 +521,7 @@ export class TeamRuntimeV2 {
|
|
|
521
521
|
...options,
|
|
522
522
|
streamId,
|
|
523
523
|
streamConfig,
|
|
524
|
-
|
|
524
|
+
gitCascadeTaskId,
|
|
525
525
|
capabilities: capabilities ?? options.capabilities,
|
|
526
526
|
// Set team scope on all team agents
|
|
527
527
|
team_instance: options.team_instance ?? this.manifest.name,
|
|
@@ -846,6 +846,26 @@ Focus on correctness — your changes go live immediately.`);
|
|
|
846
846
|
const { workspaceManager } = this.services;
|
|
847
847
|
if (!workspaceManager || !this.integrationStrategy) return;
|
|
848
848
|
|
|
849
|
+
// V3 coexistence: if TeamManagerV2 has already wired a YamlDrivenTopology
|
|
850
|
+
// from `macro_agent.workspace`, that policy owns the team root stream.
|
|
851
|
+
// Don't create a second one via the legacy createIntegrationStream.
|
|
852
|
+
const hasV3Topology =
|
|
853
|
+
typeof (
|
|
854
|
+
this.services.agentManager as {
|
|
855
|
+
getTopologyPolicy?: () => unknown;
|
|
856
|
+
}
|
|
857
|
+
).getTopologyPolicy === 'function';
|
|
858
|
+
// AgentManager doesn't expose a getter today, so detect indirectly: a
|
|
859
|
+
// V3-wired team has already created a stream owned by `team:<name>`.
|
|
860
|
+
const existingTeamRoot = workspaceManager
|
|
861
|
+
.listStreams({ ownerId: `team:${this.manifest.name}` } as never)
|
|
862
|
+
.find((s: { name: string }) => s.name === this.manifest.name);
|
|
863
|
+
|
|
864
|
+
if (existingTeamRoot) {
|
|
865
|
+
this.teamStreamId = existingTeamRoot.id;
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
|
|
849
869
|
try {
|
|
850
870
|
this.teamStreamId = workspaceManager.createIntegrationStream(
|
|
851
871
|
rootAgentId,
|