@wrongstack/core 0.291.0 → 0.291.2

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 (52) hide show
  1. package/dist/boot.d.ts.map +1 -1
  2. package/dist/chronicle/index.js +20 -1
  3. package/dist/chronicle/index.js.map +2 -2
  4. package/dist/chronicle/tool-adapter.d.ts.map +1 -1
  5. package/dist/coordination/director.d.ts +18 -0
  6. package/dist/coordination/director.d.ts.map +1 -1
  7. package/dist/coordination/fleet-spawn.d.ts.map +1 -1
  8. package/dist/coordination/index.js +37 -15
  9. package/dist/coordination/index.js.map +2 -2
  10. package/dist/core/agent-response.d.ts.map +1 -1
  11. package/dist/defaults/index.js +660 -391
  12. package/dist/defaults/index.js.map +4 -4
  13. package/dist/execution/index.js +61 -12
  14. package/dist/execution/index.js.map +3 -3
  15. package/dist/execution/tool-executor.d.ts.map +1 -1
  16. package/dist/hq/index.js +17 -0
  17. package/dist/hq/index.js.map +2 -2
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1304 -882
  21. package/dist/index.js.map +4 -4
  22. package/dist/infrastructure/index.js +15 -10
  23. package/dist/infrastructure/index.js.map +2 -2
  24. package/dist/kernel/events/tool-events.d.ts +24 -0
  25. package/dist/kernel/events/tool-events.d.ts.map +1 -1
  26. package/dist/plugins/auto-review-plugin.d.ts +10 -0
  27. package/dist/plugins/auto-review-plugin.d.ts.map +1 -1
  28. package/dist/security/index.js +148 -0
  29. package/dist/security/index.js.map +3 -3
  30. package/dist/security/permission-policy.d.ts +8 -1
  31. package/dist/security/permission-policy.d.ts.map +1 -1
  32. package/dist/storage/config-loader.d.ts +8 -0
  33. package/dist/storage/config-loader.d.ts.map +1 -1
  34. package/dist/storage/index.js +457 -379
  35. package/dist/storage/index.js.map +4 -4
  36. package/dist/storage/provider-config-watcher.d.ts +6 -0
  37. package/dist/storage/provider-config-watcher.d.ts.map +1 -1
  38. package/dist/types/permission.d.ts +38 -0
  39. package/dist/types/permission.d.ts.map +1 -1
  40. package/dist/utils/config-backup.d.ts +20 -0
  41. package/dist/utils/config-backup.d.ts.map +1 -0
  42. package/dist/utils/index.d.ts +2 -1
  43. package/dist/utils/index.d.ts.map +1 -1
  44. package/dist/utils/index.js +172 -106
  45. package/dist/utils/index.js.map +4 -4
  46. package/dist/utils/message-invariants.d.ts +12 -9
  47. package/dist/utils/message-invariants.d.ts.map +1 -1
  48. package/dist/utils/term.d.ts +6 -0
  49. package/dist/utils/term.d.ts.map +1 -1
  50. package/dist/utils/wstack-paths.d.ts +10 -0
  51. package/dist/utils/wstack-paths.d.ts.map +1 -1
  52. package/package.json +2 -2
@@ -382,6 +382,37 @@ var color = {
382
382
  bgGreen: wrap("42", "49")
383
383
  };
384
384
 
