macro-agent 0.1.10 → 0.1.12

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 (111) hide show
  1. package/CLAUDE.md +97 -0
  2. package/dist/acp/macro-agent.d.ts.map +1 -1
  3. package/dist/acp/macro-agent.js +42 -6
  4. package/dist/acp/macro-agent.js.map +1 -1
  5. package/dist/adapters/tasks-adapter.d.ts.map +1 -1
  6. package/dist/adapters/tasks-adapter.js +3 -0
  7. package/dist/adapters/tasks-adapter.js.map +1 -1
  8. package/dist/adapters/types.d.ts +1 -0
  9. package/dist/adapters/types.d.ts.map +1 -1
  10. package/dist/agent/agent-manager-v2.d.ts.map +1 -1
  11. package/dist/agent/agent-manager-v2.js +74 -11
  12. package/dist/agent/agent-manager-v2.js.map +1 -1
  13. package/dist/agent/agent-store.d.ts +10 -0
  14. package/dist/agent/agent-store.d.ts.map +1 -1
  15. package/dist/agent/agent-store.js +22 -0
  16. package/dist/agent/agent-store.js.map +1 -1
  17. package/dist/boot-v2.d.ts +88 -1
  18. package/dist/boot-v2.d.ts.map +1 -1
  19. package/dist/boot-v2.js +343 -7
  20. package/dist/boot-v2.js.map +1 -1
  21. package/dist/cli/acp.js +4 -0
  22. package/dist/cli/acp.js.map +1 -1
  23. package/dist/lifecycle/cascade.d.ts +25 -2
  24. package/dist/lifecycle/cascade.d.ts.map +1 -1
  25. package/dist/lifecycle/cascade.js +70 -2
  26. package/dist/lifecycle/cascade.js.map +1 -1
  27. package/dist/map/cascade-action-handler.d.ts +24 -0
  28. package/dist/map/cascade-action-handler.d.ts.map +1 -0
  29. package/dist/map/cascade-action-handler.js +170 -0
  30. package/dist/map/cascade-action-handler.js.map +1 -0
  31. package/dist/map/cascade-bridge.d.ts.map +1 -1
  32. package/dist/map/cascade-bridge.js +42 -5
  33. package/dist/map/cascade-bridge.js.map +1 -1
  34. package/dist/map/coordination-handler.d.ts.map +1 -1
  35. package/dist/map/coordination-handler.js +12 -1
  36. package/dist/map/coordination-handler.js.map +1 -1
  37. package/dist/map/server.d.ts.map +1 -1
  38. package/dist/map/server.js +172 -1
  39. package/dist/map/server.js.map +1 -1
  40. package/dist/map/sidecar.d.ts.map +1 -1
  41. package/dist/map/sidecar.js +18 -2
  42. package/dist/map/sidecar.js.map +1 -1
  43. package/dist/map/types.d.ts +2 -0
  44. package/dist/map/types.d.ts.map +1 -1
  45. package/dist/teams/seed-defaults.d.ts.map +1 -1
  46. package/dist/teams/seed-defaults.js +6 -2
  47. package/dist/teams/seed-defaults.js.map +1 -1
  48. package/dist/teams/team-loader.d.ts.map +1 -1
  49. package/dist/teams/team-loader.js +17 -1
  50. package/dist/teams/team-loader.js.map +1 -1
  51. package/dist/workspace/git-cascade-adapter.d.ts +1 -1
  52. package/dist/workspace/git-cascade-adapter.d.ts.map +1 -1
  53. package/dist/workspace/git-cascade-adapter.js +26 -0
  54. package/dist/workspace/git-cascade-adapter.js.map +1 -1
  55. package/dist/workspace/landing/merge-to-parent.d.ts.map +1 -1
  56. package/dist/workspace/landing/merge-to-parent.js +1 -0
  57. package/dist/workspace/landing/merge-to-parent.js.map +1 -1
  58. package/dist/workspace/recovery/spawn-resolver.d.ts.map +1 -1
  59. package/dist/workspace/recovery/spawn-resolver.js +8 -1
  60. package/dist/workspace/recovery/spawn-resolver.js.map +1 -1
  61. package/dist/workspace/types-v3.d.ts +7 -0
  62. package/dist/workspace/types-v3.d.ts.map +1 -1
  63. package/dist/workspace/types-v3.js.map +1 -1
  64. package/dist/workspace/types.d.ts +17 -0
  65. package/dist/workspace/types.d.ts.map +1 -1
  66. package/dist/workspace/workspace-manager.d.ts +9 -0
  67. package/dist/workspace/workspace-manager.d.ts.map +1 -1
  68. package/dist/workspace/workspace-manager.js +45 -2
  69. package/dist/workspace/workspace-manager.js.map +1 -1
  70. package/docs/design/task-dispatcher.md +880 -0
  71. package/package.json +3 -3
  72. package/src/__tests__/boot-v2.test.ts +435 -0
  73. package/src/__tests__/e2e/acp-over-map.e2e.test.ts +92 -0
  74. package/src/__tests__/e2e/bootstrap.e2e.test.ts +319 -0
  75. package/src/__tests__/e2e/dispatch-coordination.e2e.test.ts +495 -0
  76. package/src/__tests__/e2e/dispatch-live.e2e.test.ts +564 -0
  77. package/src/__tests__/e2e/dispatch-opentasks.e2e.test.ts +496 -0
  78. package/src/__tests__/e2e/dispatch-phase2-live.e2e.test.ts +456 -0
  79. package/src/__tests__/e2e/dispatch-phase2.e2e.test.ts +386 -0
  80. package/src/__tests__/e2e/dispatch.e2e.test.ts +376 -0
  81. package/src/acp/macro-agent.ts +41 -6
  82. package/src/adapters/__tests__/tasks-adapter.test.ts +1 -0
  83. package/src/adapters/tasks-adapter.ts +3 -0
  84. package/src/adapters/types.ts +1 -0
  85. package/src/agent/__tests__/agent-store.test.ts +52 -0
  86. package/src/agent/agent-manager-v2.ts +79 -11
  87. package/src/agent/agent-store.ts +24 -0
  88. package/src/boot-v2.ts +522 -35
  89. package/src/cli/acp.ts +4 -0
  90. package/src/lifecycle/__tests__/cascade-consolidation.test.ts +240 -0
  91. package/src/lifecycle/cascade.ts +77 -2
  92. package/src/map/__tests__/emit-event.test.ts +71 -0
  93. package/src/map/cascade-action-handler.ts +205 -0
  94. package/src/map/cascade-bridge.ts +43 -5
  95. package/src/map/coordination-handler.ts +13 -1
  96. package/src/map/server.ts +178 -1
  97. package/src/map/sidecar.ts +19 -2
  98. package/src/map/types.ts +3 -0
  99. package/src/teams/seed-defaults.ts +6 -2
  100. package/src/teams/team-loader.ts +18 -1
  101. package/src/workspace/__tests__/land-dispatch.test.ts +214 -0
  102. package/src/workspace/__tests__/self-driving-yaml.test.ts +10 -2
  103. package/src/workspace/git-cascade-adapter.ts +30 -3
  104. package/src/workspace/landing/__tests__/strategies.test.ts +42 -0
  105. package/src/workspace/landing/merge-to-parent.ts +1 -0
  106. package/src/workspace/recovery/spawn-resolver.ts +8 -1
  107. package/src/workspace/types-v3.ts +7 -0
  108. package/src/workspace/types.ts +20 -0
  109. package/src/workspace/workspace-manager.ts +61 -2
  110. package/templates/teams/self-driving/team.yaml +142 -0
  111. package/tsconfig.json +2 -1
