@wrongstack/core 0.298.3 → 0.299.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 (42) hide show
  1. package/dist/chronicle/index.js +4 -1
  2. package/dist/coordination/agents/index.js +4 -1
  3. package/dist/coordination/index.js +7 -4
  4. package/dist/core/index.js +62 -8
  5. package/dist/defaults/index.js +120 -18
  6. package/dist/design/index.js +4 -1
  7. package/dist/execution/council-brain.d.ts +6 -2
  8. package/dist/execution/council-personas.d.ts +10 -0
  9. package/dist/execution/index.d.ts +1 -1
  10. package/dist/execution/index.js +57 -7
  11. package/dist/goal/index.js +4 -1
  12. package/dist/hooks/index.js +132 -6
  13. package/dist/hq/exposure.d.ts +0 -11
  14. package/dist/hq/index.js +16 -4
  15. package/dist/hq/protocol/client.d.ts +14 -1
  16. package/dist/hq/protocol/fleet.d.ts +2 -0
  17. package/dist/hq/protocol.js +2 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.js +207 -47
  20. package/dist/infrastructure/index.js +50 -2
  21. package/dist/infrastructure/mcp-servers.d.ts +35 -0
  22. package/dist/kernel/events/provider-events.d.ts +7 -1
  23. package/dist/kernel/events/sdd-events.d.ts +2 -0
  24. package/dist/plugin/index.js +4 -1
  25. package/dist/storage/index.js +13 -1
  26. package/dist/tools/council-tool.d.ts +1 -1
  27. package/dist/tools/index.js +61 -10
  28. package/dist/types/config/skills-fleet-brain.d.ts +4 -2
  29. package/dist/types/config/tools.d.ts +99 -0
  30. package/dist/types/index.d.ts +2 -1
  31. package/dist/types/index.js +3 -3
  32. package/dist/types/one-shot-llm.d.ts +22 -3
  33. package/dist/types/session.d.ts +5 -1
  34. package/dist/utils/index.js +4 -1
  35. package/dist/utils/wstack-paths.d.ts +2 -0
  36. package/dist/worktree/index.js +43 -21
  37. package/dist/worktree/worktree-manager.d.ts +16 -10
  38. package/instructions/coordination/subagent-baseline.md +8 -0
  39. package/instructions/system-lite.md +2 -0
  40. package/instructions/system-pro.md +14 -10
  41. package/instructions/system.md +11 -7
  42. package/package.json +3 -3
