@autopilot-agent/cli 0.0.0-stage → 0.2.0
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/README.md +52 -2
- package/dist/activity-animation.d.ts +59 -0
- package/dist/activity-animation.js +148 -0
- package/dist/animation-clock.d.ts +22 -0
- package/dist/animation-clock.js +76 -0
- package/dist/composition-root.d.ts +105 -0
- package/dist/composition-root.js +600 -0
- package/dist/context-report.d.ts +20 -0
- package/dist/context-report.js +173 -0
- package/dist/controller.d.ts +79 -0
- package/dist/controller.js +741 -0
- package/dist/design-tokens.d.ts +38 -0
- package/dist/design-tokens.js +38 -0
- package/dist/error-diagnostics.d.ts +8 -0
- package/dist/error-diagnostics.js +62 -0
- package/dist/event-projector.d.ts +26 -0
- package/dist/event-projector.js +335 -0
- package/dist/file-mention-context.d.ts +34 -0
- package/dist/file-mention-context.js +173 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +14 -0
- package/dist/main.d.ts +40 -0
- package/dist/main.js +543 -0
- package/dist/parser.d.ts +17 -0
- package/dist/parser.js +400 -0
- package/dist/renderer.d.ts +9 -0
- package/dist/renderer.js +62 -0
- package/dist/scroll-viewport.d.ts +22 -0
- package/dist/scroll-viewport.js +62 -0
- package/dist/termination-format.d.ts +4 -0
- package/dist/termination-format.js +18 -0
- package/dist/timeline-lines.d.ts +38 -0
- package/dist/timeline-lines.js +104 -0
- package/dist/tui/app.d.ts +30 -0
- package/dist/tui/app.js +413 -0
- package/dist/tui/approval.d.ts +19 -0
- package/dist/tui/approval.js +41 -0
- package/dist/tui/brand/brand-header.d.ts +20 -0
- package/dist/tui/brand/brand-header.js +22 -0
- package/dist/tui/brand/intro.d.ts +12 -0
- package/dist/tui/brand/intro.js +88 -0
- package/dist/tui/brand/pixel.d.ts +8 -0
- package/dist/tui/brand/pixel.js +50 -0
- package/dist/tui/constants.d.ts +56 -0
- package/dist/tui/constants.js +57 -0
- package/dist/tui/context.d.ts +11 -0
- package/dist/tui/context.js +12 -0
- package/dist/tui/diff-stats.d.ts +8 -0
- package/dist/tui/diff-stats.js +7 -0
- package/dist/tui/format.d.ts +75 -0
- package/dist/tui/format.js +178 -0
- package/dist/tui/hooks/use-file-mention-index.d.ts +15 -0
- package/dist/tui/hooks/use-file-mention-index.js +59 -0
- package/dist/tui/hooks/use-pickers.d.ts +47 -0
- package/dist/tui/hooks/use-pickers.js +288 -0
- package/dist/tui/hooks/use-prompt-input.d.ts +54 -0
- package/dist/tui/hooks/use-prompt-input.js +258 -0
- package/dist/tui/hooks/use-status-view.d.ts +8 -0
- package/dist/tui/hooks/use-status-view.js +13 -0
- package/dist/tui/hooks/use-viewport.d.ts +22 -0
- package/dist/tui/hooks/use-viewport.js +117 -0
- package/dist/tui/input/file-mention.d.ts +33 -0
- package/dist/tui/input/file-mention.js +112 -0
- package/dist/tui/input/graphemes.d.ts +5 -0
- package/dist/tui/input/graphemes.js +10 -0
- package/dist/tui/input/keys.d.ts +16 -0
- package/dist/tui/input/keys.js +58 -0
- package/dist/tui/input/prompt-layout.d.ts +27 -0
- package/dist/tui/input/prompt-layout.js +84 -0
- package/dist/tui/input-area.d.ts +53 -0
- package/dist/tui/input-area.js +89 -0
- package/dist/tui/jobs.d.ts +10 -0
- package/dist/tui/jobs.js +24 -0
- package/dist/tui/lifecycle.d.ts +23 -0
- package/dist/tui/lifecycle.js +114 -0
- package/dist/tui/markdown/diff.d.ts +8 -0
- package/dist/tui/markdown/diff.js +19 -0
- package/dist/tui/markdown/inline.d.ts +4 -0
- package/dist/tui/markdown/inline.js +32 -0
- package/dist/tui/markdown/markdown.d.ts +17 -0
- package/dist/tui/markdown/markdown.js +117 -0
- package/dist/tui/markdown/table.d.ts +19 -0
- package/dist/tui/markdown/table.js +74 -0
- package/dist/tui/performance-diagnostics.d.ts +7 -0
- package/dist/tui/performance-diagnostics.js +57 -0
- package/dist/tui/pickers/effort.d.ts +10 -0
- package/dist/tui/pickers/effort.js +12 -0
- package/dist/tui/pickers/file-mention.d.ts +11 -0
- package/dist/tui/pickers/file-mention.js +22 -0
- package/dist/tui/pickers/model.d.ts +10 -0
- package/dist/tui/pickers/model.js +12 -0
- package/dist/tui/pickers/permission.d.ts +10 -0
- package/dist/tui/pickers/permission.js +13 -0
- package/dist/tui/pickers/skill.d.ts +13 -0
- package/dist/tui/pickers/skill.js +25 -0
- package/dist/tui/pickers/slash.d.ts +10 -0
- package/dist/tui/pickers/slash.js +11 -0
- package/dist/tui/prompt-input.d.ts +11 -0
- package/dist/tui/prompt-input.js +73 -0
- package/dist/tui/render-mode.d.ts +20 -0
- package/dist/tui/render-mode.js +31 -0
- package/dist/tui/run.d.ts +6 -0
- package/dist/tui/run.js +237 -0
- package/dist/tui/status-line.d.ts +16 -0
- package/dist/tui/status-line.js +37 -0
- package/dist/tui/store.d.ts +4 -0
- package/dist/tui/store.js +380 -0
- package/dist/tui/timeline/live.d.ts +16 -0
- package/dist/tui/timeline/live.js +153 -0
- package/dist/tui/timeline/presentation-timeline.d.ts +15 -0
- package/dist/tui/timeline/presentation-timeline.js +128 -0
- package/dist/tui/timeline/progress.d.ts +12 -0
- package/dist/tui/timeline/progress.js +43 -0
- package/dist/tui/timeline/static-timeline.d.ts +13 -0
- package/dist/tui/timeline/static-timeline.js +131 -0
- package/dist/tui/timeline/tool-tree.d.ts +7 -0
- package/dist/tui/timeline/tool-tree.js +25 -0
- package/dist/tui/timeline/turn-items.d.ts +12 -0
- package/dist/tui/timeline/turn-items.js +28 -0
- package/dist/tui/timeline/turn.d.ts +19 -0
- package/dist/tui/timeline/turn.js +21 -0
- package/dist/tui/todo-list.d.ts +50 -0
- package/dist/tui/todo-list.js +120 -0
- package/dist/tui/transcript.d.ts +24 -0
- package/dist/tui/transcript.js +34 -0
- package/dist/tui/trust-dialog.d.ts +51 -0
- package/dist/tui/trust-dialog.js +127 -0
- package/dist/tui/types.d.ts +189 -0
- package/dist/tui/types.js +5 -0
- package/dist/tui/viewport/selection.d.ts +27 -0
- package/dist/tui/viewport/selection.js +79 -0
- package/dist/tui/viewport/sources.d.ts +29 -0
- package/dist/tui/viewport/sources.js +592 -0
- package/dist/tui/viewport/viewport.d.ts +12 -0
- package/dist/tui/viewport/viewport.js +91 -0
- package/dist/tui-input-history.d.ts +23 -0
- package/dist/tui-input-history.js +42 -0
- package/dist/tui-input-router.d.ts +24 -0
- package/dist/tui-input-router.js +115 -0
- package/dist/tui.d.ts +15 -0
- package/dist/tui.js +13 -0
- package/dist/types.d.ts +467 -0
- package/dist/types.js +7 -0
- package/package.json +58 -4
package/dist/parser.js
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI argv 与 slash command 的最小确定性 parser。
|
|
3
|
+
*
|
|
4
|
+
* parser 只做语法、选项归属和局部值校验,不创建 Runtime、不读取工作区,
|
|
5
|
+
* 也不会把未知命令静默转换为普通模型 prompt。
|
|
6
|
+
*/
|
|
7
|
+
/** 交互 TTY 可用的 slash 命令目录;只列出 parser 已真正支持的命令。 */
|
|
8
|
+
export const CLI_SLASH_COMMANDS = [
|
|
9
|
+
{ name: "/exit", usage: "/exit", description: "退出当前 CLI 会话" },
|
|
10
|
+
{ name: "/cancel", usage: "/cancel", description: "取消正在运行的 Turn" },
|
|
11
|
+
{ name: "/queue", usage: "/queue <prompt>", description: "把下一条 prompt 排队" },
|
|
12
|
+
{ name: "/compact", usage: "/compact", description: "压缩当前 Turn 上下文" },
|
|
13
|
+
{ name: "/context", usage: "/context [all]", description: "查看当前上下文用量(all 展开完整细节)" },
|
|
14
|
+
{ name: "/jobs", usage: "/jobs [show|output|stop <id>]", description: "管理当前 Session 的后台任务" },
|
|
15
|
+
{ name: "/model", usage: "/model", description: "查看或设置下一个 Turn 的模型" },
|
|
16
|
+
{ name: "/effort", usage: "/effort", description: "查看或设置下一个 Turn 的推理强度" },
|
|
17
|
+
{ name: "/skill", usage: "/skill [name]", description: "打开 Skill picker 或选择 Skill" },
|
|
18
|
+
{ name: "/permissions", usage: "/permissions [mode | grants | revoke <constraint>]", description: "查看/设置权限模式,或查看与撤销持久授权" },
|
|
19
|
+
{ name: "/approve", usage: "/approve <decision>", description: "提交当前工具审批决定" },
|
|
20
|
+
{ name: "/undo", usage: "/undo [confirm]", description: "预览并确认撤销当前会话最近一轮文件修改" },
|
|
21
|
+
];
|
|
22
|
+
/** 返回当前 prompt 可用的 slash 命令建议;只有输入 `/` 或命令前缀时打开。 */
|
|
23
|
+
export function getSlashCommandSuggestions(input) {
|
|
24
|
+
const trimmed = input.trimStart();
|
|
25
|
+
if (!trimmed.startsWith("/") || /\s/u.test(trimmed))
|
|
26
|
+
return [];
|
|
27
|
+
const query = trimmed.toLocaleLowerCase();
|
|
28
|
+
return CLI_SLASH_COMMANDS.filter(function matchSlashCommand(command) {
|
|
29
|
+
return command.name.startsWith(query);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** 返回稳定的 CLI 帮助文本。 */
|
|
33
|
+
export function renderHelp() {
|
|
34
|
+
return [
|
|
35
|
+
"Usage:",
|
|
36
|
+
" autopilot enter interactive chat (bare command, current directory)",
|
|
37
|
+
" autopilot project open [path] [--workspace-root <path>] [--state-root <path>]",
|
|
38
|
+
" autopilot project list | current | remove <workspaceId>",
|
|
39
|
+
" autopilot session list | rename <id> <title> | delete <id> --yes",
|
|
40
|
+
" autopilot trust status | revoke",
|
|
41
|
+
" autopilot config show|get|set ... --scope user|project|local",
|
|
42
|
+
" autopilot mcp list | approve <server> [--yes] | revoke <server>",
|
|
43
|
+
" autopilot job list | show <id> | output <id> | kill <id>",
|
|
44
|
+
" autopilot undo --session <id> --yes",
|
|
45
|
+
" autopilot doctor [--connectivity]",
|
|
46
|
+
" autopilot sandbox status | setup --yes",
|
|
47
|
+
" autopilot chat [prompt] [--session <id>] [--output interactive|text|jsonl]",
|
|
48
|
+
" autopilot resume [--session <id>] [--output interactive|text|jsonl]",
|
|
49
|
+
"",
|
|
50
|
+
"Options:",
|
|
51
|
+
" --workspace-root <path> canonical Agent workspace root",
|
|
52
|
+
" --state-root <path> explicit durable Runtime state root (default: stable workspace identity)",
|
|
53
|
+
" --trust-root <path> explicit trust record root",
|
|
54
|
+
" -h, --help show this help",
|
|
55
|
+
"",
|
|
56
|
+
"Permissions: ask requires confirmation; auto_approve only auto-runs safe writes and workspace commands after Trust; full_access still enforces path, schema, and destructive-command guards.",
|
|
57
|
+
"Limits: settings.json maxTurns, AGENT_MAX_STEPS and AGENT_MAX_DURATION_MS are optional (unset means no step/time cap); maxTurns maps to per-run steps without a fixed platform cap; AGENT_MAX_TOOL_RETRIES defaults to 5 and is capped at 10.",
|
|
58
|
+
"Exit codes: 0 success/normal exit, 1 runtime error, 2 usage error, 130 SIGINT, 143 SIGTERM.",
|
|
59
|
+
"Interactive uses Ink on a TTY and Plain fallback otherwise; --output jsonl never emits ANSI or prompts.",
|
|
60
|
+
].join("\n");
|
|
61
|
+
}
|
|
62
|
+
/** 读取需要值的 argv 选项;缺失值统一返回 usage error。 */
|
|
63
|
+
function readOptionValue(argv, index, option) {
|
|
64
|
+
const value = argv[index + 1];
|
|
65
|
+
if (!value || value.startsWith("-")) {
|
|
66
|
+
return { nextIndex: index, error: `选项 ${option} 需要一个值` };
|
|
67
|
+
}
|
|
68
|
+
return { value, nextIndex: index + 1 };
|
|
69
|
+
}
|
|
70
|
+
/** 将输出模式解析为固定联合类型。 */
|
|
71
|
+
function parseOutputFormat(value) {
|
|
72
|
+
if (value === "interactive" || value === "text" || value === "jsonl")
|
|
73
|
+
return value;
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
/** 解析 argv;不触碰 Runtime 或文件系统。 */
|
|
77
|
+
export function parseArgv(argv) {
|
|
78
|
+
if (argv.length === 0) {
|
|
79
|
+
const command = { type: "chat", output: "interactive" };
|
|
80
|
+
return { kind: "command", invocation: createInvocation(command, {}) };
|
|
81
|
+
}
|
|
82
|
+
let commandName;
|
|
83
|
+
let subcommand;
|
|
84
|
+
let output = "interactive";
|
|
85
|
+
let sessionId;
|
|
86
|
+
let scope;
|
|
87
|
+
let yes = false;
|
|
88
|
+
let connectivity = false;
|
|
89
|
+
const positional = [];
|
|
90
|
+
const host = {};
|
|
91
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
92
|
+
const token = argv[index];
|
|
93
|
+
if (token === "-h" || token === "--help")
|
|
94
|
+
return { kind: "help", text: renderHelp() };
|
|
95
|
+
if (token === "--jsonl") {
|
|
96
|
+
output = "jsonl";
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (token === "--yes") {
|
|
100
|
+
yes = true;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (token === "--connectivity") {
|
|
104
|
+
connectivity = true;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (token === "--state-root" || token === "--trust-root" || token === "--workspace-root" || token === "--session" || token === "--output" || token === "--scope") {
|
|
108
|
+
const option = readOptionValue(argv, index, token);
|
|
109
|
+
if (option.error || !option.value)
|
|
110
|
+
return { kind: "error", message: option.error ?? `选项 ${token} 需要一个值` };
|
|
111
|
+
index = option.nextIndex;
|
|
112
|
+
if (token === "--state-root")
|
|
113
|
+
host.stateRoot = option.value;
|
|
114
|
+
else if (token === "--trust-root")
|
|
115
|
+
host.trustRoot = option.value;
|
|
116
|
+
else if (token === "--workspace-root")
|
|
117
|
+
host.workspaceRoot = option.value;
|
|
118
|
+
else if (token === "--session")
|
|
119
|
+
sessionId = option.value;
|
|
120
|
+
else if (token === "--scope") {
|
|
121
|
+
if (option.value !== "user" && option.value !== "project" && option.value !== "local")
|
|
122
|
+
return { kind: "error", message: `--scope 不支持值 ${option.value}` };
|
|
123
|
+
scope = option.value;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
const parsedOutput = parseOutputFormat(option.value);
|
|
127
|
+
if (!parsedOutput)
|
|
128
|
+
return { kind: "error", message: `--output 不支持值 ${option.value}` };
|
|
129
|
+
output = parsedOutput;
|
|
130
|
+
}
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
if (token === "-s") {
|
|
134
|
+
const option = readOptionValue(argv, index, "-s");
|
|
135
|
+
if (option.error || !option.value)
|
|
136
|
+
return { kind: "error", message: option.error ?? "选项 -s 需要一个值" };
|
|
137
|
+
index = option.nextIndex;
|
|
138
|
+
sessionId = option.value;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
if (token.startsWith("-"))
|
|
142
|
+
return { kind: "error", message: `未知选项 ${token}` };
|
|
143
|
+
if (!commandName) {
|
|
144
|
+
commandName = token;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if ((commandName === "project" || commandName === "session" || commandName === "trust" || commandName === "config" || commandName === "mcp" || commandName === "job" || commandName === "sandbox") && !subcommand) {
|
|
148
|
+
subcommand = token;
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
positional.push(token);
|
|
152
|
+
}
|
|
153
|
+
if (commandName === "project") {
|
|
154
|
+
if (subcommand === "open") {
|
|
155
|
+
if (sessionId || output !== "interactive" || scope || yes || connectivity)
|
|
156
|
+
return { kind: "error", message: "project open 不接受当前选项" };
|
|
157
|
+
if (positional.length > 1)
|
|
158
|
+
return { kind: "error", message: "project open 最多接受一个 path" };
|
|
159
|
+
const command = { type: "project_open", path: positional[0] };
|
|
160
|
+
return { kind: "command", invocation: createInvocation(command, host) };
|
|
161
|
+
}
|
|
162
|
+
if (subcommand === "list" && positional.length === 0 && !sessionId && output === "interactive" && !scope && !yes && !connectivity)
|
|
163
|
+
return { kind: "command", invocation: createInvocation({ type: "project_list" }, host) };
|
|
164
|
+
if (subcommand === "current" && positional.length === 0 && !sessionId && output === "interactive" && !scope && !yes && !connectivity)
|
|
165
|
+
return { kind: "command", invocation: createInvocation({ type: "project_current" }, host) };
|
|
166
|
+
if (subcommand === "remove" && positional.length === 1 && !sessionId && output === "interactive" && !scope && !yes && !connectivity)
|
|
167
|
+
return { kind: "command", invocation: createInvocation({ type: "project_remove", workspaceId: positional[0] }, host) };
|
|
168
|
+
return { kind: "error", message: "project 支持 open|list|current|remove,且参数或选项不合法" };
|
|
169
|
+
}
|
|
170
|
+
if (commandName === "session") {
|
|
171
|
+
if (sessionId || output !== "interactive" || scope || connectivity)
|
|
172
|
+
return { kind: "error", message: "session 不接受聊天、scope 或 connectivity 选项" };
|
|
173
|
+
if (subcommand === "list" && positional.length === 0 && !yes)
|
|
174
|
+
return { kind: "command", invocation: createInvocation({ type: "session_list" }, host) };
|
|
175
|
+
if (subcommand === "rename" && positional.length >= 2 && !yes)
|
|
176
|
+
return { kind: "command", invocation: createInvocation({ type: "session_rename", sessionId: positional[0], title: positional.slice(1).join(" ") }, host) };
|
|
177
|
+
if (subcommand === "delete" && positional.length === 1)
|
|
178
|
+
return { kind: "command", invocation: createInvocation({ type: "session_delete", sessionId: positional[0], yes }, host) };
|
|
179
|
+
return { kind: "error", message: "session 支持 list|rename <id> <title>|delete <id> --yes" };
|
|
180
|
+
}
|
|
181
|
+
if (commandName === "trust") {
|
|
182
|
+
if (positional.length !== 0 || sessionId || output !== "interactive" || scope || yes || connectivity)
|
|
183
|
+
return { kind: "error", message: "trust 参数或选项不合法" };
|
|
184
|
+
if (subcommand === "status")
|
|
185
|
+
return { kind: "command", invocation: createInvocation({ type: "trust_status" }, host) };
|
|
186
|
+
if (subcommand === "revoke")
|
|
187
|
+
return { kind: "command", invocation: createInvocation({ type: "trust_revoke" }, host) };
|
|
188
|
+
return { kind: "error", message: "trust 支持 status|revoke" };
|
|
189
|
+
}
|
|
190
|
+
if (commandName === "config") {
|
|
191
|
+
if (!scope)
|
|
192
|
+
return { kind: "error", message: "config 必须显式提供 --scope user|project|local" };
|
|
193
|
+
if (sessionId || output !== "interactive" || yes || connectivity)
|
|
194
|
+
return { kind: "error", message: "config 不接受聊天或确认选项" };
|
|
195
|
+
if (subcommand === "show" && positional.length === 0)
|
|
196
|
+
return { kind: "command", invocation: createInvocation({ type: "config_show", scope }, host) };
|
|
197
|
+
if (subcommand === "get" && positional.length === 1)
|
|
198
|
+
return { kind: "command", invocation: createInvocation({ type: "config_get", scope, key: positional[0] }, host) };
|
|
199
|
+
if (subcommand === "set" && positional.length === 2)
|
|
200
|
+
return { kind: "command", invocation: createInvocation({ type: "config_set", scope, key: positional[0], value: positional[1] }, host) };
|
|
201
|
+
return { kind: "error", message: "config 支持 show|get <key>|set <key> <value>,且必须显式 --scope" };
|
|
202
|
+
}
|
|
203
|
+
if (commandName === "mcp") {
|
|
204
|
+
if (sessionId || output !== "interactive" || scope || connectivity)
|
|
205
|
+
return { kind: "error", message: "mcp 不接受聊天、scope 或 connectivity 选项" };
|
|
206
|
+
if (subcommand === "list" && positional.length === 0 && !yes)
|
|
207
|
+
return { kind: "command", invocation: createInvocation({ type: "mcp_list" }, host) };
|
|
208
|
+
if (subcommand === "approve" && positional.length === 1)
|
|
209
|
+
return { kind: "command", invocation: createInvocation({ type: "mcp_approve", serverName: positional[0], yes }, host) };
|
|
210
|
+
if (subcommand === "revoke" && positional.length === 1 && !yes)
|
|
211
|
+
return { kind: "command", invocation: createInvocation({ type: "mcp_revoke", serverName: positional[0] }, host) };
|
|
212
|
+
return { kind: "error", message: "mcp 支持 list|approve <server> [--yes]|revoke <server>" };
|
|
213
|
+
}
|
|
214
|
+
if (commandName === "job") {
|
|
215
|
+
if (sessionId || output !== "interactive" || scope || yes || connectivity)
|
|
216
|
+
return { kind: "error", message: "job 不接受聊天、scope 或确认选项" };
|
|
217
|
+
if (subcommand === "list" && positional.length === 0)
|
|
218
|
+
return { kind: "command", invocation: createInvocation({ type: "job_list" }, host) };
|
|
219
|
+
if (subcommand === "show" && positional.length === 1)
|
|
220
|
+
return { kind: "command", invocation: createInvocation({ type: "job_show", jobId: positional[0] }, host) };
|
|
221
|
+
if (subcommand === "output" && positional.length === 1)
|
|
222
|
+
return { kind: "command", invocation: createInvocation({ type: "job_output", jobId: positional[0] }, host) };
|
|
223
|
+
if (subcommand === "kill" && positional.length === 1)
|
|
224
|
+
return { kind: "command", invocation: createInvocation({ type: "job_kill", jobId: positional[0] }, host) };
|
|
225
|
+
return { kind: "error", message: "job 支持 list|show <id>|output <id>|kill <id>" };
|
|
226
|
+
}
|
|
227
|
+
if (commandName === "undo") {
|
|
228
|
+
if (subcommand || positional.length !== 0 || !sessionId || output !== "interactive" || scope || connectivity)
|
|
229
|
+
return { kind: "error", message: "undo 需要 --session <id> --yes" };
|
|
230
|
+
return { kind: "command", invocation: createInvocation({ type: "undo", sessionId, yes }, host) };
|
|
231
|
+
}
|
|
232
|
+
if (commandName === "doctor") {
|
|
233
|
+
if (subcommand || positional.length !== 0 || sessionId || output !== "interactive" || scope || yes)
|
|
234
|
+
return { kind: "error", message: "doctor 只接受可选 --connectivity" };
|
|
235
|
+
return { kind: "command", invocation: createInvocation({ type: "doctor", connectivity }, host) };
|
|
236
|
+
}
|
|
237
|
+
if (commandName === "sandbox") {
|
|
238
|
+
if (sessionId || output !== "interactive" || scope || connectivity)
|
|
239
|
+
return { kind: "error", message: "sandbox 不接受聊天、scope 或 connectivity 选项" };
|
|
240
|
+
if (subcommand === "status" && positional.length === 0 && !yes)
|
|
241
|
+
return { kind: "command", invocation: createInvocation({ type: "sandbox_status" }, host) };
|
|
242
|
+
// Windows provision 属于系统变更(UAC 提权),必须显式 --yes 确认。
|
|
243
|
+
if (subcommand === "setup" && positional.length === 0 && yes)
|
|
244
|
+
return { kind: "command", invocation: createInvocation({ type: "sandbox_setup", yes }, host) };
|
|
245
|
+
return { kind: "error", message: "sandbox 支持 status | setup --yes" };
|
|
246
|
+
}
|
|
247
|
+
if (commandName === "project") {
|
|
248
|
+
const command = { type: "project_open", path: positional[0] };
|
|
249
|
+
return { kind: "command", invocation: createInvocation(command, host) };
|
|
250
|
+
}
|
|
251
|
+
if (commandName === "chat") {
|
|
252
|
+
const prompt = positional.length > 0 ? positional.join(" ") : undefined;
|
|
253
|
+
if (prompt !== undefined && prompt.trim().length === 0) {
|
|
254
|
+
return { kind: "error", message: "chat prompt 不能为空" };
|
|
255
|
+
}
|
|
256
|
+
const command = {
|
|
257
|
+
type: "chat",
|
|
258
|
+
prompt,
|
|
259
|
+
sessionId,
|
|
260
|
+
output,
|
|
261
|
+
};
|
|
262
|
+
return { kind: "command", invocation: createInvocation(command, host) };
|
|
263
|
+
}
|
|
264
|
+
if (commandName === "resume") {
|
|
265
|
+
if (positional.length > 0)
|
|
266
|
+
return { kind: "error", message: "resume 不接受位置参数,请使用 --session" };
|
|
267
|
+
const command = { type: "resume", sessionId, output };
|
|
268
|
+
return { kind: "command", invocation: createInvocation(command, host) };
|
|
269
|
+
}
|
|
270
|
+
return { kind: "error", message: commandName ? `未知命令 ${commandName}` : "缺少命令" };
|
|
271
|
+
}
|
|
272
|
+
/** 为命令附加宿主配置,保持命令 AST 与 Composition Root 参数分离。 */
|
|
273
|
+
function createInvocation(command, host) {
|
|
274
|
+
return { command, host };
|
|
275
|
+
}
|
|
276
|
+
/** 解析单行 slash command;普通文本不在此函数内改写。 */
|
|
277
|
+
export function parseSlashAction(input) {
|
|
278
|
+
const trimmed = input.trim();
|
|
279
|
+
if (!trimmed.startsWith("/")) {
|
|
280
|
+
if (!trimmed)
|
|
281
|
+
return { kind: "error", message: "prompt 不能为空" };
|
|
282
|
+
return { kind: "action", action: { type: "submit_prompt", content: trimmed } };
|
|
283
|
+
}
|
|
284
|
+
const parts = trimmed.slice(1).split(/\s+/u).filter(Boolean);
|
|
285
|
+
const name = parts[0];
|
|
286
|
+
if (!name)
|
|
287
|
+
return { kind: "error", message: "slash command 不能为空" };
|
|
288
|
+
if (name === "exit")
|
|
289
|
+
return parts.length === 1 ? { kind: "action", action: { type: "exit" } } : invalidSlashArguments(name);
|
|
290
|
+
if (name === "cancel")
|
|
291
|
+
return parts.length === 1 ? { kind: "action", action: { type: "cancel_turn" } } : invalidSlashArguments(name);
|
|
292
|
+
if (name === "queue")
|
|
293
|
+
return parseQueuedPrompt(parts);
|
|
294
|
+
if (name === "compact")
|
|
295
|
+
return parts.length === 1 ? { kind: "action", action: { type: "compact" } } : invalidSlashArguments(name);
|
|
296
|
+
if (name === "context") {
|
|
297
|
+
if (parts.length === 1)
|
|
298
|
+
return { kind: "action", action: { type: "show_context", all: false } };
|
|
299
|
+
if (parts.length === 2 && parts[1] === "all")
|
|
300
|
+
return { kind: "action", action: { type: "show_context", all: true } };
|
|
301
|
+
return { kind: "error", message: "/context 只接受可选 all 参数" };
|
|
302
|
+
}
|
|
303
|
+
if (name === "jobs")
|
|
304
|
+
return parseJobsAction(parts);
|
|
305
|
+
if (name === "model")
|
|
306
|
+
return parseRunConfigAction("model", parts);
|
|
307
|
+
if (name === "effort" || name === "reasoning")
|
|
308
|
+
return parseRunConfigAction("effort", parts);
|
|
309
|
+
if (name === "skill") {
|
|
310
|
+
if (parts.length === 1)
|
|
311
|
+
return { kind: "action", action: { type: "list_skills" } };
|
|
312
|
+
if (parts.length !== 2)
|
|
313
|
+
return { kind: "error", message: "/skill 最多接受一个 canonical Skill 名称" };
|
|
314
|
+
return { kind: "action", action: { type: "select_skill", skill: parts[1] } };
|
|
315
|
+
}
|
|
316
|
+
if (name === "permissions")
|
|
317
|
+
return parsePermissionsAction(parts);
|
|
318
|
+
if (name === "approve") {
|
|
319
|
+
if (parts.length !== 2 || !isApprovalDecision(parts[1])) {
|
|
320
|
+
return { kind: "error", message: "/approve 需要 allow_once|allow_session|allow_workspace|deny" };
|
|
321
|
+
}
|
|
322
|
+
return { kind: "action", action: { type: "approve", decision: parts[1] } };
|
|
323
|
+
}
|
|
324
|
+
if (name === "undo") {
|
|
325
|
+
if (parts.length === 1)
|
|
326
|
+
return { kind: "action", action: { type: "undo_preview" } };
|
|
327
|
+
if (parts.length === 2 && parts[1] === "confirm")
|
|
328
|
+
return { kind: "action", action: { type: "undo_confirm" } };
|
|
329
|
+
return { kind: "error", message: "/undo 只接受可选 confirm;请先 /undo 查看预览" };
|
|
330
|
+
}
|
|
331
|
+
return { kind: "error", message: `未知 slash command /${name}` };
|
|
332
|
+
}
|
|
333
|
+
/** 将 /jobs 的列表、详情、日志和停止操作解析为宿主管理 Action。 */
|
|
334
|
+
function parseJobsAction(parts) {
|
|
335
|
+
if (parts.length === 1)
|
|
336
|
+
return { kind: "action", action: { type: "list_jobs" } };
|
|
337
|
+
if (parts.length !== 3)
|
|
338
|
+
return { kind: "error", message: "/jobs 支持 show|output|stop <jobId>" };
|
|
339
|
+
if (parts[1] === "show")
|
|
340
|
+
return { kind: "action", action: { type: "show_job", jobId: parts[2] } };
|
|
341
|
+
if (parts[1] === "output")
|
|
342
|
+
return { kind: "action", action: { type: "read_job_output", jobId: parts[2] } };
|
|
343
|
+
if (parts[1] === "stop")
|
|
344
|
+
return { kind: "action", action: { type: "stop_job", jobId: parts[2] } };
|
|
345
|
+
return { kind: "error", message: "/jobs 支持 show|output|stop <jobId>" };
|
|
346
|
+
}
|
|
347
|
+
/** 将 /queue <prompt> 解析为明确的 durable queue 投递,而不把命令文本交给模型。 */
|
|
348
|
+
function parseQueuedPrompt(parts) {
|
|
349
|
+
const content = parts.slice(1).join(" ").trim();
|
|
350
|
+
if (!content)
|
|
351
|
+
return { kind: "error", message: "/queue 需要一条 prompt" };
|
|
352
|
+
return { kind: "action", action: { type: "submit_prompt", content, delivery: "queue" } };
|
|
353
|
+
}
|
|
354
|
+
/** 检查 slash 审批决定是否属于固定宿主联合类型。 */
|
|
355
|
+
function isApprovalDecision(value) {
|
|
356
|
+
return value === "allow_once" || value === "allow_session" || value === "allow_workspace" || value === "deny";
|
|
357
|
+
}
|
|
358
|
+
/** 返回统一的 slash 参数错误。 */
|
|
359
|
+
function invalidSlashArguments(name) {
|
|
360
|
+
return { kind: "error", message: `/${name} 不接受额外参数` };
|
|
361
|
+
}
|
|
362
|
+
/** 解析权限模式查询、Session 默认权限更新与持久授权查看/撤销(Talk-Plan1013 Stage 4)。 */
|
|
363
|
+
function parsePermissionsAction(parts) {
|
|
364
|
+
if (parts.length === 1)
|
|
365
|
+
return { kind: "action", action: { type: "open_permissions" } };
|
|
366
|
+
if (parts.length === 2 && parts[1] === "grants")
|
|
367
|
+
return { kind: "action", action: { type: "list_permission_grants" } };
|
|
368
|
+
if (parts[1] === "revoke") {
|
|
369
|
+
if (parts.length === 2)
|
|
370
|
+
return { kind: "error", message: "/permissions revoke 需要从 /permissions grants 列表中复制的完整 constraint" };
|
|
371
|
+
// constraint 由 ext-file:/path:/program: 前缀与路径组成,路径可含空格,按原样拼接且不再拆分。
|
|
372
|
+
const constraint = parts.slice(2).join(" ").trim();
|
|
373
|
+
return { kind: "action", action: { type: "revoke_permission_grant", constraint } };
|
|
374
|
+
}
|
|
375
|
+
if (parts.length !== 2 || !isPermissionMode(parts[1])) {
|
|
376
|
+
return { kind: "error", message: "/permissions 需要 ask|auto_approve|full_access 或 grants | revoke <constraint>" };
|
|
377
|
+
}
|
|
378
|
+
return { kind: "action", action: { type: "set_permission_mode", mode: parts[1] } };
|
|
379
|
+
}
|
|
380
|
+
/** 检查 slash 命令是否属于 Core 的三态工具权限模式。 */
|
|
381
|
+
function isPermissionMode(value) {
|
|
382
|
+
return value === "ask" || value === "auto_approve" || value === "full_access";
|
|
383
|
+
}
|
|
384
|
+
/** 将模型/effort 命令解析为 picker 查询或下一 Turn 的 Session 默认值更新。 */
|
|
385
|
+
function parseRunConfigAction(name, parts) {
|
|
386
|
+
if (parts.length > 2)
|
|
387
|
+
return { kind: "error", message: `/${name} 最多接受一个值` };
|
|
388
|
+
if (name === "model") {
|
|
389
|
+
if (!parts[1])
|
|
390
|
+
return { kind: "action", action: { type: "open_model_picker" } };
|
|
391
|
+
const separator = parts[1].indexOf("/");
|
|
392
|
+
if (separator > 0 && separator < parts[1].length - 1) {
|
|
393
|
+
return { kind: "action", action: { type: "set_model", provider: parts[1].slice(0, separator), modelId: parts[1].slice(separator + 1) } };
|
|
394
|
+
}
|
|
395
|
+
return { kind: "action", action: { type: "set_model", modelId: parts[1] } };
|
|
396
|
+
}
|
|
397
|
+
return parts[1]
|
|
398
|
+
? { kind: "action", action: { type: "set_effort", effort: parts[1] } }
|
|
399
|
+
: { kind: "action", action: { type: "open_effort_picker" } };
|
|
400
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Plain/text/JSONL Renderer。
|
|
3
|
+
*
|
|
4
|
+
* Renderer 只消费 event-projector 产生的安全事件,不读取 AgentEvent 原文、
|
|
5
|
+
* Store、checkpoint 或 Web SSE DTO;因此 text 与 JSONL 的语义来源一致。
|
|
6
|
+
*/
|
|
7
|
+
import type { CliRenderer } from "./types.js";
|
|
8
|
+
/** 创建 Plain Renderer;jsonl 模式只输出稳定安全事件 JSON。 */
|
|
9
|
+
export declare function createPlainRenderer(format: "text" | "jsonl"): CliRenderer;
|
package/dist/renderer.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Plain/text/JSONL Renderer。
|
|
3
|
+
*
|
|
4
|
+
* Renderer 只消费 event-projector 产生的安全事件,不读取 AgentEvent 原文、
|
|
5
|
+
* Store、checkpoint 或 Web SSE DTO;因此 text 与 JSONL 的语义来源一致。
|
|
6
|
+
*/
|
|
7
|
+
import { formatAgentTermination } from "./termination-format.js";
|
|
8
|
+
/** 判断审批约束是否为无沙箱直接执行;与 Web 审批卡同一字符串判定,休眠底座不签发,历史记录仍可能携带。 */
|
|
9
|
+
function isNoSandboxApproval(constraint) {
|
|
10
|
+
return constraint === "no-sandbox-exec:v1";
|
|
11
|
+
}
|
|
12
|
+
/** 将安全事件渲染为不带 ANSI 的 Plain 文本行。 */
|
|
13
|
+
function renderTextEvent(event) {
|
|
14
|
+
switch (event.type) {
|
|
15
|
+
case "turn_started": return `turn started (${event.turnId})`;
|
|
16
|
+
case "reasoning_summary": return `reasoning: ${event.summary}`;
|
|
17
|
+
case "reasoning_hidden": return "reasoning: [hidden]";
|
|
18
|
+
case "answer_delta": return `answer: ${event.delta}`;
|
|
19
|
+
case "answer_reset": return "answer reset";
|
|
20
|
+
case "tool_started": return `tool ${event.toolName}: ${event.summary}`;
|
|
21
|
+
case "tool_progress": return `tool ${event.toolName}: ${event.tail}`;
|
|
22
|
+
case "tool_result": {
|
|
23
|
+
const isolation = event.display?.executionMode === "unisolated" ? " · 未隔离:宿主直接执行" : "";
|
|
24
|
+
const diagnostics = event.display?.diagnostics?.length ? ` · 诊断:${event.display.diagnostics.join(";")}` : "";
|
|
25
|
+
return `tool ${event.toolName} [${event.status}]: ${event.summary}${event.errorCode ? ` (${event.errorCode})` : ""}${diagnostics}${isolation}${event.diff ? `\n${event.diff}` : ""}`;
|
|
26
|
+
}
|
|
27
|
+
case "approval_required": {
|
|
28
|
+
const cwd = event.display?.cwd ? ` · 工作目录:${event.display.cwd}` : "";
|
|
29
|
+
const noSandbox = isNoSandboxApproval(event.constraint) ? " · 无沙箱直接执行,仅本次有效" : "";
|
|
30
|
+
// Talk-Plan1022 S3:宿主执行审批卡按"将采用"措辞说明无隔离事实,待审批不显示成已执行。
|
|
31
|
+
const hostExecution = event.display?.executionMode === "unisolated" ? " · 将在当前用户权限下运行(未启用沙箱隔离)" : "";
|
|
32
|
+
return `approval ${event.approvalId}: ${event.toolName} ${event.reason}${cwd}${noSandbox}${hostExecution}`;
|
|
33
|
+
}
|
|
34
|
+
case "input_queued": return `input queued: ${event.inputId} (${event.delivery})`;
|
|
35
|
+
case "todo_updated": {
|
|
36
|
+
const total = event.todos.length;
|
|
37
|
+
const inProgress = event.todos.find(function findActiveTodo(todo) { return todo.status === "in_progress"; });
|
|
38
|
+
return `todo: ${event.counts.completed}/${total} completed${inProgress ? ` · in progress: ${inProgress.activeForm}` : ""}`;
|
|
39
|
+
}
|
|
40
|
+
case "input_joined": return `input joined: ${event.inputId}`;
|
|
41
|
+
case "input_consumed": return `input consumed: ${event.inputId}`;
|
|
42
|
+
case "model_recovery": return `model recovery: ${event.reason} (${event.attempt}/${event.maxAttempts})`;
|
|
43
|
+
case "context_overflow": return "context overflow: recovery required";
|
|
44
|
+
case "context_compact_started": return "context compact started";
|
|
45
|
+
case "context_compacted": return `context compacted: ${event.preTokens} -> ${event.postTokens}`;
|
|
46
|
+
case "context_compact_failed": return "context compact failed";
|
|
47
|
+
case "context_compact_circuit_open": return "context compact circuit open";
|
|
48
|
+
// 用量变化只属于动态状态栏;text 输出空行跳过,JSONL 仍输出结构化安全 DTO。
|
|
49
|
+
case "context_usage": return "";
|
|
50
|
+
case "active_skills": return `active skills: ${event.skills.map(function skillId(skill) { return skill.skillId; }).join(", ") || "none"}`;
|
|
51
|
+
case "status": return `status: ${event.status}${event.code ? ` (${event.code})` : ""}${event.reason ? ` · ${event.reason}` : ""}${event.termination ? ` · ${formatAgentTermination(event.termination)}` : ""}`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** 创建 Plain Renderer;jsonl 模式只输出稳定安全事件 JSON。 */
|
|
55
|
+
export function createPlainRenderer(format) {
|
|
56
|
+
return {
|
|
57
|
+
format,
|
|
58
|
+
render(event) {
|
|
59
|
+
return format === "jsonl" ? JSON.stringify(event) : renderTextEvent(event);
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** TUI 备用屏视口滚动状态机;纯状态无副作用,供后续渲染层消费。 */
|
|
2
|
+
/** 完整时间线相对于可视窗口的滚动状态。 */
|
|
3
|
+
export interface ScrollViewportState {
|
|
4
|
+
scrollTop: number;
|
|
5
|
+
totalHeight: number;
|
|
6
|
+
viewportHeight: number;
|
|
7
|
+
pinnedToBottom: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** 创建钉底的初始视口状态,并统一收敛非法高度。 */
|
|
10
|
+
export declare function createScrollViewportState(totalHeight?: number, viewportHeight?: number): ScrollViewportState;
|
|
11
|
+
/** 计算指定内容与视口高度下允许的最大首行偏移。 */
|
|
12
|
+
export declare function getScrollViewportMaxTop(totalHeight: number, viewportHeight: number): number;
|
|
13
|
+
/** 向上滚动并解除钉底;即使已经在顶部也保持解除状态。 */
|
|
14
|
+
export declare function scrollViewportUp(state: ScrollViewportState, lines: number): ScrollViewportState;
|
|
15
|
+
/** 直接将视口定位到内容顶部并解除钉底。 */
|
|
16
|
+
export declare function scrollViewportToTop(state: ScrollViewportState): ScrollViewportState;
|
|
17
|
+
/** 向下滚动;只有到达底部后的额外滚动量才重新钉底。 */
|
|
18
|
+
export declare function scrollViewportDown(state: ScrollViewportState, lines: number): ScrollViewportState;
|
|
19
|
+
/** 直接将视口定位到内容底部并恢复钉底。 */
|
|
20
|
+
export declare function scrollViewportToBottom(state: ScrollViewportState): ScrollViewportState;
|
|
21
|
+
/** 更新内容或终端尺寸;非钉底时以当前可见首行为锚并仅做边界收敛。 */
|
|
22
|
+
export declare function resizeScrollViewport(state: ScrollViewportState, totalHeight: number, viewportHeight: number): ScrollViewportState;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/** TUI 备用屏视口滚动状态机;纯状态无副作用,供后续渲染层消费。 */
|
|
2
|
+
/** 创建钉底的初始视口状态,并统一收敛非法高度。 */
|
|
3
|
+
export function createScrollViewportState(totalHeight = 0, viewportHeight = 0) {
|
|
4
|
+
const normalizedTotalHeight = Math.max(0, totalHeight);
|
|
5
|
+
const normalizedViewportHeight = Math.max(0, viewportHeight);
|
|
6
|
+
return {
|
|
7
|
+
scrollTop: getScrollViewportMaxTop(normalizedTotalHeight, normalizedViewportHeight),
|
|
8
|
+
totalHeight: normalizedTotalHeight,
|
|
9
|
+
viewportHeight: normalizedViewportHeight,
|
|
10
|
+
pinnedToBottom: true,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/** 计算指定内容与视口高度下允许的最大首行偏移。 */
|
|
14
|
+
export function getScrollViewportMaxTop(totalHeight, viewportHeight) {
|
|
15
|
+
return Math.max(0, Math.max(0, totalHeight) - Math.max(0, viewportHeight));
|
|
16
|
+
}
|
|
17
|
+
/** 向上滚动并解除钉底;即使已经在顶部也保持解除状态。 */
|
|
18
|
+
export function scrollViewportUp(state, lines) {
|
|
19
|
+
return {
|
|
20
|
+
...state,
|
|
21
|
+
scrollTop: Math.max(0, state.scrollTop - Math.max(0, lines)),
|
|
22
|
+
pinnedToBottom: false,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/** 直接将视口定位到内容顶部并解除钉底。 */
|
|
26
|
+
export function scrollViewportToTop(state) {
|
|
27
|
+
return {
|
|
28
|
+
...state,
|
|
29
|
+
scrollTop: 0,
|
|
30
|
+
pinnedToBottom: false,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** 向下滚动;只有到达底部后的额外滚动量才重新钉底。 */
|
|
34
|
+
export function scrollViewportDown(state, lines) {
|
|
35
|
+
const maxTop = getScrollViewportMaxTop(state.totalHeight, state.viewportHeight);
|
|
36
|
+
const requestedTop = state.scrollTop + Math.max(0, lines);
|
|
37
|
+
return {
|
|
38
|
+
...state,
|
|
39
|
+
scrollTop: Math.min(maxTop, requestedTop),
|
|
40
|
+
pinnedToBottom: state.pinnedToBottom || requestedTop > maxTop,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** 直接将视口定位到内容底部并恢复钉底。 */
|
|
44
|
+
export function scrollViewportToBottom(state) {
|
|
45
|
+
return {
|
|
46
|
+
...state,
|
|
47
|
+
scrollTop: getScrollViewportMaxTop(state.totalHeight, state.viewportHeight),
|
|
48
|
+
pinnedToBottom: true,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** 更新内容或终端尺寸;非钉底时以当前可见首行为锚并仅做边界收敛。 */
|
|
52
|
+
export function resizeScrollViewport(state, totalHeight, viewportHeight) {
|
|
53
|
+
const normalizedTotalHeight = Math.max(0, totalHeight);
|
|
54
|
+
const normalizedViewportHeight = Math.max(0, viewportHeight);
|
|
55
|
+
const maxTop = getScrollViewportMaxTop(normalizedTotalHeight, normalizedViewportHeight);
|
|
56
|
+
return {
|
|
57
|
+
...state,
|
|
58
|
+
totalHeight: normalizedTotalHeight,
|
|
59
|
+
viewportHeight: normalizedViewportHeight,
|
|
60
|
+
scrollTop: state.pinnedToBottom ? maxTop : Math.min(Math.max(0, state.scrollTop), maxTop),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** 将白名单终态事实格式化为 Plain、JSONL 辅助展示与 TUI 共用的短文案。 */
|
|
2
|
+
export function formatAgentTermination(view) {
|
|
3
|
+
const labels = {
|
|
4
|
+
runtime_budget: "执行时长达到上限",
|
|
5
|
+
token_budget: "Token 预算耗尽",
|
|
6
|
+
repeated_tool_calls: "检测到重复工具调用",
|
|
7
|
+
write_verification: "等待修改文件读回验证",
|
|
8
|
+
stale_lease: "执行租约已过期",
|
|
9
|
+
turn_limit_reached: "达到执行步数上限",
|
|
10
|
+
};
|
|
11
|
+
// 旧版本可能对可恢复暂停发送无 reason 的 termination;recoverable 时的安全兜底是“执行已暂停”,不是“任务已停止”。
|
|
12
|
+
const reason = view.reason ? labels[view.reason] : view.recoverable ? "执行已暂停" : "任务已停止";
|
|
13
|
+
const recovery = view.recoverable ? "可继续" : "不可继续";
|
|
14
|
+
const budget = view.budget
|
|
15
|
+
? `${view.budget.used}/${view.budget.limit} ${view.budget.unit}`
|
|
16
|
+
: undefined;
|
|
17
|
+
return [reason, recovery, budget].filter(Boolean).join(" · ");
|
|
18
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** TUI 时间线视觉行模型;统一文字换行、样式片段与源文本坐标,暂不接入渲染。 */
|
|
2
|
+
/** 时间线原始文本的样式范围;坐标使用 JavaScript 字符串偏移。 */
|
|
3
|
+
export interface TimelineTextSpan {
|
|
4
|
+
start: number;
|
|
5
|
+
end: number;
|
|
6
|
+
style: string;
|
|
7
|
+
}
|
|
8
|
+
/** 可转换为视觉行的安全时间线项。 */
|
|
9
|
+
export interface TimelineLineSource {
|
|
10
|
+
itemId: string;
|
|
11
|
+
text: string;
|
|
12
|
+
spans?: TimelineTextSpan[];
|
|
13
|
+
}
|
|
14
|
+
/** 单条视觉行及其在源文本中的可逆映射。 */
|
|
15
|
+
export interface TimelineVisualLine {
|
|
16
|
+
itemId: string;
|
|
17
|
+
text: string;
|
|
18
|
+
displayWidth: number;
|
|
19
|
+
sourceStart: number;
|
|
20
|
+
sourceEnd: number;
|
|
21
|
+
spans: TimelineTextSpan[];
|
|
22
|
+
}
|
|
23
|
+
/** 视觉行缓存的可观测计数,供定向性能合同确认历史行没有重复换行。 */
|
|
24
|
+
export interface TimelineVisualLineCacheStats {
|
|
25
|
+
builds: number;
|
|
26
|
+
hits: number;
|
|
27
|
+
widthInvalidations: number;
|
|
28
|
+
}
|
|
29
|
+
/** 备用屏视觉行增量缓存的公开内部接口。 */
|
|
30
|
+
export interface TimelineVisualLineCache {
|
|
31
|
+
getLines(items: TimelineLineSource[], columns: number): TimelineVisualLine[];
|
|
32
|
+
getStats(): TimelineVisualLineCacheStats;
|
|
33
|
+
clear(): void;
|
|
34
|
+
}
|
|
35
|
+
/** 将安全时间线文本按当前终端列宽转换为统一的视觉行。 */
|
|
36
|
+
export declare function createTimelineVisualLines(items: TimelineLineSource[], columns: number): TimelineVisualLine[];
|
|
37
|
+
/** 创建按 itemId、文本、spans 与终端宽度复用的视觉行缓存。 */
|
|
38
|
+
export declare function createTimelineVisualLineCache(): TimelineVisualLineCache;
|