@armadra/agent 0.2.1 → 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 +99 -0
- package/README.md +142 -49
- package/dist/agent/retry.d.ts +1 -1
- package/dist/agent/retry.js +2 -1
- package/dist/agent/session-cache.js +6 -3
- 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-state.js +2 -1
- package/dist/agent/session-sync.js +9 -2
- package/dist/agent/session-tools.js +24 -3
- package/dist/agent/session.d.ts +3 -0
- package/dist/agent/session.js +20 -10
- 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 +5 -3
- package/dist/ai/cache/fingerprint.d.ts +1 -1
- package/dist/ai/cache/fingerprint.js +1 -1
- package/dist/ai/cache/reporting.js +2 -1
- package/dist/ai/http.d.ts +28 -6
- package/dist/ai/http.js +41 -8
- package/dist/ai/providers/channels.d.ts +51 -0
- package/dist/ai/providers/channels.js +96 -0
- package/dist/ai/providers/enrich.d.ts +34 -0
- package/dist/ai/providers/enrich.js +86 -0
- package/dist/ai/providers/models-dev-cache.d.ts +53 -0
- package/dist/ai/providers/models-dev-cache.js +147 -0
- package/dist/ai/providers/models-dev.d.ts +99 -0
- package/dist/ai/providers/models-dev.js +315 -0
- package/dist/ai/providers/registry.d.ts +42 -4
- package/dist/ai/providers/registry.js +195 -34
- 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 +30 -3
- package/dist/bundle/ama.cjs +17893 -10698
- package/dist/cli/args.d.ts +23 -5
- package/dist/cli/args.js +113 -13
- package/dist/cli/bootstrap.js +28 -3
- package/dist/cli/codemode-notice.d.ts +20 -0
- package/dist/cli/codemode-notice.js +55 -0
- package/dist/cli/compose-providers.js +3 -0
- package/dist/cli/compose-session.d.ts +8 -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 +17 -5
- package/dist/cli/compose.js +53 -19
- package/dist/cli/default-model.d.ts +38 -1
- package/dist/cli/default-model.js +96 -9
- package/dist/cli/deps.d.ts +38 -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 +10 -2
- package/dist/cli/main.js +88 -2
- 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 +20 -13
- package/dist/cli/subcommands/config.d.ts +29 -4
- package/dist/cli/subcommands/config.js +188 -25
- package/dist/cli/subcommands/context.js +5 -1
- package/dist/cli/subcommands/doctor.js +12 -1
- package/dist/cli/subcommands/init.d.ts +6 -0
- package/dist/cli/subcommands/init.js +23 -0
- package/dist/cli/subcommands/model-meta.d.ts +16 -0
- package/dist/cli/subcommands/model-meta.js +54 -0
- package/dist/cli/subcommands/models-cache-probe.js +1 -1
- package/dist/cli/subcommands/models-discover.d.ts +18 -6
- package/dist/cli/subcommands/models-discover.js +87 -52
- package/dist/cli/subcommands/models.js +26 -18
- package/dist/cli/subcommands/probe-runner.d.ts +96 -0
- package/dist/cli/subcommands/probe-runner.js +264 -0
- package/dist/cli/subcommands/providers-list.d.ts +8 -0
- package/dist/cli/subcommands/providers-list.js +97 -0
- package/dist/cli/subcommands/providers-plan.d.ts +79 -0
- package/dist/cli/subcommands/providers-plan.js +215 -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 +28 -0
- package/dist/cli/subcommands/providers.js +436 -0
- 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 +34 -0
- package/dist/config/init.js +99 -0
- package/dist/config/json-schema.d.ts +15 -0
- package/dist/config/json-schema.js +215 -0
- 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 +97 -15
- package/dist/config/types.d.ts +57 -11
- package/dist/config/types.js +19 -1
- package/dist/modes/commands-core.js +6 -5
- package/dist/modes/image-input.d.ts +27 -0
- package/dist/modes/image-input.js +78 -0
- 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 +58 -19
- package/dist/modes/interactive/interactive-mode.d.ts +2 -1
- package/dist/modes/interactive/interactive-mode.js +70 -82
- 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 +9 -5
- 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/startup-ui.d.ts +7 -2
- package/dist/modes/interactive/startup-ui.js +41 -11
- 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 +33 -4
- package/dist/modes/print/print-mode.js +128 -8
- package/dist/modes/rpc/commands.js +12 -2
- 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/rpc.d.ts +2 -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/projection.js +5 -1
- 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/session/types.d.ts +2 -0
- package/dist/tools/image-file.d.ts +31 -0
- package/dist/tools/image-file.js +114 -0
- package/dist/tools/presets.d.ts +35 -8
- package/dist/tools/presets.js +56 -17
- package/dist/tools/read.d.ts +4 -8
- package/dist/tools/read.js +15 -64
- 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 +241 -3
- package/docs/rpc.md +38 -38
- package/docs/session-format.md +3 -2
- package/docs/sessions.md +134 -0
- package/docs/tui.md +140 -61
- package/package.json +3 -1
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 消息区面板(终端界面视觉设计 v1 §3.13):`/session`、`/cache`、`/permissions` 用左竖条卡片 + 键值表
|
|
3
|
+
* 渲染,而不是把纯文本拍进通知。line 模式与 RPC 仍用 session-report 的文本版。
|
|
4
|
+
*
|
|
5
|
+
* - `/session`:标题 `会话 <id 前 8 位> · <文件>`;模型 / 消息 / 用量 / 上下文(余量表,阈值着色),
|
|
6
|
+
* 空一行后「缓存」段(与 `/cache` 共用 `cacheRows`);
|
|
7
|
+
* - `/permissions`:权限模式、判定顺序(折行对齐值列)、规则(allow success、deny error、来源 dim)、
|
|
8
|
+
* 最近的 auto 判定。
|
|
9
|
+
*/
|
|
10
|
+
import { AUTO_LAYER_TEXT, permissionModeLabel } from "../../permissions/modes.js";
|
|
11
|
+
import { Card, KeyValue, Meter, } from "../../tui.js";
|
|
12
|
+
import { cacheRows, formatTokenCount, formatUsd } from "../session-report.js";
|
|
13
|
+
import { tildePath } from "../../cli/startup-screen.js";
|
|
14
|
+
/** 多段内容按顺序拼起来(段之间不加空行,由调用方放空串)。 */
|
|
15
|
+
class Stack {
|
|
16
|
+
parts;
|
|
17
|
+
constructor(parts) {
|
|
18
|
+
this.parts = parts;
|
|
19
|
+
}
|
|
20
|
+
render(width) {
|
|
21
|
+
return this.parts.flatMap((part) => (typeof part === "string" ? [part] : part.render(width)));
|
|
22
|
+
}
|
|
23
|
+
invalidate() {
|
|
24
|
+
for (const part of this.parts)
|
|
25
|
+
if (typeof part !== "string")
|
|
26
|
+
part.invalidate();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** 缩进若干列的子组件。 */
|
|
30
|
+
class Indent {
|
|
31
|
+
child;
|
|
32
|
+
columns;
|
|
33
|
+
constructor(child, columns) {
|
|
34
|
+
this.child = child;
|
|
35
|
+
this.columns = columns;
|
|
36
|
+
}
|
|
37
|
+
render(width) {
|
|
38
|
+
const pad = " ".repeat(this.columns);
|
|
39
|
+
return this.child
|
|
40
|
+
.render(Math.max(1, width - this.columns))
|
|
41
|
+
.map((line) => (line === "" ? "" : pad + line));
|
|
42
|
+
}
|
|
43
|
+
invalidate() {
|
|
44
|
+
this.child.invalidate();
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function keyValue(rows, theme, wrap = false) {
|
|
48
|
+
return new KeyValue(rows, { theme, maxKeyRatio: 0.3, ...(wrap ? { wrap: true } : {}) });
|
|
49
|
+
}
|
|
50
|
+
function cacheSection(session, theme, now) {
|
|
51
|
+
return [theme.bold("缓存"), new Indent(keyValue(cacheRows(session, now), theme), 2)];
|
|
52
|
+
}
|
|
53
|
+
export function sessionPanel(session, theme, options = {}) {
|
|
54
|
+
const now = options.now ?? Date.now();
|
|
55
|
+
const state = session.state;
|
|
56
|
+
const stats = session.getStats();
|
|
57
|
+
const t = stats.tokens;
|
|
58
|
+
const sep = theme.fg("dim", " · ");
|
|
59
|
+
const model = state.model === undefined ? "?" : `${state.model.provider}/${state.model.id}`;
|
|
60
|
+
const ratio = stats.contextPercent === undefined ? undefined : stats.contextPercent / 100;
|
|
61
|
+
const meter = new Meter(ratio, { theme }).render(40)[0] ?? "";
|
|
62
|
+
const window = stats.contextTokens === undefined && stats.contextWindow === undefined
|
|
63
|
+
? ""
|
|
64
|
+
: sep +
|
|
65
|
+
`${stats.contextTokens === undefined ? "?" : formatTokenCount(stats.contextTokens)} / ` +
|
|
66
|
+
`${stats.contextWindow === undefined ? "?" : formatTokenCount(stats.contextWindow)}`;
|
|
67
|
+
const rows = [
|
|
68
|
+
{
|
|
69
|
+
key: "模型",
|
|
70
|
+
value: theme.fg("accent", model) +
|
|
71
|
+
sep +
|
|
72
|
+
`思考 ${state.thinkingLevel}` +
|
|
73
|
+
sep +
|
|
74
|
+
`权限 ${permissionModeLabel(state.permissionMode)}`,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
key: "消息",
|
|
78
|
+
value: [
|
|
79
|
+
`用户 ${stats.userMessages}`,
|
|
80
|
+
`助手 ${stats.assistantMessages}`,
|
|
81
|
+
`工具调用 ${stats.toolCalls}`,
|
|
82
|
+
].join(sep),
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
key: "用量",
|
|
86
|
+
value: [
|
|
87
|
+
`输入 ${formatTokenCount(t.input)}`,
|
|
88
|
+
`输出 ${formatTokenCount(t.output)}`,
|
|
89
|
+
`缓存读 ${formatTokenCount(t.cacheRead)}`,
|
|
90
|
+
`缓存写 ${formatTokenCount(t.cacheWrite)}`,
|
|
91
|
+
formatUsd(stats.cost),
|
|
92
|
+
].join(sep),
|
|
93
|
+
},
|
|
94
|
+
{ key: "上下文", value: meter + window },
|
|
95
|
+
];
|
|
96
|
+
const file = state.sessionFile === undefined ? "未落盘" : tildePath(state.sessionFile, options.home);
|
|
97
|
+
return new Card(new Stack([keyValue(rows, theme), "", ...cacheSection(session, theme, now)]), {
|
|
98
|
+
theme,
|
|
99
|
+
title: `会话 ${state.sessionId.slice(0, 8)}`,
|
|
100
|
+
subtitle: file,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
export function cachePanel(session, theme, now = Date.now()) {
|
|
104
|
+
return new Card(keyValue(cacheRows(session, now), theme), { theme, title: "缓存" });
|
|
105
|
+
}
|
|
106
|
+
const DECISION_COLOR = {
|
|
107
|
+
allow: "success",
|
|
108
|
+
deny: "error",
|
|
109
|
+
ask: "warning",
|
|
110
|
+
};
|
|
111
|
+
export function permissionsPanel(runtime, session, theme) {
|
|
112
|
+
const rules = runtime.permission.rules;
|
|
113
|
+
const mode = session.state.permissionMode;
|
|
114
|
+
const order = mode === "auto"
|
|
115
|
+
? "deny 规则 → Hook deny → 危险命令确认 → 规则层(受保护路径、项目外写入、网络、删除类)→ Hook ask → allow 规则 / Hook allow / 本会话记忆 → 静态判定(只读、项目内写入、安全名单)→ 模型分类器 → 询问"
|
|
116
|
+
: mode === "allowlist"
|
|
117
|
+
? "deny 规则 → Hook deny → 危险命令(拒绝)→ 只读工具 / allow 规则 / Hook allow 放行 → 其余拒绝(从不询问)"
|
|
118
|
+
: "deny 规则 → Hook deny → 危险命令确认 → 权限模式 → allow 规则 / Hook allow / 本会话记忆 → 询问";
|
|
119
|
+
const effect = (decision) => theme.fg(DECISION_COLOR[decision] ?? "text", decision.padEnd(5));
|
|
120
|
+
const parts = [
|
|
121
|
+
keyValue([
|
|
122
|
+
{ key: "权限模式", value: `${permissionModeLabel(mode)}${theme.fg("dim", `(${mode})`)}` },
|
|
123
|
+
{ key: "判定顺序", value: order },
|
|
124
|
+
], theme, true),
|
|
125
|
+
theme.bold(rules.length === 0 ? "规则(无)" : `规则(${rules.length})`),
|
|
126
|
+
];
|
|
127
|
+
if (rules.length > 0) {
|
|
128
|
+
parts.push(new Indent(keyValue(rules.map((r) => ({
|
|
129
|
+
key: effect(r.effect),
|
|
130
|
+
value: `${r.raw} ${theme.fg("dim", `[${r.source}]`)}`,
|
|
131
|
+
})), theme), 2));
|
|
132
|
+
}
|
|
133
|
+
const recent = runtime.permission.autoDecisions?.() ?? [];
|
|
134
|
+
if (recent.length > 0) {
|
|
135
|
+
parts.push(theme.bold(`最近的 auto 判定(${recent.length})`));
|
|
136
|
+
parts.push(new Indent(keyValue(recent.map((d) => ({
|
|
137
|
+
key: AUTO_LAYER_TEXT[d.layer],
|
|
138
|
+
value: `${effect(d.decision)} ${d.toolName} ${d.summary} — ${d.reason}` +
|
|
139
|
+
(d.cached === true ? theme.fg("dim", "(缓存)") : ""),
|
|
140
|
+
})), theme), 2));
|
|
141
|
+
}
|
|
142
|
+
return new Card(new Stack(parts), { theme });
|
|
143
|
+
}
|
|
@@ -4,8 +4,11 @@
|
|
|
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
|
*/
|
|
11
|
+
import type { PermissionMode } from "../../permissions/types.js";
|
|
9
12
|
import type { SessionEntry } from "../../session/types.js";
|
|
10
13
|
import { type Keybindings, type OverlayHandle, type OverlayOptions, type SelectItem, type Theme, type Component } from "../../tui.js";
|
|
11
14
|
export { modelItems, sessionItems } from "./startup-ui.js";
|
|
@@ -23,10 +26,28 @@ export interface PickerSpec {
|
|
|
23
26
|
filterable?: boolean;
|
|
24
27
|
maxVisible?: number;
|
|
25
28
|
emptyText?: string;
|
|
29
|
+
/** 右侧序号 1–9,按数字直接选中。 */
|
|
30
|
+
numberKeys?: boolean;
|
|
31
|
+
/** 说明放在标签下一行。 */
|
|
32
|
+
stacked?: boolean;
|
|
33
|
+
/** 当前值(标 ✓)。 */
|
|
34
|
+
currentValue?: string;
|
|
35
|
+
/** 列表下的按键提示,缺省「↑↓ 选择 · Enter 确认 · Esc 取消」。 */
|
|
36
|
+
footer?: string;
|
|
37
|
+
/** 提示行前带 (i/n)。 */
|
|
38
|
+
showCount?: boolean;
|
|
39
|
+
/** 框内上下留白行数。 */
|
|
40
|
+
paddingY?: number;
|
|
26
41
|
}
|
|
42
|
+
export declare const PICKER_FOOTER = "\u2191\u2193 \u9009\u62E9 \u00B7 Enter \u786E\u8BA4 \u00B7 Esc \u53D6\u6D88";
|
|
27
43
|
/** 打开一个居中选择器;Enter 返回选中项,Esc / Ctrl+C 返回 undefined。 */
|
|
28
44
|
export declare function openPicker(host: PickerHost, spec: PickerSpec): Promise<SelectItem | undefined>;
|
|
29
|
-
|
|
45
|
+
/**
|
|
46
|
+
* 模式选择器(标题「权限模式」):界面顺序、当前模式打勾(`currentValue`)、配置里的缺省模式标
|
|
47
|
+
* `Default`、Auto 标 `Recommended`,右侧数字快捷键 1–6,说明在下一行。
|
|
48
|
+
*/
|
|
49
|
+
export declare function permissionItems(_current: PermissionMode, configDefault?: PermissionMode): SelectItem[];
|
|
50
|
+
export declare function permissionPickerSpec(current: PermissionMode, configDefault?: PermissionMode): PickerSpec;
|
|
30
51
|
export declare function thinkingItems(reasoning: boolean): SelectItem[];
|
|
31
52
|
export interface TreeNode {
|
|
32
53
|
entry: SessionEntry;
|
|
@@ -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
|
+
}
|