385
+ // src/utils/config-backup.ts
386
+ import * as fs2 from "node:fs/promises";
387
+ import * as path2 from "node:path";
388
+ function configHistoryDir(globalRoot) {
389
+ return path2.join(globalRoot, "config-history");
390
+ }
391
+ function configSlug(absolutePath, globalRoot) {
392
+ const rel = path2.relative(globalRoot, absolutePath);
393
+ const normalized = rel.replace(/\\/g, "/").replace(/\.json$/i, "");
394
+ return normalized.replace(/\//g, "-");
395
+ }
396
+ async function backupConfigFile(filePath, paths) {
397
+ let currentContent;
398
+ try {
399
+ currentContent = await fs2.readFile(filePath, "utf8");
400
+ if (!currentContent.trim()) return;
401
+ } catch {
402
+ return;
403
+ }
404
+ const now = /* @__PURE__ */ new Date();
405
+ const ts = now.toISOString().replace(/[:.]/g, "-").replace(/Z$/, "");
406
+ const slug = configSlug(filePath, paths.globalRoot);
407
+ const backupDir = configHistoryDir(paths.globalRoot);
408
+ const backupFile = path2.join(backupDir, `${slug}-${ts}.json`);
409
+ try {
410
+ await fs2.mkdir(backupDir, { recursive: true });
411
+ await fs2.writeFile(backupFile, currentContent, { mode: 384, encoding: "utf8" });
412
+ } catch {
413
+ }
414
+ }
415
+
385
416
  // src/utils/context-evidence.ts
386
417
  var MAX_DIGEST_CHARS = 4e3;
387
418
  function createContextEvidenceState() {
@@ -841,10 +872,10 @@ function validateAgainstSchema(value, schema) {
841
872
  return { ok: errors.length === 0, errors };
842
873
  }
843
874
  var MAX_SCHEMA_DEPTH = 64;
844
- function walk(value, schema, path29, errors, depth) {
875
+ function walk(value, schema, path30, errors, depth) {
845
876
  if (depth > MAX_SCHEMA_DEPTH) {
846
877
  errors.push({
847
- path: path29 || "<root>",
878
+ path: path30 || "<root>",
848
879
  message: `schema nesting exceeds maximum depth (${MAX_SCHEMA_DEPTH})`
849
880
  });
850
881
  return;
@@ -852,7 +883,7 @@ function walk(value, schema, path29, errors, depth) {
852
883
  if (schema.enum !== void 0) {
853
884
  if (!enumIncludes(schema.enum, value)) {
854
885
  errors.push({
855
- path: path29 || "<root>",
886
+ path: path30 || "<root>",
856
887
  message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
857
888
  });
858
889
  return;
@@ -861,7 +892,7 @@ function walk(value, schema, path29, errors, depth) {
861
892
  if (typeof schema.type === "string") {
862
893
  if (!checkType(value, schema.type)) {
863
894
  errors.push({
864
- path: path29 || "<root>",
895
+ path: path30 || "<root>",
865
896
  message: `expected ${schema.type}, got ${describeType(value)} (${previewValue(value)})`
866
897
  });
867
898
  return;
@@ -873,7 +904,7 @@ function walk(value, schema, path29, errors, depth) {
873
904
  if (!(req in obj)) {
874
905
  const expected = schema.properties?.[req]?.type;
875
906
  errors.push({
876
- path: joinPath(path29, req),
907
+ path: joinPath(path30, req),
877
908
  message: `required property missing${typeof expected === "string" ? ` (expected ${expected})` : ""}`
878
909
  });
879
910
  }
@@ -881,14 +912,14 @@ function walk(value, schema, path29, errors, depth) {
881
912
  if (schema.properties) {
882
913
  for (const [key, subSchema] of Object.entries(schema.properties)) {
883
914
  if (key in obj) {
884
- walk(obj[key], subSchema, joinPath(path29, key), errors, depth + 1);
915
+ walk(obj[key], subSchema, joinPath(path30, key), errors, depth + 1);
885
916
  }
886
917
  }
887
918
  }
888
919
  }
889
920
  if (schema.type === "array" && Array.isArray(value) && schema.items) {
890
921
  for (let i = 0; i < value.length; i++) {
891
- walk(value[i], schema.items, `${path29}[${i}]`, errors, depth + 1);
922
+ walk(value[i], schema.items, `${path30}[${i}]`, errors, depth + 1);
892
923
  }
893
924
  }
894
925
  }
@@ -1082,16 +1113,6 @@ function stripUndefined(obj) {
1082
1113
  }
1083
1114
 
1084
1115
  // src/utils/message-invariants.ts
1085
- function hasMeaningfulContent(content) {
1086
- if (typeof content === "string") return content.trim().length > 0;
1087
- return content.some((block) => {
1088
- if (block.type === "text") return block.text.trim().length > 0;
1089
- if (block.type === "thinking") {
1090
- return block.thinking.trim().length > 0 || Boolean(block.signature);
1091
- }
1092
- return true;
1093
- });
1094
- }
1095
1116
  function repairToolUseAdjacency(messages) {
1096
1117
  const removedToolUses = [];
1097
1118
  const removedToolResults = [];
@@ -1178,6 +1199,21 @@ function mapContent(msg, fn) {
1178
1199
  }
1179
1200
  return { ...msg, content: next };
1180
1201
  }
1202
+ function hasMeaningfulContent(content) {
1203
+ if (typeof content === "string") return content.trim().length > 0;
1204
+ for (const block of content) {
1205
+ if (block.type === "text") {
1206
+ if (block.text.trim().length > 0) return true;
1207
+ continue;
1208
+ }
1209
+ if (block.type === "thinking") {
1210
+ if (block.thinking.trim().length > 0 || block.signature) return true;
1211
+ continue;
1212
+ }
1213
+ return true;
1214
+ }
1215
+ return false;
1216
+ }
1181
1217
  function isEmptyMessage(msg) {
1182
1218
  return !hasMeaningfulContent(msg.content);
1183
1219
  }
@@ -1255,14 +1291,14 @@ function safeStringify(value, pretty = false) {
1255
1291
  }
1256
1292
 
1257
1293
  // src/utils/session-scoped-path.ts
1258
- import * as path2 from "node:path";
1294
+ import * as path3 from "node:path";
1259
1295
  function sessionScopedPath(dir, sessionId, suffix) {
1260
1296
  if (!sessionId || sessionId.includes("\\") || sessionId.includes("..")) {
1261
1297
  throw invalid(sessionId);
1262
1298
  }
1263
- const resolved = path2.resolve(dir, `${sessionId}${suffix}`);
1264
- const rel = path2.relative(path2.resolve(dir), resolved);
1265
- if (rel.startsWith("..") || path2.isAbsolute(rel)) {
1299
+ const resolved = path3.resolve(dir, `${sessionId}${suffix}`);
1300
+ const rel = path3.relative(path3.resolve(dir), resolved);
1301
+ if (rel.startsWith("..") || path3.isAbsolute(rel)) {
1266
1302
  throw invalid(sessionId);
1267
1303
  }
1268
1304
  return resolved;
@@ -2316,23 +2352,23 @@ function ulid(seedTime = Date.now()) {
2316
2352
 
2317
2353
  // src/utils/wstack-paths.ts
2318
2354
  import { createHash } from "node:crypto";
2319
- import * as fs2 from "node:fs";
2355
+ import * as fs3 from "node:fs";
2320
2356
  import * as os from "node:os";
2321
- import * as path3 from "node:path";
2357
+ import * as path4 from "node:path";
2322
2358
  function canonicalProjectRoot(absRoot) {
2323
- const checkoutRoot = path3.resolve(absRoot);
2324
- const dotGit = path3.join(checkoutRoot, ".git");
2359
+ const checkoutRoot = path4.resolve(absRoot);
2360
+ const dotGit = path4.join(checkoutRoot, ".git");
2325
2361
  try {
2326
- if (!fs2.statSync(dotGit).isFile()) return checkoutRoot;
2327
- const gitDirLine = fs2.readFileSync(dotGit, "utf8").trim();
2362
+ if (!fs3.statSync(dotGit).isFile()) return checkoutRoot;
2363
+ const gitDirLine = fs3.readFileSync(dotGit, "utf8").trim();
2328
2364
  const match = /^gitdir:\s*(.+)$/i.exec(gitDirLine);
2329
2365
  if (!match?.[1]) return checkoutRoot;
2330
- const gitDir = path3.resolve(checkoutRoot, match[1].trim());
2331
- const commonDirFile = path3.join(gitDir, "commondir");
2332
- if (!fs2.statSync(commonDirFile).isFile()) return checkoutRoot;
2333
- const commonDir = path3.resolve(gitDir, fs2.readFileSync(commonDirFile, "utf8").trim());
2334
- if (path3.basename(commonDir).toLowerCase() !== ".git") return checkoutRoot;
2335
- return path3.dirname(commonDir);
2366
+ const gitDir = path4.resolve(checkoutRoot, match[1].trim());
2367
+ const commonDirFile = path4.join(gitDir, "commondir");
2368
+ if (!fs3.statSync(commonDirFile).isFile()) return checkoutRoot;
2369
+ const commonDir = path4.resolve(gitDir, fs3.readFileSync(commonDirFile, "utf8").trim());
2370
+ if (path4.basename(commonDir).toLowerCase() !== ".git") return checkoutRoot;
2371
+ return path4.dirname(commonDir);
2336
2372
  } catch {
2337
2373
  return checkoutRoot;
2338
2374
  }
@@ -2342,7 +2378,7 @@ function projectHash(absRoot) {
2342
2378
  }
2343
2379
  function projectSlug(absRoot) {
2344
2380
  const identityRoot = canonicalProjectRoot(absRoot);
2345
- const base = slugify2(path3.basename(identityRoot));
2381
+ const base = slugify2(path4.basename(identityRoot));
2346
2382
  const hash2 = createHash("sha256").update(identityRoot).digest("hex").slice(0, 6);
2347
2383
  return `${base}-${hash2}`;
2348
2384
  }
@@ -2351,66 +2387,83 @@ function slugify2(name) {
2351
2387
  }
2352
2388
  function wstackGlobalRoot() {
2353
2389
  const fromEnv = process.env["WRONGSTACK_HOME"];
2354
- if (fromEnv && fromEnv.trim().length > 0) return path3.resolve(fromEnv);
2355
- return path3.join(os.homedir(), ".wrongstack");
2390
+ if (fromEnv && fromEnv.trim().length > 0) return path4.resolve(fromEnv);
2391
+ return path4.join(os.homedir(), ".wrongstack");
2356
2392
  }
2357
2393
  function resolveWstackPaths(opts) {
2358
- const globalRoot = opts.globalRoot ?? (opts.userHome ? path3.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
2394
+ const globalRoot = opts.globalRoot ?? (opts.userHome ? path4.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
2359
2395
  const homeDir = opts.userHome ?? os.homedir();
2360
2396
  const hash2 = projectHash(opts.projectRoot);
2361
2397
  const slug = projectSlug(opts.projectRoot);
2362
- const projectDir = path3.join(globalRoot, "projects", slug);
2398
+ const projectDir = path4.join(globalRoot, "projects", slug);
2363
2399
  return {
2364
2400
  globalRoot,
2365
2401
  projectRoot: opts.projectRoot,
2366
2402
  homeDir,
2367
2403
  configDir: globalRoot,
2368
- globalConfig: path3.join(globalRoot, "config.json"),
2369
- profilesDir: path3.join(globalRoot, "profiles"),
2404
+ globalConfig: path4.join(globalRoot, "config.json"),
2405
+ profilesDir: path4.join(globalRoot, "profiles"),
2370
2406
  profileConfig: (name) => {
2371
2407
  const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
2372
- return path3.join(globalRoot, "profiles", safe || "default", "config.json");
2408
+ return path4.join(globalRoot, "profiles", safe || "default", "config.json");
2409
+ },
2410
+ profileStatuslineConfig: (name) => {
2411
+ const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
2412
+ return path4.join(globalRoot, "profiles", safe || "default", "statusline.json");
2413
+ },
2414
+ profileModeConfig: (name) => {
2415
+ const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
2416
+ return path4.join(globalRoot, "profiles", safe || "default", "mode.json");
2373
2417
  },
2374
- secretsKey: path3.join(globalRoot, ".key"),
2375
- globalMemory: path3.join(globalRoot, "memory.md"),
2376
- globalSkills: path3.join(globalRoot, "skills"),
2377
- globalClaudeSkills: path3.join(homeDir, ".claude", "skills"),
2378
- globalDesignKits: path3.join(globalRoot, "design-kits"),
2379
- globalPrompts: path3.join(globalRoot, "prompts"),
2380
- globalInstructions: path3.join(globalRoot, "instructions"),
2381
- promptUsage: path3.join(globalRoot, "prompt-usage.json"),
2382
- cacheDir: path3.join(globalRoot, "cache"),
2383
- modelsCache: path3.join(globalRoot, "cache", "models.dev.json"),
2384
- modelsOverlayCache: path3.join(globalRoot, "cache", "models-overlay.json"),
2385
- historyFile: path3.join(globalRoot, "history"),
2386
- logFile: path3.join(globalRoot, "logs", "wrongstack.log"),
2418
+ profileProviderStatus: (name) => {
2419
+ const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
2420
+ return path4.join(globalRoot, "profiles", safe || "default", "provider-status.json");
2421
+ },
2422
+ profileUpdateCache: (name) => {
2423
+ const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
2424
+ return path4.join(globalRoot, "profiles", safe || "default", "update-cache.json");
2425
+ },
2426
+ secretsKey: path4.join(globalRoot, ".key"),
2427
+ globalMemory: path4.join(globalRoot, "memory.md"),
2428
+ globalSkills: path4.join(globalRoot, "skills"),
2429
+ globalClaudeSkills: path4.join(homeDir, ".claude", "skills"),
2430
+ globalDesignKits: path4.join(globalRoot, "design-kits"),
2431
+ globalPrompts: path4.join(globalRoot, "prompts"),
2432
+ globalInstructions: path4.join(globalRoot, "instructions"),
2433
+ promptUsage: path4.join(globalRoot, "prompt-usage.json"),
2434
+ cacheDir: path4.join(globalRoot, "cache"),
2435
+ modelsCache: path4.join(globalRoot, "cache", "models.dev.json"),
2436
+ modelsOverlayCache: path4.join(globalRoot, "cache", "models-overlay.json"),
2437
+ historyFile: path4.join(globalRoot, "history"),
2438
+ logFile: path4.join(globalRoot, "logs", "wrongstack.log"),
2387
2439
  projectDir,
2388
- projectCodebaseIndex: path3.join(projectDir, "codebase-index"),
2389
- projectMemory: path3.join(projectDir, "memory.md"),
2390
- projectSessions: path3.join(projectDir, "sessions"),
2391
- projectTrust: path3.join(projectDir, "trust.json"),
2392
- projectMeta: path3.join(projectDir, "meta.json"),
2393
- projectLocalConfig: path3.join(projectDir, "config.local.json"),
2394
- inProjectConfig: path3.join(opts.projectRoot, ".wrongstack", "config.json"),
2395
- inProjectAgentsFile: path3.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
2396
- inProjectSkills: path3.join(opts.projectRoot, ".wrongstack", "skills"),
2397
- inProjectClaudeSkills: path3.join(opts.projectRoot, ".claude", "skills"),
2398
- inProjectPrompts: path3.join(opts.projectRoot, ".wrongstack", "prompts"),
2399
- inProjectInstructions: path3.join(opts.projectRoot, ".wrongstack", "instructions"),
2400
- inProjectDesignKits: path3.join(opts.projectRoot, ".wrongstack", "design-kits"),
2401
- inProjectWorktrees: path3.join(opts.projectRoot, ".wrongstack", "worktrees"),
2440
+ projectCodebaseIndex: path4.join(projectDir, "codebase-index"),
2441
+ projectMemory: path4.join(projectDir, "memory.md"),
2442
+ projectSessions: path4.join(projectDir, "sessions"),
2443
+ projectTrust: path4.join(projectDir, "trust.json"),
2444
+ projectMeta: path4.join(projectDir, "meta.json"),
2445
+ projectLocalConfig: path4.join(projectDir, "config.local.json"),
2446
+ inProjectConfig: path4.join(opts.projectRoot, ".wrongstack", "config.json"),
2447
+ inProjectAgentsFile: path4.join(opts.projectRoot, ".wrongstack", "AGENTS.md"),
2448
+ inProjectSkills: path4.join(opts.projectRoot, ".wrongstack", "skills"),
2449
+ inProjectClaudeSkills: path4.join(opts.projectRoot, ".claude", "skills"),
2450
+ inProjectPrompts: path4.join(opts.projectRoot, ".wrongstack", "prompts"),
2451
+ inProjectInstructions: path4.join(opts.projectRoot, ".wrongstack", "instructions"),
2452
+ inProjectDesignKits: path4.join(opts.projectRoot, ".wrongstack", "design-kits"),
2453
+ inProjectWorktrees: path4.join(opts.projectRoot, ".wrongstack", "worktrees"),
2402
2454
  projectHash: hash2,
2403
2455
  projectSlug: slug,
2404
- projectGoal: path3.join(projectDir, "goal.json"),
2405
- projectInputHistory: path3.join(projectDir, "input-history.json"),
2406
- projectSpecs: path3.join(projectDir, "specs"),
2407
- projectTaskGraphs: path3.join(projectDir, "task-graphs"),
2408
- projectSddSession: path3.join(projectDir, "sdd-session.json"),
2409
- projectPlan: path3.join(projectDir, "plan.json"),
2410
- projectAutophase: path3.join(projectDir, "autophase"),
2411
- projectSddBoards: path3.join(projectDir, "sdd-boards"),
2412
- syncConfig: path3.join(globalRoot, "sync.json"),
2413
- projectStatus: (projectHash2) => path3.join(globalRoot, "projects", projectHash2, "status.json")
2456
+ projectGoal: path4.join(projectDir, "goal.json"),
2457
+ projectInputHistory: path4.join(projectDir, "input-history.json"),
2458
+ projectSpecs: path4.join(projectDir, "specs"),
2459
+ projectTaskGraphs: path4.join(projectDir, "task-graphs"),
2460
+ projectSddSession: path4.join(projectDir, "sdd-session.json"),
2461
+ projectPlan: path4.join(projectDir, "plan.json"),
2462
+ projectAutophase: path4.join(projectDir, "autophase"),
2463
+ projectSddBoards: path4.join(projectDir, "sdd-boards"),
2464
+ syncConfig: path4.join(globalRoot, "sync.json"),
2465
+ configHistoryDir: path4.join(globalRoot, "config-history"),
2466
+ projectStatus: (projectHash2) => path4.join(globalRoot, "projects", projectHash2, "status.json")
2414
2467
  };
2415
2468
  }
2416
2469
 
@@ -3719,7 +3772,7 @@ var TOOLS = {
3719
3772
  // src/coordination/agents/agent-prompts.ts
3720
3773
  import { readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
3721
3774
  import * as os2 from "node:os";
3722
- import * as path4 from "node:path";
3775
+ import * as path5 from "node:path";
3723
3776
  import { fileURLToPath } from "node:url";
3724
3777
  var promptCache = /* @__PURE__ */ new Map();
3725
3778
  var candidateCache = /* @__PURE__ */ new Map();
@@ -3732,7 +3785,7 @@ function agentPrompt(id) {
3732
3785
  let resolved = "";
3733
3786
  for (const dir of agentPromptDirCandidates(envDir)) {
3734
3787
  try {
3735
- resolved = readFileSync2(path4.join(dir, fileName), "utf8").trimEnd();
3788
+ resolved = readFileSync2(path5.join(dir, fileName), "utf8").trimEnd();
3736
3789
  break;
3737
3790
  } catch {
3738
3791
  }
@@ -3741,20 +3794,20 @@ function agentPrompt(id) {
3741
3794
  return resolved;
3742
3795
  }
3743
3796
  function agentPromptDirCandidates(envDir) {
3744
- const globalRoot = process.env["WRONGSTACK_HOME"] || path4.join(os2.homedir(), ".wrongstack");
3797
+ const globalRoot = process.env["WRONGSTACK_HOME"] || path5.join(os2.homedir(), ".wrongstack");
3745
3798
  const candKey = `${envDir}\0${globalRoot}`;
3746
3799
  const cached = candidateCache.get(candKey);
3747
3800
  if (cached !== void 0) return cached;
3748
- const here = path4.dirname(fileURLToPath(import.meta.url));
3801
+ const here = path5.dirname(fileURLToPath(import.meta.url));
3749
3802
  const explicitDir = envDir || void 0;
3750
3803
  const candidates = [
3751
- ...explicitDir ? [path4.resolve(explicitDir)] : [],
3752
- path4.join(globalRoot, "instructions", "agents"),
3753
- path4.resolve(here, "../../../../instructions/agents"),
3754
- path4.resolve(here, "../../../instructions/agents"),
3755
- path4.resolve(here, "../../instructions/agents"),
3756
- path4.resolve(here, "../instructions/agents"),
3757
- path4.resolve(here, "instructions/agents")
3804
+ ...explicitDir ? [path5.resolve(explicitDir)] : [],
3805
+ path5.join(globalRoot, "instructions", "agents"),
3806
+ path5.resolve(here, "../../../../instructions/agents"),
3807
+ path5.resolve(here, "../../../instructions/agents"),
3808
+ path5.resolve(here, "../../instructions/agents"),
3809
+ path5.resolve(here, "../instructions/agents"),
3810
+ path5.resolve(here, "instructions/agents")
3758
3811
  ];
3759
3812
  const ordered = candidates.sort((a, b) => Number(!isDirectory(a)) - Number(!isDirectory(b)));
3760
3813
  candidateCache.set(candKey, ordered);
@@ -5357,7 +5410,7 @@ function attachAutoExtend(events, policy = {}) {
5357
5410
 
5358
5411
  // src/coordination/delegate-tool.ts
5359
5412
  import * as fsp2 from "node:fs/promises";
5360
- import * as path5 from "node:path";
5413
+ import * as path6 from "node:path";
5361
5414
  import { randomUUID as randomUUID2 } from "node:crypto";
5362
5415
 
5363
5416
  // src/coordination/fleet.ts
@@ -5978,13 +6031,13 @@ async function readSubagentPartial(opts, subagentId) {
5978
6031
  if (!opts.sessionsRoot) return void 0;
5979
6032
  const candidates = [];
5980
6033
  if (opts.directorRunId) {
5981
- candidates.push(path5.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
6034
+ candidates.push(path6.join(opts.sessionsRoot, opts.directorRunId, `${subagentId}.jsonl`));
5982
6035
  } else {
5983
6036
  try {
5984
6037
  const entries = await fsp2.readdir(opts.sessionsRoot, { withFileTypes: true });
5985
6038
  for (const entry of entries) {
5986
6039
  if (entry.isDirectory()) {
5987
- candidates.push(path5.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
6040
+ candidates.push(path6.join(opts.sessionsRoot, entry.name, `${subagentId}.jsonl`));
5988
6041
  }
5989
6042
  }
5990
6043
  } catch {
@@ -6031,7 +6084,7 @@ async function readSubagentPartial(opts, subagentId) {
6031
6084
  // src/coordination/director.ts
6032
6085
  import { randomUUID as randomUUID6 } from "node:crypto";
6033
6086
  import * as fsp5 from "node:fs/promises";
6034
- import * as path7 from "node:path";
6087
+ import * as path8 from "node:path";
6035
6088
 
6036
6089
  // src/storage/director-state.ts
6037
6090
  import * as fsp3 from "node:fs/promises";
@@ -7101,7 +7154,7 @@ var DirectorBtwNotes = class {
7101
7154
 
7102
7155
  // src/utils/instruction-file.ts
7103
7156
  import { readFileSync as readFileSync3, statSync as statSync3 } from "node:fs";
7104
- import * as path6 from "node:path";
7157
+ import * as path7 from "node:path";
7105
7158
  import { fileURLToPath as fileURLToPath2 } from "node:url";
7106
7159
  var textCache = /* @__PURE__ */ new Map();
7107
7160
  var rootCandidates;
@@ -7111,7 +7164,7 @@ function readBundledInstructionText(relativePath) {
7111
7164
  let resolved = "";
7112
7165
  for (const root of instructionRootCandidates()) {
7113
7166
  try {
7114
- resolved = readFileSync3(path6.join(root, relativePath), "utf8").trimEnd();
7167
+ resolved = readFileSync3(path7.join(root, relativePath), "utf8").trimEnd();
7115
7168
  break;
7116
7169
  } catch {
7117
7170
  }
@@ -7127,11 +7180,11 @@ function renderInstructionTemplate(template, values) {
7127
7180
  }
7128
7181
  function instructionRootCandidates() {
7129
7182
  if (rootCandidates !== void 0) return rootCandidates;
7130
- const here = path6.dirname(fileURLToPath2(import.meta.url));
7183
+ const here = path7.dirname(fileURLToPath2(import.meta.url));
7131
7184
  const candidates = [
7132
- path6.resolve(here, "../../instructions"),
7133
- path6.resolve(here, "../instructions"),
7134
- path6.resolve(here, "instructions")
7185
+ path7.resolve(here, "../../instructions"),
7186
+ path7.resolve(here, "../instructions"),
7187
+ path7.resolve(here, "instructions")
7135
7188
  ];
7136
7189
  rootCandidates = candidates.sort((a, b) => Number(!isDirectory2(a)) - Number(!isDirectory2(b)));
7137
7190
  return rootCandidates;
@@ -10679,6 +10732,10 @@ var Director = class _Director {
10679
10732
  largeAnswerStore;
10680
10733
  /** Shared provider/model status tracker, or undefined. */
10681
10734
  statusTracker;
10735
+ /** Session/leader's provider id — absolute last-resort fallback for every spawn. */
10736
+ sessionProvider;
10737
+ /** Session/leader's model id — paired with sessionProvider above. */
10738
+ sessionModel;
10682
10739
  constructor(opts) {
10683
10740
  this.id = opts.config.coordinatorId || randomUUID6();
10684
10741
  this.brain = opts.brain;
@@ -10719,6 +10776,8 @@ var Director = class _Director {
10719
10776
  this.fleetManager = opts.fleetManager;
10720
10777
  this.statusTracker = opts.statusTracker;
10721
10778
  this.logger = opts.logger;
10779
+ this.sessionProvider = opts.sessionProvider;
10780
+ this.sessionModel = opts.sessionModel;
10722
10781
  if (this.sharedScratchpadPath) {
10723
10782
  void fsp5.mkdir(this.sharedScratchpadPath, { recursive: true }).catch((err) => this.logShutdownError("shared_scratchpad_mkdir", err));
10724
10783
  }
@@ -11227,20 +11286,31 @@ var Director = class _Director {
11227
11286
  return result.subagentId;
11228
11287
  }
11229
11288
  resolveSpawnModel(config) {
11289
+ if (config.provider?.trim() === "") config.provider = void 0;
11290
+ if (config.model?.trim() === "") config.model = void 0;
11230
11291
  if (!config.model && this.modelMatrix) {
11231
11292
  const matrix = typeof this.modelMatrix === "function" ? this.modelMatrix() : this.modelMatrix;
11232
11293
  const resolution = resolveModelMatrixResolution(matrix, config.role);
11233
11294
  const entry = resolution?.source === "default" && roleNeedsIndependentReviewModel(config.role) ? void 0 : resolution?.entry;
11234
- if (entry?.model) {
11235
- config.model = entry.model;
11295
+ if (entry) {
11296
+ if (entry.model) config.model = entry.model;
11236
11297
  if (entry.provider) config.provider = entry.provider;
11237
11298
  if (entry.fallbackProfile) config.fallbackProfile = entry.fallbackProfile;
11238
11299
  if (entry.modelRuntime) config.modelRuntime = entry.modelRuntime;
11239
- } else if (entry?.fallbackProfile) {
11240
- config.fallbackProfile = entry.fallbackProfile;
11241
- if (entry.modelRuntime) config.modelRuntime = entry.modelRuntime;
11242
11300
  }
11243
11301
  }
11302
+ if (!config.provider && this.sessionProvider) {
11303
+ config.provider = this.sessionProvider;
11304
+ this.logger?.info(
11305
+ `spawn: provider="${config.provider}" for role "${config.role ?? "?"}" fell back to session provider (matrix resolution left it undefined)`
11306
+ );
11307
+ }
11308
+ if (!config.model && this.sessionModel) {
11309
+ config.model = this.sessionModel;
11310
+ this.logger?.info(
11311
+ `spawn: model="${config.model}" for role "${config.role ?? "?"}" fell back to session model (matrix resolution left it undefined)`
11312
+ );
11313
+ }
11244
11314
  if (this.statusTracker && config.provider && config.model) {
11245
11315
  if (!this.statusTracker.isAvailable(config.provider, config.model)) {
11246
11316
  this.logger?.warn(
@@ -11455,7 +11525,7 @@ var Director = class _Director {
11455
11525
  })),
11456
11526
  usage: this.usage.snapshot()
11457
11527
  };
11458
- await fsp5.mkdir(path7.dirname(this.manifestPath), { recursive: true });
11528
+ await fsp5.mkdir(path8.dirname(this.manifestPath), { recursive: true });
11459
11529
  await atomicWrite(this.manifestPath, JSON.stringify(manifest, null, 2), { mode: 384 });
11460
11530
  return this.manifestPath;
11461
11531
  }
@@ -11859,7 +11929,7 @@ var Director = class _Director {
11859
11929
  */
11860
11930
  async readSession(subagentId, tail) {
11861
11931
  if (!this.sessionsRoot) return null;
11862
- const filePath = path7.join(this.sessionsRoot, this.directorRunId, `${subagentId}.jsonl`);
11932
+ const filePath = path8.join(this.sessionsRoot, this.directorRunId, `${subagentId}.jsonl`);
11863
11933
  let raw;
11864
11934
  try {
11865
11935
  raw = await fsp5.readFile(filePath, "utf8");
@@ -12013,19 +12083,19 @@ var Director = class _Director {
12013
12083
  };
12014
12084
 
12015
12085
  // src/coordination/director-session.ts
12016
- import * as path12 from "node:path";
12086
+ import * as path13 from "node:path";
12017
12087
 
12018
12088
  // src/storage/session-store.ts
12019
12089
  import { createHash as createHash4 } from "node:crypto";
12020
12090
  import { createReadStream } from "node:fs";
12021
12091
  import * as fsp9 from "node:fs/promises";
12022
- import * as path11 from "node:path";
12092
+ import * as path12 from "node:path";
12023
12093
  import { createInterface } from "node:readline";
12024
12094
 
12025
12095
  // src/storage/file-session-writer.ts
12026
12096
  import { closeSync, fsyncSync, openSync, writeSync } from "node:fs";
12027
12097
  import * as fsp6 from "node:fs/promises";
12028
- import * as path8 from "node:path";
12098
+ import * as path9 from "node:path";
12029
12099
 
12030
12100
  // src/storage/session-helpers.ts
12031
12101
  function userInputTitle(content) {
@@ -12042,7 +12112,7 @@ var FileSessionWriter = class _FileSessionWriter {
12042
12112
  this.meta = meta;
12043
12113
  this.events = events;
12044
12114
  this.resumed = opts.resumed ?? false;
12045
- this.manifestFile = opts.dir ? path8.join(opts.dir, `${path8.basename(id)}.summary.json`) : "";
12115
+ this.manifestFile = opts.dir ? path9.join(opts.dir, `${path9.basename(id)}.summary.json`) : "";
12046
12116
  this.filePath = opts.filePath ?? "";
12047
12117
  this.secretScrubber = opts.secretScrubber;
12048
12118
  this.checkpointCas = opts.checkpointCas;
@@ -12824,7 +12894,7 @@ var FileSessionWriter = class _FileSessionWriter {
12824
12894
  import { spawn } from "node:child_process";
12825
12895
  import { createHash as createHash2, randomUUID as randomUUID7 } from "node:crypto";
12826
12896
  import * as fsp7 from "node:fs/promises";
12827
- import * as path9 from "node:path";
12897
+ import * as path10 from "node:path";
12828
12898
 
12829
12899
  // src/storage/storage-concurrency.ts
12830
12900
  async function mapWithConcurrency(items, concurrency, mapper) {
@@ -12855,29 +12925,29 @@ function sha256(content) {
12855
12925
  return createHash2("sha256").update(content).digest("hex");
12856
12926
  }
12857
12927
  function isInside(root, target) {
12858
- const relative5 = path9.relative(root, target);
12859
- return relative5 === "" || !relative5.startsWith("..") && !path9.isAbsolute(relative5);
12928
+ const relative6 = path10.relative(root, target);
12929
+ return relative6 === "" || !relative6.startsWith("..") && !path10.isAbsolute(relative6);
12860
12930
  }
12861
12931
  function normalizeRelative(input) {
12862
- if (!input || path9.isAbsolute(input)) return null;
12932
+ if (!input || path10.isAbsolute(input)) return null;
12863
12933
  const normalized = input.replace(/\\/g, "/").replace(/^\.\//, "");
12864
- const resolved = path9.posix.normalize(normalized);
12934
+ const resolved = path10.posix.normalize(normalized);
12865
12935
  if (!resolved || resolved === "." || resolved === ".." || resolved.startsWith("../")) return null;
12866
12936
  return resolved;
12867
12937
  }
12868
12938
  function parseNulPaths(output) {
12869
12939
  return output.split("\0").map(normalizeRelative).filter((value) => value !== null);
12870
12940
  }
12871
- function isWrongStackWorktreePath(relative5) {
12872
- return relative5 === ".wrongstack/worktrees" || relative5.startsWith(".wrongstack/worktrees/");
12941
+ function isWrongStackWorktreePath(relative6) {
12942
+ return relative6 === ".wrongstack/worktrees" || relative6.startsWith(".wrongstack/worktrees/");
12873
12943
  }
12874
12944
  var SessionCheckpointCas = class {
12875
12945
  rootDir;
12876
12946
  projectRoot;
12877
12947
  runGit;
12878
12948
  constructor(opts) {
12879
- this.rootDir = path9.resolve(opts.rootDir);
12880
- this.projectRoot = path9.resolve(opts.projectRoot);
12949
+ this.rootDir = path10.resolve(opts.rootDir);
12950
+ this.projectRoot = path10.resolve(opts.projectRoot);
12881
12951
  this.runGit = opts.runGit ?? defaultRunGit;
12882
12952
  }
12883
12953
  async capture(_sessionId, _promptIndex) {
@@ -12895,46 +12965,46 @@ var SessionCheckpointCas = class {
12895
12965
  }
12896
12966
  const relativePaths = [
12897
12967
  .../* @__PURE__ */ new Set([...parseNulPaths(tracked.stdout), ...parseNulPaths(untracked.stdout)])
12898
- ].filter((relative5) => !isWrongStackWorktreePath(relative5)).sort();
12968
+ ].filter((relative6) => !isWrongStackWorktreePath(relative6)).sort();
12899
12969
  const unresolved = [];
12900
12970
  let scheduledBytes = 0;
12901
12971
  const captured = await mapWithConcurrency(
12902
12972
  relativePaths,
12903
12973
  CAPTURE_CONCURRENCY,
12904
- async (relative5) => {
12905
- const absolute = path9.resolve(this.projectRoot, ...relative5.split("/"));
12974
+ async (relative6) => {
12975
+ const absolute = path10.resolve(this.projectRoot, ...relative6.split("/"));
12906
12976
  if (!isInside(this.projectRoot, absolute)) {
12907
- unresolved.push({ path: relative5, reason: "path escapes project root" });
12977
+ unresolved.push({ path: relative6, reason: "path escapes project root" });
12908
12978
  return null;
12909
12979
  }
12910
12980
  try {
12911
12981
  const stat13 = await fsp7.lstat(absolute);
12912
12982
  if (stat13.isSymbolicLink()) {
12913
12983
  const linkTarget = await fsp7.readlink(absolute);
12914
- const resolvedLink = path9.resolve(path9.dirname(absolute), linkTarget);
12915
- if (path9.isAbsolute(linkTarget) || !isInside(this.projectRoot, resolvedLink)) {
12984
+ const resolvedLink = path10.resolve(path10.dirname(absolute), linkTarget);
12985
+ if (path10.isAbsolute(linkTarget) || !isInside(this.projectRoot, resolvedLink)) {
12916
12986
  unresolved.push({
12917
- path: relative5,
12987
+ path: relative6,
12918
12988
  reason: "symlink target escapes project root"
12919
12989
  });
12920
12990
  return null;
12921
12991
  }
12922
- return { path: relative5, state: "symlink", linkTarget };
12992
+ return { path: relative6, state: "symlink", linkTarget };
12923
12993
  }
12924
12994
  if (!stat13.isFile()) {
12925
- unresolved.push({ path: relative5, reason: "changed path is not a regular file" });
12995
+ unresolved.push({ path: relative6, reason: "changed path is not a regular file" });
12926
12996
  return null;
12927
12997
  }
12928
12998
  if (stat13.size > MAX_BLOB_BYTES) {
12929
12999
  unresolved.push({
12930
- path: relative5,
13000
+ path: relative6,
12931
13001
  reason: `file exceeds ${MAX_BLOB_BYTES}-byte checkpoint blob limit`
12932
13002
  });
12933
13003
  return null;
12934
13004
  }
12935
13005
  if (scheduledBytes + stat13.size > MAX_CHECKPOINT_BYTES) {
12936
13006
  unresolved.push({
12937
- path: relative5,
13007
+ path: relative6,
12938
13008
  reason: `checkpoint exceeds ${MAX_CHECKPOINT_BYTES}-byte aggregate blob limit`
12939
13009
  });
12940
13010
  return null;
@@ -12943,12 +13013,12 @@ var SessionCheckpointCas = class {
12943
13013
  const content = await fsp7.readFile(absolute);
12944
13014
  const blobHash = sha256(content);
12945
13015
  await this.putBlob(blobHash, content);
12946
- return { path: relative5, state: "file", blobHash, mode: stat13.mode & 511 };
13016
+ return { path: relative6, state: "file", blobHash, mode: stat13.mode & 511 };
12947
13017
  } catch (err) {
12948
13018
  if (err.code === "ENOENT") {
12949
- return { path: relative5, state: "absent" };
13019
+ return { path: relative6, state: "absent" };
12950
13020
  }
12951
- unresolved.push({ path: relative5, reason: toErrorMessage(err) });
13021
+ unresolved.push({ path: relative6, reason: toErrorMessage(err) });
12952
13022
  return null;
12953
13023
  }
12954
13024
  }
@@ -12974,7 +13044,7 @@ var SessionCheckpointCas = class {
12974
13044
  };
12975
13045
  }
12976
13046
  async materialize(checkpoint, targetRoot) {
12977
- const target = path9.resolve(targetRoot);
13047
+ const target = path10.resolve(targetRoot);
12978
13048
  if (target === this.projectRoot) {
12979
13049
  throw new Error("Refusing to materialize a workspace checkpoint over the parent project root");
12980
13050
  }
@@ -13014,10 +13084,10 @@ var SessionCheckpointCas = class {
13014
13084
  try {
13015
13085
  const output = await this.safeOutputPath(target, realTarget, entry.path);
13016
13086
  if (entry.state === "symlink") {
13017
- if (path9.isAbsolute(entry.linkTarget)) {
13087
+ if (path10.isAbsolute(entry.linkTarget)) {
13018
13088
  throw new Error("absolute symlink target refused");
13019
13089
  }
13020
- const resolvedLink = path9.resolve(path9.dirname(output), entry.linkTarget);
13090
+ const resolvedLink = path10.resolve(path10.dirname(output), entry.linkTarget);
13021
13091
  if (!isInside(target, resolvedLink)) throw new Error("symlink target escapes checkpoint root");
13022
13092
  }
13023
13093
  prepared.push({
@@ -13045,7 +13115,7 @@ var SessionCheckpointCas = class {
13045
13115
  await fsp7.unlink(output).catch((err) => {
13046
13116
  if (err.code !== "ENOENT") throw err;
13047
13117
  });
13048
- await fsp7.mkdir(path9.dirname(output), { recursive: true });
13118
+ await fsp7.mkdir(path10.dirname(output), { recursive: true });
13049
13119
  await fsp7.symlink(entry.linkTarget, output);
13050
13120
  writtenFiles.push(entry.path);
13051
13121
  continue;
@@ -13062,15 +13132,15 @@ var SessionCheckpointCas = class {
13062
13132
  }
13063
13133
  objectPath(hash2) {
13064
13134
  if (!HASH_RE.test(hash2)) throw new Error(`Invalid CAS object hash: ${hash2}`);
13065
- return path9.join(this.rootDir, "objects", hash2.slice(0, 2), hash2.slice(2));
13135
+ return path10.join(this.rootDir, "objects", hash2.slice(0, 2), hash2.slice(2));
13066
13136
  }
13067
13137
  manifestPath(hash2) {
13068
13138
  if (!HASH_RE.test(hash2)) throw new Error(`Invalid checkpoint manifest hash: ${hash2}`);
13069
- return path9.join(this.rootDir, "manifests", `${hash2}.json`);
13139
+ return path10.join(this.rootDir, "manifests", `${hash2}.json`);
13070
13140
  }
13071
13141
  async putBlob(hash2, content) {
13072
13142
  const target = this.objectPath(hash2);
13073
- await fsp7.mkdir(path9.dirname(target), { recursive: true });
13143
+ await fsp7.mkdir(path10.dirname(target), { recursive: true });
13074
13144
  try {
13075
13145
  const existing = await fsp7.readFile(target);
13076
13146
  if (sha256(existing) !== hash2) throw new Error(`Corrupt CAS object collision: ${hash2}`);
@@ -13078,9 +13148,9 @@ var SessionCheckpointCas = class {
13078
13148
  } catch (err) {
13079
13149
  if (err.code !== "ENOENT") throw err;
13080
13150
  }
13081
- const temp = path9.join(
13082
- path9.dirname(target),
13083
- `.${path9.basename(target)}.${process.pid}.${randomUUID7()}.tmp`
13151
+ const temp = path10.join(
13152
+ path10.dirname(target),
13153
+ `.${path10.basename(target)}.${process.pid}.${randomUUID7()}.tmp`
13084
13154
  );
13085
13155
  let handle;
13086
13156
  try {
@@ -13138,10 +13208,10 @@ var SessionCheckpointCas = class {
13138
13208
  }
13139
13209
  return parsed;
13140
13210
  }
13141
- async safeOutputPath(target, realTarget, relative5) {
13142
- const normalized = normalizeRelative(relative5);
13211
+ async safeOutputPath(target, realTarget, relative6) {
13212
+ const normalized = normalizeRelative(relative6);
13143
13213
  if (!normalized) throw new Error("invalid relative path");
13144
- const output = path9.resolve(target, ...normalized.split("/"));
13214
+ const output = path10.resolve(target, ...normalized.split("/"));
13145
13215
  if (!isInside(target, output)) throw new Error("path escapes checkpoint target");
13146
13216
  let probe = output;
13147
13217
  for (; ; ) {
@@ -13151,7 +13221,7 @@ var SessionCheckpointCas = class {
13151
13221
  return output;
13152
13222
  } catch (err) {
13153
13223
  if (err.code !== "ENOENT") throw err;
13154
- const parent = path9.dirname(probe);
13224
+ const parent = path10.dirname(probe);
13155
13225
  if (parent === probe) throw err;
13156
13226
  probe = parent;
13157
13227
  }
@@ -13217,13 +13287,13 @@ function generateSessionId(startedAt, _model) {
13217
13287
  // src/storage/session-resume-validation.ts
13218
13288
  import { createHash as createHash3 } from "node:crypto";
13219
13289
  import * as fsp8 from "node:fs/promises";
13220
- import * as path10 from "node:path";
13290
+ import * as path11 from "node:path";
13221
13291
  var MAX_REVALIDATE_BYTES = 5 * 1024 * 1024;
13222
13292
  var VALIDATION_CONCURRENCY = 8;
13223
13293
  var NOTICE_PATH_LIMIT = 20;
13224
13294
  function isInside2(root, target) {
13225
- const relative5 = path10.relative(root, target);
13226
- return relative5 === "" || !relative5.startsWith("..") && !path10.isAbsolute(relative5);
13295
+ const relative6 = path11.relative(root, target);
13296
+ return relative6 === "" || !relative6.startsWith("..") && !path11.isAbsolute(relative6);
13227
13297
  }
13228
13298
  function errno(err) {
13229
13299
  return err && typeof err === "object" && "code" in err ? String(err.code) : void 0;
@@ -13234,7 +13304,7 @@ function latestObservations(events, projectRoot) {
13234
13304
  if (event.type !== "file_observation" || typeof event.path !== "string" || event.path.length === 0 || typeof event.hash !== "string" || !/^[a-f\d]{64}$/i.test(event.hash)) {
13235
13305
  continue;
13236
13306
  }
13237
- const normalized = path10.resolve(projectRoot, event.path);
13307
+ const normalized = path11.resolve(projectRoot, event.path);
13238
13308
  latest.set(normalized, {
13239
13309
  path: normalized,
13240
13310
  hash: event.hash.toLowerCase(),
@@ -13292,7 +13362,7 @@ async function validateOne(observation, lexicalRoot, realRoot) {
13292
13362
  }
13293
13363
  }
13294
13364
  async function validateResumeFileObservations(events, projectRoot) {
13295
- const lexicalRoot = path10.resolve(projectRoot);
13365
+ const lexicalRoot = path11.resolve(projectRoot);
13296
13366
  const realRoot = await fsp8.realpath(lexicalRoot).catch(() => lexicalRoot);
13297
13367
  const observations = latestObservations(events, lexicalRoot);
13298
13368
  const results = await mapWithConcurrency(
@@ -13308,10 +13378,10 @@ async function validateResumeFileObservations(events, projectRoot) {
13308
13378
  }
13309
13379
  function formatResumeValidationNotice(validation, projectRoot) {
13310
13380
  if (validation.staleFiles.length === 0) return null;
13311
- const root = path10.resolve(projectRoot);
13381
+ const root = path11.resolve(projectRoot);
13312
13382
  const shown = validation.staleFiles.slice(0, NOTICE_PATH_LIMIT).map((entry) => {
13313
- const relative5 = path10.relative(root, entry.path);
13314
- const display = isInside2(root, entry.path) ? relative5 || "." : entry.path;
13383
+ const relative6 = path11.relative(root, entry.path);
13384
+ const display = isInside2(root, entry.path) ? relative6 || "." : entry.path;
13315
13385
  return `- ${JSON.stringify(display)} [${entry.status}]`;
13316
13386
  });
13317
13387
  const omitted = validation.staleFiles.length - shown.length;
@@ -13444,9 +13514,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
13444
13514
  static LIST_SCAN_CONCURRENCY = 32;
13445
13515
  constructor(opts) {
13446
13516
  this.dir = opts.dir;
13447
- this.projectRoot = opts.projectRoot ? path11.resolve(opts.projectRoot) : void 0;
13517
+ this.projectRoot = opts.projectRoot ? path12.resolve(opts.projectRoot) : void 0;
13448
13518
  this.checkpointCas = this.projectRoot ? new SessionCheckpointCas({
13449
- rootDir: path11.join(this.dir, "_cas"),
13519
+ rootDir: path12.join(this.dir, "_cas"),
13450
13520
  projectRoot: this.projectRoot
13451
13521
  }) : void 0;
13452
13522
  this.events = opts.events;
@@ -13512,17 +13582,17 @@ var DefaultSessionStore = class _DefaultSessionStore {
13512
13582
  }
13513
13583
  /** Absolute path to the session index file. */
13514
13584
  get indexFile() {
13515
- return path11.join(this.dir, "_index.jsonl");
13585
+ return path12.join(this.dir, "_index.jsonl");
13516
13586
  }
13517
13587
  /** Join session ID to its absolute path within the store directory. */
13518
13588
  sessionPath(id, ext) {
13519
13589
  return sessionScopedPath(this.dir, id, ext);
13520
13590
  }
13521
13591
  shardManifestPath(shardKey) {
13522
- return shardKey ? path11.join(this.dir, shardKey, "_manifest.json") : path11.join(this.dir, "_manifest.json");
13592
+ return shardKey ? path12.join(this.dir, shardKey, "_manifest.json") : path12.join(this.dir, "_manifest.json");
13523
13593
  }
13524
13594
  shardKeyForSessionId(id) {
13525
- const dirName = path11.dirname(id);
13595
+ const dirName = path12.dirname(id);
13526
13596
  return dirName === "." ? "" : dirName;
13527
13597
  }
13528
13598
  invalidateShardManifestBySessionId(id) {
@@ -13534,7 +13604,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
13534
13604
  * subdirectory so sessions group naturally by day.
13535
13605
  */
13536
13606
  async ensureShardDir(id) {
13537
- const dirPath = path11.dirname(sessionScopedPath(this.dir, id, ""));
13607
+ const dirPath = path12.dirname(sessionScopedPath(this.dir, id, ""));
13538
13608
  await ensureDir(dirPath);
13539
13609
  return dirPath;
13540
13610
  }
@@ -13698,7 +13768,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
13698
13768
  // Shard directory (sessions/<date>/) — must match create() so the
13699
13769
  // .summary.json sidecar lands next to the JSONL instead of the
13700
13770
  // sessions root (where summaryFor() would never find it).
13701
- dir: path11.dirname(file),
13771
+ dir: path12.dirname(file),
13702
13772
  filePath: file,
13703
13773
  secretScrubber: this.secretScrubber,
13704
13774
  checkpointCas: this.checkpointCas,
@@ -14319,7 +14389,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
14319
14389
  return entry;
14320
14390
  }
14321
14391
  async collectSessionFilesInShard(shardKey) {
14322
- const dir = shardKey ? path11.join(this.dir, shardKey) : this.dir;
14392
+ const dir = shardKey ? path12.join(this.dir, shardKey) : this.dir;
14323
14393
  const entries = await this.collectSessionFiles(dir, shardKey);
14324
14394
  return shardKey ? entries.filter((entry) => entry.id.startsWith(`${shardKey}/`)) : entries.filter((entry) => !entry.id.includes("/"));
14325
14395
  }
@@ -14342,13 +14412,13 @@ var DefaultSessionStore = class _DefaultSessionStore {
14342
14412
  if (entry.name === "_index.jsonl") continue;
14343
14413
  const base = entry.name.replace(/\.jsonl$/, "");
14344
14414
  const id = prefix ? `${prefix}/${base}` : base;
14345
- files.push({ id, filePath: path11.join(dir, entry.name) });
14415
+ files.push({ id, filePath: path12.join(dir, entry.name) });
14346
14416
  }
14347
14417
  }
14348
14418
  const childFileArrays = await Promise.all(
14349
14419
  dirEntries.map((entry) => {
14350
14420
  const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
14351
- return this.collectSessionFiles(path11.join(dir, entry.name), childPrefix, depth + 1);
14421
+ return this.collectSessionFiles(path12.join(dir, entry.name), childPrefix, depth + 1);
14352
14422
  })
14353
14423
  );
14354
14424
  return [...childFileArrays.flat(), ...files];
@@ -14381,7 +14451,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
14381
14451
  const childIdArrays = await Promise.all(
14382
14452
  dirEntries.map((entry) => {
14383
14453
  const childPrefix = depth === 0 ? entry.name : `${prefix}/${entry.name}`;
14384
- return this.collectSessionIds(path11.join(dir, entry.name), childPrefix, depth + 1);
14454
+ return this.collectSessionIds(path12.join(dir, entry.name), childPrefix, depth + 1);
14385
14455
  })
14386
14456
  );
14387
14457
  return [...childIdArrays.flat(), ...fileIds];
@@ -14496,9 +14566,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
14496
14566
  async deleteSession(id) {
14497
14567
  const jsonlPath = this.sessionPath(id, ".jsonl");
14498
14568
  const summaryPath = this.sessionPath(id, ".summary.json");
14499
- const shardDir = path11.dirname(jsonlPath);
14500
- const base = path11.basename(id);
14501
- const sessDir = path11.join(shardDir, base);
14569
+ const shardDir = path12.dirname(jsonlPath);
14570
+ const base = path12.basename(id);
14571
+ const sessDir = path12.join(shardDir, base);
14502
14572
  const deletions = [
14503
14573
  fsp9.unlink(jsonlPath),
14504
14574
  fsp9.unlink(summaryPath),
@@ -14543,7 +14613,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
14543
14613
  */
14544
14614
  async readActiveSessionId() {
14545
14615
  try {
14546
- const raw = await fsp9.readFile(path11.join(this.dir, "active.json"), "utf8");
14616
+ const raw = await fsp9.readFile(path12.join(this.dir, "active.json"), "utf8");
14547
14617
  const active = JSON.parse(raw);
14548
14618
  return active.sessionId ?? null;
14549
14619
  } catch {
@@ -14606,7 +14676,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
14606
14676
  const activeSessionId = await this.readActiveSessionId();
14607
14677
  const isPrunableJsonl = (name) => name.endsWith(".jsonl") && name !== "_index.jsonl" && name !== "_mailbox.jsonl" && !name.endsWith(".replay.jsonl") && !name.endsWith(".audit.jsonl");
14608
14678
  const pruneFile = async (dir, name, prefix) => {
14609
- const jsonlPath = path11.join(dir, name);
14679
+ const jsonlPath = path12.join(dir, name);
14610
14680
  try {
14611
14681
  const stat13 = await fsp9.stat(jsonlPath);
14612
14682
  if (stat13.mtimeMs >= cutoff) return;
@@ -14626,7 +14696,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
14626
14696
  continue;
14627
14697
  }
14628
14698
  if (!entry.isDirectory()) continue;
14629
- const dateDir = path11.join(this.dir, entry.name);
14699
+ const dateDir = path12.join(this.dir, entry.name);
14630
14700
  const files = await fsp9.readdir(dateDir, { withFileTypes: true }).catch(() => []);
14631
14701
  for (const file of files) {
14632
14702
  if (!file.isFile() || !isPrunableJsonl(file.name)) continue;
@@ -14638,7 +14708,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
14638
14708
  }
14639
14709
  for (const entry of entries) {
14640
14710
  if (!entry.isDirectory()) continue;
14641
- const dateDir = path11.join(this.dir, entry.name);
14711
+ const dateDir = path12.join(this.dir, entry.name);
14642
14712
  try {
14643
14713
  const remaining = await fsp9.readdir(dateDir);
14644
14714
  if (remaining.length === 0) {
@@ -14769,9 +14839,9 @@ function makeDirectorSessionFactory(opts) {
14769
14839
  let dir;
14770
14840
  if (opts.store) {
14771
14841
  store = opts.store;
14772
- dir = opts.sessionsRoot ? path12.join(opts.sessionsRoot, runId) : "(caller-managed)";
14842
+ dir = opts.sessionsRoot ? path13.join(opts.sessionsRoot, runId) : "(caller-managed)";
14773
14843
  } else if (opts.sessionsRoot) {
14774
- dir = path12.join(opts.sessionsRoot, runId);
14844
+ dir = path13.join(opts.sessionsRoot, runId);
14775
14845
  store = new DefaultSessionStore({ dir });
14776
14846
  } else {
14777
14847
  throw new Error("makeDirectorSessionFactory requires either `store` or `sessionsRoot`");
@@ -16117,8 +16187,8 @@ function isDesignStack(v) {
16117
16187
 
16118
16188
  // src/execution/design-kit-loader.ts
16119
16189
  import { existsSync } from "node:fs";
16120
- import * as fs3 from "node:fs/promises";
16121
- import * as path13 from "node:path";
16190
+ import * as fs4 from "node:fs/promises";
16191
+ import * as path14 from "node:path";
16122
16192
  import { fileURLToPath as fileURLToPath3 } from "node:url";
16123
16193
  var KIT_FILE = "KIT.md";
16124
16194
  var TOKENS_FILE = "tokens.json";
@@ -16225,15 +16295,15 @@ var DefaultDesignKitLoader = class {
16225
16295
  for (const { dir, source } of this.dirs) {
16226
16296
  let entries;
16227
16297
  try {
16228
- entries = await fs3.readdir(dir, { withFileTypes: true });
16298
+ entries = await fs4.readdir(dir, { withFileTypes: true });
16229
16299
  } catch {
16230
16300
  continue;
16231
16301
  }
16232
16302
  for (const e of entries) {
16233
16303
  if (!e.isDirectory()) continue;
16234
- const kitFile = path13.join(dir, e.name, KIT_FILE);
16304
+ const kitFile = path14.join(dir, e.name, KIT_FILE);
16235
16305
  try {
16236
- const raw = await fs3.readFile(kitFile, "utf8");
16306
+ const raw = await fs4.readFile(kitFile, "utf8");
16237
16307
  const fm = parseKitFrontmatter(raw);
16238
16308
  const id = fm.id ?? e.name;
16239
16309
  if (!fm.name) continue;
@@ -16294,7 +16364,7 @@ var DefaultDesignKitLoader = class {
16294
16364
  if (cached !== void 0) return cached;
16295
16365
  const m = await this.find(id);
16296
16366
  if (!m) throw new Error(`Design kit "${id}" not found`);
16297
- const raw = await fs3.readFile(m.path, "utf8");
16367
+ const raw = await fs4.readFile(m.path, "utf8");
16298
16368
  const body = narrowStackSections(stripFrontmatter(raw), stack);
16299
16369
  this.bodyCache.set(key, body);
16300
16370
  return body;
@@ -16305,9 +16375,9 @@ var DefaultDesignKitLoader = class {
16305
16375
  const m = await this.find(id);
16306
16376
  let tokens;
16307
16377
  if (m) {
16308
- const tokensPath = path13.join(path13.dirname(m.path), TOKENS_FILE);
16378
+ const tokensPath = path14.join(path14.dirname(m.path), TOKENS_FILE);
16309
16379
  try {
16310
- const raw = await fs3.readFile(tokensPath, "utf8");
16380
+ const raw = await fs4.readFile(tokensPath, "utf8");
16311
16381
  const parsed = JSON.parse(raw);
16312
16382
  tokens = parsed;
16313
16383
  } catch {
@@ -16332,12 +16402,12 @@ var DefaultDesignKitLoader = class {
16332
16402
  };
16333
16403
  function resolveBundledDesignKitsDir() {
16334
16404
  try {
16335
- const here = path13.dirname(fileURLToPath3(import.meta.url));
16405
+ const here = path14.dirname(fileURLToPath3(import.meta.url));
16336
16406
  const candidates = [
16337
- path13.join(here, "design-kits"),
16338
- path13.join(here, "..", "design-kits"),
16339
- path13.join(here, "..", "..", "design-kits"),
16340
- path13.join(here, "..", "..", "..", "design-kits")
16407
+ path14.join(here, "design-kits"),
16408
+ path14.join(here, "..", "design-kits"),
16409
+ path14.join(here, "..", "..", "design-kits"),
16410
+ path14.join(here, "..", "..", "..", "design-kits")
16341
16411
  ];
16342
16412
  for (const c of candidates) {
16343
16413
  if (existsSync(c)) return c;
@@ -16365,11 +16435,11 @@ function _resetDesignKitLoaderMemo() {
16365
16435
 
16366
16436
  // src/execution/design-project-store.ts
16367
16437
  import { existsSync as existsSync2 } from "node:fs";
16368
- import * as fs4 from "node:fs/promises";
16369
- import * as path14 from "node:path";
16438
+ import * as fs5 from "node:fs/promises";
16439
+ import * as path15 from "node:path";
16370
16440
  var DESIGN_DIR = ".design";
16371
16441
  function designProjectDir(projectRoot) {
16372
- return path14.join(projectRoot, DESIGN_DIR);
16442
+ return path15.join(projectRoot, DESIGN_DIR);
16373
16443
  }
16374
16444
  var RULE_FILES = ["rules.md", "RULES.md", "design.md"];
16375
16445
  var rulesCache = /* @__PURE__ */ new Map();
@@ -16378,7 +16448,7 @@ async function loadProjectDesignRules(projectRoot) {
16378
16448
  let rules;
16379
16449
  for (const name of RULE_FILES) {
16380
16450
  try {
16381
- const txt = await fs4.readFile(path14.join(designProjectDir(projectRoot), name), "utf8");
16451
+ const txt = await fs5.readFile(path15.join(designProjectDir(projectRoot), name), "utf8");
16382
16452
  if (txt.trim()) {
16383
16453
  rules = txt.trim();
16384
16454
  break;
@@ -16399,7 +16469,7 @@ function parseOverrides(value) {
16399
16469
  }
16400
16470
  async function loadActiveKit(projectRoot) {
16401
16471
  try {
16402
- const raw = await fs4.readFile(path14.join(designProjectDir(projectRoot), "active.json"), "utf8");
16472
+ const raw = await fs5.readFile(path15.join(designProjectDir(projectRoot), "active.json"), "utf8");
16403
16473
  const parsed = JSON.parse(raw);
16404
16474
  if (parsed && typeof parsed.kit === "string") {
16405
16475
  return {
@@ -16432,11 +16502,11 @@ function applyTokenOverrides(tokens, overrides) {
16432
16502
  }
16433
16503
  async function ensureDesignDir(projectRoot) {
16434
16504
  const dir = designProjectDir(projectRoot);
16435
- await fs4.mkdir(dir, { recursive: true });
16436
- const gi = path14.join(dir, ".gitignore");
16505
+ await fs5.mkdir(dir, { recursive: true });
16506
+ const gi = path15.join(dir, ".gitignore");
16437
16507
  if (!existsSync2(gi)) {
16438
16508
  try {
16439
- await fs4.writeFile(gi, "*\n");
16509
+ await fs5.writeFile(gi, "*\n");
16440
16510
  } catch {
16441
16511
  }
16442
16512
  }
@@ -16447,11 +16517,11 @@ async function recordKitChoice(projectRoot, kit, stack, source, isoTime, overrid
16447
16517
  const dir = await ensureDesignDir(projectRoot);
16448
16518
  const record = { kit, stack: stack ?? null };
16449
16519
  if (overrides && Object.keys(overrides).length > 0) record.overrides = overrides;
16450
- await fs4.writeFile(path14.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
16520
+ await fs5.writeFile(path15.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
16451
16521
  `);
16452
16522
  const line = `- ${isoTime} \xB7 kit=${kit}${stack ? ` stack=${stack}` : ""} \xB7 via=${source}
16453
16523
  `;
16454
- await fs4.appendFile(path14.join(dir, "decisions.md"), line);
16524
+ await fs5.appendFile(path15.join(dir, "decisions.md"), line);
16455
16525
  } catch {
16456
16526
  }
16457
16527
  }
@@ -16467,11 +16537,11 @@ async function recordOverrides(projectRoot, patch, isoTime) {
16467
16537
  const dir = await ensureDesignDir(projectRoot);
16468
16538
  const record = { kit: active.kit, stack: active.stack ?? null };
16469
16539
  if (Object.keys(merged).length > 0) record.overrides = merged;
16470
- await fs4.writeFile(path14.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
16540
+ await fs5.writeFile(path15.join(dir, "active.json"), `${JSON.stringify(record, null, 2)}
16471
16541
  `);
16472
16542
  const keys = Object.keys(patch).join(",");
16473
- await fs4.appendFile(
16474
- path14.join(dir, "decisions.md"),
16543
+ await fs5.appendFile(
16544
+ path15.join(dir, "decisions.md"),
16475
16545
  `- ${isoTime} \xB7 kit=${active.kit} \xB7 override=${keys} \xB7 via=set
16476
16546
  `
16477
16547
  );
@@ -16481,7 +16551,7 @@ async function recordOverrides(projectRoot, patch, isoTime) {
16481
16551
  }
16482
16552
  async function clearPersistedActiveKit(projectRoot) {
16483
16553
  try {
16484
- await fs4.rm(path14.join(designProjectDir(projectRoot), "active.json"), { force: true });
16554
+ await fs5.rm(path15.join(designProjectDir(projectRoot), "active.json"), { force: true });
16485
16555
  } catch {
16486
16556
  }
16487
16557
  }
@@ -16597,12 +16667,12 @@ function verifyFiles(tokens, files) {
16597
16667
  const violations = [];
16598
16668
  let onPalette = 0;
16599
16669
  let offPalette = 0;
16600
- for (const { path: path29, text } of files) {
16670
+ for (const { path: path30, text } of files) {
16601
16671
  const lines = text.split("\n");
16602
16672
  lines.forEach((lineText, i) => {
16603
16673
  const lineNo = i + 1;
16604
16674
  const flag = (snippet, reason) => {
16605
- violations.push({ file: path29, line: lineNo, snippet: snippet.slice(0, 80), reason });
16675
+ violations.push({ file: path30, line: lineNo, snippet: snippet.slice(0, 80), reason });
16606
16676
  };
16607
16677
  for (const re of [HEX_RE, FUNC_COLOR_RE]) {
16608
16678
  re.lastIndex = 0;
@@ -16654,14 +16724,14 @@ var SKIP_DIR = /* @__PURE__ */ new Set([
16654
16724
  ".design"
16655
16725
  ]);
16656
16726
  async function walkUiFiles(root, max = 200) {
16657
- const { default: fs15 } = await import("node:fs/promises");
16727
+ const { default: fs16 } = await import("node:fs/promises");
16658
16728
  const { default: nodePath } = await import("node:path");
16659
16729
  const found = [];
16660
16730
  async function rec(dir, depth) {
16661
16731
  if (found.length >= max || depth > 8) return;
16662
16732
  let entries;
16663
16733
  try {
16664
- entries = await fs15.readdir(dir, { withFileTypes: true });
16734
+ entries = await fs16.readdir(dir, { withFileTypes: true });
16665
16735
  } catch {
16666
16736
  return;
16667
16737
  }
@@ -16679,13 +16749,13 @@ async function walkUiFiles(root, max = 200) {
16679
16749
  return found;
16680
16750
  }
16681
16751
  async function runDesignVerify(projectRoot, tokens, explicitFiles) {
16682
- const { default: fs15 } = await import("node:fs/promises");
16752
+ const { default: fs16 } = await import("node:fs/promises");
16683
16753
  const { default: nodePath } = await import("node:path");
16684
16754
  const abs = explicitFiles && explicitFiles.length > 0 ? explicitFiles.map((f) => nodePath.isAbsolute(f) ? f : nodePath.join(projectRoot, f)) : await walkUiFiles(projectRoot);
16685
16755
  const files = [];
16686
16756
  for (const a of abs) {
16687
16757
  try {
16688
- files.push({ path: nodePath.relative(projectRoot, a), text: await fs15.readFile(a, "utf8") });
16758
+ files.push({ path: nodePath.relative(projectRoot, a), text: await fs16.readFile(a, "utf8") });
16689
16759
  } catch {
16690
16760
  }
16691
16761
  }
@@ -16816,10 +16886,10 @@ function makeDesignVerifyToolCallMiddleware() {
16816
16886
  const p = typeof input?.path === "string" ? input.path : "";
16817
16887
  if (!p || !detectFrontendFile(p)) return out;
16818
16888
  const ctx = out.ctx;
16819
- const { default: fs15 } = await import("node:fs/promises");
16889
+ const { default: fs16 } = await import("node:fs/promises");
16820
16890
  const { default: nodePath } = await import("node:path");
16821
16891
  const abs = nodePath.isAbsolute(p) ? p : nodePath.join(ctx.projectRoot, p);
16822
- const text = await fs15.readFile(abs, "utf8").catch(() => "");
16892
+ const text = await fs16.readFile(abs, "utf8").catch(() => "");
16823
16893
  if (!text) return out;
16824
16894
  const loader = getDesignKitLoader(ctx.projectRoot);
16825
16895
  const rawTokens = await loader.readTokens(state.activeKit);
@@ -19915,8 +19985,8 @@ Summarize the following message range:`;
19915
19985
  };
19916
19986
 
19917
19987
  // src/execution/skill-loader.ts
19918
- import * as fs5 from "node:fs/promises";
19919
- import * as path15 from "node:path";
19988
+ import * as fs6 from "node:fs/promises";
19989
+ import * as path16 from "node:path";
19920
19990
 
19921
19991
  // src/skills/foreign-sources.ts
19922
19992
  var FOREIGN_SKILL_TOOLS = [
@@ -20146,7 +20216,7 @@ async function entryIsDirectory(dir, entry) {
20146
20216
  if (entry.isDirectory()) return true;
20147
20217
  if (entry.isSymbolicLink()) {
20148
20218
  try {
20149
- return (await fs5.stat(path15.join(dir, entry.name))).isDirectory();
20219
+ return (await fs6.stat(path16.join(dir, entry.name))).isDirectory();
20150
20220
  } catch {
20151
20221
  return false;
20152
20222
  }
@@ -20167,7 +20237,7 @@ var DefaultSkillLoader = class {
20167
20237
  for (const tool of FOREIGN_SKILL_TOOLS) {
20168
20238
  if (!foreignIds.includes(tool.id)) continue;
20169
20239
  dirs.push({
20170
- dir: path15.join(root, "." + tool.id, tool.subdir),
20240
+ dir: path16.join(root, "." + tool.id, tool.subdir),
20171
20241
  source: "foreign",
20172
20242
  originTool: tool.id
20173
20243
  });
@@ -20189,14 +20259,14 @@ var DefaultSkillLoader = class {
20189
20259
  const seen = /* @__PURE__ */ new Set();
20190
20260
  for (const { dir, source, originTool } of this.dirs) {
20191
20261
  try {
20192
- const entries = (await fs5.readdir(dir, { withFileTypes: true })).sort(
20262
+ const entries = (await fs6.readdir(dir, { withFileTypes: true })).sort(
20193
20263
  (a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0
20194
20264
  );
20195
20265
  for (const e of entries) {
20196
20266
  if (!await entryIsDirectory(dir, e)) continue;
20197
- const skillFile = path15.join(dir, e.name, "SKILL.md");
20267
+ const skillFile = path16.join(dir, e.name, "SKILL.md");
20198
20268
  try {
20199
- const raw = await fs5.readFile(skillFile, "utf8");
20269
+ const raw = await fs6.readFile(skillFile, "utf8");
20200
20270
  const fm = parseSkillFrontmatter(raw);
20201
20271
  if (!fm.name || !fm.description) continue;
20202
20272
  if (!isValidSkillNameFormat(fm.name)) continue;
@@ -20268,7 +20338,7 @@ var DefaultSkillLoader = class {
20268
20338
  if (cached !== void 0) return cached;
20269
20339
  const m = await this.find(name);
20270
20340
  if (!m) throw new Error(`Skill "${name}" not found`);
20271
- const body = await fs5.readFile(m.path, "utf8");
20341
+ const body = await fs6.readFile(m.path, "utf8");
20272
20342
  this.bodyCache.set(key, body);
20273
20343
  return body;
20274
20344
  }
@@ -20278,12 +20348,12 @@ var DefaultSkillLoader = class {
20278
20348
  if (cached !== void 0) return cached;
20279
20349
  const m = await this.find(name);
20280
20350
  if (!m) throw new Error(`Skill "${name}" not found`);
20281
- const savePath = path15.join(path15.dirname(m.path), "SKILL.save.md");
20351
+ const savePath = path16.join(path16.dirname(m.path), "SKILL.save.md");
20282
20352
  let result;
20283
20353
  try {
20284
- result = await fs5.readFile(savePath, "utf8");
20354
+ result = await fs6.readFile(savePath, "utf8");
20285
20355
  } catch {
20286
- const full = await fs5.readFile(m.path, "utf8");
20356
+ const full = await fs6.readFile(m.path, "utf8");
20287
20357
  const body = stripFrontmatter2(full);
20288
20358
  const compact = compactSkillBody(body);
20289
20359
  if (compact) {
@@ -20311,12 +20381,12 @@ function parseDescriptionFromText(desc) {
20311
20381
  }
20312
20382
 
20313
20383
  // src/execution/prompt-loader.ts
20314
- import * as fs7 from "node:fs/promises";
20315
- import * as path17 from "node:path";
20384
+ import * as fs8 from "node:fs/promises";
20385
+ import * as path18 from "node:path";
20316
20386
 
20317
20387
  // src/storage/prompt-store.ts
20318
- import * as fs6 from "node:fs/promises";
20319
- import * as path16 from "node:path";
20388
+ import * as fs7 from "node:fs/promises";
20389
+ import * as path17 from "node:path";
20320
20390
  var SCHEMA_VERSION = 2;
20321
20391
  function migratePromptEntry(raw) {
20322
20392
  if (!raw || typeof raw !== "object") return null;
@@ -20377,12 +20447,12 @@ var DefaultPromptStore = class {
20377
20447
  await ensureDir(this.dir);
20378
20448
  const entries = [];
20379
20449
  try {
20380
- const files = await fs6.readdir(this.dir);
20450
+ const files = await fs7.readdir(this.dir);
20381
20451
  for (const file of files) {
20382
20452
  if (!file.endsWith(".json")) continue;
20383
20453
  try {
20384
20454
  const raw = JSON.parse(
20385
- await fs6.readFile(path16.join(this.dir, file), "utf8")
20455
+ await fs7.readFile(path17.join(this.dir, file), "utf8")
20386
20456
  );
20387
20457
  const migrated = migratePromptEntry(raw.entry);
20388
20458
  if (migrated) entries.push(migrated);
@@ -20396,9 +20466,9 @@ var DefaultPromptStore = class {
20396
20466
  );
20397
20467
  }
20398
20468
  async get(id) {
20399
- const file = path16.join(this.dir, `${id}.json`);
20469
+ const file = path17.join(this.dir, `${id}.json`);
20400
20470
  try {
20401
- const raw = JSON.parse(await fs6.readFile(file, "utf8"));
20471
+ const raw = JSON.parse(await fs7.readFile(file, "utf8"));
20402
20472
  return migratePromptEntry(raw.entry);
20403
20473
  } catch {
20404
20474
  return null;
@@ -20406,14 +20476,14 @@ var DefaultPromptStore = class {
20406
20476
  }
20407
20477
  async save(entry) {
20408
20478
  await ensureDir(this.dir);
20409
- const file = path16.join(this.dir, `${entry.id}.json`);
20479
+ const file = path17.join(this.dir, `${entry.id}.json`);
20410
20480
  const raw = { version: SCHEMA_VERSION, entry };
20411
20481
  await atomicWrite(file, JSON.stringify(raw, null, 2));
20412
20482
  }
20413
20483
  async delete(id) {
20414
- const file = path16.join(this.dir, `${id}.json`);
20484
+ const file = path17.join(this.dir, `${id}.json`);
20415
20485
  try {
20416
- await fs6.unlink(file);
20486
+ await fs7.unlink(file);
20417
20487
  return true;
20418
20488
  } catch {
20419
20489
  return false;
@@ -20500,7 +20570,7 @@ var DefaultPromptLoader = class {
20500
20570
  constructor(opts) {
20501
20571
  this.projectStore = typeof opts.paths.inProjectPrompts === "string" ? new DefaultPromptStore(opts.paths.inProjectPrompts) : void 0;
20502
20572
  this.userStore = typeof opts.paths.globalPrompts === "string" ? new DefaultPromptStore(opts.paths.globalPrompts) : void 0;
20503
- this.builtinDir = opts.bundledDir ? path17.join(opts.bundledDir, "prompts") : void 0;
20573
+ this.builtinDir = opts.bundledDir ? path18.join(opts.bundledDir, "prompts") : void 0;
20504
20574
  }
20505
20575
  async list() {
20506
20576
  if (this.cache) return this.cache;
@@ -20613,7 +20683,7 @@ var DefaultPromptLoader = class {
20613
20683
  const files = await walkJson(dir);
20614
20684
  for (const file of files) {
20615
20685
  try {
20616
- const parsed = JSON.parse(await fs7.readFile(file, "utf8"));
20686
+ const parsed = JSON.parse(await fs8.readFile(file, "utf8"));
20617
20687
  const migrated = migratePromptEntry(parsed);
20618
20688
  if (migrated) out.push({ ...migrated, source: "builtin" });
20619
20689
  } catch {
@@ -20627,12 +20697,12 @@ async function walkJson(dir) {
20627
20697
  const out = [];
20628
20698
  let entries;
20629
20699
  try {
20630
- entries = await fs7.readdir(dir, { withFileTypes: true });
20700
+ entries = await fs8.readdir(dir, { withFileTypes: true });
20631
20701
  } catch {
20632
20702
  return out;
20633
20703
  }
20634
20704
  for (const e of entries) {
20635
- const full = path17.join(dir, e.name);
20705
+ const full = path18.join(dir, e.name);
20636
20706
  if (e.isDirectory()) {
20637
20707
  out.push(...await walkJson(full));
20638
20708
  } else if (e.name.endsWith(".json") && e.name !== "index.json" && e.name !== "schema.json") {
@@ -20818,7 +20888,7 @@ function readPolicy(ctx) {
20818
20888
  }
20819
20889
 
20820
20890
  // src/execution/tool-executor.ts
20821
- import { randomUUID as randomUUID11 } from "node:crypto";
20891
+ import { createHash as createHash6, randomUUID as randomUUID11 } from "node:crypto";
20822
20892
 
20823
20893
  // src/observability/process-telemetry.ts
20824
20894
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
@@ -20829,12 +20899,12 @@ function runWithProcessTelemetry(context, run) {
20829
20899
 
20830
20900
  // src/execution/tool-executor.ts
20831
20901
  import { isDeepStrictEqual } from "node:util";
20832
- import * as fs8 from "node:fs/promises";
20833
- import * as path19 from "node:path";
20902
+ import * as fs9 from "node:fs/promises";
20903
+ import * as path20 from "node:path";
20834
20904
 
20835
20905
  // src/security/kanban-boundary.ts
20836
20906
  import { realpath as realpath3 } from "node:fs/promises";
20837
- import * as path18 from "node:path";
20907
+ import * as path19 from "node:path";
20838
20908
  import {
20839
20909
  evaluateKanbanBoundaryOpaque,
20840
20910
  evaluateKanbanBoundaryPath,
@@ -20909,10 +20979,10 @@ function resolveKanbanIdentity(ctx) {
20909
20979
  async function extractCandidatePaths(toolName, input, ctx) {
20910
20980
  if (toolName === "patch" && typeof input["patch"] === "string") {
20911
20981
  const directoryInput = stringValue(input["directory"]) ?? ctx.workingDir;
20912
- const directory = path18.isAbsolute(directoryInput) ? directoryInput : path18.resolve(ctx.workingDir, directoryInput);
20982
+ const directory = path19.isAbsolute(directoryInput) ? directoryInput : path19.resolve(ctx.workingDir, directoryInput);
20913
20983
  const strip = Math.max(1, numericValue(input["strip"]) ?? 1);
20914
20984
  const targets = extractPatchTargets(input["patch"], strip).map(
20915
- (target) => relativeToProject(path18.resolve(directory, target), ctx.projectRoot)
20985
+ (target) => relativeToProject(path19.resolve(directory, target), ctx.projectRoot)
20916
20986
  );
20917
20987
  return Promise.all(targets.map((target) => canonicalizeCandidatePath(target, ctx)));
20918
20988
  }
@@ -20922,7 +20992,7 @@ async function extractCandidatePaths(toolName, input, ctx) {
20922
20992
  collectPathValues(input, values, pathKeys);
20923
20993
  if (toolName === "scaffold" && typeof input["name"] === "string") {
20924
20994
  const cwd = stringValue(input["cwd"]) ?? ctx.workingDir;
20925
- values.push(path18.join(cwd, input["name"]));
20995
+ values.push(path19.join(cwd, input["name"]));
20926
20996
  }
20927
20997
  const candidates = [
20928
20998
  ...new Set(values.flatMap(splitPathList).map((value) => resolveInputPath(value, ctx)))
@@ -20930,21 +21000,21 @@ async function extractCandidatePaths(toolName, input, ctx) {
20930
21000
  return Promise.all(candidates.map((candidate) => canonicalizeCandidatePath(candidate, ctx)));
20931
21001
  }
20932
21002
  async function canonicalizeCandidatePath(candidate, ctx) {
20933
- if (path18.isAbsolute(candidate)) return candidate;
20934
- const absolute = path18.resolve(ctx.projectRoot, candidate);
21003
+ if (path19.isAbsolute(candidate)) return candidate;
21004
+ const absolute = path19.resolve(ctx.projectRoot, candidate);
20935
21005
  const canonicalRoot = await realpath3(ctx.projectRoot).catch(() => ctx.projectRoot);
20936
21006
  let probe = absolute;
20937
21007
  const missingSegments = [];
20938
21008
  while (true) {
20939
21009
  try {
20940
- const canonical = path18.join(await realpath3(probe), ...missingSegments);
21010
+ const canonical = path19.join(await realpath3(probe), ...missingSegments);
20941
21011
  return relativeToProject(canonical, canonicalRoot);
20942
21012
  } catch (cause) {
20943
21013
  const code = cause.code;
20944
21014
  if (code !== "ENOENT" && code !== "ENOTDIR") return absolute;
20945
- const parent = path18.dirname(probe);
21015
+ const parent = path19.dirname(probe);
20946
21016
  if (parent === probe) return absolute;
20947
- missingSegments.unshift(path18.basename(probe));
21017
+ missingSegments.unshift(path19.basename(probe));
20948
21018
  probe = parent;
20949
21019
  }
20950
21020
  }
@@ -20967,12 +21037,12 @@ function splitPathList(value) {
20967
21037
  return value.split(",").map((item) => item.trim()).filter(Boolean);
20968
21038
  }
20969
21039
  function resolveInputPath(value, ctx) {
20970
- const absolute = path18.isAbsolute(value) ? value : path18.resolve(ctx.workingDir, value);
21040
+ const absolute = path19.isAbsolute(value) ? value : path19.resolve(ctx.workingDir, value);
20971
21041
  return relativeToProject(absolute, ctx.projectRoot);
20972
21042
  }
20973
21043
  function relativeToProject(absolute, projectRoot) {
20974
- const relative5 = path18.relative(projectRoot, absolute).replace(/\\/g, "/");
20975
- return relative5.startsWith("../") || path18.isAbsolute(relative5) ? absolute : relative5 || ".";
21044
+ const relative6 = path19.relative(projectRoot, absolute).replace(/\\/g, "/");
21045
+ return relative6.startsWith("../") || path19.isAbsolute(relative6) ? absolute : relative6 || ".";
20976
21046
  }
20977
21047
  function extractPatchTargets(patchText, strip) {
20978
21048
  const targets = [];
@@ -21226,12 +21296,30 @@ ${errorDetails}`,
21226
21296
  const policy = this.opts.permissionPolicy;
21227
21297
  const yolo = policy.getYolo?.() === true;
21228
21298
  const authoritativeAuto = decision.source === "yolo";
21229
- if (toolDangerousCaps.length > 0 && effectivePermission === "auto" && !yolo && !authoritativeAuto) {
21299
+ const capabilityDowngraded = toolDangerousCaps.length > 0 && effectivePermission === "auto" && !yolo && !authoritativeAuto;
21300
+ if (capabilityDowngraded) {
21230
21301
  effectivePermission = "confirm";
21231
21302
  }
21232
21303
  if (boundary.decision === "confirm" && effectivePermission !== "deny") {
21233
21304
  effectivePermission = "confirm";
21234
21305
  }
21306
+ this.opts.events?.emit("permission.evaluated", {
21307
+ sessionId: ctx.session.id,
21308
+ ...ctx.traceId ? { traceId: ctx.traceId } : {},
21309
+ ...ctx.agentId ? { agentId: ctx.agentId } : {},
21310
+ name: tool.name,
21311
+ id: use.id,
21312
+ inputHash: hashPermissionInput(use.input, this.opts.secretScrubber),
21313
+ policyDecision: decision.permission,
21314
+ effectiveDecision: effectivePermission,
21315
+ decisionSource: decision.source,
21316
+ ...decision.reason ? { reason: decision.reason } : {},
21317
+ ...decision.riskTier ?? tool.riskTier ? { riskTier: decision.riskTier ?? tool.riskTier } : {},
21318
+ yoloEnabled: yolo,
21319
+ boundaryDecision: boundary.decision,
21320
+ ...boundary.reason ? { boundaryReason: boundary.reason } : {},
21321
+ capabilityDowngraded
21322
+ });
21235
21323
  if (effectivePermission === "deny") {
21236
21324
  const result = this.deniedResult(use, decision.reason);
21237
21325
  budget = this.budgetForString(result.content, budget);
@@ -21298,10 +21386,10 @@ ${errorDetails}`,
21298
21386
  const inputPath = use.input && typeof use.input === "object" ? use.input.path : void 0;
21299
21387
  const caps = tool.capabilities ?? [];
21300
21388
  const hasFileCapability = caps.includes("fs.read") || caps.includes("fs.write");
21301
- const absPath = hasFileCapability && typeof inputPath === "string" ? path19.isAbsolute(inputPath) ? inputPath : path19.resolve(ctx.projectRoot, inputPath) : void 0;
21389
+ const absPath = hasFileCapability && typeof inputPath === "string" ? path20.isAbsolute(inputPath) ? inputPath : path20.resolve(ctx.projectRoot, inputPath) : void 0;
21302
21390
  let writeTargetExisted;
21303
21391
  if (tool.name === "write" && caps.includes("fs.write") && absPath) {
21304
- writeTargetExisted = await fs8.stat(absPath).then(
21392
+ writeTargetExisted = await fs9.stat(absPath).then(
21305
21393
  (stat13) => stat13.isFile(),
21306
21394
  (error2) => error2.code === "ENOENT" ? false : void 0
21307
21395
  );
@@ -21890,12 +21978,12 @@ async function maybePersistLargeToolOutput(toolName, content, budget) {
21890
21978
  return content;
21891
21979
  }
21892
21980
  try {
21893
- const dir = path19.join(wstackGlobalRoot(), "tool-output");
21894
- await fs8.mkdir(dir, { recursive: true });
21981
+ const dir = path20.join(wstackGlobalRoot(), "tool-output");
21982
+ await fs9.mkdir(dir, { recursive: true });
21895
21983
  const safeTool = toolName.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
21896
21984
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
21897
- const filePath = path19.join(dir, `${stamp}-${safeTool}-${randomUUID11()}.log`);
21898
- await fs8.writeFile(filePath, content, "utf8");
21985
+ const filePath = path20.join(dir, `${stamp}-${safeTool}-${randomUUID11()}.log`);
21986
+ await fs9.writeFile(filePath, content, "utf8");
21899
21987
  const marker = `[full tool output: ${bytes} bytes at ${filePath}; read/grep that file selectively instead of re-running or requesting more output]`;
21900
21988
  const fixedBytes = Buffer.byteLength(marker + TOOL_OUTPUT_ARTIFACT_OMISSION, "utf8");
21901
21989
  const previewBytes = Math.min(
@@ -21913,6 +22001,15 @@ ${head}${TOOL_OUTPUT_ARTIFACT_OMISSION}${tail}`;
21913
22001
  return content;
21914
22002
  }
21915
22003
  }
22004
+ function hashPermissionInput(input, scrubber) {
22005
+ let serialized;
22006
+ try {
22007
+ serialized = JSON.stringify(input) ?? "";
22008
+ } catch {
22009
+ serialized = String(input);
22010
+ }
22011
+ return createHash6("sha256").update(scrubber.scrub(serialized), "utf8").digest("hex");
22012
+ }
21916
22013
  function sliceUtf8Prefix(text, maxBytes) {
21917
22014
  if (maxBytes <= 0) return "";
21918
22015
  if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
@@ -22206,7 +22303,7 @@ var contextManagerTool = createContextManagerTool();
22206
22303
 
22207
22304
  // src/infrastructure/logger.ts
22208
22305
  import * as fsp11 from "node:fs/promises";
22209
- import * as path20 from "node:path";
22306
+ import * as path21 from "node:path";
22210
22307
  var LEVEL_RANK2 = {
22211
22308
  error: 0,
22212
22309
  warn: 1,
@@ -22270,7 +22367,7 @@ var DefaultLogger = class _DefaultLogger {
22270
22367
  this.stderr = opts.stderr !== false;
22271
22368
  this.maxFileBytes = opts.maxFileBytes ?? 10 * 1024 * 1024;
22272
22369
  if (this.file) {
22273
- const dir = path20.dirname(this.file);
22370
+ const dir = path21.dirname(this.file);
22274
22371
  this._tail = this._tail.then(async () => {
22275
22372
  await fsp11.mkdir(dir, { recursive: true });
22276
22373
  }).catch(() => void 0);
@@ -22591,28 +22688,28 @@ function codexModelMeta(id) {
22591
22688
  }
22592
22689
 
22593
22690
  // src/models/mode-store.ts
22594
- import * as fs9 from "node:fs/promises";
22595
- import * as path22 from "node:path";
22691
+ import * as fs10 from "node:fs/promises";
22692
+ import * as path23 from "node:path";
22596
22693
 
22597
22694
  // src/types/mode-prompts.ts
22598
22695
  import { readFileSync as readFileSync4, statSync as statSync4 } from "node:fs";
22599
- import * as path21 from "node:path";
22696
+ import * as path22 from "node:path";
22600
22697
  import { fileURLToPath as fileURLToPath4 } from "node:url";
22601
22698
  function modePrompt(id) {
22602
22699
  for (const dir of modePromptDirCandidates()) {
22603
22700
  try {
22604
- return readFileSync4(path21.join(dir, `${id}.md`), "utf8").trimEnd();
22701
+ return readFileSync4(path22.join(dir, `${id}.md`), "utf8").trimEnd();
22605
22702
  } catch {
22606
22703
  }
22607
22704
  }
22608
22705
  return "";
22609
22706
  }
22610
22707
  function modePromptDirCandidates() {
22611
- const here = path21.dirname(fileURLToPath4(import.meta.url));
22708
+ const here = path22.dirname(fileURLToPath4(import.meta.url));
22612
22709
  const candidates = [
22613
- path21.resolve(here, "../../instructions/modes"),
22614
- path21.resolve(here, "../instructions/modes"),
22615
- path21.resolve(here, "instructions/modes")
22710
+ path22.resolve(here, "../../instructions/modes"),
22711
+ path22.resolve(here, "../instructions/modes"),
22712
+ path22.resolve(here, "instructions/modes")
22616
22713
  ];
22617
22714
  return candidates.sort((a, b) => Number(!isDirectory3(a)) - Number(!isDirectory3(b)));
22618
22715
  }
@@ -22844,8 +22941,8 @@ var DefaultModeStore = class {
22844
22941
  }
22845
22942
  async loadActiveMode() {
22846
22943
  try {
22847
- const configPath = path22.join(this.configDir, "mode.json");
22848
- const content = await fs9.readFile(configPath, "utf8");
22944
+ const configPath = path23.join(this.configDir, "mode.json");
22945
+ const content = await fs10.readFile(configPath, "utf8");
22849
22946
  const data = JSON.parse(content);
22850
22947
  this.activeModeId = data.activeMode ?? null;
22851
22948
  } catch {
@@ -22854,8 +22951,8 @@ var DefaultModeStore = class {
22854
22951
  }
22855
22952
  async saveActiveMode() {
22856
22953
  try {
22857
- await fs9.mkdir(this.configDir, { recursive: true });
22858
- const configPath = path22.join(this.configDir, "mode.json");
22954
+ await fs10.mkdir(this.configDir, { recursive: true });
22955
+ const configPath = path23.join(this.configDir, "mode.json");
22859
22956
  await atomicWrite(
22860
22957
  configPath,
22861
22958
  JSON.stringify({ activeMode: this.activeModeId }, null, 2)
@@ -22867,14 +22964,14 @@ var DefaultModeStore = class {
22867
22964
  async function loadProjectModes(modesDir) {
22868
22965
  const modes = [];
22869
22966
  try {
22870
- const entries = await fs9.readdir(modesDir);
22967
+ const entries = await fs10.readdir(modesDir);
22871
22968
  for (const entry of entries) {
22872
22969
  if (!entry.endsWith(".md") && !entry.endsWith(".txt")) continue;
22873
- const filePath = path22.join(modesDir, entry);
22874
- const stat13 = await fs9.stat(filePath);
22970
+ const filePath = path23.join(modesDir, entry);
22971
+ const stat13 = await fs10.stat(filePath);
22875
22972
  if (!stat13.isFile()) continue;
22876
- const content = await fs9.readFile(filePath, "utf8");
22877
- const id = path22.basename(entry, path22.extname(entry));
22973
+ const content = await fs10.readFile(filePath, "utf8");
22974
+ const id = path23.basename(entry, path23.extname(entry));
22878
22975
  modes.push({
22879
22976
  id,
22880
22977
  name: id.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
@@ -22890,8 +22987,8 @@ async function loadProjectModes(modesDir) {
22890
22987
  async function loadUserModes(modesDir) {
22891
22988
  const modes = [];
22892
22989
  try {
22893
- const manifestPath = path22.join(modesDir, "modes.json");
22894
- const content = await fs9.readFile(manifestPath, "utf8");
22990
+ const manifestPath = path23.join(modesDir, "modes.json");
22991
+ const content = await fs10.readFile(manifestPath, "utf8");
22895
22992
  const manifest = JSON.parse(content);
22896
22993
  for (const mode of manifest.modes) {
22897
22994
  modes.push(mode);
@@ -22902,8 +22999,8 @@ async function loadUserModes(modesDir) {
22902
22999
  }
22903
23000
 
22904
23001
  // src/models/models-registry.ts
22905
- import * as fs10 from "node:fs/promises";
22906
- import * as path23 from "node:path";
23002
+ import * as fs11 from "node:fs/promises";
23003
+ import * as path24 from "node:path";
22907
23004
  var DEFAULT_URL = "https://models.dev/api.json";
22908
23005
  var ENV_URL_KEY = "WRONGSTACK_MODELS_DEV_URL";
22909
23006
  var DEFAULT_TTL_SECONDS = 24 * 3600;
@@ -22979,7 +23076,7 @@ var DefaultModelsRegistry = class {
22979
23076
  this.overlay = opts.overlay;
22980
23077
  this.overlayUrl = opts.overlayUrl;
22981
23078
  this.overlayFile = opts.overlayFile;
22982
- this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ? path23.join(path23.dirname(opts.cacheFile), "models-overlay-cache.json") : void 0);
23079
+ this.overlayCacheFile = opts.overlayCacheFile ?? (opts.overlayUrl ? path24.join(path24.dirname(opts.cacheFile), "models-overlay-cache.json") : void 0);
22983
23080
  }
22984
23081
  async load(opts = {}) {
22985
23082
  if (this.payload && !opts.force) return this.payload;
@@ -23144,7 +23241,7 @@ var DefaultModelsRegistry = class {
23144
23241
  async readOverlayFile() {
23145
23242
  if (!this.overlayFile) return void 0;
23146
23243
  try {
23147
- const raw = await fs10.readFile(this.overlayFile, "utf8");
23244
+ const raw = await fs11.readFile(this.overlayFile, "utf8");
23148
23245
  return JSON.parse(raw);
23149
23246
  } catch {
23150
23247
  return void 0;
@@ -23223,7 +23320,7 @@ var DefaultModelsRegistry = class {
23223
23320
  }
23224
23321
  async readCacheAt(file) {
23225
23322
  try {
23226
- const raw = await fs10.readFile(file, "utf8");
23323
+ const raw = await fs11.readFile(file, "utf8");
23227
23324
  return JSON.parse(raw);
23228
23325
  } catch {
23229
23326
  return void 0;
@@ -23231,7 +23328,7 @@ var DefaultModelsRegistry = class {
23231
23328
  }
23232
23329
  /** Used by `wstack models refresh` to expose where the cache lives. */
23233
23330
  cacheLocation() {
23234
- return path23.resolve(this.cacheFile);
23331
+ return path24.resolve(this.cacheFile);
23235
23332
  }
23236
23333
  };
23237
23334
  function formatAge(seconds) {
@@ -23655,7 +23752,7 @@ async function startMetricsServer(opts) {
23655
23752
  const tls = opts.tls;
23656
23753
  const useHttps = !!(tls?.cert && tls?.key);
23657
23754
  const host = opts.host ?? "127.0.0.1";
23658
- const path29 = opts.path ?? "/metrics";
23755
+ const path30 = opts.path ?? "/metrics";
23659
23756
  const healthPath = opts.healthPath ?? "/healthz";
23660
23757
  const healthRegistry = opts.healthRegistry;
23661
23758
  const listener = (req, res) => {
@@ -23665,7 +23762,7 @@ async function startMetricsServer(opts) {
23665
23762
  return;
23666
23763
  }
23667
23764
  const url = req.url.split("?")[0];
23668
- if (url === path29) {
23765
+ if (url === path30) {
23669
23766
  let body;
23670
23767
  try {
23671
23768
  body = renderPrometheus(opts.sink.snapshot());
@@ -23729,7 +23826,7 @@ async function startMetricsServer(opts) {
23729
23826
  const protocol = useHttps ? "https" : "http";
23730
23827
  return {
23731
23828
  port: boundPort,
23732
- url: `${protocol}://${host}:${boundPort}${path29}`,
23829
+ url: `${protocol}://${host}:${boundPort}${path30}`,
23733
23830
  close: () => new Promise((resolve14, reject) => {
23734
23831
  server.close((err) => err ? reject(err) : resolve14());
23735
23832
  })
@@ -24001,7 +24098,7 @@ function startOtlpTraceExporter(opts) {
24001
24098
  }
24002
24099
 
24003
24100
  // src/security/permission-policy.ts
24004
- import * as fs11 from "node:fs/promises";
24101
+ import * as fs12 from "node:fs/promises";
24005
24102
 
24006
24103
  // src/security/permission-policy-schema.ts
24007
24104
  var TRUST_POLICY_LIMITS = Object.freeze({
@@ -24046,19 +24143,19 @@ var UNSAFE_PROPERTY_NAMES = /* @__PURE__ */ new Set(["__proto__", "prototype", "
24046
24143
  function isRecord5(value) {
24047
24144
  return typeof value === "object" && value !== null && !Array.isArray(value);
24048
24145
  }
24049
- function error(diagnostics, code, path29, message) {
24050
- diagnostics.push({ severity: "error", code, path: path29, message });
24146
+ function error(diagnostics, code, path30, message) {
24147
+ diagnostics.push({ severity: "error", code, path: path30, message });
24051
24148
  }
24052
- function validatePatterns(value, path29, diagnostics) {
24149
+ function validatePatterns(value, path30, diagnostics) {
24053
24150
  if (!Array.isArray(value)) {
24054
- error(diagnostics, "invalid_pattern_list", path29, "must be an array of strings");
24151
+ error(diagnostics, "invalid_pattern_list", path30, "must be an array of strings");
24055
24152
  return void 0;
24056
24153
  }
24057
24154
  if (value.length > TRUST_POLICY_LIMITS.maxPatternsPerRule) {
24058
24155
  error(
24059
24156
  diagnostics,
24060
24157
  "too_many_patterns",
24061
- path29,
24158
+ path30,
24062
24159
  `must contain at most ${TRUST_POLICY_LIMITS.maxPatternsPerRule} patterns`
24063
24160
  );
24064
24161
  return void 0;
@@ -24066,7 +24163,7 @@ function validatePatterns(value, path29, diagnostics) {
24066
24163
  const patterns = [];
24067
24164
  const seen = /* @__PURE__ */ new Set();
24068
24165
  for (const [index, pattern] of value.entries()) {
24069
- const itemPath = `${path29}[${index}]`;
24166
+ const itemPath = `${path30}[${index}]`;
24070
24167
  if (typeof pattern !== "string" || pattern.length === 0 || pattern.length > TRUST_POLICY_LIMITS.maxPatternChars) {
24071
24168
  error(
24072
24169
  diagnostics,
@@ -24390,7 +24487,7 @@ var DefaultPermissionPolicy = class {
24390
24487
  this.policyDiagnostics = [];
24391
24488
  this.policyInvalid = false;
24392
24489
  try {
24393
- const raw = await fs11.readFile(this.trustFile, "utf8");
24490
+ const raw = await fs12.readFile(this.trustFile, "utf8");
24394
24491
  const parsed = safeParse(raw);
24395
24492
  if (!parsed.ok) {
24396
24493
  this.policy = {};
@@ -24640,6 +24737,136 @@ var DefaultPermissionPolicy = class {
24640
24737
  this.sessionAllowed.set(`${rule.tool}::${rule.pattern}`, true);
24641
24738
  this._evalCache.clear();
24642
24739
  }
24740
+ /**
24741
+ * Side-effect-free permission evaluation trace. Mirrors `evaluate()`
24742
+ * step-by-step without prompting the user, writing to trust files, or
24743
+ * mutating session state. Returns the full ordered trace and winner.
24744
+ */
24745
+ async explain(tool, input, ctx) {
24746
+ if (!this.loaded) await this.reload();
24747
+ const subject = subjectForToolInput(tool.name, input, tool.subjectKey);
24748
+ const steps = [];
24749
+ let winnerIndex = -1;
24750
+ const add = (rule, matched, decision, source, detail) => {
24751
+ steps.push({ rule, matched, decision, source, detail });
24752
+ };
24753
+ if (this.policyInvalid) {
24754
+ add("policy invalid", true, "deny", "deny", "trust policy is invalid; all tools are denied");
24755
+ winnerIndex = 0;
24756
+ return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "deny", source: "deny", reason: "trust policy is invalid" } };
24757
+ }
24758
+ add("policy valid", false, "auto", "default", "trust policy loaded successfully");
24759
+ const namespaceEntry = this.findNamespaceEntry(tool.name);
24760
+ const entry = this.policy[tool.name] ?? namespaceEntry;
24761
+ const namespaceSource = namespaceEntry ? `wildcard entry matched (${Object.keys(this.policy).find((k) => k.includes("*") && matchGlob(k, tool.name))})` : "no namespace match";
24762
+ const cacheKey = `${tool.name}::${subject ?? tool.name}`;
24763
+ if (this.sessionDenied.has(cacheKey)) {
24764
+ add("session soft deny", true, "deny", "deny", 'user pressed "no" earlier in this session \u2014 blocked until reload');
24765
+ winnerIndex = steps.length - 1;
24766
+ return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "deny", source: "deny", reason: "session soft deny (user pressed no)" } };
24767
+ }
24768
+ add("session soft deny", false, "deny", "deny", "no session-level soft deny for this tool+subject");
24769
+ if (this.sessionAllowed.has(cacheKey)) {
24770
+ add("session soft allow", true, "auto", "trust", 'user pressed "yes" in current session \u2014 one-shot auto-approve (consumed)');
24771
+ winnerIndex = steps.length - 1;
24772
+ return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "trust", reason: "session one-shot allow (user pressed yes)" } };
24773
+ }
24774
+ add("session soft allow", false, "auto", "trust", "no session-level one-shot allow for this tool+subject");
24775
+ if (entry?.deny && subject && matchesTrust(entry.deny, subject)) {
24776
+ add("trust deny", true, "deny", "deny", `subject "${subject}" matched a deny pattern in trust file`);
24777
+ winnerIndex = steps.length - 1;
24778
+ return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "deny", source: "deny", reason: "matched deny pattern" } };
24779
+ }
24780
+ add("trust deny", false, "deny", "deny", `no deny pattern matched (namespace: ${namespaceSource})`);
24781
+ if (tool.permission === "deny") {
24782
+ add("tool default deny", true, "deny", "default", `tool "${tool.name}" has permission: deny by default`);
24783
+ winnerIndex = steps.length - 1;
24784
+ return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "deny", source: "default", reason: "tool default deny" } };
24785
+ }
24786
+ add("tool default deny", false, "deny", "default", `tool "${tool.name}" has permission: "${tool.permission}"`);
24787
+ if (entry?.allow && subject && matchesTrust(entry.allow, subject)) {
24788
+ add("trust allow", true, "auto", "trust", `subject "${subject}" matched an allow pattern in trust file`);
24789
+ winnerIndex = steps.length - 1;
24790
+ return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "trust", reason: "matched allow pattern" } };
24791
+ }
24792
+ add("trust allow", false, "auto", "trust", `no allow pattern matched (namespace: ${namespaceSource})`);
24793
+ if (entry?.auto) {
24794
+ add("trust auto", true, "auto", "trust", `trust file has auto: true for "${tool.name}"`);
24795
+ winnerIndex = steps.length - 1;
24796
+ return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "trust", reason: "trust auto" } };
24797
+ }
24798
+ add("trust auto", false, "auto", "trust", `no auto flag for "${tool.name}" in trust file`);
24799
+ if (!this.yolo && this.isSensitiveReadCall(tool, input)) {
24800
+ const hasDelegate2 = this.promptDelegate !== void 0;
24801
+ add(
24802
+ "sensitive read",
24803
+ true,
24804
+ hasDelegate2 ? "confirm" : "confirm",
24805
+ "default",
24806
+ hasDelegate2 ? "sensitive file read detected \u2014 would prompt user for approval" : "sensitive file read detected \u2014 returns confirm (no prompt delegate)"
24807
+ );
24808
+ winnerIndex = steps.length - 1;
24809
+ return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "confirm", source: "default", riskTier: "standard", reason: "sensitive file read needs explicit approval" } };
24810
+ }
24811
+ add("sensitive read", false, "confirm", "default", "not a sensitive read call (or YOLO bypasses this check)");
24812
+ if (this.yolo) {
24813
+ add("yolo", true, "auto", "yolo", "YOLO mode is active \u2014 auto-approving every non-denied call");
24814
+ winnerIndex = steps.length - 1;
24815
+ return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "yolo" } };
24816
+ }
24817
+ add("yolo", false, "auto", "yolo", "YOLO mode is not active");
24818
+ if (tool.name === "write" && subject) {
24819
+ const hasRead = ctx.hasRead(subject);
24820
+ add(
24821
+ "write smart bypass",
24822
+ hasRead,
24823
+ "auto",
24824
+ "context",
24825
+ hasRead ? `file "${subject}" was already read in this session \u2014 auto-approving write` : `file "${subject}" was not read in this session \u2014 bypass does not apply`
24826
+ );
24827
+ if (hasRead) {
24828
+ winnerIndex = steps.length - 1;
24829
+ return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "context", reason: "file already read in this session" } };
24830
+ }
24831
+ } else {
24832
+ add("write smart bypass", false, "auto", "context", 'tool is not "write" or has no subject \u2014 bypass does not apply');
24833
+ }
24834
+ const hasWriteCap = hasCapability(tool, ToolCapabilities.FS_WRITE);
24835
+ const hasShellCap = hasCapability(tool, [
24836
+ ToolCapabilities.SHELL_ARBITRARY,
24837
+ ToolCapabilities.SHELL_RESTRICTED,
24838
+ ToolCapabilities.SHELL_EXEC
24839
+ ]);
24840
+ const hasInstallCap = hasCapability(tool, ToolCapabilities.PACKAGE_INSTALL);
24841
+ const hasConfigCap = hasCapability(tool, ToolCapabilities.CONFIG_MUTATE);
24842
+ const hasSubagentCap = hasCapability(tool, ToolCapabilities.SUBAGENT_SPAWN);
24843
+ const isMutating = tool.mutating || hasWriteCap || hasShellCap || hasInstallCap || hasConfigCap || hasSubagentCap;
24844
+ if (tool.permission === "auto" && !isMutating) {
24845
+ add("safe default auto", true, "auto", "default", `tool "${tool.name}" has auto permission and is not mutating \u2014 safe to auto-approve`);
24846
+ winnerIndex = steps.length - 1;
24847
+ return { toolName: tool.name, subject, steps, winnerIndex, decision: { permission: "auto", source: "default" } };
24848
+ }
24849
+ add(
24850
+ "mutating default confirm",
24851
+ true,
24852
+ "confirm",
24853
+ "default",
24854
+ isMutating ? `tool "${tool.name}" is mutating (default auto not enough) \u2014 needs confirmation` : `tool "${tool.name}" has "${tool.permission}" permission \u2014 needs confirmation`
24855
+ );
24856
+ winnerIndex = steps.length - 1;
24857
+ const hasDelegate = this.promptDelegate !== void 0;
24858
+ return {
24859
+ toolName: tool.name,
24860
+ subject,
24861
+ steps,
24862
+ winnerIndex,
24863
+ decision: {
24864
+ permission: "confirm",
24865
+ source: "default",
24866
+ ...hasDelegate ? { reason: "would prompt user via delegate" } : {}
24867
+ }
24868
+ };
24869
+ }
24643
24870
  findNamespaceEntry(toolName) {
24644
24871
  for (const { pattern, value } of this.wildcardEntries) {
24645
24872
  if (matchGlob(pattern, toolName)) return value;
@@ -24685,6 +24912,24 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
24685
24912
  }
24686
24913
  allowOnce() {
24687
24914
  }
24915
+ async explain(tool) {
24916
+ const decision = await this.evaluate(tool);
24917
+ return {
24918
+ toolName: tool.name,
24919
+ subject: null,
24920
+ steps: [
24921
+ {
24922
+ rule: "subagent auto",
24923
+ matched: decision.permission === "auto",
24924
+ decision: decision.permission,
24925
+ source: decision.source,
24926
+ detail: decision.reason ?? `subagent policy: ${decision.permission}`
24927
+ }
24928
+ ],
24929
+ winnerIndex: 0,
24930
+ decision
24931
+ };
24932
+ }
24688
24933
  async reload() {
24689
24934
  }
24690
24935
  };
@@ -24885,9 +25130,9 @@ var DefaultSecretScrubber = class {
24885
25130
 
24886
25131
  // src/security/secret-vault.ts
24887
25132
  import { createCipheriv, createDecipheriv, randomBytes as randomBytes4, scryptSync } from "node:crypto";
24888
- import * as fs12 from "node:fs";
25133
+ import * as fs13 from "node:fs";
24889
25134
  import * as fsp12 from "node:fs/promises";
24890
- import * as path24 from "node:path";
25135
+ import * as path25 from "node:path";
24891
25136
 
24892
25137
  // src/types/secret-vault.ts
24893
25138
  var ENCRYPTED_PREFIX_PATTERN = /^enc:v(\d+):/;
@@ -25027,7 +25272,7 @@ function checkKeyFilePermissions(keyFile, opts) {
25027
25272
  if (process.platform === "win32") return;
25028
25273
  const warn = opts?.warn ?? ((msg) => console.warn(msg));
25029
25274
  try {
25030
- const stat13 = fs12.statSync(keyFile);
25275
+ const stat13 = fs13.statSync(keyFile);
25031
25276
  const actualMode = stat13.mode & 511;
25032
25277
  if (actualMode !== KEY_FILE_MODE) {
25033
25278
  warn(
@@ -25124,16 +25369,16 @@ var DefaultSecretVault = class {
25124
25369
  const oldVersion = this._keyVersion;
25125
25370
  const newKey = randomBytes4(KEY_BYTES);
25126
25371
  const newVersion = oldVersion + 1;
25127
- fs12.mkdirSync(path24.dirname(this.keyFile), { recursive: true });
25372
+ fs13.mkdirSync(path25.dirname(this.keyFile), { recursive: true });
25128
25373
  const passphrase = getVaultPassphrase();
25129
25374
  if (passphrase) {
25130
- fs12.writeFileSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase), { mode: 384 });
25375
+ fs13.writeFileSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase), { mode: 384 });
25131
25376
  } else {
25132
25377
  const keyFileBuf = Buffer.alloc(VERSIONED_KEY_FILE_SIZE);
25133
25378
  KEY_FILE_MAGIC.copy(keyFileBuf, 0);
25134
25379
  keyFileBuf[KEY_FILE_MAGIC.length] = newVersion;
25135
25380
  newKey.copy(keyFileBuf, KEY_FILE_MAGIC.length + 1);
25136
- fs12.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
25381
+ fs13.writeFileSync(this.keyFile, keyFileBuf, { mode: 384 });
25137
25382
  }
25138
25383
  checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
25139
25384
  this.key = newKey;
@@ -25151,7 +25396,7 @@ var DefaultSecretVault = class {
25151
25396
  const passphrase = getVaultPassphrase();
25152
25397
  if (!passphrase || !this.key) return;
25153
25398
  try {
25154
- fs12.writeFileSync(this.keyFile, wrapDataKey(this.key, this._keyVersion, passphrase), {
25399
+ fs13.writeFileSync(this.keyFile, wrapDataKey(this.key, this._keyVersion, passphrase), {
25155
25400
  mode: 384
25156
25401
  });
25157
25402
  checkKeyFilePermissions(this.keyFile, { warn: (msg) => this.logWarn(msg) });
@@ -25161,7 +25406,7 @@ var DefaultSecretVault = class {
25161
25406
  loadOrCreateKey() {
25162
25407
  if (this.key) return this.key;
25163
25408
  try {
25164
- const buf = fs12.readFileSync(this.keyFile);
25409
+ const buf = fs13.readFileSync(this.keyFile);
25165
25410
  if (isWrappedKeyFile(buf)) {
25166
25411
  const { key: key2, version } = unwrapDataKey(buf, this.keyFile);
25167
25412
  this.key = key2;
@@ -25208,15 +25453,15 @@ var DefaultSecretVault = class {
25208
25453
  } catch (err) {
25209
25454
  if (err.code !== "ENOENT") throw err;
25210
25455
  }
25211
- fs12.mkdirSync(path24.dirname(this.keyFile), { recursive: true });
25456
+ fs13.mkdirSync(path25.dirname(this.keyFile), { recursive: true });
25212
25457
  const key = randomBytes4(KEY_BYTES);
25213
25458
  const passphrase = getVaultPassphrase();
25214
25459
  const initialBytes = passphrase ? wrapDataKey(key, 1, passphrase) : key;
25215
25460
  try {
25216
- fs12.writeFileSync(this.keyFile, initialBytes, { mode: 384, flag: "wx" });
25461
+ fs13.writeFileSync(this.keyFile, initialBytes, { mode: 384, flag: "wx" });
25217
25462
  } catch (err) {
25218
25463
  if (err.code !== "EEXIST") throw err;
25219
- const buf = fs12.readFileSync(this.keyFile);
25464
+ const buf = fs13.readFileSync(this.keyFile);
25220
25465
  if (isWrappedKeyFile(buf)) {
25221
25466
  const { key: winnerKey, version } = unwrapDataKey(buf, this.keyFile);
25222
25467
  this.key = winnerKey;
@@ -25266,7 +25511,7 @@ async function rewriteConfigEncrypted(configPath, vault, patch) {
25266
25511
  }
25267
25512
  const merged = deepMerge(current, patch ?? {});
25268
25513
  const encrypted = encryptConfigSecrets(merged, vault);
25269
- await fsp12.mkdir(path24.dirname(configPath), { recursive: true });
25514
+ await fsp12.mkdir(path25.dirname(configPath), { recursive: true });
25270
25515
  await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
25271
25516
  await restrictFilePermissions(configPath);
25272
25517
  }
@@ -25350,7 +25595,7 @@ function walkCount(node, vault, counter) {
25350
25595
  // src/storage/attachment-store.ts
25351
25596
  import { randomBytes as randomBytes5 } from "node:crypto";
25352
25597
  import * as fsp13 from "node:fs/promises";
25353
- import * as path25 from "node:path";
25598
+ import * as path26 from "node:path";
25354
25599
  var DEFAULT_SPOOL_THRESHOLD = 256 * 1024;
25355
25600
  var PLACEHOLDER_RE = /\[(pasted|image|file) #(\d+)[^\]]*\]|\[file:([^\]]+)\]/g;
25356
25601
  var DefaultAttachmentStore = class {
@@ -25371,7 +25616,7 @@ var DefaultAttachmentStore = class {
25371
25616
  let data = input.data;
25372
25617
  if (this.spoolDir && bytes >= this.spoolThreshold) {
25373
25618
  await fsp13.mkdir(this.spoolDir, { recursive: true });
25374
- spooledPath = path25.join(this.spoolDir, `${id}.bin`);
25619
+ spooledPath = path26.join(this.spoolDir, `${id}.bin`);
25375
25620
  await atomicWrite(spooledPath, input.data, {
25376
25621
  encoding: input.kind === "image" ? "base64" : "utf8"
25377
25622
  });
@@ -25490,8 +25735,8 @@ function mergeAdjacentText(blocks) {
25490
25735
  }
25491
25736
 
25492
25737
  // src/storage/config-loader.ts
25493
- import * as fs13 from "node:fs/promises";
25494
- import * as path26 from "node:path";
25738
+ import * as fs14 from "node:fs/promises";
25739
+ import * as path27 from "node:path";
25495
25740
 
25496
25741
  // src/types/config.ts
25497
25742
  var DEFAULT_TUI_THINKING_WORD = "thinking";
@@ -25975,8 +26220,8 @@ function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => conso
25975
26220
  return out;
25976
26221
  }
25977
26222
  function samePath(a, b) {
25978
- let ra = path26.resolve(a);
25979
- let rb = path26.resolve(b);
26223
+ let ra = path27.resolve(a);
26224
+ let rb = path27.resolve(b);
25980
26225
  if (process.platform === "win32" || process.platform === "darwin") {
25981
26226
  ra = ra.toLowerCase();
25982
26227
  rb = rb.toLowerCase();
@@ -25998,7 +26243,7 @@ function deepMerge2(base, patch) {
25998
26243
  opts
25999
26244
  );
26000
26245
  }
26001
- var DefaultConfigLoader = class {
26246
+ var DefaultConfigLoader = class _DefaultConfigLoader {
26002
26247
  paths;
26003
26248
  strict;
26004
26249
  vault;
@@ -26105,6 +26350,11 @@ var DefaultConfigLoader = class {
26105
26350
  }
26106
26351
  return Object.freeze(cfg);
26107
26352
  }
26353
+ /** Check whether a config object contains only the two bootstrap keys. */
26354
+ static isBootstrapOnly(config) {
26355
+ const BOOTSTRAP_KEYS = /* @__PURE__ */ new Set(["version", "activeProfile"]);
26356
+ return Object.keys(config).every((k) => BOOTSTRAP_KEYS.has(k));
26357
+ }
26108
26358
  async ensureGlobalDefaults() {
26109
26359
  const fp = this.paths.globalConfig;
26110
26360
  const t0 = Date.now();
@@ -26113,7 +26363,7 @@ var DefaultConfigLoader = class {
26113
26363
  let parsed;
26114
26364
  let fileExisted = true;
26115
26365
  try {
26116
- const raw = await fs13.readFile(fp, "utf8");
26366
+ const raw = await fs14.readFile(fp, "utf8");
26117
26367
  const result = safeParse(raw);
26118
26368
  if (!result.ok || !isPlainRecord(result.value)) {
26119
26369
  return;
@@ -26142,9 +26392,12 @@ var DefaultConfigLoader = class {
26142
26392
  fileExisted = false;
26143
26393
  parsed = {};
26144
26394
  }
26395
+ const profileName = parsed.activeProfile ?? "default";
26396
+ const profileFp = this.paths.profileConfig(profileName);
26397
+ await this.ensureProfileConfig(profileFp, parsed, fileExisted);
26145
26398
  const bootstrap = {
26146
26399
  version: 1,
26147
- activeProfile: parsed.activeProfile ?? "default"
26400
+ activeProfile: profileName
26148
26401
  };
26149
26402
  let needsBootstrapWrite = false;
26150
26403
  if (parsed.version !== 1) {
@@ -26153,7 +26406,11 @@ var DefaultConfigLoader = class {
26153
26406
  if (parsed.activeProfile === void 0) {
26154
26407
  needsBootstrapWrite = true;
26155
26408
  }
26409
+ if (!_DefaultConfigLoader.isBootstrapOnly(parsed)) {
26410
+ needsBootstrapWrite = true;
26411
+ }
26156
26412
  if (needsBootstrapWrite) {
26413
+ await backupConfigFile(fp, this.paths);
26157
26414
  await atomicWrite(fp, JSON.stringify(bootstrap, null, 2), { mode: 384 });
26158
26415
  this.events?.emit("storage.write", {
26159
26416
  sessionId: "~config~",
@@ -26165,9 +26422,6 @@ var DefaultConfigLoader = class {
26165
26422
  ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
26166
26423
  });
26167
26424
  }
26168
- const profileName = bootstrap.activeProfile ?? "default";
26169
- const profileFp = this.paths.profileConfig(profileName);
26170
- await this.ensureProfileConfig(profileFp, parsed, fileExisted);
26171
26425
  });
26172
26426
  } catch (err) {
26173
26427
  this.events?.emit("storage.error", {
@@ -26193,77 +26447,92 @@ var DefaultConfigLoader = class {
26193
26447
  * On first boot: migrate content from the old flat global config, or seed
26194
26448
  * with behavior defaults. Subsequent boots: fill any missing keys from
26195
26449
  * BEHAVIOR_DEFAULTS (keeping user settings intact).
26450
+ *
26451
+ * CRITICAL SAFETY NET: when the profile already exists but the old global
26452
+ * config has extra non-bootstrap keys (because WebUI/CLI persistence functions
26453
+ * mistakenly wrote settings to the root config), those keys are merged into
26454
+ * the profile so they are NOT silently destroyed by the subsequent bootstrap
26455
+ * trim. This prevents the "config keeps getting emptied" bug.
26196
26456
  */
26197
26457
  async ensureProfileConfig(profileFp, oldGlobalParsed, oldFileExisted) {
26198
26458
  const t0 = Date.now();
26459
+ let parsed;
26460
+ let existed = true;
26199
26461
  try {
26200
- await withFileLock(profileFp, async () => {
26201
- let parsed;
26202
- let existed = true;
26203
- try {
26204
- const raw = await fs13.readFile(profileFp, "utf8");
26205
- const result = safeParse(raw);
26206
- if (!result.ok || !isPlainRecord(result.value)) {
26207
- this.logWarn("Profile config parse failed \u2014 falling back to defaults", {
26208
- event: "config.profile_parse_failed",
26209
- path: profileFp
26210
- });
26211
- parsed = {};
26212
- } else {
26213
- parsed = result.value;
26462
+ const raw = await fs14.readFile(profileFp, "utf8");
26463
+ const result = safeParse(raw);
26464
+ if (!result.ok || !isPlainRecord(result.value)) {
26465
+ this.logWarn("Profile config parse failed \u2014 falling back to defaults", {
26466
+ event: "config.profile_parse_failed",
26467
+ path: profileFp
26468
+ });
26469
+ parsed = {};
26470
+ } else {
26471
+ parsed = result.value;
26472
+ }
26473
+ } catch (err) {
26474
+ if (err.code !== "ENOENT") {
26475
+ this.logWarn("Profile config read failed", {
26476
+ event: "config.profile_read_failed",
26477
+ path: profileFp,
26478
+ message: toErrorMessage(err)
26479
+ });
26480
+ return;
26481
+ }
26482
+ existed = false;
26483
+ parsed = {};
26484
+ }
26485
+ const profileHasContent = existed && Object.keys(parsed).some((k) => k !== "version" && k !== "activeProfile");
26486
+ let seed;
26487
+ if (!profileHasContent && oldFileExisted && Object.keys(oldGlobalParsed).length >= 1) {
26488
+ seed = { ...oldGlobalParsed };
26489
+ delete seed["activeProfile"];
26490
+ const filled = fillMissingDefaults(seed, BEHAVIOR_DEFAULTS);
26491
+ seed = filled.value;
26492
+ } else {
26493
+ if (existed && !_DefaultConfigLoader.isBootstrapOnly(oldGlobalParsed)) {
26494
+ const BOOTSTRAP_KEYS = /* @__PURE__ */ new Set(["version", "activeProfile"]);
26495
+ let merged = false;
26496
+ for (const [k, v] of Object.entries(oldGlobalParsed)) {
26497
+ if (!BOOTSTRAP_KEYS.has(k) && !(k in parsed)) {
26498
+ parsed[k] = v;
26499
+ merged = true;
26214
26500
  }
26215
- } catch (err) {
26216
- if (err.code !== "ENOENT") {
26217
- this.logWarn("Profile config read failed", {
26218
- event: "config.profile_read_failed",
26219
- path: profileFp,
26220
- message: toErrorMessage(err)
26501
+ }
26502
+ if (merged) {
26503
+ const filled2 = fillMissingDefaults(parsed, BEHAVIOR_DEFAULTS);
26504
+ if (filled2.changed) {
26505
+ seed = filled2.value;
26506
+ await backupConfigFile(profileFp, this.paths);
26507
+ await atomicWrite(profileFp, JSON.stringify(seed, null, 2), { mode: 384 });
26508
+ this.events?.emit("storage.write", {
26509
+ sessionId: "~config~",
26510
+ store: "config",
26511
+ filePath: profileFp,
26512
+ operation: "ensure_profile_defaults",
26513
+ outcome: "success",
26514
+ durationMs: Date.now() - t0,
26515
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
26221
26516
  });
26222
26517
  return;
26223
26518
  }
26224
- existed = false;
26225
- parsed = {};
26226
26519
  }
26227
- let seed;
26228
- if (!existed && oldFileExisted && Object.keys(oldGlobalParsed).length >= 1) {
26229
- seed = { ...oldGlobalParsed };
26230
- delete seed["activeProfile"];
26231
- const filled = fillMissingDefaults(seed, BEHAVIOR_DEFAULTS);
26232
- seed = filled.value;
26233
- } else {
26234
- const filled = fillMissingDefaults(parsed, BEHAVIOR_DEFAULTS);
26235
- if (!filled.changed) return;
26236
- seed = filled.value;
26237
- }
26238
- await atomicWrite(profileFp, JSON.stringify(seed, null, 2), { mode: 384 });
26239
- this.events?.emit("storage.write", {
26240
- sessionId: "~config~",
26241
- store: "config",
26242
- filePath: profileFp,
26243
- operation: "ensure_profile_defaults",
26244
- outcome: "success",
26245
- durationMs: Date.now() - t0,
26246
- ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
26247
- });
26248
- });
26249
- } catch (err) {
26250
- this.events?.emit("storage.error", {
26251
- sessionId: "~config~",
26252
- store: "config",
26253
- filePath: profileFp,
26254
- operation: "ensure_profile_defaults",
26255
- outcome: "failure",
26256
- error: storageErrorString(err),
26257
- recoverable: false,
26258
- durationMs: Date.now() - t0,
26259
- ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
26260
- });
26261
- this.logWarn("Profile config defaults write failed", {
26262
- event: "config.profile_write_failed",
26263
- path: profileFp,
26264
- message: toErrorMessage(err)
26265
- });
26520
+ }
26521
+ const filled = fillMissingDefaults(parsed, BEHAVIOR_DEFAULTS);
26522
+ if (!filled.changed) return;
26523
+ seed = filled.value;
26266
26524
  }
26525
+ await backupConfigFile(profileFp, this.paths);
26526
+ await atomicWrite(profileFp, JSON.stringify(seed, null, 2), { mode: 384 });
26527
+ this.events?.emit("storage.write", {
26528
+ sessionId: "~config~",
26529
+ store: "config",
26530
+ filePath: profileFp,
26531
+ operation: "ensure_profile_defaults",
26532
+ outcome: "success",
26533
+ durationMs: Date.now() - t0,
26534
+ ...this.traceId !== void 0 ? { traceId: this.traceId } : {}
26535
+ });
26267
26536
  }
26268
26537
  /**
26269
26538
  * Persist a sync config to ~/.wrongstack/sync.json, with the token encrypted
@@ -26313,7 +26582,7 @@ var DefaultConfigLoader = class {
26313
26582
  const fp = this.paths.syncConfig;
26314
26583
  const t0 = Date.now();
26315
26584
  try {
26316
- const raw = await fs13.readFile(fp, "utf8");
26585
+ const raw = await fs14.readFile(fp, "utf8");
26317
26586
  const parsed = safeParse(raw);
26318
26587
  if (!parsed.ok || !parsed.value) {
26319
26588
  this.events?.emit("storage.read", {
@@ -26375,7 +26644,7 @@ var DefaultConfigLoader = class {
26375
26644
  const t0 = Date.now();
26376
26645
  let mtimeMs = null;
26377
26646
  try {
26378
- const stat13 = await fs13.stat(file);
26647
+ const stat13 = await fs14.stat(file);
26379
26648
  mtimeMs = stat13.mtimeMs;
26380
26649
  const cached = this.jsonCache.get(file);
26381
26650
  if (cached && cached.mtimeMs === mtimeMs) {
@@ -26405,7 +26674,7 @@ var DefaultConfigLoader = class {
26405
26674
  }
26406
26675
  let raw;
26407
26676
  try {
26408
- raw = await fs13.readFile(file, "utf8");
26677
+ raw = await fs14.readFile(file, "utf8");
26409
26678
  } catch (err) {
26410
26679
  if (err.code !== "ENOENT") {
26411
26680
  this.events?.emit("storage.read", {
@@ -26936,7 +27205,7 @@ ${cat}:`);
26936
27205
 
26937
27206
  // src/storage/queue-store.ts
26938
27207
  import * as fsp15 from "node:fs/promises";
26939
- import * as path27 from "node:path";
27208
+ import * as path28 from "node:path";
26940
27209
  var QueueStore = class {
26941
27210
  file;
26942
27211
  // Use `| undefined` (not `?`) so exactOptionalPropertyTypes doesn't
@@ -26945,7 +27214,7 @@ var QueueStore = class {
26945
27214
  traceId;
26946
27215
  logger;
26947
27216
  constructor(opts) {
26948
- this.file = path27.join(opts.dir, "queue.json");
27217
+ this.file = path28.join(opts.dir, "queue.json");
26949
27218
  this.events = opts.events;
26950
27219
  this.traceId = opts.traceId;
26951
27220
  this.logger = opts.logger;
@@ -27103,7 +27372,7 @@ function isPersistedQueueItem(v) {
27103
27372
  // src/storage/recovery-lock.ts
27104
27373
  import * as fsp16 from "node:fs/promises";
27105
27374
  import * as os3 from "node:os";
27106
- import * as path28 from "node:path";
27375
+ import * as path29 from "node:path";
27107
27376
  var LOCK_FILE = "active.json";
27108
27377
  var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
27109
27378
  var RecoveryLock = class {
@@ -27114,7 +27383,7 @@ var RecoveryLock = class {
27114
27383
  sessionStore;
27115
27384
  probe;
27116
27385
  constructor(opts) {
27117
- this.file = path28.join(opts.dir, LOCK_FILE);
27386
+ this.file = path29.join(opts.dir, LOCK_FILE);
27118
27387
  this.pid = opts.pid ?? process.pid;
27119
27388
  this.hostname = opts.hostname ?? os3.hostname();
27120
27389
  this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
@@ -27175,7 +27444,7 @@ var RecoveryLock = class {
27175
27444
  * null return before calling this.
27176
27445
  */
27177
27446
  async write(sessionId) {
27178
- await ensureDir(path28.dirname(this.file));
27447
+ await ensureDir(path29.dirname(this.file));
27179
27448
  const lock = {
27180
27449
  v: 1,
27181
27450
  sessionId,
@@ -27474,7 +27743,7 @@ function resolveSessionLoggingConfig(cfg) {
27474
27743
  }
27475
27744
 
27476
27745
  // src/storage/session-reader.ts
27477
- import * as fs14 from "node:fs/promises";
27746
+ import * as fs15 from "node:fs/promises";
27478
27747
  var DefaultSessionReader = class _DefaultSessionReader {
27479
27748
  store;
27480
27749
  eventCache = /* @__PURE__ */ new Map();
@@ -27492,7 +27761,7 @@ var DefaultSessionReader = class _DefaultSessionReader {
27492
27761
  const sessionPath = sessionScopedPath(rootDir, sessionId, ".jsonl");
27493
27762
  let mtimeMs = null;
27494
27763
  try {
27495
- const stat13 = await fs14.stat(sessionPath);
27764
+ const stat13 = await fs15.stat(sessionPath);
27496
27765
  mtimeMs = stat13.mtimeMs;
27497
27766
  } catch {
27498
27767
  this.eventCache.delete(sessionId);