@ra3orblade/swarm 0.5.0 → 0.6.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/dist/swarm-mcp.js +24 -1
- package/dist/swarm.js +93 -7
- package/dist/swarmd.js +919 -161
- package/package.json +1 -1
- package/web/app.js +282 -8
- package/web/icons.js +2 -2
- package/web/index.html +29 -0
- package/web/release-notes.js +2 -0
package/dist/swarm-mcp.js
CHANGED
|
@@ -19765,6 +19765,29 @@ function buildServer() {
|
|
|
19765
19765
|
resources
|
|
19766
19766
|
});
|
|
19767
19767
|
});
|
|
19768
|
+
server.registerTool("swarm_search", {
|
|
19769
|
+
title: "Search Swarm's memory",
|
|
19770
|
+
description: "Full-text search over what Swarm remembers about this repo: handoffs (what was done / what's left), incidents (commands the rules stopped, and why), gate runs (rubric + evidence) and what previous sessions last said. Not the codebase \u2014 grep that. Words are AND-ed, the last one is a prefix; quote a phrase; `kind:incident` / `task:M1.2` filter. Use it before redoing work someone may have done, or when a rule blocks you and you want to know how it was handled before.",
|
|
19771
|
+
inputSchema: {
|
|
19772
|
+
query: exports_external.string(),
|
|
19773
|
+
kind: exports_external.enum(["handoff", "incident", "gate", "session"]).optional(),
|
|
19774
|
+
all_projects: exports_external.boolean().optional().describe("search every project, not just this repo"),
|
|
19775
|
+
limit: exports_external.number().int().min(1).max(100).optional()
|
|
19776
|
+
}
|
|
19777
|
+
}, async ({ query, kind, all_projects, limit }) => {
|
|
19778
|
+
const q = new URLSearchParams({ q: query, limit: String(limit ?? 20) });
|
|
19779
|
+
if (!all_projects)
|
|
19780
|
+
q.set("project", await projectId());
|
|
19781
|
+
if (kind)
|
|
19782
|
+
q.set("kind", kind);
|
|
19783
|
+
const r = await api2(`/v1/memory?${q}`);
|
|
19784
|
+
if (!r.hits.length)
|
|
19785
|
+
return ok(`nothing in memory matches "${query}"`, { hits: [] });
|
|
19786
|
+
return ok(r.hits.map((h) => `[${h.kind}] ${h.title}${h.task ? ` (${h.task})` : ""} \u2014 ${h.ts.slice(0, 16)}
|
|
19787
|
+
${h.text}`).join(`
|
|
19788
|
+
|
|
19789
|
+
`), r.hits);
|
|
19790
|
+
});
|
|
19768
19791
|
server.registerTool("swarm_next_task", {
|
|
19769
19792
|
title: "Next claimable task",
|
|
19770
19793
|
description: "The first unclaimed task in this repo's task source whose dependencies are done \u2014 what to pick up next. Needs `[tasks] source` in .swarm.toml. Pass all=true to list every ready task.",
|
|
@@ -19815,7 +19838,7 @@ function buildServer() {
|
|
|
19815
19838
|
});
|
|
19816
19839
|
server.registerTool("swarm_resume", {
|
|
19817
19840
|
title: "Read the latest handoff",
|
|
19818
|
-
description: "The latest handoff on a task: done, remaining, files, verify. Read it before continuing someone else's work.",
|
|
19841
|
+
description: "The latest handoff on a task: done, remaining, files, verify. Read it before continuing someone else's work. Handoffs marked `auto:` were derived by Swarm from what the previous session did (files edited, last verify command) when it stopped without leaving one.",
|
|
19819
19842
|
inputSchema: { task: exports_external.string() }
|
|
19820
19843
|
}, async ({ task }) => {
|
|
19821
19844
|
const pid = await projectId();
|
package/dist/swarm.js
CHANGED
|
@@ -147,6 +147,8 @@ var HOOK_EVENTS = [
|
|
|
147
147
|
// packages/core/src/rules.ts
|
|
148
148
|
var LIVE_WINDOW_MS = 10 * 60000;
|
|
149
149
|
var WRITE_TOOLS = new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
|
|
150
|
+
// packages/core/src/ledger.ts
|
|
151
|
+
var EDIT_TOOLS = new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
150
152
|
// packages/cli/src/install.ts
|
|
151
153
|
var MARK = "swarm-hook";
|
|
152
154
|
var isOurs = (h) => h.command.includes(MARK) || h.command.includes("/packages/hook/src/bin.ts");
|
|
@@ -397,6 +399,7 @@ var help = `swarm \u2014 control plane for AI-agent development
|
|
|
397
399
|
run --task <id> (--prompt "\u2026" | --prompt-file f) [--model m] [--permission-mode m] [--allowed-tools a,b] [--max-turns n]
|
|
398
400
|
claim the task and spawn claude -p in its worktree; the session shows in Fleet
|
|
399
401
|
run ls | send <task|id> "text" | stop <task|id> steer (stdin) or stop a spawned run, by pid never pattern
|
|
402
|
+
run resume <session-id> [--model m] [--permission-mode m] spawn a run that picks up where a dead session stopped (its handoff + tail)
|
|
400
403
|
handoff <task> --done "\u2026" --remaining "\u2026" [--files a,b] [--verify "\u2026"] leave notes for the next holder
|
|
401
404
|
resume <task> print the latest handoff (the next session gets it automatically on start)
|
|
402
405
|
res ls | acquire <name> [--owner n] [--pid n] [--port n] | release <name> [--force]
|
|
@@ -406,6 +409,8 @@ var help = `swarm \u2014 control plane for AI-agent development
|
|
|
406
409
|
serve ls | stop [name] list / stop servers this project started (by pid, never by pattern)
|
|
407
410
|
proc start [--name n] -- <cmd> | ls | stop <name|pid> same, for workers without a port
|
|
408
411
|
stats [-p] [--json] all-time totals, streak, records (the dashboard's Stats view)
|
|
412
|
+
search <query\u2026> [-p] [--kind handoff|incident|gate|session] [--json] memory over Swarm's own data (handoffs, incidents, gates, what sessions said)
|
|
413
|
+
rules dryrun [--set rule=mode,\u2026] [--limit n] [--json] replay this repo's history under rule modes; shows what would fire + flaky signals
|
|
409
414
|
|
|
410
415
|
install | uninstall add/remove Swarm hooks in ~/.claude/settings.json
|
|
411
416
|
|
|
@@ -659,21 +664,24 @@ url: ${resolveBaseUrl()}`);
|
|
|
659
664
|
}
|
|
660
665
|
break;
|
|
661
666
|
}
|
|
662
|
-
const
|
|
663
|
-
|
|
667
|
+
const resumeFrom = sub === "resume" ? positionals[1] : undefined;
|
|
668
|
+
if (sub === "resume" && !resumeFrom)
|
|
669
|
+
throw new Error("usage: swarm run resume <session-id>");
|
|
670
|
+
const task = resumeFrom ? "(resumed)" : flag("--task") ?? sub;
|
|
671
|
+
let prompt = resumeFrom ? "(from handoff)" : flag("--prompt");
|
|
664
672
|
const pf = flag("--prompt-file");
|
|
665
673
|
if (!prompt && pf)
|
|
666
674
|
prompt = await Bun.file(resolve3(pf)).text();
|
|
667
675
|
if (!task || !prompt)
|
|
668
676
|
throw new Error('usage: swarm run --task <id> --prompt "\u2026" | --prompt-file f [--model] [--permission-mode] [--allowed-tools a,b] [--max-turns n]');
|
|
669
|
-
const r = await fetch(`${base}/v1/runs`, {
|
|
677
|
+
const r = await fetch(resumeFrom ? `${base}/v1/sessions/${encodeURIComponent(resumeFrom)}/resume` : `${base}/v1/runs`, {
|
|
670
678
|
method: "POST",
|
|
671
679
|
headers: { "content-type": "application/json" },
|
|
672
680
|
body: JSON.stringify({
|
|
673
681
|
projectId: proj.id,
|
|
674
682
|
task,
|
|
675
683
|
prompt,
|
|
676
|
-
owner: flag("--owner") ?? process.env.USER ?? "me",
|
|
684
|
+
owner: flag("--owner") ?? (resumeFrom ? undefined : process.env.USER ?? "me"),
|
|
677
685
|
model: flag("--model"),
|
|
678
686
|
permissionMode: flag("--permission-mode"),
|
|
679
687
|
allowedTools: flag("--allowed-tools")?.split(",").map((t) => t.trim()).filter(Boolean),
|
|
@@ -683,11 +691,11 @@ url: ${resolveBaseUrl()}`);
|
|
|
683
691
|
if (json)
|
|
684
692
|
console.log(JSON.stringify(r));
|
|
685
693
|
else if (r.ok && r.run)
|
|
686
|
-
console.log(`run ${r.run.id} on ${task} (pid ${r.run.pid})
|
|
694
|
+
console.log(`run ${r.run.id} on ${r.run.task} (pid ${r.run.pid})
|
|
687
695
|
worktree: ${r.run.worktree}
|
|
688
696
|
session: ${r.run.sessionId}
|
|
689
697
|
log: ${r.run.log}
|
|
690
|
-
steer: swarm run send ${task} "\u2026" stop: swarm run stop ${task} watch: swarm tail --session ${r.run.sessionId}`);
|
|
698
|
+
steer: swarm run send ${r.run.task} "\u2026" stop: swarm run stop ${r.run.task} watch: swarm tail --session ${r.run.sessionId}`);
|
|
691
699
|
else {
|
|
692
700
|
console.error(`REFUSED: ${r.error}`);
|
|
693
701
|
process.exit(1);
|
|
@@ -817,6 +825,82 @@ url: ${resolveBaseUrl()}`);
|
|
|
817
825
|
}
|
|
818
826
|
throw new Error("usage: swarm gate record|ls");
|
|
819
827
|
}
|
|
828
|
+
case "search": {
|
|
829
|
+
await ensureDaemon({ quiet: true });
|
|
830
|
+
const q = new URLSearchParams({ limit: "30" });
|
|
831
|
+
const words = [];
|
|
832
|
+
for (let i = 0;i < rest.length; i++) {
|
|
833
|
+
const a = rest[i];
|
|
834
|
+
if (a === "--kind")
|
|
835
|
+
q.set("kind", rest[++i] ?? "");
|
|
836
|
+
else if (a === "-p") {
|
|
837
|
+
const proj = await api("/v1/projects", {
|
|
838
|
+
method: "POST",
|
|
839
|
+
headers: { "content-type": "application/json" },
|
|
840
|
+
body: JSON.stringify({ path: resolve3(".") })
|
|
841
|
+
});
|
|
842
|
+
q.set("project", proj.id);
|
|
843
|
+
} else if (!a.startsWith("--"))
|
|
844
|
+
words.push(a);
|
|
845
|
+
}
|
|
846
|
+
if (!words.length)
|
|
847
|
+
throw new Error("usage: swarm search <query\u2026> [-p] [--kind k]");
|
|
848
|
+
q.set("q", words.join(" "));
|
|
849
|
+
const r = await api(`/v1/memory?${q}`);
|
|
850
|
+
if (json)
|
|
851
|
+
console.log(JSON.stringify(r.hits));
|
|
852
|
+
else if (!r.hits.length)
|
|
853
|
+
console.log("nothing in memory matches");
|
|
854
|
+
else
|
|
855
|
+
for (const h of r.hits)
|
|
856
|
+
console.log(`${h.kind.padEnd(8)} ${h.ts.slice(0, 16).replace("T", " ")} ${h.title}${h.task ? ` [${h.task}]` : ""}
|
|
857
|
+
${h.snippet.split("\x01").join("").split("\x02").join("").replace(/\s+/g, " ")}${h.sessionId ? `
|
|
858
|
+
session ${h.sessionId}` : ""}`);
|
|
859
|
+
break;
|
|
860
|
+
}
|
|
861
|
+
case "rules": {
|
|
862
|
+
if (rest[0] !== "dryrun")
|
|
863
|
+
throw new Error("usage: swarm rules dryrun [--set rule=mode,\u2026] [--limit n]");
|
|
864
|
+
await ensureDaemon({ quiet: true });
|
|
865
|
+
const proj = await api("/v1/projects", {
|
|
866
|
+
method: "POST",
|
|
867
|
+
headers: { "content-type": "application/json" },
|
|
868
|
+
body: JSON.stringify({ path: resolve3(".") })
|
|
869
|
+
});
|
|
870
|
+
const q = new URLSearchParams({ project: proj.id });
|
|
871
|
+
const si = rest.indexOf("--set");
|
|
872
|
+
if (si >= 0)
|
|
873
|
+
for (const kv of (rest[si + 1] ?? "").split(",")) {
|
|
874
|
+
const [k, v] = kv.split("=");
|
|
875
|
+
if (k && v)
|
|
876
|
+
q.set(k.trim(), v.trim());
|
|
877
|
+
}
|
|
878
|
+
const li = rest.indexOf("--limit");
|
|
879
|
+
if (li >= 0)
|
|
880
|
+
q.set("limit", rest[li + 1] ?? "");
|
|
881
|
+
const r = await api(`/v1/rules/dryrun?${q}`);
|
|
882
|
+
if (json) {
|
|
883
|
+
console.log(JSON.stringify(r));
|
|
884
|
+
break;
|
|
885
|
+
}
|
|
886
|
+
console.log(`dry-run over ${r.evaluated} of ${r.calls} recorded calls (nothing recorded)`);
|
|
887
|
+
for (const [rule, n] of Object.entries(r.byRule))
|
|
888
|
+
console.log(` ${rule.padEnd(26)} ${String(r.modes[rule]).padEnd(5)} ask ${String(n.ask).padStart(4)} deny ${String(n.deny).padStart(4)}`);
|
|
889
|
+
if (r.flaky.length) {
|
|
890
|
+
console.log(`
|
|
891
|
+
flaky signals:`);
|
|
892
|
+
for (const f of r.flaky)
|
|
893
|
+
console.log(` ${f.display}
|
|
894
|
+
${f.suggestion}`);
|
|
895
|
+
}
|
|
896
|
+
if (r.hits.length) {
|
|
897
|
+
console.log(`
|
|
898
|
+
would have fired (newest last):`);
|
|
899
|
+
for (const h of r.hits.slice(-20))
|
|
900
|
+
console.log(` ${h.ts.slice(11, 19)} ${h.action.padEnd(4)} ${h.rule.padEnd(20)} ${h.display}${h.completed ? " (ran)" : ""}`);
|
|
901
|
+
}
|
|
902
|
+
break;
|
|
903
|
+
}
|
|
820
904
|
case "tasks": {
|
|
821
905
|
await ensureDaemon({ quiet: true });
|
|
822
906
|
const proj = await api("/v1/projects", {
|
|
@@ -830,7 +914,9 @@ url: ${resolveBaseUrl()}`);
|
|
|
830
914
|
if (json)
|
|
831
915
|
console.log(JSON.stringify(rows));
|
|
832
916
|
else if (!t.source)
|
|
833
|
-
console.log('no task source \u2014 add `[tasks] source = "path/to/plan.md"` to .swarm.toml');
|
|
917
|
+
console.log('no task source \u2014 add `[tasks] source = "path/to/plan.md"` (or "github" / "linear") to .swarm.toml');
|
|
918
|
+
else if (t.error && !t.tasks.length)
|
|
919
|
+
console.log(`${t.source}: ${t.error}`);
|
|
834
920
|
else if (!rows.length)
|
|
835
921
|
console.log(ready ? "nothing ready to claim" : `no tasks in ${t.source}`);
|
|
836
922
|
else
|