@wrongstack/core 0.265.1 → 0.268.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/{agent-bridge-DrkBxszZ.d.ts → agent-bridge-UhojbpWx.d.ts} +1 -1
  2. package/dist/{agent-subagent-runner-DM2pP-B6.d.ts → agent-subagent-runner-Bvtf1o9K.d.ts} +25 -7
  3. package/dist/{brain-BXd_61kQ.d.ts → brain-69wzMKp1.d.ts} +73 -1
  4. package/dist/{compactor-B8pOf45Y.d.ts → compactor-CBQAJoDc.d.ts} +19 -1
  5. package/dist/{config-BMCj_XDs.d.ts → config-VKfOZ-6X.d.ts} +122 -3
  6. package/dist/{context-MRk5PhNv.d.ts → context-C0U8B9NF.d.ts} +88 -1
  7. package/dist/coordination/index.d.ts +57 -161
  8. package/dist/coordination/index.js +471 -177
  9. package/dist/coordination/index.js.map +1 -1
  10. package/dist/defaults/index.d.ts +26 -25
  11. package/dist/defaults/index.js +1818 -844
  12. package/dist/defaults/index.js.map +1 -1
  13. package/dist/execution/index.d.ts +72 -16
  14. package/dist/execution/index.js +1270 -265
  15. package/dist/execution/index.js.map +1 -1
  16. package/dist/execution/prompt-enhancer.d.ts +1 -1
  17. package/dist/extension/index.d.ts +7 -6
  18. package/dist/global-mailbox-KByEFFBa.d.ts +663 -0
  19. package/dist/{goal-preamble-DvHDSKSe.d.ts → goal-preamble-CrYjmdw4.d.ts} +28 -11
  20. package/dist/{goal-store-DtLMySNb.d.ts → goal-store-Y_zdLZ3q.d.ts} +1 -1
  21. package/dist/hq/index.d.ts +195 -0
  22. package/dist/hq/index.js +1884 -0
  23. package/dist/hq/index.js.map +1 -0
  24. package/dist/index-BfaS-f_m.d.ts +82 -0
  25. package/dist/{index-B-ch8K9C.d.ts → index-CtQnmkaS.d.ts} +8 -8
  26. package/dist/{index-CEDeNodM.d.ts → index-gCv830d7.d.ts} +5 -5
  27. package/dist/index.d.ts +124 -47
  28. package/dist/index.js +5600 -2662
  29. package/dist/index.js.map +1 -1
  30. package/dist/infrastructure/index.d.ts +6 -6
  31. package/dist/infrastructure/index.js +117 -19
  32. package/dist/infrastructure/index.js.map +1 -1
  33. package/dist/kernel/index.d.ts +10 -9
  34. package/dist/kernel/index.js.map +1 -1
  35. package/dist/{pipeline-DPDxH_7m.d.ts → mailbox-types-Ct2hJq0P.d.ts} +1 -244
  36. package/dist/{mcp-servers-2x4w6Jn9.d.ts → mcp-servers-HT3Fi7Bl.d.ts} +10 -4
  37. package/dist/models/index.d.ts +5 -5
  38. package/dist/models/index.js +33 -3
  39. package/dist/models/index.js.map +1 -1
  40. package/dist/{models-registry-DmJlKuNp.d.ts → models-registry-Bvcl3Vaa.d.ts} +1 -1
  41. package/dist/{multi-agent-coordinator-DyCkCZnU.d.ts → multi-agent-coordinator-BACjsmkC.d.ts} +1 -1
  42. package/dist/{null-fleet-bus-CG9QY2aP.d.ts → null-fleet-bus-DA7fvhUg.d.ts} +14 -9
  43. package/dist/observability/index.d.ts +2 -2
  44. package/dist/{parallel-eternal-engine-Jw9uhEoT.d.ts → parallel-eternal-engine-Ci71gYu_.d.ts} +11 -15
  45. package/dist/{path-resolver-Dy2ej-gE.d.ts → path-resolver-O1IJnmKE.d.ts} +4 -3
  46. package/dist/{permission-B9SB45lp.d.ts → permission-Bd-57Lbl.d.ts} +1 -1
  47. package/dist/{permission-policy-CkjSXabK.d.ts → permission-policy-uNXC6Kge.d.ts} +2 -3
  48. package/dist/pipeline-BDNvENyV.d.ts +245 -0
  49. package/dist/{plan-templates-CzD9GnAU.d.ts → plan-templates-EMsalEtN.d.ts} +5 -5
  50. package/dist/{llm-selector-C0tfTCUe.d.ts → provider-model-resolve-CEb9x886.d.ts} +40 -3
  51. package/dist/{provider-runner-DMa70ODu.d.ts → provider-runner-DWJbpo70.d.ts} +3 -3
  52. package/dist/{retry-policy-CN0khdlj.d.ts → retry-policy-C3s_lvdK.d.ts} +1 -1
  53. package/dist/sdd/index.d.ts +9 -8
  54. package/dist/sdd/index.js +44 -14
  55. package/dist/sdd/index.js.map +1 -1
  56. package/dist/{secret-vault-B2yw84VT.d.ts → secret-vault-Cgduf5xL.d.ts} +2 -2
  57. package/dist/security/index.d.ts +5 -67
  58. package/dist/security/index.js +129 -99
  59. package/dist/security/index.js.map +1 -1
  60. package/dist/{selector-CzHh_igB.d.ts → selector-47LBnBVk.d.ts} +1 -1
  61. package/dist/{session-event-bridge-BUI6Jf-4.d.ts → session-event-bridge-Cw7oqmW2.d.ts} +1 -1
  62. package/dist/{session-reader-CMgdMSRP.d.ts → session-reader-DD4v2Obw.d.ts} +1 -1
  63. package/dist/storage/index.d.ts +14 -12
  64. package/dist/storage/index.js +144 -120
  65. package/dist/storage/index.js.map +1 -1
  66. package/dist/tools/index.d.ts +4 -2
  67. package/dist/tools/index.js +166 -31
  68. package/dist/tools/index.js.map +1 -1
  69. package/dist/types/index.d.ts +20 -19
  70. package/dist/types/index.js +1358 -476
  71. package/dist/types/index.js.map +1 -1
  72. package/dist/utils/index.d.ts +472 -405
  73. package/dist/utils/index.js +2321 -1193
  74. package/dist/utils/index.js.map +1 -1
  75. package/package.json +5 -1
@@ -1,16 +1,23 @@
1
1
  import { randomBytes, createHash } from 'crypto';
2
2
  import * as fs from 'fs/promises';
3
- import * as path2 from 'path';
3
+ import * as path3 from 'path';
4
4
  import { isAbsolute, resolve } from 'path';
5
- import * as os from 'os';
6
5
  import * as dns from 'dns/promises';
7
6
  import * as net from 'net';
7
+ import * as os from 'os';
8
8
 
