@keyou007/agent-bot 0.1.12 → 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 +18 -0
- package/README.md +32 -13
- package/README.zh.md +32 -13
- package/config.example.yaml +0 -24
- package/dist/cli/AgentPrerequisites.js +262 -0
- package/dist/cli/AgentPrerequisites.js.map +1 -0
- package/dist/cli/DefaultAgentSelection.js +47 -0
- package/dist/cli/DefaultAgentSelection.js.map +1 -0
- package/dist/cli/Initializer.js +165 -2
- package/dist/cli/Initializer.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 -6
- package/dist/cli/help.js.map +1 -1
- package/dist/cli.js +233 -4
- package/dist/cli.js.map +1 -1
- package/dist/codex/AppServerConnection.js +3 -3
- package/dist/codex/AppServerConnection.js.map +1 -1
- package/dist/codex/CodexProcessManager.js +4 -4
- package/dist/codex/CodexProcessManager.js.map +1 -1
- package/dist/codex/CodexRuntime.js +28 -16
- 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 -24
- package/dist/config/loadConfig.js.map +1 -1
- package/dist/console/ConsoleConnector.js +2 -2
- package/dist/console/ConsoleConnector.js.map +1 -1
- package/dist/feishu/CardRenderer.js +90 -30
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/index.js +104 -24
- 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 +321 -98
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/runtime/AcpRuntimeAdapter.js +3 -2
- package/dist/runtime/AcpRuntimeAdapter.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 +42 -20
- package/docs/technical-reference.zh.md +42 -20
- package/npm-shrinkwrap.json +2 -2
- package/package.json +7 -3
- package/skills/agent-bot/SKILL.md +21 -9
- package/src/cli/AgentPrerequisites.ts +377 -0
- package/src/cli/DefaultAgentSelection.ts +56 -0
- package/src/cli/Initializer.ts +204 -3
- package/src/cli/ServerStarter.ts +25 -2
- package/src/cli/controlProtocol.ts +6 -1
- package/src/cli/help.ts +16 -6
- package/src/cli.ts +350 -5
- package/src/codex/AppServerConnection.ts +3 -3
- package/src/codex/CodexProcessManager.ts +4 -4
- package/src/codex/CodexRuntime.ts +38 -18
- 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 -24
- package/src/console/ConsoleConnector.ts +2 -2
- package/src/feishu/CardRenderer.ts +117 -30
- package/src/index.ts +162 -22
- package/src/presentation/turnGraph.ts +48 -0
- package/src/proxy/ProxySessionController.ts +375 -107
- package/src/runtime/AcpRuntimeAdapter.ts +3 -1
- 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
|
@@ -163,7 +163,7 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
163
163
|
if (!isUnsupportedExcludeTurnsError(error)) throw error;
|
|
164
164
|
this.logger.warn(
|
|
165
165
|
{ error },
|
|
166
|
-
"
|
|
166
|
+
"App Server does not support thread/fork excludeTurns; retrying without it.",
|
|
167
167
|
);
|
|
168
168
|
response = await client.request<ThreadResponse>("thread/fork", forkParams, FORK_REQUEST_TIMEOUT_MS);
|
|
169
169
|
}
|
|
@@ -343,7 +343,7 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
343
343
|
);
|
|
344
344
|
this.logger.info(
|
|
345
345
|
{ ...(sessionId ? { sessionId } : {}), threadId: remoteSessionId, turnId },
|
|
346
|
-
"
|
|
346
|
+
"App Server accepted the turn interrupt request.",
|
|
347
347
|
);
|
|
348
348
|
}
|
|
349
349
|
|
|
@@ -554,7 +554,7 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
554
554
|
const session = [...this.sessions.values()].find((candidate) => candidate.remoteSessionId === threadId);
|
|
555
555
|
if (session?.activeTurnId && status && status !== "active") {
|
|
556
556
|
void this.synchronizeSession(session.localSessionId).catch((error: unknown) => {
|
|
557
|
-
this.logger.warn({ error, sessionId: session.localSessionId, status }, "Failed to reconcile
|
|
557
|
+
this.logger.warn({ error, sessionId: session.localSessionId, status }, "Failed to reconcile App Server thread status.");
|
|
558
558
|
});
|
|
559
559
|
}
|
|
560
560
|
return;
|
|
@@ -566,7 +566,7 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
566
566
|
if (session.terminalTurnIds.has(mapped.turnId)) {
|
|
567
567
|
this.logger.debug(
|
|
568
568
|
{ sessionId: session.localSessionId, turnId: mapped.turnId, method },
|
|
569
|
-
"Ignoring a replayed terminal
|
|
569
|
+
"Ignoring a replayed terminal App Server turn event.",
|
|
570
570
|
);
|
|
571
571
|
return;
|
|
572
572
|
}
|
|
@@ -586,7 +586,7 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
586
586
|
notificationTurnId: mapped.turnId,
|
|
587
587
|
notificationStartedAt: mapped.startedAt,
|
|
588
588
|
},
|
|
589
|
-
"Ignoring a replayed historical
|
|
589
|
+
"Ignoring a replayed historical App Server turn start.",
|
|
590
590
|
);
|
|
591
591
|
return;
|
|
592
592
|
}
|
|
@@ -597,10 +597,10 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
597
597
|
if (!session.activeTurnId || session.activeTurnId !== mapped.turnId) {
|
|
598
598
|
this.logger.debug(
|
|
599
599
|
{ sessionId: session.localSessionId, activeTurnId: session.activeTurnId, notificationTurnId: mapped.turnId, method },
|
|
600
|
-
"Ignoring out-of-order
|
|
600
|
+
"Ignoring out-of-order App Server notification and scheduling reconciliation.",
|
|
601
601
|
);
|
|
602
602
|
void this.synchronizeSession(session.localSessionId).catch((error: unknown) => {
|
|
603
|
-
this.logger.warn({ error, sessionId: session.localSessionId }, "Failed to reconcile out-of-order
|
|
603
|
+
this.logger.warn({ error, sessionId: session.localSessionId }, "Failed to reconcile out-of-order App Server notification.");
|
|
604
604
|
});
|
|
605
605
|
return;
|
|
606
606
|
}
|
|
@@ -671,9 +671,12 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
671
671
|
if (mapped.status === "cancelled") {
|
|
672
672
|
this.emit({ type: "turn_cancelled", sessionId, turnId: mapped.turnId });
|
|
673
673
|
} else if (mapped.status === "failed") {
|
|
674
|
-
this.emit({ type: "turn_failed", sessionId, turnId: mapped.turnId, message: mapped.error ?? "
|
|
674
|
+
this.emit({ type: "turn_failed", sessionId, turnId: mapped.turnId, message: mapped.error ?? "App Server turn failed." });
|
|
675
675
|
} else {
|
|
676
|
-
const finalResponse =
|
|
676
|
+
const finalResponse = appendModelSwitchGuidance(
|
|
677
|
+
appendGeneratedImageMarkdown(session.finalText, session.generatedImagePaths),
|
|
678
|
+
session.model,
|
|
679
|
+
);
|
|
677
680
|
this.emit({
|
|
678
681
|
type: "turn_completed",
|
|
679
682
|
sessionId,
|
|
@@ -703,7 +706,7 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
703
706
|
turnId,
|
|
704
707
|
request: {
|
|
705
708
|
id: requestId,
|
|
706
|
-
title: stringValue(params.command) ?? "
|
|
709
|
+
title: stringValue(params.command) ?? "Agent approval request",
|
|
707
710
|
command: stringValue(params.command),
|
|
708
711
|
reason: stringValue(params.reason),
|
|
709
712
|
options: [
|
|
@@ -781,7 +784,7 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
781
784
|
type: "turn_failed",
|
|
782
785
|
sessionId: session.localSessionId,
|
|
783
786
|
turnId: activeTurnId,
|
|
784
|
-
message: "
|
|
787
|
+
message: "The App Server no longer reports this execution in the thread history.",
|
|
785
788
|
});
|
|
786
789
|
}
|
|
787
790
|
return;
|
|
@@ -828,7 +831,7 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
828
831
|
type: "turn_failed",
|
|
829
832
|
sessionId: session.localSessionId,
|
|
830
833
|
turnId: turn.id,
|
|
831
|
-
message: turn.error?.message ?? "
|
|
834
|
+
message: turn.error?.message ?? "App Server turn failed.",
|
|
832
835
|
});
|
|
833
836
|
return;
|
|
834
837
|
}
|
|
@@ -836,9 +839,12 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
836
839
|
...session.generatedImagePaths,
|
|
837
840
|
...extractGeneratedImagePaths(turn),
|
|
838
841
|
]);
|
|
839
|
-
const finalResponse =
|
|
840
|
-
|
|
841
|
-
|
|
842
|
+
const finalResponse = appendModelSwitchGuidance(
|
|
843
|
+
appendGeneratedImageMarkdown(
|
|
844
|
+
extractFinalResponse(turn) || session.finalText,
|
|
845
|
+
generatedImagePaths,
|
|
846
|
+
),
|
|
847
|
+
session.model,
|
|
842
848
|
);
|
|
843
849
|
session.finalText = finalResponse;
|
|
844
850
|
session.generatedImagePaths = [];
|
|
@@ -888,13 +894,13 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
888
894
|
|
|
889
895
|
private requireSession(sessionId: string): CodexSession {
|
|
890
896
|
const session = this.sessions.get(sessionId);
|
|
891
|
-
if (!session) throw new Error(`Unknown
|
|
897
|
+
if (!session) throw new Error(`Unknown App Server session: ${sessionId}`);
|
|
892
898
|
return session;
|
|
893
899
|
}
|
|
894
900
|
|
|
895
901
|
private requireActiveTurn(sessionId: string, turnId: string): CodexSession {
|
|
896
902
|
const session = this.requireSession(sessionId);
|
|
897
|
-
if (session.activeTurnId !== turnId) throw new Error(`
|
|
903
|
+
if (session.activeTurnId !== turnId) throw new Error(`App Server turn is no longer active: ${turnId}`);
|
|
898
904
|
return session;
|
|
899
905
|
}
|
|
900
906
|
|
|
@@ -917,7 +923,7 @@ export class CodexRuntime implements AgentRuntime {
|
|
|
917
923
|
type: "turn_failed",
|
|
918
924
|
sessionId: session.localSessionId,
|
|
919
925
|
turnId,
|
|
920
|
-
message: `
|
|
926
|
+
message: `App Server disconnected: ${error.message}`,
|
|
921
927
|
});
|
|
922
928
|
}
|
|
923
929
|
for (const [requestId, pending] of this.approvals) {
|
|
@@ -978,6 +984,20 @@ function permissionParams(mode: PermissionMode): { approvalPolicy: "never" | "on
|
|
|
978
984
|
: { approvalPolicy: "on-request", sandbox: "workspace-write" };
|
|
979
985
|
}
|
|
980
986
|
|
|
987
|
+
const MODEL_SWITCH_GUIDANCE = "请发送 `/model` 切换到其他模型后重试。";
|
|
988
|
+
|
|
989
|
+
function appendModelSwitchGuidance(response: string, model?: string): string {
|
|
990
|
+
if (!hasKnownCodeModeFailure(response) || response.includes(MODEL_SWITCH_GUIDANCE)) return response;
|
|
991
|
+
const modelLabel = model?.trim() ? ` \`${model.trim()}\`` : "";
|
|
992
|
+
const guidance = `> 当前模型${modelLabel} 的本地工具执行通道异常。${MODEL_SWITCH_GUIDANCE}`;
|
|
993
|
+
return response.trimEnd() ? `${response.trimEnd()}\n\n${guidance}` : guidance;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
function hasKnownCodeModeFailure(response: string): boolean {
|
|
997
|
+
return /code-mode host closed its stdout/i.test(response)
|
|
998
|
+
|| /exec expects an object containing raw JavaScript in [`'"]?input[`'"]?/i.test(response);
|
|
999
|
+
}
|
|
1000
|
+
|
|
981
1001
|
function threadLifecycleParams(cwd: string): {
|
|
982
1002
|
developerInstructions: string;
|
|
983
1003
|
} {
|
|
@@ -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
|
@@ -43,30 +43,6 @@ agents:
|
|
|
43
43
|
- "stdio://"
|
|
44
44
|
env: {}
|
|
45
45
|
|
|
46
|
-
coco:
|
|
47
|
-
title: "Coco"
|
|
48
|
-
command: "coco"
|
|
49
|
-
args:
|
|
50
|
-
- "acp"
|
|
51
|
-
- "serve"
|
|
52
|
-
env: {}
|
|
53
|
-
|
|
54
|
-
coco-yolo:
|
|
55
|
-
title: "Coco YOLO"
|
|
56
|
-
command: "coco"
|
|
57
|
-
args:
|
|
58
|
-
- "acp"
|
|
59
|
-
- "serve"
|
|
60
|
-
- "--yolo"
|
|
61
|
-
env: {}
|
|
62
|
-
|
|
63
|
-
example:
|
|
64
|
-
title: "Example ACP Agent"
|
|
65
|
-
command: "node"
|
|
66
|
-
args:
|
|
67
|
-
- "./examples/example-acp-agent.js"
|
|
68
|
-
env: {}
|
|
69
|
-
|
|
70
46
|
defaults:
|
|
71
47
|
agent: "codex"
|
|
72
48
|
cwd: "."
|
|
@@ -82,6 +58,9 @@ logging:
|
|
|
82
58
|
export function loadConfig(
|
|
83
59
|
configPath?: string,
|
|
84
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
|
+
}
|
|
85
64
|
loadDotEnv({
|
|
86
65
|
path: defaultDotEnvPath(),
|
|
87
66
|
quiet: true,
|
|
@@ -12,8 +12,8 @@ export class ConsoleConnector {
|
|
|
12
12
|
|
|
13
13
|
start(): void {
|
|
14
14
|
if (this.reader) return;
|
|
15
|
-
this.logger.info("Starting local
|
|
16
|
-
console.log("
|
|
15
|
+
this.logger.info("Starting the local Agent Bot console.");
|
|
16
|
+
console.log("Agent Bot console ready. Type /help or enter a prompt.");
|
|
17
17
|
const reader = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: "agentbot> " });
|
|
18
18
|
this.reader = reader;
|
|
19
19
|
reader.prompt();
|
|
@@ -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 {
|
|
@@ -163,7 +176,7 @@ export class CardRenderer {
|
|
|
163
176
|
: `**默认 Agent**:${inlineCode(view.currentAgent)}`,
|
|
164
177
|
...(view.runtimeSettingsAvailable
|
|
165
178
|
? [
|
|
166
|
-
`**Provider / 模型**:${inlineCode(view.currentProvider ?? "
|
|
179
|
+
`**Provider / 模型**:${inlineCode(view.currentProvider ?? "Agent 默认")} / ${inlineCode(view.currentModel ?? "默认")}`,
|
|
167
180
|
`**思考强度 / 权限**:${inlineCode(view.currentEffort ?? "自动")} / ${inlineCode(permissionModeLabel(view.currentPermissionMode))}`,
|
|
168
181
|
]
|
|
169
182
|
: []),
|
|
@@ -190,7 +203,7 @@ export class CardRenderer {
|
|
|
190
203
|
if (!view.providerSupported) {
|
|
191
204
|
elements.push(markdown("当前运行时不支持 Provider 切换;其他设置仍可通过上方 tab 修改。"));
|
|
192
205
|
} else if (view.providers.length === 0) {
|
|
193
|
-
elements.push(markdown("
|
|
206
|
+
elements.push(markdown("当前 Agent 配置中没有可用的 Provider。"));
|
|
194
207
|
} else {
|
|
195
208
|
elements.push(...view.providers.map((provider) => settingsOptionRow({
|
|
196
209
|
label: [
|
|
@@ -270,14 +283,14 @@ export class CardRenderer {
|
|
|
270
283
|
renderProviderSelector(view: ProviderSelectorCardView): Record<string, unknown> {
|
|
271
284
|
const elements: Record<string, unknown>[] = [
|
|
272
285
|
markdown([
|
|
273
|
-
`**当前 Provider**:${inlineCode(view.currentProvider ?? "
|
|
286
|
+
`**当前 Provider**:${inlineCode(view.currentProvider ?? "Agent 默认")}`,
|
|
274
287
|
`**模型 / 思考强度 / 权限**:${inlineCode(view.currentModel ?? "默认")} / ${inlineCode(view.reasoningEffort ?? "自动")} / ${inlineCode(permissionModeLabel(view.permissionMode))}`,
|
|
275
288
|
...(view.notice ? [view.notice] : []),
|
|
276
289
|
].join("\n")),
|
|
277
290
|
{ tag: "hr" },
|
|
278
291
|
];
|
|
279
292
|
if (view.providers.length === 0) {
|
|
280
|
-
elements.push(markdown("
|
|
293
|
+
elements.push(markdown("当前 Agent 配置中没有可用的 Provider。"));
|
|
281
294
|
} else {
|
|
282
295
|
elements.push(...view.providers.map((provider) => ({
|
|
283
296
|
tag: "column_set",
|
|
@@ -635,14 +648,14 @@ export class CardRenderer {
|
|
|
635
648
|
? [
|
|
636
649
|
`**当前任务**:${inlineCode(view.currentTask.title ?? view.currentTask.id)}`,
|
|
637
650
|
workspaceLine,
|
|
638
|
-
`**Provider / 模型 / 思考强度 / 权限**:${inlineCode(view.currentTask.modelProvider ?? "
|
|
651
|
+
`**Provider / 模型 / 思考强度 / 权限**:${inlineCode(view.currentTask.modelProvider ?? "Agent 默认")} / ${inlineCode(view.currentTask.model ?? "默认")} / ${inlineCode(view.currentTask.reasoningEffort ?? "自动")} / ${inlineCode(permissionModeLabel(view.currentTask.permissionMode))}`,
|
|
639
652
|
`**任务状态 / Agent**:${persistedTaskStatus(view.currentTask.sessionStatus, view.currentTask.lastTurnStatus)} / ${inlineCode(view.currentTask.agentName)}`,
|
|
640
653
|
`**任务 ID**:${inlineCode(view.currentTask.id)}`,
|
|
641
654
|
]
|
|
642
655
|
: [
|
|
643
656
|
"**当前任务**:无,下一条普通消息会创建新任务",
|
|
644
657
|
workspaceLine,
|
|
645
|
-
`**Provider / 模型 / 思考强度 / 权限**:${inlineCode("
|
|
658
|
+
`**Provider / 模型 / 思考强度 / 权限**:${inlineCode("Agent 默认")} / ${inlineCode("默认")} / ${inlineCode("自动")} / ${inlineCode(permissionModeLabel())}`,
|
|
646
659
|
`**默认 Agent**:${view.defaultAgentTitle} (${inlineCode(view.defaultAgentName)})`,
|
|
647
660
|
];
|
|
648
661
|
lines.push(
|
|
@@ -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),
|
|
@@ -673,8 +698,8 @@ export class CardRenderer {
|
|
|
673
698
|
|
|
674
699
|
renderTurnDetails(state: TurnViewState): Record<string, unknown> {
|
|
675
700
|
const title = state.taskTitle
|
|
676
|
-
?
|
|
677
|
-
: "
|
|
701
|
+
? `任务执行详情:${truncateText(state.taskTitle.replace(/\s+/g, " ").trim(), 60)}`
|
|
702
|
+
: "任务执行详情";
|
|
678
703
|
return turnCard(title, "blue", renderTurnElements(state, "always"), renderTurnSubtitle(state));
|
|
679
704
|
}
|
|
680
705
|
|
|
@@ -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",
|