@defai.digital/automatosx 11.3.3 → 11.3.4

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/index.js CHANGED
@@ -2270,251 +2270,148 @@ function isRateLimitError(error, providerName) {
2270
2270
  function isLimitError(error, providerName) {
2271
2271
  return isQuotaError(error, providerName) || isRateLimitError(error, providerName);
2272
2272
  }
2273
- var PROVIDER_ERROR_PATTERNS, GENERIC_ERROR_PATTERNS;
2273
+ var GEMINI_PATTERNS, CLAUDE_PATTERNS, OPENAI_PATTERNS, AX_CLI_PATTERNS, PROVIDER_ERROR_PATTERNS, GENERIC_ERROR_PATTERNS;
2274
2274
  var init_error_patterns = __esm({
2275
2275
  "src/providers/error-patterns.ts"() {
2276
2276
  init_esm_shims();
2277
+ GEMINI_PATTERNS = {
2278
+ quota: [
2279
+ "RESOURCE_EXHAUSTED",
2280
+ "resource_exhausted",
2281
+ "quotaExceeded",
2282
+ "quota exceeded",
2283
+ "quota limit reached",
2284
+ "daily quota exceeded",
2285
+ "monthly quota exceeded",
2286
+ "insufficient quota"
2287
+ ],
2288
+ rateLimit: [
2289
+ "RATE_LIMIT_EXCEEDED",
2290
+ "rate_limit_exceeded",
2291
+ "rate limit exceeded",
2292
+ "too many requests",
2293
+ "requests per minute exceeded",
2294
+ "requests per day exceeded"
2295
+ ],
2296
+ statusCodes: [429],
2297
+ // Too Many Requests
2298
+ errorCodes: [
2299
+ "RESOURCE_EXHAUSTED",
2300
+ "RATE_LIMIT_EXCEEDED",
2301
+ "QUOTA_EXCEEDED"
2302
+ ]
2303
+ };
2304
+ CLAUDE_PATTERNS = {
2305
+ quota: [
2306
+ "insufficient_quota",
2307
+ "quota_exceeded",
2308
+ "quota exceeded",
2309
+ "credit limit reached",
2310
+ "usage limit exceeded",
2311
+ "monthly quota exceeded"
2312
+ ],
2313
+ rateLimit: [
2314
+ "rate_limit_error",
2315
+ "rate limit exceeded",
2316
+ "overloaded_error",
2317
+ "overloaded",
2318
+ "too many requests",
2319
+ "requests per minute exceeded"
2320
+ ],
2321
+ statusCodes: [429, 529],
2322
+ // Too Many Requests, Overloaded
2323
+ errorCodes: [
2324
+ "rate_limit_error",
2325
+ "overloaded_error",
2326
+ "insufficient_quota"
2327
+ ]
2328
+ };
2329
+ OPENAI_PATTERNS = {
2330
+ quota: [
2331
+ "insufficient_quota",
2332
+ "quota_exceeded",
2333
+ "quota exceeded",
2334
+ "billing hard limit reached",
2335
+ "usage limit exceeded",
2336
+ "monthly quota exceeded",
2337
+ "credit limit reached"
2338
+ ],
2339
+ rateLimit: [
2340
+ "rate_limit_exceeded",
2341
+ "rate limit exceeded",
2342
+ "too_many_requests",
2343
+ "too many requests",
2344
+ "requests per minute exceeded",
2345
+ "tokens per minute exceeded",
2346
+ "rate limit reached"
2347
+ ],
2348
+ statusCodes: [429],
2349
+ // Too Many Requests
2350
+ errorCodes: [
2351
+ "insufficient_quota",
2352
+ "rate_limit_exceeded",
2353
+ "quota_exceeded",
2354
+ "billing_hard_limit_reached"
2355
+ ]
2356
+ };
2357
+ AX_CLI_PATTERNS = {
2358
+ quota: [
2359
+ // GLM patterns
2360
+ "quota exceeded",
2361
+ "quota limit reached",
2362
+ "insufficient quota",
2363
+ // OpenAI patterns (via ax-cli)
2364
+ "insufficient_quota",
2365
+ "quota_exceeded",
2366
+ "billing hard limit reached",
2367
+ "usage limit exceeded",
2368
+ "monthly quota exceeded",
2369
+ "credit limit reached",
2370
+ // Generic patterns
2371
+ "daily quota exceeded",
2372
+ "api quota exceeded"
2373
+ ],
2374
+ rateLimit: [
2375
+ // Common patterns
2376
+ "rate_limit_exceeded",
2377
+ "rate limit exceeded",
2378
+ "too_many_requests",
2379
+ "too many requests",
2380
+ "requests per minute exceeded",
2381
+ "tokens per minute exceeded",
2382
+ "rate limit reached",
2383
+ // Anthropic patterns (via ax-cli)
2384
+ "rate_limit_error",
2385
+ "overloaded_error",
2386
+ "overloaded",
2387
+ // xAI/Grok patterns
2388
+ "throttled",
2389
+ "request throttled"
2390
+ ],
2391
+ statusCodes: [429, 529],
2392
+ errorCodes: [
2393
+ "insufficient_quota",
2394
+ "rate_limit_exceeded",
2395
+ "quota_exceeded",
2396
+ "rate_limit_error",
2397
+ "overloaded_error",
2398
+ "RATE_LIMIT_EXCEEDED",
2399
+ "QUOTA_EXCEEDED"
2400
+ ]
2401
+ };
2277
2402
  PROVIDER_ERROR_PATTERNS = {
2278
- /**
2279
- * Google Gemini Error Patterns
2280
- *
2281
- * Gemini uses gRPC-style error codes and returns structured errors.
2282
- * Common quota errors include RESOURCE_EXHAUSTED and quotaExceeded.
2283
- */
2284
- gemini: {
2285
- quota: [
2286
- "RESOURCE_EXHAUSTED",
2287
- "resource_exhausted",
2288
- "quotaExceeded",
2289
- "quota exceeded",
2290
- "quota limit reached",
2291
- "daily quota exceeded",
2292
- "monthly quota exceeded",
2293
- "insufficient quota"
2294
- ],
2295
- rateLimit: [
2296
- "RATE_LIMIT_EXCEEDED",
2297
- "rate_limit_exceeded",
2298
- "rate limit exceeded",
2299
- "too many requests",
2300
- "requests per minute exceeded",
2301
- "requests per day exceeded"
2302
- ],
2303
- statusCodes: [429],
2304
- // Too Many Requests
2305
- errorCodes: [
2306
- "RESOURCE_EXHAUSTED",
2307
- "RATE_LIMIT_EXCEEDED",
2308
- "QUOTA_EXCEEDED"
2309
- ]
2310
- },
2311
- /**
2312
- * Anthropic Claude Error Patterns
2313
- *
2314
- * Claude returns different error types for rate limiting vs overload.
2315
- * Status 529 indicates temporary overload, 429 indicates rate limiting.
2316
- */
2317
- claude: {
2318
- quota: [
2319
- "insufficient_quota",
2320
- "quota_exceeded",
2321
- "quota exceeded",
2322
- "credit limit reached",
2323
- "usage limit exceeded",
2324
- "monthly quota exceeded"
2325
- ],
2326
- rateLimit: [
2327
- "rate_limit_error",
2328
- "rate limit exceeded",
2329
- "overloaded_error",
2330
- "overloaded",
2331
- "too many requests",
2332
- "requests per minute exceeded"
2333
- ],
2334
- statusCodes: [429, 529],
2335
- // Too Many Requests, Overloaded
2336
- errorCodes: [
2337
- "rate_limit_error",
2338
- "overloaded_error",
2339
- "insufficient_quota"
2340
- ]
2341
- },
2342
- /**
2343
- * OpenAI Error Patterns
2344
- *
2345
- * OpenAI provides detailed error messages for different types of limits.
2346
- * Distinguishes between quota exhaustion and rate limiting.
2347
- */
2348
- openai: {
2349
- quota: [
2350
- "insufficient_quota",
2351
- "quota_exceeded",
2352
- "quota exceeded",
2353
- "billing hard limit reached",
2354
- "usage limit exceeded",
2355
- "monthly quota exceeded",
2356
- "credit limit reached"
2357
- ],
2358
- rateLimit: [
2359
- "rate_limit_exceeded",
2360
- "rate limit exceeded",
2361
- "too_many_requests",
2362
- "too many requests",
2363
- "requests per minute exceeded",
2364
- "tokens per minute exceeded",
2365
- "rate limit reached"
2366
- ],
2367
- statusCodes: [429],
2368
- // Too Many Requests
2369
- errorCodes: [
2370
- "insufficient_quota",
2371
- "rate_limit_exceeded",
2372
- "quota_exceeded",
2373
- "billing_hard_limit_reached"
2374
- ]
2375
- },
2376
- /**
2377
- * Claude Code Provider (extension of Claude patterns)
2378
- */
2379
- "claude-code": {
2380
- quota: [
2381
- "insufficient_quota",
2382
- "quota_exceeded",
2383
- "quota exceeded",
2384
- "credit limit reached",
2385
- "usage limit exceeded",
2386
- "monthly quota exceeded"
2387
- ],
2388
- rateLimit: [
2389
- "rate_limit_error",
2390
- "rate limit exceeded",
2391
- "overloaded_error",
2392
- "overloaded",
2393
- "too many requests",
2394
- "requests per minute exceeded"
2395
- ],
2396
- statusCodes: [429, 529],
2397
- errorCodes: [
2398
- "rate_limit_error",
2399
- "overloaded_error",
2400
- "insufficient_quota"
2401
- ]
2402
- },
2403
- /**
2404
- * Gemini CLI Provider (same patterns as Gemini)
2405
- */
2406
- "gemini-cli": {
2407
- quota: [
2408
- "RESOURCE_EXHAUSTED",
2409
- "resource_exhausted",
2410
- "quotaExceeded",
2411
- "quota exceeded",
2412
- "quota limit reached",
2413
- "daily quota exceeded",
2414
- "monthly quota exceeded",
2415
- "insufficient quota"
2416
- ],
2417
- rateLimit: [
2418
- "RATE_LIMIT_EXCEEDED",
2419
- "rate_limit_exceeded",
2420
- "rate limit exceeded",
2421
- "too many requests",
2422
- "requests per minute exceeded",
2423
- "requests per day exceeded"
2424
- ],
2425
- statusCodes: [429],
2426
- errorCodes: [
2427
- "RESOURCE_EXHAUSTED",
2428
- "RATE_LIMIT_EXCEEDED",
2429
- "QUOTA_EXCEEDED"
2430
- ]
2431
- },
2432
- /**
2433
- * Codex CLI Provider (same patterns as OpenAI)
2434
- */
2435
- codex: {
2436
- quota: [
2437
- "insufficient_quota",
2438
- "quota_exceeded",
2439
- "quota exceeded",
2440
- "billing hard limit reached",
2441
- "usage limit exceeded",
2442
- "monthly quota exceeded",
2443
- "credit limit reached"
2444
- ],
2445
- rateLimit: [
2446
- "rate_limit_exceeded",
2447
- "rate limit exceeded",
2448
- "too_many_requests",
2449
- "too many requests",
2450
- "requests per minute exceeded",
2451
- "tokens per minute exceeded",
2452
- "rate limit reached"
2453
- ],
2454
- statusCodes: [429],
2455
- errorCodes: [
2456
- "insufficient_quota",
2457
- "rate_limit_exceeded",
2458
- "quota_exceeded",
2459
- "billing_hard_limit_reached"
2460
- ]
2461
- },
2462
- /**
2463
- * ax-cli Provider Error Patterns (v9.2.0)
2464
- *
2465
- * ax-cli is a multi-model provider supporting GLM, xAI, OpenAI, Anthropic, Ollama, etc.
2466
- * BUG FIX: Added missing patterns - previously fell back to generic patterns which
2467
- * may miss provider-specific errors from the underlying model providers.
2468
- *
2469
- * Combines patterns from all supported backends since ax-cli proxies to them.
2470
- */
2471
- "ax-cli": {
2472
- quota: [
2473
- // GLM patterns
2474
- "quota exceeded",
2475
- "quota limit reached",
2476
- "insufficient quota",
2477
- // OpenAI patterns (via ax-cli)
2478
- "insufficient_quota",
2479
- "quota_exceeded",
2480
- "billing hard limit reached",
2481
- "usage limit exceeded",
2482
- "monthly quota exceeded",
2483
- "credit limit reached",
2484
- // Anthropic patterns (via ax-cli)
2485
- "credit limit reached",
2486
- // Generic patterns
2487
- "daily quota exceeded",
2488
- "api quota exceeded"
2489
- ],
2490
- rateLimit: [
2491
- // Common patterns
2492
- "rate_limit_exceeded",
2493
- "rate limit exceeded",
2494
- "too_many_requests",
2495
- "too many requests",
2496
- "requests per minute exceeded",
2497
- "tokens per minute exceeded",
2498
- "rate limit reached",
2499
- // Anthropic patterns (via ax-cli)
2500
- "rate_limit_error",
2501
- "overloaded_error",
2502
- "overloaded",
2503
- // xAI/Grok patterns
2504
- "throttled",
2505
- "request throttled"
2506
- ],
2507
- statusCodes: [429, 529],
2508
- errorCodes: [
2509
- "insufficient_quota",
2510
- "rate_limit_exceeded",
2511
- "quota_exceeded",
2512
- "rate_limit_error",
2513
- "overloaded_error",
2514
- "RATE_LIMIT_EXCEEDED",
2515
- "QUOTA_EXCEEDED"
2516
- ]
2517
- }
2403
+ // Primary providers
2404
+ gemini: GEMINI_PATTERNS,
2405
+ claude: CLAUDE_PATTERNS,
2406
+ codex: OPENAI_PATTERNS,
2407
+ "ax-cli": AX_CLI_PATTERNS,
2408
+ // Aliases - reference same patterns to avoid duplication
2409
+ "gemini-cli": GEMINI_PATTERNS,
2410
+ // Alias for gemini
2411
+ "claude-code": CLAUDE_PATTERNS,
2412
+ // Alias for claude
2413
+ "openai": OPENAI_PATTERNS
2414
+ // Alias for codex (same underlying API)
2518
2415
  };
2519
2416
  GENERIC_ERROR_PATTERNS = {
2520
2417
  quota: [
@@ -2897,9 +2794,14 @@ var init_base_provider = __esm({
2897
2794
  });
2898
2795
  child.kill("SIGTERM");
2899
2796
  forceKillTimer = setTimeout(() => {
2900
- if (child.pid && !child.killed) {
2901
- logger.warn("Force killing child process", { pid: child.pid });
2902
- child.kill("SIGKILL");
2797
+ if (child.pid) {
2798
+ try {
2799
+ process.kill(child.pid, 0);
2800
+ logger.warn("Force killing child process", { pid: child.pid });
2801
+ child.kill("SIGKILL");
2802
+ } catch {
2803
+ logger.debug("Process already exited before SIGKILL", { pid: child.pid });
2804
+ }
2903
2805
  }
2904
2806
  }, _BaseProvider.SIGKILL_ESCALATION_MS);
2905
2807
  }
@@ -3080,9 +2982,14 @@ var init_base_provider = __esm({
3080
2982
  });
3081
2983
  child.kill("SIGTERM");
3082
2984
  forceKillTimer = setTimeout(() => {
3083
- if (child.pid && !child.killed) {
3084
- logger.warn("Force killing child process", { pid: child.pid });
3085
- child.kill("SIGKILL");
2985
+ if (child.pid) {
2986
+ try {
2987
+ process.kill(child.pid, 0);
2988
+ logger.warn("Force killing child process", { pid: child.pid });
2989
+ child.kill("SIGKILL");
2990
+ } catch {
2991
+ logger.debug("Process already exited before SIGKILL", { pid: child.pid });
2992
+ }
3086
2993
  }
3087
2994
  }, _BaseProvider.SIGKILL_ESCALATION_MS);
3088
2995
  }
@@ -4361,12 +4268,20 @@ var init_openai_provider = __esm({
4361
4268
  OpenAIProvider = class extends BaseProvider {
4362
4269
  hybridAdapter = null;
4363
4270
  providerConfig;
4271
+ /** BUG FIX: Track pending initialization to prevent race condition where
4272
+ * concurrent execute() calls could each trigger initializeHybridAdapter() */
4273
+ initializationPromise = null;
4274
+ /** BUG FIX: Track destroyed state to prevent use-after-destroy race condition */
4275
+ isDestroyed = false;
4364
4276
  constructor(config) {
4365
4277
  super(config);
4366
4278
  this.providerConfig = config;
4367
4279
  logger.debug("[OpenAI/Codex] Initialized", { mode: config.mode || "auto" });
4368
4280
  }
4369
4281
  async execute(request) {
4282
+ if (this.isDestroyed) {
4283
+ throw new Error("OpenAIProvider has been destroyed and cannot execute requests");
4284
+ }
4370
4285
  if (process.env.AX_MOCK_PROVIDERS === "true") {
4371
4286
  return {
4372
4287
  content: this.getMockResponse(),
@@ -4377,9 +4292,7 @@ var init_openai_provider = __esm({
4377
4292
  };
4378
4293
  }
4379
4294
  const startTime = Date.now();
4380
- if (!this.hybridAdapter) {
4381
- this.initializeHybridAdapter();
4382
- }
4295
+ await this.ensureInitialized();
4383
4296
  try {
4384
4297
  const result = await this.hybridAdapter.execute(
4385
4298
  request.prompt,
@@ -4404,7 +4317,30 @@ var init_openai_provider = __esm({
4404
4317
  throw this.handleError(error);
4405
4318
  }
4406
4319
  }
4407
- initializeHybridAdapter() {
4320
+ /**
4321
+ * BUG FIX: Ensure adapter is initialized exactly once, even with concurrent calls.
4322
+ * Uses a promise-based lock pattern to prevent race conditions where multiple
4323
+ * execute() calls could each trigger initializeHybridAdapter().
4324
+ */
4325
+ async ensureInitialized() {
4326
+ if (this.hybridAdapter) {
4327
+ return;
4328
+ }
4329
+ if (this.initializationPromise) {
4330
+ await this.initializationPromise;
4331
+ return;
4332
+ }
4333
+ this.initializationPromise = this.initializeHybridAdapter();
4334
+ try {
4335
+ await this.initializationPromise;
4336
+ } finally {
4337
+ this.initializationPromise = null;
4338
+ }
4339
+ }
4340
+ async initializeHybridAdapter() {
4341
+ if (this.hybridAdapter) {
4342
+ return;
4343
+ }
4408
4344
  const options = {
4409
4345
  mode: this.providerConfig.mode || "auto",
4410
4346
  cli: {
@@ -4442,6 +4378,7 @@ var init_openai_provider = __esm({
4442
4378
  this.hybridAdapter?.switchToCliMode();
4443
4379
  }
4444
4380
  async destroy() {
4381
+ this.isDestroyed = true;
4445
4382
  if (this.hybridAdapter) {
4446
4383
  await this.hybridAdapter.destroy();
4447
4384
  this.hybridAdapter = null;
@@ -5088,6 +5025,8 @@ var init_subagent_adapter = __esm({
5088
5025
  DEFAULT_TIMEOUT_MS = 3e5;
5089
5026
  DEFAULT_SUBAGENT_MAX_TOOL_ROUNDS = 100;
5090
5027
  SubagentAdapter = class {
5028
+ // PRODUCTION FIX: Use typed interfaces instead of 'any'
5029
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5091
5030
  orchestrator = null;
5092
5031
  subagents = /* @__PURE__ */ new Map();
5093
5032
  busySubagents = /* @__PURE__ */ new Set();
@@ -5283,12 +5222,29 @@ ${task.task}` : task.task;
5283
5222
  });
5284
5223
  let content = "";
5285
5224
  let totalTokens = 0;
5286
- for await (const chunk of subagent.processUserMessageStream(prompt)) {
5287
- if (chunk.type === "content") {
5288
- content += chunk.content || "";
5289
- }
5290
- if (chunk.type === "token_count" && chunk.tokenCount) {
5291
- totalTokens += chunk.tokenCount;
5225
+ const timeoutMs = this.options.timeout;
5226
+ let timeoutTimer = null;
5227
+ try {
5228
+ const timeoutPromise = new Promise((_, reject) => {
5229
+ timeoutTimer = setTimeout(() => {
5230
+ reject(new Error(`Subagent task timed out after ${timeoutMs}ms`));
5231
+ }, timeoutMs);
5232
+ });
5233
+ const streamPromise = (async () => {
5234
+ for await (const chunk of subagent.processUserMessageStream(prompt)) {
5235
+ if (chunk.type === "content") {
5236
+ content += chunk.content || "";
5237
+ }
5238
+ if (chunk.type === "token_count" && chunk.tokenCount) {
5239
+ totalTokens += chunk.tokenCount;
5240
+ }
5241
+ }
5242
+ })();
5243
+ await Promise.race([streamPromise, timeoutPromise]);
5244
+ } finally {
5245
+ if (timeoutTimer !== null) {
5246
+ clearTimeout(timeoutTimer);
5247
+ timeoutTimer = null;
5292
5248
  }
5293
5249
  }
5294
5250
  const latencyMs = Date.now() - startTime;
@@ -5357,7 +5313,7 @@ ${task.task}` : task.task;
5357
5313
  };
5358
5314
  } finally {
5359
5315
  if (release) {
5360
- release();
5316
+ await release();
5361
5317
  }
5362
5318
  }
5363
5319
  }
@@ -5401,11 +5357,16 @@ ${task.task}` : task.task;
5401
5357
  }
5402
5358
  /**
5403
5359
  * Get or create a subagent for the given config
5360
+ *
5361
+ * PRODUCTION FIX: Return typed SDKSubagent instead of 'any'
5362
+ * Note: We still need 'as unknown as SDKSubagent' cast since SDK types may differ,
5363
+ * but this is safer than raw 'any' as it enforces interface compliance.
5404
5364
  */
5405
5365
  async getOrCreateSubagent(config) {
5406
5366
  const key = this.getSubagentKey(config);
5407
- if (this.subagents.has(key)) {
5408
- return this.subagents.get(key);
5367
+ const cached = this.subagents.get(key);
5368
+ if (cached) {
5369
+ return cached;
5409
5370
  }
5410
5371
  try {
5411
5372
  const { createSubagent } = await import('@defai.digital/ax-cli/sdk');
@@ -5430,6 +5391,8 @@ ${task.task}` : task.task;
5430
5391
  }
5431
5392
  /**
5432
5393
  * Create a temporary subagent instance. Used when the cached subagent is busy.
5394
+ *
5395
+ * PRODUCTION FIX: Return typed SDKSubagent instead of 'any'
5433
5396
  */
5434
5397
  async createEphemeralSubagent(config) {
5435
5398
  const { createSubagent } = await import('@defai.digital/ax-cli/sdk');
@@ -5442,6 +5405,9 @@ ${task.task}` : task.task;
5442
5405
  * Acquire a subagent instance for a task. If a cached instance is already
5443
5406
  * running another task, create a short-lived one to avoid interleaved
5444
5407
  * streams from concurrent executions.
5408
+ *
5409
+ * PRODUCTION FIX (v11.3.4): Changed release() to return Promise for proper
5410
+ * async cleanup of ephemeral subagents. Callers must await release().
5445
5411
  */
5446
5412
  async acquireSubagent(config) {
5447
5413
  const key = this.getSubagentKey(config);
@@ -5456,23 +5422,27 @@ ${task.task}` : task.task;
5456
5422
  }
5457
5423
  return {
5458
5424
  subagent,
5459
- release: () => this.releaseSubagent(key)
5425
+ release: async () => this.releaseSubagent(key)
5460
5426
  };
5461
5427
  }
5462
5428
  logger.debug("Cached subagent busy, creating ephemeral instance", { key });
5463
5429
  const ephemeral = await this.createEphemeralSubagent(config);
5464
5430
  return {
5465
5431
  subagent: ephemeral,
5466
- release: () => {
5432
+ // PRODUCTION FIX: Properly await async dispose to ensure cleanup completes
5433
+ // before next task starts, preventing resource accumulation
5434
+ release: async () => {
5467
5435
  try {
5468
5436
  if (ephemeral.dispose) {
5469
5437
  const result = ephemeral.dispose();
5470
5438
  if (result instanceof Promise) {
5471
- result.catch((err) => logger.warn("Error disposing ephemeral subagent", { error: err instanceof Error ? err.message : String(err) }));
5439
+ await result;
5472
5440
  }
5473
5441
  }
5442
+ logger.debug("Ephemeral subagent disposed successfully", { key });
5474
5443
  } catch (error) {
5475
5444
  logger.warn("Error disposing ephemeral subagent", {
5445
+ key,
5476
5446
  error: error instanceof Error ? error.message : String(error)
5477
5447
  });
5478
5448
  }
@@ -5602,7 +5572,7 @@ ${task.task}` : task.task;
5602
5572
  };
5603
5573
  }
5604
5574
  });
5605
- var DEFAULT_CHECKPOINT_DIR, DEFAULT_MAX_CHECKPOINTS, FLUSH_TIMEOUT_MS, CheckpointAdapter;
5575
+ var DEFAULT_CHECKPOINT_DIR, DEFAULT_MAX_CHECKPOINTS, FLUSH_TIMEOUT_MS, FLUSH_MAX_RETRIES, FLUSH_RETRY_DELAY_MS, CheckpointAdapter;
5606
5576
  var init_checkpoint_adapter = __esm({
5607
5577
  "src/integrations/ax-cli-sdk/checkpoint-adapter.ts"() {
5608
5578
  init_esm_shims();
@@ -5610,6 +5580,8 @@ var init_checkpoint_adapter = __esm({
5610
5580
  DEFAULT_CHECKPOINT_DIR = ".automatosx/checkpoints";
5611
5581
  DEFAULT_MAX_CHECKPOINTS = 10;
5612
5582
  FLUSH_TIMEOUT_MS = 5e3;
5583
+ FLUSH_MAX_RETRIES = 3;
5584
+ FLUSH_RETRY_DELAY_MS = 500;
5613
5585
  CheckpointAdapter = class {
5614
5586
  checkpointDir;
5615
5587
  sdkCheckpointManager = null;
@@ -5781,11 +5753,15 @@ var init_checkpoint_adapter = __esm({
5781
5753
  let checkpoint = await this.load(workflowId);
5782
5754
  const phaseIndex = workflow.phases.findIndex((p) => p.id === phaseId);
5783
5755
  if (phaseIndex === -1) {
5784
- logger.warn("Phase not found in workflow", {
5756
+ const availablePhases = workflow.phases.map((p) => p.id);
5757
+ logger.error("Phase not found in workflow - cannot save checkpoint", {
5785
5758
  workflowId,
5786
5759
  phaseId,
5787
- availablePhases: workflow.phases.map((p) => p.id)
5760
+ availablePhases
5788
5761
  });
5762
+ throw new Error(
5763
+ `Invalid phase ID '${phaseId}' for workflow '${workflowId}'. Available phases: ${availablePhases.join(", ")}`
5764
+ );
5789
5765
  }
5790
5766
  if (!checkpoint) {
5791
5767
  checkpoint = {
@@ -5802,13 +5778,12 @@ var init_checkpoint_adapter = __esm({
5802
5778
  };
5803
5779
  }
5804
5780
  if (result.success) {
5805
- checkpoint.completedTasks.push(phaseId);
5806
- if (phaseIndex >= 0) {
5807
- checkpoint.phase = phaseIndex + 1;
5781
+ if (!checkpoint.completedTasks.includes(phaseId)) {
5782
+ checkpoint.completedTasks.push(phaseId);
5808
5783
  }
5809
- const phaseLabel = phaseIndex >= 0 ? `Phase ${phaseIndex + 1}` : "Unknown Phase";
5784
+ checkpoint.phase = phaseIndex + 1;
5810
5785
  checkpoint.context += `
5811
- [${phaseLabel}: ${phaseId}]
5786
+ [Phase ${phaseIndex + 1}: ${phaseId}]
5812
5787
  ${result.content.substring(0, 1e3)}
5813
5788
  `;
5814
5789
  if (result.tokensUsed && checkpoint.tokensUsed) {
@@ -5956,6 +5931,8 @@ ${result.content.substring(0, 1e3)}
5956
5931
  * Cleanup resources
5957
5932
  * BUG FIX: Flush pending checkpoint before destroying to prevent data loss
5958
5933
  * BUG FIX: Add timeout to prevent hanging during cleanup
5934
+ * PRODUCTION FIX (v11.3.4): Add retry logic for checkpoint flush to handle
5935
+ * transient filesystem errors (e.g., disk briefly unavailable)
5959
5936
  */
5960
5937
  async destroy() {
5961
5938
  if (this.autoSaveTimer) {
@@ -5963,22 +5940,44 @@ ${result.content.substring(0, 1e3)}
5963
5940
  this.autoSaveTimer = null;
5964
5941
  }
5965
5942
  if (this.pendingCheckpoint) {
5966
- try {
5967
- await Promise.race([
5968
- this.save(
5969
- this.pendingCheckpoint.workflowId,
5970
- this.pendingCheckpoint
5971
- ),
5972
- new Promise(
5973
- (_, reject) => setTimeout(() => reject(new Error("Checkpoint flush timed out")), FLUSH_TIMEOUT_MS)
5974
- )
5975
- ]);
5976
- logger.debug("Pending checkpoint flushed on destroy", {
5977
- workflowId: this.pendingCheckpoint.workflowId
5978
- });
5979
- } catch (error) {
5980
- logger.warn("Failed to flush pending checkpoint on destroy", {
5981
- error: error instanceof Error ? error.message : String(error)
5943
+ const checkpoint = this.pendingCheckpoint;
5944
+ let lastError = null;
5945
+ let success = false;
5946
+ for (let attempt = 1; attempt <= FLUSH_MAX_RETRIES; attempt++) {
5947
+ try {
5948
+ await Promise.race([
5949
+ this.save(checkpoint.workflowId, checkpoint),
5950
+ new Promise(
5951
+ (_, reject) => setTimeout(() => reject(new Error("Checkpoint flush timed out")), FLUSH_TIMEOUT_MS)
5952
+ )
5953
+ ]);
5954
+ logger.debug("Pending checkpoint flushed on destroy", {
5955
+ workflowId: checkpoint.workflowId,
5956
+ attempt
5957
+ });
5958
+ success = true;
5959
+ break;
5960
+ } catch (error) {
5961
+ lastError = error instanceof Error ? error : new Error(String(error));
5962
+ logger.warn("Checkpoint flush attempt failed", {
5963
+ workflowId: checkpoint.workflowId,
5964
+ attempt,
5965
+ maxAttempts: FLUSH_MAX_RETRIES,
5966
+ error: lastError.message
5967
+ });
5968
+ if (attempt < FLUSH_MAX_RETRIES) {
5969
+ await new Promise((resolve13) => setTimeout(resolve13, FLUSH_RETRY_DELAY_MS));
5970
+ }
5971
+ }
5972
+ }
5973
+ if (!success && lastError) {
5974
+ logger.error("Failed to flush checkpoint after all retries", {
5975
+ workflowId: checkpoint.workflowId,
5976
+ phase: checkpoint.phase,
5977
+ totalPhases: checkpoint.totalPhases,
5978
+ completedTasks: checkpoint.completedTasks.length,
5979
+ error: lastError.message,
5980
+ hint: "Checkpoint data may need manual recovery"
5982
5981
  });
5983
5982
  }
5984
5983
  this.pendingCheckpoint = null;
@@ -6225,20 +6224,39 @@ var init_instructions_bridge = __esm({
6225
6224
  * - Strip quotes from string values
6226
6225
  * - Handle empty values correctly
6227
6226
  */
6227
+ /**
6228
+ * PRODUCTION FIX (v11.3.4): Enhanced YAML parser with better edge case handling:
6229
+ * - Handle values containing colons (e.g., "description: Function: does X")
6230
+ * - Strip YAML comments (lines starting with # or inline # comments)
6231
+ * - Preserve escaped quotes within values
6232
+ * - Handle empty/null values correctly
6233
+ */
6228
6234
  parseYamlProfile(content) {
6229
6235
  const lines = content.split("\n");
6230
6236
  const profile = {};
6231
6237
  let currentKey = "";
6232
6238
  let instructionsBuffer = [];
6233
6239
  let inInstructions = false;
6234
- for (const line of lines) {
6240
+ for (let i = 0; i < lines.length; i++) {
6241
+ const line = lines[i];
6242
+ if (line === void 0) continue;
6243
+ if (line.trim().startsWith("#")) {
6244
+ continue;
6245
+ }
6235
6246
  const match = line.match(/^([\w-]+):\s*(.*)$/);
6236
6247
  if (match) {
6237
6248
  const [, rawKey, rawValue] = match;
6238
6249
  const key = rawKey ?? "";
6239
6250
  let value = rawValue ?? "";
6251
+ if (!value.startsWith('"') && !value.startsWith("'")) {
6252
+ const commentIndex = value.search(/\s+#/);
6253
+ if (commentIndex > 0) {
6254
+ value = value.substring(0, commentIndex).trim();
6255
+ }
6256
+ }
6240
6257
  if (value && (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'"))) {
6241
6258
  value = value.slice(1, -1);
6259
+ value = value.replace(/\\"/g, '"').replace(/\\'/g, "'");
6242
6260
  }
6243
6261
  const normalizedKey = key.replace(/-/g, "_");
6244
6262
  if (inInstructions && normalizedKey !== "instructions") {
@@ -6249,7 +6267,7 @@ var init_instructions_bridge = __esm({
6249
6267
  if (normalizedKey === "instructions") {
6250
6268
  inInstructions = true;
6251
6269
  currentKey = normalizedKey;
6252
- if (value && value.startsWith("|")) ; else if (value) {
6270
+ if (value && (value.startsWith("|") || value.startsWith(">"))) ; else if (value) {
6253
6271
  profile.instructions = value;
6254
6272
  inInstructions = false;
6255
6273
  }
@@ -6257,14 +6275,23 @@ var init_instructions_bridge = __esm({
6257
6275
  profile.expertise = [];
6258
6276
  currentKey = normalizedKey;
6259
6277
  } else if (normalizedKey && value !== void 0) {
6260
- profile[normalizedKey] = value;
6278
+ if (value === "null" || value === "~" || value === "") {
6279
+ profile[normalizedKey] = "";
6280
+ } else {
6281
+ profile[normalizedKey] = value;
6282
+ }
6261
6283
  currentKey = normalizedKey;
6262
6284
  }
6263
6285
  } else if (inInstructions) {
6264
- instructionsBuffer.push(line.replace(/^ /, ""));
6286
+ const trimmedLine = line.replace(/^ /, "");
6287
+ instructionsBuffer.push(trimmedLine);
6265
6288
  } else if (currentKey === "expertise" && line.trim().startsWith("- ")) {
6289
+ let listValue = line.trim().substring(2);
6290
+ if (listValue.startsWith('"') && listValue.endsWith('"') || listValue.startsWith("'") && listValue.endsWith("'")) {
6291
+ listValue = listValue.slice(1, -1);
6292
+ }
6266
6293
  profile.expertise = profile.expertise ?? [];
6267
- profile.expertise.push(line.trim().substring(2));
6294
+ profile.expertise.push(listValue);
6268
6295
  }
6269
6296
  }
6270
6297
  if (inInstructions && instructionsBuffer.length > 0) {
@@ -6643,7 +6670,7 @@ var init_mcp_manager = __esm({
6643
6670
  async addFromTemplate(templateName, env) {
6644
6671
  const configResult = await this.generateConfigFromTemplate(templateName, env);
6645
6672
  if (!configResult.ok) {
6646
- return configResult;
6673
+ return { ok: false, error: configResult.error };
6647
6674
  }
6648
6675
  return this.addServer(templateName, configResult.value);
6649
6676
  }
@@ -6653,7 +6680,7 @@ var init_mcp_manager = __esm({
6653
6680
  async listServers() {
6654
6681
  const configResult = await this.loadConfig();
6655
6682
  if (!configResult.ok) {
6656
- return configResult;
6683
+ return { ok: false, error: configResult.error };
6657
6684
  }
6658
6685
  return { ok: true, value: Object.keys(configResult.value) };
6659
6686
  }
@@ -6706,10 +6733,13 @@ var init_mcp_manager = __esm({
6706
6733
  const { MCPManagerV2 } = this.mcpModule;
6707
6734
  if (MCPManagerV2?.getAllServerStatuses) {
6708
6735
  const statuses = MCPManagerV2.getAllServerStatuses();
6709
- for (const status of statuses) {
6710
- this.serverStatuses.set(status.name, status);
6736
+ if (statuses && Array.isArray(statuses)) {
6737
+ for (const status of statuses) {
6738
+ this.serverStatuses.set(status.name, status);
6739
+ }
6740
+ return { ok: true, value: statuses };
6711
6741
  }
6712
- return { ok: true, value: statuses };
6742
+ return { ok: true, value: [] };
6713
6743
  }
6714
6744
  return { ok: true, value: Array.from(this.serverStatuses.values()) };
6715
6745
  } catch (error) {
@@ -6876,7 +6906,7 @@ var init_mcp_manager = __esm({
6876
6906
  logger.warn("Cannot enumerate servers for shutdown", {
6877
6907
  error: serversResult.error.message
6878
6908
  });
6879
- return serversResult;
6909
+ return { ok: false, error: serversResult.error };
6880
6910
  }
6881
6911
  const errors = [];
6882
6912
  for (const name of serversResult.value) {
@@ -6920,8 +6950,8 @@ var init_adapter2 = __esm({
6920
6950
  init_mcp_manager();
6921
6951
  DEFAULT_MAX_TOOL_ROUNDS = 400;
6922
6952
  AxCliSdkAdapter = class _AxCliSdkAdapter {
6953
+ // PRODUCTION FIX: Use typed SDKAgent interface instead of 'any'
6923
6954
  agent = null;
6924
- // Will type as LLMAgent after import
6925
6955
  agentConfig = null;
6926
6956
  initPromise = null;
6927
6957
  executionLock = Promise.resolve();
@@ -7181,8 +7211,11 @@ var init_adapter2 = __esm({
7181
7211
  userWantsCallbacks: !!(options.onStream || options.onTool),
7182
7212
  note: "SDK handles custom instructions and project memory"
7183
7213
  });
7184
- const chatHistory = typeof this.agent.getChatHistory === "function" ? this.agent.getChatHistory() : null;
7214
+ const chatHistory = this.agent && typeof this.agent.getChatHistory === "function" ? this.agent.getChatHistory() : null;
7185
7215
  const historyLengthBefore = Array.isArray(chatHistory) ? chatHistory.length : 0;
7216
+ if (!this.agent) {
7217
+ throw new Error("Agent not initialized");
7218
+ }
7186
7219
  for await (const chunk of this.agent.processUserMessageStream(prompt)) {
7187
7220
  if (chunk.type === "token_count" && chunk.tokenCount) {
7188
7221
  totalTokens += chunk.tokenCount;
@@ -7219,7 +7252,7 @@ var init_adapter2 = __esm({
7219
7252
  }
7220
7253
  }
7221
7254
  }
7222
- const fullHistory = typeof this.agent.getChatHistory === "function" ? this.agent.getChatHistory() : [];
7255
+ const fullHistory = this.agent && typeof this.agent.getChatHistory === "function" ? this.agent.getChatHistory() : [];
7223
7256
  const result = Array.isArray(fullHistory) ? fullHistory.slice(historyLengthBefore) : [];
7224
7257
  logger.debug("Chat history extraction", {
7225
7258
  historyLengthBefore,
@@ -7408,7 +7441,7 @@ var init_adapter2 = __esm({
7408
7441
  });
7409
7442
  this.agent.on("error", (error) => {
7410
7443
  logger.error("Agent error event", {
7411
- error: error.message
7444
+ error: error instanceof Error ? error.message : String(error)
7412
7445
  });
7413
7446
  });
7414
7447
  } catch (error) {
@@ -7499,7 +7532,7 @@ var init_adapter2 = __esm({
7499
7532
  );
7500
7533
  return {
7501
7534
  content: content2,
7502
- model: this.agent?.getCurrentModel() ?? "unknown",
7535
+ model: this.agent?.getCurrentModel?.() ?? "unknown",
7503
7536
  tokensUsed: tokensUsed2,
7504
7537
  latencyMs,
7505
7538
  finishReason: "stop",
@@ -7516,7 +7549,7 @@ var init_adapter2 = __esm({
7516
7549
  );
7517
7550
  return {
7518
7551
  content,
7519
- model: this.agent?.getCurrentModel() ?? "unknown",
7552
+ model: this.agent?.getCurrentModel?.() ?? "unknown",
7520
7553
  tokensUsed,
7521
7554
  latencyMs,
7522
7555
  finishReason: "stop",
@@ -7663,9 +7696,11 @@ var init_adapter2 = __esm({
7663
7696
  this.initError = null;
7664
7697
  if (!this.agent) return;
7665
7698
  try {
7666
- const result = this.agent.dispose();
7667
- if (result instanceof Promise) {
7668
- await result;
7699
+ if (this.agent.dispose) {
7700
+ const result = this.agent.dispose();
7701
+ if (result instanceof Promise) {
7702
+ await result;
7703
+ }
7669
7704
  }
7670
7705
  } catch (error) {
7671
7706
  logger.warn("Error during agent cleanup", {
@@ -7695,6 +7730,9 @@ var init_hybrid_adapter2 = __esm({
7695
7730
  sdkAdapter = null;
7696
7731
  cliAdapter = null;
7697
7732
  sdkOptions;
7733
+ // BUG FIX (v11.3.4): Track pending initialization to prevent race condition
7734
+ // where concurrent execute() calls could both try to initialize adapters
7735
+ initPromise = null;
7698
7736
  constructor(options = {}) {
7699
7737
  this.mode = options.mode || "auto";
7700
7738
  this.sdkOptions = options.sdk;
@@ -7771,11 +7809,30 @@ var init_hybrid_adapter2 = __esm({
7771
7809
  }
7772
7810
  /**
7773
7811
  * Ensure adapters are initialized based on mode
7812
+ *
7813
+ * BUG FIX (v11.3.4): Use initPromise to prevent race condition where
7814
+ * concurrent execute() calls could both attempt initialization before
7815
+ * activeMode is set. Now subsequent callers wait for the first init.
7774
7816
  */
7775
7817
  async ensureAdapters() {
7776
7818
  if (this.activeMode !== null) {
7777
7819
  return;
7778
7820
  }
7821
+ if (this.initPromise) {
7822
+ await this.initPromise;
7823
+ return;
7824
+ }
7825
+ this.initPromise = this.doInitializeAdapters();
7826
+ try {
7827
+ await this.initPromise;
7828
+ } finally {
7829
+ this.initPromise = null;
7830
+ }
7831
+ }
7832
+ /**
7833
+ * Perform actual adapter initialization (called only once via ensureAdapters)
7834
+ */
7835
+ async doInitializeAdapters() {
7779
7836
  if (this.mode === "sdk") {
7780
7837
  await this.initializeSdk(true);
7781
7838
  this.activeMode = "sdk";
@@ -27519,13 +27576,17 @@ init_logger();
27519
27576
  // src/providers/mcp/mcp-client.ts
27520
27577
  init_esm_shims();
27521
27578
  init_logger();
27522
- var McpClient = class extends EventEmitter {
27579
+ var McpClient = class _McpClient extends EventEmitter {
27523
27580
  config;
27524
27581
  process = null;
27525
27582
  readline = null;
27526
27583
  state;
27527
27584
  pendingRequests = /* @__PURE__ */ new Map();
27528
27585
  nextRequestId = 1;
27586
+ /** BUG FIX: Maximum request ID before wraparound to prevent Number precision loss.
27587
+ * JavaScript loses integer precision after Number.MAX_SAFE_INTEGER (2^53 - 1).
27588
+ * Using 1 billion as a safe wraparound point - still unique within any realistic session. */
27589
+ static MAX_REQUEST_ID = 1e9;
27529
27590
  serverInfo = null;
27530
27591
  constructor(config) {
27531
27592
  super();
@@ -27715,6 +27776,9 @@ var McpClient = class extends EventEmitter {
27715
27776
  throw new Error("MCP client not connected");
27716
27777
  }
27717
27778
  const id = this.nextRequestId++;
27779
+ if (this.nextRequestId > _McpClient.MAX_REQUEST_ID) {
27780
+ this.nextRequestId = 1;
27781
+ }
27718
27782
  const request = {
27719
27783
  jsonrpc: "2.0",
27720
27784
  id,
@@ -28045,10 +28109,13 @@ var McpClientPool = class extends EventEmitter {
28045
28109
  } else {
28046
28110
  this.removeFromPool(pool, pooledClient);
28047
28111
  this.emitEvent("connection_closed", provider, { reason: "connection_died" });
28112
+ pool.pendingConnections++;
28048
28113
  this.createConnection(provider).then((newClient) => {
28049
28114
  pool.clients.push(this.createPooledClient(newClient));
28050
28115
  waiter.resolve(newClient);
28051
- }).catch(waiter.reject);
28116
+ }).catch(waiter.reject).finally(() => {
28117
+ pool.pendingConnections--;
28118
+ });
28052
28119
  }
28053
28120
  }
28054
28121
  }
@@ -35226,7 +35293,7 @@ var IterateError = class extends Error {
35226
35293
  // src/core/iterate/iterate-classifier.ts
35227
35294
  init_esm_shims();
35228
35295
  init_logger();
35229
- var IterateClassifier = class {
35296
+ var IterateClassifier = class _IterateClassifier {
35230
35297
  config;
35231
35298
  patterns;
35232
35299
  /**
@@ -35307,7 +35374,7 @@ var IterateClassifier = class {
35307
35374
  reason = "Semantic similarity";
35308
35375
  } else {
35309
35376
  result = { type: "status_update", confidence: 0.3 };
35310
- method = "semantic_scoring";
35377
+ method = "fallback";
35311
35378
  reason = "No matches found, using default";
35312
35379
  }
35313
35380
  }
@@ -35322,7 +35389,10 @@ var IterateClassifier = class {
35322
35389
  reason,
35323
35390
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
35324
35391
  context: {
35325
- latencyMs: latency
35392
+ latencyMs: latency,
35393
+ // Store the original message for context window lookups
35394
+ // This is used by getRecentMessages() in the controller
35395
+ message
35326
35396
  }
35327
35397
  };
35328
35398
  logger.info("Classification complete", {
@@ -35453,19 +35523,25 @@ var IterateClassifier = class {
35453
35523
  * **Phase 1 (Week 1)**: Skeleton only
35454
35524
  * **Phase 2 (Week 2)**: Full pattern matching implementation
35455
35525
  */
35526
+ /**
35527
+ * Default priority order for classification types
35528
+ * genuine_question has highest priority to avoid false auto-responses
35529
+ * @private
35530
+ */
35531
+ static DEFAULT_PRIORITY_ORDER = [
35532
+ "genuine_question",
35533
+ "blocking_request",
35534
+ "rate_limit_or_context",
35535
+ "error_signal",
35536
+ "completion_signal",
35537
+ "confirmation_prompt",
35538
+ "status_update"
35539
+ ];
35456
35540
  classifyWithPatterns(message) {
35457
35541
  if (!this.patterns || this.compiledPatterns.size === 0) {
35458
35542
  return null;
35459
35543
  }
35460
- const priorityOrder = [
35461
- "genuine_question",
35462
- "blocking_request",
35463
- "rate_limit_or_context",
35464
- "error_signal",
35465
- "completion_signal",
35466
- "confirmation_prompt",
35467
- "status_update"
35468
- ];
35544
+ const priorityOrder = this.config.classificationPriorityOrder ?? _IterateClassifier.DEFAULT_PRIORITY_ORDER;
35469
35545
  for (const type of priorityOrder) {
35470
35546
  const patterns = this.compiledPatterns.get(type);
35471
35547
  if (!patterns || patterns.length === 0) {
@@ -35595,28 +35671,71 @@ var IterateClassifier = class {
35595
35671
  };
35596
35672
  }
35597
35673
  /**
35598
- * Compile regex pattern from string
35674
+ * Pattern validation errors collected during loading
35675
+ * @private
35676
+ */
35677
+ patternValidationErrors = [];
35678
+ /**
35679
+ * Compile regex pattern from string with validation
35599
35680
  *
35600
35681
  * @param patternStr - Pattern string
35601
- * @returns Compiled RegExp
35682
+ * @param classificationType - Type this pattern belongs to (for error reporting)
35683
+ * @returns Compiled RegExp, or null if invalid
35602
35684
  * @private
35603
35685
  */
35604
- compilePattern(patternStr) {
35686
+ compilePattern(patternStr, classificationType) {
35687
+ if (!patternStr || patternStr.trim() === "") {
35688
+ const errorMsg = "Pattern is empty or whitespace-only";
35689
+ logger.warn("Invalid pattern: empty", {
35690
+ type: classificationType,
35691
+ error: errorMsg
35692
+ });
35693
+ this.patternValidationErrors.push({
35694
+ pattern: patternStr || "(empty)",
35695
+ type: classificationType || "unknown",
35696
+ error: errorMsg
35697
+ });
35698
+ return null;
35699
+ }
35605
35700
  try {
35606
35701
  return new RegExp(patternStr, "i");
35607
35702
  } catch (error) {
35608
- logger.warn("Failed to compile pattern", {
35609
- pattern: patternStr,
35610
- error: error.message
35703
+ const errorMsg = error.message;
35704
+ logger.warn("Failed to compile pattern - invalid regex syntax", {
35705
+ pattern: patternStr.substring(0, 100),
35706
+ type: classificationType,
35707
+ error: errorMsg
35611
35708
  });
35612
- return /(?!)/;
35709
+ this.patternValidationErrors.push({
35710
+ pattern: patternStr.substring(0, 100),
35711
+ type: classificationType || "unknown",
35712
+ error: errorMsg
35713
+ });
35714
+ return null;
35613
35715
  }
35614
35716
  }
35717
+ /**
35718
+ * Get pattern validation errors from last load operation
35719
+ *
35720
+ * @returns Array of validation errors with pattern, type, and error message
35721
+ */
35722
+ getPatternValidationErrors() {
35723
+ return [...this.patternValidationErrors];
35724
+ }
35725
+ /**
35726
+ * Check if there are any pattern validation errors
35727
+ *
35728
+ * @returns True if there are validation errors
35729
+ */
35730
+ hasPatternValidationErrors() {
35731
+ return this.patternValidationErrors.length > 0;
35732
+ }
35615
35733
  /**
35616
35734
  * Compile all patterns from pattern library
35617
35735
  *
35618
35736
  * Compiles regex patterns and caches them for fast matching.
35619
35737
  * Patterns are sorted by priority (higher priority first).
35738
+ * Invalid patterns are tracked in patternValidationErrors for reporting.
35620
35739
  *
35621
35740
  * @private
35622
35741
  */
@@ -35625,22 +35744,45 @@ var IterateClassifier = class {
35625
35744
  return;
35626
35745
  }
35627
35746
  this.compiledPatterns.clear();
35747
+ this.patternValidationErrors = [];
35628
35748
  let totalCompiled = 0;
35749
+ let totalFailed = 0;
35629
35750
  for (const [type, patterns] of Object.entries(this.patterns.patterns)) {
35630
35751
  const classificationType = type;
35631
35752
  if (!Array.isArray(patterns) || patterns.length === 0) {
35632
35753
  continue;
35633
35754
  }
35634
- const compiled = patterns.map((p) => this.compilePattern(p.pattern)).filter((regex) => regex.source !== "(?!)");
35755
+ const compiled = [];
35756
+ for (const p of patterns) {
35757
+ const regex = this.compilePattern(p.pattern, classificationType);
35758
+ if (regex !== null) {
35759
+ compiled.push(regex);
35760
+ totalCompiled++;
35761
+ } else {
35762
+ totalFailed++;
35763
+ }
35764
+ }
35635
35765
  if (compiled.length > 0) {
35636
35766
  this.compiledPatterns.set(classificationType, compiled);
35637
- totalCompiled += compiled.length;
35638
35767
  }
35639
35768
  }
35640
- logger.debug("Patterns compiled", {
35641
- types: this.compiledPatterns.size,
35642
- totalPatterns: totalCompiled
35643
- });
35769
+ if (totalFailed > 0) {
35770
+ logger.warn("Pattern compilation completed with errors", {
35771
+ types: this.compiledPatterns.size,
35772
+ totalCompiled,
35773
+ totalFailed,
35774
+ errors: this.patternValidationErrors.map((e) => ({
35775
+ type: e.type,
35776
+ pattern: e.pattern.substring(0, 50),
35777
+ error: e.error
35778
+ }))
35779
+ });
35780
+ } else {
35781
+ logger.debug("Patterns compiled successfully", {
35782
+ types: this.compiledPatterns.size,
35783
+ totalPatterns: totalCompiled
35784
+ });
35785
+ }
35644
35786
  }
35645
35787
  };
35646
35788
 
@@ -36051,14 +36193,22 @@ var IterateModeController = class {
36051
36193
  * - Safety checks
36052
36194
  * - Budget monitoring
36053
36195
  *
36196
+ * **Orchestration Flow**:
36197
+ * 1. Initialize state and check initial budgets
36198
+ * 2. Execute agent via provider with hooks installed
36199
+ * 3. When response received, classify and determine action
36200
+ * 4. If 'continue': send auto-response and loop
36201
+ * 5. If 'pause'/'stop': return control to caller
36202
+ *
36054
36203
  * @param context - Execution context
36055
36204
  * @param options - Execution options
36205
+ * @param executor - Optional agent executor for actual execution (injected for testability)
36056
36206
  * @returns Execution result with iterate stats
36057
36207
  *
36058
- * **Phase 1 (Week 1)**: Skeleton implementation (no-op, falls back to standard execution)
36059
- * **Phase 3 (Week 3)**: Full implementation with orchestration loop
36208
+ * @since v6.4.0 - Initial skeleton
36209
+ * @since v11.3.4 - Full implementation with orchestration loop
36060
36210
  */
36061
- async executeWithIterate(context, options) {
36211
+ async executeWithIterate(context, options, executor) {
36062
36212
  await this.ensureInitialized();
36063
36213
  const sessionId = this.sessionManager ? (await this.sessionManager.createSession(context.task, context.agent.name)).id : randomUUID();
36064
36214
  this.initializeState(sessionId);
@@ -36083,29 +36233,125 @@ var IterateModeController = class {
36083
36233
  }
36084
36234
  }
36085
36235
  });
36236
+ const responseContents = [];
36237
+ let totalTokensUsed = { prompt: 0, completion: 0, total: 0 };
36238
+ let lastResponse;
36086
36239
  try {
36087
36240
  if (this.checkTimeBudget()) {
36088
36241
  throw new IterateError("Time budget exceeded before execution", "budget_exceeded");
36089
36242
  }
36090
- if (this.checkTokenBudget()) {
36091
- throw new IterateError("Token budget exceeded before execution", "budget_exceeded");
36243
+ let currentTask = context.task;
36244
+ let shouldContinue = true;
36245
+ while (shouldContinue) {
36246
+ const iterationContext = {
36247
+ ...context,
36248
+ task: currentTask
36249
+ };
36250
+ let result2;
36251
+ let action;
36252
+ if (executor) {
36253
+ let capturedAction;
36254
+ const iterateOptions = {
36255
+ ...options,
36256
+ iterateMode: true,
36257
+ hooks: {
36258
+ ...options.hooks,
36259
+ onPostResponse: async (response) => {
36260
+ const responseAction = await this.handleResponse(response);
36261
+ capturedAction = responseAction;
36262
+ return responseAction;
36263
+ }
36264
+ }
36265
+ };
36266
+ result2 = await executor.execute(iterationContext, iterateOptions);
36267
+ action = capturedAction || { type: "stop", reason: "No action from executor" };
36268
+ } else {
36269
+ const startTime = Date.now();
36270
+ const response = await context.provider.execute({
36271
+ prompt: currentTask,
36272
+ systemPrompt: context.agent.systemPrompt,
36273
+ model: context.agent.model,
36274
+ temperature: context.agent.temperature,
36275
+ maxTokens: context.agent.maxTokens,
36276
+ signal: options.signal
36277
+ });
36278
+ action = await this.handleResponse(response);
36279
+ result2 = {
36280
+ response,
36281
+ duration: Date.now() - startTime,
36282
+ context: iterationContext
36283
+ };
36284
+ }
36285
+ lastResponse = result2.response;
36286
+ if (result2.response.content) {
36287
+ responseContents.push(result2.response.content);
36288
+ }
36289
+ if (result2.response.tokensUsed) {
36290
+ totalTokensUsed.prompt += result2.response.tokensUsed.prompt || 0;
36291
+ totalTokensUsed.completion += result2.response.tokensUsed.completion || 0;
36292
+ totalTokensUsed.total += result2.response.tokensUsed.total || 0;
36293
+ }
36294
+ logger.debug("Iterate loop action", {
36295
+ actionType: action.type,
36296
+ reason: action.reason,
36297
+ hasAutoResponse: !!action.response
36298
+ });
36299
+ switch (action.type) {
36300
+ case "continue":
36301
+ if (action.response) {
36302
+ currentTask = action.response;
36303
+ this.emitEvent({
36304
+ type: "iterate.auto_response",
36305
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
36306
+ sessionId,
36307
+ payload: {
36308
+ response: action.response,
36309
+ iteration: this.state?.totalIterations || 0
36310
+ }
36311
+ });
36312
+ } else {
36313
+ shouldContinue = false;
36314
+ }
36315
+ break;
36316
+ case "stop":
36317
+ shouldContinue = false;
36318
+ this.emitEvent({
36319
+ type: "iterate.stop",
36320
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
36321
+ sessionId,
36322
+ payload: {
36323
+ reason: action.reason,
36324
+ stats: this.getStats()
36325
+ }
36326
+ });
36327
+ break;
36328
+ case "pause":
36329
+ shouldContinue = false;
36330
+ await this.pause(action.pauseReason || "user_interrupt", action.context);
36331
+ break;
36332
+ case "retry":
36333
+ logger.info("Retry requested, continuing with same task");
36334
+ break;
36335
+ case "no_op":
36336
+ shouldContinue = false;
36337
+ break;
36338
+ default:
36339
+ logger.warn("Unknown action type, stopping iterate loop", { actionType: action.type });
36340
+ shouldContinue = false;
36341
+ }
36092
36342
  }
36093
36343
  const duration = Date.now() - new Date(this.state.startedAt).getTime();
36344
+ const stats = this.getStats();
36345
+ if (this.statusRenderer) {
36346
+ this.statusRenderer.renderSummary(stats);
36347
+ }
36094
36348
  const result = {
36095
36349
  response: {
36096
- content: `Iterate mode framework initialized for: ${context.task}
36097
-
36098
- Session ID: ${sessionId}
36099
- State: ${JSON.stringify(this.getStats(), null, 2)}
36100
-
36101
- Note: Full execution loop will be implemented in future phases`,
36102
- tokensUsed: {
36103
- prompt: 0,
36104
- completion: 0,
36105
- total: 0
36106
- },
36350
+ content: responseContents.join("\n\n---\n\n") || "Iterate mode completed with no output",
36351
+ tokensUsed: totalTokensUsed,
36107
36352
  latencyMs: duration,
36108
- model: context.agent.name,
36353
+ model: lastResponse?.model || context.agent.model || "unknown",
36354
+ // 'stop' is standard LLM finish reason for both normal completion and pauses
36109
36355
  finishReason: "stop"
36110
36356
  },
36111
36357
  duration,
@@ -36114,30 +36360,40 @@ Note: Full execution loop will be implemented in future phases`,
36114
36360
  logger.info("Iterate mode execution completed", {
36115
36361
  sessionId,
36116
36362
  duration: result.duration,
36117
- stats: this.getStats()
36363
+ iterations: stats.totalIterations,
36364
+ autoResponses: stats.totalAutoResponses,
36365
+ tokens: stats.totalTokens,
36366
+ stopReason: stats.stopReason
36118
36367
  });
36119
36368
  return result;
36120
36369
  } catch (error) {
36121
36370
  const duration = this.state ? Date.now() - new Date(this.state.startedAt).getTime() : 0;
36371
+ const stats = this.getStats();
36122
36372
  logger.error("Iterate mode execution failed", {
36123
36373
  sessionId,
36124
36374
  error: error.message,
36125
- duration
36375
+ duration,
36376
+ stats
36377
+ });
36378
+ this.emitEvent({
36379
+ type: "iterate.error",
36380
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
36381
+ sessionId,
36382
+ payload: {
36383
+ error: error.message,
36384
+ stats
36385
+ }
36126
36386
  });
36127
36387
  return {
36128
36388
  response: {
36129
- content: `Iterate mode execution failed: ${error.message}
36130
-
36131
- Session ID: ${sessionId}
36132
- Error: ${error.message}${this.state ? `
36133
- State: ${JSON.stringify(this.getStats(), null, 2)}` : ""}`,
36134
- tokensUsed: {
36135
- prompt: 0,
36136
- completion: 0,
36137
- total: 0
36138
- },
36389
+ content: responseContents.length > 0 ? responseContents.join("\n\n---\n\n") + `
36390
+
36391
+ ---
36392
+
36393
+ Iterate mode error: ${error.message}` : `Iterate mode execution failed: ${error.message}`,
36394
+ tokensUsed: totalTokensUsed,
36139
36395
  latencyMs: duration,
36140
- model: context.agent.name,
36396
+ model: lastResponse?.model || context.agent.model || "unknown",
36141
36397
  finishReason: "error"
36142
36398
  },
36143
36399
  duration,
@@ -36203,16 +36459,12 @@ State: ${JSON.stringify(this.getStats(), null, 2)}` : ""}`,
36203
36459
  response
36204
36460
  });
36205
36461
  }
36206
- if (response.tokensUsed && response.tokensUsed.total) {
36207
- this.updateTokens(response.tokensUsed.total);
36208
- }
36209
- this.incrementIterations();
36210
- if (this.checkTokenBudget()) {
36462
+ if (this.checkIterationLimit()) {
36211
36463
  return {
36212
36464
  type: "pause",
36213
- reason: "Token budget exceeded",
36214
- pauseReason: "token_limit_exceeded",
36215
- context: `Maximum token limit of ${this.config.defaults.maxTotalTokens} reached`
36465
+ reason: "Iteration limit exceeded",
36466
+ pauseReason: "iteration_limit_exceeded",
36467
+ context: `Maximum iterations reached`
36216
36468
  };
36217
36469
  }
36218
36470
  if (this.checkTimeBudget()) {
@@ -36223,14 +36475,19 @@ State: ${JSON.stringify(this.getStats(), null, 2)}` : ""}`,
36223
36475
  context: `Maximum duration of ${this.config.defaults.maxDurationMinutes} minutes reached`
36224
36476
  };
36225
36477
  }
36226
- if (this.checkIterationLimit()) {
36478
+ const newTokens = response.tokensUsed?.total || 0;
36479
+ if (this.wouldExceedTokenBudget(newTokens)) {
36227
36480
  return {
36228
36481
  type: "pause",
36229
- reason: "Iteration limit exceeded",
36230
- pauseReason: "iteration_limit_exceeded",
36231
- context: `Maximum iterations reached`
36482
+ reason: "Token budget exceeded",
36483
+ pauseReason: "token_limit_exceeded",
36484
+ context: `Maximum token limit of ${this.config.defaults.maxTotalTokens} reached`
36232
36485
  };
36233
36486
  }
36487
+ if (newTokens > 0) {
36488
+ this.updateTokens(newTokens);
36489
+ }
36490
+ this.incrementIterations();
36234
36491
  const safetyCheck = this.checkSafetyGuards(response.content || "");
36235
36492
  if (!safetyCheck.safe && this.config.notifications.pauseOnHighRiskOperation) {
36236
36493
  const dangerousOps = safetyCheck.detectedOperations.map((op) => `${op.type}: ${op.match}`).join(", ");
@@ -36243,6 +36500,18 @@ State: ${JSON.stringify(this.getStats(), null, 2)}` : ""}`,
36243
36500
  }
36244
36501
  const action = this.determineAction(classification, response);
36245
36502
  if ((action.type === "continue" || action.type === "stop") && action.response === void 0) {
36503
+ if (this.state && this.state.currentStageAutoResponses >= this.config.defaults.maxAutoResponsesPerStage) {
36504
+ logger.warn("Auto-response limit reached, pausing instead of auto-responding", {
36505
+ currentStageAutoResponses: this.state.currentStageAutoResponses,
36506
+ limit: this.config.defaults.maxAutoResponsesPerStage
36507
+ });
36508
+ return {
36509
+ type: "pause",
36510
+ reason: "Auto-response limit exceeded",
36511
+ pauseReason: "auto_response_limit_exceeded",
36512
+ context: `Maximum auto-responses per stage (${this.config.defaults.maxAutoResponsesPerStage}) reached`
36513
+ };
36514
+ }
36246
36515
  const autoResponse = await this.responder.generateResponse(
36247
36516
  classification,
36248
36517
  {
@@ -36524,14 +36793,31 @@ State: ${JSON.stringify(this.getStats(), null, 2)}` : ""}`,
36524
36793
  const userInterventions = breakdown.genuine_question + breakdown.blocking_request;
36525
36794
  let stopReason = "completion";
36526
36795
  if (this.state.pauseReason) {
36527
- if (this.state.pauseReason === "time_limit_exceeded") {
36528
- stopReason = "timeout";
36529
- } else if (this.state.pauseReason === "token_limit_exceeded") {
36530
- stopReason = "token_limit";
36531
- } else if (this.state.pauseReason === "user_interrupt") {
36532
- stopReason = "user_interrupt";
36533
- } else if (this.state.pauseReason === "error_recovery_needed") {
36534
- stopReason = "error";
36796
+ switch (this.state.pauseReason) {
36797
+ case "time_limit_exceeded":
36798
+ stopReason = "timeout";
36799
+ break;
36800
+ case "token_limit_exceeded":
36801
+ stopReason = "token_limit";
36802
+ break;
36803
+ case "iteration_limit_exceeded":
36804
+ case "auto_response_limit_exceeded":
36805
+ stopReason = "iteration_limit";
36806
+ break;
36807
+ case "user_interrupt":
36808
+ case "genuine_question":
36809
+ case "blocking_request":
36810
+ stopReason = "user_interrupt";
36811
+ break;
36812
+ case "high_risk_operation":
36813
+ stopReason = "safety_paused";
36814
+ break;
36815
+ case "error_recovery_needed":
36816
+ stopReason = "error";
36817
+ break;
36818
+ default:
36819
+ logger.warn("Unknown pauseReason in getStats", { pauseReason: this.state.pauseReason });
36820
+ stopReason = "completion";
36535
36821
  }
36536
36822
  }
36537
36823
  const errorCount = breakdown.error_signal;
@@ -36625,50 +36911,86 @@ State: ${JSON.stringify(this.getStats(), null, 2)}` : ""}`,
36625
36911
  return true;
36626
36912
  }
36627
36913
  const percentElapsed = elapsedMinutes / maxMinutes * 100;
36914
+ if (!this.state.metadata.emittedTimeWarnings) {
36915
+ this.state.metadata.emittedTimeWarnings = [];
36916
+ }
36917
+ const emittedTimeWarnings = this.state.metadata.emittedTimeWarnings;
36628
36918
  for (const threshold of this.config.notifications.warnAtTimePercent) {
36629
- if (percentElapsed >= threshold && percentElapsed < threshold + 1) {
36919
+ if (percentElapsed >= threshold && !emittedTimeWarnings.includes(threshold)) {
36630
36920
  logger.warn(`Time budget at ${threshold}% of limit`, {
36631
36921
  elapsedMinutes: Math.round(elapsedMinutes),
36632
36922
  maxMinutes,
36633
36923
  percentUsed: Math.round(percentElapsed)
36634
36924
  });
36925
+ emittedTimeWarnings.push(threshold);
36926
+ this.state.lastWarningThreshold = {
36927
+ type: "time",
36928
+ percent: threshold,
36929
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
36930
+ };
36635
36931
  }
36636
36932
  }
36637
36933
  return false;
36638
36934
  }
36639
36935
  /**
36640
- * Check if token budget exceeded
36936
+ * Check if adding new tokens would exceed the token budget
36641
36937
  *
36642
- * @returns True if token budget exceeded
36938
+ * @param newTokens - Number of new tokens to add
36939
+ * @returns True if adding these tokens would exceed the budget
36643
36940
  * @private
36644
- * @since v8.6.0
36941
+ * @since v11.3.4
36645
36942
  *
36646
- * More reliable than cost checking as token counts don't change
36943
+ * This is a pre-check to prevent exceeding the budget (check BEFORE adding)
36647
36944
  */
36648
- checkTokenBudget() {
36945
+ wouldExceedTokenBudget(newTokens) {
36649
36946
  if (!this.state) return false;
36650
36947
  const maxTotalTokens = this.config.defaults.maxTotalTokens;
36651
36948
  if (!maxTotalTokens) {
36652
36949
  return false;
36653
36950
  }
36654
36951
  const currentTokens = this.state.totalTokens;
36655
- if (currentTokens >= maxTotalTokens) {
36656
- logger.warn("Token budget exceeded", {
36952
+ const projectedTokens = currentTokens + newTokens;
36953
+ if (projectedTokens > maxTotalTokens) {
36954
+ logger.warn("Token budget would be exceeded", {
36657
36955
  currentTokens,
36956
+ newTokens,
36957
+ projectedTokens,
36658
36958
  maxTotalTokens,
36659
36959
  sessionId: this.state.sessionId
36660
36960
  });
36661
36961
  return true;
36662
36962
  }
36963
+ return false;
36964
+ }
36965
+ /**
36966
+ * Check token budget warnings (called after tokens are added)
36967
+ *
36968
+ * @private
36969
+ * @since v8.6.0
36970
+ *
36971
+ * Emits warnings at configured thresholds (default: 75%, 90%)
36972
+ */
36973
+ checkTokenBudgetWarnings() {
36974
+ if (!this.state) return;
36975
+ const maxTotalTokens = this.config.defaults.maxTotalTokens;
36976
+ if (!maxTotalTokens) {
36977
+ return;
36978
+ }
36979
+ const currentTokens = this.state.totalTokens;
36663
36980
  const percentUsed = currentTokens / maxTotalTokens * 100;
36664
36981
  const warnThresholds = this.config.defaults.warnAtTokenPercent || [75, 90];
36982
+ if (!this.state.metadata.emittedTokenWarnings) {
36983
+ this.state.metadata.emittedTokenWarnings = [];
36984
+ }
36985
+ const emittedTokenWarnings = this.state.metadata.emittedTokenWarnings;
36665
36986
  for (const threshold of warnThresholds) {
36666
- if (percentUsed >= threshold && percentUsed < threshold + 1) {
36987
+ if (percentUsed >= threshold && !emittedTokenWarnings.includes(threshold)) {
36667
36988
  logger.warn(`Token budget at ${threshold}% of limit`, {
36668
36989
  currentTokens,
36669
36990
  maxTotalTokens,
36670
36991
  percentUsed: percentUsed.toFixed(1)
36671
36992
  });
36993
+ emittedTokenWarnings.push(threshold);
36672
36994
  this.state.lastWarningThreshold = {
36673
36995
  type: "tokens",
36674
36996
  percent: threshold,
@@ -36676,7 +36998,6 @@ State: ${JSON.stringify(this.getStats(), null, 2)}` : ""}`,
36676
36998
  };
36677
36999
  }
36678
37000
  }
36679
- return false;
36680
37001
  }
36681
37002
  /**
36682
37003
  * Check if iteration limit exceeded
@@ -36705,13 +37026,6 @@ State: ${JSON.stringify(this.getStats(), null, 2)}` : ""}`,
36705
37026
  });
36706
37027
  return true;
36707
37028
  }
36708
- if (this.state.currentStageAutoResponses >= this.config.defaults.maxAutoResponsesPerStage) {
36709
- logger.warn("Auto-response limit exceeded", {
36710
- currentStageAutoResponses: this.state.currentStageAutoResponses,
36711
- limit: this.config.defaults.maxAutoResponsesPerStage
36712
- });
36713
- return true;
36714
- }
36715
37029
  return false;
36716
37030
  }
36717
37031
  /**
@@ -36787,6 +37101,7 @@ State: ${JSON.stringify(this.getStats(), null, 2)}` : ""}`,
36787
37101
  currentStageTokens: this.state.currentStageTokens,
36788
37102
  maxTotalTokens: this.config.defaults.maxTotalTokens
36789
37103
  });
37104
+ this.checkTokenBudgetWarnings();
36790
37105
  }
36791
37106
  /**
36792
37107
  * Get recent messages from classification history
@@ -36947,9 +37262,9 @@ State: ${JSON.stringify(this.getStats(), null, 2)}` : ""}`,
36947
37262
  if (riskTolerance === "permissive") {
36948
37263
  return op.risk !== "HIGH";
36949
37264
  } else if (riskTolerance === "balanced") {
36950
- return op.risk === "LOW";
37265
+ return op.risk !== "HIGH";
36951
37266
  } else {
36952
- return false;
37267
+ return op.risk === "LOW";
36953
37268
  }
36954
37269
  });
36955
37270
  if (detectedOperations.length > 0) {