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,495 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dispatch + Coordination Handler E2E Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests the full-stack dispatch flow:
|
|
5
|
+
* OpenHive task.assign → coordination handler → opentasks → swarm-dispatch → agent
|
|
6
|
+
*
|
|
7
|
+
* Exercises the integration between:
|
|
8
|
+
* - Coordination handler (receives x-openhive/task.assign, creates task in opentasks)
|
|
9
|
+
* - opentasks daemon (stores tasks, serves queryReady)
|
|
10
|
+
* - swarm-dispatch (polls, claims, spawns agents)
|
|
11
|
+
* - AgentManagerV2 (real agent spawning)
|
|
12
|
+
* - Lifecycle listener (tracks completion/failure)
|
|
13
|
+
*
|
|
14
|
+
* REQUIRES: RUN_FULL_AGENT_TESTS=true
|
|
15
|
+
*
|
|
16
|
+
* Run with:
|
|
17
|
+
* RUN_FULL_AGENT_TESTS=true npx vitest run --config vitest.e2e.config.ts src/__tests__/e2e/dispatch-coordination.e2e.test.ts
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
21
|
+
import * as path from "node:path";
|
|
22
|
+
import * as os from "node:os";
|
|
23
|
+
import * as fs from "node:fs";
|
|
24
|
+
import { execFileSync } from "node:child_process";
|
|
25
|
+
import { bootV2, type MacroAgentSystemV2 } from "../../boot-v2.js";
|
|
26
|
+
import {
|
|
27
|
+
ensureOpentasksDaemon,
|
|
28
|
+
type DaemonHandle,
|
|
29
|
+
} from "../../adapters/opentasks-daemon.js";
|
|
30
|
+
import {
|
|
31
|
+
createTaskDispatcher,
|
|
32
|
+
type TaskDispatcher,
|
|
33
|
+
type DispatchAgentRuntime,
|
|
34
|
+
type DispatchTaskSource,
|
|
35
|
+
} from "swarm-dispatch";
|
|
36
|
+
import { setupCoordinationHandlers } from "../../map/coordination-handler.js";
|
|
37
|
+
|
|
38
|
+
// ─────────────────────────────────────────────────────────────────
|
|
39
|
+
// Configuration
|
|
40
|
+
// ─────────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
const RUN_FULL_AGENT = !!process.env.RUN_FULL_AGENT_TESTS;
|
|
43
|
+
const describeFn = RUN_FULL_AGENT ? describe : describe.skip;
|
|
44
|
+
|
|
45
|
+
const TIMEOUT = {
|
|
46
|
+
SETUP: 30_000,
|
|
47
|
+
DISPATCH: 120_000,
|
|
48
|
+
MULTI: 180_000,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// ─────────────────────────────────────────────────────────────────
|
|
52
|
+
// Helpers
|
|
53
|
+
// ─────────────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
function createTempDir(prefix = "dispatch-coord"): string {
|
|
56
|
+
const suffix = Math.random().toString(36).slice(2, 8);
|
|
57
|
+
const dir = path.join(os.tmpdir(), `${prefix}-${suffix}`);
|
|
58
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
59
|
+
return dir;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function initGitRepo(dir: string): void {
|
|
63
|
+
execFileSync("git", ["init"], { cwd: dir, stdio: "pipe" });
|
|
64
|
+
execFileSync("git", ["config", "user.email", "test@e2e.dev"], { cwd: dir, stdio: "pipe" });
|
|
65
|
+
execFileSync("git", ["config", "user.name", "E2E Test"], { cwd: dir, stdio: "pipe" });
|
|
66
|
+
execFileSync("git", ["config", "commit.gpgsign", "false"], { cwd: dir, stdio: "pipe" });
|
|
67
|
+
fs.writeFileSync(path.join(dir, "README.md"), "# Dispatch Coordination E2E\n");
|
|
68
|
+
execFileSync("git", ["add", "."], { cwd: dir, stdio: "pipe" });
|
|
69
|
+
execFileSync("git", ["commit", "-m", "init"], { cwd: dir, stdio: "pipe" });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function cleanupDir(dir: string): void {
|
|
73
|
+
try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function log(msg: string): void {
|
|
77
|
+
console.log(`[DISPATCH-COORD] ${msg}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function sleep(ms: number): Promise<void> {
|
|
81
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Simulates an OpenHive MAP connection that can send coordination notifications.
|
|
86
|
+
*/
|
|
87
|
+
function createMockMAPConnection() {
|
|
88
|
+
const handlers = new Map<string, Array<(params: unknown) => void | Promise<void>>>();
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
onNotification(method: string, handler: (params: unknown) => void | Promise<void>) {
|
|
92
|
+
if (!handlers.has(method)) handlers.set(method, []);
|
|
93
|
+
handlers.get(method)!.push(handler);
|
|
94
|
+
},
|
|
95
|
+
offNotification(method: string, handler: (params: unknown) => void | Promise<void>) {
|
|
96
|
+
const list = handlers.get(method);
|
|
97
|
+
if (list) {
|
|
98
|
+
const idx = list.indexOf(handler);
|
|
99
|
+
if (idx >= 0) list.splice(idx, 1);
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
async sendNotification() { /* no-op for inbound-only */ },
|
|
103
|
+
|
|
104
|
+
/** Simulate OpenHive sending a notification */
|
|
105
|
+
async simulateNotification(method: string, params: unknown) {
|
|
106
|
+
const list = handlers.get(method);
|
|
107
|
+
if (list) {
|
|
108
|
+
for (const handler of list) {
|
|
109
|
+
await handler(params);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ─────────────────────────────────────────────────────────────────
|
|
117
|
+
// Tests
|
|
118
|
+
// ─────────────────────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
describeFn("Dispatch + Coordination Handler E2E", () => {
|
|
121
|
+
let daemonDir: string;
|
|
122
|
+
let registryDir: string;
|
|
123
|
+
let daemonHandle: DaemonHandle;
|
|
124
|
+
let canStartDaemon = false;
|
|
125
|
+
|
|
126
|
+
let system: MacroAgentSystemV2;
|
|
127
|
+
let dispatcher: TaskDispatcher;
|
|
128
|
+
let testRepoDir: string;
|
|
129
|
+
let coordinationCleanup: (() => void) | null = null;
|
|
130
|
+
|
|
131
|
+
function createSourceAdapter(tasksAdapter: typeof system.tasksAdapter): DispatchTaskSource {
|
|
132
|
+
return {
|
|
133
|
+
queryReady: (opts) => tasksAdapter.queryReady(opts),
|
|
134
|
+
claim: async (taskId, claimantId) => {
|
|
135
|
+
try {
|
|
136
|
+
await tasksAdapter.assignTask(taskId, claimantId);
|
|
137
|
+
return { success: true as const };
|
|
138
|
+
} catch { return { success: false as const }; }
|
|
139
|
+
},
|
|
140
|
+
release: async (taskId) => tasksAdapter.unclaimTask(taskId),
|
|
141
|
+
transition: async (taskId, action) => tasksAdapter.transitionTask(taskId, action),
|
|
142
|
+
getTask: async (taskId) => tasksAdapter.getTask(taskId),
|
|
143
|
+
listInProgress: async () => tasksAdapter.listTasks({ status: "in_progress" }),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function createRuntimeAdapter(agentManager: typeof system.agentManager): DispatchAgentRuntime {
|
|
148
|
+
return {
|
|
149
|
+
spawn: async (opts) => {
|
|
150
|
+
const spawned = await agentManager.spawn({
|
|
151
|
+
task: opts.prompt, task_id: opts.taskId, role: opts.role, parent: null,
|
|
152
|
+
});
|
|
153
|
+
return { id: spawned.id };
|
|
154
|
+
},
|
|
155
|
+
terminate: async (agentId) => agentManager.terminate(agentId, "cancelled"),
|
|
156
|
+
onStopped: (cb) => agentManager.onLifecycleEvent((event) => {
|
|
157
|
+
if (event.type === "stopped") cb(event.agent.id, event.reason);
|
|
158
|
+
}),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
beforeEach(async () => {
|
|
163
|
+
try {
|
|
164
|
+
daemonDir = createTempDir("ot-dmn");
|
|
165
|
+
registryDir = createTempDir("ot-reg");
|
|
166
|
+
initGitRepo(daemonDir);
|
|
167
|
+
|
|
168
|
+
daemonHandle = await ensureOpentasksDaemon(daemonDir, {
|
|
169
|
+
timeoutMs: 15_000,
|
|
170
|
+
registryPath: path.join(registryDir, "registry.json"),
|
|
171
|
+
});
|
|
172
|
+
canStartDaemon = true;
|
|
173
|
+
log(`Daemon started at ${daemonHandle.socketPath}`);
|
|
174
|
+
} catch (err) {
|
|
175
|
+
console.warn(`[dispatch-coord] Skipping: daemon could not start: ${(err as Error).message}`);
|
|
176
|
+
canStartDaemon = false;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
testRepoDir = createTempDir("dispatch-repo");
|
|
181
|
+
initGitRepo(testRepoDir);
|
|
182
|
+
|
|
183
|
+
const baseDir = path.join(testRepoDir, ".macro-agent");
|
|
184
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
185
|
+
|
|
186
|
+
system = await bootV2({
|
|
187
|
+
cwd: testRepoDir,
|
|
188
|
+
baseDir,
|
|
189
|
+
defaultPermissionMode: "auto-approve",
|
|
190
|
+
inbox: { socketPath: path.join(baseDir, "inbox.sock") },
|
|
191
|
+
tasks: { socketPath: daemonHandle.socketPath },
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
dispatcher = createTaskDispatcher(
|
|
195
|
+
createSourceAdapter(system.tasksAdapter),
|
|
196
|
+
createRuntimeAdapter(system.agentManager),
|
|
197
|
+
{
|
|
198
|
+
claimantId: `test:${process.pid}:dispatch-coord`,
|
|
199
|
+
pollIntervalMs: 600_000,
|
|
200
|
+
defaultRole: "worker",
|
|
201
|
+
concurrency: { global: 3 },
|
|
202
|
+
retry: { maxRetries: 3, baseDelayMs: 1_000, maxDelayMs: 60_000 },
|
|
203
|
+
reconcile: { enabled: true, intervalMs: 600_000 },
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
await dispatcher.start();
|
|
207
|
+
|
|
208
|
+
log("System booted with real opentasks + swarm-dispatch");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
afterEach(async () => {
|
|
212
|
+
if (coordinationCleanup) { coordinationCleanup(); coordinationCleanup = null; }
|
|
213
|
+
if (dispatcher) await dispatcher.stop();
|
|
214
|
+
if (system) {
|
|
215
|
+
try {
|
|
216
|
+
const running = system.agentManager.list({ state: "running" } as any);
|
|
217
|
+
for (const agent of running) {
|
|
218
|
+
try { await system.agentManager.terminate(agent.id, "cancelled"); } catch { /* */ }
|
|
219
|
+
}
|
|
220
|
+
await system.shutdown();
|
|
221
|
+
} catch { /* */ }
|
|
222
|
+
}
|
|
223
|
+
if (daemonHandle) { try { await daemonHandle.stop(); } catch { /* */ } }
|
|
224
|
+
if (testRepoDir) cleanupDir(testRepoDir);
|
|
225
|
+
if (daemonDir) cleanupDir(daemonDir);
|
|
226
|
+
if (registryDir) cleanupDir(registryDir);
|
|
227
|
+
log("Cleanup complete");
|
|
228
|
+
}, 30_000);
|
|
229
|
+
|
|
230
|
+
// ── Full flow: OpenHive assigns task → dispatch → agent ────
|
|
231
|
+
|
|
232
|
+
it(
|
|
233
|
+
"OpenHive task.assign creates task, dispatch picks it up and spawns agent",
|
|
234
|
+
async () => {
|
|
235
|
+
if (!canStartDaemon) return;
|
|
236
|
+
|
|
237
|
+
// Wire coordination handler with a mock MAP connection
|
|
238
|
+
const mockConnection = createMockMAPConnection();
|
|
239
|
+
coordinationCleanup = setupCoordinationHandlers({
|
|
240
|
+
connection: mockConnection,
|
|
241
|
+
agentManager: system.agentManager,
|
|
242
|
+
inboxAdapter: system.inboxAdapter,
|
|
243
|
+
tasksAdapter: system.tasksAdapter,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Simulate OpenHive sending a task assignment
|
|
247
|
+
log("Simulating OpenHive task.assign...");
|
|
248
|
+
await mockConnection.simulateNotification("x-openhive/task.assign", {
|
|
249
|
+
title: "Fix the login page",
|
|
250
|
+
description: "The login form has a CSS bug. Fix it and call done().",
|
|
251
|
+
assigned_by: "openhive-hub",
|
|
252
|
+
priority: "high",
|
|
253
|
+
context: {
|
|
254
|
+
tags: ["auto", "frontend"],
|
|
255
|
+
sourceUrl: "https://linear.app/team/FE-42",
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Verify task was created in opentasks
|
|
260
|
+
await sleep(1_000);
|
|
261
|
+
const readyTasks = await system.tasksAdapter.queryReady();
|
|
262
|
+
log(`Ready tasks after assign: ${readyTasks.length}`);
|
|
263
|
+
expect(readyTasks.length).toBeGreaterThanOrEqual(1);
|
|
264
|
+
|
|
265
|
+
const task = readyTasks.find((t) => t.title === "Fix the login page");
|
|
266
|
+
expect(task).toBeDefined();
|
|
267
|
+
log(`Task created: ${task!.id} — "${task!.title}"`);
|
|
268
|
+
|
|
269
|
+
// Verify priority was mapped
|
|
270
|
+
expect(task!.priority).toBe(2); // "high" → 2
|
|
271
|
+
|
|
272
|
+
// Tags — verified if the daemon returns them
|
|
273
|
+
log(`Tags: ${JSON.stringify(task!.tags)}`);
|
|
274
|
+
if (task!.tags && task!.tags.length > 0) {
|
|
275
|
+
expect(task!.tags).toContain("auto");
|
|
276
|
+
expect(task!.tags).toContain("frontend");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Trigger dispatch — should pick up the task from opentasks
|
|
280
|
+
log("Triggering dispatch...");
|
|
281
|
+
await dispatcher.dispatchNow();
|
|
282
|
+
await sleep(3_000);
|
|
283
|
+
|
|
284
|
+
// Verify agent was spawned
|
|
285
|
+
log(`Tracker active: ${dispatcher.tracker.activeCount()}`);
|
|
286
|
+
expect(dispatcher.tracker.activeCount()).toBeGreaterThanOrEqual(1);
|
|
287
|
+
|
|
288
|
+
const dispatch = dispatcher.tracker.listActive().find(
|
|
289
|
+
(d) => d.taskId === task!.id
|
|
290
|
+
);
|
|
291
|
+
expect(dispatch).toBeDefined();
|
|
292
|
+
|
|
293
|
+
const agentId = dispatch!.agentId;
|
|
294
|
+
log(`Agent spawned: ${agentId}`);
|
|
295
|
+
|
|
296
|
+
const agentRecord = system.agentStore.getAgent(agentId);
|
|
297
|
+
expect(agentRecord).not.toBeNull();
|
|
298
|
+
expect(agentRecord!.state).toBe("running");
|
|
299
|
+
expect(agentRecord!.parent_id).toBeNull(); // Parentless
|
|
300
|
+
},
|
|
301
|
+
TIMEOUT.DISPATCH
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
// ── Multiple OpenHive tasks dispatched concurrently ─────────
|
|
305
|
+
|
|
306
|
+
it(
|
|
307
|
+
"multiple OpenHive tasks are dispatched concurrently",
|
|
308
|
+
async () => {
|
|
309
|
+
if (!canStartDaemon) return;
|
|
310
|
+
|
|
311
|
+
const mockConnection = createMockMAPConnection();
|
|
312
|
+
coordinationCleanup = setupCoordinationHandlers({
|
|
313
|
+
connection: mockConnection,
|
|
314
|
+
agentManager: system.agentManager,
|
|
315
|
+
inboxAdapter: system.inboxAdapter,
|
|
316
|
+
tasksAdapter: system.tasksAdapter,
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Send 3 tasks from OpenHive
|
|
320
|
+
log("Simulating 3 OpenHive task.assign messages...");
|
|
321
|
+
for (let i = 0; i < 3; i++) {
|
|
322
|
+
await mockConnection.simulateNotification("x-openhive/task.assign", {
|
|
323
|
+
title: `Batch task ${i + 1}`,
|
|
324
|
+
description: `Task ${i + 1} from OpenHive batch.`,
|
|
325
|
+
assigned_by: "openhive-hub",
|
|
326
|
+
context: { tags: ["auto", "batch"] },
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
await sleep(1_000);
|
|
331
|
+
const ready = await system.tasksAdapter.queryReady();
|
|
332
|
+
log(`Ready after 3 assigns: ${ready.length}`);
|
|
333
|
+
expect(ready.length).toBeGreaterThanOrEqual(3);
|
|
334
|
+
|
|
335
|
+
// Dispatch all
|
|
336
|
+
log("Triggering dispatch...");
|
|
337
|
+
await dispatcher.dispatchNow();
|
|
338
|
+
await sleep(3_000);
|
|
339
|
+
|
|
340
|
+
log(`Tracker active: ${dispatcher.tracker.activeCount()}`);
|
|
341
|
+
expect(dispatcher.tracker.activeCount()).toBe(3);
|
|
342
|
+
|
|
343
|
+
// All agents running
|
|
344
|
+
for (const record of dispatcher.tracker.listActive()) {
|
|
345
|
+
const agent = system.agentStore.getAgent(record.agentId);
|
|
346
|
+
expect(agent).not.toBeNull();
|
|
347
|
+
expect(agent!.state).toBe("running");
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
TIMEOUT.DISPATCH
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
// ── OpenHive task with agent completion ─────────────────────
|
|
354
|
+
|
|
355
|
+
it(
|
|
356
|
+
"agent completes OpenHive-assigned task and lifecycle tracks it",
|
|
357
|
+
async () => {
|
|
358
|
+
if (!canStartDaemon) return;
|
|
359
|
+
|
|
360
|
+
const mockConnection = createMockMAPConnection();
|
|
361
|
+
coordinationCleanup = setupCoordinationHandlers({
|
|
362
|
+
connection: mockConnection,
|
|
363
|
+
agentManager: system.agentManager,
|
|
364
|
+
inboxAdapter: system.inboxAdapter,
|
|
365
|
+
tasksAdapter: system.tasksAdapter,
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// Track dispatch events
|
|
369
|
+
const events: any[] = [];
|
|
370
|
+
dispatcher.onEvent((e) => events.push(e));
|
|
371
|
+
|
|
372
|
+
// Send task from OpenHive
|
|
373
|
+
log("Simulating OpenHive task.assign...");
|
|
374
|
+
await mockConnection.simulateNotification("x-openhive/task.assign", {
|
|
375
|
+
title: "Write a test file",
|
|
376
|
+
description:
|
|
377
|
+
'Create test.txt with "hello" and call done(status="completed").',
|
|
378
|
+
assigned_by: "openhive-hub",
|
|
379
|
+
priority: "critical",
|
|
380
|
+
context: { tags: ["auto"] },
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
await sleep(1_000);
|
|
384
|
+
|
|
385
|
+
// Dispatch
|
|
386
|
+
log("Triggering dispatch...");
|
|
387
|
+
await dispatcher.dispatchNow();
|
|
388
|
+
await sleep(3_000);
|
|
389
|
+
|
|
390
|
+
expect(dispatcher.tracker.activeCount()).toBe(1);
|
|
391
|
+
const agentId = dispatcher.tracker.listActive()[0].agentId;
|
|
392
|
+
log(`Agent: ${agentId}`);
|
|
393
|
+
|
|
394
|
+
// Prompt agent to complete
|
|
395
|
+
log("Prompting agent to complete...");
|
|
396
|
+
const result = await system.agentManager.promptUntilDone(
|
|
397
|
+
agentId,
|
|
398
|
+
'Create test.txt with "hello", then call done(status="completed", summary="done").',
|
|
399
|
+
{
|
|
400
|
+
maxFollowUps: 3,
|
|
401
|
+
onUpdate: (update: any) => {
|
|
402
|
+
if (update.sessionUpdate === "tool_call") {
|
|
403
|
+
log(` [tool_call] ${update.title ?? "unknown"}`);
|
|
404
|
+
}
|
|
405
|
+
},
|
|
406
|
+
}
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
log(`promptUntilDone: doneCalled=${result.doneCalled}, status=${result.doneStatus}`);
|
|
410
|
+
await sleep(3_000);
|
|
411
|
+
|
|
412
|
+
// Check dispatch events
|
|
413
|
+
const dispatched = events.find((e) => e.type === "dispatched");
|
|
414
|
+
expect(dispatched).toBeDefined();
|
|
415
|
+
log(`Events: ${events.map((e) => e.type).join(", ")}`);
|
|
416
|
+
|
|
417
|
+
if (result.doneCalled && result.doneStatus === "completed") {
|
|
418
|
+
// Task should be completed in tracker
|
|
419
|
+
const completed = events.find((e) => e.type === "completed");
|
|
420
|
+
expect(completed).toBeDefined();
|
|
421
|
+
expect(dispatcher.tracker.isTracked(dispatched.taskId)).toBe(false);
|
|
422
|
+
|
|
423
|
+
const agentAfter = system.agentStore.getAgent(agentId);
|
|
424
|
+
expect(agentAfter?.state).toBe("stopped");
|
|
425
|
+
} else {
|
|
426
|
+
log("Agent did not call done(completed) — LLM behavior variance");
|
|
427
|
+
}
|
|
428
|
+
},
|
|
429
|
+
TIMEOUT.MULTI
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
// ── Context metadata flows through to task ──────────────────
|
|
433
|
+
|
|
434
|
+
it(
|
|
435
|
+
"OpenHive context metadata is preserved in opentasks task",
|
|
436
|
+
async () => {
|
|
437
|
+
if (!canStartDaemon) return;
|
|
438
|
+
|
|
439
|
+
const mockConnection = createMockMAPConnection();
|
|
440
|
+
coordinationCleanup = setupCoordinationHandlers({
|
|
441
|
+
connection: mockConnection,
|
|
442
|
+
agentManager: system.agentManager,
|
|
443
|
+
inboxAdapter: system.inboxAdapter,
|
|
444
|
+
tasksAdapter: system.tasksAdapter,
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
await mockConnection.simulateNotification("x-openhive/task.assign", {
|
|
448
|
+
title: "Metadata test task",
|
|
449
|
+
description: "Test that metadata flows through.",
|
|
450
|
+
assigned_by: "hub-user-123",
|
|
451
|
+
priority: "low",
|
|
452
|
+
deadline: "2026-12-31T00:00:00Z",
|
|
453
|
+
context: {
|
|
454
|
+
tags: ["auto", "test"],
|
|
455
|
+
sourceUrl: "https://jira.example.com/PROJ-99",
|
|
456
|
+
criteria: ["Must pass CI", "No regressions"],
|
|
457
|
+
files: ["src/main.ts"],
|
|
458
|
+
custom_field: "custom_value",
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
await sleep(1_000);
|
|
463
|
+
const tasks = await system.tasksAdapter.queryReady();
|
|
464
|
+
const task = tasks.find((t) => t.title === "Metadata test task");
|
|
465
|
+
expect(task).toBeDefined();
|
|
466
|
+
|
|
467
|
+
// Priority mapped: "low" → 4
|
|
468
|
+
expect(task!.priority).toBe(4);
|
|
469
|
+
|
|
470
|
+
// Tags and metadata — verified if the daemon returns them
|
|
471
|
+
// (opentasks daemon stores these but may not return them in all query modes)
|
|
472
|
+
log(`Tags: ${JSON.stringify(task!.tags)}`);
|
|
473
|
+
log(`Metadata: ${JSON.stringify(task!.metadata)}`);
|
|
474
|
+
|
|
475
|
+
if (task!.tags && task!.tags.length > 0) {
|
|
476
|
+
expect(task!.tags).toContain("auto");
|
|
477
|
+
expect(task!.tags).toContain("test");
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (task!.metadata && Object.keys(task!.metadata).length > 0) {
|
|
481
|
+
expect(task!.metadata.assigned_by).toBe("hub-user-123");
|
|
482
|
+
expect(task!.metadata.deadline).toBe("2026-12-31T00:00:00Z");
|
|
483
|
+
expect(task!.metadata.sourceUrl).toBe("https://jira.example.com/PROJ-99");
|
|
484
|
+
expect(task!.metadata.criteria).toEqual(["Must pass CI", "No regressions"]);
|
|
485
|
+
expect(task!.metadata.files).toEqual(["src/main.ts"]);
|
|
486
|
+
expect(task!.metadata.custom_field).toBe("custom_value");
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Core assertion: task was created with correct title and priority regardless
|
|
490
|
+
expect(task!.title).toBe("Metadata test task");
|
|
491
|
+
expect(task!.priority).toBe(4);
|
|
492
|
+
},
|
|
493
|
+
TIMEOUT.SETUP
|
|
494
|
+
);
|
|
495
|
+
});
|