@wrongstack/core 0.291.2 → 0.292.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. package/dist/coordination/director-tools.d.ts.map +1 -1
  2. package/dist/coordination/index.d.ts +1 -1
  3. package/dist/coordination/index.d.ts.map +1 -1
  4. package/dist/coordination/index.js +276 -152
  5. package/dist/coordination/index.js.map +4 -4
  6. package/dist/coordination/mailbox-http-router.d.ts +38 -0
  7. package/dist/coordination/mailbox-http-router.d.ts.map +1 -1
  8. package/dist/coordination/provider-status-tracker.d.ts.map +1 -1
  9. package/dist/core/agent-loop.d.ts.map +1 -1
  10. package/dist/core/agent-response.d.ts.map +1 -1
  11. package/dist/core/context.d.ts +10 -0
  12. package/dist/core/context.d.ts.map +1 -1
  13. package/dist/core/fallback-model.d.ts.map +1 -1
  14. package/dist/core/system-prompt-builder.d.ts +20 -1
  15. package/dist/core/system-prompt-builder.d.ts.map +1 -1
  16. package/dist/defaults/index.d.ts +1 -0
  17. package/dist/defaults/index.d.ts.map +1 -1
  18. package/dist/defaults/index.js +3137 -2441
  19. package/dist/defaults/index.js.map +4 -4
  20. package/dist/execution/auto-compaction-middleware.d.ts +34 -2
  21. package/dist/execution/auto-compaction-middleware.d.ts.map +1 -1
  22. package/dist/execution/compaction-core.d.ts +59 -0
  23. package/dist/execution/compaction-core.d.ts.map +1 -1
  24. package/dist/execution/compactor.d.ts.map +1 -1
  25. package/dist/execution/error-handler.d.ts +7 -0
  26. package/dist/execution/error-handler.d.ts.map +1 -1
  27. package/dist/execution/index.d.ts +1 -0
  28. package/dist/execution/index.d.ts.map +1 -1
  29. package/dist/execution/index.js +4684 -3924
  30. package/dist/execution/index.js.map +4 -4
  31. package/dist/execution/intelligent-compactor.d.ts.map +1 -1
  32. package/dist/execution/model-runtime.d.ts.map +1 -1
  33. package/dist/execution/one-shot-llm.d.ts.map +1 -1
  34. package/dist/execution/selective-compactor.d.ts +10 -0
  35. package/dist/execution/selective-compactor.d.ts.map +1 -1
  36. package/dist/execution/subagent-compaction.d.ts +24 -0
  37. package/dist/execution/subagent-compaction.d.ts.map +1 -0
  38. package/dist/hq/alerts.d.ts +5 -0
  39. package/dist/hq/alerts.d.ts.map +1 -1
  40. package/dist/hq/auth-audit.d.ts +115 -0
  41. package/dist/hq/auth-audit.d.ts.map +1 -0
  42. package/dist/hq/auth-store.d.ts +98 -2
  43. package/dist/hq/auth-store.d.ts.map +1 -1
  44. package/dist/hq/brain-bridge.d.ts +2 -8
  45. package/dist/hq/brain-bridge.d.ts.map +1 -1
  46. package/dist/hq/bridge-context.d.ts +74 -0
  47. package/dist/hq/bridge-context.d.ts.map +1 -0
  48. package/dist/hq/cost-bridge.d.ts +4 -10
  49. package/dist/hq/cost-bridge.d.ts.map +1 -1
  50. package/dist/hq/fleet-bridge.d.ts +2 -8
  51. package/dist/hq/fleet-bridge.d.ts.map +1 -1
  52. package/dist/hq/index.d.ts +2 -0
  53. package/dist/hq/index.d.ts.map +1 -1
  54. package/dist/hq/index.js +358 -222
  55. package/dist/hq/index.js.map +4 -4
  56. package/dist/hq/protocol/session.d.ts +13 -0
  57. package/dist/hq/protocol/session.d.ts.map +1 -1
  58. package/dist/hq/tool-bridge.d.ts +2 -8
  59. package/dist/hq/tool-bridge.d.ts.map +1 -1
  60. package/dist/hq/worktree-bridge.d.ts +2 -8
  61. package/dist/hq/worktree-bridge.d.ts.map +1 -1
  62. package/dist/index.d.ts +4 -2
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +6332 -4853
  65. package/dist/index.js.map +4 -4
  66. package/dist/infrastructure/index.js +32 -15
  67. package/dist/infrastructure/index.js.map +2 -2
  68. package/dist/infrastructure/provider-cache-ledger.d.ts +48 -0
  69. package/dist/infrastructure/provider-cache-ledger.d.ts.map +1 -0
  70. package/dist/infrastructure/token-counter.d.ts +4 -2
  71. package/dist/infrastructure/token-counter.d.ts.map +1 -1
  72. package/dist/kernel/events/brain-events.d.ts +2 -0
  73. package/dist/kernel/events/brain-events.d.ts.map +1 -1
  74. package/dist/kernel/events/memory-events.d.ts +102 -0
  75. package/dist/kernel/events/memory-events.d.ts.map +1 -1
  76. package/dist/kernel/events/session-events.d.ts +35 -0
  77. package/dist/kernel/events/session-events.d.ts.map +1 -1
  78. package/dist/models/index.js +161 -78
  79. package/dist/models/index.js.map +3 -3
  80. package/dist/models/llm-selector.d.ts.map +1 -1
  81. package/dist/notifications/index.d.ts +40 -0
  82. package/dist/notifications/index.d.ts.map +1 -0
  83. package/dist/notifications/notifier-impl.d.ts +34 -0
  84. package/dist/notifications/notifier-impl.d.ts.map +1 -0
  85. package/dist/notifications/notifier.d.ts +95 -0
  86. package/dist/notifications/notifier.d.ts.map +1 -0
  87. package/dist/notifications/types.d.ts +111 -0
  88. package/dist/notifications/types.d.ts.map +1 -0
  89. package/dist/plugin/api.d.ts +10 -1
  90. package/dist/plugin/api.d.ts.map +1 -1
  91. package/dist/registry/provider-registry.d.ts +1 -1
  92. package/dist/registry/provider-registry.d.ts.map +1 -1
  93. package/dist/security/index.js +15 -15
  94. package/dist/security/index.js.map +3 -3
  95. package/dist/skills/index.js +80 -80
  96. package/dist/skills/index.js.map +3 -3
  97. package/dist/storage/config-loader.d.ts +3 -5
  98. package/dist/storage/config-loader.d.ts.map +1 -1
  99. package/dist/storage/index.js +325 -259
  100. package/dist/storage/index.js.map +4 -4
  101. package/dist/storage/memory-consolidator.d.ts +14 -2
  102. package/dist/storage/memory-consolidator.d.ts.map +1 -1
  103. package/dist/storage/queue-store.d.ts +2 -0
  104. package/dist/storage/queue-store.d.ts.map +1 -1
  105. package/dist/tools/index.js +14 -7
  106. package/dist/tools/index.js.map +3 -3
  107. package/dist/tools/mcp-control.d.ts +1 -1
  108. package/dist/types/config.d.ts +64 -20
  109. package/dist/types/config.d.ts.map +1 -1
  110. package/dist/types/index.js +65 -35
  111. package/dist/types/index.js.map +3 -3
  112. package/dist/types/plugin.d.ts +11 -0
  113. package/dist/types/plugin.d.ts.map +1 -1
  114. package/dist/types/provider.d.ts +31 -0
  115. package/dist/types/provider.d.ts.map +1 -1
  116. package/dist/types/token-counter.d.ts +8 -1
  117. package/dist/types/token-counter.d.ts.map +1 -1
  118. package/dist/utils/cache-key.d.ts +19 -0
  119. package/dist/utils/cache-key.d.ts.map +1 -0
  120. package/dist/utils/compaction-preview.d.ts +8 -0
  121. package/dist/utils/compaction-preview.d.ts.map +1 -0
  122. package/dist/utils/connectivity.d.ts +36 -0
  123. package/dist/utils/connectivity.d.ts.map +1 -0
  124. package/dist/utils/context-breakdown.d.ts +59 -0
  125. package/dist/utils/context-breakdown.d.ts.map +1 -0
  126. package/dist/utils/context-evidence.d.ts +22 -0
  127. package/dist/utils/context-evidence.d.ts.map +1 -1
  128. package/dist/utils/index.d.ts +3 -0
  129. package/dist/utils/index.d.ts.map +1 -1
  130. package/dist/utils/index.js +1761 -1541
  131. package/dist/utils/index.js.map +4 -4
  132. package/dist/utils/token-estimate.d.ts +27 -0
  133. package/dist/utils/token-estimate.d.ts.map +1 -1
  134. package/instructions/llm/memory-consolidator.md +19 -3
  135. package/instructions/system.md +10 -2
  136. package/package.json +2 -2
@@ -727,6 +727,50 @@ async function backupConfigFile(filePath, paths) {
727
727
  }
728
728
  }
729
729
 
730
+ // src/utils/connectivity.ts
731
+ var DEFAULT_PROBE_URL = "https://1.1.1.1";
732
+ var DEFAULT_TIMEOUT_MS = 5e3;
733
+ var DEFAULT_TTL_MS = 3e4;
734
+ var cached;
735
+ async function checkConnectivity(opts) {
736
+ const url = opts?.probeUrl ?? DEFAULT_PROBE_URL;
737
+ const timeout = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
738
+ const ttl = opts?.ttlMs ?? DEFAULT_TTL_MS;
739
+ if (cached && ttl > 0 && Date.now() - cached.at < ttl) {
740
+ return cached.ok;
741
+ }
742
+ cached = { ok: await probe(url, timeout), at: Date.now() };
743
+ return cached.ok;
744
+ }
745
+ async function probe(url, timeoutMs) {
746
+ const controller = new AbortController();
747
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
748
+ try {
749
+ await fetch(url, { method: "HEAD", signal: controller.signal });
750
+ return true;
751
+ } catch {
752
+ return false;
753
+ } finally {
754
+ clearTimeout(timer);
755
+ }
756
+ }
757
+ function resetConnectivityCache() {
758
+ cached = void 0;
759
+ }
760
+
761
+ // src/utils/cache-key.ts
762
+ import { createHash } from "node:crypto";
763
+ var keyCache = /* @__PURE__ */ new WeakMap();
764
+ function deriveCachePrefixKey(systemPrompt) {
765
+ const cached2 = keyCache.get(systemPrompt);
766
+ if (cached2 !== void 0) return cached2;
767
+ const h = createHash("sha256");
768
+ for (const block of systemPrompt) h.update(block.text).update("\0");
769
+ const key = `ws-${h.digest("hex").slice(0, 32)}`;
770
+ keyCache.set(systemPrompt, key);
771
+ return key;
772
+ }
773
+
730
774
  // src/utils/config-json.ts
731
775
  import * as fs3 from "node:fs/promises";
732
776
  async function readJsonObjectFile(filePath) {
@@ -1209,217 +1253,6 @@ function metadataReferencedByText(metadata, haystack) {
1209
1253
  return false;
1210
1254
  }
1211
1255
 
1212
- // src/utils/deep-merge.ts
1213
- var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
1214
- "__proto__",
1215
- "constructor",
1216
- "prototype",
1217
- "__defineGetter__",
1218
- "__defineSetter__",
1219
- "__lookupGetter__",
1220
- "__lookupSetter__"
1221
- ]);
1222
- function isPrimitiveArray(a) {
1223
- return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
1224
- }
1225
- function deepMerge(base, patch, options = {}) {
1226
- const {
1227
- conflictResolution = "prefer-patch",
1228
- arrayMode = "replace",
1229
- protectProto = true,
1230
- onNonPrimitiveArrayReplace
1231
- } = options;
1232
- if (typeof base !== "object" || base === null) {
1233
- return conflictResolution === "prefer-patch" ? patch : base;
1234
- }
1235
- if (typeof patch !== "object" || patch === null) {
1236
- return conflictResolution === "prefer-patch" ? patch : base;
1237
- }
1238
- if (Array.isArray(base) && Array.isArray(patch)) {
1239
- if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
1240
- return [.../* @__PURE__ */ new Set([...base, ...patch])];
1241
- }
1242
- return conflictResolution === "prefer-patch" ? patch : base;
1243
- }
1244
- if (Array.isArray(base) || Array.isArray(patch)) {
1245
- return conflictResolution === "prefer-patch" ? patch : base;
1246
- }
1247
- const baseObj = base;
1248
- const patchObj = patch;
1249
- const out = { ...baseObj };
1250
- for (const [k, v] of Object.entries(patchObj)) {
1251
- if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
1252
- const existing = out[k];
1253
- if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
1254
- out[k] = deepMerge(existing, v, options);
1255
- } else if (Array.isArray(v) && Array.isArray(existing)) {
1256
- if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
1257
- onNonPrimitiveArrayReplace(k, existing.length, v.length);
1258
- }
1259
- out[k] = deepMerge(existing, v, options);
1260
- } else if (v !== void 0) {
1261
- if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
1262
- const existingLen = Array.isArray(existing) ? existing.length : 0;
1263
- onNonPrimitiveArrayReplace(k, existingLen, v.length);
1264
- }
1265
- out[k] = v;
1266
- }
1267
- }
1268
- return out;
1269
- }
1270
-
1271
- // src/utils/diff.ts
1272
- function myersDiff(a, b) {
1273
- const N = a.length;
1274
- const M = b.length;
1275
- const max = N + M;
1276
- if (max === 0) return [];
1277
- const v = /* @__PURE__ */ new Map();
1278
- v.set(1, 0);
1279
- const trace = [];
1280
- for (let d = 0; d <= max; d++) {
1281
- const snapshot = new Map(v);
1282
- trace.push(snapshot);
1283
- for (let k = -d; k <= d; k += 2) {
1284
- const left = v.get(k - 1) ?? -1;
1285
- const right = v.get(k + 1) ?? -1;
1286
- let x;
1287
- if (k === -d || k !== d && left < right) {
1288
- x = right;
1289
- } else {
1290
- x = left + 1;
1291
- }
1292
- let y = x - k;
1293
- while (x < N && y < M && a[x] === b[y]) {
1294
- x++;
1295
- y++;
1296
- }
1297
- v.set(k, x);
1298
- if (x >= N && y >= M) {
1299
- return backtrack(trace, a, b, N, M, d);
1300
- }
1301
- }
1302
- }
1303
- return [];
1304
- }
1305
- function backtrack(trace, a, b, N, M, finalD) {
1306
- const edits = [];
1307
- let x = N;
1308
- let y = M;
1309
- for (let d = finalD; d > 0; d--) {
1310
- const v = trace[d];
1311
- if (!v) break;
1312
- const k = x - y;
1313
- const left = v.get(k - 1) ?? -1;
1314
- const right = v.get(k + 1) ?? -1;
1315
- let prevK;
1316
- if (k === -d || k !== d && left < right) {
1317
- prevK = k + 1;
1318
- } else {
1319
- prevK = k - 1;
1320
- }
1321
- const prevX = v.get(prevK) ?? 0;
1322
- const prevY = prevX - prevK;
1323
- while (x > prevX && y > prevY) {
1324
- edits.push({ op: "equal", a: x - 1, b: y - 1, line: a[x - 1] ?? "" });
1325
- x--;
1326
- y--;
1327
- }
1328
- if (d > 0) {
1329
- if (x === prevX) {
1330
- edits.push({ op: "insert", a: x, b: y - 1, line: b[y - 1] ?? "" });
1331
- } else {
1332
- edits.push({ op: "delete", a: x - 1, b: y, line: a[x - 1] ?? "" });
1333
- }
1334
- x = prevX;
1335
- y = prevY;
1336
- }
1337
- }
1338
- while (x > 0 && y > 0) {
1339
- edits.push({ op: "equal", a: x - 1, b: y - 1, line: a[x - 1] ?? "" });
1340
- x--;
1341
- y--;
1342
- }
1343
- return edits.reverse();
1344
- }
1345
- function unifiedDiff(oldText, newText, opts = {}) {
1346
- const context = opts.context ?? 3;
1347
- const a = oldText.split("\n");
1348
- const b = newText.split("\n");
1349
- if (a[a.length - 1] === "") a.pop();
1350
- if (b[b.length - 1] === "") b.pop();
1351
- const edits = myersDiff(a, b);
1352
- if (edits.every((e) => e.op === "equal")) return "";
1353
- const hunks = [];
1354
- let i = 0;
1355
- while (i < edits.length) {
1356
- while (i < edits.length && edits[i]?.op === "equal") i++;
1357
- if (i >= edits.length) break;
1358
- const hunkStart = Math.max(0, i - context);
1359
- const lines = [];
1360
- let aStart = (edits[hunkStart]?.a ?? 0) + 1;
1361
- let bStart = (edits[hunkStart]?.b ?? 0) + 1;
1362
- let aCount = 0;
1363
- let bCount = 0;
1364
- let cursor = hunkStart;
1365
- let trailing = 0;
1366
- while (cursor < edits.length) {
1367
- const e = edits[cursor];
1368
- if (!e) break;
1369
- if (e.op === "equal") {
1370
- trailing++;
1371
- if (trailing > context * 2) break;
1372
- } else {
1373
- trailing = 0;
1374
- }
1375
- if (e.op === "equal") {
1376
- lines.push(` ${e.line}`);
1377
- aCount++;
1378
- bCount++;
1379
- } else if (e.op === "delete") {
1380
- lines.push(`-${e.line}`);
1381
- aCount++;
1382
- } else {
1383
- lines.push(`+${e.line}`);
1384
- bCount++;
1385
- }
1386
- cursor++;
1387
- }
1388
- while (lines.length > 0 && lines[lines.length - 1]?.startsWith(" ") && trailing > context) {
1389
- lines.pop();
1390
- aCount--;
1391
- bCount--;
1392
- trailing--;
1393
- }
1394
- if (aCount === 0) aStart = 0;
1395
- if (bCount === 0) bStart = 0;
1396
- hunks.push({ aStart, bStart, lines });
1397
- i = cursor;
1398
- }
1399
- if (hunks.length === 0) return "";
1400
- let out = "";
1401
- out += `--- ${opts.fromFile ?? "a"}
1402
- `;
1403
- out += `+++ ${opts.toFile ?? "b"}
1404
- `;
1405
- for (const h of hunks) {
1406
- let aCount = 0;
1407
- let bCount = 0;
1408
- for (const l of h.lines) {
1409
- if (l.startsWith(" ")) {
1410
- aCount++;
1411
- bCount++;
1412
- } else if (l.startsWith("-")) aCount++;
1413
- else if (l.startsWith("+")) bCount++;
1414
- }
1415
- out += `@@ -${h.aStart},${aCount} +${h.bStart},${bCount} @@
1416
- `;
1417
- out += `${h.lines.join("\n")}
1418
- `;
1419
- }
1420
- return out;
1421
- }
1422
-
1423
1256
  // src/utils/error.ts
