macro-agent 0.1.8 → 0.1.10
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 +166 -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 +155 -6
- 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/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 +234 -43
- 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/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 +41 -0
- package/dist/boot-v2.d.ts.map +1 -1
- package/dist/boot-v2.js +16 -1
- package/dist/boot-v2.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/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-bridge.d.ts +44 -0
- package/dist/map/cascade-bridge.d.ts.map +1 -0
- package/dist/map/cascade-bridge.js +257 -0
- package/dist/map/cascade-bridge.js.map +1 -0
- 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 +47 -6
- package/dist/map/server.js.map +1 -1
- package/dist/map/sidecar.d.ts.map +1 -1
- package/dist/map/sidecar.js +33 -2
- package/dist/map/sidecar.js.map +1 -1
- package/dist/map/types.d.ts +20 -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 +908 -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 +185 -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 +111 -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 +110 -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 +145 -17
- package/dist/workspace/types.d.ts.map +1 -1
- package/dist/workspace/workspace-manager.d.ts +92 -13
- package/dist/workspace/workspace-manager.d.ts.map +1 -1
- package/dist/workspace/workspace-manager.js +373 -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/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 +4 -4
- package/src/__tests__/e2e/auto-sync.e2e.test.ts +257 -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/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 +167 -9
- package/src/acp/types.ts +10 -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__/task-ref-resolution.test.ts +231 -0
- package/src/agent/agent-manager-v2.ts +293 -48
- package/src/agent/agent-manager.ts +14 -0
- package/src/agent/types.ts +16 -2
- package/src/boot-v2.ts +68 -1
- 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/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__/lifecycle-bridge.test.ts +86 -10
- package/src/map/acp-bridge.ts +26 -3
- package/src/map/cascade-bridge.ts +301 -0
- package/src/map/lifecycle-bridge.ts +52 -17
- package/src/map/server.ts +47 -6
- package/src/map/sidecar.ts +31 -1
- package/src/map/types.ts +20 -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__/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 +1186 -0
- package/src/workspace/index.ts +11 -11
- package/src/workspace/landing/__tests__/strategies.test.ts +142 -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 +228 -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 +145 -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 +155 -0
- package/src/workspace/types.ts +191 -20
- package/src/workspace/workspace-manager.ts +474 -19
- package/src/workspace/yaml-schema.ts +216 -0
- package/src/workspace/dataplane-adapter.ts +0 -546
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YamlDrivenTopology tests (Phase 3).
|
|
3
|
+
*
|
|
4
|
+
* Verifies that the topology compiles each of the 6 workflow shapes from
|
|
5
|
+
* docs/git-cascade-integration-gaps.md §5 into correct WorkspaceDecisions.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
9
|
+
import { YamlDrivenTopology } from '../yaml-driven.js';
|
|
10
|
+
import { parseTeamWorkspaceConfig, type TeamWorkspaceConfig } from '../../yaml-schema.js';
|
|
11
|
+
import type { WorkspaceManager } from '../../types.js';
|
|
12
|
+
|
|
13
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
function mockWorkspaceManager(): WorkspaceManager {
|
|
16
|
+
const createdStreams: string[] = [];
|
|
17
|
+
return {
|
|
18
|
+
createStreamV3: vi.fn((_spec) => {
|
|
19
|
+
const id = `stream-${createdStreams.length + 1}`;
|
|
20
|
+
createdStreams.push(id);
|
|
21
|
+
return id;
|
|
22
|
+
}),
|
|
23
|
+
abandonStream: vi.fn(),
|
|
24
|
+
deallocateWorkspace: vi.fn(),
|
|
25
|
+
// The other methods are unused by topology logic in these tests.
|
|
26
|
+
} as unknown as WorkspaceManager;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ─── Tests ────────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
describe('YamlDrivenTopology', () => {
|
|
32
|
+
describe('peer swarm', () => {
|
|
33
|
+
let topology: YamlDrivenTopology;
|
|
34
|
+
let ws: WorkspaceManager;
|
|
35
|
+
|
|
36
|
+
beforeEach(async () => {
|
|
37
|
+
const config = parseTeamWorkspaceConfig({
|
|
38
|
+
roles: {
|
|
39
|
+
orchestrator: { workspace: 'none' },
|
|
40
|
+
peer: {
|
|
41
|
+
workspace: 'new_stream',
|
|
42
|
+
stream_lineage: 'fork_from_team_root',
|
|
43
|
+
landing: 'merge_to_parent_stream',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
topology = new YamlDrivenTopology(config!);
|
|
48
|
+
ws = mockWorkspaceManager();
|
|
49
|
+
await topology.onTeamStart({
|
|
50
|
+
teamName: 'peer-swarm',
|
|
51
|
+
teamInstanceId: 'swarm-1',
|
|
52
|
+
workspaceConfig: config,
|
|
53
|
+
workspaceManager: ws,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('creates a team root stream at start', () => {
|
|
58
|
+
expect(ws.createStreamV3).toHaveBeenCalledOnce();
|
|
59
|
+
expect(ws.createStreamV3).toHaveBeenCalledWith(
|
|
60
|
+
expect.objectContaining({
|
|
61
|
+
ownerId: 'team:peer-swarm',
|
|
62
|
+
forkFrom: 'main',
|
|
63
|
+
})
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('spawns orchestrator with kind=none', async () => {
|
|
68
|
+
const decision = await topology.onAgentSpawn({
|
|
69
|
+
agentId: 'agent-orch',
|
|
70
|
+
role: 'orchestrator',
|
|
71
|
+
workspaceManager: ws,
|
|
72
|
+
});
|
|
73
|
+
expect(decision.kind).toBe('none');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('spawns peer with new-stream forked off team root', async () => {
|
|
77
|
+
const decision = await topology.onAgentSpawn({
|
|
78
|
+
agentId: 'agent-peer-1',
|
|
79
|
+
role: 'peer',
|
|
80
|
+
workspaceManager: ws,
|
|
81
|
+
});
|
|
82
|
+
expect(decision.kind).toBe('new-stream');
|
|
83
|
+
if (decision.kind === 'new-stream') {
|
|
84
|
+
expect(decision.streamSpec.parent).toBe('stream-1');
|
|
85
|
+
expect(decision.streamSpec.ownerId).toBe('agent-peer-1');
|
|
86
|
+
expect(decision.allocateWorktree).toBe(true);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('triad (coordinator / worker / integrator)', () => {
|
|
92
|
+
let topology: YamlDrivenTopology;
|
|
93
|
+
let ws: WorkspaceManager;
|
|
94
|
+
|
|
95
|
+
beforeEach(async () => {
|
|
96
|
+
const config = parseTeamWorkspaceConfig({
|
|
97
|
+
roles: {
|
|
98
|
+
coordinator: { workspace: 'attach_to_team_root' },
|
|
99
|
+
worker: {
|
|
100
|
+
workspace: 'new_stream',
|
|
101
|
+
stream_lineage: 'fork_from_parent',
|
|
102
|
+
landing: 'queue_to_branch',
|
|
103
|
+
},
|
|
104
|
+
integrator: {
|
|
105
|
+
workspace: 'attach_to_team_root',
|
|
106
|
+
capabilities: ['workspace.merge', 'merge_queue.drain'],
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
topology = new YamlDrivenTopology(config!);
|
|
111
|
+
ws = mockWorkspaceManager();
|
|
112
|
+
await topology.onTeamStart({
|
|
113
|
+
teamName: 'triad',
|
|
114
|
+
teamInstanceId: 'triad-1',
|
|
115
|
+
workspaceConfig: config,
|
|
116
|
+
workspaceManager: ws,
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('attaches coordinator to team root stream', async () => {
|
|
121
|
+
const decision = await topology.onAgentSpawn({
|
|
122
|
+
agentId: 'agent-coord',
|
|
123
|
+
role: 'coordinator',
|
|
124
|
+
workspaceManager: ws,
|
|
125
|
+
});
|
|
126
|
+
expect(decision.kind).toBe('attach-to-stream');
|
|
127
|
+
if (decision.kind === 'attach-to-stream') {
|
|
128
|
+
expect(decision.streamId).toBe('stream-1');
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('spawns worker with fork_from_parent lineage', async () => {
|
|
133
|
+
const decision = await topology.onAgentSpawn({
|
|
134
|
+
agentId: 'agent-worker',
|
|
135
|
+
role: 'worker',
|
|
136
|
+
parentAgentId: 'agent-coord',
|
|
137
|
+
parentStreamId: 'stream-1',
|
|
138
|
+
workspaceManager: ws,
|
|
139
|
+
});
|
|
140
|
+
expect(decision.kind).toBe('new-stream');
|
|
141
|
+
if (decision.kind === 'new-stream') {
|
|
142
|
+
expect(decision.streamSpec.parent).toBe('stream-1');
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
describe('pipeline (share_with_agent)', () => {
|
|
148
|
+
it('resolves share_with via getAgentByRole callback', async () => {
|
|
149
|
+
const config = parseTeamWorkspaceConfig({
|
|
150
|
+
roles: {
|
|
151
|
+
coder: {
|
|
152
|
+
workspace: 'new_stream',
|
|
153
|
+
stream_lineage: 'fork_from_team_root',
|
|
154
|
+
},
|
|
155
|
+
reviewer: {
|
|
156
|
+
workspace: 'share_with_agent',
|
|
157
|
+
share_with: 'coder',
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
const topology = new YamlDrivenTopology(config!);
|
|
162
|
+
const ws = mockWorkspaceManager();
|
|
163
|
+
await topology.onTeamStart({
|
|
164
|
+
teamName: 'pipeline',
|
|
165
|
+
teamInstanceId: 'p-1',
|
|
166
|
+
workspaceConfig: config,
|
|
167
|
+
workspaceManager: ws,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const decision = await topology.onAgentSpawn({
|
|
171
|
+
agentId: 'agent-rev',
|
|
172
|
+
role: 'reviewer',
|
|
173
|
+
workspaceManager: ws,
|
|
174
|
+
getAgentByRole: (r) => (r === 'coder' ? 'agent-coder' : null),
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
expect(decision.kind).toBe('share-with-agent');
|
|
178
|
+
if (decision.kind === 'share-with-agent') {
|
|
179
|
+
expect(decision.agentId).toBe('agent-coder');
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('falls back to share-parent-cwd when share_with role has no active agent', async () => {
|
|
184
|
+
const config = parseTeamWorkspaceConfig({
|
|
185
|
+
roles: {
|
|
186
|
+
coder: { workspace: 'new_stream', stream_lineage: 'fork_from_team_root' },
|
|
187
|
+
reviewer: { workspace: 'share_with_agent', share_with: 'coder' },
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
const topology = new YamlDrivenTopology(config!);
|
|
191
|
+
const ws = mockWorkspaceManager();
|
|
192
|
+
await topology.onTeamStart({
|
|
193
|
+
teamName: 'pipeline',
|
|
194
|
+
teamInstanceId: 'p-1',
|
|
195
|
+
workspaceConfig: config,
|
|
196
|
+
workspaceManager: ws,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const decision = await topology.onAgentSpawn({
|
|
200
|
+
agentId: 'agent-rev',
|
|
201
|
+
role: 'reviewer',
|
|
202
|
+
workspaceManager: ws,
|
|
203
|
+
getAgentByRole: () => null,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
expect(decision.kind).toBe('share-parent-cwd');
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
describe('research / read-only', () => {
|
|
211
|
+
it('returns none for workspace: none, skips team root creation', async () => {
|
|
212
|
+
const config = parseTeamWorkspaceConfig({
|
|
213
|
+
roles: {
|
|
214
|
+
researcher: { workspace: 'none' },
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
const topology = new YamlDrivenTopology(config!);
|
|
218
|
+
const ws = mockWorkspaceManager();
|
|
219
|
+
const plan = await topology.onTeamStart({
|
|
220
|
+
teamName: 'research',
|
|
221
|
+
teamInstanceId: 'r-1',
|
|
222
|
+
workspaceConfig: config,
|
|
223
|
+
workspaceManager: ws,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
expect(plan.teamStreamId).toBeUndefined();
|
|
227
|
+
expect(ws.createStreamV3).not.toHaveBeenCalled();
|
|
228
|
+
|
|
229
|
+
const decision = await topology.onAgentSpawn({
|
|
230
|
+
agentId: 'agent-1',
|
|
231
|
+
role: 'researcher',
|
|
232
|
+
workspaceManager: ws,
|
|
233
|
+
});
|
|
234
|
+
expect(decision.kind).toBe('none');
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe('solo stack', () => {
|
|
239
|
+
it('author gets a new stream forked from team root', async () => {
|
|
240
|
+
const config = parseTeamWorkspaceConfig({
|
|
241
|
+
roles: {
|
|
242
|
+
author: {
|
|
243
|
+
workspace: 'new_stream',
|
|
244
|
+
stream_lineage: 'fork_from_team_root',
|
|
245
|
+
landing: 'merge_to_parent_stream',
|
|
246
|
+
cascade_on_parent_update: true,
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
const topology = new YamlDrivenTopology(config!);
|
|
251
|
+
const ws = mockWorkspaceManager();
|
|
252
|
+
await topology.onTeamStart({
|
|
253
|
+
teamName: 'solo',
|
|
254
|
+
teamInstanceId: 's-1',
|
|
255
|
+
workspaceConfig: config,
|
|
256
|
+
workspaceManager: ws,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const decision = await topology.onAgentSpawn({
|
|
260
|
+
agentId: 'agent-auth',
|
|
261
|
+
role: 'author',
|
|
262
|
+
workspaceManager: ws,
|
|
263
|
+
});
|
|
264
|
+
expect(decision.kind).toBe('new-stream');
|
|
265
|
+
if (decision.kind === 'new-stream') {
|
|
266
|
+
expect(decision.streamSpec.parent).toBe('stream-1');
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
describe('onTeamStop', () => {
|
|
272
|
+
it('abandons team stream when on_team_complete=abandon', async () => {
|
|
273
|
+
const config = parseTeamWorkspaceConfig({
|
|
274
|
+
on_team_complete: 'abandon',
|
|
275
|
+
roles: {
|
|
276
|
+
peer: { workspace: 'new_stream', stream_lineage: 'fork_from_team_root' },
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
const topology = new YamlDrivenTopology(config!);
|
|
280
|
+
const ws = mockWorkspaceManager();
|
|
281
|
+
await topology.onTeamStart({
|
|
282
|
+
teamName: 'x',
|
|
283
|
+
teamInstanceId: 'x-1',
|
|
284
|
+
workspaceConfig: config,
|
|
285
|
+
workspaceManager: ws,
|
|
286
|
+
});
|
|
287
|
+
await topology.onTeamStop({
|
|
288
|
+
teamName: 'x',
|
|
289
|
+
teamInstanceId: 'x-1',
|
|
290
|
+
teamStreamId: 'stream-1',
|
|
291
|
+
workspaceManager: ws,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
expect(ws.abandonStream).toHaveBeenCalledWith(
|
|
295
|
+
'stream-1',
|
|
296
|
+
expect.objectContaining({ cascade: true })
|
|
297
|
+
);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it('keeps team stream when on_team_complete=keep (default)', async () => {
|
|
301
|
+
const config = parseTeamWorkspaceConfig({
|
|
302
|
+
roles: {
|
|
303
|
+
peer: { workspace: 'new_stream', stream_lineage: 'fork_from_team_root' },
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
const topology = new YamlDrivenTopology(config!);
|
|
307
|
+
const ws = mockWorkspaceManager();
|
|
308
|
+
await topology.onTeamStart({
|
|
309
|
+
teamName: 'x',
|
|
310
|
+
teamInstanceId: 'x-1',
|
|
311
|
+
workspaceConfig: config,
|
|
312
|
+
workspaceManager: ws,
|
|
313
|
+
});
|
|
314
|
+
await topology.onTeamStop({
|
|
315
|
+
teamName: 'x',
|
|
316
|
+
teamInstanceId: 'x-1',
|
|
317
|
+
teamStreamId: 'stream-1',
|
|
318
|
+
workspaceManager: ws,
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
expect(ws.abandonStream).not.toHaveBeenCalled();
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
describe('onAgentComplete', () => {
|
|
326
|
+
it('deallocates the agent workspace', async () => {
|
|
327
|
+
const config = parseTeamWorkspaceConfig({
|
|
328
|
+
roles: {
|
|
329
|
+
peer: { workspace: 'new_stream', stream_lineage: 'fork_from_team_root' },
|
|
330
|
+
},
|
|
331
|
+
});
|
|
332
|
+
const topology = new YamlDrivenTopology(config!);
|
|
333
|
+
const ws = mockWorkspaceManager();
|
|
334
|
+
|
|
335
|
+
await topology.onAgentComplete({
|
|
336
|
+
agentId: 'agent-1',
|
|
337
|
+
role: 'peer',
|
|
338
|
+
reason: 'completed',
|
|
339
|
+
workspaceManager: ws,
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
expect(ws.deallocateWorkspace).toHaveBeenCalledWith('agent-1');
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Topology policies — compile team YAML into workspace decisions.
|
|
3
|
+
*
|
|
4
|
+
* @module workspace/topology
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type {
|
|
8
|
+
TopologyPolicy,
|
|
9
|
+
TeamStartContext,
|
|
10
|
+
TeamStartPlan,
|
|
11
|
+
SpawnContext,
|
|
12
|
+
WorkspaceDecision,
|
|
13
|
+
AgentCompleteContext,
|
|
14
|
+
TeamStopContext,
|
|
15
|
+
} from './types.js';
|
|
16
|
+
|
|
17
|
+
export { YamlDrivenTopology } from './yaml-driven.js';
|
|
18
|
+
export { NoWorkspaceTopology } from './no-workspace.js';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NoWorkspaceTopology — null TopologyPolicy.
|
|
3
|
+
*
|
|
4
|
+
* Returns `share-parent-cwd` for every spawn. Used when no team YAML declares
|
|
5
|
+
* `macro_agent.workspace` — agents run in the spawner's cwd with no git-cascade
|
|
6
|
+
* state. Safe default for teams that don't need isolation.
|
|
7
|
+
*
|
|
8
|
+
* @module workspace/topology/no-workspace
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
TopologyPolicy,
|
|
13
|
+
TeamStartContext,
|
|
14
|
+
TeamStartPlan,
|
|
15
|
+
SpawnContext,
|
|
16
|
+
WorkspaceDecision,
|
|
17
|
+
AgentCompleteContext,
|
|
18
|
+
TeamStopContext,
|
|
19
|
+
} from './types.js';
|
|
20
|
+
|
|
21
|
+
export class NoWorkspaceTopology implements TopologyPolicy {
|
|
22
|
+
readonly name = 'no-workspace';
|
|
23
|
+
|
|
24
|
+
async onTeamStart(_ctx: TeamStartContext): Promise<TeamStartPlan> {
|
|
25
|
+
return {};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async onAgentSpawn(_ctx: SpawnContext): Promise<WorkspaceDecision> {
|
|
29
|
+
return { kind: 'share-parent-cwd' };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async onAgentComplete(_ctx: AgentCompleteContext): Promise<void> {
|
|
33
|
+
// No-op
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async onTeamStop(_ctx: TeamStopContext): Promise<void> {
|
|
37
|
+
// No-op
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TopologyPolicy — compiles team YAML into per-spawn workspace decisions.
|
|
3
|
+
*
|
|
4
|
+
* A topology policy sits between AgentManagerV2 and WorkspaceManager. It
|
|
5
|
+
* translates declarative team configuration (role shape, stream lineage,
|
|
6
|
+
* landing strategy) into imperative `WorkspaceDecision`s that AgentManager
|
|
7
|
+
* executes via the WorkspaceManager v3 surface.
|
|
8
|
+
*
|
|
9
|
+
* Three built-in policies:
|
|
10
|
+
* - `YamlDrivenTopology` — primary; compiles TeamWorkspaceConfig (default)
|
|
11
|
+
* - `NoWorkspaceTopology` — null policy; returns `none` for every agent
|
|
12
|
+
* - `CognitiveCoreTopology` — programmatic policy for cognitive-core style
|
|
13
|
+
* callers that bypass YAML (deferred; stub for now)
|
|
14
|
+
*
|
|
15
|
+
* @module workspace/topology/types
|
|
16
|
+
* @see docs/workspace-interfaces.md §7
|
|
17
|
+
* @see docs/workspace-redesign-plan.md Phase 3
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type { AgentId, StreamId, Principal, StreamSpec } from '../types-v3.js';
|
|
21
|
+
import type { WorkspaceManager } from '../types.js';
|
|
22
|
+
import type {
|
|
23
|
+
TeamWorkspaceConfig,
|
|
24
|
+
RoleWorkspaceConfig,
|
|
25
|
+
} from '../yaml-schema.js';
|
|
26
|
+
|
|
27
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
28
|
+
// Context types
|
|
29
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
export interface TeamStartContext {
|
|
32
|
+
teamName: string;
|
|
33
|
+
teamInstanceId: string;
|
|
34
|
+
workspaceConfig: TeamWorkspaceConfig | null;
|
|
35
|
+
workspaceManager: WorkspaceManager;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface TeamStartPlan {
|
|
39
|
+
teamStreamId?: StreamId;
|
|
40
|
+
additionalStreams?: StreamId[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface SpawnContext {
|
|
44
|
+
agentId: AgentId;
|
|
45
|
+
role: string;
|
|
46
|
+
parentAgentId?: AgentId;
|
|
47
|
+
parentStreamId?: StreamId;
|
|
48
|
+
teamStreamId?: StreamId;
|
|
49
|
+
workspaceManager: WorkspaceManager;
|
|
50
|
+
/** Look up a live agent ID by role (for workspace: share_with_agent). */
|
|
51
|
+
getAgentByRole?: (role: string) => AgentId | null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface AgentCompleteContext {
|
|
55
|
+
agentId: AgentId;
|
|
56
|
+
role: string;
|
|
57
|
+
reason: 'completed' | 'failed' | 'cascade' | 'interrupted';
|
|
58
|
+
streamId?: StreamId;
|
|
59
|
+
workspaceManager: WorkspaceManager;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface TeamStopContext {
|
|
63
|
+
teamName: string;
|
|
64
|
+
teamInstanceId: string;
|
|
65
|
+
teamStreamId?: StreamId;
|
|
66
|
+
workspaceManager: WorkspaceManager;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
70
|
+
// Decision type
|
|
71
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Declarative description of what workspace the spawning agent should get.
|
|
75
|
+
* AgentManagerV2 executes this via WorkspaceManager calls.
|
|
76
|
+
*/
|
|
77
|
+
export type WorkspaceDecision =
|
|
78
|
+
| { kind: 'none' }
|
|
79
|
+
| { kind: 'share-parent-cwd' }
|
|
80
|
+
| { kind: 'share-with-agent'; agentId: AgentId }
|
|
81
|
+
| { kind: 'attach-to-stream'; streamId: StreamId; allocateWorktree: boolean }
|
|
82
|
+
| {
|
|
83
|
+
kind: 'new-stream';
|
|
84
|
+
streamSpec: StreamSpec;
|
|
85
|
+
allocateWorktree: boolean;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
89
|
+
// Policy interface
|
|
90
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
export interface TopologyPolicy {
|
|
93
|
+
readonly name: string;
|
|
94
|
+
|
|
95
|
+
onTeamStart(ctx: TeamStartContext): Promise<TeamStartPlan>;
|
|
96
|
+
onAgentSpawn(ctx: SpawnContext): Promise<WorkspaceDecision>;
|
|
97
|
+
onAgentComplete(ctx: AgentCompleteContext): Promise<void>;
|
|
98
|
+
onTeamStop(ctx: TeamStopContext): Promise<void>;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Optional: subscribed when the topology cares about parent-stream updates.
|
|
102
|
+
* Used by roles with `on_parent_advanced: sync_with_parent`.
|
|
103
|
+
*/
|
|
104
|
+
onParentStreamAdvanced?(ctx: {
|
|
105
|
+
parentStreamId: StreamId;
|
|
106
|
+
affectedAgents: AgentId[];
|
|
107
|
+
workspaceManager: WorkspaceManager;
|
|
108
|
+
}): Promise<void>;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Look up the per-role config if this policy is YAML-driven. Other code
|
|
112
|
+
* (e.g., the conflict recovery dispatcher) consults this to find
|
|
113
|
+
* `on_conflict_recovery` overrides at dispatch time.
|
|
114
|
+
*/
|
|
115
|
+
getRoleConfig?(role: string): RoleWorkspaceConfig | null;
|
|
116
|
+
}
|