@wrongstack/core 0.54.1 → 0.66.13

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 (63) hide show
  1. package/dist/{agent-bridge-Dnhw4tnM.d.ts → agent-bridge-D-j6OOBT.d.ts} +1 -1
  2. package/dist/agent-subagent-runner-DRZ9-NnR.d.ts +1042 -0
  3. package/dist/{compactor-Duhsf0ge.d.ts → compactor-D_ExJajC.d.ts} +1 -1
  4. package/dist/{config-bht0txXS.d.ts → config--86aHSln.d.ts} +112 -2
  5. package/dist/{context-DtPKqKYV.d.ts → context-y87Jc5ei.d.ts} +8 -8
  6. package/dist/coordination/index.d.ts +12 -12
  7. package/dist/coordination/index.js +87 -69
  8. package/dist/coordination/index.js.map +1 -1
  9. package/dist/defaults/index.d.ts +22 -22
  10. package/dist/defaults/index.js +365 -174
  11. package/dist/defaults/index.js.map +1 -1
  12. package/dist/{events-CbHTS4ZZ.d.ts → events-CIplI98R.d.ts} +20 -1
  13. package/dist/execution/index.d.ts +16 -385
  14. package/dist/execution/index.js +129 -61
  15. package/dist/execution/index.js.map +1 -1
  16. package/dist/extension/index.d.ts +7 -7
  17. package/dist/goal-store-C7jcumEh.d.ts +96 -0
  18. package/dist/index-DKUvyTvV.d.ts +560 -0
  19. package/dist/{index-ge5F2dnc.d.ts → index-b5uhfTSl.d.ts} +10 -8
  20. package/dist/index.d.ts +59 -33
  21. package/dist/index.js +1138 -733
  22. package/dist/index.js.map +1 -1
  23. package/dist/infrastructure/index.d.ts +6 -6
  24. package/dist/infrastructure/index.js +1 -1
  25. package/dist/infrastructure/index.js.map +1 -1
  26. package/dist/kernel/index.d.ts +9 -9
  27. package/dist/kernel/index.js +3 -1
  28. package/dist/kernel/index.js.map +1 -1
  29. package/dist/{mcp-servers-DE6gzBry.d.ts → mcp-servers-DwoNBf6r.d.ts} +3 -3
  30. package/dist/models/index.d.ts +2 -2
  31. package/dist/{multi-agent-coordinator-CjNX4uBD.d.ts → multi-agent-coordinator-CWnH-CiX.d.ts} +10 -2
  32. package/dist/{null-fleet-bus-BNiSlTna.d.ts → null-fleet-bus-VApKRxcp.d.ts} +6 -7
  33. package/dist/observability/index.d.ts +2 -2
  34. package/dist/parallel-eternal-engine-0UwotoSx.d.ts +483 -0
  35. package/dist/{path-resolver-Bax85amb.d.ts → path-resolver-DVkEcIw8.d.ts} +2 -2
  36. package/dist/{permission-Drm7LpPo.d.ts → permission-C1A5whY5.d.ts} +17 -1
  37. package/dist/{permission-policy-CU6sqWxF.d.ts → permission-policy-B2dK-T5N.d.ts} +28 -7
  38. package/dist/{plan-templates-CLRcurWN.d.ts → plan-templates-Bprrzhbu.d.ts} +4 -4
  39. package/dist/{provider-runner-BikCxGCx.d.ts → provider-runner-mXvXGSIw.d.ts} +3 -3
  40. package/dist/{retry-policy-Chtlvr5b.d.ts → retry-policy-CG3qvH_e.d.ts} +1 -1
  41. package/dist/sdd/index.d.ts +9 -9
  42. package/dist/sdd/index.js +59 -52
  43. package/dist/sdd/index.js.map +1 -1
  44. package/dist/security/index.d.ts +3 -3
  45. package/dist/security/index.js +144 -33
  46. package/dist/security/index.js.map +1 -1
  47. package/dist/{selector-BvSPdJj6.d.ts → selector-RvBR_YRW.d.ts} +1 -1
  48. package/dist/session-event-bridge-CDHxcmQU.d.ts +93 -0
  49. package/dist/{session-reader-BGhzMir4.d.ts → session-reader-BIpwM60D.d.ts} +1 -1
  50. package/dist/storage/index.d.ts +7 -6
  51. package/dist/{system-prompt-dtzV_mLm.d.ts → system-prompt-b61lOd49.d.ts} +32 -2
  52. package/dist/types/index.d.ts +23 -14
  53. package/dist/types/index.js +62 -6
  54. package/dist/types/index.js.map +1 -1
  55. package/dist/utils/index.d.ts +2 -2
  56. package/dist/utils/index.js.map +1 -1
  57. package/package.json +1 -1
  58. package/skills/multi-agent/SKILL.md +0 -2
  59. package/dist/agent-subagent-runner-By7jruZ_.d.ts +0 -182
  60. package/dist/goal-store-DwcTDDiX.d.ts +0 -188
  61. package/dist/index-CI271MjL.d.ts +0 -903
  62. package/dist/multi-agent-BmC_xiog.d.ts +0 -554
  63. package/dist/tool-executor-CgU0yWpB.d.ts +0 -110
@@ -1,7 +1,7 @@
1
1
  import * as crypto2 from 'crypto';
2
2
  import { randomBytes, randomUUID, createCipheriv, createDecipheriv, createHash } from 'crypto';
3
3
  import * as fsp from 'fs/promises';
4
- import * as path15 from 'path';
4
+ import * as path16 from 'path';
5
5
  import { isAbsolute, resolve } from 'path';
6
6
  import * as fs5 from 'fs';
7
7
  import * as os from 'os';
@@ -32,9 +32,9 @@ __export(atomic_write_exports, {
32
32
  ensureDir: () => ensureDir
33
33
  });
