@wrongstack/core 0.309.1 → 0.310.1

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 (120) hide show
  1. package/dist/chronicle/index.js +113 -41
  2. package/dist/chronicle/metrics-ingest.d.ts +7 -0
  3. package/dist/chronicle/metrics-schema.d.ts +4 -1
  4. package/dist/chronicle/project-server.js +94 -38
  5. package/dist/chronicle/query.d.ts +1 -0
  6. package/dist/chronicle/sqlite-journal-schema.d.ts +2 -1
  7. package/dist/chronicle/types.d.ts +2 -0
  8. package/dist/{agent-status-helpers.d.ts → coordination/agent-status-helpers.d.ts} +1 -1
  9. package/dist/{agent-status-tracker.d.ts → coordination/agent-status-tracker.d.ts} +2 -2
  10. package/dist/{middleware → coordination}/collab-pause.d.ts +1 -1
  11. package/dist/coordination/director-kanban-queue-helpers.d.ts +1 -1
  12. package/dist/coordination/fleet-status-tool.d.ts +1 -1
  13. package/dist/coordination/index.d.ts +5 -1
  14. package/dist/coordination/index.js +2061 -346
  15. package/dist/coordination/kanban-dispatch-port.d.ts +30 -0
  16. package/dist/coordination/kanban-ops-port.d.ts +21 -0
  17. package/dist/coordination/mailbox-hooks.d.ts +1 -1
  18. package/dist/coordination/mailbox-project-server.js +14 -9
  19. package/dist/core/context.d.ts +35 -44
  20. package/dist/core/conversation-state.d.ts +16 -52
  21. package/dist/core/index.js +53 -16
  22. package/dist/core/provider-runner.d.ts +2 -2
  23. package/dist/core/run-env.d.ts +7 -28
  24. package/dist/core/streaming-response-builder.d.ts +2 -2
  25. package/dist/execution/index.js +111 -153
  26. package/dist/goal/index.js +100 -22
  27. package/dist/hq/auth-store.d.ts +9 -0
  28. package/dist/hq/cost-bridge.d.ts +1 -1
  29. package/dist/hq/index.js +24 -2
  30. package/dist/index.d.ts +12 -5
  31. package/dist/index.js +27808 -33937
  32. package/dist/infrastructure/index.js +210 -132
  33. package/dist/infrastructure/provider-cache-ledger.d.ts +1 -1
  34. package/dist/infrastructure/token-counter.d.ts +5 -1
  35. package/dist/kernel/events/file-events.d.ts +6 -0
  36. package/dist/kernel/events/provider-events.d.ts +10 -7
  37. package/dist/kernel/events/session-events.d.ts +4 -4
  38. package/dist/kernel/events/tool-events.d.ts +12 -2
  39. package/dist/plugin/index.js +105 -35
  40. package/dist/security/index.d.ts +1 -0
  41. package/dist/security/index.js +47 -18
  42. package/dist/security/kanban-boundary.d.ts +1 -1
  43. package/dist/security/kanban-governance-port.d.ts +28 -0
  44. package/dist/session-catalog/index.js +2 -3
  45. package/dist/session-catalog/project-server.js +2 -3
  46. package/dist/session-catalog/protocol.d.ts +1 -1
  47. package/dist/session-catalog/registry.d.ts +1 -1
  48. package/dist/session-catalog/store-schema.d.ts +1 -1
  49. package/dist/session-catalog/store.d.ts +1 -1
  50. package/dist/storage/annotations-store.d.ts +1 -1
  51. package/dist/storage/board-store-port.d.ts +45 -0
  52. package/dist/storage/completed-work-checkpoint.d.ts +1 -1
  53. package/dist/storage/config-loader/types.d.ts +1 -1
  54. package/dist/storage/event-bus-port.d.ts +27 -0
  55. package/dist/storage/file-session-writer.d.ts +47 -5
  56. package/dist/storage/goal-coordination.d.ts +1 -1
  57. package/dist/storage/goal-store.d.ts +1 -1
  58. package/dist/storage/index.d.ts +3 -4
  59. package/dist/storage/index.js +1063 -1459
  60. package/dist/storage/plan-store.d.ts +1 -1
  61. package/dist/storage/queue-store.d.ts +1 -1
  62. package/dist/storage/replay-log-store.d.ts +1 -1
  63. package/dist/storage/session-recovery.d.ts +10 -0
  64. package/dist/storage/session-resume-validation.d.ts +13 -1
  65. package/dist/storage/session-store/events.d.ts +1 -1
  66. package/dist/storage/session-store/load-session-data.d.ts +1 -1
  67. package/dist/storage/session-store/rename-session.d.ts +1 -1
  68. package/dist/storage/session-store/resume-session.d.ts +3 -1
  69. package/dist/storage/session-store/session-store-index.d.ts +2 -1
  70. package/dist/storage/session-store/types.d.ts +1 -1
  71. package/dist/storage/session-store.d.ts +70 -0
  72. package/dist/storage/session-summary-tracker.d.ts +8 -0
  73. package/dist/storage/session-write-buffer.d.ts +31 -2
  74. package/dist/storage/task-store.d.ts +1 -1
  75. package/dist/storage/todos-checkpoint.d.ts +1 -1
  76. package/dist/storage/tool-audit-log.d.ts +1 -1
  77. package/dist/tasking/index.js +100 -22
  78. package/dist/tasking/task-tracker.d.ts +24 -1
  79. package/dist/types/compactor.d.ts +2 -2
  80. package/dist/types/config/runtime.d.ts +7 -0
  81. package/dist/types/config/tools.d.ts +30 -0
  82. package/dist/types/context.d.ts +212 -0
  83. package/dist/types/conversation-state.d.ts +109 -0
  84. package/dist/types/error-handler.d.ts +2 -2
  85. package/dist/types/file-event-record.d.ts +6 -0
  86. package/dist/types/index.d.ts +4 -4
  87. package/dist/types/index.js +19 -1
  88. package/dist/types/permission.d.ts +3 -3
  89. package/dist/types/plugin.d.ts +3 -3
  90. package/dist/types/provider-runner.d.ts +2 -2
  91. package/dist/types/provider.d.ts +10 -0
  92. package/dist/types/run-env.d.ts +32 -0
  93. package/dist/types/session.d.ts +3 -0
  94. package/dist/types/slash-command.d.ts +2 -2
  95. package/dist/types/token-counter.d.ts +30 -1
  96. package/dist/types/tool-executor.d.ts +2 -2
  97. package/dist/types/tool.d.ts +5 -5
  98. package/dist/utils/context-breakdown.d.ts +2 -2
  99. package/dist/utils/context-evidence.d.ts +12 -12
  100. package/dist/utils/crash-shield.d.ts +9 -0
  101. package/dist/utils/heap-watchdog.js +11 -3
  102. package/dist/utils/index.d.ts +1 -1
  103. package/dist/utils/index.js +72 -156
  104. package/dist/utils/regex-guard.d.ts +7 -30
  105. package/dist/utils/todos-format.d.ts +1 -1
  106. package/dist/utils/tree-kill.d.ts +2 -0
  107. package/dist/utils/tree-kill.js +1 -0
  108. package/dist/wiring/proxy-rewrite.d.ts +76 -0
  109. package/dist/wiring/proxy-rewrite.js +70 -0
  110. package/instructions/coordination/subagent-baseline.md +10 -0
  111. package/instructions/system-lite.md +2 -0
  112. package/instructions/system-pro.md +40 -0
  113. package/instructions/system.md +15 -0
  114. package/package.json +10 -9
  115. package/dist/defaults/index.d.ts +0 -69
  116. package/dist/defaults/index.js +0 -38133
  117. /package/dist/{fleet-notifier.d.ts → coordination/fleet-notifier.d.ts} +0 -0
  118. /package/dist/{session-registry-atomic-file.d.ts → session-catalog/session-registry-atomic-file.d.ts} +0 -0
  119. /package/dist/{session-registry-types.d.ts → session-catalog/session-registry-types.d.ts} +0 -0
  120. /package/dist/{session-registry.d.ts → session-catalog/session-registry.d.ts} +0 -0
