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.
- package/CLAUDE.md +263 -33
- package/README.md +781 -131
- package/dist/acp/claude-code-replay.d.ts +11 -0
- package/dist/acp/claude-code-replay.d.ts.map +1 -0
- package/dist/acp/claude-code-replay.js +190 -0
- package/dist/acp/claude-code-replay.js.map +1 -0
- package/dist/acp/macro-agent.d.ts.map +1 -1
- package/dist/acp/macro-agent.js +192 -7
- package/dist/acp/macro-agent.js.map +1 -1
- package/dist/acp/types.d.ts +9 -0
- package/dist/acp/types.d.ts.map +1 -1
- package/dist/acp/types.js.map +1 -1
- package/dist/adapters/tasks-adapter.d.ts.map +1 -1
- package/dist/adapters/tasks-adapter.js +3 -0
- package/dist/adapters/tasks-adapter.js.map +1 -1
- package/dist/adapters/types.d.ts +1 -0
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/agent/agent-manager-v2.d.ts +21 -0
- package/dist/agent/agent-manager-v2.d.ts.map +1 -1
- package/dist/agent/agent-manager-v2.js +308 -54
- package/dist/agent/agent-manager-v2.js.map +1 -1
- package/dist/agent/agent-manager.d.ts +12 -0
- package/dist/agent/agent-manager.d.ts.map +1 -1
- package/dist/agent/agent-manager.js.map +1 -1
- package/dist/agent/agent-store.d.ts +10 -0
- package/dist/agent/agent-store.d.ts.map +1 -1
- package/dist/agent/agent-store.js +22 -0
- package/dist/agent/agent-store.js.map +1 -1
- package/dist/agent/types.d.ts +15 -2
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/boot-v2.d.ts +129 -1
- package/dist/boot-v2.d.ts.map +1 -1
- package/dist/boot-v2.js +359 -8
- package/dist/boot-v2.js.map +1 -1
- package/dist/cli/acp.js +4 -0
- package/dist/cli/acp.js.map +1 -1
- package/dist/cli/index.js +56 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cognitive/macro-agent-backend.d.ts.map +1 -1
- package/dist/cognitive/macro-agent-backend.js +40 -22
- package/dist/cognitive/macro-agent-backend.js.map +1 -1
- package/dist/integrations/skilltree.d.ts.map +1 -1
- package/dist/integrations/skilltree.js +1 -0
- package/dist/integrations/skilltree.js.map +1 -1
- package/dist/lifecycle/cascade.d.ts +25 -2
- package/dist/lifecycle/cascade.d.ts.map +1 -1
- package/dist/lifecycle/cascade.js +70 -2
- package/dist/lifecycle/cascade.js.map +1 -1
- package/dist/lifecycle/cleanup.d.ts +33 -2
- package/dist/lifecycle/cleanup.d.ts.map +1 -1
- package/dist/lifecycle/cleanup.js +28 -6
- package/dist/lifecycle/cleanup.js.map +1 -1
- package/dist/lifecycle/handlers-v2.d.ts +7 -0
- package/dist/lifecycle/handlers-v2.d.ts.map +1 -1
- package/dist/lifecycle/handlers-v2.js +28 -2
- package/dist/lifecycle/handlers-v2.js.map +1 -1
- package/dist/lifecycle/types.d.ts +11 -0
- package/dist/lifecycle/types.d.ts.map +1 -1
- package/dist/lifecycle/types.js.map +1 -1
- package/dist/map/acp-bridge.d.ts +9 -0
- package/dist/map/acp-bridge.d.ts.map +1 -1
- package/dist/map/acp-bridge.js +15 -2
- package/dist/map/acp-bridge.js.map +1 -1
- package/dist/map/cascade-action-handler.d.ts +24 -0
- package/dist/map/cascade-action-handler.d.ts.map +1 -0
- package/dist/map/cascade-action-handler.js +170 -0
- package/dist/map/cascade-action-handler.js.map +1 -0
- package/dist/map/cascade-bridge.d.ts +44 -0
- package/dist/map/cascade-bridge.d.ts.map +1 -0
- package/dist/map/cascade-bridge.js +294 -0
- package/dist/map/cascade-bridge.js.map +1 -0
- package/dist/map/coordination-handler.d.ts.map +1 -1
- package/dist/map/coordination-handler.js +12 -1
- package/dist/map/coordination-handler.js.map +1 -1
- package/dist/map/lifecycle-bridge.d.ts +1 -1
- package/dist/map/lifecycle-bridge.d.ts.map +1 -1
- package/dist/map/lifecycle-bridge.js +58 -23
- package/dist/map/lifecycle-bridge.js.map +1 -1
- package/dist/map/server.d.ts.map +1 -1
- package/dist/map/server.js +219 -7
- package/dist/map/server.js.map +1 -1
- package/dist/map/sidecar.d.ts.map +1 -1
- package/dist/map/sidecar.js +49 -2
- package/dist/map/sidecar.js.map +1 -1
- package/dist/map/types.d.ts +22 -0
- package/dist/map/types.d.ts.map +1 -1
- package/dist/mcp/tools/done-v2.d.ts.map +1 -1
- package/dist/mcp/tools/done-v2.js +8 -0
- package/dist/mcp/tools/done-v2.js.map +1 -1
- package/dist/teams/team-manager-v2.d.ts.map +1 -1
- package/dist/teams/team-manager-v2.js +26 -0
- package/dist/teams/team-manager-v2.js.map +1 -1
- package/dist/teams/team-runtime-v2.d.ts.map +1 -1
- package/dist/teams/team-runtime-v2.js +16 -3
- package/dist/teams/team-runtime-v2.js.map +1 -1
- package/dist/workspace/config.d.ts +10 -10
- package/dist/workspace/config.d.ts.map +1 -1
- package/dist/workspace/config.js +4 -4
- package/dist/workspace/config.js.map +1 -1
- package/dist/workspace/git-cascade-adapter.d.ts +510 -0
- package/dist/workspace/git-cascade-adapter.d.ts.map +1 -0
- package/dist/workspace/git-cascade-adapter.js +934 -0
- package/dist/workspace/git-cascade-adapter.js.map +1 -0
- package/dist/workspace/index.d.ts +3 -3
- package/dist/workspace/index.d.ts.map +1 -1
- package/dist/workspace/index.js +4 -4
- package/dist/workspace/index.js.map +1 -1
- package/dist/workspace/landing/direct-push.d.ts +20 -0
- package/dist/workspace/landing/direct-push.d.ts.map +1 -0
- package/dist/workspace/landing/direct-push.js +74 -0
- package/dist/workspace/landing/direct-push.js.map +1 -0
- package/dist/workspace/landing/index.d.ts +29 -0
- package/dist/workspace/landing/index.d.ts.map +1 -0
- package/dist/workspace/landing/index.js +37 -0
- package/dist/workspace/landing/index.js.map +1 -0
- package/dist/workspace/landing/merge-to-parent.d.ts +41 -0
- package/dist/workspace/landing/merge-to-parent.d.ts.map +1 -0
- package/dist/workspace/landing/merge-to-parent.js +186 -0
- package/dist/workspace/landing/merge-to-parent.js.map +1 -0
- package/dist/workspace/landing/optimistic-push.d.ts +16 -0
- package/dist/workspace/landing/optimistic-push.d.ts.map +1 -0
- package/dist/workspace/landing/optimistic-push.js +27 -0
- package/dist/workspace/landing/optimistic-push.js.map +1 -0
- package/dist/workspace/landing/queue-to-branch.d.ts +24 -0
- package/dist/workspace/landing/queue-to-branch.d.ts.map +1 -0
- package/dist/workspace/landing/queue-to-branch.js +79 -0
- package/dist/workspace/landing/queue-to-branch.js.map +1 -0
- package/dist/workspace/merge-queue/merge-queue.d.ts +10 -0
- package/dist/workspace/merge-queue/merge-queue.d.ts.map +1 -1
- package/dist/workspace/merge-queue/merge-queue.js +10 -0
- package/dist/workspace/merge-queue/merge-queue.js.map +1 -1
- package/dist/workspace/merge-queue/types.d.ts +16 -2
- package/dist/workspace/merge-queue/types.d.ts.map +1 -1
- package/dist/workspace/merge-queue/types.js +9 -0
- package/dist/workspace/merge-queue/types.js.map +1 -1
- package/dist/workspace/pool/types.d.ts +1 -0
- package/dist/workspace/pool/types.d.ts.map +1 -1
- package/dist/workspace/pool/worktree-pool.d.ts.map +1 -1
- package/dist/workspace/pool/worktree-pool.js +1 -0
- package/dist/workspace/pool/worktree-pool.js.map +1 -1
- package/dist/workspace/recovery/abandon.d.ts +15 -0
- package/dist/workspace/recovery/abandon.d.ts.map +1 -0
- package/dist/workspace/recovery/abandon.js +45 -0
- package/dist/workspace/recovery/abandon.js.map +1 -0
- package/dist/workspace/recovery/auto-resolve.d.ts +27 -0
- package/dist/workspace/recovery/auto-resolve.d.ts.map +1 -0
- package/dist/workspace/recovery/auto-resolve.js +99 -0
- package/dist/workspace/recovery/auto-resolve.js.map +1 -0
- package/dist/workspace/recovery/defer.d.ts +15 -0
- package/dist/workspace/recovery/defer.d.ts.map +1 -0
- package/dist/workspace/recovery/defer.js +16 -0
- package/dist/workspace/recovery/defer.js.map +1 -0
- package/dist/workspace/recovery/escalate.d.ts +16 -0
- package/dist/workspace/recovery/escalate.d.ts.map +1 -0
- package/dist/workspace/recovery/escalate.js +24 -0
- package/dist/workspace/recovery/escalate.js.map +1 -0
- package/dist/workspace/recovery/index.d.ts +32 -0
- package/dist/workspace/recovery/index.d.ts.map +1 -0
- package/dist/workspace/recovery/index.js +45 -0
- package/dist/workspace/recovery/index.js.map +1 -0
- package/dist/workspace/recovery/spawn-resolver.d.ts +45 -0
- package/dist/workspace/recovery/spawn-resolver.d.ts.map +1 -0
- package/dist/workspace/recovery/spawn-resolver.js +118 -0
- package/dist/workspace/recovery/spawn-resolver.js.map +1 -0
- package/dist/workspace/recovery/types.d.ts +63 -0
- package/dist/workspace/recovery/types.d.ts.map +1 -0
- package/dist/workspace/recovery/types.js +12 -0
- package/dist/workspace/recovery/types.js.map +1 -0
- package/dist/workspace/topology/index.d.ts +9 -0
- package/dist/workspace/topology/index.d.ts.map +1 -0
- package/dist/workspace/topology/index.js +8 -0
- package/dist/workspace/topology/index.js.map +1 -0
- package/dist/workspace/topology/no-workspace.d.ts +18 -0
- package/dist/workspace/topology/no-workspace.d.ts.map +1 -0
- package/dist/workspace/topology/no-workspace.js +25 -0
- package/dist/workspace/topology/no-workspace.js.map +1 -0
- package/dist/workspace/topology/types.d.ts +97 -0
- package/dist/workspace/topology/types.d.ts.map +1 -0
- package/dist/workspace/topology/types.js +20 -0
- package/dist/workspace/topology/types.js.map +1 -0
- package/dist/workspace/topology/yaml-driven.d.ts +69 -0
- package/dist/workspace/topology/yaml-driven.d.ts.map +1 -0
- package/dist/workspace/topology/yaml-driven.js +273 -0
- package/dist/workspace/topology/yaml-driven.js.map +1 -0
- package/dist/workspace/types-v3.d.ts +117 -0
- package/dist/workspace/types-v3.d.ts.map +1 -0
- package/dist/workspace/types-v3.js +20 -0
- package/dist/workspace/types-v3.js.map +1 -0
- package/dist/workspace/types.d.ts +162 -17
- package/dist/workspace/types.d.ts.map +1 -1
- package/dist/workspace/workspace-manager.d.ts +101 -13
- package/dist/workspace/workspace-manager.d.ts.map +1 -1
- package/dist/workspace/workspace-manager.js +416 -13
- package/dist/workspace/workspace-manager.js.map +1 -1
- package/dist/workspace/yaml-schema.d.ts +254 -0
- package/dist/workspace/yaml-schema.d.ts.map +1 -0
- package/dist/workspace/yaml-schema.js +170 -0
- package/dist/workspace/yaml-schema.js.map +1 -0
- package/docs/conflict-recovery.md +472 -0
- package/docs/design/task-dispatcher.md +880 -0
- package/docs/git-cascade-integration-gaps.md +678 -0
- package/docs/workspace-interfaces.md +731 -0
- package/docs/workspace-redesign-plan.md +302 -0
- package/package.json +6 -5
- package/src/__tests__/boot-v2.test.ts +435 -0
- package/src/__tests__/e2e/acp-over-map.e2e.test.ts +92 -0
- package/src/__tests__/e2e/auto-sync.e2e.test.ts +257 -0
- package/src/__tests__/e2e/bootstrap.e2e.test.ts +319 -0
- package/src/__tests__/e2e/cascade-rebase.e2e.test.ts +254 -0
- package/src/__tests__/e2e/cli-run.e2e.test.ts +167 -0
- package/src/__tests__/e2e/dispatch-coordination.e2e.test.ts +495 -0
- package/src/__tests__/e2e/dispatch-live.e2e.test.ts +564 -0
- package/src/__tests__/e2e/dispatch-opentasks.e2e.test.ts +496 -0
- package/src/__tests__/e2e/dispatch-phase2-live.e2e.test.ts +456 -0
- package/src/__tests__/e2e/dispatch-phase2.e2e.test.ts +386 -0
- package/src/__tests__/e2e/dispatch.e2e.test.ts +376 -0
- package/src/__tests__/e2e/self-driving-v3.e2e.test.ts +197 -0
- package/src/__tests__/e2e/spawn-resolver.e2e.test.ts +200 -0
- package/src/__tests__/e2e/workspace-lifecycle.e2e.test.ts +30 -22
- package/src/__tests__/e2e/workspace-v3.e2e.test.ts +413 -0
- package/src/acp/__tests__/claude-code-replay.test.ts +225 -0
- package/src/acp/__tests__/macro-agent.test.ts +39 -1
- package/src/acp/claude-code-replay.ts +208 -0
- package/src/acp/macro-agent.ts +203 -10
- package/src/acp/types.ts +10 -0
- package/src/adapters/__tests__/tasks-adapter.test.ts +1 -0
- package/src/adapters/tasks-adapter.ts +3 -0
- package/src/adapters/types.ts +1 -0
- package/src/agent/__tests__/agent-manager-topology.test.ts +73 -0
- package/src/agent/__tests__/agent-manager-v2.test.ts +66 -0
- package/src/agent/__tests__/agent-store.test.ts +52 -0
- package/src/agent/__tests__/task-ref-resolution.test.ts +231 -0
- package/src/agent/agent-manager-v2.ts +372 -59
- package/src/agent/agent-manager.ts +14 -0
- package/src/agent/agent-store.ts +24 -0
- package/src/agent/types.ts +16 -2
- package/src/boot-v2.ts +589 -35
- package/src/cli/acp.ts +4 -0
- package/src/cli/index.ts +61 -0
- package/src/cognitive/macro-agent-backend.ts +45 -29
- package/src/integrations/skilltree.ts +1 -0
- package/src/lifecycle/__tests__/cascade-consolidation.test.ts +240 -0
- package/src/lifecycle/cascade.ts +77 -2
- package/src/lifecycle/cleanup.ts +52 -3
- package/src/lifecycle/handlers-v2.ts +40 -3
- package/src/lifecycle/types.ts +12 -0
- package/src/map/__tests__/cascade-bridge.test.ts +229 -0
- package/src/map/__tests__/emit-event.test.ts +71 -0
- package/src/map/__tests__/lifecycle-bridge.test.ts +86 -10
- package/src/map/acp-bridge.ts +26 -3
- package/src/map/cascade-action-handler.ts +205 -0
- package/src/map/cascade-bridge.ts +339 -0
- package/src/map/coordination-handler.ts +13 -1
- package/src/map/lifecycle-bridge.ts +52 -17
- package/src/map/server.ts +225 -7
- package/src/map/sidecar.ts +48 -1
- package/src/map/types.ts +23 -0
- package/src/mcp/tools/done-v2.ts +9 -0
- package/src/teams/team-manager-v2.ts +37 -0
- package/src/teams/team-runtime-v2.ts +23 -3
- package/src/workspace/__tests__/{dataplane-adapter.test.ts → git-cascade-adapter.test.ts} +209 -14
- package/src/workspace/__tests__/land-dispatch.test.ts +214 -0
- package/src/workspace/__tests__/self-driving-yaml.test.ts +114 -0
- package/src/workspace/__tests__/shared-worktree-refcount.test.ts +154 -0
- package/src/workspace/__tests__/standalone-mode.test.ts +118 -0
- package/src/workspace/__tests__/workspace-manager-v3.test.ts +245 -0
- package/src/workspace/__tests__/yaml-schema.test.ts +210 -0
- package/src/workspace/config.ts +11 -11
- package/src/workspace/git-cascade-adapter.ts +1213 -0
- package/src/workspace/index.ts +11 -11
- package/src/workspace/landing/__tests__/strategies.test.ts +184 -0
- package/src/workspace/landing/direct-push.ts +91 -0
- package/src/workspace/landing/index.ts +40 -0
- package/src/workspace/landing/merge-to-parent.ts +229 -0
- package/src/workspace/landing/optimistic-push.ts +36 -0
- package/src/workspace/landing/queue-to-branch.ts +108 -0
- package/src/workspace/merge-queue/merge-queue.ts +10 -0
- package/src/workspace/merge-queue/types.ts +16 -2
- package/src/workspace/pool/__tests__/worktree-pool.integration.test.ts +5 -5
- package/src/workspace/pool/types.ts +1 -0
- package/src/workspace/pool/worktree-pool.ts +1 -0
- package/src/workspace/recovery/__tests__/auto-resolve-integration.test.ts +127 -0
- package/src/workspace/recovery/__tests__/spawn-resolver.test.ts +139 -0
- package/src/workspace/recovery/__tests__/strategies.test.ts +145 -0
- package/src/workspace/recovery/abandon.ts +51 -0
- package/src/workspace/recovery/auto-resolve.ts +119 -0
- package/src/workspace/recovery/defer.ts +23 -0
- package/src/workspace/recovery/escalate.ts +30 -0
- package/src/workspace/recovery/index.ts +58 -0
- package/src/workspace/recovery/spawn-resolver.ts +152 -0
- package/src/workspace/recovery/types.ts +54 -0
- package/src/workspace/topology/__tests__/yaml-driven.test.ts +345 -0
- package/src/workspace/topology/index.ts +18 -0
- package/src/workspace/topology/no-workspace.ts +39 -0
- package/src/workspace/topology/types.ts +116 -0
- package/src/workspace/topology/yaml-driven.ts +316 -0
- package/src/workspace/types-v3.ts +162 -0
- package/src/workspace/types.ts +211 -20
- package/src/workspace/workspace-manager.ts +533 -19
- package/src/workspace/yaml-schema.ts +216 -0
- package/dist/workspace/dataplane-adapter.d.ts +0 -260
- package/dist/workspace/dataplane-adapter.d.ts.map +0 -1
- package/dist/workspace/dataplane-adapter.js +0 -416
- package/dist/workspace/dataplane-adapter.js.map +0 -1
- package/src/workspace/dataplane-adapter.ts +0 -546
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `abandon` conflict recovery strategy.
|
|
3
|
+
*
|
|
4
|
+
* Abandons the conflicted stream. Throwaway-exploration teams; CI-driven
|
|
5
|
+
* flows where broken work is discarded rather than resolved.
|
|
6
|
+
*
|
|
7
|
+
* @module workspace/recovery/abandon
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
ConflictContext,
|
|
12
|
+
ConflictRecoveryStrategy,
|
|
13
|
+
ConflictResolution,
|
|
14
|
+
} from './types.js';
|
|
15
|
+
|
|
16
|
+
export class AbandonStrategy implements ConflictRecoveryStrategy {
|
|
17
|
+
readonly name = 'abandon';
|
|
18
|
+
readonly mode = 'sync' as const;
|
|
19
|
+
|
|
20
|
+
async recover(ctx: ConflictContext): Promise<ConflictResolution> {
|
|
21
|
+
try {
|
|
22
|
+
// Mark the conflict resolved (method='abandoned') so the OpenHive hub
|
|
23
|
+
// moves cascade_conflicts.status from pending → resolved instead of
|
|
24
|
+
// showing it stuck pending forever.
|
|
25
|
+
try {
|
|
26
|
+
ctx.workspaceManager.resolveConflict({
|
|
27
|
+
conflictId: ctx.conflictId,
|
|
28
|
+
resolvedBy: 'system:abandon',
|
|
29
|
+
method: 'abandoned',
|
|
30
|
+
summary: `stream abandoned: ${ctx.streamId}`,
|
|
31
|
+
});
|
|
32
|
+
} catch {
|
|
33
|
+
// Non-fatal — abandonStream below still runs
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
ctx.workspaceManager.abandonStream(ctx.streamId, {
|
|
37
|
+
reason: `abandon strategy: conflict ${ctx.conflictId}`,
|
|
38
|
+
});
|
|
39
|
+
return {
|
|
40
|
+
kind: 'abandoned',
|
|
41
|
+
streamId: ctx.streamId,
|
|
42
|
+
reason: `conflict ${ctx.conflictId}`,
|
|
43
|
+
};
|
|
44
|
+
} catch (err) {
|
|
45
|
+
return {
|
|
46
|
+
kind: 'failed',
|
|
47
|
+
error: err instanceof Error ? err.message : String(err),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `auto-resolve` conflict recovery strategy.
|
|
3
|
+
*
|
|
4
|
+
* Replays the failed merge in the agent's worktree with a git strategy
|
|
5
|
+
* option (`-X ours` / `-X theirs` / `-s union` via `-X`), commits the
|
|
6
|
+
* resolution, and returns the resulting commit hash.
|
|
7
|
+
*
|
|
8
|
+
* Strategy config:
|
|
9
|
+
* - `strategy`: 'ours' | 'theirs' | 'union' (default: 'ours')
|
|
10
|
+
* - `commit_message`: string (default: `resolve: ${conflictId} via <strategy>`)
|
|
11
|
+
*
|
|
12
|
+
* Scope limits:
|
|
13
|
+
* - Only handles `operation: 'merge'`. Rebase conflicts need different tooling.
|
|
14
|
+
* - Requires `ctx.worktree` to be set. If absent, returns `failed`.
|
|
15
|
+
* - Requires `ctx.sourceCommit` to identify what to replay. If absent,
|
|
16
|
+
* tries `HEAD^2` (the merge commit's incoming side).
|
|
17
|
+
*
|
|
18
|
+
* @module workspace/recovery/auto-resolve
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { execSync } from 'child_process';
|
|
22
|
+
import type {
|
|
23
|
+
ConflictContext,
|
|
24
|
+
ConflictRecoveryStrategy,
|
|
25
|
+
ConflictResolution,
|
|
26
|
+
} from './types.js';
|
|
27
|
+
|
|
28
|
+
export class AutoResolveStrategy implements ConflictRecoveryStrategy {
|
|
29
|
+
readonly name = 'auto-resolve';
|
|
30
|
+
readonly mode = 'sync' as const;
|
|
31
|
+
|
|
32
|
+
canHandle(ctx: ConflictContext): boolean {
|
|
33
|
+
return ctx.operation === 'merge' && !!ctx.worktree;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async recover(ctx: ConflictContext): Promise<ConflictResolution> {
|
|
37
|
+
if (ctx.operation !== 'merge') {
|
|
38
|
+
return {
|
|
39
|
+
kind: 'failed',
|
|
40
|
+
error: `auto-resolve only handles merge conflicts, not ${ctx.operation}`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!ctx.worktree) {
|
|
45
|
+
return {
|
|
46
|
+
kind: 'failed',
|
|
47
|
+
error: 'auto-resolve requires ctx.worktree',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const strategy = (ctx.strategyConfig?.strategy as string | undefined) ?? 'ours';
|
|
52
|
+
if (strategy !== 'ours' && strategy !== 'theirs' && strategy !== 'union') {
|
|
53
|
+
return {
|
|
54
|
+
kind: 'failed',
|
|
55
|
+
error: `auto-resolve: unsupported strategy "${strategy}" (use ours | theirs | union)`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const sourceRef = ctx.sourceCommit ?? 'MERGE_HEAD';
|
|
60
|
+
const commitMessage =
|
|
61
|
+
(ctx.strategyConfig?.commit_message as string | undefined) ??
|
|
62
|
+
`resolve: ${ctx.conflictId} via ${strategy}`;
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
// Abort any in-progress merge state first (safety — merge may have
|
|
66
|
+
// left the index in a partial state)
|
|
67
|
+
try {
|
|
68
|
+
execSync('git merge --abort', {
|
|
69
|
+
cwd: ctx.worktree,
|
|
70
|
+
stdio: 'pipe',
|
|
71
|
+
});
|
|
72
|
+
} catch {
|
|
73
|
+
// No merge in progress — that's fine
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Replay the merge with the chosen strategy
|
|
77
|
+
execSync(
|
|
78
|
+
`git merge -X ${strategy} --no-edit -m ${quote(commitMessage)} ${quote(sourceRef)}`,
|
|
79
|
+
{
|
|
80
|
+
cwd: ctx.worktree,
|
|
81
|
+
stdio: 'pipe',
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Capture the new HEAD as the resolution commit
|
|
86
|
+
const resolutionCommit = execSync('git rev-parse HEAD', {
|
|
87
|
+
cwd: ctx.worktree,
|
|
88
|
+
encoding: 'utf-8',
|
|
89
|
+
}).trim();
|
|
90
|
+
|
|
91
|
+
// Notify the WorkspaceManager so downstream consumers see the resolution
|
|
92
|
+
try {
|
|
93
|
+
ctx.workspaceManager.resolveConflict({
|
|
94
|
+
conflictId: ctx.conflictId,
|
|
95
|
+
resolvedBy: ctx.landingAgentId ?? 'system:auto-resolve',
|
|
96
|
+
resolutionCommit,
|
|
97
|
+
method: 'auto-resolve',
|
|
98
|
+
summary: `merged with -X ${strategy}`,
|
|
99
|
+
});
|
|
100
|
+
} catch {
|
|
101
|
+
// Non-fatal — resolution is recorded via return value regardless
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { kind: 'resolved', resolutionCommit };
|
|
105
|
+
} catch (err) {
|
|
106
|
+
return {
|
|
107
|
+
kind: 'failed',
|
|
108
|
+
error: `auto-resolve (strategy=${strategy}): ${
|
|
109
|
+
err instanceof Error ? err.message : String(err)
|
|
110
|
+
}`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Minimal shell-safe single-quoting for commit messages / refs. */
|
|
117
|
+
function quote(s: string): string {
|
|
118
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
119
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `defer` conflict recovery strategy — no-op.
|
|
3
|
+
*
|
|
4
|
+
* Leaves the conflict record in place; stream stays `conflicted`. Something
|
|
5
|
+
* else (human, external process, scheduled recovery) resolves later.
|
|
6
|
+
*
|
|
7
|
+
* @module workspace/recovery/defer
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type {
|
|
11
|
+
ConflictContext,
|
|
12
|
+
ConflictRecoveryStrategy,
|
|
13
|
+
ConflictResolution,
|
|
14
|
+
} from './types.js';
|
|
15
|
+
|
|
16
|
+
export class DeferStrategy implements ConflictRecoveryStrategy {
|
|
17
|
+
readonly name = 'defer';
|
|
18
|
+
readonly mode = 'sync' as const;
|
|
19
|
+
|
|
20
|
+
async recover(_ctx: ConflictContext): Promise<ConflictResolution> {
|
|
21
|
+
return { kind: 'deferred', reason: 'no recovery strategy configured' };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `escalate` conflict recovery strategy — human-in-the-loop.
|
|
3
|
+
*
|
|
4
|
+
* Pauses the stream and emits an escalation marker. External systems (UI,
|
|
5
|
+
* on-call agent, operator) resolve the conflict manually, then call
|
|
6
|
+
* `resolve_conflict` MCP tool to unblock.
|
|
7
|
+
*
|
|
8
|
+
* @module workspace/recovery/escalate
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
ConflictContext,
|
|
13
|
+
ConflictRecoveryStrategy,
|
|
14
|
+
ConflictResolution,
|
|
15
|
+
} from './types.js';
|
|
16
|
+
|
|
17
|
+
export class EscalateStrategy implements ConflictRecoveryStrategy {
|
|
18
|
+
readonly name = 'escalate';
|
|
19
|
+
readonly mode = 'async' as const;
|
|
20
|
+
|
|
21
|
+
async recover(ctx: ConflictContext): Promise<ConflictResolution> {
|
|
22
|
+
try {
|
|
23
|
+
ctx.workspaceManager.pauseStream(ctx.streamId, 'awaiting human resolution');
|
|
24
|
+
} catch {
|
|
25
|
+
// Non-fatal — stream may already be in a paused/conflicted state
|
|
26
|
+
}
|
|
27
|
+
const target = (ctx.strategyConfig?.notify as string | undefined) ?? 'human';
|
|
28
|
+
return { kind: 'escalated', escalatedTo: target as 'human' };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conflict recovery strategies.
|
|
3
|
+
*
|
|
4
|
+
* Register via `WorkspaceManager.registerConflictRecoveryStrategy` (Phase 7b).
|
|
5
|
+
* Selected via per-role YAML `on_conflict_recovery` or team default.
|
|
6
|
+
*
|
|
7
|
+
* Built-ins:
|
|
8
|
+
* - `defer` — no-op; leave conflict record for external resolution
|
|
9
|
+
* - `abandon` — abandon the conflicted stream
|
|
10
|
+
* - `escalate` — pause stream; notify human
|
|
11
|
+
* - `auto-resolve` — git strategies (ours/theirs/union) — scaffold only
|
|
12
|
+
* - `spawn-resolver` — LLM resolver agent (Phase 7b; requires AgentManager)
|
|
13
|
+
*
|
|
14
|
+
* @module workspace/recovery
|
|
15
|
+
* @see docs/conflict-recovery.md
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export type {
|
|
19
|
+
ConflictContext,
|
|
20
|
+
ConflictRecoveryStrategy,
|
|
21
|
+
ConflictResolution,
|
|
22
|
+
ConflictResolutionMode,
|
|
23
|
+
} from './types.js';
|
|
24
|
+
|
|
25
|
+
export { DeferStrategy } from './defer.js';
|
|
26
|
+
export { AbandonStrategy } from './abandon.js';
|
|
27
|
+
export { EscalateStrategy } from './escalate.js';
|
|
28
|
+
export { AutoResolveStrategy } from './auto-resolve.js';
|
|
29
|
+
export {
|
|
30
|
+
SpawnResolverStrategy,
|
|
31
|
+
createSpawnResolverStrategy,
|
|
32
|
+
type SpawnResolverStrategyOptions,
|
|
33
|
+
} from './spawn-resolver.js';
|
|
34
|
+
|
|
35
|
+
import { DeferStrategy } from './defer.js';
|
|
36
|
+
import { AbandonStrategy } from './abandon.js';
|
|
37
|
+
import { EscalateStrategy } from './escalate.js';
|
|
38
|
+
import { AutoResolveStrategy } from './auto-resolve.js';
|
|
39
|
+
import type { ConflictRecoveryStrategy } from './types.js';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Build a registry of built-in conflict recovery strategies.
|
|
43
|
+
*
|
|
44
|
+
* Returns a Map keyed by strategy name. Callers plug this into their dispatch
|
|
45
|
+
* layer (Phase 7b). `spawn-resolver` is not included — it requires
|
|
46
|
+
* AgentManager injection and is added by the consumer.
|
|
47
|
+
*/
|
|
48
|
+
export function buildBuiltinRecoveryRegistry(): Map<string, ConflictRecoveryStrategy> {
|
|
49
|
+
const map = new Map<string, ConflictRecoveryStrategy>();
|
|
50
|
+
const strategies: ConflictRecoveryStrategy[] = [
|
|
51
|
+
new DeferStrategy(),
|
|
52
|
+
new AbandonStrategy(),
|
|
53
|
+
new EscalateStrategy(),
|
|
54
|
+
new AutoResolveStrategy(),
|
|
55
|
+
];
|
|
56
|
+
for (const s of strategies) map.set(s.name, s);
|
|
57
|
+
return map;
|
|
58
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `spawn-resolver` conflict recovery strategy.
|
|
3
|
+
*
|
|
4
|
+
* Async strategy that spawns a dedicated resolver agent on the conflicted
|
|
5
|
+
* stream. The resolver reads conflict markers, resolves, commits, and calls
|
|
6
|
+
* the `resolve_conflict` MCP tool — unblocking the original landing.
|
|
7
|
+
*
|
|
8
|
+
* Strategy config:
|
|
9
|
+
* - `role`: string (default 'resolver') — role to spawn
|
|
10
|
+
* - `max_concurrent`: number (default 2) — cap on simultaneous resolvers per stream
|
|
11
|
+
* - `timeout_ms`: number (default 1,800,000 = 30 min) — fallback to escalate
|
|
12
|
+
*
|
|
13
|
+
* Unlike the other built-in strategies, this one requires an `AgentManager`
|
|
14
|
+
* reference at construction time. Callers inject it via the factory. That's
|
|
15
|
+
* why it's not in `buildBuiltinRecoveryRegistry()` — consumers register it
|
|
16
|
+
* explicitly after constructing AgentManager.
|
|
17
|
+
*
|
|
18
|
+
* @module workspace/recovery/spawn-resolver
|
|
19
|
+
* @see docs/conflict-recovery.md §4.3
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { AgentManager } from '../../agent/agent-manager.js';
|
|
23
|
+
import type { SpawnAgentOptions } from '../../agent/types.js';
|
|
24
|
+
import type {
|
|
25
|
+
ConflictContext,
|
|
26
|
+
ConflictRecoveryStrategy,
|
|
27
|
+
ConflictResolution,
|
|
28
|
+
} from './types.js';
|
|
29
|
+
|
|
30
|
+
export interface SpawnResolverStrategyOptions {
|
|
31
|
+
agentManager: AgentManager;
|
|
32
|
+
/** Default role to spawn if not overridden in strategyConfig. */
|
|
33
|
+
defaultRole?: string;
|
|
34
|
+
/** Default timeout in ms. */
|
|
35
|
+
defaultTimeoutMs?: number;
|
|
36
|
+
/** Default concurrency cap per stream. */
|
|
37
|
+
defaultMaxConcurrent?: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class SpawnResolverStrategy implements ConflictRecoveryStrategy {
|
|
41
|
+
readonly name = 'spawn-resolver';
|
|
42
|
+
readonly mode = 'async' as const;
|
|
43
|
+
|
|
44
|
+
// Tracks in-progress resolvers per stream to enforce max_concurrent.
|
|
45
|
+
private readonly activeByStream = new Map<string, Set<string>>();
|
|
46
|
+
|
|
47
|
+
constructor(private readonly opts: SpawnResolverStrategyOptions) {}
|
|
48
|
+
|
|
49
|
+
async recover(ctx: ConflictContext): Promise<ConflictResolution> {
|
|
50
|
+
const role =
|
|
51
|
+
(ctx.strategyConfig?.role as string | undefined) ??
|
|
52
|
+
this.opts.defaultRole ??
|
|
53
|
+
'resolver';
|
|
54
|
+
const timeoutMs =
|
|
55
|
+
(ctx.strategyConfig?.timeout_ms as number | undefined) ??
|
|
56
|
+
this.opts.defaultTimeoutMs ??
|
|
57
|
+
30 * 60 * 1000;
|
|
58
|
+
const maxConcurrent =
|
|
59
|
+
(ctx.strategyConfig?.max_concurrent as number | undefined) ??
|
|
60
|
+
this.opts.defaultMaxConcurrent ??
|
|
61
|
+
2;
|
|
62
|
+
|
|
63
|
+
// Concurrency cap
|
|
64
|
+
const active = this.activeByStream.get(ctx.streamId) ?? new Set();
|
|
65
|
+
if (active.size >= maxConcurrent) {
|
|
66
|
+
return {
|
|
67
|
+
kind: 'retry-after',
|
|
68
|
+
backoffMs: 30_000,
|
|
69
|
+
reason: `max concurrent resolvers (${maxConcurrent}) on stream ${ctx.streamId}`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Spawn the resolver. Injects MACRO_RECOVERY_STRATEGY + MACRO_CONFLICT_ID
|
|
74
|
+
// so the resolve_conflict MCP tool can tag the resolution correctly.
|
|
75
|
+
let resolverAgentId: string;
|
|
76
|
+
try {
|
|
77
|
+
const spawnOpts: SpawnAgentOptions = {
|
|
78
|
+
role,
|
|
79
|
+
task: `Resolve conflict ${ctx.conflictId} on stream ${ctx.streamId}`,
|
|
80
|
+
parent: ctx.landingAgentId,
|
|
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
|
+
},
|
|
88
|
+
};
|
|
89
|
+
const spawned = await this.opts.agentManager.spawn(spawnOpts);
|
|
90
|
+
resolverAgentId = spawned.id;
|
|
91
|
+
} catch (err) {
|
|
92
|
+
return {
|
|
93
|
+
kind: 'failed',
|
|
94
|
+
error: `spawn-resolver: failed to spawn resolver — ${
|
|
95
|
+
err instanceof Error ? err.message : String(err)
|
|
96
|
+
}`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
active.add(resolverAgentId);
|
|
101
|
+
this.activeByStream.set(ctx.streamId, active);
|
|
102
|
+
|
|
103
|
+
// Wait for conflict.resolved event OR timeout. The resolver agent calls
|
|
104
|
+
// `resolve_conflict` MCP tool, which invokes workspaceManager.resolveConflict,
|
|
105
|
+
// which emits 'conflict:resolved' event.
|
|
106
|
+
try {
|
|
107
|
+
const resolution = await this.awaitResolution(ctx, timeoutMs);
|
|
108
|
+
return resolution;
|
|
109
|
+
} finally {
|
|
110
|
+
active.delete(resolverAgentId);
|
|
111
|
+
if (active.size === 0) this.activeByStream.delete(ctx.streamId);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private awaitResolution(
|
|
116
|
+
ctx: ConflictContext,
|
|
117
|
+
timeoutMs: number
|
|
118
|
+
): Promise<ConflictResolution> {
|
|
119
|
+
return new Promise((resolve) => {
|
|
120
|
+
const unsubscribe = ctx.workspaceManager.onEvent((event) => {
|
|
121
|
+
if (
|
|
122
|
+
event.type === 'conflict:resolved' &&
|
|
123
|
+
event.data.conflictId === ctx.conflictId
|
|
124
|
+
) {
|
|
125
|
+
clearTimeout(timer);
|
|
126
|
+
unsubscribe();
|
|
127
|
+
resolve({
|
|
128
|
+
kind: 'resolved',
|
|
129
|
+
resolutionCommit: (event.data.resolutionCommit as string) ?? 'unknown',
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const timer = setTimeout(() => {
|
|
135
|
+
unsubscribe();
|
|
136
|
+
resolve({
|
|
137
|
+
kind: 'escalated',
|
|
138
|
+
escalatedTo: 'human',
|
|
139
|
+
});
|
|
140
|
+
}, timeoutMs);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Factory for SpawnResolverStrategy — requires AgentManager.
|
|
147
|
+
*/
|
|
148
|
+
export function createSpawnResolverStrategy(
|
|
149
|
+
opts: SpawnResolverStrategyOptions
|
|
150
|
+
): SpawnResolverStrategy {
|
|
151
|
+
return new SpawnResolverStrategy(opts);
|
|
152
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conflict recovery types.
|
|
3
|
+
*
|
|
4
|
+
* Parallel to LandingStrategy. Dispatched from the agent's done() flow when
|
|
5
|
+
* a landing returns a conflict. Registered globally; selected via per-role
|
|
6
|
+
* YAML `on_conflict_recovery` or team default.
|
|
7
|
+
*
|
|
8
|
+
* @module workspace/recovery/types
|
|
9
|
+
* @see docs/conflict-recovery.md
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { AgentId, StreamId, Principal } from '../types-v3.js';
|
|
13
|
+
import type { WorkspaceManager } from '../types.js';
|
|
14
|
+
|
|
15
|
+
export interface ConflictContext {
|
|
16
|
+
conflictId: string;
|
|
17
|
+
streamId: StreamId;
|
|
18
|
+
sourceCommit?: string;
|
|
19
|
+
targetCommit?: string;
|
|
20
|
+
targetStreamId?: StreamId;
|
|
21
|
+
paths: string[];
|
|
22
|
+
operation: 'merge' | 'sync' | 'rebase' | 'cascade';
|
|
23
|
+
landingAgentId?: AgentId;
|
|
24
|
+
/**
|
|
25
|
+
* Worktree path where the conflict occurred. Required for
|
|
26
|
+
* `auto-resolve` and any other strategy that needs to replay git
|
|
27
|
+
* operations. Optional because some strategies (`defer`, `abandon`,
|
|
28
|
+
* `escalate`) don't need filesystem access.
|
|
29
|
+
*/
|
|
30
|
+
worktree?: string;
|
|
31
|
+
recoveryDepth: number;
|
|
32
|
+
strategyConfig?: Record<string, unknown>;
|
|
33
|
+
workspaceManager: WorkspaceManager;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type ConflictResolution =
|
|
37
|
+
| { kind: 'resolved'; resolutionCommit: string }
|
|
38
|
+
| { kind: 'deferred'; reason: string }
|
|
39
|
+
| { kind: 'abandoned'; streamId: StreamId; reason: string }
|
|
40
|
+
| { kind: 'escalated'; escalatedTo: Principal | 'human' }
|
|
41
|
+
| { kind: 'retry-after'; backoffMs: number; reason: string }
|
|
42
|
+
| { kind: 'failed'; error: string };
|
|
43
|
+
|
|
44
|
+
export type ConflictResolutionMode = 'sync' | 'async';
|
|
45
|
+
|
|
46
|
+
export interface ConflictRecoveryStrategy {
|
|
47
|
+
readonly name: string;
|
|
48
|
+
readonly mode: ConflictResolutionMode;
|
|
49
|
+
|
|
50
|
+
canHandle?(ctx: ConflictContext): boolean;
|
|
51
|
+
recover(ctx: ConflictContext): Promise<ConflictResolution>;
|
|
52
|
+
initialize?(): Promise<void>;
|
|
53
|
+
close?(): Promise<void>;
|
|
54
|
+
}
|