@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
|
@@ -2,6 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import type { Logger } from "pino";
|
|
4
4
|
import { createProjectlessWorkspace, detectProjectlessWorkspace } from "../codex/ProjectlessWorkspace.js";
|
|
5
|
+
import { resolveUserPath } from "../config/paths.js";
|
|
5
6
|
import type { AppConfig } from "../config/schema.js";
|
|
6
7
|
import { CommandRouter } from "../commands/CommandRouter.js";
|
|
7
8
|
import type { Command } from "../commands/commandTypes.js";
|
|
@@ -12,7 +13,12 @@ import type {
|
|
|
12
13
|
IncomingMessage,
|
|
13
14
|
MessageReplyTarget,
|
|
14
15
|
} from "../feishu/types.js";
|
|
15
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
CardRenderer,
|
|
18
|
+
type CardSection,
|
|
19
|
+
type ExecutionSettingsTab,
|
|
20
|
+
type TaskListCardAction,
|
|
21
|
+
} from "../feishu/CardRenderer.js";
|
|
16
22
|
import { generateGroupAvatarPng, resolveGroupAvatarProjectName } from "../feishu/GroupAvatarGenerator.js";
|
|
17
23
|
import type { OutboundRouter } from "../presentation/OutboundRouter.js";
|
|
18
24
|
import type { TurnActivity, TurnViewState } from "../presentation/turnViewTypes.js";
|
|
@@ -55,6 +61,18 @@ const MESSAGE_COMPLETED_REACTION = "DONE";
|
|
|
55
61
|
const MESSAGE_FAILED_REACTION = "ERROR";
|
|
56
62
|
const MESSAGE_CANCELLED_REACTION = "CrossMark";
|
|
57
63
|
const SESSION_PAGE_SIZE = 5;
|
|
64
|
+
const REMOTE_SESSION_REFERENCE_PREFIX = "agent-runtime:";
|
|
65
|
+
|
|
66
|
+
interface AgentRemoteSession {
|
|
67
|
+
agentName: string;
|
|
68
|
+
runtime: AgentRuntime;
|
|
69
|
+
remote: RemoteSessionSummary;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface AgentRemoteSessionSummary {
|
|
73
|
+
agentName: string;
|
|
74
|
+
session: RemoteSessionSummary;
|
|
75
|
+
}
|
|
58
76
|
|
|
59
77
|
interface SessionsCardOptions {
|
|
60
78
|
updateMessageId?: string;
|
|
@@ -76,7 +94,12 @@ interface ThinkingCardOptions extends ModelCardOptions {
|
|
|
76
94
|
expectedModel?: string;
|
|
77
95
|
}
|
|
78
96
|
|
|
97
|
+
interface ExecutionSettingsCardOptions extends ModelCardOptions {
|
|
98
|
+
notice?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
79
101
|
interface SessionExecutionSettings {
|
|
102
|
+
modelProvider?: string;
|
|
80
103
|
model?: string;
|
|
81
104
|
reasoningEffort?: string;
|
|
82
105
|
permissionMode?: PermissionMode;
|
|
@@ -99,6 +122,7 @@ interface ForkSessionPlan {
|
|
|
99
122
|
lastTurnId: string;
|
|
100
123
|
cwd: string;
|
|
101
124
|
forkTitle: string;
|
|
125
|
+
modelProvider?: string;
|
|
102
126
|
model?: string;
|
|
103
127
|
reasoningEffort?: string;
|
|
104
128
|
permissionMode: PermissionMode;
|
|
@@ -130,7 +154,7 @@ interface CreatedFeishuGroupContext {
|
|
|
130
154
|
|
|
131
155
|
export interface ProxyLifecycle {
|
|
132
156
|
supervised?: boolean;
|
|
133
|
-
restart(contextKey: string): Promise<void>;
|
|
157
|
+
restart(contextKey: string, force: boolean): Promise<void>;
|
|
134
158
|
cancelSafeRestart?(scheduleId: number): Promise<boolean>;
|
|
135
159
|
}
|
|
136
160
|
|
|
@@ -168,9 +192,9 @@ export class ProxySessionController {
|
|
|
168
192
|
"Marked interrupted ACP sessions as failed after process restart.",
|
|
169
193
|
);
|
|
170
194
|
}
|
|
171
|
-
for (const
|
|
195
|
+
for (const [, runtime] of this.runtimes.entries()) {
|
|
172
196
|
this.unsubscribe.push(
|
|
173
|
-
|
|
197
|
+
runtime.onEvent((event) => {
|
|
174
198
|
void this.handleRuntimeEvent(event).catch((error: unknown) => {
|
|
175
199
|
this.logger.warn({ error, event }, "Failed to present runtime event.");
|
|
176
200
|
});
|
|
@@ -236,7 +260,7 @@ export class ProxySessionController {
|
|
|
236
260
|
let command: Command;
|
|
237
261
|
try {
|
|
238
262
|
command = imageCount > 0 && !message.text.trimStart().startsWith("/")
|
|
239
|
-
? { type: "prompt", text: message.text.trim() || "
|
|
263
|
+
? { type: "prompt", text: message.text.trim() || "请查看这张图片" }
|
|
240
264
|
: this.router.parse(message.text);
|
|
241
265
|
} catch (error) {
|
|
242
266
|
await this.finalizeStandaloneMessageReaction(message.messageId, "failed");
|
|
@@ -327,7 +351,7 @@ export class ProxySessionController {
|
|
|
327
351
|
contextKey,
|
|
328
352
|
String(scopedAction.value.turnId ?? ""),
|
|
329
353
|
requestedPage === "latest" ? "latest" : Number.isFinite(numericPage) ? numericPage : 0,
|
|
330
|
-
scopedAction.messageId,
|
|
354
|
+
requiredCardMessageId(scopedAction.messageId),
|
|
331
355
|
);
|
|
332
356
|
} else if (kind === "turn_cancel") {
|
|
333
357
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
@@ -378,6 +402,37 @@ export class ProxySessionController {
|
|
|
378
402
|
await this.status(contextKey, String(scopedAction.value.sessionId ?? ""));
|
|
379
403
|
} else if (kind === "session_status_refresh") {
|
|
380
404
|
await this.refreshStatusCardFromAction(scopedAction);
|
|
405
|
+
} else if (kind === "settings_tab_open") {
|
|
406
|
+
await this.openExecutionSettings(contextKey, executionSettingsTabValue(scopedAction.value.tab), {
|
|
407
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
408
|
+
updateMessageId: scopedAction.messageId,
|
|
409
|
+
});
|
|
410
|
+
} else if (kind === "settings_agent_select") {
|
|
411
|
+
await this.setDefaultAgent(contextKey, String(scopedAction.value.agent ?? ""), {
|
|
412
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
413
|
+
updateMessageId: scopedAction.messageId,
|
|
414
|
+
});
|
|
415
|
+
} else if (kind === "settings_provider_select") {
|
|
416
|
+
await this.selectProvider(contextKey, String(scopedAction.value.provider ?? ""), {
|
|
417
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
418
|
+
updateMessageId: scopedAction.messageId,
|
|
419
|
+
});
|
|
420
|
+
} else if (kind === "settings_model_select") {
|
|
421
|
+
await this.model(contextKey, String(scopedAction.value.model ?? ""), {
|
|
422
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
423
|
+
updateMessageId: scopedAction.messageId,
|
|
424
|
+
});
|
|
425
|
+
} else if (kind === "settings_thinking_select") {
|
|
426
|
+
await this.thinking(contextKey, String(scopedAction.value.effort ?? ""), {
|
|
427
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
428
|
+
updateMessageId: scopedAction.messageId,
|
|
429
|
+
expectedModel: String(scopedAction.value.model ?? ""),
|
|
430
|
+
});
|
|
431
|
+
} else if (kind === "settings_permission_select") {
|
|
432
|
+
await this.permissions(contextKey, permissionModeValue(scopedAction.value.permissionMode), {
|
|
433
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
434
|
+
updateMessageId: scopedAction.messageId,
|
|
435
|
+
});
|
|
381
436
|
} else if (kind === "model_select") {
|
|
382
437
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
383
438
|
const model = String(scopedAction.value.model ?? "");
|
|
@@ -400,6 +455,52 @@ export class ProxySessionController {
|
|
|
400
455
|
updateMessageId: scopedAction.messageId,
|
|
401
456
|
expectedModel: model,
|
|
402
457
|
});
|
|
458
|
+
} else if (kind === "provider_open") {
|
|
459
|
+
await this.openExecutionSettings(contextKey, "provider", {
|
|
460
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
461
|
+
updateMessageId: scopedAction.messageId,
|
|
462
|
+
});
|
|
463
|
+
} else if (kind === "provider_select" || kind === "provider_model_open") {
|
|
464
|
+
await this.openProviderModelSelector(
|
|
465
|
+
contextKey,
|
|
466
|
+
String(scopedAction.value.provider ?? ""),
|
|
467
|
+
permissionModeValue(scopedAction.value.permissionMode),
|
|
468
|
+
String(scopedAction.value.sessionId ?? ""),
|
|
469
|
+
requiredCardMessageId(scopedAction.messageId),
|
|
470
|
+
);
|
|
471
|
+
} else if (kind === "provider_model_select" || kind === "provider_reasoning_open") {
|
|
472
|
+
await this.openProviderReasoningSelector(
|
|
473
|
+
contextKey,
|
|
474
|
+
String(scopedAction.value.provider ?? ""),
|
|
475
|
+
String(scopedAction.value.model ?? ""),
|
|
476
|
+
permissionModeValue(scopedAction.value.permissionMode),
|
|
477
|
+
String(scopedAction.value.sessionId ?? ""),
|
|
478
|
+
requiredCardMessageId(scopedAction.messageId),
|
|
479
|
+
);
|
|
480
|
+
} else if (kind === "provider_reasoning_select") {
|
|
481
|
+
await this.openProviderPermissionSelector(
|
|
482
|
+
contextKey,
|
|
483
|
+
String(scopedAction.value.provider ?? ""),
|
|
484
|
+
String(scopedAction.value.model ?? ""),
|
|
485
|
+
String(scopedAction.value.effort ?? ""),
|
|
486
|
+
permissionModeValue(scopedAction.value.permissionMode),
|
|
487
|
+
String(scopedAction.value.sessionId ?? ""),
|
|
488
|
+
requiredCardMessageId(scopedAction.messageId),
|
|
489
|
+
);
|
|
490
|
+
} else if (kind === "provider_permission_select") {
|
|
491
|
+
await this.applyProviderSettings(
|
|
492
|
+
contextKey,
|
|
493
|
+
{
|
|
494
|
+
provider: String(scopedAction.value.provider ?? ""),
|
|
495
|
+
model: String(scopedAction.value.model ?? ""),
|
|
496
|
+
effort: String(scopedAction.value.effort ?? ""),
|
|
497
|
+
mode: permissionModeValue(scopedAction.value.permissionMode),
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
501
|
+
updateMessageId: scopedAction.messageId,
|
|
502
|
+
},
|
|
503
|
+
);
|
|
403
504
|
} else if (kind === "approval") {
|
|
404
505
|
await this.resolveApproval(scopedAction);
|
|
405
506
|
}
|
|
@@ -445,7 +546,7 @@ export class ProxySessionController {
|
|
|
445
546
|
if (!record) throw new Error(`Task not found: ${localSessionId}`);
|
|
446
547
|
let remote: RemoteSessionSummary | undefined;
|
|
447
548
|
if (record.remoteSessionId) {
|
|
448
|
-
const runtime = this.runtimes.forAgent(
|
|
549
|
+
const runtime = this.runtimes.forAgent(record.agentName);
|
|
449
550
|
if (runtime.readRemoteSession) {
|
|
450
551
|
try {
|
|
451
552
|
remote = await runtime.readRemoteSession(record.remoteSessionId);
|
|
@@ -484,7 +585,7 @@ export class ProxySessionController {
|
|
|
484
585
|
if (!promptText) throw new Error("The Prompt cannot be empty.");
|
|
485
586
|
const record = this.store.getSession(localSessionId);
|
|
486
587
|
if (!record || record.status === "closed") throw new Error(`Task not found: ${localSessionId}`);
|
|
487
|
-
const runtime = this.runtimes.forAgent(
|
|
588
|
+
const runtime = this.runtimes.forAgent(record.agentName);
|
|
488
589
|
const activeTurnId = runtime.getSession(localSessionId)?.activeTurnId;
|
|
489
590
|
const routedContextKey = this.outbound.getSessionContextKey(localSessionId);
|
|
490
591
|
const responseContextKey = (activeTurnId ? this.store.getTurnContextKey(activeTurnId) ?? routedContextKey : undefined)
|
|
@@ -546,7 +647,7 @@ export class ProxySessionController {
|
|
|
546
647
|
|
|
547
648
|
const sessionReference = typeof action.value.sessionId === "string" ? action.value.sessionId : undefined;
|
|
548
649
|
const session = sessionReference
|
|
549
|
-
? this.store.getSession(sessionReference) ?? this.
|
|
650
|
+
? this.store.getSession(sessionReference) ?? this.findStoredSessionByReference(sessionReference)
|
|
550
651
|
: undefined;
|
|
551
652
|
if (session && baseChatContextKey(session.contextKey) === baseChatContextKey(action.contextKey)) {
|
|
552
653
|
return session.contextKey;
|
|
@@ -581,20 +682,30 @@ export class ProxySessionController {
|
|
|
581
682
|
await this.createSession(
|
|
582
683
|
contextKey,
|
|
583
684
|
context.defaultAgent,
|
|
584
|
-
command.projectless
|
|
685
|
+
command.projectless
|
|
686
|
+
? undefined
|
|
687
|
+
: command.cwd === undefined
|
|
688
|
+
? this.inheritedNewTaskCwd(contextKey)
|
|
689
|
+
: resolveUserPath(command.cwd),
|
|
585
690
|
true,
|
|
586
691
|
false,
|
|
587
692
|
undefined,
|
|
588
693
|
undefined,
|
|
589
694
|
command.title,
|
|
695
|
+
this.inheritedExecutionSettings(contextKey, context.defaultAgent),
|
|
590
696
|
);
|
|
591
697
|
return;
|
|
592
698
|
case "newgroup":
|
|
699
|
+
if (command.projectless && this.ensureAgent(context.defaultAgent).kind !== "codex") {
|
|
700
|
+
throw new Error("/newgroup --nodir 仅支持 Codex Agent。");
|
|
701
|
+
}
|
|
593
702
|
await this.createFeishuGroup(
|
|
594
703
|
contextKey,
|
|
595
704
|
context.defaultAgent,
|
|
596
705
|
command.title,
|
|
597
706
|
userId,
|
|
707
|
+
command.cwd === undefined ? undefined : resolveUserPath(command.cwd),
|
|
708
|
+
command.projectless === true,
|
|
598
709
|
);
|
|
599
710
|
return;
|
|
600
711
|
case "forkgroup":
|
|
@@ -606,7 +717,6 @@ export class ProxySessionController {
|
|
|
606
717
|
case "title":
|
|
607
718
|
await this.setTitle(contextKey, command.title);
|
|
608
719
|
return;
|
|
609
|
-
case "ask":
|
|
610
720
|
case "prompt":
|
|
611
721
|
await this.prompt(contextKey, command.text, messageId, replyTarget, localImagePaths);
|
|
612
722
|
return;
|
|
@@ -621,11 +731,7 @@ export class ProxySessionController {
|
|
|
621
731
|
return;
|
|
622
732
|
case "agent":
|
|
623
733
|
if (command.agent) await this.setDefaultAgent(contextKey, command.agent);
|
|
624
|
-
else await this.
|
|
625
|
-
return;
|
|
626
|
-
case "use":
|
|
627
|
-
await this.setDefaultAgent(contextKey, command.agent);
|
|
628
|
-
await this.createSession(contextKey, command.agent, command.cwd, true, false);
|
|
734
|
+
else await this.openAgentSettings(contextKey);
|
|
629
735
|
return;
|
|
630
736
|
case "status":
|
|
631
737
|
await this.status(contextKey, command.sessionId);
|
|
@@ -635,20 +741,19 @@ export class ProxySessionController {
|
|
|
635
741
|
return;
|
|
636
742
|
case "restart":
|
|
637
743
|
if (!this.lifecycle) throw new Error("当前运行方式不支持自动重启。");
|
|
638
|
-
await this.lifecycle.restart(contextKey);
|
|
744
|
+
await this.lifecycle.restart(contextKey, command.force === true);
|
|
639
745
|
return;
|
|
640
746
|
case "model":
|
|
641
|
-
await this.
|
|
747
|
+
await this.openExecutionSettings(contextKey, "model");
|
|
748
|
+
return;
|
|
749
|
+
case "provider":
|
|
750
|
+
await this.openProviderSettings(contextKey);
|
|
642
751
|
return;
|
|
643
752
|
case "thinking":
|
|
644
|
-
await this.
|
|
753
|
+
await this.openExecutionSettings(contextKey, "thinking");
|
|
645
754
|
return;
|
|
646
755
|
case "permissions":
|
|
647
|
-
await this.
|
|
648
|
-
return;
|
|
649
|
-
case "modes":
|
|
650
|
-
case "mode":
|
|
651
|
-
await this.outbound.sendText(contextKey, "当前统一运行时不再暴露 ACP modes;Codex 请使用 /model 和 /permissions。");
|
|
756
|
+
await this.openExecutionSettings(contextKey, "permission");
|
|
652
757
|
return;
|
|
653
758
|
case "help":
|
|
654
759
|
await this.help(contextKey);
|
|
@@ -731,7 +836,7 @@ export class ProxySessionController {
|
|
|
731
836
|
replyTarget?: MessageReplyTarget,
|
|
732
837
|
localImagePaths?: string[],
|
|
733
838
|
): Promise<void> {
|
|
734
|
-
const configuredRuntime = this.runtimes.forAgent(
|
|
839
|
+
const configuredRuntime = this.runtimes.forAgent(record.agentName);
|
|
735
840
|
if (!configuredRuntime.getSession(record.localSessionId)) {
|
|
736
841
|
this.outbound.registerSession(record.localSessionId, contextKey, record.title, record.cwd);
|
|
737
842
|
await this.outbound.startPendingTurn(record.localSessionId, contextKey, record.title, replyTarget, text);
|
|
@@ -967,7 +1072,7 @@ export class ProxySessionController {
|
|
|
967
1072
|
private async forkThreadSession(message: IncomingMessage): Promise<void> {
|
|
968
1073
|
const { anchor, source, snapshot } = this.resolveThreadForkAnchor(message);
|
|
969
1074
|
const agent = this.ensureAgent(source.agentName);
|
|
970
|
-
const runtime = this.runtimes.forAgent(
|
|
1075
|
+
const runtime = this.runtimes.forAgent(source.agentName);
|
|
971
1076
|
if (runtime.kind !== "codex" || !runtime.forkSession) {
|
|
972
1077
|
throw new Error("当前 Codex 运行时不支持 fork 任务。");
|
|
973
1078
|
}
|
|
@@ -987,6 +1092,7 @@ export class ProxySessionController {
|
|
|
987
1092
|
this.store.updateRuntimeSession(localSessionId, {
|
|
988
1093
|
runtimeKind: "codex",
|
|
989
1094
|
title: forkTitle,
|
|
1095
|
+
modelProvider: source.modelProvider,
|
|
990
1096
|
model: source.model,
|
|
991
1097
|
reasoningEffort: source.reasoningEffort,
|
|
992
1098
|
permissionMode: source.permissionMode ?? "auto",
|
|
@@ -1002,6 +1108,7 @@ export class ProxySessionController {
|
|
|
1002
1108
|
agentName: source.agentName,
|
|
1003
1109
|
cwd: source.cwd,
|
|
1004
1110
|
title: forkTitle,
|
|
1111
|
+
modelProvider: source.modelProvider,
|
|
1005
1112
|
model: source.model,
|
|
1006
1113
|
reasoningEffort: source.reasoningEffort,
|
|
1007
1114
|
permissionMode: source.permissionMode ?? "auto",
|
|
@@ -1091,7 +1198,7 @@ export class ProxySessionController {
|
|
|
1091
1198
|
if (direct?.status === "closed") {
|
|
1092
1199
|
throw new Error(`找不到任务:${taskId}`);
|
|
1093
1200
|
}
|
|
1094
|
-
const global = direct ?? this.
|
|
1201
|
+
const global = direct ?? this.findStoredSessionByReference(taskId);
|
|
1095
1202
|
source = global ? { ...global, contextKey } : undefined;
|
|
1096
1203
|
}
|
|
1097
1204
|
|
|
@@ -1101,25 +1208,27 @@ export class ProxySessionController {
|
|
|
1101
1208
|
|
|
1102
1209
|
let runtime: AgentRuntime;
|
|
1103
1210
|
let agentName: string;
|
|
1211
|
+
let remote: RemoteSessionSummary | undefined;
|
|
1104
1212
|
if (source) {
|
|
1105
1213
|
agentName = source.agentName;
|
|
1106
|
-
runtime = this.runtimes.forAgent(
|
|
1214
|
+
runtime = this.runtimes.forAgent(agentName);
|
|
1107
1215
|
} else {
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
runtime =
|
|
1216
|
+
if (!taskId) throw new Error("缺少要 fork 的 Codex 任务 ID。");
|
|
1217
|
+
const resolved = await this.resolveRemoteCodexSession(taskId);
|
|
1218
|
+
agentName = resolved.agentName;
|
|
1219
|
+
runtime = resolved.runtime;
|
|
1220
|
+
remote = resolved.remote;
|
|
1112
1221
|
}
|
|
1113
1222
|
if (runtime.kind !== "codex" || !runtime.forkSession) {
|
|
1114
1223
|
throw new Error("当前 Codex 运行时不支持 fork 任务。");
|
|
1115
1224
|
}
|
|
1116
1225
|
|
|
1117
|
-
const remoteSessionId = source?.remoteSessionId ?? taskId;
|
|
1226
|
+
const remoteSessionId = source?.remoteSessionId ?? remote?.id ?? taskId;
|
|
1118
1227
|
if (!remoteSessionId) throw new Error("当前任务尚未创建 Codex 任务 ID,暂时不能 fork。");
|
|
1119
1228
|
if (!runtime.readRemoteSession && !source) {
|
|
1120
1229
|
throw new Error("当前 Codex 运行时不支持读取指定任务。");
|
|
1121
1230
|
}
|
|
1122
|
-
|
|
1231
|
+
remote ??= runtime.readRemoteSession
|
|
1123
1232
|
? await runtime.readRemoteSession(remoteSessionId)
|
|
1124
1233
|
: undefined;
|
|
1125
1234
|
const latestTurnId = remote?.lastTurnId ?? source?.lastTurnId;
|
|
@@ -1145,7 +1254,8 @@ export class ProxySessionController {
|
|
|
1145
1254
|
if (!cwd) throw new Error("指定的 Codex 任务没有可用的工作目录,暂时不能 fork。");
|
|
1146
1255
|
const sourceTitle = remote?.title ?? source?.title ?? remote?.preview;
|
|
1147
1256
|
const forkTitle = normalizeTaskTitle(requestedTitle) ?? this.store.nextForkTitle(sourceTitle);
|
|
1148
|
-
const
|
|
1257
|
+
const modelProvider = remote?.modelProvider ?? source?.modelProvider;
|
|
1258
|
+
const model = remote?.model ?? source?.model;
|
|
1149
1259
|
const reasoningEffort = source?.reasoningEffort;
|
|
1150
1260
|
const permissionMode = source?.permissionMode ?? "auto";
|
|
1151
1261
|
|
|
@@ -1158,6 +1268,7 @@ export class ProxySessionController {
|
|
|
1158
1268
|
lastTurnId,
|
|
1159
1269
|
cwd,
|
|
1160
1270
|
forkTitle,
|
|
1271
|
+
modelProvider,
|
|
1161
1272
|
model,
|
|
1162
1273
|
reasoningEffort,
|
|
1163
1274
|
permissionMode,
|
|
@@ -1223,7 +1334,7 @@ export class ProxySessionController {
|
|
|
1223
1334
|
if (!source.remoteSessionId || !this.isCodexSession(source)) {
|
|
1224
1335
|
throw new Error(`${sourceLabel}不是可 fork 的 Codex 任务。`);
|
|
1225
1336
|
}
|
|
1226
|
-
const runtime = this.runtimes.forAgent(
|
|
1337
|
+
const runtime = this.runtimes.forAgent(source.agentName);
|
|
1227
1338
|
if (runtime.kind !== "codex" || !runtime.forkSession) {
|
|
1228
1339
|
throw new Error("当前 Codex 运行时不支持 fork 任务。");
|
|
1229
1340
|
}
|
|
@@ -1248,6 +1359,7 @@ export class ProxySessionController {
|
|
|
1248
1359
|
lastTurnId,
|
|
1249
1360
|
cwd: source.cwd,
|
|
1250
1361
|
forkTitle,
|
|
1362
|
+
modelProvider: source.modelProvider,
|
|
1251
1363
|
model: source.model,
|
|
1252
1364
|
reasoningEffort: source.reasoningEffort,
|
|
1253
1365
|
permissionMode: source.permissionMode ?? "auto",
|
|
@@ -1274,6 +1386,7 @@ export class ProxySessionController {
|
|
|
1274
1386
|
this.store.updateRuntimeSession(localSessionId, {
|
|
1275
1387
|
runtimeKind: "codex",
|
|
1276
1388
|
title: plan.forkTitle,
|
|
1389
|
+
modelProvider: plan.modelProvider,
|
|
1277
1390
|
model: plan.model,
|
|
1278
1391
|
reasoningEffort: plan.reasoningEffort,
|
|
1279
1392
|
permissionMode: plan.permissionMode,
|
|
@@ -1287,6 +1400,7 @@ export class ProxySessionController {
|
|
|
1287
1400
|
agentName: plan.agentName,
|
|
1288
1401
|
cwd: plan.cwd,
|
|
1289
1402
|
title: plan.forkTitle,
|
|
1403
|
+
modelProvider: plan.modelProvider,
|
|
1290
1404
|
model: plan.model,
|
|
1291
1405
|
reasoningEffort: plan.reasoningEffort,
|
|
1292
1406
|
permissionMode: plan.permissionMode,
|
|
@@ -1348,26 +1462,26 @@ export class ProxySessionController {
|
|
|
1348
1462
|
if (direct?.status === "closed") {
|
|
1349
1463
|
throw new Error(`找不到任务:${taskId}`);
|
|
1350
1464
|
}
|
|
1351
|
-
const source = direct ?? this.
|
|
1465
|
+
const source = direct ?? this.findStoredSessionByReference(taskId);
|
|
1352
1466
|
if (source && !this.isCodexSession(source)) {
|
|
1353
1467
|
throw new Error("指定任务不是 Codex 任务,暂时不能按项目创建新任务。");
|
|
1354
1468
|
}
|
|
1355
1469
|
|
|
1356
1470
|
let agentName: string;
|
|
1357
1471
|
let runtime: AgentRuntime;
|
|
1472
|
+
let remote: RemoteSessionSummary | undefined;
|
|
1358
1473
|
if (source) {
|
|
1359
1474
|
agentName = source.agentName;
|
|
1360
|
-
runtime = this.runtimes.forAgent(
|
|
1475
|
+
runtime = this.runtimes.forAgent(agentName);
|
|
1361
1476
|
} else {
|
|
1362
|
-
const
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1477
|
+
const resolved = await this.resolveRemoteCodexSession(taskId);
|
|
1478
|
+
agentName = resolved.agentName;
|
|
1479
|
+
runtime = resolved.runtime;
|
|
1480
|
+
remote = resolved.remote;
|
|
1366
1481
|
}
|
|
1367
1482
|
|
|
1368
|
-
const remoteSessionId = source?.remoteSessionId ?? taskId;
|
|
1369
|
-
|
|
1370
|
-
if (runtime.readRemoteSession && remoteSessionId) {
|
|
1483
|
+
const remoteSessionId = source?.remoteSessionId ?? remote?.id ?? taskId;
|
|
1484
|
+
if (!remote && runtime.readRemoteSession && remoteSessionId) {
|
|
1371
1485
|
try {
|
|
1372
1486
|
remote = await runtime.readRemoteSession(remoteSessionId);
|
|
1373
1487
|
} catch (error) {
|
|
@@ -1384,6 +1498,7 @@ export class ProxySessionController {
|
|
|
1384
1498
|
throw new Error("指定的 Codex 任务没有可用的工作目录,暂时不能按项目创建新任务。");
|
|
1385
1499
|
}
|
|
1386
1500
|
const executionSettings: SessionExecutionSettings = {
|
|
1501
|
+
modelProvider: remote?.modelProvider ?? source?.modelProvider,
|
|
1387
1502
|
model: remote?.model ?? source?.model,
|
|
1388
1503
|
reasoningEffort: remote?.reasoningEffort ?? source?.reasoningEffort,
|
|
1389
1504
|
permissionMode: remote?.permissionMode ?? source?.permissionMode ?? "auto",
|
|
@@ -1455,9 +1570,10 @@ export class ProxySessionController {
|
|
|
1455
1570
|
? createProjectlessWorkspace({ prompt: initialTitle }).cwd
|
|
1456
1571
|
: path.resolve(cwd ?? this.config.defaults.cwd);
|
|
1457
1572
|
const record = this.store.createSession({ localSessionId, contextKey, agentName, cwd: sessionCwd, status: "starting" });
|
|
1458
|
-
if (initialTitle || executionSettings.model || executionSettings.reasoningEffort || executionSettings.permissionMode) {
|
|
1573
|
+
if (initialTitle || executionSettings.modelProvider || executionSettings.model || executionSettings.reasoningEffort || executionSettings.permissionMode) {
|
|
1459
1574
|
this.store.updateRuntimeSession(localSessionId, {
|
|
1460
1575
|
title: initialTitle,
|
|
1576
|
+
modelProvider: executionSettings.modelProvider,
|
|
1461
1577
|
model: executionSettings.model,
|
|
1462
1578
|
reasoningEffort: executionSettings.reasoningEffort,
|
|
1463
1579
|
permissionMode: executionSettings.permissionMode,
|
|
@@ -1465,7 +1581,7 @@ export class ProxySessionController {
|
|
|
1465
1581
|
}
|
|
1466
1582
|
this.store.setCurrentSession(contextKey, localSessionId);
|
|
1467
1583
|
this.outbound.registerSession(localSessionId, contextKey, initialTitle, sessionCwd);
|
|
1468
|
-
const runtime = this.runtimes.forAgent(
|
|
1584
|
+
const runtime = this.runtimes.forAgent(agentName);
|
|
1469
1585
|
try {
|
|
1470
1586
|
if (prepareTurn) {
|
|
1471
1587
|
await this.outbound.startPendingTurn(localSessionId, contextKey, initialTitle, replyTarget, prompt);
|
|
@@ -1475,6 +1591,7 @@ export class ProxySessionController {
|
|
|
1475
1591
|
agentName,
|
|
1476
1592
|
cwd: sessionCwd,
|
|
1477
1593
|
title: initialTitle,
|
|
1594
|
+
modelProvider: executionSettings.modelProvider,
|
|
1478
1595
|
model: executionSettings.model,
|
|
1479
1596
|
reasoningEffort: executionSettings.reasoningEffort,
|
|
1480
1597
|
permissionMode: executionSettings.permissionMode ?? "auto",
|
|
@@ -1500,11 +1617,16 @@ export class ProxySessionController {
|
|
|
1500
1617
|
agentName: string,
|
|
1501
1618
|
requestedTitle: string | undefined,
|
|
1502
1619
|
userId: string | undefined,
|
|
1620
|
+
requestedProjectCwd?: string,
|
|
1621
|
+
forceProjectless = false,
|
|
1503
1622
|
): Promise<void> {
|
|
1504
1623
|
const source = this.currentSession(sourceContextKey);
|
|
1505
|
-
const boundProjectCwd =
|
|
1624
|
+
const boundProjectCwd = forceProjectless
|
|
1625
|
+
? undefined
|
|
1626
|
+
: requestedProjectCwd ?? this.currentProjectCwd(sourceContextKey);
|
|
1506
1627
|
const executionSettings: SessionExecutionSettings = source?.agentName === agentName
|
|
1507
1628
|
? {
|
|
1629
|
+
modelProvider: source.modelProvider,
|
|
1508
1630
|
model: source.model,
|
|
1509
1631
|
reasoningEffort: source.reasoningEffort,
|
|
1510
1632
|
permissionMode: source.permissionMode,
|
|
@@ -1587,8 +1709,10 @@ export class ProxySessionController {
|
|
|
1587
1709
|
"群和新任务已创建。",
|
|
1588
1710
|
`当前任务:${taskDescription}`,
|
|
1589
1711
|
`当前 Project 目录:${boundProjectCwd ?? "未绑定(Projectless)"}`,
|
|
1712
|
+
`当前 Provider:${task.modelProvider ?? "Codex 默认"}`,
|
|
1590
1713
|
`当前模型:${task.model ?? "默认"}`,
|
|
1591
1714
|
`思考强度:${task.reasoningEffort ?? "自动"}`,
|
|
1715
|
+
`权限类型:${task.permissionMode === "confirm" ? "执行前确认" : "自动执行"}`,
|
|
1592
1716
|
].join("\n"),
|
|
1593
1717
|
);
|
|
1594
1718
|
await this.outbound.sendText(
|
|
@@ -1675,6 +1799,7 @@ export class ProxySessionController {
|
|
|
1675
1799
|
`已从${prepared.sourceDescription}创建分支。`,
|
|
1676
1800
|
`当前任务:${taskDescription}`,
|
|
1677
1801
|
`当前 Project 目录:${boundProjectCwd ?? "未绑定(Projectless)"}`,
|
|
1802
|
+
`当前 Provider:${forked.session.modelProvider ?? "Codex 默认"}`,
|
|
1678
1803
|
`当前模型:${forked.session.model ?? "默认"}`,
|
|
1679
1804
|
`思考强度:${forked.session.reasoningEffort ?? "自动"}`,
|
|
1680
1805
|
`权限类型:${forked.session.permissionMode === "confirm" ? "执行前确认" : "自动执行"}`,
|
|
@@ -1716,7 +1841,7 @@ export class ProxySessionController {
|
|
|
1716
1841
|
|
|
1717
1842
|
private async loadSession(record: SessionRecord): Promise<LoadedSession> {
|
|
1718
1843
|
const agent = this.ensureAgent(record.agentName);
|
|
1719
|
-
const runtime = this.runtimes.forAgent(
|
|
1844
|
+
const runtime = this.runtimes.forAgent(record.agentName);
|
|
1720
1845
|
const existing = runtime.getSession(record.localSessionId);
|
|
1721
1846
|
if (existing) return { record, runtime, session: existing };
|
|
1722
1847
|
const pending = this.sessionLoads.get(record.localSessionId);
|
|
@@ -1745,6 +1870,7 @@ export class ProxySessionController {
|
|
|
1745
1870
|
agentName: record.agentName,
|
|
1746
1871
|
cwd: record.cwd,
|
|
1747
1872
|
title: record.title,
|
|
1873
|
+
modelProvider: record.modelProvider,
|
|
1748
1874
|
model: record.model,
|
|
1749
1875
|
reasoningEffort: record.reasoningEffort,
|
|
1750
1876
|
permissionMode,
|
|
@@ -1762,6 +1888,7 @@ export class ProxySessionController {
|
|
|
1762
1888
|
agentName: record.agentName,
|
|
1763
1889
|
cwd: record.cwd,
|
|
1764
1890
|
title: record.title,
|
|
1891
|
+
modelProvider: record.modelProvider,
|
|
1765
1892
|
model: record.model,
|
|
1766
1893
|
reasoningEffort: record.reasoningEffort,
|
|
1767
1894
|
permissionMode,
|
|
@@ -1773,6 +1900,7 @@ export class ProxySessionController {
|
|
|
1773
1900
|
agentName: record.agentName,
|
|
1774
1901
|
cwd: record.cwd,
|
|
1775
1902
|
title: record.title,
|
|
1903
|
+
modelProvider: record.modelProvider,
|
|
1776
1904
|
model: record.model,
|
|
1777
1905
|
reasoningEffort: record.reasoningEffort,
|
|
1778
1906
|
permissionMode,
|
|
@@ -1795,6 +1923,7 @@ export class ProxySessionController {
|
|
|
1795
1923
|
runtimeKind: session.runtimeKind,
|
|
1796
1924
|
remoteSessionId: session.remoteSessionId,
|
|
1797
1925
|
title: session.title,
|
|
1926
|
+
modelProvider: session.modelProvider,
|
|
1798
1927
|
model: session.model,
|
|
1799
1928
|
reasoningEffort: session.reasoningEffort,
|
|
1800
1929
|
permissionMode: session.permissionMode,
|
|
@@ -1821,36 +1950,39 @@ export class ProxySessionController {
|
|
|
1821
1950
|
});
|
|
1822
1951
|
}
|
|
1823
1952
|
|
|
1953
|
+
let presentationError: unknown;
|
|
1824
1954
|
try {
|
|
1825
1955
|
await this.outbound.onEvent(event);
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
? "completed"
|
|
1829
|
-
: event.type === "turn_cancelled"
|
|
1830
|
-
? "cancelled"
|
|
1831
|
-
: "failed";
|
|
1832
|
-
const latest = this.store.getSession(event.sessionId);
|
|
1833
|
-
if (latest?.lastTurnId === event.turnId && latest.lastTurnStatus === terminalStatus) {
|
|
1834
|
-
await this.finalizeTurnMessageReactions(event.turnId, terminalStatus);
|
|
1835
|
-
}
|
|
1836
|
-
}
|
|
1837
|
-
} finally {
|
|
1838
|
-
if (event.type === "turn_completed" || event.type === "turn_cancelled" || event.type === "turn_failed") {
|
|
1839
|
-
const latest = this.store.getSession(event.sessionId);
|
|
1840
|
-
const agent = latest ? this.ensureAgent(latest.agentName) : undefined;
|
|
1841
|
-
const activeTurnId = agent ? this.runtimes.forAgent(agent).getSession(event.sessionId)?.activeTurnId : undefined;
|
|
1842
|
-
if (latest?.lastTurnId === event.turnId && !activeTurnId) {
|
|
1843
|
-
this.store.updateSession(event.sessionId, { status: event.type === "turn_failed" ? "failed" : "ready" });
|
|
1844
|
-
}
|
|
1845
|
-
}
|
|
1956
|
+
} catch (error) {
|
|
1957
|
+
presentationError = error;
|
|
1846
1958
|
}
|
|
1959
|
+
|
|
1847
1960
|
if (event.type === "turn_completed" || event.type === "turn_cancelled" || event.type === "turn_failed") {
|
|
1961
|
+
const terminalStatus = event.type === "turn_completed"
|
|
1962
|
+
? "completed"
|
|
1963
|
+
: event.type === "turn_cancelled"
|
|
1964
|
+
? "cancelled"
|
|
1965
|
+
: "failed";
|
|
1966
|
+
const reactionSession = this.store.getSession(event.sessionId);
|
|
1967
|
+
if (
|
|
1968
|
+
!presentationError
|
|
1969
|
+
&& reactionSession?.lastTurnId === event.turnId
|
|
1970
|
+
&& reactionSession.lastTurnStatus === terminalStatus
|
|
1971
|
+
) {
|
|
1972
|
+
await this.finalizeTurnMessageReactions(event.turnId, terminalStatus);
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1848
1975
|
const latest = this.store.getSession(event.sessionId);
|
|
1849
|
-
const
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1976
|
+
const activeTurnId = latest
|
|
1977
|
+
? this.runtimes.forAgent(latest.agentName).getSession(event.sessionId)?.activeTurnId
|
|
1978
|
+
: undefined;
|
|
1979
|
+
if (latest?.lastTurnId === event.turnId && !activeTurnId) {
|
|
1980
|
+
this.store.updateSession(event.sessionId, { status: event.type === "turn_failed" ? "failed" : "ready" });
|
|
1981
|
+
await this.scheduleNextQueuedPrompt(event.sessionId);
|
|
1982
|
+
}
|
|
1853
1983
|
}
|
|
1984
|
+
|
|
1985
|
+
if (presentationError) throw presentationError;
|
|
1854
1986
|
}
|
|
1855
1987
|
|
|
1856
1988
|
private scheduleNextQueuedPrompt(sessionId: string): Promise<void> {
|
|
@@ -1960,7 +2092,7 @@ export class ProxySessionController {
|
|
|
1960
2092
|
}
|
|
1961
2093
|
|
|
1962
2094
|
private async cancelSession(record: SessionRecord): Promise<void> {
|
|
1963
|
-
const runtime = this.runtimes.forAgent(
|
|
2095
|
+
const runtime = this.runtimes.forAgent(record.agentName);
|
|
1964
2096
|
if (runtime.kind === "codex" && record.remoteSessionId && runtime.interruptRemoteTurn) {
|
|
1965
2097
|
if (runtime.getGoal && runtime.setGoal) {
|
|
1966
2098
|
try {
|
|
@@ -2022,29 +2154,295 @@ export class ProxySessionController {
|
|
|
2022
2154
|
await loaded.runtime.respondToApproval(sessionId, requestId, decision);
|
|
2023
2155
|
}
|
|
2024
2156
|
|
|
2025
|
-
private async
|
|
2157
|
+
private async openExecutionSettings(
|
|
2158
|
+
contextKey: string,
|
|
2159
|
+
activeTab: ExecutionSettingsTab,
|
|
2160
|
+
options: ExecutionSettingsCardOptions = {},
|
|
2161
|
+
): Promise<void> {
|
|
2162
|
+
const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent!);
|
|
2163
|
+
const record = options.sessionId
|
|
2164
|
+
? this.requireSession(contextKey, options.sessionId)
|
|
2165
|
+
: this.currentSession(contextKey);
|
|
2166
|
+
if (!record && activeTab !== "agent") this.requireCurrentSession(contextKey);
|
|
2167
|
+
const agents = Object.entries(this.config.agents).map(([name, agent]) => ({
|
|
2168
|
+
name,
|
|
2169
|
+
title: agent.title,
|
|
2170
|
+
}));
|
|
2171
|
+
|
|
2172
|
+
let runtimeSettings: Omit<Parameters<CardRenderer["renderExecutionSettings"]>[0],
|
|
2173
|
+
"sessionId" | "contextKey" | "activeTab" | "currentAgent" | "taskAgent" | "agents" | "runtimeSettingsAvailable" | "notice"> = {
|
|
2174
|
+
currentPermissionMode: "auto",
|
|
2175
|
+
providers: [],
|
|
2176
|
+
providerSupported: false,
|
|
2177
|
+
models: [],
|
|
2178
|
+
reasoningOptions: [],
|
|
2179
|
+
};
|
|
2180
|
+
if (record) {
|
|
2181
|
+
const loaded = await this.loadSession(record);
|
|
2182
|
+
const models = await loaded.runtime.listModels();
|
|
2183
|
+
const currentModel = models.find((model) => model.id === loaded.session.model)
|
|
2184
|
+
?? models.find((model) => model.isDefault);
|
|
2185
|
+
const providerSupported = loaded.runtime.kind === "codex" && Boolean(loaded.runtime.listModelProviders);
|
|
2186
|
+
const providers = providerSupported ? await this.modelProviderOptions(loaded) : [];
|
|
2187
|
+
const currentProvider = loaded.session.modelProvider ?? providers.find((provider) => provider.isDefault)?.id;
|
|
2188
|
+
const currentEffort = currentModel?.supportedReasoningEfforts.some(
|
|
2189
|
+
(option) => option.value === loaded.session.reasoningEffort,
|
|
2190
|
+
)
|
|
2191
|
+
? loaded.session.reasoningEffort
|
|
2192
|
+
: currentModel?.defaultReasoningEffort ?? loaded.session.reasoningEffort;
|
|
2193
|
+
runtimeSettings = {
|
|
2194
|
+
currentProvider,
|
|
2195
|
+
currentModel: currentModel?.id ?? loaded.session.model,
|
|
2196
|
+
currentEffort,
|
|
2197
|
+
currentPermissionMode: loaded.session.permissionMode,
|
|
2198
|
+
providers,
|
|
2199
|
+
providerSupported,
|
|
2200
|
+
models,
|
|
2201
|
+
reasoningOptions: currentModel?.supportedReasoningEfforts ?? [],
|
|
2202
|
+
};
|
|
2203
|
+
}
|
|
2204
|
+
const card = this.cardRenderer.renderExecutionSettings({
|
|
2205
|
+
...(record ? { sessionId: record.localSessionId, taskAgent: record.agentName } : {}),
|
|
2206
|
+
contextKey,
|
|
2207
|
+
activeTab,
|
|
2208
|
+
currentAgent: context.defaultAgent,
|
|
2209
|
+
agents,
|
|
2210
|
+
runtimeSettingsAvailable: Boolean(record),
|
|
2211
|
+
...runtimeSettings,
|
|
2212
|
+
notice: options.notice,
|
|
2213
|
+
});
|
|
2214
|
+
if (options.updateMessageId) {
|
|
2215
|
+
await this.outbound.updateInteractiveCard(contextKey, options.updateMessageId, card);
|
|
2216
|
+
} else {
|
|
2217
|
+
await this.outbound.sendInteractiveCard(contextKey, card);
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
private async openAgentSettings(contextKey: string): Promise<void> {
|
|
2222
|
+
const current = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent!).defaultAgent;
|
|
2223
|
+
if (Object.keys(this.config.agents).length <= 1) {
|
|
2224
|
+
await this.outbound.sendText(contextKey, `当前 Agent:${current}\n当前没有其他 Agent 可以切换。`);
|
|
2225
|
+
return;
|
|
2226
|
+
}
|
|
2227
|
+
await this.openExecutionSettings(contextKey, "agent");
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
private async openProviderSettings(contextKey: string): Promise<void> {
|
|
2231
|
+
const loaded = await this.loadSession(this.requireCurrentSession(contextKey));
|
|
2232
|
+
const providers = loaded.runtime.kind === "codex" && loaded.runtime.listModelProviders
|
|
2233
|
+
? await this.modelProviderOptions(loaded)
|
|
2234
|
+
: [];
|
|
2235
|
+
const current = loaded.session.modelProvider?.trim()
|
|
2236
|
+
|| providers.find((provider) => provider.isDefault)?.id
|
|
2237
|
+
|| "运行时默认";
|
|
2238
|
+
if (providers.length <= 1) {
|
|
2239
|
+
await this.outbound.sendText(contextKey, `当前 Provider:${current}\n当前没有其他 Provider 可以切换。`);
|
|
2240
|
+
return;
|
|
2241
|
+
}
|
|
2242
|
+
await this.openExecutionSettings(contextKey, "provider", { sessionId: loaded.record.localSessionId });
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
private async selectProvider(
|
|
2246
|
+
contextKey: string,
|
|
2247
|
+
modelProvider: string,
|
|
2248
|
+
options: ModelCardOptions = {},
|
|
2249
|
+
): Promise<void> {
|
|
2026
2250
|
const record = options.sessionId
|
|
2027
2251
|
? this.requireSession(contextKey, options.sessionId)
|
|
2028
2252
|
: this.requireCurrentSession(contextKey);
|
|
2029
2253
|
const loaded = await this.loadSession(record);
|
|
2254
|
+
await this.assertModelProvider(loaded, modelProvider);
|
|
2030
2255
|
const models = await loaded.runtime.listModels();
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2256
|
+
const model = models.find((candidate) => candidate.id === loaded.session.model)
|
|
2257
|
+
?? models.find((candidate) => candidate.isDefault);
|
|
2258
|
+
if (!model) throw new Error("当前运行时没有可用于 Provider 切换的模型。");
|
|
2259
|
+
const effort = model.supportedReasoningEfforts.some(
|
|
2260
|
+
(candidate) => candidate.value === loaded.session.reasoningEffort,
|
|
2261
|
+
)
|
|
2262
|
+
? loaded.session.reasoningEffort
|
|
2263
|
+
: model.defaultReasoningEffort ?? model.supportedReasoningEfforts[0]?.value;
|
|
2264
|
+
if (!effort) throw new Error(`模型 ${model.id} 没有可用于 Provider 切换的思考强度。`);
|
|
2265
|
+
await this.applyProviderSettings(contextKey, {
|
|
2266
|
+
provider: modelProvider,
|
|
2267
|
+
model: model.id,
|
|
2268
|
+
effort,
|
|
2269
|
+
mode: loaded.session.permissionMode,
|
|
2270
|
+
}, options);
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
private async openProviderSelector(
|
|
2274
|
+
contextKey: string,
|
|
2275
|
+
options: ExecutionSettingsCardOptions = {},
|
|
2276
|
+
): Promise<void> {
|
|
2277
|
+
await this.openExecutionSettings(contextKey, "provider", options);
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
private async openProviderModelSelector(
|
|
2281
|
+
contextKey: string,
|
|
2282
|
+
modelProvider: string,
|
|
2283
|
+
permissionMode: PermissionMode,
|
|
2284
|
+
sessionId: string,
|
|
2285
|
+
updateMessageId: string,
|
|
2286
|
+
): Promise<void> {
|
|
2287
|
+
const loaded = await this.loadSession(this.requireSession(contextKey, sessionId));
|
|
2288
|
+
await this.assertModelProvider(loaded, modelProvider);
|
|
2289
|
+
const models = await loaded.runtime.listModels();
|
|
2290
|
+
const card = this.cardRenderer.renderModelSelector({
|
|
2291
|
+
sessionId,
|
|
2292
|
+
contextKey,
|
|
2293
|
+
currentModel: loaded.session.model ?? models.find((model) => model.isDefault)?.id,
|
|
2294
|
+
reasoningEffort: loaded.session.reasoningEffort,
|
|
2295
|
+
models,
|
|
2296
|
+
modelProvider,
|
|
2297
|
+
permissionMode,
|
|
2298
|
+
unifiedSettings: true,
|
|
2299
|
+
});
|
|
2300
|
+
await this.outbound.updateInteractiveCard(contextKey, updateMessageId, card);
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
private async openProviderReasoningSelector(
|
|
2304
|
+
contextKey: string,
|
|
2305
|
+
modelProvider: string,
|
|
2306
|
+
model: string,
|
|
2307
|
+
permissionMode: PermissionMode,
|
|
2308
|
+
sessionId: string,
|
|
2309
|
+
updateMessageId: string,
|
|
2310
|
+
): Promise<void> {
|
|
2311
|
+
const loaded = await this.loadSession(this.requireSession(contextKey, sessionId));
|
|
2312
|
+
await this.assertModelProvider(loaded, modelProvider);
|
|
2313
|
+
const models = await loaded.runtime.listModels();
|
|
2314
|
+
const selected = models.find((candidate) => candidate.id === model);
|
|
2315
|
+
if (!selected) throw new Error(`未知模型:${model}`);
|
|
2316
|
+
const currentEffort = selected.supportedReasoningEfforts.some(
|
|
2317
|
+
(option) => option.value === loaded.session.reasoningEffort,
|
|
2318
|
+
)
|
|
2319
|
+
? loaded.session.reasoningEffort
|
|
2320
|
+
: selected.defaultReasoningEffort;
|
|
2321
|
+
const card = this.cardRenderer.renderReasoningSelector({
|
|
2322
|
+
sessionId,
|
|
2323
|
+
contextKey,
|
|
2324
|
+
modelProvider,
|
|
2325
|
+
model,
|
|
2326
|
+
currentEffort,
|
|
2327
|
+
options: selected.supportedReasoningEfforts,
|
|
2328
|
+
permissionMode,
|
|
2329
|
+
unifiedSettings: true,
|
|
2330
|
+
});
|
|
2331
|
+
await this.outbound.updateInteractiveCard(contextKey, updateMessageId, card);
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
private async openProviderPermissionSelector(
|
|
2335
|
+
contextKey: string,
|
|
2336
|
+
modelProvider: string,
|
|
2337
|
+
model: string,
|
|
2338
|
+
effort: string,
|
|
2339
|
+
permissionMode: PermissionMode,
|
|
2340
|
+
sessionId: string,
|
|
2341
|
+
updateMessageId: string,
|
|
2342
|
+
): Promise<void> {
|
|
2343
|
+
const loaded = await this.loadSession(this.requireSession(contextKey, sessionId));
|
|
2344
|
+
await this.assertProviderModelSettings(loaded, modelProvider, model, effort);
|
|
2345
|
+
await this.outbound.updateInteractiveCard(
|
|
2346
|
+
contextKey,
|
|
2347
|
+
updateMessageId,
|
|
2348
|
+
this.cardRenderer.renderPermissionSelector({
|
|
2349
|
+
sessionId,
|
|
2035
2350
|
contextKey,
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2351
|
+
modelProvider,
|
|
2352
|
+
model,
|
|
2353
|
+
reasoningEffort: effort,
|
|
2354
|
+
currentMode: permissionMode,
|
|
2355
|
+
}),
|
|
2356
|
+
);
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
private async applyProviderSettings(
|
|
2360
|
+
contextKey: string,
|
|
2361
|
+
settings: { provider: string; model: string; effort: string; mode: PermissionMode },
|
|
2362
|
+
options: ModelCardOptions = {},
|
|
2363
|
+
): Promise<void> {
|
|
2364
|
+
const record = options.sessionId
|
|
2365
|
+
? this.requireSession(contextKey, options.sessionId)
|
|
2366
|
+
: this.requireCurrentSession(contextKey);
|
|
2367
|
+
const loaded = await this.loadSession(record);
|
|
2368
|
+
await this.assertProviderModelSettings(loaded, settings.provider, settings.model, settings.effort);
|
|
2369
|
+
if (!loaded.runtime.setExecutionSettings) {
|
|
2370
|
+
throw new Error("当前运行时不支持 Provider 设置。");
|
|
2371
|
+
}
|
|
2372
|
+
const session = await loaded.runtime.setExecutionSettings(loaded.record.localSessionId, {
|
|
2373
|
+
modelProvider: settings.provider,
|
|
2374
|
+
model: settings.model,
|
|
2375
|
+
reasoningEffort: settings.effort,
|
|
2376
|
+
permissionMode: settings.mode,
|
|
2377
|
+
});
|
|
2378
|
+
this.store.updateRuntimeSession(loaded.record.localSessionId, {
|
|
2379
|
+
modelProvider: session.modelProvider ?? settings.provider,
|
|
2380
|
+
model: session.model ?? settings.model,
|
|
2381
|
+
reasoningEffort: session.reasoningEffort ?? settings.effort,
|
|
2382
|
+
permissionMode: session.permissionMode,
|
|
2383
|
+
});
|
|
2384
|
+
const notice = [
|
|
2385
|
+
`Provider 已切换为 ${cardCode(session.modelProvider ?? settings.provider)}`,
|
|
2386
|
+
`模型 ${cardCode(session.model ?? settings.model)}`,
|
|
2387
|
+
`思考强度 ${cardCode(session.reasoningEffort ?? settings.effort)}`,
|
|
2388
|
+
`权限 ${cardCode(session.permissionMode)}`,
|
|
2389
|
+
"从下一次请求生效。",
|
|
2390
|
+
].join(",");
|
|
2391
|
+
if (options.updateMessageId) {
|
|
2392
|
+
await this.openProviderSelector(contextKey, {
|
|
2393
|
+
sessionId: loaded.record.localSessionId,
|
|
2394
|
+
updateMessageId: options.updateMessageId,
|
|
2395
|
+
notice,
|
|
2039
2396
|
});
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2397
|
+
} else {
|
|
2398
|
+
await this.outbound.sendText(contextKey, notice.replaceAll("`", ""));
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
private async assertProviderModelSettings(
|
|
2403
|
+
loaded: LoadedSession,
|
|
2404
|
+
modelProvider: string,
|
|
2405
|
+
model: string,
|
|
2406
|
+
effort: string,
|
|
2407
|
+
): Promise<void> {
|
|
2408
|
+
await this.assertModelProvider(loaded, modelProvider);
|
|
2409
|
+
const selected = (await loaded.runtime.listModels()).find((candidate) => candidate.id === model);
|
|
2410
|
+
if (!selected) throw new Error(`未知模型:${model}`);
|
|
2411
|
+
if (!selected.supportedReasoningEfforts.some((option) => option.value === effort)) {
|
|
2412
|
+
const supported = selected.supportedReasoningEfforts.map((option) => option.value).join("、") || "无";
|
|
2413
|
+
throw new Error(`模型 ${model} 不支持思考强度 ${effort}。支持的强度:${supported}`);
|
|
2046
2414
|
}
|
|
2415
|
+
}
|
|
2047
2416
|
|
|
2417
|
+
private async assertModelProvider(loaded: LoadedSession, modelProvider: string): Promise<void> {
|
|
2418
|
+
const providers = await this.modelProviderOptions(loaded);
|
|
2419
|
+
if (!providers.some((provider) => provider.id === modelProvider)) {
|
|
2420
|
+
throw new Error(`未知 Provider:${modelProvider}`);
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
private async modelProviderOptions(loaded: LoadedSession) {
|
|
2425
|
+
if (loaded.runtime.kind !== "codex" || !loaded.runtime.listModelProviders) {
|
|
2426
|
+
throw new Error("当前任务不支持 Provider 设置。");
|
|
2427
|
+
}
|
|
2428
|
+
const providers = await loaded.runtime.listModelProviders();
|
|
2429
|
+
const current = loaded.session.modelProvider?.trim();
|
|
2430
|
+
if (current && !providers.some((provider) => provider.id === current)) {
|
|
2431
|
+
providers.unshift({ id: current });
|
|
2432
|
+
}
|
|
2433
|
+
return providers;
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
private async model(contextKey: string, model?: string, options: ModelCardOptions = {}): Promise<void> {
|
|
2437
|
+
if (!model) {
|
|
2438
|
+
await this.openExecutionSettings(contextKey, "model", options);
|
|
2439
|
+
return;
|
|
2440
|
+
}
|
|
2441
|
+
const record = options.sessionId
|
|
2442
|
+
? this.requireSession(contextKey, options.sessionId)
|
|
2443
|
+
: this.requireCurrentSession(contextKey);
|
|
2444
|
+
const loaded = await this.loadSession(record);
|
|
2445
|
+
const models = await loaded.runtime.listModels();
|
|
2048
2446
|
const selected = models.find((item) => item.id === model);
|
|
2049
2447
|
if (!selected) throw new Error(`未知模型:${model}`);
|
|
2050
2448
|
const currentEffort = loaded.session.reasoningEffort;
|
|
@@ -2062,18 +2460,11 @@ export class ProxySessionController {
|
|
|
2062
2460
|
? `,思考强度已自动调整为 ${nextEffort}`
|
|
2063
2461
|
: "";
|
|
2064
2462
|
if (options.updateMessageId) {
|
|
2065
|
-
await this.
|
|
2066
|
-
|
|
2067
|
-
options.updateMessageId,
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
contextKey,
|
|
2071
|
-
model,
|
|
2072
|
-
currentEffort: nextEffort,
|
|
2073
|
-
options: selected.supportedReasoningEfforts,
|
|
2074
|
-
notice: `模型已切换为 ${cardCode(model)},请选择思考模式。`,
|
|
2075
|
-
}),
|
|
2076
|
-
);
|
|
2463
|
+
await this.openExecutionSettings(contextKey, "model", {
|
|
2464
|
+
sessionId: loaded.record.localSessionId,
|
|
2465
|
+
updateMessageId: options.updateMessageId,
|
|
2466
|
+
notice: `模型已切换为 ${cardCode(model)}${effortMessage},从下一次请求生效。`,
|
|
2467
|
+
});
|
|
2077
2468
|
return;
|
|
2078
2469
|
}
|
|
2079
2470
|
await this.outbound.sendText(contextKey, `模型已切换为 ${model}${effortMessage},从下一次请求生效。`);
|
|
@@ -2242,6 +2633,10 @@ export class ProxySessionController {
|
|
|
2242
2633
|
}
|
|
2243
2634
|
|
|
2244
2635
|
private async thinking(contextKey: string, effort?: string, options: ThinkingCardOptions = {}): Promise<void> {
|
|
2636
|
+
if (!effort) {
|
|
2637
|
+
await this.openExecutionSettings(contextKey, "thinking", options);
|
|
2638
|
+
return;
|
|
2639
|
+
}
|
|
2245
2640
|
const record = options.sessionId
|
|
2246
2641
|
? this.requireSession(contextKey, options.sessionId)
|
|
2247
2642
|
: this.requireCurrentSession(contextKey);
|
|
@@ -2255,22 +2650,6 @@ export class ProxySessionController {
|
|
|
2255
2650
|
}
|
|
2256
2651
|
const supported = currentModel.supportedReasoningEfforts;
|
|
2257
2652
|
|
|
2258
|
-
if (!effort) {
|
|
2259
|
-
const card = this.cardRenderer.renderReasoningSelector({
|
|
2260
|
-
sessionId: loaded.record.localSessionId,
|
|
2261
|
-
contextKey,
|
|
2262
|
-
model: currentModel.id,
|
|
2263
|
-
currentEffort: loaded.session.reasoningEffort ?? currentModel.defaultReasoningEffort,
|
|
2264
|
-
options: supported,
|
|
2265
|
-
});
|
|
2266
|
-
if (options.updateMessageId) {
|
|
2267
|
-
await this.outbound.updateInteractiveCard(contextKey, options.updateMessageId, card);
|
|
2268
|
-
} else {
|
|
2269
|
-
await this.outbound.sendInteractiveCard(contextKey, card);
|
|
2270
|
-
}
|
|
2271
|
-
return;
|
|
2272
|
-
}
|
|
2273
|
-
|
|
2274
2653
|
if (!supported.some((option) => option.value === effort)) {
|
|
2275
2654
|
const options = supported.map((option) => option.value).join("、") || "无";
|
|
2276
2655
|
throw new Error(`不支持的思考强度:${effort}。支持的强度:${options}`);
|
|
@@ -2278,48 +2657,45 @@ export class ProxySessionController {
|
|
|
2278
2657
|
await loaded.runtime.setReasoningEffort(loaded.record.localSessionId, effort);
|
|
2279
2658
|
this.store.updateRuntimeSession(loaded.record.localSessionId, { reasoningEffort: effort });
|
|
2280
2659
|
if (options.updateMessageId) {
|
|
2281
|
-
await this.
|
|
2282
|
-
|
|
2283
|
-
options.updateMessageId,
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
contextKey,
|
|
2287
|
-
model: currentModel.id,
|
|
2288
|
-
currentEffort: effort,
|
|
2289
|
-
options: supported,
|
|
2290
|
-
notice: `思考模式已切换为 ${cardCode(effort)},从下一次请求生效。`,
|
|
2291
|
-
}),
|
|
2292
|
-
);
|
|
2660
|
+
await this.openExecutionSettings(contextKey, "thinking", {
|
|
2661
|
+
sessionId: loaded.record.localSessionId,
|
|
2662
|
+
updateMessageId: options.updateMessageId,
|
|
2663
|
+
notice: `思考强度已切换为 ${cardCode(effort)},从下一次请求生效。`,
|
|
2664
|
+
});
|
|
2293
2665
|
return;
|
|
2294
2666
|
}
|
|
2295
2667
|
await this.outbound.sendText(contextKey, `思考强度已切换为 ${effort},从下一次请求生效。`);
|
|
2296
2668
|
}
|
|
2297
2669
|
|
|
2298
|
-
private async permissions(
|
|
2299
|
-
|
|
2670
|
+
private async permissions(
|
|
2671
|
+
contextKey: string,
|
|
2672
|
+
mode?: PermissionMode,
|
|
2673
|
+
options: ModelCardOptions = {},
|
|
2674
|
+
): Promise<void> {
|
|
2300
2675
|
if (!mode) {
|
|
2301
|
-
await this.
|
|
2676
|
+
await this.openExecutionSettings(contextKey, "permission", options);
|
|
2302
2677
|
return;
|
|
2303
2678
|
}
|
|
2679
|
+
const record = options.sessionId
|
|
2680
|
+
? this.requireSession(contextKey, options.sessionId)
|
|
2681
|
+
: this.requireCurrentSession(contextKey);
|
|
2304
2682
|
const loaded = await this.loadSession(record);
|
|
2305
2683
|
await loaded.runtime.setPermissionMode(record.localSessionId, mode);
|
|
2684
|
+
loaded.session.permissionMode = mode;
|
|
2306
2685
|
this.store.updateRuntimeSession(record.localSessionId, { permissionMode: mode });
|
|
2686
|
+
if (options.updateMessageId) {
|
|
2687
|
+
await this.openExecutionSettings(contextKey, "permission", {
|
|
2688
|
+
sessionId: loaded.record.localSessionId,
|
|
2689
|
+
updateMessageId: options.updateMessageId,
|
|
2690
|
+
notice: mode === "auto"
|
|
2691
|
+
? "已切换为自动执行模式,从下一次请求生效。"
|
|
2692
|
+
: "已切换为执行前确认模式,从下一次请求生效。",
|
|
2693
|
+
});
|
|
2694
|
+
return;
|
|
2695
|
+
}
|
|
2307
2696
|
await this.outbound.sendText(contextKey, mode === "auto" ? "已切换为自动执行模式。" : "已切换为执行前确认模式。");
|
|
2308
2697
|
}
|
|
2309
2698
|
|
|
2310
|
-
private async listAgents(contextKey: string): Promise<void> {
|
|
2311
|
-
const current = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent!).defaultAgent;
|
|
2312
|
-
const lines = Object.entries(this.config.agents).map(([name, agent]) => {
|
|
2313
|
-
const selected = name === current;
|
|
2314
|
-
return `- ${selected ? "✅ " : ""}${asInlineCode(name)}:${agent.title}${selected ? "(当前)" : ""}`;
|
|
2315
|
-
});
|
|
2316
|
-
await this.outbound.sendMarkdown(contextKey, [
|
|
2317
|
-
`当前 Agent:${asInlineCode(current)}`,
|
|
2318
|
-
"可用 Agent:",
|
|
2319
|
-
lines.join("\n") || "无",
|
|
2320
|
-
].join("\n"));
|
|
2321
|
-
}
|
|
2322
|
-
|
|
2323
2699
|
private async listSessions(
|
|
2324
2700
|
contextKey: string,
|
|
2325
2701
|
searchTerm?: string,
|
|
@@ -2331,39 +2707,46 @@ export class ProxySessionController {
|
|
|
2331
2707
|
const localSessions = this.store.listSessions(contextKey).filter((session) =>
|
|
2332
2708
|
session.status !== "closed" && this.isCodexSession(session),
|
|
2333
2709
|
);
|
|
2334
|
-
|
|
2335
|
-
|
|
2710
|
+
const remoteSessions: AgentRemoteSessionSummary[] = [];
|
|
2711
|
+
const remoteErrors: string[] = [];
|
|
2336
2712
|
let remoteHasMore = false;
|
|
2337
|
-
|
|
2338
|
-
|
|
2713
|
+
await Promise.all(this.runtimes.entries("codex").map(async ([agentName, runtime]) => {
|
|
2714
|
+
if (!runtime.listRemoteSessions) return;
|
|
2339
2715
|
try {
|
|
2340
2716
|
const page = await runtime.listRemoteSessions({ searchTerm, limit: visibleCount });
|
|
2341
|
-
remoteSessions
|
|
2342
|
-
remoteHasMore
|
|
2717
|
+
remoteSessions.push(...page.sessions.map((session) => ({ agentName, session })));
|
|
2718
|
+
remoteHasMore ||= Boolean(page.nextCursor);
|
|
2343
2719
|
} catch (error) {
|
|
2344
|
-
this.logger.warn({ error, contextKey }, "Failed to list Codex sessions.");
|
|
2345
|
-
|
|
2720
|
+
this.logger.warn({ error, contextKey, agentName }, "Failed to list Codex sessions for an Agent.");
|
|
2721
|
+
remoteErrors.push(`${agentName}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2346
2722
|
}
|
|
2347
|
-
}
|
|
2723
|
+
}));
|
|
2724
|
+
const remoteHint = remoteErrors.length > 0
|
|
2725
|
+
? `部分 Agent 的任务读取失败:${remoteErrors.join(";")}`
|
|
2726
|
+
: undefined;
|
|
2348
2727
|
|
|
2349
2728
|
const entries = mergeTaskList(localSessions, remoteSessions, context.currentSessionId)
|
|
2350
|
-
.filter((entry) => !normalizedSearch
|
|
2729
|
+
.filter((entry) => !normalizedSearch
|
|
2730
|
+
|| [entry.id, entry.title, entry.cwd, entry.agentName]
|
|
2731
|
+
.some((value) => value.toLowerCase().includes(normalizedSearch)))
|
|
2351
2732
|
.sort((left, right) => Number(right.current) - Number(left.current)
|
|
2352
2733
|
|| Number(right.active) - Number(left.active)
|
|
2353
2734
|
|| right.updatedAt - left.updatedAt);
|
|
2354
2735
|
const activeCount = entries.filter((entry) => entry.active).length;
|
|
2355
2736
|
const visibleEntries = entries.slice(0, visibleCount);
|
|
2356
2737
|
const hasMore = remoteHasMore || entries.length > visibleCount;
|
|
2357
|
-
this.lastSessionListings.set(contextKey, visibleEntries.map((entry) => entry.
|
|
2738
|
+
this.lastSessionListings.set(contextKey, visibleEntries.map((entry) => entry.reference));
|
|
2358
2739
|
const cardEntries = visibleEntries.map((entry, index) => {
|
|
2359
2740
|
const marker = entry.current ? "✅" : entry.active ? "🟢 **活跃**" : "•";
|
|
2360
|
-
const showStop = entry.status === "外部执行中"
|
|
2741
|
+
const showStop = entry.status === "外部执行中"
|
|
2742
|
+
&& entry.reference !== options.forceSwitchTaskId
|
|
2743
|
+
&& entry.id !== options.forceSwitchTaskId;
|
|
2361
2744
|
const actions: TaskListCardAction[] = entry.current ? [] : [{
|
|
2362
2745
|
text: showStop ? "Stop" : "Switch",
|
|
2363
2746
|
type: showStop ? "danger" as const : "default" as const,
|
|
2364
2747
|
value: {
|
|
2365
2748
|
action: showStop ? "session_stop" : "session_switch",
|
|
2366
|
-
sessionId: entry.
|
|
2749
|
+
sessionId: entry.reference,
|
|
2367
2750
|
...(searchTerm ? { searchTerm } : {}),
|
|
2368
2751
|
visibleCount: String(visibleCount),
|
|
2369
2752
|
contextKey,
|
|
@@ -2373,7 +2756,7 @@ export class ProxySessionController {
|
|
|
2373
2756
|
text: "New",
|
|
2374
2757
|
value: {
|
|
2375
2758
|
action: "session_new",
|
|
2376
|
-
sessionId: entry.
|
|
2759
|
+
sessionId: entry.reference,
|
|
2377
2760
|
...(searchTerm ? { searchTerm } : {}),
|
|
2378
2761
|
visibleCount: String(visibleCount),
|
|
2379
2762
|
contextKey,
|
|
@@ -2383,7 +2766,7 @@ export class ProxySessionController {
|
|
|
2383
2766
|
text: "NewGroup",
|
|
2384
2767
|
value: {
|
|
2385
2768
|
action: "session_new_group",
|
|
2386
|
-
sessionId: entry.
|
|
2769
|
+
sessionId: entry.reference,
|
|
2387
2770
|
...(searchTerm ? { searchTerm } : {}),
|
|
2388
2771
|
visibleCount: String(visibleCount),
|
|
2389
2772
|
contextKey,
|
|
@@ -2393,7 +2776,7 @@ export class ProxySessionController {
|
|
|
2393
2776
|
text: "Fork",
|
|
2394
2777
|
value: {
|
|
2395
2778
|
action: "session_fork",
|
|
2396
|
-
sessionId: entry.
|
|
2779
|
+
sessionId: entry.reference,
|
|
2397
2780
|
...(searchTerm ? { searchTerm } : {}),
|
|
2398
2781
|
visibleCount: String(visibleCount),
|
|
2399
2782
|
contextKey,
|
|
@@ -2403,7 +2786,7 @@ export class ProxySessionController {
|
|
|
2403
2786
|
text: "ForkGroup",
|
|
2404
2787
|
value: {
|
|
2405
2788
|
action: "session_fork_group",
|
|
2406
|
-
sessionId: entry.
|
|
2789
|
+
sessionId: entry.reference,
|
|
2407
2790
|
...(searchTerm ? { searchTerm } : {}),
|
|
2408
2791
|
visibleCount: String(visibleCount),
|
|
2409
2792
|
contextKey,
|
|
@@ -2413,14 +2796,14 @@ export class ProxySessionController {
|
|
|
2413
2796
|
text: "Status",
|
|
2414
2797
|
value: {
|
|
2415
2798
|
action: "session_status",
|
|
2416
|
-
sessionId: entry.
|
|
2799
|
+
sessionId: entry.reference,
|
|
2417
2800
|
contextKey,
|
|
2418
2801
|
},
|
|
2419
2802
|
});
|
|
2420
2803
|
return {
|
|
2421
2804
|
lines: [
|
|
2422
2805
|
`**${index + 1}.** ${marker} **${cardText(entry.title)}** ${cardText(entry.id)}`,
|
|
2423
|
-
`${entry.status} · ${entry.updatedLabel} · ${cardText(entry.cwd || "目录未知")}`,
|
|
2806
|
+
`${entry.status} · ${entry.updatedLabel} · ${cardText(entry.agentName)} · ${cardText(entry.cwd || "目录未知")}`,
|
|
2424
2807
|
],
|
|
2425
2808
|
actions,
|
|
2426
2809
|
};
|
|
@@ -2483,6 +2866,46 @@ export class ProxySessionController {
|
|
|
2483
2866
|
});
|
|
2484
2867
|
}
|
|
2485
2868
|
|
|
2869
|
+
private findStoredSessionByReference(reference: string, contextKey?: string): SessionRecord | undefined {
|
|
2870
|
+
const scoped = parseRemoteSessionReference(reference);
|
|
2871
|
+
return scoped
|
|
2872
|
+
? this.store.findSessionByRemoteSessionId(scoped.remoteSessionId, contextKey, scoped.agentName)
|
|
2873
|
+
: this.store.findSessionByRemoteSessionId(reference, contextKey);
|
|
2874
|
+
}
|
|
2875
|
+
|
|
2876
|
+
private async resolveRemoteCodexSession(reference: string): Promise<AgentRemoteSession> {
|
|
2877
|
+
const scoped = parseRemoteSessionReference(reference);
|
|
2878
|
+
const candidates = scoped
|
|
2879
|
+
? [[scoped.agentName, this.runtimes.forAgent(scoped.agentName)] as const]
|
|
2880
|
+
: this.runtimes.entries("codex");
|
|
2881
|
+
if (candidates.length === 0) throw new Error("未配置 Codex Agent。");
|
|
2882
|
+
|
|
2883
|
+
const reads = await Promise.allSettled(candidates.map(async ([agentName, runtime]) => {
|
|
2884
|
+
if (runtime.kind !== "codex" || !runtime.readRemoteSession) {
|
|
2885
|
+
throw new Error(`Agent ${agentName} 不支持读取远端任务。`);
|
|
2886
|
+
}
|
|
2887
|
+
return {
|
|
2888
|
+
agentName,
|
|
2889
|
+
runtime,
|
|
2890
|
+
remote: await runtime.readRemoteSession(scoped?.remoteSessionId ?? reference),
|
|
2891
|
+
} satisfies AgentRemoteSession;
|
|
2892
|
+
}));
|
|
2893
|
+
const matches = reads
|
|
2894
|
+
.filter((result): result is PromiseFulfilledResult<AgentRemoteSession> => result.status === "fulfilled")
|
|
2895
|
+
.map((result) => result.value);
|
|
2896
|
+
if (matches.length === 1) return matches[0]!;
|
|
2897
|
+
if (matches.length > 1) {
|
|
2898
|
+
throw new Error(`多个 Agent 中存在相同任务 ID:${reference}。请先使用 /sessions,再通过序号选择任务。`);
|
|
2899
|
+
}
|
|
2900
|
+
const details = reads
|
|
2901
|
+
.map((result, index) => result.status === "rejected"
|
|
2902
|
+
? `${candidates[index]?.[0] ?? "unknown"}: ${runtimeErrorMessage(result.reason)}`
|
|
2903
|
+
: undefined)
|
|
2904
|
+
.filter((detail): detail is string => Boolean(detail))
|
|
2905
|
+
.join(";");
|
|
2906
|
+
throw new Error(`找不到 Codex 任务:${scoped?.remoteSessionId ?? reference}${details ? `(${details})` : ""}`);
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2486
2909
|
private async switchSession(contextKey: string, reference?: string): Promise<void> {
|
|
2487
2910
|
this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent!);
|
|
2488
2911
|
const taskId = this.resolveSessionReference(contextKey, reference);
|
|
@@ -2490,9 +2913,9 @@ export class ProxySessionController {
|
|
|
2490
2913
|
if (direct?.status === "closed") {
|
|
2491
2914
|
throw new Error(`找不到任务:${taskId}`);
|
|
2492
2915
|
}
|
|
2493
|
-
const existing = direct ?? this.
|
|
2916
|
+
const existing = direct ?? this.findStoredSessionByReference(taskId);
|
|
2494
2917
|
if (existing) {
|
|
2495
|
-
const runtime = this.runtimes.forAgent(
|
|
2918
|
+
const runtime = this.runtimes.forAgent(existing.agentName);
|
|
2496
2919
|
await this.assertSessionTurnOwnership(existing, runtime);
|
|
2497
2920
|
this.store.attachSessionToContext(contextKey, existing.localSessionId);
|
|
2498
2921
|
this.store.setCurrentSession(contextKey, existing.localSessionId);
|
|
@@ -2501,16 +2924,11 @@ export class ProxySessionController {
|
|
|
2501
2924
|
return;
|
|
2502
2925
|
}
|
|
2503
2926
|
|
|
2504
|
-
const
|
|
2505
|
-
if (!runtime.readRemoteSession) throw new Error("当前 Codex 运行时不支持读取任务。");
|
|
2506
|
-
const remote = await runtime.readRemoteSession(taskId);
|
|
2927
|
+
const { agentName, remote } = await this.resolveRemoteCodexSession(taskId);
|
|
2507
2928
|
if (remote.status === "active" || remote.lastTurnStatus === "inProgress") {
|
|
2508
2929
|
throw new Error(`这个任务正在外部 Codex 中执行,当前不会切换。可使用 /status ${taskId} 查看进度。`);
|
|
2509
2930
|
}
|
|
2510
2931
|
if (!remote.cwd) throw new Error("这个 Codex 任务没有可用的工作目录,暂时无法切换。");
|
|
2511
|
-
const agentEntry = Object.entries(this.config.agents).find(([, agent]) => agent.kind === "codex");
|
|
2512
|
-
if (!agentEntry) throw new Error("未配置 Codex Agent。");
|
|
2513
|
-
const [agentName] = agentEntry;
|
|
2514
2932
|
const localSessionId = createId("sess");
|
|
2515
2933
|
this.store.createSession({
|
|
2516
2934
|
localSessionId,
|
|
@@ -2543,17 +2961,16 @@ export class ProxySessionController {
|
|
|
2543
2961
|
return;
|
|
2544
2962
|
}
|
|
2545
2963
|
|
|
2546
|
-
const existing = this.
|
|
2964
|
+
const existing = this.findStoredSessionByReference(taskId);
|
|
2547
2965
|
if (existing) {
|
|
2548
2966
|
await this.cancelSession({ ...existing, contextKey });
|
|
2549
2967
|
return;
|
|
2550
2968
|
}
|
|
2551
2969
|
|
|
2552
|
-
const runtime = this.
|
|
2970
|
+
const { runtime, remote } = await this.resolveRemoteCodexSession(taskId);
|
|
2553
2971
|
if (!runtime.readRemoteSession || !runtime.interruptRemoteTurn) {
|
|
2554
2972
|
throw new Error("当前 Codex 运行时不支持停止外部任务。");
|
|
2555
2973
|
}
|
|
2556
|
-
const remote = await runtime.readRemoteSession(taskId);
|
|
2557
2974
|
const turnId = remote.status === "active" || remote.lastTurnStatus === "inProgress"
|
|
2558
2975
|
? remote.lastTurnId
|
|
2559
2976
|
: undefined;
|
|
@@ -2628,11 +3045,22 @@ export class ProxySessionController {
|
|
|
2628
3045
|
return undefined;
|
|
2629
3046
|
}
|
|
2630
3047
|
|
|
2631
|
-
private async setDefaultAgent(
|
|
3048
|
+
private async setDefaultAgent(
|
|
3049
|
+
contextKey: string,
|
|
3050
|
+
agentName: string,
|
|
3051
|
+
options: ExecutionSettingsCardOptions = {},
|
|
3052
|
+
): Promise<void> {
|
|
2632
3053
|
this.ensureAgent(agentName);
|
|
2633
3054
|
this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent!);
|
|
2634
3055
|
this.store.setDefaultAgent(contextKey, agentName);
|
|
2635
|
-
|
|
3056
|
+
if (Object.keys(this.config.agents).length <= 1) {
|
|
3057
|
+
await this.outbound.sendText(contextKey, `当前 Agent:${agentName}\n当前没有其他 Agent 可以切换。`);
|
|
3058
|
+
return;
|
|
3059
|
+
}
|
|
3060
|
+
await this.openExecutionSettings(contextKey, "agent", {
|
|
3061
|
+
...options,
|
|
3062
|
+
notice: `默认 Agent 已切换为 ${cardCode(agentName)},从下一次新建任务生效。`,
|
|
3063
|
+
});
|
|
2636
3064
|
}
|
|
2637
3065
|
|
|
2638
3066
|
private async status(
|
|
@@ -2647,7 +3075,7 @@ export class ProxySessionController {
|
|
|
2647
3075
|
const direct = this.store.getSession(targetSessionId);
|
|
2648
3076
|
current = direct
|
|
2649
3077
|
? { ...direct, contextKey }
|
|
2650
|
-
: this.
|
|
3078
|
+
: this.findStoredSessionByReference(targetSessionId, contextKey);
|
|
2651
3079
|
if (!current) {
|
|
2652
3080
|
await this.statusForCodexTask(contextKey, targetSessionId, options);
|
|
2653
3081
|
return;
|
|
@@ -2658,7 +3086,7 @@ export class ProxySessionController {
|
|
|
2658
3086
|
|
|
2659
3087
|
if (current && current.status === "running") {
|
|
2660
3088
|
try {
|
|
2661
|
-
const runtime = this.runtimes.forAgent(
|
|
3089
|
+
const runtime = this.runtimes.forAgent(current.agentName);
|
|
2662
3090
|
if (runtime.getSession(current.localSessionId)) {
|
|
2663
3091
|
await runtime.synchronizeSession(current.localSessionId);
|
|
2664
3092
|
current = this.store.getSession(current.localSessionId) ?? current;
|
|
@@ -2672,7 +3100,7 @@ export class ProxySessionController {
|
|
|
2672
3100
|
let remote: RemoteSessionSummary | undefined;
|
|
2673
3101
|
let goal: RuntimeGoal | undefined;
|
|
2674
3102
|
if (current?.remoteSessionId) {
|
|
2675
|
-
const runtime = this.runtimes.forAgent(
|
|
3103
|
+
const runtime = this.runtimes.forAgent(current.agentName);
|
|
2676
3104
|
if (runtime.readRemoteSession) {
|
|
2677
3105
|
try {
|
|
2678
3106
|
remote = await runtime.readRemoteSession(current.remoteSessionId);
|
|
@@ -2700,7 +3128,7 @@ export class ProxySessionController {
|
|
|
2700
3128
|
taskLines.push("无。直接发送消息即可创建一个未指定项目的 Codex 任务。");
|
|
2701
3129
|
} else {
|
|
2702
3130
|
const agent = this.ensureAgent(current.agentName);
|
|
2703
|
-
const runtimeSession = this.runtimes.forAgent(
|
|
3131
|
+
const runtimeSession = this.runtimes.forAgent(current.agentName).getSession(current.localSessionId);
|
|
2704
3132
|
activeTurnId = runtimeSession?.activeTurnId;
|
|
2705
3133
|
const remoteActive = isRemoteSessionActive(remote);
|
|
2706
3134
|
activeTurnId = remoteActive ? remote?.lastTurnId ?? activeTurnId : activeTurnId;
|
|
@@ -2715,7 +3143,7 @@ export class ProxySessionController {
|
|
|
2715
3143
|
taskLines.push(
|
|
2716
3144
|
`**标题**:${cardCode(current.title ?? "未命名任务")}`,
|
|
2717
3145
|
`**工作目录**:${cardCode(current.cwd)}`,
|
|
2718
|
-
|
|
3146
|
+
`**Provider / 模型 / 思考强度**:${cardCode(current.modelProvider ?? "Codex 默认")} / ${cardCode(current.model ?? "默认")} / ${cardCode(current.reasoningEffort ?? "自动")}`,
|
|
2719
3147
|
`**状态 / 最近结果**:${statusLabel} / ${resultLabel}`,
|
|
2720
3148
|
`**Agent**:${cardCode(agent.title)}`,
|
|
2721
3149
|
`**权限 / 任务范围**:${current.permissionMode === "confirm" ? "执行前确认" : "自动执行"} / ${detectProjectlessWorkspace(current.cwd) ? "未指定项目" : "指定项目"}`,
|
|
@@ -2752,12 +3180,17 @@ export class ProxySessionController {
|
|
|
2752
3180
|
const title = targetSessionId && current
|
|
2753
3181
|
? `Codex 状态:${truncateText((current.title ?? current.remoteSessionId ?? current.localSessionId).replace(/\s+/g, " "), 40)}`
|
|
2754
3182
|
: "Codex 状态";
|
|
2755
|
-
const taskId = current?.remoteSessionId
|
|
3183
|
+
const taskId = current?.remoteSessionId
|
|
3184
|
+
? remoteSessionReference(current.agentName, current.remoteSessionId)
|
|
3185
|
+
: current?.localSessionId;
|
|
2756
3186
|
const isCurrent = Boolean(current && current.localSessionId === context.currentSessionId);
|
|
2757
3187
|
const remoteActive = isRemoteSessionActive(remote);
|
|
2758
3188
|
const botOwnsActiveTurn = Boolean(localCurrent && remote && isBotOwnedActiveTurn(localCurrent, remote));
|
|
2759
3189
|
const active = Boolean(activeTurnId || current?.status === "running" || remoteActive);
|
|
2760
|
-
const forceSwitch = Boolean(taskId &&
|
|
3190
|
+
const forceSwitch = Boolean(taskId && (
|
|
3191
|
+
options.forceSwitchTaskId === taskId
|
|
3192
|
+
|| options.forceSwitchTaskId === current?.remoteSessionId
|
|
3193
|
+
));
|
|
2761
3194
|
const taskActions: TaskListCardAction[] = !taskId ? []
|
|
2762
3195
|
: remoteActive && !botOwnsActiveTurn && !forceSwitch
|
|
2763
3196
|
? [statusCardAction("Stop", "danger", "session_stop", taskId, contextKey)]
|
|
@@ -2777,12 +3210,11 @@ export class ProxySessionController {
|
|
|
2777
3210
|
|
|
2778
3211
|
private async statusForCodexTask(
|
|
2779
3212
|
contextKey: string,
|
|
2780
|
-
|
|
3213
|
+
reference: string,
|
|
2781
3214
|
options: StatusCardOptions = {},
|
|
2782
3215
|
): Promise<void> {
|
|
2783
|
-
const
|
|
2784
|
-
|
|
2785
|
-
const remote = await runtime.readRemoteSession(remoteSessionId);
|
|
3216
|
+
const { agentName, remote } = await this.resolveRemoteCodexSession(reference);
|
|
3217
|
+
const actionReference = remoteSessionReference(agentName, remote.id);
|
|
2786
3218
|
const sections: CardSection[] = [
|
|
2787
3219
|
{
|
|
2788
3220
|
title: "指定任务",
|
|
@@ -2790,6 +3222,7 @@ export class ProxySessionController {
|
|
|
2790
3222
|
`**标题**:${cardCode(remote.title ?? remote.preview ?? "未命名任务")}`,
|
|
2791
3223
|
`**工作目录**:${cardCode(remote.cwd || "目录未知")}`,
|
|
2792
3224
|
`**状态 / 当前任务**:${remoteSessionDetailStatus(remote)} / 未切换`,
|
|
3225
|
+
`**Agent**:${cardCode(agentName)}`,
|
|
2793
3226
|
`**Codex 任务 ID**:${cardCode(remote.id)}`,
|
|
2794
3227
|
`**最近回合**:${cardCode(remote.lastTurnId ?? "无")} ${remoteTurnStatusLabel(remote.lastTurnStatus)}`,
|
|
2795
3228
|
`**创建时间 / 最近活动**:${formatRemoteTime(remote.createdAt)} / ${formatRemoteTime(latestRemoteTimestamp(remote.recencyAt, remote.updatedAt))}`,
|
|
@@ -2809,14 +3242,16 @@ export class ProxySessionController {
|
|
|
2809
3242
|
},
|
|
2810
3243
|
];
|
|
2811
3244
|
const title = `Codex 状态:${truncateText((remote.title ?? remote.preview ?? remote.id).replace(/\s+/g, " "), 40)}`;
|
|
2812
|
-
const showStop = isRemoteSessionActive(remote)
|
|
3245
|
+
const showStop = isRemoteSessionActive(remote)
|
|
3246
|
+
&& options.forceSwitchTaskId !== actionReference
|
|
3247
|
+
&& options.forceSwitchTaskId !== remote.id;
|
|
2813
3248
|
const actions = [
|
|
2814
|
-
statusRefreshAction(
|
|
3249
|
+
statusRefreshAction(actionReference, contextKey),
|
|
2815
3250
|
statusCardAction(
|
|
2816
3251
|
showStop ? "Stop" : "Switch",
|
|
2817
3252
|
showStop ? "danger" : "default",
|
|
2818
3253
|
showStop ? "session_stop" : "session_switch",
|
|
2819
|
-
|
|
3254
|
+
actionReference,
|
|
2820
3255
|
contextKey,
|
|
2821
3256
|
),
|
|
2822
3257
|
];
|
|
@@ -2840,7 +3275,7 @@ export class ProxySessionController {
|
|
|
2840
3275
|
title: "任务管理",
|
|
2841
3276
|
lines: [
|
|
2842
3277
|
"**/new [title] [--dir <cwd> | --nodir]** 使用默认 Agent 创建任务;--nodir 强制创建 Projectless 任务",
|
|
2843
|
-
"**/newgroup [title]**
|
|
3278
|
+
"**/newgroup [title] [--dir <cwd> | --nodir]** 创建飞书群和新任务;参数与 /new 相同",
|
|
2844
3279
|
"**/forkgroup [title]** 从当前任务最新已完成轮次创建分支并绑定到新群",
|
|
2845
3280
|
"**/fork [序号或任务 ID]** 从当前或指定任务创建分支;运行中使用最近已完成轮次",
|
|
2846
3281
|
"**/title <新标题>** 修改当前任务的标题",
|
|
@@ -2855,22 +3290,23 @@ export class ProxySessionController {
|
|
|
2855
3290
|
"**/stop** 停止当前执行",
|
|
2856
3291
|
"**/queue <prompt>**(兼容 **/nosteer**) 不追加到当前轮次,按顺序排队为后续轮次",
|
|
2857
3292
|
"**/goal [目标]** 查看或创建长任务 Goal;支持 pause、resume、edit、clear",
|
|
2858
|
-
"**/
|
|
2859
|
-
"**/
|
|
2860
|
-
"**/
|
|
3293
|
+
"**/provider** 选择 AI 服务提供商;没有其他候选时仅显示当前 Provider",
|
|
3294
|
+
"**/model** 选择当前任务使用的模型",
|
|
3295
|
+
"**/thinking** 设置模型的思考强度",
|
|
3296
|
+
"**/permissions** 设置执行工具前是否需要确认",
|
|
2861
3297
|
],
|
|
2862
3298
|
},
|
|
2863
3299
|
{
|
|
2864
3300
|
title: "Agent",
|
|
2865
3301
|
lines: [
|
|
2866
|
-
"**/agent [name]**
|
|
3302
|
+
"**/agent [name]** 选择新任务使用的默认 Agent;仅配置一个时直接显示当前 Agent",
|
|
2867
3303
|
],
|
|
2868
3304
|
},
|
|
2869
3305
|
{
|
|
2870
3306
|
title: "系统",
|
|
2871
3307
|
lines: [
|
|
2872
3308
|
"**/status [序号或任务 ID]** 查看当前或指定任务的详细状态",
|
|
2873
|
-
"**/restart**
|
|
3309
|
+
"**/restart [--force]** 默认安全重启;--force 立即重启",
|
|
2874
3310
|
"**/help** 显示本帮助",
|
|
2875
3311
|
],
|
|
2876
3312
|
},
|
|
@@ -2904,6 +3340,17 @@ export class ProxySessionController {
|
|
|
2904
3340
|
: current.cwd;
|
|
2905
3341
|
}
|
|
2906
3342
|
|
|
3343
|
+
private inheritedExecutionSettings(contextKey: string, agentName: string): SessionExecutionSettings {
|
|
3344
|
+
const current = this.currentSession(contextKey);
|
|
3345
|
+
if (!current || current.agentName !== agentName) return {};
|
|
3346
|
+
return {
|
|
3347
|
+
modelProvider: current.modelProvider,
|
|
3348
|
+
model: current.model,
|
|
3349
|
+
reasoningEffort: current.reasoningEffort,
|
|
3350
|
+
permissionMode: current.permissionMode,
|
|
3351
|
+
};
|
|
3352
|
+
}
|
|
3353
|
+
|
|
2907
3354
|
private currentProjectCwd(contextKey: string): string | undefined {
|
|
2908
3355
|
const current = this.currentSession(contextKey);
|
|
2909
3356
|
return current && !detectProjectlessWorkspace(current.cwd) ? current.cwd : undefined;
|
|
@@ -2969,7 +3416,9 @@ function statusRefreshAction(
|
|
|
2969
3416
|
}
|
|
2970
3417
|
|
|
2971
3418
|
interface UnifiedTaskListEntry {
|
|
3419
|
+
reference: string;
|
|
2972
3420
|
id: string;
|
|
3421
|
+
agentName: string;
|
|
2973
3422
|
title: string;
|
|
2974
3423
|
cwd: string;
|
|
2975
3424
|
status: string;
|
|
@@ -2981,17 +3430,17 @@ interface UnifiedTaskListEntry {
|
|
|
2981
3430
|
|
|
2982
3431
|
function mergeTaskList(
|
|
2983
3432
|
localSessions: SessionRecord[],
|
|
2984
|
-
remoteSessions:
|
|
3433
|
+
remoteSessions: AgentRemoteSessionSummary[],
|
|
2985
3434
|
currentLocalSessionId?: string,
|
|
2986
3435
|
): UnifiedTaskListEntry[] {
|
|
2987
3436
|
const localByRemoteId = new Map(
|
|
2988
3437
|
localSessions
|
|
2989
3438
|
.filter((session) => session.runtimeKind === "codex" && session.remoteSessionId)
|
|
2990
|
-
.map((session) => [session.remoteSessionId
|
|
3439
|
+
.map((session) => [agentRemoteKey(session.agentName, session.remoteSessionId!), session]),
|
|
2991
3440
|
);
|
|
2992
3441
|
const representedLocalIds = new Set<string>();
|
|
2993
|
-
const entries = remoteSessions.map((remote): UnifiedTaskListEntry => {
|
|
2994
|
-
const local = localByRemoteId.get(remote.id);
|
|
3442
|
+
const entries = remoteSessions.map(({ agentName, session: remote }): UnifiedTaskListEntry => {
|
|
3443
|
+
const local = localByRemoteId.get(agentRemoteKey(agentName, remote.id));
|
|
2995
3444
|
if (local) representedLocalIds.add(local.localSessionId);
|
|
2996
3445
|
const active = remote.status === "active" || remote.lastTurnStatus === "inProgress";
|
|
2997
3446
|
const status = remote.status === "active" || remote.lastTurnStatus === "inProgress"
|
|
@@ -2999,7 +3448,9 @@ function mergeTaskList(
|
|
|
2999
3448
|
: remoteSessionStatusLabel(remote.status);
|
|
3000
3449
|
const recencyAt = remote.recencyAt ?? remote.updatedAt;
|
|
3001
3450
|
return {
|
|
3451
|
+
reference: remoteSessionReference(agentName, remote.id),
|
|
3002
3452
|
id: remote.id,
|
|
3453
|
+
agentName,
|
|
3003
3454
|
title: remote.title ?? remote.preview ?? local?.title ?? "未命名任务",
|
|
3004
3455
|
cwd: remote.cwd || local?.cwd || "",
|
|
3005
3456
|
status,
|
|
@@ -3014,7 +3465,11 @@ function mergeTaskList(
|
|
|
3014
3465
|
if (representedLocalIds.has(local.localSessionId)) continue;
|
|
3015
3466
|
const updatedAt = Date.parse(local.updatedAt);
|
|
3016
3467
|
entries.push({
|
|
3468
|
+
reference: local.remoteSessionId
|
|
3469
|
+
? remoteSessionReference(local.agentName, local.remoteSessionId)
|
|
3470
|
+
: local.localSessionId,
|
|
3017
3471
|
id: local.remoteSessionId ?? local.localSessionId,
|
|
3472
|
+
agentName: local.agentName,
|
|
3018
3473
|
title: local.title ?? "未命名任务",
|
|
3019
3474
|
cwd: local.cwd,
|
|
3020
3475
|
status: sessionStatusLabel(local.status),
|
|
@@ -3027,6 +3482,32 @@ function mergeTaskList(
|
|
|
3027
3482
|
return entries;
|
|
3028
3483
|
}
|
|
3029
3484
|
|
|
3485
|
+
function agentRemoteKey(agentName: string, remoteSessionId: string): string {
|
|
3486
|
+
return `${agentName}\u0000${remoteSessionId}`;
|
|
3487
|
+
}
|
|
3488
|
+
|
|
3489
|
+
function remoteSessionReference(agentName: string, remoteSessionId: string): string {
|
|
3490
|
+
return `${REMOTE_SESSION_REFERENCE_PREFIX}${encodeURIComponent(agentName)}:${encodeURIComponent(remoteSessionId)}`;
|
|
3491
|
+
}
|
|
3492
|
+
|
|
3493
|
+
function parseRemoteSessionReference(reference: string): { agentName: string; remoteSessionId: string } | undefined {
|
|
3494
|
+
if (!reference.startsWith(REMOTE_SESSION_REFERENCE_PREFIX)) return undefined;
|
|
3495
|
+
const value = reference.slice(REMOTE_SESSION_REFERENCE_PREFIX.length);
|
|
3496
|
+
const separator = value.indexOf(":");
|
|
3497
|
+
if (separator <= 0 || separator === value.length - 1) return undefined;
|
|
3498
|
+
try {
|
|
3499
|
+
const agentName = decodeURIComponent(value.slice(0, separator));
|
|
3500
|
+
const remoteSessionId = decodeURIComponent(value.slice(separator + 1));
|
|
3501
|
+
return agentName && remoteSessionId ? { agentName, remoteSessionId } : undefined;
|
|
3502
|
+
} catch {
|
|
3503
|
+
return undefined;
|
|
3504
|
+
}
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
function runtimeErrorMessage(error: unknown): string {
|
|
3508
|
+
return error instanceof Error ? error.message : String(error);
|
|
3509
|
+
}
|
|
3510
|
+
|
|
3030
3511
|
function isMissingRolloutError(error: unknown): boolean {
|
|
3031
3512
|
const message = error instanceof Error ? error.message : String(error);
|
|
3032
3513
|
return /no rollout found|rollout[^\n]*(?:not found|missing)|thread\/resume failed/i.test(message);
|
|
@@ -3208,6 +3689,21 @@ function cardCode(value: string): string {
|
|
|
3208
3689
|
return `\`${value.replaceAll("`", "'")}\``;
|
|
3209
3690
|
}
|
|
3210
3691
|
|
|
3692
|
+
function permissionModeValue(value: unknown): PermissionMode {
|
|
3693
|
+
if (value === "auto" || value === "confirm") return value;
|
|
3694
|
+
throw new Error("权限模式只能是 auto 或 confirm。");
|
|
3695
|
+
}
|
|
3696
|
+
|
|
3697
|
+
function executionSettingsTabValue(value: unknown): ExecutionSettingsTab {
|
|
3698
|
+
if (value === "agent" || value === "provider" || value === "model" || value === "thinking" || value === "permission") return value;
|
|
3699
|
+
throw new Error("设置卡片的 tab 无效,请重新发送设置命令。");
|
|
3700
|
+
}
|
|
3701
|
+
|
|
3702
|
+
function requiredCardMessageId(value: string | undefined): string {
|
|
3703
|
+
if (value) return value;
|
|
3704
|
+
throw new Error("无法更新设置卡片,请重新发送设置命令。");
|
|
3705
|
+
}
|
|
3706
|
+
|
|
3211
3707
|
function remoteSessionStatusLabel(status: RemoteSessionSummary["status"]): string {
|
|
3212
3708
|
const labels: Record<RemoteSessionSummary["status"], string> = {
|
|
3213
3709
|
active: "执行中",
|
|
@@ -3243,6 +3739,10 @@ function mergeRemoteTaskStatus(record: SessionRecord, remote?: RemoteSessionSumm
|
|
|
3243
3739
|
...record,
|
|
3244
3740
|
title: remote.title ?? record.title,
|
|
3245
3741
|
cwd: remote.cwd || record.cwd,
|
|
3742
|
+
modelProvider: remote.modelProvider ?? record.modelProvider,
|
|
3743
|
+
model: remote.model ?? record.model,
|
|
3744
|
+
reasoningEffort: remote.reasoningEffort ?? record.reasoningEffort,
|
|
3745
|
+
permissionMode: remote.permissionMode ?? record.permissionMode,
|
|
3246
3746
|
status: isRemoteSessionActive(remote)
|
|
3247
3747
|
? "running"
|
|
3248
3748
|
: remote.lastTurnStatus
|
|
@@ -3299,9 +3799,7 @@ function isBotOwnedActiveTurn(record: SessionRecord, remote: RemoteSessionSummar
|
|
|
3299
3799
|
function isQueueIndependentCommand(command: Command): boolean {
|
|
3300
3800
|
if (["stop", "status", "restart", "help", "sessions", "goal", "nosteer"].includes(command.type)) return true;
|
|
3301
3801
|
if (command.type === "agent") return command.agent === undefined;
|
|
3302
|
-
if (
|
|
3303
|
-
if (command.type === "thinking") return command.effort === undefined;
|
|
3304
|
-
if (command.type === "permissions") return command.mode === undefined;
|
|
3802
|
+
if (["model", "provider", "thinking", "permissions"].includes(command.type)) return true;
|
|
3305
3803
|
return false;
|
|
3306
3804
|
}
|
|
3307
3805
|
|
|
@@ -3311,7 +3809,7 @@ function parseSessionVisibleCount(value: unknown): number {
|
|
|
3311
3809
|
}
|
|
3312
3810
|
|
|
3313
3811
|
function isPromptCommand(command: Command): boolean {
|
|
3314
|
-
return command.type === "
|
|
3812
|
+
return command.type === "prompt" || command.type === "nosteer";
|
|
3315
3813
|
}
|
|
3316
3814
|
|
|
3317
3815
|
function runtimePrompt(text: string, localImagePaths?: string[]): RuntimePrompt {
|
|
@@ -3332,8 +3830,8 @@ function formatNewGroupName(
|
|
|
3332
3830
|
includeTimestamp: boolean,
|
|
3333
3831
|
): string {
|
|
3334
3832
|
const prefix = `[${agentName}] `;
|
|
3335
|
-
const
|
|
3336
|
-
const titlePrefix = `${prefix}${
|
|
3833
|
+
const projectPrefix = projectCwd ? `${formatGroupProjectDirectory(projectCwd)} ` : "";
|
|
3834
|
+
const titlePrefix = `${prefix}${projectPrefix}`;
|
|
3337
3835
|
const timestampSuffix = includeTimestamp ? ` (${formatGroupDateSuffix(date)})` : "";
|
|
3338
3836
|
const availableTitleLength = FEISHU_GROUP_NAME_MAX_LENGTH
|
|
3339
3837
|
- Array.from(titlePrefix).length
|
|
@@ -3345,8 +3843,8 @@ function formatNewGroupName(
|
|
|
3345
3843
|
const FEISHU_GROUP_NAME_MAX_LENGTH = 60;
|
|
3346
3844
|
const GROUP_PROJECT_DIRECTORY_MAX_LENGTH = 15;
|
|
3347
3845
|
|
|
3348
|
-
function formatGroupProjectDirectory(projectCwd: string
|
|
3349
|
-
const value =
|
|
3846
|
+
function formatGroupProjectDirectory(projectCwd: string): string {
|
|
3847
|
+
const value = abbreviateHomeDirectory(projectCwd);
|
|
3350
3848
|
if (Array.from(value).length <= GROUP_PROJECT_DIRECTORY_MAX_LENGTH) return `[${value}]`;
|
|
3351
3849
|
|
|
3352
3850
|
const levels = value
|