@armadra/agent 0.6.3 → 0.6.5

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.
Files changed (73) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/CHANGELOG.zh-CN.md +21 -0
  3. package/dist/agent/queue.d.ts +9 -0
  4. package/dist/agent/queue.js +27 -0
  5. package/dist/agent/session-subagent.d.ts +1 -0
  6. package/dist/agent/session-subagent.js +9 -0
  7. package/dist/agent/session.d.ts +5 -0
  8. package/dist/agent/session.js +21 -2
  9. package/dist/agent/subagent-direct.d.ts +5 -2
  10. package/dist/agent/subagent-direct.js +34 -1
  11. package/dist/agent/subagent-registry.d.ts +2 -1
  12. package/dist/agent/subagent-registry.js +2 -2
  13. package/dist/agent/types.d.ts +7 -0
  14. package/dist/agents/task-record.d.ts +3 -3
  15. package/dist/ai/apis/chatgpt-rate-limits.js +7 -2
  16. package/dist/ai/types.d.ts +2 -1
  17. package/dist/bundle/ama.cjs +417 -106
  18. package/dist/config/json-schema.js +1 -0
  19. package/dist/config/key-docs.js +8 -1
  20. package/dist/config/schema.js +2 -0
  21. package/dist/config/settings-registry.js +1 -0
  22. package/dist/config/types.d.ts +2 -0
  23. package/dist/drivers/acp/client.d.ts +13 -4
  24. package/dist/drivers/acp/client.js +17 -6
  25. package/dist/drivers/acp/types.d.ts +22 -2
  26. package/dist/drivers/runner.js +29 -2
  27. package/dist/i18n/catalog.d.ts +13 -0
  28. package/dist/i18n/messages/agents.d.ts +4 -0
  29. package/dist/i18n/messages/agents.js +4 -0
  30. package/dist/i18n/messages/config-keys.d.ts +2 -0
  31. package/dist/i18n/messages/config-keys.js +2 -0
  32. package/dist/i18n/messages/config.d.ts +2 -0
  33. package/dist/i18n/messages/interactive-view.d.ts +8 -0
  34. package/dist/i18n/messages/interactive-view.js +10 -0
  35. package/dist/i18n/messages/interactive.d.ts +10 -0
  36. package/dist/i18n/messages/interactive.js +6 -0
  37. package/dist/i18n/messages/report.d.ts +8 -0
  38. package/dist/i18n/messages/report.js +8 -0
  39. package/dist/i18n/messages/settings.d.ts +2 -0
  40. package/dist/i18n/messages/settings.js +2 -0
  41. package/dist/modes/commands-core.js +11 -0
  42. package/dist/modes/interactive/agent-ui.js +1 -0
  43. package/dist/modes/interactive/agent-view.d.ts +5 -0
  44. package/dist/modes/interactive/agent-view.js +14 -3
  45. package/dist/modes/interactive/interactive-mode.js +13 -1
  46. package/dist/modes/interactive/interrupt-send.d.ts +25 -0
  47. package/dist/modes/interactive/interrupt-send.js +32 -0
  48. package/dist/modes/interactive/key-dispatch.d.ts +8 -1
  49. package/dist/modes/interactive/key-dispatch.js +37 -0
  50. package/dist/modes/interactive/line/line-mode.d.ts +1 -0
  51. package/dist/modes/interactive/line/line-mode.js +29 -7
  52. package/dist/modes/interactive/run-indicator.d.ts +19 -2
  53. package/dist/modes/interactive/run-indicator.js +29 -7
  54. package/dist/modes/interactive/status-bar.d.ts +4 -1
  55. package/dist/modes/interactive/status-bar.js +17 -6
  56. package/dist/modes/interactive/status-line.d.ts +10 -9
  57. package/dist/modes/interactive/status-line.js +46 -38
  58. package/dist/modes/interactive/status-quota.d.ts +16 -2
  59. package/dist/modes/interactive/status-quota.js +54 -34
  60. package/dist/modes/interactive/task-background.d.ts +2 -2
  61. package/dist/modes/rpc/commands.d.ts +2 -0
  62. package/dist/modes/rpc/commands.js +15 -1
  63. package/dist/rpc.d.ts +7 -0
  64. package/dist/tools/types.d.ts +6 -0
  65. package/dist/tui/keybindings.d.ts +7 -0
  66. package/dist/tui/keybindings.js +7 -0
  67. package/docs/acp.md +3 -0
  68. package/docs/en/rpc.md +9 -7
  69. package/docs/en/tui.md +49 -31
  70. package/docs/rpc.md +9 -7
  71. package/docs/session-format.md +1 -1
  72. package/docs/tui.md +49 -31
  73. package/package.json +1 -1
@@ -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.6.3" : readPackageVersion();
44
+ AMA_VERSION = true ? "0.6.5" : readPackageVersion();
45
45
  }
46
46
  });
47
47
 
