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.
Files changed (306) hide show
  1. package/CLAUDE.md +263 -33
  2. package/README.md +781 -131
  3. package/dist/acp/claude-code-replay.d.ts +11 -0
  4. package/dist/acp/claude-code-replay.d.ts.map +1 -0
  5. package/dist/acp/claude-code-replay.js +190 -0
  6. package/dist/acp/claude-code-replay.js.map +1 -0
  7. package/dist/acp/macro-agent.d.ts.map +1 -1
  8. package/dist/acp/macro-agent.js +192 -7
  9. package/dist/acp/macro-agent.js.map +1 -1
  10. package/dist/acp/types.d.ts +9 -0
  11. package/dist/acp/types.d.ts.map +1 -1
  12. package/dist/acp/types.js.map +1 -1
  13. package/dist/adapters/tasks-adapter.d.ts.map +1 -1
  14. package/dist/adapters/tasks-adapter.js +3 -0
  15. package/dist/adapters/tasks-adapter.js.map +1 -1
  16. package/dist/adapters/types.d.ts +1 -0
  17. package/dist/adapters/types.d.ts.map +1 -1
  18. package/dist/agent/agent-manager-v2.d.ts +21 -0
  19. package/dist/agent/agent-manager-v2.d.ts.map +1 -1
  20. package/dist/agent/agent-manager-v2.js +308 -54
  21. package/dist/agent/agent-manager-v2.js.map +1 -1
  22. package/dist/agent/agent-manager.d.ts +12 -0
  23. package/dist/agent/agent-manager.d.ts.map +1 -1
  24. package/dist/agent/agent-manager.js.map +1 -1
  25. package/dist/agent/agent-store.d.ts +10 -0
  26. package/dist/agent/agent-store.d.ts.map +1 -1
  27. package/dist/agent/agent-store.js +22 -0
  28. package/dist/agent/agent-store.js.map +1 -1
  29. package/dist/agent/types.d.ts +15 -2
  30. package/dist/agent/types.d.ts.map +1 -1
  31. package/dist/agent/types.js.map +1 -1
  32. package/dist/boot-v2.d.ts +129 -1
  33. package/dist/boot-v2.d.ts.map +1 -1
  34. package/dist/boot-v2.js +359 -8
  35. package/dist/boot-v2.js.map +1 -1
  36. package/dist/cli/acp.js +4 -0
  37. package/dist/cli/acp.js.map +1 -1
  38. package/dist/cli/index.js +56 -0
  39. package/dist/cli/index.js.map +1 -1
  40. package/dist/cognitive/macro-agent-backend.d.ts.map +1 -1
  41. package/dist/cognitive/macro-agent-backend.js +40 -22
  42. package/dist/cognitive/macro-agent-backend.js.map +1 -1
  43. package/dist/integrations/skilltree.d.ts.map +1 -1
  44. package/dist/integrations/skilltree.js +1 -0
  45. package/dist/integrations/skilltree.js.map +1 -1
  46. package/dist/lifecycle/cascade.d.ts +25 -2
  47. package/dist/lifecycle/cascade.d.ts.map +1 -1
  48. package/dist/lifecycle/cascade.js +70 -2
  49. package/dist/lifecycle/cascade.js.map +1 -1
  50. package/dist/lifecycle/cleanup.d.ts +33 -2
  51. package/dist/lifecycle/cleanup.d.ts.map +1 -1
  52. package/dist/lifecycle/cleanup.js +28 -6
  53. package/dist/lifecycle/cleanup.js.map +1 -1
  54. package/dist/lifecycle/handlers-v2.d.ts +7 -0
  55. package/dist/lifecycle/handlers-v2.d.ts.map +1 -1
  56. package/dist/lifecycle/handlers-v2.js +28 -2
  57. package/dist/lifecycle/handlers-v2.js.map +1 -1
  58. package/dist/lifecycle/types.d.ts +11 -0
  59. package/dist/lifecycle/types.d.ts.map +1 -1
  60. package/dist/lifecycle/types.js.map +1 -1
  61. package/dist/map/acp-bridge.d.ts +9 -0
  62. package/dist/map/acp-bridge.d.ts.map +1 -1
  63. package/dist/map/acp-bridge.js +15 -2
  64. package/dist/map/acp-bridge.js.map +1 -1
  65. package/dist/map/cascade-action-handler.d.ts +24 -0
  66. package/dist/map/cascade-action-handler.d.ts.map +1 -0
  67. package/dist/map/cascade-action-handler.js +170 -0
  68. package/dist/map/cascade-action-handler.js.map +1 -0
  69. package/dist/map/cascade-bridge.d.ts +44 -0
  70. package/dist/map/cascade-bridge.d.ts.map +1 -0
  71. package/dist/map/cascade-bridge.js +294 -0
  72. package/dist/map/cascade-bridge.js.map +1 -0
  73. package/dist/map/coordination-handler.d.ts.map +1 -1
  74. package/dist/map/coordination-handler.js +12 -1
  75. package/dist/map/coordination-handler.js.map +1 -1
  76. package/dist/map/lifecycle-bridge.d.ts +1 -1
  77. package/dist/map/lifecycle-bridge.d.ts.map +1 -1
  78. package/dist/map/lifecycle-bridge.js +58 -23
  79. package/dist/map/lifecycle-bridge.js.map +1 -1
  80. package/dist/map/server.d.ts.map +1 -1
  81. package/dist/map/server.js +219 -7
  82. package/dist/map/server.js.map +1 -1
  83. package/dist/map/sidecar.d.ts.map +1 -1
  84. package/dist/map/sidecar.js +49 -2
  85. package/dist/map/sidecar.js.map +1 -1
  86. package/dist/map/types.d.ts +22 -0
  87. package/dist/map/types.d.ts.map +1 -1
  88. package/dist/mcp/tools/done-v2.d.ts.map +1 -1
  89. package/dist/mcp/tools/done-v2.js +8 -0
  90. package/dist/mcp/tools/done-v2.js.map +1 -1
  91. package/dist/teams/team-manager-v2.d.ts.map +1 -1
  92. package/dist/teams/team-manager-v2.js +26 -0
  93. package/dist/teams/team-manager-v2.js.map +1 -1
  94. package/dist/teams/team-runtime-v2.d.ts.map +1 -1
  95. package/dist/teams/team-runtime-v2.js +16 -3
  96. package/dist/teams/team-runtime-v2.js.map +1 -1
  97. package/dist/workspace/config.d.ts +10 -10
  98. package/dist/workspace/config.d.ts.map +1 -1
  99. package/dist/workspace/config.js +4 -4
  100. package/dist/workspace/config.js.map +1 -1
  101. package/dist/workspace/git-cascade-adapter.d.ts +510 -0
  102. package/dist/workspace/git-cascade-adapter.d.ts.map +1 -0
  103. package/dist/workspace/git-cascade-adapter.js +934 -0
  104. package/dist/workspace/git-cascade-adapter.js.map +1 -0
  105. package/dist/workspace/index.d.ts +3 -3
  106. package/dist/workspace/index.d.ts.map +1 -1
  107. package/dist/workspace/index.js +4 -4
  108. package/dist/workspace/index.js.map +1 -1
  109. package/dist/workspace/landing/direct-push.d.ts +20 -0
  110. package/dist/workspace/landing/direct-push.d.ts.map +1 -0
  111. package/dist/workspace/landing/direct-push.js +74 -0
  112. package/dist/workspace/landing/direct-push.js.map +1 -0
  113. package/dist/workspace/landing/index.d.ts +29 -0
  114. package/dist/workspace/landing/index.d.ts.map +1 -0
  115. package/dist/workspace/landing/index.js +37 -0
  116. package/dist/workspace/landing/index.js.map +1 -0
  117. package/dist/workspace/landing/merge-to-parent.d.ts +41 -0
  118. package/dist/workspace/landing/merge-to-parent.d.ts.map +1 -0
  119. package/dist/workspace/landing/merge-to-parent.js +186 -0
  120. package/dist/workspace/landing/merge-to-parent.js.map +1 -0
  121. package/dist/workspace/landing/optimistic-push.d.ts +16 -0
  122. package/dist/workspace/landing/optimistic-push.d.ts.map +1 -0
  123. package/dist/workspace/landing/optimistic-push.js +27 -0
  124. package/dist/workspace/landing/optimistic-push.js.map +1 -0
  125. package/dist/workspace/landing/queue-to-branch.d.ts +24 -0
  126. package/dist/workspace/landing/queue-to-branch.d.ts.map +1 -0
  127. package/dist/workspace/landing/queue-to-branch.js +79 -0
  128. package/dist/workspace/landing/queue-to-branch.js.map +1 -0
  129. package/dist/workspace/merge-queue/merge-queue.d.ts +10 -0
  130. package/dist/workspace/merge-queue/merge-queue.d.ts.map +1 -1
  131. package/dist/workspace/merge-queue/merge-queue.js +10 -0
  132. package/dist/workspace/merge-queue/merge-queue.js.map +1 -1
  133. package/dist/workspace/merge-queue/types.d.ts +16 -2
  134. package/dist/workspace/merge-queue/types.d.ts.map +1 -1
  135. package/dist/workspace/merge-queue/types.js +9 -0
  136. package/dist/workspace/merge-queue/types.js.map +1 -1
  137. package/dist/workspace/pool/types.d.ts +1 -0
  138. package/dist/workspace/pool/types.d.ts.map +1 -1
  139. package/dist/workspace/pool/worktree-pool.d.ts.map +1 -1
  140. package/dist/workspace/pool/worktree-pool.js +1 -0
  141. package/dist/workspace/pool/worktree-pool.js.map +1 -1
  142. package/dist/workspace/recovery/abandon.d.ts +15 -0
  143. package/dist/workspace/recovery/abandon.d.ts.map +1 -0
  144. package/dist/workspace/recovery/abandon.js +45 -0
  145. package/dist/workspace/recovery/abandon.js.map +1 -0
  146. package/dist/workspace/recovery/auto-resolve.d.ts +27 -0
  147. package/dist/workspace/recovery/auto-resolve.d.ts.map +1 -0
  148. package/dist/workspace/recovery/auto-resolve.js +99 -0
  149. package/dist/workspace/recovery/auto-resolve.js.map +1 -0
  150. package/dist/workspace/recovery/defer.d.ts +15 -0
  151. package/dist/workspace/recovery/defer.d.ts.map +1 -0
  152. package/dist/workspace/recovery/defer.js +16 -0
  153. package/dist/workspace/recovery/defer.js.map +1 -0
  154. package/dist/workspace/recovery/escalate.d.ts +16 -0
  155. package/dist/workspace/recovery/escalate.d.ts.map +1 -0
  156. package/dist/workspace/recovery/escalate.js +24 -0
  157. package/dist/workspace/recovery/escalate.js.map +1 -0
  158. package/dist/workspace/recovery/index.d.ts +32 -0
  159. package/dist/workspace/recovery/index.d.ts.map +1 -0
  160. package/dist/workspace/recovery/index.js +45 -0
  161. package/dist/workspace/recovery/index.js.map +1 -0
  162. package/dist/workspace/recovery/spawn-resolver.d.ts +45 -0
  163. package/dist/workspace/recovery/spawn-resolver.d.ts.map +1 -0
  164. package/dist/workspace/recovery/spawn-resolver.js +118 -0
  165. package/dist/workspace/recovery/spawn-resolver.js.map +1 -0
  166. package/dist/workspace/recovery/types.d.ts +63 -0
  167. package/dist/workspace/recovery/types.d.ts.map +1 -0
  168. package/dist/workspace/recovery/types.js +12 -0
  169. package/dist/workspace/recovery/types.js.map +1 -0
  170. package/dist/workspace/topology/index.d.ts +9 -0
  171. package/dist/workspace/topology/index.d.ts.map +1 -0
  172. package/dist/workspace/topology/index.js +8 -0
  173. package/dist/workspace/topology/index.js.map +1 -0
  174. package/dist/workspace/topology/no-workspace.d.ts +18 -0
  175. package/dist/workspace/topology/no-workspace.d.ts.map +1 -0
  176. package/dist/workspace/topology/no-workspace.js +25 -0
  177. package/dist/workspace/topology/no-workspace.js.map +1 -0
  178. package/dist/workspace/topology/types.d.ts +97 -0
  179. package/dist/workspace/topology/types.d.ts.map +1 -0
  180. package/dist/workspace/topology/types.js +20 -0
  181. package/dist/workspace/topology/types.js.map +1 -0
  182. package/dist/workspace/topology/yaml-driven.d.ts +69 -0
  183. package/dist/workspace/topology/yaml-driven.d.ts.map +1 -0
  184. package/dist/workspace/topology/yaml-driven.js +273 -0
  185. package/dist/workspace/topology/yaml-driven.js.map +1 -0
  186. package/dist/workspace/types-v3.d.ts +117 -0
  187. package/dist/workspace/types-v3.d.ts.map +1 -0
  188. package/dist/workspace/types-v3.js +20 -0
  189. package/dist/workspace/types-v3.js.map +1 -0
  190. package/dist/workspace/types.d.ts +162 -17
  191. package/dist/workspace/types.d.ts.map +1 -1
  192. package/dist/workspace/workspace-manager.d.ts +101 -13
  193. package/dist/workspace/workspace-manager.d.ts.map +1 -1
  194. package/dist/workspace/workspace-manager.js +416 -13
  195. package/dist/workspace/workspace-manager.js.map +1 -1
  196. package/dist/workspace/yaml-schema.d.ts +254 -0
  197. package/dist/workspace/yaml-schema.d.ts.map +1 -0
  198. package/dist/workspace/yaml-schema.js +170 -0
  199. package/dist/workspace/yaml-schema.js.map +1 -0
  200. package/docs/conflict-recovery.md +472 -0
  201. package/docs/design/task-dispatcher.md +880 -0
  202. package/docs/git-cascade-integration-gaps.md +678 -0
  203. package/docs/workspace-interfaces.md +731 -0
  204. package/docs/workspace-redesign-plan.md +302 -0
  205. package/package.json +6 -5
  206. package/src/__tests__/boot-v2.test.ts +435 -0
  207. package/src/__tests__/e2e/acp-over-map.e2e.test.ts +92 -0
  208. package/src/__tests__/e2e/auto-sync.e2e.test.ts +257 -0
  209. package/src/__tests__/e2e/bootstrap.e2e.test.ts +319 -0
  210. package/src/__tests__/e2e/cascade-rebase.e2e.test.ts +254 -0
  211. package/src/__tests__/e2e/cli-run.e2e.test.ts +167 -0
  212. package/src/__tests__/e2e/dispatch-coordination.e2e.test.ts +495 -0
  213. package/src/__tests__/e2e/dispatch-live.e2e.test.ts +564 -0
  214. package/src/__tests__/e2e/dispatch-opentasks.e2e.test.ts +496 -0
  215. package/src/__tests__/e2e/dispatch-phase2-live.e2e.test.ts +456 -0
  216. package/src/__tests__/e2e/dispatch-phase2.e2e.test.ts +386 -0
  217. package/src/__tests__/e2e/dispatch.e2e.test.ts +376 -0
  218. package/src/__tests__/e2e/self-driving-v3.e2e.test.ts +197 -0
  219. package/src/__tests__/e2e/spawn-resolver.e2e.test.ts +200 -0
  220. package/src/__tests__/e2e/workspace-lifecycle.e2e.test.ts +30 -22
  221. package/src/__tests__/e2e/workspace-v3.e2e.test.ts +413 -0
  222. package/src/acp/__tests__/claude-code-replay.test.ts +225 -0
  223. package/src/acp/__tests__/macro-agent.test.ts +39 -1
  224. package/src/acp/claude-code-replay.ts +208 -0
  225. package/src/acp/macro-agent.ts +203 -10
  226. package/src/acp/types.ts +10 -0
  227. package/src/adapters/__tests__/tasks-adapter.test.ts +1 -0
  228. package/src/adapters/tasks-adapter.ts +3 -0
  229. package/src/adapters/types.ts +1 -0
  230. package/src/agent/__tests__/agent-manager-topology.test.ts +73 -0
  231. package/src/agent/__tests__/agent-manager-v2.test.ts +66 -0
  232. package/src/agent/__tests__/agent-store.test.ts +52 -0
  233. package/src/agent/__tests__/task-ref-resolution.test.ts +231 -0
  234. package/src/agent/agent-manager-v2.ts +372 -59
  235. package/src/agent/agent-manager.ts +14 -0
  236. package/src/agent/agent-store.ts +24 -0
  237. package/src/agent/types.ts +16 -2
  238. package/src/boot-v2.ts +589 -35
  239. package/src/cli/acp.ts +4 -0
  240. package/src/cli/index.ts +61 -0
  241. package/src/cognitive/macro-agent-backend.ts +45 -29
  242. package/src/integrations/skilltree.ts +1 -0
  243. package/src/lifecycle/__tests__/cascade-consolidation.test.ts +240 -0
  244. package/src/lifecycle/cascade.ts +77 -2
  245. package/src/lifecycle/cleanup.ts +52 -3
  246. package/src/lifecycle/handlers-v2.ts +40 -3
  247. package/src/lifecycle/types.ts +12 -0
  248. package/src/map/__tests__/cascade-bridge.test.ts +229 -0
  249. package/src/map/__tests__/emit-event.test.ts +71 -0
  250. package/src/map/__tests__/lifecycle-bridge.test.ts +86 -10
  251. package/src/map/acp-bridge.ts +26 -3
  252. package/src/map/cascade-action-handler.ts +205 -0
  253. package/src/map/cascade-bridge.ts +339 -0
  254. package/src/map/coordination-handler.ts +13 -1
  255. package/src/map/lifecycle-bridge.ts +52 -17
  256. package/src/map/server.ts +225 -7
  257. package/src/map/sidecar.ts +48 -1
  258. package/src/map/types.ts +23 -0
  259. package/src/mcp/tools/done-v2.ts +9 -0
  260. package/src/teams/team-manager-v2.ts +37 -0
  261. package/src/teams/team-runtime-v2.ts +23 -3
  262. package/src/workspace/__tests__/{dataplane-adapter.test.ts → git-cascade-adapter.test.ts} +209 -14
  263. package/src/workspace/__tests__/land-dispatch.test.ts +214 -0
  264. package/src/workspace/__tests__/self-driving-yaml.test.ts +114 -0
  265. package/src/workspace/__tests__/shared-worktree-refcount.test.ts +154 -0
  266. package/src/workspace/__tests__/standalone-mode.test.ts +118 -0
  267. package/src/workspace/__tests__/workspace-manager-v3.test.ts +245 -0
  268. package/src/workspace/__tests__/yaml-schema.test.ts +210 -0
  269. package/src/workspace/config.ts +11 -11
  270. package/src/workspace/git-cascade-adapter.ts +1213 -0
  271. package/src/workspace/index.ts +11 -11
  272. package/src/workspace/landing/__tests__/strategies.test.ts +184 -0
  273. package/src/workspace/landing/direct-push.ts +91 -0
  274. package/src/workspace/landing/index.ts +40 -0
  275. package/src/workspace/landing/merge-to-parent.ts +229 -0
  276. package/src/workspace/landing/optimistic-push.ts +36 -0
  277. package/src/workspace/landing/queue-to-branch.ts +108 -0
  278. package/src/workspace/merge-queue/merge-queue.ts +10 -0
  279. package/src/workspace/merge-queue/types.ts +16 -2
  280. package/src/workspace/pool/__tests__/worktree-pool.integration.test.ts +5 -5
  281. package/src/workspace/pool/types.ts +1 -0
  282. package/src/workspace/pool/worktree-pool.ts +1 -0
  283. package/src/workspace/recovery/__tests__/auto-resolve-integration.test.ts +127 -0
  284. package/src/workspace/recovery/__tests__/spawn-resolver.test.ts +139 -0
  285. package/src/workspace/recovery/__tests__/strategies.test.ts +145 -0
  286. package/src/workspace/recovery/abandon.ts +51 -0
  287. package/src/workspace/recovery/auto-resolve.ts +119 -0
  288. package/src/workspace/recovery/defer.ts +23 -0
  289. package/src/workspace/recovery/escalate.ts +30 -0
  290. package/src/workspace/recovery/index.ts +58 -0
  291. package/src/workspace/recovery/spawn-resolver.ts +152 -0
  292. package/src/workspace/recovery/types.ts +54 -0
  293. package/src/workspace/topology/__tests__/yaml-driven.test.ts +345 -0
  294. package/src/workspace/topology/index.ts +18 -0
  295. package/src/workspace/topology/no-workspace.ts +39 -0
  296. package/src/workspace/topology/types.ts +116 -0
  297. package/src/workspace/topology/yaml-driven.ts +316 -0
  298. package/src/workspace/types-v3.ts +162 -0
  299. package/src/workspace/types.ts +211 -20
  300. package/src/workspace/workspace-manager.ts +533 -19
  301. package/src/workspace/yaml-schema.ts +216 -0
  302. package/dist/workspace/dataplane-adapter.d.ts +0 -260
  303. package/dist/workspace/dataplane-adapter.d.ts.map +0 -1
  304. package/dist/workspace/dataplane-adapter.js +0 -416
  305. package/dist/workspace/dataplane-adapter.js.map +0 -1
  306. package/src/workspace/dataplane-adapter.ts +0 -546
