@keyou007/agent-bot 0.1.10 → 0.1.11
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 +15 -0
- package/README.md +16 -10
- package/README.zh.md +16 -10
- package/assets/agent-bot-logo.png +0 -0
- package/dist/acp/AcpSessionManager.js +78 -46
- package/dist/acp/AcpSessionManager.js.map +1 -1
- package/dist/codex/AppServerConnection.js +15 -1
- package/dist/codex/AppServerConnection.js.map +1 -1
- package/dist/codex/CodexLocalActivityDetector.js +4 -1
- package/dist/codex/CodexLocalActivityDetector.js.map +1 -1
- package/dist/codex/CodexProcessManager.js +1 -0
- package/dist/codex/CodexProcessManager.js.map +1 -1
- package/dist/codex/CodexRuntime.js +77 -2
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/commands/CommandRouter.js +64 -49
- package/dist/commands/CommandRouter.js.map +1 -1
- package/dist/console/ConsoleTurnPresenter.js +1 -1
- package/dist/console/ConsoleTurnPresenter.js.map +1 -1
- package/dist/feishu/CardRenderer.js +345 -11
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuMessageClient.js +51 -5
- package/dist/feishu/FeishuMessageClient.js.map +1 -1
- package/dist/feishu/FeishuTurnPresenter.js +8 -3
- package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
- package/dist/index.js +18 -17
- package/dist/index.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +589 -205
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/runtime/AcpRuntimeAdapter.js +1 -0
- package/dist/runtime/AcpRuntimeAdapter.js.map +1 -1
- package/dist/runtime/AgentRuntimeRegistry.js +7 -4
- package/dist/runtime/AgentRuntimeRegistry.js.map +1 -1
- package/dist/runtime/createAgentRuntimeRegistry.js +22 -0
- package/dist/runtime/createAgentRuntimeRegistry.js.map +1 -0
- package/dist/startup/SessionMetadataHydrator.js +2 -2
- package/dist/startup/SessionMetadataHydrator.js.map +1 -1
- package/dist/startup/StartupNotifier.js +1 -0
- package/dist/startup/StartupNotifier.js.map +1 -1
- package/dist/state/StateStore.js +21 -15
- package/dist/state/StateStore.js.map +1 -1
- package/dist/supervision/SafeRestartScheduler.js +5 -0
- package/dist/supervision/SafeRestartScheduler.js.map +1 -1
- package/docs/technical-reference.md +15 -5
- package/docs/technical-reference.zh.md +15 -5
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
- package/skills/agent-bot/SKILL.md +13 -3
- package/src/acp/AcpSessionManager.ts +73 -54
- package/src/codex/AppServerConnection.ts +21 -1
- package/src/codex/CodexLocalActivityDetector.ts +8 -1
- package/src/codex/CodexProcessManager.ts +1 -0
- package/src/codex/CodexRuntime.ts +91 -2
- package/src/commands/CommandRouter.ts +71 -49
- package/src/commands/commandTypes.ts +6 -11
- package/src/console/ConsoleTurnPresenter.ts +1 -1
- package/src/feishu/CardRenderer.ts +418 -11
- package/src/feishu/FeishuMessageClient.ts +66 -3
- package/src/feishu/FeishuTurnPresenter.ts +7 -2
- package/src/index.ts +21 -22
- package/src/proxy/ProxySessionController.ts +706 -208
- package/src/runtime/AcpRuntimeAdapter.ts +1 -0
- package/src/runtime/AgentRuntimeRegistry.ts +7 -6
- package/src/runtime/createAgentRuntimeRegistry.ts +25 -0
- package/src/runtime/types.ts +18 -0
- package/src/startup/SessionMetadataHydrator.ts +2 -2
- package/src/startup/StartupNotifier.ts +1 -0
- package/src/state/StateStore.ts +29 -15
- package/src/supervision/SafeRestartScheduler.ts +5 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import { createProjectlessWorkspace, detectProjectlessWorkspace } from "../codex/ProjectlessWorkspace.js";
|
|
4
|
+
import { resolveUserPath } from "../config/paths.js";
|
|
4
5
|
import { CommandRouter } from "../commands/CommandRouter.js";
|
|
5
6
|
import { baseChatContextKey, isThreadContextKey } from "../feishu/contextKey.js";
|
|
6
|
-
import { CardRenderer } from "../feishu/CardRenderer.js";
|
|
7
|
+
import { CardRenderer, } from "../feishu/CardRenderer.js";
|
|
7
8
|
import { generateGroupAvatarPng, resolveGroupAvatarProjectName } from "../feishu/GroupAvatarGenerator.js";
|
|
8
9
|
import { createId } from "../utils/id.js";
|
|
9
10
|
import { asInlineCode, codeBlock, truncateMiddle, truncateText } from "../utils/markdown.js";
|
|
@@ -14,6 +15,7 @@ const MESSAGE_COMPLETED_REACTION = "DONE";
|
|
|
14
15
|
const MESSAGE_FAILED_REACTION = "ERROR";
|
|
15
16
|
const MESSAGE_CANCELLED_REACTION = "CrossMark";
|
|
16
17
|
const SESSION_PAGE_SIZE = 5;
|
|
18
|
+
const REMOTE_SESSION_REFERENCE_PREFIX = "agent-runtime:";
|
|
17
19
|
export class ProxySessionController {
|
|
18
20
|
config;
|
|
19
21
|
store;
|
|
@@ -46,8 +48,8 @@ export class ProxySessionController {
|
|
|
46
48
|
if (interruptedAcpSessions.length > 0) {
|
|
47
49
|
this.logger.warn({ sessionIds: interruptedAcpSessions.map((session) => session.localSessionId) }, "Marked interrupted ACP sessions as failed after process restart.");
|
|
48
50
|
}
|
|
49
|
-
for (const
|
|
50
|
-
this.unsubscribe.push(
|
|
51
|
+
for (const [, runtime] of this.runtimes.entries()) {
|
|
52
|
+
this.unsubscribe.push(runtime.onEvent((event) => {
|
|
51
53
|
void this.handleRuntimeEvent(event).catch((error) => {
|
|
52
54
|
this.logger.warn({ error, event }, "Failed to present runtime event.");
|
|
53
55
|
});
|
|
@@ -102,7 +104,7 @@ export class ProxySessionController {
|
|
|
102
104
|
let command;
|
|
103
105
|
try {
|
|
104
106
|
command = imageCount > 0 && !message.text.trimStart().startsWith("/")
|
|
105
|
-
? { type: "prompt", text: message.text.trim() || "
|
|
107
|
+
? { type: "prompt", text: message.text.trim() || "请查看这张图片" }
|
|
106
108
|
: this.router.parse(message.text);
|
|
107
109
|
}
|
|
108
110
|
catch (error) {
|
|
@@ -169,7 +171,7 @@ export class ProxySessionController {
|
|
|
169
171
|
else if (kind === "activity_history") {
|
|
170
172
|
const requestedPage = String(scopedAction.value.page ?? "0");
|
|
171
173
|
const numericPage = Number(requestedPage);
|
|
172
|
-
await this.outbound.showActivityPage(contextKey, String(scopedAction.value.turnId ?? ""), requestedPage === "latest" ? "latest" : Number.isFinite(numericPage) ? numericPage : 0, scopedAction.messageId);
|
|
174
|
+
await this.outbound.showActivityPage(contextKey, String(scopedAction.value.turnId ?? ""), requestedPage === "latest" ? "latest" : Number.isFinite(numericPage) ? numericPage : 0, requiredCardMessageId(scopedAction.messageId));
|
|
173
175
|
}
|
|
174
176
|
else if (kind === "turn_cancel") {
|
|
175
177
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
@@ -236,6 +238,43 @@ export class ProxySessionController {
|
|
|
236
238
|
else if (kind === "session_status_refresh") {
|
|
237
239
|
await this.refreshStatusCardFromAction(scopedAction);
|
|
238
240
|
}
|
|
241
|
+
else if (kind === "settings_tab_open") {
|
|
242
|
+
await this.openExecutionSettings(contextKey, executionSettingsTabValue(scopedAction.value.tab), {
|
|
243
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
244
|
+
updateMessageId: scopedAction.messageId,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
else if (kind === "settings_agent_select") {
|
|
248
|
+
await this.setDefaultAgent(contextKey, String(scopedAction.value.agent ?? ""), {
|
|
249
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
250
|
+
updateMessageId: scopedAction.messageId,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
else if (kind === "settings_provider_select") {
|
|
254
|
+
await this.selectProvider(contextKey, String(scopedAction.value.provider ?? ""), {
|
|
255
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
256
|
+
updateMessageId: scopedAction.messageId,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
else if (kind === "settings_model_select") {
|
|
260
|
+
await this.model(contextKey, String(scopedAction.value.model ?? ""), {
|
|
261
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
262
|
+
updateMessageId: scopedAction.messageId,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
else if (kind === "settings_thinking_select") {
|
|
266
|
+
await this.thinking(contextKey, String(scopedAction.value.effort ?? ""), {
|
|
267
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
268
|
+
updateMessageId: scopedAction.messageId,
|
|
269
|
+
expectedModel: String(scopedAction.value.model ?? ""),
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
else if (kind === "settings_permission_select") {
|
|
273
|
+
await this.permissions(contextKey, permissionModeValue(scopedAction.value.permissionMode), {
|
|
274
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
275
|
+
updateMessageId: scopedAction.messageId,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
239
278
|
else if (kind === "model_select") {
|
|
240
279
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
241
280
|
const model = String(scopedAction.value.model ?? "");
|
|
@@ -261,6 +300,32 @@ export class ProxySessionController {
|
|
|
261
300
|
expectedModel: model,
|
|
262
301
|
});
|
|
263
302
|
}
|
|
303
|
+
else if (kind === "provider_open") {
|
|
304
|
+
await this.openExecutionSettings(contextKey, "provider", {
|
|
305
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
306
|
+
updateMessageId: scopedAction.messageId,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
else if (kind === "provider_select" || kind === "provider_model_open") {
|
|
310
|
+
await this.openProviderModelSelector(contextKey, String(scopedAction.value.provider ?? ""), permissionModeValue(scopedAction.value.permissionMode), String(scopedAction.value.sessionId ?? ""), requiredCardMessageId(scopedAction.messageId));
|
|
311
|
+
}
|
|
312
|
+
else if (kind === "provider_model_select" || kind === "provider_reasoning_open") {
|
|
313
|
+
await this.openProviderReasoningSelector(contextKey, String(scopedAction.value.provider ?? ""), String(scopedAction.value.model ?? ""), permissionModeValue(scopedAction.value.permissionMode), String(scopedAction.value.sessionId ?? ""), requiredCardMessageId(scopedAction.messageId));
|
|
314
|
+
}
|
|
315
|
+
else if (kind === "provider_reasoning_select") {
|
|
316
|
+
await this.openProviderPermissionSelector(contextKey, String(scopedAction.value.provider ?? ""), String(scopedAction.value.model ?? ""), String(scopedAction.value.effort ?? ""), permissionModeValue(scopedAction.value.permissionMode), String(scopedAction.value.sessionId ?? ""), requiredCardMessageId(scopedAction.messageId));
|
|
317
|
+
}
|
|
318
|
+
else if (kind === "provider_permission_select") {
|
|
319
|
+
await this.applyProviderSettings(contextKey, {
|
|
320
|
+
provider: String(scopedAction.value.provider ?? ""),
|
|
321
|
+
model: String(scopedAction.value.model ?? ""),
|
|
322
|
+
effort: String(scopedAction.value.effort ?? ""),
|
|
323
|
+
mode: permissionModeValue(scopedAction.value.permissionMode),
|
|
324
|
+
}, {
|
|
325
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
326
|
+
updateMessageId: scopedAction.messageId,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
264
329
|
else if (kind === "approval") {
|
|
265
330
|
await this.resolveApproval(scopedAction);
|
|
266
331
|
}
|
|
@@ -304,7 +369,7 @@ export class ProxySessionController {
|
|
|
304
369
|
throw new Error(`Task not found: ${localSessionId}`);
|
|
305
370
|
let remote;
|
|
306
371
|
if (record.remoteSessionId) {
|
|
307
|
-
const runtime = this.runtimes.forAgent(
|
|
372
|
+
const runtime = this.runtimes.forAgent(record.agentName);
|
|
308
373
|
if (runtime.readRemoteSession) {
|
|
309
374
|
try {
|
|
310
375
|
remote = await runtime.readRemoteSession(record.remoteSessionId);
|
|
@@ -348,7 +413,7 @@ export class ProxySessionController {
|
|
|
348
413
|
const record = this.store.getSession(localSessionId);
|
|
349
414
|
if (!record || record.status === "closed")
|
|
350
415
|
throw new Error(`Task not found: ${localSessionId}`);
|
|
351
|
-
const runtime = this.runtimes.forAgent(
|
|
416
|
+
const runtime = this.runtimes.forAgent(record.agentName);
|
|
352
417
|
const activeTurnId = runtime.getSession(localSessionId)?.activeTurnId;
|
|
353
418
|
const routedContextKey = this.outbound.getSessionContextKey(localSessionId);
|
|
354
419
|
const responseContextKey = (activeTurnId ? this.store.getTurnContextKey(activeTurnId) ?? routedContextKey : undefined)
|
|
@@ -402,7 +467,7 @@ export class ProxySessionController {
|
|
|
402
467
|
return explicit;
|
|
403
468
|
const sessionReference = typeof action.value.sessionId === "string" ? action.value.sessionId : undefined;
|
|
404
469
|
const session = sessionReference
|
|
405
|
-
? this.store.getSession(sessionReference) ?? this.
|
|
470
|
+
? this.store.getSession(sessionReference) ?? this.findStoredSessionByReference(sessionReference)
|
|
406
471
|
: undefined;
|
|
407
472
|
if (session && baseChatContextKey(session.contextKey) === baseChatContextKey(action.contextKey)) {
|
|
408
473
|
return session.contextKey;
|
|
@@ -424,10 +489,17 @@ export class ProxySessionController {
|
|
|
424
489
|
if (command.projectless && this.ensureAgent(context.defaultAgent).kind !== "codex") {
|
|
425
490
|
throw new Error("/new --nodir 仅支持 Codex Agent。");
|
|
426
491
|
}
|
|
427
|
-
await this.createSession(contextKey, context.defaultAgent, command.projectless
|
|
492
|
+
await this.createSession(contextKey, context.defaultAgent, command.projectless
|
|
493
|
+
? undefined
|
|
494
|
+
: command.cwd === undefined
|
|
495
|
+
? this.inheritedNewTaskCwd(contextKey)
|
|
496
|
+
: resolveUserPath(command.cwd), true, false, undefined, undefined, command.title, this.inheritedExecutionSettings(contextKey, context.defaultAgent));
|
|
428
497
|
return;
|
|
429
498
|
case "newgroup":
|
|
430
|
-
|
|
499
|
+
if (command.projectless && this.ensureAgent(context.defaultAgent).kind !== "codex") {
|
|
500
|
+
throw new Error("/newgroup --nodir 仅支持 Codex Agent。");
|
|
501
|
+
}
|
|
502
|
+
await this.createFeishuGroup(contextKey, context.defaultAgent, command.title, userId, command.cwd === undefined ? undefined : resolveUserPath(command.cwd), command.projectless === true);
|
|
431
503
|
return;
|
|
432
504
|
case "forkgroup":
|
|
433
505
|
await this.forkCurrentSessionToFeishuGroup(contextKey, command.title, userId, incomingMessage);
|
|
@@ -438,7 +510,6 @@ export class ProxySessionController {
|
|
|
438
510
|
case "title":
|
|
439
511
|
await this.setTitle(contextKey, command.title);
|
|
440
512
|
return;
|
|
441
|
-
case "ask":
|
|
442
513
|
case "prompt":
|
|
443
514
|
await this.prompt(contextKey, command.text, messageId, replyTarget, localImagePaths);
|
|
444
515
|
return;
|
|
@@ -455,11 +526,7 @@ export class ProxySessionController {
|
|
|
455
526
|
if (command.agent)
|
|
456
527
|
await this.setDefaultAgent(contextKey, command.agent);
|
|
457
528
|
else
|
|
458
|
-
await this.
|
|
459
|
-
return;
|
|
460
|
-
case "use":
|
|
461
|
-
await this.setDefaultAgent(contextKey, command.agent);
|
|
462
|
-
await this.createSession(contextKey, command.agent, command.cwd, true, false);
|
|
529
|
+
await this.openAgentSettings(contextKey);
|
|
463
530
|
return;
|
|
464
531
|
case "status":
|
|
465
532
|
await this.status(contextKey, command.sessionId);
|
|
@@ -470,20 +537,19 @@ export class ProxySessionController {
|
|
|
470
537
|
case "restart":
|
|
471
538
|
if (!this.lifecycle)
|
|
472
539
|
throw new Error("当前运行方式不支持自动重启。");
|
|
473
|
-
await this.lifecycle.restart(contextKey);
|
|
540
|
+
await this.lifecycle.restart(contextKey, command.force === true);
|
|
474
541
|
return;
|
|
475
542
|
case "model":
|
|
476
|
-
await this.
|
|
543
|
+
await this.openExecutionSettings(contextKey, "model");
|
|
544
|
+
return;
|
|
545
|
+
case "provider":
|
|
546
|
+
await this.openProviderSettings(contextKey);
|
|
477
547
|
return;
|
|
478
548
|
case "thinking":
|
|
479
|
-
await this.
|
|
549
|
+
await this.openExecutionSettings(contextKey, "thinking");
|
|
480
550
|
return;
|
|
481
551
|
case "permissions":
|
|
482
|
-
await this.
|
|
483
|
-
return;
|
|
484
|
-
case "modes":
|
|
485
|
-
case "mode":
|
|
486
|
-
await this.outbound.sendText(contextKey, "当前统一运行时不再暴露 ACP modes;Codex 请使用 /model 和 /permissions。");
|
|
552
|
+
await this.openExecutionSettings(contextKey, "permission");
|
|
487
553
|
return;
|
|
488
554
|
case "help":
|
|
489
555
|
await this.help(contextKey);
|
|
@@ -546,7 +612,7 @@ export class ProxySessionController {
|
|
|
546
612
|
await this.promptSession(record, contextKey, text, messageId, replyTarget, localImagePaths);
|
|
547
613
|
}
|
|
548
614
|
async promptSession(record, contextKey, text, messageId, replyTarget, localImagePaths) {
|
|
549
|
-
const configuredRuntime = this.runtimes.forAgent(
|
|
615
|
+
const configuredRuntime = this.runtimes.forAgent(record.agentName);
|
|
550
616
|
if (!configuredRuntime.getSession(record.localSessionId)) {
|
|
551
617
|
this.outbound.registerSession(record.localSessionId, contextKey, record.title, record.cwd);
|
|
552
618
|
await this.outbound.startPendingTurn(record.localSessionId, contextKey, record.title, replyTarget, text);
|
|
@@ -760,7 +826,7 @@ export class ProxySessionController {
|
|
|
760
826
|
async forkThreadSession(message) {
|
|
761
827
|
const { anchor, source, snapshot } = this.resolveThreadForkAnchor(message);
|
|
762
828
|
const agent = this.ensureAgent(source.agentName);
|
|
763
|
-
const runtime = this.runtimes.forAgent(
|
|
829
|
+
const runtime = this.runtimes.forAgent(source.agentName);
|
|
764
830
|
if (runtime.kind !== "codex" || !runtime.forkSession) {
|
|
765
831
|
throw new Error("当前 Codex 运行时不支持 fork 任务。");
|
|
766
832
|
}
|
|
@@ -779,6 +845,7 @@ export class ProxySessionController {
|
|
|
779
845
|
this.store.updateRuntimeSession(localSessionId, {
|
|
780
846
|
runtimeKind: "codex",
|
|
781
847
|
title: forkTitle,
|
|
848
|
+
modelProvider: source.modelProvider,
|
|
782
849
|
model: source.model,
|
|
783
850
|
reasoningEffort: source.reasoningEffort,
|
|
784
851
|
permissionMode: source.permissionMode ?? "auto",
|
|
@@ -793,6 +860,7 @@ export class ProxySessionController {
|
|
|
793
860
|
agentName: source.agentName,
|
|
794
861
|
cwd: source.cwd,
|
|
795
862
|
title: forkTitle,
|
|
863
|
+
modelProvider: source.modelProvider,
|
|
796
864
|
model: source.model,
|
|
797
865
|
reasoningEffort: source.reasoningEffort,
|
|
798
866
|
permissionMode: source.permissionMode ?? "auto",
|
|
@@ -869,7 +937,7 @@ export class ProxySessionController {
|
|
|
869
937
|
if (direct?.status === "closed") {
|
|
870
938
|
throw new Error(`找不到任务:${taskId}`);
|
|
871
939
|
}
|
|
872
|
-
const global = direct ?? this.
|
|
940
|
+
const global = direct ?? this.findStoredSessionByReference(taskId);
|
|
873
941
|
source = global ? { ...global, contextKey } : undefined;
|
|
874
942
|
}
|
|
875
943
|
if (source && (!source.remoteSessionId || !this.isCodexSession(source))) {
|
|
@@ -877,27 +945,29 @@ export class ProxySessionController {
|
|
|
877
945
|
}
|
|
878
946
|
let runtime;
|
|
879
947
|
let agentName;
|
|
948
|
+
let remote;
|
|
880
949
|
if (source) {
|
|
881
950
|
agentName = source.agentName;
|
|
882
|
-
runtime = this.runtimes.forAgent(
|
|
951
|
+
runtime = this.runtimes.forAgent(agentName);
|
|
883
952
|
}
|
|
884
953
|
else {
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
runtime =
|
|
954
|
+
if (!taskId)
|
|
955
|
+
throw new Error("缺少要 fork 的 Codex 任务 ID。");
|
|
956
|
+
const resolved = await this.resolveRemoteCodexSession(taskId);
|
|
957
|
+
agentName = resolved.agentName;
|
|
958
|
+
runtime = resolved.runtime;
|
|
959
|
+
remote = resolved.remote;
|
|
890
960
|
}
|
|
891
961
|
if (runtime.kind !== "codex" || !runtime.forkSession) {
|
|
892
962
|
throw new Error("当前 Codex 运行时不支持 fork 任务。");
|
|
893
963
|
}
|
|
894
|
-
const remoteSessionId = source?.remoteSessionId ?? taskId;
|
|
964
|
+
const remoteSessionId = source?.remoteSessionId ?? remote?.id ?? taskId;
|
|
895
965
|
if (!remoteSessionId)
|
|
896
966
|
throw new Error("当前任务尚未创建 Codex 任务 ID,暂时不能 fork。");
|
|
897
967
|
if (!runtime.readRemoteSession && !source) {
|
|
898
968
|
throw new Error("当前 Codex 运行时不支持读取指定任务。");
|
|
899
969
|
}
|
|
900
|
-
|
|
970
|
+
remote ??= runtime.readRemoteSession
|
|
901
971
|
? await runtime.readRemoteSession(remoteSessionId)
|
|
902
972
|
: undefined;
|
|
903
973
|
const latestTurnId = remote?.lastTurnId ?? source?.lastTurnId;
|
|
@@ -921,7 +991,8 @@ export class ProxySessionController {
|
|
|
921
991
|
throw new Error("指定的 Codex 任务没有可用的工作目录,暂时不能 fork。");
|
|
922
992
|
const sourceTitle = remote?.title ?? source?.title ?? remote?.preview;
|
|
923
993
|
const forkTitle = normalizeTaskTitle(requestedTitle) ?? this.store.nextForkTitle(sourceTitle);
|
|
924
|
-
const
|
|
994
|
+
const modelProvider = remote?.modelProvider ?? source?.modelProvider;
|
|
995
|
+
const model = remote?.model ?? source?.model;
|
|
925
996
|
const reasoningEffort = source?.reasoningEffort;
|
|
926
997
|
const permissionMode = source?.permissionMode ?? "auto";
|
|
927
998
|
return {
|
|
@@ -933,6 +1004,7 @@ export class ProxySessionController {
|
|
|
933
1004
|
lastTurnId,
|
|
934
1005
|
cwd,
|
|
935
1006
|
forkTitle,
|
|
1007
|
+
modelProvider,
|
|
936
1008
|
model,
|
|
937
1009
|
reasoningEffort,
|
|
938
1010
|
permissionMode,
|
|
@@ -975,7 +1047,7 @@ export class ProxySessionController {
|
|
|
975
1047
|
if (!source.remoteSessionId || !this.isCodexSession(source)) {
|
|
976
1048
|
throw new Error(`${sourceLabel}不是可 fork 的 Codex 任务。`);
|
|
977
1049
|
}
|
|
978
|
-
const runtime = this.runtimes.forAgent(
|
|
1050
|
+
const runtime = this.runtimes.forAgent(source.agentName);
|
|
979
1051
|
if (runtime.kind !== "codex" || !runtime.forkSession) {
|
|
980
1052
|
throw new Error("当前 Codex 运行时不支持 fork 任务。");
|
|
981
1053
|
}
|
|
@@ -996,6 +1068,7 @@ export class ProxySessionController {
|
|
|
996
1068
|
lastTurnId,
|
|
997
1069
|
cwd: source.cwd,
|
|
998
1070
|
forkTitle,
|
|
1071
|
+
modelProvider: source.modelProvider,
|
|
999
1072
|
model: source.model,
|
|
1000
1073
|
reasoningEffort: source.reasoningEffort,
|
|
1001
1074
|
permissionMode: source.permissionMode ?? "auto",
|
|
@@ -1018,6 +1091,7 @@ export class ProxySessionController {
|
|
|
1018
1091
|
this.store.updateRuntimeSession(localSessionId, {
|
|
1019
1092
|
runtimeKind: "codex",
|
|
1020
1093
|
title: plan.forkTitle,
|
|
1094
|
+
modelProvider: plan.modelProvider,
|
|
1021
1095
|
model: plan.model,
|
|
1022
1096
|
reasoningEffort: plan.reasoningEffort,
|
|
1023
1097
|
permissionMode: plan.permissionMode,
|
|
@@ -1030,6 +1104,7 @@ export class ProxySessionController {
|
|
|
1030
1104
|
agentName: plan.agentName,
|
|
1031
1105
|
cwd: plan.cwd,
|
|
1032
1106
|
title: plan.forkTitle,
|
|
1107
|
+
modelProvider: plan.modelProvider,
|
|
1033
1108
|
model: plan.model,
|
|
1034
1109
|
reasoningEffort: plan.reasoningEffort,
|
|
1035
1110
|
permissionMode: plan.permissionMode,
|
|
@@ -1077,26 +1152,25 @@ export class ProxySessionController {
|
|
|
1077
1152
|
if (direct?.status === "closed") {
|
|
1078
1153
|
throw new Error(`找不到任务:${taskId}`);
|
|
1079
1154
|
}
|
|
1080
|
-
const source = direct ?? this.
|
|
1155
|
+
const source = direct ?? this.findStoredSessionByReference(taskId);
|
|
1081
1156
|
if (source && !this.isCodexSession(source)) {
|
|
1082
1157
|
throw new Error("指定任务不是 Codex 任务,暂时不能按项目创建新任务。");
|
|
1083
1158
|
}
|
|
1084
1159
|
let agentName;
|
|
1085
1160
|
let runtime;
|
|
1161
|
+
let remote;
|
|
1086
1162
|
if (source) {
|
|
1087
1163
|
agentName = source.agentName;
|
|
1088
|
-
runtime = this.runtimes.forAgent(
|
|
1164
|
+
runtime = this.runtimes.forAgent(agentName);
|
|
1089
1165
|
}
|
|
1090
1166
|
else {
|
|
1091
|
-
const
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
runtime = this.runtimes.get("codex");
|
|
1167
|
+
const resolved = await this.resolveRemoteCodexSession(taskId);
|
|
1168
|
+
agentName = resolved.agentName;
|
|
1169
|
+
runtime = resolved.runtime;
|
|
1170
|
+
remote = resolved.remote;
|
|
1096
1171
|
}
|
|
1097
|
-
const remoteSessionId = source?.remoteSessionId ?? taskId;
|
|
1098
|
-
|
|
1099
|
-
if (runtime.readRemoteSession && remoteSessionId) {
|
|
1172
|
+
const remoteSessionId = source?.remoteSessionId ?? remote?.id ?? taskId;
|
|
1173
|
+
if (!remote && runtime.readRemoteSession && remoteSessionId) {
|
|
1100
1174
|
try {
|
|
1101
1175
|
remote = await runtime.readRemoteSession(remoteSessionId);
|
|
1102
1176
|
}
|
|
@@ -1111,6 +1185,7 @@ export class ProxySessionController {
|
|
|
1111
1185
|
throw new Error("指定的 Codex 任务没有可用的工作目录,暂时不能按项目创建新任务。");
|
|
1112
1186
|
}
|
|
1113
1187
|
const executionSettings = {
|
|
1188
|
+
modelProvider: remote?.modelProvider ?? source?.modelProvider,
|
|
1114
1189
|
model: remote?.model ?? source?.model,
|
|
1115
1190
|
reasoningEffort: remote?.reasoningEffort ?? source?.reasoningEffort,
|
|
1116
1191
|
permissionMode: remote?.permissionMode ?? source?.permissionMode ?? "auto",
|
|
@@ -1156,9 +1231,10 @@ export class ProxySessionController {
|
|
|
1156
1231
|
? createProjectlessWorkspace({ prompt: initialTitle }).cwd
|
|
1157
1232
|
: path.resolve(cwd ?? this.config.defaults.cwd);
|
|
1158
1233
|
const record = this.store.createSession({ localSessionId, contextKey, agentName, cwd: sessionCwd, status: "starting" });
|
|
1159
|
-
if (initialTitle || executionSettings.model || executionSettings.reasoningEffort || executionSettings.permissionMode) {
|
|
1234
|
+
if (initialTitle || executionSettings.modelProvider || executionSettings.model || executionSettings.reasoningEffort || executionSettings.permissionMode) {
|
|
1160
1235
|
this.store.updateRuntimeSession(localSessionId, {
|
|
1161
1236
|
title: initialTitle,
|
|
1237
|
+
modelProvider: executionSettings.modelProvider,
|
|
1162
1238
|
model: executionSettings.model,
|
|
1163
1239
|
reasoningEffort: executionSettings.reasoningEffort,
|
|
1164
1240
|
permissionMode: executionSettings.permissionMode,
|
|
@@ -1166,7 +1242,7 @@ export class ProxySessionController {
|
|
|
1166
1242
|
}
|
|
1167
1243
|
this.store.setCurrentSession(contextKey, localSessionId);
|
|
1168
1244
|
this.outbound.registerSession(localSessionId, contextKey, initialTitle, sessionCwd);
|
|
1169
|
-
const runtime = this.runtimes.forAgent(
|
|
1245
|
+
const runtime = this.runtimes.forAgent(agentName);
|
|
1170
1246
|
try {
|
|
1171
1247
|
if (prepareTurn) {
|
|
1172
1248
|
await this.outbound.startPendingTurn(localSessionId, contextKey, initialTitle, replyTarget, prompt);
|
|
@@ -1176,6 +1252,7 @@ export class ProxySessionController {
|
|
|
1176
1252
|
agentName,
|
|
1177
1253
|
cwd: sessionCwd,
|
|
1178
1254
|
title: initialTitle,
|
|
1255
|
+
modelProvider: executionSettings.modelProvider,
|
|
1179
1256
|
model: executionSettings.model,
|
|
1180
1257
|
reasoningEffort: executionSettings.reasoningEffort,
|
|
1181
1258
|
permissionMode: executionSettings.permissionMode ?? "auto",
|
|
@@ -1196,11 +1273,14 @@ export class ProxySessionController {
|
|
|
1196
1273
|
throw error;
|
|
1197
1274
|
}
|
|
1198
1275
|
}
|
|
1199
|
-
async createFeishuGroup(sourceContextKey, agentName, requestedTitle, userId) {
|
|
1276
|
+
async createFeishuGroup(sourceContextKey, agentName, requestedTitle, userId, requestedProjectCwd, forceProjectless = false) {
|
|
1200
1277
|
const source = this.currentSession(sourceContextKey);
|
|
1201
|
-
const boundProjectCwd =
|
|
1278
|
+
const boundProjectCwd = forceProjectless
|
|
1279
|
+
? undefined
|
|
1280
|
+
: requestedProjectCwd ?? this.currentProjectCwd(sourceContextKey);
|
|
1202
1281
|
const executionSettings = source?.agentName === agentName
|
|
1203
1282
|
? {
|
|
1283
|
+
modelProvider: source.modelProvider,
|
|
1204
1284
|
model: source.model,
|
|
1205
1285
|
reasoningEffort: source.reasoningEffort,
|
|
1206
1286
|
permissionMode: source.permissionMode,
|
|
@@ -1232,8 +1312,10 @@ export class ProxySessionController {
|
|
|
1232
1312
|
"群和新任务已创建。",
|
|
1233
1313
|
`当前任务:${taskDescription}`,
|
|
1234
1314
|
`当前 Project 目录:${boundProjectCwd ?? "未绑定(Projectless)"}`,
|
|
1315
|
+
`当前 Provider:${task.modelProvider ?? "Codex 默认"}`,
|
|
1235
1316
|
`当前模型:${task.model ?? "默认"}`,
|
|
1236
1317
|
`思考强度:${task.reasoningEffort ?? "自动"}`,
|
|
1318
|
+
`权限类型:${task.permissionMode === "confirm" ? "执行前确认" : "自动执行"}`,
|
|
1237
1319
|
].join("\n"));
|
|
1238
1320
|
await this.outbound.sendText(sourceContextKey, `已创建飞书群:${group.name},并创建新任务 ${taskDescription}。`);
|
|
1239
1321
|
}
|
|
@@ -1275,6 +1357,7 @@ export class ProxySessionController {
|
|
|
1275
1357
|
`已从${prepared.sourceDescription}创建分支。`,
|
|
1276
1358
|
`当前任务:${taskDescription}`,
|
|
1277
1359
|
`当前 Project 目录:${boundProjectCwd ?? "未绑定(Projectless)"}`,
|
|
1360
|
+
`当前 Provider:${forked.session.modelProvider ?? "Codex 默认"}`,
|
|
1278
1361
|
`当前模型:${forked.session.model ?? "默认"}`,
|
|
1279
1362
|
`思考强度:${forked.session.reasoningEffort ?? "自动"}`,
|
|
1280
1363
|
`权限类型:${forked.session.permissionMode === "confirm" ? "执行前确认" : "自动执行"}`,
|
|
@@ -1300,7 +1383,7 @@ export class ProxySessionController {
|
|
|
1300
1383
|
}
|
|
1301
1384
|
async loadSession(record) {
|
|
1302
1385
|
const agent = this.ensureAgent(record.agentName);
|
|
1303
|
-
const runtime = this.runtimes.forAgent(
|
|
1386
|
+
const runtime = this.runtimes.forAgent(record.agentName);
|
|
1304
1387
|
const existing = runtime.getSession(record.localSessionId);
|
|
1305
1388
|
if (existing)
|
|
1306
1389
|
return { record, runtime, session: existing };
|
|
@@ -1329,6 +1412,7 @@ export class ProxySessionController {
|
|
|
1329
1412
|
agentName: record.agentName,
|
|
1330
1413
|
cwd: record.cwd,
|
|
1331
1414
|
title: record.title,
|
|
1415
|
+
modelProvider: record.modelProvider,
|
|
1332
1416
|
model: record.model,
|
|
1333
1417
|
reasoningEffort: record.reasoningEffort,
|
|
1334
1418
|
permissionMode,
|
|
@@ -1348,6 +1432,7 @@ export class ProxySessionController {
|
|
|
1348
1432
|
agentName: record.agentName,
|
|
1349
1433
|
cwd: record.cwd,
|
|
1350
1434
|
title: record.title,
|
|
1435
|
+
modelProvider: record.modelProvider,
|
|
1351
1436
|
model: record.model,
|
|
1352
1437
|
reasoningEffort: record.reasoningEffort,
|
|
1353
1438
|
permissionMode,
|
|
@@ -1360,6 +1445,7 @@ export class ProxySessionController {
|
|
|
1360
1445
|
agentName: record.agentName,
|
|
1361
1446
|
cwd: record.cwd,
|
|
1362
1447
|
title: record.title,
|
|
1448
|
+
modelProvider: record.modelProvider,
|
|
1363
1449
|
model: record.model,
|
|
1364
1450
|
reasoningEffort: record.reasoningEffort,
|
|
1365
1451
|
permissionMode,
|
|
@@ -1382,6 +1468,7 @@ export class ProxySessionController {
|
|
|
1382
1468
|
runtimeKind: session.runtimeKind,
|
|
1383
1469
|
remoteSessionId: session.remoteSessionId,
|
|
1384
1470
|
title: session.title,
|
|
1471
|
+
modelProvider: session.modelProvider,
|
|
1385
1472
|
model: session.model,
|
|
1386
1473
|
reasoningEffort: session.reasoningEffort,
|
|
1387
1474
|
permissionMode: session.permissionMode,
|
|
@@ -1407,38 +1494,36 @@ export class ProxySessionController {
|
|
|
1407
1494
|
lastTurnStatus: event.type === "turn_completed" ? "completed" : event.type === "turn_cancelled" ? "cancelled" : "failed",
|
|
1408
1495
|
});
|
|
1409
1496
|
}
|
|
1497
|
+
let presentationError;
|
|
1410
1498
|
try {
|
|
1411
1499
|
await this.outbound.onEvent(event);
|
|
1412
|
-
if (event.type === "turn_completed" || event.type === "turn_cancelled" || event.type === "turn_failed") {
|
|
1413
|
-
const terminalStatus = event.type === "turn_completed"
|
|
1414
|
-
? "completed"
|
|
1415
|
-
: event.type === "turn_cancelled"
|
|
1416
|
-
? "cancelled"
|
|
1417
|
-
: "failed";
|
|
1418
|
-
const latest = this.store.getSession(event.sessionId);
|
|
1419
|
-
if (latest?.lastTurnId === event.turnId && latest.lastTurnStatus === terminalStatus) {
|
|
1420
|
-
await this.finalizeTurnMessageReactions(event.turnId, terminalStatus);
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
1500
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
const latest = this.store.getSession(event.sessionId);
|
|
1427
|
-
const agent = latest ? this.ensureAgent(latest.agentName) : undefined;
|
|
1428
|
-
const activeTurnId = agent ? this.runtimes.forAgent(agent).getSession(event.sessionId)?.activeTurnId : undefined;
|
|
1429
|
-
if (latest?.lastTurnId === event.turnId && !activeTurnId) {
|
|
1430
|
-
this.store.updateSession(event.sessionId, { status: event.type === "turn_failed" ? "failed" : "ready" });
|
|
1431
|
-
}
|
|
1432
|
-
}
|
|
1501
|
+
catch (error) {
|
|
1502
|
+
presentationError = error;
|
|
1433
1503
|
}
|
|
1434
1504
|
if (event.type === "turn_completed" || event.type === "turn_cancelled" || event.type === "turn_failed") {
|
|
1505
|
+
const terminalStatus = event.type === "turn_completed"
|
|
1506
|
+
? "completed"
|
|
1507
|
+
: event.type === "turn_cancelled"
|
|
1508
|
+
? "cancelled"
|
|
1509
|
+
: "failed";
|
|
1510
|
+
const reactionSession = this.store.getSession(event.sessionId);
|
|
1511
|
+
if (!presentationError
|
|
1512
|
+
&& reactionSession?.lastTurnId === event.turnId
|
|
1513
|
+
&& reactionSession.lastTurnStatus === terminalStatus) {
|
|
1514
|
+
await this.finalizeTurnMessageReactions(event.turnId, terminalStatus);
|
|
1515
|
+
}
|
|
1435
1516
|
const latest = this.store.getSession(event.sessionId);
|
|
1436
|
-
const
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1517
|
+
const activeTurnId = latest
|
|
1518
|
+
? this.runtimes.forAgent(latest.agentName).getSession(event.sessionId)?.activeTurnId
|
|
1519
|
+
: undefined;
|
|
1520
|
+
if (latest?.lastTurnId === event.turnId && !activeTurnId) {
|
|
1521
|
+
this.store.updateSession(event.sessionId, { status: event.type === "turn_failed" ? "failed" : "ready" });
|
|
1522
|
+
await this.scheduleNextQueuedPrompt(event.sessionId);
|
|
1523
|
+
}
|
|
1441
1524
|
}
|
|
1525
|
+
if (presentationError)
|
|
1526
|
+
throw presentationError;
|
|
1442
1527
|
}
|
|
1443
1528
|
scheduleNextQueuedPrompt(sessionId) {
|
|
1444
1529
|
const previous = this.queuedPromptStarts.get(sessionId) ?? Promise.resolve();
|
|
@@ -1533,7 +1618,7 @@ export class ProxySessionController {
|
|
|
1533
1618
|
await this.cancelSession(record);
|
|
1534
1619
|
}
|
|
1535
1620
|
async cancelSession(record) {
|
|
1536
|
-
const runtime = this.runtimes.forAgent(
|
|
1621
|
+
const runtime = this.runtimes.forAgent(record.agentName);
|
|
1537
1622
|
if (runtime.kind === "codex" && record.remoteSessionId && runtime.interruptRemoteTurn) {
|
|
1538
1623
|
if (runtime.getGoal && runtime.setGoal) {
|
|
1539
1624
|
try {
|
|
@@ -1590,29 +1675,237 @@ export class ProxySessionController {
|
|
|
1590
1675
|
const loaded = await this.loadSession(this.requireSession(action.contextKey, sessionId));
|
|
1591
1676
|
await loaded.runtime.respondToApproval(sessionId, requestId, decision);
|
|
1592
1677
|
}
|
|
1593
|
-
async
|
|
1678
|
+
async openExecutionSettings(contextKey, activeTab, options = {}) {
|
|
1679
|
+
const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent);
|
|
1680
|
+
const record = options.sessionId
|
|
1681
|
+
? this.requireSession(contextKey, options.sessionId)
|
|
1682
|
+
: this.currentSession(contextKey);
|
|
1683
|
+
if (!record && activeTab !== "agent")
|
|
1684
|
+
this.requireCurrentSession(contextKey);
|
|
1685
|
+
const agents = Object.entries(this.config.agents).map(([name, agent]) => ({
|
|
1686
|
+
name,
|
|
1687
|
+
title: agent.title,
|
|
1688
|
+
}));
|
|
1689
|
+
let runtimeSettings = {
|
|
1690
|
+
currentPermissionMode: "auto",
|
|
1691
|
+
providers: [],
|
|
1692
|
+
providerSupported: false,
|
|
1693
|
+
models: [],
|
|
1694
|
+
reasoningOptions: [],
|
|
1695
|
+
};
|
|
1696
|
+
if (record) {
|
|
1697
|
+
const loaded = await this.loadSession(record);
|
|
1698
|
+
const models = await loaded.runtime.listModels();
|
|
1699
|
+
const currentModel = models.find((model) => model.id === loaded.session.model)
|
|
1700
|
+
?? models.find((model) => model.isDefault);
|
|
1701
|
+
const providerSupported = loaded.runtime.kind === "codex" && Boolean(loaded.runtime.listModelProviders);
|
|
1702
|
+
const providers = providerSupported ? await this.modelProviderOptions(loaded) : [];
|
|
1703
|
+
const currentProvider = loaded.session.modelProvider ?? providers.find((provider) => provider.isDefault)?.id;
|
|
1704
|
+
const currentEffort = currentModel?.supportedReasoningEfforts.some((option) => option.value === loaded.session.reasoningEffort)
|
|
1705
|
+
? loaded.session.reasoningEffort
|
|
1706
|
+
: currentModel?.defaultReasoningEffort ?? loaded.session.reasoningEffort;
|
|
1707
|
+
runtimeSettings = {
|
|
1708
|
+
currentProvider,
|
|
1709
|
+
currentModel: currentModel?.id ?? loaded.session.model,
|
|
1710
|
+
currentEffort,
|
|
1711
|
+
currentPermissionMode: loaded.session.permissionMode,
|
|
1712
|
+
providers,
|
|
1713
|
+
providerSupported,
|
|
1714
|
+
models,
|
|
1715
|
+
reasoningOptions: currentModel?.supportedReasoningEfforts ?? [],
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
const card = this.cardRenderer.renderExecutionSettings({
|
|
1719
|
+
...(record ? { sessionId: record.localSessionId, taskAgent: record.agentName } : {}),
|
|
1720
|
+
contextKey,
|
|
1721
|
+
activeTab,
|
|
1722
|
+
currentAgent: context.defaultAgent,
|
|
1723
|
+
agents,
|
|
1724
|
+
runtimeSettingsAvailable: Boolean(record),
|
|
1725
|
+
...runtimeSettings,
|
|
1726
|
+
notice: options.notice,
|
|
1727
|
+
});
|
|
1728
|
+
if (options.updateMessageId) {
|
|
1729
|
+
await this.outbound.updateInteractiveCard(contextKey, options.updateMessageId, card);
|
|
1730
|
+
}
|
|
1731
|
+
else {
|
|
1732
|
+
await this.outbound.sendInteractiveCard(contextKey, card);
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
async openAgentSettings(contextKey) {
|
|
1736
|
+
const current = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent).defaultAgent;
|
|
1737
|
+
if (Object.keys(this.config.agents).length <= 1) {
|
|
1738
|
+
await this.outbound.sendText(contextKey, `当前 Agent:${current}\n当前没有其他 Agent 可以切换。`);
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
await this.openExecutionSettings(contextKey, "agent");
|
|
1742
|
+
}
|
|
1743
|
+
async openProviderSettings(contextKey) {
|
|
1744
|
+
const loaded = await this.loadSession(this.requireCurrentSession(contextKey));
|
|
1745
|
+
const providers = loaded.runtime.kind === "codex" && loaded.runtime.listModelProviders
|
|
1746
|
+
? await this.modelProviderOptions(loaded)
|
|
1747
|
+
: [];
|
|
1748
|
+
const current = loaded.session.modelProvider?.trim()
|
|
1749
|
+
|| providers.find((provider) => provider.isDefault)?.id
|
|
1750
|
+
|| "运行时默认";
|
|
1751
|
+
if (providers.length <= 1) {
|
|
1752
|
+
await this.outbound.sendText(contextKey, `当前 Provider:${current}\n当前没有其他 Provider 可以切换。`);
|
|
1753
|
+
return;
|
|
1754
|
+
}
|
|
1755
|
+
await this.openExecutionSettings(contextKey, "provider", { sessionId: loaded.record.localSessionId });
|
|
1756
|
+
}
|
|
1757
|
+
async selectProvider(contextKey, modelProvider, options = {}) {
|
|
1594
1758
|
const record = options.sessionId
|
|
1595
1759
|
? this.requireSession(contextKey, options.sessionId)
|
|
1596
1760
|
: this.requireCurrentSession(contextKey);
|
|
1597
1761
|
const loaded = await this.loadSession(record);
|
|
1762
|
+
await this.assertModelProvider(loaded, modelProvider);
|
|
1598
1763
|
const models = await loaded.runtime.listModels();
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1764
|
+
const model = models.find((candidate) => candidate.id === loaded.session.model)
|
|
1765
|
+
?? models.find((candidate) => candidate.isDefault);
|
|
1766
|
+
if (!model)
|
|
1767
|
+
throw new Error("当前运行时没有可用于 Provider 切换的模型。");
|
|
1768
|
+
const effort = model.supportedReasoningEfforts.some((candidate) => candidate.value === loaded.session.reasoningEffort)
|
|
1769
|
+
? loaded.session.reasoningEffort
|
|
1770
|
+
: model.defaultReasoningEffort ?? model.supportedReasoningEfforts[0]?.value;
|
|
1771
|
+
if (!effort)
|
|
1772
|
+
throw new Error(`模型 ${model.id} 没有可用于 Provider 切换的思考强度。`);
|
|
1773
|
+
await this.applyProviderSettings(contextKey, {
|
|
1774
|
+
provider: modelProvider,
|
|
1775
|
+
model: model.id,
|
|
1776
|
+
effort,
|
|
1777
|
+
mode: loaded.session.permissionMode,
|
|
1778
|
+
}, options);
|
|
1779
|
+
}
|
|
1780
|
+
async openProviderSelector(contextKey, options = {}) {
|
|
1781
|
+
await this.openExecutionSettings(contextKey, "provider", options);
|
|
1782
|
+
}
|
|
1783
|
+
async openProviderModelSelector(contextKey, modelProvider, permissionMode, sessionId, updateMessageId) {
|
|
1784
|
+
const loaded = await this.loadSession(this.requireSession(contextKey, sessionId));
|
|
1785
|
+
await this.assertModelProvider(loaded, modelProvider);
|
|
1786
|
+
const models = await loaded.runtime.listModels();
|
|
1787
|
+
const card = this.cardRenderer.renderModelSelector({
|
|
1788
|
+
sessionId,
|
|
1789
|
+
contextKey,
|
|
1790
|
+
currentModel: loaded.session.model ?? models.find((model) => model.isDefault)?.id,
|
|
1791
|
+
reasoningEffort: loaded.session.reasoningEffort,
|
|
1792
|
+
models,
|
|
1793
|
+
modelProvider,
|
|
1794
|
+
permissionMode,
|
|
1795
|
+
unifiedSettings: true,
|
|
1796
|
+
});
|
|
1797
|
+
await this.outbound.updateInteractiveCard(contextKey, updateMessageId, card);
|
|
1798
|
+
}
|
|
1799
|
+
async openProviderReasoningSelector(contextKey, modelProvider, model, permissionMode, sessionId, updateMessageId) {
|
|
1800
|
+
const loaded = await this.loadSession(this.requireSession(contextKey, sessionId));
|
|
1801
|
+
await this.assertModelProvider(loaded, modelProvider);
|
|
1802
|
+
const models = await loaded.runtime.listModels();
|
|
1803
|
+
const selected = models.find((candidate) => candidate.id === model);
|
|
1804
|
+
if (!selected)
|
|
1805
|
+
throw new Error(`未知模型:${model}`);
|
|
1806
|
+
const currentEffort = selected.supportedReasoningEfforts.some((option) => option.value === loaded.session.reasoningEffort)
|
|
1807
|
+
? loaded.session.reasoningEffort
|
|
1808
|
+
: selected.defaultReasoningEffort;
|
|
1809
|
+
const card = this.cardRenderer.renderReasoningSelector({
|
|
1810
|
+
sessionId,
|
|
1811
|
+
contextKey,
|
|
1812
|
+
modelProvider,
|
|
1813
|
+
model,
|
|
1814
|
+
currentEffort,
|
|
1815
|
+
options: selected.supportedReasoningEfforts,
|
|
1816
|
+
permissionMode,
|
|
1817
|
+
unifiedSettings: true,
|
|
1818
|
+
});
|
|
1819
|
+
await this.outbound.updateInteractiveCard(contextKey, updateMessageId, card);
|
|
1820
|
+
}
|
|
1821
|
+
async openProviderPermissionSelector(contextKey, modelProvider, model, effort, permissionMode, sessionId, updateMessageId) {
|
|
1822
|
+
const loaded = await this.loadSession(this.requireSession(contextKey, sessionId));
|
|
1823
|
+
await this.assertProviderModelSettings(loaded, modelProvider, model, effort);
|
|
1824
|
+
await this.outbound.updateInteractiveCard(contextKey, updateMessageId, this.cardRenderer.renderPermissionSelector({
|
|
1825
|
+
sessionId,
|
|
1826
|
+
contextKey,
|
|
1827
|
+
modelProvider,
|
|
1828
|
+
model,
|
|
1829
|
+
reasoningEffort: effort,
|
|
1830
|
+
currentMode: permissionMode,
|
|
1831
|
+
}));
|
|
1832
|
+
}
|
|
1833
|
+
async applyProviderSettings(contextKey, settings, options = {}) {
|
|
1834
|
+
const record = options.sessionId
|
|
1835
|
+
? this.requireSession(contextKey, options.sessionId)
|
|
1836
|
+
: this.requireCurrentSession(contextKey);
|
|
1837
|
+
const loaded = await this.loadSession(record);
|
|
1838
|
+
await this.assertProviderModelSettings(loaded, settings.provider, settings.model, settings.effort);
|
|
1839
|
+
if (!loaded.runtime.setExecutionSettings) {
|
|
1840
|
+
throw new Error("当前运行时不支持 Provider 设置。");
|
|
1841
|
+
}
|
|
1842
|
+
const session = await loaded.runtime.setExecutionSettings(loaded.record.localSessionId, {
|
|
1843
|
+
modelProvider: settings.provider,
|
|
1844
|
+
model: settings.model,
|
|
1845
|
+
reasoningEffort: settings.effort,
|
|
1846
|
+
permissionMode: settings.mode,
|
|
1847
|
+
});
|
|
1848
|
+
this.store.updateRuntimeSession(loaded.record.localSessionId, {
|
|
1849
|
+
modelProvider: session.modelProvider ?? settings.provider,
|
|
1850
|
+
model: session.model ?? settings.model,
|
|
1851
|
+
reasoningEffort: session.reasoningEffort ?? settings.effort,
|
|
1852
|
+
permissionMode: session.permissionMode,
|
|
1853
|
+
});
|
|
1854
|
+
const notice = [
|
|
1855
|
+
`Provider 已切换为 ${cardCode(session.modelProvider ?? settings.provider)}`,
|
|
1856
|
+
`模型 ${cardCode(session.model ?? settings.model)}`,
|
|
1857
|
+
`思考强度 ${cardCode(session.reasoningEffort ?? settings.effort)}`,
|
|
1858
|
+
`权限 ${cardCode(session.permissionMode)}`,
|
|
1859
|
+
"从下一次请求生效。",
|
|
1860
|
+
].join(",");
|
|
1861
|
+
if (options.updateMessageId) {
|
|
1862
|
+
await this.openProviderSelector(contextKey, {
|
|
1602
1863
|
sessionId: loaded.record.localSessionId,
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
reasoningEffort: loaded.session.reasoningEffort,
|
|
1606
|
-
models,
|
|
1864
|
+
updateMessageId: options.updateMessageId,
|
|
1865
|
+
notice,
|
|
1607
1866
|
});
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1867
|
+
}
|
|
1868
|
+
else {
|
|
1869
|
+
await this.outbound.sendText(contextKey, notice.replaceAll("`", ""));
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
async assertProviderModelSettings(loaded, modelProvider, model, effort) {
|
|
1873
|
+
await this.assertModelProvider(loaded, modelProvider);
|
|
1874
|
+
const selected = (await loaded.runtime.listModels()).find((candidate) => candidate.id === model);
|
|
1875
|
+
if (!selected)
|
|
1876
|
+
throw new Error(`未知模型:${model}`);
|
|
1877
|
+
if (!selected.supportedReasoningEfforts.some((option) => option.value === effort)) {
|
|
1878
|
+
const supported = selected.supportedReasoningEfforts.map((option) => option.value).join("、") || "无";
|
|
1879
|
+
throw new Error(`模型 ${model} 不支持思考强度 ${effort}。支持的强度:${supported}`);
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
async assertModelProvider(loaded, modelProvider) {
|
|
1883
|
+
const providers = await this.modelProviderOptions(loaded);
|
|
1884
|
+
if (!providers.some((provider) => provider.id === modelProvider)) {
|
|
1885
|
+
throw new Error(`未知 Provider:${modelProvider}`);
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
async modelProviderOptions(loaded) {
|
|
1889
|
+
if (loaded.runtime.kind !== "codex" || !loaded.runtime.listModelProviders) {
|
|
1890
|
+
throw new Error("当前任务不支持 Provider 设置。");
|
|
1891
|
+
}
|
|
1892
|
+
const providers = await loaded.runtime.listModelProviders();
|
|
1893
|
+
const current = loaded.session.modelProvider?.trim();
|
|
1894
|
+
if (current && !providers.some((provider) => provider.id === current)) {
|
|
1895
|
+
providers.unshift({ id: current });
|
|
1896
|
+
}
|
|
1897
|
+
return providers;
|
|
1898
|
+
}
|
|
1899
|
+
async model(contextKey, model, options = {}) {
|
|
1900
|
+
if (!model) {
|
|
1901
|
+
await this.openExecutionSettings(contextKey, "model", options);
|
|
1614
1902
|
return;
|
|
1615
1903
|
}
|
|
1904
|
+
const record = options.sessionId
|
|
1905
|
+
? this.requireSession(contextKey, options.sessionId)
|
|
1906
|
+
: this.requireCurrentSession(contextKey);
|
|
1907
|
+
const loaded = await this.loadSession(record);
|
|
1908
|
+
const models = await loaded.runtime.listModels();
|
|
1616
1909
|
const selected = models.find((item) => item.id === model);
|
|
1617
1910
|
if (!selected)
|
|
1618
1911
|
throw new Error(`未知模型:${model}`);
|
|
@@ -1630,14 +1923,11 @@ export class ProxySessionController {
|
|
|
1630
1923
|
? `,思考强度已自动调整为 ${nextEffort}`
|
|
1631
1924
|
: "";
|
|
1632
1925
|
if (options.updateMessageId) {
|
|
1633
|
-
await this.
|
|
1926
|
+
await this.openExecutionSettings(contextKey, "model", {
|
|
1634
1927
|
sessionId: loaded.record.localSessionId,
|
|
1635
|
-
|
|
1636
|
-
model
|
|
1637
|
-
|
|
1638
|
-
options: selected.supportedReasoningEfforts,
|
|
1639
|
-
notice: `模型已切换为 ${cardCode(model)},请选择思考模式。`,
|
|
1640
|
-
}));
|
|
1928
|
+
updateMessageId: options.updateMessageId,
|
|
1929
|
+
notice: `模型已切换为 ${cardCode(model)}${effortMessage},从下一次请求生效。`,
|
|
1930
|
+
});
|
|
1641
1931
|
return;
|
|
1642
1932
|
}
|
|
1643
1933
|
await this.outbound.sendText(contextKey, `模型已切换为 ${model}${effortMessage},从下一次请求生效。`);
|
|
@@ -1785,6 +2075,10 @@ export class ProxySessionController {
|
|
|
1785
2075
|
});
|
|
1786
2076
|
}
|
|
1787
2077
|
async thinking(contextKey, effort, options = {}) {
|
|
2078
|
+
if (!effort) {
|
|
2079
|
+
await this.openExecutionSettings(contextKey, "thinking", options);
|
|
2080
|
+
return;
|
|
2081
|
+
}
|
|
1788
2082
|
const record = options.sessionId
|
|
1789
2083
|
? this.requireSession(contextKey, options.sessionId)
|
|
1790
2084
|
: this.requireCurrentSession(contextKey);
|
|
@@ -1798,22 +2092,6 @@ export class ProxySessionController {
|
|
|
1798
2092
|
throw new Error("模型已发生变化,请重新打开 /model。");
|
|
1799
2093
|
}
|
|
1800
2094
|
const supported = currentModel.supportedReasoningEfforts;
|
|
1801
|
-
if (!effort) {
|
|
1802
|
-
const card = this.cardRenderer.renderReasoningSelector({
|
|
1803
|
-
sessionId: loaded.record.localSessionId,
|
|
1804
|
-
contextKey,
|
|
1805
|
-
model: currentModel.id,
|
|
1806
|
-
currentEffort: loaded.session.reasoningEffort ?? currentModel.defaultReasoningEffort,
|
|
1807
|
-
options: supported,
|
|
1808
|
-
});
|
|
1809
|
-
if (options.updateMessageId) {
|
|
1810
|
-
await this.outbound.updateInteractiveCard(contextKey, options.updateMessageId, card);
|
|
1811
|
-
}
|
|
1812
|
-
else {
|
|
1813
|
-
await this.outbound.sendInteractiveCard(contextKey, card);
|
|
1814
|
-
}
|
|
1815
|
-
return;
|
|
1816
|
-
}
|
|
1817
2095
|
if (!supported.some((option) => option.value === effort)) {
|
|
1818
2096
|
const options = supported.map((option) => option.value).join("、") || "无";
|
|
1819
2097
|
throw new Error(`不支持的思考强度:${effort}。支持的强度:${options}`);
|
|
@@ -1821,79 +2099,85 @@ export class ProxySessionController {
|
|
|
1821
2099
|
await loaded.runtime.setReasoningEffort(loaded.record.localSessionId, effort);
|
|
1822
2100
|
this.store.updateRuntimeSession(loaded.record.localSessionId, { reasoningEffort: effort });
|
|
1823
2101
|
if (options.updateMessageId) {
|
|
1824
|
-
await this.
|
|
2102
|
+
await this.openExecutionSettings(contextKey, "thinking", {
|
|
1825
2103
|
sessionId: loaded.record.localSessionId,
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
options: supported,
|
|
1830
|
-
notice: `思考模式已切换为 ${cardCode(effort)},从下一次请求生效。`,
|
|
1831
|
-
}));
|
|
2104
|
+
updateMessageId: options.updateMessageId,
|
|
2105
|
+
notice: `思考强度已切换为 ${cardCode(effort)},从下一次请求生效。`,
|
|
2106
|
+
});
|
|
1832
2107
|
return;
|
|
1833
2108
|
}
|
|
1834
2109
|
await this.outbound.sendText(contextKey, `思考强度已切换为 ${effort},从下一次请求生效。`);
|
|
1835
2110
|
}
|
|
1836
|
-
async permissions(contextKey, mode) {
|
|
1837
|
-
const record = this.requireCurrentSession(contextKey);
|
|
2111
|
+
async permissions(contextKey, mode, options = {}) {
|
|
1838
2112
|
if (!mode) {
|
|
1839
|
-
await this.
|
|
2113
|
+
await this.openExecutionSettings(contextKey, "permission", options);
|
|
1840
2114
|
return;
|
|
1841
2115
|
}
|
|
2116
|
+
const record = options.sessionId
|
|
2117
|
+
? this.requireSession(contextKey, options.sessionId)
|
|
2118
|
+
: this.requireCurrentSession(contextKey);
|
|
1842
2119
|
const loaded = await this.loadSession(record);
|
|
1843
2120
|
await loaded.runtime.setPermissionMode(record.localSessionId, mode);
|
|
2121
|
+
loaded.session.permissionMode = mode;
|
|
1844
2122
|
this.store.updateRuntimeSession(record.localSessionId, { permissionMode: mode });
|
|
2123
|
+
if (options.updateMessageId) {
|
|
2124
|
+
await this.openExecutionSettings(contextKey, "permission", {
|
|
2125
|
+
sessionId: loaded.record.localSessionId,
|
|
2126
|
+
updateMessageId: options.updateMessageId,
|
|
2127
|
+
notice: mode === "auto"
|
|
2128
|
+
? "已切换为自动执行模式,从下一次请求生效。"
|
|
2129
|
+
: "已切换为执行前确认模式,从下一次请求生效。",
|
|
2130
|
+
});
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
1845
2133
|
await this.outbound.sendText(contextKey, mode === "auto" ? "已切换为自动执行模式。" : "已切换为执行前确认模式。");
|
|
1846
2134
|
}
|
|
1847
|
-
async listAgents(contextKey) {
|
|
1848
|
-
const current = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent).defaultAgent;
|
|
1849
|
-
const lines = Object.entries(this.config.agents).map(([name, agent]) => {
|
|
1850
|
-
const selected = name === current;
|
|
1851
|
-
return `- ${selected ? "✅ " : ""}${asInlineCode(name)}:${agent.title}${selected ? "(当前)" : ""}`;
|
|
1852
|
-
});
|
|
1853
|
-
await this.outbound.sendMarkdown(contextKey, [
|
|
1854
|
-
`当前 Agent:${asInlineCode(current)}`,
|
|
1855
|
-
"可用 Agent:",
|
|
1856
|
-
lines.join("\n") || "无",
|
|
1857
|
-
].join("\n"));
|
|
1858
|
-
}
|
|
1859
2135
|
async listSessions(contextKey, searchTerm, options = {}) {
|
|
1860
2136
|
const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent);
|
|
1861
2137
|
const normalizedSearch = searchTerm?.trim().toLowerCase();
|
|
1862
2138
|
const visibleCount = options.visibleCount ?? SESSION_PAGE_SIZE;
|
|
1863
2139
|
const localSessions = this.store.listSessions(contextKey).filter((session) => session.status !== "closed" && this.isCodexSession(session));
|
|
1864
|
-
|
|
1865
|
-
|
|
2140
|
+
const remoteSessions = [];
|
|
2141
|
+
const remoteErrors = [];
|
|
1866
2142
|
let remoteHasMore = false;
|
|
1867
|
-
|
|
1868
|
-
|
|
2143
|
+
await Promise.all(this.runtimes.entries("codex").map(async ([agentName, runtime]) => {
|
|
2144
|
+
if (!runtime.listRemoteSessions)
|
|
2145
|
+
return;
|
|
1869
2146
|
try {
|
|
1870
2147
|
const page = await runtime.listRemoteSessions({ searchTerm, limit: visibleCount });
|
|
1871
|
-
remoteSessions
|
|
1872
|
-
remoteHasMore
|
|
2148
|
+
remoteSessions.push(...page.sessions.map((session) => ({ agentName, session })));
|
|
2149
|
+
remoteHasMore ||= Boolean(page.nextCursor);
|
|
1873
2150
|
}
|
|
1874
2151
|
catch (error) {
|
|
1875
|
-
this.logger.warn({ error, contextKey }, "Failed to list Codex sessions.");
|
|
1876
|
-
|
|
2152
|
+
this.logger.warn({ error, contextKey, agentName }, "Failed to list Codex sessions for an Agent.");
|
|
2153
|
+
remoteErrors.push(`${agentName}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1877
2154
|
}
|
|
1878
|
-
}
|
|
2155
|
+
}));
|
|
2156
|
+
const remoteHint = remoteErrors.length > 0
|
|
2157
|
+
? `部分 Agent 的任务读取失败:${remoteErrors.join(";")}`
|
|
2158
|
+
: undefined;
|
|
1879
2159
|
const entries = mergeTaskList(localSessions, remoteSessions, context.currentSessionId)
|
|
1880
|
-
.filter((entry) => !normalizedSearch
|
|
2160
|
+
.filter((entry) => !normalizedSearch
|
|
2161
|
+
|| [entry.id, entry.title, entry.cwd, entry.agentName]
|
|
2162
|
+
.some((value) => value.toLowerCase().includes(normalizedSearch)))
|
|
1881
2163
|
.sort((left, right) => Number(right.current) - Number(left.current)
|
|
1882
2164
|
|| Number(right.active) - Number(left.active)
|
|
1883
2165
|
|| right.updatedAt - left.updatedAt);
|
|
1884
2166
|
const activeCount = entries.filter((entry) => entry.active).length;
|
|
1885
2167
|
const visibleEntries = entries.slice(0, visibleCount);
|
|
1886
2168
|
const hasMore = remoteHasMore || entries.length > visibleCount;
|
|
1887
|
-
this.lastSessionListings.set(contextKey, visibleEntries.map((entry) => entry.
|
|
2169
|
+
this.lastSessionListings.set(contextKey, visibleEntries.map((entry) => entry.reference));
|
|
1888
2170
|
const cardEntries = visibleEntries.map((entry, index) => {
|
|
1889
2171
|
const marker = entry.current ? "✅" : entry.active ? "🟢 **活跃**" : "•";
|
|
1890
|
-
const showStop = entry.status === "外部执行中"
|
|
2172
|
+
const showStop = entry.status === "外部执行中"
|
|
2173
|
+
&& entry.reference !== options.forceSwitchTaskId
|
|
2174
|
+
&& entry.id !== options.forceSwitchTaskId;
|
|
1891
2175
|
const actions = entry.current ? [] : [{
|
|
1892
2176
|
text: showStop ? "Stop" : "Switch",
|
|
1893
2177
|
type: showStop ? "danger" : "default",
|
|
1894
2178
|
value: {
|
|
1895
2179
|
action: showStop ? "session_stop" : "session_switch",
|
|
1896
|
-
sessionId: entry.
|
|
2180
|
+
sessionId: entry.reference,
|
|
1897
2181
|
...(searchTerm ? { searchTerm } : {}),
|
|
1898
2182
|
visibleCount: String(visibleCount),
|
|
1899
2183
|
contextKey,
|
|
@@ -1903,7 +2187,7 @@ export class ProxySessionController {
|
|
|
1903
2187
|
text: "New",
|
|
1904
2188
|
value: {
|
|
1905
2189
|
action: "session_new",
|
|
1906
|
-
sessionId: entry.
|
|
2190
|
+
sessionId: entry.reference,
|
|
1907
2191
|
...(searchTerm ? { searchTerm } : {}),
|
|
1908
2192
|
visibleCount: String(visibleCount),
|
|
1909
2193
|
contextKey,
|
|
@@ -1913,7 +2197,7 @@ export class ProxySessionController {
|
|
|
1913
2197
|
text: "NewGroup",
|
|
1914
2198
|
value: {
|
|
1915
2199
|
action: "session_new_group",
|
|
1916
|
-
sessionId: entry.
|
|
2200
|
+
sessionId: entry.reference,
|
|
1917
2201
|
...(searchTerm ? { searchTerm } : {}),
|
|
1918
2202
|
visibleCount: String(visibleCount),
|
|
1919
2203
|
contextKey,
|
|
@@ -1923,7 +2207,7 @@ export class ProxySessionController {
|
|
|
1923
2207
|
text: "Fork",
|
|
1924
2208
|
value: {
|
|
1925
2209
|
action: "session_fork",
|
|
1926
|
-
sessionId: entry.
|
|
2210
|
+
sessionId: entry.reference,
|
|
1927
2211
|
...(searchTerm ? { searchTerm } : {}),
|
|
1928
2212
|
visibleCount: String(visibleCount),
|
|
1929
2213
|
contextKey,
|
|
@@ -1933,7 +2217,7 @@ export class ProxySessionController {
|
|
|
1933
2217
|
text: "ForkGroup",
|
|
1934
2218
|
value: {
|
|
1935
2219
|
action: "session_fork_group",
|
|
1936
|
-
sessionId: entry.
|
|
2220
|
+
sessionId: entry.reference,
|
|
1937
2221
|
...(searchTerm ? { searchTerm } : {}),
|
|
1938
2222
|
visibleCount: String(visibleCount),
|
|
1939
2223
|
contextKey,
|
|
@@ -1943,14 +2227,14 @@ export class ProxySessionController {
|
|
|
1943
2227
|
text: "Status",
|
|
1944
2228
|
value: {
|
|
1945
2229
|
action: "session_status",
|
|
1946
|
-
sessionId: entry.
|
|
2230
|
+
sessionId: entry.reference,
|
|
1947
2231
|
contextKey,
|
|
1948
2232
|
},
|
|
1949
2233
|
});
|
|
1950
2234
|
return {
|
|
1951
2235
|
lines: [
|
|
1952
2236
|
`**${index + 1}.** ${marker} **${cardText(entry.title)}** ${cardText(entry.id)}`,
|
|
1953
|
-
`${entry.status} · ${entry.updatedLabel} · ${cardText(entry.cwd || "目录未知")}`,
|
|
2237
|
+
`${entry.status} · ${entry.updatedLabel} · ${cardText(entry.agentName)} · ${cardText(entry.cwd || "目录未知")}`,
|
|
1954
2238
|
],
|
|
1955
2239
|
actions,
|
|
1956
2240
|
};
|
|
@@ -2003,6 +2287,45 @@ export class ProxySessionController {
|
|
|
2003
2287
|
forceSwitchTaskId,
|
|
2004
2288
|
});
|
|
2005
2289
|
}
|
|
2290
|
+
findStoredSessionByReference(reference, contextKey) {
|
|
2291
|
+
const scoped = parseRemoteSessionReference(reference);
|
|
2292
|
+
return scoped
|
|
2293
|
+
? this.store.findSessionByRemoteSessionId(scoped.remoteSessionId, contextKey, scoped.agentName)
|
|
2294
|
+
: this.store.findSessionByRemoteSessionId(reference, contextKey);
|
|
2295
|
+
}
|
|
2296
|
+
async resolveRemoteCodexSession(reference) {
|
|
2297
|
+
const scoped = parseRemoteSessionReference(reference);
|
|
2298
|
+
const candidates = scoped
|
|
2299
|
+
? [[scoped.agentName, this.runtimes.forAgent(scoped.agentName)]]
|
|
2300
|
+
: this.runtimes.entries("codex");
|
|
2301
|
+
if (candidates.length === 0)
|
|
2302
|
+
throw new Error("未配置 Codex Agent。");
|
|
2303
|
+
const reads = await Promise.allSettled(candidates.map(async ([agentName, runtime]) => {
|
|
2304
|
+
if (runtime.kind !== "codex" || !runtime.readRemoteSession) {
|
|
2305
|
+
throw new Error(`Agent ${agentName} 不支持读取远端任务。`);
|
|
2306
|
+
}
|
|
2307
|
+
return {
|
|
2308
|
+
agentName,
|
|
2309
|
+
runtime,
|
|
2310
|
+
remote: await runtime.readRemoteSession(scoped?.remoteSessionId ?? reference),
|
|
2311
|
+
};
|
|
2312
|
+
}));
|
|
2313
|
+
const matches = reads
|
|
2314
|
+
.filter((result) => result.status === "fulfilled")
|
|
2315
|
+
.map((result) => result.value);
|
|
2316
|
+
if (matches.length === 1)
|
|
2317
|
+
return matches[0];
|
|
2318
|
+
if (matches.length > 1) {
|
|
2319
|
+
throw new Error(`多个 Agent 中存在相同任务 ID:${reference}。请先使用 /sessions,再通过序号选择任务。`);
|
|
2320
|
+
}
|
|
2321
|
+
const details = reads
|
|
2322
|
+
.map((result, index) => result.status === "rejected"
|
|
2323
|
+
? `${candidates[index]?.[0] ?? "unknown"}: ${runtimeErrorMessage(result.reason)}`
|
|
2324
|
+
: undefined)
|
|
2325
|
+
.filter((detail) => Boolean(detail))
|
|
2326
|
+
.join(";");
|
|
2327
|
+
throw new Error(`找不到 Codex 任务:${scoped?.remoteSessionId ?? reference}${details ? `(${details})` : ""}`);
|
|
2328
|
+
}
|
|
2006
2329
|
async switchSession(contextKey, reference) {
|
|
2007
2330
|
this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent);
|
|
2008
2331
|
const taskId = this.resolveSessionReference(contextKey, reference);
|
|
@@ -2010,9 +2333,9 @@ export class ProxySessionController {
|
|
|
2010
2333
|
if (direct?.status === "closed") {
|
|
2011
2334
|
throw new Error(`找不到任务:${taskId}`);
|
|
2012
2335
|
}
|
|
2013
|
-
const existing = direct ?? this.
|
|
2336
|
+
const existing = direct ?? this.findStoredSessionByReference(taskId);
|
|
2014
2337
|
if (existing) {
|
|
2015
|
-
const runtime = this.runtimes.forAgent(
|
|
2338
|
+
const runtime = this.runtimes.forAgent(existing.agentName);
|
|
2016
2339
|
await this.assertSessionTurnOwnership(existing, runtime);
|
|
2017
2340
|
this.store.attachSessionToContext(contextKey, existing.localSessionId);
|
|
2018
2341
|
this.store.setCurrentSession(contextKey, existing.localSessionId);
|
|
@@ -2020,19 +2343,12 @@ export class ProxySessionController {
|
|
|
2020
2343
|
await this.outbound.sendText(contextKey, `已切换到任务:${existing.title ?? existing.remoteSessionId ?? taskId}`);
|
|
2021
2344
|
return;
|
|
2022
2345
|
}
|
|
2023
|
-
const
|
|
2024
|
-
if (!runtime.readRemoteSession)
|
|
2025
|
-
throw new Error("当前 Codex 运行时不支持读取任务。");
|
|
2026
|
-
const remote = await runtime.readRemoteSession(taskId);
|
|
2346
|
+
const { agentName, remote } = await this.resolveRemoteCodexSession(taskId);
|
|
2027
2347
|
if (remote.status === "active" || remote.lastTurnStatus === "inProgress") {
|
|
2028
2348
|
throw new Error(`这个任务正在外部 Codex 中执行,当前不会切换。可使用 /status ${taskId} 查看进度。`);
|
|
2029
2349
|
}
|
|
2030
2350
|
if (!remote.cwd)
|
|
2031
2351
|
throw new Error("这个 Codex 任务没有可用的工作目录,暂时无法切换。");
|
|
2032
|
-
const agentEntry = Object.entries(this.config.agents).find(([, agent]) => agent.kind === "codex");
|
|
2033
|
-
if (!agentEntry)
|
|
2034
|
-
throw new Error("未配置 Codex Agent。");
|
|
2035
|
-
const [agentName] = agentEntry;
|
|
2036
2352
|
const localSessionId = createId("sess");
|
|
2037
2353
|
this.store.createSession({
|
|
2038
2354
|
localSessionId,
|
|
@@ -2061,16 +2377,15 @@ export class ProxySessionController {
|
|
|
2061
2377
|
await this.cancelSession({ ...direct, contextKey });
|
|
2062
2378
|
return;
|
|
2063
2379
|
}
|
|
2064
|
-
const existing = this.
|
|
2380
|
+
const existing = this.findStoredSessionByReference(taskId);
|
|
2065
2381
|
if (existing) {
|
|
2066
2382
|
await this.cancelSession({ ...existing, contextKey });
|
|
2067
2383
|
return;
|
|
2068
2384
|
}
|
|
2069
|
-
const runtime = this.
|
|
2385
|
+
const { runtime, remote } = await this.resolveRemoteCodexSession(taskId);
|
|
2070
2386
|
if (!runtime.readRemoteSession || !runtime.interruptRemoteTurn) {
|
|
2071
2387
|
throw new Error("当前 Codex 运行时不支持停止外部任务。");
|
|
2072
2388
|
}
|
|
2073
|
-
const remote = await runtime.readRemoteSession(taskId);
|
|
2074
2389
|
const turnId = remote.status === "active" || remote.lastTurnStatus === "inProgress"
|
|
2075
2390
|
? remote.lastTurnId
|
|
2076
2391
|
: undefined;
|
|
@@ -2147,11 +2462,18 @@ export class ProxySessionController {
|
|
|
2147
2462
|
}
|
|
2148
2463
|
return undefined;
|
|
2149
2464
|
}
|
|
2150
|
-
async setDefaultAgent(contextKey, agentName) {
|
|
2465
|
+
async setDefaultAgent(contextKey, agentName, options = {}) {
|
|
2151
2466
|
this.ensureAgent(agentName);
|
|
2152
2467
|
this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent);
|
|
2153
2468
|
this.store.setDefaultAgent(contextKey, agentName);
|
|
2154
|
-
|
|
2469
|
+
if (Object.keys(this.config.agents).length <= 1) {
|
|
2470
|
+
await this.outbound.sendText(contextKey, `当前 Agent:${agentName}\n当前没有其他 Agent 可以切换。`);
|
|
2471
|
+
return;
|
|
2472
|
+
}
|
|
2473
|
+
await this.openExecutionSettings(contextKey, "agent", {
|
|
2474
|
+
...options,
|
|
2475
|
+
notice: `默认 Agent 已切换为 ${cardCode(agentName)},从下一次新建任务生效。`,
|
|
2476
|
+
});
|
|
2155
2477
|
}
|
|
2156
2478
|
async status(contextKey, sessionId, options = {}) {
|
|
2157
2479
|
const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent);
|
|
@@ -2161,7 +2483,7 @@ export class ProxySessionController {
|
|
|
2161
2483
|
const direct = this.store.getSession(targetSessionId);
|
|
2162
2484
|
current = direct
|
|
2163
2485
|
? { ...direct, contextKey }
|
|
2164
|
-
: this.
|
|
2486
|
+
: this.findStoredSessionByReference(targetSessionId, contextKey);
|
|
2165
2487
|
if (!current) {
|
|
2166
2488
|
await this.statusForCodexTask(contextKey, targetSessionId, options);
|
|
2167
2489
|
return;
|
|
@@ -2172,7 +2494,7 @@ export class ProxySessionController {
|
|
|
2172
2494
|
}
|
|
2173
2495
|
if (current && current.status === "running") {
|
|
2174
2496
|
try {
|
|
2175
|
-
const runtime = this.runtimes.forAgent(
|
|
2497
|
+
const runtime = this.runtimes.forAgent(current.agentName);
|
|
2176
2498
|
if (runtime.getSession(current.localSessionId)) {
|
|
2177
2499
|
await runtime.synchronizeSession(current.localSessionId);
|
|
2178
2500
|
current = this.store.getSession(current.localSessionId) ?? current;
|
|
@@ -2186,7 +2508,7 @@ export class ProxySessionController {
|
|
|
2186
2508
|
let remote;
|
|
2187
2509
|
let goal;
|
|
2188
2510
|
if (current?.remoteSessionId) {
|
|
2189
|
-
const runtime = this.runtimes.forAgent(
|
|
2511
|
+
const runtime = this.runtimes.forAgent(current.agentName);
|
|
2190
2512
|
if (runtime.readRemoteSession) {
|
|
2191
2513
|
try {
|
|
2192
2514
|
remote = await runtime.readRemoteSession(current.remoteSessionId);
|
|
@@ -2216,7 +2538,7 @@ export class ProxySessionController {
|
|
|
2216
2538
|
}
|
|
2217
2539
|
else {
|
|
2218
2540
|
const agent = this.ensureAgent(current.agentName);
|
|
2219
|
-
const runtimeSession = this.runtimes.forAgent(
|
|
2541
|
+
const runtimeSession = this.runtimes.forAgent(current.agentName).getSession(current.localSessionId);
|
|
2220
2542
|
activeTurnId = runtimeSession?.activeTurnId;
|
|
2221
2543
|
const remoteActive = isRemoteSessionActive(remote);
|
|
2222
2544
|
activeTurnId = remoteActive ? remote?.lastTurnId ?? activeTurnId : activeTurnId;
|
|
@@ -2228,7 +2550,7 @@ export class ProxySessionController {
|
|
|
2228
2550
|
const resultLabel = remoteActive
|
|
2229
2551
|
? "执行中"
|
|
2230
2552
|
: remoteTurnStatusLabel(remote?.lastTurnStatus ?? current.lastTurnStatus);
|
|
2231
|
-
taskLines.push(`**标题**:${cardCode(current.title ?? "未命名任务")}`, `**工作目录**:${cardCode(current.cwd)}`,
|
|
2553
|
+
taskLines.push(`**标题**:${cardCode(current.title ?? "未命名任务")}`, `**工作目录**:${cardCode(current.cwd)}`, `**Provider / 模型 / 思考强度**:${cardCode(current.modelProvider ?? "Codex 默认")} / ${cardCode(current.model ?? "默认")} / ${cardCode(current.reasoningEffort ?? "自动")}`, `**状态 / 最近结果**:${statusLabel} / ${resultLabel}`, `**Agent**:${cardCode(agent.title)}`, `**权限 / 任务范围**:${current.permissionMode === "confirm" ? "执行前确认" : "自动执行"} / ${detectProjectlessWorkspace(current.cwd) ? "未指定项目" : "指定项目"}`, `**Codex 任务 ID**:${cardCode(current.remoteSessionId ?? "尚未创建")}`, `**当前执行 / 排队消息**:${activeTurnId ? cardCode(activeTurnId) : "无"} / ${queued} 条`, `**创建时间 / 最近活动**:${formatStatusTime(current.createdAt)} / ${formatStatusTime(current.updatedAt)}`);
|
|
2232
2554
|
}
|
|
2233
2555
|
const sections = [
|
|
2234
2556
|
{ title: targetSessionId ? "指定任务" : "当前任务", lines: taskLines },
|
|
@@ -2257,12 +2579,15 @@ export class ProxySessionController {
|
|
|
2257
2579
|
const title = targetSessionId && current
|
|
2258
2580
|
? `Codex 状态:${truncateText((current.title ?? current.remoteSessionId ?? current.localSessionId).replace(/\s+/g, " "), 40)}`
|
|
2259
2581
|
: "Codex 状态";
|
|
2260
|
-
const taskId = current?.remoteSessionId
|
|
2582
|
+
const taskId = current?.remoteSessionId
|
|
2583
|
+
? remoteSessionReference(current.agentName, current.remoteSessionId)
|
|
2584
|
+
: current?.localSessionId;
|
|
2261
2585
|
const isCurrent = Boolean(current && current.localSessionId === context.currentSessionId);
|
|
2262
2586
|
const remoteActive = isRemoteSessionActive(remote);
|
|
2263
2587
|
const botOwnsActiveTurn = Boolean(localCurrent && remote && isBotOwnedActiveTurn(localCurrent, remote));
|
|
2264
2588
|
const active = Boolean(activeTurnId || current?.status === "running" || remoteActive);
|
|
2265
|
-
const forceSwitch = Boolean(taskId && options.forceSwitchTaskId === taskId
|
|
2589
|
+
const forceSwitch = Boolean(taskId && (options.forceSwitchTaskId === taskId
|
|
2590
|
+
|| options.forceSwitchTaskId === current?.remoteSessionId));
|
|
2266
2591
|
const taskActions = !taskId ? []
|
|
2267
2592
|
: remoteActive && !botOwnsActiveTurn && !forceSwitch
|
|
2268
2593
|
? [statusCardAction("Stop", "danger", "session_stop", taskId, contextKey)]
|
|
@@ -2281,11 +2606,9 @@ export class ProxySessionController {
|
|
|
2281
2606
|
else
|
|
2282
2607
|
await this.outbound.sendInteractiveCard(contextKey, card);
|
|
2283
2608
|
}
|
|
2284
|
-
async statusForCodexTask(contextKey,
|
|
2285
|
-
const
|
|
2286
|
-
|
|
2287
|
-
throw new Error("当前 Codex 运行时不支持读取外部任务状态。");
|
|
2288
|
-
const remote = await runtime.readRemoteSession(remoteSessionId);
|
|
2609
|
+
async statusForCodexTask(contextKey, reference, options = {}) {
|
|
2610
|
+
const { agentName, remote } = await this.resolveRemoteCodexSession(reference);
|
|
2611
|
+
const actionReference = remoteSessionReference(agentName, remote.id);
|
|
2289
2612
|
const sections = [
|
|
2290
2613
|
{
|
|
2291
2614
|
title: "指定任务",
|
|
@@ -2293,6 +2616,7 @@ export class ProxySessionController {
|
|
|
2293
2616
|
`**标题**:${cardCode(remote.title ?? remote.preview ?? "未命名任务")}`,
|
|
2294
2617
|
`**工作目录**:${cardCode(remote.cwd || "目录未知")}`,
|
|
2295
2618
|
`**状态 / 当前任务**:${remoteSessionDetailStatus(remote)} / 未切换`,
|
|
2619
|
+
`**Agent**:${cardCode(agentName)}`,
|
|
2296
2620
|
`**Codex 任务 ID**:${cardCode(remote.id)}`,
|
|
2297
2621
|
`**最近回合**:${cardCode(remote.lastTurnId ?? "无")} ${remoteTurnStatusLabel(remote.lastTurnStatus)}`,
|
|
2298
2622
|
`**创建时间 / 最近活动**:${formatRemoteTime(remote.createdAt)} / ${formatRemoteTime(latestRemoteTimestamp(remote.recencyAt, remote.updatedAt))}`,
|
|
@@ -2312,10 +2636,12 @@ export class ProxySessionController {
|
|
|
2312
2636
|
},
|
|
2313
2637
|
];
|
|
2314
2638
|
const title = `Codex 状态:${truncateText((remote.title ?? remote.preview ?? remote.id).replace(/\s+/g, " "), 40)}`;
|
|
2315
|
-
const showStop = isRemoteSessionActive(remote)
|
|
2639
|
+
const showStop = isRemoteSessionActive(remote)
|
|
2640
|
+
&& options.forceSwitchTaskId !== actionReference
|
|
2641
|
+
&& options.forceSwitchTaskId !== remote.id;
|
|
2316
2642
|
const actions = [
|
|
2317
|
-
statusRefreshAction(
|
|
2318
|
-
statusCardAction(showStop ? "Stop" : "Switch", showStop ? "danger" : "default", showStop ? "session_stop" : "session_switch",
|
|
2643
|
+
statusRefreshAction(actionReference, contextKey),
|
|
2644
|
+
statusCardAction(showStop ? "Stop" : "Switch", showStop ? "danger" : "default", showStop ? "session_stop" : "session_switch", actionReference, contextKey),
|
|
2319
2645
|
];
|
|
2320
2646
|
const card = this.cardRenderer.renderSectionsCard(title, sections, actions);
|
|
2321
2647
|
if (options.updateMessageId)
|
|
@@ -2338,7 +2664,7 @@ export class ProxySessionController {
|
|
|
2338
2664
|
title: "任务管理",
|
|
2339
2665
|
lines: [
|
|
2340
2666
|
"**/new [title] [--dir <cwd> | --nodir]** 使用默认 Agent 创建任务;--nodir 强制创建 Projectless 任务",
|
|
2341
|
-
"**/newgroup [title]**
|
|
2667
|
+
"**/newgroup [title] [--dir <cwd> | --nodir]** 创建飞书群和新任务;参数与 /new 相同",
|
|
2342
2668
|
"**/forkgroup [title]** 从当前任务最新已完成轮次创建分支并绑定到新群",
|
|
2343
2669
|
"**/fork [序号或任务 ID]** 从当前或指定任务创建分支;运行中使用最近已完成轮次",
|
|
2344
2670
|
"**/title <新标题>** 修改当前任务的标题",
|
|
@@ -2353,22 +2679,23 @@ export class ProxySessionController {
|
|
|
2353
2679
|
"**/stop** 停止当前执行",
|
|
2354
2680
|
"**/queue <prompt>**(兼容 **/nosteer**) 不追加到当前轮次,按顺序排队为后续轮次",
|
|
2355
2681
|
"**/goal [目标]** 查看或创建长任务 Goal;支持 pause、resume、edit、clear",
|
|
2356
|
-
"**/
|
|
2357
|
-
"**/
|
|
2358
|
-
"**/
|
|
2682
|
+
"**/provider** 选择 AI 服务提供商;没有其他候选时仅显示当前 Provider",
|
|
2683
|
+
"**/model** 选择当前任务使用的模型",
|
|
2684
|
+
"**/thinking** 设置模型的思考强度",
|
|
2685
|
+
"**/permissions** 设置执行工具前是否需要确认",
|
|
2359
2686
|
],
|
|
2360
2687
|
},
|
|
2361
2688
|
{
|
|
2362
2689
|
title: "Agent",
|
|
2363
2690
|
lines: [
|
|
2364
|
-
"**/agent [name]**
|
|
2691
|
+
"**/agent [name]** 选择新任务使用的默认 Agent;仅配置一个时直接显示当前 Agent",
|
|
2365
2692
|
],
|
|
2366
2693
|
},
|
|
2367
2694
|
{
|
|
2368
2695
|
title: "系统",
|
|
2369
2696
|
lines: [
|
|
2370
2697
|
"**/status [序号或任务 ID]** 查看当前或指定任务的详细状态",
|
|
2371
|
-
"**/restart**
|
|
2698
|
+
"**/restart [--force]** 默认安全重启;--force 立即重启",
|
|
2372
2699
|
"**/help** 显示本帮助",
|
|
2373
2700
|
],
|
|
2374
2701
|
},
|
|
@@ -2400,6 +2727,17 @@ export class ProxySessionController {
|
|
|
2400
2727
|
? createProjectlessWorkspace().cwd
|
|
2401
2728
|
: current.cwd;
|
|
2402
2729
|
}
|
|
2730
|
+
inheritedExecutionSettings(contextKey, agentName) {
|
|
2731
|
+
const current = this.currentSession(contextKey);
|
|
2732
|
+
if (!current || current.agentName !== agentName)
|
|
2733
|
+
return {};
|
|
2734
|
+
return {
|
|
2735
|
+
modelProvider: current.modelProvider,
|
|
2736
|
+
model: current.model,
|
|
2737
|
+
reasoningEffort: current.reasoningEffort,
|
|
2738
|
+
permissionMode: current.permissionMode,
|
|
2739
|
+
};
|
|
2740
|
+
}
|
|
2403
2741
|
currentProjectCwd(contextKey) {
|
|
2404
2742
|
const current = this.currentSession(contextKey);
|
|
2405
2743
|
return current && !detectProjectlessWorkspace(current.cwd) ? current.cwd : undefined;
|
|
@@ -2454,10 +2792,10 @@ function statusRefreshAction(sessionId, contextKey) {
|
|
|
2454
2792
|
function mergeTaskList(localSessions, remoteSessions, currentLocalSessionId) {
|
|
2455
2793
|
const localByRemoteId = new Map(localSessions
|
|
2456
2794
|
.filter((session) => session.runtimeKind === "codex" && session.remoteSessionId)
|
|
2457
|
-
.map((session) => [session.remoteSessionId, session]));
|
|
2795
|
+
.map((session) => [agentRemoteKey(session.agentName, session.remoteSessionId), session]));
|
|
2458
2796
|
const representedLocalIds = new Set();
|
|
2459
|
-
const entries = remoteSessions.map((remote) => {
|
|
2460
|
-
const local = localByRemoteId.get(remote.id);
|
|
2797
|
+
const entries = remoteSessions.map(({ agentName, session: remote }) => {
|
|
2798
|
+
const local = localByRemoteId.get(agentRemoteKey(agentName, remote.id));
|
|
2461
2799
|
if (local)
|
|
2462
2800
|
representedLocalIds.add(local.localSessionId);
|
|
2463
2801
|
const active = remote.status === "active" || remote.lastTurnStatus === "inProgress";
|
|
@@ -2466,7 +2804,9 @@ function mergeTaskList(localSessions, remoteSessions, currentLocalSessionId) {
|
|
|
2466
2804
|
: remoteSessionStatusLabel(remote.status);
|
|
2467
2805
|
const recencyAt = remote.recencyAt ?? remote.updatedAt;
|
|
2468
2806
|
return {
|
|
2807
|
+
reference: remoteSessionReference(agentName, remote.id),
|
|
2469
2808
|
id: remote.id,
|
|
2809
|
+
agentName,
|
|
2470
2810
|
title: remote.title ?? remote.preview ?? local?.title ?? "未命名任务",
|
|
2471
2811
|
cwd: remote.cwd || local?.cwd || "",
|
|
2472
2812
|
status,
|
|
@@ -2481,7 +2821,11 @@ function mergeTaskList(localSessions, remoteSessions, currentLocalSessionId) {
|
|
|
2481
2821
|
continue;
|
|
2482
2822
|
const updatedAt = Date.parse(local.updatedAt);
|
|
2483
2823
|
entries.push({
|
|
2824
|
+
reference: local.remoteSessionId
|
|
2825
|
+
? remoteSessionReference(local.agentName, local.remoteSessionId)
|
|
2826
|
+
: local.localSessionId,
|
|
2484
2827
|
id: local.remoteSessionId ?? local.localSessionId,
|
|
2828
|
+
agentName: local.agentName,
|
|
2485
2829
|
title: local.title ?? "未命名任务",
|
|
2486
2830
|
cwd: local.cwd,
|
|
2487
2831
|
status: sessionStatusLabel(local.status),
|
|
@@ -2493,6 +2837,31 @@ function mergeTaskList(localSessions, remoteSessions, currentLocalSessionId) {
|
|
|
2493
2837
|
}
|
|
2494
2838
|
return entries;
|
|
2495
2839
|
}
|
|
2840
|
+
function agentRemoteKey(agentName, remoteSessionId) {
|
|
2841
|
+
return `${agentName}\u0000${remoteSessionId}`;
|
|
2842
|
+
}
|
|
2843
|
+
function remoteSessionReference(agentName, remoteSessionId) {
|
|
2844
|
+
return `${REMOTE_SESSION_REFERENCE_PREFIX}${encodeURIComponent(agentName)}:${encodeURIComponent(remoteSessionId)}`;
|
|
2845
|
+
}
|
|
2846
|
+
function parseRemoteSessionReference(reference) {
|
|
2847
|
+
if (!reference.startsWith(REMOTE_SESSION_REFERENCE_PREFIX))
|
|
2848
|
+
return undefined;
|
|
2849
|
+
const value = reference.slice(REMOTE_SESSION_REFERENCE_PREFIX.length);
|
|
2850
|
+
const separator = value.indexOf(":");
|
|
2851
|
+
if (separator <= 0 || separator === value.length - 1)
|
|
2852
|
+
return undefined;
|
|
2853
|
+
try {
|
|
2854
|
+
const agentName = decodeURIComponent(value.slice(0, separator));
|
|
2855
|
+
const remoteSessionId = decodeURIComponent(value.slice(separator + 1));
|
|
2856
|
+
return agentName && remoteSessionId ? { agentName, remoteSessionId } : undefined;
|
|
2857
|
+
}
|
|
2858
|
+
catch {
|
|
2859
|
+
return undefined;
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
function runtimeErrorMessage(error) {
|
|
2863
|
+
return error instanceof Error ? error.message : String(error);
|
|
2864
|
+
}
|
|
2496
2865
|
function isMissingRolloutError(error) {
|
|
2497
2866
|
const message = error instanceof Error ? error.message : String(error);
|
|
2498
2867
|
return /no rollout found|rollout[^\n]*(?:not found|missing)|thread\/resume failed/i.test(message);
|
|
@@ -2671,6 +3040,21 @@ function cardText(value) {
|
|
|
2671
3040
|
function cardCode(value) {
|
|
2672
3041
|
return `\`${value.replaceAll("`", "'")}\``;
|
|
2673
3042
|
}
|
|
3043
|
+
function permissionModeValue(value) {
|
|
3044
|
+
if (value === "auto" || value === "confirm")
|
|
3045
|
+
return value;
|
|
3046
|
+
throw new Error("权限模式只能是 auto 或 confirm。");
|
|
3047
|
+
}
|
|
3048
|
+
function executionSettingsTabValue(value) {
|
|
3049
|
+
if (value === "agent" || value === "provider" || value === "model" || value === "thinking" || value === "permission")
|
|
3050
|
+
return value;
|
|
3051
|
+
throw new Error("设置卡片的 tab 无效,请重新发送设置命令。");
|
|
3052
|
+
}
|
|
3053
|
+
function requiredCardMessageId(value) {
|
|
3054
|
+
if (value)
|
|
3055
|
+
return value;
|
|
3056
|
+
throw new Error("无法更新设置卡片,请重新发送设置命令。");
|
|
3057
|
+
}
|
|
2674
3058
|
function remoteSessionStatusLabel(status) {
|
|
2675
3059
|
const labels = {
|
|
2676
3060
|
active: "执行中",
|
|
@@ -2707,6 +3091,10 @@ function mergeRemoteTaskStatus(record, remote) {
|
|
|
2707
3091
|
...record,
|
|
2708
3092
|
title: remote.title ?? record.title,
|
|
2709
3093
|
cwd: remote.cwd || record.cwd,
|
|
3094
|
+
modelProvider: remote.modelProvider ?? record.modelProvider,
|
|
3095
|
+
model: remote.model ?? record.model,
|
|
3096
|
+
reasoningEffort: remote.reasoningEffort ?? record.reasoningEffort,
|
|
3097
|
+
permissionMode: remote.permissionMode ?? record.permissionMode,
|
|
2710
3098
|
status: isRemoteSessionActive(remote)
|
|
2711
3099
|
? "running"
|
|
2712
3100
|
: remote.lastTurnStatus
|
|
@@ -2758,12 +3146,8 @@ function isQueueIndependentCommand(command) {
|
|
|
2758
3146
|
return true;
|
|
2759
3147
|
if (command.type === "agent")
|
|
2760
3148
|
return command.agent === undefined;
|
|
2761
|
-
if (
|
|
2762
|
-
return
|
|
2763
|
-
if (command.type === "thinking")
|
|
2764
|
-
return command.effort === undefined;
|
|
2765
|
-
if (command.type === "permissions")
|
|
2766
|
-
return command.mode === undefined;
|
|
3149
|
+
if (["model", "provider", "thinking", "permissions"].includes(command.type))
|
|
3150
|
+
return true;
|
|
2767
3151
|
return false;
|
|
2768
3152
|
}
|
|
2769
3153
|
function parseSessionVisibleCount(value) {
|
|
@@ -2771,7 +3155,7 @@ function parseSessionVisibleCount(value) {
|
|
|
2771
3155
|
return Number.isSafeInteger(parsed) && parsed >= SESSION_PAGE_SIZE ? parsed : SESSION_PAGE_SIZE;
|
|
2772
3156
|
}
|
|
2773
3157
|
function isPromptCommand(command) {
|
|
2774
|
-
return command.type === "
|
|
3158
|
+
return command.type === "prompt" || command.type === "nosteer";
|
|
2775
3159
|
}
|
|
2776
3160
|
function runtimePrompt(text, localImagePaths) {
|
|
2777
3161
|
return localImagePaths?.length ? { text, localImagePaths } : text;
|
|
@@ -2783,8 +3167,8 @@ function formatGroupDateSuffix(date) {
|
|
|
2783
3167
|
}
|
|
2784
3168
|
function formatNewGroupName(agentName, projectCwd, taskTitle, date, includeTimestamp) {
|
|
2785
3169
|
const prefix = `[${agentName}] `;
|
|
2786
|
-
const
|
|
2787
|
-
const titlePrefix = `${prefix}${
|
|
3170
|
+
const projectPrefix = projectCwd ? `${formatGroupProjectDirectory(projectCwd)} ` : "";
|
|
3171
|
+
const titlePrefix = `${prefix}${projectPrefix}`;
|
|
2788
3172
|
const timestampSuffix = includeTimestamp ? ` (${formatGroupDateSuffix(date)})` : "";
|
|
2789
3173
|
const availableTitleLength = FEISHU_GROUP_NAME_MAX_LENGTH
|
|
2790
3174
|
- Array.from(titlePrefix).length
|
|
@@ -2795,7 +3179,7 @@ function formatNewGroupName(agentName, projectCwd, taskTitle, date, includeTimes
|
|
|
2795
3179
|
const FEISHU_GROUP_NAME_MAX_LENGTH = 60;
|
|
2796
3180
|
const GROUP_PROJECT_DIRECTORY_MAX_LENGTH = 15;
|
|
2797
3181
|
function formatGroupProjectDirectory(projectCwd) {
|
|
2798
|
-
const value =
|
|
3182
|
+
const value = abbreviateHomeDirectory(projectCwd);
|
|
2799
3183
|
if (Array.from(value).length <= GROUP_PROJECT_DIRECTORY_MAX_LENGTH)
|
|
2800
3184
|
return `[${value}]`;
|
|
2801
3185
|
const levels = value
|