macro-agent 0.1.7 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/CLAUDE.md +179 -38
  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 -71
  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 +34 -37
  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 -8
  55. package/dist/map/lifecycle-bridge.d.ts.map +1 -1
  56. package/dist/map/lifecycle-bridge.js +76 -22
  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 -4
  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 +71 -11
  197. package/src/agent/__tests__/task-ref-resolution.test.ts +231 -0
  198. package/src/agent/agent-manager-v2.ts +293 -77
  199. package/src/agent/agent-manager.ts +14 -0
  200. package/src/agent/types.ts +16 -2
  201. package/src/boot-v2.ts +87 -36
  202. package/src/cli/index.ts +61 -0
  203. package/src/cognitive/__tests__/macro-agent-backend.test.ts +47 -5
  204. package/src/cognitive/macro-agent-backend.ts +45 -29
  205. package/src/integrations/skilltree.ts +1 -0
  206. package/src/lifecycle/cleanup.ts +52 -3
  207. package/src/lifecycle/handlers-v2.ts +40 -3
  208. package/src/lifecycle/types.ts +12 -0
  209. package/src/map/__tests__/cascade-bridge.test.ts +229 -0
  210. package/src/map/__tests__/lifecycle-bridge.test.ts +165 -22
  211. package/src/map/acp-bridge.ts +26 -3
  212. package/src/map/cascade-bridge.ts +301 -0
  213. package/src/map/lifecycle-bridge.ts +77 -27
  214. package/src/map/server.ts +47 -6
  215. package/src/map/sidecar.ts +31 -3
  216. package/src/map/types.ts +20 -0
  217. package/src/mcp/tools/done-v2.ts +9 -0
  218. package/src/teams/team-manager-v2.ts +37 -0
  219. package/src/teams/team-runtime-v2.ts +23 -3
  220. package/src/workspace/__tests__/{dataplane-adapter.test.ts → git-cascade-adapter.test.ts} +209 -14
  221. package/src/workspace/__tests__/self-driving-yaml.test.ts +114 -0
  222. package/src/workspace/__tests__/shared-worktree-refcount.test.ts +154 -0
  223. package/src/workspace/__tests__/standalone-mode.test.ts +118 -0
  224. package/src/workspace/__tests__/workspace-manager-v3.test.ts +245 -0
  225. package/src/workspace/__tests__/yaml-schema.test.ts +210 -0
  226. package/src/workspace/config.ts +11 -11
  227. package/src/workspace/git-cascade-adapter.ts +1186 -0
  228. package/src/workspace/index.ts +11 -11
  229. package/src/workspace/landing/__tests__/strategies.test.ts +142 -0
  230. package/src/workspace/landing/direct-push.ts +91 -0
  231. package/src/workspace/landing/index.ts +40 -0
  232. package/src/workspace/landing/merge-to-parent.ts +228 -0
  233. package/src/workspace/landing/optimistic-push.ts +36 -0
  234. package/src/workspace/landing/queue-to-branch.ts +108 -0
  235. package/src/workspace/merge-queue/merge-queue.ts +10 -0
  236. package/src/workspace/merge-queue/types.ts +16 -2
  237. package/src/workspace/pool/__tests__/worktree-pool.integration.test.ts +5 -5
  238. package/src/workspace/pool/types.ts +1 -0
  239. package/src/workspace/pool/worktree-pool.ts +1 -0
  240. package/src/workspace/recovery/__tests__/auto-resolve-integration.test.ts +127 -0
  241. package/src/workspace/recovery/__tests__/spawn-resolver.test.ts +139 -0
  242. package/src/workspace/recovery/__tests__/strategies.test.ts +145 -0
  243. package/src/workspace/recovery/abandon.ts +51 -0
  244. package/src/workspace/recovery/auto-resolve.ts +119 -0
  245. package/src/workspace/recovery/defer.ts +23 -0
  246. package/src/workspace/recovery/escalate.ts +30 -0
  247. package/src/workspace/recovery/index.ts +58 -0
  248. package/src/workspace/recovery/spawn-resolver.ts +145 -0
  249. package/src/workspace/recovery/types.ts +54 -0
  250. package/src/workspace/topology/__tests__/yaml-driven.test.ts +345 -0
  251. package/src/workspace/topology/index.ts +18 -0
  252. package/src/workspace/topology/no-workspace.ts +39 -0
  253. package/src/workspace/topology/types.ts +116 -0
  254. package/src/workspace/topology/yaml-driven.ts +316 -0
  255. package/src/workspace/types-v3.ts +155 -0
  256. package/src/workspace/types.ts +191 -20
  257. package/src/workspace/workspace-manager.ts +474 -19
  258. package/src/workspace/yaml-schema.ts +216 -0
  259. package/src/workspace/dataplane-adapter.ts +0 -546
