@armadra/agent 0.6.3 → 0.6.4

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 (69) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/CHANGELOG.zh-CN.md +15 -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 +391 -100
  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/runner.js +29 -2
  24. package/dist/i18n/catalog.d.ts +13 -0
  25. package/dist/i18n/messages/agents.d.ts +4 -0
  26. package/dist/i18n/messages/agents.js +4 -0
  27. package/dist/i18n/messages/config-keys.d.ts +2 -0
  28. package/dist/i18n/messages/config-keys.js +2 -0
  29. package/dist/i18n/messages/config.d.ts +2 -0
  30. package/dist/i18n/messages/interactive-view.d.ts +8 -0
  31. package/dist/i18n/messages/interactive-view.js +10 -0
  32. package/dist/i18n/messages/interactive.d.ts +10 -0
  33. package/dist/i18n/messages/interactive.js +6 -0
  34. package/dist/i18n/messages/report.d.ts +8 -0
  35. package/dist/i18n/messages/report.js +8 -0
  36. package/dist/i18n/messages/settings.d.ts +2 -0
  37. package/dist/i18n/messages/settings.js +2 -0
  38. package/dist/modes/commands-core.js +11 -0
  39. package/dist/modes/interactive/agent-ui.js +1 -0
  40. package/dist/modes/interactive/agent-view.d.ts +5 -0
  41. package/dist/modes/interactive/agent-view.js +14 -3
  42. package/dist/modes/interactive/interactive-mode.js +13 -1
  43. package/dist/modes/interactive/interrupt-send.d.ts +25 -0
  44. package/dist/modes/interactive/interrupt-send.js +32 -0
  45. package/dist/modes/interactive/key-dispatch.d.ts +8 -1
  46. package/dist/modes/interactive/key-dispatch.js +37 -0
  47. package/dist/modes/interactive/line/line-mode.d.ts +1 -0
  48. package/dist/modes/interactive/line/line-mode.js +29 -7
  49. package/dist/modes/interactive/run-indicator.d.ts +19 -2
  50. package/dist/modes/interactive/run-indicator.js +29 -7
  51. package/dist/modes/interactive/status-bar.d.ts +4 -1
  52. package/dist/modes/interactive/status-bar.js +17 -6
  53. package/dist/modes/interactive/status-line.d.ts +10 -9
  54. package/dist/modes/interactive/status-line.js +46 -38
  55. package/dist/modes/interactive/status-quota.d.ts +16 -2
  56. package/dist/modes/interactive/status-quota.js +54 -34
  57. package/dist/modes/interactive/task-background.d.ts +2 -2
  58. package/dist/modes/rpc/commands.d.ts +2 -0
  59. package/dist/modes/rpc/commands.js +15 -1
  60. package/dist/rpc.d.ts +7 -0
  61. package/dist/tools/types.d.ts +6 -0
  62. package/dist/tui/keybindings.d.ts +7 -0
  63. package/dist/tui/keybindings.js +7 -0
  64. package/docs/en/rpc.md +9 -7
  65. package/docs/en/tui.md +49 -31
  66. package/docs/rpc.md +9 -7
  67. package/docs/session-format.md +1 -1
  68. package/docs/tui.md +49 -31
  69. 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.4" : 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;
@@ -32742,7 +32839,7 @@ function failed3(text, sessionId, runner) {
32742
32839
  ...sessionId !== "" ? { sessionRef: { runner, sessionId } } : {}
32743
32840
  };
32744
32841
  }
