@armadra/agent 0.9.0 → 0.9.2
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 +14 -0
- package/CHANGELOG.zh-CN.md +14 -0
- package/dist/bundle/ama.cjs +25 -17
- package/dist/drivers/acp/client.d.ts +11 -2
- package/dist/drivers/acp/client.js +8 -4
- package/dist/i18n/messages/config-keys.js +2 -2
- package/dist/i18n/messages/interactive.js +2 -2
- package/dist/i18n/messages/report.js +2 -2
- package/dist/modes/interactive/agent-view.d.ts +1 -1
- package/dist/modes/interactive/agent-view.js +1 -1
- package/dist/modes/interactive/interrupt-send.d.ts +1 -1
- package/dist/modes/interactive/interrupt-send.js +1 -1
- package/dist/modes/interactive/key-dispatch.d.ts +1 -1
- package/dist/modes/interactive/key-dispatch.js +1 -1
- package/dist/modes/interactive/run-indicator.d.ts +4 -4
- package/dist/modes/interactive/run-indicator.js +3 -3
- package/dist/tui/keybindings.d.ts +5 -4
- package/dist/tui/keybindings.js +5 -4
- package/dist/tui/terminal.d.ts +11 -1
- package/dist/tui/terminal.js +13 -3
- package/dist/tui.d.ts +1 -1
- package/dist/tui.js +1 -1
- package/docs/en/guides/tui.md +20 -6
- package/docs/en/reference/acp.md +3 -2
- package/docs/guides/tui.md +20 -6
- package/docs/reference/acp.md +7 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ English · [简体中文](CHANGELOG.zh-CN.md)
|
|
|
5
5
|
> This file is in English starting with 0.6.0. Release notes for 0.1 through 0.5.1 are in Chinese in
|
|
6
6
|
> [CHANGELOG.zh-CN.md](CHANGELOG.zh-CN.md). New entries go into both files.
|
|
7
7
|
|
|
8
|
+
## 0.9.2 (2026-10-22)
|
|
9
|
+
|
|
10
|
+
Version aligned with Armadra and armadra-cloud (all three ship 0.9.2); protocol constants unchanged.
|
|
11
|
+
|
|
12
|
+
### TUI
|
|
13
|
+
|
|
14
|
+
- **Interrupt-and-send moves to `Ctrl+Enter`, `Ctrl+X` stays as the fallback**: `app.message.interrupt` now defaults to `["ctrl+enter", "ctrl+x"]`; the hint line, `/help` and the `/interrupt` description show `Ctrl+Enter`. To make `Ctrl+Enter` distinguishable from Enter, the real terminal now writes the kitty keyboard protocol disambiguate flag and xterm modifyOtherKeys level 1 at start (enable sequences only, no query, so no replies mix into input) and restores them when it stops; `AMA_KEYBOARD_PROTOCOL=off` disables it. kitty, Ghostty and iTerm2 work out of the box; macOS Terminal, Windows Terminal and the xterm.js terminal node cannot tell it from Enter, so use `Ctrl+X` there. Custom `app.message.interrupt` bindings keep working. Docs: docs/guides/tui.md (#221).
|
|
15
|
+
|
|
16
|
+
## 0.9.1 (2026-10-21)
|
|
17
|
+
|
|
18
|
+
### ACP
|
|
19
|
+
|
|
20
|
+
- **`AcpClient` can serve `terminal/*`**: the new `onTerminal(method, params, signal)` constructor option makes `initialize` declare `clientCapabilities.terminal: true` and hands `terminal/create|output|wait_for_exit|kill|release` to the host; without it nothing changes on the wire. `AcpClient.features.terminal` is `true` (#218).
|
|
21
|
+
|
|
8
22
|
## 0.9.0 (2026-10-20)
|
|
9
23
|
|
|
10
24
|
Version aligned with Armadra and armadra-cloud (all three products now share 0.9.0). No protocol constants changed.
|
package/CHANGELOG.zh-CN.md
CHANGED
|
@@ -4,6 +4,20 @@
|
|
|
4
4
|
|
|
5
5
|
> 从 0.6.0 起 [CHANGELOG.md](CHANGELOG.md) 为英文,本文件保留中文记录(0.1–0.5.1 的完整历史在此)。新条目两份都要加。
|
|
6
6
|
|
|
7
|
+
## 0.9.2(2026-10-22)
|
|
8
|
+
|
|
9
|
+
版本号与 Armadra、armadra-cloud 对齐(三个产品统一为 0.9.2),协议常量无变化。
|
|
10
|
+
|
|
11
|
+
### TUI
|
|
12
|
+
|
|
13
|
+
- **打断并发送改为 `Ctrl+Enter`,`Ctrl+X` 作回退**:`app.message.interrupt` 缺省 `["ctrl+enter", "ctrl+x"]`;提示行、`/help` 与 `/interrupt` 说明显示 `Ctrl+Enter`。为让终端把 `Ctrl+Enter` 与 Enter 分开,真实终端启动时写入 kitty 键盘协议 disambiguate 标志与 xterm modifyOtherKeys 级别 1(只写开启序列、不查询,没有回包混进输入),停止时恢复;`AMA_KEYBOARD_PROTOCOL=off` 可关。kitty、Ghostty、iTerm2 开箱可用;macOS Terminal、Windows Terminal、xterm.js 终端节点分不出它与 Enter,用 `Ctrl+X`。自定义的 `app.message.interrupt` 继续生效。文档:docs/guides/tui.md(#221)。
|
|
14
|
+
|
|
15
|
+
## 0.9.1(2026-10-21)
|
|
16
|
+
|
|
17
|
+
### ACP
|
|
18
|
+
|
|
19
|
+
- **`AcpClient` 可接 `terminal/*`**:构造参数新增 `onTerminal(method, params, signal)`,给了它 `initialize` 才声明 `clientCapabilities.terminal: true`,并把 `terminal/create|output|wait_for_exit|kill|release` 交给宿主;不给时线路不变。`AcpClient.features.terminal` 为 `true`(#218)。
|
|
20
|
+
|
|
7
21
|
## 0.9.0(2026-10-20)
|
|
8
22
|
|
|
9
23
|
版本号与 Armadra、armadra-cloud 对齐(三个产品统一为 0.9.0),协议常量无变化。
|
package/dist/bundle/ama.cjs
CHANGED
|
@@ -41,7 +41,7 @@ var AMA_VERSION;
|
|
|
41
41
|
var init_version = __esm({
|
|
42
42
|
"src/version.ts"() {
|
|
43
43
|
"use strict";
|
|
44
|
-
AMA_VERSION = true ? "0.9.
|
|
44
|
+
AMA_VERSION = true ? "0.9.2" : readPackageVersion();
|
|
45
45
|
}
|
|
46
46
|
});
|
|
47
47
|
|
|
@@ -1891,7 +1891,7 @@ var init_interactive = __esm({
|
|
|
1891
1891
|
keyHints: [
|
|
1892
1892
|
"Enter send (while running = steer) Alt+Enter queue after this turn Shift+Enter / Ctrl+J newline",
|
|
1893
1893
|
"Esc interrupt (queued messages go back to the editor) Alt+↑ take back the last queued message",
|
|
1894
|
-
"Ctrl+
|
|
1894
|
+
"Ctrl+Enter (while running; Ctrl+X where the terminal cannot tell it from Enter) interrupt the current turn and send the input now, queued steers first; ui.enterWhileRunning: interrupt swaps it with Enter",
|
|
1895
1895
|
"Idle Esc Esc: empty input = rewind (/rewind), with text = clear (↑ to restore)",
|
|
1896
1896
|
"Shift+Tab / Tab (empty input) cycle permission mode (confirm before Bypass) Ctrl+L model Ctrl+T thinking level Ctrl+O expand tool output and thinking",
|
|
1897
1897
|
"Approval: 1–3 or ↑↓ Enter to choose, y allow a allow for this session n / Esc deny v full input",
|
|
@@ -2031,7 +2031,7 @@ var init_interactive = __esm({
|
|
|
2031
2031
|
keyHints: [
|
|
2032
2032
|
"Enter 发送(运行中 = 插话) Alt+Enter 排到本轮之后 Shift+Enter / Ctrl+J 换行",
|
|
2033
2033
|
"Esc 中断(排队消息回填编辑器) Alt+↑ 取回最后一条排队消息",
|
|
2034
|
-
"Ctrl+X
|
|
2034
|
+
"Ctrl+Enter(运行中;终端分不出它与 Enter 时用 Ctrl+X)打断当前回合并立即发送输入(排队的插话在前);ui.enterWhileRunning: interrupt 时与 Enter 互换",
|
|
2035
2035
|
"空闲时 Esc Esc:输入框为空 = 回滚(/rewind),有字 = 清空(↑ 取回)",
|
|
2036
2036
|
"Shift+Tab / Tab(输入为空时)切换权限模式(进入 Bypass 前确认) Ctrl+L 模型 Ctrl+T 思考级别 Ctrl+O 展开工具输出与思考",
|
|
2037
2037
|
"审批:1–3 或 ↑↓ Enter 选择,y 允许 a 本会话允许同类 n / Esc 拒绝 v 完整输入",
|
|
@@ -3120,7 +3120,7 @@ var init_report = __esm({
|
|
|
3120
3120
|
agents: "Sub-agent types and external agents (installed, version)",
|
|
3121
3121
|
paste: "Paste an image from the clipboard (Ctrl+V)",
|
|
3122
3122
|
interruptArgs: "<text>",
|
|
3123
|
-
interrupt: "Interrupt the current turn and send this now, queued steers first (Ctrl+
|
|
3123
|
+
interrupt: "Interrupt the current turn and send this now, queued steers first (Ctrl+Enter)",
|
|
3124
3124
|
exit: "Exit",
|
|
3125
3125
|
config: "Settings panel; key=value sets one item directly (user level)",
|
|
3126
3126
|
traceArgs: "[task id]",
|
|
@@ -3370,7 +3370,7 @@ Available: ${inactive.join(", ")}` : ""}`,
|
|
|
3370
3370
|
agents: "子 Agent 类型与外部 Agent(安装状态、版本)",
|
|
3371
3371
|
paste: "粘贴剪贴板里的图片(Ctrl+V)",
|
|
3372
3372
|
interruptArgs: "<文本>",
|
|
3373
|
-
interrupt: "打断当前回合并立即发送这段文字,排队的插话在前(Ctrl+
|
|
3373
|
+
interrupt: "打断当前回合并立即发送这段文字,排队的插话在前(Ctrl+Enter)",
|
|
3374
3374
|
exit: "退出",
|
|
3375
3375
|
config: "设置面板;key=value 直接设一项(用户级)",
|
|
3376
3376
|
traceArgs: "[任务 id]",
|
|
@@ -3758,7 +3758,7 @@ var init_config_keys = __esm({
|
|
|
3758
3758
|
"ui.logo": "AMA logo in the startup header: auto draws it when the terminal is at least 48 columns wide, off shows only the info lines",
|
|
3759
3759
|
"ui.animation": "false: the spinner stays still while running and redraws only when seconds change; the startup logo does not animate",
|
|
3760
3760
|
"ui.restoreOnCancel": "When Esc interrupts a run before any reply or tool call in this turn, withdraw the turn and put the message back into the input box",
|
|
3761
|
-
"ui.enterWhileRunning": "Enter while a run is in progress: queue (default) queues the message as a steer delivered at the next delivery point; interrupt stops the current turn and sends it at once. The app.message.interrupt key (default Ctrl+X) does the other one",
|
|
3761
|
+
"ui.enterWhileRunning": "Enter while a run is in progress: queue (default) queues the message as a steer delivered at the next delivery point; interrupt stops the current turn and sends it at once. The app.message.interrupt key (default Ctrl+Enter, fallback Ctrl+X) does the other one",
|
|
3762
3762
|
"ui.programStatus": "Report run state to the terminal with OSC 7501 (idle / working / blocked / done / error): auto (default) sends only after the terminal answers the detection query (off inside tmux), on sends without detecting (inside tmux wrapped in passthrough; needs allow-passthrough on), off never sends. Interactive TUI only",
|
|
3763
3763
|
"ui.statusLine": "Bottom info line: full shows two lines (rate line + status line), compact one; toggle at runtime with Ctrl+G or /statusline (effective from wave 5 W5-A)",
|
|
3764
3764
|
"ui.language": "Interface language: auto decides from LC_ALL / LC_MESSAGES / LANG (zh* is Chinese, anything else English), zh, en; AMA_LANG and --lang override; affects the interface only, text sent to the model is always English",
|
|
@@ -3887,7 +3887,7 @@ var init_config_keys = __esm({
|
|
|
3887
3887
|
"ui.logo": "启动头的 AMA 字符画:auto 在终端宽 48 列以上时画,off 只显示信息行",
|
|
3888
3888
|
"ui.animation": "false:运行中 spinner 静止,只在秒数变化时重绘;启动字符画不播放动画",
|
|
3889
3889
|
"ui.restoreOnCancel": "运行中 Esc 中断、本回合还没有任何回复或工具调用时,撤回该回合并把原消息放回输入框",
|
|
3890
|
-
"ui.enterWhileRunning": "运行中按 Enter:queue(缺省)排队插话,等下一个投递点送达;interrupt 打断当前回合并立即发送。app.message.interrupt 键(缺省 Ctrl+X)做另一种",
|
|
3890
|
+
"ui.enterWhileRunning": "运行中按 Enter:queue(缺省)排队插话,等下一个投递点送达;interrupt 打断当前回合并立即发送。app.message.interrupt 键(缺省 Ctrl+Enter,回退 Ctrl+X)做另一种",
|
|
3891
3891
|
"ui.programStatus": "用 OSC 7501 把运行状态报告给终端(idle / working / blocked / done / error):auto(缺省)终端回应检测查询后才发(tmux 里不发),on 不检测直接发(tmux 里经 passthrough 包裹,需要 allow-passthrough on),off 不发。只用于交互界面",
|
|
3892
3892
|
"ui.statusLine": "底部信息行:full 两行(速率行 + 状态行),compact 一行;运行时 Ctrl+G 或 /statusline 切换(第五波 W5-A 起生效)",
|
|
3893
3893
|
"ui.language": "界面语言:auto 按 LC_ALL / LC_MESSAGES / LANG 判断(zh* 为中文,其余英文),zh,en;AMA_LANG、--lang 覆盖;只影响界面,发给模型的文本固定英文",
|
|
@@ -33890,9 +33890,10 @@ var init_client = __esm({
|
|
|
33890
33890
|
* 本版客户端支持的可选能力,供宿主做特性检测(旧版没有这个字段,旧一点的只有 `mcpServers`):
|
|
33891
33891
|
* - `mcpServers`:开会话时可经 {@link AcpSessionOptions} 传 MCP 服务器;
|
|
33892
33892
|
* - `elicitation`:构造参数 `onElicitation` 接 `elicitation/create`;
|
|
33893
|
-
* - `configOptions`:`setConfigOption` 与开会话答的 `configOptions
|
|
33893
|
+
* - `configOptions`:`setConfigOption` 与开会话答的 `configOptions`;
|
|
33894
|
+
* - `terminal`:构造参数 `onTerminal` 接 `terminal/*`。
|
|
33894
33895
|
*/
|
|
33895
|
-
static features = { mcpServers: true, elicitation: true, configOptions: true };
|
|
33896
|
+
static features = { mcpServers: true, elicitation: true, configOptions: true, terminal: true };
|
|
33896
33897
|
peer;
|
|
33897
33898
|
/** sessionId → 挂起权限请求的取消器。 */
|
|
33898
33899
|
pendingPermissions = /* @__PURE__ */ new Map();
|
|
@@ -33922,7 +33923,7 @@ var init_client = __esm({
|
|
|
33922
33923
|
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
33923
33924
|
clientCapabilities: {
|
|
33924
33925
|
fs: { readTextFile: false, writeTextFile: false },
|
|
33925
|
-
terminal:
|
|
33926
|
+
terminal: this.options.onTerminal !== void 0,
|
|
33926
33927
|
session: { configOptions: {} },
|
|
33927
33928
|
...this.options.onElicitation !== void 0 ? { elicitation: {} } : {}
|
|
33928
33929
|
},
|
|
@@ -34007,6 +34008,8 @@ var init_client = __esm({
|
|
|
34007
34008
|
async onRequest(method, params, connection) {
|
|
34008
34009
|
if (method === ACP_METHODS.elicitationCreate && this.options.onElicitation !== void 0)
|
|
34009
34010
|
return this.onElicitation(params, connection);
|
|
34011
|
+
if (method.startsWith("terminal/") && this.options.onTerminal !== void 0)
|
|
34012
|
+
return this.options.onTerminal(method, params, connection);
|
|
34010
34013
|
if (method !== ACP_METHODS.requestPermission)
|
|
34011
34014
|
throw new RpcError(RPC_ERRORS.methodNotFound, `client does not support ${method}`);
|
|
34012
34015
|
const request = params;
|
|
@@ -41531,7 +41534,7 @@ var init_stdin_buffer = __esm({
|
|
|
41531
41534
|
});
|
|
41532
41535
|
|
|
41533
41536
|
// src/tui/terminal.ts
|
|
41534
|
-
var BRACKETED_PASTE_ON, BRACKETED_PASTE_OFF, SHOW_CURSOR, HIDE_CURSOR, ProcessTerminal;
|
|
41537
|
+
var BRACKETED_PASTE_ON, BRACKETED_PASTE_OFF, KEYBOARD_ENHANCE_ON, KEYBOARD_ENHANCE_OFF, SHOW_CURSOR, HIDE_CURSOR, ProcessTerminal;
|
|
41535
41538
|
var init_terminal = __esm({
|
|
41536
41539
|
"src/tui/terminal.ts"() {
|
|
41537
41540
|
"use strict";
|
|
@@ -41539,12 +41542,15 @@ var init_terminal = __esm({
|
|
|
41539
41542
|
init_stdin_buffer();
|
|
41540
41543
|
BRACKETED_PASTE_ON = "\x1B[?2004h";
|
|
41541
41544
|
BRACKETED_PASTE_OFF = "\x1B[?2004l";
|
|
41545
|
+
KEYBOARD_ENHANCE_ON = "\x1B[>1u\x1B[>4;1m";
|
|
41546
|
+
KEYBOARD_ENHANCE_OFF = "\x1B[<u\x1B[>4m";
|
|
41542
41547
|
SHOW_CURSOR = "\x1B[?25h";
|
|
41543
41548
|
HIDE_CURSOR = "\x1B[?25l";
|
|
41544
41549
|
ProcessTerminal = class {
|
|
41545
41550
|
stdin;
|
|
41546
41551
|
stdout;
|
|
41547
41552
|
escTimeoutMs;
|
|
41553
|
+
enhancedKeys;
|
|
41548
41554
|
buffer = null;
|
|
41549
41555
|
wasRaw = false;
|
|
41550
41556
|
dataListener = null;
|
|
@@ -41553,6 +41559,7 @@ var init_terminal = __esm({
|
|
|
41553
41559
|
this.stdin = options.stdin ?? process.stdin;
|
|
41554
41560
|
this.stdout = options.stdout ?? process.stdout;
|
|
41555
41561
|
this.escTimeoutMs = options.escTimeoutMs;
|
|
41562
|
+
this.enhancedKeys = options.enhancedKeys ?? process.env["AMA_KEYBOARD_PROTOCOL"]?.toLowerCase() !== "off";
|
|
41556
41563
|
}
|
|
41557
41564
|
get columns() {
|
|
41558
41565
|
return this.stdout.columns || 80;
|
|
@@ -41579,14 +41586,14 @@ var init_terminal = __esm({
|
|
|
41579
41586
|
this.stdin.resume();
|
|
41580
41587
|
this.resizeListener = onResize;
|
|
41581
41588
|
this.stdout.on("resize", this.resizeListener);
|
|
41582
|
-
this.write(BRACKETED_PASTE_ON);
|
|
41589
|
+
this.write(BRACKETED_PASTE_ON + (this.enhancedKeys ? KEYBOARD_ENHANCE_ON : ""));
|
|
41583
41590
|
}
|
|
41584
41591
|
stop() {
|
|
41585
41592
|
if (this.buffer === null) return;
|
|
41586
41593
|
this.buffer.flush();
|
|
41587
41594
|
this.buffer.destroy();
|
|
41588
41595
|
this.buffer = null;
|
|
41589
|
-
this.write(BRACKETED_PASTE_OFF + SHOW_CURSOR);
|
|
41596
|
+
this.write((this.enhancedKeys ? KEYBOARD_ENHANCE_OFF : "") + BRACKETED_PASTE_OFF + SHOW_CURSOR);
|
|
41590
41597
|
if (this.dataListener) this.stdin.off("data", this.dataListener);
|
|
41591
41598
|
if (this.resizeListener) this.stdout.off("resize", this.resizeListener);
|
|
41592
41599
|
this.dataListener = null;
|
|
@@ -42366,11 +42373,12 @@ var init_keybindings = __esm({
|
|
|
42366
42373
|
"app.message.dequeue": ["alt+up"],
|
|
42367
42374
|
/**
|
|
42368
42375
|
* 运行中打断当前回合并立即发送输入框的文字(连同排队的插话);`ui.enterWhileRunning: "interrupt"` 时与
|
|
42369
|
-
* Enter 互换(它变成排队)。缺省 `Ctrl+X
|
|
42370
|
-
*
|
|
42371
|
-
*
|
|
42376
|
+
* Enter 互换(它变成排队)。缺省 `Ctrl+Enter`(提示与帮助显示第一项)+ 回退 `Ctrl+X`:`ProcessTerminal` 启动时
|
|
42377
|
+
* 开增强键盘上报,支持的终端把 Ctrl+Enter 发成 `ESC[13;5u` / `ESC[27;5;13~`;不支持的终端(macOS Terminal、
|
|
42378
|
+
* Windows Terminal 稳定版等)里 Ctrl+Enter 与 Enter 同为 `\r`(或变成 `\n` = Ctrl+J 换行),这时用 `Ctrl+X`
|
|
42379
|
+
* (各终端原样送达 0x18,且没有别的绑定)。`Alt+Enter` 已是 followUp,`Shift+Enter` / `Ctrl+J` 是换行,不占用。
|
|
42372
42380
|
*/
|
|
42373
|
-
"app.message.interrupt": ["ctrl+x"],
|
|
42381
|
+
"app.message.interrupt": ["ctrl+enter", "ctrl+x"],
|
|
42374
42382
|
"app.permission.cycle": ["shift+tab", "tab"],
|
|
42375
42383
|
"app.tools.expand": ["ctrl+o"],
|
|
42376
42384
|
"app.model.select": ["ctrl+l"],
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* JSON-RPC over NDJSON({@link JsonRpcPeer}),手写零依赖;`@armadra/agent/acp` 导出供 Armadra 复用。
|
|
5
5
|
*
|
|
6
|
-
* - 不声明 `fs`
|
|
6
|
+
* - 不声明 `fs` 能力:Agent 发来的 `fs/*` 请求一律 method not found。`terminal` 同理,除非构造参数给了
|
|
7
|
+
* `onTerminal`:此时声明 `terminal: true`,`terminal/*` 请求交给它(ama 自己作客户端不给)。
|
|
7
8
|
* - `session/request_permission` 交给 `onPermission`(驱动再交给 ama 的审批通道,只交给人);
|
|
8
9
|
* 没有处理器时按无人值守回首个 `reject_once`(没有则 cancelled)。
|
|
9
10
|
* - `cancel(sessionId)` 发 `session/cancel` 通知,并让该会话所有挂起的权限请求回 `cancelled`
|
|
@@ -29,6 +30,12 @@ export interface AcpClientHandlers {
|
|
|
29
30
|
* `signal`:该会话被 cancel、连接关闭时 abort(此时回 `{ action: "cancel" }`,不必再答)。
|
|
30
31
|
*/
|
|
31
32
|
onElicitation?(params: AcpElicitationParams, signal: AbortSignal): Promise<AcpElicitationResult>;
|
|
33
|
+
/**
|
|
34
|
+
* `terminal/*`(create / output / wait_for_exit / kill / release):宿主替 Agent 起终端。给了它才声明
|
|
35
|
+
* `clientCapabilities.terminal: true` 并把这些请求交给它;`method` 是完整方法名,`params` 原样交回,返回值即
|
|
36
|
+
* 答复,抛 `RpcError` 回对应错误。`signal`:连接关闭时 abort。权限、cwd 限制与输出上限由宿主负责。
|
|
37
|
+
*/
|
|
38
|
+
onTerminal?(method: string, params: unknown, signal: AbortSignal): Promise<unknown>;
|
|
32
39
|
onProtocolError?(line: string, reason: string): void;
|
|
33
40
|
onClose?(): void;
|
|
34
41
|
}
|
|
@@ -45,12 +52,14 @@ export declare class AcpClient {
|
|
|
45
52
|
* 本版客户端支持的可选能力,供宿主做特性检测(旧版没有这个字段,旧一点的只有 `mcpServers`):
|
|
46
53
|
* - `mcpServers`:开会话时可经 {@link AcpSessionOptions} 传 MCP 服务器;
|
|
47
54
|
* - `elicitation`:构造参数 `onElicitation` 接 `elicitation/create`;
|
|
48
|
-
* - `configOptions`:`setConfigOption` 与开会话答的 `configOptions
|
|
55
|
+
* - `configOptions`:`setConfigOption` 与开会话答的 `configOptions`;
|
|
56
|
+
* - `terminal`:构造参数 `onTerminal` 接 `terminal/*`。
|
|
49
57
|
*/
|
|
50
58
|
static readonly features: {
|
|
51
59
|
readonly mcpServers: true;
|
|
52
60
|
readonly elicitation: true;
|
|
53
61
|
readonly configOptions: true;
|
|
62
|
+
readonly terminal: true;
|
|
54
63
|
};
|
|
55
64
|
private readonly peer;
|
|
56
65
|
/** sessionId → 挂起权限请求的取消器。 */
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* JSON-RPC over NDJSON({@link JsonRpcPeer}),手写零依赖;`@armadra/agent/acp` 导出供 Armadra 复用。
|
|
5
5
|
*
|
|
6
|
-
* - 不声明 `fs`
|
|
6
|
+
* - 不声明 `fs` 能力:Agent 发来的 `fs/*` 请求一律 method not found。`terminal` 同理,除非构造参数给了
|
|
7
|
+
* `onTerminal`:此时声明 `terminal: true`,`terminal/*` 请求交给它(ama 自己作客户端不给)。
|
|
7
8
|
* - `session/request_permission` 交给 `onPermission`(驱动再交给 ama 的审批通道,只交给人);
|
|
8
9
|
* 没有处理器时按无人值守回首个 `reject_once`(没有则 cancelled)。
|
|
9
10
|
* - `cancel(sessionId)` 发 `session/cancel` 通知,并让该会话所有挂起的权限请求回 `cancelled`
|
|
@@ -50,9 +51,10 @@ export class AcpClient {
|
|
|
50
51
|
* 本版客户端支持的可选能力,供宿主做特性检测(旧版没有这个字段,旧一点的只有 `mcpServers`):
|
|
51
52
|
* - `mcpServers`:开会话时可经 {@link AcpSessionOptions} 传 MCP 服务器;
|
|
52
53
|
* - `elicitation`:构造参数 `onElicitation` 接 `elicitation/create`;
|
|
53
|
-
* - `configOptions`:`setConfigOption` 与开会话答的 `configOptions
|
|
54
|
+
* - `configOptions`:`setConfigOption` 与开会话答的 `configOptions`;
|
|
55
|
+
* - `terminal`:构造参数 `onTerminal` 接 `terminal/*`。
|
|
54
56
|
*/
|
|
55
|
-
static features = { mcpServers: true, elicitation: true, configOptions: true };
|
|
57
|
+
static features = { mcpServers: true, elicitation: true, configOptions: true, terminal: true };
|
|
56
58
|
peer;
|
|
57
59
|
/** sessionId → 挂起权限请求的取消器。 */
|
|
58
60
|
pendingPermissions = new Map();
|
|
@@ -105,7 +107,7 @@ export class AcpClient {
|
|
|
105
107
|
protocolVersion: ACP_PROTOCOL_VERSION,
|
|
106
108
|
clientCapabilities: {
|
|
107
109
|
fs: { readTextFile: false, writeTextFile: false },
|
|
108
|
-
terminal:
|
|
110
|
+
terminal: this.options.onTerminal !== undefined,
|
|
109
111
|
session: { configOptions: {} },
|
|
110
112
|
...(this.options.onElicitation !== undefined ? { elicitation: {} } : {}),
|
|
111
113
|
},
|
|
@@ -175,6 +177,8 @@ export class AcpClient {
|
|
|
175
177
|
async onRequest(method, params, connection) {
|
|
176
178
|
if (method === ACP_METHODS.elicitationCreate && this.options.onElicitation !== undefined)
|
|
177
179
|
return this.onElicitation(params, connection);
|
|
180
|
+
if (method.startsWith("terminal/") && this.options.onTerminal !== undefined)
|
|
181
|
+
return this.options.onTerminal(method, params, connection);
|
|
178
182
|
if (method !== ACP_METHODS.requestPermission)
|
|
179
183
|
throw new RpcError(RPC_ERRORS.methodNotFound, `client does not support ${method}`);
|
|
180
184
|
const request = params;
|
|
@@ -106,7 +106,7 @@ const keysEn = {
|
|
|
106
106
|
"ui.logo": "AMA logo in the startup header: auto draws it when the terminal is at least 48 columns wide, off shows only the info lines",
|
|
107
107
|
"ui.animation": "false: the spinner stays still while running and redraws only when seconds change; the startup logo does not animate",
|
|
108
108
|
"ui.restoreOnCancel": "When Esc interrupts a run before any reply or tool call in this turn, withdraw the turn and put the message back into the input box",
|
|
109
|
-
"ui.enterWhileRunning": "Enter while a run is in progress: queue (default) queues the message as a steer delivered at the next delivery point; interrupt stops the current turn and sends it at once. The app.message.interrupt key (default Ctrl+X) does the other one",
|
|
109
|
+
"ui.enterWhileRunning": "Enter while a run is in progress: queue (default) queues the message as a steer delivered at the next delivery point; interrupt stops the current turn and sends it at once. The app.message.interrupt key (default Ctrl+Enter, fallback Ctrl+X) does the other one",
|
|
110
110
|
"ui.programStatus": "Report run state to the terminal with OSC 7501 (idle / working / blocked / done / error): auto (default) sends only after the terminal answers the detection query (off inside tmux), on sends without detecting (inside tmux wrapped in passthrough; needs allow-passthrough on), off never sends. Interactive TUI only",
|
|
111
111
|
"ui.statusLine": "Bottom info line: full shows two lines (rate line + status line), compact one; toggle at runtime with Ctrl+G or /statusline (effective from wave 5 W5-A)",
|
|
112
112
|
"ui.language": "Interface language: auto decides from LC_ALL / LC_MESSAGES / LANG (zh* is Chinese, anything else English), zh, en; AMA_LANG and --lang override; affects the interface only, text sent to the model is always English",
|
|
@@ -235,7 +235,7 @@ const keysZh = {
|
|
|
235
235
|
"ui.logo": "启动头的 AMA 字符画:auto 在终端宽 48 列以上时画,off 只显示信息行",
|
|
236
236
|
"ui.animation": "false:运行中 spinner 静止,只在秒数变化时重绘;启动字符画不播放动画",
|
|
237
237
|
"ui.restoreOnCancel": "运行中 Esc 中断、本回合还没有任何回复或工具调用时,撤回该回合并把原消息放回输入框",
|
|
238
|
-
"ui.enterWhileRunning": "运行中按 Enter:queue(缺省)排队插话,等下一个投递点送达;interrupt 打断当前回合并立即发送。app.message.interrupt 键(缺省 Ctrl+X)做另一种",
|
|
238
|
+
"ui.enterWhileRunning": "运行中按 Enter:queue(缺省)排队插话,等下一个投递点送达;interrupt 打断当前回合并立即发送。app.message.interrupt 键(缺省 Ctrl+Enter,回退 Ctrl+X)做另一种",
|
|
239
239
|
"ui.programStatus": "用 OSC 7501 把运行状态报告给终端(idle / working / blocked / done / error):auto(缺省)终端回应检测查询后才发(tmux 里不发),on 不检测直接发(tmux 里经 passthrough 包裹,需要 allow-passthrough on),off 不发。只用于交互界面",
|
|
240
240
|
"ui.statusLine": "底部信息行:full 两行(速率行 + 状态行),compact 一行;运行时 Ctrl+G 或 /statusline 切换(第五波 W5-A 起生效)",
|
|
241
241
|
"ui.language": "界面语言:auto 按 LC_ALL / LC_MESSAGES / LANG 判断(zh* 为中文,其余英文),zh,en;AMA_LANG、--lang 覆盖;只影响界面,发给模型的文本固定英文",
|
|
@@ -20,7 +20,7 @@ export const en = {
|
|
|
20
20
|
keyHints: [
|
|
21
21
|
"Enter send (while running = steer) Alt+Enter queue after this turn Shift+Enter / Ctrl+J newline",
|
|
22
22
|
"Esc interrupt (queued messages go back to the editor) Alt+↑ take back the last queued message",
|
|
23
|
-
"Ctrl+
|
|
23
|
+
"Ctrl+Enter (while running; Ctrl+X where the terminal cannot tell it from Enter) interrupt the current turn and send the input now, queued steers first; ui.enterWhileRunning: interrupt swaps it with Enter",
|
|
24
24
|
"Idle Esc Esc: empty input = rewind (/rewind), with text = clear (↑ to restore)",
|
|
25
25
|
"Shift+Tab / Tab (empty input) cycle permission mode (confirm before Bypass) Ctrl+L model Ctrl+T thinking level Ctrl+O expand tool output and thinking",
|
|
26
26
|
"Approval: 1–3 or ↑↓ Enter to choose, y allow a allow for this session n / Esc deny v full input",
|
|
@@ -160,7 +160,7 @@ export const zh = {
|
|
|
160
160
|
keyHints: [
|
|
161
161
|
"Enter 发送(运行中 = 插话) Alt+Enter 排到本轮之后 Shift+Enter / Ctrl+J 换行",
|
|
162
162
|
"Esc 中断(排队消息回填编辑器) Alt+↑ 取回最后一条排队消息",
|
|
163
|
-
"Ctrl+X
|
|
163
|
+
"Ctrl+Enter(运行中;终端分不出它与 Enter 时用 Ctrl+X)打断当前回合并立即发送输入(排队的插话在前);ui.enterWhileRunning: interrupt 时与 Enter 互换",
|
|
164
164
|
"空闲时 Esc Esc:输入框为空 = 回滚(/rewind),有字 = 清空(↑ 取回)",
|
|
165
165
|
"Shift+Tab / Tab(输入为空时)切换权限模式(进入 Bypass 前确认) Ctrl+L 模型 Ctrl+T 思考级别 Ctrl+O 展开工具输出与思考",
|
|
166
166
|
"审批:1–3 或 ↑↓ Enter 选择,y 允许 a 本会话允许同类 n / Esc 拒绝 v 完整输入",
|
|
@@ -39,7 +39,7 @@ export const en = {
|
|
|
39
39
|
agents: "Sub-agent types and external agents (installed, version)",
|
|
40
40
|
paste: "Paste an image from the clipboard (Ctrl+V)",
|
|
41
41
|
interruptArgs: "<text>",
|
|
42
|
-
interrupt: "Interrupt the current turn and send this now, queued steers first (Ctrl+
|
|
42
|
+
interrupt: "Interrupt the current turn and send this now, queued steers first (Ctrl+Enter)",
|
|
43
43
|
exit: "Exit",
|
|
44
44
|
config: "Settings panel; key=value sets one item directly (user level)",
|
|
45
45
|
traceArgs: "[task id]",
|
|
@@ -293,7 +293,7 @@ export const zh = {
|
|
|
293
293
|
agents: "子 Agent 类型与外部 Agent(安装状态、版本)",
|
|
294
294
|
paste: "粘贴剪贴板里的图片(Ctrl+V)",
|
|
295
295
|
interruptArgs: "<文本>",
|
|
296
|
-
interrupt: "打断当前回合并立即发送这段文字,排队的插话在前(Ctrl+
|
|
296
|
+
interrupt: "打断当前回合并立即发送这段文字,排队的插话在前(Ctrl+Enter)",
|
|
297
297
|
exit: "退出",
|
|
298
298
|
config: "设置面板;key=value 直接设一项(用户级)",
|
|
299
299
|
traceArgs: "[任务 id]",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* 所查看的任务在等审批时标题显示「等待审批」,审批框照常以覆盖层弹在视图上面。
|
|
19
19
|
* [W7-C] `Ctrl+B`(`app.tasks.background`)转后台正在看的任务(只对前台任务有效,否则落回编辑器);
|
|
20
20
|
* 视图里另认 `/tasks bg [id]`。
|
|
21
|
-
* [打断并发送] `Ctrl+
|
|
21
|
+
* [打断并发送] `Ctrl+Enter`(`app.message.interrupt`,回退 `Ctrl+X`)= 打断该子 Agent 当前回合并立即发送(`registry.message` 的
|
|
22
22
|
* interrupt);外部 Agent 的驱动不能中断回合时退回排队并提示。`ui.enterWhileRunning: "interrupt"` 时与 Enter 互换。
|
|
23
23
|
*/
|
|
24
24
|
import { type Component, type Focusable, type Keybindings, type Theme } from "../../tui.js";
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* 所查看的任务在等审批时标题显示「等待审批」,审批框照常以覆盖层弹在视图上面。
|
|
19
19
|
* [W7-C] `Ctrl+B`(`app.tasks.background`)转后台正在看的任务(只对前台任务有效,否则落回编辑器);
|
|
20
20
|
* 视图里另认 `/tasks bg [id]`。
|
|
21
|
-
* [打断并发送] `Ctrl+
|
|
21
|
+
* [打断并发送] `Ctrl+Enter`(`app.message.interrupt`,回退 `Ctrl+X`)= 打断该子 Agent 当前回合并立即发送(`registry.message` 的
|
|
22
22
|
* interrupt);外部 Agent 的驱动不能中断回合时退回排队并提示。`ui.enterWhileRunning: "interrupt"` 时与 Enter 互换。
|
|
23
23
|
*/
|
|
24
24
|
import { msg } from "../../i18n/index.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 打断并立即发送(交互模式):运行中按 `app.message.interrupt`(缺省 `Ctrl+X`;`ui.enterWhileRunning:
|
|
2
|
+
* 打断并立即发送(交互模式):运行中按 `app.message.interrupt`(缺省 `Ctrl+Enter`,回退 `Ctrl+X`;`ui.enterWhileRunning:
|
|
3
3
|
* "interrupt"` 时是 Enter)→ `prompt(text, { interrupt: true })`:取走排队的插话、中止当前回合(工具按
|
|
4
4
|
* abort 收尾),以「插话… + 本条」开新回合(会话里 origin `interrupt`)。不走「中断即撤回」。
|
|
5
5
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 打断并立即发送(交互模式):运行中按 `app.message.interrupt`(缺省 `Ctrl+X`;`ui.enterWhileRunning:
|
|
2
|
+
* 打断并立即发送(交互模式):运行中按 `app.message.interrupt`(缺省 `Ctrl+Enter`,回退 `Ctrl+X`;`ui.enterWhileRunning:
|
|
3
3
|
* "interrupt"` 时是 Enter)→ `prompt(text, { interrupt: true })`:取走排队的插话、中止当前回合(工具按
|
|
4
4
|
* abort 收尾),以「插话… + 本条」开新回合(会话里 origin `interrupt`)。不走「中断即撤回」。
|
|
5
5
|
*/
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* [W7-C] `app.tasks.background`(缺省 `Ctrl+B`):有阻塞中的前台任务时全部转后台(不看输入框),底部提示
|
|
17
17
|
* 「已转后台:t2 …」;没有时落回编辑器(光标左移),不吞键。Esc 中断只连带前台任务,仍在运行的后台任务
|
|
18
18
|
* 写进中断提示。
|
|
19
|
-
* [打断并发送] 运行中 Enter 缺省排队(steer);`app.message.interrupt`(缺省 `Ctrl+X`)= 打断当前回合并立即
|
|
19
|
+
* [打断并发送] 运行中 Enter 缺省排队(steer);`app.message.interrupt`(缺省 `Ctrl+Enter`,回退 `Ctrl+X`)= 打断当前回合并立即
|
|
20
20
|
* 以输入框的文字(连同排队的插话)开新回合(`prompt{interrupt:true}`)。`ui.enterWhileRunning: "interrupt"`
|
|
21
21
|
* 时两者互换。补全打开、有覆盖层(审批 / 选择器)时不触发;斜杠命令照常当命令;空闲时专用键等同 Enter。
|
|
22
22
|
*/
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* [W7-C] `app.tasks.background`(缺省 `Ctrl+B`):有阻塞中的前台任务时全部转后台(不看输入框),底部提示
|
|
17
17
|
* 「已转后台:t2 …」;没有时落回编辑器(光标左移),不吞键。Esc 中断只连带前台任务,仍在运行的后台任务
|
|
18
18
|
* 写进中断提示。
|
|
19
|
-
* [打断并发送] 运行中 Enter 缺省排队(steer);`app.message.interrupt`(缺省 `Ctrl+X`)= 打断当前回合并立即
|
|
19
|
+
* [打断并发送] 运行中 Enter 缺省排队(steer);`app.message.interrupt`(缺省 `Ctrl+Enter`,回退 `Ctrl+X`)= 打断当前回合并立即
|
|
20
20
|
* 以输入框的文字(连同排队的插话)开新回合(`prompt{interrupt:true}`)。`ui.enterWhileRunning: "interrupt"`
|
|
21
21
|
* 时两者互换。补全打开、有覆盖层(审批 / 选择器)时不触发;斜杠命令照常当命令;空闲时专用键等同 Enter。
|
|
22
22
|
*/
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
* [W7-C] 有阻塞中的前台任务时可丢弃项前面加 `Ctrl+B 转后台`;有停靠的审批时 `↓ Agent 栏` 换成
|
|
15
15
|
* `↓ 处理审批`。顺序 `Esc 中断 · Ctrl+B 转后台 · ↓ Agent 栏`,窄屏从后往前丢。
|
|
16
16
|
*
|
|
17
|
-
* [打断并发送] 输入框有字时可丢弃项最前面加 `Enter 排队 · Ctrl+
|
|
18
|
-
* "interrupt"` 时语义互换),顺序 `Esc 中断 · Enter 排队 · Ctrl+
|
|
17
|
+
* [打断并发送] 输入框有字时可丢弃项最前面加 `Enter 排队 · Ctrl+Enter 打断并发送`(`ui.enterWhileRunning:
|
|
18
|
+
* "interrupt"` 时语义互换),顺序 `Esc 中断 · Enter 排队 · Ctrl+Enter 打断并发送 · Ctrl+B 转后台 · ↓ Agent 栏`。
|
|
19
19
|
*
|
|
20
20
|
* 排队消息:缩进 2 列(挂在当前回合下),`↳ 插话 / 之后 文本` 整体 muted、标签 dim;超过 3 条首行
|
|
21
|
-
* `… 另 N 条`;末行 `Alt+↑ 取回 · Esc 回填并中断`,有插话时中间加 `Ctrl+
|
|
21
|
+
* `… 另 N 条`;末行 `Alt+↑ 取回 · Esc 回填并中断`,有插话时中间加 `Ctrl+Enter 立即发送`(interrupt 模式是 Enter)。
|
|
22
22
|
*/
|
|
23
23
|
import type { SessionEvent } from "../../agent/types.js";
|
|
24
24
|
import { Container, Text, type Loader, type Theme } from "../../tui.js";
|
|
@@ -33,7 +33,7 @@ export interface RunIndicatorDeps {
|
|
|
33
33
|
render(): void;
|
|
34
34
|
/** [W7-C] `app.tasks.background` 的按键标签(`Ctrl+B`);没有绑定时不提示。 */
|
|
35
35
|
backgroundKey?: string;
|
|
36
|
-
/** `app.message.interrupt` 的按键标签(`Ctrl+
|
|
36
|
+
/** `app.message.interrupt` 的按键标签(`Ctrl+Enter`);没有绑定时不提示。 */
|
|
37
37
|
interruptKey?: string;
|
|
38
38
|
}
|
|
39
39
|
/** 运行中 Enter 的语义(`ui.enterWhileRunning`)。 */
|
|
@@ -14,11 +14,11 @@
|
|
|
14
14
|
* [W7-C] 有阻塞中的前台任务时可丢弃项前面加 `Ctrl+B 转后台`;有停靠的审批时 `↓ Agent 栏` 换成
|
|
15
15
|
* `↓ 处理审批`。顺序 `Esc 中断 · Ctrl+B 转后台 · ↓ Agent 栏`,窄屏从后往前丢。
|
|
16
16
|
*
|
|
17
|
-
* [打断并发送] 输入框有字时可丢弃项最前面加 `Enter 排队 · Ctrl+
|
|
18
|
-
* "interrupt"` 时语义互换),顺序 `Esc 中断 · Enter 排队 · Ctrl+
|
|
17
|
+
* [打断并发送] 输入框有字时可丢弃项最前面加 `Enter 排队 · Ctrl+Enter 打断并发送`(`ui.enterWhileRunning:
|
|
18
|
+
* "interrupt"` 时语义互换),顺序 `Esc 中断 · Enter 排队 · Ctrl+Enter 打断并发送 · Ctrl+B 转后台 · ↓ Agent 栏`。
|
|
19
19
|
*
|
|
20
20
|
* 排队消息:缩进 2 列(挂在当前回合下),`↳ 插话 / 之后 文本` 整体 muted、标签 dim;超过 3 条首行
|
|
21
|
-
* `… 另 N 条`;末行 `Alt+↑ 取回 · Esc 回填并中断`,有插话时中间加 `Ctrl+
|
|
21
|
+
* `… 另 N 条`;末行 `Alt+↑ 取回 · Esc 回填并中断`,有插话时中间加 `Ctrl+Enter 立即发送`(interrupt 模式是 Enter)。
|
|
22
22
|
*/
|
|
23
23
|
import { msg } from "../../i18n/index.js";
|
|
24
24
|
import { Container, Text } from "../../tui.js";
|
|
@@ -38,11 +38,12 @@ export declare const DEFAULT_KEYBINDINGS: {
|
|
|
38
38
|
readonly "app.message.dequeue": readonly ["alt+up"];
|
|
39
39
|
/**
|
|
40
40
|
* 运行中打断当前回合并立即发送输入框的文字(连同排队的插话);`ui.enterWhileRunning: "interrupt"` 时与
|
|
41
|
-
* Enter 互换(它变成排队)。缺省 `Ctrl+X
|
|
42
|
-
*
|
|
43
|
-
*
|
|
41
|
+
* Enter 互换(它变成排队)。缺省 `Ctrl+Enter`(提示与帮助显示第一项)+ 回退 `Ctrl+X`:`ProcessTerminal` 启动时
|
|
42
|
+
* 开增强键盘上报,支持的终端把 Ctrl+Enter 发成 `ESC[13;5u` / `ESC[27;5;13~`;不支持的终端(macOS Terminal、
|
|
43
|
+
* Windows Terminal 稳定版等)里 Ctrl+Enter 与 Enter 同为 `\r`(或变成 `\n` = Ctrl+J 换行),这时用 `Ctrl+X`
|
|
44
|
+
* (各终端原样送达 0x18,且没有别的绑定)。`Alt+Enter` 已是 followUp,`Shift+Enter` / `Ctrl+J` 是换行,不占用。
|
|
44
45
|
*/
|
|
45
|
-
readonly "app.message.interrupt": readonly ["ctrl+x"];
|
|
46
|
+
readonly "app.message.interrupt": readonly ["ctrl+enter", "ctrl+x"];
|
|
46
47
|
readonly "app.permission.cycle": readonly ["shift+tab", "tab"];
|
|
47
48
|
readonly "app.tools.expand": readonly ["ctrl+o"];
|
|
48
49
|
readonly "app.model.select": readonly ["ctrl+l"];
|
package/dist/tui/keybindings.js
CHANGED
|
@@ -41,11 +41,12 @@ export const DEFAULT_KEYBINDINGS = {
|
|
|
41
41
|
"app.message.dequeue": ["alt+up"],
|
|
42
42
|
/**
|
|
43
43
|
* 运行中打断当前回合并立即发送输入框的文字(连同排队的插话);`ui.enterWhileRunning: "interrupt"` 时与
|
|
44
|
-
* Enter 互换(它变成排队)。缺省 `Ctrl+X
|
|
45
|
-
*
|
|
46
|
-
*
|
|
44
|
+
* Enter 互换(它变成排队)。缺省 `Ctrl+Enter`(提示与帮助显示第一项)+ 回退 `Ctrl+X`:`ProcessTerminal` 启动时
|
|
45
|
+
* 开增强键盘上报,支持的终端把 Ctrl+Enter 发成 `ESC[13;5u` / `ESC[27;5;13~`;不支持的终端(macOS Terminal、
|
|
46
|
+
* Windows Terminal 稳定版等)里 Ctrl+Enter 与 Enter 同为 `\r`(或变成 `\n` = Ctrl+J 换行),这时用 `Ctrl+X`
|
|
47
|
+
* (各终端原样送达 0x18,且没有别的绑定)。`Alt+Enter` 已是 followUp,`Shift+Enter` / `Ctrl+J` 是换行,不占用。
|
|
47
48
|
*/
|
|
48
|
-
"app.message.interrupt": ["ctrl+x"],
|
|
49
|
+
"app.message.interrupt": ["ctrl+enter", "ctrl+x"],
|
|
49
50
|
"app.permission.cycle": ["shift+tab", "tab"],
|
|
50
51
|
"app.tools.expand": ["ctrl+o"],
|
|
51
52
|
"app.model.select": ["ctrl+l"],
|
package/dist/tui/terminal.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 终端抽象(设计 §12.2、§12.3、§12.8)。[B4]
|
|
3
3
|
*
|
|
4
4
|
* - `Terminal`:TUI 只依赖这个接口——尺寸、写入、输入回调、尺寸变化回调。
|
|
5
|
-
* - `ProcessTerminal`:真实 TTY。raw 模式、括号粘贴 `?2004
|
|
5
|
+
* - `ProcessTerminal`:真实 TTY。raw 模式、括号粘贴 `?2004`、增强键盘上报(Ctrl+Enter 可区分)、stdout `resize`(SIGWINCH);
|
|
6
6
|
* 输入经 `StdinBuffer` 切分,括号粘贴以 `ESC[200~正文ESC[201~` 一次交给 onInput。
|
|
7
7
|
* 停止时关括号粘贴、显示光标、恢复 cooked 模式;**不清屏**(回滚保留对话)。
|
|
8
8
|
* - `MemoryTerminal`:测试用。记录每次写入,并用 `VirtualScreen` 还原屏幕与回滚。
|
|
@@ -19,17 +19,27 @@ export interface Terminal {
|
|
|
19
19
|
}
|
|
20
20
|
export declare const BRACKETED_PASTE_ON = "\u001B[?2004h";
|
|
21
21
|
export declare const BRACKETED_PASTE_OFF = "\u001B[?2004l";
|
|
22
|
+
/**
|
|
23
|
+
* 增强键盘上报(只写不查,没有回包):kitty 键盘协议 disambiguate 标志(入栈 `CSI > 1 u`,出栈 `CSI < u`)与 xterm
|
|
24
|
+
* modifyOtherKeys 级别 1(`CSI > 4;1 m`,复位 `CSI > 4 m`)。用来让终端把 Ctrl+Enter 发成 `ESC[13;5u` /
|
|
25
|
+
* `ESC[27;5;13~`,不然它和 Enter 同为 `\r`。不支持的终端忽略这两条;tmux 只在 `extended-keys` 开着时转发。
|
|
26
|
+
*/
|
|
27
|
+
export declare const KEYBOARD_ENHANCE_ON = "\u001B[>1u\u001B[>4;1m";
|
|
28
|
+
export declare const KEYBOARD_ENHANCE_OFF = "\u001B[<u\u001B[>4m";
|
|
22
29
|
export declare const SHOW_CURSOR = "\u001B[?25h";
|
|
23
30
|
export declare const HIDE_CURSOR = "\u001B[?25l";
|
|
24
31
|
export interface ProcessTerminalOptions {
|
|
25
32
|
stdin?: NodeJS.ReadStream;
|
|
26
33
|
stdout?: NodeJS.WriteStream;
|
|
27
34
|
escTimeoutMs?: number;
|
|
35
|
+
/** 开增强键盘上报;缺省开,环境变量 `AMA_KEYBOARD_PROTOCOL=off` 关。 */
|
|
36
|
+
enhancedKeys?: boolean;
|
|
28
37
|
}
|
|
29
38
|
export declare class ProcessTerminal implements Terminal {
|
|
30
39
|
private readonly stdin;
|
|
31
40
|
private readonly stdout;
|
|
32
41
|
private readonly escTimeoutMs;
|
|
42
|
+
private readonly enhancedKeys;
|
|
33
43
|
private buffer;
|
|
34
44
|
private wasRaw;
|
|
35
45
|
private dataListener;
|
package/dist/tui/terminal.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* 终端抽象(设计 §12.2、§12.3、§12.8)。[B4]
|
|
3
3
|
*
|
|
4
4
|
* - `Terminal`:TUI 只依赖这个接口——尺寸、写入、输入回调、尺寸变化回调。
|
|
5
|
-
* - `ProcessTerminal`:真实 TTY。raw 模式、括号粘贴 `?2004
|
|
5
|
+
* - `ProcessTerminal`:真实 TTY。raw 模式、括号粘贴 `?2004`、增强键盘上报(Ctrl+Enter 可区分)、stdout `resize`(SIGWINCH);
|
|
6
6
|
* 输入经 `StdinBuffer` 切分,括号粘贴以 `ESC[200~正文ESC[201~` 一次交给 onInput。
|
|
7
7
|
* 停止时关括号粘贴、显示光标、恢复 cooked 模式;**不清屏**(回滚保留对话)。
|
|
8
8
|
* - `MemoryTerminal`:测试用。记录每次写入,并用 `VirtualScreen` 还原屏幕与回滚。
|
|
@@ -12,12 +12,20 @@ import { StdinBuffer } from "./stdin-buffer.js";
|
|
|
12
12
|
import { VirtualScreen } from "./vt-screen.js";
|
|
13
13
|
export const BRACKETED_PASTE_ON = "\x1b[?2004h";
|
|
14
14
|
export const BRACKETED_PASTE_OFF = "\x1b[?2004l";
|
|
15
|
+
/**
|
|
16
|
+
* 增强键盘上报(只写不查,没有回包):kitty 键盘协议 disambiguate 标志(入栈 `CSI > 1 u`,出栈 `CSI < u`)与 xterm
|
|
17
|
+
* modifyOtherKeys 级别 1(`CSI > 4;1 m`,复位 `CSI > 4 m`)。用来让终端把 Ctrl+Enter 发成 `ESC[13;5u` /
|
|
18
|
+
* `ESC[27;5;13~`,不然它和 Enter 同为 `\r`。不支持的终端忽略这两条;tmux 只在 `extended-keys` 开着时转发。
|
|
19
|
+
*/
|
|
20
|
+
export const KEYBOARD_ENHANCE_ON = "\x1b[>1u\x1b[>4;1m";
|
|
21
|
+
export const KEYBOARD_ENHANCE_OFF = "\x1b[<u\x1b[>4m";
|
|
15
22
|
export const SHOW_CURSOR = "\x1b[?25h";
|
|
16
23
|
export const HIDE_CURSOR = "\x1b[?25l";
|
|
17
24
|
export class ProcessTerminal {
|
|
18
25
|
stdin;
|
|
19
26
|
stdout;
|
|
20
27
|
escTimeoutMs;
|
|
28
|
+
enhancedKeys;
|
|
21
29
|
buffer = null;
|
|
22
30
|
wasRaw = false;
|
|
23
31
|
dataListener = null;
|
|
@@ -26,6 +34,8 @@ export class ProcessTerminal {
|
|
|
26
34
|
this.stdin = options.stdin ?? process.stdin;
|
|
27
35
|
this.stdout = options.stdout ?? process.stdout;
|
|
28
36
|
this.escTimeoutMs = options.escTimeoutMs;
|
|
37
|
+
this.enhancedKeys =
|
|
38
|
+
options.enhancedKeys ?? process.env["AMA_KEYBOARD_PROTOCOL"]?.toLowerCase() !== "off";
|
|
29
39
|
}
|
|
30
40
|
get columns() {
|
|
31
41
|
return this.stdout.columns || 80;
|
|
@@ -52,7 +62,7 @@ export class ProcessTerminal {
|
|
|
52
62
|
this.stdin.resume();
|
|
53
63
|
this.resizeListener = onResize;
|
|
54
64
|
this.stdout.on("resize", this.resizeListener);
|
|
55
|
-
this.write(BRACKETED_PASTE_ON);
|
|
65
|
+
this.write(BRACKETED_PASTE_ON + (this.enhancedKeys ? KEYBOARD_ENHANCE_ON : ""));
|
|
56
66
|
}
|
|
57
67
|
stop() {
|
|
58
68
|
if (this.buffer === null)
|
|
@@ -60,7 +70,7 @@ export class ProcessTerminal {
|
|
|
60
70
|
this.buffer.flush();
|
|
61
71
|
this.buffer.destroy();
|
|
62
72
|
this.buffer = null;
|
|
63
|
-
this.write(BRACKETED_PASTE_OFF + SHOW_CURSOR);
|
|
73
|
+
this.write((this.enhancedKeys ? KEYBOARD_ENHANCE_OFF : "") + BRACKETED_PASTE_OFF + SHOW_CURSOR);
|
|
64
74
|
if (this.dataListener)
|
|
65
75
|
this.stdin.off("data", this.dataListener);
|
|
66
76
|
if (this.resizeListener)
|
package/dist/tui.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
export { CURSOR_MARKER, isFocusable } from "./tui/component.js";
|
|
9
9
|
export type * from "./tui/component.js";
|
|
10
10
|
export { TUI, SYNC_BEGIN, SYNC_END, WRITE_CHUNK_SIZE, MIN_RENDER_INTERVAL_MS, type TuiOptions, type OverlayHandle, type InputListener, type RenderStats, } from "./tui/tui.js";
|
|
11
|
-
export { ProcessTerminal, MemoryTerminal, BRACKETED_PASTE_ON, BRACKETED_PASTE_OFF, SHOW_CURSOR, HIDE_CURSOR, type Terminal, type ProcessTerminalOptions, type MemoryTerminalOptions, } from "./tui/terminal.js";
|
|
11
|
+
export { ProcessTerminal, MemoryTerminal, BRACKETED_PASTE_ON, KEYBOARD_ENHANCE_ON, KEYBOARD_ENHANCE_OFF, BRACKETED_PASTE_OFF, SHOW_CURSOR, HIDE_CURSOR, type Terminal, type ProcessTerminalOptions, type MemoryTerminalOptions, } from "./tui/terminal.js";
|
|
12
12
|
export { VirtualScreen, type ScreenModes } from "./tui/vt-screen.js";
|
|
13
13
|
export { StdinBuffer, defaultEscTimeout, type StdinBufferOptions } from "./tui/stdin-buffer.js";
|
|
14
14
|
export { parseKey, matchesKey, normalizeKeyId, isPasteData, unwrapPaste, isPrintableText, PASTE_START, PASTE_END, type KeyEvent, } from "./tui/keys.js";
|
package/dist/tui.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
export { CURSOR_MARKER, isFocusable } from "./tui/component.js";
|
|
9
9
|
// ---- B4 组件库再导出 ----
|
|
10
10
|
export { TUI, SYNC_BEGIN, SYNC_END, WRITE_CHUNK_SIZE, MIN_RENDER_INTERVAL_MS, } from "./tui/tui.js";
|
|
11
|
-
export { ProcessTerminal, MemoryTerminal, BRACKETED_PASTE_ON, BRACKETED_PASTE_OFF, SHOW_CURSOR, HIDE_CURSOR, } from "./tui/terminal.js";
|
|
11
|
+
export { ProcessTerminal, MemoryTerminal, BRACKETED_PASTE_ON, KEYBOARD_ENHANCE_ON, KEYBOARD_ENHANCE_OFF, BRACKETED_PASTE_OFF, SHOW_CURSOR, HIDE_CURSOR, } from "./tui/terminal.js";
|
|
12
12
|
export { VirtualScreen } from "./tui/vt-screen.js";
|
|
13
13
|
export { StdinBuffer, defaultEscTimeout } from "./tui/stdin-buffer.js";
|
|
14
14
|
export { parseKey, matchesKey, normalizeKeyId, isPasteData, unwrapPaste, isPrintableText, PASTE_START, PASTE_END, } from "./tui/keys.js";
|
package/docs/en/guides/tui.md
CHANGED
|
@@ -120,7 +120,7 @@ Trade-offs: the status bar shows the latest hit rate (the session total lives in
|
|
|
120
120
|
| Key | Effect |
|
|
121
121
|
| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
122
122
|
| Enter | Send; while running = steer (queued until the next delivery point: the end of this model reply or of this tool batch) |
|
|
123
|
-
| Ctrl+X
|
|
123
|
+
| Ctrl+Enter / Ctrl+X | While running, interrupt and send now: stop the current turn (model stream and running tools; tools record "aborted by user") and start a new turn with the input at once, queued steers first; with an empty input only the queued steers are sent now. When idle, same as Enter. Swapped with Enter under `ui.enterWhileRunning: "interrupt"` |
|
|
124
124
|
| Alt+Enter | While running, queue after this turn (followUp); when idle, same as Enter |
|
|
125
125
|
| Shift+Enter / Ctrl+J | New line |
|
|
126
126
|
| Esc | Interrupt: queued messages go back into the input box, then the current run stops (with foreground sub-agent tasks; background tasks keep running, as the hint says); closes completion first when it is open |
|
|
@@ -142,7 +142,7 @@ Keys can be overridden in `~/.config/ama/keybindings.json`: keys are action ids
|
|
|
142
142
|
|
|
143
143
|
### Interrupt and send now
|
|
144
144
|
|
|
145
|
-
While running, Enter **queues** by default (steer): the message waits for the next delivery point, which can take a long time while the model writes a long reply or a tool runs a five-minute command. To change course right away, write the message and press **`Ctrl+
|
|
145
|
+
While running, Enter **queues** by default (steer): the message waits for the next delivery point, which can take a long time while the model writes a long reply or a tool runs a five-minute command. To change course right away, write the message and press **`Ctrl+Enter`** (action `app.message.interrupt`; use the fallback `Ctrl+X` where the terminal cannot tell it from Enter):
|
|
146
146
|
|
|
147
147
|
- The current turn stops at once: the model stream is cut, running tools finish as interrupted (every tool call has exactly one result, recorded as "aborted by user"), and the interrupted reply stays in the session under the abort rules. Then, **without pressing Enter again**, the text is sent as the user message of a new turn (`origin: "interrupt"` in the session, `↳ interrupt` in the message area).
|
|
148
148
|
- Queued steers come along: they are joined in front of the text in queue order (blank line between); messages queued after this turn (Alt+Enter) stay queued and are delivered after the new turn as usual.
|
|
@@ -150,9 +150,23 @@ While running, Enter **queues** by default (steer): the message waits for the ne
|
|
|
150
150
|
- Nothing happens while completion, an approval dialog or a picker is open; slash commands still run as commands; with an empty input only the queued steers are sent now (a hint says when there is nothing to send).
|
|
151
151
|
- The request prefix does not change: the new turn's request starts with every message of the interrupted request, so the cache keeps hitting.
|
|
152
152
|
- Esc is unchanged: it still interrupts and puts queued messages back into the input box (see interrupt to withdraw below); it never sends.
|
|
153
|
-
- To make Enter interrupt instead: set `ui.enterWhileRunning` to `interrupt` in `/config` (applies at once); Enter then interrupts and sends and `Ctrl+X` queues.
|
|
153
|
+
- To make Enter interrupt instead: set `ui.enterWhileRunning` to `interrupt` in `/config` (applies at once); Enter then interrupts and sends and `Ctrl+Enter` (and `Ctrl+X`) queues.
|
|
154
154
|
|
|
155
|
-
Why `Ctrl+
|
|
155
|
+
Why `Ctrl+Enter` by default, with `Ctrl+X` as the fallback: the request was a single modifier plus Enter, and `Alt+Enter` is already followUp while `Shift+Enter` / `Ctrl+J` are newline, so `Ctrl+Enter` is the only combination left without a conflict. Most terminals send it as the same `\r` as Enter by default, so at start ama writes (without querying, so there is no reply) the kitty keyboard protocol disambiguate flag (`CSI > 1 u`) and xterm modifyOtherKeys level 1 (`CSI > 4;1 m`), and restores them when the terminal stops (exit); terminals that do not support them ignore the sequences. Supporting terminals send Ctrl+Enter as `ESC[13;5u` or `ESC[27;5;13~`, which ama has long been able to parse. In terminals that cannot tell them apart, Ctrl+Enter is still Enter (or is read as Ctrl+J, a newline); use `Ctrl+X` there: every terminal passes 0x18 through and nothing else is bound to it. Both keys are in the default binding, so there is nothing to switch; the hint line and help show the first one (`Ctrl+Enter`). To turn the enhanced reporting off, set the environment variable `AMA_KEYBOARD_PROTOCOL=off`. Whatever you set for `app.message.interrupt` in `keybindings.json` wins (`["ctrl+x"]` restores the old behavior).
|
|
156
|
+
|
|
157
|
+
Compatibility (from each terminal's documented protocol support; not hand-tested in every terminal):
|
|
158
|
+
|
|
159
|
+
| Terminal | Enhanced keyboard protocol | Ctrl+Enter | Needs Ctrl+X |
|
|
160
|
+
| ------------------------------------------- | ------------------------------------------------------------------------ | --------------------------------------------- | ------------ |
|
|
161
|
+
| kitty | kitty keyboard protocol | works | no |
|
|
162
|
+
| Ghostty | kitty keyboard protocol | works | no |
|
|
163
|
+
| iTerm2 (3.5+) | kitty keyboard protocol, modifyOtherKeys | works | no |
|
|
164
|
+
| WezTerm | kitty keyboard protocol, off by default (`enable_kitty_keyboard = true`) | off by default, works once enabled | by default |
|
|
165
|
+
| VS Code terminal | depends on version and settings | not guaranteed | yes |
|
|
166
|
+
| macOS Terminal | none | does not work (same as Enter) | yes |
|
|
167
|
+
| Windows Terminal | none in stable | does not work (sends LF, i.e. Ctrl+J newline) | yes |
|
|
168
|
+
| Armadra canvas terminal node (xterm.js 6.0) | no kitty / modifyOtherKeys | does not work | yes |
|
|
169
|
+
| tmux | forwarded when the outer terminal supports it and `extended-keys on` | depends on the outer terminal and config | otherwise |
|
|
156
170
|
|
|
157
171
|
In line mode (`--no-tui`), typing `/interrupt <text>` while running does the same (when idle it is an ordinary prompt); the TUI accepts the command too. Over RPC it is `interrupt: true` on `prompt` / `steer`, see [rpc.md](../reference/rpc.md).
|
|
158
172
|
|
|
@@ -417,7 +431,7 @@ t2 explore · running 1m05s · 3 turns · ↑12k ↓3.4k · Esc back · /tasks s
|
|
|
417
431
|
- The body follows live: for ama sub-agents it shows every message and tool call of the sub-session (rendered like the message area); when the sub-session handle has been released (at most 16 are kept) or the session was resumed, the sub-session file is loaded read-only and live events are attached when the task runs again. External agents (claude / codex / ACP) show the live output held in this process's memory (text, thinking, tool start / end, turns, notices; at most 2000 items / 1 MB, never written to disk); after ama restarts only one line remains, saying to use the original CLI's resume <session id> for the full text.
|
|
418
432
|
- With an empty input box: `↑` / PgUp scroll up (pausing follow, with "follow paused · End to resume" at the bottom), `↓` / PgDn scroll down, End (or `f` while paused) resumes following; `←` `→` switch to the previous / next task; Esc returns to the main screen. With text in the input box, Esc clears it first.
|
|
419
433
|
- Enter sends the input to this sub-agent (recorded in the sub-session as a user message with `origin: "direct"`, see [session-format.md](../../reference/session-format.md), Chinese): ama sub-agent running → delivered when its current turn ends; external agent running or task still queued → continued after this run ends; finished → continued in the background (like `task_ctl send`; the main session receives the `<task-notification>` as usual when it completes). A line at the bottom reports the result. The parent session's model does not know you talked to the sub-agent directly; the result comes back through the completion notification.
|
|
420
|
-
- `Ctrl+X` interrupts this sub-agent and sends now: an ama sub-agent that is running stops its current turn (tools finish as interrupted) and starts a new turn with the message at once; the task completes and notifies the main session as usual. An external agent whose driver can interrupt a single turn (ACP `session/cancel`, Claude stream-json interrupt, Codex `turn/interrupt`) is interrupted and its next turn starts with the message (and any messages queued before it); when it cannot (oneshot, host drivers) or the task is still waiting in the concurrency pool, the message is queued instead with a hint; a finished task continues in the background as with Enter. Swapped with Enter under `ui.enterWhileRunning: "interrupt"`. The main session is not affected.
|
|
434
|
+
- `Ctrl+Enter` (fallback `Ctrl+X`) interrupts this sub-agent and sends now: an ama sub-agent that is running stops its current turn (tools finish as interrupted) and starts a new turn with the message at once; the task completes and notifies the main session as usual. An external agent whose driver can interrupt a single turn (ACP `session/cancel`, Claude stream-json interrupt, Codex `turn/interrupt`) is interrupted and its next turn starts with the message (and any messages queued before it); when it cannot (oneshot, host drivers) or the task is still waiting in the concurrency pool, the message is queued instead with a hint; a finished task continues in the background as with Enter. Swapped with Enter under `ui.enterWhileRunning: "interrupt"`. The main session is not affected.
|
|
421
435
|
- Esc in the view interrupts nothing: it only goes back. To stop the task use `/tasks stop <id>`; to move it to the background use `Ctrl+B` or `/tasks bg [id]`. Both work in the view's input box (the only commands the view accepts).
|
|
422
436
|
- When the viewed task waits for approval the title says "awaiting approval", and the approval dialog pops up over the view as usual (with the `[task:<type>]` origin); if its approval is docked in the bar, opening the view pops it up.
|
|
423
437
|
|
|
@@ -508,7 +522,7 @@ The interactive interface reports its run state to the terminal with the [OSC 75
|
|
|
508
522
|
## In tmux / Armadra terminal nodes
|
|
509
523
|
|
|
510
524
|
- Bracketed paste: enabled at startup; pasted multi-line content enters the input box as a whole (folded into a paste placeholder with the line count beyond 10 lines or 1 000 characters), and an Enter right after a paste sends directly, which suits writes from external programs.
|
|
511
|
-
- Apart from program status detection (see the previous section; none inside tmux), terminal capabilities are not queried, and mouse
|
|
525
|
+
- Apart from program status detection (see the previous section; none inside tmux), terminal capabilities are not queried, and mouse is not enabled, so no replies get mixed into input; the Kitty keyboard protocol and modifyOtherKeys are only switched on by writing the enable sequence, never queried (this makes Ctrl+Enter distinguishable; `AMA_KEYBOARD_PROTOCOL=off` turns it off); tmux ≥ 3.4 passes synchronized output through, and older versions display fine too.
|
|
512
526
|
- tmux's default prefix `C-b` is taken by the tmux client: to move tasks to the background press `C-b C-b` (`send-prefix` passes it through), or `↓` into the agent bar and press `b`; entering the bar uses `↓`, which the prefix does not affect.
|
|
513
527
|
- When the window size changes the last screen is redrawn in full; history in the scrollback is unaffected.
|
|
514
528
|
- Automatic fallback: non-TTY, `TERM=dumb`, `--no-tui` or a failed terminal initialization use line mode, with the same commands and approval prompts.
|
package/docs/en/reference/acp.md
CHANGED
|
@@ -149,7 +149,7 @@ The model uses ACP agents through `task(agent="acp:<program>")` (ama itself is `
|
|
|
149
149
|
|
|
150
150
|
`AcpClient` (`@armadra/agent/acp`): `initialize`, `newSession`, `resumeSession` (preferred, no replay), `loadSession`, `listSessions`, `closeSession`, `prompt`, `setMode`, `cancel`.
|
|
151
151
|
|
|
152
|
-
- Declared client capabilities: `session.configOptions: {}` (select options; boolean is not declared); `fs`
|
|
152
|
+
- Declared client capabilities: `session.configOptions: {}` (select options; boolean is not declared); `fs` is not declared (`fs/*` requests from the agent answer method not found) and `terminal` only when `onTerminal` is given (see below), and neither is `auth.terminal` (ama has no interactive terminal to lend to the agent).
|
|
153
153
|
- `$/cancel_request` is on in both directions: aborting the `signal` of a request after it was sent tells the agent to withdraw it; when the agent withdraws a pending `session/request_permission` / `elicitation/create`, the handler's `signal` aborts and the answer is `cancelled` / `cancel` (a choice the handler makes afterwards does not count).
|
|
154
154
|
- `session/request_permission` goes to `onPermission`; without a handler the first `reject_once` is answered (unattended).
|
|
155
155
|
- `cancel(sessionId)` sends `session/cancel` and answers `cancelled` to that session's pending permission requests (required by the spec).
|
|
@@ -157,7 +157,8 @@ The model uses ACP agents through `task(agent="acp:<program>")` (ama itself is `
|
|
|
157
157
|
- `mcpServers` of session-open calls (`newSession` / `resumeSession` / `loadSession`) defaults to an empty array; a host can pass a third argument `{ mcpServers }` (for stdio, `{ name, command, args, env: [{ name, value }] }`), which is forwarded to the agent as is. `AcpClient.features.mcpServers === true` signals support (older versions have no `features`). ama itself, as a client, still passes none.
|
|
158
158
|
- `elicitation/create` (the agent asking a human for structured input): only when the constructor is given `onElicitation(params, signal)` does `initialize` declare `clientCapabilities.elicitation` and accept this request; otherwise it is not declared and the request answers method not found (as in older versions). Answers are normalized to `{ action: "accept" | "decline" | "cancel", content? }` (`content` only with accept; unknown actions count as cancel); pending ones answer `{ action: "cancel" }` on `cancel(sessionId)` and when the connection closes. ama itself, as a client, gives no handler and never fills in forms for a human.
|
|
159
159
|
- Session config options: the `configOptions` of session-open answers (new / load / resume) are passed through; `setConfigOption(sessionId, configId, value)` sends `session/set_config_option`, and the answer is the new state of all options.
|
|
160
|
-
- `
|
|
160
|
+
- `terminal/*` (the agent asking the host for a terminal): only when the constructor is given `onTerminal(method, params, signal)` does `initialize` declare `clientCapabilities.terminal: true`; `terminal/create|output|wait_for_exit|kill|release` are then handed to it with the params untouched, its return value is the answer and throwing `RpcError` answers with that error. Without it `terminal: false` is declared and the requests answer method not found (as in older versions). Permissions, cwd restriction and output limits are the host's job; ama itself, as a client, gives no handler.
|
|
161
|
+
- `AcpClient.features`: `{ mcpServers, elicitation, configOptions, terminal }`, for feature detection by hosts.
|
|
161
162
|
|
|
162
163
|
`AcpDriver` implements the driver contract (`AgentDriver`) on top of the client:
|
|
163
164
|
|
package/docs/guides/tui.md
CHANGED
|
@@ -114,7 +114,7 @@ Accept edits | shift+tab 切换 claude-opus-5-5 medium | Ctx 3.0% 8.2k/272k a
|
|
|
114
114
|
| 按键 | 作用 |
|
|
115
115
|
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
116
116
|
| Enter | 发送;运行中 = steer(排队,等下一个投递点:本次模型回复结束或本批工具结束) |
|
|
117
|
-
| Ctrl+X
|
|
117
|
+
| Ctrl+Enter / Ctrl+X | 运行中打断并立即发送:中止当前回合(模型流与正在跑的工具,工具记「aborted by user」),立刻以输入框的文字开新回合,排队的插话拼在前面;输入为空时只把排队的插话立即送出。空闲时等同 Enter。`ui.enterWhileRunning: "interrupt"` 时与 Enter 互换 |
|
|
118
118
|
| Alt+Enter | 运行中排到本轮之后(followUp);空闲时等同 Enter |
|
|
119
119
|
| Shift+Enter / Ctrl+J | 换行 |
|
|
120
120
|
| Esc | 中断:排队的消息回填到输入框,然后停止当前运行(连带前台子 Agent 任务;后台任务不受影响,提示里写明);补全打开时先关补全 |
|
|
@@ -136,7 +136,7 @@ Accept edits | shift+tab 切换 claude-opus-5-5 medium | Ctx 3.0% 8.2k/272k a
|
|
|
136
136
|
|
|
137
137
|
### 打断并立即发送
|
|
138
138
|
|
|
139
|
-
运行中按 Enter 缺省是**排队**(steer):消息等下一个投递点才送达——模型在长篇输出、或工具在跑一个 5 分钟的命令时要等很久。要立刻改方向,在输入框写好后按 **`Ctrl+
|
|
139
|
+
运行中按 Enter 缺省是**排队**(steer):消息等下一个投递点才送达——模型在长篇输出、或工具在跑一个 5 分钟的命令时要等很久。要立刻改方向,在输入框写好后按 **`Ctrl+Enter`**(动作 `app.message.interrupt`;终端分不出它与 Enter 时按回退键 `Ctrl+X`):
|
|
140
140
|
|
|
141
141
|
- 当前回合立即中止:模型流断开,正在执行的工具按中断收尾(每个工具调用恰有一个结果,记「aborted by user」),被打断的回复按中断规则留在会话里;随后**不用再按 Enter**,这段文字直接作为新回合的用户消息发出(会话里 `origin: "interrupt"`,消息区标 `↳ 打断`)。
|
|
142
142
|
- 已排队的插话一起带上:按入队顺序拼在这段文字前面(空行分隔);排到本轮之后的(Alt+Enter)留在队列,新回合结束后照常送达。
|
|
@@ -144,9 +144,23 @@ Accept edits | shift+tab 切换 claude-opus-5-5 medium | Ctx 3.0% 8.2k/272k a
|
|
|
144
144
|
- 补全打开、审批框或选择器打开时不触发;斜杠命令照常当命令执行;输入为空时只把排队的插话立即送出(没有可发的内容时提示一行)。
|
|
145
145
|
- 中断的请求前缀不变:新回合的请求以被打断那次请求的全部消息为前缀,缓存照常命中。
|
|
146
146
|
- Esc 不变:仍是中断并把排队消息回填输入框(中断即撤回见下文),不会自动发送。
|
|
147
|
-
- 想让 Enter 直接打断:`/config` 里把 `ui.enterWhileRunning` 设为 `interrupt`(立即生效),这时 Enter 打断并发送、`Ctrl+X
|
|
147
|
+
- 想让 Enter 直接打断:`/config` 里把 `ui.enterWhileRunning` 设为 `interrupt`(立即生效),这时 Enter 打断并发送、`Ctrl+Enter`(和 `Ctrl+X`)排队。
|
|
148
148
|
|
|
149
|
-
缺省键选 `Ctrl+X`
|
|
149
|
+
缺省键选 `Ctrl+Enter`、回退 `Ctrl+X` 的原因:要「一个修饰键 + 回车」,而 `Alt+Enter` 已是 followUp、`Shift+Enter` / `Ctrl+J` 是换行,`Ctrl+Enter` 是剩下唯一不冲突的组合。多数终端默认把它和 Enter 发成同一个 `\r`,所以 ama 启动时向终端写入(不查询、没有回包)kitty 键盘协议的 disambiguate 标志(`CSI > 1 u`)和 xterm modifyOtherKeys 级别 1(`CSI > 4;1 m`),终端停止(退出)时恢复;不支持的终端直接忽略。支持的终端把 Ctrl+Enter 发成 `ESC[13;5u` 或 `ESC[27;5;13~`,ama 早就能解析。分不出的终端里 Ctrl+Enter 仍是 Enter(或被当作 Ctrl+J 换行),这时用 `Ctrl+X`:各终端原样送达 0x18,且没有别的绑定。两个键都在缺省绑定里,不用切换;提示行与帮助显示第一个键(`Ctrl+Enter`)。想关掉增强上报:环境变量 `AMA_KEYBOARD_PROTOCOL=off`。自己改 `keybindings.json` 里的 `app.message.interrupt` 后以你的为准(写 `["ctrl+x"]` 即恢复旧行为)。
|
|
150
|
+
|
|
151
|
+
兼容表(按各终端文档的协议支持整理,未在每个终端逐一实测):
|
|
152
|
+
|
|
153
|
+
| 终端 | 增强键盘协议 | Ctrl+Enter | 用 Ctrl+X |
|
|
154
|
+
| ------------------------------------ | ------------------------------------------------------------- | --------------------------------- | --------- |
|
|
155
|
+
| kitty | kitty 键盘协议 | 可用 | 不需要 |
|
|
156
|
+
| Ghostty | kitty 键盘协议 | 可用 | 不需要 |
|
|
157
|
+
| iTerm2(3.5+) | kitty 键盘协议、modifyOtherKeys | 可用 | 不需要 |
|
|
158
|
+
| WezTerm | kitty 键盘协议,缺省关(`enable_kitty_keyboard = true` 打开) | 缺省不可用,打开后可用 | 缺省需要 |
|
|
159
|
+
| VS Code 终端 | 取决于版本与设置 | 不保证 | 需要 |
|
|
160
|
+
| macOS Terminal | 无 | 不可用(同 Enter) | 需要 |
|
|
161
|
+
| Windows Terminal | 稳定版无 | 不可用(送 LF,等于 Ctrl+J 换行) | 需要 |
|
|
162
|
+
| Armadra 画布终端节点(xterm.js 6.0) | 无 kitty / modifyOtherKeys | 不可用 | 需要 |
|
|
163
|
+
| tmux | 外层终端支持且 `extended-keys on` 时转发 | 视外层与配置 | 否则需要 |
|
|
150
164
|
|
|
151
165
|
line 模式(`--no-tui`)运行中输入 `/interrupt <文本>` 效果相同(空闲时就是普通提示);交互界面里也认这条命令。RPC 是 `prompt` / `steer` 的 `interrupt: true`,见 [rpc.md](../reference/rpc.md)。
|
|
152
166
|
|
|
@@ -410,7 +424,7 @@ t2 explore · 运行中 1m05s · 3 轮 · ↑12k ↓3.4k · Esc 返回 · /tasks
|
|
|
410
424
|
- 正文实时跟随:ama 子 Agent 显示子会话的全部消息与工具调用(与消息区同样的渲染);子会话句柄已被释放(保留上限 16 个)或会话是 resume 进来的,就只读加载子会话文件,任务再次运行时接上实时事件。外部 Agent(claude / codex / ACP)显示本进程内存里的实时输出(文本、思考、工具起止、回合、提示;最多 2000 条 / 1 MB,不落盘);ama 重启后只剩一行说明「用原 CLI resume <会话 id> 查看全文」。
|
|
411
425
|
- 输入框为空时:`↑` / PgUp 上翻(暂停跟随,底部提示「已暂停跟随 · End 继续」),`↓` / PgDn 下翻,End(暂停时也可按 `f`)回到跟随;`←` `→` 切到上一个 / 下一个任务;Esc 返回主界面。输入框有字时 Esc 先清空。
|
|
412
426
|
- Enter 把输入发给这个子 Agent(会话里记为 `origin: "direct"` 的 user 消息,见 [session-format.md](../reference/session-format.md)):ama 子 Agent 运行中 → 排到它本轮结束时送达;外部 Agent 运行中或任务还在排队 → 等本次运行结束后续聊;已结束 → 后台续聊(同 `task_ctl send`,完成后主会话照常收到 `<task-notification>`)。底部一行提示发送结果。父会话的模型不知道你直接和子 Agent 说过话,结果经结束通知自然带回。
|
|
413
|
-
- `Ctrl+X
|
|
427
|
+
- `Ctrl+Enter`(回退 `Ctrl+X`)打断并发送给这个子 Agent:ama 子 Agent 运行中 → 中止它当前这一轮(工具按中断收尾)并立即以这条消息开新一轮,任务照常完成、照常通知主会话;外部 Agent 的驱动能中断单个回合(ACP `session/cancel`、Claude stream-json interrupt、Codex `turn/interrupt`)→ 中断后立即以它(连同之前排着的消息)开下一回合;不能(oneshot、宿主驱动)或任务还在并发池排队 → 退回排队并提示「不支持打断,已排队」;已结束 → 同 Enter 的后台续聊。`ui.enterWhileRunning: "interrupt"` 时与 Enter 互换。主会话不受影响。
|
|
414
428
|
- 视图里 Esc 不中断任何东西:Esc 只是返回。停止子任务用 `/tasks stop <id>`,转后台用 `Ctrl+B` 或 `/tasks bg [id]`——在视图输入框里也能用(视图里只认这两条命令)。
|
|
415
429
|
- 正在看的任务等审批时标题显示「等待审批」,审批框照常弹在视图上面(带 `[task:<类型>]` 来源);它的审批停靠在栏里时,打开视图即弹出。
|
|
416
430
|
|
|
@@ -515,7 +529,7 @@ t2 explore · 运行中 1m05s · 3 轮 · ↑12k ↓3.4k · Esc 返回 · /tasks
|
|
|
515
529
|
## 在 tmux / Armadra 终端节点里
|
|
516
530
|
|
|
517
531
|
- 括号粘贴:启动时开启;粘贴的多行内容整体进入输入框(超过 10 行或 1 000 字符折叠为 `[粘贴 #N · M 行]`),粘贴后紧跟的回车直接发送,适合由外部程序写入。
|
|
518
|
-
- 除程序状态检测(见上一节,tmux
|
|
532
|
+
- 除程序状态检测(见上一节,tmux 里不检测)外不查询终端能力、不开鼠标,避免回包混进输入;Kitty 键盘协议与 modifyOtherKeys 只写开启序列、不查询(让 Ctrl+Enter 可区分,`AMA_KEYBOARD_PROTOCOL=off` 关闭);tmux ≥ 3.4 透传同步输出,旧版本也能正常显示。
|
|
519
533
|
- tmux 的缺省前缀 `C-b` 会被 tmux 客户端吃掉:转后台按 `C-b C-b`(`send-prefix` 透传),或 `↓` 进 Agent 栏按 `b`;进栏用 `↓`,不受前缀影响。
|
|
520
534
|
- 窗口尺寸变化时整屏重画最后一屏,回滚里的历史不受影响。
|
|
521
535
|
- 自动降级:非 TTY、`TERM=dumb`、`--no-tui` 或终端初始化失败时使用行式界面,命令与审批问答相同。
|
package/docs/reference/acp.md
CHANGED
|
@@ -155,8 +155,8 @@ stdin 关闭后等已开始的运行结束再退出(0);`ama` 进程的 std
|
|
|
155
155
|
|
|
156
156
|
`AcpClient`(`@armadra/agent/acp`):`initialize`、`newSession`、`resumeSession`(优先,不回放)、`loadSession`、`listSessions`、`closeSession`、`prompt`、`setMode`、`cancel`。
|
|
157
157
|
|
|
158
|
-
- 声明的客户端能力:`session.configOptions: {}`(接 select 型配置项,不声明 boolean);不声明 `fs`
|
|
159
|
-
`
|
|
158
|
+
- 声明的客户端能力:`session.configOptions: {}`(接 select 型配置项,不声明 boolean);不声明 `fs`(Agent 发来的 `fs/*` 请求回 method not found);`terminal` 只在构造参数给了
|
|
159
|
+
`onTerminal` 时声明(见下),也不声明 `auth.terminal`(ama 没有可借给 Agent 的交互终端)。
|
|
160
160
|
- 线路两侧开 `$/cancel_request`:本端请求的 `signal` 在发出后 abort 会通知 Agent 撤回;Agent 撤回挂起的
|
|
161
161
|
`session/request_permission` / `elicitation/create` 时处理器的 `signal` abort,答 `cancelled` / `cancel`(处理器之后给的选择不作数)。
|
|
162
162
|
- `session/request_permission` 交给 `onPermission`;没有处理器时回首个 `reject_once`(无人值守)。
|
|
@@ -171,7 +171,11 @@ stdin 关闭后等已开始的运行结束再退出(0);`ama` 进程的 std
|
|
|
171
171
|
与连接关闭时挂起的一律回 `{ action: "cancel" }`。ama 自己作客户端时不给处理器,从不替人填表。
|
|
172
172
|
- 会话配置项:开会话(new / load / resume)答的 `configOptions` 原样交回;`setConfigOption(sessionId, configId, value)` 发
|
|
173
173
|
`session/set_config_option`,答复是全部配置项的新状态。
|
|
174
|
-
- `
|
|
174
|
+
- `terminal/*`(Agent 向宿主要终端):构造参数给了 `onTerminal(method, params, signal)` 才在 `initialize` 声明
|
|
175
|
+
`clientCapabilities.terminal: true`,并把 `terminal/create|output|wait_for_exit|kill|release` 连同原样参数交给它,返回值即答复,
|
|
176
|
+
抛 `RpcError` 回对应错误;没给时声明 `terminal: false`、请求回 method not found(与旧版相同)。权限、cwd 限制与输出上限由宿主负责;
|
|
177
|
+
ama 自己作客户端时不给处理器。
|
|
178
|
+
- `AcpClient.features`:`{ mcpServers, elicitation, configOptions, terminal }`,宿主据此做特性检测。
|
|
175
179
|
|
|
176
180
|
`AcpDriver` 在客户端之上实现驱动契约(`AgentDriver`):
|
|
177
181
|
|