package/src/boot-v2.ts CHANGED
@@ -121,6 +121,52 @@ export interface BootV2Config {
121
121
  };
122
122
  };
123
123
 
124
+ /**
125
+ * Cascade event binding config. Controls how cascade events emitted by
126
+ * git-cascade-backed agents get tagged with external task references for
127
+ * hub projection (changelog, task↔stream binding). Independent of MAP
128
+ * transport: cascade events are data/identity, not transport.
129
+ */
130
+ cascade?: {
131
+ /**
132
+ * Default OpenTasks resource ID for this swarm. When set, the agent
133
+ * manager auto-builds `taskRef = { resource_id, node_id: task_id }`
134
+ * for spawned agents so cascade events carry the binding without
135
+ * callers constructing refs by hand.
136
+ *
137
+ * Leave undefined if:
138
+ * - The swarm touches multiple opentasks graphs (use `resolveTaskRef`).
139
+ * - Every caller sets `SpawnAgentOptions.taskRef` explicitly.
140
+ * - You don't care about hub task↔stream binding.
141
+ */
142
+ taskResourceId?: string;
143
+
144
+ /**
145
+ * Custom resolver for multi-graph deployments. Called at every spawn;
146
+ * return a `TaskRef` to set the binding or `undefined` to skip.
147
+ * Precedence: explicit `SpawnAgentOptions.taskRef` > `resolveTaskRef` >
148
+ * `taskResourceId` fallback (combined with `spawnOptions.task_id`).
149
+ *
150
+ * Keep implementations cheap — this runs on every spawn.
151
+ *
152
+ * @example
153
+ * resolveTaskRef: (opts) => {
154
+ * const graph = graphForCwd(opts.cwd ?? process.cwd());
155
+ * return graph ? { resource_id: graph.resourceId, node_id: String(opts.task_id) } : undefined;
156
+ * }
157
+ */
158
+ resolveTaskRef?: (
159
+ spawnOptions: import("./agent/types.js").SpawnAgentOptions
160
+ ) => import("git-cascade/events").TaskRef | undefined;
161
+
162
+ /**
163
+ * Override the default `x-cascade` event prefix. Useful for branded
164
+ * deployments or isolating cascade namespaces in testing. Affects all
165
+ * events emitted by the tracker embedded in this swarm.
166
+ */
167
+ eventPrefix?: string;
168
+ };
169
+
124
170
  /** minimem (agent memory) — registers as MCP server for all agents */
125
171
  minimem?: {
126
172
  enabled?: boolean;
@@ -270,6 +316,8 @@ export async function bootV2(
270
316
  serverUrl: config.serverUrl,
271
317
  serverToken: config.serverToken,
272
318
  controlSocketPath,
319
+ taskResourceId: config.cascade?.taskResourceId,
320
+ resolveTaskRef: config.cascade?.resolveTaskRef,
273
321
  }
274
322
  );
275
323
 
@@ -344,23 +392,25 @@ export async function bootV2(
344
392
  }, HEALTH_CHECK_INTERVAL_MS);
345
393
  healthCheckTimer.unref(); // Don't prevent process exit
346
394
 
395
+ // Shared mutable system reference — passed to ACP server, MAP server, API server.
396
+ // Components created before the sidecar (steps 9-11) receive this object.
397
+ // When the sidecar is created (step 13), it's attached here so all components
398
+ // see it via the same reference (e.g., ACP handler accessing system.mapSidecar).
399
+ const systemRef = {
400
+ agentManager,
401
+ agentStore,
402
+ inboxAdapter,
403
+ tasksAdapter,
404
+ triggerSystem,
405
+ controlServer,
406
+ roleRegistry,
407
+ controlSocketPath,
408
+ } as any;
409
+
347
410
  // 9. REST API server (optional)
348
411
  let apiServer: ApiServer | null = null;