@@ -582,7 +582,9 @@ function safeProfileName(name) {
582
582
  function activeProfileName(globalRoot) {
583
583
  try {
584
584
  const parsed = JSON.parse(fs2.readFileSync(path4.join(globalRoot, "config.json"), "utf8"));
585
- return safeProfileName(typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0);
585
+ return safeProfileName(
586
+ typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0
587
+ );
586
588
  } catch {
587
589
  return "default";
588
590
  }
@@ -662,6 +664,7 @@ function resolveWstackPaths(opts) {
662
664
  projectPlan: path4.join(projectDir, "plan.json"),
663
665
  projectAutophase: path4.join(projectDir, "autophase"),
664
666
  projectSddBoards: path4.join(projectDir, "sdd-boards"),
667
+ projectRequirementIntakes: path4.join(projectDir, "requirement-intakes"),
665
668
  syncConfig: path4.join(profileDir, "sync.json"),
666
669
  configHistoryDir: path4.join(globalRoot, "config-history"),
667
670
  projectStatus: (projectHash2) => path4.join(globalRoot, "projects", projectHash2, "status.json")
@@ -98,7 +98,9 @@ function safeProfileName(name) {
98
98
  function activeProfileName(globalRoot) {
99
99
  try {
100
100
  const parsed = JSON.parse(fs.readFileSync(path.join(globalRoot, "config.json"), "utf8"));
101
- return safeProfileName(typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0);
101
+ return safeProfileName(
102
+ typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0
103
+ );
102
104
  } catch {
103
105
  return "default";
104
106
  }
@@ -178,6 +180,7 @@ function resolveWstackPaths(opts) {
178
180
  projectPlan: path.join(projectDir, "plan.json"),
179
181
  projectAutophase: path.join(projectDir, "autophase"),
180
182
  projectSddBoards: path.join(projectDir, "sdd-boards"),
183
+ projectRequirementIntakes: path.join(projectDir, "requirement-intakes"),
181
184
  syncConfig: path.join(profileDir, "sync.json"),
182
185
  configHistoryDir: path.join(globalRoot, "config-history"),
183
186
  projectStatus: (projectHash2) => path.join(globalRoot, "projects", projectHash2, "status.json")
@@ -1361,7 +1361,9 @@ function safeProfileName(name) {
1361
1361
  function activeProfileName(globalRoot) {
1362
1362
  try {
1363
1363
  const parsed = JSON.parse(fs.readFileSync(path.join(globalRoot, "config.json"), "utf8"));
1364
- return safeProfileName(typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0);
1364
+ return safeProfileName(
1365
+ typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0
1366
+ );
1365
1367
  } catch {
1366
1368
  return "default";
1367
1369
  }
@@ -1441,6 +1443,7 @@ function resolveWstackPaths(opts) {
1441
1443
  projectPlan: path.join(projectDir, "plan.json"),
1442
1444
  projectAutophase: path.join(projectDir, "autophase"),
1443
1445
  projectSddBoards: path.join(projectDir, "sdd-boards"),
1446
+ projectRequirementIntakes: path.join(projectDir, "requirement-intakes"),
1444
1447
  syncConfig: path.join(profileDir, "sync.json"),
1445
1448
  configHistoryDir: path.join(globalRoot, "config-history"),
1446
1449
  projectStatus: (projectHash2) => path.join(globalRoot, "projects", projectHash2, "status.json")
@@ -12609,7 +12612,7 @@ var QUOTA_EXHAUSTED_RE = /(?:insufficient|exhausted|depleted|exceeded|no|not eno
12609
12612
  var ROUTE_SCOPED_QUOTA_RE = /\b(?:for|on)\s+(?:(?:this|the)\s+)?(?:route|model)\b|\b(?:route|model)(?:[-_\s]+[\w.-]+)?[-_\s]*(?:quota|limit)\b|\b(?:quota|limit).{0,24}\b(?:for|on)\s+(?:(?:this|the)\s+)?(?:route|model)\b/i;
12610
12613
 
12611
12614
  // src/types/provider.ts
12612
- 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;
12615
+ 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;
12613
12616
  var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
12614
12617
  var RATE_LIMIT_EXCEEDED_RE = /rate[-_\s]*limit[-_\s]*exceeded/i;
12615
12618
  function classifyProviderError(status, body, message) {
@@ -12619,7 +12622,7 @@ function classifyProviderError(status, body, message) {
12619
12622
  if (status === 408) return "timeout";
12620
12623
  if (status === 599) return "stream_hang";
12621
12624
  if (status === 402 || QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
12622
- if (status === 429 && body?.message && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
12625
+ if (status === 429 && body?.message && body.type !== "rate_limit_exceeded" && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
12623
12626
  return "quota_exhausted";
12624
12627
  }
12625
12628
  if (type === "rate_limit_error" || status === 429) return "rate_limit";
@@ -12721,7 +12724,7 @@ var ProviderError = class extends WrongStackError {
12721
12724
  const e = err;
12722
12725
  const name = e.name;
12723
12726
  if (typeof name !== "string" || !name.endsWith("Error")) return false;
12724
- return typeof e.status === "number" && typeof e.retryable === "boolean" && typeof e.kind === "string";
12727
+ return typeof e.status === "number" && typeof e.retryable === "boolean" && typeof e.kind === "string" && typeof e.describe === "function";
12725
12728
  }
12726
12729
  constructor(message, status, retryable, providerId, opts = {}) {
12727
12730
  const kind = opts.kind ?? classifyProviderError(status, opts.body, message);
@@ -3804,7 +3804,7 @@ var QUOTA_EXHAUSTED_RE = /(?:insufficient|exhausted|depleted|exceeded|no|not eno
3804
3804
  function effectiveInputTokens(usage) {
3805
3805
  return usage.input + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
3806
3806
  }
3807
- 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;
3807
+ 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;
3808
3808
  var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
3809
3809
  var RATE_LIMIT_EXCEEDED_RE = /rate[-_\s]*limit[-_\s]*exceeded/i;
3810
3810
  function classifyProviderError(status, body, message) {
@@ -3814,7 +3814,7 @@ function classifyProviderError(status, body, message) {
3814
3814
  if (status === 408) return "timeout";
3815
3815
  if (status === 599) return "stream_hang";
3816
3816
  if (status === 402 || QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
3817
- if (status === 429 && body?.message && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
3817
+ if (status === 429 && body?.message && body.type !== "rate_limit_exceeded" && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
3818
3818
  return "quota_exhausted";
3819
3819
  }
3820
3820
  if (type === "rate_limit_error" || status === 429) return "rate_limit";
@@ -3874,7 +3874,7 @@ var ProviderError = class extends WrongStackError {
3874
3874
  const e = err;
3875
3875
  const name = e.name;
3876
3876
  if (typeof name !== "string" || !name.endsWith("Error")) return false;
3877
- return typeof e.status === "number" && typeof e.retryable === "boolean" && typeof e.kind === "string";
3877
+ return typeof e.status === "number" && typeof e.retryable === "boolean" && typeof e.kind === "string" && typeof e.describe === "function";
3878
3878
  }
3879
3879
  constructor(message, status, retryable, providerId, opts = {}) {
3880
3880
  const kind = opts.kind ?? classifyProviderError(status, opts.body, message);
@@ -5971,8 +5971,54 @@ function runWithNetworkTelemetry(context, run) {
5971
5971
  return storage.run(context, run);
5972
5972
  }
5973
5973
 
5974
+ // src/security/error-sanitize.ts
5975
+ import { homedir as homedir2 } from "node:os";
5976
+ var scrubber = new DefaultSecretScrubber();
5977
+ function scrubErrorText(text) {
5978
+ if (!text) return text;
5979
+ let out = scrubber.scrub(text);
5980
+ const home = safeHomedir();
5981
+ if (home && home.length > 2) {
5982
+ out = replaceAllCaseInsensitive(out, home, "~");
5983
+ const alt = home.includes("\\") ? home.replace(/\\/g, "/") : home.replace(/\//g, "\\");
5984
+ if (alt !== home) out = replaceAllCaseInsensitive(out, alt, "~");
5985
+ }
5986
+ return out;
5987
+ }
5988
+ function safeHomedir() {
5989
+ try {
5990
+ return homedir2();
5991
+ } catch {
5992
+ return "";
5993
+ }
5994
+ }
5995
+ function replaceAllCaseInsensitive(haystack, needle, replacement) {
5996
+ const lowerHay = haystack.toLowerCase();
5997
+ const lowerNeedle = needle.toLowerCase();
5998
+ let idx = lowerHay.indexOf(lowerNeedle);
5999
+ if (idx === -1) return haystack;
6000
+ let out = "";
6001
+ let from = 0;
6002
+ while (idx !== -1) {
6003
+ out += haystack.slice(from, idx) + replacement;
6004
+ from = idx + needle.length;
6005
+ idx = lowerHay.indexOf(lowerNeedle, from);
6006
+ }
6007
+ return out + haystack.slice(from);
6008
+ }
6009
+
5974
6010
  // src/core/provider-runner.ts
5975
6011
  import { randomUUID as randomUUID5 } from "node:crypto";
6012
+ function scrubProviderBody(body) {
6013
+ if (!body) return void 0;
6014
+ return {
6015
+ ...body,
6016
+ ...body.type !== void 0 ? { type: scrubErrorText(body.type) } : {},
6017
+ ...body.message !== void 0 ? { message: scrubErrorText(body.message) } : {},
6018
+ ...body.raw !== void 0 ? { raw: scrubErrorText(body.raw) } : {},
6019
+ ...body.requestId !== void 0 ? { requestId: scrubErrorText(body.requestId) } : {}
6020
+ };
6021
+ }
5976
6022
  function providerLogCtx(p, r) {
5977
6023
  return {
5978
6024
  providerId: p.id,
@@ -6057,7 +6103,10 @@ async function runProviderWithRetry(opts) {
6057
6103
  const isProviderErr = err instanceof ProviderError || ProviderError.isProviderError(err);
6058
6104
  const errAsErr = err instanceof Error ? err : new Error(String(err));
6059
6105
  const canRetry = retry.shouldRetry(isProviderErr ? err : errAsErr, attempt);
6060
- const description = isProviderErr ? err.describe() : errAsErr.message;
6106
+ const providerErrorBody = isProviderErr ? scrubProviderBody(err.body) : void 0;
6107
+ const description = scrubErrorText(
6108
+ isProviderErr ? err.describe() : errAsErr.message
6109
+ );
6061
6110
  const delay2 = canRetry ? Math.round(retry.delayMs(attempt, isProviderErr ? err : errAsErr)) : void 0;
6062
6111
  events.emit("provider.attempt.failed", {
6063
6112
  ...correlation,
@@ -6070,7 +6119,8 @@ async function runProviderWithRetry(opts) {
6070
6119
  retryable: canRetry,
6071
6120
  retryScheduled: canRetry,
6072
6121
  ...delay2 !== void 0 ? { retryDelayMs: delay2 } : {},
6073
- ...isProviderErr && err.body?.requestId ? { providerRequestId: err.body.requestId } : {}
6122
+ ...providerErrorBody?.requestId ? { providerRequestId: providerErrorBody.requestId } : {},
6123
+ ...providerErrorBody ? { errorBody: providerErrorBody } : {}
6074
6124
  });
6075
6125
  if (!canRetry) {
6076
6126
  events.emit("provider.error", {
@@ -6078,13 +6128,15 @@ async function runProviderWithRetry(opts) {
6078
6128
  providerId: isProviderErr ? err.providerId : provider.id,
6079
6129
  status: isProviderErr ? err.status : 0,
6080
6130
  description,
6081
- retryable: false
6131
+ retryable: false,
6132
+ ...providerErrorBody ? { errorBody: providerErrorBody } : {}
6082
6133
  });
6083
6134
  logger.error(`Provider call failed after ${attempt + 1} attempt(s) \u2014 ${description}`, {
6084
6135
  ...providerLogCtx(provider, request),
6085
6136
  attempts: attempt + 1,
6086
6137
  errorDescription: description,
6087
6138
  status: isProviderErr ? err.status : void 0,
6139
+ errorBody: providerErrorBody,
6088
6140
  errorName: err instanceof Error ? err.name : void 0,
6089
6141
  errorStack: err instanceof Error ? err.stack?.split("\n").slice(0, 3).join("\n") : void 0
6090
6142
  });
@@ -6098,7 +6150,8 @@ async function runProviderWithRetry(opts) {
6098
6150
  maxAttempts,
6099
6151
  delayMs: delay2,
6100
6152
  errorDescription: description,
6101
- status: isProviderErr ? err.status : void 0
6153
+ status: isProviderErr ? err.status : void 0,
6154
+ errorBody: providerErrorBody
6102
6155
  });
6103
6156
  events.emit("provider.retry", {
6104
6157
  sessionId: resolveEventSessionId(ctx),
@@ -6106,7 +6159,8 @@ async function runProviderWithRetry(opts) {
6106
6159
  attempt: attemptNum,
6107
6160
  delayMs: delay2,
6108
6161
  status: isProviderErr ? err.status : 0,
6109
- description
6162
+ description,
6163
+ ...providerErrorBody ? { errorBody: providerErrorBody } : {}
6110
6164
  });
6111
6165
  await new Promise((resolve10, reject) => {
6112
6166
  let settled = false;
@@ -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")
@@ -9397,7 +9400,7 @@ function truncate(s, max) {
9397
9400
  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
9401
 
9399
9402
  // 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;
9403
+ 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
9404
  var CONTENT_FILTER_RE = /content.(filter|policy|moderation)|safety (system|filter)/i;
9402
9405
  var RATE_LIMIT_EXCEEDED_RE = /rate[-_\s]*limit[-_\s]*exceeded/i;
9403
9406
  function classifyProviderError(status, body, message) {
@@ -9407,7 +9410,7 @@ function classifyProviderError(status, body, message) {
9407
9410
  if (status === 408) return "timeout";
9408
9411
  if (status === 599) return "stream_hang";
9409
9412
  if (status === 402 || QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
9410
- if (status === 429 && body?.message && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
9413
+ if (status === 429 && body?.message && body.type !== "rate_limit_exceeded" && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
9411
9414
  return "quota_exhausted";
9412
9415
  }
9413
9416
  if (type === "rate_limit_error" || status === 429) return "rate_limit";
@@ -9467,7 +9470,7 @@ var ProviderError = class extends WrongStackError {
9467
9470
  const e = err;
9468
9471
  const name = e.name;
9469
9472
  if (typeof name !== "string" || !name.endsWith("Error")) return false;
9470
- return typeof e.status === "number" && typeof e.retryable === "boolean" && typeof e.kind === "string";
9473
+ return typeof e.status === "number" && typeof e.retryable === "boolean" && typeof e.kind === "string" && typeof e.describe === "function";
9471
9474
  }
9472
9475
  constructor(message, status, retryable, providerId, opts = {}) {
9473
9476
  const kind = opts.kind ?? classifyProviderError(status, opts.body, message);
@@ -16311,14 +16314,14 @@ function sessionIdResolutionError(resolution) {
16311
16314
  }
16312
16315
 
16313
16316
  // src/storage/session-read-scrubber.ts
16314
- function scrubPersistedSessionEvent(event, scrubber) {
16315
- return scrubber.scrubObject(event);
16317
+ function scrubPersistedSessionEvent(event, scrubber2) {
16318
+ return scrubber2.scrubObject(event);
16316
16319
  }
16317
- function scrubPersistedSessionData(data, scrubber) {
16318
- return scrubber.scrubObject(data);
16320
+ function scrubPersistedSessionData(data, scrubber2) {
16321
+ return scrubber2.scrubObject(data);
16319
16322
  }
16320
- function scrubPersistedSessionSummary(summary, scrubber) {
16321
- const scrubbed = scrubber.scrubObject(summary);
16323
+ function scrubPersistedSessionSummary(summary, scrubber2) {
16324
+ const scrubbed = scrubber2.scrubObject(summary);
16322
16325
  const { name: _name, lastUserMessage: _lastUserMessage, ...rest } = scrubbed;
16323
16326
  const title = sessionContentText(scrubbed.title) || "(empty session)";
16324
16327
  const name = scrubbed.name === void 0 ? "" : sessionContentText(scrubbed.name);
@@ -24675,8 +24678,54 @@ function runWithNetworkTelemetry(context, run) {
24675
24678
  return storage.run(context, run);
24676
24679
  }
24677
24680
 
24681
+ // src/security/error-sanitize.ts
24682
+ import { homedir as homedir2 } from "node:os";
24683
+ var scrubber = new DefaultSecretScrubber();
24684
+ function scrubErrorText(text) {
24685
+ if (!text) return text;
24686
+ let out = scrubber.scrub(text);
24687
+ const home = safeHomedir();
24688
+ if (home && home.length > 2) {
24689
+ out = replaceAllCaseInsensitive(out, home, "~");
24690
+ const alt = home.includes("\\") ? home.replace(/\\/g, "/") : home.replace(/\//g, "\\");
24691
+ if (alt !== home) out = replaceAllCaseInsensitive(out, alt, "~");
24692
+ }
24693
+ return out;
24694
+ }
24695
+ function safeHomedir() {
24696
+ try {
24697
+ return homedir2();
24698
+ } catch {
24699
+ return "";
24700
+ }
24701
+ }
24702
+ function replaceAllCaseInsensitive(haystack, needle, replacement) {
24703
+ const lowerHay = haystack.toLowerCase();
24704
+ const lowerNeedle = needle.toLowerCase();
24705
+ let idx = lowerHay.indexOf(lowerNeedle);
24706
+ if (idx === -1) return haystack;
24707
+ let out = "";
24708
+ let from = 0;
24709
+ while (idx !== -1) {
24710
+ out += haystack.slice(from, idx) + replacement;
24711
+ from = idx + needle.length;
24712
+ idx = lowerHay.indexOf(lowerNeedle, from);
24713
+ }
24714
+ return out + haystack.slice(from);
24715
+ }
24716
+
24678
24717
  // src/core/provider-runner.ts
24679
24718
  import { randomUUID as randomUUID14 } from "node:crypto";
24719
+ function scrubProviderBody(body) {
24720
+ if (!body) return void 0;
24721
+ return {
24722
+ ...body,
24723
+ ...body.type !== void 0 ? { type: scrubErrorText(body.type) } : {},
24724
+ ...body.message !== void 0 ? { message: scrubErrorText(body.message) } : {},
24725
+ ...body.raw !== void 0 ? { raw: scrubErrorText(body.raw) } : {},
24726
+ ...body.requestId !== void 0 ? { requestId: scrubErrorText(body.requestId) } : {}
24727
+ };
24728
+ }
24680
24729
  function providerLogCtx(p, r) {
24681
24730
  return {
24682
24731
  providerId: p.id,
@@ -24761,7 +24810,10 @@ async function runProviderWithRetry(opts) {
24761
24810
  const isProviderErr = err instanceof ProviderError || ProviderError.isProviderError(err);
24762
24811
  const errAsErr = err instanceof Error ? err : new Error(String(err));
24763
24812
  const canRetry = retry.shouldRetry(isProviderErr ? err : errAsErr, attempt);
24764
- const description = isProviderErr ? err.describe() : errAsErr.message;
24813
+ const providerErrorBody = isProviderErr ? scrubProviderBody(err.body) : void 0;
24814
+ const description = scrubErrorText(
24815
+ isProviderErr ? err.describe() : errAsErr.message
24816
+ );
24765
24817
  const delay = canRetry ? Math.round(retry.delayMs(attempt, isProviderErr ? err : errAsErr)) : void 0;
24766
24818
  events.emit("provider.attempt.failed", {
24767
24819
  ...correlation,
@@ -24774,7 +24826,8 @@ async function runProviderWithRetry(opts) {
24774
24826
  retryable: canRetry,
24775
24827
  retryScheduled: canRetry,
24776
24828
  ...delay !== void 0 ? { retryDelayMs: delay } : {},
24777
- ...isProviderErr && err.body?.requestId ? { providerRequestId: err.body.requestId } : {}
24829
+ ...providerErrorBody?.requestId ? { providerRequestId: providerErrorBody.requestId } : {},
24830
+ ...providerErrorBody ? { errorBody: providerErrorBody } : {}
24778
24831
  });
24779
24832
  if (!canRetry) {
24780
24833
  events.emit("provider.error", {
@@ -24782,13 +24835,15 @@ async function runProviderWithRetry(opts) {
24782
24835
  providerId: isProviderErr ? err.providerId : provider.id,
24783
24836
  status: isProviderErr ? err.status : 0,
24784
24837
  description,
24785
- retryable: false
24838
+ retryable: false,
24839
+ ...providerErrorBody ? { errorBody: providerErrorBody } : {}
24786
24840
  });
24787
24841
  logger.error(`Provider call failed after ${attempt + 1} attempt(s) \u2014 ${description}`, {
24788
24842
  ...providerLogCtx(provider, request),
24789
24843
  attempts: attempt + 1,
24790
24844
  errorDescription: description,
24791
24845
  status: isProviderErr ? err.status : void 0,
24846
+ errorBody: providerErrorBody,
24792
24847
  errorName: err instanceof Error ? err.name : void 0,
24793
24848
  errorStack: err instanceof Error ? err.stack?.split("\n").slice(0, 3).join("\n") : void 0
24794
24849
  });
@@ -24802,7 +24857,8 @@ async function runProviderWithRetry(opts) {
24802
24857
  maxAttempts,
24803
24858
  delayMs: delay,
24804
24859
  errorDescription: description,
24805
- status: isProviderErr ? err.status : void 0
24860
+ status: isProviderErr ? err.status : void 0,
24861
+ errorBody: providerErrorBody
24806
24862
  });
24807
24863
  events.emit("provider.retry", {
24808
24864
  sessionId: resolveEventSessionId(ctx),
@@ -24810,7 +24866,8 @@ async function runProviderWithRetry(opts) {
24810
24866
  attempt: attemptNum,
24811
24867
  delayMs: delay,
24812
24868
  status: isProviderErr ? err.status : 0,
24813
- description
24869
+ description,
24870
+ ...providerErrorBody ? { errorBody: providerErrorBody } : {}
24814
24871
  });
24815
24872
  await new Promise((resolve17, reject) => {
24816
24873
  let settled = false;
@@ -26580,14 +26637,14 @@ ${head}${TOOL_OUTPUT_ARTIFACT_OMISSION}${tail}`;
26580
26637
  return content;
26581
26638
  }
26582
26639
  }
26583
- function hashPermissionInput(input, scrubber) {
26640
+ function hashPermissionInput(input, scrubber2) {
26584
26641
  let serialized;
26585
26642
  try {
26586
26643
  serialized = JSON.stringify(input) ?? "";
26587
26644
  } catch {
26588
26645
  serialized = String(input);
26589
26646
  }
26590
- return createHash7("sha256").update(scrubber.scrub(serialized), "utf8").digest("hex");
26647
+ return createHash7("sha256").update(scrubber2.scrub(serialized), "utf8").digest("hex");
26591
26648
  }
26592
26649
  function sliceUtf8Prefix(text, maxBytes) {
26593
26650
  if (maxBytes <= 0) return "";
@@ -27902,6 +27959,38 @@ var sshManagerServer = () => ({
27902
27959
  permission: "confirm",
27903
27960
  requestTimeoutMs: 18e4
27904
27961
  });
27962
+ var requirementIntakeServer = () => ({
27963
+ name: "requirement-intake",
27964
+ description: "WrongStack Requirements Intake \u2014 list intake records and file new ones (project-scoped, --writable)",
27965
+ transport: "stdio",
27966
+ command: "wstack-requirement-intake-mcp",
27967
+ args: ["--project-root", ".", "--writable"],
27968
+ permission: "auto"
27969
+ });
27970
+ var kanbanServer = () => ({
27971
+ name: "kanban",
27972
+ description: "WrongStack Kanban \u2014 inspect and manage project work boards (project-scoped, manage tier, no destructive ops)",
27973
+ transport: "stdio",
27974
+ command: "wstack-kanban-mcp",
27975
+ args: ["--project-root", ".", "--writable"],
27976
+ permission: "confirm"
27977
+ });
27978
+ var mailboxServer = () => ({
27979
+ name: "mailbox",
27980
+ description: "WrongStack Mailbox \u2014 read and send project agent mail (project-scoped, no admin/credentials)",
27981
+ transport: "stdio",
27982
+ command: "wstack-mailbox-mcp",
27983
+ args: ["--project-root", ".", "--actor", "external-agent", "--writable"],
27984
+ permission: "auto"
27985
+ });
27986
+ var codebaseIndexServer = () => ({
27987
+ name: "codebase-index",
27988
+ description: "WrongStack Codebase Index \u2014 symbol search and dependency graphs (project-scoped, --writable)",
27989
+ transport: "stdio",
27990
+ command: "wstack-codebase-index-mcp",
27991
+ args: ["--project-root", ".", "--writable"],
27992
+ permission: "auto"
27993
+ });
27905
27994
  var allServers = () => ({
27906
27995
  filesystem: { ...filesystemServer(), enabled: false },
27907
27996
  github: { ...githubServer(), enabled: false },
@@ -27916,7 +28005,11 @@ var allServers = () => ({
27916
28005
  "zai-vision": { ...zaiVisionServer(), enabled: false },
27917
28006
  "minimax-vision": { ...miniMaxVisionServer(), enabled: false },
27918
28007
  playwright: { ...playwrightServer(), enabled: false },
27919
- ssh: { ...sshManagerServer(), enabled: false }
28008
+ ssh: { ...sshManagerServer(), enabled: false },
28009
+ kanban: { ...kanbanServer(), enabled: false },
28010
+ mailbox: { ...mailboxServer(), enabled: false },
28011
+ "codebase-index": { ...codebaseIndexServer(), enabled: false },
28012
+ "requirement-intake": { ...requirementIntakeServer(), enabled: false }
27920
28013
  });
27921
28014
 
27922
28015
  // src/models/codex-catalog.ts
@@ -31866,6 +31959,15 @@ var IN_PROJECT_DENIED_PATHS = [
31866
31959
  reason: "Extends the exec allow-list; a repo could authorise its own binaries."
31867
31960
  },
31868
31961
  { path: "tools.exec.danger", reason: "Weakens the destructive-command banner." },
31962
+ {
31963
+ // The whole subtree, not just the dangerous leaves: a persona's
31964
+ // `instruction` is rendered into the voter SYSTEM prompt, a profile seat
31965
+ // may pin providerId/model, and `defaultProfile` selects which of those
31966
+ // runs when the agent names no profile. Denying the parent leaves no leaf
31967
+ // to reclassify wrongly later.
31968
+ path: "tools.council",
31969
+ reason: "Council tool panel definitions: persona instructions are injected into the voter SYSTEM prompt and profile seats can pin an attacker-chosen providerId/model."
31970
+ },
31869
31971
  { path: "skills.extraDirs", reason: "Loads skill definitions from repo-chosen directories." },
31870
31972
  { path: "skills.registryUrl", reason: "Redirects skill installs to a repo-chosen host." },
31871
31973
  // Deliberately NOT denied: skills.mode and skills.eagerMaxChars. The audit
@@ -91,7 +91,9 @@ function safeProfileName(name) {
91
91
  function activeProfileName(globalRoot) {
92
92
  try {
93
93
  const parsed = JSON.parse(fs.readFileSync(path.join(globalRoot, "config.json"), "utf8"));
94
- return safeProfileName(typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0);
94
+ return safeProfileName(
95
+ typeof parsed.activeProfile === "string" ? parsed.activeProfile : void 0
96
+ );
95
97
  } catch {
96
98
  return "default";
97
99
  }
@@ -171,6 +173,7 @@ function resolveWstackPaths(opts) {
171
173
  projectPlan: path.join(projectDir, "plan.json"),
172
174
  projectAutophase: path.join(projectDir, "autophase"),
173
175
  projectSddBoards: path.join(projectDir, "sdd-boards"),
176
+ projectRequirementIntakes: path.join(projectDir, "requirement-intakes"),
174
177
  syncConfig: path.join(profileDir, "sync.json"),
175
178
  configHistoryDir: path.join(globalRoot, "config-history"),
176
179
  projectStatus: (projectHash2) => path.join(globalRoot, "projects", projectHash2, "status.json")
@@ -13,6 +13,9 @@
13
13
  * - maintainer — evaluates complexity, compatibility, long-term ownership.
14
14
  * - user-advocate — evaluates from the affected user's perspective.
15
15
  *
16
+ * Any other persona string is registered as an ad-hoc lens instead of being
17
+ * rejected — see `resolvePersonaRegistry`.
18
+ *
16
19
  * @module council-brain
17
20
  */
18
21
  import type { BrainArbiter, BrainDecisionRequest } from '../coordination/brain.js';
@@ -30,8 +33,9 @@ export interface CouncilVoter {
30
33
  /** Model id this voter uses. */
31
34
  model: string;
32
35
  /**
33
- * Decision lens. Built-ins: 'executor', 'skeptic', 'auditor'. Any other
34
- * string is injected verbatim as the persona description.
36
+ * Decision lens. Built-ins: 'executor', 'skeptic', 'auditor', 'security',
37
+ * 'maintainer', 'user-advocate'. Any other string is registered as an ad-hoc
38
+ * lens whose instruction is the string itself (see `resolvePersonaRegistry`).
35
39
  */
36
40
  persona?: string | undefined;
37
41
  /** Vote weight. Default 1. */
@@ -1,6 +1,16 @@
1
1
  import type { CouncilPersona } from '../types/council.js';
2
2
  /** Provider-neutral personas shipped with every Council installation. */
3
3
  export declare const BUILTIN_COUNCIL_PERSONAS: readonly CouncilPersona[];
4
+ /**
5
+ * Built-in persona ids in display order.
6
+ *
7
+ * The single source every surface cycles through. The TUI panel, the WebUI
8
+ * settings section and `/brain council` each used to hard-code
9
+ * `['executor','skeptic','auditor']`, which is why `security`, `maintainer`
10
+ * and `user-advocate` shipped in the registry but were unreachable from any
11
+ * UI — exactly the lenses `risk-review` is built around.
12
+ */
13
+ export declare const BUILTIN_COUNCIL_PERSONA_IDS: readonly string[];
4
14
  /** Immutable registry of trusted Council persona definitions. */
5
15
  export declare class CouncilPersonaRegistry {
6
16
  private readonly byId;
@@ -7,7 +7,7 @@ export { BRAIN_EVALUATION_CASE_VERSION, type BrainEvaluationCaseResult, type Bra
7
7
  export { type BrainApplyResult, type BrainConfigPatch, type BrainConfigSnapshot, type BrainCouncilMinRisk, type BrainCouncilPatch, type BrainDefaultsContext, type BrainPoolStrategy, type BrainRuntime, type BrainRuntimeLedgerHost, type BrainRuntimeOptions, createBrainRuntime, resolveBrainConfigDefaults, } from './brain-runtime.js';
8
8
  export { COUNCIL_REFUSE_OPTION_ID, type CouncilBrainOptions, type CouncilVoter, createCouncilBrainArbiter, } from './council-brain.js';
9
9
  export { type CouncilResolution, type CouncilResolutionInput, type CouncilResolutionSeat, type CouncilResolutionVote, resolveCouncilVotes, } from './council-resolution.js';
10
- export { BUILTIN_COUNCIL_PERSONAS, CouncilPersonaRegistry, createCouncilPersonaRegistry, DEFAULT_COUNCIL_PERSONA_REGISTRY, } from './council-personas.js';
10
+ export { BUILTIN_COUNCIL_PERSONA_IDS, BUILTIN_COUNCIL_PERSONAS, CouncilPersonaRegistry, createCouncilPersonaRegistry, DEFAULT_COUNCIL_PERSONA_REGISTRY, } from './council-personas.js';
11
11
  export { BUILTIN_COUNCIL_PROFILES, CouncilProfileRegistry, createCouncilProfileRegistry, DEFAULT_COUNCIL_APPROVAL_FRACTION, DEFAULT_COUNCIL_JUDGE_MAX_TOKENS, DEFAULT_COUNCIL_OVERALL_TIMEOUT_MS, DEFAULT_COUNCIL_PER_CALL_TIMEOUT_MS, DEFAULT_COUNCIL_PROFILE_REGISTRY, DEFAULT_COUNCIL_QUORUM_FRACTION, DEFAULT_COUNCIL_VOTER_MAX_TOKENS, normalizeCouncilProfile, resolveCouncilProfile, } from './council-profiles.js';
12
12
  export { buildCouncilJudgeSystemPrompt, buildCouncilJudgeUserPrompt, buildCouncilQuestionPrompt, buildCouncilVoterSystemPrompt, buildCouncilVoterUserPrompt, COUNCIL_JUDGE_PROMPT_PATH, COUNCIL_VOTER_PROMPT_PATH, } from './council-prompts.js';
13
13
  export { COUNCIL_REFUSAL_OPTION_ID, CouncilOrchestrator, type CouncilOrchestratorOptions, DEFAULT_COUNCIL_MAX_CONCURRENCY, MAX_COUNCIL_CONCURRENCY, } from './council-orchestrator.js';