@wrongstack/core 0.276.4 → 0.277.1

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.
Files changed (68) hide show
  1. package/dist/{agent-bridge-D7A-eu3C.d.ts → agent-bridge-BFJ2ODzI.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-CEuw4ATz.d.ts → agent-subagent-runner-BimKihiC.d.ts} +7 -7
  3. package/dist/{brain-BLOyN5ZP.d.ts → brain-CCfuEOdp.d.ts} +1 -1
  4. package/dist/{compactor-DcBpaJsI.d.ts → compactor-D3BGw26y.d.ts} +1 -1
  5. package/dist/{config-Bf5mj-ad.d.ts → config-DAOjriz9.d.ts} +1 -1
  6. package/dist/{context-CLnUMW5g.d.ts → context-DPlA6kid.d.ts} +5 -6
  7. package/dist/coordination/index.d.ts +17 -17
  8. package/dist/coordination/index.js +38 -14
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/defaults/index.d.ts +27 -27
  11. package/dist/defaults/index.js +177 -93
  12. package/dist/defaults/index.js.map +1 -1
  13. package/dist/execution/index.d.ts +15 -15
  14. package/dist/execution/index.js +13 -1
  15. package/dist/execution/index.js.map +1 -1
  16. package/dist/execution/prompt-enhancer.d.ts +1 -1
  17. package/dist/extension/index.d.ts +6 -6
  18. package/dist/{global-mailbox-Iqfkgmwu.d.ts → global-mailbox-Dr4cTKqL.d.ts} +1 -1
  19. package/dist/{goal-store-DGb6b5Ed.d.ts → goal-store-C1uH4srH.d.ts} +1 -1
  20. package/dist/hq/index.d.ts +5 -5
  21. package/dist/{index-Cn0NOshr.d.ts → index-DJXj-dcr.d.ts} +5 -5
  22. package/dist/{index-L4RZN9jJ.d.ts → index-cMEmzCVN.d.ts} +23 -5
  23. package/dist/index.d.ts +41 -41
  24. package/dist/index.js +220 -111
  25. package/dist/index.js.map +1 -1
  26. package/dist/infrastructure/index.d.ts +6 -6
  27. package/dist/infrastructure/index.js +4 -1
  28. package/dist/infrastructure/index.js.map +1 -1
  29. package/dist/kernel/index.d.ts +11 -11
  30. package/dist/{mcp-servers-CuZGf9fI.d.ts → mcp-servers-CFb60-pH.d.ts} +3 -3
  31. package/dist/models/index.d.ts +5 -5
  32. package/dist/{models-registry-8XOdxWQu.d.ts → models-registry-5Ufn7f2m.d.ts} +1 -1
  33. package/dist/{multi-agent-coordinator-CiRtKVTk.d.ts → multi-agent-coordinator-CcrcncvG.d.ts} +1 -1
  34. package/dist/{null-fleet-bus-d9G-bVy9.d.ts → null-fleet-bus-C9KsYyrI.d.ts} +13 -6
  35. package/dist/observability/index.d.ts +2 -2
  36. package/dist/{path-resolver-BhIb6mtd.d.ts → path-resolver-CEeX9I7O.d.ts} +3 -3
  37. package/dist/{permission-BCbQDR2s.d.ts → permission-DbsGOA1C.d.ts} +7 -6
  38. package/dist/{permission-policy-C0ikndX_.d.ts → permission-policy-BpEea3r7.d.ts} +12 -14
  39. package/dist/{pipeline-Dl6XbfE7.d.ts → pipeline-CEjBjzVA.d.ts} +2 -2
  40. package/dist/{provider-model-resolve-B70epO19.d.ts → provider-model-resolve-BpfXp3Jj.d.ts} +3 -3
  41. package/dist/{provider-runner-DZ808MSM.d.ts → provider-runner-CnOSr5BN.d.ts} +3 -3
  42. package/dist/{retry-policy-Dt3_z8Aj.d.ts → retry-policy-Git9WF6d.d.ts} +1 -1
  43. package/dist/sdd/index.d.ts +9 -9
  44. package/dist/{secret-vault-BUJ2d1gB.d.ts → secret-vault-DDSMHqIm.d.ts} +1 -1
  45. package/dist/security/index.d.ts +5 -5
  46. package/dist/security/index.js +173 -94
  47. package/dist/security/index.js.map +1 -1
  48. package/dist/{selector-BCkWgdwy.d.ts → selector-Cq72C0Oy.d.ts} +1 -1
  49. package/dist/{session-event-bridge-CMvIO59_.d.ts → session-event-bridge-DG94B3Bk.d.ts} +1 -1
  50. package/dist/{session-reader-C8aiChUu.d.ts → session-reader-BzT-iMQT.d.ts} +1 -1
  51. package/dist/storage/index.d.ts +11 -11
  52. package/dist/{strategy-compactor-DI1OHVbB.d.ts → strategy-compactor-Bt_ZH6R0.d.ts} +10 -10
  53. package/dist/{todos-checkpoint-Ddd2CGr0.d.ts → todos-checkpoint-CH1pcua9.d.ts} +5 -5
  54. package/dist/{tool-executor-Bmd5Ygoo.d.ts → tool-executor-SVFq7IOR.d.ts} +9 -9
  55. package/dist/tools/index.d.ts +2 -2
  56. package/dist/tools/index.js +5 -6
  57. package/dist/tools/index.js.map +1 -1
  58. package/dist/types/index.d.ts +19 -19
  59. package/dist/types/index.js +13 -1
  60. package/dist/types/index.js.map +1 -1
  61. package/dist/utils/index.d.ts +17 -3
  62. package/dist/utils/index.js +5 -1
  63. package/dist/utils/index.js.map +1 -1
  64. package/dist/{worktree-manager-DBdl_5rs.d.ts → worktree-manager-C4YIf1Fa.d.ts} +1 -1
  65. package/instructions/leader-after-task.md +6 -0
  66. package/package.json +2 -2
  67. package/skills/output-standards/SKILL.md +1 -0
  68. package/skills/research-web/SKILL.md +1 -1
