@keyou007/agent-bot 0.1.24-alpha.4 → 0.1.24-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +15 -3
  3. package/README.zh.md +15 -3
  4. package/dist/codex/AppServerPermissions.js +34 -0
  5. package/dist/codex/AppServerPermissions.js.map +1 -0
  6. package/dist/codex/CodexLocalActivityDetector.js +37 -5
  7. package/dist/codex/CodexLocalActivityDetector.js.map +1 -1
  8. package/dist/codex/CodexRuntime.js +37 -13
  9. package/dist/codex/CodexRuntime.js.map +1 -1
  10. package/dist/codex/ThreadWriterProcess.js +19 -5
  11. package/dist/codex/ThreadWriterProcess.js.map +1 -1
  12. package/dist/feishu/CardRenderer.js +80 -64
  13. package/dist/feishu/CardRenderer.js.map +1 -1
  14. package/dist/local-files/CsvPreview.js +148 -0
  15. package/dist/local-files/CsvPreview.js.map +1 -0
  16. package/dist/local-files/LocalFileViewerServer.js +53 -10
  17. package/dist/local-files/LocalFileViewerServer.js.map +1 -1
  18. package/dist/local-files/TurnPreviewPage.js +122 -11
  19. package/dist/local-files/TurnPreviewPage.js.map +1 -1
  20. package/dist/presentation/TurnStateReducer.js +39 -0
  21. package/dist/presentation/TurnStateReducer.js.map +1 -1
  22. package/dist/proxy/ProxySessionController.js +152 -46
  23. package/dist/proxy/ProxySessionController.js.map +1 -1
  24. package/docs/technical-reference.md +5 -3
  25. package/docs/technical-reference.zh.md +4 -2
  26. package/npm-shrinkwrap.json +2 -2
  27. package/package.json +1 -1
  28. package/skills/agent-bot/SKILL.md +6 -0
  29. package/src/codex/AppServerPermissions.ts +39 -0
  30. package/src/codex/CodexLocalActivityDetector.ts +34 -4
  31. package/src/codex/CodexRuntime.ts +44 -17
  32. package/src/codex/ThreadWriterProcess.ts +17 -9
  33. package/src/feishu/CardRenderer.ts +78 -64
  34. package/src/local-files/CsvPreview.ts +132 -0
  35. package/src/local-files/LocalFileViewerServer.ts +54 -11
  36. package/src/local-files/TurnPreviewPage.ts +125 -12
  37. package/src/presentation/TurnStateReducer.ts +41 -0
  38. package/src/presentation/turnViewTypes.ts +4 -0
  39. package/src/proxy/ProxySessionController.ts +137 -48
  40. package/src/runtime/types.ts +2 -0
@@ -48,6 +48,7 @@ const REMOTE_TURN_HISTORY_REFRESH_INTERVAL_MS = 30_000;
48
48
  const MAX_LLM_TURN_RETRIES = 3;
49
49
  const QUEUED_PROMPT_RETRY_DELAY_MS = 5_000;
50
50
  const RECOVERY_ACTIVITY_WINDOW_MS = 10 * 60 * 1_000;
51
+ const RECOVERY_RETRY_WINDOW_MS = 60_000;
51
52
  const RECOVERY_HEARTBEAT_INTERVAL_MS = 60 * 1_000;
52
53
  const SHELL_COMMAND_JOB_POLL_INTERVAL_MS = 1_000;
53
54
  const SHELL_COMMAND_JOB_RELAUNCH_DELAY_MS = 5_000;
