@keyou007/agent-bot 0.1.24-alpha.1 → 0.1.24-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +94 -0
- package/README.md +33 -7
- package/README.zh.md +32 -7
- package/dist/acp/AcpJsonRpcConnection.js +3 -1
- package/dist/acp/AcpJsonRpcConnection.js.map +1 -1
- package/dist/cli/SelfUpdateRunner.js +2 -0
- package/dist/cli/SelfUpdateRunner.js.map +1 -1
- package/dist/cli/SelfUpdater.js +2 -0
- package/dist/cli/SelfUpdater.js.map +1 -1
- package/dist/codex/AppServerConnection.js +3 -1
- package/dist/codex/AppServerConnection.js.map +1 -1
- package/dist/codex/CodexEventMapper.js +41 -3
- package/dist/codex/CodexEventMapper.js.map +1 -1
- package/dist/codex/CodexRuntime.js +95 -3
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/commands/CommandRouter.js +5 -0
- package/dist/commands/CommandRouter.js.map +1 -1
- package/dist/feishu/CardRenderer.js +49 -23
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuConnector.js +2 -0
- package/dist/feishu/FeishuConnector.js.map +1 -1
- package/dist/feishu/FeishuMarkdown.js +70 -4
- package/dist/feishu/FeishuMarkdown.js.map +1 -1
- package/dist/feishu/FeishuMessageClient.js +44 -0
- package/dist/feishu/FeishuMessageClient.js.map +1 -1
- package/dist/feishu/FeishuTurnPresenter.js +114 -37
- package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
- package/dist/feishu/RecentMessageContext.js +70 -0
- package/dist/feishu/RecentMessageContext.js.map +1 -0
- package/dist/index.js +62 -24
- package/dist/index.js.map +1 -1
- package/dist/local-files/DiagramPreview.js +171 -0
- package/dist/local-files/DiagramPreview.js.map +1 -0
- package/dist/local-files/LocalFileViewerServer.js +141 -3
- package/dist/local-files/LocalFileViewerServer.js.map +1 -1
- package/dist/local-files/PreviewSyntaxHighlight.js +57 -0
- package/dist/local-files/PreviewSyntaxHighlight.js.map +1 -0
- package/dist/local-files/TurnPreviewPage.js +499 -60
- package/dist/local-files/TurnPreviewPage.js.map +1 -1
- package/dist/presentation/OutboundRouter.js +18 -9
- package/dist/presentation/OutboundRouter.js.map +1 -1
- package/dist/presentation/TurnStateReducer.js +157 -8
- package/dist/presentation/TurnStateReducer.js.map +1 -1
- package/dist/presentation/turnReasoning.js +23 -0
- package/dist/presentation/turnReasoning.js.map +1 -0
- package/dist/proxy/ProxySessionController.js +461 -49
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/state/StateStore.js +43 -2
- package/dist/state/StateStore.js.map +1 -1
- package/dist/state/TurnPreviewJournal.js +469 -0
- package/dist/state/TurnPreviewJournal.js.map +1 -0
- package/dist/state/migrations.js +10 -0
- package/dist/state/migrations.js.map +1 -1
- package/dist/updates/AutomaticUpdatePreparer.js +16 -7
- package/dist/updates/AutomaticUpdatePreparer.js.map +1 -1
- package/dist/updates/ManualUpdateController.js +102 -0
- package/dist/updates/ManualUpdateController.js.map +1 -0
- package/dist/updates/PublishedRelease.js +42 -4
- package/dist/updates/PublishedRelease.js.map +1 -1
- package/dist/utils/ProtocolLineWarning.js +15 -0
- package/dist/utils/ProtocolLineWarning.js.map +1 -0
- package/docs/technical-reference.md +21 -1
- package/docs/technical-reference.zh.md +21 -1
- package/npm-shrinkwrap.json +1401 -10
- package/package.json +3 -1
- package/skills/agent-bot/SKILL.md +9 -2
- package/src/acp/AcpJsonRpcConnection.ts +3 -1
- package/src/cli/SelfUpdateRunner.ts +3 -0
- package/src/cli/SelfUpdater.ts +2 -0
- package/src/codex/AppServerConnection.ts +3 -1
- package/src/codex/CodexEventMapper.ts +40 -3
- package/src/codex/CodexRuntime.ts +91 -4
- package/src/commands/CommandRouter.ts +4 -0
- package/src/commands/commandTypes.ts +1 -0
- package/src/feishu/CardRenderer.ts +47 -21
- package/src/feishu/FeishuConnector.ts +2 -0
- package/src/feishu/FeishuMarkdown.ts +69 -3
- package/src/feishu/FeishuMessageClient.ts +44 -0
- package/src/feishu/FeishuTurnPresenter.ts +107 -31
- package/src/feishu/RecentMessageContext.ts +90 -0
- package/src/feishu/types.ts +5 -0
- package/src/index.ts +62 -22
- package/src/local-files/DiagramPreview.ts +173 -0
- package/src/local-files/LocalFileViewerServer.ts +129 -2
- package/src/local-files/PreviewSyntaxHighlight.ts +58 -0
- package/src/local-files/TurnPreviewPage.ts +539 -57
- package/src/presentation/OutboundRouter.ts +25 -7
- package/src/presentation/TurnStateReducer.ts +153 -9
- package/src/presentation/turnReasoning.ts +21 -0
- package/src/presentation/turnViewTypes.ts +19 -1
- package/src/proxy/ProxySessionController.ts +453 -40
- package/src/runtime/types.ts +21 -0
- package/src/state/StateStore.ts +45 -2
- package/src/state/TurnPreviewJournal.ts +398 -0
- package/src/state/migrations.ts +10 -0
- package/src/updates/AutomaticUpdatePreparer.ts +21 -7
- package/src/updates/ManualUpdateController.ts +116 -0
- package/src/updates/PublishedRelease.ts +49 -3
- package/src/utils/ProtocolLineWarning.ts +13 -0
|
@@ -15,9 +15,11 @@ import { CardUpdateScheduler } from "../feishu/CardUpdateScheduler.js";
|
|
|
15
15
|
import { generateGroupAvatarPng, resolveGroupAvatarProjectName } from "../feishu/GroupAvatarGenerator.js";
|
|
16
16
|
import { formatGroupNameDate, formatNewGroupName, parseTaskNameFromGroupName, } from "../feishu/GroupNameFormatter.js";
|
|
17
17
|
import { normalizeFeishuPostText } from "../feishu/InboundText.js";
|
|
18
|
+
import { RECENT_CONTEXT_LIMIT, RECENT_CONTEXT_IMAGE_LIMIT, RECENT_CONTEXT_TEXT_LIMIT, recentContextPrompt } from "../feishu/RecentMessageContext.js";
|
|
18
19
|
import { allowsFeishuUser } from "../feishu/ownerAccess.js";
|
|
19
20
|
import { classifyFileContent } from "../local-files/LocalFileViewerServer.js";
|
|
20
21
|
import { errorLogValue } from "../logging/errorLogValue.js";
|
|
22
|
+
import { createTurnViewState, hydrateTurnViewState } from "../presentation/TurnStateReducer.js";
|
|
21
23
|
import { buildTurnGraphRows } from "../presentation/turnGraph.js";
|
|
22
24
|
import { isActiveShellCommandJob, ShellCommandJobManager, } from "../shell/ShellCommandJobManager.js";
|
|
23
25
|
import { createId } from "../utils/id.js";
|
|
@@ -62,6 +64,7 @@ const FORK_GROUP_STARTED_MESSAGE = "正在创建分支群聊,请稍后。";
|
|
|
62
64
|
const LIST_SESSIONS_STARTED_MESSAGE = "正在读取任务列表,请稍后。";
|
|
63
65
|
const LIST_TURNS_STARTED_MESSAGE = "正在读取历史轮次,请稍后。";
|
|
64
66
|
const SWITCH_TASK_STARTED_MESSAGE = "正在切换任务,请稍后。";
|
|
67
|
+
const SWITCH_GROUP_STARTED_MESSAGE = "正在创建群聊并绑定所选任务,请稍后。";
|
|
65
68
|
const STATUS_STARTED_MESSAGE = "正在读取任务状态,请稍后。";
|
|
66
69
|
const GOAL_STARTED_MESSAGE = "正在处理 Goal,请稍后。";
|
|
67
70
|
const ARCHIVE_TASK_STARTED_MESSAGE = "正在归档任务,请稍后。";
|
|
@@ -190,6 +193,7 @@ const HELP_COMMAND_SECTIONS = [
|
|
|
190
193
|
usage: "[--force]",
|
|
191
194
|
description: "默认安全重启;--force 立即重启",
|
|
192
195
|
},
|
|
196
|
+
{ command: "/update", description: "查看正式版和 Alpha 版更新,选择版本安全更新" },
|
|
193
197
|
{
|
|
194
198
|
command: "/release",
|
|
195
199
|
description: "释放 Agent Bot 占用的 App Server 任务",
|
|
@@ -220,6 +224,7 @@ export class ProxySessionController {
|
|
|
220
224
|
router = new CommandRouter();
|
|
221
225
|
cardRenderer = new CardRenderer();
|
|
222
226
|
messageQueues = new Map();
|
|
227
|
+
switchGroupOperations = new Set();
|
|
223
228
|
sessionLoads = new Map();
|
|
224
229
|
queuedPromptStarts = new Map();
|
|
225
230
|
queuedPromptCards = new Map();
|
|
@@ -228,6 +233,7 @@ export class ProxySessionController {
|
|
|
228
233
|
resetHistoryOperations = new Map();
|
|
229
234
|
remoteTurnHistoryRefreshes = new Map();
|
|
230
235
|
turnHistoryHydrations = new Map();
|
|
236
|
+
turnDetailHydrations = new Map();
|
|
231
237
|
forkHistoryCursors = new Map();
|
|
232
238
|
lastSessionListings = new Map();
|
|
233
239
|
threadInitializations = new Map();
|
|
@@ -536,15 +542,24 @@ export class ProxySessionController {
|
|
|
536
542
|
let executableImagePaths = localImagePaths;
|
|
537
543
|
let executableMessage = message;
|
|
538
544
|
let injectedRootMessageId;
|
|
545
|
+
let recentContextIds = [];
|
|
539
546
|
if (command.type === "prompt") {
|
|
540
547
|
await this.ensureThreadFork(message);
|
|
541
548
|
const prepared = await this.prepareTopicRootPrompt(message, command.text, localImagePaths);
|
|
542
|
-
|
|
543
|
-
|
|
549
|
+
const recent = await this.prepareMutedGroupContext(message, prepared.text, prepared.localImagePaths);
|
|
550
|
+
executableCommand = { ...command, text: recent.text };
|
|
551
|
+
executableImagePaths = recent.localImagePaths;
|
|
552
|
+
recentContextIds = recent.messageIds;
|
|
544
553
|
executableMessage = { ...message, displayText: prepared.displayPrompt };
|
|
545
554
|
injectedRootMessageId = prepared.injectedRootMessageId;
|
|
546
555
|
}
|
|
547
556
|
await this.execute(message.contextKey, executableCommand, message.messageId, replyTarget, executableImagePaths, message.userId, executableMessage);
|
|
557
|
+
if (command.type === "prompt" && message.chatType === "group" && message.mentionedBot
|
|
558
|
+
&& this.store.chatRequiresMention(baseChatContextKey(message.contextKey))) {
|
|
559
|
+
const current = this.currentSession(message.contextKey);
|
|
560
|
+
if (current)
|
|
561
|
+
this.store.rememberRecentContextMessages(current.localSessionId, current.remoteSessionId ?? "", message.contextKey, message.messageId, [...recentContextIds, message.messageId, ...[referencedMessageId, injectedRootMessageId].filter((id) => Boolean(id))]);
|
|
562
|
+
}
|
|
548
563
|
if (injectedRootMessageId) {
|
|
549
564
|
const current = this.currentSession(message.contextKey);
|
|
550
565
|
if (current) {
|
|
@@ -609,11 +624,18 @@ export class ProxySessionController {
|
|
|
609
624
|
throw new Error("自动更新功能未启用。");
|
|
610
625
|
await this.lifecycle.cancelAutomaticUpdate(scopedAction);
|
|
611
626
|
}
|
|
627
|
+
else if (kind === "agentbot_update_select") {
|
|
628
|
+
if (!this.lifecycle?.selectUpdate)
|
|
629
|
+
throw new Error("当前运行方式不支持在线更新。");
|
|
630
|
+
await this.lifecycle.selectUpdate(scopedAction, replyTarget);
|
|
631
|
+
}
|
|
612
632
|
else if (kind === "help_command") {
|
|
613
633
|
await this.executeHelpCommandAction(scopedAction, contextKey, replyTarget);
|
|
614
634
|
}
|
|
615
635
|
else if (kind === "turn_details") {
|
|
616
|
-
|
|
636
|
+
const turnId = String(scopedAction.value.turnId ?? "");
|
|
637
|
+
await this.loadTurnDetails(turnId);
|
|
638
|
+
await this.outbound.showDetails(contextKey, turnId);
|
|
617
639
|
}
|
|
618
640
|
else if (kind === "activity_history") {
|
|
619
641
|
const requestedPage = String(scopedAction.value.page ?? "0");
|
|
@@ -745,6 +767,10 @@ export class ProxySessionController {
|
|
|
745
767
|
await this.forkSessionReference(contextKey, sessionId);
|
|
746
768
|
await this.refreshSessionsCardFromAction(scopedAction, { page: 0 });
|
|
747
769
|
}
|
|
770
|
+
else if (kind === "session_switch_group") {
|
|
771
|
+
await this.switchSessionReferenceToFeishuGroup(contextKey, String(scopedAction.value.sessionId ?? ""), scopedAction.userId);
|
|
772
|
+
await this.refreshSessionsCardFromAction(scopedAction);
|
|
773
|
+
}
|
|
748
774
|
else if (kind === "session_fork_group") {
|
|
749
775
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
750
776
|
await this.forkSessionReferenceToFeishuGroup(contextKey, sessionId, scopedAction.userId);
|
|
@@ -789,6 +815,21 @@ export class ProxySessionController {
|
|
|
789
815
|
await this.refreshSessionsCardFromAction(scopedAction, { forceSwitchTaskId: sessionId });
|
|
790
816
|
}
|
|
791
817
|
}
|
|
818
|
+
else if (kind === "session_turns" || kind === "session_turns_page") {
|
|
819
|
+
const sessionReference = String(scopedAction.value.sessionId ?? "").trim();
|
|
820
|
+
if (!sessionReference)
|
|
821
|
+
throw new Error("任务轮次卡片无效,请重新发送 /sessions。");
|
|
822
|
+
if (kind === "session_turns")
|
|
823
|
+
await this.outbound.sendText(contextKey, LIST_TURNS_STARTED_MESSAGE);
|
|
824
|
+
await this.openResetHistory(contextKey, {
|
|
825
|
+
sessionReference,
|
|
826
|
+
...(kind === "session_turns_page" ? {
|
|
827
|
+
updateMessageId: requiredCardMessageId(scopedAction.messageId),
|
|
828
|
+
page: resetHistoryPageValue(scopedAction.value.page),
|
|
829
|
+
cursor: typeof scopedAction.value.cursor === "string" ? scopedAction.value.cursor : undefined,
|
|
830
|
+
} : {}),
|
|
831
|
+
});
|
|
832
|
+
}
|
|
792
833
|
else if (kind === "session_status") {
|
|
793
834
|
await this.outbound.sendText(contextKey, STATUS_STARTED_MESSAGE);
|
|
794
835
|
await this.status(contextKey, String(scopedAction.value.sessionId ?? ""));
|
|
@@ -1054,7 +1095,7 @@ export class ProxySessionController {
|
|
|
1054
1095
|
?? { ...record, contextKey: responseContextKey };
|
|
1055
1096
|
const previous = this.messageQueues.get(responseContextKey) ?? Promise.resolve();
|
|
1056
1097
|
const next = previous.catch(() => undefined).then(async () => {
|
|
1057
|
-
this.outbound.registerSession(scopedRecord.localSessionId, responseContextKey, scopedRecord.title, scopedRecord.cwd, this.agentLabel(scopedRecord.agentName), scopedRecord.model);
|
|
1098
|
+
this.outbound.registerSession(scopedRecord.localSessionId, responseContextKey, scopedRecord.title, scopedRecord.cwd, this.agentLabel(scopedRecord.agentName), scopedRecord.model, scopedRecord.modelProvider);
|
|
1058
1099
|
const promptMessageId = await this.outbound.withReplyTarget(responseContextKey, existingReplyTarget, () => this.outbound.sendText(responseContextKey, promptText));
|
|
1059
1100
|
const turnReplyTarget = isThreadContextKey(responseContextKey)
|
|
1060
1101
|
? promptMessageId
|
|
@@ -1256,7 +1297,7 @@ export class ProxySessionController {
|
|
|
1256
1297
|
await loaded.runtime.setReasoningEffort(record.localSessionId, nextEffort);
|
|
1257
1298
|
}
|
|
1258
1299
|
this.store.updateRuntimeSession(record.localSessionId, { model: nextValue, reasoningEffort: nextEffort });
|
|
1259
|
-
this.outbound.updateSessionModel(record.localSessionId, nextValue);
|
|
1300
|
+
this.outbound.updateSessionModel(record.localSessionId, nextValue, loaded.session.modelProvider);
|
|
1260
1301
|
}
|
|
1261
1302
|
else if (setting === "thinking" && nextValue) {
|
|
1262
1303
|
const models = await loaded.runtime.listModels(loaded.session.modelProvider, loaded.session.model);
|
|
@@ -1580,6 +1621,11 @@ export class ProxySessionController {
|
|
|
1580
1621
|
throw new Error("当前运行方式不支持自动重启。");
|
|
1581
1622
|
await this.lifecycle.restart(contextKey, command.force === true, replyTarget);
|
|
1582
1623
|
return;
|
|
1624
|
+
case "update":
|
|
1625
|
+
if (!this.lifecycle?.showUpdates)
|
|
1626
|
+
throw new Error("当前运行方式不支持检查更新。");
|
|
1627
|
+
await this.lifecycle.showUpdates(contextKey, userId);
|
|
1628
|
+
return;
|
|
1583
1629
|
case "release":
|
|
1584
1630
|
await this.requestAppServerRelease(contextKey);
|
|
1585
1631
|
return;
|
|
@@ -1600,7 +1646,7 @@ export class ProxySessionController {
|
|
|
1600
1646
|
await this.openExecutionSettings(contextKey, "model");
|
|
1601
1647
|
return;
|
|
1602
1648
|
case "provider":
|
|
1603
|
-
await this.
|
|
1649
|
+
await this.openExecutionSettings(contextKey, "provider");
|
|
1604
1650
|
return;
|
|
1605
1651
|
case "thinking":
|
|
1606
1652
|
await this.openExecutionSettings(contextKey, "thinking");
|
|
@@ -1628,7 +1674,7 @@ export class ProxySessionController {
|
|
|
1628
1674
|
: context.contextKey;
|
|
1629
1675
|
if (!this.outbound.canRoute(turnContextKey))
|
|
1630
1676
|
continue;
|
|
1631
|
-
this.outbound.registerSession(session.localSessionId, turnContextKey, session.title, session.cwd, this.agentLabel(session.agentName), session.model);
|
|
1677
|
+
this.outbound.registerSession(session.localSessionId, turnContextKey, session.title, session.cwd, this.agentLabel(session.agentName), session.model, session.modelProvider);
|
|
1632
1678
|
if (!session.lastTurnId)
|
|
1633
1679
|
continue;
|
|
1634
1680
|
void this.outbound.resumeDelivery(session.localSessionId, turnContextKey, session.lastTurnId).catch((error) => {
|
|
@@ -1702,7 +1748,7 @@ export class ProxySessionController {
|
|
|
1702
1748
|
continue;
|
|
1703
1749
|
}
|
|
1704
1750
|
if (session) {
|
|
1705
|
-
this.outbound.registerSession(session.localSessionId, attempt.contextKey, session.title, session.cwd, this.agentLabel(session.agentName), session.model);
|
|
1751
|
+
this.outbound.registerSession(session.localSessionId, attempt.contextKey, session.title, session.cwd, this.agentLabel(session.agentName), session.model, session.modelProvider);
|
|
1706
1752
|
}
|
|
1707
1753
|
try {
|
|
1708
1754
|
await this.handleRuntimeEvent({
|
|
@@ -1814,7 +1860,7 @@ export class ProxySessionController {
|
|
|
1814
1860
|
const replyTarget = attempt.replyMessageId
|
|
1815
1861
|
? { messageId: attempt.replyMessageId, replyInThread: true }
|
|
1816
1862
|
: undefined;
|
|
1817
|
-
this.outbound.registerSession(session.localSessionId, contextKey, session.title, session.cwd, this.agentLabel(session.agentName), session.model);
|
|
1863
|
+
this.outbound.registerSession(session.localSessionId, contextKey, session.title, session.cwd, this.agentLabel(session.agentName), session.model, session.modelProvider);
|
|
1818
1864
|
if (announce) {
|
|
1819
1865
|
await this.outbound.withReplyTarget(contextKey, replyTarget, () => this.outbound.sendText(contextKey, "检测到任务在 Agent Bot 重启前尚未完成,正在自动恢复。")).catch((error) => {
|
|
1820
1866
|
this.logger.warn({ error, attemptId: attempt.attemptId, contextKey }, "Failed to send the task recovery notification.");
|
|
@@ -2016,8 +2062,8 @@ export class ProxySessionController {
|
|
|
2016
2062
|
}
|
|
2017
2063
|
const configuredRuntime = this.runtimes.forAgent(record.agentName);
|
|
2018
2064
|
if (!configuredRuntime.getSession(record.localSessionId)) {
|
|
2019
|
-
this.outbound.registerSession(record.localSessionId, contextKey, record.title, record.cwd, this.agentLabel(record.agentName), record.model);
|
|
2020
|
-
await this.outbound.startPendingTurn(record.localSessionId, contextKey, record.title, replyTarget, displayPrompt ?? text);
|
|
2065
|
+
this.outbound.registerSession(record.localSessionId, contextKey, record.title, record.cwd, this.agentLabel(record.agentName), record.model, record.modelProvider);
|
|
2066
|
+
await this.outbound.startPendingTurn(record.localSessionId, contextKey, record.title, replyTarget, displayPrompt ?? text, localImagePaths);
|
|
2021
2067
|
}
|
|
2022
2068
|
let loaded;
|
|
2023
2069
|
try {
|
|
@@ -2060,7 +2106,7 @@ export class ProxySessionController {
|
|
|
2060
2106
|
}
|
|
2061
2107
|
try {
|
|
2062
2108
|
await loaded.runtime.steerTurn(record.localSessionId, activeTurnId, runtimePrompt(text, localImagePaths));
|
|
2063
|
-
await this.presentSteerMessage(record.localSessionId, activeTurnId, displayPrompt ?? text, messageId);
|
|
2109
|
+
await this.presentSteerMessage(record.localSessionId, activeTurnId, displayPrompt ?? text, messageId, localImagePaths);
|
|
2064
2110
|
if (messageId)
|
|
2065
2111
|
await this.bindMessageReactionToTurn(messageId, record.localSessionId, activeTurnId);
|
|
2066
2112
|
return;
|
|
@@ -2083,7 +2129,7 @@ export class ProxySessionController {
|
|
|
2083
2129
|
if (current.activeTurnId !== activeTurnId) {
|
|
2084
2130
|
try {
|
|
2085
2131
|
await loaded.runtime.steerTurn(record.localSessionId, current.activeTurnId, runtimePrompt(text, localImagePaths));
|
|
2086
|
-
await this.presentSteerMessage(record.localSessionId, current.activeTurnId, displayPrompt ?? text, messageId);
|
|
2132
|
+
await this.presentSteerMessage(record.localSessionId, current.activeTurnId, displayPrompt ?? text, messageId, localImagePaths);
|
|
2087
2133
|
if (messageId)
|
|
2088
2134
|
await this.bindMessageReactionToTurn(messageId, record.localSessionId, current.activeTurnId);
|
|
2089
2135
|
return;
|
|
@@ -2105,10 +2151,10 @@ export class ProxySessionController {
|
|
|
2105
2151
|
if (messageId)
|
|
2106
2152
|
await this.bindMessageReactionToTurn(messageId, record.localSessionId, turnId);
|
|
2107
2153
|
}
|
|
2108
|
-
async presentSteerMessage(localSessionId, turnId, text, messageId) {
|
|
2154
|
+
async presentSteerMessage(localSessionId, turnId, text, messageId, localImagePaths) {
|
|
2109
2155
|
this.store.touchTurnAttempt(turnId);
|
|
2110
2156
|
try {
|
|
2111
|
-
await this.outbound.appendSteerMessage(localSessionId, turnId, text, messageId);
|
|
2157
|
+
await this.outbound.appendSteerMessage(localSessionId, turnId, text, messageId, localImagePaths);
|
|
2112
2158
|
}
|
|
2113
2159
|
catch (error) {
|
|
2114
2160
|
this.logger.warn({ error, sessionId: localSessionId, turnId, messageId }, "Failed to insert a steer message into the thinking card.");
|
|
@@ -2239,6 +2285,72 @@ export class ProxySessionController {
|
|
|
2239
2285
|
}
|
|
2240
2286
|
}
|
|
2241
2287
|
}
|
|
2288
|
+
async prepareMutedGroupContext(message, text, localImagePaths) {
|
|
2289
|
+
if (message.chatType !== "group" || !message.chatId || message.mentionedBot !== true
|
|
2290
|
+
|| !this.store.chatRequiresMention(baseChatContextKey(message.contextKey))) {
|
|
2291
|
+
return { text, localImagePaths, messageIds: [] };
|
|
2292
|
+
}
|
|
2293
|
+
if (message.threadContext && !message.threadId)
|
|
2294
|
+
throw new Error("无法识别当前话题,已停止补充上下文以避免读取其他话题。");
|
|
2295
|
+
try {
|
|
2296
|
+
const current = this.currentSession(message.contextKey);
|
|
2297
|
+
const stopIds = current ? new Set([
|
|
2298
|
+
...this.store.recentContextMessageIds(current.localSessionId, current.remoteSessionId ?? "", message.contextKey, false),
|
|
2299
|
+
...this.store.recentSubmittedMessageIds(current.localSessionId, current.remoteSessionId ?? "", message.contextKey),
|
|
2300
|
+
]) : new Set();
|
|
2301
|
+
const messages = await this.outbound.readRecentMessages(message.contextKey, {
|
|
2302
|
+
chatId: message.chatId,
|
|
2303
|
+
...(message.threadId ? { threadId: message.threadId } : {}),
|
|
2304
|
+
beforeMessageId: message.messageId,
|
|
2305
|
+
beforeTimestamp: message.createdAt,
|
|
2306
|
+
...(stopIds.size ? { stopBeforeMessageIds: [...stopIds] } : {}),
|
|
2307
|
+
});
|
|
2308
|
+
const seen = current ? this.store.recentContextMessageIds(current.localSessionId, current.remoteSessionId ?? "", message.contextKey) : new Set();
|
|
2309
|
+
seen.add(message.messageId);
|
|
2310
|
+
if (message.parentMessageId)
|
|
2311
|
+
seen.add(message.parentMessageId);
|
|
2312
|
+
if (message.threadContext && message.rootMessageId)
|
|
2313
|
+
seen.add(message.rootMessageId);
|
|
2314
|
+
const selected = [];
|
|
2315
|
+
const imagePaths = [...(localImagePaths ?? [])];
|
|
2316
|
+
const imageKeys = new Set();
|
|
2317
|
+
let remaining = RECENT_CONTEXT_TEXT_LIMIT;
|
|
2318
|
+
let imageCount = 0;
|
|
2319
|
+
for (const item of messages.slice(-RECENT_CONTEXT_LIMIT).reverse()) {
|
|
2320
|
+
if (stopIds.has(item.messageId) || remaining <= 0)
|
|
2321
|
+
break;
|
|
2322
|
+
if (seen.has(item.messageId))
|
|
2323
|
+
continue;
|
|
2324
|
+
seen.add(item.messageId);
|
|
2325
|
+
const limit = Math.min(2_000, remaining);
|
|
2326
|
+
let body = item.text.length > limit ? `${item.text.slice(0, limit)}\n[消息文本已截断]` : item.text;
|
|
2327
|
+
remaining -= Math.min(item.text.length, limit);
|
|
2328
|
+
for (const [index, image] of item.images.entries()) {
|
|
2329
|
+
if (imageKeys.has(image.imageKey))
|
|
2330
|
+
continue;
|
|
2331
|
+
imageKeys.add(image.imageKey);
|
|
2332
|
+
if (imageCount >= RECENT_CONTEXT_IMAGE_LIMIT) {
|
|
2333
|
+
body += `\n[图片 ${index + 1} 未附带:已达到近期图片上限]`;
|
|
2334
|
+
continue;
|
|
2335
|
+
}
|
|
2336
|
+
const imagePath = await this.outbound.downloadImage(message.contextKey, image.messageId, image.imageKey);
|
|
2337
|
+
imagePaths.push(imagePath);
|
|
2338
|
+
imageCount++;
|
|
2339
|
+
body += `\n[图片 ${index + 1} 已附带:${imagePath}]`;
|
|
2340
|
+
}
|
|
2341
|
+
selected.unshift({ ...item, text: body });
|
|
2342
|
+
}
|
|
2343
|
+
return {
|
|
2344
|
+
text: recentContextPrompt(text, selected),
|
|
2345
|
+
...(imagePaths.length ? { localImagePaths: [...new Set(imagePaths)] } : {}),
|
|
2346
|
+
messageIds: selected.map((item) => item.messageId),
|
|
2347
|
+
};
|
|
2348
|
+
}
|
|
2349
|
+
catch (error) {
|
|
2350
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2351
|
+
throw new Error(`无法补充近期群聊上下文(包括图片):${detail}。请检查飞书历史消息/群消息读取权限及机器人是否在群内,或附图重试。`);
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2242
2354
|
async prepareTopicRootPrompt(message, text, localImagePaths) {
|
|
2243
2355
|
const displayPrompt = message.displayText ?? text;
|
|
2244
2356
|
const rootMessageId = topicRootMessageId(message);
|
|
@@ -2309,7 +2421,7 @@ export class ProxySessionController {
|
|
|
2309
2421
|
permissionMode: source.permissionMode ?? "auto",
|
|
2310
2422
|
});
|
|
2311
2423
|
this.store.setCurrentSession(message.contextKey, localSessionId);
|
|
2312
|
-
this.outbound.registerSession(localSessionId, message.contextKey, forkTitle, source.cwd, this.agentLabel(source.agentName), source.model);
|
|
2424
|
+
this.outbound.registerSession(localSessionId, message.contextKey, forkTitle, source.cwd, this.agentLabel(source.agentName), source.model, source.modelProvider);
|
|
2313
2425
|
try {
|
|
2314
2426
|
const forked = await runtime.forkSession({
|
|
2315
2427
|
localSessionId,
|
|
@@ -2607,7 +2719,7 @@ export class ProxySessionController {
|
|
|
2607
2719
|
lastTurnId: plan.lastTurnId,
|
|
2608
2720
|
lastTurnStatus: plan.lastTurnStatus,
|
|
2609
2721
|
});
|
|
2610
|
-
this.outbound.registerSession(localSessionId, contextKey, forked.title ?? plan.forkTitle, plan.cwd, this.agentLabel(plan.agentName), plan.model);
|
|
2722
|
+
this.outbound.registerSession(localSessionId, contextKey, forked.title ?? plan.forkTitle, plan.cwd, this.agentLabel(plan.agentName), forked.model ?? plan.model, forked.modelProvider ?? plan.modelProvider);
|
|
2611
2723
|
this.store.audit(contextKey, "session_forked", {
|
|
2612
2724
|
sourceLocalSessionId: plan.source?.localSessionId,
|
|
2613
2725
|
sourceRemoteSessionId: plan.remoteSessionId,
|
|
@@ -2647,6 +2759,7 @@ export class ProxySessionController {
|
|
|
2647
2759
|
sessionId: input.localSessionId,
|
|
2648
2760
|
turnId: turn.id,
|
|
2649
2761
|
prompt: turn.prompt ?? "未记录对话内容",
|
|
2762
|
+
historyDetail: "summary",
|
|
2650
2763
|
status: "completed",
|
|
2651
2764
|
startedAt,
|
|
2652
2765
|
completedAt,
|
|
@@ -2963,8 +3076,9 @@ export class ProxySessionController {
|
|
|
2963
3076
|
while (true) {
|
|
2964
3077
|
let pendingTurnId;
|
|
2965
3078
|
try {
|
|
2966
|
-
|
|
2967
|
-
|
|
3079
|
+
const session = loaded.runtime.getSession(loaded.record.localSessionId) ?? loaded.session;
|
|
3080
|
+
this.outbound.updateSessionModel(loaded.record.localSessionId, session.model, session.modelProvider);
|
|
3081
|
+
pendingTurnId = await this.outbound.startPendingTurn(loaded.record.localSessionId, loaded.record.contextKey, title, replyTarget, cardPrompt, localImagePaths);
|
|
2968
3082
|
}
|
|
2969
3083
|
catch (error) {
|
|
2970
3084
|
this.store.updateTurnAttempt(attemptId, {
|
|
@@ -3056,7 +3170,7 @@ export class ProxySessionController {
|
|
|
3056
3170
|
}
|
|
3057
3171
|
if (activate)
|
|
3058
3172
|
this.store.setCurrentSession(contextKey, localSessionId);
|
|
3059
|
-
this.outbound.registerSession(localSessionId, contextKey, initialTitle, sessionCwd, this.agentLabel(agentName), resolvedExecutionSettings.model);
|
|
3173
|
+
this.outbound.registerSession(localSessionId, contextKey, initialTitle, sessionCwd, this.agentLabel(agentName), resolvedExecutionSettings.model, resolvedExecutionSettings.modelProvider);
|
|
3060
3174
|
const runtime = this.runtimes.forAgent(agentName);
|
|
3061
3175
|
try {
|
|
3062
3176
|
if (prepareTurn) {
|
|
@@ -3073,7 +3187,7 @@ export class ProxySessionController {
|
|
|
3073
3187
|
permissionMode: resolvedExecutionSettings.permissionMode ?? "auto",
|
|
3074
3188
|
});
|
|
3075
3189
|
this.persistRuntimeSession(record, session, session.activeTurnId ? "running" : "ready");
|
|
3076
|
-
this.outbound.updateSessionModel(localSessionId, session.model);
|
|
3190
|
+
this.outbound.updateSessionModel(localSessionId, session.model, session.modelProvider);
|
|
3077
3191
|
const saved = this.store.getSession(localSessionId) ?? record;
|
|
3078
3192
|
if (announce) {
|
|
3079
3193
|
const task = initialTitle ? `${initialTitle}(${session.remoteSessionId})` : session.remoteSessionId;
|
|
@@ -3109,6 +3223,111 @@ export class ProxySessionController {
|
|
|
3109
3223
|
const boundProjectCwd = detectProjectlessWorkspace(resolved.cwd) ? undefined : resolved.cwd;
|
|
3110
3224
|
await this.createFeishuGroupWithTask(sourceContextKey, resolved.agentName, undefined, userId, boundProjectCwd, resolved.executionSettings);
|
|
3111
3225
|
}
|
|
3226
|
+
async switchSessionReferenceToFeishuGroup(sourceContextKey, reference, userId) {
|
|
3227
|
+
if (!userId?.startsWith("ou_"))
|
|
3228
|
+
throw new Error("SwitchGroup 只能由具有 open_id 的飞书用户触发。");
|
|
3229
|
+
if (!reference.trim())
|
|
3230
|
+
throw new Error("任务无效,请重新发送 /sessions。");
|
|
3231
|
+
await this.outbound.sendText(sourceContextKey, SWITCH_GROUP_STARTED_MESSAGE);
|
|
3232
|
+
const taskId = this.resolveSessionReference(sourceContextKey, reference);
|
|
3233
|
+
let task = this.store.getSession(taskId) ?? this.findStoredSessionByReference(taskId);
|
|
3234
|
+
if (task?.status === "closed")
|
|
3235
|
+
throw new Error(`找不到任务:${taskId}`);
|
|
3236
|
+
if (task && (!this.isCodexSession(task) || !task.remoteSessionId)) {
|
|
3237
|
+
throw new Error("指定任务不是可切换的 App Server 任务。");
|
|
3238
|
+
}
|
|
3239
|
+
let remote;
|
|
3240
|
+
let agentName;
|
|
3241
|
+
let runtime;
|
|
3242
|
+
if (task) {
|
|
3243
|
+
agentName = task.agentName;
|
|
3244
|
+
runtime = this.runtimes.forAgent(agentName);
|
|
3245
|
+
await this.assertSessionTurnOwnership(task, runtime);
|
|
3246
|
+
}
|
|
3247
|
+
else {
|
|
3248
|
+
const resolved = await this.resolveRemoteCodexSession(taskId);
|
|
3249
|
+
({ agentName, runtime, remote } = resolved);
|
|
3250
|
+
if (isRemoteSessionActive(remote)) {
|
|
3251
|
+
throw new Error("这个任务正在外部 Agent 中执行,请等待完成后再使用 SwitchGroup。");
|
|
3252
|
+
}
|
|
3253
|
+
}
|
|
3254
|
+
const remoteSessionId = task?.remoteSessionId ?? remote.id;
|
|
3255
|
+
const operationKey = agentRemoteKey(agentName, remoteSessionId);
|
|
3256
|
+
if (this.switchGroupOperations.has(operationKey))
|
|
3257
|
+
throw new Error("正在为此任务创建群聊,请勿重复点击。");
|
|
3258
|
+
const cwd = task?.cwd ?? remote.cwd;
|
|
3259
|
+
if (!cwd)
|
|
3260
|
+
throw new Error("指定任务没有可用的工作目录,暂时无法创建群聊。");
|
|
3261
|
+
const title = task?.title ?? remote?.title ?? remote?.preview ?? remoteSessionId;
|
|
3262
|
+
const boundProjectCwd = detectProjectlessWorkspace(cwd) ? undefined : cwd;
|
|
3263
|
+
this.switchGroupOperations.add(operationKey);
|
|
3264
|
+
try {
|
|
3265
|
+
const group = await this.createFeishuGroupContext(sourceContextKey, agentName, title, userId, boundProjectCwd, "SwitchGroup");
|
|
3266
|
+
try {
|
|
3267
|
+
const latest = task ? undefined : await runtime.readRemoteSession(remoteSessionId);
|
|
3268
|
+
if (latest && isRemoteSessionActive(latest))
|
|
3269
|
+
throw new Error("所选任务已在外部 Agent 中开始执行,请等待完成后切换。");
|
|
3270
|
+
// Group creation may take time; reuse any canonical task linked in the meantime.
|
|
3271
|
+
task = this.store.findSessionByRemoteSessionId(remoteSessionId, undefined, agentName) ?? task;
|
|
3272
|
+
if (task) {
|
|
3273
|
+
task = this.store.getSession(task.localSessionId);
|
|
3274
|
+
if (!task || task.status === "closed")
|
|
3275
|
+
throw new Error("所选任务已归档,请重新选择任务。");
|
|
3276
|
+
await this.assertSessionTurnOwnership(task, runtime);
|
|
3277
|
+
task = this.store.getSession(task.localSessionId);
|
|
3278
|
+
if (!task || task.status === "closed" || task.remoteSessionId !== remoteSessionId) {
|
|
3279
|
+
throw new Error("所选任务已变化,请重新选择任务。");
|
|
3280
|
+
}
|
|
3281
|
+
this.store.attachSessionToContext(group.contextKey, task.localSessionId);
|
|
3282
|
+
}
|
|
3283
|
+
else {
|
|
3284
|
+
if (!latest)
|
|
3285
|
+
throw new Error("无法读取所选任务,请重试。");
|
|
3286
|
+
const localSessionId = createId("sess");
|
|
3287
|
+
this.store.createSession({ localSessionId, contextKey: group.contextKey, agentName, cwd, status: "ready" });
|
|
3288
|
+
this.store.updateRuntimeSession(localSessionId, {
|
|
3289
|
+
runtimeKind: "codex",
|
|
3290
|
+
remoteSessionId,
|
|
3291
|
+
title: latest.title ?? latest.preview ?? title,
|
|
3292
|
+
modelProvider: latest.modelProvider ?? remote?.modelProvider,
|
|
3293
|
+
model: latest.model ?? remote?.model,
|
|
3294
|
+
reasoningEffort: latest.reasoningEffort ?? remote?.reasoningEffort,
|
|
3295
|
+
permissionMode: latest.permissionMode ?? remote?.permissionMode ?? "auto",
|
|
3296
|
+
lastTurnId: latest.lastTurnId ?? remote?.lastTurnId,
|
|
3297
|
+
lastTurnStatus: mapRemoteTurnStatus(latest.lastTurnStatus ?? remote?.lastTurnStatus),
|
|
3298
|
+
});
|
|
3299
|
+
task = this.store.getSession(localSessionId);
|
|
3300
|
+
}
|
|
3301
|
+
this.store.setCurrentSession(group.contextKey, task.localSessionId);
|
|
3302
|
+
// A live Turn keeps its original delivery route. The new group's next Turn
|
|
3303
|
+
// establishes its own route through startPendingTurn.
|
|
3304
|
+
if (task.status !== "running" && !runtime.getSession(task.localSessionId)?.activeTurnId) {
|
|
3305
|
+
this.outbound.registerSession(task.localSessionId, group.contextKey, task.title, task.cwd, this.agentLabel(agentName), task.model, task.modelProvider);
|
|
3306
|
+
}
|
|
3307
|
+
this.store.audit(sourceContextKey, "session_switch_group", {
|
|
3308
|
+
localSessionId: task.localSessionId, remoteSessionId, groupContextKey: group.contextKey,
|
|
3309
|
+
});
|
|
3310
|
+
}
|
|
3311
|
+
catch (error) {
|
|
3312
|
+
await this.outbound.sendText(group.contextKey, `群已创建,但绑定任务失败:${runtimeErrorMessage(error)}。可使用 /sessions 重新选择任务。`)
|
|
3313
|
+
.catch((noticeError) => this.logger.warn({ error: noticeError, contextKey: group.contextKey }, "Failed to report SwitchGroup binding failure."));
|
|
3314
|
+
throw new Error(`群 ${group.name} 已创建,但绑定任务失败:${runtimeErrorMessage(error)}`);
|
|
3315
|
+
}
|
|
3316
|
+
await this.outbound.sendText(group.contextKey, [
|
|
3317
|
+
"已切换到已有任务,历史与原会话共享。",
|
|
3318
|
+
`当前任务:${task.title ?? title}(${remoteSessionId})`,
|
|
3319
|
+
`当前 Project 目录:${boundProjectCwd ?? "未绑定(Projectless)"}`,
|
|
3320
|
+
`当前 Provider:${task.modelProvider ?? "Agent 默认"}`,
|
|
3321
|
+
`当前模型:${task.model ?? "默认"}`,
|
|
3322
|
+
`思考强度:${task.reasoningEffort ?? "自动"}`,
|
|
3323
|
+
`权限类型:${task.permissionMode === "confirm" ? "执行前确认" : "自动执行"}`,
|
|
3324
|
+
].join("\n"));
|
|
3325
|
+
await this.outbound.sendText(sourceContextKey, `已创建飞书群:${group.name},并切换到任务 ${task.title ?? remoteSessionId}。原会话不变。`);
|
|
3326
|
+
}
|
|
3327
|
+
finally {
|
|
3328
|
+
this.switchGroupOperations.delete(operationKey);
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3112
3331
|
async createFeishuGroupWithTask(sourceContextKey, agentName, requestedTitle, userId, boundProjectCwd, executionSettings) {
|
|
3113
3332
|
const explicitTitle = normalizeTaskTitle(requestedTitle);
|
|
3114
3333
|
const taskTitle = explicitTitle ?? `新任务 (${formatGroupNameDate(new Date(), this.config.feishu?.groupNameFormat?.dateFormat ?? DEFAULT_GROUP_NAME_FORMAT.dateFormat)})`;
|
|
@@ -3298,7 +3517,7 @@ export class ProxySessionController {
|
|
|
3298
3517
|
record: { ...loaded.record, contextKey: record.contextKey },
|
|
3299
3518
|
};
|
|
3300
3519
|
}
|
|
3301
|
-
this.outbound.registerSession(record.localSessionId, record.contextKey, record.title, record.cwd, this.agentLabel(record.agentName), record.model);
|
|
3520
|
+
this.outbound.registerSession(record.localSessionId, record.contextKey, record.title, record.cwd, this.agentLabel(record.agentName), record.model, record.modelProvider);
|
|
3302
3521
|
const loading = (async () => {
|
|
3303
3522
|
if (record.lastTurnId) {
|
|
3304
3523
|
try {
|
|
@@ -3949,7 +4168,7 @@ export class ProxySessionController {
|
|
|
3949
4168
|
lastTurnId: turnId,
|
|
3950
4169
|
lastTurnStatus: "completed",
|
|
3951
4170
|
});
|
|
3952
|
-
this.outbound.registerSession(current.localSessionId, contextKey, forked.title ?? current.title, current.cwd, this.agentLabel(current.agentName), current.model);
|
|
4171
|
+
this.outbound.registerSession(current.localSessionId, contextKey, forked.title ?? current.title, current.cwd, this.agentLabel(current.agentName), forked.model ?? current.model, forked.modelProvider ?? current.modelProvider);
|
|
3953
4172
|
this.store.audit(contextKey, "session_reset_to_turn", {
|
|
3954
4173
|
localSessionId: current.localSessionId,
|
|
3955
4174
|
previousRemoteSessionId,
|
|
@@ -4046,20 +4265,6 @@ export class ProxySessionController {
|
|
|
4046
4265
|
}
|
|
4047
4266
|
await this.openExecutionSettings(contextKey, "agent");
|
|
4048
4267
|
}
|
|
4049
|
-
async openProviderSettings(contextKey) {
|
|
4050
|
-
const loaded = await this.loadSession(this.requireCurrentSession(contextKey));
|
|
4051
|
-
const providers = loaded.runtime.kind === "codex" && loaded.runtime.listModelProviders
|
|
4052
|
-
? await this.modelProviderOptions(loaded)
|
|
4053
|
-
: [];
|
|
4054
|
-
const current = loaded.session.modelProvider?.trim()
|
|
4055
|
-
|| providers.find((provider) => provider.isDefault)?.id
|
|
4056
|
-
|| "运行时默认";
|
|
4057
|
-
if (providers.length <= 1) {
|
|
4058
|
-
await this.outbound.sendText(contextKey, `当前 Provider:${current}\n当前没有其他 Provider 可以切换。`);
|
|
4059
|
-
return;
|
|
4060
|
-
}
|
|
4061
|
-
await this.openExecutionSettings(contextKey, "provider", { sessionId: loaded.record.localSessionId });
|
|
4062
|
-
}
|
|
4063
4268
|
async selectProvider(contextKey, modelProvider, options = {}) {
|
|
4064
4269
|
const record = options.sessionId
|
|
4065
4270
|
? this.requireSession(contextKey, options.sessionId)
|
|
@@ -4194,7 +4399,7 @@ export class ProxySessionController {
|
|
|
4194
4399
|
}
|
|
4195
4400
|
};
|
|
4196
4401
|
const session = await loaded.runtime.setExecutionSettings(loaded.record.localSessionId, settings, persist);
|
|
4197
|
-
this.outbound.updateSessionModel(loaded.record.localSessionId, session.model);
|
|
4402
|
+
this.outbound.updateSessionModel(loaded.record.localSessionId, session.model, session.modelProvider);
|
|
4198
4403
|
return session;
|
|
4199
4404
|
}
|
|
4200
4405
|
async assertProviderModelSettings(loaded, modelProvider, model, effort) {
|
|
@@ -4256,7 +4461,7 @@ export class ProxySessionController {
|
|
|
4256
4461
|
await loaded.runtime.setReasoningEffort(loaded.record.localSessionId, nextEffort);
|
|
4257
4462
|
}
|
|
4258
4463
|
this.store.updateRuntimeSession(loaded.record.localSessionId, { model, reasoningEffort: nextEffort });
|
|
4259
|
-
this.outbound.updateSessionModel(loaded.record.localSessionId, model);
|
|
4464
|
+
this.outbound.updateSessionModel(loaded.record.localSessionId, model, loaded.session.modelProvider);
|
|
4260
4465
|
await this.persistAgentExecutionDefaults(loaded.record.localSessionId);
|
|
4261
4466
|
const effortMessage = nextEffort && nextEffort !== currentEffort
|
|
4262
4467
|
? `,思考强度已自动调整为 ${nextEffort}`
|
|
@@ -4706,6 +4911,87 @@ export class ProxySessionController {
|
|
|
4706
4911
|
assistantText: "", plan: [], activities: [], completedTools: [], failedTools: [], fileSummary: [],
|
|
4707
4912
|
};
|
|
4708
4913
|
}
|
|
4914
|
+
async loadTurnDetails(turnId) {
|
|
4915
|
+
const existing = this.turnDetailHydrations.get(turnId);
|
|
4916
|
+
if (existing)
|
|
4917
|
+
return existing;
|
|
4918
|
+
const snapshot = turnViewSnapshot(this.store.getTurnSnapshot(turnId));
|
|
4919
|
+
if ((!snapshot || snapshot.previewJournal) && this.store.previews.has(turnId))
|
|
4920
|
+
return this.store.previews.load(turnId);
|
|
4921
|
+
if (snapshot && !needsHistoricalDetails(snapshot))
|
|
4922
|
+
return snapshot;
|
|
4923
|
+
const origin = !snapshot ? this.store.getTurnRuntimeOrigin(turnId) : undefined;
|
|
4924
|
+
if (!snapshot && (!origin || !this.runtimes.forAgent(origin.agentName).readRemoteTurn))
|
|
4925
|
+
return undefined;
|
|
4926
|
+
const hydration = snapshot ? this.hydrateTurnDetails(snapshot) : this.loadUnrecordedTurnPreview(origin);
|
|
4927
|
+
this.turnDetailHydrations.set(turnId, hydration);
|
|
4928
|
+
try {
|
|
4929
|
+
return await hydration;
|
|
4930
|
+
}
|
|
4931
|
+
finally {
|
|
4932
|
+
if (this.turnDetailHydrations.get(turnId) === hydration)
|
|
4933
|
+
this.turnDetailHydrations.delete(turnId);
|
|
4934
|
+
}
|
|
4935
|
+
}
|
|
4936
|
+
async loadUnrecordedTurnPreview(origin) {
|
|
4937
|
+
const runtime = this.runtimes.forAgent(origin.agentName);
|
|
4938
|
+
if (!runtime.readRemoteTurn)
|
|
4939
|
+
throw new Error("此 Agent 不支持读取历史执行详情。");
|
|
4940
|
+
const details = await runtime.readRemoteTurn(origin.remoteSessionId, origin.turnId);
|
|
4941
|
+
if (details.turnId !== origin.turnId)
|
|
4942
|
+
throw new Error("Agent 返回的轮次与请求不一致,请重试。");
|
|
4943
|
+
let remote;
|
|
4944
|
+
try {
|
|
4945
|
+
remote = await runtime.readRemoteSession?.(origin.remoteSessionId, "metadata");
|
|
4946
|
+
}
|
|
4947
|
+
catch (error) {
|
|
4948
|
+
this.logger.warn({ error, turnId: origin.turnId }, "Failed to read Preview task metadata; retaining full Turn details.");
|
|
4949
|
+
}
|
|
4950
|
+
// An Agent Bot turn may have arrived while the read-only request was in flight.
|
|
4951
|
+
const latest = turnViewSnapshot(this.store.getTurnSnapshot(origin.turnId));
|
|
4952
|
+
if (latest)
|
|
4953
|
+
return latest.previewJournal ? this.store.previews.load(origin.turnId) ?? latest : latest;
|
|
4954
|
+
const state = hydrateTurnViewState(createTurnViewState(origin.localSessionId, origin.turnId, details.startedAt ?? 0, remote?.title, undefined, remote?.cwd, undefined, this.agentLabel(origin.agentName)), details);
|
|
4955
|
+
// Viewing external work must not create a task, a chat binding or a pending final reply.
|
|
4956
|
+
this.store.previews.seed(state);
|
|
4957
|
+
this.store.previews.release(state.turnId);
|
|
4958
|
+
return state;
|
|
4959
|
+
}
|
|
4960
|
+
async hydrateTurnDetails(snapshot) {
|
|
4961
|
+
let updated;
|
|
4962
|
+
try {
|
|
4963
|
+
const origin = this.store.getTurnRuntimeOrigin(snapshot.turnId);
|
|
4964
|
+
if (!origin)
|
|
4965
|
+
throw new Error("未保存本轮的原始 Agent 会话信息,无法读取历史执行详情。");
|
|
4966
|
+
const runtime = this.runtimes.forAgent(origin.agentName);
|
|
4967
|
+
if (!runtime.readRemoteTurn)
|
|
4968
|
+
throw new Error("此 Agent 不支持读取历史执行详情。");
|
|
4969
|
+
const details = await runtime.readRemoteTurn(origin.remoteSessionId, snapshot.turnId);
|
|
4970
|
+
if (details.turnId !== snapshot.turnId)
|
|
4971
|
+
throw new Error("Agent 返回的轮次与请求不一致,请重试。");
|
|
4972
|
+
const session = this.store.getSession(snapshot.sessionId);
|
|
4973
|
+
updated = hydrateTurnViewState({
|
|
4974
|
+
...snapshot,
|
|
4975
|
+
projectCwd: snapshot.projectCwd ?? session?.cwd,
|
|
4976
|
+
agentLabel: snapshot.agentLabel ?? this.agentLabel(origin.agentName),
|
|
4977
|
+
}, details);
|
|
4978
|
+
}
|
|
4979
|
+
catch (error) {
|
|
4980
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4981
|
+
this.logger.warn({ error, turnId: snapshot.turnId }, "Failed to load historical Turn details.");
|
|
4982
|
+
updated = { ...snapshot, historyDetail: "summary", historyDetailError: message };
|
|
4983
|
+
}
|
|
4984
|
+
const latest = turnViewSnapshot(this.store.getTurnSnapshot(snapshot.turnId));
|
|
4985
|
+
if (!latest || !needsHistoricalDetails(latest))
|
|
4986
|
+
return latest;
|
|
4987
|
+
if (updated.historyDetail === "full") {
|
|
4988
|
+
this.store.previews.seed(updated);
|
|
4989
|
+
this.store.previews.flush(updated.turnId, true);
|
|
4990
|
+
updated = { ...updated, previewJournal: true };
|
|
4991
|
+
}
|
|
4992
|
+
this.store.saveTurnSnapshot(snapshot.turnId, snapshot.sessionId, updated);
|
|
4993
|
+
return updated;
|
|
4994
|
+
}
|
|
4709
4995
|
async openTurnDetails(contextKey, reference) {
|
|
4710
4996
|
const current = this.requireCurrentSession(contextKey);
|
|
4711
4997
|
let turnId;
|
|
@@ -4748,10 +5034,23 @@ export class ProxySessionController {
|
|
|
4748
5034
|
await this.outbound.sendInteractiveCard(contextKey, this.cardRenderer.renderTurnDetails(this.runningTurnSnapshot(current, turnId)));
|
|
4749
5035
|
return;
|
|
4750
5036
|
}
|
|
5037
|
+
await this.loadTurnDetails(turnId);
|
|
4751
5038
|
await this.outbound.showDetails(contextKey, turnId);
|
|
4752
5039
|
}
|
|
4753
5040
|
async openResetHistory(contextKey, options = {}) {
|
|
4754
|
-
const
|
|
5041
|
+
const taskId = options.sessionReference
|
|
5042
|
+
? this.resolveSessionReference(contextKey, options.sessionReference)
|
|
5043
|
+
: undefined;
|
|
5044
|
+
const current = taskId
|
|
5045
|
+
? this.store.getSession(taskId) ?? this.findStoredSessionByReference(taskId)
|
|
5046
|
+
: this.requireCurrentSession(contextKey);
|
|
5047
|
+
if (!current && taskId) {
|
|
5048
|
+
await this.openRemoteTurnHistory(contextKey, taskId, options);
|
|
5049
|
+
return;
|
|
5050
|
+
}
|
|
5051
|
+
if (!current || current.status === "closed")
|
|
5052
|
+
throw new Error(`找不到任务:${taskId}`);
|
|
5053
|
+
const readOnly = this.currentSession(contextKey)?.localSessionId !== current.localSessionId;
|
|
4755
5054
|
if (options.expectedSessionId && current.localSessionId !== options.expectedSessionId) {
|
|
4756
5055
|
throw new Error("这张历史轮次卡片不属于当前任务。请先切换回对应任务,再重新发送 /turns。");
|
|
4757
5056
|
}
|
|
@@ -4809,7 +5108,7 @@ export class ProxySessionController {
|
|
|
4809
5108
|
current: isCurrent,
|
|
4810
5109
|
running: isRunning,
|
|
4811
5110
|
resetting: isResetting,
|
|
4812
|
-
actions: isCurrent || isRunning || resettingTurnId ? undefined : [{
|
|
5111
|
+
actions: readOnly || isCurrent || isRunning || resettingTurnId ? undefined : [{
|
|
4813
5112
|
text: "Reset",
|
|
4814
5113
|
value: {
|
|
4815
5114
|
action: "turn_reset",
|
|
@@ -4826,8 +5125,8 @@ export class ProxySessionController {
|
|
|
4826
5125
|
...(page > 0 ? [{
|
|
4827
5126
|
text: "Previous",
|
|
4828
5127
|
value: {
|
|
4829
|
-
action: "turn_reset_page",
|
|
4830
|
-
sessionId: current.localSessionId,
|
|
5128
|
+
action: options.sessionReference ? "session_turns_page" : "turn_reset_page",
|
|
5129
|
+
sessionId: options.sessionReference ?? current.localSessionId,
|
|
4831
5130
|
contextKey,
|
|
4832
5131
|
page: String(page - 1),
|
|
4833
5132
|
},
|
|
@@ -4835,14 +5134,15 @@ export class ProxySessionController {
|
|
|
4835
5134
|
...(page < totalPages - 1 ? [{
|
|
4836
5135
|
text: "Next",
|
|
4837
5136
|
value: {
|
|
4838
|
-
action: "turn_reset_page",
|
|
4839
|
-
sessionId: current.localSessionId,
|
|
5137
|
+
action: options.sessionReference ? "session_turns_page" : "turn_reset_page",
|
|
5138
|
+
sessionId: options.sessionReference ?? current.localSessionId,
|
|
4840
5139
|
contextKey,
|
|
4841
5140
|
page: String(page + 1),
|
|
4842
5141
|
},
|
|
4843
5142
|
}] : []),
|
|
4844
5143
|
];
|
|
4845
5144
|
const card = this.cardRenderer.renderResetHistoryCard({
|
|
5145
|
+
...(options.sessionReference ? { taskTitle: current.title ?? current.remoteSessionId, readOnly } : {}),
|
|
4846
5146
|
entries,
|
|
4847
5147
|
footerLines: [
|
|
4848
5148
|
hasMoreHistory
|
|
@@ -4861,6 +5161,75 @@ export class ProxySessionController {
|
|
|
4861
5161
|
await this.outbound.sendInteractiveCard(contextKey, card);
|
|
4862
5162
|
}
|
|
4863
5163
|
}
|
|
5164
|
+
async openRemoteTurnHistory(contextKey, reference, options) {
|
|
5165
|
+
const { runtime, remote } = await this.resolveRemoteCodexSession(reference);
|
|
5166
|
+
if (!runtime.listRemoteTurnSummaries)
|
|
5167
|
+
throw new Error("当前 Agent 不支持读取历史轮次。");
|
|
5168
|
+
const page = Math.max(0, Math.trunc(options.page ?? 0));
|
|
5169
|
+
const running = remote.lastTurnStatus === "inProgress" && remote.lastTurnId;
|
|
5170
|
+
let cursor = options.cursor;
|
|
5171
|
+
// Next carries its cursor; only backwards navigation needs to locate a page again.
|
|
5172
|
+
if (!cursor && page > 0) {
|
|
5173
|
+
const seen = new Set();
|
|
5174
|
+
for (let index = 0; index < page; index += 1) {
|
|
5175
|
+
const previous = await runtime.listRemoteTurnSummaries(remote.id, {
|
|
5176
|
+
cursor,
|
|
5177
|
+
limit: RESET_HISTORY_PAGE_SIZE - (index === 0 && running ? 1 : 0),
|
|
5178
|
+
});
|
|
5179
|
+
if (!previous.nextCursor || seen.has(previous.nextCursor)) {
|
|
5180
|
+
throw new Error("任务轮次已变化,请从 /sessions 重新打开 Turns。");
|
|
5181
|
+
}
|
|
5182
|
+
cursor = previous.nextCursor;
|
|
5183
|
+
seen.add(cursor);
|
|
5184
|
+
}
|
|
5185
|
+
}
|
|
5186
|
+
const result = await runtime.listRemoteTurnSummaries(remote.id, {
|
|
5187
|
+
cursor,
|
|
5188
|
+
limit: RESET_HISTORY_PAGE_SIZE - (page === 0 && running ? 1 : 0),
|
|
5189
|
+
});
|
|
5190
|
+
if (result.nextCursor && result.nextCursor === cursor) {
|
|
5191
|
+
throw new Error("历史轮次分页游标无效,请稍后重试。");
|
|
5192
|
+
}
|
|
5193
|
+
const turns = [
|
|
5194
|
+
...(page === 0 && running ? [{ id: running, prompt: remote.lastUserPrompt }] : []),
|
|
5195
|
+
...result.turns,
|
|
5196
|
+
];
|
|
5197
|
+
const entries = turns.map((turn, index) => ({
|
|
5198
|
+
sequence: page * RESET_HISTORY_PAGE_SIZE + index + 1,
|
|
5199
|
+
graphNodeLine: "●",
|
|
5200
|
+
graphConnectorLine: index < turns.length - 1 ? "│" : undefined,
|
|
5201
|
+
lines: [cardText(truncateText((turn.prompt ?? "未记录对话内容").replace(/\s+/g, " ").trim(), 100))],
|
|
5202
|
+
timestamp: formatResetTurnTime(turn.completedAt ?? turn.startedAt),
|
|
5203
|
+
current: remote.lastTurnStatus === "completed" && remote.lastTurnId === turn.id,
|
|
5204
|
+
running: running === turn.id,
|
|
5205
|
+
}));
|
|
5206
|
+
const pageAction = (text, targetPage, targetCursor) => ({
|
|
5207
|
+
text,
|
|
5208
|
+
value: {
|
|
5209
|
+
action: "session_turns_page",
|
|
5210
|
+
sessionId: reference,
|
|
5211
|
+
contextKey,
|
|
5212
|
+
page: String(targetPage),
|
|
5213
|
+
...(targetCursor ? { cursor: targetCursor } : {}),
|
|
5214
|
+
},
|
|
5215
|
+
});
|
|
5216
|
+
const card = this.cardRenderer.renderResetHistoryCard({
|
|
5217
|
+
taskTitle: remote.title ?? remote.preview ?? remote.id,
|
|
5218
|
+
readOnly: true,
|
|
5219
|
+
entries,
|
|
5220
|
+
footerLines: [`第 ${page + 1} 页 · 本页 ${entries.length} 个 turn`],
|
|
5221
|
+
pageActions: [
|
|
5222
|
+
...(page > 0 ? [pageAction("Previous", page - 1)] : []),
|
|
5223
|
+
...(result.nextCursor ? [pageAction("Next", page + 1, result.nextCursor)] : []),
|
|
5224
|
+
],
|
|
5225
|
+
});
|
|
5226
|
+
if (options.updateMessageId) {
|
|
5227
|
+
await this.outbound.updateInteractiveCard(contextKey, options.updateMessageId, card);
|
|
5228
|
+
}
|
|
5229
|
+
else {
|
|
5230
|
+
await this.outbound.sendInteractiveCard(contextKey, card);
|
|
5231
|
+
}
|
|
5232
|
+
}
|
|
4864
5233
|
async openDirectoryBrowser(contextKey, requestedDirectory, options = {}) {
|
|
4865
5234
|
if (requestedDirectory === WINDOWS_DRIVES_DIRECTORY) {
|
|
4866
5235
|
await this.openWindowsDriveBrowser(contextKey, options);
|
|
@@ -5132,6 +5501,16 @@ export class ProxySessionController {
|
|
|
5132
5501
|
contextKey,
|
|
5133
5502
|
},
|
|
5134
5503
|
})];
|
|
5504
|
+
actions.push(bindCardAction({
|
|
5505
|
+
text: "SwitchGroup",
|
|
5506
|
+
value: {
|
|
5507
|
+
action: "session_switch_group",
|
|
5508
|
+
sessionId: entry.reference,
|
|
5509
|
+
...(searchTerm ? { searchTerm } : {}),
|
|
5510
|
+
page: String(page),
|
|
5511
|
+
contextKey,
|
|
5512
|
+
},
|
|
5513
|
+
}));
|
|
5135
5514
|
const projectActions = [bindCardAction({
|
|
5136
5515
|
text: "New",
|
|
5137
5516
|
value: {
|
|
@@ -5179,6 +5558,14 @@ export class ProxySessionController {
|
|
|
5179
5558
|
contextKey,
|
|
5180
5559
|
},
|
|
5181
5560
|
}));
|
|
5561
|
+
actions.push(bindCardAction({
|
|
5562
|
+
text: "Turns",
|
|
5563
|
+
value: {
|
|
5564
|
+
action: "session_turns",
|
|
5565
|
+
sessionId: entry.reference,
|
|
5566
|
+
contextKey,
|
|
5567
|
+
},
|
|
5568
|
+
}));
|
|
5182
5569
|
actions.push(bindCardAction({
|
|
5183
5570
|
text: "Archive",
|
|
5184
5571
|
value: {
|
|
@@ -5372,7 +5759,7 @@ export class ProxySessionController {
|
|
|
5372
5759
|
await this.assertSessionTurnOwnership(existing, runtime);
|
|
5373
5760
|
this.store.attachSessionToContext(contextKey, existing.localSessionId);
|
|
5374
5761
|
this.store.setCurrentSession(contextKey, existing.localSessionId);
|
|
5375
|
-
this.outbound.registerSession(existing.localSessionId, contextKey, existing.title, existing.cwd, this.agentLabel(existing.agentName), existing.model);
|
|
5762
|
+
this.outbound.registerSession(existing.localSessionId, contextKey, existing.title, existing.cwd, this.agentLabel(existing.agentName), existing.model, existing.modelProvider);
|
|
5376
5763
|
await this.outbound.sendText(contextKey, `已切换到任务:${existing.title ?? existing.remoteSessionId ?? taskId}`);
|
|
5377
5764
|
return;
|
|
5378
5765
|
}
|
|
@@ -5399,7 +5786,7 @@ export class ProxySessionController {
|
|
|
5399
5786
|
lastTurnStatus: mapRemoteTurnStatus(remote.lastTurnStatus),
|
|
5400
5787
|
});
|
|
5401
5788
|
this.store.setCurrentSession(contextKey, localSessionId);
|
|
5402
|
-
this.outbound.registerSession(localSessionId, contextKey, remote.title ?? remote.preview, remote.cwd, this.agentLabel(agentName), remote.model);
|
|
5789
|
+
this.outbound.registerSession(localSessionId, contextKey, remote.title ?? remote.preview, remote.cwd, this.agentLabel(agentName), remote.model, remote.modelProvider);
|
|
5403
5790
|
await this.outbound.sendText(contextKey, `已切换到任务:${remote.title ?? remote.preview ?? remote.id}。历史消息不会重新发送。`);
|
|
5404
5791
|
}
|
|
5405
5792
|
async stopSessionReference(contextKey, taskId) {
|
|
@@ -5784,6 +6171,9 @@ export class ProxySessionController {
|
|
|
5784
6171
|
const actions = [
|
|
5785
6172
|
statusRefreshAction(taskId, contextKey),
|
|
5786
6173
|
...taskActions,
|
|
6174
|
+
...this.statusPreviewActions(contextKey, activeTurnId ?? current?.lastTurnId, current?.remoteSessionId ? {
|
|
6175
|
+
agentName: current.agentName, remoteSessionId: current.remoteSessionId, localSessionId: current.localSessionId,
|
|
6176
|
+
} : undefined),
|
|
5787
6177
|
];
|
|
5788
6178
|
const card = this.cardRenderer.renderSectionsCard(title, sections, actions);
|
|
5789
6179
|
if (options.updateMessageId)
|
|
@@ -5791,6 +6181,15 @@ export class ProxySessionController {
|
|
|
5791
6181
|
else
|
|
5792
6182
|
await this.outbound.sendInteractiveCard(contextKey, card);
|
|
5793
6183
|
}
|
|
6184
|
+
statusPreviewActions(contextKey, turnId, source) {
|
|
6185
|
+
if (!turnId || turnId.startsWith("pending_"))
|
|
6186
|
+
return [];
|
|
6187
|
+
if (source && this.runtimes.forAgent(source.agentName).readRemoteTurn) {
|
|
6188
|
+
this.store.saveTurnRuntimeOrigin(turnId, source.localSessionId ?? remoteSessionReference(source.agentName, source.remoteSessionId), source.agentName, source.remoteSessionId);
|
|
6189
|
+
}
|
|
6190
|
+
const url = this.outbound.getTurnPreviewUrl(contextKey, turnId);
|
|
6191
|
+
return url ? [{ text: "Preview", value: {}, url }] : [];
|
|
6192
|
+
}
|
|
5794
6193
|
async statusForCodexTask(contextKey, reference, options = {}) {
|
|
5795
6194
|
const { agentName, remote } = await this.resolveRemoteCodexSession(reference, { view: "latest-full" });
|
|
5796
6195
|
const runtime = this.runtimes.forAgent(agentName);
|
|
@@ -5838,6 +6237,7 @@ export class ProxySessionController {
|
|
|
5838
6237
|
const actions = [
|
|
5839
6238
|
statusRefreshAction(actionReference, contextKey),
|
|
5840
6239
|
statusCardAction(showStop ? "Stop" : "Switch", showStop ? "danger" : "default", showStop ? "session_stop" : "session_switch", actionReference, contextKey),
|
|
6240
|
+
...this.statusPreviewActions(contextKey, remote.lastTurnId, { agentName, remoteSessionId: remote.id }),
|
|
5841
6241
|
];
|
|
5842
6242
|
const card = this.cardRenderer.renderSectionsCard(title, sections, actions);
|
|
5843
6243
|
if (options.updateMessageId)
|
|
@@ -5905,7 +6305,7 @@ export class ProxySessionController {
|
|
|
5905
6305
|
}
|
|
5906
6306
|
this.store.setChatRequiresMention(chatContextKey, enabled);
|
|
5907
6307
|
await this.outbound.sendText(contextKey, enabled
|
|
5908
|
-
? "已开启当前群的静音模式。之后只有 @
|
|
6308
|
+
? "已开启当前群的静音模式。之后只有 @ 机器人的消息会被处理;普通提问会补充当前群/话题近期文字和图片(最多 20 条、6 张图片)。发送 @机器人 /mute off 可恢复自动响应并关闭自动补充。"
|
|
5909
6309
|
: "已关闭当前群的静音模式。机器人将恢复自动响应群消息。");
|
|
5910
6310
|
}
|
|
5911
6311
|
requireControlSession(localSessionId) {
|
|
@@ -6638,6 +7038,18 @@ function isUnmaterializedCodexThreadError(error) {
|
|
|
6638
7038
|
const message = error instanceof Error ? error.message : String(error);
|
|
6639
7039
|
return /thread\/read failed:[^\n]*(?:not materialized|not loaded|includeTurns is unavailable before first user message)/i.test(message);
|
|
6640
7040
|
}
|
|
7041
|
+
function needsHistoricalDetails(state) {
|
|
7042
|
+
if (!isTerminalTurnViewStatus(state.status) || state.historyDetail === "full")
|
|
7043
|
+
return false;
|
|
7044
|
+
if (state.historyDetail === "summary")
|
|
7045
|
+
return true;
|
|
7046
|
+
return state.status === "completed" && state.finalResponse === undefined && !state.assistantText
|
|
7047
|
+
&& !state.error && !state.progressText && !state.approval && !state.activeTool
|
|
7048
|
+
&& !state.activities?.length && !state.plan?.length && !state.reasoningItems?.length
|
|
7049
|
+
&& !state.completedTools?.length && !state.failedTools?.length && !state.fileSummary?.length
|
|
7050
|
+
&& !state.totalToolCount && !state.totalTokens
|
|
7051
|
+
&& state.fullToolOutputs === undefined && state.fullToolErrors === undefined && state.toolStatuses === undefined;
|
|
7052
|
+
}
|
|
6641
7053
|
function turnViewSnapshot(value) {
|
|
6642
7054
|
if (!value || typeof value !== "object")
|
|
6643
7055
|
return undefined;
|
|
@@ -6951,7 +7363,7 @@ function isBotOwnedActiveTurn(record, remote) {
|
|
|
6951
7363
|
&& record.lastTurnId === remote.lastTurnId;
|
|
6952
7364
|
}
|
|
6953
7365
|
function isQueueIndependentCommand(command) {
|
|
6954
|
-
if (["archive", "dismiss", "stop", "status", "restart", "release", "mute", "help", "sessions", "dir", "file", "goal", "nosteer", "shell"].includes(command.type))
|
|
7366
|
+
if (["archive", "dismiss", "stop", "status", "restart", "update", "release", "mute", "help", "sessions", "dir", "file", "goal", "nosteer", "shell"].includes(command.type))
|
|
6955
7367
|
return true;
|
|
6956
7368
|
if (command.type === "agent")
|
|
6957
7369
|
return command.agent === undefined;
|