@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/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");