@defai.digital/automatosx 11.3.1 → 11.3.3

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.
package/dist/mcp/index.js CHANGED
@@ -3,6 +3,7 @@ import * as path4 from 'path';
3
3
  import path4__default, { dirname, join, extname as extname$1, basename, resolve, relative, isAbsolute, sep, parse, delimiter } from 'path';
4
4
  import { fileURLToPath } from 'url';
5
5
  import { mkdir, appendFile, readFile, readdir, writeFile, rename, unlink, copyFile, access, stat, realpath } from 'fs/promises';
6
+ import * as fs3 from 'fs';
6
7
  import { existsSync, readFileSync, promises, mkdirSync, createWriteStream, writeFileSync, unlinkSync, constants } from 'fs';
7
8
  import Database2 from 'better-sqlite3';
8
9
  import { glob } from 'glob';
@@ -23,9 +24,7 @@ import { randomUUID } from 'crypto';
23
24
  import yaml from 'yaml';
24
25
 
25
26
  var __defProp = Object.defineProperty;
26
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
27
27
  var __getOwnPropNames = Object.getOwnPropertyNames;
28
- var __hasOwnProp = Object.prototype.hasOwnProperty;
29
28
  var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
30
29
  get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
31
30
  }) : x)(function(x) {
@@ -39,15 +38,6 @@ var __export = (target, all) => {
39
38
  for (var name in all)
40
39
  __defProp(target, name, { get: all[name], enumerable: true });
41
40
  };
42
- var __copyProps = (to, from, except, desc) => {
43
- if (from && typeof from === "object" || typeof from === "function") {
44
- for (let key of __getOwnPropNames(from))
45
- if (!__hasOwnProp.call(to, key) && key !== except)
46
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
47
- }
48
- return to;
49
- };
50
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
51
41
  var init_esm_shims = __esm({
52
42
  "node_modules/tsup/assets/esm_shims.js"() {
53
43
  }
@@ -328,16 +318,16 @@ var init_errors = __esm({
328
318
  constructor(message, code = "E1001" /* CONFIG_INVALID */, suggestions = [], context) {
329
319
  super(message, code, suggestions, context);
330
320
  }
331
- static notFound(path5) {
321
+ static notFound(path6) {
332
322
  return new _ConfigError(
333
- `Configuration file not found: ${path5}`,
323
+ `Configuration file not found: ${path6}`,
334
324
  "E1000" /* CONFIG_NOT_FOUND */,
335
325
  [
336
326
  'Run "automatosx setup" to create a new configuration',
337
327
  "Specify a custom config path with --config option",
338
328
  "Check that you are in a valid AutomatosX project directory"
339
329
  ],
340
- { path: path5 }
330
+ { path: path6 }
341
331
  );
342
332
  }
343
333
  static invalid(reason, context) {
@@ -352,7 +342,7 @@ var init_errors = __esm({
352
342
  context
353
343
  );
354
344
  }
355
- static parseError(error, path5) {
345
+ static parseError(error, path6) {
356
346
  return new _ConfigError(
357
347
  `Failed to parse configuration: ${error.message}`,
358
348
  "E1002" /* CONFIG_PARSE_ERROR */,
@@ -361,7 +351,7 @@ var init_errors = __esm({
361
351
  "Use a JSON validator to find syntax errors",
362
352
  'Reset to default with "automatosx setup --force"'
363
353
  ],
364
- { path: path5, originalError: error.message }
354
+ { path: path6, originalError: error.message }
365
355
  );
366
356
  }
367
357
  };
@@ -1174,11 +1164,11 @@ var init_workspace_indexer = __esm({
1174
1164
  /**
1175
1165
  * Detect file type and language
1176
1166
  */
1177
- detectTypeAndLanguage(path5, ext) {
1178
- if (path5.match(/package\.json|tsconfig\.json|\.config\.(js|ts|json|yaml|yml)|\.eslintrc|\.prettierrc/)) {
1167
+ detectTypeAndLanguage(path6, ext) {
1168
+ if (path6.match(/package\.json|tsconfig\.json|\.config\.(js|ts|json|yaml|yml)|\.eslintrc|\.prettierrc/)) {
1179
1169
  return { type: "config", language: "json" };
1180
1170
  }
1181
- if (path5.match(/\.(test|spec)\.(ts|js|tsx|jsx)$/) || path5.includes("__tests__")) {
1171
+ if (path6.match(/\.(test|spec)\.(ts|js|tsx|jsx)$/) || path6.includes("__tests__")) {
1182
1172
  const lang = LANGUAGE_MAP[ext];
1183
1173
  return { type: "test", language: lang };
1184
1174
  }
@@ -1195,15 +1185,15 @@ var init_workspace_indexer = __esm({
1195
1185
  *
1196
1186
  * Higher score = more likely to be relevant
1197
1187
  */
1198
- calculateImportance(path5, type, size) {
1188
+ calculateImportance(path6, type, size) {
1199
1189
  let score = 0.5;
1200
- if (path5.match(/^(index|main|app)\.(ts|js|tsx|jsx)$/)) score = 1;
1201
- if (path5.match(/^src\/(index|main|app)\.(ts|js|tsx|jsx)$/)) score = 0.95;
1202
- if (path5.match(/^src\/cli\/(index|main)\.(ts|js)$/)) score = 0.9;
1190
+ if (path6.match(/^(index|main|app)\.(ts|js|tsx|jsx)$/)) score = 1;
1191
+ if (path6.match(/^src\/(index|main|app)\.(ts|js|tsx|jsx)$/)) score = 0.95;
1192
+ if (path6.match(/^src\/cli\/(index|main)\.(ts|js)$/)) score = 0.9;
1203
1193
  if (type === "config") score = 0.85;
1204
- if (path5.includes("/core/")) score += 0.15;
1205
- if (path5.includes("/types/")) score += 0.1;
1206
- if (path5.includes("/utils/")) score += 0.05;
1194
+ if (path6.includes("/core/")) score += 0.15;
1195
+ if (path6.includes("/types/")) score += 0.1;
1196
+ if (path6.includes("/utils/")) score += 0.05;
1207
1197
  if (type === "test") score = Math.max(0.3, score - 0.3);
1208
1198
  if (type === "doc") score = Math.max(0.4, score - 0.2);
1209
1199
  if (size > 1e4) score += 0.05;
@@ -1679,10 +1669,10 @@ function findOnPathUnix(cmdBase) {
1679
1669
  try {
1680
1670
  const which = spawnSync("which", [cmdBase], { timeout: 3e3 });
1681
1671
  if (which.status === 0) {
1682
- const path5 = which.stdout.toString().trim();
1683
- if (path5) {
1684
- logger.debug("Found via which", { cmdBase, path: path5 });
1685
- return { found: true, path: path5 };
1672
+ const path6 = which.stdout.toString().trim();
1673
+ if (path6) {
1674
+ logger.debug("Found via which", { cmdBase, path: path6 });
1675
+ return { found: true, path: path6 };
1686
1676
  }
1687
1677
  }
1688
1678
  } catch (error) {
@@ -1723,8 +1713,8 @@ var init_provider_schemas = __esm({
1723
1713
  completion: z.number().int().nonnegative(),
1724
1714
  total: z.number().int().nonnegative()
1725
1715
  }).refine(
1726
- (data) => data.total === data.prompt + data.completion,
1727
- "Total tokens must equal prompt + completion"
1716
+ (data) => data.total >= data.prompt + data.completion,
1717
+ "Total tokens must be greater than or equal to prompt + completion"
1728
1718
  ).describe("Token usage information");
1729
1719
  finishReasonSchema = z.enum([
1730
1720
  "stop",
@@ -1981,15 +1971,6 @@ var init_streaming_progress_parser = __esm({
1981
1971
  });
1982
1972
 
1983
1973
  // src/providers/error-patterns.ts
1984
- var error_patterns_exports = {};
1985
- __export(error_patterns_exports, {
1986
- GENERIC_ERROR_PATTERNS: () => GENERIC_ERROR_PATTERNS,
1987
- PROVIDER_ERROR_PATTERNS: () => PROVIDER_ERROR_PATTERNS,
1988
- getProviderErrorPatterns: () => getProviderErrorPatterns,
1989
- isLimitError: () => isLimitError,
1990
- isQuotaError: () => isQuotaError,
1991
- isRateLimitError: () => isRateLimitError
1992
- });
1993
1974
  function isQuotaError(error, providerName) {
1994
1975
  const patterns = PROVIDER_ERROR_PATTERNS[providerName] || GENERIC_ERROR_PATTERNS;
1995
1976
  const message = (error?.message || "").toLowerCase();
@@ -2031,9 +2012,6 @@ function isRateLimitError(error, providerName) {
2031
2012
  function isLimitError(error, providerName) {
2032
2013
  return isQuotaError(error, providerName) || isRateLimitError(error, providerName);
2033
2014
  }
2034
- function getProviderErrorPatterns(providerName) {
2035
- return PROVIDER_ERROR_PATTERNS[providerName] || GENERIC_ERROR_PATTERNS;
2036
- }
2037
2015
  var PROVIDER_ERROR_PATTERNS, GENERIC_ERROR_PATTERNS;
2038
2016
  var init_error_patterns = __esm({
2039
2017
  "src/providers/error-patterns.ts"() {
@@ -2222,6 +2200,62 @@ var init_error_patterns = __esm({
2222
2200
  "quota_exceeded",
2223
2201
  "billing_hard_limit_reached"
2224
2202
  ]
2203
+ },
2204
+ /**
2205
+ * ax-cli Provider Error Patterns (v9.2.0)
2206
+ *
2207
+ * ax-cli is a multi-model provider supporting GLM, xAI, OpenAI, Anthropic, Ollama, etc.
2208
+ * BUG FIX: Added missing patterns - previously fell back to generic patterns which
2209
+ * may miss provider-specific errors from the underlying model providers.
2210
+ *
2211
+ * Combines patterns from all supported backends since ax-cli proxies to them.
2212
+ */
2213
+ "ax-cli": {
2214
+ quota: [
2215
+ // GLM patterns
2216
+ "quota exceeded",
2217
+ "quota limit reached",
2218
+ "insufficient quota",
2219
+ // OpenAI patterns (via ax-cli)
2220
+ "insufficient_quota",
2221
+ "quota_exceeded",
2222
+ "billing hard limit reached",
2223
+ "usage limit exceeded",
2224
+ "monthly quota exceeded",
2225
+ "credit limit reached",
2226
+ // Anthropic patterns (via ax-cli)
2227
+ "credit limit reached",
2228
+ // Generic patterns
2229
+ "daily quota exceeded",
2230
+ "api quota exceeded"
2231
+ ],
2232
+ rateLimit: [
2233
+ // Common patterns
2234
+ "rate_limit_exceeded",
2235
+ "rate limit exceeded",
2236
+ "too_many_requests",
2237
+ "too many requests",
2238
+ "requests per minute exceeded",
2239
+ "tokens per minute exceeded",
2240
+ "rate limit reached",
2241
+ // Anthropic patterns (via ax-cli)
2242
+ "rate_limit_error",
2243
+ "overloaded_error",
2244
+ "overloaded",
2245
+ // xAI/Grok patterns
2246
+ "throttled",
2247
+ "request throttled"
2248
+ ],
2249
+ statusCodes: [429, 529],
2250
+ errorCodes: [
2251
+ "insufficient_quota",
2252
+ "rate_limit_exceeded",
2253
+ "quota_exceeded",
2254
+ "rate_limit_error",
2255
+ "overloaded_error",
2256
+ "RATE_LIMIT_EXCEEDED",
2257
+ "QUOTA_EXCEEDED"
2258
+ ]
2225
2259
  }
2226
2260
  };
2227
2261
  GENERIC_ERROR_PATTERNS = {
@@ -2244,6 +2278,105 @@ var init_error_patterns = __esm({
2244
2278
  };
2245
2279
  }
2246
2280
  });
2281
+
2282
+ // src/providers/retry-errors.ts
2283
+ function containsErrorPattern(message, patterns) {
2284
+ const lowerMessage = message.toLowerCase();
2285
+ return patterns.some((pattern) => lowerMessage.includes(pattern.toLowerCase()));
2286
+ }
2287
+ function getRetryableErrors(provider) {
2288
+ const baseErrors = [
2289
+ ...COMMON_NETWORK_ERRORS,
2290
+ ...COMMON_RATE_LIMIT_ERRORS,
2291
+ ...COMMON_SERVER_ERRORS
2292
+ ];
2293
+ switch (provider) {
2294
+ case "claude":
2295
+ return [...baseErrors, ...CLAUDE_RETRYABLE_ERRORS];
2296
+ case "gemini":
2297
+ return [...baseErrors, ...GEMINI_RETRYABLE_ERRORS];
2298
+ case "openai":
2299
+ return [...baseErrors, ...OPENAI_RETRYABLE_ERRORS];
2300
+ case "ax-cli":
2301
+ return [...baseErrors, ...AX_CLI_RETRYABLE_ERRORS];
2302
+ case "codex":
2303
+ return [...baseErrors, ...CODEX_RETRYABLE_ERRORS];
2304
+ case "base":
2305
+ default:
2306
+ return baseErrors;
2307
+ }
2308
+ }
2309
+ function shouldRetryError(error, provider) {
2310
+ const message = error.message;
2311
+ if (containsErrorPattern(message, NON_RETRYABLE_ERRORS)) {
2312
+ return false;
2313
+ }
2314
+ const retryableErrors = getRetryableErrors(provider);
2315
+ return containsErrorPattern(message, retryableErrors);
2316
+ }
2317
+ var COMMON_NETWORK_ERRORS, COMMON_RATE_LIMIT_ERRORS, COMMON_SERVER_ERRORS, CLAUDE_RETRYABLE_ERRORS, GEMINI_RETRYABLE_ERRORS, OPENAI_RETRYABLE_ERRORS, AX_CLI_RETRYABLE_ERRORS, CODEX_RETRYABLE_ERRORS, NON_RETRYABLE_ERRORS;
2318
+ var init_retry_errors = __esm({
2319
+ "src/providers/retry-errors.ts"() {
2320
+ init_esm_shims();
2321
+ COMMON_NETWORK_ERRORS = [
2322
+ "ECONNRESET",
2323
+ "ETIMEDOUT",
2324
+ "ENOTFOUND",
2325
+ "ECONNREFUSED",
2326
+ "connection_error",
2327
+ "network connection error",
2328
+ "timeout"
2329
+ ];
2330
+ COMMON_RATE_LIMIT_ERRORS = [
2331
+ "rate_limit",
2332
+ "rate_limit_error",
2333
+ "too many requests"
2334
+ ];
2335
+ COMMON_SERVER_ERRORS = [
2336
+ "internal_server_error",
2337
+ "server_error",
2338
+ "service_unavailable",
2339
+ "unavailable"
2340
+ ];
2341
+ CLAUDE_RETRYABLE_ERRORS = [
2342
+ "overloaded_error",
2343
+ "internal_server_error"
2344
+ ];
2345
+ GEMINI_RETRYABLE_ERRORS = [
2346
+ "resource_exhausted",
2347
+ "deadline_exceeded",
2348
+ "internal"
2349
+ ];
2350
+ OPENAI_RETRYABLE_ERRORS = [
2351
+ "internal_error"
2352
+ ];
2353
+ AX_CLI_RETRYABLE_ERRORS = [
2354
+ // Inherited from Anthropic (via ax-cli)
2355
+ "overloaded_error",
2356
+ // Inherited from OpenAI (via ax-cli)
2357
+ "internal_error",
2358
+ // ax-cli specific
2359
+ "agent_error",
2360
+ "tool_execution_error",
2361
+ // xAI/Grok specific
2362
+ "service_overloaded"
2363
+ ];
2364
+ CODEX_RETRYABLE_ERRORS = [
2365
+ ...OPENAI_RETRYABLE_ERRORS,
2366
+ "sandbox_error"
2367
+ // Codex-specific sandbox issues are often transient
2368
+ ];
2369
+ NON_RETRYABLE_ERRORS = [
2370
+ "authentication",
2371
+ "unauthorized",
2372
+ "api key",
2373
+ "not found",
2374
+ "permission denied",
2375
+ "invalid_api_key",
2376
+ "invalid_request"
2377
+ ];
2378
+ }
2379
+ });
2247
2380
  var BaseProvider;
2248
2381
  var init_base_provider = __esm({
2249
2382
  "src/providers/base-provider.ts"() {
@@ -2254,6 +2387,8 @@ var init_base_provider = __esm({
2254
2387
  init_provider_schemas();
2255
2388
  init_streaming_progress_parser();
2256
2389
  init_verbosity_manager();
2390
+ init_error_patterns();
2391
+ init_retry_errors();
2257
2392
  BaseProvider = class _BaseProvider {
2258
2393
  /**
2259
2394
  * Whitelist of allowed provider names for security
@@ -2286,6 +2421,10 @@ var init_base_provider = __esm({
2286
2421
  NO_UPDATE_NOTIFIER: "1",
2287
2422
  DEBIAN_FRONTEND: "noninteractive"
2288
2423
  };
2424
+ /** Default CLI execution timeout in milliseconds */
2425
+ static DEFAULT_TIMEOUT_MS = 12e4;
2426
+ /** Time to wait after SIGTERM before escalating to SIGKILL */
2427
+ static SIGKILL_ESCALATION_MS = 5e3;
2289
2428
  config;
2290
2429
  logger = logger;
2291
2430
  health;
@@ -2303,6 +2442,8 @@ var init_base_provider = __esm({
2303
2442
  latencyMs: 0,
2304
2443
  errorRate: 0,
2305
2444
  consecutiveFailures: 0,
2445
+ consecutiveSuccesses: 0,
2446
+ // BUG FIX: Track consecutive successes
2306
2447
  lastCheck: Date.now()
2307
2448
  };
2308
2449
  }
@@ -2343,7 +2484,7 @@ var init_base_provider = __esm({
2343
2484
  useStdin,
2344
2485
  streaming: process.env.AUTOMATOSX_SHOW_PROVIDER_OUTPUT === "true"
2345
2486
  });
2346
- const result = useStdin ? await this.executeWithStdin(cliCommand, argsString.trim(), prompt) : await this.executeWithSpawn(fullCommand, cliCommand);
2487
+ const result = useStdin ? await this.executeWithStdin(cliCommand, cliArgs, prompt) : await this.executeWithSpawn(fullCommand, cliCommand);
2347
2488
  if (!result.stdout) {
2348
2489
  throw new Error(`${cliCommand} CLI returned empty output. stderr: ${result.stderr || "none"}`);
2349
2490
  }
@@ -2365,8 +2506,8 @@ var init_base_provider = __esm({
2365
2506
  /**
2366
2507
  * Execute command using spawn() with streaming support (v8.4.18)
2367
2508
  *
2368
- * Replaces execWithCleanup to support real-time line-by-line output streaming.
2369
- * Maintains all timeout and cleanup behaviors from execWithCleanup.
2509
+ * Supports real-time line-by-line output streaming with proper timeout
2510
+ * and cleanup behaviors (SIGTERM → SIGKILL escalation pattern).
2370
2511
  *
2371
2512
  * @param command - Full command string to execute
2372
2513
  * @param cliCommand - CLI command name (for logging)
@@ -2377,7 +2518,7 @@ var init_base_provider = __esm({
2377
2518
  const child = spawn(command, [], {
2378
2519
  shell: true,
2379
2520
  // Auto-detects: cmd.exe on Windows, /bin/sh on Unix
2380
- timeout: this.config.timeout || 12e4,
2521
+ timeout: this.config.timeout || _BaseProvider.DEFAULT_TIMEOUT_MS,
2381
2522
  env: { ...process.env, ..._BaseProvider.NON_INTERACTIVE_ENV }
2382
2523
  });
2383
2524
  let stdout = "";
@@ -2488,7 +2629,7 @@ var init_base_provider = __esm({
2488
2629
  });
2489
2630
  reject(new Error(`Failed to spawn ${cliCommand} CLI: ${error.message}`));
2490
2631
  });
2491
- const timeout = this.config.timeout || 12e4;
2632
+ const timeout = this.config.timeout || _BaseProvider.DEFAULT_TIMEOUT_MS;
2492
2633
  timeoutId = setTimeout(() => {
2493
2634
  if (child.pid && !child.killed) {
2494
2635
  logger.warn("Killing child process due to timeout", {
@@ -2502,7 +2643,7 @@ var init_base_provider = __esm({
2502
2643
  logger.warn("Force killing child process", { pid: child.pid });
2503
2644
  child.kill("SIGKILL");
2504
2645
  }
2505
- }, 5e3);
2646
+ }, _BaseProvider.SIGKILL_ESCALATION_MS);
2506
2647
  }
2507
2648
  }, timeout);
2508
2649
  });
@@ -2520,14 +2661,13 @@ var init_base_provider = __esm({
2520
2661
  */
2521
2662
  async executeWithStdin(cliCommand, cliArgs, prompt) {
2522
2663
  return new Promise((resolve5, reject) => {
2523
- const commandArgs = cliArgs ? cliArgs.split(" ").filter(Boolean) : [];
2524
2664
  logger.debug(`Executing ${cliCommand} CLI with stdin`, {
2525
2665
  command: cliCommand,
2526
- args: commandArgs,
2666
+ args: cliArgs,
2527
2667
  promptLength: prompt.length
2528
2668
  });
2529
- const child = spawn(cliCommand, commandArgs, {
2530
- timeout: this.config.timeout || 12e4,
2669
+ const child = spawn(cliCommand, cliArgs, {
2670
+ timeout: this.config.timeout || _BaseProvider.DEFAULT_TIMEOUT_MS,
2531
2671
  env: { ...process.env, ..._BaseProvider.NON_INTERACTIVE_ENV }
2532
2672
  });
2533
2673
  let stdout = "";
@@ -2550,11 +2690,20 @@ var init_base_provider = __esm({
2550
2690
  child.stdin.write(prompt);
2551
2691
  child.stdin.end();
2552
2692
  } catch (error) {
2693
+ const errorMessage = error instanceof Error ? error.message : String(error);
2553
2694
  logger.error("Failed to write to stdin", {
2554
2695
  command: cliCommand,
2555
- error: error instanceof Error ? error.message : String(error)
2696
+ error: errorMessage
2556
2697
  });
2698
+ child.kill("SIGTERM");
2699
+ reject(new Error(`Failed to write prompt to ${cliCommand} stdin: ${errorMessage}`));
2700
+ return;
2557
2701
  }
2702
+ } else {
2703
+ logger.error("stdin not available for child process", { command: cliCommand });
2704
+ child.kill("SIGTERM");
2705
+ reject(new Error(`${cliCommand} stdin not available - cannot send prompt`));
2706
+ return;
2558
2707
  }
2559
2708
  if (child.stdout) {
2560
2709
  readlineInterface = readline.createInterface({
@@ -2663,7 +2812,7 @@ var init_base_provider = __esm({
2663
2812
  });
2664
2813
  reject(new Error(`Failed to spawn ${cliCommand} CLI: ${error.message}`));
2665
2814
  });
2666
- const timeout = this.config.timeout || 12e4;
2815
+ const timeout = this.config.timeout || _BaseProvider.DEFAULT_TIMEOUT_MS;
2667
2816
  timeoutId = setTimeout(() => {
2668
2817
  if (child.pid && !child.killed) {
2669
2818
  logger.warn("Killing child process due to timeout", {
@@ -2677,7 +2826,7 @@ var init_base_provider = __esm({
2677
2826
  logger.warn("Force killing child process", { pid: child.pid });
2678
2827
  child.kill("SIGKILL");
2679
2828
  }
2680
- }, 5e3);
2829
+ }, _BaseProvider.SIGKILL_ESCALATION_MS);
2681
2830
  }
2682
2831
  }, timeout);
2683
2832
  });
@@ -2743,9 +2892,7 @@ var init_base_provider = __esm({
2743
2892
  ${request.prompt}`;
2744
2893
  }
2745
2894
  if (process.env.AUTOMATOSX_DEBUG_PROMPT === "true") {
2746
- const fs3 = await import('fs');
2747
- const path5 = await import('path');
2748
- const debugPath = path5.join(process.cwd(), "automatosx/tmp/debug-prompt.txt");
2895
+ const debugPath = path4.join(process.cwd(), "automatosx/tmp/debug-prompt.txt");
2749
2896
  fs3.writeFileSync(debugPath, `=== FULL PROMPT SENT TO ${this.getCLICommand()} ===
2750
2897
 
2751
2898
  ${fullPrompt}
@@ -2757,6 +2904,7 @@ ${fullPrompt}
2757
2904
  const result = await this.executeCLI(fullPrompt);
2758
2905
  const latencyMs = Date.now() - startTime;
2759
2906
  this.health.consecutiveFailures = 0;
2907
+ this.health.consecutiveSuccesses++;
2760
2908
  this.health.available = true;
2761
2909
  this.health.errorRate = 0;
2762
2910
  this.health.latencyMs = latencyMs;
@@ -2789,6 +2937,7 @@ ${fullPrompt}
2789
2937
  return response;
2790
2938
  } catch (error) {
2791
2939
  this.health.consecutiveFailures++;
2940
+ this.health.consecutiveSuccesses = 0;
2792
2941
  this.health.available = false;
2793
2942
  this.health.errorRate = 1;
2794
2943
  this.health.latencyMs = Date.now() - startTime;
@@ -2802,19 +2951,23 @@ ${fullPrompt}
2802
2951
  async isAvailable() {
2803
2952
  try {
2804
2953
  const available = await this.checkCLIAvailable();
2805
- this.health = {
2806
- available,
2807
- latencyMs: 0,
2808
- errorRate: available ? 0 : 1,
2809
- lastCheck: Date.now(),
2810
- consecutiveFailures: available ? 0 : this.health.consecutiveFailures + 1
2811
- };
2954
+ this.health.available = available;
2955
+ this.health.errorRate = available ? 0 : 1;
2956
+ this.health.lastCheck = Date.now();
2957
+ if (available) {
2958
+ this.health.consecutiveFailures = 0;
2959
+ this.health.consecutiveSuccesses++;
2960
+ } else {
2961
+ this.health.consecutiveFailures++;
2962
+ this.health.consecutiveSuccesses = 0;
2963
+ }
2812
2964
  return available;
2813
2965
  } catch (error) {
2814
2966
  this.health.available = false;
2815
2967
  this.health.errorRate = 1;
2816
2968
  this.health.lastCheck = Date.now();
2817
- this.health.consecutiveFailures = this.health.consecutiveFailures + 1;
2969
+ this.health.consecutiveFailures++;
2970
+ this.health.consecutiveSuccesses = 0;
2818
2971
  return false;
2819
2972
  }
2820
2973
  }
@@ -2869,26 +3022,16 @@ ${fullPrompt}
2869
3022
  * @returns true if this is a rate limit or quota error
2870
3023
  */
2871
3024
  detectRateLimitError(error) {
2872
- const { isLimitError: isLimitError2 } = (init_error_patterns(), __toCommonJS(error_patterns_exports));
2873
- try {
2874
- const isLimited = isLimitError2(error, this.config.name);
2875
- if (isLimited) {
2876
- this.logger.debug("Rate limit error detected", {
2877
- provider: this.config.name,
2878
- message: error?.message,
2879
- code: error?.code,
2880
- status: error?.status || error?.statusCode
2881
- });
2882
- }
2883
- return isLimited;
2884
- } catch (detectionError) {
2885
- this.logger.warn("Rate limit detection failed, using fallback", {
3025
+ const isLimited = isLimitError(error, this.config.name);
3026
+ if (isLimited) {
3027
+ this.logger.debug("Rate limit error detected", {
2886
3028
  provider: this.config.name,
2887
- error: detectionError
3029
+ message: error?.message,
3030
+ code: error?.code,
3031
+ status: error?.status || error?.statusCode
2888
3032
  });
2889
- const message = (error?.message || "").toLowerCase();
2890
- return message.includes("rate limit") || message.includes("quota") || message.includes("resource_exhausted") || message.includes("too many requests");
2891
3033
  }
3034
+ return isLimited;
2892
3035
  }
2893
3036
  /**
2894
3037
  * Escape shell command arguments to prevent injection
@@ -2981,8 +3124,20 @@ ${fullPrompt}
2981
3124
  };
2982
3125
  }
2983
3126
  shouldRetry(error) {
2984
- const message = error.message.toLowerCase();
2985
- return message.includes("timeout") || message.includes("rate limit") || message.includes("temporarily unavailable") || message.includes("503") || message.includes("502");
3127
+ const providerName = this.config.name.toLowerCase();
3128
+ let retryableProvider = "base";
3129
+ if (providerName === "claude" || providerName === "claude-code") {
3130
+ retryableProvider = "claude";
3131
+ } else if (providerName === "gemini" || providerName === "gemini-cli") {
3132
+ retryableProvider = "gemini";
3133
+ } else if (providerName === "openai") {
3134
+ retryableProvider = "openai";
3135
+ } else if (providerName === "codex") {
3136
+ retryableProvider = "codex";
3137
+ } else if (providerName === "ax-cli" || providerName === "glm") {
3138
+ retryableProvider = "ax-cli";
3139
+ }
3140
+ return shouldRetryError(error, retryableProvider);
2986
3141
  }
2987
3142
  getRetryDelay(attempt) {
2988
3143
  return Math.min(1e3 * Math.pow(2, attempt - 1), 3e4);
@@ -3006,7 +3161,9 @@ ${fullPrompt}
3006
3161
  },
3007
3162
  health: {
3008
3163
  consecutiveFailures: this.health.consecutiveFailures,
3009
- consecutiveSuccesses: this.health.consecutiveFailures === 0 ? 1 : 0,
3164
+ // BUG FIX: Use actual tracked value instead of incorrect derivation
3165
+ // Previously: `consecutiveFailures === 0 ? 1 : 0` which was wrong
3166
+ consecutiveSuccesses: this.health.consecutiveSuccesses,
3010
3167
  lastCheckTime: this.health.lastCheck,
3011
3168
  lastCheckDuration: 0,
3012
3169
  uptime: this.health.lastCheck > 0 ? Date.now() - this.health.lastCheck : 0
@@ -3706,7 +3863,20 @@ var init_sdk_adapter = __esm({
3706
3863
  }
3707
3864
  }
3708
3865
  const tokenCount = result.usage ? (result.usage.input_tokens || 0) + (result.usage.output_tokens || 0) : void 0;
3709
- if (!this.options.reuseThreads) {
3866
+ if (!this.options.reuseThreads && this.activeThread) {
3867
+ try {
3868
+ const threadWithDispose = this.activeThread;
3869
+ if (typeof threadWithDispose.dispose === "function") {
3870
+ const disposeResult = threadWithDispose.dispose();
3871
+ if (disposeResult instanceof Promise) {
3872
+ await disposeResult;
3873
+ }
3874
+ }
3875
+ } catch (disposeError) {
3876
+ logger.warn("Error disposing thread", {
3877
+ error: disposeError instanceof Error ? disposeError.message : String(disposeError)
3878
+ });
3879
+ }
3710
3880
  this.activeThread = null;
3711
3881
  }
3712
3882
  return {
@@ -3885,12 +4055,38 @@ var init_hybrid_adapter = __esm({
3885
4055
  }
3886
4056
  this.activeMode = "cli";
3887
4057
  }
4058
+ /**
4059
+ * Cleanup resources
4060
+ *
4061
+ * BUG FIX: Use Promise.allSettled to ensure cleanup continues even if one
4062
+ * adapter fails, and properly log any cleanup errors. Previously, a failure
4063
+ * in SDK destroy would prevent CLI cleanup from running.
4064
+ */
3888
4065
  async destroy() {
3889
- if (this.sdkAdapter) await this.sdkAdapter.destroy();
3890
- if (this.cliAdapter) await this.cliAdapter.cleanup();
4066
+ const cleanupPromises = [];
4067
+ if (this.sdkAdapter) {
4068
+ cleanupPromises.push(
4069
+ this.sdkAdapter.destroy().catch((err) => {
4070
+ logger.warn("Error destroying SDK adapter", {
4071
+ error: err instanceof Error ? err.message : String(err)
4072
+ });
4073
+ })
4074
+ );
4075
+ }
4076
+ if (this.cliAdapter) {
4077
+ cleanupPromises.push(
4078
+ this.cliAdapter.cleanup().catch((err) => {
4079
+ logger.warn("Error cleaning up CLI adapter", {
4080
+ error: err instanceof Error ? err.message : String(err)
4081
+ });
4082
+ })
4083
+ );
4084
+ }
4085
+ await Promise.allSettled(cleanupPromises);
3891
4086
  this.sdkAdapter = null;
3892
4087
  this.cliAdapter = null;
3893
4088
  this.activeMode = null;
4089
+ logger.debug("HybridCodexAdapter destroyed");
3894
4090
  }
3895
4091
  };
3896
4092
  }
@@ -3947,7 +4143,7 @@ var init_openai_provider = __esm({
3947
4143
  error: error instanceof Error ? error.message : String(error),
3948
4144
  mode: this.hybridAdapter?.getActiveMode() || "unknown"
3949
4145
  });
3950
- throw error;
4146
+ throw this.handleError(error);
3951
4147
  }
3952
4148
  }
3953
4149
  initializeHybridAdapter() {
@@ -4578,11 +4774,11 @@ var VALIDATION_LIMITS = {
4578
4774
  MAX_PORT: 65535
4579
4775
  // Maximum port number
4580
4776
  };
4581
- function isValidRelativePath(path5) {
4582
- if (!path5 || typeof path5 !== "string") {
4777
+ function isValidRelativePath(path6) {
4778
+ if (!path6 || typeof path6 !== "string") {
4583
4779
  return false;
4584
4780
  }
4585
- const normalizedPath = path5.replace(/\\/g, "/");
4781
+ const normalizedPath = path6.replace(/\\/g, "/");
4586
4782
  if (normalizedPath.startsWith("/")) {
4587
4783
  return false;
4588
4784
  }
@@ -4974,7 +5170,7 @@ var PRECOMPILED_CONFIG = {
4974
5170
  "providers": {
4975
5171
  "claude-code": {
4976
5172
  "enabled": true,
4977
- "priority": 1,
5173
+ "priority": 3,
4978
5174
  "timeout": 27e5,
4979
5175
  "command": "claude",
4980
5176
  "healthCheck": {
@@ -5034,7 +5230,7 @@ var PRECOMPILED_CONFIG = {
5034
5230
  },
5035
5231
  "openai": {
5036
5232
  "enabled": true,
5037
- "priority": 3,
5233
+ "priority": 1,
5038
5234
  "timeout": 27e5,
5039
5235
  "command": "codex",
5040
5236
  "healthCheck": {
@@ -5272,7 +5468,7 @@ var PRECOMPILED_CONFIG = {
5272
5468
  "enableFreeTierPrioritization": true,
5273
5469
  "enableWorkloadAwareRouting": true
5274
5470
  },
5275
- "version": "11.3.1"
5471
+ "version": "11.3.3"
5276
5472
  };
5277
5473
 
5278
5474
  // src/core/config/schemas.ts
@@ -5299,7 +5495,7 @@ var safeNameSchema = z.string().min(1).max(VALIDATION_LIMITS.MAX_NAME_LENGTH).re
5299
5495
  "Name must be alphanumeric with dash/underscore only"
5300
5496
  ).describe("Safe identifier name");
5301
5497
  var relativePathSchema = z.string().min(1).refine(
5302
- (path5) => !path5.includes("..") && !path5.startsWith("/"),
5498
+ (path6) => !path6.includes("..") && !path6.startsWith("/"),
5303
5499
  "Path must be relative (no ../, no absolute paths)"
5304
5500
  ).describe("Relative path within project");
5305
5501
  var fileExtensionSchema = z.string().regex(
@@ -5639,16 +5835,16 @@ async function loadConfigUncached(projectDir) {
5639
5835
  });
5640
5836
  return config;
5641
5837
  }
5642
- async function loadConfigFile(path5) {
5838
+ async function loadConfigFile(path6) {
5643
5839
  try {
5644
- const content = await readFile(path5, "utf-8");
5840
+ const content = await readFile(path6, "utf-8");
5645
5841
  if (content.length > VALIDATION_LIMITS.MAX_CONFIG_FILE_SIZE) {
5646
5842
  throw ConfigError.parseError(
5647
5843
  new Error(`Config file too large (max ${VALIDATION_LIMITS.MAX_CONFIG_FILE_SIZE / 1024}KB, got ${Math.ceil(content.length / 1024)}KB)`),
5648
- path5
5844
+ path6
5649
5845
  );
5650
5846
  }
5651
- const ext = extname(path5).toLowerCase();
5847
+ const ext = extname(path6).toLowerCase();
5652
5848
  let userConfig;
5653
5849
  try {
5654
5850
  if (ext === ".yaml" || ext === ".yml") {
@@ -5657,7 +5853,7 @@ async function loadConfigFile(path5) {
5657
5853
  userConfig = JSON.parse(content);
5658
5854
  }
5659
5855
  } catch (parseError) {
5660
- throw ConfigError.parseError(parseError, path5);
5856
+ throw ConfigError.parseError(parseError, path6);
5661
5857
  }
5662
5858
  const config = mergeConfig(DEFAULT_CONFIG, userConfig);
5663
5859
  if (config.execution && userConfig.execution?.maxConcurrentAgents === void 0) {
@@ -5673,35 +5869,35 @@ async function loadConfigFile(path5) {
5673
5869
  if (validationErrors.length > 0) {
5674
5870
  throw ConfigError.invalid(
5675
5871
  validationErrors.join("; "),
5676
- { path: path5, errors: validationErrors }
5872
+ { path: path6, errors: validationErrors }
5677
5873
  );
5678
5874
  }
5679
- logger.info("Config loaded successfully", { path: normalizePath(path5), format: ext });
5875
+ logger.info("Config loaded successfully", { path: normalizePath(path6), format: ext });
5680
5876
  return config;
5681
5877
  } catch (error) {
5682
5878
  if (error instanceof ConfigError) {
5683
5879
  throw error;
5684
5880
  }
5685
5881
  if (error.code === "ENOENT") {
5686
- throw ConfigError.notFound(path5);
5882
+ throw ConfigError.notFound(path6);
5687
5883
  }
5688
5884
  if (error.code === "EACCES") {
5689
5885
  throw new ConfigError(
5690
- `Permission denied reading config: ${path5}`,
5886
+ `Permission denied reading config: ${path6}`,
5691
5887
  "E1002" /* CONFIG_PARSE_ERROR */,
5692
5888
  [
5693
5889
  "Check file permissions",
5694
5890
  "Run with appropriate user privileges",
5695
5891
  "Verify the file is accessible"
5696
5892
  ],
5697
- { path: path5, error: error.message }
5893
+ { path: path6, error: error.message }
5698
5894
  );
5699
5895
  }
5700
5896
  throw new ConfigError(
5701
5897
  `Failed to load config: ${error.message}`,
5702
5898
  "E1002" /* CONFIG_PARSE_ERROR */,
5703
5899
  ["Check file format and permissions"],
5704
- { path: path5, originalError: error.message }
5900
+ { path: path6, originalError: error.message }
5705
5901
  );
5706
5902
  }
5707
5903
  }
@@ -5717,8 +5913,8 @@ function validateConfigWithZod(config) {
5717
5913
  return ["Configuration validation failed with unknown error structure"];
5718
5914
  }
5719
5915
  return result.error.issues.map((err) => {
5720
- const path5 = err.path.join(".");
5721
- return `${path5}: ${err.message}`;
5916
+ const path6 = err.path.join(".");
5917
+ return `${path6}: ${err.message}`;
5722
5918
  });
5723
5919
  }
5724
5920
  function validateConfig(config) {
@@ -6128,8 +6324,8 @@ var PathError = class extends Error {
6128
6324
  };
6129
6325
 
6130
6326
  // src/shared/validation/path-resolver.ts
6131
- function isWindowsPath(path5) {
6132
- return /^[a-zA-Z]:[/\\]/.test(path5);
6327
+ function isWindowsPath(path6) {
6328
+ return /^[a-zA-Z]:[/\\]/.test(path6);
6133
6329
  }
6134
6330
  async function detectProjectRoot(startDir = process.cwd()) {
6135
6331
  if (process.env.AUTOMATOSX_PROJECT_ROOT) {
@@ -6229,8 +6425,8 @@ var PathResolver = class {
6229
6425
  /**
6230
6426
  * Validate path is within allowed base directory
6231
6427
  */
6232
- validatePath(path5, baseDir) {
6233
- const normalized = normalizePath(resolvePath(path5));
6428
+ validatePath(path6, baseDir) {
6429
+ const normalized = normalizePath(resolvePath(path6));
6234
6430
  const base = normalizePath(resolvePath(baseDir));
6235
6431
  const separator = "/";
6236
6432
  const pathWithSep = normalized + separator;
@@ -6240,15 +6436,15 @@ var PathResolver = class {
6240
6436
  /**
6241
6437
  * Check if path is within allowed boundaries
6242
6438
  */
6243
- isPathAllowed(path5) {
6244
- const boundary = this.checkBoundaries(path5);
6439
+ isPathAllowed(path6) {
6440
+ const boundary = this.checkBoundaries(path6);
6245
6441
  return boundary === "agent_workspace" || boundary === "user_project";
6246
6442
  }
6247
6443
  /**
6248
6444
  * Check which boundary a path belongs to
6249
6445
  */
6250
- checkBoundaries(path5) {
6251
- const normalized = resolvePath(path5);
6446
+ checkBoundaries(path6) {
6447
+ const normalized = resolvePath(path6);
6252
6448
  if (this.validatePath(normalized, this.config.agentWorkspace)) {
6253
6449
  return "agent_workspace";
6254
6450
  }
@@ -6266,15 +6462,15 @@ var PathResolver = class {
6266
6462
  /**
6267
6463
  * Get relative path from project root
6268
6464
  */
6269
- getRelativeToProject(path5) {
6270
- const normalized = resolvePath(path5);
6465
+ getRelativeToProject(path6) {
6466
+ const normalized = resolvePath(path6);
6271
6467
  return normalizePath(getRelativePath(this.config.projectDir, normalized));
6272
6468
  }
6273
6469
  /**
6274
6470
  * Get relative path from working directory
6275
6471
  */
6276
- getRelativeToWorking(path5) {
6277
- const normalized = resolvePath(path5);
6472
+ getRelativeToWorking(path6) {
6473
+ const normalized = resolvePath(path6);
6278
6474
  return normalizePath(getRelativePath(this.config.workingDir, normalized));
6279
6475
  }
6280
6476
  /**
@@ -6293,11 +6489,11 @@ var PathResolver = class {
6293
6489
  * Validate path is within project boundaries
6294
6490
  * @throws PathError if outside project
6295
6491
  */
6296
- validateInProject(path5) {
6297
- const boundary = this.checkBoundaries(path5);
6492
+ validateInProject(path6) {
6493
+ const boundary = this.checkBoundaries(path6);
6298
6494
  if (boundary === "outside_boundaries" || boundary === "system_restricted") {
6299
6495
  throw new PathError("Path outside project directory", {
6300
- path: path5,
6496
+ path: path6,
6301
6497
  projectDir: this.config.projectDir,
6302
6498
  boundary
6303
6499
  });
@@ -8164,18 +8360,18 @@ var ProviderSessionManager = class {
8164
8360
  };
8165
8361
  var providerSessionInstances = /* @__PURE__ */ new Map();
8166
8362
  async function getProviderSession(workspacePath) {
8167
- let path5;
8363
+ let path6;
8168
8364
  {
8169
8365
  if (process.env.NODE_ENV === "test" || process.env.VITEST) {
8170
- path5 = process.cwd();
8366
+ path6 = process.cwd();
8171
8367
  } else {
8172
- path5 = await detectProjectRoot();
8368
+ path6 = await detectProjectRoot();
8173
8369
  }
8174
8370
  }
8175
- if (!providerSessionInstances.has(path5)) {
8176
- providerSessionInstances.set(path5, new ProviderSessionManager(path5));
8371
+ if (!providerSessionInstances.has(path6)) {
8372
+ providerSessionInstances.set(path6, new ProviderSessionManager(path6));
8177
8373
  }
8178
- return providerSessionInstances.get(path5);
8374
+ return providerSessionInstances.get(path6);
8179
8375
  }
8180
8376
 
8181
8377
  // src/core/router/router.ts
@@ -9944,6 +10140,9 @@ var MemoryManager = class _MemoryManager {
9944
10140
  if (!Number.isInteger(offsetValue) || offsetValue < 0) {
9945
10141
  throw new Error(`Invalid offset value: ${options.offset}. Must be a non-negative integer.`);
9946
10142
  }
10143
+ if (!limitClause) {
10144
+ limitClause = "LIMIT -1";
10145
+ }
9947
10146
  offsetClause = `OFFSET ${offsetValue}`;
9948
10147
  }
9949
10148
  const sql = `
@@ -10297,6 +10496,13 @@ var MemoryManager = class _MemoryManager {
10297
10496
  await this.initialize();
10298
10497
  logger.info("Database restored successfully (atomic operation)", { srcPath: normalizePath(srcPath) });
10299
10498
  } catch (error) {
10499
+ try {
10500
+ DatabaseFactory.close(this.db);
10501
+ } finally {
10502
+ this.initialized = false;
10503
+ this.entryCount = 0;
10504
+ this.statements = {};
10505
+ }
10300
10506
  throw new MemoryError(
10301
10507
  `Failed to restore database: ${error.message}`,
10302
10508
  "DATABASE_ERROR",
@@ -10846,6 +11052,10 @@ var SessionManager = class _SessionManager {
10846
11052
  MAX_TASKS_PER_SESSION = 1e3;
10847
11053
  /** UUID v4 validation regex (static for performance) */
10848
11054
  static UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
11055
+ /** Tracks whether manager has been destroyed to prevent late saves */
11056
+ destroyed = false;
11057
+ /** Last persistence error (reported during flush) */
11058
+ lastSaveError;
10849
11059
  /**
10850
11060
  * Validate session ID format (must be valid UUID v4)
10851
11061
  *
@@ -11440,6 +11650,7 @@ var SessionManager = class _SessionManager {
11440
11650
  * ```
11441
11651
  */
11442
11652
  async destroy() {
11653
+ this.destroyed = true;
11443
11654
  if (this.saveTimeout) {
11444
11655
  clearTimeout(this.saveTimeout);
11445
11656
  this.saveTimeout = void 0;
@@ -11450,9 +11661,12 @@ var SessionManager = class _SessionManager {
11450
11661
  logger.error("Error flushing save during destroy", {
11451
11662
  error: error.message
11452
11663
  });
11664
+ this.lastSaveError = void 0;
11453
11665
  }
11666
+ const sessionCount = this.activeSessions.size;
11667
+ this.activeSessions.clear();
11454
11668
  logger.debug("SessionManager destroyed", {
11455
- sessions: this.activeSessions.size
11669
+ sessions: sessionCount
11456
11670
  });
11457
11671
  }
11458
11672
  /**
@@ -11477,11 +11691,21 @@ var SessionManager = class _SessionManager {
11477
11691
  this.pendingSave = void 0;
11478
11692
  });
11479
11693
  await this.pendingSave;
11694
+ if (this.lastSaveError) {
11695
+ const err = this.lastSaveError;
11696
+ this.lastSaveError = void 0;
11697
+ throw err;
11698
+ }
11480
11699
  return;
11481
11700
  }
11482
11701
  if (this.pendingSave) {
11483
11702
  try {
11484
11703
  await this.pendingSave;
11704
+ if (this.lastSaveError) {
11705
+ const err = this.lastSaveError;
11706
+ this.lastSaveError = void 0;
11707
+ throw err;
11708
+ }
11485
11709
  } catch (err) {
11486
11710
  throw err;
11487
11711
  }
@@ -11646,7 +11870,7 @@ var SessionManager = class _SessionManager {
11646
11870
  * @private
11647
11871
  */
11648
11872
  saveToFile() {
11649
- if (!this.persistencePath) {
11873
+ if (!this.persistencePath || this.destroyed) {
11650
11874
  return;
11651
11875
  }
11652
11876
  if (this.saveTimeout) {
@@ -11659,18 +11883,18 @@ var SessionManager = class _SessionManager {
11659
11883
  }
11660
11884
  this.saveNeeded = false;
11661
11885
  const executeNextSave = async () => {
11662
- await this.doSave();
11886
+ try {
11887
+ await this.doSave();
11888
+ this.lastSaveError = void 0;
11889
+ } catch (err) {
11890
+ this.lastSaveError = err;
11891
+ }
11663
11892
  if (this.saveNeeded) {
11664
11893
  this.saveNeeded = false;
11665
11894
  return executeNextSave();
11666
11895
  }
11667
11896
  };
11668
- this.pendingSave = executeNextSave().catch((err) => {
11669
- logger.error("Debounced save failed", {
11670
- error: err.message
11671
- });
11672
- throw err;
11673
- }).finally(() => {
11897
+ this.pendingSave = executeNextSave().finally(() => {
11674
11898
  this.pendingSave = void 0;
11675
11899
  });
11676
11900
  }, 100);
@@ -15081,9 +15305,9 @@ var DependencyGraphBuilder = class {
15081
15305
  detectCycles(graph) {
15082
15306
  const visiting = /* @__PURE__ */ new Set();
15083
15307
  const visited = /* @__PURE__ */ new Set();
15084
- const visit = (nodeName, path5) => {
15308
+ const visit = (nodeName, path6) => {
15085
15309
  if (visiting.has(nodeName)) {
15086
- throw new Error(`Circular dependency detected: ${[...path5, nodeName].join(" \u2192 ")}`);
15310
+ throw new Error(`Circular dependency detected: ${[...path6, nodeName].join(" \u2192 ")}`);
15087
15311
  }
15088
15312
  if (visited.has(nodeName)) {
15089
15313
  return;
@@ -15098,7 +15322,7 @@ var DependencyGraphBuilder = class {
15098
15322
  }
15099
15323
  visiting.add(nodeName);
15100
15324
  for (const dependency of node.dependencies) {
15101
- visit(dependency, [...path5, nodeName]);
15325
+ visit(dependency, [...path6, nodeName]);
15102
15326
  }
15103
15327
  visiting.delete(nodeName);
15104
15328
  visited.add(nodeName);
@@ -16831,59 +17055,59 @@ var DANGEROUS_PATH_PATTERNS = [
16831
17055
  // Common data drive (Windows, alt format)
16832
17056
  ];
16833
17057
  var SUSPICIOUS_PATH_CHARS = /[<>:|"]/;
16834
- function validatePathParameter(path5, paramName, projectRoot = process.cwd()) {
16835
- if (!path5 || path5.trim() === "") {
17058
+ function validatePathParameter(path6, paramName, projectRoot = process.cwd()) {
17059
+ if (!path6 || path6.trim() === "") {
16836
17060
  throw new ValidationError(
16837
17061
  `Invalid ${paramName}: path cannot be empty`,
16838
17062
  -32602 /* InvalidParams */,
16839
- { path: path5, paramName }
17063
+ { path: path6, paramName }
16840
17064
  );
16841
17065
  }
16842
17066
  for (const pattern of DANGEROUS_PATH_PATTERNS) {
16843
- if (path5.includes(pattern)) {
17067
+ if (path6.includes(pattern)) {
16844
17068
  throw new ValidationError(
16845
17069
  `Invalid ${paramName}: path contains dangerous pattern "${pattern}"`,
16846
17070
  -32602 /* InvalidParams */,
16847
- { path: path5, paramName, pattern }
17071
+ { path: path6, paramName, pattern }
16848
17072
  );
16849
17073
  }
16850
17074
  }
16851
- if (isAbsolute(path5)) {
17075
+ if (isAbsolute(path6)) {
16852
17076
  throw new ValidationError(
16853
17077
  `Invalid ${paramName}: absolute paths are not allowed`,
16854
17078
  -32602 /* InvalidParams */,
16855
- { path: path5, paramName }
17079
+ { path: path6, paramName }
16856
17080
  );
16857
17081
  }
16858
17082
  try {
16859
- const resolvedPath = resolve(projectRoot, path5);
17083
+ const resolvedPath = resolve(projectRoot, path6);
16860
17084
  const normalizedRoot = resolve(projectRoot);
16861
17085
  if (!resolvedPath.startsWith(normalizedRoot + sep) && resolvedPath !== normalizedRoot) {
16862
17086
  throw new ValidationError(
16863
17087
  `Invalid ${paramName}: path escapes project boundary`,
16864
17088
  -32602 /* InvalidParams */,
16865
- { path: path5, paramName, projectRoot, resolvedPath }
17089
+ { path: path6, paramName, projectRoot, resolvedPath }
16866
17090
  );
16867
17091
  }
16868
17092
  } catch (error) {
16869
17093
  throw new ValidationError(
16870
17094
  `Invalid ${paramName}: path resolution failed`,
16871
17095
  -32602 /* InvalidParams */,
16872
- { path: path5, paramName, error: String(error) }
17096
+ { path: path6, paramName, error: String(error) }
16873
17097
  );
16874
17098
  }
16875
- if (path5.includes("\0")) {
17099
+ if (path6.includes("\0")) {
16876
17100
  throw new ValidationError(
16877
17101
  `Invalid ${paramName}: path contains null byte`,
16878
17102
  -32602 /* InvalidParams */,
16879
- { path: path5, paramName }
17103
+ { path: path6, paramName }
16880
17104
  );
16881
17105
  }
16882
- if (SUSPICIOUS_PATH_CHARS.test(path5)) {
17106
+ if (SUSPICIOUS_PATH_CHARS.test(path6)) {
16883
17107
  throw new ValidationError(
16884
17108
  `Invalid ${paramName}: path contains invalid characters`,
16885
17109
  -32602 /* InvalidParams */,
16886
- { path: path5, paramName }
17110
+ { path: path6, paramName }
16887
17111
  );
16888
17112
  }
16889
17113
  }
@@ -17579,8 +17803,8 @@ function createMemoryExportHandler(deps) {
17579
17803
  return async (input) => {
17580
17804
  logger.info("[MCP] memory_export called", { input });
17581
17805
  try {
17582
- const { path: path5 } = input;
17583
- const absolutePath = resolveExportPath(deps.pathResolver, path5);
17806
+ const { path: path6 } = input;
17807
+ const absolutePath = resolveExportPath(deps.pathResolver, path6);
17584
17808
  const exported = await deps.memoryManager.exportToJSON(absolutePath);
17585
17809
  const result = {
17586
17810
  success: true,
@@ -17616,8 +17840,8 @@ function createMemoryImportHandler(deps) {
17616
17840
  return async (input) => {
17617
17841
  logger.info("[MCP] memory_import called", { input });
17618
17842
  try {
17619
- const { path: path5 } = input;
17620
- const absolutePath = resolveImportPath(deps.pathResolver, path5);
17843
+ const { path: path6 } = input;
17844
+ const absolutePath = resolveImportPath(deps.pathResolver, path6);
17621
17845
  const imported = await deps.memoryManager.importFromJSON(absolutePath);
17622
17846
  const result = {
17623
17847
  success: true,
@@ -18170,7 +18394,8 @@ var McpClient = class extends EventEmitter {
18170
18394
  * Send JSON-RPC request and wait for response
18171
18395
  */
18172
18396
  async sendRequest(method, params, timeout) {
18173
- if (!this.process?.stdin) {
18397
+ const stdin = this.process?.stdin;
18398
+ if (!stdin) {
18174
18399
  throw new Error("MCP client not connected");
18175
18400
  }
18176
18401
  const id = this.nextRequestId++;
@@ -18192,7 +18417,18 @@ var McpClient = class extends EventEmitter {
18192
18417
  timeout: timeoutHandle
18193
18418
  });
18194
18419
  const message = JSON.stringify(request) + "\n";
18195
- this.process.stdin.write(message);
18420
+ try {
18421
+ stdin.write(message);
18422
+ } catch (error) {
18423
+ const pending = this.pendingRequests.get(id);
18424
+ if (pending) {
18425
+ clearTimeout(pending.timeout);
18426
+ this.pendingRequests.delete(id);
18427
+ }
18428
+ const err = error instanceof Error ? error : new Error(String(error));
18429
+ reject(err);
18430
+ return;
18431
+ }
18196
18432
  logger.debug("[MCP Client] Sent request", {
18197
18433
  id,
18198
18434
  method,
@@ -18202,6 +18438,10 @@ var McpClient = class extends EventEmitter {
18202
18438
  }
18203
18439
  /**
18204
18440
  * Send JSON-RPC notification (no response expected)
18441
+ *
18442
+ * BUG FIX: Handle write errors gracefully. If the process has exited or the
18443
+ * pipe is broken, write() could throw an uncaught exception that would crash
18444
+ * the application.
18205
18445
  */
18206
18446
  sendNotification(method, params) {
18207
18447
  if (!this.process?.stdin) {
@@ -18213,7 +18453,14 @@ var McpClient = class extends EventEmitter {
18213
18453
  params
18214
18454
  };
18215
18455
  const message = JSON.stringify(notification) + "\n";
18216
- this.process.stdin.write(message);
18456
+ try {
18457
+ this.process.stdin.write(message);
18458
+ } catch (error) {
18459
+ logger.debug("[MCP Client] Failed to send notification (pipe may be closed)", {
18460
+ method,
18461
+ error: error instanceof Error ? error.message : String(error)
18462
+ });
18463
+ }
18217
18464
  }
18218
18465
  /**
18219
18466
  * Handle incoming JSON-RPC message
@@ -18564,6 +18811,11 @@ var McpClientPool = class extends EventEmitter {
18564
18811
  }
18565
18812
  /**
18566
18813
  * Check if pool has available capacity for provider
18814
+ *
18815
+ * BUG FIX: Account for pendingConnections to avoid reporting capacity
18816
+ * when concurrent connection creations are in progress. Previously,
18817
+ * hasCapacity() could return true even when all slots were about to be
18818
+ * filled by in-flight createConnection() calls, causing over-allocation.
18567
18819
  */
18568
18820
  hasCapacity(provider) {
18569
18821
  const pool = this.pools.get(provider);
@@ -18577,7 +18829,8 @@ var McpClientPool = class extends EventEmitter {
18577
18829
  connectedCount++;
18578
18830
  }
18579
18831
  }
18580
- return connectedCount < this.config.maxConnectionsPerProvider;
18832
+ const totalAllocated = connectedCount + pool.pendingConnections;
18833
+ return totalAllocated < this.config.maxConnectionsPerProvider;
18581
18834
  }
18582
18835
  // ============================================
18583
18836
  // Private Methods
@@ -18700,6 +18953,10 @@ var McpClientPool = class extends EventEmitter {
18700
18953
  }
18701
18954
  }
18702
18955
  for (const pooledClient of toRemove) {
18956
+ if (pooledClient.inUse) {
18957
+ logger.debug("[MCP Pool] Skipping idle client removal - now in use", { provider });
18958
+ continue;
18959
+ }
18703
18960
  logger.debug("[MCP Pool] Closing idle connection", {
18704
18961
  provider,
18705
18962
  idleTimeMs: now - pooledClient.lastUsed