@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-hook.js
CHANGED
|
@@ -100,168 +100,268 @@ var init_adapters = __esm(() => {
|
|
|
100
100
|
init_updates();
|
|
101
101
|
init_db();
|
|
102
102
|
});
|
|
103
|
+
// packages/core/src/prompt.ts
|
|
104
|
+
var init_prompt = () => {};
|
|
103
105
|
|
|
104
106
|
// packages/core/src/adapters/claude-code/hooks.ts
|
|
105
|
-
var
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
"
|
|
112
|
-
"
|
|
113
|
-
"
|
|
114
|
-
"
|
|
115
|
-
"
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
"
|
|
119
|
-
"
|
|
120
|
-
"
|
|
121
|
-
"
|
|
122
|
-
" SSDOOOOOOOOOOOOOOOOOOOOOOOOOOOOOKSS ",
|
|
123
|
-
" SSOMMMMMMMMMMMMMMMMMMMMMMMMMMMMMOSS ",
|
|
124
|
-
" DDKMMLLLMLLMMMMMMMMMMMMMMMMMMMMMODD ",
|
|
125
|
-
" DDOMMLMMMMMMMMMMMMMMMMMMMMMMMMMMODD ",
|
|
126
|
-
" SSSSODDOMMLMMMMMMMMMMMMMMMMMMMMMMMMMMODDOSSSS ",
|
|
127
|
-
" SSSSODDOMMMMMMMMMMMMMMMMMMMMMMMMMMMMMODDOSSSS ",
|
|
128
|
-
" SMMMMODDOMMLMMMMMMMMMMMMMMMMMMMMMMMMMMODDOSMMMS ",
|
|
129
|
-
" DMSSSODDOMMMMOOOOOOMMMMMMMMMOOOOOOMMMMODDOSSSED ",
|
|
130
|
-
" DSSSSODDOMMMMODDDDDMMMMMMMMMODDDDDMMMMODDOSSSSK ",
|
|
131
|
-
" DSMMSODDOMMMMDDDDDDMMMMMMMMMKDDDDDMMMMODDOSMMSK ",
|
|
132
|
-
" DSSSSODDOMMMMDDDDDDMMMMMMMMMDDDDDDMMMMODDOSSSSK ",
|
|
133
|
-
" DSSSSODDOMMMMDDDDDDMMMMMMMMMDDDDDDMMMMODDODSSSK ",
|
|
134
|
-
" DSSSSODDOMMMMDDDDDDMMMMMMMMMDDDDDDMMMMODDODSSSK ",
|
|
135
|
-
" DSSSKODDOMMMMDDDDDDMMMMMMMMMDDDDDDMMMMODDODSSSD ",
|
|
136
|
-
" OKKKKODDOMMMMMMMMMMMMMMMMMMMMMMMMMMMMMODDOKKKKO ",
|
|
137
|
-
" KKKKODDOMMMMMMMMMMMMMMMMMMMMMMMMMMMMMODDOOKKK ",
|
|
138
|
-
" KKKOODDOMMMMMMMMMMMMMMMMMMMMMMMMMMMMMODDOOKKK ",
|
|
139
|
-
" DDOMMMMDDDDDDDDDDDDDDDDDDDDDMMMMODD ",
|
|
140
|
-
" DDOMMMMDDDDDDDDDDDDDDDDDDDDDMMMMODD ",
|
|
141
|
-
" DDOMMMMDDDDDDDDDDDDDDDDDDDDDMMMMODD ",
|
|
142
|
-
" DDOMMMMMMMMMMMMMMMMMMMMMMMMMMMMMODD ",
|
|
143
|
-
" DDOMMMMMMMMMMMMMMMMMMMMMMMMMMMMMODD ",
|
|
144
|
-
" DDOMMMMMMMMMMMMMMMMMMMMMMMMMMMMMODD ",
|
|
145
|
-
" DDDOOOOOOOOOOOOOOOOOOOOOOOOOOOOOKDD ",
|
|
146
|
-
" DDDDDDDDDDDDDDDDDDDDDDDDDDDDDKDDDDD ",
|
|
147
|
-
" DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD ",
|
|
148
|
-
" OOOOOOOOOOO ",
|
|
149
|
-
" OODDDDDKKOO ",
|
|
150
|
-
" DSMMMMMMSDD ",
|
|
151
|
-
" OOOOOOOOOOO ",
|
|
152
|
-
" KDSMMMMMESSKK ",
|
|
153
|
-
" MMMMMMSSKOOKDSMMMMMSSSDKOODSEMMMMMM ",
|
|
154
|
-
" SSSSDO MMMMMMSSKKOOOOOOOOOOOOOOOOOKSSSMMMMMM ODSSSS ",
|
|
155
|
-
" EEESSSSOOLLLLMMMMMESSSSSSSSSSSSSSSSSSSSMMMMMLLLLOOSSSSMME ",
|
|
156
|
-
" SMMSSSSKOEELLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLEEOOSSSSEMS ",
|
|
157
|
-
" DSSSSSSSOOSEEKSMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMSOEEDOOSSSSSSSK ",
|
|
158
|
-
" DSSSSSSDOOSEOSKEMMMMMMMMMMMMMMMMMMMMMMMMMMMMMEOSDEDOODSSSSSSK ",
|
|
159
|
-
" KSSSSSSKOODSSOSEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEESOEEDOOKSSSSSSK ",
|
|
160
|
-
" DKSSSSKKOODSMEEMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMSEMSDOOKKSSSSDK ",
|
|
161
|
-
" OKDDKDKKOOKSMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMSKOOKKKKKKKO ",
|
|
162
|
-
" OOOOOKKKOOKSMMMMESSSSSSSSSSSSSMMMMEEEEEESSSSEMMMMSKOOKKKOOOOO ",
|
|
163
|
-
" OOOOOOKKOOKSMMMSSKKKKKKKKKKKKKKMMMDKOOOOOOOODDMMMSKOOKKOOOOOO ",
|
|
164
|
-
" OKSSKOOOOOKSMMMKKMLLLLLLLLLLLMKMMMDDSSSSSSSSDDMMMSKOOOOODSSKO ",
|
|
165
|
-
" SDOKKK KSMMMKDLLLLLLLLLLLLLKMMMKDKKKDDKKKDKMMMSK KKKKDS ",
|
|
166
|
-
" DMLLMKO KSMMMKDLLLLLLLLLLLLLKMMMDDDDDDDDDSKDMMMSK ODMLLMD ",
|
|
167
|
-
" DELLMSD KSMMMKKMLLLLLLLLLLLLKMMMDKKKKKKKKKDDMMMSK DSMLLEO ",
|
|
168
|
-
" KSKKKKDO KSMMMEDDDDKKDDDKKKDKSMMMDDDDDDDDDSDDMMMSK ODDKKKSK ",
|
|
169
|
-
" EMLLMDO KSMMMMLLLLLLLLLLLLLLLMMMDOOOOOOOOODDMMMSK ODMLLME ",
|
|
170
|
-
" KMLLMSD KSMMMMMMMMMMMMMMMMMMMMMMDDSSDDSSSSDDMMMSK SSMLLEK ",
|
|
171
|
-
" KSKKKKSO KSMMMMMOOMMOOMKOEMOOMMMMDOOOOOOOOODDMMMSK OSKKKKSO ",
|
|
172
|
-
" SMLLMDO KSMMMMMKKMMKKMDKEMKKMMMMKDDDDSSSSDDDMMMSK ODMLLMS ",
|
|
173
|
-
" SMMMMSD KSMMMMMEEMMEEMEEMMEEMMMMDSOOOOOOOODDMMSSK DSMMMMD ",
|
|
174
|
-
" OOOOOOO KSSMMMMMMMMMMMMMMMMMMMMMDDDDDDDDDDDDMSSSK OOOOOOO ",
|
|
175
|
-
" KSMLLLMMSKO KSSSSMMMMMMMMMMMMMMMMMMMSOOOOOOOOOOSSSSSK OKSMLLLLMSO ",
|
|
176
|
-
" DSMMMEEESKK KSSKSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSOSSK KDSMMMMMESD ",
|
|
177
|
-
" DSMMMMMESKK KSOOKSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSOOKSK KKSMMMMMMSD ",
|
|
178
|
-
" DSMMMMMESKK OKSKSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSDSKO KDSMMMMMMSD ",
|
|
179
|
-
" KDDDDDDDDKK KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK KKDDDDDDDDO ",
|
|
180
|
-
" OOOOOOOOOOO OOOOOOOOO OOOOOOOOO OOOOOOOOOOO ",
|
|
181
|
-
"DSDO OSSO ODKKKKKKK OKKKKKKKO KEDO ODEK",
|
|
182
|
-
"SMDO OSMD KDSSSSSSK KDSSSSSKO SMDO ODMS",
|
|
183
|
-
"DEKO OOED OOKKKOOO OOOKKOOO SSOO OKMS",
|
|
184
|
-
"DEKO OOED SEMMMMSK OSMMMMED SSKO OKES",
|
|
185
|
-
"DSSO OESK SEMMMMSK OSMMMMED KSSO OSSK",
|
|
186
|
-
"KSMS EEDO OOKKKKKO OOKKKKKO OSEE EMSO",
|
|
187
|
-
" DSS SSK OSEEESDO ODSEEESO KSS SSD ",
|
|
188
|
-
" OOO OO SEMMMMSK OSMMMMED OO OOO ",
|
|
189
|
-
" KSSSSSDO OKSSESSK ",
|
|
190
|
-
" DKKDDDDDDKKDK KDKDDDDDDDKDK ",
|
|
191
|
-
" SSMLLMMMMMMLMSK DSSMMMMMMMLLMSS ",
|
|
192
|
-
" SMLMMMMMMMMLLED SEMMMMMMMMMMLMS ",
|
|
193
|
-
" DMMMMMMMMMMMMMMD SMMMMMMMMMMMMMSK ",
|
|
194
|
-
" ODSSSSSSSSSSSSSSDO ODSSSSSSSSSSSSSSKO ",
|
|
195
|
-
" OOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOO ",
|
|
196
|
-
" OKSSSSSSSSSSSSSSK KSSSSSSSSSSSSSSKO ",
|
|
197
|
-
" OKDDDDDDDDDDDDDDK KDDDDDDDDDDDDDDOO "
|
|
198
|
-
];
|
|
199
|
-
HEAD = ROBOT.slice(0, 37);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// packages/core/src/audit.ts
|
|
203
|
-
var AUDIT_TYPES, AUDIT_TYPES_SQL;
|
|
204
|
-
var init_audit = __esm(() => {
|
|
205
|
-
AUDIT_TYPES = new Set([
|
|
206
|
-
"session.started",
|
|
207
|
-
"session.ended",
|
|
208
|
-
"tool.denied",
|
|
209
|
-
"claim.acquired",
|
|
210
|
-
"claim.renewed",
|
|
211
|
-
"claim.released",
|
|
212
|
-
"claim.expired",
|
|
213
|
-
"claim.orphaned",
|
|
214
|
-
"claim.denied",
|
|
215
|
-
"rules.changed",
|
|
216
|
-
"worktree.reclaimed",
|
|
217
|
-
"worktree.created",
|
|
218
|
-
"worktree.removed",
|
|
219
|
-
"worktree.bootstrapped",
|
|
220
|
-
"pr.opened",
|
|
221
|
-
"question.asked",
|
|
222
|
-
"question.answered",
|
|
223
|
-
"dispatch.queued",
|
|
224
|
-
"dispatch.started",
|
|
225
|
-
"dispatch.finished",
|
|
226
|
-
"resource.acquired",
|
|
227
|
-
"resource.released",
|
|
228
|
-
"resource.reaped",
|
|
229
|
-
"process.started",
|
|
230
|
-
"process.exited",
|
|
231
|
-
"gate.recorded",
|
|
232
|
-
"gate.blocked",
|
|
233
|
-
"codify.applied",
|
|
234
|
-
"handoff.recorded",
|
|
235
|
-
"permission.requested",
|
|
236
|
-
"permission.resolved",
|
|
237
|
-
"incident.opened",
|
|
238
|
-
"incident.acked",
|
|
239
|
-
"run.result",
|
|
240
|
-
"workflow.started",
|
|
241
|
-
"workflow.finished"
|
|
107
|
+
var OWN_SUMMARY;
|
|
108
|
+
var init_hooks = __esm(() => {
|
|
109
|
+
init_prompt();
|
|
110
|
+
OWN_SUMMARY = new Set([
|
|
111
|
+
"Bash",
|
|
112
|
+
"Read",
|
|
113
|
+
"Edit",
|
|
114
|
+
"Write",
|
|
115
|
+
"MultiEdit",
|
|
116
|
+
"NotebookEdit",
|
|
117
|
+
"Glob",
|
|
118
|
+
"Grep",
|
|
119
|
+
"Agent",
|
|
120
|
+
"Task",
|
|
121
|
+
"WebFetch",
|
|
122
|
+
"WebSearch",
|
|
123
|
+
"AskUserQuestion"
|
|
242
124
|
]);
|
|
243
|
-
AUDIT_TYPES_SQL = [...AUDIT_TYPES].map((t) => `'${t}'`).join(", ");
|
|
244
125
|
});
|
|
245
126
|
|
|
246
|
-
// packages/core/src/
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
127
|
+
// packages/core/src/guards.ts
|
|
128
|
+
function expandHome(s, home) {
|
|
129
|
+
return s.replace(/(^|[\s="'`:])(?:~|\$HOME|\$\{HOME\})(?=\/|$|[\s"'`])/g, `$1${home}`);
|
|
130
|
+
}
|
|
131
|
+
function segments(cmd) {
|
|
132
|
+
return cmd.split(/\s*(?:&&|\|\||;|\n)\s*/).map((s) => s.trim()).filter(Boolean);
|
|
133
|
+
}
|
|
134
|
+
function words(seg) {
|
|
135
|
+
return (seg.match(/"[^"]*"|'[^']*'|\S+/g) ?? []).map((w) => w.replace(/^["']|["']$/g, ""));
|
|
136
|
+
}
|
|
137
|
+
function dangerousTarget(target, home, toplevel) {
|
|
138
|
+
if (/^\$\{?\w+\}?(\/|$)/.test(target) && !target.startsWith(home))
|
|
139
|
+
return `starts with ${target.match(/^\$\{?\w+\}?/)?.[0]} \u2014 if that is ever unset, it is /`;
|
|
140
|
+
const t = trimSlash(target.replace(/\/\*$/, "/").replace(/\/\.$/, "/"));
|
|
141
|
+
if (t === "*" || target === "/*")
|
|
142
|
+
return "removes everything the glob matches, from the top";
|
|
143
|
+
if (t === "." || t === ".." || t.startsWith("../"))
|
|
144
|
+
return "removes the working directory or its parent";
|
|
145
|
+
if (t === home || t === trimSlash(home))
|
|
146
|
+
return "removes the home directory";
|
|
147
|
+
if (!t.startsWith("/"))
|
|
148
|
+
return null;
|
|
149
|
+
if (SYSTEM_DIRS.includes(t))
|
|
150
|
+
return `removes ${t}`;
|
|
151
|
+
if (t.split("/").length <= 3 && /^\/(Users|home)\//.test(t))
|
|
152
|
+
return `removes the home directory ${t}`;
|
|
153
|
+
if (SCRATCH.test(t))
|
|
154
|
+
return null;
|
|
155
|
+
if (toplevel && !(t === toplevel || t.startsWith(`${trimSlash(toplevel)}/`)))
|
|
156
|
+
return `removes ${t}, outside this repository`;
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
function destructiveFs(cmd, home, toplevel) {
|
|
160
|
+
const c = expandHome(cmd, home);
|
|
161
|
+
for (const seg of segments(c)) {
|
|
162
|
+
const w = words(seg.replace(/^sudo\s+/, ""));
|
|
163
|
+
if (w[0] === "rm") {
|
|
164
|
+
const flags = w.filter((x) => x.startsWith("-"));
|
|
165
|
+
const recursive = flags.some((f) => /^-[a-zA-Z]*[rR]/.test(f) || f === "--recursive");
|
|
166
|
+
if (!recursive)
|
|
167
|
+
continue;
|
|
168
|
+
for (const target of w.slice(1).filter((x) => !x.startsWith("-"))) {
|
|
169
|
+
const why = dangerousTarget(target, home, toplevel);
|
|
170
|
+
if (why)
|
|
171
|
+
return { what: `\`rm -r ${target}\` ${why}` };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (w[0] === "find" && (w.includes("-delete") || /-exec\s+rm\b/.test(seg))) {
|
|
175
|
+
const root = w[1] ?? ".";
|
|
176
|
+
const why = dangerousTarget(root, home, toplevel);
|
|
177
|
+
if (why && root !== ".")
|
|
178
|
+
return { what: `\`find ${root} \u2026 -delete\` ${why}` };
|
|
179
|
+
}
|
|
180
|
+
if (/^(mkfs(\.\w+)?|newfs\w*|wipefs)$/.test(w[0] ?? ""))
|
|
181
|
+
return { what: `\`${w[0]}\` formats a disk` };
|
|
182
|
+
if (w[0] === "dd" && w.some((x) => /^of=\/dev\//.test(x)))
|
|
183
|
+
return { what: "`dd` writes over a device" };
|
|
184
|
+
if (w[0] === "diskutil" && /^(erase|zero|secureErase|reformat)/i.test(w[1] ?? ""))
|
|
185
|
+
return { what: `\`diskutil ${w[1]}\` erases a disk` };
|
|
186
|
+
if ((w[0] === "chmod" || w[0] === "chown") && w.some((x) => /^-[a-zA-Z]*R/.test(x))) {
|
|
187
|
+
const target = w[w.length - 1] ?? "";
|
|
188
|
+
if (target.startsWith("/") || target === home) {
|
|
189
|
+
const why = dangerousTarget(target, home, toplevel);
|
|
190
|
+
if (why)
|
|
191
|
+
return { what: `\`${w[0]} -R ${target}\` ${why.replace(/^removes/, "changes")}` };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
function destructiveInfra(cmd) {
|
|
198
|
+
for (const r of INFRA)
|
|
199
|
+
if (r.re.test(cmd) && (!r.needs || r.needs.test(cmd)))
|
|
200
|
+
return { what: r.what };
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
function pipeToShell(cmd) {
|
|
204
|
+
if (new RegExp(`${FETCHER}[^|;&]*\\|\\s*(${INTERP})\\b`).test(cmd))
|
|
205
|
+
return { what: "pipes a downloaded script straight into an interpreter" };
|
|
206
|
+
if (/\b((ba|z)?sh|source|\.)\s+<\(\s*(curl|wget)\b/.test(cmd))
|
|
207
|
+
return { what: "runs a downloaded script via process substitution" };
|
|
208
|
+
if (/\b(ba|z)?sh\s+-c\s+["']?\$\(\s*(curl|wget)\b/.test(cmd))
|
|
209
|
+
return { what: "runs a downloaded script via command substitution" };
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
function secretFileIn(text) {
|
|
213
|
+
return SECRET_FILES.find((s) => s.re.test(text))?.what ?? null;
|
|
214
|
+
}
|
|
215
|
+
function secretCommand(cmd) {
|
|
216
|
+
if (/\bsecurity\s+find-(generic|internet)-password\b[^|;&]*\s-[a-z]*w/.test(cmd))
|
|
217
|
+
return { what: "prints a password from the macOS keychain" };
|
|
218
|
+
if (/\b(printenv|env)\s*(\||>|$)/.test(cmd) && /\|\s*(curl|nc|ncat)\b/.test(cmd))
|
|
219
|
+
return { what: "sends the environment over the network" };
|
|
220
|
+
for (const seg of segments(cmd)) {
|
|
221
|
+
if (!READERS.test(seg))
|
|
222
|
+
continue;
|
|
223
|
+
const w = words(seg);
|
|
224
|
+
const copy = /^(cp|scp|rsync)$/.test(w[0] ?? "");
|
|
225
|
+
const text = copy ? w.slice(1, -1).filter((x) => !x.startsWith("-")).join(" ") : seg;
|
|
226
|
+
const what = secretFileIn(text);
|
|
227
|
+
if (what)
|
|
228
|
+
return { what: `${copy ? "copies" : "reads"} ${what}` };
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
function secretFile(tool, path) {
|
|
233
|
+
const what = secretFileIn(path);
|
|
234
|
+
if (!what)
|
|
235
|
+
return null;
|
|
236
|
+
if (tool === "Read")
|
|
237
|
+
return { what: `reads ${what}` };
|
|
238
|
+
if (/\.env(\.[\w-]+)?$|\.(pem|p12|pfx|key)$/i.test(path))
|
|
239
|
+
return { what: `writes ${what}` };
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
function guardConfigFile(path, home) {
|
|
243
|
+
const p = expandHome(path, home);
|
|
244
|
+
const h = trimSlash(home);
|
|
245
|
+
if (/(^|\/)\.claude\/settings(\.local)?\.json$/.test(p))
|
|
246
|
+
return "Claude Code settings (hooks and permissions)";
|
|
247
|
+
if (p === `${h}/.claude.json`)
|
|
248
|
+
return "Claude Code's MCP registration";
|
|
249
|
+
if (p.startsWith(`${h}/.swarm/`) || p === `${h}/.swarm`)
|
|
250
|
+
return "Swarm's config and ledger";
|
|
251
|
+
if (/(^|\/)\.swarm\.toml$/.test(p))
|
|
252
|
+
return "this repo's Swarm rules";
|
|
253
|
+
if (p === `${h}/.codex/config.toml` || p === `${h}/.gemini/settings.json` || p === `${h}/.cursor/hooks.json`)
|
|
254
|
+
return "another agent's hook config";
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
function configTamperCommand(cmd, home) {
|
|
258
|
+
if (/(^|[\s;&|])swarm\s+uninstall\b/.test(cmd))
|
|
259
|
+
return { what: "uninstalls Swarm's hooks" };
|
|
260
|
+
for (const seg of segments(expandHome(cmd, home))) {
|
|
261
|
+
if (!WRITERS.test(seg))
|
|
262
|
+
continue;
|
|
263
|
+
for (const w of words(seg)) {
|
|
264
|
+
const target = w.replace(/^>+/, "");
|
|
265
|
+
if (!target.includes("/") && !target.startsWith("."))
|
|
266
|
+
continue;
|
|
267
|
+
const what = guardConfigFile(target, home);
|
|
268
|
+
if (what)
|
|
269
|
+
return { what: `changes ${what} (${target})` };
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
var trimSlash = (p) => p.length > 1 ? p.replace(/\/+$/, "") : p, SYSTEM_DIRS, SCRATCH, DB_CLIENT, INFRA, FETCHER, INTERP, SECRET_FILES, READERS, WRITERS;
|
|
275
|
+
var init_guards = __esm(() => {
|
|
276
|
+
SYSTEM_DIRS = [
|
|
277
|
+
"/",
|
|
278
|
+
"/bin",
|
|
279
|
+
"/boot",
|
|
280
|
+
"/dev",
|
|
281
|
+
"/etc",
|
|
282
|
+
"/home",
|
|
283
|
+
"/lib",
|
|
284
|
+
"/opt",
|
|
285
|
+
"/private",
|
|
286
|
+
"/root",
|
|
287
|
+
"/sbin",
|
|
288
|
+
"/System",
|
|
289
|
+
"/Library",
|
|
290
|
+
"/Applications",
|
|
291
|
+
"/Users",
|
|
292
|
+
"/usr",
|
|
293
|
+
"/var"
|
|
294
|
+
];
|
|
295
|
+
SCRATCH = /^(\/tmp|\/private\/tmp|\/var\/folders|\/private\/var\/folders)(\/|$)/;
|
|
296
|
+
DB_CLIENT = /\b(psql|mysql|mariadb|sqlite3|sqlcmd|clickhouse(-client)?|cockroach|mongo(sh)?|duckdb)\b/i;
|
|
297
|
+
INFRA = [
|
|
298
|
+
{
|
|
299
|
+
what: "`terraform destroy` tears the stack down",
|
|
300
|
+
re: /\b(terraform|tofu)\s+(\S+\s+)*destroy\b/
|
|
301
|
+
},
|
|
302
|
+
{ what: "`terraform state rm` forgets live resources", re: /\b(terraform|tofu)\s+state\s+rm\b/ },
|
|
303
|
+
{
|
|
304
|
+
what: "`kubectl delete` of a namespace or everything",
|
|
305
|
+
re: /\bkubectl\s+(\S+\s+)*delete\s+(\S+\s+)*(ns|namespaces?|pvc?|persistentvolume(claim)?s?|--all\b|-A\b|--all-namespaces\b)/
|
|
306
|
+
},
|
|
307
|
+
{ what: "`kubectl drain` evicts a node's workloads", re: /\bkubectl\s+(\S+\s+)*drain\b/ },
|
|
308
|
+
{ what: "`helm uninstall` removes a release", re: /\bhelm\s+(\S+\s+)*(uninstall|delete)\b/ },
|
|
309
|
+
{
|
|
310
|
+
what: "`aws s3 rm --recursive` empties a bucket path",
|
|
311
|
+
re: /\baws\s+s3\s+rm\b[^|;&]*--recursive/
|
|
312
|
+
},
|
|
313
|
+
{ what: "`aws s3 rb` deletes a bucket", re: /\baws\s+s3\s+rb\b/ },
|
|
314
|
+
{
|
|
315
|
+
what: "an AWS delete / terminate call",
|
|
316
|
+
re: /\baws\s+[\w-]+\s+(terminate-instances|delete-(db-instance|db-cluster|stack|table|bucket|cluster|function|user|role|repository))\b/
|
|
317
|
+
},
|
|
318
|
+
{ what: "a `gcloud \u2026 delete`", re: /\bgcloud\s+(\S+\s+)+delete\b/ },
|
|
319
|
+
{ what: "an `az \u2026 delete`", re: /\baz\s+(\S+\s+)+delete\b/ },
|
|
320
|
+
{
|
|
321
|
+
what: "`docker system prune` removes images and volumes",
|
|
322
|
+
re: /\bdocker\s+system\s+prune\b[^|;&]*(-a\b|--all\b|--volumes\b)/
|
|
323
|
+
},
|
|
324
|
+
{ what: "`docker volume rm/prune` deletes data volumes", re: /\bdocker\s+volume\s+(rm|prune)\b/ },
|
|
325
|
+
{ what: "a SQL `DROP`", re: /\bdrop\s+(database|schema|table)\b/i, needs: DB_CLIENT },
|
|
326
|
+
{ what: "a SQL `TRUNCATE`", re: /\btruncate\s+(table\s+)?\w/i, needs: DB_CLIENT },
|
|
327
|
+
{
|
|
328
|
+
what: "an unbounded SQL `DELETE`",
|
|
329
|
+
re: /\bdelete\s+from\s+[\w."]+\s*(;|"|'|$)/i,
|
|
330
|
+
needs: DB_CLIENT
|
|
331
|
+
},
|
|
332
|
+
{ what: "`dropdb` deletes a database", re: /(^|[\s;&|])dropdb\s/ },
|
|
333
|
+
{
|
|
334
|
+
what: "a database reset",
|
|
335
|
+
re: /\b(prisma\s+migrate\s+reset|rails\s+db:(drop|reset)|rake\s+db:(drop|reset)|supabase\s+db\s+reset)\b/
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
what: "`redis-cli FLUSHALL/FLUSHDB` wipes the store",
|
|
339
|
+
re: /\bredis-cli\b[^|;&]*\bflush(all|db)\b/i
|
|
340
|
+
},
|
|
341
|
+
{ what: "`dropDatabase()` in the Mongo shell", re: /\bdropDatabase\s*\(/, needs: DB_CLIENT }
|
|
342
|
+
];
|
|
343
|
+
FETCHER = String.raw`\b(curl|wget|fetch|http|xh)\b`;
|
|
344
|
+
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`;
|
|
345
|
+
SECRET_FILES = [
|
|
346
|
+
{
|
|
347
|
+
what: "a .env file",
|
|
348
|
+
re: /(^|[/\s"'`=@])\.env(\.(?!example\b|sample\b|template\b|dist\b|defaults?\b)[\w-]+)?(?=$|[\s"'`;|&)])/
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
what: "an SSH private key",
|
|
352
|
+
re: /(^|[/\s"'`])(id_rsa|id_ed25519|id_ecdsa|id_dsa)(?=$|[\s"'`;|&)])/
|
|
353
|
+
},
|
|
354
|
+
{ what: "AWS credentials", re: /\.aws\/credentials\b/ },
|
|
355
|
+
{ what: "an npm token file", re: /(^|[/\s"'`])\.npmrc\b/ },
|
|
356
|
+
{ what: "a kubeconfig", re: /\.kube\/config\b/ },
|
|
357
|
+
{ what: "Google Cloud credentials", re: /gcloud\/[\w-]*credential/i },
|
|
358
|
+
{ what: "a private key file", re: /[\w-]\.(pem|p12|pfx|key)(?=$|[\s"'`;|&)])/i },
|
|
359
|
+
{ what: "a netrc", re: /(^|[/\s"'`])\.netrc\b/ }
|
|
360
|
+
];
|
|
361
|
+
READERS = /(^|[\s;&|(])(cat|less|more|head|tail|bat|strings|base64|xxd|od|hexdump|grep|egrep|rg|ag|awk|nl|tac|cp|scp|rsync|openssl|gpg|pbcopy)\b|curl\b[^|;&]*(-d|--data[\w-]*|-F|--form|-T|--upload-file)\s*@/;
|
|
362
|
+
WRITERS = /(^|[\s;&|(])(>>?|tee|rm|mv|cp|truncate|ln|chmod|chown|unlink|shred)\b|>>?\s*\S|\bsed\s+(-[a-zA-Z]*\s+)*-i|\bperl\s+-[a-zA-Z]*i|\bsqlite3\b[^|;&]*\b(delete|update|drop|insert)\b/i;
|
|
258
363
|
});
|
|
259
364
|
|
|
260
|
-
// packages/core/src/context.ts
|
|
261
|
-
var init_context = () => {};
|
|
262
|
-
|
|
263
|
-
// packages/core/src/dag.ts
|
|
264
|
-
var init_dag = () => {};
|
|
265
365
|
// packages/core/src/rules.ts
|
|
266
366
|
function otherLiveInSameTree(current, sessions, now, withinMs = LIVE_WINDOW_MS) {
|
|
267
367
|
if (!current.toplevel)
|
|
@@ -312,6 +412,41 @@ function killedPorts(cmd) {
|
|
|
312
412
|
ports.add(Number(m[1]));
|
|
313
413
|
return [...ports];
|
|
314
414
|
}
|
|
415
|
+
function familyHitsBash(cmd, home, toplevel) {
|
|
416
|
+
const out = [];
|
|
417
|
+
const add = (rule, h) => h && out.push({ rule, what: h.what });
|
|
418
|
+
add("destructive_fs", destructiveFs(cmd, home, toplevel));
|
|
419
|
+
add("destructive_infra", destructiveInfra(cmd));
|
|
420
|
+
add("pipe_to_shell", pipeToShell(cmd));
|
|
421
|
+
add("secrets", secretCommand(cmd));
|
|
422
|
+
add("config_tamper", configTamperCommand(cmd, home));
|
|
423
|
+
return out;
|
|
424
|
+
}
|
|
425
|
+
function familyHitsFile(tool, path, home) {
|
|
426
|
+
const out = [];
|
|
427
|
+
const secret = secretFile(tool, path);
|
|
428
|
+
if (secret)
|
|
429
|
+
out.push({ rule: "secrets", what: secret.what });
|
|
430
|
+
if (WRITE_TOOLS.has(tool)) {
|
|
431
|
+
const what = guardConfigFile(path, home);
|
|
432
|
+
if (what)
|
|
433
|
+
out.push({ rule: "config_tamper", what: `changes ${what} (${path})` });
|
|
434
|
+
}
|
|
435
|
+
return out;
|
|
436
|
+
}
|
|
437
|
+
function familyDecision(hits, modes) {
|
|
438
|
+
for (const h of hits) {
|
|
439
|
+
const mode = modes[h.rule] ?? "off";
|
|
440
|
+
if (mode !== "off")
|
|
441
|
+
return { action: mode, rule: h.rule, reason: FAMILY_REASON[h.rule](h.what) };
|
|
442
|
+
}
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
function guardFile(tool, path, modes = DEFAULT_MODES, home = process.env.HOME ?? "") {
|
|
446
|
+
if ((modes.secrets ?? "off") === "off" && (modes.config_tamper ?? "off") === "off")
|
|
447
|
+
return { action: "allow" };
|
|
448
|
+
return familyDecision(familyHitsFile(tool, path, home), modes) ?? { action: "allow" };
|
|
449
|
+
}
|
|
315
450
|
function isSingleCommand(cmd) {
|
|
316
451
|
return !/[&|;<>`\n]/.test(cmd) && !cmd.includes("$(");
|
|
317
452
|
}
|
|
@@ -436,7 +571,7 @@ function guardBash(cmd, current, sessions, now, modes = DEFAULT_MODES, ctx = {})
|
|
|
436
571
|
if (modes.shared_tree !== "off" && isBroadStage(cmd)) {
|
|
437
572
|
const o = other();
|
|
438
573
|
if (o) {
|
|
439
|
-
const d = hit("shared_tree",
|
|
574
|
+
const d = hit("shared_tree", sharedTreeReason(o.id));
|
|
440
575
|
if (d.action !== "allow")
|
|
441
576
|
return d;
|
|
442
577
|
}
|
|
@@ -449,6 +584,11 @@ function guardBash(cmd, current, sessions, now, modes = DEFAULT_MODES, ctx = {})
|
|
|
449
584
|
return d;
|
|
450
585
|
}
|
|
451
586
|
}
|
|
587
|
+
if (FAMILY_RULES.some((r) => (modes[r] ?? "off") !== "off")) {
|
|
588
|
+
const d = familyDecision(familyHitsBash(cmd, ctx.home ?? process.env.HOME ?? "", current.toplevel), modes);
|
|
589
|
+
if (d)
|
|
590
|
+
return d;
|
|
591
|
+
}
|
|
452
592
|
for (const rule of modes.custom ?? []) {
|
|
453
593
|
const d = applyCustomRule(rule, cmd);
|
|
454
594
|
if (d)
|
|
@@ -456,7 +596,7 @@ function guardBash(cmd, current, sessions, now, modes = DEFAULT_MODES, ctx = {})
|
|
|
456
596
|
}
|
|
457
597
|
const nv = rewriteNoVerify(cmd);
|
|
458
598
|
if (nv) {
|
|
459
|
-
const d = fix("no_verify",
|
|
599
|
+
const d = fix("no_verify", NO_VERIFY_REASON, nv);
|
|
460
600
|
if (d.action !== "allow")
|
|
461
601
|
return d;
|
|
462
602
|
}
|
|
@@ -519,8 +659,9 @@ function guardWrite(target, current, claims, modes = DEFAULT_MODES, kind = "file
|
|
|
519
659
|
}
|
|
520
660
|
return { action: "allow" };
|
|
521
661
|
}
|
|
522
|
-
var LIVE_WINDOW_MS, GIT_GLOBAL_OPTS = "(?:\\s+(?:-C\\s+\\S+|-c\\s+\\S+|--git-dir(?:=|\\s+)\\S+|--work-tree(?:=|\\s+)\\S+|--namespace(?:=|\\s+)\\S+|--no-pager|--paginate|--bare|--literal-pathspecs|--exec-path(?:=\\S+)?))*", DEFAULT_MODES, customRe, WRITE_TOOLS;
|
|
662
|
+
var LIVE_WINDOW_MS, GIT_GLOBAL_OPTS = "(?:\\s+(?:-C\\s+\\S+|-c\\s+\\S+|--git-dir(?:=|\\s+)\\S+|--work-tree(?:=|\\s+)\\S+|--namespace(?:=|\\s+)\\S+|--no-pager|--paginate|--bare|--literal-pathspecs|--exec-path(?:=\\S+)?))*", DEFAULT_MODES, FAMILY_RULES, FAMILY_REASON, customRe, sharedTreeReason = (sessionId) => `Another session (${sessionId.slice(0, 8)}) is active in this same checkout. \`git add -A\` / \`git commit -a\` will sweep its uncommitted changes into your commit. Stage explicit paths (\`git add <path>\`), or give each session its own git worktree.`, NO_VERIFY_REASON = "Hooks and signing exist for a reason: `--no-verify` / `--no-gpg-sign` was dropped. If a hook is wrong, fix the hook.", WRITE_TOOLS;
|
|
523
663
|
var init_rules = __esm(() => {
|
|
664
|
+
init_guards();
|
|
524
665
|
LIVE_WINDOW_MS = 10 * 60000;
|
|
525
666
|
DEFAULT_MODES = {
|
|
526
667
|
shared_tree: "ask",
|
|
@@ -532,12 +673,348 @@ var init_rules = __esm(() => {
|
|
|
532
673
|
no_verify: "off",
|
|
533
674
|
dry_run_first: "off",
|
|
534
675
|
custom: [],
|
|
676
|
+
destructive_fs: "off",
|
|
677
|
+
destructive_infra: "off",
|
|
678
|
+
pipe_to_shell: "off",
|
|
679
|
+
secrets: "off",
|
|
680
|
+
config_tamper: "off",
|
|
535
681
|
protected: { ports: [] }
|
|
536
682
|
};
|
|
683
|
+
FAMILY_RULES = [
|
|
684
|
+
"destructive_fs",
|
|
685
|
+
"destructive_infra",
|
|
686
|
+
"pipe_to_shell",
|
|
687
|
+
"secrets",
|
|
688
|
+
"config_tamper"
|
|
689
|
+
];
|
|
690
|
+
FAMILY_REASON = {
|
|
691
|
+
destructive_fs: (w) => `This ${w}. Nothing gets that back \u2014 name the exact paths you mean, inside this repository.`,
|
|
692
|
+
destructive_infra: (w) => `This is ${w} \u2014 outside this checkout, and not something git can undo. Confirm with the owner first.`,
|
|
693
|
+
pipe_to_shell: (w) => `This ${w}. Download it to a file, read it, then run it.`,
|
|
694
|
+
secrets: (w) => `This ${w}. Credentials stay out of the transcript: use the variable by name, or ask the owner.`,
|
|
695
|
+
config_tamper: (w) => `This ${w} \u2014 the settings that decide what agents may do. Ask the owner to change them.`
|
|
696
|
+
};
|
|
537
697
|
customRe = new WeakMap;
|
|
538
698
|
WRITE_TOOLS = new Set(["Write", "Edit", "MultiEdit", "NotebookEdit"]);
|
|
539
699
|
});
|
|
540
700
|
|
|
701
|
+
// packages/core/src/policy.ts
|
|
702
|
+
import { createHash } from "crypto";
|
|
703
|
+
function verifyPolicyCache(raw) {
|
|
704
|
+
if (!raw || typeof raw !== "object")
|
|
705
|
+
return null;
|
|
706
|
+
const c = raw;
|
|
707
|
+
if (c.version !== POLICY_CACHE_VERSION || typeof c.sha256 !== "string")
|
|
708
|
+
return null;
|
|
709
|
+
const { sha256, ...body } = c;
|
|
710
|
+
if (!body.modes || !Array.isArray(body.sessions) || !Array.isArray(body.worktrees))
|
|
711
|
+
return null;
|
|
712
|
+
return digest(body) === sha256 ? c : null;
|
|
713
|
+
}
|
|
714
|
+
function evaluateOffline(cache, raw, toplevel, now = Date.now()) {
|
|
715
|
+
const tool = typeof raw.tool_name === "string" ? raw.tool_name : "";
|
|
716
|
+
const input = raw.tool_input ?? {};
|
|
717
|
+
const id = typeof raw.session_id === "string" ? raw.session_id : "";
|
|
718
|
+
const cwd = typeof raw.cwd === "string" ? raw.cwd : "";
|
|
719
|
+
const isWrite = WRITE_TOOLS.has(tool) && typeof input.file_path === "string";
|
|
720
|
+
const cmd = tool === "Bash" && typeof input.command === "string" ? input.command : null;
|
|
721
|
+
const modes = cache.modes;
|
|
722
|
+
if (typeof input.file_path === "string" && (isWrite || tool === "Read")) {
|
|
723
|
+
const f = guardFile(tool, absolutePath(input.file_path, cwd), modes);
|
|
724
|
+
if (f.action !== "allow")
|
|
725
|
+
return f;
|
|
726
|
+
}
|
|
727
|
+
if (!isWrite && !cmd)
|
|
728
|
+
return { action: "allow" };
|
|
729
|
+
const current = { id, cwd, toplevel: toplevel(cwd) };
|
|
730
|
+
if (modes.no_foreign_worktree !== "off" || modes.claim_required_to_write !== "off") {
|
|
731
|
+
const target = isWrite ? absolutePath(input.file_path, cwd) : cwd;
|
|
732
|
+
const w = guardWrite(target, current, cache.worktrees, modes, isWrite ? "file" : "bash");
|
|
733
|
+
if (w.action !== "allow")
|
|
734
|
+
return w;
|
|
735
|
+
}
|
|
736
|
+
if (!cmd)
|
|
737
|
+
return { action: "allow" };
|
|
738
|
+
return guardBash(cmd, current, cache.sessions, now, modes);
|
|
739
|
+
}
|
|
740
|
+
var POLICY_CACHE_VERSION = 1, POLICY_CACHE_FILE = "policy.cache.json", digest = (body) => createHash("sha256").update(JSON.stringify(body)).digest("hex");
|
|
741
|
+
var init_policy = __esm(() => {
|
|
742
|
+
init_hooks();
|
|
743
|
+
init_rules();
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
// packages/core/src/agenthooks.ts
|
|
747
|
+
function detectAgent(raw) {
|
|
748
|
+
if (typeof raw.cursor_version === "string" || typeof raw.conversation_id === "string")
|
|
749
|
+
return "cursor";
|
|
750
|
+
if (raw.hook_event_name === "BeforeTool")
|
|
751
|
+
return "gemini";
|
|
752
|
+
if (typeof raw.turn_id === "string")
|
|
753
|
+
return "codex";
|
|
754
|
+
return "claude-code";
|
|
755
|
+
}
|
|
756
|
+
function patchPaths(patch) {
|
|
757
|
+
const out = [];
|
|
758
|
+
for (const m of patch.matchAll(/^\*\*\* (?:Add File|Update File|Delete File|Move to): (.+)$/gm)) {
|
|
759
|
+
const p = m[1]?.trim();
|
|
760
|
+
if (p && !out.includes(p))
|
|
761
|
+
out.push(p);
|
|
762
|
+
}
|
|
763
|
+
return out;
|
|
764
|
+
}
|
|
765
|
+
function toToolRequests(agent, raw) {
|
|
766
|
+
const input = raw.tool_input && typeof raw.tool_input === "object" ? raw.tool_input : {};
|
|
767
|
+
const tool = str(raw.tool_name) ?? "";
|
|
768
|
+
const cwd = str(raw.cwd) ?? "";
|
|
769
|
+
const sessionId = str(raw.session_id) ?? str(raw.conversation_id) ?? "";
|
|
770
|
+
const req = (t, i, at = cwd) => ({
|
|
771
|
+
tool: t,
|
|
772
|
+
input: i,
|
|
773
|
+
sessionId,
|
|
774
|
+
cwd: at
|
|
775
|
+
});
|
|
776
|
+
switch (agent) {
|
|
777
|
+
case "claude-code": {
|
|
778
|
+
const command = str(input.command);
|
|
779
|
+
const file_path = str(input.file_path);
|
|
780
|
+
return [req(tool, { ...command ? { command } : {}, ...file_path ? { file_path } : {} })];
|
|
781
|
+
}
|
|
782
|
+
case "codex": {
|
|
783
|
+
const command = str(input.command);
|
|
784
|
+
if (tool === "Bash" && command)
|
|
785
|
+
return [req("Bash", { command })];
|
|
786
|
+
if (tool === "apply_patch" && command)
|
|
787
|
+
return patchPaths(command).map((p) => req("Write", { file_path: p }));
|
|
788
|
+
return [];
|
|
789
|
+
}
|
|
790
|
+
case "gemini": {
|
|
791
|
+
const shell = str(input.command);
|
|
792
|
+
if (tool === "run_shell_command" && shell)
|
|
793
|
+
return [req("Bash", { command: shell }, str(input.dir_path) ?? cwd)];
|
|
794
|
+
const fp = str(input.file_path);
|
|
795
|
+
if (!fp)
|
|
796
|
+
return [];
|
|
797
|
+
if (tool === "read_file")
|
|
798
|
+
return [req("Read", { file_path: fp })];
|
|
799
|
+
if (tool === "write_file" || tool === "replace")
|
|
800
|
+
return [req("Write", { file_path: fp })];
|
|
801
|
+
return [];
|
|
802
|
+
}
|
|
803
|
+
case "cursor": {
|
|
804
|
+
const shell = str(input.command);
|
|
805
|
+
if (tool === "Shell" && shell)
|
|
806
|
+
return [req("Bash", { command: shell }, str(input.working_directory) ?? cwd)];
|
|
807
|
+
const fp = str(input.file_path) ?? str(input.path) ?? str(input.target_file);
|
|
808
|
+
if (!fp)
|
|
809
|
+
return [];
|
|
810
|
+
if (tool === "Read")
|
|
811
|
+
return [req("Read", { file_path: fp })];
|
|
812
|
+
if (tool === "Write" || tool === "Delete")
|
|
813
|
+
return [req("Write", { file_path: fp })];
|
|
814
|
+
return [];
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
function effectiveDecision(agent, d) {
|
|
819
|
+
if (d.action === "ask" && !CAN_ASK.has(agent))
|
|
820
|
+
return {
|
|
821
|
+
...d,
|
|
822
|
+
action: "deny",
|
|
823
|
+
reason: `${d.reason ?? ""} (Swarm would ask the user here; ${AGENT_LABEL[agent]}'s hooks cannot ask, so the call is refused \u2014 ask the user to run it, or to turn the rule off.)`.trim()
|
|
824
|
+
};
|
|
825
|
+
if (d.action === "rewrite" && agent !== "claude-code")
|
|
826
|
+
return {
|
|
827
|
+
...d,
|
|
828
|
+
action: "deny",
|
|
829
|
+
reason: `${d.reason ?? ""} Run this instead: ${d.command ?? ""}`.trim()
|
|
830
|
+
};
|
|
831
|
+
return d;
|
|
832
|
+
}
|
|
833
|
+
function renderAgentDecision(agent, d) {
|
|
834
|
+
const e = effectiveDecision(agent, d);
|
|
835
|
+
if (e.action === "allow")
|
|
836
|
+
return "{}";
|
|
837
|
+
const reason = `[swarm] ${e.reason ?? ""}`.trim();
|
|
838
|
+
if (agent === "gemini")
|
|
839
|
+
return JSON.stringify({ decision: "deny", reason });
|
|
840
|
+
return JSON.stringify({
|
|
841
|
+
hookSpecificOutput: {
|
|
842
|
+
hookEventName: "PreToolUse",
|
|
843
|
+
permissionDecision: e.action === "ask" ? "ask" : "deny",
|
|
844
|
+
permissionDecisionReason: reason
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
var AGENT_LABEL, CAN_ASK, str = (v) => typeof v === "string" && v ? v : undefined;
|
|
849
|
+
var init_agenthooks = __esm(() => {
|
|
850
|
+
init_policy();
|
|
851
|
+
AGENT_LABEL = {
|
|
852
|
+
"claude-code": "Claude Code",
|
|
853
|
+
codex: "Codex",
|
|
854
|
+
gemini: "Gemini CLI",
|
|
855
|
+
cursor: "Cursor"
|
|
856
|
+
};
|
|
857
|
+
CAN_ASK = new Set(["claude-code"]);
|
|
858
|
+
});
|
|
859
|
+
|
|
860
|
+
// packages/core/src/art.ts
|
|
861
|
+
var ROBOT, HEAD;
|
|
862
|
+
var init_art = __esm(() => {
|
|
863
|
+
ROBOT = [
|
|
864
|
+
" MM MM ",
|
|
865
|
+
" LL LL ",
|
|
866
|
+
" LL LL ",
|
|
867
|
+
" LM MM ",
|
|
868
|
+
" MD MS ",
|
|
869
|
+
" LE LE ",
|
|
870
|
+
" ME ME ",
|
|
871
|
+
" MS MS ",
|
|
872
|
+
" KK KK ",
|
|
873
|
+
" SSDDDSDDDDDDDDDDDDDDDDDDDDDDDDDDDDS ",
|
|
874
|
+
" SSDSDDDDDDDDDDDDDDDDDDDDDDDDDDDDDSS ",
|
|
875
|
+
" SSDOOOOOOOOOOOOOOOOOOOOOOOOOOOOOKSS ",
|
|
876
|
+
" SSOMMMMMMMMMMMMMMMMMMMMMMMMMMMMMOSS ",
|
|
877
|
+
" DDKMMLLLMLLMMMMMMMMMMMMMMMMMMMMMODD ",
|
|
878
|
+
" DDOMMLMMMMMMMMMMMMMMMMMMMMMMMMMMODD ",
|
|
879
|
+
" SSSSODDOMMLMMMMMMMMMMMMMMMMMMMMMMMMMMODDOSSSS ",
|
|
880
|
+
" SSSSODDOMMMMMMMMMMMMMMMMMMMMMMMMMMMMMODDOSSSS ",
|
|
881
|
+
" SMMMMODDOMMLMMMMMMMMMMMMMMMMMMMMMMMMMMODDOSMMMS ",
|
|
882
|
+
" DMSSSODDOMMMMOOOOOOMMMMMMMMMOOOOOOMMMMODDOSSSED ",
|
|
883
|
+
" DSSSSODDOMMMMODDDDDMMMMMMMMMODDDDDMMMMODDOSSSSK ",
|
|
884
|
+
" DSMMSODDOMMMMDDDDDDMMMMMMMMMKDDDDDMMMMODDOSMMSK ",
|
|
885
|
+
" DSSSSODDOMMMMDDDDDDMMMMMMMMMDDDDDDMMMMODDOSSSSK ",
|
|
886
|
+
" DSSSSODDOMMMMDDDDDDMMMMMMMMMDDDDDDMMMMODDODSSSK ",
|
|
887
|
+
" DSSSSODDOMMMMDDDDDDMMMMMMMMMDDDDDDMMMMODDODSSSK ",
|
|
888
|
+
" DSSSKODDOMMMMDDDDDDMMMMMMMMMDDDDDDMMMMODDODSSSD ",
|
|
889
|
+
" OKKKKODDOMMMMMMMMMMMMMMMMMMMMMMMMMMMMMODDOKKKKO ",
|
|
890
|
+
" KKKKODDOMMMMMMMMMMMMMMMMMMMMMMMMMMMMMODDOOKKK ",
|
|
891
|
+
" KKKOODDOMMMMMMMMMMMMMMMMMMMMMMMMMMMMMODDOOKKK ",
|
|
892
|
+
" DDOMMMMDDDDDDDDDDDDDDDDDDDDDMMMMODD ",
|
|
893
|
+
" DDOMMMMDDDDDDDDDDDDDDDDDDDDDMMMMODD ",
|
|
894
|
+
" DDOMMMMDDDDDDDDDDDDDDDDDDDDDMMMMODD ",
|
|
895
|
+
" DDOMMMMMMMMMMMMMMMMMMMMMMMMMMMMMODD ",
|
|
896
|
+
" DDOMMMMMMMMMMMMMMMMMMMMMMMMMMMMMODD ",
|
|
897
|
+
" DDOMMMMMMMMMMMMMMMMMMMMMMMMMMMMMODD ",
|
|
898
|
+
" DDDOOOOOOOOOOOOOOOOOOOOOOOOOOOOOKDD ",
|
|
899
|
+
" DDDDDDDDDDDDDDDDDDDDDDDDDDDDDKDDDDD ",
|
|
900
|
+
" DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD ",
|
|
901
|
+
" OOOOOOOOOOO ",
|
|
902
|
+
" OODDDDDKKOO ",
|
|
903
|
+
" DSMMMMMMSDD ",
|
|
904
|
+
" OOOOOOOOOOO ",
|
|
905
|
+
" KDSMMMMMESSKK ",
|
|
906
|
+
" MMMMMMSSKOOKDSMMMMMSSSDKOODSEMMMMMM ",
|
|
907
|
+
" SSSSDO MMMMMMSSKKOOOOOOOOOOOOOOOOOKSSSMMMMMM ODSSSS ",
|
|
908
|
+
" EEESSSSOOLLLLMMMMMESSSSSSSSSSSSSSSSSSSSMMMMMLLLLOOSSSSMME ",
|
|
909
|
+
" SMMSSSSKOEELLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLEEOOSSSSEMS ",
|
|
910
|
+
" DSSSSSSSOOSEEKSMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMSOEEDOOSSSSSSSK ",
|
|
911
|
+
" DSSSSSSDOOSEOSKEMMMMMMMMMMMMMMMMMMMMMMMMMMMMMEOSDEDOODSSSSSSK ",
|
|
912
|
+
" KSSSSSSKOODSSOSEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEESOEEDOOKSSSSSSK ",
|
|
913
|
+
" DKSSSSKKOODSMEEMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLMSEMSDOOKKSSSSDK ",
|
|
914
|
+
" OKDDKDKKOOKSMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMSKOOKKKKKKKO ",
|
|
915
|
+
" OOOOOKKKOOKSMMMMESSSSSSSSSSSSSMMMMEEEEEESSSSEMMMMSKOOKKKOOOOO ",
|
|
916
|
+
" OOOOOOKKOOKSMMMSSKKKKKKKKKKKKKKMMMDKOOOOOOOODDMMMSKOOKKOOOOOO ",
|
|
917
|
+
" OKSSKOOOOOKSMMMKKMLLLLLLLLLLLMKMMMDDSSSSSSSSDDMMMSKOOOOODSSKO ",
|
|
918
|
+
" SDOKKK KSMMMKDLLLLLLLLLLLLLKMMMKDKKKDDKKKDKMMMSK KKKKDS ",
|
|
919
|
+
" DMLLMKO KSMMMKDLLLLLLLLLLLLLKMMMDDDDDDDDDSKDMMMSK ODMLLMD ",
|
|
920
|
+
" DELLMSD KSMMMKKMLLLLLLLLLLLLKMMMDKKKKKKKKKDDMMMSK DSMLLEO ",
|
|
921
|
+
" KSKKKKDO KSMMMEDDDDKKDDDKKKDKSMMMDDDDDDDDDSDDMMMSK ODDKKKSK ",
|
|
922
|
+
" EMLLMDO KSMMMMLLLLLLLLLLLLLLLMMMDOOOOOOOOODDMMMSK ODMLLME ",
|
|
923
|
+
" KMLLMSD KSMMMMMMMMMMMMMMMMMMMMMMDDSSDDSSSSDDMMMSK SSMLLEK ",
|
|
924
|
+
" KSKKKKSO KSMMMMMOOMMOOMKOEMOOMMMMDOOOOOOOOODDMMMSK OSKKKKSO ",
|
|
925
|
+
" SMLLMDO KSMMMMMKKMMKKMDKEMKKMMMMKDDDDSSSSDDDMMMSK ODMLLMS ",
|
|
926
|
+
" SMMMMSD KSMMMMMEEMMEEMEEMMEEMMMMDSOOOOOOOODDMMSSK DSMMMMD ",
|
|
927
|
+
" OOOOOOO KSSMMMMMMMMMMMMMMMMMMMMMDDDDDDDDDDDDMSSSK OOOOOOO ",
|
|
928
|
+
" KSMLLLMMSKO KSSSSMMMMMMMMMMMMMMMMMMMSOOOOOOOOOOSSSSSK OKSMLLLLMSO ",
|
|
929
|
+
" DSMMMEEESKK KSSKSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSOSSK KDSMMMMMESD ",
|
|
930
|
+
" DSMMMMMESKK KSOOKSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSOOKSK KKSMMMMMMSD ",
|
|
931
|
+
" DSMMMMMESKK OKSKSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSDSKO KDSMMMMMMSD ",
|
|
932
|
+
" KDDDDDDDDKK KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK KKDDDDDDDDO ",
|
|
933
|
+
" OOOOOOOOOOO OOOOOOOOO OOOOOOOOO OOOOOOOOOOO ",
|
|
934
|
+
"DSDO OSSO ODKKKKKKK OKKKKKKKO KEDO ODEK",
|
|
935
|
+
"SMDO OSMD KDSSSSSSK KDSSSSSKO SMDO ODMS",
|
|
936
|
+
"DEKO OOED OOKKKOOO OOOKKOOO SSOO OKMS",
|
|
937
|
+
"DEKO OOED SEMMMMSK OSMMMMED SSKO OKES",
|
|
938
|
+
"DSSO OESK SEMMMMSK OSMMMMED KSSO OSSK",
|
|
939
|
+
"KSMS EEDO OOKKKKKO OOKKKKKO OSEE EMSO",
|
|
940
|
+
" DSS SSK OSEEESDO ODSEEESO KSS SSD ",
|
|
941
|
+
" OOO OO SEMMMMSK OSMMMMED OO OOO ",
|
|
942
|
+
" KSSSSSDO OKSSESSK ",
|
|
943
|
+
" DKKDDDDDDKKDK KDKDDDDDDDKDK ",
|
|
944
|
+
" SSMLLMMMMMMLMSK DSSMMMMMMMLLMSS ",
|
|
945
|
+
" SMLMMMMMMMMLLED SEMMMMMMMMMMLMS ",
|
|
946
|
+
" DMMMMMMMMMMMMMMD SMMMMMMMMMMMMMSK ",
|
|
947
|
+
" ODSSSSSSSSSSSSSSDO ODSSSSSSSSSSSSSSKO ",
|
|
948
|
+
" OOOOOOOOOOOOOOOOO OOOOOOOOOOOOOOOOO ",
|
|
949
|
+
" OKSSSSSSSSSSSSSSK KSSSSSSSSSSSSSSKO ",
|
|
950
|
+
" OKDDDDDDDDDDDDDDK KDDDDDDDDDDDDDDOO "
|
|
951
|
+
];
|
|
952
|
+
HEAD = ROBOT.slice(0, 37);
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
// packages/core/src/audit.ts
|
|
956
|
+
var AUDIT_TYPES, AUDIT_TYPES_SQL;
|
|
957
|
+
var init_audit = __esm(() => {
|
|
958
|
+
AUDIT_TYPES = new Set([
|
|
959
|
+
"session.started",
|
|
960
|
+
"session.ended",
|
|
961
|
+
"tool.denied",
|
|
962
|
+
"claim.acquired",
|
|
963
|
+
"claim.renewed",
|
|
964
|
+
"claim.released",
|
|
965
|
+
"claim.expired",
|
|
966
|
+
"claim.orphaned",
|
|
967
|
+
"claim.denied",
|
|
968
|
+
"rules.changed",
|
|
969
|
+
"worktree.reclaimed",
|
|
970
|
+
"worktree.created",
|
|
971
|
+
"worktree.removed",
|
|
972
|
+
"worktree.bootstrapped",
|
|
973
|
+
"pr.opened",
|
|
974
|
+
"question.asked",
|
|
975
|
+
"question.answered",
|
|
976
|
+
"dispatch.queued",
|
|
977
|
+
"dispatch.started",
|
|
978
|
+
"dispatch.finished",
|
|
979
|
+
"resource.acquired",
|
|
980
|
+
"resource.released",
|
|
981
|
+
"resource.reaped",
|
|
982
|
+
"process.started",
|
|
983
|
+
"process.exited",
|
|
984
|
+
"gate.recorded",
|
|
985
|
+
"gate.blocked",
|
|
986
|
+
"codify.applied",
|
|
987
|
+
"handoff.recorded",
|
|
988
|
+
"permission.requested",
|
|
989
|
+
"permission.resolved",
|
|
990
|
+
"incident.opened",
|
|
991
|
+
"incident.acked",
|
|
992
|
+
"run.result",
|
|
993
|
+
"workflow.started",
|
|
994
|
+
"workflow.finished"
|
|
995
|
+
]);
|
|
996
|
+
AUDIT_TYPES_SQL = [...AUDIT_TYPES].map((t) => `'${t}'`).join(", ");
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
// packages/core/src/budget.ts
|
|
1000
|
+
var BUDGET_ASK_TOOLS;
|
|
1001
|
+
var init_budget = __esm(() => {
|
|
1002
|
+
BUDGET_ASK_TOOLS = new Set(["Bash", "Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
1003
|
+
});
|
|
1004
|
+
// packages/core/src/workflows.ts
|
|
1005
|
+
var init_workflows = () => {};
|
|
1006
|
+
|
|
1007
|
+
// packages/core/src/config.ts
|
|
1008
|
+
var init_config = __esm(() => {
|
|
1009
|
+
init_audit();
|
|
1010
|
+
init_workflows();
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
// packages/core/src/context.ts
|
|
1014
|
+
var init_context = () => {};
|
|
1015
|
+
|
|
1016
|
+
// packages/core/src/dag.ts
|
|
1017
|
+
var init_dag = () => {};
|
|
541
1018
|
// packages/core/src/dryrun.ts
|
|
542
1019
|
var init_dryrun = __esm(() => {
|
|
543
1020
|
init_rules();
|
|
@@ -578,6 +1055,7 @@ var init_hygiene = __esm(() => {
|
|
|
578
1055
|
// packages/core/src/ledger.ts
|
|
579
1056
|
var EDIT_TOOLS;
|
|
580
1057
|
var init_ledger = __esm(() => {
|
|
1058
|
+
init_prompt();
|
|
581
1059
|
EDIT_TOOLS = new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
|
|
582
1060
|
});
|
|
583
1061
|
// packages/core/src/lessons-apply.ts
|
|
@@ -592,65 +1070,31 @@ var init_lineage = __esm(() => {
|
|
|
592
1070
|
var init_mcphealth = __esm(() => {
|
|
593
1071
|
init_gatehealth();
|
|
594
1072
|
});
|
|
595
|
-
|
|
596
1073
|
// packages/core/src/memory.ts
|
|
597
1074
|
var init_memory = () => {};
|
|
1075
|
+
// packages/core/src/otel.ts
|
|
1076
|
+
var init_otel = () => {};
|
|
1077
|
+
|
|
598
1078
|
// packages/core/src/outcomes.ts
|
|
599
1079
|
var DEFAULT_BRANCHES;
|
|
600
1080
|
var init_outcomes = __esm(() => {
|
|
601
1081
|
DEFAULT_BRANCHES = new Set(["main", "master", "develop", "trunk"]);
|
|
602
1082
|
});
|
|
603
|
-
// packages/core/src/policy.ts
|
|
604
|
-
import { createHash } from "crypto";
|
|
605
|
-
function verifyPolicyCache(raw) {
|
|
606
|
-
if (!raw || typeof raw !== "object")
|
|
607
|
-
return null;
|
|
608
|
-
const c = raw;
|
|
609
|
-
if (c.version !== POLICY_CACHE_VERSION || typeof c.sha256 !== "string")
|
|
610
|
-
return null;
|
|
611
|
-
const { sha256, ...body } = c;
|
|
612
|
-
if (!body.modes || !Array.isArray(body.sessions) || !Array.isArray(body.worktrees))
|
|
613
|
-
return null;
|
|
614
|
-
return digest(body) === sha256 ? c : null;
|
|
615
|
-
}
|
|
616
|
-
function evaluateOffline(cache, raw, toplevel, now = Date.now()) {
|
|
617
|
-
const tool = typeof raw.tool_name === "string" ? raw.tool_name : "";
|
|
618
|
-
const input = raw.tool_input ?? {};
|
|
619
|
-
const id = typeof raw.session_id === "string" ? raw.session_id : "";
|
|
620
|
-
const cwd = typeof raw.cwd === "string" ? raw.cwd : "";
|
|
621
|
-
const isWrite = WRITE_TOOLS.has(tool) && typeof input.file_path === "string";
|
|
622
|
-
const cmd = tool === "Bash" && typeof input.command === "string" ? input.command : null;
|
|
623
|
-
if (!isWrite && !cmd)
|
|
624
|
-
return { action: "allow" };
|
|
625
|
-
const current = { id, cwd, toplevel: toplevel(cwd) };
|
|
626
|
-
const modes = cache.modes;
|
|
627
|
-
if (modes.no_foreign_worktree !== "off" || modes.claim_required_to_write !== "off") {
|
|
628
|
-
const target = isWrite ? absolutePath(input.file_path, cwd) : cwd;
|
|
629
|
-
const w = guardWrite(target, current, cache.worktrees, modes, isWrite ? "file" : "bash");
|
|
630
|
-
if (w.action !== "allow")
|
|
631
|
-
return w;
|
|
632
|
-
}
|
|
633
|
-
if (!cmd)
|
|
634
|
-
return { action: "allow" };
|
|
635
|
-
return guardBash(cmd, current, cache.sessions, now, modes);
|
|
636
|
-
}
|
|
637
|
-
var POLICY_CACHE_VERSION = 1, POLICY_CACHE_FILE = "policy.cache.json", digest = (body) => createHash("sha256").update(JSON.stringify(body)).digest("hex");
|
|
638
|
-
var init_policy = __esm(() => {
|
|
639
|
-
init_hooks();
|
|
640
|
-
init_rules();
|
|
641
|
-
});
|
|
642
1083
|
|
|
643
1084
|
// packages/core/src/pricing.ts
|
|
644
1085
|
var init_pricing = () => {};
|
|
645
1086
|
// packages/core/src/provenance.ts
|
|
646
1087
|
var init_provenance = () => {};
|
|
647
1088
|
// packages/core/src/quota.ts
|
|
648
|
-
var BURN_MIN_SPAN_MS;
|
|
1089
|
+
var BURN_MIN_SPAN_MS, BURN_LOOKBACK_MS;
|
|
649
1090
|
var init_quota = __esm(() => {
|
|
650
1091
|
BURN_MIN_SPAN_MS = 10 * 60000;
|
|
1092
|
+
BURN_LOOKBACK_MS = 60 * 60000;
|
|
651
1093
|
});
|
|
652
1094
|
// packages/core/src/security.ts
|
|
653
|
-
var init_security = () => {
|
|
1095
|
+
var init_security = __esm(() => {
|
|
1096
|
+
init_rules();
|
|
1097
|
+
});
|
|
654
1098
|
|
|
655
1099
|
// packages/core/src/stall.ts
|
|
656
1100
|
var init_stall = () => {};
|
|
@@ -749,6 +1193,7 @@ var init_src2 = __esm(() => {
|
|
|
749
1193
|
init_actor();
|
|
750
1194
|
init_adapters();
|
|
751
1195
|
init_hooks();
|
|
1196
|
+
init_agenthooks();
|
|
752
1197
|
init_art();
|
|
753
1198
|
init_audit();
|
|
754
1199
|
init_budget();
|
|
@@ -766,9 +1211,11 @@ var init_src2 = __esm(() => {
|
|
|
766
1211
|
init_lineage();
|
|
767
1212
|
init_mcphealth();
|
|
768
1213
|
init_memory();
|
|
1214
|
+
init_otel();
|
|
769
1215
|
init_outcomes();
|
|
770
1216
|
init_policy();
|
|
771
1217
|
init_pricing();
|
|
1218
|
+
init_prompt();
|
|
772
1219
|
init_provenance();
|
|
773
1220
|
init_quota();
|
|
774
1221
|
init_rules();
|
|
@@ -921,7 +1368,7 @@ if (out === null && base !== fallback) {
|
|
|
921
1368
|
out = await post(fallback);
|
|
922
1369
|
} catch {}
|
|
923
1370
|
}
|
|
924
|
-
if (out === null && event === "PreToolUse")
|
|
1371
|
+
if (out === null && (event === "PreToolUse" || event === "BeforeTool"))
|
|
925
1372
|
out = offline();
|
|
926
1373
|
process.stdout.write(`${out ?? "{}"}
|
|
927
1374
|
`);
|
|
@@ -934,6 +1381,18 @@ function offline() {
|
|
|
934
1381
|
if (!cache)
|
|
935
1382
|
return null;
|
|
936
1383
|
const raw = input ? JSON.parse(input) : {};
|
|
1384
|
+
const agent = detectAgent(raw);
|
|
1385
|
+
if (agent !== "claude-code") {
|
|
1386
|
+
for (const r of toToolRequests(agent, raw)) {
|
|
1387
|
+
const d = evaluateOffline(cache, { tool_name: r.tool, tool_input: r.input, session_id: r.sessionId, cwd: r.cwd }, gitToplevel);
|
|
1388
|
+
if (d.action !== "allow")
|
|
1389
|
+
return renderAgentDecision(agent, {
|
|
1390
|
+
...d,
|
|
1391
|
+
reason: `(daemon unreachable, policy-locked rule) ${d.reason}`
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
return null;
|
|
1395
|
+
}
|
|
937
1396
|
const d = evaluateOffline(cache, raw, gitToplevel);
|
|
938
1397
|
if (d.action === "allow")
|
|
939
1398
|
return null;
|