@keyou007/agent-bot 0.1.24-alpha.5 → 0.1.24-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/README.md +29 -3
- package/README.zh.md +29 -3
- package/SECURITY.md +12 -0
- package/config.example.yaml +5 -0
- package/dist/cli/controlProtocol.js.map +1 -1
- package/dist/cli/serverStatus.js +1 -0
- package/dist/cli/serverStatus.js.map +1 -1
- package/dist/cli.js +6 -1
- package/dist/cli.js.map +1 -1
- package/dist/codex/CodexEventMapper.js +1 -0
- package/dist/codex/CodexEventMapper.js.map +1 -1
- package/dist/codex/CodexRuntime.js +247 -120
- package/dist/codex/CodexRuntime.js.map +1 -1
- package/dist/codex/ProviderSwitch.js +12 -13
- package/dist/codex/ProviderSwitch.js.map +1 -1
- package/dist/codex/TraePlanEntry.js +70 -0
- package/dist/codex/TraePlanEntry.js.map +1 -0
- package/dist/config/AgentExecutionDefaultsStore.js +4 -0
- package/dist/config/AgentExecutionDefaultsStore.js.map +1 -1
- package/dist/config/loadConfig.js +4 -0
- package/dist/config/loadConfig.js.map +1 -1
- package/dist/config/schema.js +3 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/feishu/CardRenderer.js +92 -8
- package/dist/feishu/CardRenderer.js.map +1 -1
- package/dist/feishu/FeishuConnector.js +1 -0
- package/dist/feishu/FeishuConnector.js.map +1 -1
- package/dist/feishu/FeishuTurnPresenter.js +121 -17
- package/dist/feishu/FeishuTurnPresenter.js.map +1 -1
- package/dist/index.js +57 -7
- package/dist/index.js.map +1 -1
- package/dist/local-files/LocalFileViewerServer.js +51 -4
- package/dist/local-files/LocalFileViewerServer.js.map +1 -1
- package/dist/local-files/TurnPreviewPage.js +7 -2
- package/dist/local-files/TurnPreviewPage.js.map +1 -1
- package/dist/presentation/TurnStateReducer.js +41 -6
- package/dist/presentation/TurnStateReducer.js.map +1 -1
- package/dist/proxy/ProxySessionController.js +620 -214
- package/dist/proxy/ProxySessionController.js.map +1 -1
- package/dist/proxy/SessionOperationQueue.js +14 -0
- package/dist/proxy/SessionOperationQueue.js.map +1 -0
- package/dist/runtime/AcpRuntimeAdapter.js +4 -2
- package/dist/runtime/AcpRuntimeAdapter.js.map +1 -1
- package/dist/state/StateStore.js +22 -0
- package/dist/state/StateStore.js.map +1 -1
- package/dist/state/TurnPreviewJournal.js +19 -7
- package/dist/state/TurnPreviewJournal.js.map +1 -1
- package/dist/state/migrations.js +6 -0
- package/dist/state/migrations.js.map +1 -1
- package/dist/supervision/SafeRestartNotifier.js +33 -1
- package/dist/supervision/SafeRestartNotifier.js.map +1 -1
- package/dist/supervision/SafeRestartScheduler.js +22 -6
- package/dist/supervision/SafeRestartScheduler.js.map +1 -1
- package/dist/terminal/TerminalCards.js +13 -0
- package/dist/terminal/TerminalCards.js.map +1 -0
- package/dist/terminal/TerminalCommandTracker.js +74 -0
- package/dist/terminal/TerminalCommandTracker.js.map +1 -0
- package/dist/terminal/TerminalLinks.js +76 -0
- package/dist/terminal/TerminalLinks.js.map +1 -0
- package/dist/terminal/TerminalPage.js +212 -0
- package/dist/terminal/TerminalPage.js.map +1 -0
- package/dist/terminal/TerminalProcess.js +95 -0
- package/dist/terminal/TerminalProcess.js.map +1 -0
- package/dist/terminal/TerminalServer.js +676 -0
- package/dist/terminal/TerminalServer.js.map +1 -0
- package/docs/technical-reference.md +48 -0
- package/docs/technical-reference.zh.md +48 -0
- package/npm-shrinkwrap.json +55 -5
- package/package.json +11 -4
- package/skills/agent-bot/SKILL.md +11 -2
- package/src/cli/controlProtocol.ts +2 -1
- package/src/cli/serverStatus.ts +1 -0
- package/src/cli.ts +6 -1
- package/src/codex/CodexEventMapper.ts +1 -0
- package/src/codex/CodexRuntime.ts +244 -128
- package/src/codex/ProviderSwitch.ts +11 -12
- package/src/codex/TraePlanEntry.ts +63 -0
- package/src/config/AgentExecutionDefaultsStore.ts +2 -0
- package/src/config/loadConfig.ts +4 -0
- package/src/config/schema.ts +5 -1
- package/src/feishu/CardRenderer.ts +109 -8
- package/src/feishu/FeishuConnector.ts +1 -0
- package/src/feishu/FeishuTurnPresenter.ts +108 -16
- package/src/feishu/types.ts +1 -0
- package/src/index.ts +56 -8
- package/src/local-files/LocalFileViewerServer.ts +48 -4
- package/src/local-files/TurnPreviewPage.ts +7 -2
- package/src/presentation/TurnStateReducer.ts +46 -7
- package/src/presentation/turnViewTypes.ts +5 -0
- package/src/proxy/ProxySessionController.ts +629 -211
- package/src/proxy/SessionOperationQueue.ts +13 -0
- package/src/runtime/AcpRuntimeAdapter.ts +5 -3
- package/src/runtime/types.ts +8 -3
- package/src/state/StateStore.ts +38 -1
- package/src/state/TurnPreviewJournal.ts +19 -7
- package/src/state/migrations.ts +6 -0
- package/src/supervision/SafeRestartNotifier.ts +35 -2
- package/src/supervision/SafeRestartScheduler.ts +25 -6
- package/src/terminal/TerminalCards.ts +18 -0
- package/src/terminal/TerminalCommandTracker.ts +74 -0
- package/src/terminal/TerminalLinks.ts +61 -0
- package/src/terminal/TerminalPage.ts +213 -0
- package/src/terminal/TerminalProcess.ts +93 -0
- package/src/terminal/TerminalServer.ts +551 -0
|
@@ -1,24 +1,23 @@
|
|
|
1
|
-
import { AppServerRequestError } from "./AppServerConnection.js";
|
|
2
1
|
import type { RuntimeExecutionSettings } from "../runtime/types.js";
|
|
3
2
|
|
|
4
3
|
export const PROVIDER_SWITCH_BUSY = "当前任务正在执行,暂时不能切换 Provider。请等待任务完成,或停止任务后重试。";
|
|
4
|
+
export const PROVIDER_SWITCH_EMPTY = "App Server 尚未保存此空会话,无法在保留任务 ID 的前提下切换 Provider。未改变会话或设置,也不会自动创建替代任务。";
|
|
5
5
|
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return /no rollout found|rollout[^\n]*(?:not found|missing)/iu.test(message);
|
|
6
|
+
export class ProviderSwitchError extends Error {
|
|
7
|
+
constructor(message: string, readonly previousSettingsRestored: boolean, options?: ErrorOptions) {
|
|
8
|
+
super(message, options);
|
|
9
|
+
this.name = "ProviderSwitchError";
|
|
10
|
+
}
|
|
12
11
|
}
|
|
13
12
|
|
|
14
13
|
export function assertProviderSettingsApplied(
|
|
15
14
|
actual: { modelProvider?: string; model?: string },
|
|
16
|
-
requested: Pick<RuntimeExecutionSettings, "modelProvider" | "model"
|
|
15
|
+
requested: Partial<Pick<RuntimeExecutionSettings, "modelProvider" | "model">>,
|
|
17
16
|
): void {
|
|
18
|
-
if (actual.modelProvider !== requested.modelProvider) {
|
|
19
|
-
throw new Error(`Provider 未生效:请求 ${requested.modelProvider},实际返回 ${actual.modelProvider ?? "未知"}
|
|
17
|
+
if (requested.modelProvider !== undefined && actual.modelProvider !== requested.modelProvider) {
|
|
18
|
+
throw new Error(`Provider 未生效:请求 ${requested.modelProvider},实际返回 ${actual.modelProvider ?? "未知"}。App Server 未应用设置;可在任务空闲后安全重启再重试,不会自动 Fork 或更改历史。`);
|
|
20
19
|
}
|
|
21
|
-
if (actual.model !== requested.model) {
|
|
20
|
+
if (requested.model !== undefined && actual.model !== requested.model) {
|
|
22
21
|
throw new Error(`模型未生效:请求 ${requested.model},实际返回 ${actual.model ?? "未知"}。请确认目标 Provider 支持该模型。`);
|
|
23
22
|
}
|
|
24
23
|
}
|
|
@@ -29,7 +28,7 @@ export function providerSwitchFailure(error: unknown): string {
|
|
|
29
28
|
if (/model[^\n]*(?:unsupported|not supported|not found|unavailable|does not exist)|unknown model/iu.test(message)) {
|
|
30
29
|
return `目标 Provider 不支持当前模型,请选择兼容的模型后重试。${message}`;
|
|
31
30
|
}
|
|
32
|
-
if (/unauthorized|forbidden|auth(?:
|
|
31
|
+
if (/unauthorized|forbidden|auth(?:enticat(?:e|ion)|orization)|api.?key|provider[^\n]*(?:not found|unknown|not configured)/iu.test(message)) {
|
|
33
32
|
return `请检查目标 Provider 的配置和认证信息。${message}`;
|
|
34
33
|
}
|
|
35
34
|
return message;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { open } from "node:fs/promises";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
const MAX_TAIL_BYTES = 256 * 1024;
|
|
5
|
+
const mutation = z.object({
|
|
6
|
+
type: z.literal("history_mutation"),
|
|
7
|
+
payload: z.object({
|
|
8
|
+
version: z.literal(1),
|
|
9
|
+
turn_id: z.string(),
|
|
10
|
+
operation: z.string(),
|
|
11
|
+
items: z.array(z.object({
|
|
12
|
+
type: z.string(),
|
|
13
|
+
call_id: z.string().optional(),
|
|
14
|
+
name: z.string().optional(),
|
|
15
|
+
output: z.unknown().optional(),
|
|
16
|
+
})).optional(),
|
|
17
|
+
}),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// TraeX can finish EnterPlanMode without a mode/changeResolved notification.
|
|
21
|
+
// Read only a bounded tail, and require the exact call and its successful result.
|
|
22
|
+
export async function hasCompletedTraePlanEntry(
|
|
23
|
+
rolloutPath: string, turnId: string, toolCallId: string,
|
|
24
|
+
): Promise<boolean> {
|
|
25
|
+
const file = await open(rolloutPath, "r");
|
|
26
|
+
try {
|
|
27
|
+
const { size } = await file.stat();
|
|
28
|
+
const start = Math.max(0, size - MAX_TAIL_BYTES);
|
|
29
|
+
const buffer = Buffer.alloc(size - start);
|
|
30
|
+
const { bytesRead } = await file.read(buffer, 0, buffer.length, start);
|
|
31
|
+
const text = buffer.subarray(0, bytesRead).toString("utf8");
|
|
32
|
+
const lines = text.split("\n");
|
|
33
|
+
if (start > 0) lines.shift();
|
|
34
|
+
lines.pop(); // Ignore an uncommitted/partial final record.
|
|
35
|
+
let entered = false;
|
|
36
|
+
let matchedCall = false;
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
let value: unknown;
|
|
39
|
+
try { value = JSON.parse(line); } catch { continue; }
|
|
40
|
+
const parsed = mutation.safeParse(value);
|
|
41
|
+
if (!parsed.success || parsed.data.payload.turn_id !== turnId) continue;
|
|
42
|
+
const payload = parsed.data.payload;
|
|
43
|
+
if (payload.operation !== "append") {
|
|
44
|
+
matchedCall = false;
|
|
45
|
+
entered = false;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
for (const item of payload.items ?? []) {
|
|
49
|
+
if (item.call_id !== toolCallId) continue;
|
|
50
|
+
if (item.type === "function_call") {
|
|
51
|
+
matchedCall = item.name === "EnterPlanMode";
|
|
52
|
+
entered = false;
|
|
53
|
+
} else if (item.type === "function_call_output") {
|
|
54
|
+
entered = matchedCall && typeof item.output === "string"
|
|
55
|
+
&& item.output.startsWith("Entered plan mode.");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return entered;
|
|
60
|
+
} finally {
|
|
61
|
+
await file.close();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -33,7 +33,9 @@ export function writeAgentExecutionDefaults(
|
|
|
33
33
|
for (const key of DEFAULT_KEYS) {
|
|
34
34
|
const value = defaults[key];
|
|
35
35
|
if (value !== undefined) target.set(key, value);
|
|
36
|
+
else target.delete(key);
|
|
36
37
|
}
|
|
38
|
+
if (target.items.length === 0) agent.delete("defaults");
|
|
37
39
|
|
|
38
40
|
const updated = document.toString();
|
|
39
41
|
if (updated === original) return false;
|
package/src/config/loadConfig.ts
CHANGED
|
@@ -30,6 +30,10 @@ updates:
|
|
|
30
30
|
# Daily stable update check, private 60-second cancellation window, then safe update.
|
|
31
31
|
enabled: true
|
|
32
32
|
|
|
33
|
+
terminal:
|
|
34
|
+
enabled: true
|
|
35
|
+
# Shares fileViewer host, port and publicBaseUrl; keeps independent owner authorization.
|
|
36
|
+
|
|
33
37
|
fileViewer:
|
|
34
38
|
enabled: true
|
|
35
39
|
# Use 0.0.0.0 to accept LAN connections and automatically prefer
|
package/src/config/schema.ts
CHANGED
|
@@ -57,6 +57,9 @@ export const agentConfigSchema = z.object({
|
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
export const appConfigSchema = z.object({
|
|
60
|
+
terminal: z.object({
|
|
61
|
+
enabled: z.boolean().default(true),
|
|
62
|
+
}).default({ enabled: true }),
|
|
60
63
|
updates: z.object({ enabled: z.boolean().default(true) }).default({ enabled: true }),
|
|
61
64
|
console: z
|
|
62
65
|
.object({
|
|
@@ -108,6 +111,7 @@ export type AgentConfig = Omit<ParsedAgentConfig, "defaults"> & {
|
|
|
108
111
|
defaults?: AgentExecutionDefaults;
|
|
109
112
|
};
|
|
110
113
|
export type GroupNameFormatConfig = z.infer<typeof groupNameFormatSchema>;
|
|
111
|
-
export type AppConfig = Omit<z.infer<typeof appConfigSchema>, "updates"> & {
|
|
114
|
+
export type AppConfig = Omit<z.infer<typeof appConfigSchema>, "updates" | "terminal"> & {
|
|
112
115
|
updates?: { enabled: boolean };
|
|
116
|
+
terminal?: z.infer<typeof appConfigSchema>["terminal"];
|
|
113
117
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { approvalActivityId } from "../presentation/TurnStateReducer.js";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import type { JsonValue } from "../acp/acpTypes.js";
|
|
4
5
|
import type { RuntimeSession } from "../acp/AcpSessionManager.js";
|
|
@@ -14,6 +15,7 @@ import type {
|
|
|
14
15
|
import type { TurnActivity, TurnViewState, TurnViewStatus } from "../presentation/turnViewTypes.js";
|
|
15
16
|
import { truncateMiddle, truncateText } from "../utils/markdown.js";
|
|
16
17
|
import type { UpdateNotice } from "../updates/types.js";
|
|
18
|
+
import type { ActiveTerminal, TerminalCardView } from "../terminal/TerminalServer.js";
|
|
17
19
|
import { localCardImage } from "./LocalCardImage.js";
|
|
18
20
|
|
|
19
21
|
export interface StartupStatusView {
|
|
@@ -58,6 +60,9 @@ export interface InitializationWelcomeView {
|
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
export interface SafeRestartStatusView {
|
|
63
|
+
activeTerminals?: number;
|
|
64
|
+
terminalBlockers?: ActiveTerminal[];
|
|
65
|
+
contextKey?: string;
|
|
61
66
|
scheduleId: number;
|
|
62
67
|
reason: string;
|
|
63
68
|
phase: "waiting_tasks" | "waiting_delivery" | "countdown" | "restarting" | "cancelled" | "superseded";
|
|
@@ -85,6 +90,7 @@ export interface ModelSelectorCardView {
|
|
|
85
90
|
sessionId: string;
|
|
86
91
|
contextKey: string;
|
|
87
92
|
currentModel?: string;
|
|
93
|
+
modelPage?: number;
|
|
88
94
|
reasoningEffort?: string;
|
|
89
95
|
models: ModelOption[];
|
|
90
96
|
modelProvider?: string;
|
|
@@ -131,7 +137,7 @@ export type ExecutionSettingsTab = "agent" | "provider" | "model" | "thinking" |
|
|
|
131
137
|
|
|
132
138
|
const EXECUTION_SETTING_EFFECTS: Record<ExecutionSettingsTab, string> = {
|
|
133
139
|
agent: "只影响新建任务,不切换当前任务。",
|
|
134
|
-
provider: "
|
|
140
|
+
provider: "只影响当前任务;运行中选择将在本轮结束后生效。",
|
|
135
141
|
model: "只影响当前任务,下一轮生效。",
|
|
136
142
|
thinking: "只影响当前任务,下一轮生效。",
|
|
137
143
|
permission: "只影响当前任务,下一轮生效。",
|
|
@@ -143,6 +149,8 @@ export interface ExecutionSettingsAgentOption {
|
|
|
143
149
|
}
|
|
144
150
|
|
|
145
151
|
export interface ExecutionSettingsCardView {
|
|
152
|
+
pendingNotice?: string;
|
|
153
|
+
pendingFailed?: boolean;
|
|
146
154
|
sessionId?: string;
|
|
147
155
|
contextKey: string;
|
|
148
156
|
activeTab: ExecutionSettingsTab;
|
|
@@ -152,6 +160,7 @@ export interface ExecutionSettingsCardView {
|
|
|
152
160
|
runtimeSettingsAvailable: boolean;
|
|
153
161
|
currentProvider?: string;
|
|
154
162
|
currentModel?: string;
|
|
163
|
+
modelPage?: number;
|
|
155
164
|
currentEffort?: string;
|
|
156
165
|
currentPermissionMode: PermissionMode;
|
|
157
166
|
providers: ModelProviderOption[];
|
|
@@ -263,6 +272,7 @@ export interface DismissGroupCardView {
|
|
|
263
272
|
}
|
|
264
273
|
|
|
265
274
|
export interface ShellCommandCardView {
|
|
275
|
+
terminalUrl?: string;
|
|
266
276
|
jobId?: string;
|
|
267
277
|
contextKey?: string;
|
|
268
278
|
command: string;
|
|
@@ -508,6 +518,9 @@ export class CardRenderer {
|
|
|
508
518
|
...(outputWasTruncated ? ["输出过长,已保留开头和结尾并截断中间内容。"] : []),
|
|
509
519
|
].join("\n")),
|
|
510
520
|
];
|
|
521
|
+
if (view.terminalUrl) {
|
|
522
|
+
elements.push(markdown(`[Open Terminal](${view.terminalUrl}) · 独立终端,需在本会话由所有者授权`));
|
|
523
|
+
}
|
|
511
524
|
if (view.status === "running" && view.jobId && view.contextKey) {
|
|
512
525
|
elements.push({
|
|
513
526
|
tag: "button",
|
|
@@ -553,6 +566,7 @@ export class CardRenderer {
|
|
|
553
566
|
`**思考强度 / 权限**:${inlineCode(view.currentEffort ?? "自动")} / ${inlineCode(permissionModeLabel(view.currentPermissionMode))}`,
|
|
554
567
|
]
|
|
555
568
|
: []),
|
|
569
|
+
...(view.pendingNotice ? [view.pendingNotice] : []),
|
|
556
570
|
...(view.notice ? [view.notice] : []),
|
|
557
571
|
...(view.warning ? [`**提示**:${escapeCardHtml(view.warning)}`] : []),
|
|
558
572
|
].join("\n")),
|
|
@@ -584,7 +598,8 @@ export class CardRenderer {
|
|
|
584
598
|
inlineCode(provider.id),
|
|
585
599
|
provider.isDefault ? " · 默认" : "",
|
|
586
600
|
].join(""),
|
|
587
|
-
current: provider.id === view.currentProvider,
|
|
601
|
+
current: provider.id === view.currentProvider && !view.pendingFailed,
|
|
602
|
+
currentLabel: view.pendingNotice ? "待生效" : undefined,
|
|
588
603
|
action: {
|
|
589
604
|
text: "Switch",
|
|
590
605
|
value: {
|
|
@@ -599,18 +614,24 @@ export class CardRenderer {
|
|
|
599
614
|
if (view.models.length === 0) {
|
|
600
615
|
elements.push(markdown("当前运行时未返回可用模型。"));
|
|
601
616
|
} else {
|
|
602
|
-
|
|
617
|
+
const page = paginateModels(view.models, view.currentModel, view.modelPage, {
|
|
618
|
+
action: "settings_tab_open", ...baseAction, tab: "model", provider: view.currentProvider ?? "",
|
|
619
|
+
});
|
|
620
|
+
elements.push(...page.models.map((model) => settingsOptionRow({
|
|
603
621
|
label: `${inlineCode(model.id)}${model.isDefault ? " · 默认" : ""}`,
|
|
604
|
-
current: model.id === view.currentModel,
|
|
622
|
+
current: model.id === view.currentModel && !view.pendingFailed,
|
|
623
|
+
currentLabel: view.pendingNotice ? "待生效" : undefined,
|
|
605
624
|
action: {
|
|
606
625
|
text: "Switch",
|
|
607
626
|
value: {
|
|
608
627
|
action: "settings_model_select",
|
|
609
628
|
...baseAction,
|
|
629
|
+
provider: view.currentProvider ?? "",
|
|
610
630
|
model: model.id,
|
|
611
631
|
},
|
|
612
632
|
},
|
|
613
633
|
})));
|
|
634
|
+
elements.push(...page.navigation);
|
|
614
635
|
}
|
|
615
636
|
} else if (view.activeTab === "thinking") {
|
|
616
637
|
if (!view.currentModel) {
|
|
@@ -622,11 +643,13 @@ export class CardRenderer {
|
|
|
622
643
|
elements.push(...view.reasoningOptions.map((option) => settingsOptionRow({
|
|
623
644
|
label: inlineCode(option.value),
|
|
624
645
|
current: option.value === view.currentEffort,
|
|
646
|
+
currentLabel: view.pendingNotice ? "待生效" : undefined,
|
|
625
647
|
action: {
|
|
626
648
|
text: "Switch",
|
|
627
649
|
value: {
|
|
628
650
|
action: "settings_thinking_select",
|
|
629
651
|
...baseAction,
|
|
652
|
+
provider: view.currentProvider ?? "",
|
|
630
653
|
model: currentModel,
|
|
631
654
|
effort: option.value,
|
|
632
655
|
},
|
|
@@ -637,6 +660,7 @@ export class CardRenderer {
|
|
|
637
660
|
elements.push(...(["auto", "confirm"] as PermissionMode[]).map((mode) => settingsOptionRow({
|
|
638
661
|
label: `${inlineCode(mode)} · ${permissionModeLabel(mode)}`,
|
|
639
662
|
current: mode === view.currentPermissionMode,
|
|
663
|
+
currentLabel: view.pendingNotice ? "待生效" : undefined,
|
|
640
664
|
action: {
|
|
641
665
|
text: "Switch",
|
|
642
666
|
value: {
|
|
@@ -649,7 +673,7 @@ export class CardRenderer {
|
|
|
649
673
|
}
|
|
650
674
|
|
|
651
675
|
elements.push(markdown(`> ${EXECUTION_SETTING_EFFECTS[view.activeTab]}`));
|
|
652
|
-
return sectionCard("运行设置", elements, view.warning ? "orange" : view.notice ? "green" : "blue");
|
|
676
|
+
return sectionCard("运行设置", elements, view.warning || view.pendingFailed ? "orange" : view.notice ? "green" : "blue");
|
|
653
677
|
}
|
|
654
678
|
|
|
655
679
|
renderProviderSelector(view: ProviderSelectorCardView): Record<string, unknown> {
|
|
@@ -785,7 +809,14 @@ export class CardRenderer {
|
|
|
785
809
|
if (view.models.length === 0) {
|
|
786
810
|
elements.push(markdown("当前运行时未返回可用模型。"));
|
|
787
811
|
} else {
|
|
788
|
-
|
|
812
|
+
const page = paginateModels(view.models, view.currentModel, view.modelPage, {
|
|
813
|
+
action: view.unifiedSettings ? "provider_model_open" : "model_open",
|
|
814
|
+
sessionId: view.sessionId,
|
|
815
|
+
contextKey: view.contextKey,
|
|
816
|
+
...(view.modelProvider ? { provider: view.modelProvider } : {}),
|
|
817
|
+
...(view.permissionMode ? { permissionMode: view.permissionMode } : {}),
|
|
818
|
+
});
|
|
819
|
+
elements.push(...page.models.map((model) => {
|
|
789
820
|
const isCurrent = model.id === view.currentModel;
|
|
790
821
|
return {
|
|
791
822
|
tag: "column_set",
|
|
@@ -823,6 +854,7 @@ export class CardRenderer {
|
|
|
823
854
|
],
|
|
824
855
|
};
|
|
825
856
|
}));
|
|
857
|
+
elements.push(...page.navigation);
|
|
826
858
|
}
|
|
827
859
|
if (view.unifiedSettings) {
|
|
828
860
|
elements.push(
|
|
@@ -994,6 +1026,24 @@ export class CardRenderer {
|
|
|
994
1026
|
view.status === "completed" ? "green" : view.status === "cancelled" ? "grey" : "blue");
|
|
995
1027
|
}
|
|
996
1028
|
|
|
1029
|
+
renderTerminalAuthorization(view: TerminalCardView): Record<string, unknown> {
|
|
1030
|
+
const action = (decision: string) => ({ action: "terminal_authorize", contextKey: view.contextKey, requestId: view.requestId, token: view.approvalToken, decision });
|
|
1031
|
+
const elements: Record<string, unknown>[] = [markdown(`**目录**:${inlineCode(view.cwd)}\n**终端**:${inlineCode(view.reopenToken)}`)];
|
|
1032
|
+
if (view.state === "pending") {
|
|
1033
|
+
elements.push(markdown(`**验证码**:${inlineCode(view.code)}\n仅 AgentBot 所有者可授权。授权后,该浏览器可在本机执行任意命令,拥有 AgentBot 的系统用户权限。请核对网页验证码,仅批准本人发起的请求。5 分钟内有效。${view.recovery ? (view.shellStarted ? "恢复原 Shell,不创建新终端;授权后旧网页失去访问权限。拒绝不影响原终端。" : "继续打开同一个终端;授权后旧网页失去访问权限,Shell 在连接成功后启动。拒绝不影响原授权。") : "不执行原卡片命令。"}`),
|
|
1034
|
+
taskActionRow([{ text: "Allow", value: action("allow") }, { text: "Deny", type: "danger", value: action("deny") }, { text: "刷新", value: action("refresh") }]));
|
|
1035
|
+
} else if (view.state === "closed") {
|
|
1036
|
+
elements.push(markdown(`**状态**:${view.message ? escapeCardHtml(view.message) : "已关闭"}\n原终端已结束时,请从命令卡片创建新终端。`));
|
|
1037
|
+
} else {
|
|
1038
|
+
const waiting = view.state === "approved";
|
|
1039
|
+
const label = waiting ? "打开终端" : "重新打开终端";
|
|
1040
|
+
elements.push(markdown(`**状态**:${waiting ? "已授权,等待网页连接" : view.connected ? "已连接" : "网页已断开,Shell 仍在运行"}\n${waiting ? "请返回原网页连接;原网页已关闭时,点击下方“打开终端”。Shell 尚未启动。" : "点击下方“重新打开终端”恢复原 Shell。前台连接时不因空闲关闭;后台空闲 30 分钟或网页断开 3 分钟后会自动关闭 Shell,成功重连取消断开计时并重置空闲计时;“关闭终端”会中断其中的命令。"}`));
|
|
1041
|
+
elements.push(markdown(`[${label}](${view.reopenUrl}) · 新网页需核对验证码重新授权${waiting ? ",连接成功后启动 Shell" : ",恢复原 Shell"}`));
|
|
1042
|
+
elements.push(taskActionRow([{ text: "关闭终端", type: "danger", value: action("close") }, { text: "刷新", value: action("refresh") }]));
|
|
1043
|
+
}
|
|
1044
|
+
return sectionCard(view.state === "pending" ? "网页终端授权" : "网页终端管理", elements, view.state === "closed" ? "grey" : view.state === "pending" ? "orange" : "blue");
|
|
1045
|
+
}
|
|
1046
|
+
|
|
997
1047
|
renderSafeRestartStatus(view: SafeRestartStatusView): Record<string, unknown> {
|
|
998
1048
|
const actionable = view.phase !== "restarting" && view.phase !== "cancelled" && view.phase !== "superseded";
|
|
999
1049
|
const countdown = view.phase === "countdown"
|
|
@@ -1021,8 +1071,35 @@ export class CardRenderer {
|
|
|
1021
1071
|
`**重启原因**:${inlineCode(view.reason)}`,
|
|
1022
1072
|
`**重启倒计时**:${countdown}`,
|
|
1023
1073
|
`**待投递结果**:${view.pendingFinalDeliveries} 条`,
|
|
1074
|
+
...(view.activeTerminals ? [`**活动终端**:${view.activeTerminals} 个。结束终端会中断其中的命令;ForceRestart 会中断所有终端。`] : []),
|
|
1024
1075
|
];
|
|
1025
1076
|
const elements: Record<string, unknown>[] = [markdown(lines.join("\n"))];
|
|
1077
|
+
if (view.terminalBlockers?.length) {
|
|
1078
|
+
elements.push({ tag: "hr" }, markdown(`**阻止重启的终端(${view.terminalBlockers.length})**`));
|
|
1079
|
+
for (const terminal of view.terminalBlockers) {
|
|
1080
|
+
const command = terminal.state === "closing" ? "正在结束,等待 Shell 退出"
|
|
1081
|
+
: terminal.state === "starting" ? "Shell 启动中"
|
|
1082
|
+
: terminal.command.status === "idle" ? "无(Shell 空闲)"
|
|
1083
|
+
: terminal.command.command ? inlineCode(escapeCardHtml(truncateText(terminal.command.command.replace(/\s+/gu, " "), 240)))
|
|
1084
|
+
: terminal.command.status === "running" ? "命令执行中(命令内容不可用)"
|
|
1085
|
+
: "未知(Shell 未提供命令信息)";
|
|
1086
|
+
const connection = terminal.connected ? terminal.foreground ? "网页前台" : "网页已连接" : "网页已断开";
|
|
1087
|
+
const label = `**终端**:${inlineCode(terminal.token)} · ${terminal.active ? "活跃" : "不活跃"} · ${connection}\n**目录**:${inlineCode(escapeCardHtml(truncateText(terminal.cwd, 240)))}\n**${terminal.command.source === "process" ? "前台进程" : "正在执行"}**:${command}`;
|
|
1088
|
+
elements.push({
|
|
1089
|
+
tag: "column_set", flex_mode: "none", horizontal_spacing: "8px",
|
|
1090
|
+
columns: [
|
|
1091
|
+
{ tag: "column", width: "weighted", weight: 1, elements: [markdown(label)] },
|
|
1092
|
+
...(actionable && terminal.state !== "closing" ? [{
|
|
1093
|
+
tag: "column", width: "auto", elements: [taskActionElement({
|
|
1094
|
+
text: "结束终端", type: "danger",
|
|
1095
|
+
value: { action: "safe_restart_terminal_stop", scheduleId: String(view.scheduleId), terminalId: terminal.terminalId, ...(view.contextKey ? { contextKey: view.contextKey } : {}) },
|
|
1096
|
+
})],
|
|
1097
|
+
}] : []),
|
|
1098
|
+
],
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
elements.push(markdown("活跃:前台连接、正在执行命令或最近 30 秒有输入输出。不活跃的 Shell 仍会阻止安全重启。"));
|
|
1102
|
+
}
|
|
1026
1103
|
if (view.waitingTasks.length > 0) {
|
|
1027
1104
|
const visible = view.waitingTasks.slice(0, 10);
|
|
1028
1105
|
elements.push({ tag: "hr" }, markdown(`**当前等待的任务(${view.waitingTasks.length})**`));
|
|
@@ -1782,6 +1859,10 @@ function renderApprovalElements(state: TurnViewState, request: ApprovalRequest):
|
|
|
1782
1859
|
request.reason,
|
|
1783
1860
|
].filter(Boolean).join("\n")));
|
|
1784
1861
|
}
|
|
1862
|
+
if (request.automatic) {
|
|
1863
|
+
elements.push(markdown("自动执行模式:正在自动同意。"));
|
|
1864
|
+
return elements;
|
|
1865
|
+
}
|
|
1785
1866
|
elements.push({
|
|
1786
1867
|
tag: "column_set",
|
|
1787
1868
|
flex_mode: "flow",
|
|
@@ -2246,7 +2327,8 @@ function removeMarkdownBold(value: string): string {
|
|
|
2246
2327
|
}
|
|
2247
2328
|
|
|
2248
2329
|
function turnActivities(state: TurnViewState): TurnActivity[] {
|
|
2249
|
-
if (state.activities?.length) return state.
|
|
2330
|
+
if (state.activities?.length) return state.approval
|
|
2331
|
+
? state.activities.filter((a) => a.id !== approvalActivityId(state.turnId, state.approval!.id)) : state.activities;
|
|
2250
2332
|
|
|
2251
2333
|
const activities: TurnActivity[] = [];
|
|
2252
2334
|
if (state.progressText) {
|
|
@@ -2845,6 +2927,24 @@ function acpPermissionOptionLabel(option: { name: string; kind: string }): strin
|
|
|
2845
2927
|
return labels[option.kind] ?? (/^[\x20-\x7E]+$/.test(option.name) ? option.name : "Select");
|
|
2846
2928
|
}
|
|
2847
2929
|
|
|
2930
|
+
const MODEL_PAGE_SIZE = 20;
|
|
2931
|
+
|
|
2932
|
+
function paginateModels(
|
|
2933
|
+
models: ModelOption[],
|
|
2934
|
+
currentModel: string | undefined,
|
|
2935
|
+
requestedPage: number | undefined,
|
|
2936
|
+
action: Record<string, string>,
|
|
2937
|
+
): { models: ModelOption[]; navigation: Record<string, unknown>[] } {
|
|
2938
|
+
const totalPages = Math.max(1, Math.ceil(models.length / MODEL_PAGE_SIZE));
|
|
2939
|
+
const currentPage = Math.floor(models.findIndex((model) => model.id === currentModel) / MODEL_PAGE_SIZE);
|
|
2940
|
+
const page = Math.max(0, Math.min(requestedPage ?? currentPage, totalPages - 1));
|
|
2941
|
+
const navigation = totalPages > 1 ? [taskActionRow([
|
|
2942
|
+
...(page > 0 ? [{ text: "上一页", value: { ...action, page: String(page - 1) } }] : []),
|
|
2943
|
+
...(page < totalPages - 1 ? [{ text: "下一页", value: { ...action, page: String(page + 1) } }] : []),
|
|
2944
|
+
], `第 ${page + 1}/${totalPages} 页 · 共 ${models.length} 个模型`)] : [];
|
|
2945
|
+
return { models: models.slice(page * MODEL_PAGE_SIZE, (page + 1) * MODEL_PAGE_SIZE), navigation };
|
|
2946
|
+
}
|
|
2947
|
+
|
|
2848
2948
|
function settingsTabRow(
|
|
2849
2949
|
activeTab: ExecutionSettingsTab,
|
|
2850
2950
|
baseAction: Record<string, string>,
|
|
@@ -2918,6 +3018,7 @@ function settingsTabElement(
|
|
|
2918
3018
|
function settingsOptionRow(input: {
|
|
2919
3019
|
label: string;
|
|
2920
3020
|
current: boolean;
|
|
3021
|
+
currentLabel?: string;
|
|
2921
3022
|
action: TaskListCardAction;
|
|
2922
3023
|
}): Record<string, unknown> {
|
|
2923
3024
|
return {
|
|
@@ -2937,7 +3038,7 @@ function settingsOptionRow(input: {
|
|
|
2937
3038
|
tag: "column",
|
|
2938
3039
|
width: "auto",
|
|
2939
3040
|
vertical_align: "center",
|
|
2940
|
-
elements: input.current ? [markdown("
|
|
3041
|
+
elements: input.current ? [markdown(`✅ ${input.currentLabel ?? "当前"}`)] : [taskActionElement(input.action)],
|
|
2941
3042
|
},
|
|
2942
3043
|
],
|
|
2943
3044
|
};
|
|
@@ -262,6 +262,7 @@ function toIncomingMessage(
|
|
|
262
262
|
const messageId = message.message_id ?? `${Date.now()}`;
|
|
263
263
|
return {
|
|
264
264
|
messageId,
|
|
265
|
+
...(typeof messageType === "string" ? { messageType } : {}),
|
|
265
266
|
...(Number.isFinite(Number(message.create_time)) && Number(message.create_time) > 0
|
|
266
267
|
? { createdAt: Number(message.create_time) } : {}),
|
|
267
268
|
contextKey: chatId
|