opencode-swarm-plugin 0.1.0 → 0.2.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.
- package/.beads/issues.jsonl +308 -0
- package/README.md +157 -11
- package/dist/index.js +804 -23
- package/dist/plugin.js +443 -23
- package/package.json +7 -3
- package/src/agent-mail.ts +46 -3
- package/src/index.ts +60 -0
- package/src/learning.integration.test.ts +326 -1
- package/src/storage.integration.test.ts +341 -0
- package/src/storage.ts +679 -0
- package/src/swarm.integration.test.ts +194 -3
- package/src/swarm.ts +185 -32
- package/src/tool-availability.ts +389 -0
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Run with: pnpm test:integration (or docker:test for full Docker environment)
|
|
9
9
|
*/
|
|
10
|
-
import { describe, it, expect, beforeAll } from "vitest";
|
|
10
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
11
11
|
import {
|
|
12
12
|
swarm_decompose,
|
|
13
13
|
swarm_validate_decomposition,
|
|
@@ -277,8 +277,8 @@ describe("swarm_subtask_prompt", () => {
|
|
|
277
277
|
expect(result).toContain("Configure Google OAuth");
|
|
278
278
|
expect(result).toContain("src/auth/google.ts");
|
|
279
279
|
expect(result).toContain("NextAuth.js v5");
|
|
280
|
-
expect(result).toContain("
|
|
281
|
-
expect(result).toContain("
|
|
280
|
+
expect(result).toContain("swarm_progress");
|
|
281
|
+
expect(result).toContain("swarm_complete");
|
|
282
282
|
});
|
|
283
283
|
|
|
284
284
|
it("handles missing optional fields", async () => {
|
|
@@ -761,3 +761,194 @@ describe("full swarm flow (integration)", () => {
|
|
|
761
761
|
},
|
|
762
762
|
);
|
|
763
763
|
});
|
|
764
|
+
|
|
765
|
+
// ============================================================================
|
|
766
|
+
// Tool Availability & Graceful Degradation Tests
|
|
767
|
+
// ============================================================================
|
|
768
|
+
|
|
769
|
+
import {
|
|
770
|
+
checkTool,
|
|
771
|
+
isToolAvailable,
|
|
772
|
+
checkAllTools,
|
|
773
|
+
formatToolAvailability,
|
|
774
|
+
resetToolCache,
|
|
775
|
+
withToolFallback,
|
|
776
|
+
ifToolAvailable,
|
|
777
|
+
} from "./tool-availability";
|
|
778
|
+
import { swarm_init } from "./swarm";
|
|
779
|
+
|
|
780
|
+
describe("Tool Availability", () => {
|
|
781
|
+
beforeAll(() => {
|
|
782
|
+
resetToolCache();
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
afterAll(() => {
|
|
786
|
+
resetToolCache();
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
it("checks individual tool availability", async () => {
|
|
790
|
+
const status = await checkTool("semantic-memory");
|
|
791
|
+
expect(status).toHaveProperty("available");
|
|
792
|
+
expect(status).toHaveProperty("checkedAt");
|
|
793
|
+
expect(typeof status.available).toBe("boolean");
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
it("caches tool availability checks", async () => {
|
|
797
|
+
const status1 = await checkTool("semantic-memory");
|
|
798
|
+
const status2 = await checkTool("semantic-memory");
|
|
799
|
+
// Same timestamp means cached
|
|
800
|
+
expect(status1.checkedAt).toBe(status2.checkedAt);
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
it("checks all tools at once", async () => {
|
|
804
|
+
const availability = await checkAllTools();
|
|
805
|
+
expect(availability.size).toBe(5);
|
|
806
|
+
expect(availability.has("semantic-memory")).toBe(true);
|
|
807
|
+
expect(availability.has("cass")).toBe(true);
|
|
808
|
+
expect(availability.has("ubs")).toBe(true);
|
|
809
|
+
expect(availability.has("beads")).toBe(true);
|
|
810
|
+
expect(availability.has("agent-mail")).toBe(true);
|
|
811
|
+
});
|
|
812
|
+
|
|
813
|
+
it("formats tool availability for display", async () => {
|
|
814
|
+
const availability = await checkAllTools();
|
|
815
|
+
const formatted = formatToolAvailability(availability);
|
|
816
|
+
expect(formatted).toContain("Tool Availability:");
|
|
817
|
+
expect(formatted).toContain("semantic-memory");
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
it("executes with fallback when tool unavailable", async () => {
|
|
821
|
+
// Force cache reset to test fresh
|
|
822
|
+
resetToolCache();
|
|
823
|
+
|
|
824
|
+
const result = await withToolFallback(
|
|
825
|
+
"ubs", // May or may not be available
|
|
826
|
+
async () => "action-result",
|
|
827
|
+
() => "fallback-result",
|
|
828
|
+
);
|
|
829
|
+
|
|
830
|
+
// Either result is valid depending on tool availability
|
|
831
|
+
expect(["action-result", "fallback-result"]).toContain(result);
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
it("returns undefined when tool unavailable with ifToolAvailable", async () => {
|
|
835
|
+
resetToolCache();
|
|
836
|
+
|
|
837
|
+
// This will return undefined if agent-mail is not running
|
|
838
|
+
const result = await ifToolAvailable("agent-mail", async () => "success");
|
|
839
|
+
|
|
840
|
+
// Result is either "success" or undefined
|
|
841
|
+
expect([undefined, "success"]).toContain(result);
|
|
842
|
+
});
|
|
843
|
+
});
|
|
844
|
+
|
|
845
|
+
describe("swarm_init", () => {
|
|
846
|
+
it("reports tool availability status", async () => {
|
|
847
|
+
resetToolCache();
|
|
848
|
+
|
|
849
|
+
const result = await swarm_init.execute({}, mockContext);
|
|
850
|
+
const parsed = JSON.parse(result);
|
|
851
|
+
|
|
852
|
+
expect(parsed).toHaveProperty("ready", true);
|
|
853
|
+
expect(parsed).toHaveProperty("tool_availability");
|
|
854
|
+
expect(parsed).toHaveProperty("report");
|
|
855
|
+
|
|
856
|
+
// Check tool availability structure
|
|
857
|
+
const tools = parsed.tool_availability;
|
|
858
|
+
expect(tools).toHaveProperty("semantic-memory");
|
|
859
|
+
expect(tools).toHaveProperty("cass");
|
|
860
|
+
expect(tools).toHaveProperty("ubs");
|
|
861
|
+
expect(tools).toHaveProperty("beads");
|
|
862
|
+
expect(tools).toHaveProperty("agent-mail");
|
|
863
|
+
|
|
864
|
+
// Each tool should have available and fallback
|
|
865
|
+
for (const [, info] of Object.entries(tools)) {
|
|
866
|
+
expect(info).toHaveProperty("available");
|
|
867
|
+
expect(info).toHaveProperty("fallback");
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
|
|
871
|
+
it("includes recommendations", async () => {
|
|
872
|
+
const result = await swarm_init.execute({}, mockContext);
|
|
873
|
+
const parsed = JSON.parse(result);
|
|
874
|
+
|
|
875
|
+
expect(parsed).toHaveProperty("recommendations");
|
|
876
|
+
expect(parsed.recommendations).toHaveProperty("beads");
|
|
877
|
+
expect(parsed.recommendations).toHaveProperty("agent_mail");
|
|
878
|
+
});
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
describe("Graceful Degradation", () => {
|
|
882
|
+
it("swarm_decompose works without CASS", async () => {
|
|
883
|
+
// This should work regardless of CASS availability
|
|
884
|
+
const result = await swarm_decompose.execute(
|
|
885
|
+
{
|
|
886
|
+
task: "Add user authentication",
|
|
887
|
+
max_subtasks: 3,
|
|
888
|
+
query_cass: true, // Request CASS but it may not be available
|
|
889
|
+
},
|
|
890
|
+
mockContext,
|
|
891
|
+
);
|
|
892
|
+
|
|
893
|
+
const parsed = JSON.parse(result);
|
|
894
|
+
|
|
895
|
+
// Should always return a valid prompt
|
|
896
|
+
expect(parsed).toHaveProperty("prompt");
|
|
897
|
+
expect(parsed.prompt).toContain("Add user authentication");
|
|
898
|
+
|
|
899
|
+
// CASS history should indicate whether it was queried
|
|
900
|
+
expect(parsed).toHaveProperty("cass_history");
|
|
901
|
+
expect(parsed.cass_history).toHaveProperty("queried");
|
|
902
|
+
});
|
|
903
|
+
|
|
904
|
+
it("swarm_decompose can skip CASS explicitly", async () => {
|
|
905
|
+
const result = await swarm_decompose.execute(
|
|
906
|
+
{
|
|
907
|
+
task: "Add user authentication",
|
|
908
|
+
max_subtasks: 3,
|
|
909
|
+
query_cass: false, // Explicitly skip CASS
|
|
910
|
+
},
|
|
911
|
+
mockContext,
|
|
912
|
+
);
|
|
913
|
+
|
|
914
|
+
const parsed = JSON.parse(result);
|
|
915
|
+
|
|
916
|
+
expect(parsed.cass_history.queried).toBe(false);
|
|
917
|
+
});
|
|
918
|
+
|
|
919
|
+
it("decomposition prompt includes beads discipline", async () => {
|
|
920
|
+
const result = await swarm_decompose.execute(
|
|
921
|
+
{
|
|
922
|
+
task: "Build feature X",
|
|
923
|
+
max_subtasks: 3,
|
|
924
|
+
},
|
|
925
|
+
mockContext,
|
|
926
|
+
);
|
|
927
|
+
|
|
928
|
+
const parsed = JSON.parse(result);
|
|
929
|
+
|
|
930
|
+
// Check that beads discipline is in the prompt
|
|
931
|
+
expect(parsed.prompt).toContain("MANDATORY");
|
|
932
|
+
expect(parsed.prompt).toContain("bead");
|
|
933
|
+
expect(parsed.prompt).toContain("Plan aggressively");
|
|
934
|
+
});
|
|
935
|
+
|
|
936
|
+
it("subtask prompt includes agent-mail discipline", async () => {
|
|
937
|
+
const result = await swarm_subtask_prompt.execute(
|
|
938
|
+
{
|
|
939
|
+
agent_name: "TestAgent",
|
|
940
|
+
bead_id: "bd-test123.1",
|
|
941
|
+
epic_id: "bd-test123",
|
|
942
|
+
subtask_title: "Test task",
|
|
943
|
+
files: ["src/test.ts"],
|
|
944
|
+
},
|
|
945
|
+
mockContext,
|
|
946
|
+
);
|
|
947
|
+
|
|
948
|
+
// Check that agent-mail discipline is in the prompt
|
|
949
|
+
expect(result).toContain("MANDATORY");
|
|
950
|
+
expect(result).toContain("Agent Mail");
|
|
951
|
+
expect(result).toContain("agentmail_send");
|
|
952
|
+
expect(result).toContain("Report progress");
|
|
953
|
+
});
|
|
954
|
+
});
|
package/src/swarm.ts
CHANGED
|
@@ -35,6 +35,13 @@ import {
|
|
|
35
35
|
type FeedbackEvent,
|
|
36
36
|
DEFAULT_LEARNING_CONFIG,
|
|
37
37
|
} from "./learning";
|
|
38
|
+
import {
|
|
39
|
+
isToolAvailable,
|
|
40
|
+
warnMissingTool,
|
|
41
|
+
checkAllTools,
|
|
42
|
+
formatToolAvailability,
|
|
43
|
+
type ToolName,
|
|
44
|
+
} from "./tool-availability";
|
|
38
45
|
|
|
39
46
|
// ============================================================================
|
|
40
47
|
// Conflict Detection
|
|
@@ -203,6 +210,18 @@ export const DECOMPOSITION_PROMPT = `You are decomposing a task into paralleliza
|
|
|
203
210
|
|
|
204
211
|
{context_section}
|
|
205
212
|
|
|
213
|
+
## MANDATORY: Beads Issue Tracking
|
|
214
|
+
|
|
215
|
+
**Every subtask MUST become a bead.** This is non-negotiable.
|
|
216
|
+
|
|
217
|
+
After decomposition, the coordinator will:
|
|
218
|
+
1. Create an epic bead for the overall task
|
|
219
|
+
2. Create child beads for each subtask
|
|
220
|
+
3. Track progress through bead status updates
|
|
221
|
+
4. Close beads with summaries when complete
|
|
222
|
+
|
|
223
|
+
Agents MUST update their bead status as they work. No silent progress.
|
|
224
|
+
|
|
206
225
|
## Requirements
|
|
207
226
|
|
|
208
227
|
1. **Break into 2-{max_subtasks} independent subtasks** that can run in parallel
|
|
@@ -210,6 +229,7 @@ export const DECOMPOSITION_PROMPT = `You are decomposing a task into paralleliza
|
|
|
210
229
|
3. **No file overlap** - files cannot appear in multiple subtasks (they get exclusive locks)
|
|
211
230
|
4. **Order by dependency** - if subtask B needs subtask A's output, A must come first in the array
|
|
212
231
|
5. **Estimate complexity** - 1 (trivial) to 5 (complex)
|
|
232
|
+
6. **Plan aggressively** - break down more than you think necessary, smaller is better
|
|
213
233
|
|
|
214
234
|
## Response Format
|
|
215
235
|
|
|
@@ -236,10 +256,12 @@ Respond with a JSON object matching this schema:
|
|
|
236
256
|
|
|
237
257
|
## Guidelines
|
|
238
258
|
|
|
259
|
+
- **Plan aggressively** - when in doubt, split further. 3 small tasks > 1 medium task
|
|
239
260
|
- **Prefer smaller, focused subtasks** over large complex ones
|
|
240
261
|
- **Include test files** in the same subtask as the code they test
|
|
241
262
|
- **Consider shared types** - if multiple files share types, handle that first
|
|
242
263
|
- **Think about imports** - changes to exported APIs affect downstream files
|
|
264
|
+
- **Explicit > implicit** - spell out what each subtask should do, don't assume
|
|
243
265
|
|
|
244
266
|
## File Assignment Examples
|
|
245
267
|
|
|
@@ -277,19 +299,49 @@ send a message to the coordinator requesting the change.
|
|
|
277
299
|
## Shared Context
|
|
278
300
|
{shared_context}
|
|
279
301
|
|
|
302
|
+
## MANDATORY: Beads Tracking
|
|
303
|
+
|
|
304
|
+
You MUST keep your bead updated as you work:
|
|
305
|
+
|
|
306
|
+
1. **Your bead is already in_progress** - don't change this unless blocked
|
|
307
|
+
2. **If blocked**: \`bd update {bead_id} --status blocked\` and message coordinator
|
|
308
|
+
3. **When done**: Use \`swarm_complete\` - it closes your bead automatically
|
|
309
|
+
4. **Discovered issues**: Create new beads with \`bd create "issue" -t bug\`
|
|
310
|
+
|
|
311
|
+
**Never work silently.** Your bead status is how the swarm tracks progress.
|
|
312
|
+
|
|
313
|
+
## MANDATORY: Agent Mail Communication
|
|
314
|
+
|
|
315
|
+
You MUST communicate with other agents:
|
|
316
|
+
|
|
317
|
+
1. **Report progress** every significant milestone (not just at the end)
|
|
318
|
+
2. **Ask questions** if requirements are unclear - don't guess
|
|
319
|
+
3. **Announce blockers** immediately - don't spin trying to fix alone
|
|
320
|
+
4. **Coordinate on shared concerns** - if you see something affecting other agents, say so
|
|
321
|
+
|
|
322
|
+
Use Agent Mail for all communication:
|
|
323
|
+
\`\`\`
|
|
324
|
+
agentmail_send(
|
|
325
|
+
to: ["coordinator" or specific agent],
|
|
326
|
+
subject: "Brief subject",
|
|
327
|
+
body: "Message content",
|
|
328
|
+
thread_id: "{epic_id}"
|
|
329
|
+
)
|
|
330
|
+
\`\`\`
|
|
331
|
+
|
|
280
332
|
## Coordination Protocol
|
|
281
333
|
|
|
282
334
|
1. **Start**: Your bead is already marked in_progress
|
|
283
|
-
2. **Progress**: Use
|
|
284
|
-
3. **Blocked**:
|
|
285
|
-
4. **Complete**: Use
|
|
335
|
+
2. **Progress**: Use swarm_progress to report status updates
|
|
336
|
+
3. **Blocked**: Report immediately via Agent Mail - don't spin
|
|
337
|
+
4. **Complete**: Use swarm_complete when done - it handles:
|
|
286
338
|
- Closing your bead with a summary
|
|
287
339
|
- Releasing file reservations
|
|
288
340
|
- Notifying the coordinator
|
|
289
341
|
|
|
290
342
|
## Self-Evaluation
|
|
291
343
|
|
|
292
|
-
Before calling
|
|
344
|
+
Before calling swarm_complete, evaluate your work:
|
|
293
345
|
- Type safety: Does it compile without errors?
|
|
294
346
|
- No obvious bugs: Did you handle edge cases?
|
|
295
347
|
- Follows patterns: Does it match existing code style?
|
|
@@ -297,17 +349,13 @@ Before calling swarm:complete, evaluate your work:
|
|
|
297
349
|
|
|
298
350
|
If evaluation fails, fix the issues before completing.
|
|
299
351
|
|
|
300
|
-
##
|
|
352
|
+
## Planning Your Work
|
|
301
353
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
body: "Message content",
|
|
308
|
-
thread_id: "{epic_id}"
|
|
309
|
-
)
|
|
310
|
-
\`\`\`
|
|
354
|
+
Before writing code:
|
|
355
|
+
1. **Read the files** you're assigned to understand current state
|
|
356
|
+
2. **Plan your approach** - what changes, in what order?
|
|
357
|
+
3. **Identify risks** - what could go wrong? What dependencies?
|
|
358
|
+
4. **Communicate your plan** via Agent Mail if non-trivial
|
|
311
359
|
|
|
312
360
|
Begin work on your subtask now.`;
|
|
313
361
|
|
|
@@ -440,15 +488,23 @@ export function formatEvaluationPrompt(params: {
|
|
|
440
488
|
* Query beads for subtasks of an epic
|
|
441
489
|
*/
|
|
442
490
|
async function queryEpicSubtasks(epicId: string): Promise<Bead[]> {
|
|
491
|
+
// Check if beads is available
|
|
492
|
+
const beadsAvailable = await isToolAvailable("beads");
|
|
493
|
+
if (!beadsAvailable) {
|
|
494
|
+
warnMissingTool("beads");
|
|
495
|
+
return []; // Return empty - swarm can still function without status tracking
|
|
496
|
+
}
|
|
497
|
+
|
|
443
498
|
const result = await Bun.$`bd list --parent ${epicId} --json`
|
|
444
499
|
.quiet()
|
|
445
500
|
.nothrow();
|
|
446
501
|
|
|
447
502
|
if (result.exitCode !== 0) {
|
|
448
|
-
throw
|
|
449
|
-
|
|
450
|
-
|
|
503
|
+
// Don't throw - just return empty and warn
|
|
504
|
+
console.warn(
|
|
505
|
+
`[swarm] Failed to query subtasks: ${result.stderr.toString()}`,
|
|
451
506
|
);
|
|
507
|
+
return [];
|
|
452
508
|
}
|
|
453
509
|
|
|
454
510
|
try {
|
|
@@ -456,11 +512,8 @@ async function queryEpicSubtasks(epicId: string): Promise<Bead[]> {
|
|
|
456
512
|
return z.array(BeadSchema).parse(parsed);
|
|
457
513
|
} catch (error) {
|
|
458
514
|
if (error instanceof z.ZodError) {
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
"query_subtasks",
|
|
462
|
-
error.issues,
|
|
463
|
-
);
|
|
515
|
+
console.warn(`[swarm] Invalid bead data: ${error.message}`);
|
|
516
|
+
return [];
|
|
464
517
|
}
|
|
465
518
|
throw error;
|
|
466
519
|
}
|
|
@@ -473,6 +526,13 @@ async function querySwarmMessages(
|
|
|
473
526
|
projectKey: string,
|
|
474
527
|
threadId: string,
|
|
475
528
|
): Promise<number> {
|
|
529
|
+
// Check if agent-mail is available
|
|
530
|
+
const agentMailAvailable = await isToolAvailable("agent-mail");
|
|
531
|
+
if (!agentMailAvailable) {
|
|
532
|
+
// Don't warn here - it's checked elsewhere
|
|
533
|
+
return 0;
|
|
534
|
+
}
|
|
535
|
+
|
|
476
536
|
try {
|
|
477
537
|
interface ThreadSummary {
|
|
478
538
|
summary: { total_messages: number };
|
|
@@ -539,16 +599,18 @@ async function queryCassHistory(
|
|
|
539
599
|
task: string,
|
|
540
600
|
limit: number = 3,
|
|
541
601
|
): Promise<CassSearchResult | null> {
|
|
602
|
+
// Check if CASS is available first
|
|
603
|
+
const cassAvailable = await isToolAvailable("cass");
|
|
604
|
+
if (!cassAvailable) {
|
|
605
|
+
warnMissingTool("cass");
|
|
606
|
+
return null;
|
|
607
|
+
}
|
|
608
|
+
|
|
542
609
|
try {
|
|
543
610
|
const result = await Bun.$`cass search ${task} --limit ${limit} --json`
|
|
544
611
|
.quiet()
|
|
545
612
|
.nothrow();
|
|
546
613
|
|
|
547
|
-
if (result.exitCode === 127) {
|
|
548
|
-
// CASS not installed
|
|
549
|
-
return null;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
614
|
if (result.exitCode !== 0) {
|
|
553
615
|
return null;
|
|
554
616
|
}
|
|
@@ -1005,17 +1067,19 @@ async function runUbsScan(files: string[]): Promise<UbsScanResult | null> {
|
|
|
1005
1067
|
return null;
|
|
1006
1068
|
}
|
|
1007
1069
|
|
|
1070
|
+
// Check if UBS is available first
|
|
1071
|
+
const ubsAvailable = await isToolAvailable("ubs");
|
|
1072
|
+
if (!ubsAvailable) {
|
|
1073
|
+
warnMissingTool("ubs");
|
|
1074
|
+
return null;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1008
1077
|
try {
|
|
1009
1078
|
// Run UBS scan with JSON output
|
|
1010
1079
|
const result = await Bun.$`ubs scan ${files.join(" ")} --json`
|
|
1011
1080
|
.quiet()
|
|
1012
1081
|
.nothrow();
|
|
1013
1082
|
|
|
1014
|
-
if (result.exitCode === 127) {
|
|
1015
|
-
// UBS not installed
|
|
1016
|
-
return null;
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
1083
|
const output = result.stdout.toString();
|
|
1020
1084
|
if (!output.trim()) {
|
|
1021
1085
|
return {
|
|
@@ -1395,11 +1459,100 @@ export const swarm_evaluation_prompt = tool({
|
|
|
1395
1459
|
},
|
|
1396
1460
|
});
|
|
1397
1461
|
|
|
1462
|
+
/**
|
|
1463
|
+
* Initialize swarm and check tool availability
|
|
1464
|
+
*
|
|
1465
|
+
* Call this at the start of a swarm session to see what tools are available
|
|
1466
|
+
* and what features will be degraded.
|
|
1467
|
+
*/
|
|
1468
|
+
export const swarm_init = tool({
|
|
1469
|
+
description:
|
|
1470
|
+
"Initialize swarm session and check tool availability. Call at swarm start to see what features are available.",
|
|
1471
|
+
args: {
|
|
1472
|
+
project_path: tool.schema
|
|
1473
|
+
.string()
|
|
1474
|
+
.optional()
|
|
1475
|
+
.describe("Project path (for Agent Mail init)"),
|
|
1476
|
+
},
|
|
1477
|
+
async execute(args) {
|
|
1478
|
+
// Check all tools
|
|
1479
|
+
const availability = await checkAllTools();
|
|
1480
|
+
|
|
1481
|
+
// Build status report
|
|
1482
|
+
const report = formatToolAvailability(availability);
|
|
1483
|
+
|
|
1484
|
+
// Check critical tools
|
|
1485
|
+
const beadsAvailable = availability.get("beads")?.status.available ?? false;
|
|
1486
|
+
const agentMailAvailable =
|
|
1487
|
+
availability.get("agent-mail")?.status.available ?? false;
|
|
1488
|
+
|
|
1489
|
+
// Build warnings
|
|
1490
|
+
const warnings: string[] = [];
|
|
1491
|
+
const degradedFeatures: string[] = [];
|
|
1492
|
+
|
|
1493
|
+
if (!beadsAvailable) {
|
|
1494
|
+
warnings.push(
|
|
1495
|
+
"⚠️ beads (bd) not available - issue tracking disabled, swarm coordination will be limited",
|
|
1496
|
+
);
|
|
1497
|
+
degradedFeatures.push("issue tracking", "progress persistence");
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
if (!agentMailAvailable) {
|
|
1501
|
+
warnings.push(
|
|
1502
|
+
"⚠️ agent-mail not available - multi-agent communication disabled",
|
|
1503
|
+
);
|
|
1504
|
+
degradedFeatures.push("agent communication", "file reservations");
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
if (!availability.get("cass")?.status.available) {
|
|
1508
|
+
degradedFeatures.push("historical context from past sessions");
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
if (!availability.get("ubs")?.status.available) {
|
|
1512
|
+
degradedFeatures.push("pre-completion bug scanning");
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
if (!availability.get("semantic-memory")?.status.available) {
|
|
1516
|
+
degradedFeatures.push("persistent learning (using in-memory fallback)");
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
return JSON.stringify(
|
|
1520
|
+
{
|
|
1521
|
+
ready: true,
|
|
1522
|
+
tool_availability: Object.fromEntries(
|
|
1523
|
+
Array.from(availability.entries()).map(([k, v]) => [
|
|
1524
|
+
k,
|
|
1525
|
+
{
|
|
1526
|
+
available: v.status.available,
|
|
1527
|
+
fallback: v.status.available ? null : v.fallbackBehavior,
|
|
1528
|
+
},
|
|
1529
|
+
]),
|
|
1530
|
+
),
|
|
1531
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
1532
|
+
degraded_features:
|
|
1533
|
+
degradedFeatures.length > 0 ? degradedFeatures : undefined,
|
|
1534
|
+
recommendations: {
|
|
1535
|
+
beads: beadsAvailable
|
|
1536
|
+
? "✓ Use beads for all task tracking"
|
|
1537
|
+
: "Install beads: npm i -g @joelhooks/beads",
|
|
1538
|
+
agent_mail: agentMailAvailable
|
|
1539
|
+
? "✓ Use Agent Mail for coordination"
|
|
1540
|
+
: "Start Agent Mail: agent-mail serve",
|
|
1541
|
+
},
|
|
1542
|
+
report,
|
|
1543
|
+
},
|
|
1544
|
+
null,
|
|
1545
|
+
2,
|
|
1546
|
+
);
|
|
1547
|
+
},
|
|
1548
|
+
});
|
|
1549
|
+
|
|
1398
1550
|
// ============================================================================
|
|
1399
1551
|
// Export all tools
|
|
1400
1552
|
// ============================================================================
|
|
1401
1553
|
|
|
1402
1554
|
export const swarmTools = {
|
|
1555
|
+
swarm_init: swarm_init,
|
|
1403
1556
|
swarm_decompose: swarm_decompose,
|
|
1404
1557
|
swarm_validate_decomposition: swarm_validate_decomposition,
|
|
1405
1558
|
swarm_status: swarm_status,
|