@keyou007/agent-bot 0.1.13-alpha.0 → 0.1.13-alpha.2
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 +2 -2
- package/CHANGELOG.md +15 -0
- package/README.md +107 -98
- package/README.zh.md +81 -72
- package/dist/acp/AcpProcessManager.js +8 -3
- package/dist/acp/AcpProcessManager.js.map +1 -1
- package/dist/acp/AcpSessionManager.js +15 -0
- package/dist/acp/AcpSessionManager.js.map +1 -1
- package/dist/cli/ServerStarter.js +17 -2
- package/dist/cli/ServerStarter.js.map +1 -1
- package/dist/cli/controlProtocol.js.map +1 -1
- package/dist/cli/help.js +16 -4
- package/dist/cli/help.js.map +1 -1
- package/dist/cli/serverStatus.js +27 -0
- package/dist/cli/serverStatus.js.map +1 -1
- package/dist/cli/taskListOutput.js +72 -0
- package/dist/cli/taskListOutput.js.map +1 -0
- package/dist/cli.js +88 -54
- package/dist/cli.js.map +1 -1
- package/dist/codex/CodexProcessManager.js +34 -4
- package/dist/codex/CodexProcessManager.js.map +1 -1
- package/dist/codex/CodexRuntime.js +3 -0
- 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/config/FeishuUserOpenIdStore.js +76 -0
- package/dist/config/FeishuUserOpenIdStore.js.map +1 -0
- package/dist/config/loadConfig.js +3 -0
- package/dist/config/loadConfig.js.map +1 -1
- package/dist/feishu/CardRenderer.js +123 -22
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuConnector.js +16 -5
- package/dist/feishu/FeishuConnector.js.map +1 -1
- package/dist/index.js +125 -26
- package/dist/index.js.map +1 -1
- package/dist/presentation/turnGraph.js +36 -0
- package/dist/presentation/turnGraph.js.map +1 -0
- package/dist/proxy/ProxySessionController.js +425 -104
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/runtime/AcpRuntimeAdapter.js +3 -0
- package/dist/runtime/AcpRuntimeAdapter.js.map +1 -1
- package/dist/runtime/agentEnvironment.js +24 -4
- package/dist/runtime/agentEnvironment.js.map +1 -1
- package/dist/runtime/createAgentRuntimeRegistry.js +7 -3
- package/dist/runtime/createAgentRuntimeRegistry.js.map +1 -1
- package/dist/startup/StartupNotifier.js +16 -7
- package/dist/startup/StartupNotifier.js.map +1 -1
- package/dist/startup/startFeishu.js +2 -2
- package/dist/startup/startFeishu.js.map +1 -1
- package/dist/state/StateStore.js +172 -0
- package/dist/state/StateStore.js.map +1 -1
- package/dist/state/migrations.js +21 -0
- package/dist/state/migrations.js.map +1 -1
- package/dist/supervision/SafeRestartNotifier.js +46 -3
- package/dist/supervision/SafeRestartNotifier.js.map +1 -1
- package/dist/supervision/SafeRestartScheduler.js +65 -6
- package/dist/supervision/SafeRestartScheduler.js.map +1 -1
- package/dist/supervision/replacementSupervisor.js +27 -2
- package/dist/supervision/replacementSupervisor.js.map +1 -1
- package/dist/supervisor.js +27 -2
- package/dist/supervisor.js.map +1 -1
- package/docs/technical-reference.md +20 -7
- package/docs/technical-reference.zh.md +20 -7
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/agent-bot/SKILL.md +63 -26
- package/src/acp/AcpProcessManager.ts +16 -3
- package/src/acp/AcpSessionManager.ts +14 -0
- package/src/cli/ServerStarter.ts +25 -2
- package/src/cli/controlProtocol.ts +6 -1
- package/src/cli/help.ts +16 -4
- package/src/cli/serverStatus.ts +28 -0
- package/src/cli/taskListOutput.ts +91 -0
- package/src/cli.ts +123 -51
- package/src/codex/CodexProcessManager.ts +38 -3
- package/src/codex/CodexRuntime.ts +6 -0
- package/src/commands/CommandRouter.ts +4 -0
- package/src/commands/commandTypes.ts +1 -0
- package/src/config/FeishuUserOpenIdStore.ts +81 -0
- package/src/config/loadConfig.ts +3 -0
- package/src/feishu/CardRenderer.ts +169 -22
- package/src/feishu/FeishuConnector.ts +13 -4
- package/src/index.ts +195 -23
- package/src/presentation/turnGraph.ts +48 -0
- package/src/proxy/ProxySessionController.ts +507 -112
- package/src/runtime/AcpRuntimeAdapter.ts +5 -0
- package/src/runtime/agentEnvironment.ts +41 -3
- package/src/runtime/createAgentRuntimeRegistry.ts +18 -3
- package/src/runtime/types.ts +5 -0
- package/src/startup/StartupNotifier.ts +23 -7
- package/src/startup/startFeishu.ts +3 -2
- package/src/state/StateStore.ts +224 -0
- package/src/state/migrations.ts +21 -0
- package/src/supervision/SafeRestartNotifier.ts +53 -4
- package/src/supervision/SafeRestartScheduler.ts +78 -7
- package/src/supervision/replacementSupervisor.ts +27 -1
- package/src/supervisor.ts +33 -2
|
@@ -17,11 +17,15 @@ import {
|
|
|
17
17
|
CardRenderer,
|
|
18
18
|
type CardSection,
|
|
19
19
|
type ExecutionSettingsTab,
|
|
20
|
+
type HelpCardSection,
|
|
21
|
+
type ResetHistoryCardEntry,
|
|
20
22
|
type TaskListCardAction,
|
|
23
|
+
type TaskListCardEntry,
|
|
21
24
|
} from "../feishu/CardRenderer.js";
|
|
22
25
|
import { generateGroupAvatarPng, resolveGroupAvatarProjectName } from "../feishu/GroupAvatarGenerator.js";
|
|
23
26
|
import type { OutboundRouter } from "../presentation/OutboundRouter.js";
|
|
24
27
|
import type { TurnActivity, TurnViewState } from "../presentation/turnViewTypes.js";
|
|
28
|
+
import { buildTurnGraphRows } from "../presentation/turnGraph.js";
|
|
25
29
|
import type { AgentRuntimeRegistry } from "../runtime/AgentRuntimeRegistry.js";
|
|
26
30
|
import type {
|
|
27
31
|
AgentRuntime,
|
|
@@ -61,8 +65,126 @@ const MESSAGE_COMPLETED_REACTION = "DONE";
|
|
|
61
65
|
const MESSAGE_FAILED_REACTION = "ERROR";
|
|
62
66
|
const MESSAGE_CANCELLED_REACTION = "CrossMark";
|
|
63
67
|
const SESSION_PAGE_SIZE = 5;
|
|
68
|
+
const RESET_HISTORY_PAGE_SIZE = 10;
|
|
64
69
|
const REMOTE_SESSION_REFERENCE_PREFIX = "agent-runtime:";
|
|
65
70
|
|
|
71
|
+
interface HelpCommandDefinition {
|
|
72
|
+
command: string;
|
|
73
|
+
usage?: string;
|
|
74
|
+
description: string;
|
|
75
|
+
requiresArgument?: boolean;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const HELP_COMMAND_SECTIONS: Array<{
|
|
79
|
+
title: string;
|
|
80
|
+
commands: HelpCommandDefinition[];
|
|
81
|
+
}> = [
|
|
82
|
+
{
|
|
83
|
+
title: "任务管理",
|
|
84
|
+
commands: [
|
|
85
|
+
{
|
|
86
|
+
command: "/new",
|
|
87
|
+
usage: "[title] [--dir <cwd> | --nodir]",
|
|
88
|
+
description: "使用默认 Agent 创建任务;--nodir 创建 Projectless 任务",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
command: "/newgroup",
|
|
92
|
+
usage: "[title] [--dir <cwd> | --nodir]",
|
|
93
|
+
description: "创建飞书群和新任务",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
command: "/forkgroup",
|
|
97
|
+
usage: "[title]",
|
|
98
|
+
description: "从当前任务最近完成轮次创建分支群",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
command: "/fork",
|
|
102
|
+
usage: "[序号或任务 ID]",
|
|
103
|
+
description: "从当前或指定任务最近完成轮次创建分支",
|
|
104
|
+
},
|
|
105
|
+
{ command: "/turns", description: "浏览历史轮次,并可 Reset 对话上下文" },
|
|
106
|
+
{
|
|
107
|
+
command: "/title",
|
|
108
|
+
usage: "<新标题>",
|
|
109
|
+
description: "修改当前任务标题",
|
|
110
|
+
requiresArgument: true,
|
|
111
|
+
},
|
|
112
|
+
{ command: "/sessions", usage: "[关键词]", description: "查找本机任务" },
|
|
113
|
+
{
|
|
114
|
+
command: "/switch",
|
|
115
|
+
usage: "[序号或任务 ID]",
|
|
116
|
+
description: "切换任务;不填参数切回上一个任务",
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
title: "执行设置",
|
|
122
|
+
commands: [
|
|
123
|
+
{
|
|
124
|
+
command: "!",
|
|
125
|
+
usage: "<命令>",
|
|
126
|
+
description: "在当前任务目录执行本地命令",
|
|
127
|
+
requiresArgument: true,
|
|
128
|
+
},
|
|
129
|
+
{ command: "/stop", description: "停止当前执行" },
|
|
130
|
+
{
|
|
131
|
+
command: "/queue",
|
|
132
|
+
usage: "<prompt>",
|
|
133
|
+
description: "将 Prompt 排队为后续轮次",
|
|
134
|
+
requiresArgument: true,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
command: "/nosteer",
|
|
138
|
+
usage: "<prompt>",
|
|
139
|
+
description: "与 /queue 相同",
|
|
140
|
+
requiresArgument: true,
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
command: "/goal",
|
|
144
|
+
usage: "<目标或操作>",
|
|
145
|
+
description: "查看或创建长任务 Goal;支持 pause、resume、edit、clear",
|
|
146
|
+
requiresArgument: true,
|
|
147
|
+
},
|
|
148
|
+
{ command: "/provider", description: "选择 AI 服务提供商" },
|
|
149
|
+
{ command: "/model", description: "选择当前任务使用的模型" },
|
|
150
|
+
{ command: "/thinking", description: "设置模型的思考强度" },
|
|
151
|
+
{ command: "/permissions", description: "设置执行工具前是否需要确认" },
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
title: "Agent",
|
|
156
|
+
commands: [
|
|
157
|
+
{
|
|
158
|
+
command: "/agent",
|
|
159
|
+
usage: "[name]",
|
|
160
|
+
description: "选择新任务使用的默认 Agent",
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
title: "系统",
|
|
166
|
+
commands: [
|
|
167
|
+
{
|
|
168
|
+
command: "/status",
|
|
169
|
+
usage: "[序号或任务 ID]",
|
|
170
|
+
description: "查看当前或指定任务状态",
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
command: "/restart",
|
|
174
|
+
usage: "[--force]",
|
|
175
|
+
description: "默认安全重启;--force 立即重启",
|
|
176
|
+
},
|
|
177
|
+
{ command: "/help", description: "显示本帮助" },
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
const HELP_DEFAULT_COMMANDS = new Set(
|
|
183
|
+
HELP_COMMAND_SECTIONS.flatMap((section) => section.commands)
|
|
184
|
+
.filter((command) => !command.requiresArgument)
|
|
185
|
+
.map((command) => command.command),
|
|
186
|
+
);
|
|
187
|
+
|
|
66
188
|
interface AgentRemoteSession {
|
|
67
189
|
agentName: string;
|
|
68
190
|
runtime: AgentRuntime;
|
|
@@ -77,7 +199,7 @@ interface AgentRemoteSessionSummary {
|
|
|
77
199
|
interface SessionsCardOptions {
|
|
78
200
|
updateMessageId?: string;
|
|
79
201
|
forceSwitchTaskId?: string;
|
|
80
|
-
|
|
202
|
+
page?: number;
|
|
81
203
|
}
|
|
82
204
|
|
|
83
205
|
interface StatusCardOptions {
|
|
@@ -85,6 +207,12 @@ interface StatusCardOptions {
|
|
|
85
207
|
forceSwitchTaskId?: string;
|
|
86
208
|
}
|
|
87
209
|
|
|
210
|
+
interface ResetHistoryCardOptions {
|
|
211
|
+
expectedSessionId?: string;
|
|
212
|
+
updateMessageId?: string;
|
|
213
|
+
page?: number;
|
|
214
|
+
}
|
|
215
|
+
|
|
88
216
|
interface ModelCardOptions {
|
|
89
217
|
sessionId?: string;
|
|
90
218
|
updateMessageId?: string;
|
|
@@ -167,8 +295,9 @@ export interface ControlTaskGroupResult {
|
|
|
167
295
|
|
|
168
296
|
export interface ProxyLifecycle {
|
|
169
297
|
supervised?: boolean;
|
|
170
|
-
restart(contextKey: string, force: boolean): Promise<void>;
|
|
298
|
+
restart(contextKey: string, force: boolean, replyTarget?: MessageReplyTarget): Promise<void>;
|
|
171
299
|
cancelSafeRestart?(scheduleId: number): Promise<boolean>;
|
|
300
|
+
rememberFeishuUserOpenId?(userOpenId: string): Promise<void> | void;
|
|
172
301
|
}
|
|
173
302
|
|
|
174
303
|
export type ShellCommandExecutor = (command: string, cwd: string) => Promise<ShellCommandResult>;
|
|
@@ -181,6 +310,7 @@ export class ProxySessionController {
|
|
|
181
310
|
private readonly queuedPromptStarts = new Map<string, Promise<void>>();
|
|
182
311
|
private readonly queuedPromptCards = new Map<string, Map<string, string>>();
|
|
183
312
|
private readonly queuedPromptCardWrites = new Map<string, Promise<void>>();
|
|
313
|
+
private readonly sessionResets = new Map<string, Promise<void>>();
|
|
184
314
|
private readonly lastSessionListings = new Map<string, string[]>();
|
|
185
315
|
private readonly threadInitializations = new Map<string, Promise<void>>();
|
|
186
316
|
private readonly unsubscribe: Array<() => void> = [];
|
|
@@ -240,8 +370,20 @@ export class ProxySessionController {
|
|
|
240
370
|
"Failed to acknowledge the incoming Feishu message with a reaction.",
|
|
241
371
|
);
|
|
242
372
|
}
|
|
373
|
+
if (message.chatType === "p2p" && message.userId?.startsWith("ou_")) {
|
|
374
|
+
try {
|
|
375
|
+
await this.lifecycle?.rememberFeishuUserOpenId?.(message.userId);
|
|
376
|
+
} catch (error) {
|
|
377
|
+
this.logger.warn(
|
|
378
|
+
{ error, messageId: message.messageId },
|
|
379
|
+
"Failed to persist the first private-chat Lark user Open ID.",
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
243
383
|
if (message.chatId && message.chatType) {
|
|
244
|
-
|
|
384
|
+
const chatContextKey = baseChatContextKey(message.contextKey);
|
|
385
|
+
this.store.recordChatContext(chatContextKey, message.chatType);
|
|
386
|
+
this.store.markChatActive(chatContextKey);
|
|
245
387
|
}
|
|
246
388
|
const replyTarget = message.replyInThread
|
|
247
389
|
? { messageId: message.messageId, replyInThread: true as const }
|
|
@@ -252,9 +394,6 @@ export class ProxySessionController {
|
|
|
252
394
|
text: message.text,
|
|
253
395
|
...(imageCount > 0 ? { imageCount } : {}),
|
|
254
396
|
});
|
|
255
|
-
if (imageCount > 0 && message.chatId && message.chatType) {
|
|
256
|
-
this.store.markChatActive(baseChatContextKey(message.contextKey));
|
|
257
|
-
}
|
|
258
397
|
let localImagePaths: string[] | undefined;
|
|
259
398
|
if (imageCount > 0) {
|
|
260
399
|
try {
|
|
@@ -284,17 +423,6 @@ export class ProxySessionController {
|
|
|
284
423
|
);
|
|
285
424
|
return;
|
|
286
425
|
}
|
|
287
|
-
if (
|
|
288
|
-
message.chatId
|
|
289
|
-
&& message.chatType
|
|
290
|
-
&& (
|
|
291
|
-
command.type === "shell"
|
|
292
|
-
|| (command.type === "prompt" && !message.text.trimStart().startsWith("/"))
|
|
293
|
-
)
|
|
294
|
-
) {
|
|
295
|
-
this.store.markChatActive(baseChatContextKey(message.contextKey));
|
|
296
|
-
}
|
|
297
|
-
|
|
298
426
|
// Operational and read-only commands must remain available even if a prompt operation is slow.
|
|
299
427
|
if (isQueueIndependentCommand(command)) {
|
|
300
428
|
await this.outbound.withReplyTarget(message.contextKey, replyTarget, async () => {
|
|
@@ -355,7 +483,9 @@ export class ProxySessionController {
|
|
|
355
483
|
await this.outbound.withReplyTarget(contextKey, replyTarget, async () => {
|
|
356
484
|
try {
|
|
357
485
|
const kind = String(scopedAction.value.action ?? "");
|
|
358
|
-
if (kind === "
|
|
486
|
+
if (kind === "help_command") {
|
|
487
|
+
await this.executeHelpCommandAction(scopedAction, contextKey, replyTarget);
|
|
488
|
+
} else if (kind === "turn_details") {
|
|
359
489
|
await this.outbound.showDetails(contextKey, String(scopedAction.value.turnId ?? ""));
|
|
360
490
|
} else if (kind === "activity_history") {
|
|
361
491
|
const requestedPage = String(scopedAction.value.page ?? "0");
|
|
@@ -369,6 +499,26 @@ export class ProxySessionController {
|
|
|
369
499
|
} else if (kind === "turn_cancel") {
|
|
370
500
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
371
501
|
await this.cancelSession(this.requireSession(contextKey, sessionId));
|
|
502
|
+
} else if (kind === "turn_reset") {
|
|
503
|
+
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
504
|
+
await this.resetCurrentSessionToTurn(
|
|
505
|
+
contextKey,
|
|
506
|
+
sessionId,
|
|
507
|
+
String(scopedAction.value.turnId ?? ""),
|
|
508
|
+
);
|
|
509
|
+
if (scopedAction.value.cardView === "reset_history") {
|
|
510
|
+
await this.openResetHistory(contextKey, {
|
|
511
|
+
expectedSessionId: sessionId,
|
|
512
|
+
updateMessageId: requiredCardMessageId(scopedAction.messageId),
|
|
513
|
+
page: resetHistoryPageValue(scopedAction.value.page),
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
} else if (kind === "turn_reset_page") {
|
|
517
|
+
await this.openResetHistory(contextKey, {
|
|
518
|
+
expectedSessionId: String(scopedAction.value.sessionId ?? ""),
|
|
519
|
+
updateMessageId: requiredCardMessageId(scopedAction.messageId),
|
|
520
|
+
page: resetHistoryPageValue(scopedAction.value.page),
|
|
521
|
+
});
|
|
372
522
|
} else if (kind === "queued_prompt_cancel") {
|
|
373
523
|
await this.cancelQueuedPrompt(scopedAction);
|
|
374
524
|
} else if (kind === "safe_restart_cancel") {
|
|
@@ -383,17 +533,17 @@ export class ProxySessionController {
|
|
|
383
533
|
if (!cancelled) {
|
|
384
534
|
await this.outbound.sendText(contextKey, "该安全重启计划已失效,请查看最新状态卡片。");
|
|
385
535
|
}
|
|
386
|
-
} else if (kind === "
|
|
387
|
-
await this.refreshSessionsCardFromAction(scopedAction
|
|
536
|
+
} else if (kind === "session_page") {
|
|
537
|
+
await this.refreshSessionsCardFromAction(scopedAction);
|
|
388
538
|
} else if (kind === "session_switch") {
|
|
389
539
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
390
540
|
await this.switchSession(contextKey, sessionId);
|
|
391
541
|
if (scopedAction.value.cardView === "status") await this.refreshStatusCardFromAction(scopedAction);
|
|
392
|
-
else await this.refreshSessionsCardFromAction(scopedAction);
|
|
542
|
+
else await this.refreshSessionsCardFromAction(scopedAction, { page: 0 });
|
|
393
543
|
} else if (kind === "session_fork") {
|
|
394
544
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
395
545
|
await this.forkSessionReference(contextKey, sessionId);
|
|
396
|
-
await this.refreshSessionsCardFromAction(scopedAction);
|
|
546
|
+
await this.refreshSessionsCardFromAction(scopedAction, { page: 0 });
|
|
397
547
|
} else if (kind === "session_fork_group") {
|
|
398
548
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
399
549
|
await this.forkSessionReferenceToFeishuGroup(contextKey, sessionId, scopedAction.userId);
|
|
@@ -401,7 +551,7 @@ export class ProxySessionController {
|
|
|
401
551
|
} else if (kind === "session_new") {
|
|
402
552
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
403
553
|
await this.createProjectSessionFromReference(contextKey, sessionId);
|
|
404
|
-
await this.refreshSessionsCardFromAction(scopedAction);
|
|
554
|
+
await this.refreshSessionsCardFromAction(scopedAction, { page: 0 });
|
|
405
555
|
} else if (kind === "session_new_group") {
|
|
406
556
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
407
557
|
await this.createFeishuGroupFromReference(contextKey, sessionId, scopedAction.userId);
|
|
@@ -410,7 +560,7 @@ export class ProxySessionController {
|
|
|
410
560
|
const sessionId = String(scopedAction.value.sessionId ?? "");
|
|
411
561
|
await this.stopSessionReference(contextKey, sessionId);
|
|
412
562
|
if (scopedAction.value.cardView === "status") await this.refreshStatusCardFromAction(scopedAction, sessionId);
|
|
413
|
-
else await this.refreshSessionsCardFromAction(scopedAction, sessionId);
|
|
563
|
+
else await this.refreshSessionsCardFromAction(scopedAction, { forceSwitchTaskId: sessionId });
|
|
414
564
|
} else if (kind === "session_status") {
|
|
415
565
|
await this.status(contextKey, String(scopedAction.value.sessionId ?? ""));
|
|
416
566
|
} else if (kind === "session_status_refresh") {
|
|
@@ -541,6 +691,7 @@ export class ProxySessionController {
|
|
|
541
691
|
this.lastSessionListings.clear();
|
|
542
692
|
this.queuedPromptCards.clear();
|
|
543
693
|
this.queuedPromptCardWrites.clear();
|
|
694
|
+
this.sessionResets.clear();
|
|
544
695
|
}
|
|
545
696
|
|
|
546
697
|
async controlStopTask(localSessionId: string): Promise<string> {
|
|
@@ -831,7 +982,10 @@ export class ProxySessionController {
|
|
|
831
982
|
return;
|
|
832
983
|
case "restart":
|
|
833
984
|
if (!this.lifecycle) throw new Error("当前运行方式不支持自动重启。");
|
|
834
|
-
await this.lifecycle.restart(contextKey, command.force === true);
|
|
985
|
+
await this.lifecycle.restart(contextKey, command.force === true, replyTarget);
|
|
986
|
+
return;
|
|
987
|
+
case "turns":
|
|
988
|
+
await this.openResetHistory(contextKey);
|
|
835
989
|
return;
|
|
836
990
|
case "model":
|
|
837
991
|
await this.openExecutionSettings(contextKey, "model");
|
|
@@ -2063,6 +2217,24 @@ export class ProxySessionController {
|
|
|
2063
2217
|
}
|
|
2064
2218
|
|
|
2065
2219
|
private async handleRuntimeEvent(event: RuntimeEvent): Promise<void> {
|
|
2220
|
+
if ("turnId" in event) {
|
|
2221
|
+
const source = this.store.getSession(event.sessionId);
|
|
2222
|
+
if (source?.remoteSessionId && this.isCodexSession(source)) {
|
|
2223
|
+
this.store.saveTurnRuntimeOrigin(
|
|
2224
|
+
event.turnId,
|
|
2225
|
+
source.localSessionId,
|
|
2226
|
+
source.agentName,
|
|
2227
|
+
source.remoteSessionId,
|
|
2228
|
+
);
|
|
2229
|
+
}
|
|
2230
|
+
if (event.type === "turn_started") {
|
|
2231
|
+
this.store.saveTurnParent(
|
|
2232
|
+
event.turnId,
|
|
2233
|
+
event.sessionId,
|
|
2234
|
+
this.completedParentTurnId(source),
|
|
2235
|
+
);
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2066
2238
|
if (event.type === "session_metadata_updated") {
|
|
2067
2239
|
this.store.updateRuntimeSession(event.sessionId, { title: event.title });
|
|
2068
2240
|
this.outbound.updateSessionTitle(event.sessionId, event.title);
|
|
@@ -2157,6 +2329,13 @@ export class ProxySessionController {
|
|
|
2157
2329
|
}
|
|
2158
2330
|
}
|
|
2159
2331
|
|
|
2332
|
+
private completedParentTurnId(source: SessionRecord | undefined): string | undefined {
|
|
2333
|
+
if (!source?.lastTurnId) return undefined;
|
|
2334
|
+
if (source.lastTurnStatus === "completed") return source.lastTurnId;
|
|
2335
|
+
const snapshot = turnViewSnapshot(this.store.getTurnSnapshot(source.lastTurnId));
|
|
2336
|
+
return snapshot?.status === "completed" ? source.lastTurnId : undefined;
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2160
2339
|
private async finalizeStandaloneMessageReaction(
|
|
2161
2340
|
messageId: string,
|
|
2162
2341
|
status: "completed" | "failed" | "cancelled",
|
|
@@ -2271,6 +2450,111 @@ export class ProxySessionController {
|
|
|
2271
2450
|
await loaded.runtime.cancelTurn(record.localSessionId, turnId);
|
|
2272
2451
|
}
|
|
2273
2452
|
|
|
2453
|
+
private async resetCurrentSessionToTurn(
|
|
2454
|
+
contextKey: string,
|
|
2455
|
+
sessionId: string,
|
|
2456
|
+
turnId: string,
|
|
2457
|
+
): Promise<void> {
|
|
2458
|
+
if (!sessionId || !turnId) throw new Error("无效的 Reset 请求。请使用最新的思考卡片重试。");
|
|
2459
|
+
const previous = this.sessionResets.get(sessionId) ?? Promise.resolve();
|
|
2460
|
+
const reset = previous.catch(() => undefined).then(() =>
|
|
2461
|
+
this.performCurrentSessionReset(contextKey, sessionId, turnId));
|
|
2462
|
+
this.sessionResets.set(sessionId, reset);
|
|
2463
|
+
try {
|
|
2464
|
+
await reset;
|
|
2465
|
+
} finally {
|
|
2466
|
+
if (this.sessionResets.get(sessionId) === reset) this.sessionResets.delete(sessionId);
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
private async performCurrentSessionReset(
|
|
2471
|
+
contextKey: string,
|
|
2472
|
+
sessionId: string,
|
|
2473
|
+
turnId: string,
|
|
2474
|
+
): Promise<void> {
|
|
2475
|
+
const current = this.requireCurrentSession(contextKey);
|
|
2476
|
+
if (current.localSessionId !== sessionId) {
|
|
2477
|
+
throw new Error("这张思考卡片不属于当前任务。请先切换回对应任务,再点击 Reset。");
|
|
2478
|
+
}
|
|
2479
|
+
if (!current.remoteSessionId || !this.isCodexSession(current)) {
|
|
2480
|
+
throw new Error("当前任务不是可 Reset 的 App Server 任务。");
|
|
2481
|
+
}
|
|
2482
|
+
|
|
2483
|
+
const snapshot = turnViewSnapshot(this.store.getTurnSnapshot(turnId));
|
|
2484
|
+
if (!snapshot || snapshot.sessionId !== sessionId || snapshot.status !== "completed") {
|
|
2485
|
+
throw new Error("只能将当前任务 Reset 到已成功完成的轮次。");
|
|
2486
|
+
}
|
|
2487
|
+
if (current.lastTurnId === turnId && current.lastTurnStatus === "completed") {
|
|
2488
|
+
await this.outbound.sendText(contextKey, "当前任务已经处于本轮完成后的对话状态,无需 Reset。");
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
const runtime = this.runtimes.forAgent(current.agentName);
|
|
2493
|
+
if (runtime.kind !== "codex" || !runtime.forkSession) {
|
|
2494
|
+
throw new Error("当前 App Server Agent 不支持 Reset。");
|
|
2495
|
+
}
|
|
2496
|
+
const loaded = await this.loadSession(current);
|
|
2497
|
+
const activity = await this.assertSessionTurnOwnership(current, runtime);
|
|
2498
|
+
const active = activity?.active
|
|
2499
|
+
?? Boolean(loaded.session.activeTurnId || current.status === "running" || current.lastTurnStatus === "running");
|
|
2500
|
+
if (active) {
|
|
2501
|
+
throw new Error("当前任务仍在执行。请等待任务完成或先停止任务,再点击 Reset。");
|
|
2502
|
+
}
|
|
2503
|
+
|
|
2504
|
+
const origin = this.store.getTurnRuntimeOrigin(turnId);
|
|
2505
|
+
if (origin && (origin.localSessionId !== sessionId || origin.agentName !== current.agentName)) {
|
|
2506
|
+
throw new Error("这张思考卡片与当前任务不匹配,已拒绝 Reset。");
|
|
2507
|
+
}
|
|
2508
|
+
const sourceRemoteSessionId = origin?.remoteSessionId ?? current.remoteSessionId;
|
|
2509
|
+
const previousRemoteSessionId = current.remoteSessionId;
|
|
2510
|
+
const forked = await runtime.forkSession({
|
|
2511
|
+
localSessionId: current.localSessionId,
|
|
2512
|
+
remoteSessionId: sourceRemoteSessionId,
|
|
2513
|
+
lastTurnId: turnId,
|
|
2514
|
+
agentName: current.agentName,
|
|
2515
|
+
cwd: current.cwd,
|
|
2516
|
+
title: current.title,
|
|
2517
|
+
modelProvider: current.modelProvider,
|
|
2518
|
+
model: current.model,
|
|
2519
|
+
reasoningEffort: current.reasoningEffort,
|
|
2520
|
+
permissionMode: current.permissionMode ?? "auto",
|
|
2521
|
+
});
|
|
2522
|
+
this.persistRuntimeSession(current, forked, "ready");
|
|
2523
|
+
this.store.updateRuntimeSession(current.localSessionId, {
|
|
2524
|
+
lastTurnId: turnId,
|
|
2525
|
+
lastTurnStatus: "completed",
|
|
2526
|
+
});
|
|
2527
|
+
this.outbound.registerSession(
|
|
2528
|
+
current.localSessionId,
|
|
2529
|
+
contextKey,
|
|
2530
|
+
forked.title ?? current.title,
|
|
2531
|
+
current.cwd,
|
|
2532
|
+
this.agentLabel(current.agentName),
|
|
2533
|
+
);
|
|
2534
|
+
this.store.audit(contextKey, "session_reset_to_turn", {
|
|
2535
|
+
localSessionId: current.localSessionId,
|
|
2536
|
+
previousRemoteSessionId,
|
|
2537
|
+
sourceRemoteSessionId,
|
|
2538
|
+
resetTurnId: turnId,
|
|
2539
|
+
forkedRemoteSessionId: forked.remoteSessionId,
|
|
2540
|
+
});
|
|
2541
|
+
const targetSummary = truncateText(
|
|
2542
|
+
(snapshot.prompt ?? snapshot.taskTitle ?? "未记录对话内容").replace(/\s+/g, " ").trim(),
|
|
2543
|
+
100,
|
|
2544
|
+
) || "未记录对话内容";
|
|
2545
|
+
const targetTime = snapshot.completedAt ?? snapshot.startedAt;
|
|
2546
|
+
await this.outbound.sendText(
|
|
2547
|
+
contextKey,
|
|
2548
|
+
[
|
|
2549
|
+
"已将当前任务重置到:",
|
|
2550
|
+
targetSummary,
|
|
2551
|
+
`完成时间:${targetTime === undefined ? "未知" : formatResetTurnTime(targetTime)}`,
|
|
2552
|
+
`Turn ID:${turnId}`,
|
|
2553
|
+
"后续对话将从该轮完成后的状态继续;本地文件没有回退。",
|
|
2554
|
+
].join("\n"),
|
|
2555
|
+
);
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2274
2558
|
private async resolveApproval(action: CardAction): Promise<void> {
|
|
2275
2559
|
const sessionId = String(action.value.sessionId ?? "");
|
|
2276
2560
|
const requestId = String(action.value.requestId ?? "");
|
|
@@ -2824,6 +3108,92 @@ export class ProxySessionController {
|
|
|
2824
3108
|
await this.outbound.sendText(contextKey, mode === "auto" ? "已切换为自动执行模式。" : "已切换为执行前确认模式。");
|
|
2825
3109
|
}
|
|
2826
3110
|
|
|
3111
|
+
private async openResetHistory(
|
|
3112
|
+
contextKey: string,
|
|
3113
|
+
options: ResetHistoryCardOptions = {},
|
|
3114
|
+
): Promise<void> {
|
|
3115
|
+
const current = this.requireCurrentSession(contextKey);
|
|
3116
|
+
if (options.expectedSessionId && current.localSessionId !== options.expectedSessionId) {
|
|
3117
|
+
throw new Error("这张历史轮次卡片不属于当前任务。请先切换回对应任务,再重新发送 /turns。");
|
|
3118
|
+
}
|
|
3119
|
+
if (!current.remoteSessionId || !this.isCodexSession(current)) {
|
|
3120
|
+
throw new Error("当前任务不是可 Reset 的 App Server 任务。");
|
|
3121
|
+
}
|
|
3122
|
+
const allTurns = this.store.listCompletedTurnGraph(current.localSessionId, contextKey);
|
|
3123
|
+
const graphRows = buildTurnGraphRows(allTurns.map((turn) => ({
|
|
3124
|
+
turnId: turn.turnId,
|
|
3125
|
+
parentTurnId: turn.parentTurnId,
|
|
3126
|
+
})));
|
|
3127
|
+
const total = allTurns.length;
|
|
3128
|
+
const totalPages = Math.max(1, Math.ceil(total / RESET_HISTORY_PAGE_SIZE));
|
|
3129
|
+
const page = Math.max(0, Math.min(Math.trunc(options.page ?? 0), totalPages - 1));
|
|
3130
|
+
const offset = page * RESET_HISTORY_PAGE_SIZE;
|
|
3131
|
+
const turns = allTurns.slice(offset, offset + RESET_HISTORY_PAGE_SIZE);
|
|
3132
|
+
const entries: ResetHistoryCardEntry[] = turns.flatMap((turn, index) => {
|
|
3133
|
+
const snapshot = turnViewSnapshot(turn.snapshot);
|
|
3134
|
+
if (!snapshot) return [];
|
|
3135
|
+
const graph = graphRows[offset + index]!;
|
|
3136
|
+
const summary = truncateText(
|
|
3137
|
+
(snapshot.prompt ?? snapshot.taskTitle ?? "未记录对话内容").replace(/\s+/g, " ").trim(),
|
|
3138
|
+
100,
|
|
3139
|
+
);
|
|
3140
|
+
const isCurrent = current.lastTurnId === turn.turnId && current.lastTurnStatus === "completed";
|
|
3141
|
+
return [{
|
|
3142
|
+
sequence: graph.sequence,
|
|
3143
|
+
graphNodeLine: graph.nodeLine,
|
|
3144
|
+
graphConnectorLine: graph.connectorLine,
|
|
3145
|
+
lines: [
|
|
3146
|
+
`**${cardText(summary || "未记录对话内容")}**`,
|
|
3147
|
+
`${formatResetTurnTime(snapshot.completedAt ?? snapshot.startedAt)} · ${cardCode(turn.turnId)}`,
|
|
3148
|
+
],
|
|
3149
|
+
current: isCurrent,
|
|
3150
|
+
actions: isCurrent ? undefined : [{
|
|
3151
|
+
text: "Reset",
|
|
3152
|
+
value: {
|
|
3153
|
+
action: "turn_reset",
|
|
3154
|
+
cardView: "reset_history",
|
|
3155
|
+
sessionId: current.localSessionId,
|
|
3156
|
+
turnId: turn.turnId,
|
|
3157
|
+
contextKey,
|
|
3158
|
+
page: String(page),
|
|
3159
|
+
},
|
|
3160
|
+
}],
|
|
3161
|
+
}];
|
|
3162
|
+
});
|
|
3163
|
+
const pageActions: TaskListCardAction[] = [
|
|
3164
|
+
...(page > 0 ? [{
|
|
3165
|
+
text: "Previous",
|
|
3166
|
+
value: {
|
|
3167
|
+
action: "turn_reset_page",
|
|
3168
|
+
sessionId: current.localSessionId,
|
|
3169
|
+
contextKey,
|
|
3170
|
+
page: String(page - 1),
|
|
3171
|
+
},
|
|
3172
|
+
}] : []),
|
|
3173
|
+
...(page < totalPages - 1 ? [{
|
|
3174
|
+
text: "Next",
|
|
3175
|
+
value: {
|
|
3176
|
+
action: "turn_reset_page",
|
|
3177
|
+
sessionId: current.localSessionId,
|
|
3178
|
+
contextKey,
|
|
3179
|
+
page: String(page + 1),
|
|
3180
|
+
},
|
|
3181
|
+
}] : []),
|
|
3182
|
+
];
|
|
3183
|
+
const card = this.cardRenderer.renderResetHistoryCard({
|
|
3184
|
+
entries,
|
|
3185
|
+
footerLines: [
|
|
3186
|
+
`第 ${page + 1}/${totalPages} 页 · 共 ${total} 个已完成 turn`,
|
|
3187
|
+
],
|
|
3188
|
+
pageActions,
|
|
3189
|
+
});
|
|
3190
|
+
if (options.updateMessageId) {
|
|
3191
|
+
await this.outbound.updateInteractiveCard(contextKey, options.updateMessageId, card);
|
|
3192
|
+
} else {
|
|
3193
|
+
await this.outbound.sendInteractiveCard(contextKey, card);
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
|
|
2827
3197
|
private async listSessions(
|
|
2828
3198
|
contextKey: string,
|
|
2829
3199
|
searchTerm?: string,
|
|
@@ -2831,7 +3201,8 @@ export class ProxySessionController {
|
|
|
2831
3201
|
): Promise<void> {
|
|
2832
3202
|
const context = this.store.getOrCreateUserContext(contextKey, this.config.defaults.agent!);
|
|
2833
3203
|
const normalizedSearch = searchTerm?.trim().toLowerCase();
|
|
2834
|
-
const
|
|
3204
|
+
const requestedPage = Math.max(0, Math.trunc(options.page ?? 0));
|
|
3205
|
+
const fetchLimit = (requestedPage + 1) * SESSION_PAGE_SIZE;
|
|
2835
3206
|
const localSessions = this.store.listSessions(contextKey).filter((session) =>
|
|
2836
3207
|
session.status !== "closed" && this.isCodexSession(session),
|
|
2837
3208
|
);
|
|
@@ -2841,9 +3212,9 @@ export class ProxySessionController {
|
|
|
2841
3212
|
await Promise.all(this.runtimes.entries("codex").map(async ([agentName, runtime]) => {
|
|
2842
3213
|
if (!runtime.listRemoteSessions) return;
|
|
2843
3214
|
try {
|
|
2844
|
-
const
|
|
2845
|
-
remoteSessions.push(...
|
|
2846
|
-
remoteHasMore ||= Boolean(
|
|
3215
|
+
const result = await runtime.listRemoteSessions({ searchTerm, limit: fetchLimit });
|
|
3216
|
+
remoteSessions.push(...result.sessions.map((session) => ({ agentName, session })));
|
|
3217
|
+
remoteHasMore ||= Boolean(result.nextCursor);
|
|
2847
3218
|
} catch (error) {
|
|
2848
3219
|
this.logger.warn({ error, contextKey, agentName }, "Failed to list App Server sessions for an Agent.");
|
|
2849
3220
|
remoteErrors.push(`${agentName}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -2861,9 +3232,13 @@ export class ProxySessionController {
|
|
|
2861
3232
|
|| Number(right.active) - Number(left.active)
|
|
2862
3233
|
|| right.updatedAt - left.updatedAt);
|
|
2863
3234
|
const activeCount = entries.filter((entry) => entry.active).length;
|
|
2864
|
-
const
|
|
2865
|
-
const
|
|
2866
|
-
|
|
3235
|
+
const lastKnownPage = Math.max(0, Math.ceil(entries.length / SESSION_PAGE_SIZE) - 1);
|
|
3236
|
+
const page = remoteHasMore ? requestedPage : Math.min(requestedPage, lastKnownPage);
|
|
3237
|
+
const offset = page * SESSION_PAGE_SIZE;
|
|
3238
|
+
const visibleEntries = entries.slice(offset, offset + SESSION_PAGE_SIZE);
|
|
3239
|
+
const hasPrevious = page > 0;
|
|
3240
|
+
const hasNext = remoteHasMore || entries.length > offset + SESSION_PAGE_SIZE;
|
|
3241
|
+
this.lastSessionListings.set(contextKey, entries.map((entry) => entry.reference));
|
|
2867
3242
|
const cardEntries = visibleEntries.map((entry, index) => {
|
|
2868
3243
|
const marker = entry.current ? "✅" : entry.active ? "🟢 **活跃**" : "•";
|
|
2869
3244
|
const showStop = entry.status === "外部执行中"
|
|
@@ -2876,7 +3251,7 @@ export class ProxySessionController {
|
|
|
2876
3251
|
action: showStop ? "session_stop" : "session_switch",
|
|
2877
3252
|
sessionId: entry.reference,
|
|
2878
3253
|
...(searchTerm ? { searchTerm } : {}),
|
|
2879
|
-
|
|
3254
|
+
page: String(page),
|
|
2880
3255
|
contextKey,
|
|
2881
3256
|
},
|
|
2882
3257
|
}];
|
|
@@ -2886,7 +3261,7 @@ export class ProxySessionController {
|
|
|
2886
3261
|
action: "session_new",
|
|
2887
3262
|
sessionId: entry.reference,
|
|
2888
3263
|
...(searchTerm ? { searchTerm } : {}),
|
|
2889
|
-
|
|
3264
|
+
page: String(page),
|
|
2890
3265
|
contextKey,
|
|
2891
3266
|
},
|
|
2892
3267
|
});
|
|
@@ -2896,7 +3271,7 @@ export class ProxySessionController {
|
|
|
2896
3271
|
action: "session_new_group",
|
|
2897
3272
|
sessionId: entry.reference,
|
|
2898
3273
|
...(searchTerm ? { searchTerm } : {}),
|
|
2899
|
-
|
|
3274
|
+
page: String(page),
|
|
2900
3275
|
contextKey,
|
|
2901
3276
|
},
|
|
2902
3277
|
});
|
|
@@ -2906,7 +3281,7 @@ export class ProxySessionController {
|
|
|
2906
3281
|
action: "session_fork",
|
|
2907
3282
|
sessionId: entry.reference,
|
|
2908
3283
|
...(searchTerm ? { searchTerm } : {}),
|
|
2909
|
-
|
|
3284
|
+
page: String(page),
|
|
2910
3285
|
contextKey,
|
|
2911
3286
|
},
|
|
2912
3287
|
});
|
|
@@ -2916,7 +3291,7 @@ export class ProxySessionController {
|
|
|
2916
3291
|
action: "session_fork_group",
|
|
2917
3292
|
sessionId: entry.reference,
|
|
2918
3293
|
...(searchTerm ? { searchTerm } : {}),
|
|
2919
|
-
|
|
3294
|
+
page: String(page),
|
|
2920
3295
|
contextKey,
|
|
2921
3296
|
},
|
|
2922
3297
|
});
|
|
@@ -2930,7 +3305,7 @@ export class ProxySessionController {
|
|
|
2930
3305
|
});
|
|
2931
3306
|
return {
|
|
2932
3307
|
lines: [
|
|
2933
|
-
`**${index + 1}.** ${marker} **${cardText(entry.title)}** ${cardText(entry.id)}`,
|
|
3308
|
+
`**${offset + index + 1}.** ${marker} **${cardText(entry.title)}** ${cardText(entry.id)}`,
|
|
2934
3309
|
`${entry.status} · ${entry.updatedLabel} · ${cardText(entry.agentName)} · ${cardText(entry.cwd || "目录未知")}`,
|
|
2935
3310
|
],
|
|
2936
3311
|
actions,
|
|
@@ -2942,22 +3317,31 @@ export class ProxySessionController {
|
|
|
2942
3317
|
cardEntries,
|
|
2943
3318
|
[
|
|
2944
3319
|
...(remoteHint ? [remoteHint] : []),
|
|
2945
|
-
|
|
2946
|
-
"
|
|
2947
|
-
"
|
|
2948
|
-
"
|
|
2949
|
-
|
|
3320
|
+
`第 ${page + 1} 页 · 每页 ${SESSION_PAGE_SIZE} 个任务${hasNext ? "" : ` · 当前共 ${entries.length} 个任务`}`,
|
|
3321
|
+
"",
|
|
3322
|
+
"> **Switch** 切换任务;**Stop** 停止运行;**Status** 查看状态。",
|
|
3323
|
+
"> **New** / **Fork** 新建 / 分支任务;**NewGroup** / **ForkGroup** 新建 / 分支群。",
|
|
3324
|
+
],
|
|
3325
|
+
[
|
|
3326
|
+
...(hasPrevious ? [{
|
|
3327
|
+
text: "Previous",
|
|
3328
|
+
value: {
|
|
3329
|
+
action: "session_page",
|
|
3330
|
+
...(searchTerm ? { searchTerm } : {}),
|
|
3331
|
+
page: String(page - 1),
|
|
3332
|
+
contextKey,
|
|
3333
|
+
},
|
|
3334
|
+
}] : []),
|
|
3335
|
+
...(hasNext ? [{
|
|
3336
|
+
text: "Next",
|
|
3337
|
+
value: {
|
|
3338
|
+
action: "session_page",
|
|
3339
|
+
...(searchTerm ? { searchTerm } : {}),
|
|
3340
|
+
page: String(page + 1),
|
|
3341
|
+
contextKey,
|
|
3342
|
+
},
|
|
3343
|
+
}] : []),
|
|
2950
3344
|
],
|
|
2951
|
-
hasMore ? {
|
|
2952
|
-
text: "More",
|
|
2953
|
-
type: "primary",
|
|
2954
|
-
value: {
|
|
2955
|
-
action: "session_more",
|
|
2956
|
-
...(searchTerm ? { searchTerm } : {}),
|
|
2957
|
-
visibleCount: String(visibleCount),
|
|
2958
|
-
contextKey,
|
|
2959
|
-
},
|
|
2960
|
-
} : undefined,
|
|
2961
3345
|
);
|
|
2962
3346
|
if (options.updateMessageId) {
|
|
2963
3347
|
await this.outbound.updateInteractiveCard(contextKey, options.updateMessageId, card);
|
|
@@ -2968,18 +3352,16 @@ export class ProxySessionController {
|
|
|
2968
3352
|
|
|
2969
3353
|
private async refreshSessionsCardFromAction(
|
|
2970
3354
|
action: CardAction,
|
|
2971
|
-
forceSwitchTaskId?: string,
|
|
2972
|
-
loadMore = false,
|
|
3355
|
+
options: { forceSwitchTaskId?: string; page?: number } = {},
|
|
2973
3356
|
): Promise<void> {
|
|
2974
3357
|
if (!action.messageId) return;
|
|
2975
3358
|
const searchTerm = typeof action.value.searchTerm === "string" && action.value.searchTerm.trim()
|
|
2976
3359
|
? action.value.searchTerm
|
|
2977
3360
|
: undefined;
|
|
2978
|
-
const currentVisibleCount = parseSessionVisibleCount(action.value.visibleCount);
|
|
2979
3361
|
await this.listSessions(action.contextKey, searchTerm, {
|
|
2980
3362
|
updateMessageId: action.messageId,
|
|
2981
|
-
forceSwitchTaskId,
|
|
2982
|
-
|
|
3363
|
+
forceSwitchTaskId: options.forceSwitchTaskId,
|
|
3364
|
+
page: options.page ?? parseSessionPage(action.value.page),
|
|
2983
3365
|
});
|
|
2984
3366
|
}
|
|
2985
3367
|
|
|
@@ -3400,58 +3782,61 @@ export class ProxySessionController {
|
|
|
3400
3782
|
else await this.outbound.sendInteractiveCard(contextKey, card);
|
|
3401
3783
|
}
|
|
3402
3784
|
|
|
3785
|
+
private async executeHelpCommandAction(
|
|
3786
|
+
action: CardAction,
|
|
3787
|
+
contextKey: string,
|
|
3788
|
+
replyTarget?: MessageReplyTarget,
|
|
3789
|
+
): Promise<void> {
|
|
3790
|
+
const commandText = typeof action.value.command === "string"
|
|
3791
|
+
? action.value.command.trim()
|
|
3792
|
+
: "";
|
|
3793
|
+
if (!HELP_DEFAULT_COMMANDS.has(commandText)) {
|
|
3794
|
+
throw new Error("帮助卡片中的命令无效,请发送 /help 获取最新卡片。");
|
|
3795
|
+
}
|
|
3796
|
+
const command = this.router.parse(commandText);
|
|
3797
|
+
await this.execute(
|
|
3798
|
+
contextKey,
|
|
3799
|
+
command,
|
|
3800
|
+
undefined,
|
|
3801
|
+
replyTarget,
|
|
3802
|
+
undefined,
|
|
3803
|
+
action.userId,
|
|
3804
|
+
);
|
|
3805
|
+
}
|
|
3806
|
+
|
|
3403
3807
|
private async help(contextKey: string): Promise<void> {
|
|
3404
|
-
const sections:
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
"**/permissions** 设置执行工具前是否需要确认",
|
|
3437
|
-
],
|
|
3438
|
-
},
|
|
3439
|
-
{
|
|
3440
|
-
title: "Agent",
|
|
3441
|
-
lines: [
|
|
3442
|
-
"**/agent [name]** 选择新任务使用的默认 Agent;仅配置一个时直接显示当前 Agent",
|
|
3443
|
-
],
|
|
3444
|
-
},
|
|
3445
|
-
{
|
|
3446
|
-
title: "系统",
|
|
3447
|
-
lines: [
|
|
3448
|
-
"**/status [序号或任务 ID]** 查看当前或指定任务的详细状态",
|
|
3449
|
-
"**/restart [--force]** 默认安全重启;--force 立即重启",
|
|
3450
|
-
"**/help** 显示本帮助",
|
|
3451
|
-
],
|
|
3452
|
-
},
|
|
3453
|
-
];
|
|
3454
|
-
await this.outbound.sendInteractiveCard(contextKey, this.cardRenderer.renderSectionsCard("Agent Bot 使用帮助", sections));
|
|
3808
|
+
const sections: HelpCardSection[] = HELP_COMMAND_SECTIONS.map((section) => ({
|
|
3809
|
+
title: section.title,
|
|
3810
|
+
commands: section.commands.map((command) => ({
|
|
3811
|
+
text: command.command,
|
|
3812
|
+
...(command.requiresArgument
|
|
3813
|
+
? {}
|
|
3814
|
+
: {
|
|
3815
|
+
action: {
|
|
3816
|
+
text: command.command,
|
|
3817
|
+
value: {
|
|
3818
|
+
action: "help_command",
|
|
3819
|
+
command: command.command,
|
|
3820
|
+
contextKey,
|
|
3821
|
+
},
|
|
3822
|
+
},
|
|
3823
|
+
}),
|
|
3824
|
+
...(command.usage ? { usage: command.usage } : {}),
|
|
3825
|
+
description: command.description,
|
|
3826
|
+
})),
|
|
3827
|
+
}));
|
|
3828
|
+
await this.outbound.sendInteractiveCard(contextKey, this.cardRenderer.renderHelpCard(
|
|
3829
|
+
"Agent Bot 使用帮助",
|
|
3830
|
+
[
|
|
3831
|
+
"直接发送消息即可继续当前任务;执行中发送的新消息会追加到本次任务。",
|
|
3832
|
+
"点击命令按钮可执行默认形式;有必填参数的命令需要手动发送。",
|
|
3833
|
+
"> 命令前缀示例:/sess 等同于 /sessions。",
|
|
3834
|
+
"> 命令缩写示例:/fg 等同于 /forkgroup。",
|
|
3835
|
+
"前缀或缩写匹配多个命令时,需要输入更长的形式。",
|
|
3836
|
+
"**[参数]** 可选 **<参数>** 必填",
|
|
3837
|
+
],
|
|
3838
|
+
sections,
|
|
3839
|
+
));
|
|
3455
3840
|
}
|
|
3456
3841
|
|
|
3457
3842
|
private ensureAgent(agentName: string) {
|
|
@@ -3825,6 +4210,10 @@ function formatStatusTime(value: string): string {
|
|
|
3825
4210
|
}).format(date);
|
|
3826
4211
|
}
|
|
3827
4212
|
|
|
4213
|
+
function formatResetTurnTime(value?: number): string {
|
|
4214
|
+
return value === undefined ? "完成时间未知" : formatStatusTime(new Date(value).toISOString());
|
|
4215
|
+
}
|
|
4216
|
+
|
|
3828
4217
|
function cardText(value: string): string {
|
|
3829
4218
|
return value.replaceAll("&", "&").replaceAll("`", "`").replaceAll("<", "<").replaceAll(">", ">");
|
|
3830
4219
|
}
|
|
@@ -3843,9 +4232,15 @@ function executionSettingsTabValue(value: unknown): ExecutionSettingsTab {
|
|
|
3843
4232
|
throw new Error("设置卡片的 tab 无效,请重新发送设置命令。");
|
|
3844
4233
|
}
|
|
3845
4234
|
|
|
4235
|
+
function resetHistoryPageValue(value: unknown): number {
|
|
4236
|
+
const page = Number(value);
|
|
4237
|
+
if (!Number.isSafeInteger(page) || page < 0) throw new Error("历史轮次卡片页码无效,请重新发送 /turns。");
|
|
4238
|
+
return page;
|
|
4239
|
+
}
|
|
4240
|
+
|
|
3846
4241
|
function requiredCardMessageId(value: string | undefined): string {
|
|
3847
4242
|
if (value) return value;
|
|
3848
|
-
throw new Error("
|
|
4243
|
+
throw new Error("无法更新卡片,请重新发送对应命令。");
|
|
3849
4244
|
}
|
|
3850
4245
|
|
|
3851
4246
|
function remoteSessionStatusLabel(status: RemoteSessionSummary["status"]): string {
|
|
@@ -3947,9 +4342,9 @@ function isQueueIndependentCommand(command: Command): boolean {
|
|
|
3947
4342
|
return false;
|
|
3948
4343
|
}
|
|
3949
4344
|
|
|
3950
|
-
function
|
|
4345
|
+
function parseSessionPage(value: unknown): number {
|
|
3951
4346
|
const parsed = typeof value === "string" || typeof value === "number" ? Number(value) : Number.NaN;
|
|
3952
|
-
return Number.isSafeInteger(parsed) && parsed >=
|
|
4347
|
+
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : 0;
|
|
3953
4348
|
}
|
|
3954
4349
|
|
|
3955
4350
|
function isPromptCommand(command: Command): boolean {
|