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,51 @@
1
+ /**
2
+ * `abandon` conflict recovery strategy.
3
+ *
4
+ * Abandons the conflicted stream. Throwaway-exploration teams; CI-driven
5
+ * flows where broken work is discarded rather than resolved.
6
+ *
7
+ * @module workspace/recovery/abandon
8
+ */
9
+
10
+ import type {
11
+ ConflictContext,
12
+ ConflictRecoveryStrategy,
13
+ ConflictResolution,
14
+ } from './types.js';
15
+
16
+ export class AbandonStrategy implements ConflictRecoveryStrategy {
17
+ readonly name = 'abandon';
18
+ readonly mode = 'sync' as const;
19
+
20
+ async recover(ctx: ConflictContext): Promise<ConflictResolution> {
21
+ try {
22
+ // Mark the conflict resolved (method='abandoned') so the OpenHive hub
23
+ // moves cascade_conflicts.status from pending → resolved instead of
24
+ // showing it stuck pending forever.
25
+ try {
26
+ ctx.workspaceManager.resolveConflict({
27
+ conflictId: ctx.conflictId,
28
+ resolvedBy: 'system:abandon',
29
+ method: 'abandoned',
30
+ summary: `stream abandoned: ${ctx.streamId}`,
31
+ });
32
+ } catch {
33
+ // Non-fatal — abandonStream below still runs
34
+ }
35
+
36
+ ctx.workspaceManager.abandonStream(ctx.streamId, {
37
+ reason: `abandon strategy: conflict ${ctx.conflictId}`,
38
+ });
39
+ return {
40
+ kind: 'abandoned',
41
+ streamId: ctx.streamId,
42
+ reason: `conflict ${ctx.conflictId}`,
43
+ };
44
+ } catch (err) {
45
+ return {
46
+ kind: 'failed',
47
+ error: err instanceof Error ? err.message : String(err),
48
+ };
49
+ }
50
+ }
51
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * `auto-resolve` conflict recovery strategy.
3
+ *
4
+ * Replays the failed merge in the agent's worktree with a git strategy
5
+ * option (`-X ours` / `-X theirs` / `-s union` via `-X`), commits the
6
+ * resolution, and returns the resulting commit hash.
7
+ *
8
+ * Strategy config:
9
+ * - `strategy`: 'ours' | 'theirs' | 'union' (default: 'ours')
10
+ * - `commit_message`: string (default: `resolve: ${conflictId} via <strategy>`)
11
+ *
12
+ * Scope limits:
13
+ * - Only handles `operation: 'merge'`. Rebase conflicts need different tooling.
14
+ * - Requires `ctx.worktree` to be set. If absent, returns `failed`.
15
+ * - Requires `ctx.sourceCommit` to identify what to replay. If absent,
16
+ * tries `HEAD^2` (the merge commit's incoming side).
17
+ *
18
+ * @module workspace/recovery/auto-resolve
19
+ */
20
+
21
+ import { execSync } from 'child_process';
22
+ import type {
23
+ ConflictContext,
24
+ ConflictRecoveryStrategy,
25
+ ConflictResolution,
26
+ } from './types.js';
27
+
28
+ export class AutoResolveStrategy implements ConflictRecoveryStrategy {
29
+ readonly name = 'auto-resolve';
30
+ readonly mode = 'sync' as const;
31
+
32
+ canHandle(ctx: ConflictContext): boolean {
33
+ return ctx.operation === 'merge' && !!ctx.worktree;
34
+ }
35
+
36
+ async recover(ctx: ConflictContext): Promise<ConflictResolution> {
37
+ if (ctx.operation !== 'merge') {
38
+ return {
39
+ kind: 'failed',
40
+ error: `auto-resolve only handles merge conflicts, not ${ctx.operation}`,
41
+ };
42
+ }
43
+
44
+ if (!ctx.worktree) {
45
+ return {
46
+ kind: 'failed',
47
+ error: 'auto-resolve requires ctx.worktree',
48
+ };
49
+ }
50
+
51
+ const strategy = (ctx.strategyConfig?.strategy as string | undefined) ?? 'ours';
52
+ if (strategy !== 'ours' && strategy !== 'theirs' && strategy !== 'union') {
53
+ return {
54
+ kind: 'failed',
55
+ error: `auto-resolve: unsupported strategy "${strategy}" (use ours | theirs | union)`,
56
+ };
57
+ }
58
+
59
+ const sourceRef = ctx.sourceCommit ?? 'MERGE_HEAD';
60
+ const commitMessage =
61
+ (ctx.strategyConfig?.commit_message as string | undefined) ??
62
+ `resolve: ${ctx.conflictId} via ${strategy}`;
63
+
64
+ try {
65
+ // Abort any in-progress merge state first (safety — merge may have
66
+ // left the index in a partial state)
67
+ try {
68
+ execSync('git merge --abort', {
69
+ cwd: ctx.worktree,
70
+ stdio: 'pipe',
71
+ });
72
+ } catch {
73
+ // No merge in progress — that's fine
74
+ }
75
+
76
+ // Replay the merge with the chosen strategy
77
+ execSync(
78
+ `git merge -X ${strategy} --no-edit -m ${quote(commitMessage)} ${quote(sourceRef)}`,
79
+ {
80
+ cwd: ctx.worktree,
81
+ stdio: 'pipe',
82
+ }
83
+ );
84
+
85
+ // Capture the new HEAD as the resolution commit
86
+ const resolutionCommit = execSync('git rev-parse HEAD', {
87
+ cwd: ctx.worktree,
88
+ encoding: 'utf-8',
89
+ }).trim();
90
+
91
+ // Notify the WorkspaceManager so downstream consumers see the resolution
92
+ try {
93
+ ctx.workspaceManager.resolveConflict({
94
+ conflictId: ctx.conflictId,
95
+ resolvedBy: ctx.landingAgentId ?? 'system:auto-resolve',
96
+ resolutionCommit,
97
+ method: 'auto-resolve',
98
+ summary: `merged with -X ${strategy}`,
99
+ });
100
+ } catch {
101
+ // Non-fatal — resolution is recorded via return value regardless
102
+ }
103
+
104
+ return { kind: 'resolved', resolutionCommit };
105
+ } catch (err) {
106
+ return {
107
+ kind: 'failed',
108
+ error: `auto-resolve (strategy=${strategy}): ${
109
+ err instanceof Error ? err.message : String(err)
110
+ }`,
111
+ };
112
+ }
113
+ }
114
+ }
115
+
116
+ /** Minimal shell-safe single-quoting for commit messages / refs. */
117
+ function quote(s: string): string {
118
+ return `'${s.replace(/'/g, "'\\''")}'`;
119
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * `defer` conflict recovery strategy — no-op.
3
+ *
4
+ * Leaves the conflict record in place; stream stays `conflicted`. Something
5
+ * else (human, external process, scheduled recovery) resolves later.
6
+ *
7
+ * @module workspace/recovery/defer
8
+ */
9
+
10
+ import type {
11
+ ConflictContext,
12
+ ConflictRecoveryStrategy,
13
+ ConflictResolution,
14
+ } from './types.js';
15
+
16
+ export class DeferStrategy implements ConflictRecoveryStrategy {
17
+ readonly name = 'defer';
18
+ readonly mode = 'sync' as const;
19
+
20
+ async recover(_ctx: ConflictContext): Promise<ConflictResolution> {
21
+ return { kind: 'deferred', reason: 'no recovery strategy configured' };
22
+ }
23
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * `escalate` conflict recovery strategy — human-in-the-loop.
3
+ *
4
+ * Pauses the stream and emits an escalation marker. External systems (UI,
5
+ * on-call agent, operator) resolve the conflict manually, then call
6
+ * `resolve_conflict` MCP tool to unblock.
7
+ *
8
+ * @module workspace/recovery/escalate
9
+ */
10
+
11
+ import type {
12
+ ConflictContext,
13
+ ConflictRecoveryStrategy,
14
+ ConflictResolution,
15
+ } from './types.js';
16
+
17
+ export class EscalateStrategy implements ConflictRecoveryStrategy {
18
+ readonly name = 'escalate';
19
+ readonly mode = 'async' as const;
20
+
21
+ async recover(ctx: ConflictContext): Promise<ConflictResolution> {
22
+ try {
23
+ ctx.workspaceManager.pauseStream(ctx.streamId, 'awaiting human resolution');
24
+ } catch {
25
+ // Non-fatal — stream may already be in a paused/conflicted state
26
+ }
27
+ const target = (ctx.strategyConfig?.notify as string | undefined) ?? 'human';
28
+ return { kind: 'escalated', escalatedTo: target as 'human' };
29
+ }
30
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Conflict recovery strategies.
3
+ *
4
+ * Register via `WorkspaceManager.registerConflictRecoveryStrategy` (Phase 7b).
5
+ * Selected via per-role YAML `on_conflict_recovery` or team default.
6
+ *
7
+ * Built-ins:
8
+ * - `defer` — no-op; leave conflict record for external resolution
9
+ * - `abandon` — abandon the conflicted stream
10
+ * - `escalate` — pause stream; notify human
11
+ * - `auto-resolve` — git strategies (ours/theirs/union) — scaffold only
12
+ * - `spawn-resolver` — LLM resolver agent (Phase 7b; requires AgentManager)
13
+ *
14
+ * @module workspace/recovery
15
+ * @see docs/conflict-recovery.md
16
+ */
17
+
18
+ export type {
19
+ ConflictContext,
20
+ ConflictRecoveryStrategy,
21
+ ConflictResolution,
22
+ ConflictResolutionMode,
23
+ } from './types.js';
24
+
25
+ export { DeferStrategy } from './defer.js';
26
+ export { AbandonStrategy } from './abandon.js';
27
+ export { EscalateStrategy } from './escalate.js';
28
+ export { AutoResolveStrategy } from './auto-resolve.js';
29
+ export {
30
+ SpawnResolverStrategy,
31
+ createSpawnResolverStrategy,
32
+ type SpawnResolverStrategyOptions,
33
+ } from './spawn-resolver.js';
34
+
35
+ import { DeferStrategy } from './defer.js';
36
+ import { AbandonStrategy } from './abandon.js';
37
+ import { EscalateStrategy } from './escalate.js';
38
+ import { AutoResolveStrategy } from './auto-resolve.js';
39
+ import type { ConflictRecoveryStrategy } from './types.js';
40
+
41
+ /**
42
+ * Build a registry of built-in conflict recovery strategies.
43
+ *
44
+ * Returns a Map keyed by strategy name. Callers plug this into their dispatch
45
+ * layer (Phase 7b). `spawn-resolver` is not included — it requires
46
+ * AgentManager injection and is added by the consumer.
47
+ */
48
+ export function buildBuiltinRecoveryRegistry(): Map<string, ConflictRecoveryStrategy> {
49
+ const map = new Map<string, ConflictRecoveryStrategy>();
50
+ const strategies: ConflictRecoveryStrategy[] = [
51
+ new DeferStrategy(),
52
+ new AbandonStrategy(),
53
+ new EscalateStrategy(),
54
+ new AutoResolveStrategy(),
55
+ ];
56
+ for (const s of strategies) map.set(s.name, s);
57
+ return map;
58
+ }
@@ -0,0 +1,152 @@
1
+ /**
2
+ * `spawn-resolver` conflict recovery strategy.
3
+ *
4
+ * Async strategy that spawns a dedicated resolver agent on the conflicted
5
+ * stream. The resolver reads conflict markers, resolves, commits, and calls
6
+ * the `resolve_conflict` MCP tool — unblocking the original landing.
7
+ *
8
+ * Strategy config:
9
+ * - `role`: string (default 'resolver') — role to spawn
10
+ * - `max_concurrent`: number (default 2) — cap on simultaneous resolvers per stream
11
+ * - `timeout_ms`: number (default 1,800,000 = 30 min) — fallback to escalate
12
+ *
13
+ * Unlike the other built-in strategies, this one requires an `AgentManager`
14
+ * reference at construction time. Callers inject it via the factory. That's
15
+ * why it's not in `buildBuiltinRecoveryRegistry()` — consumers register it
16
+ * explicitly after constructing AgentManager.
17
+ *
18
+ * @module workspace/recovery/spawn-resolver
19
+ * @see docs/conflict-recovery.md §4.3
20
+ */
21
+
22
+ import type { AgentManager } from '../../agent/agent-manager.js';
23
+ import type { SpawnAgentOptions } from '../../agent/types.js';
24
+ import type {
25
+ ConflictContext,
26
+ ConflictRecoveryStrategy,
27
+ ConflictResolution,
28
+ } from './types.js';
29
+
30
+ export interface SpawnResolverStrategyOptions {
31
+ agentManager: AgentManager;
32
+ /** Default role to spawn if not overridden in strategyConfig. */
33
+ defaultRole?: string;
34
+ /** Default timeout in ms. */
35
+ defaultTimeoutMs?: number;
36
+ /** Default concurrency cap per stream. */
37
+ defaultMaxConcurrent?: number;
38
+ }
39
+
40
+ export class SpawnResolverStrategy implements ConflictRecoveryStrategy {
41
+ readonly name = 'spawn-resolver';
42
+ readonly mode = 'async' as const;
43
+
44
+ // Tracks in-progress resolvers per stream to enforce max_concurrent.
45
+ private readonly activeByStream = new Map<string, Set<string>>();
46
+
47
+ constructor(private readonly opts: SpawnResolverStrategyOptions) {}
48
+
49
+ async recover(ctx: ConflictContext): Promise<ConflictResolution> {
50
+ const role =
51
+ (ctx.strategyConfig?.role as string | undefined) ??
52
+ this.opts.defaultRole ??
53
+ 'resolver';
54
+ const timeoutMs =
55
+ (ctx.strategyConfig?.timeout_ms as number | undefined) ??
56
+ this.opts.defaultTimeoutMs ??
57
+ 30 * 60 * 1000;
58
+ const maxConcurrent =
59
+ (ctx.strategyConfig?.max_concurrent as number | undefined) ??
60
+ this.opts.defaultMaxConcurrent ??
61
+ 2;
62
+
63
+ // Concurrency cap
64
+ const active = this.activeByStream.get(ctx.streamId) ?? new Set();
65
+ if (active.size >= maxConcurrent) {
66
+ return {
67
+ kind: 'retry-after',
68
+ backoffMs: 30_000,
69
+ reason: `max concurrent resolvers (${maxConcurrent}) on stream ${ctx.streamId}`,
70
+ };
71
+ }
72
+
73
+ // Spawn the resolver. Injects MACRO_RECOVERY_STRATEGY + MACRO_CONFLICT_ID
74
+ // so the resolve_conflict MCP tool can tag the resolution correctly.
75
+ let resolverAgentId: string;
76
+ try {
77
+ const spawnOpts: SpawnAgentOptions = {
78
+ role,
79
+ task: `Resolve conflict ${ctx.conflictId} on stream ${ctx.streamId}`,
80
+ parent: ctx.landingAgentId,
81
+ capabilities: ['workspace.commit', 'workspace.resolve', 'workspace.read'],
82
+ config: {
83
+ env: {
84
+ MACRO_RECOVERY_STRATEGY: 'spawn-resolver',
85
+ MACRO_CONFLICT_ID: ctx.conflictId,
86
+ },
87
+ },
88
+ };
89
+ const spawned = await this.opts.agentManager.spawn(spawnOpts);
90
+ resolverAgentId = spawned.id;
91
+ } catch (err) {
92
+ return {
93
+ kind: 'failed',
94
+ error: `spawn-resolver: failed to spawn resolver — ${
95
+ err instanceof Error ? err.message : String(err)
96
+ }`,
97
+ };
98
+ }
99
+
100
+ active.add(resolverAgentId);
101
+ this.activeByStream.set(ctx.streamId, active);
102
+
103
+ // Wait for conflict.resolved event OR timeout. The resolver agent calls
104
+ // `resolve_conflict` MCP tool, which invokes workspaceManager.resolveConflict,
105
+ // which emits 'conflict:resolved' event.
106
+ try {
107
+ const resolution = await this.awaitResolution(ctx, timeoutMs);
108
+ return resolution;
109
+ } finally {
110
+ active.delete(resolverAgentId);
111
+ if (active.size === 0) this.activeByStream.delete(ctx.streamId);
112
+ }
113
+ }
114
+
115
+ private awaitResolution(
116
+ ctx: ConflictContext,
117
+ timeoutMs: number
118
+ ): Promise<ConflictResolution> {
119
+ return new Promise((resolve) => {
120
+ const unsubscribe = ctx.workspaceManager.onEvent((event) => {
121
+ if (
122
+ event.type === 'conflict:resolved' &&
123
+ event.data.conflictId === ctx.conflictId
124
+ ) {
125
+ clearTimeout(timer);
126
+ unsubscribe();
127
+ resolve({
128
+ kind: 'resolved',
129
+ resolutionCommit: (event.data.resolutionCommit as string) ?? 'unknown',
130
+ });
131
+ }
132
+ });
133
+
134
+ const timer = setTimeout(() => {
135
+ unsubscribe();
136
+ resolve({
137
+ kind: 'escalated',
138
+ escalatedTo: 'human',
139
+ });
140
+ }, timeoutMs);
141
+ });
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Factory for SpawnResolverStrategy — requires AgentManager.
147
+ */
148
+ export function createSpawnResolverStrategy(
149
+ opts: SpawnResolverStrategyOptions
150
+ ): SpawnResolverStrategy {
151
+ return new SpawnResolverStrategy(opts);
152
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Conflict recovery types.
3
+ *
4
+ * Parallel to LandingStrategy. Dispatched from the agent's done() flow when
5
+ * a landing returns a conflict. Registered globally; selected via per-role
6
+ * YAML `on_conflict_recovery` or team default.
7
+ *
8
+ * @module workspace/recovery/types
9
+ * @see docs/conflict-recovery.md
10
+ */
11
+
12
+ import type { AgentId, StreamId, Principal } from '../types-v3.js';
13
+ import type { WorkspaceManager } from '../types.js';
14
+
15
+ export interface ConflictContext {
16
+ conflictId: string;
17
+ streamId: StreamId;
18
+ sourceCommit?: string;
19
+ targetCommit?: string;
20
+ targetStreamId?: StreamId;
21
+ paths: string[];
22
+ operation: 'merge' | 'sync' | 'rebase' | 'cascade';
23
+ landingAgentId?: AgentId;
24
+ /**
25
+ * Worktree path where the conflict occurred. Required for
26
+ * `auto-resolve` and any other strategy that needs to replay git
27
+ * operations. Optional because some strategies (`defer`, `abandon`,
28
+ * `escalate`) don't need filesystem access.
29
+ */
30
+ worktree?: string;
31
+ recoveryDepth: number;
32
+ strategyConfig?: Record<string, unknown>;
33
+ workspaceManager: WorkspaceManager;
34
+ }
35
+
36
+ export type ConflictResolution =
37
+ | { kind: 'resolved'; resolutionCommit: string }
38
+ | { kind: 'deferred'; reason: string }
39
+ | { kind: 'abandoned'; streamId: StreamId; reason: string }
40
+ | { kind: 'escalated'; escalatedTo: Principal | 'human' }
41
+ | { kind: 'retry-after'; backoffMs: number; reason: string }
42
+ | { kind: 'failed'; error: string };
43
+
44
+ export type ConflictResolutionMode = 'sync' | 'async';
45
+
46
+ export interface ConflictRecoveryStrategy {
47
+ readonly name: string;
48
+ readonly mode: ConflictResolutionMode;
49
+
50
+ canHandle?(ctx: ConflictContext): boolean;
51
+ recover(ctx: ConflictContext): Promise<ConflictResolution>;
52
+ initialize?(): Promise<void>;
53
+ close?(): Promise<void>;
54
+ }