macro-agent 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/CLAUDE.md +166 -33
  2. package/README.md +781 -131
  3. package/dist/acp/claude-code-replay.d.ts +11 -0
  4. package/dist/acp/claude-code-replay.d.ts.map +1 -0
  5. package/dist/acp/claude-code-replay.js +190 -0
  6. package/dist/acp/claude-code-replay.js.map +1 -0
  7. package/dist/acp/macro-agent.d.ts.map +1 -1
  8. package/dist/acp/macro-agent.js +155 -6
  9. package/dist/acp/macro-agent.js.map +1 -1
  10. package/dist/acp/types.d.ts +9 -0
  11. package/dist/acp/types.d.ts.map +1 -1
  12. package/dist/acp/types.js.map +1 -1
  13. package/dist/agent/agent-manager-v2.d.ts +21 -0
  14. package/dist/agent/agent-manager-v2.d.ts.map +1 -1
  15. package/dist/agent/agent-manager-v2.js +234 -43
  16. package/dist/agent/agent-manager-v2.js.map +1 -1
  17. package/dist/agent/agent-manager.d.ts +12 -0
  18. package/dist/agent/agent-manager.d.ts.map +1 -1
  19. package/dist/agent/agent-manager.js.map +1 -1
  20. package/dist/agent/types.d.ts +15 -2
  21. package/dist/agent/types.d.ts.map +1 -1
  22. package/dist/agent/types.js.map +1 -1
  23. package/dist/boot-v2.d.ts +41 -0
  24. package/dist/boot-v2.d.ts.map +1 -1
  25. package/dist/boot-v2.js +16 -1
  26. package/dist/boot-v2.js.map +1 -1
  27. package/dist/cli/index.js +56 -0
  28. package/dist/cli/index.js.map +1 -1
  29. package/dist/cognitive/macro-agent-backend.d.ts.map +1 -1
  30. package/dist/cognitive/macro-agent-backend.js +40 -22
  31. package/dist/cognitive/macro-agent-backend.js.map +1 -1
  32. package/dist/integrations/skilltree.d.ts.map +1 -1
  33. package/dist/integrations/skilltree.js +1 -0
  34. package/dist/integrations/skilltree.js.map +1 -1
  35. package/dist/lifecycle/cleanup.d.ts +33 -2
  36. package/dist/lifecycle/cleanup.d.ts.map +1 -1
  37. package/dist/lifecycle/cleanup.js +28 -6
  38. package/dist/lifecycle/cleanup.js.map +1 -1
  39. package/dist/lifecycle/handlers-v2.d.ts +7 -0
  40. package/dist/lifecycle/handlers-v2.d.ts.map +1 -1
  41. package/dist/lifecycle/handlers-v2.js +28 -2
  42. package/dist/lifecycle/handlers-v2.js.map +1 -1
  43. package/dist/lifecycle/types.d.ts +11 -0
  44. package/dist/lifecycle/types.d.ts.map +1 -1
  45. package/dist/lifecycle/types.js.map +1 -1
  46. package/dist/map/acp-bridge.d.ts +9 -0
  47. package/dist/map/acp-bridge.d.ts.map +1 -1
  48. package/dist/map/acp-bridge.js +15 -2
  49. package/dist/map/acp-bridge.js.map +1 -1
  50. package/dist/map/cascade-bridge.d.ts +44 -0
  51. package/dist/map/cascade-bridge.d.ts.map +1 -0
  52. package/dist/map/cascade-bridge.js +257 -0
  53. package/dist/map/cascade-bridge.js.map +1 -0
  54. package/dist/map/lifecycle-bridge.d.ts +1 -1
  55. package/dist/map/lifecycle-bridge.d.ts.map +1 -1
  56. package/dist/map/lifecycle-bridge.js +58 -23
  57. package/dist/map/lifecycle-bridge.js.map +1 -1
  58. package/dist/map/server.d.ts.map +1 -1
  59. package/dist/map/server.js +47 -6
  60. package/dist/map/server.js.map +1 -1
  61. package/dist/map/sidecar.d.ts.map +1 -1
  62. package/dist/map/sidecar.js +33 -2
  63. package/dist/map/sidecar.js.map +1 -1
  64. package/dist/map/types.d.ts +20 -0
  65. package/dist/map/types.d.ts.map +1 -1
  66. package/dist/mcp/tools/done-v2.d.ts.map +1 -1
  67. package/dist/mcp/tools/done-v2.js +8 -0
  68. package/dist/mcp/tools/done-v2.js.map +1 -1
  69. package/dist/teams/team-manager-v2.d.ts.map +1 -1
  70. package/dist/teams/team-manager-v2.js +26 -0
  71. package/dist/teams/team-manager-v2.js.map +1 -1
  72. package/dist/teams/team-runtime-v2.d.ts.map +1 -1
  73. package/dist/teams/team-runtime-v2.js +16 -3
  74. package/dist/teams/team-runtime-v2.js.map +1 -1
  75. package/dist/workspace/config.d.ts +10 -10
  76. package/dist/workspace/config.d.ts.map +1 -1
  77. package/dist/workspace/config.js +4 -4
  78. package/dist/workspace/config.js.map +1 -1
  79. package/dist/workspace/git-cascade-adapter.d.ts +510 -0
  80. package/dist/workspace/git-cascade-adapter.d.ts.map +1 -0
  81. package/dist/workspace/git-cascade-adapter.js +908 -0
  82. package/dist/workspace/git-cascade-adapter.js.map +1 -0
  83. package/dist/workspace/index.d.ts +3 -3
  84. package/dist/workspace/index.d.ts.map +1 -1
  85. package/dist/workspace/index.js +4 -4
  86. package/dist/workspace/index.js.map +1 -1
  87. package/dist/workspace/landing/direct-push.d.ts +20 -0
  88. package/dist/workspace/landing/direct-push.d.ts.map +1 -0
  89. package/dist/workspace/landing/direct-push.js +74 -0
  90. package/dist/workspace/landing/direct-push.js.map +1 -0
  91. package/dist/workspace/landing/index.d.ts +29 -0
  92. package/dist/workspace/landing/index.d.ts.map +1 -0
  93. package/dist/workspace/landing/index.js +37 -0
  94. package/dist/workspace/landing/index.js.map +1 -0
  95. package/dist/workspace/landing/merge-to-parent.d.ts +41 -0
  96. package/dist/workspace/landing/merge-to-parent.d.ts.map +1 -0
  97. package/dist/workspace/landing/merge-to-parent.js +185 -0
  98. package/dist/workspace/landing/merge-to-parent.js.map +1 -0
  99. package/dist/workspace/landing/optimistic-push.d.ts +16 -0
  100. package/dist/workspace/landing/optimistic-push.d.ts.map +1 -0
  101. package/dist/workspace/landing/optimistic-push.js +27 -0
  102. package/dist/workspace/landing/optimistic-push.js.map +1 -0
  103. package/dist/workspace/landing/queue-to-branch.d.ts +24 -0
  104. package/dist/workspace/landing/queue-to-branch.d.ts.map +1 -0
  105. package/dist/workspace/landing/queue-to-branch.js +79 -0
  106. package/dist/workspace/landing/queue-to-branch.js.map +1 -0
  107. package/dist/workspace/merge-queue/merge-queue.d.ts +10 -0
  108. package/dist/workspace/merge-queue/merge-queue.d.ts.map +1 -1
  109. package/dist/workspace/merge-queue/merge-queue.js +10 -0
  110. package/dist/workspace/merge-queue/merge-queue.js.map +1 -1
  111. package/dist/workspace/merge-queue/types.d.ts +16 -2
  112. package/dist/workspace/merge-queue/types.d.ts.map +1 -1
  113. package/dist/workspace/merge-queue/types.js +9 -0
  114. package/dist/workspace/merge-queue/types.js.map +1 -1
  115. package/dist/workspace/pool/types.d.ts +1 -0
  116. package/dist/workspace/pool/types.d.ts.map +1 -1
  117. package/dist/workspace/pool/worktree-pool.d.ts.map +1 -1
  118. package/dist/workspace/pool/worktree-pool.js +1 -0
  119. package/dist/workspace/pool/worktree-pool.js.map +1 -1
  120. package/dist/workspace/recovery/abandon.d.ts +15 -0
  121. package/dist/workspace/recovery/abandon.d.ts.map +1 -0
  122. package/dist/workspace/recovery/abandon.js +45 -0
  123. package/dist/workspace/recovery/abandon.js.map +1 -0
  124. package/dist/workspace/recovery/auto-resolve.d.ts +27 -0
  125. package/dist/workspace/recovery/auto-resolve.d.ts.map +1 -0
  126. package/dist/workspace/recovery/auto-resolve.js +99 -0
  127. package/dist/workspace/recovery/auto-resolve.js.map +1 -0
  128. package/dist/workspace/recovery/defer.d.ts +15 -0
  129. package/dist/workspace/recovery/defer.d.ts.map +1 -0
  130. package/dist/workspace/recovery/defer.js +16 -0
  131. package/dist/workspace/recovery/defer.js.map +1 -0
  132. package/dist/workspace/recovery/escalate.d.ts +16 -0
  133. package/dist/workspace/recovery/escalate.d.ts.map +1 -0
  134. package/dist/workspace/recovery/escalate.js +24 -0
  135. package/dist/workspace/recovery/escalate.js.map +1 -0
  136. package/dist/workspace/recovery/index.d.ts +32 -0
  137. package/dist/workspace/recovery/index.d.ts.map +1 -0
  138. package/dist/workspace/recovery/index.js +45 -0
  139. package/dist/workspace/recovery/index.js.map +1 -0
  140. package/dist/workspace/recovery/spawn-resolver.d.ts +45 -0
  141. package/dist/workspace/recovery/spawn-resolver.d.ts.map +1 -0
  142. package/dist/workspace/recovery/spawn-resolver.js +111 -0
  143. package/dist/workspace/recovery/spawn-resolver.js.map +1 -0
  144. package/dist/workspace/recovery/types.d.ts +63 -0
  145. package/dist/workspace/recovery/types.d.ts.map +1 -0
  146. package/dist/workspace/recovery/types.js +12 -0
  147. package/dist/workspace/recovery/types.js.map +1 -0
  148. package/dist/workspace/topology/index.d.ts +9 -0
  149. package/dist/workspace/topology/index.d.ts.map +1 -0
  150. package/dist/workspace/topology/index.js +8 -0
  151. package/dist/workspace/topology/index.js.map +1 -0
  152. package/dist/workspace/topology/no-workspace.d.ts +18 -0
  153. package/dist/workspace/topology/no-workspace.d.ts.map +1 -0
  154. package/dist/workspace/topology/no-workspace.js +25 -0
  155. package/dist/workspace/topology/no-workspace.js.map +1 -0
  156. package/dist/workspace/topology/types.d.ts +97 -0
  157. package/dist/workspace/topology/types.d.ts.map +1 -0
  158. package/dist/workspace/topology/types.js +20 -0
  159. package/dist/workspace/topology/types.js.map +1 -0
  160. package/dist/workspace/topology/yaml-driven.d.ts +69 -0
  161. package/dist/workspace/topology/yaml-driven.d.ts.map +1 -0
  162. package/dist/workspace/topology/yaml-driven.js +273 -0
  163. package/dist/workspace/topology/yaml-driven.js.map +1 -0
  164. package/dist/workspace/types-v3.d.ts +110 -0
  165. package/dist/workspace/types-v3.d.ts.map +1 -0
  166. package/dist/workspace/types-v3.js +20 -0
  167. package/dist/workspace/types-v3.js.map +1 -0
  168. package/dist/workspace/types.d.ts +145 -17
  169. package/dist/workspace/types.d.ts.map +1 -1
  170. package/dist/workspace/workspace-manager.d.ts +92 -13
  171. package/dist/workspace/workspace-manager.d.ts.map +1 -1
  172. package/dist/workspace/workspace-manager.js +373 -13
  173. package/dist/workspace/workspace-manager.js.map +1 -1
  174. package/dist/workspace/yaml-schema.d.ts +254 -0
  175. package/dist/workspace/yaml-schema.d.ts.map +1 -0
  176. package/dist/workspace/yaml-schema.js +170 -0
  177. package/dist/workspace/yaml-schema.js.map +1 -0
  178. package/docs/conflict-recovery.md +472 -0
  179. package/docs/git-cascade-integration-gaps.md +678 -0
  180. package/docs/workspace-interfaces.md +731 -0
  181. package/docs/workspace-redesign-plan.md +302 -0
  182. package/package.json +4 -4
  183. package/src/__tests__/e2e/auto-sync.e2e.test.ts +257 -0
  184. package/src/__tests__/e2e/cascade-rebase.e2e.test.ts +254 -0
  185. package/src/__tests__/e2e/cli-run.e2e.test.ts +167 -0
  186. package/src/__tests__/e2e/self-driving-v3.e2e.test.ts +197 -0
  187. package/src/__tests__/e2e/spawn-resolver.e2e.test.ts +200 -0
  188. package/src/__tests__/e2e/workspace-lifecycle.e2e.test.ts +30 -22
  189. package/src/__tests__/e2e/workspace-v3.e2e.test.ts +413 -0
  190. package/src/acp/__tests__/claude-code-replay.test.ts +225 -0
  191. package/src/acp/__tests__/macro-agent.test.ts +39 -1
  192. package/src/acp/claude-code-replay.ts +208 -0
  193. package/src/acp/macro-agent.ts +167 -9
  194. package/src/acp/types.ts +10 -0
  195. package/src/agent/__tests__/agent-manager-topology.test.ts +73 -0
  196. package/src/agent/__tests__/agent-manager-v2.test.ts +66 -0
  197. package/src/agent/__tests__/task-ref-resolution.test.ts +231 -0
  198. package/src/agent/agent-manager-v2.ts +293 -48
  199. package/src/agent/agent-manager.ts +14 -0
  200. package/src/agent/types.ts +16 -2
  201. package/src/boot-v2.ts +68 -1
  202. package/src/cli/index.ts +61 -0
  203. package/src/cognitive/macro-agent-backend.ts +45 -29
  204. package/src/integrations/skilltree.ts +1 -0
  205. package/src/lifecycle/cleanup.ts +52 -3
  206. package/src/lifecycle/handlers-v2.ts +40 -3
  207. package/src/lifecycle/types.ts +12 -0
  208. package/src/map/__tests__/cascade-bridge.test.ts +229 -0
  209. package/src/map/__tests__/lifecycle-bridge.test.ts +86 -10
  210. package/src/map/acp-bridge.ts +26 -3
  211. package/src/map/cascade-bridge.ts +301 -0
  212. package/src/map/lifecycle-bridge.ts +52 -17
  213. package/src/map/server.ts +47 -6
  214. package/src/map/sidecar.ts +31 -1
  215. package/src/map/types.ts +20 -0
  216. package/src/mcp/tools/done-v2.ts +9 -0
  217. package/src/teams/team-manager-v2.ts +37 -0
  218. package/src/teams/team-runtime-v2.ts +23 -3
  219. package/src/workspace/__tests__/{dataplane-adapter.test.ts → git-cascade-adapter.test.ts} +209 -14
  220. package/src/workspace/__tests__/self-driving-yaml.test.ts +114 -0
  221. package/src/workspace/__tests__/shared-worktree-refcount.test.ts +154 -0
  222. package/src/workspace/__tests__/standalone-mode.test.ts +118 -0
  223. package/src/workspace/__tests__/workspace-manager-v3.test.ts +245 -0
  224. package/src/workspace/__tests__/yaml-schema.test.ts +210 -0
  225. package/src/workspace/config.ts +11 -11
  226. package/src/workspace/git-cascade-adapter.ts +1186 -0
  227. package/src/workspace/index.ts +11 -11
  228. package/src/workspace/landing/__tests__/strategies.test.ts +142 -0
  229. package/src/workspace/landing/direct-push.ts +91 -0
  230. package/src/workspace/landing/index.ts +40 -0
  231. package/src/workspace/landing/merge-to-parent.ts +228 -0
  232. package/src/workspace/landing/optimistic-push.ts +36 -0
  233. package/src/workspace/landing/queue-to-branch.ts +108 -0
  234. package/src/workspace/merge-queue/merge-queue.ts +10 -0
  235. package/src/workspace/merge-queue/types.ts +16 -2
  236. package/src/workspace/pool/__tests__/worktree-pool.integration.test.ts +5 -5
  237. package/src/workspace/pool/types.ts +1 -0
  238. package/src/workspace/pool/worktree-pool.ts +1 -0
  239. package/src/workspace/recovery/__tests__/auto-resolve-integration.test.ts +127 -0
  240. package/src/workspace/recovery/__tests__/spawn-resolver.test.ts +139 -0
  241. package/src/workspace/recovery/__tests__/strategies.test.ts +145 -0
  242. package/src/workspace/recovery/abandon.ts +51 -0
  243. package/src/workspace/recovery/auto-resolve.ts +119 -0
  244. package/src/workspace/recovery/defer.ts +23 -0
  245. package/src/workspace/recovery/escalate.ts +30 -0
  246. package/src/workspace/recovery/index.ts +58 -0
  247. package/src/workspace/recovery/spawn-resolver.ts +145 -0
  248. package/src/workspace/recovery/types.ts +54 -0
  249. package/src/workspace/topology/__tests__/yaml-driven.test.ts +345 -0
  250. package/src/workspace/topology/index.ts +18 -0
  251. package/src/workspace/topology/no-workspace.ts +39 -0
  252. package/src/workspace/topology/types.ts +116 -0
  253. package/src/workspace/topology/yaml-driven.ts +316 -0
  254. package/src/workspace/types-v3.ts +155 -0
  255. package/src/workspace/types.ts +191 -20
  256. package/src/workspace/workspace-manager.ts +474 -19
  257. package/src/workspace/yaml-schema.ts +216 -0
  258. package/src/workspace/dataplane-adapter.ts +0 -546
