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
@@ -116,6 +116,29 @@ export interface AgentManagerV2Config {
116
116
  serverToken?: string;
117
117
  /** Control socket path for MCP subprocess lifecycle RPC */
118
118
  controlSocketPath?: string;
119
+ /**
120
+ * Default opentasks resource ID hosted on the OpenHive hub. When set,
121
+ * spawn paths build `taskRef = { resource_id: <this>, node_id: task_id }`
122
+ * automatically from `SpawnAgentOptions.task_id` (for any spawn where
123
+ * `resolveTaskRef` returned undefined AND the caller didn't supply an
124
+ * explicit `taskRef`).
125
+ *
126
+ * Operators set this once at swarm registration for the common
127
+ * single-graph case. Multi-graph deployments should use `resolveTaskRef`
128
+ * instead.
129
+ */
130
+ taskResourceId?: string;
131
+
132
+ /**
133
+ * Multi-graph resolver. Called at every spawn; return a `TaskRef` to set
134
+ * the binding or `undefined` to fall through to the `taskResourceId`
135
+ * default. Explicit `SpawnAgentOptions.taskRef` always wins over both.
136
+ *
137
+ * Keep cheap — runs per-spawn.
138
+ */
139
+ resolveTaskRef?: (
140
+ spawnOptions: SpawnAgentOptions
141
+ ) => import("git-cascade/events").TaskRef | undefined;
119
142
  }
120
143
 
121
144
  // ─────────────────────────────────────────────────────────────────
@@ -139,6 +162,8 @@ export function createAgentManagerV2(
139
162
  serverToken,
140
163
  agentTokenManager,
141
164
  controlSocketPath,
165
+ taskResourceId,
166
+ resolveTaskRef,
142
167
  } = config;
143
168
 
144
169
  // In-memory state
