@keyou007/agent-bot 0.1.9 → 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 +23 -0
- package/README.md +22 -10
- package/README.zh.md +22 -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/cli/FeishuAppConfiguration.js +84 -22
- package/dist/cli/FeishuAppConfiguration.js.map +1 -1
- package/dist/cli/FeishuAppRegistration.js +12 -9
- package/dist/cli/FeishuAppRegistration.js.map +1 -1
- package/dist/cli/Initializer.js +7 -6
- package/dist/cli/Initializer.js.map +1 -1
- package/dist/cli/LocalControlClient.js +3 -2
- package/dist/cli/LocalControlClient.js.map +1 -1
- package/dist/cli/LocalControlServer.js +2 -1
- package/dist/cli/LocalControlServer.js.map +1 -1
- package/dist/cli/OptionalAuthorizationSkip.js +27 -6
- package/dist/cli/OptionalAuthorizationSkip.js.map +1 -1
- package/dist/cli/ServerStarter.js +12 -3
- package/dist/cli/ServerStarter.js.map +1 -1
- package/dist/cli/SkillRegistry.js +5 -4
- package/dist/cli/SkillRegistry.js.map +1 -1
- package/dist/cli/VerificationOutput.js +2 -1
- package/dist/cli/VerificationOutput.js.map +1 -1
- package/dist/cli/help.js +48 -1
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/i18n.js +27 -0
- package/dist/cli/i18n.js.map +1 -0
- package/dist/cli/initOptions.js +5 -4
- package/dist/cli/initOptions.js.map +1 -1
- package/dist/cli/packageVersion.js +2 -1
- package/dist/cli/packageVersion.js.map +1 -1
- package/dist/cli/profile.js +4 -3
- package/dist/cli/profile.js.map +1 -1
- package/dist/cli/serverStatus.js +15 -14
- package/dist/cli/serverStatus.js.map +1 -1
- package/dist/cli/taskChatRoute.js +3 -2
- package/dist/cli/taskChatRoute.js.map +1 -1
- package/dist/cli.js +165 -107
- package/dist/cli.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 +108 -47
- 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 +595 -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 +27 -6
- package/docs/technical-reference.zh.md +27 -6
- package/npm-shrinkwrap.json +2 -2
- package/package.json +2 -1
- package/skills/agent-bot/SKILL.md +19 -9
- package/src/acp/AcpSessionManager.ts +73 -54
- package/src/cli/FeishuAppConfiguration.ts +130 -18
- package/src/cli/FeishuAppRegistration.ts +24 -9
- package/src/cli/Initializer.ts +25 -6
- package/src/cli/LocalControlClient.ts +9 -2
- package/src/cli/LocalControlServer.ts +2 -1
- package/src/cli/OptionalAuthorizationSkip.ts +51 -6
- package/src/cli/ServerStarter.ts +20 -3
- package/src/cli/SkillRegistry.ts +17 -4
- package/src/cli/VerificationOutput.ts +4 -1
- package/src/cli/help.ts +50 -1
- package/src/cli/i18n.ts +38 -0
- package/src/cli/initOptions.ts +17 -4
- package/src/cli/packageVersion.ts +5 -1
- package/src/cli/profile.ts +13 -3
- package/src/cli/serverStatus.ts +19 -14
- package/src/cli/taskChatRoute.ts +9 -2
- package/src/cli.ts +290 -103
- 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 +123 -46
- 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 +712 -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,10 @@ export class ProxySessionController {
|
|
|
1675
1799
|
`已从${prepared.sourceDescription}创建分支。`,
|
|
1676
1800
|
`当前任务:${taskDescription}`,
|
|
1677
1801
|
`当前 Project 目录:${boundProjectCwd ?? "未绑定(Projectless)"}`,
|
|
1802
|
+
`当前 Provider:${forked.session.modelProvider ?? "Codex 默认"}`,
|
|
1803
|
+
`当前模型:${forked.session.model ?? "默认"}`,
|
|
1804
|
+
`思考强度:${forked.session.reasoningEffort ?? "自动"}`,
|
|
1805
|
+
`权限类型:${forked.session.permissionMode === "confirm" ? "执行前确认" : "自动执行"}`,
|
|
1678
1806
|
].join("\n"),
|
|
1679
1807
|
);
|
|
1680
1808
|
await this.outbound.sendText(
|
|
@@ -1713,7 +1841,7 @@ export class ProxySessionController {
|
|
|
1713
1841
|
|
|
1714
1842
|
private async loadSession(record: SessionRecord): Promise<LoadedSession> {
|
|
1715
1843
|
const agent = this.ensureAgent(record.agentName);
|
|
1716
|
-
const runtime = this.runtimes.forAgent(
|
|
1844
|
+
const runtime = this.runtimes.forAgent(record.agentName);
|
|
1717
1845
|
const existing = runtime.getSession(record.localSessionId);
|
|
1718
1846
|
if (existing) return { record, runtime, session: existing };
|
|
1719
1847
|
const pending = this.sessionLoads.get(record.localSessionId);
|
|
@@ -1742,6 +1870,7 @@ export class ProxySessionController {
|
|
|
1742
1870
|
agentName: record.agentName,
|
|
1743
1871
|
cwd: record.cwd,
|
|
1744
1872
|
title: record.title,
|
|
1873
|
+
modelProvider: record.modelProvider,
|
|
1745
1874
|
model: record.model,
|
|
1746
1875
|
reasoningEffort: record.reasoningEffort,
|
|
1747
1876
|
permissionMode,
|
|
@@ -1759,6 +1888,7 @@ export class ProxySessionController {
|
|
|
1759
1888
|
agentName: record.agentName,
|
|
1760
1889
|
cwd: record.cwd,
|
|
1761
1890
|
title: record.title,
|
|
1891
|
+
modelProvider: record.modelProvider,
|
|
1762
1892
|
model: record.model,
|
|
1763
1893
|
reasoningEffort: record.reasoningEffort,
|
|
1764
1894
|
permissionMode,
|
|
@@ -1770,6 +1900,7 @@ export class ProxySessionController {
|
|
|
1770
1900
|
agentName: record.agentName,
|
|
1771
1901
|
cwd: record.cwd,
|
|
1772
1902
|
title: record.title,
|
|
1903
|
+
modelProvider: record.modelProvider,
|
|
1773
1904
|
model: record.model,
|
|
1774
1905
|
reasoningEffort: record.reasoningEffort,
|
|
1775
1906
|
permissionMode,
|
|
@@ -1792,6 +1923,7 @@ export class ProxySessionController {
|
|
|
1792
1923
|
runtimeKind: session.runtimeKind,
|
|
1793
1924
|
remoteSessionId: session.remoteSessionId,
|
|
1794
1925
|
title: session.title,
|
|
1926
|
+
modelProvider: session.modelProvider,
|
|
1795
1927
|
model: session.model,
|
|
1796
1928
|
reasoningEffort: session.reasoningEffort,
|
|
1797
1929
|
permissionMode: session.permissionMode,
|
|
@@ -1818,36 +1950,39 @@ export class ProxySessionController {
|
|
|
1818
1950
|
});
|
|
1819
1951
|
}
|
|
1820
1952
|
|
|
1953
|
+
let presentationError: unknown;
|
|
1821
1954
|
try {
|
|
1822
1955
|
await this.outbound.onEvent(event);
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
? "completed"
|
|
1826
|
-
: event.type === "turn_cancelled"
|
|
1827
|
-
? "cancelled"
|
|
1828
|
-
: "failed";
|
|
1829
|
-
const latest = this.store.getSession(event.sessionId);
|
|
1830
|
-
if (latest?.lastTurnId === event.turnId && latest.lastTurnStatus === terminalStatus) {
|
|
1831
|
-
await this.finalizeTurnMessageReactions(event.turnId, terminalStatus);
|
|
1832
|
-
}
|
|
1833
|
-
}
|
|
1834
|
-
} finally {
|
|
1835
|
-
if (event.type === "turn_completed" || event.type === "turn_cancelled" || event.type === "turn_failed") {
|
|
1836
|
-
const latest = this.store.getSession(event.sessionId);
|
|
1837
|
-
const agent = latest ? this.ensureAgent(latest.agentName) : undefined;
|
|
1838
|
-
const activeTurnId = agent ? this.runtimes.forAgent(agent).getSession(event.sessionId)?.activeTurnId : undefined;
|
|
1839
|
-
if (latest?.lastTurnId === event.turnId && !activeTurnId) {
|
|
1840
|
-
this.store.updateSession(event.sessionId, { status: event.type === "turn_failed" ? "failed" : "ready" });
|
|
1841
|
-
}
|
|
1842
|
-
}
|
|
1956
|
+
} catch (error) {
|
|
1957
|
+
presentationError = error;
|
|
1843
1958
|
}
|
|
1959
|
+
|
|
1844
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
|
+
|
|
1845
1975
|
const latest = this.store.getSession(event.sessionId);
|
|
1846
|
-
const
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
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
|
+
}
|
|
1850
1983
|
}
|
|
1984
|
+
|
|
1985
|
+
if (presentationError) throw presentationError;
|
|
1851
1986
|
}
|
|
1852
1987
|
|
|
1853
1988
|
private scheduleNextQueuedPrompt(sessionId: string): Promise<void> {
|
|
@@ -1957,7 +2092,7 @@ export class ProxySessionController {
|
|
|
1957
2092
|
}
|
|
1958
2093
|
|
|
1959
2094
|
private async cancelSession(record: SessionRecord): Promise<void> {
|
|
1960
|
-
const runtime = this.runtimes.forAgent(
|
|
2095
|
+
const runtime = this.runtimes.forAgent(record.agentName);
|
|
1961
2096
|
if (runtime.kind === "codex" && record.remoteSessionId && runtime.interruptRemoteTurn) {
|
|
1962
2097
|
if (runtime.getGoal && runtime.setGoal) {
|
|
1963
2098
|
try {
|
|
@@ -2019,29 +2154,295 @@ export class ProxySessionController {
|
|
|
2019
2154
|
await loaded.runtime.respondToApproval(sessionId, requestId, decision);
|
|
2020
2155
|
}
|
|
2021
2156
|
|
|
2022
|
-
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> {
|
|
2023
2250
|
const record = options.sessionId
|
|
2024
2251
|
? this.requireSession(contextKey, options.sessionId)
|
|
2025
2252
|
: this.requireCurrentSession(contextKey);
|
|
2026
2253
|
const loaded = await this.loadSession(record);
|
|
2254
|
+
await this.assertModelProvider(loaded, modelProvider);
|
|
2027
2255
|
const models = await loaded.runtime.listModels();
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
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,
|
|
2032
2350
|
contextKey,
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
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,
|
|
2036
2396
|
});
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
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}`);
|
|
2043
2414
|
}
|
|
2415
|
+
}
|
|
2044
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();
|
|
2045
2446
|
const selected = models.find((item) => item.id === model);
|
|
2046
2447
|
if (!selected) throw new Error(`未知模型:${model}`);
|
|
2047
2448
|
const currentEffort = loaded.session.reasoningEffort;
|
|
@@ -2059,18 +2460,11 @@ export class ProxySessionController {
|
|
|
2059
2460
|
? `,思考强度已自动调整为 ${nextEffort}`
|
|
2060
2461
|
: "";
|
|
2061
2462
|
if (options.updateMessageId) {
|
|
2062
|
-
await this.
|
|
2063
|
-
|
|
2064
|
-
options.updateMessageId,
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
contextKey,
|
|
2068
|
-
model,
|
|
2069
|
-
currentEffort: nextEffort,
|
|
2070
|
-
options: selected.supportedReasoningEfforts,
|
|
2071
|
-
notice: `模型已切换为 ${cardCode(model)},请选择思考模式。`,
|
|
2072
|
-
}),
|
|
2073
|
-
);
|
|
2463
|
+
await this.openExecutionSettings(contextKey, "model", {
|
|
2464
|
+
sessionId: loaded.record.localSessionId,
|
|
2465
|
+
updateMessageId: options.updateMessageId,
|
|
2466
|
+
notice: `模型已切换为 ${cardCode(model)}${effortMessage},从下一次请求生效。`,
|
|
2467
|
+
});
|
|
2074
2468
|
return;
|
|
2075
2469
|
}
|
|
2076
2470
|
await this.outbound.sendText(contextKey, `模型已切换为 ${model}${effortMessage},从下一次请求生效。`);
|
|
@@ -2239,6 +2633,10 @@ export class ProxySessionController {
|
|
|
2239
2633
|
}
|
|
2240
2634
|
|
|
2241
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
|
+
}
|
|
2242
2640
|
const record = options.sessionId
|
|
2243
2641
|
? this.requireSession(contextKey, options.sessionId)
|
|
2244
2642
|
: this.requireCurrentSession(contextKey);
|
|
@@ -2252,22 +2650,6 @@ export class ProxySessionController {
|
|
|
2252
2650
|
}
|
|
2253
2651
|
const supported = currentModel.supportedReasoningEfforts;
|
|
2254
2652
|
|
|
2255
|
-
if (!effort) {
|
|
2256
|
-
const card = this.cardRenderer.renderReasoningSelector({
|
|
2257
|
-
sessionId: loaded.record.localSessionId,
|
|
2258
|
-
contextKey,
|
|
2259
|
-
model: currentModel.id,
|
|
2260
|
-
currentEffort: loaded.session.reasoningEffort ?? currentModel.defaultReasoningEffort,
|
|
2261
|
-
options: supported,
|
|
2262
|
-
});
|
|
2263
|
-
if (options.updateMessageId) {
|
|
2264
|
-
await this.outbound.updateInteractiveCard(contextKey, options.updateMessageId, card);
|
|
2265
|
-
} else {
|
|
2266
|
-
await this.outbound.sendInteractiveCard(contextKey, card);
|
|
2267
|
-
}
|
|
2268
|
-
return;
|
|
2269
|
-
}
|
|
2270
|
-
|
|
2271
2653
|
if (!supported.some((option) => option.value === effort)) {
|
|
2272
2654
|
const options = supported.map((option) => option.value).join("、") || "无";
|
|
2273
2655
|
throw new Error(`不支持的思考强度:${effort}。支持的强度:${options}`);
|
|
@@ -2275,48 +2657,45 @@ export class ProxySessionController {
|
|
|
2275
2657
|
await loaded.runtime.setReasoningEffort(loaded.record.localSessionId, effort);
|
|
2276
2658
|
this.store.updateRuntimeSession(loaded.record.localSessionId, { reasoningEffort: effort });
|
|
2277
2659
|
if (options.updateMessageId) {
|
|
2278
|
-
await this.
|
|
2279
|
-
|
|
2280
|
-
options.updateMessageId,
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
contextKey,
|
|
2284
|
-
model: currentModel.id,
|
|
2285
|
-
currentEffort: effort,
|
|
2286
|
-
options: supported,
|
|
2287
|
-
notice: `思考模式已切换为 ${cardCode(effort)},从下一次请求生效。`,
|
|
2288
|
-
}),
|
|
2289
|
-
);
|
|
2660
|
+
await this.openExecutionSettings(contextKey, "thinking", {
|
|
2661
|
+
sessionId: loaded.record.localSessionId,
|
|
2662
|
+
updateMessageId: options.updateMessageId,
|
|
2663
|
+
notice: `思考强度已切换为 ${cardCode(effort)},从下一次请求生效。`,
|
|
2664
|
+
});
|
|
2290
2665
|
return;
|
|
2291
2666
|
}
|
|
2292
2667
|
await this.outbound.sendText(contextKey, `思考强度已切换为 ${effort},从下一次请求生效。`);
|
|
2293
2668
|
}
|
|
2294
2669
|
|
|
2295
|
-
private async permissions(
|
|
2296
|
-
|
|
2670
|
+
private async permissions(
|
|
2671
|
+
contextKey: string,
|
|
2672
|
+
mode?: PermissionMode,
|
|
2673
|
+
options: ModelCardOptions = {},
|
|
2674
|
+
): Promise<void> {
|
|
2297
2675
|
if (!mode) {
|
|
2298
|
-
await this.
|
|
2676
|
+
await this.openExecutionSettings(contextKey, "permission", options);
|
|
2299
2677
|
return;
|
|
2300
2678
|
}
|
|
2679
|
+
const record = options.sessionId
|
|
2680
|
+
? this.requireSession(contextKey, options.sessionId)
|
|
2681
|
+
: this.requireCurrentSession(contextKey);
|
|
2301
2682
|
const loaded = await this.loadSession(record);
|
|
2302
2683
|
await loaded.runtime.setPermissionMode(record.localSessionId, mode);
|
|
2684
|
+
loaded.session.permissionMode = mode;
|
|
2303
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
|
+
}
|
|
2304
2696
|
await this.outbound.sendText(contextKey, mode === "auto" ? "已切换为自动执行模式。" : "已切换为执行前确认模式。");
|
|
2305
2697
|
}
|
|
2306
2698
|
|
|
2307
|
-
private async listAgents(contextKey: string): Promise<void> {
|
|
2308
|
-
const current = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent!).defaultAgent;
|
|
2309
|
-
const lines = Object.entries(this.config.agents).map(([name, agent]) => {
|
|
2310
|
-
const selected = name === current;
|
|
2311
|
-
return `- ${selected ? "✅ " : ""}${asInlineCode(name)}:${agent.title}${selected ? "(当前)" : ""}`;
|
|
2312
|
-
});
|
|
2313
|
-
await this.outbound.sendMarkdown(contextKey, [
|
|
2314
|
-
`当前 Agent:${asInlineCode(current)}`,
|
|
2315
|
-
"可用 Agent:",
|
|
2316
|
-
lines.join("\n") || "无",
|
|
2317
|
-
].join("\n"));
|
|
2318
|
-
}
|
|
2319
|
-
|
|
2320
2699
|
private async listSessions(
|
|
2321
2700
|
contextKey: string,
|
|
2322
2701
|
searchTerm?: string,
|
|
@@ -2328,39 +2707,46 @@ export class ProxySessionController {
|
|
|
2328
2707
|
const localSessions = this.store.listSessions(contextKey).filter((session) =>
|
|
2329
2708
|
session.status !== "closed" && this.isCodexSession(session),
|
|
2330
2709
|
);
|
|
2331
|
-
|
|
2332
|
-
|
|
2710
|
+
const remoteSessions: AgentRemoteSessionSummary[] = [];
|
|
2711
|
+
const remoteErrors: string[] = [];
|
|
2333
2712
|
let remoteHasMore = false;
|
|
2334
|
-
|
|
2335
|
-
|
|
2713
|
+
await Promise.all(this.runtimes.entries("codex").map(async ([agentName, runtime]) => {
|
|
2714
|
+
if (!runtime.listRemoteSessions) return;
|
|
2336
2715
|
try {
|
|
2337
2716
|
const page = await runtime.listRemoteSessions({ searchTerm, limit: visibleCount });
|
|
2338
|
-
remoteSessions
|
|
2339
|
-
remoteHasMore
|
|
2717
|
+
remoteSessions.push(...page.sessions.map((session) => ({ agentName, session })));
|
|
2718
|
+
remoteHasMore ||= Boolean(page.nextCursor);
|
|
2340
2719
|
} catch (error) {
|
|
2341
|
-
this.logger.warn({ error, contextKey }, "Failed to list Codex sessions.");
|
|
2342
|
-
|
|
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)}`);
|
|
2343
2722
|
}
|
|
2344
|
-
}
|
|
2723
|
+
}));
|
|
2724
|
+
const remoteHint = remoteErrors.length > 0
|
|
2725
|
+
? `部分 Agent 的任务读取失败:${remoteErrors.join(";")}`
|
|
2726
|
+
: undefined;
|
|
2345
2727
|
|
|
2346
2728
|
const entries = mergeTaskList(localSessions, remoteSessions, context.currentSessionId)
|
|
2347
|
-
.filter((entry) => !normalizedSearch
|
|
2729
|
+
.filter((entry) => !normalizedSearch
|
|
2730
|
+
|| [entry.id, entry.title, entry.cwd, entry.agentName]
|
|
2731
|
+
.some((value) => value.toLowerCase().includes(normalizedSearch)))
|
|
2348
2732
|
.sort((left, right) => Number(right.current) - Number(left.current)
|
|
2349
2733
|
|| Number(right.active) - Number(left.active)
|
|
2350
2734
|
|| right.updatedAt - left.updatedAt);
|
|
2351
2735
|
const activeCount = entries.filter((entry) => entry.active).length;
|
|
2352
2736
|
const visibleEntries = entries.slice(0, visibleCount);
|
|
2353
2737
|
const hasMore = remoteHasMore || entries.length > visibleCount;
|
|
2354
|
-
this.lastSessionListings.set(contextKey, visibleEntries.map((entry) => entry.
|
|
2738
|
+
this.lastSessionListings.set(contextKey, visibleEntries.map((entry) => entry.reference));
|
|
2355
2739
|
const cardEntries = visibleEntries.map((entry, index) => {
|
|
2356
2740
|
const marker = entry.current ? "✅" : entry.active ? "🟢 **活跃**" : "•";
|
|
2357
|
-
const showStop = entry.status === "外部执行中"
|
|
2741
|
+
const showStop = entry.status === "外部执行中"
|
|
2742
|
+
&& entry.reference !== options.forceSwitchTaskId
|
|
2743
|
+
&& entry.id !== options.forceSwitchTaskId;
|
|
2358
2744
|
const actions: TaskListCardAction[] = entry.current ? [] : [{
|
|
2359
2745
|
text: showStop ? "Stop" : "Switch",
|
|
2360
2746
|
type: showStop ? "danger" as const : "default" as const,
|
|
2361
2747
|
value: {
|
|
2362
2748
|
action: showStop ? "session_stop" : "session_switch",
|
|
2363
|
-
sessionId: entry.
|
|
2749
|
+
sessionId: entry.reference,
|
|
2364
2750
|
...(searchTerm ? { searchTerm } : {}),
|
|
2365
2751
|
visibleCount: String(visibleCount),
|
|
2366
2752
|
contextKey,
|
|
@@ -2370,7 +2756,7 @@ export class ProxySessionController {
|
|
|
2370
2756
|
text: "New",
|
|
2371
2757
|
value: {
|
|
2372
2758
|
action: "session_new",
|
|
2373
|
-
sessionId: entry.
|
|
2759
|
+
sessionId: entry.reference,
|
|
2374
2760
|
...(searchTerm ? { searchTerm } : {}),
|
|
2375
2761
|
visibleCount: String(visibleCount),
|
|
2376
2762
|
contextKey,
|
|
@@ -2380,7 +2766,7 @@ export class ProxySessionController {
|
|
|
2380
2766
|
text: "NewGroup",
|
|
2381
2767
|
value: {
|
|
2382
2768
|
action: "session_new_group",
|
|
2383
|
-
sessionId: entry.
|
|
2769
|
+
sessionId: entry.reference,
|
|
2384
2770
|
...(searchTerm ? { searchTerm } : {}),
|
|
2385
2771
|
visibleCount: String(visibleCount),
|
|
2386
2772
|
contextKey,
|
|
@@ -2390,7 +2776,7 @@ export class ProxySessionController {
|
|
|
2390
2776
|
text: "Fork",
|
|
2391
2777
|
value: {
|
|
2392
2778
|
action: "session_fork",
|
|
2393
|
-
sessionId: entry.
|
|
2779
|
+
sessionId: entry.reference,
|
|
2394
2780
|
...(searchTerm ? { searchTerm } : {}),
|
|
2395
2781
|
visibleCount: String(visibleCount),
|
|
2396
2782
|
contextKey,
|
|
@@ -2400,7 +2786,7 @@ export class ProxySessionController {
|
|
|
2400
2786
|
text: "ForkGroup",
|
|
2401
2787
|
value: {
|
|
2402
2788
|
action: "session_fork_group",
|
|
2403
|
-
sessionId: entry.
|
|
2789
|
+
sessionId: entry.reference,
|
|
2404
2790
|
...(searchTerm ? { searchTerm } : {}),
|
|
2405
2791
|
visibleCount: String(visibleCount),
|
|
2406
2792
|
contextKey,
|
|
@@ -2410,14 +2796,14 @@ export class ProxySessionController {
|
|
|
2410
2796
|
text: "Status",
|
|
2411
2797
|
value: {
|
|
2412
2798
|
action: "session_status",
|
|
2413
|
-
sessionId: entry.
|
|
2799
|
+
sessionId: entry.reference,
|
|
2414
2800
|
contextKey,
|
|
2415
2801
|
},
|
|
2416
2802
|
});
|
|
2417
2803
|
return {
|
|
2418
2804
|
lines: [
|
|
2419
2805
|
`**${index + 1}.** ${marker} **${cardText(entry.title)}** ${cardText(entry.id)}`,
|
|
2420
|
-
`${entry.status} · ${entry.updatedLabel} · ${cardText(entry.cwd || "目录未知")}`,
|
|
2806
|
+
`${entry.status} · ${entry.updatedLabel} · ${cardText(entry.agentName)} · ${cardText(entry.cwd || "目录未知")}`,
|
|
2421
2807
|
],
|
|
2422
2808
|
actions,
|
|
2423
2809
|
};
|
|
@@ -2480,6 +2866,46 @@ export class ProxySessionController {
|
|
|
2480
2866
|
});
|
|
2481
2867
|
}
|
|
2482
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
|
+
|
|
2483
2909
|
private async switchSession(contextKey: string, reference?: string): Promise<void> {
|
|
2484
2910
|
this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent!);
|
|
2485
2911
|
const taskId = this.resolveSessionReference(contextKey, reference);
|
|
@@ -2487,9 +2913,9 @@ export class ProxySessionController {
|
|
|
2487
2913
|
if (direct?.status === "closed") {
|
|
2488
2914
|
throw new Error(`找不到任务:${taskId}`);
|
|
2489
2915
|
}
|
|
2490
|
-
const existing = direct ?? this.
|
|
2916
|
+
const existing = direct ?? this.findStoredSessionByReference(taskId);
|
|
2491
2917
|
if (existing) {
|
|
2492
|
-
const runtime = this.runtimes.forAgent(
|
|
2918
|
+
const runtime = this.runtimes.forAgent(existing.agentName);
|
|
2493
2919
|
await this.assertSessionTurnOwnership(existing, runtime);
|
|
2494
2920
|
this.store.attachSessionToContext(contextKey, existing.localSessionId);
|
|
2495
2921
|
this.store.setCurrentSession(contextKey, existing.localSessionId);
|
|
@@ -2498,16 +2924,11 @@ export class ProxySessionController {
|
|
|
2498
2924
|
return;
|
|
2499
2925
|
}
|
|
2500
2926
|
|
|
2501
|
-
const
|
|
2502
|
-
if (!runtime.readRemoteSession) throw new Error("当前 Codex 运行时不支持读取任务。");
|
|
2503
|
-
const remote = await runtime.readRemoteSession(taskId);
|
|
2927
|
+
const { agentName, remote } = await this.resolveRemoteCodexSession(taskId);
|
|
2504
2928
|
if (remote.status === "active" || remote.lastTurnStatus === "inProgress") {
|
|
2505
2929
|
throw new Error(`这个任务正在外部 Codex 中执行,当前不会切换。可使用 /status ${taskId} 查看进度。`);
|
|
2506
2930
|
}
|
|
2507
2931
|
if (!remote.cwd) throw new Error("这个 Codex 任务没有可用的工作目录,暂时无法切换。");
|
|
2508
|
-
const agentEntry = Object.entries(this.config.agents).find(([, agent]) => agent.kind === "codex");
|
|
2509
|
-
if (!agentEntry) throw new Error("未配置 Codex Agent。");
|
|
2510
|
-
const [agentName] = agentEntry;
|
|
2511
2932
|
const localSessionId = createId("sess");
|
|
2512
2933
|
this.store.createSession({
|
|
2513
2934
|
localSessionId,
|
|
@@ -2540,17 +2961,16 @@ export class ProxySessionController {
|
|
|
2540
2961
|
return;
|
|
2541
2962
|
}
|
|
2542
2963
|
|
|
2543
|
-
const existing = this.
|
|
2964
|
+
const existing = this.findStoredSessionByReference(taskId);
|
|
2544
2965
|
if (existing) {
|
|
2545
2966
|
await this.cancelSession({ ...existing, contextKey });
|
|
2546
2967
|
return;
|
|
2547
2968
|
}
|
|
2548
2969
|
|
|
2549
|
-
const runtime = this.
|
|
2970
|
+
const { runtime, remote } = await this.resolveRemoteCodexSession(taskId);
|
|
2550
2971
|
if (!runtime.readRemoteSession || !runtime.interruptRemoteTurn) {
|
|
2551
2972
|
throw new Error("当前 Codex 运行时不支持停止外部任务。");
|
|
2552
2973
|
}
|
|
2553
|
-
const remote = await runtime.readRemoteSession(taskId);
|
|
2554
2974
|
const turnId = remote.status === "active" || remote.lastTurnStatus === "inProgress"
|
|
2555
2975
|
? remote.lastTurnId
|
|
2556
2976
|
: undefined;
|
|
@@ -2625,11 +3045,22 @@ export class ProxySessionController {
|
|
|
2625
3045
|
return undefined;
|
|
2626
3046
|
}
|
|
2627
3047
|
|
|
2628
|
-
private async setDefaultAgent(
|
|
3048
|
+
private async setDefaultAgent(
|
|
3049
|
+
contextKey: string,
|
|
3050
|
+
agentName: string,
|
|
3051
|
+
options: ExecutionSettingsCardOptions = {},
|
|
3052
|
+
): Promise<void> {
|
|
2629
3053
|
this.ensureAgent(agentName);
|
|
2630
3054
|
this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent!);
|
|
2631
3055
|
this.store.setDefaultAgent(contextKey, agentName);
|
|
2632
|
-
|
|
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
|
+
});
|
|
2633
3064
|
}
|
|
2634
3065
|
|
|
2635
3066
|
private async status(
|
|
@@ -2644,7 +3075,7 @@ export class ProxySessionController {
|
|
|
2644
3075
|
const direct = this.store.getSession(targetSessionId);
|
|
2645
3076
|
current = direct
|
|
2646
3077
|
? { ...direct, contextKey }
|
|
2647
|
-
: this.
|
|
3078
|
+
: this.findStoredSessionByReference(targetSessionId, contextKey);
|
|
2648
3079
|
if (!current) {
|
|
2649
3080
|
await this.statusForCodexTask(contextKey, targetSessionId, options);
|
|
2650
3081
|
return;
|
|
@@ -2655,7 +3086,7 @@ export class ProxySessionController {
|
|
|
2655
3086
|
|
|
2656
3087
|
if (current && current.status === "running") {
|
|
2657
3088
|
try {
|
|
2658
|
-
const runtime = this.runtimes.forAgent(
|
|
3089
|
+
const runtime = this.runtimes.forAgent(current.agentName);
|
|
2659
3090
|
if (runtime.getSession(current.localSessionId)) {
|
|
2660
3091
|
await runtime.synchronizeSession(current.localSessionId);
|
|
2661
3092
|
current = this.store.getSession(current.localSessionId) ?? current;
|
|
@@ -2669,7 +3100,7 @@ export class ProxySessionController {
|
|
|
2669
3100
|
let remote: RemoteSessionSummary | undefined;
|
|
2670
3101
|
let goal: RuntimeGoal | undefined;
|
|
2671
3102
|
if (current?.remoteSessionId) {
|
|
2672
|
-
const runtime = this.runtimes.forAgent(
|
|
3103
|
+
const runtime = this.runtimes.forAgent(current.agentName);
|
|
2673
3104
|
if (runtime.readRemoteSession) {
|
|
2674
3105
|
try {
|
|
2675
3106
|
remote = await runtime.readRemoteSession(current.remoteSessionId);
|
|
@@ -2697,7 +3128,7 @@ export class ProxySessionController {
|
|
|
2697
3128
|
taskLines.push("无。直接发送消息即可创建一个未指定项目的 Codex 任务。");
|
|
2698
3129
|
} else {
|
|
2699
3130
|
const agent = this.ensureAgent(current.agentName);
|
|
2700
|
-
const runtimeSession = this.runtimes.forAgent(
|
|
3131
|
+
const runtimeSession = this.runtimes.forAgent(current.agentName).getSession(current.localSessionId);
|
|
2701
3132
|
activeTurnId = runtimeSession?.activeTurnId;
|
|
2702
3133
|
const remoteActive = isRemoteSessionActive(remote);
|
|
2703
3134
|
activeTurnId = remoteActive ? remote?.lastTurnId ?? activeTurnId : activeTurnId;
|
|
@@ -2712,7 +3143,7 @@ export class ProxySessionController {
|
|
|
2712
3143
|
taskLines.push(
|
|
2713
3144
|
`**标题**:${cardCode(current.title ?? "未命名任务")}`,
|
|
2714
3145
|
`**工作目录**:${cardCode(current.cwd)}`,
|
|
2715
|
-
|
|
3146
|
+
`**Provider / 模型 / 思考强度**:${cardCode(current.modelProvider ?? "Codex 默认")} / ${cardCode(current.model ?? "默认")} / ${cardCode(current.reasoningEffort ?? "自动")}`,
|
|
2716
3147
|
`**状态 / 最近结果**:${statusLabel} / ${resultLabel}`,
|
|
2717
3148
|
`**Agent**:${cardCode(agent.title)}`,
|
|
2718
3149
|
`**权限 / 任务范围**:${current.permissionMode === "confirm" ? "执行前确认" : "自动执行"} / ${detectProjectlessWorkspace(current.cwd) ? "未指定项目" : "指定项目"}`,
|
|
@@ -2749,12 +3180,17 @@ export class ProxySessionController {
|
|
|
2749
3180
|
const title = targetSessionId && current
|
|
2750
3181
|
? `Codex 状态:${truncateText((current.title ?? current.remoteSessionId ?? current.localSessionId).replace(/\s+/g, " "), 40)}`
|
|
2751
3182
|
: "Codex 状态";
|
|
2752
|
-
const taskId = current?.remoteSessionId
|
|
3183
|
+
const taskId = current?.remoteSessionId
|
|
3184
|
+
? remoteSessionReference(current.agentName, current.remoteSessionId)
|
|
3185
|
+
: current?.localSessionId;
|
|
2753
3186
|
const isCurrent = Boolean(current && current.localSessionId === context.currentSessionId);
|
|
2754
3187
|
const remoteActive = isRemoteSessionActive(remote);
|
|
2755
3188
|
const botOwnsActiveTurn = Boolean(localCurrent && remote && isBotOwnedActiveTurn(localCurrent, remote));
|
|
2756
3189
|
const active = Boolean(activeTurnId || current?.status === "running" || remoteActive);
|
|
2757
|
-
const forceSwitch = Boolean(taskId &&
|
|
3190
|
+
const forceSwitch = Boolean(taskId && (
|
|
3191
|
+
options.forceSwitchTaskId === taskId
|
|
3192
|
+
|| options.forceSwitchTaskId === current?.remoteSessionId
|
|
3193
|
+
));
|
|
2758
3194
|
const taskActions: TaskListCardAction[] = !taskId ? []
|
|
2759
3195
|
: remoteActive && !botOwnsActiveTurn && !forceSwitch
|
|
2760
3196
|
? [statusCardAction("Stop", "danger", "session_stop", taskId, contextKey)]
|
|
@@ -2774,12 +3210,11 @@ export class ProxySessionController {
|
|
|
2774
3210
|
|
|
2775
3211
|
private async statusForCodexTask(
|
|
2776
3212
|
contextKey: string,
|
|
2777
|
-
|
|
3213
|
+
reference: string,
|
|
2778
3214
|
options: StatusCardOptions = {},
|
|
2779
3215
|
): Promise<void> {
|
|
2780
|
-
const
|
|
2781
|
-
|
|
2782
|
-
const remote = await runtime.readRemoteSession(remoteSessionId);
|
|
3216
|
+
const { agentName, remote } = await this.resolveRemoteCodexSession(reference);
|
|
3217
|
+
const actionReference = remoteSessionReference(agentName, remote.id);
|
|
2783
3218
|
const sections: CardSection[] = [
|
|
2784
3219
|
{
|
|
2785
3220
|
title: "指定任务",
|
|
@@ -2787,6 +3222,7 @@ export class ProxySessionController {
|
|
|
2787
3222
|
`**标题**:${cardCode(remote.title ?? remote.preview ?? "未命名任务")}`,
|
|
2788
3223
|
`**工作目录**:${cardCode(remote.cwd || "目录未知")}`,
|
|
2789
3224
|
`**状态 / 当前任务**:${remoteSessionDetailStatus(remote)} / 未切换`,
|
|
3225
|
+
`**Agent**:${cardCode(agentName)}`,
|
|
2790
3226
|
`**Codex 任务 ID**:${cardCode(remote.id)}`,
|
|
2791
3227
|
`**最近回合**:${cardCode(remote.lastTurnId ?? "无")} ${remoteTurnStatusLabel(remote.lastTurnStatus)}`,
|
|
2792
3228
|
`**创建时间 / 最近活动**:${formatRemoteTime(remote.createdAt)} / ${formatRemoteTime(latestRemoteTimestamp(remote.recencyAt, remote.updatedAt))}`,
|
|
@@ -2806,14 +3242,16 @@ export class ProxySessionController {
|
|
|
2806
3242
|
},
|
|
2807
3243
|
];
|
|
2808
3244
|
const title = `Codex 状态:${truncateText((remote.title ?? remote.preview ?? remote.id).replace(/\s+/g, " "), 40)}`;
|
|
2809
|
-
const showStop = isRemoteSessionActive(remote)
|
|
3245
|
+
const showStop = isRemoteSessionActive(remote)
|
|
3246
|
+
&& options.forceSwitchTaskId !== actionReference
|
|
3247
|
+
&& options.forceSwitchTaskId !== remote.id;
|
|
2810
3248
|
const actions = [
|
|
2811
|
-
statusRefreshAction(
|
|
3249
|
+
statusRefreshAction(actionReference, contextKey),
|
|
2812
3250
|
statusCardAction(
|
|
2813
3251
|
showStop ? "Stop" : "Switch",
|
|
2814
3252
|
showStop ? "danger" : "default",
|
|
2815
3253
|
showStop ? "session_stop" : "session_switch",
|
|
2816
|
-
|
|
3254
|
+
actionReference,
|
|
2817
3255
|
contextKey,
|
|
2818
3256
|
),
|
|
2819
3257
|
];
|
|
@@ -2827,6 +3265,9 @@ export class ProxySessionController {
|
|
|
2827
3265
|
{
|
|
2828
3266
|
lines: [
|
|
2829
3267
|
"直接发送消息即可继续当前任务;执行中发送的新消息会追加到本次任务。",
|
|
3268
|
+
"> 命令前缀示例:/sess 等同于 /sessions。",
|
|
3269
|
+
"> 命令缩写示例:/fg 等同于 /forkgroup。",
|
|
3270
|
+
"前缀或缩写匹配多个命令时,需要输入更长的形式。",
|
|
2830
3271
|
"**[参数]** 可选 **<参数>** 必填",
|
|
2831
3272
|
],
|
|
2832
3273
|
},
|
|
@@ -2834,7 +3275,7 @@ export class ProxySessionController {
|
|
|
2834
3275
|
title: "任务管理",
|
|
2835
3276
|
lines: [
|
|
2836
3277
|
"**/new [title] [--dir <cwd> | --nodir]** 使用默认 Agent 创建任务;--nodir 强制创建 Projectless 任务",
|
|
2837
|
-
"**/newgroup [title]**
|
|
3278
|
+
"**/newgroup [title] [--dir <cwd> | --nodir]** 创建飞书群和新任务;参数与 /new 相同",
|
|
2838
3279
|
"**/forkgroup [title]** 从当前任务最新已完成轮次创建分支并绑定到新群",
|
|
2839
3280
|
"**/fork [序号或任务 ID]** 从当前或指定任务创建分支;运行中使用最近已完成轮次",
|
|
2840
3281
|
"**/title <新标题>** 修改当前任务的标题",
|
|
@@ -2849,22 +3290,23 @@ export class ProxySessionController {
|
|
|
2849
3290
|
"**/stop** 停止当前执行",
|
|
2850
3291
|
"**/queue <prompt>**(兼容 **/nosteer**) 不追加到当前轮次,按顺序排队为后续轮次",
|
|
2851
3292
|
"**/goal [目标]** 查看或创建长任务 Goal;支持 pause、resume、edit、clear",
|
|
2852
|
-
"**/
|
|
2853
|
-
"**/
|
|
2854
|
-
"**/
|
|
3293
|
+
"**/provider** 选择 AI 服务提供商;没有其他候选时仅显示当前 Provider",
|
|
3294
|
+
"**/model** 选择当前任务使用的模型",
|
|
3295
|
+
"**/thinking** 设置模型的思考强度",
|
|
3296
|
+
"**/permissions** 设置执行工具前是否需要确认",
|
|
2855
3297
|
],
|
|
2856
3298
|
},
|
|
2857
3299
|
{
|
|
2858
3300
|
title: "Agent",
|
|
2859
3301
|
lines: [
|
|
2860
|
-
"**/agent [name]**
|
|
3302
|
+
"**/agent [name]** 选择新任务使用的默认 Agent;仅配置一个时直接显示当前 Agent",
|
|
2861
3303
|
],
|
|
2862
3304
|
},
|
|
2863
3305
|
{
|
|
2864
3306
|
title: "系统",
|
|
2865
3307
|
lines: [
|
|
2866
3308
|
"**/status [序号或任务 ID]** 查看当前或指定任务的详细状态",
|
|
2867
|
-
"**/restart**
|
|
3309
|
+
"**/restart [--force]** 默认安全重启;--force 立即重启",
|
|
2868
3310
|
"**/help** 显示本帮助",
|
|
2869
3311
|
],
|
|
2870
3312
|
},
|
|
@@ -2898,6 +3340,17 @@ export class ProxySessionController {
|
|
|
2898
3340
|
: current.cwd;
|
|
2899
3341
|
}
|
|
2900
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
|
+
|
|
2901
3354
|
private currentProjectCwd(contextKey: string): string | undefined {
|
|
2902
3355
|
const current = this.currentSession(contextKey);
|
|
2903
3356
|
return current && !detectProjectlessWorkspace(current.cwd) ? current.cwd : undefined;
|
|
@@ -2963,7 +3416,9 @@ function statusRefreshAction(
|
|
|
2963
3416
|
}
|
|
2964
3417
|
|
|
2965
3418
|
interface UnifiedTaskListEntry {
|
|
3419
|
+
reference: string;
|
|
2966
3420
|
id: string;
|
|
3421
|
+
agentName: string;
|
|
2967
3422
|
title: string;
|
|
2968
3423
|
cwd: string;
|
|
2969
3424
|
status: string;
|
|
@@ -2975,17 +3430,17 @@ interface UnifiedTaskListEntry {
|
|
|
2975
3430
|
|
|
2976
3431
|
function mergeTaskList(
|
|
2977
3432
|
localSessions: SessionRecord[],
|
|
2978
|
-
remoteSessions:
|
|
3433
|
+
remoteSessions: AgentRemoteSessionSummary[],
|
|
2979
3434
|
currentLocalSessionId?: string,
|
|
2980
3435
|
): UnifiedTaskListEntry[] {
|
|
2981
3436
|
const localByRemoteId = new Map(
|
|
2982
3437
|
localSessions
|
|
2983
3438
|
.filter((session) => session.runtimeKind === "codex" && session.remoteSessionId)
|
|
2984
|
-
.map((session) => [session.remoteSessionId
|
|
3439
|
+
.map((session) => [agentRemoteKey(session.agentName, session.remoteSessionId!), session]),
|
|
2985
3440
|
);
|
|
2986
3441
|
const representedLocalIds = new Set<string>();
|
|
2987
|
-
const entries = remoteSessions.map((remote): UnifiedTaskListEntry => {
|
|
2988
|
-
const local = localByRemoteId.get(remote.id);
|
|
3442
|
+
const entries = remoteSessions.map(({ agentName, session: remote }): UnifiedTaskListEntry => {
|
|
3443
|
+
const local = localByRemoteId.get(agentRemoteKey(agentName, remote.id));
|
|
2989
3444
|
if (local) representedLocalIds.add(local.localSessionId);
|
|
2990
3445
|
const active = remote.status === "active" || remote.lastTurnStatus === "inProgress";
|
|
2991
3446
|
const status = remote.status === "active" || remote.lastTurnStatus === "inProgress"
|
|
@@ -2993,7 +3448,9 @@ function mergeTaskList(
|
|
|
2993
3448
|
: remoteSessionStatusLabel(remote.status);
|
|
2994
3449
|
const recencyAt = remote.recencyAt ?? remote.updatedAt;
|
|
2995
3450
|
return {
|
|
3451
|
+
reference: remoteSessionReference(agentName, remote.id),
|
|
2996
3452
|
id: remote.id,
|
|
3453
|
+
agentName,
|
|
2997
3454
|
title: remote.title ?? remote.preview ?? local?.title ?? "未命名任务",
|
|
2998
3455
|
cwd: remote.cwd || local?.cwd || "",
|
|
2999
3456
|
status,
|
|
@@ -3008,7 +3465,11 @@ function mergeTaskList(
|
|
|
3008
3465
|
if (representedLocalIds.has(local.localSessionId)) continue;
|
|
3009
3466
|
const updatedAt = Date.parse(local.updatedAt);
|
|
3010
3467
|
entries.push({
|
|
3468
|
+
reference: local.remoteSessionId
|
|
3469
|
+
? remoteSessionReference(local.agentName, local.remoteSessionId)
|
|
3470
|
+
: local.localSessionId,
|
|
3011
3471
|
id: local.remoteSessionId ?? local.localSessionId,
|
|
3472
|
+
agentName: local.agentName,
|
|
3012
3473
|
title: local.title ?? "未命名任务",
|
|
3013
3474
|
cwd: local.cwd,
|
|
3014
3475
|
status: sessionStatusLabel(local.status),
|
|
@@ -3021,6 +3482,32 @@ function mergeTaskList(
|
|
|
3021
3482
|
return entries;
|
|
3022
3483
|
}
|
|
3023
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
|
+
|
|
3024
3511
|
function isMissingRolloutError(error: unknown): boolean {
|
|
3025
3512
|
const message = error instanceof Error ? error.message : String(error);
|
|
3026
3513
|
return /no rollout found|rollout[^\n]*(?:not found|missing)|thread\/resume failed/i.test(message);
|
|
@@ -3202,6 +3689,21 @@ function cardCode(value: string): string {
|
|
|
3202
3689
|
return `\`${value.replaceAll("`", "'")}\``;
|
|
3203
3690
|
}
|
|
3204
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
|
+
|
|
3205
3707
|
function remoteSessionStatusLabel(status: RemoteSessionSummary["status"]): string {
|
|
3206
3708
|
const labels: Record<RemoteSessionSummary["status"], string> = {
|
|
3207
3709
|
active: "执行中",
|
|
@@ -3237,6 +3739,10 @@ function mergeRemoteTaskStatus(record: SessionRecord, remote?: RemoteSessionSumm
|
|
|
3237
3739
|
...record,
|
|
3238
3740
|
title: remote.title ?? record.title,
|
|
3239
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,
|
|
3240
3746
|
status: isRemoteSessionActive(remote)
|
|
3241
3747
|
? "running"
|
|
3242
3748
|
: remote.lastTurnStatus
|
|
@@ -3293,9 +3799,7 @@ function isBotOwnedActiveTurn(record: SessionRecord, remote: RemoteSessionSummar
|
|
|
3293
3799
|
function isQueueIndependentCommand(command: Command): boolean {
|
|
3294
3800
|
if (["stop", "status", "restart", "help", "sessions", "goal", "nosteer"].includes(command.type)) return true;
|
|
3295
3801
|
if (command.type === "agent") return command.agent === undefined;
|
|
3296
|
-
if (
|
|
3297
|
-
if (command.type === "thinking") return command.effort === undefined;
|
|
3298
|
-
if (command.type === "permissions") return command.mode === undefined;
|
|
3802
|
+
if (["model", "provider", "thinking", "permissions"].includes(command.type)) return true;
|
|
3299
3803
|
return false;
|
|
3300
3804
|
}
|
|
3301
3805
|
|
|
@@ -3305,7 +3809,7 @@ function parseSessionVisibleCount(value: unknown): number {
|
|
|
3305
3809
|
}
|
|
3306
3810
|
|
|
3307
3811
|
function isPromptCommand(command: Command): boolean {
|
|
3308
|
-
return command.type === "
|
|
3812
|
+
return command.type === "prompt" || command.type === "nosteer";
|
|
3309
3813
|
}
|
|
3310
3814
|
|
|
3311
3815
|
function runtimePrompt(text: string, localImagePaths?: string[]): RuntimePrompt {
|
|
@@ -3326,8 +3830,8 @@ function formatNewGroupName(
|
|
|
3326
3830
|
includeTimestamp: boolean,
|
|
3327
3831
|
): string {
|
|
3328
3832
|
const prefix = `[${agentName}] `;
|
|
3329
|
-
const
|
|
3330
|
-
const titlePrefix = `${prefix}${
|
|
3833
|
+
const projectPrefix = projectCwd ? `${formatGroupProjectDirectory(projectCwd)} ` : "";
|
|
3834
|
+
const titlePrefix = `${prefix}${projectPrefix}`;
|
|
3331
3835
|
const timestampSuffix = includeTimestamp ? ` (${formatGroupDateSuffix(date)})` : "";
|
|
3332
3836
|
const availableTitleLength = FEISHU_GROUP_NAME_MAX_LENGTH
|
|
3333
3837
|
- Array.from(titlePrefix).length
|
|
@@ -3339,8 +3843,8 @@ function formatNewGroupName(
|
|
|
3339
3843
|
const FEISHU_GROUP_NAME_MAX_LENGTH = 60;
|
|
3340
3844
|
const GROUP_PROJECT_DIRECTORY_MAX_LENGTH = 15;
|
|
3341
3845
|
|
|
3342
|
-
function formatGroupProjectDirectory(projectCwd: string
|
|
3343
|
-
const value =
|
|
3846
|
+
function formatGroupProjectDirectory(projectCwd: string): string {
|
|
3847
|
+
const value = abbreviateHomeDirectory(projectCwd);
|
|
3344
3848
|
if (Array.from(value).length <= GROUP_PROJECT_DIRECTORY_MAX_LENGTH) return `[${value}]`;
|
|
3345
3849
|
|
|
3346
3850
|
const levels = value
|