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