@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
|
@@ -17,11 +17,14 @@ import {
|
|
|
17
17
|
CardRenderer,
|
|
18
18
|
type CardSection,
|
|
19
19
|
type ExecutionSettingsTab,
|
|
20
|
+
type ResetHistoryCardEntry,
|
|
20
21
|
type TaskListCardAction,
|
|
22
|
+
type TaskListCardEntry,
|
|
21
23
|
} from "../feishu/CardRenderer.js";
|
|
22
24
|
import { generateGroupAvatarPng, resolveGroupAvatarProjectName } from "../feishu/GroupAvatarGenerator.js";
|
|
23
25
|
import type { OutboundRouter } from "../presentation/OutboundRouter.js";
|
|
24
26
|
import type { TurnActivity, TurnViewState } from "../presentation/turnViewTypes.js";
|
|
27
|
+
import { buildTurnGraphRows } from "../presentation/turnGraph.js";
|
|
25
28
|
import type { AgentRuntimeRegistry } from "../runtime/AgentRuntimeRegistry.js";
|
|
26
29
|
import type {
|
|
27
30
|
AgentRuntime,
|
|
@@ -61,6 +64,7 @@ const MESSAGE_COMPLETED_REACTION = "DONE";
|
|
|
61
64
|
const MESSAGE_FAILED_REACTION = "ERROR";
|
|
62
65
|
const MESSAGE_CANCELLED_REACTION = "CrossMark";
|
|
63
66
|
const SESSION_PAGE_SIZE = 5;
|
|
67
|
+
const RESET_HISTORY_PAGE_SIZE = 10;
|
|
64
68
|
const REMOTE_SESSION_REFERENCE_PREFIX = "agent-runtime:";
|
|
65
69
|
|
|
66
70
|
interface AgentRemoteSession {
|
|
@@ -77,7 +81,7 @@ interface AgentRemoteSessionSummary {
|
|
|
77
81
|
interface SessionsCardOptions {
|
|
78
82
|
updateMessageId?: string;
|
|
79
83
|
forceSwitchTaskId?: string;
|
|
80
|
-
|
|
84
|
+
page?: number;
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
interface StatusCardOptions {
|
|
@@ -85,6 +89,12 @@ interface StatusCardOptions {
|
|
|
85
89
|
forceSwitchTaskId?: string;
|
|
86
90
|
}
|
|
87
91
|
|
|
92
|
+
interface ResetHistoryCardOptions {
|
|
93
|
+
expectedSessionId?: string;
|
|
94
|
+
updateMessageId?: string;
|
|
95
|
+
page?: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
88
98
|
interface ModelCardOptions {
|
|
89
99
|
sessionId?: string;
|
|
90
100
|
updateMessageId?: string;
|
|
@@ -167,8 +177,9 @@ export interface ControlTaskGroupResult {
|
|
|
167
177
|
|
|
168
178
|
export interface ProxyLifecycle {
|
|
169
179
|
supervised?: boolean;
|
|
170
|
-
restart(contextKey: string, force: boolean): Promise<void>;
|
|
180
|
+
restart(contextKey: string, force: boolean, replyTarget?: MessageReplyTarget): Promise<void>;
|
|
171
181
|
cancelSafeRestart?(scheduleId: number): Promise<boolean>;
|
|
182
|
+
rememberFeishuUserOpenId?(userOpenId: string): Promise<void> | void;
|
|
172
183
|
}
|
|
173
184
|
|
|
174
185
|
export type ShellCommandExecutor = (command: string, cwd: string) => Promise<ShellCommandResult>;
|
|
@@ -181,6 +192,7 @@ export class ProxySessionController {
|
|
|
181
192
|
private readonly queuedPromptStarts = new Map<string, Promise<void>>();
|
|
182
193
|
private readonly queuedPromptCards = new Map<string, Map<string, string>>();
|
|
183
194
|
private readonly queuedPromptCardWrites = new Map<string, Promise<void>>();
|
|
195
|
+
private readonly sessionResets = new Map<string, Promise<void>>();
|
|
184
196
|
private readonly lastSessionListings = new Map<string, string[]>();
|
|
185
197
|
private readonly threadInitializations = new Map<string, Promise<void>>();
|
|
186
198
|
private readonly unsubscribe: Array<() => void> = [];
|
|
@@ -240,8 +252,20 @@ export class ProxySessionController {
|
|
|
240
252
|
"Failed to acknowledge the incoming Feishu message with a reaction.",
|
|
241
253
|
);
|
|
242
254
|
}
|
|
255
|
+
if (message.chatType === "p2p" && message.userId?.startsWith("ou_")) {
|
|
256
|
+
try {
|
|
257
|
+
await this.lifecycle?.rememberFeishuUserOpenId?.(message.userId);
|
|
258
|
+
} catch (error) {
|
|
259
|
+
this.logger.warn(
|
|
260
|
+
{ error, messageId: message.messageId },
|
|
261
|
+
"Failed to persist the first private-chat Lark user Open ID.",
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
243
265
|
if (message.chatId && message.chatType) {
|
|
244
|
-
|
|
266
|
+
const chatContextKey = baseChatContextKey(message.contextKey);
|
|
267
|
+
this.store.recordChatContext(chatContextKey, message.chatType);
|
|
268
|
+
this.store.markChatActive(chatContextKey);
|
|
245
269
|
}
|
|
246
270
|
const replyTarget = message.replyInThread
|
|
247
271
|
? { messageId: message.messageId, replyInThread: true as const }
|
|
@@ -252,9 +276,6 @@ export class ProxySessionController {
|
|
|
252
276
|
text: message.text,
|
|
253
277
|
...(imageCount > 0 ? { imageCount } : {}),
|
|
254
278
|
});
|
|
255
|
-
if (imageCount > 0 && message.chatId && message.chatType) {
|
|
256
|
-
this.store.markChatActive(baseChatContextKey(message.contextKey));
|
|
257
|
-
}
|
|
258
279
|
let localImagePaths: string[] | undefined;
|
|
259
280
|
if (imageCount > 0) {
|
|
260
281
|
try {
|
|
@@ -284,17 +305,6 @@ export class ProxySessionController {
|
|
|
284
305
|
);
|
|
285
306
|
return;
|
|
286
307
|
}
|
|
287
|
-
if (
|
|
288
|
-
message.chatId
|
|
289
|
-
&& message.chatType
|
|
290
|
-
&& (
|
|
291
|
-
command.type === "shell"
|
|
292
|
-
|| (command.type === "prompt" && !message.text.trimStart().startsWith("/"))
|
|
293
|
-
)
|
|
294
|
-
) {
|
|
295
|
-
this.store.markChatActive(baseChatContextKey(message.contextKey));
|
|
296
|
-
}
|
|
297
|
-
|
|
298
308
|
// Operational and read-only commands must remain available even if a prompt operation is slow.
|
|
299
309
|
if (isQueueIndependentCommand(command)) {
|
|
300
310
|
await this.outbound.withReplyTarget(message.contextKey, replyTarget, async () => {
|
|
@@ -369,6 +379,26 @@ export class ProxySessionController {
|
|
|
369
379
|
} else if (kind === "turn_cancel") {
|
|
370
380
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
371
381
|
await this.cancelSession(this.requireSession(contextKey, sessionId));
|
|
382
|
+
} else if (kind === "turn_reset") {
|
|
383
|
+
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
384
|
+
await this.resetCurrentSessionToTurn(
|
|
385
|
+
contextKey,
|
|
386
|
+
sessionId,
|
|
387
|
+
String(scopedAction.value.turnId ?? ""),
|
|
388
|
+
);
|
|
389
|
+
if (scopedAction.value.cardView === "reset_history") {
|
|
390
|
+
await this.openResetHistory(contextKey, {
|
|
391
|
+
expectedSessionId: sessionId,
|
|
392
|
+
updateMessageId: requiredCardMessageId(scopedAction.messageId),
|
|
393
|
+
page: resetHistoryPageValue(scopedAction.value.page),
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
} else if (kind === "turn_reset_page") {
|
|
397
|
+
await this.openResetHistory(contextKey, {
|
|
398
|
+
expectedSessionId: String(scopedAction.value.sessionId ?? ""),
|
|
399
|
+
updateMessageId: requiredCardMessageId(scopedAction.messageId),
|
|
400
|
+
page: resetHistoryPageValue(scopedAction.value.page),
|
|
401
|
+
});
|
|
372
402
|
} else if (kind === "queued_prompt_cancel") {
|
|
373
403
|
await this.cancelQueuedPrompt(scopedAction);
|
|
374
404
|
} else if (kind === "safe_restart_cancel") {
|
|
@@ -383,17 +413,17 @@ export class ProxySessionController {
|
|
|
383
413
|
if (!cancelled) {
|
|
384
414
|
await this.outbound.sendText(contextKey, "该安全重启计划已失效,请查看最新状态卡片。");
|
|
385
415
|
}
|
|
386
|
-
} else if (kind === "
|
|
387
|
-
await this.refreshSessionsCardFromAction(scopedAction
|
|
416
|
+
} else if (kind === "session_page") {
|
|
417
|
+
await this.refreshSessionsCardFromAction(scopedAction);
|
|
388
418
|
} else if (kind === "session_switch") {
|
|
389
419
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
390
420
|
await this.switchSession(contextKey, sessionId);
|
|
391
421
|
if (scopedAction.value.cardView === "status") await this.refreshStatusCardFromAction(scopedAction);
|
|
392
|
-
else await this.refreshSessionsCardFromAction(scopedAction);
|
|
422
|
+
else await this.refreshSessionsCardFromAction(scopedAction, { page: 0 });
|
|
393
423
|
} else if (kind === "session_fork") {
|
|
394
424
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
395
425
|
await this.forkSessionReference(contextKey, sessionId);
|
|
396
|
-
await this.refreshSessionsCardFromAction(scopedAction);
|
|
426
|
+
await this.refreshSessionsCardFromAction(scopedAction, { page: 0 });
|
|
397
427
|
} else if (kind === "session_fork_group") {
|
|
398
428
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
399
429
|
await this.forkSessionReferenceToFeishuGroup(contextKey, sessionId, scopedAction.userId);
|
|
@@ -401,7 +431,7 @@ export class ProxySessionController {
|
|
|
401
431
|
} else if (kind === "session_new") {
|
|
402
432
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
403
433
|
await this.createProjectSessionFromReference(contextKey, sessionId);
|
|
404
|
-
await this.refreshSessionsCardFromAction(scopedAction);
|
|
434
|
+
await this.refreshSessionsCardFromAction(scopedAction, { page: 0 });
|
|
405
435
|
} else if (kind === "session_new_group") {
|
|
406
436
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
407
437
|
await this.createFeishuGroupFromReference(contextKey, sessionId, scopedAction.userId);
|
|
@@ -410,7 +440,7 @@ export class ProxySessionController {
|
|
|
410
440
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
411
441
|
await this.stopSessionReference(contextKey, sessionId);
|
|
412
442
|
if (scopedAction.value.cardView === "status") await this.refreshStatusCardFromAction(scopedAction, sessionId);
|
|
413
|
-
else await this.refreshSessionsCardFromAction(scopedAction, sessionId);
|
|
443
|
+
else await this.refreshSessionsCardFromAction(scopedAction, { forceSwitchTaskId: sessionId });
|
|
414
444
|
} else if (kind === "session_status") {
|
|
415
445
|
await this.status(contextKey, String(scopedAction.value.sessionId ?? ""));
|
|
416
446
|
} else if (kind === "session_status_refresh") {
|
|
@@ -541,6 +571,7 @@ export class ProxySessionController {
|
|
|
541
571
|
this.lastSessionListings.clear();
|
|
542
572
|
this.queuedPromptCards.clear();
|
|
543
573
|
this.queuedPromptCardWrites.clear();
|
|
574
|
+
this.sessionResets.clear();
|
|
544
575
|
}
|
|
545
576
|
|
|
546
577
|
async controlStopTask(localSessionId: string): Promise<string> {
|
|
@@ -564,7 +595,7 @@ export class ProxySessionController {
|
|
|
564
595
|
try {
|
|
565
596
|
remote = await runtime.readRemoteSession(record.remoteSessionId);
|
|
566
597
|
} catch (error) {
|
|
567
|
-
this.logger.warn({ error, sessionId: record.localSessionId }, "Failed to inspect
|
|
598
|
+
this.logger.warn({ error, sessionId: record.localSessionId }, "Failed to inspect App Server task status for CLI.");
|
|
568
599
|
}
|
|
569
600
|
}
|
|
570
601
|
}
|
|
@@ -668,7 +699,7 @@ export class ProxySessionController {
|
|
|
668
699
|
const agentName = requestedAgentName?.trim() || source.agentName;
|
|
669
700
|
const agent = this.ensureAgent(agentName);
|
|
670
701
|
if (forceProjectless && agent.kind !== "app-server") {
|
|
671
|
-
throw new Error("task newgroup --nodir is only available for
|
|
702
|
+
throw new Error("task newgroup --nodir is only available for App Server agents.");
|
|
672
703
|
}
|
|
673
704
|
const sourceContextKey = this.outbound.getSessionContextKey(localSessionId) ?? source.contextKey;
|
|
674
705
|
const replyTarget = this.outbound.getSessionReplyTarget(localSessionId);
|
|
@@ -831,7 +862,10 @@ export class ProxySessionController {
|
|
|
831
862
|
return;
|
|
832
863
|
case "restart":
|
|
833
864
|
if (!this.lifecycle) throw new Error("当前运行方式不支持自动重启。");
|
|
834
|
-
await this.lifecycle.restart(contextKey, command.force === true);
|
|
865
|
+
await this.lifecycle.restart(contextKey, command.force === true, replyTarget);
|
|
866
|
+
return;
|
|
867
|
+
case "turns":
|
|
868
|
+
await this.openResetHistory(contextKey);
|
|
835
869
|
return;
|
|
836
870
|
case "model":
|
|
837
871
|
await this.openExecutionSettings(contextKey, "model");
|
|
@@ -907,7 +941,7 @@ export class ProxySessionController {
|
|
|
907
941
|
replyTarget?: MessageReplyTarget,
|
|
908
942
|
localImagePaths?: string[],
|
|
909
943
|
): Promise<void> {
|
|
910
|
-
if (!text.trim()) throw new Error("请输入要交给
|
|
944
|
+
if (!text.trim()) throw new Error("请输入要交给 Agent 的内容。");
|
|
911
945
|
let record = this.currentSession(contextKey);
|
|
912
946
|
if (!record) {
|
|
913
947
|
const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent!);
|
|
@@ -981,7 +1015,7 @@ export class ProxySessionController {
|
|
|
981
1015
|
if (messageId) await this.bindMessageReactionToTurn(messageId, record.localSessionId, activeTurnId);
|
|
982
1016
|
return;
|
|
983
1017
|
} catch (error) {
|
|
984
|
-
this.logger.debug({ error, sessionId: record.localSessionId, activeTurnId }, "Steering failed; reconciling the
|
|
1018
|
+
this.logger.debug({ error, sessionId: record.localSessionId, activeTurnId }, "Steering failed; reconciling the App Server thread.");
|
|
985
1019
|
let current = loaded.runtime.getSession(record.localSessionId);
|
|
986
1020
|
try {
|
|
987
1021
|
current = await loaded.runtime.synchronizeSession(record.localSessionId);
|
|
@@ -1006,7 +1040,7 @@ export class ProxySessionController {
|
|
|
1006
1040
|
} catch (retryError) {
|
|
1007
1041
|
this.logger.warn(
|
|
1008
1042
|
{ error: retryError, sessionId: record.localSessionId, activeTurnId: current.activeTurnId },
|
|
1009
|
-
"Failed to steer the reconciled
|
|
1043
|
+
"Failed to steer the reconciled App Server turn; queueing prompt.",
|
|
1010
1044
|
);
|
|
1011
1045
|
}
|
|
1012
1046
|
}
|
|
@@ -1176,7 +1210,7 @@ export class ProxySessionController {
|
|
|
1176
1210
|
const agent = this.ensureAgent(source.agentName);
|
|
1177
1211
|
const runtime = this.runtimes.forAgent(source.agentName);
|
|
1178
1212
|
if (runtime.kind !== "codex" || !runtime.forkSession) {
|
|
1179
|
-
throw new Error("当前
|
|
1213
|
+
throw new Error("当前 App Server Agent 不支持 fork 任务。");
|
|
1180
1214
|
}
|
|
1181
1215
|
|
|
1182
1216
|
const context = this.store.getOrCreateUserContext(message.contextKey, source.agentName);
|
|
@@ -1253,14 +1287,14 @@ export class ProxySessionController {
|
|
|
1253
1287
|
.map((messageId) => this.store.findTurnAnchorByMessageId(messageId))
|
|
1254
1288
|
.find((candidate) => candidate !== undefined);
|
|
1255
1289
|
if (!anchor) {
|
|
1256
|
-
throw new Error("无法确定这个话题对应的
|
|
1290
|
+
throw new Error("无法确定这个话题对应的 App Server 轮次,因此没有创建分支任务。请从该轮的用户消息、思考卡片或最终回答创建话题。");
|
|
1257
1291
|
}
|
|
1258
1292
|
|
|
1259
1293
|
const source = anchor.contextKey
|
|
1260
1294
|
? this.store.getSessionForContext(anchor.localSessionId, anchor.contextKey)
|
|
1261
1295
|
: this.store.getSession(anchor.localSessionId);
|
|
1262
1296
|
if (!source || !source.remoteSessionId || !this.isCodexSession(source)) {
|
|
1263
|
-
throw new Error("这个话题的来源不是可 fork 的
|
|
1297
|
+
throw new Error("这个话题的来源不是可 fork 的 App Server 任务。");
|
|
1264
1298
|
}
|
|
1265
1299
|
if (baseChatContextKey(anchor.contextKey ?? source.contextKey) !== `chat_id:${message.chatId}`) {
|
|
1266
1300
|
throw new Error("话题来源任务不属于当前会话,已拒绝创建分支。");
|
|
@@ -1269,7 +1303,7 @@ export class ProxySessionController {
|
|
|
1269
1303
|
const snapshot = turnViewSnapshot(this.store.getTurnSnapshot(anchor.turnId));
|
|
1270
1304
|
if (isTurnStillRunning(snapshot?.status)
|
|
1271
1305
|
|| (source.lastTurnId === anchor.turnId && source.lastTurnStatus === "running")) {
|
|
1272
|
-
throw new Error("话题对应的轮次仍在执行,
|
|
1306
|
+
throw new Error("话题对应的轮次仍在执行,App Server 暂时不能从这一轮 fork。请等待该轮完成后再在话题中发送消息。");
|
|
1273
1307
|
}
|
|
1274
1308
|
|
|
1275
1309
|
return { anchor, source, snapshot };
|
|
@@ -1311,7 +1345,7 @@ export class ProxySessionController {
|
|
|
1311
1345
|
}
|
|
1312
1346
|
|
|
1313
1347
|
if (source && (!source.remoteSessionId || !this.isCodexSession(source))) {
|
|
1314
|
-
throw new Error(`${sourceLabel}不是可 fork 的
|
|
1348
|
+
throw new Error(`${sourceLabel}不是可 fork 的 App Server 任务。`);
|
|
1315
1349
|
}
|
|
1316
1350
|
|
|
1317
1351
|
let runtime: AgentRuntime;
|
|
@@ -1321,20 +1355,20 @@ export class ProxySessionController {
|
|
|
1321
1355
|
agentName = source.agentName;
|
|
1322
1356
|
runtime = this.runtimes.forAgent(agentName);
|
|
1323
1357
|
} else {
|
|
1324
|
-
if (!taskId) throw new Error("缺少要 fork 的
|
|
1358
|
+
if (!taskId) throw new Error("缺少要 fork 的 App Server 任务 ID。");
|
|
1325
1359
|
const resolved = await this.resolveRemoteCodexSession(taskId);
|
|
1326
1360
|
agentName = resolved.agentName;
|
|
1327
1361
|
runtime = resolved.runtime;
|
|
1328
1362
|
remote = resolved.remote;
|
|
1329
1363
|
}
|
|
1330
1364
|
if (runtime.kind !== "codex" || !runtime.forkSession) {
|
|
1331
|
-
throw new Error("当前
|
|
1365
|
+
throw new Error("当前 App Server Agent 不支持 fork 任务。");
|
|
1332
1366
|
}
|
|
1333
1367
|
|
|
1334
1368
|
const remoteSessionId = source?.remoteSessionId ?? remote?.id ?? taskId;
|
|
1335
|
-
if (!remoteSessionId) throw new Error("当前任务尚未创建
|
|
1369
|
+
if (!remoteSessionId) throw new Error("当前任务尚未创建 App Server 任务 ID,暂时不能 fork。");
|
|
1336
1370
|
if (!runtime.readRemoteSession && !source) {
|
|
1337
|
-
throw new Error("当前
|
|
1371
|
+
throw new Error("当前 App Server Agent 不支持读取指定任务。");
|
|
1338
1372
|
}
|
|
1339
1373
|
remote ??= runtime.readRemoteSession
|
|
1340
1374
|
? await runtime.readRemoteSession(remoteSessionId)
|
|
@@ -1359,7 +1393,7 @@ export class ProxySessionController {
|
|
|
1359
1393
|
const forkedFromHistoricalTurn = isRunning && lastTurnId !== latestTurnId;
|
|
1360
1394
|
|
|
1361
1395
|
const cwd = remote?.cwd || source?.cwd;
|
|
1362
|
-
if (!cwd) throw new Error("指定的
|
|
1396
|
+
if (!cwd) throw new Error("指定的 App Server 任务没有可用的工作目录,暂时不能 fork。");
|
|
1363
1397
|
const sourceTitle = remote?.title ?? source?.title ?? remote?.preview;
|
|
1364
1398
|
const forkTitle = normalizeTaskTitle(requestedTitle) ?? this.store.nextForkTitle(sourceTitle);
|
|
1365
1399
|
const modelProvider = remote?.modelProvider ?? source?.modelProvider;
|
|
@@ -1440,16 +1474,16 @@ export class ProxySessionController {
|
|
|
1440
1474
|
sourceLabel: string,
|
|
1441
1475
|
): ForkSessionPlan {
|
|
1442
1476
|
if (!source.remoteSessionId || !this.isCodexSession(source)) {
|
|
1443
|
-
throw new Error(`${sourceLabel}不是可 fork 的
|
|
1477
|
+
throw new Error(`${sourceLabel}不是可 fork 的 App Server 任务。`);
|
|
1444
1478
|
}
|
|
1445
1479
|
const runtime = this.runtimes.forAgent(source.agentName);
|
|
1446
1480
|
if (runtime.kind !== "codex" || !runtime.forkSession) {
|
|
1447
|
-
throw new Error("当前
|
|
1481
|
+
throw new Error("当前 App Server Agent 不支持 fork 任务。");
|
|
1448
1482
|
}
|
|
1449
1483
|
|
|
1450
1484
|
const snapshot = turnViewSnapshot(this.store.getTurnSnapshot(lastTurnId));
|
|
1451
1485
|
if (isTurnStillRunning(snapshot?.status)) {
|
|
1452
|
-
throw new Error(`${sourceLabel}仍在执行,
|
|
1486
|
+
throw new Error(`${sourceLabel}仍在执行,App Server 暂时不能从这一轮 fork。`);
|
|
1453
1487
|
}
|
|
1454
1488
|
const sourceWasRunning = Boolean(
|
|
1455
1489
|
runtime.getSession(source.localSessionId)?.activeTurnId
|
|
@@ -1578,7 +1612,7 @@ export class ProxySessionController {
|
|
|
1578
1612
|
}
|
|
1579
1613
|
const source = direct ?? this.findStoredSessionByReference(taskId);
|
|
1580
1614
|
if (source && !this.isCodexSession(source)) {
|
|
1581
|
-
throw new Error("指定任务不是
|
|
1615
|
+
throw new Error("指定任务不是 App Server 任务,暂时不能按项目创建新任务。");
|
|
1582
1616
|
}
|
|
1583
1617
|
|
|
1584
1618
|
let agentName: string;
|
|
@@ -1609,7 +1643,7 @@ export class ProxySessionController {
|
|
|
1609
1643
|
|
|
1610
1644
|
const cwd = remote?.cwd || source?.cwd;
|
|
1611
1645
|
if (!cwd) {
|
|
1612
|
-
throw new Error("指定的
|
|
1646
|
+
throw new Error("指定的 App Server 任务没有可用的工作目录,暂时不能按项目创建新任务。");
|
|
1613
1647
|
}
|
|
1614
1648
|
const executionSettings: SessionExecutionSettings = {
|
|
1615
1649
|
modelProvider: remote?.modelProvider ?? source?.modelProvider,
|
|
@@ -1829,7 +1863,7 @@ export class ProxySessionController {
|
|
|
1829
1863
|
"群和新任务已创建。",
|
|
1830
1864
|
`当前任务:${taskDescription}`,
|
|
1831
1865
|
`当前 Project 目录:${boundProjectCwd ?? "未绑定(Projectless)"}`,
|
|
1832
|
-
`当前 Provider:${task.modelProvider ?? "
|
|
1866
|
+
`当前 Provider:${task.modelProvider ?? "Agent 默认"}`,
|
|
1833
1867
|
`当前模型:${task.model ?? "默认"}`,
|
|
1834
1868
|
`思考强度:${task.reasoningEffort ?? "自动"}`,
|
|
1835
1869
|
`权限类型:${task.permissionMode === "confirm" ? "执行前确认" : "自动执行"}`,
|
|
@@ -1920,7 +1954,7 @@ export class ProxySessionController {
|
|
|
1920
1954
|
`已从${prepared.sourceDescription}创建分支。`,
|
|
1921
1955
|
`当前任务:${taskDescription}`,
|
|
1922
1956
|
`当前 Project 目录:${boundProjectCwd ?? "未绑定(Projectless)"}`,
|
|
1923
|
-
`当前 Provider:${forked.session.modelProvider ?? "
|
|
1957
|
+
`当前 Provider:${forked.session.modelProvider ?? "Agent 默认"}`,
|
|
1924
1958
|
`当前模型:${forked.session.model ?? "默认"}`,
|
|
1925
1959
|
`思考强度:${forked.session.reasoningEffort ?? "自动"}`,
|
|
1926
1960
|
`权限类型:${forked.session.permissionMode === "confirm" ? "执行前确认" : "自动执行"}`,
|
|
@@ -2010,7 +2044,7 @@ export class ProxySessionController {
|
|
|
2010
2044
|
});
|
|
2011
2045
|
} catch (error) {
|
|
2012
2046
|
if (!(agent.kind === "app-server" && !record.lastTurnId && isMissingRolloutError(error))) throw error;
|
|
2013
|
-
this.logger.warn({ error, sessionId: record.localSessionId }, "
|
|
2047
|
+
this.logger.warn({ error, sessionId: record.localSessionId }, "App Server task has no rollout; creating a replacement task.");
|
|
2014
2048
|
session = await runtime.createSession({
|
|
2015
2049
|
localSessionId: record.localSessionId,
|
|
2016
2050
|
agentName: record.agentName,
|
|
@@ -2063,6 +2097,24 @@ export class ProxySessionController {
|
|
|
2063
2097
|
}
|
|
2064
2098
|
|
|
2065
2099
|
private async handleRuntimeEvent(event: RuntimeEvent): Promise<void> {
|
|
2100
|
+
if ("turnId" in event) {
|
|
2101
|
+
const source = this.store.getSession(event.sessionId);
|
|
2102
|
+
if (source?.remoteSessionId && this.isCodexSession(source)) {
|
|
2103
|
+
this.store.saveTurnRuntimeOrigin(
|
|
2104
|
+
event.turnId,
|
|
2105
|
+
source.localSessionId,
|
|
2106
|
+
source.agentName,
|
|
2107
|
+
source.remoteSessionId,
|
|
2108
|
+
);
|
|
2109
|
+
}
|
|
2110
|
+
if (event.type === "turn_started") {
|
|
2111
|
+
this.store.saveTurnParent(
|
|
2112
|
+
event.turnId,
|
|
2113
|
+
event.sessionId,
|
|
2114
|
+
source?.lastTurnStatus === "completed" ? source.lastTurnId : undefined,
|
|
2115
|
+
);
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2066
2118
|
if (event.type === "session_metadata_updated") {
|
|
2067
2119
|
this.store.updateRuntimeSession(event.sessionId, { title: event.title });
|
|
2068
2120
|
this.outbound.updateSessionTitle(event.sessionId, event.title);
|
|
@@ -2230,7 +2282,7 @@ export class ProxySessionController {
|
|
|
2230
2282
|
} catch (error) {
|
|
2231
2283
|
this.logger.warn(
|
|
2232
2284
|
{ error, sessionId: record.localSessionId },
|
|
2233
|
-
"Failed to pause the active
|
|
2285
|
+
"Failed to pause the active Agent goal before interrupting its turn.",
|
|
2234
2286
|
);
|
|
2235
2287
|
}
|
|
2236
2288
|
}
|
|
@@ -2244,7 +2296,7 @@ export class ProxySessionController {
|
|
|
2244
2296
|
} catch (error) {
|
|
2245
2297
|
this.logger.warn(
|
|
2246
2298
|
{ error, sessionId: record.localSessionId, remoteSessionId: record.remoteSessionId },
|
|
2247
|
-
"Failed to inspect the current
|
|
2299
|
+
"Failed to inspect the current App Server turn before interrupting; using the locally tracked turn.",
|
|
2248
2300
|
);
|
|
2249
2301
|
}
|
|
2250
2302
|
}
|
|
@@ -2258,7 +2310,7 @@ export class ProxySessionController {
|
|
|
2258
2310
|
remoteSessionId: record.remoteSessionId,
|
|
2259
2311
|
turnId,
|
|
2260
2312
|
});
|
|
2261
|
-
await this.outbound.sendText(record.contextKey, `已向
|
|
2313
|
+
await this.outbound.sendText(record.contextKey, `已向 Agent 发送 Interrupt 请求:${turnId}`);
|
|
2262
2314
|
return;
|
|
2263
2315
|
}
|
|
2264
2316
|
|
|
@@ -2271,6 +2323,111 @@ export class ProxySessionController {
|
|
|
2271
2323
|
await loaded.runtime.cancelTurn(record.localSessionId, turnId);
|
|
2272
2324
|
}
|
|
2273
2325
|
|
|
2326
|
+
private async resetCurrentSessionToTurn(
|
|
2327
|
+
contextKey: string,
|
|
2328
|
+
sessionId: string,
|
|
2329
|
+
turnId: string,
|
|
2330
|
+
): Promise<void> {
|
|
2331
|
+
if (!sessionId || !turnId) throw new Error("无效的 Reset 请求。请使用最新的思考卡片重试。");
|
|
2332
|
+
const previous = this.sessionResets.get(sessionId) ?? Promise.resolve();
|
|
2333
|
+
const reset = previous.catch(() => undefined).then(() =>
|
|
2334
|
+
this.performCurrentSessionReset(contextKey, sessionId, turnId));
|
|
2335
|
+
this.sessionResets.set(sessionId, reset);
|
|
2336
|
+
try {
|
|
2337
|
+
await reset;
|
|
2338
|
+
} finally {
|
|
2339
|
+
if (this.sessionResets.get(sessionId) === reset) this.sessionResets.delete(sessionId);
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
private async performCurrentSessionReset(
|
|
2344
|
+
contextKey: string,
|
|
2345
|
+
sessionId: string,
|
|
2346
|
+
turnId: string,
|
|
2347
|
+
): Promise<void> {
|
|
2348
|
+
const current = this.requireCurrentSession(contextKey);
|
|
2349
|
+
if (current.localSessionId !== sessionId) {
|
|
2350
|
+
throw new Error("这张思考卡片不属于当前任务。请先切换回对应任务,再点击 Reset。");
|
|
2351
|
+
}
|
|
2352
|
+
if (!current.remoteSessionId || !this.isCodexSession(current)) {
|
|
2353
|
+
throw new Error("当前任务不是可 Reset 的 App Server 任务。");
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
const snapshot = turnViewSnapshot(this.store.getTurnSnapshot(turnId));
|
|
2357
|
+
if (!snapshot || snapshot.sessionId !== sessionId || snapshot.status !== "completed") {
|
|
2358
|
+
throw new Error("只能将当前任务 Reset 到已成功完成的轮次。");
|
|
2359
|
+
}
|
|
2360
|
+
if (current.lastTurnId === turnId && current.lastTurnStatus === "completed") {
|
|
2361
|
+
await this.outbound.sendText(contextKey, "当前任务已经处于本轮完成后的对话状态,无需 Reset。");
|
|
2362
|
+
return;
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
const runtime = this.runtimes.forAgent(current.agentName);
|
|
2366
|
+
if (runtime.kind !== "codex" || !runtime.forkSession) {
|
|
2367
|
+
throw new Error("当前 App Server Agent 不支持 Reset。");
|
|
2368
|
+
}
|
|
2369
|
+
const loaded = await this.loadSession(current);
|
|
2370
|
+
const activity = await this.assertSessionTurnOwnership(current, runtime);
|
|
2371
|
+
const active = activity?.active
|
|
2372
|
+
?? Boolean(loaded.session.activeTurnId || current.status === "running" || current.lastTurnStatus === "running");
|
|
2373
|
+
if (active) {
|
|
2374
|
+
throw new Error("当前任务仍在执行。请等待任务完成或先停止任务,再点击 Reset。");
|
|
2375
|
+
}
|
|
2376
|
+
|
|
2377
|
+
const origin = this.store.getTurnRuntimeOrigin(turnId);
|
|
2378
|
+
if (origin && (origin.localSessionId !== sessionId || origin.agentName !== current.agentName)) {
|
|
2379
|
+
throw new Error("这张思考卡片与当前任务不匹配,已拒绝 Reset。");
|
|
2380
|
+
}
|
|
2381
|
+
const sourceRemoteSessionId = origin?.remoteSessionId ?? current.remoteSessionId;
|
|
2382
|
+
const previousRemoteSessionId = current.remoteSessionId;
|
|
2383
|
+
const forked = await runtime.forkSession({
|
|
2384
|
+
localSessionId: current.localSessionId,
|
|
2385
|
+
remoteSessionId: sourceRemoteSessionId,
|
|
2386
|
+
lastTurnId: turnId,
|
|
2387
|
+
agentName: current.agentName,
|
|
2388
|
+
cwd: current.cwd,
|
|
2389
|
+
title: current.title,
|
|
2390
|
+
modelProvider: current.modelProvider,
|
|
2391
|
+
model: current.model,
|
|
2392
|
+
reasoningEffort: current.reasoningEffort,
|
|
2393
|
+
permissionMode: current.permissionMode ?? "auto",
|
|
2394
|
+
});
|
|
2395
|
+
this.persistRuntimeSession(current, forked, "ready");
|
|
2396
|
+
this.store.updateRuntimeSession(current.localSessionId, {
|
|
2397
|
+
lastTurnId: turnId,
|
|
2398
|
+
lastTurnStatus: "completed",
|
|
2399
|
+
});
|
|
2400
|
+
this.outbound.registerSession(
|
|
2401
|
+
current.localSessionId,
|
|
2402
|
+
contextKey,
|
|
2403
|
+
forked.title ?? current.title,
|
|
2404
|
+
current.cwd,
|
|
2405
|
+
this.agentLabel(current.agentName),
|
|
2406
|
+
);
|
|
2407
|
+
this.store.audit(contextKey, "session_reset_to_turn", {
|
|
2408
|
+
localSessionId: current.localSessionId,
|
|
2409
|
+
previousRemoteSessionId,
|
|
2410
|
+
sourceRemoteSessionId,
|
|
2411
|
+
resetTurnId: turnId,
|
|
2412
|
+
forkedRemoteSessionId: forked.remoteSessionId,
|
|
2413
|
+
});
|
|
2414
|
+
const targetSummary = truncateText(
|
|
2415
|
+
(snapshot.prompt ?? snapshot.taskTitle ?? "未记录对话内容").replace(/\s+/g, " ").trim(),
|
|
2416
|
+
100,
|
|
2417
|
+
) || "未记录对话内容";
|
|
2418
|
+
const targetTime = snapshot.completedAt ?? snapshot.startedAt;
|
|
2419
|
+
await this.outbound.sendText(
|
|
2420
|
+
contextKey,
|
|
2421
|
+
[
|
|
2422
|
+
"已将当前任务重置到:",
|
|
2423
|
+
targetSummary,
|
|
2424
|
+
`完成时间:${targetTime === undefined ? "未知" : formatResetTurnTime(targetTime)}`,
|
|
2425
|
+
`Turn ID:${turnId}`,
|
|
2426
|
+
"后续对话将从该轮完成后的状态继续;本地文件没有回退。",
|
|
2427
|
+
].join("\n"),
|
|
2428
|
+
);
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2274
2431
|
private async resolveApproval(action: CardAction): Promise<void> {
|
|
2275
2432
|
const sessionId = String(action.value.sessionId ?? "");
|
|
2276
2433
|
const requestId = String(action.value.requestId ?? "");
|
|
@@ -2621,7 +2778,7 @@ export class ProxySessionController {
|
|
|
2621
2778
|
|
|
2622
2779
|
const loaded = await this.loadSession(record);
|
|
2623
2780
|
if (loaded.runtime.kind !== "codex" || !loaded.runtime.getGoal || !loaded.runtime.setGoal || !loaded.runtime.clearGoal) {
|
|
2624
|
-
throw new Error("
|
|
2781
|
+
throw new Error("当前 Agent 不支持 Goal 模式。");
|
|
2625
2782
|
}
|
|
2626
2783
|
|
|
2627
2784
|
if (command.action === "show") {
|
|
@@ -2648,7 +2805,7 @@ export class ProxySessionController {
|
|
|
2648
2805
|
goal,
|
|
2649
2806
|
command.action === "pause"
|
|
2650
2807
|
? "Goal 已暂停;当前轮次可以完成,但不会继续自动执行。"
|
|
2651
|
-
: "Goal 已恢复,
|
|
2808
|
+
: "Goal 已恢复,Agent 会继续自动执行。",
|
|
2652
2809
|
record,
|
|
2653
2810
|
);
|
|
2654
2811
|
return;
|
|
@@ -2669,7 +2826,7 @@ export class ProxySessionController {
|
|
|
2669
2826
|
await this.sendGoalCard(
|
|
2670
2827
|
contextKey,
|
|
2671
2828
|
goal,
|
|
2672
|
-
command.action === "edit" ? "Goal 已更新。" : "Goal 已启动,
|
|
2829
|
+
command.action === "edit" ? "Goal 已更新。" : "Goal 已启动,Agent 会持续执行直到完成、暂停或遇到阻塞。",
|
|
2673
2830
|
record,
|
|
2674
2831
|
);
|
|
2675
2832
|
}
|
|
@@ -2690,7 +2847,7 @@ export class ProxySessionController {
|
|
|
2690
2847
|
title: "任务",
|
|
2691
2848
|
lines: [
|
|
2692
2849
|
`**标题**:${cardText(record.title ?? "未命名任务")}`,
|
|
2693
|
-
`**
|
|
2850
|
+
`**App Server 任务 ID**:${cardText(record.remoteSessionId ?? "尚未创建")}`,
|
|
2694
2851
|
],
|
|
2695
2852
|
});
|
|
2696
2853
|
}
|
|
@@ -2701,7 +2858,7 @@ export class ProxySessionController {
|
|
|
2701
2858
|
"**/goal edit <新目标>** 修改 **/goal clear** 清除",
|
|
2702
2859
|
],
|
|
2703
2860
|
});
|
|
2704
|
-
await this.outbound.sendInteractiveCard(contextKey, this.cardRenderer.renderSectionsCard("
|
|
2861
|
+
await this.outbound.sendInteractiveCard(contextKey, this.cardRenderer.renderSectionsCard("Agent Goal", sections));
|
|
2705
2862
|
}
|
|
2706
2863
|
|
|
2707
2864
|
private async runShellCommand(contextKey: string, command: string): Promise<void> {
|
|
@@ -2824,6 +2981,92 @@ export class ProxySessionController {
|
|
|
2824
2981
|
await this.outbound.sendText(contextKey, mode === "auto" ? "已切换为自动执行模式。" : "已切换为执行前确认模式。");
|
|
2825
2982
|
}
|
|
2826
2983
|
|
|
2984
|
+
private async openResetHistory(
|
|
2985
|
+
contextKey: string,
|
|
2986
|
+
options: ResetHistoryCardOptions = {},
|
|
2987
|
+
): Promise<void> {
|
|
2988
|
+
const current = this.requireCurrentSession(contextKey);
|
|
2989
|
+
if (options.expectedSessionId && current.localSessionId !== options.expectedSessionId) {
|
|
2990
|
+
throw new Error("这张历史轮次卡片不属于当前任务。请先切换回对应任务,再重新发送 /turns。");
|
|
2991
|
+
}
|
|
2992
|
+
if (!current.remoteSessionId || !this.isCodexSession(current)) {
|
|
2993
|
+
throw new Error("当前任务不是可 Reset 的 App Server 任务。");
|
|
2994
|
+
}
|
|
2995
|
+
const allTurns = this.store.listCompletedTurnGraph(current.localSessionId, contextKey);
|
|
2996
|
+
const graphRows = buildTurnGraphRows(allTurns.map((turn) => ({
|
|
2997
|
+
turnId: turn.turnId,
|
|
2998
|
+
parentTurnId: turn.parentTurnId,
|
|
2999
|
+
})));
|
|
3000
|
+
const total = allTurns.length;
|
|
3001
|
+
const totalPages = Math.max(1, Math.ceil(total / RESET_HISTORY_PAGE_SIZE));
|
|
3002
|
+
const page = Math.max(0, Math.min(Math.trunc(options.page ?? 0), totalPages - 1));
|
|
3003
|
+
const offset = page * RESET_HISTORY_PAGE_SIZE;
|
|
3004
|
+
const turns = allTurns.slice(offset, offset + RESET_HISTORY_PAGE_SIZE);
|
|
3005
|
+
const entries: ResetHistoryCardEntry[] = turns.flatMap((turn, index) => {
|
|
3006
|
+
const snapshot = turnViewSnapshot(turn.snapshot);
|
|
3007
|
+
if (!snapshot) return [];
|
|
3008
|
+
const graph = graphRows[offset + index]!;
|
|
3009
|
+
const summary = truncateText(
|
|
3010
|
+
(snapshot.prompt ?? snapshot.taskTitle ?? "未记录对话内容").replace(/\s+/g, " ").trim(),
|
|
3011
|
+
100,
|
|
3012
|
+
);
|
|
3013
|
+
const isCurrent = current.lastTurnId === turn.turnId && current.lastTurnStatus === "completed";
|
|
3014
|
+
return [{
|
|
3015
|
+
sequence: graph.sequence,
|
|
3016
|
+
graphNodeLine: graph.nodeLine,
|
|
3017
|
+
graphConnectorLine: graph.connectorLine,
|
|
3018
|
+
lines: [
|
|
3019
|
+
`**${cardText(summary || "未记录对话内容")}**`,
|
|
3020
|
+
`${formatResetTurnTime(snapshot.completedAt ?? snapshot.startedAt)} · ${cardCode(turn.turnId)}`,
|
|
3021
|
+
],
|
|
3022
|
+
current: isCurrent,
|
|
3023
|
+
actions: isCurrent ? undefined : [{
|
|
3024
|
+
text: "Reset",
|
|
3025
|
+
value: {
|
|
3026
|
+
action: "turn_reset",
|
|
3027
|
+
cardView: "reset_history",
|
|
3028
|
+
sessionId: current.localSessionId,
|
|
3029
|
+
turnId: turn.turnId,
|
|
3030
|
+
contextKey,
|
|
3031
|
+
page: String(page),
|
|
3032
|
+
},
|
|
3033
|
+
}],
|
|
3034
|
+
}];
|
|
3035
|
+
});
|
|
3036
|
+
const pageActions: TaskListCardAction[] = [
|
|
3037
|
+
...(page > 0 ? [{
|
|
3038
|
+
text: "Previous",
|
|
3039
|
+
value: {
|
|
3040
|
+
action: "turn_reset_page",
|
|
3041
|
+
sessionId: current.localSessionId,
|
|
3042
|
+
contextKey,
|
|
3043
|
+
page: String(page - 1),
|
|
3044
|
+
},
|
|
3045
|
+
}] : []),
|
|
3046
|
+
...(page < totalPages - 1 ? [{
|
|
3047
|
+
text: "Next",
|
|
3048
|
+
value: {
|
|
3049
|
+
action: "turn_reset_page",
|
|
3050
|
+
sessionId: current.localSessionId,
|
|
3051
|
+
contextKey,
|
|
3052
|
+
page: String(page + 1),
|
|
3053
|
+
},
|
|
3054
|
+
}] : []),
|
|
3055
|
+
];
|
|
3056
|
+
const card = this.cardRenderer.renderResetHistoryCard({
|
|
3057
|
+
entries,
|
|
3058
|
+
footerLines: [
|
|
3059
|
+
`第 ${page + 1}/${totalPages} 页 · 共 ${total} 个已完成 turn`,
|
|
3060
|
+
],
|
|
3061
|
+
pageActions,
|
|
3062
|
+
});
|
|
3063
|
+
if (options.updateMessageId) {
|
|
3064
|
+
await this.outbound.updateInteractiveCard(contextKey, options.updateMessageId, card);
|
|
3065
|
+
} else {
|
|
3066
|
+
await this.outbound.sendInteractiveCard(contextKey, card);
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
|
|
2827
3070
|
private async listSessions(
|
|
2828
3071
|
contextKey: string,
|
|
2829
3072
|
searchTerm?: string,
|
|
@@ -2831,7 +3074,8 @@ export class ProxySessionController {
|
|
|
2831
3074
|
): Promise<void> {
|
|
2832
3075
|
const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent!);
|
|
2833
3076
|
const normalizedSearch = searchTerm?.trim().toLowerCase();
|
|
2834
|
-
const
|
|
3077
|
+
const requestedPage = Math.max(0, Math.trunc(options.page ?? 0));
|
|
3078
|
+
const fetchLimit = (requestedPage + 1) * SESSION_PAGE_SIZE;
|
|
2835
3079
|
const localSessions = this.store.listSessions(contextKey).filter((session) =>
|
|
2836
3080
|
session.status !== "closed" && this.isCodexSession(session),
|
|
2837
3081
|
);
|
|
@@ -2841,11 +3085,11 @@ export class ProxySessionController {
|
|
|
2841
3085
|
await Promise.all(this.runtimes.entries("codex").map(async ([agentName, runtime]) => {
|
|
2842
3086
|
if (!runtime.listRemoteSessions) return;
|
|
2843
3087
|
try {
|
|
2844
|
-
const
|
|
2845
|
-
remoteSessions.push(...
|
|
2846
|
-
remoteHasMore ||= Boolean(
|
|
3088
|
+
const result = await runtime.listRemoteSessions({ searchTerm, limit: fetchLimit });
|
|
3089
|
+
remoteSessions.push(...result.sessions.map((session) => ({ agentName, session })));
|
|
3090
|
+
remoteHasMore ||= Boolean(result.nextCursor);
|
|
2847
3091
|
} catch (error) {
|
|
2848
|
-
this.logger.warn({ error, contextKey, agentName }, "Failed to list
|
|
3092
|
+
this.logger.warn({ error, contextKey, agentName }, "Failed to list App Server sessions for an Agent.");
|
|
2849
3093
|
remoteErrors.push(`${agentName}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2850
3094
|
}
|
|
2851
3095
|
}));
|
|
@@ -2861,9 +3105,13 @@ export class ProxySessionController {
|
|
|
2861
3105
|
|| Number(right.active) - Number(left.active)
|
|
2862
3106
|
|| right.updatedAt - left.updatedAt);
|
|
2863
3107
|
const activeCount = entries.filter((entry) => entry.active).length;
|
|
2864
|
-
const
|
|
2865
|
-
const
|
|
2866
|
-
|
|
3108
|
+
const lastKnownPage = Math.max(0, Math.ceil(entries.length / SESSION_PAGE_SIZE) - 1);
|
|
3109
|
+
const page = remoteHasMore ? requestedPage : Math.min(requestedPage, lastKnownPage);
|
|
3110
|
+
const offset = page * SESSION_PAGE_SIZE;
|
|
3111
|
+
const visibleEntries = entries.slice(offset, offset + SESSION_PAGE_SIZE);
|
|
3112
|
+
const hasPrevious = page > 0;
|
|
3113
|
+
const hasNext = remoteHasMore || entries.length > offset + SESSION_PAGE_SIZE;
|
|
3114
|
+
this.lastSessionListings.set(contextKey, entries.map((entry) => entry.reference));
|
|
2867
3115
|
const cardEntries = visibleEntries.map((entry, index) => {
|
|
2868
3116
|
const marker = entry.current ? "✅" : entry.active ? "🟢 **活跃**" : "•";
|
|
2869
3117
|
const showStop = entry.status === "外部执行中"
|
|
@@ -2876,7 +3124,7 @@ export class ProxySessionController {
|
|
|
2876
3124
|
action: showStop ? "session_stop" : "session_switch",
|
|
2877
3125
|
sessionId: entry.reference,
|
|
2878
3126
|
...(searchTerm ? { searchTerm } : {}),
|
|
2879
|
-
|
|
3127
|
+
page: String(page),
|
|
2880
3128
|
contextKey,
|
|
2881
3129
|
},
|
|
2882
3130
|
}];
|
|
@@ -2886,7 +3134,7 @@ export class ProxySessionController {
|
|
|
2886
3134
|
action: "session_new",
|
|
2887
3135
|
sessionId: entry.reference,
|
|
2888
3136
|
...(searchTerm ? { searchTerm } : {}),
|
|
2889
|
-
|
|
3137
|
+
page: String(page),
|
|
2890
3138
|
contextKey,
|
|
2891
3139
|
},
|
|
2892
3140
|
});
|
|
@@ -2896,7 +3144,7 @@ export class ProxySessionController {
|
|
|
2896
3144
|
action: "session_new_group",
|
|
2897
3145
|
sessionId: entry.reference,
|
|
2898
3146
|
...(searchTerm ? { searchTerm } : {}),
|
|
2899
|
-
|
|
3147
|
+
page: String(page),
|
|
2900
3148
|
contextKey,
|
|
2901
3149
|
},
|
|
2902
3150
|
});
|
|
@@ -2906,7 +3154,7 @@ export class ProxySessionController {
|
|
|
2906
3154
|
action: "session_fork",
|
|
2907
3155
|
sessionId: entry.reference,
|
|
2908
3156
|
...(searchTerm ? { searchTerm } : {}),
|
|
2909
|
-
|
|
3157
|
+
page: String(page),
|
|
2910
3158
|
contextKey,
|
|
2911
3159
|
},
|
|
2912
3160
|
});
|
|
@@ -2916,7 +3164,7 @@ export class ProxySessionController {
|
|
|
2916
3164
|
action: "session_fork_group",
|
|
2917
3165
|
sessionId: entry.reference,
|
|
2918
3166
|
...(searchTerm ? { searchTerm } : {}),
|
|
2919
|
-
|
|
3167
|
+
page: String(page),
|
|
2920
3168
|
contextKey,
|
|
2921
3169
|
},
|
|
2922
3170
|
});
|
|
@@ -2930,34 +3178,45 @@ export class ProxySessionController {
|
|
|
2930
3178
|
});
|
|
2931
3179
|
return {
|
|
2932
3180
|
lines: [
|
|
2933
|
-
`**${index + 1}.** ${marker} **${cardText(entry.title)}** ${cardText(entry.id)}`,
|
|
3181
|
+
`**${offset + index + 1}.** ${marker} **${cardText(entry.title)}** ${cardText(entry.id)}`,
|
|
2934
3182
|
`${entry.status} · ${entry.updatedLabel} · ${cardText(entry.agentName)} · ${cardText(entry.cwd || "目录未知")}`,
|
|
2935
3183
|
],
|
|
2936
3184
|
actions,
|
|
2937
3185
|
};
|
|
2938
3186
|
});
|
|
2939
3187
|
const card = this.cardRenderer.renderTaskListCard(
|
|
2940
|
-
searchTerm ? `
|
|
3188
|
+
searchTerm ? `App Server 任务:${searchTerm}` : "App Server 任务",
|
|
2941
3189
|
activeCount > 0 ? `任务(${activeCount} 个活跃)` : "任务",
|
|
2942
3190
|
cardEntries,
|
|
2943
3191
|
[
|
|
2944
3192
|
...(remoteHint ? [remoteHint] : []),
|
|
3193
|
+
`第 ${page + 1} 页 · 每页 ${SESSION_PAGE_SIZE} 个任务${hasNext ? "" : ` · 当前共 ${entries.length} 个任务`}`,
|
|
2945
3194
|
"点击 **New** 在对应任务的项目中创建新任务;点击 **Switch** 快速切换;点击 **Fork** 从任务最新已完成轮次创建分支;外部正在运行的任务显示 **Stop**,点击后发送 Interrupt 并变为 **Switch**。",
|
|
2946
3195
|
"点击 **NewGroup** 在对应任务的项目中创建新群和新任务;点击 **ForkGroup** 从对应任务最新已完成轮次创建分支群。",
|
|
2947
3196
|
"也可发送 **/switch [序号或任务 ID]**;不带参数切回上一个任务。外部正在执行的回合不会被接管。",
|
|
2948
3197
|
"发送 **/fork [序号或任务 ID]**,可从当前或指定任务创建分支;任务运行中时使用最近已完成轮次。",
|
|
2949
3198
|
"点击 **Status**,或发送 **/status [序号或任务 ID]**,查看当前或指定任务状态。",
|
|
2950
3199
|
],
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
3200
|
+
[
|
|
3201
|
+
...(hasPrevious ? [{
|
|
3202
|
+
text: "Previous",
|
|
3203
|
+
value: {
|
|
3204
|
+
action: "session_page",
|
|
3205
|
+
...(searchTerm ? { searchTerm } : {}),
|
|
3206
|
+
page: String(page - 1),
|
|
3207
|
+
contextKey,
|
|
3208
|
+
},
|
|
3209
|
+
}] : []),
|
|
3210
|
+
...(hasNext ? [{
|
|
3211
|
+
text: "Next",
|
|
3212
|
+
value: {
|
|
3213
|
+
action: "session_page",
|
|
3214
|
+
...(searchTerm ? { searchTerm } : {}),
|
|
3215
|
+
page: String(page + 1),
|
|
3216
|
+
contextKey,
|
|
3217
|
+
},
|
|
3218
|
+
}] : []),
|
|
3219
|
+
],
|
|
2961
3220
|
);
|
|
2962
3221
|
if (options.updateMessageId) {
|
|
2963
3222
|
await this.outbound.updateInteractiveCard(contextKey, options.updateMessageId, card);
|
|
@@ -2968,18 +3227,16 @@ export class ProxySessionController {
|
|
|
2968
3227
|
|
|
2969
3228
|
private async refreshSessionsCardFromAction(
|
|
2970
3229
|
action: CardAction,
|
|
2971
|
-
forceSwitchTaskId?: string,
|
|
2972
|
-
loadMore = false,
|
|
3230
|
+
options: { forceSwitchTaskId?: string; page?: number } = {},
|
|
2973
3231
|
): Promise<void> {
|
|
2974
3232
|
if (!action.messageId) return;
|
|
2975
3233
|
const searchTerm = typeof action.value.searchTerm === "string" && action.value.searchTerm.trim()
|
|
2976
3234
|
? action.value.searchTerm
|
|
2977
3235
|
: undefined;
|
|
2978
|
-
const currentVisibleCount = parseSessionVisibleCount(action.value.visibleCount);
|
|
2979
3236
|
await this.listSessions(action.contextKey, searchTerm, {
|
|
2980
3237
|
updateMessageId: action.messageId,
|
|
2981
|
-
forceSwitchTaskId,
|
|
2982
|
-
|
|
3238
|
+
forceSwitchTaskId: options.forceSwitchTaskId,
|
|
3239
|
+
page: options.page ?? parseSessionPage(action.value.page),
|
|
2983
3240
|
});
|
|
2984
3241
|
}
|
|
2985
3242
|
|
|
@@ -3006,7 +3263,7 @@ export class ProxySessionController {
|
|
|
3006
3263
|
const candidates = scoped
|
|
3007
3264
|
? [[scoped.agentName, this.runtimes.forAgent(scoped.agentName)] as const]
|
|
3008
3265
|
: this.runtimes.entries("codex");
|
|
3009
|
-
if (candidates.length === 0) throw new Error("未配置
|
|
3266
|
+
if (candidates.length === 0) throw new Error("未配置 App Server Agent。");
|
|
3010
3267
|
|
|
3011
3268
|
const reads = await Promise.allSettled(candidates.map(async ([agentName, runtime]) => {
|
|
3012
3269
|
if (runtime.kind !== "codex" || !runtime.readRemoteSession) {
|
|
@@ -3031,7 +3288,7 @@ export class ProxySessionController {
|
|
|
3031
3288
|
: undefined)
|
|
3032
3289
|
.filter((detail): detail is string => Boolean(detail))
|
|
3033
3290
|
.join(";");
|
|
3034
|
-
throw new Error(`找不到
|
|
3291
|
+
throw new Error(`找不到 App Server 任务:${scoped?.remoteSessionId ?? reference}${details ? `(${details})` : ""}`);
|
|
3035
3292
|
}
|
|
3036
3293
|
|
|
3037
3294
|
private async switchSession(contextKey: string, reference?: string): Promise<void> {
|
|
@@ -3060,9 +3317,9 @@ export class ProxySessionController {
|
|
|
3060
3317
|
|
|
3061
3318
|
const { agentName, remote } = await this.resolveRemoteCodexSession(taskId);
|
|
3062
3319
|
if (remote.status === "active" || remote.lastTurnStatus === "inProgress") {
|
|
3063
|
-
throw new Error(`这个任务正在外部
|
|
3320
|
+
throw new Error(`这个任务正在外部 Agent 中执行,当前不会切换。可使用 /status ${taskId} 查看进度。`);
|
|
3064
3321
|
}
|
|
3065
|
-
if (!remote.cwd) throw new Error("这个
|
|
3322
|
+
if (!remote.cwd) throw new Error("这个 App Server 任务没有可用的工作目录,暂时无法切换。");
|
|
3066
3323
|
const localSessionId = createId("sess");
|
|
3067
3324
|
this.store.createSession({
|
|
3068
3325
|
localSessionId,
|
|
@@ -3109,7 +3366,7 @@ export class ProxySessionController {
|
|
|
3109
3366
|
|
|
3110
3367
|
const { runtime, remote } = await this.resolveRemoteCodexSession(taskId);
|
|
3111
3368
|
if (!runtime.readRemoteSession || !runtime.interruptRemoteTurn) {
|
|
3112
|
-
throw new Error("当前
|
|
3369
|
+
throw new Error("当前 App Server Agent 不支持停止外部任务。");
|
|
3113
3370
|
}
|
|
3114
3371
|
const turnId = remote.status === "active" || remote.lastTurnStatus === "inProgress"
|
|
3115
3372
|
? remote.lastTurnId
|
|
@@ -3124,7 +3381,7 @@ export class ProxySessionController {
|
|
|
3124
3381
|
turnId,
|
|
3125
3382
|
source: "sessions_card",
|
|
3126
3383
|
});
|
|
3127
|
-
await this.outbound.sendText(contextKey, `已向
|
|
3384
|
+
await this.outbound.sendText(contextKey, `已向 Agent 发送 Interrupt 请求:${turnId}`);
|
|
3128
3385
|
}
|
|
3129
3386
|
|
|
3130
3387
|
private resolveSessionReference(contextKey: string, reference?: string): string {
|
|
@@ -3165,7 +3422,7 @@ export class ProxySessionController {
|
|
|
3165
3422
|
&& activity.activeTurnId === record.lastTurnId;
|
|
3166
3423
|
const botOwnsActiveTurn = runtimeOwnsActiveTurn || persistedTurnMatches;
|
|
3167
3424
|
if (activity.active && !botOwnsActiveTurn) {
|
|
3168
|
-
throw new Error("这个任务正在外部
|
|
3425
|
+
throw new Error("这个任务正在外部 Agent 中执行。Agent Bot 不会接管或追加消息,请等待外部执行完成。");
|
|
3169
3426
|
}
|
|
3170
3427
|
return activity;
|
|
3171
3428
|
}
|
|
@@ -3173,14 +3430,14 @@ export class ProxySessionController {
|
|
|
3173
3430
|
try {
|
|
3174
3431
|
remote = await runtime.readRemoteSession(record.remoteSessionId);
|
|
3175
3432
|
} catch (error) {
|
|
3176
|
-
//
|
|
3433
|
+
// App Server does not materialize a new thread until its first user message.
|
|
3177
3434
|
// Such a thread has no turn to take over, so allow turn/start to create it.
|
|
3178
3435
|
if (!record.lastTurnId && isUnmaterializedCodexThreadError(error)) return;
|
|
3179
3436
|
throw error;
|
|
3180
3437
|
}
|
|
3181
3438
|
const botOwnsActiveTurn = isBotOwnedActiveTurn(record, remote);
|
|
3182
3439
|
if ((remote.status === "active" || remote.lastTurnStatus === "inProgress") && !botOwnsActiveTurn) {
|
|
3183
|
-
throw new Error("这个任务正在外部
|
|
3440
|
+
throw new Error("这个任务正在外部 Agent 中执行。Agent Bot 不会接管或追加消息,请等待外部执行完成。");
|
|
3184
3441
|
}
|
|
3185
3442
|
return undefined;
|
|
3186
3443
|
}
|
|
@@ -3245,7 +3502,7 @@ export class ProxySessionController {
|
|
|
3245
3502
|
try {
|
|
3246
3503
|
remote = await runtime.readRemoteSession(current.remoteSessionId);
|
|
3247
3504
|
} catch (error) {
|
|
3248
|
-
this.logger.warn({ error, sessionId: current.localSessionId }, "Failed to inspect
|
|
3505
|
+
this.logger.warn({ error, sessionId: current.localSessionId }, "Failed to inspect App Server task status.");
|
|
3249
3506
|
}
|
|
3250
3507
|
}
|
|
3251
3508
|
if (runtime.kind === "codex" && runtime.getGoal) {
|
|
@@ -3253,7 +3510,7 @@ export class ProxySessionController {
|
|
|
3253
3510
|
const loaded = runtime.getSession(current.localSessionId) ?? (await this.loadSession(current)).session;
|
|
3254
3511
|
goal = await runtime.getGoal(loaded.localSessionId);
|
|
3255
3512
|
} catch (error) {
|
|
3256
|
-
this.logger.warn({ error, sessionId: current.localSessionId }, "Failed to inspect
|
|
3513
|
+
this.logger.warn({ error, sessionId: current.localSessionId }, "Failed to inspect Agent goal status.");
|
|
3257
3514
|
}
|
|
3258
3515
|
}
|
|
3259
3516
|
}
|
|
@@ -3265,7 +3522,7 @@ export class ProxySessionController {
|
|
|
3265
3522
|
let activeTurnId: string | undefined;
|
|
3266
3523
|
let queued = 0;
|
|
3267
3524
|
if (!current) {
|
|
3268
|
-
taskLines.push("无。直接发送消息即可创建一个未指定项目的
|
|
3525
|
+
taskLines.push("无。直接发送消息即可创建一个未指定项目的 Agent 任务。");
|
|
3269
3526
|
} else {
|
|
3270
3527
|
const agent = this.ensureAgent(current.agentName);
|
|
3271
3528
|
const runtimeSession = this.runtimes.forAgent(current.agentName).getSession(current.localSessionId);
|
|
@@ -3283,11 +3540,11 @@ export class ProxySessionController {
|
|
|
3283
3540
|
taskLines.push(
|
|
3284
3541
|
`**标题**:${cardCode(current.title ?? "未命名任务")}`,
|
|
3285
3542
|
`**工作目录**:${cardCode(current.cwd)}`,
|
|
3286
|
-
`**Provider / 模型 / 思考强度**:${cardCode(current.modelProvider ?? "
|
|
3543
|
+
`**Provider / 模型 / 思考强度**:${cardCode(current.modelProvider ?? "Agent 默认")} / ${cardCode(current.model ?? "默认")} / ${cardCode(current.reasoningEffort ?? "自动")}`,
|
|
3287
3544
|
`**状态 / 最近结果**:${statusLabel} / ${resultLabel}`,
|
|
3288
3545
|
`**Agent**:${cardCode(agent.title)}`,
|
|
3289
3546
|
`**权限 / 任务范围**:${current.permissionMode === "confirm" ? "执行前确认" : "自动执行"} / ${detectProjectlessWorkspace(current.cwd) ? "未指定项目" : "指定项目"}`,
|
|
3290
|
-
`**
|
|
3547
|
+
`**App Server 任务 ID**:${cardCode(current.remoteSessionId ?? "尚未创建")}`,
|
|
3291
3548
|
`**当前执行 / 排队消息**:${activeTurnId ? cardCode(activeTurnId) : "无"} / ${queued} 条`,
|
|
3292
3549
|
`**创建时间 / 最近活动**:${formatStatusTime(current.createdAt)} / ${formatStatusTime(current.updatedAt)}`,
|
|
3293
3550
|
);
|
|
@@ -3318,8 +3575,8 @@ export class ProxySessionController {
|
|
|
3318
3575
|
},
|
|
3319
3576
|
];
|
|
3320
3577
|
const title = targetSessionId && current
|
|
3321
|
-
? `
|
|
3322
|
-
: "
|
|
3578
|
+
? `Agent 状态:${truncateText((current.title ?? current.remoteSessionId ?? current.localSessionId).replace(/\s+/g, " "), 40)}`
|
|
3579
|
+
: "Agent 状态";
|
|
3323
3580
|
const taskId = current?.remoteSessionId
|
|
3324
3581
|
? remoteSessionReference(current.agentName, current.remoteSessionId)
|
|
3325
3582
|
: current?.localSessionId;
|
|
@@ -3363,7 +3620,7 @@ export class ProxySessionController {
|
|
|
3363
3620
|
`**工作目录**:${cardCode(remote.cwd || "目录未知")}`,
|
|
3364
3621
|
`**状态 / 当前任务**:${remoteSessionDetailStatus(remote)} / 未切换`,
|
|
3365
3622
|
`**Agent**:${cardCode(agentName)}`,
|
|
3366
|
-
`**
|
|
3623
|
+
`**App Server 任务 ID**:${cardCode(remote.id)}`,
|
|
3367
3624
|
`**最近回合**:${cardCode(remote.lastTurnId ?? "无")} ${remoteTurnStatusLabel(remote.lastTurnStatus)}`,
|
|
3368
3625
|
`**创建时间 / 最近活动**:${formatRemoteTime(remote.createdAt)} / ${formatRemoteTime(latestRemoteTimestamp(remote.recencyAt, remote.updatedAt))}`,
|
|
3369
3626
|
],
|
|
@@ -3374,14 +3631,14 @@ export class ProxySessionController {
|
|
|
3374
3631
|
lines: [
|
|
3375
3632
|
`**当前 / 最后步骤**:${statusExcerpt(remote.lastActivity ?? remoteStatusStep(remote), 500)}`,
|
|
3376
3633
|
isRemoteSessionActive(remote)
|
|
3377
|
-
? "外部
|
|
3634
|
+
? "外部 Agent 正在执行;Agent Bot 只读取状态,不会接管。"
|
|
3378
3635
|
: `发送 **/switch ${cardText(remote.id)}** 切换到此任务。`,
|
|
3379
3636
|
],
|
|
3380
3637
|
collapsible: true,
|
|
3381
3638
|
elementId: "status_execution_details",
|
|
3382
3639
|
},
|
|
3383
3640
|
];
|
|
3384
|
-
const title = `
|
|
3641
|
+
const title = `Agent 状态:${truncateText((remote.title ?? remote.preview ?? remote.id).replace(/\s+/g, " "), 40)}`;
|
|
3385
3642
|
const showStop = isRemoteSessionActive(remote)
|
|
3386
3643
|
&& options.forceSwitchTaskId !== actionReference
|
|
3387
3644
|
&& options.forceSwitchTaskId !== remote.id;
|
|
@@ -3418,6 +3675,7 @@ export class ProxySessionController {
|
|
|
3418
3675
|
"**/newgroup [title] [--dir <cwd> | --nodir]** 创建飞书群和新任务;参数与 /new 相同",
|
|
3419
3676
|
"**/forkgroup [title]** 从当前任务最新已完成轮次创建分支并绑定到新群",
|
|
3420
3677
|
"**/fork [序号或任务 ID]** 从当前或指定任务创建分支;运行中使用最近已完成轮次",
|
|
3678
|
+
"**/turns** 浏览当前任务的历史对话轮次,并可将对话上下文恢复到所选轮次",
|
|
3421
3679
|
"**/title <新标题>** 修改当前任务的标题",
|
|
3422
3680
|
"**/sessions [关键词]** 查找本机任务",
|
|
3423
3681
|
"**/switch [序号或任务 ID]** 不填参数切回上一个任务",
|
|
@@ -3825,6 +4083,10 @@ function formatStatusTime(value: string): string {
|
|
|
3825
4083
|
}).format(date);
|
|
3826
4084
|
}
|
|
3827
4085
|
|
|
4086
|
+
function formatResetTurnTime(value?: number): string {
|
|
4087
|
+
return value === undefined ? "完成时间未知" : formatStatusTime(new Date(value).toISOString());
|
|
4088
|
+
}
|
|
4089
|
+
|
|
3828
4090
|
function cardText(value: string): string {
|
|
3829
4091
|
return value.replaceAll("&", "&").replaceAll("`", "`").replaceAll("<", "<").replaceAll(">", ">");
|
|
3830
4092
|
}
|
|
@@ -3843,9 +4105,15 @@ function executionSettingsTabValue(value: unknown): ExecutionSettingsTab {
|
|
|
3843
4105
|
throw new Error("设置卡片的 tab 无效,请重新发送设置命令。");
|
|
3844
4106
|
}
|
|
3845
4107
|
|
|
4108
|
+
function resetHistoryPageValue(value: unknown): number {
|
|
4109
|
+
const page = Number(value);
|
|
4110
|
+
if (!Number.isSafeInteger(page) || page < 0) throw new Error("历史轮次卡片页码无效,请重新发送 /turns。");
|
|
4111
|
+
return page;
|
|
4112
|
+
}
|
|
4113
|
+
|
|
3846
4114
|
function requiredCardMessageId(value: string | undefined): string {
|
|
3847
4115
|
if (value) return value;
|
|
3848
|
-
throw new Error("
|
|
4116
|
+
throw new Error("无法更新卡片,请重新发送对应命令。");
|
|
3849
4117
|
}
|
|
3850
4118
|
|
|
3851
4119
|
function remoteSessionStatusLabel(status: RemoteSessionSummary["status"]): string {
|
|
@@ -3947,9 +4215,9 @@ function isQueueIndependentCommand(command: Command): boolean {
|
|
|
3947
4215
|
return false;
|
|
3948
4216
|
}
|
|
3949
4217
|
|
|
3950
|
-
function
|
|
4218
|
+
function parseSessionPage(value: unknown): number {
|
|
3951
4219
|
const parsed = typeof value === "string" || typeof value === "number" ? Number(value) : Number.NaN;
|
|
3952
|
-
return Number.isSafeInteger(parsed) && parsed >=
|
|
4220
|
+
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : 0;
|
|
3953
4221
|
}
|
|
3954
4222
|
|
|
3955
4223
|
function isPromptCommand(command: Command): boolean {
|