34
34
  async function atomicWrite(targetPath, content, opts = {}) {
35
- const dir = path15.dirname(targetPath);
35
+ const dir = path16.dirname(targetPath);
36
36
  await fsp.mkdir(dir, { recursive: true });
37
- const tmp = path15.join(dir, `.${path15.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
37
+ const tmp = path16.join(dir, `.${path16.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
38
38
  try {
39
39
  if (typeof content === "string") {
40
40
  await fsp.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
@@ -89,7 +89,7 @@ async function renameWithRetry(from, to) {
89
89
  if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
90
90
  throw err;
91
91
  }
92
- await new Promise((resolve4) => setTimeout(resolve4, delays[i]));
92
+ await new Promise((resolve5) => setTimeout(resolve5, delays[i]));
93
93
  }
94
94
  }
95
95
  throw lastErr;
@@ -169,7 +169,7 @@ var DefaultLogger = class _DefaultLogger {
169
169
  this.pretty = opts.pretty ?? true;
170
170
  if (this.file) {
171
171
  try {
172
- fs5.mkdirSync(path15.dirname(this.file), { recursive: true });
172
+ fs5.mkdirSync(path16.dirname(this.file), { recursive: true });
173
173
  } catch {
174
174
  }
175
175
  }
@@ -342,7 +342,7 @@ var DefaultSessionStore = class {
342
342
  }
343
343
  /** Join session ID to its absolute path within the store directory. */
344
344
  sessionPath(id, ext) {
345
- return path15.join(this.dir, `${id}${ext}`);
345
+ return path16.join(this.dir, `${id}${ext}`);
346
346
  }
347
347
  async ensureShardDir(_id) {
348
348
  await ensureDir(this.dir);
@@ -352,7 +352,7 @@ var DefaultSessionStore = class {
352
352
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
353
353
  const id = meta.id ?? `${startedAt.replace(/[:.]/g, "-")}-${randomBytes(2).toString("hex")}`;
354
354
  const shardDir = await this.ensureShardDir(id);
355
- const file = path15.join(shardDir, `${id}.jsonl`);
355
+ const file = path16.join(shardDir, `${id}.jsonl`);
356
356
  let handle;
357
357
  try {
358
358
  handle = await fsp.open(file, "a", 384);
@@ -445,7 +445,7 @@ var DefaultSessionStore = class {
445
445
  const ids = [];
446
446
  const entries = await fsp.readdir(dir, { withFileTypes: true });
447
447
  for (const entry of entries) {
448
- const full = path15.join(dir, entry.name);
448
+ const full = path16.join(dir, entry.name);
449
449
  if (entry.isDirectory()) {
450
450
  ids.push(...await this.collectSessionIds(full));
451
451
  } else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
@@ -601,7 +601,7 @@ var FileSessionWriter = class {
601
601
  this.meta = meta;
602
602
  this.events = events;
603
603
  this.resumed = opts.resumed ?? false;
604
- this.manifestFile = opts.dir ? path15.join(opts.dir, `${id}.summary.json`) : "";
604
+ this.manifestFile = opts.dir ? path16.join(opts.dir, `${id}.summary.json`) : "";
605
605
  this.filePath = opts.filePath ?? "";
606
606
  this.secretScrubber = opts.secretScrubber;
607
607
  this.summary = {
@@ -883,7 +883,7 @@ init_atomic_write();
883
883
  var QueueStore = class {
884
884
  file;
885
885
  constructor(opts) {
886
- this.file = path15.join(opts.dir, "queue.json");
886
+ this.file = path16.join(opts.dir, "queue.json");
887
887
  }
888
888
  async write(items) {
889
889
  if (items.length === 0) {
@@ -953,7 +953,7 @@ var DefaultAttachmentStore = class {
953
953
  let data = input.data;
954
954
  if (this.spoolDir && bytes >= this.spoolThreshold) {
955
955
  await fsp.mkdir(this.spoolDir, { recursive: true });
956
- spooledPath = path15.join(this.spoolDir, `${id}.bin`);
956
+ spooledPath = path16.join(this.spoolDir, `${id}.bin`);
957
957
  await atomicWrite(spooledPath, input.data, {
958
958
  encoding: input.kind === "image" ? "base64" : "utf8"
959
959
  });
@@ -1141,7 +1141,7 @@ ${body.trim()}`);
1141
1141
  async remember(text, scope = "project-memory") {
1142
1142
  return this.runSerialized(scope, async () => {
1143
1143
  const file = this.files[scope];
1144
- await ensureDir(path15.dirname(file));
1144
+ await ensureDir(path16.dirname(file));
1145
1145
  let existing = "";
1146
1146
  try {
1147
1147
  existing = await fsp.readFile(file, "utf8");
@@ -1765,7 +1765,7 @@ var RecoveryLock = class {
1765
1765
  sessionStore;
1766
1766
  probe;
1767
1767
  constructor(opts) {
1768
- this.file = path15.join(opts.dir, LOCK_FILE);
1768
+ this.file = path16.join(opts.dir, LOCK_FILE);
1769
1769
  this.pid = opts.pid ?? process.pid;
1770
1770
  this.hostname = opts.hostname ?? os.hostname();
1771
1771
  this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
@@ -1823,7 +1823,7 @@ var RecoveryLock = class {
1823
1823
  * null return before calling this.
1824
1824
  */
1825
1825
  async write(sessionId) {
1826
- await ensureDir(path15.dirname(this.file));
1826
+ await ensureDir(path16.dirname(this.file));
1827
1827
  const lock = {
1828
1828
  v: 1,
1829
1829
  sessionId,
@@ -3050,7 +3050,7 @@ var DefaultSecretVault = class {
3050
3050
  } catch (err) {
3051
3051
  if (err.code !== "ENOENT") throw err;
3052
3052
  }
3053
- fs5.mkdirSync(path15.dirname(this.keyFile), { recursive: true });
3053
+ fs5.mkdirSync(path16.dirname(this.keyFile), { recursive: true });
3054
3054
  const key = randomBytes(KEY_BYTES);
3055
3055
  try {
3056
3056
  fs5.writeFileSync(this.keyFile, key, { mode: 384, flag: "wx" });
@@ -3119,7 +3119,7 @@ async function rewriteConfigEncrypted(configPath, vault, patch) {
3119
3119
  }
3120
3120
  const merged = deepMerge2(current, patch ?? {});
3121
3121
  const encrypted = encryptConfigSecrets(merged, vault);
3122
- await fsp.mkdir(path15.dirname(configPath), { recursive: true });
3122
+ await fsp.mkdir(path16.dirname(configPath), { recursive: true });
3123
3123
  await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
3124
3124
  await restrictFilePermissions(configPath);
3125
3125
  }
@@ -3321,6 +3321,87 @@ function matchGlob(pattern, input) {
3321
3321
  function matchAny(patterns, input) {
3322
3322
  return patterns.some((p) => matchGlob(p, input));
3323
3323
  }
3324
+ var DESTRUCTIVE_BASH_PATTERNS = [
3325
+ /\bgit\s+(?:clean\s+-[^\s]*[xdf]|reset\s+--hard)\b/i,
3326
+ /\b(?:drop|truncate)\s+(?:table|database|schema)\b/i,
3327
+ /\bdelete\s+from\b/i,
3328
+ /\b(?:mkfs|format|diskpart|shutdown|reboot)\b/i,
3329
+ /\bchmod\s+-R\s+777\b/i,
3330
+ /\bchown\s+-R\b/i,
3331
+ /\b(?:curl|wget)\b.*\|\s*(?:sh|bash|zsh|pwsh|powershell)\b/i,
3332
+ /\b(?:powershell|pwsh)\b.*(?:-encodedcommand|-enc)\b/i,
3333
+ /:\(\)\s*\{\s*:\|:&\s*}\s*;/
3334
+ ];
3335
+ var PROJECT_ESCAPE_PATTERN = /(?:^|[\s"'])\.\.(?:[\\/]|$)/;
3336
+ var ABSOLUTE_PATH_PATTERN = /(?:^|[\s"'])(?:~[\\/]|\/[A-Za-z0-9_.-]|[A-Za-z]:[\\/])/;
3337
+ var SHELL_OPERATORS = /* @__PURE__ */ new Set(["&&", "||", "|", ";", ">", ">>", "<", "2>", "2>>"]);
3338
+ function getInputString(input, key) {
3339
+ if (!input || typeof input !== "object") return void 0;
3340
+ const value = input[key];
3341
+ return typeof value === "string" ? value : void 0;
3342
+ }
3343
+ function pathLooksInsideProject(rawPath, projectRoot) {
3344
+ if (!projectRoot) return false;
3345
+ if (rawPath === "~" || rawPath.startsWith("~/") || rawPath.startsWith("~\\")) return false;
3346
+ const resolved = path16.resolve(projectRoot, rawPath);
3347
+ const relative2 = path16.relative(projectRoot, resolved);
3348
+ return !!relative2 && !relative2.startsWith("..") && !path16.isAbsolute(relative2);
3349
+ }
3350
+ function tokenizeShell(command) {
3351
+ return command.match(/"[^"]*"|'[^']*'|\S+/g)?.map((token) => token.replace(/^['"]|['"]$/g, "")) ?? [];
3352
+ }
3353
+ function pathTokenIsOutsideProject(token, projectRoot) {
3354
+ if (!token || SHELL_OPERATORS.has(token) || token.startsWith("-")) return false;
3355
+ if (token === "/" || token === "~" || token === "." || token === "..") return token !== ".";
3356
+ if (token.includes("*")) return true;
3357
+ if (token.startsWith("..") || token.includes("../") || token.includes("..\\")) return true;
3358
+ if (path16.isAbsolute(token) || token.startsWith("~/")) return !pathLooksInsideProject(token, projectRoot);
3359
+ return false;
3360
+ }
3361
+ function hasDangerousDeleteTarget(tokens, start, projectRoot) {
3362
+ const targets = tokens.slice(start).filter((token) => !token.startsWith("-") && !SHELL_OPERATORS.has(token));
3363
+ if (targets.length === 0) return true;
3364
+ return targets.some((target) => pathTokenIsOutsideProject(target, projectRoot));
3365
+ }
3366
+ function hasDestructiveDelete(command, projectRoot) {
3367
+ const tokens = tokenizeShell(command);
3368
+ for (let i = 0; i < tokens.length; i++) {
3369
+ const token = tokens[i]?.toLowerCase();
3370
+ if (!token) continue;
3371
+ if (token === "rm") {
3372
+ const args = tokens.slice(i + 1);
3373
+ const recursiveOrForce = args.some(
3374
+ (arg) => /^-[^-]*[rf]/i.test(arg) || arg === "--recursive" || arg === "--force"
3375
+ );
3376
+ if (recursiveOrForce && hasDangerousDeleteTarget(tokens, i + 1, projectRoot)) return true;
3377
+ }
3378
+ if (token === "rmdir" || token === "rd") {
3379
+ const args = tokens.slice(i + 1);
3380
+ const recursive = args.some((arg) => arg.toLowerCase() === "/s");
3381
+ if (recursive && hasDangerousDeleteTarget(tokens, i + 1, projectRoot)) return true;
3382
+ }
3383
+ if (token === "del" || token === "erase") {
3384
+ if (hasDangerousDeleteTarget(tokens, i + 1, projectRoot)) return true;
3385
+ }
3386
+ if (token === "remove-item") {
3387
+ const args = tokens.slice(i + 1).map((arg) => arg.toLowerCase());
3388
+ const recursiveOrForce = args.includes("-recurse") || args.includes("-force");
3389
+ if (recursiveOrForce && hasDangerousDeleteTarget(tokens, i + 1, projectRoot)) return true;
3390
+ }
3391
+ }
3392
+ return false;
3393
+ }
3394
+ function isClearlyDestructiveBashCommand(command, projectRoot) {
3395
+ const trimmed = command.trim();
3396
+ if (!trimmed) return false;
3397
+ if (hasDestructiveDelete(trimmed, projectRoot)) return true;
3398
+ if (DESTRUCTIVE_BASH_PATTERNS.some((pattern) => pattern.test(trimmed))) return true;
3399
+ if (/\bcd\s+(?:\.\.|~|\/|[A-Za-z]:[\\/])/i.test(trimmed)) return true;
3400
+ if (PROJECT_ESCAPE_PATTERN.test(trimmed)) return true;
3401
+ const absolute = trimmed.match(ABSOLUTE_PATH_PATTERN)?.[0]?.trim().replace(/^['"]|['"]$/g, "");
3402
+ if (absolute && !pathLooksInsideProject(absolute, projectRoot)) return true;
3403
+ return false;
3404
+ }
3324
3405
 
3325
3406
  // src/security/permission-policy.ts
3326
3407
  var DefaultPermissionPolicy = class {
@@ -3328,7 +3409,9 @@ var DefaultPermissionPolicy = class {
3328
3409
  loaded = false;
3329
3410
  trustFile;
3330
3411
  yolo;
3331
- forceAllYolo;
3412
+ yoloDestructive;
3413
+ /** When true, destructive ops still require confirmation even in YOLO mode. */
3414
+ confirmDestructive;
3332
3415
  /**
3333
3416
  * Session-scoped "soft deny" map. When the user presses 'n' (block once),
3334
3417
  * the tool+pattern is added here. If the LLM retries in the same session,
@@ -3361,7 +3444,8 @@ var DefaultPermissionPolicy = class {
3361
3444
  constructor(opts) {
3362
3445
  this.trustFile = opts.trustFile;
3363
3446
  this.yolo = opts.yolo ?? false;
3364
- this.forceAllYolo = opts.forceAllYolo ?? false;
3447
+ this.yoloDestructive = opts.yoloDestructive ?? opts.forceAllYolo ?? false;
3448
+ this.confirmDestructive = opts.confirmDestructive ?? false;
3365
3449
  this.promptDelegate = opts.promptDelegate;
3366
3450
  }
3367
3451
  /**
@@ -3381,13 +3465,29 @@ var DefaultPermissionPolicy = class {
3381
3465
  getYolo() {
3382
3466
  return this.yolo;
3383
3467
  }
3384
- /** Toggle force-all-YOLO at runtime. */
3468
+ /** Toggle the destructive YOLO override at runtime. */
3469
+ setYoloDestructive(enabled) {
3470
+ this.yoloDestructive = enabled;
3471
+ }
3472
+ /** Check whether the destructive YOLO override is active. */
3473
+ getYoloDestructive() {
3474
+ return this.yoloDestructive;
3475
+ }
3476
+ /** Toggle destructive confirmation gate (only meaningful when yolo is active). */
3477
+ setConfirmDestructive(enabled) {
3478
+ this.confirmDestructive = enabled;
3479
+ }
3480
+ /** Check whether destructive confirmation gate is active. */
3481
+ getConfirmDestructive() {
3482
+ return this.confirmDestructive;
3483
+ }
3484
+ /** @deprecated Use `setYoloDestructive`. */
3385
3485
  setForceAllYolo(enabled) {
3386
- this.forceAllYolo = enabled;
3486
+ this.setYoloDestructive(enabled);
3387
3487
  }
3388
- /** Check whether force-all-YOLO is active. */
3488
+ /** @deprecated Use `getYoloDestructive`. */
3389
3489
  getForceAllYolo() {
3390
- return this.forceAllYolo;
3490
+ return this.getYoloDestructive();
3391
3491
  }
3392
3492
  async reload() {
3393
3493
  try {
@@ -3434,29 +3534,28 @@ var DefaultPermissionPolicy = class {
3434
3534
  return { permission: "auto", source: "trust" };
3435
3535
  }
3436
3536
  if (this.yolo) {
3437
- if (tool.riskTier === "destructive" && !this.forceAllYolo) {
3438
- if (this.promptDelegate) {
3439
- const decision = await this.promptDelegate(tool, input, subject ?? tool.name);
3440
- if (decision === "always") {
3441
- await this.trust({ tool: tool.name, pattern: subject ?? tool.name });
3442
- return {
3443
- permission: "auto",
3444
- source: "user",
3445
- reason: "destructive yolo always-allowed"
3446
- };
3447
- }
3448
- if (decision === "deny") {
3449
- await this.deny({ tool: tool.name, pattern: subject ?? tool.name });
3450
- return { permission: "deny", source: "user", reason: "user denied destructive yolo" };
3537
+ if (this.confirmDestructive) {
3538
+ const destructive = this.isDestructiveYoloCall(tool, input, ctx);
3539
+ if (destructive) {
3540
+ if (this.promptDelegate) {
3541
+ const decision = await this.promptDelegate(tool, input, subject ?? tool.name);
3542
+ if (decision === "always") {
3543
+ await this.trust({ tool: tool.name, pattern: subject ?? tool.name });
3544
+ return { permission: "auto", source: "user", reason: "destructive yolo always-allowed" };
3545
+ }
3546
+ if (decision === "deny") {
3547
+ await this.deny({ tool: tool.name, pattern: subject ?? tool.name });
3548
+ return { permission: "deny", source: "user", reason: "user denied destructive yolo" };
3549
+ }
3550
+ return { permission: decision === "yes" ? "auto" : "deny", source: "user" };
3451
3551
  }
3452
- return { permission: decision === "yes" ? "auto" : "deny", source: "user" };
3552
+ return {
3553
+ permission: "confirm",
3554
+ source: "yolo_destructive",
3555
+ riskTier: "destructive",
3556
+ reason: "destructive tool needs explicit approval (confirmDestructive is on)"
3557
+ };
3453
3558
  }
3454
- return {
3455
- permission: "confirm",
3456
- source: "yolo_destructive",
3457
- riskTier: "destructive",
3458
- reason: "destructive tool needs explicit approval even in yolo mode"
3459
- };
3460
3559
  }
3461
3560
  return { permission: "auto", source: "yolo" };
3462
3561
  }
@@ -3486,6 +3585,18 @@ var DefaultPermissionPolicy = class {
3486
3585
  }
3487
3586
  return { permission: "confirm", source: "default" };
3488
3587
  }
3588
+ isDestructiveYoloCall(tool, input, ctx) {
3589
+ if (tool.name === "bash") {
3590
+ const command = getInputString(input, "command");
3591
+ return command ? isClearlyDestructiveBashCommand(command, ctx.projectRoot) : true;
3592
+ }
3593
+ if (tool.name === "write" || tool.name === "edit" || tool.name === "replace" || tool.name === "patch") {
3594
+ const targetPath = getInputString(input, "path") ?? getInputString(input, "file");
3595
+ if (!targetPath || !ctx.projectRoot) return false;
3596
+ return !pathLooksInsideProject(targetPath, ctx.projectRoot);
3597
+ }
3598
+ return tool.riskTier === "destructive";
3599
+ }
3489
3600
  async trust(rule) {
3490
3601
  if (!this.loaded) await this.reload();
3491
3602
  const entry = this.policy[rule.tool] ?? {};
@@ -3803,7 +3914,7 @@ var DefaultRetryPolicy = class {
3803
3914
  };
3804
3915
 
3805
3916
  // src/execution/error-handler.ts
3806
- var CONTEXT_OVERFLOW_RE = /context|too long|tokens/i;
3917
+ var CONTEXT_OVERFLOW_RE = /context|too long|tokens|exceeds the context window|context window/i;
3807
3918
  function buildRecoveryStrategies(opts) {
3808
3919
  return [
3809
3920
  {
@@ -3811,7 +3922,7 @@ function buildRecoveryStrategies(opts) {
3811
3922
  compactor: opts?.compactor,
3812
3923
  async attempt(err, ctx) {
3813
3924
  if (!(err instanceof ProviderError)) return null;
3814
- if (err.status !== 413 && !CONTEXT_OVERFLOW_RE.test(err.message)) return null;
3925
+ if (err.status !== 413 && !isContextOverflowError(err)) return null;
3815
3926
  if (this.compactor) {
3816
3927
  try {
3817
3928
  const report = await this.compactor.compact(ctx, { aggressive: true });
@@ -3845,6 +3956,14 @@ function buildRecoveryStrategies(opts) {
3845
3956
  ];
3846
3957
  }
3847
3958
  var DEFAULT_RECOVERY_STRATEGIES = buildRecoveryStrategies();
3959
+ function isContextOverflowError(err) {
3960
+ return CONTEXT_OVERFLOW_RE.test([
3961
+ err.message,
3962
+ err.body?.message,
3963
+ err.body?.type,
3964
+ err.body?.raw
3965
+ ].filter(Boolean).join("\n"));
3966
+ }
3848
3967
  var DefaultErrorHandler = class {
3849
3968
  strategies;
3850
3969
  constructor(strategies = DEFAULT_RECOVERY_STRATEGIES) {
@@ -3861,7 +3980,7 @@ var DefaultErrorHandler = class {
3861
3980
  if (err.status === 429) return { kind: "rate_limit", retryable: true };
3862
3981
  if (err.status === 529) return { kind: "overloaded", retryable: true };
3863
3982
  if (err.status >= 500) return { kind: "server", retryable: true };
3864
- if (err.status === 413 || CONTEXT_OVERFLOW_RE.test(err.message)) {
3983
+ if (err.status === 413 || isContextOverflowError(err)) {
3865
3984
  return { kind: "context_overflow", retryable: false };
3866
3985
  }
3867
3986
  if (err.status >= 400) return { kind: "client", retryable: false };
@@ -3900,7 +4019,7 @@ var DefaultSkillLoader = class {
3900
4019
  const entries = await fsp.readdir(dir, { withFileTypes: true });
3901
4020
  for (const e of entries) {
3902
4021
  if (!e.isDirectory()) continue;
3903
- const skillFile = path15.join(dir, e.name, "SKILL.md");
4022
+ const skillFile = path16.join(dir, e.name, "SKILL.md");
3904
4023
  try {
3905
4024
  const raw = await fsp.readFile(skillFile, "utf8");
3906
4025
  const meta = parseFrontmatter(raw);
@@ -4312,8 +4431,8 @@ async function streamProviderToResponse(provider, req, signal, ctx, events) {
4312
4431
  try {
4313
4432
  await Promise.race([
4314
4433
  Promise.resolve(iter.return?.()),
4315
- new Promise((resolve4) => {
4316
- drainTimer = setTimeout(resolve4, 500);
4434
+ new Promise((resolve5) => {
4435
+ drainTimer = setTimeout(resolve5, 500);
4317
4436
  })
4318
4437
  ]);
4319
4438
  } finally {
@@ -4374,7 +4493,7 @@ async function runProviderWithRetry(opts) {
4374
4493
  description
4375
4494
  });
4376
4495
  }
4377
- await new Promise((resolve4, reject) => {
4496
+ await new Promise((resolve5, reject) => {
4378
4497
  let settled = false;
4379
4498
  const onAbort = () => {
4380
4499
  if (settled) return;
@@ -4387,7 +4506,7 @@ async function runProviderWithRetry(opts) {
4387
4506
  settled = true;
4388
4507
  clearTimeout(t);
4389
4508
  signal.removeEventListener("abort", onAbort);
4390
- resolve4();
4509
+ resolve5();
4391
4510
  }, delay);
4392
4511
  if (signal.aborted) {
4393
4512
  onAbort();
@@ -5488,11 +5607,11 @@ function validateAgainstSchema(value, schema) {
5488
5607
  walk3(value, schema, "", errors);
5489
5608
  return { ok: errors.length === 0, errors };
5490
5609
  }
5491
- function walk3(value, schema, path18, errors) {
5610
+ function walk3(value, schema, path19, errors) {
5492
5611
  if (schema.enum !== void 0) {
5493
5612
  if (!schema.enum.some((e) => deepEqual(e, value))) {
5494
5613
  errors.push({
5495
- path: path18 || "<root>",
5614
+ path: path19 || "<root>",
5496
5615
  message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
5497
5616
  });
5498
5617
  return;
@@ -5501,7 +5620,7 @@ function walk3(value, schema, path18, errors) {
5501
5620
  if (typeof schema.type === "string") {
5502
5621
  if (!checkType(value, schema.type)) {
5503
5622
  errors.push({
5504
- path: path18 || "<root>",
5623
+ path: path19 || "<root>",
5505
5624
  message: `expected ${schema.type}, got ${describeType(value)}`
5506
5625
  });
5507
5626
  return;
@@ -5511,19 +5630,19 @@ function walk3(value, schema, path18, errors) {
5511
5630
  const obj = value;
5512
5631
  for (const req of schema.required ?? []) {
5513
5632
  if (!(req in obj)) {
5514
- errors.push({ path: joinPath(path18, req), message: "required property missing" });
5633
+ errors.push({ path: joinPath(path19, req), message: "required property missing" });
5515
5634
  }
5516
5635
  }
5517
5636
  if (schema.properties) {
5518
5637
  for (const [key, subSchema] of Object.entries(schema.properties)) {
5519
5638
  if (key in obj) {
5520
- walk3(obj[key], subSchema, joinPath(path18, key), errors);
5639
+ walk3(obj[key], subSchema, joinPath(path19, key), errors);
5521
5640
  }
5522
5641
  }
5523
5642
  }
5524
5643
  }
5525
5644
  if (schema.type === "array" && Array.isArray(value) && schema.items) {
5526
- value.forEach((item, i) => walk3(item, schema.items, `${path18}[${i}]`, errors));
5645
+ value.forEach((item, i) => walk3(item, schema.items, `${path19}[${i}]`, errors));
5527
5646
  }
5528
5647
  }
5529
5648
  function checkType(value, type) {
@@ -5649,8 +5768,9 @@ var ToolExecutor = class {
5649
5768
  */
5650
5769
  async executeBatch(toolUses, ctx, strategy) {
5651
5770
  let budget = this.opts.perIterationOutputCapBytes ?? 1e5;
5652
- const runOne = async (use) => {
5771
+ const runOne = async (use0) => {
5653
5772
  const start = Date.now();
5773
+ let use = use0;
5654
5774
  const tool = this.registry.get(use.name);
5655
5775
  if (!tool) {
5656
5776
  const result = this.unknownToolResult(use, () => this.registry.list().map((t) => t.name));
@@ -5683,10 +5803,36 @@ Please call the tool again with arguments that match its inputSchema. You can us
5683
5803
  budget = this.decrementBudget(result, budget);
5684
5804
  return { result, tool, durationMs: Date.now() - start };
5685
5805
  }
5806
+ if (this.opts.hookRunner?.has("PreToolUse")) {
5807
+ const pre = await this.opts.hookRunner.preToolUse(tool.name, use.input, ctx);
5808
+ if (pre.block) {
5809
+ const result = this.blockedByHookResult(use, pre.reason);
5810
+ budget = this.decrementBudget(result, budget);
5811
+ return { result, tool, durationMs: Date.now() - start };
5812
+ }
5813
+ if (pre.input) {
5814
+ const reval = validateAgainstSchema(pre.input, tool.inputSchema);
5815
+ if (!reval.ok) {
5816
+ const errorDetails = reval.errors.map((e) => ` - ${e.path || "input"}: ${e.message}`).join("\n");
5817
+ const result = {
5818
+ type: "tool_result",
5819
+ tool_use_id: use.id,
5820
+ content: `A PreToolUse hook rewrote the arguments for "${tool.name}" into an invalid shape.
5821
+
5822
+ Validation errors:
5823
+ ${errorDetails}`,
5824
+ is_error: true
5825
+ };
5826
+ budget = this.decrementBudget(result, budget);
5827
+ return { result, tool, durationMs: Date.now() - start };
5828
+ }
5829
+ use = { ...use, input: pre.input };
5830
+ }
5831
+ }
5686
5832
  const decision = await this.opts.permissionPolicy.evaluate(tool, use.input, ctx);
5687
5833
  let effectivePermission = decision.permission;
5688
5834
  const policy = this.opts.permissionPolicy;
5689
- const yolo = policy.getYolo?.() === true || policy.getForceAllYolo?.() === true;
5835
+ const yolo = policy.getYolo?.() === true || policy.getYoloDestructive?.() === true || policy.getForceAllYolo?.() === true;
5690
5836
  if (toolDangerousCaps.length > 0 && effectivePermission === "auto" && !yolo) {
5691
5837
  effectivePermission = "confirm";
5692
5838
  }
@@ -5729,7 +5875,20 @@ Please call the tool again with arguments that match its inputSchema. You can us
5729
5875
  "tool.has_dangerous_capabilities": toolCapsForAudit.length > 0
5730
5876
  });
5731
5877
  try {
5732
- const result = await this.executeTool(tool, use, ctx, budget);
5878
+ let result = await this.executeTool(tool, use, ctx, budget);
5879
+ if (this.opts.hookRunner?.has("PostToolUse")) {
5880
+ const post = await this.opts.hookRunner.postToolUse(
5881
+ tool.name,
5882
+ use.input,
5883
+ { content: String(result.content), isError: !!result.is_error },
5884
+ ctx
5885
+ );
5886
+ if (post.additionalContext) {
5887
+ result = { ...result, content: `${result.content}
5888
+
5889
+ ${post.additionalContext}` };
5890
+ }
5891
+ }
5733
5892
  budget = this.decrementBudget(result, budget);
5734
5893
  span?.setAttribute("tool.is_error", !!result.is_error);
5735
5894
  span?.setAttribute(
@@ -5918,6 +6077,14 @@ ${excerpt}`;
5918
6077
  is_error: true
5919
6078
  };
5920
6079
  }
6080
+ blockedByHookResult(use, reason) {
6081
+ return {
6082
+ type: "tool_result",
6083
+ tool_use_id: use.id,
6084
+ content: `Tool "${use.name}" was blocked by a PreToolUse hook: ${reason ?? "no reason given"}`,
6085
+ is_error: true
6086
+ };
6087
+ }
5921
6088
  decrementBudget(result, budget) {
5922
6089
  const contentBytes = typeof result.content === "string" ? Buffer.byteLength(result.content, "utf8") : Buffer.byteLength(JSON.stringify(result.content), "utf8");
5923
6090
  return Math.max(0, budget - contentBytes);
@@ -6144,8 +6311,8 @@ var AutonomousRunner = class {
6144
6311
  init_atomic_write();
6145
6312
  var MAX_JOURNAL_ENTRIES = 500;
6146
6313
  function goalFilePath(projectRoot) {
6147
- const hash = createHash("sha256").update(path15.resolve(projectRoot)).digest("hex").slice(0, 12);
6148
- return path15.join(os.homedir(), ".wrongstack", "projects", hash, "goal.json");
6314
+ const hash = createHash("sha256").update(path16.resolve(projectRoot)).digest("hex").slice(0, 12);
6315
+ return path16.join(os.homedir(), ".wrongstack", "projects", hash, "goal.json");
6149
6316
  }
6150
6317
  async function loadGoal(filePath) {
6151
6318
  let raw;
@@ -6631,7 +6798,8 @@ ${recentJournal}` : "No prior iterations.",
6631
6798
  " \u2022 When this iteration's Task is finished (real artifact / passing",
6632
6799
  " test / applied diff / clean output), emit `[done]` on its own line.",
6633
6800
  " \u2022 Do not stop on the first obstacle \u2014 try at least 3 distinct",
6634
- " approaches before giving up. YOLO is active; no confirmations.",
6801
+ " approaches before giving up. YOLO is active for normal project work;",
6802
+ " destructive-gated confirmations still belong to the permission flow.",
6635
6803
  "",
6636
6804
  "2. UPDATE TODO STATE (when Source is `todo`)",
6637
6805
  " \u2022 Mark this todo `in_progress` via the todos tool before tool work.",
@@ -6760,7 +6928,7 @@ ${recentJournal}` : "No prior iterations.",
6760
6928
  }
6761
6929
  };
6762
6930
  function sleep(ms) {
6763
- return new Promise((resolve4) => setTimeout(resolve4, ms));
6931
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
6764
6932
  }
6765
6933
 
6766
6934
  // src/coordination/subagent-budget.ts
@@ -6976,12 +7144,12 @@ var SubagentBudget = class _SubagentBudget {
6976
7144
  if (!bus || !bus.hasListenerFor("budget.threshold_reached")) {
6977
7145
  return Promise.resolve("stop");
6978
7146
  }
6979
- return new Promise((resolve4) => {
7147
+ return new Promise((resolve5) => {
6980
7148
  let resolved = false;
6981
7149
  const respond = (d) => {
6982
7150
  if (resolved) return;
6983
7151
  resolved = true;
6984
- resolve4(d);
7152
+ resolve5(d);
6985
7153
  };
6986
7154
  const fallback = setTimeout(
6987
7155
  () => respond("stop"),
@@ -7052,15 +7220,22 @@ var SubagentBudget = class _SubagentBudget {
7052
7220
  void this.checkLimits();
7053
7221
  }
7054
7222
  /**
7055
- * Wall-clock budget check. Unlike other limits, timeout is always a hard stop
7056
- * wall-clock time cannot be "extended" by the coordinator, so it throws
7057
- * synchronously rather than entering the negotiation flow.
7223
+ * Wall-clock / idle budget check. Delegates to `checkLimits(elapsed)`, so
7224
+ * `timeout` and `idle_timeout` follow the SAME negotiation path as the other
7225
+ * kinds they are NOT a special-cased hard stop. This is deliberate: a
7226
+ * heartbeat-aware policy (see `attachAutoExtend` and `CollabSession`) grants
7227
+ * a timeout extension only while the agent is making progress and denies it
7228
+ * once the agent is genuinely stuck, which is safer than an unconditional
7229
+ * hard kill of a long-but-working agent. The runner translates the resulting
7230
+ * `BudgetThresholdSignal` decision (`extend` → patch limits in place,
7231
+ * `stop` → abort) just like every other kind.
7058
7232
  *
7059
- * Decision table:
7060
- * - no `onThreshold` handler → throw `BudgetExceededError`
7061
- * - `mode === 'sync'` → throw `BudgetExceededError`
7062
- * - `mode === 'auto'` + no listener → throw `BudgetExceededError`
7063
- * - `mode === 'auto'` + listener → throw `BudgetExceededError` (timeout is not extendable)
7233
+ * Decision table (same as `checkLimits`):
7234
+ * - no `onThreshold` handler → throw `BudgetExceededError` (hard stop)
7235
+ * - `mode === 'sync'` → throw `BudgetExceededError` (hard stop)
7236
+ * - `mode === 'auto'` + no listener → throw `BudgetExceededError` (no one to ask)
7237
+ * - `mode === 'auto'` + listener → throw `BudgetThresholdSignal` (negotiated;
7238
+ * a heartbeat-aware policy may extend the timeout)
7064
7239
  */
7065
7240
  checkTimeout() {
7066
7241
  if (this.startTime === null) return;
@@ -9926,7 +10101,10 @@ var NICKNAME_POOL = {
9926
10101
  "lavoisier": { name: "Lavoisier", domain: "chemistry" },
9927
10102
  "mendeleev": { name: "Mendeleev", domain: "chemistry" }
9928
10103
  };
9929
- var ALL_NICKNAMES = Object.values(NICKNAME_POOL);
10104
+ var ALL_NICKNAMES = Object.entries(NICKNAME_POOL);
10105
+ var NAME_TO_KEY = Object.fromEntries(
10106
+ ALL_NICKNAMES.map(([key, entry]) => [entry.name, key])
10107
+ );
9930
10108
  var DOMAIN_PREFERENCES = {
9931
10109
  "security": ["shannon", "turing", "lamarr", "stallman"],
9932
10110
  "bug-hunter": ["darwin", "curie", "feynman", "fermi"],
@@ -9959,17 +10137,23 @@ function assignNickname(role, used) {
9959
10137
  for (const key of preferences) {
9960
10138
  const entry = NICKNAME_POOL[key];
9961
10139
  if (entry && !used.has(key)) {
9962
- return `${entry.name} (${formatRole(role)})`;
10140
+ return { key, display: `${entry.name} (${formatRole(role)})` };
9963
10141
  }
9964
10142
  }
9965
- for (const entry of ALL_NICKNAMES) {
9966
- const key = Object.entries(NICKNAME_POOL).find(([, v]) => v.name === entry.name)?.[0];
9967
- if (key && !used.has(key)) {
9968
- return `${entry.name} (${formatRole(role)})`;
10143
+ for (const [key, entry] of ALL_NICKNAMES) {
10144
+ if (!used.has(key)) {
10145
+ return { key, display: `${entry.name} (${formatRole(role)})` };
9969
10146
  }
9970
10147
  }
9971
10148
  const counter = used.size + 1;
9972
- return `Scientist #${counter} (${formatRole(role)})`;
10149
+ return { key: `scientist-${counter}`, display: `Scientist #${counter} (${formatRole(role)})` };
10150
+ }
10151
+ function nicknameKeyFromDisplay(display) {
10152
+ const base = display.replace(/\s*\([^)]*\)\s*$/, "").trim();
10153
+ const key = NAME_TO_KEY[base];
10154
+ if (key) return key;
10155
+ const synthesized = base.match(/^Scientist #(\d+)$/);
10156
+ return synthesized ? `scientist-${synthesized[1]}` : void 0;
9973
10157
  }
9974
10158
  function formatRole(role) {
9975
10159
  return role.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
@@ -10056,11 +10240,10 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
10056
10240
  const name = subagent.name?.trim() ?? "";
10057
10241
  const isPlaceholder = name === "" || name.toLowerCase() === role.toLowerCase() || name === "subagent" || name === "adhoc" || name === "generic" || /^slot-/.test(name);
10058
10242
  if (!isPlaceholder) return subagent;
10059
- const nickname = assignNickname(role, this.usedNicknames);
10060
- const baseKey = nickname.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
10061
- this.usedNicknames.add(baseKey);
10062
- this.subagentNicknames.set(subagentId, baseKey);
10063
- return { ...subagent, name: nickname };
10243
+ const { key, display } = assignNickname(role, this.usedNicknames);
10244
+ this.usedNicknames.add(key);
10245
+ this.subagentNicknames.set(subagentId, key);
10246
+ return { ...subagent, name: display };
10064
10247
  }
10065
10248
  async spawn(subagent) {
10066
10249
  const id = subagent.id || randomUUID();
@@ -10208,7 +10391,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
10208
10391
  taskIds.map((id) => {
10209
10392
  const cached = this.completedResults.find((r) => r.taskId === id);
10210
10393
  if (cached) return cached;
10211
- return new Promise((resolve4, reject) => {
10394
+ return new Promise((resolve5, reject) => {
10212
10395
  const timeout = setTimeout(() => {
10213
10396
  this.off("task.completed", handler);
10214
10397
  reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
@@ -10217,7 +10400,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
10217
10400
  if (result.taskId === id) {
10218
10401
  clearTimeout(timeout);
10219
10402
  this.off("task.completed", handler);
10220
- resolve4(result);
10403
+ resolve5(result);
10221
10404
  }
10222
10405
  };
10223
10406
  this.on("task.completed", handler);
@@ -10312,23 +10495,32 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
10312
10495
  */
10313
10496
  drainPendingAsAborted(message) {
10314
10497
  const dropped = this.pendingTasks.splice(0, this.pendingTasks.length);
10315
- for (const t of dropped) {
10316
- const synthetic = {
10317
- subagentId: t.subagentId ?? "unassigned",
10318
- taskId: t.id,
10319
- status: "stopped",
10320
- error: {
10321
- kind: "aborted_by_parent",
10322
- message,
10323
- retryable: false
10324
- },
10325
- iterations: 0,
10326
- toolCalls: 0,
10327
- durationMs: 0
10328
- };
10329
- this.completedResults.push(synthetic);
10330
- this.emit("task.completed", { task: t, result: synthetic });
10331
- }
10498
+ for (const t of dropped) this.emitPendingAborted(t, message);
10499
+ }
10500
+ /**
10501
+ * Emit a synthetic `stopped`/`aborted_by_parent` completion for a single
10502
+ * PENDING task — one that was never counted in `inFlight`. This MUST bypass
10503
+ * `recordCompletion`: that path does `inFlight--`, which for a pending task
10504
+ * steals a decrement from a genuinely in-flight task and trips the underflow
10505
+ * guard — suppressing that real task's `task.completed` and hanging its
10506
+ * `awaitTasks()` caller. Pushes the result and fires the event directly.
10507
+ */
10508
+ emitPendingAborted(task, message) {
10509
+ const synthetic = {
10510
+ subagentId: task.subagentId ?? "unassigned",
10511
+ taskId: task.id,
10512
+ status: "stopped",
10513
+ error: {
10514
+ kind: "aborted_by_parent",
10515
+ message,
10516
+ retryable: false
10517
+ },
10518
+ iterations: 0,
10519
+ toolCalls: 0,
10520
+ durationMs: 0
10521
+ };
10522
+ this.completedResults.push(synthetic);
10523
+ this.emit("task.completed", { task, result: synthetic });
10332
10524
  }
10333
10525
  async runDispatched(subagentId, task) {
10334
10526
  const subagent = this.subagents.get(subagentId);
@@ -10589,20 +10781,10 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
10589
10781
  const orphaned = this.pendingTasks.filter((t) => t.subagentId === subagentId);
10590
10782
  this.pendingTasks = this.pendingTasks.filter((t) => t.subagentId !== subagentId);
10591
10783
  for (const t of orphaned) {
10592
- const synthetic = {
10593
- subagentId,
10594
- taskId: t.id,
10595
- status: "stopped",
10596
- error: {
10597
- kind: "aborted_by_parent",
10598
- message: `Subagent "${subagentId}" was removed while task "${t.id}" was pending`,
10599
- retryable: false
10600
- },
10601
- iterations: 0,
10602
- toolCalls: 0,
10603
- durationMs: 0
10604
- };
10605
- this.recordCompletion(synthetic);
10784
+ this.emitPendingAborted(
10785
+ t,
10786
+ `Subagent "${subagentId}" was removed while task "${t.id}" was pending`
10787
+ );
10606
10788
  }
10607
10789
  this.fleetBus?.emit({
10608
10790
  subagentId,
@@ -10720,7 +10902,7 @@ function providerErrorToSubagentError(err, message, cause) {
10720
10902
 
10721
10903
  // src/execution/parallel-eternal-engine.ts
10722
10904
  function sleep2(ms) {
10723
- return new Promise((resolve4) => setTimeout(resolve4, ms));
10905
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
10724
10906
  }
10725
10907
  var GOAL_COMPLETE_MARKER2 = /^\s*\[goal[_\s-]?complete\]\s*$/im;
10726
10908
  var ParallelEternalEngine = class {
@@ -10899,7 +11081,8 @@ ${recentJournal}` : "No prior iterations.",
10899
11081
  "\u2500\u2500 EXECUTION PROTOCOL \u2500\u2500",
10900
11082
  "\u2022 Execute the assigned task end-to-end using multiple tool calls.",
10901
11083
  "\u2022 Emit `[done]` on its own line when the task is complete.",
10902
- "\u2022 Do not ask for confirmation \u2014 YOLO is active.",
11084
+ "\u2022 Do not ask before routine in-project tool use \u2014 YOLO is active for normal project work.",
11085
+ "\u2022 If a destructive-gated confirmation appears, wait for the permission flow.",
10903
11086
  "\u2022 If the overall Mission is accomplished, emit `[GOAL_COMPLETE]` followed by a verification recipe.",
10904
11087
  "\u2022 Keep output concise \u2014 summarize findings, do not transcribe files."
10905
11088
  ].join("\n");
@@ -10979,6 +11162,7 @@ ${personaLine}Task: ${task}
10979
11162
  } catch {
10980
11163
  results = coordinator.results().slice(-taskIds.length);
10981
11164
  }
11165
+ await Promise.allSettled(subagentIds.map((id) => coordinator.remove(id)));
10982
11166
  const allSuccessful = results.length > 0 && results.every((r) => r.status === "success");
10983
11167
  const goalComplete = results.some(
10984
11168
  (r) => r.status === "success" && typeof r.result === "string" && GOAL_COMPLETE_MARKER2.test(r.result)
@@ -11156,8 +11340,10 @@ ${journalTail.join("\n")}` : "Recent journal: (none \u2014 this is the first ite
11156
11340
  " decide.",
11157
11341
  "",
11158
11342
  "### Operating principles",
11159
- "- YOLO is active. Do NOT ask for confirmation, do NOT propose",
11160
- " options. Pick the best path and execute it.",
11343
+ "- YOLO is active for normal project work. Proceed with routine",
11344
+ " in-project tool use without pre-confirming; pick the best path and execute it.",
11345
+ " If the permission system raises a destructive-gated confirmation, wait",
11346
+ " for that flow instead of trying to bypass it.",
11161
11347
  "- Use tools freely; multiple calls per turn are normal and expected.",
11162
11348
  "- When working on a todo, mark it `in_progress` via the todos tool",
11163
11349
  " before tool work and `completed` (or `cancelled` with a reason)",
@@ -11346,7 +11532,7 @@ var InMemoryAgentBridge = class {
11346
11532
  );
11347
11533
  }
11348
11534
  this.inflightGuards.add(correlationId);
11349
- return new Promise((resolve4, reject) => {
11535
+ return new Promise((resolve5, reject) => {
11350
11536
  const timer = setTimeout(() => {
11351
11537
  this.inflightGuards.delete(correlationId);
11352
11538
  this.pendingRequests.delete(correlationId);
@@ -11358,7 +11544,7 @@ var InMemoryAgentBridge = class {
11358
11544
  return;
11359
11545
  }
11360
11546
  this.pendingRequests.set(correlationId, {
11361
- resolve: resolve4,
11547
+ resolve: resolve5,
11362
11548
  reject,
11363
11549
  timer
11364
11550
  });
@@ -12036,6 +12222,10 @@ Emit each evaluation immediately. Do not wait until you have read all reports.`;
12036
12222
  return lines.join("\n");
12037
12223
  }
12038
12224
  cleanup() {
12225
+ if (this._timeoutTimer) {
12226
+ clearTimeout(this._timeoutTimer);
12227
+ this._timeoutTimer = void 0;
12228
+ }
12039
12229
  for (const dispose of this.disposers) dispose();
12040
12230
  this.disposers.length = 0;
12041
12231
  }
@@ -13153,18 +13343,20 @@ var Director = class _Director {
13153
13343
  if (e.subagentId.startsWith("bug-hunter-") || e.subagentId.startsWith("refactor-planner-") || e.subagentId.startsWith("critic-")) {
13154
13344
  return;
13155
13345
  }
13156
- if (payload.kind === "timeout") {
13346
+ if (payload.kind === "timeout" || payload.kind === "idle_timeout") {
13347
+ const heartbeatKey = `${e.subagentId}:${payload.kind}`;
13157
13348
  const progress = progressBySubagent.get(e.subagentId) ?? 0;
13158
- const lastProgress = lastTimeoutProgress.get(e.subagentId) ?? -1;
13349
+ const lastProgress = lastTimeoutProgress.get(heartbeatKey) ?? -1;
13159
13350
  if (progress <= lastProgress) {
13160
13351
  payload.deny();
13161
13352
  return;
13162
13353
  }
13163
- lastTimeoutProgress.set(e.subagentId, progress);
13354
+ lastTimeoutProgress.set(heartbeatKey, progress);
13355
+ const field = payload.kind === "timeout" ? "timeoutMs" : "idleTimeoutMs";
13164
13356
  setImmediate(() => {
13165
13357
  const newLimit = Math.min(Math.ceil(payload.limit * 2), 24 * 60 * 6e4);
13166
- this.recordExtension(e.subagentId, e.taskId, "timeout", newLimit);
13167
- payload.extend({ timeoutMs: newLimit });
13358
+ this.recordExtension(e.subagentId, e.taskId, payload.kind, newLimit);
13359
+ payload.extend({ [field]: newLimit });
13168
13360
  });
13169
13361
  return;
13170
13362
  }
@@ -13480,10 +13672,9 @@ var Director = class _Director {
13480
13672
  if (this.fleetManager) {
13481
13673
  this.fleetManager.assignNicknameAndRecord(config);
13482
13674
  } else {
13483
- config.name = assignNickname(role, this._usedNicknames);
13484
- this._usedNicknames.add(
13485
- config.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-")
13486
- );
13675
+ const { key, display } = assignNickname(role, this._usedNicknames);
13676
+ config.name = display;
13677
+ this._usedNicknames.add(key);
13487
13678
  }
13488
13679
  }
13489
13680
  result = await this.coordinator.spawn(config);
@@ -13658,7 +13849,7 @@ var Director = class _Director {
13658
13849
  })),
13659
13850
  usage: this.usage.snapshot()
13660
13851
  };
13661
- await fsp.mkdir(path15.dirname(this.manifestPath), { recursive: true });
13852
+ await fsp.mkdir(path16.dirname(this.manifestPath), { recursive: true });
13662
13853
  await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
13663
13854
  return this.manifestPath;
13664
13855
  }
@@ -13777,11 +13968,11 @@ var Director = class _Director {
13777
13968
  if (cached) return cached;
13778
13969
  const existing = this.taskWaiters.get(id);
13779
13970
  if (existing) return existing.promise;
13780
- let resolve4;
13971
+ let resolve5;
13781
13972
  const promise = new Promise((res) => {
13782
- resolve4 = res;
13973
+ resolve5 = res;
13783
13974
  });
13784
- this.taskWaiters.set(id, { promise, resolve: resolve4 });
13975
+ this.taskWaiters.set(id, { promise, resolve: resolve5 });
13785
13976
  return promise;
13786
13977
  })
13787
13978
  );
@@ -13805,8 +13996,8 @@ var Director = class _Director {
13805
13996
  } else {
13806
13997
  const entry = this.manifestEntries.get(subagentId);
13807
13998
  if (entry?.name) {
13808
- const nicknameKey = entry.name.split(" ")[0].toLowerCase().replace(/[^a-z0-9-]/g, "-");
13809
- this._usedNicknames.delete(nicknameKey);
13999
+ const nicknameKey = nicknameKeyFromDisplay(entry.name);
14000
+ if (nicknameKey) this._usedNicknames.delete(nicknameKey);
13810
14001
  }
13811
14002
  }
13812
14003
  this.manifestEntries.delete(subagentId);
@@ -13864,7 +14055,7 @@ var Director = class _Director {
13864
14055
  */
13865
14056
  async readSession(subagentId, tail) {
13866
14057
  if (!this.sessionsRoot) return null;
13867
- const filePath = path15.join(this.sessionsRoot, this.directorRunId, `${subagentId}.jsonl`);
14058
+ const filePath = path16.join(this.sessionsRoot, this.directorRunId, `${subagentId}.jsonl`);
13868
14059
  let raw;
13869
14060
  try {
13870
14061
  raw = await fsp.readFile(filePath, "utf8");
@@ -14166,7 +14357,7 @@ function createDelegateTool(opts) {
14166
14357
  subagentId
14167
14358
  });
14168
14359
  const dir = director;
14169
- const result = await new Promise((resolve4) => {
14360
+ const result = await new Promise((resolve5) => {
14170
14361
  let settled = false;
14171
14362
  let timer;
14172
14363
  const finish = (value) => {
@@ -14176,7 +14367,7 @@ function createDelegateTool(opts) {
14176
14367
  offTool();
14177
14368
  offIter();
14178
14369
  offProgress();
14179
- resolve4(value);
14370
+ resolve5(value);
14180
14371
  };
14181
14372
  const arm = () => {
14182
14373
  if (timer) clearTimeout(timer);
@@ -14320,13 +14511,13 @@ async function readSubagentPartial(opts, subagentId) {
14320
14511
  if (!opts.sessionsRoot) return void 0;
14321
14512
  const candidates = [];
14322
14513
  if (opts.directorRunId) {
14323
- candidates.push(path15.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
14514
+ candidates.push(path16.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
14324
14515
  } else {
14325
14516
  try {
14326
14517
  const entries = await fsp.readdir(opts.sessionsRoot, { withFileTypes: true });
14327
14518
  for (const entry of entries) {
14328
14519
  if (entry.isDirectory()) {
14329
- candidates.push(path15.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
14520
+ candidates.push(path16.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
14330
14521
  }
14331
14522
  }
14332
14523
  } catch {
@@ -14373,9 +14564,9 @@ function makeDirectorSessionFactory(opts) {
14373
14564
  let dir;
14374
14565
  if (opts.store) {
14375
14566
  store = opts.store;
14376
- dir = opts.sessionsRoot ? path15.join(opts.sessionsRoot, runId) : "(caller-managed)";
14567
+ dir = opts.sessionsRoot ? path16.join(opts.sessionsRoot, runId) : "(caller-managed)";
14377
14568
  } else if (opts.sessionsRoot) {
14378
- dir = path15.join(opts.sessionsRoot, runId);
14569
+ dir = path16.join(opts.sessionsRoot, runId);
14379
14570
  store = new DefaultSessionStore({ dir });
14380
14571
  } else {
14381
14572
  throw new Error("makeDirectorSessionFactory requires either `store` or `sessionsRoot`");
@@ -14579,7 +14770,7 @@ var DefaultModelsRegistry = class {
14579
14770
  this.overlay = opts.overlay;
14580
14771
  this.overlayUrl = opts.overlayUrl;
14581
14772
  this.overlayFile = opts.overlayFile;
14582
- this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ? path15.join(path15.dirname(opts.cacheFile), "models-overlay-cache.json") : void 0);
14773
+ this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ? path16.join(path16.dirname(opts.cacheFile), "models-overlay-cache.json") : void 0);
14583
14774
  }
14584
14775
  async load(opts = {}) {
14585
14776
  if (this.payload && !opts.force) return this.payload;
@@ -14792,7 +14983,7 @@ var DefaultModelsRegistry = class {
14792
14983
  }
14793
14984
  /** Used by `wstack models refresh` to expose where the cache lives. */
14794
14985
  cacheLocation() {
14795
- return path15.resolve(this.cacheFile);
14986
+ return path16.resolve(this.cacheFile);
14796
14987
  }
14797
14988
  };
14798
14989
  function hasEntries(payload) {
@@ -15079,7 +15270,7 @@ var DefaultModeStore = class {
15079
15270
  }
15080
15271
  async loadActiveMode() {
15081
15272
  try {
15082
- const configPath = path15.join(this.configDir, "mode.json");
15273
+ const configPath = path16.join(this.configDir, "mode.json");
15083
15274
  const content = await fsp.readFile(configPath, "utf8");
15084
15275
  const data = JSON.parse(content);
15085
15276
  this.activeModeId = data.activeMode ?? null;
@@ -15090,7 +15281,7 @@ var DefaultModeStore = class {
15090
15281
  async saveActiveMode() {
15091
15282
  try {
15092
15283
  await fsp.mkdir(this.configDir, { recursive: true });
15093
- const configPath = path15.join(this.configDir, "mode.json");
15284
+ const configPath = path16.join(this.configDir, "mode.json");
15094
15285
  await atomicWrite(
15095
15286
  configPath,
15096
15287
  JSON.stringify({ activeMode: this.activeModeId }, null, 2)
@@ -15105,11 +15296,11 @@ async function loadProjectModes(modesDir) {
15105
15296
  const entries = await fsp.readdir(modesDir);
15106
15297
  for (const entry of entries) {
15107
15298
  if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
15108
- const filePath = path15.join(modesDir, entry);
15299
+ const filePath = path16.join(modesDir, entry);
15109
15300
  const stat5 = await fsp.stat(filePath);
15110
15301
  if (!stat5.isFile()) continue;
15111
15302
  const content = await fsp.readFile(filePath, "utf8");
15112
- const id = path15.basename(entry, path15.extname(entry));
15303
+ const id = path16.basename(entry, path16.extname(entry));
15113
15304
  modes.push({
15114
15305
  id,
15115
15306
  name: id.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
@@ -15125,7 +15316,7 @@ async function loadProjectModes(modesDir) {
15125
15316
  async function loadUserModes(modesDir) {
15126
15317
  const modes = [];
15127
15318
  try {
15128
- const manifestPath = path15.join(modesDir, "modes.json");
15319
+ const manifestPath = path16.join(modesDir, "modes.json");
15129
15320
  const content = await fsp.readFile(manifestPath, "utf8");
15130
15321
  const manifest = JSON.parse(content);
15131
15322
  for (const mode of manifest.modes) {
@@ -16042,7 +16233,7 @@ var SpecStore = class {
16042
16233
  indexPath;
16043
16234
  constructor(opts) {
16044
16235
  this.baseDir = opts.baseDir;
16045
- this.indexPath = path15.join(this.baseDir, "_index.json");
16236
+ this.indexPath = path16.join(this.baseDir, "_index.json");
16046
16237
  }
16047
16238
  async save(spec) {
16048
16239
  await ensureDir(this.baseDir);
@@ -16111,7 +16302,7 @@ var SpecStore = class {
16111
16302
  return updated;
16112
16303
  }
16113
16304
  filePath(id) {
16114
- return path15.join(this.baseDir, `${id}.json`);
16305
+ return path16.join(this.baseDir, `${id}.json`);
16115
16306
  }
16116
16307
  async readIndex() {
16117
16308
  try {
@@ -16168,7 +16359,7 @@ var TaskGraphStore = class {
16168
16359
  indexPath;
16169
16360
  constructor(opts) {
16170
16361
  this.baseDir = opts.baseDir;
16171
- this.indexPath = path15.join(this.baseDir, "_index.json");
16362
+ this.indexPath = path16.join(this.baseDir, "_index.json");
16172
16363
  }
16173
16364
  async save(graph) {
16174
16365
  await ensureDir(this.baseDir);
@@ -16206,7 +16397,7 @@ var TaskGraphStore = class {
16206
16397
  }
16207
16398
  }
16208
16399
  filePath(id) {
16209
- return path15.join(this.baseDir, `${id}.json`);
16400
+ return path16.join(this.baseDir, `${id}.json`);
16210
16401
  }
16211
16402
  async readIndex() {
16212
16403
  try {
@@ -16451,9 +16642,9 @@ var AISpecBuilder = class {
16451
16642
  if (!this.sessionPath) return;
16452
16643
  try {
16453
16644
  const fsp16 = await import('fs/promises');
16454
- const path18 = await import('path');
16645
+ const path19 = await import('path');
16455
16646
  const { atomicWrite: atomicWrite2 } = await Promise.resolve().then(() => (init_atomic_write(), atomic_write_exports));
16456
- await fsp16.mkdir(path18.dirname(this.sessionPath), { recursive: true });
16647
+ await fsp16.mkdir(path19.dirname(this.sessionPath), { recursive: true });
16457
16648
  await atomicWrite2(this.sessionPath, JSON.stringify(this.session, null, 2));
16458
16649
  } catch {
16459
16650
  }
@@ -17163,15 +17354,15 @@ function computeCriticalPath(graph, _topoOrder, blockedByMap) {
17163
17354
  maxId = id;
17164
17355
  }
17165
17356
  }
17166
- const path18 = [];
17357
+ const path19 = [];
17167
17358
  let current = maxId;
17168
17359
  const visited = /* @__PURE__ */ new Set();
17169
17360
  while (current && !visited.has(current)) {
17170
17361
  visited.add(current);
17171
- path18.unshift(current);
17362
+ path19.unshift(current);
17172
17363
  current = prev.get(current) ?? null;
17173
17364
  }
17174
- return path18;
17365
+ return path19;
17175
17366
  }
17176
17367
  function computeParallelGroups(graph, blockedByMap) {
17177
17368
  const groups = [];
@@ -17721,7 +17912,7 @@ var SddParallelRun = class {
17721
17912
  "\u2500\u2500 EXECUTION PROTOCOL \u2500\u2500",
17722
17913
  "\u2022 Execute the assigned SDD task end-to-end using multiple tool calls.",
17723
17914
  "\u2022 Mark the task [done] in the tracker when complete.",
17724
- "\u2022 Do not ask for confirmation.",
17915
+ "\u2022 Do not ask before routine in-project tool use; if a permission gate appears, wait for that flow.",
17725
17916
  "\u2022 Keep output concise \u2014 summarize changes, do not transcribe files."
17726
17917
  ].join("\n");
17727
17918
  const spawns = subagentIds.map(
@@ -17964,9 +18155,9 @@ var DefaultHealthRegistry = class {
17964
18155
  }
17965
18156
  async runOne(check) {
17966
18157
  let timer = null;
17967
- const timeout = new Promise((resolve4) => {
18158
+ const timeout = new Promise((resolve5) => {
17968
18159
  timer = setTimeout(
17969
- () => resolve4({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
18160
+ () => resolve5({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
17970
18161
  this.timeoutMs
17971
18162
  );
17972
18163
  });
@@ -18149,7 +18340,7 @@ async function startMetricsServer(opts) {
18149
18340
  const tls = opts.tls;
18150
18341
  const useHttps = !!(tls?.cert && tls?.key);
18151
18342
  const host = opts.host ?? "127.0.0.1";
18152
- const path18 = opts.path ?? "/metrics";
18343
+ const path19 = opts.path ?? "/metrics";
18153
18344
  const healthPath = opts.healthPath ?? "/healthz";
18154
18345
  const healthRegistry = opts.healthRegistry;
18155
18346
  const listener = (req, res) => {
@@ -18159,7 +18350,7 @@ async function startMetricsServer(opts) {
18159
18350
  return;
18160
18351
  }
18161
18352
  const url = req.url.split("?")[0];
18162
- if (url === path18) {
18353
+ if (url === path19) {
18163
18354
  let body;
18164
18355
  try {
18165
18356
  body = renderPrometheus(opts.sink.snapshot());
@@ -18205,14 +18396,14 @@ async function startMetricsServer(opts) {
18205
18396
  const { createServer } = await import('http');
18206
18397
  server = createServer(listener);
18207
18398
  }
18208
- await new Promise((resolve4, reject) => {
18399
+ await new Promise((resolve5, reject) => {
18209
18400
  const onError = (err) => {
18210
18401
  server.off("listening", onListening);
18211
18402
  reject(err);
18212
18403
  };
18213
18404
  const onListening = () => {
18214
18405
  server.off("error", onError);
18215
- resolve4();
18406
+ resolve5();
18216
18407
  };
18217
18408
  server.once("error", onError);
18218
18409
  server.once("listening", onListening);
@@ -18223,9 +18414,9 @@ async function startMetricsServer(opts) {
18223
18414
  const protocol = useHttps ? "https" : "http";
18224
18415
  return {
18225
18416
  port: boundPort,
18226
- url: `${protocol}://${host}:${boundPort}${path18}`,
18227
- close: () => new Promise((resolve4, reject) => {
18228
- server.close((err) => err ? reject(err) : resolve4());
18417
+ url: `${protocol}://${host}:${boundPort}${path19}`,
18418
+ close: () => new Promise((resolve5, reject) => {
18419
+ server.close((err) => err ? reject(err) : resolve5());
18229
18420
  })
18230
18421
  };
18231
18422
  }
@@ -18774,7 +18965,7 @@ var context7Server = () => ({
18774
18965
  name: "context7",
18775
18966
  description: "Codebase-aware documentation and Q&A (context7.ai)",
18776
18967
  transport: "streamable-http",
18777
- url: "https://server.context7.ai/mcp",
18968
+ url: "https://mcp.context7.com/mcp",
18778
18969
  permission: "confirm"
18779
18970
  });
18780
18971
  var braveSearchServer = () => ({