@keyou007/agent-bot 0.1.10 → 0.1.12
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/.env.example +1 -1
- package/CHANGELOG.md +25 -0
- package/README.md +55 -43
- package/README.zh.md +55 -43
- package/assets/agent-bot-logo.png +0 -0
- package/config.example.yaml +11 -1
- package/dist/acp/AcpSessionManager.js +78 -46
- package/dist/acp/AcpSessionManager.js.map +1 -1
- package/dist/cli/FeishuAppConfiguration.js +1 -1
- package/dist/cli/FeishuAppConfiguration.js.map +1 -1
- package/dist/cli/FeishuAppRegistration.js +2 -2
- package/dist/cli/FeishuAppRegistration.js.map +1 -1
- package/dist/cli/Initializer.js +1 -1
- package/dist/cli/Initializer.js.map +1 -1
- package/dist/cli/ServerStarter.js +7 -2
- package/dist/cli/ServerStarter.js.map +1 -1
- package/dist/cli/controlProtocol.js.map +1 -1
- package/dist/cli/help.js +20 -16
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/taskGroupOptions.js +95 -0
- package/dist/cli/taskGroupOptions.js.map +1 -0
- package/dist/cli.js +74 -2
- 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 +112 -11
- 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/config/loadConfig.js +11 -1
- package/dist/config/loadConfig.js.map +1 -1
- package/dist/config/schema.js +6 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/console/ConsoleConnector.js +1 -1
- package/dist/console/ConsoleConnector.js.map +1 -1
- package/dist/console/ConsoleTurnPresenter.js +15 -8
- package/dist/console/ConsoleTurnPresenter.js.map +1 -1
- package/dist/deprecated-cli.js +5 -0
- package/dist/deprecated-cli.js.map +1 -0
- package/dist/feishu/CardRenderer.js +350 -15
- 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 +17 -8
- package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
- package/dist/feishu/transport.js +1 -1
- package/dist/feishu/transport.js.map +1 -1
- package/dist/index.js +38 -19
- package/dist/index.js.map +1 -1
- package/dist/presentation/OutboundRouter.js +2 -2
- package/dist/presentation/OutboundRouter.js.map +1 -1
- package/dist/presentation/TurnStateReducer.js +2 -1
- package/dist/presentation/TurnStateReducer.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +665 -225
- 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/dist/supervision/systemEnvironment.js +129 -0
- package/dist/supervision/systemEnvironment.js.map +1 -0
- package/dist/supervisor.js +13 -1
- package/dist/supervisor.js.map +1 -1
- package/docs/technical-reference.md +35 -21
- package/docs/technical-reference.zh.md +35 -21
- package/npm-shrinkwrap.json +4 -3
- package/package.json +4 -2
- package/skills/agent-bot/SKILL.md +73 -30
- package/src/acp/AcpSessionManager.ts +73 -54
- package/src/cli/FeishuAppConfiguration.ts +2 -2
- package/src/cli/FeishuAppRegistration.ts +4 -4
- package/src/cli/Initializer.ts +2 -2
- package/src/cli/ServerStarter.ts +8 -3
- package/src/cli/controlProtocol.ts +21 -1
- package/src/cli/help.ts +20 -16
- package/src/cli/taskGroupOptions.ts +149 -0
- package/src/cli.ts +98 -4
- 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 +137 -15
- package/src/commands/CommandRouter.ts +71 -49
- package/src/commands/commandTypes.ts +6 -11
- package/src/config/loadConfig.ts +11 -1
- package/src/config/schema.ts +7 -1
- package/src/console/ConsoleConnector.ts +1 -1
- package/src/console/ConsoleTurnPresenter.ts +21 -8
- package/src/deprecated-cli.ts +9 -0
- package/src/feishu/CardRenderer.ts +423 -15
- package/src/feishu/FeishuMessageClient.ts +66 -3
- package/src/feishu/FeishuTurnPresenter.ts +22 -3
- package/src/feishu/transport.ts +1 -1
- package/src/index.ts +54 -24
- package/src/presentation/OutboundRouter.ts +15 -3
- package/src/presentation/TurnStateReducer.ts +2 -0
- package/src/presentation/turnViewTypes.ts +1 -0
- package/src/proxy/ProxySessionController.ts +870 -228
- 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
- package/src/supervision/systemEnvironment.ts +174 -0
- package/src/supervisor.ts +12 -1
|
@@ -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;
|
|
@@ -124,13 +148,26 @@ interface ResolvedThreadForkAnchor {
|
|
|
124
148
|
}
|
|
125
149
|
|
|
126
150
|
interface CreatedFeishuGroupContext {
|
|
151
|
+
chatId: string;
|
|
127
152
|
contextKey: string;
|
|
128
153
|
name: string;
|
|
129
154
|
}
|
|
130
155
|
|
|
156
|
+
interface CreatedFeishuTaskGroup {
|
|
157
|
+
group: CreatedFeishuGroupContext;
|
|
158
|
+
task: SessionRecord;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export interface ControlTaskGroupResult {
|
|
162
|
+
sourceLocalSessionId: string;
|
|
163
|
+
sourceTurnId?: string;
|
|
164
|
+
group: CreatedFeishuGroupContext;
|
|
165
|
+
task: SessionRecord;
|
|
166
|
+
}
|
|
167
|
+
|
|
131
168
|
export interface ProxyLifecycle {
|
|
132
169
|
supervised?: boolean;
|
|
133
|
-
restart(contextKey: string): Promise<void>;
|
|
170
|
+
restart(contextKey: string, force: boolean): Promise<void>;
|
|
134
171
|
cancelSafeRestart?(scheduleId: number): Promise<boolean>;
|
|
135
172
|
}
|
|
136
173
|
|
|
@@ -168,9 +205,9 @@ export class ProxySessionController {
|
|
|
168
205
|
"Marked interrupted ACP sessions as failed after process restart.",
|
|
169
206
|
);
|
|
170
207
|
}
|
|
171
|
-
for (const
|
|
208
|
+
for (const [, runtime] of this.runtimes.entries()) {
|
|
172
209
|
this.unsubscribe.push(
|
|
173
|
-
|
|
210
|
+
runtime.onEvent((event) => {
|
|
174
211
|
void this.handleRuntimeEvent(event).catch((error: unknown) => {
|
|
175
212
|
this.logger.warn({ error, event }, "Failed to present runtime event.");
|
|
176
213
|
});
|
|
@@ -236,7 +273,7 @@ export class ProxySessionController {
|
|
|
236
273
|
let command: Command;
|
|
237
274
|
try {
|
|
238
275
|
command = imageCount > 0 && !message.text.trimStart().startsWith("/")
|
|
239
|
-
? { type: "prompt", text: message.text.trim() || "
|
|
276
|
+
? { type: "prompt", text: message.text.trim() || "请查看这张图片" }
|
|
240
277
|
: this.router.parse(message.text);
|
|
241
278
|
} catch (error) {
|
|
242
279
|
await this.finalizeStandaloneMessageReaction(message.messageId, "failed");
|
|
@@ -327,7 +364,7 @@ export class ProxySessionController {
|
|
|
327
364
|
contextKey,
|
|
328
365
|
String(scopedAction.value.turnId ?? ""),
|
|
329
366
|
requestedPage === "latest" ? "latest" : Number.isFinite(numericPage) ? numericPage : 0,
|
|
330
|
-
scopedAction.messageId,
|
|
367
|
+
requiredCardMessageId(scopedAction.messageId),
|
|
331
368
|
);
|
|
332
369
|
} else if (kind === "turn_cancel") {
|
|
333
370
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
@@ -378,6 +415,37 @@ export class ProxySessionController {
|
|
|
378
415
|
await this.status(contextKey, String(scopedAction.value.sessionId ?? ""));
|
|
379
416
|
} else if (kind === "session_status_refresh") {
|
|
380
417
|
await this.refreshStatusCardFromAction(scopedAction);
|
|
418
|
+
} else if (kind === "settings_tab_open") {
|
|
419
|
+
await this.openExecutionSettings(contextKey, executionSettingsTabValue(scopedAction.value.tab), {
|
|
420
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
421
|
+
updateMessageId: scopedAction.messageId,
|
|
422
|
+
});
|
|
423
|
+
} else if (kind === "settings_agent_select") {
|
|
424
|
+
await this.setDefaultAgent(contextKey, String(scopedAction.value.agent ?? ""), {
|
|
425
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
426
|
+
updateMessageId: scopedAction.messageId,
|
|
427
|
+
});
|
|
428
|
+
} else if (kind === "settings_provider_select") {
|
|
429
|
+
await this.selectProvider(contextKey, String(scopedAction.value.provider ?? ""), {
|
|
430
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
431
|
+
updateMessageId: scopedAction.messageId,
|
|
432
|
+
});
|
|
433
|
+
} else if (kind === "settings_model_select") {
|
|
434
|
+
await this.model(contextKey, String(scopedAction.value.model ?? ""), {
|
|
435
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
436
|
+
updateMessageId: scopedAction.messageId,
|
|
437
|
+
});
|
|
438
|
+
} else if (kind === "settings_thinking_select") {
|
|
439
|
+
await this.thinking(contextKey, String(scopedAction.value.effort ?? ""), {
|
|
440
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
441
|
+
updateMessageId: scopedAction.messageId,
|
|
442
|
+
expectedModel: String(scopedAction.value.model ?? ""),
|
|
443
|
+
});
|
|
444
|
+
} else if (kind === "settings_permission_select") {
|
|
445
|
+
await this.permissions(contextKey, permissionModeValue(scopedAction.value.permissionMode), {
|
|
446
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
447
|
+
updateMessageId: scopedAction.messageId,
|
|
448
|
+
});
|
|
381
449
|
} else if (kind === "model_select") {
|
|
382
450
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
383
451
|
const model = String(scopedAction.value.model ?? "");
|
|
@@ -400,6 +468,52 @@ export class ProxySessionController {
|
|
|
400
468
|
updateMessageId: scopedAction.messageId,
|
|
401
469
|
expectedModel: model,
|
|
402
470
|
});
|
|
471
|
+
} else if (kind === "provider_open") {
|
|
472
|
+
await this.openExecutionSettings(contextKey, "provider", {
|
|
473
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
474
|
+
updateMessageId: scopedAction.messageId,
|
|
475
|
+
});
|
|
476
|
+
} else if (kind === "provider_select" || kind === "provider_model_open") {
|
|
477
|
+
await this.openProviderModelSelector(
|
|
478
|
+
contextKey,
|
|
479
|
+
String(scopedAction.value.provider ?? ""),
|
|
480
|
+
permissionModeValue(scopedAction.value.permissionMode),
|
|
481
|
+
String(scopedAction.value.sessionId ?? ""),
|
|
482
|
+
requiredCardMessageId(scopedAction.messageId),
|
|
483
|
+
);
|
|
484
|
+
} else if (kind === "provider_model_select" || kind === "provider_reasoning_open") {
|
|
485
|
+
await this.openProviderReasoningSelector(
|
|
486
|
+
contextKey,
|
|
487
|
+
String(scopedAction.value.provider ?? ""),
|
|
488
|
+
String(scopedAction.value.model ?? ""),
|
|
489
|
+
permissionModeValue(scopedAction.value.permissionMode),
|
|
490
|
+
String(scopedAction.value.sessionId ?? ""),
|
|
491
|
+
requiredCardMessageId(scopedAction.messageId),
|
|
492
|
+
);
|
|
493
|
+
} else if (kind === "provider_reasoning_select") {
|
|
494
|
+
await this.openProviderPermissionSelector(
|
|
495
|
+
contextKey,
|
|
496
|
+
String(scopedAction.value.provider ?? ""),
|
|
497
|
+
String(scopedAction.value.model ?? ""),
|
|
498
|
+
String(scopedAction.value.effort ?? ""),
|
|
499
|
+
permissionModeValue(scopedAction.value.permissionMode),
|
|
500
|
+
String(scopedAction.value.sessionId ?? ""),
|
|
501
|
+
requiredCardMessageId(scopedAction.messageId),
|
|
502
|
+
);
|
|
503
|
+
} else if (kind === "provider_permission_select") {
|
|
504
|
+
await this.applyProviderSettings(
|
|
505
|
+
contextKey,
|
|
506
|
+
{
|
|
507
|
+
provider: String(scopedAction.value.provider ?? ""),
|
|
508
|
+
model: String(scopedAction.value.model ?? ""),
|
|
509
|
+
effort: String(scopedAction.value.effort ?? ""),
|
|
510
|
+
mode: permissionModeValue(scopedAction.value.permissionMode),
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
sessionId: String(scopedAction.value.sessionId ?? ""),
|
|
514
|
+
updateMessageId: scopedAction.messageId,
|
|
515
|
+
},
|
|
516
|
+
);
|
|
403
517
|
} else if (kind === "approval") {
|
|
404
518
|
await this.resolveApproval(scopedAction);
|
|
405
519
|
}
|
|
@@ -445,7 +559,7 @@ export class ProxySessionController {
|
|
|
445
559
|
if (!record) throw new Error(`Task not found: ${localSessionId}`);
|
|
446
560
|
let remote: RemoteSessionSummary | undefined;
|
|
447
561
|
if (record.remoteSessionId) {
|
|
448
|
-
const runtime = this.runtimes.forAgent(
|
|
562
|
+
const runtime = this.runtimes.forAgent(record.agentName);
|
|
449
563
|
if (runtime.readRemoteSession) {
|
|
450
564
|
try {
|
|
451
565
|
remote = await runtime.readRemoteSession(record.remoteSessionId);
|
|
@@ -484,7 +598,7 @@ export class ProxySessionController {
|
|
|
484
598
|
if (!promptText) throw new Error("The Prompt cannot be empty.");
|
|
485
599
|
const record = this.store.getSession(localSessionId);
|
|
486
600
|
if (!record || record.status === "closed") throw new Error(`Task not found: ${localSessionId}`);
|
|
487
|
-
const runtime = this.runtimes.forAgent(
|
|
601
|
+
const runtime = this.runtimes.forAgent(record.agentName);
|
|
488
602
|
const activeTurnId = runtime.getSession(localSessionId)?.activeTurnId;
|
|
489
603
|
const routedContextKey = this.outbound.getSessionContextKey(localSessionId);
|
|
490
604
|
const responseContextKey = (activeTurnId ? this.store.getTurnContextKey(activeTurnId) ?? routedContextKey : undefined)
|
|
@@ -514,6 +628,7 @@ export class ProxySessionController {
|
|
|
514
628
|
responseContextKey,
|
|
515
629
|
scopedRecord.title,
|
|
516
630
|
scopedRecord.cwd,
|
|
631
|
+
this.agentLabel(scopedRecord.agentName),
|
|
517
632
|
);
|
|
518
633
|
const promptMessageId = await this.outbound.withReplyTarget(
|
|
519
634
|
responseContextKey,
|
|
@@ -540,13 +655,89 @@ export class ProxySessionController {
|
|
|
540
655
|
return `The Prompt was posted to the original chat and submitted to the task: ${record.title ?? record.remoteSessionId ?? record.localSessionId}`;
|
|
541
656
|
}
|
|
542
657
|
|
|
658
|
+
async controlCreateTaskGroup(
|
|
659
|
+
localSessionId: string,
|
|
660
|
+
requestedTitle: string | undefined,
|
|
661
|
+
userOpenId: string | undefined,
|
|
662
|
+
requestedProjectCwd?: string,
|
|
663
|
+
forceProjectless = false,
|
|
664
|
+
requestedAgentName?: string,
|
|
665
|
+
): Promise<ControlTaskGroupResult> {
|
|
666
|
+
const source = this.store.getSession(localSessionId);
|
|
667
|
+
if (!source || source.status === "closed") throw new Error(`Task not found: ${localSessionId}`);
|
|
668
|
+
const agentName = requestedAgentName?.trim() || source.agentName;
|
|
669
|
+
const agent = this.ensureAgent(agentName);
|
|
670
|
+
if (forceProjectless && agent.kind !== "app-server") {
|
|
671
|
+
throw new Error("task newgroup --nodir is only available for Codex agents.");
|
|
672
|
+
}
|
|
673
|
+
const sourceContextKey = this.outbound.getSessionContextKey(localSessionId) ?? source.contextKey;
|
|
674
|
+
const replyTarget = this.outbound.getSessionReplyTarget(localSessionId);
|
|
675
|
+
const boundProjectCwd = forceProjectless
|
|
676
|
+
? undefined
|
|
677
|
+
: requestedProjectCwd === undefined
|
|
678
|
+
? detectProjectlessWorkspace(source.cwd) ? undefined : source.cwd
|
|
679
|
+
: resolveUserPath(requestedProjectCwd);
|
|
680
|
+
const created = await this.outbound.withReplyTarget(sourceContextKey, replyTarget, () =>
|
|
681
|
+
this.createFeishuGroupWithTask(
|
|
682
|
+
sourceContextKey,
|
|
683
|
+
agentName,
|
|
684
|
+
requestedTitle,
|
|
685
|
+
userOpenId,
|
|
686
|
+
boundProjectCwd,
|
|
687
|
+
source.agentName === agentName
|
|
688
|
+
? {
|
|
689
|
+
modelProvider: source.modelProvider,
|
|
690
|
+
model: source.model,
|
|
691
|
+
reasoningEffort: source.reasoningEffort,
|
|
692
|
+
permissionMode: source.permissionMode,
|
|
693
|
+
}
|
|
694
|
+
: {},
|
|
695
|
+
));
|
|
696
|
+
return {
|
|
697
|
+
sourceLocalSessionId: source.localSessionId,
|
|
698
|
+
group: created.group,
|
|
699
|
+
task: created.task,
|
|
700
|
+
};
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
async controlForkTaskGroup(
|
|
704
|
+
localSessionId: string,
|
|
705
|
+
requestedTitle: string | undefined,
|
|
706
|
+
userOpenId: string | undefined,
|
|
707
|
+
): Promise<ControlTaskGroupResult> {
|
|
708
|
+
const source = this.store.getSession(localSessionId);
|
|
709
|
+
if (!source || source.status === "closed") throw new Error(`Task not found: ${localSessionId}`);
|
|
710
|
+
const sourceContextKey = this.outbound.getSessionContextKey(localSessionId) ?? source.contextKey;
|
|
711
|
+
const replyTarget = this.outbound.getSessionReplyTarget(localSessionId);
|
|
712
|
+
const plan = await this.prepareForkSession(sourceContextKey, localSessionId, requestedTitle);
|
|
713
|
+
const prepared: ForkGroupSessionPlan = {
|
|
714
|
+
plan,
|
|
715
|
+
sourceDescription: plan.forkedFromHistoricalTurn
|
|
716
|
+
? "当前任务最近已完成轮次"
|
|
717
|
+
: "当前任务最新轮次",
|
|
718
|
+
};
|
|
719
|
+
const forked = await this.outbound.withReplyTarget(sourceContextKey, replyTarget, () =>
|
|
720
|
+
this.forkPreparedSessionToFeishuGroup(
|
|
721
|
+
sourceContextKey,
|
|
722
|
+
prepared,
|
|
723
|
+
userOpenId ?? "",
|
|
724
|
+
"当前任务",
|
|
725
|
+
));
|
|
726
|
+
return {
|
|
727
|
+
sourceLocalSessionId: source.localSessionId,
|
|
728
|
+
sourceTurnId: plan.lastTurnId,
|
|
729
|
+
group: forked.group,
|
|
730
|
+
task: forked.task,
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
|
|
543
734
|
private cardActionContextKey(action: CardAction): string {
|
|
544
735
|
const explicit = typeof action.value.contextKey === "string" ? action.value.contextKey : undefined;
|
|
545
736
|
if (explicit && baseChatContextKey(explicit) === baseChatContextKey(action.contextKey)) return explicit;
|
|
546
737
|
|
|
547
738
|
const sessionReference = typeof action.value.sessionId === "string" ? action.value.sessionId : undefined;
|
|
548
739
|
const session = sessionReference
|
|
549
|
-
? this.store.getSession(sessionReference) ?? this.
|
|
740
|
+
? this.store.getSession(sessionReference) ?? this.findStoredSessionByReference(sessionReference)
|
|
550
741
|
: undefined;
|
|
551
742
|
if (session && baseChatContextKey(session.contextKey) === baseChatContextKey(action.contextKey)) {
|
|
552
743
|
return session.contextKey;
|
|
@@ -575,26 +766,36 @@ export class ProxySessionController {
|
|
|
575
766
|
await this.runShellCommand(contextKey, command.command);
|
|
576
767
|
return;
|
|
577
768
|
case "new":
|
|
578
|
-
if (command.projectless && this.ensureAgent(context.defaultAgent).kind !== "
|
|
579
|
-
throw new Error("/new --nodir 仅支持
|
|
769
|
+
if (command.projectless && this.ensureAgent(context.defaultAgent).kind !== "app-server") {
|
|
770
|
+
throw new Error("/new --nodir 仅支持 App Server Agent。");
|
|
580
771
|
}
|
|
581
772
|
await this.createSession(
|
|
582
773
|
contextKey,
|
|
583
774
|
context.defaultAgent,
|
|
584
|
-
command.projectless
|
|
775
|
+
command.projectless
|
|
776
|
+
? undefined
|
|
777
|
+
: command.cwd === undefined
|
|
778
|
+
? this.inheritedNewTaskCwd(contextKey)
|
|
779
|
+
: resolveUserPath(command.cwd),
|
|
585
780
|
true,
|
|
586
781
|
false,
|
|
587
782
|
undefined,
|
|
588
783
|
undefined,
|
|
589
784
|
command.title,
|
|
785
|
+
this.inheritedExecutionSettings(contextKey, context.defaultAgent),
|
|
590
786
|
);
|
|
591
787
|
return;
|
|
592
788
|
case "newgroup":
|
|
789
|
+
if (command.projectless && this.ensureAgent(context.defaultAgent).kind !== "app-server") {
|
|
790
|
+
throw new Error("/newgroup --nodir 仅支持 App Server Agent。");
|
|
791
|
+
}
|
|
593
792
|
await this.createFeishuGroup(
|
|
594
793
|
contextKey,
|
|
595
794
|
context.defaultAgent,
|
|
596
795
|
command.title,
|
|
597
796
|
userId,
|
|
797
|
+
command.cwd === undefined ? undefined : resolveUserPath(command.cwd),
|
|
798
|
+
command.projectless === true,
|
|
598
799
|
);
|
|
599
800
|
return;
|
|
600
801
|
case "forkgroup":
|
|
@@ -606,7 +807,6 @@ export class ProxySessionController {
|
|
|
606
807
|
case "title":
|
|
607
808
|
await this.setTitle(contextKey, command.title);
|
|
608
809
|
return;
|
|
609
|
-
case "ask":
|
|
610
810
|
case "prompt":
|
|
611
811
|
await this.prompt(contextKey, command.text, messageId, replyTarget, localImagePaths);
|
|
612
812
|
return;
|
|
@@ -621,11 +821,7 @@ export class ProxySessionController {
|
|
|
621
821
|
return;
|
|
622
822
|
case "agent":
|
|
623
823
|
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);
|
|
824
|
+
else await this.openAgentSettings(contextKey);
|
|
629
825
|
return;
|
|
630
826
|
case "status":
|
|
631
827
|
await this.status(contextKey, command.sessionId);
|
|
@@ -635,20 +831,19 @@ export class ProxySessionController {
|
|
|
635
831
|
return;
|
|
636
832
|
case "restart":
|
|
637
833
|
if (!this.lifecycle) throw new Error("当前运行方式不支持自动重启。");
|
|
638
|
-
await this.lifecycle.restart(contextKey);
|
|
834
|
+
await this.lifecycle.restart(contextKey, command.force === true);
|
|
639
835
|
return;
|
|
640
836
|
case "model":
|
|
641
|
-
await this.
|
|
837
|
+
await this.openExecutionSettings(contextKey, "model");
|
|
838
|
+
return;
|
|
839
|
+
case "provider":
|
|
840
|
+
await this.openProviderSettings(contextKey);
|
|
642
841
|
return;
|
|
643
842
|
case "thinking":
|
|
644
|
-
await this.
|
|
843
|
+
await this.openExecutionSettings(contextKey, "thinking");
|
|
645
844
|
return;
|
|
646
845
|
case "permissions":
|
|
647
|
-
await this.
|
|
648
|
-
return;
|
|
649
|
-
case "modes":
|
|
650
|
-
case "mode":
|
|
651
|
-
await this.outbound.sendText(contextKey, "当前统一运行时不再暴露 ACP modes;Codex 请使用 /model 和 /permissions。");
|
|
846
|
+
await this.openExecutionSettings(contextKey, "permission");
|
|
652
847
|
return;
|
|
653
848
|
case "help":
|
|
654
849
|
await this.help(contextKey);
|
|
@@ -667,7 +862,13 @@ export class ProxySessionController {
|
|
|
667
862
|
const turnContextKey = session.lastTurnId
|
|
668
863
|
? this.store.getTurnContextKey(session.lastTurnId) ?? context.contextKey
|
|
669
864
|
: context.contextKey;
|
|
670
|
-
this.outbound.registerSession(
|
|
865
|
+
this.outbound.registerSession(
|
|
866
|
+
session.localSessionId,
|
|
867
|
+
turnContextKey,
|
|
868
|
+
session.title,
|
|
869
|
+
session.cwd,
|
|
870
|
+
this.agentLabel(session.agentName),
|
|
871
|
+
);
|
|
671
872
|
if (!session.lastTurnId) continue;
|
|
672
873
|
void this.outbound.resumeDelivery(session.localSessionId, turnContextKey, session.lastTurnId).catch((error: unknown) => {
|
|
673
874
|
this.logger.warn({ error, sessionId: session.localSessionId }, "Failed to restore persisted turn delivery.");
|
|
@@ -731,9 +932,15 @@ export class ProxySessionController {
|
|
|
731
932
|
replyTarget?: MessageReplyTarget,
|
|
732
933
|
localImagePaths?: string[],
|
|
733
934
|
): Promise<void> {
|
|
734
|
-
const configuredRuntime = this.runtimes.forAgent(
|
|
935
|
+
const configuredRuntime = this.runtimes.forAgent(record.agentName);
|
|
735
936
|
if (!configuredRuntime.getSession(record.localSessionId)) {
|
|
736
|
-
this.outbound.registerSession(
|
|
937
|
+
this.outbound.registerSession(
|
|
938
|
+
record.localSessionId,
|
|
939
|
+
contextKey,
|
|
940
|
+
record.title,
|
|
941
|
+
record.cwd,
|
|
942
|
+
this.agentLabel(record.agentName),
|
|
943
|
+
);
|
|
737
944
|
await this.outbound.startPendingTurn(record.localSessionId, contextKey, record.title, replyTarget, text);
|
|
738
945
|
}
|
|
739
946
|
let loaded: LoadedSession;
|
|
@@ -967,7 +1174,7 @@ export class ProxySessionController {
|
|
|
967
1174
|
private async forkThreadSession(message: IncomingMessage): Promise<void> {
|
|
968
1175
|
const { anchor, source, snapshot } = this.resolveThreadForkAnchor(message);
|
|
969
1176
|
const agent = this.ensureAgent(source.agentName);
|
|
970
|
-
const runtime = this.runtimes.forAgent(
|
|
1177
|
+
const runtime = this.runtimes.forAgent(source.agentName);
|
|
971
1178
|
if (runtime.kind !== "codex" || !runtime.forkSession) {
|
|
972
1179
|
throw new Error("当前 Codex 运行时不支持 fork 任务。");
|
|
973
1180
|
}
|
|
@@ -987,12 +1194,19 @@ export class ProxySessionController {
|
|
|
987
1194
|
this.store.updateRuntimeSession(localSessionId, {
|
|
988
1195
|
runtimeKind: "codex",
|
|
989
1196
|
title: forkTitle,
|
|
1197
|
+
modelProvider: source.modelProvider,
|
|
990
1198
|
model: source.model,
|
|
991
1199
|
reasoningEffort: source.reasoningEffort,
|
|
992
1200
|
permissionMode: source.permissionMode ?? "auto",
|
|
993
1201
|
});
|
|
994
1202
|
this.store.setCurrentSession(message.contextKey, localSessionId);
|
|
995
|
-
this.outbound.registerSession(
|
|
1203
|
+
this.outbound.registerSession(
|
|
1204
|
+
localSessionId,
|
|
1205
|
+
message.contextKey,
|
|
1206
|
+
forkTitle,
|
|
1207
|
+
source.cwd,
|
|
1208
|
+
this.agentLabel(source.agentName),
|
|
1209
|
+
);
|
|
996
1210
|
|
|
997
1211
|
try {
|
|
998
1212
|
const forked = await runtime.forkSession({
|
|
@@ -1002,6 +1216,7 @@ export class ProxySessionController {
|
|
|
1002
1216
|
agentName: source.agentName,
|
|
1003
1217
|
cwd: source.cwd,
|
|
1004
1218
|
title: forkTitle,
|
|
1219
|
+
modelProvider: source.modelProvider,
|
|
1005
1220
|
model: source.model,
|
|
1006
1221
|
reasoningEffort: source.reasoningEffort,
|
|
1007
1222
|
permissionMode: source.permissionMode ?? "auto",
|
|
@@ -1091,7 +1306,7 @@ export class ProxySessionController {
|
|
|
1091
1306
|
if (direct?.status === "closed") {
|
|
1092
1307
|
throw new Error(`找不到任务:${taskId}`);
|
|
1093
1308
|
}
|
|
1094
|
-
const global = direct ?? this.
|
|
1309
|
+
const global = direct ?? this.findStoredSessionByReference(taskId);
|
|
1095
1310
|
source = global ? { ...global, contextKey } : undefined;
|
|
1096
1311
|
}
|
|
1097
1312
|
|
|
@@ -1101,25 +1316,27 @@ export class ProxySessionController {
|
|
|
1101
1316
|
|
|
1102
1317
|
let runtime: AgentRuntime;
|
|
1103
1318
|
let agentName: string;
|
|
1319
|
+
let remote: RemoteSessionSummary | undefined;
|
|
1104
1320
|
if (source) {
|
|
1105
1321
|
agentName = source.agentName;
|
|
1106
|
-
runtime = this.runtimes.forAgent(
|
|
1322
|
+
runtime = this.runtimes.forAgent(agentName);
|
|
1107
1323
|
} else {
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
runtime =
|
|
1324
|
+
if (!taskId) throw new Error("缺少要 fork 的 Codex 任务 ID。");
|
|
1325
|
+
const resolved = await this.resolveRemoteCodexSession(taskId);
|
|
1326
|
+
agentName = resolved.agentName;
|
|
1327
|
+
runtime = resolved.runtime;
|
|
1328
|
+
remote = resolved.remote;
|
|
1112
1329
|
}
|
|
1113
1330
|
if (runtime.kind !== "codex" || !runtime.forkSession) {
|
|
1114
1331
|
throw new Error("当前 Codex 运行时不支持 fork 任务。");
|
|
1115
1332
|
}
|
|
1116
1333
|
|
|
1117
|
-
const remoteSessionId = source?.remoteSessionId ?? taskId;
|
|
1334
|
+
const remoteSessionId = source?.remoteSessionId ?? remote?.id ?? taskId;
|
|
1118
1335
|
if (!remoteSessionId) throw new Error("当前任务尚未创建 Codex 任务 ID,暂时不能 fork。");
|
|
1119
1336
|
if (!runtime.readRemoteSession && !source) {
|
|
1120
1337
|
throw new Error("当前 Codex 运行时不支持读取指定任务。");
|
|
1121
1338
|
}
|
|
1122
|
-
|
|
1339
|
+
remote ??= runtime.readRemoteSession
|
|
1123
1340
|
? await runtime.readRemoteSession(remoteSessionId)
|
|
1124
1341
|
: undefined;
|
|
1125
1342
|
const latestTurnId = remote?.lastTurnId ?? source?.lastTurnId;
|
|
@@ -1145,7 +1362,8 @@ export class ProxySessionController {
|
|
|
1145
1362
|
if (!cwd) throw new Error("指定的 Codex 任务没有可用的工作目录,暂时不能 fork。");
|
|
1146
1363
|
const sourceTitle = remote?.title ?? source?.title ?? remote?.preview;
|
|
1147
1364
|
const forkTitle = normalizeTaskTitle(requestedTitle) ?? this.store.nextForkTitle(sourceTitle);
|
|
1148
|
-
const
|
|
1365
|
+
const modelProvider = remote?.modelProvider ?? source?.modelProvider;
|
|
1366
|
+
const model = remote?.model ?? source?.model;
|
|
1149
1367
|
const reasoningEffort = source?.reasoningEffort;
|
|
1150
1368
|
const permissionMode = source?.permissionMode ?? "auto";
|
|
1151
1369
|
|
|
@@ -1158,6 +1376,7 @@ export class ProxySessionController {
|
|
|
1158
1376
|
lastTurnId,
|
|
1159
1377
|
cwd,
|
|
1160
1378
|
forkTitle,
|
|
1379
|
+
modelProvider,
|
|
1161
1380
|
model,
|
|
1162
1381
|
reasoningEffort,
|
|
1163
1382
|
permissionMode,
|
|
@@ -1223,7 +1442,7 @@ export class ProxySessionController {
|
|
|
1223
1442
|
if (!source.remoteSessionId || !this.isCodexSession(source)) {
|
|
1224
1443
|
throw new Error(`${sourceLabel}不是可 fork 的 Codex 任务。`);
|
|
1225
1444
|
}
|
|
1226
|
-
const runtime = this.runtimes.forAgent(
|
|
1445
|
+
const runtime = this.runtimes.forAgent(source.agentName);
|
|
1227
1446
|
if (runtime.kind !== "codex" || !runtime.forkSession) {
|
|
1228
1447
|
throw new Error("当前 Codex 运行时不支持 fork 任务。");
|
|
1229
1448
|
}
|
|
@@ -1248,6 +1467,7 @@ export class ProxySessionController {
|
|
|
1248
1467
|
lastTurnId,
|
|
1249
1468
|
cwd: source.cwd,
|
|
1250
1469
|
forkTitle,
|
|
1470
|
+
modelProvider: source.modelProvider,
|
|
1251
1471
|
model: source.model,
|
|
1252
1472
|
reasoningEffort: source.reasoningEffort,
|
|
1253
1473
|
permissionMode: source.permissionMode ?? "auto",
|
|
@@ -1274,6 +1494,7 @@ export class ProxySessionController {
|
|
|
1274
1494
|
this.store.updateRuntimeSession(localSessionId, {
|
|
1275
1495
|
runtimeKind: "codex",
|
|
1276
1496
|
title: plan.forkTitle,
|
|
1497
|
+
modelProvider: plan.modelProvider,
|
|
1277
1498
|
model: plan.model,
|
|
1278
1499
|
reasoningEffort: plan.reasoningEffort,
|
|
1279
1500
|
permissionMode: plan.permissionMode,
|
|
@@ -1287,6 +1508,7 @@ export class ProxySessionController {
|
|
|
1287
1508
|
agentName: plan.agentName,
|
|
1288
1509
|
cwd: plan.cwd,
|
|
1289
1510
|
title: plan.forkTitle,
|
|
1511
|
+
modelProvider: plan.modelProvider,
|
|
1290
1512
|
model: plan.model,
|
|
1291
1513
|
reasoningEffort: plan.reasoningEffort,
|
|
1292
1514
|
permissionMode: plan.permissionMode,
|
|
@@ -1297,7 +1519,13 @@ export class ProxySessionController {
|
|
|
1297
1519
|
lastTurnStatus: plan.lastTurnStatus,
|
|
1298
1520
|
});
|
|
1299
1521
|
this.store.setCurrentSession(contextKey, localSessionId);
|
|
1300
|
-
this.outbound.registerSession(
|
|
1522
|
+
this.outbound.registerSession(
|
|
1523
|
+
localSessionId,
|
|
1524
|
+
contextKey,
|
|
1525
|
+
forked.title ?? plan.forkTitle,
|
|
1526
|
+
plan.cwd,
|
|
1527
|
+
this.agentLabel(plan.agentName),
|
|
1528
|
+
);
|
|
1301
1529
|
this.store.audit(contextKey, "session_forked", {
|
|
1302
1530
|
sourceLocalSessionId: plan.source?.localSessionId,
|
|
1303
1531
|
sourceRemoteSessionId: plan.remoteSessionId,
|
|
@@ -1348,26 +1576,26 @@ export class ProxySessionController {
|
|
|
1348
1576
|
if (direct?.status === "closed") {
|
|
1349
1577
|
throw new Error(`找不到任务:${taskId}`);
|
|
1350
1578
|
}
|
|
1351
|
-
const source = direct ?? this.
|
|
1579
|
+
const source = direct ?? this.findStoredSessionByReference(taskId);
|
|
1352
1580
|
if (source && !this.isCodexSession(source)) {
|
|
1353
1581
|
throw new Error("指定任务不是 Codex 任务,暂时不能按项目创建新任务。");
|
|
1354
1582
|
}
|
|
1355
1583
|
|
|
1356
1584
|
let agentName: string;
|
|
1357
1585
|
let runtime: AgentRuntime;
|
|
1586
|
+
let remote: RemoteSessionSummary | undefined;
|
|
1358
1587
|
if (source) {
|
|
1359
1588
|
agentName = source.agentName;
|
|
1360
|
-
runtime = this.runtimes.forAgent(
|
|
1589
|
+
runtime = this.runtimes.forAgent(agentName);
|
|
1361
1590
|
} else {
|
|
1362
|
-
const
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1591
|
+
const resolved = await this.resolveRemoteCodexSession(taskId);
|
|
1592
|
+
agentName = resolved.agentName;
|
|
1593
|
+
runtime = resolved.runtime;
|
|
1594
|
+
remote = resolved.remote;
|
|
1366
1595
|
}
|
|
1367
1596
|
|
|
1368
|
-
const remoteSessionId = source?.remoteSessionId ?? taskId;
|
|
1369
|
-
|
|
1370
|
-
if (runtime.readRemoteSession && remoteSessionId) {
|
|
1597
|
+
const remoteSessionId = source?.remoteSessionId ?? remote?.id ?? taskId;
|
|
1598
|
+
if (!remote && runtime.readRemoteSession && remoteSessionId) {
|
|
1371
1599
|
try {
|
|
1372
1600
|
remote = await runtime.readRemoteSession(remoteSessionId);
|
|
1373
1601
|
} catch (error) {
|
|
@@ -1384,6 +1612,7 @@ export class ProxySessionController {
|
|
|
1384
1612
|
throw new Error("指定的 Codex 任务没有可用的工作目录,暂时不能按项目创建新任务。");
|
|
1385
1613
|
}
|
|
1386
1614
|
const executionSettings: SessionExecutionSettings = {
|
|
1615
|
+
modelProvider: remote?.modelProvider ?? source?.modelProvider,
|
|
1387
1616
|
model: remote?.model ?? source?.model,
|
|
1388
1617
|
reasoningEffort: remote?.reasoningEffort ?? source?.reasoningEffort,
|
|
1389
1618
|
permissionMode: remote?.permissionMode ?? source?.permissionMode ?? "auto",
|
|
@@ -1451,21 +1680,28 @@ export class ProxySessionController {
|
|
|
1451
1680
|
const agent = this.ensureAgent(agentName);
|
|
1452
1681
|
const localSessionId = createId("sess");
|
|
1453
1682
|
const initialTitle = normalizeTaskTitle(requestedTitle ?? prompt ?? "");
|
|
1454
|
-
const sessionCwd = cwd === undefined && agent.kind === "
|
|
1683
|
+
const sessionCwd = cwd === undefined && agent.kind === "app-server"
|
|
1455
1684
|
? createProjectlessWorkspace({ prompt: initialTitle }).cwd
|
|
1456
1685
|
: path.resolve(cwd ?? this.config.defaults.cwd);
|
|
1457
1686
|
const record = this.store.createSession({ localSessionId, contextKey, agentName, cwd: sessionCwd, status: "starting" });
|
|
1458
|
-
if (initialTitle || executionSettings.model || executionSettings.reasoningEffort || executionSettings.permissionMode) {
|
|
1687
|
+
if (initialTitle || executionSettings.modelProvider || executionSettings.model || executionSettings.reasoningEffort || executionSettings.permissionMode) {
|
|
1459
1688
|
this.store.updateRuntimeSession(localSessionId, {
|
|
1460
1689
|
title: initialTitle,
|
|
1690
|
+
modelProvider: executionSettings.modelProvider,
|
|
1461
1691
|
model: executionSettings.model,
|
|
1462
1692
|
reasoningEffort: executionSettings.reasoningEffort,
|
|
1463
1693
|
permissionMode: executionSettings.permissionMode,
|
|
1464
1694
|
});
|
|
1465
1695
|
}
|
|
1466
1696
|
this.store.setCurrentSession(contextKey, localSessionId);
|
|
1467
|
-
this.outbound.registerSession(
|
|
1468
|
-
|
|
1697
|
+
this.outbound.registerSession(
|
|
1698
|
+
localSessionId,
|
|
1699
|
+
contextKey,
|
|
1700
|
+
initialTitle,
|
|
1701
|
+
sessionCwd,
|
|
1702
|
+
this.agentLabel(agentName),
|
|
1703
|
+
);
|
|
1704
|
+
const runtime = this.runtimes.forAgent(agentName);
|
|
1469
1705
|
try {
|
|
1470
1706
|
if (prepareTurn) {
|
|
1471
1707
|
await this.outbound.startPendingTurn(localSessionId, contextKey, initialTitle, replyTarget, prompt);
|
|
@@ -1475,6 +1711,7 @@ export class ProxySessionController {
|
|
|
1475
1711
|
agentName,
|
|
1476
1712
|
cwd: sessionCwd,
|
|
1477
1713
|
title: initialTitle,
|
|
1714
|
+
modelProvider: executionSettings.modelProvider,
|
|
1478
1715
|
model: executionSettings.model,
|
|
1479
1716
|
reasoningEffort: executionSettings.reasoningEffort,
|
|
1480
1717
|
permissionMode: executionSettings.permissionMode ?? "auto",
|
|
@@ -1500,11 +1737,16 @@ export class ProxySessionController {
|
|
|
1500
1737
|
agentName: string,
|
|
1501
1738
|
requestedTitle: string | undefined,
|
|
1502
1739
|
userId: string | undefined,
|
|
1740
|
+
requestedProjectCwd?: string,
|
|
1741
|
+
forceProjectless = false,
|
|
1503
1742
|
): Promise<void> {
|
|
1504
1743
|
const source = this.currentSession(sourceContextKey);
|
|
1505
|
-
const boundProjectCwd =
|
|
1744
|
+
const boundProjectCwd = forceProjectless
|
|
1745
|
+
? undefined
|
|
1746
|
+
: requestedProjectCwd ?? this.currentProjectCwd(sourceContextKey);
|
|
1506
1747
|
const executionSettings: SessionExecutionSettings = source?.agentName === agentName
|
|
1507
1748
|
? {
|
|
1749
|
+
modelProvider: source.modelProvider,
|
|
1508
1750
|
model: source.model,
|
|
1509
1751
|
reasoningEffort: source.reasoningEffort,
|
|
1510
1752
|
permissionMode: source.permissionMode,
|
|
@@ -1544,9 +1786,9 @@ export class ProxySessionController {
|
|
|
1544
1786
|
userId: string | undefined,
|
|
1545
1787
|
boundProjectCwd: string | undefined,
|
|
1546
1788
|
executionSettings: SessionExecutionSettings,
|
|
1547
|
-
): Promise<
|
|
1789
|
+
): Promise<CreatedFeishuTaskGroup> {
|
|
1548
1790
|
const explicitTitle = normalizeTaskTitle(requestedTitle);
|
|
1549
|
-
const taskTitle = explicitTitle ??
|
|
1791
|
+
const taskTitle = explicitTitle ?? `新任务 (${formatGroupDateSuffix(new Date())})`;
|
|
1550
1792
|
const group = await this.createFeishuGroupContext(
|
|
1551
1793
|
sourceContextKey,
|
|
1552
1794
|
agentName,
|
|
@@ -1554,7 +1796,7 @@ export class ProxySessionController {
|
|
|
1554
1796
|
userId,
|
|
1555
1797
|
boundProjectCwd,
|
|
1556
1798
|
"/newgroup",
|
|
1557
|
-
|
|
1799
|
+
false,
|
|
1558
1800
|
);
|
|
1559
1801
|
|
|
1560
1802
|
let task: SessionRecord;
|
|
@@ -1587,14 +1829,17 @@ export class ProxySessionController {
|
|
|
1587
1829
|
"群和新任务已创建。",
|
|
1588
1830
|
`当前任务:${taskDescription}`,
|
|
1589
1831
|
`当前 Project 目录:${boundProjectCwd ?? "未绑定(Projectless)"}`,
|
|
1832
|
+
`当前 Provider:${task.modelProvider ?? "Codex 默认"}`,
|
|
1590
1833
|
`当前模型:${task.model ?? "默认"}`,
|
|
1591
1834
|
`思考强度:${task.reasoningEffort ?? "自动"}`,
|
|
1835
|
+
`权限类型:${task.permissionMode === "confirm" ? "执行前确认" : "自动执行"}`,
|
|
1592
1836
|
].join("\n"),
|
|
1593
1837
|
);
|
|
1594
1838
|
await this.outbound.sendText(
|
|
1595
1839
|
sourceContextKey,
|
|
1596
1840
|
`已创建飞书群:${group.name},并创建新任务 ${taskDescription}。`,
|
|
1597
1841
|
);
|
|
1842
|
+
return { group, task };
|
|
1598
1843
|
}
|
|
1599
1844
|
|
|
1600
1845
|
private async forkCurrentSessionToFeishuGroup(
|
|
@@ -1642,7 +1887,7 @@ export class ProxySessionController {
|
|
|
1642
1887
|
prepared: ForkGroupSessionPlan,
|
|
1643
1888
|
userId: string,
|
|
1644
1889
|
sourceSummary: string,
|
|
1645
|
-
): Promise<
|
|
1890
|
+
): Promise<CreatedFeishuTaskGroup> {
|
|
1646
1891
|
const { plan } = prepared;
|
|
1647
1892
|
const boundProjectCwd = detectProjectlessWorkspace(plan.cwd) ? undefined : plan.cwd;
|
|
1648
1893
|
const group = await this.createFeishuGroupContext(
|
|
@@ -1675,6 +1920,7 @@ export class ProxySessionController {
|
|
|
1675
1920
|
`已从${prepared.sourceDescription}创建分支。`,
|
|
1676
1921
|
`当前任务:${taskDescription}`,
|
|
1677
1922
|
`当前 Project 目录:${boundProjectCwd ?? "未绑定(Projectless)"}`,
|
|
1923
|
+
`当前 Provider:${forked.session.modelProvider ?? "Codex 默认"}`,
|
|
1678
1924
|
`当前模型:${forked.session.model ?? "默认"}`,
|
|
1679
1925
|
`思考强度:${forked.session.reasoningEffort ?? "自动"}`,
|
|
1680
1926
|
`权限类型:${forked.session.permissionMode === "confirm" ? "执行前确认" : "自动执行"}`,
|
|
@@ -1684,6 +1930,7 @@ export class ProxySessionController {
|
|
|
1684
1930
|
sourceContextKey,
|
|
1685
1931
|
`已将${sourceSummary} Fork 到飞书群:${group.name};新群当前任务为 ${taskDescription}。`,
|
|
1686
1932
|
);
|
|
1933
|
+
return { group, task: forked.record };
|
|
1687
1934
|
}
|
|
1688
1935
|
|
|
1689
1936
|
private async createFeishuGroupContext(
|
|
@@ -1711,18 +1958,24 @@ export class ProxySessionController {
|
|
|
1711
1958
|
this.store.recordChatContext(groupContextKey, "group");
|
|
1712
1959
|
this.store.getOrCreateUserContext(groupContextKey, agentName);
|
|
1713
1960
|
if (boundProjectCwd) this.store.setBoundProjectCwd(groupContextKey, boundProjectCwd);
|
|
1714
|
-
return { contextKey: groupContextKey, name: group.name };
|
|
1961
|
+
return { chatId: group.chatId, contextKey: groupContextKey, name: group.name };
|
|
1715
1962
|
}
|
|
1716
1963
|
|
|
1717
1964
|
private async loadSession(record: SessionRecord): Promise<LoadedSession> {
|
|
1718
1965
|
const agent = this.ensureAgent(record.agentName);
|
|
1719
|
-
const runtime = this.runtimes.forAgent(
|
|
1966
|
+
const runtime = this.runtimes.forAgent(record.agentName);
|
|
1720
1967
|
const existing = runtime.getSession(record.localSessionId);
|
|
1721
1968
|
if (existing) return { record, runtime, session: existing };
|
|
1722
1969
|
const pending = this.sessionLoads.get(record.localSessionId);
|
|
1723
1970
|
if (pending) return pending;
|
|
1724
1971
|
|
|
1725
|
-
this.outbound.registerSession(
|
|
1972
|
+
this.outbound.registerSession(
|
|
1973
|
+
record.localSessionId,
|
|
1974
|
+
record.contextKey,
|
|
1975
|
+
record.title,
|
|
1976
|
+
record.cwd,
|
|
1977
|
+
this.agentLabel(record.agentName),
|
|
1978
|
+
);
|
|
1726
1979
|
const loading = (async (): Promise<LoadedSession> => {
|
|
1727
1980
|
if (record.lastTurnId) {
|
|
1728
1981
|
try {
|
|
@@ -1745,23 +1998,25 @@ export class ProxySessionController {
|
|
|
1745
1998
|
agentName: record.agentName,
|
|
1746
1999
|
cwd: record.cwd,
|
|
1747
2000
|
title: record.title,
|
|
2001
|
+
modelProvider: record.modelProvider,
|
|
1748
2002
|
model: record.model,
|
|
1749
2003
|
reasoningEffort: record.reasoningEffort,
|
|
1750
2004
|
permissionMode,
|
|
1751
|
-
activeTurnId: agent.kind === "
|
|
2005
|
+
activeTurnId: agent.kind === "app-server" && record.status === "running" && record.lastTurnStatus === "running"
|
|
1752
2006
|
? record.lastTurnId
|
|
1753
2007
|
: undefined,
|
|
1754
2008
|
lastTurnId: record.lastTurnId,
|
|
1755
2009
|
lastTurnStatus: record.lastTurnStatus,
|
|
1756
2010
|
});
|
|
1757
2011
|
} catch (error) {
|
|
1758
|
-
if (!(agent.kind === "
|
|
2012
|
+
if (!(agent.kind === "app-server" && !record.lastTurnId && isMissingRolloutError(error))) throw error;
|
|
1759
2013
|
this.logger.warn({ error, sessionId: record.localSessionId }, "Codex task has no rollout; creating a replacement task.");
|
|
1760
2014
|
session = await runtime.createSession({
|
|
1761
2015
|
localSessionId: record.localSessionId,
|
|
1762
2016
|
agentName: record.agentName,
|
|
1763
2017
|
cwd: record.cwd,
|
|
1764
2018
|
title: record.title,
|
|
2019
|
+
modelProvider: record.modelProvider,
|
|
1765
2020
|
model: record.model,
|
|
1766
2021
|
reasoningEffort: record.reasoningEffort,
|
|
1767
2022
|
permissionMode,
|
|
@@ -1773,6 +2028,7 @@ export class ProxySessionController {
|
|
|
1773
2028
|
agentName: record.agentName,
|
|
1774
2029
|
cwd: record.cwd,
|
|
1775
2030
|
title: record.title,
|
|
2031
|
+
modelProvider: record.modelProvider,
|
|
1776
2032
|
model: record.model,
|
|
1777
2033
|
reasoningEffort: record.reasoningEffort,
|
|
1778
2034
|
permissionMode,
|
|
@@ -1795,6 +2051,7 @@ export class ProxySessionController {
|
|
|
1795
2051
|
runtimeKind: session.runtimeKind,
|
|
1796
2052
|
remoteSessionId: session.remoteSessionId,
|
|
1797
2053
|
title: session.title,
|
|
2054
|
+
modelProvider: session.modelProvider,
|
|
1798
2055
|
model: session.model,
|
|
1799
2056
|
reasoningEffort: session.reasoningEffort,
|
|
1800
2057
|
permissionMode: session.permissionMode,
|
|
@@ -1821,36 +2078,39 @@ export class ProxySessionController {
|
|
|
1821
2078
|
});
|
|
1822
2079
|
}
|
|
1823
2080
|
|
|
2081
|
+
let presentationError: unknown;
|
|
1824
2082
|
try {
|
|
1825
2083
|
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
|
-
}
|
|
2084
|
+
} catch (error) {
|
|
2085
|
+
presentationError = error;
|
|
1846
2086
|
}
|
|
2087
|
+
|
|
1847
2088
|
if (event.type === "turn_completed" || event.type === "turn_cancelled" || event.type === "turn_failed") {
|
|
2089
|
+
const terminalStatus = event.type === "turn_completed"
|
|
2090
|
+
? "completed"
|
|
2091
|
+
: event.type === "turn_cancelled"
|
|
2092
|
+
? "cancelled"
|
|
2093
|
+
: "failed";
|
|
2094
|
+
const reactionSession = this.store.getSession(event.sessionId);
|
|
2095
|
+
if (
|
|
2096
|
+
!presentationError
|
|
2097
|
+
&& reactionSession?.lastTurnId === event.turnId
|
|
2098
|
+
&& reactionSession.lastTurnStatus === terminalStatus
|
|
2099
|
+
) {
|
|
2100
|
+
await this.finalizeTurnMessageReactions(event.turnId, terminalStatus);
|
|
2101
|
+
}
|
|
2102
|
+
|
|
1848
2103
|
const latest = this.store.getSession(event.sessionId);
|
|
1849
|
-
const
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
2104
|
+
const activeTurnId = latest
|
|
2105
|
+
? this.runtimes.forAgent(latest.agentName).getSession(event.sessionId)?.activeTurnId
|
|
2106
|
+
: undefined;
|
|
2107
|
+
if (latest?.lastTurnId === event.turnId && !activeTurnId) {
|
|
2108
|
+
this.store.updateSession(event.sessionId, { status: event.type === "turn_failed" ? "failed" : "ready" });
|
|
2109
|
+
await this.scheduleNextQueuedPrompt(event.sessionId);
|
|
2110
|
+
}
|
|
1853
2111
|
}
|
|
2112
|
+
|
|
2113
|
+
if (presentationError) throw presentationError;
|
|
1854
2114
|
}
|
|
1855
2115
|
|
|
1856
2116
|
private scheduleNextQueuedPrompt(sessionId: string): Promise<void> {
|
|
@@ -1960,7 +2220,7 @@ export class ProxySessionController {
|
|
|
1960
2220
|
}
|
|
1961
2221
|
|
|
1962
2222
|
private async cancelSession(record: SessionRecord): Promise<void> {
|
|
1963
|
-
const runtime = this.runtimes.forAgent(
|
|
2223
|
+
const runtime = this.runtimes.forAgent(record.agentName);
|
|
1964
2224
|
if (runtime.kind === "codex" && record.remoteSessionId && runtime.interruptRemoteTurn) {
|
|
1965
2225
|
if (runtime.getGoal && runtime.setGoal) {
|
|
1966
2226
|
try {
|
|
@@ -2022,29 +2282,295 @@ export class ProxySessionController {
|
|
|
2022
2282
|
await loaded.runtime.respondToApproval(sessionId, requestId, decision);
|
|
2023
2283
|
}
|
|
2024
2284
|
|
|
2025
|
-
private async
|
|
2285
|
+
private async openExecutionSettings(
|
|
2286
|
+
contextKey: string,
|
|
2287
|
+
activeTab: ExecutionSettingsTab,
|
|
2288
|
+
options: ExecutionSettingsCardOptions = {},
|
|
2289
|
+
): Promise<void> {
|
|
2290
|
+
const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent!);
|
|
2291
|
+
const record = options.sessionId
|
|
2292
|
+
? this.requireSession(contextKey, options.sessionId)
|
|
2293
|
+
: this.currentSession(contextKey);
|
|
2294
|
+
if (!record && activeTab !== "agent") this.requireCurrentSession(contextKey);
|
|
2295
|
+
const agents = Object.entries(this.config.agents).map(([name, agent]) => ({
|
|
2296
|
+
name,
|
|
2297
|
+
title: agent.title,
|
|
2298
|
+
}));
|
|
2299
|
+
|
|
2300
|
+
let runtimeSettings: Omit<Parameters<CardRenderer["renderExecutionSettings"]>[0],
|
|
2301
|
+
"sessionId" | "contextKey" | "activeTab" | "currentAgent" | "taskAgent" | "agents" | "runtimeSettingsAvailable" | "notice"> = {
|
|
2302
|
+
currentPermissionMode: "auto",
|
|
2303
|
+
providers: [],
|
|
2304
|
+
providerSupported: false,
|
|
2305
|
+
models: [],
|
|
2306
|
+
reasoningOptions: [],
|
|
2307
|
+
};
|
|
2308
|
+
if (record) {
|
|
2309
|
+
const loaded = await this.loadSession(record);
|
|
2310
|
+
const models = await loaded.runtime.listModels();
|
|
2311
|
+
const currentModel = models.find((model) => model.id === loaded.session.model)
|
|
2312
|
+
?? models.find((model) => model.isDefault);
|
|
2313
|
+
const providerSupported = loaded.runtime.kind === "codex" && Boolean(loaded.runtime.listModelProviders);
|
|
2314
|
+
const providers = providerSupported ? await this.modelProviderOptions(loaded) : [];
|
|
2315
|
+
const currentProvider = loaded.session.modelProvider ?? providers.find((provider) => provider.isDefault)?.id;
|
|
2316
|
+
const currentEffort = currentModel?.supportedReasoningEfforts.some(
|
|
2317
|
+
(option) => option.value === loaded.session.reasoningEffort,
|
|
2318
|
+
)
|
|
2319
|
+
? loaded.session.reasoningEffort
|
|
2320
|
+
: currentModel?.defaultReasoningEffort ?? loaded.session.reasoningEffort;
|
|
2321
|
+
runtimeSettings = {
|
|
2322
|
+
currentProvider,
|
|
2323
|
+
currentModel: currentModel?.id ?? loaded.session.model,
|
|
2324
|
+
currentEffort,
|
|
2325
|
+
currentPermissionMode: loaded.session.permissionMode,
|
|
2326
|
+
providers,
|
|
2327
|
+
providerSupported,
|
|
2328
|
+
models,
|
|
2329
|
+
reasoningOptions: currentModel?.supportedReasoningEfforts ?? [],
|
|
2330
|
+
};
|
|
2331
|
+
}
|
|
2332
|
+
const card = this.cardRenderer.renderExecutionSettings({
|
|
2333
|
+
...(record ? { sessionId: record.localSessionId, taskAgent: record.agentName } : {}),
|
|
2334
|
+
contextKey,
|
|
2335
|
+
activeTab,
|
|
2336
|
+
currentAgent: context.defaultAgent,
|
|
2337
|
+
agents,
|
|
2338
|
+
runtimeSettingsAvailable: Boolean(record),
|
|
2339
|
+
...runtimeSettings,
|
|
2340
|
+
notice: options.notice,
|
|
2341
|
+
});
|
|
2342
|
+
if (options.updateMessageId) {
|
|
2343
|
+
await this.outbound.updateInteractiveCard(contextKey, options.updateMessageId, card);
|
|
2344
|
+
} else {
|
|
2345
|
+
await this.outbound.sendInteractiveCard(contextKey, card);
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
private async openAgentSettings(contextKey: string): Promise<void> {
|
|
2350
|
+
const current = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent!).defaultAgent;
|
|
2351
|
+
if (Object.keys(this.config.agents).length <= 1) {
|
|
2352
|
+
await this.outbound.sendText(contextKey, `当前 Agent:${current}\n当前没有其他 Agent 可以切换。`);
|
|
2353
|
+
return;
|
|
2354
|
+
}
|
|
2355
|
+
await this.openExecutionSettings(contextKey, "agent");
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
private async openProviderSettings(contextKey: string): Promise<void> {
|
|
2359
|
+
const loaded = await this.loadSession(this.requireCurrentSession(contextKey));
|
|
2360
|
+
const providers = loaded.runtime.kind === "codex" && loaded.runtime.listModelProviders
|
|
2361
|
+
? await this.modelProviderOptions(loaded)
|
|
2362
|
+
: [];
|
|
2363
|
+
const current = loaded.session.modelProvider?.trim()
|
|
2364
|
+
|| providers.find((provider) => provider.isDefault)?.id
|
|
2365
|
+
|| "运行时默认";
|
|
2366
|
+
if (providers.length <= 1) {
|
|
2367
|
+
await this.outbound.sendText(contextKey, `当前 Provider:${current}\n当前没有其他 Provider 可以切换。`);
|
|
2368
|
+
return;
|
|
2369
|
+
}
|
|
2370
|
+
await this.openExecutionSettings(contextKey, "provider", { sessionId: loaded.record.localSessionId });
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
private async selectProvider(
|
|
2374
|
+
contextKey: string,
|
|
2375
|
+
modelProvider: string,
|
|
2376
|
+
options: ModelCardOptions = {},
|
|
2377
|
+
): Promise<void> {
|
|
2026
2378
|
const record = options.sessionId
|
|
2027
2379
|
? this.requireSession(contextKey, options.sessionId)
|
|
2028
2380
|
: this.requireCurrentSession(contextKey);
|
|
2029
2381
|
const loaded = await this.loadSession(record);
|
|
2382
|
+
await this.assertModelProvider(loaded, modelProvider);
|
|
2030
2383
|
const models = await loaded.runtime.listModels();
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2384
|
+
const model = models.find((candidate) => candidate.id === loaded.session.model)
|
|
2385
|
+
?? models.find((candidate) => candidate.isDefault);
|
|
2386
|
+
if (!model) throw new Error("当前运行时没有可用于 Provider 切换的模型。");
|
|
2387
|
+
const effort = model.supportedReasoningEfforts.some(
|
|
2388
|
+
(candidate) => candidate.value === loaded.session.reasoningEffort,
|
|
2389
|
+
)
|
|
2390
|
+
? loaded.session.reasoningEffort
|
|
2391
|
+
: model.defaultReasoningEffort ?? model.supportedReasoningEfforts[0]?.value;
|
|
2392
|
+
if (!effort) throw new Error(`模型 ${model.id} 没有可用于 Provider 切换的思考强度。`);
|
|
2393
|
+
await this.applyProviderSettings(contextKey, {
|
|
2394
|
+
provider: modelProvider,
|
|
2395
|
+
model: model.id,
|
|
2396
|
+
effort,
|
|
2397
|
+
mode: loaded.session.permissionMode,
|
|
2398
|
+
}, options);
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
private async openProviderSelector(
|
|
2402
|
+
contextKey: string,
|
|
2403
|
+
options: ExecutionSettingsCardOptions = {},
|
|
2404
|
+
): Promise<void> {
|
|
2405
|
+
await this.openExecutionSettings(contextKey, "provider", options);
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
private async openProviderModelSelector(
|
|
2409
|
+
contextKey: string,
|
|
2410
|
+
modelProvider: string,
|
|
2411
|
+
permissionMode: PermissionMode,
|
|
2412
|
+
sessionId: string,
|
|
2413
|
+
updateMessageId: string,
|
|
2414
|
+
): Promise<void> {
|
|
2415
|
+
const loaded = await this.loadSession(this.requireSession(contextKey, sessionId));
|
|
2416
|
+
await this.assertModelProvider(loaded, modelProvider);
|
|
2417
|
+
const models = await loaded.runtime.listModels();
|
|
2418
|
+
const card = this.cardRenderer.renderModelSelector({
|
|
2419
|
+
sessionId,
|
|
2420
|
+
contextKey,
|
|
2421
|
+
currentModel: loaded.session.model ?? models.find((model) => model.isDefault)?.id,
|
|
2422
|
+
reasoningEffort: loaded.session.reasoningEffort,
|
|
2423
|
+
models,
|
|
2424
|
+
modelProvider,
|
|
2425
|
+
permissionMode,
|
|
2426
|
+
unifiedSettings: true,
|
|
2427
|
+
});
|
|
2428
|
+
await this.outbound.updateInteractiveCard(contextKey, updateMessageId, card);
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
private async openProviderReasoningSelector(
|
|
2432
|
+
contextKey: string,
|
|
2433
|
+
modelProvider: string,
|
|
2434
|
+
model: string,
|
|
2435
|
+
permissionMode: PermissionMode,
|
|
2436
|
+
sessionId: string,
|
|
2437
|
+
updateMessageId: string,
|
|
2438
|
+
): Promise<void> {
|
|
2439
|
+
const loaded = await this.loadSession(this.requireSession(contextKey, sessionId));
|
|
2440
|
+
await this.assertModelProvider(loaded, modelProvider);
|
|
2441
|
+
const models = await loaded.runtime.listModels();
|
|
2442
|
+
const selected = models.find((candidate) => candidate.id === model);
|
|
2443
|
+
if (!selected) throw new Error(`未知模型:${model}`);
|
|
2444
|
+
const currentEffort = selected.supportedReasoningEfforts.some(
|
|
2445
|
+
(option) => option.value === loaded.session.reasoningEffort,
|
|
2446
|
+
)
|
|
2447
|
+
? loaded.session.reasoningEffort
|
|
2448
|
+
: selected.defaultReasoningEffort;
|
|
2449
|
+
const card = this.cardRenderer.renderReasoningSelector({
|
|
2450
|
+
sessionId,
|
|
2451
|
+
contextKey,
|
|
2452
|
+
modelProvider,
|
|
2453
|
+
model,
|
|
2454
|
+
currentEffort,
|
|
2455
|
+
options: selected.supportedReasoningEfforts,
|
|
2456
|
+
permissionMode,
|
|
2457
|
+
unifiedSettings: true,
|
|
2458
|
+
});
|
|
2459
|
+
await this.outbound.updateInteractiveCard(contextKey, updateMessageId, card);
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
private async openProviderPermissionSelector(
|
|
2463
|
+
contextKey: string,
|
|
2464
|
+
modelProvider: string,
|
|
2465
|
+
model: string,
|
|
2466
|
+
effort: string,
|
|
2467
|
+
permissionMode: PermissionMode,
|
|
2468
|
+
sessionId: string,
|
|
2469
|
+
updateMessageId: string,
|
|
2470
|
+
): Promise<void> {
|
|
2471
|
+
const loaded = await this.loadSession(this.requireSession(contextKey, sessionId));
|
|
2472
|
+
await this.assertProviderModelSettings(loaded, modelProvider, model, effort);
|
|
2473
|
+
await this.outbound.updateInteractiveCard(
|
|
2474
|
+
contextKey,
|
|
2475
|
+
updateMessageId,
|
|
2476
|
+
this.cardRenderer.renderPermissionSelector({
|
|
2477
|
+
sessionId,
|
|
2035
2478
|
contextKey,
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2479
|
+
modelProvider,
|
|
2480
|
+
model,
|
|
2481
|
+
reasoningEffort: effort,
|
|
2482
|
+
currentMode: permissionMode,
|
|
2483
|
+
}),
|
|
2484
|
+
);
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
private async applyProviderSettings(
|
|
2488
|
+
contextKey: string,
|
|
2489
|
+
settings: { provider: string; model: string; effort: string; mode: PermissionMode },
|
|
2490
|
+
options: ModelCardOptions = {},
|
|
2491
|
+
): Promise<void> {
|
|
2492
|
+
const record = options.sessionId
|
|
2493
|
+
? this.requireSession(contextKey, options.sessionId)
|
|
2494
|
+
: this.requireCurrentSession(contextKey);
|
|
2495
|
+
const loaded = await this.loadSession(record);
|
|
2496
|
+
await this.assertProviderModelSettings(loaded, settings.provider, settings.model, settings.effort);
|
|
2497
|
+
if (!loaded.runtime.setExecutionSettings) {
|
|
2498
|
+
throw new Error("当前运行时不支持 Provider 设置。");
|
|
2499
|
+
}
|
|
2500
|
+
const session = await loaded.runtime.setExecutionSettings(loaded.record.localSessionId, {
|
|
2501
|
+
modelProvider: settings.provider,
|
|
2502
|
+
model: settings.model,
|
|
2503
|
+
reasoningEffort: settings.effort,
|
|
2504
|
+
permissionMode: settings.mode,
|
|
2505
|
+
});
|
|
2506
|
+
this.store.updateRuntimeSession(loaded.record.localSessionId, {
|
|
2507
|
+
modelProvider: session.modelProvider ?? settings.provider,
|
|
2508
|
+
model: session.model ?? settings.model,
|
|
2509
|
+
reasoningEffort: session.reasoningEffort ?? settings.effort,
|
|
2510
|
+
permissionMode: session.permissionMode,
|
|
2511
|
+
});
|
|
2512
|
+
const notice = [
|
|
2513
|
+
`Provider 已切换为 ${cardCode(session.modelProvider ?? settings.provider)}`,
|
|
2514
|
+
`模型 ${cardCode(session.model ?? settings.model)}`,
|
|
2515
|
+
`思考强度 ${cardCode(session.reasoningEffort ?? settings.effort)}`,
|
|
2516
|
+
`权限 ${cardCode(session.permissionMode)}`,
|
|
2517
|
+
"从下一次请求生效。",
|
|
2518
|
+
].join(",");
|
|
2519
|
+
if (options.updateMessageId) {
|
|
2520
|
+
await this.openProviderSelector(contextKey, {
|
|
2521
|
+
sessionId: loaded.record.localSessionId,
|
|
2522
|
+
updateMessageId: options.updateMessageId,
|
|
2523
|
+
notice,
|
|
2039
2524
|
});
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2525
|
+
} else {
|
|
2526
|
+
await this.outbound.sendText(contextKey, notice.replaceAll("`", ""));
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
private async assertProviderModelSettings(
|
|
2531
|
+
loaded: LoadedSession,
|
|
2532
|
+
modelProvider: string,
|
|
2533
|
+
model: string,
|
|
2534
|
+
effort: string,
|
|
2535
|
+
): Promise<void> {
|
|
2536
|
+
await this.assertModelProvider(loaded, modelProvider);
|
|
2537
|
+
const selected = (await loaded.runtime.listModels()).find((candidate) => candidate.id === model);
|
|
2538
|
+
if (!selected) throw new Error(`未知模型:${model}`);
|
|
2539
|
+
if (!selected.supportedReasoningEfforts.some((option) => option.value === effort)) {
|
|
2540
|
+
const supported = selected.supportedReasoningEfforts.map((option) => option.value).join("、") || "无";
|
|
2541
|
+
throw new Error(`模型 ${model} 不支持思考强度 ${effort}。支持的强度:${supported}`);
|
|
2046
2542
|
}
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
private async assertModelProvider(loaded: LoadedSession, modelProvider: string): Promise<void> {
|
|
2546
|
+
const providers = await this.modelProviderOptions(loaded);
|
|
2547
|
+
if (!providers.some((provider) => provider.id === modelProvider)) {
|
|
2548
|
+
throw new Error(`未知 Provider:${modelProvider}`);
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2552
|
+
private async modelProviderOptions(loaded: LoadedSession) {
|
|
2553
|
+
if (loaded.runtime.kind !== "codex" || !loaded.runtime.listModelProviders) {
|
|
2554
|
+
throw new Error("当前任务不支持 Provider 设置。");
|
|
2555
|
+
}
|
|
2556
|
+
const providers = await loaded.runtime.listModelProviders();
|
|
2557
|
+
const current = loaded.session.modelProvider?.trim();
|
|
2558
|
+
if (current && !providers.some((provider) => provider.id === current)) {
|
|
2559
|
+
providers.unshift({ id: current });
|
|
2560
|
+
}
|
|
2561
|
+
return providers;
|
|
2562
|
+
}
|
|
2047
2563
|
|
|
2564
|
+
private async model(contextKey: string, model?: string, options: ModelCardOptions = {}): Promise<void> {
|
|
2565
|
+
if (!model) {
|
|
2566
|
+
await this.openExecutionSettings(contextKey, "model", options);
|
|
2567
|
+
return;
|
|
2568
|
+
}
|
|
2569
|
+
const record = options.sessionId
|
|
2570
|
+
? this.requireSession(contextKey, options.sessionId)
|
|
2571
|
+
: this.requireCurrentSession(contextKey);
|
|
2572
|
+
const loaded = await this.loadSession(record);
|
|
2573
|
+
const models = await loaded.runtime.listModels();
|
|
2048
2574
|
const selected = models.find((item) => item.id === model);
|
|
2049
2575
|
if (!selected) throw new Error(`未知模型:${model}`);
|
|
2050
2576
|
const currentEffort = loaded.session.reasoningEffort;
|
|
@@ -2062,18 +2588,11 @@ export class ProxySessionController {
|
|
|
2062
2588
|
? `,思考强度已自动调整为 ${nextEffort}`
|
|
2063
2589
|
: "";
|
|
2064
2590
|
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
|
-
);
|
|
2591
|
+
await this.openExecutionSettings(contextKey, "model", {
|
|
2592
|
+
sessionId: loaded.record.localSessionId,
|
|
2593
|
+
updateMessageId: options.updateMessageId,
|
|
2594
|
+
notice: `模型已切换为 ${cardCode(model)}${effortMessage},从下一次请求生效。`,
|
|
2595
|
+
});
|
|
2077
2596
|
return;
|
|
2078
2597
|
}
|
|
2079
2598
|
await this.outbound.sendText(contextKey, `模型已切换为 ${model}${effortMessage},从下一次请求生效。`);
|
|
@@ -2089,7 +2608,7 @@ export class ProxySessionController {
|
|
|
2089
2608
|
}
|
|
2090
2609
|
const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent!);
|
|
2091
2610
|
const agent = this.ensureAgent(context.defaultAgent);
|
|
2092
|
-
if (agent.kind !== "
|
|
2611
|
+
if (agent.kind !== "app-server") throw new Error("Goal 模式仅支持 App Server 任务。");
|
|
2093
2612
|
record = await this.createSession(
|
|
2094
2613
|
contextKey,
|
|
2095
2614
|
context.defaultAgent,
|
|
@@ -2242,6 +2761,10 @@ export class ProxySessionController {
|
|
|
2242
2761
|
}
|
|
2243
2762
|
|
|
2244
2763
|
private async thinking(contextKey: string, effort?: string, options: ThinkingCardOptions = {}): Promise<void> {
|
|
2764
|
+
if (!effort) {
|
|
2765
|
+
await this.openExecutionSettings(contextKey, "thinking", options);
|
|
2766
|
+
return;
|
|
2767
|
+
}
|
|
2245
2768
|
const record = options.sessionId
|
|
2246
2769
|
? this.requireSession(contextKey, options.sessionId)
|
|
2247
2770
|
: this.requireCurrentSession(contextKey);
|
|
@@ -2255,22 +2778,6 @@ export class ProxySessionController {
|
|
|
2255
2778
|
}
|
|
2256
2779
|
const supported = currentModel.supportedReasoningEfforts;
|
|
2257
2780
|
|
|
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
2781
|
if (!supported.some((option) => option.value === effort)) {
|
|
2275
2782
|
const options = supported.map((option) => option.value).join("、") || "无";
|
|
2276
2783
|
throw new Error(`不支持的思考强度:${effort}。支持的强度:${options}`);
|
|
@@ -2278,48 +2785,45 @@ export class ProxySessionController {
|
|
|
2278
2785
|
await loaded.runtime.setReasoningEffort(loaded.record.localSessionId, effort);
|
|
2279
2786
|
this.store.updateRuntimeSession(loaded.record.localSessionId, { reasoningEffort: effort });
|
|
2280
2787
|
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
|
-
);
|
|
2788
|
+
await this.openExecutionSettings(contextKey, "thinking", {
|
|
2789
|
+
sessionId: loaded.record.localSessionId,
|
|
2790
|
+
updateMessageId: options.updateMessageId,
|
|
2791
|
+
notice: `思考强度已切换为 ${cardCode(effort)},从下一次请求生效。`,
|
|
2792
|
+
});
|
|
2293
2793
|
return;
|
|
2294
2794
|
}
|
|
2295
2795
|
await this.outbound.sendText(contextKey, `思考强度已切换为 ${effort},从下一次请求生效。`);
|
|
2296
2796
|
}
|
|
2297
2797
|
|
|
2298
|
-
private async permissions(
|
|
2299
|
-
|
|
2798
|
+
private async permissions(
|
|
2799
|
+
contextKey: string,
|
|
2800
|
+
mode?: PermissionMode,
|
|
2801
|
+
options: ModelCardOptions = {},
|
|
2802
|
+
): Promise<void> {
|
|
2300
2803
|
if (!mode) {
|
|
2301
|
-
await this.
|
|
2804
|
+
await this.openExecutionSettings(contextKey, "permission", options);
|
|
2302
2805
|
return;
|
|
2303
2806
|
}
|
|
2807
|
+
const record = options.sessionId
|
|
2808
|
+
? this.requireSession(contextKey, options.sessionId)
|
|
2809
|
+
: this.requireCurrentSession(contextKey);
|
|
2304
2810
|
const loaded = await this.loadSession(record);
|
|
2305
2811
|
await loaded.runtime.setPermissionMode(record.localSessionId, mode);
|
|
2812
|
+
loaded.session.permissionMode = mode;
|
|
2306
2813
|
this.store.updateRuntimeSession(record.localSessionId, { permissionMode: mode });
|
|
2814
|
+
if (options.updateMessageId) {
|
|
2815
|
+
await this.openExecutionSettings(contextKey, "permission", {
|
|
2816
|
+
sessionId: loaded.record.localSessionId,
|
|
2817
|
+
updateMessageId: options.updateMessageId,
|
|
2818
|
+
notice: mode === "auto"
|
|
2819
|
+
? "已切换为自动执行模式,从下一次请求生效。"
|
|
2820
|
+
: "已切换为执行前确认模式,从下一次请求生效。",
|
|
2821
|
+
});
|
|
2822
|
+
return;
|
|
2823
|
+
}
|
|
2307
2824
|
await this.outbound.sendText(contextKey, mode === "auto" ? "已切换为自动执行模式。" : "已切换为执行前确认模式。");
|
|
2308
2825
|
}
|
|
2309
2826
|
|
|
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
2827
|
private async listSessions(
|
|
2324
2828
|
contextKey: string,
|
|
2325
2829
|
searchTerm?: string,
|
|
@@ -2331,39 +2835,46 @@ export class ProxySessionController {
|
|
|
2331
2835
|
const localSessions = this.store.listSessions(contextKey).filter((session) =>
|
|
2332
2836
|
session.status !== "closed" && this.isCodexSession(session),
|
|
2333
2837
|
);
|
|
2334
|
-
|
|
2335
|
-
|
|
2838
|
+
const remoteSessions: AgentRemoteSessionSummary[] = [];
|
|
2839
|
+
const remoteErrors: string[] = [];
|
|
2336
2840
|
let remoteHasMore = false;
|
|
2337
|
-
|
|
2338
|
-
|
|
2841
|
+
await Promise.all(this.runtimes.entries("codex").map(async ([agentName, runtime]) => {
|
|
2842
|
+
if (!runtime.listRemoteSessions) return;
|
|
2339
2843
|
try {
|
|
2340
2844
|
const page = await runtime.listRemoteSessions({ searchTerm, limit: visibleCount });
|
|
2341
|
-
remoteSessions
|
|
2342
|
-
remoteHasMore
|
|
2845
|
+
remoteSessions.push(...page.sessions.map((session) => ({ agentName, session })));
|
|
2846
|
+
remoteHasMore ||= Boolean(page.nextCursor);
|
|
2343
2847
|
} catch (error) {
|
|
2344
|
-
this.logger.warn({ error, contextKey }, "Failed to list Codex sessions.");
|
|
2345
|
-
|
|
2848
|
+
this.logger.warn({ error, contextKey, agentName }, "Failed to list Codex sessions for an Agent.");
|
|
2849
|
+
remoteErrors.push(`${agentName}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2346
2850
|
}
|
|
2347
|
-
}
|
|
2851
|
+
}));
|
|
2852
|
+
const remoteHint = remoteErrors.length > 0
|
|
2853
|
+
? `部分 Agent 的任务读取失败:${remoteErrors.join(";")}`
|
|
2854
|
+
: undefined;
|
|
2348
2855
|
|
|
2349
2856
|
const entries = mergeTaskList(localSessions, remoteSessions, context.currentSessionId)
|
|
2350
|
-
.filter((entry) => !normalizedSearch
|
|
2857
|
+
.filter((entry) => !normalizedSearch
|
|
2858
|
+
|| [entry.id, entry.title, entry.cwd, entry.agentName]
|
|
2859
|
+
.some((value) => value.toLowerCase().includes(normalizedSearch)))
|
|
2351
2860
|
.sort((left, right) => Number(right.current) - Number(left.current)
|
|
2352
2861
|
|| Number(right.active) - Number(left.active)
|
|
2353
2862
|
|| right.updatedAt - left.updatedAt);
|
|
2354
2863
|
const activeCount = entries.filter((entry) => entry.active).length;
|
|
2355
2864
|
const visibleEntries = entries.slice(0, visibleCount);
|
|
2356
2865
|
const hasMore = remoteHasMore || entries.length > visibleCount;
|
|
2357
|
-
this.lastSessionListings.set(contextKey, visibleEntries.map((entry) => entry.
|
|
2866
|
+
this.lastSessionListings.set(contextKey, visibleEntries.map((entry) => entry.reference));
|
|
2358
2867
|
const cardEntries = visibleEntries.map((entry, index) => {
|
|
2359
2868
|
const marker = entry.current ? "✅" : entry.active ? "🟢 **活跃**" : "•";
|
|
2360
|
-
const showStop = entry.status === "外部执行中"
|
|
2869
|
+
const showStop = entry.status === "外部执行中"
|
|
2870
|
+
&& entry.reference !== options.forceSwitchTaskId
|
|
2871
|
+
&& entry.id !== options.forceSwitchTaskId;
|
|
2361
2872
|
const actions: TaskListCardAction[] = entry.current ? [] : [{
|
|
2362
2873
|
text: showStop ? "Stop" : "Switch",
|
|
2363
2874
|
type: showStop ? "danger" as const : "default" as const,
|
|
2364
2875
|
value: {
|
|
2365
2876
|
action: showStop ? "session_stop" : "session_switch",
|
|
2366
|
-
sessionId: entry.
|
|
2877
|
+
sessionId: entry.reference,
|
|
2367
2878
|
...(searchTerm ? { searchTerm } : {}),
|
|
2368
2879
|
visibleCount: String(visibleCount),
|
|
2369
2880
|
contextKey,
|
|
@@ -2373,7 +2884,7 @@ export class ProxySessionController {
|
|
|
2373
2884
|
text: "New",
|
|
2374
2885
|
value: {
|
|
2375
2886
|
action: "session_new",
|
|
2376
|
-
sessionId: entry.
|
|
2887
|
+
sessionId: entry.reference,
|
|
2377
2888
|
...(searchTerm ? { searchTerm } : {}),
|
|
2378
2889
|
visibleCount: String(visibleCount),
|
|
2379
2890
|
contextKey,
|
|
@@ -2383,7 +2894,7 @@ export class ProxySessionController {
|
|
|
2383
2894
|
text: "NewGroup",
|
|
2384
2895
|
value: {
|
|
2385
2896
|
action: "session_new_group",
|
|
2386
|
-
sessionId: entry.
|
|
2897
|
+
sessionId: entry.reference,
|
|
2387
2898
|
...(searchTerm ? { searchTerm } : {}),
|
|
2388
2899
|
visibleCount: String(visibleCount),
|
|
2389
2900
|
contextKey,
|
|
@@ -2393,7 +2904,7 @@ export class ProxySessionController {
|
|
|
2393
2904
|
text: "Fork",
|
|
2394
2905
|
value: {
|
|
2395
2906
|
action: "session_fork",
|
|
2396
|
-
sessionId: entry.
|
|
2907
|
+
sessionId: entry.reference,
|
|
2397
2908
|
...(searchTerm ? { searchTerm } : {}),
|
|
2398
2909
|
visibleCount: String(visibleCount),
|
|
2399
2910
|
contextKey,
|
|
@@ -2403,7 +2914,7 @@ export class ProxySessionController {
|
|
|
2403
2914
|
text: "ForkGroup",
|
|
2404
2915
|
value: {
|
|
2405
2916
|
action: "session_fork_group",
|
|
2406
|
-
sessionId: entry.
|
|
2917
|
+
sessionId: entry.reference,
|
|
2407
2918
|
...(searchTerm ? { searchTerm } : {}),
|
|
2408
2919
|
visibleCount: String(visibleCount),
|
|
2409
2920
|
contextKey,
|
|
@@ -2413,14 +2924,14 @@ export class ProxySessionController {
|
|
|
2413
2924
|
text: "Status",
|
|
2414
2925
|
value: {
|
|
2415
2926
|
action: "session_status",
|
|
2416
|
-
sessionId: entry.
|
|
2927
|
+
sessionId: entry.reference,
|
|
2417
2928
|
contextKey,
|
|
2418
2929
|
},
|
|
2419
2930
|
});
|
|
2420
2931
|
return {
|
|
2421
2932
|
lines: [
|
|
2422
2933
|
`**${index + 1}.** ${marker} **${cardText(entry.title)}** ${cardText(entry.id)}`,
|
|
2423
|
-
`${entry.status} · ${entry.updatedLabel} · ${cardText(entry.cwd || "目录未知")}`,
|
|
2934
|
+
`${entry.status} · ${entry.updatedLabel} · ${cardText(entry.agentName)} · ${cardText(entry.cwd || "目录未知")}`,
|
|
2424
2935
|
],
|
|
2425
2936
|
actions,
|
|
2426
2937
|
};
|
|
@@ -2483,6 +2994,46 @@ export class ProxySessionController {
|
|
|
2483
2994
|
});
|
|
2484
2995
|
}
|
|
2485
2996
|
|
|
2997
|
+
private findStoredSessionByReference(reference: string, contextKey?: string): SessionRecord | undefined {
|
|
2998
|
+
const scoped = parseRemoteSessionReference(reference);
|
|
2999
|
+
return scoped
|
|
3000
|
+
? this.store.findSessionByRemoteSessionId(scoped.remoteSessionId, contextKey, scoped.agentName)
|
|
3001
|
+
: this.store.findSessionByRemoteSessionId(reference, contextKey);
|
|
3002
|
+
}
|
|
3003
|
+
|
|
3004
|
+
private async resolveRemoteCodexSession(reference: string): Promise<AgentRemoteSession> {
|
|
3005
|
+
const scoped = parseRemoteSessionReference(reference);
|
|
3006
|
+
const candidates = scoped
|
|
3007
|
+
? [[scoped.agentName, this.runtimes.forAgent(scoped.agentName)] as const]
|
|
3008
|
+
: this.runtimes.entries("codex");
|
|
3009
|
+
if (candidates.length === 0) throw new Error("未配置 Codex Agent。");
|
|
3010
|
+
|
|
3011
|
+
const reads = await Promise.allSettled(candidates.map(async ([agentName, runtime]) => {
|
|
3012
|
+
if (runtime.kind !== "codex" || !runtime.readRemoteSession) {
|
|
3013
|
+
throw new Error(`Agent ${agentName} 不支持读取远端任务。`);
|
|
3014
|
+
}
|
|
3015
|
+
return {
|
|
3016
|
+
agentName,
|
|
3017
|
+
runtime,
|
|
3018
|
+
remote: await runtime.readRemoteSession(scoped?.remoteSessionId ?? reference),
|
|
3019
|
+
} satisfies AgentRemoteSession;
|
|
3020
|
+
}));
|
|
3021
|
+
const matches = reads
|
|
3022
|
+
.filter((result): result is PromiseFulfilledResult<AgentRemoteSession> => result.status === "fulfilled")
|
|
3023
|
+
.map((result) => result.value);
|
|
3024
|
+
if (matches.length === 1) return matches[0]!;
|
|
3025
|
+
if (matches.length > 1) {
|
|
3026
|
+
throw new Error(`多个 Agent 中存在相同任务 ID:${reference}。请先使用 /sessions,再通过序号选择任务。`);
|
|
3027
|
+
}
|
|
3028
|
+
const details = reads
|
|
3029
|
+
.map((result, index) => result.status === "rejected"
|
|
3030
|
+
? `${candidates[index]?.[0] ?? "unknown"}: ${runtimeErrorMessage(result.reason)}`
|
|
3031
|
+
: undefined)
|
|
3032
|
+
.filter((detail): detail is string => Boolean(detail))
|
|
3033
|
+
.join(";");
|
|
3034
|
+
throw new Error(`找不到 Codex 任务:${scoped?.remoteSessionId ?? reference}${details ? `(${details})` : ""}`);
|
|
3035
|
+
}
|
|
3036
|
+
|
|
2486
3037
|
private async switchSession(contextKey: string, reference?: string): Promise<void> {
|
|
2487
3038
|
this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent!);
|
|
2488
3039
|
const taskId = this.resolveSessionReference(contextKey, reference);
|
|
@@ -2490,27 +3041,28 @@ export class ProxySessionController {
|
|
|
2490
3041
|
if (direct?.status === "closed") {
|
|
2491
3042
|
throw new Error(`找不到任务:${taskId}`);
|
|
2492
3043
|
}
|
|
2493
|
-
const existing = direct ?? this.
|
|
3044
|
+
const existing = direct ?? this.findStoredSessionByReference(taskId);
|
|
2494
3045
|
if (existing) {
|
|
2495
|
-
const runtime = this.runtimes.forAgent(
|
|
3046
|
+
const runtime = this.runtimes.forAgent(existing.agentName);
|
|
2496
3047
|
await this.assertSessionTurnOwnership(existing, runtime);
|
|
2497
3048
|
this.store.attachSessionToContext(contextKey, existing.localSessionId);
|
|
2498
3049
|
this.store.setCurrentSession(contextKey, existing.localSessionId);
|
|
2499
|
-
this.outbound.registerSession(
|
|
3050
|
+
this.outbound.registerSession(
|
|
3051
|
+
existing.localSessionId,
|
|
3052
|
+
contextKey,
|
|
3053
|
+
existing.title,
|
|
3054
|
+
existing.cwd,
|
|
3055
|
+
this.agentLabel(existing.agentName),
|
|
3056
|
+
);
|
|
2500
3057
|
await this.outbound.sendText(contextKey, `已切换到任务:${existing.title ?? existing.remoteSessionId ?? taskId}`);
|
|
2501
3058
|
return;
|
|
2502
3059
|
}
|
|
2503
3060
|
|
|
2504
|
-
const
|
|
2505
|
-
if (!runtime.readRemoteSession) throw new Error("当前 Codex 运行时不支持读取任务。");
|
|
2506
|
-
const remote = await runtime.readRemoteSession(taskId);
|
|
3061
|
+
const { agentName, remote } = await this.resolveRemoteCodexSession(taskId);
|
|
2507
3062
|
if (remote.status === "active" || remote.lastTurnStatus === "inProgress") {
|
|
2508
3063
|
throw new Error(`这个任务正在外部 Codex 中执行,当前不会切换。可使用 /status ${taskId} 查看进度。`);
|
|
2509
3064
|
}
|
|
2510
3065
|
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
3066
|
const localSessionId = createId("sess");
|
|
2515
3067
|
this.store.createSession({
|
|
2516
3068
|
localSessionId,
|
|
@@ -2528,7 +3080,13 @@ export class ProxySessionController {
|
|
|
2528
3080
|
lastTurnStatus: mapRemoteTurnStatus(remote.lastTurnStatus),
|
|
2529
3081
|
});
|
|
2530
3082
|
this.store.setCurrentSession(contextKey, localSessionId);
|
|
2531
|
-
this.outbound.registerSession(
|
|
3083
|
+
this.outbound.registerSession(
|
|
3084
|
+
localSessionId,
|
|
3085
|
+
contextKey,
|
|
3086
|
+
remote.title ?? remote.preview,
|
|
3087
|
+
remote.cwd,
|
|
3088
|
+
this.agentLabel(agentName),
|
|
3089
|
+
);
|
|
2532
3090
|
await this.outbound.sendText(
|
|
2533
3091
|
contextKey,
|
|
2534
3092
|
`已切换到任务:${remote.title ?? remote.preview ?? remote.id}。历史消息不会重新发送。`,
|
|
@@ -2543,17 +3101,16 @@ export class ProxySessionController {
|
|
|
2543
3101
|
return;
|
|
2544
3102
|
}
|
|
2545
3103
|
|
|
2546
|
-
const existing = this.
|
|
3104
|
+
const existing = this.findStoredSessionByReference(taskId);
|
|
2547
3105
|
if (existing) {
|
|
2548
3106
|
await this.cancelSession({ ...existing, contextKey });
|
|
2549
3107
|
return;
|
|
2550
3108
|
}
|
|
2551
3109
|
|
|
2552
|
-
const runtime = this.
|
|
3110
|
+
const { runtime, remote } = await this.resolveRemoteCodexSession(taskId);
|
|
2553
3111
|
if (!runtime.readRemoteSession || !runtime.interruptRemoteTurn) {
|
|
2554
3112
|
throw new Error("当前 Codex 运行时不支持停止外部任务。");
|
|
2555
3113
|
}
|
|
2556
|
-
const remote = await runtime.readRemoteSession(taskId);
|
|
2557
3114
|
const turnId = remote.status === "active" || remote.lastTurnStatus === "inProgress"
|
|
2558
3115
|
? remote.lastTurnId
|
|
2559
3116
|
: undefined;
|
|
@@ -2628,11 +3185,22 @@ export class ProxySessionController {
|
|
|
2628
3185
|
return undefined;
|
|
2629
3186
|
}
|
|
2630
3187
|
|
|
2631
|
-
private async setDefaultAgent(
|
|
3188
|
+
private async setDefaultAgent(
|
|
3189
|
+
contextKey: string,
|
|
3190
|
+
agentName: string,
|
|
3191
|
+
options: ExecutionSettingsCardOptions = {},
|
|
3192
|
+
): Promise<void> {
|
|
2632
3193
|
this.ensureAgent(agentName);
|
|
2633
3194
|
this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent!);
|
|
2634
3195
|
this.store.setDefaultAgent(contextKey, agentName);
|
|
2635
|
-
|
|
3196
|
+
if (Object.keys(this.config.agents).length <= 1) {
|
|
3197
|
+
await this.outbound.sendText(contextKey, `当前 Agent:${agentName}\n当前没有其他 Agent 可以切换。`);
|
|
3198
|
+
return;
|
|
3199
|
+
}
|
|
3200
|
+
await this.openExecutionSettings(contextKey, "agent", {
|
|
3201
|
+
...options,
|
|
3202
|
+
notice: `默认 Agent 已切换为 ${cardCode(agentName)},从下一次新建任务生效。`,
|
|
3203
|
+
});
|
|
2636
3204
|
}
|
|
2637
3205
|
|
|
2638
3206
|
private async status(
|
|
@@ -2647,7 +3215,7 @@ export class ProxySessionController {
|
|
|
2647
3215
|
const direct = this.store.getSession(targetSessionId);
|
|
2648
3216
|
current = direct
|
|
2649
3217
|
? { ...direct, contextKey }
|
|
2650
|
-
: this.
|
|
3218
|
+
: this.findStoredSessionByReference(targetSessionId, contextKey);
|
|
2651
3219
|
if (!current) {
|
|
2652
3220
|
await this.statusForCodexTask(contextKey, targetSessionId, options);
|
|
2653
3221
|
return;
|
|
@@ -2658,7 +3226,7 @@ export class ProxySessionController {
|
|
|
2658
3226
|
|
|
2659
3227
|
if (current && current.status === "running") {
|
|
2660
3228
|
try {
|
|
2661
|
-
const runtime = this.runtimes.forAgent(
|
|
3229
|
+
const runtime = this.runtimes.forAgent(current.agentName);
|
|
2662
3230
|
if (runtime.getSession(current.localSessionId)) {
|
|
2663
3231
|
await runtime.synchronizeSession(current.localSessionId);
|
|
2664
3232
|
current = this.store.getSession(current.localSessionId) ?? current;
|
|
@@ -2672,7 +3240,7 @@ export class ProxySessionController {
|
|
|
2672
3240
|
let remote: RemoteSessionSummary | undefined;
|
|
2673
3241
|
let goal: RuntimeGoal | undefined;
|
|
2674
3242
|
if (current?.remoteSessionId) {
|
|
2675
|
-
const runtime = this.runtimes.forAgent(
|
|
3243
|
+
const runtime = this.runtimes.forAgent(current.agentName);
|
|
2676
3244
|
if (runtime.readRemoteSession) {
|
|
2677
3245
|
try {
|
|
2678
3246
|
remote = await runtime.readRemoteSession(current.remoteSessionId);
|
|
@@ -2700,7 +3268,7 @@ export class ProxySessionController {
|
|
|
2700
3268
|
taskLines.push("无。直接发送消息即可创建一个未指定项目的 Codex 任务。");
|
|
2701
3269
|
} else {
|
|
2702
3270
|
const agent = this.ensureAgent(current.agentName);
|
|
2703
|
-
const runtimeSession = this.runtimes.forAgent(
|
|
3271
|
+
const runtimeSession = this.runtimes.forAgent(current.agentName).getSession(current.localSessionId);
|
|
2704
3272
|
activeTurnId = runtimeSession?.activeTurnId;
|
|
2705
3273
|
const remoteActive = isRemoteSessionActive(remote);
|
|
2706
3274
|
activeTurnId = remoteActive ? remote?.lastTurnId ?? activeTurnId : activeTurnId;
|
|
@@ -2715,7 +3283,7 @@ export class ProxySessionController {
|
|
|
2715
3283
|
taskLines.push(
|
|
2716
3284
|
`**标题**:${cardCode(current.title ?? "未命名任务")}`,
|
|
2717
3285
|
`**工作目录**:${cardCode(current.cwd)}`,
|
|
2718
|
-
|
|
3286
|
+
`**Provider / 模型 / 思考强度**:${cardCode(current.modelProvider ?? "Codex 默认")} / ${cardCode(current.model ?? "默认")} / ${cardCode(current.reasoningEffort ?? "自动")}`,
|
|
2719
3287
|
`**状态 / 最近结果**:${statusLabel} / ${resultLabel}`,
|
|
2720
3288
|
`**Agent**:${cardCode(agent.title)}`,
|
|
2721
3289
|
`**权限 / 任务范围**:${current.permissionMode === "confirm" ? "执行前确认" : "自动执行"} / ${detectProjectlessWorkspace(current.cwd) ? "未指定项目" : "指定项目"}`,
|
|
@@ -2752,12 +3320,17 @@ export class ProxySessionController {
|
|
|
2752
3320
|
const title = targetSessionId && current
|
|
2753
3321
|
? `Codex 状态:${truncateText((current.title ?? current.remoteSessionId ?? current.localSessionId).replace(/\s+/g, " "), 40)}`
|
|
2754
3322
|
: "Codex 状态";
|
|
2755
|
-
const taskId = current?.remoteSessionId
|
|
3323
|
+
const taskId = current?.remoteSessionId
|
|
3324
|
+
? remoteSessionReference(current.agentName, current.remoteSessionId)
|
|
3325
|
+
: current?.localSessionId;
|
|
2756
3326
|
const isCurrent = Boolean(current && current.localSessionId === context.currentSessionId);
|
|
2757
3327
|
const remoteActive = isRemoteSessionActive(remote);
|
|
2758
3328
|
const botOwnsActiveTurn = Boolean(localCurrent && remote && isBotOwnedActiveTurn(localCurrent, remote));
|
|
2759
3329
|
const active = Boolean(activeTurnId || current?.status === "running" || remoteActive);
|
|
2760
|
-
const forceSwitch = Boolean(taskId &&
|
|
3330
|
+
const forceSwitch = Boolean(taskId && (
|
|
3331
|
+
options.forceSwitchTaskId === taskId
|
|
3332
|
+
|| options.forceSwitchTaskId === current?.remoteSessionId
|
|
3333
|
+
));
|
|
2761
3334
|
const taskActions: TaskListCardAction[] = !taskId ? []
|
|
2762
3335
|
: remoteActive && !botOwnsActiveTurn && !forceSwitch
|
|
2763
3336
|
? [statusCardAction("Stop", "danger", "session_stop", taskId, contextKey)]
|
|
@@ -2777,12 +3350,11 @@ export class ProxySessionController {
|
|
|
2777
3350
|
|
|
2778
3351
|
private async statusForCodexTask(
|
|
2779
3352
|
contextKey: string,
|
|
2780
|
-
|
|
3353
|
+
reference: string,
|
|
2781
3354
|
options: StatusCardOptions = {},
|
|
2782
3355
|
): Promise<void> {
|
|
2783
|
-
const
|
|
2784
|
-
|
|
2785
|
-
const remote = await runtime.readRemoteSession(remoteSessionId);
|
|
3356
|
+
const { agentName, remote } = await this.resolveRemoteCodexSession(reference);
|
|
3357
|
+
const actionReference = remoteSessionReference(agentName, remote.id);
|
|
2786
3358
|
const sections: CardSection[] = [
|
|
2787
3359
|
{
|
|
2788
3360
|
title: "指定任务",
|
|
@@ -2790,6 +3362,7 @@ export class ProxySessionController {
|
|
|
2790
3362
|
`**标题**:${cardCode(remote.title ?? remote.preview ?? "未命名任务")}`,
|
|
2791
3363
|
`**工作目录**:${cardCode(remote.cwd || "目录未知")}`,
|
|
2792
3364
|
`**状态 / 当前任务**:${remoteSessionDetailStatus(remote)} / 未切换`,
|
|
3365
|
+
`**Agent**:${cardCode(agentName)}`,
|
|
2793
3366
|
`**Codex 任务 ID**:${cardCode(remote.id)}`,
|
|
2794
3367
|
`**最近回合**:${cardCode(remote.lastTurnId ?? "无")} ${remoteTurnStatusLabel(remote.lastTurnStatus)}`,
|
|
2795
3368
|
`**创建时间 / 最近活动**:${formatRemoteTime(remote.createdAt)} / ${formatRemoteTime(latestRemoteTimestamp(remote.recencyAt, remote.updatedAt))}`,
|
|
@@ -2809,14 +3382,16 @@ export class ProxySessionController {
|
|
|
2809
3382
|
},
|
|
2810
3383
|
];
|
|
2811
3384
|
const title = `Codex 状态:${truncateText((remote.title ?? remote.preview ?? remote.id).replace(/\s+/g, " "), 40)}`;
|
|
2812
|
-
const showStop = isRemoteSessionActive(remote)
|
|
3385
|
+
const showStop = isRemoteSessionActive(remote)
|
|
3386
|
+
&& options.forceSwitchTaskId !== actionReference
|
|
3387
|
+
&& options.forceSwitchTaskId !== remote.id;
|
|
2813
3388
|
const actions = [
|
|
2814
|
-
statusRefreshAction(
|
|
3389
|
+
statusRefreshAction(actionReference, contextKey),
|
|
2815
3390
|
statusCardAction(
|
|
2816
3391
|
showStop ? "Stop" : "Switch",
|
|
2817
3392
|
showStop ? "danger" : "default",
|
|
2818
3393
|
showStop ? "session_stop" : "session_switch",
|
|
2819
|
-
|
|
3394
|
+
actionReference,
|
|
2820
3395
|
contextKey,
|
|
2821
3396
|
),
|
|
2822
3397
|
];
|
|
@@ -2840,7 +3415,7 @@ export class ProxySessionController {
|
|
|
2840
3415
|
title: "任务管理",
|
|
2841
3416
|
lines: [
|
|
2842
3417
|
"**/new [title] [--dir <cwd> | --nodir]** 使用默认 Agent 创建任务;--nodir 强制创建 Projectless 任务",
|
|
2843
|
-
"**/newgroup [title]**
|
|
3418
|
+
"**/newgroup [title] [--dir <cwd> | --nodir]** 创建飞书群和新任务;参数与 /new 相同",
|
|
2844
3419
|
"**/forkgroup [title]** 从当前任务最新已完成轮次创建分支并绑定到新群",
|
|
2845
3420
|
"**/fork [序号或任务 ID]** 从当前或指定任务创建分支;运行中使用最近已完成轮次",
|
|
2846
3421
|
"**/title <新标题>** 修改当前任务的标题",
|
|
@@ -2855,22 +3430,23 @@ export class ProxySessionController {
|
|
|
2855
3430
|
"**/stop** 停止当前执行",
|
|
2856
3431
|
"**/queue <prompt>**(兼容 **/nosteer**) 不追加到当前轮次,按顺序排队为后续轮次",
|
|
2857
3432
|
"**/goal [目标]** 查看或创建长任务 Goal;支持 pause、resume、edit、clear",
|
|
2858
|
-
"**/
|
|
2859
|
-
"**/
|
|
2860
|
-
"**/
|
|
3433
|
+
"**/provider** 选择 AI 服务提供商;没有其他候选时仅显示当前 Provider",
|
|
3434
|
+
"**/model** 选择当前任务使用的模型",
|
|
3435
|
+
"**/thinking** 设置模型的思考强度",
|
|
3436
|
+
"**/permissions** 设置执行工具前是否需要确认",
|
|
2861
3437
|
],
|
|
2862
3438
|
},
|
|
2863
3439
|
{
|
|
2864
3440
|
title: "Agent",
|
|
2865
3441
|
lines: [
|
|
2866
|
-
"**/agent [name]**
|
|
3442
|
+
"**/agent [name]** 选择新任务使用的默认 Agent;仅配置一个时直接显示当前 Agent",
|
|
2867
3443
|
],
|
|
2868
3444
|
},
|
|
2869
3445
|
{
|
|
2870
3446
|
title: "系统",
|
|
2871
3447
|
lines: [
|
|
2872
3448
|
"**/status [序号或任务 ID]** 查看当前或指定任务的详细状态",
|
|
2873
|
-
"**/restart**
|
|
3449
|
+
"**/restart [--force]** 默认安全重启;--force 立即重启",
|
|
2874
3450
|
"**/help** 显示本帮助",
|
|
2875
3451
|
],
|
|
2876
3452
|
},
|
|
@@ -2884,9 +3460,13 @@ export class ProxySessionController {
|
|
|
2884
3460
|
return agent;
|
|
2885
3461
|
}
|
|
2886
3462
|
|
|
3463
|
+
private agentLabel(agentName: string): string {
|
|
3464
|
+
return this.ensureAgent(agentName).title;
|
|
3465
|
+
}
|
|
3466
|
+
|
|
2887
3467
|
private isCodexSession(session: SessionRecord): boolean {
|
|
2888
3468
|
if (session.runtimeKind) return session.runtimeKind === "codex";
|
|
2889
|
-
return this.config.agents[session.agentName]?.kind === "
|
|
3469
|
+
return this.config.agents[session.agentName]?.kind === "app-server";
|
|
2890
3470
|
}
|
|
2891
3471
|
|
|
2892
3472
|
private currentSession(contextKey: string): SessionRecord | undefined {
|
|
@@ -2904,6 +3484,17 @@ export class ProxySessionController {
|
|
|
2904
3484
|
: current.cwd;
|
|
2905
3485
|
}
|
|
2906
3486
|
|
|
3487
|
+
private inheritedExecutionSettings(contextKey: string, agentName: string): SessionExecutionSettings {
|
|
3488
|
+
const current = this.currentSession(contextKey);
|
|
3489
|
+
if (!current || current.agentName !== agentName) return {};
|
|
3490
|
+
return {
|
|
3491
|
+
modelProvider: current.modelProvider,
|
|
3492
|
+
model: current.model,
|
|
3493
|
+
reasoningEffort: current.reasoningEffort,
|
|
3494
|
+
permissionMode: current.permissionMode,
|
|
3495
|
+
};
|
|
3496
|
+
}
|
|
3497
|
+
|
|
2907
3498
|
private currentProjectCwd(contextKey: string): string | undefined {
|
|
2908
3499
|
const current = this.currentSession(contextKey);
|
|
2909
3500
|
return current && !detectProjectlessWorkspace(current.cwd) ? current.cwd : undefined;
|
|
@@ -2969,7 +3560,9 @@ function statusRefreshAction(
|
|
|
2969
3560
|
}
|
|
2970
3561
|
|
|
2971
3562
|
interface UnifiedTaskListEntry {
|
|
3563
|
+
reference: string;
|
|
2972
3564
|
id: string;
|
|
3565
|
+
agentName: string;
|
|
2973
3566
|
title: string;
|
|
2974
3567
|
cwd: string;
|
|
2975
3568
|
status: string;
|
|
@@ -2981,17 +3574,17 @@ interface UnifiedTaskListEntry {
|
|
|
2981
3574
|
|
|
2982
3575
|
function mergeTaskList(
|
|
2983
3576
|
localSessions: SessionRecord[],
|
|
2984
|
-
remoteSessions:
|
|
3577
|
+
remoteSessions: AgentRemoteSessionSummary[],
|
|
2985
3578
|
currentLocalSessionId?: string,
|
|
2986
3579
|
): UnifiedTaskListEntry[] {
|
|
2987
3580
|
const localByRemoteId = new Map(
|
|
2988
3581
|
localSessions
|
|
2989
3582
|
.filter((session) => session.runtimeKind === "codex" && session.remoteSessionId)
|
|
2990
|
-
.map((session) => [session.remoteSessionId
|
|
3583
|
+
.map((session) => [agentRemoteKey(session.agentName, session.remoteSessionId!), session]),
|
|
2991
3584
|
);
|
|
2992
3585
|
const representedLocalIds = new Set<string>();
|
|
2993
|
-
const entries = remoteSessions.map((remote): UnifiedTaskListEntry => {
|
|
2994
|
-
const local = localByRemoteId.get(remote.id);
|
|
3586
|
+
const entries = remoteSessions.map(({ agentName, session: remote }): UnifiedTaskListEntry => {
|
|
3587
|
+
const local = localByRemoteId.get(agentRemoteKey(agentName, remote.id));
|
|
2995
3588
|
if (local) representedLocalIds.add(local.localSessionId);
|
|
2996
3589
|
const active = remote.status === "active" || remote.lastTurnStatus === "inProgress";
|
|
2997
3590
|
const status = remote.status === "active" || remote.lastTurnStatus === "inProgress"
|
|
@@ -2999,7 +3592,9 @@ function mergeTaskList(
|
|
|
2999
3592
|
: remoteSessionStatusLabel(remote.status);
|
|
3000
3593
|
const recencyAt = remote.recencyAt ?? remote.updatedAt;
|
|
3001
3594
|
return {
|
|
3595
|
+
reference: remoteSessionReference(agentName, remote.id),
|
|
3002
3596
|
id: remote.id,
|
|
3597
|
+
agentName,
|
|
3003
3598
|
title: remote.title ?? remote.preview ?? local?.title ?? "未命名任务",
|
|
3004
3599
|
cwd: remote.cwd || local?.cwd || "",
|
|
3005
3600
|
status,
|
|
@@ -3014,7 +3609,11 @@ function mergeTaskList(
|
|
|
3014
3609
|
if (representedLocalIds.has(local.localSessionId)) continue;
|
|
3015
3610
|
const updatedAt = Date.parse(local.updatedAt);
|
|
3016
3611
|
entries.push({
|
|
3612
|
+
reference: local.remoteSessionId
|
|
3613
|
+
? remoteSessionReference(local.agentName, local.remoteSessionId)
|
|
3614
|
+
: local.localSessionId,
|
|
3017
3615
|
id: local.remoteSessionId ?? local.localSessionId,
|
|
3616
|
+
agentName: local.agentName,
|
|
3018
3617
|
title: local.title ?? "未命名任务",
|
|
3019
3618
|
cwd: local.cwd,
|
|
3020
3619
|
status: sessionStatusLabel(local.status),
|
|
@@ -3027,6 +3626,32 @@ function mergeTaskList(
|
|
|
3027
3626
|
return entries;
|
|
3028
3627
|
}
|
|
3029
3628
|
|
|
3629
|
+
function agentRemoteKey(agentName: string, remoteSessionId: string): string {
|
|
3630
|
+
return `${agentName}\u0000${remoteSessionId}`;
|
|
3631
|
+
}
|
|
3632
|
+
|
|
3633
|
+
function remoteSessionReference(agentName: string, remoteSessionId: string): string {
|
|
3634
|
+
return `${REMOTE_SESSION_REFERENCE_PREFIX}${encodeURIComponent(agentName)}:${encodeURIComponent(remoteSessionId)}`;
|
|
3635
|
+
}
|
|
3636
|
+
|
|
3637
|
+
function parseRemoteSessionReference(reference: string): { agentName: string; remoteSessionId: string } | undefined {
|
|
3638
|
+
if (!reference.startsWith(REMOTE_SESSION_REFERENCE_PREFIX)) return undefined;
|
|
3639
|
+
const value = reference.slice(REMOTE_SESSION_REFERENCE_PREFIX.length);
|
|
3640
|
+
const separator = value.indexOf(":");
|
|
3641
|
+
if (separator <= 0 || separator === value.length - 1) return undefined;
|
|
3642
|
+
try {
|
|
3643
|
+
const agentName = decodeURIComponent(value.slice(0, separator));
|
|
3644
|
+
const remoteSessionId = decodeURIComponent(value.slice(separator + 1));
|
|
3645
|
+
return agentName && remoteSessionId ? { agentName, remoteSessionId } : undefined;
|
|
3646
|
+
} catch {
|
|
3647
|
+
return undefined;
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
|
|
3651
|
+
function runtimeErrorMessage(error: unknown): string {
|
|
3652
|
+
return error instanceof Error ? error.message : String(error);
|
|
3653
|
+
}
|
|
3654
|
+
|
|
3030
3655
|
function isMissingRolloutError(error: unknown): boolean {
|
|
3031
3656
|
const message = error instanceof Error ? error.message : String(error);
|
|
3032
3657
|
return /no rollout found|rollout[^\n]*(?:not found|missing)|thread\/resume failed/i.test(message);
|
|
@@ -3208,6 +3833,21 @@ function cardCode(value: string): string {
|
|
|
3208
3833
|
return `\`${value.replaceAll("`", "'")}\``;
|
|
3209
3834
|
}
|
|
3210
3835
|
|
|
3836
|
+
function permissionModeValue(value: unknown): PermissionMode {
|
|
3837
|
+
if (value === "auto" || value === "confirm") return value;
|
|
3838
|
+
throw new Error("权限模式只能是 auto 或 confirm。");
|
|
3839
|
+
}
|
|
3840
|
+
|
|
3841
|
+
function executionSettingsTabValue(value: unknown): ExecutionSettingsTab {
|
|
3842
|
+
if (value === "agent" || value === "provider" || value === "model" || value === "thinking" || value === "permission") return value;
|
|
3843
|
+
throw new Error("设置卡片的 tab 无效,请重新发送设置命令。");
|
|
3844
|
+
}
|
|
3845
|
+
|
|
3846
|
+
function requiredCardMessageId(value: string | undefined): string {
|
|
3847
|
+
if (value) return value;
|
|
3848
|
+
throw new Error("无法更新设置卡片,请重新发送设置命令。");
|
|
3849
|
+
}
|
|
3850
|
+
|
|
3211
3851
|
function remoteSessionStatusLabel(status: RemoteSessionSummary["status"]): string {
|
|
3212
3852
|
const labels: Record<RemoteSessionSummary["status"], string> = {
|
|
3213
3853
|
active: "执行中",
|
|
@@ -3243,6 +3883,10 @@ function mergeRemoteTaskStatus(record: SessionRecord, remote?: RemoteSessionSumm
|
|
|
3243
3883
|
...record,
|
|
3244
3884
|
title: remote.title ?? record.title,
|
|
3245
3885
|
cwd: remote.cwd || record.cwd,
|
|
3886
|
+
modelProvider: remote.modelProvider ?? record.modelProvider,
|
|
3887
|
+
model: remote.model ?? record.model,
|
|
3888
|
+
reasoningEffort: remote.reasoningEffort ?? record.reasoningEffort,
|
|
3889
|
+
permissionMode: remote.permissionMode ?? record.permissionMode,
|
|
3246
3890
|
status: isRemoteSessionActive(remote)
|
|
3247
3891
|
? "running"
|
|
3248
3892
|
: remote.lastTurnStatus
|
|
@@ -3299,9 +3943,7 @@ function isBotOwnedActiveTurn(record: SessionRecord, remote: RemoteSessionSummar
|
|
|
3299
3943
|
function isQueueIndependentCommand(command: Command): boolean {
|
|
3300
3944
|
if (["stop", "status", "restart", "help", "sessions", "goal", "nosteer"].includes(command.type)) return true;
|
|
3301
3945
|
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;
|
|
3946
|
+
if (["model", "provider", "thinking", "permissions"].includes(command.type)) return true;
|
|
3305
3947
|
return false;
|
|
3306
3948
|
}
|
|
3307
3949
|
|
|
@@ -3311,7 +3953,7 @@ function parseSessionVisibleCount(value: unknown): number {
|
|
|
3311
3953
|
}
|
|
3312
3954
|
|
|
3313
3955
|
function isPromptCommand(command: Command): boolean {
|
|
3314
|
-
return command.type === "
|
|
3956
|
+
return command.type === "prompt" || command.type === "nosteer";
|
|
3315
3957
|
}
|
|
3316
3958
|
|
|
3317
3959
|
function runtimePrompt(text: string, localImagePaths?: string[]): RuntimePrompt {
|
|
@@ -3332,8 +3974,8 @@ function formatNewGroupName(
|
|
|
3332
3974
|
includeTimestamp: boolean,
|
|
3333
3975
|
): string {
|
|
3334
3976
|
const prefix = `[${agentName}] `;
|
|
3335
|
-
const
|
|
3336
|
-
const titlePrefix = `${prefix}${
|
|
3977
|
+
const projectPrefix = projectCwd ? `${formatGroupProjectDirectory(projectCwd)} ` : "";
|
|
3978
|
+
const titlePrefix = `${prefix}${projectPrefix}`;
|
|
3337
3979
|
const timestampSuffix = includeTimestamp ? ` (${formatGroupDateSuffix(date)})` : "";
|
|
3338
3980
|
const availableTitleLength = FEISHU_GROUP_NAME_MAX_LENGTH
|
|
3339
3981
|
- Array.from(titlePrefix).length
|
|
@@ -3345,8 +3987,8 @@ function formatNewGroupName(
|
|
|
3345
3987
|
const FEISHU_GROUP_NAME_MAX_LENGTH = 60;
|
|
3346
3988
|
const GROUP_PROJECT_DIRECTORY_MAX_LENGTH = 15;
|
|
3347
3989
|
|
|
3348
|
-
function formatGroupProjectDirectory(projectCwd: string
|
|
3349
|
-
const value =
|
|
3990
|
+
function formatGroupProjectDirectory(projectCwd: string): string {
|
|
3991
|
+
const value = abbreviateHomeDirectory(projectCwd);
|
|
3350
3992
|
if (Array.from(value).length <= GROUP_PROJECT_DIRECTORY_MAX_LENGTH) return `[${value}]`;
|
|
3351
3993
|
|
|
3352
3994
|
const levels = value
|