@keyou007/agent-bot 0.1.16-alpha.3 → 0.1.16-alpha.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.
@@ -28,7 +28,7 @@ const SESSION_PROMPT_PREVIEW_LENGTH = 50;
28
28
  const DIRECTORY_PAGE_SIZE = 15;
29
29
  const RESET_HISTORY_PAGE_SIZE = 10;
30
30
  const MAX_LLM_TURN_RETRIES = 3;
31
- const RECOVERY_ACTIVITY_WINDOW_MS = 5 * 60 * 1_000;
31
+ const RECOVERY_ACTIVITY_WINDOW_MS = 10 * 60 * 1_000;
32
32
  const RECOVERY_HEARTBEAT_INTERVAL_MS = 60 * 1_000;
33
33
  const SHELL_COMMAND_JOB_POLL_INTERVAL_MS = 1_000;
34
34
  const SHELL_COMMAND_JOB_RELAUNCH_DELAY_MS = 5_000;
@@ -1439,6 +1439,7 @@ export class ProxySessionController {
1439
1439
  return recovery;
1440
1440
  }
1441
1441
  async runStartupRecovery() {
1442
+ await this.finishPersistedCancellationRequests();
1442
1443
  await this.recoverShellCommandJobs();
1443
1444
  this.backfillTurnAttemptsForUpgrade();
1444
1445
  const attempts = this.store.listIncompleteTurnAttempts();
@@ -1464,6 +1465,31 @@ export class ProxySessionController {
1464
1465
  }
1465
1466
  this.restorePersistedQueuedPrompts();
1466
1467
  }