9
- // src/utils/atomic-write.ts
9
+ // src/utils/assert-never.ts
10
+ function assertNever(x, message) {
11
+ const err = new Error(
12
+ message ?? `Unhandled case: ${JSON.stringify(x)}`
13
+ );
14
+ err.name = "AssertNeverError";
15
+ throw err;
16
+ }
10
17
  async function atomicWrite(targetPath, content, opts = {}) {
11
- const dir = path2.dirname(targetPath);
18
+ const dir = path3.dirname(targetPath);
12
19
  await fs.mkdir(dir, { recursive: true });
13
- const tmp = path2.join(dir, `.${path2.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
20
+ const tmp = path3.join(dir, `.${path3.basename(targetPath)}.${randomBytes(6).toString("hex")}.tmp`);
14
21
  try {
15
22
  if (typeof content === "string") {
16
23
  await fs.writeFile(tmp, content, { flag: "wx", encoding: opts.encoding ?? "utf8" });
@@ -49,9 +56,9 @@ async function ensureDir(dir) {
49
56
  await fs.mkdir(dir, { recursive: true });
50
57
  }
51
58
  async function withFileLock(targetPath, fn, opts = {}) {
52
- const dir = path2.dirname(targetPath);
59
+ const dir = path3.dirname(targetPath);
53
60
  await fs.mkdir(dir, { recursive: true });
54
- const lockPath = path2.join(dir, `.${path2.basename(targetPath)}.lock`);
61
+ const lockPath = path3.join(dir, `.${path3.basename(targetPath)}.lock`);
55
62
  const timeoutMs = opts.timeoutMs ?? 5e3;
56
63
  const staleMs = opts.staleMs ?? 3e4;
57
64
  const started = Date.now();
@@ -80,7 +87,7 @@ async function withFileLock(targetPath, fn, opts = {}) {
80
87
  if (Date.now() - started >= timeoutMs) {
81
88
  throw new Error(`Timed out waiting for file lock: ${targetPath}`);
82
89
  }
83
- await new Promise((resolve3) => setTimeout(resolve3, 25));
90
+ await new Promise((resolve4) => setTimeout(resolve4, 25));
84
91
  }
85
92
  }
86
93
  try {
@@ -114,150 +121,105 @@ async function renameWithRetry(from, to) {
114
121
  if (!code || !TRANSIENT_RENAME_CODES.has(code) || i === delays.length) {
115
122
  throw err;
116
123
  }
117
- await new Promise((resolve3) => setTimeout(resolve3, delays[i]));
124
+ await new Promise((resolve4) => setTimeout(resolve4, delays[i]));
118
125
  }
119
126
  }
120
127
  throw lastErr;
121
128
  }
122
129
 
123
- // src/utils/error.ts
124
- function toErrorMessage(err) {
125
- return err instanceof Error ? err.message : String(err);
126
- }
127
-
128
- // src/utils/safe-json.ts
129
- function safeParse(input, maxBytes = 5e6) {
130
- if (input.length > maxBytes) {
131
- return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };
132
- }
133
- try {
134
- return { ok: true, value: JSON.parse(input) };
135
- } catch (err) {
136
- return {
137
- ok: false,
138
- error: toErrorMessage(err)
139
- };
130
+ // src/utils/child-env.ts
131
+ var ALLOWED_KEYS = /* @__PURE__ */ new Set([
132
+ "PATH",
133
+ "HOME",
134
+ "USER",
135
+ "USERNAME",
136
+ "LOGNAME",
137
+ "SHELL",
138
+ "LANG",
139
+ "LC_ALL",
140
+ "LC_CTYPE",
141
+ "TERM",
142
+ "TZ",
143
+ "TMPDIR",
144
+ "TEMP",
145
+ "TMP",
146
+ "PWD",
147
+ "OLDPWD",
148
+ "COMSPEC",
149
+ "SYSTEMROOT",
150
+ "SYSTEMDRIVE",
151
+ "WINDIR",
152
+ "PROGRAMFILES",
153
+ "PROGRAMFILES(X86)",
154
+ "PROGRAMDATA",
155
+ "APPDATA",
156
+ "LOCALAPPDATA",
157
+ "USERPROFILE",
158
+ "PUBLIC",
159
+ "PATHEXT"
160
+ ]);
161
+ var SECRET_NAME_PARTS = [
162
+ "TOKEN",
163
+ "SECRET",
164
+ "PASSWORD",
165
+ "PASSWD",
166
+ "AUTH",
167
+ "CRED",
168
+ "BEARER",
169
+ "COOKIE",
170
+ "PRIVATE"
171
+ ];
172
+ function looksSecret(name) {
173
+ const upper = name.toUpperCase();
174
+ for (const p of SECRET_NAME_PARTS) {
175
+ if (upper.includes(p)) return true;
140
176
  }
141
- }
142
- function safeStringify(value, pretty = false) {
143
- const seen = /* @__PURE__ */ new WeakSet();
144
- const replacer = (_k, v) => {
145
- if (typeof v === "bigint") return v.toString();
146
- if (v instanceof Error) {
147
- return { name: v.name, message: v.message, stack: v.stack };
148
- }
149
- if (typeof v === "object" && v !== null) {
150
- if (seen.has(v)) return "[Circular]";
151
- seen.add(v);
152
- }
153
- return v;
154
- };
155
- try {
156
- return JSON.stringify(value, replacer, pretty ? 2 : void 0) ?? "null";
157
- } catch (err) {
158
- return JSON.stringify({
159
- __serialization_error: toErrorMessage(err)
160
- });
177
+ if (/(?:^|_)KEY(?:$|_|S$)/i.test(upper)) return true;
178
+ if (/API[_-]?KEY/i.test(upper)) return true;
179
+ if (/ACCESS[_-]?KEY/i.test(upper)) return true;
180
+ if (/SESSION[_-]?ID/i.test(upper) === false && /SESSION/i.test(upper)) {
181
+ return true;
161
182
  }
183
+ return false;
162
184
  }
163
- function sanitizeJsonString(s) {
164
- let out = s.trim();
165
- out = stripSingleLineComments(out);
166
- out = out.replace(/,(\s*[}\]])/g, "$1");
167
- out = escapeControlCharsInStrings(out);
168
- try {
169
- JSON.parse(out);
170
- return out;
171
- } catch {
172
- return null;
185
+ function buildChildEnv(optsOrSessionId) {
186
+ const opts = typeof optsOrSessionId === "string" ? { sessionId: optsOrSessionId } : optsOrSessionId ?? {};
187
+ const hasOwn = Object.hasOwn(process.env, "WRONGSTACK_CHILD_ENV_PASSTHROUGH");
188
+ const legacyHasOwn = Object.hasOwn(process.env, "WRONGSTACK_BASH_ENV_PASSTHROUGH");
189
+ const passthrough = hasOwn && process.env["WRONGSTACK_CHILD_ENV_PASSTHROUGH"] === "1" || legacyHasOwn && process.env["WRONGSTACK_BASH_ENV_PASSTHROUGH"] === "1";
190
+ if (passthrough && !process.env["CI"]) {
191
+ console.warn(
192
+ "[agent] WARNING: WRONGSTACK_*_ENV_PASSTHROUGH=1 is active \u2014\n all parent env vars (including API keys) forwarded to child processes.\n Do not use on shared or multi-tenant systems."
193
+ );
173
194
  }
174
- }
175
- function escapeControlCharsInStrings(s) {
176
- let inString = false;
177
- let out = "";
178
- for (let i = 0; i < s.length; i++) {
179
- const c = s.charAt(i);
180
- if (c === '"' && (i === 0 || s[i - 1] !== "\\")) {
181
- inString = !inString;
182
- out += c;
195
+ const out = {};
196
+ const nodeEnvDefaulted = process.env["WRONGSTACK_NODE_ENV_DEFAULTED"] === "1";
197
+ for (const [k, v] of Object.entries(process.env)) {
198
+ if (v === void 0) continue;
199
+ if (nodeEnvDefaulted && (k === "NODE_ENV" || k === "WRONGSTACK_NODE_ENV_DEFAULTED")) continue;
200
+ if (passthrough) {
201
+ out[k] = v;
183
202
  continue;
184
203
  }
185
- const code = c.charCodeAt(0);
186
- if (inString && code < 32) {
187
- switch (c) {
188
- case "\n":
189
- out += "\\n";
190
- break;
191
- case "\r":
192
- out += "\\r";
193
- break;
194
- case " ":
195
- out += "\\t";
196
- break;
197
- case "\b":
198
- out += "\\b";
199
- break;
200
- case "\f":
201
- out += "\\f";
202
- break;
203
- default:
204
- out += `\\u${code.toString(16).padStart(4, "0")}`;
205
- }
204
+ const upper = k.toUpperCase();
205
+ if (ALLOWED_KEYS.has(upper)) {
206
+ out[k] = v;
206
207
  continue;
207
208
  }
208
- out += c;
209
- }
210
- return out;
211
- }
212
- function stripSingleLineComments(s) {
213
- let inString = false;
214
- const chars = [];
215
- let i = 0;
216
- while (i < s.length) {
217
- const c = s.charAt(i);
218
- if (c === '"' && (i === 0 || s.charAt(i - 1) !== "\\")) {
219
- inString = !inString;
220
- chars.push(c);
221
- } else if (c === "/" && s.charAt(i + 1) === "/" && !inString) {
222
- while (i < s.length && s.charAt(i) !== "\n") i++;
223
- } else {
224
- chars.push(c);
209
+ if (looksSecret(upper)) continue;
210
+ if (upper.startsWith("NODE_") || upper.startsWith("NPM_") || upper.startsWith("PNPM_") || upper.startsWith("YARN_") || upper.startsWith("GIT_") || upper.startsWith("CI") || upper.startsWith("XDG_") || // Our own non-secret knobs (WRONGSTACK_HOME, WRONGSTACK_SESSION_ID, …).
211
+ // Secrets never live in WRONGSTACK_* env vars (they're in the encrypted
212
+ // vault). Forwarding keeps child wstack processes — e.g. ones spawned
213
+ // by the test suite — inside the same redirected global root.
214
+ upper.startsWith("WRONGSTACK_") || upper === "EDITOR" || upper === "VISUAL" || upper === "PAGER") {
215
+ out[k] = v;
225
216
  }
226
- i++;
227
217
  }
228
- return chars.join("");
229
- }
230
-
231
- // src/utils/newline-normalize.ts
232
- function detectNewlineStyle(text) {
233
- let lf = 0;
234
- let crlf = 0;
235
- let cr = 0;
236
- for (let i = 0; i < text.length; i++) {
237
- const c = text.charCodeAt(i);
238
- if (c === 13) {
239
- if (text.charCodeAt(i + 1) === 10) {
240
- crlf++;
241
- i++;
242
- } else {
243
- cr++;
244
- }
245
- } else if (c === 10) {
246
- lf++;
247
- }
218
+ if (opts.extra) {
219
+ Object.assign(out, opts.extra);
248
220
  }
249
- if (crlf > lf && crlf > cr) return "crlf";
250
- if (cr > lf && cr > crlf) return "cr";
251
- return "lf";
252
- }
253
- function toStyle(text, style) {
254
- const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
255
- if (style === "lf") return normalized;
256
- if (style === "crlf") return normalized.replace(/\n/g, "\r\n");
257
- return normalized.replace(/\n/g, "\r");
258
- }
259
- function normalizeToLf(text) {
260
- return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
221
+ if (opts.sessionId) out["WRONGSTACK_SESSION_ID"] = opts.sessionId;
222
+ return out;
261
223
  }
262
224
 
263
225
  // src/utils/term.ts
@@ -360,255 +322,526 @@ function stripAnsi(s) {
360
322
  ""
361
323
  );
362
324
  }
363
-
364
- // src/utils/todos-format.ts
365
- function formatTodosList(todos) {
366
- if (todos.length === 0) return "No todos.";
367
- const lines = [];
368
- const done = todos.filter((t) => t.status === "completed").length;
369
- lines.push(color.dim(`Todos (${done}/${todos.length} done):`));
370
- todos.forEach((t, i) => {
371
- const mark = t.status === "completed" ? color.green("[x]") : t.status === "in_progress" ? color.yellow("[~]") : color.dim("[ ]");
372
- const text = t.status === "in_progress" && t.activeForm ? t.activeForm : t.content;
373
- const label = t.status === "completed" ? color.dim(text) : text;
374
- lines.push(` ${color.dim(String(i + 1).padStart(2))}. ${mark} ${label}`);
325
+ async function readJsonObjectFile(filePath) {
326
+ try {
327
+ const parsed = JSON.parse(await fs.readFile(filePath, "utf8"));
328
+ return isJsonObject(parsed) ? parsed : {};
329
+ } catch {
330
+ return {};
331
+ }
332
+ }
333
+ async function jsonObjectFileExists(filePath) {
334
+ try {
335
+ await fs.access(filePath);
336
+ return true;
337
+ } catch {
338
+ return false;
339
+ }
340
+ }
341
+ async function writeJsonObjectFile(filePath, value) {
342
+ await atomicWrite(filePath, JSON.stringify(value, null, 2), { mode: 384 });
343
+ }
344
+ async function updateJsonObjectFile(filePath, mutator) {
345
+ const config = await readJsonObjectFile(filePath);
346
+ const maybeNext = await mutator(config);
347
+ const next = maybeNext && isJsonObject(maybeNext) ? maybeNext : config;
348
+ await writeJsonObjectFile(filePath, next);
349
+ return next;
350
+ }
351
+ function getJsonPath(root, path4) {
352
+ let current = root;
353
+ for (const segment of path4) {
354
+ if (typeof segment === "number") {
355
+ if (!Array.isArray(current)) return void 0;
356
+ current = current[segment];
357
+ continue;
358
+ }
359
+ if (!isJsonObject(current)) return void 0;
360
+ current = current[segment];
361
+ }
362
+ return current;
363
+ }
364
+ function setJsonPath(root, path4, value) {
365
+ if (path4.length === 0) {
366
+ if (!isJsonObject(value)) throw new Error("Root config value must be an object");
367
+ return value;
368
+ }
369
+ const parent = ensureJsonParent(root, path4);
370
+ const leaf = lastPathSegment(path4);
371
+ if (typeof leaf === "number") {
372
+ if (!Array.isArray(parent)) throw new Error(`Cannot set numeric segment ${leaf} on non-array parent`);
373
+ parent[leaf] = value;
374
+ } else {
375
+ if (!isJsonObject(parent)) throw new Error(`Cannot set property ${leaf} on non-object parent`);
376
+ parent[leaf] = value;
377
+ }
378
+ return root;
379
+ }
380
+ function removeJsonPath(root, path4) {
381
+ if (path4.length === 0) return false;
382
+ const parent = getJsonPath(root, path4.slice(0, -1));
383
+ const leaf = lastPathSegment(path4);
384
+ if (typeof leaf === "number") {
385
+ if (!Array.isArray(parent) || leaf < 0 || leaf >= parent.length) return false;
386
+ parent.splice(leaf, 1);
387
+ return true;
388
+ }
389
+ if (!isJsonObject(parent) || !(leaf in parent)) return false;
390
+ delete parent[leaf];
391
+ return true;
392
+ }
393
+ async function setJsonPathInFile(filePath, path4, value) {
394
+ return updateJsonObjectFile(filePath, (config) => setJsonPath(config, path4, value));
395
+ }
396
+ async function removeJsonPathInFile(filePath, path4) {
397
+ return updateJsonObjectFile(filePath, (config) => {
398
+ removeJsonPath(config, path4);
375
399
  });
376
- return lines.join("\n");
377
400
  }
378
-
379
- // src/utils/task-format.ts
380
- function computeTaskItemProgress(tasks) {
381
- let completed = 0;
382
- let pending = 0;
383
- let inProgress = 0;
384
- let blocked = 0;
385
- let failed = 0;
386
- let review = 0;
387
- let estimatedHours = 0;
388
- const actualHours = 0;
389
- for (const t of tasks) {
390
- switch (t.status) {
391
- case "completed":
392
- completed++;
393
- break;
394
- case "pending":
395
- pending++;
396
- break;
397
- case "in_progress":
398
- inProgress++;
399
- break;
400
- case "blocked":
401
- blocked++;
402
- break;
403
- case "failed":
404
- failed++;
405
- break;
406
- case "review":
407
- review++;
408
- break;
401
+ function isJsonObject(value) {
402
+ return typeof value === "object" && value !== null && !Array.isArray(value);
403
+ }
404
+ function lastPathSegment(path4) {
405
+ const segment = path4[path4.length - 1];
406
+ if (segment === void 0) throw new Error("Invalid empty JSON path");
407
+ return segment;
408
+ }
409
+ function ensureJsonParent(root, path4) {
410
+ let current = root;
411
+ for (let i = 0; i < path4.length - 1; i += 1) {
412
+ const segment = path4[i];
413
+ const nextSegment = path4[i + 1];
414
+ if (segment === void 0) throw new Error("Invalid empty JSON path segment");
415
+ const nextContainer = typeof nextSegment === "number" ? [] : {};
416
+ if (typeof segment === "number") {
417
+ if (!Array.isArray(current)) throw new Error(`Cannot traverse numeric segment ${segment} on non-array parent`);
418
+ if (!isJsonObject(current[segment]) && !Array.isArray(current[segment])) current[segment] = nextContainer;
419
+ current = current[segment];
420
+ } else {
421
+ if (!isJsonObject(current)) throw new Error(`Cannot traverse property ${segment} on non-object parent`);
422
+ if (!isJsonObject(current[segment]) && !Array.isArray(current[segment])) current[segment] = nextContainer;
423
+ current = current[segment];
409
424
  }
410
- estimatedHours += t.estimateHours ?? 0;
411
425
  }
426
+ return current;
427
+ }
428
+ var MAX_TOOL_CALLS = 80;
429
+ var MAX_FACTS = 40;
430
+ var MAX_ERRORS = 20;
431
+ var MAX_DIGEST_CHARS = 4e3;
432
+ var WRITE_TOOLS = /* @__PURE__ */ new Set(["edit", "write", "replace", "patch"]);
433
+ var READ_TOOLS = /* @__PURE__ */ new Set(["read", "grep", "glob", "ls", "tree"]);
434
+ function createContextEvidenceState() {
412
435
  return {
413
- total: tasks.length,
414
- pending,
415
- inProgress,
416
- blocked,
417
- failed,
418
- review,
419
- completed,
420
- percentComplete: tasks.length > 0 ? Math.round(completed / tasks.length * 100) : 0,
421
- estimatedHours,
422
- actualHours
436
+ sessionGoals: [],
437
+ implicitFacts: [],
438
+ activeErrors: [],
439
+ toolCalls: [],
440
+ fileGraph: {},
441
+ repeatedReads: [],
442
+ updatedAt: Date.now()
423
443
  };
424
444
  }
425
- var STATUS_ICON = {
426
- pending: "\u25CB",
427
- in_progress: "\u25D0",
428
- blocked: "\u2298",
429
- failed: "\u2717",
430
- review: "\u25D1",
431
- completed: "\u25CF"
432
- };
433
- var PRIORITY_ICON = {
434
- critical: "\u{1F534}",
435
- high: "\u{1F7E0}",
436
- medium: "\u{1F7E1}",
437
- low: "\u{1F7E2}"
438
- };
439
- var TYPE_ICON = {
440
- feature: "\u26A1",
441
- bugfix: "\u{1F41B}",
442
- refactor: "\u267B\uFE0F",
443
- docs: "\u{1F4DD}",
444
- test: "\u{1F9EA}",
445
- chore: "\u{1F527}"
446
- };
447
- function formatTaskProgress(tasks) {
448
- const p = computeTaskItemProgress(tasks);
449
- if (p.total === 0) return "No tasks.";
450
- const barWidth = 24;
451
- const filled = Math.round(p.percentComplete / 100 * barWidth);
452
- const empty = barWidth - filled;
453
- const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
454
- return [
455
- `${color.bold("Tasks")} [${bar}] ${p.percentComplete}%`,
456
- ` ${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`,
457
- p.estimatedHours > 0 ? ` ${color.dim(`est. ${p.estimatedHours}h`)}` : ""
458
- ].filter(Boolean).join("\n");
459
- }
460
- function formatTaskList(tasks) {
461
- if (tasks.length === 0) return "No tasks.";
462
- const order = ["in_progress", "blocked", "review", "pending", "failed", "completed"];
463
- const groups = /* @__PURE__ */ new Map();
464
- for (const t of tasks) {
465
- const list = groups.get(t.status) ?? [];
466
- list.push(t);
467
- groups.set(t.status, list);
445
+ function recordUserIntentEvidence(ctx, text) {
446
+ const intent = normalizeWhitespace(text).slice(0, 700);
447
+ if (!intent) return;
448
+ const state = ensureEvidence(ctx);
449
+ state.currentIntent = { text: intent, updatedAt: Date.now() };
450
+ if (state.sessionGoals.length === 0 || isGoalish(intent)) {
451
+ pushUniqueBounded(state.sessionGoals, intent, 8);
452
+ }
453
+ state.updatedAt = Date.now();
454
+ }
455
+ function recordToolOutputEvidence(ctx, input) {
456
+ const state = ensureEvidence(ctx);
457
+ const files = extractFiles(ctx, input.toolName, input.input, input.content);
458
+ const symbols = extractSymbols(input.content, input.input);
459
+ const commands = extractCommands(input.toolName, input.input);
460
+ const errors = extractErrors(input.content);
461
+ const summary = summarizeToolOutput(input.toolName, input.input, input.content, {
462
+ files,
463
+ errors,
464
+ ok: input.ok
465
+ });
466
+ const metadata = {
467
+ toolUseId: input.toolUseId,
468
+ toolName: input.toolName,
469
+ ok: input.ok,
470
+ inputSummary: summarizeInput(input.input),
471
+ summary,
472
+ files,
473
+ symbols,
474
+ commands,
475
+ errors,
476
+ status: "seen",
477
+ referenceCount: 0,
478
+ seenAt: Date.now(),
479
+ outputBytes: input.outputBytes,
480
+ outputTokens: input.outputTokens,
481
+ outputLines: input.outputLines
482
+ };
483
+ state.toolCalls.push(metadata);
484
+ if (state.toolCalls.length > MAX_TOOL_CALLS) {
485
+ state.toolCalls.splice(0, state.toolCalls.length - MAX_TOOL_CALLS);
486
+ }
487
+ updateFileGraph(state, metadata);
488
+ updateRepeatedReadSignals(state, metadata);
489
+ if (errors.length > 0) {
490
+ for (const err of errors) pushUniqueBounded(state.activeErrors, err, MAX_ERRORS);
491
+ }
492
+ const fact = implicitFactFor(metadata);
493
+ if (fact) pushUniqueBounded(state.implicitFacts, fact, MAX_FACTS);
494
+ state.updatedAt = Date.now();
495
+ return metadata;
496
+ }
497
+ function markAssistantReferencedEvidence(ctx, text) {
498
+ const state = ensureEvidence(ctx);
499
+ const haystack = text.toLowerCase();
500
+ if (!haystack.trim()) return;
501
+ for (const tool of state.toolCalls) {
502
+ if (!metadataReferencedByText(tool, haystack)) continue;
503
+ tool.status = "referenced";
504
+ tool.referenceCount++;
505
+ tool.referencedAt = Date.now();
506
+ for (const file of tool.files) {
507
+ const node = state.fileGraph[file];
508
+ if (node) node.referenced = true;
509
+ }
468
510
  }
511
+ state.updatedAt = Date.now();
512
+ }
513
+ function buildContextEvidenceDigest(ctx) {
514
+ const state = ensureEvidence(ctx);
469
515
  const lines = [];
470
- lines.push(color.dim(`Tasks (${tasks.length} total):`));
471
- for (const status of order) {
472
- const group = groups.get(status);
473
- if (!group || group.length === 0) continue;
474
- const icon = STATUS_ICON[status];
475
- lines.push(` ${icon} ${status.toUpperCase()} (${group.length})`);
476
- for (const t of group) {
477
- const prio = PRIORITY_ICON[t.priority];
478
- const type = TYPE_ICON[t.type];
479
- const deps = t.dependsOn && t.dependsOn.length > 0 ? ` ${color.dim("\u2190")} ${color.dim(t.dependsOn.map((d) => d.slice(0, 8)).join(", "))}` : "";
480
- const who = t.assignee ? ` ${color.dim(`@${t.assignee}`)}` : "";
481
- const hrs = t.estimateHours ? ` ${color.dim(`${t.estimateHours}h`)}` : "";
482
- lines.push(` ${type} ${prio} ${t.title}${deps}${who}${hrs}`);
516
+ if (state.currentIntent?.text) {
517
+ lines.push(`intent: ${state.currentIntent.text}`);
518
+ }
519
+ const goals = state.sessionGoals.slice(-3);
520
+ if (goals.length > 0) {
521
+ lines.push("session_goals:");
522
+ for (const goal of goals) lines.push(`- ${goal}`);
523
+ }
524
+ const activeErrors = state.activeErrors.slice(-5);
525
+ if (activeErrors.length > 0) {
526
+ lines.push("active_errors:");
527
+ for (const err of activeErrors) lines.push(`- ${err}`);
528
+ }
529
+ const files = Object.values(state.fileGraph).sort((a, b) => b.writes - a.writes || b.reads - a.reads || a.path.localeCompare(b.path)).slice(0, 12);
530
+ if (files.length > 0) {
531
+ lines.push("dependency_graph:");
532
+ for (const file of files) {
533
+ const actions = [
534
+ file.reads > 0 ? `read ${file.reads}x` : "",
535
+ file.writes > 0 ? `write ${file.writes}x` : ""
536
+ ].filter(Boolean).join(", ");
537
+ const refs = file.referenced ? "; referenced by assistant" : "";
538
+ const via = file.lastToolUseId ? `; last via ${file.lastToolUseId}` : "";
539
+ lines.push(`- ${file.path} (${actions || "seen"}${refs}${via})`);
483
540
  }
484
541
  }
485
- return lines.join("\n");
542
+ const referenced = state.toolCalls.filter((tool) => tool.status === "referenced").slice(-10);
543
+ const recentSeen = state.toolCalls.filter((tool) => tool.status === "seen").slice(-5);
544
+ const trail = [...referenced, ...recentSeen];
545
+ if (trail.length > 0) {
546
+ lines.push("tool_trail:");
547
+ for (const tool of trail) {
548
+ const size = tool.outputTokens ? `; ~${tool.outputTokens} tokens` : "";
549
+ const filesText = tool.files.length > 0 ? `; files=${tool.files.slice(0, 4).join(", ")}` : "";
550
+ const symbolsText = tool.symbols.length > 0 ? `; symbols=${tool.symbols.slice(0, 4).join(", ")}` : "";
551
+ lines.push(
552
+ `- ${tool.toolUseId} ${tool.toolName} ${tool.status}: ${tool.summary}${filesText}${symbolsText}${size}`
553
+ );
554
+ }
555
+ }
556
+ const facts = state.implicitFacts.slice(-8);
557
+ if (facts.length > 0) {
558
+ lines.push("implicit_facts:");
559
+ for (const fact of facts) lines.push(`- ${fact}`);
560
+ }
561
+ const digest = lines.join("\n");
562
+ if (digest.length <= MAX_DIGEST_CHARS) return digest;
563
+ return `${digest.slice(0, MAX_DIGEST_CHARS)}... [+${digest.length - MAX_DIGEST_CHARS} chars]`;
486
564
  }
487
-
488
- // src/utils/string.ts
489
- function truncate(s, max) {
490
- return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
565
+ function repeatedReadPressure(ctx) {
566
+ return ensureEvidence(ctx).repeatedReads.reduce((max, item) => Math.max(max, item.count), 0);
491
567
  }
492
-
493
- // src/utils/expect-defined.ts
494
- function expectDefined(value, label) {
495
- if (value === null || value === void 0) {
496
- const err = new Error(label ? `Expected ${label} to be defined` : "Expected value to be defined");
497
- err.name = "ExpectDefinedError";
498
- throw err;
568
+ function ensureEvidence(ctx) {
569
+ if (!ctx.contextEvidence) {
570
+ ctx.contextEvidence = createContextEvidenceState();
499
571
  }
500
- return value;
572
+ return ctx.contextEvidence;
501
573
  }
502
-
503
- // src/utils/glob-match.ts
504
- function escapeRegex(s) {
505
- return s.replace(/[.+^${}()|\\]/g, "\\$&");
574
+ function isGoalish(text) {
575
+ return /\b(goal|objective|task|need|want|implement|fix|improve|refactor|add|remove|hedef|amac|istiyorum|gerekiyor|iyilestir|duzelt|ekle|kaldir)\b/i.test(text);
506
576
  }
507
- var COMPILED_GLOB_CACHE = /* @__PURE__ */ new Map();
508
- var CACHE_MAX_SIZE = 2e3;
509
- function getCachedGlob(pattern) {
510
- const cached = COMPILED_GLOB_CACHE.get(pattern);
511
- if (cached) return cached;
512
- if (COMPILED_GLOB_CACHE.size >= CACHE_MAX_SIZE) {
513
- const keys = [...COMPILED_GLOB_CACHE.keys()];
514
- for (let i = 0; i < Math.floor(CACHE_MAX_SIZE / 4); i++) {
515
- COMPILED_GLOB_CACHE.delete(expectDefined(keys[i]));
577
+ function normalizeWhitespace(text) {
578
+ return text.replace(/\s+/g, " ").trim();
579
+ }
580
+ function pushUniqueBounded(list, value, max) {
581
+ const normalized = normalizeWhitespace(value);
582
+ if (!normalized) return;
583
+ const existing = list.findIndex((item) => item.toLowerCase() === normalized.toLowerCase());
584
+ if (existing >= 0) list.splice(existing, 1);
585
+ list.push(normalized);
586
+ if (list.length > max) list.splice(0, list.length - max);
587
+ }
588
+ function extractFiles(ctx, toolName, input, content) {
589
+ const out = /* @__PURE__ */ new Set();
590
+ for (const value of inputPathValues(input)) addPath(ctx, out, value);
591
+ if (toolName === "grep" || toolName === "glob" || toolName === "bash") {
592
+ const re = /(?:(?:[A-Za-z]:)?[./\\]?[\w@.-]+(?:[\\/][\w@(). -]+)+\.[A-Za-z0-9]{1,12})/g;
593
+ for (const match of content.matchAll(re)) addPath(ctx, out, match[0]);
594
+ }
595
+ return [...out].slice(0, 30);
596
+ }
597
+ function inputPathValues(input) {
598
+ const values = [];
599
+ const visit = (value, key) => {
600
+ if (typeof value === "string") {
601
+ if (key && /^(path|file|files|fromFile|toFile|dir|cwd)$/i.test(key)) values.push(value);
602
+ return;
603
+ }
604
+ if (Array.isArray(value)) {
605
+ for (const item of value) visit(item, key);
606
+ return;
607
+ }
608
+ if (!value || typeof value !== "object") return;
609
+ for (const [k, v] of Object.entries(value)) visit(v, k);
610
+ };
611
+ visit(input);
612
+ return values;
613
+ }
614
+ function addPath(ctx, out, raw) {
615
+ const clean = raw.trim().replace(/^["'`]+|["'`),;:]+$/g, "");
616
+ if (!clean || clean.length > 260) return;
617
+ let normalized = clean.replace(/\\/g, "/");
618
+ try {
619
+ const abs = path3.isAbsolute(clean) ? path3.resolve(clean) : null;
620
+ if (abs) {
621
+ const rel = path3.relative(ctx.projectRoot, abs);
622
+ if (!rel.startsWith("..") && !path3.isAbsolute(rel)) {
623
+ normalized = rel.replace(/\\/g, "/");
624
+ }
625
+ }
626
+ } catch {
627
+ }
628
+ if (normalized.length > 0) out.add(normalized);
629
+ }
630
+ function extractSymbols(content, input) {
631
+ const out = /* @__PURE__ */ new Set();
632
+ const patterns = [
633
+ /\b(?:function|class|interface|type|enum|const|let|var|def|fn|struct)\s+([A-Za-z_$][\w$]*)/g,
634
+ /\b(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)/g
635
+ ];
636
+ for (const re of patterns) {
637
+ for (const match of content.matchAll(re)) {
638
+ if (match[1]) out.add(match[1]);
639
+ if (out.size >= 30) break;
516
640
  }
517
641
  }
518
- const re = compileGlob(pattern);
519
- COMPILED_GLOB_CACHE.set(pattern, re);
520
- return re;
642
+ const pattern = input && typeof input === "object" ? input["pattern"] : void 0;
643
+ if (typeof pattern === "string" && /^[A-Za-z_$][\w$]*$/.test(pattern)) {
644
+ out.add(pattern);
645
+ }
646
+ return [...out].slice(0, 30);
521
647
  }
522
- var MAX_GLOB_PATTERN_LEN = 1024;
523
- function compileGlob(pattern) {
524
- if (pattern.length > MAX_GLOB_PATTERN_LEN) {
525
- throw new Error(`Glob pattern exceeds ${MAX_GLOB_PATTERN_LEN} characters`);
648
+ function extractCommands(toolName, input) {
649
+ if (toolName !== "bash" && toolName !== "exec" && toolName !== "shell") return [];
650
+ if (!input || typeof input !== "object") return [];
651
+ const command = input["command"];
652
+ if (typeof command !== "string") return [];
653
+ return [command.slice(0, 220)];
654
+ }
655
+ function extractErrors(content) {
656
+ const lines = content.split(/\r?\n/);
657
+ const errors = [];
658
+ for (const line of lines) {
659
+ if (!/\b(error|exception|failed|failure|fatal|panic|timeout|denied|enoent|eacces|eperm|typeerror|syntaxerror)\b/i.test(line)) continue;
660
+ errors.push(normalizeWhitespace(line).slice(0, 260));
661
+ if (errors.length >= 5) break;
662
+ }
663
+ return errors;
664
+ }
665
+ function summarizeInput(input) {
666
+ if (!input || typeof input !== "object") return void 0;
667
+ const obj = input;
668
+ const parts = [];
669
+ for (const key of ["path", "file", "pattern", "glob", "command"]) {
670
+ const value = obj[key];
671
+ if (typeof value === "string") parts.push(`${key}=${value.slice(0, 160)}`);
672
+ }
673
+ return parts.length > 0 ? parts.join(", ") : void 0;
674
+ }
675
+ function summarizeToolOutput(toolName, input, content, opts) {
676
+ if (!opts.ok && opts.errors.length > 0) return opts.errors[0] ?? `${toolName} failed`;
677
+ if (toolName === "read" && opts.files[0]) return `read ${opts.files[0]}`;
678
+ if (toolName === "grep") {
679
+ const pattern = input && typeof input === "object" ? input["pattern"] : void 0;
680
+ return `searched ${typeof pattern === "string" ? pattern : "pattern"} (${opts.files.length} file hint(s))`;
681
+ }
682
+ if ((toolName === "edit" || toolName === "write") && opts.files[0]) {
683
+ return `${toolName === "write" ? "wrote" : "edited"} ${opts.files[0]}`;
684
+ }
685
+ const firstLine = normalizeWhitespace(content.split(/\r?\n/).find((line) => line.trim()) ?? "");
686
+ return firstLine ? firstLine.slice(0, 220) : `${toolName} returned no text`;
687
+ }
688
+ function updateFileGraph(state, metadata) {
689
+ const writes = WRITE_TOOLS.has(metadata.toolName) ? 1 : 0;
690
+ const reads = writes === 0 && (READ_TOOLS.has(metadata.toolName) || metadata.files.length > 0) ? 1 : 0;
691
+ for (const file of metadata.files) {
692
+ const existing = state.fileGraph[file] ?? {
693
+ path: file,
694
+ reads: 0,
695
+ writes: 0,
696
+ tools: [],
697
+ referenced: false
698
+ };
699
+ existing.reads += reads;
700
+ existing.writes += writes;
701
+ existing.lastToolUseId = metadata.toolUseId;
702
+ pushUniqueBounded(existing.tools, `${metadata.toolName}#${metadata.toolUseId}`, 8);
703
+ state.fileGraph[file] = existing;
526
704
  }
527
- let i = 0;
528
- let re = "^";
529
- while (i < pattern.length) {
530
- const c = pattern[i];
531
- if (c === "*") {
532
- if (pattern[i + 1] === "*") {
533
- re += ".*";
534
- i += 2;
535
- if (pattern[i] === "/") i++;
536
- } else {
537
- re += "[^/]*";
538
- i++;
539
- }
540
- } else if (c === "?") {
541
- re += "[^/]";
542
- i++;
543
- } else if (c === "[") {
544
- let cls = "[";
545
- i++;
546
- if (pattern[i] === "!" || pattern[i] === "^") {
547
- cls += "^";
548
- i++;
549
- }
550
- while (i < pattern.length && pattern[i] !== "]") {
551
- const ch = pattern[i] ?? "";
552
- if (ch === "\\") {
553
- cls += "\\\\";
554
- } else if (ch === "]" || ch === "^") {
555
- cls += `\\${ch}`;
556
- } else {
557
- cls += ch;
558
- }
559
- i++;
560
- }
561
- cls += "]";
562
- re += cls;
563
- i++;
705
+ }
706
+ function updateRepeatedReadSignals(state, metadata) {
707
+ if (metadata.toolName !== "read" || metadata.files.length === 0) {
708
+ state.lastReadPath = void 0;
709
+ return;
710
+ }
711
+ const file = metadata.files[0];
712
+ if (state.lastReadPath === file) {
713
+ const existing = state.repeatedReads.find((item) => item.file === file);
714
+ if (existing) {
715
+ existing.count++;
716
+ existing.lastToolUseId = metadata.toolUseId;
564
717
  } else {
565
- re += escapeRegex(c ?? "");
566
- i++;
718
+ state.repeatedReads.push({ file, count: 2, lastToolUseId: metadata.toolUseId });
567
719
  }
720
+ if (state.repeatedReads.length > 10) state.repeatedReads.shift();
568
721
  }
569
- re += "$";
570
- return new RegExp(re);
722
+ state.lastReadPath = file;
571
723
  }
572
- function matchGlob(pattern, input) {
573
- return getCachedGlob(pattern).test(input);
724
+ function implicitFactFor(metadata) {
725
+ if (metadata.errors.length > 0) return `${metadata.toolName}#${metadata.toolUseId} exposed error: ${metadata.errors[0]}`;
726
+ if (metadata.toolName === "read" && metadata.files[0]) {
727
+ const size = metadata.outputLines ? ` (${metadata.outputLines} line(s) returned)` : "";
728
+ return `read ${metadata.files[0]}${size}`;
729
+ }
730
+ if ((metadata.toolName === "edit" || metadata.toolName === "write") && metadata.files[0]) {
731
+ return `${metadata.toolName} changed ${metadata.files[0]}`;
732
+ }
733
+ if (metadata.status === "referenced") return `${metadata.toolName}#${metadata.toolUseId} was referenced`;
734
+ return void 0;
574
735
  }
575
- function matchAny(patterns, input) {
576
- return patterns.some((p) => matchGlob(p, input));
736
+ function metadataReferencedByText(metadata, haystack) {
737
+ for (const file of metadata.files) {
738
+ const f = file.toLowerCase();
739
+ const base = path3.basename(file).toLowerCase();
740
+ if (f && haystack.includes(f)) return true;
741
+ if (base && haystack.includes(base)) return true;
742
+ }
743
+ for (const symbol of metadata.symbols) {
744
+ if (symbol.length >= 3 && haystack.includes(symbol.toLowerCase())) return true;
745
+ }
746
+ for (const err of metadata.errors) {
747
+ const head = err.slice(0, 80).toLowerCase();
748
+ if (head.length >= 12 && haystack.includes(head)) return true;
749
+ }
750
+ return false;
577
751
  }
578
752
 
579
- // src/utils/diff.ts
580
- function myersDiff(a, b) {
581
- const N = a.length;
582
- const M = b.length;
583
- const max = N + M;
584
- if (max === 0) return [];
585
- const v = /* @__PURE__ */ new Map();
586
- v.set(1, 0);
587
- const trace = [];
588
- for (let d = 0; d <= max; d++) {
589
- const snapshot = new Map(v);
590
- trace.push(snapshot);
591
- for (let k = -d; k <= d; k += 2) {
592
- const left = v.get(k - 1) ?? -1;
593
- const right = v.get(k + 1) ?? -1;
594
- let x;
595
- if (k === -d || k !== d && left < right) {
596
- x = right;
597
- } else {
598
- x = left + 1;
599
- }
600
- let y = x - k;
601
- while (x < N && y < M && a[x] === b[y]) {
602
- x++;
603
- y++;
604
- }
605
- v.set(k, x);
606
- if (x >= N && y >= M) {
607
- return backtrack(trace, a, b, N, M, d);
608
- }
609
- }
610
- }
611
- return [];
753
+ // src/utils/deep-merge.ts
754
+ var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
755
+ "__proto__",
756
+ "constructor",
757
+ "prototype",
758
+ "__defineGetter__",
759
+ "__defineSetter__",
760
+ "__lookupGetter__",
761
+ "__lookupSetter__"
762
+ ]);
763
+ function isPrimitiveArray(a) {
764
+ return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
765
+ }
766
+ function deepMerge(base, patch, options = {}) {
767
+ const {
768
+ conflictResolution = "prefer-patch",
769
+ arrayMode = "replace",
770
+ protectProto = true,
771
+ onNonPrimitiveArrayReplace
772
+ } = options;
773
+ if (typeof base !== "object" || base === null) {
774
+ return conflictResolution === "prefer-patch" ? patch : base;
775
+ }
776
+ if (typeof patch !== "object" || patch === null) {
777
+ return conflictResolution === "prefer-patch" ? patch : base;
778
+ }
779
+ if (Array.isArray(base) && Array.isArray(patch)) {
780
+ if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
781
+ return [.../* @__PURE__ */ new Set([...base, ...patch])];
782
+ }
783
+ return conflictResolution === "prefer-patch" ? patch : base;
784
+ }
785
+ if (Array.isArray(base) || Array.isArray(patch)) {
786
+ return conflictResolution === "prefer-patch" ? patch : base;
787
+ }
788
+ const baseObj = base;
789
+ const patchObj = patch;
790
+ const out = { ...baseObj };
791
+ for (const [k, v] of Object.entries(patchObj)) {
792
+ if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
793
+ const existing = out[k];
794
+ if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
795
+ out[k] = deepMerge(existing, v, options);
796
+ } else if (Array.isArray(v) && Array.isArray(existing)) {
797
+ if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
798
+ onNonPrimitiveArrayReplace(k, existing.length, v.length);
799
+ }
800
+ out[k] = deepMerge(existing, v, options);
801
+ } else if (v !== void 0) {
802
+ if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
803
+ const existingLen = Array.isArray(existing) ? existing.length : 0;
804
+ onNonPrimitiveArrayReplace(k, existingLen, v.length);
805
+ }
806
+ out[k] = v;
807
+ }
808
+ }
809
+ return out;
810
+ }
811
+
812
+ // src/utils/diff.ts
813
+ function myersDiff(a, b) {
814
+ const N = a.length;
815
+ const M = b.length;
816
+ const max = N + M;
817
+ if (max === 0) return [];
818
+ const v = /* @__PURE__ */ new Map();
819
+ v.set(1, 0);
820
+ const trace = [];
821
+ for (let d = 0; d <= max; d++) {
822
+ const snapshot = new Map(v);
823
+ trace.push(snapshot);
824
+ for (let k = -d; k <= d; k += 2) {
825
+ const left = v.get(k - 1) ?? -1;
826
+ const right = v.get(k + 1) ?? -1;
827
+ let x;
828
+ if (k === -d || k !== d && left < right) {
829
+ x = right;
830
+ } else {
831
+ x = left + 1;
832
+ }
833
+ let y = x - k;
834
+ while (x < N && y < M && a[x] === b[y]) {
835
+ x++;
836
+ y++;
837
+ }
838
+ v.set(k, x);
839
+ if (x >= N && y >= M) {
840
+ return backtrack(trace, a, b, N, M, d);
841
+ }
842
+ }
843
+ }
844
+ return [];
612
845
  }
613
846
  function backtrack(trace, a, b, N, M, finalD) {
614
847
  const edits = [];
@@ -727,532 +960,392 @@ function unifiedDiff(oldText, newText, opts = {}) {
727
960
  }
728
961
  return out;
729
962
  }
730
- function projectHash(absRoot) {
731
- return createHash("sha256").update(path2.resolve(absRoot)).digest("hex").slice(0, 12);
732
- }
733
- function projectSlug(absRoot) {
734
- const base = slugify(path2.basename(absRoot));
735
- const hash = createHash("sha256").update(path2.resolve(absRoot)).digest("hex").slice(0, 6);
736
- return `${base}-${hash}`;
737
- }
738
- function slugify(name) {
739
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
740
- }
741
- function wstackGlobalRoot() {
742
- const fromEnv = process.env["WRONGSTACK_HOME"];
743
- if (fromEnv && fromEnv.trim().length > 0) return path2.resolve(fromEnv);
744
- return path2.join(os.homedir(), ".wrongstack");
745
- }
746
- function resolveWstackPaths(opts) {
747
- const globalRoot = opts.globalRoot ?? (opts.userHome ? path2.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
748
- const hash = projectHash(opts.projectRoot);
749
- const slug = projectSlug(opts.projectRoot);
750
- const projectDir = path2.join(globalRoot, "projects", slug);
751
- return {
752
- globalRoot,
753
- configDir: globalRoot,
754
- globalConfig: path2.join(globalRoot, "config.json"),
755
- secretsKey: path2.join(globalRoot, ".key"),
756
- globalMemory: path2.join(globalRoot, "memory.md"),
757
- globalSkills: path2.join(globalRoot, "skills"),
758
- globalPrompts: path2.join(globalRoot, "prompts"),
759
- cacheDir: path2.join(globalRoot, "cache"),
760
- modelsCache: path2.join(globalRoot, "cache", "models.dev.json"),
761
- modelsOverlayCache: path2.join(globalRoot, "cache", "models-overlay.json"),
762
- historyFile: path2.join(globalRoot, "history"),
763
- logFile: path2.join(globalRoot, "logs", "wrongstack.log"),
764
- projectDir,
765
- projectCodebaseIndex: path2.join(projectDir, "codebase-index"),
766
- projectMemory: path2.join(projectDir, "memory.md"),
767
- projectSessions: path2.join(projectDir, "sessions"),
768
- projectTrust: path2.join(projectDir, "trust.json"),
769
- projectMeta: path2.join(projectDir, "meta.json"),
770
- projectLocalConfig: path2.join(projectDir, "config.local.json"),
771
- inProjectConfig: path2.join(opts.projectRoot, ".wrongstack", "config.json"),
772
- inProjectAgentsFile: path2.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
773
- inProjectSkills: path2.join(opts.projectRoot, ".wrongstack", "skills"),
774
- inProjectWorktrees: path2.join(opts.projectRoot, ".wrongstack", "worktrees"),
775
- projectHash: hash,
776
- projectSlug: slug,
777
- projectGoal: path2.join(projectDir, "goal.json"),
778
- projectSpecs: path2.join(projectDir, "specs"),
779
- projectTaskGraphs: path2.join(projectDir, "task-graphs"),
780
- projectSddSession: path2.join(projectDir, "sdd-session.json"),
781
- projectPlan: path2.join(projectDir, "plan.json"),
782
- projectAutophase: path2.join(projectDir, "autophase"),
783
- syncConfig: path2.join(globalRoot, "sync.json"),
784
- projectStatus: (projectHash2) => path2.join(globalRoot, "projects", projectHash2, "status.json")
785
- };
963
+
964
+ // src/utils/error.ts
965
+ function toErrorMessage(err) {
966
+ return err instanceof Error ? err.message : String(err);
786
967
  }
787
968
 
788
- // src/utils/child-env.ts
789
- var ALLOWED_KEYS = /* @__PURE__ */ new Set([
790
- "PATH",
791
- "HOME",
792
- "USER",
793
- "USERNAME",
794
- "LOGNAME",
795
- "SHELL",
796
- "LANG",
797
- "LC_ALL",
798
- "LC_CTYPE",
799
- "TERM",
800
- "TZ",
801
- "TMPDIR",
802
- "TEMP",
803
- "TMP",
804
- "PWD",
805
- "OLDPWD",
806
- "COMSPEC",
807
- "SYSTEMROOT",
808
- "SYSTEMDRIVE",
809
- "WINDIR",
810
- "PROGRAMFILES",
811
- "PROGRAMFILES(X86)",
812
- "PROGRAMDATA",
813
- "APPDATA",
814
- "LOCALAPPDATA",
815
- "USERPROFILE",
816
- "PUBLIC",
817
- "PATHEXT"
818
- ]);
819
- var SECRET_NAME_PARTS = [
820
- "TOKEN",
821
- "SECRET",
822
- "PASSWORD",
823
- "PASSWD",
824
- "AUTH",
825
- "CRED",
826
- "BEARER",
827
- "COOKIE",
828
- "PRIVATE"
829
- ];
830
- function looksSecret(name) {
831
- const upper = name.toUpperCase();
832
- for (const p of SECRET_NAME_PARTS) {
833
- if (upper.includes(p)) return true;
969
+ // src/utils/expect-defined.ts
970
+ function expectDefined(value, label) {
971
+ if (value === null || value === void 0) {
972
+ const err = new Error(label ? `Expected ${label} to be defined` : "Expected value to be defined");
973
+ err.name = "ExpectDefinedError";
974
+ throw err;
834
975
  }
835
- if (/(?:^|_)KEY(?:$|_|S$)/i.test(upper)) return true;
836
- if (/API[_-]?KEY/i.test(upper)) return true;
837
- if (/ACCESS[_-]?KEY/i.test(upper)) return true;
838
- if (/SESSION[_-]?ID/i.test(upper) === false && /SESSION/i.test(upper)) {
839
- return true;
976
+ return value;
977
+ }
978
+ var GLOB_CHARS = /* @__PURE__ */ new Set(["*", "?", "["]);
979
+ var IS_WINDOWS = process.platform === "win32";
980
+ var SEP = IS_WINDOWS ? "\\" : "/";
981
+ function isGlob(p) {
982
+ for (const c of p) {
983
+ if (GLOB_CHARS.has(c)) return true;
840
984
  }
841
985
  return false;
842
986
  }
843
- function buildChildEnv(optsOrSessionId) {
844
- const opts = typeof optsOrSessionId === "string" ? { sessionId: optsOrSessionId } : optsOrSessionId ?? {};
845
- const hasOwn = Object.hasOwn(process.env, "WRONGSTACK_CHILD_ENV_PASSTHROUGH");
846
- const legacyHasOwn = Object.hasOwn(process.env, "WRONGSTACK_BASH_ENV_PASSTHROUGH");
847
- const passthrough = hasOwn && process.env["WRONGSTACK_CHILD_ENV_PASSTHROUGH"] === "1" || legacyHasOwn && process.env["WRONGSTACK_BASH_ENV_PASSTHROUGH"] === "1";
848
- if (passthrough && !process.env["CI"]) {
849
- console.warn(
850
- "[agent] WARNING: WRONGSTACK_*_ENV_PASSTHROUGH=1 is active \u2014\n all parent env vars (including API keys) forwarded to child processes.\n Do not use on shared or multi-tenant systems."
851
- );
852
- }
853
- const out = {};
854
- const nodeEnvDefaulted = process.env["WRONGSTACK_NODE_ENV_DEFAULTED"] === "1";
855
- for (const [k, v] of Object.entries(process.env)) {
856
- if (v === void 0) continue;
857
- if (nodeEnvDefaulted && (k === "NODE_ENV" || k === "WRONGSTACK_NODE_ENV_DEFAULTED")) continue;
858
- if (passthrough) {
859
- out[k] = v;
860
- continue;
861
- }
862
- const upper = k.toUpperCase();
863
- if (ALLOWED_KEYS.has(upper)) {
864
- out[k] = v;
865
- continue;
866
- }
867
- if (looksSecret(upper)) continue;
868
- if (upper.startsWith("NODE_") || upper.startsWith("NPM_") || upper.startsWith("PNPM_") || upper.startsWith("YARN_") || upper.startsWith("GIT_") || upper.startsWith("CI") || upper.startsWith("XDG_") || // Our own non-secret knobs (WRONGSTACK_HOME, WRONGSTACK_SESSION_ID, …).
869
- // Secrets never live in WRONGSTACK_* env vars (they're in the encrypted
870
- // vault). Forwarding keeps child wstack processes e.g. ones spawned
871
- // by the test suite — inside the same redirected global root.
872
- upper.startsWith("WRONGSTACK_") || upper === "EDITOR" || upper === "VISUAL" || upper === "PAGER") {
873
- out[k] = v;
987
+ function globToRegex(pat) {
988
+ let i = 0;
989
+ let re = "^";
990
+ while (i < pat.length) {
991
+ const c = expectDefined(pat[i]);
992
+ if (c === "*") {
993
+ if (pat[i + 1] === "*") {
994
+ re += ".*";
995
+ i += 2;
996
+ if (pat[i] === "/") i++;
997
+ } else {
998
+ re += "[^/\\\\]*";
999
+ i++;
1000
+ }
1001
+ } else if (c === "?") {
1002
+ re += "[^/\\\\]";
1003
+ i++;
1004
+ } else if (c === "[") {
1005
+ let cls = "[";
1006
+ i++;
1007
+ if (pat[i] === "!" || pat[i] === "^") {
1008
+ cls += "^";
1009
+ i++;
1010
+ }
1011
+ while (i < pat.length && pat[i] !== "]") {
1012
+ const ch = pat[i] ?? "";
1013
+ if (ch === "\\") cls += "\\\\";
1014
+ else if (ch === "]" || ch === "^") cls += `\\${ch}`;
1015
+ else cls += ch;
1016
+ i++;
1017
+ }
1018
+ cls += "]";
1019
+ re += cls;
1020
+ i++;
1021
+ } else {
1022
+ re += c.replace(/[.+^${}()|\\]/g, "\\$&");
1023
+ i++;
874
1024
  }
875
1025
  }
876
- if (opts.extra) {
877
- Object.assign(out, opts.extra);
878
- }
879
- if (opts.sessionId) out["WRONGSTACK_SESSION_ID"] = opts.sessionId;
880
- return out;
1026
+ return new RegExp(re + "$");
881
1027
  }
882
-
883
- // src/utils/sleep.ts
884
- function sleep(ms) {
885
- return new Promise((resolve3) => setTimeout(resolve3, ms));
1028
+ function baseDir(pat) {
1029
+ let i = pat.length - 1;
1030
+ while (i >= 0 && !GLOB_CHARS.has(expectDefined(pat[i])) && pat[i] !== SEP && pat[i] !== "/") i--;
1031
+ const cut = i >= 0 ? pat.lastIndexOf(SEP, i) : pat.lastIndexOf("/", i);
1032
+ return cut < 0 ? "." : pat.slice(0, cut);
886
1033
  }
887
-
888
- // src/utils/assert-never.ts
889
- function assertNever(x, message) {
890
- const err = new Error(
891
- message ?? `Unhandled case: ${JSON.stringify(x)}`
892
- );
893
- err.name = "AssertNeverError";
894
- throw err;
895
- }
896
-
897
- // src/utils/deep-merge.ts
898
- var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
899
- "__proto__",
900
- "constructor",
901
- "prototype",
902
- "__defineGetter__",
903
- "__defineSetter__",
904
- "__lookupGetter__",
905
- "__lookupSetter__"
906
- ]);
907
- function isPrimitiveArray(a) {
908
- return a.every((v) => v === null || typeof v !== "object" && typeof v !== "function");
909
- }
910
- function deepMerge(base, patch, options = {}) {
911
- const {
912
- conflictResolution = "prefer-patch",
913
- arrayMode = "replace",
914
- protectProto = true,
915
- onNonPrimitiveArrayReplace
916
- } = options;
917
- if (typeof base !== "object" || base === null) {
918
- return conflictResolution === "prefer-patch" ? patch : base;
919
- }
920
- if (typeof patch !== "object" || patch === null) {
921
- return conflictResolution === "prefer-patch" ? patch : base;
922
- }
923
- if (Array.isArray(base) && Array.isArray(patch)) {
924
- if (arrayMode === "concat-primitives" && isPrimitiveArray(base) && isPrimitiveArray(patch)) {
925
- return [.../* @__PURE__ */ new Set([...base, ...patch])];
1034
+ async function expandGlob(pattern) {
1035
+ if (!isGlob(pattern)) return [pattern];
1036
+ const results = /* @__PURE__ */ new Set();
1037
+ const abs = isAbsolute(pattern);
1038
+ const base = abs ? baseDir(pattern) : baseDir(pattern);
1039
+ const relPat = base === "." ? pattern : pattern.slice(base.length + 1);
1040
+ async function walk2(dir, pat) {
1041
+ let entries;
1042
+ try {
1043
+ entries = await fs.readdir(dir);
1044
+ } catch {
1045
+ return;
926
1046
  }
927
- return conflictResolution === "prefer-patch" ? patch : base;
928
- }
929
- if (Array.isArray(base) || Array.isArray(patch)) {
930
- return conflictResolution === "prefer-patch" ? patch : base;
931
- }
932
- const baseObj = base;
933
- const patchObj = patch;
934
- const out = { ...baseObj };
935
- for (const [k, v] of Object.entries(patchObj)) {
936
- if (protectProto && FORBIDDEN_PROTO_KEYS.has(k)) continue;
937
- const existing = out[k];
938
- if (v !== null && typeof v === "object" && !Array.isArray(v) && existing !== null && typeof existing === "object" && !Array.isArray(existing)) {
939
- out[k] = deepMerge(existing, v, options);
940
- } else if (Array.isArray(v) && Array.isArray(existing)) {
941
- if (onNonPrimitiveArrayReplace && !isPrimitiveArray(v)) {
942
- onNonPrimitiveArrayReplace(k, existing.length, v.length);
943
- }
944
- out[k] = deepMerge(existing, v, options);
945
- } else if (v !== void 0) {
946
- if (onNonPrimitiveArrayReplace && Array.isArray(v) && !isPrimitiveArray(v)) {
947
- const existingLen = Array.isArray(existing) ? existing.length : 0;
948
- onNonPrimitiveArrayReplace(k, existingLen, v.length);
1047
+ const firstGlob = pat.search(/[*?[[]/);
1048
+ if (firstGlob < 0) {
1049
+ const re = globToRegex(pat);
1050
+ for (const e of entries) {
1051
+ if (re.test(e)) {
1052
+ const full = `${dir}${SEP}${e}`;
1053
+ results.add(abs ? resolve(full) : full);
1054
+ }
949
1055
  }
950
- out[k] = v;
1056
+ return;
951
1057
  }
952
- }
953
- return out;
954
- }
955
-
956
- // src/utils/tool-output-serializer.ts
957
- function createToolOutputSerializer(opts = {}) {
958
- const capBytes = opts.perIterationOutputCapBytes ?? 1e5;
959
- function serialize(value) {
960
- if (typeof value === "string") return value;
961
- if (value === null || value === void 0) return "";
962
- if (typeof value === "object") {
963
- if (Array.isArray(value)) return value.map(serialize).join("\n");
964
- if ("text" in value) {
965
- const t = value.text;
966
- return typeof t === "string" ? t : JSON.stringify(value, null, 2);
1058
+ const before = pat.slice(0, firstGlob);
1059
+ const rest = pat.slice(firstGlob);
1060
+ if (before.endsWith("**")) {
1061
+ await walk2(dir, rest);
1062
+ for (const e of entries) {
1063
+ const full = `${dir}${SEP}${e}`;
1064
+ try {
1065
+ const stat3 = await fs.stat(full);
1066
+ if (stat3.isDirectory()) await walk2(full, rest);
1067
+ } catch {
1068
+ }
967
1069
  }
968
- try {
969
- return JSON.stringify(value, null, 2);
970
- } catch {
971
- return String(value);
1070
+ } else if (before === "") {
1071
+ const re = globToRegex(rest);
1072
+ for (const e of entries) {
1073
+ if (re.test(e)) {
1074
+ const full = `${dir}${SEP}${e}`;
1075
+ results.add(abs ? resolve(full) : full);
1076
+ }
1077
+ }
1078
+ } else {
1079
+ const seg = before.replace(/[*?[\]]/g, "").replace(/\/$/, "");
1080
+ if (entries.includes(seg)) {
1081
+ const full = `${dir}${SEP}${seg}`;
1082
+ try {
1083
+ const stat3 = await fs.stat(full);
1084
+ if (stat3.isDirectory()) await walk2(full, rest);
1085
+ } catch {
1086
+ }
972
1087
  }
973
1088
  }
974
- return String(value);
975
- }
976
- function enforceCap(text, remainingBudget) {
977
- if (remainingBudget <= 0) {
978
- return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
979
- }
980
- const textBytes = Buffer.byteLength(text, "utf8");
981
- if (textBytes <= remainingBudget) {
982
- return { text, newBudget: remainingBudget - textBytes };
983
- }
984
- const marker = `
985
- \u2026[truncated ${textBytes - remainingBudget} bytes]\u2026
986
- `;
987
- const markerBytes = Buffer.byteLength(marker, "utf8");
988
- const available = remainingBudget - markerBytes;
989
- if (available <= 0) {
990
- return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
991
- }
992
- const half = Math.floor(available / 2);
993
- const first = text.slice(0, half);
994
- const second = text.slice(text.length - half);
995
- return { text: `${first}${marker}${second}`, newBudget: 0 };
996
1089
  }
997
- return { serialize, enforceCap, capBytes };
1090
+ await walk2(base === "." ? "." : base, relPat);
1091
+ return [...results];
998
1092
  }
999
1093
 
1000
- // src/utils/token-estimate.ts
1001
- var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
1002
- var CAL_ALPHA = 0.3;
1003
- var CALIBRATION_GLOBAL_KEY = "__global__";
1004
- var _cals = /* @__PURE__ */ new Map();
1005
- function calState(key) {
1006
- let state = _cals.get(key);
1007
- if (!state) {
1008
- state = { ratio: 1, count: 0, prevEst: 0 };
1009
- _cals.set(key, state);
1010
- }
1011
- return state;
1094
+ // src/utils/glob-match.ts
1095
+ function escapeRegex(s) {
1096
+ return s.replace(/[.+^${}()|\\]/g, "\\$&");
1012
1097
  }
1013
- var MIN_SAMPLES_FOR_CALIBRATION = 3;
1014
- var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
1015
- var ESTIMATE_CACHE_MAX_SIZE = 1e4;
1016
- function getCachedEstimate(key, compute) {
1017
- const existing = ESTIMATE_CACHE.get(key);
1018
- if (existing !== void 0) return existing;
1019
- if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
1020
- for (const k of ESTIMATE_CACHE.keys()) {
1021
- if (ESTIMATE_CACHE.size <= Math.floor(ESTIMATE_CACHE_MAX_SIZE / 2)) break;
1022
- ESTIMATE_CACHE.delete(k);
1098
+ var COMPILED_GLOB_CACHE = /* @__PURE__ */ new Map();
1099
+ var CACHE_MAX_SIZE = 2e3;
1100
+ function getCachedGlob(pattern) {
1101
+ const cached = COMPILED_GLOB_CACHE.get(pattern);
1102
+ if (cached) return cached;
1103
+ if (COMPILED_GLOB_CACHE.size >= CACHE_MAX_SIZE) {
1104
+ const keys = [...COMPILED_GLOB_CACHE.keys()];
1105
+ for (let i = 0; i < Math.floor(CACHE_MAX_SIZE / 4); i++) {
1106
+ COMPILED_GLOB_CACHE.delete(expectDefined(keys[i]));
1023
1107
  }
1024
1108
  }
1025
- const estimate = compute(key);
1026
- ESTIMATE_CACHE.set(key, estimate);
1027
- return estimate;
1109
+ const re = compileGlob(pattern);
1110
+ COMPILED_GLOB_CACHE.set(pattern, re);
1111
+ return re;
1028
1112
  }
1029
- function estimateToolInputTokens(input) {
1030
- if (typeof input === "string") return RoughTokenEstimate(input);
1031
- if (input === null || typeof input !== "object") {
1032
- return RoughTokenEstimate(String(input));
1113
+ var MAX_GLOB_PATTERN_LEN = 1024;
1114
+ function compileGlob(pattern) {
1115
+ if (pattern.length > MAX_GLOB_PATTERN_LEN) {
1116
+ throw new Error(`Glob pattern exceeds ${MAX_GLOB_PATTERN_LEN} characters`);
1033
1117
  }
1034
- return getCachedEstimate(JSON.stringify(input), (key) => RoughTokenEstimate(key));
1118
+ let i = 0;
1119
+ let re = "^";
1120
+ while (i < pattern.length) {
1121
+ const c = pattern[i];
1122
+ if (c === "*") {
1123
+ if (pattern[i + 1] === "*") {
1124
+ re += ".*";
1125
+ i += 2;
1126
+ if (pattern[i] === "/") i++;
1127
+ } else {
1128
+ re += "[^/]*";
1129
+ i++;
1130
+ }
1131
+ } else if (c === "?") {
1132
+ re += "[^/]";
1133
+ i++;
1134
+ } else if (c === "[") {
1135
+ let cls = "[";
1136
+ i++;
1137
+ if (pattern[i] === "!" || pattern[i] === "^") {
1138
+ cls += "^";
1139
+ i++;
1140
+ }
1141
+ while (i < pattern.length && pattern[i] !== "]") {
1142
+ const ch = pattern[i] ?? "";
1143
+ if (ch === "\\") {
1144
+ cls += "\\\\";
1145
+ } else if (ch === "]" || ch === "^") {
1146
+ cls += `\\${ch}`;
1147
+ } else {
1148
+ cls += ch;
1149
+ }
1150
+ i++;
1151
+ }
1152
+ cls += "]";
1153
+ re += cls;
1154
+ i++;
1155
+ } else {
1156
+ re += escapeRegex(c ?? "");
1157
+ i++;
1158
+ }
1159
+ }
1160
+ re += "$";
1161
+ return new RegExp(re);
1035
1162
  }
1036
- function estimateToolResultTokens(content) {
1037
- if (typeof content === "string") return RoughTokenEstimate(content);
1038
- return getCachedEstimate(JSON.stringify(content), (key) => RoughTokenEstimate(key));
1163
+ function matchGlob(pattern, input) {
1164
+ return getCachedGlob(pattern).test(input);
1039
1165
  }
1040
- function estimateTextTokens(text) {
1041
- return RoughTokenEstimate(text);
1166
+ function matchAny(patterns, input) {
1167
+ return patterns.some((p) => matchGlob(p, input));
1042
1168
  }
1043
- function computeMessageTokens(msg) {
1044
- if (typeof msg.content === "string") return estimateTextTokens(msg.content);
1045
- let total = 0;
1046
- for (const b of msg.content) {
1047
- if (b.type === "text") total += estimateTextTokens(b.text);
1048
- else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
1049
- else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
1050
- else total += RoughTokenEstimate(JSON.stringify(b));
1169
+ function isPrivateIPv4(addr) {
1170
+ const parts = addr.split(".").map((p) => Number.parseInt(p, 10));
1171
+ if (parts.length !== 4 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255)) {
1172
+ return true;
1051
1173
  }
1052
- return total;
1174
+ const [a, b, c] = parts;
1175
+ if (a === 0) return true;
1176
+ if (a === 10) return true;
1177
+ if (a === 127) return true;
1178
+ if (a === 169 && b === 254) return true;
1179
+ if (a === 172 && b >= 16 && b <= 31) return true;
1180
+ if (a === 192 && b === 168) return true;
1181
+ if (a === 192 && b === 0 && c === 0) return true;
1182
+ if (a === 100 && b >= 64 && b <= 127) return true;
1183
+ if (a >= 224) return true;
1184
+ return false;
1053
1185
  }
1054
- function estimateMessageTokens(messages) {
1055
- let total = 0;
1056
- for (const m of messages) {
1057
- if (typeof m._estTokens === "number" && m._estTokens > 0) {
1058
- total += m._estTokens;
1059
- continue;
1060
- }
1061
- total += computeMessageTokens(m);
1186
+ function isPrivateIPv6(raw) {
1187
+ const lower = raw.toLowerCase();
1188
+ if (lower === "::" || lower === "::1") return true;
1189
+ const groups = expandIPv6(lower);
1190
+ if (!groups) return true;
1191
+ if (groups[0] === 0 && groups[1] === 0 && groups[2] === 0 && groups[3] === 0 && groups[4] === 0 && groups[5] === 65535) {
1192
+ const a = (groups[6] ?? 0) >> 8;
1193
+ const b = (groups[6] ?? 0) & 255;
1194
+ const c = (groups[7] ?? 0) >> 8;
1195
+ const d = (groups[7] ?? 0) & 255;
1196
+ return isPrivateIPv4(`${a}.${b}.${c}.${d}`);
1062
1197
  }
1063
- return total;
1198
+ const high = groups[0] ?? 0;
1199
+ if ((high & 65024) === 64512) return true;
1200
+ if ((high & 65472) === 65152) return true;
1201
+ if ((high & 65280) === 65280) return true;
1202
+ return false;
1064
1203
  }
1065
- function estimateToolDefTokens(tool) {
1066
- const cached = tool._estDefTokens;
1067
- if (typeof cached === "number" && cached > 0) return cached;
1068
- return RoughTokenEstimate(tool.name) + RoughTokenEstimate(tool.description ?? "") + RoughTokenEstimate(JSON.stringify(tool.inputSchema));
1204
+ function expandIPv6(addr) {
1205
+ const parts = addr.split("::");
1206
+ if (parts.length > 2) return null;
1207
+ const parseGroups = (s) => {
1208
+ if (s === "") return [];
1209
+ const out = [];
1210
+ for (const g of s.split(":")) {
1211
+ if (g.length === 0 || g.length > 4) return null;
1212
+ const n = Number.parseInt(g, 16);
1213
+ if (Number.isNaN(n) || n < 0 || n > 65535) return null;
1214
+ out.push(n);
1215
+ }
1216
+ return out;
1217
+ };
1218
+ if (parts.length === 1) {
1219
+ const groups = parseGroups(parts[0] ?? "");
1220
+ if (!groups || groups.length !== 8) return null;
1221
+ return groups;
1222
+ }
1223
+ const head = parseGroups(parts[0] ?? "");
1224
+ const tail = parseGroups(parts[1] ?? "");
1225
+ if (!head || !tail) return null;
1226
+ const fill = 8 - head.length - tail.length;
1227
+ if (fill < 0) return null;
1228
+ return [...head, ...new Array(fill).fill(0), ...tail];
1069
1229
  }
1070
- function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
1071
- let messagesTokens = 0;
1072
- if (typeof messages === "string") {
1073
- messagesTokens = RoughTokenEstimate(messages);
1074
- } else if (Array.isArray(messages)) {
1075
- for (const m of messages) {
1076
- if (typeof m === "object" && m !== null && "content" in m) {
1077
- const cached = m._estTokens;
1078
- if (typeof cached === "number" && cached > 0) {
1079
- messagesTokens += cached;
1080
- continue;
1081
- }
1082
- const content = m.content;
1083
- if (typeof content === "string") {
1084
- messagesTokens += RoughTokenEstimate(content);
1085
- } else if (Array.isArray(content)) {
1086
- for (const b of content) {
1087
- if (typeof b === "object" && b !== null) {
1088
- if (b.type === "text") {
1089
- messagesTokens += RoughTokenEstimate(b.text);
1090
- } else {
1091
- messagesTokens += RoughTokenEstimate(JSON.stringify(b));
1092
- }
1093
- }
1094
- }
1230
+ async function assertNotPrivateHost(hostname) {
1231
+ const host = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
1232
+ if (host === "localhost" || host.endsWith(".localhost")) {
1233
+ throw new Error("fetch: blocked localhost target");
1234
+ }
1235
+ const ipVersion = net.isIP(host);
1236
+ if (ipVersion === 4) {
1237
+ if (isPrivateIPv4(host)) {
1238
+ throw new Error(`fetch: blocked private/loopback address "${host}"`);
1239
+ }
1240
+ } else if (ipVersion === 6) {
1241
+ if (isPrivateIPv6(host)) {
1242
+ throw new Error(`fetch: blocked private/loopback address "${host}"`);
1243
+ }
1244
+ } else {
1245
+ try {
1246
+ const records = await dns.lookup(host, { all: true });
1247
+ for (const r of records) {
1248
+ const bad = r.family === 4 ? isPrivateIPv4(r.address) : isPrivateIPv6(r.address);
1249
+ if (bad) {
1250
+ throw new Error(`fetch: resolved to private address ${r.address}`);
1095
1251
  }
1096
1252
  }
1253
+ } catch (err) {
1254
+ if (err instanceof Error && err.message.startsWith("fetch:")) throw err;
1097
1255
  }
1098
1256
  }
1099
- let systemTokens = 0;
1100
- if (typeof systemPrompt === "string") {
1101
- systemTokens = RoughTokenEstimate(systemPrompt);
1102
- } else if (Array.isArray(systemPrompt)) {
1103
- for (const b of systemPrompt) {
1104
- if (typeof b === "object" && b !== null && b.type === "text") {
1105
- systemTokens += RoughTokenEstimate(b.text);
1257
+ }
1258
+
1259
+ // src/utils/json-repair.ts
1260
+ function completePartialObject(s) {
1261
+ if (!s.trim().startsWith("{")) return s;
1262
+ if (tryParse(s).ok) return s;
1263
+ return repairTruncated(s);
1264
+ }
1265
+ function repairTruncated(s) {
1266
+ const stack = [];
1267
+ let inString = false;
1268
+ let escaped = false;
1269
+ let sawKey = false;
1270
+ let prevSig = "";
1271
+ let contentEnd = 0;
1272
+ let stringBraceDepth = 0;
1273
+ for (let i = 0; i < s.length; i++) {
1274
+ const ch = expectDefined(s[i]);
1275
+ if (inString) {
1276
+ contentEnd = i + 1;
1277
+ if (escaped) {
1278
+ escaped = false;
1279
+ continue;
1280
+ }
1281
+ if (ch === "\\") {
1282
+ escaped = true;
1283
+ continue;
1284
+ }
1285
+ if (ch === '"') {
1286
+ inString = false;
1287
+ prevSig = '"';
1288
+ stringBraceDepth = 0;
1289
+ continue;
1106
1290
  }
1291
+ if (ch === "{") stringBraceDepth++;
1292
+ else if (ch === "}" && stringBraceDepth > 0) stringBraceDepth--;
1293
+ continue;
1294
+ }
1295
+ if (ch === " " || ch === " " || ch === "\n" || ch === "\r") continue;
1296
+ contentEnd = i + 1;
1297
+ if (ch === '"') {
1298
+ inString = true;
1299
+ sawKey = true;
1300
+ stringBraceDepth = 0;
1301
+ prevSig = '"';
1302
+ } else if (ch === "{" || ch === "[") {
1303
+ stack.push(ch);
1304
+ prevSig = ch;
1305
+ } else if (ch === "}" || ch === "]") {
1306
+ stack.pop();
1307
+ prevSig = ch;
1308
+ } else {
1309
+ prevSig = ch;
1107
1310
  }
1108
1311
  }
1109
- let toolsTokens = 0;
1110
- for (const t of tools) {
1111
- toolsTokens += estimateToolDefTokens(t);
1312
+ if (!sawKey && !inString) return s;
1313
+ let result = s.slice(0, contentEnd);
1314
+ if (inString) {
1315
+ if (escaped) {
1316
+ result = result.slice(0, -1);
1317
+ } else if (endsWithInvalidEscape(result)) {
1318
+ result = result.slice(0, -2);
1319
+ }
1320
+ if (stringBraceDepth > 0) result += "}".repeat(stringBraceDepth);
1321
+ result += '"';
1322
+ } else if (prevSig === ":") {
1323
+ result += "null";
1112
1324
  }
1113
- const total = messagesTokens + systemTokens + toolsTokens;
1114
- calState(calibrationKey).prevEst = total;
1115
- return {
1116
- messages: messagesTokens,
1117
- systemPrompt: systemTokens,
1118
- tools: toolsTokens,
1119
- total
1120
- };
1121
- }
1122
- function recordActualUsage(actualInputTokens, estimatedInputTokens, calibrationKey = CALIBRATION_GLOBAL_KEY) {
1123
- if (actualInputTokens <= 0) return;
1124
- const cal = calState(calibrationKey);
1125
- const est = estimatedInputTokens ?? cal.prevEst;
1126
- if (est <= 0) return;
1127
- const sampleRatio = actualInputTokens / est;
1128
- if (cal.count === 0) {
1129
- cal.ratio = sampleRatio;
1130
- } else {
1131
- cal.ratio = CAL_ALPHA * sampleRatio + (1 - CAL_ALPHA) * cal.ratio;
1325
+ for (let k = stack.length - 1; k >= 0; k--) {
1326
+ result += stack[k] === "{" ? "}" : "]";
1132
1327
  }
1133
- cal.ratio = Math.min(1.5, Math.max(0.5, cal.ratio));
1134
- cal.count++;
1135
- }
1136
- function getCalibrationState(calibrationKey = CALIBRATION_GLOBAL_KEY) {
1137
- const cal = calState(calibrationKey);
1138
- return {
1139
- ratio: cal.ratio,
1140
- count: cal.count,
1141
- calibrated: cal.count >= MIN_SAMPLES_FOR_CALIBRATION
1142
- };
1143
- }
1144
- function estimateRequestTokensCalibrated(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
1145
- const result = estimateRequestTokens(messages, systemPrompt, tools, calibrationKey);
1146
- const cal = calState(calibrationKey);
1147
- if (cal.count >= MIN_SAMPLES_FOR_CALIBRATION) {
1148
- const safeRatio = Math.min(1.5, Math.max(0.5, cal.ratio));
1149
- return {
1150
- messages: Math.round(result.messages * safeRatio),
1151
- systemPrompt: Math.round(result.systemPrompt * safeRatio),
1152
- tools: Math.round(result.tools * safeRatio),
1153
- total: Math.round(result.total * safeRatio)
1154
- };
1328
+ if (!tryParse(result).ok) {
1329
+ const patched = result.replace(/:(\s*)([}\]])/g, ":null$2");
1330
+ if (tryParse(patched).ok) result = patched;
1155
1331
  }
1156
1332
  return result;
1157
1333
  }
1158
- function resetCalibration(calibrationKey) {
1159
- if (calibrationKey === void 0) {
1160
- _cals.clear();
1161
- return;
1162
- }
1163
- _cals.delete(calibrationKey);
1334
+ var VALID_ESCAPE = /* @__PURE__ */ new Set(['"', "\\", "/", "b", "f", "n", "r", "t", "u"]);
1335
+ function endsWithInvalidEscape(str) {
1336
+ const last = str[str.length - 1];
1337
+ if (str[str.length - 2] !== "\\" || last === void 0) return false;
1338
+ if (VALID_ESCAPE.has(last)) return false;
1339
+ let backslashes = 0;
1340
+ for (let k = str.length - 2; k >= 0 && str[k] === "\\"; k--) backslashes++;
1341
+ return backslashes % 2 === 1;
1164
1342
  }
1165
-
1166
- // src/utils/message-invariants.ts
1167
- function repairToolUseAdjacency(messages) {
1168
- const removedToolUses = [];
1169
- const removedToolResults = [];
1170
- let removedMessages = 0;
1171
- let changed = false;
1172
- const out = [];
1173
- for (let i = 0; i < messages.length; i++) {
1174
- const original = expectDefined(messages[i]);
1175
- let msg = original;
1176
- if (hasToolUse(msg)) {
1177
- const nextIds = toolResultIds(messages[i + 1]);
1178
- const filtered = mapContent(msg, (blocks) => {
1179
- const next = [];
1180
- for (const block of blocks) {
1181
- if (block.type === "tool_use" && !nextIds.has(block.id)) {
1182
- removedToolUses.push(block.id);
1183
- changed = true;
1184
- continue;
1185
- }
1186
- next.push(block);
1187
- }
1188
- return next;
1189
- });
1190
- msg = filtered ?? msg;
1191
- }
1192
- if (hasToolResult(msg)) {
1193
- const allowed = toolUseIds(out[out.length - 1]);
1194
- const filtered = mapContent(msg, (blocks) => {
1195
- const next = [];
1196
- for (const block of blocks) {
1197
- if (block.type === "tool_result" && !allowed.has(block.tool_use_id)) {
1198
- removedToolResults.push(block.tool_use_id);
1199
- changed = true;
1200
- continue;
1201
- }
1202
- next.push(block);
1203
- }
1204
- return next;
1205
- });
1206
- msg = filtered ?? msg;
1207
- }
1208
- if (isEmptyMessage(msg)) {
1209
- removedMessages++;
1210
- changed = true;
1211
- continue;
1212
- }
1213
- out.push(msg);
1214
- }
1215
- return {
1216
- messages: changed ? out : messages,
1217
- report: { changed, removedToolUses, removedToolResults, removedMessages }
1218
- };
1219
- }
1220
- function hasToolUse(msg) {
1221
- return contentBlocks(msg).some((b) => b.type === "tool_use");
1222
- }
1223
- function hasToolResult(msg) {
1224
- return contentBlocks(msg).some((b) => b.type === "tool_result");
1225
- }
1226
- function toolUseIds(msg) {
1227
- const ids = /* @__PURE__ */ new Set();
1228
- if (!msg || msg.role !== "assistant") return ids;
1229
- for (const block of contentBlocks(msg)) {
1230
- if (block.type === "tool_use") ids.add(block.id);
1231
- }
1232
- return ids;
1233
- }
1234
- function toolResultIds(msg) {
1235
- const ids = /* @__PURE__ */ new Set();
1236
- if (!msg || msg.role !== "user") return ids;
1237
- for (const block of contentBlocks(msg)) {
1238
- if (block.type === "tool_result") ids.add(block.tool_use_id);
1239
- }
1240
- return ids;
1241
- }
1242
- function contentBlocks(msg) {
1243
- return msg && Array.isArray(msg.content) ? msg.content : [];
1244
- }
1245
- function mapContent(msg, fn) {
1246
- if (!Array.isArray(msg.content)) return msg;
1247
- const next = fn(msg.content);
1248
- if (next.length === msg.content.length && next.every((b, idx) => b === msg.content[idx])) {
1249
- return msg;
1343
+ function tryParse(s) {
1344
+ try {
1345
+ return { ok: true, value: JSON.parse(s) };
1346
+ } catch {
1347
+ return { ok: false };
1250
1348
  }
1251
- return { ...msg, content: next };
1252
- }
1253
- function isEmptyMessage(msg) {
1254
- if (typeof msg.content === "string") return msg.content.trim().length === 0;
1255
- return msg.content.length === 0;
1256
1349
  }
1257
1350
 
1258
1351
  // src/utils/json-schema-validate.ts
@@ -1261,11 +1354,11 @@ function validateAgainstSchema(value, schema) {
1261
1354
  walk(value, schema, "", errors);
1262
1355
  return { ok: errors.length === 0, errors };
1263
1356
  }
1264
- function walk(value, schema, path3, errors) {
1357
+ function walk(value, schema, path4, errors) {
1265
1358
  if (schema.enum !== void 0) {
1266
1359
  if (!schema.enum.some((e) => deepEqual(e, value))) {
1267
1360
  errors.push({
1268
- path: path3 || "<root>",
1361
+ path: path4 || "<root>",
1269
1362
  message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
1270
1363
  });
1271
1364
  return;
@@ -1274,7 +1367,7 @@ function walk(value, schema, path3, errors) {
1274
1367
  if (typeof schema.type === "string") {
1275
1368
  if (!checkType(value, schema.type)) {
1276
1369
  errors.push({
1277
- path: path3 || "<root>",
1370
+ path: path4 || "<root>",
1278
1371
  message: `expected ${schema.type}, got ${describeType(value)}`
1279
1372
  });
1280
1373
  return;
@@ -1284,20 +1377,20 @@ function walk(value, schema, path3, errors) {
1284
1377
  const obj = value;
1285
1378
  for (const req of schema.required ?? []) {
1286
1379
  if (!(req in obj)) {
1287
- errors.push({ path: joinPath(path3, req), message: "required property missing" });
1380
+ errors.push({ path: joinPath(path4, req), message: "required property missing" });
1288
1381
  }
1289
1382
  }
1290
1383
  if (schema.properties) {
1291
1384
  for (const [key, subSchema] of Object.entries(schema.properties)) {
1292
1385
  if (key in obj) {
1293
- walk(obj[key], subSchema, joinPath(path3, key), errors);
1386
+ walk(obj[key], subSchema, joinPath(path4, key), errors);
1294
1387
  }
1295
1388
  }
1296
1389
  }
1297
1390
  }
1298
1391
  if (schema.type === "array" && Array.isArray(value) && schema.items) {
1299
1392
  for (let i = 0; i < value.length; i++) {
1300
- walk(value[i], schema.items, `${path3}[${i}]`, errors);
1393
+ walk(value[i], schema.items, `${path4}[${i}]`, errors);
1301
1394
  }
1302
1395
  }
1303
1396
  }
@@ -1351,418 +1444,1453 @@ function deepEqual(a, b) {
1351
1444
  return false;
1352
1445
  }
1353
1446
 
1354
- // src/utils/regex-guard.ts
1355
- var MAX_PATTERN_LEN = 512;
1356
- var DANGEROUS_PATTERNS = [
1357
- /(\([^)]*[+*][^)]*\))[+*]/,
1358
- // (a+)+, (.*)+, etc
1359
- /(\(\?:[^)]*[+*][^)]*\))[+*]/
1360
- // same, with non-capturing group
1361
- ];
1362
- function compileUserRegex(pattern, flags) {
1363
- if (typeof pattern !== "string") {
1364
- return { ok: false, reason: "pattern must be a string" };
1447
+ // src/utils/merge-custom-models.ts
1448
+ function mergeCustomModelDefs(providerCustomModels, configModels) {
1449
+ const out = {};
1450
+ if (providerCustomModels) {
1451
+ for (const [id, def] of Object.entries(providerCustomModels)) {
1452
+ out[id] = { ...def };
1453
+ }
1365
1454
  }
1366
- if (pattern.length === 0) {
1367
- return { ok: false, reason: "pattern is empty" };
1455
+ if (configModels) {
1456
+ for (const [id, def] of Object.entries(configModels)) {
1457
+ out[id] = { ...def };
1458
+ }
1368
1459
  }
1369
- if (pattern.length > MAX_PATTERN_LEN) {
1370
- return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
1460
+ if (Object.keys(out).length === 0) return void 0;
1461
+ return out;
1462
+ }
1463
+
1464
+ // src/utils/merge-models-payload.ts
1465
+ function mergeModelsPayload(base, overlay) {
1466
+ const out = {};
1467
+ for (const [id, provider] of Object.entries(base)) {
1468
+ out[id] = cloneProvider(provider);
1371
1469
  }
1372
- for (const rx of DANGEROUS_PATTERNS) {
1373
- if (rx.test(pattern)) {
1374
- return {
1375
- ok: false,
1376
- reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
1377
- };
1378
- }
1470
+ for (const [id, ovProvider] of Object.entries(overlay)) {
1471
+ const existing = out[id];
1472
+ out[id] = existing ? mergeProvider(existing, ovProvider) : cloneProvider(ovProvider);
1379
1473
  }
1380
- try {
1381
- return { ok: true, regex: new RegExp(pattern, flags) };
1382
- } catch (err) {
1383
- return {
1384
- ok: false,
1385
- reason: err instanceof Error ? err.message : "invalid regex"
1386
- };
1474
+ return out;
1475
+ }
1476
+ function mergeProvider(base, overlay) {
1477
+ const models = {};
1478
+ for (const [mid, m] of Object.entries(base.models ?? {})) {
1479
+ models[mid] = { ...m };
1480
+ }
1481
+ for (const [mid, ovModel] of Object.entries(overlay.models ?? {})) {
1482
+ const existing = models[mid];
1483
+ models[mid] = existing ? mergeModel(existing, ovModel) : { ...ovModel };
1387
1484
  }
1485
+ return {
1486
+ ...base,
1487
+ // Overlay scalar fields win when explicitly provided; otherwise keep base.
1488
+ ...stripUndefined({
1489
+ id: overlay.id,
1490
+ name: overlay.name,
1491
+ npm: overlay.npm,
1492
+ api: overlay.api,
1493
+ env: overlay.env,
1494
+ doc: overlay.doc
1495
+ }),
1496
+ models
1497
+ };
1388
1498
  }
1389
- var GLOB_CHARS = /* @__PURE__ */ new Set(["*", "?", "["]);
1390
- var IS_WINDOWS = process.platform === "win32";
1391
- var SEP = IS_WINDOWS ? "\\" : "/";
1392
- function isGlob(p) {
1393
- for (const c of p) {
1394
- if (GLOB_CHARS.has(c)) return true;
1499
+ function mergeModel(base, overlay) {
1500
+ const merged = { ...base, ...overlay };
1501
+ if (base.limit || overlay.limit) {
1502
+ merged.limit = { ...base.limit, ...overlay.limit };
1395
1503
  }
1396
- return false;
1504
+ if (base.cost || overlay.cost) {
1505
+ merged.cost = { ...base.cost, ...overlay.cost };
1506
+ }
1507
+ if (base.modalities || overlay.modalities) {
1508
+ merged.modalities = { ...base.modalities, ...overlay.modalities };
1509
+ }
1510
+ return merged;
1397
1511
  }
1398
- function globToRegex(pat) {
1399
- let i = 0;
1400
- let re = "^";
1401
- while (i < pat.length) {
1402
- const c = expectDefined(pat[i]);
1403
- if (c === "*") {
1404
- if (pat[i + 1] === "*") {
1405
- re += ".*";
1406
- i += 2;
1407
- if (pat[i] === "/") i++;
1408
- } else {
1409
- re += "[^/\\\\]*";
1410
- i++;
1411
- }
1412
- } else if (c === "?") {
1413
- re += "[^/\\\\]";
1414
- i++;
1415
- } else if (c === "[") {
1416
- let cls = "[";
1417
- i++;
1418
- if (pat[i] === "!" || pat[i] === "^") {
1419
- cls += "^";
1420
- i++;
1421
- }
1422
- while (i < pat.length && pat[i] !== "]") {
1423
- const ch = pat[i] ?? "";
1424
- if (ch === "\\") cls += "\\\\";
1425
- else if (ch === "]" || ch === "^") cls += `\\${ch}`;
1426
- else cls += ch;
1427
- i++;
1428
- }
1429
- cls += "]";
1430
- re += cls;
1431
- i++;
1432
- } else {
1433
- re += c.replace(/[.+^${}()|\\]/g, "\\$&");
1434
- i++;
1435
- }
1512
+ function cloneProvider(p) {
1513
+ const models = {};
1514
+ for (const [mid, m] of Object.entries(p.models ?? {})) {
1515
+ models[mid] = { ...m };
1436
1516
  }
1437
- return new RegExp(re + "$");
1517
+ return { ...p, models };
1438
1518
  }
1439
- function baseDir(pat) {
1440
- let i = pat.length - 1;
1441
- while (i >= 0 && !GLOB_CHARS.has(expectDefined(pat[i])) && pat[i] !== SEP && pat[i] !== "/") i--;
1442
- const cut = i >= 0 ? pat.lastIndexOf(SEP, i) : pat.lastIndexOf("/", i);
1443
- return cut < 0 ? "." : pat.slice(0, cut);
1519
+ function stripUndefined(obj) {
1520
+ const out = {};
1521
+ for (const [k, v] of Object.entries(obj)) {
1522
+ if (v !== void 0) out[k] = v;
1523
+ }
1524
+ return out;
1444
1525
  }
1445
- async function expandGlob(pattern) {
1446
- if (!isGlob(pattern)) return [pattern];
1447
- const results = /* @__PURE__ */ new Set();
1448
- const abs = isAbsolute(pattern);
1449
- const base = abs ? baseDir(pattern) : baseDir(pattern);
1450
- const relPat = base === "." ? pattern : pattern.slice(base.length + 1);
1451
- async function walk2(dir, pat) {
1452
- let entries;
1453
- try {
1454
- entries = await fs.readdir(dir);
1455
- } catch {
1456
- return;
1457
- }
1458
- const firstGlob = pat.search(/[*?[[]/);
1459
- if (firstGlob < 0) {
1460
- const re = globToRegex(pat);
1461
- for (const e of entries) {
1462
- if (re.test(e)) {
1463
- const full = `${dir}${SEP}${e}`;
1464
- results.add(abs ? resolve(full) : full);
1526
+
1527
+ // src/utils/message-invariants.ts
1528
+ function repairToolUseAdjacency(messages) {
1529
+ const removedToolUses = [];
1530
+ const removedToolResults = [];
1531
+ let removedMessages = 0;
1532
+ let changed = false;
1533
+ const out = [];
1534
+ for (let i = 0; i < messages.length; i++) {
1535
+ const original = expectDefined(messages[i]);
1536
+ let msg = original;
1537
+ if (hasToolUse(msg)) {
1538
+ const nextIds = toolResultIds(messages[i + 1]);
1539
+ const filtered = mapContent(msg, (blocks) => {
1540
+ const next = [];
1541
+ for (const block of blocks) {
1542
+ if (block.type === "tool_use" && !nextIds.has(block.id)) {
1543
+ removedToolUses.push(block.id);
1544
+ changed = true;
1545
+ continue;
1546
+ }
1547
+ next.push(block);
1465
1548
  }
1466
- }
1467
- return;
1549
+ return next;
1550
+ });
1551
+ msg = filtered ?? msg;
1468
1552
  }
1469
- const before = pat.slice(0, firstGlob);
1470
- const rest = pat.slice(firstGlob);
1471
- if (before.endsWith("**")) {
1472
- await walk2(dir, rest);
1473
- for (const e of entries) {
1474
- const full = `${dir}${SEP}${e}`;
1475
- try {
1476
- const stat3 = await fs.stat(full);
1477
- if (stat3.isDirectory()) await walk2(full, rest);
1478
- } catch {
1553
+ if (hasToolResult(msg)) {
1554
+ const allowed = toolUseIds(out[out.length - 1]);
1555
+ const filtered = mapContent(msg, (blocks) => {
1556
+ const next = [];
1557
+ for (const block of blocks) {
1558
+ if (block.type === "tool_result" && !allowed.has(block.tool_use_id)) {
1559
+ removedToolResults.push(block.tool_use_id);
1560
+ changed = true;
1561
+ continue;
1562
+ }
1563
+ next.push(block);
1479
1564
  }
1565
+ return next;
1566
+ });
1567
+ msg = filtered ?? msg;
1568
+ }
1569
+ if (isEmptyMessage(msg)) {
1570
+ removedMessages++;
1571
+ changed = true;
1572
+ continue;
1573
+ }
1574
+ out.push(msg);
1575
+ }
1576
+ return {
1577
+ messages: changed ? out : messages,
1578
+ report: { changed, removedToolUses, removedToolResults, removedMessages }
1579
+ };
1580
+ }
1581
+ function hasToolUse(msg) {
1582
+ return contentBlocks(msg).some((b) => b.type === "tool_use");
1583
+ }
1584
+ function hasToolResult(msg) {
1585
+ return contentBlocks(msg).some((b) => b.type === "tool_result");
1586
+ }
1587
+ function toolUseIds(msg) {
1588
+ const ids = /* @__PURE__ */ new Set();
1589
+ if (!msg || msg.role !== "assistant") return ids;
1590
+ for (const block of contentBlocks(msg)) {
1591
+ if (block.type === "tool_use") ids.add(block.id);
1592
+ }
1593
+ return ids;
1594
+ }
1595
+ function toolResultIds(msg) {
1596
+ const ids = /* @__PURE__ */ new Set();
1597
+ if (!msg || msg.role !== "user") return ids;
1598
+ for (const block of contentBlocks(msg)) {
1599
+ if (block.type === "tool_result") ids.add(block.tool_use_id);
1600
+ }
1601
+ return ids;
1602
+ }
1603
+ function contentBlocks(msg) {
1604
+ return msg && Array.isArray(msg.content) ? msg.content : [];
1605
+ }
1606
+ function mapContent(msg, fn) {
1607
+ if (!Array.isArray(msg.content)) return msg;
1608
+ const next = fn(msg.content);
1609
+ if (next.length === msg.content.length && next.every((b, idx) => b === msg.content[idx])) {
1610
+ return msg;
1611
+ }
1612
+ return { ...msg, content: next };
1613
+ }
1614
+ function isEmptyMessage(msg) {
1615
+ if (typeof msg.content === "string") return msg.content.trim().length === 0;
1616
+ return msg.content.length === 0;
1617
+ }
1618
+
1619
+ // src/utils/newline-normalize.ts
1620
+ function detectNewlineStyle(text) {
1621
+ let lf = 0;
1622
+ let crlf = 0;
1623
+ let cr = 0;
1624
+ for (let i = 0; i < text.length; i++) {
1625
+ const c = text.charCodeAt(i);
1626
+ if (c === 13) {
1627
+ if (text.charCodeAt(i + 1) === 10) {
1628
+ crlf++;
1629
+ i++;
1630
+ } else {
1631
+ cr++;
1480
1632
  }
1481
- } else if (before === "") {
1482
- const re = globToRegex(rest);
1483
- for (const e of entries) {
1484
- if (re.test(e)) {
1485
- const full = `${dir}${SEP}${e}`;
1486
- results.add(abs ? resolve(full) : full);
1487
- }
1633
+ } else if (c === 10) {
1634
+ lf++;
1635
+ }
1636
+ }
1637
+ if (crlf > lf && crlf > cr) return "crlf";
1638
+ if (cr > lf && cr > crlf) return "cr";
1639
+ return "lf";
1640
+ }
1641
+ function toStyle(text, style) {
1642
+ const normalized = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
1643
+ if (style === "lf") return normalized;
1644
+ if (style === "crlf") return normalized.replace(/\n/g, "\r\n");
1645
+ return normalized.replace(/\n/g, "\r");
1646
+ }
1647
+ function normalizeToLf(text) {
1648
+ return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
1649
+ }
1650
+
1651
+ // src/utils/regex-guard.ts
1652
+ var MAX_PATTERN_LEN = 512;
1653
+ var DANGEROUS_PATTERNS = [
1654
+ /(\([^)]*[+*][^)]*\))[+*]/,
1655
+ // (a+)+, (.*)+, etc
1656
+ /(\(\?:[^)]*[+*][^)]*\))[+*]/
1657
+ // same, with non-capturing group
1658
+ ];
1659
+ function compileUserRegex(pattern, flags) {
1660
+ if (typeof pattern !== "string") {
1661
+ return { ok: false, reason: "pattern must be a string" };
1662
+ }
1663
+ if (pattern.length === 0) {
1664
+ return { ok: false, reason: "pattern is empty" };
1665
+ }
1666
+ if (pattern.length > MAX_PATTERN_LEN) {
1667
+ return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
1668
+ }
1669
+ for (const rx of DANGEROUS_PATTERNS) {
1670
+ if (rx.test(pattern)) {
1671
+ return {
1672
+ ok: false,
1673
+ reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
1674
+ };
1675
+ }
1676
+ }
1677
+ try {
1678
+ return { ok: true, regex: new RegExp(pattern, flags) };
1679
+ } catch (err) {
1680
+ return {
1681
+ ok: false,
1682
+ reason: err instanceof Error ? err.message : "invalid regex"
1683
+ };
1684
+ }
1685
+ }
1686
+
1687
+ // src/utils/safe-json.ts
1688
+ function safeParse(input, maxBytes = 5e6) {
1689
+ if (input.length > maxBytes) {
1690
+ return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };
1691
+ }
1692
+ try {
1693
+ return { ok: true, value: JSON.parse(input) };
1694
+ } catch (err) {
1695
+ return {
1696
+ ok: false,
1697
+ error: toErrorMessage(err)
1698
+ };
1699
+ }
1700
+ }
1701
+ function safeStringify(value, pretty = false) {
1702
+ const seen = /* @__PURE__ */ new WeakSet();
1703
+ const replacer = (_k, v) => {
1704
+ if (typeof v === "bigint") return v.toString();
1705
+ if (v instanceof Error) {
1706
+ return { name: v.name, message: v.message, stack: v.stack };
1707
+ }
1708
+ if (typeof v === "object" && v !== null) {
1709
+ if (seen.has(v)) return "[Circular]";
1710
+ seen.add(v);
1711
+ }
1712
+ return v;
1713
+ };
1714
+ try {
1715
+ return JSON.stringify(value, replacer, pretty ? 2 : void 0) ?? "null";
1716
+ } catch (err) {
1717
+ return JSON.stringify({
1718
+ __serialization_error: toErrorMessage(err)
1719
+ });
1720
+ }
1721
+ }
1722
+ function sanitizeJsonString(s) {
1723
+ let out = s.trim();
1724
+ out = stripSingleLineComments(out);
1725
+ out = out.replace(/,(\s*[}\]])/g, "$1");
1726
+ out = escapeControlCharsInStrings(out);
1727
+ try {
1728
+ JSON.parse(out);
1729
+ return out;
1730
+ } catch {
1731
+ return null;
1732
+ }
1733
+ }
1734
+ function escapeControlCharsInStrings(s) {
1735
+ let inString = false;
1736
+ let out = "";
1737
+ for (let i = 0; i < s.length; i++) {
1738
+ const c = s.charAt(i);
1739
+ if (c === '"' && (i === 0 || s[i - 1] !== "\\")) {
1740
+ inString = !inString;
1741
+ out += c;
1742
+ continue;
1743
+ }
1744
+ const code = c.charCodeAt(0);
1745
+ if (inString && code < 32) {
1746
+ switch (c) {
1747
+ case "\n":
1748
+ out += "\\n";
1749
+ break;
1750
+ case "\r":
1751
+ out += "\\r";
1752
+ break;
1753
+ case " ":
1754
+ out += "\\t";
1755
+ break;
1756
+ case "\b":
1757
+ out += "\\b";
1758
+ break;
1759
+ case "\f":
1760
+ out += "\\f";
1761
+ break;
1762
+ default:
1763
+ out += `\\u${code.toString(16).padStart(4, "0")}`;
1488
1764
  }
1765
+ continue;
1766
+ }
1767
+ out += c;
1768
+ }
1769
+ return out;
1770
+ }
1771
+ function stripSingleLineComments(s) {
1772
+ let inString = false;
1773
+ const chars = [];
1774
+ let i = 0;
1775
+ while (i < s.length) {
1776
+ const c = s.charAt(i);
1777
+ if (c === '"' && (i === 0 || s.charAt(i - 1) !== "\\")) {
1778
+ inString = !inString;
1779
+ chars.push(c);
1780
+ } else if (c === "/" && s.charAt(i + 1) === "/" && !inString) {
1781
+ while (i < s.length && s.charAt(i) !== "\n") i++;
1489
1782
  } else {
1490
- const seg = before.replace(/[*?[\]]/g, "").replace(/\/$/, "");
1491
- if (entries.includes(seg)) {
1492
- const full = `${dir}${SEP}${seg}`;
1493
- try {
1494
- const stat3 = await fs.stat(full);
1495
- if (stat3.isDirectory()) await walk2(full, rest);
1496
- } catch {
1497
- }
1498
- }
1783
+ chars.push(c);
1784
+ }
1785
+ i++;
1786
+ }
1787
+ return chars.join("");
1788
+ }
1789
+
1790
+ // src/utils/sleep.ts
1791
+ function sleep(ms) {
1792
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
1793
+ }
1794
+
1795
+ // src/utils/string.ts
1796
+ function truncate(s, max) {
1797
+ return s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
1798
+ }
1799
+
1800
+ // src/utils/task-format.ts
1801
+ function computeTaskItemProgress(tasks) {
1802
+ let completed = 0;
1803
+ let pending = 0;
1804
+ let inProgress = 0;
1805
+ let blocked = 0;
1806
+ let failed = 0;
1807
+ let review = 0;
1808
+ let estimatedHours = 0;
1809
+ const actualHours = 0;
1810
+ for (const t of tasks) {
1811
+ switch (t.status) {
1812
+ case "completed":
1813
+ completed++;
1814
+ break;
1815
+ case "pending":
1816
+ pending++;
1817
+ break;
1818
+ case "in_progress":
1819
+ inProgress++;
1820
+ break;
1821
+ case "blocked":
1822
+ blocked++;
1823
+ break;
1824
+ case "failed":
1825
+ failed++;
1826
+ break;
1827
+ case "review":
1828
+ review++;
1829
+ break;
1830
+ }
1831
+ estimatedHours += t.estimateHours ?? 0;
1832
+ }
1833
+ return {
1834
+ total: tasks.length,
1835
+ pending,
1836
+ inProgress,
1837
+ blocked,
1838
+ failed,
1839
+ review,
1840
+ completed,
1841
+ percentComplete: tasks.length > 0 ? Math.round(completed / tasks.length * 100) : 0,
1842
+ estimatedHours,
1843
+ actualHours
1844
+ };
1845
+ }
1846
+ var STATUS_ICON = {
1847
+ pending: "\u25CB",
1848
+ in_progress: "\u25D0",
1849
+ blocked: "\u2298",
1850
+ failed: "\u2717",
1851
+ review: "\u25D1",
1852
+ completed: "\u25CF"
1853
+ };
1854
+ var PRIORITY_ICON = {
1855
+ critical: "\u{1F534}",
1856
+ high: "\u{1F7E0}",
1857
+ medium: "\u{1F7E1}",
1858
+ low: "\u{1F7E2}"
1859
+ };
1860
+ var TYPE_ICON = {
1861
+ feature: "\u26A1",
1862
+ bugfix: "\u{1F41B}",
1863
+ refactor: "\u267B\uFE0F",
1864
+ docs: "\u{1F4DD}",
1865
+ test: "\u{1F9EA}",
1866
+ chore: "\u{1F527}"
1867
+ };
1868
+ function formatTaskProgress(tasks) {
1869
+ const p = computeTaskItemProgress(tasks);
1870
+ if (p.total === 0) return "No tasks.";
1871
+ const barWidth = 24;
1872
+ const filled = Math.round(p.percentComplete / 100 * barWidth);
1873
+ const empty = barWidth - filled;
1874
+ const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
1875
+ return [
1876
+ `${color.bold("Tasks")} [${bar}] ${p.percentComplete}%`,
1877
+ ` ${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`,
1878
+ p.estimatedHours > 0 ? ` ${color.dim(`est. ${p.estimatedHours}h`)}` : ""
1879
+ ].filter(Boolean).join("\n");
1880
+ }
1881
+ function formatTaskList(tasks) {
1882
+ if (tasks.length === 0) return "No tasks.";
1883
+ const order = ["in_progress", "blocked", "review", "pending", "failed", "completed"];
1884
+ const groups = /* @__PURE__ */ new Map();
1885
+ for (const t of tasks) {
1886
+ const list = groups.get(t.status) ?? [];
1887
+ list.push(t);
1888
+ groups.set(t.status, list);
1889
+ }
1890
+ const lines = [];
1891
+ lines.push(color.dim(`Tasks (${tasks.length} total):`));
1892
+ for (const status of order) {
1893
+ const group = groups.get(status);
1894
+ if (!group || group.length === 0) continue;
1895
+ const icon = STATUS_ICON[status];
1896
+ lines.push(` ${icon} ${status.toUpperCase()} (${group.length})`);
1897
+ for (const t of group) {
1898
+ const prio = PRIORITY_ICON[t.priority];
1899
+ const type = TYPE_ICON[t.type];
1900
+ const deps = t.dependsOn && t.dependsOn.length > 0 ? ` ${color.dim("\u2190")} ${color.dim(t.dependsOn.map((d) => d.slice(0, 8)).join(", "))}` : "";
1901
+ const who = t.assignee ? ` ${color.dim(`@${t.assignee}`)}` : "";
1902
+ const hrs = t.estimateHours ? ` ${color.dim(`${t.estimateHours}h`)}` : "";
1903
+ lines.push(` ${type} ${prio} ${t.title}${deps}${who}${hrs}`);
1499
1904
  }
1500
1905
  }
1501
- await walk2(base === "." ? "." : base, relPat);
1502
- return [...results];
1906
+ return lines.join("\n");
1503
1907
  }
1504
1908
 
1505
- // src/utils/json-repair.ts
1506
- function completePartialObject(s) {
1507
- if (!s.trim().startsWith("{")) return s;
1508
- if (tryParse(s).ok) return s;
1509
- return repairTruncated(s);
1909
+ // src/utils/todos-format.ts
1910
+ function formatTodosList(todos) {
1911
+ if (todos.length === 0) return "No todos.";
1912
+ const lines = [];
1913
+ const done = todos.filter((t) => t.status === "completed").length;
1914
+ lines.push(color.dim(`Todos (${done}/${todos.length} done):`));
1915
+ todos.forEach((t, i) => {
1916
+ const mark = t.status === "completed" ? color.green("[x]") : t.status === "in_progress" ? color.yellow("[~]") : color.dim("[ ]");
1917
+ const text = t.status === "in_progress" && t.activeForm ? t.activeForm : t.content;
1918
+ const label = t.status === "completed" ? color.dim(text) : text;
1919
+ lines.push(` ${color.dim(String(i + 1).padStart(2))}. ${mark} ${label}`);
1920
+ });
1921
+ return lines.join("\n");
1510
1922
  }
1511
- function repairTruncated(s) {
1512
- const stack = [];
1513
- let inString = false;
1514
- let escaped = false;
1515
- let sawKey = false;
1516
- let prevSig = "";
1517
- let contentEnd = 0;
1518
- let stringBraceDepth = 0;
1519
- for (let i = 0; i < s.length; i++) {
1520
- const ch = expectDefined(s[i]);
1521
- if (inString) {
1522
- contentEnd = i + 1;
1523
- if (escaped) {
1524
- escaped = false;
1525
- continue;
1526
- }
1527
- if (ch === "\\") {
1528
- escaped = true;
1529
- continue;
1530
- }
1531
- if (ch === '"') {
1532
- inString = false;
1533
- prevSig = '"';
1534
- stringBraceDepth = 0;
1535
- continue;
1536
- }
1537
- if (ch === "{") stringBraceDepth++;
1538
- else if (ch === "}" && stringBraceDepth > 0) stringBraceDepth--;
1539
- continue;
1540
- }
1541
- if (ch === " " || ch === " " || ch === "\n" || ch === "\r") continue;
1542
- contentEnd = i + 1;
1543
- if (ch === '"') {
1544
- inString = true;
1545
- sawKey = true;
1546
- stringBraceDepth = 0;
1547
- prevSig = '"';
1548
- } else if (ch === "{" || ch === "[") {
1549
- stack.push(ch);
1550
- prevSig = ch;
1551
- } else if (ch === "}" || ch === "]") {
1552
- stack.pop();
1553
- prevSig = ch;
1554
- } else {
1555
- prevSig = ch;
1923
+
1924
+ // src/utils/tool-subject.ts
1925
+ var GLOB_METACHARACTERS = /[*?[\]]/g;
1926
+ function escapeGlobSubject(value) {
1927
+ return value.replace(GLOB_METACHARACTERS, (char) => `\\${char}`);
1928
+ }
1929
+ function normalizePathSubject(value) {
1930
+ return escapeGlobSubject(value.replace(/\\/g, "/"));
1931
+ }
1932
+ function isPathSubjectKey(subjectKey) {
1933
+ return subjectKey === "path" || subjectKey === "file" || subjectKey === "files";
1934
+ }
1935
+ function subjectForToolInput(toolName, input, subjectKey) {
1936
+ if (!input || typeof input !== "object") return void 0;
1937
+ const obj = input;
1938
+ if (subjectKey) {
1939
+ const value = obj[subjectKey];
1940
+ if (typeof value === "string") {
1941
+ return isPathSubjectKey(subjectKey) ? normalizePathSubject(value) : escapeGlobSubject(value);
1556
1942
  }
1557
1943
  }
1558
- if (!sawKey && !inString) return s;
1559
- let result = s.slice(0, contentEnd);
1560
- if (inString) {
1561
- if (escaped) {
1562
- result = result.slice(0, -1);
1563
- } else if (endsWithInvalidEscape(result)) {
1564
- result = result.slice(0, -2);
1565
- }
1566
- if (stringBraceDepth > 0) result += "}".repeat(stringBraceDepth);
1567
- result += '"';
1568
- } else if (prevSig === ":") {
1569
- result += "null";
1944
+ if (toolName === "bash" && typeof obj.command === "string") {
1945
+ return escapeGlobSubject(obj.command);
1570
1946
  }
1571
- for (let k = stack.length - 1; k >= 0; k--) {
1572
- result += stack[k] === "{" ? "}" : "]";
1947
+ if (typeof obj.path === "string") {
1948
+ return normalizePathSubject(obj.path);
1573
1949
  }
1574
- if (!tryParse(result).ok) {
1575
- const patched = result.replace(/:(\s*)([}\]])/g, ":null$2");
1576
- if (tryParse(patched).ok) result = patched;
1950
+ if (typeof obj.url === "string") {
1951
+ return escapeGlobSubject(obj.url);
1577
1952
  }
1578
- return result;
1953
+ if (typeof obj.name === "string") {
1954
+ return escapeGlobSubject(obj.name);
1955
+ }
1956
+ return void 0;
1579
1957
  }
1580
- var VALID_ESCAPE = /* @__PURE__ */ new Set(['"', "\\", "/", "b", "f", "n", "r", "t", "u"]);
1581
- function endsWithInvalidEscape(str) {
1582
- const last = str[str.length - 1];
1583
- if (str[str.length - 2] !== "\\" || last === void 0) return false;
1584
- if (VALID_ESCAPE.has(last)) return false;
1585
- let backslashes = 0;
1586
- for (let k = str.length - 2; k >= 0 && str[k] === "\\"; k--) backslashes++;
1587
- return backslashes % 2 === 1;
1958
+
1959
+ // src/utils/tool-wire-compact.ts
1960
+ var TOOL_DESCRIPTION_MAX_CHARS = 640;
1961
+ var SCHEMA_DESCRIPTION_MAX_CHARS = 180;
1962
+ var compactCache = /* @__PURE__ */ new WeakMap();
1963
+ function compactToolDefinitionForWire(tool, opts = {}) {
1964
+ const useDefaultOptions = opts.descriptionMaxChars === void 0 && opts.schemaDescriptionMaxChars === void 0;
1965
+ if (useDefaultOptions && typeof tool === "object" && tool !== null) {
1966
+ const cached = compactCache.get(tool);
1967
+ if (cached) return cached;
1968
+ }
1969
+ const compact = {
1970
+ name: tool.name,
1971
+ description: compactDescription(
1972
+ tool.description ?? "",
1973
+ opts.descriptionMaxChars ?? TOOL_DESCRIPTION_MAX_CHARS
1974
+ ),
1975
+ inputSchema: compactSchemaDescriptions(
1976
+ tool.inputSchema,
1977
+ opts.schemaDescriptionMaxChars ?? SCHEMA_DESCRIPTION_MAX_CHARS
1978
+ )
1979
+ };
1980
+ if (useDefaultOptions && typeof tool === "object" && tool !== null) {
1981
+ compactCache.set(tool, compact);
1982
+ }
1983
+ return compact;
1588
1984
  }
1589
- function tryParse(s) {
1590
- try {
1591
- return { ok: true, value: JSON.parse(s) };
1592
- } catch {
1593
- return { ok: false };
1985
+ function compactSchemaDescriptions(schema, maxDescriptionChars = SCHEMA_DESCRIPTION_MAX_CHARS) {
1986
+ const compact = compactSchemaNode(schema, maxDescriptionChars);
1987
+ return isRecord(compact) ? compact : { type: "object", properties: {} };
1988
+ }
1989
+ function compactSchemaNode(node, maxDescriptionChars) {
1990
+ if (Array.isArray(node)) {
1991
+ return node.map((item) => compactSchemaNode(item, maxDescriptionChars));
1594
1992
  }
1993
+ if (!isRecord(node)) return node;
1994
+ const out = {};
1995
+ for (const [key, value] of Object.entries(node)) {
1996
+ if (key === "description" && typeof value === "string") {
1997
+ out[key] = compactDescription(value, maxDescriptionChars);
1998
+ } else {
1999
+ out[key] = compactSchemaNode(value, maxDescriptionChars);
2000
+ }
2001
+ }
2002
+ return out;
2003
+ }
2004
+ function compactDescription(text, maxChars) {
2005
+ const normalized = text.replace(/\s+/g, " ").trim();
2006
+ if (normalized.length <= maxChars) return normalized;
2007
+ if (maxChars <= 20) return normalized.slice(0, maxChars);
2008
+ const hardLimit = maxChars - 12;
2009
+ const boundary = findSemanticBoundary(normalized, hardLimit);
2010
+ const head = normalized.slice(0, boundary > 0 ? boundary : hardLimit).trimEnd();
2011
+ return `${head} ...`;
2012
+ }
2013
+ function findSemanticBoundary(text, limit) {
2014
+ const punctuation = Math.max(
2015
+ text.lastIndexOf(". ", limit),
2016
+ text.lastIndexOf("; ", limit),
2017
+ text.lastIndexOf(": ", limit)
2018
+ );
2019
+ if (punctuation >= Math.floor(limit * 0.45)) return punctuation + 1;
2020
+ const comma = text.lastIndexOf(", ", limit);
2021
+ if (comma >= Math.floor(limit * 0.6)) return comma + 1;
2022
+ const space = text.lastIndexOf(" ", limit);
2023
+ return space >= Math.floor(limit * 0.6) ? space : limit;
2024
+ }
2025
+ function isRecord(value) {
2026
+ return !!value && typeof value === "object" && !Array.isArray(value);
1595
2027
  }
1596
2028
 
1597
- // src/utils/merge-models-payload.ts
1598
- function mergeModelsPayload(base, overlay) {
1599
- const out = {};
1600
- for (const [id, provider] of Object.entries(base)) {
1601
- out[id] = cloneProvider(provider);
2029
+ // src/utils/token-estimate.ts
2030
+ var RoughTokenEstimate = (text, charsPerToken = 3.5) => Math.max(1, Math.ceil(text.length / charsPerToken));
2031
+ var CAL_ALPHA = 0.3;
2032
+ var CALIBRATION_GLOBAL_KEY = "__global__";
2033
+ var _cals = /* @__PURE__ */ new Map();
2034
+ function calState(key) {
2035
+ let state = _cals.get(key);
2036
+ if (!state) {
2037
+ state = { ratio: 1, count: 0, prevEst: 0 };
2038
+ _cals.set(key, state);
1602
2039
  }
1603
- for (const [id, ovProvider] of Object.entries(overlay)) {
1604
- const existing = out[id];
1605
- out[id] = existing ? mergeProvider(existing, ovProvider) : cloneProvider(ovProvider);
2040
+ return state;
2041
+ }
2042
+ var MIN_SAMPLES_FOR_CALIBRATION = 3;
2043
+ var ESTIMATE_CACHE = /* @__PURE__ */ new Map();
2044
+ var ESTIMATE_CACHE_MAX_SIZE = 1e4;
2045
+ function getCachedEstimate(key, compute) {
2046
+ const existing = ESTIMATE_CACHE.get(key);
2047
+ if (existing !== void 0) return existing;
2048
+ if (ESTIMATE_CACHE.size >= ESTIMATE_CACHE_MAX_SIZE) {
2049
+ for (const k of ESTIMATE_CACHE.keys()) {
2050
+ if (ESTIMATE_CACHE.size <= Math.floor(ESTIMATE_CACHE_MAX_SIZE / 2)) break;
2051
+ ESTIMATE_CACHE.delete(k);
2052
+ }
1606
2053
  }
1607
- return out;
2054
+ const estimate = compute(key);
2055
+ ESTIMATE_CACHE.set(key, estimate);
2056
+ return estimate;
1608
2057
  }
1609
- function mergeProvider(base, overlay) {
1610
- const models = {};
1611
- for (const [mid, m] of Object.entries(base.models ?? {})) {
1612
- models[mid] = { ...m };
2058
+ function estimateToolInputTokens(input) {
2059
+ if (typeof input === "string") return RoughTokenEstimate(input);
2060
+ if (input === null || typeof input !== "object") {
2061
+ return RoughTokenEstimate(String(input));
1613
2062
  }
1614
- for (const [mid, ovModel] of Object.entries(overlay.models ?? {})) {
1615
- const existing = models[mid];
1616
- models[mid] = existing ? mergeModel(existing, ovModel) : { ...ovModel };
2063
+ return getCachedEstimate(JSON.stringify(input), (key) => RoughTokenEstimate(key));
2064
+ }
2065
+ function estimateToolResultTokens(content) {
2066
+ if (typeof content === "string") return RoughTokenEstimate(content);
2067
+ return getCachedEstimate(JSON.stringify(content), (key) => RoughTokenEstimate(key));
2068
+ }
2069
+ function estimateTextTokens(text) {
2070
+ return RoughTokenEstimate(text);
2071
+ }
2072
+ function computeMessageTokens(msg) {
2073
+ if (typeof msg.content === "string") return estimateTextTokens(msg.content);
2074
+ let total = 0;
2075
+ for (const b of msg.content) {
2076
+ if (b.type === "text") total += estimateTextTokens(b.text);
2077
+ else if (b.type === "tool_use") total += estimateToolInputTokens(b.input);
2078
+ else if (b.type === "tool_result") total += estimateToolResultTokens(b.content);
2079
+ else total += RoughTokenEstimate(JSON.stringify(b));
2080
+ }
2081
+ return total;
2082
+ }
2083
+ function estimateMessageTokens(messages) {
2084
+ let total = 0;
2085
+ for (const m of messages) {
2086
+ if (typeof m._estTokens === "number" && m._estTokens > 0) {
2087
+ total += m._estTokens;
2088
+ continue;
2089
+ }
2090
+ total += computeMessageTokens(m);
2091
+ }
2092
+ return total;
2093
+ }
2094
+ function estimateToolDefTokens(tool) {
2095
+ const cached = tool._estDefTokens;
2096
+ if (typeof cached === "number" && cached > 0) return cached;
2097
+ const compact = compactToolDefinitionForWire(tool);
2098
+ return RoughTokenEstimate(tool.name) + RoughTokenEstimate(compact.description) + RoughTokenEstimate(JSON.stringify(compact.inputSchema));
2099
+ }
2100
+ function estimateRequestTokens(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
2101
+ let messagesTokens = 0;
2102
+ if (typeof messages === "string") {
2103
+ messagesTokens = RoughTokenEstimate(messages);
2104
+ } else if (Array.isArray(messages)) {
2105
+ for (const m of messages) {
2106
+ if (typeof m === "object" && m !== null && "content" in m) {
2107
+ const cached = m._estTokens;
2108
+ if (typeof cached === "number" && cached > 0) {
2109
+ messagesTokens += cached;
2110
+ continue;
2111
+ }
2112
+ const content = m.content;
2113
+ if (typeof content === "string") {
2114
+ messagesTokens += RoughTokenEstimate(content);
2115
+ } else if (Array.isArray(content)) {
2116
+ for (const b of content) {
2117
+ if (typeof b === "object" && b !== null) {
2118
+ if (b.type === "text") {
2119
+ messagesTokens += RoughTokenEstimate(b.text);
2120
+ } else {
2121
+ messagesTokens += RoughTokenEstimate(JSON.stringify(b));
2122
+ }
2123
+ }
2124
+ }
2125
+ }
2126
+ }
2127
+ }
2128
+ }
2129
+ let systemTokens = 0;
2130
+ if (typeof systemPrompt === "string") {
2131
+ systemTokens = RoughTokenEstimate(systemPrompt);
2132
+ } else if (Array.isArray(systemPrompt)) {
2133
+ for (const b of systemPrompt) {
2134
+ if (typeof b === "object" && b !== null && b.type === "text") {
2135
+ systemTokens += RoughTokenEstimate(b.text);
2136
+ }
2137
+ }
2138
+ }
2139
+ let toolsTokens = 0;
2140
+ for (const t of tools) {
2141
+ toolsTokens += estimateToolDefTokens(t);
1617
2142
  }
2143
+ const total = messagesTokens + systemTokens + toolsTokens;
2144
+ calState(calibrationKey).prevEst = total;
1618
2145
  return {
1619
- ...base,
1620
- // Overlay scalar fields win when explicitly provided; otherwise keep base.
1621
- ...stripUndefined({
1622
- id: overlay.id,
1623
- name: overlay.name,
1624
- npm: overlay.npm,
1625
- api: overlay.api,
1626
- env: overlay.env,
1627
- doc: overlay.doc
1628
- }),
1629
- models
2146
+ messages: messagesTokens,
2147
+ systemPrompt: systemTokens,
2148
+ tools: toolsTokens,
2149
+ total
1630
2150
  };
1631
2151
  }
1632
- function mergeModel(base, overlay) {
1633
- const merged = { ...base, ...overlay };
1634
- if (base.limit || overlay.limit) {
1635
- merged.limit = { ...base.limit, ...overlay.limit };
1636
- }
1637
- if (base.cost || overlay.cost) {
1638
- merged.cost = { ...base.cost, ...overlay.cost };
1639
- }
1640
- if (base.modalities || overlay.modalities) {
1641
- merged.modalities = { ...base.modalities, ...overlay.modalities };
2152
+ function recordActualUsage(actualInputTokens, estimatedInputTokens, calibrationKey = CALIBRATION_GLOBAL_KEY) {
2153
+ if (actualInputTokens <= 0) return;
2154
+ const cal = calState(calibrationKey);
2155
+ const est = estimatedInputTokens ?? cal.prevEst;
2156
+ if (est <= 0) return;
2157
+ const sampleRatio = actualInputTokens / est;
2158
+ if (cal.count === 0) {
2159
+ cal.ratio = sampleRatio;
2160
+ } else {
2161
+ cal.ratio = CAL_ALPHA * sampleRatio + (1 - CAL_ALPHA) * cal.ratio;
1642
2162
  }
1643
- return merged;
2163
+ cal.ratio = Math.min(1.5, Math.max(0.5, cal.ratio));
2164
+ cal.count++;
1644
2165
  }
1645
- function cloneProvider(p) {
1646
- const models = {};
1647
- for (const [mid, m] of Object.entries(p.models ?? {})) {
1648
- models[mid] = { ...m };
2166
+ function getCalibrationState(calibrationKey = CALIBRATION_GLOBAL_KEY) {
2167
+ const cal = calState(calibrationKey);
2168
+ return {
2169
+ ratio: cal.ratio,
2170
+ count: cal.count,
2171
+ calibrated: cal.count >= MIN_SAMPLES_FOR_CALIBRATION
2172
+ };
2173
+ }
2174
+ function estimateRequestTokensCalibrated(messages, systemPrompt, tools, calibrationKey = CALIBRATION_GLOBAL_KEY) {
2175
+ const result = estimateRequestTokens(messages, systemPrompt, tools, calibrationKey);
2176
+ const cal = calState(calibrationKey);
2177
+ if (cal.count >= MIN_SAMPLES_FOR_CALIBRATION) {
2178
+ const safeRatio = Math.min(1.5, Math.max(0.5, cal.ratio));
2179
+ return {
2180
+ messages: Math.round(result.messages * safeRatio),
2181
+ systemPrompt: Math.round(result.systemPrompt * safeRatio),
2182
+ tools: Math.round(result.tools * safeRatio),
2183
+ total: Math.round(result.total * safeRatio)
2184
+ };
1649
2185
  }
1650
- return { ...p, models };
2186
+ return result;
1651
2187
  }
1652
- function stripUndefined(obj) {
1653
- const out = {};
1654
- for (const [k, v] of Object.entries(obj)) {
1655
- if (v !== void 0) out[k] = v;
2188
+ function resetCalibration(calibrationKey) {
2189
+ if (calibrationKey === void 0) {
2190
+ _cals.clear();
2191
+ return;
1656
2192
  }
1657
- return out;
2193
+ _cals.delete(calibrationKey);
1658
2194
  }
1659
2195
 
1660
- // src/utils/merge-custom-models.ts
1661
- function mergeCustomModelDefs(providerCustomModels, configModels) {
1662
- const out = {};
1663
- if (providerCustomModels) {
1664
- for (const [id, def] of Object.entries(providerCustomModels)) {
1665
- out[id] = { ...def };
2196
+ // src/utils/tool-output-serializer.ts
2197
+ var DEFAULT_LIST_LIMIT = 500;
2198
+ var LOG_ENTRY_LIMIT = 200;
2199
+ var INLINE_LIMIT = 240;
2200
+ var GREP_FILE_LIMIT = 80;
2201
+ var GREP_MATCHES_PER_FILE = 3;
2202
+ var DIFF_INLINE_LINE_LIMIT = 260;
2203
+ var DIFF_HUNK_LIMIT = 8;
2204
+ var DIFF_HUNK_CONTEXT = 14;
2205
+ function createToolOutputSerializer(opts = {}) {
2206
+ const capBytes = opts.perIterationOutputCapBytes ?? 1e5;
2207
+ function serialize(value, context = {}) {
2208
+ if (typeof value === "string") return value;
2209
+ if (value === null || value === void 0) return "";
2210
+ if (typeof value === "object") {
2211
+ if (Array.isArray(value)) return value.map((item) => serialize(item)).join("\n");
2212
+ if (context.toolName) {
2213
+ const compact = renderToolObject(context.toolName, value, context.input);
2214
+ if (compact !== void 0) return compact;
2215
+ return renderGenericToolObject(context.toolName, value);
2216
+ }
2217
+ if ("text" in value) {
2218
+ const t = value.text;
2219
+ return typeof t === "string" ? t : JSON.stringify(value, null, 2);
2220
+ }
2221
+ try {
2222
+ return JSON.stringify(value, null, 2);
2223
+ } catch {
2224
+ return String(value);
2225
+ }
1666
2226
  }
2227
+ return String(value);
1667
2228
  }
1668
- if (configModels) {
1669
- for (const [id, def] of Object.entries(configModels)) {
1670
- out[id] = { ...def };
2229
+ function enforceCap(text, remainingBudget) {
2230
+ if (remainingBudget <= 0) {
2231
+ return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
2232
+ }
2233
+ const textBytes = Buffer.byteLength(text, "utf8");
2234
+ if (textBytes <= remainingBudget) {
2235
+ return { text, newBudget: remainingBudget - textBytes };
2236
+ }
2237
+ const marker = `
2238
+ \u2026[truncated ${textBytes - remainingBudget} bytes]\u2026
2239
+ `;
2240
+ const markerBytes = Buffer.byteLength(marker, "utf8");
2241
+ const available = remainingBudget - markerBytes;
2242
+ if (available <= 0) {
2243
+ return { text: "[truncated: iteration output cap exceeded]", newBudget: 0 };
1671
2244
  }
2245
+ const half = Math.floor(available / 2);
2246
+ const first = text.slice(0, half);
2247
+ const second = text.slice(text.length - half);
2248
+ return { text: `${first}${marker}${second}`, newBudget: 0 };
1672
2249
  }
1673
- if (Object.keys(out).length === 0) return void 0;
1674
- return out;
2250
+ return { serialize, enforceCap, capBytes };
1675
2251
  }
1676
- function isPrivateIPv4(addr) {
1677
- const parts = addr.split(".").map((p) => Number.parseInt(p, 10));
1678
- if (parts.length !== 4 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255)) {
1679
- return true;
2252
+ function renderToolObject(toolName, obj, input) {
2253
+ if (toolName === "read" && typeof obj["text"] === "string") {
2254
+ return joinSections([
2255
+ renderHeader(
2256
+ `read: ${stringFromInput(input, "path") ?? stringField(obj, "path") ?? "<unknown>"}`,
2257
+ {
2258
+ offset: numberFromInput(input, "offset"),
2259
+ limit: numberFromInput(input, "limit"),
2260
+ total_lines: obj["total_lines"],
2261
+ encoding: obj["encoding"],
2262
+ truncated: obj["truncated"],
2263
+ cached: obj["cached"],
2264
+ note: obj["note"]
2265
+ }
2266
+ ),
2267
+ obj["text"]
2268
+ ]);
2269
+ }
2270
+ if (toolName === "grep" && Array.isArray(obj["matches"])) {
2271
+ const matches = stringArrayField(obj, "matches");
2272
+ return joinSections([
2273
+ renderHeader(`grep: ${stringFromInput(input, "pattern") ?? "<pattern>"}`, {
2274
+ path: stringFromInput(input, "path"),
2275
+ glob: stringFromInput(input, "glob"),
2276
+ mode: stringFromInput(input, "output_mode"),
2277
+ count: obj["count"],
2278
+ shown: matches.length,
2279
+ truncated: obj["truncated"],
2280
+ used: obj["used"]
2281
+ }),
2282
+ renderGrepMatches(matches, stringFromInput(input, "output_mode"))
2283
+ ]);
2284
+ }
2285
+ if (toolName === "patch" && Array.isArray(obj["files"])) {
2286
+ const files = stringArrayField(obj, "files");
2287
+ return joinSections([
2288
+ renderHeader("patch", {
2289
+ applied: obj["applied"],
2290
+ rejected: obj["rejected"],
2291
+ files: files.length,
2292
+ dry_run: obj["dry_run"]
2293
+ }),
2294
+ typeof obj["message"] === "string" ? `message:
2295
+ ${obj["message"]}` : void 0,
2296
+ files.length > 0 ? `files:
2297
+ ${renderStringList(files)}` : void 0
2298
+ ]);
2299
+ }
2300
+ if (toolName === "glob" && Array.isArray(obj["files"])) {
2301
+ const files = stringArrayField(obj, "files");
2302
+ return joinSections([
2303
+ renderHeader(
2304
+ `${toolName}: ${stringFromInput(input, "pattern") ?? stringFromInput(input, "files") ?? stringFromInput(input, "path") ?? ""}`.trim(),
2305
+ {
2306
+ path: stringFromInput(input, "path"),
2307
+ files: files.length,
2308
+ truncated: obj["truncated"]
2309
+ }
2310
+ ),
2311
+ renderStringList(files, "(no files)")
2312
+ ]);
2313
+ }
2314
+ if (toolName === "tree" && typeof obj["tree"] === "string") {
2315
+ return joinSections([
2316
+ renderHeader(
2317
+ `tree: ${stringField(obj, "path") ?? stringFromInput(input, "path") ?? "<cwd>"}`,
2318
+ {
2319
+ total_files: obj["total_files"],
2320
+ total_dirs: obj["total_dirs"],
2321
+ truncated: obj["truncated"]
2322
+ }
2323
+ ),
2324
+ obj["tree"]
2325
+ ]);
2326
+ }
2327
+ if (toolName === "fetch" && typeof obj["content"] === "string") {
2328
+ return joinSections([
2329
+ renderHeader(
2330
+ `fetch: ${stringField(obj, "url") ?? stringFromInput(input, "url") ?? "<url>"}`,
2331
+ {
2332
+ status: obj["status"],
2333
+ content_type: obj["content_type"]
2334
+ }
2335
+ ),
2336
+ obj["content"]
2337
+ ]);
2338
+ }
2339
+ if (toolName === "replace" && Array.isArray(obj["results"])) {
2340
+ const results = obj["results"].filter(isRecord2);
2341
+ const sections = [
2342
+ renderHeader("replace", {
2343
+ files_modified: obj["files_modified"],
2344
+ total_replacements: obj["total_replacements"],
2345
+ dry_run: obj["dry_run"]
2346
+ })
2347
+ ];
2348
+ for (const r of results.slice(0, DEFAULT_LIST_LIMIT)) {
2349
+ sections.push(
2350
+ joinSections([
2351
+ renderHeader(`file: ${stringField(r, "path") ?? "<unknown>"}`, {
2352
+ replacements: r["replacements"]
2353
+ }),
2354
+ typeof r["diff"] === "string" ? r["diff"] : void 0
2355
+ ])
2356
+ );
2357
+ }
2358
+ if (results.length > DEFAULT_LIST_LIMIT) {
2359
+ sections.push(`[serializer omitted ${results.length - DEFAULT_LIST_LIMIT} result item(s)]`);
2360
+ }
2361
+ return joinSections(sections);
2362
+ }
2363
+ if (typeof obj["diff"] === "string") {
2364
+ const diff = obj["diff"];
2365
+ return joinSections([
2366
+ renderHeader(toolName, {
2367
+ path: obj["path"],
2368
+ replacements: obj["replacements"],
2369
+ bytes_written: obj["bytes_written"],
2370
+ created: obj["created"],
2371
+ note: obj["note"],
2372
+ files: Array.isArray(obj["files"]) ? obj["files"].length : void 0,
2373
+ truncated: obj["truncated"],
2374
+ mode: obj["mode"]
2375
+ }),
2376
+ compactDiff(diff)
2377
+ ]);
2378
+ }
2379
+ if (toolName === "test" && typeof obj["output"] === "string") {
2380
+ return renderTestOutput(obj, input);
2381
+ }
2382
+ if ((toolName === "typecheck" || toolName === "lint" || toolName === "format") && typeof obj["output"] === "string") {
2383
+ return renderVerifierOutput(toolName, obj, input);
2384
+ }
2385
+ if (hasCommandOutputShape(obj)) {
2386
+ return renderCommandOutput(toolName, obj, input);
2387
+ }
2388
+ if (toolName === "json" && typeof obj["formatted"] === "string") {
2389
+ return joinSections([
2390
+ renderHeader("json", {
2391
+ type: obj["type"],
2392
+ keys: Array.isArray(obj["keys"]) ? obj["keys"].length : void 0,
2393
+ query: stringFromInput(input, "query"),
2394
+ error: obj["error"]
2395
+ }),
2396
+ obj["formatted"]
2397
+ ]);
2398
+ }
2399
+ if (toolName === "logs" && Array.isArray(obj["entries"])) {
2400
+ const entries = obj["entries"].filter(isRecord2);
2401
+ const lines = entries.slice(0, LOG_ENTRY_LIMIT).map((entry) => {
2402
+ const ts = stringField(entry, "timestamp") ?? "";
2403
+ const level = stringField(entry, "level") ?? "info";
2404
+ const message = stringField(entry, "message") ?? "";
2405
+ const source = stringField(entry, "source");
2406
+ return [ts, level, source, message].filter(Boolean).join(" ");
2407
+ });
2408
+ if (entries.length > LOG_ENTRY_LIMIT) {
2409
+ lines.push(`[serializer omitted ${entries.length - LOG_ENTRY_LIMIT} log entry item(s)]`);
2410
+ }
2411
+ return joinSections([
2412
+ renderHeader(`logs: ${stringField(obj, "source") ?? "<source>"}`, {
2413
+ total: obj["total"],
2414
+ shown: Math.min(entries.length, LOG_ENTRY_LIMIT),
2415
+ truncated: obj["truncated"],
2416
+ stream_mode: obj["stream_mode"]
2417
+ }),
2418
+ lines.length > 0 ? lines.join("\n") : "(no log entries)"
2419
+ ]);
2420
+ }
2421
+ if (toolName === "audit" && Array.isArray(obj["vulnerabilities"])) {
2422
+ const vulns = obj["vulnerabilities"].filter(isRecord2);
2423
+ const lines = vulns.slice(0, DEFAULT_LIST_LIMIT).map((v) => {
2424
+ const severity = stringField(v, "severity") ?? "unknown";
2425
+ const pkg = stringField(v, "package") ?? "<package>";
2426
+ const title = stringField(v, "title") ?? "";
2427
+ const url = stringField(v, "url");
2428
+ return [severity, pkg, title, url].filter(Boolean).join(" | ");
2429
+ });
2430
+ if (vulns.length > DEFAULT_LIST_LIMIT) {
2431
+ lines.push(`[serializer omitted ${vulns.length - DEFAULT_LIST_LIMIT} vulnerability item(s)]`);
2432
+ }
2433
+ return joinSections([
2434
+ renderHeader("audit", {
2435
+ exit_code: obj["exit_code"],
2436
+ total: obj["total"],
2437
+ summary: obj["summary"],
2438
+ truncated: obj["truncated"]
2439
+ }),
2440
+ lines.length > 0 ? lines.join("\n") : stringField(obj, "output")
2441
+ ]);
2442
+ }
2443
+ if (toolName === "outdated" && Array.isArray(obj["packages"])) {
2444
+ const packages = obj["packages"].filter(isRecord2);
2445
+ const lines = packages.slice(0, DEFAULT_LIST_LIMIT).map(
2446
+ (p) => [
2447
+ stringField(p, "name") ?? "<package>",
2448
+ `current=${stringField(p, "current") ?? "unknown"}`,
2449
+ `wanted=${stringField(p, "wanted") ?? "unknown"}`,
2450
+ `latest=${stringField(p, "latest") ?? "unknown"}`,
2451
+ stringField(p, "type")
2452
+ ].filter(Boolean).join(" | ")
2453
+ );
2454
+ if (packages.length > DEFAULT_LIST_LIMIT) {
2455
+ lines.push(`[serializer omitted ${packages.length - DEFAULT_LIST_LIMIT} package item(s)]`);
2456
+ }
2457
+ return joinSections([
2458
+ renderHeader("outdated", {
2459
+ exit_code: obj["exit_code"],
2460
+ total: obj["total"],
2461
+ truncated: obj["truncated"]
2462
+ }),
2463
+ lines.length > 0 ? lines.join("\n") : stringField(obj, "output")
2464
+ ]);
2465
+ }
2466
+ return void 0;
2467
+ }
2468
+ function renderTestOutput(obj, input) {
2469
+ const exitCode = numberField(obj, "exit_code") ?? 0;
2470
+ const failed = numberField(obj, "failed") ?? 0;
2471
+ const output = stringField(obj, "output") ?? "";
2472
+ const header = renderHeader(`test: ${stringField(obj, "runner") ?? "runner"}`, {
2473
+ exit_code: obj["exit_code"],
2474
+ tests_run: obj["tests_run"],
2475
+ passed: obj["passed"],
2476
+ failed: obj["failed"],
2477
+ duration_ms: obj["duration_ms"],
2478
+ truncated: obj["truncated"],
2479
+ files: inputListSummary(input, "files"),
2480
+ grep: stringFromInput(input, "grep")
2481
+ });
2482
+ if (exitCode === 0 && failed === 0) {
2483
+ return joinSections([
2484
+ header,
2485
+ joinSections([
2486
+ "report:",
2487
+ `status=passed`,
2488
+ `tests_run=${obj["tests_run"] ?? 0}`,
2489
+ `passed=${obj["passed"] ?? 0}`,
2490
+ `failed=${obj["failed"] ?? 0}`,
2491
+ `duration_ms=${obj["duration_ms"] ?? 0}`,
2492
+ extractSpoolNote(output)
2493
+ ])
2494
+ ]);
2495
+ }
2496
+ return joinSections([
2497
+ header,
2498
+ `error_context:
2499
+ ${compactFailureOutput(output || "(no runner output)")}`
2500
+ ]);
2501
+ }
2502
+ function renderVerifierOutput(toolName, obj, input) {
2503
+ const exitCode = numberField(obj, "exit_code") ?? 0;
2504
+ const errors = numberField(obj, "errors") ?? 0;
2505
+ const warnings = numberField(obj, "warnings") ?? 0;
2506
+ const output = stringField(obj, "output") ?? "";
2507
+ const changed = numberField(obj, "files_changed") ?? 0;
2508
+ const header = renderHeader(toolName, {
2509
+ exit_code: obj["exit_code"],
2510
+ errors: obj["errors"],
2511
+ warnings: obj["warnings"],
2512
+ files_checked: obj["files_checked"],
2513
+ files_changed: obj["files_changed"],
2514
+ fix_applied: obj["fix_applied"],
2515
+ fixer: obj["fixer"],
2516
+ linter: obj["linter"],
2517
+ project: obj["project"],
2518
+ truncated: obj["truncated"],
2519
+ files: inputListSummary(input, "files"),
2520
+ cwd: stringFromInput(input, "cwd")
2521
+ });
2522
+ if (exitCode === 0 && errors === 0 && (toolName !== "format" || changed === 0)) {
2523
+ return joinSections([
2524
+ header,
2525
+ joinSections([
2526
+ "report:",
2527
+ "status=passed",
2528
+ `errors=${errors}`,
2529
+ `warnings=${warnings}`,
2530
+ toolName === "format" ? `files_changed=${changed}` : void 0,
2531
+ extractSpoolNote(output)
2532
+ ])
2533
+ ]);
2534
+ }
2535
+ if (exitCode === 0 && toolName === "format") {
2536
+ return joinSections([
2537
+ header,
2538
+ joinSections([
2539
+ "report:",
2540
+ "status=changed",
2541
+ `files_changed=${changed}`,
2542
+ extractSpoolNote(output)
2543
+ ])
2544
+ ]);
2545
+ }
2546
+ return joinSections([
2547
+ header,
2548
+ `error_context:
2549
+ ${compactFailureOutput(output || "(no verifier output)")}`
2550
+ ]);
2551
+ }
2552
+ function renderGrepMatches(matches, mode) {
2553
+ if (matches.length === 0) return "(no matches)";
2554
+ if (mode === "files_with_matches") return renderStringList(matches, "(no files)");
2555
+ if (mode === "count") return renderStringList(matches, "(no counts)");
2556
+ const groups = /* @__PURE__ */ new Map();
2557
+ const passthrough = [];
2558
+ for (const match of matches) {
2559
+ const parsed = parseGrepContentLine(match);
2560
+ if (!parsed) {
2561
+ passthrough.push(match);
2562
+ continue;
2563
+ }
2564
+ const list = groups.get(parsed.file) ?? [];
2565
+ list.push(`${parsed.line}:${parsed.text}`);
2566
+ groups.set(parsed.file, list);
2567
+ }
2568
+ if (groups.size === 0) return renderStringList(matches, "(no matches)");
2569
+ const sections = [];
2570
+ let fileIndex = 0;
2571
+ for (const [file, lines] of groups) {
2572
+ fileIndex++;
2573
+ if (fileIndex > GREP_FILE_LIMIT) break;
2574
+ const shown = lines.slice(0, GREP_MATCHES_PER_FILE);
2575
+ sections.push(
2576
+ `${file} (${lines.length} match(es), showing ${shown.length})
2577
+ ${shown.join("\n")}`
2578
+ );
1680
2579
  }
1681
- const [a, b, c] = parts;
1682
- if (a === 0) return true;
1683
- if (a === 10) return true;
1684
- if (a === 127) return true;
1685
- if (a === 169 && b === 254) return true;
1686
- if (a === 172 && b >= 16 && b <= 31) return true;
1687
- if (a === 192 && b === 168) return true;
1688
- if (a === 192 && b === 0 && c === 0) return true;
1689
- if (a === 100 && b >= 64 && b <= 127) return true;
1690
- if (a >= 224) return true;
1691
- return false;
1692
- }
1693
- function isPrivateIPv6(raw) {
1694
- const lower = raw.toLowerCase();
1695
- if (lower === "::" || lower === "::1") return true;
1696
- const groups = expandIPv6(lower);
1697
- if (!groups) return true;
1698
- if (groups[0] === 0 && groups[1] === 0 && groups[2] === 0 && groups[3] === 0 && groups[4] === 0 && groups[5] === 65535) {
1699
- const a = (groups[6] ?? 0) >> 8;
1700
- const b = (groups[6] ?? 0) & 255;
1701
- const c = (groups[7] ?? 0) >> 8;
1702
- const d = (groups[7] ?? 0) & 255;
1703
- return isPrivateIPv4(`${a}.${b}.${c}.${d}`);
2580
+ if (groups.size > GREP_FILE_LIMIT) {
2581
+ sections.push(`[serializer omitted ${groups.size - GREP_FILE_LIMIT} file group(s)]`);
2582
+ }
2583
+ if (passthrough.length > 0) {
2584
+ sections.push(`ungrouped:
2585
+ ${renderStringList(passthrough, "", 50)}`);
2586
+ }
2587
+ return sections.join("\n");
2588
+ }
2589
+ function parseGrepContentLine(line) {
2590
+ const match = /^(.+?):(\d+):(.*)$/.exec(line);
2591
+ if (!match?.[1] || !match[2]) return void 0;
2592
+ return { file: match[1], line: match[2], text: match[3] ?? "" };
2593
+ }
2594
+ function compactDiff(diff) {
2595
+ const lines = diff.split(/\r?\n/);
2596
+ if (lines.length <= DIFF_INLINE_LINE_LIMIT) return diff;
2597
+ const fileCount = Math.max(
2598
+ new Set(
2599
+ lines.map(
2600
+ (line) => /^diff --git\s+a\/(.+?)\s+b\//.exec(line)?.[1] ?? /^---\s+(.+)/.exec(line)?.[1]
2601
+ ).filter(Boolean)
2602
+ ).size,
2603
+ 0
2604
+ );
2605
+ const hunks = lines.filter((line) => line.startsWith("@@")).length;
2606
+ const added = lines.filter((line) => line.startsWith("+") && !line.startsWith("+++")).length;
2607
+ const removed = lines.filter((line) => line.startsWith("-") && !line.startsWith("---")).length;
2608
+ const selected = /* @__PURE__ */ new Set();
2609
+ let hunkCount = 0;
2610
+ for (let i = 0; i < lines.length; i++) {
2611
+ const line = lines[i] ?? "";
2612
+ if (line.startsWith("diff --git") || line.startsWith("--- ") || line.startsWith("+++ ")) {
2613
+ selected.add(i);
2614
+ continue;
2615
+ }
2616
+ if (!line.startsWith("@@")) continue;
2617
+ if (hunkCount >= DIFF_HUNK_LIMIT) continue;
2618
+ hunkCount++;
2619
+ for (let j = i; j <= Math.min(lines.length - 1, i + DIFF_HUNK_CONTEXT); j++) {
2620
+ selected.add(j);
2621
+ }
1704
2622
  }
1705
- const high = groups[0] ?? 0;
1706
- if ((high & 65024) === 64512) return true;
1707
- if ((high & 65472) === 65152) return true;
1708
- if ((high & 65280) === 65280) return true;
1709
- return false;
1710
- }
1711
- function expandIPv6(addr) {
1712
- const parts = addr.split("::");
1713
- if (parts.length > 2) return null;
1714
- const parseGroups = (s) => {
1715
- if (s === "") return [];
1716
- const out = [];
1717
- for (const g of s.split(":")) {
1718
- if (g.length === 0 || g.length > 4) return null;
1719
- const n = Number.parseInt(g, 16);
1720
- if (Number.isNaN(n) || n < 0 || n > 65535) return null;
1721
- out.push(n);
2623
+ if (selected.size === 0) {
2624
+ return joinSections([
2625
+ renderHeader("diff_summary", {
2626
+ files: fileCount,
2627
+ hunks,
2628
+ added,
2629
+ removed,
2630
+ lines: lines.length
2631
+ }),
2632
+ lines.slice(0, DIFF_INLINE_LINE_LIMIT).join("\n"),
2633
+ `[serializer omitted ${Math.max(0, lines.length - DIFF_INLINE_LINE_LIMIT)} diff line(s)]`
2634
+ ]);
2635
+ }
2636
+ const excerpt = [];
2637
+ let previous = -1;
2638
+ for (const index of [...selected].sort((a, b) => a - b)) {
2639
+ if (index > previous + 1) {
2640
+ const omitted = previous === -1 ? index : index - previous - 1;
2641
+ excerpt.push(`[serializer omitted ${omitted} diff line(s)]`);
2642
+ }
2643
+ excerpt.push(lines[index] ?? "");
2644
+ previous = index;
2645
+ }
2646
+ const trailing = lines.length - previous - 1;
2647
+ if (trailing > 0) excerpt.push(`[serializer omitted ${trailing} trailing diff line(s)]`);
2648
+ return joinSections([
2649
+ renderHeader("diff_summary", {
2650
+ files: fileCount,
2651
+ hunks,
2652
+ shown_hunks: Math.min(hunks, DIFF_HUNK_LIMIT),
2653
+ added,
2654
+ removed,
2655
+ lines: lines.length
2656
+ }),
2657
+ excerpt.join("\n")
2658
+ ]);
2659
+ }
2660
+ function compactFailureOutput(output) {
2661
+ const lines = output.split(/\r?\n/);
2662
+ if (lines.length <= 260) return output.trimEnd();
2663
+ const selected = /* @__PURE__ */ new Set();
2664
+ const marker = /\b(fail|failed|failure|error|exception|assertionerror|expected|received|actual|timeout|stack)\b/i;
2665
+ let markerHits = 0;
2666
+ for (let i = 0; i < lines.length; i++) {
2667
+ if (!marker.test(lines[i] ?? "")) continue;
2668
+ markerHits++;
2669
+ for (let j = Math.max(0, i - 4); j <= Math.min(lines.length - 1, i + 10); j++) {
2670
+ selected.add(j);
1722
2671
  }
1723
- return out;
1724
- };
1725
- if (parts.length === 1) {
1726
- const groups = parseGroups(parts[0] ?? "");
1727
- if (!groups || groups.length !== 8) return null;
1728
- return groups;
1729
2672
  }
1730
- const head = parseGroups(parts[0] ?? "");
1731
- const tail = parseGroups(parts[1] ?? "");
1732
- if (!head || !tail) return null;
1733
- const fill = 8 - head.length - tail.length;
1734
- if (fill < 0) return null;
1735
- return [...head, ...new Array(fill).fill(0), ...tail];
1736
- }
1737
- async function assertNotPrivateHost(hostname) {
1738
- const host = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
1739
- if (host === "localhost" || host.endsWith(".localhost")) {
1740
- throw new Error("fetch: blocked localhost target");
2673
+ if (markerHits === 0) {
2674
+ return lines.slice(-220).join("\n").trimEnd();
1741
2675
  }
1742
- const ipVersion = net.isIP(host);
1743
- if (ipVersion === 4) {
1744
- if (isPrivateIPv4(host)) {
1745
- throw new Error(`fetch: blocked private/loopback address "${host}"`);
2676
+ const ordered = [...selected].sort((a, b) => a - b);
2677
+ const out = [];
2678
+ let previous = -1;
2679
+ for (const index of ordered) {
2680
+ if (index > previous + 1) {
2681
+ const omitted = previous === -1 ? index : index - previous - 1;
2682
+ out.push(`[serializer omitted ${omitted} line(s)]`);
1746
2683
  }
1747
- } else if (ipVersion === 6) {
1748
- if (isPrivateIPv6(host)) {
1749
- throw new Error(`fetch: blocked private/loopback address "${host}"`);
2684
+ out.push(lines[index] ?? "");
2685
+ previous = index;
2686
+ }
2687
+ return out.join("\n").trimEnd();
2688
+ }
2689
+ function extractSpoolNote(output) {
2690
+ return output.split(/\r?\n/).find((line) => line.startsWith("[output truncated") && line.includes("full"));
2691
+ }
2692
+ function hasCommandOutputShape(obj) {
2693
+ return typeof obj["stdout"] === "string" || typeof obj["stderr"] === "string" || typeof obj["output"] === "string" || typeof obj["exitCode"] === "number" || typeof obj["exit_code"] === "number";
2694
+ }
2695
+ function renderCommandOutput(toolName, obj, input) {
2696
+ const command = stringField(obj, "command") ?? stringFromInput(input, "command");
2697
+ const args = stringArrayField(obj, "args");
2698
+ const commandLine = command ? [command, ...args].join(" ") : void 0;
2699
+ const output = stringField(obj, "output");
2700
+ const stdout = stringField(obj, "stdout");
2701
+ const stderr = stringField(obj, "stderr");
2702
+ return joinSections([
2703
+ renderHeader(commandLine ? `${toolName}: ${commandLine}` : toolName, {
2704
+ exit_code: obj["exit_code"] ?? obj["exitCode"],
2705
+ timed_out: obj["timed_out"],
2706
+ pid: obj["pid"],
2707
+ allowed: obj["allowed"],
2708
+ truncated: obj["truncated"],
2709
+ runner: obj["runner"],
2710
+ linter: obj["linter"],
2711
+ fixer: obj["fixer"],
2712
+ project: obj["project"],
2713
+ tests_run: obj["tests_run"],
2714
+ passed: obj["passed"],
2715
+ failed: obj["failed"],
2716
+ duration_ms: obj["duration_ms"],
2717
+ errors: obj["errors"],
2718
+ warnings: obj["warnings"],
2719
+ files_checked: obj["files_checked"],
2720
+ files_changed: obj["files_changed"],
2721
+ fix_applied: obj["fix_applied"]
2722
+ }),
2723
+ stringField(obj, "error") ? `error:
2724
+ ${stringField(obj, "error")}` : void 0,
2725
+ output ? `output:
2726
+ ${output}` : void 0,
2727
+ stdout ? `stdout:
2728
+ ${stdout}` : void 0,
2729
+ stderr ? `stderr:
2730
+ ${stderr}` : void 0
2731
+ ]);
2732
+ }
2733
+ function renderGenericToolObject(toolName, obj) {
2734
+ const scalars = {};
2735
+ const blocks = [];
2736
+ for (const [key, value] of Object.entries(obj)) {
2737
+ if (value === void 0) continue;
2738
+ if (isScalar(value)) {
2739
+ const inline = String(value);
2740
+ if (inline.length <= INLINE_LIMIT && !inline.includes("\n")) {
2741
+ scalars[key] = value;
2742
+ } else {
2743
+ blocks.push(`${key}:
2744
+ ${inline}`);
2745
+ }
2746
+ continue;
1750
2747
  }
1751
- } else {
1752
- try {
1753
- const records = await dns.lookup(host, { all: true });
1754
- for (const r of records) {
1755
- const bad = r.family === 4 ? isPrivateIPv4(r.address) : isPrivateIPv6(r.address);
1756
- if (bad) {
1757
- throw new Error(`fetch: resolved to private address ${r.address}`);
1758
- }
2748
+ if (Array.isArray(value)) {
2749
+ if (value.every((item) => typeof item === "string")) {
2750
+ blocks.push(`${key}:
2751
+ ${renderStringList(value)}`);
2752
+ } else {
2753
+ blocks.push(`${key}:
2754
+ ${renderUnknownList(value)}`);
1759
2755
  }
1760
- } catch (err) {
1761
- if (err instanceof Error && err.message.startsWith("fetch:")) throw err;
2756
+ continue;
1762
2757
  }
2758
+ blocks.push(`${key}: ${clipInline(oneLineJson(value))}`);
2759
+ }
2760
+ return joinSections([renderHeader(toolName, scalars), ...blocks]);
2761
+ }
2762
+ function renderHeader(label, fields) {
2763
+ const parts = Object.entries(fields).filter(([, value]) => value !== void 0 && value !== null && value !== "").map(([key, value]) => `${key}=${clipInline(formatInlineValue(value))}`);
2764
+ return parts.length > 0 ? `${label} (${parts.join(" ")})` : label;
2765
+ }
2766
+ function renderStringList(items, empty = "", limit = DEFAULT_LIST_LIMIT) {
2767
+ if (items.length === 0) return empty;
2768
+ const shown = items.slice(0, limit);
2769
+ const omitted = items.length - shown.length;
2770
+ return [
2771
+ ...shown,
2772
+ ...omitted > 0 ? [`[serializer omitted ${omitted} item(s); narrow the request for more]`] : []
2773
+ ].join("\n");
2774
+ }
2775
+ function renderUnknownList(items, limit = DEFAULT_LIST_LIMIT) {
2776
+ const shown = items.slice(0, limit).map((item) => clipInline(oneLineJson(item), 1e3));
2777
+ const omitted = items.length - shown.length;
2778
+ if (omitted > 0)
2779
+ shown.push(`[serializer omitted ${omitted} item(s); narrow the request for more]`);
2780
+ return shown.join("\n");
2781
+ }
2782
+ function joinSections(sections) {
2783
+ return sections.map((section) => typeof section === "string" ? section.trimEnd() : void 0).filter((section) => !!section).join("\n");
2784
+ }
2785
+ function formatInlineValue(value) {
2786
+ if (Array.isArray(value)) return `[${value.map(formatInlineValue).join(",")}]`;
2787
+ if (isScalar(value)) return String(value);
2788
+ return oneLineJson(value);
2789
+ }
2790
+ function clipInline(value, max = INLINE_LIMIT) {
2791
+ const compact = value.replace(/\s+/g, " ").trim();
2792
+ return compact.length <= max ? compact : `${compact.slice(0, max - 15)}...(${compact.length} chars)`;
2793
+ }
2794
+ function oneLineJson(value) {
2795
+ try {
2796
+ return JSON.stringify(value);
2797
+ } catch {
2798
+ return String(value);
1763
2799
  }
1764
2800
  }
2801
+ function stringField(obj, key) {
2802
+ const value = obj[key];
2803
+ return typeof value === "string" ? value : void 0;
2804
+ }
2805
+ function numberField(obj, key) {
2806
+ const value = obj[key];
2807
+ return typeof value === "number" ? value : void 0;
2808
+ }
2809
+ function stringArrayField(obj, key) {
2810
+ const value = obj[key];
2811
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
2812
+ }
2813
+ function stringFromInput(input, key) {
2814
+ if (!isRecord2(input)) return void 0;
2815
+ const value = input[key];
2816
+ return typeof value === "string" ? value : void 0;
2817
+ }
2818
+ function numberFromInput(input, key) {
2819
+ if (!isRecord2(input)) return void 0;
2820
+ const value = input[key];
2821
+ return typeof value === "number" ? value : void 0;
2822
+ }
2823
+ function inputListSummary(input, key) {
2824
+ if (!isRecord2(input)) return void 0;
2825
+ const value = input[key];
2826
+ if (typeof value === "string") return value;
2827
+ if (Array.isArray(value)) return value.filter((item) => typeof item === "string").join(",");
2828
+ return void 0;
2829
+ }
2830
+ function isRecord2(value) {
2831
+ return !!value && typeof value === "object" && !Array.isArray(value);
2832
+ }
2833
+ function isScalar(value) {
2834
+ return value === null || ["string", "number", "boolean"].includes(typeof value);
2835
+ }
2836
+ function projectHash(absRoot) {
2837
+ return createHash("sha256").update(path3.resolve(absRoot)).digest("hex").slice(0, 12);
2838
+ }
2839
+ function projectSlug(absRoot) {
2840
+ const base = slugify(path3.basename(absRoot));
2841
+ const hash = createHash("sha256").update(path3.resolve(absRoot)).digest("hex").slice(0, 6);
2842
+ return `${base}-${hash}`;
2843
+ }
2844
+ function slugify(name) {
2845
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "project";
2846
+ }
2847
+ function wstackGlobalRoot() {
2848
+ const fromEnv = process.env["WRONGSTACK_HOME"];
2849
+ if (fromEnv && fromEnv.trim().length > 0) return path3.resolve(fromEnv);
2850
+ return path3.join(os.homedir(), ".wrongstack");
2851
+ }
2852
+ function resolveWstackPaths(opts) {
2853
+ const globalRoot = opts.globalRoot ?? (opts.userHome ? path3.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
2854
+ const hash = projectHash(opts.projectRoot);
2855
+ const slug = projectSlug(opts.projectRoot);
2856
+ const projectDir = path3.join(globalRoot, "projects", slug);
2857
+ return {
2858
+ globalRoot,
2859
+ configDir: globalRoot,
2860
+ globalConfig: path3.join(globalRoot, "config.json"),
2861
+ secretsKey: path3.join(globalRoot, ".key"),
2862
+ globalMemory: path3.join(globalRoot, "memory.md"),
2863
+ globalSkills: path3.join(globalRoot, "skills"),
2864
+ globalPrompts: path3.join(globalRoot, "prompts"),
2865
+ cacheDir: path3.join(globalRoot, "cache"),
2866
+ modelsCache: path3.join(globalRoot, "cache", "models.dev.json"),
2867
+ modelsOverlayCache: path3.join(globalRoot, "cache", "models-overlay.json"),
2868
+ historyFile: path3.join(globalRoot, "history"),
2869
+ logFile: path3.join(globalRoot, "logs", "wrongstack.log"),
2870
+ projectDir,
2871
+ projectCodebaseIndex: path3.join(projectDir, "codebase-index"),
2872
+ projectMemory: path3.join(projectDir, "memory.md"),
2873
+ projectSessions: path3.join(projectDir, "sessions"),
2874
+ projectTrust: path3.join(projectDir, "trust.json"),
2875
+ projectMeta: path3.join(projectDir, "meta.json"),
2876
+ projectLocalConfig: path3.join(projectDir, "config.local.json"),
2877
+ inProjectConfig: path3.join(opts.projectRoot, ".wrongstack", "config.json"),
2878
+ inProjectAgentsFile: path3.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
2879
+ inProjectSkills: path3.join(opts.projectRoot, ".wrongstack", "skills"),
2880
+ inProjectWorktrees: path3.join(opts.projectRoot, ".wrongstack", "worktrees"),
2881
+ projectHash: hash,
2882
+ projectSlug: slug,
2883
+ projectGoal: path3.join(projectDir, "goal.json"),
2884
+ projectSpecs: path3.join(projectDir, "specs"),
2885
+ projectTaskGraphs: path3.join(projectDir, "task-graphs"),
2886
+ projectSddSession: path3.join(projectDir, "sdd-session.json"),
2887
+ projectPlan: path3.join(projectDir, "plan.json"),
2888
+ projectAutophase: path3.join(projectDir, "autophase"),
2889
+ syncConfig: path3.join(globalRoot, "sync.json"),
2890
+ projectStatus: (projectHash2) => path3.join(globalRoot, "projects", projectHash2, "status.json")
2891
+ };
2892
+ }
1765
2893
 
1766
- export { FORBIDDEN_PROTO_KEYS, assertNever, assertNotPrivateHost, atomicWrite, buildChildEnv, color, compileGlob, compileUserRegex, completePartialObject, computeMessageTokens, computeTaskItemProgress, createToolOutputSerializer, deepMerge, detectNewlineStyle, ensureDir, estimateMessageTokens, estimateRequestTokens, estimateRequestTokensCalibrated, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, expandGlob, expandIPv6, expectDefined, formatTaskList, formatTaskProgress, formatTodosList, getCalibrationState, getTermSize, isInteractive, isPrimitiveArray, isPrivateIPv4, isPrivateIPv6, isStdinTTY, isStdoutTTY, matchAny, matchGlob, mergeCustomModelDefs, mergeModelsPayload, normalizeToLf, onResize, projectHash, projectSlug, recordActualUsage, repairToolUseAdjacency, resetCalibration, resolveWstackPaths, safeParse, safeStringify, sanitizeJsonString, setOutputLineGuard, setRawMode, sleep, stripAnsi, toErrorMessage, toStyle, truncate, unifiedDiff, validateAgainstSchema, withFileLock, writeErr, writeOut, wstackGlobalRoot };
2894
+ export { FORBIDDEN_PROTO_KEYS, assertNever, assertNotPrivateHost, atomicWrite, buildChildEnv, buildContextEvidenceDigest, color, compactSchemaDescriptions, compactToolDefinitionForWire, compileGlob, compileUserRegex, completePartialObject, computeMessageTokens, computeTaskItemProgress, createContextEvidenceState, createToolOutputSerializer, deepMerge, detectNewlineStyle, ensureDir, escapeGlobSubject, estimateMessageTokens, estimateRequestTokens, estimateRequestTokensCalibrated, estimateTextTokens, estimateToolDefTokens, estimateToolInputTokens, estimateToolResultTokens, expandGlob, expandIPv6, expectDefined, formatTaskList, formatTaskProgress, formatTodosList, getCalibrationState, getJsonPath, getTermSize, isInteractive, isJsonObject, isPathSubjectKey, isPrimitiveArray, isPrivateIPv4, isPrivateIPv6, isStdinTTY, isStdoutTTY, jsonObjectFileExists, markAssistantReferencedEvidence, matchAny, matchGlob, mergeCustomModelDefs, mergeModelsPayload, normalizePathSubject, normalizeToLf, onResize, projectHash, projectSlug, readJsonObjectFile, recordActualUsage, recordToolOutputEvidence, recordUserIntentEvidence, removeJsonPath, removeJsonPathInFile, repairToolUseAdjacency, repeatedReadPressure, resetCalibration, resolveWstackPaths, safeParse, safeStringify, sanitizeJsonString, setJsonPath, setJsonPathInFile, setOutputLineGuard, setRawMode, sleep, stripAnsi, subjectForToolInput, toErrorMessage, toStyle, truncate, unifiedDiff, updateJsonObjectFile, validateAgainstSchema, withFileLock, writeErr, writeJsonObjectFile, writeOut, wstackGlobalRoot };
1767
2895
  //# sourceMappingURL=index.js.map
1768
2896
  //# sourceMappingURL=index.js.map