@@ -1,7 +1,7 @@
1
1
  import { randomBytes, createCipheriv, createDecipheriv, scryptSync } from 'crypto';
2
2
  import * as fs2 from 'fs';
3
3
  import * as fs from 'fs/promises';
4
- import * as path3 from 'path';
4
+ import * as path from 'path';
5
5
 
6
6
  // src/security/secret-scrubber.ts
7
7
  var PATTERNS = [
@@ -192,9 +192,9 @@ var DefaultSecretScrubber = class {
192
192
  }
193
193
  };
194
194
  async function atomicWrite(targetPath, content, opts = {}) {
195
- const dir = path3.dirname(targetPath);
195
+ const dir = path.dirname(targetPath);
196
196
  await fs.mkdir(dir, { recursive: true });
197
- const tmp = path3.join(dir, `.${path3.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
197
+ const tmp = path.join(dir, `.${path.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
198
198
  try {
199
199
  if (typeof content === "string") {
200
200
  await fs.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
@@ -748,7 +748,7 @@ var DefaultSecretVault = class {
748
748
  const oldVersion = this._keyVersion;
749
749
  const newKey = randomBytes(KEY_BYTES);
750
750
  const newVersion = oldVersion + 1;
751
- fs2.mkdirSync(path3.dirname(this.keyFile), { recursive: true });
751
+ fs2.mkdirSync(path.dirname(this.keyFile), { recursive: true });
752
752
  const passphrase = getVaultPassphrase();
753
753
  if (passphrase) {
754
754
  fs2.writeFileSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase), { mode: 384 });
@@ -832,7 +832,7 @@ var DefaultSecretVault = class {
832
832
  } catch (err) {
833
833
  if (err.code !== "ENOENT") throw err;
834
834
  }
835
- fs2.mkdirSync(path3.dirname(this.keyFile), { recursive: true });
835
+ fs2.mkdirSync(path.dirname(this.keyFile), { recursive: true });
836
836
  const key = randomBytes(KEY_BYTES);
837
837
  const passphrase = getVaultPassphrase();
838
838
  const initialBytes = passphrase ? wrapDataKey(key, 1, passphrase) : key;
@@ -931,7 +931,7 @@ async function rewriteConfigEncrypted(configPath, vault, patch) {
931
931
  }
932
932
  const merged = deepMerge(current, patch ?? {});
933
933
  const encrypted = encryptConfigSecrets(merged, vault);
934
- await fs.mkdir(path3.dirname(configPath), { recursive: true });
934
+ await fs.mkdir(path.dirname(configPath), { recursive: true });
935
935
  await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
936
936
  await restrictFilePermissions(configPath);
937
937
  }
@@ -1101,6 +1101,8 @@ var ToolCapabilities = {
1101
1101
  SHELL_ARBITRARY: "shell.arbitrary",
1102
1102
  /** Can execute a restricted set of commands (the `exec` tool). */
1103
1103
  SHELL_RESTRICTED: "shell.restricted",
1104
+ /** Can run a restricted project formatter/linter-style command. */
1105
+ SHELL_EXEC: "shell.exec",
1104
1106
  /** Can read files inside the project (and possibly outside via symlinks if not guarded). */
1105
1107
  FS_READ: "fs.read",
1106
1108
  /** Can write / modify / delete files inside the project. */
@@ -1109,6 +1111,20 @@ var ToolCapabilities = {
1109
1111
  FS_WRITE_OUTSIDE_PROJECT: "fs.write.outside-project",
1110
1112
  /** Can perform outbound network requests. */
1111
1113
  NET_OUTBOUND: "net.outbound",
1114
+ /** Can mutate in-memory session todos only. */
1115
+ SESSION_TODO: "session.todo",
1116
+ /** Can mutate in-memory session mode only. */
1117
+ SESSION_MODE: "session.mode",
1118
+ /** Can inspect registered tool metadata. */
1119
+ TOOL_META: "tool.meta",
1120
+ /** Can invoke arbitrary registered tools through a meta-tool. */
1121
+ TOOL_MUTATE_ANY: "tool.mutate.any",
1122
+ /** Can read persistent memory. */
1123
+ MEMORY_READ: "memory.read",
1124
+ /** Can write persistent memory. */
1125
+ MEMORY_WRITE: "memory.write",
1126
+ /** Can delete persistent memory. */
1127
+ MEMORY_DELETE: "memory.delete",
1112
1128
  /** Proxies tools from external MCP servers (unknown capability). */
1113
1129
  MCP_PROXY: "mcp.proxy",
1114
1130
  /** Can spawn or manage subagents / multi-agent tasks. */
@@ -1127,8 +1143,12 @@ var ToolCapabilities = {
1127
1143
  var DANGEROUS_FOR_SUBAGENTS = [
1128
1144
  ToolCapabilities.SHELL_ARBITRARY,
1129
1145
  ToolCapabilities.SHELL_RESTRICTED,
1146
+ ToolCapabilities.SHELL_EXEC,
1130
1147
  ToolCapabilities.FS_WRITE,
1131
1148
  ToolCapabilities.FS_WRITE_OUTSIDE_PROJECT,
1149
+ ToolCapabilities.TOOL_MUTATE_ANY,
1150
+ ToolCapabilities.MEMORY_WRITE,
1151
+ ToolCapabilities.MEMORY_DELETE,
1132
1152
  ToolCapabilities.MCP_PROXY,
1133
1153
  ToolCapabilities.SUBAGENT_SPAWN,
1134
1154
  ToolCapabilities.CONFIG_MUTATE,
@@ -1138,8 +1158,12 @@ var DANGEROUS_FOR_SUBAGENTS = [
1138
1158
  ToolCapabilities.FS_READ,
1139
1159
  ToolCapabilities.FS_WRITE,
1140
1160
  ToolCapabilities.NET_OUTBOUND,
1161
+ ToolCapabilities.SESSION_TODO,
1162
+ ToolCapabilities.TOOL_META,
1163
+ ToolCapabilities.MEMORY_READ,
1141
1164
  ToolCapabilities.SHELL_ARBITRARY,
1142
1165
  ToolCapabilities.SHELL_RESTRICTED,
1166
+ ToolCapabilities.SHELL_EXEC,
1143
1167
  ToolCapabilities.PACKAGE_INSTALL
1144
1168
  ];
1145
1169
  function hasDangerousCapabilityForSubagents(toolOrCaps) {
@@ -1198,19 +1222,51 @@ var LruCache = class {
1198
1222
  return this.store.size;
1199
1223
  }
1200
1224
  };
1201
- var DESTRUCTIVE_BASH_PATTERNS = [
1202
- /\bgit\s+(?:clean\s+-[^\s]*[xdf]|reset\s+--hard)\b/i,
1203
- /\b(?:drop|truncate)\s+(?:table|database|schema)\b/i,
1204
- /\bdelete\s+from\b/i,
1205
- /\b(?:mkfs|format|diskpart|shutdown|reboot)\b/i,
1206
- /\bchmod\s+-R\s+777\b/i,
1207
- /\bchown\s+-R\b/i,
1208
- /\b(?:curl|wget)\b.*\|\s*(?:sh|bash|zsh|pwsh|powershell)\b/i,
1209
- /\b(?:powershell|pwsh)\b.*(?:-encodedcommand|-enc)\b/i,
1210
- /:\(\)\s*\{\s*:\|:&\s*}\s*;/
1225
+ var CATASTROPHIC_PATTERNS = [
1226
+ /\b(?:mkfs(?:\.[a-z0-9]+)?|mke2fs|newfs)\b/i,
1227
+ // make a filesystem — wipes a partition
1228
+ /\bformat\s+[A-Za-z]:/i,
1229
+ // format C: — wipes a Windows volume
1230
+ /\bdiskpart\b/i,
1231
+ // Windows partition editor
1232
+ /\bdd\b[^|]*\bof=(?:\/dev\/|\\\\[.?]\\)/i,
1233
+ // dd writing straight to a raw device
1234
+ />\s*\/dev\/(?:sd|hd|nvme|disk|mapper|vd)/i,
1235
+ // redirect into a raw block device
1236
+ /:\(\)\s*\{\s*:\|:&\s*\}\s*;/
1237
+ // classic fork bomb
1211
1238
  ];
1212
- var PROJECT_ESCAPE_PATTERN = /(?:^|[\s"'])\.\.(?:[\\/]|$)/;
1213
- var ABSOLUTE_PATH_PATTERN = /(?:^|[\s"'])(?:~[\\/]|\/[A-Za-z0-9_.-]|[A-Za-z]:[\\/])/;
1239
+ var CATASTROPHIC_POSIX_ROOTS = /* @__PURE__ */ new Set([
1240
+ "/etc",
1241
+ "/usr",
1242
+ "/bin",
1243
+ "/sbin",
1244
+ "/lib",
1245
+ "/lib64",
1246
+ "/var",
1247
+ "/boot",
1248
+ "/dev",
1249
+ "/sys",
1250
+ "/proc",
1251
+ "/opt",
1252
+ "/root",
1253
+ "/home",
1254
+ "/srv",
1255
+ "/run",
1256
+ "/system",
1257
+ "/library",
1258
+ "/applications",
1259
+ "/users"
1260
+ ]);
1261
+ var CATASTROPHIC_WIN_SUBDIRS = /* @__PURE__ */ new Set([
1262
+ "windows",
1263
+ "system32",
1264
+ "winnt",
1265
+ "program files",
1266
+ "program files (x86)",
1267
+ "programdata",
1268
+ "users"
1269
+ ]);
1214
1270
  var SHELL_OPERATORS = /* @__PURE__ */ new Set(["&&", "||", "|", ";", ">", ">>", "<", "2>", "2>>"]);
1215
1271
  function getInputString(input, key) {
1216
1272
  if (!input || typeof input !== "object") return void 0;
@@ -1220,27 +1276,28 @@ function getInputString(input, key) {
1220
1276
  function pathLooksInsideProject(rawPath, projectRoot) {
1221
1277
  if (!projectRoot) return false;
1222
1278
  if (rawPath === "~" || rawPath.startsWith("~/") || rawPath.startsWith("~\\")) return false;
1223
- const resolved = path3.resolve(projectRoot, rawPath);
1224
- const relative2 = path3.relative(projectRoot, resolved);
1225
- return !!relative2 && !relative2.startsWith("..") && !path3.isAbsolute(relative2);
1279
+ const resolved = path.resolve(projectRoot, rawPath);
1280
+ const relative2 = path.relative(projectRoot, resolved);
1281
+ return !!relative2 && !relative2.startsWith("..") && !path.isAbsolute(relative2);
1226
1282
  }
1227
1283
  function tokenizeShell(command) {
1228
1284
  return command.match(/"[^"]*"|'[^']*'|\S+/g)?.map((token) => token.replace(/^['"]|['"]$/g, "")) ?? [];
1229
1285
  }
1230
- function pathTokenIsOutsideProject(token, projectRoot) {
1231
- if (!token || SHELL_OPERATORS.has(token) || token.startsWith("-")) return false;
1232
- if (token === "/" || token === "~" || token === "." || token === "..") return token !== ".";
1233
- if (token.includes("*")) return true;
1234
- if (token.startsWith("..") || token.includes("../") || token.includes("..\\")) return true;
1235
- if (path3.isAbsolute(token) || token.startsWith("~/")) return !pathLooksInsideProject(token, projectRoot);
1286
+ function isCatastrophicDeleteTarget(rawTarget) {
1287
+ const t = rawTarget.replace(/^['"]|['"]$/g, "").trim();
1288
+ if (!t) return false;
1289
+ if (t === "*" || t === "." || t === "./" || t === ".\\" || t === "./*" || t === ".\\*") return true;
1290
+ const s = t.replace(/[\\/]\*+$/, "").replace(/[\\/]+$/, "");
1291
+ if (s === "") return true;
1292
+ if (s === "~" || /^\$HOME$/i.test(s) || /^%USERPROFILE%$/i.test(s)) return true;
1293
+ if (/^[A-Za-z]:$/.test(s)) return true;
1294
+ const norm = s.toLowerCase().replace(/\\/g, "/");
1295
+ if (CATASTROPHIC_POSIX_ROOTS.has(norm)) return true;
1296
+ const win = norm.match(/^[a-z]:\/([^/]+)$/);
1297
+ if (win?.[1] && CATASTROPHIC_WIN_SUBDIRS.has(win[1])) return true;
1236
1298
  return false;
1237
1299
  }
1238
- function hasDangerousDeleteTarget(tokens, start, projectRoot) {
1239
- const targets = tokens.slice(start).filter((token) => !token.startsWith("-") && !SHELL_OPERATORS.has(token));
1240
- if (targets.length === 0) return true;
1241
- return targets.some((target) => pathTokenIsOutsideProject(target, projectRoot));
1242
- }
1243
- function hasDestructiveDelete(command, projectRoot) {
1300
+ function hasCatastrophicDelete(command) {
1244
1301
  const tokens = tokenizeShell(command);
1245
1302
  for (let i = 0; i < tokens.length; i++) {
1246
1303
  const token = tokens[i]?.toLowerCase();
@@ -1248,35 +1305,43 @@ function hasDestructiveDelete(command, projectRoot) {
1248
1305
  if (token === "rm") {
1249
1306
  const args = tokens.slice(i + 1);
1250
1307
  const recursiveOrForce = args.some(
1251
- (arg) => /^-[^-]*[rf]/i.test(arg) || arg === "--recursive" || arg === "--force"
1308
+ (arg) => /^-[^-]*[rf]/i.test(arg) || arg === "--recursive" || arg === "--force" || arg === "--no-preserve-root"
1252
1309
  );
1253
- if (recursiveOrForce && hasDangerousDeleteTarget(tokens, i + 1, projectRoot)) return true;
1310
+ if (!recursiveOrForce) continue;
1311
+ const targets = args.filter((arg) => !arg.startsWith("-") && !SHELL_OPERATORS.has(arg));
1312
+ if (targets.length === 0) return true;
1313
+ if (targets.some(isCatastrophicDeleteTarget)) return true;
1314
+ }
1315
+ if (token === "remove-item" || token === "ri") {
1316
+ const args = tokens.slice(i + 1);
1317
+ const recursive = args.some((arg) => {
1318
+ const a = arg.toLowerCase();
1319
+ return a === "-recurse" || a === "-force";
1320
+ });
1321
+ if (!recursive) continue;
1322
+ const targets = args.filter((arg) => !arg.startsWith("-") && !SHELL_OPERATORS.has(arg));
1323
+ if (targets.some(isCatastrophicDeleteTarget)) return true;
1254
1324
  }
1255
1325
  if (token === "rmdir" || token === "rd") {
1256
1326
  const args = tokens.slice(i + 1);
1257
1327
  const recursive = args.some((arg) => arg.toLowerCase() === "/s");
1258
- if (recursive && hasDangerousDeleteTarget(tokens, i + 1, projectRoot)) return true;
1328
+ if (!recursive) continue;
1329
+ const targets = args.filter((arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg));
1330
+ if (targets.some(isCatastrophicDeleteTarget)) return true;
1259
1331
  }
1260
1332
  if (token === "del" || token === "erase") {
1261
- if (hasDangerousDeleteTarget(tokens, i + 1, projectRoot)) return true;
1262
- }
1263
- if (token === "remove-item") {
1264
- const args = tokens.slice(i + 1).map((arg) => arg.toLowerCase());
1265
- const recursiveOrForce = args.includes("-recurse") || args.includes("-force");
1266
- if (recursiveOrForce && hasDangerousDeleteTarget(tokens, i + 1, projectRoot)) return true;
1333
+ const args = tokens.slice(i + 1);
1334
+ const targets = args.filter((arg) => !arg.startsWith("-") && !arg.startsWith("/") && !SHELL_OPERATORS.has(arg));
1335
+ if (targets.some(isCatastrophicDeleteTarget)) return true;
1267
1336
  }
1268
1337
  }
1269
1338
  return false;
1270
1339
  }
1271
- function isClearlyDestructiveBashCommand(command, projectRoot) {
1340
+ function isClearlyDestructiveBashCommand(command, _projectRoot) {
1272
1341
  const trimmed = command.trim();
1273
1342
  if (!trimmed) return false;
1274
- if (hasDestructiveDelete(trimmed, projectRoot)) return true;
1275
- if (DESTRUCTIVE_BASH_PATTERNS.some((pattern) => pattern.test(trimmed))) return true;
1276
- if (/\bcd\s+(?:\.\.|~|\/|[A-Za-z]:[\\/])/i.test(trimmed)) return true;
1277
- if (PROJECT_ESCAPE_PATTERN.test(trimmed)) return true;
1278
- const absolute = trimmed.match(ABSOLUTE_PATH_PATTERN)?.[0]?.trim().replace(/^['"]|['"]$/g, "");
1279
- if (absolute && !pathLooksInsideProject(absolute, projectRoot)) return true;
1343
+ if (hasCatastrophicDelete(trimmed)) return true;
1344
+ if (CATASTROPHIC_PATTERNS.some((pattern) => pattern.test(trimmed))) return true;
1280
1345
  return false;
1281
1346
  }
1282
1347
 
@@ -1284,6 +1349,15 @@ function isClearlyDestructiveBashCommand(command, projectRoot) {
1284
1349
  function matchesTrust(patterns, subject) {
1285
1350
  return patterns.includes(subject) || matchAny(patterns, subject);
1286
1351
  }
1352
+ function shellCommandLineFromInput(input) {
1353
+ const command = getInputString(input, "command") ?? getInputString(input, "cmd") ?? getInputString(input, "script");
1354
+ if (!command) return void 0;
1355
+ if (!input || typeof input !== "object") return command;
1356
+ const args = input["args"];
1357
+ if (!Array.isArray(args) || args.length === 0) return command;
1358
+ const renderedArgs = args.filter((arg) => typeof arg === "string").map((arg) => /\s/.test(arg) ? `"${arg.replace(/"/g, '\\"')}"` : arg);
1359
+ return [command, ...renderedArgs].join(" ");
1360
+ }
1287
1361
  var DefaultPermissionPolicy = class {
1288
1362
  policy = {};
1289
1363
  loaded = false;
@@ -1301,9 +1375,10 @@ var DefaultPermissionPolicy = class {
1301
1375
  */
1302
1376
  sessionDenied = /* @__PURE__ */ new Map();
1303
1377
  /**
1304
- * Session-scoped "soft trust" map. When the user presses 'a' (allow once),
1305
- * the tool+pattern is added here. If the LLM retries in the same session,
1306
- * we return auto directly without asking again.
1378
+ * Session-scoped one-shot "soft trust" map. When the user presses 'y', the
1379
+ * tool+pattern is added here so the immediate confirm re-run can proceed.
1380
+ * The entry is consumed on first use; future calls must ask again unless the
1381
+ * user chose persistent trust.
1307
1382
  *
1308
1383
  * Cleared on reload().
1309
1384
  */
@@ -1342,7 +1417,7 @@ var DefaultPermissionPolicy = class {
1342
1417
  this.trustFile = opts.trustFile;
1343
1418
  this.yolo = opts.yolo ?? false;
1344
1419
  this.yoloDestructive = opts.yoloDestructive ?? opts.forceAllYolo ?? false;
1345
- this.confirmDestructive = opts.confirmDestructive ?? false;
1420
+ this.confirmDestructive = true;
1346
1421
  this.promptDelegate = opts.promptDelegate;
1347
1422
  }
1348
1423
  /**
@@ -1373,9 +1448,9 @@ var DefaultPermissionPolicy = class {
1373
1448
  return this.yoloDestructive;
1374
1449
  }
1375
1450
  /** Toggle destructive confirmation gate (only meaningful when yolo is active). */
1376
- setConfirmDestructive(enabled) {
1377
- if (this.confirmDestructive !== enabled) this._evalCache.clear();
1378
- this.confirmDestructive = enabled;
1451
+ setConfirmDestructive(_enabled) {
1452
+ if (!this.confirmDestructive) this._evalCache.clear();
1453
+ this.confirmDestructive = true;
1379
1454
  }
1380
1455
  /** Check whether destructive confirmation gate is active. */
1381
1456
  getConfirmDestructive() {
@@ -1414,12 +1489,12 @@ var DefaultPermissionPolicy = class {
1414
1489
  return decision;
1415
1490
  }
1416
1491
  if (this.sessionAllowed.has(cacheKey)) {
1492
+ this.sessionAllowed.delete(cacheKey);
1417
1493
  const decision = {
1418
1494
  permission: "auto",
1419
1495
  source: "trust",
1420
- reason: "session soft allow (user pressed yes)"
1496
+ reason: "session one-shot allow (user pressed yes)"
1421
1497
  };
1422
- this._evalCache.set(cacheKey, decision);
1423
1498
  return decision;
1424
1499
  }
1425
1500
  if (entry?.deny && subject && matchesTrust(entry.deny, subject)) {
@@ -1432,6 +1507,29 @@ var DefaultPermissionPolicy = class {
1432
1507
  this._evalCache.set(cacheKey, decision);
1433
1508
  return decision;
1434
1509
  }
1510
+ if (this.yolo) {
1511
+ const destructive = this.isDestructiveYoloCall(tool, input, ctx);
1512
+ if (destructive) {
1513
+ if (this.promptDelegate) {
1514
+ const decision = await this.promptDelegate(tool, input, subject ?? tool.name);
1515
+ if (decision === "deny") {
1516
+ await this.deny({ tool: tool.name, pattern: subject ?? tool.name });
1517
+ return { permission: "deny", source: "user", reason: "user denied destructive yolo" };
1518
+ }
1519
+ return {
1520
+ permission: decision === "yes" || decision === "always" ? "auto" : "deny",
1521
+ source: "user",
1522
+ reason: "destructive yolo approved for this call"
1523
+ };
1524
+ }
1525
+ return {
1526
+ permission: "confirm",
1527
+ source: "yolo_destructive",
1528
+ riskTier: "destructive",
1529
+ reason: "destructive tool needs explicit approval in YOLO mode"
1530
+ };
1531
+ }
1532
+ }
1435
1533
  if (entry?.allow && subject && matchesTrust(entry.allow, subject)) {
1436
1534
  const decision = { permission: "auto", source: "trust", reason: "matched allow pattern" };
1437
1535
  this._evalCache.set(cacheKey, decision);
@@ -1443,29 +1541,6 @@ var DefaultPermissionPolicy = class {
1443
1541
  return decision;
1444
1542
  }
1445
1543
  if (this.yolo) {
1446
- if (this.confirmDestructive) {
1447
- const destructive = this.isDestructiveYoloCall(tool, input, ctx);
1448
- if (destructive) {
1449
- if (this.promptDelegate) {
1450
- const decision2 = await this.promptDelegate(tool, input, subject ?? tool.name);
1451
- if (decision2 === "always") {
1452
- await this.trust({ tool: tool.name, pattern: subject ?? tool.name });
1453
- return { permission: "auto", source: "user", reason: "destructive yolo always-allowed" };
1454
- }
1455
- if (decision2 === "deny") {
1456
- await this.deny({ tool: tool.name, pattern: subject ?? tool.name });
1457
- return { permission: "deny", source: "user", reason: "user denied destructive yolo" };
1458
- }
1459
- return { permission: decision2 === "yes" ? "auto" : "deny", source: "user" };
1460
- }
1461
- return {
1462
- permission: "confirm",
1463
- source: "yolo_destructive",
1464
- riskTier: "destructive",
1465
- reason: "destructive tool needs explicit approval (confirmDestructive is on)"
1466
- };
1467
- }
1468
- }
1469
1544
  const decision = { permission: "auto", source: "yolo" };
1470
1545
  this._evalCache.set(cacheKey, decision);
1471
1546
  return decision;
@@ -1482,7 +1557,8 @@ var DefaultPermissionPolicy = class {
1482
1557
  const hasWriteCap = hasCapability(tool, ToolCapabilities.FS_WRITE);
1483
1558
  const hasShellCap = hasCapability(tool, [
1484
1559
  ToolCapabilities.SHELL_ARBITRARY,
1485
- ToolCapabilities.SHELL_RESTRICTED
1560
+ ToolCapabilities.SHELL_RESTRICTED,
1561
+ ToolCapabilities.SHELL_EXEC
1486
1562
  ]);
1487
1563
  const hasInstallCap = hasCapability(tool, ToolCapabilities.PACKAGE_INSTALL);
1488
1564
  const hasConfigCap = hasCapability(tool, ToolCapabilities.CONFIG_MUTATE);
@@ -1510,27 +1586,30 @@ var DefaultPermissionPolicy = class {
1510
1586
  // Capability-based destructive check (preferred over name-based)
1511
1587
  isDestructiveByCapability(tool) {
1512
1588
  const caps = tool.capabilities ?? [];
1513
- if (caps.includes("shell.arbitrary")) return true;
1514
- if (caps.includes("fs.write")) return true;
1515
- if (caps.includes("fs.write.outside-project")) return true;
1589
+ if (caps.includes(ToolCapabilities.SHELL_ARBITRARY)) return true;
1590
+ if (caps.includes(ToolCapabilities.SHELL_RESTRICTED)) return true;
1591
+ if (caps.includes(ToolCapabilities.SHELL_EXEC)) return true;
1592
+ if (caps.includes(ToolCapabilities.FS_WRITE)) return true;
1593
+ if (caps.includes(ToolCapabilities.FS_WRITE_OUTSIDE_PROJECT)) return true;
1516
1594
  return false;
1517
1595
  }
1518
1596
  isDestructiveYoloCall(tool, input, ctx) {
1519
1597
  if (this.isDestructiveByCapability(tool)) {
1520
- if (tool.name === "bash") {
1521
- const command = getInputString(input, "command");
1522
- return command ? isClearlyDestructiveBashCommand(command, ctx.projectRoot) : true;
1598
+ const caps = tool.capabilities ?? [];
1599
+ if (caps.includes(ToolCapabilities.SHELL_ARBITRARY) || caps.includes(ToolCapabilities.SHELL_RESTRICTED) || caps.includes(ToolCapabilities.SHELL_EXEC)) {
1600
+ const command = shellCommandLineFromInput(input);
1601
+ return command ? isClearlyDestructiveBashCommand(command, ctx.projectRoot) : tool.riskTier === "destructive";
1523
1602
  }
1524
- if (tool.name === "write" || tool.name === "edit" || tool.name === "replace" || tool.name === "patch") {
1603
+ if (caps.includes(ToolCapabilities.FS_WRITE_OUTSIDE_PROJECT)) return true;
1604
+ if (caps.includes(ToolCapabilities.FS_WRITE)) {
1525
1605
  const targetPath = getInputString(input, "path") ?? getInputString(input, "file");
1526
1606
  if (!targetPath || !ctx.projectRoot) return false;
1527
1607
  return !pathLooksInsideProject(targetPath, ctx.projectRoot);
1528
1608
  }
1529
- return true;
1530
1609
  }
1531
- if (tool.name === "bash") {
1532
- const command = getInputString(input, "command");
1533
- return command ? isClearlyDestructiveBashCommand(command, ctx.projectRoot) : true;
1610
+ if (tool.name === "bash" || tool.name === "shell" || tool.name === "exec") {
1611
+ const command = shellCommandLineFromInput(input);
1612
+ return command ? isClearlyDestructiveBashCommand(command, ctx.projectRoot) : tool.riskTier === "destructive";
1534
1613
  }
1535
1614
  if (tool.name === "write" || tool.name === "edit" || tool.name === "replace" || tool.name === "patch") {
1536
1615
  const targetPath = getInputString(input, "path") ?? getInputString(input, "file");
@@ -1579,7 +1658,7 @@ var DefaultPermissionPolicy = class {
1579
1658
  this.sessionDenied.set(`${rule.tool}::${rule.pattern}`, true);
1580
1659
  this._evalCache.clear();
1581
1660
  }
1582
- /** Auto-approve this tool+pattern for the rest of this session (no trust file). */
1661
+ /** Auto-approve this tool+pattern once (no trust file). */
1583
1662
  allowOnce(rule) {
1584
1663
  this.sessionAllowed.set(`${rule.tool}::${rule.pattern}`, true);
1585
1664
  this._evalCache.clear();