@@ -158,6 +183,12 @@ export function createAgentManagerV2(
158
183
  // MAP sidecar reference for trajectory reporting (set via setSidecar)
159
184
  let sidecarRef: { connected: boolean; reportCheckpoint(cp: any): Promise<any> } | null = null;
160
185
 
186
+ // TopologyPolicy for workspace allocation (Phase 3+); set via setTopologyPolicy.
187
+ // When null, createWorkspaceForRole falls back to legacy role-name dispatch.
188
+ let topologyPolicy:
189
+ | import('../workspace/topology/types.js').TopologyPolicy
190
+ | null = null;
191
+
161
192
  // ── Helpers ──────────────────────────────────────────────────
162
193
 
163
194
  function notifyLifecycle(event: AgentLifecycleEvent): void {
@@ -247,6 +278,109 @@ export function createAgentManagerV2(
247
278
 
248
279
  // ── Workspace Helper ─────────────────────────────────────────
249
280
 
281
+ /**
282
+ * Execute a TopologyPolicy decision against the WorkspaceManager.
283
+ *
284
+ * Translates declarative `WorkspaceDecision` into concrete workspace
285
+ * allocations. Returns a `Workspace` compatible with the legacy shape
286
+ * so the rest of AgentManagerV2 doesn't need to change.
287
+ */
288
+ async function executeWorkspaceDecision(
289
+ agentId: AgentId,
290
+ decision: import('../workspace/topology/types.js').WorkspaceDecision,
291
+ role?: string,
292
+ spawnOptions?: SpawnAgentOptions
293
+ ): Promise<Workspace | undefined> {
294
+ if (!workspaceManager) return undefined;
295
+
296
+ switch (decision.kind) {
297
+ case 'none':
298
+ case 'share-parent-cwd':
299
+ return undefined;
300
+
301
+ case 'share-with-agent': {
302
+ const worktree = workspaceManager.allocateWorktree({
303
+ agentId,
304
+ sharedWithAgent: decision.agentId,
305
+ });
306
+ return {
307
+ agentId,
308
+ path: worktree.path,
309
+ branch: worktree.currentStream
310
+ ? `stream/${worktree.currentStream}`
311
+ : 'unknown',
312
+ streamId: worktree.currentStream ?? '',
313
+ role: 'v3', // V3 path — bypass legacy worker task/merge-queue flows
314
+ createdAt: worktree.createdAt,
315
+ };
316
+ }
317
+
318
+ case 'attach-to-stream': {
319
+ // Record even if no worktree — the topology needs the stream↔role
320
+ // mapping for event-driven features like on_parent_advanced.
321
+ const attachPolicy = topologyPolicy as unknown as {
322
+ recordAgentStream?: (a: string, s: string, role?: string) => void;
323
+ };
324
+ attachPolicy.recordAgentStream?.(agentId, decision.streamId, role);
325
+
326
+ if (!decision.allocateWorktree) {
327
+ return undefined;
328
+ }
329
+ const worktree = workspaceManager.allocateWorktree({
330
+ agentId,
331
+ streamId: decision.streamId,
332
+ });
333
+ return {
334
+ agentId,
335
+ path: worktree.path,
336
+ branch: `stream/${decision.streamId}`,
337
+ streamId: decision.streamId,
338
+ role: 'v3', // V3 path — attach-to-team-root
339
+ createdAt: worktree.createdAt,
340
+ };
341
+ }
342
+
343
+ case 'new-stream': {
344
+ // If the spawning agent has a taskRef and the streamSpec doesn't
345
+ // already carry one, weave it into metadata so the resulting stream
346
+ // binds to the OpenTasks node. Explicit streamSpec.metadata.task_ref
347
+ // wins.
348
+ const taskRef = spawnOptions?.taskRef;
349
+ const existingMeta = decision.streamSpec.metadata as
350
+ | Record<string, unknown>
351
+ | undefined;
352
+ const streamSpec = taskRef && !existingMeta?.task_ref
353
+ ? {
354
+ ...decision.streamSpec,
355
+ metadata: { ...(existingMeta ?? {}), task_ref: taskRef },
356
+ }
357
+ : decision.streamSpec;
358
+ const streamId = workspaceManager.createStreamV3(streamSpec);
359
+ // Record the mapping in the topology if it supports it (for share-with lookup).
360
+ const policy = topologyPolicy as unknown as {
361
+ recordAgentStream?: (a: string, s: string, role?: string) => void;
362
+ };
363
+ policy.recordAgentStream?.(agentId, streamId, role);
364
+
365
+ if (!decision.allocateWorktree) {
366
+ return undefined;
367
+ }
368
+ const worktree = workspaceManager.allocateWorktree({
369
+ agentId,
370
+ streamId,
371
+ });
372
+ return {
373
+ agentId,
374
+ path: worktree.path,
375
+ branch: `stream/${streamId}`,
376
+ streamId,
377
+ role: 'v3', // V3 path — new-stream
378
+ createdAt: worktree.createdAt,
379
+ };
380
+ }
381
+ }
382
+ }
383
+
250
384
  async function createWorkspaceForRole(
251
385
  agentId: AgentId,
252
386
  role: string,
@@ -254,56 +388,92 @@ export function createAgentManagerV2(
254
388
  ): Promise<Workspace | undefined> {
255
389
  if (!workspaceManager) return undefined;
256
390
 
391
+ // V3 path — TopologyPolicy-driven. Set by boot-v2 when team YAML has
392
+ // `macro_agent.workspace`. When set, this takes precedence over the legacy
393
+ // capability/role-name dispatch below.
394
+ if (topologyPolicy) {
395
+ const decision = await topologyPolicy.onAgentSpawn({
396
+ agentId,
397
+ role,
398
+ parentAgentId: options.parent ?? undefined,
399
+ parentStreamId: options.streamId,
400
+ teamStreamId: (() => {
401
+ const stream = (
402
+ topologyPolicy as { getAgentStream?: (a: AgentId) => string | null }
403
+ ).getAgentStream?.(agentId);
404
+ return stream ?? undefined;
405
+ })(),
406
+ workspaceManager,
407
+ getAgentByRole: (r: string) => {
408
+ for (const [aid, ws] of agentWorkspaces) {
409
+ const rec = agentStore.getAgent(aid);
410
+ if (rec?.role === r) return aid;
411
+ }
412
+ return null;
413
+ },
414
+ });
415
+ return executeWorkspaceDecision(agentId, decision, role, options);
416
+ }
417
+
418
+ // Capability-based dispatch for programmatic callers that don't use
419
+ // team YAML. This is the supported path for libraries that construct
420
+ // WorkspaceManager + GitCascadeAdapter directly and spawn agents with
421
+ // explicit `capabilities` + `streamId` arguments. It coexists with the
422
+ // V3 topology path above.
423
+ return capabilityBasedDispatch(agentId, options, workspaceManager);
424
+ }
425
+
426
+ /**
427
+ * Capability-based workspace allocation for programmatic callers.
428
+ *
429
+ * Matches on `workspace.stream` / `workspace.integrate` / `workspace.worktree`
430
+ * capabilities + corresponding streamId/streamConfig args. Delegates to the
431
+ * role-shaped WorkspaceManager methods (createWorkerWorkspace,
432
+ * createIntegratorWorkspace, createCoordinatorWorkspace).
433
+ *
434
+ * Not used by team-YAML-driven teams — those go through TopologyPolicy above.
435
+ */
436
+ async function capabilityBasedDispatch(
437
+ agentId: AgentId,
438
+ options: SpawnAgentOptions,
439
+ ws: WorkspaceManager
440
+ ): Promise<Workspace | undefined> {
257
441
  const capabilities = options.capabilities ?? [];
258
442
  const streamId = options.streamId;
259
- const streamConfig = options.streamConfig;
260
- const dataplaneTaskId = options.dataplaneTaskId;
443
+ // Merge taskRef (if set at spawn time) into streamConfig.metadata so that
444
+ // adapter.createStream x-cascade/stream.opened carries the binding to
445
+ // OpenTasks. Explicit streamConfig.metadata.task_ref wins if already set.
446
+ const streamConfig = options.streamConfig
447
+ ? options.taskRef &&
448
+ !(options.streamConfig.metadata &&
449
+ (options.streamConfig.metadata as Record<string, unknown>).task_ref)
450
+ ? {
451
+ ...options.streamConfig,
452
+ metadata: {
453
+ ...(options.streamConfig.metadata ?? {}),
454
+ task_ref: options.taskRef,
455
+ },
456
+ }
457
+ : options.streamConfig
458
+ : undefined;
459
+ const gitCascadeTaskId = options.gitCascadeTaskId;
261
460
 
262
- // Capability-based dispatch
263
461
  if (capabilities.includes("workspace.stream") && streamConfig) {
264
- const newStreamId = workspaceManager.createIntegrationStream(
265
- agentId,
266
- streamConfig
267
- );
268
- return workspaceManager.createCoordinatorWorkspace(agentId, newStreamId);
462
+ const newStreamId = ws.createIntegrationStream(agentId, streamConfig);
463
+ return ws.createCoordinatorWorkspace(agentId, newStreamId);
269
464
  }
270
465
 
271
466
  if (capabilities.includes("workspace.integrate") && streamId) {
272
- return workspaceManager.createIntegratorWorkspace(agentId, streamId);
467
+ return ws.createIntegratorWorkspace(agentId, streamId);
273
468
  }
274
469
 
275
470
  if (capabilities.includes("workspace.worktree") && streamId) {
276
- const taskId = dataplaneTaskId ?? agentId;
277
- return workspaceManager.createWorkerWorkspace(agentId, taskId, streamId);
471
+ const taskId = gitCascadeTaskId ?? agentId;
472
+ return ws.createWorkerWorkspace(agentId, taskId, streamId);
278
473
  }
279
474
 
280
- // Role-name fallback
281
- switch (role) {
282
- case "coordinator":
283
- if (streamConfig) {
284
- const sid = workspaceManager.createIntegrationStream(
285
- agentId,
286
- streamConfig
287
- );
288
- return workspaceManager.createCoordinatorWorkspace(agentId, sid);
289
- }
290
- return undefined;
291
- case "integrator":
292
- if (streamId) {
293
- return workspaceManager.createIntegratorWorkspace(agentId, streamId);
294
- }
295
- return undefined;
296
- case "worker":
297
- case "worker.resolver": {
298
- if (streamId) {
299
- const tid = dataplaneTaskId ?? agentId;
300
- return workspaceManager.createWorkerWorkspace(agentId, tid, streamId);
301
- }
302
- return undefined;
303
- }
304
- default:
305
- return undefined;
306
- }
475
+ // No matching capability — agent inherits parent cwd (no workspace)
476
+ return undefined;
307
477
  }
308
478
 
309
479
  // ── Core Lifecycle ───────────────────────────────────────────
@@ -317,10 +487,40 @@ export function createAgentManagerV2(
317
487
  }
318
488
 
319
489
  // Apply spawn interceptor (set by TeamRuntime)
320
- const options = spawnInterceptor
490
+ const interceptedOptions = spawnInterceptor
321
491
  ? await spawnInterceptor(rawOptions)
322
492
  : rawOptions;
323
493
 
494
+ // Resolve taskRef with three-level precedence:
495
+ // 1. Explicit `options.taskRef` (caller knows exactly what graph).
496
+ // 2. `resolveTaskRef(opts)` (multi-graph deployments decide per spawn).
497
+ // 3. `taskResourceId` + `options.task_id` (single-graph default).
498
+ // If none resolves, spawn proceeds with no taskRef — cascade events
499
+ // land without a task binding (hub back-fills from first commit that
500
+ // carries one, if any).
501
+ let resolvedTaskRef = interceptedOptions.taskRef;
502
+ if (!resolvedTaskRef && resolveTaskRef) {
503
+ try {
504
+ resolvedTaskRef = resolveTaskRef(interceptedOptions);
505
+ } catch (err) {
506
+ // Resolver failures must not block spawn. Log + fall through.
507
+ // eslint-disable-next-line no-console
508
+ console.warn(
509
+ "[agent-manager-v2] resolveTaskRef threw; falling back to taskResourceId default:",
510
+ err instanceof Error ? err.message : err
511
+ );
512
+ }
513
+ }
514
+ if (!resolvedTaskRef && taskResourceId && interceptedOptions.task_id) {
515
+ resolvedTaskRef = {
516
+ resource_id: taskResourceId,
517
+ node_id: String(interceptedOptions.task_id),
518
+ };
519
+ }
520
+ const options = resolvedTaskRef === interceptedOptions.taskRef
521
+ ? interceptedOptions
522
+ : { ...interceptedOptions, taskRef: resolvedTaskRef };
523
+
324
524
  const {
325
525
  task,
326
526
  task_id,
@@ -404,7 +604,9 @@ export function createAgentManagerV2(
404
604
  systemPrompt += `\n\n${interactionPatterns.join("\n\n")}`;
405
605
  }
406
606
 
407
- // Persist agent in store
607
+ // Persist agent in store. Stash taskRef in metadata so done()'s
608
+ // lifecycle context can read it without separate plumbing — this is the
609
+ // path that makes per-commit task_ref binding work end-to-end.
408
610
  const now = Date.now() as Timestamp;
409
611
  const agentRecord: AgentRecord = {
410
612
  id: agentId,
@@ -422,7 +624,7 @@ export function createAgentManagerV2(
422
624
  created_at: now,
423
625
  started_at: now,
424
626
  config: agentConfig as Record<string, unknown>,
425
- metadata: {},
627
+ metadata: options.taskRef ? { task_ref: options.taskRef } : {},
426
628
  };
427
629
  agentStore.putAgent(agentRecord);
428
630
 
@@ -456,13 +658,13 @@ export function createAgentManagerV2(
456
658
  if (workspace) {
457
659
  agentWorkspaces.set(agentId, workspace);
458
660
 
459
- // Create and claim dataplane task for workers
661
+ // Create and claim git-cascade task for workers
460
662
  if (
461
663
  workspace.role === "worker" &&
462
664
  workspace.streamId &&
463
665
  workspaceManager
464
666
  ) {
465
- const dpTaskId = options.dataplaneTaskId ?? agentId;
667
+ const dpTaskId = options.gitCascadeTaskId ?? agentId;
466
668
  workspaceManager.createTask(workspace.streamId, {
467
669
  title: task ?? `Task for ${agentId}`,
468
670
  });
@@ -588,9 +790,11 @@ export function createAgentManagerV2(
588
790
  created_at: now,
589
791
  });
590
792
 
591
- // Update agent with provider session ID
793
+ // Update agent with provider session ID. Merge with existing metadata
794
+ // so fields set at spawn time (e.g. task_ref) aren't clobbered.
795
+ const existingMeta = agentStore.getAgent(agentId)?.metadata ?? {};
592
796
  agentStore.updateAgent(agentId, {
593
- metadata: { provider_session_id: session.id },
797
+ metadata: { ...existingMeta, provider_session_id: session.id },
594
798
  });
595
799
 
596
800
  // Register agent in inbox
@@ -675,7 +879,16 @@ export function createAgentManagerV2(
675
879
  healthCheckService.stopForCoordinator(agentId);
676
880
  }
677
881
 
678
- // Submit merge request if worker completed with workspace
882
+ // Land the worker's work if completed with a workspace.
883
+ //
884
+ // V3 path (preferred): look up the role's YAML landing strategy via
885
+ // TopologyPolicy.getRoleConfig and dispatch through
886
+ // WorkspaceManager.land(). This fires cascade events (stream.merged or
887
+ // queue.added) so the hub sees the work. Landing = 'none' short-circuits.
888
+ //
889
+ // Legacy fallback: if no TopologyPolicy is wired or it can't resolve a
890
+ // landing for this role, submit to the legacy MergeQueue as before.
891
+ // Keeps pre-V3 programmatic callers + tests that bypass YAML working.
679
892
  if (
680
893
  workspaceManager &&
681
894
  agentWorkspaces.has(agentId) &&
@@ -683,18 +896,45 @@ export function createAgentManagerV2(
683
896
  ) {
684
897
  const ws = agentWorkspaces.get(agentId)!;
685
898
  if (ws.role === "worker" && ws.streamId) {
686
- try {
687
- const mergeQueue = workspaceManager.getMergeQueue();
688
- if (mergeQueue) {
689
- mergeQueue.submit({
899
+ const roleConfig = topologyPolicy?.getRoleConfig?.(record.role);
900
+ const yamlLandingName = roleConfig?.landing;
901
+ const usingV3Landing =
902
+ typeof yamlLandingName === "string" && yamlLandingName.length > 0;
903
+
904
+ if (usingV3Landing) {
905
+ try {
906
+ const taskRef = (record.metadata as Record<string, unknown> | undefined)
907
+ ?.task_ref as { resource_id: string; node_id: string } | undefined;
908
+ await workspaceManager.land({
909
+ agentId,
690
910
  streamId: ws.streamId,
691
- workerBranch: ws.branch,
692
- taskId: record.task_id ?? agentId,
693
- workerAgentId: agentId,
911
+ sourceWorktree: ws.path,
912
+ strategyName: yamlLandingName,
913
+ strategyConfig: roleConfig?.landing_config,
914
+ taskRef,
915
+ // Dispatcher overwrites this with `this`; placeholder keeps the
916
+ // type satisfied without a cast.
917
+ workspaceManager,
694
918
  });
919
+ } catch {
920
+ // Non-fatal landing failure — agent still terminates; conflicts
921
+ // and strategy errors surface via WorkspaceEvent emission and
922
+ // the strategy's own logs.
923
+ }
924
+ } else {
925
+ try {
926
+ const mergeQueue = workspaceManager.getMergeQueue();
927
+ if (mergeQueue) {
928
+ mergeQueue.submit({
929
+ streamId: ws.streamId,
930
+ workerBranch: ws.branch,
931
+ taskId: record.task_id ?? agentId,
932
+ workerAgentId: agentId,
933
+ });
934
+ }
935
+ } catch {
936
+ // Non-fatal merge queue submission failure
695
937
  }
696
- } catch {
697
- // Non-fatal merge queue submission failure
698
938
  }
699
939
  }
700
940
  }
@@ -783,11 +1023,16 @@ export function createAgentManagerV2(
783
1023
  .map((r) => agentRecordToAgent(r)),
784
1024
  terminate: (id: AgentId, r: AgentStopReason) => terminate(id, r),
785
1025
  };
1026
+ const parentTaskRef = (record.metadata as Record<string, unknown> | undefined)
1027
+ ?.task_ref as { resource_id: string; node_id: string } | undefined;
786
1028
  await terminateWithChangeConsolidation(
787
1029
  child.id as AgentId,
788
1030
  agentId,
789
1031
  cascadeAdapter,
790
- wsProvider
1032
+ wsProvider,
1033
+ undefined,
1034
+ workspaceManager ?? undefined,
1035
+ parentTaskRef
791
1036
  );
792
1037
  }
793
1038
  }
@@ -893,6 +1138,21 @@ export function createAgentManagerV2(
893
1138
  });
894
1139
 
895
1140
  const agent = agentRecordToAgent(agentStore.getAgent(agentId)!);
1141
+
1142
+ // Re-publish the agent to subscribers (local MAP server, hub lifecycle
1143
+ // bridge, team auto-join listeners) so a resumed agent is a first-class
1144
+ // registered agent — not just an in-memory handle. Without this, the hub
1145
+ // never re-registers the agent after cold-start; ACP routing works but
1146
+ // the hub's "Registered Agents" view stays empty and capabilities never
1147
+ // propagate back through `map/agents/register`.
1148
+ //
1149
+ // Spawn semantics are correct here: the process is new, the session is
1150
+ // (re)loaded, and subscribers treat it as a fresh registration. Paired
1151
+ // with the `stopped` event that fired on the prior termination, this
1152
+ // keeps the bridge's `registered` map consistent.
1153
+ notifyLifecycle({ type: "spawned", agent });
1154
+ notifyLifecycle({ type: "started", agent });
1155
+
896
1156
  return {
897
1157
  id: agentId,
898
1158
  session_id: sessionRecord?.session_id ?? "",
@@ -1134,12 +1394,21 @@ export function createAgentManagerV2(
1134
1394
  async function getOrCreateHeadManager(
1135
1395
  options: HeadManagerOptions
1136
1396
  ): Promise<SpawnedAgent> {
1137
- // Check for existing head manager
1397
+ // Check for an existing head manager matching this cwd that ALSO has a
1398
+ // live session in this process. The activeSessions check has to be inside
1399
+ // the predicate (not after .find) — the agentStore is persistent across
1400
+ // process restarts, so without this filter we'd match stale "running"
1401
+ // records from previous processes whose sessions are gone, then fall
1402
+ // through to spawn() and create a duplicate coordinator.
1138
1403
  const existing = agentStore
1139
1404
  .listAgents({ parent_id: null, state: "running" })
1140
- .find((a) => a.cwd === options.cwd);
1405
+ .find(
1406
+ (a) =>
1407
+ a.cwd === options.cwd &&
1408
+ activeSessions.has(a.id as AgentId),
1409
+ );
1141
1410
 
1142
- if (existing && activeSessions.has(existing.id as AgentId)) {
1411
+ if (existing) {
1143
1412
  const sessionEntry = activeSessions.get(existing.id as AgentId)!;
1144
1413
  const storedSession = agentStore.getSession(existing.id as AgentId);
1145
1414
  return {
@@ -1167,6 +1436,30 @@ export function createAgentManagerV2(
1167
1436
  .map(agentRecordToAgent);
1168
1437
  }
1169
1438
 
1439
+ /**
1440
+ * Look up the spawned-agent shape for any agent that's still alive in this
1441
+ * process (any role, not just coordinators). Returns null if the agent
1442
+ * doesn't exist, isn't running, or has no live session in `activeSessions`.
1443
+ *
1444
+ * Used by the ACP layer to bind a session to a specific agent when the MAP
1445
+ * stream targets one explicitly — preserving the routing intent that
1446
+ * cwd-based head-manager lookup would otherwise lose in multi-coordinator
1447
+ * scenarios.
1448
+ */
1449
+ function getActiveAgentSession(agentId: AgentId): SpawnedAgent | null {
1450
+ if (!activeSessions.has(agentId)) return null;
1451
+ const record = agentStore.getAgent(agentId);
1452
+ if (!record || record.state !== "running") return null;
1453
+ const sessionEntry = activeSessions.get(agentId)!;
1454
+ const storedSession = agentStore.getSession(agentId);
1455
+ return {
1456
+ id: agentId,
1457
+ session_id: storedSession?.session_id ?? sessionEntry.session.id ?? "",
1458
+ agent: agentRecordToAgent(record),
1459
+ session: sessionEntry.session,
1460
+ };
1461
+ }
1462
+
1170
1463
  // ── Session Interaction ──────────────────────────────────────
1171
1464
 
1172
1465
  async function* prompt(
@@ -1263,6 +1556,18 @@ export function createAgentManagerV2(
1263
1556
  }
1264
1557
  }
1265
1558
 
1559
+ // Auto-terminate when done() was called and the handler signaled shouldTerminate.
1560
+ // This closes the lifecycle gap: without this, agents stay in "running" state
1561
+ // after calling done() because nothing triggers terminate().
1562
+ if (doneCalled) {
1563
+ const reason = doneStatus === "completed" ? "completed" : (doneStatus ?? "failed");
1564
+ try {
1565
+ await terminate(agentId, reason as any);
1566
+ } catch {
1567
+ // Best effort — agent may already be stopping
1568
+ }
1569
+ }
1570
+
1266
1571
  return { doneCalled, doneStatus, updates: allUpdates };
1267
1572
  }
1268
1573
 
@@ -1380,6 +1685,12 @@ export function createAgentManagerV2(
1380
1685
  sidecarRef = sidecar;
1381
1686
  }
1382
1687
 
1688
+ function setTopologyPolicyFn(
1689
+ policy: import('../workspace/topology/types.js').TopologyPolicy | null
1690
+ ): void {
1691
+ topologyPolicy = policy;
1692
+ }
1693
+
1383
1694
  function setMailServices(): void {
1384
1695
  // No-op: agent-inbox handles conversation tracking
1385
1696
  }
@@ -1422,6 +1733,7 @@ export function createAgentManagerV2(
1422
1733
  getHierarchy,
1423
1734
  getOrCreateHeadManager,
1424
1735
  listHeadManagers,
1736
+ getActiveAgentSession,
1425
1737
  prompt,
1426
1738
  promptUntilDone,
1427
1739
  getSession,
@@ -1441,6 +1753,7 @@ export function createAgentManagerV2(
1441
1753
  setIntegrationConfigs,
1442
1754
  setSkillLoadout,
1443
1755
  setSidecar,
1756
+ setTopologyPolicy: setTopologyPolicyFn,
1444
1757
  setMailServices,
1445
1758
  close,
1446
1759
  } as AgentManager;
@@ -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
  /**
@@ -340,6 +340,30 @@ export class AgentStore {
340
340
  };
341
341
  }
342
342
 
343
+ /**
344
+ * Reverse lookup: find the session row whose provider_session_id matches.
345
+ * Used by `_macro/resumeAgent` to resolve session → agent when only the
346
+ * Claude Code session UUID is known (e.g. OpenHive asks to resume a session
347
+ * by its persisted provider_session_id).
348
+ *
349
+ * Returns the most recently created session if multiple agents ever held
350
+ * the same provider_session_id — shouldn't happen, but defensive.
351
+ */
352
+ findSessionByProviderSessionId(providerSessionId: string): SessionRecord | null {
353
+ const row = this.db
354
+ .prepare(
355
+ "SELECT * FROM sessions WHERE provider_session_id = ? ORDER BY created_at DESC LIMIT 1"
356
+ )
357
+ .get(providerSessionId) as Record<string, unknown> | undefined;
358
+ if (!row) return null;
359
+ return {
360
+ agent_id: row.agent_id as string,
361
+ session_id: row.session_id as string,
362
+ provider_session_id: (row.provider_session_id as string) || undefined,
363
+ created_at: row.created_at as number,
364
+ };
365
+ }
366
+
343
367
  removeSession(agentId: AgentId): void {
344
368
  this.db.prepare("DELETE FROM sessions WHERE agent_id = ?").run(agentId);
345
369
  }