@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.
- package/CHANGELOG.md +33 -0
- package/README.md +15 -3
- package/README.zh.md +15 -3
- package/dist/codex/AppServerPermissions.js +34 -0
- package/dist/codex/AppServerPermissions.js.map +1 -0
- package/dist/codex/CodexLocalActivityDetector.js +37 -5
- package/dist/codex/CodexLocalActivityDetector.js.map +1 -1
- package/dist/codex/CodexRuntime.js +37 -13
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/codex/ThreadWriterProcess.js +19 -5
- package/dist/codex/ThreadWriterProcess.js.map +1 -1
- package/dist/feishu/CardRenderer.js +80 -64
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/local-files/CsvPreview.js +148 -0
- package/dist/local-files/CsvPreview.js.map +1 -0
- package/dist/local-files/LocalFileViewerServer.js +53 -10
- package/dist/local-files/LocalFileViewerServer.js.map +1 -1
- package/dist/local-files/TurnPreviewPage.js +122 -11
- package/dist/local-files/TurnPreviewPage.js.map +1 -1
- package/dist/presentation/TurnStateReducer.js +39 -0
- package/dist/presentation/TurnStateReducer.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +152 -46
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/docs/technical-reference.md +5 -3
- package/docs/technical-reference.zh.md +4 -2
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/agent-bot/SKILL.md +6 -0
- package/src/codex/AppServerPermissions.ts +39 -0
- package/src/codex/CodexLocalActivityDetector.ts +34 -4
- package/src/codex/CodexRuntime.ts +44 -17
- package/src/codex/ThreadWriterProcess.ts +17 -9
- package/src/feishu/CardRenderer.ts +78 -64
- package/src/local-files/CsvPreview.ts +132 -0
- package/src/local-files/LocalFileViewerServer.ts +54 -11
- package/src/local-files/TurnPreviewPage.ts +125 -12
- package/src/presentation/TurnStateReducer.ts +41 -0
- package/src/presentation/turnViewTypes.ts +4 -0
- package/src/proxy/ProxySessionController.ts +137 -48
- 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
|
|
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.
|
|
1735
|
-
|
|
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
|
-
|
|
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,
|
|
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.
|
|
1842
|
+
session = this.store.getSession(attempt.localSessionId);
|
|
1827
1843
|
if (!session || session.status === "closed")
|
|
1828
1844
|
return;
|
|
1829
|
-
|
|
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
|
-
|
|
1833
|
-
|
|
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
|
-
|
|
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.
|
|
1958
|
-
|
|
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
|
-
|
|
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
|
|
5493
|
-
|
|
5494
|
-
|
|
5495
|
-
|
|
5496
|
-
|
|
5497
|
-
|
|
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:
|
|
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
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
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**
|
|
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
|
}
|