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,205 @@
1
+ /**
2
+ * Cascade Action Handler — receives hub→runtime commands.
3
+ *
4
+ * Listens for `x-cascade/request.*` notifications from the OpenHive hub
5
+ * and dispatches to the GitCascadeAdapter. This is the inbound counterpart
6
+ * to the CascadeBridge (which handles outbound events).
7
+ *
8
+ * Actions are fire-and-forget from the hub's perspective: the hub sends
9
+ * the notification and the UI updates reactively when the resulting
10
+ * `x-cascade/stream.*` event flows back through the bridge.
11
+ *
12
+ * @module map/cascade-action-handler
13
+ */
14
+
15
+ import type { GitCascadeAdapter } from '../workspace/git-cascade-adapter.js';
16
+
17
+ export interface CascadeActionConnection {
18
+ onNotification(
19
+ method: string,
20
+ handler: (params: unknown) => void | Promise<void>,
21
+ ): void;
22
+ offNotification(
23
+ method: string,
24
+ handler: (params: unknown) => void | Promise<void>,
25
+ ): void;
26
+ }
27
+
28
+ const REQUEST_METHODS = {
29
+ MERGE: 'x-cascade/request.merge',
30
+ ABANDON: 'x-cascade/request.abandon',
31
+ PAUSE: 'x-cascade/request.pause',
32
+ RESUME: 'x-cascade/request.resume',
33
+ RESOLVE: 'x-cascade/request.resolve',
34
+ PUSH: 'x-cascade/request.push',
35
+ COMMIT: 'x-cascade/request.commit',
36
+ } as const;
37
+
38
+ /**
39
+ * Register cascade action handlers on the MAP connection.
40
+ * Returns a cleanup function that removes all handlers.
41
+ */
42
+ export function setupCascadeActionHandlers(
43
+ connection: CascadeActionConnection,
44
+ adapter: GitCascadeAdapter,
45
+ ): () => void {
46
+ const handlers: Array<{
47
+ method: string;
48
+ handler: (params: unknown) => void | Promise<void>;
49
+ }> = [];
50
+
51
+ const register = (
52
+ method: string,
53
+ handler: (params: unknown) => void | Promise<void>,
54
+ ): void => {
55
+ connection.onNotification(method, handler);
56
+ handlers.push({ method, handler });
57
+ };
58
+
59
+ /** Find the first worktree checked out on a given stream. */
60
+ function findWorktreeForStream(streamId: string): string | null {
61
+ const wts = adapter.listWorktrees();
62
+ const match = wts.find((wt) => wt.currentStream === streamId);
63
+ return match?.path ?? null;
64
+ }
65
+
66
+ // ── Merge ─────────────────────────────────────────────────────────
67
+ register(REQUEST_METHODS.MERGE, (params: unknown) => {
68
+ const p = params as { stream_id?: string; target_stream_id?: string };
69
+ if (!p?.stream_id) return;
70
+
71
+ const stream = adapter.getStream(p.stream_id);
72
+ const targetStreamId = p.target_stream_id ?? stream?.parentStream;
73
+ if (!targetStreamId) return;
74
+
75
+ const worktreePath = findWorktreeForStream(p.stream_id);
76
+ if (!worktreePath) return;
77
+
78
+ try {
79
+ adapter.mergeStream({
80
+ sourceStream: p.stream_id,
81
+ targetStream: targetStreamId,
82
+ agentId: 'hub-request',
83
+ worktree: worktreePath,
84
+ });
85
+ } catch {
86
+ // Non-fatal — the resulting event (or conflict) will surface via the bridge
87
+ }
88
+ });
89
+
90
+ // ── Abandon ───────────────────────────────────────────────────────
91
+ register(REQUEST_METHODS.ABANDON, (params: unknown) => {
92
+ const p = params as { stream_id?: string; reason?: string };
93
+ if (!p?.stream_id) return;
94
+ try {
95
+ adapter.abandonStream(p.stream_id, { reason: p.reason ?? 'hub-request' });
96
+ } catch { /* non-fatal */ }
97
+ });
98
+
99
+ // ── Pause ─────────────────────────────────────────────────────────
100
+ register(REQUEST_METHODS.PAUSE, (params: unknown) => {
101
+ const p = params as { stream_id?: string; reason?: string };
102
+ if (!p?.stream_id) return;
103
+ try {
104
+ adapter.pauseStream(p.stream_id, p.reason);
105
+ } catch { /* non-fatal */ }
106
+ });
107
+
108
+ // ── Resume ────────────────────────────────────────────────────────
109
+ register(REQUEST_METHODS.RESUME, (params: unknown) => {
110
+ const p = params as { stream_id?: string };
111
+ if (!p?.stream_id) return;
112
+ try {
113
+ adapter.resumeStream(p.stream_id);
114
+ } catch { /* non-fatal */ }
115
+ });
116
+
117
+ // ── Resolve conflict ──────────────────────────────────────────────
118
+ register(REQUEST_METHODS.RESOLVE, (params: unknown) => {
119
+ const p = params as {
120
+ stream_id?: string;
121
+ conflict_id?: string;
122
+ strategy?: string;
123
+ };
124
+ if (!p?.stream_id || !p?.conflict_id) return;
125
+ try {
126
+ adapter.resolveConflict({
127
+ conflictId: p.conflict_id,
128
+ resolution: {
129
+ method: (p.strategy as 'ours' | 'theirs') ?? 'ours',
130
+ resolvedBy: 'hub-request',
131
+ },
132
+ });
133
+ } catch { /* non-fatal */ }
134
+ });
135
+
136
+ // ── Push ───────────────────────────────────────────────────────────
137
+ register(REQUEST_METHODS.PUSH, (params: unknown) => {
138
+ const p = params as {
139
+ stream_id?: string;
140
+ remote?: string;
141
+ target_ref?: string;
142
+ };
143
+ if (!p?.stream_id) return;
144
+
145
+ const worktreePath = findWorktreeForStream(p.stream_id);
146
+ if (!worktreePath) return;
147
+
148
+ const remote = p.remote ?? 'origin';
149
+ const streamBranch = `stream/${p.stream_id}`;
150
+ const targetRef = p.target_ref ?? streamBranch;
151
+
152
+ try {
153
+ const { execSync } = require('child_process');
154
+ execSync(`git push ${remote} ${streamBranch}:refs/heads/${targetRef}`, {
155
+ cwd: worktreePath,
156
+ stdio: 'pipe',
157
+ encoding: 'utf-8',
158
+ });
159
+ // Emit pushed event so the hub records it
160
+ adapter.notifyStreamPushed?.({
161
+ streamId: p.stream_id,
162
+ agentId: 'hub-request',
163
+ pushedCommit: execSync('git rev-parse HEAD', {
164
+ cwd: worktreePath,
165
+ encoding: 'utf-8',
166
+ }).trim(),
167
+ remote,
168
+ remoteRef: targetRef,
169
+ strategy: 'hub-push',
170
+ });
171
+ } catch { /* non-fatal — push failure is reported via absence of pushed event */ }
172
+ });
173
+
174
+ // ── Commit ────────────────────────────────────────────────────────
175
+ register(REQUEST_METHODS.COMMIT, (params: unknown) => {
176
+ const p = params as {
177
+ stream_id?: string;
178
+ message?: string;
179
+ metadata?: Record<string, unknown>;
180
+ };
181
+ if (!p?.stream_id) return;
182
+
183
+ const worktreePath = findWorktreeForStream(p.stream_id);
184
+ if (!worktreePath) return;
185
+
186
+ const message = p.message ?? 'checkpoint (hub-requested)';
187
+ try {
188
+ adapter.commitChanges({
189
+ streamId: p.stream_id,
190
+ agentId: 'hub-request',
191
+ worktree: worktreePath,
192
+ message,
193
+ metadata: p.metadata,
194
+ });
195
+ } catch { /* non-fatal — nothing to commit, or stream conflicted */ }
196
+ });
197
+
198
+ // ── Cleanup ───────────────────────────────────────────────────────
199
+ return () => {
200
+ for (const { method, handler } of handlers) {
201
+ connection.offNotification(method, handler);
202
+ }
203
+ handlers.length = 0;
204
+ };
205
+ }
@@ -0,0 +1,339 @@
1
+ /**
2
+ * Cascade Bridge — forwards GitCascadeAdapter events to the MAP hub as
3
+ * `x-cascade/*` notifications.
4
+ *
5
+ * macro-agent's GitCascadeAdapter maintains a structured `GitCascadeEvent`
6
+ * stream (translated from git-cascade's native `x-cascade/*` emissions + local
7
+ * adapter events). This bridge subscribes to that stream and translates
8
+ * **back** to `x-cascade/*` MAP method calls so the OpenHive hub receives the
9
+ * canonical event schema.
10
+ *
11
+ * The round-trip (git-cascade → adapter translation → bridge re-translation)
12
+ * is deliberate: it preserves the adapter's internal abstraction (other
13
+ * macro-agent code consumes `GitCascadeEvent`, not raw MAP params) while
14
+ * guaranteeing the hub sees the same schema git-cascade defines. The bridge
15
+ * is the only place that knows about both shapes.
16
+ *
17
+ * Standalone-safe: when `connection.isConnected` is false, events are
18
+ * dropped. macro-agent continues to work without an OpenHive hub.
19
+ *
20
+ * @module map/cascade-bridge
21
+ */
22
+
23
+ import { CASCADE_METHODS } from 'git-cascade/events';
24
+
25
+ // Fallback method names for events not yet present in the installed
26
+ // git-cascade version (`STREAM_PAUSED`, `STREAM_RESUMED`,
27
+ // `STREAM_ROLLED_BACK` were added after 0.0.7). When the dep is upgraded,
28
+ // collapse these back into `CASCADE_METHODS.*` for a single source of truth.
29
+ const X_CASCADE_STREAM_PAUSED = 'x-cascade/stream.paused' as const;
30
+ const X_CASCADE_STREAM_RESUMED = 'x-cascade/stream.resumed' as const;
31
+ const X_CASCADE_STREAM_ROLLED_BACK = 'x-cascade/stream.rolled_back' as const;
32
+ import type { LifecycleBridgeConnection } from './lifecycle-bridge.js';
33
+ import type {
34
+ GitCascadeAdapter,
35
+ GitCascadeEvent,
36
+ } from '../workspace/git-cascade-adapter.js';
37
+
38
+ export interface CascadeBridgeDisposable {
39
+ /** Unsubscribe from the adapter event stream. Safe to call multiple times. */
40
+ dispose: () => void;
41
+ }
42
+
43
+ export interface CascadeBridgeOptions {
44
+ /** Enable debug logging when events fail to forward. Defaults to false. */
45
+ verbose?: boolean;
46
+ }
47
+
48
+ /**
49
+ * Create a cascade bridge.
50
+ *
51
+ * Subscribes to `adapter.onEvent()` and forwards a fixed subset of events
52
+ * (the ones that have a canonical `x-cascade/*` method name) as MAP
53
+ * notifications via `connection.callExtension()`. Events with no MAP
54
+ * counterpart (`stream:forked`, `worktree:*`, `task:*`, `mergeQueue:*`, etc.)
55
+ * are silently ignored.
56
+ *
57
+ * @returns A disposable that unsubscribes from the adapter stream.
58
+ */
59
+ export function createCascadeBridge(
60
+ connection: LifecycleBridgeConnection,
61
+ adapter: GitCascadeAdapter,
62
+ options: CascadeBridgeOptions = {}
63
+ ): CascadeBridgeDisposable {
64
+ const verbose = options.verbose ?? false;
65
+
66
+ const unsubscribe = adapter.onEvent((event: GitCascadeEvent) => {
67
+ if (!connection.isConnected) return;
68
+
69
+ const mapped = translate(event);
70
+ if (!mapped) return;
71
+
72
+ // Fire-and-forget: never block the adapter's event loop on a MAP RPC.
73
+ // Errors are swallowed to preserve standalone-safety; they indicate the
74
+ // hub is unreachable or the method isn't registered, neither of which
75
+ // should break local cascade operations.
76
+ void connection
77
+ .callExtension(mapped.method, mapped.params)
78
+ .catch((err) => {
79
+ if (verbose) {
80
+ // eslint-disable-next-line no-console
81
+ console.warn(
82
+ `[cascade-bridge] failed to forward ${event.type} as ${mapped.method}:`,
83
+ err instanceof Error ? err.message : err
84
+ );
85
+ }
86
+ });
87
+ });
88
+
89
+ return { dispose: unsubscribe };
90
+ }
91
+
92
+ // ─────────────────────────────────────────────────────────────────────────────
93
+ // GitCascadeEvent → x-cascade/* translation
94
+ // ─────────────────────────────────────────────────────────────────────────────
95
+
96
+ interface TranslatedCall {
97
+ method: string;
98
+ params: Record<string, unknown>;
99
+ }
100
+
101
+ /**
102
+ * Translate a `GitCascadeEvent` into a MAP method call.
103
+ *
104
+ * Covers the 7 event types that have canonical MAP method names. Returns
105
+ * `null` for events that are macro-agent-internal (worktree/task/mergeQueue
106
+ * lifecycle, local-only forks, etc.).
107
+ *
108
+ * Field names flip from camelCase (macro-agent internal) back to snake_case
109
+ * (MAP wire format). The bridge is intentionally conservative — it only
110
+ * emits fields present on the event, letting the hub back-fill/ignore as
111
+ * needed.
112
+ */
113
+ function translate(event: GitCascadeEvent): TranslatedCall | null {
114
+ const d = event.data;
115
+
116
+ switch (event.type) {
117
+ case 'stream:created':
118
+ return {
119
+ method: CASCADE_METHODS.STREAM_OPENED,
120
+ params: {
121
+ stream_id: d.streamId,
122
+ name: d.name,
123
+ agent_id: d.agentId,
124
+ base_commit: d.baseCommit,
125
+ parent_stream: d.parentStream,
126
+ branch_name: d.branchName,
127
+ metadata: d.metadata,
128
+ },
129
+ };
130
+
131
+ case 'stream:committed':
132
+ return {
133
+ method: CASCADE_METHODS.STREAM_COMMITTED,
134
+ params: {
135
+ stream_id: d.streamId,
136
+ commit_hash: d.commit,
137
+ change_id: d.changeId,
138
+ agent_id: d.agentId,
139
+ message_summary: d.messageSummary,
140
+ files_touched: d.filesTouched,
141
+ parent_commit: d.parentCommit,
142
+ metadata: d.metadata,
143
+ },
144
+ };
145
+
146
+ case 'stream:merged':
147
+ return {
148
+ method: CASCADE_METHODS.STREAM_MERGED,
149
+ params: {
150
+ source_stream_id: d.sourceStreamId,
151
+ target_stream_id: d.targetStreamId,
152
+ merge_commit: d.mergeCommit,
153
+ agent_id: d.agentId,
154
+ strategy: d.strategy,
155
+ source_commit: d.sourceCommit,
156
+ metadata: d.metadata,
157
+ },
158
+ };
159
+
160
+ case 'stream:conflicted':
161
+ return {
162
+ method: CASCADE_METHODS.STREAM_CONFLICTED,
163
+ params: {
164
+ stream_id: d.streamId,
165
+ conflict_id: d.conflictId,
166
+ conflicted_files: d.conflictedFiles,
167
+ agent_id: d.agentId,
168
+ conflicting_commit: d.conflictingCommit,
169
+ target_commit: d.targetCommit,
170
+ source: d.source,
171
+ metadata: d.metadata,
172
+ },
173
+ };
174
+
175
+ case 'conflict:resolved':
176
+ // The adapter emits this from two sources: workspace-manager's local
177
+ // resolveConflict path (carries resolvedBy + resolutionCommit) AND the
178
+ // forwarded git-cascade stream.conflict_resolved (carries the explicit
179
+ // resolution_method). Bridge only forwards events with conflict_id +
180
+ // stream_id present (the cascade-driven shape).
181
+ if (!d.streamId || !d.conflictId) return null;
182
+ return {
183
+ method: CASCADE_METHODS.STREAM_CONFLICT_RESOLVED,
184
+ params: {
185
+ stream_id: d.streamId,
186
+ conflict_id: d.conflictId,
187
+ resolution_method:
188
+ (d.resolutionMethod as string | undefined) ??
189
+ (d.resolvedBy ? 'agent' : 'manual'),
190
+ resolved_by: d.resolvedBy,
191
+ resolution_summary: d.resolutionSummary,
192
+ metadata: d.metadata,
193
+ },
194
+ };
195
+
196
+ case 'stream:abandoned':
197
+ return {
198
+ method: CASCADE_METHODS.STREAM_ABANDONED,
199
+ params: {
200
+ stream_id: d.streamId,
201
+ reason: d.reason,
202
+ cascade: d.cascade,
203
+ metadata: d.metadata,
204
+ },
205
+ };
206
+
207
+ case 'stream:pushed':
208
+ // Trunk-style push to a remote (direct-push / optimistic-push). Hub
209
+ // sees this as the "merged" equivalent for non-stream targets.
210
+ if (!d.streamId || !d.pushedCommit || !d.remoteRef) return null;
211
+ return {
212
+ method: CASCADE_METHODS.STREAM_PUSHED,
213
+ params: {
214
+ stream_id: d.streamId,
215
+ agent_id: d.agentId,
216
+ pushed_commit: d.pushedCommit,
217
+ remote: d.remote ?? 'origin',
218
+ remote_ref: d.remoteRef,
219
+ strategy: d.strategy,
220
+ metadata: d.metadata,
221
+ },
222
+ };
223
+
224
+ case 'cascade:rebased':
225
+ return {
226
+ method: CASCADE_METHODS.CASCADE_REBASED,
227
+ params: {
228
+ stream_id: d.streamId,
229
+ agent_id: d.agentId,
230
+ triggered_by_stream_id: d.triggeredByStreamId,
231
+ triggered_by_agent_id: d.triggeredByAgentId,
232
+ new_base_commit: d.newBaseCommit,
233
+ new_head: d.newHead,
234
+ new_commits: d.newCommits,
235
+ metadata: d.metadata,
236
+ },
237
+ };
238
+
239
+ case 'cascade:completed':
240
+ return {
241
+ method: CASCADE_METHODS.CASCADE_COMPLETED,
242
+ params: {
243
+ root_stream_id: d.rootStreamId,
244
+ agent_id: d.agentId,
245
+ strategy: d.strategy,
246
+ updated_streams: d.updatedStreams,
247
+ failed_streams: d.failedStreams,
248
+ skipped_streams: d.skippedStreams,
249
+ deferred_streams: d.deferredStreams,
250
+ metadata: d.metadata,
251
+ },
252
+ };
253
+
254
+ case 'mergeQueue:added':
255
+ if (!d.entryId || !d.streamId) return null;
256
+ return {
257
+ method: CASCADE_METHODS.QUEUE_ADDED,
258
+ params: {
259
+ entry_id: d.entryId,
260
+ stream_id: d.streamId,
261
+ target_branch: (d.targetBranch as string | undefined) ?? 'main',
262
+ metadata: d.metadata,
263
+ },
264
+ };
265
+
266
+ case 'mergeQueue:ready':
267
+ if (!d.entryId || !d.streamId) return null;
268
+ return {
269
+ method: CASCADE_METHODS.QUEUE_READY,
270
+ params: {
271
+ entry_id: d.entryId,
272
+ stream_id: d.streamId,
273
+ target_branch: (d.targetBranch as string | undefined) ?? 'main',
274
+ },
275
+ };
276
+
277
+ case 'mergeQueue:cancelled':
278
+ if (!d.entryId || !d.streamId) return null;
279
+ return {
280
+ method: CASCADE_METHODS.QUEUE_CANCELLED,
281
+ params: {
282
+ entry_id: d.entryId,
283
+ stream_id: d.streamId,
284
+ target_branch: (d.targetBranch as string | undefined) ?? 'main',
285
+ reason: d.reason,
286
+ },
287
+ };
288
+
289
+ case 'mergeQueue:removed':
290
+ if (!d.entryId || !d.streamId) return null;
291
+ return {
292
+ method: CASCADE_METHODS.QUEUE_REMOVED,
293
+ params: {
294
+ entry_id: d.entryId,
295
+ stream_id: d.streamId,
296
+ target_branch: (d.targetBranch as string | undefined) ?? 'main',
297
+ outcome: d.outcome,
298
+ },
299
+ };
300
+
301
+ case 'stream:paused':
302
+ if (!d.streamId) return null;
303
+ return {
304
+ method: X_CASCADE_STREAM_PAUSED,
305
+ params: {
306
+ stream_id: d.streamId,
307
+ reason: d.reason,
308
+ },
309
+ };
310
+
311
+ case 'stream:resumed':
312
+ if (!d.streamId) return null;
313
+ return {
314
+ method: X_CASCADE_STREAM_RESUMED,
315
+ params: {
316
+ stream_id: d.streamId,
317
+ },
318
+ };
319
+
320
+ case 'stream:rolled_back':
321
+ if (!d.streamId) return null;
322
+ return {
323
+ method: X_CASCADE_STREAM_ROLLED_BACK,
324
+ params: {
325
+ stream_id: d.streamId,
326
+ strategy: d.strategy,
327
+ target: d.target,
328
+ new_head: d.newHead,
329
+ },
330
+ };
331
+
332
+ // Local-only events with no MAP counterpart.
333
+ // 'stream:updated', 'stream:forked', 'worktree:*', 'task:*',
334
+ // 'change:*', 'conflict:*' (legacy local-only variant — cascade-bridge
335
+ // handles the cascade-driven 'conflict:resolved' separately above)
336
+ default:
337
+ return null;
338
+ }
339
+ }
@@ -75,12 +75,24 @@ export function setupCoordinationHandlers(
75
75
  if (!p?.title) return;
76
76
 
77
77
  try {
78
- // Create task in opentasks
78
+ // Extract tags and metadata from OpenHive context
79
+ const context = p.context ?? {};
80
+ const tags = Array.isArray(context.tags) ? context.tags as string[] : undefined;
81
+ const metadata: Record<string, unknown> = {
82
+ ...context,
83
+ ...(p.assigned_by ? { assigned_by: p.assigned_by } : {}),
84
+ ...(p.deadline ? { deadline: p.deadline } : {}),
85
+ };
86
+ // Remove tags from metadata (already a top-level field)
87
+ delete metadata.tags;
88
+
79
89
  const taskId = await tasksAdapter.createTask({
80
90
  title: p.title,
81
91
  content: p.description,
82
92
  assignee: p.assigned_to,
93
+ tags,
83
94
  priority: p.priority === "critical" ? 1 : p.priority === "high" ? 2 : p.priority === "low" ? 4 : 3,
95
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
84
96
  });
85
97
 
86
98
  // Optionally spawn an agent to work on the task
@@ -36,9 +36,27 @@ export function createLifecycleBridge(
36
36
  agentStore: AgentStore,
37
37
  scope: string,
38
38
  taskBridge?: TaskBridge,
39
+ getLocalMapId?: (localAgentId: string) => string | undefined,
39
40
  ): { callback: AgentLifecycleCallback; cleanup: () => Promise<void> } {
40
41
  const registered = new Map<string, RegisteredAgent>();
41
42
 
43
+ /**
44
+ * Poll for the local MAP server's assigned ID for an agent.
45
+ * The local MAP server and the lifecycle bridge both listen to the same
46
+ * lifecycle callback, so they may fire in any order. Poll briefly to handle
47
+ * the race.
48
+ */
49
+ async function waitForLocalMapId(localAgentId: string, timeoutMs = 500): Promise<string | undefined> {
50
+ if (!getLocalMapId) return undefined;
51
+ const deadline = Date.now() + timeoutMs;
52
+ while (Date.now() < deadline) {
53
+ const id = getLocalMapId(localAgentId);
54
+ if (id) return id;
55
+ await new Promise((r) => setTimeout(r, 20));
56
+ }
57
+ return getLocalMapId(localAgentId);
58
+ }
59
+
42
60
  const callback: AgentLifecycleCallback = (event) => {
43
61
  if (!connection.isConnected) return;
44
62
 
@@ -61,29 +79,46 @@ export function createLifecycleBridge(
61
79
  }
62
80
 
63
81
  // Register agent with MAP hub (use map/agents/register to preserve
64
- // per-agent capabilities; map/agents/spawn drops them)
65
- connection
66
- .callExtension("map/agents/register", {
67
- name,
68
- role,
69
- capabilities,
70
- metadata: {
71
- localAgentId: agent.id,
72
- parent: (agent as any).parent_id ?? undefined,
73
- team: (agent as any).team ?? undefined,
74
- cwd: (agent as any).cwd ?? undefined,
75
- },
76
- })
77
- .then((result: any) => {
82
+ // per-agent capabilities; map/agents/spawn drops them).
83
+ // Include the local MAP server's ID in metadata so clients can route
84
+ // ACP messages to the correct agent on the macro-agent's own MAP server.
85
+ // Also include provider_session_id so OpenHive can find the underlying
86
+ // Claude Code JSONL transcript on disk for history recovery.
87
+ const agentMetadata = (agent as any).metadata as Record<string, unknown> | undefined;
88
+ const providerSessionId =
89
+ typeof agentMetadata?.provider_session_id === "string"
90
+ ? agentMetadata.provider_session_id
91
+ : undefined;
92
+ (async () => {
93
+ const peerMapId = await waitForLocalMapId(agent.id);
94
+ try {
95
+ const result: any = await connection.callExtension("map/agents/register", {
96
+ name,
97
+ role,
98
+ capabilities,
99
+ metadata: {
100
+ // From the hub's perspective these IDs identify this agent on
101
+ // the macro-agent (peer) side. `peerAgentId` is macro-agent's
102
+ // internal store id; `peerMapId` is its local MAP server ULID.
103
+ // Hub callers use these to address the agent in routing
104
+ // (ACP streams target peerMapId, lifecycle ops use peerAgentId).
105
+ peerAgentId: agent.id,
106
+ peerMapId,
107
+ provider_session_id: providerSessionId,
108
+ parent: (agent as any).parent_id ?? undefined,
109
+ team: (agent as any).team ?? undefined,
110
+ cwd: (agent as any).cwd ?? undefined,
111
+ },
112
+ });
78
113
  // Track the MAP-assigned agent ID for unregistration
79
114
  const mapId = result?.agent?.id ?? result?.id;
80
115
  if (mapId) {
81
116
  entry.mapId = mapId;
82
117
  }
83
- })
84
- .catch(() => {
118
+ } catch {
85
119
  // Silent — MAP hub may be temporarily unavailable
86
- });
120
+ }
121
+ })();
87
122
 
88
123
  // Bridge task creation if agent has a task
89
124
  if (taskBridge && (agent as any).task_id) {