@wrongstack/core 0.298.3 → 0.300.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/dist/chronicle/index.js +4 -1
  2. package/dist/coordination/agents/index.js +4 -1
  3. package/dist/coordination/director.d.ts +8 -0
  4. package/dist/coordination/fleet-manager.d.ts +48 -3
  5. package/dist/coordination/ifleet-manager.d.ts +2 -0
  6. package/dist/coordination/index.js +127 -24
  7. package/dist/coordination/multi-agent-coordinator.d.ts +1 -0
  8. package/dist/core/fallback-model.d.ts +48 -0
  9. package/dist/core/index.d.ts +3 -2
  10. package/dist/core/index.js +288 -34
  11. package/dist/core/instruction-template.d.ts +80 -0
  12. package/dist/core/system-prompt-blocks.d.ts +10 -1
  13. package/dist/core/system-prompt-builder.d.ts +35 -1
  14. package/dist/defaults/index.js +358 -117
  15. package/dist/design/index.js +4 -1
  16. package/dist/execution/autonomy-brain.d.ts +7 -0
  17. package/dist/execution/council-brain.d.ts +17 -2
  18. package/dist/execution/council-orchestrator.d.ts +23 -4
  19. package/dist/execution/council-personas.d.ts +10 -0
  20. package/dist/execution/council-prompts.d.ts +12 -1
  21. package/dist/execution/index.d.ts +1 -1
  22. package/dist/execution/index.js +412 -145
  23. package/dist/fleet-notifier.d.ts +9 -2
  24. package/dist/goal/index.js +4 -1
  25. package/dist/hooks/index.js +140 -10
  26. package/dist/hq/exposure.d.ts +0 -11
  27. package/dist/hq/index.js +34 -8
  28. package/dist/hq/protocol/client.d.ts +14 -1
  29. package/dist/hq/protocol/fleet.d.ts +22 -0
  30. package/dist/hq/protocol.js +12 -1
  31. package/dist/index.d.ts +2 -1
  32. package/dist/index.js +1718 -753
  33. package/dist/infrastructure/index.js +50 -2
  34. package/dist/infrastructure/mcp-servers.d.ts +35 -0
  35. package/dist/kernel/events/brain-events.d.ts +9 -0
  36. package/dist/kernel/events/provider-events.d.ts +49 -2
  37. package/dist/kernel/events/sdd-events.d.ts +2 -0
  38. package/dist/models/index.js +1 -1
  39. package/dist/plugin/api.d.ts +6 -0
  40. package/dist/plugin/config.d.ts +55 -0
  41. package/dist/plugin/index.d.ts +1 -1
  42. package/dist/plugin/index.js +138 -22
  43. package/dist/security/index.d.ts +1 -1
  44. package/dist/security/index.js +157 -42
  45. package/dist/security/permission-helpers.d.ts +23 -6
  46. package/dist/security/permission-policy.d.ts +16 -0
  47. package/dist/security/totp.d.ts +14 -0
  48. package/dist/storage/director-state.d.ts +7 -0
  49. package/dist/storage/index.js +46 -9
  50. package/dist/tools/council-tool.d.ts +1 -1
  51. package/dist/tools/fallback-system-config-view-tool.d.ts +1 -1
  52. package/dist/tools/index.js +449 -112
  53. package/dist/types/config/skills-fleet-brain.d.ts +4 -2
  54. package/dist/types/config/tools.d.ts +99 -0
  55. package/dist/types/council.d.ts +11 -0
  56. package/dist/types/index.d.ts +3 -2
  57. package/dist/types/index.js +3 -3
  58. package/dist/types/multi-agent.d.ts +10 -0
  59. package/dist/types/one-shot-llm.d.ts +31 -3
  60. package/dist/types/plugin.d.ts +28 -0
  61. package/dist/types/session.d.ts +5 -1
  62. package/dist/utils/index.js +4 -1
  63. package/dist/utils/wstack-paths.d.ts +2 -0
  64. package/dist/worktree/index.js +47 -25
  65. package/dist/worktree/worktree-manager.d.ts +16 -10
  66. package/instructions/coordination/subagent-baseline.md +8 -0
  67. package/instructions/system-lite.md +83 -3
  68. package/instructions/system-pro.md +286 -97
  69. package/instructions/system.md +236 -85
  70. package/package.json +3 -3
@@ -295,7 +295,7 @@ var InMemoryAgentBridge = class {
295
295
  });
296
296
  }
297
297
  this.inflightGuards.add(correlationId);