@@ -1734,7 +1734,8 @@ var init_interactive_view = __esm({
1734
1734
  followUp: "follow-up",
1735
1735
  host: "host",
1736
1736
  task: "subagent notice",
1737
- plan: "plan"
1737
+ plan: "plan",
1738
+ interrupt: "interrupt"
1738
1739
  },
1739
1740
  planHandoff: "Start executing the approved plan",
1740
1741
  thinkingStreaming: (glyph, ellipsis) => `${glyph} Thinking${ellipsis}`,
@@ -1762,7 +1763,11 @@ var init_interactive_view = __esm({
1762
1763
  replying: "Replying",
1763
1764
  thinking: "Thinking",
1764
1765
  queueMore: (ellipsis, n) => `${ellipsis} ${n} more`,
1765
- queueHint: (arrow) => `Alt+${arrow} take back · Esc refill and interrupt`
1766
+ queueHint: (arrow) => `Alt+${arrow} take back · Esc refill and interrupt`,
1767
+ /** 有排队的插话时(`key` = 立即送出它们的键:专用键,或 interrupt 模式下的 Enter)。 */
1768
+ queueHintNow: (arrow, key) => `Alt+${arrow} take back · ${key} send now · Esc refill and interrupt`,
1769
+ /** 输入框有字时:Enter 与专用键各做什么(`ui.enterWhileRunning`)。 */
1770
+ sendHint: (mode, key) => mode === "queue" ? `Enter queue · ${key} interrupt & send` : `Enter interrupt & send · ${key} queue`
1766
1771
  },
1767
1772
  tool: {
1768
1773
  interrupted: "interrupted",
@@ -1800,7 +1805,8 @@ var init_interactive_view = __esm({
1800
1805
  followUp: "之后",
1801
1806
  host: "宿主",
1802
1807
  task: "子 Agent 通知",
1803
- plan: "计划"
1808
+ plan: "计划",
1809
+ interrupt: "打断"
1804
1810
  },
1805
1811
  planHandoff: "按批准的计划开始执行",
1806
1812
  thinkingStreaming: (glyph, ellipsis) => `${glyph} 思考中${ellipsis}`,
@@ -1828,7 +1834,9 @@ var init_interactive_view = __esm({
1828
1834
  replying: "回复中",
1829
1835
  thinking: "思考中",
1830
1836
  queueMore: (ellipsis, n) => `${ellipsis} 另 ${n} 条`,
1831
- queueHint: (arrow) => `Alt+${arrow} 取回 · Esc 回填并中断`
1837
+ queueHint: (arrow) => `Alt+${arrow} 取回 · Esc 回填并中断`,
1838
+ queueHintNow: (arrow, key) => `Alt+${arrow} 取回 · ${key} 立即发送 · Esc 回填并中断`,
1839
+ sendHint: (mode, key) => mode === "queue" ? `Enter 排队 · ${key} 打断并发送` : `Enter 打断并发送 · ${key} 排队`
1832
1840
  },
1833
1841
  tool: {
1834
1842
  interrupted: "已中断",
@@ -1875,6 +1883,7 @@ var init_interactive = __esm({
1875
1883
  keyHints: [
1876
1884
  "Enter send (while running = steer) Alt+Enter queue after this turn Shift+Enter / Ctrl+J newline",
1877
1885
  "Esc interrupt (queued messages go back to the editor) Alt+↑ take back the last queued message",
1886
+ "Ctrl+X (while running) interrupt the current turn and send the input now, queued steers first; ui.enterWhileRunning: interrupt swaps it with Enter",
1878
1887
  "Idle Esc Esc: empty input = rewind (/rewind), with text = clear (↑ to restore)",
1879
1888
  "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",
1880
1889
  "Approval: 1–3 or ↑↓ Enter to choose, y allow a allow for this session n / Esc deny v full input",
@@ -1917,6 +1926,8 @@ var init_interactive = __esm({
1917
1926
  clearedCtrlC: "Input cleared · press Ctrl+C again to quit",
1918
1927
  ctrlCAgain: "Press Ctrl+C again to quit",
1919
1928
  interrupted: "Interrupted",
1929
+ interruptSent: "Interrupted · sending now",
1930
+ nothingToSend: "Nothing to send",
1920
1931
  toolsExpanded: "Tool output: expanded",
1921
1932
  toolsCollapsed: "Tool output: collapsed"
1922
1933
  },
@@ -2007,6 +2018,7 @@ var init_interactive = __esm({
2007
2018
  keyHints: [
2008
2019
  "Enter 发送(运行中 = 插话) Alt+Enter 排到本轮之后 Shift+Enter / Ctrl+J 换行",
2009
2020
  "Esc 中断(排队消息回填编辑器) Alt+↑ 取回最后一条排队消息",
2021
+ "Ctrl+X(运行中)打断当前回合并立即发送输入(排队的插话在前);ui.enterWhileRunning: interrupt 时与 Enter 互换",
2010
2022
  "空闲时 Esc Esc:输入框为空 = 回滚(/rewind),有字 = 清空(↑ 取回)",
2011
2023
  "Shift+Tab / Tab(输入为空时)切换权限模式(进入 Bypass 前确认) Ctrl+L 模型 Ctrl+T 思考级别 Ctrl+O 展开工具输出与思考",
2012
2024
  "审批:1–3 或 ↑↓ Enter 选择,y 允许 a 本会话允许同类 n / Esc 拒绝 v 完整输入",
@@ -2047,6 +2059,8 @@ var init_interactive = __esm({
2047
2059
  clearedCtrlC: "已清空输入 · 再按 Ctrl+C 退出",
2048
2060
  ctrlCAgain: "再按一次 Ctrl+C 退出",
2049
2061
  interrupted: "已中断",
2062
+ interruptSent: "已打断,立即发送",
2063
+ nothingToSend: "没有可发送的内容",
2050
2064
  toolsExpanded: "工具输出:展开",
2051
2065
  toolsCollapsed: "工具输出:折叠"
2052
2066
  },
@@ -2983,6 +2997,8 @@ var init_report = __esm({
2983
2997
  tasks: "Sub-agent tasks: status, output, stop, background",
2984
2998
  agents: "Sub-agent types and external agents (installed, version)",
2985
2999
  paste: "Paste an image from the clipboard (Ctrl+V)",
3000
+ interruptArgs: "<text>",
3001
+ interrupt: "Interrupt the current turn and send this now, queued steers first (Ctrl+X)",
2986
3002
  exit: "Exit",
2987
3003
  config: "Settings panel; key=value sets one item directly (user level)",
2988
3004
  traceArgs: "[task id]",
@@ -3001,6 +3017,8 @@ var init_report = __esm({
3001
3017
  cacheUsage: "usage: /cache [warm off|streaming|idle | fingerprint]",
3002
3018
  taskStopped: (id) => `Stopped ${id}`,
3003
3019
  tasksUsage: "usage: /tasks [id] | /tasks stop <id> | /tasks bg [id]",
3020
+ interruptUsage: "usage: /interrupt <text>",
3021
+ interruptSent: (text) => `Interrupted; sending now: ${text}`,
3004
3022
  newSession: (id) => `New session ${id}`,
3005
3023
  resumed: (id, messages) => `Resumed session ${id} (${plural(messages, "message")})`,
3006
3024
  forked: (id) => `Forked to new session ${id}`,
@@ -3222,6 +3240,8 @@ Available: ${inactive.join(", ")}` : ""}`,
3222
3240
  tasks: "子 Agent 任务:状态、输出、停止、转后台",
3223
3241
  agents: "子 Agent 类型与外部 Agent(安装状态、版本)",
3224
3242
  paste: "粘贴剪贴板里的图片(Ctrl+V)",
3243
+ interruptArgs: "<文本>",
3244
+ interrupt: "打断当前回合并立即发送这段文字,排队的插话在前(Ctrl+X)",
3225
3245
  exit: "退出",
3226
3246
  config: "设置面板;key=value 直接设一项(用户级)",
3227
3247
  traceArgs: "[任务 id]",
@@ -3239,6 +3259,8 @@ Available: ${inactive.join(", ")}` : ""}`,
3239
3259
  cacheUsage: "用法:/cache [warm off|streaming|idle | fingerprint]",
3240
3260
  taskStopped: (id) => `已停止 ${id}`,
3241
3261
  tasksUsage: "用法:/tasks [id] | /tasks stop <id> | /tasks bg [id]",
3262
+ interruptUsage: "用法:/interrupt <文本>",
3263
+ interruptSent: (text) => `已打断,立即发送:${text}`,
3242
3264
  newSession: (id) => `已新建会话 ${id}`,
3243
3265
  resumed: (id, messages) => `已恢复会话 ${id}(${messages} 条消息)`,
3244
3266
  forked: (id) => `已分叉到新会话 ${id}`,
@@ -3628,6 +3650,7 @@ var init_config_keys = __esm({
3628
3650
  "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",
3629
3651
  "ui.animation": "false: the spinner stays still while running and redraws only when seconds change; the startup logo does not animate",
3630
3652
  "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",
3653
+ "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",
3631
3654
  "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)",
3632
3655
  "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",
3633
3656
  "ui.replyLanguage": "Reply language for the model (e.g. Chinese): one English rule is appended to the end of the rules section at session start; zero-byte change when unset; user level only (effective from wave 6)",
@@ -3750,6 +3773,7 @@ var init_config_keys = __esm({
3750
3773
  "ui.logo": "启动头的 AMA 字符画:auto 在终端宽 48 列以上时画,off 只显示信息行",
3751
3774
  "ui.animation": "false:运行中 spinner 静止,只在秒数变化时重绘;启动字符画不播放动画",
3752
3775
  "ui.restoreOnCancel": "运行中 Esc 中断、本回合还没有任何回复或工具调用时,撤回该回合并把原消息放回输入框",
3776
+ "ui.enterWhileRunning": "运行中按 Enter:queue(缺省)排队插话,等下一个投递点送达;interrupt 打断当前回合并立即发送。app.message.interrupt 键(缺省 Ctrl+X)做另一种",
3753
3777
  "ui.statusLine": "底部信息行:full 两行(速率行 + 状态行),compact 一行;运行时 Ctrl+G 或 /statusline 切换(第五波 W5-A 起生效)",
3754
3778
  "ui.language": "界面语言:auto 按 LC_ALL / LC_MESSAGES / LANG 判断(zh* 为中文,其余英文),zh,en;AMA_LANG、--lang 覆盖;只影响界面,发给模型的文本固定英文",
3755
3779
  "ui.replyLanguage": "模型回复语言(如 Chinese):会话开始在 rules 节末尾追加一句英文规则;不设时零字节变化;只认用户级(第六波起生效)",
@@ -4696,6 +4720,8 @@ var init_agents = __esm({
4696
4720
  steered: (taskId) => `Sent to ${taskId}; delivered when its current turn ends`,
4697
4721
  queued: (taskId) => `Queued for ${taskId}; sent when its current run ends`,
4698
4722
  resumed: (taskId) => `Sent to ${taskId}; it continues in the background`,
4723
+ interrupted: (taskId) => `Interrupted ${taskId}; your message starts its next turn now`,
4724
+ queuedNoInterrupt: (taskId) => `${taskId} cannot be interrupted here; queued, sent when its current run ends`,
4699
4725
  sendFailed: (message) => `Could not send: ${message}`,
4700
4726
  stopped: (taskId) => `Stopped ${taskId}`,
4701
4727
  commandsHere: "Only /tasks stop and /tasks bg work here; press Esc to go back for other commands",
@@ -4758,6 +4784,8 @@ var init_agents = __esm({
4758
4784
  steered: (taskId) => `已发给 ${taskId},本轮结束后送达`,
4759
4785
  queued: (taskId) => `已排队,${taskId} 本次运行结束后发送`,
4760
4786
  resumed: (taskId) => `已发给 ${taskId},在后台继续`,
4787
+ interrupted: (taskId) => `已打断 ${taskId},这条消息立即开始新一轮`,
4788
+ queuedNoInterrupt: (taskId) => `${taskId} 不支持打断,已排队,本次运行结束后发送`,
4761
4789
  sendFailed: (message) => `发送失败:${message}`,
4762
4790
  stopped: (taskId) => `已停止 ${taskId}`,
4763
4791
  commandsHere: "这里只能用 /tasks stop 与 /tasks bg;其它命令按 Esc 返回后输入",
@@ -5396,6 +5424,7 @@ var init_settings = __esm({
5396
5424
  "ui.logo": "Startup logo",
5397
5425
  "ui.statusLine": "Status line",
5398
5426
  "ui.restoreOnCancel": "Restore on cancel",
5427
+ "ui.enterWhileRunning": "Enter while running",
5399
5428
  "ui.quietStartup": "Startup screen",
5400
5429
  "ui.ascii": "ASCII glyphs",
5401
5430
  "ui.language": "Interface language",
@@ -5567,6 +5596,7 @@ var init_settings = __esm({
5567
5596
  "ui.logo": "启动字符画",
5568
5597
  "ui.statusLine": "底部信息行",
5569
5598
  "ui.restoreOnCancel": "中断后放回原消息",
5599
+ "ui.enterWhileRunning": "运行中按 Enter",
5570
5600
  "ui.quietStartup": "启动画面",
5571
5601
  "ui.ascii": "ASCII 字形",
5572
5602
  "ui.language": "界面语言",
@@ -7101,6 +7131,7 @@ function validateConfig(value) {
7101
7131
  "logo",
7102
7132
  "animation",
7103
7133
  "restoreOnCancel",
7134
+ "enterWhileRunning",
7104
7135
  ...W5_UI_KEYS,
7105
7136
  ...W6_UI_KEYS
7106
7137
  ],
@@ -7115,6 +7146,7 @@ function validateConfig(value) {
7115
7146
  c.oneOf(s2, "logo", p, ["auto", "off"]);
7116
7147
  c.boolean(s2, "animation", p);
7117
7148
  c.boolean(s2, "restoreOnCancel", p);
7149
+ c.oneOf(s2, "enterWhileRunning", p, ["queue", "interrupt"]);
7118
7150
  checkUiW5(c, s2, p);
7119
7151
  checkUiW6(c, s2, p);
7120
7152
  }
@@ -18176,6 +18208,24 @@ function queuedText(message) {
18176
18208
  if (typeof content === "string") return content;
18177
18209
  return content.map((block) => block.type === "text" ? block.text : "[image]").join("");
18178
18210
  }
18211
+ function mergeForInterrupt(queued, text, images = []) {
18212
+ const texts2 = [];
18213
+ const merged = [];
18214
+ for (const message of queued) {
18215
+ if (message.role !== "user") continue;
18216
+ const { content } = message;
18217
+ if (typeof content === "string") texts2.push(content);
18218
+ else {
18219
+ texts2.push(content.map((block) => block.type === "text" ? block.text : "").join(""));
18220
+ for (const block of content) if (block.type === "image") merged.push(block);
18221
+ }
18222
+ }
18223
+ texts2.push(text);
18224
+ merged.push(...images);
18225
+ const parts = texts2.filter((part) => part.trim() !== "");
18226
+ if (parts.length === 0 && merged.length === 0) return void 0;
18227
+ return { text: parts.join("\n\n"), images: merged };
18228
+ }
18179
18229
  var PendingMessageQueue;
18180
18230
  var init_queue = __esm({
18181
18231
  "src/agent/queue.ts"() {
@@ -23296,10 +23346,16 @@ function liveOf(record3) {
23296
23346
  if (entries !== void 0) live2.entries = () => entries.call(handle);
23297
23347
  return live2;
23298
23348
  }
23299
- async function directMessage(record3, text, host) {
23349
+ async function directMessage(record3, text, host, interrupt = false) {
23300
23350
  if (text.trim() === "") throw new AmaError("invalid_arguments", "empty message");
23301
23351
  if (record3.running !== void 0) {
23302
23352
  const handle = record3.handle;
23353
+ if (interrupt && record3.queued !== true && await interruptRun(record3, text))
23354
+ return "interrupted";
23355
+ if (interrupt) {
23356
+ (record3.direct ??= []).push(text);
23357
+ return "queuedNoInterrupt";
23358
+ }
23303
23359
  if (handle?.message !== void 0 && record3.queued !== true) {
23304
23360
  try {
23305
23361
  await handle.message(text, "followUp");
@@ -23314,6 +23370,24 @@ async function directMessage(record3, text, host) {
23314
23370
  await resumeDirect(record3, text, host);
23315
23371
  return "resumed";
23316
23372
  }
23373
+ async function interruptRun(record3, text) {
23374
+ const handle = record3.handle;
23375
+ if (handle?.message !== void 0) {
23376
+ try {
23377
+ await handle.message(text, "interrupt");
23378
+ return true;
23379
+ } catch (error2) {
23380
+ if (error2 instanceof AmaError && error2.code === "task_idle") return false;
23381
+ throw error2;
23382
+ }
23383
+ }
23384
+ if (handle?.interrupt === void 0) return false;
23385
+ const queued = record3.direct ?? [];
23386
+ const merged = [...queued, text].join("\n\n");
23387
+ if (!await handle.interrupt(merged)) return false;
23388
+ queued.splice(0);
23389
+ return true;
23390
+ }
23317
23391
  async function resumeDirect(record3, text, host) {
23318
23392
  const spawn9 = host.spawnSubagent;
23319
23393
  if (spawn9 === void 0)
@@ -23656,10 +23730,10 @@ var init_subagent_registry = __esm({
23656
23730
  return record3 === void 0 ? void 0 : liveOf(record3);
23657
23731
  }
23658
23732
  /** [W6-A] 人在子 Agent 视图里发的消息(subagent-direct.ts)。 */
23659
- async message(taskId, text) {
23733
+ async message(taskId, text, interrupt = false) {
23660
23734
  const record3 = this.tasks.get(taskId);
23661
23735
  if (record3 === void 0) throw new AmaError("task_not_found", `unknown task ${taskId}`);
23662
- return directMessage(record3, text, this.host);
23736
+ return directMessage(record3, text, this.host, interrupt);
23663
23737
  }
23664
23738
  async stop(taskId) {
23665
23739
  const record3 = this.tasks.get(taskId);
@@ -24109,6 +24183,7 @@ async function startAmaChild(parent, spec2, createChild, run7) {
24109
24183
  run7.signal.addEventListener("abort", onAbort, { once: true });
24110
24184
  let billed = { cacheRead: 0, prompt: 0, reBilled: 0 };
24111
24185
  let active = false;
24186
+ const forced2 = [];
24112
24187
  const runOnce = async (prompt, origin) => {
24113
24188
  const startTurns = turns;
24114
24189
  let error2;
@@ -24117,6 +24192,7 @@ async function startAmaChild(parent, spec2, createChild, run7) {
24117
24192
  if (run7.signal.aborted) throw new AmaError("aborted", "aborted");
24118
24193
  await child.prompt(prompt, origin === void 0 ? {} : { origin });
24119
24194
  for (; ; ) {
24195
+ while (forced2.length > 0) await forced2.shift();
24120
24196
  const left = child.clearQueue?.().followUp ?? [];
24121
24197
  if (left.length === 0 || run7.signal.aborted) break;
24122
24198
  await child.prompt(left.join("\n\n"), { origin: "direct" });
@@ -24161,6 +24237,10 @@ async function startAmaChild(parent, spec2, createChild, run7) {
24161
24237
  }
24162
24238
  if (!active || child.followUp === void 0)
24163
24239
  throw new AmaError("task_idle", "the sub-agent is not running");
24240
+ if (when === "interrupt") {
24241
+ forced2.push(child.prompt(text, { origin: "direct", interrupt: true }));
24242
+ return;
24243
+ }
24164
24244
  await child.followUp(text, { origin: "direct" });
24165
24245
  },
24166
24246
  wait: () => current3,
@@ -26088,6 +26168,8 @@ var init_session2 = __esm({
26088
26168
  // -------------------------------------------------------------------------
26089
26169
  async prompt(text, options = {}) {
26090
26170
  this.assertUsable();
26171
+ if (options.interrupt === true && this.cycle !== void 0)
26172
+ return this.interruptWith(text, options.images, options.origin);
26091
26173
  if (this.cycle !== void 0) {
26092
26174
  const behavior = options.streamingBehavior;
26093
26175
  if (behavior === void 0) {
@@ -26115,7 +26197,9 @@ var init_session2 = __esm({
26115
26197
  }
26116
26198
  async enqueueOrRun(text, queue, options) {
26117
26199
  this.assertUsable();
26118
- const origin = normalizeOrigin(options.origin ?? queue);
26200
+ if (options.interrupt === true && this.cycle !== void 0)
26201
+ return this.interruptWith(text, void 0, options.origin).then(() => "handled");
26202
+ const origin = normalizeOrigin(options.origin ?? (options.interrupt === true ? "user" : queue));
26119
26203
  if (this.cycle === void 0) {
26120
26204
  await this.startCycle((signal) => runPrompt(this.runDeps(), text, void 0, origin, signal));
26121
26205
  return "handled";
@@ -26129,6 +26213,19 @@ var init_session2 = __esm({
26129
26213
  followUp(text, options = {}) {
26130
26214
  return this.enqueueOrRun(text, "followUp", options);
26131
26215
  }
26216
+ /**
26217
+ * 打断并立即发送:取走排队的 steer、中止当前周期(工具按 abort 收尾),以「steer… + 本条」开新回合;
26218
+ * followUp 留在队列。没有可发的内容 → invalid_arguments(不打断)。
26219
+ */
26220
+ async interruptWith(text, images, origin) {
26221
+ const merged = mergeForInterrupt(this.agent.steeringQueue.snapshot(), text, images);
26222
+ if (merged === void 0) throw new AmaError("invalid_arguments", "nothing to send");
26223
+ this.agent.steeringQueue.clear();
26224
+ this.emitQueue();
26225
+ while (this.cycle !== void 0) await this.abort();
26226
+ const kind = normalizeOrigin(origin ?? "interrupt");
26227
+ return this.startCycle((s2) => runPrompt(this.runDeps(), merged.text, merged.images, kind, s2));
26228
+ }
26132
26229
  async abort() {
26133
26230
  const cycle = this.cycle;
26134
26231
  if (cycle === void 0) return;
@@ -30641,6 +30738,9 @@ var init_jsonrpc = __esm({
30641
30738
  });
30642
30739
 
30643
30740
  // src/drivers/acp/client.ts
30741
+ function mcpServersOf(options) {
30742
+ return [...options?.mcpServers ?? []];
30743
+ }
30644
30744
  function unattendedOutcome(params) {
30645
30745
  const reject2 = params.options.find((o) => o.kind === "reject_once");
30646
30746
  return {
@@ -30672,6 +30772,11 @@ var init_client = __esm({
30672
30772
  });
30673
30773
  }
30674
30774
  options;
30775
+ /**
30776
+ * 本版客户端支持的可选能力,供宿主做特性检测(旧版没有这个字段):
30777
+ * - `mcpServers`:开会话时可经 {@link AcpSessionOptions} 传 MCP 服务器。
30778
+ */
30779
+ static features = { mcpServers: true };
30675
30780
  peer;
30676
30781
  /** sessionId → 挂起权限请求的取消器。 */
30677
30782
  pendingPermissions = /* @__PURE__ */ new Map();
@@ -30721,16 +30826,28 @@ var init_client = __esm({
30721
30826
  supportsImages() {
30722
30827
  return this.agentCapabilities.promptCapabilities?.image === true;
30723
30828
  }
30724
- newSession(cwd, signal) {
30725
- return this.peer.request(ACP_METHODS.sessionNew, { cwd, mcpServers: [] }, signal);
30829
+ newSession(cwd, signal, options) {
30830
+ return this.peer.request(
30831
+ ACP_METHODS.sessionNew,
30832
+ { cwd, mcpServers: mcpServersOf(options) },
30833
+ signal
30834
+ );
30726
30835
  }
30727
30836
  /** 不回放历史(优先,R13:v2 计划取消 load)。 */
30728
- resumeSession(sessionId, cwd, signal) {
30729
- return this.peer.request(ACP_METHODS.sessionResume, { sessionId, cwd, mcpServers: [] }, signal);
30837
+ resumeSession(sessionId, cwd, signal, options) {
30838
+ return this.peer.request(
30839
+ ACP_METHODS.sessionResume,
30840
+ { sessionId, cwd, mcpServers: mcpServersOf(options) },
30841
+ signal
30842
+ );
30730
30843
  }
30731
30844
  /** 回放历史(以 session/update 通知)。 */
30732
- loadSession(sessionId, cwd, signal) {
30733
- return this.peer.request(ACP_METHODS.sessionLoad, { sessionId, cwd, mcpServers: [] }, signal);
30845
+ loadSession(sessionId, cwd, signal, options) {
30846
+ return this.peer.request(
30847
+ ACP_METHODS.sessionLoad,
30848
+ { sessionId, cwd, mcpServers: mcpServersOf(options) },
30849
+ signal
30850
+ );
30734
30851
  }
30735
30852
  listSessions(cwd, cursor) {
30736
30853
  return this.peer.request(ACP_METHODS.sessionList, {
@@ -32742,7 +32859,7 @@ function failed3(text, sessionId, runner) {
32742
32859
  ...sessionId !== "" ? { sessionRef: { runner, sessionId } } : {}
32743
32860
  };
32744
32861
  }
32745
- var RUN_TIMEOUT_MS, IDLE_CLOSE_MS, ProcessRunner, ProcessHandle;
32862
+ var RUN_TIMEOUT_MS, IDLE_CLOSE_MS, ProcessRunner, TURN_INTERRUPT, ProcessHandle;
32746
32863
  var init_runner = __esm({
32747
32864
  "src/drivers/runner.ts"() {
32748
32865
  "use strict";
@@ -32828,6 +32945,12 @@ var init_runner = __esm({
32828
32945
  return this.deps;
32829
32946
  }
32830
32947
  };
32948
+ TURN_INTERRUPT = /* @__PURE__ */ new Set([
32949
+ "acp",
32950
+ "acp-adapter",
32951
+ "claude-stream",
32952
+ "codex-app-server"
32953
+ ]);
32831
32954
  ProcessHandle = class {
32832
32955
  constructor(runner, chosen, request, deps) {
32833
32956
  this.runner = runner;
@@ -32899,8 +33022,22 @@ var init_runner = __esm({
32899
33022
  }
32900
33023
  this.chain = this.chain.then(() => this.runTurn(text));
32901
33024
  }
32902
- wait() {
32903
- return this.chain;
33025
+ /** 跟到链尾:回合进行中被 `interrupt` 接上的下一回合也算在这次运行里。 */
33026
+ async wait() {
33027
+ for (; ; ) {
33028
+ const chain = this.chain;
33029
+ const result = await chain;
33030
+ if (chain === this.chain) return result;
33031
+ }
33032
+ }
33033
+ /** 子 Agent 视图的「打断并发送」:中断当前回合,紧接着以 `text` 开下一回合。 */
33034
+ async interrupt(text) {
33035
+ const session = this.session;
33036
+ if (this.stopped || !this.busy || session === void 0) return false;
33037
+ if (!TURN_INTERRUPT.has(this.chosen.driver.kind)) return false;
33038
+ this.chain = this.chain.then(() => this.runTurn(text));
33039
+ await session.cancel();
33040
+ return true;
32904
33041
  }
32905
33042
  async stop() {
32906
33043
  if (this.stopped) return;
@@ -34957,10 +35094,11 @@ function window(used, minutes, resetSeconds) {
34957
35094
  if (usedPercent === void 0) return void 0;
34958
35095
  const out = { usedPercent: Math.max(0, Math.min(100, usedPercent)) };
34959
35096
  const m = num(minutes);
34960
- if (m !== void 0) out.windowMinutes = m;
35097
+ if (m !== void 0 && m > 0) out.windowMinutes = m;
34961
35098
  const reset = num(resetSeconds);
34962
35099
  if (reset !== void 0 && reset > 0) out.resetsAt = reset * 1e3;
34963
- return out;
35100
+ const empty = out.usedPercent === 0 && out.windowMinutes === void 0 && out.resetsAt === void 0;
35101
+ return empty ? void 0 : out;
34964
35102
  }
34965
35103
  function snapshot(primary, secondary, plan2) {
34966
35104
  const out = {};
@@ -35306,7 +35444,14 @@ var init_key_docs = __esm({
35306
35444
  codemode: { inlineBudget: DEFAULT_INLINE_BUDGET, requireStrict: false },
35307
35445
  cache: { ...DEFAULT_CACHE_CONFIG },
35308
35446
  request: { idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS },
35309
- ui: { compact: false, logo: "auto", animation: true, restoreOnCancel: true, language: "auto" },
35447
+ ui: {
35448
+ compact: false,
35449
+ logo: "auto",
35450
+ animation: true,
35451
+ restoreOnCancel: true,
35452
+ enterWhileRunning: "queue",
35453
+ language: "auto"
35454
+ },
35310
35455
  // [W6-C0] 第六波键的缺省(行为由各批次实现)
35311
35456
  memory: {
35312
35457
  enabled: false,
@@ -35709,6 +35854,7 @@ function buildBaseSchema() {
35709
35854
  logo: oneOf(["auto", "off"]),
35710
35855
  animation: bool(),
35711
35856
  restoreOnCancel: bool(),
35857
+ enterWhileRunning: oneOf(["queue", "interrupt"]),
35712
35858
  statusLine: oneOf(STATUS_LINE_MODES),
35713
35859
  language: oneOf(LANGUAGE_SETTINGS),
35714
35860
  replyLanguage: str3(),
@@ -39722,6 +39868,7 @@ var init_settings_registry = __esm({
39722
39868
  row("ui.logo", "ui", "restart", "any"),
39723
39869
  row("ui.statusLine", "ui", "now", "any"),
39724
39870
  row("ui.restoreOnCancel", "ui", "nextSession", "any"),
39871
+ row("ui.enterWhileRunning", "ui", "now", "any"),
39725
39872
  row("ui.quietStartup", "ui", "restart", "any"),
39726
39873
  row("ui.ascii", "ui", "restart", "any", { envOverride: "AMA_ASCII" }),
39727
39874
  row("ui.language", "ui", "restart", "any", { envOverride: "AMA_LANG" }),
@@ -41477,6 +41624,13 @@ var init_keybindings = __esm({
41477
41624
  "app.exit": ["ctrl+d"],
41478
41625
  "app.message.followUp": ["alt+enter"],
41479
41626
  "app.message.dequeue": ["alt+up"],
41627
+ /**
41628
+ * 运行中打断当前回合并立即发送输入框的文字(连同排队的插话);`ui.enterWhileRunning: "interrupt"` 时与
41629
+ * Enter 互换(它变成排队)。缺省 `Ctrl+X`:各终端(macOS Terminal、iTerm2、tmux、Windows Terminal)都原样
41630
+ * 送达 0x18,且没有别的绑定;`Ctrl+Enter` 不开 kitty 键盘协议时与 Enter 无法区分,`Ctrl+S` 可能被 XOFF
41631
+ * 流控吃掉,`Alt+Enter` 已是 followUp,`Alt+<字母>` 在 macOS 缺省不送 Meta,`Ctrl+]` 在非美式键盘难按。
41632
+ */
41633
+ "app.message.interrupt": ["ctrl+x"],
41480
41634
  "app.permission.cycle": ["shift+tab", "tab"],
41481
41635
  "app.tools.expand": ["ctrl+o"],
41482
41636
  "app.model.select": ["ctrl+l"],
@@ -44928,34 +45082,46 @@ function windowSpan(minutes) {
44928
45082
  if (minutes % 60 === 0) return `${minutes / 60}h`;
44929
45083
  return `${Math.round(minutes)}m`;
44930
45084
  }
44931
- function labels(w, which) {
44932
- const m = msg().interactive.statusLine.quota;
44933
- const minutes = w.windowMinutes;
44934
- const standard = minutes === void 0 || minutes === (which === "primary" ? FIVE_HOURS : WEEK);
44935
- if (!standard)
44936
- return { pct: m.window(windowSpan(minutes)), reset: m.windowReset(windowSpan(minutes)) };
44937
- return which === "primary" ? { pct: m.session, reset: m.reset } : { pct: m.weekly, reset: m.weeklyReset };
45085
+ function minutesOf(w) {
45086
+ const m = w.windowMinutes;
45087
+ return m !== void 0 && Number.isFinite(m) && m > 0 ? m : void 0;
45088
+ }
45089
+ function kindOf(minutes) {
45090
+ if (minutes === FIVE_HOURS) return "session";
45091
+ if (minutes === WEEK) return "weekly";
45092
+ return minutes;
44938
45093
  }
44939
- function shortLabel(w, which) {
45094
+ function labels(kind) {
44940
45095
  const m = msg().interactive.statusLine.quota;
44941
- const minutes = w.windowMinutes;
44942
- if (minutes === FIVE_HOURS || minutes === void 0 && which === "primary") return m.short5h;
44943
- if (minutes === WEEK || minutes === void 0 && which === "secondary") return m.shortWeek;
44944
- return windowSpan(minutes);
45096
+ if (kind === "session") return { pct: m.session, reset: m.reset, short: m.short5h };
45097
+ if (kind === "weekly") return { pct: m.weekly, reset: m.weeklyReset, short: m.shortWeek };
45098
+ const span = windowSpan(kind);
45099
+ return { pct: m.window(span), reset: m.windowReset(span), short: span };
44945
45100
  }
44946
45101
  function percent2(theme, used, decimal) {
44947
45102
  return theme.fg(levelColor(used / 100), decimal ? `${used.toFixed(1)}%` : `${Math.round(used)}%`);
44948
45103
  }
44949
- function windows(quota) {
44950
- const out = [];
44951
- if (quota.primary !== void 0) out.push(["primary", quota.primary]);
44952
- if (quota.secondary !== void 0) out.push(["secondary", quota.secondary]);
44953
- return out;
45104
+ function hasData(w) {
45105
+ return w.usedPercent !== 0 || minutesOf(w) !== void 0 || w.resetsAt !== void 0;
45106
+ }
45107
+ function shownWindows(quota) {
45108
+ const slots = ["primary", "secondary"].map((which) => ({ which, w: quota[which] })).filter((x) => x.w !== void 0).filter((x) => hasData(x.w));
45109
+ const known = slots.map((x) => kindOf(minutesOf(x.w)));
45110
+ const out = slots.map((x, i) => {
45111
+ const own = known[i];
45112
+ if (own !== void 0) return { kind: own, window: x.w };
45113
+ const guess = x.which === "primary" ? "session" : "weekly";
45114
+ const taken = known.some((k, j) => j !== i && k === guess);
45115
+ const kind = taken ? guess === "session" ? "weekly" : "session" : guess;
45116
+ return { kind, window: x.w };
45117
+ });
45118
+ const span = (k) => typeof k === "number" ? k : SPAN[k];
45119
+ return out.sort((a, b) => span(a.kind) - span(b.kind));
44954
45120
  }
44955
45121
  function compactQuotaItem(quota, theme) {
44956
45122
  const dim = (text) => theme.fg("dim", text);
44957
- const items = windows(quota).map(
44958
- ([which, w]) => `${dim(shortLabel(w, which))} ${percent2(theme, w.usedPercent, false)}`
45123
+ const items = shownWindows(quota).map(
45124
+ ({ kind, window: w }) => `${dim(labels(kind).short)} ${percent2(theme, w.usedPercent, false)}`
44959
45125
  );
44960
45126
  return items.length === 0 ? void 0 : items.join(" ");
44961
45127
  }
@@ -44963,30 +45129,31 @@ function quotaParts2(quota, width2, now, theme) {
44963
45129
  const dim = (text) => theme.fg("dim", text);
44964
45130
  const narrow = width2 < QUOTA_NARROW_WIDTH;
44965
45131
  const parts = [];
44966
- windows(quota).forEach(([which, w], i) => {
45132
+ shownWindows(quota).forEach(({ kind, window: w }, i) => {
44967
45133
  const first = i === 0;
44968
- const group = narrow ? which : void 0;
44969
- const name = narrow ? `${shortLabel(w, which)} ` : labels(w, which).pct;
45134
+ const group = narrow ? `w${i}` : void 0;
45135
+ const label2 = labels(kind);
45136
+ const name = narrow ? `${label2.short} ` : label2.pct;
44970
45137
  parts.push({
44971
45138
  text: dim(name) + percent2(theme, w.usedPercent, !narrow),
44972
45139
  priority: first ? void 0 : 2,
44973
- zone: "left",
45140
+ zone: "right",
44974
45141
  ...group !== void 0 ? { group } : {}
44975
45142
  });
44976
45143
  if (w.resetsAt === void 0) return;
44977
45144
  const left = formatRemaining(w.resetsAt - now, narrow);
44978
45145
  parts.push({
44979
- text: narrow ? theme.fg("tool", `${theme.glyphs.retry}${left}`) : dim(labels(w, which).reset) + theme.fg("tool", left),
45146
+ text: narrow ? theme.fg("tool", `${theme.glyphs.retry}${left}`) : dim(label2.reset) + theme.fg("tool", left),
44980
45147
  priority: first ? 3 : 1,
44981
- zone: "left",
45148
+ zone: "right",
44982
45149
  // 宽格式按最宽形状占位(`23h 59m`),数值变化不让某项时有时无
44983
- reserve: narrow ? 0 : visibleWidth(labels(w, which).reset) + 7,
45150
+ reserve: narrow ? 0 : visibleWidth(label2.reset) + 7,
44984
45151
  ...group !== void 0 ? { group } : {}
44985
45152
  });
44986
45153
  });
44987
45154
  return parts;
44988
45155
  }
44989
- var QUOTA_NARROW_WIDTH, FIVE_HOURS, WEEK, QuotaLine;
45156
+ var QUOTA_NARROW_WIDTH, FIVE_HOURS, WEEK, SPAN, QuotaLine;
44990
45157
  var init_status_quota = __esm({
44991
45158
  "src/modes/interactive/status-quota.ts"() {
44992
45159
  "use strict";
@@ -44996,6 +45163,7 @@ var init_status_quota = __esm({
44996
45163
  QUOTA_NARROW_WIDTH = 80;
44997
45164
  FIVE_HOURS = 300;
44998
45165
  WEEK = 10080;
45166
+ SPAN = { session: FIVE_HOURS, weekly: WEEK };
44999
45167
  QuotaLine = class {
45000
45168
  constructor(source, theme) {
45001
45169
  this.source = source;
@@ -45009,7 +45177,8 @@ var init_status_quota = __esm({
45009
45177
  if (view === void 0) return [];
45010
45178
  const theme = this.theme;
45011
45179
  if (view === "pending") {
45012
- return [truncateToWidth(theme.fg("dim", msg().interactive.statusLine.quota.pending), width2)];
45180
+ const text = theme.fg("dim", msg().interactive.statusLine.quota.pending);
45181
+ return [layoutRow([{ text, priority: void 0, zone: "right" }], width2, theme)];
45013
45182
  }
45014
45183
  const parts = quotaParts2(view.quota, width2, this.source.now(), theme);
45015
45184
  if (parts.length === 0) return [];
@@ -45085,9 +45254,10 @@ function layoutRow(parts, width2, theme, style = {}) {
45085
45254
  let kept = [...parts];
45086
45255
  const zone = (z) => kept.filter((p) => p.zone === z);
45087
45256
  const fits = () => {
45088
- const l2 = join66(zone("left"))[1];
45257
+ const left = zone("left");
45089
45258
  const right = zone("right");
45090
- return l2 + (right.length === 0 ? 0 : sep5.length + join66(right)[1]) <= width2;
45259
+ const between = left.length === 0 || right.length === 0 ? 0 : sep5.length;
45260
+ return join66(left)[1] + between + join66(right)[1] <= width2;
45091
45261
  };
45092
45262
  while (!fits()) {
45093
45263
  const droppable = kept.filter((p) => p.priority !== void 0);
@@ -45098,7 +45268,7 @@ function layoutRow(parts, width2, theme, style = {}) {
45098
45268
  const l = join66(zone("left"))[0];
45099
45269
  const r = join66(zone("right"))[0];
45100
45270
  const gap = width2 - visibleWidth(l) - visibleWidth(r);
45101
- const line2 = r === "" ? l : gap >= MIN_GAP ? l + " ".repeat(gap) + r : l + dim(sep5) + r;
45271
+ const line2 = r === "" ? l : l === "" ? " ".repeat(Math.max(0, gap)) + r : gap >= MIN_GAP ? l + " ".repeat(gap) + r : l + dim(sep5) + r;
45102
45272
  return truncateToWidth(line2, width2);
45103
45273
  }
45104
45274
  function abbreviateModel(ref, width2) {
@@ -45230,7 +45400,7 @@ var init_status_bar = __esm({
45230
45400
  }
45231
45401
  const model = state.model === void 0 ? "?" : abbreviateModel(formatModelRef(state.model), width2);
45232
45402
  const p = full ? FULL : COMPACT;
45233
- const fallback = this.source.fallback?.();
45403
+ const fallback = full ? void 0 : this.source.fallback?.();
45234
45404
  const modelText = fallback === void 0 ? theme.fg("accent", model) : theme.fg("accent", abbreviateModel(fallback.from, width2)) + dim(g.ascii ? " -> " : " → ") + theme.fg("warning", abbreviateModel(fallback.to, width2));
45235
45405
  right(modelText, p.model, full ? { group: "model" } : {});
45236
45406
  if (state.thinkingLevel !== "off") {
@@ -49024,6 +49194,13 @@ async function runSlashCommand(line2, ctx) {
49024
49194
  return planCommand(session, args, ctx);
49025
49195
  case "tasks":
49026
49196
  return { kind: "handled", message: await tasksCommand(session, args) };
49197
+ case "interrupt": {
49198
+ if (args === "") throw new AmaError("invalid_arguments", msg().report.command.interruptUsage);
49199
+ const { isStreaming, isCompacting } = session.state;
49200
+ if (!isStreaming && !isCompacting) return { kind: "prompt", text: args };
49201
+ void session.prompt(args, { interrupt: true }).catch(() => void 0);
49202
+ return { kind: "handled", message: msg().report.command.interruptSent(args) };
49203
+ }
49027
49204
  case "agents":
49028
49205
  return { kind: "handled", message: describeAgents(session.state.sessionId) };
49029
49206
  case "paste": {
@@ -49079,6 +49256,7 @@ var init_commands_core = __esm({
49079
49256
  command("tasks", "tasks", "[id] | stop <id> | bg [id]"),
49080
49257
  command("agents", "agents"),
49081
49258
  command("paste", "paste"),
49259
+ command("interrupt", "interrupt", { key: "interruptArgs" }),
49082
49260
  command("exit", "exit"),
49083
49261
  // [W6-C0] 第六波(W6-S / W6-T1 / W6-M 实现)
49084
49262
  command("config", "config", "[key=value]"),
@@ -49129,6 +49307,11 @@ function applyPlanAttendance(ctx) {
49129
49307
  ctx.capabilities.has("plans") ? "client" : "unattended"
49130
49308
  );
49131
49309
  }
49310
+ function interruptFlag(value) {
49311
+ if (value !== void 0 && typeof value !== "boolean")
49312
+ throw new AmaError("invalid_arguments", "interrupt must be a boolean");
49313
+ return value === true;
49314
+ }
49132
49315
  async function startRun(ctx, run7) {
49133
49316
  const session = ctx.session();
49134
49317
  let settled = false;
@@ -49251,10 +49434,17 @@ var init_commands = __esm({
49251
49434
  ctx,
49252
49435
  () => ctx.session().prompt(p.message, {
49253
49436
  ...p.images !== void 0 ? { images: p.images } : {},
49254
- ...p.streamingBehavior !== void 0 ? { streamingBehavior: p.streamingBehavior } : {}
49437
+ ...p.streamingBehavior !== void 0 ? { streamingBehavior: p.streamingBehavior } : {},
49438
+ ...interruptFlag(p.interrupt) ? { interrupt: true } : {}
49255
49439
  })
49256
49440
  ),
49257
- steer: (p, ctx) => startRun(ctx, () => ctx.session().steer(p.message)),
49441
+ steer: (p, ctx) => startRun(
49442
+ ctx,
49443
+ () => interruptFlag(p.interrupt) ? ctx.session().prompt(p.message, {
49444
+ interrupt: true,
49445
+ ...p.images !== void 0 ? { images: p.images } : {}
49446
+ }) : ctx.session().steer(p.message)
49447
+ ),
49258
49448
  follow_up: (p, ctx) => startRun(ctx, () => ctx.session().followUp(p.message)),
49259
49449
  abort: async (_p, ctx) => {
49260
49450
  await ctx.session().abort();
@@ -52879,13 +53069,13 @@ ${bypassRiskLines().map((l) => ` ${l}
52879
53069
  void ed.ask(`
52880
53070
  ${preview.join("")}${question}`, "n").then((answer) => {
52881
53071
  resolve39(answer === "y" ? "allow" : answer === "a" ? "allow_session" : "deny");
52882
- if (!busy) ed.render();
53072
+ if (running === 0) ed.render();
52883
53073
  });
52884
53074
  })
52885
53075
  });
52886
53076
  runtime.notifier.set((message, level) => err(`ama: [${level}] ${message}
52887
53077
  `));
52888
- let busy = false;
53078
+ let running = 0;
52889
53079
  let lastInterrupt = 0;
52890
53080
  return new Promise((resolve39) => {
52891
53081
  let finished = false;
@@ -52905,11 +53095,23 @@ ${preview.join("")}${question}`, "n").then((answer) => {
52905
53095
  });
52906
53096
  };
52907
53097
  const run7 = (text) => {
52908
- busy = true;
53098
+ running++;
52909
53099
  void handle(text).then((outcome) => {
52910
- busy = false;
53100
+ running--;
52911
53101
  if (outcome === "exit") finish2(ExitCode.Ok);
52912
- else ed.render();
53102
+ else if (running === 0) ed.render();
53103
+ });
53104
+ };
53105
+ const interruptWith = (text) => {
53106
+ if (text === "") return err(`ama: ${msg().report.command.interruptUsage}
53107
+ `);
53108
+ out(`${msg().report.command.interruptSent(text)}
53109
+ `);
53110
+ running++;
53111
+ void session.prompt(text, { interrupt: true }).catch((error2) => err(`ama: ${errorText3(error2)}
53112
+ `)).finally(() => {
53113
+ printer.endLine();
53114
+ if (--running === 0) ed.render();
52913
53115
  });
52914
53116
  };
52915
53117
  let flushTimer;
@@ -52917,7 +53119,7 @@ ${preview.join("")}${question}`, "n").then((answer) => {
52917
53119
  for (const action of actions) {
52918
53120
  if (action.kind === "eof") return finish2(ExitCode.Ok);
52919
53121
  if (action.kind === "interrupt") {
52920
- if (busy) {
53122
+ if (running > 0) {
52921
53123
  void session.abort();
52922
53124
  err(`${msg().interactive.line.interrupted}
52923
53125
  `);
@@ -52932,7 +53134,12 @@ ${msg().interactive.line.ctrlCAgain}
52932
53134
  }
52933
53135
  continue;
52934
53136
  }
52935
- if (busy) {
53137
+ if (running > 0) {
53138
+ const forced2 = /^\/interrupt(?:\s+([\s\S]*))?$/.exec(action.text.trim());
53139
+ if (forced2 !== null) {
53140
+ interruptWith((forced2[1] ?? "").trim());
53141
+ continue;
53142
+ }
52936
53143
  if (/^\/tasks\s+bg(\s+\S+)?\s*$/.test(action.text.trim())) {
52937
53144
  void handle(action.text.trim());
52938
53145
  continue;
@@ -53421,7 +53628,7 @@ var init_agent_view = __esm({
53421
53628
  maxVisibleLines: 4,
53422
53629
  placeholder: msg().agents.view.placeholder(taskId),
53423
53630
  requestRender: () => deps.render(),
53424
- onSubmit: (text) => void this.submit(text)
53631
+ onSubmit: (text) => void this.submit(text, this.enterInterrupts())
53425
53632
  });
53426
53633
  this.attach();
53427
53634
  }
@@ -53519,7 +53726,10 @@ var init_agent_view = __esm({
53519
53726
  this.attach();
53520
53727
  this.deps.onSwitch?.(next);
53521
53728
  }
53522
- async submit(text) {
53729
+ enterInterrupts() {
53730
+ return this.deps.enterMode?.() === "interrupt";
53731
+ }
53732
+ async submit(text, interrupt = false) {
53523
53733
  const m = msg().agents.view;
53524
53734
  const trimmed = text.trim();
53525
53735
  if (trimmed === "") return;
@@ -53545,7 +53755,7 @@ var init_agent_view = __esm({
53545
53755
  const registry2 = this.deps.registry();
53546
53756
  try {
53547
53757
  if (registry2 === void 0) throw new Error(m.unknown(taskId));
53548
- const reply = await registry2.message(taskId, text);
53758
+ const reply = await registry2.message(taskId, text, interrupt);
53549
53759
  this.flash = m[reply](taskId);
53550
53760
  } catch (error2) {
53551
53761
  this.flash = m.sendFailed(error2 instanceof Error ? error2.message : String(error2));
@@ -53565,6 +53775,11 @@ var init_agent_view = __esm({
53565
53775
  return;
53566
53776
  }
53567
53777
  }
53778
+ if (keys.matches(data2, "app.message.interrupt") && !this.editor.isCompletionOpen) {
53779
+ const text = this.editor.takeSubmission();
53780
+ if (text !== null) void this.submit(text, !this.enterInterrupts());
53781
+ return;
53782
+ }
53568
53783
  if (keys.matches(data2, "tui.select.cancel") && !this.editor.isCompletionOpen) {
53569
53784
  if (!empty) this.editor.clear();
53570
53785
  else this.deps.close();
@@ -54691,6 +54906,7 @@ var init_agent_ui = __esm({
54691
54906
  close: () => this.closeView(),
54692
54907
  stop: (id) => stopTask(this.deps.session().state.sessionId, id),
54693
54908
  background: (id) => this.deps.session().backgroundTask(id, "user"),
54909
+ enterMode: () => this.deps.area?.ui().enterWhileRunning === "interrupt" ? "interrupt" : "queue",
54694
54910
  messages: {
54695
54911
  ...ui.showThinking !== void 0 ? { showThinking: ui.showThinking } : {},
54696
54912
  ...ui.markdown !== void 0 ? { markdown: ui.markdown } : {},
@@ -55624,16 +55840,18 @@ var init_status_line = __esm({
55624
55840
  init_status_bar();
55625
55841
  RESERVE = { tps: 14, amount: 16, avg: 7, ttft: 9 };
55626
55842
  PRIORITY = {
55627
- ttft: 1,
55628
- avg: 2,
55629
- host: 3,
55843
+ codemode: 1,
55844
+ sandbox: 2,
55845
+ fallback: 3,
55630
55846
  preset: 4,
55631
- rebill: 5,
55632
- cache: 6,
55633
- tokens: 7,
55634
- queue: 8,
55635
- codemode: 9,
55636
- amount: 10
55847
+ queue: 5,
55848
+ host: 6,
55849
+ ttft: 7,
55850
+ avg: 8,
55851
+ rebill: 9,
55852
+ cache: 10,
55853
+ tokens: 11,
55854
+ amount: 12
55637
55855
  };
55638
55856
  COLLAPSE_HINT = "[-]";
55639
55857
  RATE_STYLE = { groups: { stats: { glue: " · ", open: "(", close: ")" } } };
@@ -55646,7 +55864,7 @@ var init_status_line = __esm({
55646
55864
  bar;
55647
55865
  source;
55648
55866
  theme;
55649
- parts() {
55867
+ parts(width2) {
55650
55868
  const theme = this.theme;
55651
55869
  const dim = (text) => theme.fg("dim", text);
55652
55870
  const stats = this.bar.current();
@@ -55654,9 +55872,9 @@ var init_status_line = __esm({
55654
55872
  const live2 = telemetry?.live;
55655
55873
  const last = telemetry?.last;
55656
55874
  const parts = [];
55657
- const left = (text, priority, reserve, extra = {}) => void parts.push({ text, priority, zone: "left", reserve, ...extra });
55658
- const right = (text, priority) => {
55659
- if (text !== void 0) parts.push({ text, priority, zone: "right" });
55875
+ const metric = (text, priority, reserve, extra = {}) => void parts.push({ text, priority, zone: "right", reserve, ...extra });
55876
+ const add3 = (zone, text, priority) => {
55877
+ if (text !== void 0) parts.push({ text, priority, zone });
55660
55878
  };
55661
55879
  const blue = (text) => theme.fg("accent", text);
55662
55880
  const purple = (text) => theme.fg("tool", text);
@@ -55673,20 +55891,25 @@ var init_status_line = __esm({
55673
55891
  }
55674
55892
  }
55675
55893
  const label2 = live2 !== void 0 ? theme.fg("accent", "tps:") : dim("tps:");
55676
- left(
55894
+ metric(
55677
55895
  `${label2} ${tps === void 0 ? dim("—") : `${purple(formatRate(tps))}${dim(" tok/s")}`}`,
55678
55896
  void 0,
55679
55897
  RESERVE.tps
55680
55898
  );
55681
55899
  if (amount !== void 0) {
55682
- left(amount, PRIORITY.amount, RESERVE.amount, { lead: ` ${theme.glyphs.dot} ` });
55900
+ metric(amount, PRIORITY.amount, RESERVE.amount, { lead: ` ${theme.glyphs.dot} ` });
55683
55901
  }
55684
55902
  const stat16 = { group: "stats", lead: " " };
55685
55903
  if (telemetry?.avgTps !== void 0) {
55686
- left(`${dim("avg ")}${blue(formatRate(telemetry.avgTps))}`, PRIORITY.avg, RESERVE.avg, stat16);
55904
+ metric(
55905
+ `${dim("avg ")}${blue(formatRate(telemetry.avgTps))}`,
55906
+ PRIORITY.avg,
55907
+ RESERVE.avg,
55908
+ stat16
55909
+ );
55687
55910
  }
55688
55911
  if (last?.ttftMs !== void 0) {
55689
- left(
55912
+ metric(
55690
55913
  `${dim("ttft ")}${purple(formatSeconds2(last.ttftMs))}`,
55691
55914
  PRIORITY.ttft,
55692
55915
  RESERVE.ttft,
@@ -55694,20 +55917,29 @@ var init_status_line = __esm({
55694
55917
  );
55695
55918
  }
55696
55919
  const usage = usageItems(stats, this.bar.queued(), this.source, theme);
55697
- right(usage.tokens, PRIORITY.tokens);
55698
- right(usage.cache, PRIORITY.cache);
55699
- right(usage.rebill, PRIORITY.rebill);
55700
- right(usage.queue, PRIORITY.queue);
55701
- right(usage.codemode, PRIORITY.codemode);
55702
- right(usage.sandbox, PRIORITY.codemode);
55703
- right(usage.preset, PRIORITY.preset);
55704
- right(usage.host, PRIORITY.host);
55705
- right(dim(COLLAPSE_HINT), void 0);
55920
+ add3("right", usage.tokens, PRIORITY.tokens);
55921
+ add3("right", usage.cache, PRIORITY.cache);
55922
+ add3("right", usage.rebill, PRIORITY.rebill);
55923
+ add3("right", dim(COLLAPSE_HINT), void 0);
55924
+ add3("left", usage.codemode, PRIORITY.codemode);
55925
+ add3("left", usage.sandbox, PRIORITY.sandbox);
55926
+ add3("left", usage.preset, PRIORITY.preset);
55927
+ const fallback = this.source.fallback?.();
55928
+ if (fallback !== void 0) {
55929
+ const arrow = theme.glyphs.ascii ? "-> " : "→ ";
55930
+ add3(
55931
+ "left",
55932
+ dim(arrow) + theme.fg("warning", abbreviateModel(fallback.to, width2)),
55933
+ PRIORITY.fallback
55934
+ );
55935
+ }
55936
+ add3("left", usage.queue, PRIORITY.queue);
55937
+ add3("left", usage.host, PRIORITY.host);
55706
55938
  return parts;
55707
55939
  }
55708
55940
  render(width2) {
55709
55941
  if (this.bar.layout() !== "full") return [];
55710
- return [truncateToWidth(layoutRow(this.parts(), width2, this.theme, RATE_STYLE), width2)];
55942
+ return [truncateToWidth(layoutRow(this.parts(width2), width2, this.theme, RATE_STYLE), width2)];
55711
55943
  }
55712
55944
  invalidate() {
55713
55945
  }
@@ -56008,6 +56240,27 @@ ${current3}`);
56008
56240
  if (typeof answer === "boolean") settle(answer);
56009
56241
  else void answer.then(settle);
56010
56242
  };
56243
+ const sendKeys = (is) => {
56244
+ if (deps.interruptSend === void 0 || editor.isCompletionOpen) return false;
56245
+ const viaKey = is("app.message.interrupt");
56246
+ if (!deps.busy()) {
56247
+ if (!viaKey || editor.isEmpty()) return false;
56248
+ const text = editor.takeSubmission();
56249
+ if (text !== null) deps.submit(text, "enter");
56250
+ return true;
56251
+ }
56252
+ const interruptMode = deps.enterWhileRunning?.() === "interrupt";
56253
+ if (!viaKey && !(interruptMode && is("tui.editor.submit"))) return false;
56254
+ const command3 = editor.getExpandedText().trimStart().startsWith("/");
56255
+ if (command3 || viaKey === interruptMode) {
56256
+ if (!viaKey) return false;
56257
+ const text = editor.takeSubmission();
56258
+ if (text !== null) deps.submit(text, "enter");
56259
+ return true;
56260
+ }
56261
+ deps.interruptSend(editor.takeSubmission() ?? "");
56262
+ return true;
56263
+ };
56011
56264
  const atLastLine = () => editor.cursor.line >= editor.getText().split("\n").length - 1;
56012
56265
  const focusAgents = (agents, data2) => {
56013
56266
  if (editor.isCompletionOpen || editor.isBrowsingHistory) return false;
@@ -56064,6 +56317,7 @@ ${current3}`);
56064
56317
  return true;
56065
56318
  }
56066
56319
  ctrlCArmedAt = Number.NEGATIVE_INFINITY;
56320
+ if (sendKeys(is)) return true;
56067
56321
  if (is("app.interrupt") && !editor.isCompletionOpen && deps.busy()) {
56068
56322
  esc.reset();
56069
56323
  interrupt();
@@ -56138,6 +56392,34 @@ var init_key_dispatch = __esm({
56138
56392
  }
56139
56393
  });
56140
56394
 
56395
+ // src/modes/interactive/interrupt-send.ts
56396
+ function interruptSender(deps) {
56397
+ return (text) => {
56398
+ const images = deps.takeImages();
56399
+ deps.showHint(msg().interactive.keys.interruptSent);
56400
+ const options = images.length > 0 ? { interrupt: true, images } : { interrupt: true };
56401
+ void deps.session().prompt(text, options).catch((error2) => {
56402
+ if (isAmaError(error2) && error2.code === "invalid_arguments")
56403
+ deps.showHint(msg().interactive.keys.nothingToSend);
56404
+ });
56405
+ };
56406
+ }
56407
+ function enterModeOf(runtime) {
56408
+ return runtime.config.ui?.enterWhileRunning === "interrupt" ? "interrupt" : "queue";
56409
+ }
56410
+ function interruptKeys(keys, mode) {
56411
+ const key = keyLabel(keys, "app.message.interrupt");
56412
+ return { key, now: () => mode() === "interrupt" ? "Enter" : key };
56413
+ }
56414
+ var init_interrupt_send = __esm({
56415
+ "src/modes/interactive/interrupt-send.ts"() {
56416
+ "use strict";
56417
+ init_errors();
56418
+ init_i18n();
56419
+ init_task_background();
56420
+ }
56421
+ });
56422
+
56141
56423
  // src/modes/interactive/model-picker.ts
56142
56424
  async function openModelPicker(host, options) {
56143
56425
  const catalog = await loadModelCatalog(options.providers);
@@ -57500,10 +57782,7 @@ var init_run_indicator = __esm({
57500
57782
  RunIndicator = class {
57501
57783
  constructor(deps) {
57502
57784
  this.deps = deps;
57503
- deps.loader.onFrame(() => {
57504
- if (this.busy && !this.approval && this.optionalHints().join("\n") !== this.hintKey)
57505
- this.applyVerb();
57506
- });
57785
+ deps.loader.onFrame(() => this.refreshHints());
57507
57786
  }
57508
57787
  deps;
57509
57788
  running = false;
@@ -57517,12 +57796,24 @@ var init_run_indicator = __esm({
57517
57796
  background = () => false;
57518
57797
  /** [W7-C] 有停靠在 Agent 栏的审批。 */
57519
57798
  docked = () => false;
57799
+ /** 输入框有字(显示 Enter / 专用键的提示)。 */
57800
+ draft = () => false;
57801
+ /** 运行中 Enter 的语义。 */
57802
+ enterMode = () => "queue";
57520
57803
  hintKey = "";
57804
+ /** 可丢弃提示变了才换动词(每帧、输入框内容变化时调)。 */
57805
+ refreshHints() {
57806
+ if (this.busy && !this.approval && this.optionalHints().join("\n") !== this.hintKey)
57807
+ this.applyVerb();
57808
+ }
57521
57809
  /** 可丢弃的按键提示(按丢弃的逆序排列)。 */
57522
57810
  optionalHints() {
57523
57811
  const down = this.deps.theme.glyphs.arrowDown;
57524
57812
  const key = this.deps.backgroundKey;
57525
57813
  const out = [];
57814
+ const send2 = this.deps.interruptKey;
57815
+ if (send2 !== void 0 && this.draft())
57816
+ out.push(msg().interactive.view.run.sendHint(this.enterMode(), send2));
57526
57817
  if (key !== void 0 && this.background()) out.push(msg().agents.background.runHint(key));
57527
57818
  if (this.docked()) out.push(msg().agents.approval.runHint(down));
57528
57819
  else if (this.agents()) out.push(msg().agents.bar.runHint(down));
@@ -57643,11 +57934,16 @@ var init_run_indicator = __esm({
57643
57934
  }
57644
57935
  };
57645
57936
  QueueView = class extends Text {
57646
- constructor(theme) {
57937
+ /**
57938
+ * @param nowKey 立即送出排队插话的键(专用键;interrupt 模式下是 Enter);undefined 不提示。
57939
+ */
57940
+ constructor(theme, nowKey = () => void 0) {
57647
57941
  super("");
57648
57942
  this.theme = theme;
57943
+ this.nowKey = nowKey;
57649
57944
  }
57650
57945
  theme;
57946
+ nowKey;
57651
57947
  setQueue(steering, followUp) {
57652
57948
  const t = this.theme;
57653
57949
  const row3 = (origin, text) => ` ${t.fg("dim", `${t.glyphs.queued} ${originLabel(origin)}`)} ${t.fg("muted", oneLine8(text))}`;
@@ -57665,7 +57961,10 @@ var init_run_indicator = __esm({
57665
57961
  );
57666
57962
  }
57667
57963
  if (rows.length > 0) {
57668
- shown2.push(" " + t.fg("dim", msg().interactive.view.run.queueHint(t.glyphs.arrowUp)));
57964
+ const run7 = msg().interactive.view.run;
57965
+ const now = steering.length > 0 ? this.nowKey() : void 0;
57966
+ const hint = now === void 0 ? run7.queueHint(t.glyphs.arrowUp) : run7.queueHintNow(t.glyphs.arrowUp, now);
57967
+ shown2.push(" " + t.fg("dim", hint));
57669
57968
  }
57670
57969
  this.setText(shown2.join("\n"));
57671
57970
  }
@@ -57830,7 +58129,9 @@ function runInteractiveMode(runtime, context, options = {}) {
57830
58129
  spinner: () => loader.frame,
57831
58130
  subagent: (toolCallId2) => subagents.forToolCall(toolCallId2)
57832
58131
  });
57833
- const queueView = new QueueView(theme);
58132
+ const enterMode = () => enterModeOf(runtime);
58133
+ const sendKeys = interruptKeys(keys, enterMode);
58134
+ const queueView = new QueueView(theme, sendKeys.now);
57834
58135
  const loaderSlot = new Container();
57835
58136
  const loader = new Loader(() => tui.requestRender(), {
57836
58137
  theme,
@@ -57867,7 +58168,8 @@ function runInteractiveMode(runtime, context, options = {}) {
57867
58168
  autocomplete: completion,
57868
58169
  requestRender: () => tui.requestRender(),
57869
58170
  ...historyFile !== void 0 ? { historyFile } : {},
57870
- onSubmit: (text) => submit(text, "enter")
58171
+ onSubmit: (text) => submit(text, "enter"),
58172
+ onChange: () => indicator.refreshHints()
57871
58173
  });
57872
58174
  tui.addChild(view);
57873
58175
  tui.addChild(new Spacer());
@@ -57902,8 +58204,10 @@ function runInteractiveMode(runtime, context, options = {}) {
57902
58204
  const backgroundKey = keyLabel(keys, "app.tasks.background");
57903
58205
  const indicator = new RunIndicator({
57904
58206
  ...{ theme, loader, slot: loaderSlot, tools, render },
57905
- ...backgroundKey !== void 0 ? { backgroundKey } : {}
58207
+ ...backgroundKey !== void 0 ? { backgroundKey } : {},
58208
+ ...sendKeys.key !== void 0 ? { interruptKey: sendKeys.key } : {}
57906
58209
  });
58210
+ Object.assign(indicator, { draft: () => !editor.isEmpty(), enterMode });
57907
58211
  const setQueue = (steering, followUp) => {
57908
58212
  status.setQueue(steering.length, followUp.length);
57909
58213
  queueView.setQueue(steering, followUp);
@@ -58095,6 +58399,12 @@ function runInteractiveMode(runtime, context, options = {}) {
58095
58399
  onPasteImage: () => void agentUi.paste(),
58096
58400
  confirmMode,
58097
58401
  submit: (text, via) => submit(text, via),
58402
+ enterWhileRunning: enterMode,
58403
+ interruptSend: interruptSender({
58404
+ session: () => session,
58405
+ takeImages: () => draftImages.splice(0),
58406
+ showHint
58407
+ }),
58098
58408
  runCommand: (line2) => void runCommand2(line2),
58099
58409
  exit: (code) => exit(code),
58100
58410
  onInterrupted: (empty) => void rewind.afterInterrupt(empty),
@@ -58225,6 +58535,7 @@ var init_interactive_mode = __esm({
58225
58535
  init_confirm_dialog();
58226
58536
  init_config_ui();
58227
58537
  init_key_dispatch();
58538
+ init_interrupt_send();
58228
58539
  init_message_view();
58229
58540
  init_pickers();
58230
58541
  init_model_picker();
@@ -65078,19 +65389,19 @@ function redactedLookup(lookup) {
65078
65389
  }
65079
65390
  function axisPoints(trace) {
65080
65391
  const t0 = trace.startedAt;
65081
- const windows2 = [];
65392
+ const windows = [];
65082
65393
  for (const turn of trace.turns) {
65083
65394
  const span = turnSpan(turn);
65084
- if (span !== void 0) windows2.push([span.start - t0, span.end - t0]);
65395
+ if (span !== void 0) windows.push([span.start - t0, span.end - t0]);
65085
65396
  }
65086
65397
  for (const aux of trace.aux)
65087
65398
  if (aux.startedAt !== void 0)
65088
- windows2.push([aux.startedAt - t0, (aux.endedAt ?? aux.startedAt) - t0]);
65089
- windows2.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
65399
+ windows.push([aux.startedAt - t0, (aux.endedAt ?? aux.startedAt) - t0]);
65400
+ windows.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
65090
65401
  const points = [[0, 0]];
65091
65402
  let raw = 0;
65092
65403
  let mapped = 0;
65093
- for (const [start, end] of windows2) {
65404
+ for (const [start, end] of windows) {
65094
65405
  if (end <= raw) continue;
65095
65406
  const from = Math.max(start, raw);
65096
65407
  if (from > raw) {