@keyou007/agent-bot 0.1.13-alpha.0 → 0.1.13-alpha.1
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 +9 -0
- package/README.md +10 -4
- package/README.zh.md +10 -4
- 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 +12 -2
- package/dist/cli/help.js.map +1 -1
- package/dist/cli.js +39 -0
- package/dist/cli.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 +82 -22
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/index.js +103 -23
- 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 +270 -47
- package/dist/proxy/ProxySessionController.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 +169 -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 +11 -4
- package/docs/technical-reference.zh.md +11 -4
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/skills/agent-bot/SKILL.md +10 -4
- package/src/cli/ServerStarter.ts +25 -2
- package/src/cli/controlProtocol.ts +6 -1
- package/src/cli/help.ts +12 -2
- package/src/cli.ts +57 -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 +109 -22
- package/src/index.ts +161 -21
- package/src/presentation/turnGraph.ts +48 -0
- package/src/proxy/ProxySessionController.ts +323 -55
- package/src/startup/StartupNotifier.ts +23 -7
- package/src/startup/startFeishu.ts +3 -2
- package/src/state/StateStore.ts +221 -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
|
@@ -6,7 +6,12 @@ import type { SessionRecord } from "../state/StateStore.js";
|
|
|
6
6
|
|
|
7
7
|
export type ControlRequest =
|
|
8
8
|
| { action: "health" }
|
|
9
|
-
| {
|
|
9
|
+
| {
|
|
10
|
+
action: "server_restart";
|
|
11
|
+
mode: "safe" | "immediate";
|
|
12
|
+
reason: string;
|
|
13
|
+
notificationSessionId?: string;
|
|
14
|
+
}
|
|
10
15
|
| { action: "server_stop" }
|
|
11
16
|
| { action: "task_status"; localSessionId: string }
|
|
12
17
|
| { action: "task_stop"; localSessionId: string }
|
package/src/cli/help.ts
CHANGED
|
@@ -14,7 +14,7 @@ export function renderCliHelp(version: string, language: CliLanguage = cliLangua
|
|
|
14
14
|
console 打开本地控制台
|
|
15
15
|
server start 在后台启动 Agent Bot
|
|
16
16
|
server status 显示服务、飞书 App ID 和安全重启状态
|
|
17
|
-
server restart
|
|
17
|
+
server restart [--task <任务>] 安排安全重启并将状态发回指定任务会话
|
|
18
18
|
server stop 停止 Agent Bot
|
|
19
19
|
task list 列出任务
|
|
20
20
|
task status <任务> 显示任务状态
|
|
@@ -39,6 +39,11 @@ export function renderCliHelp(version: string, language: CliLanguage = cliLangua
|
|
|
39
39
|
--skip-feishu 仅初始化 Console 环境
|
|
40
40
|
--reconfigure-feishu 仅替换现有飞书凭据
|
|
41
41
|
|
|
42
|
+
重启选项:
|
|
43
|
+
--task <任务> 将安全重启状态发回任务所在会话
|
|
44
|
+
--reason <原因> 设置重启原因
|
|
45
|
+
--immediate 立即重启,可能中断任务
|
|
46
|
+
|
|
42
47
|
示例:
|
|
43
48
|
agentbot init
|
|
44
49
|
agentbot --profile ~/.agent-bot init --reset
|
|
@@ -62,7 +67,7 @@ Common commands:
|
|
|
62
67
|
console Open the local console
|
|
63
68
|
server start Start Agent Bot in the background
|
|
64
69
|
server status Show the server, Lark App ID, and safe-restart status
|
|
65
|
-
server restart
|
|
70
|
+
server restart [--task <task>] Schedule a safe restart and return status to the task's conversation
|
|
66
71
|
server stop Stop Agent Bot
|
|
67
72
|
task list List tasks
|
|
68
73
|
task status <task> Show task status
|
|
@@ -87,6 +92,11 @@ Init options:
|
|
|
87
92
|
--skip-feishu Initialize for Console only
|
|
88
93
|
--reconfigure-feishu Replace only the existing Lark credentials
|
|
89
94
|
|
|
95
|
+
Restart options:
|
|
96
|
+
--task <task> Return safe-restart status to the task's conversation
|
|
97
|
+
--reason <reason> Set the restart reason
|
|
98
|
+
--immediate Restart immediately and possibly interrupt tasks
|
|
99
|
+
|
|
90
100
|
Examples:
|
|
91
101
|
agentbot init
|
|
92
102
|
agentbot --profile ~/.agent-bot init --reset
|
package/src/cli.ts
CHANGED
|
@@ -65,6 +65,7 @@ import {
|
|
|
65
65
|
type ServerStartResult,
|
|
66
66
|
} from "./cli/ServerStarter.js";
|
|
67
67
|
import { taskChatRoute } from "./cli/taskChatRoute.js";
|
|
68
|
+
import { isThreadContextKey } from "./feishu/contextKey.js";
|
|
68
69
|
import { refreshedSystemEnvironment } from "./supervision/systemEnvironment.js";
|
|
69
70
|
import {
|
|
70
71
|
parseTaskForkGroupOptions,
|
|
@@ -136,6 +137,7 @@ interface InitCommandResult extends InitializationResult {
|
|
|
136
137
|
feishu: {
|
|
137
138
|
status: FeishuInitializationStatus;
|
|
138
139
|
appId?: string;
|
|
140
|
+
userOpenIdStatus?: "configured" | "pending";
|
|
139
141
|
configuration?: EnsureFeishuAppConfigurationResult;
|
|
140
142
|
};
|
|
141
143
|
server: InitializationServerResult;
|
|
@@ -448,6 +450,9 @@ async function initializeFeishu(
|
|
|
448
450
|
return {
|
|
449
451
|
status: "skipped",
|
|
450
452
|
...(existing.appId ? { appId: existing.appId } : {}),
|
|
453
|
+
...(existing.appId && existing.appSecret
|
|
454
|
+
? { userOpenIdStatus: existing.userOpenId ? "configured" as const : "pending" as const }
|
|
455
|
+
: {}),
|
|
451
456
|
};
|
|
452
457
|
}
|
|
453
458
|
if (!options.json) {
|
|
@@ -513,6 +518,7 @@ async function initializeFeishu(
|
|
|
513
518
|
return {
|
|
514
519
|
status,
|
|
515
520
|
appId: credentials.appId,
|
|
521
|
+
userOpenIdStatus: credentials.userOpenId ? "configured" : "pending",
|
|
516
522
|
configuration,
|
|
517
523
|
};
|
|
518
524
|
} finally {
|
|
@@ -659,12 +665,14 @@ async function serverCommand(input: string[]): Promise<void> {
|
|
|
659
665
|
}
|
|
660
666
|
if (action === "restart") {
|
|
661
667
|
const immediate = rest.includes("--immediate") || rest.includes("--force");
|
|
668
|
+
const notificationSessionId = resolveRestartNotificationSessionId(config.storage.sqlitePath, rest);
|
|
662
669
|
const reason = optionValue(rest, "--reason")
|
|
663
670
|
?? (immediate ? "通过 Agent Bot CLI 立即重启" : "通过 Agent Bot CLI 安全重启");
|
|
664
671
|
ensureOk(await sendControlRequest(endpoint, {
|
|
665
672
|
action: "server_restart",
|
|
666
673
|
mode: immediate ? "immediate" : "safe",
|
|
667
674
|
reason,
|
|
675
|
+
notificationSessionId,
|
|
668
676
|
}));
|
|
669
677
|
process.stdout.write(immediate
|
|
670
678
|
? cliText("Immediate restart requested.\n", "已请求立即重启。\n")
|
|
@@ -674,6 +682,42 @@ async function serverCommand(input: string[]): Promise<void> {
|
|
|
674
682
|
throw new Error(cliText(`Unknown server command: ${action}`, `未知的 server 命令:${action}`));
|
|
675
683
|
}
|
|
676
684
|
|
|
685
|
+
function resolveRestartNotificationSessionId(sqlitePath: string, args: string[]): string | undefined {
|
|
686
|
+
const store = new StateStore(sqlitePath);
|
|
687
|
+
try {
|
|
688
|
+
const sessions = store.listAllSessions();
|
|
689
|
+
if (args.includes("--task")) {
|
|
690
|
+
const reference = optionValue(args, "--task")?.trim();
|
|
691
|
+
if (!reference || reference.startsWith("--")) throw new Error(cliText(
|
|
692
|
+
"server restart --task requires a task number or task ID.",
|
|
693
|
+
"server restart --task 需要任务序号或任务 ID。",
|
|
694
|
+
));
|
|
695
|
+
const session = resolveTask(sessions, reference);
|
|
696
|
+
if (
|
|
697
|
+
isThreadContextKey(session.contextKey)
|
|
698
|
+
&& !store.findLatestMessageIdForSession(session.localSessionId, session.contextKey)
|
|
699
|
+
&& !store.findLatestMessageIdForContext(session.contextKey)
|
|
700
|
+
) {
|
|
701
|
+
throw new Error(cliText(
|
|
702
|
+
"The task topic has no message anchor for restart notifications. Send /restart in that topic instead.",
|
|
703
|
+
"该任务话题没有可用于重启通知的消息锚点,请直接在该话题中发送 /restart。",
|
|
704
|
+
));
|
|
705
|
+
}
|
|
706
|
+
return session.localSessionId;
|
|
707
|
+
}
|
|
708
|
+
const runningContexts = new Set(
|
|
709
|
+
sessions.filter((session) => session.status === "running").map((session) => session.contextKey),
|
|
710
|
+
);
|
|
711
|
+
if (runningContexts.size > 1) throw new Error(cliText(
|
|
712
|
+
"Multiple conversations are running. Pass --task <task> so restart notifications return to the requester.",
|
|
713
|
+
"当前有多个会话正在运行。请传入 --task <任务>,确保重启通知返回发起会话。",
|
|
714
|
+
));
|
|
715
|
+
return undefined;
|
|
716
|
+
} finally {
|
|
717
|
+
store.close();
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
677
721
|
async function taskCommand(input: string[]): Promise<void> {
|
|
678
722
|
const [action = "list", ...rest] = input;
|
|
679
723
|
const config = loadConfig();
|
|
@@ -945,6 +989,12 @@ function printInitializationResult(result: Omit<InitCommandResult, "server">): v
|
|
|
945
989
|
"飞书应用:已跳过;请使用 Console 模式或稍后重新运行 init。\n",
|
|
946
990
|
));
|
|
947
991
|
}
|
|
992
|
+
if (result.feishu.userOpenIdStatus === "pending") {
|
|
993
|
+
process.stdout.write(cliText(
|
|
994
|
+
"Lark user: pending; send the bot a direct message to save FEISHU_USER_OPEN_ID\n",
|
|
995
|
+
"飞书用户:等待补全;请私聊机器人以保存 FEISHU_USER_OPEN_ID\n",
|
|
996
|
+
));
|
|
997
|
+
}
|
|
948
998
|
if (result.feishu.configuration?.status === "updated") {
|
|
949
999
|
const added = result.feishu.configuration.added;
|
|
950
1000
|
process.stdout.write(cliText(
|
|
@@ -984,6 +1034,13 @@ function printInitializationServerResult(result: InitCommandResult["server"]): v
|
|
|
984
1034
|
));
|
|
985
1035
|
return;
|
|
986
1036
|
}
|
|
1037
|
+
if (result.status === "restart-scheduled") {
|
|
1038
|
+
process.stdout.write(cliText(
|
|
1039
|
+
"Agent Bot server: safe restart scheduled to load the current version.\n",
|
|
1040
|
+
"Agent Bot 服务:已安排安全重启,以加载当前版本。\n",
|
|
1041
|
+
));
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
987
1044
|
printServerStartResult(result);
|
|
988
1045
|
}
|
|
989
1046
|
|
|
@@ -20,6 +20,7 @@ const COMMAND_NAMES = [
|
|
|
20
20
|
"switch",
|
|
21
21
|
"thinking",
|
|
22
22
|
"title",
|
|
23
|
+
"turns",
|
|
23
24
|
] as const;
|
|
24
25
|
|
|
25
26
|
type CommandName = (typeof COMMAND_NAMES)[number];
|
|
@@ -93,6 +94,9 @@ export class CommandRouter {
|
|
|
93
94
|
if (args.length === 0) return { type: "restart" };
|
|
94
95
|
if (args.length === 1 && args[0] === "--force") return { type: "restart", force: true };
|
|
95
96
|
throw new Error("/restart 只接受一个可选的 --force 参数。");
|
|
97
|
+
case "turns":
|
|
98
|
+
if (args.length > 0) throw new Error("/turns 不接受参数,请在历史轮次卡片中选择 turn。");
|
|
99
|
+
return { type: "turns" };
|
|
96
100
|
case "model":
|
|
97
101
|
return settingsCommand("/model", args, { type: "model" });
|
|
98
102
|
case "provider":
|
|
@@ -15,6 +15,7 @@ export type Command =
|
|
|
15
15
|
| { type: "goal"; action: "set" | "edit"; objective: string }
|
|
16
16
|
| { type: "goal"; action: "pause" | "resume" | "clear" }
|
|
17
17
|
| { type: "restart"; force?: boolean }
|
|
18
|
+
| { type: "turns" }
|
|
18
19
|
| { type: "model" }
|
|
19
20
|
| { type: "provider" }
|
|
20
21
|
| { type: "thinking" }
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { parse as parseDotEnv } from "dotenv";
|
|
4
|
+
|
|
5
|
+
const USER_OPEN_ID_KEY = "FEISHU_USER_OPEN_ID";
|
|
6
|
+
|
|
7
|
+
export interface FeishuUserOpenIdPersistenceResult {
|
|
8
|
+
status: "stored" | "existing";
|
|
9
|
+
userOpenId: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function persistFeishuUserOpenIdIfMissing(
|
|
13
|
+
envPath: string,
|
|
14
|
+
userOpenId: string,
|
|
15
|
+
): FeishuUserOpenIdPersistenceResult {
|
|
16
|
+
if (!/^ou_[A-Za-z0-9_-]+$/u.test(userOpenId)) {
|
|
17
|
+
throw new Error("Cannot persist an invalid Lark user Open ID.");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const original = fs.existsSync(envPath) ? fs.readFileSync(envPath, "utf8") : "";
|
|
21
|
+
const existing = parseDotEnv(original)[USER_OPEN_ID_KEY]?.trim();
|
|
22
|
+
if (existing) return { status: "existing", userOpenId: existing };
|
|
23
|
+
|
|
24
|
+
fs.mkdirSync(path.dirname(envPath), { recursive: true });
|
|
25
|
+
writeFileAtomically(envPath, upsertDotEnvValue(original, USER_OPEN_ID_KEY, userOpenId));
|
|
26
|
+
return { status: "stored", userOpenId };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function upsertDotEnvValue(contents: string, key: string, value: string): string {
|
|
30
|
+
const newline = contents.includes("\r\n") ? "\r\n" : "\n";
|
|
31
|
+
const lines = contents.length > 0 ? contents.split(/\r?\n/u) : [];
|
|
32
|
+
const matcher = new RegExp(`^\\s*(?:export\\s+)?${key}\\s*=`);
|
|
33
|
+
let replaced = false;
|
|
34
|
+
const updated = lines.map((line) => {
|
|
35
|
+
if (!matcher.test(line)) return line;
|
|
36
|
+
replaced = true;
|
|
37
|
+
return `${key}=${value}`;
|
|
38
|
+
});
|
|
39
|
+
while (updated.length > 0 && updated.at(-1) === "") updated.pop();
|
|
40
|
+
if (!replaced) updated.push(`${key}=${value}`);
|
|
41
|
+
return `${updated.join(newline)}${newline}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function writeFileAtomically(filePath: string, contents: string): void {
|
|
45
|
+
const temporaryPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
46
|
+
let temporaryFile: number | undefined;
|
|
47
|
+
try {
|
|
48
|
+
const mode = fs.statSync(filePath, { throwIfNoEntry: false })?.mode ?? 0o600;
|
|
49
|
+
temporaryFile = fs.openSync(temporaryPath, "wx", mode);
|
|
50
|
+
fs.writeFileSync(temporaryFile, contents, "utf8");
|
|
51
|
+
fs.fsyncSync(temporaryFile);
|
|
52
|
+
fs.closeSync(temporaryFile);
|
|
53
|
+
temporaryFile = undefined;
|
|
54
|
+
fs.renameSync(temporaryPath, filePath);
|
|
55
|
+
restrictFilePermissions(filePath);
|
|
56
|
+
syncDirectory(path.dirname(filePath));
|
|
57
|
+
} finally {
|
|
58
|
+
if (temporaryFile !== undefined) fs.closeSync(temporaryFile);
|
|
59
|
+
fs.rmSync(temporaryPath, { force: true });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function restrictFilePermissions(filePath: string): void {
|
|
64
|
+
try {
|
|
65
|
+
fs.chmodSync(filePath, 0o600);
|
|
66
|
+
} catch {
|
|
67
|
+
// Windows and some mounted filesystems do not implement POSIX modes.
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function syncDirectory(directoryPath: string): void {
|
|
72
|
+
let directory: number | undefined;
|
|
73
|
+
try {
|
|
74
|
+
directory = fs.openSync(directoryPath, "r");
|
|
75
|
+
fs.fsyncSync(directory);
|
|
76
|
+
} catch {
|
|
77
|
+
// Directory fsync is not supported on every Windows or mounted filesystem.
|
|
78
|
+
} finally {
|
|
79
|
+
if (directory !== undefined) fs.closeSync(directory);
|
|
80
|
+
}
|
|
81
|
+
}
|
package/src/config/loadConfig.ts
CHANGED
|
@@ -58,6 +58,9 @@ logging:
|
|
|
58
58
|
export function loadConfig(
|
|
59
59
|
configPath?: string,
|
|
60
60
|
): AppConfig {
|
|
61
|
+
if (process.env.FEISHU_USER_OPEN_ID !== undefined && !process.env.FEISHU_USER_OPEN_ID.trim()) {
|
|
62
|
+
delete process.env.FEISHU_USER_OPEN_ID;
|
|
63
|
+
}
|
|
61
64
|
loadDotEnv({
|
|
62
65
|
path: defaultDotEnvPath(),
|
|
63
66
|
quiet: true,
|
|
@@ -142,6 +142,19 @@ export interface TaskListCardAction {
|
|
|
142
142
|
export interface TaskListCardEntry {
|
|
143
143
|
lines: string[];
|
|
144
144
|
actions?: TaskListCardAction[];
|
|
145
|
+
current?: boolean;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface ResetHistoryCardEntry extends TaskListCardEntry {
|
|
149
|
+
sequence: number;
|
|
150
|
+
graphNodeLine: string;
|
|
151
|
+
graphConnectorLine?: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export interface ResetHistoryCardView {
|
|
155
|
+
entries: ResetHistoryCardEntry[];
|
|
156
|
+
footerLines: string[];
|
|
157
|
+
pageActions: TaskListCardAction[];
|
|
145
158
|
}
|
|
146
159
|
|
|
147
160
|
export class CardRenderer {
|
|
@@ -662,6 +675,18 @@ export class CardRenderer {
|
|
|
662
675
|
type: "danger",
|
|
663
676
|
value: { action: "turn_cancel", sessionId: state.sessionId, turnId: state.turnId },
|
|
664
677
|
}]));
|
|
678
|
+
} else if (state.status === "completed") {
|
|
679
|
+
elements.push(
|
|
680
|
+
{ tag: "hr" },
|
|
681
|
+
taskActionRow([{
|
|
682
|
+
text: "Reset",
|
|
683
|
+
value: { action: "turn_reset", sessionId: state.sessionId, turnId: state.turnId },
|
|
684
|
+
}]),
|
|
685
|
+
{
|
|
686
|
+
...markdown("<font color='grey'>Reset 会将当前任务的对话上下文恢复到本轮完成时;不会回退本地文件。</font>"),
|
|
687
|
+
text_size: "notation",
|
|
688
|
+
},
|
|
689
|
+
);
|
|
665
690
|
}
|
|
666
691
|
return turnCard(
|
|
667
692
|
turnTitle(state.status, state.prompt ?? state.taskTitle),
|
|
@@ -774,7 +799,7 @@ export class CardRenderer {
|
|
|
774
799
|
sectionTitle: string,
|
|
775
800
|
entries: TaskListCardEntry[],
|
|
776
801
|
footerLines: string[],
|
|
777
|
-
|
|
802
|
+
footerActions: TaskListCardAction[] = [],
|
|
778
803
|
): Record<string, unknown> {
|
|
779
804
|
const elements: Record<string, unknown>[] = [markdown(`**${sectionTitle}**`)];
|
|
780
805
|
if (entries.length === 0) {
|
|
@@ -791,27 +816,7 @@ export class CardRenderer {
|
|
|
791
816
|
if (footerLines.length > 0) {
|
|
792
817
|
elements.push({ tag: "hr" }, markdown(footerLines.join("\n")));
|
|
793
818
|
}
|
|
794
|
-
if (
|
|
795
|
-
elements.push({
|
|
796
|
-
tag: "column_set",
|
|
797
|
-
flex_mode: "none",
|
|
798
|
-
columns: [{
|
|
799
|
-
tag: "column",
|
|
800
|
-
width: "weighted",
|
|
801
|
-
weight: 1,
|
|
802
|
-
elements: [{
|
|
803
|
-
tag: "button",
|
|
804
|
-
text: { tag: "plain_text", content: footerAction.text },
|
|
805
|
-
type: footerAction.type ?? "default",
|
|
806
|
-
width: "fill",
|
|
807
|
-
behaviors: [{
|
|
808
|
-
type: "callback",
|
|
809
|
-
value: footerAction.value,
|
|
810
|
-
}],
|
|
811
|
-
}],
|
|
812
|
-
}],
|
|
813
|
-
});
|
|
814
|
-
}
|
|
819
|
+
if (footerActions.length > 0) elements.push(taskActionRow(footerActions));
|
|
815
820
|
return {
|
|
816
821
|
schema: "2.0",
|
|
817
822
|
config: {
|
|
@@ -831,6 +836,39 @@ export class CardRenderer {
|
|
|
831
836
|
};
|
|
832
837
|
}
|
|
833
838
|
|
|
839
|
+
renderResetHistoryCard(view: ResetHistoryCardView): Record<string, unknown> {
|
|
840
|
+
const elements: Record<string, unknown>[] = [
|
|
841
|
+
{
|
|
842
|
+
...markdown("<font color='grey'>Reset 会将当前任务的对话上下文恢复到所选轮次完成时;不会回退本地文件。</font>"),
|
|
843
|
+
text_size: "notation",
|
|
844
|
+
},
|
|
845
|
+
{ tag: "hr" },
|
|
846
|
+
markdown("**历史对话轮次**"),
|
|
847
|
+
];
|
|
848
|
+
if (view.entries.length === 0) {
|
|
849
|
+
elements.push(markdown("当前任务还没有成功完成的 turn。"));
|
|
850
|
+
} else {
|
|
851
|
+
view.entries.forEach((entry) => {
|
|
852
|
+
const action = entry.actions?.[0];
|
|
853
|
+
elements.push(resetHistoryEntryRow(
|
|
854
|
+
entry.graphNodeLine,
|
|
855
|
+
entry.graphConnectorLine,
|
|
856
|
+
entry.lines,
|
|
857
|
+
action,
|
|
858
|
+
entry.current === true,
|
|
859
|
+
));
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
if (view.footerLines.length > 0) {
|
|
863
|
+
elements.push({ tag: "hr" }, {
|
|
864
|
+
...markdown(view.footerLines.join("\n")),
|
|
865
|
+
text_size: "notation",
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
if (view.pageActions.length > 0) elements.push(taskActionRow(view.pageActions));
|
|
869
|
+
return sectionCard("历史对话轮次", elements);
|
|
870
|
+
}
|
|
871
|
+
|
|
834
872
|
private baseCard(title: string, template: string, elements: unknown[]): Record<string, unknown> {
|
|
835
873
|
return {
|
|
836
874
|
config: {
|
|
@@ -1470,6 +1508,55 @@ function settingsOptionRow(input: {
|
|
|
1470
1508
|
};
|
|
1471
1509
|
}
|
|
1472
1510
|
|
|
1511
|
+
function resetHistoryEntryRow(
|
|
1512
|
+
graphNodeLine: string,
|
|
1513
|
+
graphConnectorLine: string | undefined,
|
|
1514
|
+
lines: string[],
|
|
1515
|
+
action: TaskListCardAction | undefined,
|
|
1516
|
+
current: boolean,
|
|
1517
|
+
): Record<string, unknown> {
|
|
1518
|
+
const graph = [
|
|
1519
|
+
`<font color='${current ? "green" : "blue"}'>${escapeCardHtml(graphNodeLine)}</font>`,
|
|
1520
|
+
...(graphConnectorLine
|
|
1521
|
+
? [`<font color='grey'>${escapeCardHtml(graphConnectorLine)}</font>`]
|
|
1522
|
+
: []),
|
|
1523
|
+
].join("\n");
|
|
1524
|
+
return {
|
|
1525
|
+
tag: "column_set",
|
|
1526
|
+
flex_mode: "none",
|
|
1527
|
+
horizontal_spacing: "12px",
|
|
1528
|
+
vertical_align: "top",
|
|
1529
|
+
columns: [
|
|
1530
|
+
{
|
|
1531
|
+
tag: "column",
|
|
1532
|
+
width: "auto",
|
|
1533
|
+
vertical_align: "top",
|
|
1534
|
+
elements: [{
|
|
1535
|
+
...markdown(graph),
|
|
1536
|
+
text_align: "center",
|
|
1537
|
+
}],
|
|
1538
|
+
},
|
|
1539
|
+
{
|
|
1540
|
+
tag: "column",
|
|
1541
|
+
width: "weighted",
|
|
1542
|
+
weight: 1,
|
|
1543
|
+
vertical_align: "top",
|
|
1544
|
+
elements: [markdown(lines.join("\n"))],
|
|
1545
|
+
},
|
|
1546
|
+
{
|
|
1547
|
+
tag: "column",
|
|
1548
|
+
width: "auto",
|
|
1549
|
+
vertical_align: "top",
|
|
1550
|
+
elements: current
|
|
1551
|
+
? [markdown("✅ 当前")]
|
|
1552
|
+
: action
|
|
1553
|
+
? [taskActionElement(action)]
|
|
1554
|
+
: [],
|
|
1555
|
+
},
|
|
1556
|
+
],
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1473
1560
|
function taskActionRow(actions: TaskListCardAction[]): Record<string, unknown> {
|
|
1474
1561
|
return {
|
|
1475
1562
|
tag: "column_set",
|