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.
Files changed (258) hide show
  1. package/CLAUDE.md +166 -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 +155 -6
  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/agent/agent-manager-v2.d.ts +21 -0
  14. package/dist/agent/agent-manager-v2.d.ts.map +1 -1
  15. package/dist/agent/agent-manager-v2.js +234 -43
  16. package/dist/agent/agent-manager-v2.js.map +1 -1
  17. package/dist/agent/agent-manager.d.ts +12 -0
  18. package/dist/agent/agent-manager.d.ts.map +1 -1
  19. package/dist/agent/agent-manager.js.map +1 -1
  20. package/dist/agent/types.d.ts +15 -2
  21. package/dist/agent/types.d.ts.map +1 -1
  22. package/dist/agent/types.js.map +1 -1
  23. package/dist/boot-v2.d.ts +41 -0
  24. package/dist/boot-v2.d.ts.map +1 -1
  25. package/dist/boot-v2.js +16 -1
  26. package/dist/boot-v2.js.map +1 -1
  27. package/dist/cli/index.js +56 -0
  28. package/dist/cli/index.js.map +1 -1
  29. package/dist/cognitive/macro-agent-backend.d.ts.map +1 -1
  30. package/dist/cognitive/macro-agent-backend.js +40 -22
  31. package/dist/cognitive/macro-agent-backend.js.map +1 -1
  32. package/dist/integrations/skilltree.d.ts.map +1 -1
  33. package/dist/integrations/skilltree.js +1 -0
  34. package/dist/integrations/skilltree.js.map +1 -1
  35. package/dist/lifecycle/cleanup.d.ts +33 -2
  36. package/dist/lifecycle/cleanup.d.ts.map +1 -1
  37. package/dist/lifecycle/cleanup.js +28 -6
  38. package/dist/lifecycle/cleanup.js.map +1 -1
  39. package/dist/lifecycle/handlers-v2.d.ts +7 -0
  40. package/dist/lifecycle/handlers-v2.d.ts.map +1 -1
  41. package/dist/lifecycle/handlers-v2.js +28 -2
  42. package/dist/lifecycle/handlers-v2.js.map +1 -1
  43. package/dist/lifecycle/types.d.ts +11 -0
  44. package/dist/lifecycle/types.d.ts.map +1 -1
  45. package/dist/lifecycle/types.js.map +1 -1
  46. package/dist/map/acp-bridge.d.ts +9 -0
  47. package/dist/map/acp-bridge.d.ts.map +1 -1
  48. package/dist/map/acp-bridge.js +15 -2
  49. package/dist/map/acp-bridge.js.map +1 -1
  50. package/dist/map/cascade-bridge.d.ts +44 -0
  51. package/dist/map/cascade-bridge.d.ts.map +1 -0
  52. package/dist/map/cascade-bridge.js +257 -0
  53. package/dist/map/cascade-bridge.js.map +1 -0
  54. package/dist/map/lifecycle-bridge.d.ts +1 -1
  55. package/dist/map/lifecycle-bridge.d.ts.map +1 -1
  56. package/dist/map/lifecycle-bridge.js +58 -23
  57. package/dist/map/lifecycle-bridge.js.map +1 -1
  58. package/dist/map/server.d.ts.map +1 -1
  59. package/dist/map/server.js +47 -6
  60. package/dist/map/server.js.map +1 -1
  61. package/dist/map/sidecar.d.ts.map +1 -1
  62. package/dist/map/sidecar.js +33 -2
  63. package/dist/map/sidecar.js.map +1 -1
  64. package/dist/map/types.d.ts +20 -0
  65. package/dist/map/types.d.ts.map +1 -1
  66. package/dist/mcp/tools/done-v2.d.ts.map +1 -1
  67. package/dist/mcp/tools/done-v2.js +8 -0
  68. package/dist/mcp/tools/done-v2.js.map +1 -1
  69. package/dist/teams/team-manager-v2.d.ts.map +1 -1
  70. package/dist/teams/team-manager-v2.js +26 -0
  71. package/dist/teams/team-manager-v2.js.map +1 -1
  72. package/dist/teams/team-runtime-v2.d.ts.map +1 -1
  73. package/dist/teams/team-runtime-v2.js +16 -3
  74. package/dist/teams/team-runtime-v2.js.map +1 -1
  75. package/dist/workspace/config.d.ts +10 -10
  76. package/dist/workspace/config.d.ts.map +1 -1
  77. package/dist/workspace/config.js +4 -4
  78. package/dist/workspace/config.js.map +1 -1
  79. package/dist/workspace/git-cascade-adapter.d.ts +510 -0
  80. package/dist/workspace/git-cascade-adapter.d.ts.map +1 -0
  81. package/dist/workspace/git-cascade-adapter.js +908 -0
  82. package/dist/workspace/git-cascade-adapter.js.map +1 -0
  83. package/dist/workspace/index.d.ts +3 -3
  84. package/dist/workspace/index.d.ts.map +1 -1
  85. package/dist/workspace/index.js +4 -4
  86. package/dist/workspace/index.js.map +1 -1
  87. package/dist/workspace/landing/direct-push.d.ts +20 -0
  88. package/dist/workspace/landing/direct-push.d.ts.map +1 -0
  89. package/dist/workspace/landing/direct-push.js +74 -0
  90. package/dist/workspace/landing/direct-push.js.map +1 -0
  91. package/dist/workspace/landing/index.d.ts +29 -0
  92. package/dist/workspace/landing/index.d.ts.map +1 -0
  93. package/dist/workspace/landing/index.js +37 -0
  94. package/dist/workspace/landing/index.js.map +1 -0
  95. package/dist/workspace/landing/merge-to-parent.d.ts +41 -0
  96. package/dist/workspace/landing/merge-to-parent.d.ts.map +1 -0
  97. package/dist/workspace/landing/merge-to-parent.js +185 -0
  98. package/dist/workspace/landing/merge-to-parent.js.map +1 -0
  99. package/dist/workspace/landing/optimistic-push.d.ts +16 -0
  100. package/dist/workspace/landing/optimistic-push.d.ts.map +1 -0
  101. package/dist/workspace/landing/optimistic-push.js +27 -0
  102. package/dist/workspace/landing/optimistic-push.js.map +1 -0
  103. package/dist/workspace/landing/queue-to-branch.d.ts +24 -0
  104. package/dist/workspace/landing/queue-to-branch.d.ts.map +1 -0
  105. package/dist/workspace/landing/queue-to-branch.js +79 -0
  106. package/dist/workspace/landing/queue-to-branch.js.map +1 -0
  107. package/dist/workspace/merge-queue/merge-queue.d.ts +10 -0
  108. package/dist/workspace/merge-queue/merge-queue.d.ts.map +1 -1
  109. package/dist/workspace/merge-queue/merge-queue.js +10 -0
  110. package/dist/workspace/merge-queue/merge-queue.js.map +1 -1
  111. package/dist/workspace/merge-queue/types.d.ts +16 -2
  112. package/dist/workspace/merge-queue/types.d.ts.map +1 -1
  113. package/dist/workspace/merge-queue/types.js +9 -0
  114. package/dist/workspace/merge-queue/types.js.map +1 -1
  115. package/dist/workspace/pool/types.d.ts +1 -0
  116. package/dist/workspace/pool/types.d.ts.map +1 -1
  117. package/dist/workspace/pool/worktree-pool.d.ts.map +1 -1
  118. package/dist/workspace/pool/worktree-pool.js +1 -0
  119. package/dist/workspace/pool/worktree-pool.js.map +1 -1
  120. package/dist/workspace/recovery/abandon.d.ts +15 -0
  121. package/dist/workspace/recovery/abandon.d.ts.map +1 -0
  122. package/dist/workspace/recovery/abandon.js +45 -0
  123. package/dist/workspace/recovery/abandon.js.map +1 -0
  124. package/dist/workspace/recovery/auto-resolve.d.ts +27 -0
  125. package/dist/workspace/recovery/auto-resolve.d.ts.map +1 -0
  126. package/dist/workspace/recovery/auto-resolve.js +99 -0
  127. package/dist/workspace/recovery/auto-resolve.js.map +1 -0
  128. package/dist/workspace/recovery/defer.d.ts +15 -0
  129. package/dist/workspace/recovery/defer.d.ts.map +1 -0
  130. package/dist/workspace/recovery/defer.js +16 -0
  131. package/dist/workspace/recovery/defer.js.map +1 -0
  132. package/dist/workspace/recovery/escalate.d.ts +16 -0
  133. package/dist/workspace/recovery/escalate.d.ts.map +1 -0
  134. package/dist/workspace/recovery/escalate.js +24 -0
  135. package/dist/workspace/recovery/escalate.js.map +1 -0
  136. package/dist/workspace/recovery/index.d.ts +32 -0
  137. package/dist/workspace/recovery/index.d.ts.map +1 -0
  138. package/dist/workspace/recovery/index.js +45 -0
  139. package/dist/workspace/recovery/index.js.map +1 -0
  140. package/dist/workspace/recovery/spawn-resolver.d.ts +45 -0
  141. package/dist/workspace/recovery/spawn-resolver.d.ts.map +1 -0
  142. package/dist/workspace/recovery/spawn-resolver.js +111 -0
  143. package/dist/workspace/recovery/spawn-resolver.js.map +1 -0
  144. package/dist/workspace/recovery/types.d.ts +63 -0
  145. package/dist/workspace/recovery/types.d.ts.map +1 -0
  146. package/dist/workspace/recovery/types.js +12 -0
  147. package/dist/workspace/recovery/types.js.map +1 -0
  148. package/dist/workspace/topology/index.d.ts +9 -0
  149. package/dist/workspace/topology/index.d.ts.map +1 -0
  150. package/dist/workspace/topology/index.js +8 -0
  151. package/dist/workspace/topology/index.js.map +1 -0
  152. package/dist/workspace/topology/no-workspace.d.ts +18 -0
  153. package/dist/workspace/topology/no-workspace.d.ts.map +1 -0
  154. package/dist/workspace/topology/no-workspace.js +25 -0
  155. package/dist/workspace/topology/no-workspace.js.map +1 -0
  156. package/dist/workspace/topology/types.d.ts +97 -0
  157. package/dist/workspace/topology/types.d.ts.map +1 -0
  158. package/dist/workspace/topology/types.js +20 -0
  159. package/dist/workspace/topology/types.js.map +1 -0
  160. package/dist/workspace/topology/yaml-driven.d.ts +69 -0
  161. package/dist/workspace/topology/yaml-driven.d.ts.map +1 -0
  162. package/dist/workspace/topology/yaml-driven.js +273 -0
  163. package/dist/workspace/topology/yaml-driven.js.map +1 -0
  164. package/dist/workspace/types-v3.d.ts +110 -0
  165. package/dist/workspace/types-v3.d.ts.map +1 -0
  166. package/dist/workspace/types-v3.js +20 -0
  167. package/dist/workspace/types-v3.js.map +1 -0
  168. package/dist/workspace/types.d.ts +145 -17
  169. package/dist/workspace/types.d.ts.map +1 -1
  170. package/dist/workspace/workspace-manager.d.ts +92 -13
  171. package/dist/workspace/workspace-manager.d.ts.map +1 -1
  172. package/dist/workspace/workspace-manager.js +373 -13
  173. package/dist/workspace/workspace-manager.js.map +1 -1
  174. package/dist/workspace/yaml-schema.d.ts +254 -0
  175. package/dist/workspace/yaml-schema.d.ts.map +1 -0
  176. package/dist/workspace/yaml-schema.js +170 -0
  177. package/dist/workspace/yaml-schema.js.map +1 -0
  178. package/docs/conflict-recovery.md +472 -0
  179. package/docs/git-cascade-integration-gaps.md +678 -0
  180. package/docs/workspace-interfaces.md +731 -0
  181. package/docs/workspace-redesign-plan.md +302 -0
  182. package/package.json +4 -4
  183. package/src/__tests__/e2e/auto-sync.e2e.test.ts +257 -0
  184. package/src/__tests__/e2e/cascade-rebase.e2e.test.ts +254 -0
  185. package/src/__tests__/e2e/cli-run.e2e.test.ts +167 -0
  186. package/src/__tests__/e2e/self-driving-v3.e2e.test.ts +197 -0
  187. package/src/__tests__/e2e/spawn-resolver.e2e.test.ts +200 -0
  188. package/src/__tests__/e2e/workspace-lifecycle.e2e.test.ts +30 -22
  189. package/src/__tests__/e2e/workspace-v3.e2e.test.ts +413 -0
  190. package/src/acp/__tests__/claude-code-replay.test.ts +225 -0
  191. package/src/acp/__tests__/macro-agent.test.ts +39 -1
  192. package/src/acp/claude-code-replay.ts +208 -0
  193. package/src/acp/macro-agent.ts +167 -9
  194. package/src/acp/types.ts +10 -0
  195. package/src/agent/__tests__/agent-manager-topology.test.ts +73 -0
  196. package/src/agent/__tests__/agent-manager-v2.test.ts +66 -0
  197. package/src/agent/__tests__/task-ref-resolution.test.ts +231 -0
  198. package/src/agent/agent-manager-v2.ts +293 -48
  199. package/src/agent/agent-manager.ts +14 -0
  200. package/src/agent/types.ts +16 -2
  201. package/src/boot-v2.ts +68 -1
  202. package/src/cli/index.ts +61 -0
  203. package/src/cognitive/macro-agent-backend.ts +45 -29
  204. package/src/integrations/skilltree.ts +1 -0
  205. package/src/lifecycle/cleanup.ts +52 -3
  206. package/src/lifecycle/handlers-v2.ts +40 -3
  207. package/src/lifecycle/types.ts +12 -0
  208. package/src/map/__tests__/cascade-bridge.test.ts +229 -0
  209. package/src/map/__tests__/lifecycle-bridge.test.ts +86 -10
  210. package/src/map/acp-bridge.ts +26 -3
  211. package/src/map/cascade-bridge.ts +301 -0
  212. package/src/map/lifecycle-bridge.ts +52 -17
  213. package/src/map/server.ts +47 -6
  214. package/src/map/sidecar.ts +31 -1
  215. package/src/map/types.ts +20 -0
  216. package/src/mcp/tools/done-v2.ts +9 -0
  217. package/src/teams/team-manager-v2.ts +37 -0
  218. package/src/teams/team-runtime-v2.ts +23 -3
  219. package/src/workspace/__tests__/{dataplane-adapter.test.ts → git-cascade-adapter.test.ts} +209 -14
  220. package/src/workspace/__tests__/self-driving-yaml.test.ts +114 -0
  221. package/src/workspace/__tests__/shared-worktree-refcount.test.ts +154 -0
  222. package/src/workspace/__tests__/standalone-mode.test.ts +118 -0
  223. package/src/workspace/__tests__/workspace-manager-v3.test.ts +245 -0
  224. package/src/workspace/__tests__/yaml-schema.test.ts +210 -0
  225. package/src/workspace/config.ts +11 -11
  226. package/src/workspace/git-cascade-adapter.ts +1186 -0
  227. package/src/workspace/index.ts +11 -11
  228. package/src/workspace/landing/__tests__/strategies.test.ts +142 -0
  229. package/src/workspace/landing/direct-push.ts +91 -0
  230. package/src/workspace/landing/index.ts +40 -0
  231. package/src/workspace/landing/merge-to-parent.ts +228 -0
  232. package/src/workspace/landing/optimistic-push.ts +36 -0
  233. package/src/workspace/landing/queue-to-branch.ts +108 -0
  234. package/src/workspace/merge-queue/merge-queue.ts +10 -0
  235. package/src/workspace/merge-queue/types.ts +16 -2
  236. package/src/workspace/pool/__tests__/worktree-pool.integration.test.ts +5 -5
  237. package/src/workspace/pool/types.ts +1 -0
  238. package/src/workspace/pool/worktree-pool.ts +1 -0
  239. package/src/workspace/recovery/__tests__/auto-resolve-integration.test.ts +127 -0
  240. package/src/workspace/recovery/__tests__/spawn-resolver.test.ts +139 -0
  241. package/src/workspace/recovery/__tests__/strategies.test.ts +145 -0
  242. package/src/workspace/recovery/abandon.ts +51 -0
  243. package/src/workspace/recovery/auto-resolve.ts +119 -0
  244. package/src/workspace/recovery/defer.ts +23 -0
  245. package/src/workspace/recovery/escalate.ts +30 -0
  246. package/src/workspace/recovery/index.ts +58 -0
  247. package/src/workspace/recovery/spawn-resolver.ts +145 -0
  248. package/src/workspace/recovery/types.ts +54 -0
  249. package/src/workspace/topology/__tests__/yaml-driven.test.ts +345 -0
  250. package/src/workspace/topology/index.ts +18 -0
  251. package/src/workspace/topology/no-workspace.ts +39 -0
  252. package/src/workspace/topology/types.ts +116 -0
  253. package/src/workspace/topology/yaml-driven.ts +316 -0
  254. package/src/workspace/types-v3.ts +155 -0
  255. package/src/workspace/types.ts +191 -20
  256. package/src/workspace/workspace-manager.ts +474 -19
  257. package/src/workspace/yaml-schema.ts +216 -0
  258. package/src/workspace/dataplane-adapter.ts +0 -546
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * WorkspaceManager Implementation
3
3
  *
