@wrongstack/core 0.257.2 → 0.264.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.
Files changed (82) hide show
  1. package/dist/{agent-bridge-BrxWHEOm.d.ts → agent-bridge-D8sa1vtv.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-US741uBH.d.ts → agent-subagent-runner-c9DLkaas.d.ts} +31 -9
  3. package/dist/{brain-TjEEwSpw.d.ts → brain-O1IdKPaK.d.ts} +59 -2
  4. package/dist/{compactor-C5sT4U7I.d.ts → compactor-BBy0rCtB.d.ts} +1 -1
  5. package/dist/{config-DuAu23zm.d.ts → config-Dz2F3H2K.d.ts} +7 -1
  6. package/dist/{context-CGdgA0q6.d.ts → context-BGSpZNSE.d.ts} +33 -0
  7. package/dist/coordination/index.d.ts +1681 -15
  8. package/dist/coordination/index.js +2826 -405
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/defaults/index.d.ts +25 -25
  11. package/dist/defaults/index.js +2258 -1433
  12. package/dist/defaults/index.js.map +1 -1
  13. package/dist/dispatcher-types.d-BBeXBQgS.d.ts +66 -0
  14. package/dist/execution/index.d.ts +15 -15
  15. package/dist/execution/index.js +502 -398
  16. package/dist/execution/index.js.map +1 -1
  17. package/dist/execution/prompt-enhancer.d.ts +2 -2
  18. package/dist/execution/prompt-enhancer.js +7 -1
  19. package/dist/execution/prompt-enhancer.js.map +1 -1
  20. package/dist/extension/index.d.ts +6 -6
  21. package/dist/extension/index.js.map +1 -1
  22. package/dist/{goal-preamble-CznHTZqP.d.ts → goal-preamble-DzjFuN3p.d.ts} +21 -9
  23. package/dist/{goal-store-CV9Yz2X_.d.ts → goal-store-CxWmCGbH.d.ts} +4 -2
  24. package/dist/{index-CC0Mcm05.d.ts → index-CYIQrXVF.d.ts} +8 -8
  25. package/dist/{index-CitPrI3a.d.ts → index-CbLSI66_.d.ts} +5 -5
  26. package/dist/index.d.ts +50 -94
  27. package/dist/index.js +16009 -12406
  28. package/dist/index.js.map +1 -1
  29. package/dist/infrastructure/index.d.ts +6 -6
  30. package/dist/kernel/index.d.ts +9 -9
  31. package/dist/kernel/index.js +6 -1
  32. package/dist/kernel/index.js.map +1 -1
  33. package/dist/{llm-selector-CJ4SyAFE.d.ts → llm-selector-DzxuZnNz.d.ts} +2 -2
  34. package/dist/{mcp-servers-D8YnLaEp.d.ts → mcp-servers-DC4QRPUI.d.ts} +3 -3
  35. package/dist/models/index.d.ts +5 -5
  36. package/dist/models/index.js +6 -1
  37. package/dist/models/index.js.map +1 -1
  38. package/dist/{models-registry-ByZCdFuQ.d.ts → models-registry-B_siPxqN.d.ts} +1 -1
  39. package/dist/{multi-agent-coordinator-DqTUEAeC.d.ts → multi-agent-coordinator-CK5Jdj9K.d.ts} +2 -2
  40. package/dist/{null-fleet-bus-B5mfTJXT.d.ts → null-fleet-bus-DgvD4SCO.d.ts} +13 -8
  41. package/dist/observability/index.d.ts +2 -2
  42. package/dist/observability/index.js +8 -3
  43. package/dist/observability/index.js.map +1 -1
  44. package/dist/{parallel-eternal-engine-C0juOszP.d.ts → parallel-eternal-engine-bK0JQBR_.d.ts} +13 -9
  45. package/dist/{path-resolver-CbkT-RMU.d.ts → path-resolver-BPEDlN38.d.ts} +3 -3
  46. package/dist/{permission-CwBBpCoF.d.ts → permission-4yvGmMRB.d.ts} +1 -1
  47. package/dist/{permission-policy-B8rSu908.d.ts → permission-policy-C6XpsBOy.d.ts} +3 -2
  48. package/dist/{pipeline-JG8XoudC.d.ts → pipeline-CXCeMz8J.d.ts} +58 -3
  49. package/dist/{plan-templates-DPiQMkBz.d.ts → plan-templates-BvzRBkJc.d.ts} +32 -11
  50. package/dist/{provider-runner-hM7EXlLI.d.ts → provider-runner-C5aQpDWE.d.ts} +3 -3
  51. package/dist/{retry-policy-Tg7LXkoK.d.ts → retry-policy-CFhdtRzz.d.ts} +1 -1
  52. package/dist/sdd/index.d.ts +8 -8
  53. package/dist/sdd/index.js +59 -31
  54. package/dist/sdd/index.js.map +1 -1
  55. package/dist/{secret-vault-gxtFZYBt.d.ts → secret-vault-CxiVLbt1.d.ts} +1 -1
  56. package/dist/security/index.d.ts +4 -4
  57. package/dist/security/index.js +238 -204
  58. package/dist/security/index.js.map +1 -1
  59. package/dist/{selector-DWsqVjGf.d.ts → selector-gIuhRTkN.d.ts} +1 -1
  60. package/dist/{session-event-bridge-BAFWdgQ3.d.ts → session-event-bridge-DkvvrpDt.d.ts} +8 -2
  61. package/dist/{session-reader-CqRvaL5v.d.ts → session-reader-KdfVwkKP.d.ts} +1 -1
  62. package/dist/skills/index.js +67 -64
  63. package/dist/skills/index.js.map +1 -1
  64. package/dist/storage/index.d.ts +50 -22
  65. package/dist/storage/index.js +1654 -525
  66. package/dist/storage/index.js.map +1 -1
  67. package/dist/tools/index.d.ts +57 -0
  68. package/dist/tools/index.js +411 -0
  69. package/dist/tools/index.js.map +1 -0
  70. package/dist/types/index.d.ts +19 -19
  71. package/dist/types/index.js +711 -694
  72. package/dist/types/index.js.map +1 -1
  73. package/dist/utils/error.d.ts +7 -0
  74. package/dist/utils/error.js +8 -0
  75. package/dist/utils/error.js.map +1 -0
  76. package/dist/utils/index.d.ts +7 -67
  77. package/dist/utils/index.js +17 -5
  78. package/dist/utils/index.js.map +1 -1
  79. package/package.json +5 -1
  80. package/skills/output-standards/SKILL.md +14 -9
  81. package/skills/output-standards/SKILL.save.md +3 -2
  82. package/dist/package-outdated-watcher-BSgR_kK-.d.ts +0 -581
@@ -1,9 +1,9 @@
1
- import { execFile } from 'child_process';
2
- import { promisify } from 'util';
3
- import * as fs from 'fs/promises';
4
1
  import { randomUUID, randomBytes, createHash } from 'crypto';
2
+ import * as fs from 'fs/promises';
5
3
  import * as path2 from 'path';
6
4
  import * as os from 'os';
5
+ import { execFile } from 'child_process';
6
+ import { promisify } from 'util';
7
7
  import { EventEmitter } from 'events';
8
8
 
9
9
  // src/utils/token-estimate.ts
@@ -1250,6 +1250,323 @@ function readPolicy(ctx) {
1250
1250
  if (typeof candidate.preserveK !== "number" || !candidate.thresholds) return null;
1251
1251
  return candidate;
1252
1252
  }
