@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
|
@@ -11,29 +11,33 @@
|
|
|
11
11
|
* - 晚绑定:`runtime.approvals.setUiBroker`(审批对话框)与 `runtime.notifier.set`(宿主通知进消息区),
|
|
12
12
|
* 退出时撤下。
|
|
13
13
|
* - 键位:Enter 发送(运行中 = steer),其余应用级键位见 `key-dispatch.ts`。
|
|
14
|
-
* -
|
|
14
|
+
* - 启动头按 `ui.quietStartup`(startup-header.ts):normal 框 + 模型 / 目录 / 模式 / 资源清单(窄屏或
|
|
15
|
+
* `ui.compact` 去框),header 一行,silent 不输出。
|
|
15
16
|
*/
|
|
17
|
+
import { promptImages, sessionModel } from "../image-input.js";
|
|
16
18
|
import { join } from "node:path";
|
|
17
19
|
import { AgentSessionImpl } from "../../agent/session.js";
|
|
18
20
|
import { currentSession, switchSession } from "../../cli/compose-session.js";
|
|
19
|
-
import {
|
|
21
|
+
import { startupInfo, startupScreenLevel } from "../../cli/startup-screen.js";
|
|
20
22
|
import { KEYBINDINGS_FILE } from "../../config/paths.js";
|
|
21
23
|
import { AmaError, isAmaError } from "../../errors.js";
|
|
22
24
|
import { detectSandboxCapability } from "../../codemode/capability.js";
|
|
23
25
|
import { effectiveCodemodeMode } from "../../tools/presets.js";
|
|
24
|
-
import { Container, Editor, Keybindings, Loader, ProcessTerminal, Spacer, TUI, Text, createTheme, detectCapabilities, loadKeybindingsFile, truncateToWidth, } from "../../tui.js";
|
|
26
|
+
import { Container, Editor, Keybindings, Loader, ProcessTerminal, Spacer, TUI, Text, createTheme, detectCapabilities, resolveAscii, resolveThemeName, loadKeybindingsFile, truncateToWidth, } from "../../tui.js";
|
|
25
27
|
import { cacheEventNotice, cacheNoticesEnabled } from "../session-report.js";
|
|
26
28
|
import { onTerminationSignals } from "../shared.js";
|
|
27
29
|
import { ApprovalDialogBroker, approvalOutcomeText } from "./approval-dialog.js";
|
|
28
30
|
import { ALL_COMMANDS, runInteractiveCommand } from "./commands.js";
|
|
29
31
|
import { InteractiveCompletion } from "./completion.js";
|
|
30
32
|
import { createKeyDispatch } from "./key-dispatch.js";
|
|
31
|
-
import { MessageView } from "./message-view.js";
|
|
33
|
+
import { MessageView, exitSummaryLines } from "./message-view.js";
|
|
32
34
|
import { openPicker } from "./pickers.js";
|
|
35
|
+
import { StartupHeader } from "./startup-header.js";
|
|
36
|
+
import { QueueView, RunIndicator } from "./run-indicator.js";
|
|
33
37
|
import { StatusBar } from "./status-bar.js";
|
|
34
38
|
import { ToolTracker } from "./tool-view.js";
|
|
35
39
|
const HINT_MS = 2500;
|
|
36
|
-
const
|
|
40
|
+
const EDITOR_PLACEHOLDER = "输入消息,/ 命令,@ 文件,Shift+Enter 换行";
|
|
37
41
|
function processTerminal() {
|
|
38
42
|
const stdin = process.stdin;
|
|
39
43
|
if (stdin.isTTY !== true || typeof stdin.setRawMode !== "function" || !process.stdout.isTTY) {
|
|
@@ -58,17 +62,17 @@ class HintLine {
|
|
|
58
62
|
}
|
|
59
63
|
invalidate() { }
|
|
60
64
|
}
|
|
61
|
-
function oneLine(text) {
|
|
62
|
-
return text.replace(/\s+/g, " ").trim();
|
|
63
|
-
}
|
|
64
65
|
export function runInteractiveMode(runtime, context, options = {}) {
|
|
65
66
|
const terminal = options.terminal ?? processTerminal();
|
|
66
67
|
const ui = runtime.config.ui ?? {};
|
|
68
|
+
const env = context.io.env;
|
|
67
69
|
const theme = options.theme ??
|
|
68
|
-
createTheme(ui.theme
|
|
69
|
-
caps: detectCapabilities(
|
|
70
|
+
createTheme(resolveThemeName(ui.theme, env), {
|
|
71
|
+
caps: detectCapabilities(env, context.io.stdoutIsTTY),
|
|
72
|
+
ascii: resolveAscii(ui.ascii, env),
|
|
70
73
|
});
|
|
71
74
|
const now = options.now ?? Date.now;
|
|
75
|
+
const startedAt = now();
|
|
72
76
|
const startupWarnings = [];
|
|
73
77
|
const keys = options.keybindings ?? loadKeys(runtime, (m) => startupWarnings.push(m));
|
|
74
78
|
let session = currentSession(runtime);
|
|
@@ -77,20 +81,25 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
77
81
|
theme,
|
|
78
82
|
...(ui.showThinking !== undefined ? { showThinking: ui.showThinking } : {}),
|
|
79
83
|
...(ui.markdown !== undefined ? { markdown: ui.markdown } : {}),
|
|
84
|
+
...(ui.compact === true ? { compact: true } : {}),
|
|
80
85
|
});
|
|
81
86
|
const tools = new ToolTracker({
|
|
82
87
|
theme,
|
|
83
88
|
cwd: session.state.cwd,
|
|
84
89
|
getTool: (name) => session.getTools().find((tool) => tool.name === name),
|
|
90
|
+
now,
|
|
91
|
+
spinner: () => loader.frame,
|
|
85
92
|
});
|
|
86
|
-
const
|
|
93
|
+
const queueView = new QueueView(theme);
|
|
87
94
|
const loaderSlot = new Container();
|
|
88
95
|
const loader = new Loader(() => tui.requestRender(), {
|
|
89
96
|
theme,
|
|
90
|
-
message: "
|
|
97
|
+
message: "思考中",
|
|
91
98
|
now,
|
|
99
|
+
...(ui.animation === false ? { animation: false } : {}),
|
|
92
100
|
...(options.spinnerIntervalMs !== undefined ? { intervalMs: options.spinnerIntervalMs } : {}),
|
|
93
101
|
});
|
|
102
|
+
loader.onFrame(() => tools.tick());
|
|
94
103
|
const hint = new HintLine();
|
|
95
104
|
let sandboxStrict;
|
|
96
105
|
const status = new StatusBar({
|
|
@@ -118,6 +127,7 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
118
127
|
theme,
|
|
119
128
|
keybindings: keys,
|
|
120
129
|
maxVisibleLines: 8,
|
|
130
|
+
placeholder: EDITOR_PLACEHOLDER,
|
|
121
131
|
autocomplete: completion,
|
|
122
132
|
requestRender: () => tui.requestRender(),
|
|
123
133
|
...(historyFile !== undefined ? { historyFile } : {}),
|
|
@@ -125,15 +135,13 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
125
135
|
});
|
|
126
136
|
tui.addChild(view);
|
|
127
137
|
tui.addChild(new Spacer());
|
|
128
|
-
tui.addChild(
|
|
138
|
+
tui.addChild(queueView);
|
|
129
139
|
tui.addChild(loaderSlot);
|
|
130
140
|
tui.addChild(editor);
|
|
131
141
|
tui.addChild(hint);
|
|
132
142
|
tui.addChild(status);
|
|
133
143
|
// ---- 小工具 ---------------------------------------------------------------
|
|
134
144
|
let finished = false;
|
|
135
|
-
let running = false;
|
|
136
|
-
let compacting = false;
|
|
137
145
|
let hintTimer;
|
|
138
146
|
let resolveExit = () => undefined;
|
|
139
147
|
const render = () => tui.requestRender();
|
|
@@ -155,47 +163,20 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
155
163
|
}
|
|
156
164
|
render();
|
|
157
165
|
};
|
|
158
|
-
const
|
|
159
|
-
const active = running || compacting;
|
|
160
|
-
if (active && loaderSlot.children.length === 0) {
|
|
161
|
-
loader.setMessage((compacting && !running ? "压缩上下文" : "工作中") + theme.fg("dim", " · Esc 中断"));
|
|
162
|
-
loaderSlot.addChild(loader);
|
|
163
|
-
loader.start();
|
|
164
|
-
}
|
|
165
|
-
else if (!active && loaderSlot.children.length > 0) {
|
|
166
|
-
loader.stop();
|
|
167
|
-
loaderSlot.clear();
|
|
168
|
-
}
|
|
169
|
-
render();
|
|
170
|
-
};
|
|
166
|
+
const indicator = new RunIndicator({ theme, loader, slot: loaderSlot, tools, render });
|
|
171
167
|
const setQueue = (steering, followUp) => {
|
|
172
168
|
status.setQueue(steering.length, followUp.length);
|
|
173
|
-
|
|
174
|
-
...steering.map((t) => `↳ steer ${oneLine(t)}`),
|
|
175
|
-
...followUp.map((t) => `↳ followUp ${oneLine(t)}`),
|
|
176
|
-
];
|
|
177
|
-
const shown = rows.slice(-QUEUE_PREVIEW);
|
|
178
|
-
if (rows.length > shown.length)
|
|
179
|
-
shown.unshift(`… 另 ${rows.length - shown.length} 条`);
|
|
180
|
-
if (rows.length > 0)
|
|
181
|
-
shown.push("Alt+↑ 取回 · Esc 全部回填并中断");
|
|
182
|
-
queueText.setText(shown.map((l) => theme.fg("dim", l)).join("\n"));
|
|
169
|
+
queueView.setQueue(steering, followUp);
|
|
183
170
|
render();
|
|
184
171
|
};
|
|
185
172
|
// ---- 会话事件 -------------------------------------------------------------
|
|
186
173
|
const onEvent = (event) => {
|
|
187
174
|
switch (event.type) {
|
|
188
|
-
case "agent_start":
|
|
189
|
-
running = true;
|
|
190
|
-
syncLoader();
|
|
191
|
-
return;
|
|
192
175
|
case "agent_settled":
|
|
193
|
-
running = false;
|
|
194
176
|
if (event.warning !== undefined)
|
|
195
177
|
view.addNotice("warn", event.warning);
|
|
196
178
|
status.refresh();
|
|
197
|
-
|
|
198
|
-
return;
|
|
179
|
+
break;
|
|
199
180
|
case "message_start": {
|
|
200
181
|
const message = event.message;
|
|
201
182
|
if (message.role === "user")
|
|
@@ -205,26 +186,22 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
205
186
|
else if (message.role === "custom" && message.display) {
|
|
206
187
|
view.addNotice("info", typeof message.content === "string" ? message.content : "");
|
|
207
188
|
}
|
|
208
|
-
|
|
209
|
-
return;
|
|
189
|
+
break;
|
|
210
190
|
}
|
|
211
191
|
case "message_update":
|
|
212
192
|
view.updateAssistant(event.message);
|
|
213
|
-
|
|
214
|
-
return;
|
|
193
|
+
break;
|
|
215
194
|
case "message_end":
|
|
216
195
|
if (event.message.role === "assistant") {
|
|
217
196
|
view.endAssistant(event.message);
|
|
218
197
|
status.refresh();
|
|
219
198
|
}
|
|
220
|
-
|
|
221
|
-
return;
|
|
199
|
+
break;
|
|
222
200
|
case "tool_execution_start": {
|
|
223
201
|
const started = tools.start(event);
|
|
224
202
|
if (started.topLevel)
|
|
225
|
-
view.
|
|
226
|
-
|
|
227
|
-
return;
|
|
203
|
+
view.addTool(started.view);
|
|
204
|
+
break;
|
|
228
205
|
}
|
|
229
206
|
case "tool_execution_update":
|
|
230
207
|
tools.update(event.toolCallId, event.partial);
|
|
@@ -232,17 +209,11 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
232
209
|
return;
|
|
233
210
|
case "tool_execution_end":
|
|
234
211
|
tools.end(event.toolCallId, event.result, event.isError);
|
|
235
|
-
|
|
236
|
-
return;
|
|
212
|
+
break;
|
|
237
213
|
case "queue_update":
|
|
238
214
|
setQueue(event.steering, event.followUp);
|
|
239
215
|
return;
|
|
240
|
-
case "compaction_start":
|
|
241
|
-
compacting = true;
|
|
242
|
-
syncLoader();
|
|
243
|
-
return;
|
|
244
216
|
case "compaction_end":
|
|
245
|
-
compacting = false;
|
|
246
217
|
if (event.result !== undefined)
|
|
247
218
|
view.addCompaction(event.result);
|
|
248
219
|
else if (event.error !== undefined)
|
|
@@ -250,17 +221,14 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
250
221
|
else if (event.aborted)
|
|
251
222
|
view.addNotice("info", "压缩已取消");
|
|
252
223
|
status.refresh();
|
|
253
|
-
|
|
254
|
-
return;
|
|
224
|
+
break;
|
|
255
225
|
case "auto_retry_start":
|
|
256
226
|
view.addRetry(event.attempt, event.maxAttempts, event.delayMs, event.errorMessage);
|
|
257
|
-
|
|
258
|
-
return;
|
|
227
|
+
break;
|
|
259
228
|
case "auto_retry_end":
|
|
260
229
|
if (!event.success)
|
|
261
230
|
view.addRetryFailed(event.finalError);
|
|
262
|
-
|
|
263
|
-
return;
|
|
231
|
+
break;
|
|
264
232
|
case "cache_miss":
|
|
265
233
|
case "context_pressure": {
|
|
266
234
|
const shown = cacheEventNotice(event, cacheNoticesEnabled(session));
|
|
@@ -279,15 +247,21 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
279
247
|
render();
|
|
280
248
|
return;
|
|
281
249
|
default:
|
|
282
|
-
|
|
250
|
+
break;
|
|
283
251
|
}
|
|
252
|
+
indicator.onEvent(event);
|
|
253
|
+
render();
|
|
284
254
|
};
|
|
285
255
|
let unsubscribe = session.subscribe(onEvent);
|
|
286
|
-
const
|
|
287
|
-
|
|
256
|
+
const startupLevel = startupScreenLevel(runtime);
|
|
257
|
+
const home = env["HOME"] ?? env["USERPROFILE"];
|
|
258
|
+
const info = startupInfo(runtime, home);
|
|
259
|
+
const header = (level) => new StartupHeader(info, { theme, level, ...(ui.compact === true ? { compact: true } : {}) });
|
|
260
|
+
/** 清空消息区(切换会话、/tree):只留一行头。 */
|
|
288
261
|
const resetView = () => {
|
|
289
262
|
view.reset();
|
|
290
|
-
|
|
263
|
+
if (startupLevel !== "silent")
|
|
264
|
+
view.add(header("header"));
|
|
291
265
|
};
|
|
292
266
|
const replay = () => {
|
|
293
267
|
view.replay(session.messages, {
|
|
@@ -307,11 +281,9 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
307
281
|
resetView();
|
|
308
282
|
tools.clear();
|
|
309
283
|
replay();
|
|
310
|
-
running = false;
|
|
311
|
-
compacting = false;
|
|
312
284
|
setQueue([], []);
|
|
313
285
|
status.refresh();
|
|
314
|
-
|
|
286
|
+
indicator.reset();
|
|
315
287
|
if (next instanceof AgentSessionImpl)
|
|
316
288
|
next.announceStart(reason);
|
|
317
289
|
};
|
|
@@ -324,7 +296,10 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
324
296
|
const target = session;
|
|
325
297
|
void target
|
|
326
298
|
.waitForIdle()
|
|
327
|
-
.then(() =>
|
|
299
|
+
.then(async () => {
|
|
300
|
+
const images = await promptImages(text, [], target.state.cwd, sessionModel(runtime.providers, target));
|
|
301
|
+
return target.prompt(text, images.length > 0 ? { images } : {});
|
|
302
|
+
})
|
|
328
303
|
.catch((error) => {
|
|
329
304
|
if (isAmaError(error) && error.code === "busy") {
|
|
330
305
|
void target.followUp(text).catch(() => undefined);
|
|
@@ -352,6 +327,13 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
352
327
|
columns: () => terminal.columns,
|
|
353
328
|
}, spec),
|
|
354
329
|
notice,
|
|
330
|
+
panel: (component) => {
|
|
331
|
+
view.add(component);
|
|
332
|
+
render();
|
|
333
|
+
},
|
|
334
|
+
theme: () => theme,
|
|
335
|
+
env,
|
|
336
|
+
...(home !== undefined ? { home } : {}),
|
|
355
337
|
setEditorText: (text) => {
|
|
356
338
|
editor.setText(text);
|
|
357
339
|
render();
|
|
@@ -384,7 +366,7 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
384
366
|
send(text, via);
|
|
385
367
|
}
|
|
386
368
|
function send(text, via) {
|
|
387
|
-
if (!running)
|
|
369
|
+
if (!indicator.running)
|
|
388
370
|
startPrompt(text);
|
|
389
371
|
else if (via === "followUp")
|
|
390
372
|
void session.followUp(text).catch(() => undefined);
|
|
@@ -399,9 +381,10 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
399
381
|
status,
|
|
400
382
|
session: () => session,
|
|
401
383
|
inactive: () => finished || tui.hasOverlay,
|
|
402
|
-
busy: () =>
|
|
384
|
+
busy: () => indicator.busy,
|
|
403
385
|
now,
|
|
404
386
|
showHint,
|
|
387
|
+
onExpandToggle: (expanded) => view.setThinkingExpanded(expanded),
|
|
405
388
|
submit: (text, via) => submit(text, via),
|
|
406
389
|
runCommand: (line) => void runCommand(line),
|
|
407
390
|
exit: (code) => exit(code),
|
|
@@ -415,11 +398,13 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
415
398
|
showOverlay: (component) => tui.showOverlay(component, { anchor: "bottom" }),
|
|
416
399
|
onOpen: () => {
|
|
417
400
|
editor.disableSubmit = true;
|
|
418
|
-
|
|
401
|
+
tools.setAwaiting(true);
|
|
402
|
+
indicator.setApproval(true);
|
|
419
403
|
},
|
|
420
404
|
onClose: () => {
|
|
421
405
|
editor.disableSubmit = false;
|
|
422
|
-
|
|
406
|
+
tools.setAwaiting(false);
|
|
407
|
+
indicator.setApproval(false);
|
|
423
408
|
},
|
|
424
409
|
report: (request, outcome) => {
|
|
425
410
|
if (outcome === "deny" || outcome === "cancelled") {
|
|
@@ -435,9 +420,11 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
435
420
|
finished = true;
|
|
436
421
|
if (hintTimer !== undefined)
|
|
437
422
|
clearTimeout(hintTimer);
|
|
438
|
-
hint.setText("");
|
|
439
423
|
loader.stop();
|
|
440
|
-
|
|
424
|
+
// 退出摘要留在回滚里;输入框、状态栏等底部区域撤掉,屏幕停在摘要下面
|
|
425
|
+
view.addExitSummary(exitSummaryLines(session.getStats(), now() - startedAt, theme));
|
|
426
|
+
tui.clear();
|
|
427
|
+
tui.addChild(view);
|
|
441
428
|
offSignals();
|
|
442
429
|
runtime.approvals.setUiBroker(undefined);
|
|
443
430
|
runtime.notifier.set(undefined);
|
|
@@ -451,7 +438,8 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
451
438
|
resolveExit(code);
|
|
452
439
|
});
|
|
453
440
|
}
|
|
454
|
-
|
|
441
|
+
if (startupLevel !== "silent")
|
|
442
|
+
view.add(header(startupLevel));
|
|
455
443
|
for (const warning of startupWarnings)
|
|
456
444
|
view.addNotice("warn", warning);
|
|
457
445
|
replay();
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 编辑器之前的输入监听:Ctrl+C 清空输入 / 再按退出、Esc 中断(clearQueue 回填编辑器后 abort)、
|
|
5
5
|
* Ctrl+D 空输入退出、Alt+Enter followUp、Alt+↑ 取回最后一条排队消息、Shift+Tab 循环权限模式、
|
|
6
|
-
* Ctrl+O
|
|
6
|
+
* Ctrl+O 展开工具输出与思考块、Ctrl+L 模型、Ctrl+T 思考级别。键位由 `keybindings.json` 覆盖。
|
|
7
7
|
*/
|
|
8
8
|
import type { AgentSession } from "../../agent/types.js";
|
|
9
9
|
import type { Editor, Keybindings } from "../../tui.js";
|
|
@@ -21,6 +21,8 @@ export interface KeyDispatchDeps {
|
|
|
21
21
|
busy(): boolean;
|
|
22
22
|
now(): number;
|
|
23
23
|
showHint(text: string): void;
|
|
24
|
+
/** Ctrl+O 之后(消息区思考块跟着展开 / 折叠)。 */
|
|
25
|
+
onExpandToggle?(expanded: boolean): void;
|
|
24
26
|
submit(text: string, via: "followUp"): void;
|
|
25
27
|
runCommand(line: string): void;
|
|
26
28
|
exit(code: number): void;
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 编辑器之前的输入监听:Ctrl+C 清空输入 / 再按退出、Esc 中断(clearQueue 回填编辑器后 abort)、
|
|
5
5
|
* Ctrl+D 空输入退出、Alt+Enter followUp、Alt+↑ 取回最后一条排队消息、Shift+Tab 循环权限模式、
|
|
6
|
-
* Ctrl+O
|
|
6
|
+
* Ctrl+O 展开工具输出与思考块、Ctrl+L 模型、Ctrl+T 思考级别。键位由 `keybindings.json` 覆盖。
|
|
7
7
|
*/
|
|
8
8
|
import { ExitCode } from "../../cli/exit-codes.js";
|
|
9
|
-
import {
|
|
9
|
+
import { nextCycleMode, permissionModeLabel } from "../../permissions/modes.js";
|
|
10
10
|
const DOUBLE_CTRL_C_MS = 1500;
|
|
11
11
|
/** 返回输入监听器:已处理返回 true,交给编辑器返回 false。 */
|
|
12
12
|
export function createKeyDispatch(deps) {
|
|
@@ -39,12 +39,10 @@ export function createKeyDispatch(deps) {
|
|
|
39
39
|
};
|
|
40
40
|
const cyclePermission = () => {
|
|
41
41
|
const session = deps.session();
|
|
42
|
-
const
|
|
43
|
-
const index = modes.indexOf(session.state.permissionMode);
|
|
44
|
-
const next = modes[(index + 1) % modes.length];
|
|
42
|
+
const next = nextCycleMode(session.state.permissionMode);
|
|
45
43
|
session.setPermissionMode(next);
|
|
46
44
|
status.refresh();
|
|
47
|
-
deps.showHint(`权限模式:${next}`);
|
|
45
|
+
deps.showHint(`权限模式:${permissionModeLabel(next)}`);
|
|
48
46
|
};
|
|
49
47
|
return (data) => {
|
|
50
48
|
if (deps.inactive())
|
|
@@ -91,6 +89,7 @@ export function createKeyDispatch(deps) {
|
|
|
91
89
|
}
|
|
92
90
|
if (is("app.tools.expand")) {
|
|
93
91
|
const expanded = tools.toggleExpanded();
|
|
92
|
+
deps.onExpandToggle?.(expanded);
|
|
94
93
|
deps.showHint(expanded ? "工具输出:展开" : "工具输出:折叠");
|
|
95
94
|
return true;
|
|
96
95
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* = steer;Ctrl+C 运行中中断、空闲时连按两次退出(130);空行 Ctrl+D 退出;审批 y / a / N 单键问答。
|
|
6
6
|
* 问句之前逐行打印执行前预览(`request.preview`,W3-B9a-2)。
|
|
7
7
|
* - 否则(管道):逐行读 stdin,每行依次执行(等上一条运行结束);没有审批 UI,ask → deny。
|
|
8
|
+
* 模型错误在运行结束时只打印一次(重试中只显示 ↻);有运行最终失败时退出码 1。
|
|
8
9
|
* - 斜杠命令走 commands-core(与 B7 同一语义),`pick` 退化为列出候选。
|
|
9
10
|
*/
|
|
10
11
|
import type { ModeContext } from "../../../cli/deps.js";
|
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
* = steer;Ctrl+C 运行中中断、空闲时连按两次退出(130);空行 Ctrl+D 退出;审批 y / a / N 单键问答。
|
|
6
6
|
* 问句之前逐行打印执行前预览(`request.preview`,W3-B9a-2)。
|
|
7
7
|
* - 否则(管道):逐行读 stdin,每行依次执行(等上一条运行结束);没有审批 UI,ask → deny。
|
|
8
|
+
* 模型错误在运行结束时只打印一次(重试中只显示 ↻);有运行最终失败时退出码 1。
|
|
8
9
|
* - 斜杠命令走 commands-core(与 B7 同一语义),`pick` 退化为列出候选。
|
|
9
10
|
*/
|
|
11
|
+
import { promptImages, sessionModel } from "../../image-input.js";
|
|
10
12
|
import { currentSession, switchSession } from "../../../cli/compose-session.js";
|
|
11
13
|
import { ExitCode } from "../../../cli/exit-codes.js";
|
|
12
14
|
import { previewDisplayLines } from "../../../permissions/preview.js";
|
|
@@ -67,16 +69,18 @@ export async function runLineMode(runtime, context, options = {}) {
|
|
|
67
69
|
}
|
|
68
70
|
catch (error) {
|
|
69
71
|
printer.endLine();
|
|
72
|
+
printer.failures++;
|
|
70
73
|
err(`ama: ${errorText(error)}\n`);
|
|
71
74
|
}
|
|
72
75
|
return undefined;
|
|
73
76
|
};
|
|
74
77
|
const prompt = async (target, text) => {
|
|
75
|
-
await target.
|
|
78
|
+
const images = await promptImages(text, [], target.state.cwd, sessionModel(runtime.providers, target));
|
|
79
|
+
await target.prompt(text, images.length > 0 ? { images } : {});
|
|
76
80
|
printer.endLine();
|
|
77
81
|
};
|
|
78
82
|
if (!raw)
|
|
79
|
-
return runPiped(stdin, context, handle, () => session, () => unsubscribe());
|
|
83
|
+
return runPiped(stdin, context, handle, () => session, () => unsubscribe(), () => printer.failures > 0);
|
|
80
84
|
// ---- raw 终端 ----
|
|
81
85
|
const tty = stdin;
|
|
82
86
|
editor = new LineEditor({ prompt: "› ", write: (t) => io.stdout(t) });
|
|
@@ -180,8 +184,8 @@ export async function runLineMode(runtime, context, options = {}) {
|
|
|
180
184
|
ed.render();
|
|
181
185
|
});
|
|
182
186
|
}
|
|
183
|
-
/** 管道:逐行依次执行;stdin
|
|
184
|
-
function runPiped(stdin, context, handle, session, unsubscribe) {
|
|
187
|
+
/** 管道:逐行依次执行;stdin 结束后等最后一条跑完退出——有失败的运行时 1,否则 0。 */
|
|
188
|
+
function runPiped(stdin, context, handle, session, unsubscribe, failed) {
|
|
185
189
|
return new Promise((resolve) => {
|
|
186
190
|
let chain = Promise.resolve(undefined);
|
|
187
191
|
let finished = false;
|
|
@@ -203,7 +207,7 @@ function runPiped(stdin, context, handle, session, unsubscribe) {
|
|
|
203
207
|
});
|
|
204
208
|
};
|
|
205
209
|
const reader = createLineReader(stdin, enqueue, () => {
|
|
206
|
-
void chain.then(() => finish(ExitCode.Ok));
|
|
210
|
+
void chain.then(() => finish(failed() ? ExitCode.RuntimeError : ExitCode.Ok));
|
|
207
211
|
});
|
|
208
212
|
const offSignals = onTerminationSignals((code) => {
|
|
209
213
|
void session()
|
|
@@ -23,6 +23,10 @@ export declare class EventPrinter {
|
|
|
23
23
|
private readonly err;
|
|
24
24
|
private readonly options;
|
|
25
25
|
private midLine;
|
|
26
|
+
/** 本次运行的模型错误:等 agent_settled 再打(重试中的失败尝试只显示 ↻ 那一行)。 */
|
|
27
|
+
private pendingError;
|
|
28
|
+
/** 最终失败的运行次数(管道模式据此返回退出码 1)。 */
|
|
29
|
+
failures: number;
|
|
26
30
|
constructor(out: (text: string) => void, err: (text: string) => void, options?: EventPrinterOptions);
|
|
27
31
|
private line;
|
|
28
32
|
/** 收尾:保证光标在行首。 */
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { THINKING_LEVELS } from "../../../ai/thinking.js";
|
|
6
6
|
import { listSessions } from "../../../cli/compose-store.js";
|
|
7
|
-
import {
|
|
7
|
+
import { AUTO_LAYER_TEXT, permissionModeLines } from "../../../permissions/modes.js";
|
|
8
8
|
import { cacheEventNotice, warmSentNotice } from "../../session-report.js";
|
|
9
9
|
const SUMMARY_KEYS = ["command", "path", "pattern", "file_path", "url", "description", "name"];
|
|
10
10
|
/** 工具参数的一行摘要(第一个常见字段,折叠换行、截到 80 字符)。 */
|
|
@@ -24,11 +24,14 @@ export function argsSummary(args) {
|
|
|
24
24
|
export function approvalQuestion(request) {
|
|
25
25
|
const task = (request.context?.depth ?? 0) > 0 ? "[task] " : "";
|
|
26
26
|
const summary = argsSummary(request.input);
|
|
27
|
+
const auto = request.autoDecision;
|
|
27
28
|
const why = request.reason === "dangerous"
|
|
28
29
|
? "(危险命令)"
|
|
29
30
|
: request.hookReason !== undefined
|
|
30
31
|
? `(${request.hookReason})`
|
|
31
|
-
:
|
|
32
|
+
: auto !== undefined
|
|
33
|
+
? `(Auto ${AUTO_LAYER_TEXT[auto.layer]}:${auto.reason})`
|
|
34
|
+
: "";
|
|
32
35
|
return `${task}允许 ${request.toolName}${summary !== "" ? ` ${summary}` : ""}${why}?[y 允许 / a 本会话都允许 / N 拒绝] `;
|
|
33
36
|
}
|
|
34
37
|
/** `AMA_LOG=info|debug`。 */
|
|
@@ -40,6 +43,10 @@ export class EventPrinter {
|
|
|
40
43
|
err;
|
|
41
44
|
options;
|
|
42
45
|
midLine = false;
|
|
46
|
+
/** 本次运行的模型错误:等 agent_settled 再打(重试中的失败尝试只显示 ↻ 那一行)。 */
|
|
47
|
+
pendingError;
|
|
48
|
+
/** 最终失败的运行次数(管道模式据此返回退出码 1)。 */
|
|
49
|
+
failures = 0;
|
|
43
50
|
constructor(out, err, options = {}) {
|
|
44
51
|
this.out = out;
|
|
45
52
|
this.err = err;
|
|
@@ -72,9 +79,13 @@ export class EventPrinter {
|
|
|
72
79
|
if (event.message.role === "assistant") {
|
|
73
80
|
this.endLine();
|
|
74
81
|
if (event.message.stopReason === "error")
|
|
75
|
-
this.
|
|
82
|
+
this.pendingError = event.message.errorMessage ?? "模型调用失败";
|
|
76
83
|
}
|
|
77
84
|
return;
|
|
85
|
+
case "agent_end":
|
|
86
|
+
if (event.willRetry)
|
|
87
|
+
this.pendingError = undefined;
|
|
88
|
+
return;
|
|
78
89
|
case "tool_execution_start": {
|
|
79
90
|
const summary = argsSummary(event.args);
|
|
80
91
|
this.line(`● ${event.toolName}${summary !== "" ? ` ${summary}` : ""}`);
|
|
@@ -112,11 +123,19 @@ export class EventPrinter {
|
|
|
112
123
|
this.line(`ama: ${text}`, true);
|
|
113
124
|
return;
|
|
114
125
|
}
|
|
115
|
-
case "agent_settled":
|
|
126
|
+
case "agent_settled": {
|
|
116
127
|
this.endLine();
|
|
117
|
-
|
|
128
|
+
const error = this.pendingError;
|
|
129
|
+
this.pendingError = undefined;
|
|
130
|
+
if (error !== undefined) {
|
|
131
|
+
this.failures++;
|
|
132
|
+
this.line(`ama: 错误:${error}`, true);
|
|
133
|
+
}
|
|
134
|
+
// 失败时 warning 就是同一条错误文本,不再重复打印
|
|
135
|
+
if (event.warning !== undefined && event.warning !== error)
|
|
118
136
|
this.line(`ama: ${event.warning}`, true);
|
|
119
137
|
return;
|
|
138
|
+
}
|
|
120
139
|
default:
|
|
121
140
|
return;
|
|
122
141
|
}
|
|
@@ -164,7 +183,10 @@ export async function pickHint(what, runtime, session) {
|
|
|
164
183
|
].join("\n");
|
|
165
184
|
}
|
|
166
185
|
case "permission":
|
|
167
|
-
return
|
|
186
|
+
return [
|
|
187
|
+
"Mode(/permission <模式>):",
|
|
188
|
+
...permissionModeLines(session.state.permissionMode, runtime.config.permission?.mode ?? "default").map((line) => ` ${line}`),
|
|
189
|
+
].join("\n");
|
|
168
190
|
case "thinking":
|
|
169
191
|
return `思考级别(/thinking <级别>):${THINKING_LEVELS.join(" | ")};当前 ${session.state.thinkingLevel}`;
|
|
170
192
|
}
|