4
- * Bridges macro-agent roles to dataplane streams and worktrees.
4
+ * Bridges macro-agent roles to git-cascade streams and worktrees.
5
5
  * Provides a higher-level API for workspace management.
6
6
  *
7
7
  * @module workspace/workspace-manager
@@ -9,8 +9,8 @@
9
9
  */
10
10
 
11
11
  import type { Stream, WorkerTask, StartTaskResult, AgentWorktree, CleanupWorkerBranchesOptions, CleanupResult } from 'git-cascade';
12
- import { DataplaneAdapter } from './dataplane-adapter.js';
13
- import type { DataplaneConfig, WorktreePoolConfig } from './config.js';
12
+ import { GitCascadeAdapter } from './git-cascade-adapter.js';
13
+ import type { GitCascadeConfig, WorktreePoolConfig } from './config.js';
14
14
  import { DEFAULT_POOL_CONFIG } from './config.js';
15
15
  import { WorktreePool } from './pool/worktree-pool.js';
16
16
  import type { AllocationStrategy, AcquireOptions } from './pool/types.js';
@@ -35,7 +35,7 @@ import { execSync } from 'child_process';
35
35
  /**
36
36
  * Configuration options for DefaultWorkspaceManager.
37
37
  */
38
- export interface WorkspaceManagerConfig extends DataplaneConfig {
38
+ export interface WorkspaceManagerConfig extends GitCascadeConfig {
39
39
  /**
40
40
  * Base directory for worktrees.
41
41
  * Defaults to `<repoPath>/.worktrees`.
@@ -53,14 +53,14 @@ export interface WorkspaceManagerConfig extends DataplaneConfig {
53
53
  * DefaultWorkspaceManager implements the WorkspaceManager interface.
54
54
  *
55
55
  * Responsibilities:
56
- * - Wraps DataplaneAdapter for stream/worktree operations
56
+ * - Wraps GitCascadeAdapter for stream/worktree operations
57
57
  * - Maintains agentId → workspace mappings
58
58
  * - Emits events on workspace lifecycle changes
59
59
  *
60
60
  * @see [[s-7ktd]] WorkspaceManager section
61
61
  */
62
62
  export class DefaultWorkspaceManager implements WorkspaceManager {
63
- private readonly adapter: DataplaneAdapter;
63
+ private readonly adapter: GitCascadeAdapter;
64
64
  private readonly config: Required<Pick<WorkspaceManagerConfig, 'worktreeBaseDir'>>;
65
65
  private readonly poolConfig: WorktreePoolConfig;
66
66
  private readonly workspaces: Map<AgentId, Workspace> = new Map();
@@ -72,10 +72,10 @@ export class DefaultWorkspaceManager implements WorkspaceManager {
72
72
  /**
73
73
  * Create a new DefaultWorkspaceManager.
74
74
  *
75
- * @param adapter - DataplaneAdapter instance
75
+ * @param adapter - GitCascadeAdapter instance
76
76
  * @param config - Configuration options
77
77
  */
78
- constructor(adapter: DataplaneAdapter, config?: Partial<WorkspaceManagerConfig>) {
78
+ constructor(adapter: GitCascadeAdapter, config?: Partial<WorkspaceManagerConfig>) {
79
79
  this.adapter = adapter;
80
80
  this.config = {
81
81
  worktreeBaseDir: config?.worktreeBaseDir ?? `${adapter.repoPath}/.worktrees`,
@@ -86,6 +86,19 @@ export class DefaultWorkspaceManager implements WorkspaceManager {
86
86
  };
87
87
  }
88
88
 
89
+ /**
90
+ * Access the underlying GitCascadeAdapter. Exposed for bridges that
91
+ * subscribe to the cascade event stream (e.g., the MAP cascade-bridge
92
+ * that forwards events to an OpenHive hub).
93
+ *
94
+ * Most callers should use the WorkspaceManager API surface. Use this only
95
+ * when direct access to the adapter's event stream or primitives is
96
+ * required.
97
+ */
98
+ getGitCascadeAdapter(): GitCascadeAdapter {
99
+ return this.adapter;
100
+ }
101
+
89
102
  // ─────────────────────────────────────────────────────────────────────────────
90
103
  // Event System
91
104
  // ─────────────────────────────────────────────────────────────────────────────
@@ -370,11 +383,59 @@ export class DefaultWorkspaceManager implements WorkspaceManager {
370
383
  * @param agentId - ID of the agent whose workspace to deallocate
371
384
  */
372
385
  deallocateWorkspace(agentId: AgentId): void {
386
+ // Case 1: agentId is a sharer on someone else's worktree. Decrement
387
+ // the ref-count; only tear down if this was the last sharer AND the
388
+ // owner had previously departed.
389
+ for (const [path, entry] of this.sharedWorktreeRefs) {
390
+ if (entry.sharers.has(agentId)) {
391
+ entry.sharers.delete(agentId);
392
+ this.emit('worktree:released', {
393
+ agentId,
394
+ path,
395
+ kind: 'sharer',
396
+ });
397
+ if (entry.ownerDeparted && entry.sharers.size === 0) {
398
+ // Last sharer exiting after owner departed — finalize teardown
399
+ // under the original owner's id.
400
+ this.adapter.deallocateWorktree(entry.ownerId);
401
+ this.sharedWorktreeRefs.delete(path);
402
+ this.emit('workspace:deallocated', {
403
+ agentId: entry.ownerId,
404
+ path,
405
+ deferredUntilLastSharer: true,
406
+ });
407
+ }
408
+ return;
409
+ }
410
+ }
411
+
373
412
  const workspace = this.workspaces.get(agentId);
374
413
  if (!workspace) {
375
414
  return; // Already deallocated
376
415
  }
377
416
 
417
+ // Case 2: agentId is an owner of a shared worktree with active sharers.
418
+ // Defer git-cascade teardown until the last sharer leaves.
419
+ const sharedEntry = this.sharedWorktreeRefs.get(workspace.path);
420
+ if (
421
+ sharedEntry &&
422
+ sharedEntry.ownerId === agentId &&
423
+ sharedEntry.sharers.size > 0
424
+ ) {
425
+ sharedEntry.ownerDeparted = true;
426
+ this.workspaces.delete(agentId);
427
+ this.agentToStream.delete(agentId);
428
+ this.emit('worktree:released', {
429
+ agentId,
430
+ path: workspace.path,
431
+ kind: 'owner-departed',
432
+ remainingSharers: sharedEntry.sharers.size,
433
+ });
434
+ return;
435
+ }
436
+
437
+ // Case 3: normal teardown — owner with no sharers (or no sharing involved).
438
+
378
439
  // Remove from coordinator's child workspace map if this is a child
379
440
  if (workspace.role === 'worker' || workspace.role === 'integrator') {
380
441
  const streamId = workspace.streamId;
@@ -387,14 +448,18 @@ export class DefaultWorkspaceManager implements WorkspaceManager {
387
448
  }
388
449
  }
389
450
 
390
- // Deallocate via dataplane
451
+ // Deallocate via git-cascade adapter
391
452
  this.adapter.deallocateWorktree(agentId);
392
453
 
393
454
  // Clean up mappings
394
455
  this.workspaces.delete(agentId);
395
456
  this.agentToStream.delete(agentId);
396
457
 
397
- // Emit event
458
+ // Also clean up any stale entry (owner with zero sharers at deallocation time)
459
+ if (sharedEntry && sharedEntry.ownerId === agentId) {
460
+ this.sharedWorktreeRefs.delete(workspace.path);
461
+ }
462
+
398
463
  this.emit('workspace:deallocated', {
399
464
  agentId,
400
465
  role: workspace.role,
@@ -511,11 +576,11 @@ export class DefaultWorkspaceManager implements WorkspaceManager {
511
576
  }
512
577
 
513
578
  /**
514
- * Get the underlying DataplaneAdapter.
579
+ * Get the underlying GitCascadeAdapter.
515
580
  *
516
581
  * Use with caution - prefer manager methods for operations.
517
582
  */
518
- get rawAdapter(): DataplaneAdapter {
583
+ get rawAdapter(): GitCascadeAdapter {
519
584
  return this.adapter;
520
585
  }
521
586
 
@@ -527,7 +592,7 @@ export class DefaultWorkspaceManager implements WorkspaceManager {
527
592
  * Get the merge queue for coordinating worker merges.
528
593
  *
529
594
  * The merge queue is lazily initialized on first access and uses
530
- * the same database as the dataplane adapter.
595
+ * the same database as the git-cascade adapter.
531
596
  *
532
597
  * @returns MergeQueue instance
533
598
  */
@@ -535,7 +600,7 @@ export class DefaultWorkspaceManager implements WorkspaceManager {
535
600
  if (!this.mergeQueue) {
536
601
  this.mergeQueue = new MergeQueue({
537
602
  db: this.adapter.db,
538
- tablePrefix: 'macro_', // Use different prefix from dataplane tables
603
+ tablePrefix: 'macro_', // Use different prefix from git-cascade tables
539
604
  initSchema: true,
540
605
  });
541
606
  }
@@ -895,7 +960,7 @@ export class DefaultWorkspaceManager implements WorkspaceManager {
895
960
  }
896
961
  }
897
962
 
898
- // Release to pool if enabled, otherwise deallocate via dataplane
963
+ // Release to pool if enabled, otherwise deallocate via git-cascade
899
964
  const pool = this.getPool();
900
965
  if (pool) {
901
966
  await pool.release(agentId, { clean });
@@ -963,6 +1028,396 @@ export class DefaultWorkspaceManager implements WorkspaceManager {
963
1028
  return result;
964
1029
  }
965
1030
 
1031
+ // ═════════════════════════════════════════════════════════════════════════════
1032
+ // V3 — Stream-first surface
1033
+ //
1034
+ // Additive with the role-shaped methods above. Callers migrate piecewise
1035
+ // during Phases 3-4; Phase 9 removes the legacy methods. See
1036
+ // docs/workspace-interfaces.md §5 and docs/workspace-redesign-plan.md.
1037
+ // ═════════════════════════════════════════════════════════════════════════════
1038
+
1039
+ private readonly landingStrategies: Map<string, import('./types-v3.js').LandingStrategy> = new Map();
1040
+
1041
+ /**
1042
+ * Ref-counted sharing state keyed by worktree path.
1043
+ *
1044
+ * - `ownerId`: the principal that originally allocated the worktree (the
1045
+ * id that git-cascade's tracker associates with the worktree).
1046
+ * - `sharers`: other agents that allocated via `sharedWithAgent`.
1047
+ * - `ownerDeparted`: set when the owner deallocates but sharers remain —
1048
+ * the actual git-cascade teardown is deferred until `sharers` is empty.
1049
+ */
1050
+ private readonly sharedWorktreeRefs: Map<
1051
+ string,
1052
+ { ownerId: import('./types-v3.js').Principal; sharers: Set<AgentId>; ownerDeparted: boolean }
1053
+ > = new Map();
1054
+
1055
+ createStreamV3(spec: import('./types-v3.js').StreamSpec): StreamId {
1056
+ let streamId: StreamId;
1057
+ if (spec.parent) {
1058
+ streamId = this.adapter.forkStream({
1059
+ parentStreamId: spec.parent,
1060
+ name: spec.name,
1061
+ agentId: spec.ownerId,
1062
+ });
1063
+ // git-cascade's ForkStreamOptions doesn't accept metadata; apply via update.
1064
+ if (spec.metadata) {
1065
+ this.adapter.updateStream(streamId, { metadata: spec.metadata });
1066
+ }
1067
+ } else {
1068
+ streamId = this.adapter.createStream({
1069
+ name: spec.name,
1070
+ agentId: spec.ownerId,
1071
+ base: spec.forkFrom ?? 'main',
1072
+ metadata: spec.metadata,
1073
+ });
1074
+ }
1075
+ this.emit(spec.parent ? 'stream:forked' : 'stream:created', {
1076
+ streamId,
1077
+ ownerId: spec.ownerId,
1078
+ parentStreamId: spec.parent,
1079
+ });
1080
+ return streamId;
1081
+ }
1082
+
1083
+ forkStream(opts: {
1084
+ parentStreamId: StreamId;
1085
+ name: string;
1086
+ ownerId: import('./types-v3.js').Principal;
1087
+ metadata?: Record<string, unknown>;
1088
+ }): StreamId {
1089
+ const streamId = this.adapter.forkStream({
1090
+ parentStreamId: opts.parentStreamId,
1091
+ name: opts.name,
1092
+ agentId: opts.ownerId,
1093
+ });
1094
+ if (opts.metadata) {
1095
+ this.adapter.updateStream(streamId, { metadata: opts.metadata });
1096
+ }
1097
+ this.emit('stream:forked', {
1098
+ streamId,
1099
+ parentStreamId: opts.parentStreamId,
1100
+ ownerId: opts.ownerId,
1101
+ });
1102
+ return streamId;
1103
+ }
1104
+
1105
+ mergeStream(opts: {
1106
+ sourceStreamId: StreamId;
1107
+ targetStreamId: StreamId;
1108
+ agentId: import('./types-v3.js').Principal;
1109
+ worktree: string;
1110
+ }): import('./types-v3.js').MergeResult {
1111
+ // git-cascade's MergeStreamOptions uses `sourceStream`/`targetStream`.
1112
+ // We adapt to v3's `sourceStreamId`/`targetStreamId` at the boundary.
1113
+ const result = this.adapter.mergeStream({
1114
+ sourceStream: opts.sourceStreamId,
1115
+ targetStream: opts.targetStreamId,
1116
+ agentId: opts.agentId,
1117
+ worktree: opts.worktree,
1118
+ });
1119
+ if (result.success) {
1120
+ this.emit('stream:merged', {
1121
+ sourceStreamId: opts.sourceStreamId,
1122
+ targetStreamId: opts.targetStreamId,
1123
+ mergeCommit: result.newHead,
1124
+ });
1125
+ } else {
1126
+ this.emit('stream:conflicted', {
1127
+ streamId: opts.sourceStreamId,
1128
+ conflicts: result.conflicts,
1129
+ error: result.error,
1130
+ });
1131
+ }
1132
+ return result;
1133
+ }
1134
+
1135
+ syncWithParent(opts: {
1136
+ streamId: StreamId;
1137
+ agentId: import('./types-v3.js').Principal;
1138
+ worktree: string;
1139
+ onConflict?: import('./types-v3.js').ConflictStrategy;
1140
+ }): import('./types-v3.js').RebaseResult {
1141
+ return this.adapter.syncWithParent(
1142
+ opts.streamId,
1143
+ opts.agentId,
1144
+ opts.worktree,
1145
+ opts.onConflict
1146
+ );
1147
+ }
1148
+
1149
+ // abandonStream, pauseStream, resumeStream are inherited-by-name from the
1150
+ // adapter calls; expose thin wrappers that emit workspace-level events.
1151
+
1152
+ abandonStream(streamId: StreamId, opts?: { cascade?: boolean; reason?: string }): void {
1153
+ this.adapter.abandonStream(streamId, opts);
1154
+ this.emit('stream:abandoned', { streamId, ...opts });
1155
+ }
1156
+
1157
+ pauseStream(streamId: StreamId, reason?: string): void {
1158
+ this.adapter.pauseStream(streamId, reason);
1159
+ this.emit('stream:paused', { streamId, reason });
1160
+ }
1161
+
1162
+ resumeStream(streamId: StreamId): void {
1163
+ this.adapter.resumeStream(streamId);
1164
+ this.emit('stream:resumed', { streamId });
1165
+ }
1166
+
1167
+ listStreams(filter?: {
1168
+ ownerId?: import('./types-v3.js').Principal;
1169
+ status?: import('./types-v3.js').Stream['status'];
1170
+ }): import('./types-v3.js').Stream[] {
1171
+ return this.adapter.listStreams({
1172
+ agentId: filter?.ownerId,
1173
+ status: filter?.status,
1174
+ });
1175
+ }
1176
+
1177
+ commitChanges(opts: {
1178
+ agentId: import('./types-v3.js').Principal;
1179
+ streamId: StreamId;
1180
+ worktree: string;
1181
+ message: string;
1182
+ }): { commit: string; changeId: import('./types-v3.js').ChangeId } {
1183
+ const result = this.adapter.commitChanges(opts);
1184
+ this.emit('stream:committed', {
1185
+ streamId: opts.streamId,
1186
+ commit: result.commit,
1187
+ changeId: result.changeId,
1188
+ agentId: opts.agentId,
1189
+ });
1190
+ return result;
1191
+ }
1192
+
1193
+ markChangesMerged(changeIds: import('./types-v3.js').ChangeId[]): void {
1194
+ this.adapter.markChangesMerged(changeIds);
1195
+ for (const id of changeIds) {
1196
+ this.emit('change:merged', { changeId: id });
1197
+ }
1198
+ }
1199
+
1200
+ getChange(changeId: import('./types-v3.js').ChangeId): import('./types-v3.js').Change | null {
1201
+ return this.adapter.getChange(changeId);
1202
+ }
1203
+
1204
+ getChangeByCommit(commit: string): import('./types-v3.js').Change | null {
1205
+ return this.adapter.getChangeByCommit(commit);
1206
+ }
1207
+
1208
+ allocateWorktree(
1209
+ opts: import('./types-v3.js').AllocateWorktreeOpts
1210
+ ): import('./types-v3.js').Worktree {
1211
+ // Ref-counted sharing: if sharedWithAgent is set, reuse that agent's worktree
1212
+ if (opts.sharedWithAgent) {
1213
+ const owner = this.adapter.getWorktree(opts.sharedWithAgent);
1214
+ if (!owner) {
1215
+ throw new Error(
1216
+ `Cannot share worktree: agent ${opts.sharedWithAgent} has no allocated worktree`
1217
+ );
1218
+ }
1219
+ let entry = this.sharedWorktreeRefs.get(owner.path);
1220
+ if (!entry) {
1221
+ entry = {
1222
+ ownerId: opts.sharedWithAgent,
1223
+ sharers: new Set<AgentId>(),
1224
+ ownerDeparted: false,
1225
+ };
1226
+ this.sharedWorktreeRefs.set(owner.path, entry);
1227
+ }
1228
+ if (entry.ownerDeparted && entry.sharers.size === 0) {
1229
+ // Edge case: owner already left and all sharers left, but someone
1230
+ // is still trying to share. Reject — the teardown has been staged
1231
+ // but this would resurrect a dead reference.
1232
+ throw new Error(
1233
+ `Cannot share worktree at ${owner.path}: owner has departed and no active sharers`
1234
+ );
1235
+ }
1236
+ entry.sharers.add(opts.agentId);
1237
+ this.emit('worktree:shared', {
1238
+ path: owner.path,
1239
+ ownerAgentId: opts.sharedWithAgent,
1240
+ sharingAgentId: opts.agentId,
1241
+ });
1242
+ return owner;
1243
+ }
1244
+
1245
+ // Fresh worktree
1246
+ const baseDir = opts.baseDir ?? this.config.worktreeBaseDir;
1247
+ const sanitizedId = opts.agentId.replace(/[^a-zA-Z0-9_-]/g, '_');
1248
+ const path = `${baseDir}/${sanitizedId}`;
1249
+
1250
+ const worktreeArgs: import('git-cascade').CreateWorktreeOptions = {
1251
+ agentId: opts.agentId,
1252
+ path,
1253
+ };
1254
+ if (opts.streamId) {
1255
+ worktreeArgs.branch = opts.branch ?? this.adapter.getStreamBranchName(opts.streamId);
1256
+ } else if (opts.branch) {
1257
+ worktreeArgs.branch = opts.branch;
1258
+ }
1259
+
1260
+ const worktree = this.adapter.createWorktree(worktreeArgs);
1261
+
1262
+ if (opts.streamId) {
1263
+ this.agentToStream.set(opts.agentId, opts.streamId);
1264
+ }
1265
+
1266
+ // Track in workspaces map so legacy deallocateWorkspace can find this
1267
+ // V3-allocated worktree. Use role='v3' to bypass the legacy
1268
+ // worker/integrator coordinator-map cleanup path.
1269
+ this.workspaces.set(opts.agentId as AgentId, {
1270
+ agentId: opts.agentId as AgentId,
1271
+ path: worktree.path,
1272
+ branch: worktree.currentStream
1273
+ ? `stream/${worktree.currentStream}`
1274
+ : (opts.branch ?? 'unknown'),
1275
+ streamId: opts.streamId ?? '',
1276
+ role: 'v3',
1277
+ createdAt: worktree.createdAt,
1278
+ });
1279
+
1280
+ this.emit('worktree:allocated', {
1281
+ agentId: opts.agentId,
1282
+ path: worktree.path,
1283
+ streamId: opts.streamId,
1284
+ });
1285
+ return worktree;
1286
+ }
1287
+
1288
+ getWorktreeForAgent(
1289
+ agentId: import('./types-v3.js').Principal
1290
+ ): import('./types-v3.js').Worktree | null {
1291
+ return this.adapter.getWorktree(agentId);
1292
+ }
1293
+
1294
+ registerLandingStrategy(strategy: import('./types-v3.js').LandingStrategy): void {
1295
+ this.landingStrategies.set(strategy.name, strategy);
1296
+ }
1297
+
1298
+ reconcileV3(): import('./types-v3.js').MacroReconcileResult {
1299
+ const result: import('./types-v3.js').MacroReconcileResult = {
1300
+ streamsChecked: 0,
1301
+ streamsFixed: 0,
1302
+ worktreesOrphaned: 0,
1303
+ worktreesCleaned: 0,
1304
+ poolEntriesPurged: 0,
1305
+ errors: [],
1306
+ };
1307
+
1308
+ // Delegate stream↔git sync to git-cascade
1309
+ try {
1310
+ const gcResult = this.adapter.reconcile();
1311
+ result.streamsChecked = (gcResult.updated?.length ?? 0) +
1312
+ (gcResult.branchesCreated?.length ?? 0) +
1313
+ (gcResult.failed?.length ?? 0);
1314
+ result.streamsFixed = (gcResult.updated?.length ?? 0) +
1315
+ (gcResult.branchesCreated?.length ?? 0);
1316
+ for (const f of gcResult.failed ?? []) {
1317
+ result.errors.push({
1318
+ context: `stream ${f.streamId}`,
1319
+ message: f.error,
1320
+ });
1321
+ }
1322
+ } catch (err) {
1323
+ result.errors.push({
1324
+ context: 'git-cascade reconcile',
1325
+ message: err instanceof Error ? err.message : String(err),
1326
+ });
1327
+ }
1328
+
1329
+ // Worktree pool and orphan cleanup deferred to Phase 3 (when TopologyPolicy
1330
+ // owns worktree lifecycle). For now, just count what git-cascade knows about.
1331
+ try {
1332
+ const worktrees = this.adapter.listWorktrees();
1333
+ // An "orphan" is a worktree without a corresponding record in our tracking
1334
+ // map. Count only; don't delete here — that's reserved for Phase 3.
1335
+ for (const wt of worktrees) {
1336
+ if (!this.workspaces.has(wt.agentId) && !this.agentToStream.has(wt.agentId)) {
1337
+ result.worktreesOrphaned++;
1338
+ }
1339
+ }
1340
+ } catch (err) {
1341
+ result.errors.push({
1342
+ context: 'worktree orphan scan',
1343
+ message: err instanceof Error ? err.message : String(err),
1344
+ });
1345
+ }
1346
+
1347
+ return result;
1348
+ }
1349
+
1350
+ resolveConflict(opts: {
1351
+ conflictId: string;
1352
+ resolvedBy: import('./types-v3.js').Principal;
1353
+ resolutionCommit?: string;
1354
+ /**
1355
+ * How the conflict was resolved. Defaults to 'agent' for the legacy
1356
+ * call shape; recovery strategies should pass an explicit method so
1357
+ * the OpenHive hub records the right resolution.
1358
+ */
1359
+ method?: import('git-cascade').ConflictResolution['method'] | 'auto-resolve' | 'spawn-resolver' | 'abandoned';
1360
+ /** Human-readable resolution summary (e.g., 'merged with -X ours'). */
1361
+ summary?: string;
1362
+ }): void {
1363
+ const conflict = this.adapter.getConflict(opts.conflictId);
1364
+ const method = opts.method ?? 'agent';
1365
+
1366
+ // Drive git-cascade's resolveConflict so the underlying conflict record
1367
+ // moves to status='resolved' AND the tracker emits stream.conflict_resolved.
1368
+ // Hub observers (cascade-bridge → OpenHive) update cascade_conflicts.status
1369
+ // accordingly. Falls back gracefully if cascade is older than 0.0.6.
1370
+ const trackerHasResolve = typeof (
1371
+ this.adapter as { resolveConflict?: unknown }
1372
+ ).resolveConflict === 'function';
1373
+ if (trackerHasResolve) {
1374
+ try {
1375
+ (
1376
+ this.adapter as unknown as {
1377
+ resolveConflict(args: {
1378
+ conflictId: string;
1379
+ resolution: import('git-cascade').ConflictResolution & { summary?: string };
1380
+ metadata?: Record<string, unknown>;
1381
+ }): void;
1382
+ }
1383
+ ).resolveConflict({
1384
+ conflictId: opts.conflictId,
1385
+ resolution: {
1386
+ method:
1387
+ method === 'auto-resolve' ||
1388
+ method === 'spawn-resolver' ||
1389
+ method === 'abandoned'
1390
+ ? 'agent'
1391
+ : method,
1392
+ resolvedBy: opts.resolvedBy,
1393
+ details: opts.summary,
1394
+ },
1395
+ metadata: {
1396
+ resolution_method_actual: method,
1397
+ resolution_commit: opts.resolutionCommit,
1398
+ },
1399
+ });
1400
+ } catch {
1401
+ // Best-effort; legacy resume path below remains.
1402
+ }
1403
+ }
1404
+
1405
+ if (conflict?.streamId) {
1406
+ try {
1407
+ this.adapter.resumeStream(conflict.streamId);
1408
+ } catch {
1409
+ // Stream may not be paused; safe to ignore.
1410
+ }
1411
+ }
1412
+
1413
+ this.emit('conflict:resolved', {
1414
+ conflictId: opts.conflictId,
1415
+ resolvedBy: opts.resolvedBy,
1416
+ resolutionCommit: opts.resolutionCommit,
1417
+ streamId: conflict?.streamId,
1418
+ });
1419
+ }
1420
+
966
1421
  // ─────────────────────────────────────────────────────────────────────────────
967
1422
  // Lifecycle
968
1423
  // ─────────────────────────────────────────────────────────────────────────────
@@ -1044,19 +1499,19 @@ export class DefaultWorkspaceManager implements WorkspaceManager {
1044
1499
  export function createWorkspaceManager(
1045
1500
  config: WorkspaceManagerConfig
1046
1501
  ): DefaultWorkspaceManager {
1047
- const adapter = new DataplaneAdapter(config);
1502
+ const adapter = new GitCascadeAdapter(config);
1048
1503
  return new DefaultWorkspaceManager(adapter, config);
1049
1504
  }
1050
1505
 
1051
1506
  /**
1052
- * Create a WorkspaceManager with an existing DataplaneAdapter.
1507
+ * Create a WorkspaceManager with an existing GitCascadeAdapter.
1053
1508
  *
1054
- * @param adapter - DataplaneAdapter instance
1509
+ * @param adapter - GitCascadeAdapter instance
1055
1510
  * @param config - Configuration options
1056
1511
  * @returns WorkspaceManager instance
1057
1512
  */
1058
1513
  export function createWorkspaceManagerWithAdapter(
1059
- adapter: DataplaneAdapter,
1514
+ adapter: GitCascadeAdapter,
1060
1515
  config?: Partial<WorkspaceManagerConfig>
1061
1516
  ): DefaultWorkspaceManager {
1062
1517
  return new DefaultWorkspaceManager(adapter, config);