va-agent-protocol 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +558 -0
  3. package/bin/va-orchestrate.mjs +153 -0
  4. package/dist/adapter/agent-adapter.d.ts +79 -0
  5. package/dist/adapter/agent-adapter.d.ts.map +1 -0
  6. package/dist/adapter/agent-adapter.js +36 -0
  7. package/dist/adapter/agent-adapter.js.map +1 -0
  8. package/dist/adapter/codex-adapter.d.ts +54 -0
  9. package/dist/adapter/codex-adapter.d.ts.map +1 -0
  10. package/dist/adapter/codex-adapter.js +409 -0
  11. package/dist/adapter/codex-adapter.js.map +1 -0
  12. package/dist/adapter/va-auto-pilot-adapter.d.ts +51 -0
  13. package/dist/adapter/va-auto-pilot-adapter.d.ts.map +1 -0
  14. package/dist/adapter/va-auto-pilot-adapter.js +275 -0
  15. package/dist/adapter/va-auto-pilot-adapter.js.map +1 -0
  16. package/dist/index.d.ts +17 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +15 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/orchestrator/orchestrator.d.ts +101 -0
  21. package/dist/orchestrator/orchestrator.d.ts.map +1 -0
  22. package/dist/orchestrator/orchestrator.js +452 -0
  23. package/dist/orchestrator/orchestrator.js.map +1 -0
  24. package/dist/orchestrator/registry.d.ts +39 -0
  25. package/dist/orchestrator/registry.d.ts.map +1 -0
  26. package/dist/orchestrator/registry.js +62 -0
  27. package/dist/orchestrator/registry.js.map +1 -0
  28. package/dist/orchestrator/scheduler.d.ts +66 -0
  29. package/dist/orchestrator/scheduler.d.ts.map +1 -0
  30. package/dist/orchestrator/scheduler.js +155 -0
  31. package/dist/orchestrator/scheduler.js.map +1 -0
  32. package/dist/test/orchestrator-enqueue.test.d.ts +2 -0
  33. package/dist/test/orchestrator-enqueue.test.d.ts.map +1 -0
  34. package/dist/test/orchestrator-enqueue.test.js +64 -0
  35. package/dist/test/orchestrator-enqueue.test.js.map +1 -0
  36. package/dist/test/orchestrator-lifecycle.test.d.ts +2 -0
  37. package/dist/test/orchestrator-lifecycle.test.d.ts.map +1 -0
  38. package/dist/test/orchestrator-lifecycle.test.js +227 -0
  39. package/dist/test/orchestrator-lifecycle.test.js.map +1 -0
  40. package/dist/test/scheduler.test.d.ts +2 -0
  41. package/dist/test/scheduler.test.d.ts.map +1 -0
  42. package/dist/test/scheduler.test.js +140 -0
  43. package/dist/test/scheduler.test.js.map +1 -0
  44. package/dist/types/index.d.ts +209 -0
  45. package/dist/types/index.d.ts.map +1 -0
  46. package/dist/types/index.js +45 -0
  47. package/dist/types/index.js.map +1 -0
  48. package/dist/utils/logger.d.ts +17 -0
  49. package/dist/utils/logger.d.ts.map +1 -0
  50. package/dist/utils/logger.js +21 -0
  51. package/dist/utils/logger.js.map +1 -0
  52. package/package.json +61 -0
  53. package/schemas/agent-capability.schema.json +99 -0
  54. package/schemas/evidence.schema.json +201 -0
  55. package/schemas/lifecycle.schema.json +80 -0
  56. package/schemas/message.schema.json +181 -0
  57. package/schemas/task-unit.schema.json +137 -0
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Scheduler — match tasks to capable agents, respecting constraints.
3
+ *
4
+ * Responsibilities:
5
+ * - Match task requirements to agent capabilities
6
+ * - Respect concurrency limits
7
+ * - Handle dependency ordering
8
+ * - Select the best agent when multiple can handle a task
9
+ */
10
+ export class Scheduler {
11
+ registry;
12
+ /** Track how many tasks each agent is currently running. */
13
+ activeCounts = new Map();
14
+ constructor(registry) {
15
+ this.registry = registry;
16
+ }
17
+ /**
18
+ * Schedule a single task: find the best available agent.
19
+ *
20
+ * Selection criteria (in order):
21
+ * 1. Must have required capabilities (inferred from task)
22
+ * 2. Must have available concurrency slots
23
+ * 3. Prefer agents with fewer extra capabilities (more specialized)
24
+ */
25
+ schedule(task) {
26
+ const requiredCapabilities = this.inferCapabilities(task);
27
+ const candidates = this.registry.findByCapabilities(requiredCapabilities);
28
+ if (candidates.length === 0) {
29
+ return {
30
+ type: "rejected",
31
+ rejection: {
32
+ taskId: task.id,
33
+ reason: `No agent found with capabilities: ${requiredCapabilities.join(", ")}`,
34
+ },
35
+ };
36
+ }
37
+ // Filter by concurrency availability
38
+ const available = candidates.filter((c) => this.hasCapacity(c));
39
+ if (available.length === 0) {
40
+ return {
41
+ type: "rejected",
42
+ rejection: {
43
+ taskId: task.id,
44
+ reason: `All capable agents are at max concurrency.`,
45
+ },
46
+ };
47
+ }
48
+ // Pick the best available agent
49
+ const selected = available[0];
50
+ return {
51
+ type: "scheduled",
52
+ decision: {
53
+ taskId: task.id,
54
+ agentId: selected.capability.agentId,
55
+ adapter: selected.adapter,
56
+ reason: `Matched capabilities: ${requiredCapabilities.join(", ")}`,
57
+ },
58
+ };
59
+ }
60
+ /**
61
+ * Schedule multiple tasks, respecting dependencies.
62
+ * Returns tasks in executable order (dependencies first).
63
+ */
64
+ scheduleBatch(tasks) {
65
+ const scheduled = [];
66
+ const rejected = [];
67
+ const deferred = [];
68
+ // Separate tasks with unsatisfied dependencies
69
+ const completedIds = new Set();
70
+ const pending = [...tasks];
71
+ // Simple topological pass: keep scheduling until no progress
72
+ let madeProgress = true;
73
+ while (madeProgress && pending.length > 0) {
74
+ madeProgress = false;
75
+ const remaining = [];
76
+ for (const task of pending) {
77
+ const deps = task.dependsOn ?? [];
78
+ const satisfied = deps.every((dep) => completedIds.has(dep));
79
+ if (!satisfied) {
80
+ remaining.push(task);
81
+ continue;
82
+ }
83
+ const result = this.schedule(task);
84
+ if (result.type === "scheduled") {
85
+ scheduled.push(result.decision);
86
+ completedIds.add(task.id);
87
+ this.acquire(result.decision.agentId);
88
+ madeProgress = true;
89
+ }
90
+ else {
91
+ rejected.push(result.rejection);
92
+ madeProgress = true;
93
+ }
94
+ }
95
+ pending.length = 0;
96
+ pending.push(...remaining);
97
+ }
98
+ // Anything left has unsatisfied dependencies
99
+ deferred.push(...pending);
100
+ return { scheduled, rejected, deferred };
101
+ }
102
+ /** Mark an agent slot as acquired. */
103
+ acquire(agentId) {
104
+ this.activeCounts.set(agentId, (this.activeCounts.get(agentId) ?? 0) + 1);
105
+ }
106
+ /** Release an agent slot. */
107
+ release(agentId) {
108
+ const current = this.activeCounts.get(agentId) ?? 0;
109
+ if (current > 0) {
110
+ this.activeCounts.set(agentId, current - 1);
111
+ }
112
+ }
113
+ /** Reset all concurrency tracking. */
114
+ reset() {
115
+ this.activeCounts.clear();
116
+ }
117
+ // ─── Private ─────────────────────────────────────────────────────────────
118
+ hasCapacity(agent) {
119
+ const max = agent.capability.constraints?.maxConcurrent ?? 1;
120
+ const current = this.activeCounts.get(agent.capability.agentId) ?? 0;
121
+ return current < max;
122
+ }
123
+ /**
124
+ * Infer required capabilities from task content.
125
+ * This is a simple heuristic — adapters can override with explicit capability tags.
126
+ */
127
+ inferCapabilities(task) {
128
+ const caps = [];
129
+ const text = `${task.objective} ${(task.constraints ?? []).join(" ")}`.toLowerCase();
130
+ if (text.includes("implement") ||
131
+ text.includes("build") ||
132
+ text.includes("create") ||
133
+ text.includes("add")) {
134
+ caps.push("code-generation");
135
+ }
136
+ if (text.includes("review") || text.includes("audit")) {
137
+ caps.push("code-review");
138
+ }
139
+ if (text.includes("test")) {
140
+ caps.push("testing");
141
+ }
142
+ if (text.includes("refactor") || text.includes("optimize")) {
143
+ caps.push("refactoring");
144
+ }
145
+ if (text.includes("deploy") || text.includes("release")) {
146
+ caps.push("deployment");
147
+ }
148
+ // Default: if nothing inferred, require code-generation as baseline
149
+ if (caps.length === 0) {
150
+ caps.push("code-generation");
151
+ }
152
+ return caps;
153
+ }
154
+ }
155
+ //# sourceMappingURL=scheduler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../src/orchestrator/scheduler.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=orchestrator-enqueue.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator-enqueue.test.d.ts","sourceRoot":"","sources":["../../src/test/orchestrator-enqueue.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,64 @@
1
+ import assert from "node:assert";
2
+ import { describe, it } from "node:test";
3
+ import { AgentRegistry, Orchestrator, } from "../index.js";
4
+ const mockAdapter = {
5
+ id: "mock-agent",
6
+ capabilities() {
7
+ return {
8
+ agentId: "mock-agent",
9
+ name: "Mock Agent",
10
+ version: "1.0.0",
11
+ capabilities: ["test"],
12
+ interface: {
13
+ type: "cli",
14
+ command: "mock-agent",
15
+ },
16
+ };
17
+ },
18
+ async dispatch(task) {
19
+ return {
20
+ correlationId: `corr-${task.id}`,
21
+ accepted: true,
22
+ };
23
+ },
24
+ async poll(correlationId) {
25
+ return {
26
+ correlationId,
27
+ state: "running",
28
+ };
29
+ },
30
+ async cancel(_correlationId) {
31
+ return;
32
+ },
33
+ };
34
+ function makeTask(id, dependsOn) {
35
+ return {
36
+ id,
37
+ objective: `Task ${id}`,
38
+ acceptanceCriteria: ["should complete"],
39
+ dependsOn,
40
+ };
41
+ }
42
+ describe("Orchestrator.enqueue", () => {
43
+ it("adds tasks to the queue", () => {
44
+ const registry = new AgentRegistry();
45
+ registry.register(mockAdapter);
46
+ const orchestrator = new Orchestrator(registry);
47
+ orchestrator.enqueue(makeTask("task-1"), makeTask("task-2", ["task-1"]));
48
+ assert.equal(orchestrator.pendingCount, 2);
49
+ });
50
+ it("rejects tasks that participate in dependency cycles", () => {
51
+ const failedTaskIds = [];
52
+ const registry = new AgentRegistry();
53
+ registry.register(mockAdapter);
54
+ const orchestrator = new Orchestrator(registry, {
55
+ onFailed(taskId) {
56
+ failedTaskIds.push(taskId);
57
+ },
58
+ });
59
+ orchestrator.enqueue(makeTask("task-a", ["task-b"]), makeTask("task-b", ["task-a"]));
60
+ assert.equal(orchestrator.pendingCount, 0);
61
+ assert.deepEqual(new Set(failedTaskIds), new Set(["task-a", "task-b"]));
62
+ });
63
+ });
64
+ //# sourceMappingURL=orchestrator-enqueue.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator-enqueue.test.js","sourceRoot":"","sources":["../../src/test/orchestrator-enqueue.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,EACL,aAAa,EACb,YAAY,GAKb,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,GAAiB;IAChC,EAAE,EAAE,YAAY;IAChB,YAAY;QACV,OAAO;YACL,OAAO,EAAE,YAAY;YACrB,IAAI,EAAE,YAAY;YAClB,OAAO,EAAE,OAAO;YAChB,YAAY,EAAE,CAAC,MAAM,CAAC;YACtB,SAAS,EAAE;gBACT,IAAI,EAAE,KAAK;gBACX,OAAO,EAAE,YAAY;aACtB;SACF,CAAC;IACJ,CAAC;IACD,KAAK,CAAC,QAAQ,CAAC,IAAc;QAC3B,OAAO;YACL,aAAa,EAAE,QAAQ,IAAI,CAAC,EAAE,EAAE;YAChC,QAAQ,EAAE,IAAI;SACf,CAAC;IACJ,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,aAAqB;QAC9B,OAAO;YACL,aAAa;YACb,KAAK,EAAE,SAAS;SACjB,CAAC;IACJ,CAAC;IACD,KAAK,CAAC,MAAM,CAAC,cAAsB;QACjC,OAAO;IACT,CAAC;CACF,CAAC;AAEF,SAAS,QAAQ,CAAC,EAAU,EAAE,SAAoB;IAChD,OAAO;QACL,EAAE;QACF,SAAS,EAAE,QAAQ,EAAE,EAAE;QACvB,kBAAkB,EAAE,CAAC,iBAAiB,CAAC;QACvC,SAAS;KACV,CAAC;AACJ,CAAC;AAED,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACjC,MAAM,QAAQ,GAAG,IAAI,aAAa,EAAE,CAAC;QACrC,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAE/B,MAAM,YAAY,GAAG,IAAI,YAAY,CAAC,QAAQ,CAAC,CAAC;QAEhD,YAAY,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAEzE,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC7D,MAAM,aAAa,GAAa,EAAE,CAAC;QAEnC,MAAM,QAAQ,GAAG,IAAI,aAAa,EAAE,CAAC;QACrC,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;QAE/B,MAAM,YAAY,GAAG,IAAI,YAAY,CAAC,QAAQ,EAAE;YAC9C,QAAQ,CAAC,MAAM;gBACb,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC7B,CAAC;SACF,CAAC,CAAC;QAEH,YAAY,CAAC,OAAO,CAClB,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,EAC9B,QAAQ,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,CAAC,CAC/B,CAAC;QAEF,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC;QAC3C,MAAM,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,aAAa,CAAC,EAAE,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=orchestrator-lifecycle.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator-lifecycle.test.d.ts","sourceRoot":"","sources":["../../src/test/orchestrator-lifecycle.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,227 @@
1
+ import assert from "node:assert";
2
+ import { describe, it, beforeEach } from "node:test";
3
+ import { AgentRegistry, Orchestrator, noopLogger, } from "../index.js";
4
+ /**
5
+ * Configurable mock adapter that lets tests control poll responses.
6
+ */
7
+ class MockAdapter {
8
+ caps;
9
+ maxConcurrent;
10
+ id;
11
+ correlationCounter = 0;
12
+ /** Map of correlationId → sequence of PollResults to return. */
13
+ pollResponses = new Map();
14
+ pollIndex = new Map();
15
+ dispatched = [];
16
+ cancelled = [];
17
+ constructor(id, caps = ["code-generation"], maxConcurrent = 2) {
18
+ this.caps = caps;
19
+ this.maxConcurrent = maxConcurrent;
20
+ this.id = id;
21
+ }
22
+ capabilities() {
23
+ return {
24
+ agentId: this.id,
25
+ name: `Mock ${this.id}`,
26
+ version: "1.0.0",
27
+ capabilities: this.caps,
28
+ interface: { type: "cli", command: this.id },
29
+ constraints: { maxConcurrent: this.maxConcurrent },
30
+ };
31
+ }
32
+ /** Pre-configure the poll responses for the next dispatched task. */
33
+ setPollSequence(correlationId, responses) {
34
+ this.pollResponses.set(correlationId, responses);
35
+ this.pollIndex.set(correlationId, 0);
36
+ }
37
+ async dispatch(task) {
38
+ this.dispatched.push(task);
39
+ const correlationId = `corr-${this.id}-${++this.correlationCounter}`;
40
+ return { correlationId, accepted: true };
41
+ }
42
+ async poll(correlationId) {
43
+ const responses = this.pollResponses.get(correlationId);
44
+ if (!responses) {
45
+ return { correlationId, state: "running" };
46
+ }
47
+ const idx = this.pollIndex.get(correlationId) ?? 0;
48
+ const response = responses[Math.min(idx, responses.length - 1)];
49
+ this.pollIndex.set(correlationId, idx + 1);
50
+ return response;
51
+ }
52
+ async cancel(correlationId) {
53
+ this.cancelled.push(correlationId);
54
+ }
55
+ }
56
+ function makeTask(id, opts = {}) {
57
+ return {
58
+ id,
59
+ objective: opts.objective ?? `Task ${id}`,
60
+ acceptanceCriteria: opts.acceptanceCriteria ?? ["should pass"],
61
+ ...opts,
62
+ };
63
+ }
64
+ describe("Orchestrator lifecycle", () => {
65
+ let registry;
66
+ let adapter;
67
+ beforeEach(() => {
68
+ registry = new AgentRegistry();
69
+ adapter = new MockAdapter("test-agent");
70
+ registry.register(adapter);
71
+ });
72
+ describe("state transitions", () => {
73
+ it("transitions to completed and fires onCompleted callback", async () => {
74
+ const completed = [];
75
+ const orchestrator = new Orchestrator(registry, {
76
+ logger: noopLogger,
77
+ onCompleted: (taskId) => completed.push(taskId),
78
+ });
79
+ const task = makeTask("T1", { objective: "Build something" });
80
+ const correlationId = await orchestrator.dispatchNow(task);
81
+ assert.ok(correlationId);
82
+ // Configure adapter to return completed on next poll
83
+ adapter.setPollSequence(correlationId, [
84
+ {
85
+ correlationId,
86
+ state: "completed",
87
+ evidence: {
88
+ taskId: "T1",
89
+ status: "completed",
90
+ verification: "All gates passed",
91
+ },
92
+ },
93
+ ]);
94
+ // Manually trigger a poll cycle by starting/stopping quickly
95
+ // Use dispatchNow + direct status check
96
+ const status = orchestrator.status();
97
+ assert.equal(status.length, 1);
98
+ assert.equal(status[0].state, "running");
99
+ await orchestrator.shutdown();
100
+ });
101
+ it("transitions to failed and retries with pitfall context", async () => {
102
+ const failedPermanently = [];
103
+ const orchestrator = new Orchestrator(registry, {
104
+ logger: noopLogger,
105
+ maxRetries: 0, // No retries — fail immediately
106
+ onFailed: (taskId) => failedPermanently.push(taskId),
107
+ });
108
+ const task = makeTask("T1", { objective: "Create a new feature" });
109
+ const correlationId = await orchestrator.dispatchNow(task);
110
+ assert.ok(correlationId);
111
+ // Configure adapter to return failed
112
+ adapter.setPollSequence(correlationId, [
113
+ {
114
+ correlationId,
115
+ state: "failed",
116
+ evidence: {
117
+ taskId: "T1",
118
+ status: "failed",
119
+ failureDetail: {
120
+ failureType: "gate",
121
+ attempted: "typecheck",
122
+ hypothesis: "Missing import statement",
123
+ },
124
+ },
125
+ },
126
+ ]);
127
+ await orchestrator.shutdown();
128
+ });
129
+ it("handles blocked state and onBlocked callback", async () => {
130
+ const blocked = [];
131
+ const orchestrator = new Orchestrator(registry, {
132
+ logger: noopLogger,
133
+ onBlocked: (taskId, reason) => blocked.push({ taskId, reason }),
134
+ });
135
+ const task = makeTask("T1", { objective: "Implement auth" });
136
+ const correlationId = await orchestrator.dispatchNow(task);
137
+ assert.ok(correlationId);
138
+ // Configure adapter to return blocked
139
+ adapter.setPollSequence(correlationId, [
140
+ {
141
+ correlationId,
142
+ state: "blocked",
143
+ evidence: {
144
+ taskId: "T1",
145
+ status: "blocked",
146
+ blockReason: {
147
+ type: "human-decision",
148
+ description: "Needs architecture review",
149
+ },
150
+ },
151
+ },
152
+ ]);
153
+ await orchestrator.shutdown();
154
+ });
155
+ });
156
+ describe("input resolution", () => {
157
+ it("dispatches tasks with static inputs unchanged", async () => {
158
+ const orchestrator = new Orchestrator(registry, {
159
+ logger: noopLogger,
160
+ });
161
+ const task = makeTask("T1", {
162
+ objective: "Build a component",
163
+ inputs: { framework: "react", version: 18 },
164
+ });
165
+ const correlationId = await orchestrator.dispatchNow(task);
166
+ assert.ok(correlationId);
167
+ // The adapter should receive the task with inputs intact
168
+ assert.equal(adapter.dispatched.length, 1);
169
+ assert.deepEqual(adapter.dispatched[0].inputs, {
170
+ framework: "react",
171
+ version: 18,
172
+ });
173
+ await orchestrator.shutdown();
174
+ });
175
+ });
176
+ describe("timeout", () => {
177
+ it("respects task timeout during dispatch", async () => {
178
+ const failedTasks = [];
179
+ const orchestrator = new Orchestrator(registry, {
180
+ logger: noopLogger,
181
+ onFailed: (taskId) => failedTasks.push(taskId),
182
+ });
183
+ // Create a task with an already-expired timeout (0ms)
184
+ const task = makeTask("T1", {
185
+ objective: "Build something",
186
+ timeout: 1, // 1ms — will timeout immediately on next poll
187
+ });
188
+ const correlationId = await orchestrator.dispatchNow(task);
189
+ assert.ok(correlationId);
190
+ // Verify the task is active before timeout check
191
+ const status = orchestrator.status();
192
+ assert.equal(status.length, 1);
193
+ await orchestrator.shutdown();
194
+ });
195
+ });
196
+ describe("cancellation", () => {
197
+ it("cancels an active task", async () => {
198
+ const orchestrator = new Orchestrator(registry, {
199
+ logger: noopLogger,
200
+ });
201
+ const task = makeTask("T1", { objective: "Build a module" });
202
+ const correlationId = await orchestrator.dispatchNow(task);
203
+ assert.ok(correlationId);
204
+ await orchestrator.cancelTask(correlationId);
205
+ // Task should be removed from active
206
+ assert.equal(orchestrator.status().length, 0);
207
+ // Adapter should have received cancel
208
+ assert.ok(adapter.cancelled.includes(correlationId));
209
+ await orchestrator.shutdown();
210
+ });
211
+ });
212
+ describe("dependency ordering", () => {
213
+ it("enqueues tasks and respects pendingCount", () => {
214
+ const orchestrator = new Orchestrator(registry, {
215
+ logger: noopLogger,
216
+ });
217
+ const t1 = makeTask("T1", { objective: "Build core" });
218
+ const t2 = makeTask("T2", {
219
+ objective: "Add tests",
220
+ dependsOn: ["T1"],
221
+ });
222
+ orchestrator.enqueue(t1, t2);
223
+ assert.equal(orchestrator.pendingCount, 2);
224
+ });
225
+ });
226
+ });
227
+ //# sourceMappingURL=orchestrator-lifecycle.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"orchestrator-lifecycle.test.js","sourceRoot":"","sources":["../../src/test/orchestrator-lifecycle.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=scheduler.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.test.d.ts","sourceRoot":"","sources":["../../src/test/scheduler.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,140 @@
1
+ import assert from "node:assert";
2
+ import { describe, it, beforeEach } from "node:test";
3
+ import { AgentRegistry, Scheduler, } from "../index.js";
4
+ function makeAdapter(id, capabilities, maxConcurrent = 1) {
5
+ return {
6
+ id,
7
+ capabilities() {
8
+ return {
9
+ agentId: id,
10
+ name: `Agent ${id}`,
11
+ version: "1.0.0",
12
+ capabilities,
13
+ interface: { type: "cli", command: id },
14
+ constraints: { maxConcurrent },
15
+ };
16
+ },
17
+ async dispatch(_task) {
18
+ return { correlationId: `corr-${_task.id}`, accepted: true };
19
+ },
20
+ async poll(correlationId) {
21
+ return { correlationId, state: "running" };
22
+ },
23
+ async cancel() { },
24
+ };
25
+ }
26
+ function makeTask(id, objective, dependsOn) {
27
+ return {
28
+ id,
29
+ objective,
30
+ acceptanceCriteria: ["should pass"],
31
+ dependsOn,
32
+ };
33
+ }
34
+ describe("Scheduler", () => {
35
+ let registry;
36
+ let scheduler;
37
+ beforeEach(() => {
38
+ registry = new AgentRegistry();
39
+ scheduler = new Scheduler(registry);
40
+ });
41
+ describe("capability matching", () => {
42
+ it("matches a task to an agent with the required capability", () => {
43
+ registry.register(makeAdapter("coder", ["code-generation"]));
44
+ const result = scheduler.schedule(makeTask("T1", "Implement a login form"));
45
+ assert.equal(result.type, "scheduled");
46
+ if (result.type === "scheduled") {
47
+ assert.equal(result.decision.agentId, "coder");
48
+ }
49
+ });
50
+ it("rejects a task when no agent has the required capability", () => {
51
+ registry.register(makeAdapter("reviewer", ["code-review"]));
52
+ // "Implement" infers "code-generation" which the reviewer doesn't have
53
+ const result = scheduler.schedule(makeTask("T1", "Implement feature X"));
54
+ assert.equal(result.type, "rejected");
55
+ if (result.type === "rejected") {
56
+ assert.ok(result.rejection.reason.includes("No agent found"));
57
+ }
58
+ });
59
+ it("prefers specialized agents over generalists", () => {
60
+ // Generalist has more capabilities
61
+ registry.register(makeAdapter("generalist", [
62
+ "code-generation",
63
+ "code-review",
64
+ "testing",
65
+ "refactoring",
66
+ ]));
67
+ // Specialist has only what's needed
68
+ registry.register(makeAdapter("specialist", ["code-generation"]));
69
+ const result = scheduler.schedule(makeTask("T1", "Build a new API endpoint"));
70
+ assert.equal(result.type, "scheduled");
71
+ if (result.type === "scheduled") {
72
+ assert.equal(result.decision.agentId, "specialist");
73
+ }
74
+ });
75
+ });
76
+ describe("concurrency limits", () => {
77
+ it("rejects when all capable agents are at max concurrency", () => {
78
+ registry.register(makeAdapter("coder", ["code-generation"], 1));
79
+ scheduler.acquire("coder"); // Fill the single slot
80
+ const result = scheduler.schedule(makeTask("T1", "Create a new module"));
81
+ assert.equal(result.type, "rejected");
82
+ if (result.type === "rejected") {
83
+ assert.ok(result.rejection.reason.includes("max concurrency"));
84
+ }
85
+ });
86
+ it("allows scheduling when concurrency slots are available", () => {
87
+ registry.register(makeAdapter("coder", ["code-generation"], 2));
88
+ scheduler.acquire("coder"); // Fill one of two slots
89
+ const result = scheduler.schedule(makeTask("T1", "Create a new module"));
90
+ assert.equal(result.type, "scheduled");
91
+ });
92
+ it("releases slots correctly", () => {
93
+ registry.register(makeAdapter("coder", ["code-generation"], 1));
94
+ scheduler.acquire("coder");
95
+ // Should be rejected — slot full
96
+ const r1 = scheduler.schedule(makeTask("T1", "Build feature"));
97
+ assert.equal(r1.type, "rejected");
98
+ scheduler.release("coder");
99
+ // Now should succeed
100
+ const r2 = scheduler.schedule(makeTask("T2", "Build another feature"));
101
+ assert.equal(r2.type, "scheduled");
102
+ });
103
+ it("reset clears all concurrency tracking", () => {
104
+ registry.register(makeAdapter("coder", ["code-generation"], 1));
105
+ scheduler.acquire("coder");
106
+ scheduler.reset();
107
+ const result = scheduler.schedule(makeTask("T1", "Add a new component"));
108
+ assert.equal(result.type, "scheduled");
109
+ });
110
+ });
111
+ describe("scheduleBatch", () => {
112
+ it("schedules tasks in dependency order", () => {
113
+ registry.register(makeAdapter("coder", ["code-generation", "testing"], 10));
114
+ const tasks = [
115
+ makeTask("T2", "Add tests for module", ["T1"]),
116
+ makeTask("T1", "Build the module"),
117
+ ];
118
+ const { scheduled, rejected, deferred } = scheduler.scheduleBatch(tasks);
119
+ // T1 goes first (no deps), T2 depends on T1
120
+ assert.equal(scheduled.length, 2);
121
+ assert.equal(scheduled[0].taskId, "T1");
122
+ assert.equal(scheduled[1].taskId, "T2");
123
+ assert.equal(rejected.length, 0);
124
+ assert.equal(deferred.length, 0);
125
+ });
126
+ it("defers tasks with unresolvable dependencies", () => {
127
+ registry.register(makeAdapter("coder", ["code-generation"], 10));
128
+ const tasks = [
129
+ makeTask("T1", "Build the module"),
130
+ makeTask("T2", "Add tests", ["T-MISSING"]), // depends on nonexistent
131
+ ];
132
+ const { scheduled, deferred } = scheduler.scheduleBatch(tasks);
133
+ assert.equal(scheduled.length, 1);
134
+ assert.equal(scheduled[0].taskId, "T1");
135
+ assert.equal(deferred.length, 1);
136
+ assert.equal(deferred[0].id, "T2");
137
+ });
138
+ });
139
+ });
140
+ //# sourceMappingURL=scheduler.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.test.js","sourceRoot":"","sources":["../../src/test/scheduler.test.ts"],"names":[],"mappings":"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"}