1424
1257
  function toErrorMessage(err) {
1425
1258
  return err instanceof Error ? err.message : String(err);
@@ -1435,1511 +1268,1894 @@ function expectDefined(value, label) {
1435
1268
  return value;
1436
1269
  }
1437
1270
 
1438
- // src/utils/glob-expand.ts
1439
- import * as fsp from "node:fs/promises";
1440
- import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
1441
- var GLOB_CHARS = /* @__PURE__ */ new Set(["*", "?", "["]);
1442
- var IS_WINDOWS = process.platform === "win32";
1443
- var SEP = IS_WINDOWS ? "\\" : "/";
1444
- function isGlob(p) {
1445
- for (const c of p) {
1446
- if (GLOB_CHARS.has(c)) return true;
1447
- }
1448
- return false;
1449
- }
1450
- function globToRegex(pat) {
1451
- let i = 0;
1452
- let re = "^";
1453
- while (i < pat.length) {
1454
- const c = expectDefined(pat[i]);
1455
- if (c === "*") {
1456
- if (pat[i + 1] === "*") {
1457
- re += ".*";
1458
- i += 2;
1459
- if (pat[i] === "/") i++;
1460
- } else {
1461
- re += "[^/\\\\]*";
1462
- i++;
1463
- }
1464
- } else if (c === "?") {
1465
- re += "[^/\\\\]";
1466
- i++;
1467
- } else if (c === "[") {
1468
- let cls = "[";
1469
- i++;
1470
- if (pat[i] === "!" || pat[i] === "^") {
1471
- cls += "^";
1472
- i++;
1473
- }
1474
- while (i < pat.length && pat[i] !== "]") {
1475
- const ch = pat[i] ?? "";
1476
- if (ch === "\\") cls += "\\\\";
1477
- else if (ch === "]" || ch === "^") cls += `\\${ch}`;
1478
- else cls += ch;
1479
- i++;
1480
- }
1481
- cls += "]";
1482
- re += cls;
1483
- i++;
1484
- } else {
1485
- re += c.replace(/[.+^${}()|\\]/g, "\\$&");
1486
- i++;
1487
- }
1488
- }
1489
- return new RegExp(re + "$");
1490
- }
1491
- function baseDir(pat) {
1492
- let firstGlob = pat.length;
1493
- for (let i = 0; i < pat.length; i++) {
1494
- if (GLOB_CHARS.has(expectDefined(pat[i]))) {
1495
- firstGlob = i;
1496
- break;
1497
- }
1498
- }
1499
- const cut = Math.max(
1500
- pat.lastIndexOf(SEP, firstGlob - 1),
1501
- pat.lastIndexOf("/", firstGlob - 1)
1502
- );
1503
- return cut < 0 ? "." : pat.slice(0, cut);
1504
- }
1505
- async function expandGlob(pattern) {
1506
- if (!isGlob(pattern)) return [pattern];
1507
- const results = /* @__PURE__ */ new Set();
1508
- const abs = isAbsolute2(pattern);
1509
- const base = abs ? baseDir(pattern) : baseDir(pattern);
1510
- const relPat = base === "." ? pattern : pattern.slice(base.length + 1);
1511
- async function walk2(dir, pat) {
1512
- let entries;
1513
- try {
1514
- entries = await fsp.readdir(dir);
1515
- } catch {
1516
- return;
1517
- }
1518
- const firstGlob = pat.search(/[*?[[]/);
1519
- if (firstGlob < 0) {
1520
- const re = globToRegex(pat);
1521
- for (const e of entries) {
1522
- if (re.test(e)) {
1523
- const full = `${dir}${SEP}${e}`;
1524
- results.add(abs ? resolve2(full) : full);
1271
+ // src/utils/message-invariants.ts
1272
+ function repairToolUseAdjacency(messages) {
1273
+ const removedToolUses = [];
1274
+ const removedToolResults = [];
1275
+ let removedMessages = 0;
1276
+ let changed = false;
1277
+ const out = [];
1278
+ for (let i = 0; i < messages.length; i++) {
1279
+ const original = expectDefined(messages[i]);
1280
+ let msg = original;
1281
+ if (hasToolUse(msg)) {
1282
+ const nextIds = toolResultIds(messages[i + 1]);
1283
+ const filtered = mapContent(msg, (blocks) => {
1284
+ const next = [];
1285
+ for (const block of blocks) {
1286
+ if (block.type === "tool_use" && !nextIds.has(block.id)) {
1287
+ removedToolUses.push(block.id);
1288
+ changed = true;
1289
+ continue;
1290
+ }
1291
+ next.push(block);
1525
1292
  }
1526
- }
1527
- return;
1293
+ return next;
1294
+ });
1295
+ msg = filtered ?? msg;
1528
1296
  }
1529
- const before = pat.slice(0, firstGlob);
1530
- const rest = pat.slice(firstGlob);
1531
- if (before.endsWith("**")) {
1532
- await walk2(dir, rest);
1533
- for (const e of entries) {
1534
- const full = `${dir}${SEP}${e}`;
1535
- try {
1536
- const stat3 = await fsp.stat(full);
1537
- if (stat3.isDirectory()) await walk2(full, rest);
1538
- } catch {
1539
- }
1540
- }
1541
- } else if (before === "") {
1542
- const re = globToRegex(rest);
1543
- for (const e of entries) {
1544
- if (re.test(e)) {
1545
- const full = `${dir}${SEP}${e}`;
1546
- results.add(abs ? resolve2(full) : full);
1547
- }
1548
- }
1549
- } else {
1550
- const seg = before.replace(/[*?[\]]/g, "").replace(/\/$/, "");
1551
- if (entries.includes(seg)) {
1552
- const full = `${dir}${SEP}${seg}`;
1553
- try {
1554
- const stat3 = await fsp.stat(full);
1555
- if (stat3.isDirectory()) await walk2(full, rest);
1556
- } catch {
1297
+ if (hasToolResult(msg)) {
1298
+ const allowed = toolUseIds(out[out.length - 1]);
1299
+ const filtered = mapContent(msg, (blocks) => {
1300
+ const next = [];
1301
+ for (const block of blocks) {
1302
+ if (block.type === "tool_result" && !allowed.has(block.tool_use_id)) {
1303
+ removedToolResults.push(block.tool_use_id);
1304
+ changed = true;
1305
+ continue;
1306
+ }
1307
+ next.push(block);
1557
1308
  }
1558
- }
1309
+ return next;
1310
+ });
1311
+ msg = filtered ?? msg;
1312
+ }
1313
+ if (isEmptyMessage(msg)) {
1314
+ removedMessages++;
1315
+ changed = true;
1316
+ continue;
1559
1317
  }
1318
+ out.push(msg);
1560
1319
  }
1561
- await walk2(base === "." ? "." : base, relPat);
1562
- return [...results];
1320
+ return {
1321
+ messages: changed ? out : messages,
1322
+ report: { changed, removedToolUses, removedToolResults, removedMessages }
1323
+ };
1563
1324
  }
1564
-
1565
- // src/utils/glob-match.ts
1566
- function escapeRegex(s) {
1567
- return s.replace(/[.+^${}()|\\]/g, "\\$&");
1325
+ function hasToolUse(msg) {
1326
+ return contentBlocks(msg).some((b) => b.type === "tool_use");
1568
1327
  }
1569
- var COMPILED_GLOB_CACHE = /* @__PURE__ */ new Map();
1570
- var CACHE_MAX_SIZE = 2e3;
1571
- var NEVER_MATCH = /[^\s\S]/;
1572
- function getCachedGlob(pattern) {
1573
- const cached = COMPILED_GLOB_CACHE.get(pattern);
1574
- if (cached) return cached;
1575
- if (COMPILED_GLOB_CACHE.size >= CACHE_MAX_SIZE) {
1576
- const keys = [...COMPILED_GLOB_CACHE.keys()];
1577
- for (let i = 0; i < Math.floor(CACHE_MAX_SIZE / 4); i++) {
1578
- COMPILED_GLOB_CACHE.delete(expectDefined(keys[i]));
1579
- }
1328
+ function hasToolResult(msg) {
1329
+ return contentBlocks(msg).some((b) => b.type === "tool_result");
1330
+ }
1331
+ function toolUseIds(msg) {
1332
+ const ids = /* @__PURE__ */ new Set();
1333
+ if (msg?.role !== "assistant") return ids;
1334
+ for (const block of contentBlocks(msg)) {
1335
+ if (block.type === "tool_use") ids.add(block.id);
1580
1336
  }
1581
- let re;
1582
- try {
1583
- re = compileGlob(pattern);
1584
- } catch {
1585
- re = NEVER_MATCH;
1337
+ return ids;
1338
+ }
1339
+ function toolResultIds(msg) {
1340
+ const ids = /* @__PURE__ */ new Set();
1341
+ if (msg?.role !== "user") return ids;
1342
+ for (const block of contentBlocks(msg)) {
1343
+ if (block.type === "tool_result") ids.add(block.tool_use_id);
1586
1344
  }
1587
- COMPILED_GLOB_CACHE.set(pattern, re);
1588
- return re;
1345
+ return ids;
1589
1346
  }
1590
- var MAX_GLOB_PATTERN_LEN = 1024;
1591
- function compileGlob(pattern) {
1592
- if (pattern.length > MAX_GLOB_PATTERN_LEN) {
1593
- throw new Error(`Glob pattern exceeds ${MAX_GLOB_PATTERN_LEN} characters`);
1347
+ function contentBlocks(msg) {
1348
+ return msg && Array.isArray(msg.content) ? msg.content : [];
1349
+ }
1350
+ function mapContent(msg, fn) {
1351
+ if (!Array.isArray(msg.content)) return msg;
1352
+ const next = fn(msg.content);
1353
+ if (next.length === msg.content.length && next.every((b, idx) => b === msg.content[idx])) {
1354
+ return msg;
1594
1355
  }
1595
- let i = 0;
1596
- let re = "^";
1597
- while (i < pattern.length) {
1598
- const c = pattern[i];
1599
- if (c === "*") {
1600
- if (pattern[i + 1] === "*") {
1601
- re += ".*";
1602
- i += 2;
1603
- if (pattern[i] === "/") i++;
1604
- } else {
1605
- re += "[^/]*";
1606
- i++;
1607
- }
1608
- } else if (c === "?") {
1609
- re += "[^/]";
1610
- i++;
1611
- } else if (c === "[") {
1612
- let cls = "[";
1613
- i++;
1614
- if (pattern[i] === "!" || pattern[i] === "^") {
1615
- cls += "^";
1616
- i++;
1617
- }
1618
- while (i < pattern.length && pattern[i] !== "]") {
1619
- const ch = pattern[i] ?? "";
1620
- if (ch === "\\") {
1621
- cls += "\\\\";
1622
- } else if (ch === "]" || ch === "^") {
1623
- cls += `\\${ch}`;
1624
- } else {
1625
- cls += ch;
1626
- }
1627
- i++;
1628
- }
1629
- cls += "]";
1630
- re += cls;
1631
- i++;
1632
- } else {
1633
- re += escapeRegex(c ?? "");
1634
- i++;
1356
+ return { ...msg, content: next };
1357
+ }
1358
+ function hasMeaningfulContent(content) {
1359
+ if (typeof content === "string") return content.trim().length > 0;
1360
+ for (const block of content) {
1361
+ if (block.type === "text") {
1362
+ if (block.text.trim().length > 0) return true;
1363
+ continue;
1364
+ }
1365
+ if (block.type === "thinking") {
1366
+ if (block.thinking.trim().length > 0 || block.signature) return true;
1367
+ continue;
1635
1368
  }
1369
+ return true;
1636
1370
  }
1637
- re += "$";
1638
- return new RegExp(re);
1639
- }
1640
- function matchGlob(pattern, input) {
1641
- return getCachedGlob(pattern).test(input);
1371
+ return false;
1642
1372
  }
1643
- function matchAny(patterns, input) {
1644
- return patterns.some((p) => matchGlob(p, input));
1373
+ function isEmptyMessage(msg) {
1374
+ return !hasMeaningfulContent(msg.content);
1645
1375
  }
1646
1376
 
1647
- // src/utils/incoming-images.ts
1648
- var MAX_INCOMING_IMAGES = 8;
1649
- var MAX_INCOMING_IMAGE_BYTES = 8 * 1024 * 1024;
1650
- var ALLOWED_IMAGE_MEDIA_TYPES = /* @__PURE__ */ new Set([
1651
- "image/png",
1652
- "image/jpeg",
1653
- "image/webp",
1654
- "image/gif"
1655
- ]);
1656
- var IncomingImageError = class extends Error {
1657
- constructor(message) {
1658
- super(message);
1659
- this.name = "IncomingImageError";
1377
+ // src/core/agent-response.ts
1378
+ var MAX_TODO_SNAPSHOT_ITEMS = 10;
1379
+ var MAX_TODO_SNAPSHOT_CONTENT = 180;
1380
+ function buildLiveNextStepsGateBlock(ctx) {
1381
+ if (ctx.agentId !== "leader") return void 0;
1382
+ const openTodos = ctx.todos.filter(
1383
+ (todo) => todo.status === "pending" || todo.status === "in_progress"
1384
+ );
1385
+ if (openTodos.length === 0) {
1386
+ return {
1387
+ type: "text",
1388
+ text: [
1389
+ "[nextsteps_gate]",
1390
+ "Authoritative live state for this request: open todos = 0.",
1391
+ "On the final response, you MUST take exactly one branch:",
1392
+ "1. If at least one genuinely useful follow-on action exists, include a balanced <nextsteps> block containing 1-4 exact prompt messages that can be submitted back to you through the current TUI or WebUI input.",
1393
+ "Every item must ask the agent to perform work. Never put a human-only chore or an instruction addressed to the user inside <nextsteps>; natural-language agent-directed imperatives are valid and need not be shell commands.",
1394
+ "2. If no useful follow-on action truly exists, omit <nextsteps> and explicitly tell the user in normal prose that no further steps are needed for this task.",
1395
+ "Silently omitting both is invalid. Do not decide by chance, tone, or response length, and do not invent filler suggestions.",
1396
+ "[/nextsteps_gate]"
1397
+ ].join("\n"),
1398
+ cache_control: { type: "ephemeral" }
1399
+ };
1660
1400
  }
1661
- };
1662
- var DATA_URL_RE = /^data:([a-z0-9.+-]+\/[a-z0-9.+-]+)?(?:;[a-z0-9-]+=[^;,]*)*(;base64)?,/i;
1663
- function splitDataUrl(data) {
1664
- const match = DATA_URL_RE.exec(data);
1665
- if (!match) return { base64: data.trim() };
1401
+ const todoSnapshot = openTodos.slice(0, MAX_TODO_SNAPSHOT_ITEMS).map((todo) => {
1402
+ const normalized = todo.content.replace(/\s+/g, " ").trim();
1403
+ const content = normalized.length > MAX_TODO_SNAPSHOT_CONTENT ? `${normalized.slice(0, MAX_TODO_SNAPSHOT_CONTENT - 1)}\u2026` : normalized;
1404
+ return `- [${todo.status}] ${content}`;
1405
+ });
1406
+ const omitted = openTodos.length - todoSnapshot.length;
1407
+ if (omitted > 0) todoSnapshot.push(`- \u2026and ${omitted} more open todo(s)`);
1666
1408
  return {
1667
- base64: data.slice(match[0].length).trim(),
1668
- mediaType: match[1]?.toLowerCase()
1409
+ type: "text",
1410
+ text: [
1411
+ "[nextsteps_gate]",
1412
+ `Authoritative live state for this request: open todos = ${openTodos.length}.`,
1413
+ "You MUST omit <nextsteps> entirely while these todos remain open. Continue or finish the tracked work; do not propose unrelated follow-on work.",
1414
+ "Open todo snapshot:",
1415
+ ...todoSnapshot,
1416
+ "[/nextsteps_gate]"
1417
+ ].join("\n"),
1418
+ cache_control: { type: "ephemeral" }
1669
1419
  };
1670
1420
  }
1671
- function parseIncomingImages(images, legacyImageBase64) {
1672
- const raw = [...images ?? []];
1673
- if (legacyImageBase64) raw.push({ data: legacyImageBase64 });
1674
- if (raw.length === 0) return [];
1675
- if (raw.length > MAX_INCOMING_IMAGES) {
1676
- throw new IncomingImageError(
1677
- `Too many images: ${raw.length} (max ${MAX_INCOMING_IMAGES} per message).`
1678
- );
1421
+
1422
+ // src/core/system-prompt-builder.ts
1423
+ var SYSTEM_BLOCK_SOURCE = /* @__PURE__ */ new WeakMap();
1424
+
1425
+ // src/utils/tool-wire-compact.ts
1426
+ var TOOL_DESCRIPTION_MAX_CHARS = 400;
1427
+ var SCHEMA_DESCRIPTION_MAX_CHARS = 120;
1428
+ var compactCache = /* @__PURE__ */ new WeakMap();
1429
+ function compactToolDefinitionForWire(tool, opts = {}) {
1430
+ const useDefaultOptions = opts.descriptionMaxChars === void 0 && opts.schemaDescriptionMaxChars === void 0;
1431
+ if (useDefaultOptions && typeof tool === "object" && tool !== null) {
1432
+ const cached2 = compactCache.get(tool);
1433
+ if (cached2) return cached2;
1679
1434
  }
1680
- return raw.map((img, i) => {
1681
- const { base64, mediaType: fromUrl } = splitDataUrl(img.data ?? "");
1682
- const mediaType = (img.mediaType ?? fromUrl ?? "image/png").toLowerCase();
1683
- if (!ALLOWED_IMAGE_MEDIA_TYPES.has(mediaType)) {
1684
- throw new IncomingImageError(
1685
- `Image ${i + 1}: unsupported media type "${mediaType}" (allowed: ${[...ALLOWED_IMAGE_MEDIA_TYPES].join(", ")}).`
1686
- );
1687
- }
1688
- if (!base64) {
1689
- throw new IncomingImageError(`Image ${i + 1}: empty image data.`);
1690
- }
1691
- if (!/^[A-Za-z0-9+/]+={0,2}$/.test(base64)) {
1692
- throw new IncomingImageError(`Image ${i + 1}: data is not valid base64.`);
1435
+ const compact = {
1436
+ name: tool.name,
1437
+ description: compactDescription(
1438
+ tool.description ?? "",
1439
+ opts.descriptionMaxChars ?? TOOL_DESCRIPTION_MAX_CHARS
1440
+ ),
1441
+ inputSchema: normalizeTopLevelToolSchema(
1442
+ compactSchemaDescriptions(
1443
+ tool.inputSchema,
1444
+ opts.schemaDescriptionMaxChars ?? SCHEMA_DESCRIPTION_MAX_CHARS
1445
+ )
1446
+ )
1447
+ };
1448
+ if (useDefaultOptions && typeof tool === "object" && tool !== null) {
1449
+ compactCache.set(tool, compact);
1450
+ }
1451
+ return compact;
1452
+ }
1453
+ function normalizeTopLevelToolSchema(schema) {
1454
+ const combinators = ["oneOf", "anyOf", "allOf"].map((keyword) => ({ keyword, branches: schema[keyword] })).filter(
1455
+ (entry) => Array.isArray(entry.branches)
1456
+ );
1457
+ if (combinators.length === 0) return schema;
1458
+ const out = { ...schema, type: "object" };
1459
+ delete out["oneOf"];
1460
+ delete out["anyOf"];
1461
+ delete out["allOf"];
1462
+ const properties = isRecord(schema["properties"]) ? { ...schema["properties"] } : {};
1463
+ let required = stringSet(schema["required"]);
1464
+ for (const { keyword, branches } of combinators) {
1465
+ const objectBranches = branches.filter(isRecord);
1466
+ for (const branch of objectBranches) {
1467
+ if (isRecord(branch["properties"])) Object.assign(properties, branch["properties"]);
1693
1468
  }
1694
- const bytes = Math.floor(base64.length * 3 / 4);
1695
- if (bytes > MAX_INCOMING_IMAGE_BYTES) {
1696
- throw new IncomingImageError(
1697
- `Image ${i + 1}: ${(bytes / (1024 * 1024)).toFixed(1)} MB exceeds the ${MAX_INCOMING_IMAGE_BYTES / (1024 * 1024)} MB limit.`
1469
+ const branchRequired = objectBranches.map((branch) => stringSet(branch["required"]));
1470
+ if (keyword === "allOf") {
1471
+ for (const fields of branchRequired) for (const field of fields) required.add(field);
1472
+ } else if (branchRequired.length > 0) {
1473
+ const common = new Set(
1474
+ [...branchRequired[0]].filter(
1475
+ (field) => branchRequired.slice(1).every((fields) => fields.has(field))
1476
+ )
1698
1477
  );
1478
+ required = /* @__PURE__ */ new Set([...required, ...common]);
1699
1479
  }
1700
- return {
1701
- type: "image",
1702
- source: { type: "base64", media_type: mediaType, data: base64 }
1703
- };
1704
- });
1705
- }
1706
- function buildUserContentBlocks(text, images) {
1707
- const blocks = [...images];
1708
- if (text) blocks.push({ type: "text", text });
1709
- return blocks;
1710
- }
1711
-
1712
- // src/utils/ip-guard.ts
1713
- import * as dns from "node:dns/promises";
1714
- import * as net from "node:net";
1715
- function isPrivateIPv4(addr) {
1716
- const parts = addr.split(".").map((p) => Number.parseInt(p, 10));
1717
- if (parts.length !== 4 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255)) {
1718
- return true;
1719
1480
  }
1720
- const [a, b, c] = parts;
1721
- if (a === 0) return true;
1722
- if (a === 10) return true;
1723
- if (a === 127) return true;
1724
- if (a === 169 && b === 254) return true;
1725
- if (a === 172 && b >= 16 && b <= 31) return true;
1726
- if (a === 192 && b === 168) return true;
1727
- if (a === 192 && b === 0 && c === 0) return true;
1728
- if (a === 100 && b >= 64 && b <= 127) return true;
1729
- if (a >= 224) return true;
1730
- return false;
1481
+ out["properties"] = properties;
1482
+ if (required.size > 0) out["required"] = [...required];
1483
+ else delete out["required"];
1484
+ return out;
1731
1485
  }
1732
- function isPrivateIPv6(raw) {
1733
- const lower = raw.toLowerCase();
1734
- if (lower === "::" || lower === "::1") return true;
1735
- const groups = expandIPv6(lower);
1736
- if (!groups) return true;
1737
- if (groups[0] === 0 && groups[1] === 0 && groups[2] === 0 && groups[3] === 0 && groups[4] === 0 && groups[5] === 65535) {
1738
- const a = (groups[6] ?? 0) >> 8;
1739
- const b = (groups[6] ?? 0) & 255;
1740
- const c = (groups[7] ?? 0) >> 8;
1741
- const d = (groups[7] ?? 0) & 255;
1742
- return isPrivateIPv4(`${a}.${b}.${c}.${d}`);
1743
- }
1744
- const high = groups[0] ?? 0;
1745
- if ((high & 65024) === 64512) return true;
1746
- if ((high & 65472) === 65152) return true;
1747
- if ((high & 65280) === 65280) return true;
1748
- return false;
1486
+ function stringSet(value) {
1487
+ return new Set(
1488
+ Array.isArray(value) ? value.filter((item) => typeof item === "string") : []
1489
+ );
1749
1490
  }
1750
- function expandIPv6(addr) {
1751
- const parts = addr.split("::");
1752
- if (parts.length > 2) return null;
1753
- const parseGroups = (s) => {
1754
- if (s === "") return [];
1755
- const out = [];
1756
- for (const g of s.split(":")) {
1757
- if (g.length === 0 || g.length > 4) return null;
1758
- const n = Number.parseInt(g, 16);
1759
- if (Number.isNaN(n) || n < 0 || n > 65535) return null;
1760
- out.push(n);
1761
- }
1762
- return out;
1763
- };
1764
- if (parts.length === 1) {
1765
- const groups = parseGroups(parts[0] ?? "");
1766
- if (groups?.length !== 8) return null;
1767
- return groups;
1768
- }
1769
- const head = parseGroups(parts[0] ?? "");
1770
- const tail = parseGroups(parts[1] ?? "");
1771
- if (!head || !tail) return null;
1772
- const fill = 8 - head.length - tail.length;
1773
- if (fill < 0) return null;
1774
- return [...head, ...new Array(fill).fill(0), ...tail];
1491
+ function compactSchemaDescriptions(schema, maxDescriptionChars = SCHEMA_DESCRIPTION_MAX_CHARS) {
1492
+ const compact = compactSchemaNode(schema, maxDescriptionChars);
1493
+ return isRecord(compact) ? compact : { type: "object", properties: {} };
1775
1494
  }
1776
- async function assertNotPrivateHost(hostname) {
1777
- const host = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
1778
- if (host === "localhost" || host.endsWith(".localhost")) {
1779
- throw new Error("fetch: blocked localhost target");
1495
+ function compactSchemaNode(node, maxDescriptionChars) {
1496
+ if (Array.isArray(node)) {
1497
+ return node.map((item) => compactSchemaNode(item, maxDescriptionChars));
1780
1498
  }
1781
- const ipVersion = net.isIP(host);
1782
- if (ipVersion === 4) {
1783
- if (isPrivateIPv4(host)) {
1784
- throw new Error(`fetch: blocked private/loopback address "${host}"`);
1785
- }
1786
- } else if (ipVersion === 6) {
1787
- if (isPrivateIPv6(host)) {
1788
- throw new Error(`fetch: blocked private/loopback address "${host}"`);
1789
- }
1790
- } else {
1791
- try {
1792
- const records = await dns.lookup(host, { all: true });
1793
- for (const r of records) {
1794
- const bad = r.family === 4 ? isPrivateIPv4(r.address) : isPrivateIPv6(r.address);
1795
- if (bad) {
1796
- throw new Error(`fetch: resolved to private address ${r.address}`);
1797
- }
1798
- }
1799
- } catch (err) {
1800
- if (err instanceof Error && err.message.startsWith("fetch:")) throw err;
1499
+ if (!isRecord(node)) return node;
1500
+ const out = {};
1501
+ for (const [key, value] of Object.entries(node)) {
1502
+ if (key === "description" && typeof value === "string") {
1503
+ out[key] = compactDescription(value, maxDescriptionChars);
1504
+ } else {
1505
+ out[key] = compactSchemaNode(value, maxDescriptionChars);
1801
1506
  }
1802
1507
  }
1508
+ return out;
1509
+ }
1510
+ function compactDescription(text, maxChars) {
1511
+ const normalized = text.replace(/\s+/g, " ").trim();
1512
+ if (normalized.length <= maxChars) return normalized;
1513
+ if (maxChars <= 20) return normalized.slice(0, maxChars);
1514
+ const hardLimit = maxChars - 12;
1515
+ const boundary = findSemanticBoundary(normalized, hardLimit);
1516
+ const head = normalized.slice(0, boundary > 0 ? boundary : hardLimit).trimEnd();
1517
+ return `${head} ...`;
1518
+ }
1519
+ function findSemanticBoundary(text, limit) {
1520
+ const punctuation = Math.max(
1521
+ text.lastIndexOf(". ", limit),
1522
+ text.lastIndexOf("; ", limit),
1523
+ text.lastIndexOf(": ", limit)
1524
+ );
1525
+ if (punctuation >= Math.floor(limit * 0.45)) return punctuation + 1;
1526
+ const comma = text.lastIndexOf(", ", limit);
1527
+ if (comma >= Math.floor(limit * 0.6)) return comma + 1;
1528
+ const space = text.lastIndexOf(" ", limit);
1529
+ return space >= Math.floor(limit * 0.6) ? space : limit;
1530
+ }
1531
+ function isRecord(value) {
1532
+ return !!value && typeof value === "object" && !Array.isArray(value);
1803
1533
  }
1804
1534
 
1805
- // src/utils/json-repair.ts
1806
- function completePartialObject(s) {
1807
- if (!s.trim().startsWith("{")) return s;
1808
- if (tryParse(s).ok) return s;
1809
- return repairTruncated(s);
1535
+ // src/utils/token-estimate.ts
1536
+ var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
1537
+ var CAL_ALPHA = 0.3;
1538
+ var CALIBRATION_GLOBAL_KEY = "__global__";
1539
+ var _cals = /* @__PURE__ */ new Map();
1540
+ function calState(key) {
1541
+ let state = _cals.get(key);
1542
+ if (!state) {
1543
+ state = { ratio: 1, count: 0, prevEst: 0 };
1544
+ _cals.set(key, state);
1545
+ }
1546
+ return state;
1810
1547
  }
1811
- function repairTruncated(s) {
1812
- const stack = [];
1813
- let inString = false;
1814
- let escaped = false;
1815
- let sawKey = false;
1816
- let prevSig = "";
1817
- let contentEnd = 0;
1818
- let stringBraceDepth = 0;
1819
- for (let i = 0; i < s.length; i++) {
1820
- const ch = expectDefined(s[i]);
1821
- if (inString) {
1822
- contentEnd = i + 1;
1823
- if (escaped) {
1824
- escaped = false;
1825
- continue;
1826
- }
1827
- if (ch === "\\") {
1828
- escaped = true;
1829
- continue;
1830
- }
1831
- if (ch === '"') {
1832
- inString = false;
1833
- prevSig = '"';
1834
- stringBraceDepth = 0;
1835
- continue;
1836
- }
1837
- if (ch === "{") stringBraceDepth++;
1838
- else if (ch === "}" && stringBraceDepth > 0) stringBraceDepth--;
1839
- continue;
1840
- }
1841
- if (ch === " " || ch === " " || ch === "\n" || ch === "\r") continue;
1842
- contentEnd = i + 1;
1843
- if (ch === '"') {
1844
- inString = true;
1845
- sawKey = true;
1846
- stringBraceDepth = 0;
1847
- prevSig = '"';
1848
- } else if (ch === "{" || ch === "[") {
1849
- stack.push(ch);
1850
- prevSig = ch;
1851
- } else if (ch === "}" || ch === "]") {
1852
- stack.pop();
1853
- prevSig = ch;
1854
- } else {
1855
- prevSig = ch;
1856
- }
1857
- }
1858
- if (!sawKey && !inString) return s;
1859
- let result = s.slice(0, contentEnd);
1860
- if (inString) {
1861
- if (escaped) {
1862
- result = result.slice(0, -1);
1863
- } else if (endsWithInvalidEscape(result)) {
1864
- result = result.slice(0, -2);
1548
+ var MIN_SAMPLES_FOR_CALIBRATION = 3;
1549
+ var MODEL_FAMILY_RATIO = {
1550
+ // Anthropic: ~3.8-4.0 chars/token depending on model
1551
+ claude: 3.8,
1552
+ // OpenAI: ~4.0 chars/token
1553
+ "gpt-4": 4,
1554
+ "gpt-3.5": 4,
1555
+ // Google: ~3.5 chars/token
1556
+ gemini: 3.5,
1557
+ // DeepSeek: ~3.5 chars/token
1558
+ deepseek: 3.5
1559
+ };
1560
+ var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
1561
+ var _estimateCacheOrder = [];
1562
+ var ESTIMATE_CACHE_MAX_SIZE = 5e4;
1563
+ function getCachedEstimate(key, compute) {
1564
+ const existing = ESTIMATE_CACHE.get(key);
1565
+ if (existing !== void 0) return existing;
1566
+ if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
1567
+ while (ESTIMATE_CACHE.size > Math.floor(ESTIMATE_CACHE_MAX_SIZE / 2)) {
1568
+ const oldest = _estimateCacheOrder.shift();
1569
+ if (oldest !== void 0) ESTIMATE_CACHE.delete(oldest);
1865
1570
  }
1866
- if (stringBraceDepth > 0) result += "}".repeat(stringBraceDepth);
1867
- result += '"';
1868
- } else if (prevSig === ":") {
1869
- result += "null";
1870
- }
1871
- for (let k = stack.length - 1; k >= 0; k--) {
1872
- result += stack[k] === "{" ? "}" : "]";
1873
1571
  }
1874
- if (!tryParse(result).ok) {
1875
- const patched = result.replace(/:(\s*)([}\]])/g, ":null$2");
1876
- if (tryParse(patched).ok) result = patched;
1877
- }
1878
- return result;
1879
- }
1880
- var VALID_ESCAPE = /* @__PURE__ */ new Set(['"', "\\", "/", "b", "f", "n", "r", "t", "u"]);
1881
- function endsWithInvalidEscape(str) {
1882
- const last = str[str.length - 1];
1883
- if (str[str.length - 2] !== "\\" || last === void 0) return false;
1884
- if (VALID_ESCAPE.has(last)) return false;
1885
- let backslashes = 0;
1886
- for (let k = str.length - 2; k >= 0 && str[k] === "\\"; k--) backslashes++;
1887
- return backslashes % 2 === 1;
1572
+ const estimate = compute(key);
1573
+ ESTIMATE_CACHE.set(key, estimate);
1574
+ _estimateCacheOrder.push(key);
1575
+ return estimate;
1888
1576
  }
1889
- function tryParse(s) {
1890
- try {
1891
- return { ok: true, value: JSON.parse(s) };
1892
- } catch {
1893
- return { ok: false };
1577
+ function estimateToolInputTokens(input) {
1578
+ if (typeof input === "string") return RoughTokenEstimate(input);
1579
+ if (input === null || typeof input !== "object") {
1580
+ return RoughTokenEstimate(String(input));
1894
1581
  }
1582
+ return getCachedEstimate(JSON.stringify(input), (key) => RoughTokenEstimate(key));
1895
1583
  }
1896
-
1897
- // src/utils/json-schema-validate.ts
1898
- function validateAgainstSchema(value, schema) {
1899
- const errors = [];
1900
- walk(value, schema, "", errors, 0);
1901
- return { ok: errors.length === 0, errors };
1584
+ function estimateToolResultTokens(content) {
1585
+ if (typeof content === "string") return RoughTokenEstimate(content);
1586
+ return getCachedEstimate(JSON.stringify(content), (key) => RoughTokenEstimate(key));
1902
1587
  }
1903
- var MAX_SCHEMA_DEPTH = 64;
1904
- function walk(value, schema, path6, errors, depth) {
1905
- if (depth > MAX_SCHEMA_DEPTH) {
1906
- errors.push({
1907
- path: path6 || "<root>",
1908
- message: `schema nesting exceeds maximum depth (${MAX_SCHEMA_DEPTH})`
1909
- });
1910
- return;
1911
- }
1912
- if (schema.enum !== void 0) {
1913
- if (!enumIncludes(schema.enum, value)) {
1914
- errors.push({
1915
- path: path6 || "<root>",
1916
- message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
1917
- });
1918
- return;
1919
- }
1920
- }
1921
- if (typeof schema.type === "string") {
1922
- if (!checkType(value, schema.type)) {
1923
- errors.push({
1924
- path: path6 || "<root>",
1925
- message: `expected ${schema.type}, got ${describeType(value)} (${previewValue(value)})`
1926
- });
1927
- return;
1928
- }
1929
- }
1930
- if (schema.type === "object" && isPlainObject(value)) {
1931
- const obj = value;
1932
- for (const req of schema.required ?? []) {
1933
- if (!(req in obj)) {
1934
- const expected = schema.properties?.[req]?.type;
1935
- errors.push({
1936
- path: joinPath(path6, req),
1937
- message: `required property missing${typeof expected === "string" ? ` (expected ${expected})` : ""}`
1938
- });
1939
- }
1940
- }
1941
- if (schema.properties) {
1942
- for (const [key, subSchema] of Object.entries(schema.properties)) {
1943
- if (key in obj) {
1944
- walk(obj[key], subSchema, joinPath(path6, key), errors, depth + 1);
1945
- }
1946
- }
1947
- }
1588
+ function estimateTextTokens(text) {
1589
+ return RoughTokenEstimate(text);
1590
+ }
1591
+ function computeMessageTokens(msg) {
1592
+ if (typeof msg.content === "string") return estimateTextTokens(msg.content);
1593
+ let total = 0;
1594
+ for (const b of msg.content) {
1595
+ if (b.type === "text") total += estimateTextTokens(b.text);
1596
+ else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
1597
+ else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
1598
+ else total += RoughTokenEstimate(JSON.stringify(b));
1948
1599
  }
1949
- if (schema.type === "array" && Array.isArray(value) && schema.items) {
1950
- for (let i = 0; i < value.length; i++) {
1951
- walk(value[i], schema.items, `${path6}[${i}]`, errors, depth + 1);
1600
+ return total;
1601
+ }
1602
+ function estimateMessageTokens(messages) {
1603
+ let total = 0;
1604
+ for (const m of messages) {
1605
+ if (typeof m._estTokens === "number" && m._estTokens > 0) {
1606
+ total += m._estTokens;
1607
+ continue;
1952
1608
  }
1609
+ total += computeMessageTokens(m);
1953
1610
  }
1611
+ return total;
1954
1612
  }
1955
- function coerceAgainstSchema(value, schema) {
1956
- return coerceWalk(value, schema, 0);
1613
+ function estimateToolDefTokens(tool) {
1614
+ const cached2 = tool._estDefTokens;
1615
+ if (typeof cached2 === "number" && cached2 > 0) return cached2;
1616
+ const compact = compactToolDefinitionForWire(tool);
1617
+ return RoughTokenEstimate(tool.name) + RoughTokenEstimate(compact.description) + RoughTokenEstimate(JSON.stringify(compact.inputSchema));
1957
1618
  }
1958
- function coerceWalk(value, schema, depth) {
1959
- if (depth > MAX_SCHEMA_DEPTH) return { value, changed: false };
1960
- const type = typeof schema.type === "string" ? schema.type : void 0;
1961
- if (type && !checkType(value, type)) {
1962
- if (type === "string" && (typeof value === "number" || typeof value === "boolean")) {
1963
- return { value: String(value), changed: true };
1964
- }
1965
- if ((type === "number" || type === "integer") && typeof value === "string") {
1966
- const trimmed = value.trim();
1967
- if (trimmed !== "" && /^-?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(trimmed)) {
1968
- const num = Number(trimmed);
1969
- if (!Number.isNaN(num) && (type === "number" || Number.isInteger(num))) {
1970
- return { value: num, changed: true };
1619
+ function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
1620
+ let messagesTokens = 0;
1621
+ if (typeof messages === "string") {
1622
+ messagesTokens = RoughTokenEstimate(messages);
1623
+ } else if (Array.isArray(messages)) {
1624
+ for (const m of messages) {
1625
+ if (typeof m === "object" && m !== null && "content" in m) {
1626
+ const cached2 = m._estTokens;
1627
+ if (typeof cached2 === "number" && cached2 > 0) {
1628
+ messagesTokens += cached2;
1629
+ continue;
1630
+ }
1631
+ const content = m.content;
1632
+ if (typeof content === "string") {
1633
+ messagesTokens += RoughTokenEstimate(content);
1634
+ } else if (Array.isArray(content)) {
1635
+ for (const b of content) {
1636
+ if (typeof b === "object" && b !== null) {
1637
+ if (b.type === "text") {
1638
+ messagesTokens += RoughTokenEstimate(b.text);
1639
+ } else {
1640
+ messagesTokens += RoughTokenEstimate(JSON.stringify(b));
1641
+ }
1642
+ }
1643
+ }
1971
1644
  }
1972
1645
  }
1973
- return { value, changed: false };
1974
- }
1975
- if (type === "boolean" && typeof value === "string") {
1976
- const lowered = value.trim().toLowerCase();
1977
- if (lowered === "true") return { value: true, changed: true };
1978
- if (lowered === "false") return { value: false, changed: true };
1979
- return { value, changed: false };
1980
1646
  }
1981
- if ((type === "object" || type === "array") && typeof value === "string") {
1982
- try {
1983
- const parsed = JSON.parse(value);
1984
- if (checkType(parsed, type)) {
1985
- return { value: coerceWalk(parsed, schema, depth + 1).value, changed: true };
1986
- }
1987
- } catch {
1647
+ }
1648
+ let systemTokens = 0;
1649
+ if (typeof systemPrompt === "string") {
1650
+ systemTokens = RoughTokenEstimate(systemPrompt);
1651
+ } else if (Array.isArray(systemPrompt)) {
1652
+ for (const b of systemPrompt) {
1653
+ if (typeof b === "object" && b !== null && b.type === "text") {
1654
+ systemTokens += RoughTokenEstimate(b.text);
1988
1655
  }
1989
- return { value, changed: false };
1990
1656
  }
1991
- return { value, changed: false };
1992
1657
  }
1993
- if (type === "object" && isPlainObject(value) && schema.properties) {
1994
- const obj = value;
1995
- let changed = false;
1996
- const out = { ...obj };
1997
- for (const [key, subSchema] of Object.entries(schema.properties)) {
1998
- if (!(key in obj)) continue;
1999
- const r = coerceWalk(obj[key], subSchema, depth + 1);
2000
- if (r.changed) {
2001
- out[key] = r.value;
2002
- changed = true;
2003
- }
2004
- }
2005
- return changed ? { value: out, changed } : { value, changed: false };
2006
- }
2007
- if (type === "array" && Array.isArray(value) && schema.items) {
2008
- let changed = false;
2009
- const out = value.map((item) => {
2010
- const r = coerceWalk(item, schema.items, depth + 1);
2011
- if (r.changed) changed = true;
2012
- return r.value;
2013
- });
2014
- return changed ? { value: out, changed } : { value, changed: false };
1658
+ let toolsTokens = 0;
1659
+ for (const t of tools) {
1660
+ toolsTokens += estimateToolDefTokens(t);
2015
1661
  }
2016
- return { value, changed: false };
1662
+ const total = messagesTokens + systemTokens + toolsTokens;
1663
+ calState(calibrationKey).prevEst = total;
1664
+ return {
1665
+ messages: messagesTokens,
1666
+ systemPrompt: systemTokens,
1667
+ tools: toolsTokens,
1668
+ total
1669
+ };
2017
1670
  }
2018
- function checkType(value, type) {
2019
- switch (type) {
2020
- case "string":
2021
- return typeof value === "string";
2022
- case "number":
2023
- return typeof value === "number" && !Number.isNaN(value);
2024
- case "integer":
2025
- return typeof value === "number" && Number.isInteger(value);
2026
- case "boolean":
2027
- return typeof value === "boolean";
2028
- case "null":
2029
- return value === null;
2030
- case "array":
2031
- return Array.isArray(value);
2032
- case "object":
2033
- return isPlainObject(value);
2034
- default:
2035
- return true;
1671
+ function recordActualUsage(actualInputTokens, estimatedInputTokens, calibrationKey = CALIBRATION_GLOBAL_KEY) {
1672
+ if (actualInputTokens <= 0) return;
1673
+ const cal = calState(calibrationKey);
1674
+ const est = estimatedInputTokens ?? cal.prevEst;
1675
+ if (est <= 0) return;
1676
+ const sampleRatio = actualInputTokens / est;
1677
+ if (cal.count === 0) {
1678
+ cal.ratio = sampleRatio;
1679
+ } else {
1680
+ cal.ratio = CAL_ALPHA * sampleRatio + (1 - CAL_ALPHA) * cal.ratio;
2036
1681
  }
1682
+ cal.ratio = Math.min(1.5, Math.max(0.5, cal.ratio));
1683
+ cal.count++;
2037
1684
  }
2038
- function isPlainObject(v) {
2039
- return typeof v === "object" && v !== null && !Array.isArray(v);
2040
- }
2041
- function describeType(v) {
2042
- if (v === null) return "null";
2043
- if (Array.isArray(v)) return "array";
2044
- return typeof v;
1685
+ function getCalibrationState(calibrationKey = CALIBRATION_GLOBAL_KEY) {
1686
+ const cal = calState(calibrationKey);
1687
+ return {
1688
+ ratio: cal.ratio,
1689
+ count: cal.count,
1690
+ calibrated: cal.count >= MIN_SAMPLES_FOR_CALIBRATION
1691
+ };
2045
1692
  }
2046
- function previewValue(v) {
2047
- try {
2048
- const s = JSON.stringify(v);
2049
- if (s === void 0) return String(v);
2050
- return s.length > 80 ? `${s.slice(0, 80)}\u2026` : s;
2051
- } catch {
2052
- return String(v);
1693
+ function estimateRequestTokensCalibrated(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
1694
+ const result = estimateRequestTokens(messages, systemPrompt, tools, calibrationKey);
1695
+ const cal = calState(calibrationKey);
1696
+ if (cal.count >= MIN_SAMPLES_FOR_CALIBRATION) {
1697
+ const safeRatio = Math.min(1.5, Math.max(0.5, cal.ratio));
1698
+ return {
1699
+ messages: Math.round(result.messages * safeRatio),
1700
+ systemPrompt: Math.round(result.systemPrompt * safeRatio),
1701
+ tools: Math.round(result.tools * safeRatio),
1702
+ total: Math.round(result.total * safeRatio)
1703
+ };
2053
1704
  }
1705
+ const fallbackRatio = getModelFamilyRatio(calibrationKey);
1706
+ if (fallbackRatio !== null) {
1707
+ return {
1708
+ messages: Math.round(result.messages * fallbackRatio),
1709
+ systemPrompt: Math.round(result.systemPrompt * fallbackRatio),
1710
+ tools: Math.round(result.tools * fallbackRatio),
1711
+ total: Math.round(result.total * fallbackRatio)
1712
+ };
1713
+ }
1714
+ return result;
2054
1715
  }
2055
- function joinPath(parent, key) {
2056
- if (!parent) return key;
2057
- return `${parent}.${key}`;
2058
- }
2059
- function enumIncludes(values, value) {
2060
- if (value === null || typeof value !== "object") return values.includes(value);
2061
- return values.some((candidate) => deepEqual(candidate, value));
2062
- }
2063
- function deepEqual(a, b) {
2064
- if (a === b) return true;
2065
- if (typeof a !== typeof b) return false;
2066
- if (a === null || b === null) return a === b;
2067
- if (Array.isArray(a) && Array.isArray(b)) {
2068
- return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
1716
+ function getModelFamilyRatio(calibrationKey) {
1717
+ const lower = calibrationKey.toLowerCase();
1718
+ for (const [family, ratio] of Object.entries(MODEL_FAMILY_RATIO)) {
1719
+ if (lower.includes(family)) return ratio / 3.5;
2069
1720
  }
2070
- if (typeof a === "object" && typeof b === "object") {
2071
- const ak = Object.keys(a);
2072
- const bk = Object.keys(b);
2073
- if (ak.length !== bk.length) return false;
2074
- return ak.every(
2075
- (k) => deepEqual(a[k], b[k])
2076
- );
1721
+ return null;
1722
+ }
1723
+ function resetCalibration(calibrationKey) {
1724
+ if (calibrationKey === void 0) {
1725
+ _cals.clear();
1726
+ return;
2077
1727
  }
2078
- return false;
1728
+ _cals.delete(calibrationKey);
2079
1729
  }
2080
1730
 
2081
- // src/utils/merge-custom-models.ts
2082
- function mergeCustomModelDefs(providerCustomModels, configModels) {
1731
+ // src/utils/context-breakdown.ts
1732
+ var SYSTEM_BLOCK_SOURCES = [
1733
+ "identity",
1734
+ "tool-usage",
1735
+ "environment",
1736
+ "skills",
1737
+ "mode",
1738
+ "plan",
1739
+ "leader-after-task",
1740
+ "contributor",
1741
+ "ledger",
1742
+ "nextsteps",
1743
+ "other"
1744
+ ];
1745
+ function emptyBySource() {
2083
1746
  const out = {};
2084
- if (providerCustomModels) {
2085
- for (const [id, def] of Object.entries(providerCustomModels)) {
2086
- out[id] = { ...def };
1747
+ for (const key of SYSTEM_BLOCK_SOURCES) out[key] = 0;
1748
+ return out;
1749
+ }
1750
+ function isMcpTool(tool) {
1751
+ return (tool.capabilities?.includes("mcp.proxy") ?? false) || tool.name.startsWith("mcp__");
1752
+ }
1753
+ function mcpServerOf(tool) {
1754
+ const parts = tool.name.split("__");
1755
+ return parts.length >= 3 && parts[0] === "mcp" ? parts[1] ?? "mcp" : "mcp";
1756
+ }
1757
+ function safeBuild(fn, warn = () => {
1758
+ }) {
1759
+ try {
1760
+ return fn();
1761
+ } catch (e) {
1762
+ warn(e);
1763
+ return void 0;
1764
+ }
1765
+ }
1766
+ function resolveEffectiveMaxContext(ctx) {
1767
+ const metaLimit = ctx.meta?.["effectiveMaxContext"];
1768
+ const providerMax = ctx.provider.capabilities.maxContext;
1769
+ return typeof metaLimit === "number" && metaLimit > 0 ? metaLimit : typeof providerMax === "number" && providerMax > 0 ? providerMax : 2e5;
1770
+ }
1771
+ function getContextBreakdown(ctx) {
1772
+ const bySource = emptyBySource();
1773
+ let systemTotal = 0;
1774
+ for (const block of ctx.systemPrompt) {
1775
+ const tokens = estimateTextTokens(block.text);
1776
+ systemTotal += tokens;
1777
+ bySource[SYSTEM_BLOCK_SOURCE.get(block) ?? "other"] += tokens;
1778
+ }
1779
+ let toolsBuiltin = 0;
1780
+ let toolsMcp = 0;
1781
+ const mcpByServer = {};
1782
+ for (const tool of ctx.tools) {
1783
+ const tokens = estimateToolDefTokens(tool);
1784
+ if (isMcpTool(tool)) {
1785
+ toolsMcp += tokens;
1786
+ const server = mcpServerOf(tool);
1787
+ mcpByServer[server] = (mcpByServer[server] ?? 0) + tokens;
1788
+ } else {
1789
+ toolsBuiltin += tokens;
2087
1790
  }
2088
1791
  }
2089
- if (configModels) {
2090
- for (const [id, def] of Object.entries(configModels)) {
2091
- out[id] = { ...def };
1792
+ let histText = 0;
1793
+ let histToolResults = 0;
1794
+ for (const msg of ctx.messages) {
1795
+ if (typeof msg.content === "string") {
1796
+ histText += estimateTextTokens(msg.content);
1797
+ continue;
1798
+ }
1799
+ for (const b of msg.content) {
1800
+ switch (b.type) {
1801
+ case "text":
1802
+ histText += estimateTextTokens(b.text);
1803
+ break;
1804
+ case "tool_use":
1805
+ histText += estimateToolInputTokens(b.input);
1806
+ break;
1807
+ case "tool_result":
1808
+ histToolResults += estimateToolResultTokens(b.content);
1809
+ break;
1810
+ case "thinking":
1811
+ histText += estimateTextTokens(b.thinking);
1812
+ break;
1813
+ default:
1814
+ histText += estimateTextTokens(JSON.stringify(b));
1815
+ }
2092
1816
  }
2093
1817
  }
2094
- if (Object.keys(out).length === 0) return void 0;
2095
- return out;
1818
+ const warnings = [];
1819
+ const warn = (e) => {
1820
+ const msg = e instanceof Error ? e.message : String(e);
1821
+ warnings.push(msg);
1822
+ };
1823
+ const ledgerBlock = safeBuild(() => buildCompletedWorkLedgerBlock(ctx), warn);
1824
+ const nextstepsBlock = safeBuild(() => buildLiveNextStepsGateBlock(ctx), warn);
1825
+ const ledger = ledgerBlock ? estimateTextTokens(ledgerBlock.text) : 0;
1826
+ const nextsteps = nextstepsBlock ? estimateTextTokens(nextstepsBlock.text) : 0;
1827
+ const toolsTotal = toolsBuiltin + toolsMcp;
1828
+ const historyTotal = histText + histToolResults;
1829
+ const volatileTotal = ledger + nextsteps;
1830
+ const total = systemTotal + toolsTotal + historyTotal + volatileTotal;
1831
+ const effectiveMaxContext = resolveEffectiveMaxContext(ctx);
1832
+ return {
1833
+ system: { total: systemTotal, bySource },
1834
+ tools: {
1835
+ total: toolsTotal,
1836
+ builtin: toolsBuiltin,
1837
+ mcp: toolsMcp,
1838
+ count: ctx.tools.length,
1839
+ mcpByServer
1840
+ },
1841
+ history: {
1842
+ total: historyTotal,
1843
+ text: histText,
1844
+ toolResults: histToolResults,
1845
+ messageCount: ctx.messages.length
1846
+ },
1847
+ volatile: { ledger, nextsteps, total: volatileTotal },
1848
+ total,
1849
+ effectiveMaxContext,
1850
+ usedPct: effectiveMaxContext > 0 ? total / effectiveMaxContext : 0,
1851
+ warnings
1852
+ };
2096
1853
  }
2097
1854
 
2098
- // src/utils/merge-models-payload.ts
2099
- function mergeModelsPayload(base, overlay) {
2100
- const out = {};
2101
- for (const [id, provider] of Object.entries(base)) {
2102
- out[id] = cloneProvider(provider);
2103
- }
2104
- for (const [id, ovProvider] of Object.entries(overlay)) {
2105
- const existing = out[id];
2106
- out[id] = existing ? mergeProvider(existing, ovProvider) : cloneProvider(ovProvider);
2107
- }
2108
- return out;
1855
+ // src/utils/deep-merge.ts
1856
+ var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
1857
+ "__proto__",
1858
+ "constructor",
1859
+ "prototype",
1860
+ "__defineGetter__",
1861
+ "__defineSetter__",
1862
+ "__lookupGetter__",
1863
+ "__lookupSetter__"
1864
+ ]);
1865
+ function isPrimitiveArray(a) {
1866
+ return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
2109
1867
  }
2110
- function mergeProvider(base, overlay) {
2111
- const models = {};
2112
- for (const [mid, m] of Object.entries(base.models ?? {})) {
2113
- models[mid] = { ...m };
1868
+ function deepMerge(base, patch, options = {}) {
1869
+ const {
1870
+ conflictResolution = "prefer-patch",
1871
+ arrayMode = "replace",
1872
+ protectProto = true,
1873
+ onNonPrimitiveArrayReplace
1874
+ } = options;
1875
+ if (typeof base !== "object" || base === null) {
1876
+ return conflictResolution === "prefer-patch" ? patch : base;
2114
1877
  }
2115
- for (const [mid, ovModel] of Object.entries(overlay.models ?? {})) {
2116
- const existing = models[mid];
2117
- models[mid] = existing ? mergeModel(existing, ovModel) : { ...ovModel };
1878
+ if (typeof patch !== "object" || patch === null) {
1879
+ return conflictResolution === "prefer-patch" ? patch : base;
2118
1880
  }
2119
- return {
2120
- ...base,
2121
- // Overlay scalar fields win when explicitly provided; otherwise keep base.
2122
- ...stripUndefined({
2123
- id: overlay.id,
2124
- name: overlay.name,
2125
- npm: overlay.npm,
2126
- api: overlay.api,
2127
- env: overlay.env,
2128
- doc: overlay.doc
2129
- }),
2130
- models
2131
- };
2132
- }
2133
- function mergeModel(base, overlay) {
2134
- const merged = { ...base, ...overlay };
2135
- if (base.limit || overlay.limit) {
2136
- merged.limit = { ...base.limit, ...overlay.limit };
1881
+ if (Array.isArray(base) && Array.isArray(patch)) {
1882
+ if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
1883
+ return [.../* @__PURE__ */ new Set([...base, ...patch])];
1884
+ }
1885
+ return conflictResolution === "prefer-patch" ? patch : base;
2137
1886
  }
2138
- if (base.cost || overlay.cost) {
2139
- merged.cost = { ...base.cost, ...overlay.cost };
1887
+ if (Array.isArray(base) || Array.isArray(patch)) {
1888
+ return conflictResolution === "prefer-patch" ? patch : base;
2140
1889
  }
2141
- if (base.modalities || overlay.modalities) {
2142
- merged.modalities = { ...base.modalities, ...overlay.modalities };
1890
+ const baseObj = base;
1891
+ const patchObj = patch;
1892
+ const out = { ...baseObj };
1893
+ for (const [k, v] of Object.entries(patchObj)) {
1894
+ if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
1895
+ const existing = out[k];
1896
+ if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
1897
+ out[k] = deepMerge(existing, v, options);
1898
+ } else if (Array.isArray(v) && Array.isArray(existing)) {
1899
+ if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
1900
+ onNonPrimitiveArrayReplace(k, existing.length, v.length);
1901
+ }
1902
+ out[k] = deepMerge(existing, v, options);
1903
+ } else if (v !== void 0) {
1904
+ if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
1905
+ const existingLen = Array.isArray(existing) ? existing.length : 0;
1906
+ onNonPrimitiveArrayReplace(k, existingLen, v.length);
1907
+ }
1908
+ out[k] = v;
1909
+ }
2143
1910
  }
2144
- return merged;
1911
+ return out;
2145
1912
  }
2146
- function cloneProvider(p) {
2147
- const models = {};
2148
- for (const [mid, m] of Object.entries(p.models ?? {})) {
2149
- models[mid] = { ...m };
2150
- }
2151
- return { ...p, models };
2152
- }
2153
- function stripUndefined(obj) {
2154
- const out = {};
2155
- for (const [k, v] of Object.entries(obj)) {
2156
- if (v !== void 0) out[k] = v;
1913
+
1914
+ // src/utils/diff.ts
1915
+ function myersDiff(a, b) {
1916
+ const N = a.length;
1917
+ const M = b.length;
1918
+ const max = N + M;
1919
+ if (max === 0) return [];
1920
+ const v = /* @__PURE__ */ new Map();
1921
+ v.set(1, 0);
1922
+ const trace = [];
1923
+ for (let d = 0; d <= max; d++) {
1924
+ const snapshot = new Map(v);
1925
+ trace.push(snapshot);
1926
+ for (let k = -d; k <= d; k += 2) {
1927
+ const left = v.get(k - 1) ?? -1;
1928
+ const right = v.get(k + 1) ?? -1;
1929
+ let x;
1930
+ if (k === -d || k !== d && left < right) {
1931
+ x = right;
1932
+ } else {
1933
+ x = left + 1;
1934
+ }
1935
+ let y = x - k;
1936
+ while (x < N && y < M && a[x] === b[y]) {
1937
+ x++;
1938
+ y++;
1939
+ }
1940
+ v.set(k, x);
1941
+ if (x >= N && y >= M) {
1942
+ return backtrack(trace, a, b, N, M, d);
1943
+ }
1944
+ }
2157
1945
  }
2158
- return out;
1946
+ return [];
2159
1947
  }
2160
-
2161
- // src/utils/message-invariants.ts
2162
- function repairToolUseAdjacency(messages) {
2163
- const removedToolUses = [];
2164
- const removedToolResults = [];
2165
- let removedMessages = 0;
2166
- let changed = false;
2167
- const out = [];
2168
- for (let i = 0; i < messages.length; i++) {
2169
- const original = expectDefined(messages[i]);
2170
- let msg = original;
2171
- if (hasToolUse(msg)) {
2172
- const nextIds = toolResultIds(messages[i + 1]);
2173
- const filtered = mapContent(msg, (blocks) => {
2174
- const next = [];
2175
- for (const block of blocks) {
2176
- if (block.type === "tool_use" && !nextIds.has(block.id)) {
2177
- removedToolUses.push(block.id);
2178
- changed = true;
2179
- continue;
2180
- }
2181
- next.push(block);
2182
- }
2183
- return next;
2184
- });
2185
- msg = filtered ?? msg;
1948
+ function backtrack(trace, a, b, N, M, finalD) {
1949
+ const edits = [];
1950
+ let x = N;
1951
+ let y = M;
1952
+ for (let d = finalD; d > 0; d--) {
1953
+ const v = trace[d];
1954
+ if (!v) break;
1955
+ const k = x - y;
1956
+ const left = v.get(k - 1) ?? -1;
1957
+ const right = v.get(k + 1) ?? -1;
1958
+ let prevK;
1959
+ if (k === -d || k !== d && left < right) {
1960
+ prevK = k + 1;
1961
+ } else {
1962
+ prevK = k - 1;
2186
1963
  }
2187
- if (hasToolResult(msg)) {
2188
- const allowed = toolUseIds(out[out.length - 1]);
2189
- const filtered = mapContent(msg, (blocks) => {
2190
- const next = [];
2191
- for (const block of blocks) {
2192
- if (block.type === "tool_result" && !allowed.has(block.tool_use_id)) {
2193
- removedToolResults.push(block.tool_use_id);
2194
- changed = true;
2195
- continue;
2196
- }
2197
- next.push(block);
2198
- }
2199
- return next;
2200
- });
2201
- msg = filtered ?? msg;
1964
+ const prevX = v.get(prevK) ?? 0;
1965
+ const prevY = prevX - prevK;
1966
+ while (x > prevX && y > prevY) {
1967
+ edits.push({ op: "equal", a: x - 1, b: y - 1, line: a[x - 1] ?? "" });
1968
+ x--;
1969
+ y--;
2202
1970
  }
2203
- if (isEmptyMessage(msg)) {
2204
- removedMessages++;
2205
- changed = true;
2206
- continue;
1971
+ if (d > 0) {
1972
+ if (x === prevX) {
1973
+ edits.push({ op: "insert", a: x, b: y - 1, line: b[y - 1] ?? "" });
1974
+ } else {
1975
+ edits.push({ op: "delete", a: x - 1, b: y, line: a[x - 1] ?? "" });
1976
+ }
1977
+ x = prevX;
1978
+ y = prevY;
2207
1979
  }
2208
- out.push(msg);
2209
1980
  }
2210
- return {
2211
- messages: changed ? out : messages,
2212
- report: { changed, removedToolUses, removedToolResults, removedMessages }
2213
- };
2214
- }
2215
- function hasToolUse(msg) {
2216
- return contentBlocks(msg).some((b) => b.type === "tool_use");
2217
- }
2218
- function hasToolResult(msg) {
2219
- return contentBlocks(msg).some((b) => b.type === "tool_result");
2220
- }
2221
- function toolUseIds(msg) {
2222
- const ids = /* @__PURE__ */ new Set();
2223
- if (msg?.role !== "assistant") return ids;
2224
- for (const block of contentBlocks(msg)) {
2225
- if (block.type === "tool_use") ids.add(block.id);
1981
+ while (x > 0 && y > 0) {
1982
+ edits.push({ op: "equal", a: x - 1, b: y - 1, line: a[x - 1] ?? "" });
1983
+ x--;
1984
+ y--;
2226
1985
  }
2227
- return ids;
1986
+ return edits.reverse();
2228
1987
  }
2229
- function toolResultIds(msg) {
2230
- const ids = /* @__PURE__ */ new Set();
2231
- if (msg?.role !== "user") return ids;
2232
- for (const block of contentBlocks(msg)) {
2233
- if (block.type === "tool_result") ids.add(block.tool_use_id);
1988
+ function unifiedDiff(oldText, newText, opts = {}) {
1989
+ const context = opts.context ?? 3;
1990
+ const a = oldText.split("\n");
1991
+ const b = newText.split("\n");
1992
+ if (a[a.length - 1] === "") a.pop();
1993
+ if (b[b.length - 1] === "") b.pop();
1994
+ const edits = myersDiff(a, b);
1995
+ if (edits.every((e) => e.op === "equal")) return "";
1996
+ const hunks = [];
1997
+ let i = 0;
1998
+ while (i < edits.length) {
1999
+ while (i < edits.length && edits[i]?.op === "equal") i++;
2000
+ if (i >= edits.length) break;
2001
+ const hunkStart = Math.max(0, i - context);
2002
+ const lines = [];
2003
+ let aStart = (edits[hunkStart]?.a ?? 0) + 1;
2004
+ let bStart = (edits[hunkStart]?.b ?? 0) + 1;
2005
+ let aCount = 0;
2006
+ let bCount = 0;
2007
+ let cursor = hunkStart;
2008
+ let trailing = 0;
2009
+ while (cursor < edits.length) {
2010
+ const e = edits[cursor];
2011
+ if (!e) break;
2012
+ if (e.op === "equal") {
2013
+ trailing++;
2014
+ if (trailing > context * 2) break;
2015
+ } else {
2016
+ trailing = 0;
2017
+ }
2018
+ if (e.op === "equal") {
2019
+ lines.push(` ${e.line}`);
2020
+ aCount++;
2021
+ bCount++;
2022
+ } else if (e.op === "delete") {
2023
+ lines.push(`-${e.line}`);
2024
+ aCount++;
2025
+ } else {
2026
+ lines.push(`+${e.line}`);
2027
+ bCount++;
2028
+ }
2029
+ cursor++;
2030
+ }
2031
+ while (lines.length > 0 && lines[lines.length - 1]?.startsWith(" ") && trailing > context) {
2032
+ lines.pop();
2033
+ aCount--;
2034
+ bCount--;
2035
+ trailing--;
2036
+ }
2037
+ if (aCount === 0) aStart = 0;
2038
+ if (bCount === 0) bStart = 0;
2039
+ hunks.push({ aStart, bStart, lines });
2040
+ i = cursor;
2234
2041
  }
2235
- return ids;
2236
- }
2237
- function contentBlocks(msg) {
2238
- return msg && Array.isArray(msg.content) ? msg.content : [];
2239
- }
2240
- function mapContent(msg, fn) {
2241
- if (!Array.isArray(msg.content)) return msg;
2242
- const next = fn(msg.content);
2243
- if (next.length === msg.content.length && next.every((b, idx) => b === msg.content[idx])) {
2244
- return msg;
2042
+ if (hunks.length === 0) return "";
2043
+ let out = "";
2044
+ out += `--- ${opts.fromFile ?? "a"}
2045
+ `;
2046
+ out += `+++ ${opts.toFile ?? "b"}
2047
+ `;
2048
+ for (const h of hunks) {
2049
+ let aCount = 0;
2050
+ let bCount = 0;
2051
+ for (const l of h.lines) {
2052
+ if (l.startsWith(" ")) {
2053
+ aCount++;
2054
+ bCount++;
2055
+ } else if (l.startsWith("-")) aCount++;
2056
+ else if (l.startsWith("+")) bCount++;
2057
+ }
2058
+ out += `@@ -${h.aStart},${aCount} +${h.bStart},${bCount} @@
2059
+ `;
2060
+ out += `${h.lines.join("\n")}
2061
+ `;
2245
2062
  }
2246
- return { ...msg, content: next };
2063
+ return out;
2247
2064
  }
2248
- function hasMeaningfulContent(content) {
2249
- if (typeof content === "string") return content.trim().length > 0;
2250
- for (const block of content) {
2251
- if (block.type === "text") {
2252
- if (block.text.trim().length > 0) return true;
2253
- continue;
2254
- }
2255
- if (block.type === "thinking") {
2256
- if (block.thinking.trim().length > 0 || block.signature) return true;
2257
- continue;
2258
- }
2259
- return true;
2065
+
2066
+ // src/utils/glob-expand.ts
2067
+ import * as fsp from "node:fs/promises";
2068
+ import { isAbsolute as isAbsolute2, resolve as resolve2 } from "node:path";
2069
+ var GLOB_CHARS = /* @__PURE__ */ new Set(["*", "?", "["]);
2070
+ var IS_WINDOWS = process.platform === "win32";
2071
+ var SEP = IS_WINDOWS ? "\\" : "/";
2072
+ function isGlob(p) {
2073
+ for (const c of p) {
2074
+ if (GLOB_CHARS.has(c)) return true;
2260
2075
  }
2261
2076
  return false;
2262
2077
  }
2263
- function isEmptyMessage(msg) {
2264
- return !hasMeaningfulContent(msg.content);
2265
- }
2266
-
2267
- // src/utils/newline-normalize.ts
2268
- function detectNewlineStyle(text) {
2269
- let lf = 0;
2270
- let crlf = 0;
2271
- let cr = 0;
2272
- for (let i = 0; i < text.length; i++) {
2273
- const c = text.charCodeAt(i);
2274
- if (c === 13) {
2275
- if (text.charCodeAt(i + 1) === 10) {
2276
- crlf++;
2277
- i++;
2078
+ function globToRegex(pat) {
2079
+ let i = 0;
2080
+ let re = "^";
2081
+ while (i < pat.length) {
2082
+ const c = expectDefined(pat[i]);
2083
+ if (c === "*") {
2084
+ if (pat[i + 1] === "*") {
2085
+ re += ".*";
2086
+ i += 2;
2087
+ if (pat[i] === "/") i++;
2278
2088
  } else {
2279
- cr++;
2089
+ re += "[^/\\\\]*";
2090
+ i++;
2280
2091
  }
2281
- } else if (c === 10) {
2282
- lf++;
2092
+ } else if (c === "?") {
2093
+ re += "[^/\\\\]";
2094
+ i++;
2095
+ } else if (c === "[") {
2096
+ let cls = "[";
2097
+ i++;
2098
+ if (pat[i] === "!" || pat[i] === "^") {
2099
+ cls += "^";
2100
+ i++;
2101
+ }
2102
+ while (i < pat.length && pat[i] !== "]") {
2103
+ const ch = pat[i] ?? "";
2104
+ if (ch === "\\") cls += "\\\\";
2105
+ else if (ch === "]" || ch === "^") cls += `\\${ch}`;
2106
+ else cls += ch;
2107
+ i++;
2108
+ }
2109
+ cls += "]";
2110
+ re += cls;
2111
+ i++;
2112
+ } else {
2113
+ re += c.replace(/[.+^${}()|\\]/g, "\\$&");
2114
+ i++;
2283
2115
  }
2284
2116
  }
2285
- if (crlf > lf && crlf > cr) return "crlf";
2286
- if (cr > lf && cr > crlf) return "cr";
2287
- return "lf";
2288
- }
2289
- function toStyle(text, style) {
2290
- const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
2291
- if (style === "lf") return normalized;
2292
- if (style === "crlf") return normalized.replace(/\n/g, "\r\n");
2293
- return normalized.replace(/\n/g, "\r");
2294
- }
2295
- function normalizeToLf(text) {
2296
- return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
2117
+ return new RegExp(re + "$");
2297
2118
  }
2298
-
2299
- // src/utils/regex-guard.ts
2300
- var MAX_PATTERN_LEN = 512;
2301
- var DANGEROUS_PATTERNS = [
2302
- /(\([^)]*[+*][^)]*\))[+*]/,
2303
- // (a+)+, (.*)+, etc
2304
- /(\(\?:[^)]*[+*][^)]*\))[+*]/
2305
- // same, with non-capturing group
2306
- ];
2307
- function compileUserRegex(pattern, flags) {
2308
- if (typeof pattern !== "string") {
2309
- return { ok: false, reason: "pattern must be a string" };
2310
- }
2311
- if (pattern.length === 0) {
2312
- return { ok: false, reason: "pattern is empty" };
2313
- }
2314
- if (pattern.length > MAX_PATTERN_LEN) {
2315
- return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
2316
- }
2317
- for (const rx of DANGEROUS_PATTERNS) {
2318
- if (rx.test(pattern)) {
2319
- return {
2320
- ok: false,
2321
- reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
2322
- };
2119
+ function baseDir(pat) {
2120
+ let firstGlob = pat.length;
2121
+ for (let i = 0; i < pat.length; i++) {
2122
+ if (GLOB_CHARS.has(expectDefined(pat[i]))) {
2123
+ firstGlob = i;
2124
+ break;
2323
2125
  }
2324
2126
  }
2325
- try {
2326
- return { ok: true, regex: new RegExp(pattern, flags) };
2327
- } catch (err) {
2328
- return {
2329
- ok: false,
2330
- reason: err instanceof Error ? err.message : "invalid regex"
2331
- };
2127
+ const cut = Math.max(
2128
+ pat.lastIndexOf(SEP, firstGlob - 1),
2129
+ pat.lastIndexOf("/", firstGlob - 1)
2130
+ );
2131
+ return cut < 0 ? "." : pat.slice(0, cut);
2132
+ }
2133
+ async function expandGlob(pattern) {
2134
+ if (!isGlob(pattern)) return [pattern];
2135
+ const results = /* @__PURE__ */ new Set();
2136
+ const abs = isAbsolute2(pattern);
2137
+ const base = abs ? baseDir(pattern) : baseDir(pattern);
2138
+ const relPat = base === "." ? pattern : pattern.slice(base.length + 1);
2139
+ async function walk2(dir, pat) {
2140
+ let entries;
2141
+ try {
2142
+ entries = await fsp.readdir(dir);
2143
+ } catch {
2144
+ return;
2145
+ }
2146
+ const firstGlob = pat.search(/[*?[[]/);
2147
+ if (firstGlob < 0) {
2148
+ const re = globToRegex(pat);
2149
+ for (const e of entries) {
2150
+ if (re.test(e)) {
2151
+ const full = `${dir}${SEP}${e}`;
2152
+ results.add(abs ? resolve2(full) : full);
2153
+ }
2154
+ }
2155
+ return;
2156
+ }
2157
+ const before = pat.slice(0, firstGlob);
2158
+ const rest = pat.slice(firstGlob);
2159
+ if (before.endsWith("**")) {
2160
+ await walk2(dir, rest);
2161
+ for (const e of entries) {
2162
+ const full = `${dir}${SEP}${e}`;
2163
+ try {
2164
+ const stat3 = await fsp.stat(full);
2165
+ if (stat3.isDirectory()) await walk2(full, rest);
2166
+ } catch {
2167
+ }
2168
+ }
2169
+ } else if (before === "") {
2170
+ const re = globToRegex(rest);
2171
+ for (const e of entries) {
2172
+ if (re.test(e)) {
2173
+ const full = `${dir}${SEP}${e}`;
2174
+ results.add(abs ? resolve2(full) : full);
2175
+ }
2176
+ }
2177
+ } else {
2178
+ const seg = before.replace(/[*?[\]]/g, "").replace(/\/$/, "");
2179
+ if (entries.includes(seg)) {
2180
+ const full = `${dir}${SEP}${seg}`;
2181
+ try {
2182
+ const stat3 = await fsp.stat(full);
2183
+ if (stat3.isDirectory()) await walk2(full, rest);
2184
+ } catch {
2185
+ }
2186
+ }
2187
+ }
2332
2188
  }
2189
+ await walk2(base === "." ? "." : base, relPat);
2190
+ return [...results];
2333
2191
  }
2334
2192
 
2335
- // src/utils/safe-json.ts
2336
- function safeParse(input, maxBytes = 5e6) {
2337
- if (Buffer.byteLength(input, "utf8") > maxBytes) {
2338
- return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };
2339
- }
2340
- try {
2341
- return { ok: true, value: JSON.parse(input) };
2342
- } catch (err) {
2343
- return {
2344
- ok: false,
2345
- error: toErrorMessage(err)
2346
- };
2347
- }
2193
+ // src/utils/glob-match.ts
2194
+ function escapeRegex(s) {
2195
+ return s.replace(/[.+^${}()|\\]/g, "\\$&");
2348
2196
  }
2349
- function safeStringify(value, pretty = false) {
2350
- const seen = /* @__PURE__ */ new WeakSet();
2351
- const replacer = (_k, v) => {
2352
- if (typeof v === "bigint") return v.toString();
2353
- if (v instanceof Error) {
2354
- return { name: v.name, message: v.message, stack: v.stack };
2355
- }
2356
- if (typeof v === "object" && v !== null) {
2357
- if (seen.has(v)) return "[Circular]";
2358
- seen.add(v);
2197
+ var COMPILED_GLOB_CACHE = /* @__PURE__ */ new Map();
2198
+ var CACHE_MAX_SIZE = 2e3;
2199
+ var NEVER_MATCH = /[^\s\S]/;
2200
+ function getCachedGlob(pattern) {
2201
+ const cached2 = COMPILED_GLOB_CACHE.get(pattern);
2202
+ if (cached2) return cached2;
2203
+ if (COMPILED_GLOB_CACHE.size >= CACHE_MAX_SIZE) {
2204
+ const keys = [...COMPILED_GLOB_CACHE.keys()];
2205
+ for (let i = 0; i < Math.floor(CACHE_MAX_SIZE / 4); i++) {
2206
+ COMPILED_GLOB_CACHE.delete(expectDefined(keys[i]));
2359
2207
  }
2360
- return v;
2361
- };
2362
- try {
2363
- return JSON.stringify(value, replacer, pretty ? 2 : void 0) ?? "null";
2364
- } catch (err) {
2365
- return JSON.stringify({
2366
- __serialization_error: toErrorMessage(err)
2367
- });
2368
2208
  }
2369
- }
2370
- function sanitizeJsonString(s) {
2371
- let out = s.trim();
2372
- out = stripJsonComments(out);
2373
- out = out.replace(/,(\s*[}\]])/g, "$1");
2374
- out = escapeControlCharsInStrings(out);
2209
+ let re;
2375
2210
  try {
2376
- JSON.parse(out);
2377
- return out;
2211
+ re = compileGlob(pattern);
2378
2212
  } catch {
2379
- return null;
2213
+ re = NEVER_MATCH;
2380
2214
  }
2215
+ COMPILED_GLOB_CACHE.set(pattern, re);
2216
+ return re;
2381
2217
  }
2382
- function stripCodeFences(s) {
2383
- const trimmed = s.trim();
2384
- const opener = /^```[\w+-]*[ \t]*\r?\n?/.exec(trimmed);
2385
- if (opener) {
2386
- const inner = trimmed.slice(opener[0].length).replace(/(\r?\n)?[ \t]*```[ \t]*$/, "");
2387
- return inner.trim();
2218
+ var MAX_GLOB_PATTERN_LEN = 1024;
2219
+ function compileGlob(pattern) {
2220
+ if (pattern.length > MAX_GLOB_PATTERN_LEN) {
2221
+ throw new Error(`Glob pattern exceeds ${MAX_GLOB_PATTERN_LEN} characters`);
2388
2222
  }
2389
- const embedded = /```[\w+-]*[ \t]*\r?\n([\s\S]*?)\r?\n[ \t]*```/.exec(trimmed);
2390
- if (embedded) return (embedded[1] ?? "").trim();
2391
- return null;
2392
- }
2393
- function escapeControlCharsInStrings(s) {
2394
- let inString = false;
2395
- let out = "";
2396
- for (let i = 0; i < s.length; i++) {
2397
- const c = s.charAt(i);
2398
- if (c === '"' && (i === 0 || s[i - 1] !== "\\")) {
2399
- inString = !inString;
2400
- out += c;
2401
- continue;
2402
- }
2403
- const code = c.charCodeAt(0);
2404
- if (inString && code < 32) {
2405
- switch (c) {
2406
- case "\n":
2407
- out += "\\n";
2408
- break;
2409
- case "\r":
2410
- out += "\\r";
2411
- break;
2412
- case " ":
2413
- out += "\\t";
2414
- break;
2415
- case "\b":
2416
- out += "\\b";
2417
- break;
2418
- case "\f":
2419
- out += "\\f";
2420
- break;
2421
- default:
2422
- out += `\\u${code.toString(16).padStart(4, "0")}`;
2423
- }
2424
- continue;
2425
- }
2426
- out += c;
2427
- }
2428
- return out;
2429
- }
2430
- function stripJsonComments(s) {
2431
- let inString = false;
2432
- let escaped = false;
2433
- const chars = [];
2434
2223
  let i = 0;
2435
- while (i < s.length) {
2436
- const c = s.charAt(i);
2437
- if (inString) {
2438
- chars.push(c);
2439
- if (escaped) {
2440
- escaped = false;
2441
- } else if (c === "\\") {
2442
- escaped = true;
2443
- } else if (c === '"') {
2444
- inString = false;
2224
+ let re = "^";
2225
+ while (i < pattern.length) {
2226
+ const c = pattern[i];
2227
+ if (c === "*") {
2228
+ if (pattern[i + 1] === "*") {
2229
+ re += ".*";
2230
+ i += 2;
2231
+ if (pattern[i] === "/") i++;
2232
+ } else {
2233
+ re += "[^/]*";
2234
+ i++;
2445
2235
  }
2236
+ } else if (c === "?") {
2237
+ re += "[^/]";
2446
2238
  i++;
2447
- continue;
2448
- }
2449
- if (c === '"') {
2450
- inString = true;
2451
- chars.push(c);
2239
+ } else if (c === "[") {
2240
+ let cls = "[";
2452
2241
  i++;
2453
- continue;
2454
- }
2455
- if (c === "/" && s.charAt(i + 1) === "/") {
2456
- while (i < s.length && s.charAt(i) !== "\n") i++;
2457
- continue;
2458
- }
2459
- if (c === "/" && s.charAt(i + 1) === "*") {
2460
- const end = s.indexOf("*/", i + 2);
2461
- if (end === -1) {
2462
- chars.push(s.slice(i));
2463
- break;
2242
+ if (pattern[i] === "!" || pattern[i] === "^") {
2243
+ cls += "^";
2244
+ i++;
2464
2245
  }
2465
- i = end + 2;
2466
- continue;
2246
+ while (i < pattern.length && pattern[i] !== "]") {
2247
+ const ch = pattern[i] ?? "";
2248
+ if (ch === "\\") {
2249
+ cls += "\\\\";
2250
+ } else if (ch === "]" || ch === "^") {
2251
+ cls += `\\${ch}`;
2252
+ } else {
2253
+ cls += ch;
2254
+ }
2255
+ i++;
2256
+ }
2257
+ cls += "]";
2258
+ re += cls;
2259
+ i++;
2260
+ } else {
2261
+ re += escapeRegex(c ?? "");
2262
+ i++;
2467
2263
  }
2468
- chars.push(c);
2469
- i++;
2470
2264
  }
2471
- return chars.join("");
2265
+ re += "$";
2266
+ return new RegExp(re);
2472
2267
  }
2473
-
2474
- // src/utils/session-scoped-path.ts
2475
- import * as path4 from "node:path";
2476
- function sessionScopedPath(dir, sessionId, suffix) {
2477
- if (!sessionId || sessionId.includes("\\") || sessionId.includes("..")) {
2478
- throw invalid(sessionId);
2479
- }
2480
- const resolved = path4.resolve(dir, `${sessionId}${suffix}`);
2481
- const rel = path4.relative(path4.resolve(dir), resolved);
2482
- if (rel.startsWith("..") || path4.isAbsolute(rel)) {
2483
- throw invalid(sessionId);
2484
- }
2485
- return resolved;
2268
+ function matchGlob(pattern, input) {
2269
+ return getCachedGlob(pattern).test(input);
2486
2270
  }
2487
- function invalid(sessionId) {
2488
- return new FsError({
2489
- message: `Invalid sessionId: ${sessionId}`,
2490
- code: ERROR_CODES.FS_DELETE_FAILED,
2491
- path: sessionId,
2492
- context: { reason: "path_traversal" }
2493
- });
2271
+ function matchAny(patterns, input) {
2272
+ return patterns.some((p) => matchGlob(p, input));
2494
2273
  }
2495
2274
 
2496
- // src/utils/sleep.ts
2497
- function sleep(ms) {
2498
- return new Promise((resolve5) => setTimeout(resolve5, ms));
2275
+ // src/utils/incoming-images.ts
2276
+ var MAX_INCOMING_IMAGES = 8;
2277
+ var MAX_INCOMING_IMAGE_BYTES = 8 * 1024 * 1024;
2278
+ var ALLOWED_IMAGE_MEDIA_TYPES = /* @__PURE__ */ new Set([
2279
+ "image/png",
2280
+ "image/jpeg",
2281
+ "image/webp",
2282
+ "image/gif"
2283
+ ]);
2284
+ var IncomingImageError = class extends Error {
2285
+ constructor(message) {
2286
+ super(message);
2287
+ this.name = "IncomingImageError";
2288
+ }
2289
+ };
2290
+ var DATA_URL_RE = /^data:([a-z0-9.+-]+\/[a-z0-9.+-]+)?(?:;[a-z0-9-]+=[^;,]*)*(;base64)?,/i;
2291
+ function splitDataUrl(data) {
2292
+ const match = DATA_URL_RE.exec(data);
2293
+ if (!match) return { base64: data.trim() };
2294
+ return {
2295
+ base64: data.slice(match[0].length).trim(),
2296
+ mediaType: match[1]?.toLowerCase()
2297
+ };
2499
2298
  }
2500
-
2501
- // src/utils/slug.ts
2502
- function slugify(name, fallback = "prompt", maxLen = 64) {
2503
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, maxLen).replace(/-+$/g, "") || fallback;
2299
+ function parseIncomingImages(images, legacyImageBase64) {
2300
+ const raw = [...images ?? []];
2301
+ if (legacyImageBase64) raw.push({ data: legacyImageBase64 });
2302
+ if (raw.length === 0) return [];
2303
+ if (raw.length > MAX_INCOMING_IMAGES) {
2304
+ throw new IncomingImageError(
2305
+ `Too many images: ${raw.length} (max ${MAX_INCOMING_IMAGES} per message).`
2306
+ );
2307
+ }
2308
+ return raw.map((img, i) => {
2309
+ const { base64, mediaType: fromUrl } = splitDataUrl(img.data ?? "");
2310
+ const mediaType = (img.mediaType ?? fromUrl ?? "image/png").toLowerCase();
2311
+ if (!ALLOWED_IMAGE_MEDIA_TYPES.has(mediaType)) {
2312
+ throw new IncomingImageError(
2313
+ `Image ${i + 1}: unsupported media type "${mediaType}" (allowed: ${[...ALLOWED_IMAGE_MEDIA_TYPES].join(", ")}).`
2314
+ );
2315
+ }
2316
+ if (!base64) {
2317
+ throw new IncomingImageError(`Image ${i + 1}: empty image data.`);
2318
+ }
2319
+ if (!/^[A-Za-z0-9+/]+={0,2}$/.test(base64)) {
2320
+ throw new IncomingImageError(`Image ${i + 1}: data is not valid base64.`);
2321
+ }
2322
+ const bytes = Math.floor(base64.length * 3 / 4);
2323
+ if (bytes > MAX_INCOMING_IMAGE_BYTES) {
2324
+ throw new IncomingImageError(
2325
+ `Image ${i + 1}: ${(bytes / (1024 * 1024)).toFixed(1)} MB exceeds the ${MAX_INCOMING_IMAGE_BYTES / (1024 * 1024)} MB limit.`
2326
+ );
2327
+ }
2328
+ return {
2329
+ type: "image",
2330
+ source: { type: "base64", media_type: mediaType, data: base64 }
2331
+ };
2332
+ });
2504
2333
  }
2505
-
2506
- // src/utils/string.ts
2507
- function truncate(s, max) {
2508
- return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
2334
+ function buildUserContentBlocks(text, images) {
2335
+ const blocks = [...images];
2336
+ if (text) blocks.push({ type: "text", text });
2337
+ return blocks;
2509
2338
  }
2510
2339
 
2511
- // src/utils/task-format.ts
2512
- function computeTaskItemProgress(tasks) {
2513
- let completed = 0;
2514
- let pending = 0;
2515
- let inProgress = 0;
2516
- let blocked = 0;
2517
- let failed = 0;
2518
- let review = 0;
2519
- let estimatedHours = 0;
2520
- const actualHours = 0;
2521
- for (const t of tasks) {
2522
- switch (t.status) {
2523
- case "completed":
2524
- completed++;
2525
- break;
2526
- case "pending":
2527
- pending++;
2528
- break;
2529
- case "in_progress":
2530
- inProgress++;
2531
- break;
2532
- case "blocked":
2533
- blocked++;
2534
- break;
2535
- case "failed":
2536
- failed++;
2537
- break;
2538
- case "review":
2539
- review++;
2540
- break;
2541
- }
2542
- estimatedHours += t.estimateHours ?? 0;
2340
+ // src/utils/ip-guard.ts
2341
+ import * as dns from "node:dns/promises";
2342
+ import * as net from "node:net";
2343
+ function isPrivateIPv4(addr) {
2344
+ const parts = addr.split(".").map((p) => Number.parseInt(p, 10));
2345
+ if (parts.length !== 4 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255)) {
2346
+ return true;
2543
2347
  }
2544
- return {
2545
- total: tasks.length,
2546
- pending,
2547
- inProgress,
2548
- blocked,
2549
- failed,
2550
- review,
2551
- completed,
2552
- percentComplete: tasks.length > 0 ? Math.round(completed / tasks.length * 100) : 0,
2553
- estimatedHours,
2554
- actualHours
2555
- };
2348
+ const [a, b, c] = parts;
2349
+ if (a === 0) return true;
2350
+ if (a === 10) return true;
2351
+ if (a === 127) return true;
2352
+ if (a === 169 && b === 254) return true;
2353
+ if (a === 172 && b >= 16 && b <= 31) return true;
2354
+ if (a === 192 && b === 168) return true;
2355
+ if (a === 192 && b === 0 && c === 0) return true;
2356
+ if (a === 100 && b >= 64 && b <= 127) return true;
2357
+ if (a >= 224) return true;
2358
+ return false;
2556
2359
  }
2557
- var STATUS_ICON = {
2558
- pending: "\u25CB",
2559
- in_progress: "\u25D0",
2560
- blocked: "\u2298",
2561
- failed: "\u2717",
2562
- review: "\u25D1",
2563
- completed: "\u25CF"
2564
- };
2565
- var PRIORITY_ICON = {
2566
- critical: "\u{1F534}",
2567
- high: "\u{1F7E0}",
2568
- medium: "\u{1F7E1}",
2569
- low: "\u{1F7E2}"
2570
- };
2571
- var TYPE_ICON = {
2572
- feature: "\u26A1",
2573
- bugfix: "\u{1F41B}",
2574
- refactor: "\u267B\uFE0F",
2575
- docs: "\u{1F4DD}",
2576
- test: "\u{1F9EA}",
2577
- chore: "\u{1F527}"
2578
- };
2579
- function formatTaskProgress(tasks) {
2580
- const p = computeTaskItemProgress(tasks);
2581
- if (p.total === 0) return "No tasks.";
2582
- const barWidth = 24;
2583
- const filled = Math.round(p.percentComplete / 100 * barWidth);
2584
- const empty = barWidth - filled;
2585
- const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
2586
- return [
2587
- `${color.bold("Tasks")} [${bar}] ${p.percentComplete}%`,
2588
- ` ${color.green("\u25CF")} ${p.completed} done \u2502 ${color.yellow("\u25D0")} ${p.inProgress} active \u2502 ${color.dim("\u25CB")} ${p.pending} pending \u2502 \u2298 ${p.blocked} blocked \u2502 \u2717 ${p.failed} failed`,
2589
- p.estimatedHours > 0 ? ` ${color.dim(`est. ${p.estimatedHours}h`)}` : ""
2590
- ].filter(Boolean).join("\n");
2360
+ function isPrivateIPv6(raw) {
2361
+ const lower = raw.toLowerCase();
2362
+ if (lower === "::" || lower === "::1") return true;
2363
+ const groups = expandIPv6(lower);
2364
+ if (!groups) return true;
2365
+ if (groups[0] === 0 && groups[1] === 0 && groups[2] === 0 && groups[3] === 0 && groups[4] === 0 && groups[5] === 65535) {
2366
+ const a = (groups[6] ?? 0) >> 8;
2367
+ const b = (groups[6] ?? 0) & 255;
2368
+ const c = (groups[7] ?? 0) >> 8;
2369
+ const d = (groups[7] ?? 0) & 255;
2370
+ return isPrivateIPv4(`${a}.${b}.${c}.${d}`);
2371
+ }
2372
+ const high = groups[0] ?? 0;
2373
+ if ((high & 65024) === 64512) return true;
2374
+ if ((high & 65472) === 65152) return true;
2375
+ if ((high & 65280) === 65280) return true;
2376
+ return false;
2377
+ }
2378
+ function expandIPv6(addr) {
2379
+ const parts = addr.split("::");
2380
+ if (parts.length > 2) return null;
2381
+ const parseGroups = (s) => {
2382
+ if (s === "") return [];
2383
+ const out = [];
2384
+ for (const g of s.split(":")) {
2385
+ if (g.length === 0 || g.length > 4) return null;
2386
+ const n = Number.parseInt(g, 16);
2387
+ if (Number.isNaN(n) || n < 0 || n > 65535) return null;
2388
+ out.push(n);
2389
+ }
2390
+ return out;
2391
+ };
2392
+ if (parts.length === 1) {
2393
+ const groups = parseGroups(parts[0] ?? "");
2394
+ if (groups?.length !== 8) return null;
2395
+ return groups;
2396
+ }
2397
+ const head = parseGroups(parts[0] ?? "");
2398
+ const tail = parseGroups(parts[1] ?? "");
2399
+ if (!head || !tail) return null;
2400
+ const fill = 8 - head.length - tail.length;
2401
+ if (fill < 0) return null;
2402
+ return [...head, ...new Array(fill).fill(0), ...tail];
2403
+ }
2404
+ async function assertNotPrivateHost(hostname) {
2405
+ const host = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
2406
+ if (host === "localhost" || host.endsWith(".localhost")) {
2407
+ throw new Error("fetch: blocked localhost target");
2408
+ }
2409
+ const ipVersion = net.isIP(host);
2410
+ if (ipVersion === 4) {
2411
+ if (isPrivateIPv4(host)) {
2412
+ throw new Error(`fetch: blocked private/loopback address "${host}"`);
2413
+ }
2414
+ } else if (ipVersion === 6) {
2415
+ if (isPrivateIPv6(host)) {
2416
+ throw new Error(`fetch: blocked private/loopback address "${host}"`);
2417
+ }
2418
+ } else {
2419
+ try {
2420
+ const records = await dns.lookup(host, { all: true });
2421
+ for (const r of records) {
2422
+ const bad = r.family === 4 ? isPrivateIPv4(r.address) : isPrivateIPv6(r.address);
2423
+ if (bad) {
2424
+ throw new Error(`fetch: resolved to private address ${r.address}`);
2425
+ }
2426
+ }
2427
+ } catch (err) {
2428
+ if (err instanceof Error && err.message.startsWith("fetch:")) throw err;
2429
+ }
2430
+ }
2431
+ }
2432
+
2433
+ // src/utils/json-repair.ts
2434
+ function completePartialObject(s) {
2435
+ if (!s.trim().startsWith("{")) return s;
2436
+ if (tryParse(s).ok) return s;
2437
+ return repairTruncated(s);
2438
+ }
2439
+ function repairTruncated(s) {
2440
+ const stack = [];
2441
+ let inString = false;
2442
+ let escaped = false;
2443
+ let sawKey = false;
2444
+ let prevSig = "";
2445
+ let contentEnd = 0;
2446
+ let stringBraceDepth = 0;
2447
+ for (let i = 0; i < s.length; i++) {
2448
+ const ch = expectDefined(s[i]);
2449
+ if (inString) {
2450
+ contentEnd = i + 1;
2451
+ if (escaped) {
2452
+ escaped = false;
2453
+ continue;
2454
+ }
2455
+ if (ch === "\\") {
2456
+ escaped = true;
2457
+ continue;
2458
+ }
2459
+ if (ch === '"') {
2460
+ inString = false;
2461
+ prevSig = '"';
2462
+ stringBraceDepth = 0;
2463
+ continue;
2464
+ }
2465
+ if (ch === "{") stringBraceDepth++;
2466
+ else if (ch === "}" && stringBraceDepth > 0) stringBraceDepth--;
2467
+ continue;
2468
+ }
2469
+ if (ch === " " || ch === " " || ch === "\n" || ch === "\r") continue;
2470
+ contentEnd = i + 1;
2471
+ if (ch === '"') {
2472
+ inString = true;
2473
+ sawKey = true;
2474
+ stringBraceDepth = 0;
2475
+ prevSig = '"';
2476
+ } else if (ch === "{" || ch === "[") {
2477
+ stack.push(ch);
2478
+ prevSig = ch;
2479
+ } else if (ch === "}" || ch === "]") {
2480
+ stack.pop();
2481
+ prevSig = ch;
2482
+ } else {
2483
+ prevSig = ch;
2484
+ }
2485
+ }
2486
+ if (!sawKey && !inString) return s;
2487
+ let result = s.slice(0, contentEnd);
2488
+ if (inString) {
2489
+ if (escaped) {
2490
+ result = result.slice(0, -1);
2491
+ } else if (endsWithInvalidEscape(result)) {
2492
+ result = result.slice(0, -2);
2493
+ }
2494
+ if (stringBraceDepth > 0) result += "}".repeat(stringBraceDepth);
2495
+ result += '"';
2496
+ } else if (prevSig === ":") {
2497
+ result += "null";
2498
+ }
2499
+ for (let k = stack.length - 1; k >= 0; k--) {
2500
+ result += stack[k] === "{" ? "}" : "]";
2501
+ }
2502
+ if (!tryParse(result).ok) {
2503
+ const patched = result.replace(/:(\s*)([}\]])/g, ":null$2");
2504
+ if (tryParse(patched).ok) result = patched;
2505
+ }
2506
+ return result;
2507
+ }
2508
+ var VALID_ESCAPE = /* @__PURE__ */ new Set(['"', "\\", "/", "b", "f", "n", "r", "t", "u"]);
2509
+ function endsWithInvalidEscape(str) {
2510
+ const last = str[str.length - 1];
2511
+ if (str[str.length - 2] !== "\\" || last === void 0) return false;
2512
+ if (VALID_ESCAPE.has(last)) return false;
2513
+ let backslashes = 0;
2514
+ for (let k = str.length - 2; k >= 0 && str[k] === "\\"; k--) backslashes++;
2515
+ return backslashes % 2 === 1;
2516
+ }
2517
+ function tryParse(s) {
2518
+ try {
2519
+ return { ok: true, value: JSON.parse(s) };
2520
+ } catch {
2521
+ return { ok: false };
2522
+ }
2523
+ }
2524
+
2525
+ // src/utils/json-schema-validate.ts
2526
+ function validateAgainstSchema(value, schema) {
2527
+ const errors = [];
2528
+ walk(value, schema, "", errors, 0);
2529
+ return { ok: errors.length === 0, errors };
2530
+ }
2531
+ var MAX_SCHEMA_DEPTH = 64;
2532
+ function walk(value, schema, path6, errors, depth) {
2533
+ if (depth > MAX_SCHEMA_DEPTH) {
2534
+ errors.push({
2535
+ path: path6 || "<root>",
2536
+ message: `schema nesting exceeds maximum depth (${MAX_SCHEMA_DEPTH})`
2537
+ });
2538
+ return;
2539
+ }
2540
+ if (schema.enum !== void 0) {
2541
+ if (!enumIncludes(schema.enum, value)) {
2542
+ errors.push({
2543
+ path: path6 || "<root>",
2544
+ message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
2545
+ });
2546
+ return;
2547
+ }
2548
+ }
2549
+ if (typeof schema.type === "string") {
2550
+ if (!checkType(value, schema.type)) {
2551
+ errors.push({
2552
+ path: path6 || "<root>",
2553
+ message: `expected ${schema.type}, got ${describeType(value)} (${previewValue(value)})`
2554
+ });
2555
+ return;
2556
+ }
2557
+ }
2558
+ if (schema.type === "object" && isPlainObject(value)) {
2559
+ const obj = value;
2560
+ for (const req of schema.required ?? []) {
2561
+ if (!(req in obj)) {
2562
+ const expected = schema.properties?.[req]?.type;
2563
+ errors.push({
2564
+ path: joinPath(path6, req),
2565
+ message: `required property missing${typeof expected === "string" ? ` (expected ${expected})` : ""}`
2566
+ });
2567
+ }
2568
+ }
2569
+ if (schema.properties) {
2570
+ for (const [key, subSchema] of Object.entries(schema.properties)) {
2571
+ if (key in obj) {
2572
+ walk(obj[key], subSchema, joinPath(path6, key), errors, depth + 1);
2573
+ }
2574
+ }
2575
+ }
2576
+ }
2577
+ if (schema.type === "array" && Array.isArray(value) && schema.items) {
2578
+ for (let i = 0; i < value.length; i++) {
2579
+ walk(value[i], schema.items, `${path6}[${i}]`, errors, depth + 1);
2580
+ }
2581
+ }
2582
+ }
2583
+ function coerceAgainstSchema(value, schema) {
2584
+ return coerceWalk(value, schema, 0);
2585
+ }
2586
+ function coerceWalk(value, schema, depth) {
2587
+ if (depth > MAX_SCHEMA_DEPTH) return { value, changed: false };
2588
+ const type = typeof schema.type === "string" ? schema.type : void 0;
2589
+ if (type && !checkType(value, type)) {
2590
+ if (type === "string" && (typeof value === "number" || typeof value === "boolean")) {
2591
+ return { value: String(value), changed: true };
2592
+ }
2593
+ if ((type === "number" || type === "integer") && typeof value === "string") {
2594
+ const trimmed = value.trim();
2595
+ if (trimmed !== "" && /^-?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(trimmed)) {
2596
+ const num = Number(trimmed);
2597
+ if (!Number.isNaN(num) && (type === "number" || Number.isInteger(num))) {
2598
+ return { value: num, changed: true };
2599
+ }
2600
+ }
2601
+ return { value, changed: false };
2602
+ }
2603
+ if (type === "boolean" && typeof value === "string") {
2604
+ const lowered = value.trim().toLowerCase();
2605
+ if (lowered === "true") return { value: true, changed: true };
2606
+ if (lowered === "false") return { value: false, changed: true };
2607
+ return { value, changed: false };
2608
+ }
2609
+ if ((type === "object" || type === "array") && typeof value === "string") {
2610
+ try {
2611
+ const parsed = JSON.parse(value);
2612
+ if (checkType(parsed, type)) {
2613
+ return { value: coerceWalk(parsed, schema, depth + 1).value, changed: true };
2614
+ }
2615
+ } catch {
2616
+ }
2617
+ return { value, changed: false };
2618
+ }
2619
+ return { value, changed: false };
2620
+ }
2621
+ if (type === "object" && isPlainObject(value) && schema.properties) {
2622
+ const obj = value;
2623
+ let changed = false;
2624
+ const out = { ...obj };
2625
+ for (const [key, subSchema] of Object.entries(schema.properties)) {
2626
+ if (!(key in obj)) continue;
2627
+ const r = coerceWalk(obj[key], subSchema, depth + 1);
2628
+ if (r.changed) {
2629
+ out[key] = r.value;
2630
+ changed = true;
2631
+ }
2632
+ }
2633
+ return changed ? { value: out, changed } : { value, changed: false };
2634
+ }
2635
+ if (type === "array" && Array.isArray(value) && schema.items) {
2636
+ let changed = false;
2637
+ const out = value.map((item) => {
2638
+ const r = coerceWalk(item, schema.items, depth + 1);
2639
+ if (r.changed) changed = true;
2640
+ return r.value;
2641
+ });
2642
+ return changed ? { value: out, changed } : { value, changed: false };
2643
+ }
2644
+ return { value, changed: false };
2591
2645
  }
2592
- function formatTaskList(tasks) {
2593
- if (tasks.length === 0) return "No tasks.";
2594
- const order = ["in_progress", "blocked", "review", "pending", "failed", "completed"];
2595
- const groups = /* @__PURE__ */ new Map();
2596
- for (const t of tasks) {
2597
- const list = groups.get(t.status) ?? [];
2598
- list.push(t);
2599
- groups.set(t.status, list);
2646
+ function checkType(value, type) {
2647
+ switch (type) {
2648
+ case "string":
2649
+ return typeof value === "string";
2650
+ case "number":
2651
+ return typeof value === "number" && !Number.isNaN(value);
2652
+ case "integer":
2653
+ return typeof value === "number" && Number.isInteger(value);
2654
+ case "boolean":
2655
+ return typeof value === "boolean";
2656
+ case "null":
2657
+ return value === null;
2658
+ case "array":
2659
+ return Array.isArray(value);
2660
+ case "object":
2661
+ return isPlainObject(value);
2662
+ default:
2663
+ return true;
2600
2664
  }
2601
- const lines = [];
2602
- lines.push(color.dim(`Tasks (${tasks.length} total):`));
2603
- for (const status of order) {
2604
- const group = groups.get(status);
2605
- if (!group || group.length === 0) continue;
2606
- const icon = STATUS_ICON[status];
2607
- lines.push(` ${icon} ${status.toUpperCase()} (${group.length})`);
2608
- for (const t of group) {
2609
- const prio = PRIORITY_ICON[t.priority];
2610
- const type = TYPE_ICON[t.type];
2611
- const deps = t.dependsOn && t.dependsOn.length > 0 ? ` ${color.dim("\u2190")} ${color.dim(t.dependsOn.map((d) => d.slice(0, 8)).join(", "))}` : "";
2612
- const who = t.assignee ? ` ${color.dim(`@${t.assignee}`)}` : "";
2613
- const hrs = t.estimateHours ? ` ${color.dim(`${t.estimateHours}h`)}` : "";
2614
- lines.push(` ${type} ${prio} ${t.title}${deps}${who}${hrs}`);
2615
- }
2665
+ }
2666
+ function isPlainObject(v) {
2667
+ return typeof v === "object" && v !== null && !Array.isArray(v);
2668
+ }
2669
+ function describeType(v) {
2670
+ if (v === null) return "null";
2671
+ if (Array.isArray(v)) return "array";
2672
+ return typeof v;
2673
+ }
2674
+ function previewValue(v) {
2675
+ try {
2676
+ const s = JSON.stringify(v);
2677
+ if (s === void 0) return String(v);
2678
+ return s.length > 80 ? `${s.slice(0, 80)}\u2026` : s;
2679
+ } catch {
2680
+ return String(v);
2616
2681
  }
2617
- return lines.join("\n");
2618
2682
  }
2619
-
2620
- // src/utils/todos-format.ts
2621
- function formatTodosList(todos) {
2622
- if (todos.length === 0) return "No todos.";
2623
- const lines = [];
2624
- const done = todos.filter((t) => t.status === "completed").length;
2625
- lines.push(color.dim(`Todos (${done}/${todos.length} done):`));
2626
- todos.forEach((t, i) => {
2627
- const mark = t.status === "completed" ? color.green("[x]") : t.status === "in_progress" ? color.yellow("[~]") : color.dim("[ ]");
2628
- const text = t.status === "in_progress" && t.activeForm ? t.activeForm : t.content;
2629
- const label = t.status === "completed" ? color.dim(text) : text;
2630
- lines.push(` ${color.dim(String(i + 1).padStart(2))}. ${mark} ${label}`);
2631
- });
2632
- return lines.join("\n");
2683
+ function joinPath(parent, key) {
2684
+ if (!parent) return key;
2685
+ return `${parent}.${key}`;
2633
2686
  }
2634
- function hasOpenTodos(todos) {
2635
- if (!Array.isArray(todos) || todos.length === 0) return false;
2636
- return todos.some((t) => t.status === "pending" || t.status === "in_progress");
2687
+ function enumIncludes(values, value) {
2688
+ if (value === null || typeof value !== "object") return values.includes(value);
2689
+ return values.some((candidate) => deepEqual(candidate, value));
2637
2690
  }
2638
-
2639
- // src/utils/tool-wire-compact.ts
2640
- var TOOL_DESCRIPTION_MAX_CHARS = 400;
2641
- var SCHEMA_DESCRIPTION_MAX_CHARS = 120;
2642
- var compactCache = /* @__PURE__ */ new WeakMap();
2643
- function compactToolDefinitionForWire(tool, opts = {}) {
2644
- const useDefaultOptions = opts.descriptionMaxChars === void 0 && opts.schemaDescriptionMaxChars === void 0;
2645
- if (useDefaultOptions && typeof tool === "object" && tool !== null) {
2646
- const cached = compactCache.get(tool);
2647
- if (cached) return cached;
2691
+ function deepEqual(a, b) {
2692
+ if (a === b) return true;
2693
+ if (typeof a !== typeof b) return false;
2694
+ if (a === null || b === null) return a === b;
2695
+ if (Array.isArray(a) && Array.isArray(b)) {
2696
+ return a.length === b.length && a.every((v, i) => deepEqual(v, b[i]));
2648
2697
  }
2649
- const compact = {
2650
- name: tool.name,
2651
- description: compactDescription(
2652
- tool.description ?? "",
2653
- opts.descriptionMaxChars ?? TOOL_DESCRIPTION_MAX_CHARS
2654
- ),
2655
- inputSchema: normalizeTopLevelToolSchema(
2656
- compactSchemaDescriptions(
2657
- tool.inputSchema,
2658
- opts.schemaDescriptionMaxChars ?? SCHEMA_DESCRIPTION_MAX_CHARS
2659
- )
2660
- )
2661
- };
2662
- if (useDefaultOptions && typeof tool === "object" && tool !== null) {
2663
- compactCache.set(tool, compact);
2698
+ if (typeof a === "object" && typeof b === "object") {
2699
+ const ak = Object.keys(a);
2700
+ const bk = Object.keys(b);
2701
+ if (ak.length !== bk.length) return false;
2702
+ return ak.every(
2703
+ (k) => deepEqual(a[k], b[k])
2704
+ );
2664
2705
  }
2665
- return compact;
2706
+ return false;
2666
2707
  }
2667
- function normalizeTopLevelToolSchema(schema) {
2668
- const combinators = ["oneOf", "anyOf", "allOf"].map((keyword) => ({ keyword, branches: schema[keyword] })).filter(
2669
- (entry) => Array.isArray(entry.branches)
2670
- );
2671
- if (combinators.length === 0) return schema;
2672
- const out = { ...schema, type: "object" };
2673
- delete out["oneOf"];
2674
- delete out["anyOf"];
2675
- delete out["allOf"];
2676
- const properties = isRecord(schema["properties"]) ? { ...schema["properties"] } : {};
2677
- let required = stringSet(schema["required"]);
2678
- for (const { keyword, branches } of combinators) {
2679
- const objectBranches = branches.filter(isRecord);
2680
- for (const branch of objectBranches) {
2681
- if (isRecord(branch["properties"])) Object.assign(properties, branch["properties"]);
2708
+
2709
+ // src/utils/merge-custom-models.ts
2710
+ function mergeCustomModelDefs(providerCustomModels, configModels) {
2711
+ const out = {};
2712
+ if (providerCustomModels) {
2713
+ for (const [id, def] of Object.entries(providerCustomModels)) {
2714
+ out[id] = { ...def };
2682
2715
  }
2683
- const branchRequired = objectBranches.map((branch) => stringSet(branch["required"]));
2684
- if (keyword === "allOf") {
2685
- for (const fields of branchRequired) for (const field of fields) required.add(field);
2686
- } else if (branchRequired.length > 0) {
2687
- const common = new Set(
2688
- [...branchRequired[0]].filter(
2689
- (field) => branchRequired.slice(1).every((fields) => fields.has(field))
2690
- )
2691
- );
2692
- required = /* @__PURE__ */ new Set([...required, ...common]);
2716
+ }
2717
+ if (configModels) {
2718
+ for (const [id, def] of Object.entries(configModels)) {
2719
+ out[id] = { ...def };
2693
2720
  }
2694
2721
  }
2695
- out["properties"] = properties;
2696
- if (required.size > 0) out["required"] = [...required];
2697
- else delete out["required"];
2722
+ if (Object.keys(out).length === 0) return void 0;
2698
2723
  return out;
2699
2724
  }
2700
- function stringSet(value) {
2701
- return new Set(
2702
- Array.isArray(value) ? value.filter((item) => typeof item === "string") : []
2703
- );
2725
+
2726
+ // src/utils/merge-models-payload.ts
2727
+ function mergeModelsPayload(base, overlay) {
2728
+ const out = {};
2729
+ for (const [id, provider] of Object.entries(base)) {
2730
+ out[id] = cloneProvider(provider);
2731
+ }
2732
+ for (const [id, ovProvider] of Object.entries(overlay)) {
2733
+ const existing = out[id];
2734
+ out[id] = existing ? mergeProvider(existing, ovProvider) : cloneProvider(ovProvider);
2735
+ }
2736
+ return out;
2704
2737
  }
2705
- function compactSchemaDescriptions(schema, maxDescriptionChars = SCHEMA_DESCRIPTION_MAX_CHARS) {
2706
- const compact = compactSchemaNode(schema, maxDescriptionChars);
2707
- return isRecord(compact) ? compact : { type: "object", properties: {} };
2738
+ function mergeProvider(base, overlay) {
2739
+ const models = {};
2740
+ for (const [mid, m] of Object.entries(base.models ?? {})) {
2741
+ models[mid] = { ...m };
2742
+ }
2743
+ for (const [mid, ovModel] of Object.entries(overlay.models ?? {})) {
2744
+ const existing = models[mid];
2745
+ models[mid] = existing ? mergeModel(existing, ovModel) : { ...ovModel };
2746
+ }
2747
+ return {
2748
+ ...base,
2749
+ // Overlay scalar fields win when explicitly provided; otherwise keep base.
2750
+ ...stripUndefined({
2751
+ id: overlay.id,
2752
+ name: overlay.name,
2753
+ npm: overlay.npm,
2754
+ api: overlay.api,
2755
+ env: overlay.env,
2756
+ doc: overlay.doc
2757
+ }),
2758
+ models
2759
+ };
2708
2760
  }
2709
- function compactSchemaNode(node, maxDescriptionChars) {
2710
- if (Array.isArray(node)) {
2711
- return node.map((item) => compactSchemaNode(item, maxDescriptionChars));
2761
+ function mergeModel(base, overlay) {
2762
+ const merged = { ...base, ...overlay };
2763
+ if (base.limit || overlay.limit) {
2764
+ merged.limit = { ...base.limit, ...overlay.limit };
2712
2765
  }
2713
- if (!isRecord(node)) return node;
2766
+ if (base.cost || overlay.cost) {
2767
+ merged.cost = { ...base.cost, ...overlay.cost };
2768
+ }
2769
+ if (base.modalities || overlay.modalities) {
2770
+ merged.modalities = { ...base.modalities, ...overlay.modalities };
2771
+ }
2772
+ return merged;
2773
+ }
2774
+ function cloneProvider(p) {
2775
+ const models = {};
2776
+ for (const [mid, m] of Object.entries(p.models ?? {})) {
2777
+ models[mid] = { ...m };
2778
+ }
2779
+ return { ...p, models };
2780
+ }
2781
+ function stripUndefined(obj) {
2714
2782
  const out = {};
2715
- for (const [key, value] of Object.entries(node)) {
2716
- if (key === "description" && typeof value === "string") {
2717
- out[key] = compactDescription(value, maxDescriptionChars);
2718
- } else {
2719
- out[key] = compactSchemaNode(value, maxDescriptionChars);
2783
+ for (const [k, v] of Object.entries(obj)) {
2784
+ if (v !== void 0) out[k] = v;
2785
+ }
2786
+ return out;
2787
+ }
2788
+
2789
+ // src/utils/newline-normalize.ts
2790
+ function detectNewlineStyle(text) {
2791
+ let lf = 0;
2792
+ let crlf = 0;
2793
+ let cr = 0;
2794
+ for (let i = 0; i < text.length; i++) {
2795
+ const c = text.charCodeAt(i);
2796
+ if (c === 13) {
2797
+ if (text.charCodeAt(i + 1) === 10) {
2798
+ crlf++;
2799
+ i++;
2800
+ } else {
2801
+ cr++;
2802
+ }
2803
+ } else if (c === 10) {
2804
+ lf++;
2720
2805
  }
2721
2806
  }
2722
- return out;
2723
- }
2724
- function compactDescription(text, maxChars) {
2725
- const normalized = text.replace(/\s+/g, " ").trim();
2726
- if (normalized.length <= maxChars) return normalized;
2727
- if (maxChars <= 20) return normalized.slice(0, maxChars);
2728
- const hardLimit = maxChars - 12;
2729
- const boundary = findSemanticBoundary(normalized, hardLimit);
2730
- const head = normalized.slice(0, boundary > 0 ? boundary : hardLimit).trimEnd();
2731
- return `${head} ...`;
2807
+ if (crlf > lf && crlf > cr) return "crlf";
2808
+ if (cr > lf && cr > crlf) return "cr";
2809
+ return "lf";
2732
2810
  }
2733
- function findSemanticBoundary(text, limit) {
2734
- const punctuation = Math.max(
2735
- text.lastIndexOf(". ", limit),
2736
- text.lastIndexOf("; ", limit),
2737
- text.lastIndexOf(": ", limit)
2738
- );
2739
- if (punctuation >= Math.floor(limit * 0.45)) return punctuation + 1;
2740
- const comma = text.lastIndexOf(", ", limit);
2741
- if (comma >= Math.floor(limit * 0.6)) return comma + 1;
2742
- const space = text.lastIndexOf(" ", limit);
2743
- return space >= Math.floor(limit * 0.6) ? space : limit;
2811
+ function toStyle(text, style) {
2812
+ const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
2813
+ if (style === "lf") return normalized;
2814
+ if (style === "crlf") return normalized.replace(/\n/g, "\r\n");
2815
+ return normalized.replace(/\n/g, "\r");
2744
2816
  }
2745
- function isRecord(value) {
2746
- return !!value && typeof value === "object" && !Array.isArray(value);
2817
+ function normalizeToLf(text) {
2818
+ return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
2747
2819
  }
2748
2820
 
2749
- // src/utils/token-estimate.ts
2750
- var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
2751
- var CAL_ALPHA = 0.3;
2752
- var CALIBRATION_GLOBAL_KEY = "__global__";
2753
- var _cals = /* @__PURE__ */ new Map();
2754
- function calState(key) {
2755
- let state = _cals.get(key);
2756
- if (!state) {
2757
- state = { ratio: 1, count: 0, prevEst: 0 };
2758
- _cals.set(key, state);
2821
+ // src/utils/regex-guard.ts
2822
+ var MAX_PATTERN_LEN = 512;
2823
+ var DANGEROUS_PATTERNS = [
2824
+ /(\([^)]*[+*][^)]*\))[+*]/,
2825
+ // (a+)+, (.*)+, etc
2826
+ /(\(\?:[^)]*[+*][^)]*\))[+*]/
2827
+ // same, with non-capturing group
2828
+ ];
2829
+ function compileUserRegex(pattern, flags) {
2830
+ if (typeof pattern !== "string") {
2831
+ return { ok: false, reason: "pattern must be a string" };
2759
2832
  }
2760
- return state;
2761
- }
2762
- var MIN_SAMPLES_FOR_CALIBRATION = 3;
2763
- var MODEL_FAMILY_RATIO = {
2764
- // Anthropic: ~3.8-4.0 chars/token depending on model
2765
- claude: 3.8,
2766
- // OpenAI: ~4.0 chars/token
2767
- "gpt-4": 4,
2768
- "gpt-3.5": 4,
2769
- // Google: ~3.5 chars/token
2770
- gemini: 3.5,
2771
- // DeepSeek: ~3.5 chars/token
2772
- deepseek: 3.5
2773
- };
2774
- var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
2775
- var _estimateCacheOrder = [];
2776
- var ESTIMATE_CACHE_MAX_SIZE = 5e4;
2777
- function getCachedEstimate(key, compute) {
2778
- const existing = ESTIMATE_CACHE.get(key);
2779
- if (existing !== void 0) return existing;
2780
- if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
2781
- while (ESTIMATE_CACHE.size > Math.floor(ESTIMATE_CACHE_MAX_SIZE / 2)) {
2782
- const oldest = _estimateCacheOrder.shift();
2783
- if (oldest !== void 0) ESTIMATE_CACHE.delete(oldest);
2833
+ if (pattern.length === 0) {
2834
+ return { ok: false, reason: "pattern is empty" };
2835
+ }
2836
+ if (pattern.length > MAX_PATTERN_LEN) {
2837
+ return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
2838
+ }
2839
+ for (const rx of DANGEROUS_PATTERNS) {
2840
+ if (rx.test(pattern)) {
2841
+ return {
2842
+ ok: false,
2843
+ reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
2844
+ };
2784
2845
  }
2785
2846
  }
2786
- const estimate = compute(key);
2787
- ESTIMATE_CACHE.set(key, estimate);
2788
- _estimateCacheOrder.push(key);
2789
- return estimate;
2847
+ try {
2848
+ return { ok: true, regex: new RegExp(pattern, flags) };
2849
+ } catch (err) {
2850
+ return {
2851
+ ok: false,
2852
+ reason: err instanceof Error ? err.message : "invalid regex"
2853
+ };
2854
+ }
2790
2855
  }
2791
- function estimateToolInputTokens(input) {
2792
- if (typeof input === "string") return RoughTokenEstimate(input);
2793
- if (input === null || typeof input !== "object") {
2794
- return RoughTokenEstimate(String(input));
2856
+
2857
+ // src/utils/safe-json.ts
2858
+ function safeParse(input, maxBytes = 5e6) {
2859
+ if (Buffer.byteLength(input, "utf8") > maxBytes) {
2860
+ return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };
2861
+ }
2862
+ try {
2863
+ return { ok: true, value: JSON.parse(input) };
2864
+ } catch (err) {
2865
+ return {
2866
+ ok: false,
2867
+ error: toErrorMessage(err)
2868
+ };
2795
2869
  }
2796
- return getCachedEstimate(JSON.stringify(input), (key) => RoughTokenEstimate(key));
2797
2870
  }
2798
- function estimateToolResultTokens(content) {
2799
- if (typeof content === "string") return RoughTokenEstimate(content);
2800
- return getCachedEstimate(JSON.stringify(content), (key) => RoughTokenEstimate(key));
2871
+ function safeStringify(value, pretty = false) {
2872
+ const seen = /* @__PURE__ */ new WeakSet();
2873
+ const replacer = (_k, v) => {
2874
+ if (typeof v === "bigint") return v.toString();
2875
+ if (v instanceof Error) {
2876
+ return { name: v.name, message: v.message, stack: v.stack };
2877
+ }
2878
+ if (typeof v === "object" && v !== null) {
2879
+ if (seen.has(v)) return "[Circular]";
2880
+ seen.add(v);
2881
+ }
2882
+ return v;
2883
+ };
2884
+ try {
2885
+ return JSON.stringify(value, replacer, pretty ? 2 : void 0) ?? "null";
2886
+ } catch (err) {
2887
+ return JSON.stringify({
2888
+ __serialization_error: toErrorMessage(err)
2889
+ });
2890
+ }
2801
2891
  }
2802
- function estimateTextTokens(text) {
2803
- return RoughTokenEstimate(text);
2892
+ function sanitizeJsonString(s) {
2893
+ let out = s.trim();
2894
+ out = stripJsonComments(out);
2895
+ out = out.replace(/,(\s*[}\]])/g, "$1");
2896
+ out = escapeControlCharsInStrings(out);
2897
+ try {
2898
+ JSON.parse(out);
2899
+ return out;
2900
+ } catch {
2901
+ return null;
2902
+ }
2804
2903
  }
2805
- function computeMessageTokens(msg) {
2806
- if (typeof msg.content === "string") return estimateTextTokens(msg.content);
2807
- let total = 0;
2808
- for (const b of msg.content) {
2809
- if (b.type === "text") total += estimateTextTokens(b.text);
2810
- else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
2811
- else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
2812
- else total += RoughTokenEstimate(JSON.stringify(b));
2904
+ function stripCodeFences(s) {
2905
+ const trimmed = s.trim();
2906
+ const opener = /^```[\w+-]*[ \t]*\r?\n?/.exec(trimmed);
2907
+ if (opener) {
2908
+ const inner = trimmed.slice(opener[0].length).replace(/(\r?\n)?[ \t]*```[ \t]*$/, "");
2909
+ return inner.trim();
2813
2910
  }
2814
- return total;
2911
+ const embedded = /```[\w+-]*[ \t]*\r?\n([\s\S]*?)\r?\n[ \t]*```/.exec(trimmed);
2912
+ if (embedded) return (embedded[1] ?? "").trim();
2913
+ return null;
2815
2914
  }
2816
- function estimateMessageTokens(messages) {
2817
- let total = 0;
2818
- for (const m of messages) {
2819
- if (typeof m._estTokens === "number" && m._estTokens > 0) {
2820
- total += m._estTokens;
2915
+ function escapeControlCharsInStrings(s) {
2916
+ let inString = false;
2917
+ let out = "";
2918
+ for (let i = 0; i < s.length; i++) {
2919
+ const c = s.charAt(i);
2920
+ if (c === '"' && (i === 0 || s[i - 1] !== "\\")) {
2921
+ inString = !inString;
2922
+ out += c;
2821
2923
  continue;
2822
2924
  }
2823
- total += computeMessageTokens(m);
2925
+ const code = c.charCodeAt(0);
2926
+ if (inString && code < 32) {
2927
+ switch (c) {
2928
+ case "\n":
2929
+ out += "\\n";
2930
+ break;
2931
+ case "\r":
2932
+ out += "\\r";
2933
+ break;
2934
+ case " ":
2935
+ out += "\\t";
2936
+ break;
2937
+ case "\b":
2938
+ out += "\\b";
2939
+ break;
2940
+ case "\f":
2941
+ out += "\\f";
2942
+ break;
2943
+ default:
2944
+ out += `\\u${code.toString(16).padStart(4, "0")}`;
2945
+ }
2946
+ continue;
2947
+ }
2948
+ out += c;
2824
2949
  }
2825
- return total;
2826
- }
2827
- function estimateToolDefTokens(tool) {
2828
- const cached = tool._estDefTokens;
2829
- if (typeof cached === "number" && cached > 0) return cached;
2830
- const compact = compactToolDefinitionForWire(tool);
2831
- return RoughTokenEstimate(tool.name) + RoughTokenEstimate(compact.description) + RoughTokenEstimate(JSON.stringify(compact.inputSchema));
2950
+ return out;
2832
2951
  }
2833
- function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
2834
- let messagesTokens = 0;
2835
- if (typeof messages === "string") {
2836
- messagesTokens = RoughTokenEstimate(messages);
2837
- } else if (Array.isArray(messages)) {
2838
- for (const m of messages) {
2839
- if (typeof m === "object" && m !== null && "content" in m) {
2840
- const cached = m._estTokens;
2841
- if (typeof cached === "number" && cached > 0) {
2842
- messagesTokens += cached;
2843
- continue;
2844
- }
2845
- const content = m.content;
2846
- if (typeof content === "string") {
2847
- messagesTokens += RoughTokenEstimate(content);
2848
- } else if (Array.isArray(content)) {
2849
- for (const b of content) {
2850
- if (typeof b === "object" && b !== null) {
2851
- if (b.type === "text") {
2852
- messagesTokens += RoughTokenEstimate(b.text);
2853
- } else {
2854
- messagesTokens += RoughTokenEstimate(JSON.stringify(b));
2855
- }
2856
- }
2857
- }
2858
- }
2952
+ function stripJsonComments(s) {
2953
+ let inString = false;
2954
+ let escaped = false;
2955
+ const chars = [];
2956
+ let i = 0;
2957
+ while (i < s.length) {
2958
+ const c = s.charAt(i);
2959
+ if (inString) {
2960
+ chars.push(c);
2961
+ if (escaped) {
2962
+ escaped = false;
2963
+ } else if (c === "\\") {
2964
+ escaped = true;
2965
+ } else if (c === '"') {
2966
+ inString = false;
2859
2967
  }
2968
+ i++;
2969
+ continue;
2860
2970
  }
2861
- }
2862
- let systemTokens = 0;
2863
- if (typeof systemPrompt === "string") {
2864
- systemTokens = RoughTokenEstimate(systemPrompt);
2865
- } else if (Array.isArray(systemPrompt)) {
2866
- for (const b of systemPrompt) {
2867
- if (typeof b === "object" && b !== null && b.type === "text") {
2868
- systemTokens += RoughTokenEstimate(b.text);
2971
+ if (c === '"') {
2972
+ inString = true;
2973
+ chars.push(c);
2974
+ i++;
2975
+ continue;
2976
+ }
2977
+ if (c === "/" && s.charAt(i + 1) === "/") {
2978
+ while (i < s.length && s.charAt(i) !== "\n") i++;
2979
+ continue;
2980
+ }
2981
+ if (c === "/" && s.charAt(i + 1) === "*") {
2982
+ const end = s.indexOf("*/", i + 2);
2983
+ if (end === -1) {
2984
+ chars.push(s.slice(i));
2985
+ break;
2869
2986
  }
2987
+ i = end + 2;
2988
+ continue;
2870
2989
  }
2990
+ chars.push(c);
2991
+ i++;
2871
2992
  }
2872
- let toolsTokens = 0;
2873
- for (const t of tools) {
2874
- toolsTokens += estimateToolDefTokens(t);
2875
- }
2876
- const total = messagesTokens + systemTokens + toolsTokens;
2877
- calState(calibrationKey).prevEst = total;
2878
- return {
2879
- messages: messagesTokens,
2880
- systemPrompt: systemTokens,
2881
- tools: toolsTokens,
2882
- total
2883
- };
2993
+ return chars.join("");
2884
2994
  }
2885
- function recordActualUsage(actualInputTokens, estimatedInputTokens, calibrationKey = CALIBRATION_GLOBAL_KEY) {
2886
- if (actualInputTokens <= 0) return;
2887
- const cal = calState(calibrationKey);
2888
- const est = estimatedInputTokens ?? cal.prevEst;
2889
- if (est <= 0) return;
2890
- const sampleRatio = actualInputTokens / est;
2891
- if (cal.count === 0) {
2892
- cal.ratio = sampleRatio;
2893
- } else {
2894
- cal.ratio = CAL_ALPHA * sampleRatio + (1 - CAL_ALPHA) * cal.ratio;
2995
+
2996
+ // src/utils/session-scoped-path.ts
2997
+ import * as path4 from "node:path";
2998
+ function sessionScopedPath(dir, sessionId, suffix) {
2999
+ if (!sessionId || sessionId.includes("\\") || sessionId.includes("..")) {
3000
+ throw invalid(sessionId);
2895
3001
  }
2896
- cal.ratio = Math.min(1.5, Math.max(0.5, cal.ratio));
2897
- cal.count++;
3002
+ const resolved = path4.resolve(dir, `${sessionId}${suffix}`);
3003
+ const rel = path4.relative(path4.resolve(dir), resolved);
3004
+ if (rel.startsWith("..") || path4.isAbsolute(rel)) {
3005
+ throw invalid(sessionId);
3006
+ }
3007
+ return resolved;
2898
3008
  }
2899
- function getCalibrationState(calibrationKey = CALIBRATION_GLOBAL_KEY) {
2900
- const cal = calState(calibrationKey);
3009
+ function invalid(sessionId) {
3010
+ return new FsError({
3011
+ message: `Invalid sessionId: ${sessionId}`,
3012
+ code: ERROR_CODES.FS_DELETE_FAILED,
3013
+ path: sessionId,
3014
+ context: { reason: "path_traversal" }
3015
+ });
3016
+ }
3017
+
3018
+ // src/utils/sleep.ts
3019
+ function sleep(ms) {
3020
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
3021
+ }
3022
+
3023
+ // src/utils/slug.ts
3024
+ function slugify(name, fallback = "prompt", maxLen = 64) {
3025
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, maxLen).replace(/-+$/g, "") || fallback;
3026
+ }
3027
+
3028
+ // src/utils/string.ts
3029
+ function truncate(s, max) {
3030
+ return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
3031
+ }
3032
+
3033
+ // src/utils/task-format.ts
3034
+ function computeTaskItemProgress(tasks) {
3035
+ let completed = 0;
3036
+ let pending = 0;
3037
+ let inProgress = 0;
3038
+ let blocked = 0;
3039
+ let failed = 0;
3040
+ let review = 0;
3041
+ let estimatedHours = 0;
3042
+ const actualHours = 0;
3043
+ for (const t of tasks) {
3044
+ switch (t.status) {
3045
+ case "completed":
3046
+ completed++;
3047
+ break;
3048
+ case "pending":
3049
+ pending++;
3050
+ break;
3051
+ case "in_progress":
3052
+ inProgress++;
3053
+ break;
3054
+ case "blocked":
3055
+ blocked++;
3056
+ break;
3057
+ case "failed":
3058
+ failed++;
3059
+ break;
3060
+ case "review":
3061
+ review++;
3062
+ break;
3063
+ }
3064
+ estimatedHours += t.estimateHours ?? 0;
3065
+ }
2901
3066
  return {
2902
- ratio: cal.ratio,
2903
- count: cal.count,
2904
- calibrated: cal.count >= MIN_SAMPLES_FOR_CALIBRATION
3067
+ total: tasks.length,
3068
+ pending,
3069
+ inProgress,
3070
+ blocked,
3071
+ failed,
3072
+ review,
3073
+ completed,
3074
+ percentComplete: tasks.length > 0 ? Math.round(completed / tasks.length * 100) : 0,
3075
+ estimatedHours,
3076
+ actualHours
2905
3077
  };
2906
3078
  }
2907
- function estimateRequestTokensCalibrated(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
2908
- const result = estimateRequestTokens(messages, systemPrompt, tools, calibrationKey);
2909
- const cal = calState(calibrationKey);
2910
- if (cal.count >= MIN_SAMPLES_FOR_CALIBRATION) {
2911
- const safeRatio = Math.min(1.5, Math.max(0.5, cal.ratio));
2912
- return {
2913
- messages: Math.round(result.messages * safeRatio),
2914
- systemPrompt: Math.round(result.systemPrompt * safeRatio),
2915
- tools: Math.round(result.tools * safeRatio),
2916
- total: Math.round(result.total * safeRatio)
2917
- };
3079
+ var STATUS_ICON = {
3080
+ pending: "\u25CB",
3081
+ in_progress: "\u25D0",
3082
+ blocked: "\u2298",
3083
+ failed: "\u2717",
3084
+ review: "\u25D1",
3085
+ completed: "\u25CF"
3086
+ };
3087
+ var PRIORITY_ICON = {
3088
+ critical: "\u{1F534}",
3089
+ high: "\u{1F7E0}",
3090
+ medium: "\u{1F7E1}",
3091
+ low: "\u{1F7E2}"
3092
+ };
3093
+ var TYPE_ICON = {
3094
+ feature: "\u26A1",
3095
+ bugfix: "\u{1F41B}",
3096
+ refactor: "\u267B\uFE0F",
3097
+ docs: "\u{1F4DD}",
3098
+ test: "\u{1F9EA}",
3099
+ chore: "\u{1F527}"
3100
+ };
3101
+ function formatTaskProgress(tasks) {
3102
+ const p = computeTaskItemProgress(tasks);
3103
+ if (p.total === 0) return "No tasks.";
3104
+ const barWidth = 24;
3105
+ const filled = Math.round(p.percentComplete / 100 * barWidth);
3106
+ const empty = barWidth - filled;
3107
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
3108
+ return [
3109
+ `${color.bold("Tasks")} [${bar}] ${p.percentComplete}%`,
3110
+ ` ${color.green("\u25CF")} ${p.completed} done \u2502 ${color.yellow("\u25D0")} ${p.inProgress} active \u2502 ${color.dim("\u25CB")} ${p.pending} pending \u2502 \u2298 ${p.blocked} blocked \u2502 \u2717 ${p.failed} failed`,
3111
+ p.estimatedHours > 0 ? ` ${color.dim(`est. ${p.estimatedHours}h`)}` : ""
3112
+ ].filter(Boolean).join("\n");
3113
+ }
3114
+ function formatTaskList(tasks) {
3115
+ if (tasks.length === 0) return "No tasks.";
3116
+ const order = ["in_progress", "blocked", "review", "pending", "failed", "completed"];
3117
+ const groups = /* @__PURE__ */ new Map();
3118
+ for (const t of tasks) {
3119
+ const list = groups.get(t.status) ?? [];
3120
+ list.push(t);
3121
+ groups.set(t.status, list);
2918
3122
  }
2919
- const fallbackRatio = getModelFamilyRatio(calibrationKey);
2920
- if (fallbackRatio !== null) {
2921
- return {
2922
- messages: Math.round(result.messages * fallbackRatio),
2923
- systemPrompt: Math.round(result.systemPrompt * fallbackRatio),
2924
- tools: Math.round(result.tools * fallbackRatio),
2925
- total: Math.round(result.total * fallbackRatio)
2926
- };
3123
+ const lines = [];
3124
+ lines.push(color.dim(`Tasks (${tasks.length} total):`));
3125
+ for (const status of order) {
3126
+ const group = groups.get(status);
3127
+ if (!group || group.length === 0) continue;
3128
+ const icon = STATUS_ICON[status];
3129
+ lines.push(` ${icon} ${status.toUpperCase()} (${group.length})`);
3130
+ for (const t of group) {
3131
+ const prio = PRIORITY_ICON[t.priority];
3132
+ const type = TYPE_ICON[t.type];
3133
+ const deps = t.dependsOn && t.dependsOn.length > 0 ? ` ${color.dim("\u2190")} ${color.dim(t.dependsOn.map((d) => d.slice(0, 8)).join(", "))}` : "";
3134
+ const who = t.assignee ? ` ${color.dim(`@${t.assignee}`)}` : "";
3135
+ const hrs = t.estimateHours ? ` ${color.dim(`${t.estimateHours}h`)}` : "";
3136
+ lines.push(` ${type} ${prio} ${t.title}${deps}${who}${hrs}`);
3137
+ }
2927
3138
  }
2928
- return result;
3139
+ return lines.join("\n");
2929
3140
  }
2930
- function getModelFamilyRatio(calibrationKey) {
2931
- const lower = calibrationKey.toLowerCase();
2932
- for (const [family, ratio] of Object.entries(MODEL_FAMILY_RATIO)) {
2933
- if (lower.includes(family)) return ratio / 3.5;
2934
- }
2935
- return null;
3141
+
3142
+ // src/utils/todos-format.ts
3143
+ function formatTodosList(todos) {
3144
+ if (todos.length === 0) return "No todos.";
3145
+ const lines = [];
3146
+ const done = todos.filter((t) => t.status === "completed").length;
3147
+ lines.push(color.dim(`Todos (${done}/${todos.length} done):`));
3148
+ todos.forEach((t, i) => {
3149
+ const mark = t.status === "completed" ? color.green("[x]") : t.status === "in_progress" ? color.yellow("[~]") : color.dim("[ ]");
3150
+ const text = t.status === "in_progress" && t.activeForm ? t.activeForm : t.content;
3151
+ const label = t.status === "completed" ? color.dim(text) : text;
3152
+ lines.push(` ${color.dim(String(i + 1).padStart(2))}. ${mark} ${label}`);
3153
+ });
3154
+ return lines.join("\n");
2936
3155
  }
2937
- function resetCalibration(calibrationKey) {
2938
- if (calibrationKey === void 0) {
2939
- _cals.clear();
2940
- return;
2941
- }
2942
- _cals.delete(calibrationKey);
3156
+ function hasOpenTodos(todos) {
3157
+ if (!Array.isArray(todos) || todos.length === 0) return false;
3158
+ return todos.some((t) => t.status === "pending" || t.status === "in_progress");
2943
3159
  }
2944
3160
 
2945
3161
  // src/utils/tool-description-mode.ts
@@ -3821,7 +4037,7 @@ function isUlid(value) {
3821
4037
  }
3822
4038
 
3823
4039
  // src/utils/wstack-paths.ts
3824
- import { createHash } from "node:crypto";
4040
+ import { createHash as createHash2 } from "node:crypto";
3825
4041
  import * as fs4 from "node:fs";
3826
4042
  import * as os from "node:os";
3827
4043
  import * as path5 from "node:path";
@@ -3844,12 +4060,12 @@ function canonicalProjectRoot(absRoot) {
3844
4060
  }
3845
4061
  }
3846
4062
  function projectHash(absRoot) {
3847
- return createHash("sha256").update(canonicalProjectRoot(absRoot)).digest("hex").slice(0, 12);
4063
+ return createHash2("sha256").update(canonicalProjectRoot(absRoot)).digest("hex").slice(0, 12);
3848
4064
  }
3849
4065
  function projectSlug(absRoot) {
3850
4066
  const identityRoot = canonicalProjectRoot(absRoot);
3851
4067
  const base = slugify2(path5.basename(identityRoot));
3852
- const hash = createHash("sha256").update(identityRoot).digest("hex").slice(0, 6);
4068
+ const hash = createHash2("sha256").update(identityRoot).digest("hex").slice(0, 6);
3853
4069
  return `${base}-${hash}`;
3854
4070
  }
3855
4071
  function slugify2(name) {
@@ -3960,6 +4176,7 @@ export {
3960
4176
  buildTitleSequence,
3961
4177
  buildUserContentBlocks,
3962
4178
  canonicalProjectRoot,
4179
+ checkConnectivity,
3963
4180
  coerceAgainstSchema,
3964
4181
  color,
3965
4182
  compactSchemaDescriptions,
@@ -3974,6 +4191,7 @@ export {
3974
4191
  createContextEvidenceState,
3975
4192
  createToolOutputSerializer,
3976
4193
  deepMerge,
4194
+ deriveCachePrefixKey,
3977
4195
  detectNewlineStyle,
3978
4196
  detectTerminal,
3979
4197
  ensureDir,
@@ -3994,6 +4212,7 @@ export {
3994
4212
  formatTodosList,
3995
4213
  getCalibrationState,
3996
4214
  getChildEnvGitIdentity,
4215
+ getContextBreakdown,
3997
4216
  getJsonPath,
3998
4217
  getToolDescriptionMode,
3999
4218
  getToolResultRenderMode,
@@ -4029,6 +4248,7 @@ export {
4029
4248
  repairToolUseAdjacency,
4030
4249
  repeatedReadPressure,
4031
4250
  resetCalibration,
4251
+ resetConnectivityCache,
4032
4252
  resolveToolDescriptionMode,
4033
4253
  resolveToolResultRenderMode,
4034
4254
  resolveWstackPaths,