349
412
  if (config.api?.enabled) {
350
413
  const { createApiServer } = await import("./api/server.js");
351
- // Build a partial system reference for the API server.
352
- // The full system object is returned below; we create the API server
353
- // first so it can be included in the return value and shut down cleanly.
354
- const systemRef = {
355
- agentManager,
356
- agentStore,
357
- inboxAdapter,
358
- tasksAdapter,
359
- triggerSystem,
360
- controlServer,
361
- roleRegistry,
362
- controlSocketPath,
363
- } as any;
364
414
  apiServer = createApiServer(systemRef, {
365
415
  port: config.api.port,
366
416
  host: config.api.host,
@@ -373,17 +423,7 @@ export async function bootV2(
373
423
  if (config.acp?.enabled) {
374
424
  const { createWebSocketACPServer } = await import("./acp/websocket-server.js");
375
425
  acpServer = createWebSocketACPServer(
376
- // Pass a partial system ref (the full object is built below)
377
- {
378
- agentManager,
379
- agentStore,
380
- inboxAdapter,
381
- tasksAdapter,
382
- triggerSystem,
383
- controlServer,
384
- roleRegistry,
385
- controlSocketPath,
386
- } as any,
426
+ systemRef,
387
427
  {
388
428
  port: config.acp.port,
389
429
  host: config.acp.host,
@@ -404,17 +444,7 @@ export async function bootV2(
404
444
  agentStore,
405
445
  inboxAdapter,
406
446
  tasksAdapter,
407
- // Pass partial system ref for ACP-over-MAP bridge
408
- system: {
409
- agentManager,
410
- agentStore,
411
- inboxAdapter,
412
- tasksAdapter,
413
- triggerSystem,
414
- controlServer,
415
- roleRegistry,
416
- controlSocketPath,
417
- } as any,
447
+ system: systemRef,
418
448
  },
419
449
  {
420
450
  port: config.mapServer.port,
@@ -468,8 +498,27 @@ export async function bootV2(
468
498
  if (config.map?.enabled && config.map.server) {
469
499
  try {
470
500
  const { createMAPSidecar } = await import("./map/sidecar.js");
501
+ // If a workspace manager is present, pull out its GitCascadeAdapter
502
+ // so the sidecar can forward cascade events to the hub.
503
+ const wsMgr = config.workspaceManager as
504
+ | {
505
+ getGitCascadeAdapter?: () =>
506
+ | import("./workspace/git-cascade-adapter.js").GitCascadeAdapter
507
+ | undefined;
508
+ }
509
+ | undefined;
510
+ const gitCascadeAdapter = wsMgr?.getGitCascadeAdapter?.();
471
511
  mapSidecar = createMAPSidecar(
472
- { agentManager, agentStore, inboxAdapter, tasksAdapter },
512
+ {
513
+ agentManager,
514
+ agentStore,
515
+ inboxAdapter,
516
+ tasksAdapter,
517
+ getLocalMapId: mapServerInstance
518
+ ? (id: string) => mapServerInstance!.getLocalMapId(id)
519
+ : undefined,
520
+ gitCascadeAdapter,
521
+ },
473
522
  {
474
523
  server: config.map.server,
475
524
  token: config.map.token,
@@ -487,6 +536,8 @@ export async function bootV2(
487
536
  await mapSidecar.start();
488
537
  // Wire sidecar into agent manager for session-end checkpoints
489
538
  agentManager.setSidecar(mapSidecar);
539
+ // Attach to shared system ref so ACP/MAP handlers can access it
540
+ systemRef.mapSidecar = mapSidecar;
490
541
  } catch (err) {
491
542
  // Non-fatal — MAP hub connectivity is optional
492
543
  console.warn(
package/src/cli/index.ts CHANGED
@@ -498,6 +498,67 @@ program
498
498
  }
499
499
  });
500
500
 
501
+ // ─────────────────────────────────────────────────────────────────
502
+ // Run Team Command (Phase 10)
503
+ // ─────────────────────────────────────────────────────────────────
504
+
505
+ program
506
+ .command("run <teamName>")
507
+ .description("Boot the system, start a team by name, and optionally prompt the root agent")
508
+ .option("--task <task>", "Task to prompt the root agent with")
509
+ .option("--cwd <path>", "Working directory for agents")
510
+ .option("--base-path <path>", "Base path for team YAML lookup (default: cwd)")
511
+ .action(async (teamName: string, options: { task?: string; cwd?: string; basePath?: string }) => {
512
+ const cwd = options.cwd ?? process.cwd();
513
+ console.log(chalk.blue(`Booting macro-agent and starting team: ${teamName}`));
514
+
515
+ let system: Awaited<ReturnType<typeof bootV2>> | null = null;
516
+ try {
517
+ system = await bootV2({ cwd });
518
+ console.log(chalk.green("System booted."));
519
+
520
+ // Construct & install a TeamManagerV2; start the requested team.
521
+ const { TeamManagerV2 } = await import("../teams/team-manager-v2.js");
522
+ const teamManager = new TeamManagerV2({
523
+ agentManager: system.agentManager,
524
+ inboxAdapter: system.inboxAdapter,
525
+ tasksAdapter: system.tasksAdapter,
526
+ });
527
+ teamManager.install();
528
+
529
+ const instanceId = await teamManager.startTeam(teamName, options.basePath ?? cwd);
530
+ console.log(chalk.green(`Team started: ${teamName} (instance ${instanceId})`));
531
+
532
+ // Find the root agent (first spawned agent in the instance)
533
+ const agents = system.agentStore.listAgents({ state: "running" });
534
+ const root = agents[0];
535
+
536
+ if (options.task && root) {
537
+ console.log(chalk.gray(`Prompting root agent (${root.id}) with task...`));
538
+ const iter = system.agentManager.prompt(root.id, options.task);
539
+ for await (const chunk of iter) {
540
+ process.stdout.write(typeof chunk === "string" ? chunk : JSON.stringify(chunk));
541
+ }
542
+ console.log();
543
+ } else if (!root) {
544
+ console.log(chalk.yellow("No root agent found to prompt."));
545
+ }
546
+
547
+ console.log(chalk.gray("Press Ctrl+C to shut down."));
548
+
549
+ // Graceful shutdown on SIGINT
550
+ process.on("SIGINT", async () => {
551
+ console.log(chalk.yellow("\nShutting down..."));
552
+ if (system) await system.shutdown();
553
+ process.exit(0);
554
+ });
555
+ } catch (error) {
556
+ console.error(chalk.red(`Failed to run team: ${error}`));
557
+ if (system) await system.shutdown();
558
+ process.exit(1);
559
+ }
560
+ });
561
+
501
562
  // ─────────────────────────────────────────────────────────────────
502
563
  // Parse and Run
503
564
  // ─────────────────────────────────────────────────────────────────
@@ -126,13 +126,31 @@ describe("MacroAgentBackend", () => {
126
126
 
127
127
  describe("spawn", () => {
128
128
  it("spawns analyst via agentManager", async () => {
129
- const session = await backend.spawn({
129
+ // Use a deferred promise so promptUntilDone hangs, keeping session in "running"
130
+ let resolvePrompt!: () => void;
131
+ const hangingPrompt = new Promise<void>((r) => { resolvePrompt = r; });
132
+
133
+ const am = createMockAgentManager({
134
+ promptUntilDone: vi.fn().mockImplementation(async () => {
135
+ await hangingPrompt;
136
+ return {
137
+ doneCalled: true,
138
+ doneStatus: "completed",
139
+ exceededMax: false,
140
+ followUpCount: 0,
141
+ updates: [],
142
+ };
143
+ }),
144
+ });
145
+ const b = new MacroAgentBackend(am);
146
+
147
+ const session = await b.spawn({
130
148
  agentType: "claude-code",
131
149
  task: { description: "Analyze trajectory" },
132
150
  cwd: "/workspace",
133
151
  });
134
152
 
135
- expect(agentManager.spawn).toHaveBeenCalledWith(
153
+ expect(am.spawn).toHaveBeenCalledWith(
136
154
  expect.objectContaining({
137
155
  task: "Analyze trajectory",
138
156
  role: "analyst",
@@ -143,6 +161,9 @@ describe("MacroAgentBackend", () => {
143
161
  expect(session.state).toBe("running");
144
162
  expect(session.agentType).toBe("claude-code");
145
163
  expect(session.task.description).toBe("Analyze trajectory");
164
+
165
+ // Let the hanging prompt resolve to avoid dangling promises
166
+ resolvePrompt();
146
167
  });
147
168
 
148
169
  it("returns session with unique ID", async () => {
@@ -382,17 +403,38 @@ describe("MacroAgentBackend", () => {
382
403
 
383
404
  describe("terminate", () => {
384
405
  it("terminates the macro-agent process", async () => {
385
- const session = await backend.spawn({
406
+ // Use a deferred promise so promptUntilDone hangs until after terminate
407
+ let resolvePrompt!: () => void;
408
+ const hangingPrompt = new Promise<void>((r) => { resolvePrompt = r; });
409
+
410
+ const am = createMockAgentManager({
411
+ promptUntilDone: vi.fn().mockImplementation(async () => {
412
+ await hangingPrompt;
413
+ return {
414
+ doneCalled: true,
415
+ doneStatus: "completed",
416
+ exceededMax: false,
417
+ followUpCount: 0,
418
+ updates: [],
419
+ };
420
+ }),
421
+ });
422
+ const b = new MacroAgentBackend(am);
423
+
424
+ const session = await b.spawn({
386
425
  agentType: "claude-code",
387
426
  task: { description: "test" },
388
427
  });
389
428
 
390
- await backend.terminate(session.id);
429
+ await b.terminate(session.id);
391
430
 
392
- expect(agentManager.terminate).toHaveBeenCalledWith(
431
+ expect(am.terminate).toHaveBeenCalledWith(
393
432
  "agent_test123",
394
433
  "cancelled",
395
434
  );
435
+
436
+ // Let the hanging prompt resolve to avoid dangling promises
437
+ resolvePrompt();
396
438
  });
397
439
 
398
440
  it("sets session to failed", async () => {
@@ -240,6 +240,46 @@ export class MacroAgentBackend {
240
240
  const timeout = config.timeout;
241
241
  let softTimer: ReturnType<typeof setTimeout> | undefined;
242
242
  let hardTimer: ReturnType<typeof setTimeout> | undefined;
243
+ let completionNotified = false;
244
+
245
+ // Helper: fire the completion callback + inbox notification exactly once.
246
+ // Called from both the hard-timer path and the finally block to avoid a
247
+ // race where waitForSession returns (state != "running") before finally
248
+ // runs and fires the callback.
249
+ const notifyCompletion = async (): Promise<void> => {
250
+ if (completionNotified) return;
251
+ if (session.state !== "completed" && session.state !== "failed") return;
252
+ completionNotified = true;
253
+
254
+ const completeEvent: SessionCompleteEvent = {
255
+ sessionId: session.id,
256
+ agentId: agentId as string,
257
+ state: session.state,
258
+ duration_ms: session.endTime
259
+ ? session.endTime.getTime() - session.startTime.getTime()
260
+ : 0,
261
+ message_count: session.messages.length,
262
+ tool_call_count: session.toolCalls.length,
263
+ };
264
+
265
+ this.onSessionComplete?.(completeEvent);
266
+
267
+ if (this.inboxAdapter) {
268
+ try {
269
+ await this.inboxAdapter.send(
270
+ agentId as string,
271
+ agentId as string,
272
+ {
273
+ type: "session.complete",
274
+ sessionId: session.id,
275
+ state: session.state,
276
+ duration_ms: completeEvent.duration_ms,
277
+ outcome: session.state === "completed" ? "success" : "failure",
278
+ },
279
+ );
280
+ } catch { /* best-effort */ }
281
+ }
282
+ };
243
283
 
244
284
  try {
245
285
  if (timeout && timeout > 0) {
@@ -263,6 +303,9 @@ export class MacroAgentBackend {
263
303
  session.state = "failed";
264
304
  session.error = "Timeout exceeded";
265
305
  session.endTime = new Date();
306
+ // Fire completion callback immediately — the finally block's
307
+ // deferred callback could race with waitForSession returning.
308
+ void notifyCompletion();
266
309
  this.agentManager.terminate(agentId, "timeout").catch(() => {});
267
310
  }, timeout);
268
311
  }
@@ -307,35 +350,8 @@ export class MacroAgentBackend {
307
350
  }
308
351
 
309
352
  // Notify completion (no trajectory extraction — OpenHive handles that via sessionlog)
310
- if (session.state === "completed" || session.state === "failed") {
311
- const completeEvent: SessionCompleteEvent = {
312
- sessionId: session.id,
313
- agentId: agentId as string,
314
- state: session.state,
315
- duration_ms: session.endTime
316
- ? session.endTime.getTime() - session.startTime.getTime()
317
- : 0,
318
- message_count: session.messages.length,
319
- tool_call_count: session.toolCalls.length,
320
- };
321
-
322
- this.onSessionComplete?.(completeEvent);
323
-
324
- if (this.inboxAdapter) {
325
- await this.inboxAdapter.send(
326
- agentId as string,
327
- agentId as string,
328
- {
329
- type: "session.complete",
330
- sessionId: session.id,
331
- state: session.state,
332
- duration_ms: completeEvent.duration_ms,
333
- outcome: session.state === "completed" ? "success" : "failure",
334
- },
335
- { subject: "session.complete" },
336
- ).catch(() => {});
337
- }
338
- }
353
+ // This is a no-op if the hard-timer path already fired it.
354
+ await notifyCompletion();
339
355
 
340
356
  // Clean up agent process
341
357
  if (session.state !== "running") {
@@ -55,6 +55,7 @@ async function loadSkillTree(): Promise<any> {
55
55
  _loadAttempted = true;
56
56
 
57
57
  try {
58
+ // @ts-ignore - optional peer dependency, may not be installed
58
59
  _skillTreeModule = await import("skill-tree");
59
60
  return _skillTreeModule;
60
61
  } catch {
@@ -124,15 +124,45 @@ export function detectCleanupStatus(
124
124
  // =============================================================================
125
125
 
126
126
  /**
127
- * Commit all uncommitted changes in a workspace
127
+ * Optional handle for routing commits through git-cascade so the resulting
128
+ * commit gets a Change-Id trailer and emits an `x-cascade/stream.committed`
129
+ * event. When omitted, commits are made via raw git (no Change-Id, no
130
+ * cascade event) — used by legacy/null-workspace paths.
131
+ */
132
+ export interface TrackedCommitHandle {
133
+ /** WorkspaceManager-style commitChanges signature */
134
+ commitChanges(opts: {
135
+ streamId: string;
136
+ agentId: string;
137
+ worktree: string;
138
+ message: string;
139
+ metadata?: Record<string, unknown>;
140
+ }): { commit: string; changeId: string };
141
+ /** Stream this commit belongs to */
142
+ streamId: string;
143
+ /** Agent making the commit */
144
+ agentId: string;
145
+ /** Optional metadata threaded into the cascade event (e.g. `{ task_ref }`) */
146
+ metadata?: Record<string, unknown>;
147
+ }
148
+
149
+ /**
150
+ * Commit all uncommitted changes in a workspace.
151
+ *
152
+ * When a `TrackedCommitHandle` is supplied, the commit goes through
153
+ * git-cascade's tracker — gaining a stable Change-Id trailer and emitting
154
+ * `x-cascade/stream.committed` so OpenHive sees the work. Without a handle,
155
+ * falls back to raw git (legacy behavior).
128
156
  *
129
157
  * @param workspacePath - Path to the workspace
130
158
  * @param message - Commit message
159
+ * @param tracked - Optional handle to commit via the cascade tracker
131
160
  * @returns Commit hash if successful, undefined if nothing to commit
132
161
  */
133
162
  export function commitChanges(
134
163
  workspacePath: string,
135
- message: string
164
+ message: string,
165
+ tracked?: TrackedCommitHandle
136
166
  ): string | undefined {
137
167
  try {
138
168
  // Check if there are changes to commit
@@ -140,7 +170,26 @@ export function commitChanges(
140
170
  return undefined;
141
171
  }
142
172
 
143
- // Stage all changes
173
+ // Tracked path: stage + commit through git-cascade so Change-Id +
174
+ // x-cascade events fire.
175
+ if (tracked) {
176
+ try {
177
+ const { commit } = tracked.commitChanges({
178
+ streamId: tracked.streamId,
179
+ agentId: tracked.agentId,
180
+ worktree: workspacePath,
181
+ message,
182
+ metadata: tracked.metadata,
183
+ });
184
+ return commit;
185
+ } catch {
186
+ // Fall through to raw-git path on tracker failure (e.g., stream
187
+ // conflicted) so callers still get a commit. Caveat: no Change-Id
188
+ // and no cascade event in this fallback.
189
+ }
190
+ }
191
+
192
+ // Raw-git path: stage all changes
144
193
  execFileSync("git", ["add", "--all"], {
145
194
  cwd: workspacePath,
146
195
  encoding: "utf-8",
@@ -22,8 +22,9 @@ import type {
22
22
  CleanupStatus,
23
23
  DoneHandlerResult,
24
24
  } from "./types.js";
25
- import { commitChanges, attemptMerge, abortMerge } from "./cleanup.js";
25
+ import { commitChanges, attemptMerge, abortMerge, type TrackedCommitHandle } from "./cleanup.js";
26
26
  import type { MergeQueueInterface } from "../workspace/merge-queue/types.js";
27
+ import type { WorkspaceManager } from "../workspace/types.js";
27
28
  import {
28
29
  getAllDescendants,
29
30
  needsCascadeTermination,
@@ -40,6 +41,40 @@ export interface HandlerDepsV2 {
40
41
  agentManager: AgentManager;
41
42
  taskMode?: "push" | "pull";
42
43
  mergeQueue?: MergeQueueInterface;
44
+ /**
45
+ * Optional workspace manager. When provided AND `context.streamId` is set,
46
+ * commits route through the cascade tracker (Change-Id + x-cascade events).
47
+ * Without it, commits use raw git (legacy / null-workspace path).
48
+ */
49
+ workspaceManager?: WorkspaceManager;
50
+ }
51
+
52
+ // =============================================================================
53
+ // Tracked Commit Helper
54
+ // =============================================================================
55
+
56
+ /**
57
+ * Build a TrackedCommitHandle when the agent has a streamId + workspaceManager.
58
+ * Returns undefined if either is missing — caller falls back to raw git.
59
+ */
60
+ function buildTrackedHandle(
61
+ context: LifecycleContext,
62
+ deps: HandlerDepsV2
63
+ ): TrackedCommitHandle | undefined {
64
+ if (!context.streamId || !deps.workspaceManager) return undefined;
65
+ const ws = deps.workspaceManager;
66
+ // Build metadata with task_ref if known. Empty object is fine — the hub
67
+ // ignores unknown fields, and back-fill kicks in if task_ref appears later.
68
+ const metadata: Record<string, unknown> = {};
69
+ if (context.taskRef) {
70
+ metadata.task_ref = context.taskRef;
71
+ }
72
+ return {
73
+ streamId: context.streamId,
74
+ agentId: context.agentId,
75
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
76
+ commitChanges: (opts) => ws.commitChanges(opts),
77
+ };
43
78
  }
44
79
 
45
80
  // =============================================================================
@@ -110,10 +145,12 @@ async function handleWorkerDone(
110
145
  ? `WIP: ${args.summary}`
111
146
  : `WIP: Auto-commit from done() with ${uncommittedCount} uncommitted file(s)`;
112
147
 
113
- const commitHash = commitChanges(context.workspacePath, commitMessage);
148
+ const tracked = buildTrackedHandle(context, deps);
149
+ const commitHash = commitChanges(context.workspacePath, commitMessage, tracked);
114
150
  if (commitHash) {
151
+ const via = tracked ? "tracker" : "raw-git";
115
152
  cleanupActions.push(
116
- `Committed ${uncommittedCount} file(s): ${commitHash.slice(0, 8)}`
153
+ `Committed ${uncommittedCount} file(s) via ${via}: ${commitHash.slice(0, 8)}`
117
154
  );
118
155
  } else {
119
156
  warnings.push("Failed to auto-commit uncommitted changes");
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import type { AgentId, TaskId } from "../store/types/index.js";
12
+ import type { TaskRef } from "git-cascade/events";
12
13
 
13
14
  // =============================================================================
14
15
  // Done Status Types
@@ -120,6 +121,17 @@ export interface LifecycleContext {
120
121
 
121
122
  /** Resolved capabilities for the agent's role (for capability-based handler dispatch) */
122
123
  capabilities?: string[];
124
+
125
+ /**
126
+ * Optional reference to an external task this agent is working on. When
127
+ * present, threaded into commit metadata so cascade events carry the
128
+ * binding (enabling task↔stream queries on the OpenHive hub).
129
+ *
130
+ * Sourced from `SpawnAgentOptions.taskRef` at spawn time and propagated
131
+ * via the agent manager. Late-binding (mid-session pull-mode claim) can
132
+ * mutate this on the live context.
133
+ */
134
+ taskRef?: TaskRef;
123
135
  }
124
136
 
125
137
  /**