ai-project-manage-cli 8.1.26 → 8.1.28

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
@@ -1436,7 +1436,7 @@ function resolveHomeRepositoryAbsolutePaths(workdirInput, entries) {
1436
1436
  }
1437
1437
 
1438
1438
  // src/utils/git.ts
1439
- import { execFile } from "child_process";
1439
+ import { execFile, spawn } from "child_process";
1440
1440
  import { dirname as dirname3, join as join8 } from "path";
1441
1441
  import { fileURLToPath as fileURLToPath3 } from "url";
1442
1442
  import { promisify } from "util";
@@ -1505,7 +1505,9 @@ async function execGit(cwd, args, quietOrOptions = false) {
1505
1505
  const e = err;
1506
1506
  if (e.killed || e.signal === "SIGTERM" || e.code === "ETIMEDOUT") {
1507
1507
  throw new Error(
1508
- `[apm] git ${args.join(" ")} \u8D85\u65F6\uFF08>${timeoutMs}ms\uFF09\uFF0C\u8BF7\u68C0\u67E5\u5230\u8FDC\u7A0B\u7684\u7F51\u7EDC/SSH\uFF1B\u53EF\u7A0D\u540E\u91CD\u8BD5`
1508
+ `[apm] git ${args.join(
1509
+ " "
1510
+ )} \u8D85\u65F6\uFF08>${timeoutMs}ms\uFF09\uFF0C\u8BF7\u68C0\u67E5\u5230\u8FDC\u7A0B\u7684\u7F51\u7EDC/SSH\uFF1B\u53EF\u7A0D\u540E\u91CD\u8BD5`
1509
1511
  );
1510
1512
  }
1511
1513
  const detail = (e.stderr ?? e.message ?? String(err)).trim();
@@ -1528,33 +1530,67 @@ async function runGitScript(name, options) {
1528
1530
  const timeoutMs = options.timeoutMs ?? GIT_NETWORK_TIMEOUT_MS;
1529
1531
  const script = resolveGitScript(name);
1530
1532
  const args = [script, ...options.args ?? []];
1531
- try {
1532
- const { stdout, stderr } = await execFileAsync("bash", args, {
1533
+ return new Promise((resolve5, reject) => {
1534
+ const child = spawn("bash", args, {
1533
1535
  cwd: options.cwd,
1534
1536
  env: gitExecEnv(),
1535
- encoding: "utf8",
1536
- maxBuffer: 10 * 1024 * 1024,
1537
- timeout: timeoutMs,
1538
- killSignal: "SIGTERM"
1537
+ stdio: ["ignore", "pipe", "pipe"]
1539
1538
  });
1540
- if (stdout.trim()) process.stdout.write(stdout);
1541
- if (stderr.trim()) process.stderr.write(stderr);
1542
- return true;
1543
- } catch (err) {
1544
- const e = err;
1545
- if (e.stdout?.trim()) process.stdout.write(e.stdout);
1546
- if (e.stderr?.trim()) process.stderr.write(e.stderr);
1547
- if (e.code === skipCode) {
1548
- return false;
1549
- }
1550
- if (e.killed || e.signal === "SIGTERM" || e.code === "ETIMEDOUT") {
1551
- throw new Error(
1552
- `[apm] ${name} \u8D85\u65F6\uFF08>${timeoutMs}ms\uFF09\uFF0C\u8BF7\u68C0\u67E5\u5230\u8FDC\u7A0B\u7684\u7F51\u7EDC/SSH\uFF1B\u53EF\u7A0D\u540E\u91CD\u8BD5`
1553
- );
1554
- }
1555
- const detail = (e.stderr ?? e.message ?? String(err)).trim();
1556
- throw new Error(`[apm] ${name} \u5931\u8D25${detail ? `: ${detail}` : ""}`);
1557
- }
1539
+ let stderrBuf = "";
1540
+ let settled = false;
1541
+ let timedOut = false;
1542
+ const timer = timeoutMs > 0 ? setTimeout(() => {
1543
+ timedOut = true;
1544
+ child.kill("SIGTERM");
1545
+ const forceKill = setTimeout(() => {
1546
+ child.kill("SIGKILL");
1547
+ }, 2e3);
1548
+ forceKill.unref();
1549
+ }, timeoutMs) : null;
1550
+ const finish = (fn) => {
1551
+ if (settled) return;
1552
+ settled = true;
1553
+ if (timer) clearTimeout(timer);
1554
+ fn();
1555
+ };
1556
+ child.stdout?.on("data", (chunk) => {
1557
+ process.stdout.write(chunk);
1558
+ });
1559
+ child.stderr?.on("data", (chunk) => {
1560
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
1561
+ stderrBuf += text;
1562
+ process.stderr.write(chunk);
1563
+ });
1564
+ child.on("error", (err) => {
1565
+ finish(() => reject(new Error(`[apm] ${name} \u542F\u52A8\u5931\u8D25: ${err.message}`)));
1566
+ });
1567
+ child.on("close", (code, signal) => {
1568
+ finish(() => {
1569
+ if (timedOut || signal === "SIGTERM" || signal === "SIGKILL") {
1570
+ reject(
1571
+ new Error(
1572
+ `[apm] ${name} \u8D85\u65F6\uFF08>${timeoutMs}ms\uFF09\uFF0C\u8BF7\u770B\u4E0A\u65B9\u6700\u540E\u4E00\u6B65\u65E5\u5FD7\u5B9A\u4F4D\u5361\u70B9\uFF08\u591A\u4E3A ls-remote/fetch/push\uFF09\uFF1B\u68C0\u67E5\u5230\u8FDC\u7A0B\u7684\u7F51\u7EDC/SSH \u540E\u53EF\u7A0D\u540E\u91CD\u8BD5`
1573
+ )
1574
+ );
1575
+ return;
1576
+ }
1577
+ if (code === skipCode) {
1578
+ resolve5(false);
1579
+ return;
1580
+ }
1581
+ if (code === 0) {
1582
+ resolve5(true);
1583
+ return;
1584
+ }
1585
+ const detail = stderrBuf.trim();
1586
+ reject(
1587
+ new Error(
1588
+ `[apm] ${name} \u5931\u8D25${detail ? `: ${detail}` : code != null ? ` (exit ${code})` : ""}`
1589
+ )
1590
+ );
1591
+ });
1592
+ });
1593
+ });
1558
1594
  }
1559
1595
  async function commitWorkingTreeIfDirty(cwd, message) {
1560
1596
  await ensureGitRepo(cwd);
@@ -6,8 +6,11 @@ set -euo pipefail
6
6
 
7
7
  baseline="${1:?用法: ensure_baseline.sh <baseline-branch>}"
8
8
 
9
+ echo "[apm] ensure_baseline: fetch origin ${baseline} …"
9
10
  git fetch origin "$baseline"
11
+ echo "[apm] ensure_baseline: ls-remote origin ${baseline} …"
10
12
  if git ls-remote --heads origin "$baseline" | grep -q .; then
13
+ echo "[apm] ensure_baseline: origin/${baseline} 已确认"
11
14
  exit 0
12
15
  fi
13
16
 
@@ -9,9 +9,12 @@ baseline="${2:?用法: ensure_feature_branch.sh <branch> <baseline-branch>}"
9
9
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
10
10
 
11
11
  git rev-parse --git-dir >/dev/null
12
+ origin_url="$(git config --get remote.origin.url 2>/dev/null || true)"
13
+ echo "[apm] ensure_feature_branch: branch=${branch} baseline=${baseline}${origin_url:+ origin=${origin_url}}"
12
14
 
13
15
  # 有未提交改动时先 stash(含 untracked),再切/建分支,避免污染目标分支工作区
14
16
  if [[ -n "$(git status --porcelain)" ]]; then
17
+ echo "[apm] ensure_feature_branch: stash 未提交改动 …"
15
18
  git stash push -u -m "apm: ensure ${branch}"
16
19
  echo "[apm] 已 stash 未提交改动,再确认分支 ${branch}"
17
20
  fi
@@ -20,16 +23,26 @@ current="$(git rev-parse --abbrev-ref HEAD)"
20
23
  if [[ "$current" == "$branch" ]]; then
21
24
  echo "[apm] 已在分支 ${branch}"
22
25
  else
26
+ echo "[apm] ensure_feature_branch: 当前 ${current} → 准备 ${branch}"
23
27
  bash "$SCRIPT_DIR/ensure_baseline.sh" "$baseline"
24
28
 
29
+ echo "[apm] ensure_feature_branch: ls-remote origin ${branch} …"
25
30
  if git ls-remote --heads origin "$branch" | grep -q .; then
31
+ echo "[apm] ensure_feature_branch: 远程已有 ${branch},fetch + checkout …"
26
32
  git fetch origin "$branch"
27
33
  git checkout -B "$branch" "origin/${branch}"
28
34
  elif git show-ref --verify --quiet "refs/heads/${branch}"; then
35
+ echo "[apm] ensure_feature_branch: 本地已有 ${branch},checkout …"
29
36
  git checkout "$branch"
30
37
  echo "[apm] 分支 ${branch} 已存在,跳过创建"
31
38
  else
39
+ # 先切到远程基线(便于 post-checkout 等 hook 装依赖),再基于 origin/<baseline> 建特性分支(不基于本地基线)
40
+ echo "[apm] ensure_feature_branch: 本地/远程均无 ${branch},先切基线 origin/${baseline} …"
41
+ echo "[apm] ensure_feature_branch: fetch origin ${baseline} …"
32
42
  git fetch origin "$baseline"
43
+ echo "[apm] ensure_feature_branch: checkout -B ${baseline} origin/${baseline} …"
44
+ git checkout -B "$baseline" "origin/${baseline}"
45
+ echo "[apm] ensure_feature_branch: 基于 origin/${baseline} 创建 ${branch} …"
33
46
  set +e
34
47
  git checkout --no-track -b "$branch" "origin/${baseline}"
35
48
  create_status=$?
@@ -44,14 +57,19 @@ else
44
57
  else
45
58
  exit "$create_status"
46
59
  fi
60
+ else
61
+ echo "[apm] ensure_feature_branch: 已创建本地分支 ${branch}"
47
62
  fi
48
63
  fi
49
64
  fi
50
65
 
51
66
  # 本地已在目标分支时也要补推:上次 push 挂死/超时后常见「本地有、远端无」
67
+ echo "[apm] ensure_feature_branch: 再次 ls-remote 确认远程是否已有 ${branch} …"
52
68
  if ! git ls-remote --heads origin "$branch" | grep -q .; then
53
69
  echo "[apm] 远程尚无 ${branch},正在 push …"
54
70
  bash "$SCRIPT_DIR/push_code.sh" "$branch"
71
+ else
72
+ echo "[apm] ensure_feature_branch: 远程已有 ${branch},跳过 push"
55
73
  fi
56
74
 
57
75
  echo "[apm] 已就绪分支 ${branch}"
@@ -17,5 +17,8 @@ if ! git config --get remote.origin.url >/dev/null 2>&1; then
17
17
  exit 2
18
18
  fi
19
19
 
20
+ origin_url="$(git config --get remote.origin.url)"
21
+ echo "[apm] push: git push -u origin ${branch}(${origin_url})…"
20
22
  # 显式 origin + -u:避免裸 git push 因 upstream 与当前分支名不一致失败
21
23
  git push -u origin "$branch"
24
+ echo "[apm] push: 完成 ${branch}"
@@ -359,7 +359,7 @@ async function ensureWorkspaceApmDirForInit(cwd = resolveWorkdirPath()) {
359
359
  }
360
360
 
361
361
  // src/utils/git.ts
362
- import { execFile } from "child_process";
362
+ import { execFile, spawn } from "child_process";
363
363
  import { dirname, join as join3 } from "path";
364
364
  import { fileURLToPath } from "url";
365
365
  import { promisify } from "util";
@@ -428,7 +428,9 @@ async function execGit(cwd, args, quietOrOptions = false) {
428
428
  const e = err;
429
429
  if (e.killed || e.signal === "SIGTERM" || e.code === "ETIMEDOUT") {
430
430
  throw new Error(
431
- `[apm] git ${args.join(" ")} \u8D85\u65F6\uFF08>${timeoutMs}ms\uFF09\uFF0C\u8BF7\u68C0\u67E5\u5230\u8FDC\u7A0B\u7684\u7F51\u7EDC/SSH\uFF1B\u53EF\u7A0D\u540E\u91CD\u8BD5`
431
+ `[apm] git ${args.join(
432
+ " "
433
+ )} \u8D85\u65F6\uFF08>${timeoutMs}ms\uFF09\uFF0C\u8BF7\u68C0\u67E5\u5230\u8FDC\u7A0B\u7684\u7F51\u7EDC/SSH\uFF1B\u53EF\u7A0D\u540E\u91CD\u8BD5`
432
434
  );
433
435
  }
434
436
  const detail = (e.stderr ?? e.message ?? String(err)).trim();
@@ -458,33 +460,67 @@ async function runGitScript(name, options) {
458
460
  const timeoutMs = options.timeoutMs ?? GIT_NETWORK_TIMEOUT_MS;
459
461
  const script = resolveGitScript(name);
460
462
  const args = [script, ...options.args ?? []];
461
- try {
462
- const { stdout, stderr } = await execFileAsync("bash", args, {
463
+ return new Promise((resolve7, reject) => {
464
+ const child = spawn("bash", args, {
463
465
  cwd: options.cwd,
464
466
  env: gitExecEnv(),
465
- encoding: "utf8",
466
- maxBuffer: 10 * 1024 * 1024,
467
- timeout: timeoutMs,
468
- killSignal: "SIGTERM"
467
+ stdio: ["ignore", "pipe", "pipe"]
469
468
  });
470
- if (stdout.trim()) process.stdout.write(stdout);
471
- if (stderr.trim()) process.stderr.write(stderr);
472
- return true;
473
- } catch (err) {
474
- const e = err;
475
- if (e.stdout?.trim()) process.stdout.write(e.stdout);
476
- if (e.stderr?.trim()) process.stderr.write(e.stderr);
477
- if (e.code === skipCode) {
478
- return false;
479
- }
480
- if (e.killed || e.signal === "SIGTERM" || e.code === "ETIMEDOUT") {
481
- throw new Error(
482
- `[apm] ${name} \u8D85\u65F6\uFF08>${timeoutMs}ms\uFF09\uFF0C\u8BF7\u68C0\u67E5\u5230\u8FDC\u7A0B\u7684\u7F51\u7EDC/SSH\uFF1B\u53EF\u7A0D\u540E\u91CD\u8BD5`
483
- );
484
- }
485
- const detail = (e.stderr ?? e.message ?? String(err)).trim();
486
- throw new Error(`[apm] ${name} \u5931\u8D25${detail ? `: ${detail}` : ""}`);
487
- }
469
+ let stderrBuf = "";
470
+ let settled = false;
471
+ let timedOut = false;
472
+ const timer = timeoutMs > 0 ? setTimeout(() => {
473
+ timedOut = true;
474
+ child.kill("SIGTERM");
475
+ const forceKill = setTimeout(() => {
476
+ child.kill("SIGKILL");
477
+ }, 2e3);
478
+ forceKill.unref();
479
+ }, timeoutMs) : null;
480
+ const finish = (fn) => {
481
+ if (settled) return;
482
+ settled = true;
483
+ if (timer) clearTimeout(timer);
484
+ fn();
485
+ };
486
+ child.stdout?.on("data", (chunk) => {
487
+ process.stdout.write(chunk);
488
+ });
489
+ child.stderr?.on("data", (chunk) => {
490
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
491
+ stderrBuf += text;
492
+ process.stderr.write(chunk);
493
+ });
494
+ child.on("error", (err) => {
495
+ finish(() => reject(new Error(`[apm] ${name} \u542F\u52A8\u5931\u8D25: ${err.message}`)));
496
+ });
497
+ child.on("close", (code, signal) => {
498
+ finish(() => {
499
+ if (timedOut || signal === "SIGTERM" || signal === "SIGKILL") {
500
+ reject(
501
+ new Error(
502
+ `[apm] ${name} \u8D85\u65F6\uFF08>${timeoutMs}ms\uFF09\uFF0C\u8BF7\u770B\u4E0A\u65B9\u6700\u540E\u4E00\u6B65\u65E5\u5FD7\u5B9A\u4F4D\u5361\u70B9\uFF08\u591A\u4E3A ls-remote/fetch/push\uFF09\uFF1B\u68C0\u67E5\u5230\u8FDC\u7A0B\u7684\u7F51\u7EDC/SSH \u540E\u53EF\u7A0D\u540E\u91CD\u8BD5`
503
+ )
504
+ );
505
+ return;
506
+ }
507
+ if (code === skipCode) {
508
+ resolve7(false);
509
+ return;
510
+ }
511
+ if (code === 0) {
512
+ resolve7(true);
513
+ return;
514
+ }
515
+ const detail = stderrBuf.trim();
516
+ reject(
517
+ new Error(
518
+ `[apm] ${name} \u5931\u8D25${detail ? `: ${detail}` : code != null ? ` (exit ${code})` : ""}`
519
+ )
520
+ );
521
+ });
522
+ });
523
+ });
488
524
  }
489
525
  async function branchHasChangesAgainstBase(cwd, baseBranch, headBranch) {
490
526
  return runGitScript("branch_has_changes.sh", {
@@ -2401,6 +2437,17 @@ async function createApmCursorAgentStore(cfg2, workspaceRef) {
2401
2437
  runEvents: mirrorRunEventsForVisualization(cfg2, sqlite.runEvents)
2402
2438
  });
2403
2439
  }
2440
+ async function purgeLocalSqliteAgentData(input) {
2441
+ const agentId = input.agentId.trim();
2442
+ const workspaceRef = input.workspaceRef.trim();
2443
+ if (!agentId || !workspaceRef) return;
2444
+ const sqlite = await openSqliteLocalAgentStore(workspaceRef);
2445
+ const runIds = [...input.runIds ?? []];
2446
+ if (runIds.length > 0) {
2447
+ await sqlite.runEvents.delete({ filter: { runIds } });
2448
+ }
2449
+ await sqlite.checkpoints.delete({ filter: { agentIds: [agentId] } });
2450
+ }
2404
2451
 
2405
2452
  // src/commands/connect/core/worker/run-steer.ts
2406
2453
  var impl = null;
@@ -2427,6 +2474,10 @@ function deliverRunSteer(text) {
2427
2474
  }
2428
2475
 
2429
2476
  // src/commands/connect/cursor-agent.ts
2477
+ function isCorruptLocalAgentCheckpointError(err) {
2478
+ const message = err instanceof Error ? err.message : String(err);
2479
+ return /Corrupt local agent checkpoint/i.test(message) || /missing root blob/i.test(message);
2480
+ }
2430
2481
  var noopRemoteLogSync = {
2431
2482
  schedule(_session) {
2432
2483
  },
@@ -2475,6 +2526,19 @@ async function obtainAgent(ctx) {
2475
2526
  err instanceof Error ? err.message : err
2476
2527
  );
2477
2528
  ctx.onInvalidatePersistedAgentId?.();
2529
+ if (isCorruptLocalAgentCheckpointError(err)) {
2530
+ try {
2531
+ await purgeLocalSqliteAgentData({
2532
+ workspaceRef: ctx.cwd,
2533
+ agentId: savedAgentId
2534
+ });
2535
+ } catch (purgeErr) {
2536
+ console.warn(
2537
+ `[apm] \u6E05\u7406\u635F\u574F checkpoint \u5931\u8D25 agentId=${savedAgentId}:`,
2538
+ purgeErr instanceof Error ? purgeErr.message : purgeErr
2539
+ );
2540
+ }
2541
+ }
2478
2542
  }
2479
2543
  }
2480
2544
  try {
@@ -2489,6 +2553,9 @@ async function obtainAgent(ctx) {
2489
2553
  }
2490
2554
  }
2491
2555
  async function runCursorAgent(cfg2, ctx, options) {
2556
+ return runCursorAgentAttempt(cfg2, ctx, options, { allowCorruptRetry: true });
2557
+ }
2558
+ async function runCursorAgentAttempt(cfg2, ctx, options, attempt) {
2492
2559
  const signal = options.signal;
2493
2560
  if (signal?.aborted) {
2494
2561
  throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
@@ -2535,6 +2602,8 @@ async function runCursorAgent(cfg2, ctx, options) {
2535
2602
  const eventSession = options.eventSession ?? new EventSession(prompt);
2536
2603
  const syncRemoteLog = options.createRemoteLogSync ? options.createRemoteLogSync(agent.agentId) : noopRemoteLogSync;
2537
2604
  let activeRun;
2605
+ let retryCorruptWithNewAgent = false;
2606
+ const corruptAgentId = agent.agentId?.trim() || ctx.resumeAgentId?.trim();
2538
2607
  const abortRun = () => {
2539
2608
  if (!activeRun?.supports("cancel")) return;
2540
2609
  void activeRun.cancel().catch(() => void 0);
@@ -2603,58 +2672,94 @@ async function runCursorAgent(cfg2, ctx, options) {
2603
2672
  resultText: result.result
2604
2673
  });
2605
2674
  console.error(`[apm] ${failureMessage}`);
2606
- if (resumed) {
2675
+ if (attempt.allowCorruptRetry && resumed && isCorruptLocalAgentCheckpointError(failureMessage)) {
2676
+ retryCorruptWithNewAgent = true;
2607
2677
  invalidateAgentMapping();
2678
+ } else {
2679
+ if (resumed) {
2680
+ invalidateAgentMapping();
2681
+ }
2682
+ throw new Error(failureMessage);
2608
2683
  }
2609
- throw new Error(failureMessage);
2610
- }
2611
- if (result.status === "cancelled") {
2684
+ } else if (result.status === "cancelled") {
2612
2685
  throw new Error(`Cursor run \u5DF2\u53D6\u6D88: ${result.id}`);
2613
- }
2614
- console.log(`[apm] Cursor Agent \u5B8C\u6210 messageId=${ctx.messageId}`);
2615
- const artifacts = await agent.listArtifacts().catch(() => []);
2616
- const artifactDocuments = [];
2617
- for (const artifact of artifacts) {
2618
- try {
2619
- const content = (await agent.downloadArtifact(artifact.path)).toString(
2620
- "utf8"
2621
- );
2622
- artifactDocuments.push({ path: artifact.path, content });
2623
- } catch (err) {
2624
- console.warn(
2625
- `[apm] \u8BFB\u53D6\u4EA7\u7269\u5931\u8D25 path=${artifact.path}:`,
2626
- err instanceof Error ? err.message : err
2627
- );
2686
+ } else {
2687
+ console.log(`[apm] Cursor Agent \u5B8C\u6210 messageId=${ctx.messageId}`);
2688
+ const artifacts = await agent.listArtifacts().catch(() => []);
2689
+ const artifactDocuments = [];
2690
+ for (const artifact of artifacts) {
2691
+ try {
2692
+ const content = (await agent.downloadArtifact(artifact.path)).toString("utf8");
2693
+ artifactDocuments.push({ path: artifact.path, content });
2694
+ } catch (err) {
2695
+ console.warn(
2696
+ `[apm] \u8BFB\u53D6\u4EA7\u7269\u5931\u8D25 path=${artifact.path}:`,
2697
+ err instanceof Error ? err.message : err
2698
+ );
2699
+ }
2628
2700
  }
2701
+ return {
2702
+ runId: result.id,
2703
+ agentId: agent.agentId,
2704
+ status: result.status,
2705
+ result: result.result,
2706
+ durationMs: result.durationMs,
2707
+ usage: result.usage,
2708
+ modelId: result.model?.id?.trim() || ctx.model?.trim() || void 0,
2709
+ assistantText: eventSession.getAssistantText(),
2710
+ createPlan: eventSession.getCreatePlanContent(),
2711
+ artifacts,
2712
+ artifactDocuments
2713
+ };
2629
2714
  }
2630
- return {
2631
- runId: result.id,
2632
- agentId: agent.agentId,
2633
- status: result.status,
2634
- result: result.result,
2635
- durationMs: result.durationMs,
2636
- usage: result.usage,
2637
- modelId: result.model?.id?.trim() || ctx.model?.trim() || void 0,
2638
- assistantText: eventSession.getAssistantText(),
2639
- createPlan: eventSession.getCreatePlanContent(),
2640
- artifacts,
2641
- artifactDocuments
2642
- };
2643
2715
  } catch (err) {
2644
- if (err instanceof CursorAgentError) {
2716
+ if (retryCorruptWithNewAgent) {
2717
+ } else if (attempt.allowCorruptRetry && resumed && isCorruptLocalAgentCheckpointError(err)) {
2718
+ console.warn(
2719
+ `[apm] checkpoint \u635F\u574F\uFF08agentId=${corruptAgentId ?? "?"}\uFF09\uFF0C\u672C\u8F6E\u6539\u7528\u65B0 Agent`
2720
+ );
2721
+ retryCorruptWithNewAgent = true;
2722
+ invalidateAgentMapping();
2723
+ } else if (err instanceof CursorAgentError) {
2645
2724
  if (resumed) {
2646
2725
  invalidateAgentMapping();
2647
2726
  }
2648
2727
  throw new Error(
2649
2728
  `Cursor \u542F\u52A8\u5931\u8D25: ${err.message}${err.isRetryable ? "\uFF08\u53EF\u91CD\u8BD5\uFF09" : ""}`
2650
2729
  );
2730
+ } else {
2731
+ throw err;
2651
2732
  }
2652
- throw err;
2653
2733
  } finally {
2654
2734
  clearRunSteer();
2655
2735
  signal?.removeEventListener("abort", abortRun);
2656
2736
  await agent[Symbol.asyncDispose]();
2657
2737
  }
2738
+ if (retryCorruptWithNewAgent) {
2739
+ if (corruptAgentId) {
2740
+ try {
2741
+ await purgeLocalSqliteAgentData({
2742
+ workspaceRef: workdir,
2743
+ agentId: corruptAgentId
2744
+ });
2745
+ } catch (purgeErr) {
2746
+ console.warn(
2747
+ `[apm] \u6E05\u7406\u635F\u574F checkpoint \u5931\u8D25 agentId=${corruptAgentId}:`,
2748
+ purgeErr instanceof Error ? purgeErr.message : purgeErr
2749
+ );
2750
+ }
2751
+ }
2752
+ console.warn(
2753
+ `[apm] \u4E22\u5F03\u635F\u574F Agent${corruptAgentId ? ` agentId=${corruptAgentId}` : ""}\uFF0C\u65B0\u5EFA\u540E\u91CD\u8BD5 messageId=${ctx.messageId}`
2754
+ );
2755
+ return runCursorAgentAttempt(
2756
+ cfg2,
2757
+ { ...ctx, resumeAgentId: void 0 },
2758
+ options,
2759
+ { allowCorruptRetry: false }
2760
+ );
2761
+ }
2762
+ throw new Error("Cursor Agent \u672A\u8FD4\u56DE\u7ED3\u679C");
2658
2763
  }
2659
2764
 
2660
2765
  // src/commands/connect/webide-agent-registry.ts
@@ -4304,8 +4409,9 @@ ${assumptionAnswersText}` : msg2.content,
4304
4409
  if (startAction) {
4305
4410
  await webIdeDiscardTransitionPrepDraft(cfg2, taskId, startAction);
4306
4411
  }
4307
- if (detail.includes("\u590D\u7528 Agent") || detail.includes("resume")) {
4412
+ if (detail.includes("\u590D\u7528 Agent") || detail.includes("resume") || /Corrupt local agent checkpoint/i.test(detail) || /missing root blob/i.test(detail)) {
4308
4413
  clearWebIdeAgentId(workdir, taskId);
4414
+ clearWebIdeDesignAgentId(workdir, taskId);
4309
4415
  }
4310
4416
  try {
4311
4417
  await setError(cfg2, messageId, detail);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "8.1.26",
3
+ "version": "8.1.28",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,