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,456 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Dispatch Phase 2 Live Agent E2E Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests the Phase 2 dispatch orchestrator with REAL Claude Code agents.
|
|
5
|
+
* Constructs the orchestrator manually (like dispatch-live.e2e.test.ts)
|
|
6
|
+
* with Phase 2 ports to verify:
|
|
7
|
+
* - createOrchestrator with MessagePort + AgentRoster
|
|
8
|
+
* - Snapshot includes Phase 2 fields during live execution
|
|
9
|
+
* - Event emission with Phase 2 event types (dispatched.via)
|
|
10
|
+
* - Dispatch mode prefer-route falls back to spawn (no roster agents)
|
|
11
|
+
* - Lifecycle events tracked through Phase 2 orchestrator
|
|
12
|
+
*
|
|
13
|
+
* REQUIRES: RUN_FULL_AGENT_TESTS=true
|
|
14
|
+
*
|
|
15
|
+
* Run with:
|
|
16
|
+
* RUN_FULL_AGENT_TESTS=true npx vitest run --config vitest.e2e.config.ts src/__tests__/e2e/dispatch-phase2-live.e2e.test.ts
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
describe,
|
|
21
|
+
it,
|
|
22
|
+
expect,
|
|
23
|
+
beforeEach,
|
|
24
|
+
afterEach,
|
|
25
|
+
vi,
|
|
26
|
+
} from "vitest";
|
|
27
|
+
import * as path from "path";
|
|
28
|
+
import * as os from "os";
|
|
29
|
+
import * as fs from "fs";
|
|
30
|
+
import { execSync } from "child_process";
|
|
31
|
+
import { bootV2, type MacroAgentSystemV2 } from "../../boot-v2.js";
|
|
32
|
+
import {
|
|
33
|
+
createOrchestrator,
|
|
34
|
+
type Orchestrator,
|
|
35
|
+
type DispatchAgentRuntime,
|
|
36
|
+
type DispatchTaskSource,
|
|
37
|
+
type DispatchEvent,
|
|
38
|
+
type Snapshot,
|
|
39
|
+
type AgentRoster,
|
|
40
|
+
type AgentRef,
|
|
41
|
+
} from "swarm-dispatch";
|
|
42
|
+
import type { TaskRecord } from "../../adapters/types.js";
|
|
43
|
+
|
|
44
|
+
// ─────────────────────────────────────────────────────────────────
|
|
45
|
+
// Configuration
|
|
46
|
+
// ─────────────────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
const RUN_FULL_AGENT = !!process.env.RUN_FULL_AGENT_TESTS;
|
|
49
|
+
const describeFn = RUN_FULL_AGENT ? describe : describe.skip;
|
|
50
|
+
|
|
51
|
+
const TIMEOUT = {
|
|
52
|
+
SPAWN: 60_000,
|
|
53
|
+
DISPATCH: 120_000,
|
|
54
|
+
MULTI: 180_000,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// ─────────────────────────────────────────────────────────────────
|
|
58
|
+
// Helpers
|
|
59
|
+
// ─────────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
function createTestRepo(prefix: string): { path: string; cleanup: () => void } {
|
|
62
|
+
const tmpDir = fs.mkdtempSync(
|
|
63
|
+
path.join(os.tmpdir(), `dispatch-p2-live-${prefix}-`)
|
|
64
|
+
);
|
|
65
|
+
const repoPath = path.join(tmpDir, "test-repo");
|
|
66
|
+
fs.mkdirSync(repoPath);
|
|
67
|
+
execSync("git init", { cwd: repoPath, stdio: "pipe" });
|
|
68
|
+
execSync('git config user.email "test@test.com"', { cwd: repoPath, stdio: "pipe" });
|
|
69
|
+
execSync('git config user.name "Test User"', { cwd: repoPath, stdio: "pipe" });
|
|
70
|
+
fs.writeFileSync(path.join(repoPath, "README.md"), "# Test Repo\n");
|
|
71
|
+
execSync("git add -A", { cwd: repoPath, stdio: "pipe" });
|
|
72
|
+
execSync('git commit -m "Initial commit"', { cwd: repoPath, stdio: "pipe" });
|
|
73
|
+
return {
|
|
74
|
+
path: repoPath,
|
|
75
|
+
cleanup: () => fs.rmSync(tmpDir, { recursive: true, force: true }),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function log(msg: string): void {
|
|
80
|
+
console.log(`[DISPATCH-P2-LIVE] ${msg}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function sleep(ms: number): Promise<void> {
|
|
84
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function createSourceAdapter(tasksAdapter: any): DispatchTaskSource {
|
|
88
|
+
return {
|
|
89
|
+
queryReady: (opts) => tasksAdapter.queryReady(opts),
|
|
90
|
+
claim: async (taskId, claimantId) => {
|
|
91
|
+
try {
|
|
92
|
+
await tasksAdapter.assignTask(taskId, claimantId);
|
|
93
|
+
return { success: true as const };
|
|
94
|
+
} catch { return { success: false as const }; }
|
|
95
|
+
},
|
|
96
|
+
release: async (taskId) => tasksAdapter.unclaimTask(taskId),
|
|
97
|
+
transition: async (taskId, action) => tasksAdapter.transitionTask(taskId, action),
|
|
98
|
+
getTask: async (taskId) => tasksAdapter.getTask(taskId),
|
|
99
|
+
isStillActive: async (taskId) => {
|
|
100
|
+
try {
|
|
101
|
+
const t = await tasksAdapter.getTask(taskId);
|
|
102
|
+
return t.status === "open";
|
|
103
|
+
} catch { return false; }
|
|
104
|
+
},
|
|
105
|
+
listInProgress: async () => tasksAdapter.listTasks({ status: "in_progress" }),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function createRuntimeAdapter(agentManager: any): DispatchAgentRuntime {
|
|
110
|
+
return {
|
|
111
|
+
spawn: async (opts) => {
|
|
112
|
+
const spawned = await agentManager.spawn({
|
|
113
|
+
task: opts.prompt, task_id: opts.taskId, role: opts.role, parent: null,
|
|
114
|
+
});
|
|
115
|
+
return { id: spawned.id };
|
|
116
|
+
},
|
|
117
|
+
terminate: async (agentId, reason) => agentManager.terminate(agentId, reason ?? "cancelled"),
|
|
118
|
+
onStopped: (cb) => agentManager.onLifecycleEvent((event: any) => {
|
|
119
|
+
if (event.type === "stopped") cb(event.agent.id, event.reason);
|
|
120
|
+
}),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function createEmptyRoster(): AgentRoster {
|
|
125
|
+
return {
|
|
126
|
+
async findAvailable() { return []; },
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ─────────────────────────────────────────────────────────────────
|
|
131
|
+
// Tests
|
|
132
|
+
// ─────────────────────────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
describeFn("Task Dispatch Phase 2 Live Agent E2E", () => {
|
|
135
|
+
let system: MacroAgentSystemV2;
|
|
136
|
+
let testRepo: { path: string; cleanup: () => void };
|
|
137
|
+
let baseDir: string;
|
|
138
|
+
let orchestrator: Orchestrator;
|
|
139
|
+
|
|
140
|
+
beforeEach(async () => {
|
|
141
|
+
testRepo = createTestRepo("p2");
|
|
142
|
+
baseDir = path.join(testRepo.path, ".macro-agent");
|
|
143
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
144
|
+
|
|
145
|
+
system = await bootV2({
|
|
146
|
+
cwd: testRepo.path,
|
|
147
|
+
baseDir,
|
|
148
|
+
defaultPermissionMode: "auto-approve",
|
|
149
|
+
inbox: { socketPath: path.join(baseDir, "inbox.sock") },
|
|
150
|
+
});
|
|
151
|
+
log("System booted");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
afterEach(async () => {
|
|
155
|
+
if (orchestrator) await orchestrator.stop();
|
|
156
|
+
if (system) {
|
|
157
|
+
try {
|
|
158
|
+
const running = system.agentManager.list({ state: "running" });
|
|
159
|
+
for (const agent of running) {
|
|
160
|
+
try { await system.agentManager.terminate(agent.id, "cancelled"); } catch {}
|
|
161
|
+
}
|
|
162
|
+
await system.shutdown();
|
|
163
|
+
} catch {}
|
|
164
|
+
}
|
|
165
|
+
testRepo?.cleanup();
|
|
166
|
+
log("Cleanup complete");
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
function mockTasksAdapter(tasks: TaskRecord[]): void {
|
|
170
|
+
const tasksAdapter = system.tasksAdapter;
|
|
171
|
+
const taskMap = new Map(tasks.map((t) => [t.id, t]));
|
|
172
|
+
(tasksAdapter as any).queryReady = vi.fn()
|
|
173
|
+
.mockResolvedValueOnce(tasks)
|
|
174
|
+
.mockResolvedValue([]);
|
|
175
|
+
(tasksAdapter as any).assignTask = vi.fn().mockResolvedValue(undefined);
|
|
176
|
+
(tasksAdapter as any).transitionTask = vi.fn().mockResolvedValue(undefined);
|
|
177
|
+
(tasksAdapter as any).unclaimTask = vi.fn().mockResolvedValue(undefined);
|
|
178
|
+
(tasksAdapter as any).getTask = vi.fn().mockImplementation(
|
|
179
|
+
async (id: string) => taskMap.get(id) ?? { id, title: "Unknown", status: "open" }
|
|
180
|
+
);
|
|
181
|
+
(tasksAdapter as any).listTasks = vi.fn().mockResolvedValue([]);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ── createOrchestrator with Phase 2 ports ──────────────────
|
|
185
|
+
|
|
186
|
+
it(
|
|
187
|
+
"dispatches via createOrchestrator with Phase 2 config (prefer-route → spawn fallback)",
|
|
188
|
+
async () => {
|
|
189
|
+
const task: TaskRecord = {
|
|
190
|
+
id: "p2-live-1",
|
|
191
|
+
title: "Create hello file",
|
|
192
|
+
content:
|
|
193
|
+
'Create a file called hello.txt with the text "hello from phase 2". ' +
|
|
194
|
+
'Then call the "done" MCP tool with status="completed" and summary="Created hello.txt".',
|
|
195
|
+
status: "open",
|
|
196
|
+
tags: ["auto"],
|
|
197
|
+
priority: 3,
|
|
198
|
+
};
|
|
199
|
+
mockTasksAdapter([task]);
|
|
200
|
+
|
|
201
|
+
const events: DispatchEvent[] = [];
|
|
202
|
+
orchestrator = createOrchestrator(
|
|
203
|
+
createSourceAdapter(system.tasksAdapter),
|
|
204
|
+
createRuntimeAdapter(system.agentManager),
|
|
205
|
+
{
|
|
206
|
+
claimantId: `test:${process.pid}:p2-live`,
|
|
207
|
+
pollIntervalMs: 600_000,
|
|
208
|
+
defaultRole: "worker",
|
|
209
|
+
concurrency: { global: 3 },
|
|
210
|
+
retry: { maxRetries: 3, baseDelayMs: 1_000, maxDelayMs: 60_000 },
|
|
211
|
+
reconcile: { enabled: true, intervalMs: 600_000 },
|
|
212
|
+
roster: createEmptyRoster(),
|
|
213
|
+
dispatchMode: "prefer-route",
|
|
214
|
+
}
|
|
215
|
+
);
|
|
216
|
+
orchestrator.onEvent((e) => events.push(e));
|
|
217
|
+
await orchestrator.start();
|
|
218
|
+
|
|
219
|
+
log("Triggering dispatch (prefer-route, empty roster → spawn fallback)...");
|
|
220
|
+
await orchestrator.dispatchNow();
|
|
221
|
+
await sleep(2_000);
|
|
222
|
+
|
|
223
|
+
log(`Tracker active: ${orchestrator.tracker.activeCount()}`);
|
|
224
|
+
expect(orchestrator.tracker.activeCount()).toBe(1);
|
|
225
|
+
|
|
226
|
+
const dispatched = events.find(
|
|
227
|
+
(e): e is Extract<DispatchEvent, { type: "dispatched" }> => e.type === "dispatched"
|
|
228
|
+
);
|
|
229
|
+
expect(dispatched).toBeDefined();
|
|
230
|
+
expect(dispatched!.via).toBe("spawn");
|
|
231
|
+
log(`Dispatched via: ${dispatched!.via}`);
|
|
232
|
+
|
|
233
|
+
const agentId = dispatched!.agentId;
|
|
234
|
+
const agentRecord = system.agentStore.getAgent(agentId);
|
|
235
|
+
expect(agentRecord).not.toBeNull();
|
|
236
|
+
expect(agentRecord!.state).toBe("running");
|
|
237
|
+
expect(agentRecord!.parent_id).toBeNull();
|
|
238
|
+
|
|
239
|
+
// Prompt agent to complete
|
|
240
|
+
log("Prompting agent to complete...");
|
|
241
|
+
const result = await system.agentManager.promptUntilDone(
|
|
242
|
+
agentId,
|
|
243
|
+
'Create hello.txt with "hello from phase 2", then call done(status="completed", summary="Created hello.txt").',
|
|
244
|
+
{ maxFollowUps: 3 }
|
|
245
|
+
);
|
|
246
|
+
log(`Done result: doneCalled=${result.doneCalled}, status=${result.doneStatus}`);
|
|
247
|
+
await sleep(3_000);
|
|
248
|
+
|
|
249
|
+
if (result.doneCalled && result.doneStatus === "completed") {
|
|
250
|
+
const helloPath = path.join(testRepo.path, "hello.txt");
|
|
251
|
+
if (fs.existsSync(helloPath)) {
|
|
252
|
+
expect(fs.readFileSync(helloPath, "utf-8")).toContain("hello from phase 2");
|
|
253
|
+
log("hello.txt verified");
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
TIMEOUT.MULTI
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
// ── Snapshot during live execution ─────────────────────────
|
|
261
|
+
|
|
262
|
+
it(
|
|
263
|
+
"snapshot includes Phase 2 fields during live execution",
|
|
264
|
+
async () => {
|
|
265
|
+
const task: TaskRecord = {
|
|
266
|
+
id: "snap-1",
|
|
267
|
+
title: "Snapshot test",
|
|
268
|
+
content: "Wait for instructions. Do not call done().",
|
|
269
|
+
status: "open",
|
|
270
|
+
};
|
|
271
|
+
mockTasksAdapter([task]);
|
|
272
|
+
|
|
273
|
+
orchestrator = createOrchestrator(
|
|
274
|
+
createSourceAdapter(system.tasksAdapter),
|
|
275
|
+
createRuntimeAdapter(system.agentManager),
|
|
276
|
+
{
|
|
277
|
+
claimantId: `test:${process.pid}:snap`,
|
|
278
|
+
pollIntervalMs: 600_000,
|
|
279
|
+
defaultRole: "worker",
|
|
280
|
+
concurrency: { global: 5 },
|
|
281
|
+
retry: { maxRetries: 3, baseDelayMs: 1_000, maxDelayMs: 60_000 },
|
|
282
|
+
reconcile: { enabled: true, intervalMs: 600_000 },
|
|
283
|
+
}
|
|
284
|
+
);
|
|
285
|
+
await orchestrator.start();
|
|
286
|
+
await orchestrator.dispatchNow();
|
|
287
|
+
await sleep(2_000);
|
|
288
|
+
|
|
289
|
+
const snap: Snapshot = orchestrator.snapshot();
|
|
290
|
+
log(`Snapshot counts: ${JSON.stringify(snap.counts)}`);
|
|
291
|
+
|
|
292
|
+
expect(snap.generatedAt).toBeTruthy();
|
|
293
|
+
expect(snap.counts.running).toBe(1);
|
|
294
|
+
expect(snap.running).toHaveLength(1);
|
|
295
|
+
expect(snap.subscriberErrorCount).toBe(0);
|
|
296
|
+
|
|
297
|
+
// Phase 2 fields
|
|
298
|
+
expect(snap.totals).toBeDefined();
|
|
299
|
+
expect(snap.totals!.tokens).toBeDefined();
|
|
300
|
+
expect(snap.totals!.agentSeconds).toBeGreaterThanOrEqual(0);
|
|
301
|
+
|
|
302
|
+
const entry = snap.running[0];
|
|
303
|
+
expect(entry.taskId).toBe("snap-1");
|
|
304
|
+
expect(entry.agentId).toBeTruthy();
|
|
305
|
+
expect(entry.state).toBe("Running");
|
|
306
|
+
expect(entry.origin).toBe("source");
|
|
307
|
+
log(`Entry: taskId=${entry.taskId}, origin=${entry.origin}, agentSeconds=${snap.totals!.agentSeconds!.toFixed(1)}`);
|
|
308
|
+
},
|
|
309
|
+
TIMEOUT.DISPATCH
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
// ── Event types ────────────────────────────────────────────
|
|
313
|
+
|
|
314
|
+
it(
|
|
315
|
+
"emits Phase 2 event types during dispatch + reconcile",
|
|
316
|
+
async () => {
|
|
317
|
+
orchestrator = createOrchestrator(
|
|
318
|
+
createSourceAdapter(system.tasksAdapter),
|
|
319
|
+
createRuntimeAdapter(system.agentManager),
|
|
320
|
+
{
|
|
321
|
+
claimantId: `test:${process.pid}:events`,
|
|
322
|
+
pollIntervalMs: 600_000,
|
|
323
|
+
defaultRole: "worker",
|
|
324
|
+
concurrency: { global: 3 },
|
|
325
|
+
retry: { maxRetries: 3, baseDelayMs: 1_000, maxDelayMs: 60_000 },
|
|
326
|
+
reconcile: { enabled: true, intervalMs: 600_000, stallTimeoutMs: 120_000 },
|
|
327
|
+
}
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
const events: DispatchEvent[] = [];
|
|
331
|
+
orchestrator.onEvent((e) => events.push(e));
|
|
332
|
+
await orchestrator.start();
|
|
333
|
+
|
|
334
|
+
// Empty dispatch → poll event
|
|
335
|
+
await orchestrator.dispatchNow();
|
|
336
|
+
const poll = events.find((e) => e.type === "poll");
|
|
337
|
+
expect(poll).toBeDefined();
|
|
338
|
+
|
|
339
|
+
// Empty reconcile → reconciled event
|
|
340
|
+
await orchestrator.reconcileNow();
|
|
341
|
+
const reconciled = events.find(
|
|
342
|
+
(e): e is Extract<DispatchEvent, { type: "reconciled" }> => e.type === "reconciled"
|
|
343
|
+
);
|
|
344
|
+
expect(reconciled).toBeDefined();
|
|
345
|
+
expect(reconciled!.stalled).toBe(0);
|
|
346
|
+
log(`Events: poll + reconciled verified`);
|
|
347
|
+
},
|
|
348
|
+
TIMEOUT.SPAWN
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
// ── Lifecycle tracks termination ───────────────────────────
|
|
352
|
+
|
|
353
|
+
it(
|
|
354
|
+
"tracks agent termination and queues retry",
|
|
355
|
+
async () => {
|
|
356
|
+
const task: TaskRecord = {
|
|
357
|
+
id: "term-1",
|
|
358
|
+
title: "Terminate test",
|
|
359
|
+
content: "Wait for instructions.",
|
|
360
|
+
status: "open",
|
|
361
|
+
};
|
|
362
|
+
mockTasksAdapter([task]);
|
|
363
|
+
|
|
364
|
+
const events: DispatchEvent[] = [];
|
|
365
|
+
orchestrator = createOrchestrator(
|
|
366
|
+
createSourceAdapter(system.tasksAdapter),
|
|
367
|
+
createRuntimeAdapter(system.agentManager),
|
|
368
|
+
{
|
|
369
|
+
claimantId: `test:${process.pid}:term`,
|
|
370
|
+
pollIntervalMs: 600_000,
|
|
371
|
+
defaultRole: "worker",
|
|
372
|
+
concurrency: { global: 3 },
|
|
373
|
+
retry: { maxRetries: 3, baseDelayMs: 1_000, maxDelayMs: 60_000 },
|
|
374
|
+
reconcile: { enabled: true, intervalMs: 600_000 },
|
|
375
|
+
}
|
|
376
|
+
);
|
|
377
|
+
orchestrator.onEvent((e) => events.push(e));
|
|
378
|
+
await orchestrator.start();
|
|
379
|
+
await orchestrator.dispatchNow();
|
|
380
|
+
await sleep(2_000);
|
|
381
|
+
|
|
382
|
+
expect(orchestrator.tracker.activeCount()).toBe(1);
|
|
383
|
+
const agentId = orchestrator.tracker.listActive()[0].agentId;
|
|
384
|
+
log(`Agent: ${agentId}`);
|
|
385
|
+
|
|
386
|
+
// Externally terminate
|
|
387
|
+
await system.agentManager.terminate(agentId, "cancelled");
|
|
388
|
+
await sleep(3_000);
|
|
389
|
+
|
|
390
|
+
log(`Active: ${orchestrator.tracker.activeCount()}, Retries: ${orchestrator.tracker.listRetries().length}`);
|
|
391
|
+
expect(orchestrator.tracker.isTracked("term-1")).toBe(true);
|
|
392
|
+
|
|
393
|
+
const retrying = events.find((e) => e.type === "retrying");
|
|
394
|
+
expect(retrying).toBeDefined();
|
|
395
|
+
log(`Retrying event found: attempt=${(retrying as any)?.attempt}`);
|
|
396
|
+
},
|
|
397
|
+
TIMEOUT.DISPATCH
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
// ── Reconciliation with Phase 2 orchestrator ───────────────
|
|
401
|
+
|
|
402
|
+
it(
|
|
403
|
+
"reconciliation detects closed task and terminates agent",
|
|
404
|
+
async () => {
|
|
405
|
+
const task: TaskRecord = {
|
|
406
|
+
id: "reconcile-p2",
|
|
407
|
+
title: "Reconcile test",
|
|
408
|
+
content: "Wait for instructions.",
|
|
409
|
+
status: "open",
|
|
410
|
+
};
|
|
411
|
+
mockTasksAdapter([task]);
|
|
412
|
+
|
|
413
|
+
const events: DispatchEvent[] = [];
|
|
414
|
+
orchestrator = createOrchestrator(
|
|
415
|
+
createSourceAdapter(system.tasksAdapter),
|
|
416
|
+
createRuntimeAdapter(system.agentManager),
|
|
417
|
+
{
|
|
418
|
+
claimantId: `test:${process.pid}:reconcile`,
|
|
419
|
+
pollIntervalMs: 600_000,
|
|
420
|
+
defaultRole: "worker",
|
|
421
|
+
concurrency: { global: 3 },
|
|
422
|
+
retry: { maxRetries: 3, baseDelayMs: 1_000, maxDelayMs: 60_000 },
|
|
423
|
+
reconcile: { enabled: true, intervalMs: 600_000 },
|
|
424
|
+
}
|
|
425
|
+
);
|
|
426
|
+
orchestrator.onEvent((e) => events.push(e));
|
|
427
|
+
await orchestrator.start();
|
|
428
|
+
await orchestrator.dispatchNow();
|
|
429
|
+
await sleep(2_000);
|
|
430
|
+
|
|
431
|
+
expect(orchestrator.tracker.activeCount()).toBe(1);
|
|
432
|
+
const agentId = orchestrator.tracker.listActive()[0].agentId;
|
|
433
|
+
|
|
434
|
+
// Close the task externally
|
|
435
|
+
(system.tasksAdapter as any).getTask = vi.fn().mockResolvedValue({
|
|
436
|
+
...task,
|
|
437
|
+
status: "closed",
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
log("Triggering reconcile (task now closed)...");
|
|
441
|
+
await orchestrator.reconcileNow();
|
|
442
|
+
await sleep(3_000);
|
|
443
|
+
|
|
444
|
+
expect(orchestrator.tracker.isTracked("reconcile-p2")).toBe(false);
|
|
445
|
+
expect(orchestrator.tracker.activeCount()).toBe(0);
|
|
446
|
+
|
|
447
|
+
const cancelled = events.find((e) => e.type === "cancelled");
|
|
448
|
+
expect(cancelled).toBeDefined();
|
|
449
|
+
log("Reconciliation cancelled the agent correctly");
|
|
450
|
+
|
|
451
|
+
const finalRecord = system.agentStore.getAgent(agentId);
|
|
452
|
+
expect(finalRecord?.state).toBe("stopped");
|
|
453
|
+
},
|
|
454
|
+
TIMEOUT.DISPATCH
|
|
455
|
+
);
|
|
456
|
+
});
|