1253
+ async function atomicWrite(targetPath, content, opts = {}) {
1254
+ const dir = path2.dirname(targetPath);
1255
+ await fs.mkdir(dir, { recursive: true });
1256
+ const tmp = path2.join(dir, `.${path2.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
1257
+ try {
1258
+ if (typeof content === "string") {
1259
+ await fs.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
1260
+ } else {
1261
+ await fs.writeFile(tmp, content, { flag: "wx" });
1262
+ }
1263
+ try {
1264
+ const fh = await fs.open(tmp, "r+");
1265
+ try {
1266
+ await fh.sync();
1267
+ } finally {
1268
+ await fh.close();
1269
+ }
1270
+ } catch {
1271
+ }
1272
+ let mode;
1273
+ try {
1274
+ const stat2 = await fs.stat(targetPath);
1275
+ mode = stat2.mode & 511;
1276
+ } catch {
1277
+ mode = opts.mode;
1278
+ }
1279
+ if (mode !== void 0) {
1280
+ await fs.chmod(tmp, mode);
1281
+ }
1282
+ await renameWithRetry(tmp, targetPath);
1283
+ } catch (err) {
1284
+ try {
1285
+ await fs.unlink(tmp);
1286
+ } catch {
1287
+ }
1288
+ throw err;
1289
+ }
1290
+ }
1291
+ var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
1292
+ async function renameWithRetry(from, to) {
1293
+ if (process.platform !== "win32") {
1294
+ await fs.rename(from, to);
1295
+ return;
1296
+ }
1297
+ const delays = [10, 25, 60, 120, 250];
1298
+ let lastErr;
1299
+ for (let i = 0; i <= delays.length; i++) {
1300
+ try {
1301
+ await fs.rename(from, to);
1302
+ return;
1303
+ } catch (err) {
1304
+ lastErr = err;
1305
+ const code = err?.code;
1306
+ if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
1307
+ throw err;
1308
+ }
1309
+ await new Promise((resolve2) => setTimeout(resolve2, delays[i]));
1310
+ }
1311
+ }
1312
+ throw lastErr;
1313
+ }
1314
+
1315
+ // src/utils/error.ts
1316
+ function toErrorMessage(err) {
1317
+ return err instanceof Error ? err.message : String(err);
1318
+ }
1319
+
1320
+ // src/utils/string.ts
1321
+ function truncate(s, max) {
1322
+ return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
1323
+ }
1324
+ function projectHash(absRoot) {
1325
+ return createHash("sha256").update(path2.resolve(absRoot)).digest("hex").slice(0, 12);
1326
+ }
1327
+ function projectSlug(absRoot) {
1328
+ const base = slugify(path2.basename(absRoot));
1329
+ const hash = createHash("sha256").update(path2.resolve(absRoot)).digest("hex").slice(0, 6);
1330
+ return `${base}-${hash}`;
1331
+ }
1332
+ function slugify(name) {
1333
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
1334
+ }
1335
+ function wstackGlobalRoot() {
1336
+ const fromEnv = process.env["WRONGSTACK_HOME"];
1337
+ if (fromEnv && fromEnv.trim().length > 0) return path2.resolve(fromEnv);
1338
+ return path2.join(os.homedir(), ".wrongstack");
1339
+ }
1340
+ function resolveWstackPaths(opts) {
1341
+ const globalRoot = opts.globalRoot ?? (opts.userHome ? path2.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
1342
+ const hash = projectHash(opts.projectRoot);
1343
+ const slug = projectSlug(opts.projectRoot);
1344
+ const projectDir = path2.join(globalRoot, "projects", slug);
1345
+ return {
1346
+ globalRoot,
1347
+ configDir: globalRoot,
1348
+ globalConfig: path2.join(globalRoot, "config.json"),
1349
+ secretsKey: path2.join(globalRoot, ".key"),
1350
+ globalMemory: path2.join(globalRoot, "memory.md"),
1351
+ globalSkills: path2.join(globalRoot, "skills"),
1352
+ globalPrompts: path2.join(globalRoot, "prompts"),
1353
+ cacheDir: path2.join(globalRoot, "cache"),
1354
+ modelsCache: path2.join(globalRoot, "cache", "models.dev.json"),
1355
+ modelsOverlayCache: path2.join(globalRoot, "cache", "models-overlay.json"),
1356
+ historyFile: path2.join(globalRoot, "history"),
1357
+ logFile: path2.join(globalRoot, "logs", "wrongstack.log"),
1358
+ projectDir,
1359
+ projectCodebaseIndex: path2.join(projectDir, "codebase-index"),
1360
+ projectMemory: path2.join(projectDir, "memory.md"),
1361
+ projectSessions: path2.join(projectDir, "sessions"),
1362
+ projectTrust: path2.join(projectDir, "trust.json"),
1363
+ projectMeta: path2.join(projectDir, "meta.json"),
1364
+ projectLocalConfig: path2.join(projectDir, "config.local.json"),
1365
+ inProjectConfig: path2.join(opts.projectRoot, ".wrongstack", "config.json"),
1366
+ inProjectAgentsFile: path2.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
1367
+ inProjectSkills: path2.join(opts.projectRoot, ".wrongstack", "skills"),
1368
+ inProjectWorktrees: path2.join(opts.projectRoot, ".wrongstack", "worktrees"),
1369
+ projectHash: hash,
1370
+ projectSlug: slug,
1371
+ projectGoal: path2.join(projectDir, "goal.json"),
1372
+ projectSpecs: path2.join(projectDir, "specs"),
1373
+ projectTaskGraphs: path2.join(projectDir, "task-graphs"),
1374
+ projectSddSession: path2.join(projectDir, "sdd-session.json"),
1375
+ projectPlan: path2.join(projectDir, "plan.json"),
1376
+ projectAutophase: path2.join(projectDir, "autophase"),
1377
+ syncConfig: path2.join(globalRoot, "sync.json")
1378
+ };
1379
+ }
1380
+
1381
+ // src/utils/sleep.ts
1382
+ function sleep(ms) {
1383
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
1384
+ }
1385
+
1386
+ // src/utils/assert-never.ts
1387
+ function assertNever(x, message) {
1388
+ const err = new Error(
1389
+ `Unhandled case: ${JSON.stringify(x)}`
1390
+ );
1391
+ err.name = "AssertNeverError";
1392
+ throw err;
1393
+ }
1394
+
1395
+ // src/utils/tool-output-serializer.ts
1396
+ function createToolOutputSerializer(opts = {}) {
1397
+ const capBytes = opts.perIterationOutputCapBytes ?? 1e5;
1398
+ function serialize(value) {
1399
+ if (typeof value === "string") return value;
1400
+ if (value === null || value === void 0) return "";
1401
+ if (typeof value === "object") {
1402
+ if (Array.isArray(value)) return value.map(serialize).join("\n");
1403
+ if ("text" in value) {
1404
+ const t = value.text;
1405
+ return typeof t === "string" ? t : JSON.stringify(value, null, 2);
1406
+ }
1407
+ try {
1408
+ return JSON.stringify(value, null, 2);
1409
+ } catch {
1410
+ return String(value);
1411
+ }
1412
+ }
1413
+ return String(value);
1414
+ }
1415
+ function enforceCap(text, remainingBudget) {
1416
+ if (remainingBudget <= 0) {
1417
+ return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
1418
+ }
1419
+ const textBytes = Buffer.byteLength(text, "utf8");
1420
+ if (textBytes <= remainingBudget) {
1421
+ return { text, newBudget: remainingBudget - textBytes };
1422
+ }
1423
+ const marker = `
1424
+ \u2026[truncated ${textBytes - remainingBudget} bytes]\u2026
1425
+ `;
1426
+ const markerBytes = Buffer.byteLength(marker, "utf8");
1427
+ const available = remainingBudget - markerBytes;
1428
+ if (available <= 0) {
1429
+ return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
1430
+ }
1431
+ const half = Math.floor(available / 2);
1432
+ const first = text.slice(0, half);
1433
+ const second = text.slice(text.length - half);
1434
+ return { text: `${first}${marker}${second}`, newBudget: 0 };
1435
+ }
1436
+ return { serialize, enforceCap, capBytes };
1437
+ }
1438
+
1439
+ // src/utils/json-schema-validate.ts
1440
+ function validateAgainstSchema(value, schema) {
1441
+ const errors = [];
1442
+ walk(value, schema, "", errors);
1443
+ return { ok: errors.length === 0, errors };
1444
+ }
1445
+ function walk(value, schema, path4, errors) {
1446
+ if (schema.enum !== void 0) {
1447
+ if (!schema.enum.some((e) => deepEqual(e, value))) {
1448
+ errors.push({
1449
+ path: path4 || "<root>",
1450
+ message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
1451
+ });
1452
+ return;
1453
+ }
1454
+ }
1455
+ if (typeof schema.type === "string") {
1456
+ if (!checkType(value, schema.type)) {
1457
+ errors.push({
1458
+ path: path4 || "<root>",
1459
+ message: `expected ${schema.type}, got ${describeType(value)}`
1460
+ });
1461
+ return;
1462
+ }
1463
+ }
1464
+ if (schema.type === "object" && isPlainObject(value)) {
1465
+ const obj = value;
1466
+ for (const req of schema.required ?? []) {
1467
+ if (!(req in obj)) {
1468
+ errors.push({ path: joinPath(path4, req), message: "required property missing" });
1469
+ }
1470
+ }
1471
+ if (schema.properties) {
1472
+ for (const [key, subSchema] of Object.entries(schema.properties)) {
1473
+ if (key in obj) {
1474
+ walk(obj[key], subSchema, joinPath(path4, key), errors);
1475
+ }
1476
+ }
1477
+ }
1478
+ }
1479
+ if (schema.type === "array" && Array.isArray(value) && schema.items) {
1480
+ for (let i = 0; i < value.length; i++) {
1481
+ walk(value[i], schema.items, `${path4}[${i}]`, errors);
1482
+ }
1483
+ }
1484
+ }
1485
+ function checkType(value, type) {
1486
+ switch (type) {
1487
+ case "string":
1488
+ return typeof value === "string";
1489
+ case "number":
1490
+ return typeof value === "number" && !Number.isNaN(value);
1491
+ case "integer":
1492
+ return typeof value === "number" && Number.isInteger(value);
1493
+ case "boolean":
1494
+ return typeof value === "boolean";
1495
+ case "null":
1496
+ return value === null;
1497
+ case "array":
1498
+ return Array.isArray(value);
1499
+ case "object":
1500
+ return isPlainObject(value);
1501
+ default:
1502
+ return true;
1503
+ }
1504
+ }
1505
+ function isPlainObject(v) {
1506
+ return typeof v === "object" && v !== null && !Array.isArray(v);
1507
+ }
1508
+ function describeType(v) {
1509
+ if (v === null) return "null";
1510
+ if (Array.isArray(v)) return "array";
1511
+ return typeof v;
1512
+ }
1513
+ function joinPath(parent, key) {
1514
+ if (!parent) return key;
1515
+ return `${parent}.${key}`;
1516
+ }
1517
+ function deepEqual(a, b) {
1518
+ if (a === b) return true;
1519
+ if (typeof a !== typeof b) return false;
1520
+ if (a === null || b === null) return a === b;
1521
+ if (Array.isArray(a) && Array.isArray(b)) {
1522
+ return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
1523
+ }
1524
+ if (typeof a === "object" && typeof b === "object") {
1525
+ const ak = Object.keys(a);
1526
+ const bk = Object.keys(b);
1527
+ if (ak.length !== bk.length) return false;
1528
+ return ak.every(
1529
+ (k) => deepEqual(a[k], b[k])
1530
+ );
1531
+ }
1532
+ return false;
1533
+ }
1534
+
1535
+ // src/utils/regex-guard.ts
1536
+ var MAX_PATTERN_LEN = 512;
1537
+ var DANGEROUS_PATTERNS = [
1538
+ /(\([^)]*[+*][^)]*\))[+*]/,
1539
+ // (a+)+, (.*)+, etc
1540
+ /(\(\?:[^)]*[+*][^)]*\))[+*]/
1541
+ // same, with non-capturing group
1542
+ ];
1543
+ function compileUserRegex(pattern, flags) {
1544
+ if (typeof pattern !== "string") {
1545
+ return { ok: false, reason: "pattern must be a string" };
1546
+ }
1547
+ if (pattern.length === 0) {
1548
+ return { ok: false, reason: "pattern is empty" };
1549
+ }
1550
+ if (pattern.length > MAX_PATTERN_LEN) {
1551
+ return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
1552
+ }
1553
+ for (const rx of DANGEROUS_PATTERNS) {
1554
+ if (rx.test(pattern)) {
1555
+ return {
1556
+ ok: false,
1557
+ reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
1558
+ };
1559
+ }
1560
+ }
1561
+ try {
1562
+ return { ok: true, regex: new RegExp(pattern, flags) };
1563
+ } catch (err) {
1564
+ return {
1565
+ ok: false,
1566
+ reason: err instanceof Error ? err.message : "invalid regex"
1567
+ };
1568
+ }
1569
+ }
1253
1570
 
1254
1571
  // src/types/errors.ts
1255
1572
  var ERROR_CODES = {
@@ -1312,7 +1629,7 @@ var AgentError = class extends WrongStackError {
1312
1629
  };
1313
1630
  function toWrongStackError(err, code = ERROR_CODES.AGENT_RUN_FAILED) {
1314
1631
  if (err instanceof WrongStackError) return err;
1315
- const message = err instanceof Error ? err.message : String(err);
1632
+ const message = toErrorMessage(err);
1316
1633
  return new AgentError({
1317
1634
  message,
1318
1635
  code: code === "UNKNOWN" ? ERROR_CODES.AGENT_RUN_FAILED : code,
@@ -1353,6 +1670,12 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
1353
1670
  hardThreshold;
1354
1671
  /** Writable so model-switch can update the denominator without re-registering the middleware. */
1355
1672
  _maxContext;
1673
+ /**
1674
+ * Runtime on/off gate. The middleware is always installed in the pipeline so
1675
+ * auto-compaction can be toggled live from the TUI `/settings` picker; when
1676
+ * disabled the handler is a pass-through. Defaults to enabled.
1677
+ */
1678
+ _enabled = true;
1356
1679
  aggressiveOn;
1357
1680
  events;
1358
1681
  failureMode;
@@ -1410,8 +1733,19 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
1410
1733
  setMaxContext(maxContext) {
1411
1734
  this._maxContext = maxContext;
1412
1735
  }
1736
+ /** Whether auto-compaction is currently active. */
1737
+ get enabled() {
1738
+ return this._enabled;
1739
+ }
1740
+ /** Toggle auto-compaction on a live session (TUI `/settings`). When disabled
1741
+ * the middleware passes every iteration straight through without estimating
1742
+ * tokens or compacting. */
1743
+ setEnabled(enabled) {
1744
+ this._enabled = enabled;
1745
+ }
1413
1746
  handler() {
1414
1747
  return async (ctx, next) => {
1748
+ if (!this._enabled) return next(ctx);
1415
1749
  const msgCount = ctx.messages.length;
1416
1750
  const toolCount = (ctx.tools ?? []).length;
1417
1751
  let tokens;
@@ -1546,193 +1880,55 @@ var AutoCompactionMiddleware = class _AutoCompactionMiddleware {
1546
1880
  tokens: pressure.tokens,
1547
1881
  maxContext: this._maxContext
1548
1882
  },
1549
- cause: err
1550
- });
1551
- }
1552
- }
1553
- }
1554
- };
1555
-
1556
- // src/security/capabilities.ts
1557
- var ToolCapabilities = {
1558
- /** Can execute arbitrary commands in the user's shell (the `bash` tool). */
1559
- SHELL_ARBITRARY: "shell.arbitrary",
1560
- /** Can execute a restricted set of commands (the `exec` tool). */
1561
- SHELL_RESTRICTED: "shell.restricted",
1562
- /** Can write / modify / delete files inside the project. */
1563
- FS_WRITE: "fs.write",
1564
- /** Can write files outside the current project root (very high risk). */
1565
- FS_WRITE_OUTSIDE_PROJECT: "fs.write.outside-project",
1566
- /** Proxies tools from external MCP servers (unknown capability). */
1567
- MCP_PROXY: "mcp.proxy",
1568
- /** Can spawn or manage subagents / multi-agent tasks. */
1569
- SUBAGENT_SPAWN: "subagent.spawn",
1570
- /** Can mutate global or session configuration / trust state. */
1571
- CONFIG_MUTATE: "config.mutate",
1572
- /** Can install packages or run package managers with side effects. */
1573
- PACKAGE_INSTALL: "package.install"
1574
- };
1575
- var DANGEROUS_FOR_SUBAGENTS = [
1576
- ToolCapabilities.SHELL_ARBITRARY,
1577
- ToolCapabilities.SHELL_RESTRICTED,
1578
- ToolCapabilities.FS_WRITE,
1579
- ToolCapabilities.FS_WRITE_OUTSIDE_PROJECT,
1580
- ToolCapabilities.MCP_PROXY,
1581
- ToolCapabilities.SUBAGENT_SPAWN,
1582
- ToolCapabilities.CONFIG_MUTATE,
1583
- ToolCapabilities.PACKAGE_INSTALL
1584
- ];
1585
- function hasDangerousCapabilityForSubagents(toolOrCaps) {
1586
- if (!toolOrCaps) return false;
1587
- const input = toolOrCaps;
1588
- const caps = Array.isArray(toolOrCaps) ? toolOrCaps : input.capabilities ?? [];
1589
- return caps.some((c) => DANGEROUS_FOR_SUBAGENTS.includes(c));
1590
- }
1591
- function getDangerousCapabilities(toolOrCaps) {
1592
- if (!toolOrCaps) return [];
1593
- const input = toolOrCaps;
1594
- const caps = Array.isArray(toolOrCaps) ? toolOrCaps : input.capabilities ?? [];
1595
- return caps.filter(
1596
- (c) => DANGEROUS_FOR_SUBAGENTS.includes(c)
1597
- );
1598
- }
1599
-
1600
- // src/utils/json-schema-validate.ts
1601
- function validateAgainstSchema(value, schema) {
1602
- const errors = [];
1603
- walk(value, schema, "", errors);
1604
- return { ok: errors.length === 0, errors };
1605
- }
1606
- function walk(value, schema, path4, errors) {
1607
- if (schema.enum !== void 0) {
1608
- if (!schema.enum.some((e) => deepEqual(e, value))) {
1609
- errors.push({
1610
- path: path4 || "<root>",
1611
- message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
1612
- });
1613
- return;
1614
- }
1615
- }
1616
- if (typeof schema.type === "string") {
1617
- if (!checkType(value, schema.type)) {
1618
- errors.push({
1619
- path: path4 || "<root>",
1620
- message: `expected ${schema.type}, got ${describeType(value)}`
1621
- });
1622
- return;
1623
- }
1624
- }
1625
- if (schema.type === "object" && isPlainObject(value)) {
1626
- const obj = value;
1627
- for (const req of schema.required ?? []) {
1628
- if (!(req in obj)) {
1629
- errors.push({ path: joinPath(path4, req), message: "required property missing" });
1630
- }
1631
- }
1632
- if (schema.properties) {
1633
- for (const [key, subSchema] of Object.entries(schema.properties)) {
1634
- if (key in obj) {
1635
- walk(obj[key], subSchema, joinPath(path4, key), errors);
1636
- }
1637
- }
1638
- }
1639
- }
1640
- if (schema.type === "array" && Array.isArray(value) && schema.items) {
1641
- value.forEach((item, i) => walk(item, schema.items, `${path4}[${i}]`, errors));
1642
- }
1643
- }
1644
- function checkType(value, type) {
1645
- switch (type) {
1646
- case "string":
1647
- return typeof value === "string";
1648
- case "number":
1649
- return typeof value === "number" && !Number.isNaN(value);
1650
- case "integer":
1651
- return typeof value === "number" && Number.isInteger(value);
1652
- case "boolean":
1653
- return typeof value === "boolean";
1654
- case "null":
1655
- return value === null;
1656
- case "array":
1657
- return Array.isArray(value);
1658
- case "object":
1659
- return isPlainObject(value);
1660
- default:
1661
- return true;
1662
- }
1663
- }
1664
- function isPlainObject(v) {
1665
- return typeof v === "object" && v !== null && !Array.isArray(v);
1666
- }
1667
- function describeType(v) {
1668
- if (v === null) return "null";
1669
- if (Array.isArray(v)) return "array";
1670
- return typeof v;
1671
- }
1672
- function joinPath(parent, key) {
1673
- if (!parent) return key;
1674
- return `${parent}.${key}`;
1675
- }
1676
- function deepEqual(a, b) {
1677
- if (a === b) return true;
1678
- if (typeof a !== typeof b) return false;
1679
- if (a === null || b === null) return a === b;
1680
- if (Array.isArray(a) && Array.isArray(b)) {
1681
- return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
1682
- }
1683
- if (typeof a === "object" && typeof b === "object") {
1684
- const ak = Object.keys(a);
1685
- const bk = Object.keys(b);
1686
- if (ak.length !== bk.length) return false;
1687
- return ak.every(
1688
- (k) => deepEqual(a[k], b[k])
1689
- );
1690
- }
1691
- return false;
1692
- }
1693
-
1694
- // src/utils/tool-output-serializer.ts
1695
- function createToolOutputSerializer(opts = {}) {
1696
- const capBytes = opts.perIterationOutputCapBytes ?? 1e5;
1697
- function serialize(value) {
1698
- if (typeof value === "string") return value;
1699
- if (value === null || value === void 0) return "";
1700
- if (typeof value === "object") {
1701
- if (Array.isArray(value)) return value.map(serialize).join("\n");
1702
- if ("text" in value) {
1703
- const t = value.text;
1704
- return typeof t === "string" ? t : JSON.stringify(value, null, 2);
1705
- }
1706
- try {
1707
- return JSON.stringify(value, null, 2);
1708
- } catch {
1709
- return String(value);
1883
+ cause: err
1884
+ });
1710
1885
  }
1711
1886
  }
1712
- return String(value);
1713
- }
1714
- function enforceCap(text, remainingBudget) {
1715
- if (remainingBudget <= 0) {
1716
- return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
1717
- }
1718
- const textBytes = Buffer.byteLength(text, "utf8");
1719
- if (textBytes <= remainingBudget) {
1720
- return { text, newBudget: remainingBudget - textBytes };
1721
- }
1722
- const marker = `
1723
- \u2026[truncated ${textBytes - remainingBudget} bytes]\u2026
1724
- `;
1725
- const markerBytes = Buffer.byteLength(marker, "utf8");
1726
- const available = remainingBudget - markerBytes;
1727
- if (available <= 0) {
1728
- return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
1729
- }
1730
- const half = Math.floor(available / 2);
1731
- const first = text.slice(0, half);
1732
- const second = text.slice(text.length - half);
1733
- return { text: `${first}${marker}${second}`, newBudget: 0 };
1734
1887
  }
1735
- return { serialize, enforceCap, capBytes };
1888
+ };
1889
+
1890
+ // src/security/capabilities.ts
1891
+ var ToolCapabilities = {
1892
+ /** Can execute arbitrary commands in the user's shell (the `bash` tool). */
1893
+ SHELL_ARBITRARY: "shell.arbitrary",
1894
+ /** Can execute a restricted set of commands (the `exec` tool). */
1895
+ SHELL_RESTRICTED: "shell.restricted",
1896
+ /** Can write / modify / delete files inside the project. */
1897
+ FS_WRITE: "fs.write",
1898
+ /** Can write files outside the current project root (very high risk). */
1899
+ FS_WRITE_OUTSIDE_PROJECT: "fs.write.outside-project",
1900
+ /** Proxies tools from external MCP servers (unknown capability). */
1901
+ MCP_PROXY: "mcp.proxy",
1902
+ /** Can spawn or manage subagents / multi-agent tasks. */
1903
+ SUBAGENT_SPAWN: "subagent.spawn",
1904
+ /** Can mutate global or session configuration / trust state. */
1905
+ CONFIG_MUTATE: "config.mutate",
1906
+ /** Can install packages or run package managers with side effects. */
1907
+ PACKAGE_INSTALL: "package.install"
1908
+ };
1909
+ var DANGEROUS_FOR_SUBAGENTS = [
1910
+ ToolCapabilities.SHELL_ARBITRARY,
1911
+ ToolCapabilities.SHELL_RESTRICTED,
1912
+ ToolCapabilities.FS_WRITE,
1913
+ ToolCapabilities.FS_WRITE_OUTSIDE_PROJECT,
1914
+ ToolCapabilities.MCP_PROXY,
1915
+ ToolCapabilities.SUBAGENT_SPAWN,
1916
+ ToolCapabilities.CONFIG_MUTATE,
1917
+ ToolCapabilities.PACKAGE_INSTALL
1918
+ ];
1919
+ function hasDangerousCapabilityForSubagents(toolOrCaps) {
1920
+ if (!toolOrCaps) return false;
1921
+ const input = toolOrCaps;
1922
+ const caps = Array.isArray(toolOrCaps) ? toolOrCaps : input.capabilities ?? [];
1923
+ return caps.some((c) => DANGEROUS_FOR_SUBAGENTS.includes(c));
1924
+ }
1925
+ function getDangerousCapabilities(toolOrCaps) {
1926
+ if (!toolOrCaps) return [];
1927
+ const input = toolOrCaps;
1928
+ const caps = Array.isArray(toolOrCaps) ? toolOrCaps : input.capabilities ?? [];
1929
+ return caps.filter(
1930
+ (c) => DANGEROUS_FOR_SUBAGENTS.includes(c)
1931
+ );
1736
1932
  }
1737
1933
 
1738
1934
  // src/execution/tool-executor.ts
@@ -1898,7 +2094,7 @@ ${post.additionalContext}`;
1898
2094
  );
1899
2095
  return { result, tool, durationMs: Date.now() - start };
1900
2096
  } catch (err) {
1901
- const msg = err instanceof Error ? err.message : String(err);
2097
+ const msg = toErrorMessage(err);
1902
2098
  const scrubbed = this.opts.secretScrubber.scrub(msg);
1903
2099
  this.opts.renderer?.writeToolResult(tool.name, scrubbed, true);
1904
2100
  const result = {
@@ -1919,7 +2115,7 @@ ${post.additionalContext}`;
1919
2115
  try {
1920
2116
  return await runOne(use);
1921
2117
  } catch (err) {
1922
- const msg = err instanceof Error ? err.message : String(err);
2118
+ const msg = toErrorMessage(err);
1923
2119
  const scrubbed = this.opts.secretScrubber.scrub(msg);
1924
2120
  const result = {
1925
2121
  type: "tool_result",
@@ -2202,51 +2398,6 @@ function extractMalformedRaw(input) {
2202
2398
  }
2203
2399
  }
2204
2400
 
2205
- // src/utils/assert-never.ts
2206
- function assertNever(x, message) {
2207
- const err = new Error(
2208
- `Unhandled case: ${JSON.stringify(x)}`
2209
- );
2210
- err.name = "AssertNeverError";
2211
- throw err;
2212
- }
2213
-
2214
- // src/utils/regex-guard.ts
2215
- var MAX_PATTERN_LEN = 512;
2216
- var DANGEROUS_PATTERNS = [
2217
- /(\([^)]*[+*][^)]*\))[+*]/,
2218
- // (a+)+, (.*)+, etc
2219
- /(\(\?:[^)]*[+*][^)]*\))[+*]/
2220
- // same, with non-capturing group
2221
- ];
2222
- function compileUserRegex(pattern, flags) {
2223
- if (typeof pattern !== "string") {
2224
- return { ok: false, reason: "pattern must be a string" };
2225
- }
2226
- if (pattern.length === 0) {
2227
- return { ok: false, reason: "pattern is empty" };
2228
- }
2229
- if (pattern.length > MAX_PATTERN_LEN) {
2230
- return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
2231
- }
2232
- for (const rx of DANGEROUS_PATTERNS) {
2233
- if (rx.test(pattern)) {
2234
- return {
2235
- ok: false,
2236
- reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
2237
- };
2238
- }
2239
- }
2240
- try {
2241
- return { ok: true, regex: new RegExp(pattern, flags) };
2242
- } catch (err) {
2243
- return {
2244
- ok: false,
2245
- reason: err instanceof Error ? err.message : "invalid regex"
2246
- };
2247
- }
2248
- }
2249
-
2250
2401
  // src/execution/autonomous-runner.ts
2251
2402
  var DoneConditionChecker = class {
2252
2403
  constructor(condition) {
@@ -2423,136 +2574,36 @@ var AutonomousRunner = class {
2423
2574
  this.stopped = true;
2424
2575
  }
2425
2576
  };
2426
- async function atomicWrite(targetPath, content, opts = {}) {
2427
- const dir = path2.dirname(targetPath);
2428
- await fs.mkdir(dir, { recursive: true });
2429
- const tmp = path2.join(dir, `.${path2.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
2430
- try {
2431
- if (typeof content === "string") {
2432
- await fs.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
2433
- } else {
2434
- await fs.writeFile(tmp, content, { flag: "wx" });
2435
- }
2436
- try {
2437
- const fh = await fs.open(tmp, "r+");
2438
- try {
2439
- await fh.sync();
2440
- } finally {
2441
- await fh.close();
2442
- }
2443
- } catch {
2444
- }
2445
- let mode;
2446
- try {
2447
- const stat2 = await fs.stat(targetPath);
2448
- mode = stat2.mode & 511;
2449
- } catch {
2450
- mode = opts.mode;
2451
- }
2452
- if (mode !== void 0) {
2453
- await fs.chmod(tmp, mode);
2454
- }
2455
- await renameWithRetry(tmp, targetPath);
2456
- } catch (err) {
2457
- try {
2458
- await fs.unlink(tmp);
2459
- } catch {
2460
- }
2461
- throw err;
2462
- }
2463
- }
2464
- var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EBUSY", "EACCES", "ENOTEMPTY"]);
2465
- async function renameWithRetry(from, to) {
2466
- if (process.platform !== "win32") {
2467
- await fs.rename(from, to);
2468
- return;
2469
- }
2470
- const delays = [10, 25, 60, 120, 250];
2471
- let lastErr;
2472
- for (let i = 0; i <= delays.length; i++) {
2473
- try {
2474
- await fs.rename(from, to);
2475
- return;
2476
- } catch (err) {
2477
- lastErr = err;
2478
- const code = err?.code;
2479
- if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
2480
- throw err;
2481
- }
2482
- await new Promise((resolve2) => setTimeout(resolve2, delays[i]));
2483
- }
2484
- }
2485
- throw lastErr;
2486
- }
2487
- function projectHash(absRoot) {
2488
- return createHash("sha256").update(path2.resolve(absRoot)).digest("hex").slice(0, 12);
2489
- }
2490
- function projectSlug(absRoot) {
2491
- const base = slugify(path2.basename(absRoot));
2492
- const hash = createHash("sha256").update(path2.resolve(absRoot)).digest("hex").slice(0, 6);
2493
- return `${base}-${hash}`;
2494
- }
2495
- function slugify(name) {
2496
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
2497
- }
2498
- function wstackGlobalRoot() {
2499
- const fromEnv = process.env["WRONGSTACK_HOME"];
2500
- if (fromEnv && fromEnv.trim().length > 0) return path2.resolve(fromEnv);
2501
- return path2.join(os.homedir(), ".wrongstack");
2502
- }
2503
- function resolveWstackPaths(opts) {
2504
- const globalRoot = opts.globalRoot ?? (opts.userHome ? path2.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
2505
- const hash = projectHash(opts.projectRoot);
2506
- const slug = projectSlug(opts.projectRoot);
2507
- const projectDir = path2.join(globalRoot, "projects", slug);
2508
- return {
2509
- globalRoot,
2510
- configDir: globalRoot,
2511
- globalConfig: path2.join(globalRoot, "config.json"),
2512
- secretsKey: path2.join(globalRoot, ".key"),
2513
- globalMemory: path2.join(globalRoot, "memory.md"),
2514
- globalSkills: path2.join(globalRoot, "skills"),
2515
- globalPrompts: path2.join(globalRoot, "prompts"),
2516
- cacheDir: path2.join(globalRoot, "cache"),
2517
- modelsCache: path2.join(globalRoot, "cache", "models.dev.json"),
2518
- modelsOverlayCache: path2.join(globalRoot, "cache", "models-overlay.json"),
2519
- historyFile: path2.join(globalRoot, "history"),
2520
- logFile: path2.join(globalRoot, "logs", "wrongstack.log"),
2521
- projectDir,
2522
- projectCodebaseIndex: path2.join(projectDir, "codebase-index"),
2523
- projectMemory: path2.join(projectDir, "memory.md"),
2524
- projectSessions: path2.join(projectDir, "sessions"),
2525
- projectTrust: path2.join(projectDir, "trust.json"),
2526
- projectMeta: path2.join(projectDir, "meta.json"),
2527
- projectLocalConfig: path2.join(projectDir, "config.local.json"),
2528
- inProjectConfig: path2.join(opts.projectRoot, ".wrongstack", "config.json"),
2529
- inProjectAgentsFile: path2.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
2530
- inProjectSkills: path2.join(opts.projectRoot, ".wrongstack", "skills"),
2531
- inProjectWorktrees: path2.join(opts.projectRoot, ".wrongstack", "worktrees"),
2532
- projectHash: hash,
2533
- projectSlug: slug,
2534
- projectGoal: path2.join(projectDir, "goal.json"),
2535
- projectSpecs: path2.join(projectDir, "specs"),
2536
- projectTaskGraphs: path2.join(projectDir, "task-graphs"),
2537
- projectSddSession: path2.join(projectDir, "sdd-session.json"),
2538
- projectPlan: path2.join(projectDir, "plan.json"),
2539
- projectAutophase: path2.join(projectDir, "autophase"),
2540
- syncConfig: path2.join(globalRoot, "sync.json")
2541
- };
2542
- }
2543
-
2544
- // src/storage/goal-store.ts
2545
2577
  var MAX_JOURNAL_ENTRIES = 500;
2546
2578
  function goalFilePath(projectRoot) {
2547
2579
  return resolveWstackPaths({ projectRoot }).projectGoal;
2548
2580
  }
2549
- async function loadGoal(filePath) {
2581
+ async function loadGoal(filePath, events) {
2582
+ const t0 = Date.now();
2550
2583
  let raw;
2551
2584
  try {
2552
2585
  raw = await fs.readFile(filePath, "utf8");
2553
2586
  } catch (err) {
2554
2587
  const code = err.code;
2555
- if (code === "ENOENT") return null;
2588
+ if (code === "ENOENT") {
2589
+ events?.emit("storage.read", {
2590
+ sessionId: "~boot~",
2591
+ store: "goal",
2592
+ filePath,
2593
+ operation: "load",
2594
+ outcome: "success",
2595
+ durationMs: Date.now() - t0
2596
+ });
2597
+ return null;
2598
+ }
2599
+ events?.emit("storage.error", {
2600
+ sessionId: "~boot~",
2601
+ store: "goal",
2602
+ filePath,
2603
+ operation: "load",
2604
+ error: toErrorMessage(err),
2605
+ recoverable: false
2606
+ });
2556
2607
  throw err;
2557
2608
  }
2558
2609
  try {
@@ -2565,8 +2616,25 @@ async function loadGoal(filePath) {
2565
2616
  message: "invalid schema \u2014 consider deleting and re-creating",
2566
2617
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2567
2618
  }));
2619
+ events?.emit("storage.read", {
2620
+ sessionId: "~boot~",
2621
+ store: "goal",
2622
+ filePath,
2623
+ operation: "load",
2624
+ outcome: "failure",
2625
+ durationMs: Date.now() - t0,
2626
+ error: "invalid_schema"
2627
+ });
2568
2628
  return null;
2569
2629
  }
2630
+ events?.emit("storage.read", {
2631
+ sessionId: "~boot~",
2632
+ store: "goal",
2633
+ filePath,
2634
+ operation: "load",
2635
+ outcome: "success",
2636
+ durationMs: Date.now() - t0
2637
+ });
2570
2638
  return parsed;
2571
2639
  } catch {
2572
2640
  console.warn(JSON.stringify({
@@ -2576,15 +2644,41 @@ async function loadGoal(filePath) {
2576
2644
  message: "JSON parse failed \u2014 consider deleting and re-creating",
2577
2645
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2578
2646
  }));
2647
+ events?.emit("storage.read", {
2648
+ sessionId: "~boot~",
2649
+ store: "goal",
2650
+ filePath,
2651
+ operation: "load",
2652
+ outcome: "failure",
2653
+ durationMs: Date.now() - t0,
2654
+ error: "parse_failed"
2655
+ });
2579
2656
  return null;
2580
2657
  }
2581
2658
  }
2582
- async function saveGoal(filePath, goal) {
2659
+ async function saveGoal(filePath, goal, events) {
2660
+ const t0 = Date.now();
2583
2661
  try {
2584
2662
  await atomicWrite(filePath, JSON.stringify(goal, null, 2), { mode: 384 });
2663
+ events?.emit("storage.write", {
2664
+ sessionId: "~boot~",
2665
+ store: "goal",
2666
+ filePath,
2667
+ operation: "save",
2668
+ outcome: "success",
2669
+ durationMs: Date.now() - t0
2670
+ });
2585
2671
  } catch (err) {
2672
+ events?.emit("storage.error", {
2673
+ sessionId: "~boot~",
2674
+ store: "goal",
2675
+ filePath,
2676
+ operation: "save",
2677
+ error: toErrorMessage(err),
2678
+ recoverable: false
2679
+ });
2586
2680
  throw new FsError({
2587
- message: err instanceof Error ? err.message : String(err),
2681
+ message: toErrorMessage(err),
2588
2682
  code: ERROR_CODES.FS_ATOMIC_WRITE_FAILED,
2589
2683
  path: filePath,
2590
2684
  cause: err
@@ -2638,11 +2732,6 @@ function computeTrend(history) {
2638
2732
  return "steady";
2639
2733
  }
2640
2734
 
2641
- // src/utils/sleep.ts
2642
- function sleep(ms) {
2643
- return new Promise((resolve2) => setTimeout(resolve2, ms));
2644
- }
2645
-
2646
2735
  // src/execution/autonomy-brain.ts
2647
2736
  var RISK_LEVELS = {
2648
2737
  low: 0,
@@ -2879,7 +2968,7 @@ var EternalAutonomyEngine = class {
2879
2968
  console.error(JSON.stringify({
2880
2969
  level: "error",
2881
2970
  event: "engine.persist_state_failed",
2882
- message: err instanceof Error ? err.message : String(err),
2971
+ message: toErrorMessage(err),
2883
2972
  context: { expectedState: "stopped" },
2884
2973
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2885
2974
  }));
@@ -2912,7 +3001,7 @@ var EternalAutonomyEngine = class {
2912
3001
  } catch (err) {
2913
3002
  this.consecutiveFailures++;
2914
3003
  this.opts.onError?.(err instanceof Error ? err : new Error(String(err)), this.consecutiveFailures);
2915
- await this.appendFailure("engine error", err instanceof Error ? err.message : String(err));
3004
+ await this.appendFailure("engine error", toErrorMessage(err));
2916
3005
  }
2917
3006
  if (iterationOk) {
2918
3007
  this.consecutiveFailures = 0;
@@ -2938,7 +3027,7 @@ var EternalAutonomyEngine = class {
2938
3027
  const emit = (stage) => {
2939
3028
  this.opts.onStage?.(stage);
2940
3029
  };
2941
- const goal = await loadGoal(this.goalPath);
3030
+ const goal = await loadGoal(this.goalPath, this.opts.events);
2942
3031
  if (!goal) {
2943
3032
  emit({ phase: "stopped" });
2944
3033
  this.stopRequested = true;
@@ -3013,7 +3102,7 @@ var EternalAutonomyEngine = class {
3013
3102
  } catch (err) {
3014
3103
  const isAbort = err instanceof Error && (err.name === "AbortError" || err.message.includes("abort"));
3015
3104
  status = isAbort ? "aborted" : "failure";
3016
- note = err instanceof Error ? err.message : String(err);
3105
+ note = toErrorMessage(err);
3017
3106
  if (!isAbort && typeof err?.recoverable === "boolean") {
3018
3107
  isTransientFailure = err.recoverable;
3019
3108
  }
@@ -3042,7 +3131,7 @@ var EternalAutonomyEngine = class {
3042
3131
  emit({ phase: "reflect", status, note });
3043
3132
  let iterationIndex = 0;
3044
3133
  try {
3045
- const reloaded = await loadGoal(this.goalPath);
3134
+ const reloaded = await loadGoal(this.goalPath, this.opts.events);
3046
3135
  iterationIndex = reloaded?.iterations ?? 0;
3047
3136
  } catch {
3048
3137
  }
@@ -3362,12 +3451,12 @@ ${recentJournal}` : "No prior iterations.",
3362
3451
  }
3363
3452
  }
3364
3453
  async appendIterationEntry(entry) {
3365
- const current = await loadGoal(this.goalPath);
3454
+ const current = await loadGoal(this.goalPath, this.opts.events);
3366
3455
  if (!current) {
3367
3456
  return;
3368
3457
  }
3369
3458
  const updated = appendJournal(current, entry);
3370
- await saveGoal(this.goalPath, updated);
3459
+ await saveGoal(this.goalPath, updated, this.opts.events);
3371
3460
  }
3372
3461
  /**
3373
3462
  * Persistent per-todo failure counter. Skipped silently when the goal
@@ -3376,11 +3465,11 @@ ${recentJournal}` : "No prior iterations.",
3376
3465
  * the counter to rotate past stuck todos once they cross `todoMaxAttempts`.
3377
3466
  */
3378
3467
  async bumpTodoAttempt(todoId) {
3379
- const current = await loadGoal(this.goalPath);
3468
+ const current = await loadGoal(this.goalPath, this.opts.events);
3380
3469
  if (!current) return;
3381
3470
  const attempts = { ...current.todoAttempts ?? {} };
3382
3471
  attempts[todoId] = (attempts[todoId] ?? 0) + 1;
3383
- await saveGoal(this.goalPath, { ...current, todoAttempts: attempts });
3472
+ await saveGoal(this.goalPath, { ...current, todoAttempts: attempts }, this.opts.events);
3384
3473
  }
3385
3474
  /**
3386
3475
  * Flip the mission to `completed` and journal it. Called from two
@@ -3390,7 +3479,7 @@ ${recentJournal}` : "No prior iterations.",
3390
3479
  * goal is already `completed`.
3391
3480
  */
3392
3481
  async markGoalCompleted(action, note) {
3393
- const current = await loadGoal(this.goalPath);
3482
+ const current = await loadGoal(this.goalPath, this.opts.events);
3394
3483
  if (!current) return;
3395
3484
  if (current.goalState === "completed") return;
3396
3485
  const withFlag = { ...current, goalState: "completed" };
@@ -3400,7 +3489,7 @@ ${recentJournal}` : "No prior iterations.",
3400
3489
  status: "success",
3401
3490
  note: note.slice(0, 240)
3402
3491
  });
3403
- await saveGoal(this.goalPath, withEntry);
3492
+ await saveGoal(this.goalPath, withEntry, this.opts.events);
3404
3493
  this.opts.onEternalStop?.();
3405
3494
  }
3406
3495
  /**
@@ -3409,10 +3498,10 @@ ${recentJournal}` : "No prior iterations.",
3409
3498
  * `onEternalStop` so the REPL returns to normal mode.
3410
3499
  */
3411
3500
  async clearGoalManually(note) {
3412
- const current = await loadGoal(this.goalPath);
3501
+ const current = await loadGoal(this.goalPath, this.opts.events);
3413
3502
  if (current) {
3414
3503
  const abandoned = { ...current, goalState: "abandoned" };
3415
- await saveGoal(this.goalPath, abandoned);
3504
+ await saveGoal(this.goalPath, abandoned, this.opts.events);
3416
3505
  }
3417
3506
  try {
3418
3507
  const { unlink: unlink3 } = await import('fs/promises');
@@ -3488,16 +3577,16 @@ ${recentJournal}` : ""
3488
3577
  * Persist a progress update from the agent's [PROGRESS: N%] output.
3489
3578
  */
3490
3579
  async updateProgress(progress, note) {
3491
- const current = await loadGoal(this.goalPath);
3580
+ const current = await loadGoal(this.goalPath, this.opts.events);
3492
3581
  if (!current) return;
3493
3582
  const updated = recordProgress(current, progress, note);
3494
- await saveGoal(this.goalPath, updated);
3583
+ await saveGoal(this.goalPath, updated, this.opts.events);
3495
3584
  }
3496
3585
  async persistEngineState(state) {
3497
- const current = await loadGoal(this.goalPath);
3586
+ const current = await loadGoal(this.goalPath, this.opts.events);
3498
3587
  if (!current) return;
3499
3588
  if (current.engineState === state) return;
3500
- await saveGoal(this.goalPath, { ...current, engineState: state });
3589
+ await saveGoal(this.goalPath, { ...current, engineState: state }, this.opts.events);
3501
3590
  }
3502
3591
  };
3503
3592
 
@@ -3846,6 +3935,20 @@ var SubagentBudget = class _SubagentBudget {
3846
3935
  };
3847
3936
 
3848
3937
  // src/coordination/agent-subagent-runner.ts
3938
+ function withDisabledToolFiltering(factory) {
3939
+ return async (config) => {
3940
+ const result = await factory(config);
3941
+ const disabled = config.disabledTools ?? [];
3942
+ if (disabled.length === 0) return result;
3943
+ const registry = result.agent.tools;
3944
+ if (registry && typeof registry.unregister === "function") {
3945
+ for (const toolName of disabled) {
3946
+ registry.unregister(toolName);
3947
+ }
3948
+ }
3949
+ return result;
3950
+ };
3951
+ }
3849
3952
  function makeAgentSubagentRunner(opts) {
3850
3953
  const format = opts.formatTaskInput ?? defaultFormatTaskInput;
3851
3954
  return async (task, ctx) => {
@@ -6743,11 +6846,6 @@ async function dispatchAgent(task, opts = {}) {
6743
6846
  };
6744
6847
  }
6745
6848
 
6746
- // src/utils/string.ts
6747
- function truncate(s, max) {
6748
- return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
6749
- }
6750
-
6751
6849
  // src/types/provider.ts
6752
6850
  var ProviderError = class extends WrongStackError {
6753
6851
  status;
@@ -6827,7 +6925,7 @@ function classifySubagentError(err, hints = {}) {
6827
6925
  const baseMessage2 = err.describe();
6828
6926
  return providerErrorToSubagentError(err, baseMessage2, cause);
6829
6927
  }
6830
- const baseMessage = err instanceof Error ? err.message : String(err);
6928
+ const baseMessage = toErrorMessage(err);
6831
6929
  if (err instanceof BudgetExceededError) {
6832
6930
  const map = {
6833
6931
  iterations: "budget_iterations",
@@ -7807,7 +7905,7 @@ var ParallelEternalEngine = class {
7807
7905
  console.error(JSON.stringify({
7808
7906
  level: "error",
7809
7907
  event: "engine.persist_state_failed",
7810
- message: err instanceof Error ? err.message : String(err),
7908
+ message: toErrorMessage(err),
7811
7909
  context: { expectedState: "stopped" },
7812
7910
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
7813
7911
  }));
@@ -7828,7 +7926,8 @@ var ParallelEternalEngine = class {
7828
7926
  doneCondition: { type: "all_tasks_done" }
7829
7927
  };
7830
7928
  this.coordinator = new DefaultMultiAgentCoordinator(config);
7831
- const runner = makeAgentSubagentRunner({ factory: this.agentFactory });
7929
+ const filteredFactory = withDisabledToolFiltering(this.agentFactory);
7930
+ const runner = makeAgentSubagentRunner({ factory: filteredFactory });
7832
7931
  this.coordinator.setRunner?.(runner);
7833
7932
  try {
7834
7933
  while (!this.stopRequested) {
@@ -7842,7 +7941,7 @@ var ParallelEternalEngine = class {
7842
7941
  );
7843
7942
  await this.appendFailure(
7844
7943
  "engine error",
7845
- err instanceof Error ? err.message : String(err)
7944
+ toErrorMessage(err)
7846
7945
  );
7847
7946
  }
7848
7947
  if (this.stopRequested) break;
@@ -7863,7 +7962,7 @@ var ParallelEternalEngine = class {
7863
7962
  this.opts.onStage?.(stage);
7864
7963
  };
7865
7964
  this.iterations++;
7866
- const goal = await loadGoal(this.goalPath);
7965
+ const goal = await loadGoal(this.goalPath, this.opts.events);
7867
7966
  if (!goal) {
7868
7967
  this.stopRequested = true;
7869
7968
  emit({ phase: "stopped" });
@@ -7881,7 +7980,8 @@ var ParallelEternalEngine = class {
7881
7980
  doneCondition: { type: "all_tasks_done" }
7882
7981
  };
7883
7982
  this.coordinator = new DefaultMultiAgentCoordinator(config);
7884
- const runner = makeAgentSubagentRunner({ factory: this.agentFactory });
7983
+ const filteredFactory = withDisabledToolFiltering(this.agentFactory);
7984
+ const runner = makeAgentSubagentRunner({ factory: filteredFactory });
7885
7985
  this.coordinator.setRunner?.(runner);
7886
7986
  }
7887
7987
  emit({ phase: "decompose" });
@@ -7990,13 +8090,17 @@ ${personaLine}Task: ${task}
7990
8090
  role: route.role,
7991
8091
  tools: route.definition.config.tools,
7992
8092
  systemPromptOverride: route.definition.config.prompt,
7993
- timeoutMs: this.timeoutMs
8093
+ timeoutMs: this.timeoutMs,
8094
+ // Disable delegation — subagents are leaf workers, not orchestrators
8095
+ disabledTools: ["delegate"]
7994
8096
  } : {
7995
8097
  id: subagentId,
7996
8098
  name: `slot-${subagentId.slice(-6)}`,
7997
8099
  // Let the coordinator apply its default budget (roster or generic).
7998
8100
  // Hardcoding low limits here defeats the x10 budget improvement.
7999
- timeoutMs: this.timeoutMs
8101
+ timeoutMs: this.timeoutMs,
8102
+ // Disable delegation — subagents are leaf workers, not orchestrators
8103
+ disabledTools: ["delegate"]
8000
8104
  }
8001
8105
  );
8002
8106
  subagentIds.push(subagentId);
@@ -8142,10 +8246,10 @@ ${lastFew}` : "No prior iterations.",
8142
8246
  // Helpers
8143
8247
  // -------------------------------------------------------------------------
8144
8248
  async appendIterationEntry(entry) {
8145
- const current = await loadGoal(this.goalPath);
8249
+ const current = await loadGoal(this.goalPath, this.opts.events);
8146
8250
  if (!current) return;
8147
8251
  const updated = appendJournal(current, entry);
8148
- await saveGoal(this.goalPath, updated);
8252
+ await saveGoal(this.goalPath, updated, this.opts.events);
8149
8253
  const entryWithMeta = {
8150
8254
  at: (this.opts.now?.() ?? /* @__PURE__ */ new Date()).toISOString(),
8151
8255
  iteration: updated.iterations,
@@ -8157,10 +8261,10 @@ ${lastFew}` : "No prior iterations.",
8157
8261
  await this.appendIterationEntry({ source: "manual", task, status: "failure", note });
8158
8262
  }
8159
8263
  async persistState(state) {
8160
- const current = await loadGoal(this.goalPath);
8264
+ const current = await loadGoal(this.goalPath, this.opts.events);
8161
8265
  if (!current) return;
8162
8266
  if (current.engineState === state) return;
8163
- await saveGoal(this.goalPath, { ...current, engineState: state });
8267
+ await saveGoal(this.goalPath, { ...current, engineState: state }, this.opts.events);
8164
8268
  }
8165
8269
  };
8166
8270
 
@@ -8629,7 +8733,7 @@ function parseDescription(raw) {
8629
8733
  const scope = [];
8630
8734
  const coversMatch = /(?:covers|for|including)\s+([^.]+)/i.exec(desc);
8631
8735
  if (coversMatch) {
8632
- const items = coversMatch[1] ?? "".replace(/[·•]/g, ",").split(",").map((s) => s.trim()).filter(Boolean);
8736
+ const items = (coversMatch[1] ?? "").replace(/[·•]/g, ",").split(",").map((s) => s.trim()).filter(Boolean);
8633
8737
  scope.push(...items);
8634
8738
  }
8635
8739
  return { trigger, scope };