@@ -0,0 +1,316 @@
1
+ /**
2
+ * YamlDrivenTopology — primary TopologyPolicy implementation.
3
+ *
4
+ * Compiles `TeamWorkspaceConfig` into per-spawn workspace decisions. Covers
5
+ * all 6 workflows in `docs/git-cascade-integration-gaps.md` §5 through YAML
6
+ * alone.
7
+ *
8
+ * @module workspace/topology/yaml-driven
9
+ * @see docs/workspace-redesign-plan.md Phase 3
10
+ */
11
+
12
+ import type { AgentId, StreamId, Principal } from '../types-v3.js';
13
+ import type {
14
+ TeamWorkspaceConfig,
15
+ RoleWorkspaceConfig,
16
+ StreamLineage,
17
+ } from '../yaml-schema.js';
18
+ import type {
19
+ TopologyPolicy,
20
+ TeamStartContext,
21
+ TeamStartPlan,
22
+ SpawnContext,
23
+ WorkspaceDecision,
24
+ AgentCompleteContext,
25
+ TeamStopContext,
26
+ } from './types.js';
27
+
28
+ /**
29
+ * Topology policy driven entirely by `macro_agent.workspace` YAML.
30
+ */
31
+ export class YamlDrivenTopology implements TopologyPolicy {
32
+ readonly name = 'yaml-driven';
33
+
34
+ private teamStreamId?: StreamId;
35
+ private readonly agentStreams: Map<AgentId, StreamId> = new Map();
36
+ /** Tracks which role each live agent is, so sync-with-parent can dispatch. */
37
+ private readonly agentRoles: Map<AgentId, string> = new Map();
38
+ /** Unsubscribe from the workspace event stream; set on onTeamStart, cleared on onTeamStop. */
39
+ private eventUnsubscribe?: () => void;
40
+ /** Debounce map: stream id → last-sync timestamp (ms) for coalescing. */
41
+ private readonly lastSyncAt: Map<StreamId, number> = new Map();
42
+ /** Minimum interval between auto-syncs per parent stream (ms). */
43
+ private static readonly SYNC_COALESCE_MS = 2_000;
44
+
45
+ constructor(private readonly config: TeamWorkspaceConfig) {}
46
+
47
+ /**
48
+ * Look up per-role config. Returns null for roles not declared in YAML.
49
+ */
50
+ getRoleConfig(role: string): RoleWorkspaceConfig | null {
51
+ return this.config.roles[role] ?? null;
52
+ }
53
+
54
+ async onTeamStart(ctx: TeamStartContext): Promise<TeamStartPlan> {
55
+ const needsTeamRoot = this.teamNeedsRootStream();
56
+
57
+ if (needsTeamRoot) {
58
+ const forkFrom = this.config.default_stream?.fork_from ?? 'main';
59
+ const nameTemplate = this.config.default_stream?.name_template ?? '{team}';
60
+ const streamName = nameTemplate.replace('{team}', ctx.teamName);
61
+
62
+ this.teamStreamId = ctx.workspaceManager.createStreamV3({
63
+ name: streamName,
64
+ ownerId: `team:${ctx.teamName}` as const,
65
+ forkFrom,
66
+ metadata: { kind: 'team_root', teamInstanceId: ctx.teamInstanceId },
67
+ });
68
+ }
69
+
70
+ // Wire auto-sync if any role declared on_parent_advanced: sync_with_parent.
71
+ if (this.hasAutoSyncRoles()) {
72
+ this.eventUnsubscribe = ctx.workspaceManager.onEvent((event) => {
73
+ if (event.type !== 'stream:committed') return;
74
+ const streamId = event.data.streamId as StreamId | undefined;
75
+ if (!streamId) return;
76
+ // Fire-and-forget: schedule sync for affected agents on children of
77
+ // this stream, coalesced.
78
+ void this.dispatchSync(ctx, streamId);
79
+ });
80
+ }
81
+
82
+ return this.teamStreamId
83
+ ? { teamStreamId: this.teamStreamId }
84
+ : {};
85
+ }
86
+
87
+ async onAgentSpawn(ctx: SpawnContext): Promise<WorkspaceDecision> {
88
+ const roleConfig = this.getRoleConfig(ctx.role);
89
+ if (!roleConfig) {
90
+ // Role not declared in macro_agent.workspace → conservative default:
91
+ // inherit parent's cwd (no isolation, no stream).
92
+ return { kind: 'share-parent-cwd' };
93
+ }
94
+
95
+ switch (roleConfig.workspace) {
96
+ case 'none':
97
+ return { kind: 'none' };
98
+
99
+ case 'share_parent_cwd':
100
+ return { kind: 'share-parent-cwd' };
101
+
102
+ case 'share_with_agent': {
103
+ if (!roleConfig.share_with) {
104
+ // Schema guarantees this, but double-check
105
+ return { kind: 'share-parent-cwd' };
106
+ }
107
+ const partnerId = ctx.getAgentByRole?.(roleConfig.share_with);
108
+ if (!partnerId) {
109
+ // Partner role not yet spawned; fall back to share-parent-cwd.
110
+ return { kind: 'share-parent-cwd' };
111
+ }
112
+ return { kind: 'share-with-agent', agentId: partnerId };
113
+ }
114
+
115
+ case 'attach_to_team_root': {
116
+ if (!this.teamStreamId) {
117
+ // Should have been created in onTeamStart; defensive fallback
118
+ return { kind: 'share-parent-cwd' };
119
+ }
120
+ return {
121
+ kind: 'attach-to-stream',
122
+ streamId: this.teamStreamId,
123
+ allocateWorktree: roleConfig.allocation !== 'inherit_parent_cwd',
124
+ };
125
+ }
126
+
127
+ case 'new_stream': {
128
+ const parent = this.resolveParentStream(roleConfig, ctx);
129
+ const forkFrom =
130
+ roleConfig.stream_lineage === 'independent'
131
+ ? this.config.default_stream?.fork_from ?? 'main'
132
+ : undefined;
133
+ const streamName = this.buildStreamName(ctx.role, ctx.agentId);
134
+
135
+ const spec = {
136
+ name: streamName,
137
+ ownerId: ctx.agentId,
138
+ parent,
139
+ forkFrom,
140
+ metadata: { role: ctx.role },
141
+ };
142
+
143
+ return {
144
+ kind: 'new-stream',
145
+ streamSpec: spec,
146
+ allocateWorktree: roleConfig.allocation !== 'inherit_parent_cwd',
147
+ };
148
+ }
149
+ }
150
+ }
151
+
152
+ async onAgentComplete(ctx: AgentCompleteContext): Promise<void> {
153
+ // Deallocate the agent's worktree (if any). Landing was already handled
154
+ // by the LandingStrategy invoked from done(); this is pure cleanup.
155
+ try {
156
+ ctx.workspaceManager.deallocateWorkspace(ctx.agentId);
157
+ } catch {
158
+ // Non-fatal — agent may not have had a worktree
159
+ }
160
+ this.agentStreams.delete(ctx.agentId);
161
+ this.agentRoles.delete(ctx.agentId);
162
+ }
163
+
164
+ async onTeamStop(ctx: TeamStopContext): Promise<void> {
165
+ // Stop auto-sync event subscription
166
+ if (this.eventUnsubscribe) {
167
+ this.eventUnsubscribe();
168
+ this.eventUnsubscribe = undefined;
169
+ }
170
+
171
+ if (!this.teamStreamId) return;
172
+
173
+ const action = this.config.on_team_complete;
174
+ switch (action) {
175
+ case 'abandon':
176
+ ctx.workspaceManager.abandonStream(this.teamStreamId, {
177
+ cascade: true,
178
+ reason: 'team stopped',
179
+ });
180
+ break;
181
+ case 'merge_to_main':
182
+ // Requires a landing strategy configured for the team stream.
183
+ // Deferred to Phase 5 (LandingStrategy integration); log a warning.
184
+ // Leaving stream active for now.
185
+ break;
186
+ case 'keep':
187
+ default:
188
+ // Leave the stream active for human review / PR.
189
+ break;
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Track agent→stream mapping after a successful spawn. Called externally
195
+ * after the WorkspaceDecision is executed; lets the policy record state.
196
+ */
197
+ recordAgentStream(agentId: AgentId, streamId: StreamId, role?: string): void {
198
+ this.agentStreams.set(agentId, streamId);
199
+ if (role) this.agentRoles.set(agentId, role);
200
+ }
201
+
202
+ /**
203
+ * Get the stream attached to an agent (from our local tracking).
204
+ */
205
+ getAgentStream(agentId: AgentId): StreamId | null {
206
+ return this.agentStreams.get(agentId) ?? null;
207
+ }
208
+
209
+ // ── Helpers ─────────────────────────────────────────────────────────────
210
+
211
+ /**
212
+ * A team needs a root stream if any role uses team-root-relative lineage or
213
+ * attaches to the team root.
214
+ */
215
+ private teamNeedsRootStream(): boolean {
216
+ for (const roleConfig of Object.values(this.config.roles)) {
217
+ if (roleConfig.workspace === 'attach_to_team_root') return true;
218
+ if (
219
+ roleConfig.workspace === 'new_stream' &&
220
+ (roleConfig.stream_lineage === 'from_team_root' ||
221
+ roleConfig.stream_lineage === 'fork_from_team_root')
222
+ ) {
223
+ return true;
224
+ }
225
+ }
226
+ return false;
227
+ }
228
+
229
+ private resolveParentStream(
230
+ roleConfig: RoleWorkspaceConfig,
231
+ ctx: SpawnContext
232
+ ): StreamId | undefined {
233
+ const lineage = roleConfig.stream_lineage;
234
+ switch (lineage) {
235
+ case 'fork_from_team_root':
236
+ case 'from_team_root':
237
+ return this.teamStreamId;
238
+ case 'fork_from_parent':
239
+ return ctx.parentStreamId ?? this.teamStreamId;
240
+ case 'independent':
241
+ return undefined;
242
+ case 'track_existing_branch':
243
+ return undefined; // Caller uses track_branch directly
244
+ default:
245
+ return undefined;
246
+ }
247
+ }
248
+
249
+ private buildStreamName(role: string, agentId: AgentId): string {
250
+ const shortId = agentId.slice(-8);
251
+ return `${role}-${shortId}`;
252
+ }
253
+
254
+ /**
255
+ * True if any role declared `on_parent_advanced: sync_with_parent`.
256
+ * Used to decide whether to subscribe to stream:committed events at all.
257
+ */
258
+ private hasAutoSyncRoles(): boolean {
259
+ for (const roleConfig of Object.values(this.config.roles)) {
260
+ if (roleConfig.on_parent_advanced === 'sync_with_parent') return true;
261
+ }
262
+ return false;
263
+ }
264
+
265
+ /**
266
+ * Dispatch syncWithParent for live agents whose role has
267
+ * `on_parent_advanced: sync_with_parent` and whose active stream's parent
268
+ * is `parentStreamId`.
269
+ *
270
+ * Coalesces: skips if a sync fired for this parent within SYNC_COALESCE_MS.
271
+ */
272
+ private async dispatchSync(
273
+ ctx: TeamStartContext,
274
+ parentStreamId: StreamId
275
+ ): Promise<void> {
276
+ const now = Date.now();
277
+ const last = this.lastSyncAt.get(parentStreamId) ?? 0;
278
+ if (now - last < YamlDrivenTopology.SYNC_COALESCE_MS) {
279
+ return;
280
+ }
281
+ this.lastSyncAt.set(parentStreamId, now);
282
+
283
+ // Find live agents whose stream's parent matches and whose role has
284
+ // on_parent_advanced: sync_with_parent.
285
+ for (const [agentId, streamId] of this.agentStreams) {
286
+ const role = this.agentRoles.get(agentId);
287
+ if (!role) continue;
288
+ const roleConfig = this.getRoleConfig(role);
289
+ if (roleConfig?.on_parent_advanced !== 'sync_with_parent') continue;
290
+
291
+ const stream = ctx.workspaceManager.getStream(streamId);
292
+ if (stream?.parentStream !== parentStreamId) continue;
293
+
294
+ // Find agent's worktree
295
+ const worktree = ctx.workspaceManager.getWorktreeForAgent(agentId);
296
+ if (!worktree) continue; // no worktree → can't sync; skip silently
297
+
298
+ // Map YAML's on_conflict → git-cascade ConflictStrategy. 'defer' has
299
+ // no git-native equivalent; we use 'manual' (git-cascade records the
300
+ // conflict and leaves the worktree in a conflict state).
301
+ const yamlStrategy = roleConfig.on_conflict ?? 'defer';
302
+ const onConflict =
303
+ yamlStrategy === 'defer' ? 'manual' : yamlStrategy;
304
+ try {
305
+ ctx.workspaceManager.syncWithParent({
306
+ streamId,
307
+ agentId,
308
+ worktree: worktree.path,
309
+ onConflict,
310
+ });
311
+ } catch {
312
+ // Best-effort — don't throw out of an event handler
313
+ }
314
+ }
315
+ }
316
+ }
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Workspace V3 Types — stream-first primitives.
3
+ *
4
+ * These types supplement the legacy role-shaped `types.ts` for the redesigned
5
+ * workspace layer (see `docs/workspace-interfaces.md`). Once the migration is
6
+ * complete (Phase 9), these merge back into `types.ts` and the V3 prefix is
7
+ * dropped.
8
+ *
9
+ * @module workspace/types-v3
10
+ */
11
+
12
+ import type {
13
+ Stream as GCStream,
14
+ AgentWorktree,
15
+ MergeResult as GCMergeResult,
16
+ RebaseResult as GCRebaseResult,
17
+ Change as GCChange,
18
+ ChangeStatus,
19
+ ConflictStrategy as GCConflictStrategy,
20
+ ConflictRecord as GCConflictRecord,
21
+ } from 'git-cascade';
22
+
23
+ // ─────────────────────────────────────────────────────────────────────────────
24
+ // Identity
25
+ // ─────────────────────────────────────────────────────────────────────────────
26
+
27
+ export type AgentId = string;
28
+ export type StreamId = string;
29
+ export type ChangeId = string;
30
+ export type QueueEntryId = string;
31
+
32
+ /**
33
+ * Pseudo-principals own resources that aren't bound to a live agent. Team-root
34
+ * streams use `team:<name>`; system-created streams use `system:<subsystem>`.
35
+ * Tagged via prefix; never terminates.
36
+ */
37
+ export type PseudoAgentId = `team:${string}` | `system:${string}`;
38
+
39
+ export type Principal = AgentId | PseudoAgentId;
40
+
41
+ export const isPseudoAgentId = (p: Principal): p is PseudoAgentId =>
42
+ p.startsWith('team:') || p.startsWith('system:');
43
+
44
+ // ─────────────────────────────────────────────────────────────────────────────
45
+ // Streams
46
+ // ─────────────────────────────────────────────────────────────────────────────
47
+
48
+ /**
49
+ * Specification for creating a new stream. If `parent` is set, the stream
50
+ * forks from that parent stream. Otherwise, `forkFrom` names the branch to
51
+ * fork from (defaults to 'main').
52
+ */
53
+ export interface StreamSpec {
54
+ name: string;
55
+ ownerId: Principal;
56
+ parent?: StreamId;
57
+ forkFrom?: string;
58
+ metadata?: Record<string, unknown>;
59
+ }
60
+
61
+ export type Stream = GCStream;
62
+ export type MergeResult = GCMergeResult;
63
+ export type RebaseResult = GCRebaseResult;
64
+ export type Change = GCChange;
65
+ export type ConflictStrategy = GCConflictStrategy;
66
+ export type ConflictRecord = GCConflictRecord;
67
+ export type { ChangeStatus };
68
+
69
+ // ─────────────────────────────────────────────────────────────────────────────
70
+ // Worktrees (role-neutral)
71
+ // ─────────────────────────────────────────────────────────────────────────────
72
+
73
+ export interface AllocateWorktreeOpts {
74
+ agentId: Principal;
75
+ streamId?: StreamId;
76
+ baseDir?: string;
77
+ pooled?: boolean;
78
+ /**
79
+ * If set, co-locate this agent on the referenced agent's worktree
80
+ * (ref-counted; last-out wins on deallocation).
81
+ */
82
+ sharedWithAgent?: AgentId;
83
+ /** Optional branch override; defaults to the stream's branch. */
84
+ branch?: string;
85
+ }
86
+
87
+ export type Worktree = AgentWorktree;
88
+
89
+ // ─────────────────────────────────────────────────────────────────────────────
90
+ // Cascade
91
+ // ─────────────────────────────────────────────────────────────────────────────
92
+
93
+ export type CascadeStrategy = 'stop_on_conflict' | 'skip_conflicting' | 'defer_conflicts';
94
+
95
+ export interface CascadeResult {
96
+ rootStreamId: StreamId;
97
+ succeeded: StreamId[];
98
+ failed: Array<{ streamId: StreamId; conflictId?: string; error?: string }>;
99
+ }
100
+
101
+ // ─────────────────────────────────────────────────────────────────────────────
102
+ // Reconcile
103
+ // ─────────────────────────────────────────────────────────────────────────────
104
+
105
+ /**
106
+ * Result of macro-agent's reconcile wrapper — handles both git-cascade's
107
+ * stream↔git sync and macro-level worktree/pool state.
108
+ */
109
+ export interface MacroReconcileResult {
110
+ streamsChecked: number;
111
+ streamsFixed: number;
112
+ worktreesOrphaned: number;
113
+ worktreesCleaned: number;
114
+ poolEntriesPurged: number;
115
+ errors: Array<{ context: string; message: string }>;
116
+ }
117
+
118
+ // ─────────────────────────────────────────────────────────────────────────────
119
+ // Landing (interface only — strategies in Phase 5)
120
+ // ─────────────────────────────────────────────────────────────────────────────
121
+
122
+ export interface LandingContext {
123
+ agentId: AgentId;
124
+ streamId: StreamId;
125
+ sourceWorktree: string;
126
+ targetStreamId?: StreamId;
127
+ /**
128
+ * Strategy selector. Accepts either an internal strategy name
129
+ * (`merge-to-parent`, `queue-to-branch`, …) or the YAML form
130
+ * (`merge_to_parent_stream`, `queue_to_branch`, …). `WorkspaceManager.land`
131
+ * normalizes. When undefined, `merge-to-parent` is used.
132
+ */
133
+ strategyName?: string;
134
+ strategyConfig?: Record<string, unknown>;
135
+ /** Reference to the manager; strategies call back for merge/cascade. */
136
+ workspaceManager: unknown; // WorkspaceManager — circular; narrowed at callsite
137
+ /**
138
+ * Optional task reference inherited from the spawning agent. Strategies
139
+ * that produce commits/merges should thread this into their adapter
140
+ * calls' metadata as `{ task_ref }` so the resulting cascade events carry
141
+ * the binding.
142
+ */
143
+ taskRef?: import('git-cascade/events').TaskRef;
144
+ }
145
+
146
+ export interface LandingStrategy {
147
+ readonly name: string;
148
+ canLand?(ctx: LandingContext): boolean;
149
+ land(ctx: LandingContext): Promise<MergeResult>;
150
+ initialize?(): Promise<void>;
151
+ close?(): Promise<void>;
152
+ }
153
+
154
+ // ─────────────────────────────────────────────────────────────────────────────
155
+ // Helpers
156
+ // ─────────────────────────────────────────────────────────────────────────────
157
+
158
+ /**
159
+ * Opaque branded marker — use for inferring that something is a v3 concept.
160
+ * Only used in documentation / type exports; no runtime effect.
161
+ */
162
+ export const V3_MARKER: unique symbol = Symbol('workspace.v3');