@@ -131,6 +131,12 @@ export interface AgentManager {
131
131
  */
132
132
  listHeadManagers(): Agent[];
133
133
 
134
+ /**
135
+ * Look up the SpawnedAgent shape for any agent (any role) that's running
136
+ * AND has a live session in this process. Returns null otherwise.
137
+ */
138
+ getActiveAgentSession(agentId: AgentId): SpawnedAgent | null;
139
+
134
140
  // ── Session Interaction ────────────────────────────────────────
135
141
 
136
142
  /**
@@ -296,6 +302,14 @@ export interface AgentManager {
296
302
  */
297
303
  setSidecar(sidecar: { connected: boolean; reportCheckpoint(cp: any): Promise<any> } | null): void;
298
304
 
305
+ /**
306
+ * Set a TopologyPolicy for workspace allocation decisions.
307
+ * When set, AgentManagerV2 delegates `createWorkspaceForRole` to the policy
308
+ * before falling back to role-name dispatch. Set by boot-v2 when team YAML
309
+ * contains `macro_agent.workspace`.
310
+ */
311
+ setTopologyPolicy(policy: import('../workspace/topology/types.js').TopologyPolicy | null): void;
312
+
299
313
  // ── Cleanup ────────────────────────────────────────────────────
300
314
 
301
315
  /**
@@ -15,6 +15,7 @@ import type {
15
15
  StreamConfig,
16
16
  Workspace,
17
17
  } from "../workspace/types.js";
18
+ import type { TaskRef } from "git-cascade/events";
18
19
 
19
20
  // ─────────────────────────────────────────────────────────────────
20
21
  // Spawn Options
@@ -90,16 +91,29 @@ export interface SpawnAgentOptions {
90
91
  streamConfig?: StreamConfig;
91
92
 
92
93
  /**
93
- * Dataplane task ID to claim (for workers).
94
+ * git-cascade task ID to claim (for workers).
94
95
  * If provided, the worker will claim this task and work on it.
95
96
  */
96
- dataplaneTaskId?: string;
97
+ gitCascadeTaskId?: string;
97
98
 
98
99
  /**
99
100
  * Resolved capabilities for this agent's role.
100
101
  * Injected by TeamRuntime spawn interceptor for capability-based workspace dispatch.
101
102
  */
102
103
  capabilities?: string[];
104
+
105
+ /**
106
+ * Optional reference to an external task this agent is working on. When
107
+ * set, the reference is woven into cascade event metadata so OpenHive (or
108
+ * any other observer) can bind the agent's commits, merges, and streams
109
+ * to the OpenTasks node. Propagates through:
110
+ * - stream creation: `adapter.createStream({ metadata: { task_ref } })`
111
+ * - commits: `adapter.commitChanges({ metadata: { task_ref } })` (pass
112
+ * per-commit; each commit can carry a distinct sub-task binding)
113
+ * Can also arrive late via pull-mode `claim_task` — workers that claim a
114
+ * task mid-session should include the ref on subsequent commits.
115
+ */
116
+ taskRef?: TaskRef;
103
117
  }
104
118
 
105
119
  /**
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
 
@@ -450,8 +498,27 @@ export async function bootV2(
450
498
  if (config.map?.enabled && config.map.server) {
451
499
  try {
452
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?.();
453
511
  mapSidecar = createMAPSidecar(
454
- { 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
+ },
455
522
  {
456
523
  server: config.map.server,
457
524
  token: config.map.token,
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
  // ─────────────────────────────────────────────────────────────────
@@ -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
  /**