@ra3orblade/swarm 0.14.0 → 0.15.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/README.md +15 -12
- package/dist/swarm-hook.js +660 -201
- package/dist/swarm.js +181 -37
- package/dist/swarmd.js +3173 -1435
- package/package.json +1 -1
- package/web/dashboard.css +1 -1
- package/web/dashboard.js +23 -19
- package/web/icons.js +2 -2
- package/web/release-notes.js +1 -1
package/dist/swarm.js
CHANGED
|
@@ -196,10 +196,13 @@ var init_adapters = __esm(() => {
|
|
|
196
196
|
init_updates();
|
|
197
197
|
init_db();
|
|
198
198
|
});
|
|
199
|
+
// packages/core/src/prompt.ts
|
|
200
|
+
var init_prompt = () => {};
|
|
199
201
|
|
|
200
202
|
// packages/core/src/adapters/claude-code/hooks.ts
|
|
201
|
-
var HOOK_EVENTS;
|
|
203
|
+
var HOOK_EVENTS, OWN_SUMMARY;
|
|
202
204
|
var init_hooks = __esm(() => {
|
|
205
|
+
init_prompt();
|
|
203
206
|
HOOK_EVENTS = [
|
|
204
207
|
"SessionStart",
|
|
205
208
|
"UserPromptSubmit",
|
|
@@ -211,8 +214,71 @@ var init_hooks = __esm(() => {
|
|
|
211
214
|
"SessionEnd",
|
|
212
215
|
"Notification",
|
|
213
216
|
"PreCompact",
|
|
214
|
-
"PermissionRequest"
|
|
217
|
+
"PermissionRequest",
|
|
218
|
+
"PostToolUseFailure"
|
|
215
219
|
];
|
|
220
|
+
OWN_SUMMARY = new Set([
|
|
221
|
+
"Bash",
|
|
222
|
+
"Read",
|
|
223
|
+
"Edit",
|
|
224
|
+
"Write",
|
|
225
|
+
"MultiEdit",
|
|
226
|
+
"NotebookEdit",
|
|
227
|
+
"Glob",
|
|
228
|
+
"Grep",
|
|
229
|
+
"Agent",
|
|
230
|
+
"Task",
|
|
231
|
+
"WebFetch",
|
|
232
|
+
"WebSearch",
|
|
233
|
+
"AskUserQuestion"
|
|
234
|
+
]);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// packages/core/src/guards.ts
|
|
238
|
+
var FETCHER, INTERP;
|
|
239
|
+
var init_guards = __esm(() => {
|
|
240
|
+
FETCHER = String.raw`\b(curl|wget|fetch|http|xh)\b`;
|
|
241
|
+
INTERP = String.raw`(sudo\s+(-\S+\s+)*)?(env\s+(\w+=\S+\s+)*)?(ba|z|k|da|fi)?sh|python[0-9.]*|node|perl|ruby|php|iex`;
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// packages/core/src/rules.ts
|
|
245
|
+
var LIVE_WINDOW_MS, customRe, WRITE_TOOLS;
|
|
246
|
+
var init_rules = __esm(() => {
|
|
247
|
+
init_guards();
|
|
248
|
+
LIVE_WINDOW_MS = 10 * 60000;
|
|
249
|
+
customRe = new WeakMap;
|
|
250
|
+
WRITE_TOOLS = new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// packages/core/src/policy.ts
|
|
254
|
+
function hookCoverage(settings) {
|
|
255
|
+
const hooks = settings && typeof settings === "object" && !Array.isArray(settings) ? settings.hooks : undefined;
|
|
256
|
+
const missing = [];
|
|
257
|
+
const short = [];
|
|
258
|
+
for (const ev of HOOK_EVENTS) {
|
|
259
|
+
const groups = Array.isArray(hooks?.[ev]) ? hooks?.[ev] : [];
|
|
260
|
+
const ours = groups.flatMap((g) => {
|
|
261
|
+
const list = g?.hooks;
|
|
262
|
+
return Array.isArray(list) ? list.filter((h) => hookIsOurs(h)) : [];
|
|
263
|
+
});
|
|
264
|
+
if (!ours.length)
|
|
265
|
+
missing.push(ev);
|
|
266
|
+
else if (ours.every((h) => typeof h.timeout === "number" && h.timeout < MIN_HOOK_TIMEOUT_S))
|
|
267
|
+
short.push(ev);
|
|
268
|
+
}
|
|
269
|
+
return { missing, short, complete: !missing.length && !short.length };
|
|
270
|
+
}
|
|
271
|
+
var HOOK_MARK = "swarm-hook", hookIsOurs = (h) => typeof h.command === "string" && (h.command.includes(HOOK_MARK) || h.command.includes("/packages/hook/src/bin.ts")), MIN_HOOK_TIMEOUT_S = 5;
|
|
272
|
+
var init_policy = __esm(() => {
|
|
273
|
+
init_hooks();
|
|
274
|
+
init_rules();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// packages/core/src/agenthooks.ts
|
|
278
|
+
var CAN_ASK;
|
|
279
|
+
var init_agenthooks = __esm(() => {
|
|
280
|
+
init_policy();
|
|
281
|
+
CAN_ASK = new Set(["claude-code"]);
|
|
216
282
|
});
|
|
217
283
|
|
|
218
284
|
// packages/core/src/art.ts
|
|
@@ -562,6 +628,19 @@ function validate(c) {
|
|
|
562
628
|
return typeof w === "string" && /^https?:\/\//.test(w.trim()) ? w.trim() : null;
|
|
563
629
|
})()
|
|
564
630
|
},
|
|
631
|
+
otel: (() => {
|
|
632
|
+
const o = c.otel ?? {};
|
|
633
|
+
const e = typeof o.endpoint === "string" ? o.endpoint.trim().replace(/\/+$/, "") : "";
|
|
634
|
+
const h = o.headers && typeof o.headers === "object" ? o.headers : {};
|
|
635
|
+
const n = Number(o.interval);
|
|
636
|
+
return {
|
|
637
|
+
endpoint: /^https?:\/\//.test(e) ? e : null,
|
|
638
|
+
headers: Object.fromEntries(Object.entries(h).filter((kv) => typeof kv[1] === "string")),
|
|
639
|
+
compat: o.compat === "claude-code" ? "claude-code" : "genai",
|
|
640
|
+
include_content: o.include_content === true,
|
|
641
|
+
interval: Number.isFinite(n) && n >= 5 && n <= 3600 ? Math.round(n) : 30
|
|
642
|
+
};
|
|
643
|
+
})(),
|
|
565
644
|
messages: {
|
|
566
645
|
wake: c.messages?.wake !== false
|
|
567
646
|
},
|
|
@@ -628,6 +707,11 @@ function validate(c) {
|
|
|
628
707
|
no_verify: rewriteMode(c.rules?.no_verify, "off"),
|
|
629
708
|
dry_run_first: rewriteMode(c.rules?.dry_run_first, "off"),
|
|
630
709
|
custom: parseCustomRules(c.rules?.custom),
|
|
710
|
+
destructive_fs: mode(c.rules?.destructive_fs, "off"),
|
|
711
|
+
destructive_infra: mode(c.rules?.destructive_infra, "off"),
|
|
712
|
+
pipe_to_shell: mode(c.rules?.pipe_to_shell, "off"),
|
|
713
|
+
secrets: mode(c.rules?.secrets, "off"),
|
|
714
|
+
config_tamper: mode(c.rules?.config_tamper, "off"),
|
|
631
715
|
collision_context: c.rules?.collision_context !== false,
|
|
632
716
|
collision_window: (() => {
|
|
633
717
|
const n = Number(c.rules?.collision_window);
|
|
@@ -737,6 +821,7 @@ var init_config = __esm(() => {
|
|
|
737
821
|
budget: { daily: null, weekly: null, warn_at: 0.8, on_exceed: "warn", window_warn_at: 0.8 },
|
|
738
822
|
models: { allow: [] },
|
|
739
823
|
notify: { webhook: null },
|
|
824
|
+
otel: { endpoint: null, headers: {}, compat: "genai", include_content: false, interval: 30 },
|
|
740
825
|
messages: { wake: true },
|
|
741
826
|
codify: { target: "both" },
|
|
742
827
|
broker: { interactive_wait: 30 },
|
|
@@ -763,6 +848,11 @@ var init_config = __esm(() => {
|
|
|
763
848
|
no_verify: "off",
|
|
764
849
|
dry_run_first: "off",
|
|
765
850
|
custom: [],
|
|
851
|
+
destructive_fs: "off",
|
|
852
|
+
destructive_infra: "off",
|
|
853
|
+
pipe_to_shell: "off",
|
|
854
|
+
secrets: "off",
|
|
855
|
+
config_tamper: "off",
|
|
766
856
|
collision_context: true,
|
|
767
857
|
collision_window: 15,
|
|
768
858
|
protected: { ports: [] }
|
|
@@ -777,14 +867,6 @@ var init_context = () => {};
|
|
|
777
867
|
|
|
778
868
|
// packages/core/src/dag.ts
|
|
779
869
|
var init_dag = () => {};
|
|
780
|
-
// packages/core/src/rules.ts
|
|
781
|
-
var LIVE_WINDOW_MS, customRe, WRITE_TOOLS;
|
|
782
|
-
var init_rules = __esm(() => {
|
|
783
|
-
LIVE_WINDOW_MS = 10 * 60000;
|
|
784
|
-
customRe = new WeakMap;
|
|
785
|
-
WRITE_TOOLS = new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
|
|
786
|
-
});
|
|
787
|
-
|
|
788
870
|
// packages/core/src/dryrun.ts
|
|
789
871
|
var init_dryrun = __esm(() => {
|
|
790
872
|
init_rules();
|
|
@@ -825,6 +907,7 @@ var init_hygiene = __esm(() => {
|
|
|
825
907
|
// packages/core/src/ledger.ts
|
|
826
908
|
var EDIT_TOOLS;
|
|
827
909
|
var init_ledger = __esm(() => {
|
|
910
|
+
init_prompt();
|
|
828
911
|
EDIT_TOOLS = new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
829
912
|
});
|
|
830
913
|
// packages/core/src/lessons-apply.ts
|
|
@@ -839,49 +922,31 @@ var init_lineage = __esm(() => {
|
|
|
839
922
|
var init_mcphealth = __esm(() => {
|
|
840
923
|
init_gatehealth();
|
|
841
924
|
});
|
|
842
|
-
|
|
843
925
|
// packages/core/src/memory.ts
|
|
844
926
|
var init_memory = () => {};
|
|
927
|
+
// packages/core/src/otel.ts
|
|
928
|
+
var init_otel = () => {};
|
|
929
|
+
|
|
845
930
|
// packages/core/src/outcomes.ts
|
|
846
931
|
var DEFAULT_BRANCHES;
|
|
847
932
|
var init_outcomes = __esm(() => {
|
|
848
933
|
DEFAULT_BRANCHES = new Set(["main", "master", "develop", "trunk"]);
|
|
849
934
|
});
|
|
850
|
-
// packages/core/src/policy.ts
|
|
851
|
-
function hookCoverage(settings) {
|
|
852
|
-
const hooks = settings && typeof settings === "object" && !Array.isArray(settings) ? settings.hooks : undefined;
|
|
853
|
-
const missing = [];
|
|
854
|
-
const short = [];
|
|
855
|
-
for (const ev of HOOK_EVENTS) {
|
|
856
|
-
const groups = Array.isArray(hooks?.[ev]) ? hooks?.[ev] : [];
|
|
857
|
-
const ours = groups.flatMap((g) => {
|
|
858
|
-
const list = g?.hooks;
|
|
859
|
-
return Array.isArray(list) ? list.filter((h) => hookIsOurs(h)) : [];
|
|
860
|
-
});
|
|
861
|
-
if (!ours.length)
|
|
862
|
-
missing.push(ev);
|
|
863
|
-
else if (ours.every((h) => typeof h.timeout === "number" && h.timeout < MIN_HOOK_TIMEOUT_S))
|
|
864
|
-
short.push(ev);
|
|
865
|
-
}
|
|
866
|
-
return { missing, short, complete: !missing.length && !short.length };
|
|
867
|
-
}
|
|
868
|
-
var HOOK_MARK = "swarm-hook", hookIsOurs = (h) => typeof h.command === "string" && (h.command.includes(HOOK_MARK) || h.command.includes("/packages/hook/src/bin.ts")), MIN_HOOK_TIMEOUT_S = 5;
|
|
869
|
-
var init_policy = __esm(() => {
|
|
870
|
-
init_hooks();
|
|
871
|
-
init_rules();
|
|
872
|
-
});
|
|
873
935
|
|
|
874
936
|
// packages/core/src/pricing.ts
|
|
875
937
|
var init_pricing = () => {};
|
|
876
938
|
// packages/core/src/provenance.ts
|
|
877
939
|
var init_provenance = () => {};
|
|
878
940
|
// packages/core/src/quota.ts
|
|
879
|
-
var BURN_MIN_SPAN_MS;
|
|
941
|
+
var BURN_MIN_SPAN_MS, BURN_LOOKBACK_MS;
|
|
880
942
|
var init_quota = __esm(() => {
|
|
881
943
|
BURN_MIN_SPAN_MS = 10 * 60000;
|
|
944
|
+
BURN_LOOKBACK_MS = 60 * 60000;
|
|
882
945
|
});
|
|
883
946
|
// packages/core/src/security.ts
|
|
884
|
-
var init_security = () => {
|
|
947
|
+
var init_security = __esm(() => {
|
|
948
|
+
init_rules();
|
|
949
|
+
});
|
|
885
950
|
|
|
886
951
|
// packages/core/src/stall.ts
|
|
887
952
|
var init_stall = () => {};
|
|
@@ -986,6 +1051,7 @@ var init_src2 = __esm(() => {
|
|
|
986
1051
|
init_actor();
|
|
987
1052
|
init_adapters();
|
|
988
1053
|
init_hooks();
|
|
1054
|
+
init_agenthooks();
|
|
989
1055
|
init_art();
|
|
990
1056
|
init_audit();
|
|
991
1057
|
init_budget();
|
|
@@ -1003,9 +1069,11 @@ var init_src2 = __esm(() => {
|
|
|
1003
1069
|
init_lineage();
|
|
1004
1070
|
init_mcphealth();
|
|
1005
1071
|
init_memory();
|
|
1072
|
+
init_otel();
|
|
1006
1073
|
init_outcomes();
|
|
1007
1074
|
init_policy();
|
|
1008
1075
|
init_pricing();
|
|
1076
|
+
init_prompt();
|
|
1009
1077
|
init_provenance();
|
|
1010
1078
|
init_quota();
|
|
1011
1079
|
init_rules();
|
|
@@ -1202,12 +1270,79 @@ function unregisterGemini() {
|
|
|
1202
1270
|
return false;
|
|
1203
1271
|
}
|
|
1204
1272
|
}
|
|
1273
|
+
var codexHooksPath = () => process.env.CODEX_HOOKS ?? join3(codexConfigPath(), "..", "hooks.json");
|
|
1274
|
+
var oursIn = (g) => g.hooks.some((h) => typeof h.command === "string" && isOurs(h));
|
|
1275
|
+
function readShared(p) {
|
|
1276
|
+
try {
|
|
1277
|
+
return existsSync4(p) ? JSON.parse(readFileSync3(p, "utf8")) : {};
|
|
1278
|
+
} catch {
|
|
1279
|
+
return null;
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
function putHook(p, event, group) {
|
|
1283
|
+
const c = readShared(p);
|
|
1284
|
+
if (!c)
|
|
1285
|
+
return false;
|
|
1286
|
+
const hooks = c.hooks ?? {};
|
|
1287
|
+
const kept = (hooks[event] ?? []).filter((g) => !oursIn(g));
|
|
1288
|
+
const had = kept.length !== (hooks[event] ?? []).length;
|
|
1289
|
+
if (group)
|
|
1290
|
+
kept.push(group);
|
|
1291
|
+
else if (!had)
|
|
1292
|
+
return false;
|
|
1293
|
+
if (kept.length)
|
|
1294
|
+
hooks[event] = kept;
|
|
1295
|
+
else
|
|
1296
|
+
delete hooks[event];
|
|
1297
|
+
if (Object.keys(hooks).length)
|
|
1298
|
+
c.hooks = hooks;
|
|
1299
|
+
else
|
|
1300
|
+
delete c.hooks;
|
|
1301
|
+
writeFileSync2(p, `${JSON.stringify(c, null, 2)}
|
|
1302
|
+
`);
|
|
1303
|
+
return true;
|
|
1304
|
+
}
|
|
1305
|
+
function guardCodex(on) {
|
|
1306
|
+
const p = codexHooksPath();
|
|
1307
|
+
if (!existsSync4(join3(p, "..")))
|
|
1308
|
+
return false;
|
|
1309
|
+
return putHook(p, "PreToolUse", on ? {
|
|
1310
|
+
matcher: "Bash|apply_patch",
|
|
1311
|
+
hooks: [{ type: "command", command: hookCommand("PreToolUse"), timeout: 5 }]
|
|
1312
|
+
} : null);
|
|
1313
|
+
}
|
|
1314
|
+
function guardGemini(on) {
|
|
1315
|
+
const p = geminiSettingsPath();
|
|
1316
|
+
if (!existsSync4(join3(p, "..")))
|
|
1317
|
+
return false;
|
|
1318
|
+
return putHook(p, "BeforeTool", on ? {
|
|
1319
|
+
matcher: "run_shell_command|write_file|replace|read_file",
|
|
1320
|
+
hooks: [
|
|
1321
|
+
{ name: "swarm", type: "command", command: hookCommand("BeforeTool"), timeout: 5000 }
|
|
1322
|
+
]
|
|
1323
|
+
} : null);
|
|
1324
|
+
}
|
|
1325
|
+
function guardedAgents() {
|
|
1326
|
+
const out = [];
|
|
1327
|
+
const has = (p, event) => {
|
|
1328
|
+
const c = readShared(p);
|
|
1329
|
+
const groups = c?.hooks?.[event] ?? [];
|
|
1330
|
+
return groups.some(oursIn);
|
|
1331
|
+
};
|
|
1332
|
+
if (existsSync4(codexHooksPath()) && has(codexHooksPath(), "PreToolUse"))
|
|
1333
|
+
out.push("codex");
|
|
1334
|
+
if (existsSync4(geminiSettingsPath()) && has(geminiSettingsPath(), "BeforeTool"))
|
|
1335
|
+
out.push("gemini");
|
|
1336
|
+
return out;
|
|
1337
|
+
}
|
|
1205
1338
|
function registerOtherAgents() {
|
|
1206
1339
|
const out = [];
|
|
1207
1340
|
if (registerCodex())
|
|
1208
1341
|
out.push("codex");
|
|
1209
1342
|
if (registerGemini())
|
|
1210
1343
|
out.push("gemini");
|
|
1344
|
+
guardCodex(true);
|
|
1345
|
+
guardGemini(true);
|
|
1211
1346
|
return out;
|
|
1212
1347
|
}
|
|
1213
1348
|
function unregisterOtherAgents() {
|
|
@@ -1216,6 +1351,8 @@ function unregisterOtherAgents() {
|
|
|
1216
1351
|
out.push("codex");
|
|
1217
1352
|
if (unregisterGemini())
|
|
1218
1353
|
out.push("gemini");
|
|
1354
|
+
guardCodex(false);
|
|
1355
|
+
guardGemini(false);
|
|
1219
1356
|
return out;
|
|
1220
1357
|
}
|
|
1221
1358
|
var hookCommand = (event) => `${binCommand("swarm-hook")} ${event}`;
|
|
@@ -1343,7 +1480,8 @@ function status() {
|
|
|
1343
1480
|
path: settingsPath(),
|
|
1344
1481
|
shim: shimPath(),
|
|
1345
1482
|
otherAgents,
|
|
1346
|
-
statusline: statuslineStatus(s)
|
|
1483
|
+
statusline: statuslineStatus(s),
|
|
1484
|
+
guarded: guardedAgents()
|
|
1347
1485
|
};
|
|
1348
1486
|
}
|
|
1349
1487
|
function setTeamUrl(home, url) {
|
|
@@ -1649,6 +1787,9 @@ Open the dashboard: ${base}`);
|
|
|
1649
1787
|
console.log(st.statusline === "other" ? "\xB7 statusLine: another status line is set (swarm install --statusline would leave it alone)" : "\xB7 statusLine not set \u2014 swarm install --statusline puts task, lease, budget and plan windows in Claude Code's footer");
|
|
1650
1788
|
if (st.otherAgents.length)
|
|
1651
1789
|
console.log(`\u2713 MCP server also registered for ${st.otherAgents.join(", ")} (swarm_* tools in those CLIs too)`);
|
|
1790
|
+
if (st.guarded.length)
|
|
1791
|
+
console.log(`\u2713 rules also hold on ${st.guarded.join(", ")}${st.guarded.includes("codex") ? " (Codex runs a new hook only after you trust it once: /hooks)" : ""}`);
|
|
1792
|
+
console.log("\xB7 Cursor runs the Claude Code hooks above itself (Settings \u2192 Agents \u2192 Third-Party Imports, on by default); an `ask` rule refuses there, since Cursor's preToolUse cannot ask");
|
|
1652
1793
|
const forge = (bin, auth) => {
|
|
1653
1794
|
const path = Bun.which(bin);
|
|
1654
1795
|
if (!path)
|
|
@@ -1664,6 +1805,9 @@ Open the dashboard: ${base}`);
|
|
|
1664
1805
|
const lag = t.pending ? `${t.pending} pending${t.oldest ? ` since ${t.oldest}` : ""}` : "in sync";
|
|
1665
1806
|
line(!t.lastError, `team forwarding \u2192 ${t.url} (${lag})`, `last error: ${t.lastError} \u2014 check the team daemon and [team].url`);
|
|
1666
1807
|
}
|
|
1808
|
+
const o = await api("/v1/otel").catch(() => null);
|
|
1809
|
+
if (o?.configured)
|
|
1810
|
+
line(!o.lastError, `OTLP export \u2192 ${o.endpoint} (${o.compat}${o.lastOkAt ? `, last sent ${o.lastOkAt}` : ", nothing sent yet"})`, `last error: ${o.lastError} \u2014 check the collector and [otel].endpoint`);
|
|
1667
1811
|
}
|
|
1668
1812
|
if (process.env.GITLAB_TOKEN)
|
|
1669
1813
|
console.log("\xB7 glab uses GITLAB_TOKEN from this shell \u2014 a daemon started by the desktop app won't see it; run `glab auth login` to store it instead");
|