@@ -75,6 +75,48 @@ describe('landing strategies', () => {
75
75
  expect.objectContaining({ targetStreamId: 'target-1' })
76
76
  );
77
77
  });
78
+
79
+ it('threads ctx.taskRef into mergeStream metadata when present', async () => {
80
+ const strategy = new MergeToParentStrategy();
81
+ const ws = {
82
+ listStreams: vi.fn(() => []),
83
+ mergeStream: vi.fn(() => ({ success: true, newHead: 'aaa' })),
84
+ } as unknown as WorkspaceManager;
85
+
86
+ const taskRef = { resource_id: 'res-a1', node_id: 'task-a1' };
87
+ await strategy.land({
88
+ agentId: 'agent-1',
89
+ streamId: 'src-1',
90
+ sourceWorktree: '/tmp/wt',
91
+ targetStreamId: 'target-1',
92
+ taskRef,
93
+ workspaceManager: ws,
94
+ });
95
+
96
+ expect(ws.mergeStream).toHaveBeenCalledWith(
97
+ expect.objectContaining({ metadata: { task_ref: taskRef } })
98
+ );
99
+ });
100
+
101
+ it('omits metadata when ctx.taskRef is absent', async () => {
102
+ const strategy = new MergeToParentStrategy();
103
+ const ws = {
104
+ listStreams: vi.fn(() => []),
105
+ mergeStream: vi.fn(() => ({ success: true, newHead: 'bbb' })),
106
+ } as unknown as WorkspaceManager;
107
+
108
+ await strategy.land({
109
+ agentId: 'agent-1',
110
+ streamId: 'src-1',
111
+ sourceWorktree: '/tmp/wt',
112
+ targetStreamId: 'target-1',
113
+ workspaceManager: ws,
114
+ });
115
+
116
+ expect(ws.mergeStream).toHaveBeenCalledWith(
117
+ expect.objectContaining({ metadata: undefined })
118
+ );
119
+ });
78
120
  });