32745
- var RUN_TIMEOUT_MS, IDLE_CLOSE_MS, ProcessRunner, ProcessHandle;
32842
+ var RUN_TIMEOUT_MS, IDLE_CLOSE_MS, ProcessRunner, TURN_INTERRUPT, ProcessHandle;
32746
32843
  var init_runner = __esm({
32747
32844
  "src/drivers/runner.ts"() {
32748
32845
  "use strict";
@@ -32828,6 +32925,12 @@ var init_runner = __esm({
32828
32925
  return this.deps;
32829
32926
  }
32830
32927
  };
32928
+ TURN_INTERRUPT = /* @__PURE__ */ new Set([
32929
+ "acp",
32930
+ "acp-adapter",
32931
+ "claude-stream",
32932
+ "codex-app-server"
32933
+ ]);
32831
32934
  ProcessHandle = class {
32832
32935
  constructor(runner, chosen, request, deps) {
32833
32936
  this.runner = runner;
@@ -32899,8 +33002,22 @@ var init_runner = __esm({
32899
33002
  }
32900
33003
  this.chain = this.chain.then(() => this.runTurn(text));
32901
33004
  }
32902
- wait() {
32903
- return this.chain;
33005
+ /** 跟到链尾:回合进行中被 `interrupt` 接上的下一回合也算在这次运行里。 */
33006
+ async wait() {
33007
+ for (; ; ) {
33008
+ const chain = this.chain;
33009
+ const result = await chain;
33010
+ if (chain === this.chain) return result;
33011
+ }
33012
+ }
33013
+ /** 子 Agent 视图的「打断并发送」:中断当前回合,紧接着以 `text` 开下一回合。 */
33014
+ async interrupt(text) {
33015
+ const session = this.session;
33016
+ if (this.stopped || !this.busy || session === void 0) return false;
33017
+ if (!TURN_INTERRUPT.has(this.chosen.driver.kind)) return false;
33018
+ this.chain = this.chain.then(() => this.runTurn(text));
33019
+ await session.cancel();
33020
+ return true;
32904
33021
  }
32905
33022
  async stop() {
32906
33023
  if (this.stopped) return;
@@ -34957,10 +35074,11 @@ function window(used, minutes, resetSeconds) {
34957
35074
  if (usedPercent === void 0) return void 0;
34958
35075
  const out = { usedPercent: Math.max(0, Math.min(100, usedPercent)) };
34959
35076
  const m = num(minutes);
34960
- if (m !== void 0) out.windowMinutes = m;
35077
+ if (m !== void 0 && m > 0) out.windowMinutes = m;
34961
35078
  const reset = num(resetSeconds);
34962
35079
  if (reset !== void 0 && reset > 0) out.resetsAt = reset * 1e3;
34963
- return out;
35080
+ const empty = out.usedPercent === 0 && out.windowMinutes === void 0 && out.resetsAt === void 0;
35081
+ return empty ? void 0 : out;
34964
35082
  }
34965
35083
  function snapshot(primary, secondary, plan2) {
34966
35084
  const out = {};
@@ -35306,7 +35424,14 @@ var init_key_docs = __esm({
35306
35424
  codemode: { inlineBudget: DEFAULT_INLINE_BUDGET, requireStrict: false },
35307
35425
  cache: { ...DEFAULT_CACHE_CONFIG },
35308
35426
  request: { idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS },
35309
- ui: { compact: false, logo: "auto", animation: true, restoreOnCancel: true, language: "auto" },
35427
+ ui: {
35428
+ compact: false,
35429
+ logo: "auto",
35430
+ animation: true,
35431
+ restoreOnCancel: true,
35432
+ enterWhileRunning: "queue",
35433
+ language: "auto"
35434
+ },
35310
35435
  // [W6-C0] 第六波键的缺省(行为由各批次实现)
35311
35436
  memory: {
35312
35437
  enabled: false,
@@ -35709,6 +35834,7 @@ function buildBaseSchema() {
35709
35834
  logo: oneOf(["auto", "off"]),
35710
35835
  animation: bool(),
35711
35836
  restoreOnCancel: bool(),
35837
+ enterWhileRunning: oneOf(["queue", "interrupt"]),
35712
35838
  statusLine: oneOf(STATUS_LINE_MODES),
35713
35839
  language: oneOf(LANGUAGE_SETTINGS),
35714
35840
  replyLanguage: str3(),
@@ -39722,6 +39848,7 @@ var init_settings_registry = __esm({
39722
39848
  row("ui.logo", "ui", "restart", "any"),
39723
39849
  row("ui.statusLine", "ui", "now", "any"),
39724
39850
  row("ui.restoreOnCancel", "ui", "nextSession", "any"),
39851
+ row("ui.enterWhileRunning", "ui", "now", "any"),
39725
39852
  row("ui.quietStartup", "ui", "restart", "any"),
39726
39853
  row("ui.ascii", "ui", "restart", "any", { envOverride: "AMA_ASCII" }),
39727
39854
  row("ui.language", "ui", "restart", "any", { envOverride: "AMA_LANG" }),
@@ -41477,6 +41604,13 @@ var init_keybindings = __esm({
41477
41604
  "app.exit": ["ctrl+d"],
41478
41605
  "app.message.followUp": ["alt+enter"],
41479
41606
  "app.message.dequeue": ["alt+up"],
41607
+ /**
41608
+ * 运行中打断当前回合并立即发送输入框的文字(连同排队的插话);`ui.enterWhileRunning: "interrupt"` 时与
41609
+ * Enter 互换(它变成排队)。缺省 `Ctrl+X`:各终端(macOS Terminal、iTerm2、tmux、Windows Terminal)都原样
41610
+ * 送达 0x18,且没有别的绑定;`Ctrl+Enter` 不开 kitty 键盘协议时与 Enter 无法区分,`Ctrl+S` 可能被 XOFF
41611
+ * 流控吃掉,`Alt+Enter` 已是 followUp,`Alt+<字母>` 在 macOS 缺省不送 Meta,`Ctrl+]` 在非美式键盘难按。
41612
+ */
41613
+ "app.message.interrupt": ["ctrl+x"],
41480
41614
  "app.permission.cycle": ["shift+tab", "tab"],
41481
41615
  "app.tools.expand": ["ctrl+o"],
41482
41616
  "app.model.select": ["ctrl+l"],
@@ -44928,34 +45062,46 @@ function windowSpan(minutes) {
44928
45062
  if (minutes % 60 === 0) return `${minutes / 60}h`;
44929
45063
  return `${Math.round(minutes)}m`;
44930
45064
  }
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 };
45065
+ function minutesOf(w) {
45066
+ const m = w.windowMinutes;
45067
+ return m !== void 0 && Number.isFinite(m) && m > 0 ? m : void 0;
45068
+ }
45069
+ function kindOf(minutes) {
45070
+ if (minutes === FIVE_HOURS) return "session";
45071
+ if (minutes === WEEK) return "weekly";
45072
+ return minutes;
44938
45073
  }
44939
- function shortLabel(w, which) {
45074
+ function labels(kind) {
44940
45075
  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);
45076
+ if (kind === "session") return { pct: m.session, reset: m.reset, short: m.short5h };
45077
+ if (kind === "weekly") return { pct: m.weekly, reset: m.weeklyReset, short: m.shortWeek };
45078
+ const span = windowSpan(kind);
45079
+ return { pct: m.window(span), reset: m.windowReset(span), short: span };
44945
45080
  }
44946
45081
  function percent2(theme, used, decimal) {
44947
45082
  return theme.fg(levelColor(used / 100), decimal ? `${used.toFixed(1)}%` : `${Math.round(used)}%`);
44948
45083
  }
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;
45084
+ function hasData(w) {
45085
+ return w.usedPercent !== 0 || minutesOf(w) !== void 0 || w.resetsAt !== void 0;
45086
+ }
45087
+ function shownWindows(quota) {
45088
+ const slots = ["primary", "secondary"].map((which) => ({ which, w: quota[which] })).filter((x) => x.w !== void 0).filter((x) => hasData(x.w));
45089
+ const known = slots.map((x) => kindOf(minutesOf(x.w)));
45090
+ const out = slots.map((x, i) => {
45091
+ const own = known[i];
45092
+ if (own !== void 0) return { kind: own, window: x.w };
45093
+ const guess = x.which === "primary" ? "session" : "weekly";
45094
+ const taken = known.some((k, j) => j !== i && k === guess);
45095
+ const kind = taken ? guess === "session" ? "weekly" : "session" : guess;
45096
+ return { kind, window: x.w };
45097
+ });
45098
+ const span = (k) => typeof k === "number" ? k : SPAN[k];
45099
+ return out.sort((a, b) => span(a.kind) - span(b.kind));
44954
45100
  }
44955
45101
  function compactQuotaItem(quota, theme) {
44956
45102
  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)}`
45103
+ const items = shownWindows(quota).map(
45104
+ ({ kind, window: w }) => `${dim(labels(kind).short)} ${percent2(theme, w.usedPercent, false)}`
44959
45105
  );
44960
45106
  return items.length === 0 ? void 0 : items.join(" ");
44961
45107
  }
@@ -44963,30 +45109,31 @@ function quotaParts2(quota, width2, now, theme) {
44963
45109
  const dim = (text) => theme.fg("dim", text);
44964
45110
  const narrow = width2 < QUOTA_NARROW_WIDTH;
44965
45111
  const parts = [];
44966
- windows(quota).forEach(([which, w], i) => {
45112
+ shownWindows(quota).forEach(({ kind, window: w }, i) => {
44967
45113
  const first = i === 0;
44968
- const group = narrow ? which : void 0;
44969
- const name = narrow ? `${shortLabel(w, which)} ` : labels(w, which).pct;
45114
+ const group = narrow ? `w${i}` : void 0;
45115
+ const label2 = labels(kind);
45116
+ const name = narrow ? `${label2.short} ` : label2.pct;
44970
45117
  parts.push({
44971
45118
  text: dim(name) + percent2(theme, w.usedPercent, !narrow),
44972
45119
  priority: first ? void 0 : 2,
44973
- zone: "left",
45120
+ zone: "right",
44974
45121
  ...group !== void 0 ? { group } : {}
44975
45122
  });
44976
45123
  if (w.resetsAt === void 0) return;
44977
45124
  const left = formatRemaining(w.resetsAt - now, narrow);
44978
45125
  parts.push({
44979
- text: narrow ? theme.fg("tool", `${theme.glyphs.retry}${left}`) : dim(labels(w, which).reset) + theme.fg("tool", left),
45126
+ text: narrow ? theme.fg("tool", `${theme.glyphs.retry}${left}`) : dim(label2.reset) + theme.fg("tool", left),
44980
45127
  priority: first ? 3 : 1,
44981
- zone: "left",
45128
+ zone: "right",
44982
45129
  // 宽格式按最宽形状占位(`23h 59m`),数值变化不让某项时有时无
44983
- reserve: narrow ? 0 : visibleWidth(labels(w, which).reset) + 7,
45130
+ reserve: narrow ? 0 : visibleWidth(label2.reset) + 7,
44984
45131
  ...group !== void 0 ? { group } : {}
44985
45132
  });
44986
45133
  });
44987
45134
  return parts;
44988
45135
  }
44989
- var QUOTA_NARROW_WIDTH, FIVE_HOURS, WEEK, QuotaLine;
45136
+ var QUOTA_NARROW_WIDTH, FIVE_HOURS, WEEK, SPAN, QuotaLine;
44990
45137
  var init_status_quota = __esm({
44991
45138
  "src/modes/interactive/status-quota.ts"() {
44992
45139
  "use strict";
@@ -44996,6 +45143,7 @@ var init_status_quota = __esm({
44996
45143
  QUOTA_NARROW_WIDTH = 80;
44997
45144
  FIVE_HOURS = 300;
44998
45145
  WEEK = 10080;
45146
+ SPAN = { session: FIVE_HOURS, weekly: WEEK };
44999
45147
  QuotaLine = class {
45000
45148
  constructor(source, theme) {
45001
45149
  this.source = source;
@@ -45009,7 +45157,8 @@ var init_status_quota = __esm({
45009
45157
  if (view === void 0) return [];
45010
45158
  const theme = this.theme;
45011
45159
  if (view === "pending") {
45012
- return [truncateToWidth(theme.fg("dim", msg().interactive.statusLine.quota.pending), width2)];
45160
+ const text = theme.fg("dim", msg().interactive.statusLine.quota.pending);
45161
+ return [layoutRow([{ text, priority: void 0, zone: "right" }], width2, theme)];
45013
45162
  }
45014
45163
  const parts = quotaParts2(view.quota, width2, this.source.now(), theme);
45015
45164
  if (parts.length === 0) return [];
@@ -45085,9 +45234,10 @@ function layoutRow(parts, width2, theme, style = {}) {
45085
45234
  let kept = [...parts];
45086
45235
  const zone = (z) => kept.filter((p) => p.zone === z);
45087
45236
  const fits = () => {
45088
- const l2 = join66(zone("left"))[1];
45237
+ const left = zone("left");
45089
45238
  const right = zone("right");
45090
- return l2 + (right.length === 0 ? 0 : sep5.length + join66(right)[1]) <= width2;
45239
+ const between = left.length === 0 || right.length === 0 ? 0 : sep5.length;
45240
+ return join66(left)[1] + between + join66(right)[1] <= width2;
45091
45241
  };
45092
45242
  while (!fits()) {
45093
45243
  const droppable = kept.filter((p) => p.priority !== void 0);
@@ -45098,7 +45248,7 @@ function layoutRow(parts, width2, theme, style = {}) {
45098
45248
  const l = join66(zone("left"))[0];
45099
45249
  const r = join66(zone("right"))[0];
45100
45250
  const gap = width2 - visibleWidth(l) - visibleWidth(r);
45101
- const line2 = r === "" ? l : gap >= MIN_GAP ? l + " ".repeat(gap) + r : l + dim(sep5) + r;
45251
+ const line2 = r === "" ? l : l === "" ? " ".repeat(Math.max(0, gap)) + r : gap >= MIN_GAP ? l + " ".repeat(gap) + r : l + dim(sep5) + r;
45102
45252
  return truncateToWidth(line2, width2);
45103
45253
  }
45104
45254
  function abbreviateModel(ref, width2) {
@@ -45230,7 +45380,7 @@ var init_status_bar = __esm({
45230
45380
  }
45231
45381
  const model = state.model === void 0 ? "?" : abbreviateModel(formatModelRef(state.model), width2);
45232
45382
  const p = full ? FULL : COMPACT;
45233
- const fallback = this.source.fallback?.();
45383
+ const fallback = full ? void 0 : this.source.fallback?.();
45234
45384
  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
45385
  right(modelText, p.model, full ? { group: "model" } : {});
45236
45386
  if (state.thinkingLevel !== "off") {
@@ -49024,6 +49174,13 @@ async function runSlashCommand(line2, ctx) {
49024
49174
  return planCommand(session, args, ctx);
49025
49175
  case "tasks":
49026
49176
  return { kind: "handled", message: await tasksCommand(session, args) };
49177
+ case "interrupt": {
49178
+ if (args === "") throw new AmaError("invalid_arguments", msg().report.command.interruptUsage);
49179
+ const { isStreaming, isCompacting } = session.state;
49180
+ if (!isStreaming && !isCompacting) return { kind: "prompt", text: args };
49181
+ void session.prompt(args, { interrupt: true }).catch(() => void 0);
49182
+ return { kind: "handled", message: msg().report.command.interruptSent(args) };
49183
+ }
49027
49184
  case "agents":
49028
49185
  return { kind: "handled", message: describeAgents(session.state.sessionId) };
49029
49186
  case "paste": {
@@ -49079,6 +49236,7 @@ var init_commands_core = __esm({
49079
49236
  command("tasks", "tasks", "[id] | stop <id> | bg [id]"),
49080
49237
  command("agents", "agents"),
49081
49238
  command("paste", "paste"),
49239
+ command("interrupt", "interrupt", { key: "interruptArgs" }),
49082
49240
  command("exit", "exit"),
49083
49241
  // [W6-C0] 第六波(W6-S / W6-T1 / W6-M 实现)
49084
49242
  command("config", "config", "[key=value]"),
@@ -49129,6 +49287,11 @@ function applyPlanAttendance(ctx) {
49129
49287
  ctx.capabilities.has("plans") ? "client" : "unattended"
49130
49288
  );
49131
49289
  }
49290
+ function interruptFlag(value) {
49291
+ if (value !== void 0 && typeof value !== "boolean")
49292
+ throw new AmaError("invalid_arguments", "interrupt must be a boolean");
49293
+ return value === true;
49294
+ }
49132
49295
  async function startRun(ctx, run7) {
49133
49296
  const session = ctx.session();
49134
49297
  let settled = false;
@@ -49251,10 +49414,17 @@ var init_commands = __esm({
49251
49414
  ctx,
49252
49415
  () => ctx.session().prompt(p.message, {
49253
49416
  ...p.images !== void 0 ? { images: p.images } : {},
49254
- ...p.streamingBehavior !== void 0 ? { streamingBehavior: p.streamingBehavior } : {}
49417
+ ...p.streamingBehavior !== void 0 ? { streamingBehavior: p.streamingBehavior } : {},
49418
+ ...interruptFlag(p.interrupt) ? { interrupt: true } : {}
49255
49419
  })
49256
49420
  ),
49257
- steer: (p, ctx) => startRun(ctx, () => ctx.session().steer(p.message)),
49421
+ steer: (p, ctx) => startRun(
49422
+ ctx,
49423
+ () => interruptFlag(p.interrupt) ? ctx.session().prompt(p.message, {
49424
+ interrupt: true,
49425
+ ...p.images !== void 0 ? { images: p.images } : {}
49426
+ }) : ctx.session().steer(p.message)
49427
+ ),
49258
49428
  follow_up: (p, ctx) => startRun(ctx, () => ctx.session().followUp(p.message)),
49259
49429
  abort: async (_p, ctx) => {
49260
49430
  await ctx.session().abort();
@@ -52879,13 +53049,13 @@ ${bypassRiskLines().map((l) => ` ${l}
52879
53049
  void ed.ask(`
52880
53050
  ${preview.join("")}${question}`, "n").then((answer) => {
52881
53051
  resolve39(answer === "y" ? "allow" : answer === "a" ? "allow_session" : "deny");
52882
- if (!busy) ed.render();
53052
+ if (running === 0) ed.render();
52883
53053
  });
52884
53054
  })
52885
53055
  });
52886
53056
  runtime.notifier.set((message, level) => err(`ama: [${level}] ${message}
52887
53057
  `));
52888
- let busy = false;
53058
+ let running = 0;
52889
53059
  let lastInterrupt = 0;
52890
53060
  return new Promise((resolve39) => {
52891
53061
  let finished = false;
@@ -52905,11 +53075,23 @@ ${preview.join("")}${question}`, "n").then((answer) => {
52905
53075
  });
52906
53076
  };
52907
53077
  const run7 = (text) => {
52908
- busy = true;
53078
+ running++;
52909
53079
  void handle(text).then((outcome) => {
52910
- busy = false;
53080
+ running--;
52911
53081
  if (outcome === "exit") finish2(ExitCode.Ok);
52912
- else ed.render();
53082
+ else if (running === 0) ed.render();
53083
+ });
53084
+ };
53085
+ const interruptWith = (text) => {
53086
+ if (text === "") return err(`ama: ${msg().report.command.interruptUsage}
53087
+ `);
53088
+ out(`${msg().report.command.interruptSent(text)}
53089
+ `);
53090
+ running++;
53091
+ void session.prompt(text, { interrupt: true }).catch((error2) => err(`ama: ${errorText3(error2)}
53092
+ `)).finally(() => {
53093
+ printer.endLine();
53094
+ if (--running === 0) ed.render();
52913
53095
  });
52914
53096
  };
52915
53097
  let flushTimer;
@@ -52917,7 +53099,7 @@ ${preview.join("")}${question}`, "n").then((answer) => {
52917
53099
  for (const action of actions) {
52918
53100
  if (action.kind === "eof") return finish2(ExitCode.Ok);
52919
53101
  if (action.kind === "interrupt") {
52920
- if (busy) {
53102
+ if (running > 0) {
52921
53103
  void session.abort();
52922
53104
  err(`${msg().interactive.line.interrupted}
52923
53105
  `);
@@ -52932,7 +53114,12 @@ ${msg().interactive.line.ctrlCAgain}
52932
53114
  }
52933
53115
  continue;
52934
53116
  }
52935
- if (busy) {
53117
+ if (running > 0) {
53118
+ const forced2 = /^\/interrupt(?:\s+([\s\S]*))?$/.exec(action.text.trim());
53119
+ if (forced2 !== null) {
53120
+ interruptWith((forced2[1] ?? "").trim());
53121
+ continue;
53122
+ }
52936
53123
  if (/^\/tasks\s+bg(\s+\S+)?\s*$/.test(action.text.trim())) {
52937
53124
  void handle(action.text.trim());
52938
53125
  continue;
@@ -53421,7 +53608,7 @@ var init_agent_view = __esm({
53421
53608
  maxVisibleLines: 4,
53422
53609
  placeholder: msg().agents.view.placeholder(taskId),
53423
53610
  requestRender: () => deps.render(),
53424
- onSubmit: (text) => void this.submit(text)
53611
+ onSubmit: (text) => void this.submit(text, this.enterInterrupts())
53425
53612
  });
53426
53613
  this.attach();
53427
53614
  }
@@ -53519,7 +53706,10 @@ var init_agent_view = __esm({
53519
53706
  this.attach();
53520
53707
  this.deps.onSwitch?.(next);
53521
53708
  }
53522
- async submit(text) {
53709
+ enterInterrupts() {
53710
+ return this.deps.enterMode?.() === "interrupt";
53711
+ }
53712
+ async submit(text, interrupt = false) {
53523
53713
  const m = msg().agents.view;
53524
53714
  const trimmed = text.trim();
53525
53715
  if (trimmed === "") return;
@@ -53545,7 +53735,7 @@ var init_agent_view = __esm({
53545
53735
  const registry2 = this.deps.registry();
53546
53736
  try {
53547
53737
  if (registry2 === void 0) throw new Error(m.unknown(taskId));
53548
- const reply = await registry2.message(taskId, text);
53738
+ const reply = await registry2.message(taskId, text, interrupt);
53549
53739
  this.flash = m[reply](taskId);
53550
53740
  } catch (error2) {
53551
53741
  this.flash = m.sendFailed(error2 instanceof Error ? error2.message : String(error2));
@@ -53565,6 +53755,11 @@ var init_agent_view = __esm({
53565
53755
  return;
53566
53756
  }
53567
53757
  }
53758
+ if (keys.matches(data2, "app.message.interrupt") && !this.editor.isCompletionOpen) {
53759
+ const text = this.editor.takeSubmission();
53760
+ if (text !== null) void this.submit(text, !this.enterInterrupts());
53761
+ return;
53762
+ }
53568
53763
  if (keys.matches(data2, "tui.select.cancel") && !this.editor.isCompletionOpen) {
53569
53764
  if (!empty) this.editor.clear();
53570
53765
  else this.deps.close();
@@ -54691,6 +54886,7 @@ var init_agent_ui = __esm({
54691
54886
  close: () => this.closeView(),
54692
54887
  stop: (id) => stopTask(this.deps.session().state.sessionId, id),
54693
54888
  background: (id) => this.deps.session().backgroundTask(id, "user"),
54889
+ enterMode: () => this.deps.area?.ui().enterWhileRunning === "interrupt" ? "interrupt" : "queue",
54694
54890
  messages: {
54695
54891
  ...ui.showThinking !== void 0 ? { showThinking: ui.showThinking } : {},
54696
54892
  ...ui.markdown !== void 0 ? { markdown: ui.markdown } : {},
@@ -55624,16 +55820,18 @@ var init_status_line = __esm({
55624
55820
  init_status_bar();
55625
55821
  RESERVE = { tps: 14, amount: 16, avg: 7, ttft: 9 };
55626
55822
  PRIORITY = {
55627
- ttft: 1,
55628
- avg: 2,
55629
- host: 3,
55823
+ codemode: 1,
55824
+ sandbox: 2,
55825
+ fallback: 3,
55630
55826
  preset: 4,
55631
- rebill: 5,
55632
- cache: 6,
55633
- tokens: 7,
55634
- queue: 8,
55635
- codemode: 9,
55636
- amount: 10
55827
+ queue: 5,
55828
+ host: 6,
55829
+ ttft: 7,
55830
+ avg: 8,
55831
+ rebill: 9,
55832
+ cache: 10,
55833
+ tokens: 11,
55834
+ amount: 12
55637
55835
  };
55638
55836
  COLLAPSE_HINT = "[-]";
55639
55837
  RATE_STYLE = { groups: { stats: { glue: " · ", open: "(", close: ")" } } };
@@ -55646,7 +55844,7 @@ var init_status_line = __esm({
55646
55844
  bar;
55647
55845
  source;
55648
55846
  theme;
55649
- parts() {
55847
+ parts(width2) {
55650
55848
  const theme = this.theme;
55651
55849
  const dim = (text) => theme.fg("dim", text);
55652
55850
  const stats = this.bar.current();
@@ -55654,9 +55852,9 @@ var init_status_line = __esm({
55654
55852
  const live2 = telemetry?.live;
55655
55853
  const last = telemetry?.last;
55656
55854
  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" });
55855
+ const metric = (text, priority, reserve, extra = {}) => void parts.push({ text, priority, zone: "right", reserve, ...extra });
55856
+ const add3 = (zone, text, priority) => {
55857
+ if (text !== void 0) parts.push({ text, priority, zone });
55660
55858
  };
55661
55859
  const blue = (text) => theme.fg("accent", text);
55662
55860
  const purple = (text) => theme.fg("tool", text);
@@ -55673,20 +55871,25 @@ var init_status_line = __esm({
55673
55871
  }
55674
55872
  }
55675
55873
  const label2 = live2 !== void 0 ? theme.fg("accent", "tps:") : dim("tps:");
55676
- left(
55874
+ metric(
55677
55875
  `${label2} ${tps === void 0 ? dim("—") : `${purple(formatRate(tps))}${dim(" tok/s")}`}`,
55678
55876
  void 0,
55679
55877
  RESERVE.tps
55680
55878
  );
55681
55879
  if (amount !== void 0) {
55682
- left(amount, PRIORITY.amount, RESERVE.amount, { lead: ` ${theme.glyphs.dot} ` });
55880
+ metric(amount, PRIORITY.amount, RESERVE.amount, { lead: ` ${theme.glyphs.dot} ` });
55683
55881
  }
55684
55882
  const stat16 = { group: "stats", lead: " " };
55685
55883
  if (telemetry?.avgTps !== void 0) {
55686
- left(`${dim("avg ")}${blue(formatRate(telemetry.avgTps))}`, PRIORITY.avg, RESERVE.avg, stat16);
55884
+ metric(
55885
+ `${dim("avg ")}${blue(formatRate(telemetry.avgTps))}`,
55886
+ PRIORITY.avg,
55887
+ RESERVE.avg,
55888
+ stat16
55889
+ );
55687
55890
  }
55688
55891
  if (last?.ttftMs !== void 0) {
55689
- left(
55892
+ metric(
55690
55893
  `${dim("ttft ")}${purple(formatSeconds2(last.ttftMs))}`,
55691
55894
  PRIORITY.ttft,
55692
55895
  RESERVE.ttft,
@@ -55694,20 +55897,29 @@ var init_status_line = __esm({
55694
55897
  );
55695
55898
  }
55696
55899
  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);
55900
+ add3("right", usage.tokens, PRIORITY.tokens);
55901
+ add3("right", usage.cache, PRIORITY.cache);
55902
+ add3("right", usage.rebill, PRIORITY.rebill);
55903
+ add3("right", dim(COLLAPSE_HINT), void 0);
55904
+ add3("left", usage.codemode, PRIORITY.codemode);
55905
+ add3("left", usage.sandbox, PRIORITY.sandbox);
55906
+ add3("left", usage.preset, PRIORITY.preset);
55907
+ const fallback = this.source.fallback?.();
55908
+ if (fallback !== void 0) {
55909
+ const arrow = theme.glyphs.ascii ? "-> " : "→ ";
55910
+ add3(
55911
+ "left",
55912
+ dim(arrow) + theme.fg("warning", abbreviateModel(fallback.to, width2)),
55913
+ PRIORITY.fallback
55914
+ );
55915
+ }
55916
+ add3("left", usage.queue, PRIORITY.queue);
55917
+ add3("left", usage.host, PRIORITY.host);
55706
55918
  return parts;
55707
55919
  }
55708
55920
  render(width2) {
55709
55921
  if (this.bar.layout() !== "full") return [];
55710
- return [truncateToWidth(layoutRow(this.parts(), width2, this.theme, RATE_STYLE), width2)];
55922
+ return [truncateToWidth(layoutRow(this.parts(width2), width2, this.theme, RATE_STYLE), width2)];
55711
55923
  }
55712
55924
  invalidate() {
55713
55925
  }
@@ -56008,6 +56220,27 @@ ${current3}`);
56008
56220
  if (typeof answer === "boolean") settle(answer);
56009
56221
  else void answer.then(settle);
56010
56222
  };
56223
+ const sendKeys = (is) => {
56224
+ if (deps.interruptSend === void 0 || editor.isCompletionOpen) return false;
56225
+ const viaKey = is("app.message.interrupt");
56226
+ if (!deps.busy()) {
56227
+ if (!viaKey || editor.isEmpty()) return false;
56228
+ const text = editor.takeSubmission();
56229
+ if (text !== null) deps.submit(text, "enter");
56230
+ return true;
56231
+ }
56232
+ const interruptMode = deps.enterWhileRunning?.() === "interrupt";
56233
+ if (!viaKey && !(interruptMode && is("tui.editor.submit"))) return false;
56234
+ const command3 = editor.getExpandedText().trimStart().startsWith("/");
56235
+ if (command3 || viaKey === interruptMode) {
56236
+ if (!viaKey) return false;
56237
+ const text = editor.takeSubmission();
56238
+ if (text !== null) deps.submit(text, "enter");
56239
+ return true;
56240
+ }
56241
+ deps.interruptSend(editor.takeSubmission() ?? "");
56242
+ return true;
56243
+ };
56011
56244
  const atLastLine = () => editor.cursor.line >= editor.getText().split("\n").length - 1;
56012
56245
  const focusAgents = (agents, data2) => {
56013
56246
  if (editor.isCompletionOpen || editor.isBrowsingHistory) return false;
@@ -56064,6 +56297,7 @@ ${current3}`);
56064
56297
  return true;
56065
56298
  }
56066
56299
  ctrlCArmedAt = Number.NEGATIVE_INFINITY;
56300
+ if (sendKeys(is)) return true;
56067
56301
  if (is("app.interrupt") && !editor.isCompletionOpen && deps.busy()) {
56068
56302
  esc.reset();
56069
56303
  interrupt();
@@ -56138,6 +56372,34 @@ var init_key_dispatch = __esm({
56138
56372
  }
56139
56373
  });
56140
56374
 
56375
+ // src/modes/interactive/interrupt-send.ts
56376
+ function interruptSender(deps) {
56377
+ return (text) => {
56378
+ const images = deps.takeImages();
56379
+ deps.showHint(msg().interactive.keys.interruptSent);
56380
+ const options = images.length > 0 ? { interrupt: true, images } : { interrupt: true };
56381
+ void deps.session().prompt(text, options).catch((error2) => {
56382
+ if (isAmaError(error2) && error2.code === "invalid_arguments")
56383
+ deps.showHint(msg().interactive.keys.nothingToSend);
56384
+ });
56385
+ };
56386
+ }
56387
+ function enterModeOf(runtime) {
56388
+ return runtime.config.ui?.enterWhileRunning === "interrupt" ? "interrupt" : "queue";
56389
+ }
56390
+ function interruptKeys(keys, mode) {
56391
+ const key = keyLabel(keys, "app.message.interrupt");
56392
+ return { key, now: () => mode() === "interrupt" ? "Enter" : key };
56393
+ }
56394
+ var init_interrupt_send = __esm({
56395
+ "src/modes/interactive/interrupt-send.ts"() {
56396
+ "use strict";
56397
+ init_errors();
56398
+ init_i18n();
56399
+ init_task_background();
56400
+ }
56401
+ });
56402
+
56141
56403
  // src/modes/interactive/model-picker.ts
56142
56404
  async function openModelPicker(host, options) {
56143
56405
  const catalog = await loadModelCatalog(options.providers);
@@ -57500,10 +57762,7 @@ var init_run_indicator = __esm({
57500
57762
  RunIndicator = class {
57501
57763
  constructor(deps) {
57502
57764
  this.deps = deps;
57503
- deps.loader.onFrame(() => {
57504
- if (this.busy && !this.approval && this.optionalHints().join("\n") !== this.hintKey)
57505
- this.applyVerb();
57506
- });
57765
+ deps.loader.onFrame(() => this.refreshHints());
57507
57766
  }
57508
57767
  deps;
57509
57768
  running = false;
@@ -57517,12 +57776,24 @@ var init_run_indicator = __esm({
57517
57776
  background = () => false;
57518
57777
  /** [W7-C] 有停靠在 Agent 栏的审批。 */
57519
57778
  docked = () => false;
57779
+ /** 输入框有字(显示 Enter / 专用键的提示)。 */
57780
+ draft = () => false;
57781
+ /** 运行中 Enter 的语义。 */
57782
+ enterMode = () => "queue";
57520
57783
  hintKey = "";
57784
+ /** 可丢弃提示变了才换动词(每帧、输入框内容变化时调)。 */
57785
+ refreshHints() {
57786
+ if (this.busy && !this.approval && this.optionalHints().join("\n") !== this.hintKey)
57787
+ this.applyVerb();
57788
+ }
57521
57789
  /** 可丢弃的按键提示(按丢弃的逆序排列)。 */
57522
57790
  optionalHints() {
57523
57791
  const down = this.deps.theme.glyphs.arrowDown;
57524
57792
  const key = this.deps.backgroundKey;
57525
57793
  const out = [];
57794
+ const send2 = this.deps.interruptKey;
57795
+ if (send2 !== void 0 && this.draft())
57796
+ out.push(msg().interactive.view.run.sendHint(this.enterMode(), send2));
57526
57797
  if (key !== void 0 && this.background()) out.push(msg().agents.background.runHint(key));
57527
57798
  if (this.docked()) out.push(msg().agents.approval.runHint(down));
57528
57799
  else if (this.agents()) out.push(msg().agents.bar.runHint(down));
@@ -57643,11 +57914,16 @@ var init_run_indicator = __esm({
57643
57914
  }
57644
57915
  };
57645
57916
  QueueView = class extends Text {
57646
- constructor(theme) {
57917
+ /**
57918
+ * @param nowKey 立即送出排队插话的键(专用键;interrupt 模式下是 Enter);undefined 不提示。
57919
+ */
57920
+ constructor(theme, nowKey = () => void 0) {
57647
57921
  super("");
57648
57922
  this.theme = theme;
57923
+ this.nowKey = nowKey;
57649
57924
  }
57650
57925
  theme;
57926
+ nowKey;
57651
57927
  setQueue(steering, followUp) {
57652
57928
  const t = this.theme;
57653
57929
  const row3 = (origin, text) => ` ${t.fg("dim", `${t.glyphs.queued} ${originLabel(origin)}`)} ${t.fg("muted", oneLine8(text))}`;
@@ -57665,7 +57941,10 @@ var init_run_indicator = __esm({
57665
57941
  );
57666
57942
  }
57667
57943
  if (rows.length > 0) {
57668
- shown2.push(" " + t.fg("dim", msg().interactive.view.run.queueHint(t.glyphs.arrowUp)));
57944
+ const run7 = msg().interactive.view.run;
57945
+ const now = steering.length > 0 ? this.nowKey() : void 0;
57946
+ const hint = now === void 0 ? run7.queueHint(t.glyphs.arrowUp) : run7.queueHintNow(t.glyphs.arrowUp, now);
57947
+ shown2.push(" " + t.fg("dim", hint));
57669
57948
  }
57670
57949
  this.setText(shown2.join("\n"));
57671
57950
  }
@@ -57830,7 +58109,9 @@ function runInteractiveMode(runtime, context, options = {}) {
57830
58109
  spinner: () => loader.frame,
57831
58110
  subagent: (toolCallId2) => subagents.forToolCall(toolCallId2)
57832
58111
  });
57833
- const queueView = new QueueView(theme);
58112
+ const enterMode = () => enterModeOf(runtime);
58113
+ const sendKeys = interruptKeys(keys, enterMode);
58114
+ const queueView = new QueueView(theme, sendKeys.now);
57834
58115
  const loaderSlot = new Container();
57835
58116
  const loader = new Loader(() => tui.requestRender(), {
57836
58117
  theme,
@@ -57867,7 +58148,8 @@ function runInteractiveMode(runtime, context, options = {}) {
57867
58148
  autocomplete: completion,
57868
58149
  requestRender: () => tui.requestRender(),
57869
58150
  ...historyFile !== void 0 ? { historyFile } : {},
57870
- onSubmit: (text) => submit(text, "enter")
58151
+ onSubmit: (text) => submit(text, "enter"),
58152
+ onChange: () => indicator.refreshHints()
57871
58153
  });
57872
58154
  tui.addChild(view);
57873
58155
  tui.addChild(new Spacer());
@@ -57902,8 +58184,10 @@ function runInteractiveMode(runtime, context, options = {}) {
57902
58184
  const backgroundKey = keyLabel(keys, "app.tasks.background");
57903
58185
  const indicator = new RunIndicator({
57904
58186
  ...{ theme, loader, slot: loaderSlot, tools, render },
57905
- ...backgroundKey !== void 0 ? { backgroundKey } : {}
58187
+ ...backgroundKey !== void 0 ? { backgroundKey } : {},
58188
+ ...sendKeys.key !== void 0 ? { interruptKey: sendKeys.key } : {}
57906
58189
  });
58190
+ Object.assign(indicator, { draft: () => !editor.isEmpty(), enterMode });
57907
58191
  const setQueue = (steering, followUp) => {
57908
58192
  status.setQueue(steering.length, followUp.length);
57909
58193
  queueView.setQueue(steering, followUp);
@@ -58095,6 +58379,12 @@ function runInteractiveMode(runtime, context, options = {}) {
58095
58379
  onPasteImage: () => void agentUi.paste(),
58096
58380
  confirmMode,
58097
58381
  submit: (text, via) => submit(text, via),
58382
+ enterWhileRunning: enterMode,
58383
+ interruptSend: interruptSender({
58384
+ session: () => session,
58385
+ takeImages: () => draftImages.splice(0),
58386
+ showHint
58387
+ }),
58098
58388
  runCommand: (line2) => void runCommand2(line2),
58099
58389
  exit: (code) => exit(code),
58100
58390
  onInterrupted: (empty) => void rewind.afterInterrupt(empty),
@@ -58225,6 +58515,7 @@ var init_interactive_mode = __esm({
58225
58515
  init_confirm_dialog();
58226
58516
  init_config_ui();
58227
58517
  init_key_dispatch();
58518
+ init_interrupt_send();
58228
58519
  init_message_view();
58229
58520
  init_pickers();
58230
58521
  init_model_picker();
@@ -65078,19 +65369,19 @@ function redactedLookup(lookup) {
65078
65369
  }
65079
65370
  function axisPoints(trace) {
65080
65371
  const t0 = trace.startedAt;
65081
- const windows2 = [];
65372
+ const windows = [];
65082
65373
  for (const turn of trace.turns) {
65083
65374
  const span = turnSpan(turn);
65084
- if (span !== void 0) windows2.push([span.start - t0, span.end - t0]);
65375
+ if (span !== void 0) windows.push([span.start - t0, span.end - t0]);
65085
65376
  }
65086
65377
  for (const aux of trace.aux)
65087
65378
  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]);
65379
+ windows.push([aux.startedAt - t0, (aux.endedAt ?? aux.startedAt) - t0]);
65380
+ windows.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
65090
65381
  const points = [[0, 0]];
65091
65382
  let raw = 0;
65092
65383
  let mapped = 0;
65093
- for (const [start, end] of windows2) {
65384
+ for (const [start, end] of windows) {
65094
65385
  if (end <= raw) continue;
65095
65386
  const from = Math.max(start, raw);
65096
65387
  if (from > raw) {