@wrongstack/core 0.310.0 → 0.313.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.
@@ -20988,7 +20988,7 @@ async function readDirectorSubagentSession(args) {
20988
20988
  }
20989
20989
 
20990
20990
  // src/types/quota-regex.ts
20991
- 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;
20991
+ 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|credit)[-_\s]*(?:quota|limit)(?![-_\w])|usage[-_\s]*(?:limit|quota)[-_\s]*(?:reached|exceeded|for)/i;
20992
20992
  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;
20993
20993
 
20994
20994
  // src/types/provider.ts
@@ -21001,7 +21001,8 @@ function classifyProviderError(status, body, message) {
21001
21001
  if (status === 0) return "network";
21002
21002
  if (status === 408) return "timeout";
21003
21003
  if (status === 599) return "stream_hang";
21004
- if (status === 402 || QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
21004
+ if (QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
21005
+ if (status === 402) return "quota_exhausted";
21005
21006
  if (status === 429 && body?.message && body.type !== "rate_limit_exceeded" && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
21006
21007
  return "quota_exhausted";
21007
21008
  }
@@ -67,8 +67,8 @@ var EventBus = class {
67
67
  *
68
68
  * Returns an unsubscribe function.
69
69
  */
70
- onAny(fn) {
71
- return this.onPattern("*", fn);
70
+ onAny(fn, owner) {
71
+ return this.onPattern("*", fn, owner);
72
72
  }
73
73
  /**
74
74
  * Subscribe to all events whose name matches a glob-style prefix.
@@ -81,16 +81,16 @@ var EventBus = class {
81
81
  *
82
82
  * Returns an unsubscribe function.
83
83
  */
84
- onPattern(pattern, fn) {
84
+ onPattern(pattern, fn, owner) {
85
85
  if (this.wildcards.length >= MAX_WILDCARDS) {
86
86
  this.logger?.error(
87
- `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onPattern("${pattern}"). Callers must dispose their wildcard listeners to prevent unbounded growth.`
87
+ `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onPattern("${pattern}")` + (owner ? ` (owner: ${owner})` : "") + ". Callers must dispose their wildcard listeners to prevent unbounded growth."
88
88
  );
89
89
  return () => {
90
90
  };
91
91
  }
92
92
  const match = makePatternMatcher(pattern);
93
- const entry = { match, fn };
93
+ const entry = { match, fn, owner };
94
94
  this.wildcards.push(entry);
95
95
  this.wildcardSnapshotCache = null;
96
96
  return () => {
@@ -108,15 +108,15 @@ var EventBus = class {
108
108
  *
109
109
  * Returns an unsubscribe function.
110
110
  */
111
- onRegex(regex, fn) {
111
+ onRegex(regex, fn, owner) {
112
112
  if (this.wildcards.length >= MAX_WILDCARDS) {
113
113
  this.logger?.error(
114
- `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onRegex(${regex}). Callers must dispose their wildcard listeners to prevent unbounded growth.`
114
+ `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onRegex(${regex})` + (owner ? ` (owner: ${owner})` : "") + ". Callers must dispose their wildcard listeners to prevent unbounded growth."
115
115
  );
116
116
  return () => {
117
117
  };
118
118
  }
119
- const entry = { match: (e) => regex.test(e), fn };
119
+ const entry = { match: (e) => regex.test(e), fn, owner };
120
120
  this.wildcards.push(entry);
121
121
  this.wildcardSnapshotCache = null;
122
122
  return () => {
@@ -1416,6 +1416,38 @@ function isMailboxReceiptRecordV2(value) {
1416
1416
  return true;
1417
1417
  }
1418
1418
 
1419
+ // src/coordination/mailbox-session-sync.ts
1420
+ function isAffectedBySessionAffinity(message) {
1421
+ return message.sessionAffinity !== void 0;
1422
+ }
1423
+ async function acceptMailboxMessageForSession(message, currentSessionId, ctx) {
1424
+ if (!isAffectedBySessionAffinity(message)) return true;
1425
+ const affinity = message.sessionAffinity;
1426
+ if (affinity === null || typeof affinity !== "object" || Array.isArray(affinity)) {
1427
+ return false;
1428
+ }
1429
+ if (affinity.sessionId !== void 0 && typeof affinity.sessionId !== "string" || affinity.reportId !== void 0 && typeof affinity.reportId !== "string") {
1430
+ return false;
1431
+ }
1432
+ if (!currentSessionId) {
1433
+ return ctx?.allowUnscoped === true;
1434
+ }
1435
+ if (typeof affinity.sessionId === "string" && affinity.sessionId.length > 0) {
1436
+ if (affinity.sessionId !== currentSessionId) return false;
1437
+ return true;
1438
+ }
1439
+ if (affinity.reportId && ctx?.resolveChimeraReportSessionId) {
1440
+ try {
1441
+ const resolved = await ctx.resolveChimeraReportSessionId(affinity.reportId);
1442
+ if (resolved === currentSessionId) return true;
1443
+ if (resolved !== void 0) return false;
1444
+ } catch {
1445
+ }
1446
+ }
1447
+ if (ctx?.allowUnscoped === true) return true;
1448
+ return false;
1449
+ }
1450
+
1419
1451
  // src/coordination/mailbox-message-codec.ts
1420
1452
  var MESSAGE_TYPES = /* @__PURE__ */ new Set([
1421
1453
  "note",
@@ -2772,7 +2804,7 @@ var SqliteMailbox = class {
2772
2804
  }
2773
2805
  const rows = this.stmt(sql).all(...params);
2774
2806
  const idFilter = query.ids === void 0 ? void 0 : new Set(query.ids);
2775
- const messages = this.materializeMessageRows(rows).filter((message) => {
2807
+ const filtered = this.materializeMessageRows(rows).filter((message) => {
2776
2808
  if (idFilter !== void 0 && !idFilter.has(message.id)) return false;
2777
2809
  if (query.to !== void 0 && message.to !== query.to && message.to !== "*") return false;
2778
2810
  if (query.from !== void 0 && message.from !== query.from) return false;
@@ -2791,6 +2823,20 @@ var SqliteMailbox = class {
2791
2823
  if (query.replyTo !== void 0 && message.replyTo !== query.replyTo) return false;
2792
2824
  return true;
2793
2825
  });
2826
+ let messages = filtered;
2827
+ if (query.currentSessionId !== void 0 || query.sessionAffinityCtx !== void 0) {
2828
+ const kept = [];
2829
+ for (const message of filtered) {
2830
+ if (await acceptMailboxMessageForSession(
2831
+ message,
2832
+ query.currentSessionId,
2833
+ query.sessionAffinityCtx
2834
+ )) {
2835
+ kept.push(message);
2836
+ }
2837
+ }
2838
+ messages = kept;
2839
+ }
2794
2840
  messages.sort((left, right) => right.timestamp.localeCompare(left.timestamp));
2795
2841
  return messages.slice(0, query.limit ?? 50).map((message) => {
2796
2842
  const copy = {
@@ -2903,7 +2949,7 @@ var SqliteMailbox = class {
2903
2949
  * identity is `leader`. This call path carries no role, matching the
2904
2950
  * `isMailboxMessageVisibleTo(message, forAgentId)` it replaces.
2905
2951
  */
2906
- async unreadCount(forAgentId, sessionId) {
2952
+ async unreadCount(forAgentId, sessionId, ctx) {
2907
2953
  const sessionAddress = sessionId === void 0 ? void 0 : sessionRecipient(sessionId);
2908
2954
  const where = [];
2909
2955
  const params = [];
@@ -2944,10 +2990,20 @@ var SqliteMailbox = class {
2944
2990
  WHERE any_receipt.message_id = messages.id
2945
2991
  )
2946
2992
  )`);
2947
- const row = this.stmt(
2948
- `SELECT COUNT(*) AS total FROM messages WHERE ${where.join(" AND ")}`
2949
- ).get(...params);
2950
- return Number(row?.total ?? 0);
2993
+ if (sessionId === void 0 && ctx === void 0) {
2994
+ const row = this.stmt(
2995
+ `SELECT COUNT(*) AS total FROM messages WHERE ${where.join(" AND ")}`
2996
+ ).get(...params);
2997
+ return Number(row?.total ?? 0);
2998
+ }
2999
+ const rows = this.stmt(
3000
+ `SELECT id, data, legacy_global_completion FROM messages WHERE ${where.join(" AND ")}`
3001
+ ).all(...params);
3002
+ let total = 0;
3003
+ for (const message of this.materializeMessageRows(rows)) {
3004
+ if (await acceptMailboxMessageForSession(message, sessionId, ctx)) total += 1;
3005
+ }
3006
+ return total;
2951
3007
  }
2952
3008
  async softDelete(mailId, by) {
2953
3009
  const message = this.findMessage(mailId);
@@ -3,6 +3,7 @@ import type { MailboxAudience, MailboxMessage, MailboxSessionAffinity, MailboxTa
3
3
  export { MAILBOX_TYPE_PROPERTIES, type MailboxMessageType, type MailboxTypeCategory, } from './mailbox-type-properties.js';
4
4
  export { expandMailboxCapabilities, hasMailboxCapability, MAILBOX_CAPABILITY_IMPLICATIONS, type MailboxActorContext, type MailboxAuthMode, type MailboxCapability, type MailboxPrincipalKind, } from './mailbox-auth-types.js';
5
5
  export { isActionRequiredForActor, isMailboxLeader, isMailboxMessageVisibleTo, isMailboxReceiptRecordV2, isMailboxSenderInFamily, mailboxIdentityBase, normalizeRecipient, SESSION_RECIPIENT_PREFIX, sessionRecipient, validateSendType, } from './mailbox-predicates.js';
6
+ import type { MailboxSessionAffinityContext } from './mailbox-session-sync.js';
6
7
  export { acceptMailboxMessageForSession, acceptMailboxMessageForSessionSync, type MailboxSessionAffinityContext, } from './mailbox-session-sync.js';
7
8
  export { type ActorMailboxMessage, type MailboxAudience, type MailboxLegacyReportSessionAffinity, type MailboxMessage, type MailboxReceiptRecordV2, type MailboxScopedSessionAffinity, type MailboxSessionAffinity, type MailboxTaskContext, type ReadReceipts, } from './mailbox-message-types.js';
8
9
  export interface RegisteredAgent {
@@ -48,7 +49,18 @@ export interface MailboxQuery {
48
49
  minPriority?: 'low' | 'normal' | 'high' | undefined;
49
50
  limit?: number | undefined;
50
51
  since?: string | undefined;
52
+ /** Filters by the message's SENDER session id (origin), not the reader's session. */
51
53
  sessionId?: string | undefined;
54
+ /**
55
+ * Reader's current session id. When present, messages carrying a
56
+ * session-affinity token for a DIFFERENT session are excluded, matching the
57
+ * inbox checker's receive-side filter (mailbox-attach applySessionAffinityFilter).
58
+ * Kept distinct from `sessionId` (sender origin) so a reader can filter by
59
+ * both simultaneously.
60
+ */
61
+ currentSessionId?: string | undefined;
62
+ /** Resolver + unscoped policy for session-affinity tokens (see acceptMailboxMessageForSession). */
63
+ sessionAffinityCtx?: MailboxSessionAffinityContext | undefined;
52
64
  includeDeleted?: boolean | undefined;
53
65
  replyTo?: string | undefined;
54
66
  }
@@ -172,7 +184,7 @@ export interface Mailbox {
172
184
  registerAgent(input: AgentRegistrationInput): Promise<void>;
173
185
  deregisterAgent(agentId: string): Promise<void>;
174
186
  heartbeat(input: AgentHeartbeatInput): Promise<void>;
175
- unreadCount(forAgentId: string, sessionId?: string): Promise<number>;
187
+ unreadCount(forAgentId: string, sessionId?: string, ctx?: MailboxSessionAffinityContext): Promise<number>;
176
188
  close(): Promise<void>;
177
189
  clearAll(): Promise<void>;
178
190
  purgeStale(opts?: PurgeOptions): Promise<PurgeResult>;
@@ -2,6 +2,7 @@ import type { EventBus } from '../kernel/events.js';
2
2
  import type { CredentialValidation, IssueCredentialOptions, MailboxCredential } from './mailbox-credential-store.js';
3
3
  import type { MailboxEventEmitter } from './mailbox-events.js';
4
4
  import type { AgentHeartbeatInput, AgentRegistrationInput, AutoCompactOptions, AutoCompactResult, ClientHeartbeatInput, ClientRegistrationInput, ClientStatus, Mailbox, MailboxAckBatchInput, MailboxAckInput, MailboxAgentStatus, MailboxMessage, MailboxQuery, MailboxSendInput, PurgeOptions, PurgeResult } from './mailbox-types.js';
5
+ import { type MailboxSessionAffinityContext } from './mailbox-types.js';
5
6
  export declare const SQLITE_MAILBOX_FILE = "_mailbox.sqlite";
6
7
  export declare class SqliteMailbox implements Mailbox {
7
8
  readonly projectDir: string;
@@ -59,7 +60,7 @@ export declare class SqliteMailbox implements Mailbox {
59
60
  * identity is `leader`. This call path carries no role, matching the
60
61
  * `isMailboxMessageVisibleTo(message, forAgentId)` it replaces.
61
62
  */
62
- unreadCount(forAgentId: string, sessionId?: string): Promise<number>;
63
+ unreadCount(forAgentId: string, sessionId?: string, ctx?: MailboxSessionAffinityContext): Promise<number>;
63
64
  softDelete(mailId: string, by: string): Promise<MailboxMessage | null>;
64
65
  restore(mailId: string): Promise<MailboxMessage | null>;
65
66
  private persistAgent;
@@ -3947,7 +3947,7 @@ function truncate(s, max) {
3947
3947
  }
3948
3948
 
3949
3949
  // src/types/quota-regex.ts
3950
- 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;
3950
+ 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|credit)[-_\s]*(?:quota|limit)(?![-_\w])|usage[-_\s]*(?:limit|quota)[-_\s]*(?:reached|exceeded|for)/i;
3951
3951
 
3952
3952
  // src/types/provider.ts
3953
3953
  function effectiveInputTokens(usage) {
@@ -3962,7 +3962,8 @@ function classifyProviderError(status, body, message) {
3962
3962
  if (status === 0) return "network";
3963
3963
  if (status === 408) return "timeout";
3964
3964
  if (status === 599) return "stream_hang";
3965
- if (status === 402 || QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
3965
+ if (QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
3966
+ if (status === 402) return "quota_exhausted";
3966
3967
  if (status === 429 && body?.message && body.type !== "rate_limit_exceeded" && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
3967
3968
  return "quota_exhausted";
3968
3969
  }
@@ -4854,7 +4854,7 @@ function truncate(s, max) {
4854
4854
  }
4855
4855
 
4856
4856
  // src/types/quota-regex.ts
4857
- 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;
4857
+ 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|credit)[-_\s]*(?:quota|limit)(?![-_\w])|usage[-_\s]*(?:limit|quota)[-_\s]*(?:reached|exceeded|for)/i;
4858
4858
 
4859
4859
  // src/types/provider.ts
4860
4860
  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;
@@ -4866,7 +4866,8 @@ function classifyProviderError(status, body, message) {
4866
4866
  if (status === 0) return "network";
4867
4867
  if (status === 408) return "timeout";
4868
4868
  if (status === 599) return "stream_hang";
4869
- if (status === 402 || QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
4869
+ if (QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
4870
+ if (status === 402) return "quota_exhausted";
4870
4871
  if (status === 429 && body?.message && body.type !== "rate_limit_exceeded" && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
4871
4872
  return "quota_exhausted";
4872
4873
  }
package/dist/index.js CHANGED
@@ -3484,6 +3484,7 @@ function resolveTokenSavingTier(val, maxContext) {
3484
3484
  }
3485
3485
  var DEFAULT_TUI_THINKING_WORD = "thinking";
3486
3486
  var MAX_TUI_THINKING_WORD_LENGTH = 16;
3487
+ var MAX_WRONGPROXY_URL_LENGTH = 2048;
3487
3488
  function normalizeTuiThinkingWord(value) {
3488
3489
  if (typeof value !== "string") return DEFAULT_TUI_THINKING_WORD;
3489
3490
  const word = value.trim();
@@ -38420,7 +38421,7 @@ import { randomUUID as randomUUID18 } from "node:crypto";
38420
38421
  init_errors();
38421
38422
 
38422
38423
  // src/types/quota-regex.ts
38423
- 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;
38424
+ 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|credit)[-_\s]*(?:quota|limit)(?![-_\w])|usage[-_\s]*(?:limit|quota)[-_\s]*(?:reached|exceeded|for)/i;
38424
38425
  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;
38425
38426
 
38426
38427
  // src/types/provider.ts
@@ -38457,7 +38458,8 @@ function classifyProviderError(status, body, message) {
38457
38458
  if (status === 0) return "network";
38458
38459
  if (status === 408) return "timeout";
38459
38460
  if (status === 599) return "stream_hang";
38460
- if (status === 402 || QUOTA_EXHAUSTED_RE.test(text2)) return "quota_exhausted";
38461
+ if (QUOTA_EXHAUSTED_RE.test(text2)) return "quota_exhausted";
38462
+ if (status === 402) return "quota_exhausted";
38461
38463
  if (status === 429 && body?.message && body.type !== "rate_limit_exceeded" && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
38462
38464
  return "quota_exhausted";
38463
38465
  }
@@ -73360,8 +73362,8 @@ var EventBus = class {
73360
73362
  *
73361
73363
  * Returns an unsubscribe function.
73362
73364
  */
73363
- onAny(fn) {
73364
- return this.onPattern("*", fn);
73365
+ onAny(fn, owner) {
73366
+ return this.onPattern("*", fn, owner);
73365
73367
  }
73366
73368
  /**
73367
73369
  * Subscribe to all events whose name matches a glob-style prefix.
@@ -73374,16 +73376,16 @@ var EventBus = class {
73374
73376
  *
73375
73377
  * Returns an unsubscribe function.
73376
73378
  */
73377
- onPattern(pattern, fn) {
73379
+ onPattern(pattern, fn, owner) {
73378
73380
  if (this.wildcards.length >= MAX_WILDCARDS) {
73379
73381
  this.logger?.error(
73380
- `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onPattern("${pattern}"). Callers must dispose their wildcard listeners to prevent unbounded growth.`
73382
+ `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onPattern("${pattern}")` + (owner ? ` (owner: ${owner})` : "") + ". Callers must dispose their wildcard listeners to prevent unbounded growth."
73381
73383
  );
73382
73384
  return () => {
73383
73385
  };
73384
73386
  }
73385
73387
  const match = makePatternMatcher(pattern);
73386
- const entry = { match, fn };
73388
+ const entry = { match, fn, owner };
73387
73389
  this.wildcards.push(entry);
73388
73390
  this.wildcardSnapshotCache = null;
73389
73391
  return () => {
@@ -73401,15 +73403,15 @@ var EventBus = class {
73401
73403
  *
73402
73404
  * Returns an unsubscribe function.
73403
73405
  */
73404
- onRegex(regex, fn) {
73406
+ onRegex(regex, fn, owner) {
73405
73407
  if (this.wildcards.length >= MAX_WILDCARDS) {
73406
73408
  this.logger?.error(
73407
- `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onRegex(${regex}). Callers must dispose their wildcard listeners to prevent unbounded growth.`
73409
+ `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onRegex(${regex})` + (owner ? ` (owner: ${owner})` : "") + ". Callers must dispose their wildcard listeners to prevent unbounded growth."
73408
73410
  );
73409
73411
  return () => {
73410
73412
  };
73411
73413
  }
73412
- const entry = { match: (e) => regex.test(e), fn };
73414
+ const entry = { match: (e) => regex.test(e), fn, owner };
73413
73415
  this.wildcards.push(entry);
73414
73416
  this.wildcardSnapshotCache = null;
73415
73417
  return () => {
@@ -73587,16 +73589,16 @@ var ScopedEventBus = class extends EventBus {
73587
73589
  * Subscribe to all events. Alias for `onPattern('*')` — the listener is
73588
73590
  * tracked so that `teardown()` will remove it automatically.
73589
73591
  */
73590
- onAny(fn) {
73592
+ onAny(fn, owner) {
73591
73593
  if (this.wildcards.length >= MAX_WILDCARDS) {
73592
73594
  this.logger?.error(
73593
- `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onAny(). Callers must dispose their wildcard listeners to prevent unbounded growth.`
73595
+ `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onAny()` + (owner ? ` (owner: ${owner})` : "") + ". Callers must dispose their wildcard listeners to prevent unbounded growth."
73594
73596
  );
73595
73597
  return () => {
73596
73598
  };
73597
73599
  }
73598
73600
  const key = this.nextKey++;
73599
- const unsub = EventBus.prototype.onPattern.call(this, "*", fn);
73601
+ const unsub = EventBus.prototype.onPattern.call(this, "*", fn, owner);
73600
73602
  this.registrations.set(key, unsub);
73601
73603
  return () => {
73602
73604
  this.registrations.delete(key);
@@ -73607,16 +73609,16 @@ var ScopedEventBus = class extends EventBus {
73607
73609
  * Identical to `EventBus.onPattern` but the listener is tracked so that
73608
73610
  * `teardown()` will remove it automatically.
73609
73611
  */
73610
- onPattern(pattern, fn) {
73612
+ onPattern(pattern, fn, owner) {
73611
73613
  if (this.wildcards.length >= MAX_WILDCARDS) {
73612
73614
  this.logger?.error(
73613
- `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onPattern("${pattern}"). Callers must dispose their wildcard listeners to prevent unbounded growth.`
73615
+ `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onPattern("${pattern}")` + (owner ? ` (owner: ${owner})` : "") + ". Callers must dispose their wildcard listeners to prevent unbounded growth."
73614
73616
  );
73615
73617
  return () => {
73616
73618
  };
73617
73619
  }
73618
73620
  const key = this.nextKey++;
73619
- const unsub = super.onPattern(pattern, fn);
73621
+ const unsub = super.onPattern(pattern, fn, owner);
73620
73622
  this.registrations.set(key, unsub);
73621
73623
  return () => {
73622
73624
  this.registrations.delete(key);
@@ -73627,16 +73629,16 @@ var ScopedEventBus = class extends EventBus {
73627
73629
  * Identical to `EventBus.onRegex` but the listener is tracked so that
73628
73630
  * `teardown()` will remove it automatically.
73629
73631
  */
73630
- onRegex(regex, fn) {
73632
+ onRegex(regex, fn, owner) {
73631
73633
  if (this.wildcards.length >= MAX_WILDCARDS) {
73632
73634
  this.logger?.error(
73633
- `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onRegex(${regex}). Callers must dispose their wildcard listeners to prevent unbounded growth.`
73635
+ `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onRegex(${regex})` + (owner ? ` (owner: ${owner})` : "") + ". Callers must dispose their wildcard listeners to prevent unbounded growth."
73634
73636
  );
73635
73637
  return () => {
73636
73638
  };
73637
73639
  }
73638
73640
  const key = this.nextKey++;
73639
- const unsub = super.onRegex(regex, fn);
73641
+ const unsub = super.onRegex(regex, fn, owner);
73640
73642
  this.registrations.set(key, unsub);
73641
73643
  return () => {
73642
73644
  this.registrations.delete(key);
@@ -92933,6 +92935,7 @@ export {
92933
92935
  MAX_SUBAGENT_STRUCTURED_REPORT_CHARS,
92934
92936
  MAX_SUBJECT_LEN,
92935
92937
  MAX_TUI_THINKING_WORD_LENGTH,
92938
+ MAX_WRONGPROXY_URL_LENGTH,
92936
92939
  MEDIUM_BUDGET,
92937
92940
  MEMORY_EVIDENCE_TAG,
92938
92941
  MEMORY_TYPE_LABELS,
@@ -0,0 +1,26 @@
1
+ /**
2
+ * WrongTrace gate-decision events.
3
+ *
4
+ * Emitted by the WrongTrace lock-gate hooks (see
5
+ * `@wrongstack/wrongtrace` hooks.ts) through each host's EventBus:
6
+ * every mutating tool call's gate decision is observable — denied edits,
7
+ * fragile-file nudges, lock acquisition / race-loss / release.
8
+ *
9
+ * Kept structurally matching the adapter's `WrongTraceGateDecisionEvent`
10
+ * union so core never imports the adapter (direction: wrongtrace is the
11
+ * leaf; core must not depend on it).
12
+ */
13
+ export interface WrongTraceEventMap {
14
+ 'wrongtrace.gate.decision': {
15
+ kind: 'deny' | 'allow-fragile' | 'lock-acquired' | 'lock-conflict-race' | 'lock-released';
16
+ /** Target file path the gate decided on. */
17
+ path: string;
18
+ /** `deny` — human-readable owner/expiry reason shown to the model. */
19
+ reason?: string | undefined;
20
+ /** `allow-fragile` — why the file is considered fragile. */
21
+ reasons?: readonly string[] | undefined;
22
+ /** `lock-acquired` — lock owner identity (`wrongstack:<sessionId>`). */
23
+ owner?: string | undefined;
24
+ };
25
+ }
26
+ //# sourceMappingURL=wrongtrace-events.d.ts.map
@@ -14,6 +14,7 @@ import type { SddEventMap } from './events/sdd-events.js';
14
14
  import type { SessionEventMap } from './events/session-events.js';
15
15
  import type { ToolEventMap } from './events/tool-events.js';
16
16
  import type { WorktreeEventMap } from './events/worktree-events.js';
17
+ import type { WrongTraceEventMap } from './events/wrongtrace-events.js';
17
18
  /** Distress signals the BrainMonitor watches. See `coordination/brain-monitor.ts`. */
18
19
  export type BrainInterventionKind = 'tool_failure_streak' | 'error_storm' | 'agent_stall' | 'file_churn';
19
20
  /**
@@ -48,7 +49,7 @@ export interface TrackedAgentSnapshot {
48
49
  latestPromptAt?: number | undefined;
49
50
  lastActivityAt: string;
50
51
  }
51
- export interface EventMap extends AgentEventMap, BrainEventMap, SessionEventMap, ProviderEventMap, ProcessEventMap, NetworkEventMap, FileEventMap, ToolEventMap, MemoryEventMap, SddEventMap, WorktreeEventMap, FleetEventMap {
52
+ export interface EventMap extends AgentEventMap, BrainEventMap, SessionEventMap, ProviderEventMap, ProcessEventMap, NetworkEventMap, FileEventMap, ToolEventMap, MemoryEventMap, SddEventMap, WorktreeEventMap, FleetEventMap, WrongTraceEventMap {
52
53
  }
53
54
  export type EventName = keyof EventMap;
54
55
  export type Listener<E extends EventName> = (payload: EventMap[E]) => void;
@@ -60,6 +61,8 @@ export declare class EventBus {
60
61
  protected readonly wildcards: Array<{
61
62
  match: (event: string) => boolean;
62
63
  fn: (event: string, payload: unknown) => void;
64
+ /** Optional registration-site label included in cap-rejection warnings. */
65
+ owner?: string | undefined;
63
66
  }>;
64
67
  protected logger?: EventLogger | undefined;
65
68
  /**
@@ -82,7 +85,9 @@ export declare class EventBus {
82
85
  *
83
86
  * Returns an unsubscribe function.
84
87
  */
85
- onAny(fn: (event: string, payload: unknown) => void): () => void;
88
+ onAny(fn: (event: string, payload: unknown) => void,
89
+ /** Optional registration-site label included in cap-rejection warnings. */
90
+ owner?: string): () => void;
86
91
  /**
87
92
  * Subscribe to all events whose name matches a glob-style prefix.
88
93
  * `'tool.*'` matches `tool.started`, `tool.executed`, `tool.progress`, etc.
@@ -94,7 +99,9 @@ export declare class EventBus {
94
99
  *
95
100
  * Returns an unsubscribe function.
96
101
  */
97
- onPattern(pattern: string, fn: (event: string, payload: unknown) => void): () => void;
102
+ onPattern(pattern: string, fn: (event: string, payload: unknown) => void,
103
+ /** Optional registration-site label included in cap-rejection warnings. */
104
+ owner?: string): () => void;
98
105
  /**
99
106
  * Subscribe to all events whose name matches a RegExp.
100
107
  * More flexible than `onPattern` — use when you need regex features
@@ -102,7 +109,9 @@ export declare class EventBus {
102
109
  *
103
110
  * Returns an unsubscribe function.
104
111
  */
105
- onRegex(regex: RegExp, fn: (event: string, payload: unknown) => void): () => void;
112
+ onRegex(regex: RegExp, fn: (event: string, payload: unknown) => void,
113
+ /** Optional registration-site label included in cap-rejection warnings. */
114
+ owner?: string): () => void;
106
115
  emit<E extends EventName>(event: E, payload: EventMap[E]): void;
107
116
  /**
108
117
  * Dispatch array for one event name, or `undefined` when nothing is
@@ -208,17 +217,17 @@ export declare class ScopedEventBus extends EventBus {
208
217
  * Subscribe to all events. Alias for `onPattern('*')` — the listener is
209
218
  * tracked so that `teardown()` will remove it automatically.
210
219
  */
211
- onAny(fn: (event: string, payload: unknown) => void): () => void;
220
+ onAny(fn: (event: string, payload: unknown) => void, owner?: string): () => void;
212
221
  /**
213
222
  * Identical to `EventBus.onPattern` but the listener is tracked so that
214
223
  * `teardown()` will remove it automatically.
215
224
  */
216
- onPattern(pattern: string, fn: (event: string, payload: unknown) => void): () => void;
225
+ onPattern(pattern: string, fn: (event: string, payload: unknown) => void, owner?: string): () => void;
217
226
  /**
218
227
  * Identical to `EventBus.onRegex` but the listener is tracked so that
219
228
  * `teardown()` will remove it automatically.
220
229
  */
221
- onRegex(regex: RegExp, fn: (event: string, payload: unknown) => void): () => void;
230
+ onRegex(regex: RegExp, fn: (event: string, payload: unknown) => void, owner?: string): () => void;
222
231
  /**
223
232
  * Remove every listener that was registered through this scoped bus.
224
233
  * Idempotent — calling it multiple times is safe.
@@ -499,8 +499,8 @@ var EventBus = class {
499
499
  *
500
500
  * Returns an unsubscribe function.
501
501
  */
502
- onAny(fn) {
503
- return this.onPattern("*", fn);
502
+ onAny(fn, owner) {
503
+ return this.onPattern("*", fn, owner);
504
504
  }
505
505
  /**
506
506
  * Subscribe to all events whose name matches a glob-style prefix.
@@ -513,16 +513,16 @@ var EventBus = class {
513
513
  *
514
514
  * Returns an unsubscribe function.
515
515
  */
516
- onPattern(pattern, fn) {
516
+ onPattern(pattern, fn, owner) {
517
517
  if (this.wildcards.length >= MAX_WILDCARDS) {
518
518
  this.logger?.error(
519
- `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onPattern("${pattern}"). Callers must dispose their wildcard listeners to prevent unbounded growth.`
519
+ `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onPattern("${pattern}")` + (owner ? ` (owner: ${owner})` : "") + ". Callers must dispose their wildcard listeners to prevent unbounded growth."
520
520
  );
521
521
  return () => {
522
522
  };
523
523
  }
524
524
  const match = makePatternMatcher(pattern);
525
- const entry = { match, fn };
525
+ const entry = { match, fn, owner };
526
526
  this.wildcards.push(entry);
527
527
  this.wildcardSnapshotCache = null;
528
528
  return () => {
@@ -540,15 +540,15 @@ var EventBus = class {
540
540
  *
541
541
  * Returns an unsubscribe function.
542
542
  */
543
- onRegex(regex, fn) {
543
+ onRegex(regex, fn, owner) {
544
544
  if (this.wildcards.length >= MAX_WILDCARDS) {
545
545
  this.logger?.error(
546
- `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onRegex(${regex}). Callers must dispose their wildcard listeners to prevent unbounded growth.`
546
+ `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onRegex(${regex})` + (owner ? ` (owner: ${owner})` : "") + ". Callers must dispose their wildcard listeners to prevent unbounded growth."
547
547
  );
548
548
  return () => {
549
549
  };
550
550
  }
551
- const entry = { match: (e) => regex.test(e), fn };
551
+ const entry = { match: (e) => regex.test(e), fn, owner };
552
552
  this.wildcards.push(entry);
553
553
  this.wildcardSnapshotCache = null;
554
554
  return () => {
@@ -726,16 +726,16 @@ var ScopedEventBus = class extends EventBus {
726
726
  * Subscribe to all events. Alias for `onPattern('*')` — the listener is
727
727
  * tracked so that `teardown()` will remove it automatically.
728
728
  */
729
- onAny(fn) {
729
+ onAny(fn, owner) {
730
730
  if (this.wildcards.length >= MAX_WILDCARDS) {
731
731
  this.logger?.error(
732
- `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onAny(). Callers must dispose their wildcard listeners to prevent unbounded growth.`
732
+ `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onAny()` + (owner ? ` (owner: ${owner})` : "") + ". Callers must dispose their wildcard listeners to prevent unbounded growth."
733
733
  );
734
734
  return () => {
735
735
  };
736
736
  }
737
737
  const key = this.nextKey++;
738
- const unsub = EventBus.prototype.onPattern.call(this, "*", fn);
738
+ const unsub = EventBus.prototype.onPattern.call(this, "*", fn, owner);
739
739
  this.registrations.set(key, unsub);
740
740
  return () => {
741
741
  this.registrations.delete(key);
@@ -746,16 +746,16 @@ var ScopedEventBus = class extends EventBus {
746
746
  * Identical to `EventBus.onPattern` but the listener is tracked so that
747
747
  * `teardown()` will remove it automatically.
748
748
  */
749
- onPattern(pattern, fn) {
749
+ onPattern(pattern, fn, owner) {
750
750
  if (this.wildcards.length >= MAX_WILDCARDS) {
751
751
  this.logger?.error(
752
- `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onPattern("${pattern}"). Callers must dispose their wildcard listeners to prevent unbounded growth.`
752
+ `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onPattern("${pattern}")` + (owner ? ` (owner: ${owner})` : "") + ". Callers must dispose their wildcard listeners to prevent unbounded growth."
753
753
  );
754
754
  return () => {
755
755
  };
756
756
  }
757
757
  const key = this.nextKey++;
758
- const unsub = super.onPattern(pattern, fn);
758
+ const unsub = super.onPattern(pattern, fn, owner);
759
759
  this.registrations.set(key, unsub);
760
760
  return () => {
761
761
  this.registrations.delete(key);
@@ -766,16 +766,16 @@ var ScopedEventBus = class extends EventBus {
766
766
  * Identical to `EventBus.onRegex` but the listener is tracked so that
767
767
  * `teardown()` will remove it automatically.
768
768
  */
769
- onRegex(regex, fn) {
769
+ onRegex(regex, fn, owner) {
770
770
  if (this.wildcards.length >= MAX_WILDCARDS) {
771
771
  this.logger?.error(
772
- `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onRegex(${regex}). Callers must dispose their wildcard listeners to prevent unbounded growth.`
772
+ `EventBus wildcard limit (${MAX_WILDCARDS}) reached \u2014 rejecting onRegex(${regex})` + (owner ? ` (owner: ${owner})` : "") + ". Callers must dispose their wildcard listeners to prevent unbounded growth."
773
773
  );
774
774
  return () => {
775
775
  };
776
776
  }
777
777
  const key = this.nextKey++;
778
- const unsub = super.onRegex(regex, fn);
778
+ const unsub = super.onRegex(regex, fn, owner);
779
779
  this.registrations.set(key, unsub);
780
780
  return () => {
781
781
  this.registrations.delete(key);
@@ -1551,7 +1551,7 @@ var FsError = class extends WrongStackError {
1551
1551
  };
1552
1552
 
1553
1553
  // src/types/quota-regex.ts
1554
- 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;
1554
+ 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|credit)[-_\s]*(?:quota|limit)(?![-_\w])|usage[-_\s]*(?:limit|quota)[-_\s]*(?:reached|exceeded|for)/i;
1555
1555
 
1556
1556
  // src/types/provider.ts
1557
1557
  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;
@@ -1563,7 +1563,8 @@ function classifyProviderError(status, body, message) {
1563
1563
  if (status === 0) return "network";
1564
1564
  if (status === 408) return "timeout";
1565
1565
  if (status === 599) return "stream_hang";
1566
- if (status === 402 || QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
1566
+ if (QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
1567
+ if (status === 402) return "quota_exhausted";
1567
1568
  if (status === 429 && body?.message && body.type !== "rate_limit_exceeded" && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
1568
1569
  return "quota_exhausted";
1569
1570
  }
@@ -162,6 +162,13 @@ export declare function normalizeTokenSavingTier(val?: TokenSavingTier | boolean
162
162
  export declare function resolveTokenSavingTier(val: TokenSavingTier | boolean | undefined, maxContext: number | undefined): ConcreteTokenSavingTier;
163
163
  export declare const DEFAULT_TUI_THINKING_WORD = "thinking";
164
164
  export declare const MAX_TUI_THINKING_WORD_LENGTH = 16;
165
+ /**
166
+ * Hard cap on the WrongProxy / WrongTrace URL draft. URLs don't have a
167
+ * strict length limit, but capping to a sane 2 KiB prevents a runaway
168
+ * paste from bloating the `settingsPicker` slice. The runtime probe
169
+ * accepts any well-formed URL up to whatever the OS / fetch allow.
170
+ */
171
+ export declare const MAX_WRONGPROXY_URL_LENGTH = 2048;
165
172
  /**
166
173
  * Normalize the configurable statusline word shown while the TUI is working.
167
174
  * The value must be a single short word; invalid values fall back to the default.
@@ -109,6 +109,36 @@ export interface ToolsConfig {
109
109
  * in the next session (same as `features.tokenSavingMode`).
110
110
  */
111
111
  nextsteps?: NextStepsToolConfig | undefined;
112
+ /**
113
+ * WrongProxy / WrongTrace: automatic base-URL rerouting through a
114
+ * local proxy daemon (default `http://localhost:3444`). When
115
+ * `enabled` is true AND the daemon at `url` is reachable, every
116
+ * provider's base URL is rewritten through
117
+ * `${url}/proxy/<host><path>`. openai-codex is excluded by spec.
118
+ *
119
+ * Mirrors the WebUI `LocalPrefs` shape (single object with two
120
+ * fields, not two top-level keys). Persisted to the encrypted
121
+ * profile config and mirrored into `ctx.meta` by the TUI settings
122
+ * adapter so the runtime probe can read it mid-session.
123
+ */
124
+ wrongProxy?: WrongProxyToolConfig | undefined;
125
+ }
126
+ /** WrongProxy / WrongTrace tool-config (`tools.wrongProxy`). */
127
+ export interface WrongProxyToolConfig {
128
+ /**
129
+ * Master switch. When true AND the daemon at `url` is reachable,
130
+ * every provider's base URL is rewritten through
131
+ * `${url}/proxy/<host><path>`. openai-codex is excluded by spec.
132
+ * Default: false.
133
+ */
134
+ enabled?: boolean | undefined;
135
+ /**
136
+ * Where the local proxy daemon listens. Default
137
+ * `http://localhost:3444`. The CLI's periodic probe targets
138
+ * `<url>/api/health`; a 2xx response flips the runtime's
139
+ * `active` flag.
140
+ */
141
+ url?: string | undefined;
112
142
  }
113
143
  /** Opt-in switch for the agent-callable `nextsteps` tool (`tools.nextsteps`). */
114
144
  export interface NextStepsToolConfig {
@@ -4,7 +4,7 @@ export type { ContentBlock, ImageBlock, TextBlock, ThinkingBlock, ToolResultBloc
4
4
  export { isImageBlock, isTextBlock, isToolResultBlock, isToolUseBlock } from './blocks.js';
5
5
  export type { Compactor, CompactReport } from './compactor.js';
6
6
  export type { AdaptiveConcurrencyConfig, AgentLearningConfig, AutonomyConfig, BrainConfig, BrainCouncilConfig, BrainCouncilVoterConfig, BrainModelEntry, CircuitBreakerRuntimeConfig, ConcreteTokenSavingTier, Config, ConfigLoader, ConfigStore, ContextConfig, CouncilPersonaDefinition, CouncilToolConfig, CouncilToolProfileDefinition, CustomModelDefinition, ExecDangerConfig, ExecToolConfig, FeaturesConfig, FleetChatVerbosity, FleetConfig, FleetSupervisorConfig, GitBehaviorConfig, HqClientConfig, IndexingConfig, InputHistoryConfig, LaunchConfig, LaunchMenuChoice, LogConfig, LoopDetectionConfig, MCPHealthConfig, MCPHealthThresholds, MCPServerConfig, ModelMatrixEntry, ModelRuntimeCacheConfig, ModelRuntimeConfig, ModelRuntimeParametersConfig, ModelRuntimeReasoningConfig, NextStepsToolConfig, PluginConfig, PluginManagerConfig, ProviderApiKey, ProviderConfig, SageConfig, SessionLoggingConfig, SkillsConfig, SyncCategory, SyncConfig, ThemePresetId, TokenSavingTier, ToolDescriptionMode, ToolDescriptionModeConfig, ToolResultRenderMode, ToolResultRenderModeConfig, ToolsConfig, } from './config.js';
7
- export { DEFAULT_TUI_THINKING_WORD, FLEET_CHAT_VERBOSITY_VALUES, MAX_TUI_THINKING_WORD_LENGTH, normalizeTokenSavingTier, normalizeTuiThinkingWord, resolveFleetChatVerbosity, resolveTokenSavingTier, THEME_PRESET_IDS, } from './config.js';
7
+ export { DEFAULT_TUI_THINKING_WORD, FLEET_CHAT_VERBOSITY_VALUES, MAX_TUI_THINKING_WORD_LENGTH, MAX_WRONGPROXY_URL_LENGTH, normalizeTokenSavingTier, normalizeTuiThinkingWord, resolveFleetChatVerbosity, resolveTokenSavingTier, THEME_PRESET_IDS, } from './config.js';
8
8
  export type { CompletedWorkEvidence, CompletedWorkSource, ContextEvidenceState, ContextFileEvidence, ContextIntentEvidence, ContextRepeatedReadEvidence, ToolEvidenceStatus, ToolOutputMetadata, } from './context-evidence.js';
9
9
  export type { ContextSnapshot, ContextWindowAggressiveOn, ContextWindowConfigLike, ContextWindowMode, ContextWindowModeId, ContextWindowModeSelectionId, ContextWindowPolicy, ContextWindowThresholds, DeprecatedContextWindowModeId, } from './context-window.js';
10
10
  export { CONTEXT_WINDOW_MODE_PINNED_META_KEY, CONTEXT_WINDOW_MODES, DEFAULT_CONTEXT_WINDOW_MODE_ID, DEPRECATED_CONTEXT_WINDOW_MODE_ALIASES, formatContextWindowModeList, getContextWindowMode, isContextWindowModeId, isContextWindowModeSelectionId, isDeprecatedContextWindowModeId, LARGE_WINDOW_DEEP_MODE_THRESHOLD, listContextWindowModes, normalizeContextWindowModeId, resolveContextWindowPolicy, } from './context-window.js';
@@ -49,6 +49,7 @@ function resolveTokenSavingTier(val, maxContext) {
49
49
  }
50
50
  var DEFAULT_TUI_THINKING_WORD = "thinking";
51
51
  var MAX_TUI_THINKING_WORD_LENGTH = 16;
52
+ var MAX_WRONGPROXY_URL_LENGTH = 2048;
52
53
  function normalizeTuiThinkingWord(value) {
53
54
  if (typeof value !== "string") return DEFAULT_TUI_THINKING_WORD;
54
55
  const word = value.trim();
@@ -911,7 +912,7 @@ function truncate(s, max) {
911
912
  }
912
913
 
913
914
  // src/types/quota-regex.ts
914
- 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;
915
+ 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|credit)[-_\s]*(?:quota|limit)(?![-_\w])|usage[-_\s]*(?:limit|quota)[-_\s]*(?:reached|exceeded|for)/i;
915
916
 
916
917
  // src/types/provider.ts
917
918
  var REASONING_EFFORT_LEVELS = [
@@ -947,7 +948,8 @@ function classifyProviderError(status, body, message) {
947
948
  if (status === 0) return "network";
948
949
  if (status === 408) return "timeout";
949
950
  if (status === 599) return "stream_hang";
950
- if (status === 402 || QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
951
+ if (QUOTA_EXHAUSTED_RE.test(text)) return "quota_exhausted";
952
+ if (status === 402) return "quota_exhausted";
951
953
  if (status === 429 && body?.message && body.type !== "rate_limit_exceeded" && RATE_LIMIT_EXCEEDED_RE.test(body.message)) {
952
954
  return "quota_exhausted";
953
955
  }
@@ -1367,6 +1369,7 @@ export {
1367
1369
  LARGE_WINDOW_DEEP_MODE_THRESHOLD,
1368
1370
  MALFORMED_ARG_MARKERS,
1369
1371
  MAX_TUI_THINKING_WORD_LENGTH,
1372
+ MAX_WRONGPROXY_URL_LENGTH,
1370
1373
  MEMORY_TYPE_LABELS,
1371
1374
  PROMPT_CATEGORY_LABELS,
1372
1375
  ParseError,
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Automatic proxy/trace rerouting for provider base URLs.
3
+ *
4
+ * Lives in `@wrongstack/core` so both `cli` and `runtime` packages can
5
+ * import the same pure-logic rewriter without reaching across workspaces.
6
+ * The CLI's `proxy-probe` (side-effectful, owns `setInterval`) lives in
7
+ * `@wrongstack/cli` and pushes state into this module via `applyProxyConfig`.
8
+ *
9
+ * Example:
10
+ * original = "https://api.openai.com/v1"
11
+ * proxyUrl = "http://localhost:3444"
12
+ * output = "http://localhost:3444/proxy/api.openai.com/v1"
13
+ *
14
+ * The host appears *without* a scheme in the path; the proxy terminates
15
+ * TLS (or speaks plain HTTP for localhost) and forwards the original
16
+ * scheme in the `X-Forwarded-Proto` header.
17
+ */
18
+ /**
19
+ * Providers whose base URLs MUST NOT be rewritten. openai-codex talks
20
+ * directly to the ChatGPT backend over an OAuth-issued token, and the
21
+ * `client_id` / audience in the token constrains the request origin —
22
+ * routing it through a generic proxy breaks the auth check.
23
+ */
24
+ export declare const PROXY_EXCLUDED_PROVIDERS: ReadonlySet<string>;
25
+ /**
26
+ * Decide whether a provider id is eligible for proxy rerouting.
27
+ *
28
+ * - `openai-codex` is excluded by spec.
29
+ * - Everything else (openai, anthropic, google, openai-compatible,
30
+ * custom saved-config aliases) flows through.
31
+ */
32
+ export declare function isProxyEligible(providerId: string): boolean;
33
+ /**
34
+ * Rewrite a provider base URL through the proxy.
35
+ *
36
+ * Returns the original input (passthrough) when the input is missing or
37
+ * malformed — we never want the proxy rewriter to be the reason a request
38
+ * fails when the proxy itself is misconfigured. The guards below are
39
+ * deliberately permissive: a misconfigured proxy must NOT silently turn
40
+ * into a hard error during provider construction.
41
+ */
42
+ export declare function rewriteBaseUrl(originalBaseUrl: string | undefined, proxyUrl: string | undefined): string | undefined;
43
+ /**
44
+ * Active proxy configuration consumed by `resolveProviderCfg`. Module-scoped
45
+ * so the wiring layer can read it without threading state through every
46
+ * call site; updates from `applyProxyConfig()` are atomic from the JS
47
+ * perspective (a single assignment).
48
+ */
49
+ export interface ProxyConfig {
50
+ /** Master switch — when false, no rewrite happens regardless of url. */
51
+ enabled: boolean;
52
+ /** Where the proxy listens. Empty string = unset (treated as disabled). */
53
+ url: string;
54
+ /** Last-known reachability state from the periodic probe. */
55
+ active: boolean;
56
+ }
57
+ /** Read the current proxy configuration. Safe to call from any layer. */
58
+ export declare function getProxyConfig(): ProxyConfig;
59
+ /**
60
+ * Listener notified by `applyProxyConfig` when the resulting config
61
+ * MATERIALLY changed (`enabled` / `url` / `active`). The periodic probe
62
+ * re-writes the same healthy values every tick, so a value-identical
63
+ * write must NOT notify — subscribers (e.g. the instant-apply provider
64
+ * rebuilder) would otherwise run every probe interval.
65
+ */
66
+ export type ProxyConfigListener = (next: ProxyConfig, previous: ProxyConfig) => void;
67
+ /**
68
+ * Subscribe to material proxy-config changes. Returns an unsubscribe
69
+ * function. A throwing listener is isolated: it cannot break the probe
70
+ * loop or starve other subscribers.
71
+ */
72
+ export declare function subscribeToProxyConfig(listener: ProxyConfigListener): () => void;
73
+ /**
74
+ * Apply a new proxy configuration. Returns the previous config so callers
75
+ * (notably the probe) can decide whether the change requires an immediate
76
+ * probe vs. waiting for the next tick. Notifies subscribers only when the
77
+ * merged result actually differs from the previous state.
78
+ */
79
+ export declare function applyProxyConfig(next: Partial<ProxyConfig>): ProxyConfig;
80
+ /**
81
+ * Convenience: should the rewriter run for a given provider id given the
82
+ * current configuration? Centralizes the "is proxy on AND active AND not
83
+ * excluded" rule so future call sites can't accidentally skip a check.
84
+ */
85
+ export declare function shouldRewriteFor(providerId: string): boolean;
86
+ /**
87
+ * Reset to defaults. Intended for tests; do NOT call from production code
88
+ * (the singleton lives for the lifetime of the process). Also drops all
89
+ * change listeners so tests never observe each other's notifications.
90
+ */
91
+ export declare function __resetProxyConfigForTests(): void;
92
+ /** Minimal structural logger — keeps this module dependency-free. */
93
+ export interface ProxyInstantApplyLogger {
94
+ info: (message: string) => void;
95
+ warn: (message: string) => void;
96
+ }
97
+ export interface ProxyInstantApplyDeps {
98
+ /** Current active provider id — the provider whose URL is baked in. */
99
+ getActiveProviderId: () => string;
100
+ /**
101
+ * Raw (pre-rewrite) base URL for a provider id, exactly as
102
+ * `resolveProviderCfg` reads it: `savedCfg?.baseUrl ?? config.baseUrl`.
103
+ * The singleton holds no provider configs, so the caller — who owns the
104
+ * live Config — must inject this reader.
105
+ */
106
+ getRawBaseUrl: (providerId: string) => string | undefined;
107
+ /**
108
+ * Rebuild the live provider for `providerId` and swap it into the live
109
+ * context. Callers inject their existing path — e.g.
110
+ * `buildProviderForModel(providerId, model)` — which re-resolves
111
+ * proxy-aware config via `shouldRewriteFor` on every build. Should
112
+ * serialize the swap through the host's model-transition gate and
113
+ * re-check the live provider id inside it (superseded guard).
114
+ */
115
+ rebuildProvider: (providerId: string) => Promise<void>;
116
+ /** Structured logger for the rebuild decisions. */
117
+ logger: ProxyInstantApplyLogger;
118
+ }
119
+ export interface ProxyInstantApplyHandle {
120
+ /** Detach the subscription. Safe to call more than once. */
121
+ dispose: () => void;
122
+ }
123
+ /**
124
+ * Subscribe to material proxy-config changes and rebuild the live
125
+ * provider when the routing verdict for the ACTIVE provider changes.
126
+ *
127
+ * Comparison is on the effective base URL, so:
128
+ * - probe ticks that rewrite identical values never rebuild;
129
+ * - a proxy URL change while enabled+active rebuilds (new target);
130
+ * - deactivation rebuilds (proxy-rewritten → raw);
131
+ * - toggling off while already direct does NOT rebuild;
132
+ * - a provider/model switch between notifications re-baselines against
133
+ * the PREVIOUS config — the config the switch actually built under —
134
+ * and then falls through to the verdict comparison. Seeding from the
135
+ * new config instead would swallow a needed rebuild: a provider that
136
+ * switched in while the proxy was on (built rewritten) must rebuild
137
+ * direct when the very next change deactivates the proxy.
138
+ *
139
+ * Rebuilds are SERIALIZED: a toggle-off immediately followed by a probe
140
+ * verdict must not race two async rebuilds against each other — the
141
+ * slower, stale build could overwrite the fresh provider swap. Each
142
+ * rebuild re-reads proxy state at build time, so the chain converges on
143
+ * the latest verdict.
144
+ */
145
+ export declare function createProxyInstantApply(deps: ProxyInstantApplyDeps): ProxyInstantApplyHandle;
146
+ //# sourceMappingURL=proxy-rewrite.d.ts.map
@@ -0,0 +1,132 @@
1
+ // src/wiring/proxy-rewrite.ts
2
+ var PROXY_PATH_PREFIX = "/proxy/";
3
+ var PROXY_EXCLUDED_PROVIDERS = /* @__PURE__ */ new Set(["openai-codex"]);
4
+ function isProxyEligible(providerId) {
5
+ if (!providerId) return false;
6
+ if (PROXY_EXCLUDED_PROVIDERS.has(providerId)) return false;
7
+ return true;
8
+ }
9
+ function rewriteBaseUrl(originalBaseUrl, proxyUrl) {
10
+ if (!originalBaseUrl) return void 0;
11
+ if (!proxyUrl) return originalBaseUrl;
12
+ if (!isProxyEligibleForRewrite(originalBaseUrl, proxyUrl)) return originalBaseUrl;
13
+ return composeRewrittenUrl(originalBaseUrl, proxyUrl);
14
+ }
15
+ function isProxyEligibleForRewrite(originalBaseUrl, proxyUrl) {
16
+ if (!originalBaseUrl.includes("://")) return false;
17
+ if (!proxyUrl.includes("://")) return false;
18
+ const normalizedProxy = proxyUrl.replace(/\/+$/, "");
19
+ if (originalBaseUrl.startsWith(`${normalizedProxy}${PROXY_PATH_PREFIX}`)) {
20
+ return false;
21
+ }
22
+ try {
23
+ const parsed = new URL(originalBaseUrl);
24
+ const isLoopback = parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || // WHATWG URL serializes IPv6 hosts with brackets — `[::1]`, not `::1`.
25
+ parsed.hostname === "[::1]";
26
+ if (isLoopback && parsed.port !== "") return false;
27
+ } catch {
28
+ }
29
+ return true;
30
+ }
31
+ function composeRewrittenUrl(originalBaseUrl, proxyUrl) {
32
+ const parsedOriginal = new URL(originalBaseUrl);
33
+ const hostAndPath = `${parsedOriginal.host}${parsedOriginal.pathname}`;
34
+ const trailingQuery = parsedOriginal.search || "";
35
+ const trailingHash = parsedOriginal.hash || "";
36
+ const proxyRoot = proxyUrl.replace(/\/+$/, "");
37
+ return `${proxyRoot}${PROXY_PATH_PREFIX}${hostAndPath}${trailingQuery}${trailingHash}`;
38
+ }
39
+ var DEFAULT_PROXY_CONFIG = { enabled: false, url: "", active: false };
40
+ var currentConfig = { ...DEFAULT_PROXY_CONFIG };
41
+ function getProxyConfig() {
42
+ return currentConfig;
43
+ }
44
+ var listeners = /* @__PURE__ */ new Set();
45
+ function subscribeToProxyConfig(listener) {
46
+ listeners.add(listener);
47
+ return () => {
48
+ listeners.delete(listener);
49
+ };
50
+ }
51
+ function applyProxyConfig(next) {
52
+ const previous = currentConfig;
53
+ currentConfig = {
54
+ enabled: next.enabled ?? previous.enabled,
55
+ url: next.url ?? previous.url,
56
+ active: next.active ?? previous.active
57
+ };
58
+ const materialChange = currentConfig.enabled !== previous.enabled || currentConfig.url !== previous.url || currentConfig.active !== previous.active;
59
+ if (materialChange) {
60
+ for (const listener of listeners) {
61
+ try {
62
+ listener(currentConfig, previous);
63
+ } catch {
64
+ }
65
+ }
66
+ }
67
+ return previous;
68
+ }
69
+ function shouldRewriteFor(providerId) {
70
+ const cfg = currentConfig;
71
+ if (!cfg.enabled || !cfg.active || !cfg.url) return false;
72
+ return isProxyEligible(providerId);
73
+ }
74
+ function __resetProxyConfigForTests() {
75
+ currentConfig = { ...DEFAULT_PROXY_CONFIG };
76
+ listeners.clear();
77
+ }
78
+ function effectiveBaseUrlFor(cfg, providerId, rawBaseUrl) {
79
+ if (!rawBaseUrl) return void 0;
80
+ const active = cfg.enabled && cfg.active && !!cfg.url && isProxyEligible(providerId);
81
+ return active ? rewriteBaseUrl(rawBaseUrl, cfg.url) : rawBaseUrl;
82
+ }
83
+ function effectiveBaseUrl(providerId, rawBaseUrl) {
84
+ return effectiveBaseUrlFor(currentConfig, providerId, rawBaseUrl);
85
+ }
86
+ function createProxyInstantApply(deps) {
87
+ const { getActiveProviderId, getRawBaseUrl, rebuildProvider, logger } = deps;
88
+ let lastProviderId = getActiveProviderId();
89
+ let lastEffectiveUrl = effectiveBaseUrl(lastProviderId, getRawBaseUrl(lastProviderId));
90
+ let disposed = false;
91
+ let rebuildChain = Promise.resolve();
92
+ const unsubscribe = subscribeToProxyConfig((next, previous) => {
93
+ if (disposed) return;
94
+ const providerId = getActiveProviderId();
95
+ const raw = getRawBaseUrl(providerId);
96
+ const nextUrl = effectiveBaseUrlFor(next, providerId, raw);
97
+ const baselineUrl = providerId === lastProviderId ? lastEffectiveUrl : effectiveBaseUrlFor(previous, providerId, raw);
98
+ lastProviderId = providerId;
99
+ lastEffectiveUrl = nextUrl;
100
+ if (nextUrl === baselineUrl) return;
101
+ rebuildChain = rebuildChain.then(() => rebuildProvider(providerId)).then(() => {
102
+ if (!disposed) {
103
+ logger.info(`WrongProxy routing changed \u2014 live provider rebuilt (${providerId})`);
104
+ }
105
+ }).catch((err) => {
106
+ if (!disposed) {
107
+ logger.warn(
108
+ `WrongProxy instant-apply: provider rebuild failed for ${providerId}: ${err instanceof Error ? err.message : String(err)}`
109
+ );
110
+ }
111
+ });
112
+ });
113
+ return {
114
+ dispose() {
115
+ if (disposed) return;
116
+ disposed = true;
117
+ unsubscribe();
118
+ }
119
+ };
120
+ }
121
+ export {
122
+ PROXY_EXCLUDED_PROVIDERS,
123
+ __resetProxyConfigForTests,
124
+ applyProxyConfig,
125
+ createProxyInstantApply,
126
+ getProxyConfig,
127
+ isProxyEligible,
128
+ rewriteBaseUrl,
129
+ shouldRewriteFor,
130
+ subscribeToProxyConfig
131
+ };
132
+ //# sourceMappingURL=proxy-rewrite.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/core",
3
- "version": "0.310.0",
3
+ "version": "0.313.0",
4
4
  "license": "MIT",
5
5
  "description": "WrongStack core: kernel, types, and shared utilities for the WrongStack CLI agent.",
6
6
  "repository": {
@@ -165,6 +165,10 @@
165
165
  "./replay": {
166
166
  "types": "./dist/replay/index.d.ts",
167
167
  "import": "./dist/replay/index.js"
168
+ },
169
+ "./wiring/proxy-rewrite": {
170
+ "types": "./dist/wiring/proxy-rewrite.d.ts",
171
+ "import": "./dist/wiring/proxy-rewrite.js"
168
172
  }
169
173
  },
170
174
  "files": [
@@ -178,9 +182,9 @@
178
182
  "wrongstackApiVersion": "0.1.10",
179
183
  "dependencies": {
180
184
  "zod": "4.4.3",
181
- "@wrongstack/primitives": "0.310.0",
182
- "@wrongstack/kanban": "0.310.0",
183
- "@wrongstack/persistence": "0.310.0"
185
+ "@wrongstack/kanban": "0.313.0",
186
+ "@wrongstack/primitives": "0.313.0",
187
+ "@wrongstack/persistence": "0.313.0"
184
188
  },
185
189
  "devDependencies": {
186
190
  "@types/node": "^22.19.0",