79
121
 
80
122
  describe('QueueToBranchStrategy', () => {
@@ -81,6 +81,7 @@ export class MergeToParentStrategy implements LandingStrategy {
81
81
  targetStreamId,
82
82
  agentId: ctx.agentId,
83
83
  worktree: mergeWorktree.path,
84
+ metadata: ctx.taskRef ? { task_ref: ctx.taskRef } : undefined,
84
85
  });
85
86
 
86
87
  // Cascade rebase on dependents if requested.
@@ -70,7 +70,8 @@ export class SpawnResolverStrategy implements ConflictRecoveryStrategy {
70
70
  };
71
71
  }
72
72
 
73
- // Spawn the resolver
73
+ // Spawn the resolver. Injects MACRO_RECOVERY_STRATEGY + MACRO_CONFLICT_ID
74
+ // so the resolve_conflict MCP tool can tag the resolution correctly.
74
75
  let resolverAgentId: string;
75
76
  try {
76
77
  const spawnOpts: SpawnAgentOptions = {
@@ -78,6 +79,12 @@ export class SpawnResolverStrategy implements ConflictRecoveryStrategy {
78
79
  task: `Resolve conflict ${ctx.conflictId} on stream ${ctx.streamId}`,
79
80
  parent: ctx.landingAgentId,
80
81
  capabilities: ['workspace.commit', 'workspace.resolve', 'workspace.read'],
82
+ config: {
83
+ env: {
84
+ MACRO_RECOVERY_STRATEGY: 'spawn-resolver',
85
+ MACRO_CONFLICT_ID: ctx.conflictId,
86
+ },
87
+ },
81
88
  };
82
89
  const spawned = await this.opts.agentManager.spawn(spawnOpts);
83
90
  resolverAgentId = spawned.id;
@@ -124,6 +124,13 @@ export interface LandingContext {
124
124
  streamId: StreamId;
125
125
  sourceWorktree: string;
126
126
  targetStreamId?: StreamId;
127
+ /**
128
+ * Strategy selector. Accepts either an internal strategy name
129
+ * (`merge-to-parent`, `queue-to-branch`, …) or the YAML form
130
+ * (`merge_to_parent_stream`, `queue_to_branch`, …). `WorkspaceManager.land`
131
+ * normalizes. When undefined, `merge-to-parent` is used.
132
+ */
133
+ strategyName?: string;
127
134
  strategyConfig?: Record<string, unknown>;
128
135
  /** Reference to the manager; strategies call back for merge/cascade. */
129
136
  workspaceManager: unknown; // WorkspaceManager — circular; narrowed at callsite
@@ -353,12 +353,17 @@ export interface WorkspaceManager {
353
353
 
354
354
  /**
355
355
  * Merge a source stream into a target stream.
356
+ *
357
+ * `metadata` is forwarded into the `x-cascade/stream.merged` emit so
358
+ * consumers (e.g., the OpenHive hub) can bind the merge to a task ref.
359
+ * Landing strategies pass `{ task_ref: ctx.taskRef }` through here.
356
360
  */
357
361
  mergeStream(opts: {
358
362
  sourceStreamId: StreamId;
359
363
  targetStreamId: StreamId;
360
364
  agentId: import('./types-v3.js').Principal;
361
365
  worktree: string;
366
+ metadata?: import('git-cascade/events').EventMetadata;
362
367
  }): import('./types-v3.js').MergeResult;
363
368
 
364
369
  /**
@@ -422,6 +427,21 @@ export interface WorkspaceManager {
422
427
  */
423
428
  registerLandingStrategy(strategy: import('./types-v3.js').LandingStrategy): void;
424
429
 
430
+ /**
431
+ * Dispatch a landing. Resolves the strategy by `ctx.strategyName`
432
+ * (defaulting to `merge-to-parent`), accepts both internal and YAML-style
433
+ * names, and invokes the strategy's `land(ctx)`. Strategies call back
434
+ * into this manager via `ctx.workspaceManager`, which is filled in here.
435
+ *
436
+ * Returns the strategy's `MergeResult`. Conflicts are reflected in the
437
+ * result (`success: false` + `conflicts: [...]`); thrown errors indicate
438
+ * unrecoverable failures (unknown strategy, strategy rejected context,
439
+ * transport-level issues).
440
+ */
441
+ land(
442
+ ctx: import('./types-v3.js').LandingContext,
443
+ ): Promise<import('./types-v3.js').MergeResult>;
444
+
425
445
  /**
426
446
  * Run macro-level reconciliation:
427
447
  * - Delegates to git-cascade's `reconcile()` for stream↔git sync.
@@ -1107,9 +1107,18 @@ export class DefaultWorkspaceManager implements WorkspaceManager {
1107
1107
  targetStreamId: StreamId;
1108
1108
  agentId: import('./types-v3.js').Principal;
1109
1109
  worktree: string;
1110
+ /**
1111
+ * Free-form metadata forwarded into the `x-cascade/stream.merged` emit.
1112
+ * The canonical binding is `{ task_ref: { resource_id, node_id } }` —
1113
+ * landing strategies thread `LandingContext.taskRef` through here so the
1114
+ * hub's cascade_merges projection records which task drove the merge.
1115
+ */
1116
+ metadata?: import('git-cascade/events').EventMetadata;
1110
1117
  }): import('./types-v3.js').MergeResult {
1111
- // git-cascade's MergeStreamOptions uses `sourceStream`/`targetStream`.
1112
- // We adapt to v3's `sourceStreamId`/`targetStreamId` at the boundary.
1118
+ // git-cascade's MergeStreamOptions uses `sourceStream`/`targetStream` and
1119
+ // doesn't accept an opts.metadata field the tagging is our concern, not
1120
+ // the tracker's. Carry `opts.metadata` through on the emitted event
1121
+ // instead, which is what the hub's cascade_merges projection reads.
1113
1122
  const result = this.adapter.mergeStream({
1114
1123
  sourceStream: opts.sourceStreamId,
1115
1124
  targetStream: opts.targetStreamId,
@@ -1121,6 +1130,7 @@ export class DefaultWorkspaceManager implements WorkspaceManager {
1121
1130
  sourceStreamId: opts.sourceStreamId,
1122
1131
  targetStreamId: opts.targetStreamId,
1123
1132
  mergeCommit: result.newHead,
1133
+ ...(opts.metadata ? { metadata: opts.metadata } : {}),
1124
1134
  });
1125
1135
  } else {
1126
1136
  this.emit('stream:conflicted', {
@@ -1295,6 +1305,31 @@ export class DefaultWorkspaceManager implements WorkspaceManager {
1295
1305
  this.landingStrategies.set(strategy.name, strategy);
1296
1306
  }
1297
1307
 
1308
+ async land(
1309
+ ctx: import('./types-v3.js').LandingContext,
1310
+ ): Promise<import('./types-v3.js').MergeResult> {
1311
+ const internalName = resolveLandingStrategyName(ctx.strategyName);
1312
+ if (internalName === 'none') {
1313
+ return { success: true, alreadyMerged: true } as import('./types-v3.js').MergeResult;
1314
+ }
1315
+ const strategy = this.landingStrategies.get(internalName);
1316
+ if (!strategy) {
1317
+ throw new Error(
1318
+ `No landing strategy registered for "${internalName}" (from ctx.strategyName="${ctx.strategyName ?? 'merge-to-parent'}"). Registered: ${Array.from(this.landingStrategies.keys()).join(', ') || '<none>'}.`,
1319
+ );
1320
+ }
1321
+ const resolved: import('./types-v3.js').LandingContext = {
1322
+ ...ctx,
1323
+ workspaceManager: this,
1324
+ };
1325
+ if (strategy.canLand && !strategy.canLand(resolved)) {
1326
+ throw new Error(
1327
+ `Landing strategy "${internalName}" rejected context for agent ${ctx.agentId}, stream ${ctx.streamId}`,
1328
+ );
1329
+ }
1330
+ return strategy.land(resolved);
1331
+ }
1332
+
1298
1333
  reconcileV3(): import('./types-v3.js').MacroReconcileResult {
1299
1334
  const result: import('./types-v3.js').MacroReconcileResult = {
1300
1335
  streamsChecked: 0,
@@ -1516,3 +1551,27 @@ export function createWorkspaceManagerWithAdapter(
1516
1551
  ): DefaultWorkspaceManager {
1517
1552
  return new DefaultWorkspaceManager(adapter, config);
1518
1553
  }
1554
+
1555
+ // ═════════════════════════════════════════════════════════════════════════════
1556
+ // Landing strategy name resolution
1557
+ // ═════════════════════════════════════════════════════════════════════════════
1558
+ //
1559
+ // YAML uses snake_case (`merge_to_parent_stream`, `queue_to_branch`, …) to
1560
+ // match the team-config naming convention. Strategy classes expose
1561
+ // kebab-case internal names (`merge-to-parent`, …). `WorkspaceManager.land()`
1562
+ // accepts either and normalizes before dispatch so AgentManagerV2 can pass
1563
+ // `roleConfig.landing` directly without another translation layer.
1564
+
1565
+ const YAML_TO_INTERNAL_LANDING: Record<string, string> = {
1566
+ merge_to_parent_stream: 'merge-to-parent',
1567
+ queue_to_branch: 'queue-to-branch',
1568
+ direct_push: 'direct-push',
1569
+ optimistic_push: 'optimistic-push',
1570
+ cherry_pick_stack: 'cherry-pick-stack',
1571
+ none: 'none',
1572
+ };
1573
+
1574
+ export function resolveLandingStrategyName(input?: string): string {
1575
+ if (!input) return 'merge-to-parent';
1576
+ return YAML_TO_INTERNAL_LANDING[input] ?? input;
1577
+ }
@@ -0,0 +1,142 @@
1
+ name: self-driving
2
+ description: "Cursor-style autonomous codebase development: planners explore and create tasks, grinders claim and execute in their own streams, judges evaluate quality."
3
+ version: 1
4
+
5
+ # ─────────────────────────────────────────────────────────────
6
+ # Roles
7
+ # ─────────────────────────────────────────────────────────────
8
+ roles:
9
+ - planner
10
+ - grinder
11
+ - judge
12
+
13
+ # ─────────────────────────────────────────────────────────────
14
+ # Topology
15
+ # ─────────────────────────────────────────────────────────────
16
+ topology:
17
+ root:
18
+ role: planner
19
+ prompt: prompts/planner.md
20
+ config:
21
+ model: sonnet
22
+ companions:
23
+ - role: judge
24
+ prompt: prompts/judge.md
25
+ config:
26
+ model: haiku
27
+ spawn_rules:
28
+ planner: [grinder, planner]
29
+ judge: []
30
+ grinder: []
31
+
32
+ # ─────────────────────────────────────────────────────────────
33
+ # Communication
34
+ # ─────────────────────────────────────────────────────────────
35
+ communication:
36
+ enforcement: permissive
37
+
38
+ channels:
39
+ task_updates:
40
+ description: "Task lifecycle events"
41
+ signals: [TASK_CREATED, TASK_COMPLETED, TASK_FAILED]
42
+ work_coordination:
43
+ description: "Work assignment and completion"
44
+ signals: [WORK_ASSIGNED, WORKER_DONE, MERGE_REQUEST]
45
+ health:
46
+ description: "System health monitoring"
47
+ signals: [HEALTH_CHECK, METRIC_SNAPSHOT, GREEN_SNAPSHOT, FIXUP_CREATED]
48
+
49
+ subscriptions:
50
+ planner:
51
+ - channel: task_updates
52
+ - channel: work_coordination
53
+ signals: [WORKER_DONE]
54
+ - channel: health
55
+ signals: [METRIC_SNAPSHOT, GREEN_SNAPSHOT, FIXUP_CREATED]
56
+ judge:
57
+ - channel: task_updates
58
+ signals: [TASK_FAILED]
59
+ - channel: work_coordination
60
+ signals: [WORKER_DONE]
61
+ - channel: health
62
+ grinder:
63
+ - channel: work_coordination
64
+ signals: [WORK_ASSIGNED]
65
+
66
+ emissions:
67
+ planner: [TASK_CREATED, WORK_ASSIGNED, PLANNING_COMPLETE]
68
+ judge: [HEALTH_CHECK, GREEN_SNAPSHOT, FIXUP_CREATED]
69
+ grinder: [WORKER_DONE]
70
+
71
+ routing:
72
+ peers:
73
+ - from: judge
74
+ to: planner
75
+ via: direct
76
+ signals: [FIXUP_CREATED, GREEN_SNAPSHOT]
77
+ - from: planner
78
+ to: judge
79
+ via: direct
80
+ signals: [CONVERGENCE_CHECK]
81
+
82
+ # ─────────────────────────────────────────────────────────────
83
+ # macro-agent specific extensions
84
+ # ─────────────────────────────────────────────────────────────
85
+ macro_agent:
86
+ # V3 stream-first workspace topology.
87
+ # Compiled by YamlDrivenTopology into per-spawn WorkspaceDecision values.
88
+ workspace:
89
+ default_stream:
90
+ fork_from: main
91
+ name_template: "{team}"
92
+ change_id_tracking: true
93
+
94
+ on_team_complete: keep
95
+
96
+ roles:
97
+ # Planner shares the team root stream — continuous exploration lives on the
98
+ # same branch as the team's accumulated work; no per-planner fork.
99
+ planner:
100
+ workspace: attach_to_team_root
101
+ capabilities:
102
+ - workspace.read
103
+ - agent.spawn
104
+
105
+ # Grinders fork their own stream off the team root, land via direct-push
106
+ # (trunk integration). A conflict during landing aborts and leaves the
107
+ # stream for human / judge review.
108
+ grinder:
109
+ workspace: new_stream
110
+ stream_lineage: fork_from_team_root
111
+ landing: direct_push
112
+ on_conflict: defer
113
+ capabilities:
114
+ - workspace.commit
115
+ - workspace.land
116
+
117
+ # Judge is read-only — it observes repo state but doesn't need a
118
+ # workspace of its own.
119
+ judge:
120
+ workspace: none
121
+ capabilities:
122
+ - workspace.read
123
+
124
+ # Declarative shorthand read by TeamRuntimeV2.getStrategyName(). `trunk`
125
+ # pairs with the grinder's direct_push landing above: every task lands
126
+ # straight onto the team-root stream rather than going through a merge
127
+ # queue. Kept here as a top-level scalar so runtimes that don't parse the
128
+ # full V3 workspace block (env-var consumers, logs, metrics labels) still
129
+ # see the integration strategy.
130
+ integration:
131
+ strategy: trunk
132
+
133
+ task_assignment:
134
+ mode: pull
135
+ pull:
136
+ idle_timeout_s: 300
137
+ claim_retry_delay_ms: 2000
138
+ max_concurrent_per_agent: 1
139
+
140
+ observability:
141
+ metrics_window_s: 3600
142
+ snapshot_interval_s: 300
package/tsconfig.json CHANGED
@@ -13,7 +13,8 @@
13
13
  "declaration": true,
14
14
  "declarationMap": true,
15
15
  "sourceMap": true,
16
- "resolveJsonModule": true
16
+ "resolveJsonModule": true,
17
+ "types": ["node", "better-sqlite3", "express", "js-yaml", "supertest", "ws"]
17
18
  },
18
19
  "include": ["src/**/*"],
19
20
  "exclude": ["node_modules", "dist", "**/*.test.ts"]