macro-agent 0.1.7 → 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 +179 -38
- 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 -71
- 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 +34 -37
- 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 -8
- package/dist/map/lifecycle-bridge.d.ts.map +1 -1
- package/dist/map/lifecycle-bridge.js +76 -22
- 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 -4
- 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 +71 -11
- package/src/agent/__tests__/task-ref-resolution.test.ts +231 -0
- package/src/agent/agent-manager-v2.ts +293 -77
- package/src/agent/agent-manager.ts +14 -0
- package/src/agent/types.ts +16 -2
- package/src/boot-v2.ts +87 -36
- package/src/cli/index.ts +61 -0
- package/src/cognitive/__tests__/macro-agent-backend.test.ts +47 -5
- 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 +165 -22
- package/src/map/acp-bridge.ts +26 -3
- package/src/map/cascade-bridge.ts +301 -0
- package/src/map/lifecycle-bridge.ts +77 -27
- package/src/map/server.ts +47 -6
- package/src/map/sidecar.ts +31 -3
- 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,302 @@
|
|
|
1
|
+
# Workspace Redesign: Implementation Plan
|
|
2
|
+
|
|
3
|
+
Turns the design in `docs/git-cascade-integration-gaps.md`, `docs/workspace-interfaces.md`, and `docs/conflict-recovery.md` into ordered, independently-reviewable work.
|
|
4
|
+
|
|
5
|
+
**Status**: active. Phase 0 starting now.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Goal
|
|
10
|
+
|
|
11
|
+
Replace macro-agent's role-shaped workspace layer with a stream-first abstraction over git-cascade, configurable via team YAML, usable for all 6 workflows in the design doc (§5), with clean hooks for conflict recovery.
|
|
12
|
+
|
|
13
|
+
## Non-goals (v1)
|
|
14
|
+
|
|
15
|
+
- Graphite-style simultaneous multi-stream per agent (deferred — see interfaces §7.8 carve-out)
|
|
16
|
+
- cc-swarm integration (separate follow-up)
|
|
17
|
+
- Human-escalation UI for conflict recovery
|
|
18
|
+
- Cross-process conflict coordination
|
|
19
|
+
- Petitioning git-cascade to expose `updateWorktreeStream` publicly (accept raw `git checkout` workaround)
|
|
20
|
+
|
|
21
|
+
## Success criteria
|
|
22
|
+
|
|
23
|
+
- All 6 workflows from `git-cascade-integration-gaps.md` §5 expressible via YAML only
|
|
24
|
+
- Zero references to `src/workspace/merge-queue/` (duplicate removed)
|
|
25
|
+
- Every commit from a streamed agent carries a Change-Id
|
|
26
|
+
- `cascadeRebase` called on parent-stream updates when YAML requests it
|
|
27
|
+
- Conflict recovery dispatch works end-to-end with `spawn-resolver` strategy
|
|
28
|
+
- Existing `self-driving` team continues to work (regression-free)
|
|
29
|
+
- AgentManagerV2 has no role-name `switch(role)` dispatch for workspace allocation
|
|
30
|
+
- Total consumer-site diff under ~400 lines across `agent-manager-v2.ts` + `team-runtime-v2.ts`
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Phase summary
|
|
35
|
+
|
|
36
|
+
| # | Phase | Risk | Blocks |
|
|
37
|
+
|---|---|---|---|
|
|
38
|
+
| 0 | Additive DataplaneAdapter expansion | Very low | 1, 7 |
|
|
39
|
+
| 1 | New WorkspaceManager surface (alongside old) | Low | 3, 5, 7 |
|
|
40
|
+
| 2 | YAML Zod schema + team config parsing | Very low | 3 |
|
|
41
|
+
| 3 | TopologyPolicy + YamlDrivenTopology | Medium | 4 |
|
|
42
|
+
| 4 | AgentManagerV2 uses TopologyPolicy | Medium | 5, 8 |
|
|
43
|
+
| 5 | LandingStrategy integration | Medium | 6 |
|
|
44
|
+
| 6 | Delete macro-agent's duplicate MergeQueue | Low (once 5 lands) | — |
|
|
45
|
+
| 7 | ConflictRecoveryStrategy infrastructure | Low | — |
|
|
46
|
+
| 8 | Remove role-name fallback from AgentManagerV2 | Low (once 4 lands) | 9 |
|
|
47
|
+
| 9 | Deprecate & remove old WorkspaceManager methods | Low | — |
|
|
48
|
+
| 10 | Boot ergonomics: `macro-agent run <team>` CLI | Low | — |
|
|
49
|
+
|
|
50
|
+
Each phase is 1 PR unless noted.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Phase 0 — Additive DataplaneAdapter expansion
|
|
55
|
+
|
|
56
|
+
**Scope**: expose the git-cascade primitives macro-agent doesn't currently surface. No new consumers yet; no behavior change.
|
|
57
|
+
|
|
58
|
+
**Subtasks**:
|
|
59
|
+
- Add to `src/workspace/dataplane-adapter.ts`:
|
|
60
|
+
- `forkStream(opts)` → `tracker.forkStream`
|
|
61
|
+
- `mergeStream(opts)` → `tracker.mergeStream`
|
|
62
|
+
- `syncWithParent(opts)` → `tracker.syncWithParent`
|
|
63
|
+
- `rebaseOntoStream(opts)` + async variant
|
|
64
|
+
- `cascadeRebase(opts)` → imports `cascade` module
|
|
65
|
+
- `commitChanges(opts)` → `tracker.commitChanges`
|
|
66
|
+
- `abandonStream` / `pauseStream` / `resumeStream`
|
|
67
|
+
- `listStreams` / `getStreamHierarchy` / `getDependents`
|
|
68
|
+
- `addToMergeQueue` / `getNextToMerge` / `processMergeQueue` / `markMergeQueueReady` / etc.
|
|
69
|
+
- `getChange` / `getChangeByCommit` / `markChangesMerged`
|
|
70
|
+
- `getConflictForStream` / `listConflicts` (if exposed)
|
|
71
|
+
- Event subscription via tracker's `emit` hook
|
|
72
|
+
- Unit test each new method against a temp-repo fixture.
|
|
73
|
+
- No changes to public WorkspaceManager interface yet.
|
|
74
|
+
|
|
75
|
+
**Acceptance**: all new methods have unit tests passing; `src/workspace/dataplane-adapter.ts` surface matches what §5 of `workspace-interfaces.md` needs. No existing tests broken.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Phase 1 — New WorkspaceManager surface (alongside old)
|
|
80
|
+
|
|
81
|
+
**Scope**: introduce new methods on `DefaultWorkspaceManager` matching the redesigned interface in `workspace-interfaces.md` §5. Old methods untouched.
|
|
82
|
+
|
|
83
|
+
**Subtasks**:
|
|
84
|
+
- Create `src/workspace/types-v3.ts` with new types: `Principal`, `PseudoAgentId`, `StreamSpec`, `Worktree` (v3 shape), `AllocateWorktreeOpts`, `WorkspaceEvent` union, `MergeResult`, `RebaseResult`, `CascadeResult`, `ReconcileResult`.
|
|
85
|
+
- Extend `src/workspace/types.ts` `WorkspaceManager` interface with new methods (marked `// v3` for clarity):
|
|
86
|
+
- `createStream`, `forkStream`, `mergeStream`, `syncWithParent`, `rebaseOntoStream`, `cascadeRebase`
|
|
87
|
+
- `commitChanges`, `markChangesMerged`
|
|
88
|
+
- `allocateWorktree` (new signature), `getWorktreeForAgent`, `listWorktrees`
|
|
89
|
+
- `land`, `registerLandingStrategy`, `unregisterLandingStrategy`
|
|
90
|
+
- `reconcile`, `healthCheck`, `onEvent`
|
|
91
|
+
- Implement on `DefaultWorkspaceManager` by delegating to DataplaneAdapter.
|
|
92
|
+
- Write own `reconcile()` wrapper: calls git-cascade's + walks worktree pool + cleans orphans.
|
|
93
|
+
- Add structured event re-emission: subscribe to git-cascade's `x-cascade/*` events; emit unified `WorkspaceEvent`.
|
|
94
|
+
- No consumers use new methods yet.
|
|
95
|
+
|
|
96
|
+
**Acceptance**: new interface is importable and usable; unit tests for each new method; existing `agent-manager-v2.ts` and `team-runtime-v2.ts` compile unchanged.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Phase 2 — YAML Zod schema + team config parsing
|
|
101
|
+
|
|
102
|
+
**Scope**: `macro_agent.workspace` section validated via Zod at team load time.
|
|
103
|
+
|
|
104
|
+
**Subtasks**:
|
|
105
|
+
- Create `src/workspace/yaml-schema.ts` with `TeamWorkspaceConfigSchema` + `RoleWorkspaceConfigSchema` per `workspace-interfaces.md` §8.
|
|
106
|
+
- Plumb parsing into `src/teams/team-loader.ts`: when `macro_agent.workspace` is present, validate and attach to resolved team.
|
|
107
|
+
- Add cross-field validations in `superRefine` (`workspace: share_with_agent` requires `share_with`; `stream_lineage: track_existing_branch` requires `track_branch`).
|
|
108
|
+
- Update `self-driving/team.yaml` and any other existing configs to the new schema (or leave as-is if they omit `macro_agent.workspace`; parser accepts missing).
|
|
109
|
+
- Schema failures surface as team load errors with useful messages.
|
|
110
|
+
|
|
111
|
+
**Acceptance**: valid YAML parses; invalid YAML fails with clear error; test coverage for each schema branch.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Phase 3 — TopologyPolicy + YamlDrivenTopology
|
|
116
|
+
|
|
117
|
+
**Scope**: introduce the `TopologyPolicy` interface and the default YAML-driven implementation.
|
|
118
|
+
|
|
119
|
+
**Subtasks**:
|
|
120
|
+
- Create `src/workspace/topology/types.ts` with `TopologyPolicy` interface + context types per `workspace-interfaces.md` §7.
|
|
121
|
+
- Create `src/workspace/topology/yaml-driven.ts` implementing `YamlDrivenTopology`:
|
|
122
|
+
- `onTeamStart` → creates team root stream if any role's `stream_lineage` requires it
|
|
123
|
+
- `onAgentSpawn` → maps role YAML config to `WorkspaceDecision`
|
|
124
|
+
- `onAgentComplete` → deallocate, optionally cascade
|
|
125
|
+
- `onParentStreamAdvanced` → schedule `syncWithParent` via WakeManager (for `on_parent_advanced: sync_with_parent` roles)
|
|
126
|
+
- Create `src/workspace/topology/cognitive-core.ts` — minimal policy for programmatic consumers
|
|
127
|
+
- Create `src/workspace/topology/no-workspace.ts` — null policy
|
|
128
|
+
- Unit test: `YamlDrivenTopology` on `self-driving` config reproduces current spawn decisions; on `peer-swarm` (new example) produces correct forkStream decisions.
|
|
129
|
+
|
|
130
|
+
**Acceptance**: policies produce same decisions as current code for self-driving; new YAML-only teams work.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Phase 4 — AgentManagerV2 uses TopologyPolicy
|
|
135
|
+
|
|
136
|
+
**Scope**: rewire AgentManagerV2 to delegate workspace allocation to a TopologyPolicy instance. Keep fallback to old `switch(role)` dispatch if no policy set.
|
|
137
|
+
|
|
138
|
+
**Subtasks**:
|
|
139
|
+
- Add `setTopologyPolicy(policy: TopologyPolicy)` to AgentManagerV2.
|
|
140
|
+
- In `spawn()`, when `topologyPolicy` is set: call `onAgentSpawn` and execute the `WorkspaceDecision` via new `WorkspaceManager` methods.
|
|
141
|
+
- When unset: preserve existing `createWorkspaceForRole` behavior.
|
|
142
|
+
- Wire through boot-v2: when team config has `macro_agent.workspace`, instantiate `YamlDrivenTopology` and call `setTopologyPolicy`.
|
|
143
|
+
- Update TeamManagerV2 / TeamRuntimeV2 to use `TopologyPolicy.onTeamStart` for stream creation instead of direct `createIntegrationStream`.
|
|
144
|
+
|
|
145
|
+
**Acceptance**: self-driving team works unchanged; a new peer-swarm test team with `macro_agent.workspace` config spawns agents with correct streams.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Phase 5 — LandingStrategy integration
|
|
150
|
+
|
|
151
|
+
**Scope**: replace the dead `IntegrationStrategy` abstraction with the new `LandingStrategy`. Workers land via `workspaceManager.land()` instead of direct queue submit.
|
|
152
|
+
|
|
153
|
+
**Subtasks**:
|
|
154
|
+
- Create `src/workspace/landing/types.ts` with `LandingStrategy` + `LandingContext` per `workspace-interfaces.md` §6.
|
|
155
|
+
- Create `src/workspace/landing/` strategies:
|
|
156
|
+
- `merge-to-parent.ts` — calls `mergeStream` + optional `cascadeRebase`
|
|
157
|
+
- `queue-to-branch.ts` — calls `addToMergeQueue`; drains are integrator-driven
|
|
158
|
+
- `cherry-pick-stack.ts` — `createStackFromStream` + `cherryPickStackToTarget`
|
|
159
|
+
- `direct-push.ts` — raw rebase+push (current trunk behavior, retained for compat)
|
|
160
|
+
- `optimistic-push.ts` — direct-push + validation event
|
|
161
|
+
- Register all 5 at boot.
|
|
162
|
+
- Add MCP tool: `land` (gated by `workspace.land` capability).
|
|
163
|
+
- Wire `AgentManagerV2.terminate` (worker completion path): replace hardcoded `mergeQueue.submit` with `workspaceManager.land({ agentId, streamId, strategyName: roleConfig.landing })`.
|
|
164
|
+
- Add MCP tools for integrator: `next_merge_request`, `merge_stream`, `mark_merge_complete` (gated by `merge_queue.drain` + `workspace.merge`).
|
|
165
|
+
- Delete dead `src/workspace/strategies/` code (the `IntegrationStrategy` files).
|
|
166
|
+
|
|
167
|
+
**Acceptance**: worker completion routes through strategy; `self-driving` still works via `queue-to-branch` strategy; dead strategies directory gone.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Phase 6 — Delete macro-agent's duplicate MergeQueue
|
|
172
|
+
|
|
173
|
+
**Scope**: remove `src/workspace/merge-queue/` — fully replaced by git-cascade's built-in.
|
|
174
|
+
|
|
175
|
+
**Subtasks**:
|
|
176
|
+
- Verify all merge queue access goes through `DataplaneAdapter.addToMergeQueue` / `getNextToMerge` / etc. (surfaces git-cascade's module).
|
|
177
|
+
- Delete `src/workspace/merge-queue/*`.
|
|
178
|
+
- Update `DefaultWorkspaceManager.getMergeQueue()`: either return a thin shim wrapping git-cascade's queue APIs, or remove the method entirely (preferred — callers use `workspaceManager.addToMergeQueue` / etc. directly).
|
|
179
|
+
- Remove `macro_` table prefix handling if present.
|
|
180
|
+
|
|
181
|
+
**Acceptance**: no references to the old MergeQueue class; `self-driving` still works; merge queue persistence now uses git-cascade's schema.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Phase 7 — ConflictRecoveryStrategy infrastructure
|
|
186
|
+
|
|
187
|
+
**Scope**: implement `ConflictRecoveryStrategy` per `docs/conflict-recovery.md`. Register the 5 built-ins. Wire dispatch into `done()` flow.
|
|
188
|
+
|
|
189
|
+
**Subtasks**:
|
|
190
|
+
- Create `src/workspace/recovery/types.ts` with `ConflictRecoveryStrategy`, `ConflictContext`, `ConflictResolution`.
|
|
191
|
+
- Implement built-ins:
|
|
192
|
+
- `src/workspace/recovery/auto-resolve.ts`
|
|
193
|
+
- `src/workspace/recovery/defer.ts`
|
|
194
|
+
- `src/workspace/recovery/spawn-resolver.ts`
|
|
195
|
+
- `src/workspace/recovery/abandon.ts`
|
|
196
|
+
- `src/workspace/recovery/escalate.ts`
|
|
197
|
+
- Add `registerConflictRecoveryStrategy` / `recoverConflict` to WorkspaceManager.
|
|
198
|
+
- Wire into AgentManagerV2's `done()` flow: on `LandingResult.success = false && conflictId`, look up role config `on_conflict` (or team default), call `recoverConflict`.
|
|
199
|
+
- Add MCP tools: `resolve_conflict`, `list_conflicts`, `get_conflict` (gated by `workspace.resolve` capability).
|
|
200
|
+
- Per-stream recovery lock to serialize concurrent recoveries.
|
|
201
|
+
- `max_recovery_depth` enforcement with fallback to `escalate`.
|
|
202
|
+
|
|
203
|
+
**Acceptance**: `defer` works end-to-end (conflict creates record, stream marked conflicted, no crash); `spawn-resolver` spawns a resolver agent successfully in a test; `auto-resolve` with `ours` strategy works for synthetic merge conflicts.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Phase 8 — Remove role-name fallback from AgentManagerV2
|
|
208
|
+
|
|
209
|
+
**Scope**: clean up the transitional `switch(role)` path left in Phase 4.
|
|
210
|
+
|
|
211
|
+
**Subtasks**:
|
|
212
|
+
- Remove `createWorkspaceForRole` role-name `switch` from `agent-manager-v2.ts:281-306`.
|
|
213
|
+
- Require `topologyPolicy` be set before any spawn with workspace-needing roles (throw on missing).
|
|
214
|
+
- Default to `NoWorkspaceTopology` when no YAML config — spawns all agents with `share-parent-cwd`.
|
|
215
|
+
- Update any tests that relied on role-name dispatch to provide an explicit policy.
|
|
216
|
+
|
|
217
|
+
**Acceptance**: grep for `case "coordinator"` / `case "worker"` / `case "integrator"` in `agent-manager-v2.ts` returns nothing; tests green.
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Phase 9 — Deprecate & remove old WorkspaceManager methods
|
|
222
|
+
|
|
223
|
+
**Scope**: clean up the old `createWorkerWorkspace` / `createIntegratorWorkspace` / `createCoordinatorWorkspace` / `getMergeQueue` / old-signature `deallocateWorkspace`.
|
|
224
|
+
|
|
225
|
+
**Subtasks**:
|
|
226
|
+
- Migrate any remaining callers to new methods.
|
|
227
|
+
- Remove the old methods from interface + implementation.
|
|
228
|
+
- Update tests.
|
|
229
|
+
- Old merge-queue-based `IntegrationStrategy` interface removed (was dead code, per Phase 5).
|
|
230
|
+
|
|
231
|
+
**Acceptance**: `WorkspaceManager` interface matches `workspace-interfaces.md` §5 exactly; no legacy methods.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Phase 10 — Boot ergonomics: `macro-agent run <team>` CLI
|
|
236
|
+
|
|
237
|
+
**Scope**: first-class CLI for running a team with one command.
|
|
238
|
+
|
|
239
|
+
**Subtasks**:
|
|
240
|
+
- Add `src/cli/run.ts` — loads team YAML, constructs system, starts team, prompts root agent.
|
|
241
|
+
- Wire into `src/cli/index.ts` as `run <team>` command.
|
|
242
|
+
- `bootV2` accepts `config.team: string | TeamManifest` — auto-wires WorkspaceManager + TopologyPolicy + TeamManager.
|
|
243
|
+
- Add `config.task?: string` — if set, prompts root agent with this task after start.
|
|
244
|
+
- Streaming output (re-use existing ACP prompt streaming).
|
|
245
|
+
|
|
246
|
+
**Acceptance**: `macro-agent run self-driving --task "x"` starts the team, prompts root, streams output; Ctrl-C cleans up.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Risks & mitigations
|
|
251
|
+
|
|
252
|
+
| Risk | Mitigation |
|
|
253
|
+
|---|---|
|
|
254
|
+
| `self-driving` team regresses during migration | Run its e2e test after each phase; gate each PR on it |
|
|
255
|
+
| Raw `git checkout` for pool reuse bypasses git-cascade op log | Accepted; tracked as follow-up to petition git-cascade for public `updateWorktreeStream` |
|
|
256
|
+
| Git-cascade semantics differ from assumptions | Phase 0 unit tests verify each primitive against temp-repo fixture before wiring upward |
|
|
257
|
+
| Conflict recovery infinite loops | `max_recovery_depth` cap; `max_concurrent` per strategy; per-stream lock |
|
|
258
|
+
| Event storm from `x-cascade/*` subscription | Coalescing in WakeManager integration (Phase 3 `onParentStreamAdvanced`) |
|
|
259
|
+
| Reviewer-outlives-coder shared worktree deallocation | Ref-count implemented in Phase 1 `allocateWorktree` with `sharedWithAgent` opt |
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Known upstream gaps (git-cascade 0.0.1)
|
|
264
|
+
|
|
265
|
+
The published `git-cascade@0.0.1` dependency (pinned in `package.json`) lacks features present in the source at `references/git-cascade/`:
|
|
266
|
+
|
|
267
|
+
- **No `emit` callback** on `TrackerOptions` — event subscription to `x-cascade/*` not possible via the published API. Phase 0 falls back to local wrapper-level emits; Phase 5 (LandingStrategy) and Phase 3 (`on_parent_advanced`) will need this for event-driven sync.
|
|
268
|
+
- **`cascade.cascadeRebase` not namespace-exported** from `git-cascade` root. The cascade module exists as `dist/cascade.js` but isn't reachable (no subpath exports, no `export * as cascade` in index). Blocks Phase 5's `merge-to-parent` strategy's cascade step and Phase 3's auto-cascade.
|
|
269
|
+
|
|
270
|
+
**Resolution options:**
|
|
271
|
+
- (a) Publish a new `git-cascade` version that exposes these. Preferred.
|
|
272
|
+
- (b) Pin `git-cascade` to a local file path (`file:./references/git-cascade/`) temporarily until (a).
|
|
273
|
+
- (c) Workaround: for `cascadeRebase`, walk dependents manually via `getDependents()` + `syncWithParent()` in a loop. Less efficient, no atomic transactions.
|
|
274
|
+
|
|
275
|
+
Tracking as a P0 dependency issue; needs to land before Phase 5.
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Open items to resolve during implementation
|
|
280
|
+
|
|
281
|
+
From `docs/workspace-interfaces.md` §11:
|
|
282
|
+
- [ ] **`checkout_stream` semantics** — decide in Phase 5 when we implement `fork_stream` MCP tool: relocate cwd vs re-allocate worktree. Likely: fresh worktree per stream, update `MACRO_STREAM_ID` in agent env on checkout.
|
|
283
|
+
- [ ] **`attach-to-stream` worktree** — decide in Phase 3. Proposal: always fresh worktree on target branch.
|
|
284
|
+
- [ ] **Conflict recovery retry ownership** — decide in Phase 7. Proposal: caller subscribes to `conflict.resolved` event; recovery strategy does not auto-retry.
|
|
285
|
+
- [ ] **Cascade event triggering** — decide in Phase 3. Proposal: `YamlDrivenTopology.onTeamStart` subscribes to `x-cascade/stream.committed` per relevant stream; WakeManager coalesces.
|
|
286
|
+
|
|
287
|
+
From `docs/conflict-recovery.md` §11:
|
|
288
|
+
- [ ] **Cross-team conflicts** — decide in Phase 7. Proposal: owning team's policy; default escalate for federation.
|
|
289
|
+
- [ ] **Recovery observability** — decide in Phase 7. Proposal: optional `RECOVERY_PROGRESS` signal on team channel.
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## Tracking
|
|
294
|
+
|
|
295
|
+
Per-phase progress tracked via TaskCreate/TaskUpdate. Each phase is one or more tasks; each task has acceptance criteria.
|
|
296
|
+
|
|
297
|
+
Milestone tags:
|
|
298
|
+
- `v3.0.0-alpha1` — Phases 0-2 complete (infrastructure additive, no behavior change)
|
|
299
|
+
- `v3.0.0-alpha2` — Phases 3-6 complete (TopologyPolicy + LandingStrategy in place, duplicate queue gone)
|
|
300
|
+
- `v3.0.0-alpha3` — Phase 7 complete (conflict recovery working)
|
|
301
|
+
- `v3.0.0-beta1` — Phases 8-9 complete (legacy removed)
|
|
302
|
+
- `v3.0.0` — Phase 10 complete (CLI ergonomics)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "macro-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Interact with multiple agents as if they were a single agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"chalk": "^5.6.2",
|
|
58
58
|
"commander": "^14.0.2",
|
|
59
59
|
"express": "^5.2.1",
|
|
60
|
-
"git-cascade": "0.0.
|
|
60
|
+
"git-cascade": "^0.0.7",
|
|
61
61
|
"js-yaml": "^4.1.1",
|
|
62
62
|
"nanoid": "^5.0.0",
|
|
63
63
|
"opentasks": "^0.0.3",
|
|
@@ -67,10 +67,10 @@
|
|
|
67
67
|
"zod": "^4.2.1"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
|
+
"agentic-mesh": "*",
|
|
70
71
|
"minimem": "*",
|
|
71
|
-
"skill-tree": "*",
|
|
72
72
|
"sessionlog": "*",
|
|
73
|
-
"
|
|
73
|
+
"skill-tree": "*"
|
|
74
74
|
},
|
|
75
75
|
"peerDependenciesMeta": {
|
|
76
76
|
"minimem": {
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* on_parent_advanced: sync_with_parent auto-sync e2e.
|
|
3
|
+
*
|
|
4
|
+
* Scenario: a role with `on_parent_advanced: sync_with_parent` has its
|
|
5
|
+
* stream automatically rebased onto its parent when the parent advances.
|
|
6
|
+
*
|
|
7
|
+
* Fixture:
|
|
8
|
+
* team_root (parent of both)
|
|
9
|
+
* ├── feature_owner stream (declares on_parent_advanced: sync_with_parent)
|
|
10
|
+
* └── (we simulate the parent advancing via commitChanges on team_root
|
|
11
|
+
* from a separate agent)
|
|
12
|
+
*
|
|
13
|
+
* Verifies:
|
|
14
|
+
* - Topology subscribes to stream:committed events on onTeamStart
|
|
15
|
+
* - syncWithParent is invoked on the feature_owner's stream when team_root
|
|
16
|
+
* gets a new commit
|
|
17
|
+
* - Coalescing: back-to-back commits within the debounce window produce
|
|
18
|
+
* only one sync call
|
|
19
|
+
*
|
|
20
|
+
* REQUIRES: RUN_E2E_TESTS=true
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
24
|
+
import * as fs from 'fs';
|
|
25
|
+
import * as path from 'path';
|
|
26
|
+
import * as os from 'os';
|
|
27
|
+
import { execSync } from 'child_process';
|
|
28
|
+
import { GitCascadeAdapter, createGitCascadeAdapter } from '../../workspace/git-cascade-adapter.js';
|
|
29
|
+
import {
|
|
30
|
+
DefaultWorkspaceManager,
|
|
31
|
+
createWorkspaceManagerWithAdapter,
|
|
32
|
+
} from '../../workspace/workspace-manager.js';
|
|
33
|
+
import { YamlDrivenTopology } from '../../workspace/topology/yaml-driven.js';
|
|
34
|
+
import { parseTeamWorkspaceConfig } from '../../workspace/yaml-schema.js';
|
|
35
|
+
|
|
36
|
+
const RUN_E2E = !!process.env.RUN_E2E_TESTS;
|
|
37
|
+
const describeFn = RUN_E2E ? describe : describe.skip;
|
|
38
|
+
|
|
39
|
+
describeFn('on_parent_advanced auto-sync', () => {
|
|
40
|
+
let tempDir: string;
|
|
41
|
+
let repoPath: string;
|
|
42
|
+
let adapter: GitCascadeAdapter;
|
|
43
|
+
let manager: DefaultWorkspaceManager;
|
|
44
|
+
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'auto-sync-'));
|
|
47
|
+
repoPath = path.join(tempDir, 'repo');
|
|
48
|
+
fs.mkdirSync(repoPath);
|
|
49
|
+
|
|
50
|
+
execSync('git init -b main', { cwd: repoPath, stdio: 'pipe' });
|
|
51
|
+
execSync('git config user.email "t@t.com"', { cwd: repoPath, stdio: 'pipe' });
|
|
52
|
+
execSync('git config user.name "T"', { cwd: repoPath, stdio: 'pipe' });
|
|
53
|
+
fs.writeFileSync(path.join(repoPath, 'README.md'), '# test\n');
|
|
54
|
+
execSync('git add .', { cwd: repoPath, stdio: 'pipe' });
|
|
55
|
+
execSync('git commit -m "init"', { cwd: repoPath, stdio: 'pipe' });
|
|
56
|
+
|
|
57
|
+
adapter = createGitCascadeAdapter({
|
|
58
|
+
enabled: true,
|
|
59
|
+
repoPath,
|
|
60
|
+
dbPath: path.join(tempDir, 'gc.db'),
|
|
61
|
+
skipRecovery: true,
|
|
62
|
+
});
|
|
63
|
+
manager = createWorkspaceManagerWithAdapter(adapter, {
|
|
64
|
+
worktreeBaseDir: path.join(tempDir, 'worktrees'),
|
|
65
|
+
}) as DefaultWorkspaceManager;
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
afterEach(() => {
|
|
69
|
+
manager.close();
|
|
70
|
+
adapter.close();
|
|
71
|
+
if (fs.existsSync(tempDir)) fs.rmSync(tempDir, { recursive: true, force: true });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('subscribes to stream:committed and syncs affected children', async () => {
|
|
75
|
+
const config = parseTeamWorkspaceConfig({
|
|
76
|
+
roles: {
|
|
77
|
+
feature_owner: {
|
|
78
|
+
workspace: 'new_stream',
|
|
79
|
+
stream_lineage: 'fork_from_team_root',
|
|
80
|
+
landing: 'merge_to_parent_stream',
|
|
81
|
+
on_parent_advanced: 'sync_with_parent',
|
|
82
|
+
on_conflict: 'ours',
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
const topology = new YamlDrivenTopology(config!);
|
|
87
|
+
const startCtx = {
|
|
88
|
+
teamName: 'sync-test',
|
|
89
|
+
teamInstanceId: 'sync-1',
|
|
90
|
+
workspaceConfig: config,
|
|
91
|
+
workspaceManager: manager,
|
|
92
|
+
};
|
|
93
|
+
await topology.onTeamStart(startCtx);
|
|
94
|
+
|
|
95
|
+
const teamStream = manager
|
|
96
|
+
.listStreams()
|
|
97
|
+
.find((s) => s.agentId === 'team:sync-test');
|
|
98
|
+
expect(teamStream).toBeDefined();
|
|
99
|
+
|
|
100
|
+
// Spawn a feature_owner and allocate its workspace
|
|
101
|
+
const decision = await topology.onAgentSpawn({
|
|
102
|
+
agentId: 'agent-feat',
|
|
103
|
+
role: 'feature_owner',
|
|
104
|
+
workspaceManager: manager,
|
|
105
|
+
});
|
|
106
|
+
expect(decision.kind).toBe('new-stream');
|
|
107
|
+
let featStreamId: string | null = null;
|
|
108
|
+
if (decision.kind === 'new-stream') {
|
|
109
|
+
featStreamId = manager.createStreamV3(decision.streamSpec);
|
|
110
|
+
manager.allocateWorktree({
|
|
111
|
+
agentId: 'agent-feat',
|
|
112
|
+
streamId: featStreamId,
|
|
113
|
+
});
|
|
114
|
+
topology.recordAgentStream('agent-feat', featStreamId, 'feature_owner');
|
|
115
|
+
}
|
|
116
|
+
expect(featStreamId).not.toBeNull();
|
|
117
|
+
|
|
118
|
+
// Spy on syncWithParent to detect auto-sync calls
|
|
119
|
+
const syncSpy = vi.spyOn(manager, 'syncWithParent');
|
|
120
|
+
|
|
121
|
+
// Simulate team_root advancing: commit something via a separate agent
|
|
122
|
+
// on the team_root stream.
|
|
123
|
+
manager.allocateWorktree({
|
|
124
|
+
agentId: 'agent-on-root',
|
|
125
|
+
streamId: teamStream!.id,
|
|
126
|
+
});
|
|
127
|
+
const rootWorktree = manager.getWorktreeForAgent('agent-on-root')!;
|
|
128
|
+
fs.writeFileSync(path.join(rootWorktree.path, 'advance.txt'), 'new content\n');
|
|
129
|
+
manager.commitChanges({
|
|
130
|
+
agentId: 'agent-on-root',
|
|
131
|
+
streamId: teamStream!.id,
|
|
132
|
+
worktree: rootWorktree.path,
|
|
133
|
+
message: 'advance team root',
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Allow event handler to fire (it's sync but dispatch is async)
|
|
137
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
138
|
+
|
|
139
|
+
expect(syncSpy).toHaveBeenCalled();
|
|
140
|
+
const call = syncSpy.mock.calls[0]?.[0];
|
|
141
|
+
expect(call?.streamId).toBe(featStreamId);
|
|
142
|
+
expect(call?.agentId).toBe('agent-feat');
|
|
143
|
+
expect(call?.onConflict).toBe('ours');
|
|
144
|
+
|
|
145
|
+
await topology.onTeamStop({
|
|
146
|
+
teamName: 'sync-test',
|
|
147
|
+
teamInstanceId: 'sync-1',
|
|
148
|
+
teamStreamId: teamStream!.id,
|
|
149
|
+
workspaceManager: manager,
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('coalesces back-to-back commits within debounce window', async () => {
|
|
154
|
+
const config = parseTeamWorkspaceConfig({
|
|
155
|
+
roles: {
|
|
156
|
+
feature_owner: {
|
|
157
|
+
workspace: 'new_stream',
|
|
158
|
+
stream_lineage: 'fork_from_team_root',
|
|
159
|
+
on_parent_advanced: 'sync_with_parent',
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
const topology = new YamlDrivenTopology(config!);
|
|
164
|
+
await topology.onTeamStart({
|
|
165
|
+
teamName: 'coalesce-test',
|
|
166
|
+
teamInstanceId: 'c-1',
|
|
167
|
+
workspaceConfig: config,
|
|
168
|
+
workspaceManager: manager,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const teamStream = manager
|
|
172
|
+
.listStreams()
|
|
173
|
+
.find((s) => s.agentId === 'team:coalesce-test');
|
|
174
|
+
expect(teamStream).toBeDefined();
|
|
175
|
+
|
|
176
|
+
const decision = await topology.onAgentSpawn({
|
|
177
|
+
agentId: 'agent-feat',
|
|
178
|
+
role: 'feature_owner',
|
|
179
|
+
workspaceManager: manager,
|
|
180
|
+
});
|
|
181
|
+
let featStreamId: string | null = null;
|
|
182
|
+
if (decision.kind === 'new-stream') {
|
|
183
|
+
featStreamId = manager.createStreamV3(decision.streamSpec);
|
|
184
|
+
manager.allocateWorktree({
|
|
185
|
+
agentId: 'agent-feat',
|
|
186
|
+
streamId: featStreamId,
|
|
187
|
+
});
|
|
188
|
+
topology.recordAgentStream('agent-feat', featStreamId, 'feature_owner');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const syncSpy = vi.spyOn(manager, 'syncWithParent');
|
|
192
|
+
|
|
193
|
+
manager.allocateWorktree({
|
|
194
|
+
agentId: 'agent-root',
|
|
195
|
+
streamId: teamStream!.id,
|
|
196
|
+
});
|
|
197
|
+
const rootWorktree = manager.getWorktreeForAgent('agent-root')!;
|
|
198
|
+
|
|
199
|
+
// 3 back-to-back commits — should coalesce to 1 sync
|
|
200
|
+
for (let i = 0; i < 3; i++) {
|
|
201
|
+
fs.writeFileSync(
|
|
202
|
+
path.join(rootWorktree.path, `c${i}.txt`),
|
|
203
|
+
`content ${i}\n`
|
|
204
|
+
);
|
|
205
|
+
manager.commitChanges({
|
|
206
|
+
agentId: 'agent-root',
|
|
207
|
+
streamId: teamStream!.id,
|
|
208
|
+
worktree: rootWorktree.path,
|
|
209
|
+
message: `commit ${i}`,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
214
|
+
expect(syncSpy.mock.calls.length).toBe(1);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
it('does not subscribe when no role declares on_parent_advanced', async () => {
|
|
218
|
+
const config = parseTeamWorkspaceConfig({
|
|
219
|
+
roles: {
|
|
220
|
+
plain_role: {
|
|
221
|
+
workspace: 'new_stream',
|
|
222
|
+
stream_lineage: 'fork_from_team_root',
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
const topology = new YamlDrivenTopology(config!);
|
|
227
|
+
await topology.onTeamStart({
|
|
228
|
+
teamName: 'no-autosync',
|
|
229
|
+
teamInstanceId: 'n-1',
|
|
230
|
+
workspaceConfig: config,
|
|
231
|
+
workspaceManager: manager,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const teamStream = manager
|
|
235
|
+
.listStreams()
|
|
236
|
+
.find((s) => s.agentId === 'team:no-autosync');
|
|
237
|
+
|
|
238
|
+
const syncSpy = vi.spyOn(manager, 'syncWithParent');
|
|
239
|
+
|
|
240
|
+
// Even if we commit on team_root, no sync should fire
|
|
241
|
+
manager.allocateWorktree({
|
|
242
|
+
agentId: 'agent-x',
|
|
243
|
+
streamId: teamStream!.id,
|
|
244
|
+
});
|
|
245
|
+
const wt = manager.getWorktreeForAgent('agent-x')!;
|
|
246
|
+
fs.writeFileSync(path.join(wt.path, 'x.txt'), 'x\n');
|
|
247
|
+
manager.commitChanges({
|
|
248
|
+
agentId: 'agent-x',
|
|
249
|
+
streamId: teamStream!.id,
|
|
250
|
+
worktree: wt.path,
|
|
251
|
+
message: 'x',
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
255
|
+
expect(syncSpy).not.toHaveBeenCalled();
|
|
256
|
+
});
|
|
257
|
+
});
|