298
- return new Promise((resolve17, reject) => {
298
+ return new Promise((resolve19, reject) => {
299
299
  const timer = setTimeout(() => {
300
300
  this.inflightGuards.delete(correlationId);
301
301
  this.pendingRequests.delete(correlationId);
@@ -314,7 +314,7 @@ var InMemoryAgentBridge = class {
314
314
  return;
315
315
  }
316
316
  this.pendingRequests.set(correlationId, {
317
- resolve: resolve17,
317
+ resolve: resolve19,
318
318
  reject,
319
319
  timer
320
320
  });
@@ -663,13 +663,13 @@ var SubagentBudget = class _SubagentBudget {
663
663
  if (!bus?.hasListenerFor("budget.threshold_reached")) {
664
664
  return Promise.resolve("stop");
665
665
  }
666
- return new Promise((resolve17) => {
666
+ return new Promise((resolve19) => {
667
667
  let resolved = false;
668
668
  const respond = (d) => {
669
669
  if (resolved) return;
670
670
  resolved = true;
671
671
  clearTimeout(fallback);
672
- resolve17(d);
672
+ resolve19(d);
673
673
  };
674
674
  const fallback = setTimeout(() => respond("stop"), _SubagentBudget.DECISION_TIMEOUT_MS);
675
675
  const sessionId = this.currentSessionId();
@@ -1374,7 +1374,9 @@ function safeProfileName(name) {
1374
1374
  function activeProfileName(globalRoot) {
1375
1375
  try {
1376
1376
  const parsed = JSON.parse(fs.readFileSync(path.join(globalRoot, "config.json"), "utf8"));
1377
- return safeProfileName(typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0);
1377
+ return safeProfileName(
1378
+ typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0
1379
+ );
1378
1380
  } catch {
1379
1381
  return "default";
1380
1382
  }
@@ -1454,6 +1456,7 @@ function resolveWstackPaths(opts) {
1454
1456
  projectPlan: path.join(projectDir, "plan.json"),
1455
1457
  projectAutophase: path.join(projectDir, "autophase"),
1456
1458
  projectSddBoards: path.join(projectDir, "sdd-boards"),
1459
+ projectRequirementIntakes: path.join(projectDir, "requirement-intakes"),
1457
1460
  syncConfig: path.join(profileDir, "sync.json"),
1458
1461
  configHistoryDir: path.join(globalRoot, "config-history"),
1459
1462
  projectStatus: (projectHash2) => path.join(globalRoot, "projects", projectHash2, "status.json")
@@ -5303,7 +5306,7 @@ function createDelegateTool(opts) {
5303
5306
  };
5304
5307
  }
5305
5308
  async function awaitDelegateAttempt(director, subagentId, taskId, timeoutMs, abortSignal) {
5306
- return new Promise((resolve17) => {
5309
+ return new Promise((resolve19) => {
5307
5310
  let settled = false;
5308
5311
  let timer;
5309
5312
  let offAbort = () => {
@@ -5316,7 +5319,7 @@ async function awaitDelegateAttempt(director, subagentId, taskId, timeoutMs, abo
5316
5319
  offIter();
5317
5320
  offProgress();
5318
5321
  offAbort();
5319
- resolve17(value);
5322
+ resolve19(value);
5320
5323
  };
5321
5324
  const arm = () => {
5322
5325
  if (timer) clearTimeout(timer);
@@ -5658,6 +5661,21 @@ var DirectorStateCheckpoint = class {
5658
5661
  resume(snapshot) {
5659
5662
  this.snapshot = snapshot;
5660
5663
  }
5664
+ /**
5665
+ * After resume, pin the live spawn ceiling from the current profile/flag
5666
+ * while preserving `spawnCount` (cumulative used budget). Checkpoint
5667
+ * metadata previously stored a historical `maxSpawns` that can diverge
5668
+ * from the live runtime ceiling — operators need the live value to win.
5669
+ */
5670
+ applyLiveMaxSpawns(maxSpawns) {
5671
+ if (this.snapshot.maxSpawns === maxSpawns) return;
5672
+ this.snapshot = {
5673
+ ...this.snapshot,
5674
+ maxSpawns
5675
+ };
5676
+ this.bumpUpdatedAt();
5677
+ this.schedule();
5678
+ }
5661
5679
  current() {
5662
5680
  return this.snapshot;
5663
5681
  }
@@ -7170,10 +7188,10 @@ var DirectorTaskRegistry = class _DirectorTaskRegistry {
7170
7188
  pending: taskIds.filter((id) => !done.has(id))
7171
7189
  });
7172
7190
  }
7173
- return new Promise((resolve17) => {
7191
+ return new Promise((resolve19) => {
7174
7192
  const entry = {
7175
7193
  ids: new Set(taskIds),
7176
- resolve: (result) => resolve17({
7194
+ resolve: (result) => resolve19({
7177
7195
  completed: [result],
7178
7196
  pending: taskIds.filter((id) => id !== result.taskId)
7179
7197
  })
@@ -7181,7 +7199,7 @@ var DirectorTaskRegistry = class _DirectorTaskRegistry {
7181
7199
  if (opts?.timeoutMs !== void 0) {
7182
7200
  entry.timer = setTimeout(() => {
7183
7201
  this.anyWaiters.delete(entry);
7184
- resolve17({ completed: [], pending: [...taskIds], timedOut: true });
7202
+ resolve19({ completed: [], pending: [...taskIds], timedOut: true });
7185
7203
  }, opts.timeoutMs);
7186
7204
  }
7187
7205
  this.anyWaiters.add(entry);
@@ -7299,11 +7317,11 @@ ${JSON.stringify(result.result, null, 2)}
7299
7317
  this.makeStoppedResult(taskId, "director", `Unknown task id "${taskId}" \u2014 never assigned`)
7300
7318
  );
7301
7319
  }
7302
- let resolve17;
7320
+ let resolve19;
7303
7321
  const promise = new Promise((done) => {
7304
- resolve17 = done;
7322
+ resolve19 = done;
7305
7323
  });
7306
- this.taskWaiters.set(taskId, { promise, resolve: resolve17 });
7324
+ this.taskWaiters.set(taskId, { promise, resolve: resolve19 });
7307
7325
  return promise;
7308
7326
  }
7309
7327
  recordAssignment(task) {
@@ -9397,7 +9415,7 @@ function truncate(s, max) {
9397
9415
  var QUOTA_EXHAUSTED_RE = /(?:insufficient|exhausted|depleted|exceeded|no|not enough)[-_\s]*(?:quota|credit|balance)|(?:quota|credit|balance)(?:\s+(?:has|have))?(?:\s+been)?[-_\s]*(?:exhausted|depleted|exceeded|insufficient)|billing[_\s-]*(?:hard[_\s-]*)?limit|payment required|spending limit|plan limit|usage[-_\s]*limit[-_\s]*(?:reached|exceeded)/i;
9398
9416
 
9399
9417
  // src/types/provider.ts
9400
- var CONTEXT_OVERFLOW_RE = /context.length|context.window|maximum context|max.*tokens?.*exceeded|prompt is too long|too long|exceeds the context|\btokens\b.*exceed|too many tokens|reduce the length|resulted in \d+ tokens|input.{0,12}too (?:large|long)|context_length_exceeded/i;
9418
+ var CONTEXT_OVERFLOW_RE = /context.length|context.window|maximum context|max.*tokens?.*exceeded|(?:prompt|request|input|messages?).{0,12}too (?:large|long)|exceeds the context|\btokens\b.*exceed|too many tokens|reduce the length|resulted in \d+ tokens|context_length_exceeded/i;
9401
9419
  var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
9402
9420
  var RATE_LIMIT_EXCEEDED_RE = /rate[-_\s]*limit[-_\s]*exceeded/i;
9403
9421
  function classifyProviderError(status, body, message) {
@@ -9407,7 +9425,7 @@ function classifyProviderError(status, body, message) {
9407
9425
  if (status === 408) return "timeout";
9408
9426
  if (status === 599) return "stream_hang";
9409
9427
  if (status === 402 || QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
9410
- if (status === 429 && body?.message && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
9428
+ if (status === 429 && body?.message && body.type !== "rate_limit_exceeded" && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
9411
9429
  return "quota_exhausted";
9412
9430
  }
9413
9431
  if (type === "rate_limit_error" || status === 429) return "rate_limit";
@@ -9467,7 +9485,7 @@ var ProviderError = class extends WrongStackError {
9467
9485
  const e = err;
9468
9486
  const name = e.name;
9469
9487
  if (typeof name !== "string" || !name.endsWith("Error")) return false;
9470
- return typeof e.status === "number" && typeof e.retryable === "boolean" && typeof e.kind === "string";
9488
+ return typeof e.status === "number" && typeof e.retryable === "boolean" && typeof e.kind === "string" && typeof e.describe === "function";
9471
9489
  }
9472
9490
  constructor(message, status, retryable, providerId, opts = {}) {
9473
9491
  const kind = opts.kind ?? classifyProviderError(status, opts.body, message);
@@ -11057,7 +11075,7 @@ async function spawn(host, config, priceLookup) {
11057
11075
  if (host.spawnDepth >= maxSpawnDepth) {
11058
11076
  throw new FleetSpawnBudgetError("max_spawn_depth", maxSpawnDepth, host.spawnDepth);
11059
11077
  }
11060
- if (host.spawnCount >= host.maxSpawns) {
11078
+ if (host.spawnCount >= host.maxSpawns && !config.spawnBudgetExempt) {
11061
11079
  throw new FleetSpawnBudgetError("max_spawns", host.maxSpawns, host.spawnCount + 1);
11062
11080
  }
11063
11081
  if (host.maxFleetCostUsd < Number.POSITIVE_INFINITY) {
@@ -11108,7 +11126,9 @@ async function spawn(host, config, priceLookup) {
11108
11126
  ...Number.isFinite(budget?.remainingSpawns ?? host.maxSpawns - host.spawnCount) ? {
11109
11127
  remainingSpawns: Math.max(
11110
11128
  0,
11111
- (budget?.remainingSpawns ?? host.maxSpawns - host.spawnCount) - 1
11129
+ // Exempt spawns don't consume leader budget, so the reported
11130
+ // headroom is not decremented for them.
11131
+ (budget?.remainingSpawns ?? host.maxSpawns - host.spawnCount) - (config.spawnBudgetExempt ? 0 : 1)
11112
11132
  )
11113
11133
  } : {},
11114
11134
  ...Number.isFinite(maxFleetTokens) ? { maxTokens: maxFleetTokens } : {},
@@ -11121,7 +11141,9 @@ async function spawn(host, config, priceLookup) {
11121
11141
  if (host.fleetManager) {
11122
11142
  host.fleetManager.recordSpawn(result.subagentId, config, priceLookup);
11123
11143
  } else {
11124
- host.spawnCount += 1;
11144
+ if (!config.spawnBudgetExempt) {
11145
+ host.spawnCount += 1;
11146
+ }
11125
11147
  host.subagentMeta.set(result.subagentId, {
11126
11148
  provider: config.provider,
11127
11149
  model: config.model
@@ -11473,12 +11495,12 @@ async function executeSubagentWithTimeout({
11473
11495
  }
11474
11496
  return new Promise((resolveDecision) => {
11475
11497
  let settled = false;
11476
- const resolve17 = (d) => {
11498
+ const resolve19 = (d) => {
11477
11499
  if (settled) return;
11478
11500
  settled = true;
11479
11501
  resolveDecision(d);
11480
11502
  };
11481
- const fallback = setTimeout(() => resolve17("stop"), DECISION_TIMEOUT_MS);
11503
+ const fallback = setTimeout(() => resolve19("stop"), DECISION_TIMEOUT_MS);
11482
11504
  const sessionId = currentSessionId();
11483
11505
  budget._events?.emit("budget.threshold_reached", {
11484
11506
  ...sessionId ? { sessionId } : {},
@@ -11488,11 +11510,11 @@ async function executeSubagentWithTimeout({
11488
11510
  timeoutMs: DECISION_TIMEOUT_MS,
11489
11511
  extend: (extra) => {
11490
11512
  clearTimeout(fallback);
11491
- queueMicrotask(() => resolve17({ extend: extra }));
11513
+ queueMicrotask(() => resolve19({ extend: extra }));
11492
11514
  },
11493
11515
  deny: () => {
11494
11516
  clearTimeout(fallback);
11495
- resolve17("stop");
11517
+ resolve19("stop");
11496
11518
  }
11497
11519
  });
11498
11520
  });
@@ -11879,7 +11901,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
11879
11901
  taskIds.map((id) => {
11880
11902
  const cached = this.completedResults.find((r) => r.taskId === id);
11881
11903
  if (cached) return cached;
11882
- return new Promise((resolve17, reject) => {
11904
+ return new Promise((resolve19, reject) => {
11883
11905
  const timeout = setTimeout(() => {
11884
11906
  this.off("task.completed", handler);
11885
11907
  reject(new Error(`awaitTasks timed out waiting for task "${id}"`));
@@ -11888,7 +11910,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
11888
11910
  if (result.taskId === id) {
11889
11911
  clearTimeout(timeout);
11890
11912
  this.off("task.completed", handler);
11891
- resolve17(result);
11913
+ resolve19(result);
11892
11914
  }
11893
11915
  };
11894
11916
  this.on("task.completed", handler);
@@ -11913,13 +11935,13 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
11913
11935
  const done = new Set(completed.map((r) => r.taskId));
11914
11936
  return { completed, pending: taskIds.filter((id) => !done.has(id)) };
11915
11937
  }
11916
- return new Promise((resolve17) => {
11938
+ return new Promise((resolve19) => {
11917
11939
  let timer;
11918
11940
  const handler = ({ result }) => {
11919
11941
  if (!ids.has(result.taskId)) return;
11920
11942
  if (timer) clearTimeout(timer);
11921
11943
  this.off("task.completed", handler);
11922
- resolve17({
11944
+ resolve19({
11923
11945
  completed: [result],
11924
11946
  pending: taskIds.filter((id) => id !== result.taskId)
11925
11947
  });
@@ -11927,7 +11949,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
11927
11949
  if (opts?.timeoutMs !== void 0) {
11928
11950
  timer = setTimeout(() => {
11929
11951
  this.off("task.completed", handler);
11930
- resolve17({ completed: [], pending: [...taskIds], timedOut: true });
11952
+ resolve19({ completed: [], pending: [...taskIds], timedOut: true });
11931
11953
  }, opts.timeoutMs);
11932
11954
  }
11933
11955
  this.on("task.completed", handler);
@@ -12046,8 +12068,17 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
12046
12068
  durationMs: 0
12047
12069
  };
12048
12070
  this.completedResults.push(synthetic);
12071
+ this.trimCompletedResults();
12049
12072
  this.emit("task.completed", { task, result: synthetic });
12050
12073
  }
12074
+ trimCompletedResults() {
12075
+ if (this.completedResults.length > _DefaultMultiAgentCoordinator.MAX_COMPLETED_RESULTS) {
12076
+ this.completedResults.splice(
12077
+ 0,
12078
+ this.completedResults.length - _DefaultMultiAgentCoordinator.MAX_COMPLETED_RESULTS
12079
+ );
12080
+ }
12081
+ }
12051
12082
  async runDispatched(subagentId, task) {
12052
12083
  const subagent = this.subagents.get(subagentId);
12053
12084
  if (!subagent) return;
@@ -12181,12 +12212,7 @@ var DefaultMultiAgentCoordinator = class _DefaultMultiAgentCoordinator extends E
12181
12212
  }
12182
12213
  recordCompletion(result) {
12183
12214
  this.completedResults.push(result);
12184
- if (this.completedResults.length > _DefaultMultiAgentCoordinator.MAX_COMPLETED_RESULTS) {
12185
- this.completedResults.splice(
12186
- 0,
12187
- this.completedResults.length - _DefaultMultiAgentCoordinator.MAX_COMPLETED_RESULTS
12188
- );
12189
- }
12215
+ this.trimCompletedResults();
12190
12216
  this.totalIterations += result.iterations;
12191
12217
  if (this.inFlight > 0) {
12192
12218
  this.inFlight--;
@@ -13604,10 +13630,10 @@ function validateAgainstSchema(value, schema) {
13604
13630
  return { ok: errors.length === 0, errors };
13605
13631
  }
13606
13632
  var MAX_SCHEMA_DEPTH = 64;
13607
- function walk(value, schema, path40, errors, depth) {
13633
+ function walk(value, schema, path42, errors, depth) {
13608
13634
  if (depth > MAX_SCHEMA_DEPTH) {
13609
13635
  errors.push({
13610
- path: path40 || "<root>",
13636
+ path: path42 || "<root>",
13611
13637
  message: `schema nesting exceeds maximum depth (${MAX_SCHEMA_DEPTH})`
13612
13638
  });
13613
13639
  return;
@@ -13615,7 +13641,7 @@ function walk(value, schema, path40, errors, depth) {
13615
13641
  if (schema.enum !== void 0) {
13616
13642
  if (!enumIncludes(schema.enum, value)) {
13617
13643
  errors.push({
13618
- path: path40 || "<root>",
13644
+ path: path42 || "<root>",
13619
13645
  message: `expected one of ${JSON.stringify(schema.enum)}, got ${JSON.stringify(value)}`
13620
13646
  });
13621
13647
  return;
@@ -13624,7 +13650,7 @@ function walk(value, schema, path40, errors, depth) {
13624
13650
  if (typeof schema.type === "string") {
13625
13651
  if (!checkType(value, schema.type)) {
13626
13652
  errors.push({
13627
- path: path40 || "<root>",
13653
+ path: path42 || "<root>",
13628
13654
  message: `expected ${schema.type}, got ${describeType(value)} (${previewValue(value)})`
13629
13655
  });
13630
13656
  return;
@@ -13636,7 +13662,7 @@ function walk(value, schema, path40, errors, depth) {
13636
13662
  if (!(req in obj)) {
13637
13663
  const expected = schema.properties?.[req]?.type;
13638
13664
  errors.push({
13639
- path: joinPath(path40, req),
13665
+ path: joinPath(path42, req),
13640
13666
  message: `required property missing${typeof expected === "string" ? ` (expected ${expected})` : ""}`
13641
13667
  });
13642
13668
  }
@@ -13644,14 +13670,14 @@ function walk(value, schema, path40, errors, depth) {
13644
13670
  if (schema.properties) {
13645
13671
  for (const [key, subSchema] of Object.entries(schema.properties)) {
13646
13672
  if (key in obj) {
13647
- walk(obj[key], subSchema, joinPath(path40, key), errors, depth + 1);
13673
+ walk(obj[key], subSchema, joinPath(path42, key), errors, depth + 1);
13648
13674
  }
13649
13675
  }
13650
13676
  }
13651
13677
  }
13652
13678
  if (schema.type === "array" && Array.isArray(value) && schema.items) {
13653
13679
  for (let i = 0; i < value.length; i++) {
13654
- walk(value[i], schema.items, `${path40}[${i}]`, errors, depth + 1);
13680
+ walk(value[i], schema.items, `${path42}[${i}]`, errors, depth + 1);
13655
13681
  }
13656
13682
  }
13657
13683
  }
@@ -13947,7 +13973,7 @@ function invalid(sessionId) {
13947
13973
 
13948
13974
  // src/utils/sleep.ts
13949
13975
  function sleep(ms) {
13950
- return new Promise((resolve17) => setTimeout(resolve17, ms));
13976
+ return new Promise((resolve19) => setTimeout(resolve19, ms));
13951
13977
  }
13952
13978
 
13953
13979
  // src/utils/slug.ts
@@ -16218,7 +16244,7 @@ var SessionCheckpointCas = class {
16218
16244
  }
16219
16245
  };
16220
16246
  function defaultRunGit(args, cwd) {
16221
- return new Promise((resolve17) => {
16247
+ return new Promise((resolve19) => {
16222
16248
  const stdoutChunks = [];
16223
16249
  const stderrChunks = [];
16224
16250
  let stdoutBytes = 0;
@@ -16261,8 +16287,8 @@ function defaultRunGit(args, cwd) {
16261
16287
  stdoutTruncated,
16262
16288
  stderrTruncated
16263
16289
  });
16264
- child.on("error", (err) => resolve17(result(1, err.message)));
16265
- child.on("close", (code) => resolve17(result(code ?? 1)));
16290
+ child.on("error", (err) => resolve19(result(1, err.message)));
16291
+ child.on("close", (code) => resolve19(result(code ?? 1)));
16266
16292
  });
16267
16293
  }
16268
16294
 
@@ -16311,14 +16337,14 @@ function sessionIdResolutionError(resolution) {
16311
16337
  }
16312
16338
 
16313
16339
  // src/storage/session-read-scrubber.ts
16314
- function scrubPersistedSessionEvent(event, scrubber) {
16315
- return scrubber.scrubObject(event);
16340
+ function scrubPersistedSessionEvent(event, scrubber2) {
16341
+ return scrubber2.scrubObject(event);
16316
16342
  }
16317
- function scrubPersistedSessionData(data, scrubber) {
16318
- return scrubber.scrubObject(data);
16343
+ function scrubPersistedSessionData(data, scrubber2) {
16344
+ return scrubber2.scrubObject(data);
16319
16345
  }
16320
- function scrubPersistedSessionSummary(summary, scrubber) {
16321
- const scrubbed = scrubber.scrubObject(summary);
16346
+ function scrubPersistedSessionSummary(summary, scrubber2) {
16347
+ const scrubbed = scrubber2.scrubObject(summary);
16322
16348
  const { name: _name, lastUserMessage: _lastUserMessage, ...rest } = scrubbed;
16323
16349
  const title = sessionContentText(scrubbed.title) || "(empty session)";
16324
16350
  const name = scrubbed.name === void 0 ? "" : sessionContentText(scrubbed.name);
@@ -18902,6 +18928,7 @@ var Director = class _Director {
18902
18928
  }
18903
18929
  setCheckpointState(snapshot) {
18904
18930
  setCheckpointState(this.checkpointHost(), snapshot);
18931
+ this.applyResumeBudget(snapshot);
18905
18932
  }
18906
18933
  async readSession(subagentId, tail) {
18907
18934
  return readDirectorSubagentSession({
@@ -18953,6 +18980,22 @@ var Director = class _Director {
18953
18980
  }
18954
18981
  resumeFromCheckpoint(snapshot) {
18955
18982
  resumeFromCheckpoint(this.checkpointHost(), snapshot);
18983
+ this.applyResumeBudget(snapshot);
18984
+ }
18985
+ /**
18986
+ * After re-attaching checkpoint metadata, pin the live maxSpawns ceiling
18987
+ * (profile/flag/env wins over historical checkpoint metadata). The
18988
+ * historical cumulative spawn counter is deliberately NOT restored — the
18989
+ * lifetime budget is scoped to this director run, so a restarted session
18990
+ * resumes with a fresh budget rather than a possibly-exhausted counter.
18991
+ */
18992
+ applyResumeBudget(snapshot) {
18993
+ if (this.fleetManager) {
18994
+ this.fleetManager.restoreFromCheckpoint(snapshot);
18995
+ }
18996
+ this.stateCheckpoint?.applyLiveMaxSpawns(
18997
+ Number.isFinite(this.maxSpawns) ? this.maxSpawns : void 0
18998
+ );
18956
18999
  }
18957
19000
  };
18958
19001
 
@@ -21620,12 +21663,12 @@ function verifyFiles(tokens, files) {
21620
21663
  const violations = [];
21621
21664
  let onPalette = 0;
21622
21665
  let offPalette = 0;
21623
- for (const { path: path40, text } of files) {
21666
+ for (const { path: path42, text } of files) {
21624
21667
  const lines = text.split("\n");
21625
21668
  lines.forEach((lineText, i) => {
21626
21669
  const lineNo = i + 1;
21627
21670
  const flag = (snippet, reason, axis = "color") => {
21628
- violations.push({ file: path40, line: lineNo, snippet: snippet.slice(0, 80), reason, axis });
21671
+ violations.push({ file: path42, line: lineNo, snippet: snippet.slice(0, 80), reason, axis });
21629
21672
  };
21630
21673
  for (const re of [HEX_RE, FUNC_COLOR_RE]) {
21631
21674
  re.lastIndex = 0;
@@ -22308,9 +22351,9 @@ function buildRecoveryStrategies(opts) {
22308
22351
  const delayMs = err.body?.retryAfterMs ?? 5e3;
22309
22352
  const delay = Math.min(6e4, Math.max(1e3, delayMs));
22310
22353
  if (ctx?.signal) {
22311
- await new Promise((resolve17) => {
22354
+ await new Promise((resolve19) => {
22312
22355
  if (ctx.signal.aborted) {
22313
- resolve17();
22356
+ resolve19();
22314
22357
  return;
22315
22358
  }
22316
22359
  let settled = false;
@@ -22321,7 +22364,7 @@ function buildRecoveryStrategies(opts) {
22321
22364
  settled = true;
22322
22365
  if (timer !== void 0) clearTimeout(timer);
22323
22366
  ctx.signal.removeEventListener("abort", onAbort);
22324
- resolve17();
22367
+ resolve19();
22325
22368
  };
22326
22369
  timer = setTimeout(finish, delay);
22327
22370
  ctx.signal.addEventListener("abort", onAbort, { once: true });
@@ -24514,8 +24557,8 @@ async function streamProviderToResponse(provider, req, signal, ctx, events, logg
24514
24557
  });
24515
24558
  await Promise.race([
24516
24559
  drainPromise,
24517
- new Promise((resolve17) => {
24518
- drainTimer = setTimeout(resolve17, STREAM_DRAIN_TIMEOUT_MS);
24560
+ new Promise((resolve19) => {
24561
+ drainTimer = setTimeout(resolve19, STREAM_DRAIN_TIMEOUT_MS);
24519
24562
  })
24520
24563
  ]);
24521
24564
  } finally {
@@ -24675,8 +24718,54 @@ function runWithNetworkTelemetry(context, run) {
24675
24718
  return storage.run(context, run);
24676
24719
  }
24677
24720
 
24721
+ // src/security/error-sanitize.ts
24722
+ import { homedir as homedir2 } from "node:os";
24723
+ var scrubber = new DefaultSecretScrubber();
24724
+ function scrubErrorText(text) {
24725
+ if (!text) return text;
24726
+ let out = scrubber.scrub(text);
24727
+ const home = safeHomedir();
24728
+ if (home && home.length > 2) {
24729
+ out = replaceAllCaseInsensitive(out, home, "~");
24730
+ const alt = home.includes("\\") ? home.replace(/\\/g, "/") : home.replace(/\//g, "\\");
24731
+ if (alt !== home) out = replaceAllCaseInsensitive(out, alt, "~");
24732
+ }
24733
+ return out;
24734
+ }
24735
+ function safeHomedir() {
24736
+ try {
24737
+ return homedir2();
24738
+ } catch {
24739
+ return "";
24740
+ }
24741
+ }
24742
+ function replaceAllCaseInsensitive(haystack, needle, replacement) {
24743
+ const lowerHay = haystack.toLowerCase();
24744
+ const lowerNeedle = needle.toLowerCase();
24745
+ let idx = lowerHay.indexOf(lowerNeedle);
24746
+ if (idx === -1) return haystack;
24747
+ let out = "";
24748
+ let from = 0;
24749
+ while (idx !== -1) {
24750
+ out += haystack.slice(from, idx) + replacement;
24751
+ from = idx + needle.length;
24752
+ idx = lowerHay.indexOf(lowerNeedle, from);
24753
+ }
24754
+ return out + haystack.slice(from);
24755
+ }
24756
+
24678
24757
  // src/core/provider-runner.ts
24679
24758
  import { randomUUID as randomUUID14 } from "node:crypto";
24759
+ function scrubProviderBody(body) {
24760
+ if (!body) return void 0;
24761
+ return {
24762
+ ...body,
24763
+ ...body.type !== void 0 ? { type: scrubErrorText(body.type) } : {},
24764
+ ...body.message !== void 0 ? { message: scrubErrorText(body.message) } : {},
24765
+ ...body.raw !== void 0 ? { raw: scrubErrorText(body.raw) } : {},
24766
+ ...body.requestId !== void 0 ? { requestId: scrubErrorText(body.requestId) } : {}
24767
+ };
24768
+ }
24680
24769
  function providerLogCtx(p, r) {
24681
24770
  return {
24682
24771
  providerId: p.id,
@@ -24761,7 +24850,10 @@ async function runProviderWithRetry(opts) {
24761
24850
  const isProviderErr = err instanceof ProviderError || ProviderError.isProviderError(err);
24762
24851
  const errAsErr = err instanceof Error ? err : new Error(String(err));
24763
24852
  const canRetry = retry.shouldRetry(isProviderErr ? err : errAsErr, attempt);
24764
- const description = isProviderErr ? err.describe() : errAsErr.message;
24853
+ const providerErrorBody = isProviderErr ? scrubProviderBody(err.body) : void 0;
24854
+ const description = scrubErrorText(
24855
+ isProviderErr ? err.describe() : errAsErr.message
24856
+ );
24765
24857
  const delay = canRetry ? Math.round(retry.delayMs(attempt, isProviderErr ? err : errAsErr)) : void 0;
24766
24858
  events.emit("provider.attempt.failed", {
24767
24859
  ...correlation,
@@ -24774,7 +24866,8 @@ async function runProviderWithRetry(opts) {
24774
24866
  retryable: canRetry,
24775
24867
  retryScheduled: canRetry,
24776
24868
  ...delay !== void 0 ? { retryDelayMs: delay } : {},
24777
- ...isProviderErr && err.body?.requestId ? { providerRequestId: err.body.requestId } : {}
24869
+ ...providerErrorBody?.requestId ? { providerRequestId: providerErrorBody.requestId } : {},
24870
+ ...providerErrorBody ? { errorBody: providerErrorBody } : {}
24778
24871
  });
24779
24872
  if (!canRetry) {
24780
24873
  events.emit("provider.error", {
@@ -24782,13 +24875,15 @@ async function runProviderWithRetry(opts) {
24782
24875
  providerId: isProviderErr ? err.providerId : provider.id,
24783
24876
  status: isProviderErr ? err.status : 0,
24784
24877
  description,
24785
- retryable: false
24878
+ retryable: false,
24879
+ ...providerErrorBody ? { errorBody: providerErrorBody } : {}
24786
24880
  });
24787
24881
  logger.error(`Provider call failed after ${attempt + 1} attempt(s) \u2014 ${description}`, {
24788
24882
  ...providerLogCtx(provider, request),
24789
24883
  attempts: attempt + 1,
24790
24884
  errorDescription: description,
24791
24885
  status: isProviderErr ? err.status : void 0,
24886
+ errorBody: providerErrorBody,
24792
24887
  errorName: err instanceof Error ? err.name : void 0,
24793
24888
  errorStack: err instanceof Error ? err.stack?.split("\n").slice(0, 3).join("\n") : void 0
24794
24889
  });
@@ -24802,7 +24897,8 @@ async function runProviderWithRetry(opts) {
24802
24897
  maxAttempts,
24803
24898
  delayMs: delay,
24804
24899
  errorDescription: description,
24805
- status: isProviderErr ? err.status : void 0
24900
+ status: isProviderErr ? err.status : void 0,
24901
+ errorBody: providerErrorBody
24806
24902
  });
24807
24903
  events.emit("provider.retry", {
24808
24904
  sessionId: resolveEventSessionId(ctx),
@@ -24810,9 +24906,10 @@ async function runProviderWithRetry(opts) {
24810
24906
  attempt: attemptNum,
24811
24907
  delayMs: delay,
24812
24908
  status: isProviderErr ? err.status : 0,
24813
- description
24909
+ description,
24910
+ ...providerErrorBody ? { errorBody: providerErrorBody } : {}
24814
24911
  });
24815
- await new Promise((resolve17, reject) => {
24912
+ await new Promise((resolve19, reject) => {
24816
24913
  let settled = false;
24817
24914
  const cleanup = () => {
24818
24915
  clearTimeout(t);
@@ -24828,7 +24925,7 @@ async function runProviderWithRetry(opts) {
24828
24925
  if (settled) return;
24829
24926
  settled = true;
24830
24927
  cleanup();
24831
- resolve17();
24928
+ resolve19();
24832
24929
  }, delay);
24833
24930
  if (signal.aborted) {
24834
24931
  onAbort();
@@ -26580,14 +26677,14 @@ ${head}${TOOL_OUTPUT_ARTIFACT_OMISSION}${tail}`;
26580
26677
  return content;
26581
26678
  }
26582
26679
  }
26583
- function hashPermissionInput(input, scrubber) {
26680
+ function hashPermissionInput(input, scrubber2) {
26584
26681
  let serialized;
26585
26682
  try {
26586
26683
  serialized = JSON.stringify(input) ?? "";
26587
26684
  } catch {
26588
26685
  serialized = String(input);
26589
26686
  }
26590
- return createHash7("sha256").update(scrubber.scrub(serialized), "utf8").digest("hex");
26687
+ return createHash7("sha256").update(scrubber2.scrub(serialized), "utf8").digest("hex");
26591
26688
  }
26592
26689
  function sliceUtf8Prefix(text, maxBytes) {
26593
26690
  if (maxBytes <= 0) return "";
@@ -27090,18 +27187,18 @@ ${errorDetails}`,
27090
27187
  if (this.opts.confirmAwaiter) {
27091
27188
  const awaiter = this.opts.confirmAwaiter;
27092
27189
  const choice = await new Promise(
27093
- (resolve17, reject) => {
27190
+ (resolve19, reject) => {
27094
27191
  const signal = ctx.signal;
27095
- const onAbort = () => resolve17("abort");
27192
+ const onAbort = () => resolve19("abort");
27096
27193
  if (signal.aborted) {
27097
- resolve17("abort");
27194
+ resolve19("abort");
27098
27195
  return;
27099
27196
  }
27100
27197
  signal.addEventListener("abort", onAbort, { once: true });
27101
27198
  awaiter(tool, use.input, use.id, suggestedPattern).then(
27102
27199
  (c) => {
27103
27200
  signal.removeEventListener("abort", onAbort);
27104
- resolve17(c);
27201
+ resolve19(c);
27105
27202
  },
27106
27203
  (e) => {
27107
27204
  signal.removeEventListener("abort", onAbort);
@@ -27439,7 +27536,7 @@ ${post.additionalContext}`;
27439
27536
  toolPromise.catch(() => {
27440
27537
  });
27441
27538
  try {
27442
- output = await new Promise((resolve17, reject) => {
27539
+ output = await new Promise((resolve19, reject) => {
27443
27540
  const onAbort = () => {
27444
27541
  setTimeout(() => reject(abortReasonToError(combined.reason)), 0);
27445
27542
  };
@@ -27447,7 +27544,7 @@ ${post.additionalContext}`;
27447
27544
  toolPromise.then(
27448
27545
  (v) => {
27449
27546
  combined.removeEventListener("abort", onAbort);
27450
- resolve17(v);
27547
+ resolve19(v);
27451
27548
  },
27452
27549
  (e) => {
27453
27550
  combined.removeEventListener("abort", onAbort);
@@ -27902,6 +27999,38 @@ var sshManagerServer = () => ({
27902
27999
  permission: "confirm",
27903
28000
  requestTimeoutMs: 18e4
27904
28001
  });
28002
+ var requirementIntakeServer = () => ({
28003
+ name: "requirement-intake",
28004
+ description: "WrongStack Requirements Intake \u2014 list intake records and file new ones (project-scoped, --writable)",
28005
+ transport: "stdio",
28006
+ command: "wstack-requirement-intake-mcp",
28007
+ args: ["--project-root", ".", "--writable"],
28008
+ permission: "auto"
28009
+ });
28010
+ var kanbanServer = () => ({
28011
+ name: "kanban",
28012
+ description: "WrongStack Kanban \u2014 inspect and manage project work boards (project-scoped, manage tier, no destructive ops)",
28013
+ transport: "stdio",
28014
+ command: "wstack-kanban-mcp",
28015
+ args: ["--project-root", ".", "--writable"],
28016
+ permission: "confirm"
28017
+ });
28018
+ var mailboxServer = () => ({
28019
+ name: "mailbox",
28020
+ description: "WrongStack Mailbox \u2014 read and send project agent mail (project-scoped, no admin/credentials)",
28021
+ transport: "stdio",
28022
+ command: "wstack-mailbox-mcp",
28023
+ args: ["--project-root", ".", "--actor", "external-agent", "--writable"],
28024
+ permission: "auto"
28025
+ });
28026
+ var codebaseIndexServer = () => ({
28027
+ name: "codebase-index",
28028
+ description: "WrongStack Codebase Index \u2014 symbol search and dependency graphs (project-scoped, --writable)",
28029
+ transport: "stdio",
28030
+ command: "wstack-codebase-index-mcp",
28031
+ args: ["--project-root", ".", "--writable"],
28032
+ permission: "auto"
28033
+ });
27905
28034
  var allServers = () => ({
27906
28035
  filesystem: { ...filesystemServer(), enabled: false },
27907
28036
  github: { ...githubServer(), enabled: false },
@@ -27916,7 +28045,11 @@ var allServers = () => ({
27916
28045
  "zai-vision": { ...zaiVisionServer(), enabled: false },
27917
28046
  "minimax-vision": { ...miniMaxVisionServer(), enabled: false },
27918
28047
  playwright: { ...playwrightServer(), enabled: false },
27919
- ssh: { ...sshManagerServer(), enabled: false }
28048
+ ssh: { ...sshManagerServer(), enabled: false },
28049
+ kanban: { ...kanbanServer(), enabled: false },
28050
+ mailbox: { ...mailboxServer(), enabled: false },
28051
+ "codebase-index": { ...codebaseIndexServer(), enabled: false },
28052
+ "requirement-intake": { ...requirementIntakeServer(), enabled: false }
27920
28053
  });
27921
28054
 
27922
28055
  // src/models/codex-catalog.ts
@@ -28359,7 +28492,7 @@ var DefaultModelsRegistry = class {
28359
28492
  async load(opts = {}) {
28360
28493
  if (this.payload && !opts.force) return this.payload;
28361
28494
  if (this.seed) {
28362
- this.payload = this.seed;
28495
+ this.payload = this.withExtraOverlay(this.seed);
28363
28496
  this.fetchedAt = /* @__PURE__ */ new Date();
28364
28497
  return this.payload;
28365
28498
  }
@@ -28979,9 +29112,9 @@ var DefaultHealthRegistry = class {
28979
29112
  }
28980
29113
  async runOne(check) {
28981
29114
  let timer = null;
28982
- const timeout = new Promise((resolve17) => {
29115
+ const timeout = new Promise((resolve19) => {
28983
29116
  timer = setTimeout(
28984
- () => resolve17({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
29117
+ () => resolve19({ status: "unhealthy", detail: `timeout after ${this.timeoutMs}ms` }),
28985
29118
  this.timeoutMs
28986
29119
  );
28987
29120
  });
@@ -29179,7 +29312,7 @@ async function startMetricsServer(opts) {
29179
29312
  const tls = opts.tls;
29180
29313
  const useHttps = !!(tls?.cert && tls?.key);
29181
29314
  const host = opts.host ?? "127.0.0.1";
29182
- const path40 = opts.path ?? "/metrics";
29315
+ const path42 = opts.path ?? "/metrics";
29183
29316
  const healthPath = opts.healthPath ?? "/healthz";
29184
29317
  const healthRegistry = opts.healthRegistry;
29185
29318
  const token = opts.token;
@@ -29202,7 +29335,7 @@ async function startMetricsServer(opts) {
29202
29335
  return;
29203
29336
  }
29204
29337
  const url = req.url.split("?")[0];
29205
- if (url === path40) {
29338
+ if (url === path42) {
29206
29339
  let body;
29207
29340
  try {
29208
29341
  body = renderPrometheus(opts.sink.snapshot());
@@ -29248,14 +29381,14 @@ async function startMetricsServer(opts) {
29248
29381
  const { createServer } = await import("node:http");
29249
29382
  server = createServer(listener);
29250
29383
  }
29251
- await new Promise((resolve17, reject) => {
29384
+ await new Promise((resolve19, reject) => {
29252
29385
  const onError = (err) => {
29253
29386
  server.off("listening", onListening);
29254
29387
  reject(err);
29255
29388
  };
29256
29389
  const onListening = () => {
29257
29390
  server.off("error", onError);
29258
- resolve17();
29391
+ resolve19();
29259
29392
  };
29260
29393
  server.once("error", onError);
29261
29394
  server.once("listening", onListening);
@@ -29266,9 +29399,9 @@ async function startMetricsServer(opts) {
29266
29399
  const protocol = useHttps ? "https" : "http";
29267
29400
  return {
29268
29401
  port: boundPort,
29269
- url: `${protocol}://${host}:${boundPort}${path40}`,
29270
- close: () => new Promise((resolve17, reject) => {
29271
- server.close((err) => err ? reject(err) : resolve17());
29402
+ url: `${protocol}://${host}:${boundPort}${path42}`,
29403
+ close: () => new Promise((resolve19, reject) => {
29404
+ server.close((err) => err ? reject(err) : resolve19());
29272
29405
  })
29273
29406
  };
29274
29407
  }
@@ -29539,6 +29672,7 @@ function startOtlpTraceExporter(opts) {
29539
29672
 
29540
29673
  // src/security/permission-policy.ts
29541
29674
  import * as fs12 from "node:fs/promises";
29675
+ import * as path36 from "node:path";
29542
29676
 
29543
29677
  // src/security/permission-policy-schema.ts
29544
29678
  var TRUST_POLICY_LIMITS = Object.freeze({
@@ -29583,19 +29717,19 @@ var UNSAFE_PROPERTY_NAMES = /* @__PURE__ */ new Set(["__proto__", "prototype", "
29583
29717
  function isRecord5(value) {
29584
29718
  return typeof value === "object" && value !== null && !Array.isArray(value);
29585
29719
  }
29586
- function error(diagnostics, code, path40, message) {
29587
- diagnostics.push({ severity: "error", code, path: path40, message });
29720
+ function error(diagnostics, code, path42, message) {
29721
+ diagnostics.push({ severity: "error", code, path: path42, message });
29588
29722
  }
29589
- function validatePatterns(value, path40, diagnostics) {
29723
+ function validatePatterns(value, path42, diagnostics) {
29590
29724
  if (!Array.isArray(value)) {
29591
- error(diagnostics, "invalid_pattern_list", path40, "must be an array of strings");
29725
+ error(diagnostics, "invalid_pattern_list", path42, "must be an array of strings");
29592
29726
  return void 0;
29593
29727
  }
29594
29728
  if (value.length > TRUST_POLICY_LIMITS.maxPatternsPerRule) {
29595
29729
  error(
29596
29730
  diagnostics,
29597
29731
  "too_many_patterns",
29598
- path40,
29732
+ path42,
29599
29733
  `must contain at most ${TRUST_POLICY_LIMITS.maxPatternsPerRule} patterns`
29600
29734
  );
29601
29735
  return void 0;
@@ -29603,7 +29737,7 @@ function validatePatterns(value, path40, diagnostics) {
29603
29737
  const patterns = [];
29604
29738
  const seen = /* @__PURE__ */ new Set();
29605
29739
  for (const [index, pattern] of value.entries()) {
29606
- const itemPath = `${path40}[${index}]`;
29740
+ const itemPath = `${path42}[${index}]`;
29607
29741
  if (typeof pattern !== "string" || pattern.length === 0 || pattern.length > TRUST_POLICY_LIMITS.maxPatternChars) {
29608
29742
  error(
29609
29743
  diagnostics,
@@ -30018,6 +30152,8 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
30018
30152
  };
30019
30153
 
30020
30154
  // src/security/permission-helpers.ts
30155
+ import { realpathSync } from "node:fs";
30156
+ import * as path35 from "node:path";
30021
30157
  function matchesTrust(patterns, subject) {
30022
30158
  return patterns.includes(subject) || matchAny(patterns, subject);
30023
30159
  }
@@ -30088,9 +30224,51 @@ var SHELL_READ_VERBS = /* @__PURE__ */ new Set([
30088
30224
  function stripShellQuotes(value) {
30089
30225
  return value.replace(/^['"]|['"]$/g, "");
30090
30226
  }
30227
+ var AGENT_STATE_SENSITIVE_BASENAMES = /^(?:config\.json|config\.local\.json|trust\.json|auth\.json|\.key)$/i;
30228
+ function unescapeGlobSubject(value) {
30229
+ return value.replace(/\\([*?[\]])/g, "$1");
30230
+ }
30231
+ function normalizeForCompare(value) {
30232
+ const forward = unescapeGlobSubject(value).replace(/\\/g, "/").replace(/\/+$/, "");
30233
+ return process.platform === "win32" ? forward.toLowerCase() : forward;
30234
+ }
30235
+ function realpathOfNearestExisting(p) {
30236
+ let probe = p;
30237
+ const tail = [];
30238
+ for (; ; ) {
30239
+ try {
30240
+ return tail.length === 0 ? realpathSync(probe) : path35.join(realpathSync(probe), ...tail);
30241
+ } catch {
30242
+ const parent = path35.dirname(probe);
30243
+ if (parent === probe) return p;
30244
+ tail.unshift(path35.basename(probe));
30245
+ probe = parent;
30246
+ }
30247
+ }
30248
+ }
30249
+ var agentStateRootRealCache = /* @__PURE__ */ new Map();
30250
+ function isInsideAgentStateRoot(absPath) {
30251
+ const lexicalRoot = normalizeForCompare(path35.resolve(wstackGlobalRoot()));
30252
+ if (!lexicalRoot) return false;
30253
+ const target = normalizeForCompare(absPath);
30254
+ if (target === lexicalRoot || target.startsWith(`${lexicalRoot}/`)) return true;
30255
+ let realRoot = agentStateRootRealCache.get(lexicalRoot);
30256
+ if (realRoot === void 0) {
30257
+ realRoot = normalizeForCompare(realpathOfNearestExisting(path35.resolve(wstackGlobalRoot())));
30258
+ agentStateRootRealCache.set(lexicalRoot, realRoot);
30259
+ }
30260
+ if (!realRoot || realRoot === lexicalRoot) return false;
30261
+ const realTarget = normalizeForCompare(realpathOfNearestExisting(absPath));
30262
+ return realTarget === realRoot || realTarget.startsWith(`${realRoot}/`);
30263
+ }
30264
+ function isProtectedAgentStatePath(absPath) {
30265
+ if (!isInsideAgentStateRoot(absPath)) return false;
30266
+ return AGENT_STATE_SENSITIVE_BASENAMES.test(path35.basename(normalizeForCompare(absPath)));
30267
+ }
30091
30268
  function pathLooksSensitive(rawPath) {
30092
30269
  const normalized = stripShellQuotes(rawPath).replace(/\\/g, "/");
30093
- return SENSITIVE_READ_PATHS.some((pattern) => pattern.test(normalized));
30270
+ if (SENSITIVE_READ_PATHS.some((pattern) => pattern.test(normalized))) return true;
30271
+ return isProtectedAgentStatePath(normalized);
30094
30272
  }
30095
30273
  function inputPathLooksSensitive(input) {
30096
30274
  if (!input || typeof input !== "object") return false;
@@ -30114,6 +30292,34 @@ function shellCommandReadsSensitivePath(command) {
30114
30292
  }
30115
30293
 
30116
30294
  // src/security/permission-policy.ts
30295
+ function fsWriteTargetPaths(input) {
30296
+ const out = [];
30297
+ if (!input || typeof input !== "object") return out;
30298
+ const obj = input;
30299
+ for (const key of [
30300
+ "path",
30301
+ "file_path",
30302
+ "file",
30303
+ "filePath",
30304
+ "files",
30305
+ "target",
30306
+ "targetPath",
30307
+ "out",
30308
+ "directory",
30309
+ "cwd",
30310
+ "template"
30311
+ ]) {
30312
+ const value = obj[key];
30313
+ if (typeof value === "string") {
30314
+ if (value.length > 0) out.push(value);
30315
+ } else if (Array.isArray(value)) {
30316
+ for (const item of value) {
30317
+ if (typeof item === "string" && item.length > 0) out.push(item);
30318
+ }
30319
+ }
30320
+ }
30321
+ return out;
30322
+ }
30117
30323
  var DefaultPermissionPolicy = class {
30118
30324
  policy = {};
30119
30325
  loaded = false;
@@ -30199,6 +30405,30 @@ var DefaultPermissionPolicy = class {
30199
30405
  getYoloDestructive() {
30200
30406
  return this.yoloDestructive;
30201
30407
  }
30408
+ /**
30409
+ * True when this call is an FS_WRITE whose target lands inside WrongStack's
30410
+ * own state root.
30411
+ *
30412
+ * FS_WRITE tools disagree on where the target path lives: write/edit use
30413
+ * `path`, replace/format/git `files` (string OR array), patch `directory`,
30414
+ * design `out`, scaffold writes under `cwd`/`template`. Checking only
30415
+ * `path`/`file_path` let a state-root write merely switch tools, so every
30416
+ * path-bearing key is inspected.
30417
+ *
30418
+ * Shared by the YOLO carve-out and the eval-cache guard so the two cannot
30419
+ * drift: the carve-out scans all path keys while the cache key fingerprints
30420
+ * only the subject key, so the cache must consult the same predicate or a
30421
+ * secondary state-root key can be replayed a cached `auto`.
30422
+ */
30423
+ hasAgentStateWriteTarget(tool, input, ctx) {
30424
+ if (!hasCapability(tool, ToolCapabilities.FS_WRITE)) return false;
30425
+ for (const targetPath of fsWriteTargetPaths(input)) {
30426
+ const base = ctx.workingDir ?? ctx.cwd;
30427
+ const resolved = base ? path36.resolve(base, targetPath) : path36.resolve(targetPath);
30428
+ if (isInsideAgentStateRoot(resolved)) return true;
30429
+ }
30430
+ return false;
30431
+ }
30202
30432
  /**
30203
30433
  * True when YOLO is on but this specific call is a destructive shell command
30204
30434
  * the user has not opted to auto-approve. Shared by `evaluate()` and the
@@ -30206,6 +30436,7 @@ var DefaultPermissionPolicy = class {
30206
30436
  */
30207
30437
  yoloBlockedAsDestructive(tool, input, ctx) {
30208
30438
  if (!this.yolo || this.yoloDestructive) return false;
30439
+ if (this.hasAgentStateWriteTarget(tool, input, ctx)) return true;
30209
30440
  const isShellSurface = tool.name === "bash" || tool.name === "exec" || (tool.capabilities ?? []).includes("shell.arbitrary");
30210
30441
  if (!isShellSurface) return false;
30211
30442
  const command = getInputString(input, "command") ?? shellCommandLineFromInput(input);
@@ -30312,7 +30543,7 @@ var DefaultPermissionPolicy = class {
30312
30543
  const subject = subjectForToolInput(tool.name, input, tool.subjectKey);
30313
30544
  const cacheKey = `${tool.name}::${subject ?? tool.name}`;
30314
30545
  const evalKey = `${cacheKey}::${permissionFingerprint(tool)}`;
30315
- if (tool.name !== "write") {
30546
+ if (tool.name !== "write" && !this.hasAgentStateWriteTarget(tool, input, ctx)) {
30316
30547
  const cached = this._evalCache.get(evalKey);
30317
30548
  if (cached !== void 0) return cached;
30318
30549
  }
@@ -30420,7 +30651,7 @@ var DefaultPermissionPolicy = class {
30420
30651
  return decision;
30421
30652
  }
30422
30653
  if (tool.name === "write" && subject) {
30423
- if (ctx.hasRead(subject)) {
30654
+ if (ctx.hasRead(subject) && !isInsideAgentStateRoot(subject)) {
30424
30655
  return {
30425
30656
  permission: "auto",
30426
30657
  source: "context",
@@ -30770,13 +31001,14 @@ var DefaultPermissionPolicy = class {
30770
31001
  }
30771
31002
  add("yolo", false, "auto", "yolo", "YOLO mode is not active");
30772
31003
  if (tool.name === "write" && subject) {
30773
- const hasRead = ctx.hasRead(subject);
31004
+ const isAgentState = isInsideAgentStateRoot(subject);
31005
+ const hasRead = ctx.hasRead(subject) && !isAgentState;
30774
31006
  add(
30775
31007
  "write smart bypass",
30776
31008
  hasRead,
30777
31009
  "auto",
30778
31010
  "context",
30779
- 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`
31011
+ isAgentState ? `file "${subject}" is WrongStack's own state \u2014 bypass never applies, write always confirms` : 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`
30780
31012
  );
30781
31013
  if (hasRead) {
30782
31014
  winnerIndex = steps.length - 1;
@@ -30861,7 +31093,7 @@ var DefaultPermissionPolicy = class {
30861
31093
  import { createCipheriv, createDecipheriv, randomBytes as randomBytes3, scryptSync } from "node:crypto";
30862
31094
  import * as fs13 from "node:fs";
30863
31095
  import * as fsp22 from "node:fs/promises";
30864
- import * as path35 from "node:path";
31096
+ import * as path37 from "node:path";
30865
31097
 
30866
31098
  // src/types/secret-vault.ts
30867
31099
  var ENCRYPTED_PREFIX_PATTERN = /^enc:v(\d+):/;
@@ -31154,7 +31386,7 @@ var DefaultSecretVault = class {
31154
31386
  const oldVersion = this._keyVersion;
31155
31387
  const newKey = randomBytes3(KEY_BYTES);
31156
31388
  const newVersion = oldVersion + 1;
31157
- fs13.mkdirSync(path35.dirname(this.keyFile), { recursive: true });
31389
+ fs13.mkdirSync(path37.dirname(this.keyFile), { recursive: true });
31158
31390
  const passphrase = getVaultPassphrase();
31159
31391
  if (passphrase) {
31160
31392
  writeKeyFileAtomicSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase));
@@ -31241,7 +31473,7 @@ var DefaultSecretVault = class {
31241
31473
  } catch (err) {
31242
31474
  if (err.code !== "ENOENT") throw err;
31243
31475
  }
31244
- fs13.mkdirSync(path35.dirname(this.keyFile), { recursive: true });
31476
+ fs13.mkdirSync(path37.dirname(this.keyFile), { recursive: true });
31245
31477
  const key = randomBytes3(KEY_BYTES);
31246
31478
  const passphrase = getVaultPassphrase();
31247
31479
  const initialBytes = passphrase ? wrapDataKey(key, 1, passphrase) : key;
@@ -31301,7 +31533,7 @@ async function rewriteConfigEncrypted(configPath, vault, patch) {
31301
31533
  }
31302
31534
  const merged = deepMerge(current, patch ?? {});
31303
31535
  const encrypted = encryptConfigSecrets(merged, vault);
31304
- await fsp22.mkdir(path35.dirname(configPath), { recursive: true });
31536
+ await fsp22.mkdir(path37.dirname(configPath), { recursive: true });
31305
31537
  await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
31306
31538
  await restrictFilePermissions2(configPath);
31307
31539
  await vault.flushHardening?.();
@@ -31356,7 +31588,7 @@ function walkCount(node, vault, counter) {
31356
31588
  // src/storage/attachment-store.ts
31357
31589
  import { randomBytes as randomBytes4 } from "node:crypto";
31358
31590
  import * as fsp23 from "node:fs/promises";
31359
- import * as path36 from "node:path";
31591
+ import * as path38 from "node:path";
31360
31592
  var DEFAULT_SPOOL_THRESHOLD = 256 * 1024;
31361
31593
  var PLACEHOLDER_RE = /\[(pasted|image|file) #(\d+)[^\]]*\]|\[file:([^\]]+)\]/g;
31362
31594
  var DefaultAttachmentStore = class {
@@ -31377,7 +31609,7 @@ var DefaultAttachmentStore = class {
31377
31609
  let data = input.data;
31378
31610
  if (this.spoolDir && bytes >= this.spoolThreshold) {
31379
31611
  await fsp23.mkdir(this.spoolDir, { recursive: true });
31380
- spooledPath = path36.join(this.spoolDir, `${id}.bin`);
31612
+ spooledPath = path38.join(this.spoolDir, `${id}.bin`);
31381
31613
  await atomicWrite(spooledPath, input.data, {
31382
31614
  encoding: input.kind === "image" ? "base64" : "utf8"
31383
31615
  });
@@ -31866,6 +32098,15 @@ var IN_PROJECT_DENIED_PATHS = [
31866
32098
  reason: "Extends the exec allow-list; a repo could authorise its own binaries."
31867
32099
  },
31868
32100
  { path: "tools.exec.danger", reason: "Weakens the destructive-command banner." },
32101
+ {
32102
+ // The whole subtree, not just the dangerous leaves: a persona's
32103
+ // `instruction` is rendered into the voter SYSTEM prompt, a profile seat
32104
+ // may pin providerId/model, and `defaultProfile` selects which of those
32105
+ // runs when the agent names no profile. Denying the parent leaves no leaf
32106
+ // to reclassify wrongly later.
32107
+ path: "tools.council",
32108
+ reason: "Council tool panel definitions: persona instructions are injected into the voter SYSTEM prompt and profile seats can pin an attacker-chosen providerId/model."
32109
+ },
31869
32110
  { path: "skills.extraDirs", reason: "Loads skill definitions from repo-chosen directories." },
31870
32111
  { path: "skills.registryUrl", reason: "Redirects skill installs to a repo-chosen host." },
31871
32112
  // Deliberately NOT denied: skills.mode and skills.eagerMaxChars. The audit
@@ -31901,8 +32142,8 @@ var IN_PROJECT_DENIED_PATHS = [
31901
32142
  reason: "The other half of the filesystem confinement switch."
31902
32143
  }
31903
32144
  ];
31904
- function deleteNestedPath(target, path40) {
31905
- const segments = path40.split(".");
32145
+ function deleteNestedPath(target, path42) {
32146
+ const segments = path42.split(".");
31906
32147
  const last = segments[segments.length - 1];
31907
32148
  if (last === void 0) return false;
31908
32149
  let cursor = target;
@@ -31959,16 +32200,16 @@ function assertInProjectAllowListComplete() {
31959
32200
  );
31960
32201
  }
31961
32202
  const orphanedPaths = IN_PROJECT_DENIED_PATHS.filter(
31962
- ({ path: path40 }) => !IN_PROJECT_ALLOWED_KEYS.has(path40.split(".")[0] ?? "")
31963
- ).map(({ path: path40 }) => path40);
32203
+ ({ path: path42 }) => !IN_PROJECT_ALLOWED_KEYS.has(path42.split(".")[0] ?? "")
32204
+ ).map(({ path: path42 }) => path42);
31964
32205
  if (orphanedPaths.length > 0) {
31965
32206
  problems.push(
31966
32207
  `IN_PROJECT_DENIED_PATHS entr(ies) whose top-level parent is not allowed: ` + orphanedPaths.join(", ") + ". The parent is already stripped wholesale, so the nested denial is dead \u2014 remove it."
31967
32208
  );
31968
32209
  }
31969
32210
  const malformedPaths = IN_PROJECT_DENIED_PATHS.filter(
31970
- ({ path: path40 }) => path40.split(".").length < 2 || path40.split(".").some((s) => s.length === 0)
31971
- ).map(({ path: path40 }) => path40);
32211
+ ({ path: path42 }) => path42.split(".").length < 2 || path42.split(".").some((s) => s.length === 0)
32212
+ ).map(({ path: path42 }) => path42);
31972
32213
  if (malformedPaths.length > 0) {
31973
32214
  problems.push(
31974
32215
  `IN_PROJECT_DENIED_PATHS entr(ies) are not dotted nested paths: ` + malformedPaths.join(", ") + ". Top-level keys belong in KNOWN_DENIED_IN_PROJECT instead."
@@ -31996,8 +32237,8 @@ function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => conso
31996
32237
  }
31997
32238
  stripped.push(k);
31998
32239
  }
31999
- for (const { path: path40 } of IN_PROJECT_DENIED_PATHS) {
32000
- if (deleteNestedPath(out, path40)) stripped.push(path40);
32240
+ for (const { path: path42 } of IN_PROJECT_DENIED_PATHS) {
32241
+ if (deleteNestedPath(out, path42)) stripped.push(path42);
32001
32242
  }
32002
32243
  if (stripped.length > 0) {
32003
32244
  warn(
@@ -32186,10 +32427,10 @@ function removeLegacySageEngine(config) {
32186
32427
  }
32187
32428
 
32188
32429
  // src/storage/config-loader/path-identity.ts
32189
- import * as path37 from "node:path";
32430
+ import * as path39 from "node:path";
32190
32431
  function samePath(a, b) {
32191
- let ra = path37.resolve(a);
32192
- let rb = path37.resolve(b);
32432
+ let ra = path39.resolve(a);
32433
+ let rb = path39.resolve(b);
32193
32434
  if (process.platform === "win32" || process.platform === "darwin") {
32194
32435
  ra = ra.toLowerCase();
32195
32436
  rb = rb.toLowerCase();
@@ -33170,7 +33411,7 @@ ${cat}:`);
33170
33411
 
33171
33412
  // src/storage/queue-store.ts
33172
33413
  import * as fsp25 from "node:fs/promises";
33173
- import * as path38 from "node:path";
33414
+ import * as path40 from "node:path";
33174
33415
  var QUEUE_MAX_ITEMS = 100;
33175
33416
  var QUEUE_MAX_BYTES = 16 * 1024 * 1024;
33176
33417
  var QUEUE_MAX_ITEM_BYTES = 8 * 1024 * 1024;
@@ -33203,7 +33444,7 @@ var QueueStore = class {
33203
33444
  traceId;
33204
33445
  logger;
33205
33446
  constructor(opts) {
33206
- this.file = path38.join(opts.dir, "queue.json");
33447
+ this.file = path40.join(opts.dir, "queue.json");
33207
33448
  this.events = opts.events;
33208
33449
  this.traceId = opts.traceId;
33209
33450
  this.logger = opts.logger;
@@ -33419,7 +33660,7 @@ function isPersistedQueueItem(v) {
33419
33660
  // src/storage/recovery-lock.ts
33420
33661
  import * as fsp26 from "node:fs/promises";
33421
33662
  import * as os2 from "node:os";
33422
- import * as path39 from "node:path";
33663
+ import * as path41 from "node:path";
33423
33664
  var LOCK_FILE = "active.json";
33424
33665
  var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
33425
33666
  var RecoveryLock = class {
@@ -33430,7 +33671,7 @@ var RecoveryLock = class {
33430
33671
  sessionStore;
33431
33672
  probe;
33432
33673
  constructor(opts) {
33433
- this.file = path39.join(opts.dir, LOCK_FILE);
33674
+ this.file = path41.join(opts.dir, LOCK_FILE);
33434
33675
  this.pid = opts.pid ?? process.pid;
33435
33676
  this.hostname = opts.hostname ?? os2.hostname();
33436
33677
  this.maxAgeMs = opts.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
@@ -33504,7 +33745,7 @@ var RecoveryLock = class {
33504
33745
  * null return before calling this.
33505
33746
  */
33506
33747
  async write(sessionId) {
33507
- await ensureDir(path39.dirname(this.file));
33748
+ await ensureDir(path41.dirname(this.file));
33508
33749
  const lock = {
33509
33750
  v: 1,
33510
33751
  sessionId,