@keyou007/agent-bot 0.1.12 → 0.1.13-alpha.1
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/.env.example +2 -2
- package/CHANGELOG.md +18 -0
- package/README.md +32 -13
- package/README.zh.md +32 -13
- package/config.example.yaml +0 -24
- package/dist/cli/AgentPrerequisites.js +262 -0
- package/dist/cli/AgentPrerequisites.js.map +1 -0
- package/dist/cli/DefaultAgentSelection.js +47 -0
- package/dist/cli/DefaultAgentSelection.js.map +1 -0
- package/dist/cli/Initializer.js +165 -2
- package/dist/cli/Initializer.js.map +1 -1
- package/dist/cli/ServerStarter.js +17 -2
- package/dist/cli/ServerStarter.js.map +1 -1
- package/dist/cli/controlProtocol.js.map +1 -1
- package/dist/cli/help.js +16 -6
- package/dist/cli/help.js.map +1 -1
- package/dist/cli.js +233 -4
- package/dist/cli.js.map +1 -1
- package/dist/codex/AppServerConnection.js +3 -3
- package/dist/codex/AppServerConnection.js.map +1 -1
- package/dist/codex/CodexProcessManager.js +4 -4
- package/dist/codex/CodexProcessManager.js.map +1 -1
- package/dist/codex/CodexRuntime.js +28 -16
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/commands/CommandRouter.js +5 -0
- package/dist/commands/CommandRouter.js.map +1 -1
- package/dist/config/FeishuUserOpenIdStore.js +76 -0
- package/dist/config/FeishuUserOpenIdStore.js.map +1 -0
- package/dist/config/loadConfig.js +3 -24
- package/dist/config/loadConfig.js.map +1 -1
- package/dist/console/ConsoleConnector.js +2 -2
- package/dist/console/ConsoleConnector.js.map +1 -1
- package/dist/feishu/CardRenderer.js +90 -30
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/index.js +104 -24
- package/dist/index.js.map +1 -1
- package/dist/presentation/turnGraph.js +36 -0
- package/dist/presentation/turnGraph.js.map +1 -0
- package/dist/proxy/ProxySessionController.js +321 -98
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/runtime/AcpRuntimeAdapter.js +3 -2
- package/dist/runtime/AcpRuntimeAdapter.js.map +1 -1
- package/dist/startup/StartupNotifier.js +16 -7
- package/dist/startup/StartupNotifier.js.map +1 -1
- package/dist/startup/startFeishu.js +2 -2
- package/dist/startup/startFeishu.js.map +1 -1
- package/dist/state/StateStore.js +169 -0
- package/dist/state/StateStore.js.map +1 -1
- package/dist/state/migrations.js +21 -0
- package/dist/state/migrations.js.map +1 -1
- package/dist/supervision/SafeRestartNotifier.js +46 -3
- package/dist/supervision/SafeRestartNotifier.js.map +1 -1
- package/dist/supervision/SafeRestartScheduler.js +65 -6
- package/dist/supervision/SafeRestartScheduler.js.map +1 -1
- package/dist/supervision/replacementSupervisor.js +27 -2
- package/dist/supervision/replacementSupervisor.js.map +1 -1
- package/dist/supervisor.js +27 -2
- package/dist/supervisor.js.map +1 -1
- package/docs/technical-reference.md +42 -20
- package/docs/technical-reference.zh.md +42 -20
- package/npm-shrinkwrap.json +2 -2
- package/package.json +7 -3
- package/skills/agent-bot/SKILL.md +21 -9
- package/src/cli/AgentPrerequisites.ts +377 -0
- package/src/cli/DefaultAgentSelection.ts +56 -0
- package/src/cli/Initializer.ts +204 -3
- package/src/cli/ServerStarter.ts +25 -2
- package/src/cli/controlProtocol.ts +6 -1
- package/src/cli/help.ts +16 -6
- package/src/cli.ts +350 -5
- package/src/codex/AppServerConnection.ts +3 -3
- package/src/codex/CodexProcessManager.ts +4 -4
- package/src/codex/CodexRuntime.ts +38 -18
- package/src/commands/CommandRouter.ts +4 -0
- package/src/commands/commandTypes.ts +1 -0
- package/src/config/FeishuUserOpenIdStore.ts +81 -0
- package/src/config/loadConfig.ts +3 -24
- package/src/console/ConsoleConnector.ts +2 -2
- package/src/feishu/CardRenderer.ts +117 -30
- package/src/index.ts +162 -22
- package/src/presentation/turnGraph.ts +48 -0
- package/src/proxy/ProxySessionController.ts +375 -107
- package/src/runtime/AcpRuntimeAdapter.ts +3 -1
- package/src/startup/StartupNotifier.ts +23 -7
- package/src/startup/startFeishu.ts +3 -2
- package/src/state/StateStore.ts +221 -0
- package/src/state/migrations.ts +21 -0
- package/src/supervision/SafeRestartNotifier.ts +53 -4
- package/src/supervision/SafeRestartScheduler.ts +78 -7
- package/src/supervision/replacementSupervisor.ts +27 -1
- package/src/supervisor.ts +33 -2
|
@@ -6,6 +6,7 @@ import { CommandRouter } from "../commands/CommandRouter.js";
|
|
|
6
6
|
import { baseChatContextKey, isThreadContextKey } from "../feishu/contextKey.js";
|
|
7
7
|
import { CardRenderer, } from "../feishu/CardRenderer.js";
|
|
8
8
|
import { generateGroupAvatarPng, resolveGroupAvatarProjectName } from "../feishu/GroupAvatarGenerator.js";
|
|
9
|
+
import { buildTurnGraphRows } from "../presentation/turnGraph.js";
|
|
9
10
|
import { createId } from "../utils/id.js";
|
|
10
11
|
import { asInlineCode, codeBlock, truncateMiddle, truncateText } from "../utils/markdown.js";
|
|
11
12
|
import { normalizeTaskTitle } from "../utils/taskTitle.js";
|
|
@@ -15,6 +16,7 @@ const MESSAGE_COMPLETED_REACTION = "DONE";
|
|
|
15
16
|
const MESSAGE_FAILED_REACTION = "ERROR";
|
|
16
17
|
const MESSAGE_CANCELLED_REACTION = "CrossMark";
|
|
17
18
|
const SESSION_PAGE_SIZE = 5;
|
|
19
|
+
const RESET_HISTORY_PAGE_SIZE = 10;
|
|
18
20
|
const REMOTE_SESSION_REFERENCE_PREFIX = "agent-runtime:";
|
|
19
21
|
export class ProxySessionController {
|
|
20
22
|
config;
|
|
@@ -31,6 +33,7 @@ export class ProxySessionController {
|
|
|
31
33
|
queuedPromptStarts = new Map();
|
|
32
34
|
queuedPromptCards = new Map();
|
|
33
35
|
queuedPromptCardWrites = new Map();
|
|
36
|
+
sessionResets = new Map();
|
|
34
37
|
lastSessionListings = new Map();
|
|
35
38
|
threadInitializations = new Map();
|
|
36
39
|
unsubscribe = [];
|
|
@@ -75,8 +78,18 @@ export class ProxySessionController {
|
|
|
75
78
|
catch (error) {
|
|
76
79
|
this.logger.warn({ error, messageId: message.messageId, contextKey: message.contextKey }, "Failed to acknowledge the incoming Feishu message with a reaction.");
|
|
77
80
|
}
|
|
81
|
+
if (message.chatType === "p2p" && message.userId?.startsWith("ou_")) {
|
|
82
|
+
try {
|
|
83
|
+
await this.lifecycle?.rememberFeishuUserOpenId?.(message.userId);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
this.logger.warn({ error, messageId: message.messageId }, "Failed to persist the first private-chat Lark user Open ID.");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
78
89
|
if (message.chatId && message.chatType) {
|
|
79
|
-
|
|
90
|
+
const chatContextKey = baseChatContextKey(message.contextKey);
|
|
91
|
+
this.store.recordChatContext(chatContextKey, message.chatType);
|
|
92
|
+
this.store.markChatActive(chatContextKey);
|
|
80
93
|
}
|
|
81
94
|
const replyTarget = message.replyInThread
|
|
82
95
|
? { messageId: message.messageId, replyInThread: true }
|
|
@@ -87,9 +100,6 @@ export class ProxySessionController {
|
|
|
87
100
|
text: message.text,
|
|
88
101
|
...(imageCount > 0 ? { imageCount } : {}),
|
|
89
102
|
});
|
|
90
|
-
if (imageCount > 0 && message.chatId && message.chatType) {
|
|
91
|
-
this.store.markChatActive(baseChatContextKey(message.contextKey));
|
|
92
|
-
}
|
|
93
103
|
let localImagePaths;
|
|
94
104
|
if (imageCount > 0) {
|
|
95
105
|
try {
|
|
@@ -112,12 +122,6 @@ export class ProxySessionController {
|
|
|
112
122
|
await this.outbound.withReplyTarget(message.contextKey, replyTarget, () => this.sendError(message.contextKey, error));
|
|
113
123
|
return;
|
|
114
124
|
}
|
|
115
|
-
if (message.chatId
|
|
116
|
-
&& message.chatType
|
|
117
|
-
&& (command.type === "shell"
|
|
118
|
-
|| (command.type === "prompt" && !message.text.trimStart().startsWith("/")))) {
|
|
119
|
-
this.store.markChatActive(baseChatContextKey(message.contextKey));
|
|
120
|
-
}
|
|
121
125
|
// Operational and read-only commands must remain available even if a prompt operation is slow.
|
|
122
126
|
if (isQueueIndependentCommand(command)) {
|
|
123
127
|
await this.outbound.withReplyTarget(message.contextKey, replyTarget, async () => {
|
|
@@ -177,6 +181,24 @@ export class ProxySessionController {
|
|
|
177
181
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
178
182
|
await this.cancelSession(this.requireSession(contextKey, sessionId));
|
|
179
183
|
}
|
|
184
|
+
else if (kind === "turn_reset") {
|
|
185
|
+
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
186
|
+
await this.resetCurrentSessionToTurn(contextKey, sessionId, String(scopedAction.value.turnId ?? ""));
|
|
187
|
+
if (scopedAction.value.cardView === "reset_history") {
|
|
188
|
+
await this.openResetHistory(contextKey, {
|
|
189
|
+
expectedSessionId: sessionId,
|
|
190
|
+
updateMessageId: requiredCardMessageId(scopedAction.messageId),
|
|
191
|
+
page: resetHistoryPageValue(scopedAction.value.page),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else if (kind === "turn_reset_page") {
|
|
196
|
+
await this.openResetHistory(contextKey, {
|
|
197
|
+
expectedSessionId: String(scopedAction.value.sessionId ?? ""),
|
|
198
|
+
updateMessageId: requiredCardMessageId(scopedAction.messageId),
|
|
199
|
+
page: resetHistoryPageValue(scopedAction.value.page),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
180
202
|
else if (kind === "queued_prompt_cancel") {
|
|
181
203
|
await this.cancelQueuedPrompt(scopedAction);
|
|
182
204
|
}
|
|
@@ -193,8 +215,8 @@ export class ProxySessionController {
|
|
|
193
215
|
await this.outbound.sendText(contextKey, "该安全重启计划已失效,请查看最新状态卡片。");
|
|
194
216
|
}
|
|
195
217
|
}
|
|
196
|
-
else if (kind === "
|
|
197
|
-
await this.refreshSessionsCardFromAction(scopedAction
|
|
218
|
+
else if (kind === "session_page") {
|
|
219
|
+
await this.refreshSessionsCardFromAction(scopedAction);
|
|
198
220
|
}
|
|
199
221
|
else if (kind === "session_switch") {
|
|
200
222
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
@@ -202,12 +224,12 @@ export class ProxySessionController {
|
|
|
202
224
|
if (scopedAction.value.cardView === "status")
|
|
203
225
|
await this.refreshStatusCardFromAction(scopedAction);
|
|
204
226
|
else
|
|
205
|
-
await this.refreshSessionsCardFromAction(scopedAction);
|
|
227
|
+
await this.refreshSessionsCardFromAction(scopedAction, { page: 0 });
|
|
206
228
|
}
|
|
207
229
|
else if (kind === "session_fork") {
|
|
208
230
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
209
231
|
await this.forkSessionReference(contextKey, sessionId);
|
|
210
|
-
await this.refreshSessionsCardFromAction(scopedAction);
|
|
232
|
+
await this.refreshSessionsCardFromAction(scopedAction, { page: 0 });
|
|
211
233
|
}
|
|
212
234
|
else if (kind === "session_fork_group") {
|
|
213
235
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
@@ -217,7 +239,7 @@ export class ProxySessionController {
|
|
|
217
239
|
else if (kind === "session_new") {
|
|
218
240
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
219
241
|
await this.createProjectSessionFromReference(contextKey, sessionId);
|
|
220
|
-
await this.refreshSessionsCardFromAction(scopedAction);
|
|
242
|
+
await this.refreshSessionsCardFromAction(scopedAction, { page: 0 });
|
|
221
243
|
}
|
|
222
244
|
else if (kind === "session_new_group") {
|
|
223
245
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
@@ -230,7 +252,7 @@ export class ProxySessionController {
|
|
|
230
252
|
if (scopedAction.value.cardView === "status")
|
|
231
253
|
await this.refreshStatusCardFromAction(scopedAction, sessionId);
|
|
232
254
|
else
|
|
233
|
-
await this.refreshSessionsCardFromAction(scopedAction, sessionId);
|
|
255
|
+
await this.refreshSessionsCardFromAction(scopedAction, { forceSwitchTaskId: sessionId });
|
|
234
256
|
}
|
|
235
257
|
else if (kind === "session_status") {
|
|
236
258
|
await this.status(contextKey, String(scopedAction.value.sessionId ?? ""));
|
|
@@ -355,6 +377,7 @@ export class ProxySessionController {
|
|
|
355
377
|
this.lastSessionListings.clear();
|
|
356
378
|
this.queuedPromptCards.clear();
|
|
357
379
|
this.queuedPromptCardWrites.clear();
|
|
380
|
+
this.sessionResets.clear();
|
|
358
381
|
}
|
|
359
382
|
async controlStopTask(localSessionId) {
|
|
360
383
|
const record = this.store.getSession(localSessionId);
|
|
@@ -375,7 +398,7 @@ export class ProxySessionController {
|
|
|
375
398
|
remote = await runtime.readRemoteSession(record.remoteSessionId);
|
|
376
399
|
}
|
|
377
400
|
catch (error) {
|
|
378
|
-
this.logger.warn({ error, sessionId: record.localSessionId }, "Failed to inspect
|
|
401
|
+
this.logger.warn({ error, sessionId: record.localSessionId }, "Failed to inspect App Server task status for CLI.");
|
|
379
402
|
}
|
|
380
403
|
}
|
|
381
404
|
}
|
|
@@ -468,7 +491,7 @@ export class ProxySessionController {
|
|
|
468
491
|
const agentName = requestedAgentName?.trim() || source.agentName;
|
|
469
492
|
const agent = this.ensureAgent(agentName);
|
|
470
493
|
if (forceProjectless && agent.kind !== "app-server") {
|
|
471
|
-
throw new Error("task newgroup --nodir is only available for
|
|
494
|
+
throw new Error("task newgroup --nodir is only available for App Server agents.");
|
|
472
495
|
}
|
|
473
496
|
const sourceContextKey = this.outbound.getSessionContextKey(localSessionId) ?? source.contextKey;
|
|
474
497
|
const replyTarget = this.outbound.getSessionReplyTarget(localSessionId);
|
|
@@ -588,7 +611,10 @@ export class ProxySessionController {
|
|
|
588
611
|
case "restart":
|
|
589
612
|
if (!this.lifecycle)
|
|
590
613
|
throw new Error("当前运行方式不支持自动重启。");
|
|
591
|
-
await this.lifecycle.restart(contextKey, command.force === true);
|
|
614
|
+
await this.lifecycle.restart(contextKey, command.force === true, replyTarget);
|
|
615
|
+
return;
|
|
616
|
+
case "turns":
|
|
617
|
+
await this.openResetHistory(contextKey);
|
|
592
618
|
return;
|
|
593
619
|
case "model":
|
|
594
620
|
await this.openExecutionSettings(contextKey, "model");
|
|
@@ -654,7 +680,7 @@ export class ProxySessionController {
|
|
|
654
680
|
}
|
|
655
681
|
async prompt(contextKey, text, messageId, replyTarget, localImagePaths) {
|
|
656
682
|
if (!text.trim())
|
|
657
|
-
throw new Error("请输入要交给
|
|
683
|
+
throw new Error("请输入要交给 Agent 的内容。");
|
|
658
684
|
let record = this.currentSession(contextKey);
|
|
659
685
|
if (!record) {
|
|
660
686
|
const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent);
|
|
@@ -711,7 +737,7 @@ export class ProxySessionController {
|
|
|
711
737
|
return;
|
|
712
738
|
}
|
|
713
739
|
catch (error) {
|
|
714
|
-
this.logger.debug({ error, sessionId: record.localSessionId, activeTurnId }, "Steering failed; reconciling the
|
|
740
|
+
this.logger.debug({ error, sessionId: record.localSessionId, activeTurnId }, "Steering failed; reconciling the App Server thread.");
|
|
715
741
|
let current = loaded.runtime.getSession(record.localSessionId);
|
|
716
742
|
try {
|
|
717
743
|
current = await loaded.runtime.synchronizeSession(record.localSessionId);
|
|
@@ -734,7 +760,7 @@ export class ProxySessionController {
|
|
|
734
760
|
return;
|
|
735
761
|
}
|
|
736
762
|
catch (retryError) {
|
|
737
|
-
this.logger.warn({ error: retryError, sessionId: record.localSessionId, activeTurnId: current.activeTurnId }, "Failed to steer the reconciled
|
|
763
|
+
this.logger.warn({ error: retryError, sessionId: record.localSessionId, activeTurnId: current.activeTurnId }, "Failed to steer the reconciled App Server turn; queueing prompt.");
|
|
738
764
|
}
|
|
739
765
|
}
|
|
740
766
|
this.persistQueuedPrompt(record.localSessionId, contextKey, text, {
|
|
@@ -879,7 +905,7 @@ export class ProxySessionController {
|
|
|
879
905
|
const agent = this.ensureAgent(source.agentName);
|
|
880
906
|
const runtime = this.runtimes.forAgent(source.agentName);
|
|
881
907
|
if (runtime.kind !== "codex" || !runtime.forkSession) {
|
|
882
|
-
throw new Error("当前
|
|
908
|
+
throw new Error("当前 App Server Agent 不支持 fork 任务。");
|
|
883
909
|
}
|
|
884
910
|
const context = this.store.getOrCreateUserContext(message.contextKey, source.agentName);
|
|
885
911
|
if (context.currentSessionId && this.store.getSession(context.currentSessionId))
|
|
@@ -948,13 +974,13 @@ export class ProxySessionController {
|
|
|
948
974
|
.map((messageId) => this.store.findTurnAnchorByMessageId(messageId))
|
|
949
975
|
.find((candidate) => candidate !== undefined);
|
|
950
976
|
if (!anchor) {
|
|
951
|
-
throw new Error("无法确定这个话题对应的
|
|
977
|
+
throw new Error("无法确定这个话题对应的 App Server 轮次,因此没有创建分支任务。请从该轮的用户消息、思考卡片或最终回答创建话题。");
|
|
952
978
|
}
|
|
953
979
|
const source = anchor.contextKey
|
|
954
980
|
? this.store.getSessionForContext(anchor.localSessionId, anchor.contextKey)
|
|
955
981
|
: this.store.getSession(anchor.localSessionId);
|
|
956
982
|
if (!source || !source.remoteSessionId || !this.isCodexSession(source)) {
|
|
957
|
-
throw new Error("这个话题的来源不是可 fork 的
|
|
983
|
+
throw new Error("这个话题的来源不是可 fork 的 App Server 任务。");
|
|
958
984
|
}
|
|
959
985
|
if (baseChatContextKey(anchor.contextKey ?? source.contextKey) !== `chat_id:${message.chatId}`) {
|
|
960
986
|
throw new Error("话题来源任务不属于当前会话,已拒绝创建分支。");
|
|
@@ -962,7 +988,7 @@ export class ProxySessionController {
|
|
|
962
988
|
const snapshot = turnViewSnapshot(this.store.getTurnSnapshot(anchor.turnId));
|
|
963
989
|
if (isTurnStillRunning(snapshot?.status)
|
|
964
990
|
|| (source.lastTurnId === anchor.turnId && source.lastTurnStatus === "running")) {
|
|
965
|
-
throw new Error("话题对应的轮次仍在执行,
|
|
991
|
+
throw new Error("话题对应的轮次仍在执行,App Server 暂时不能从这一轮 fork。请等待该轮完成后再在话题中发送消息。");
|
|
966
992
|
}
|
|
967
993
|
return { anchor, source, snapshot };
|
|
968
994
|
}
|
|
@@ -992,7 +1018,7 @@ export class ProxySessionController {
|
|
|
992
1018
|
source = global ? { ...global, contextKey } : undefined;
|
|
993
1019
|
}
|
|
994
1020
|
if (source && (!source.remoteSessionId || !this.isCodexSession(source))) {
|
|
995
|
-
throw new Error(`${sourceLabel}不是可 fork 的
|
|
1021
|
+
throw new Error(`${sourceLabel}不是可 fork 的 App Server 任务。`);
|
|
996
1022
|
}
|
|
997
1023
|
let runtime;
|
|
998
1024
|
let agentName;
|
|
@@ -1003,20 +1029,20 @@ export class ProxySessionController {
|
|
|
1003
1029
|
}
|
|
1004
1030
|
else {
|
|
1005
1031
|
if (!taskId)
|
|
1006
|
-
throw new Error("缺少要 fork 的
|
|
1032
|
+
throw new Error("缺少要 fork 的 App Server 任务 ID。");
|
|
1007
1033
|
const resolved = await this.resolveRemoteCodexSession(taskId);
|
|
1008
1034
|
agentName = resolved.agentName;
|
|
1009
1035
|
runtime = resolved.runtime;
|
|
1010
1036
|
remote = resolved.remote;
|
|
1011
1037
|
}
|
|
1012
1038
|
if (runtime.kind !== "codex" || !runtime.forkSession) {
|
|
1013
|
-
throw new Error("当前
|
|
1039
|
+
throw new Error("当前 App Server Agent 不支持 fork 任务。");
|
|
1014
1040
|
}
|
|
1015
1041
|
const remoteSessionId = source?.remoteSessionId ?? remote?.id ?? taskId;
|
|
1016
1042
|
if (!remoteSessionId)
|
|
1017
|
-
throw new Error("当前任务尚未创建
|
|
1043
|
+
throw new Error("当前任务尚未创建 App Server 任务 ID,暂时不能 fork。");
|
|
1018
1044
|
if (!runtime.readRemoteSession && !source) {
|
|
1019
|
-
throw new Error("当前
|
|
1045
|
+
throw new Error("当前 App Server Agent 不支持读取指定任务。");
|
|
1020
1046
|
}
|
|
1021
1047
|
remote ??= runtime.readRemoteSession
|
|
1022
1048
|
? await runtime.readRemoteSession(remoteSessionId)
|
|
@@ -1039,7 +1065,7 @@ export class ProxySessionController {
|
|
|
1039
1065
|
const forkedFromHistoricalTurn = isRunning && lastTurnId !== latestTurnId;
|
|
1040
1066
|
const cwd = remote?.cwd || source?.cwd;
|
|
1041
1067
|
if (!cwd)
|
|
1042
|
-
throw new Error("指定的
|
|
1068
|
+
throw new Error("指定的 App Server 任务没有可用的工作目录,暂时不能 fork。");
|
|
1043
1069
|
const sourceTitle = remote?.title ?? source?.title ?? remote?.preview;
|
|
1044
1070
|
const forkTitle = normalizeTaskTitle(requestedTitle) ?? this.store.nextForkTitle(sourceTitle);
|
|
1045
1071
|
const modelProvider = remote?.modelProvider ?? source?.modelProvider;
|
|
@@ -1096,15 +1122,15 @@ export class ProxySessionController {
|
|
|
1096
1122
|
}
|
|
1097
1123
|
prepareForkSessionFromTurn(source, lastTurnId, requestedTitle, sourceLabel) {
|
|
1098
1124
|
if (!source.remoteSessionId || !this.isCodexSession(source)) {
|
|
1099
|
-
throw new Error(`${sourceLabel}不是可 fork 的
|
|
1125
|
+
throw new Error(`${sourceLabel}不是可 fork 的 App Server 任务。`);
|
|
1100
1126
|
}
|
|
1101
1127
|
const runtime = this.runtimes.forAgent(source.agentName);
|
|
1102
1128
|
if (runtime.kind !== "codex" || !runtime.forkSession) {
|
|
1103
|
-
throw new Error("当前
|
|
1129
|
+
throw new Error("当前 App Server Agent 不支持 fork 任务。");
|
|
1104
1130
|
}
|
|
1105
1131
|
const snapshot = turnViewSnapshot(this.store.getTurnSnapshot(lastTurnId));
|
|
1106
1132
|
if (isTurnStillRunning(snapshot?.status)) {
|
|
1107
|
-
throw new Error(`${sourceLabel}仍在执行,
|
|
1133
|
+
throw new Error(`${sourceLabel}仍在执行,App Server 暂时不能从这一轮 fork。`);
|
|
1108
1134
|
}
|
|
1109
1135
|
const sourceWasRunning = Boolean(runtime.getSession(source.localSessionId)?.activeTurnId
|
|
1110
1136
|
|| source.lastTurnStatus === "running"
|
|
@@ -1205,7 +1231,7 @@ export class ProxySessionController {
|
|
|
1205
1231
|
}
|
|
1206
1232
|
const source = direct ?? this.findStoredSessionByReference(taskId);
|
|
1207
1233
|
if (source && !this.isCodexSession(source)) {
|
|
1208
|
-
throw new Error("指定任务不是
|
|
1234
|
+
throw new Error("指定任务不是 App Server 任务,暂时不能按项目创建新任务。");
|
|
1209
1235
|
}
|
|
1210
1236
|
let agentName;
|
|
1211
1237
|
let runtime;
|
|
@@ -1233,7 +1259,7 @@ export class ProxySessionController {
|
|
|
1233
1259
|
}
|
|
1234
1260
|
const cwd = remote?.cwd || source?.cwd;
|
|
1235
1261
|
if (!cwd) {
|
|
1236
|
-
throw new Error("指定的
|
|
1262
|
+
throw new Error("指定的 App Server 任务没有可用的工作目录,暂时不能按项目创建新任务。");
|
|
1237
1263
|
}
|
|
1238
1264
|
const executionSettings = {
|
|
1239
1265
|
modelProvider: remote?.modelProvider ?? source?.modelProvider,
|
|
@@ -1363,7 +1389,7 @@ export class ProxySessionController {
|
|
|
1363
1389
|
"群和新任务已创建。",
|
|
1364
1390
|
`当前任务:${taskDescription}`,
|
|
1365
1391
|
`当前 Project 目录:${boundProjectCwd ?? "未绑定(Projectless)"}`,
|
|
1366
|
-
`当前 Provider:${task.modelProvider ?? "
|
|
1392
|
+
`当前 Provider:${task.modelProvider ?? "Agent 默认"}`,
|
|
1367
1393
|
`当前模型:${task.model ?? "默认"}`,
|
|
1368
1394
|
`思考强度:${task.reasoningEffort ?? "自动"}`,
|
|
1369
1395
|
`权限类型:${task.permissionMode === "confirm" ? "执行前确认" : "自动执行"}`,
|
|
@@ -1409,7 +1435,7 @@ export class ProxySessionController {
|
|
|
1409
1435
|
`已从${prepared.sourceDescription}创建分支。`,
|
|
1410
1436
|
`当前任务:${taskDescription}`,
|
|
1411
1437
|
`当前 Project 目录:${boundProjectCwd ?? "未绑定(Projectless)"}`,
|
|
1412
|
-
`当前 Provider:${forked.session.modelProvider ?? "
|
|
1438
|
+
`当前 Provider:${forked.session.modelProvider ?? "Agent 默认"}`,
|
|
1413
1439
|
`当前模型:${forked.session.model ?? "默认"}`,
|
|
1414
1440
|
`思考强度:${forked.session.reasoningEffort ?? "自动"}`,
|
|
1415
1441
|
`权限类型:${forked.session.permissionMode === "confirm" ? "执行前确认" : "自动执行"}`,
|
|
@@ -1479,7 +1505,7 @@ export class ProxySessionController {
|
|
|
1479
1505
|
catch (error) {
|
|
1480
1506
|
if (!(agent.kind === "app-server" && !record.lastTurnId && isMissingRolloutError(error)))
|
|
1481
1507
|
throw error;
|
|
1482
|
-
this.logger.warn({ error, sessionId: record.localSessionId }, "
|
|
1508
|
+
this.logger.warn({ error, sessionId: record.localSessionId }, "App Server task has no rollout; creating a replacement task.");
|
|
1483
1509
|
session = await runtime.createSession({
|
|
1484
1510
|
localSessionId: record.localSessionId,
|
|
1485
1511
|
agentName: record.agentName,
|
|
@@ -1532,6 +1558,15 @@ export class ProxySessionController {
|
|
|
1532
1558
|
});
|
|
1533
1559
|
}
|
|
1534
1560
|
async handleRuntimeEvent(event) {
|
|
1561
|
+
if ("turnId" in event) {
|
|
1562
|
+
const source = this.store.getSession(event.sessionId);
|
|
1563
|
+
if (source?.remoteSessionId && this.isCodexSession(source)) {
|
|
1564
|
+
this.store.saveTurnRuntimeOrigin(event.turnId, source.localSessionId, source.agentName, source.remoteSessionId);
|
|
1565
|
+
}
|
|
1566
|
+
if (event.type === "turn_started") {
|
|
1567
|
+
this.store.saveTurnParent(event.turnId, event.sessionId, source?.lastTurnStatus === "completed" ? source.lastTurnId : undefined);
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1535
1570
|
if (event.type === "session_metadata_updated") {
|
|
1536
1571
|
this.store.updateRuntimeSession(event.sessionId, { title: event.title });
|
|
1537
1572
|
this.outbound.updateSessionTitle(event.sessionId, event.title);
|
|
@@ -1682,7 +1717,7 @@ export class ProxySessionController {
|
|
|
1682
1717
|
await runtime.setGoal(record.localSessionId, { status: "paused" });
|
|
1683
1718
|
}
|
|
1684
1719
|
catch (error) {
|
|
1685
|
-
this.logger.warn({ error, sessionId: record.localSessionId }, "Failed to pause the active
|
|
1720
|
+
this.logger.warn({ error, sessionId: record.localSessionId }, "Failed to pause the active Agent goal before interrupting its turn.");
|
|
1686
1721
|
}
|
|
1687
1722
|
}
|
|
1688
1723
|
let turnId = runtime.getSession(record.localSessionId)?.activeTurnId;
|
|
@@ -1694,7 +1729,7 @@ export class ProxySessionController {
|
|
|
1694
1729
|
: undefined;
|
|
1695
1730
|
}
|
|
1696
1731
|
catch (error) {
|
|
1697
|
-
this.logger.warn({ error, sessionId: record.localSessionId, remoteSessionId: record.remoteSessionId }, "Failed to inspect the current
|
|
1732
|
+
this.logger.warn({ error, sessionId: record.localSessionId, remoteSessionId: record.remoteSessionId }, "Failed to inspect the current App Server turn before interrupting; using the locally tracked turn.");
|
|
1698
1733
|
}
|
|
1699
1734
|
}
|
|
1700
1735
|
if (!turnId) {
|
|
@@ -1707,7 +1742,7 @@ export class ProxySessionController {
|
|
|
1707
1742
|
remoteSessionId: record.remoteSessionId,
|
|
1708
1743
|
turnId,
|
|
1709
1744
|
});
|
|
1710
|
-
await this.outbound.sendText(record.contextKey, `已向
|
|
1745
|
+
await this.outbound.sendText(record.contextKey, `已向 Agent 发送 Interrupt 请求:${turnId}`);
|
|
1711
1746
|
return;
|
|
1712
1747
|
}
|
|
1713
1748
|
const loaded = await this.loadSession(record);
|
|
@@ -1718,6 +1753,88 @@ export class ProxySessionController {
|
|
|
1718
1753
|
}
|
|
1719
1754
|
await loaded.runtime.cancelTurn(record.localSessionId, turnId);
|
|
1720
1755
|
}
|
|
1756
|
+
async resetCurrentSessionToTurn(contextKey, sessionId, turnId) {
|
|
1757
|
+
if (!sessionId || !turnId)
|
|
1758
|
+
throw new Error("无效的 Reset 请求。请使用最新的思考卡片重试。");
|
|
1759
|
+
const previous = this.sessionResets.get(sessionId) ?? Promise.resolve();
|
|
1760
|
+
const reset = previous.catch(() => undefined).then(() => this.performCurrentSessionReset(contextKey, sessionId, turnId));
|
|
1761
|
+
this.sessionResets.set(sessionId, reset);
|
|
1762
|
+
try {
|
|
1763
|
+
await reset;
|
|
1764
|
+
}
|
|
1765
|
+
finally {
|
|
1766
|
+
if (this.sessionResets.get(sessionId) === reset)
|
|
1767
|
+
this.sessionResets.delete(sessionId);
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
async performCurrentSessionReset(contextKey, sessionId, turnId) {
|
|
1771
|
+
const current = this.requireCurrentSession(contextKey);
|
|
1772
|
+
if (current.localSessionId !== sessionId) {
|
|
1773
|
+
throw new Error("这张思考卡片不属于当前任务。请先切换回对应任务,再点击 Reset。");
|
|
1774
|
+
}
|
|
1775
|
+
if (!current.remoteSessionId || !this.isCodexSession(current)) {
|
|
1776
|
+
throw new Error("当前任务不是可 Reset 的 App Server 任务。");
|
|
1777
|
+
}
|
|
1778
|
+
const snapshot = turnViewSnapshot(this.store.getTurnSnapshot(turnId));
|
|
1779
|
+
if (!snapshot || snapshot.sessionId !== sessionId || snapshot.status !== "completed") {
|
|
1780
|
+
throw new Error("只能将当前任务 Reset 到已成功完成的轮次。");
|
|
1781
|
+
}
|
|
1782
|
+
if (current.lastTurnId === turnId && current.lastTurnStatus === "completed") {
|
|
1783
|
+
await this.outbound.sendText(contextKey, "当前任务已经处于本轮完成后的对话状态,无需 Reset。");
|
|
1784
|
+
return;
|
|
1785
|
+
}
|
|
1786
|
+
const runtime = this.runtimes.forAgent(current.agentName);
|
|
1787
|
+
if (runtime.kind !== "codex" || !runtime.forkSession) {
|
|
1788
|
+
throw new Error("当前 App Server Agent 不支持 Reset。");
|
|
1789
|
+
}
|
|
1790
|
+
const loaded = await this.loadSession(current);
|
|
1791
|
+
const activity = await this.assertSessionTurnOwnership(current, runtime);
|
|
1792
|
+
const active = activity?.active
|
|
1793
|
+
?? Boolean(loaded.session.activeTurnId || current.status === "running" || current.lastTurnStatus === "running");
|
|
1794
|
+
if (active) {
|
|
1795
|
+
throw new Error("当前任务仍在执行。请等待任务完成或先停止任务,再点击 Reset。");
|
|
1796
|
+
}
|
|
1797
|
+
const origin = this.store.getTurnRuntimeOrigin(turnId);
|
|
1798
|
+
if (origin && (origin.localSessionId !== sessionId || origin.agentName !== current.agentName)) {
|
|
1799
|
+
throw new Error("这张思考卡片与当前任务不匹配,已拒绝 Reset。");
|
|
1800
|
+
}
|
|
1801
|
+
const sourceRemoteSessionId = origin?.remoteSessionId ?? current.remoteSessionId;
|
|
1802
|
+
const previousRemoteSessionId = current.remoteSessionId;
|
|
1803
|
+
const forked = await runtime.forkSession({
|
|
1804
|
+
localSessionId: current.localSessionId,
|
|
1805
|
+
remoteSessionId: sourceRemoteSessionId,
|
|
1806
|
+
lastTurnId: turnId,
|
|
1807
|
+
agentName: current.agentName,
|
|
1808
|
+
cwd: current.cwd,
|
|
1809
|
+
title: current.title,
|
|
1810
|
+
modelProvider: current.modelProvider,
|
|
1811
|
+
model: current.model,
|
|
1812
|
+
reasoningEffort: current.reasoningEffort,
|
|
1813
|
+
permissionMode: current.permissionMode ?? "auto",
|
|
1814
|
+
});
|
|
1815
|
+
this.persistRuntimeSession(current, forked, "ready");
|
|
1816
|
+
this.store.updateRuntimeSession(current.localSessionId, {
|
|
1817
|
+
lastTurnId: turnId,
|
|
1818
|
+
lastTurnStatus: "completed",
|
|
1819
|
+
});
|
|
1820
|
+
this.outbound.registerSession(current.localSessionId, contextKey, forked.title ?? current.title, current.cwd, this.agentLabel(current.agentName));
|
|
1821
|
+
this.store.audit(contextKey, "session_reset_to_turn", {
|
|
1822
|
+
localSessionId: current.localSessionId,
|
|
1823
|
+
previousRemoteSessionId,
|
|
1824
|
+
sourceRemoteSessionId,
|
|
1825
|
+
resetTurnId: turnId,
|
|
1826
|
+
forkedRemoteSessionId: forked.remoteSessionId,
|
|
1827
|
+
});
|
|
1828
|
+
const targetSummary = truncateText((snapshot.prompt ?? snapshot.taskTitle ?? "未记录对话内容").replace(/\s+/g, " ").trim(), 100) || "未记录对话内容";
|
|
1829
|
+
const targetTime = snapshot.completedAt ?? snapshot.startedAt;
|
|
1830
|
+
await this.outbound.sendText(contextKey, [
|
|
1831
|
+
"已将当前任务重置到:",
|
|
1832
|
+
targetSummary,
|
|
1833
|
+
`完成时间:${targetTime === undefined ? "未知" : formatResetTurnTime(targetTime)}`,
|
|
1834
|
+
`Turn ID:${turnId}`,
|
|
1835
|
+
"后续对话将从该轮完成后的状态继续;本地文件没有回退。",
|
|
1836
|
+
].join("\n"));
|
|
1837
|
+
}
|
|
1721
1838
|
async resolveApproval(action) {
|
|
1722
1839
|
const sessionId = String(action.value.sessionId ?? "");
|
|
1723
1840
|
const requestId = String(action.value.requestId ?? "");
|
|
@@ -2002,7 +2119,7 @@ export class ProxySessionController {
|
|
|
2002
2119
|
}
|
|
2003
2120
|
const loaded = await this.loadSession(record);
|
|
2004
2121
|
if (loaded.runtime.kind !== "codex" || !loaded.runtime.getGoal || !loaded.runtime.setGoal || !loaded.runtime.clearGoal) {
|
|
2005
|
-
throw new Error("
|
|
2122
|
+
throw new Error("当前 Agent 不支持 Goal 模式。");
|
|
2006
2123
|
}
|
|
2007
2124
|
if (command.action === "show") {
|
|
2008
2125
|
await this.sendGoalCard(contextKey, await loaded.runtime.getGoal(record.localSessionId));
|
|
@@ -2025,7 +2142,7 @@ export class ProxySessionController {
|
|
|
2025
2142
|
const goal = await loaded.runtime.setGoal(record.localSessionId, { status });
|
|
2026
2143
|
await this.sendGoalCard(contextKey, goal, command.action === "pause"
|
|
2027
2144
|
? "Goal 已暂停;当前轮次可以完成,但不会继续自动执行。"
|
|
2028
|
-
: "Goal 已恢复,
|
|
2145
|
+
: "Goal 已恢复,Agent 会继续自动执行。", record);
|
|
2029
2146
|
return;
|
|
2030
2147
|
}
|
|
2031
2148
|
if (objective === undefined)
|
|
@@ -2041,7 +2158,7 @@ export class ProxySessionController {
|
|
|
2041
2158
|
status: command.action === "edit" ? current.status : "active",
|
|
2042
2159
|
...(command.action === "edit" ? { tokenBudget: current.tokenBudget } : {}),
|
|
2043
2160
|
});
|
|
2044
|
-
await this.sendGoalCard(contextKey, goal, command.action === "edit" ? "Goal 已更新。" : "Goal 已启动,
|
|
2161
|
+
await this.sendGoalCard(contextKey, goal, command.action === "edit" ? "Goal 已更新。" : "Goal 已启动,Agent 会持续执行直到完成、暂停或遇到阻塞。", record);
|
|
2045
2162
|
}
|
|
2046
2163
|
async sendGoalCard(contextKey, goal, notice, record = this.currentSession(contextKey)) {
|
|
2047
2164
|
const sections = [];
|
|
@@ -2055,7 +2172,7 @@ export class ProxySessionController {
|
|
|
2055
2172
|
title: "任务",
|
|
2056
2173
|
lines: [
|
|
2057
2174
|
`**标题**:${cardText(record.title ?? "未命名任务")}`,
|
|
2058
|
-
`**
|
|
2175
|
+
`**App Server 任务 ID**:${cardText(record.remoteSessionId ?? "尚未创建")}`,
|
|
2059
2176
|
],
|
|
2060
2177
|
});
|
|
2061
2178
|
}
|
|
@@ -2066,7 +2183,7 @@ export class ProxySessionController {
|
|
|
2066
2183
|
"**/goal edit <新目标>** 修改 **/goal clear** 清除",
|
|
2067
2184
|
],
|
|
2068
2185
|
});
|
|
2069
|
-
await this.outbound.sendInteractiveCard(contextKey, this.cardRenderer.renderSectionsCard("
|
|
2186
|
+
await this.outbound.sendInteractiveCard(contextKey, this.cardRenderer.renderSectionsCard("Agent Goal", sections));
|
|
2070
2187
|
}
|
|
2071
2188
|
async runShellCommand(contextKey, command) {
|
|
2072
2189
|
const cwd = this.currentSession(contextKey)?.cwd ?? this.config.defaults.cwd;
|
|
@@ -2185,10 +2302,92 @@ export class ProxySessionController {
|
|
|
2185
2302
|
}
|
|
2186
2303
|
await this.outbound.sendText(contextKey, mode === "auto" ? "已切换为自动执行模式。" : "已切换为执行前确认模式。");
|
|
2187
2304
|
}
|
|
2305
|
+
async openResetHistory(contextKey, options = {}) {
|
|
2306
|
+
const current = this.requireCurrentSession(contextKey);
|
|
2307
|
+
if (options.expectedSessionId && current.localSessionId !== options.expectedSessionId) {
|
|
2308
|
+
throw new Error("这张历史轮次卡片不属于当前任务。请先切换回对应任务,再重新发送 /turns。");
|
|
2309
|
+
}
|
|
2310
|
+
if (!current.remoteSessionId || !this.isCodexSession(current)) {
|
|
2311
|
+
throw new Error("当前任务不是可 Reset 的 App Server 任务。");
|
|
2312
|
+
}
|
|
2313
|
+
const allTurns = this.store.listCompletedTurnGraph(current.localSessionId, contextKey);
|
|
2314
|
+
const graphRows = buildTurnGraphRows(allTurns.map((turn) => ({
|
|
2315
|
+
turnId: turn.turnId,
|
|
2316
|
+
parentTurnId: turn.parentTurnId,
|
|
2317
|
+
})));
|
|
2318
|
+
const total = allTurns.length;
|
|
2319
|
+
const totalPages = Math.max(1, Math.ceil(total / RESET_HISTORY_PAGE_SIZE));
|
|
2320
|
+
const page = Math.max(0, Math.min(Math.trunc(options.page ?? 0), totalPages - 1));
|
|
2321
|
+
const offset = page * RESET_HISTORY_PAGE_SIZE;
|
|
2322
|
+
const turns = allTurns.slice(offset, offset + RESET_HISTORY_PAGE_SIZE);
|
|
2323
|
+
const entries = turns.flatMap((turn, index) => {
|
|
2324
|
+
const snapshot = turnViewSnapshot(turn.snapshot);
|
|
2325
|
+
if (!snapshot)
|
|
2326
|
+
return [];
|
|
2327
|
+
const graph = graphRows[offset + index];
|
|
2328
|
+
const summary = truncateText((snapshot.prompt ?? snapshot.taskTitle ?? "未记录对话内容").replace(/\s+/g, " ").trim(), 100);
|
|
2329
|
+
const isCurrent = current.lastTurnId === turn.turnId && current.lastTurnStatus === "completed";
|
|
2330
|
+
return [{
|
|
2331
|
+
sequence: graph.sequence,
|
|
2332
|
+
graphNodeLine: graph.nodeLine,
|
|
2333
|
+
graphConnectorLine: graph.connectorLine,
|
|
2334
|
+
lines: [
|
|
2335
|
+
`**${cardText(summary || "未记录对话内容")}**`,
|
|
2336
|
+
`${formatResetTurnTime(snapshot.completedAt ?? snapshot.startedAt)} · ${cardCode(turn.turnId)}`,
|
|
2337
|
+
],
|
|
2338
|
+
current: isCurrent,
|
|
2339
|
+
actions: isCurrent ? undefined : [{
|
|
2340
|
+
text: "Reset",
|
|
2341
|
+
value: {
|
|
2342
|
+
action: "turn_reset",
|
|
2343
|
+
cardView: "reset_history",
|
|
2344
|
+
sessionId: current.localSessionId,
|
|
2345
|
+
turnId: turn.turnId,
|
|
2346
|
+
contextKey,
|
|
2347
|
+
page: String(page),
|
|
2348
|
+
},
|
|
2349
|
+
}],
|
|
2350
|
+
}];
|
|
2351
|
+
});
|
|
2352
|
+
const pageActions = [
|
|
2353
|
+
...(page > 0 ? [{
|
|
2354
|
+
text: "Previous",
|
|
2355
|
+
value: {
|
|
2356
|
+
action: "turn_reset_page",
|
|
2357
|
+
sessionId: current.localSessionId,
|
|
2358
|
+
contextKey,
|
|
2359
|
+
page: String(page - 1),
|
|
2360
|
+
},
|
|
2361
|
+
}] : []),
|
|
2362
|
+
...(page < totalPages - 1 ? [{
|
|
2363
|
+
text: "Next",
|
|
2364
|
+
value: {
|
|
2365
|
+
action: "turn_reset_page",
|
|
2366
|
+
sessionId: current.localSessionId,
|
|
2367
|
+
contextKey,
|
|
2368
|
+
page: String(page + 1),
|
|
2369
|
+
},
|
|
2370
|
+
}] : []),
|
|
2371
|
+
];
|
|
2372
|
+
const card = this.cardRenderer.renderResetHistoryCard({
|
|
2373
|
+
entries,
|
|
2374
|
+
footerLines: [
|
|
2375
|
+
`第 ${page + 1}/${totalPages} 页 · 共 ${total} 个已完成 turn`,
|
|
2376
|
+
],
|
|
2377
|
+
pageActions,
|
|
2378
|
+
});
|
|
2379
|
+
if (options.updateMessageId) {
|
|
2380
|
+
await this.outbound.updateInteractiveCard(contextKey, options.updateMessageId, card);
|
|
2381
|
+
}
|
|
2382
|
+
else {
|
|
2383
|
+
await this.outbound.sendInteractiveCard(contextKey, card);
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2188
2386
|
async listSessions(contextKey, searchTerm, options = {}) {
|
|
2189
2387
|
const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent);
|
|
2190
2388
|
const normalizedSearch = searchTerm?.trim().toLowerCase();
|
|
2191
|
-
const
|
|
2389
|
+
const requestedPage = Math.max(0, Math.trunc(options.page ?? 0));
|
|
2390
|
+
const fetchLimit = (requestedPage + 1) * SESSION_PAGE_SIZE;
|
|
2192
2391
|
const localSessions = this.store.listSessions(contextKey).filter((session) => session.status !== "closed" && this.isCodexSession(session));
|
|
2193
2392
|
const remoteSessions = [];
|
|
2194
2393
|
const remoteErrors = [];
|
|
@@ -2197,12 +2396,12 @@ export class ProxySessionController {
|
|
|
2197
2396
|
if (!runtime.listRemoteSessions)
|
|
2198
2397
|
return;
|
|
2199
2398
|
try {
|
|
2200
|
-
const
|
|
2201
|
-
remoteSessions.push(...
|
|
2202
|
-
remoteHasMore ||= Boolean(
|
|
2399
|
+
const result = await runtime.listRemoteSessions({ searchTerm, limit: fetchLimit });
|
|
2400
|
+
remoteSessions.push(...result.sessions.map((session) => ({ agentName, session })));
|
|
2401
|
+
remoteHasMore ||= Boolean(result.nextCursor);
|
|
2203
2402
|
}
|
|
2204
2403
|
catch (error) {
|
|
2205
|
-
this.logger.warn({ error, contextKey, agentName }, "Failed to list
|
|
2404
|
+
this.logger.warn({ error, contextKey, agentName }, "Failed to list App Server sessions for an Agent.");
|
|
2206
2405
|
remoteErrors.push(`${agentName}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2207
2406
|
}
|
|
2208
2407
|
}));
|
|
@@ -2217,9 +2416,13 @@ export class ProxySessionController {
|
|
|
2217
2416
|
|| Number(right.active) - Number(left.active)
|
|
2218
2417
|
|| right.updatedAt - left.updatedAt);
|
|
2219
2418
|
const activeCount = entries.filter((entry) => entry.active).length;
|
|
2220
|
-
const
|
|
2221
|
-
const
|
|
2222
|
-
|
|
2419
|
+
const lastKnownPage = Math.max(0, Math.ceil(entries.length / SESSION_PAGE_SIZE) - 1);
|
|
2420
|
+
const page = remoteHasMore ? requestedPage : Math.min(requestedPage, lastKnownPage);
|
|
2421
|
+
const offset = page * SESSION_PAGE_SIZE;
|
|
2422
|
+
const visibleEntries = entries.slice(offset, offset + SESSION_PAGE_SIZE);
|
|
2423
|
+
const hasPrevious = page > 0;
|
|
2424
|
+
const hasNext = remoteHasMore || entries.length > offset + SESSION_PAGE_SIZE;
|
|
2425
|
+
this.lastSessionListings.set(contextKey, entries.map((entry) => entry.reference));
|
|
2223
2426
|
const cardEntries = visibleEntries.map((entry, index) => {
|
|
2224
2427
|
const marker = entry.current ? "✅" : entry.active ? "🟢 **活跃**" : "•";
|
|
2225
2428
|
const showStop = entry.status === "外部执行中"
|
|
@@ -2232,7 +2435,7 @@ export class ProxySessionController {
|
|
|
2232
2435
|
action: showStop ? "session_stop" : "session_switch",
|
|
2233
2436
|
sessionId: entry.reference,
|
|
2234
2437
|
...(searchTerm ? { searchTerm } : {}),
|
|
2235
|
-
|
|
2438
|
+
page: String(page),
|
|
2236
2439
|
contextKey,
|
|
2237
2440
|
},
|
|
2238
2441
|
}];
|
|
@@ -2242,7 +2445,7 @@ export class ProxySessionController {
|
|
|
2242
2445
|
action: "session_new",
|
|
2243
2446
|
sessionId: entry.reference,
|
|
2244
2447
|
...(searchTerm ? { searchTerm } : {}),
|
|
2245
|
-
|
|
2448
|
+
page: String(page),
|
|
2246
2449
|
contextKey,
|
|
2247
2450
|
},
|
|
2248
2451
|
});
|
|
@@ -2252,7 +2455,7 @@ export class ProxySessionController {
|
|
|
2252
2455
|
action: "session_new_group",
|
|
2253
2456
|
sessionId: entry.reference,
|
|
2254
2457
|
...(searchTerm ? { searchTerm } : {}),
|
|
2255
|
-
|
|
2458
|
+
page: String(page),
|
|
2256
2459
|
contextKey,
|
|
2257
2460
|
},
|
|
2258
2461
|
});
|
|
@@ -2262,7 +2465,7 @@ export class ProxySessionController {
|
|
|
2262
2465
|
action: "session_fork",
|
|
2263
2466
|
sessionId: entry.reference,
|
|
2264
2467
|
...(searchTerm ? { searchTerm } : {}),
|
|
2265
|
-
|
|
2468
|
+
page: String(page),
|
|
2266
2469
|
contextKey,
|
|
2267
2470
|
},
|
|
2268
2471
|
});
|
|
@@ -2272,7 +2475,7 @@ export class ProxySessionController {
|
|
|
2272
2475
|
action: "session_fork_group",
|
|
2273
2476
|
sessionId: entry.reference,
|
|
2274
2477
|
...(searchTerm ? { searchTerm } : {}),
|
|
2275
|
-
|
|
2478
|
+
page: String(page),
|
|
2276
2479
|
contextKey,
|
|
2277
2480
|
},
|
|
2278
2481
|
});
|
|
@@ -2286,29 +2489,40 @@ export class ProxySessionController {
|
|
|
2286
2489
|
});
|
|
2287
2490
|
return {
|
|
2288
2491
|
lines: [
|
|
2289
|
-
`**${index + 1}.** ${marker} **${cardText(entry.title)}** ${cardText(entry.id)}`,
|
|
2492
|
+
`**${offset + index + 1}.** ${marker} **${cardText(entry.title)}** ${cardText(entry.id)}`,
|
|
2290
2493
|
`${entry.status} · ${entry.updatedLabel} · ${cardText(entry.agentName)} · ${cardText(entry.cwd || "目录未知")}`,
|
|
2291
2494
|
],
|
|
2292
2495
|
actions,
|
|
2293
2496
|
};
|
|
2294
2497
|
});
|
|
2295
|
-
const card = this.cardRenderer.renderTaskListCard(searchTerm ? `
|
|
2498
|
+
const card = this.cardRenderer.renderTaskListCard(searchTerm ? `App Server 任务:${searchTerm}` : "App Server 任务", activeCount > 0 ? `任务(${activeCount} 个活跃)` : "任务", cardEntries, [
|
|
2296
2499
|
...(remoteHint ? [remoteHint] : []),
|
|
2500
|
+
`第 ${page + 1} 页 · 每页 ${SESSION_PAGE_SIZE} 个任务${hasNext ? "" : ` · 当前共 ${entries.length} 个任务`}`,
|
|
2297
2501
|
"点击 **New** 在对应任务的项目中创建新任务;点击 **Switch** 快速切换;点击 **Fork** 从任务最新已完成轮次创建分支;外部正在运行的任务显示 **Stop**,点击后发送 Interrupt 并变为 **Switch**。",
|
|
2298
2502
|
"点击 **NewGroup** 在对应任务的项目中创建新群和新任务;点击 **ForkGroup** 从对应任务最新已完成轮次创建分支群。",
|
|
2299
2503
|
"也可发送 **/switch [序号或任务 ID]**;不带参数切回上一个任务。外部正在执行的回合不会被接管。",
|
|
2300
2504
|
"发送 **/fork [序号或任务 ID]**,可从当前或指定任务创建分支;任务运行中时使用最近已完成轮次。",
|
|
2301
2505
|
"点击 **Status**,或发送 **/status [序号或任务 ID]**,查看当前或指定任务状态。",
|
|
2302
|
-
],
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2506
|
+
], [
|
|
2507
|
+
...(hasPrevious ? [{
|
|
2508
|
+
text: "Previous",
|
|
2509
|
+
value: {
|
|
2510
|
+
action: "session_page",
|
|
2511
|
+
...(searchTerm ? { searchTerm } : {}),
|
|
2512
|
+
page: String(page - 1),
|
|
2513
|
+
contextKey,
|
|
2514
|
+
},
|
|
2515
|
+
}] : []),
|
|
2516
|
+
...(hasNext ? [{
|
|
2517
|
+
text: "Next",
|
|
2518
|
+
value: {
|
|
2519
|
+
action: "session_page",
|
|
2520
|
+
...(searchTerm ? { searchTerm } : {}),
|
|
2521
|
+
page: String(page + 1),
|
|
2522
|
+
contextKey,
|
|
2523
|
+
},
|
|
2524
|
+
}] : []),
|
|
2525
|
+
]);
|
|
2312
2526
|
if (options.updateMessageId) {
|
|
2313
2527
|
await this.outbound.updateInteractiveCard(contextKey, options.updateMessageId, card);
|
|
2314
2528
|
}
|
|
@@ -2316,17 +2530,16 @@ export class ProxySessionController {
|
|
|
2316
2530
|
await this.outbound.sendInteractiveCard(contextKey, card);
|
|
2317
2531
|
}
|
|
2318
2532
|
}
|
|
2319
|
-
async refreshSessionsCardFromAction(action,
|
|
2533
|
+
async refreshSessionsCardFromAction(action, options = {}) {
|
|
2320
2534
|
if (!action.messageId)
|
|
2321
2535
|
return;
|
|
2322
2536
|
const searchTerm = typeof action.value.searchTerm === "string" && action.value.searchTerm.trim()
|
|
2323
2537
|
? action.value.searchTerm
|
|
2324
2538
|
: undefined;
|
|
2325
|
-
const currentVisibleCount = parseSessionVisibleCount(action.value.visibleCount);
|
|
2326
2539
|
await this.listSessions(action.contextKey, searchTerm, {
|
|
2327
2540
|
updateMessageId: action.messageId,
|
|
2328
|
-
forceSwitchTaskId,
|
|
2329
|
-
|
|
2541
|
+
forceSwitchTaskId: options.forceSwitchTaskId,
|
|
2542
|
+
page: options.page ?? parseSessionPage(action.value.page),
|
|
2330
2543
|
});
|
|
2331
2544
|
}
|
|
2332
2545
|
async refreshStatusCardFromAction(action, forceSwitchTaskId) {
|
|
@@ -2352,7 +2565,7 @@ export class ProxySessionController {
|
|
|
2352
2565
|
? [[scoped.agentName, this.runtimes.forAgent(scoped.agentName)]]
|
|
2353
2566
|
: this.runtimes.entries("codex");
|
|
2354
2567
|
if (candidates.length === 0)
|
|
2355
|
-
throw new Error("未配置
|
|
2568
|
+
throw new Error("未配置 App Server Agent。");
|
|
2356
2569
|
const reads = await Promise.allSettled(candidates.map(async ([agentName, runtime]) => {
|
|
2357
2570
|
if (runtime.kind !== "codex" || !runtime.readRemoteSession) {
|
|
2358
2571
|
throw new Error(`Agent ${agentName} 不支持读取远端任务。`);
|
|
@@ -2377,7 +2590,7 @@ export class ProxySessionController {
|
|
|
2377
2590
|
: undefined)
|
|
2378
2591
|
.filter((detail) => Boolean(detail))
|
|
2379
2592
|
.join(";");
|
|
2380
|
-
throw new Error(`找不到
|
|
2593
|
+
throw new Error(`找不到 App Server 任务:${scoped?.remoteSessionId ?? reference}${details ? `(${details})` : ""}`);
|
|
2381
2594
|
}
|
|
2382
2595
|
async switchSession(contextKey, reference) {
|
|
2383
2596
|
this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent);
|
|
@@ -2398,10 +2611,10 @@ export class ProxySessionController {
|
|
|
2398
2611
|
}
|
|
2399
2612
|
const { agentName, remote } = await this.resolveRemoteCodexSession(taskId);
|
|
2400
2613
|
if (remote.status === "active" || remote.lastTurnStatus === "inProgress") {
|
|
2401
|
-
throw new Error(`这个任务正在外部
|
|
2614
|
+
throw new Error(`这个任务正在外部 Agent 中执行,当前不会切换。可使用 /status ${taskId} 查看进度。`);
|
|
2402
2615
|
}
|
|
2403
2616
|
if (!remote.cwd)
|
|
2404
|
-
throw new Error("这个
|
|
2617
|
+
throw new Error("这个 App Server 任务没有可用的工作目录,暂时无法切换。");
|
|
2405
2618
|
const localSessionId = createId("sess");
|
|
2406
2619
|
this.store.createSession({
|
|
2407
2620
|
localSessionId,
|
|
@@ -2437,7 +2650,7 @@ export class ProxySessionController {
|
|
|
2437
2650
|
}
|
|
2438
2651
|
const { runtime, remote } = await this.resolveRemoteCodexSession(taskId);
|
|
2439
2652
|
if (!runtime.readRemoteSession || !runtime.interruptRemoteTurn) {
|
|
2440
|
-
throw new Error("当前
|
|
2653
|
+
throw new Error("当前 App Server Agent 不支持停止外部任务。");
|
|
2441
2654
|
}
|
|
2442
2655
|
const turnId = remote.status === "active" || remote.lastTurnStatus === "inProgress"
|
|
2443
2656
|
? remote.lastTurnId
|
|
@@ -2452,7 +2665,7 @@ export class ProxySessionController {
|
|
|
2452
2665
|
turnId,
|
|
2453
2666
|
source: "sessions_card",
|
|
2454
2667
|
});
|
|
2455
|
-
await this.outbound.sendText(contextKey, `已向
|
|
2668
|
+
await this.outbound.sendText(contextKey, `已向 Agent 发送 Interrupt 请求:${turnId}`);
|
|
2456
2669
|
}
|
|
2457
2670
|
resolveSessionReference(contextKey, reference) {
|
|
2458
2671
|
if (reference === undefined) {
|
|
@@ -2494,7 +2707,7 @@ export class ProxySessionController {
|
|
|
2494
2707
|
&& activity.activeTurnId === record.lastTurnId;
|
|
2495
2708
|
const botOwnsActiveTurn = runtimeOwnsActiveTurn || persistedTurnMatches;
|
|
2496
2709
|
if (activity.active && !botOwnsActiveTurn) {
|
|
2497
|
-
throw new Error("这个任务正在外部
|
|
2710
|
+
throw new Error("这个任务正在外部 Agent 中执行。Agent Bot 不会接管或追加消息,请等待外部执行完成。");
|
|
2498
2711
|
}
|
|
2499
2712
|
return activity;
|
|
2500
2713
|
}
|
|
@@ -2503,7 +2716,7 @@ export class ProxySessionController {
|
|
|
2503
2716
|
remote = await runtime.readRemoteSession(record.remoteSessionId);
|
|
2504
2717
|
}
|
|
2505
2718
|
catch (error) {
|
|
2506
|
-
//
|
|
2719
|
+
// App Server does not materialize a new thread until its first user message.
|
|
2507
2720
|
// Such a thread has no turn to take over, so allow turn/start to create it.
|
|
2508
2721
|
if (!record.lastTurnId && isUnmaterializedCodexThreadError(error))
|
|
2509
2722
|
return;
|
|
@@ -2511,7 +2724,7 @@ export class ProxySessionController {
|
|
|
2511
2724
|
}
|
|
2512
2725
|
const botOwnsActiveTurn = isBotOwnedActiveTurn(record, remote);
|
|
2513
2726
|
if ((remote.status === "active" || remote.lastTurnStatus === "inProgress") && !botOwnsActiveTurn) {
|
|
2514
|
-
throw new Error("这个任务正在外部
|
|
2727
|
+
throw new Error("这个任务正在外部 Agent 中执行。Agent Bot 不会接管或追加消息,请等待外部执行完成。");
|
|
2515
2728
|
}
|
|
2516
2729
|
return undefined;
|
|
2517
2730
|
}
|
|
@@ -2567,7 +2780,7 @@ export class ProxySessionController {
|
|
|
2567
2780
|
remote = await runtime.readRemoteSession(current.remoteSessionId);
|
|
2568
2781
|
}
|
|
2569
2782
|
catch (error) {
|
|
2570
|
-
this.logger.warn({ error, sessionId: current.localSessionId }, "Failed to inspect
|
|
2783
|
+
this.logger.warn({ error, sessionId: current.localSessionId }, "Failed to inspect App Server task status.");
|
|
2571
2784
|
}
|
|
2572
2785
|
}
|
|
2573
2786
|
if (runtime.kind === "codex" && runtime.getGoal) {
|
|
@@ -2576,7 +2789,7 @@ export class ProxySessionController {
|
|
|
2576
2789
|
goal = await runtime.getGoal(loaded.localSessionId);
|
|
2577
2790
|
}
|
|
2578
2791
|
catch (error) {
|
|
2579
|
-
this.logger.warn({ error, sessionId: current.localSessionId }, "Failed to inspect
|
|
2792
|
+
this.logger.warn({ error, sessionId: current.localSessionId }, "Failed to inspect Agent goal status.");
|
|
2580
2793
|
}
|
|
2581
2794
|
}
|
|
2582
2795
|
}
|
|
@@ -2587,7 +2800,7 @@ export class ProxySessionController {
|
|
|
2587
2800
|
let activeTurnId;
|
|
2588
2801
|
let queued = 0;
|
|
2589
2802
|
if (!current) {
|
|
2590
|
-
taskLines.push("无。直接发送消息即可创建一个未指定项目的
|
|
2803
|
+
taskLines.push("无。直接发送消息即可创建一个未指定项目的 Agent 任务。");
|
|
2591
2804
|
}
|
|
2592
2805
|
else {
|
|
2593
2806
|
const agent = this.ensureAgent(current.agentName);
|
|
@@ -2603,7 +2816,7 @@ export class ProxySessionController {
|
|
|
2603
2816
|
const resultLabel = remoteActive
|
|
2604
2817
|
? "执行中"
|
|
2605
2818
|
: remoteTurnStatusLabel(remote?.lastTurnStatus ?? current.lastTurnStatus);
|
|
2606
|
-
taskLines.push(`**标题**:${cardCode(current.title ?? "未命名任务")}`, `**工作目录**:${cardCode(current.cwd)}`, `**Provider / 模型 / 思考强度**:${cardCode(current.modelProvider ?? "
|
|
2819
|
+
taskLines.push(`**标题**:${cardCode(current.title ?? "未命名任务")}`, `**工作目录**:${cardCode(current.cwd)}`, `**Provider / 模型 / 思考强度**:${cardCode(current.modelProvider ?? "Agent 默认")} / ${cardCode(current.model ?? "默认")} / ${cardCode(current.reasoningEffort ?? "自动")}`, `**状态 / 最近结果**:${statusLabel} / ${resultLabel}`, `**Agent**:${cardCode(agent.title)}`, `**权限 / 任务范围**:${current.permissionMode === "confirm" ? "执行前确认" : "自动执行"} / ${detectProjectlessWorkspace(current.cwd) ? "未指定项目" : "指定项目"}`, `**App Server 任务 ID**:${cardCode(current.remoteSessionId ?? "尚未创建")}`, `**当前执行 / 排队消息**:${activeTurnId ? cardCode(activeTurnId) : "无"} / ${queued} 条`, `**创建时间 / 最近活动**:${formatStatusTime(current.createdAt)} / ${formatStatusTime(current.updatedAt)}`);
|
|
2607
2820
|
}
|
|
2608
2821
|
const sections = [
|
|
2609
2822
|
{ title: targetSessionId ? "指定任务" : "当前任务", lines: taskLines },
|
|
@@ -2630,8 +2843,8 @@ export class ProxySessionController {
|
|
|
2630
2843
|
},
|
|
2631
2844
|
];
|
|
2632
2845
|
const title = targetSessionId && current
|
|
2633
|
-
? `
|
|
2634
|
-
: "
|
|
2846
|
+
? `Agent 状态:${truncateText((current.title ?? current.remoteSessionId ?? current.localSessionId).replace(/\s+/g, " "), 40)}`
|
|
2847
|
+
: "Agent 状态";
|
|
2635
2848
|
const taskId = current?.remoteSessionId
|
|
2636
2849
|
? remoteSessionReference(current.agentName, current.remoteSessionId)
|
|
2637
2850
|
: current?.localSessionId;
|
|
@@ -2670,7 +2883,7 @@ export class ProxySessionController {
|
|
|
2670
2883
|
`**工作目录**:${cardCode(remote.cwd || "目录未知")}`,
|
|
2671
2884
|
`**状态 / 当前任务**:${remoteSessionDetailStatus(remote)} / 未切换`,
|
|
2672
2885
|
`**Agent**:${cardCode(agentName)}`,
|
|
2673
|
-
`**
|
|
2886
|
+
`**App Server 任务 ID**:${cardCode(remote.id)}`,
|
|
2674
2887
|
`**最近回合**:${cardCode(remote.lastTurnId ?? "无")} ${remoteTurnStatusLabel(remote.lastTurnStatus)}`,
|
|
2675
2888
|
`**创建时间 / 最近活动**:${formatRemoteTime(remote.createdAt)} / ${formatRemoteTime(latestRemoteTimestamp(remote.recencyAt, remote.updatedAt))}`,
|
|
2676
2889
|
],
|
|
@@ -2681,14 +2894,14 @@ export class ProxySessionController {
|
|
|
2681
2894
|
lines: [
|
|
2682
2895
|
`**当前 / 最后步骤**:${statusExcerpt(remote.lastActivity ?? remoteStatusStep(remote), 500)}`,
|
|
2683
2896
|
isRemoteSessionActive(remote)
|
|
2684
|
-
? "外部
|
|
2897
|
+
? "外部 Agent 正在执行;Agent Bot 只读取状态,不会接管。"
|
|
2685
2898
|
: `发送 **/switch ${cardText(remote.id)}** 切换到此任务。`,
|
|
2686
2899
|
],
|
|
2687
2900
|
collapsible: true,
|
|
2688
2901
|
elementId: "status_execution_details",
|
|
2689
2902
|
},
|
|
2690
2903
|
];
|
|
2691
|
-
const title = `
|
|
2904
|
+
const title = `Agent 状态:${truncateText((remote.title ?? remote.preview ?? remote.id).replace(/\s+/g, " "), 40)}`;
|
|
2692
2905
|
const showStop = isRemoteSessionActive(remote)
|
|
2693
2906
|
&& options.forceSwitchTaskId !== actionReference
|
|
2694
2907
|
&& options.forceSwitchTaskId !== remote.id;
|
|
@@ -2720,6 +2933,7 @@ export class ProxySessionController {
|
|
|
2720
2933
|
"**/newgroup [title] [--dir <cwd> | --nodir]** 创建飞书群和新任务;参数与 /new 相同",
|
|
2721
2934
|
"**/forkgroup [title]** 从当前任务最新已完成轮次创建分支并绑定到新群",
|
|
2722
2935
|
"**/fork [序号或任务 ID]** 从当前或指定任务创建分支;运行中使用最近已完成轮次",
|
|
2936
|
+
"**/turns** 浏览当前任务的历史对话轮次,并可将对话上下文恢复到所选轮次",
|
|
2723
2937
|
"**/title <新标题>** 修改当前任务的标题",
|
|
2724
2938
|
"**/sessions [关键词]** 查找本机任务",
|
|
2725
2939
|
"**/switch [序号或任务 ID]** 不填参数切回上一个任务",
|
|
@@ -3090,6 +3304,9 @@ function formatStatusTime(value) {
|
|
|
3090
3304
|
hourCycle: "h23",
|
|
3091
3305
|
}).format(date);
|
|
3092
3306
|
}
|
|
3307
|
+
function formatResetTurnTime(value) {
|
|
3308
|
+
return value === undefined ? "完成时间未知" : formatStatusTime(new Date(value).toISOString());
|
|
3309
|
+
}
|
|
3093
3310
|
function cardText(value) {
|
|
3094
3311
|
return value.replaceAll("&", "&").replaceAll("`", "`").replaceAll("<", "<").replaceAll(">", ">");
|
|
3095
3312
|
}
|
|
@@ -3106,10 +3323,16 @@ function executionSettingsTabValue(value) {
|
|
|
3106
3323
|
return value;
|
|
3107
3324
|
throw new Error("设置卡片的 tab 无效,请重新发送设置命令。");
|
|
3108
3325
|
}
|
|
3326
|
+
function resetHistoryPageValue(value) {
|
|
3327
|
+
const page = Number(value);
|
|
3328
|
+
if (!Number.isSafeInteger(page) || page < 0)
|
|
3329
|
+
throw new Error("历史轮次卡片页码无效,请重新发送 /turns。");
|
|
3330
|
+
return page;
|
|
3331
|
+
}
|
|
3109
3332
|
function requiredCardMessageId(value) {
|
|
3110
3333
|
if (value)
|
|
3111
3334
|
return value;
|
|
3112
|
-
throw new Error("
|
|
3335
|
+
throw new Error("无法更新卡片,请重新发送对应命令。");
|
|
3113
3336
|
}
|
|
3114
3337
|
function remoteSessionStatusLabel(status) {
|
|
3115
3338
|
const labels = {
|
|
@@ -3206,9 +3429,9 @@ function isQueueIndependentCommand(command) {
|
|
|
3206
3429
|
return true;
|
|
3207
3430
|
return false;
|
|
3208
3431
|
}
|
|
3209
|
-
function
|
|
3432
|
+
function parseSessionPage(value) {
|
|
3210
3433
|
const parsed = typeof value === "string" || typeof value === "number" ? Number(value) : Number.NaN;
|
|
3211
|
-
return Number.isSafeInteger(parsed) && parsed >=
|
|
3434
|
+
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : 0;
|
|
3212
3435
|
}
|
|
3213
3436
|
function isPromptCommand(command) {
|
|
3214
3437
|
return command.type === "prompt" || command.type === "nosteer";
|