@armadra/agent 0.3.0 → 0.4.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/CHANGELOG.md +84 -0
- package/README.md +93 -44
- package/dist/agent/retry.d.ts +1 -1
- package/dist/agent/retry.js +2 -1
- package/dist/agent/session-cache.js +1 -1
- package/dist/agent/session-classifier.d.ts +18 -0
- package/dist/agent/session-classifier.js +103 -0
- package/dist/agent/session-core.d.ts +13 -0
- package/dist/agent/session-tools.js +24 -3
- package/dist/agent/session.d.ts +3 -0
- package/dist/agent/session.js +12 -3
- package/dist/agent/tool-runner.js +15 -2
- package/dist/agent/types.d.ts +9 -1
- package/dist/ai/apis/anthropic-messages.js +3 -2
- package/dist/ai/apis/google-generative-ai.js +3 -2
- package/dist/ai/apis/openai-completions.js +3 -2
- package/dist/ai/apis/openai-responses.js +3 -2
- package/dist/ai/http.d.ts +28 -6
- package/dist/ai/http.js +41 -8
- package/dist/ai/providers/registry.d.ts +8 -1
- package/dist/ai/providers/registry.js +30 -22
- package/dist/ai/providers/suggest.d.ts +18 -0
- package/dist/ai/providers/suggest.js +72 -0
- package/dist/ai/sse.d.ts +6 -2
- package/dist/ai/sse.js +22 -2
- package/dist/ai/types.d.ts +9 -2
- package/dist/bundle/ama.cjs +15279 -10151
- package/dist/cli/args.d.ts +19 -4
- package/dist/cli/args.js +83 -10
- package/dist/cli/bootstrap.js +27 -3
- package/dist/cli/codemode-notice.d.ts +20 -0
- package/dist/cli/codemode-notice.js +55 -0
- package/dist/cli/compose-session.d.ts +5 -0
- package/dist/cli/compose-session.js +32 -1
- package/dist/cli/compose-store.d.ts +1 -1
- package/dist/cli/compose-store.js +3 -1
- package/dist/cli/compose.d.ts +14 -4
- package/dist/cli/compose.js +29 -10
- package/dist/cli/default-model.d.ts +38 -1
- package/dist/cli/default-model.js +96 -9
- package/dist/cli/deps.d.ts +36 -0
- package/dist/cli/exit-codes.d.ts +2 -0
- package/dist/cli/exit-codes.js +3 -0
- package/dist/cli/fake-visibility.d.ts +13 -0
- package/dist/cli/fake-visibility.js +26 -0
- package/dist/cli/from-prompt.d.ts +18 -0
- package/dist/cli/from-prompt.js +49 -0
- package/dist/cli/main.d.ts +9 -2
- package/dist/cli/main.js +77 -3
- package/dist/cli/proxy.d.ts +51 -0
- package/dist/cli/proxy.js +135 -0
- package/dist/cli/startup-screen.d.ts +29 -0
- package/dist/cli/startup-screen.js +47 -0
- package/dist/cli/startup-steps.d.ts +1 -1
- package/dist/cli/startup-steps.js +14 -12
- package/dist/cli/subcommands/config.d.ts +19 -3
- package/dist/cli/subcommands/config.js +108 -21
- package/dist/cli/subcommands/context.js +4 -1
- package/dist/cli/subcommands/doctor.js +12 -1
- package/dist/cli/subcommands/init.js +2 -1
- package/dist/cli/subcommands/models-discover.d.ts +15 -9
- package/dist/cli/subcommands/models-discover.js +53 -46
- package/dist/cli/subcommands/probe-runner.d.ts +96 -0
- package/dist/cli/subcommands/probe-runner.js +264 -0
- package/dist/cli/subcommands/providers-probe.d.ts +34 -0
- package/dist/cli/subcommands/providers-probe.js +87 -0
- package/dist/cli/subcommands/providers.d.ts +3 -2
- package/dist/cli/subcommands/providers.js +48 -45
- package/dist/cli/subcommands/sessions-export.d.ts +10 -0
- package/dist/cli/subcommands/sessions-export.js +59 -0
- package/dist/cli/subcommands/sessions-search.d.ts +13 -0
- package/dist/cli/subcommands/sessions-search.js +103 -0
- package/dist/cli/subcommands/sessions.d.ts +3 -2
- package/dist/cli/subcommands/sessions.js +22 -1
- package/dist/cli/subcommands/stats.d.ts +17 -0
- package/dist/cli/subcommands/stats.js +198 -0
- package/dist/cli/system-prompt-arg.d.ts +11 -0
- package/dist/cli/system-prompt-arg.js +34 -0
- package/dist/codemode/modes.d.ts +4 -11
- package/dist/codemode/modes.js +5 -27
- package/dist/codemode/tool.d.ts +18 -14
- package/dist/codemode/tool.js +71 -28
- package/dist/config/init.d.ts +6 -2
- package/dist/config/init.js +12 -5
- package/dist/config/json-schema.d.ts +1 -0
- package/dist/config/json-schema.js +36 -3
- package/dist/config/key-docs.d.ts +22 -0
- package/dist/config/key-docs.js +105 -0
- package/dist/config/merge.d.ts +9 -8
- package/dist/config/merge.js +22 -7
- package/dist/config/schema.d.ts +1 -1
- package/dist/config/schema.js +24 -5
- package/dist/config/types.d.ts +34 -6
- package/dist/config/types.js +17 -1
- package/dist/modes/commands-core.js +6 -5
- package/dist/modes/interactive/approval-dialog.d.ts +21 -5
- package/dist/modes/interactive/approval-dialog.js +106 -27
- package/dist/modes/interactive/commands.d.ts +11 -2
- package/dist/modes/interactive/commands.js +56 -16
- package/dist/modes/interactive/interactive-mode.d.ts +2 -1
- package/dist/modes/interactive/interactive-mode.js +65 -81
- package/dist/modes/interactive/key-dispatch.d.ts +3 -1
- package/dist/modes/interactive/key-dispatch.js +5 -6
- package/dist/modes/interactive/line/line-mode.d.ts +1 -0
- package/dist/modes/interactive/line/line-mode.js +6 -4
- package/dist/modes/interactive/line/line-render.d.ts +4 -0
- package/dist/modes/interactive/line/line-render.js +28 -6
- package/dist/modes/interactive/message-view.d.ts +48 -9
- package/dist/modes/interactive/message-view.js +238 -44
- package/dist/modes/interactive/panels.d.ts +18 -0
- package/dist/modes/interactive/panels.js +143 -0
- package/dist/modes/interactive/pickers.d.ts +23 -2
- package/dist/modes/interactive/pickers.js +48 -15
- package/dist/modes/interactive/run-indicator.d.ts +51 -0
- package/dist/modes/interactive/run-indicator.js +189 -0
- package/dist/modes/interactive/startup-header.d.ts +40 -0
- package/dist/modes/interactive/startup-header.js +169 -0
- package/dist/modes/interactive/status-bar.d.ts +18 -15
- package/dist/modes/interactive/status-bar.js +98 -56
- package/dist/modes/interactive/tool-summary.d.ts +46 -0
- package/dist/modes/interactive/tool-summary.js +218 -0
- package/dist/modes/interactive/tool-view.d.ts +48 -15
- package/dist/modes/interactive/tool-view.js +203 -145
- package/dist/modes/print/print-mode.d.ts +31 -4
- package/dist/modes/print/print-mode.js +116 -7
- package/dist/modes/rpc/commands.js +5 -0
- package/dist/permissions/auto-safe.d.ts +60 -0
- package/dist/permissions/auto-safe.js +529 -0
- package/dist/permissions/classifier.d.ts +64 -0
- package/dist/permissions/classifier.js +184 -0
- package/dist/permissions/dangerous.d.ts +5 -0
- package/dist/permissions/dangerous.js +1 -1
- package/dist/permissions/modes.d.ts +30 -0
- package/dist/permissions/modes.js +78 -0
- package/dist/permissions/pipeline.d.ts +31 -4
- package/dist/permissions/pipeline.js +196 -6
- package/dist/permissions/protected.d.ts +19 -0
- package/dist/permissions/protected.js +74 -0
- package/dist/permissions/rules.js +3 -0
- package/dist/permissions/types.d.ts +50 -3
- package/dist/permissions/types.js +3 -0
- package/dist/sdk.d.ts +9 -3
- package/dist/sdk.js +10 -2
- package/dist/session/export.d.ts +32 -0
- package/dist/session/export.js +187 -0
- package/dist/session/redact.d.ts +15 -0
- package/dist/session/redact.js +55 -0
- package/dist/session/reuse.d.ts +33 -0
- package/dist/session/reuse.js +86 -0
- package/dist/session/scan.d.ts +34 -0
- package/dist/session/scan.js +140 -0
- package/dist/session/search.d.ts +52 -0
- package/dist/session/search.js +211 -0
- package/dist/session/stats-aggregate.d.ts +63 -0
- package/dist/session/stats-aggregate.js +163 -0
- package/dist/session/stats-index.d.ts +26 -0
- package/dist/session/stats-index.js +91 -0
- package/dist/session/stats-scan.d.ts +54 -0
- package/dist/session/stats-scan.js +236 -0
- package/dist/tools/presets.d.ts +35 -8
- package/dist/tools/presets.js +56 -17
- package/dist/tui/component.d.ts +8 -2
- package/dist/tui/component.js +3 -1
- package/dist/tui/components/box.d.ts +6 -1
- package/dist/tui/components/box.js +16 -6
- package/dist/tui/components/card.d.ts +23 -0
- package/dist/tui/components/card.js +37 -0
- package/dist/tui/components/editor-history.d.ts +6 -0
- package/dist/tui/components/editor-history.js +45 -0
- package/dist/tui/components/editor-paste.d.ts +1 -1
- package/dist/tui/components/editor-paste.js +4 -4
- package/dist/tui/components/editor.d.ts +11 -5
- package/dist/tui/components/editor.js +52 -58
- package/dist/tui/components/key-value.d.ts +3 -0
- package/dist/tui/components/key-value.js +16 -6
- package/dist/tui/components/loader.d.ts +37 -7
- package/dist/tui/components/loader.js +84 -21
- package/dist/tui/components/markdown.d.ts +5 -1
- package/dist/tui/components/markdown.js +45 -21
- package/dist/tui/components/meter.d.ts +3 -3
- package/dist/tui/components/meter.js +13 -11
- package/dist/tui/components/select-list.d.ts +23 -1
- package/dist/tui/components/select-list.js +76 -13
- package/dist/tui/glyphs.d.ts +68 -0
- package/dist/tui/glyphs.js +114 -0
- package/dist/tui/theme.d.ts +30 -6
- package/dist/tui/theme.js +103 -17
- package/dist/tui.d.ts +4 -2
- package/dist/tui.js +3 -1
- package/docs/codemode.md +23 -9
- package/docs/hooks.md +10 -10
- package/docs/permissions.md +148 -0
- package/docs/providers.md +33 -10
- package/docs/rpc.md +36 -36
- package/docs/session-format.md +2 -1
- package/docs/sessions.md +134 -0
- package/docs/tui.md +139 -62
- package/package.json +3 -1
|
@@ -4,14 +4,17 @@
|
|
|
4
4
|
* - 模型:按供应商分组并标 key 状态(`modelItems`,与启动期共用),当前模型预选;
|
|
5
5
|
* - 会话:名字或首条提示、相对时间、消息数(`sessionItems`);
|
|
6
6
|
* - 树:会话文件里全部用户消息,按分叉缩进,`●` 标出当前分支;选中项回填编辑器(由 commands.ts 处理);
|
|
7
|
-
* -
|
|
7
|
+
* - 权限模式(标题「权限模式」,界面顺序 1–6,当前模式 ✓ / Default dim / Recommended accent,底部按键提示,
|
|
8
|
+
* 上下留白,见 permissionItems)与思考级别;
|
|
9
|
+
* - 所有选择器底部一行按键提示(dim),模型选择器带 (i/n) 并给当前模型打 ✓。
|
|
8
10
|
*/
|
|
9
11
|
import { THINKING_LEVELS } from "../../ai/thinking.js";
|
|
10
|
-
import {
|
|
12
|
+
import { PERMISSION_MODE_INFO, PERMISSION_MODE_ORDER, RECOMMENDED_PERMISSION_MODE, } from "../../permissions/modes.js";
|
|
11
13
|
import { Box, SelectList, } from "../../tui.js";
|
|
12
14
|
import { contentText } from "./message-view.js";
|
|
13
15
|
import { relativeTime } from "./startup-ui.js";
|
|
14
16
|
export { modelItems, sessionItems } from "./startup-ui.js";
|
|
17
|
+
export const PICKER_FOOTER = "↑↓ 选择 · Enter 确认 · Esc 取消";
|
|
15
18
|
/** 打开一个居中选择器;Enter 返回选中项,Esc / Ctrl+C 返回 undefined。 */
|
|
16
19
|
export function openPicker(host, spec) {
|
|
17
20
|
return new Promise((resolve) => {
|
|
@@ -26,30 +29,60 @@ export function openPicker(host, spec) {
|
|
|
26
29
|
filterable: spec.filterable ?? spec.items.length > 8,
|
|
27
30
|
...(host.keybindings !== undefined ? { keybindings: host.keybindings } : {}),
|
|
28
31
|
...(spec.emptyText !== undefined ? { emptyText: spec.emptyText } : {}),
|
|
32
|
+
...(spec.numberKeys === true ? { numberKeys: true } : {}),
|
|
33
|
+
...(spec.stacked === true ? { stacked: true } : {}),
|
|
34
|
+
...(spec.currentValue !== undefined ? { currentValue: spec.currentValue } : {}),
|
|
35
|
+
...(spec.showCount === true ? { showCount: true } : {}),
|
|
36
|
+
footer: spec.footer ?? PICKER_FOOTER,
|
|
29
37
|
onSelect: (item) => close(item),
|
|
30
38
|
onCancel: () => close(undefined),
|
|
31
39
|
});
|
|
32
40
|
if (spec.selected !== undefined)
|
|
33
41
|
list.selectValue(spec.selected);
|
|
34
42
|
const width = Math.max(20, Math.min(host.columns() - 2, 72));
|
|
35
|
-
|
|
43
|
+
const box = new Box(list, {
|
|
44
|
+
title: spec.title,
|
|
45
|
+
theme: host.theme,
|
|
46
|
+
...(spec.paddingY !== undefined ? { paddingY: spec.paddingY } : {}),
|
|
47
|
+
});
|
|
48
|
+
handle = host.showOverlay(box, {
|
|
36
49
|
anchor: "center",
|
|
37
50
|
width,
|
|
38
51
|
});
|
|
39
52
|
});
|
|
40
53
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
/**
|
|
55
|
+
* 模式选择器(标题「权限模式」):界面顺序、当前模式打勾(`currentValue`)、配置里的缺省模式标
|
|
56
|
+
* `Default`、Auto 标 `Recommended`,右侧数字快捷键 1–6,说明在下一行。
|
|
57
|
+
*/
|
|
58
|
+
export function permissionItems(_current, configDefault = "default") {
|
|
59
|
+
return PERMISSION_MODE_ORDER.map((mode) => {
|
|
60
|
+
const info = PERMISSION_MODE_INFO[mode];
|
|
61
|
+
const item = { value: mode, label: info.label, description: info.description };
|
|
62
|
+
const badges = [
|
|
63
|
+
...(mode === configDefault ? ["Default"] : []),
|
|
64
|
+
...(mode === RECOMMENDED_PERMISSION_MODE ? ["Recommended"] : []),
|
|
65
|
+
];
|
|
66
|
+
if (badges.length > 0) {
|
|
67
|
+
item.badge = badges.join(" · ");
|
|
68
|
+
if (mode !== RECOMMENDED_PERMISSION_MODE)
|
|
69
|
+
item.badgeColor = "dim";
|
|
70
|
+
}
|
|
71
|
+
return item;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
export function permissionPickerSpec(current, configDefault = "default") {
|
|
75
|
+
return {
|
|
76
|
+
title: "权限模式",
|
|
77
|
+
items: permissionItems(current, configDefault),
|
|
78
|
+
selected: current,
|
|
79
|
+
currentValue: current,
|
|
80
|
+
filterable: false,
|
|
81
|
+
numberKeys: true,
|
|
82
|
+
stacked: true,
|
|
83
|
+
footer: "↑↓ 选择 · 1-6 直接选 · Enter 确认 · Esc 取消",
|
|
84
|
+
paddingY: 1,
|
|
85
|
+
};
|
|
53
86
|
}
|
|
54
87
|
export function thinkingItems(reasoning) {
|
|
55
88
|
return THINKING_LEVELS.map((level) => {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 运行指示与排队消息(终端界面视觉设计 v1 §3.7、§3.8):输入框上方的 Loader 动词状态机与排队列表。
|
|
3
|
+
*
|
|
4
|
+
* 动词按「当前最深状态」取,避免事件乱序时闪回:
|
|
5
|
+
* 审批打开 → `等待确认`(无 Esc 提示,Esc 由对话框消费)
|
|
6
|
+
* 有运行中的工具 → `运行 bash` / `运行 3 个工具`
|
|
7
|
+
* 自动重试等待中 → `重试 2/3 · 2s 后`(不显示已用时)
|
|
8
|
+
* 压缩中 → `压缩上下文`
|
|
9
|
+
* 助手在流式输出 → 末块是思考 `思考中`,否则 `回复中 · ↓≈1.2k`(本条输出的估算 token,4 字符 ≈ 1)
|
|
10
|
+
* 其余(等首个 token)→ `思考中`
|
|
11
|
+
* 运行或压缩时 Loader 才挂进槽位;附加项 `Esc 中断`。
|
|
12
|
+
*
|
|
13
|
+
* 排队消息:缩进 2 列(挂在当前回合下),`↳ 插话 / 之后 文本` 整体 muted、标签 dim;超过 3 条首行
|
|
14
|
+
* `… 另 N 条`;末行 `Alt+↑ 取回 · Esc 回填并中断`。
|
|
15
|
+
*/
|
|
16
|
+
import type { SessionEvent } from "../../agent/types.js";
|
|
17
|
+
import { Container, Text, type Loader, type Theme } from "../../tui.js";
|
|
18
|
+
import type { ToolTracker } from "./tool-view.js";
|
|
19
|
+
export declare const QUEUE_PREVIEW = 3;
|
|
20
|
+
export interface RunIndicatorDeps {
|
|
21
|
+
theme: Theme;
|
|
22
|
+
loader: Loader;
|
|
23
|
+
/** Loader 的挂载槽位(运行时才有子组件)。 */
|
|
24
|
+
slot: Container;
|
|
25
|
+
tools: ToolTracker;
|
|
26
|
+
render(): void;
|
|
27
|
+
}
|
|
28
|
+
export declare class RunIndicator {
|
|
29
|
+
private readonly deps;
|
|
30
|
+
running: boolean;
|
|
31
|
+
compacting: boolean;
|
|
32
|
+
private approval;
|
|
33
|
+
private retry;
|
|
34
|
+
private stream;
|
|
35
|
+
constructor(deps: RunIndicatorDeps);
|
|
36
|
+
get busy(): boolean;
|
|
37
|
+
setApproval(open: boolean): void;
|
|
38
|
+
/** 会话切换后清零。 */
|
|
39
|
+
reset(): void;
|
|
40
|
+
/** 会话事件 → 状态;返回后已同步 Loader。 */
|
|
41
|
+
onEvent(event: SessionEvent): void;
|
|
42
|
+
/** 按当前状态换动词,并挂上 / 撤下 Loader。 */
|
|
43
|
+
sync(): void;
|
|
44
|
+
private applyVerb;
|
|
45
|
+
}
|
|
46
|
+
/** 排队消息(插话 / 之后),挂在 Loader 之上。 */
|
|
47
|
+
export declare class QueueView extends Text {
|
|
48
|
+
private readonly theme;
|
|
49
|
+
constructor(theme: Theme);
|
|
50
|
+
setQueue(steering: readonly string[], followUp: readonly string[]): void;
|
|
51
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 运行指示与排队消息(终端界面视觉设计 v1 §3.7、§3.8):输入框上方的 Loader 动词状态机与排队列表。
|
|
3
|
+
*
|
|
4
|
+
* 动词按「当前最深状态」取,避免事件乱序时闪回:
|
|
5
|
+
* 审批打开 → `等待确认`(无 Esc 提示,Esc 由对话框消费)
|
|
6
|
+
* 有运行中的工具 → `运行 bash` / `运行 3 个工具`
|
|
7
|
+
* 自动重试等待中 → `重试 2/3 · 2s 后`(不显示已用时)
|
|
8
|
+
* 压缩中 → `压缩上下文`
|
|
9
|
+
* 助手在流式输出 → 末块是思考 `思考中`,否则 `回复中 · ↓≈1.2k`(本条输出的估算 token,4 字符 ≈ 1)
|
|
10
|
+
* 其余(等首个 token)→ `思考中`
|
|
11
|
+
* 运行或压缩时 Loader 才挂进槽位;附加项 `Esc 中断`。
|
|
12
|
+
*
|
|
13
|
+
* 排队消息:缩进 2 列(挂在当前回合下),`↳ 插话 / 之后 文本` 整体 muted、标签 dim;超过 3 条首行
|
|
14
|
+
* `… 另 N 条`;末行 `Alt+↑ 取回 · Esc 回填并中断`。
|
|
15
|
+
*/
|
|
16
|
+
import { Container, Text } from "../../tui.js";
|
|
17
|
+
import { ORIGIN_LABELS } from "./message-view.js";
|
|
18
|
+
import { formatTokens } from "./status-bar.js";
|
|
19
|
+
export const QUEUE_PREVIEW = 3;
|
|
20
|
+
function oneLine(text) {
|
|
21
|
+
return text.replace(/\s+/g, " ").trim();
|
|
22
|
+
}
|
|
23
|
+
/** 助手消息里已流出的文字(思考 + 正文)的估算 token 与末块类型。 */
|
|
24
|
+
function streamState(message) {
|
|
25
|
+
let chars = 0;
|
|
26
|
+
let last;
|
|
27
|
+
for (const block of message.content) {
|
|
28
|
+
if (block.type === "text") {
|
|
29
|
+
chars += block.text.length;
|
|
30
|
+
if (block.text !== "")
|
|
31
|
+
last = "text";
|
|
32
|
+
}
|
|
33
|
+
else if (block.type === "thinking") {
|
|
34
|
+
chars += block.thinking.length;
|
|
35
|
+
if (block.thinking !== "")
|
|
36
|
+
last = "thinking";
|
|
37
|
+
}
|
|
38
|
+
else if (block.type === "toolCall")
|
|
39
|
+
last = "toolCall";
|
|
40
|
+
}
|
|
41
|
+
return { tokens: Math.ceil(chars / 4), thinking: last === "thinking" };
|
|
42
|
+
}
|
|
43
|
+
export class RunIndicator {
|
|
44
|
+
deps;
|
|
45
|
+
running = false;
|
|
46
|
+
compacting = false;
|
|
47
|
+
approval = false;
|
|
48
|
+
retry;
|
|
49
|
+
stream;
|
|
50
|
+
constructor(deps) {
|
|
51
|
+
this.deps = deps;
|
|
52
|
+
}
|
|
53
|
+
get busy() {
|
|
54
|
+
return this.running || this.compacting;
|
|
55
|
+
}
|
|
56
|
+
setApproval(open) {
|
|
57
|
+
this.approval = open;
|
|
58
|
+
this.sync();
|
|
59
|
+
}
|
|
60
|
+
/** 会话切换后清零。 */
|
|
61
|
+
reset() {
|
|
62
|
+
this.running = false;
|
|
63
|
+
this.compacting = false;
|
|
64
|
+
this.approval = false;
|
|
65
|
+
this.retry = undefined;
|
|
66
|
+
this.stream = undefined;
|
|
67
|
+
this.sync();
|
|
68
|
+
}
|
|
69
|
+
/** 会话事件 → 状态;返回后已同步 Loader。 */
|
|
70
|
+
onEvent(event) {
|
|
71
|
+
switch (event.type) {
|
|
72
|
+
case "agent_start":
|
|
73
|
+
this.running = true;
|
|
74
|
+
break;
|
|
75
|
+
case "agent_settled":
|
|
76
|
+
this.running = false;
|
|
77
|
+
this.retry = undefined;
|
|
78
|
+
this.stream = undefined;
|
|
79
|
+
break;
|
|
80
|
+
case "message_start":
|
|
81
|
+
if (event.message.role === "assistant") {
|
|
82
|
+
this.retry = undefined;
|
|
83
|
+
this.stream = streamState(event.message);
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
case "message_update":
|
|
87
|
+
if (event.message.role === "assistant")
|
|
88
|
+
this.stream = streamState(event.message);
|
|
89
|
+
break;
|
|
90
|
+
case "message_end":
|
|
91
|
+
if (event.message.role === "assistant")
|
|
92
|
+
this.stream = undefined;
|
|
93
|
+
break;
|
|
94
|
+
case "compaction_start":
|
|
95
|
+
this.compacting = true;
|
|
96
|
+
break;
|
|
97
|
+
case "compaction_end":
|
|
98
|
+
this.compacting = false;
|
|
99
|
+
break;
|
|
100
|
+
case "auto_retry_start":
|
|
101
|
+
this.retry = { attempt: event.attempt, max: event.maxAttempts, delayMs: event.delayMs };
|
|
102
|
+
break;
|
|
103
|
+
case "auto_retry_end":
|
|
104
|
+
this.retry = undefined;
|
|
105
|
+
break;
|
|
106
|
+
case "tool_execution_start":
|
|
107
|
+
case "tool_execution_end":
|
|
108
|
+
break;
|
|
109
|
+
default:
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
this.sync();
|
|
113
|
+
}
|
|
114
|
+
/** 按当前状态换动词,并挂上 / 撤下 Loader。 */
|
|
115
|
+
sync() {
|
|
116
|
+
const { loader, slot, render } = this.deps;
|
|
117
|
+
const active = this.busy;
|
|
118
|
+
if (active)
|
|
119
|
+
this.applyVerb();
|
|
120
|
+
if (active && slot.children.length === 0) {
|
|
121
|
+
slot.addChild(loader);
|
|
122
|
+
loader.start();
|
|
123
|
+
}
|
|
124
|
+
else if (!active && slot.children.length > 0) {
|
|
125
|
+
loader.stop();
|
|
126
|
+
slot.clear();
|
|
127
|
+
}
|
|
128
|
+
render();
|
|
129
|
+
}
|
|
130
|
+
applyVerb() {
|
|
131
|
+
const { loader, tools } = this.deps;
|
|
132
|
+
const esc = ["Esc 中断"];
|
|
133
|
+
if (this.approval) {
|
|
134
|
+
loader.setVerb("等待确认");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const running = tools.running();
|
|
138
|
+
const top = running.filter((view) => !running.some((p) => p.children.includes(view)));
|
|
139
|
+
if (top.length === 1) {
|
|
140
|
+
loader.setVerb(`运行 ${top[0].toolName}`, esc);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (top.length > 1) {
|
|
144
|
+
loader.setVerb(`运行 ${top.length} 个工具`, esc);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (this.retry !== undefined) {
|
|
148
|
+
const seconds = Math.max(1, Math.round(this.retry.delayMs / 1000));
|
|
149
|
+
loader.setVerb(`重试 ${this.retry.attempt}/${this.retry.max}`, [`${seconds}s 后`], {
|
|
150
|
+
elapsed: false,
|
|
151
|
+
});
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (this.compacting) {
|
|
155
|
+
loader.setVerb("压缩上下文", esc);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const stream = this.stream;
|
|
159
|
+
if (stream !== undefined && !stream.thinking && stream.tokens > 0) {
|
|
160
|
+
loader.setVerb("回复中", [`↓≈${formatTokens(stream.tokens)}`, ...esc]);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
loader.setVerb("思考中", esc);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/** 排队消息(插话 / 之后),挂在 Loader 之上。 */
|
|
167
|
+
export class QueueView extends Text {
|
|
168
|
+
theme;
|
|
169
|
+
constructor(theme) {
|
|
170
|
+
super("");
|
|
171
|
+
this.theme = theme;
|
|
172
|
+
}
|
|
173
|
+
setQueue(steering, followUp) {
|
|
174
|
+
const t = this.theme;
|
|
175
|
+
const row = (origin, text) => ` ${t.fg("dim", `${t.glyphs.queued} ${ORIGIN_LABELS[origin]}`)} ${t.fg("muted", oneLine(text))}`;
|
|
176
|
+
const rows = [
|
|
177
|
+
...steering.map((text) => row("steer", text)),
|
|
178
|
+
...followUp.map((text) => row("followUp", text)),
|
|
179
|
+
];
|
|
180
|
+
const shown = rows.slice(-QUEUE_PREVIEW);
|
|
181
|
+
if (rows.length > shown.length) {
|
|
182
|
+
shown.unshift(" " + t.fg("dim", `${t.glyphs.ellipsis} 另 ${rows.length - shown.length} 条`));
|
|
183
|
+
}
|
|
184
|
+
if (rows.length > 0) {
|
|
185
|
+
shown.push(" " + t.fg("dim", `Alt+${t.glyphs.arrowUp} 取回 · Esc 回填并中断`));
|
|
186
|
+
}
|
|
187
|
+
this.setText(shown.join("\n"));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 交互模式启动头(终端界面视觉设计 v1 §3.1)。
|
|
3
|
+
*
|
|
4
|
+
* - `normal`:宽度 ≥ 56 且非 `ui.compact` 时画框(宽 `min(width, 64)`,左对齐)——标题 `✻ ama x.y.z`、
|
|
5
|
+
* 键值行(模型 / 目录 / 模式 / 已加载 / 宿主 / 警告)、按键提示;否则去框去键列,每项一行。
|
|
6
|
+
* - `header`:一行 `✻ ama x.y.z · 模型 · 模式 · /help`,无框。`silent` 不输出(调用方不加本组件)。
|
|
7
|
+
* - 着色:`✻` accent、标题粗体;键列 dim;模型 accent;已信任 success / 未信任 warning;Bypass 模式 warning;
|
|
8
|
+
* 按键提示 dim。路径过长时从左截断(`…/armadra-agent`)。宽度变化时按新宽重算是否去框。
|
|
9
|
+
*/
|
|
10
|
+
import type { StartupInfo } from "../../cli/startup-screen.js";
|
|
11
|
+
import { type Component, type Theme } from "../../tui.js";
|
|
12
|
+
/** 画框的最小宽度;更窄时去框去键列。 */
|
|
13
|
+
export declare const HEADER_BOX_MIN_WIDTH = 56;
|
|
14
|
+
/** 设计稿写 60;常见的「供应商/模型@渠道 · 思考 级别」要 62 列左右才不截断,取 64。 */
|
|
15
|
+
export declare const HEADER_BOX_MAX_WIDTH = 64;
|
|
16
|
+
export interface StartupHeaderOptions {
|
|
17
|
+
theme: Theme;
|
|
18
|
+
level: "normal" | "header";
|
|
19
|
+
/** `ui.compact`:normal 也不画框。 */
|
|
20
|
+
compact?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/** 从左截断到 `width` 列(保留末尾,前面加省略号)。 */
|
|
23
|
+
export declare function truncateLeft(text: string, width: number, ellipsis?: string): string;
|
|
24
|
+
export declare class StartupHeader implements Component {
|
|
25
|
+
private readonly info;
|
|
26
|
+
private readonly options;
|
|
27
|
+
private cache;
|
|
28
|
+
constructor(info: StartupInfo, options: StartupHeaderOptions);
|
|
29
|
+
private get theme();
|
|
30
|
+
private title;
|
|
31
|
+
private modeText;
|
|
32
|
+
private trustText;
|
|
33
|
+
private loadedText;
|
|
34
|
+
private codemodeText;
|
|
35
|
+
render(width: number): string[];
|
|
36
|
+
private headerLine;
|
|
37
|
+
private plainLines;
|
|
38
|
+
private boxLines;
|
|
39
|
+
invalidate(): void;
|
|
40
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 交互模式启动头(终端界面视觉设计 v1 §3.1)。
|
|
3
|
+
*
|
|
4
|
+
* - `normal`:宽度 ≥ 56 且非 `ui.compact` 时画框(宽 `min(width, 64)`,左对齐)——标题 `✻ ama x.y.z`、
|
|
5
|
+
* 键值行(模型 / 目录 / 模式 / 已加载 / 宿主 / 警告)、按键提示;否则去框去键列,每项一行。
|
|
6
|
+
* - `header`:一行 `✻ ama x.y.z · 模型 · 模式 · /help`,无框。`silent` 不输出(调用方不加本组件)。
|
|
7
|
+
* - 着色:`✻` accent、标题粗体;键列 dim;模型 accent;已信任 success / 未信任 warning;Bypass 模式 warning;
|
|
8
|
+
* 按键提示 dim。路径过长时从左截断(`…/armadra-agent`)。宽度变化时按新宽重算是否去框。
|
|
9
|
+
*/
|
|
10
|
+
import { permissionModeLabel } from "../../permissions/modes.js";
|
|
11
|
+
import { Box, KeyValue, padToWidth, truncateToWidth, visibleWidth, } from "../../tui.js";
|
|
12
|
+
/** 画框的最小宽度;更窄时去框去键列。 */
|
|
13
|
+
export const HEADER_BOX_MIN_WIDTH = 56;
|
|
14
|
+
/** 设计稿写 60;常见的「供应商/模型@渠道 · 思考 级别」要 62 列左右才不截断,取 64。 */
|
|
15
|
+
export const HEADER_BOX_MAX_WIDTH = 64;
|
|
16
|
+
/** 从左截断到 `width` 列(保留末尾,前面加省略号)。 */
|
|
17
|
+
export function truncateLeft(text, width, ellipsis = "…") {
|
|
18
|
+
if (visibleWidth(text) <= width)
|
|
19
|
+
return text;
|
|
20
|
+
const room = width - visibleWidth(ellipsis);
|
|
21
|
+
if (room <= 0)
|
|
22
|
+
return truncateToWidth(ellipsis, width);
|
|
23
|
+
const chars = [...text];
|
|
24
|
+
let out = "";
|
|
25
|
+
let used = 0;
|
|
26
|
+
for (let i = chars.length - 1; i >= 0; i--) {
|
|
27
|
+
const w = visibleWidth(chars[i]);
|
|
28
|
+
if (used + w > room)
|
|
29
|
+
break;
|
|
30
|
+
out = chars[i] + out;
|
|
31
|
+
used += w;
|
|
32
|
+
}
|
|
33
|
+
return ellipsis + out;
|
|
34
|
+
}
|
|
35
|
+
/** 去掉模型引用里的渠道(`@messages`)。 */
|
|
36
|
+
function withoutChannel(model) {
|
|
37
|
+
const at = model.indexOf("@");
|
|
38
|
+
return at === -1 ? model : model.slice(0, at);
|
|
39
|
+
}
|
|
40
|
+
export class StartupHeader {
|
|
41
|
+
info;
|
|
42
|
+
options;
|
|
43
|
+
cache = null;
|
|
44
|
+
constructor(info, options) {
|
|
45
|
+
this.info = info;
|
|
46
|
+
this.options = options;
|
|
47
|
+
}
|
|
48
|
+
get theme() {
|
|
49
|
+
return this.options.theme;
|
|
50
|
+
}
|
|
51
|
+
title() {
|
|
52
|
+
const t = this.theme;
|
|
53
|
+
return `${t.fg("accent", t.glyphs.thinking)} ${t.bold(`ama ${this.info.version}`)}`;
|
|
54
|
+
}
|
|
55
|
+
modeText() {
|
|
56
|
+
const label = permissionModeLabel(this.info.permissionMode);
|
|
57
|
+
return this.info.permissionMode === "full-auto" ? this.theme.fg("warning", label) : label;
|
|
58
|
+
}
|
|
59
|
+
trustText(withSource) {
|
|
60
|
+
const { trusted, trustSource } = this.info;
|
|
61
|
+
const text = `${trusted ? "已信任" : "未信任"}${withSource ? `(${trustSource})` : ""}`;
|
|
62
|
+
return this.theme.fg(trusted ? "success" : "warning", text);
|
|
63
|
+
}
|
|
64
|
+
loadedText() {
|
|
65
|
+
const { contextFiles, skills, prompts, hooks } = this.info;
|
|
66
|
+
const parts = [];
|
|
67
|
+
if (contextFiles.length > 0)
|
|
68
|
+
parts.push(contextFiles.join(", "));
|
|
69
|
+
if (skills > 0)
|
|
70
|
+
parts.push(`${skills} Skill`);
|
|
71
|
+
if (prompts > 0)
|
|
72
|
+
parts.push(`${prompts} 模板`);
|
|
73
|
+
if (hooks > 0)
|
|
74
|
+
parts.push(`${hooks} Hook`);
|
|
75
|
+
return parts.length === 0 ? undefined : parts.join(" · ");
|
|
76
|
+
}
|
|
77
|
+
codemodeText() {
|
|
78
|
+
return this.info.codemode === "off" ? undefined : `codemode ${this.info.codemode}`;
|
|
79
|
+
}
|
|
80
|
+
render(width) {
|
|
81
|
+
if (this.cache?.width === width)
|
|
82
|
+
return this.cache.lines;
|
|
83
|
+
const lines = this.options.level === "header"
|
|
84
|
+
? [this.headerLine(width)]
|
|
85
|
+
: this.options.compact === true || width < HEADER_BOX_MIN_WIDTH
|
|
86
|
+
? this.plainLines(width)
|
|
87
|
+
: this.boxLines(width);
|
|
88
|
+
this.cache = { width, lines };
|
|
89
|
+
return lines;
|
|
90
|
+
}
|
|
91
|
+
headerLine(width) {
|
|
92
|
+
const t = this.theme;
|
|
93
|
+
const sep = t.fg("dim", " · ");
|
|
94
|
+
const parts = [
|
|
95
|
+
this.title(),
|
|
96
|
+
t.fg("accent", withoutChannel(this.info.model)),
|
|
97
|
+
this.modeText(),
|
|
98
|
+
t.fg("dim", "/help"),
|
|
99
|
+
];
|
|
100
|
+
return truncateToWidth(parts.join(sep), width);
|
|
101
|
+
}
|
|
102
|
+
plainLines(width) {
|
|
103
|
+
const t = this.theme;
|
|
104
|
+
const sep = t.fg("dim", " · ");
|
|
105
|
+
const ellipsis = t.glyphs.ellipsis;
|
|
106
|
+
const { info } = this;
|
|
107
|
+
const lines = [this.title()];
|
|
108
|
+
lines.push(t.fg("accent", withoutChannel(info.model)) + sep + info.thinking);
|
|
109
|
+
const trust = this.trustText(false);
|
|
110
|
+
const room = Math.max(4, width - visibleWidth(trust) - 3);
|
|
111
|
+
lines.push(truncateLeft(info.cwd, room, ellipsis) + sep + trust);
|
|
112
|
+
const mode = [this.modeText(), info.preset, this.codemodeText()].filter((p) => p !== undefined);
|
|
113
|
+
lines.push(mode.join(sep));
|
|
114
|
+
const loaded = this.loadedText();
|
|
115
|
+
if (loaded !== undefined)
|
|
116
|
+
lines.push(loaded);
|
|
117
|
+
if (info.host !== undefined)
|
|
118
|
+
lines.push(`宿主 ${info.host}`);
|
|
119
|
+
if (info.warnings > 0)
|
|
120
|
+
lines.push(t.fg("warning", `警告 ${info.warnings} 条(ama doctor)`));
|
|
121
|
+
lines.push(t.fg("dim", "/help · Shift+Tab 切模式"));
|
|
122
|
+
return lines.map((line) => truncateToWidth(line, width));
|
|
123
|
+
}
|
|
124
|
+
boxLines(width) {
|
|
125
|
+
const t = this.theme;
|
|
126
|
+
const sep = t.fg("dim", " · ");
|
|
127
|
+
const { info } = this;
|
|
128
|
+
const boxWidth = Math.min(width, HEADER_BOX_MAX_WIDTH);
|
|
129
|
+
/** 框内文本宽:边框 2 + 内边距 2;键列 6 + 间隔 1。 */
|
|
130
|
+
const valueWidth = boxWidth - 4 - 7;
|
|
131
|
+
const trust = this.trustText(true);
|
|
132
|
+
const cwdRoom = Math.max(4, valueWidth - visibleWidth(trust) - 3);
|
|
133
|
+
const rows = [
|
|
134
|
+
{ key: "模型", value: t.fg("accent", info.model) + sep + `思考 ${info.thinking}` },
|
|
135
|
+
{ key: "目录", value: truncateLeft(info.cwd, cwdRoom, t.glyphs.ellipsis) + sep + trust },
|
|
136
|
+
{
|
|
137
|
+
key: "模式",
|
|
138
|
+
value: [this.modeText(), `预设 ${info.preset}`, this.codemodeText()]
|
|
139
|
+
.filter((p) => p !== undefined)
|
|
140
|
+
.join(sep),
|
|
141
|
+
},
|
|
142
|
+
];
|
|
143
|
+
const loaded = this.loadedText();
|
|
144
|
+
if (loaded !== undefined)
|
|
145
|
+
rows.push({ key: "已加载", value: loaded });
|
|
146
|
+
if (info.host !== undefined)
|
|
147
|
+
rows.push({ key: "宿主", value: info.host });
|
|
148
|
+
if (info.warnings > 0) {
|
|
149
|
+
rows.push({ key: "警告", value: t.fg("warning", `${info.warnings} 条(ama doctor 查看)`) });
|
|
150
|
+
}
|
|
151
|
+
// 键列固定 6 列(「已加载」的宽度),没有这一行时也对齐
|
|
152
|
+
const padded = rows.map((row) => ({ ...row, key: padToWidth(row.key, 6) }));
|
|
153
|
+
const kv = new KeyValue(padded, { theme: t, gap: 1, maxKeyRatio: 1 });
|
|
154
|
+
const body = {
|
|
155
|
+
render: (inner) => [
|
|
156
|
+
this.title(),
|
|
157
|
+
"",
|
|
158
|
+
...kv.render(inner),
|
|
159
|
+
"",
|
|
160
|
+
t.fg("dim", "/help 命令 · Shift+Tab 切模式 · Ctrl+O 展开工具输出"),
|
|
161
|
+
],
|
|
162
|
+
invalidate: () => kv.invalidate(),
|
|
163
|
+
};
|
|
164
|
+
return new Box(body, { theme: t }).render(boxWidth);
|
|
165
|
+
}
|
|
166
|
+
invalidate() {
|
|
167
|
+
this.cache = null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 状态栏(设计 §12.6
|
|
2
|
+
* 状态栏(设计 §12.6;终端界面视觉设计 v1 §3.10):编辑器下方一行,永远是最后一行。[B7]
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* 两区:左区「权限模式 · shift+tab 切换」,右区用量,中间用空格撑开;空隙不足 4 列时退回单区(` · ` 连接)。
|
|
5
|
+
* `Accept edits · shift+tab 切换 sonnet-4-5 · medium · ↑12.3k ↓1.2k · cache 80% ♨ · $0.12 · ctx 34%`
|
|
6
6
|
*
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* -
|
|
14
|
-
*
|
|
15
|
-
* -
|
|
16
|
-
* -
|
|
17
|
-
* codemode
|
|
7
|
+
* - 右区顺序固定:模型 · 思考级别(只显示值,off 不显示)· `↑` 输入(含缓存读写)`↓` 输出 · cache · 费用 ·
|
|
8
|
+
* rebill · ctx · queue · codemode · 预设(非 default 才显示)· [宿主状态];分隔符固定为 ` · `,
|
|
9
|
+
* 模式永远在最左(tmux 宿主可按此解析)。
|
|
10
|
+
* - 用量来自 `session.getStats()`,只在 `refresh()` 时读取(事件驱动),渲染只拼字符串;
|
|
11
|
+
* - [W3-C2] `cache` 是**最近一次**请求的命中率;端点三态 `cache —` / `cache 未报告`;保温中追加 `♨`;
|
|
12
|
+
* 重计费 `rebill $x`(无价模型显示 token);codemode 生效时 `codemode on|only`,网络未隔离追加 `net!`;
|
|
13
|
+
* - 着色:整行 dim;模式名正文色(Bypass permissions warning、Plan accent);模型 accent;ctx 按阈值
|
|
14
|
+
* success / warning / error;rebill、queue warning;`net!` error。宽 ≥ 110 时 ctx 用余量表。
|
|
15
|
+
* - 模型名缩写:宽 < 100 去掉供应商前缀,< 60 再去掉 `@渠道`,< 48 去掉版本后缀(第一个 `-数字` 起)。
|
|
16
|
+
* - 宽度不够时按优先级丢弃(数字大的先丢):切换提示 12 → 宿主 11 → 预设 10 → rebill 9 → 费用 8 →
|
|
17
|
+
* cache 7 → token 6 → 思考 5 → 队列 4 → codemode 3 → ctx 1 → 模型 0;模式永不丢。
|
|
18
18
|
*/
|
|
19
19
|
import type { AgentSession, SessionCacheStats } from "../../agent/types.js";
|
|
20
20
|
import { type Component, type Theme } from "../../tui.js";
|
|
@@ -32,7 +32,9 @@ export interface StatusBarSource {
|
|
|
32
32
|
export declare function formatTokens(count: number): string;
|
|
33
33
|
export declare function formatCost(cost: number): string;
|
|
34
34
|
/** `cache 83%` / `cache —` / `cache 未报告`,保温计时中追加 ` ♨`。 */
|
|
35
|
-
export declare function cacheText(cache: SessionCacheStats): string;
|
|
35
|
+
export declare function cacheText(cache: SessionCacheStats, warm?: string): string;
|
|
36
|
+
/** 按终端宽度缩写模型名(§3.10)。 */
|
|
37
|
+
export declare function abbreviateModel(ref: string, width: number): string;
|
|
36
38
|
export declare class StatusBar implements Component {
|
|
37
39
|
private readonly source;
|
|
38
40
|
private readonly theme;
|
|
@@ -43,6 +45,7 @@ export declare class StatusBar implements Component {
|
|
|
43
45
|
refresh(): void;
|
|
44
46
|
setQueue(steering: number, followUp: number): void;
|
|
45
47
|
private parts;
|
|
48
|
+
private ctxText;
|
|
46
49
|
render(width: number): string[];
|
|
47
50
|
invalidate(): void;
|
|
48
51
|
}
|