@@ -241,6 +242,9 @@ export class ProxySessionController {
241
242
  retriedFailureTurnIds = new Set();
242
243
  queuedPromptRetryTimers = new Map();
243
244
  recoveryRetryTimers = new Map();
245
+ recoveryRetryStartedAt = new Map();
246
+ recoveredLiveAttempts = new Set();
247
+ recoveryClosed = false;
244
248
  pendingMergedForwards = new Map();
245
249
  pendingResourceForwards = new Map();
246
250
  relatedReactionMessageIds = new Map();
@@ -812,7 +816,7 @@ export class ProxySessionController {
812
816
  if (scopedAction.value.cardView === "status")
813
817
  await this.refreshStatusCardFromAction(scopedAction, sessionId);
814
818
  else if (scopedAction.value.cardView !== "safe_restart") {
815
- await this.refreshSessionsCardFromAction(scopedAction, { forceSwitchTaskId: sessionId });
819
+ await this.refreshSessionsCardFromAction(scopedAction);
816
820
  }
817
821
  }
818
822
  else if (kind === "session_turns" || kind === "session_turns_page") {
@@ -953,6 +957,7 @@ export class ProxySessionController {
953
957
  }
954
958
  }
955
959
  close() {
960
+ this.recoveryClosed = true;
956
961
  for (const unsubscribe of this.unsubscribe)
957
962
  unsubscribe();
958
963
  this.unsubscribe.length = 0;
@@ -977,6 +982,8 @@ export class ProxySessionController {
977
982
  for (const timer of this.recoveryRetryTimers.values())
978
983
  clearTimeout(timer);
979
984
  this.recoveryRetryTimers.clear();
985
+ this.recoveryRetryStartedAt.clear();
986
+ this.recoveredLiveAttempts.clear();
980
987
  for (const schedule of this.appServerReleaseSchedules.values()) {
981
988
  if (schedule.timer)
982
989
  clearTimeout(schedule.timer);
@@ -1727,12 +1734,16 @@ export class ProxySessionController {
1727
1734
  }
1728
1735
  if (!this.outbound.canRoute(attempt.contextKey))
1729
1736
  continue;
1737
+ this.recoveryRetryStartedAt.set(attempt.attemptId, Date.now());
1730
1738
  try {
1731
1739
  await this.recoverTurnAttempt(attempt);
1732
1740
  }
1733
1741
  catch (error) {
1734
- this.logger.error({ error, attemptId: attempt.attemptId, sessionId: attempt.localSessionId }, "Failed to recover an unfinished task; it will be retried while the server is running.");
1735
- this.scheduleRecoveryRetry(attempt.attemptId);
1742
+ await this.handleRecoveryError(attempt.attemptId, error);
1743
+ }
1744
+ finally {
1745
+ if (!this.recoveryRetryTimers.has(attempt.attemptId))
1746
+ this.recoveryRetryStartedAt.delete(attempt.attemptId);
1736
1747
  }
1737
1748
  }
1738
1749
  this.restorePersistedQueuedPrompts();
@@ -1815,22 +1826,36 @@ export class ProxySessionController {
1815
1826
  });
1816
1827
  }
1817
1828
  }