1468
+ async finishPersistedCancellationRequests() {
1469
+ for (const attempt of this.store.listCancellationRequestedTurnAttempts()) {
1470
+ const session = this.store.getSession(attempt.localSessionId);
1471
+ if (!attempt.turnId) {
1472
+ this.store.updateTurnAttempt(attempt.attemptId, { status: "cancelled" });
1473
+ if (session?.status === "starting" || session?.status === "running") {
1474
+ this.store.updateSession(session.localSessionId, { status: "ready" });
1475
+ }
1476
+ continue;
1477
+ }
1478
+ if (session) {
1479
+ this.outbound.registerSession(session.localSessionId, attempt.contextKey, session.title, session.cwd, this.agentLabel(session.agentName));
1480
+ }
1481
+ try {
1482
+ await this.handleRuntimeEvent({
1483
+ type: "turn_cancelled",
1484
+ sessionId: attempt.localSessionId,
1485
+ turnId: attempt.turnId,
1486
+ });
1487
+ }
1488
+ catch (error) {
1489
+ this.logger.warn({ error, attemptId: attempt.attemptId, turnId: attempt.turnId }, "Failed to finish a persisted user-requested turn cancellation.");
1490
+ }
1491
+ }
1492
+ }
1467
1493
  backfillTurnAttemptsForUpgrade() {
1468
1494
  for (const session of this.store.listAllSessions()) {
1469
1495
  if (session.status !== "starting" && session.status !== "running" && session.status !== "ready")
@@ -1521,7 +1547,7 @@ export class ProxySessionController {
1521
1547
  const session = this.store.getSession(attempt.localSessionId);
1522
1548
  const oldCardTurnId = attempt.pendingTurnId ?? attempt.turnId;
1523
1549
  if (session && oldCardTurnId && this.outbound.canRoute(attempt.contextKey)) {
1524
- await this.outbound.interruptTurnForRecovery(session.localSessionId, attempt.contextKey, oldCardTurnId, "执行中断已超过 5 分钟,未自动恢复。").catch((error) => {
1550
+ await this.outbound.interruptTurnForRecovery(session.localSessionId, attempt.contextKey, oldCardTurnId, "执行中断已超过 10 分钟,未自动恢复。").catch((error) => {
1525
1551
  this.logger.warn({ error, attemptId: attempt.attemptId, turnId: oldCardTurnId }, "Failed to close an expired interrupted thinking card.");
1526
1552
  });
1527
1553
  }
@@ -2382,17 +2408,68 @@ export class ProxySessionController {
2382
2408
  });
2383
2409
  }
2384
2410
  let turnId;
2411
+ let runtimeText = text;
2412
+ let cardPrompt = options.displayPrompt ?? text;
2413
+ let retriedStart = false;
2385
2414
  try {
2386
- const pendingTurnId = await this.outbound.startPendingTurn(loaded.record.localSessionId, loaded.record.contextKey, title, replyTarget, options.displayPrompt ?? text);
2387
- this.store.updateTurnAttempt(attemptId, { pendingTurnId: pendingTurnId ?? null });
2388
- turnId = await loaded.runtime.startTurn(loaded.record.localSessionId, runtimePrompt(text, localImagePaths));
2415
+ while (true) {
2416
+ let pendingTurnId;
2417
+ try {
2418
+ pendingTurnId = await this.outbound.startPendingTurn(loaded.record.localSessionId, loaded.record.contextKey, title, replyTarget, cardPrompt);
2419
+ }
2420
+ catch (error) {
2421
+ this.store.updateTurnAttempt(attemptId, {
2422
+ status: options.preserveAttemptOnFailure ? "recovering" : "failed",
2423
+ });
2424
+ throw error;
2425
+ }
2426
+ this.store.updateTurnAttempt(attemptId, { pendingTurnId: pendingTurnId ?? null });
2427
+ try {
2428
+ turnId = await loaded.runtime.startTurn(loaded.record.localSessionId, runtimePrompt(runtimeText, localImagePaths));
2429
+ break;
2430
+ }
2431
+ catch (error) {
2432
+ const message = error instanceof Error ? error.message : String(error);
2433
+ const attempt = this.store.getTurnAttempt(attemptId);
2434
+ const retryAttempt = attempt
2435
+ && isRetryableLlmTurnFailure(message)
2436
+ && attempt.retryCount < MAX_LLM_TURN_RETRIES
2437
+ ? this.store.prepareUnstartedTurnAttemptRetry(attemptId)
2438
+ : undefined;
2439
+ if (!retryAttempt) {
2440
+ const exhausted = attempt
2441
+ && isRetryableLlmTurnFailure(message)
2442
+ && attempt.retryCount >= MAX_LLM_TURN_RETRIES;
2443
+ const finalMessage = exhausted
2444
+ ? `${message}\n\n已自动重试 ${MAX_LLM_TURN_RETRIES} 次,仍未成功。`
2445
+ : message;
2446
+ this.store.updateTurnAttempt(attemptId, {
2447
+ status: options.preserveAttemptOnFailure ? "recovering" : "failed",
2448
+ });
2449
+ await this.outbound.failPendingTurn(loaded.record.localSessionId, finalMessage);
2450
+ if (finalMessage === message)
2451
+ throw error;
2452
+ throw new Error(finalMessage, { cause: error });
2453
+ }
2454
+ retriedStart = true;
2455
+ this.llmRetryingSessions.add(loaded.record.localSessionId);
2456
+ this.store.audit(loaded.record.contextKey, "llm_turn_start_retry_started", {
2457
+ attemptId,
2458
+ retryNumber: retryAttempt.retryCount,
2459
+ maxRetries: MAX_LLM_TURN_RETRIES,
2460
+ error: message,
2461
+ });
2462
+ await this.outbound.failPendingTurn(loaded.record.localSessionId, `${message}\n\n检测到临时模型服务错误,正在自动重试(${retryAttempt.retryCount}/${MAX_LLM_TURN_RETRIES})。`).catch((presentationError) => {
2463
+ this.logger.warn({ error: presentationError, sessionId: loaded.record.localSessionId }, "Failed to finalize the pending thinking card before an LLM start retry.");
2464
+ });
2465
+ runtimeText = llmRetryRuntimePrompt(retryAttempt.promptText, retryAttempt.retryCount);
2466
+ cardPrompt = llmRetryCardPrompt(options.displayPrompt ?? retryAttempt.promptText, retryAttempt.retryCount);
2467
+ }
2468
+ }
2389
2469
  }
2390
- catch (error) {
2391
- this.store.updateTurnAttempt(attemptId, {
2392
- status: options.preserveAttemptOnFailure ? "recovering" : "failed",
2393
- });
2394
- await this.outbound.failPendingTurn(loaded.record.localSessionId, error instanceof Error ? error.message : String(error));
2395
- throw error;
2470
+ finally {
2471
+ if (retriedStart)
2472
+ this.llmRetryingSessions.delete(loaded.record.localSessionId);
2396
2473
  }
2397
2474
  const attempt = this.store.getTurnAttempt(attemptId);
2398
2475
  this.store.updateTurnAttempt(attemptId, {
@@ -3043,7 +3120,15 @@ export class ProxySessionController {
3043
3120
  await this.outbound.sendText(record.contextKey, "当前没有正在执行的任务。");
3044
3121
  return;
3045
3122
  }
3046
- await runtime.interruptRemoteTurn(record.remoteSessionId, turnId);
3123
+ const cancellationAttempt = this.store.requestTurnAttemptCancellation(turnId);
3124
+ try {
3125
+ await runtime.interruptRemoteTurn(record.remoteSessionId, turnId);
3126
+ }
3127
+ catch (error) {
3128
+ if (cancellationAttempt)
3129
+ this.store.restoreTurnAttemptAfterCancellationFailure(cancellationAttempt);
3130
+ throw error;
3131
+ }
3047
3132
  this.store.audit(record.contextKey, "turn_interrupt_sent", {
3048
3133
  localSessionId: record.localSessionId,
3049
3134
  remoteSessionId: record.remoteSessionId,
@@ -3058,7 +3143,15 @@ export class ProxySessionController {
3058
3143
  await this.outbound.sendText(record.contextKey, "当前没有正在执行的任务。");
3059
3144
  return;
3060
3145
  }
3061
- await loaded.runtime.cancelTurn(record.localSessionId, turnId);
3146
+ const cancellationAttempt = this.store.requestTurnAttemptCancellation(turnId);
3147
+ try {
3148
+ await loaded.runtime.cancelTurn(record.localSessionId, turnId);
3149
+ }
3150
+ catch (error) {
3151
+ if (cancellationAttempt)
3152
+ this.store.restoreTurnAttemptAfterCancellationFailure(cancellationAttempt);
3153
+ throw error;
3154
+ }
3062
3155
  }
3063
3156
  async resetCurrentSessionToTurn(contextKey, sessionId, turnId) {
3064
3157
  if (!sessionId || !turnId)
@@ -5756,7 +5849,8 @@ function isRetryableLlmTurnFailure(message) {
5756
5849
  /service unavailable|internal server error|bad gateway|gateway timeout/u,
5757
5850
  /(?:^|\D)(?:500|502|503|504)(?:\D|$)|upstream (?:error|failure|timeout)/u,
5758
5851
  /deadline exceeded|request timed out|request timeout|response timeout/u,
5759
- /connection (?:reset|closed|aborted)|network error|socket hang up/u,
5852
+ /connection (?:reset|closed|aborted|disconnected)|network error|socket hang up/u,
5853
+ /app server .*(?:closed|disconnected|exited|reset|aborted)/u,
5760
5854
  /stream (?:closed|disconnected|interrupted|error)/u,
5761
5855
  /\b(?:llm|model|provider|inference)\b.*\b(?:error|failed|failure|unavailable|timeout|timed out)\b/u,
5762
5856
  ].some((pattern) => pattern.test(normalized));