@keyou007/agent-bot 0.1.20-alpha.1 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +27 -4
- package/README.zh.md +23 -4
- package/dist/cli/AgentPrerequisites.js +11 -47
- package/dist/cli/AgentPrerequisites.js.map +1 -1
- package/dist/cli/DefaultAgentSelection.js +1 -1
- package/dist/cli/DefaultAgentSelection.js.map +1 -1
- package/dist/cli.js +15 -0
- package/dist/cli.js.map +1 -1
- package/dist/codex/CodexEventMapper.js +21 -1
- package/dist/codex/CodexEventMapper.js.map +1 -1
- package/dist/codex/CodexLocalActivityDetector.js +32 -10
- package/dist/codex/CodexLocalActivityDetector.js.map +1 -1
- package/dist/codex/CodexProcessManager.js +56 -7
- package/dist/codex/CodexProcessManager.js.map +1 -1
- package/dist/codex/CodexRuntime.js +361 -168
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/codex/CodexVersion.js +18 -0
- package/dist/codex/CodexVersion.js.map +1 -0
- package/dist/codex/ProviderModels.js +159 -0
- package/dist/codex/ProviderModels.js.map +1 -0
- package/dist/codex/ProviderSwitch.js +32 -0
- package/dist/codex/ProviderSwitch.js.map +1 -0
- package/dist/commands/CommandRouter.js +8 -3
- package/dist/commands/CommandRouter.js.map +1 -1
- package/dist/console/ConsoleTurnPresenter.js +2 -1
- package/dist/console/ConsoleTurnPresenter.js.map +1 -1
- package/dist/feishu/CardRenderer.js +82 -27
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuConnector.js +17 -5
- package/dist/feishu/FeishuConnector.js.map +1 -1
- package/dist/feishu/FeishuTurnPresenter.js +34 -8
- package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
- package/dist/feishu/InboundText.js +11 -0
- package/dist/feishu/InboundText.js.map +1 -1
- package/dist/feishu/MergedForwardMessage.js +17 -5
- package/dist/feishu/MergedForwardMessage.js.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/local-files/LocalFileViewerServer.js +164 -19
- package/dist/local-files/LocalFileViewerServer.js.map +1 -1
- package/dist/local-files/MarkdownFileLinks.js +65 -0
- package/dist/local-files/MarkdownFileLinks.js.map +1 -0
- package/dist/local-files/TurnPreviewPage.js +725 -0
- package/dist/local-files/TurnPreviewPage.js.map +1 -0
- package/dist/presentation/OutboundRouter.js +6 -1
- package/dist/presentation/OutboundRouter.js.map +1 -1
- package/dist/presentation/TurnStateReducer.js +142 -3
- package/dist/presentation/TurnStateReducer.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +573 -325
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/state/StateStore.js +65 -65
- package/dist/state/StateStore.js.map +1 -1
- package/dist/utils/formatStorageSize.js +15 -0
- package/dist/utils/formatStorageSize.js.map +1 -0
- package/dist/utils/semanticVersion.js +39 -0
- package/dist/utils/semanticVersion.js.map +1 -0
- package/docs/technical-reference.md +11 -3
- package/docs/technical-reference.zh.md +11 -3
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/agent-bot/SKILL.md +11 -1
- package/src/cli/AgentPrerequisites.ts +14 -40
- package/src/cli/DefaultAgentSelection.ts +1 -1
- package/src/cli.ts +13 -0
- package/src/codex/CodexEventMapper.ts +31 -1
- package/src/codex/CodexLocalActivityDetector.ts +41 -9
- package/src/codex/CodexProcessManager.ts +48 -7
- package/src/codex/CodexRuntime.ts +397 -204
- package/src/codex/CodexVersion.ts +18 -0
- package/src/codex/ProviderModels.ts +185 -0
- package/src/codex/ProviderSwitch.ts +36 -0
- package/src/commands/CommandRouter.ts +7 -2
- package/src/commands/commandTypes.ts +1 -1
- package/src/console/ConsoleTurnPresenter.ts +3 -0
- package/src/feishu/CardRenderer.ts +84 -43
- package/src/feishu/FeishuConnector.ts +15 -5
- package/src/feishu/FeishuTurnPresenter.ts +46 -4
- package/src/feishu/InboundText.ts +11 -0
- package/src/feishu/MergedForwardMessage.ts +15 -5
- package/src/index.ts +3 -1
- package/src/local-files/LocalFileViewerServer.ts +179 -20
- package/src/local-files/MarkdownFileLinks.ts +64 -0
- package/src/local-files/TurnPreviewPage.ts +792 -0
- package/src/presentation/OutboundRouter.ts +8 -0
- package/src/presentation/TurnStateReducer.ts +175 -2
- package/src/presentation/turnViewTypes.ts +13 -0
- package/src/proxy/ProxySessionController.ts +547 -297
- package/src/runtime/types.ts +33 -3
- package/src/state/StateStore.ts +86 -90
- package/src/utils/formatStorageSize.ts +13 -0
- package/src/utils/semanticVersion.ts +28 -0
|
@@ -3,6 +3,8 @@ import { promises as fs } from "node:fs";
|
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { createProjectlessWorkspace, detectProjectlessWorkspace } from "../codex/ProjectlessWorkspace.js";
|
|
6
|
+
import { CodexVersionError } from "../codex/CodexVersion.js";
|
|
7
|
+
import { assertProviderSettingsApplied, isMissingRolloutError } from "../codex/ProviderSwitch.js";
|
|
6
8
|
import { SystemThreadWriterProcessController, } from "../codex/ThreadWriterProcess.js";
|
|
7
9
|
import { resolveUserPath } from "../config/paths.js";
|
|
8
10
|
import { DEFAULT_GROUP_NAME_FORMAT, } from "../config/schema.js";
|
|
@@ -19,6 +21,7 @@ import { errorLogValue } from "../logging/errorLogValue.js";
|
|
|
19
21
|
import { buildTurnGraphRows } from "../presentation/turnGraph.js";
|
|
20
22
|
import { isActiveShellCommandJob, ShellCommandJobManager, } from "../shell/ShellCommandJobManager.js";
|
|
21
23
|
import { createId } from "../utils/id.js";
|
|
24
|
+
import { formatStorageSize } from "../utils/formatStorageSize.js";
|
|
22
25
|
import { truncateMiddle, truncateText } from "../utils/markdown.js";
|
|
23
26
|
import { normalizeTaskTitle } from "../utils/taskTitle.js";
|
|
24
27
|
import { executeShellCommand, } from "../utils/executeShellCommand.js";
|
|
@@ -50,6 +53,22 @@ const FORWARD_ATTACHMENT_WINDOW_MS = 800;
|
|
|
50
53
|
const DEFAULT_MERGED_FORWARD_INSTRUCTION = "请参考以下内容回复用户";
|
|
51
54
|
const DEFAULT_REFERENCED_MESSAGE_INSTRUCTION = "请参考引用消息回复用户";
|
|
52
55
|
const TURN_STOP_REQUESTED_MESSAGE = "已发送停止信号,任务会自动停止。";
|
|
56
|
+
const TURN_RESET_STARTED_MESSAGE = "Reset 正在进行,请稍后。期间发送的消息会自动排队。";
|
|
57
|
+
const CREATE_TASK_STARTED_MESSAGE = "正在创建任务,请稍后。";
|
|
58
|
+
const CREATE_GROUP_STARTED_MESSAGE = "正在创建群聊和任务,请稍后。";
|
|
59
|
+
const FORK_TASK_STARTED_MESSAGE = "正在创建分支任务,请稍后。";
|
|
60
|
+
const FORK_GROUP_STARTED_MESSAGE = "正在创建分支群聊,请稍后。";
|
|
61
|
+
const LIST_SESSIONS_STARTED_MESSAGE = "正在读取任务列表,请稍后。";
|
|
62
|
+
const LIST_TURNS_STARTED_MESSAGE = "正在读取历史轮次,请稍后。";
|
|
63
|
+
const SWITCH_TASK_STARTED_MESSAGE = "正在切换任务,请稍后。";
|
|
64
|
+
const STATUS_STARTED_MESSAGE = "正在读取任务状态,请稍后。";
|
|
65
|
+
const GOAL_STARTED_MESSAGE = "正在处理 Goal,请稍后。";
|
|
66
|
+
const ARCHIVE_TASK_STARTED_MESSAGE = "正在归档任务,请稍后。";
|
|
67
|
+
const DISMISS_GROUP_STARTED_MESSAGE = "正在归档任务并解散群聊,请稍后。";
|
|
68
|
+
const SEND_FILE_STARTED_MESSAGE = "正在发送文件,请稍后。";
|
|
69
|
+
const CHANGE_TITLE_STARTED_MESSAGE = "正在修改任务标题,请稍后。";
|
|
70
|
+
const STOP_TASK_STARTED_MESSAGE = "正在停止任务,请稍后。";
|
|
71
|
+
const CLOSE_WRITER_STARTED_MESSAGE = "正在检查并关闭占用进程,请稍后。";
|
|
53
72
|
const REMOTE_SESSION_REFERENCE_PREFIX = "agent-runtime:";
|
|
54
73
|
const WINDOWS_DRIVES_DIRECTORY = "agentbot://windows-drives";
|
|
55
74
|
const IMAGE_FILE_EXTENSIONS = new Set([
|
|
@@ -90,7 +109,7 @@ const HELP_COMMAND_SECTIONS = [
|
|
|
90
109
|
usage: "[序号或任务 ID]",
|
|
91
110
|
description: "从当前或指定任务最近完成轮次创建分支",
|
|
92
111
|
},
|
|
93
|
-
{ command: "/turns", description: "浏览历史轮次,并可 Reset
|
|
112
|
+
{ command: "/turns", usage: "[Turn ID 或序号]", description: "浏览历史轮次,并可 Reset 对话上下文;带参数查看运行详情" },
|
|
94
113
|
{
|
|
95
114
|
command: "/title",
|
|
96
115
|
usage: "<新标题>",
|
|
@@ -205,6 +224,8 @@ export class ProxySessionController {
|
|
|
205
224
|
sessionResets = new Map();
|
|
206
225
|
resetHistoryOperations = new Map();
|
|
207
226
|
remoteTurnHistoryRefreshes = new Map();
|
|
227
|
+
turnHistoryHydrations = new Map();
|
|
228
|
+
forkHistoryCursors = new Map();
|
|
208
229
|
lastSessionListings = new Map();
|
|
209
230
|
threadInitializations = new Map();
|
|
210
231
|
llmRetryingSessions = new Set();
|
|
@@ -282,6 +303,7 @@ export class ProxySessionController {
|
|
|
282
303
|
catch (error) {
|
|
283
304
|
this.logger.warn({ error, messageId: message.messageId, contextKey: message.contextKey }, "Failed to acknowledge the incoming Feishu message with a reaction.");
|
|
284
305
|
}
|
|
306
|
+
await this.waitForCurrentSessionReset(message.contextKey);
|
|
285
307
|
if (attachmentReservation && this.completeForwardAttachment(message, attachmentReservation)) {
|
|
286
308
|
this.store.audit(message.contextKey, `${attachmentReservation.sourceKind}_attachment_coalesced`, {
|
|
287
309
|
messageId: message.messageId,
|
|
@@ -478,10 +500,11 @@ export class ProxySessionController {
|
|
|
478
500
|
command: command.type,
|
|
479
501
|
}, "Processing Agent Bot command.");
|
|
480
502
|
}
|
|
481
|
-
// Operational and read-only commands
|
|
503
|
+
// Operational and read-only commands bypass the prompt queue, but still wait for a Reset binding change.
|
|
482
504
|
if (isQueueIndependentCommand(command)) {
|
|
483
505
|
await this.outbound.withReplyTarget(message.contextKey, replyTarget, async () => {
|
|
484
506
|
try {
|
|
507
|
+
await this.waitForCurrentSessionReset(message.contextKey);
|
|
485
508
|
if (command.type === "prompt")
|
|
486
509
|
await this.ensureThreadFork(message);
|
|
487
510
|
await this.execute(message.contextKey, command, message.messageId, replyTarget, localImagePaths, message.userId, message);
|
|
@@ -502,45 +525,48 @@ export class ProxySessionController {
|
|
|
502
525
|
return;
|
|
503
526
|
}
|
|
504
527
|
const previous = this.messageQueues.get(message.contextKey) ?? Promise.resolve();
|
|
505
|
-
const next = previous.catch(() => undefined).then(
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
if (
|
|
523
|
-
this.
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
+
const next = previous.catch(() => undefined).then(async () => {
|
|
529
|
+
await this.waitForCurrentSessionReset(message.contextKey);
|
|
530
|
+
return this.outbound.withReplyTarget(message.contextKey, replyTarget, async () => {
|
|
531
|
+
try {
|
|
532
|
+
let executableCommand = command;
|
|
533
|
+
let executableImagePaths = localImagePaths;
|
|
534
|
+
let executableMessage = message;
|
|
535
|
+
let injectedRootMessageId;
|
|
536
|
+
if (command.type === "prompt") {
|
|
537
|
+
await this.ensureThreadFork(message);
|
|
538
|
+
const prepared = await this.prepareTopicRootPrompt(message, command.text, localImagePaths);
|
|
539
|
+
executableCommand = { ...command, text: prepared.text };
|
|
540
|
+
executableImagePaths = prepared.localImagePaths;
|
|
541
|
+
executableMessage = { ...message, displayText: prepared.displayPrompt };
|
|
542
|
+
injectedRootMessageId = prepared.injectedRootMessageId;
|
|
543
|
+
}
|
|
544
|
+
await this.execute(message.contextKey, executableCommand, message.messageId, replyTarget, executableImagePaths, message.userId, executableMessage);
|
|
545
|
+
if (injectedRootMessageId) {
|
|
546
|
+
const current = this.currentSession(message.contextKey);
|
|
547
|
+
if (current) {
|
|
548
|
+
this.store.audit(message.contextKey, "topic_root_injected", {
|
|
549
|
+
localSessionId: current.localSessionId,
|
|
550
|
+
rootMessageId: injectedRootMessageId,
|
|
551
|
+
promptMessageId: message.messageId,
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
if (!commandDefersReactionFinalization(command)) {
|
|
556
|
+
await this.finalizeStandaloneMessageReaction(message.messageId, "completed");
|
|
528
557
|
}
|
|
529
558
|
}
|
|
530
|
-
|
|
531
|
-
await this.finalizeStandaloneMessageReaction(message.messageId, "
|
|
559
|
+
catch (error) {
|
|
560
|
+
await this.finalizeStandaloneMessageReaction(message.messageId, "failed");
|
|
561
|
+
await this.sendError(message.contextKey, error, {
|
|
562
|
+
requestId: message.messageId,
|
|
563
|
+
messageId: message.messageId,
|
|
564
|
+
command: command.type,
|
|
565
|
+
phase: "command_execute",
|
|
566
|
+
});
|
|
532
567
|
}
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
await this.finalizeStandaloneMessageReaction(message.messageId, "failed");
|
|
536
|
-
await this.sendError(message.contextKey, error, {
|
|
537
|
-
requestId: message.messageId,
|
|
538
|
-
messageId: message.messageId,
|
|
539
|
-
command: command.type,
|
|
540
|
-
phase: "command_execute",
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
}));
|
|
568
|
+
});
|
|
569
|
+
});
|
|
544
570
|
this.messageQueues.set(message.contextKey, next);
|
|
545
571
|
await next;
|
|
546
572
|
if (this.messageQueues.get(message.contextKey) === next)
|
|
@@ -653,6 +679,7 @@ export class ProxySessionController {
|
|
|
653
679
|
else if (kind === "directory_send_file") {
|
|
654
680
|
const filePath = directoryFileActionPath(scopedAction.value.filePath);
|
|
655
681
|
await this.assertSendableFile(filePath);
|
|
682
|
+
await this.outbound.sendText(contextKey, SEND_FILE_STARTED_MESSAGE);
|
|
656
683
|
await this.outbound.sendFile(contextKey, filePath);
|
|
657
684
|
}
|
|
658
685
|
else if (kind === "directory_new_folder_prompt") {
|
|
@@ -704,11 +731,13 @@ export class ProxySessionController {
|
|
|
704
731
|
}
|
|
705
732
|
else if (kind === "session_new") {
|
|
706
733
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
734
|
+
await this.outbound.sendText(contextKey, CREATE_TASK_STARTED_MESSAGE);
|
|
707
735
|
await this.createProjectSessionFromReference(contextKey, sessionId);
|
|
708
736
|
await this.refreshSessionsCardFromAction(scopedAction, { page: 0 });
|
|
709
737
|
}
|
|
710
738
|
else if (kind === "session_new_group") {
|
|
711
739
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
740
|
+
await this.outbound.sendText(contextKey, CREATE_GROUP_STARTED_MESSAGE);
|
|
712
741
|
await this.createFeishuGroupFromReference(contextKey, sessionId, scopedAction.userId);
|
|
713
742
|
await this.refreshSessionsCardFromAction(scopedAction);
|
|
714
743
|
}
|
|
@@ -726,10 +755,12 @@ export class ProxySessionController {
|
|
|
726
755
|
}
|
|
727
756
|
else if (kind === "group_dismiss_confirm") {
|
|
728
757
|
this.assertDismissGroupRequester(scopedAction);
|
|
758
|
+
await this.outbound.sendText(contextKey, DISMISS_GROUP_STARTED_MESSAGE);
|
|
729
759
|
await this.dismissGroupAndArchiveTask(contextKey, String(scopedAction.value.sessionId ?? ""), "command");
|
|
730
760
|
}
|
|
731
761
|
else if (kind === "session_stop") {
|
|
732
762
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
763
|
+
await this.outbound.sendText(contextKey, STOP_TASK_STARTED_MESSAGE);
|
|
733
764
|
await this.stopSessionReference(contextKey, sessionId);
|
|
734
765
|
if (scopedAction.value.cardView === "status")
|
|
735
766
|
await this.refreshStatusCardFromAction(scopedAction, sessionId);
|
|
@@ -737,6 +768,7 @@ export class ProxySessionController {
|
|
|
737
768
|
await this.refreshSessionsCardFromAction(scopedAction, { forceSwitchTaskId: sessionId });
|
|
738
769
|
}
|
|
739
770
|
else if (kind === "session_status") {
|
|
771
|
+
await this.outbound.sendText(contextKey, STATUS_STARTED_MESSAGE);
|
|
740
772
|
await this.status(contextKey, String(scopedAction.value.sessionId ?? ""));
|
|
741
773
|
}
|
|
742
774
|
else if (kind === "session_status_refresh") {
|
|
@@ -867,6 +899,8 @@ export class ProxySessionController {
|
|
|
867
899
|
this.sessionResets.clear();
|
|
868
900
|
this.resetHistoryOperations.clear();
|
|
869
901
|
this.remoteTurnHistoryRefreshes.clear();
|
|
902
|
+
this.forkHistoryCursors.clear();
|
|
903
|
+
this.turnHistoryHydrations.clear();
|
|
870
904
|
for (const pending of this.pendingMergedForwards.values())
|
|
871
905
|
pending.resolveAttachment(undefined);
|
|
872
906
|
this.pendingMergedForwards.clear();
|
|
@@ -930,9 +964,11 @@ export class ProxySessionController {
|
|
|
930
964
|
const runtime = this.runtimes.forAgent(record.agentName);
|
|
931
965
|
if (runtime.readRemoteSession) {
|
|
932
966
|
try {
|
|
933
|
-
remote = await runtime.readRemoteSession(record.remoteSessionId);
|
|
967
|
+
remote = await runtime.readRemoteSession(record.remoteSessionId, "latest-full");
|
|
934
968
|
}
|
|
935
969
|
catch (error) {
|
|
970
|
+
if (error instanceof CodexVersionError)
|
|
971
|
+
throw error;
|
|
936
972
|
this.logger.warn({ error, sessionId: record.localSessionId }, "Failed to inspect App Server task status for CLI.");
|
|
937
973
|
}
|
|
938
974
|
}
|
|
@@ -996,7 +1032,7 @@ export class ProxySessionController {
|
|
|
996
1032
|
?? { ...record, contextKey: responseContextKey };
|
|
997
1033
|
const previous = this.messageQueues.get(responseContextKey) ?? Promise.resolve();
|
|
998
1034
|
const next = previous.catch(() => undefined).then(async () => {
|
|
999
|
-
this.outbound.registerSession(scopedRecord.localSessionId, responseContextKey, scopedRecord.title, scopedRecord.cwd, this.agentLabel(scopedRecord.agentName));
|
|
1035
|
+
this.outbound.registerSession(scopedRecord.localSessionId, responseContextKey, scopedRecord.title, scopedRecord.cwd, this.agentLabel(scopedRecord.agentName), scopedRecord.model);
|
|
1000
1036
|
const promptMessageId = await this.outbound.withReplyTarget(responseContextKey, existingReplyTarget, () => this.outbound.sendText(responseContextKey, promptText));
|
|
1001
1037
|
const turnReplyTarget = isThreadContextKey(responseContextKey)
|
|
1002
1038
|
? promptMessageId
|
|
@@ -1155,44 +1191,36 @@ export class ProxySessionController {
|
|
|
1155
1191
|
}
|
|
1156
1192
|
async controlTaskSettings(localSessionId, setting, value) {
|
|
1157
1193
|
const record = this.requireControlSession(localSessionId);
|
|
1158
|
-
const loaded = await this.loadSession(record);
|
|
1159
1194
|
if (setting && !value?.trim())
|
|
1160
1195
|
throw new Error(`task ${setting} requires a value.`);
|
|
1161
1196
|
const nextValue = value?.trim();
|
|
1197
|
+
if (setting === "provider" && nextValue) {
|
|
1198
|
+
const contextKey = this.controlSessionContextKey(record);
|
|
1199
|
+
await this.outbound.withReplyTarget(contextKey, this.controlSessionReplyTarget(record), () => this.outbound.sendText(contextKey, `正在切换到 Provider ${nextValue},请稍后。`));
|
|
1200
|
+
}
|
|
1201
|
+
const loaded = await this.loadSession(record);
|
|
1162
1202
|
if (setting === "provider" && nextValue) {
|
|
1163
1203
|
await this.assertModelProvider(loaded, nextValue);
|
|
1164
|
-
const models = await loaded.runtime.listModels();
|
|
1165
|
-
const model = models
|
|
1166
|
-
?? models.find((candidate) => candidate.isDefault);
|
|
1204
|
+
const models = await loaded.runtime.listModels(nextValue, loaded.session.model);
|
|
1205
|
+
const model = selectProviderModel(models, loaded.session.model);
|
|
1167
1206
|
if (!model)
|
|
1168
1207
|
throw new Error("The current runtime has no model available for a Provider change.");
|
|
1169
|
-
const effort = model
|
|
1170
|
-
|
|
1171
|
-
: model.defaultReasoningEffort ?? model.supportedReasoningEfforts[0]?.value;
|
|
1172
|
-
if (!effort)
|
|
1173
|
-
throw new Error(`Model ${model.id} has no reasoning effort available for a Provider change.`);
|
|
1174
|
-
if (!loaded.runtime.setExecutionSettings)
|
|
1175
|
-
throw new Error("The current runtime does not support Provider settings.");
|
|
1176
|
-
const updated = await loaded.runtime.setExecutionSettings(record.localSessionId, {
|
|
1208
|
+
const effort = selectProviderReasoningEffort(model, loaded.session.reasoningEffort);
|
|
1209
|
+
await this.changeProviderSettings(loaded, {
|
|
1177
1210
|
modelProvider: nextValue,
|
|
1178
1211
|
model: model.id,
|
|
1179
1212
|
reasoningEffort: effort,
|
|
1180
1213
|
permissionMode: loaded.session.permissionMode,
|
|
1181
1214
|
});
|
|
1182
|
-
this.store.updateRuntimeSession(record.localSessionId, {
|
|
1183
|
-
modelProvider: updated.modelProvider ?? nextValue,
|
|
1184
|
-
model: updated.model ?? model.id,
|
|
1185
|
-
reasoningEffort: updated.reasoningEffort ?? effort,
|
|
1186
|
-
permissionMode: updated.permissionMode,
|
|
1187
|
-
});
|
|
1188
1215
|
}
|
|
1189
1216
|
else if (setting === "model" && nextValue) {
|
|
1190
|
-
const models = await loaded.runtime.listModels();
|
|
1217
|
+
const models = await loaded.runtime.listModels(loaded.session.modelProvider, loaded.session.model);
|
|
1191
1218
|
const selected = models.find((candidate) => candidate.id === nextValue);
|
|
1192
1219
|
if (!selected)
|
|
1193
1220
|
throw new Error(`Unknown model: ${nextValue}`);
|
|
1194
1221
|
const currentEffort = loaded.session.reasoningEffort;
|
|
1195
|
-
const nextEffort = currentEffort && selected.supportedReasoningEfforts.
|
|
1222
|
+
const nextEffort = currentEffort && (selected.supportedReasoningEfforts.length === 0
|
|
1223
|
+
|| selected.supportedReasoningEfforts.some((candidate) => candidate.value === currentEffort))
|
|
1196
1224
|
? currentEffort
|
|
1197
1225
|
: selected.defaultReasoningEffort;
|
|
1198
1226
|
await loaded.runtime.setModel(record.localSessionId, nextValue);
|
|
@@ -1202,7 +1230,7 @@ export class ProxySessionController {
|
|
|
1202
1230
|
this.store.updateRuntimeSession(record.localSessionId, { model: nextValue, reasoningEffort: nextEffort });
|
|
1203
1231
|
}
|
|
1204
1232
|
else if (setting === "thinking" && nextValue) {
|
|
1205
|
-
const models = await loaded.runtime.listModels();
|
|
1233
|
+
const models = await loaded.runtime.listModels(loaded.session.modelProvider, loaded.session.model);
|
|
1206
1234
|
const currentModel = models.find((candidate) => candidate.id === loaded.session.model)
|
|
1207
1235
|
?? models.find((candidate) => candidate.isDefault);
|
|
1208
1236
|
if (!currentModel)
|
|
@@ -1222,13 +1250,13 @@ export class ProxySessionController {
|
|
|
1222
1250
|
this.store.updateRuntimeSession(record.localSessionId, { permissionMode: nextValue });
|
|
1223
1251
|
}
|
|
1224
1252
|
const session = this.store.getSession(record.localSessionId) ?? record;
|
|
1225
|
-
if (setting)
|
|
1253
|
+
if (setting && setting !== "provider")
|
|
1226
1254
|
await this.persistAgentExecutionDefaults(session.localSessionId);
|
|
1227
|
-
const models = await loaded.runtime.listModels();
|
|
1255
|
+
const models = await loaded.runtime.listModels(session.modelProvider, session.model);
|
|
1228
1256
|
const currentModel = models.find((candidate) => candidate.id === session.model)
|
|
1229
1257
|
?? models.find((candidate) => candidate.isDefault);
|
|
1230
1258
|
const providers = loaded.runtime.listModelProviders
|
|
1231
|
-
? await
|
|
1259
|
+
? await this.modelProviderOptions(loaded)
|
|
1232
1260
|
: [];
|
|
1233
1261
|
return {
|
|
1234
1262
|
session,
|
|
@@ -1271,17 +1299,18 @@ export class ProxySessionController {
|
|
|
1271
1299
|
}),
|
|
1272
1300
|
};
|
|
1273
1301
|
}
|
|
1274
|
-
controlListTaskTurns(localSessionId) {
|
|
1302
|
+
async controlListTaskTurns(localSessionId) {
|
|
1275
1303
|
const session = this.requireControlSession(localSessionId);
|
|
1276
1304
|
if (!session.remoteSessionId || !this.isCodexSession(session)) {
|
|
1277
1305
|
throw new Error("The task is not an App Server task that can be reset.");
|
|
1278
1306
|
}
|
|
1279
|
-
|
|
1307
|
+
await this.hydrateTaskTurnHistory(session);
|
|
1308
|
+
const rows = this.store.listTaskTurnGraphIndex(session.localSessionId);
|
|
1280
1309
|
const graph = buildTurnGraphRows(rows.map((turn) => ({ turnId: turn.turnId, parentTurnId: turn.parentTurnId })));
|
|
1281
1310
|
return {
|
|
1282
1311
|
session,
|
|
1283
1312
|
turns: rows.flatMap((turn, index) => {
|
|
1284
|
-
const snapshot =
|
|
1313
|
+
const snapshot = this.store.getTurnPromptSummary(turn.turnId);
|
|
1285
1314
|
if (!snapshot)
|
|
1286
1315
|
return [];
|
|
1287
1316
|
return [{
|
|
@@ -1299,16 +1328,10 @@ export class ProxySessionController {
|
|
|
1299
1328
|
async controlResetTaskToTurn(localSessionId, turnId) {
|
|
1300
1329
|
const record = this.requireControlSession(localSessionId);
|
|
1301
1330
|
const contextKey = this.controlSessionContextKey(record);
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
await reset;
|
|
1307
|
-
}
|
|
1308
|
-
finally {
|
|
1309
|
-
if (this.sessionResets.get(record.localSessionId) === reset)
|
|
1310
|
-
this.sessionResets.delete(record.localSessionId);
|
|
1311
|
-
}
|
|
1331
|
+
await this.runSessionReset(record.localSessionId, async () => {
|
|
1332
|
+
await this.outbound.sendText(contextKey, TURN_RESET_STARTED_MESSAGE);
|
|
1333
|
+
await this.performSessionReset(contextKey, record, turnId, false);
|
|
1334
|
+
});
|
|
1312
1335
|
return this.store.getSession(record.localSessionId) ?? record;
|
|
1313
1336
|
}
|
|
1314
1337
|
controlTaskMute(localSessionId, enabled) {
|
|
@@ -1438,6 +1461,7 @@ export class ProxySessionController {
|
|
|
1438
1461
|
if (command.projectless && this.ensureAgent(context.defaultAgent).kind !== "app-server") {
|
|
1439
1462
|
throw new Error("/new --nodir 仅支持 App Server Agent。");
|
|
1440
1463
|
}
|
|
1464
|
+
await this.outbound.sendText(contextKey, CREATE_TASK_STARTED_MESSAGE);
|
|
1441
1465
|
await this.createSession(contextKey, context.defaultAgent, command.projectless
|
|
1442
1466
|
? undefined
|
|
1443
1467
|
: command.cwd === undefined
|
|
@@ -1448,6 +1472,10 @@ export class ProxySessionController {
|
|
|
1448
1472
|
if (command.projectless && this.ensureAgent(context.defaultAgent).kind !== "app-server") {
|
|
1449
1473
|
throw new Error("/newgroup --nodir 仅支持 App Server Agent。");
|
|
1450
1474
|
}
|
|
1475
|
+
if (!userId?.startsWith("ou_")) {
|
|
1476
|
+
throw new Error("/newgroup 只能由具有 open_id 的飞书用户消息触发。");
|
|
1477
|
+
}
|
|
1478
|
+
await this.outbound.sendText(contextKey, CREATE_GROUP_STARTED_MESSAGE);
|
|
1451
1479
|
await this.createFeishuGroup(contextKey, context.defaultAgent, command.title, userId, command.cwd === undefined ? undefined : resolveUserPath(command.cwd), command.projectless === true);
|
|
1452
1480
|
return;
|
|
1453
1481
|
case "forkgroup":
|
|
@@ -1466,6 +1494,7 @@ export class ProxySessionController {
|
|
|
1466
1494
|
await this.enqueueNoSteerPrompt(contextKey, command.text, messageId, replyTarget);
|
|
1467
1495
|
return;
|
|
1468
1496
|
case "sessions":
|
|
1497
|
+
await this.outbound.sendText(contextKey, LIST_SESSIONS_STARTED_MESSAGE);
|
|
1469
1498
|
await this.listSessions(contextKey, command.searchTerm);
|
|
1470
1499
|
return;
|
|
1471
1500
|
case "switch":
|
|
@@ -1491,9 +1520,11 @@ export class ProxySessionController {
|
|
|
1491
1520
|
await this.openDismissGroupCard(contextKey, userId);
|
|
1492
1521
|
return;
|
|
1493
1522
|
case "status":
|
|
1523
|
+
await this.outbound.sendText(contextKey, STATUS_STARTED_MESSAGE);
|
|
1494
1524
|
await this.status(contextKey, command.sessionId);
|
|
1495
1525
|
return;
|
|
1496
1526
|
case "goal":
|
|
1527
|
+
await this.outbound.sendText(contextKey, GOAL_STARTED_MESSAGE);
|
|
1497
1528
|
await this.goal(contextKey, command);
|
|
1498
1529
|
return;
|
|
1499
1530
|
case "restart":
|
|
@@ -1508,7 +1539,14 @@ export class ProxySessionController {
|
|
|
1508
1539
|
await this.setGroupMute(contextKey, command.enabled);
|
|
1509
1540
|
return;
|
|
1510
1541
|
case "turns":
|
|
1511
|
-
|
|
1542
|
+
if (command.turnReference) {
|
|
1543
|
+
await this.outbound.sendText(contextKey, "正在读取轮次运行信息,请稍后。");
|
|
1544
|
+
await this.openTurnDetails(contextKey, command.turnReference);
|
|
1545
|
+
}
|
|
1546
|
+
else {
|
|
1547
|
+
await this.outbound.sendText(contextKey, LIST_TURNS_STARTED_MESSAGE);
|
|
1548
|
+
await this.openResetHistory(contextKey);
|
|
1549
|
+
}
|
|
1512
1550
|
return;
|
|
1513
1551
|
case "model":
|
|
1514
1552
|
await this.openExecutionSettings(contextKey, "model");
|
|
@@ -1542,7 +1580,7 @@ export class ProxySessionController {
|
|
|
1542
1580
|
: context.contextKey;
|
|
1543
1581
|
if (!this.outbound.canRoute(turnContextKey))
|
|
1544
1582
|
continue;
|
|
1545
|
-
this.outbound.registerSession(session.localSessionId, turnContextKey, session.title, session.cwd, this.agentLabel(session.agentName));
|
|
1583
|
+
this.outbound.registerSession(session.localSessionId, turnContextKey, session.title, session.cwd, this.agentLabel(session.agentName), session.model);
|
|
1546
1584
|
if (!session.lastTurnId)
|
|
1547
1585
|
continue;
|
|
1548
1586
|
void this.outbound.resumeDelivery(session.localSessionId, turnContextKey, session.lastTurnId).catch((error) => {
|
|
@@ -1616,7 +1654,7 @@ export class ProxySessionController {
|
|
|
1616
1654
|
continue;
|
|
1617
1655
|
}
|
|
1618
1656
|
if (session) {
|
|
1619
|
-
this.outbound.registerSession(session.localSessionId, attempt.contextKey, session.title, session.cwd, this.agentLabel(session.agentName));
|
|
1657
|
+
this.outbound.registerSession(session.localSessionId, attempt.contextKey, session.title, session.cwd, this.agentLabel(session.agentName), session.model);
|
|
1620
1658
|
}
|
|
1621
1659
|
try {
|
|
1622
1660
|
await this.handleRuntimeEvent({
|
|
@@ -1728,7 +1766,7 @@ export class ProxySessionController {
|
|
|
1728
1766
|
const replyTarget = attempt.replyMessageId
|
|
1729
1767
|
? { messageId: attempt.replyMessageId, replyInThread: true }
|
|
1730
1768
|
: undefined;
|
|
1731
|
-
this.outbound.registerSession(session.localSessionId, contextKey, session.title, session.cwd, this.agentLabel(session.agentName));
|
|
1769
|
+
this.outbound.registerSession(session.localSessionId, contextKey, session.title, session.cwd, this.agentLabel(session.agentName), session.model);
|
|
1732
1770
|
if (announce) {
|
|
1733
1771
|
await this.outbound.withReplyTarget(contextKey, replyTarget, () => this.outbound.sendText(contextKey, "检测到任务在 Agent Bot 重启前尚未完成,正在自动恢复。")).catch((error) => {
|
|
1734
1772
|
this.logger.warn({ error, attemptId: attempt.attemptId, contextKey }, "Failed to send the task recovery notification.");
|
|
@@ -1747,6 +1785,8 @@ export class ProxySessionController {
|
|
|
1747
1785
|
remote = await runtime.readRemoteSession(session.remoteSessionId);
|
|
1748
1786
|
}
|
|
1749
1787
|
catch (error) {
|
|
1788
|
+
if (error instanceof CodexVersionError)
|
|
1789
|
+
throw error;
|
|
1750
1790
|
this.logger.warn({ error, attemptId: attempt.attemptId, remoteSessionId: session.remoteSessionId }, "Failed to read the remote task during startup recovery; treating the old local execution as interrupted.");
|
|
1751
1791
|
}
|
|
1752
1792
|
}
|
|
@@ -1928,7 +1968,7 @@ export class ProxySessionController {
|
|
|
1928
1968
|
}
|
|
1929
1969
|
const configuredRuntime = this.runtimes.forAgent(record.agentName);
|
|
1930
1970
|
if (!configuredRuntime.getSession(record.localSessionId)) {
|
|
1931
|
-
this.outbound.registerSession(record.localSessionId, contextKey, record.title, record.cwd, this.agentLabel(record.agentName));
|
|
1971
|
+
this.outbound.registerSession(record.localSessionId, contextKey, record.title, record.cwd, this.agentLabel(record.agentName), record.model);
|
|
1932
1972
|
await this.outbound.startPendingTurn(record.localSessionId, contextKey, record.title, replyTarget, displayPrompt ?? text);
|
|
1933
1973
|
}
|
|
1934
1974
|
let loaded;
|
|
@@ -2221,7 +2261,7 @@ export class ProxySessionController {
|
|
|
2221
2261
|
permissionMode: source.permissionMode ?? "auto",
|
|
2222
2262
|
});
|
|
2223
2263
|
this.store.setCurrentSession(message.contextKey, localSessionId);
|
|
2224
|
-
this.outbound.registerSession(localSessionId, message.contextKey, forkTitle, source.cwd, this.agentLabel(source.agentName));
|
|
2264
|
+
this.outbound.registerSession(localSessionId, message.contextKey, forkTitle, source.cwd, this.agentLabel(source.agentName), source.model);
|
|
2225
2265
|
try {
|
|
2226
2266
|
const forked = await runtime.forkSession({
|
|
2227
2267
|
localSessionId,
|
|
@@ -2284,6 +2324,7 @@ export class ProxySessionController {
|
|
|
2284
2324
|
return { anchor, source, snapshot };
|
|
2285
2325
|
}
|
|
2286
2326
|
async forkSessionReference(contextKey, reference) {
|
|
2327
|
+
await this.outbound.sendText(contextKey, FORK_TASK_STARTED_MESSAGE);
|
|
2287
2328
|
const plan = await this.prepareForkSession(contextKey, reference);
|
|
2288
2329
|
const forked = await this.forkSessionIntoContext(contextKey, plan);
|
|
2289
2330
|
const forkSourceLabel = plan.forkedFromHistoricalTurn
|
|
@@ -2335,32 +2376,22 @@ export class ProxySessionController {
|
|
|
2335
2376
|
if (!runtime.readRemoteSession && !source) {
|
|
2336
2377
|
throw new Error("当前 App Server Agent 不支持读取指定任务。");
|
|
2337
2378
|
}
|
|
2338
|
-
const
|
|
2379
|
+
const persistedCompletedTurnId = source
|
|
2380
|
+
? this.store.findLatestCompletedTurnId(source.localSessionId)
|
|
2381
|
+
: undefined;
|
|
2339
2382
|
const sourceLastSnapshot = turnViewSnapshot(source?.lastTurnId ? this.store.getTurnSnapshot(source.lastTurnId) : undefined);
|
|
2340
|
-
const
|
|
2341
|
-
|
|
2342
|
-
: source?.lastTurnId
|
|
2383
|
+
const localCompletedTurnId = persistedCompletedTurnId
|
|
2384
|
+
?? (source?.lastTurnId
|
|
2343
2385
|
&& (source.lastTurnStatus === "completed" || sourceLastSnapshot?.status === "completed")
|
|
2344
|
-
?
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
}]
|
|
2352
|
-
: [];
|
|
2353
|
-
if (!remote && runtime.readRemoteSession && (!source || persistedCompletedTurns.length === 0)) {
|
|
2354
|
-
try {
|
|
2355
|
-
remote = await runtime.readRemoteSession(remoteSessionId);
|
|
2356
|
-
}
|
|
2357
|
-
catch (error) {
|
|
2358
|
-
if (localCompletedTurns.length === 0)
|
|
2359
|
-
throw error;
|
|
2360
|
-
this.logger.warn({ error, contextKey, remoteSessionId, sourceTurnId: localCompletedTurns.at(-1)?.id }, "Failed to read the complete source task before Fork; using the locally persisted completed Turn.");
|
|
2361
|
-
}
|
|
2386
|
+
? source.lastTurnId
|
|
2387
|
+
: undefined)
|
|
2388
|
+
?? (source ? this.store.getForkHistorySource(source.localSessionId)?.sourceTurnId : undefined);
|
|
2389
|
+
if (!remote && !localCompletedTurnId) {
|
|
2390
|
+
remote = runtime.readRemoteForkSource
|
|
2391
|
+
? await runtime.readRemoteForkSource(remoteSessionId)
|
|
2392
|
+
: await runtime.readRemoteSession?.(remoteSessionId);
|
|
2362
2393
|
}
|
|
2363
|
-
const latestTurnId = remote?.lastTurnId ?? source?.lastTurnId ??
|
|
2394
|
+
const latestTurnId = remote?.lastTurnId ?? source?.lastTurnId ?? localCompletedTurnId;
|
|
2364
2395
|
const latestSnapshot = turnViewSnapshot(latestTurnId ? this.store.getTurnSnapshot(latestTurnId) : undefined);
|
|
2365
2396
|
const isRunning = remote
|
|
2366
2397
|
? isRemoteSessionActive(remote)
|
|
@@ -2369,7 +2400,7 @@ export class ProxySessionController {
|
|
|
2369
2400
|
|| isTurnStillRunning(latestSnapshot?.status));
|
|
2370
2401
|
const lastTurnId = remote?.lastCompletedTurnId
|
|
2371
2402
|
?? (remote?.lastTurnStatus === "completed" ? remote.lastTurnId : undefined)
|
|
2372
|
-
??
|
|
2403
|
+
?? localCompletedTurnId;
|
|
2373
2404
|
if (!lastTurnId) {
|
|
2374
2405
|
if (isRunning) {
|
|
2375
2406
|
throw new Error(`${sourceLabel}正在执行,且还没有已完成轮次可供 fork。请等待当前轮次完成后重试。`);
|
|
@@ -2378,8 +2409,7 @@ export class ProxySessionController {
|
|
|
2378
2409
|
}
|
|
2379
2410
|
const snapshot = turnViewSnapshot(this.store.getTurnSnapshot(lastTurnId));
|
|
2380
2411
|
const forkedFromHistoricalTurn = lastTurnId !== latestTurnId;
|
|
2381
|
-
const completedTurn =
|
|
2382
|
-
.find((turn) => turn.id === lastTurnId);
|
|
2412
|
+
const completedTurn = remote?.completedTurns?.find((turn) => turn.id === lastTurnId);
|
|
2383
2413
|
const cwd = remote?.cwd || source?.cwd;
|
|
2384
2414
|
if (!cwd)
|
|
2385
2415
|
throw new Error("指定的 App Server 任务没有可用的工作目录,暂时不能 fork。");
|
|
@@ -2421,27 +2451,10 @@ export class ProxySessionController {
|
|
|
2421
2451
|
sourceTurnPrompt,
|
|
2422
2452
|
sourceTurnStartedAt: snapshot?.startedAt ?? sourceTurnCompletedAt,
|
|
2423
2453
|
sourceTurnCompletedAt,
|
|
2424
|
-
sourceCompletedTurns: remote?.completedTurns
|
|
2425
|
-
?? (persistedCompletedTurns.length > 0 ? [] : localCompletedTurns),
|
|
2426
2454
|
sourceWasRunning: isRunning,
|
|
2427
2455
|
forkedFromHistoricalTurn,
|
|
2428
2456
|
};
|
|
2429
2457
|
}
|
|
2430
|
-
localForkCompletedTurns(source) {
|
|
2431
|
-
return this.store.listTaskTurnGraph(source.localSessionId)
|
|
2432
|
-
.slice()
|
|
2433
|
-
.reverse()
|
|
2434
|
-
.map((record) => {
|
|
2435
|
-
const snapshot = turnViewSnapshot(record.snapshot);
|
|
2436
|
-
const startedAt = snapshot?.startedAt ?? parseIsoTimestamp(record.updatedAt);
|
|
2437
|
-
return {
|
|
2438
|
-
id: record.turnId,
|
|
2439
|
-
prompt: latestUserPromptFromTurnView(snapshot),
|
|
2440
|
-
startedAt,
|
|
2441
|
-
completedAt: snapshot?.completedAt ?? startedAt,
|
|
2442
|
-
};
|
|
2443
|
-
});
|
|
2444
|
-
}
|
|
2445
2458
|
async prepareForkGroupSession(contextKey, requestedTitle, incomingMessage) {
|
|
2446
2459
|
if (!incomingMessage?.threadContext) {
|
|
2447
2460
|
const plan = await this.prepareForkSession(contextKey, undefined, requestedTitle);
|
|
@@ -2511,7 +2524,6 @@ export class ProxySessionController {
|
|
|
2511
2524
|
};
|
|
2512
2525
|
}
|
|
2513
2526
|
async forkSessionIntoContext(contextKey, plan) {
|
|
2514
|
-
const sourceCompletedTurns = await this.resolveForkSourceCompletedTurns(plan);
|
|
2515
2527
|
const localSessionId = createId("sess");
|
|
2516
2528
|
const record = this.store.createSession({
|
|
2517
2529
|
localSessionId,
|
|
@@ -2546,8 +2558,7 @@ export class ProxySessionController {
|
|
|
2546
2558
|
lastTurnId: plan.lastTurnId,
|
|
2547
2559
|
lastTurnStatus: plan.lastTurnStatus,
|
|
2548
2560
|
});
|
|
2549
|
-
this.outbound.registerSession(localSessionId, contextKey, forked.title ?? plan.forkTitle, plan.cwd, this.agentLabel(plan.agentName));
|
|
2550
|
-
const sourceTurnHistoryCount = this.persistForkSourceHistory(contextKey, localSessionId, plan, sourceCompletedTurns);
|
|
2561
|
+
this.outbound.registerSession(localSessionId, contextKey, forked.title ?? plan.forkTitle, plan.cwd, this.agentLabel(plan.agentName), plan.model);
|
|
2551
2562
|
this.store.audit(contextKey, "session_forked", {
|
|
2552
2563
|
sourceLocalSessionId: plan.source?.localSessionId,
|
|
2553
2564
|
sourceRemoteSessionId: plan.remoteSessionId,
|
|
@@ -2555,7 +2566,6 @@ export class ProxySessionController {
|
|
|
2555
2566
|
sourceTurnPrompt: plan.sourceTurnPrompt,
|
|
2556
2567
|
sourceTurnStartedAt: plan.sourceTurnStartedAt,
|
|
2557
2568
|
sourceTurnCompletedAt: plan.sourceTurnCompletedAt,
|
|
2558
|
-
sourceTurnHistoryCount,
|
|
2559
2569
|
sourceWasRunning: plan.sourceWasRunning,
|
|
2560
2570
|
forkedLocalSessionId: localSessionId,
|
|
2561
2571
|
forkedRemoteSessionId: forked.remoteSessionId,
|
|
@@ -2571,42 +2581,6 @@ export class ProxySessionController {
|
|
|
2571
2581
|
throw error;
|
|
2572
2582
|
}
|
|
2573
2583
|
}
|
|
2574
|
-
async resolveForkSourceCompletedTurns(plan) {
|
|
2575
|
-
let completedTurns = plan.sourceCompletedTurns;
|
|
2576
|
-
if (completedTurns === undefined && plan.runtime.readRemoteSession) {
|
|
2577
|
-
try {
|
|
2578
|
-
completedTurns = (await plan.runtime.readRemoteSession(plan.remoteSessionId)).completedTurns ?? [];
|
|
2579
|
-
}
|
|
2580
|
-
catch (error) {
|
|
2581
|
-
this.logger.warn({ error, remoteSessionId: plan.remoteSessionId, sourceTurnId: plan.lastTurnId }, "Failed to read the source task Turn history before Fork; keeping the Fork anchor fallback.");
|
|
2582
|
-
}
|
|
2583
|
-
}
|
|
2584
|
-
if (!completedTurns?.length)
|
|
2585
|
-
return [];
|
|
2586
|
-
const anchorIndex = completedTurns.findIndex((turn) => turn.id === plan.lastTurnId);
|
|
2587
|
-
if (anchorIndex < 0) {
|
|
2588
|
-
this.logger.warn({ remoteSessionId: plan.remoteSessionId, sourceTurnId: plan.lastTurnId }, "The source task Turn history did not include the Fork anchor; keeping the Fork anchor fallback.");
|
|
2589
|
-
return [];
|
|
2590
|
-
}
|
|
2591
|
-
return completedTurns.slice(0, anchorIndex + 1);
|
|
2592
|
-
}
|
|
2593
|
-
persistForkSourceHistory(contextKey, forkedLocalSessionId, plan, completedTurns) {
|
|
2594
|
-
if (completedTurns.length === 0)
|
|
2595
|
-
return 0;
|
|
2596
|
-
const localSessionId = plan.source?.localSessionId ?? forkedLocalSessionId;
|
|
2597
|
-
const historyContextKey = plan.source?.contextKey ?? contextKey;
|
|
2598
|
-
const fallbackStart = plan.sourceTurnStartedAt
|
|
2599
|
-
?? plan.sourceTurnCompletedAt
|
|
2600
|
-
?? Date.now() - completedTurns.length;
|
|
2601
|
-
return this.persistCompletedTurnHistory({
|
|
2602
|
-
localSessionId,
|
|
2603
|
-
contextKey: historyContextKey,
|
|
2604
|
-
agentName: plan.agentName,
|
|
2605
|
-
remoteSessionId: plan.remoteSessionId,
|
|
2606
|
-
completedTurns,
|
|
2607
|
-
fallbackStart,
|
|
2608
|
-
});
|
|
2609
|
-
}
|
|
2610
2584
|
persistCompletedTurnHistory(input) {
|
|
2611
2585
|
try {
|
|
2612
2586
|
this.store.importCompletedTurnHistory({
|
|
@@ -2614,6 +2588,7 @@ export class ProxySessionController {
|
|
|
2614
2588
|
contextKey: input.contextKey,
|
|
2615
2589
|
agentName: input.agentName,
|
|
2616
2590
|
remoteSessionId: input.remoteSessionId,
|
|
2591
|
+
replaceParents: input.replaceParents,
|
|
2617
2592
|
turns: input.completedTurns.map((turn, index) => {
|
|
2618
2593
|
const startedAt = turn.startedAt ?? input.fallbackStart + index;
|
|
2619
2594
|
const completedAt = turn.completedAt ?? startedAt;
|
|
@@ -2644,80 +2619,202 @@ export class ProxySessionController {
|
|
|
2644
2619
|
return 0;
|
|
2645
2620
|
}
|
|
2646
2621
|
}
|
|
2647
|
-
async
|
|
2648
|
-
const
|
|
2649
|
-
if (
|
|
2650
|
-
|
|
2651
|
-
|
|
2622
|
+
async hydrateTaskTurnHistory(current, requiredCount = RESET_HISTORY_PAGE_SIZE, targetTurnId) {
|
|
2623
|
+
const existing = this.turnHistoryHydrations.get(current.localSessionId);
|
|
2624
|
+
if (existing)
|
|
2625
|
+
await existing;
|
|
2626
|
+
const hydration = (async () => {
|
|
2627
|
+
if (this.store.getForkHistorySource(current.localSessionId)) {
|
|
2628
|
+
return this.hydrateForkTurnHistory(current.contextKey, current, requiredCount, new Set(), targetTurnId);
|
|
2629
|
+
}
|
|
2630
|
+
return this.hydrateRemoteTurnHistory(current.contextKey, current, requiredCount, targetTurnId);
|
|
2631
|
+
})();
|
|
2632
|
+
this.turnHistoryHydrations.set(current.localSessionId, hydration);
|
|
2633
|
+
try {
|
|
2634
|
+
return await hydration;
|
|
2635
|
+
}
|
|
2636
|
+
finally {
|
|
2637
|
+
if (this.turnHistoryHydrations.get(current.localSessionId) === hydration) {
|
|
2638
|
+
this.turnHistoryHydrations.delete(current.localSessionId);
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
async hydrateForkTurnHistory(contextKey, current, requiredCount, visited = new Set(), targetTurnId) {
|
|
2643
|
+
if (visited.has(current.localSessionId))
|
|
2644
|
+
return false;
|
|
2645
|
+
visited.add(current.localSessionId);
|
|
2646
|
+
let source = this.store.getForkHistorySource(current.localSessionId);
|
|
2647
|
+
if (!source)
|
|
2648
|
+
return false;
|
|
2649
|
+
const completedHead = current.lastTurnStatus === "completed"
|
|
2650
|
+
? current.lastTurnId
|
|
2651
|
+
: this.store.findLatestCompletedTurnId(current.localSessionId);
|
|
2652
|
+
if (completedHead && completedHead !== source.sourceTurnId && current.remoteSessionId) {
|
|
2653
|
+
// Once the branch has its own completed work, its remote history is the
|
|
2654
|
+
// authority for missing local turns, including work continued in Desktop.
|
|
2655
|
+
source = {
|
|
2656
|
+
sourceLocalSessionId: current.localSessionId,
|
|
2657
|
+
sourceRemoteSessionId: current.remoteSessionId,
|
|
2658
|
+
sourceTurnId: completedHead,
|
|
2659
|
+
createdAt: current.createdAt,
|
|
2660
|
+
};
|
|
2661
|
+
}
|
|
2662
|
+
if (this.store.hasImportedForkTurnHistory(current.localSessionId, source.sourceRemoteSessionId, source.sourceTurnId))
|
|
2663
|
+
return false;
|
|
2664
|
+
const localTurns = this.store.listTaskTurnGraphIndex(current.localSessionId);
|
|
2665
|
+
const availableCount = localTurns.length;
|
|
2666
|
+
const key = JSON.stringify([current.agentName, source.sourceRemoteSessionId, source.sourceTurnId]);
|
|
2667
|
+
let state = this.forkHistoryCursors.get(key) ?? { anchorFound: false, complete: false };
|
|
2668
|
+
const localAnchorIndex = localTurns.findIndex((turn) => turn.turnId === source.sourceTurnId);
|
|
2669
|
+
const ownTurnCount = source.sourceLocalSessionId === current.localSessionId
|
|
2670
|
+
? Math.max(0, localAnchorIndex)
|
|
2671
|
+
: localAnchorIndex >= 0 ? localAnchorIndex : availableCount;
|
|
2672
|
+
const neededInheritedCount = Math.max(0, requiredCount - ownTurnCount);
|
|
2673
|
+
if (!targetTurnId && localAnchorIndex >= 0 && availableCount >= requiredCount
|
|
2674
|
+
&& (!state.anchorFound || (state.loadedCount ?? 0) >= neededInheritedCount))
|
|
2675
|
+
return !state.complete;
|
|
2676
|
+
const sourceSession = source.sourceLocalSessionId
|
|
2677
|
+
? this.store.getSession(source.sourceLocalSessionId)
|
|
2678
|
+
: undefined;
|
|
2679
|
+
// A branch with no new turns can reuse its ancestor's history, even before
|
|
2680
|
+
// the Agent materializes that intermediate branch.
|
|
2681
|
+
if (sourceSession && sourceSession.localSessionId !== current.localSessionId
|
|
2682
|
+
&& this.store.getForkHistorySource(sourceSession.localSessionId)?.sourceTurnId === source.sourceTurnId) {
|
|
2683
|
+
const hasMore = await this.hydrateForkTurnHistory(sourceSession.contextKey, sourceSession, this.store.listTaskTurnGraphIndex(sourceSession.localSessionId).length + requiredCount - availableCount, visited, targetTurnId);
|
|
2684
|
+
if (!hasMore && this.store.hasImportedForkTurnHistory(sourceSession.localSessionId)) {
|
|
2685
|
+
this.store.audit(contextKey, "fork_turn_history_imported", {
|
|
2686
|
+
forkedLocalSessionId: current.localSessionId,
|
|
2687
|
+
sourceRemoteSessionId: source.sourceRemoteSessionId,
|
|
2688
|
+
sourceTurnId: source.sourceTurnId,
|
|
2689
|
+
reusedLocalSessionId: sourceSession.localSessionId,
|
|
2690
|
+
});
|
|
2691
|
+
}
|
|
2692
|
+
return hasMore;
|
|
2693
|
+
}
|
|
2652
2694
|
if (!source.sourceRemoteSessionId)
|
|
2653
|
-
return;
|
|
2695
|
+
return false;
|
|
2654
2696
|
const runtime = this.runtimes.forAgent(current.agentName);
|
|
2655
|
-
if (runtime.kind !== "codex" || !runtime.
|
|
2656
|
-
return;
|
|
2697
|
+
if (runtime.kind !== "codex" || !runtime.listRemoteTurnSummaries)
|
|
2698
|
+
return false;
|
|
2699
|
+
const seenCursors = new Set();
|
|
2657
2700
|
try {
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2701
|
+
while (!state.complete && (targetTurnId
|
|
2702
|
+
? !this.store.hasTaskHistoryTurn(current.localSessionId, targetTurnId)
|
|
2703
|
+
: (state.loadedCount ?? 0) < neededInheritedCount)) {
|
|
2704
|
+
const remaining = targetTurnId ? RESET_HISTORY_PAGE_SIZE : neededInheritedCount - (state.loadedCount ?? 0);
|
|
2705
|
+
const page = await runtime.listRemoteTurnSummaries(source.sourceRemoteSessionId, {
|
|
2706
|
+
cursor: state.cursor, limit: Math.min(RESET_HISTORY_PAGE_SIZE, remaining),
|
|
2707
|
+
});
|
|
2708
|
+
if (page.nextCursor && (page.nextCursor === state.cursor || seenCursors.has(page.nextCursor))) {
|
|
2709
|
+
throw new Error("App Server repeated a Turn history cursor.");
|
|
2710
|
+
}
|
|
2711
|
+
const anchorIndex = state.anchorFound ? 0 : page.turns.findIndex((turn) => turn.id === source.sourceTurnId);
|
|
2712
|
+
const inheritedTurns = anchorIndex >= 0 ? page.turns.slice(anchorIndex) : [];
|
|
2713
|
+
const anchorFound = state.anchorFound || anchorIndex >= 0;
|
|
2714
|
+
if (!anchorFound && !page.nextCursor) {
|
|
2715
|
+
this.forkHistoryCursors.delete(key);
|
|
2716
|
+
throw new Error("The Fork source Turn was not found in the available history.");
|
|
2717
|
+
}
|
|
2718
|
+
if (inheritedTurns.length > 0) {
|
|
2719
|
+
const ownerId = sourceSession?.localSessionId ?? current.localSessionId;
|
|
2720
|
+
const imported = this.persistCompletedTurnHistory({
|
|
2721
|
+
localSessionId: ownerId,
|
|
2722
|
+
contextKey: sourceSession?.contextKey ?? contextKey,
|
|
2723
|
+
agentName: current.agentName,
|
|
2724
|
+
remoteSessionId: source.sourceRemoteSessionId,
|
|
2725
|
+
completedTurns: inheritedTurns.slice().reverse(),
|
|
2726
|
+
// Adjacent remote summaries repair gaps in locally inferred ancestry.
|
|
2727
|
+
replaceParents: true,
|
|
2728
|
+
fallbackStart: (source.sourceTurnStartedAt ?? source.sourceTurnCompletedAt
|
|
2729
|
+
?? parseIsoTimestamp(source.createdAt) ?? 0) - (state.loadedCount ?? 0) - inheritedTurns.length,
|
|
2730
|
+
});
|
|
2731
|
+
if (imported === 0)
|
|
2732
|
+
throw new Error("Failed to persist the requested Turn history page.");
|
|
2733
|
+
if (state.tailTurnId) {
|
|
2734
|
+
this.store.saveTurnParent(state.tailTurnId, ownerId, inheritedTurns[0].id, true);
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
state = {
|
|
2738
|
+
cursor: page.nextCursor,
|
|
2739
|
+
anchorFound,
|
|
2740
|
+
tailTurnId: inheritedTurns.at(-1)?.id ?? state.tailTurnId,
|
|
2741
|
+
loadedCount: (state.loadedCount ?? 0) + inheritedTurns.length,
|
|
2742
|
+
complete: !page.nextCursor,
|
|
2743
|
+
};
|
|
2744
|
+
this.forkHistoryCursors.set(key, state);
|
|
2745
|
+
if (page.nextCursor)
|
|
2746
|
+
seenCursors.add(page.nextCursor);
|
|
2747
|
+
}
|
|
2748
|
+
if (state.complete) {
|
|
2678
2749
|
this.store.audit(contextKey, "fork_turn_history_imported", {
|
|
2679
2750
|
forkedLocalSessionId: current.localSessionId,
|
|
2680
2751
|
sourceRemoteSessionId: source.sourceRemoteSessionId,
|
|
2681
2752
|
sourceTurnId: source.sourceTurnId,
|
|
2682
|
-
importedTurnCount: imported,
|
|
2683
2753
|
});
|
|
2684
2754
|
}
|
|
2755
|
+
return !state.complete;
|
|
2685
2756
|
}
|
|
2686
2757
|
catch (error) {
|
|
2687
|
-
|
|
2758
|
+
if (error instanceof CodexVersionError)
|
|
2759
|
+
throw error;
|
|
2760
|
+
this.logger.warn({ error, localSessionId: current.localSessionId, sourceRemoteSessionId: source.sourceRemoteSessionId }, "Failed to load the requested Fork Turn history page; rendering the locally available history.");
|
|
2688
2761
|
}
|
|
2762
|
+
return true;
|
|
2689
2763
|
}
|
|
2690
|
-
async hydrateRemoteTurnHistory(contextKey, current) {
|
|
2764
|
+
async hydrateRemoteTurnHistory(contextKey, current, requiredCount, targetTurnId) {
|
|
2691
2765
|
if (!current.remoteSessionId)
|
|
2692
|
-
return;
|
|
2766
|
+
return false;
|
|
2767
|
+
const key = JSON.stringify([current.agentName, current.remoteSessionId, "turn-card"]);
|
|
2693
2768
|
const refreshedAt = this.remoteTurnHistoryRefreshes.get(current.localSessionId);
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
&& this.store.hasImportedForkTurnHistory(current.localSessionId))
|
|
2699
|
-
return;
|
|
2769
|
+
let state = this.forkHistoryCursors.get(key);
|
|
2770
|
+
if (refreshedAt === undefined || Date.now() - refreshedAt >= REMOTE_TURN_HISTORY_REFRESH_INTERVAL_MS)
|
|
2771
|
+
state = undefined;
|
|
2772
|
+
state ??= { anchorFound: true, complete: false };
|
|
2700
2773
|
const runtime = this.runtimes.forAgent(current.agentName);
|
|
2701
|
-
if (runtime.kind !== "codex" || !runtime.
|
|
2702
|
-
return;
|
|
2774
|
+
if (runtime.kind !== "codex" || !runtime.listRemoteTurnSummaries)
|
|
2775
|
+
return false;
|
|
2776
|
+
const seen = new Set();
|
|
2703
2777
|
try {
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2778
|
+
while (!state.complete && (targetTurnId
|
|
2779
|
+
? !this.store.hasTaskHistoryTurn(current.localSessionId, targetTurnId)
|
|
2780
|
+
: (state.loadedCount ?? 0) < requiredCount)) {
|
|
2781
|
+
const page = await runtime.listRemoteTurnSummaries(current.remoteSessionId, {
|
|
2782
|
+
cursor: state.cursor,
|
|
2783
|
+
limit: targetTurnId ? RESET_HISTORY_PAGE_SIZE : Math.min(RESET_HISTORY_PAGE_SIZE, requiredCount - (state.loadedCount ?? 0)),
|
|
2784
|
+
});
|
|
2785
|
+
if (page.nextCursor && (page.nextCursor === state.cursor || seen.has(page.nextCursor))) {
|
|
2786
|
+
throw new Error("App Server repeated a Turn history cursor.");
|
|
2787
|
+
}
|
|
2788
|
+
if (page.turns.length) {
|
|
2789
|
+
const imported = this.persistCompletedTurnHistory({
|
|
2790
|
+
localSessionId: current.localSessionId, contextKey, agentName: current.agentName,
|
|
2791
|
+
remoteSessionId: current.remoteSessionId,
|
|
2792
|
+
completedTurns: page.turns.slice().reverse(), replaceParents: true,
|
|
2793
|
+
fallbackStart: (parseIsoTimestamp(current.createdAt) ?? 0) - (state.loadedCount ?? 0) - page.turns.length,
|
|
2794
|
+
});
|
|
2795
|
+
if (!imported)
|
|
2796
|
+
throw new Error("Failed to persist the requested Turn history page.");
|
|
2797
|
+
if (state.tailTurnId)
|
|
2798
|
+
this.store.saveTurnParent(state.tailTurnId, current.localSessionId, page.turns[0].id, true);
|
|
2799
|
+
}
|
|
2800
|
+
state = {
|
|
2801
|
+
anchorFound: true, cursor: page.nextCursor, complete: !page.nextCursor,
|
|
2802
|
+
loadedCount: (state.loadedCount ?? 0) + page.turns.length,
|
|
2803
|
+
tailTurnId: page.turns.at(-1)?.id ?? state.tailTurnId,
|
|
2804
|
+
};
|
|
2805
|
+
this.forkHistoryCursors.set(key, state);
|
|
2806
|
+
this.remoteTurnHistoryRefreshes.set(current.localSessionId, Date.now());
|
|
2807
|
+
if (page.nextCursor)
|
|
2808
|
+
seen.add(page.nextCursor);
|
|
2809
|
+
}
|
|
2810
|
+
return !state.complete;
|
|
2717
2811
|
}
|
|
2718
2812
|
catch (error) {
|
|
2813
|
+
if (error instanceof CodexVersionError)
|
|
2814
|
+
throw error;
|
|
2719
2815
|
this.logger.warn({ error, localSessionId: current.localSessionId, remoteSessionId: current.remoteSessionId }, "Failed to hydrate App Server Turn history; rendering the locally available history.");
|
|
2720
2816
|
}
|
|
2817
|
+
return true;
|
|
2721
2818
|
}
|
|
2722
2819
|
async createProjectSessionFromReference(contextKey, reference) {
|
|
2723
2820
|
const resolved = await this.resolveProjectSessionReference(contextKey, reference);
|
|
@@ -2749,7 +2846,7 @@ export class ProxySessionController {
|
|
|
2749
2846
|
runtime = this.runtimes.forAgent(agentName);
|
|
2750
2847
|
}
|
|
2751
2848
|
else {
|
|
2752
|
-
const resolved = await this.resolveRemoteCodexSession(taskId);
|
|
2849
|
+
const resolved = await this.resolveRemoteCodexSession(taskId, { view: "metadata" });
|
|
2753
2850
|
agentName = resolved.agentName;
|
|
2754
2851
|
runtime = resolved.runtime;
|
|
2755
2852
|
remote = resolved.remote;
|
|
@@ -2757,10 +2854,10 @@ export class ProxySessionController {
|
|
|
2757
2854
|
const remoteSessionId = source?.remoteSessionId ?? remote?.id ?? taskId;
|
|
2758
2855
|
if (!remote && runtime.readRemoteSession && remoteSessionId) {
|
|
2759
2856
|
try {
|
|
2760
|
-
remote = await runtime.readRemoteSession(remoteSessionId);
|
|
2857
|
+
remote = await runtime.readRemoteSession(remoteSessionId, "metadata");
|
|
2761
2858
|
}
|
|
2762
2859
|
catch (error) {
|
|
2763
|
-
if (!source)
|
|
2860
|
+
if (!source || error instanceof CodexVersionError)
|
|
2764
2861
|
throw error;
|
|
2765
2862
|
this.logger.warn({ error, contextKey, taskId: remoteSessionId }, "Failed to refresh the source task before creating a project task; using the local project path.");
|
|
2766
2863
|
}
|
|
@@ -2908,7 +3005,7 @@ export class ProxySessionController {
|
|
|
2908
3005
|
});
|
|
2909
3006
|
}
|
|
2910
3007
|
this.store.setCurrentSession(contextKey, localSessionId);
|
|
2911
|
-
this.outbound.registerSession(localSessionId, contextKey, initialTitle, sessionCwd, this.agentLabel(agentName));
|
|
3008
|
+
this.outbound.registerSession(localSessionId, contextKey, initialTitle, sessionCwd, this.agentLabel(agentName), resolvedExecutionSettings.model);
|
|
2912
3009
|
const runtime = this.runtimes.forAgent(agentName);
|
|
2913
3010
|
try {
|
|
2914
3011
|
if (prepareTurn) {
|
|
@@ -2925,6 +3022,7 @@ export class ProxySessionController {
|
|
|
2925
3022
|
permissionMode: resolvedExecutionSettings.permissionMode ?? "auto",
|
|
2926
3023
|
});
|
|
2927
3024
|
this.persistRuntimeSession(record, session, session.activeTurnId ? "running" : "ready");
|
|
3025
|
+
this.outbound.updateSessionModel(localSessionId, session.model);
|
|
2928
3026
|
const saved = this.store.getSession(localSessionId) ?? record;
|
|
2929
3027
|
if (announce) {
|
|
2930
3028
|
const task = initialTitle ? `${initialTitle}(${session.remoteSessionId})` : session.remoteSessionId;
|
|
@@ -2991,6 +3089,7 @@ export class ProxySessionController {
|
|
|
2991
3089
|
if (!userId?.startsWith("ou_")) {
|
|
2992
3090
|
throw new Error("/forkgroup 只能由具有 open_id 的飞书用户消息触发。");
|
|
2993
3091
|
}
|
|
3092
|
+
await this.outbound.sendText(sourceContextKey, FORK_GROUP_STARTED_MESSAGE);
|
|
2994
3093
|
const prepared = await this.prepareForkGroupSession(sourceContextKey, requestedTitle, incomingMessage);
|
|
2995
3094
|
await this.forkPreparedSessionToFeishuGroup(sourceContextKey, prepared, userId, incomingMessage?.threadContext ? prepared.sourceDescription : "当前任务");
|
|
2996
3095
|
}
|
|
@@ -2998,6 +3097,7 @@ export class ProxySessionController {
|
|
|
2998
3097
|
if (!userId?.startsWith("ou_")) {
|
|
2999
3098
|
throw new Error("ForkGroup 只能由具有 open_id 的飞书用户触发。");
|
|
3000
3099
|
}
|
|
3100
|
+
await this.outbound.sendText(sourceContextKey, FORK_GROUP_STARTED_MESSAGE);
|
|
3001
3101
|
const plan = await this.prepareForkSession(sourceContextKey, reference);
|
|
3002
3102
|
await this.forkPreparedSessionToFeishuGroup(sourceContextKey, {
|
|
3003
3103
|
plan,
|
|
@@ -3070,7 +3170,7 @@ export class ProxySessionController {
|
|
|
3070
3170
|
record: { ...loaded.record, contextKey: record.contextKey },
|
|
3071
3171
|
};
|
|
3072
3172
|
}
|
|
3073
|
-
this.outbound.registerSession(record.localSessionId, record.contextKey, record.title, record.cwd, this.agentLabel(record.agentName));
|
|
3173
|
+
this.outbound.registerSession(record.localSessionId, record.contextKey, record.title, record.cwd, this.agentLabel(record.agentName), record.model);
|
|
3074
3174
|
const loading = (async () => {
|
|
3075
3175
|
if (record.lastTurnId) {
|
|
3076
3176
|
try {
|
|
@@ -3106,7 +3206,8 @@ export class ProxySessionController {
|
|
|
3106
3206
|
catch (error) {
|
|
3107
3207
|
if (activeWriterThreadId(error))
|
|
3108
3208
|
throw error;
|
|
3109
|
-
if (!(agent.kind === "app-server" && !record.lastTurnId
|
|
3209
|
+
if (!(agent.kind === "app-server" && !record.lastTurnId
|
|
3210
|
+
&& !this.store.getForkHistorySource(record.localSessionId) && isMissingRolloutError(error)))
|
|
3110
3211
|
throw error;
|
|
3111
3212
|
this.logger.warn({ error, sessionId: record.localSessionId }, "App Server task has no rollout; creating a replacement task.");
|
|
3112
3213
|
session = await runtime.createSession({
|
|
@@ -3210,14 +3311,20 @@ export class ProxySessionController {
|
|
|
3210
3311
|
lastTurnStatus: event.type === "turn_completed" ? "completed" : event.type === "turn_cancelled" ? "cancelled" : "failed",
|
|
3211
3312
|
});
|
|
3212
3313
|
}
|
|
3314
|
+
const enrichedEvent = event.type === "context_compaction"
|
|
3315
|
+
? {
|
|
3316
|
+
...event,
|
|
3317
|
+
turnCount: event.turnCount ?? this.store.countTaskTurns(event.sessionId),
|
|
3318
|
+
}
|
|
3319
|
+
: event;
|
|
3213
3320
|
const presentationEvent = event.type === "turn_failed"
|
|
3214
3321
|
&& isRetryableLlmTurnFailure(event.message)
|
|
3215
3322
|
&& (failedAttempt?.retryCount ?? 0) >= MAX_LLM_TURN_RETRIES
|
|
3216
3323
|
? {
|
|
3217
|
-
...
|
|
3324
|
+
...enrichedEvent,
|
|
3218
3325
|
message: `${event.message}\n\n已自动重试 ${MAX_LLM_TURN_RETRIES} 次,仍未成功。`,
|
|
3219
3326
|
}
|
|
3220
|
-
:
|
|
3327
|
+
: enrichedEvent;
|
|
3221
3328
|
let presentationError;
|
|
3222
3329
|
try {
|
|
3223
3330
|
await this.outbound.onEvent(presentationEvent);
|
|
@@ -3521,6 +3628,7 @@ export class ProxySessionController {
|
|
|
3521
3628
|
}
|
|
3522
3629
|
async cancel(contextKey) {
|
|
3523
3630
|
const record = this.requireCurrentSession(contextKey);
|
|
3631
|
+
await this.outbound.sendText(contextKey, STOP_TASK_STARTED_MESSAGE);
|
|
3524
3632
|
await this.cancelSession(record);
|
|
3525
3633
|
}
|
|
3526
3634
|
async cancelSession(record) {
|
|
@@ -3535,6 +3643,8 @@ export class ProxySessionController {
|
|
|
3535
3643
|
await runtime.setGoal(record.localSessionId, { status: "paused" });
|
|
3536
3644
|
}
|
|
3537
3645
|
catch (error) {
|
|
3646
|
+
if (error instanceof CodexVersionError)
|
|
3647
|
+
throw error;
|
|
3538
3648
|
this.logger.warn({ error, sessionId: record.localSessionId }, "Failed to pause the active Agent goal before interrupting its turn.");
|
|
3539
3649
|
}
|
|
3540
3650
|
}
|
|
@@ -3547,6 +3657,8 @@ export class ProxySessionController {
|
|
|
3547
3657
|
: undefined;
|
|
3548
3658
|
}
|
|
3549
3659
|
catch (error) {
|
|
3660
|
+
if (error instanceof CodexVersionError)
|
|
3661
|
+
throw error;
|
|
3550
3662
|
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.");
|
|
3551
3663
|
}
|
|
3552
3664
|
}
|
|
@@ -3590,8 +3702,14 @@ export class ProxySessionController {
|
|
|
3590
3702
|
async resetCurrentSessionToTurn(contextKey, sessionId, turnId) {
|
|
3591
3703
|
if (!sessionId || !turnId)
|
|
3592
3704
|
throw new Error("无效的 Reset 请求。请使用最新的思考卡片重试。");
|
|
3705
|
+
await this.runSessionReset(sessionId, async () => {
|
|
3706
|
+
await this.outbound.sendText(contextKey, TURN_RESET_STARTED_MESSAGE);
|
|
3707
|
+
await this.performCurrentSessionReset(contextKey, sessionId, turnId);
|
|
3708
|
+
});
|
|
3709
|
+
}
|
|
3710
|
+
async runSessionReset(sessionId, operation) {
|
|
3593
3711
|
const previous = this.sessionResets.get(sessionId) ?? Promise.resolve();
|
|
3594
|
-
const reset = previous.catch(() => undefined).then(
|
|
3712
|
+
const reset = previous.catch(() => undefined).then(operation);
|
|
3595
3713
|
this.sessionResets.set(sessionId, reset);
|
|
3596
3714
|
try {
|
|
3597
3715
|
await reset;
|
|
@@ -3601,6 +3719,19 @@ export class ProxySessionController {
|
|
|
3601
3719
|
this.sessionResets.delete(sessionId);
|
|
3602
3720
|
}
|
|
3603
3721
|
}
|
|
3722
|
+
async waitForCurrentSessionReset(contextKey) {
|
|
3723
|
+
const sessionId = this.currentSession(contextKey)?.localSessionId;
|
|
3724
|
+
if (!sessionId)
|
|
3725
|
+
return;
|
|
3726
|
+
let reset = this.sessionResets.get(sessionId);
|
|
3727
|
+
while (reset) {
|
|
3728
|
+
await reset.catch(() => undefined);
|
|
3729
|
+
const next = this.sessionResets.get(sessionId);
|
|
3730
|
+
if (!next || next === reset)
|
|
3731
|
+
return;
|
|
3732
|
+
reset = next;
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3604
3735
|
async resetFromHistoryCard(contextKey, sessionId, turnId, updateMessageId, page) {
|
|
3605
3736
|
if (!sessionId || !turnId)
|
|
3606
3737
|
throw new Error("无效的 Reset 请求。请使用最新的历史轮次卡片重试。");
|
|
@@ -3611,16 +3742,11 @@ export class ProxySessionController {
|
|
|
3611
3742
|
}
|
|
3612
3743
|
this.resetHistoryOperations.set(sessionId, turnId);
|
|
3613
3744
|
try {
|
|
3614
|
-
await this.
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
}
|
|
3620
|
-
throw error;
|
|
3621
|
-
}
|
|
3622
|
-
try {
|
|
3623
|
-
await this.resetCurrentSessionToTurn(contextKey, sessionId, turnId);
|
|
3745
|
+
await this.runSessionReset(sessionId, async () => {
|
|
3746
|
+
await this.outbound.sendText(contextKey, TURN_RESET_STARTED_MESSAGE);
|
|
3747
|
+
await this.openResetHistory(contextKey, { expectedSessionId: sessionId, updateMessageId, page });
|
|
3748
|
+
await this.performCurrentSessionReset(contextKey, sessionId, turnId);
|
|
3749
|
+
});
|
|
3624
3750
|
}
|
|
3625
3751
|
catch (error) {
|
|
3626
3752
|
if (this.resetHistoryOperations.get(sessionId) === turnId) {
|
|
@@ -3650,9 +3776,9 @@ export class ProxySessionController {
|
|
|
3650
3776
|
if (!current.remoteSessionId || !this.isCodexSession(current)) {
|
|
3651
3777
|
throw new Error("当前任务不是可 Reset 的 App Server 任务。");
|
|
3652
3778
|
}
|
|
3653
|
-
const historyTurn = this.store.
|
|
3779
|
+
const historyTurn = this.store.listTaskTurnGraphIndex(current.localSessionId)
|
|
3654
3780
|
.find((turn) => turn.turnId === turnId);
|
|
3655
|
-
const snapshot =
|
|
3781
|
+
const snapshot = historyTurn ? this.store.getTurnPromptSummary(turnId) : undefined;
|
|
3656
3782
|
if (!snapshot || snapshot.status !== "completed") {
|
|
3657
3783
|
throw new Error("只能将当前任务 Reset 到已成功完成的轮次。");
|
|
3658
3784
|
}
|
|
@@ -3695,7 +3821,7 @@ export class ProxySessionController {
|
|
|
3695
3821
|
lastTurnId: turnId,
|
|
3696
3822
|
lastTurnStatus: "completed",
|
|
3697
3823
|
});
|
|
3698
|
-
this.outbound.registerSession(current.localSessionId, contextKey, forked.title ?? current.title, current.cwd, this.agentLabel(current.agentName));
|
|
3824
|
+
this.outbound.registerSession(current.localSessionId, contextKey, forked.title ?? current.title, current.cwd, this.agentLabel(current.agentName), current.model);
|
|
3699
3825
|
this.store.audit(contextKey, "session_reset_to_turn", {
|
|
3700
3826
|
localSessionId: current.localSessionId,
|
|
3701
3827
|
previousRemoteSessionId,
|
|
@@ -3745,12 +3871,13 @@ export class ProxySessionController {
|
|
|
3745
3871
|
};
|
|
3746
3872
|
if (record) {
|
|
3747
3873
|
const loaded = await this.loadSession(record);
|
|
3748
|
-
const models = await loaded.runtime.listModels();
|
|
3749
|
-
const currentModel = models.find((model) => model.id === loaded.session.model)
|
|
3750
|
-
?? models.find((model) => model.isDefault);
|
|
3751
3874
|
const providerSupported = loaded.runtime.kind === "codex" && Boolean(loaded.runtime.listModelProviders);
|
|
3752
3875
|
const providers = providerSupported ? await this.modelProviderOptions(loaded) : [];
|
|
3753
3876
|
const currentProvider = loaded.session.modelProvider ?? providers.find((provider) => provider.isDefault)?.id;
|
|
3877
|
+
const models = await loaded.runtime.listModels(currentProvider, loaded.session.model);
|
|
3878
|
+
const currentModel = models.find((model) => model.id === loaded.session.model)
|
|
3879
|
+
?? models.find((model) => model.isDefault)
|
|
3880
|
+
?? models[0];
|
|
3754
3881
|
const currentEffort = currentModel?.supportedReasoningEfforts.some((option) => option.value === loaded.session.reasoningEffort)
|
|
3755
3882
|
? loaded.session.reasoningEffort
|
|
3756
3883
|
: currentModel?.defaultReasoningEffort ?? loaded.session.reasoningEffort;
|
|
@@ -3808,24 +3935,20 @@ export class ProxySessionController {
|
|
|
3808
3935
|
const record = options.sessionId
|
|
3809
3936
|
? this.requireSession(contextKey, options.sessionId)
|
|
3810
3937
|
: this.requireCurrentSession(contextKey);
|
|
3938
|
+
await this.outbound.sendText(contextKey, `正在切换到 Provider ${modelProvider},请稍后。`);
|
|
3811
3939
|
const loaded = await this.loadSession(record);
|
|
3812
3940
|
await this.assertModelProvider(loaded, modelProvider);
|
|
3813
|
-
const models = await loaded.runtime.listModels();
|
|
3814
|
-
const model = models
|
|
3815
|
-
?? models.find((candidate) => candidate.isDefault);
|
|
3941
|
+
const models = await loaded.runtime.listModels(modelProvider, loaded.session.model);
|
|
3942
|
+
const model = selectProviderModel(models, loaded.session.model);
|
|
3816
3943
|
if (!model)
|
|
3817
3944
|
throw new Error("当前运行时没有可用于 Provider 切换的模型。");
|
|
3818
|
-
const effort = model
|
|
3819
|
-
? loaded.session.reasoningEffort
|
|
3820
|
-
: model.defaultReasoningEffort ?? model.supportedReasoningEfforts[0]?.value;
|
|
3821
|
-
if (!effort)
|
|
3822
|
-
throw new Error(`模型 ${model.id} 没有可用于 Provider 切换的思考强度。`);
|
|
3945
|
+
const effort = selectProviderReasoningEffort(model, loaded.session.reasoningEffort);
|
|
3823
3946
|
await this.applyProviderSettings(contextKey, {
|
|
3824
3947
|
provider: modelProvider,
|
|
3825
3948
|
model: model.id,
|
|
3826
3949
|
effort,
|
|
3827
3950
|
mode: loaded.session.permissionMode,
|
|
3828
|
-
}, options);
|
|
3951
|
+
}, options, false);
|
|
3829
3952
|
}
|
|
3830
3953
|
async openProviderSelector(contextKey, options = {}) {
|
|
3831
3954
|
await this.openExecutionSettings(contextKey, "provider", options);
|
|
@@ -3833,11 +3956,11 @@ export class ProxySessionController {
|
|
|
3833
3956
|
async openProviderModelSelector(contextKey, modelProvider, permissionMode, sessionId, updateMessageId) {
|
|
3834
3957
|
const loaded = await this.loadSession(this.requireSession(contextKey, sessionId));
|
|
3835
3958
|
await this.assertModelProvider(loaded, modelProvider);
|
|
3836
|
-
const models = await loaded.runtime.listModels();
|
|
3959
|
+
const models = await loaded.runtime.listModels(modelProvider, loaded.session.model);
|
|
3837
3960
|
const card = this.cardRenderer.renderModelSelector({
|
|
3838
3961
|
sessionId,
|
|
3839
3962
|
contextKey,
|
|
3840
|
-
currentModel: loaded.session.model
|
|
3963
|
+
currentModel: selectProviderModel(models, loaded.session.model)?.id,
|
|
3841
3964
|
reasoningEffort: loaded.session.reasoningEffort,
|
|
3842
3965
|
models,
|
|
3843
3966
|
modelProvider,
|
|
@@ -3849,20 +3972,23 @@ export class ProxySessionController {
|
|
|
3849
3972
|
async openProviderReasoningSelector(contextKey, modelProvider, model, permissionMode, sessionId, updateMessageId) {
|
|
3850
3973
|
const loaded = await this.loadSession(this.requireSession(contextKey, sessionId));
|
|
3851
3974
|
await this.assertModelProvider(loaded, modelProvider);
|
|
3852
|
-
const models = await loaded.runtime.listModels();
|
|
3975
|
+
const models = await loaded.runtime.listModels(modelProvider, model);
|
|
3853
3976
|
const selected = models.find((candidate) => candidate.id === model);
|
|
3854
3977
|
if (!selected)
|
|
3855
3978
|
throw new Error(`未知模型:${model}`);
|
|
3856
|
-
const
|
|
3979
|
+
const options = selected.supportedReasoningEfforts.length > 0
|
|
3980
|
+
? selected.supportedReasoningEfforts
|
|
3981
|
+
: [{ value: selectProviderReasoningEffort(selected, loaded.session.reasoningEffort) }];
|
|
3982
|
+
const currentEffort = options.some((option) => option.value === loaded.session.reasoningEffort)
|
|
3857
3983
|
? loaded.session.reasoningEffort
|
|
3858
|
-
: selected.defaultReasoningEffort;
|
|
3984
|
+
: selected.defaultReasoningEffort ?? options[0]?.value;
|
|
3859
3985
|
const card = this.cardRenderer.renderReasoningSelector({
|
|
3860
3986
|
sessionId,
|
|
3861
3987
|
contextKey,
|
|
3862
3988
|
modelProvider,
|
|
3863
3989
|
model,
|
|
3864
3990
|
currentEffort,
|
|
3865
|
-
options
|
|
3991
|
+
options,
|
|
3866
3992
|
permissionMode,
|
|
3867
3993
|
unifiedSettings: true,
|
|
3868
3994
|
});
|
|
@@ -3880,28 +4006,21 @@ export class ProxySessionController {
|
|
|
3880
4006
|
currentMode: permissionMode,
|
|
3881
4007
|
}));
|
|
3882
4008
|
}
|
|
3883
|
-
async applyProviderSettings(contextKey, settings, options = {}) {
|
|
4009
|
+
async applyProviderSettings(contextKey, settings, options = {}, announceStart = true) {
|
|
3884
4010
|
const record = options.sessionId
|
|
3885
4011
|
? this.requireSession(contextKey, options.sessionId)
|
|
3886
4012
|
: this.requireCurrentSession(contextKey);
|
|
4013
|
+
if (announceStart) {
|
|
4014
|
+
await this.outbound.sendText(contextKey, `正在切换到 Provider ${settings.provider},请稍后。`);
|
|
4015
|
+
}
|
|
3887
4016
|
const loaded = await this.loadSession(record);
|
|
3888
4017
|
await this.assertProviderModelSettings(loaded, settings.provider, settings.model, settings.effort);
|
|
3889
|
-
|
|
3890
|
-
throw new Error("当前运行时不支持 Provider 设置。");
|
|
3891
|
-
}
|
|
3892
|
-
const session = await loaded.runtime.setExecutionSettings(loaded.record.localSessionId, {
|
|
4018
|
+
const session = await this.changeProviderSettings(loaded, {
|
|
3893
4019
|
modelProvider: settings.provider,
|
|
3894
4020
|
model: settings.model,
|
|
3895
4021
|
reasoningEffort: settings.effort,
|
|
3896
4022
|
permissionMode: settings.mode,
|
|
3897
4023
|
});
|
|
3898
|
-
this.store.updateRuntimeSession(loaded.record.localSessionId, {
|
|
3899
|
-
modelProvider: session.modelProvider ?? settings.provider,
|
|
3900
|
-
model: session.model ?? settings.model,
|
|
3901
|
-
reasoningEffort: session.reasoningEffort ?? settings.effort,
|
|
3902
|
-
permissionMode: session.permissionMode,
|
|
3903
|
-
});
|
|
3904
|
-
await this.persistAgentExecutionDefaults(loaded.record.localSessionId);
|
|
3905
4024
|
const notice = [
|
|
3906
4025
|
`Provider 已切换为 ${cardCode(session.modelProvider ?? settings.provider)}`,
|
|
3907
4026
|
`模型 ${cardCode(session.model ?? settings.model)}`,
|
|
@@ -3920,12 +4039,38 @@ export class ProxySessionController {
|
|
|
3920
4039
|
await this.outbound.sendText(contextKey, notice.replaceAll("`", ""));
|
|
3921
4040
|
}
|
|
3922
4041
|
}
|
|
4042
|
+
async changeProviderSettings(loaded, settings) {
|
|
4043
|
+
if (!loaded.runtime.setExecutionSettings)
|
|
4044
|
+
throw new Error("当前运行时不支持 Provider 设置。");
|
|
4045
|
+
const persist = async (candidate) => {
|
|
4046
|
+
assertProviderSettingsApplied(candidate, settings);
|
|
4047
|
+
const agent = this.ensureAgent(loaded.record.agentName);
|
|
4048
|
+
const previousDefaults = agent.defaults;
|
|
4049
|
+
await this.persistAgentExecutionDefaults(loaded.record.localSessionId, candidate);
|
|
4050
|
+
try {
|
|
4051
|
+
this.store.updateRuntimeSession(loaded.record.localSessionId, {
|
|
4052
|
+
remoteSessionId: candidate.remoteSessionId,
|
|
4053
|
+
modelProvider: candidate.modelProvider,
|
|
4054
|
+
model: candidate.model,
|
|
4055
|
+
reasoningEffort: candidate.reasoningEffort,
|
|
4056
|
+
permissionMode: candidate.permissionMode,
|
|
4057
|
+
});
|
|
4058
|
+
}
|
|
4059
|
+
catch (error) {
|
|
4060
|
+
await this.lifecycle?.persistAgentExecutionDefaults?.(loaded.record.agentName, previousDefaults ?? {});
|
|
4061
|
+
agent.defaults = previousDefaults;
|
|
4062
|
+
throw error;
|
|
4063
|
+
}
|
|
4064
|
+
};
|
|
4065
|
+
return loaded.runtime.setExecutionSettings(loaded.record.localSessionId, settings, persist);
|
|
4066
|
+
}
|
|
3923
4067
|
async assertProviderModelSettings(loaded, modelProvider, model, effort) {
|
|
3924
4068
|
await this.assertModelProvider(loaded, modelProvider);
|
|
3925
|
-
const selected = (await loaded.runtime.listModels()).find((candidate) => candidate.id === model);
|
|
4069
|
+
const selected = (await loaded.runtime.listModels(modelProvider, model)).find((candidate) => candidate.id === model);
|
|
3926
4070
|
if (!selected)
|
|
3927
4071
|
throw new Error(`未知模型:${model}`);
|
|
3928
|
-
if (
|
|
4072
|
+
if (selected.supportedReasoningEfforts.length > 0
|
|
4073
|
+
&& !selected.supportedReasoningEfforts.some((option) => option.value === effort)) {
|
|
3929
4074
|
const supported = selected.supportedReasoningEfforts.map((option) => option.value).join("、") || "无";
|
|
3930
4075
|
throw new Error(`模型 ${model} 不支持思考强度 ${effort}。支持的强度:${supported}`);
|
|
3931
4076
|
}
|
|
@@ -3956,13 +4101,14 @@ export class ProxySessionController {
|
|
|
3956
4101
|
? this.requireSession(contextKey, options.sessionId)
|
|
3957
4102
|
: this.requireCurrentSession(contextKey);
|
|
3958
4103
|
const loaded = await this.loadSession(record);
|
|
3959
|
-
const models = await loaded.runtime.listModels();
|
|
4104
|
+
const models = await loaded.runtime.listModels(loaded.session.modelProvider, loaded.session.model);
|
|
3960
4105
|
const selected = models.find((item) => item.id === model);
|
|
3961
4106
|
if (!selected)
|
|
3962
4107
|
throw new Error(`未知模型:${model}`);
|
|
3963
4108
|
const currentEffort = loaded.session.reasoningEffort;
|
|
3964
4109
|
const compatible = currentEffort
|
|
3965
|
-
? selected.supportedReasoningEfforts.
|
|
4110
|
+
? selected.supportedReasoningEfforts.length === 0
|
|
4111
|
+
|| selected.supportedReasoningEfforts.some((option) => option.value === currentEffort)
|
|
3966
4112
|
: false;
|
|
3967
4113
|
const nextEffort = compatible ? currentEffort : selected.defaultReasoningEffort;
|
|
3968
4114
|
await loaded.runtime.setModel(loaded.record.localSessionId, model);
|
|
@@ -4282,7 +4428,9 @@ export class ProxySessionController {
|
|
|
4282
4428
|
const normalizedTitle = normalizeTaskTitle(title);
|
|
4283
4429
|
if (!normalizedTitle)
|
|
4284
4430
|
throw new Error("任务标题不能为空。");
|
|
4285
|
-
const
|
|
4431
|
+
const current = this.requireCurrentSession(contextKey);
|
|
4432
|
+
await this.outbound.sendText(contextKey, CHANGE_TITLE_STARTED_MESSAGE);
|
|
4433
|
+
const loaded = await this.loadSession(current);
|
|
4286
4434
|
if (loaded.runtime.setTitle)
|
|
4287
4435
|
await loaded.runtime.setTitle(loaded.record.localSessionId, normalizedTitle);
|
|
4288
4436
|
else
|
|
@@ -4341,7 +4489,7 @@ export class ProxySessionController {
|
|
|
4341
4489
|
? this.requireSession(contextKey, options.sessionId)
|
|
4342
4490
|
: this.requireCurrentSession(contextKey);
|
|
4343
4491
|
const loaded = await this.loadSession(record);
|
|
4344
|
-
const models = await loaded.runtime.listModels();
|
|
4492
|
+
const models = await loaded.runtime.listModels(loaded.session.modelProvider, loaded.session.model);
|
|
4345
4493
|
const currentModel = models.find((item) => item.id === loaded.session.model)
|
|
4346
4494
|
?? models.find((item) => item.isDefault);
|
|
4347
4495
|
if (!currentModel)
|
|
@@ -4394,6 +4542,73 @@ export class ProxySessionController {
|
|
|
4394
4542
|
? `已切换为自动执行模式,并已保存为 ${loaded.record.agentName} Agent 的默认设置。`
|
|
4395
4543
|
: `已切换为执行前确认模式,并已保存为 ${loaded.record.agentName} Agent 的默认设置。`);
|
|
4396
4544
|
}
|
|
4545
|
+
taskTurnIds(current) {
|
|
4546
|
+
const ids = this.store.listTaskTurnGraphIndex(current.localSessionId).map((turn) => turn.turnId);
|
|
4547
|
+
if (current.lastTurnStatus === "running" && current.lastTurnId && !ids.includes(current.lastTurnId)) {
|
|
4548
|
+
ids.unshift(current.lastTurnId);
|
|
4549
|
+
}
|
|
4550
|
+
return ids;
|
|
4551
|
+
}
|
|
4552
|
+
runningTurnSnapshot(current, turnId) {
|
|
4553
|
+
const snapshot = turnViewSnapshot(this.store.getTurnSnapshot(turnId));
|
|
4554
|
+
if (snapshot)
|
|
4555
|
+
return snapshot;
|
|
4556
|
+
const attempt = this.store.findIncompleteTurnAttemptByTurnId(turnId);
|
|
4557
|
+
const startedAt = Date.parse(attempt?.createdAt ?? "");
|
|
4558
|
+
return {
|
|
4559
|
+
sessionId: current.localSessionId,
|
|
4560
|
+
turnId,
|
|
4561
|
+
taskTitle: current.title,
|
|
4562
|
+
prompt: attempt?.promptText ?? current.title,
|
|
4563
|
+
status: "running",
|
|
4564
|
+
startedAt: Number.isFinite(startedAt) ? startedAt : Date.now(),
|
|
4565
|
+
assistantText: "", plan: [], activities: [], completedTools: [], failedTools: [], fileSummary: [],
|
|
4566
|
+
};
|
|
4567
|
+
}
|
|
4568
|
+
async openTurnDetails(contextKey, reference) {
|
|
4569
|
+
const current = this.requireCurrentSession(contextKey);
|
|
4570
|
+
let turnId;
|
|
4571
|
+
if (/^\d+$/.test(reference)) {
|
|
4572
|
+
const index = Number(reference);
|
|
4573
|
+
const runningCount = current.lastTurnStatus === "running" && current.lastTurnId ? 1 : 0;
|
|
4574
|
+
const hasMore = index > runningCount && this.isCodexSession(current)
|
|
4575
|
+
? await this.hydrateTaskTurnHistory(current, index - runningCount)
|
|
4576
|
+
: false;
|
|
4577
|
+
turnId = this.taskTurnIds(current)[index - 1];
|
|
4578
|
+
if (!turnId) {
|
|
4579
|
+
throw new Error(hasMore
|
|
4580
|
+
? "轮次历史暂未加载完成,请稍后重试。"
|
|
4581
|
+
: `未找到序号为 ${reference} 的轮次。请发送 /turn 查看当前轮次序号。`);
|
|
4582
|
+
}
|
|
4583
|
+
}
|
|
4584
|
+
else {
|
|
4585
|
+
const snapshot = turnViewSnapshot(this.store.getTurnSnapshot(reference));
|
|
4586
|
+
if (snapshot && (snapshot.sessionId === current.localSessionId
|
|
4587
|
+
|| this.store.hasTaskHistoryTurn(current.localSessionId, reference))) {
|
|
4588
|
+
turnId = reference;
|
|
4589
|
+
}
|
|
4590
|
+
else if (current.lastTurnStatus === "running" && current.lastTurnId === reference) {
|
|
4591
|
+
turnId = reference;
|
|
4592
|
+
}
|
|
4593
|
+
else if (this.isCodexSession(current) && (!snapshot || this.store.getForkHistorySource(current.localSessionId))) {
|
|
4594
|
+
const hasMore = await this.hydrateTaskTurnHistory(current, 0, reference);
|
|
4595
|
+
if (this.store.hasTaskHistoryTurn(current.localSessionId, reference))
|
|
4596
|
+
turnId = reference;
|
|
4597
|
+
else if (hasMore)
|
|
4598
|
+
throw new Error("轮次历史暂未加载完成,请稍后重试。");
|
|
4599
|
+
}
|
|
4600
|
+
if (!turnId && snapshot)
|
|
4601
|
+
throw new Error("这个 Turn 不属于当前任务的历史。请先切换到对应任务。");
|
|
4602
|
+
if (!turnId)
|
|
4603
|
+
throw new Error(`未找到当前任务的 Turn:${reference}`);
|
|
4604
|
+
}
|
|
4605
|
+
if (current.lastTurnStatus === "running" && current.lastTurnId === turnId
|
|
4606
|
+
&& !this.store.getTurnPromptSummary(turnId)) {
|
|
4607
|
+
await this.outbound.sendInteractiveCard(contextKey, this.cardRenderer.renderTurnDetails(this.runningTurnSnapshot(current, turnId)));
|
|
4608
|
+
return;
|
|
4609
|
+
}
|
|
4610
|
+
await this.outbound.showDetails(contextKey, turnId);
|
|
4611
|
+
}
|
|
4397
4612
|
async openResetHistory(contextKey, options = {}) {
|
|
4398
4613
|
const current = this.requireCurrentSession(contextKey);
|
|
4399
4614
|
if (options.expectedSessionId && current.localSessionId !== options.expectedSessionId) {
|
|
@@ -4402,35 +4617,19 @@ export class ProxySessionController {
|
|
|
4402
4617
|
if (!current.remoteSessionId || !this.isCodexSession(current)) {
|
|
4403
4618
|
throw new Error("当前任务不是可 Reset 的 App Server 任务。");
|
|
4404
4619
|
}
|
|
4405
|
-
|
|
4406
|
-
await this.
|
|
4407
|
-
const completedTurns = this.store.
|
|
4620
|
+
const requestedPage = Math.max(0, Math.trunc(options.page ?? 0));
|
|
4621
|
+
const hasMoreHistory = await this.hydrateTaskTurnHistory(current, (requestedPage + 1) * RESET_HISTORY_PAGE_SIZE);
|
|
4622
|
+
const completedTurns = this.store.listTaskTurnGraphIndex(current.localSessionId);
|
|
4408
4623
|
const completedTurnIds = new Set(completedTurns.map((turn) => turn.turnId));
|
|
4409
4624
|
const runningTurnId = current.lastTurnStatus === "running" && current.lastTurnId
|
|
4410
4625
|
&& !completedTurnIds.has(current.lastTurnId)
|
|
4411
4626
|
? current.lastTurnId
|
|
4412
4627
|
: undefined;
|
|
4413
|
-
const persistedRunningSnapshot = runningTurnId
|
|
4414
|
-
? turnViewSnapshot(this.store.getTurnSnapshot(runningTurnId))
|
|
4415
|
-
: undefined;
|
|
4416
4628
|
const runningAttempt = runningTurnId
|
|
4417
4629
|
? this.store.findIncompleteTurnAttemptByTurnId(runningTurnId)
|
|
4418
4630
|
: undefined;
|
|
4419
|
-
const attemptStartedAt = Date.parse(runningAttempt?.createdAt ?? "");
|
|
4420
4631
|
const runningSnapshot = runningTurnId
|
|
4421
|
-
?
|
|
4422
|
-
sessionId: current.localSessionId,
|
|
4423
|
-
turnId: runningTurnId,
|
|
4424
|
-
prompt: runningAttempt?.promptText ?? current.title,
|
|
4425
|
-
status: "running",
|
|
4426
|
-
startedAt: Number.isFinite(attemptStartedAt) ? attemptStartedAt : Date.now(),
|
|
4427
|
-
assistantText: "",
|
|
4428
|
-
plan: [],
|
|
4429
|
-
activities: [],
|
|
4430
|
-
completedTools: [],
|
|
4431
|
-
failedTools: [],
|
|
4432
|
-
fileSummary: [],
|
|
4433
|
-
}
|
|
4632
|
+
? this.runningTurnSnapshot(current, runningTurnId)
|
|
4434
4633
|
: undefined;
|
|
4435
4634
|
const runningTurn = runningTurnId && runningSnapshot
|
|
4436
4635
|
? {
|
|
@@ -4446,13 +4645,13 @@ export class ProxySessionController {
|
|
|
4446
4645
|
parentTurnId: turn.parentTurnId,
|
|
4447
4646
|
})));
|
|
4448
4647
|
const total = allTurns.length;
|
|
4449
|
-
const totalPages = Math.max(1, Math.ceil(total / RESET_HISTORY_PAGE_SIZE));
|
|
4648
|
+
const totalPages = Math.max(1, Math.ceil(total / RESET_HISTORY_PAGE_SIZE)) + (hasMoreHistory ? 1 : 0);
|
|
4450
4649
|
const page = Math.max(0, Math.min(Math.trunc(options.page ?? 0), totalPages - 1));
|
|
4451
4650
|
const offset = page * RESET_HISTORY_PAGE_SIZE;
|
|
4452
4651
|
const turns = allTurns.slice(offset, offset + RESET_HISTORY_PAGE_SIZE);
|
|
4453
4652
|
const resettingTurnId = this.resetHistoryOperations.get(current.localSessionId);
|
|
4454
4653
|
const entries = turns.flatMap((turn, index) => {
|
|
4455
|
-
const snapshot =
|
|
4654
|
+
const snapshot = turn.turnId === runningTurnId ? runningSnapshot : this.store.getTurnPromptSummary(turn.turnId);
|
|
4456
4655
|
if (!snapshot)
|
|
4457
4656
|
return [];
|
|
4458
4657
|
const graph = graphRows[offset + index];
|
|
@@ -4505,9 +4704,11 @@ export class ProxySessionController {
|
|
|
4505
4704
|
const card = this.cardRenderer.renderResetHistoryCard({
|
|
4506
4705
|
entries,
|
|
4507
4706
|
footerLines: [
|
|
4508
|
-
|
|
4509
|
-
? `第 ${page + 1}
|
|
4510
|
-
:
|
|
4707
|
+
hasMoreHistory
|
|
4708
|
+
? `第 ${page + 1} 页 · 已加载 ${total} 个 turn`
|
|
4709
|
+
: runningTurn
|
|
4710
|
+
? `第 ${page + 1}/${totalPages} 页 · 共 ${total} 个 turn(${completedTurns.length} 个已完成,1 个运行中)`
|
|
4711
|
+
: `第 ${page + 1}/${totalPages} 页 · 共 ${total} 个已完成 turn`,
|
|
4511
4712
|
...(resettingTurnId ? ["正在 Reset 到所选轮次,请稍候…"] : []),
|
|
4512
4713
|
],
|
|
4513
4714
|
pageActions,
|
|
@@ -4706,6 +4907,7 @@ export class ProxySessionController {
|
|
|
4706
4907
|
const record = this.requireCurrentSession(contextKey);
|
|
4707
4908
|
const filePath = resolveUserPath(requestedFilePath, record.cwd);
|
|
4708
4909
|
await this.assertSendableFile(filePath);
|
|
4910
|
+
await this.outbound.sendText(contextKey, SEND_FILE_STARTED_MESSAGE);
|
|
4709
4911
|
await this.outbound.sendFile(contextKey, filePath);
|
|
4710
4912
|
}
|
|
4711
4913
|
async listSessions(contextKey, searchTerm, options = {}) {
|
|
@@ -4996,7 +5198,7 @@ export class ProxySessionController {
|
|
|
4996
5198
|
runtime,
|
|
4997
5199
|
remote: options.forFork && runtime.readRemoteForkSource
|
|
4998
5200
|
? await runtime.readRemoteForkSource(remoteSessionId)
|
|
4999
|
-
: await runtime.readRemoteSession(remoteSessionId),
|
|
5201
|
+
: await runtime.readRemoteSession(remoteSessionId, options.view ?? "latest"),
|
|
5000
5202
|
};
|
|
5001
5203
|
}));
|
|
5002
5204
|
const matches = reads
|
|
@@ -5022,13 +5224,14 @@ export class ProxySessionController {
|
|
|
5022
5224
|
if (direct?.status === "closed") {
|
|
5023
5225
|
throw new Error(`找不到任务:${taskId}`);
|
|
5024
5226
|
}
|
|
5227
|
+
await this.outbound.sendText(contextKey, SWITCH_TASK_STARTED_MESSAGE);
|
|
5025
5228
|
const existing = direct ?? this.findStoredSessionByReference(taskId);
|
|
5026
5229
|
if (existing) {
|
|
5027
5230
|
const runtime = this.runtimes.forAgent(existing.agentName);
|
|
5028
5231
|
await this.assertSessionTurnOwnership(existing, runtime);
|
|
5029
5232
|
this.store.attachSessionToContext(contextKey, existing.localSessionId);
|
|
5030
5233
|
this.store.setCurrentSession(contextKey, existing.localSessionId);
|
|
5031
|
-
this.outbound.registerSession(existing.localSessionId, contextKey, existing.title, existing.cwd, this.agentLabel(existing.agentName));
|
|
5234
|
+
this.outbound.registerSession(existing.localSessionId, contextKey, existing.title, existing.cwd, this.agentLabel(existing.agentName), existing.model);
|
|
5032
5235
|
await this.outbound.sendText(contextKey, `已切换到任务:${existing.title ?? existing.remoteSessionId ?? taskId}`);
|
|
5033
5236
|
return;
|
|
5034
5237
|
}
|
|
@@ -5055,7 +5258,7 @@ export class ProxySessionController {
|
|
|
5055
5258
|
lastTurnStatus: mapRemoteTurnStatus(remote.lastTurnStatus),
|
|
5056
5259
|
});
|
|
5057
5260
|
this.store.setCurrentSession(contextKey, localSessionId);
|
|
5058
|
-
this.outbound.registerSession(localSessionId, contextKey, remote.title ?? remote.preview, remote.cwd, this.agentLabel(agentName));
|
|
5261
|
+
this.outbound.registerSession(localSessionId, contextKey, remote.title ?? remote.preview, remote.cwd, this.agentLabel(agentName), remote.model);
|
|
5059
5262
|
await this.outbound.sendText(contextKey, `已切换到任务:${remote.title ?? remote.preview ?? remote.id}。历史消息不会重新发送。`);
|
|
5060
5263
|
}
|
|
5061
5264
|
async stopSessionReference(contextKey, taskId) {
|
|
@@ -5106,6 +5309,9 @@ export class ProxySessionController {
|
|
|
5106
5309
|
if (local && this.store.countQueuedPrompts(local.localSessionId) > 0) {
|
|
5107
5310
|
throw new Error("任务仍有排队消息,无法归档。请先处理或取消排队消息。");
|
|
5108
5311
|
}
|
|
5312
|
+
if (options.source !== "dismiss") {
|
|
5313
|
+
await this.outbound.sendText(contextKey, ARCHIVE_TASK_STARTED_MESSAGE);
|
|
5314
|
+
}
|
|
5109
5315
|
const remoteReference = local
|
|
5110
5316
|
? remoteSessionReference(local.agentName, local.remoteSessionId)
|
|
5111
5317
|
: taskId;
|
|
@@ -5313,28 +5519,33 @@ export class ProxySessionController {
|
|
|
5313
5519
|
else {
|
|
5314
5520
|
current = context.currentSessionId ? this.store.getSession(context.currentSessionId) : undefined;
|
|
5315
5521
|
}
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5522
|
+
let remote;
|
|
5523
|
+
let metrics;
|
|
5524
|
+
if (current?.status === "running") {
|
|
5525
|
+
const runtime = this.runtimes.forAgent(current.agentName);
|
|
5526
|
+
if (runtime.getSession(current.localSessionId)) {
|
|
5527
|
+
try {
|
|
5528
|
+
remote = (await runtime.synchronizeSession(current.localSessionId)).remoteSummary;
|
|
5321
5529
|
current = this.store.getSession(current.localSessionId) ?? current;
|
|
5322
5530
|
}
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5531
|
+
catch (error) {
|
|
5532
|
+
if (error instanceof CodexVersionError)
|
|
5533
|
+
throw error;
|
|
5534
|
+
this.logger.warn({ error, sessionId: current.localSessionId }, "Failed to synchronize task status.");
|
|
5535
|
+
}
|
|
5326
5536
|
}
|
|
5327
5537
|
}
|
|
5328
5538
|
const localCurrent = current;
|
|
5329
|
-
let remote;
|
|
5330
5539
|
let goal;
|
|
5331
5540
|
if (current?.remoteSessionId) {
|
|
5332
5541
|
const runtime = this.runtimes.forAgent(current.agentName);
|
|
5333
|
-
if (runtime.readRemoteSession) {
|
|
5542
|
+
if (!remote && runtime.readRemoteSession) {
|
|
5334
5543
|
try {
|
|
5335
|
-
remote = await runtime.readRemoteSession(current.remoteSessionId);
|
|
5544
|
+
remote = await runtime.readRemoteSession(current.remoteSessionId, "latest-full");
|
|
5336
5545
|
}
|
|
5337
5546
|
catch (error) {
|
|
5547
|
+
if (error instanceof CodexVersionError)
|
|
5548
|
+
throw error;
|
|
5338
5549
|
this.logger.warn({ error, sessionId: current.localSessionId }, "Failed to inspect App Server task status.");
|
|
5339
5550
|
}
|
|
5340
5551
|
}
|
|
@@ -5347,6 +5558,14 @@ export class ProxySessionController {
|
|
|
5347
5558
|
this.logger.warn({ error, sessionId: current.localSessionId }, "Failed to inspect Agent goal status.");
|
|
5348
5559
|
}
|
|
5349
5560
|
}
|
|
5561
|
+
if (runtime.readRemoteSessionMetrics) {
|
|
5562
|
+
try {
|
|
5563
|
+
metrics = await runtime.readRemoteSessionMetrics(current.remoteSessionId);
|
|
5564
|
+
}
|
|
5565
|
+
catch (error) {
|
|
5566
|
+
this.logger.warn({ error, sessionId: current.localSessionId }, "Failed to inspect App Server task metrics.");
|
|
5567
|
+
}
|
|
5568
|
+
}
|
|
5350
5569
|
}
|
|
5351
5570
|
if (current)
|
|
5352
5571
|
current = mergeRemoteTaskStatus(current, remote);
|
|
@@ -5373,7 +5592,7 @@ export class ProxySessionController {
|
|
|
5373
5592
|
const resultLabel = remoteActive
|
|
5374
5593
|
? "执行中"
|
|
5375
5594
|
: remoteTurnStatusLabel(remote?.lastTurnStatus ?? current.lastTurnStatus);
|
|
5376
|
-
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)}`);
|
|
5595
|
+
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 ?? "尚未创建")}`, statusTaskMetricsLine(metrics?.turnCount ?? this.store.countTaskTurns(current.localSessionId), metrics?.storageBytes), `**当前执行 / 排队消息**:${activeTurnId ? cardCode(activeTurnId) : "无"} / ${queued} 条`, `**创建时间 / 最近活动**:${formatStatusTime(current.createdAt)} / ${formatStatusTime(current.updatedAt)}`);
|
|
5377
5596
|
}
|
|
5378
5597
|
const sections = [
|
|
5379
5598
|
{ title: targetSessionId ? "指定任务" : "当前任务", lines: taskLines },
|
|
@@ -5430,7 +5649,17 @@ export class ProxySessionController {
|
|
|
5430
5649
|
await this.outbound.sendInteractiveCard(contextKey, card);
|
|
5431
5650
|
}
|
|
5432
5651
|
async statusForCodexTask(contextKey, reference, options = {}) {
|
|
5433
|
-
const { agentName, remote } = await this.resolveRemoteCodexSession(reference);
|
|
5652
|
+
const { agentName, remote } = await this.resolveRemoteCodexSession(reference, { view: "latest-full" });
|
|
5653
|
+
const runtime = this.runtimes.forAgent(agentName);
|
|
5654
|
+
let metrics;
|
|
5655
|
+
if (runtime.readRemoteSessionMetrics) {
|
|
5656
|
+
try {
|
|
5657
|
+
metrics = await runtime.readRemoteSessionMetrics(remote.id);
|
|
5658
|
+
}
|
|
5659
|
+
catch (error) {
|
|
5660
|
+
this.logger.warn({ error, remoteSessionId: remote.id }, "Failed to inspect App Server task metrics.");
|
|
5661
|
+
}
|
|
5662
|
+
}
|
|
5434
5663
|
const actionReference = remoteSessionReference(agentName, remote.id);
|
|
5435
5664
|
const sections = [
|
|
5436
5665
|
{
|
|
@@ -5441,6 +5670,7 @@ export class ProxySessionController {
|
|
|
5441
5670
|
`**状态 / 当前任务**:${remoteSessionDetailStatus(remote)} / 未切换`,
|
|
5442
5671
|
`**Agent**:${cardCode(agentName)}`,
|
|
5443
5672
|
`**App Server 任务 ID**:${cardCode(remote.id)}`,
|
|
5673
|
+
statusTaskMetricsLine(metrics?.turnCount, metrics?.storageBytes),
|
|
5444
5674
|
`**最近回合**:${cardCode(remote.lastTurnId ?? "无")} ${remoteTurnStatusLabel(remote.lastTurnStatus)}`,
|
|
5445
5675
|
`**创建时间 / 最近活动**:${formatRemoteTime(remote.createdAt)} / ${formatRemoteTime(latestRemoteTimestamp(remote.recencyAt, remote.updatedAt))}`,
|
|
5446
5676
|
],
|
|
@@ -5561,10 +5791,11 @@ export class ProxySessionController {
|
|
|
5561
5791
|
throw new Error(`未知 agent:${agentName}`);
|
|
5562
5792
|
return agent;
|
|
5563
5793
|
}
|
|
5564
|
-
async persistAgentExecutionDefaults(localSessionId) {
|
|
5565
|
-
const
|
|
5566
|
-
if (!
|
|
5794
|
+
async persistAgentExecutionDefaults(localSessionId, candidate) {
|
|
5795
|
+
const record = this.store.getSession(localSessionId);
|
|
5796
|
+
if (!record)
|
|
5567
5797
|
throw new Error(`找不到任务:${localSessionId}`);
|
|
5798
|
+
const session = candidate ?? record;
|
|
5568
5799
|
const agent = this.ensureAgent(session.agentName);
|
|
5569
5800
|
const observed = {
|
|
5570
5801
|
...(session.modelProvider?.trim() ? { modelProvider: session.modelProvider } : {}),
|
|
@@ -5934,6 +6165,7 @@ export class ProxySessionController {
|
|
|
5934
6165
|
const lockPath = runtime.getThreadWriterLockPath?.(threadId);
|
|
5935
6166
|
if (!lockPath)
|
|
5936
6167
|
throw new Error("当前 Agent 不支持自动关闭占用程序。");
|
|
6168
|
+
await this.outbound.sendText(contextKey, CLOSE_WRITER_STARTED_MESSAGE);
|
|
5937
6169
|
let owners = await this.threadWriterProcesses.inspect(lockPath);
|
|
5938
6170
|
const expectedOwner = owners.length === 1 && threadWriterMatches(owners[0], expectedWriterPid, expectedWriterStartedAt, expectedApplicationPid, expectedApplicationStartedAt) ? owners[0] : undefined;
|
|
5939
6171
|
if (!expectedOwner) {
|
|
@@ -6259,10 +6491,6 @@ function bindSessionCardAction(action, bindings) {
|
|
|
6259
6491
|
function runtimeErrorMessage(error) {
|
|
6260
6492
|
return error instanceof Error ? error.message : String(error);
|
|
6261
6493
|
}
|
|
6262
|
-
function isMissingRolloutError(error) {
|
|
6263
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
6264
|
-
return /no rollout found|rollout[^\n]*(?:not found|missing)|thread\/resume failed/i.test(message);
|
|
6265
|
-
}
|
|
6266
6494
|
function isUnmaterializedCodexThreadError(error) {
|
|
6267
6495
|
const message = error instanceof Error ? error.message : String(error);
|
|
6268
6496
|
return /thread\/read failed:[^\n]*(?:not materialized|not loaded|includeTurns is unavailable before first user message)/i.test(message);
|
|
@@ -6436,6 +6664,11 @@ function statusExcerpt(value, maxLength) {
|
|
|
6436
6664
|
function formatDuration(milliseconds) {
|
|
6437
6665
|
return milliseconds < 1_000 ? `${milliseconds}ms` : `${(milliseconds / 1_000).toFixed(1)}s`;
|
|
6438
6666
|
}
|
|
6667
|
+
function statusTaskMetricsLine(turnCount, storageBytes) {
|
|
6668
|
+
const turns = turnCount === undefined ? "未知" : `${Math.max(0, Math.round(turnCount)).toLocaleString("en-US")} 轮`;
|
|
6669
|
+
const storage = storageBytes === undefined ? "未知" : formatStorageSize(storageBytes);
|
|
6670
|
+
return `**已执行轮次 / 磁盘占用**:${turns} / ${storage}`;
|
|
6671
|
+
}
|
|
6439
6672
|
function formatStatusTime(value) {
|
|
6440
6673
|
const date = new Date(value);
|
|
6441
6674
|
if (Number.isNaN(date.getTime()))
|
|
@@ -6870,6 +7103,21 @@ function deduplicateFileReferences(files) {
|
|
|
6870
7103
|
return true;
|
|
6871
7104
|
});
|
|
6872
7105
|
}
|
|
7106
|
+
function selectProviderModel(models, previousModel) {
|
|
7107
|
+
return models.find((model) => model.id === previousModel)
|
|
7108
|
+
?? models.find((model) => model.isDefault)
|
|
7109
|
+
?? models[0];
|
|
7110
|
+
}
|
|
7111
|
+
function selectProviderReasoningEffort(model, previousEffort) {
|
|
7112
|
+
if (previousEffort && (model.supportedReasoningEfforts.length === 0
|
|
7113
|
+
|| model.supportedReasoningEfforts.some((option) => option.value === previousEffort))) {
|
|
7114
|
+
return previousEffort;
|
|
7115
|
+
}
|
|
7116
|
+
return model.defaultReasoningEffort
|
|
7117
|
+
?? model.supportedReasoningEfforts[0]?.value
|
|
7118
|
+
?? previousEffort
|
|
7119
|
+
?? "medium";
|
|
7120
|
+
}
|
|
6873
7121
|
function recoveryCardPrompt(prompt) {
|
|
6874
7122
|
return `恢复重启前的任务:${prompt}`;
|
|
6875
7123
|
}
|