1818
- async expireStaleTurnAttempt(attempt) {
1819
- const session = this.store.getSession(attempt.localSessionId);
1829
+ async expireStaleTurnAttempt(attempt, message = "执行中断已超过 10 分钟,未自动恢复。") {
1830
+ clearTimeout(this.recoveryRetryTimers.get(attempt.attemptId));
1831
+ this.recoveryRetryTimers.delete(attempt.attemptId);
1832
+ this.recoveryRetryStartedAt.delete(attempt.attemptId);
1833
+ this.recoveredLiveAttempts.delete(attempt.attemptId);
1834
+ let session = this.store.getSession(attempt.localSessionId);
1835
+ this.store.updateTurnAttempt(attempt.attemptId, { status: "interrupted" });
1820
1836
  const oldCardTurnId = attempt.pendingTurnId ?? attempt.turnId;
1821
1837
  if (session && oldCardTurnId && this.outbound.canRoute(attempt.contextKey)) {
1822
- await this.outbound.interruptTurnForRecovery(session.localSessionId, attempt.contextKey, oldCardTurnId, "执行中断已超过 10 分钟,未自动恢复。").catch((error) => {
1838
+ await this.outbound.interruptTurnForRecovery(session.localSessionId, attempt.contextKey, oldCardTurnId, message).catch((error) => {
1823
1839
  this.logger.warn({ error, attemptId: attempt.attemptId, turnId: oldCardTurnId }, "Failed to close an expired interrupted thinking card.");
1824
1840
  });
1825
1841
  }
1826
- this.store.updateTurnAttempt(attempt.attemptId, { status: "interrupted" });
1842
+ session = this.store.getSession(attempt.localSessionId);
1827
1843
  if (!session || session.status === "closed")
1828
1844
  return;
1829
- if (attempt.turnId && session.lastTurnId === attempt.turnId) {
1845
+ const runtime = this.runtimes.forAgent(session.agentName);
1846
+ const activeTurnId = runtime.getSession(session.localSessionId)?.activeTurnId;
1847
+ const sameTurn = session.lastTurnId === attempt.turnId
1848
+ || session.lastTurnId === attempt.recoveredFromTurnId
1849
+ || !session.lastTurnId;
1850
+ if (sameTurn && session.lastTurnStatus !== "completed" && session.lastTurnStatus !== "failed"
1851
+ && (!activeTurnId || activeTurnId === attempt.turnId)) {
1852
+ // Forget monitoring, not the remote task. Never cancel an unknown writer.
1853
+ if (activeTurnId)
1854
+ runtime.forgetSession?.(session.localSessionId);
1830
1855
  this.store.updateRuntimeSession(session.localSessionId, { lastTurnStatus: "cancelled" });
1831
- }
1832
- if (session.status === "starting" || session.status === "running") {
1833
- this.store.updateSession(session.localSessionId, { status: "ready" });
1856
+ if (session.status === "starting" || session.status === "running") {
1857
+ this.store.updateSession(session.localSessionId, { status: "ready" });
1858
+ }
1834
1859
  }
1835
1860
  if (attempt.turnId && this.outbound.canRoute(attempt.contextKey)) {
1836
1861
  await this.finalizeTurnMessageReactions(attempt.turnId, "cancelled").catch((error) => {
@@ -1838,6 +1863,47 @@ export class ProxySessionController {
1838
1863
  });
1839
1864
  }
1840
1865
  }
1866
+ async handleRecoveryError(attemptId, error, activeTurnId) {
1867
+ if (this.recoveryClosed)
1868
+ return;
1869
+ const attempt = this.store.getTurnAttempt(attemptId);
1870
+ if (!attempt || !isIncompleteTurnAttemptStatus(attempt.status))
1871
+ return;
1872
+ this.logger.warn({ error, attemptId, sessionId: attempt.localSessionId }, "Task recovery failed.");
1873
+ const session = this.store.getSession(attempt.localSessionId);
1874
+ const writerConflict = session?.remoteSessionId === activeWriterThreadId(error) && Boolean(session?.remoteSessionId);
1875
+ const startedAt = this.recoveryRetryStartedAt.get(attemptId) ?? Date.now();
1876
+ this.recoveryRetryStartedAt.set(attemptId, startedAt);
1877
+ const localActiveTurnId = session ? this.runtimes.forAgent(session.agentName).getSession(session.localSessionId)?.activeTurnId : undefined;
1878
+ const monitoredTurnId = activeTurnId ?? (localActiveTurnId === attempt.turnId ? localActiveTurnId : undefined);
1879
+ // Once a live turn was confirmed, a monitoring outage must not make safe restart
1880
+ // consider it idle. Keep reconciling without cancelling or replaying the prompt.
1881
+ if (monitoredTurnId && this.recoveredLiveAttempts.has(attemptId) && !writerConflict) {
1882
+ this.scheduleRecoveryRetry(attemptId, monitoredTurnId);
1883
+ return;
1884
+ }
1885
+ if (writerConflict || error instanceof CodexVersionError || Date.now() - startedAt >= RECOVERY_RETRY_WINDOW_MS) {
1886
+ const message = writerConflict
1887
+ ? "自动恢复未完成:任务仍被另一个 App Server 占用。已停止自动重试,请确认占用进程后释放任务再继续。"
1888
+ : `自动恢复未完成,已停止自动重试。请检查 Agent 状态后重新发送消息。\n${error instanceof Error ? error.message : String(error)}`;
1889
+ await this.expireStaleTurnAttempt(attempt, message);
1890
+ try {
1891
+ const replyTarget = attempt.replyMessageId
1892
+ ? { messageId: attempt.replyMessageId, replyInThread: true } : undefined;
1893
+ await this.outbound.withReplyTarget(attempt.contextKey, replyTarget, async () => {
1894
+ await this.outbound.sendText(attempt.contextKey, message);
1895
+ if (writerConflict && session)
1896
+ await this.presentThreadWriterConflict(session, attempt.contextKey, error, false);
1897
+ });
1898
+ }
1899
+ catch (notificationError) {
1900
+ this.logger.warn({ error: notificationError, attemptId }, "Failed to notify about stopped task recovery.");
1901
+ }
1902
+ return;
1903
+ }
1904
+ this.store.updateTurnAttempt(attemptId, { status: "recovering" });
1905
+ this.scheduleRecoveryRetry(attemptId, monitoredTurnId);
1906
+ }
1841
1907
  persistActiveTurnHeartbeats() {
1842
1908
  for (const attempt of this.store.listIncompleteTurnAttempts()) {
1843
1909
  if (!attempt.turnId)
@@ -1851,6 +1917,8 @@ export class ProxySessionController {
1851
1917
  }
1852
1918
  }
1853
1919
  async recoverTurnAttempt(attempt, announce = true) {
1920
+ if (this.recoveryClosed)
1921
+ return;
1854
1922
  const session = this.store.getSession(attempt.localSessionId);
1855
1923
  if (!session || session.status === "closed") {
1856
1924
  this.store.updateTurnAttempt(attempt.attemptId, { status: "interrupted" });
@@ -1879,9 +1947,9 @@ export class ProxySessionController {
1879
1947
  remote = await runtime.readRemoteSession(session.remoteSessionId);
1880
1948
  }
1881
1949
  catch (error) {
1882
- if (error instanceof CodexVersionError)
1950
+ // A failed activity check is not proof of interruption. Do not replay a prompt blindly.
1951
+ if (sourceTurnId || !isUnmaterializedCodexThreadError(error))
1883
1952
  throw error;
1884
- this.logger.warn({ error, attemptId: attempt.attemptId, remoteSessionId: session.remoteSessionId }, "Failed to read the remote task during startup recovery; treating the old local execution as interrupted.");
1885
1953
  }
1886
1954
  }
1887
1955
  const remoteTurnBelongsToAttempt = remoteTurnMatchesAttempt(remote, attempt);
@@ -1927,14 +1995,22 @@ export class ProxySessionController {
1927
1995
  this.scheduleRecoveryRetry(attempt.attemptId, synchronized.activeTurnId);
1928
1996
  }
1929
1997
  scheduleRecoveryRetry(attemptId, activeTurnId) {
1930
- if (this.recoveryRetryTimers.has(attemptId))
1998
+ if (this.recoveryClosed || this.recoveryRetryTimers.has(attemptId))
1931
1999
  return;
2000
+ if (!this.recoveryRetryStartedAt.has(attemptId))
2001
+ this.recoveryRetryStartedAt.set(attemptId, Date.now());
1932
2002
  const timer = setTimeout(() => {
1933
2003
  this.recoveryRetryTimers.delete(attemptId);
1934
2004
  const attempt = this.store.getTurnAttempt(attemptId);
1935
- if (!attempt || !isIncompleteTurnAttemptStatus(attempt.status))
2005
+ if (!attempt || !isIncompleteTurnAttemptStatus(attempt.status)) {
2006
+ this.recoveryRetryStartedAt.delete(attemptId);
2007
+ this.recoveredLiveAttempts.delete(attemptId);
1936
2008
  return;
2009
+ }
1937
2010
  const retry = async () => {
2011
+ if (!activeTurnId && Date.now() - this.recoveryRetryStartedAt.get(attemptId) >= RECOVERY_RETRY_WINDOW_MS) {
2012
+ throw new Error("等待恢复超过 1 分钟,尚未确认执行已恢复。");
2013
+ }
1938
2014
  let recoveryAttempt = attempt;
1939
2015
  if (activeTurnId) {
1940
2016
  const session = this.store.getSession(attempt.localSessionId);
@@ -1942,6 +2018,8 @@ export class ProxySessionController {
1942
2018
  const runtime = this.runtimes.forAgent(session.agentName);
1943
2019
  const synchronized = await runtime.synchronizeSession(session.localSessionId);
1944
2020
  if (synchronized.activeTurnId === activeTurnId) {
2021
+ this.recoveredLiveAttempts.add(attemptId);
2022
+ this.recoveryRetryStartedAt.delete(attemptId);
1945
2023
  this.scheduleRecoveryRetry(attemptId, activeTurnId);
1946
2024
  return;
1947
2025
  }
@@ -1952,16 +2030,27 @@ export class ProxySessionController {
1952
2030
  }
1953
2031
  }
1954
2032
  await this.recoverTurnAttempt(recoveryAttempt, false);
2033
+ if (!this.recoveryRetryTimers.has(attemptId))
2034
+ this.recoveryRetryStartedAt.delete(attemptId);
1955
2035
  };
1956
2036
  void retry().catch((error) => {
1957
- this.logger.warn({ error, attemptId, sessionId: attempt.localSessionId }, "Failed to retry an unfinished task recovery.");
1958
- this.scheduleRecoveryRetry(attemptId, activeTurnId);
2037
+ return this.handleRecoveryError(attemptId, error, activeTurnId);
2038
+ }).catch((error) => {
2039
+ this.logger.error({ error, attemptId }, "Failed to finalize task recovery.");
1959
2040
  });
1960
2041
  }, 5_000);
1961
2042
  timer.unref?.();
1962
2043
  this.recoveryRetryTimers.set(attemptId, timer);
1963
2044
  }
1964
2045
  async reattachActiveRecoveredTurn(attempt, session, turnId, replyTarget) {
2046
+ // Confirm resume before replacing the old card or announcing a running turn.
2047
+ const current = this.store.getSession(session.localSessionId) ?? session;
2048
+ const loaded = await this.loadSession({
2049
+ ...current, contextKey: attempt.contextKey, status: "running", lastTurnId: turnId, lastTurnStatus: "running",
2050
+ });
2051
+ const pendingAttempt = this.store.getTurnAttempt(attempt.attemptId);
2052
+ if (!pendingAttempt || !isIncompleteTurnAttemptStatus(pendingAttempt.status))
2053
+ return;
1965
2054
  const oldCardTurnId = attempt.pendingTurnId ?? attempt.turnId;
1966
2055
  if (oldCardTurnId) {
1967
2056
  await this.outbound.interruptTurnForRecovery(session.localSessionId, attempt.contextKey, oldCardTurnId, "Agent Bot 已重启,执行仍在继续;进度已转移到新的思考卡片。");
@@ -1979,17 +2068,12 @@ export class ProxySessionController {
1979
2068
  startedAt: Date.now(),
1980
2069
  });
1981
2070
  this.store.updateTurnAttempt(attempt.attemptId, { pendingTurnId: null });
1982
- const current = this.store.getSession(session.localSessionId) ?? session;
1983
- const loaded = await this.loadSession({
1984
- ...current,
1985
- contextKey: attempt.contextKey,
1986
- status: "running",
1987
- lastTurnId: turnId,
1988
- lastTurnStatus: "running",
1989
- });
1990
2071
  const synchronized = await loaded.runtime.synchronizeSession(session.localSessionId);
1991
- if (synchronized.activeTurnId)
2072
+ this.recoveryRetryStartedAt.delete(attempt.attemptId);
2073
+ if (synchronized.activeTurnId) {
2074
+ this.recoveredLiveAttempts.add(attempt.attemptId);
1992
2075
  this.scheduleRecoveryRetry(attempt.attemptId, synchronized.activeTurnId);
2076
+ }
1993
2077
  }
1994
2078
  async continueInterruptedAttempt(attempt, session, sourceTurnId, replyTarget) {
1995
2079
  const oldCardTurnId = attempt.pendingTurnId ?? sourceTurnId;
@@ -3614,6 +3698,11 @@ export class ProxySessionController {
3614
3698
  }
3615
3699
  async handleRuntimeEvent(event) {
3616
3700
  if ("turnId" in event) {
3701
+ const recoveryAttempt = this.recoveryRetryStartedAt.size > 0
3702
+ ? this.store.findIncompleteTurnAttemptByTurnId(event.turnId) : undefined;
3703
+ if (recoveryAttempt && this.recoveryRetryStartedAt.has(recoveryAttempt.attemptId)) {
3704
+ this.recoveredLiveAttempts.add(recoveryAttempt.attemptId);
3705
+ }
3617
3706
  this.store.touchTurnAttempt(event.turnId);
3618
3707
  const source = this.store.getSession(event.sessionId);
3619
3708
  if (source?.remoteSessionId && this.isCodexSession(source)) {
@@ -3652,6 +3741,13 @@ export class ProxySessionController {
3652
3741
  this.store.updateRuntimeSession(event.sessionId, { lastTurnId: event.turnId, lastTurnStatus: "running" });
3653
3742
  }
3654
3743
  else if (event.type === "turn_completed" || event.type === "turn_cancelled" || event.type === "turn_failed") {
3744
+ const attempt = this.store.findIncompleteTurnAttemptByTurnId(event.turnId);
3745
+ if (attempt) {
3746
+ clearTimeout(this.recoveryRetryTimers.get(attempt.attemptId));
3747
+ this.recoveryRetryTimers.delete(attempt.attemptId);
3748
+ this.recoveryRetryStartedAt.delete(attempt.attemptId);
3749
+ this.recoveredLiveAttempts.delete(attempt.attemptId);
3750
+ }
3655
3751
  this.store.markTurnAttemptTerminal(event.turnId, event.type === "turn_completed" ? "completed" : event.type === "turn_cancelled" ? "cancelled" : "failed");
3656
3752
  this.store.updateRuntimeSession(event.sessionId, {
3657
3753
  lastTurnId: event.turnId,
@@ -5489,20 +5585,29 @@ export class ProxySessionController {
5489
5585
  const project = taskProjectInfo(entry);
5490
5586
  const projectActionSource = projectActionSources.get(project.key) ?? entry;
5491
5587
  const marker = entry.current ? "✅ " : entry.active ? "🟢 " : "";
5492
- const showStop = entry.status === "外部执行中"
5493
- && entry.reference !== options.forceSwitchTaskId
5494
- && entry.id !== options.forceSwitchTaskId;
5495
- const actions = entry.current ? [] : [bindCardAction({
5496
- text: showStop ? "Stop" : "Switch",
5497
- type: showStop ? "danger" : "default",
5588
+ const stopAction = entry.active ? bindCardAction({
5589
+ text: "Stop",
5590
+ type: "danger",
5591
+ value: {
5592
+ action: "session_stop",
5593
+ sessionId: entry.reference,
5594
+ ...(searchTerm ? { searchTerm } : {}),
5595
+ page: String(page),
5596
+ contextKey,
5597
+ },
5598
+ }) : undefined;
5599
+ const actions = [];
5600
+ if (!entry.current && entry.status !== "外部执行中")
5601
+ actions.push(bindCardAction({
5602
+ text: "Switch",
5498
5603
  value: {
5499
- action: showStop ? "session_stop" : "session_switch",
5604
+ action: "session_switch",
5500
5605
  sessionId: entry.reference,
5501
5606
  ...(searchTerm ? { searchTerm } : {}),
5502
5607
  page: String(page),
5503
5608
  contextKey,
5504
5609
  },
5505
- })];
5610
+ }));
5506
5611
  actions.push(bindCardAction({
5507
5612
  text: "SwitchGroup",
5508
5613
  value: {
@@ -5568,16 +5673,17 @@ export class ProxySessionController {
5568
5673
  contextKey,
5569
5674
  },
5570
5675
  }));
5571
- actions.push(bindCardAction({
5572
- text: "Archive",
5573
- value: {
5574
- action: "session_archive",
5575
- sessionId: entry.reference,
5576
- ...(searchTerm ? { searchTerm } : {}),
5577
- page: String(page),
5578
- contextKey,
5579
- },
5580
- }));
5676
+ if (!entry.active)
5677
+ actions.push(bindCardAction({
5678
+ text: "Archive",
5679
+ value: {
5680
+ action: "session_archive",
5681
+ sessionId: entry.reference,
5682
+ ...(searchTerm ? { searchTerm } : {}),
5683
+ page: String(page),
5684
+ contextKey,
5685
+ },
5686
+ }));
5581
5687
  const title = truncateText(entry.title.replace(/\s+/gu, " ").trim() || "未命名任务", 56);
5582
5688
  const lastUserPrompt = sessionPromptPreview(entry.lastUserPrompt);
5583
5689
  return {
@@ -5590,6 +5696,7 @@ export class ProxySessionController {
5590
5696
  `**最新 Prompt**:${cardText(lastUserPrompt)}`,
5591
5697
  `<font color='grey'>最近更新:${cardText(entry.updatedLabel)}</font>`,
5592
5698
  ],
5699
+ stopAction,
5593
5700
  actions,
5594
5701
  current: entry.current,
5595
5702
  },
@@ -5616,7 +5723,7 @@ export class ProxySessionController {
5616
5723
  `第 ${page + 1} 页 · 每页 ${SESSION_PAGE_SIZE} 个任务${hasNext ? "" : ` · 当前共 ${entries.length} 个任务`}`,
5617
5724
  "",
5618
5725
  "> 项目菜单:**New** 新建任务,**NewGroup** 新建群。",
5619
- "> 任务详情:**Switch** 切换,**Stop** 停止,**Fork** / **ForkGroup** 创建分支,**Status** 查看状态,**Archive** 归档。",
5726
+ "> 任务详情:**Switch** 切换,运行中任务可用 **Stop** 发送停止信号,**Fork** / **ForkGroup** 创建分支,**Status** 查看状态,**Archive** 归档。",
5620
5727
  ], [
5621
5728
  ...(hasPrevious ? [bindCardAction({
5622
5729
  text: "Previous",
@@ -5690,7 +5797,6 @@ export class ProxySessionController {
5690
5797
  : undefined;
5691
5798
  await this.listSessions(action.contextKey, searchTerm, {
5692
5799
  updateMessageId: action.messageId,
5693
- forceSwitchTaskId: options.forceSwitchTaskId,
5694
5800
  page: options.page ?? parseSessionPage(action.value.page),
5695
5801
  });
5696
5802
  }