@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
|
@@ -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)
|
|
@@ -373,7 +438,7 @@ export class ProxySessionController {
|
|
|
373
438
|
?? { ...record, contextKey: responseContextKey };
|
|
374
439
|
const previous = this.messageQueues.get(responseContextKey) ?? Promise.resolve();
|
|
375
440
|
const next = previous.catch(() => undefined).then(async () => {
|
|
376
|
-
this.outbound.registerSession(scopedRecord.localSessionId, responseContextKey, scopedRecord.title, scopedRecord.cwd);
|
|
441
|
+
this.outbound.registerSession(scopedRecord.localSessionId, responseContextKey, scopedRecord.title, scopedRecord.cwd, this.agentLabel(scopedRecord.agentName));
|
|
377
442
|
const promptMessageId = await this.outbound.withReplyTarget(responseContextKey, existingReplyTarget, () => this.outbound.sendText(responseContextKey, promptText));
|
|
378
443
|
const turnReplyTarget = isThreadContextKey(responseContextKey)
|
|
379
444
|
? promptMessageId
|
|
@@ -396,13 +461,64 @@ export class ProxySessionController {
|
|
|
396
461
|
}
|
|
397
462
|
return `The Prompt was posted to the original chat and submitted to the task: ${record.title ?? record.remoteSessionId ?? record.localSessionId}`;
|
|
398
463
|
}
|
|
464
|
+
async controlCreateTaskGroup(localSessionId, requestedTitle, userOpenId, requestedProjectCwd, forceProjectless = false, requestedAgentName) {
|
|
465
|
+
const source = this.store.getSession(localSessionId);
|
|
466
|
+
if (!source || source.status === "closed")
|
|
467
|
+
throw new Error(`Task not found: ${localSessionId}`);
|
|
468
|
+
const agentName = requestedAgentName?.trim() || source.agentName;
|
|
469
|
+
const agent = this.ensureAgent(agentName);
|
|
470
|
+
if (forceProjectless && agent.kind !== "app-server") {
|
|
471
|
+
throw new Error("task newgroup --nodir is only available for Codex agents.");
|
|
472
|
+
}
|
|
473
|
+
const sourceContextKey = this.outbound.getSessionContextKey(localSessionId) ?? source.contextKey;
|
|
474
|
+
const replyTarget = this.outbound.getSessionReplyTarget(localSessionId);
|
|
475
|
+
const boundProjectCwd = forceProjectless
|
|
476
|
+
? undefined
|
|
477
|
+
: requestedProjectCwd === undefined
|
|
478
|
+
? detectProjectlessWorkspace(source.cwd) ? undefined : source.cwd
|
|
479
|
+
: resolveUserPath(requestedProjectCwd);
|
|
480
|
+
const created = await this.outbound.withReplyTarget(sourceContextKey, replyTarget, () => this.createFeishuGroupWithTask(sourceContextKey, agentName, requestedTitle, userOpenId, boundProjectCwd, source.agentName === agentName
|
|
481
|
+
? {
|
|
482
|
+
modelProvider: source.modelProvider,
|
|
483
|
+
model: source.model,
|
|
484
|
+
reasoningEffort: source.reasoningEffort,
|
|
485
|
+
permissionMode: source.permissionMode,
|
|
486
|
+
}
|
|
487
|
+
: {}));
|
|
488
|
+
return {
|
|
489
|
+
sourceLocalSessionId: source.localSessionId,
|
|
490
|
+
group: created.group,
|
|
491
|
+
task: created.task,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
async controlForkTaskGroup(localSessionId, requestedTitle, userOpenId) {
|
|
495
|
+
const source = this.store.getSession(localSessionId);
|
|
496
|
+
if (!source || source.status === "closed")
|
|
497
|
+
throw new Error(`Task not found: ${localSessionId}`);
|
|
498
|
+
const sourceContextKey = this.outbound.getSessionContextKey(localSessionId) ?? source.contextKey;
|
|
499
|
+
const replyTarget = this.outbound.getSessionReplyTarget(localSessionId);
|
|
500
|
+
const plan = await this.prepareForkSession(sourceContextKey, localSessionId, requestedTitle);
|
|
501
|
+
const prepared = {
|
|
502
|
+
plan,
|
|
503
|
+
sourceDescription: plan.forkedFromHistoricalTurn
|
|
504
|
+
? "当前任务最近已完成轮次"
|
|
505
|
+
: "当前任务最新轮次",
|
|
506
|
+
};
|
|
507
|
+
const forked = await this.outbound.withReplyTarget(sourceContextKey, replyTarget, () => this.forkPreparedSessionToFeishuGroup(sourceContextKey, prepared, userOpenId ?? "", "当前任务"));
|
|
508
|
+
return {
|
|
509
|
+
sourceLocalSessionId: source.localSessionId,
|
|
510
|
+
sourceTurnId: plan.lastTurnId,
|
|
511
|
+
group: forked.group,
|
|
512
|
+
task: forked.task,
|
|
513
|
+
};
|
|
514
|
+
}
|
|
399
515
|
cardActionContextKey(action) {
|
|
400
516
|
const explicit = typeof action.value.contextKey === "string" ? action.value.contextKey : undefined;
|
|
401
517
|
if (explicit && baseChatContextKey(explicit) === baseChatContextKey(action.contextKey))
|
|
402
518
|
return explicit;
|
|
403
519
|
const sessionReference = typeof action.value.sessionId === "string" ? action.value.sessionId : undefined;
|
|
404
520
|
const session = sessionReference
|
|
405
|
-
? this.store.getSession(sessionReference) ?? this.
|
|
521
|
+
? this.store.getSession(sessionReference) ?? this.findStoredSessionByReference(sessionReference)
|
|
406
522
|
: undefined;
|
|
407
523
|
if (session && baseChatContextKey(session.contextKey) === baseChatContextKey(action.contextKey)) {
|
|
408
524
|
return session.contextKey;
|
|
@@ -421,13 +537,20 @@ export class ProxySessionController {
|
|
|
421
537
|
await this.runShellCommand(contextKey, command.command);
|
|
422
538
|
return;
|
|
423
539
|
case "new":
|
|
424
|
-
if (command.projectless && this.ensureAgent(context.defaultAgent).kind !== "
|
|
425
|
-
throw new Error("/new --nodir 仅支持
|
|
540
|
+
if (command.projectless && this.ensureAgent(context.defaultAgent).kind !== "app-server") {
|
|
541
|
+
throw new Error("/new --nodir 仅支持 App Server Agent。");
|
|
426
542
|
}
|
|
427
|
-
await this.createSession(contextKey, context.defaultAgent, command.projectless
|
|
543
|
+
await this.createSession(contextKey, context.defaultAgent, command.projectless
|
|
544
|
+
? undefined
|
|
545
|
+
: command.cwd === undefined
|
|
546
|
+
? this.inheritedNewTaskCwd(contextKey)
|
|
547
|
+
: resolveUserPath(command.cwd), true, false, undefined, undefined, command.title, this.inheritedExecutionSettings(contextKey, context.defaultAgent));
|
|
428
548
|
return;
|
|
429
549
|
case "newgroup":
|
|
430
|
-
|
|
550
|
+
if (command.projectless && this.ensureAgent(context.defaultAgent).kind !== "app-server") {
|
|
551
|
+
throw new Error("/newgroup --nodir 仅支持 App Server Agent。");
|
|
552
|
+
}
|
|
553
|
+
await this.createFeishuGroup(contextKey, context.defaultAgent, command.title, userId, command.cwd === undefined ? undefined : resolveUserPath(command.cwd), command.projectless === true);
|
|
431
554
|
return;
|
|
432
555
|
case "forkgroup":
|
|
433
556
|
await this.forkCurrentSessionToFeishuGroup(contextKey, command.title, userId, incomingMessage);
|
|
@@ -438,7 +561,6 @@ export class ProxySessionController {
|
|
|
438
561
|
case "title":
|
|
439
562
|
await this.setTitle(contextKey, command.title);
|
|
440
563
|
return;
|
|
441
|
-
case "ask":
|
|
442
564
|
case "prompt":
|
|
443
565
|
await this.prompt(contextKey, command.text, messageId, replyTarget, localImagePaths);
|
|
444
566
|
return;
|
|
@@ -455,11 +577,7 @@ export class ProxySessionController {
|
|
|
455
577
|
if (command.agent)
|
|
456
578
|
await this.setDefaultAgent(contextKey, command.agent);
|
|
457
579
|
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);
|
|
580
|
+
await this.openAgentSettings(contextKey);
|
|
463
581
|
return;
|
|
464
582
|
case "status":
|
|
465
583
|
await this.status(contextKey, command.sessionId);
|
|
@@ -470,20 +588,19 @@ export class ProxySessionController {
|
|
|
470
588
|
case "restart":
|
|
471
589
|
if (!this.lifecycle)
|
|
472
590
|
throw new Error("当前运行方式不支持自动重启。");
|
|
473
|
-
await this.lifecycle.restart(contextKey);
|
|
591
|
+
await this.lifecycle.restart(contextKey, command.force === true);
|
|
474
592
|
return;
|
|
475
593
|
case "model":
|
|
476
|
-
await this.
|
|
594
|
+
await this.openExecutionSettings(contextKey, "model");
|
|
595
|
+
return;
|
|
596
|
+
case "provider":
|
|
597
|
+
await this.openProviderSettings(contextKey);
|
|
477
598
|
return;
|
|
478
599
|
case "thinking":
|
|
479
|
-
await this.
|
|
600
|
+
await this.openExecutionSettings(contextKey, "thinking");
|
|
480
601
|
return;
|
|
481
602
|
case "permissions":
|
|
482
|
-
await this.
|
|
483
|
-
return;
|
|
484
|
-
case "modes":
|
|
485
|
-
case "mode":
|
|
486
|
-
await this.outbound.sendText(contextKey, "当前统一运行时不再暴露 ACP modes;Codex 请使用 /model 和 /permissions。");
|
|
603
|
+
await this.openExecutionSettings(contextKey, "permission");
|
|
487
604
|
return;
|
|
488
605
|
case "help":
|
|
489
606
|
await this.help(contextKey);
|
|
@@ -503,7 +620,7 @@ export class ProxySessionController {
|
|
|
503
620
|
const turnContextKey = session.lastTurnId
|
|
504
621
|
? this.store.getTurnContextKey(session.lastTurnId) ?? context.contextKey
|
|
505
622
|
: context.contextKey;
|
|
506
|
-
this.outbound.registerSession(session.localSessionId, turnContextKey, session.title, session.cwd);
|
|
623
|
+
this.outbound.registerSession(session.localSessionId, turnContextKey, session.title, session.cwd, this.agentLabel(session.agentName));
|
|
507
624
|
if (!session.lastTurnId)
|
|
508
625
|
continue;
|
|
509
626
|
void this.outbound.resumeDelivery(session.localSessionId, turnContextKey, session.lastTurnId).catch((error) => {
|
|
@@ -546,9 +663,9 @@ export class ProxySessionController {
|
|
|
546
663
|
await this.promptSession(record, contextKey, text, messageId, replyTarget, localImagePaths);
|
|
547
664
|
}
|
|
548
665
|
async promptSession(record, contextKey, text, messageId, replyTarget, localImagePaths) {
|
|
549
|
-
const configuredRuntime = this.runtimes.forAgent(
|
|
666
|
+
const configuredRuntime = this.runtimes.forAgent(record.agentName);
|
|
550
667
|
if (!configuredRuntime.getSession(record.localSessionId)) {
|
|
551
|
-
this.outbound.registerSession(record.localSessionId, contextKey, record.title, record.cwd);
|
|
668
|
+
this.outbound.registerSession(record.localSessionId, contextKey, record.title, record.cwd, this.agentLabel(record.agentName));
|
|
552
669
|
await this.outbound.startPendingTurn(record.localSessionId, contextKey, record.title, replyTarget, text);
|
|
553
670
|
}
|
|
554
671
|
let loaded;
|
|
@@ -760,7 +877,7 @@ export class ProxySessionController {
|
|
|
760
877
|
async forkThreadSession(message) {
|
|
761
878
|
const { anchor, source, snapshot } = this.resolveThreadForkAnchor(message);
|
|
762
879
|
const agent = this.ensureAgent(source.agentName);
|
|
763
|
-
const runtime = this.runtimes.forAgent(
|
|
880
|
+
const runtime = this.runtimes.forAgent(source.agentName);
|
|
764
881
|
if (runtime.kind !== "codex" || !runtime.forkSession) {
|
|
765
882
|
throw new Error("当前 Codex 运行时不支持 fork 任务。");
|
|
766
883
|
}
|
|
@@ -779,12 +896,13 @@ export class ProxySessionController {
|
|
|
779
896
|
this.store.updateRuntimeSession(localSessionId, {
|
|
780
897
|
runtimeKind: "codex",
|
|
781
898
|
title: forkTitle,
|
|
899
|
+
modelProvider: source.modelProvider,
|
|
782
900
|
model: source.model,
|
|
783
901
|
reasoningEffort: source.reasoningEffort,
|
|
784
902
|
permissionMode: source.permissionMode ?? "auto",
|
|
785
903
|
});
|
|
786
904
|
this.store.setCurrentSession(message.contextKey, localSessionId);
|
|
787
|
-
this.outbound.registerSession(localSessionId, message.contextKey, forkTitle, source.cwd);
|
|
905
|
+
this.outbound.registerSession(localSessionId, message.contextKey, forkTitle, source.cwd, this.agentLabel(source.agentName));
|
|
788
906
|
try {
|
|
789
907
|
const forked = await runtime.forkSession({
|
|
790
908
|
localSessionId,
|
|
@@ -793,6 +911,7 @@ export class ProxySessionController {
|
|
|
793
911
|
agentName: source.agentName,
|
|
794
912
|
cwd: source.cwd,
|
|
795
913
|
title: forkTitle,
|
|
914
|
+
modelProvider: source.modelProvider,
|
|
796
915
|
model: source.model,
|
|
797
916
|
reasoningEffort: source.reasoningEffort,
|
|
798
917
|
permissionMode: source.permissionMode ?? "auto",
|
|
@@ -869,7 +988,7 @@ export class ProxySessionController {
|
|
|
869
988
|
if (direct?.status === "closed") {
|
|
870
989
|
throw new Error(`找不到任务:${taskId}`);
|
|
871
990
|
}
|
|
872
|
-
const global = direct ?? this.
|
|
991
|
+
const global = direct ?? this.findStoredSessionByReference(taskId);
|
|
873
992
|
source = global ? { ...global, contextKey } : undefined;
|
|
874
993
|
}
|
|
875
994
|
if (source && (!source.remoteSessionId || !this.isCodexSession(source))) {
|
|
@@ -877,27 +996,29 @@ export class ProxySessionController {
|
|
|
877
996
|
}
|
|
878
997
|
let runtime;
|
|
879
998
|
let agentName;
|
|
999
|
+
let remote;
|
|
880
1000
|
if (source) {
|
|
881
1001
|
agentName = source.agentName;
|
|
882
|
-
runtime = this.runtimes.forAgent(
|
|
1002
|
+
runtime = this.runtimes.forAgent(agentName);
|
|
883
1003
|
}
|
|
884
1004
|
else {
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
runtime =
|
|
1005
|
+
if (!taskId)
|
|
1006
|
+
throw new Error("缺少要 fork 的 Codex 任务 ID。");
|
|
1007
|
+
const resolved = await this.resolveRemoteCodexSession(taskId);
|
|
1008
|
+
agentName = resolved.agentName;
|
|
1009
|
+
runtime = resolved.runtime;
|
|
1010
|
+
remote = resolved.remote;
|
|
890
1011
|
}
|
|
891
1012
|
if (runtime.kind !== "codex" || !runtime.forkSession) {
|
|
892
1013
|
throw new Error("当前 Codex 运行时不支持 fork 任务。");
|
|
893
1014
|
}
|
|
894
|
-
const remoteSessionId = source?.remoteSessionId ?? taskId;
|
|
1015
|
+
const remoteSessionId = source?.remoteSessionId ?? remote?.id ?? taskId;
|
|
895
1016
|
if (!remoteSessionId)
|
|
896
1017
|
throw new Error("当前任务尚未创建 Codex 任务 ID,暂时不能 fork。");
|
|
897
1018
|
if (!runtime.readRemoteSession && !source) {
|
|
898
1019
|
throw new Error("当前 Codex 运行时不支持读取指定任务。");
|
|
899
1020
|
}
|
|
900
|
-
|
|
1021
|
+
remote ??= runtime.readRemoteSession
|
|
901
1022
|
? await runtime.readRemoteSession(remoteSessionId)
|
|
902
1023
|
: undefined;
|
|
903
1024
|
const latestTurnId = remote?.lastTurnId ?? source?.lastTurnId;
|
|
@@ -921,7 +1042,8 @@ export class ProxySessionController {
|
|
|
921
1042
|
throw new Error("指定的 Codex 任务没有可用的工作目录,暂时不能 fork。");
|
|
922
1043
|
const sourceTitle = remote?.title ?? source?.title ?? remote?.preview;
|
|
923
1044
|
const forkTitle = normalizeTaskTitle(requestedTitle) ?? this.store.nextForkTitle(sourceTitle);
|
|
924
|
-
const
|
|
1045
|
+
const modelProvider = remote?.modelProvider ?? source?.modelProvider;
|
|
1046
|
+
const model = remote?.model ?? source?.model;
|
|
925
1047
|
const reasoningEffort = source?.reasoningEffort;
|
|
926
1048
|
const permissionMode = source?.permissionMode ?? "auto";
|
|
927
1049
|
return {
|
|
@@ -933,6 +1055,7 @@ export class ProxySessionController {
|
|
|
933
1055
|
lastTurnId,
|
|
934
1056
|
cwd,
|
|
935
1057
|
forkTitle,
|
|
1058
|
+
modelProvider,
|
|
936
1059
|
model,
|
|
937
1060
|
reasoningEffort,
|
|
938
1061
|
permissionMode,
|
|
@@ -975,7 +1098,7 @@ export class ProxySessionController {
|
|
|
975
1098
|
if (!source.remoteSessionId || !this.isCodexSession(source)) {
|
|
976
1099
|
throw new Error(`${sourceLabel}不是可 fork 的 Codex 任务。`);
|
|
977
1100
|
}
|
|
978
|
-
const runtime = this.runtimes.forAgent(
|
|
1101
|
+
const runtime = this.runtimes.forAgent(source.agentName);
|
|
979
1102
|
if (runtime.kind !== "codex" || !runtime.forkSession) {
|
|
980
1103
|
throw new Error("当前 Codex 运行时不支持 fork 任务。");
|
|
981
1104
|
}
|
|
@@ -996,6 +1119,7 @@ export class ProxySessionController {
|
|
|
996
1119
|
lastTurnId,
|
|
997
1120
|
cwd: source.cwd,
|
|
998
1121
|
forkTitle,
|
|
1122
|
+
modelProvider: source.modelProvider,
|
|
999
1123
|
model: source.model,
|
|
1000
1124
|
reasoningEffort: source.reasoningEffort,
|
|
1001
1125
|
permissionMode: source.permissionMode ?? "auto",
|
|
@@ -1018,6 +1142,7 @@ export class ProxySessionController {
|
|
|
1018
1142
|
this.store.updateRuntimeSession(localSessionId, {
|
|
1019
1143
|
runtimeKind: "codex",
|
|
1020
1144
|
title: plan.forkTitle,
|
|
1145
|
+
modelProvider: plan.modelProvider,
|
|
1021
1146
|
model: plan.model,
|
|
1022
1147
|
reasoningEffort: plan.reasoningEffort,
|
|
1023
1148
|
permissionMode: plan.permissionMode,
|
|
@@ -1030,6 +1155,7 @@ export class ProxySessionController {
|
|
|
1030
1155
|
agentName: plan.agentName,
|
|
1031
1156
|
cwd: plan.cwd,
|
|
1032
1157
|
title: plan.forkTitle,
|
|
1158
|
+
modelProvider: plan.modelProvider,
|
|
1033
1159
|
model: plan.model,
|
|
1034
1160
|
reasoningEffort: plan.reasoningEffort,
|
|
1035
1161
|
permissionMode: plan.permissionMode,
|
|
@@ -1040,7 +1166,7 @@ export class ProxySessionController {
|
|
|
1040
1166
|
lastTurnStatus: plan.lastTurnStatus,
|
|
1041
1167
|
});
|
|
1042
1168
|
this.store.setCurrentSession(contextKey, localSessionId);
|
|
1043
|
-
this.outbound.registerSession(localSessionId, contextKey, forked.title ?? plan.forkTitle, plan.cwd);
|
|
1169
|
+
this.outbound.registerSession(localSessionId, contextKey, forked.title ?? plan.forkTitle, plan.cwd, this.agentLabel(plan.agentName));
|
|
1044
1170
|
this.store.audit(contextKey, "session_forked", {
|
|
1045
1171
|
sourceLocalSessionId: plan.source?.localSessionId,
|
|
1046
1172
|
sourceRemoteSessionId: plan.remoteSessionId,
|
|
@@ -1077,26 +1203,25 @@ export class ProxySessionController {
|
|
|
1077
1203
|
if (direct?.status === "closed") {
|
|
1078
1204
|
throw new Error(`找不到任务:${taskId}`);
|
|
1079
1205
|
}
|
|
1080
|
-
const source = direct ?? this.
|
|
1206
|
+
const source = direct ?? this.findStoredSessionByReference(taskId);
|
|
1081
1207
|
if (source && !this.isCodexSession(source)) {
|
|
1082
1208
|
throw new Error("指定任务不是 Codex 任务,暂时不能按项目创建新任务。");
|
|
1083
1209
|
}
|
|
1084
1210
|
let agentName;
|
|
1085
1211
|
let runtime;
|
|
1212
|
+
let remote;
|
|
1086
1213
|
if (source) {
|
|
1087
1214
|
agentName = source.agentName;
|
|
1088
|
-
runtime = this.runtimes.forAgent(
|
|
1215
|
+
runtime = this.runtimes.forAgent(agentName);
|
|
1089
1216
|
}
|
|
1090
1217
|
else {
|
|
1091
|
-
const
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
runtime = this.runtimes.get("codex");
|
|
1218
|
+
const resolved = await this.resolveRemoteCodexSession(taskId);
|
|
1219
|
+
agentName = resolved.agentName;
|
|
1220
|
+
runtime = resolved.runtime;
|
|
1221
|
+
remote = resolved.remote;
|
|
1096
1222
|
}
|
|
1097
|
-
const remoteSessionId = source?.remoteSessionId ?? taskId;
|
|
1098
|
-
|
|
1099
|
-
if (runtime.readRemoteSession && remoteSessionId) {
|
|
1223
|
+
const remoteSessionId = source?.remoteSessionId ?? remote?.id ?? taskId;
|
|
1224
|
+
if (!remote && runtime.readRemoteSession && remoteSessionId) {
|
|
1100
1225
|
try {
|
|
1101
1226
|
remote = await runtime.readRemoteSession(remoteSessionId);
|
|
1102
1227
|
}
|
|
@@ -1111,6 +1236,7 @@ export class ProxySessionController {
|
|
|
1111
1236
|
throw new Error("指定的 Codex 任务没有可用的工作目录,暂时不能按项目创建新任务。");
|
|
1112
1237
|
}
|
|
1113
1238
|
const executionSettings = {
|
|
1239
|
+
modelProvider: remote?.modelProvider ?? source?.modelProvider,
|
|
1114
1240
|
model: remote?.model ?? source?.model,
|
|
1115
1241
|
reasoningEffort: remote?.reasoningEffort ?? source?.reasoningEffort,
|
|
1116
1242
|
permissionMode: remote?.permissionMode ?? source?.permissionMode ?? "auto",
|
|
@@ -1152,21 +1278,22 @@ export class ProxySessionController {
|
|
|
1152
1278
|
const agent = this.ensureAgent(agentName);
|
|
1153
1279
|
const localSessionId = createId("sess");
|
|
1154
1280
|
const initialTitle = normalizeTaskTitle(requestedTitle ?? prompt ?? "");
|
|
1155
|
-
const sessionCwd = cwd === undefined && agent.kind === "
|
|
1281
|
+
const sessionCwd = cwd === undefined && agent.kind === "app-server"
|
|
1156
1282
|
? createProjectlessWorkspace({ prompt: initialTitle }).cwd
|
|
1157
1283
|
: path.resolve(cwd ?? this.config.defaults.cwd);
|
|
1158
1284
|
const record = this.store.createSession({ localSessionId, contextKey, agentName, cwd: sessionCwd, status: "starting" });
|
|
1159
|
-
if (initialTitle || executionSettings.model || executionSettings.reasoningEffort || executionSettings.permissionMode) {
|
|
1285
|
+
if (initialTitle || executionSettings.modelProvider || executionSettings.model || executionSettings.reasoningEffort || executionSettings.permissionMode) {
|
|
1160
1286
|
this.store.updateRuntimeSession(localSessionId, {
|
|
1161
1287
|
title: initialTitle,
|
|
1288
|
+
modelProvider: executionSettings.modelProvider,
|
|
1162
1289
|
model: executionSettings.model,
|
|
1163
1290
|
reasoningEffort: executionSettings.reasoningEffort,
|
|
1164
1291
|
permissionMode: executionSettings.permissionMode,
|
|
1165
1292
|
});
|
|
1166
1293
|
}
|
|
1167
1294
|
this.store.setCurrentSession(contextKey, localSessionId);
|
|
1168
|
-
this.outbound.registerSession(localSessionId, contextKey, initialTitle, sessionCwd);
|
|
1169
|
-
const runtime = this.runtimes.forAgent(
|
|
1295
|
+
this.outbound.registerSession(localSessionId, contextKey, initialTitle, sessionCwd, this.agentLabel(agentName));
|
|
1296
|
+
const runtime = this.runtimes.forAgent(agentName);
|
|
1170
1297
|
try {
|
|
1171
1298
|
if (prepareTurn) {
|
|
1172
1299
|
await this.outbound.startPendingTurn(localSessionId, contextKey, initialTitle, replyTarget, prompt);
|
|
@@ -1176,6 +1303,7 @@ export class ProxySessionController {
|
|
|
1176
1303
|
agentName,
|
|
1177
1304
|
cwd: sessionCwd,
|
|
1178
1305
|
title: initialTitle,
|
|
1306
|
+
modelProvider: executionSettings.modelProvider,
|
|
1179
1307
|
model: executionSettings.model,
|
|
1180
1308
|
reasoningEffort: executionSettings.reasoningEffort,
|
|
1181
1309
|
permissionMode: executionSettings.permissionMode ?? "auto",
|
|
@@ -1196,11 +1324,14 @@ export class ProxySessionController {
|
|
|
1196
1324
|
throw error;
|
|
1197
1325
|
}
|
|
1198
1326
|
}
|
|
1199
|
-
async createFeishuGroup(sourceContextKey, agentName, requestedTitle, userId) {
|
|
1327
|
+
async createFeishuGroup(sourceContextKey, agentName, requestedTitle, userId, requestedProjectCwd, forceProjectless = false) {
|
|
1200
1328
|
const source = this.currentSession(sourceContextKey);
|
|
1201
|
-
const boundProjectCwd =
|
|
1329
|
+
const boundProjectCwd = forceProjectless
|
|
1330
|
+
? undefined
|
|
1331
|
+
: requestedProjectCwd ?? this.currentProjectCwd(sourceContextKey);
|
|
1202
1332
|
const executionSettings = source?.agentName === agentName
|
|
1203
1333
|
? {
|
|
1334
|
+
modelProvider: source.modelProvider,
|
|
1204
1335
|
model: source.model,
|
|
1205
1336
|
reasoningEffort: source.reasoningEffort,
|
|
1206
1337
|
permissionMode: source.permissionMode,
|
|
@@ -1215,8 +1346,8 @@ export class ProxySessionController {
|
|
|
1215
1346
|
}
|
|
1216
1347
|
async createFeishuGroupWithTask(sourceContextKey, agentName, requestedTitle, userId, boundProjectCwd, executionSettings) {
|
|
1217
1348
|
const explicitTitle = normalizeTaskTitle(requestedTitle);
|
|
1218
|
-
const taskTitle = explicitTitle ??
|
|
1219
|
-
const group = await this.createFeishuGroupContext(sourceContextKey, agentName, taskTitle, userId, boundProjectCwd, "/newgroup",
|
|
1349
|
+
const taskTitle = explicitTitle ?? `新任务 (${formatGroupDateSuffix(new Date())})`;
|
|
1350
|
+
const group = await this.createFeishuGroupContext(sourceContextKey, agentName, taskTitle, userId, boundProjectCwd, "/newgroup", false);
|
|
1220
1351
|
let task;
|
|
1221
1352
|
try {
|
|
1222
1353
|
task = await this.createSession(group.contextKey, agentName, boundProjectCwd, false, false, undefined, undefined, taskTitle, executionSettings);
|
|
@@ -1232,10 +1363,13 @@ export class ProxySessionController {
|
|
|
1232
1363
|
"群和新任务已创建。",
|
|
1233
1364
|
`当前任务:${taskDescription}`,
|
|
1234
1365
|
`当前 Project 目录:${boundProjectCwd ?? "未绑定(Projectless)"}`,
|
|
1366
|
+
`当前 Provider:${task.modelProvider ?? "Codex 默认"}`,
|
|
1235
1367
|
`当前模型:${task.model ?? "默认"}`,
|
|
1236
1368
|
`思考强度:${task.reasoningEffort ?? "自动"}`,
|
|
1369
|
+
`权限类型:${task.permissionMode === "confirm" ? "执行前确认" : "自动执行"}`,
|
|
1237
1370
|
].join("\n"));
|
|
1238
1371
|
await this.outbound.sendText(sourceContextKey, `已创建飞书群:${group.name},并创建新任务 ${taskDescription}。`);
|
|
1372
|
+
return { group, task };
|
|
1239
1373
|
}
|
|
1240
1374
|
async forkCurrentSessionToFeishuGroup(sourceContextKey, requestedTitle, userId, incomingMessage) {
|
|
1241
1375
|
if (!userId?.startsWith("ou_")) {
|
|
@@ -1275,11 +1409,13 @@ export class ProxySessionController {
|
|
|
1275
1409
|
`已从${prepared.sourceDescription}创建分支。`,
|
|
1276
1410
|
`当前任务:${taskDescription}`,
|
|
1277
1411
|
`当前 Project 目录:${boundProjectCwd ?? "未绑定(Projectless)"}`,
|
|
1412
|
+
`当前 Provider:${forked.session.modelProvider ?? "Codex 默认"}`,
|
|
1278
1413
|
`当前模型:${forked.session.model ?? "默认"}`,
|
|
1279
1414
|
`思考强度:${forked.session.reasoningEffort ?? "自动"}`,
|
|
1280
1415
|
`权限类型:${forked.session.permissionMode === "confirm" ? "执行前确认" : "自动执行"}`,
|
|
1281
1416
|
].join("\n"));
|
|
1282
1417
|
await this.outbound.sendText(sourceContextKey, `已将${sourceSummary} Fork 到飞书群:${group.name};新群当前任务为 ${taskDescription}。`);
|
|
1418
|
+
return { group, task: forked.record };
|
|
1283
1419
|
}
|
|
1284
1420
|
async createFeishuGroupContext(sourceContextKey, agentName, taskTitle, userId, boundProjectCwd, commandName, includeTimestamp) {
|
|
1285
1421
|
if (!userId?.startsWith("ou_")) {
|
|
@@ -1296,18 +1432,18 @@ export class ProxySessionController {
|
|
|
1296
1432
|
this.store.getOrCreateUserContext(groupContextKey, agentName);
|
|
1297
1433
|
if (boundProjectCwd)
|
|
1298
1434
|
this.store.setBoundProjectCwd(groupContextKey, boundProjectCwd);
|
|
1299
|
-
return { contextKey: groupContextKey, name: group.name };
|
|
1435
|
+
return { chatId: group.chatId, contextKey: groupContextKey, name: group.name };
|
|
1300
1436
|
}
|
|
1301
1437
|
async loadSession(record) {
|
|
1302
1438
|
const agent = this.ensureAgent(record.agentName);
|
|
1303
|
-
const runtime = this.runtimes.forAgent(
|
|
1439
|
+
const runtime = this.runtimes.forAgent(record.agentName);
|
|
1304
1440
|
const existing = runtime.getSession(record.localSessionId);
|
|
1305
1441
|
if (existing)
|
|
1306
1442
|
return { record, runtime, session: existing };
|
|
1307
1443
|
const pending = this.sessionLoads.get(record.localSessionId);
|
|
1308
1444
|
if (pending)
|
|
1309
1445
|
return pending;
|
|
1310
|
-
this.outbound.registerSession(record.localSessionId, record.contextKey, record.title, record.cwd);
|
|
1446
|
+
this.outbound.registerSession(record.localSessionId, record.contextKey, record.title, record.cwd, this.agentLabel(record.agentName));
|
|
1311
1447
|
const loading = (async () => {
|
|
1312
1448
|
if (record.lastTurnId) {
|
|
1313
1449
|
try {
|
|
@@ -1329,10 +1465,11 @@ export class ProxySessionController {
|
|
|
1329
1465
|
agentName: record.agentName,
|
|
1330
1466
|
cwd: record.cwd,
|
|
1331
1467
|
title: record.title,
|
|
1468
|
+
modelProvider: record.modelProvider,
|
|
1332
1469
|
model: record.model,
|
|
1333
1470
|
reasoningEffort: record.reasoningEffort,
|
|
1334
1471
|
permissionMode,
|
|
1335
|
-
activeTurnId: agent.kind === "
|
|
1472
|
+
activeTurnId: agent.kind === "app-server" && record.status === "running" && record.lastTurnStatus === "running"
|
|
1336
1473
|
? record.lastTurnId
|
|
1337
1474
|
: undefined,
|
|
1338
1475
|
lastTurnId: record.lastTurnId,
|
|
@@ -1340,7 +1477,7 @@ export class ProxySessionController {
|
|
|
1340
1477
|
});
|
|
1341
1478
|
}
|
|
1342
1479
|
catch (error) {
|
|
1343
|
-
if (!(agent.kind === "
|
|
1480
|
+
if (!(agent.kind === "app-server" && !record.lastTurnId && isMissingRolloutError(error)))
|
|
1344
1481
|
throw error;
|
|
1345
1482
|
this.logger.warn({ error, sessionId: record.localSessionId }, "Codex task has no rollout; creating a replacement task.");
|
|
1346
1483
|
session = await runtime.createSession({
|
|
@@ -1348,6 +1485,7 @@ export class ProxySessionController {
|
|
|
1348
1485
|
agentName: record.agentName,
|
|
1349
1486
|
cwd: record.cwd,
|
|
1350
1487
|
title: record.title,
|
|
1488
|
+
modelProvider: record.modelProvider,
|
|
1351
1489
|
model: record.model,
|
|
1352
1490
|
reasoningEffort: record.reasoningEffort,
|
|
1353
1491
|
permissionMode,
|
|
@@ -1360,6 +1498,7 @@ export class ProxySessionController {
|
|
|
1360
1498
|
agentName: record.agentName,
|
|
1361
1499
|
cwd: record.cwd,
|
|
1362
1500
|
title: record.title,
|
|
1501
|
+
modelProvider: record.modelProvider,
|
|
1363
1502
|
model: record.model,
|
|
1364
1503
|
reasoningEffort: record.reasoningEffort,
|
|
1365
1504
|
permissionMode,
|
|
@@ -1382,6 +1521,7 @@ export class ProxySessionController {
|
|
|
1382
1521
|
runtimeKind: session.runtimeKind,
|
|
1383
1522
|
remoteSessionId: session.remoteSessionId,
|
|
1384
1523
|
title: session.title,
|
|
1524
|
+
modelProvider: session.modelProvider,
|
|
1385
1525
|
model: session.model,
|
|
1386
1526
|
reasoningEffort: session.reasoningEffort,
|
|
1387
1527
|
permissionMode: session.permissionMode,
|
|
@@ -1407,38 +1547,36 @@ export class ProxySessionController {
|
|
|
1407
1547
|
lastTurnStatus: event.type === "turn_completed" ? "completed" : event.type === "turn_cancelled" ? "cancelled" : "failed",
|
|
1408
1548
|
});
|
|
1409
1549
|
}
|
|
1550
|
+
let presentationError;
|
|
1410
1551
|
try {
|
|
1411
1552
|
await this.outbound.onEvent(event);
|
|
1412
|
-
if (event.type === "turn_completed" || event.type === "turn_cancelled" || event.type === "turn_failed") {
|
|
1413
|
-
const terminalStatus = event.type === "turn_completed"
|
|
1414
|
-
? "completed"
|
|
1415
|
-
: event.type === "turn_cancelled"
|
|
1416
|
-
? "cancelled"
|
|
1417
|
-
: "failed";
|
|
1418
|
-
const latest = this.store.getSession(event.sessionId);
|
|
1419
|
-
if (latest?.lastTurnId === event.turnId && latest.lastTurnStatus === terminalStatus) {
|
|
1420
|
-
await this.finalizeTurnMessageReactions(event.turnId, terminalStatus);
|
|
1421
|
-
}
|
|
1422
|
-
}
|
|
1423
1553
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
const latest = this.store.getSession(event.sessionId);
|
|
1427
|
-
const agent = latest ? this.ensureAgent(latest.agentName) : undefined;
|
|
1428
|
-
const activeTurnId = agent ? this.runtimes.forAgent(agent).getSession(event.sessionId)?.activeTurnId : undefined;
|
|
1429
|
-
if (latest?.lastTurnId === event.turnId && !activeTurnId) {
|
|
1430
|
-
this.store.updateSession(event.sessionId, { status: event.type === "turn_failed" ? "failed" : "ready" });
|
|
1431
|
-
}
|
|
1432
|
-
}
|
|
1554
|
+
catch (error) {
|
|
1555
|
+
presentationError = error;
|
|
1433
1556
|
}
|
|
1434
1557
|
if (event.type === "turn_completed" || event.type === "turn_cancelled" || event.type === "turn_failed") {
|
|
1558
|
+
const terminalStatus = event.type === "turn_completed"
|
|
1559
|
+
? "completed"
|
|
1560
|
+
: event.type === "turn_cancelled"
|
|
1561
|
+
? "cancelled"
|
|
1562
|
+
: "failed";
|
|
1563
|
+
const reactionSession = this.store.getSession(event.sessionId);
|
|
1564
|
+
if (!presentationError
|
|
1565
|
+
&& reactionSession?.lastTurnId === event.turnId
|
|
1566
|
+
&& reactionSession.lastTurnStatus === terminalStatus) {
|
|
1567
|
+
await this.finalizeTurnMessageReactions(event.turnId, terminalStatus);
|
|
1568
|
+
}
|
|
1435
1569
|
const latest = this.store.getSession(event.sessionId);
|
|
1436
|
-
const
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1570
|
+
const activeTurnId = latest
|
|
1571
|
+
? this.runtimes.forAgent(latest.agentName).getSession(event.sessionId)?.activeTurnId
|
|
1572
|
+
: undefined;
|
|
1573
|
+
if (latest?.lastTurnId === event.turnId && !activeTurnId) {
|
|
1574
|
+
this.store.updateSession(event.sessionId, { status: event.type === "turn_failed" ? "failed" : "ready" });
|
|
1575
|
+
await this.scheduleNextQueuedPrompt(event.sessionId);
|
|
1576
|
+
}
|
|
1441
1577
|
}
|
|
1578
|
+
if (presentationError)
|
|
1579
|
+
throw presentationError;
|
|
1442
1580
|
}
|
|
1443
1581
|
scheduleNextQueuedPrompt(sessionId) {
|
|
1444
1582
|
const previous = this.queuedPromptStarts.get(sessionId) ?? Promise.resolve();
|
|
@@ -1533,7 +1671,7 @@ export class ProxySessionController {
|
|
|
1533
1671
|
await this.cancelSession(record);
|
|
1534
1672
|
}
|
|
1535
1673
|
async cancelSession(record) {
|
|
1536
|
-
const runtime = this.runtimes.forAgent(
|
|
1674
|
+
const runtime = this.runtimes.forAgent(record.agentName);
|
|
1537
1675
|
if (runtime.kind === "codex" && record.remoteSessionId && runtime.interruptRemoteTurn) {
|
|
1538
1676
|
if (runtime.getGoal && runtime.setGoal) {
|
|
1539
1677
|
try {
|
|
@@ -1590,29 +1728,237 @@ export class ProxySessionController {
|
|
|
1590
1728
|
const loaded = await this.loadSession(this.requireSession(action.contextKey, sessionId));
|
|
1591
1729
|
await loaded.runtime.respondToApproval(sessionId, requestId, decision);
|
|
1592
1730
|
}
|
|
1593
|
-
async
|
|
1731
|
+
async openExecutionSettings(contextKey, activeTab, options = {}) {
|
|
1732
|
+
const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent);
|
|
1733
|
+
const record = options.sessionId
|
|
1734
|
+
? this.requireSession(contextKey, options.sessionId)
|
|
1735
|
+
: this.currentSession(contextKey);
|
|
1736
|
+
if (!record && activeTab !== "agent")
|
|
1737
|
+
this.requireCurrentSession(contextKey);
|
|
1738
|
+
const agents = Object.entries(this.config.agents).map(([name, agent]) => ({
|
|
1739
|
+
name,
|
|
1740
|
+
title: agent.title,
|
|
1741
|
+
}));
|
|
1742
|
+
let runtimeSettings = {
|
|
1743
|
+
currentPermissionMode: "auto",
|
|
1744
|
+
providers: [],
|
|
1745
|
+
providerSupported: false,
|
|
1746
|
+
models: [],
|
|
1747
|
+
reasoningOptions: [],
|
|
1748
|
+
};
|
|
1749
|
+
if (record) {
|
|
1750
|
+
const loaded = await this.loadSession(record);
|
|
1751
|
+
const models = await loaded.runtime.listModels();
|
|
1752
|
+
const currentModel = models.find((model) => model.id === loaded.session.model)
|
|
1753
|
+
?? models.find((model) => model.isDefault);
|
|
1754
|
+
const providerSupported = loaded.runtime.kind === "codex" && Boolean(loaded.runtime.listModelProviders);
|
|
1755
|
+
const providers = providerSupported ? await this.modelProviderOptions(loaded) : [];
|
|
1756
|
+
const currentProvider = loaded.session.modelProvider ?? providers.find((provider) => provider.isDefault)?.id;
|
|
1757
|
+
const currentEffort = currentModel?.supportedReasoningEfforts.some((option) => option.value === loaded.session.reasoningEffort)
|
|
1758
|
+
? loaded.session.reasoningEffort
|
|
1759
|
+
: currentModel?.defaultReasoningEffort ?? loaded.session.reasoningEffort;
|
|
1760
|
+
runtimeSettings = {
|
|
1761
|
+
currentProvider,
|
|
1762
|
+
currentModel: currentModel?.id ?? loaded.session.model,
|
|
1763
|
+
currentEffort,
|
|
1764
|
+
currentPermissionMode: loaded.session.permissionMode,
|
|
1765
|
+
providers,
|
|
1766
|
+
providerSupported,
|
|
1767
|
+
models,
|
|
1768
|
+
reasoningOptions: currentModel?.supportedReasoningEfforts ?? [],
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
const card = this.cardRenderer.renderExecutionSettings({
|
|
1772
|
+
...(record ? { sessionId: record.localSessionId, taskAgent: record.agentName } : {}),
|
|
1773
|
+
contextKey,
|
|
1774
|
+
activeTab,
|
|
1775
|
+
currentAgent: context.defaultAgent,
|
|
1776
|
+
agents,
|
|
1777
|
+
runtimeSettingsAvailable: Boolean(record),
|
|
1778
|
+
...runtimeSettings,
|
|
1779
|
+
notice: options.notice,
|
|
1780
|
+
});
|
|
1781
|
+
if (options.updateMessageId) {
|
|
1782
|
+
await this.outbound.updateInteractiveCard(contextKey, options.updateMessageId, card);
|
|
1783
|
+
}
|
|
1784
|
+
else {
|
|
1785
|
+
await this.outbound.sendInteractiveCard(contextKey, card);
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
async openAgentSettings(contextKey) {
|
|
1789
|
+
const current = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent).defaultAgent;
|
|
1790
|
+
if (Object.keys(this.config.agents).length <= 1) {
|
|
1791
|
+
await this.outbound.sendText(contextKey, `当前 Agent:${current}\n当前没有其他 Agent 可以切换。`);
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
await this.openExecutionSettings(contextKey, "agent");
|
|
1795
|
+
}
|
|
1796
|
+
async openProviderSettings(contextKey) {
|
|
1797
|
+
const loaded = await this.loadSession(this.requireCurrentSession(contextKey));
|
|
1798
|
+
const providers = loaded.runtime.kind === "codex" && loaded.runtime.listModelProviders
|
|
1799
|
+
? await this.modelProviderOptions(loaded)
|
|
1800
|
+
: [];
|
|
1801
|
+
const current = loaded.session.modelProvider?.trim()
|
|
1802
|
+
|| providers.find((provider) => provider.isDefault)?.id
|
|
1803
|
+
|| "运行时默认";
|
|
1804
|
+
if (providers.length <= 1) {
|
|
1805
|
+
await this.outbound.sendText(contextKey, `当前 Provider:${current}\n当前没有其他 Provider 可以切换。`);
|
|
1806
|
+
return;
|
|
1807
|
+
}
|
|
1808
|
+
await this.openExecutionSettings(contextKey, "provider", { sessionId: loaded.record.localSessionId });
|
|
1809
|
+
}
|
|
1810
|
+
async selectProvider(contextKey, modelProvider, options = {}) {
|
|
1594
1811
|
const record = options.sessionId
|
|
1595
1812
|
? this.requireSession(contextKey, options.sessionId)
|
|
1596
1813
|
: this.requireCurrentSession(contextKey);
|
|
1597
1814
|
const loaded = await this.loadSession(record);
|
|
1815
|
+
await this.assertModelProvider(loaded, modelProvider);
|
|
1598
1816
|
const models = await loaded.runtime.listModels();
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1817
|
+
const model = models.find((candidate) => candidate.id === loaded.session.model)
|
|
1818
|
+
?? models.find((candidate) => candidate.isDefault);
|
|
1819
|
+
if (!model)
|
|
1820
|
+
throw new Error("当前运行时没有可用于 Provider 切换的模型。");
|
|
1821
|
+
const effort = model.supportedReasoningEfforts.some((candidate) => candidate.value === loaded.session.reasoningEffort)
|
|
1822
|
+
? loaded.session.reasoningEffort
|
|
1823
|
+
: model.defaultReasoningEffort ?? model.supportedReasoningEfforts[0]?.value;
|
|
1824
|
+
if (!effort)
|
|
1825
|
+
throw new Error(`模型 ${model.id} 没有可用于 Provider 切换的思考强度。`);
|
|
1826
|
+
await this.applyProviderSettings(contextKey, {
|
|
1827
|
+
provider: modelProvider,
|
|
1828
|
+
model: model.id,
|
|
1829
|
+
effort,
|
|
1830
|
+
mode: loaded.session.permissionMode,
|
|
1831
|
+
}, options);
|
|
1832
|
+
}
|
|
1833
|
+
async openProviderSelector(contextKey, options = {}) {
|
|
1834
|
+
await this.openExecutionSettings(contextKey, "provider", options);
|
|
1835
|
+
}
|
|
1836
|
+
async openProviderModelSelector(contextKey, modelProvider, permissionMode, sessionId, updateMessageId) {
|
|
1837
|
+
const loaded = await this.loadSession(this.requireSession(contextKey, sessionId));
|
|
1838
|
+
await this.assertModelProvider(loaded, modelProvider);
|
|
1839
|
+
const models = await loaded.runtime.listModels();
|
|
1840
|
+
const card = this.cardRenderer.renderModelSelector({
|
|
1841
|
+
sessionId,
|
|
1842
|
+
contextKey,
|
|
1843
|
+
currentModel: loaded.session.model ?? models.find((model) => model.isDefault)?.id,
|
|
1844
|
+
reasoningEffort: loaded.session.reasoningEffort,
|
|
1845
|
+
models,
|
|
1846
|
+
modelProvider,
|
|
1847
|
+
permissionMode,
|
|
1848
|
+
unifiedSettings: true,
|
|
1849
|
+
});
|
|
1850
|
+
await this.outbound.updateInteractiveCard(contextKey, updateMessageId, card);
|
|
1851
|
+
}
|
|
1852
|
+
async openProviderReasoningSelector(contextKey, modelProvider, model, permissionMode, sessionId, updateMessageId) {
|
|
1853
|
+
const loaded = await this.loadSession(this.requireSession(contextKey, sessionId));
|
|
1854
|
+
await this.assertModelProvider(loaded, modelProvider);
|
|
1855
|
+
const models = await loaded.runtime.listModels();
|
|
1856
|
+
const selected = models.find((candidate) => candidate.id === model);
|
|
1857
|
+
if (!selected)
|
|
1858
|
+
throw new Error(`未知模型:${model}`);
|
|
1859
|
+
const currentEffort = selected.supportedReasoningEfforts.some((option) => option.value === loaded.session.reasoningEffort)
|
|
1860
|
+
? loaded.session.reasoningEffort
|
|
1861
|
+
: selected.defaultReasoningEffort;
|
|
1862
|
+
const card = this.cardRenderer.renderReasoningSelector({
|
|
1863
|
+
sessionId,
|
|
1864
|
+
contextKey,
|
|
1865
|
+
modelProvider,
|
|
1866
|
+
model,
|
|
1867
|
+
currentEffort,
|
|
1868
|
+
options: selected.supportedReasoningEfforts,
|
|
1869
|
+
permissionMode,
|
|
1870
|
+
unifiedSettings: true,
|
|
1871
|
+
});
|
|
1872
|
+
await this.outbound.updateInteractiveCard(contextKey, updateMessageId, card);
|
|
1873
|
+
}
|
|
1874
|
+
async openProviderPermissionSelector(contextKey, modelProvider, model, effort, permissionMode, sessionId, updateMessageId) {
|
|
1875
|
+
const loaded = await this.loadSession(this.requireSession(contextKey, sessionId));
|
|
1876
|
+
await this.assertProviderModelSettings(loaded, modelProvider, model, effort);
|
|
1877
|
+
await this.outbound.updateInteractiveCard(contextKey, updateMessageId, this.cardRenderer.renderPermissionSelector({
|
|
1878
|
+
sessionId,
|
|
1879
|
+
contextKey,
|
|
1880
|
+
modelProvider,
|
|
1881
|
+
model,
|
|
1882
|
+
reasoningEffort: effort,
|
|
1883
|
+
currentMode: permissionMode,
|
|
1884
|
+
}));
|
|
1885
|
+
}
|
|
1886
|
+
async applyProviderSettings(contextKey, settings, options = {}) {
|
|
1887
|
+
const record = options.sessionId
|
|
1888
|
+
? this.requireSession(contextKey, options.sessionId)
|
|
1889
|
+
: this.requireCurrentSession(contextKey);
|
|
1890
|
+
const loaded = await this.loadSession(record);
|
|
1891
|
+
await this.assertProviderModelSettings(loaded, settings.provider, settings.model, settings.effort);
|
|
1892
|
+
if (!loaded.runtime.setExecutionSettings) {
|
|
1893
|
+
throw new Error("当前运行时不支持 Provider 设置。");
|
|
1894
|
+
}
|
|
1895
|
+
const session = await loaded.runtime.setExecutionSettings(loaded.record.localSessionId, {
|
|
1896
|
+
modelProvider: settings.provider,
|
|
1897
|
+
model: settings.model,
|
|
1898
|
+
reasoningEffort: settings.effort,
|
|
1899
|
+
permissionMode: settings.mode,
|
|
1900
|
+
});
|
|
1901
|
+
this.store.updateRuntimeSession(loaded.record.localSessionId, {
|
|
1902
|
+
modelProvider: session.modelProvider ?? settings.provider,
|
|
1903
|
+
model: session.model ?? settings.model,
|
|
1904
|
+
reasoningEffort: session.reasoningEffort ?? settings.effort,
|
|
1905
|
+
permissionMode: session.permissionMode,
|
|
1906
|
+
});
|
|
1907
|
+
const notice = [
|
|
1908
|
+
`Provider 已切换为 ${cardCode(session.modelProvider ?? settings.provider)}`,
|
|
1909
|
+
`模型 ${cardCode(session.model ?? settings.model)}`,
|
|
1910
|
+
`思考强度 ${cardCode(session.reasoningEffort ?? settings.effort)}`,
|
|
1911
|
+
`权限 ${cardCode(session.permissionMode)}`,
|
|
1912
|
+
"从下一次请求生效。",
|
|
1913
|
+
].join(",");
|
|
1914
|
+
if (options.updateMessageId) {
|
|
1915
|
+
await this.openProviderSelector(contextKey, {
|
|
1602
1916
|
sessionId: loaded.record.localSessionId,
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
reasoningEffort: loaded.session.reasoningEffort,
|
|
1606
|
-
models,
|
|
1917
|
+
updateMessageId: options.updateMessageId,
|
|
1918
|
+
notice,
|
|
1607
1919
|
});
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1920
|
+
}
|
|
1921
|
+
else {
|
|
1922
|
+
await this.outbound.sendText(contextKey, notice.replaceAll("`", ""));
|
|
1923
|
+
}
|
|
1924
|
+
}
|
|
1925
|
+
async assertProviderModelSettings(loaded, modelProvider, model, effort) {
|
|
1926
|
+
await this.assertModelProvider(loaded, modelProvider);
|
|
1927
|
+
const selected = (await loaded.runtime.listModels()).find((candidate) => candidate.id === model);
|
|
1928
|
+
if (!selected)
|
|
1929
|
+
throw new Error(`未知模型:${model}`);
|
|
1930
|
+
if (!selected.supportedReasoningEfforts.some((option) => option.value === effort)) {
|
|
1931
|
+
const supported = selected.supportedReasoningEfforts.map((option) => option.value).join("、") || "无";
|
|
1932
|
+
throw new Error(`模型 ${model} 不支持思考强度 ${effort}。支持的强度:${supported}`);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
async assertModelProvider(loaded, modelProvider) {
|
|
1936
|
+
const providers = await this.modelProviderOptions(loaded);
|
|
1937
|
+
if (!providers.some((provider) => provider.id === modelProvider)) {
|
|
1938
|
+
throw new Error(`未知 Provider:${modelProvider}`);
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
async modelProviderOptions(loaded) {
|
|
1942
|
+
if (loaded.runtime.kind !== "codex" || !loaded.runtime.listModelProviders) {
|
|
1943
|
+
throw new Error("当前任务不支持 Provider 设置。");
|
|
1944
|
+
}
|
|
1945
|
+
const providers = await loaded.runtime.listModelProviders();
|
|
1946
|
+
const current = loaded.session.modelProvider?.trim();
|
|
1947
|
+
if (current && !providers.some((provider) => provider.id === current)) {
|
|
1948
|
+
providers.unshift({ id: current });
|
|
1949
|
+
}
|
|
1950
|
+
return providers;
|
|
1951
|
+
}
|
|
1952
|
+
async model(contextKey, model, options = {}) {
|
|
1953
|
+
if (!model) {
|
|
1954
|
+
await this.openExecutionSettings(contextKey, "model", options);
|
|
1614
1955
|
return;
|
|
1615
1956
|
}
|
|
1957
|
+
const record = options.sessionId
|
|
1958
|
+
? this.requireSession(contextKey, options.sessionId)
|
|
1959
|
+
: this.requireCurrentSession(contextKey);
|
|
1960
|
+
const loaded = await this.loadSession(record);
|
|
1961
|
+
const models = await loaded.runtime.listModels();
|
|
1616
1962
|
const selected = models.find((item) => item.id === model);
|
|
1617
1963
|
if (!selected)
|
|
1618
1964
|
throw new Error(`未知模型:${model}`);
|
|
@@ -1630,14 +1976,11 @@ export class ProxySessionController {
|
|
|
1630
1976
|
? `,思考强度已自动调整为 ${nextEffort}`
|
|
1631
1977
|
: "";
|
|
1632
1978
|
if (options.updateMessageId) {
|
|
1633
|
-
await this.
|
|
1979
|
+
await this.openExecutionSettings(contextKey, "model", {
|
|
1634
1980
|
sessionId: loaded.record.localSessionId,
|
|
1635
|
-
|
|
1636
|
-
model
|
|
1637
|
-
|
|
1638
|
-
options: selected.supportedReasoningEfforts,
|
|
1639
|
-
notice: `模型已切换为 ${cardCode(model)},请选择思考模式。`,
|
|
1640
|
-
}));
|
|
1981
|
+
updateMessageId: options.updateMessageId,
|
|
1982
|
+
notice: `模型已切换为 ${cardCode(model)}${effortMessage},从下一次请求生效。`,
|
|
1983
|
+
});
|
|
1641
1984
|
return;
|
|
1642
1985
|
}
|
|
1643
1986
|
await this.outbound.sendText(contextKey, `模型已切换为 ${model}${effortMessage},从下一次请求生效。`);
|
|
@@ -1653,8 +1996,8 @@ export class ProxySessionController {
|
|
|
1653
1996
|
}
|
|
1654
1997
|
const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent);
|
|
1655
1998
|
const agent = this.ensureAgent(context.defaultAgent);
|
|
1656
|
-
if (agent.kind !== "
|
|
1657
|
-
throw new Error("Goal 模式仅支持
|
|
1999
|
+
if (agent.kind !== "app-server")
|
|
2000
|
+
throw new Error("Goal 模式仅支持 App Server 任务。");
|
|
1658
2001
|
record = await this.createSession(contextKey, context.defaultAgent, this.inheritedNewTaskCwd(contextKey), false, false, objective);
|
|
1659
2002
|
}
|
|
1660
2003
|
const loaded = await this.loadSession(record);
|
|
@@ -1785,6 +2128,10 @@ export class ProxySessionController {
|
|
|
1785
2128
|
});
|
|
1786
2129
|
}
|
|
1787
2130
|
async thinking(contextKey, effort, options = {}) {
|
|
2131
|
+
if (!effort) {
|
|
2132
|
+
await this.openExecutionSettings(contextKey, "thinking", options);
|
|
2133
|
+
return;
|
|
2134
|
+
}
|
|
1788
2135
|
const record = options.sessionId
|
|
1789
2136
|
? this.requireSession(contextKey, options.sessionId)
|
|
1790
2137
|
: this.requireCurrentSession(contextKey);
|
|
@@ -1798,22 +2145,6 @@ export class ProxySessionController {
|
|
|
1798
2145
|
throw new Error("模型已发生变化,请重新打开 /model。");
|
|
1799
2146
|
}
|
|
1800
2147
|
const supported = currentModel.supportedReasoningEfforts;
|
|
1801
|
-
if (!effort) {
|
|
1802
|
-
const card = this.cardRenderer.renderReasoningSelector({
|
|
1803
|
-
sessionId: loaded.record.localSessionId,
|
|
1804
|
-
contextKey,
|
|
1805
|
-
model: currentModel.id,
|
|
1806
|
-
currentEffort: loaded.session.reasoningEffort ?? currentModel.defaultReasoningEffort,
|
|
1807
|
-
options: supported,
|
|
1808
|
-
});
|
|
1809
|
-
if (options.updateMessageId) {
|
|
1810
|
-
await this.outbound.updateInteractiveCard(contextKey, options.updateMessageId, card);
|
|
1811
|
-
}
|
|
1812
|
-
else {
|
|
1813
|
-
await this.outbound.sendInteractiveCard(contextKey, card);
|
|
1814
|
-
}
|
|
1815
|
-
return;
|
|
1816
|
-
}
|
|
1817
2148
|
if (!supported.some((option) => option.value === effort)) {
|
|
1818
2149
|
const options = supported.map((option) => option.value).join("、") || "无";
|
|
1819
2150
|
throw new Error(`不支持的思考强度:${effort}。支持的强度:${options}`);
|
|
@@ -1821,79 +2152,85 @@ export class ProxySessionController {
|
|
|
1821
2152
|
await loaded.runtime.setReasoningEffort(loaded.record.localSessionId, effort);
|
|
1822
2153
|
this.store.updateRuntimeSession(loaded.record.localSessionId, { reasoningEffort: effort });
|
|
1823
2154
|
if (options.updateMessageId) {
|
|
1824
|
-
await this.
|
|
2155
|
+
await this.openExecutionSettings(contextKey, "thinking", {
|
|
1825
2156
|
sessionId: loaded.record.localSessionId,
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
options: supported,
|
|
1830
|
-
notice: `思考模式已切换为 ${cardCode(effort)},从下一次请求生效。`,
|
|
1831
|
-
}));
|
|
2157
|
+
updateMessageId: options.updateMessageId,
|
|
2158
|
+
notice: `思考强度已切换为 ${cardCode(effort)},从下一次请求生效。`,
|
|
2159
|
+
});
|
|
1832
2160
|
return;
|
|
1833
2161
|
}
|
|
1834
2162
|
await this.outbound.sendText(contextKey, `思考强度已切换为 ${effort},从下一次请求生效。`);
|
|
1835
2163
|
}
|
|
1836
|
-
async permissions(contextKey, mode) {
|
|
1837
|
-
const record = this.requireCurrentSession(contextKey);
|
|
2164
|
+
async permissions(contextKey, mode, options = {}) {
|
|
1838
2165
|
if (!mode) {
|
|
1839
|
-
await this.
|
|
2166
|
+
await this.openExecutionSettings(contextKey, "permission", options);
|
|
1840
2167
|
return;
|
|
1841
2168
|
}
|
|
2169
|
+
const record = options.sessionId
|
|
2170
|
+
? this.requireSession(contextKey, options.sessionId)
|
|
2171
|
+
: this.requireCurrentSession(contextKey);
|
|
1842
2172
|
const loaded = await this.loadSession(record);
|
|
1843
2173
|
await loaded.runtime.setPermissionMode(record.localSessionId, mode);
|
|
2174
|
+
loaded.session.permissionMode = mode;
|
|
1844
2175
|
this.store.updateRuntimeSession(record.localSessionId, { permissionMode: mode });
|
|
2176
|
+
if (options.updateMessageId) {
|
|
2177
|
+
await this.openExecutionSettings(contextKey, "permission", {
|
|
2178
|
+
sessionId: loaded.record.localSessionId,
|
|
2179
|
+
updateMessageId: options.updateMessageId,
|
|
2180
|
+
notice: mode === "auto"
|
|
2181
|
+
? "已切换为自动执行模式,从下一次请求生效。"
|
|
2182
|
+
: "已切换为执行前确认模式,从下一次请求生效。",
|
|
2183
|
+
});
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
1845
2186
|
await this.outbound.sendText(contextKey, mode === "auto" ? "已切换为自动执行模式。" : "已切换为执行前确认模式。");
|
|
1846
2187
|
}
|
|
1847
|
-
async listAgents(contextKey) {
|
|
1848
|
-
const current = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent).defaultAgent;
|
|
1849
|
-
const lines = Object.entries(this.config.agents).map(([name, agent]) => {
|
|
1850
|
-
const selected = name === current;
|
|
1851
|
-
return `- ${selected ? "✅ " : ""}${asInlineCode(name)}:${agent.title}${selected ? "(当前)" : ""}`;
|
|
1852
|
-
});
|
|
1853
|
-
await this.outbound.sendMarkdown(contextKey, [
|
|
1854
|
-
`当前 Agent:${asInlineCode(current)}`,
|
|
1855
|
-
"可用 Agent:",
|
|
1856
|
-
lines.join("\n") || "无",
|
|
1857
|
-
].join("\n"));
|
|
1858
|
-
}
|
|
1859
2188
|
async listSessions(contextKey, searchTerm, options = {}) {
|
|
1860
2189
|
const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent);
|
|
1861
2190
|
const normalizedSearch = searchTerm?.trim().toLowerCase();
|
|
1862
2191
|
const visibleCount = options.visibleCount ?? SESSION_PAGE_SIZE;
|
|
1863
2192
|
const localSessions = this.store.listSessions(contextKey).filter((session) => session.status !== "closed" && this.isCodexSession(session));
|
|
1864
|
-
|
|
1865
|
-
|
|
2193
|
+
const remoteSessions = [];
|
|
2194
|
+
const remoteErrors = [];
|
|
1866
2195
|
let remoteHasMore = false;
|
|
1867
|
-
|
|
1868
|
-
|
|
2196
|
+
await Promise.all(this.runtimes.entries("codex").map(async ([agentName, runtime]) => {
|
|
2197
|
+
if (!runtime.listRemoteSessions)
|
|
2198
|
+
return;
|
|
1869
2199
|
try {
|
|
1870
2200
|
const page = await runtime.listRemoteSessions({ searchTerm, limit: visibleCount });
|
|
1871
|
-
remoteSessions
|
|
1872
|
-
remoteHasMore
|
|
2201
|
+
remoteSessions.push(...page.sessions.map((session) => ({ agentName, session })));
|
|
2202
|
+
remoteHasMore ||= Boolean(page.nextCursor);
|
|
1873
2203
|
}
|
|
1874
2204
|
catch (error) {
|
|
1875
|
-
this.logger.warn({ error, contextKey }, "Failed to list Codex sessions.");
|
|
1876
|
-
|
|
2205
|
+
this.logger.warn({ error, contextKey, agentName }, "Failed to list Codex sessions for an Agent.");
|
|
2206
|
+
remoteErrors.push(`${agentName}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1877
2207
|
}
|
|
1878
|
-
}
|
|
2208
|
+
}));
|
|
2209
|
+
const remoteHint = remoteErrors.length > 0
|
|
2210
|
+
? `部分 Agent 的任务读取失败:${remoteErrors.join(";")}`
|
|
2211
|
+
: undefined;
|
|
1879
2212
|
const entries = mergeTaskList(localSessions, remoteSessions, context.currentSessionId)
|
|
1880
|
-
.filter((entry) => !normalizedSearch
|
|
2213
|
+
.filter((entry) => !normalizedSearch
|
|
2214
|
+
|| [entry.id, entry.title, entry.cwd, entry.agentName]
|
|
2215
|
+
.some((value) => value.toLowerCase().includes(normalizedSearch)))
|
|
1881
2216
|
.sort((left, right) => Number(right.current) - Number(left.current)
|
|
1882
2217
|
|| Number(right.active) - Number(left.active)
|
|
1883
2218
|
|| right.updatedAt - left.updatedAt);
|
|
1884
2219
|
const activeCount = entries.filter((entry) => entry.active).length;
|
|
1885
2220
|
const visibleEntries = entries.slice(0, visibleCount);
|
|
1886
2221
|
const hasMore = remoteHasMore || entries.length > visibleCount;
|
|
1887
|
-
this.lastSessionListings.set(contextKey, visibleEntries.map((entry) => entry.
|
|
2222
|
+
this.lastSessionListings.set(contextKey, visibleEntries.map((entry) => entry.reference));
|
|
1888
2223
|
const cardEntries = visibleEntries.map((entry, index) => {
|
|
1889
2224
|
const marker = entry.current ? "✅" : entry.active ? "🟢 **活跃**" : "•";
|
|
1890
|
-
const showStop = entry.status === "外部执行中"
|
|
2225
|
+
const showStop = entry.status === "外部执行中"
|
|
2226
|
+
&& entry.reference !== options.forceSwitchTaskId
|
|
2227
|
+
&& entry.id !== options.forceSwitchTaskId;
|
|
1891
2228
|
const actions = entry.current ? [] : [{
|
|
1892
2229
|
text: showStop ? "Stop" : "Switch",
|
|
1893
2230
|
type: showStop ? "danger" : "default",
|
|
1894
2231
|
value: {
|
|
1895
2232
|
action: showStop ? "session_stop" : "session_switch",
|
|
1896
|
-
sessionId: entry.
|
|
2233
|
+
sessionId: entry.reference,
|
|
1897
2234
|
...(searchTerm ? { searchTerm } : {}),
|
|
1898
2235
|
visibleCount: String(visibleCount),
|
|
1899
2236
|
contextKey,
|
|
@@ -1903,7 +2240,7 @@ export class ProxySessionController {
|
|
|
1903
2240
|
text: "New",
|
|
1904
2241
|
value: {
|
|
1905
2242
|
action: "session_new",
|
|
1906
|
-
sessionId: entry.
|
|
2243
|
+
sessionId: entry.reference,
|
|
1907
2244
|
...(searchTerm ? { searchTerm } : {}),
|
|
1908
2245
|
visibleCount: String(visibleCount),
|
|
1909
2246
|
contextKey,
|
|
@@ -1913,7 +2250,7 @@ export class ProxySessionController {
|
|
|
1913
2250
|
text: "NewGroup",
|
|
1914
2251
|
value: {
|
|
1915
2252
|
action: "session_new_group",
|
|
1916
|
-
sessionId: entry.
|
|
2253
|
+
sessionId: entry.reference,
|
|
1917
2254
|
...(searchTerm ? { searchTerm } : {}),
|
|
1918
2255
|
visibleCount: String(visibleCount),
|
|
1919
2256
|
contextKey,
|
|
@@ -1923,7 +2260,7 @@ export class ProxySessionController {
|
|
|
1923
2260
|
text: "Fork",
|
|
1924
2261
|
value: {
|
|
1925
2262
|
action: "session_fork",
|
|
1926
|
-
sessionId: entry.
|
|
2263
|
+
sessionId: entry.reference,
|
|
1927
2264
|
...(searchTerm ? { searchTerm } : {}),
|
|
1928
2265
|
visibleCount: String(visibleCount),
|
|
1929
2266
|
contextKey,
|
|
@@ -1933,7 +2270,7 @@ export class ProxySessionController {
|
|
|
1933
2270
|
text: "ForkGroup",
|
|
1934
2271
|
value: {
|
|
1935
2272
|
action: "session_fork_group",
|
|
1936
|
-
sessionId: entry.
|
|
2273
|
+
sessionId: entry.reference,
|
|
1937
2274
|
...(searchTerm ? { searchTerm } : {}),
|
|
1938
2275
|
visibleCount: String(visibleCount),
|
|
1939
2276
|
contextKey,
|
|
@@ -1943,14 +2280,14 @@ export class ProxySessionController {
|
|
|
1943
2280
|
text: "Status",
|
|
1944
2281
|
value: {
|
|
1945
2282
|
action: "session_status",
|
|
1946
|
-
sessionId: entry.
|
|
2283
|
+
sessionId: entry.reference,
|
|
1947
2284
|
contextKey,
|
|
1948
2285
|
},
|
|
1949
2286
|
});
|
|
1950
2287
|
return {
|
|
1951
2288
|
lines: [
|
|
1952
2289
|
`**${index + 1}.** ${marker} **${cardText(entry.title)}** ${cardText(entry.id)}`,
|
|
1953
|
-
`${entry.status} · ${entry.updatedLabel} · ${cardText(entry.cwd || "目录未知")}`,
|
|
2290
|
+
`${entry.status} · ${entry.updatedLabel} · ${cardText(entry.agentName)} · ${cardText(entry.cwd || "目录未知")}`,
|
|
1954
2291
|
],
|
|
1955
2292
|
actions,
|
|
1956
2293
|
};
|
|
@@ -2003,6 +2340,45 @@ export class ProxySessionController {
|
|
|
2003
2340
|
forceSwitchTaskId,
|
|
2004
2341
|
});
|
|
2005
2342
|
}
|
|
2343
|
+
findStoredSessionByReference(reference, contextKey) {
|
|
2344
|
+
const scoped = parseRemoteSessionReference(reference);
|
|
2345
|
+
return scoped
|
|
2346
|
+
? this.store.findSessionByRemoteSessionId(scoped.remoteSessionId, contextKey, scoped.agentName)
|
|
2347
|
+
: this.store.findSessionByRemoteSessionId(reference, contextKey);
|
|
2348
|
+
}
|
|
2349
|
+
async resolveRemoteCodexSession(reference) {
|
|
2350
|
+
const scoped = parseRemoteSessionReference(reference);
|
|
2351
|
+
const candidates = scoped
|
|
2352
|
+
? [[scoped.agentName, this.runtimes.forAgent(scoped.agentName)]]
|
|
2353
|
+
: this.runtimes.entries("codex");
|
|
2354
|
+
if (candidates.length === 0)
|
|
2355
|
+
throw new Error("未配置 Codex Agent。");
|
|
2356
|
+
const reads = await Promise.allSettled(candidates.map(async ([agentName, runtime]) => {
|
|
2357
|
+
if (runtime.kind !== "codex" || !runtime.readRemoteSession) {
|
|
2358
|
+
throw new Error(`Agent ${agentName} 不支持读取远端任务。`);
|
|
2359
|
+
}
|
|
2360
|
+
return {
|
|
2361
|
+
agentName,
|
|
2362
|
+
runtime,
|
|
2363
|
+
remote: await runtime.readRemoteSession(scoped?.remoteSessionId ?? reference),
|
|
2364
|
+
};
|
|
2365
|
+
}));
|
|
2366
|
+
const matches = reads
|
|
2367
|
+
.filter((result) => result.status === "fulfilled")
|
|
2368
|
+
.map((result) => result.value);
|
|
2369
|
+
if (matches.length === 1)
|
|
2370
|
+
return matches[0];
|
|
2371
|
+
if (matches.length > 1) {
|
|
2372
|
+
throw new Error(`多个 Agent 中存在相同任务 ID:${reference}。请先使用 /sessions,再通过序号选择任务。`);
|
|
2373
|
+
}
|
|
2374
|
+
const details = reads
|
|
2375
|
+
.map((result, index) => result.status === "rejected"
|
|
2376
|
+
? `${candidates[index]?.[0] ?? "unknown"}: ${runtimeErrorMessage(result.reason)}`
|
|
2377
|
+
: undefined)
|
|
2378
|
+
.filter((detail) => Boolean(detail))
|
|
2379
|
+
.join(";");
|
|
2380
|
+
throw new Error(`找不到 Codex 任务:${scoped?.remoteSessionId ?? reference}${details ? `(${details})` : ""}`);
|
|
2381
|
+
}
|
|
2006
2382
|
async switchSession(contextKey, reference) {
|
|
2007
2383
|
this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent);
|
|
2008
2384
|
const taskId = this.resolveSessionReference(contextKey, reference);
|
|
@@ -2010,29 +2386,22 @@ export class ProxySessionController {
|
|
|
2010
2386
|
if (direct?.status === "closed") {
|
|
2011
2387
|
throw new Error(`找不到任务:${taskId}`);
|
|
2012
2388
|
}
|
|
2013
|
-
const existing = direct ?? this.
|
|
2389
|
+
const existing = direct ?? this.findStoredSessionByReference(taskId);
|
|
2014
2390
|
if (existing) {
|
|
2015
|
-
const runtime = this.runtimes.forAgent(
|
|
2391
|
+
const runtime = this.runtimes.forAgent(existing.agentName);
|
|
2016
2392
|
await this.assertSessionTurnOwnership(existing, runtime);
|
|
2017
2393
|
this.store.attachSessionToContext(contextKey, existing.localSessionId);
|
|
2018
2394
|
this.store.setCurrentSession(contextKey, existing.localSessionId);
|
|
2019
|
-
this.outbound.registerSession(existing.localSessionId, contextKey, existing.title, existing.cwd);
|
|
2395
|
+
this.outbound.registerSession(existing.localSessionId, contextKey, existing.title, existing.cwd, this.agentLabel(existing.agentName));
|
|
2020
2396
|
await this.outbound.sendText(contextKey, `已切换到任务:${existing.title ?? existing.remoteSessionId ?? taskId}`);
|
|
2021
2397
|
return;
|
|
2022
2398
|
}
|
|
2023
|
-
const
|
|
2024
|
-
if (!runtime.readRemoteSession)
|
|
2025
|
-
throw new Error("当前 Codex 运行时不支持读取任务。");
|
|
2026
|
-
const remote = await runtime.readRemoteSession(taskId);
|
|
2399
|
+
const { agentName, remote } = await this.resolveRemoteCodexSession(taskId);
|
|
2027
2400
|
if (remote.status === "active" || remote.lastTurnStatus === "inProgress") {
|
|
2028
2401
|
throw new Error(`这个任务正在外部 Codex 中执行,当前不会切换。可使用 /status ${taskId} 查看进度。`);
|
|
2029
2402
|
}
|
|
2030
2403
|
if (!remote.cwd)
|
|
2031
2404
|
throw new Error("这个 Codex 任务没有可用的工作目录,暂时无法切换。");
|
|
2032
|
-
const agentEntry = Object.entries(this.config.agents).find(([, agent]) => agent.kind === "codex");
|
|
2033
|
-
if (!agentEntry)
|
|
2034
|
-
throw new Error("未配置 Codex Agent。");
|
|
2035
|
-
const [agentName] = agentEntry;
|
|
2036
2405
|
const localSessionId = createId("sess");
|
|
2037
2406
|
this.store.createSession({
|
|
2038
2407
|
localSessionId,
|
|
@@ -2050,7 +2419,7 @@ export class ProxySessionController {
|
|
|
2050
2419
|
lastTurnStatus: mapRemoteTurnStatus(remote.lastTurnStatus),
|
|
2051
2420
|
});
|
|
2052
2421
|
this.store.setCurrentSession(contextKey, localSessionId);
|
|
2053
|
-
this.outbound.registerSession(localSessionId, contextKey, remote.title ?? remote.preview, remote.cwd);
|
|
2422
|
+
this.outbound.registerSession(localSessionId, contextKey, remote.title ?? remote.preview, remote.cwd, this.agentLabel(agentName));
|
|
2054
2423
|
await this.outbound.sendText(contextKey, `已切换到任务:${remote.title ?? remote.preview ?? remote.id}。历史消息不会重新发送。`);
|
|
2055
2424
|
}
|
|
2056
2425
|
async stopSessionReference(contextKey, taskId) {
|
|
@@ -2061,16 +2430,15 @@ export class ProxySessionController {
|
|
|
2061
2430
|
await this.cancelSession({ ...direct, contextKey });
|
|
2062
2431
|
return;
|
|
2063
2432
|
}
|
|
2064
|
-
const existing = this.
|
|
2433
|
+
const existing = this.findStoredSessionByReference(taskId);
|
|
2065
2434
|
if (existing) {
|
|
2066
2435
|
await this.cancelSession({ ...existing, contextKey });
|
|
2067
2436
|
return;
|
|
2068
2437
|
}
|
|
2069
|
-
const runtime = this.
|
|
2438
|
+
const { runtime, remote } = await this.resolveRemoteCodexSession(taskId);
|
|
2070
2439
|
if (!runtime.readRemoteSession || !runtime.interruptRemoteTurn) {
|
|
2071
2440
|
throw new Error("当前 Codex 运行时不支持停止外部任务。");
|
|
2072
2441
|
}
|
|
2073
|
-
const remote = await runtime.readRemoteSession(taskId);
|
|
2074
2442
|
const turnId = remote.status === "active" || remote.lastTurnStatus === "inProgress"
|
|
2075
2443
|
? remote.lastTurnId
|
|
2076
2444
|
: undefined;
|
|
@@ -2147,11 +2515,18 @@ export class ProxySessionController {
|
|
|
2147
2515
|
}
|
|
2148
2516
|
return undefined;
|
|
2149
2517
|
}
|
|
2150
|
-
async setDefaultAgent(contextKey, agentName) {
|
|
2518
|
+
async setDefaultAgent(contextKey, agentName, options = {}) {
|
|
2151
2519
|
this.ensureAgent(agentName);
|
|
2152
2520
|
this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent);
|
|
2153
2521
|
this.store.setDefaultAgent(contextKey, agentName);
|
|
2154
|
-
|
|
2522
|
+
if (Object.keys(this.config.agents).length <= 1) {
|
|
2523
|
+
await this.outbound.sendText(contextKey, `当前 Agent:${agentName}\n当前没有其他 Agent 可以切换。`);
|
|
2524
|
+
return;
|
|
2525
|
+
}
|
|
2526
|
+
await this.openExecutionSettings(contextKey, "agent", {
|
|
2527
|
+
...options,
|
|
2528
|
+
notice: `默认 Agent 已切换为 ${cardCode(agentName)},从下一次新建任务生效。`,
|
|
2529
|
+
});
|
|
2155
2530
|
}
|
|
2156
2531
|
async status(contextKey, sessionId, options = {}) {
|
|
2157
2532
|
const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent);
|
|
@@ -2161,7 +2536,7 @@ export class ProxySessionController {
|
|
|
2161
2536
|
const direct = this.store.getSession(targetSessionId);
|
|
2162
2537
|
current = direct
|
|
2163
2538
|
? { ...direct, contextKey }
|
|
2164
|
-
: this.
|
|
2539
|
+
: this.findStoredSessionByReference(targetSessionId, contextKey);
|
|
2165
2540
|
if (!current) {
|
|
2166
2541
|
await this.statusForCodexTask(contextKey, targetSessionId, options);
|
|
2167
2542
|
return;
|
|
@@ -2172,7 +2547,7 @@ export class ProxySessionController {
|
|
|
2172
2547
|
}
|
|
2173
2548
|
if (current && current.status === "running") {
|
|
2174
2549
|
try {
|
|
2175
|
-
const runtime = this.runtimes.forAgent(
|
|
2550
|
+
const runtime = this.runtimes.forAgent(current.agentName);
|
|
2176
2551
|
if (runtime.getSession(current.localSessionId)) {
|
|
2177
2552
|
await runtime.synchronizeSession(current.localSessionId);
|
|
2178
2553
|
current = this.store.getSession(current.localSessionId) ?? current;
|
|
@@ -2186,7 +2561,7 @@ export class ProxySessionController {
|
|
|
2186
2561
|
let remote;
|
|
2187
2562
|
let goal;
|
|
2188
2563
|
if (current?.remoteSessionId) {
|
|
2189
|
-
const runtime = this.runtimes.forAgent(
|
|
2564
|
+
const runtime = this.runtimes.forAgent(current.agentName);
|
|
2190
2565
|
if (runtime.readRemoteSession) {
|
|
2191
2566
|
try {
|
|
2192
2567
|
remote = await runtime.readRemoteSession(current.remoteSessionId);
|
|
@@ -2216,7 +2591,7 @@ export class ProxySessionController {
|
|
|
2216
2591
|
}
|
|
2217
2592
|
else {
|
|
2218
2593
|
const agent = this.ensureAgent(current.agentName);
|
|
2219
|
-
const runtimeSession = this.runtimes.forAgent(
|
|
2594
|
+
const runtimeSession = this.runtimes.forAgent(current.agentName).getSession(current.localSessionId);
|
|
2220
2595
|
activeTurnId = runtimeSession?.activeTurnId;
|
|
2221
2596
|
const remoteActive = isRemoteSessionActive(remote);
|
|
2222
2597
|
activeTurnId = remoteActive ? remote?.lastTurnId ?? activeTurnId : activeTurnId;
|
|
@@ -2228,7 +2603,7 @@ export class ProxySessionController {
|
|
|
2228
2603
|
const resultLabel = remoteActive
|
|
2229
2604
|
? "执行中"
|
|
2230
2605
|
: remoteTurnStatusLabel(remote?.lastTurnStatus ?? current.lastTurnStatus);
|
|
2231
|
-
taskLines.push(`**标题**:${cardCode(current.title ?? "未命名任务")}`, `**工作目录**:${cardCode(current.cwd)}`,
|
|
2606
|
+
taskLines.push(`**标题**:${cardCode(current.title ?? "未命名任务")}`, `**工作目录**:${cardCode(current.cwd)}`, `**Provider / 模型 / 思考强度**:${cardCode(current.modelProvider ?? "Codex 默认")} / ${cardCode(current.model ?? "默认")} / ${cardCode(current.reasoningEffort ?? "自动")}`, `**状态 / 最近结果**:${statusLabel} / ${resultLabel}`, `**Agent**:${cardCode(agent.title)}`, `**权限 / 任务范围**:${current.permissionMode === "confirm" ? "执行前确认" : "自动执行"} / ${detectProjectlessWorkspace(current.cwd) ? "未指定项目" : "指定项目"}`, `**Codex 任务 ID**:${cardCode(current.remoteSessionId ?? "尚未创建")}`, `**当前执行 / 排队消息**:${activeTurnId ? cardCode(activeTurnId) : "无"} / ${queued} 条`, `**创建时间 / 最近活动**:${formatStatusTime(current.createdAt)} / ${formatStatusTime(current.updatedAt)}`);
|
|
2232
2607
|
}
|
|
2233
2608
|
const sections = [
|
|
2234
2609
|
{ title: targetSessionId ? "指定任务" : "当前任务", lines: taskLines },
|
|
@@ -2257,12 +2632,15 @@ export class ProxySessionController {
|
|
|
2257
2632
|
const title = targetSessionId && current
|
|
2258
2633
|
? `Codex 状态:${truncateText((current.title ?? current.remoteSessionId ?? current.localSessionId).replace(/\s+/g, " "), 40)}`
|
|
2259
2634
|
: "Codex 状态";
|
|
2260
|
-
const taskId = current?.remoteSessionId
|
|
2635
|
+
const taskId = current?.remoteSessionId
|
|
2636
|
+
? remoteSessionReference(current.agentName, current.remoteSessionId)
|
|
2637
|
+
: current?.localSessionId;
|
|
2261
2638
|
const isCurrent = Boolean(current && current.localSessionId === context.currentSessionId);
|
|
2262
2639
|
const remoteActive = isRemoteSessionActive(remote);
|
|
2263
2640
|
const botOwnsActiveTurn = Boolean(localCurrent && remote && isBotOwnedActiveTurn(localCurrent, remote));
|
|
2264
2641
|
const active = Boolean(activeTurnId || current?.status === "running" || remoteActive);
|
|
2265
|
-
const forceSwitch = Boolean(taskId && options.forceSwitchTaskId === taskId
|
|
2642
|
+
const forceSwitch = Boolean(taskId && (options.forceSwitchTaskId === taskId
|
|
2643
|
+
|| options.forceSwitchTaskId === current?.remoteSessionId));
|
|
2266
2644
|
const taskActions = !taskId ? []
|
|
2267
2645
|
: remoteActive && !botOwnsActiveTurn && !forceSwitch
|
|
2268
2646
|
? [statusCardAction("Stop", "danger", "session_stop", taskId, contextKey)]
|
|
@@ -2281,11 +2659,9 @@ export class ProxySessionController {
|
|
|
2281
2659
|
else
|
|
2282
2660
|
await this.outbound.sendInteractiveCard(contextKey, card);
|
|
2283
2661
|
}
|
|
2284
|
-
async statusForCodexTask(contextKey,
|
|
2285
|
-
const
|
|
2286
|
-
|
|
2287
|
-
throw new Error("当前 Codex 运行时不支持读取外部任务状态。");
|
|
2288
|
-
const remote = await runtime.readRemoteSession(remoteSessionId);
|
|
2662
|
+
async statusForCodexTask(contextKey, reference, options = {}) {
|
|
2663
|
+
const { agentName, remote } = await this.resolveRemoteCodexSession(reference);
|
|
2664
|
+
const actionReference = remoteSessionReference(agentName, remote.id);
|
|
2289
2665
|
const sections = [
|
|
2290
2666
|
{
|
|
2291
2667
|
title: "指定任务",
|
|
@@ -2293,6 +2669,7 @@ export class ProxySessionController {
|
|
|
2293
2669
|
`**标题**:${cardCode(remote.title ?? remote.preview ?? "未命名任务")}`,
|
|
2294
2670
|
`**工作目录**:${cardCode(remote.cwd || "目录未知")}`,
|
|
2295
2671
|
`**状态 / 当前任务**:${remoteSessionDetailStatus(remote)} / 未切换`,
|
|
2672
|
+
`**Agent**:${cardCode(agentName)}`,
|
|
2296
2673
|
`**Codex 任务 ID**:${cardCode(remote.id)}`,
|
|
2297
2674
|
`**最近回合**:${cardCode(remote.lastTurnId ?? "无")} ${remoteTurnStatusLabel(remote.lastTurnStatus)}`,
|
|
2298
2675
|
`**创建时间 / 最近活动**:${formatRemoteTime(remote.createdAt)} / ${formatRemoteTime(latestRemoteTimestamp(remote.recencyAt, remote.updatedAt))}`,
|
|
@@ -2312,10 +2689,12 @@ export class ProxySessionController {
|
|
|
2312
2689
|
},
|
|
2313
2690
|
];
|
|
2314
2691
|
const title = `Codex 状态:${truncateText((remote.title ?? remote.preview ?? remote.id).replace(/\s+/g, " "), 40)}`;
|
|
2315
|
-
const showStop = isRemoteSessionActive(remote)
|
|
2692
|
+
const showStop = isRemoteSessionActive(remote)
|
|
2693
|
+
&& options.forceSwitchTaskId !== actionReference
|
|
2694
|
+
&& options.forceSwitchTaskId !== remote.id;
|
|
2316
2695
|
const actions = [
|
|
2317
|
-
statusRefreshAction(
|
|
2318
|
-
statusCardAction(showStop ? "Stop" : "Switch", showStop ? "danger" : "default", showStop ? "session_stop" : "session_switch",
|
|
2696
|
+
statusRefreshAction(actionReference, contextKey),
|
|
2697
|
+
statusCardAction(showStop ? "Stop" : "Switch", showStop ? "danger" : "default", showStop ? "session_stop" : "session_switch", actionReference, contextKey),
|
|
2319
2698
|
];
|
|
2320
2699
|
const card = this.cardRenderer.renderSectionsCard(title, sections, actions);
|
|
2321
2700
|
if (options.updateMessageId)
|
|
@@ -2338,7 +2717,7 @@ export class ProxySessionController {
|
|
|
2338
2717
|
title: "任务管理",
|
|
2339
2718
|
lines: [
|
|
2340
2719
|
"**/new [title] [--dir <cwd> | --nodir]** 使用默认 Agent 创建任务;--nodir 强制创建 Projectless 任务",
|
|
2341
|
-
"**/newgroup [title]**
|
|
2720
|
+
"**/newgroup [title] [--dir <cwd> | --nodir]** 创建飞书群和新任务;参数与 /new 相同",
|
|
2342
2721
|
"**/forkgroup [title]** 从当前任务最新已完成轮次创建分支并绑定到新群",
|
|
2343
2722
|
"**/fork [序号或任务 ID]** 从当前或指定任务创建分支;运行中使用最近已完成轮次",
|
|
2344
2723
|
"**/title <新标题>** 修改当前任务的标题",
|
|
@@ -2353,22 +2732,23 @@ export class ProxySessionController {
|
|
|
2353
2732
|
"**/stop** 停止当前执行",
|
|
2354
2733
|
"**/queue <prompt>**(兼容 **/nosteer**) 不追加到当前轮次,按顺序排队为后续轮次",
|
|
2355
2734
|
"**/goal [目标]** 查看或创建长任务 Goal;支持 pause、resume、edit、clear",
|
|
2356
|
-
"**/
|
|
2357
|
-
"**/
|
|
2358
|
-
"**/
|
|
2735
|
+
"**/provider** 选择 AI 服务提供商;没有其他候选时仅显示当前 Provider",
|
|
2736
|
+
"**/model** 选择当前任务使用的模型",
|
|
2737
|
+
"**/thinking** 设置模型的思考强度",
|
|
2738
|
+
"**/permissions** 设置执行工具前是否需要确认",
|
|
2359
2739
|
],
|
|
2360
2740
|
},
|
|
2361
2741
|
{
|
|
2362
2742
|
title: "Agent",
|
|
2363
2743
|
lines: [
|
|
2364
|
-
"**/agent [name]**
|
|
2744
|
+
"**/agent [name]** 选择新任务使用的默认 Agent;仅配置一个时直接显示当前 Agent",
|
|
2365
2745
|
],
|
|
2366
2746
|
},
|
|
2367
2747
|
{
|
|
2368
2748
|
title: "系统",
|
|
2369
2749
|
lines: [
|
|
2370
2750
|
"**/status [序号或任务 ID]** 查看当前或指定任务的详细状态",
|
|
2371
|
-
"**/restart**
|
|
2751
|
+
"**/restart [--force]** 默认安全重启;--force 立即重启",
|
|
2372
2752
|
"**/help** 显示本帮助",
|
|
2373
2753
|
],
|
|
2374
2754
|
},
|
|
@@ -2381,10 +2761,13 @@ export class ProxySessionController {
|
|
|
2381
2761
|
throw new Error(`未知 agent:${agentName}`);
|
|
2382
2762
|
return agent;
|
|
2383
2763
|
}
|
|
2764
|
+
agentLabel(agentName) {
|
|
2765
|
+
return this.ensureAgent(agentName).title;
|
|
2766
|
+
}
|
|
2384
2767
|
isCodexSession(session) {
|
|
2385
2768
|
if (session.runtimeKind)
|
|
2386
2769
|
return session.runtimeKind === "codex";
|
|
2387
|
-
return this.config.agents[session.agentName]?.kind === "
|
|
2770
|
+
return this.config.agents[session.agentName]?.kind === "app-server";
|
|
2388
2771
|
}
|
|
2389
2772
|
currentSession(contextKey) {
|
|
2390
2773
|
const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent);
|
|
@@ -2400,6 +2783,17 @@ export class ProxySessionController {
|
|
|
2400
2783
|
? createProjectlessWorkspace().cwd
|
|
2401
2784
|
: current.cwd;
|
|
2402
2785
|
}
|
|
2786
|
+
inheritedExecutionSettings(contextKey, agentName) {
|
|
2787
|
+
const current = this.currentSession(contextKey);
|
|
2788
|
+
if (!current || current.agentName !== agentName)
|
|
2789
|
+
return {};
|
|
2790
|
+
return {
|
|
2791
|
+
modelProvider: current.modelProvider,
|
|
2792
|
+
model: current.model,
|
|
2793
|
+
reasoningEffort: current.reasoningEffort,
|
|
2794
|
+
permissionMode: current.permissionMode,
|
|
2795
|
+
};
|
|
2796
|
+
}
|
|
2403
2797
|
currentProjectCwd(contextKey) {
|
|
2404
2798
|
const current = this.currentSession(contextKey);
|
|
2405
2799
|
return current && !detectProjectlessWorkspace(current.cwd) ? current.cwd : undefined;
|
|
@@ -2454,10 +2848,10 @@ function statusRefreshAction(sessionId, contextKey) {
|
|
|
2454
2848
|
function mergeTaskList(localSessions, remoteSessions, currentLocalSessionId) {
|
|
2455
2849
|
const localByRemoteId = new Map(localSessions
|
|
2456
2850
|
.filter((session) => session.runtimeKind === "codex" && session.remoteSessionId)
|
|
2457
|
-
.map((session) => [session.remoteSessionId, session]));
|
|
2851
|
+
.map((session) => [agentRemoteKey(session.agentName, session.remoteSessionId), session]));
|
|
2458
2852
|
const representedLocalIds = new Set();
|
|
2459
|
-
const entries = remoteSessions.map((remote) => {
|
|
2460
|
-
const local = localByRemoteId.get(remote.id);
|
|
2853
|
+
const entries = remoteSessions.map(({ agentName, session: remote }) => {
|
|
2854
|
+
const local = localByRemoteId.get(agentRemoteKey(agentName, remote.id));
|
|
2461
2855
|
if (local)
|
|
2462
2856
|
representedLocalIds.add(local.localSessionId);
|
|
2463
2857
|
const active = remote.status === "active" || remote.lastTurnStatus === "inProgress";
|
|
@@ -2466,7 +2860,9 @@ function mergeTaskList(localSessions, remoteSessions, currentLocalSessionId) {
|
|
|
2466
2860
|
: remoteSessionStatusLabel(remote.status);
|
|
2467
2861
|
const recencyAt = remote.recencyAt ?? remote.updatedAt;
|
|
2468
2862
|
return {
|
|
2863
|
+
reference: remoteSessionReference(agentName, remote.id),
|
|
2469
2864
|
id: remote.id,
|
|
2865
|
+
agentName,
|
|
2470
2866
|
title: remote.title ?? remote.preview ?? local?.title ?? "未命名任务",
|
|
2471
2867
|
cwd: remote.cwd || local?.cwd || "",
|
|
2472
2868
|
status,
|
|
@@ -2481,7 +2877,11 @@ function mergeTaskList(localSessions, remoteSessions, currentLocalSessionId) {
|
|
|
2481
2877
|
continue;
|
|
2482
2878
|
const updatedAt = Date.parse(local.updatedAt);
|
|
2483
2879
|
entries.push({
|
|
2880
|
+
reference: local.remoteSessionId
|
|
2881
|
+
? remoteSessionReference(local.agentName, local.remoteSessionId)
|
|
2882
|
+
: local.localSessionId,
|
|
2484
2883
|
id: local.remoteSessionId ?? local.localSessionId,
|
|
2884
|
+
agentName: local.agentName,
|
|
2485
2885
|
title: local.title ?? "未命名任务",
|
|
2486
2886
|
cwd: local.cwd,
|
|
2487
2887
|
status: sessionStatusLabel(local.status),
|
|
@@ -2493,6 +2893,31 @@ function mergeTaskList(localSessions, remoteSessions, currentLocalSessionId) {
|
|
|
2493
2893
|
}
|
|
2494
2894
|
return entries;
|
|
2495
2895
|
}
|
|
2896
|
+
function agentRemoteKey(agentName, remoteSessionId) {
|
|
2897
|
+
return `${agentName}\u0000${remoteSessionId}`;
|
|
2898
|
+
}
|
|
2899
|
+
function remoteSessionReference(agentName, remoteSessionId) {
|
|
2900
|
+
return `${REMOTE_SESSION_REFERENCE_PREFIX}${encodeURIComponent(agentName)}:${encodeURIComponent(remoteSessionId)}`;
|
|
2901
|
+
}
|
|
2902
|
+
function parseRemoteSessionReference(reference) {
|
|
2903
|
+
if (!reference.startsWith(REMOTE_SESSION_REFERENCE_PREFIX))
|
|
2904
|
+
return undefined;
|
|
2905
|
+
const value = reference.slice(REMOTE_SESSION_REFERENCE_PREFIX.length);
|
|
2906
|
+
const separator = value.indexOf(":");
|
|
2907
|
+
if (separator <= 0 || separator === value.length - 1)
|
|
2908
|
+
return undefined;
|
|
2909
|
+
try {
|
|
2910
|
+
const agentName = decodeURIComponent(value.slice(0, separator));
|
|
2911
|
+
const remoteSessionId = decodeURIComponent(value.slice(separator + 1));
|
|
2912
|
+
return agentName && remoteSessionId ? { agentName, remoteSessionId } : undefined;
|
|
2913
|
+
}
|
|
2914
|
+
catch {
|
|
2915
|
+
return undefined;
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
function runtimeErrorMessage(error) {
|
|
2919
|
+
return error instanceof Error ? error.message : String(error);
|
|
2920
|
+
}
|
|
2496
2921
|
function isMissingRolloutError(error) {
|
|
2497
2922
|
const message = error instanceof Error ? error.message : String(error);
|
|
2498
2923
|
return /no rollout found|rollout[^\n]*(?:not found|missing)|thread\/resume failed/i.test(message);
|
|
@@ -2671,6 +3096,21 @@ function cardText(value) {
|
|
|
2671
3096
|
function cardCode(value) {
|
|
2672
3097
|
return `\`${value.replaceAll("`", "'")}\``;
|
|
2673
3098
|
}
|
|
3099
|
+
function permissionModeValue(value) {
|
|
3100
|
+
if (value === "auto" || value === "confirm")
|
|
3101
|
+
return value;
|
|
3102
|
+
throw new Error("权限模式只能是 auto 或 confirm。");
|
|
3103
|
+
}
|
|
3104
|
+
function executionSettingsTabValue(value) {
|
|
3105
|
+
if (value === "agent" || value === "provider" || value === "model" || value === "thinking" || value === "permission")
|
|
3106
|
+
return value;
|
|
3107
|
+
throw new Error("设置卡片的 tab 无效,请重新发送设置命令。");
|
|
3108
|
+
}
|
|
3109
|
+
function requiredCardMessageId(value) {
|
|
3110
|
+
if (value)
|
|
3111
|
+
return value;
|
|
3112
|
+
throw new Error("无法更新设置卡片,请重新发送设置命令。");
|
|
3113
|
+
}
|
|
2674
3114
|
function remoteSessionStatusLabel(status) {
|
|
2675
3115
|
const labels = {
|
|
2676
3116
|
active: "执行中",
|
|
@@ -2707,6 +3147,10 @@ function mergeRemoteTaskStatus(record, remote) {
|
|
|
2707
3147
|
...record,
|
|
2708
3148
|
title: remote.title ?? record.title,
|
|
2709
3149
|
cwd: remote.cwd || record.cwd,
|
|
3150
|
+
modelProvider: remote.modelProvider ?? record.modelProvider,
|
|
3151
|
+
model: remote.model ?? record.model,
|
|
3152
|
+
reasoningEffort: remote.reasoningEffort ?? record.reasoningEffort,
|
|
3153
|
+
permissionMode: remote.permissionMode ?? record.permissionMode,
|
|
2710
3154
|
status: isRemoteSessionActive(remote)
|
|
2711
3155
|
? "running"
|
|
2712
3156
|
: remote.lastTurnStatus
|
|
@@ -2758,12 +3202,8 @@ function isQueueIndependentCommand(command) {
|
|
|
2758
3202
|
return true;
|
|
2759
3203
|
if (command.type === "agent")
|
|
2760
3204
|
return command.agent === undefined;
|
|
2761
|
-
if (
|
|
2762
|
-
return
|
|
2763
|
-
if (command.type === "thinking")
|
|
2764
|
-
return command.effort === undefined;
|
|
2765
|
-
if (command.type === "permissions")
|
|
2766
|
-
return command.mode === undefined;
|
|
3205
|
+
if (["model", "provider", "thinking", "permissions"].includes(command.type))
|
|
3206
|
+
return true;
|
|
2767
3207
|
return false;
|
|
2768
3208
|
}
|
|
2769
3209
|
function parseSessionVisibleCount(value) {
|
|
@@ -2771,7 +3211,7 @@ function parseSessionVisibleCount(value) {
|
|
|
2771
3211
|
return Number.isSafeInteger(parsed) && parsed >= SESSION_PAGE_SIZE ? parsed : SESSION_PAGE_SIZE;
|
|
2772
3212
|
}
|
|
2773
3213
|
function isPromptCommand(command) {
|
|
2774
|
-
return command.type === "
|
|
3214
|
+
return command.type === "prompt" || command.type === "nosteer";
|
|
2775
3215
|
}
|
|
2776
3216
|
function runtimePrompt(text, localImagePaths) {
|
|
2777
3217
|
return localImagePaths?.length ? { text, localImagePaths } : text;
|
|
@@ -2783,8 +3223,8 @@ function formatGroupDateSuffix(date) {
|
|
|
2783
3223
|
}
|
|
2784
3224
|
function formatNewGroupName(agentName, projectCwd, taskTitle, date, includeTimestamp) {
|
|
2785
3225
|
const prefix = `[${agentName}] `;
|
|
2786
|
-
const
|
|
2787
|
-
const titlePrefix = `${prefix}${
|
|
3226
|
+
const projectPrefix = projectCwd ? `${formatGroupProjectDirectory(projectCwd)} ` : "";
|
|
3227
|
+
const titlePrefix = `${prefix}${projectPrefix}`;
|
|
2788
3228
|
const timestampSuffix = includeTimestamp ? ` (${formatGroupDateSuffix(date)})` : "";
|
|
2789
3229
|
const availableTitleLength = FEISHU_GROUP_NAME_MAX_LENGTH
|
|
2790
3230
|
- Array.from(titlePrefix).length
|
|
@@ -2795,7 +3235,7 @@ function formatNewGroupName(agentName, projectCwd, taskTitle, date, includeTimes
|
|
|
2795
3235
|
const FEISHU_GROUP_NAME_MAX_LENGTH = 60;
|
|
2796
3236
|
const GROUP_PROJECT_DIRECTORY_MAX_LENGTH = 15;
|
|
2797
3237
|
function formatGroupProjectDirectory(projectCwd) {
|
|
2798
|
-
const value =
|
|
3238
|
+
const value = abbreviateHomeDirectory(projectCwd);
|
|
2799
3239
|
if (Array.from(value).length <= GROUP_PROJECT_DIRECTORY_MAX_LENGTH)
|
|
2800
3240
|
return `[${value}]`;
|
|
2801
3241
|
const levels = value
|