@keyou007/agent-bot 0.1.24-alpha.5 → 0.1.24-alpha.7
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 +40 -0
- package/README.md +29 -3
- package/README.zh.md +29 -3
- package/SECURITY.md +12 -0
- package/config.example.yaml +5 -0
- package/dist/cli/controlProtocol.js.map +1 -1
- package/dist/cli/serverStatus.js +1 -0
- package/dist/cli/serverStatus.js.map +1 -1
- package/dist/cli.js +6 -1
- package/dist/cli.js.map +1 -1
- package/dist/codex/CodexEventMapper.js +1 -0
- package/dist/codex/CodexEventMapper.js.map +1 -1
- package/dist/codex/CodexRuntime.js +247 -120
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/codex/ProviderSwitch.js +12 -13
- package/dist/codex/ProviderSwitch.js.map +1 -1
- package/dist/codex/TraePlanEntry.js +70 -0
- package/dist/codex/TraePlanEntry.js.map +1 -0
- package/dist/config/AgentExecutionDefaultsStore.js +4 -0
- package/dist/config/AgentExecutionDefaultsStore.js.map +1 -1
- package/dist/config/loadConfig.js +4 -0
- package/dist/config/loadConfig.js.map +1 -1
- package/dist/config/schema.js +3 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/feishu/CardRenderer.js +92 -8
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuConnector.js +1 -0
- package/dist/feishu/FeishuConnector.js.map +1 -1
- package/dist/feishu/FeishuTurnPresenter.js +121 -17
- package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
- package/dist/index.js +57 -7
- package/dist/index.js.map +1 -1
- package/dist/local-files/LocalFileViewerServer.js +51 -4
- package/dist/local-files/LocalFileViewerServer.js.map +1 -1
- package/dist/local-files/TurnPreviewPage.js +7 -2
- package/dist/local-files/TurnPreviewPage.js.map +1 -1
- package/dist/presentation/TurnStateReducer.js +41 -6
- package/dist/presentation/TurnStateReducer.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +620 -214
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/proxy/SessionOperationQueue.js +14 -0
- package/dist/proxy/SessionOperationQueue.js.map +1 -0
- package/dist/runtime/AcpRuntimeAdapter.js +4 -2
- package/dist/runtime/AcpRuntimeAdapter.js.map +1 -1
- package/dist/state/StateStore.js +22 -0
- package/dist/state/StateStore.js.map +1 -1
- package/dist/state/TurnPreviewJournal.js +19 -7
- package/dist/state/TurnPreviewJournal.js.map +1 -1
- package/dist/state/migrations.js +6 -0
- package/dist/state/migrations.js.map +1 -1
- package/dist/supervision/SafeRestartNotifier.js +33 -1
- package/dist/supervision/SafeRestartNotifier.js.map +1 -1
- package/dist/supervision/SafeRestartScheduler.js +22 -6
- package/dist/supervision/SafeRestartScheduler.js.map +1 -1
- package/dist/terminal/TerminalCards.js +13 -0
- package/dist/terminal/TerminalCards.js.map +1 -0
- package/dist/terminal/TerminalCommandTracker.js +74 -0
- package/dist/terminal/TerminalCommandTracker.js.map +1 -0
- package/dist/terminal/TerminalLinks.js +76 -0
- package/dist/terminal/TerminalLinks.js.map +1 -0
- package/dist/terminal/TerminalPage.js +212 -0
- package/dist/terminal/TerminalPage.js.map +1 -0
- package/dist/terminal/TerminalProcess.js +95 -0
- package/dist/terminal/TerminalProcess.js.map +1 -0
- package/dist/terminal/TerminalServer.js +676 -0
- package/dist/terminal/TerminalServer.js.map +1 -0
- package/docs/technical-reference.md +48 -0
- package/docs/technical-reference.zh.md +48 -0
- package/npm-shrinkwrap.json +55 -5
- package/package.json +11 -4
- package/skills/agent-bot/SKILL.md +11 -2
- package/src/cli/controlProtocol.ts +2 -1
- package/src/cli/serverStatus.ts +1 -0
- package/src/cli.ts +6 -1
- package/src/codex/CodexEventMapper.ts +1 -0
- package/src/codex/CodexRuntime.ts +244 -128
- package/src/codex/ProviderSwitch.ts +11 -12
- package/src/codex/TraePlanEntry.ts +63 -0
- package/src/config/AgentExecutionDefaultsStore.ts +2 -0
- package/src/config/loadConfig.ts +4 -0
- package/src/config/schema.ts +5 -1
- package/src/feishu/CardRenderer.ts +109 -8
- package/src/feishu/FeishuConnector.ts +1 -0
- package/src/feishu/FeishuTurnPresenter.ts +108 -16
- package/src/feishu/types.ts +1 -0
- package/src/index.ts +56 -8
- package/src/local-files/LocalFileViewerServer.ts +48 -4
- package/src/local-files/TurnPreviewPage.ts +7 -2
- package/src/presentation/TurnStateReducer.ts +46 -7
- package/src/presentation/turnViewTypes.ts +5 -0
- package/src/proxy/ProxySessionController.ts +629 -211
- package/src/proxy/SessionOperationQueue.ts +13 -0
- package/src/runtime/AcpRuntimeAdapter.ts +5 -3
- package/src/runtime/types.ts +8 -3
- package/src/state/StateStore.ts +38 -1
- package/src/state/TurnPreviewJournal.ts +19 -7
- package/src/state/migrations.ts +6 -0
- package/src/supervision/SafeRestartNotifier.ts +35 -2
- package/src/supervision/SafeRestartScheduler.ts +25 -6
- package/src/terminal/TerminalCards.ts +18 -0
- package/src/terminal/TerminalCommandTracker.ts +74 -0
- package/src/terminal/TerminalLinks.ts +61 -0
- package/src/terminal/TerminalPage.ts +213 -0
- package/src/terminal/TerminalProcess.ts +93 -0
- package/src/terminal/TerminalServer.ts +551 -0
|
@@ -4,7 +4,7 @@ import os from "node:os";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { createProjectlessWorkspace, detectProjectlessWorkspace } from "../codex/ProjectlessWorkspace.js";
|
|
6
6
|
import { CodexVersionError } from "../codex/CodexVersion.js";
|
|
7
|
-
import { assertProviderSettingsApplied,
|
|
7
|
+
import { assertProviderSettingsApplied, ProviderSwitchError } from "../codex/ProviderSwitch.js";
|
|
8
8
|
import { SystemThreadWriterProcessController, } from "../codex/ThreadWriterProcess.js";
|
|
9
9
|
import { resolveUserPath } from "../config/paths.js";
|
|
10
10
|
import { DEFAULT_GROUP_NAME_FORMAT, } from "../config/schema.js";
|
|
@@ -22,6 +22,7 @@ import { errorLogValue } from "../logging/errorLogValue.js";
|
|
|
22
22
|
import { createTurnViewState, hydrateTurnViewState } from "../presentation/TurnStateReducer.js";
|
|
23
23
|
import { buildTurnGraphRows } from "../presentation/turnGraph.js";
|
|
24
24
|
import { isActiveShellCommandJob, ShellCommandJobManager, } from "../shell/ShellCommandJobManager.js";
|
|
25
|
+
import { SessionOperationQueue } from "./SessionOperationQueue.js";
|
|
25
26
|
import { createId } from "../utils/id.js";
|
|
26
27
|
import { formatStorageSize } from "../utils/formatStorageSize.js";
|
|
27
28
|
import { truncateMiddle, truncateText } from "../utils/markdown.js";
|
|
@@ -53,7 +54,7 @@ const RECOVERY_HEARTBEAT_INTERVAL_MS = 60 * 1_000;
|
|
|
53
54
|
const SHELL_COMMAND_JOB_POLL_INTERVAL_MS = 1_000;
|
|
54
55
|
const SHELL_COMMAND_JOB_RELAUNCH_DELAY_MS = 5_000;
|
|
55
56
|
const APP_SERVER_RELEASE_POLL_INTERVAL_MS = 500;
|
|
56
|
-
const FORWARD_ATTACHMENT_WINDOW_MS =
|
|
57
|
+
const FORWARD_ATTACHMENT_WINDOW_MS = 1_500;
|
|
57
58
|
const DEFAULT_MERGED_FORWARD_INSTRUCTION = "请参考以下内容回复用户";
|
|
58
59
|
const DEFAULT_REFERENCED_MESSAGE_INSTRUCTION = "请参考引用消息回复用户";
|
|
59
60
|
const TURN_STOP_REQUESTED_MESSAGE = "已发送停止信号,任务会自动停止。";
|
|
@@ -227,9 +228,14 @@ export class ProxySessionController {
|
|
|
227
228
|
messageQueues = new Map();
|
|
228
229
|
switchGroupOperations = new Set();
|
|
229
230
|
sessionLoads = new Map();
|
|
231
|
+
settingsOnlySessions = new Set();
|
|
230
232
|
queuedPromptStarts = new Map();
|
|
231
233
|
queuedPromptCards = new Map();
|
|
232
234
|
queuedPromptCardWrites = new Map();
|
|
235
|
+
settingsOperations = new SessionOperationQueue();
|
|
236
|
+
// Loads may run inside settingsOperations; never acquire that queue while hydrating.
|
|
237
|
+
sessionHydrationOperations = new SessionOperationQueue();
|
|
238
|
+
agentDefaultsOperations = new SessionOperationQueue();
|
|
233
239
|
sessionResets = new Map();
|
|
234
240
|
resetHistoryOperations = new Map();
|
|
235
241
|
remoteTurnHistoryRefreshes = new Map();
|
|
@@ -239,6 +245,7 @@ export class ProxySessionController {
|
|
|
239
245
|
lastSessionListings = new Map();
|
|
240
246
|
threadInitializations = new Map();
|
|
241
247
|
llmRetryingSessions = new Set();
|
|
248
|
+
planHandoffs = new Map();
|
|
242
249
|
retriedFailureTurnIds = new Set();
|
|
243
250
|
queuedPromptRetryTimers = new Map();
|
|
244
251
|
recoveryRetryTimers = new Map();
|
|
@@ -304,7 +311,7 @@ export class ProxySessionController {
|
|
|
304
311
|
const pendingMergedForward = message.mergedForwardMessageId
|
|
305
312
|
? this.registerPendingForwardAttachment(message, this.pendingMergedForwards)
|
|
306
313
|
: undefined;
|
|
307
|
-
const pendingResourceForward = !attachmentReservation &&
|
|
314
|
+
const pendingResourceForward = !attachmentReservation && isForwardAttachmentSource(message)
|
|
308
315
|
? this.registerPendingForwardAttachment(message, this.pendingResourceForwards)
|
|
309
316
|
: undefined;
|
|
310
317
|
try {
|
|
@@ -342,7 +349,9 @@ export class ProxySessionController {
|
|
|
342
349
|
const attachment = await this.waitForForwardAttachment(message.messageId, pendingResourceForward, this.pendingResourceForwards);
|
|
343
350
|
if (attachment) {
|
|
344
351
|
coalescedForwardAttachment = true;
|
|
345
|
-
const
|
|
352
|
+
const sourceText = resourceMessage.text.trim();
|
|
353
|
+
const instruction = normalizeFeishuPostText(attachment.text)
|
|
354
|
+
|| (sourceText ? DEFAULT_REFERENCED_MESSAGE_INSTRUCTION : defaultResourcePrompt(resourceMessage));
|
|
346
355
|
forwardedImages = (resourceMessage.images ?? []).map((image) => ({
|
|
347
356
|
messageId: resourceMessage.messageId,
|
|
348
357
|
imageKey: image.imageKey,
|
|
@@ -354,7 +363,7 @@ export class ProxySessionController {
|
|
|
354
363
|
}));
|
|
355
364
|
message = {
|
|
356
365
|
...attachment,
|
|
357
|
-
text: instruction,
|
|
366
|
+
text: sourceText ? referencedMessagePrompt(instruction, sourceText) : instruction,
|
|
358
367
|
displayText: instruction,
|
|
359
368
|
};
|
|
360
369
|
this.relatedReactionMessageIds.set(message.messageId, [resourceMessage.messageId]);
|
|
@@ -623,7 +632,17 @@ export class ProxySessionController {
|
|
|
623
632
|
await this.outbound.withReplyTarget(contextKey, replyTarget, async () => {
|
|
624
633
|
try {
|
|
625
634
|
const kind = String(scopedAction.value.action ?? "");
|
|
626
|
-
if (kind === "
|
|
635
|
+
if (kind === "terminal_authorize") {
|
|
636
|
+
if (!this.lifecycle?.authorizeTerminal)
|
|
637
|
+
throw new Error("网页终端未启用。");
|
|
638
|
+
await this.outbound.sendText(contextKey, await this.lifecycle.authorizeTerminal(scopedAction));
|
|
639
|
+
}
|
|
640
|
+
else if (kind === "safe_restart_terminal_stop") {
|
|
641
|
+
if (!this.lifecycle?.stopSafeRestartTerminal)
|
|
642
|
+
throw new Error("网页终端未启用。");
|
|
643
|
+
await this.outbound.sendText(contextKey, await this.lifecycle.stopSafeRestartTerminal(scopedAction));
|
|
644
|
+
}
|
|
645
|
+
else if (kind === "agentbot_update_cancel") {
|
|
627
646
|
if (!this.lifecycle?.cancelAutomaticUpdate)
|
|
628
647
|
throw new Error("自动更新功能未启用。");
|
|
629
648
|
await this.lifecycle.cancelAutomaticUpdate(scopedAction);
|
|
@@ -842,9 +861,11 @@ export class ProxySessionController {
|
|
|
842
861
|
await this.refreshStatusCardFromAction(scopedAction);
|
|
843
862
|
}
|
|
844
863
|
else if (kind === "settings_tab_open") {
|
|
864
|
+
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
845
865
|
await this.openExecutionSettings(contextKey, executionSettingsTabValue(scopedAction.value.tab), {
|
|
846
|
-
sessionId:
|
|
847
|
-
|
|
866
|
+
sessionId, updateMessageId: scopedAction.messageId,
|
|
867
|
+
modelPage: modelCardPageValue(scopedAction.value.page),
|
|
868
|
+
expectedProvider: typeof scopedAction.value.provider === "string" ? scopedAction.value.provider : undefined,
|
|
848
869
|
});
|
|
849
870
|
}
|
|
850
871
|
else if (kind === "settings_agent_select") {
|
|
@@ -861,12 +882,14 @@ export class ProxySessionController {
|
|
|
861
882
|
}
|
|
862
883
|
else if (kind === "settings_model_select") {
|
|
863
884
|
await this.model(contextKey, String(scopedAction.value.model ?? ""), {
|
|
885
|
+
expectedProvider: String(scopedAction.value.provider ?? ""),
|
|
864
886
|
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
865
887
|
updateMessageId: scopedAction.messageId,
|
|
866
888
|
});
|
|
867
889
|
}
|
|
868
890
|
else if (kind === "settings_thinking_select") {
|
|
869
891
|
await this.thinking(contextKey, String(scopedAction.value.effort ?? ""), {
|
|
892
|
+
expectedProvider: String(scopedAction.value.provider ?? ""),
|
|
870
893
|
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
871
894
|
updateMessageId: scopedAction.messageId,
|
|
872
895
|
expectedModel: String(scopedAction.value.model ?? ""),
|
|
@@ -891,6 +914,7 @@ export class ProxySessionController {
|
|
|
891
914
|
await this.model(contextKey, undefined, {
|
|
892
915
|
sessionId,
|
|
893
916
|
updateMessageId: scopedAction.messageId,
|
|
917
|
+
modelPage: modelCardPageValue(scopedAction.value.page),
|
|
894
918
|
});
|
|
895
919
|
}
|
|
896
920
|
else if (kind === "reasoning_select") {
|
|
@@ -910,7 +934,7 @@ export class ProxySessionController {
|
|
|
910
934
|
});
|
|
911
935
|
}
|
|
912
936
|
else if (kind === "provider_select" || kind === "provider_model_open") {
|
|
913
|
-
await this.openProviderModelSelector(contextKey, String(scopedAction.value.provider ?? ""), permissionModeValue(scopedAction.value.permissionMode), String(scopedAction.value.sessionId ?? ""), requiredCardMessageId(scopedAction.messageId));
|
|
937
|
+
await this.openProviderModelSelector(contextKey, String(scopedAction.value.provider ?? ""), permissionModeValue(scopedAction.value.permissionMode), String(scopedAction.value.sessionId ?? ""), requiredCardMessageId(scopedAction.messageId), modelCardPageValue(scopedAction.value.page));
|
|
914
938
|
}
|
|
915
939
|
else if (kind === "provider_model_select" || kind === "provider_reasoning_open") {
|
|
916
940
|
await this.openProviderReasoningSelector(contextKey, String(scopedAction.value.provider ?? ""), String(scopedAction.value.model ?? ""), permissionModeValue(scopedAction.value.permissionMode), String(scopedAction.value.sessionId ?? ""), requiredCardMessageId(scopedAction.messageId));
|
|
@@ -965,6 +989,7 @@ export class ProxySessionController {
|
|
|
965
989
|
this.queuedPromptCards.clear();
|
|
966
990
|
this.queuedPromptCardWrites.clear();
|
|
967
991
|
this.sessionResets.clear();
|
|
992
|
+
this.planHandoffs.clear();
|
|
968
993
|
this.resetHistoryOperations.clear();
|
|
969
994
|
this.remoteTurnHistoryRefreshes.clear();
|
|
970
995
|
this.forkHistoryCursors.clear();
|
|
@@ -1270,78 +1295,43 @@ export class ProxySessionController {
|
|
|
1270
1295
|
if (setting && !value?.trim())
|
|
1271
1296
|
throw new Error(`task ${setting} requires a value.`);
|
|
1272
1297
|
const nextValue = value?.trim();
|
|
1273
|
-
if (setting
|
|
1298
|
+
if (setting && nextValue) {
|
|
1274
1299
|
const contextKey = this.controlSessionContextKey(record);
|
|
1275
|
-
await this.outbound.withReplyTarget(contextKey, this.controlSessionReplyTarget(record), () =>
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
reasoningEffort: effort,
|
|
1289
|
-
permissionMode: loaded.session.permissionMode,
|
|
1300
|
+
await this.outbound.withReplyTarget(contextKey, this.controlSessionReplyTarget(record), async () => {
|
|
1301
|
+
const options = { sessionId: record.localSessionId };
|
|
1302
|
+
if (setting === "provider")
|
|
1303
|
+
await this.selectProvider(contextKey, nextValue, options);
|
|
1304
|
+
else if (setting === "model")
|
|
1305
|
+
await this.model(contextKey, nextValue, options);
|
|
1306
|
+
else if (setting === "thinking")
|
|
1307
|
+
await this.thinking(contextKey, nextValue, options);
|
|
1308
|
+
else {
|
|
1309
|
+
if (nextValue !== "auto" && nextValue !== "confirm")
|
|
1310
|
+
throw new Error("task permissions accepts auto or confirm.");
|
|
1311
|
+
await this.permissions(contextKey, nextValue, options);
|
|
1312
|
+
}
|
|
1290
1313
|
});
|
|
1291
1314
|
}
|
|
1292
|
-
|
|
1293
|
-
const
|
|
1294
|
-
const
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
const
|
|
1298
|
-
const
|
|
1299
|
-
|| selected.supportedReasoningEfforts.some((candidate) => candidate.value === currentEffort))
|
|
1300
|
-
? currentEffort
|
|
1301
|
-
: selected.defaultReasoningEffort;
|
|
1302
|
-
await loaded.runtime.setModel(record.localSessionId, nextValue);
|
|
1303
|
-
if (nextEffort && nextEffort !== currentEffort) {
|
|
1304
|
-
await loaded.runtime.setReasoningEffort(record.localSessionId, nextEffort);
|
|
1305
|
-
}
|
|
1306
|
-
this.store.updateRuntimeSession(record.localSessionId, { model: nextValue, reasoningEffort: nextEffort });
|
|
1307
|
-
this.outbound.updateSessionModel(record.localSessionId, nextValue, loaded.session.modelProvider);
|
|
1308
|
-
}
|
|
1309
|
-
else if (setting === "thinking" && nextValue) {
|
|
1310
|
-
const models = await loaded.runtime.listModels(loaded.session.modelProvider, loaded.session.model);
|
|
1311
|
-
const currentModel = models.find((candidate) => candidate.id === loaded.session.model)
|
|
1315
|
+
return this.settingsOperations.run(localSessionId, async () => {
|
|
1316
|
+
const loaded = await this.loadSession(this.store.getSession(localSessionId) ?? record, true);
|
|
1317
|
+
const session = this.store.getSession(record.localSessionId) ?? record;
|
|
1318
|
+
const pending = this.store.getPendingProviderSettings(record.localSessionId);
|
|
1319
|
+
const selected = pending?.settings ?? session;
|
|
1320
|
+
const models = await loaded.runtime.listModels(selected.modelProvider, selected.model);
|
|
1321
|
+
const currentModel = models.find((candidate) => candidate.id === selected.model)
|
|
1312
1322
|
?? models.find((candidate) => candidate.isDefault);
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
}
|
|
1326
|
-
await loaded.runtime.setPermissionMode(record.localSessionId, nextValue);
|
|
1327
|
-
this.store.updateRuntimeSession(record.localSessionId, { permissionMode: nextValue });
|
|
1328
|
-
}
|
|
1329
|
-
const session = this.store.getSession(record.localSessionId) ?? record;
|
|
1330
|
-
if (setting && setting !== "provider")
|
|
1331
|
-
await this.persistAgentExecutionDefaults(session.localSessionId);
|
|
1332
|
-
const models = await loaded.runtime.listModels(session.modelProvider, session.model);
|
|
1333
|
-
const currentModel = models.find((candidate) => candidate.id === session.model)
|
|
1334
|
-
?? models.find((candidate) => candidate.isDefault);
|
|
1335
|
-
const providers = loaded.runtime.listModelProviders
|
|
1336
|
-
? await this.modelProviderOptions(loaded)
|
|
1337
|
-
: [];
|
|
1338
|
-
return {
|
|
1339
|
-
session,
|
|
1340
|
-
providers,
|
|
1341
|
-
models,
|
|
1342
|
-
reasoningOptions: currentModel?.supportedReasoningEfforts ?? [],
|
|
1343
|
-
permissionModes: ["auto", "confirm"],
|
|
1344
|
-
};
|
|
1323
|
+
const providers = loaded.runtime.listModelProviders
|
|
1324
|
+
? await this.modelProviderOptions(loaded)
|
|
1325
|
+
: [];
|
|
1326
|
+
return {
|
|
1327
|
+
session,
|
|
1328
|
+
pendingProvider: pending,
|
|
1329
|
+
providers,
|
|
1330
|
+
models,
|
|
1331
|
+
reasoningOptions: currentModel?.supportedReasoningEfforts ?? [],
|
|
1332
|
+
permissionModes: ["auto", "confirm"],
|
|
1333
|
+
};
|
|
1334
|
+
});
|
|
1345
1335
|
}
|
|
1346
1336
|
async controlTaskGoal(localSessionId, action, objective) {
|
|
1347
1337
|
const record = this.requireControlSession(localSessionId);
|
|
@@ -1746,6 +1736,9 @@ export class ProxySessionController {
|
|
|
1746
1736
|
this.recoveryRetryStartedAt.delete(attempt.attemptId);
|
|
1747
1737
|
}
|
|
1748
1738
|
}
|
|
1739
|
+
for (const pending of this.store.listPendingProviderSettings()) {
|
|
1740
|
+
await this.applyPendingProviderWhenIdle(pending.localSessionId);
|
|
1741
|
+
}
|
|
1749
1742
|
this.restorePersistedQueuedPrompts();
|
|
1750
1743
|
}
|
|
1751
1744
|
async finishPersistedCancellationRequests() {
|
|
@@ -2051,6 +2044,17 @@ export class ProxySessionController {
|
|
|
2051
2044
|
const pendingAttempt = this.store.getTurnAttempt(attempt.attemptId);
|
|
2052
2045
|
if (!pendingAttempt || !isIncompleteTurnAttemptStatus(pendingAttempt.status))
|
|
2053
2046
|
return;
|
|
2047
|
+
const continued = turnViewSnapshot(this.store.getTurnSnapshot(turnId));
|
|
2048
|
+
if (continued?.continuedFromTurnId && this.store.getTurnDelivery(turnId)?.progressMessageId) {
|
|
2049
|
+
this.store.updateTurnAttempt(attempt.attemptId, { pendingTurnId: null, turnId, status: "running" });
|
|
2050
|
+
await this.outbound.onEvent({ type: "turn_started", sessionId: session.localSessionId,
|
|
2051
|
+
turnId, startedAt: continued.startedAt });
|
|
2052
|
+
const synchronized = await loaded.runtime.synchronizeSession(session.localSessionId);
|
|
2053
|
+
this.recoveryRetryStartedAt.delete(attempt.attemptId);
|
|
2054
|
+
if (synchronized.activeTurnId)
|
|
2055
|
+
this.recoveredLiveAttempts.add(attempt.attemptId);
|
|
2056
|
+
return;
|
|
2057
|
+
}
|
|
2054
2058
|
const oldCardTurnId = attempt.pendingTurnId ?? attempt.turnId;
|
|
2055
2059
|
if (oldCardTurnId) {
|
|
2056
2060
|
await this.outbound.interruptTurnForRecovery(session.localSessionId, attempt.contextKey, oldCardTurnId, "Agent Bot 已重启,执行仍在继续;进度已转移到新的思考卡片。");
|
|
@@ -2091,7 +2095,7 @@ export class ProxySessionController {
|
|
|
2091
2095
|
});
|
|
2092
2096
|
}
|
|
2093
2097
|
const current = this.store.getSession(session.localSessionId) ?? session;
|
|
2094
|
-
const loaded = await this.loadSession({ ...current, contextKey: attempt.contextKey });
|
|
2098
|
+
const loaded = await this.loadSession({ ...current, contextKey: attempt.contextKey }, Boolean(this.store.getPendingProviderSettings(current.localSessionId)));
|
|
2095
2099
|
const recoveryText = recoveryRuntimePrompt(attempt.promptText);
|
|
2096
2100
|
const turnId = await this.startTurn(loaded, recoveryText, replyTarget, attempt.localImagePaths, {
|
|
2097
2101
|
attemptId: attempt.attemptId,
|
|
@@ -2130,7 +2134,7 @@ export class ProxySessionController {
|
|
|
2130
2134
|
await this.promptSession(record, contextKey, text, messageId, replyTarget, localImagePaths, displayPrompt);
|
|
2131
2135
|
}
|
|
2132
2136
|
async promptSession(record, contextKey, text, messageId, replyTarget, localImagePaths, displayPrompt) {
|
|
2133
|
-
if (this.llmRetryingSessions.has(record.localSessionId)) {
|
|
2137
|
+
if (this.llmRetryingSessions.has(record.localSessionId) || this.planHandoffs.has(record.localSessionId)) {
|
|
2134
2138
|
const queued = this.persistQueuedPrompt(record.localSessionId, contextKey, text, {
|
|
2135
2139
|
localImagePaths,
|
|
2136
2140
|
messageId,
|
|
@@ -2151,7 +2155,7 @@ export class ProxySessionController {
|
|
|
2151
2155
|
}
|
|
2152
2156
|
let loaded;
|
|
2153
2157
|
try {
|
|
2154
|
-
loaded = await this.loadSession(record);
|
|
2158
|
+
loaded = await this.loadSession(record, Boolean(this.store.getPendingProviderSettings(record.localSessionId)));
|
|
2155
2159
|
}
|
|
2156
2160
|
catch (error) {
|
|
2157
2161
|
if (await this.presentThreadWriterConflict(record, contextKey, error, true)) {
|
|
@@ -3127,6 +3131,22 @@ export class ProxySessionController {
|
|
|
3127
3131
|
};
|
|
3128
3132
|
}
|
|
3129
3133
|
async startTurn(loaded, text, replyTarget, localImagePaths, options = {}) {
|
|
3134
|
+
return this.settingsOperations.run(loaded.record.localSessionId, async () => {
|
|
3135
|
+
try {
|
|
3136
|
+
await this.applyPendingProviderSettings(loaded);
|
|
3137
|
+
}
|
|
3138
|
+
catch (error) {
|
|
3139
|
+
await this.outbound.failPendingTurn(loaded.record.localSessionId, runtimeErrorMessage(error));
|
|
3140
|
+
throw error;
|
|
3141
|
+
}
|
|
3142
|
+
if (this.store.getPendingProviderSettings(loaded.record.localSessionId)) {
|
|
3143
|
+
throw new Error("Provider 切换尚未完成,请稍后重试。");
|
|
3144
|
+
}
|
|
3145
|
+
loaded.session = loaded.runtime.getSession(loaded.record.localSessionId) ?? loaded.session;
|
|
3146
|
+
return this.startTurnNow(loaded, text, replyTarget, localImagePaths, options);
|
|
3147
|
+
});
|
|
3148
|
+
}
|
|
3149
|
+
async startTurnNow(loaded, text, replyTarget, localImagePaths, options = {}) {
|
|
3130
3150
|
const currentRecord = this.store.getSession(loaded.record.localSessionId);
|
|
3131
3151
|
const title = currentRecord?.title ?? normalizeTaskTitle(options.displayPrompt ?? text);
|
|
3132
3152
|
if (!currentRecord?.title && title)
|
|
@@ -3172,7 +3192,13 @@ export class ProxySessionController {
|
|
|
3172
3192
|
}
|
|
3173
3193
|
this.store.updateTurnAttempt(attemptId, { pendingTurnId: pendingTurnId ?? null });
|
|
3174
3194
|
try {
|
|
3175
|
-
|
|
3195
|
+
if (options.approvedPlanTurnId && (this.planHandoffs.get(loaded.record.localSessionId) !== options.approvedPlanTurnId
|
|
3196
|
+
|| this.sessionResets.has(loaded.record.localSessionId))) {
|
|
3197
|
+
throw new Error("方案执行已取消。");
|
|
3198
|
+
}
|
|
3199
|
+
turnId = await loaded.runtime.startTurn(loaded.record.localSessionId, options.approvedPlanTurnId
|
|
3200
|
+
? { text: runtimeText, collaborationMode: "default" }
|
|
3201
|
+
: runtimePrompt(runtimeText, localImagePaths));
|
|
3176
3202
|
break;
|
|
3177
3203
|
}
|
|
3178
3204
|
catch (error) {
|
|
@@ -3587,22 +3613,44 @@ export class ProxySessionController {
|
|
|
3587
3613
|
this.store.setBoundProjectCwd(groupContextKey, boundProjectCwd);
|
|
3588
3614
|
return { chatId: group.chatId, contextKey: groupContextKey, name: group.name };
|
|
3589
3615
|
}
|
|
3590
|
-
async loadSession(record) {
|
|
3616
|
+
async loadSession(record, forSettings = false) {
|
|
3591
3617
|
const agent = this.ensureAgent(record.agentName);
|
|
3592
3618
|
const runtime = this.runtimes.forAgent(record.agentName);
|
|
3593
3619
|
const existing = runtime.getSession(record.localSessionId);
|
|
3594
|
-
if (existing)
|
|
3620
|
+
if (existing && (forSettings || !this.settingsOnlySessions.has(record.localSessionId))) {
|
|
3595
3621
|
return { record, runtime, session: existing };
|
|
3622
|
+
}
|
|
3596
3623
|
const pending = this.sessionLoads.get(record.localSessionId);
|
|
3597
3624
|
if (pending) {
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3625
|
+
try {
|
|
3626
|
+
const loaded = await pending;
|
|
3627
|
+
if (!forSettings && this.settingsOnlySessions.has(record.localSessionId)) {
|
|
3628
|
+
if (this.sessionLoads.get(record.localSessionId) === pending)
|
|
3629
|
+
this.sessionLoads.delete(record.localSessionId);
|
|
3630
|
+
return this.loadSession(this.store.getSession(record.localSessionId) ?? record);
|
|
3631
|
+
}
|
|
3632
|
+
return { ...loaded, record: { ...loaded.record, contextKey: record.contextKey } };
|
|
3633
|
+
}
|
|
3634
|
+
catch (error) {
|
|
3635
|
+
if (!forSettings || !runtime.prepareSessionForSettings)
|
|
3636
|
+
throw error;
|
|
3637
|
+
if (this.sessionLoads.get(record.localSessionId) === pending)
|
|
3638
|
+
this.sessionLoads.delete(record.localSessionId);
|
|
3639
|
+
return this.loadSession(record, forSettings);
|
|
3640
|
+
}
|
|
3603
3641
|
}
|
|
3604
3642
|
this.outbound.registerSession(record.localSessionId, record.contextKey, record.title, record.cwd, this.agentLabel(record.agentName), record.model, record.modelProvider);
|
|
3605
|
-
const loading = (async () => {
|
|
3643
|
+
const loading = this.sessionHydrationOperations.run(record.localSessionId, async () => {
|
|
3644
|
+
const latest = this.store.getSession(record.localSessionId);
|
|
3645
|
+
if (latest)
|
|
3646
|
+
record = {
|
|
3647
|
+
...record, modelProvider: latest.modelProvider, model: latest.model,
|
|
3648
|
+
reasoningEffort: latest.reasoningEffort, permissionMode: latest.permissionMode,
|
|
3649
|
+
};
|
|
3650
|
+
const current = runtime.getSession(record.localSessionId);
|
|
3651
|
+
if (current && (forSettings || !this.settingsOnlySessions.has(record.localSessionId))) {
|
|
3652
|
+
return { record, runtime, session: current };
|
|
3653
|
+
}
|
|
3606
3654
|
if (record.lastTurnId) {
|
|
3607
3655
|
try {
|
|
3608
3656
|
await this.outbound.resumeDelivery(record.localSessionId, record.contextKey, record.lastTurnId);
|
|
@@ -3612,45 +3660,32 @@ export class ProxySessionController {
|
|
|
3612
3660
|
}
|
|
3613
3661
|
}
|
|
3614
3662
|
const permissionMode = record.permissionMode ?? "auto";
|
|
3615
|
-
|
|
3663
|
+
const prepare = forSettings && record.remoteSessionId && runtime.prepareSessionForSettings;
|
|
3664
|
+
if (record.remoteSessionId && !prepare)
|
|
3616
3665
|
await this.assertSessionTurnOwnership(record, runtime);
|
|
3617
3666
|
let session;
|
|
3618
3667
|
if (record.remoteSessionId) {
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
if (!(agent.kind === "app-server" && !record.lastTurnId
|
|
3641
|
-
&& !this.store.getForkHistorySource(record.localSessionId) && isMissingRolloutError(error)))
|
|
3642
|
-
throw error;
|
|
3643
|
-
this.logger.warn({ error, sessionId: record.localSessionId }, "App Server task has no rollout; creating a replacement task.");
|
|
3644
|
-
session = await runtime.createSession({
|
|
3645
|
-
localSessionId: record.localSessionId,
|
|
3646
|
-
agentName: record.agentName,
|
|
3647
|
-
cwd: record.cwd,
|
|
3648
|
-
title: record.title,
|
|
3649
|
-
modelProvider: record.modelProvider,
|
|
3650
|
-
model: record.model,
|
|
3651
|
-
reasoningEffort: record.reasoningEffort,
|
|
3652
|
-
permissionMode,
|
|
3653
|
-
});
|
|
3668
|
+
const input = {
|
|
3669
|
+
localSessionId: record.localSessionId,
|
|
3670
|
+
remoteSessionId: record.remoteSessionId,
|
|
3671
|
+
agentName: record.agentName,
|
|
3672
|
+
cwd: record.cwd,
|
|
3673
|
+
title: record.title,
|
|
3674
|
+
modelProvider: record.modelProvider,
|
|
3675
|
+
model: record.model,
|
|
3676
|
+
reasoningEffort: record.reasoningEffort,
|
|
3677
|
+
permissionMode,
|
|
3678
|
+
activeTurnId: agent.kind === "app-server" && record.status === "running" && record.lastTurnStatus === "running"
|
|
3679
|
+
? record.lastTurnId
|
|
3680
|
+
: undefined,
|
|
3681
|
+
lastTurnId: record.lastTurnId,
|
|
3682
|
+
lastTurnStatus: record.lastTurnStatus,
|
|
3683
|
+
};
|
|
3684
|
+
if (prepare)
|
|
3685
|
+
this.settingsOnlySessions.add(record.localSessionId);
|
|
3686
|
+
session = prepare ? await prepare.call(runtime, input) : await runtime.resumeSession(input);
|
|
3687
|
+
if (session.remoteSessionId !== record.remoteSessionId) {
|
|
3688
|
+
throw new Error("加载设置返回了不同的任务 ID,不允许替换任务或更改历史。");
|
|
3654
3689
|
}
|
|
3655
3690
|
}
|
|
3656
3691
|
else {
|
|
@@ -3665,20 +3700,24 @@ export class ProxySessionController {
|
|
|
3665
3700
|
permissionMode,
|
|
3666
3701
|
});
|
|
3667
3702
|
}
|
|
3668
|
-
|
|
3703
|
+
if (!prepare) {
|
|
3704
|
+
this.settingsOnlySessions.delete(record.localSessionId);
|
|
3705
|
+
this.persistRuntimeSession(record, session, session.activeTurnId ? "running" : "ready");
|
|
3706
|
+
}
|
|
3669
3707
|
const saved = this.store.getSession(record.localSessionId) ?? record;
|
|
3670
3708
|
return {
|
|
3671
3709
|
record: { ...saved, contextKey: record.contextKey },
|
|
3672
3710
|
runtime,
|
|
3673
3711
|
session,
|
|
3674
3712
|
};
|
|
3675
|
-
})
|
|
3713
|
+
});
|
|
3676
3714
|
this.sessionLoads.set(record.localSessionId, loading);
|
|
3677
3715
|
try {
|
|
3678
3716
|
return await loading;
|
|
3679
3717
|
}
|
|
3680
3718
|
finally {
|
|
3681
|
-
this.sessionLoads.
|
|
3719
|
+
if (this.sessionLoads.get(record.localSessionId) === loading)
|
|
3720
|
+
this.sessionLoads.delete(record.localSessionId);
|
|
3682
3721
|
}
|
|
3683
3722
|
}
|
|
3684
3723
|
persistRuntimeSession(record, session, status) {
|
|
@@ -3716,6 +3755,15 @@ export class ProxySessionController {
|
|
|
3716
3755
|
this.store.saveTurnParent(event.turnId, event.sessionId, this.completedParentTurnId(source));
|
|
3717
3756
|
}
|
|
3718
3757
|
}
|
|
3758
|
+
if (event.type === "turn_completed" && event.approvedPlan) {
|
|
3759
|
+
if (this.planHandoffs.get(event.sessionId) === event.turnId)
|
|
3760
|
+
return;
|
|
3761
|
+
const attempt = this.store.findIncompleteTurnAttemptByTurnId(event.turnId);
|
|
3762
|
+
if (attempt) {
|
|
3763
|
+
await this.continueApprovedPlan(event, attempt);
|
|
3764
|
+
return;
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3719
3767
|
const failedAttempt = event.type === "turn_failed"
|
|
3720
3768
|
? this.store.findIncompleteTurnAttemptByTurnId(event.turnId)
|
|
3721
3769
|
: undefined;
|
|
@@ -3759,7 +3807,7 @@ export class ProxySessionController {
|
|
|
3759
3807
|
...event,
|
|
3760
3808
|
turnCount: event.turnCount ?? this.store.countTaskTurns(event.sessionId),
|
|
3761
3809
|
}
|
|
3762
|
-
: event;
|
|
3810
|
+
: event.type === "turn_completed" && event.approvedPlan ? { ...event, approvedPlan: false } : event;
|
|
3763
3811
|
const presentationEvent = event.type === "turn_failed"
|
|
3764
3812
|
&& isRetryableLlmTurnFailure(event.message)
|
|
3765
3813
|
&& (failedAttempt?.retryCount ?? 0) >= MAX_LLM_TURN_RETRIES
|
|
@@ -3794,12 +3842,87 @@ export class ProxySessionController {
|
|
|
3794
3842
|
: undefined;
|
|
3795
3843
|
if (latest?.lastTurnId === event.turnId && !activeTurnId) {
|
|
3796
3844
|
this.store.updateSession(event.sessionId, { status: event.type === "turn_failed" ? "failed" : "ready" });
|
|
3845
|
+
await this.applyPendingProviderWhenIdle(event.sessionId);
|
|
3797
3846
|
await this.scheduleNextQueuedPrompt(event.sessionId);
|
|
3798
3847
|
}
|
|
3799
3848
|
}
|
|
3800
3849
|
if (presentationError)
|
|
3801
3850
|
throw presentationError;
|
|
3802
3851
|
}
|
|
3852
|
+
async continueApprovedPlan(event, attempt) {
|
|
3853
|
+
const { sessionId, turnId } = event;
|
|
3854
|
+
const source = this.store.getSession(sessionId);
|
|
3855
|
+
if (!source || source.status === "closed" || source.lastTurnId !== turnId)
|
|
3856
|
+
return;
|
|
3857
|
+
this.planHandoffs.set(sessionId, turnId);
|
|
3858
|
+
this.store.markTurnAttemptTerminal(turnId, "completed");
|
|
3859
|
+
this.store.updateRuntimeSession(sessionId, { lastTurnId: turnId, lastTurnStatus: "completed" });
|
|
3860
|
+
let continued = false;
|
|
3861
|
+
let failed = false;
|
|
3862
|
+
try {
|
|
3863
|
+
try {
|
|
3864
|
+
await this.outbound.onEvent(event);
|
|
3865
|
+
}
|
|
3866
|
+
catch (error) {
|
|
3867
|
+
this.logger.warn({ error, sessionId, turnId }, "Failed to finalize the approved plan card.");
|
|
3868
|
+
}
|
|
3869
|
+
await this.settingsOperations.run(sessionId, async () => {
|
|
3870
|
+
const current = this.store.getSessionForContext(sessionId, attempt.contextKey);
|
|
3871
|
+
if (this.planHandoffs.get(sessionId) !== turnId || this.sessionResets.has(sessionId)
|
|
3872
|
+
|| !current || current.status === "closed" || current.remoteSessionId !== source.remoteSessionId)
|
|
3873
|
+
return;
|
|
3874
|
+
const loaded = await this.loadSession(current);
|
|
3875
|
+
await this.assertSessionTurnOwnership(current, loaded.runtime);
|
|
3876
|
+
if (this.planHandoffs.get(sessionId) !== turnId || this.sessionResets.has(sessionId))
|
|
3877
|
+
return;
|
|
3878
|
+
// Another client may already have started the implementation.
|
|
3879
|
+
let nextTurnId = loaded.session.activeTurnId;
|
|
3880
|
+
if (!nextTurnId) {
|
|
3881
|
+
if (current.lastTurnId !== turnId)
|
|
3882
|
+
return;
|
|
3883
|
+
await this.applyPendingProviderSettings(loaded);
|
|
3884
|
+
if (this.store.getPendingProviderSettings(sessionId))
|
|
3885
|
+
throw new Error("Provider 切换尚未完成。");
|
|
3886
|
+
if (this.planHandoffs.get(sessionId) !== turnId || this.sessionResets.has(sessionId))
|
|
3887
|
+
return;
|
|
3888
|
+
nextTurnId = await this.startTurnNow(loaded, "The proposed plan has been approved. Continue in the current task and workspace without asking for plan approval again; preserve the existing conversation and completed work. Implement the plan unless the user explicitly requested discussion or planning only, or prohibited implementation; those constraints still apply.", attempt.replyMessageId ? { messageId: attempt.replyMessageId, replyInThread: true } : undefined, undefined, { messageId: attempt.messageId, displayPrompt: "方案已确认,继续执行", approvedPlanTurnId: turnId });
|
|
3889
|
+
}
|
|
3890
|
+
continued = true;
|
|
3891
|
+
this.store.rebindPendingTurnMessages(sessionId, turnId, nextTurnId);
|
|
3892
|
+
const latest = this.store.getSession(sessionId);
|
|
3893
|
+
if (latest?.lastTurnId === nextTurnId && (latest.lastTurnStatus === "completed"
|
|
3894
|
+
|| latest.lastTurnStatus === "failed" || latest.lastTurnStatus === "cancelled")) {
|
|
3895
|
+
await this.finalizeTurnMessageReactions(nextTurnId, latest.lastTurnStatus);
|
|
3896
|
+
}
|
|
3897
|
+
this.store.audit(attempt.contextKey, "approved_plan_continued", { sessionId, turnId, nextTurnId });
|
|
3898
|
+
});
|
|
3899
|
+
}
|
|
3900
|
+
catch (error) {
|
|
3901
|
+
failed = true;
|
|
3902
|
+
this.logger.error({ error, sessionId, turnId }, "Failed to continue an approved plan.");
|
|
3903
|
+
await this.outbound.sendText(attempt.contextKey, "方案已确认,但后续执行启动失败:" + runtimeErrorMessage(error) + "。请发送消息继续。");
|
|
3904
|
+
}
|
|
3905
|
+
finally {
|
|
3906
|
+
if (this.planHandoffs.get(sessionId) === turnId)
|
|
3907
|
+
this.planHandoffs.delete(sessionId);
|
|
3908
|
+
if (!continued) {
|
|
3909
|
+
try {
|
|
3910
|
+
await this.outbound.onEvent(failed
|
|
3911
|
+
? { type: "turn_failed", sessionId, turnId, message: "方案已确认,但后续执行未能启动。" }
|
|
3912
|
+
: { type: "turn_cancelled", sessionId, turnId });
|
|
3913
|
+
}
|
|
3914
|
+
catch (error) {
|
|
3915
|
+
this.logger.warn({ error, sessionId, turnId }, "Failed to finish the plan handoff card.");
|
|
3916
|
+
}
|
|
3917
|
+
await this.finalizeTurnMessageReactions(turnId, failed ? "failed" : "cancelled");
|
|
3918
|
+
}
|
|
3919
|
+
const latest = this.store.getSession(sessionId);
|
|
3920
|
+
if (latest?.lastTurnId === turnId && latest.status !== "closed") {
|
|
3921
|
+
this.store.updateSession(sessionId, { status: failed ? "failed" : "ready" });
|
|
3922
|
+
}
|
|
3923
|
+
await this.scheduleNextQueuedPrompt(sessionId);
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3803
3926
|
async retryFailedLlmTurn(event, attempt) {
|
|
3804
3927
|
const retryNumber = attempt.retryCount;
|
|
3805
3928
|
const session = this.store.getSession(event.sessionId);
|
|
@@ -3833,7 +3956,7 @@ export class ProxySessionController {
|
|
|
3833
3956
|
: undefined;
|
|
3834
3957
|
try {
|
|
3835
3958
|
const current = this.store.getSession(session.localSessionId) ?? session;
|
|
3836
|
-
const loaded = await this.loadSession(current);
|
|
3959
|
+
const loaded = await this.loadSession(current, Boolean(this.store.getPendingProviderSettings(current.localSessionId)));
|
|
3837
3960
|
const retryTurnId = await this.startTurn(loaded, llmRetryRuntimePrompt(attempt.promptText, retryNumber), replyTarget, attempt.localImagePaths, {
|
|
3838
3961
|
attemptId: attempt.attemptId,
|
|
3839
3962
|
messageId: attempt.messageId,
|
|
@@ -3870,6 +3993,8 @@ export class ProxySessionController {
|
|
|
3870
3993
|
});
|
|
3871
3994
|
}
|
|
3872
3995
|
scheduleQueuedPromptRetry(sessionId) {
|
|
3996
|
+
if (this.store.getPendingProviderSettings(sessionId)?.error)
|
|
3997
|
+
return;
|
|
3873
3998
|
if (this.queuedPromptRetryTimers.has(sessionId))
|
|
3874
3999
|
return;
|
|
3875
4000
|
if (this.store.countQueuedPrompts(sessionId) === 0)
|
|
@@ -3885,6 +4010,11 @@ export class ProxySessionController {
|
|
|
3885
4010
|
this.queuedPromptRetryTimers.set(sessionId, timer);
|
|
3886
4011
|
}
|
|
3887
4012
|
async startNextQueuedPromptIfIdle(sessionId) {
|
|
4013
|
+
return this.settingsOperations.run(sessionId, () => this.startNextQueuedPromptIfIdleNow(sessionId));
|
|
4014
|
+
}
|
|
4015
|
+
async startNextQueuedPromptIfIdleNow(sessionId) {
|
|
4016
|
+
if (this.planHandoffs.has(sessionId) || this.sessionResets.has(sessionId))
|
|
4017
|
+
return;
|
|
3888
4018
|
if (this.store.countQueuedPrompts(sessionId) === 0)
|
|
3889
4019
|
return;
|
|
3890
4020
|
const baseRecord = this.store.getSession(sessionId);
|
|
@@ -3892,20 +4022,23 @@ export class ProxySessionController {
|
|
|
3892
4022
|
return;
|
|
3893
4023
|
let prompt;
|
|
3894
4024
|
try {
|
|
3895
|
-
let loaded = await this.loadSession(baseRecord);
|
|
4025
|
+
let loaded = await this.loadSession(baseRecord, Boolean(this.store.getPendingProviderSettings(sessionId)));
|
|
3896
4026
|
const remoteActivity = await this.assertSessionTurnOwnership(loaded.record, loaded.runtime);
|
|
3897
4027
|
if (needsFullSessionSynchronization(loaded.record, loaded.session, remoteActivity)) {
|
|
3898
4028
|
loaded = { ...loaded, session: await loaded.runtime.synchronizeSession(sessionId) };
|
|
3899
4029
|
}
|
|
3900
4030
|
if (loaded.session.activeTurnId)
|
|
3901
4031
|
return;
|
|
4032
|
+
await this.applyPendingProviderSettings(loaded);
|
|
4033
|
+
if (this.store.getPendingProviderSettings(sessionId))
|
|
4034
|
+
return;
|
|
3902
4035
|
prompt = this.store.takeNextQueuedPrompt(sessionId);
|
|
3903
4036
|
if (!prompt)
|
|
3904
4037
|
return;
|
|
3905
4038
|
const record = this.store.getSessionForContext(sessionId, prompt.contextKey) ?? baseRecord;
|
|
3906
4039
|
loaded = { ...loaded, record };
|
|
3907
4040
|
await this.refreshPromptQueueCards(sessionId);
|
|
3908
|
-
const turnId = await this.
|
|
4041
|
+
const turnId = await this.startTurnNow(loaded, prompt.text, prompt.replyMessageId ? { messageId: prompt.replyMessageId, replyInThread: true } : undefined, prompt.localImagePaths, { messageId: prompt.messageId, displayPrompt: prompt.displayPrompt });
|
|
3909
4042
|
if (prompt.messageId)
|
|
3910
4043
|
await this.bindMessageReactionToTurn(prompt.messageId, sessionId, turnId);
|
|
3911
4044
|
}
|
|
@@ -3964,6 +4097,7 @@ export class ProxySessionController {
|
|
|
3964
4097
|
});
|
|
3965
4098
|
const pending = {
|
|
3966
4099
|
contextKey: message.contextKey,
|
|
4100
|
+
userId: message.userId,
|
|
3967
4101
|
attachmentPromise,
|
|
3968
4102
|
resolveAttachment,
|
|
3969
4103
|
};
|
|
@@ -3971,7 +4105,7 @@ export class ProxySessionController {
|
|
|
3971
4105
|
return pending;
|
|
3972
4106
|
}
|
|
3973
4107
|
reserveForwardAttachment(message) {
|
|
3974
|
-
if (!message.parentMessageId)
|
|
4108
|
+
if (!message.parentMessageId || isCommandMessage(message))
|
|
3975
4109
|
return undefined;
|
|
3976
4110
|
const candidates = [
|
|
3977
4111
|
{ sourceKind: "merged_forward", registry: this.pendingMergedForwards },
|
|
@@ -3979,7 +4113,8 @@ export class ProxySessionController {
|
|
|
3979
4113
|
];
|
|
3980
4114
|
for (const candidate of candidates) {
|
|
3981
4115
|
const pending = candidate.registry.get(message.parentMessageId);
|
|
3982
|
-
if (!pending || pending.contextKey !== message.contextKey
|
|
4116
|
+
if (!pending || pending.contextKey !== message.contextKey
|
|
4117
|
+
|| pending.userId !== message.userId || pending.attachmentMessageId)
|
|
3983
4118
|
continue;
|
|
3984
4119
|
pending.attachmentMessageId = message.messageId;
|
|
3985
4120
|
return {
|
|
@@ -4004,17 +4139,20 @@ export class ProxySessionController {
|
|
|
4004
4139
|
return true;
|
|
4005
4140
|
}
|
|
4006
4141
|
async waitForForwardAttachment(messageId, pending, registry) {
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4142
|
+
const timer = setTimeout(() => {
|
|
4143
|
+
// Arrival reserves the pair before acknowledgement. A slow Reaction must not split it.
|
|
4144
|
+
if (pending.attachmentMessageId)
|
|
4145
|
+
return;
|
|
4146
|
+
if (registry.get(messageId) === pending)
|
|
4147
|
+
registry.delete(messageId);
|
|
4148
|
+
pending.resolveAttachment(undefined);
|
|
4149
|
+
}, FORWARD_ATTACHMENT_WINDOW_MS);
|
|
4150
|
+
timer.unref?.();
|
|
4012
4151
|
try {
|
|
4013
|
-
return await
|
|
4152
|
+
return await pending.attachmentPromise;
|
|
4014
4153
|
}
|
|
4015
4154
|
finally {
|
|
4016
|
-
|
|
4017
|
-
clearTimeout(timer);
|
|
4155
|
+
clearTimeout(timer);
|
|
4018
4156
|
if (registry.get(messageId) === pending) {
|
|
4019
4157
|
registry.delete(messageId);
|
|
4020
4158
|
}
|
|
@@ -4075,6 +4213,7 @@ export class ProxySessionController {
|
|
|
4075
4213
|
await this.cancelSession(record);
|
|
4076
4214
|
}
|
|
4077
4215
|
async cancelSession(record) {
|
|
4216
|
+
this.planHandoffs.delete(record.localSessionId);
|
|
4078
4217
|
const runtime = this.runtimes.forAgent(record.agentName);
|
|
4079
4218
|
if (runtime.kind === "codex" && record.remoteSessionId && runtime.interruptRemoteTurn) {
|
|
4080
4219
|
if (runtime.getGoal && runtime.setGoal) {
|
|
@@ -4151,6 +4290,7 @@ export class ProxySessionController {
|
|
|
4151
4290
|
});
|
|
4152
4291
|
}
|
|
4153
4292
|
async runSessionReset(sessionId, operation) {
|
|
4293
|
+
this.planHandoffs.delete(sessionId);
|
|
4154
4294
|
const previous = this.sessionResets.get(sessionId) ?? Promise.resolve();
|
|
4155
4295
|
const reset = previous.catch(() => undefined).then(operation);
|
|
4156
4296
|
this.sessionResets.set(sessionId, reset);
|
|
@@ -4295,6 +4435,14 @@ export class ProxySessionController {
|
|
|
4295
4435
|
await loaded.runtime.respondToApproval(sessionId, requestId, decision);
|
|
4296
4436
|
}
|
|
4297
4437
|
async openExecutionSettings(contextKey, activeTab, options = {}) {
|
|
4438
|
+
const record = options.sessionId
|
|
4439
|
+
? this.requireSession(contextKey, options.sessionId)
|
|
4440
|
+
: this.currentSession(contextKey);
|
|
4441
|
+
if (!record)
|
|
4442
|
+
return this.openExecutionSettingsNow(contextKey, activeTab, options);
|
|
4443
|
+
return this.settingsOperations.run(record.localSessionId, () => this.openExecutionSettingsNow(contextKey, activeTab, { ...options, sessionId: record.localSessionId }));
|
|
4444
|
+
}
|
|
4445
|
+
async openExecutionSettingsNow(contextKey, activeTab, options = {}) {
|
|
4298
4446
|
const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent);
|
|
4299
4447
|
const record = options.sessionId
|
|
4300
4448
|
? this.requireSession(contextKey, options.sessionId)
|
|
@@ -4313,22 +4461,31 @@ export class ProxySessionController {
|
|
|
4313
4461
|
reasoningOptions: [],
|
|
4314
4462
|
};
|
|
4315
4463
|
if (record) {
|
|
4316
|
-
const loaded = await this.loadSession(record);
|
|
4464
|
+
const loaded = await this.loadSession(record, true);
|
|
4465
|
+
const pending = this.store.getPendingProviderSettings(record.localSessionId);
|
|
4466
|
+
const selected = pending?.settings ?? loaded.session;
|
|
4467
|
+
if (pending && options.updateMessageId) {
|
|
4468
|
+
this.store.savePendingProviderSettings({
|
|
4469
|
+
...pending, contextKey, messageId: options.updateMessageId, replyMessageId: options.updateMessageId, activeTab,
|
|
4470
|
+
});
|
|
4471
|
+
}
|
|
4317
4472
|
const providerSupported = loaded.runtime.kind === "codex" && Boolean(loaded.runtime.listModelProviders);
|
|
4318
4473
|
const providers = providerSupported ? await this.modelProviderOptions(loaded) : [];
|
|
4319
|
-
const currentProvider =
|
|
4320
|
-
const { models, warning } = await this.modelOptions(loaded.runtime, currentProvider,
|
|
4321
|
-
const currentModel = models.find((model) => model.id ===
|
|
4474
|
+
const currentProvider = selected.modelProvider ?? providers.find((provider) => provider.isDefault)?.id;
|
|
4475
|
+
const { models, warning } = await this.modelOptions(loaded.runtime, currentProvider, selected.model);
|
|
4476
|
+
const currentModel = models.find((model) => model.id === selected.model)
|
|
4322
4477
|
?? models.find((model) => model.isDefault)
|
|
4323
4478
|
?? models[0];
|
|
4324
|
-
const currentEffort = currentModel?.supportedReasoningEfforts.some((option) => option.value ===
|
|
4325
|
-
?
|
|
4326
|
-
: currentModel?.defaultReasoningEffort ??
|
|
4479
|
+
const currentEffort = currentModel?.supportedReasoningEfforts.some((option) => option.value === selected.reasoningEffort)
|
|
4480
|
+
? selected.reasoningEffort
|
|
4481
|
+
: currentModel?.defaultReasoningEffort ?? selected.reasoningEffort;
|
|
4327
4482
|
runtimeSettings = {
|
|
4483
|
+
pendingNotice: pending ? this.pendingProviderNotice(pending) : undefined,
|
|
4484
|
+
pendingFailed: Boolean(pending?.error),
|
|
4328
4485
|
currentProvider,
|
|
4329
|
-
currentModel: currentModel?.id ??
|
|
4486
|
+
currentModel: pending?.settings.model ?? currentModel?.id ?? selected.model,
|
|
4330
4487
|
currentEffort,
|
|
4331
|
-
currentPermissionMode:
|
|
4488
|
+
currentPermissionMode: selected.permissionMode,
|
|
4332
4489
|
providers,
|
|
4333
4490
|
providerSupported,
|
|
4334
4491
|
models,
|
|
@@ -4344,7 +4501,10 @@ export class ProxySessionController {
|
|
|
4344
4501
|
agents,
|
|
4345
4502
|
runtimeSettingsAvailable: Boolean(record),
|
|
4346
4503
|
...runtimeSettings,
|
|
4504
|
+
modelPage: options.expectedProvider !== undefined && options.expectedProvider !== (runtimeSettings.currentProvider ?? "")
|
|
4505
|
+
? undefined : options.modelPage,
|
|
4347
4506
|
notice: options.notice,
|
|
4507
|
+
warning: [runtimeSettings.warning, options.warning].filter(Boolean).join("\n") || undefined,
|
|
4348
4508
|
});
|
|
4349
4509
|
if (options.updateMessageId) {
|
|
4350
4510
|
await this.outbound.updateInteractiveCard(contextKey, options.updateMessageId, card);
|
|
@@ -4362,35 +4522,40 @@ export class ProxySessionController {
|
|
|
4362
4522
|
await this.openExecutionSettings(contextKey, "agent");
|
|
4363
4523
|
}
|
|
4364
4524
|
async selectProvider(contextKey, modelProvider, options = {}) {
|
|
4525
|
+
const record = options.sessionId ? this.requireSession(contextKey, options.sessionId) : this.requireCurrentSession(contextKey);
|
|
4526
|
+
return this.settingsOperations.run(record.localSessionId, () => this.selectProviderNow(contextKey, modelProvider, { ...options, sessionId: record.localSessionId }));
|
|
4527
|
+
}
|
|
4528
|
+
async selectProviderNow(contextKey, modelProvider, options = {}) {
|
|
4365
4529
|
const record = options.sessionId
|
|
4366
4530
|
? this.requireSession(contextKey, options.sessionId)
|
|
4367
4531
|
: this.requireCurrentSession(contextKey);
|
|
4368
|
-
await this.
|
|
4369
|
-
const
|
|
4532
|
+
const loaded = await this.loadSession(record, true);
|
|
4533
|
+
const selected = this.store.getPendingProviderSettings(record.localSessionId)?.settings ?? loaded.session;
|
|
4370
4534
|
await this.assertModelProvider(loaded, modelProvider);
|
|
4371
|
-
const models = await loaded.runtime.listModels(modelProvider,
|
|
4372
|
-
const model = selectProviderModel(models,
|
|
4535
|
+
const models = await loaded.runtime.listModels(modelProvider, selected.model);
|
|
4536
|
+
const model = selectProviderModel(models, selected.model);
|
|
4373
4537
|
if (!model)
|
|
4374
4538
|
throw new Error("当前运行时没有可用于 Provider 切换的模型。");
|
|
4375
|
-
const effort = selectProviderReasoningEffort(model,
|
|
4376
|
-
await this.
|
|
4539
|
+
const effort = selectProviderReasoningEffort(model, selected.reasoningEffort);
|
|
4540
|
+
await this.applyProviderSettingsNow(contextKey, {
|
|
4377
4541
|
provider: modelProvider,
|
|
4378
4542
|
model: model.id,
|
|
4379
4543
|
effort,
|
|
4380
|
-
mode:
|
|
4381
|
-
}, options
|
|
4544
|
+
mode: selected.permissionMode,
|
|
4545
|
+
}, options);
|
|
4382
4546
|
}
|
|
4383
4547
|
async openProviderSelector(contextKey, options = {}) {
|
|
4384
4548
|
await this.openExecutionSettings(contextKey, "provider", options);
|
|
4385
4549
|
}
|
|
4386
|
-
async openProviderModelSelector(contextKey, modelProvider, permissionMode, sessionId, updateMessageId) {
|
|
4387
|
-
const loaded = await this.loadSession(this.requireSession(contextKey, sessionId));
|
|
4550
|
+
async openProviderModelSelector(contextKey, modelProvider, permissionMode, sessionId, updateMessageId, modelPage) {
|
|
4551
|
+
const loaded = await this.loadSession(this.requireSession(contextKey, sessionId), true);
|
|
4388
4552
|
await this.assertModelProvider(loaded, modelProvider);
|
|
4389
4553
|
const { models, warning } = await this.modelOptions(loaded.runtime, modelProvider, loaded.session.model);
|
|
4390
4554
|
const card = this.cardRenderer.renderModelSelector({
|
|
4391
4555
|
sessionId,
|
|
4392
4556
|
contextKey,
|
|
4393
4557
|
currentModel: selectProviderModel(models, loaded.session.model)?.id,
|
|
4558
|
+
modelPage,
|
|
4394
4559
|
reasoningEffort: loaded.session.reasoningEffort,
|
|
4395
4560
|
models,
|
|
4396
4561
|
warning,
|
|
@@ -4401,7 +4566,7 @@ export class ProxySessionController {
|
|
|
4401
4566
|
await this.outbound.updateInteractiveCard(contextKey, updateMessageId, card);
|
|
4402
4567
|
}
|
|
4403
4568
|
async openProviderReasoningSelector(contextKey, modelProvider, model, permissionMode, sessionId, updateMessageId) {
|
|
4404
|
-
const loaded = await this.loadSession(this.requireSession(contextKey, sessionId));
|
|
4569
|
+
const loaded = await this.loadSession(this.requireSession(contextKey, sessionId), true);
|
|
4405
4570
|
await this.assertModelProvider(loaded, modelProvider);
|
|
4406
4571
|
const { models, warning } = await this.modelOptions(loaded.runtime, modelProvider, model);
|
|
4407
4572
|
const selected = models.find((candidate) => candidate.id === model);
|
|
@@ -4427,7 +4592,7 @@ export class ProxySessionController {
|
|
|
4427
4592
|
await this.outbound.updateInteractiveCard(contextKey, updateMessageId, card);
|
|
4428
4593
|
}
|
|
4429
4594
|
async openProviderPermissionSelector(contextKey, modelProvider, model, effort, permissionMode, sessionId, updateMessageId) {
|
|
4430
|
-
const loaded = await this.loadSession(this.requireSession(contextKey, sessionId));
|
|
4595
|
+
const loaded = await this.loadSession(this.requireSession(contextKey, sessionId), true);
|
|
4431
4596
|
await this.assertProviderModelSettings(loaded, modelProvider, model, effort);
|
|
4432
4597
|
await this.outbound.updateInteractiveCard(contextKey, updateMessageId, this.cardRenderer.renderPermissionSelector({
|
|
4433
4598
|
sessionId,
|
|
@@ -4439,20 +4604,47 @@ export class ProxySessionController {
|
|
|
4439
4604
|
}));
|
|
4440
4605
|
}
|
|
4441
4606
|
async applyProviderSettings(contextKey, settings, options = {}, announceStart = true) {
|
|
4607
|
+
const record = options.sessionId ? this.requireSession(contextKey, options.sessionId) : this.requireCurrentSession(contextKey);
|
|
4608
|
+
return this.settingsOperations.run(record.localSessionId, () => this.applyProviderSettingsNow(contextKey, settings, { ...options, sessionId: record.localSessionId }, announceStart));
|
|
4609
|
+
}
|
|
4610
|
+
async applyProviderSettingsNow(contextKey, settings, options = {}, announceStart = true) {
|
|
4442
4611
|
const record = options.sessionId
|
|
4443
4612
|
? this.requireSession(contextKey, options.sessionId)
|
|
4444
4613
|
: this.requireCurrentSession(contextKey);
|
|
4445
|
-
|
|
4614
|
+
const loaded = await this.loadSession(record, true);
|
|
4615
|
+
const warning = await this.assertProviderModelSettings(loaded, settings.provider, settings.model, settings.effort);
|
|
4616
|
+
const next = {
|
|
4617
|
+
modelProvider: settings.provider, model: settings.model,
|
|
4618
|
+
reasoningEffort: settings.effort, permissionMode: settings.mode,
|
|
4619
|
+
};
|
|
4620
|
+
await this.reconcilePreparedSettingsSession(loaded);
|
|
4621
|
+
if (loaded.session.activeTurnId || this.store.getPendingProviderSettings(record.localSessionId)) {
|
|
4622
|
+
await this.savePendingProviderSelection(loaded, next, contextKey, options, "provider");
|
|
4623
|
+
return;
|
|
4624
|
+
}
|
|
4625
|
+
if (announceStart)
|
|
4446
4626
|
await this.outbound.sendText(contextKey, `正在切换到 Provider ${settings.provider},请稍后。`);
|
|
4627
|
+
const previous = { ...loaded.session };
|
|
4628
|
+
let session;
|
|
4629
|
+
try {
|
|
4630
|
+
session = await this.changeProviderSettings(loaded, next);
|
|
4631
|
+
}
|
|
4632
|
+
catch (error) {
|
|
4633
|
+
if (!this.providerSettingsRestored(loaded, previous, error)) {
|
|
4634
|
+
this.store.savePendingProviderSettings({
|
|
4635
|
+
localSessionId: record.localSessionId, contextKey, settings: next, activeTab: "provider",
|
|
4636
|
+
messageId: options.updateMessageId,
|
|
4637
|
+
replyMessageId: options.updateMessageId ?? this.controlSessionReplyTarget(record)?.messageId,
|
|
4638
|
+
error: runtimeErrorMessage(error),
|
|
4639
|
+
});
|
|
4640
|
+
if (options.updateMessageId) {
|
|
4641
|
+
await this.openExecutionSettingsNow(contextKey, "provider", options).catch((presentationError) => {
|
|
4642
|
+
this.logger.warn({ error: presentationError, sessionId: record.localSessionId }, "Failed to refresh rejected Provider selection.");
|
|
4643
|
+
});
|
|
4644
|
+
}
|
|
4645
|
+
}
|
|
4646
|
+
throw error;
|
|
4447
4647
|
}
|
|
4448
|
-
const loaded = await this.loadSession(record);
|
|
4449
|
-
const warning = await this.assertProviderModelSettings(loaded, settings.provider, settings.model, settings.effort);
|
|
4450
|
-
const session = await this.changeProviderSettings(loaded, {
|
|
4451
|
-
modelProvider: settings.provider,
|
|
4452
|
-
model: settings.model,
|
|
4453
|
-
reasoningEffort: settings.effort,
|
|
4454
|
-
permissionMode: settings.mode,
|
|
4455
|
-
});
|
|
4456
4648
|
const notice = [
|
|
4457
4649
|
`Provider 已切换为 ${cardCode(session.modelProvider ?? settings.provider)}`,
|
|
4458
4650
|
`模型 ${cardCode(session.model ?? settings.model)}`,
|
|
@@ -4461,7 +4653,7 @@ export class ProxySessionController {
|
|
|
4461
4653
|
`并已保存为 ${cardCode(loaded.record.agentName)} Agent 的默认设置,从下一次请求生效。`,
|
|
4462
4654
|
].join(",");
|
|
4463
4655
|
if (options.updateMessageId) {
|
|
4464
|
-
await this.
|
|
4656
|
+
await this.openExecutionSettingsNow(contextKey, "provider", {
|
|
4465
4657
|
sessionId: loaded.record.localSessionId,
|
|
4466
4658
|
updateMessageId: options.updateMessageId,
|
|
4467
4659
|
notice,
|
|
@@ -4472,16 +4664,23 @@ export class ProxySessionController {
|
|
|
4472
4664
|
}
|
|
4473
4665
|
}
|
|
4474
4666
|
async changeProviderSettings(loaded, settings) {
|
|
4667
|
+
return this.sessionHydrationOperations.run(loaded.record.localSessionId, () => this.changeProviderSettingsNow(loaded, settings));
|
|
4668
|
+
}
|
|
4669
|
+
async changeProviderSettingsNow(loaded, settings) {
|
|
4475
4670
|
if (!loaded.runtime.setExecutionSettings)
|
|
4476
4671
|
throw new Error("当前运行时不支持 Provider 设置。");
|
|
4477
|
-
|
|
4672
|
+
loaded.session = loaded.runtime.getSession(loaded.record.localSessionId) ?? loaded.session;
|
|
4673
|
+
const remoteSessionId = loaded.session.remoteSessionId;
|
|
4674
|
+
const persist = (candidate) => this.agentDefaultsOperations.run(loaded.record.agentName, async () => {
|
|
4675
|
+
if (candidate.remoteSessionId !== remoteSessionId) {
|
|
4676
|
+
throw new Error("设置切换返回了不同的任务 ID,未保存设置;不允许替换任务或更改历史。");
|
|
4677
|
+
}
|
|
4478
4678
|
assertProviderSettingsApplied(candidate, settings);
|
|
4479
4679
|
const agent = this.ensureAgent(loaded.record.agentName);
|
|
4480
4680
|
const previousDefaults = agent.defaults;
|
|
4481
|
-
await this.
|
|
4681
|
+
await this.persistAgentExecutionDefaultsNow(loaded.record.localSessionId, candidate);
|
|
4482
4682
|
try {
|
|
4483
4683
|
this.store.updateRuntimeSession(loaded.record.localSessionId, {
|
|
4484
|
-
remoteSessionId: candidate.remoteSessionId,
|
|
4485
4684
|
modelProvider: candidate.modelProvider,
|
|
4486
4685
|
model: candidate.model,
|
|
4487
4686
|
reasoningEffort: candidate.reasoningEffort,
|
|
@@ -4493,11 +4692,147 @@ export class ProxySessionController {
|
|
|
4493
4692
|
agent.defaults = previousDefaults;
|
|
4494
4693
|
throw error;
|
|
4495
4694
|
}
|
|
4496
|
-
};
|
|
4695
|
+
});
|
|
4497
4696
|
const session = await loaded.runtime.setExecutionSettings(loaded.record.localSessionId, settings, persist);
|
|
4697
|
+
this.settingsOnlySessions.delete(loaded.record.localSessionId);
|
|
4498
4698
|
this.outbound.updateSessionModel(loaded.record.localSessionId, session.model, session.modelProvider);
|
|
4499
4699
|
return session;
|
|
4500
4700
|
}
|
|
4701
|
+
async reconcilePreparedSettingsSession(loaded) {
|
|
4702
|
+
if (!this.settingsOnlySessions.has(loaded.record.localSessionId) || !loaded.session.activeTurnId)
|
|
4703
|
+
return;
|
|
4704
|
+
const activity = await this.assertSessionTurnOwnership(loaded.record, loaded.runtime);
|
|
4705
|
+
if (activity && !activity.active) {
|
|
4706
|
+
loaded.session = await loaded.runtime.synchronizeSession(loaded.record.localSessionId);
|
|
4707
|
+
}
|
|
4708
|
+
}
|
|
4709
|
+
providerSettingsRestored(loaded, previous, error) {
|
|
4710
|
+
const restored = loaded.runtime.getSession(loaded.record.localSessionId);
|
|
4711
|
+
const saved = this.store.getSession(loaded.record.localSessionId);
|
|
4712
|
+
const keys = ["remoteSessionId", "modelProvider", "model", "reasoningEffort", "permissionMode"];
|
|
4713
|
+
return error instanceof ProviderSwitchError && error.previousSettingsRestored
|
|
4714
|
+
&& Boolean(restored && saved && keys.every((key) => restored[key] === previous[key] && saved[key] === previous[key]));
|
|
4715
|
+
}
|
|
4716
|
+
pendingProviderNotice(pending) {
|
|
4717
|
+
const target = `Provider ${cardCode(pending.settings.modelProvider)},模型 ${cardCode(pending.settings.model)}`;
|
|
4718
|
+
return pending.error
|
|
4719
|
+
? `切换失败:${pending.error}。目标为 ${target};后续轮次已暂停,请重新选择 Provider 或模型重试。`
|
|
4720
|
+
: `待生效:当前轮次不变,结束后切换到 ${target}。可继续选择模型,以最后选择为准。`;
|
|
4721
|
+
}
|
|
4722
|
+
async savePendingProviderSelection(loaded, settings, contextKey, options, activeTab) {
|
|
4723
|
+
const previous = this.store.getPendingProviderSettings(loaded.record.localSessionId);
|
|
4724
|
+
const pending = {
|
|
4725
|
+
localSessionId: loaded.record.localSessionId, contextKey, settings, activeTab,
|
|
4726
|
+
messageId: options.updateMessageId ?? previous?.messageId,
|
|
4727
|
+
replyMessageId: options.updateMessageId ?? previous?.replyMessageId
|
|
4728
|
+
?? this.controlSessionReplyTarget(loaded.record)?.messageId,
|
|
4729
|
+
};
|
|
4730
|
+
this.store.savePendingProviderSettings(pending);
|
|
4731
|
+
await this.reconcilePreparedSettingsSession(loaded);
|
|
4732
|
+
if (!loaded.session.activeTurnId) {
|
|
4733
|
+
await this.applyPendingProviderSettings(loaded);
|
|
4734
|
+
void this.scheduleNextQueuedPrompt(loaded.record.localSessionId).catch((error) => {
|
|
4735
|
+
this.logger.warn({ error }, "Failed to resume queue after Provider selection.");
|
|
4736
|
+
});
|
|
4737
|
+
return;
|
|
4738
|
+
}
|
|
4739
|
+
if (options.updateMessageId) {
|
|
4740
|
+
await this.openExecutionSettingsNow(contextKey, activeTab, options);
|
|
4741
|
+
}
|
|
4742
|
+
else {
|
|
4743
|
+
await this.outbound.sendText(contextKey, this.pendingProviderNotice(pending).replaceAll("`", ""));
|
|
4744
|
+
}
|
|
4745
|
+
}
|
|
4746
|
+
// Caller holds settingsOperations; only a verified rollback can unblock the next turn.
|
|
4747
|
+
async applyPendingProviderSettings(loaded) {
|
|
4748
|
+
const pending = this.store.getPendingProviderSettings(loaded.record.localSessionId);
|
|
4749
|
+
if (!pending)
|
|
4750
|
+
return;
|
|
4751
|
+
await this.reconcilePreparedSettingsSession(loaded);
|
|
4752
|
+
if (loaded.runtime.getSession(loaded.record.localSessionId)?.activeTurnId)
|
|
4753
|
+
return;
|
|
4754
|
+
if (pending.error)
|
|
4755
|
+
throw new Error(this.pendingProviderNotice(pending));
|
|
4756
|
+
const previous = { ...(loaded.runtime.getSession(loaded.record.localSessionId) ?? loaded.session) };
|
|
4757
|
+
let notice;
|
|
4758
|
+
let failure;
|
|
4759
|
+
let rolledBack = false;
|
|
4760
|
+
try {
|
|
4761
|
+
loaded.session = await this.changeProviderSettings(loaded, pending.settings);
|
|
4762
|
+
this.store.deletePendingProviderSettings(pending.localSessionId);
|
|
4763
|
+
notice = `Provider 已切换为 ${cardCode(pending.settings.modelProvider)},模型 ${cardCode(pending.settings.model)},从下一轮生效。`;
|
|
4764
|
+
}
|
|
4765
|
+
catch (error) {
|
|
4766
|
+
const restored = loaded.runtime.getSession(pending.localSessionId);
|
|
4767
|
+
rolledBack = this.providerSettingsRestored(loaded, previous, error);
|
|
4768
|
+
if (rolledBack) {
|
|
4769
|
+
loaded.session = restored;
|
|
4770
|
+
this.store.deletePendingProviderSettings(pending.localSessionId);
|
|
4771
|
+
this.outbound.updateSessionModel(pending.localSessionId, previous.model, previous.modelProvider);
|
|
4772
|
+
notice = `切换到 Provider ${cardCode(pending.settings.modelProvider)}、模型 ${cardCode(pending.settings.model)} 失败:${runtimeErrorMessage(error)} 已回退到 Provider ${cardCode(previous.modelProvider ?? "Agent 默认")}、模型 ${cardCode(previous.model ?? "默认")},后续轮次继续使用原设置。`;
|
|
4773
|
+
}
|
|
4774
|
+
else {
|
|
4775
|
+
failure = error;
|
|
4776
|
+
const failed = { ...pending, error: runtimeErrorMessage(error) };
|
|
4777
|
+
this.store.savePendingProviderSettings(failed);
|
|
4778
|
+
notice = this.pendingProviderNotice(failed);
|
|
4779
|
+
}
|
|
4780
|
+
}
|
|
4781
|
+
try {
|
|
4782
|
+
await this.outbound.withReplyTarget(pending.contextKey, pending.replyMessageId
|
|
4783
|
+
? { messageId: pending.replyMessageId, replyInThread: true } : undefined, async () => {
|
|
4784
|
+
if (pending.messageId) {
|
|
4785
|
+
try {
|
|
4786
|
+
await this.openExecutionSettingsNow(pending.contextKey, pending.activeTab ?? "provider", {
|
|
4787
|
+
sessionId: pending.localSessionId, updateMessageId: pending.messageId,
|
|
4788
|
+
notice: failure || rolledBack ? undefined : notice,
|
|
4789
|
+
warning: rolledBack ? notice.replaceAll("`", "") : undefined,
|
|
4790
|
+
});
|
|
4791
|
+
return;
|
|
4792
|
+
}
|
|
4793
|
+
catch (error) {
|
|
4794
|
+
this.logger.warn({ error, sessionId: pending.localSessionId }, "Failed to refresh Provider settings card.");
|
|
4795
|
+
}
|
|
4796
|
+
}
|
|
4797
|
+
await this.outbound.sendText(pending.contextKey, notice.replaceAll("`", ""));
|
|
4798
|
+
});
|
|
4799
|
+
}
|
|
4800
|
+
catch (error) {
|
|
4801
|
+
this.logger.warn({ error, sessionId: pending.localSessionId }, "Failed to notify Provider settings result.");
|
|
4802
|
+
}
|
|
4803
|
+
if (failure)
|
|
4804
|
+
throw failure;
|
|
4805
|
+
}
|
|
4806
|
+
async applyPendingProviderWhenIdle(sessionId) {
|
|
4807
|
+
await this.settingsOperations.run(sessionId, async () => {
|
|
4808
|
+
if (this.recoveryClosed)
|
|
4809
|
+
return;
|
|
4810
|
+
const pending = this.store.getPendingProviderSettings(sessionId);
|
|
4811
|
+
if (!pending || pending.error)
|
|
4812
|
+
return;
|
|
4813
|
+
const record = this.store.getSession(sessionId);
|
|
4814
|
+
if (!record || record.status === "closed") {
|
|
4815
|
+
this.store.deletePendingProviderSettings(sessionId);
|
|
4816
|
+
return;
|
|
4817
|
+
}
|
|
4818
|
+
try {
|
|
4819
|
+
const loaded = await this.loadSession(record, true);
|
|
4820
|
+
await this.applyPendingProviderSettings(loaded);
|
|
4821
|
+
}
|
|
4822
|
+
catch (error) {
|
|
4823
|
+
this.logger.warn({ error, sessionId }, "Deferred Provider switch could not be applied.");
|
|
4824
|
+
if (!this.store.getPendingProviderSettings(sessionId)?.error) {
|
|
4825
|
+
const failed = { ...pending, error: runtimeErrorMessage(error) };
|
|
4826
|
+
this.store.savePendingProviderSettings(failed);
|
|
4827
|
+
await this.outbound.withReplyTarget(pending.contextKey, pending.replyMessageId
|
|
4828
|
+
? { messageId: pending.replyMessageId, replyInThread: true } : undefined, () => this.outbound.sendText(pending.contextKey, this.pendingProviderNotice(failed).replaceAll("`", "")))
|
|
4829
|
+
.catch((notificationError) => {
|
|
4830
|
+
this.logger.warn({ error: notificationError, sessionId }, "Failed to notify Provider recovery failure.");
|
|
4831
|
+
});
|
|
4832
|
+
}
|
|
4833
|
+
}
|
|
4834
|
+
});
|
|
4835
|
+
}
|
|
4501
4836
|
async assertProviderModelSettings(loaded, modelProvider, model, effort) {
|
|
4502
4837
|
await this.assertModelProvider(loaded, modelProvider);
|
|
4503
4838
|
const { models, warning } = await this.modelOptions(loaded.runtime, modelProvider, model);
|
|
@@ -4534,36 +4869,51 @@ export class ProxySessionController {
|
|
|
4534
4869
|
return providers;
|
|
4535
4870
|
}
|
|
4536
4871
|
async model(contextKey, model, options = {}) {
|
|
4872
|
+
const record = options.sessionId ? this.requireSession(contextKey, options.sessionId) : this.requireCurrentSession(contextKey);
|
|
4873
|
+
return this.settingsOperations.run(record.localSessionId, () => this.modelNow(contextKey, model, { ...options, sessionId: record.localSessionId }));
|
|
4874
|
+
}
|
|
4875
|
+
async modelNow(contextKey, model, options = {}) {
|
|
4537
4876
|
if (!model) {
|
|
4538
|
-
await this.
|
|
4877
|
+
await this.openExecutionSettingsNow(contextKey, "model", options);
|
|
4539
4878
|
return;
|
|
4540
4879
|
}
|
|
4541
4880
|
const record = options.sessionId
|
|
4542
4881
|
? this.requireSession(contextKey, options.sessionId)
|
|
4543
4882
|
: this.requireCurrentSession(contextKey);
|
|
4544
|
-
const loaded = await this.loadSession(record);
|
|
4545
|
-
const
|
|
4883
|
+
const loaded = await this.loadSession(record, true);
|
|
4884
|
+
const pending = this.store.getPendingProviderSettings(record.localSessionId);
|
|
4885
|
+
const selectedSettings = pending?.settings ?? loaded.session;
|
|
4886
|
+
if (options.expectedProvider && options.expectedProvider !== selectedSettings.modelProvider) {
|
|
4887
|
+
throw new Error("Provider 已发生变化,请重新打开运行设置后选择。");
|
|
4888
|
+
}
|
|
4889
|
+
const models = await loaded.runtime.listModels(selectedSettings.modelProvider, selectedSettings.model);
|
|
4546
4890
|
const selected = models.find((item) => item.id === model);
|
|
4547
4891
|
if (!selected)
|
|
4548
4892
|
throw new Error(`未知模型:${model}`);
|
|
4549
|
-
const currentEffort =
|
|
4893
|
+
const currentEffort = selectedSettings.reasoningEffort;
|
|
4550
4894
|
const compatible = currentEffort
|
|
4551
4895
|
? selected.supportedReasoningEfforts.length === 0
|
|
4552
4896
|
|| selected.supportedReasoningEfforts.some((option) => option.value === currentEffort)
|
|
4553
4897
|
: false;
|
|
4554
4898
|
const nextEffort = compatible ? currentEffort : selected.defaultReasoningEffort;
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4899
|
+
if (pending) {
|
|
4900
|
+
await this.savePendingProviderSelection(loaded, { ...pending.settings, model, reasoningEffort: selectProviderReasoningEffort(selected, nextEffort) }, contextKey, options, "model");
|
|
4901
|
+
return;
|
|
4558
4902
|
}
|
|
4559
|
-
this.
|
|
4560
|
-
|
|
4561
|
-
|
|
4903
|
+
await this.sessionHydrationOperations.run(loaded.record.localSessionId, async () => {
|
|
4904
|
+
await loaded.runtime.setModel(loaded.record.localSessionId, model);
|
|
4905
|
+
if (nextEffort && nextEffort !== currentEffort) {
|
|
4906
|
+
await loaded.runtime.setReasoningEffort(loaded.record.localSessionId, nextEffort);
|
|
4907
|
+
}
|
|
4908
|
+
this.store.updateRuntimeSession(loaded.record.localSessionId, { model, reasoningEffort: nextEffort });
|
|
4909
|
+
this.outbound.updateSessionModel(loaded.record.localSessionId, model, loaded.session.modelProvider);
|
|
4910
|
+
await this.persistAgentExecutionDefaults(loaded.record.localSessionId);
|
|
4911
|
+
});
|
|
4562
4912
|
const effortMessage = nextEffort && nextEffort !== currentEffort
|
|
4563
4913
|
? `,思考强度已自动调整为 ${nextEffort}`
|
|
4564
4914
|
: "";
|
|
4565
4915
|
if (options.updateMessageId) {
|
|
4566
|
-
await this.
|
|
4916
|
+
await this.openExecutionSettingsNow(contextKey, "model", {
|
|
4567
4917
|
sessionId: loaded.record.localSessionId,
|
|
4568
4918
|
updateMessageId: options.updateMessageId,
|
|
4569
4919
|
notice: `模型已切换为 ${cardCode(model)}${effortMessage},并已保存为 ${cardCode(loaded.record.agentName)} Agent 的默认设置,从下一次请求生效。`,
|
|
@@ -4703,6 +5053,7 @@ export class ProxySessionController {
|
|
|
4703
5053
|
const created = await this.shellCommandJobs.createJob({ contextKey, sourceMessageId, command, cwd });
|
|
4704
5054
|
let cardMessageId;
|
|
4705
5055
|
const initialState = {
|
|
5056
|
+
terminalUrl: this.lifecycle?.terminalUrl?.(created.id),
|
|
4706
5057
|
jobId: created.id,
|
|
4707
5058
|
contextKey,
|
|
4708
5059
|
command,
|
|
@@ -4725,7 +5076,7 @@ export class ProxySessionController {
|
|
|
4725
5076
|
if (cardMessageId) {
|
|
4726
5077
|
try {
|
|
4727
5078
|
const failed = await this.shellCommandJobs.readJob(created.id);
|
|
4728
|
-
await this.outbound.updateInteractiveCard(contextKey, cardMessageId, this.cardRenderer.renderShellCommandCard(shellCommandJobCardView(failed)));
|
|
5079
|
+
await this.outbound.updateInteractiveCard(contextKey, cardMessageId, this.cardRenderer.renderShellCommandCard({ ...shellCommandJobCardView(failed), terminalUrl: this.lifecycle?.terminalUrl?.(failed.id) }));
|
|
4729
5080
|
await this.shellCommandJobs.markPresented(created.id);
|
|
4730
5081
|
}
|
|
4731
5082
|
catch (cardError) {
|
|
@@ -4776,7 +5127,7 @@ export class ProxySessionController {
|
|
|
4776
5127
|
if (!job.cardMessageId || this.shellCommandJobMonitors.has(job.id))
|
|
4777
5128
|
return;
|
|
4778
5129
|
const scheduler = new CardUpdateScheduler({
|
|
4779
|
-
render: (state) => this.cardRenderer.renderShellCommandCard(state),
|
|
5130
|
+
render: (state) => this.cardRenderer.renderShellCommandCard({ ...state, terminalUrl: this.lifecycle?.terminalUrl?.(job.id) }),
|
|
4780
5131
|
write: (card) => this.outbound.updateInteractiveCard(job.contextKey, job.cardMessageId, card),
|
|
4781
5132
|
onError: (error) => this.logger.warn({ error, jobId: job.id, contextKey: job.contextKey, messageId: job.cardMessageId }, "Failed to update a background shell command card."),
|
|
4782
5133
|
});
|
|
@@ -4925,16 +5276,25 @@ export class ProxySessionController {
|
|
|
4925
5276
|
});
|
|
4926
5277
|
}
|
|
4927
5278
|
async thinking(contextKey, effort, options = {}) {
|
|
5279
|
+
const record = options.sessionId ? this.requireSession(contextKey, options.sessionId) : this.requireCurrentSession(contextKey);
|
|
5280
|
+
return this.settingsOperations.run(record.localSessionId, () => this.thinkingNow(contextKey, effort, { ...options, sessionId: record.localSessionId }));
|
|
5281
|
+
}
|
|
5282
|
+
async thinkingNow(contextKey, effort, options = {}) {
|
|
4928
5283
|
if (!effort) {
|
|
4929
|
-
await this.
|
|
5284
|
+
await this.openExecutionSettingsNow(contextKey, "thinking", options);
|
|
4930
5285
|
return;
|
|
4931
5286
|
}
|
|
4932
5287
|
const record = options.sessionId
|
|
4933
5288
|
? this.requireSession(contextKey, options.sessionId)
|
|
4934
5289
|
: this.requireCurrentSession(contextKey);
|
|
4935
|
-
const loaded = await this.loadSession(record);
|
|
4936
|
-
const
|
|
4937
|
-
const
|
|
5290
|
+
const loaded = await this.loadSession(record, true);
|
|
5291
|
+
const pending = this.store.getPendingProviderSettings(record.localSessionId);
|
|
5292
|
+
const selectedSettings = pending?.settings ?? loaded.session;
|
|
5293
|
+
if (options.expectedProvider && options.expectedProvider !== selectedSettings.modelProvider) {
|
|
5294
|
+
throw new Error("Provider 已发生变化,请重新打开运行设置后选择。");
|
|
5295
|
+
}
|
|
5296
|
+
const models = await loaded.runtime.listModels(selectedSettings.modelProvider, selectedSettings.model);
|
|
5297
|
+
const currentModel = models.find((item) => item.id === selectedSettings.model)
|
|
4938
5298
|
?? models.find((item) => item.isDefault);
|
|
4939
5299
|
if (!currentModel)
|
|
4940
5300
|
throw new Error("当前运行时没有可配置思考强度的模型。");
|
|
@@ -4946,11 +5306,17 @@ export class ProxySessionController {
|
|
|
4946
5306
|
const options = supported.map((option) => option.value).join("、") || "无";
|
|
4947
5307
|
throw new Error(`不支持的思考强度:${effort}。支持的强度:${options}`);
|
|
4948
5308
|
}
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
|
|
5309
|
+
if (pending) {
|
|
5310
|
+
await this.savePendingProviderSelection(loaded, { ...pending.settings, reasoningEffort: effort }, contextKey, options, "thinking");
|
|
5311
|
+
return;
|
|
5312
|
+
}
|
|
5313
|
+
await this.sessionHydrationOperations.run(loaded.record.localSessionId, async () => {
|
|
5314
|
+
await loaded.runtime.setReasoningEffort(loaded.record.localSessionId, effort);
|
|
5315
|
+
this.store.updateRuntimeSession(loaded.record.localSessionId, { reasoningEffort: effort });
|
|
5316
|
+
await this.persistAgentExecutionDefaults(loaded.record.localSessionId);
|
|
5317
|
+
});
|
|
4952
5318
|
if (options.updateMessageId) {
|
|
4953
|
-
await this.
|
|
5319
|
+
await this.openExecutionSettingsNow(contextKey, "thinking", {
|
|
4954
5320
|
sessionId: loaded.record.localSessionId,
|
|
4955
5321
|
updateMessageId: options.updateMessageId,
|
|
4956
5322
|
notice: `思考强度已切换为 ${cardCode(effort)},并已保存为 ${cardCode(loaded.record.agentName)} Agent 的默认设置,从下一次请求生效。`,
|
|
@@ -4960,20 +5326,32 @@ export class ProxySessionController {
|
|
|
4960
5326
|
await this.outbound.sendText(contextKey, `思考强度已切换为 ${effort},并已保存为 ${loaded.record.agentName} Agent 的默认设置,从下一次请求生效。`);
|
|
4961
5327
|
}
|
|
4962
5328
|
async permissions(contextKey, mode, options = {}) {
|
|
5329
|
+
const record = options.sessionId ? this.requireSession(contextKey, options.sessionId) : this.requireCurrentSession(contextKey);
|
|
5330
|
+
return this.settingsOperations.run(record.localSessionId, () => this.permissionsNow(contextKey, mode, { ...options, sessionId: record.localSessionId }));
|
|
5331
|
+
}
|
|
5332
|
+
async permissionsNow(contextKey, mode, options = {}) {
|
|
4963
5333
|
if (!mode) {
|
|
4964
|
-
await this.
|
|
5334
|
+
await this.openExecutionSettingsNow(contextKey, "permission", options);
|
|
4965
5335
|
return;
|
|
4966
5336
|
}
|
|
4967
5337
|
const record = options.sessionId
|
|
4968
5338
|
? this.requireSession(contextKey, options.sessionId)
|
|
4969
5339
|
: this.requireCurrentSession(contextKey);
|
|
4970
|
-
const loaded = await this.loadSession(record);
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
5340
|
+
const loaded = await this.loadSession(record, true);
|
|
5341
|
+
const pending = this.store.getPendingProviderSettings(record.localSessionId);
|
|
5342
|
+
if (pending) {
|
|
5343
|
+
await this.savePendingProviderSelection(loaded, { ...pending.settings, permissionMode: mode }, contextKey, options, "permission");
|
|
5344
|
+
return;
|
|
5345
|
+
}
|
|
5346
|
+
await this.sessionHydrationOperations.run(record.localSessionId, async () => {
|
|
5347
|
+
await loaded.runtime.setPermissionMode(record.localSessionId, mode);
|
|
5348
|
+
loaded.session = loaded.runtime.getSession(record.localSessionId) ?? loaded.session;
|
|
5349
|
+
loaded.session.permissionMode = mode;
|
|
5350
|
+
this.store.updateRuntimeSession(record.localSessionId, { permissionMode: mode });
|
|
5351
|
+
await this.persistAgentExecutionDefaults(record.localSessionId);
|
|
5352
|
+
});
|
|
4975
5353
|
if (options.updateMessageId) {
|
|
4976
|
-
await this.
|
|
5354
|
+
await this.openExecutionSettingsNow(contextKey, "permission", {
|
|
4977
5355
|
sessionId: loaded.record.localSessionId,
|
|
4978
5356
|
updateMessageId: options.updateMessageId,
|
|
4979
5357
|
notice: mode === "auto"
|
|
@@ -5014,8 +5392,9 @@ export class ProxySessionController {
|
|
|
5014
5392
|
if (existing)
|
|
5015
5393
|
return existing;
|
|
5016
5394
|
const snapshot = turnViewSnapshot(this.store.getTurnSnapshot(turnId));
|
|
5017
|
-
|
|
5018
|
-
|
|
5395
|
+
const previewTurnId = snapshot?.previewTurnId ?? turnId;
|
|
5396
|
+
if ((!snapshot || snapshot.previewJournal) && this.store.previews.has(previewTurnId))
|
|
5397
|
+
return this.store.previews.load(previewTurnId);
|
|
5019
5398
|
if (snapshot && !needsHistoricalDetails(snapshot))
|
|
5020
5399
|
return snapshot;
|
|
5021
5400
|
const origin = !snapshot ? this.store.getTurnRuntimeOrigin(turnId) : undefined;
|
|
@@ -5048,7 +5427,7 @@ export class ProxySessionController {
|
|
|
5048
5427
|
// An Agent Bot turn may have arrived while the read-only request was in flight.
|
|
5049
5428
|
const latest = turnViewSnapshot(this.store.getTurnSnapshot(origin.turnId));
|
|
5050
5429
|
if (latest)
|
|
5051
|
-
return latest.previewJournal ? this.store.previews.load(origin.turnId) ?? latest : latest;
|
|
5430
|
+
return latest.previewJournal ? this.store.previews.load(latest.previewTurnId ?? origin.turnId) ?? latest : latest;
|
|
5052
5431
|
const state = hydrateTurnViewState(createTurnViewState(origin.localSessionId, origin.turnId, details.startedAt ?? 0, remote?.title, undefined, remote?.cwd, undefined, this.agentLabel(origin.agentName)), details);
|
|
5053
5432
|
// Viewing external work must not create a task, a chat binding or a pending final reply.
|
|
5054
5433
|
this.store.previews.seed(state);
|
|
@@ -5951,7 +6330,7 @@ export class ProxySessionController {
|
|
|
5951
6330
|
const remoteReference = local
|
|
5952
6331
|
? remoteSessionReference(local.agentName, local.remoteSessionId)
|
|
5953
6332
|
: taskId;
|
|
5954
|
-
const { agentName, runtime, remote } = await this.resolveRemoteCodexSession(remoteReference);
|
|
6333
|
+
const { agentName, runtime, remote } = await this.resolveRemoteCodexSession(remoteReference, { view: "metadata" });
|
|
5955
6334
|
if (isRemoteSessionActive(remote)) {
|
|
5956
6335
|
throw new Error("任务正在执行,无法归档。请先停止任务或等待本轮完成。");
|
|
5957
6336
|
}
|
|
@@ -5959,7 +6338,11 @@ export class ProxySessionController {
|
|
|
5959
6338
|
throw new Error(`Agent ${agentName} 不支持归档任务。`);
|
|
5960
6339
|
}
|
|
5961
6340
|
const wasCurrent = Boolean(local && this.store.getUserContext(contextKey)?.currentSessionId === local.localSessionId);
|
|
5962
|
-
|
|
6341
|
+
const allowUnmaterialized = Boolean(local && !local.lastTurnId && !local.lastTurnStatus
|
|
6342
|
+
&& !this.store.hasTurnAttemptsForSession(local.localSessionId)
|
|
6343
|
+
&& !this.store.getForkHistorySource(local.localSessionId)
|
|
6344
|
+
&& this.store.listTaskTurnGraphIndex(local.localSessionId).length === 0);
|
|
6345
|
+
await runtime.archiveRemoteSession(remote.id, { allowUnmaterialized });
|
|
5963
6346
|
const title = local?.title ?? remote.title ?? remote.preview ?? remote.id;
|
|
5964
6347
|
if (local) {
|
|
5965
6348
|
this.store.archiveSession(local.localSessionId);
|
|
@@ -6097,6 +6480,7 @@ export class ProxySessionController {
|
|
|
6097
6480
|
}
|
|
6098
6481
|
const localActiveTurnId = runtime.getSession(record.localSessionId)?.activeTurnId;
|
|
6099
6482
|
const runtimeOwnsActiveTurn = Boolean(localActiveTurnId)
|
|
6483
|
+
&& (!this.settingsOnlySessions.has(record.localSessionId) || Boolean(activity.activeTurnId))
|
|
6100
6484
|
&& (!activity.activeTurnId || activity.activeTurnId === localActiveTurnId);
|
|
6101
6485
|
const persistedTurnMatches = record.status === "running"
|
|
6102
6486
|
&& record.lastTurnStatus === "running"
|
|
@@ -6443,6 +6827,12 @@ export class ProxySessionController {
|
|
|
6443
6827
|
return agent;
|
|
6444
6828
|
}
|
|
6445
6829
|
async persistAgentExecutionDefaults(localSessionId, candidate) {
|
|
6830
|
+
const record = this.store.getSession(localSessionId);
|
|
6831
|
+
if (!record)
|
|
6832
|
+
throw new Error(`找不到任务:${localSessionId}`);
|
|
6833
|
+
return this.agentDefaultsOperations.run(record.agentName, () => this.persistAgentExecutionDefaultsNow(localSessionId, candidate));
|
|
6834
|
+
}
|
|
6835
|
+
async persistAgentExecutionDefaultsNow(localSessionId, candidate) {
|
|
6446
6836
|
const record = this.store.getSession(localSessionId);
|
|
6447
6837
|
if (!record)
|
|
6448
6838
|
throw new Error(`找不到任务:${localSessionId}`);
|
|
@@ -7367,6 +7757,14 @@ function executionSettingsTabValue(value) {
|
|
|
7367
7757
|
return value;
|
|
7368
7758
|
throw new Error("设置卡片的 tab 无效,请重新发送设置命令。");
|
|
7369
7759
|
}
|
|
7760
|
+
function modelCardPageValue(value) {
|
|
7761
|
+
if (value === undefined)
|
|
7762
|
+
return undefined;
|
|
7763
|
+
const page = typeof value === "number" || (typeof value === "string" && value.trim()) ? Number(value) : NaN;
|
|
7764
|
+
if (!Number.isSafeInteger(page) || page < 0)
|
|
7765
|
+
throw new Error("模型卡片页码无效,请重新发送 /model。");
|
|
7766
|
+
return page;
|
|
7767
|
+
}
|
|
7370
7768
|
function resetHistoryPageValue(value) {
|
|
7371
7769
|
const page = Number(value);
|
|
7372
7770
|
if (!Number.isSafeInteger(page) || page < 0)
|
|
@@ -7743,10 +8141,17 @@ function defaultResourcePromptFromCounts(imageCount, fileCount) {
|
|
|
7743
8141
|
return fileCount === 1 ? "请查看这个文件" : "请查看这些文件";
|
|
7744
8142
|
return imageCount === 1 ? "请查看这张图片" : "请查看这些图片";
|
|
7745
8143
|
}
|
|
7746
|
-
function
|
|
8144
|
+
function isCommandMessage(message) {
|
|
8145
|
+
const text = normalizeFeishuPostText(message.text).trimStart();
|
|
8146
|
+
return text.startsWith("/") || text.startsWith("!");
|
|
8147
|
+
}
|
|
8148
|
+
function isForwardAttachmentSource(message) {
|
|
7747
8149
|
return !message.mergedForwardMessageId
|
|
7748
|
-
&& message.
|
|
7749
|
-
&&
|
|
8150
|
+
&& !message.parentMessageId
|
|
8151
|
+
&& !message.contextKey.startsWith("console:")
|
|
8152
|
+
&& !isCommandMessage(message)
|
|
8153
|
+
&& (message.messageType === "post"
|
|
8154
|
+
|| (message.images?.length ?? 0) > 0 || (message.files?.length ?? 0) > 0);
|
|
7750
8155
|
}
|
|
7751
8156
|
function deduplicateImageReferences(images) {
|
|
7752
8157
|
const seen = new Set();
|
|
@@ -7813,7 +8218,8 @@ function isRetryableLlmTurnFailure(message) {
|
|
|
7813
8218
|
if (!normalized)
|
|
7814
8219
|
return false;
|
|
7815
8220
|
const permanentFailure = [
|
|
7816
|
-
|
|
8221
|
+
/\b(?:auth|authenticat\w*|unauthenticat\w*|authoriz\w*|unauthorized|forbidden)\b|invalid api key|incorrect api key/u,
|
|
8222
|
+
/(?:^|\D)(?:401|403)(?:\D|$)/u,
|
|
7817
8223
|
/insufficient[_ -]?quota|quota (?:exceeded|exhausted)|usage limit|billing|credit balance/u,
|
|
7818
8224
|
/context (?:length|window)|maximum context|too many tokens|token limit/u,
|
|
7819
8225
|
/invalid request|bad request|invalid argument|malformed|unsupported|not supported/u,
|