@@ -1,4 +1,4 @@
1
- import type { Context } from '../core/context.js';
1
+ import type { AgentContext } from './context.js';
2
2
  export type Permission = 'auto' | 'confirm' | 'deny';
3
3
  /**
4
4
  * Risk tier for tools in UI/audit surfaces. YOLO auto-approves non-denied
@@ -150,7 +150,7 @@ export interface Tool<I = unknown, O = unknown> {
150
150
  * Each UI maps this id to its own icon library.
151
151
  */
152
152
  icon?: ToolIconId | undefined;
153
- execute(input: I, ctx: Context, opts: {
153
+ execute(input: I, ctx: AgentContext, opts: {
154
154
  signal: AbortSignal;
155
155
  }): Promise<O>;
156
156
  /**
@@ -176,7 +176,7 @@ export interface Tool<I = unknown, O = unknown> {
176
176
  * have intermediate state shouldn't implement this; the default `execute`
177
177
  * path is more efficient.
178
178
  */
179
- executeStream?(input: I, ctx: Context, opts: {
179
+ executeStream?(input: I, ctx: AgentContext, opts: {
180
180
  signal: AbortSignal;
181
181
  }): AsyncIterable<ToolStreamEvent<O>>;
182
182
  /**
@@ -222,7 +222,7 @@ export interface Tool<I = unknown, O = unknown> {
222
222
  * Avoid double-free by gating one on the other's effect, or pick a single
223
223
  * teardown channel per resource.
224
224
  */
225
- cleanup?(input: I, ctx: Context): Promise<void>;
225
+ cleanup?(input: I, ctx: AgentContext): Promise<void>;
226
226
  /**
227
227
  * Optional custom output serializer. When present, the executor's output
228
228
  * serializer calls this INSTEAD of the central `renderToolObject()` switch
@@ -243,7 +243,7 @@ export interface ToolCallContext {
243
243
  tool: Tool;
244
244
  input: unknown;
245
245
  callId: string;
246
- ctx: Context;
246
+ ctx: AgentContext;
247
247
  signal: AbortSignal;
248
248
  }
249
249
  /**
@@ -1,4 +1,4 @@
1
- import type { Context } from '../core/context.js';
1
+ import type { AgentContext } from '../types/context.js';
2
2
  import { type SystemBlockSource } from '../core/system-prompt-builder.js';
3
3
  /**
4
4
  * Real, per-category token accounting for the live context window — the data
@@ -61,5 +61,5 @@ export interface ContextBreakdown {
61
61
  * interactively (e.g. from `/context`): it walks the full message array once, so
62
62
  * it is O(messages·blocks), not the O(1) cached path the per-turn bar uses.
63
63
  */
64
- export declare function getContextBreakdown(ctx: Context): ContextBreakdown;
64
+ export declare function getContextBreakdown(ctx: AgentContext): ContextBreakdown;
65
65
  //# sourceMappingURL=context-breakdown.d.ts.map
@@ -1,4 +1,4 @@
1
- import type { Context } from '../core/context.js';
1
+ import type { AgentContext } from '../types/context.js';
2
2
  import { type TextBlock } from '../types/blocks.js';
3
3
  import type { CompactReport } from '../types/compactor.js';
4
4
  import type { CompletedWorkEvidence, CompletedWorkSource, ContextEvidenceState, ToolOutputMetadata } from '../types/context-evidence.js';
@@ -13,7 +13,7 @@ export interface RecordToolOutputEvidenceInput {
13
13
  outputTokens?: number | undefined;
14
14
  outputLines?: number | undefined;
15
15
  }
16
- export declare function recordUserIntentEvidence(ctx: Context, text: string): void;
16
+ export declare function recordUserIntentEvidence(ctx: AgentContext, text: string): void;
17
17
  /** Runtime steering/status blocks must not displace real conversation turns. */
18
18
  export declare function isRuntimeContextInput(text: string): boolean;
19
19
  /**
@@ -25,11 +25,11 @@ export declare function isRuntimeContextInput(text: string): boolean;
25
25
  * It is volatile provider evidence, so it never grows the durable chat log or
26
26
  * invalidates the cached base prompt.
27
27
  */
28
- export declare function buildConversationContinuityBlock(ctx: Pick<Context, 'contextEvidence' | 'messages'>): TextBlock | undefined;
29
- export declare function recordToolOutputEvidence(ctx: Context, input: RecordToolOutputEvidenceInput): ToolOutputMetadata;
30
- export declare function markAssistantReferencedEvidence(ctx: Context, text: string): void;
31
- export declare function buildContextEvidenceDigest(ctx: Context): string;
32
- export declare function repeatedReadPressure(ctx: Context): number;
28
+ export declare function buildConversationContinuityBlock(ctx: Pick<AgentContext, 'contextEvidence' | 'messages'>): TextBlock | undefined;
29
+ export declare function recordToolOutputEvidence(ctx: AgentContext, input: RecordToolOutputEvidenceInput): ToolOutputMetadata;
30
+ export declare function markAssistantReferencedEvidence(ctx: AgentContext, text: string): void;
31
+ export declare function buildContextEvidenceDigest(ctx: AgentContext): string;
32
+ export declare function repeatedReadPressure(ctx: AgentContext): number;
33
33
  /**
34
34
  * Deterministic post-compaction sanity check. Cheap and local: records whether
35
35
  * the compacted context still carries an intent anchor and a tool/path trail.
@@ -37,7 +37,7 @@ export declare function repeatedReadPressure(ctx: Context): number;
37
37
  * only HybridCompactor did). Advisory on its own — pair with
38
38
  * `injectEvidenceFloor` to actually repair a flagged loss.
39
39
  */
40
- export declare function checkCompactionQuality(ctx: Context, opts: {
40
+ export declare function checkCompactionQuality(ctx: AgentContext, opts: {
41
41
  collapsedDigest?: string | undefined;
42
42
  evidenceDigest?: string | undefined;
43
43
  reduced: boolean;
@@ -50,7 +50,7 @@ export declare function checkCompactionQuality(ctx: Context, opts: {
50
50
  * no-op when quality is fine or there is no evidence to inject. Returns true
51
51
  * when it injected a block (so the caller re-estimates tokens).
52
52
  */
53
- export declare function injectEvidenceFloor(ctx: Context, quality: CompactReport['quality'] | undefined): boolean;
53
+ export declare function injectEvidenceFloor(ctx: AgentContext, quality: CompactReport['quality'] | undefined): boolean;
54
54
  /** Marker prefixing the ledger's system-prompt block (also used to find/replace it). */
55
55
  export declare const COMPLETED_WORK_LEDGER_MARKER = "[completed_work_ledger]";
56
56
  export interface RecordCompletedWorkInput {
@@ -68,14 +68,14 @@ export interface RecordCompletedWorkInput {
68
68
  * composer renders this state as a volatile tail block; the stable system-prompt
69
69
  * prefix is never mutated here.
70
70
  */
71
- export declare function recordCompletedWorkEvidence(ctx: Context, input: RecordCompletedWorkInput): CompletedWorkEvidence;
71
+ export declare function recordCompletedWorkEvidence(ctx: AgentContext, input: RecordCompletedWorkInput): CompletedWorkEvidence;
72
72
  /** Render the ledger's system-prompt block text (marker + newest entries). */
73
73
  export declare function formatCompletedWorkLedger(items: readonly CompletedWorkEvidence[]): string;
74
74
  /** Build the current volatile completed-work block without mutating the prompt. */
75
- export declare function buildCompletedWorkLedgerBlock(ctx: Context): TextBlock | undefined;
75
+ export declare function buildCompletedWorkLedgerBlock(ctx: AgentContext): TextBlock | undefined;
76
76
  /**
77
77
  * @deprecated Volatile state must be composed at request time. Kept as a
78
78
  * compatibility no-op for embedders importing the old helper.
79
79
  */
80
- export declare function syncCompletedWorkLedgerBlock(_ctx: Context): void;
80
+ export declare function syncCompletedWorkLedgerBlock(_ctx: AgentContext): void;
81
81
  //# sourceMappingURL=context-evidence.d.ts.map
@@ -1,3 +1,12 @@
1
+ /** Register a synchronous salvage hook. Returns an unregister function. */
2
+ export declare function addFatalSalvageHook(hook: () => void): () => void;
3
+ /**
4
+ * Run every registered salvage hook once. Swallows individual hook failures
5
+ * so one broken hook cannot skip the remaining ones. Safe to call multiple
6
+ * times — hooks are expected to be idempotent (a drained buffer stays
7
+ * drained).
8
+ */
9
+ export declare function runFatalSalvageSync(): void;
1
10
  interface CrashShieldTarget {
2
11
  on(event: 'unhandledRejection' | 'uncaughtException', listener: (reason: unknown) => void): unknown;
3
12
  off(event: 'unhandledRejection' | 'uncaughtException', listener: (reason: unknown) => void): unknown;
@@ -2,7 +2,10 @@
2
2
  import * as fs from "node:fs";
3
3
  import * as fsp2 from "node:fs/promises";
4
4
  import * as path3 from "node:path";
5
- import { PerformanceObserver, constants as performanceConstants } from "node:perf_hooks";
5
+ import {
6
+ PerformanceObserver,
7
+ constants as performanceConstants
8
+ } from "node:perf_hooks";
6
9
  import * as v8 from "node:v8";
7
10
 
8
11
  // src/utils/memory-flight-recorder.ts
@@ -308,7 +311,8 @@ function createMemoryFlightRecorder(options = {}) {
308
311
  const now = options.now ?? Date.now;
309
312
  const startedAt = now();
310
313
  const root = options.diagnosticsRoot ?? path2.join(wstackGlobalRoot(), "diagnostics", "memory");
311
- const enabled = options.enabled ?? (process.env["WRONGSTACK_MEMORY_FLIGHT_RECORDER"] !== "0" && process.env["NODE_ENV"] !== "test");
314
+ const isBunRuntime = typeof globalThis.Bun !== "undefined";
315
+ const enabled = options.enabled ?? (!isBunRuntime && process.env["WRONGSTACK_MEMORY_FLIGHT_RECORDER"] !== "0" && process.env["NODE_ENV"] !== "test");
312
316
  const writer = options.captureWriter ?? (enabled ? new ContinuousMemoryCaptureWriter(root) : disabledWriter(path2.join(root, `${timestampSlug()}-pid-${process.pid}`)));
313
317
  const observations = [];
314
318
  const windowMs = options.windowMs ?? DEFAULT_WINDOW_MS;
@@ -418,7 +422,11 @@ function defaultHeapLogPath() {
418
422
  function takeHeapSample() {
419
423
  const m = process.memoryUsage();
420
424
  const heap = v8.getHeapStatistics();
421
- const spaces = v8.getHeapSpaceStatistics();
425
+ let spaces = [];
426
+ try {
427
+ spaces = v8.getHeapSpaceStatistics();
428
+ } catch {
429
+ }
422
430
  const spaceUsed = (name) => spaces.find((space) => space.space_name === name)?.space_used_size ?? 0;
423
431
  const limit = heap.heap_size_limit || 0;
424
432
  let activeResources = 0;
@@ -7,7 +7,7 @@ export * from './config-backup.js';
7
7
  export * from './config-json.js';
8
8
  export { checkConnectivity, resetConnectivityCache } from './connectivity.js';
9
9
  export { type ContextBreakdown, getContextBreakdown } from './context-breakdown.js';
10
- export { type CrashShieldOptions, installCrashShield } from './crash-shield.js';
10
+ export { addFatalSalvageHook, runFatalSalvageSync, type CrashShieldOptions, installCrashShield, } from './crash-shield.js';
11
11
  export { buildCompletedWorkLedgerBlock, buildContextEvidenceDigest, COMPLETED_WORK_LEDGER_MARKER, createContextEvidenceState, formatCompletedWorkLedger, markAssistantReferencedEvidence, type RecordCompletedWorkInput, type RecordToolOutputEvidenceInput, recordCompletedWorkEvidence, recordToolOutputEvidence, recordUserIntentEvidence, repeatedReadPressure, syncCompletedWorkLedgerBlock, } from './context-evidence.js';
12
12
  export { type DeepMergeOptions, deepMerge, FORBIDDEN_PROTO_KEYS, isPrimitiveArray, } from './deep-merge.js';
13
13
  export * from './diff.js';
@@ -1915,6 +1915,22 @@ function getContextBreakdown(ctx) {
1915
1915
  // src/utils/crash-shield.ts
1916
1916
  var CRASH_STORM_WINDOW_MS = 6e4;
1917
1917
  var CRASH_STORM_LIMIT = 20;
1918
+ var fatalSalvageHooks = [];
1919
+ function addFatalSalvageHook(hook) {
1920
+ fatalSalvageHooks.push(hook);
1921
+ return () => {
1922
+ const idx = fatalSalvageHooks.indexOf(hook);
1923
+ if (idx >= 0) fatalSalvageHooks.splice(idx, 1);
1924
+ };
1925
+ }
1926
+ function runFatalSalvageSync() {
1927
+ for (const hook of [...fatalSalvageHooks]) {
1928
+ try {
1929
+ hook();
1930
+ } catch {
1931
+ }
1932
+ }
1933
+ }
1918
1934
  function isBrokenOutputConsumer(error) {
1919
1935
  if (typeof error !== "object" || error === null || !("code" in error)) return false;
1920
1936
  return error.code === "EPIPE" || error.code === "ECONNRESET" || error.code === "ECONNABORTED";
@@ -1950,6 +1966,7 @@ function installCrashShield(options = {}) {
1950
1966
  `error storm: >${CRASH_STORM_LIMIT} in ${CRASH_STORM_WINDOW_MS / 1e3}s \u2014 exiting`
1951
1967
  )
1952
1968
  );
1969
+ runFatalSalvageSync();
1953
1970
  exit(1);
1954
1971
  }
1955
1972
  };
@@ -2232,49 +2249,57 @@ async function expandGlob(pattern) {
2232
2249
  async function walk2(dir, pat) {
2233
2250
  let entries;
2234
2251
  try {
2235
- entries = await fsp.readdir(dir);
2252
+ entries = await fsp.readdir(dir, { withFileTypes: true });
2236
2253
  } catch {
2237
2254
  return;
2238
2255
  }
2239
- const firstGlob = pat.search(/[*?[[]/);
2240
- if (firstGlob < 0) {
2241
- const re = globToRegex(pat);
2256
+ if (pat.startsWith("**/")) {
2257
+ const rest = pat.slice(3);
2258
+ await walk2(dir, rest);
2242
2259
  for (const e of entries) {
2243
- if (re.test(e)) {
2244
- const full = `${dir}${SEP}${e}`;
2245
- results.add(abs ? resolve2(full) : full);
2260
+ if (e.isDirectory()) {
2261
+ const subDir = `${dir}${SEP}${e.name}`;
2262
+ await walk2(subDir, pat);
2246
2263
  }
2247
2264
  }
2248
2265
  return;
2249
2266
  }
2250
- const before = pat.slice(0, firstGlob);
2251
- const rest = pat.slice(firstGlob);
2252
- if (before.endsWith("**")) {
2253
- await walk2(dir, rest);
2267
+ if (pat === "**") {
2254
2268
  for (const e of entries) {
2255
- const full = `${dir}${SEP}${e}`;
2256
- try {
2257
- const stat4 = await fsp.stat(full);
2258
- if (stat4.isDirectory()) await walk2(full, rest);
2259
- } catch {
2269
+ const full = `${dir}${SEP}${e.name}`;
2270
+ results.add(abs ? resolve2(full) : full);
2271
+ if (e.isDirectory()) {
2272
+ await walk2(full, "**");
2260
2273
  }
2261
2274
  }
2262
- } else if (before === "") {
2263
- const re = globToRegex(rest);
2275
+ return;
2276
+ }
2277
+ const firstSlash = pat.indexOf("/");
2278
+ if (firstSlash < 0) {
2279
+ const re = globToRegex(pat);
2264
2280
  for (const e of entries) {
2265
- if (re.test(e)) {
2266
- const full = `${dir}${SEP}${e}`;
2281
+ if (re.test(e.name)) {
2282
+ const full = `${dir}${SEP}${e.name}`;
2267
2283
  results.add(abs ? resolve2(full) : full);
2268
2284
  }
2269
2285
  }
2286
+ return;
2287
+ }
2288
+ const currentSeg = pat.slice(0, firstSlash);
2289
+ const remainingPat = pat.slice(firstSlash + 1);
2290
+ if (isGlob(currentSeg)) {
2291
+ const re = globToRegex(currentSeg);
2292
+ for (const e of entries) {
2293
+ if (e.isDirectory() && re.test(e.name)) {
2294
+ const subDir = `${dir}${SEP}${e.name}`;
2295
+ await walk2(subDir, remainingPat);
2296
+ }
2297
+ }
2270
2298
  } else {
2271
- const seg = before.replace(/[*?[\]]/g, "").replace(/\/$/, "");
2272
- if (entries.includes(seg)) {
2273
- const full = `${dir}${SEP}${seg}`;
2274
- try {
2275
- const stat4 = await fsp.stat(full);
2276
- if (stat4.isDirectory()) await walk2(full, rest);
2277
- } catch {
2299
+ for (const e of entries) {
2300
+ if (e.isDirectory() && e.name === currentSeg) {
2301
+ const subDir = `${dir}${SEP}${e.name}`;
2302
+ await walk2(subDir, remainingPat);
2278
2303
  }
2279
2304
  }
2280
2305
  }
@@ -2379,7 +2404,10 @@ function matchAnyCommand(patterns, input) {
2379
2404
  import * as fs4 from "node:fs";
2380
2405
  import * as fsp3 from "node:fs/promises";
2381
2406
  import * as path5 from "node:path";
2382
- import { PerformanceObserver, constants as performanceConstants } from "node:perf_hooks";
2407
+ import {
2408
+ PerformanceObserver,
2409
+ constants as performanceConstants
2410
+ } from "node:perf_hooks";
2383
2411
  import * as v8 from "node:v8";
2384
2412
 
2385
2413
  // src/utils/memory-flight-recorder.ts
@@ -2696,8 +2724,8 @@ async function pruneOldSessions(root, currentDir) {
2696
2724
  const sessions = await Promise.all(
2697
2725
  entries.filter((entry) => entry.isDirectory() && entry.name !== path4.basename(currentDir)).map(async (entry) => {
2698
2726
  const fullPath = path4.join(root, entry.name);
2699
- const stat4 = await fsp2.stat(fullPath);
2700
- return { fullPath, mtimeMs: stat4.mtimeMs };
2727
+ const stat3 = await fsp2.stat(fullPath);
2728
+ return { fullPath, mtimeMs: stat3.mtimeMs };
2701
2729
  })
2702
2730
  );
2703
2731
  sessions.sort((a, b) => b.mtimeMs - a.mtimeMs);
@@ -2815,7 +2843,8 @@ function createMemoryFlightRecorder(options = {}) {
2815
2843
  const now = options.now ?? Date.now;
2816
2844
  const startedAt = now();
2817
2845
  const root = options.diagnosticsRoot ?? path4.join(wstackGlobalRoot(), "diagnostics", "memory");
2818
- const enabled = options.enabled ?? (process.env["WRONGSTACK_MEMORY_FLIGHT_RECORDER"] !== "0" && process.env["NODE_ENV"] !== "test");
2846
+ const isBunRuntime = typeof globalThis.Bun !== "undefined";
2847
+ const enabled = options.enabled ?? (!isBunRuntime && process.env["WRONGSTACK_MEMORY_FLIGHT_RECORDER"] !== "0" && process.env["NODE_ENV"] !== "test");
2819
2848
  const writer = options.captureWriter ?? (enabled ? new ContinuousMemoryCaptureWriter(root) : disabledWriter(path4.join(root, `${timestampSlug()}-pid-${process.pid}`)));
2820
2849
  const observations = [];
2821
2850
  const windowMs = options.windowMs ?? DEFAULT_WINDOW_MS;
@@ -2925,7 +2954,11 @@ function defaultHeapLogPath() {
2925
2954
  function takeHeapSample() {
2926
2955
  const m = process.memoryUsage();
2927
2956
  const heap = v8.getHeapStatistics();
2928
- const spaces = v8.getHeapSpaceStatistics();
2957
+ let spaces = [];
2958
+ try {
2959
+ spaces = v8.getHeapSpaceStatistics();
2960
+ } catch {
2961
+ }
2929
2962
  const spaceUsed = (name) => spaces.find((space) => space.space_name === name)?.space_used_size ?? 0;
2930
2963
  const limit = heap.heap_size_limit || 0;
2931
2964
  let activeResources = 0;
@@ -4270,130 +4303,11 @@ function watchProjectTree(root, listener, opts) {
4270
4303
  }
4271
4304
 
4272
4305
  // src/utils/regex-guard.ts
4273
- var MAX_PATTERN_LEN = 256;
4274
- var DANGEROUS_PATTERNS = [
4275
- // (a+)+, (.*)+, etc — nested quantifier on a group with internal quantifier
4276
- /(\([^)]*[+*][^)]*\))[+*]/,
4277
- /(\(\?:[^)]*[+*][^)]*\))[+*]/,
4278
- // Adjacent quantifiers: a++ a*+
4279
- /[+*]{2,}/,
4280
- // Quantifier on alternation with length 2+
4281
- /\([^|)]+\|[^)]+\)[+*][+*]/,
4282
- // Greedy quantifier inside lookahead/lookbehind — (?!.*a+)
4283
- /[([][^)\]]*[+*][^)\]]*[)\]][^)]*\?\??/
4284
- ];
4285
- function hasAmbiguousQuantifiedAlternation(pattern) {
4286
- for (let i = 0; i < pattern.length; i++) {
4287
- if (pattern[i] !== "(") continue;
4288
- if (i > 0 && pattern[i - 1] === "\\") continue;
4289
- let depth = 0;
4290
- let inClass = false;
4291
- let j = i;
4292
- for (; j < pattern.length; j++) {
4293
- const ch = pattern[j];
4294
- if (ch === "\\") {
4295
- j++;
4296
- continue;
4297
- }
4298
- if (inClass) {
4299
- if (ch === "]") inClass = false;
4300
- continue;
4301
- }
4302
- if (ch === "[") {
4303
- inClass = true;
4304
- continue;
4305
- }
4306
- if (ch === "(") depth++;
4307
- else if (ch === ")") {
4308
- depth--;
4309
- if (depth === 0) break;
4310
- }
4311
- }
4312
- if (j >= pattern.length) return false;
4313
- const next = pattern[j + 1];
4314
- if (next !== "+" && next !== "*" && next !== "{") continue;
4315
- let inner = pattern.slice(i + 1, j);
4316
- inner = inner.replace(/^\?(?::|<?[=!])/u, "");
4317
- const branches = [];
4318
- let current = "";
4319
- let d = 0;
4320
- let cls = false;
4321
- for (let k = 0; k < inner.length; k++) {
4322
- const ch = inner[k];
4323
- if (ch === "\\") {
4324
- current += ch + (inner[k + 1] ?? "");
4325
- k++;
4326
- continue;
4327
- }
4328
- if (cls) {
4329
- if (ch === "]") cls = false;
4330
- current += ch;
4331
- continue;
4332
- }
4333
- if (ch === "[") {
4334
- cls = true;
4335
- current += ch;
4336
- continue;
4337
- }
4338
- if (ch === "(") d++;
4339
- if (ch === ")") d--;
4340
- if (ch === "|" && d === 0) {
4341
- branches.push(current);
4342
- current = "";
4343
- continue;
4344
- }
4345
- current += ch;
4346
- }
4347
- branches.push(current);
4348
- if (branches.length < 2) continue;
4349
- for (let a = 0; a < branches.length; a++) {
4350
- for (let b = a + 1; b < branches.length; b++) {
4351
- const x = branches[a];
4352
- const y = branches[b];
4353
- if (x === "" || y === "") return true;
4354
- if (x === y || x.startsWith(y) || y.startsWith(x)) return true;
4355
- }
4356
- }
4357
- }
4358
- return false;
4359
- }
4360
- function compileUserRegex(pattern, flags) {
4361
- if (typeof pattern !== "string") {
4362
- return { ok: false, reason: "pattern must be a string" };
4363
- }
4364
- if (pattern.length === 0) {
4365
- return { ok: false, reason: "pattern is empty" };
4366
- }
4367
- if (pattern.length > MAX_PATTERN_LEN) {
4368
- return { ok: false, reason: `pattern exceeds ${MAX_PATTERN_LEN} characters` };
4369
- }
4370
- for (const rx of DANGEROUS_PATTERNS) {
4371
- if (rx.test(pattern)) {
4372
- return {
4373
- ok: false,
4374
- reason: "pattern looks vulnerable to catastrophic backtracking \u2014 rewrite without nested quantifiers"
4375
- };
4376
- }
4377
- }
4378
- if (hasAmbiguousQuantifiedAlternation(pattern)) {
4379
- return {
4380
- ok: false,
4381
- reason: "pattern quantifies an alternation with overlapping branches \u2014 rewrite so no two branches can match the same text"
4382
- };
4383
- }
4384
- try {
4385
- return { ok: true, regex: new RegExp(pattern, flags) };
4386
- } catch (err) {
4387
- return {
4388
- ok: false,
4389
- reason: err instanceof Error ? err.message : "invalid regex"
4390
- };
4391
- }
4392
- }
4393
- var MAX_SUBJECT_LEN = 64 * 1024;
4394
- function capSubject(line) {
4395
- return line.length > MAX_SUBJECT_LEN ? line.slice(0, MAX_SUBJECT_LEN) : line;
4396
- }
4306
+ import {
4307
+ capSubject,
4308
+ compileUserRegex,
4309
+ MAX_SUBJECT_LEN
4310
+ } from "@wrongstack/primitives";
4397
4311
 
4398
4312
  // src/utils/safe-json.ts
4399
4313
  function safeParse(input, maxBytes = 5e6) {
@@ -5799,6 +5713,7 @@ export {
5799
5713
  WIRE_TOOL_NAME_MAX_LENGTH,
5800
5714
  WIRE_TOOL_NAME_PATTERN,
5801
5715
  activateProjectStateGuard,
5716
+ addFatalSalvageHook,
5802
5717
  applyToolDescriptionModeToTool,
5803
5718
  applyToolDescriptionModes,
5804
5719
  applyToolResultRenderModes,
@@ -5933,6 +5848,7 @@ export {
5933
5848
  resolveToolDescriptionMode,
5934
5849
  resolveToolResultRenderMode,
5935
5850
  resolveWstackPaths,
5851
+ runFatalSalvageSync,
5936
5852
  safeEmit,
5937
5853
  safeParse,
5938
5854
  safeProfileName,
@@ -1,35 +1,12 @@
1
1
  /**
2
2
  * Compile a user-supplied regex with conservative bounds against ReDoS.
3
3
  *
4
- * Duplicated from @wrongstack/tools/_regex.ts to avoid a circular
5
- * dependency (tools depends on core, not vice versa). There is a THIRD copy
6
- * `@wrongstack/kanban/src/verification/safe-regex.ts`which sits below both
7
- * in the workspace DAG for the same reason. This header used to say "keep
8
- * both copies in sync", unaware of the third, and this copy had drifted the
9
- * furthest: 2 of the 5 heuristics, a 512-character cap instead of 256, and no
10
- * subject cap at all. `packages/tools/tests/regex-guard-parity.test.ts` now
11
- * holds all three to the same verdicts — change one, change all three.
12
- *
13
- * V8's regex engine is backtracking-based and cannot interrupt a
14
- * synchronous match — a pattern like `(a+)+$` against a sufficiently
15
- * long line will pin a worker for seconds.
16
- */
17
- export interface CompileResult {
18
- ok: true;
19
- regex: RegExp;
20
- }
21
- export interface CompileFail {
22
- ok: false;
23
- reason: string;
24
- }
25
- export declare function compileUserRegex(pattern: string, flags: string): CompileResult | CompileFail;
26
- /**
27
- * Truncate a subject line to a safe length for synchronous regex eval.
28
- *
29
- * The canonical copy has always shipped this; this one did not, so callers
30
- * here bounded the pattern but never the SUBJECT — and a linear-time pattern
31
- * over a multi-megabyte line is still a stall.
4
+ * Canonical home since card #5: `@wrongstack/primitives/src/regex-guard.ts`
5
+ * (a dependency leaf, so every workspace tier including kanban, which sits
6
+ * below core and tools can share one implementation). This module is now a
7
+ * re-export shim: importers of `@wrongstack/core/utils` keep working, and
8
+ * `tools/tests/regex-guard-parity.test.ts` still pins all three historical
9
+ * entry points to identical verdicts (now trivially true one code path).
32
10
  */
33
- export declare const MAX_SUBJECT_LEN: number;
34
- export declare function capSubject(line: string): string;
11
+ export { type CompileFail, type CompileResult, capSubject, compileUserRegex, MAX_SUBJECT_LEN, } from '@wrongstack/primitives';
35
12
  //# sourceMappingURL=regex-guard.d.ts.map
@@ -1,4 +1,4 @@
1
- import type { TodoItem } from '../core/context.js';
1
+ import type { TodoItem } from '../types/context.js';
2
2
  /**
3
3
  * Canonical text rendering of the live todo list, shared by the CLI's
4
4
  * `/todos` slash command and the TUI's auto-echo (which prints the same
@@ -5,6 +5,8 @@
5
5
  */
6
6
  export interface KillableChild {
7
7
  readonly pid?: number | undefined;
8
+ /** Non-null once Node has observed that the child exited. */
9
+ readonly exitCode?: number | null | undefined;
8
10
  kill(signal?: NodeJS.Signals | number): boolean | void;
9
11
  }
10
12
  export interface TreeKillOptions {
@@ -34,6 +34,7 @@ function treeKill(child, opts = {}) {
34
34
  } catch {
35
35
  }
36
36
  setTimeout(() => {
37
+ if (child.exitCode !== void 0 && child.exitCode !== null) return;
37
38
  try {
38
39
  child.kill("SIGKILL");
39
40
  } catch {
@@ -0,0 +1,76 @@
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:8000"
12
+ * output = "http://localhost:8000/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
+ * Apply a new proxy configuration. Returns the previous config so callers
61
+ * (notably the probe) can decide whether the change requires an immediate
62
+ * probe vs. waiting for the next tick.
63
+ */
64
+ export declare function applyProxyConfig(next: Partial<ProxyConfig>): ProxyConfig;
65
+ /**
66
+ * Convenience: should the rewriter run for a given provider id given the
67
+ * current configuration? Centralizes the "is proxy on AND active AND not
68
+ * excluded" rule so future call sites can't accidentally skip a check.
69
+ */
70
+ export declare function shouldRewriteFor(providerId: string): boolean;
71
+ /**
72
+ * Reset to defaults. Intended for tests; do NOT call from production code
73
+ * (the singleton lives for the lifetime of the process).
74
+ */
75
+ export declare function __resetProxyConfigForTests(): void;
76
+ //# sourceMappingURL=proxy-rewrite.d.ts.map