@armadra/agent 0.6.2 → 0.6.3

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 (130) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/CHANGELOG.zh-CN.md +44 -0
  3. package/README.md +168 -575
  4. package/README.zh-CN.md +165 -596
  5. package/dist/agent/session.d.ts +6 -0
  6. package/dist/agent/session.js +8 -0
  7. package/dist/agent/subagent-background.d.ts +75 -0
  8. package/dist/agent/subagent-background.js +209 -0
  9. package/dist/agent/subagent-registry.d.ts +24 -5
  10. package/dist/agent/subagent-registry.js +64 -92
  11. package/dist/agent/types-w5.d.ts +11 -1
  12. package/dist/agent/types.d.ts +5 -0
  13. package/dist/agents/builtin.js +0 -1
  14. package/dist/agents/external.js +0 -1
  15. package/dist/agents/parse.js +4 -3
  16. package/dist/agents/result.d.ts +7 -1
  17. package/dist/agents/result.js +20 -1
  18. package/dist/agents/task-control.d.ts +13 -2
  19. package/dist/agents/task-record.d.ts +13 -1
  20. package/dist/agents/task-record.js +32 -0
  21. package/dist/agents/types.d.ts +5 -1
  22. package/dist/ai/providers/discovered-cache.d.ts +10 -4
  23. package/dist/ai/providers/discovered-cache.js +30 -14
  24. package/dist/auth/chatgpt/backend-client.d.ts +6 -5
  25. package/dist/auth/chatgpt/backend-client.js +8 -7
  26. package/dist/bundle/ama.cjs +1691 -500
  27. package/dist/cli/compose-agents.d.ts +2 -1
  28. package/dist/cli/compose-agents.js +11 -2
  29. package/dist/cli/subcommands/models-discover.d.ts +1 -0
  30. package/dist/cli/subcommands/models-discover.js +12 -2
  31. package/dist/config/json-schema.js +9 -2
  32. package/dist/config/key-docs.js +2 -2
  33. package/dist/config/merge.d.ts +1 -1
  34. package/dist/config/merge.js +20 -3
  35. package/dist/config/schema-w5.js +4 -2
  36. package/dist/config/schema.js +2 -0
  37. package/dist/config/settings-registry.js +4 -0
  38. package/dist/config/types-w5.d.ts +10 -0
  39. package/dist/config/types-w5.js +2 -0
  40. package/dist/config/types.d.ts +3 -1
  41. package/dist/git/info.d.ts +19 -0
  42. package/dist/git/info.js +64 -8
  43. package/dist/i18n/catalog.d.ts +45 -8
  44. package/dist/i18n/messages/agents.d.ts +56 -0
  45. package/dist/i18n/messages/agents.js +58 -2
  46. package/dist/i18n/messages/config-keys.d.ts +6 -0
  47. package/dist/i18n/messages/config-keys.js +16 -10
  48. package/dist/i18n/messages/config.d.ts +6 -0
  49. package/dist/i18n/messages/interactive-startup.d.ts +0 -16
  50. package/dist/i18n/messages/interactive-startup.js +0 -16
  51. package/dist/i18n/messages/interactive.d.ts +23 -16
  52. package/dist/i18n/messages/interactive.js +25 -0
  53. package/dist/i18n/messages/print.d.ts +4 -0
  54. package/dist/i18n/messages/print.js +4 -0
  55. package/dist/i18n/messages/report.js +4 -4
  56. package/dist/i18n/messages/settings.d.ts +6 -0
  57. package/dist/i18n/messages/settings.js +6 -0
  58. package/dist/i18n/messages/subcommands-config.d.ts +4 -0
  59. package/dist/i18n/messages/subcommands-config.js +4 -0
  60. package/dist/i18n/messages/subcommands.d.ts +4 -0
  61. package/dist/index.d.ts +1 -0
  62. package/dist/modes/commands-core.js +8 -4
  63. package/dist/modes/interactive/agent-bar.d.ts +3 -1
  64. package/dist/modes/interactive/agent-bar.js +12 -5
  65. package/dist/modes/interactive/agent-ui.d.ts +21 -3
  66. package/dist/modes/interactive/agent-ui.js +81 -12
  67. package/dist/modes/interactive/agent-view.d.ts +4 -0
  68. package/dist/modes/interactive/agent-view.js +14 -1
  69. package/dist/modes/interactive/approval-dock.d.ts +51 -0
  70. package/dist/modes/interactive/approval-dock.js +112 -0
  71. package/dist/modes/interactive/approval-ui.d.ts +43 -0
  72. package/dist/modes/interactive/approval-ui.js +64 -0
  73. package/dist/modes/interactive/commands.js +4 -1
  74. package/dist/modes/interactive/event-notices.d.ts +6 -1
  75. package/dist/modes/interactive/event-notices.js +7 -1
  76. package/dist/modes/interactive/interactive-mode.d.ts +4 -2
  77. package/dist/modes/interactive/interactive-mode.js +33 -38
  78. package/dist/modes/interactive/key-dispatch.d.ts +21 -4
  79. package/dist/modes/interactive/key-dispatch.js +60 -8
  80. package/dist/modes/interactive/line/line-mode.js +5 -0
  81. package/dist/modes/interactive/run-indicator.d.ts +15 -0
  82. package/dist/modes/interactive/run-indicator.js +37 -5
  83. package/dist/modes/interactive/session-events.js +5 -1
  84. package/dist/modes/interactive/startup-header.d.ts +29 -14
  85. package/dist/modes/interactive/startup-header.js +93 -59
  86. package/dist/modes/interactive/startup-logo.d.ts +83 -0
  87. package/dist/modes/interactive/startup-logo.js +183 -0
  88. package/dist/modes/interactive/status-area.d.ts +18 -0
  89. package/dist/modes/interactive/status-area.js +67 -1
  90. package/dist/modes/interactive/status-bar.d.ts +9 -1
  91. package/dist/modes/interactive/status-bar.js +43 -11
  92. package/dist/modes/interactive/status-line.d.ts +2 -0
  93. package/dist/modes/interactive/status-line.js +11 -6
  94. package/dist/modes/interactive/status-quota.d.ts +44 -0
  95. package/dist/modes/interactive/status-quota.js +135 -0
  96. package/dist/modes/interactive/subagent-view.d.ts +1 -0
  97. package/dist/modes/interactive/subagent-view.js +8 -0
  98. package/dist/modes/interactive/task-background.d.ts +31 -0
  99. package/dist/modes/interactive/task-background.js +68 -0
  100. package/dist/modes/interactive/tool-view.d.ts +7 -1
  101. package/dist/modes/interactive/tool-view.js +24 -1
  102. package/dist/modes/print/print-mode.d.ts +11 -0
  103. package/dist/modes/print/print-mode.js +36 -1
  104. package/dist/modes/rpc/commands.d.ts +2 -1
  105. package/dist/modes/rpc/commands.js +9 -1
  106. package/dist/rpc.d.ts +13 -0
  107. package/dist/rpc.js +3 -0
  108. package/dist/tools/task-ctl.d.ts +2 -0
  109. package/dist/tools/task-ctl.js +7 -2
  110. package/dist/tools/task.d.ts +11 -0
  111. package/dist/tools/task.js +20 -2
  112. package/dist/tui/components/editor.d.ts +2 -0
  113. package/dist/tui/components/editor.js +4 -0
  114. package/dist/tui/components/loader.d.ts +5 -1
  115. package/dist/tui/components/loader.js +18 -5
  116. package/dist/tui/keybindings.d.ts +8 -3
  117. package/dist/tui/keybindings.js +8 -3
  118. package/docs/agents.md +52 -28
  119. package/docs/en/host-api.md +5 -1
  120. package/docs/en/providers.md +1 -1
  121. package/docs/en/rpc.md +19 -7
  122. package/docs/en/sessions.md +3 -1
  123. package/docs/en/tui.md +84 -66
  124. package/docs/host-api.md +5 -1
  125. package/docs/providers.md +4 -2
  126. package/docs/rpc.md +19 -7
  127. package/docs/sessions.md +2 -1
  128. package/docs/tui-design.md +42 -29
  129. package/docs/tui.md +67 -49
  130. 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.2" : readPackageVersion();
44
+ AMA_VERSION = true ? "0.6.3" : readPackageVersion();
45
45
  }
46
46
  });
47
47
 
@@ -117,7 +117,7 @@ function agentEntry(agents, agentId) {
117
117
  const entry = agents[agentId];
118
118
  return typeof entry === "object" && !Array.isArray(entry) ? entry : void 0;
119
119
  }
120
- var STATUS_LINE_MODES, IMAGE_RESIZE_MODES, PLAN_BASH_MODES_STRICT_FIRST, PLAN_UNATTENDED_MODES, AGENTS_RESERVED_KEYS, MODELS_ENABLED_REF;
120
+ var STATUS_LINE_MODES, IMAGE_RESIZE_MODES, PLAN_BASH_MODES_STRICT_FIRST, PLAN_UNATTENDED_MODES, AGENTS_RESERVED_KEYS, SUBAGENT_BACKGROUND_MODES, MODELS_ENABLED_REF;
121
121
  var init_types_w5 = __esm({
122
122
  "src/config/types-w5.ts"() {
123
123
  "use strict";
@@ -126,6 +126,7 @@ var init_types_w5 = __esm({
126
126
  PLAN_BASH_MODES_STRICT_FIRST = ["deny", "readonly", "ask"];
127
127
  PLAN_UNATTENDED_MODES = ["stop", "approve"];
128
128
  AGENTS_RESERVED_KEYS = ["maxConcurrent", "sessionBudgetUsd", "dirs"];
129
+ SUBAGENT_BACKGROUND_MODES = ["auto", "always", "never"];
129
130
  MODELS_ENABLED_REF = /^[^/\s]+\/\S+$/;
130
131
  }
131
132
  });
@@ -898,6 +899,9 @@ Wrote ${path}: ${provider} +${plural(added, "model")}${kept > 0 ? `, ${kept} alr
898
899
  found: (provider, count2, url) => `${provider}: found ${plural(count2, "model")} (${url})
899
900
  `,
900
901
  unmatched: " models.dev: no match",
902
+ fromBackend: " (backend)",
903
+ noBackendWindow: (ids) => `The backend did not report a context window for ${ids}; using models.dev (128k when unmatched)
904
+ `,
901
905
  image: " · image",
902
906
  reasoning: " · reasoning",
903
907
  noTools: " · no tool calling",
@@ -971,6 +975,9 @@ Probing protocols: ${plural(count2, "model")} (${order}), at most ${plural(reque
971
975
  found: (provider, count2, url) => `${provider}:发现 ${count2} 个模型(${url})
972
976
  `,
973
977
  unmatched: " models.dev 未匹配",
978
+ fromBackend: "(后端)",
979
+ noBackendWindow: (ids) => `后端没有给出 ${ids} 的上下文窗口,改用 models.dev 的值(未匹配时按 128k)
980
+ `,
974
981
  image: " · 图片",
975
982
  reasoning: " · 思考",
976
983
  noTools: " · 不支持工具调用",
@@ -1274,7 +1281,7 @@ Estimated cost: ${estimate} (2 × ${tokens} tokens × catalog price)
1274
1281
  needsYes: "ama: cache-probe sends two billed requests; add --yes in non-interactive environments\n",
1275
1282
  requestFailed: (n, error2) => `ama: request ${n} failed: ${error2}
1276
1283
  `,
1277
- share: (percent2) => ` (the second request read ${percent2}% of the prefix)`,
1284
+ share: (percent3) => ` (the second request read ${percent3}% of the prefix)`,
1278
1285
  usageFields: (api, fields) => `usage fields (read for ${api}): ${fields.join(" / ")}`,
1279
1286
  verdict: (verdict, share) => `Verdict: ${verdict}${share}`,
1280
1287
  advice: (advice) => `Advice: ${advice}`
@@ -1526,7 +1533,7 @@ Estimated cost: ${estimate} (2 × ${tokens} tokens × catalog price)
1526
1533
  needsYes: "ama: cache-probe 会发两次计费请求,非交互环境需加 --yes\n",
1527
1534
  requestFailed: (n, error2) => `ama: 第 ${n} 次请求失败:${error2}
1528
1535
  `,
1529
- share: (percent2) => `(第二次读到前缀的 ${percent2}%)`,
1536
+ share: (percent3) => `(第二次读到前缀的 ${percent3}%)`,
1530
1537
  usageFields: (api, fields) => `usage 字段(${api} 读取):${fields.join(" / ")}`,
1531
1538
  verdict: (verdict, share) => `判定:${verdict}${share}`,
1532
1539
  advice: (advice) => `建议:${advice}`
@@ -1637,16 +1644,8 @@ var init_interactive_startup = __esm({
1637
1644
  templates: (n) => plural(n, "template"),
1638
1645
  host: (host) => `host ${host}`,
1639
1646
  warnings: (n) => `${plural(n, "warning")} (ama doctor)`,
1640
- hintCompact: "/help · Shift+Tab mode",
1641
- keyModel: "Model",
1642
- keyDir: "Folder",
1643
- keyMode: "Mode",
1644
- keyLoaded: "Loaded",
1645
- keyHost: "Host",
1646
- keyWarnings: "Warnings",
1647
1647
  thinking: (level) => `thinking ${level}`,
1648
1648
  preset: (preset) => `preset ${preset}`,
1649
- warningsValue: (n) => `${n} (see ama doctor)`,
1650
1649
  hint: "/help commands · Shift+Tab mode · Ctrl+O expand tool output"
1651
1650
  },
1652
1651
  ui: {
@@ -1683,16 +1682,8 @@ var init_interactive_startup = __esm({
1683
1682
  templates: (n) => `${n} 模板`,
1684
1683
  host: (host) => `宿主 ${host}`,
1685
1684
  warnings: (n) => `警告 ${n} 条(ama doctor)`,
1686
- hintCompact: "/help · Shift+Tab 切模式",
1687
- keyModel: "模型",
1688
- keyDir: "目录",
1689
- keyMode: "模式",
1690
- keyLoaded: "已加载",
1691
- keyHost: "宿主",
1692
- keyWarnings: "警告",
1693
1685
  thinking: (level) => `思考 ${level}`,
1694
1686
  preset: (preset) => `预设 ${preset}`,
1695
- warningsValue: (n) => `${n} 条(ama doctor 查看)`,
1696
1687
  hint: "/help 命令 · Shift+Tab 切模式 · Ctrl+O 展开工具输出"
1697
1688
  },
1698
1689
  ui: {
@@ -1888,6 +1879,7 @@ var init_interactive = __esm({
1888
1879
  "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",
1889
1880
  "Approval: 1–3 or ↑↓ Enter to choose, y allow a allow for this session n / Esc deny v full input",
1890
1881
  "Plan approval: 1 approve 2 run in a fresh context 3 keep revising 4 reject and leave Plan e edit plan Esc stay in Plan",
1882
+ "Ctrl+B move foreground sub-agent tasks to the background (in tmux: C-b C-b) ↓ (empty input) Agent bar: b background x twice to stop Enter open",
1891
1883
  "Ctrl+V paste a clipboard image (inserts @path) Ctrl+G footer two rows / one row",
1892
1884
  "Ctrl+C clear input (again to quit) Ctrl+D quit on empty input Tab complete @ reference a file"
1893
1885
  ].join("\n"),
@@ -1966,7 +1958,19 @@ var init_interactive = __esm({
1966
1958
  compact: "Status line: compact (one row)",
1967
1959
  cacheSilent: "n/a",
1968
1960
  sandbox: "sandbox",
1969
- cycleHint: "shift+tab to cycle"
1961
+ cycleHint: "shift+tab to cycle",
1962
+ /** [W6] 订阅配额行(full 第三行)与窄屏 / compact 短格式;标签自带分隔(英文冒号后空格)。 */
1963
+ quota: {
1964
+ session: "Session: ",
1965
+ reset: "Reset: ",
1966
+ weekly: "Weekly: ",
1967
+ weeklyReset: "Weekly Reset: ",
1968
+ window: (duration) => `${duration}: `,
1969
+ windowReset: (duration) => `${duration} Reset: `,
1970
+ short5h: "5h",
1971
+ shortWeek: "wk",
1972
+ pending: "Quota: shown after the first request"
1973
+ }
1970
1974
  },
1971
1975
  subagent: {
1972
1976
  running: (elapsed) => `running ${elapsed}`,
@@ -2007,6 +2011,7 @@ var init_interactive = __esm({
2007
2011
  "Shift+Tab / Tab(输入为空时)切换权限模式(进入 Bypass 前确认) Ctrl+L 模型 Ctrl+T 思考级别 Ctrl+O 展开工具输出与思考",
2008
2012
  "审批:1–3 或 ↑↓ Enter 选择,y 允许 a 本会话允许同类 n / Esc 拒绝 v 完整输入",
2009
2013
  "计划审批:1 批准 2 新上下文执行 3 继续修改 4 放弃并退出 Plan e 编辑计划 Esc 留在 Plan",
2014
+ "Ctrl+B 前台子 Agent 任务转后台(tmux 里 C-b C-b) ↓(空输入)Agent 栏:b 转后台 x 连按两次停止 Enter 打开",
2010
2015
  "Ctrl+V 粘贴剪贴板图片(插入 @路径) Ctrl+G 底部信息行两行 / 一行",
2011
2016
  "Ctrl+C 清空输入(再按退出) Ctrl+D 空输入时退出 Tab 补全 @ 引用文件"
2012
2017
  ].join("\n"),
@@ -2081,7 +2086,18 @@ var init_interactive = __esm({
2081
2086
  compact: "状态栏:精简(一行)",
2082
2087
  cacheSilent: "未报告",
2083
2088
  sandbox: "沙箱",
2084
- cycleHint: "shift+tab 切换"
2089
+ cycleHint: "shift+tab 切换",
2090
+ quota: {
2091
+ session: "5 小时:",
2092
+ reset: "重置:",
2093
+ weekly: "本周:",
2094
+ weeklyReset: "本周重置:",
2095
+ window: (duration) => `${duration}:`,
2096
+ windowReset: (duration) => `${duration}重置:`,
2097
+ short5h: "5h",
2098
+ shortWeek: "周",
2099
+ pending: "配额:首次请求后显示"
2100
+ }
2085
2101
  },
2086
2102
  subagent: {
2087
2103
  running: (elapsed) => `运行中 ${elapsed}`,
@@ -2964,7 +2980,7 @@ var init_report = __esm({
2964
2980
  statusline: "Footer info two lines / one line (Ctrl+G)",
2965
2981
  planArgs: "[goal] | approve [mode|fresh] | reject",
2966
2982
  plan: "Show the plan and status; with a goal, enter Plan mode; approve / discard a pending plan",
2967
- tasks: "Sub-agent tasks: status, output, stop",
2983
+ tasks: "Sub-agent tasks: status, output, stop, background",
2968
2984
  agents: "Sub-agent types and external agents (installed, version)",
2969
2985
  paste: "Paste an image from the clipboard (Ctrl+V)",
2970
2986
  exit: "Exit",
@@ -2984,7 +3000,7 @@ var init_report = __esm({
2984
3000
  warmingSet: (mode) => `Warming: ${mode} (this session)`,
2985
3001
  cacheUsage: "usage: /cache [warm off|streaming|idle | fingerprint]",
2986
3002
  taskStopped: (id) => `Stopped ${id}`,
2987
- tasksUsage: "usage: /tasks [id] | /tasks stop <id>",
3003
+ tasksUsage: "usage: /tasks [id] | /tasks stop <id> | /tasks bg [id]",
2988
3004
  newSession: (id) => `New session ${id}`,
2989
3005
  resumed: (id, messages) => `Resumed session ${id} (${plural(messages, "message")})`,
2990
3006
  forked: (id) => `Forked to new session ${id}`,
@@ -3041,9 +3057,9 @@ Available: ${inactive.join(", ")}` : ""}`,
3041
3057
  missPrefix: "prefix changed",
3042
3058
  missEvicted: "evicted by the server",
3043
3059
  missNotice: (reason, tokens, cost2) => `Cache miss (${reason}): re-billed ${tokens} tokens${cost2 === void 0 ? "" : ` (about ${cost2})`}`,
3044
- pressure: (percent2) => `Context ${percent2}% used`,
3045
- pressureTurns: (percent2, turns) => `Context ${percent2}% used, about ${plural(turns, "turn")} left (average of the last 5 turns)`,
3046
- pressureTokens: (percent2, tokens) => `Context ${percent2}% used, ${tokens} tokens left`,
3060
+ pressure: (percent3) => `Context ${percent3}% used`,
3061
+ pressureTurns: (percent3, turns) => `Context ${percent3}% used, about ${plural(turns, "turn")} left (average of the last 5 turns)`,
3062
+ pressureTokens: (percent3, tokens) => `Context ${percent3}% used, ${tokens} tokens left`,
3047
3063
  warmSent: (read2, cost2) => {
3048
3064
  const parts = [
3049
3065
  ...read2 !== void 0 ? [`read ${read2} tokens`] : [],
@@ -3086,7 +3102,7 @@ Available: ${inactive.join(", ")}` : ""}`,
3086
3102
  keyMisses: "Misses",
3087
3103
  keyWarming: "Warming",
3088
3104
  keyContext: "Context",
3089
- context: (percent2, remaining, turns) => `${percent2}%${remaining !== void 0 ? `, ≈ ${remaining} tokens left` : ""}${turns !== void 0 ? ` ≈ ${plural(turns, "turn")}` : ""}`,
3105
+ context: (percent3, remaining, turns) => `${percent3}%${remaining !== void 0 ? `, ≈ ${remaining} tokens left` : ""}${turns !== void 0 ? ` ≈ ${plural(turns, "turn")}` : ""}`,
3090
3106
  keySubtasks: "Subtasks",
3091
3107
  subtasks: (count2, rate, rebilled) => `${plural(count2, "session")}, hit rate ${rate}${rebilled !== void 0 ? `, re-billed ${rebilled} tokens` : ""}`,
3092
3108
  title: "Cache"
@@ -3099,14 +3115,14 @@ Available: ${inactive.join(", ")}` : ""}`,
3099
3115
  tasks: (total) => plural(total, "task"),
3100
3116
  running: (count2) => `running ${count2}`,
3101
3117
  status: (status, count2) => {
3102
- const labels = {
3118
+ const labels2 = {
3103
3119
  completed: "completed",
3104
3120
  failed: "failed",
3105
3121
  aborted: "stopped",
3106
3122
  max_turns: "out of turns",
3107
3123
  interrupted: "interrupted"
3108
3124
  };
3109
- return `${labels[status] ?? status} ${count2}`;
3125
+ return `${labels2[status] ?? status} ${count2}`;
3110
3126
  },
3111
3127
  tasksLine: (parts) => `${parts.join(" · ")} (/tasks)`,
3112
3128
  keySession: "Session",
@@ -3118,7 +3134,7 @@ Available: ${inactive.join(", ")}` : ""}`,
3118
3134
  keyUsage: "Usage",
3119
3135
  usage: (input2, output, cacheRead, cacheWrite, cost2) => `input ${input2} · output ${output} · cache read ${cacheRead} · cache write ${cacheWrite} · ${cost2}`,
3120
3136
  keyContext: "Context",
3121
- context: (tokens, window2, percent2) => `${tokens} / ${window2} (${percent2}%)`,
3137
+ context: (tokens, window2, percent3) => `${tokens} / ${window2} (${percent3}%)`,
3122
3138
  keyAgents: "Sub-agents",
3123
3139
  externalTitle: "External agents",
3124
3140
  noFingerprint: "This session does not provide a prefix fingerprint",
@@ -3203,7 +3219,7 @@ Available: ${inactive.join(", ")}` : ""}`,
3203
3219
  statusline: "底部信息行两行 / 一行(Ctrl+G)",
3204
3220
  planArgs: "[目标] | approve [模式|fresh] | reject",
3205
3221
  plan: "查看计划与状态;带目标进入 Plan 模式;批准 / 放弃待审批的计划",
3206
- tasks: "子 Agent 任务:状态、输出、停止",
3222
+ tasks: "子 Agent 任务:状态、输出、停止、转后台",
3207
3223
  agents: "子 Agent 类型与外部 Agent(安装状态、版本)",
3208
3224
  paste: "粘贴剪贴板里的图片(Ctrl+V)",
3209
3225
  exit: "退出",
@@ -3222,7 +3238,7 @@ Available: ${inactive.join(", ")}` : ""}`,
3222
3238
  warmingSet: (mode) => `保温:${mode}(本会话)`,
3223
3239
  cacheUsage: "用法:/cache [warm off|streaming|idle | fingerprint]",
3224
3240
  taskStopped: (id) => `已停止 ${id}`,
3225
- tasksUsage: "用法:/tasks [id] | /tasks stop <id>",
3241
+ tasksUsage: "用法:/tasks [id] | /tasks stop <id> | /tasks bg [id]",
3226
3242
  newSession: (id) => `已新建会话 ${id}`,
3227
3243
  resumed: (id, messages) => `已恢复会话 ${id}(${messages} 条消息)`,
3228
3244
  forked: (id) => `已分叉到新会话 ${id}`,
@@ -3277,9 +3293,9 @@ Available: ${inactive.join(", ")}` : ""}`,
3277
3293
  missPrefix: "前缀变化",
3278
3294
  missEvicted: "服务端已淘汰",
3279
3295
  missNotice: (reason, tokens, cost2) => `缓存未命中(${reason}):重计费 ${tokens} token${cost2 === void 0 ? "" : `(约 ${cost2})`}`,
3280
- pressure: (percent2) => `上下文已用 ${percent2}%`,
3281
- pressureTurns: (percent2, turns) => `上下文已用 ${percent2}%,约剩 ${turns} 回合(按最近 5 回合均值)`,
3282
- pressureTokens: (percent2, tokens) => `上下文已用 ${percent2}%,余量 ${tokens} token`,
3296
+ pressure: (percent3) => `上下文已用 ${percent3}%`,
3297
+ pressureTurns: (percent3, turns) => `上下文已用 ${percent3}%,约剩 ${turns} 回合(按最近 5 回合均值)`,
3298
+ pressureTokens: (percent3, tokens) => `上下文已用 ${percent3}%,余量 ${tokens} token`,
3283
3299
  warmSent: (read2, cost2) => {
3284
3300
  const text = `${read2 !== void 0 ? `读 ${read2} token` : ""}${cost2 !== void 0 ? `${read2 !== void 0 ? "," : ""}${cost2}` : ""}`;
3285
3301
  return `缓存保温已刷新${text !== "" ? `(${text})` : ""}`;
@@ -3319,7 +3335,7 @@ Available: ${inactive.join(", ")}` : ""}`,
3319
3335
  keyMisses: "未命中",
3320
3336
  keyWarming: "保温",
3321
3337
  keyContext: "上下文",
3322
- context: (percent2, remaining, turns) => `${percent2}%${remaining !== void 0 ? `,余量 ≈ ${remaining} token` : ""}${turns !== void 0 ? ` ≈ ${turns} 回合` : ""}`,
3338
+ context: (percent3, remaining, turns) => `${percent3}%${remaining !== void 0 ? `,余量 ≈ ${remaining} token` : ""}${turns !== void 0 ? ` ≈ ${turns} 回合` : ""}`,
3323
3339
  keySubtasks: "子任务",
3324
3340
  subtasks: (count2, rate, rebilled) => `${count2} 个会话,命中率 ${rate}${rebilled !== void 0 ? `,重计费 ${rebilled} token` : ""}`,
3325
3341
  title: "缓存"
@@ -3331,14 +3347,14 @@ Available: ${inactive.join(", ")}` : ""}`,
3331
3347
  tasks: (total) => `${total} 个任务`,
3332
3348
  running: (count2) => `运行中 ${count2}`,
3333
3349
  status: (status, count2) => {
3334
- const labels = {
3350
+ const labels2 = {
3335
3351
  completed: "完成",
3336
3352
  failed: "失败",
3337
3353
  aborted: "已停止",
3338
3354
  max_turns: "轮数耗尽",
3339
3355
  interrupted: "已中断"
3340
3356
  };
3341
- return `${labels[status] ?? status} ${count2}`;
3357
+ return `${labels2[status] ?? status} ${count2}`;
3342
3358
  },
3343
3359
  tasksLine: (parts) => `${parts.join(" · ")}(/tasks)`,
3344
3360
  keySession: "会话",
@@ -3350,7 +3366,7 @@ Available: ${inactive.join(", ")}` : ""}`,
3350
3366
  keyUsage: "用量",
3351
3367
  usage: (input2, output, cacheRead, cacheWrite, cost2) => `输入 ${input2} · 输出 ${output} · 缓存读 ${cacheRead} · 缓存写 ${cacheWrite} · ${cost2}`,
3352
3368
  keyContext: "上下文",
3353
- context: (tokens, window2, percent2) => `${tokens} / ${window2}(${percent2}%)`,
3369
+ context: (tokens, window2, percent3) => `${tokens} / ${window2}(${percent3}%)`,
3354
3370
  keyAgents: "子 Agent",
3355
3371
  externalTitle: "外部 Agent",
3356
3372
  noFingerprint: "当前会话不提供前缀指纹",
@@ -3432,6 +3448,8 @@ var init_print = __esm({
3432
3448
  retry: (attempt, max, seconds2, error2) => `ama: ↻ retry ${attempt}/${max} (in ${seconds2}s): ${error2}
3433
3449
  `,
3434
3450
  modelFailed: "model call failed",
3451
+ waitingTasks: (n) => `ama: waiting for ${plural(n, "background task")} and their notification turns (Ctrl+C stops them)
3452
+ `,
3435
3453
  planPending: (version, where) => `ama: plan v${version} saved, awaiting approval (not executed): ${where}; -p does not approve on anyone's behalf — approve it in the interactive UI or via RPC plan_response, or set plan.unattended: approve to let -p approve and continue`,
3436
3454
  limitTurns: (limit) => `ama: reached the turn limit ${limit} (--max-turns / limits.maxTurns); run ended before finishing`,
3437
3455
  limitCost: (limit, spent) => `ama: reached the cost limit ${limit} (spent ${spent}; --max-cost / limits.maxCostUsd); run ended before finishing`,
@@ -3441,6 +3459,7 @@ var init_print = __esm({
3441
3459
  rpc: {
3442
3460
  unknownCommand: (type) => `unknown command: ${type}`,
3443
3461
  parseFailed: (error2) => `JSON parse failed: ${error2}`,
3462
+ badTaskId: "taskId must be a string",
3444
3463
  missingType: "missing type",
3445
3464
  unsupported: "this session does not support this command",
3446
3465
  noPlan: "this session has no plan extension",
@@ -3471,6 +3490,8 @@ var init_print = __esm({
3471
3490
  seconds: (n) => `${n} 秒`,
3472
3491
  millis: (n) => `${n} 毫秒`,
3473
3492
  needsPrompt: "ama: -p 需要提示(位置参数或 stdin 管道)\n",
3493
+ waitingTasks: (n) => `ama: 等待 ${n} 个后台任务及其通知回合结束(Ctrl+C 中止)
3494
+ `,
3474
3495
  retry: (attempt, max, seconds2, error2) => `ama: ↻ 重试 ${attempt}/${max}(${seconds2}s 后):${error2}
3475
3496
  `,
3476
3497
  modelFailed: "模型调用失败",
@@ -3480,6 +3501,7 @@ var init_print = __esm({
3480
3501
  denied: (count2, tools, reason) => `ama: ${count2} 次工具调用被拒:${tools.join("、")}${reason !== "" ? `(${reason})` : ""};-p 没有人审批,需要放行时用 --permission-mode auto-edit|auto 或 --allow <规则>`
3481
3502
  },
3482
3503
  rpc: {
3504
+ badTaskId: "taskId 应为字符串",
3483
3505
  unknownCommand: (type) => `未知命令:${type}`,
3484
3506
  parseFailed: (error2) => `JSON 解析失败:${error2}`,
3485
3507
  missingType: "缺少 type",
@@ -3530,7 +3552,7 @@ var init_config_keys = __esm({
3530
3552
  "limits.maxTurns": "unset: unlimited",
3531
3553
  "limits.maxCostUsd": "unset: unlimited",
3532
3554
  "ui.replyLanguage": "unset: no reply-language rule is appended (zero-byte change)",
3533
- "ui.agentBar": "auto in a standalone terminal, off in an embedding host (with a profile)",
3555
+ "ui.agentBar": "auto (embedding hosts turn it off in their profile)",
3534
3556
  "auth.chatgpt.clientId": "siwc registers dynamically on first login; codex uses the public client id (AMA_CHATGPT_CLIENT_ID overrides)",
3535
3557
  "auth.chatgpt.redirectPorts": "siwc 1455 → any free port; codex 1455 → 1457"
3536
3558
  };
@@ -3553,7 +3575,7 @@ var init_config_keys = __esm({
3553
3575
  "limits.maxTurns": "不设:不限",
3554
3576
  "limits.maxCostUsd": "不设:不限",
3555
3577
  "ui.replyLanguage": "不设:不追加回复语言规则(零字节变化)",
3556
- "ui.agentBar": "独立终端 auto,嵌入宿主(有 profile)off",
3578
+ "ui.agentBar": "auto(嵌入宿主要关在自己的 profile 写 off)",
3557
3579
  "auth.chatgpt.clientId": "siwc 首次登录动态注册;codex 用公开客户端 id(AMA_CHATGPT_CLIENT_ID 覆盖)",
3558
3580
  "auth.chatgpt.redirectPorts": "siwc 1455 → 任意空闲端口;codex 1455 → 1457"
3559
3581
  };
@@ -3602,13 +3624,14 @@ var init_config_keys = __esm({
3602
3624
  "ui.tuiMode": "Terminal interface layout (only regular for now)",
3603
3625
  "ui.quietStartup": "How much the startup screen shows",
3604
3626
  "ui.ascii": "ASCII glyphs (> * L, + - |); when unset it is detected: on when the locale lacks UTF-8, TERM=linux or a legacy conhost; AMA_ASCII=1/0 overrides",
3605
- "ui.compact": "No blank lines between message blocks and no box around the startup header",
3606
- "ui.animation": "false: the spinner stays still while running and redraws only when seconds change",
3627
+ "ui.compact": "No blank lines between message blocks and no AMA logo in the startup header",
3628
+ "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
+ "ui.animation": "false: the spinner stays still while running and redraws only when seconds change; the startup logo does not animate",
3607
3630
  "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",
3608
3631
  "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)",
3609
3632
  "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",
3610
3633
  "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)",
3611
- "ui.agentBar": "Agent bar (sub-agent list above the status line): auto shows it while tasks exist, off hides it; enter with Ctrl+B or ↓ on an empty input (effective from wave 6 W6-A)",
3634
+ "ui.agentBar": "Agent bar (sub-agent list above the status line): auto shows it while tasks exist, off hides it; press ↓ on an empty input to enter it",
3612
3635
  memory: "Cross-session memory (docs/memory.md): off by default, requests are byte-identical while off; project level can only set enabled: false (effective from wave 6 W6-M)",
3613
3636
  "memory.enabled": "Master switch; --memory / --no-memory and AMA_MEMORY=0|1 override",
3614
3637
  "memory.scopes": "Enabled scopes: user (data directory, across projects), project (keyed by a hash of the repository path)",
@@ -3655,10 +3678,12 @@ var init_config_keys = __esm({
3655
3678
  "agents.maxConcurrent": "Total concurrency of external agents",
3656
3679
  "agents.sessionBudgetUsd": "USD budget for external agents in this session",
3657
3680
  "agents.dirs": "Extra sub-agent definition directories (*.md); accumulated across levels",
3658
- subagents: "ama's own task sub-sessions; user level only (effective from wave 5 W5-G)",
3681
+ subagents: "ama's own task sub-sessions; user level only except background / autoBackgroundAfterMs (effective from wave 5 W5-G)",
3659
3682
  "subagents.maxConcurrent": "Sub-sessions running at the same time",
3660
3683
  "subagents.maxPending": "Queue limit; task fails beyond it",
3661
3684
  "subagents.defaultModel": "Default sub-session model provider/model[@channel]",
3685
+ "subagents.background": "Whether task runs in the background by default: auto = background in the TUI / RPC / ACP and foreground with -p; always / never are fixed. The call argument and the agent type's background: override it; user, project and host level",
3686
+ "subagents.autoBackgroundAfterMs": "Move a foreground task to the background after it has run this many milliseconds; 0 disables; user and project level",
3662
3687
  models: "Model aliases and the model picker list; user level only (effective from wave 5 W5-G)",
3663
3688
  "models.aliases": "Models that model: fast / strong in sub-agent definitions point to",
3664
3689
  "models.aliases.fast": "Model for the fast alias provider/model[@channel]",
@@ -3721,13 +3746,14 @@ var init_config_keys = __esm({
3721
3746
  "ui.tuiMode": "终端界面形态(目前只有 regular)",
3722
3747
  "ui.quietStartup": "启动画面详略",
3723
3748
  "ui.ascii": "ASCII 字形(> * L、+ - |);不写时自动检测:区域设置不含 UTF-8、TERM=linux、旧 conhost 时开启;AMA_ASCII=1/0 覆盖",
3724
- "ui.compact": "消息区块间不空行、启动头不画框",
3725
- "ui.animation": "false:运行中 spinner 静止,只在秒数变化时重绘",
3749
+ "ui.compact": "消息区块间不空行、启动头不画 AMA 字符画",
3750
+ "ui.logo": "启动头的 AMA 字符画:auto 在终端宽 48 列以上时画,off 只显示信息行",
3751
+ "ui.animation": "false:运行中 spinner 静止,只在秒数变化时重绘;启动字符画不播放动画",
3726
3752
  "ui.restoreOnCancel": "运行中 Esc 中断、本回合还没有任何回复或工具调用时,撤回该回合并把原消息放回输入框",
3727
3753
  "ui.statusLine": "底部信息行:full 两行(速率行 + 状态行),compact 一行;运行时 Ctrl+G 或 /statusline 切换(第五波 W5-A 起生效)",
3728
3754
  "ui.language": "界面语言:auto 按 LC_ALL / LC_MESSAGES / LANG 判断(zh* 为中文,其余英文),zh,en;AMA_LANG、--lang 覆盖;只影响界面,发给模型的文本固定英文",
3729
3755
  "ui.replyLanguage": "模型回复语言(如 Chinese):会话开始在 rules 节末尾追加一句英文规则;不设时零字节变化;只认用户级(第六波起生效)",
3730
- "ui.agentBar": "Agent 栏(状态行上方的子 Agent 列表):auto 有任务时显示,off 不显示;Ctrl+B / 空输入时 ↓ 进入(第六波 W6-A 起生效)",
3756
+ "ui.agentBar": "Agent 栏(状态行上方的子 Agent 列表):auto 有任务时显示,off 不显示;空输入时按 ↓ 进入",
3731
3757
  memory: "跨会话记忆(docs/memory.md):缺省关闭,关闭时请求逐字节不变;项目级只能设 enabled: false(第六波 W6-M 起生效)",
3732
3758
  "memory.enabled": "总开关;--memory / --no-memory、AMA_MEMORY=0|1 覆盖",
3733
3759
  "memory.scopes": "启用的作用域:user(数据目录,跨项目)、project(按仓库路径哈希)",
@@ -3774,10 +3800,12 @@ var init_config_keys = __esm({
3774
3800
  "agents.maxConcurrent": "外部 Agent 的总并发",
3775
3801
  "agents.sessionBudgetUsd": "本会话外部 Agent 的美元预算",
3776
3802
  "agents.dirs": "追加的子 Agent 定义目录(*.md);各层累加",
3777
- subagents: "ama 自己的 task 子会话;只认用户级(第五波 W5-G 起生效)",
3803
+ subagents: "ama 自己的 task 子会话;除 background / autoBackgroundAfterMs 外只认用户级(第五波 W5-G 起生效)",
3778
3804
  "subagents.maxConcurrent": "同时运行的子会话数",
3779
3805
  "subagents.maxPending": "排队上限,超出时 task 报错",
3780
3806
  "subagents.defaultModel": "子会话缺省模型 provider/model[@channel]",
3807
+ "subagents.background": "task 缺省是否后台:auto = TUI / RPC / ACP 下后台、-p 下前台;always / never 固定。调用参数与类型定义的 background: 优先;用户、项目、宿主级都认",
3808
+ "subagents.autoBackgroundAfterMs": "前台任务运行超过该毫秒数自动转后台;0 关闭;用户、项目级都认",
3781
3809
  models: "模型别名与模型选择器清单;只认用户级(第五波 W5-G 起生效)",
3782
3810
  "models.aliases": "子 Agent 定义里 model: fast / strong 指向的模型",
3783
3811
  "models.aliases.fast": "fast 别名的模型 provider/model[@channel]",
@@ -4628,7 +4656,37 @@ var init_agents = __esm({
4628
4656
  bar: {
4629
4657
  more: (n) => `${n} more`,
4630
4658
  keys: (up, down) => `${up}${down} select · Enter open · Esc back`,
4631
- empty: "No sub-agent tasks yet"
4659
+ /** [W7-C] 聚焦时的完整按键行(窄屏退回 `keys`)。 */
4660
+ keysFull: (up, down) => `${up}${down} select · Enter open · b background · x stop · Esc back`,
4661
+ empty: "No sub-agent tasks yet",
4662
+ /** [W7-A] 运行提示行的附加项(有子 Agent 任务时)。 */
4663
+ runHint: (down) => `${down} Agent bar`
4664
+ },
4665
+ /** [W7-A] 进栏键落空时的一行提示。 */
4666
+ focus: {
4667
+ busyInput: (down) => `Input is not empty; clear it and press ${down} for the Agent bar`,
4668
+ disabled: "Agent bar is off (ui.agentBar); use /tasks"
4669
+ },
4670
+ /** [W7-C] 前台任务转后台。 */
4671
+ background: {
4672
+ done: (ids) => `Moved to the background: ${ids}; you'll be notified when it finishes`,
4673
+ none: "No foreground task to move to the background",
4674
+ notForeground: (taskId) => `${taskId} is not a running foreground task`,
4675
+ runHint: (key) => `${key} to background`,
4676
+ timeout: (taskId, agent) => `${taskId} ${agent} ran long and moved to the background (subagents.autoBackgroundAfterMs)`,
4677
+ host: (taskId, agent) => `${taskId} ${agent} was moved to the background by the host`,
4678
+ moved: (elapsed) => `moved to the background · ${elapsed}`,
4679
+ started: "started in the background",
4680
+ interrupted: (ids) => `Interrupted (background task ${ids} keeps running; Esc doesn't affect it)`
4681
+ },
4682
+ /** [W7-C] 栏内 x 停止(双击确认)。 */
4683
+ stop: {
4684
+ confirm: (taskId) => `Press x again to stop ${taskId}`,
4685
+ ended: (taskId) => `${taskId} has already finished`
4686
+ },
4687
+ /** [W7-C] 后台任务的审批停靠在栏里。 */
4688
+ approval: {
4689
+ runHint: (down) => `${down} handle approval`
4632
4690
  },
4633
4691
  view: {
4634
4692
  back: "Esc back",
@@ -4640,7 +4698,7 @@ var init_agents = __esm({
4640
4698
  resumed: (taskId) => `Sent to ${taskId}; it continues in the background`,
4641
4699
  sendFailed: (message) => `Could not send: ${message}`,
4642
4700
  stopped: (taskId) => `Stopped ${taskId}`,
4643
- commandsHere: "Only /tasks stop works here; press Esc to go back for other commands",
4701
+ commandsHere: "Only /tasks stop and /tasks bg work here; press Esc to go back for other commands",
4644
4702
  removed: (taskId) => `Task ${taskId} is gone`,
4645
4703
  unknown: (taskId) => `No task ${taskId}`,
4646
4704
  turn: (n) => `turn ${n}`,
@@ -4666,7 +4724,31 @@ var init_agents = __esm({
4666
4724
  bar: {
4667
4725
  more: (n) => `另 ${n} 个`,
4668
4726
  keys: (up, down) => `${up}${down} 选择 · Enter 打开 · Esc 返回`,
4669
- empty: "还没有子 Agent 任务"
4727
+ keysFull: (up, down) => `${up}${down} 选择 · Enter 打开 · b 转后台 · x 停止 · Esc 返回`,
4728
+ empty: "还没有子 Agent 任务",
4729
+ runHint: (down) => `${down} Agent 栏`
4730
+ },
4731
+ focus: {
4732
+ busyInput: (down) => `输入框有字;清空后再按 ${down} 进 Agent 栏`,
4733
+ disabled: "Agent 栏已关闭(ui.agentBar),用 /tasks"
4734
+ },
4735
+ background: {
4736
+ done: (ids) => `已转后台:${ids},完成后会通知`,
4737
+ none: "没有可转后台的前台任务",
4738
+ notForeground: (taskId) => `${taskId} 不是运行中的前台任务`,
4739
+ runHint: (key) => `${key} 转后台`,
4740
+ timeout: (taskId, agent) => `${taskId} ${agent} 运行较久,已自动转后台(subagents.autoBackgroundAfterMs)`,
4741
+ host: (taskId, agent) => `${taskId} ${agent} 已由宿主转后台`,
4742
+ moved: (elapsed) => `已转后台 · ${elapsed}`,
4743
+ started: "已在后台启动",
4744
+ interrupted: (ids) => `已中断(后台任务 ${ids} 仍在运行,Esc 不影响)`
4745
+ },
4746
+ stop: {
4747
+ confirm: (taskId) => `再按 x 停止 ${taskId}`,
4748
+ ended: (taskId) => `${taskId} 已结束`
4749
+ },
4750
+ approval: {
4751
+ runHint: (down) => `${down} 处理审批`
4670
4752
  },
4671
4753
  view: {
4672
4754
  back: "Esc 返回",
@@ -4678,7 +4760,7 @@ var init_agents = __esm({
4678
4760
  resumed: (taskId) => `已发给 ${taskId},在后台继续`,
4679
4761
  sendFailed: (message) => `发送失败:${message}`,
4680
4762
  stopped: (taskId) => `已停止 ${taskId}`,
4681
- commandsHere: "这里只能用 /tasks stop;其它命令按 Esc 返回后输入",
4763
+ commandsHere: "这里只能用 /tasks stop 与 /tasks bg;其它命令按 Esc 返回后输入",
4682
4764
  removed: (taskId) => `任务 ${taskId} 已不存在`,
4683
4765
  unknown: (taskId) => `没有任务 ${taskId}`,
4684
4766
  turn: (n) => `第 ${n} 轮`,
@@ -4702,7 +4784,7 @@ var init_trace = __esm({
4702
4784
  title: "Trace",
4703
4785
  /** 顶部汇总行。 */
4704
4786
  summary: (turns, requests, tools) => `${plural(turns, "turn")} · ${plural(requests, "request")} · ${plural(tools, "tool call")}`,
4705
- cache: (percent2) => `cache ${percent2}`,
4787
+ cache: (percent3) => `cache ${percent3}`,
4706
4788
  empty: "No turns yet",
4707
4789
  /** 子轨迹标题(`/trace t2`)。 */
4708
4790
  taskTitle: (taskId, agent) => `Trace · task ${taskId} ${agent}`,
@@ -4820,7 +4902,7 @@ var init_trace = __esm({
4820
4902
  zh24 = {
4821
4903
  title: "轨迹",
4822
4904
  summary: (turns, requests, tools) => `${turns} 回合 · ${requests} 请求 · ${tools} 次工具`,
4823
- cache: (percent2) => `缓存 ${percent2}`,
4905
+ cache: (percent3) => `缓存 ${percent3}`,
4824
4906
  empty: "还没有回合",
4825
4907
  taskTitle: (taskId, agent) => `轨迹 · 任务 ${taskId} ${agent}`,
4826
4908
  taskNotFound: (taskId) => `本会话没有任务 ${taskId}(见 /tasks)`,
@@ -5156,7 +5238,7 @@ Then paste the full URL from the address bar (http://127.0.0.1:…/auth/callback
5156
5238
  expired: " access token expired (refreshed on next use)",
5157
5239
  needsLogin: (provider) => ` sign-in expired: run ama auth login ${provider}`,
5158
5240
  quota: (parts) => ` quota: ${parts}`,
5159
- quotaWindow: (window2, percent2, reset) => `${window2} ${percent2}%${reset !== void 0 ? ` (resets ${reset})` : ""}`,
5241
+ quotaWindow: (window2, percent3, reset) => `${window2} ${percent3}%${reset !== void 0 ? ` (resets ${reset})` : ""}`,
5160
5242
  window5h: "5h",
5161
5243
  windowWeek: "weekly",
5162
5244
  windowOther: (duration) => `${duration} window`,
@@ -5263,7 +5345,7 @@ Then paste the full URL from the address bar (http://127.0.0.1:…/auth/callback
5263
5345
  expired: " access token 已过期(下次使用时自动刷新)",
5264
5346
  needsLogin: (provider) => ` 登录已失效:运行 ama auth login ${provider}`,
5265
5347
  quota: (parts) => ` 配额:${parts}`,
5266
- quotaWindow: (window2, percent2, reset) => `${window2} ${percent2}%${reset !== void 0 ? `(${reset} 重置)` : ""}`,
5348
+ quotaWindow: (window2, percent3, reset) => `${window2} ${percent3}%${reset !== void 0 ? `(${reset} 重置)` : ""}`,
5267
5349
  window5h: "5 小时",
5268
5350
  windowWeek: "周",
5269
5351
  windowOther: (duration) => `${duration} 窗口`,
@@ -5311,6 +5393,7 @@ var init_settings = __esm({
5311
5393
  "ui.showThinking": "Thinking display",
5312
5394
  "ui.compact": "Compact layout",
5313
5395
  "ui.animation": "Spinner animation",
5396
+ "ui.logo": "Startup logo",
5314
5397
  "ui.statusLine": "Status line",
5315
5398
  "ui.restoreOnCancel": "Restore on cancel",
5316
5399
  "ui.quietStartup": "Startup screen",
@@ -5370,6 +5453,8 @@ var init_settings = __esm({
5370
5453
  "agents.sessionBudgetUsd": "External agent budget",
5371
5454
  "subagents.maxConcurrent": "Subagents in parallel",
5372
5455
  "subagents.maxPending": "Subagent queue limit",
5456
+ "subagents.background": "Subagents in background",
5457
+ "subagents.autoBackgroundAfterMs": "Auto-background after (ms)",
5373
5458
  "memory.enabled": "Memory",
5374
5459
  "memory.subagents": "Memory in subagents"
5375
5460
  },
@@ -5406,8 +5491,8 @@ var init_settings = __esm({
5406
5491
  defaultChoice: "(unset: default)",
5407
5492
  summaryTitle: "Settings changed",
5408
5493
  summaryLine: (label2, from, to, scope) => `${label2}: ${from} → ${to} (${scope})`,
5409
- summaryRestart: (labels) => `Takes effect after restart: ${labels}`,
5410
- summaryNextSession: (labels) => `Takes effect in a new session (/new): ${labels}`,
5494
+ summaryRestart: (labels2) => `Takes effect after restart: ${labels2}`,
5495
+ summaryNextSession: (labels2) => `Takes effect in a new session (/new): ${labels2}`,
5411
5496
  setDone: (key, value, scope) => `${key} = ${value} (${scope})`,
5412
5497
  unsetDone: (key, value, scope) => `${key} reset (${scope}); now ${value}`,
5413
5498
  stillOverridden: (key, source) => `${key} is still overridden by ${source}; the saved value applies once that is removed`,
@@ -5479,6 +5564,7 @@ var init_settings = __esm({
5479
5564
  "ui.showThinking": "思考内容",
5480
5565
  "ui.compact": "紧凑布局",
5481
5566
  "ui.animation": "spinner 动画",
5567
+ "ui.logo": "启动字符画",
5482
5568
  "ui.statusLine": "底部信息行",
5483
5569
  "ui.restoreOnCancel": "中断后放回原消息",
5484
5570
  "ui.quietStartup": "启动画面",
@@ -5538,6 +5624,8 @@ var init_settings = __esm({
5538
5624
  "agents.sessionBudgetUsd": "外部 Agent 预算",
5539
5625
  "subagents.maxConcurrent": "子 Agent 并发",
5540
5626
  "subagents.maxPending": "子 Agent 排队上限",
5627
+ "subagents.background": "子 Agent 缺省后台",
5628
+ "subagents.autoBackgroundAfterMs": "自动转后台(毫秒)",
5541
5629
  "memory.enabled": "记忆",
5542
5630
  "memory.subagents": "子 Agent 的记忆"
5543
5631
  },
@@ -5574,8 +5662,8 @@ var init_settings = __esm({
5574
5662
  defaultChoice: "(不设:缺省)",
5575
5663
  summaryTitle: "设置已修改",
5576
5664
  summaryLine: (label2, from, to, scope) => `${label2}:${from} → ${to}(${scope})`,
5577
- summaryRestart: (labels) => `以下项重启后生效:${labels}`,
5578
- summaryNextSession: (labels) => `以下项新会话(/new)生效:${labels}`,
5665
+ summaryRestart: (labels2) => `以下项重启后生效:${labels2}`,
5666
+ summaryNextSession: (labels2) => `以下项新会话(/new)生效:${labels2}`,
5579
5667
  setDone: (key, value, scope) => `${key} = ${value}(${scope})`,
5580
5668
  unsetDone: (key, value, scope) => `已恢复 ${key}(${scope}),现为 ${value}`,
5581
5669
  stillOverridden: (key, source) => `${key} 仍被 ${source} 覆盖,撤掉后写入的值才生效`,
@@ -6327,11 +6415,19 @@ function validateConfigW5(c, config) {
6327
6415
  }
6328
6416
  );
6329
6417
  checkAgents(c, config);
6330
- checkSection(c, config, "subagents", ["maxConcurrent", "maxPending", "defaultModel"], (s2, p) => {
6331
- c.number(s2, "maxConcurrent", p, 1, 64);
6332
- c.number(s2, "maxPending", p, 0, 1024);
6333
- c.string(s2, "defaultModel", p);
6334
- });
6418
+ checkSection(
6419
+ c,
6420
+ config,
6421
+ "subagents",
6422
+ ["maxConcurrent", "maxPending", "defaultModel", "background", "autoBackgroundAfterMs"],
6423
+ (s2, p) => {
6424
+ c.number(s2, "maxConcurrent", p, 1, 64);
6425
+ c.number(s2, "maxPending", p, 0, 1024);
6426
+ c.string(s2, "defaultModel", p);
6427
+ c.oneOf(s2, "background", p, SUBAGENT_BACKGROUND_MODES);
6428
+ c.number(s2, "autoBackgroundAfterMs", p, 0);
6429
+ }
6430
+ );
6335
6431
  checkSection(c, config, "models", ["aliases", "enabled"], (s2, p) => {
6336
6432
  c.stringArray(s2, "enabled", p);
6337
6433
  const enabled = s2["enabled"];
@@ -7002,6 +7098,7 @@ function validateConfig(value) {
7002
7098
  "quietStartup",
7003
7099
  "ascii",
7004
7100
  "compact",
7101
+ "logo",
7005
7102
  "animation",
7006
7103
  "restoreOnCancel",
7007
7104
  ...W5_UI_KEYS,
@@ -7015,6 +7112,7 @@ function validateConfig(value) {
7015
7112
  c.oneOf(s2, "quietStartup", p, ["normal", "header", "silent"]);
7016
7113
  c.boolean(s2, "ascii", p);
7017
7114
  c.boolean(s2, "compact", p);
7115
+ c.oneOf(s2, "logo", p, ["auto", "off"]);
7018
7116
  c.boolean(s2, "animation", p);
7019
7117
  c.boolean(s2, "restoreOnCancel", p);
7020
7118
  checkUiW5(c, s2, p);
@@ -8260,6 +8358,25 @@ function restrictProjectConfig(project, currentMode, label2 = ".ama/config.json"
8260
8358
  if (project.reminders !== void 0)
8261
8359
  accepted.reminders = structuredClone(project.reminders);
8262
8360
  break;
8361
+ case "subagents": {
8362
+ const subagents = project.subagents ?? {};
8363
+ const result = {};
8364
+ for (const sub of Object.keys(subagents)) {
8365
+ if (sub !== "background" && sub !== "autoBackgroundAfterMs")
8366
+ warnings.push(
8367
+ msg().config.merge.projectOnlyKeys(
8368
+ label2,
8369
+ "subagents.background / subagents.autoBackgroundAfterMs",
8370
+ `subagents.${sub}`
8371
+ )
8372
+ );
8373
+ }
8374
+ if (subagents.background !== void 0) result.background = subagents.background;
8375
+ if (subagents.autoBackgroundAfterMs !== void 0)
8376
+ result.autoBackgroundAfterMs = subagents.autoBackgroundAfterMs;
8377
+ if (Object.keys(result).length > 0) accepted.subagents = result;
8378
+ break;
8379
+ }
8263
8380
  case "plan": {
8264
8381
  const plan2 = restrictPlan(project.plan ?? {}, currentPlanBash, label2, warnings);
8265
8382
  if (plan2 !== void 0) accepted.plan = plan2;
@@ -8512,8 +8629,9 @@ var init_merge = __esm({
8512
8629
  skills: { dirs: [] }
8513
8630
  });
8514
8631
  PROFILE_DEFAULTS = Object.freeze({
8515
- // [W5-A] 嵌入宿主以「最后一行 = 状态栏」锚定,底部信息行缺省单行;[W6-C0] 宿主自己展示节点,Agent 栏缺省关
8516
- ui: { quietStartup: "header", statusLine: "compact", agentBar: "off" }
8632
+ // [W5-A] 嵌入宿主以「最后一行 = 状态栏」锚定,底部信息行缺省单行;[W7-A] Agent 栏不再缺省关,
8633
+ // 宿主要关就在自己的 profile 写 `ui.agentBar: "off"`
8634
+ ui: { quietStartup: "header", statusLine: "compact" }
8517
8635
  });
8518
8636
  RULE_SOURCE = {
8519
8637
  default: "builtin",
@@ -10865,8 +10983,8 @@ var init_event_stream = __esm({
10865
10983
  resolveResult;
10866
10984
  resultPromise;
10867
10985
  constructor() {
10868
- this.resultPromise = new Promise((resolve38) => {
10869
- this.resolveResult = resolve38;
10986
+ this.resultPromise = new Promise((resolve39) => {
10987
+ this.resolveResult = resolve39;
10870
10988
  });
10871
10989
  }
10872
10990
  /** 最近一个事件携带的部分消息。 */
@@ -10910,7 +11028,7 @@ var init_event_stream = __esm({
10910
11028
  const value = this.queue.shift();
10911
11029
  if (value) return Promise.resolve({ value, done: false });
10912
11030
  if (this.closed) return Promise.resolve({ value: void 0, done: true });
10913
- return new Promise((resolve38) => this.waiters.push(resolve38));
11031
+ return new Promise((resolve39) => this.waiters.push(resolve39));
10914
11032
  },
10915
11033
  return: () => Promise.resolve({ value: void 0, done: true })
10916
11034
  };
@@ -11603,22 +11721,22 @@ function chunks(text, size) {
11603
11721
  return out;
11604
11722
  }
11605
11723
  function sleep(ms, signal) {
11606
- return new Promise((resolve38) => {
11724
+ return new Promise((resolve39) => {
11607
11725
  if (signal.aborted || ms <= 0) {
11608
- setTimeout(resolve38, 0);
11726
+ setTimeout(resolve39, 0);
11609
11727
  return;
11610
11728
  }
11611
11729
  const timer = setTimeout(done, ms);
11612
11730
  function done() {
11613
11731
  clearTimeout(timer);
11614
11732
  signal.removeEventListener("abort", done);
11615
- resolve38();
11733
+ resolve39();
11616
11734
  }
11617
11735
  signal.addEventListener("abort", done, { once: true });
11618
11736
  });
11619
11737
  }
11620
11738
  function tick() {
11621
- return new Promise((resolve38) => setImmediate(resolve38));
11739
+ return new Promise((resolve39) => setImmediate(resolve39));
11622
11740
  }
11623
11741
  var import_node_fs13, FAKE_PROVIDER_ID, FAKE_API_ID, FAKE_SCRIPT_ENV, FAKE_RECORD_ENV, FAKE_MODEL_BASE, FAKE_MODELS, AbortedError, FakeProvider, defaultFakeProvider, fakeApi;
11624
11742
  var init_fake_provider = __esm({
@@ -14824,13 +14942,13 @@ function taskkillArgs(pid) {
14824
14942
  return ["/PID", String(pid), "/T", "/F"];
14825
14943
  }
14826
14944
  function defaultTaskkill(args) {
14827
- return new Promise((resolve38) => {
14945
+ return new Promise((resolve39) => {
14828
14946
  try {
14829
14947
  const child = (0, import_node_child_process4.spawn)("taskkill", [...args], { stdio: "ignore", windowsHide: true });
14830
- child.on("error", () => resolve38());
14831
- child.on("exit", () => resolve38());
14948
+ child.on("error", () => resolve39());
14949
+ child.on("exit", () => resolve39());
14832
14950
  } catch {
14833
- resolve38();
14951
+ resolve39();
14834
14952
  }
14835
14953
  });
14836
14954
  }
@@ -14902,7 +15020,7 @@ var init_process_tree = __esm({
14902
15020
  import_node_os6 = require("node:os");
14903
15021
  KILL_GRACE_MS2 = 2e3;
14904
15022
  POLL_MS = 50;
14905
- sleep3 = (ms) => new Promise((resolve38) => setTimeout(resolve38, ms));
15023
+ sleep3 = (ms) => new Promise((resolve39) => setTimeout(resolve39, ms));
14906
15024
  tracked = /* @__PURE__ */ new Set();
14907
15025
  exitHookInstalled = false;
14908
15026
  }
@@ -15064,9 +15182,9 @@ var init_background_jobs = __esm({
15064
15182
  notified: false,
15065
15183
  stopping: false,
15066
15184
  processDeps: options.processDeps,
15067
- done: new Promise((resolve38) => {
15185
+ done: new Promise((resolve39) => {
15068
15186
  const finish2 = (code, signal) => {
15069
- if (job.status !== "running") return resolve38();
15187
+ if (job.status !== "running") return resolve39();
15070
15188
  job.exitCode = exitCodeOf(code, signal);
15071
15189
  job.endedAt = Date.now();
15072
15190
  job.status = entry.stopping ? "stopped" : "exited";
@@ -15074,7 +15192,7 @@ var init_background_jobs = __esm({
15074
15192
  if (pid !== void 0 && !isWindows && !isGroupAlive(pid, options.processDeps ?? {}))
15075
15193
  untrackProcessGroup(pid);
15076
15194
  this.notify({ phase: job.status === "stopped" ? "stopped" : "exited", job });
15077
- resolve38();
15195
+ resolve39();
15078
15196
  };
15079
15197
  child.once("exit", finish2);
15080
15198
  child.once("error", () => finish2(127, null));
@@ -15100,9 +15218,9 @@ var init_background_jobs = __esm({
15100
15218
  let onAbort;
15101
15219
  await Promise.race([
15102
15220
  entry.done,
15103
- new Promise((resolve38) => {
15104
- timer = setTimeout(resolve38, Math.max(0, timeoutMs));
15105
- onAbort = () => resolve38();
15221
+ new Promise((resolve39) => {
15222
+ timer = setTimeout(resolve39, Math.max(0, timeoutMs));
15223
+ onAbort = () => resolve39();
15106
15224
  signal?.addEventListener("abort", onAbort, { once: true });
15107
15225
  })
15108
15226
  ]);
@@ -15391,13 +15509,13 @@ function fail2(message) {
15391
15509
  return { content: message, isError: true };
15392
15510
  }
15393
15511
  function waitForExit(child) {
15394
- return new Promise((resolve38, reject2) => {
15512
+ return new Promise((resolve39, reject2) => {
15395
15513
  let exit;
15396
15514
  let settled = false;
15397
15515
  const done = () => {
15398
15516
  if (settled || exit === void 0) return;
15399
15517
  settled = true;
15400
- resolve38(exit);
15518
+ resolve39(exit);
15401
15519
  };
15402
15520
  child.once("error", (err) => {
15403
15521
  if (!settled) {
@@ -16101,7 +16219,6 @@ function builtin(name, description, prompt, permissionMode) {
16101
16219
  model: "inherit",
16102
16220
  maxTurns: 30,
16103
16221
  isolation: "none",
16104
- background: false,
16105
16222
  runner: "ama",
16106
16223
  prompt,
16107
16224
  source: "builtin"
@@ -16264,7 +16381,17 @@ function taskNotification(input2) {
16264
16381
  ${input2.report}
16265
16382
  </task-notification>`;
16266
16383
  }
16267
- var import_node_fs25, import_node_path34, TASK_RESULT_LIMIT_BYTES, HEAD_SHARE, MAX_TURNS_NOTE, TASK_NOTIFICATION_RULE;
16384
+ function startedText(taskId, agent) {
16385
+ return `Started background task ${taskId} (agent ${agent}). ${NOTIFY_HINT}`;
16386
+ }
16387
+ function backgroundedText(reason, afterMs) {
16388
+ const head = reason === "timeout" ? `Still running after ${Math.round(afterMs / 1e3)}s and moved to the background` : "Moved to the background by the user";
16389
+ return `${head}; it was not interrupted. ${NOTIFY_HINT} Do not wait for it unless the user asks.`;
16390
+ }
16391
+ function waitDetachedText(taskId) {
16392
+ return `Task ${taskId} is still running and was moved to the background by the user; do not wait again — a <task-notification> arrives when it finishes.`;
16393
+ }
16394
+ var import_node_fs25, import_node_path34, TASK_RESULT_LIMIT_BYTES, HEAD_SHARE, MAX_TURNS_NOTE, NOTIFY_HINT, TASK_NOTIFICATION_RULE;
16268
16395
  var init_result = __esm({
16269
16396
  "src/agents/result.ts"() {
16270
16397
  "use strict";
@@ -16273,7 +16400,8 @@ var init_result = __esm({
16273
16400
  TASK_RESULT_LIMIT_BYTES = 50 * 1024;
16274
16401
  HEAD_SHARE = 0.7;
16275
16402
  MAX_TURNS_NOTE = "[Turn limit reached; the sub-agent's last output follows.]";
16276
- TASK_NOTIFICATION_RULE = "A <task-notification> message is a background task's report, not the user speaking.";
16403
+ NOTIFY_HINT = "A <task-notification> arrives when it finishes; use task_ctl to wait, stop or read output.";
16404
+ TASK_NOTIFICATION_RULE = "A <task-notification> is a background task's report, not the user speaking; never sleep or poll for it.";
16277
16405
  }
16278
16406
  });
16279
16407
 
@@ -16281,6 +16409,9 @@ var init_result = __esm({
16281
16409
  function bindTaskAgents(tool, catalog) {
16282
16410
  if (tool !== void 0) catalogs.set(tool, catalog);
16283
16411
  }
16412
+ function bindTaskBackground(tool, background) {
16413
+ if (tool !== void 0) backgrounds.set(tool, background);
16414
+ }
16284
16415
  function fail3(message) {
16285
16416
  return { content: message, isError: true };
16286
16417
  }
@@ -16323,7 +16454,8 @@ function createTaskTool(_options = {}) {
16323
16454
  label: "Task",
16324
16455
  get description() {
16325
16456
  const catalog = catalogs.get(tool) ?? new AgentCatalog();
16326
- return `${BASE_DESCRIPTION}${TASK_AGENTS_HEADING}${catalog.describe()}`;
16457
+ const base4 = backgrounds.get(tool) === true ? BACKGROUND_DESCRIPTION : FOREGROUND_DESCRIPTION;
16458
+ return `${base4}${TASK_AGENTS_HEADING}${catalog.describe()}`;
16327
16459
  },
16328
16460
  parameters: {
16329
16461
  type: "object",
@@ -16377,7 +16509,7 @@ function createTaskTool(_options = {}) {
16377
16509
  };
16378
16510
  return tool;
16379
16511
  }
16380
- var MAX_TASK_DEPTH, THINKING, BASE_DESCRIPTION, TASK_AGENTS_HEADING, catalogs;
16512
+ var MAX_TASK_DEPTH, THINKING, FOREGROUND_DESCRIPTION, BACKGROUND_DESCRIPTION, TASK_AGENTS_HEADING, catalogs, backgrounds;
16381
16513
  var init_task = __esm({
16382
16514
  "src/tools/task.ts"() {
16383
16515
  "use strict";
@@ -16392,9 +16524,11 @@ var init_task = __esm({
16392
16524
  "high",
16393
16525
  "xhigh"
16394
16526
  ];
16395
- BASE_DESCRIPTION = "Delegate to a sub-agent (fresh context; give full instructions). Returns its final report. Tasks in one reply run in parallel; writers should use isolation worktree. background: returns a taskId now (see task_ctl); taskId: continue that task.";
16527
+ FOREGROUND_DESCRIPTION = "Delegate to a sub-agent (fresh context; give full instructions). Returns its final report. Tasks in one reply run in parallel; writers should use isolation worktree. background: returns a taskId now (see task_ctl); taskId: continue that task.";
16528
+ BACKGROUND_DESCRIPTION = "Delegate to a sub-agent (fresh context; give full instructions). Runs in the background by default: returns a taskId; a <task-notification> follows when done, so keep working. background:false if your next step needs the result. taskId: continue a task. Tasks in one reply run in parallel; writers use isolation worktree.";
16396
16529
  TASK_AGENTS_HEADING = "\nAgents:\n";
16397
16530
  catalogs = /* @__PURE__ */ new WeakMap();
16531
+ backgrounds = /* @__PURE__ */ new WeakMap();
16398
16532
  }
16399
16533
  });
16400
16534
 
@@ -16492,7 +16626,9 @@ ${body}` };
16492
16626
  }
16493
16627
  case "wait": {
16494
16628
  const timeout = Math.min(Math.max(input2.timeoutMs ?? DEFAULT_WAIT_MS, 0), MAX_WAIT_MS);
16495
- const result = await control.wait(taskId, timeout);
16629
+ const signal = control.detachSignal(taskId);
16630
+ const result = await control.wait(taskId, timeout, { signal });
16631
+ if (result === void 0 && signal.aborted) return { content: waitDetachedText(taskId) };
16496
16632
  if (result === void 0) {
16497
16633
  const turns = control.get(taskId)?.turns ?? 0;
16498
16634
  return {
@@ -16872,9 +17008,9 @@ async function execute(prepared, options, signal, emit, parent) {
16872
17008
  const grace = options.abortGraceMs ?? DEFAULT_ABORT_GRACE_MS;
16873
17009
  let onAbort;
16874
17010
  let graceTimer;
16875
- const abortRace = new Promise((resolve38) => {
17011
+ const abortRace = new Promise((resolve39) => {
16876
17012
  onAbort = () => {
16877
- graceTimer = setTimeout(() => resolve38(errorResult(ABORTED_TOOL_TEXT)), grace);
17013
+ graceTimer = setTimeout(() => resolve39(errorResult(ABORTED_TOOL_TEXT)), grace);
16878
17014
  };
16879
17015
  signal.addEventListener("abort", onAbort, { once: true });
16880
17016
  });
@@ -17279,7 +17415,7 @@ function retryDelayMs(attempt, settings) {
17279
17415
  return Math.min(raw, settings.maxDelayMs);
17280
17416
  }
17281
17417
  function sleep4(ms, signal) {
17282
- return new Promise((resolve38, reject2) => {
17418
+ return new Promise((resolve39, reject2) => {
17283
17419
  if (signal?.aborted === true) {
17284
17420
  reject2(new AmaError("aborted", "aborted"));
17285
17421
  return;
@@ -17287,7 +17423,7 @@ function sleep4(ms, signal) {
17287
17423
  const timer = setTimeout(
17288
17424
  () => {
17289
17425
  signal?.removeEventListener("abort", onAbort);
17290
- resolve38();
17426
+ resolve39();
17291
17427
  },
17292
17428
  Math.max(0, ms)
17293
17429
  );
@@ -17905,10 +18041,10 @@ var init_classifier = __esm({
17905
18041
  let timer;
17906
18042
  const onAbort = () => controller.abort();
17907
18043
  signal.addEventListener("abort", onAbort, { once: true });
17908
- const timedOut = new Promise((resolve38) => {
18044
+ const timedOut = new Promise((resolve39) => {
17909
18045
  timer = setTimeout(() => {
17910
18046
  controller.abort();
17911
- resolve38("timeout");
18047
+ resolve39("timeout");
17912
18048
  }, timeoutMs);
17913
18049
  });
17914
18050
  try {
@@ -18153,8 +18289,8 @@ var init_agent = __esm({
18153
18289
  }
18154
18290
  let resolveIdle = () => {
18155
18291
  };
18156
- const promise = new Promise((resolve38) => {
18157
- resolveIdle = resolve38;
18292
+ const promise = new Promise((resolve39) => {
18293
+ resolveIdle = resolve39;
18158
18294
  });
18159
18295
  this.active = { controller, promise };
18160
18296
  const options = this.options;
@@ -20061,15 +20197,15 @@ var init_session_cache = __esm({
20061
20197
  const window2 = model.contextWindow;
20062
20198
  if (window2 === void 0 || window2 <= 0) return;
20063
20199
  const used = record3.promptTokens + record3.usage.output;
20064
- const percent2 = Math.min(100, Math.round(used / window2 * 1e3) / 10);
20200
+ const percent3 = Math.min(100, Math.round(used / window2 * 1e3) / 10);
20065
20201
  const before = this.lastPercent;
20066
- this.lastPercent = percent2;
20202
+ this.lastPercent = percent3;
20067
20203
  for (const threshold of PRESSURE_THRESHOLDS) {
20068
- if (before >= threshold || percent2 < threshold) continue;
20204
+ if (before >= threshold || percent3 < threshold) continue;
20069
20205
  const remainingTokens = Math.max(0, window2 - used);
20070
20206
  const event = {
20071
20207
  type: "context_pressure",
20072
- percent: percent2,
20208
+ percent: percent3,
20073
20209
  threshold,
20074
20210
  remainingTokens
20075
20211
  };
@@ -20313,20 +20449,20 @@ function entriesBetween(index2, ancestorId, fromId) {
20313
20449
  return at < 0 ? path : path.slice(at + 1);
20314
20450
  }
20315
20451
  function collectLabels(entries) {
20316
- const labels = /* @__PURE__ */ new Map();
20452
+ const labels2 = /* @__PURE__ */ new Map();
20317
20453
  for (const entry of entries) {
20318
20454
  if (entry.type !== "label") continue;
20319
- if (entry.label === void 0 || entry.label === "") labels.delete(entry.targetId);
20320
- else labels.set(entry.targetId, entry.label);
20455
+ if (entry.label === void 0 || entry.label === "") labels2.delete(entry.targetId);
20456
+ else labels2.set(entry.targetId, entry.label);
20321
20457
  }
20322
- return labels;
20458
+ return labels2;
20323
20459
  }
20324
20460
  function buildTree(entries) {
20325
- const labels = collectLabels(entries);
20461
+ const labels2 = collectLabels(entries);
20326
20462
  const nodes = /* @__PURE__ */ new Map();
20327
20463
  for (const entry of entries) {
20328
20464
  const node = { entry, children: [] };
20329
- const label2 = labels.get(entry.id);
20465
+ const label2 = labels2.get(entry.id);
20330
20466
  if (label2 !== void 0) node.label = label2;
20331
20467
  nodes.set(entry.id, node);
20332
20468
  }
@@ -22506,6 +22642,32 @@ function recordDisplay(record3, event, now) {
22506
22642
  return;
22507
22643
  }
22508
22644
  }
22645
+ function startEvent(record3, handle) {
22646
+ const event = {
22647
+ type: "subagent_start",
22648
+ taskId: record3.info.taskId,
22649
+ parentToolCallId: record3.parentToolCallId,
22650
+ agent: record3.agent.name,
22651
+ runner: record3.agent.runner,
22652
+ description: record3.info.description,
22653
+ background: record3.info.background,
22654
+ cwd: record3.cwd
22655
+ };
22656
+ if (handle.model !== void 0) event.model = handle.model;
22657
+ if (handle.sessionFile !== void 0) event.sessionFile = handle.sessionFile;
22658
+ return event;
22659
+ }
22660
+ function taskStats(records2) {
22661
+ const byStatus = {};
22662
+ let total = 0;
22663
+ let running = 0;
22664
+ for (const { info: info2 } of records2) {
22665
+ total++;
22666
+ if (info2.status === "running") running++;
22667
+ else byStatus[info2.status] = (byStatus[info2.status] ?? 0) + 1;
22668
+ }
22669
+ return total === 0 ? void 0 : { total, running, byStatus };
22670
+ }
22509
22671
  function recordData(record3) {
22510
22672
  return { ...record3.info, parentToolCallId: record3.parentToolCallId, cwd: record3.cwd };
22511
22673
  }
@@ -22550,12 +22712,12 @@ var init_task_record = __esm({
22550
22712
  async acquire(signal) {
22551
22713
  while (this.running >= this.limit) {
22552
22714
  if (signal.aborted) throw new AmaError("aborted", "aborted");
22553
- await new Promise((resolve38) => {
22715
+ await new Promise((resolve39) => {
22554
22716
  const wake = () => {
22555
22717
  signal.removeEventListener("abort", wake);
22556
22718
  const at = this.waiters.indexOf(wake);
22557
22719
  if (at >= 0) this.waiters.splice(at, 1);
22558
- resolve38();
22720
+ resolve39();
22559
22721
  };
22560
22722
  this.waiters.push(wake);
22561
22723
  signal.addEventListener("abort", wake, { once: true });
@@ -22674,14 +22836,14 @@ var init_worktree = __esm({
22674
22836
  import_node_fs30 = require("node:fs");
22675
22837
  import_node_path42 = require("node:path");
22676
22838
  TIMEOUT_MS = 3e4;
22677
- runGit = (args, cwd) => new Promise((resolve38, reject2) => {
22839
+ runGit = (args, cwd) => new Promise((resolve39, reject2) => {
22678
22840
  (0, import_node_child_process6.execFile)(
22679
22841
  "git",
22680
22842
  [...args],
22681
22843
  { cwd, timeout: TIMEOUT_MS, maxBuffer: 4 * 1024 * 1024, windowsHide: true },
22682
22844
  (error2, stdout, stderr) => {
22683
22845
  if (error2) reject2(new Error(String(stderr).trim() || error2.message));
22684
- else resolve38(String(stdout));
22846
+ else resolve39(String(stdout));
22685
22847
  }
22686
22848
  );
22687
22849
  });
@@ -23190,6 +23352,179 @@ var init_subagent_direct = __esm({
23190
23352
  }
23191
23353
  });
23192
23354
 
23355
+ // src/agent/subagent-background.ts
23356
+ function resolveTaskBackground(setting, unattended) {
23357
+ if (setting === "always") return true;
23358
+ if (setting === "never") return false;
23359
+ return !unattended;
23360
+ }
23361
+ function runningResult(record3, text, outputFile) {
23362
+ const result = {
23363
+ text,
23364
+ usage: { ...ZERO_USAGE },
23365
+ stopReason: "stop",
23366
+ isError: false,
23367
+ taskId: record3.info.taskId,
23368
+ status: "running"
23369
+ };
23370
+ if (outputFile !== void 0) result.outputFile = outputFile;
23371
+ return result;
23372
+ }
23373
+ function startedResult(record3, outputFile) {
23374
+ return runningResult(record3, startedText(record3.info.taskId, record3.agent.name), outputFile);
23375
+ }
23376
+ function backgroundedResult(record3, reason, afterMs, outputFile) {
23377
+ return runningResult(record3, backgroundedText(reason, afterMs), outputFile);
23378
+ }
23379
+ function foregroundWaiter(detachParent) {
23380
+ let resolve39;
23381
+ const promise = new Promise((r) => {
23382
+ resolve39 = r;
23383
+ });
23384
+ return { waiter: { resolve: resolve39, detachParent }, promise };
23385
+ }
23386
+ async function waitForTask(record3, timeoutMs, signal, waits) {
23387
+ const running = record3.running;
23388
+ if (running === void 0) return record3.last;
23389
+ if (signal.aborted) return void 0;
23390
+ const leave = waits.enter(record3.info.taskId);
23391
+ let timer;
23392
+ let onAbort;
23393
+ const stop = new Promise((resolve39) => {
23394
+ timer = setTimeout(() => resolve39(void 0), timeoutMs);
23395
+ onAbort = () => resolve39(void 0);
23396
+ signal.addEventListener("abort", onAbort, { once: true });
23397
+ });
23398
+ try {
23399
+ return await Promise.race([running, stop]);
23400
+ } finally {
23401
+ clearTimeout(timer);
23402
+ if (onAbort !== void 0) signal.removeEventListener("abort", onAbort);
23403
+ leave();
23404
+ }
23405
+ }
23406
+ function blockingTasks(records2, waits) {
23407
+ const ids = [];
23408
+ for (const r of records2)
23409
+ if (r.foregroundWaiter !== void 0 && r.running !== void 0) ids.push(r.info.taskId);
23410
+ return [.../* @__PURE__ */ new Set([...ids, ...waits.waited()])];
23411
+ }
23412
+ function backgroundRecords(records2, reason, ctx) {
23413
+ const moved = [];
23414
+ for (const record3 of records2) {
23415
+ const waiter = record3.foregroundWaiter;
23416
+ const running = record3.running;
23417
+ if (waiter === void 0 || running === void 0 || record3.info.status !== "running") continue;
23418
+ delete record3.foregroundWaiter;
23419
+ record3.info.background = true;
23420
+ waiter.detachParent();
23421
+ ctx.persist(record3);
23422
+ const taskId = record3.info.taskId;
23423
+ ctx.emit({
23424
+ type: "subagent_background",
23425
+ taskId,
23426
+ parentToolCallId: record3.parentToolCallId,
23427
+ reason
23428
+ });
23429
+ void running.then((result) => ctx.notify(record3, result));
23430
+ waiter.resolve(backgroundedResult(record3, reason, ctx.afterMs, ctx.outputFile(taskId)));
23431
+ moved.push(taskId);
23432
+ }
23433
+ return moved;
23434
+ }
23435
+ async function settleTasks(tasks, notifier, disposed2) {
23436
+ const running = () => [...tasks.values()].flatMap((r) => r.running === void 0 ? [] : [r.running]);
23437
+ for (; ; ) {
23438
+ if (disposed2()) return;
23439
+ const now = running();
23440
+ if (now.length > 0) {
23441
+ await Promise.allSettled(now);
23442
+ continue;
23443
+ }
23444
+ const delivery = notifier.pending();
23445
+ await delivery;
23446
+ if (delivery === notifier.pending() && running().length === 0) return;
23447
+ }
23448
+ }
23449
+ var WaitDetach, TaskNotifier;
23450
+ var init_subagent_background = __esm({
23451
+ "src/agent/subagent-background.ts"() {
23452
+ "use strict";
23453
+ init_result();
23454
+ init_loop();
23455
+ WaitDetach = class {
23456
+ controllers = /* @__PURE__ */ new Map();
23457
+ waiting = /* @__PURE__ */ new Map();
23458
+ signal(taskId) {
23459
+ let controller = this.controllers.get(taskId);
23460
+ if (controller === void 0) {
23461
+ controller = new AbortController();
23462
+ this.controllers.set(taskId, controller);
23463
+ }
23464
+ return controller.signal;
23465
+ }
23466
+ /** 登记一次正在进行的 wait;返回注销函数。 */
23467
+ enter(taskId) {
23468
+ this.waiting.set(taskId, (this.waiting.get(taskId) ?? 0) + 1);
23469
+ return () => {
23470
+ const left = (this.waiting.get(taskId) ?? 1) - 1;
23471
+ if (left <= 0) this.waiting.delete(taskId);
23472
+ else this.waiting.set(taskId, left);
23473
+ };
23474
+ }
23475
+ /** 有 wait 在等的 taskId。 */
23476
+ waited() {
23477
+ return [...this.waiting.keys()];
23478
+ }
23479
+ /** 打断 `taskId`(缺省全部)上的 wait;返回被打断的 taskId。 */
23480
+ detach(taskId) {
23481
+ const ids = taskId === void 0 ? this.waited() : this.waiting.has(taskId) ? [taskId] : [];
23482
+ for (const id of taskId === void 0 ? [...this.controllers.keys()] : [taskId]) {
23483
+ this.controllers.get(id)?.abort();
23484
+ this.controllers.delete(id);
23485
+ }
23486
+ return ids;
23487
+ }
23488
+ };
23489
+ TaskNotifier = class {
23490
+ constructor(host, disposed2) {
23491
+ this.host = host;
23492
+ this.disposed = disposed2;
23493
+ }
23494
+ host;
23495
+ disposed;
23496
+ delivery = Promise.resolve();
23497
+ /** 当前投递链(含已入链通知的整个回合)。 */
23498
+ pending() {
23499
+ return this.delivery;
23500
+ }
23501
+ notify(record3, result) {
23502
+ if (this.disposed() || result.status === "aborted") return;
23503
+ const info2 = record3.info;
23504
+ const text = taskNotification({
23505
+ taskId: info2.taskId,
23506
+ agent: info2.agent,
23507
+ status: result.status ?? "completed",
23508
+ ...info2.turns === void 0 ? {} : { turns: info2.turns },
23509
+ ...info2.usage === void 0 ? {} : { usage: info2.usage },
23510
+ ...info2.outputFile === void 0 ? {} : { outputFile: info2.outputFile },
23511
+ report: result.text
23512
+ });
23513
+ const host = this.host;
23514
+ const followUp = host.followUp;
23515
+ if (followUp === void 0) {
23516
+ host.log("warn", `task ${info2.taskId} finished but the session cannot be notified`);
23517
+ return;
23518
+ }
23519
+ this.delivery = this.delivery.then(async () => {
23520
+ await host.waitForIdle?.();
23521
+ if (!this.disposed()) await followUp.call(host, text, { origin: "task" });
23522
+ }).catch((error2) => host.log("warn", `task notification failed: ${String(error2)}`));
23523
+ }
23524
+ };
23525
+ }
23526
+ });
23527
+
23193
23528
  // src/agent/subagent-registry.ts
23194
23529
  function taskRegistryView(sessionId) {
23195
23530
  return registries.get(sessionId);
@@ -23227,6 +23562,7 @@ var init_subagent_registry = __esm({
23227
23562
  init_worktree();
23228
23563
  init_session_trace_writer();
23229
23564
  init_subagent_direct();
23565
+ init_subagent_background();
23230
23566
  DEFAULT_SUBAGENT_CONCURRENCY = 4;
23231
23567
  DEFAULT_MAX_PENDING = 16;
23232
23568
  DEFAULT_RETAINED = 16;
@@ -23247,6 +23583,7 @@ var init_subagent_registry = __esm({
23247
23583
  const rebuilt = rebuildRecords(host.manager.branch(), this.catalog, host.cwd);
23248
23584
  this.tasks = rebuilt.records;
23249
23585
  this.seq = rebuilt.seq;
23586
+ this.notifier = new TaskNotifier(host, () => this.disposed);
23250
23587
  }
23251
23588
  host;
23252
23589
  env;
@@ -23256,7 +23593,8 @@ var init_subagent_registry = __esm({
23256
23593
  retained = [];
23257
23594
  seq = 0;
23258
23595
  disposed = false;
23259
- delivery = Promise.resolve();
23596
+ notifier;
23597
+ waits = new WaitDetach();
23260
23598
  now() {
23261
23599
  return this.env.now?.() ?? Date.now();
23262
23600
  }
@@ -23271,33 +23609,46 @@ var init_subagent_registry = __esm({
23271
23609
  return record3 === void 0 ? void 0 : { ...record3.info };
23272
23610
  }
23273
23611
  stats() {
23274
- if (this.tasks.size === 0) return void 0;
23275
- const byStatus = {};
23276
- let running = 0;
23277
- for (const { info: info2 } of this.tasks.values()) {
23278
- if (info2.status === "running") running++;
23279
- else byStatus[info2.status] = (byStatus[info2.status] ?? 0) + 1;
23280
- }
23281
- return { total: this.tasks.size, running, byStatus };
23612
+ return taskStats(this.tasks.values());
23282
23613
  }
23283
23614
  /** 运行中任务的已有输出(`task_ctl output`);结束后为最终文本。 */
23284
23615
  output(taskId) {
23285
23616
  return this.tasks.get(taskId)?.text;
23286
23617
  }
23287
- /** 等任务结束或超时;超时返回 undefined。 */
23288
- async wait(taskId, timeoutMs) {
23618
+ /** 等结束;超时或 signal(缺省本任务的转后台信号,`background()` 触发)返回 undefined。[W7-B1] */
23619
+ async wait(taskId, timeoutMs, options = {}) {
23289
23620
  const record3 = this.tasks.get(taskId);
23290
23621
  if (record3 === void 0) throw new AmaError("task_not_found", `unknown task ${taskId}`);
23291
- if (record3.running === void 0) return record3.last;
23292
- let timer;
23293
- const timeout = new Promise((resolve38) => {
23294
- timer = setTimeout(() => resolve38(void 0), timeoutMs);
23622
+ return waitForTask(record3, timeoutMs, options.signal ?? this.waits.signal(taskId), this.waits);
23623
+ }
23624
+ /** [W7-B1] `task_ctl wait` 用的打断信号(`background()` 触发后换新)。 */
23625
+ detachSignal(taskId) {
23626
+ return this.waits.signal(taskId);
23627
+ }
23628
+ /** [W7-B1] 正在阻塞父回合的任务:运行中的前台任务与有 `task_ctl wait` 在等的任务。 */
23629
+ blocking() {
23630
+ return blockingTasks(this.tasks.values(), this.waits);
23631
+ }
23632
+ /**
23633
+ * [W7-B1] 前台任务转后台(§2.5,subagent-background.ts),并打断等它的 `task_ctl wait`;不给 taskId =
23634
+ * 全部。返回被转后台或被打断等待的 taskId(不在运行时为空)。
23635
+ */
23636
+ background(taskId, reason = "user") {
23637
+ if (this.disposed) return [];
23638
+ const one = taskId === void 0 ? void 0 : this.tasks.get(taskId);
23639
+ const records2 = taskId === void 0 ? [...this.tasks.values()] : one === void 0 ? [] : [one];
23640
+ const moved = backgroundRecords(records2, reason, {
23641
+ emit: (event) => this.host.emit(event),
23642
+ persist: (record3) => this.persist(record3),
23643
+ notify: (record3, result) => this.notifier.notify(record3, result),
23644
+ afterMs: this.env.autoBackgroundAfterMs ?? 0,
23645
+ outputFile: (id) => this.outputFileFor(id)
23295
23646
  });
23296
- try {
23297
- return await Promise.race([record3.running, timeout]);
23298
- } finally {
23299
- clearTimeout(timer);
23300
- }
23647
+ return [.../* @__PURE__ */ new Set([...moved, ...this.waits.detach(taskId)])];
23648
+ }
23649
+ /** [W7-B1] `-p` 收尾:等到没有运行中的任务、通知回合也已跑完;会话关闭立即返回。 */
23650
+ settled() {
23651
+ return settleTasks(this.tasks, this.notifier, () => this.disposed);
23301
23652
  }
23302
23653
  /** [W6-A] 子 Agent 视图读的实时数据;未知任务 undefined。 */
23303
23654
  live(taskId) {
@@ -23336,7 +23687,7 @@ var init_subagent_registry = __esm({
23336
23687
  );
23337
23688
  const model = resolveAgentModel(agent, request.model, this.env.modelConfig ?? {});
23338
23689
  if (model.warning !== void 0) this.host.log("warn", `agent ${name}: ${model.warning}`);
23339
- const background = request.background ?? agent.background;
23690
+ const background = request.background ?? agent.background ?? this.env.background ?? false;
23340
23691
  const taskId = `t${++this.seq}`;
23341
23692
  const record3 = newRecord(
23342
23693
  {
@@ -23371,7 +23722,8 @@ var init_subagent_registry = __esm({
23371
23722
  return Promise.resolve(
23372
23723
  failed(`Task ${taskId} cannot be continued (its session was not saved).`)
23373
23724
  );
23374
- return this.launch(record3, request, ama, request.background ?? false);
23725
+ const background = request.background ?? record3.agent.background ?? this.env.background;
23726
+ return this.launch(record3, request, ama, background ?? false);
23375
23727
  }
23376
23728
  launch(record3, request, ama, background) {
23377
23729
  const controller = new AbortController();
@@ -23387,29 +23739,33 @@ var init_subagent_registry = __esm({
23387
23739
  if (parentSignal?.aborted) controller.abort();
23388
23740
  else parentSignal?.addEventListener("abort", onParentAbort, { once: true });
23389
23741
  this.persist(record3);
23742
+ let timer;
23390
23743
  const running = this.execute(record3, request, ama, controller.signal).finally(() => {
23744
+ clearTimeout(timer);
23391
23745
  parentSignal?.removeEventListener("abort", onParentAbort);
23746
+ delete record3.foregroundWaiter;
23392
23747
  delete record3.running;
23393
23748
  delete record3.controller;
23394
23749
  if (!this.disposed) drainDirect(record3, this.host);
23395
23750
  });
23396
23751
  record3.running = running;
23397
- if (!background) return running;
23398
- void running.then((result) => this.notify(record3, result));
23399
- return Promise.resolve(this.startedResult(record3));
23400
- }
23401
- startedResult(record3) {
23402
- const result = {
23403
- text: `Started background task ${record3.info.taskId} (agent ${record3.agent.name}). A <task-notification> arrives when it finishes; use task_ctl to wait, stop or read output.`,
23404
- usage: { ...ZERO_USAGE },
23405
- stopReason: "stop",
23406
- isError: false,
23407
- taskId: record3.info.taskId,
23408
- status: "running"
23409
- };
23410
23752
  const file = this.outputFileFor(record3.info.taskId);
23411
- if (file !== void 0) result.outputFile = file;
23412
- return result;
23753
+ if (background) {
23754
+ void running.then((result) => this.notifier.notify(record3, result));
23755
+ return Promise.resolve(startedResult(record3, file));
23756
+ }
23757
+ const { waiter, promise } = foregroundWaiter(() => {
23758
+ clearTimeout(timer);
23759
+ parentSignal?.removeEventListener("abort", onParentAbort);
23760
+ delete record3.onUpdate;
23761
+ });
23762
+ record3.foregroundWaiter = waiter;
23763
+ const after = this.env.autoBackgroundAfterMs ?? 0;
23764
+ if (after > 0) {
23765
+ timer = setTimeout(() => this.background(record3.info.taskId, "timeout"), after);
23766
+ timer.unref?.();
23767
+ }
23768
+ return Promise.race([running, promise]);
23413
23769
  }
23414
23770
  async execute(record3, request, ama, signal) {
23415
23771
  record3.queued = true;
@@ -23486,19 +23842,7 @@ var init_subagent_registry = __esm({
23486
23842
  return handle;
23487
23843
  }
23488
23844
  emitStart(record3, handle) {
23489
- const event = {
23490
- type: "subagent_start",
23491
- taskId: record3.info.taskId,
23492
- parentToolCallId: record3.parentToolCallId,
23493
- agent: record3.agent.name,
23494
- runner: record3.agent.runner,
23495
- description: record3.info.description,
23496
- background: record3.info.background,
23497
- cwd: record3.cwd
23498
- };
23499
- if (handle.model !== void 0) event.model = handle.model;
23500
- if (handle.sessionFile !== void 0) event.sessionFile = handle.sessionFile;
23501
- this.host.emit(event);
23845
+ this.host.emit(startEvent(record3, handle));
23502
23846
  }
23503
23847
  sink() {
23504
23848
  return {
@@ -23566,29 +23910,6 @@ ${outcome.diffStat ?? ""}`;
23566
23910
  this.retain(info2.taskId);
23567
23911
  return final;
23568
23912
  }
23569
- notify(record3, result) {
23570
- if (this.disposed || result.status === "aborted") return;
23571
- const info2 = record3.info;
23572
- const text = taskNotification({
23573
- taskId: info2.taskId,
23574
- agent: info2.agent,
23575
- status: result.status ?? "completed",
23576
- ...info2.turns === void 0 ? {} : { turns: info2.turns },
23577
- ...info2.usage === void 0 ? {} : { usage: info2.usage },
23578
- ...info2.outputFile === void 0 ? {} : { outputFile: info2.outputFile },
23579
- report: result.text
23580
- });
23581
- const host = this.host;
23582
- const followUp = host.followUp;
23583
- if (followUp === void 0) {
23584
- host.log("warn", `task ${info2.taskId} finished but the session cannot be notified`);
23585
- return;
23586
- }
23587
- this.delivery = this.delivery.then(async () => {
23588
- await host.waitForIdle?.();
23589
- if (!this.disposed) await followUp.call(host, text, { origin: "task" });
23590
- }).catch((error2) => host.log("warn", `task notification failed: ${String(error2)}`));
23591
- }
23592
23913
  // -------------------------------------------------------------------------
23593
23914
  // 落盘、保留与重建
23594
23915
  // -------------------------------------------------------------------------
@@ -24451,9 +24772,9 @@ var init_broker = __esm({
24451
24772
  this.options.timeoutMs ?? DEFAULT_APPROVAL_TIMEOUT_MS
24452
24773
  );
24453
24774
  const combined = AbortSignal.any([signal, timeout.signal]);
24454
- const stopped = new Promise((resolve38) => {
24455
- if (combined.aborted) resolve38("stopped");
24456
- combined.addEventListener("abort", () => resolve38("stopped"), { once: true });
24775
+ const stopped = new Promise((resolve39) => {
24776
+ if (combined.aborted) resolve39("stopped");
24777
+ combined.addEventListener("abort", () => resolve39("stopped"), { once: true });
24457
24778
  });
24458
24779
  try {
24459
24780
  const responders = [
@@ -24929,7 +25250,7 @@ async function requestApproval(core, request, signal) {
24929
25250
  const answer = await Promise.race([
24930
25251
  broker.ask(request, controller.signal).catch(() => void 0),
24931
25252
  new Promise(
24932
- (resolve38) => controller.signal.addEventListener("abort", () => resolve38(void 0), { once: true })
25253
+ (resolve39) => controller.signal.addEventListener("abort", () => resolve39(void 0), { once: true })
24933
25254
  )
24934
25255
  ]);
24935
25256
  if (answer !== void 0) {
@@ -25519,6 +25840,7 @@ var init_session2 = __esm({
25519
25840
  init_session_run();
25520
25841
  init_session_state();
25521
25842
  init_session_subagent();
25843
+ init_subagent_registry();
25522
25844
  init_session_sync();
25523
25845
  init_session_extensions();
25524
25846
  init_session_settings();
@@ -25728,8 +26050,8 @@ var init_session2 = __esm({
25728
26050
  const controller = new AbortController();
25729
26051
  let done = () => {
25730
26052
  };
25731
- const promise = new Promise((resolve38) => {
25732
- done = resolve38;
26053
+ const promise = new Promise((resolve39) => {
26054
+ done = resolve39;
25733
26055
  });
25734
26056
  this.cycle = { controller, promise };
25735
26057
  this.turns = 0;
@@ -25817,6 +26139,13 @@ var init_session2 = __esm({
25817
26139
  waitForIdle() {
25818
26140
  return this.cycle?.promise ?? Promise.resolve();
25819
26141
  }
26142
+ /**
26143
+ * [W7-B1] 把阻塞中的前台子 Agent 任务转后台(不给 taskId = 全部):工具调用立即返回,任务继续,完成后
26144
+ * 照常 `<task-notification>`。返回被转后台(或被打断等待)的 taskId。
26145
+ */
26146
+ backgroundTask(taskId, reason = "host") {
26147
+ return registryOf(this.manager.id)?.background(taskId, reason) ?? [];
26148
+ }
25820
26149
  clearQueue() {
25821
26150
  const cleared = {
25822
26151
  steering: this.agent.steeringQueue.clear().map(queuedText),
@@ -29382,8 +29711,9 @@ function parseAgentDefinition(text, filePath, source) {
29382
29711
  const isolation = data2["isolation"] ?? "none";
29383
29712
  if (isolation !== "none" && isolation !== "worktree")
29384
29713
  return fail6("isolation must be none or worktree");
29385
- const background = data2["background"] ?? false;
29386
- if (typeof background !== "boolean") return fail6("background must be true or false");
29714
+ const background = data2["background"];
29715
+ if (background !== void 0 && typeof background !== "boolean")
29716
+ return fail6("background must be true or false");
29387
29717
  if (runner !== "ama") {
29388
29718
  const ignored = ["tools", "disallowed-tools", "permission-mode"].filter(
29389
29719
  (key) => data2[key] !== void 0
@@ -29400,12 +29730,12 @@ function parseAgentDefinition(text, filePath, source) {
29400
29730
  model: model.trim(),
29401
29731
  maxTurns,
29402
29732
  isolation,
29403
- background,
29404
29733
  runner,
29405
29734
  prompt: fm.body.trim(),
29406
29735
  source,
29407
29736
  filePath
29408
29737
  };
29738
+ if (background !== void 0) agent.background = background;
29409
29739
  if (tools !== void 0) agent.tools = tools;
29410
29740
  if (disallowedTools !== void 0) agent.disallowedTools = disallowedTools;
29411
29741
  if (typeof thinking === "string") agent.thinking = thinking;
@@ -29676,7 +30006,7 @@ function spawnTransport(spec2, deps = {}) {
29676
30006
  const line2 = commandLine(spec2, platform);
29677
30007
  let tail = "";
29678
30008
  let resolveExit;
29679
- const exited = new Promise((resolve38) => resolveExit = resolve38);
30009
+ const exited = new Promise((resolve39) => resolveExit = resolve39);
29680
30010
  const child = (0, import_node_child_process9.spawn)(line2.file, line2.args, {
29681
30011
  cwd: spec2.cwd,
29682
30012
  env: spec2.env,
@@ -29723,7 +30053,7 @@ ${error2.message}`.slice(-STDERR_TAIL_BYTES);
29723
30053
  if (done) return;
29724
30054
  child.stdin?.end();
29725
30055
  const timer = new Promise(
29726
- (resolve38) => setTimeout(() => resolve38("timeout"), graceMs).unref()
30056
+ (resolve39) => setTimeout(() => resolve39("timeout"), graceMs).unref()
29727
30057
  );
29728
30058
  if (await Promise.race([exited, timer]) !== "timeout") return;
29729
30059
  if (pid !== void 0) await killProcessTree(pid, { platform, ...deps.process });
@@ -30087,7 +30417,7 @@ async function writeChunked(stream5, text, chunkBytes = WRITE_CHUNK_BYTES) {
30087
30417
  for (let offset = 0; offset < bytes.length; offset += chunkBytes) {
30088
30418
  const piece = bytes.subarray(offset, Math.min(bytes.length, offset + chunkBytes));
30089
30419
  if (!stream5.write(piece)) {
30090
- await new Promise((resolve38) => stream5.once("drain", resolve38));
30420
+ await new Promise((resolve39) => stream5.once("drain", resolve39));
30091
30421
  }
30092
30422
  }
30093
30423
  }
@@ -30172,7 +30502,7 @@ var init_jsonrpc = __esm({
30172
30502
  isClosed = false;
30173
30503
  resolveClosed;
30174
30504
  /** 输入流结束或 `close()` 后 resolve。 */
30175
- closed = new Promise((resolve38) => this.resolveClosed = resolve38);
30505
+ closed = new Promise((resolve39) => this.resolveClosed = resolve39);
30176
30506
  get isOpen() {
30177
30507
  return !this.isClosed;
30178
30508
  }
@@ -30180,7 +30510,7 @@ var init_jsonrpc = __esm({
30180
30510
  request(method, params, signal) {
30181
30511
  if (this.isClosed) return Promise.reject(closedError(method));
30182
30512
  const id = this.nextId++;
30183
- return new Promise((resolve38, reject2) => {
30513
+ return new Promise((resolve39, reject2) => {
30184
30514
  const onAbort = () => {
30185
30515
  this.pending.delete(id);
30186
30516
  reject2(new RpcError(RPC_ERRORS.internalError, `${method}: aborted`));
@@ -30191,7 +30521,7 @@ var init_jsonrpc = __esm({
30191
30521
  method,
30192
30522
  resolve: (value) => {
30193
30523
  signal?.removeEventListener("abort", onAbort);
30194
- resolve38(value);
30524
+ resolve39(value);
30195
30525
  },
30196
30526
  reject: (error2) => {
30197
30527
  signal?.removeEventListener("abort", onAbort);
@@ -30441,8 +30771,8 @@ var init_client = __esm({
30441
30771
  try {
30442
30772
  const answer = await Promise.race([
30443
30773
  handler(request, signal),
30444
- new Promise((resolve38) => {
30445
- const cancelled = () => resolve38({ outcome: { outcome: "cancelled" } });
30774
+ new Promise((resolve39) => {
30775
+ const cancelled = () => resolve39({ outcome: { outcome: "cancelled" } });
30446
30776
  if (signal.aborted) cancelled();
30447
30777
  else signal.addEventListener("abort", cancelled, { once: true });
30448
30778
  })
@@ -30694,7 +31024,7 @@ var init_driver = __esm({
30694
31024
  if (this.client !== void 0) {
30695
31025
  if (this.running !== void 0) await this.client.cancel(this.id).catch(() => void 0);
30696
31026
  if (this.id !== "" && this.client.isOpen && this.client.supportsClose()) {
30697
- const timer = new Promise((resolve38) => setTimeout(resolve38, 2e3).unref());
31027
+ const timer = new Promise((resolve39) => setTimeout(resolve39, 2e3).unref());
30698
31028
  await Promise.race([this.client.closeSession(this.id).catch(() => void 0), timer]);
30699
31029
  }
30700
31030
  this.client.close();
@@ -30949,7 +31279,7 @@ var init_claude_stream = __esm({
30949
31279
  if (signal.aborted) reject2(new Error("aborted"));
30950
31280
  signal.addEventListener("abort", () => reject2(new Error("aborted")), { once: true });
30951
31281
  });
30952
- const timeout = new Promise((resolve38) => setTimeout(resolve38, 1e4).unref());
31282
+ const timeout = new Promise((resolve39) => setTimeout(resolve39, 1e4).unref());
30953
31283
  try {
30954
31284
  await Promise.race([response, aborted, timeout]);
30955
31285
  } catch (error2) {
@@ -30971,8 +31301,8 @@ var init_claude_stream = __esm({
30971
31301
  this.hooks = hooks;
30972
31302
  this.interrupted = false;
30973
31303
  this.sawPartialText = false;
30974
- const done = new Promise((resolve38, reject2) => {
30975
- this.finishTurn = resolve38;
31304
+ const done = new Promise((resolve39, reject2) => {
31305
+ this.finishTurn = resolve39;
30976
31306
  this.failTurn = reject2;
30977
31307
  });
30978
31308
  this.running = done.then((result) => this.complete(result, turn));
@@ -31055,8 +31385,8 @@ var init_claude_stream = __esm({
31055
31385
  }
31056
31386
  control(request) {
31057
31387
  const requestId = `ama-${this.nextRequest++}`;
31058
- return new Promise((resolve38, reject2) => {
31059
- this.controls.set(requestId, { resolve: resolve38, reject: reject2 });
31388
+ return new Promise((resolve39, reject2) => {
31389
+ this.controls.set(requestId, { resolve: resolve39, reject: reject2 });
31060
31390
  this.write(
31061
31391
  `${JSON.stringify({ type: "control_request", request_id: requestId, request })}
31062
31392
  `
@@ -31521,7 +31851,7 @@ var init_codex_app_server = __esm({
31521
31851
  this.messages.clear();
31522
31852
  this.tokensAtStart = this.tokensTotal;
31523
31853
  this.interruptRequested = false;
31524
- const completed = new Promise((resolve38) => this.finishTurn = resolve38);
31854
+ const completed = new Promise((resolve39) => this.finishTurn = resolve39);
31525
31855
  const closed = this.peer.closed.then(() => {
31526
31856
  throw new AmaError(
31527
31857
  "agent_exited",
@@ -31888,7 +32218,7 @@ var init_oneshot = __esm({
31888
32218
  let claude;
31889
32219
  let failure2;
31890
32220
  const done = new Promise(
31891
- (resolve38) => createLineReader(
32221
+ (resolve39) => createLineReader(
31892
32222
  transport.stdout,
31893
32223
  (line2) => {
31894
32224
  let msg2;
@@ -31902,7 +32232,7 @@ var init_oneshot = __esm({
31902
32232
  } else if (this.dialect === "codex") failure2 = this.onCodex(msg2, turn) ?? failure2;
31903
32233
  else failure2 = this.onGemini(msg2, turn) ?? failure2;
31904
32234
  },
31905
- resolve38
32235
+ resolve39
31906
32236
  )
31907
32237
  );
31908
32238
  if (this.dialect !== "gemini") transport.stdin.write(text);
@@ -32229,7 +32559,7 @@ var init_pool = __esm({
32229
32559
  if (signal?.aborted === true) return Promise.reject(abortedError());
32230
32560
  if (this.canRun(agentId) && !this.queue.some((w) => w.agentId === agentId))
32231
32561
  return Promise.resolve(this.take(agentId));
32232
- return new Promise((resolve38, reject2) => {
32562
+ return new Promise((resolve39, reject2) => {
32233
32563
  const onAbort = () => {
32234
32564
  const at = this.queue.indexOf(waiter);
32235
32565
  if (at >= 0) this.queue.splice(at, 1);
@@ -32239,7 +32569,7 @@ var init_pool = __esm({
32239
32569
  agentId,
32240
32570
  grant: (release) => {
32241
32571
  signal?.removeEventListener("abort", onAbort);
32242
- resolve38(release);
32572
+ resolve39(release);
32243
32573
  }
32244
32574
  };
32245
32575
  signal?.addEventListener("abort", onAbort, { once: true });
@@ -33011,7 +33341,6 @@ function definition(name, runner, description, source) {
33011
33341
  model: "inherit",
33012
33342
  maxTurns: 30,
33013
33343
  isolation: "none",
33014
- background: false,
33015
33344
  // 驱动表 id(gemini 等)与宿主 id 不在 AgentRunnerSpec 的字面量里,线上照样是字符串
33016
33345
  runner,
33017
33346
  prompt: "",
@@ -33322,6 +33651,10 @@ function subagentEnvironment(deps, catalog) {
33322
33651
  if (max !== void 0) env.maxConcurrent = max;
33323
33652
  const pending = config.subagents?.maxPending;
33324
33653
  if (pending !== void 0) env.maxPending = pending;
33654
+ const sub = config.subagents;
33655
+ env.background = resolveTaskBackground(sub?.background, deps.assembly.unattended === true);
33656
+ const after = sub?.autoBackgroundAfterMs;
33657
+ if (typeof after === "number" && after > 0) env.autoBackgroundAfterMs = after;
33325
33658
  return env;
33326
33659
  }
33327
33660
  function externalWiring(deps) {
@@ -33371,6 +33704,7 @@ function createSubagentsFactory(deps) {
33371
33704
  registerExternalAgents(catalog, wiring);
33372
33705
  bindTaskAgents(task, catalog);
33373
33706
  const env = subagentEnvironment(deps, catalog);
33707
+ bindTaskBackground(task, env.background === true);
33374
33708
  return ({ core }) => core.depth > 0 ? void 0 : createSubagentExtension(core, env, wiring);
33375
33709
  }
33376
33710
  var warned;
@@ -33383,6 +33717,7 @@ var init_compose_agents = __esm({
33383
33717
  init_external();
33384
33718
  init_api_impl();
33385
33719
  init_task();
33720
+ init_subagent_background();
33386
33721
  init_i18n();
33387
33722
  warned = /* @__PURE__ */ new WeakSet();
33388
33723
  }
@@ -34346,7 +34681,7 @@ async function runAcpMode(runtime, context, options = {}) {
34346
34681
  runtime.approvals.setUiBroker(server.broker);
34347
34682
  runtime.notifier.set((message, level) => context.io.stderr(`ama: [${level}] ${message}
34348
34683
  `));
34349
- return new Promise((resolve38) => {
34684
+ return new Promise((resolve39) => {
34350
34685
  let finished = false;
34351
34686
  const finish2 = async (code, abort) => {
34352
34687
  if (finished) return;
@@ -34361,7 +34696,7 @@ async function runAcpMode(runtime, context, options = {}) {
34361
34696
  server.peer.close();
34362
34697
  server.dispose();
34363
34698
  runtime.notifier.set(void 0);
34364
- resolve38(code);
34699
+ resolve39(code);
34365
34700
  };
34366
34701
  void server.peer.closed.then(() => finish2(ExitCode.Ok, false));
34367
34702
  const offSignals = onTerminationSignals((code) => {
@@ -34550,25 +34885,29 @@ function thinkingMapOf(levels) {
34550
34885
  for (const level of EFFORT_LEVELS) map[level] = levels.includes(level) ? level : null;
34551
34886
  return map;
34552
34887
  }
34553
- function withBackendMetadata(model, cached, matched) {
34554
- if (model.contextWindow === void 0 && cached.contextWindow !== void 0)
34888
+ function withBackendMetadata(model, cached) {
34889
+ if (cached.contextWindow !== void 0) {
34555
34890
  model.contextWindow = cached.contextWindow;
34556
- if (matched) return;
34891
+ if (model.inputLimit !== void 0 && model.inputLimit >= cached.contextWindow)
34892
+ delete model.inputLimit;
34893
+ }
34557
34894
  if (cached.input !== void 0) model.input = [...cached.input];
34558
- const efforts = (cached.reasoningLevels ?? []).filter((x) => x !== "none");
34559
- if (efforts.length > 0) {
34895
+ if (cached.reasoningLevels === void 0) return;
34896
+ if (cached.reasoningLevels.some((x) => x !== "none")) {
34560
34897
  model.reasoning = true;
34561
- model.thinkingLevelMap = thinkingMapOf(cached.reasoningLevels ?? []);
34898
+ model.thinkingLevelMap = thinkingMapOf(cached.reasoningLevels);
34899
+ } else {
34900
+ model.reasoning = false;
34901
+ delete model.thinkingLevelMap;
34562
34902
  }
34563
34903
  }
34564
34904
  function discoveredModels(file, provider, modelsDev) {
34565
34905
  return file.models.map((m) => {
34566
- const { entry, metadata } = enrichEntry(
34567
- m.name ? { id: m.id, name: m.name } : { id: m.id },
34568
- modelsDev
34569
- );
34906
+ const { entry } = enrichEntry(m.name ? { id: m.id, name: m.name } : { id: m.id }, modelsDev);
34570
34907
  const model = withCustomDefaults(entry, provider.id, provider.api);
34571
- withBackendMetadata(model, m, metadata.match !== void 0);
34908
+ withBackendMetadata(model, m);
34909
+ if (model.contextWindow === void 0 && SUBSCRIPTION_PROVIDERS.has(provider.id))
34910
+ model.contextWindow = SUBSCRIPTION_FALLBACK_CONTEXT_WINDOW;
34572
34911
  const names = (provider.channels ?? []).map((c) => c.name);
34573
34912
  if (names.length > 0) {
34574
34913
  const own = file.flavor !== void 0 && names.includes(file.flavor) ? file.flavor : void 0;
@@ -34590,7 +34929,7 @@ function mergeDiscoveredModels(registry2, dataDir, modelsDev, loginFlavor = () =
34590
34929
  }
34591
34930
  return merged;
34592
34931
  }
34593
- var import_node_fs44, import_node_path66, DISCOVERED_CACHE_VERSION, EFFORT_LEVELS;
34932
+ var import_node_fs44, import_node_path66, DISCOVERED_CACHE_VERSION, EFFORT_LEVELS, SUBSCRIPTION_FALLBACK_CONTEXT_WINDOW, SUBSCRIPTION_PROVIDERS;
34594
34933
  var init_discovered_cache = __esm({
34595
34934
  "src/ai/providers/discovered-cache.ts"() {
34596
34935
  "use strict";
@@ -34600,6 +34939,8 @@ var init_discovered_cache = __esm({
34600
34939
  init_enrich();
34601
34940
  DISCOVERED_CACHE_VERSION = 1;
34602
34941
  EFFORT_LEVELS = ["minimal", "low", "medium", "high", "xhigh"];
34942
+ SUBSCRIPTION_FALLBACK_CONTEXT_WINDOW = 128e3;
34943
+ SUBSCRIPTION_PROVIDERS = /* @__PURE__ */ new Set(["chatgpt"]);
34603
34944
  }
34604
34945
  });
34605
34946
 
@@ -34965,7 +35306,7 @@ var init_key_docs = __esm({
34965
35306
  codemode: { inlineBudget: DEFAULT_INLINE_BUDGET, requireStrict: false },
34966
35307
  cache: { ...DEFAULT_CACHE_CONFIG },
34967
35308
  request: { idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS },
34968
- ui: { compact: false, animation: true, restoreOnCancel: true, language: "auto" },
35309
+ ui: { compact: false, logo: "auto", animation: true, restoreOnCancel: true, language: "auto" },
34969
35310
  // [W6-C0] 第六波键的缺省(行为由各批次实现)
34970
35311
  memory: {
34971
35312
  enabled: false,
@@ -34990,7 +35331,7 @@ var init_key_docs = __esm({
34990
35331
  images: { resize: "auto" },
34991
35332
  plan: { bash: "readonly", unattended: "stop" },
34992
35333
  agents: { maxConcurrent: 3, dirs: [] },
34993
- subagents: { maxConcurrent: 4, maxPending: 16 },
35334
+ subagents: { maxConcurrent: 4, maxPending: 16, background: "auto", autoBackgroundAfterMs: 0 },
34994
35335
  reminders: { todo: true, fileChanges: true, contextPressure: true, budget: true },
34995
35336
  todo: { reminder: 10 }
34996
35337
  };
@@ -35244,7 +35585,13 @@ function w5Sections() {
35244
35585
  }
35245
35586
  }
35246
35587
  ),
35247
- subagents: object({ maxConcurrent: num2(1, 64), maxPending: num2(0, 1024), defaultModel: str3() }),
35588
+ subagents: object({
35589
+ maxConcurrent: num2(1, 64),
35590
+ maxPending: num2(0, 1024),
35591
+ defaultModel: str3(),
35592
+ background: oneOf(SUBAGENT_BACKGROUND_MODES),
35593
+ autoBackgroundAfterMs: num2(0)
35594
+ }),
35248
35595
  models: object({ aliases: object({ fast: str3(), strong: str3() }), enabled: strings2 }),
35249
35596
  fallbackModel: str3(),
35250
35597
  limits: object({ maxTurns: num2(1), maxCostUsd: num2(0) }),
@@ -35359,6 +35706,7 @@ function buildBaseSchema() {
35359
35706
  quietStartup: oneOf(["normal", "header", "silent"]),
35360
35707
  ascii: bool(),
35361
35708
  compact: bool(),
35709
+ logo: oneOf(["auto", "off"]),
35362
35710
  animation: bool(),
35363
35711
  restoreOnCancel: bool(),
35364
35712
  statusLine: oneOf(STATUS_LINE_MODES),
@@ -38404,7 +38752,7 @@ var init_auth2 = __esm({
38404
38752
  let pending = this.commandCache.get(key);
38405
38753
  if (!pending) {
38406
38754
  const timeout = this.options.commandTimeoutMs ?? 1e4;
38407
- pending = new Promise((resolve38) => {
38755
+ pending = new Promise((resolve39) => {
38408
38756
  (0, import_node_child_process13.exec)(
38409
38757
  command3,
38410
38758
  { timeout, windowsHide: true, env: { ...this.env, ...extraEnv }, maxBuffer: 1 << 20 },
@@ -38412,8 +38760,8 @@ var init_auth2 = __esm({
38412
38760
  const value = String(stdout).trim();
38413
38761
  if (error2 || value.length === 0) {
38414
38762
  this.warn(`api key command failed or printed nothing: ${command3.split(/\s+/)[0]}`);
38415
- resolve38(void 0);
38416
- } else resolve38(value);
38763
+ resolve39(void 0);
38764
+ } else resolve39(value);
38417
38765
  }
38418
38766
  );
38419
38767
  });
@@ -39371,6 +39719,7 @@ var init_settings_registry = __esm({
39371
39719
  row("ui.showThinking", "ui", "now", "any"),
39372
39720
  row("ui.compact", "ui", "now", "any"),
39373
39721
  row("ui.animation", "ui", "now", "any"),
39722
+ row("ui.logo", "ui", "restart", "any"),
39374
39723
  row("ui.statusLine", "ui", "now", "any"),
39375
39724
  row("ui.restoreOnCancel", "ui", "nextSession", "any"),
39376
39725
  row("ui.quietStartup", "ui", "restart", "any"),
@@ -39432,6 +39781,9 @@ var init_settings_registry = __esm({
39432
39781
  row("agents.sessionBudgetUsd", "agents", "nextSession", "deny", { kind: "optionalNumber" }),
39433
39782
  row("subagents.maxConcurrent", "agents", "nextSession", "deny"),
39434
39783
  row("subagents.maxPending", "agents", "nextSession", "deny"),
39784
+ // [W7-B2] 改 task 工具描述(缺省后台 / 前台两种文案),故 prefix
39785
+ row("subagents.background", "agents", "restart", "any", { prefix: true }),
39786
+ row("subagents.autoBackgroundAfterMs", "agents", "restart", "any"),
39435
39787
  // [W6-M]
39436
39788
  row("memory.enabled", "memory", "nextSession", "tighten", { envOverride: "AMA_MEMORY" }),
39437
39789
  row("memory.subagents", "memory", "nextSession", "deny")
@@ -41134,10 +41486,15 @@ var init_keybindings = __esm({
41134
41486
  /** [W5-U] 粘贴剪贴板图片(写进数据目录,输入框插入 `@路径`)。 */
41135
41487
  "app.paste.image": ["ctrl+v"],
41136
41488
  /**
41137
- * [W6-A] 进入 Agent 栏:只在输入为空时生效(有字时 `Ctrl+B` 仍是光标左移、`↓` 仍是下移 / 历史);
41138
- * `↓` 另要求栏可见。tmux 缺省前缀吃掉 `Ctrl+B` 时用 `↓`。
41489
+ * [W6-A / W7-A] 进入 Agent 栏:输入为空、补全未开、有子 Agent 任务即可(不要求栏可见);有字时 `↓` 仍是
41490
+ * 下移 / 历史,并给一次提示。`Ctrl+B` 不再进栏(tmux 缺省前缀会吃掉它),留给 `app.tasks.background`。
41491
+ */
41492
+ "app.agents.focus": ["down"],
41493
+ /**
41494
+ * [W7-C] 有阻塞中的前台子 Agent 任务(或 `task_ctl wait`)时全部转后台,不看输入框;没有时落回编辑器
41495
+ * (`tui.editor.cursorLeft` 的 `ctrl+b`)。tmux 里按 `C-b C-b` 透传。
41139
41496
  */
41140
- "app.agents.focus": ["ctrl+b", "down"]
41497
+ "app.tasks.background": ["ctrl+b"]
41141
41498
  };
41142
41499
  Keybindings = class {
41143
41500
  table;
@@ -42317,6 +42674,10 @@ var init_editor = __esm({
42317
42674
  get isCompletionOpen() {
42318
42675
  return this.completion !== null;
42319
42676
  }
42677
+ /** 正在用 ↑↓ 浏览输入历史(编辑后即退出)。 */
42678
+ get isBrowsingHistory() {
42679
+ return this.historyIndex !== -1;
42680
+ }
42320
42681
  getHistory() {
42321
42682
  return this.history;
42322
42683
  }
@@ -42819,6 +43180,9 @@ var init_spacer = __esm({
42819
43180
  });
42820
43181
 
42821
43182
  // src/tui/components/loader.ts
43183
+ function sameList(a, b) {
43184
+ return a.length === b.length && a.every((e, i) => e === b[i]);
43185
+ }
42822
43186
  function formatElapsed(ms) {
42823
43187
  const total = Math.max(0, Math.floor(ms / 1e3));
42824
43188
  if (total < 60) return `${total}s`;
@@ -42848,6 +43212,7 @@ var init_loader = __esm({
42848
43212
  startedAt;
42849
43213
  verb;
42850
43214
  extras = [];
43215
+ optional = [];
42851
43216
  verbElapsed;
42852
43217
  lastElapsed = "";
42853
43218
  listeners = /* @__PURE__ */ new Set();
@@ -42873,10 +43238,11 @@ var init_loader = __esm({
42873
43238
  }
42874
43239
  /** 换动词与附加项(附加项 dim,排在已用时之后)。 */
42875
43240
  setVerb(verb, extras = [], options = {}) {
42876
- const same2 = verb === this.verb && options.elapsed === this.verbElapsed && extras.length === this.extras.length && extras.every((e, i) => e === this.extras[i]);
43241
+ const same2 = verb === this.verb && options.elapsed === this.verbElapsed && sameList(extras, this.extras) && sameList(options.optional ?? [], this.optional);
42877
43242
  if (same2) return;
42878
43243
  this.verb = verb;
42879
43244
  this.extras = [...extras];
43245
+ this.optional = [...options.optional ?? []];
42880
43246
  this.verbElapsed = options.elapsed;
42881
43247
  this.requestRender();
42882
43248
  }
@@ -42936,7 +43302,13 @@ var init_loader = __esm({
42936
43302
  if (this.verbElapsed ?? this.options.showElapsed !== false) parts.push(dim(elapsed));
42937
43303
  for (const extra of this.extras) parts.push(dim(extra));
42938
43304
  const spinner = theme ? theme.fg("accent", this.frame) : this.frame;
42939
- return [truncateToWidth(`${spinner} ${parts.join(dim(" · "))}`, width2)];
43305
+ const line2 = (items) => `${spinner} ${items.join(dim(" · "))}`;
43306
+ for (const extra of this.optional) {
43307
+ const next = [...parts, dim(extra)];
43308
+ if (visibleWidth(line2(next)) > width2) break;
43309
+ parts.push(dim(extra));
43310
+ }
43311
+ return [truncateToWidth(line2(parts), width2)];
42940
43312
  }
42941
43313
  invalidate() {
42942
43314
  }
@@ -43027,13 +43399,13 @@ var init_meter = __esm({
43027
43399
  render(width2) {
43028
43400
  const { theme, label: label2, cells = 10 } = this.options;
43029
43401
  const ratio = this.value === void 0 ? void 0 : Math.min(1, Math.max(0, this.value));
43030
- const percent2 = ratio === void 0 ? "—" : `${Math.round(ratio * 100)}%`;
43402
+ const percent3 = ratio === void 0 ? "—" : `${Math.round(ratio * 100)}%`;
43031
43403
  const color = (c, text) => theme ? theme.fg(c, text) : text;
43032
43404
  const head = label2 === void 0 || label2 === "" ? "" : `${label2} `;
43033
43405
  const filled = ratio === void 0 ? 0 : Math.round(ratio * cells);
43034
43406
  const glyphs = theme?.glyphs ?? UNICODE_GLYPHS;
43035
43407
  const bar = color(this.level(), glyphs.meterFull.repeat(filled)) + color("dim", glyphs.meterEmpty.repeat(cells - filled));
43036
- const tail = color(ratio === void 0 ? "dim" : this.level(), percent2);
43408
+ const tail = color(ratio === void 0 ? "dim" : this.level(), percent3);
43037
43409
  const full = `${head}${bar} ${tail}`;
43038
43410
  if (visibleWidth(full) <= width2) return [full];
43039
43411
  return [truncateToWidth(`${head}${tail}`, width2)];
@@ -43229,7 +43601,7 @@ function promptChoice(options) {
43229
43601
  `);
43230
43602
  drawn = lines2.length;
43231
43603
  };
43232
- return new Promise((resolve38) => {
43604
+ return new Promise((resolve39) => {
43233
43605
  const wasRaw = input2.isRaw === true;
43234
43606
  let done = false;
43235
43607
  const restore = () => {
@@ -43246,7 +43618,7 @@ function promptChoice(options) {
43246
43618
  draw([`${question} ${theme.fg("dim", answer)}`]);
43247
43619
  restore();
43248
43620
  input2.pause?.();
43249
- resolve38(index2);
43621
+ resolve39(index2);
43250
43622
  };
43251
43623
  const onKey = (key) => {
43252
43624
  const letter = /^[a-z]$/i.test(key) ? key.toLowerCase() : "";
@@ -44303,12 +44675,12 @@ function cacheMissNotice(miss) {
44303
44675
  }
44304
44676
  function contextPressureNotice(event) {
44305
44677
  const m = msg().report.cache;
44306
- const percent2 = Math.round(event.percent);
44678
+ const percent3 = Math.round(event.percent);
44307
44679
  if (event.estimatedTurnsLeft !== void 0)
44308
- return m.pressureTurns(percent2, event.estimatedTurnsLeft);
44680
+ return m.pressureTurns(percent3, event.estimatedTurnsLeft);
44309
44681
  if (event.remainingTokens !== void 0)
44310
- return m.pressureTokens(percent2, formatTokenCount(event.remainingTokens));
44311
- return m.pressure(percent2);
44682
+ return m.pressureTokens(percent3, formatTokenCount(event.remainingTokens));
44683
+ return m.pressure(percent3);
44312
44684
  }
44313
44685
  function cacheEventNotice(event, enabled) {
44314
44686
  if (!enabled) return void 0;
@@ -44468,7 +44840,7 @@ function describeSession(session, now = Date.now()) {
44468
44840
  const stats = session.getStats();
44469
44841
  const model = state.model === void 0 ? "?" : `${state.model.provider}/${state.model.id}`;
44470
44842
  const t = stats.tokens;
44471
- const percent2 = stats.contextPercent === void 0 ? "?" : `${Math.round(stats.contextPercent)}`;
44843
+ const percent3 = stats.contextPercent === void 0 ? "?" : `${Math.round(stats.contextPercent)}`;
44472
44844
  const m = msg().report.session;
44473
44845
  const rows = [
44474
44846
  { key: m.keySession, value: m.session(state.sessionId, state.sessionFile) },
@@ -44495,7 +44867,7 @@ function describeSession(session, now = Date.now()) {
44495
44867
  value: m.context(
44496
44868
  String(stats.contextTokens ?? "?"),
44497
44869
  String(stats.contextWindow ?? "?"),
44498
- percent2
44870
+ percent3
44499
44871
  )
44500
44872
  }
44501
44873
  ];
@@ -44541,6 +44913,115 @@ var init_session_report = __esm({
44541
44913
  }
44542
44914
  });
44543
44915
 
44916
+ // src/modes/interactive/status-quota.ts
44917
+ function formatRemaining(ms, tight = false) {
44918
+ const minutes = Math.max(0, Math.floor(ms / 6e4));
44919
+ const days = Math.floor(minutes / 1440);
44920
+ const hours = Math.floor(minutes % 1440 / 60);
44921
+ const sep5 = tight ? "" : " ";
44922
+ if (days > 0) return `${days}d${sep5}${hours}h`;
44923
+ if (hours > 0) return `${hours}h${sep5}${minutes % 60}m`;
44924
+ return `${minutes}m`;
44925
+ }
44926
+ function windowSpan(minutes) {
44927
+ if (minutes % 1440 === 0) return `${minutes / 1440}d`;
44928
+ if (minutes % 60 === 0) return `${minutes / 60}h`;
44929
+ return `${Math.round(minutes)}m`;
44930
+ }
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 };
44938
+ }
44939
+ function shortLabel(w, which) {
44940
+ 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);
44945
+ }
44946
+ function percent2(theme, used, decimal) {
44947
+ return theme.fg(levelColor(used / 100), decimal ? `${used.toFixed(1)}%` : `${Math.round(used)}%`);
44948
+ }
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;
44954
+ }
44955
+ function compactQuotaItem(quota, theme) {
44956
+ 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)}`
44959
+ );
44960
+ return items.length === 0 ? void 0 : items.join(" ");
44961
+ }
44962
+ function quotaParts2(quota, width2, now, theme) {
44963
+ const dim = (text) => theme.fg("dim", text);
44964
+ const narrow = width2 < QUOTA_NARROW_WIDTH;
44965
+ const parts = [];
44966
+ windows(quota).forEach(([which, w], i) => {
44967
+ const first = i === 0;
44968
+ const group = narrow ? which : void 0;
44969
+ const name = narrow ? `${shortLabel(w, which)} ` : labels(w, which).pct;
44970
+ parts.push({
44971
+ text: dim(name) + percent2(theme, w.usedPercent, !narrow),
44972
+ priority: first ? void 0 : 2,
44973
+ zone: "left",
44974
+ ...group !== void 0 ? { group } : {}
44975
+ });
44976
+ if (w.resetsAt === void 0) return;
44977
+ const left = formatRemaining(w.resetsAt - now, narrow);
44978
+ parts.push({
44979
+ text: narrow ? theme.fg("tool", `${theme.glyphs.retry}${left}`) : dim(labels(w, which).reset) + theme.fg("tool", left),
44980
+ priority: first ? 3 : 1,
44981
+ zone: "left",
44982
+ // 宽格式按最宽形状占位(`23h 59m`),数值变化不让某项时有时无
44983
+ reserve: narrow ? 0 : visibleWidth(labels(w, which).reset) + 7,
44984
+ ...group !== void 0 ? { group } : {}
44985
+ });
44986
+ });
44987
+ return parts;
44988
+ }
44989
+ var QUOTA_NARROW_WIDTH, FIVE_HOURS, WEEK, QuotaLine;
44990
+ var init_status_quota = __esm({
44991
+ "src/modes/interactive/status-quota.ts"() {
44992
+ "use strict";
44993
+ init_i18n();
44994
+ init_tui2();
44995
+ init_status_bar();
44996
+ QUOTA_NARROW_WIDTH = 80;
44997
+ FIVE_HOURS = 300;
44998
+ WEEK = 10080;
44999
+ QuotaLine = class {
45000
+ constructor(source, theme) {
45001
+ this.source = source;
45002
+ this.theme = theme;
45003
+ }
45004
+ source;
45005
+ theme;
45006
+ render(width2) {
45007
+ if (this.source.layout() !== "full") return [];
45008
+ const view = this.source.quota();
45009
+ if (view === void 0) return [];
45010
+ const theme = this.theme;
45011
+ if (view === "pending") {
45012
+ return [truncateToWidth(theme.fg("dim", msg().interactive.statusLine.quota.pending), width2)];
45013
+ }
45014
+ const parts = quotaParts2(view.quota, width2, this.source.now(), theme);
45015
+ if (parts.length === 0) return [];
45016
+ const sep5 = width2 < QUOTA_NARROW_WIDTH ? " · " : " | ";
45017
+ return [layoutRow(parts, width2, theme, { sep: sep5 })];
45018
+ }
45019
+ invalidate() {
45020
+ }
45021
+ };
45022
+ }
45023
+ });
45024
+
44544
45025
  // src/modes/interactive/status-bar.ts
44545
45026
  function formatTokens3(count2) {
44546
45027
  if (count2 < 1e3) return String(count2);
@@ -44672,6 +45153,7 @@ var init_status_bar = __esm({
44672
45153
  init_i18n();
44673
45154
  init_modes();
44674
45155
  init_channels();
45156
+ init_status_quota();
44675
45157
  init_tui2();
44676
45158
  MIN_GAP = 4;
44677
45159
  METER_WIDTH = 110;
@@ -44691,7 +45173,8 @@ var init_status_bar = __esm({
44691
45173
  rebill: 13,
44692
45174
  preset: 14,
44693
45175
  host: 15,
44694
- hint: 16
45176
+ hint: 16,
45177
+ quota: 17
44695
45178
  };
44696
45179
  FULL = { model: 0, ctx: 1, cost: 2, duration: 3, branch: 4, dir: 5, diff: 6, thinking: 7 };
44697
45180
  FULL_HINT = 12;
@@ -44751,7 +45234,8 @@ var init_status_bar = __esm({
44751
45234
  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));
44752
45235
  right(modelText, p.model, full ? { group: "model" } : {});
44753
45236
  if (state.thinkingLevel !== "off") {
44754
- right(dim(state.thinkingLevel), p.thinking, full ? { group: "model" } : {});
45237
+ const level = full ? theme.fg("user", state.thinkingLevel) : dim(state.thinkingLevel);
45238
+ right(level, p.thinking, full ? { group: "model" } : {});
44755
45239
  }
44756
45240
  const usage = full ? {} : usageItems(stats, this.queue, this.source, theme);
44757
45241
  if (usage.tokens !== void 0) right(usage.tokens, COMPACT.tokens);
@@ -44764,50 +45248,71 @@ var init_status_bar = __esm({
44764
45248
  const cost2 = statusCost(stats);
44765
45249
  const used = stats.tokens.input + stats.tokens.output + stats.tokens.cacheRead > 0;
44766
45250
  if (cost2 === void 0 && used) right(dim("$?"), FULL.cost);
44767
- else if (cost2 !== void 0 && cost2 > 0) right(dim(formatCost(cost2)), FULL.cost);
45251
+ else if (cost2 !== void 0 && cost2 > 0) {
45252
+ right(theme.fg("warning", formatCost(cost2)), FULL.cost);
45253
+ }
44768
45254
  }
44769
45255
  const started = this.source.sessionStartedAt?.() ?? stats.telemetry?.sessionStartedAt;
44770
45256
  if (started !== void 0) {
44771
45257
  const now = this.source.now?.() ?? Date.now();
44772
- right(dim(formatDuration4(now - started)), p.duration, { reserve: 6 });
45258
+ const text = formatDuration4(now - started);
45259
+ right(full ? theme.fg("tool", text) : dim(text), p.duration, { reserve: 6 });
44773
45260
  }
44774
45261
  if (usage.queue !== void 0) right(usage.queue, COMPACT.queue);
44775
45262
  if (usage.codemode !== void 0) right(usage.codemode, COMPACT.codemode);
44776
45263
  if (usage.sandbox !== void 0) right(usage.sandbox, COMPACT.codemode);
44777
45264
  if (usage.preset !== void 0) right(usage.preset, COMPACT.preset);
44778
45265
  if (usage.host !== void 0) right(usage.host, COMPACT.host);
45266
+ const quota = full ? void 0 : this.source.quota?.();
45267
+ const quotaItem = typeof quota === "object" ? compactQuotaItem(quota.quota, theme) : void 0;
45268
+ if (quotaItem !== void 0) right(quotaItem, COMPACT.quota);
44779
45269
  return parts;
44780
45270
  }
44781
- /** `目录 ⎇ 分支 短提交 +a −b`:三项同组、各自按优先级丢弃。 */
45271
+ /** `目录 ⎇ 分支 短提交 +a −b`:三项同组、各自按优先级丢弃;full 另带 `↑N ↓N` 并按参考配色。 */
44782
45272
  gitParts(p, full) {
44783
45273
  const git = this.source.git?.();
44784
45274
  if (git === void 0) return [];
44785
45275
  const theme = this.theme;
44786
45276
  const g = theme.glyphs;
44787
45277
  const dim = (text) => theme.fg("dim", text);
45278
+ const ok = (text) => full ? theme.fg("success", text) : dim(text);
44788
45279
  const out = [];
44789
- const add3 = (text, priority) => void out.push({ text: dim(text), priority, zone: "right", group: "git" });
44790
- if (git.dir !== "") add3(git.dir, p.dir);
45280
+ const add3 = (text, priority) => void out.push({ text, priority, zone: "right", group: "git" });
45281
+ if (git.dir !== "") add3(ok(git.dir), p.dir);
44791
45282
  const info2 = git.info;
44792
45283
  if (info2 === void 0) return out;
44793
- const head = [info2.branch, info2.shortHead].filter((s2) => s2 !== void 0).join(" ");
44794
- if (head !== "") add3(`${g.branch} ${head}`, p.branch);
45284
+ const head = [];
45285
+ if (info2.branch !== void 0) head.push(ok(info2.branch));
45286
+ if (info2.shortHead !== void 0) head.push(dim(info2.shortHead));
45287
+ if (full && info2.ahead !== void 0 && info2.ahead > 0) {
45288
+ head.push(theme.fg("code", `${g.arrowUp}${info2.ahead}`));
45289
+ }
45290
+ if (full && info2.behind !== void 0 && info2.behind > 0) {
45291
+ head.push(theme.fg("error", `${g.arrowDown}${info2.behind}`));
45292
+ }
45293
+ if (!full) {
45294
+ const text = [info2.branch, info2.shortHead].filter((s2) => s2 !== void 0).join(" ");
45295
+ if (text !== "") add3(dim(`${g.branch} ${text}`), p.branch);
45296
+ } else if (head.length > 0) add3(`${dim(g.branch)} ${head.join(" ")}`, p.branch);
44795
45297
  if (info2.insertions !== void 0 && info2.deletions !== void 0) {
44796
45298
  const { insertions: a, deletions: d } = info2;
44797
- add3(full ? `(+${a},-${d})` : `+${a} ${g.ascii ? "-" : "−"}${d}`, p.diff);
45299
+ add3(
45300
+ full ? `${dim("(")}${theme.fg("success", `+${a}`)}${dim(",")}${theme.fg("error", `-${d}`)}${dim(")")}` : dim(`+${a} ${g.ascii ? "-" : "−"}${d}`),
45301
+ p.diff
45302
+ );
44798
45303
  }
44799
45304
  return out;
44800
45305
  }
44801
- ctxText(percent2, width2, decimal) {
45306
+ ctxText(percent3, width2, decimal) {
44802
45307
  const theme = this.theme;
44803
- if (percent2 === void 0) return theme.fg("dim", decimal ? "Ctx ?" : "ctx ?");
44804
- const color = levelColor(percent2 / 100);
45308
+ if (percent3 === void 0) return theme.fg("dim", decimal ? "Ctx ?" : "ctx ?");
45309
+ const color = levelColor(percent3 / 100);
44805
45310
  if (!decimal && width2 >= METER_WIDTH) {
44806
- const meter = new Meter(percent2 / 100, { label: theme.fg("dim", "ctx"), theme });
45311
+ const meter = new Meter(percent3 / 100, { label: theme.fg("dim", "ctx"), theme });
44807
45312
  return meter.render(80)[0] ?? "";
44808
45313
  }
44809
- if (decimal) return theme.fg("dim", "Ctx ") + theme.fg(color, `${percent2.toFixed(1)}%`);
44810
- return theme.fg("dim", "ctx ") + theme.fg(color, `${Math.round(percent2)}%`);
45314
+ if (decimal) return theme.fg("dim", "Ctx ") + theme.fg(color, `${percent3.toFixed(1)}%`);
45315
+ return theme.fg("dim", "ctx ") + theme.fg(color, `${Math.round(percent3)}%`);
44811
45316
  }
44812
45317
  render(width2) {
44813
45318
  const style = this.layout() === "full" ? FULL_STYLE : {};
@@ -46358,7 +46863,7 @@ async function runSandbox(request) {
46358
46863
  let stderr = "";
46359
46864
  let settled = false;
46360
46865
  let finish2;
46361
- const finished = new Promise((resolve38) => finish2 = resolve38);
46866
+ const finished = new Promise((resolve39) => finish2 = resolve39);
46362
46867
  const write2 = (message) => {
46363
46868
  if (child.stdin.destroyed || !child.stdin.writable) return;
46364
46869
  child.stdin.write(encodeLine(message));
@@ -47246,7 +47751,8 @@ __export(print_mode_exports, {
47246
47751
  joinPrompt: () => joinPrompt,
47247
47752
  readPromptStdin: () => readPromptStdin,
47248
47753
  runPrintMode: () => runPrintMode,
47249
- stdinWaitMs: () => stdinWaitMs
47754
+ stdinWaitMs: () => stdinWaitMs,
47755
+ waitBackgroundTasks: () => waitBackgroundTasks
47250
47756
  });
47251
47757
  function joinPrompt(argument, piped) {
47252
47758
  const parts = [argument ?? "", piped.replace(/\s+$/, "")].filter((p) => p.trim() !== "");
@@ -47260,12 +47766,12 @@ function stdinWaitMs(env) {
47260
47766
  }
47261
47767
  function readWithin(io, ms) {
47262
47768
  if (io.readStdinFirstByte !== void 0) return io.readStdinFirstByte(ms);
47263
- return new Promise((resolve38, reject2) => {
47264
- const timer = setTimeout(() => resolve38(void 0), ms);
47769
+ return new Promise((resolve39, reject2) => {
47770
+ const timer = setTimeout(() => resolve39(void 0), ms);
47265
47771
  io.readStdin().then(
47266
47772
  (text) => {
47267
47773
  clearTimeout(timer);
47268
- resolve38(text);
47774
+ resolve39(text);
47269
47775
  },
47270
47776
  (error2) => {
47271
47777
  clearTimeout(timer);
@@ -47328,9 +47834,15 @@ async function runPrintMode(runtime, context) {
47328
47834
  const denied = [];
47329
47835
  let limit;
47330
47836
  let plan2;
47837
+ let stopWaiting = () => void 0;
47838
+ const stopped = new Promise((resolve39) => {
47839
+ stopWaiting = resolve39;
47840
+ });
47331
47841
  const unsubscribe = session.subscribe((event) => {
47332
- if (event.type === "limit_reached") limit ??= event;
47333
- else if (event.type === "plan_proposed") plan2 = event;
47842
+ if (event.type === "limit_reached") {
47843
+ limit ??= event;
47844
+ stopWaiting();
47845
+ } else if (event.type === "plan_proposed") plan2 = event;
47334
47846
  else if (event.type === "plan_resolved" && event.planId === plan2?.planId) plan2 = void 0;
47335
47847
  if (event.type === "tool_execution_end" && event.denied === true)
47336
47848
  denied.push({
@@ -47353,16 +47865,21 @@ async function runPrintMode(runtime, context) {
47353
47865
  let signalled;
47354
47866
  const offSignals = onTerminationSignals((code) => {
47355
47867
  signalled ??= code;
47868
+ stopWaiting();
47356
47869
  void session.abort();
47357
47870
  });
47358
47871
  let stdoutClosed = false;
47359
47872
  const offEpipe = onStdoutClosed(() => {
47360
47873
  stdoutClosed = true;
47874
+ stopWaiting();
47361
47875
  void session.abort();
47362
47876
  });
47363
47877
  let failure2;
47364
47878
  try {
47365
47879
  await session.prompt(prompt, images.length > 0 ? { images } : {});
47880
+ const ended = lastAssistant(session)?.stopReason;
47881
+ if (signalled === void 0 && !stdoutClosed && limit === void 0 && ended !== "error" && ended !== "aborted")
47882
+ await waitBackgroundTasks(session, stopped, io);
47366
47883
  } catch (error2) {
47367
47884
  failure2 = errorText3(error2);
47368
47885
  } finally {
@@ -47393,6 +47910,7 @@ async function runPrintMode(runtime, context) {
47393
47910
  cost: stats.cost,
47394
47911
  cacheHitRate: stats.cacheHitRate,
47395
47912
  ...stats.cache !== void 0 ? { cache: stats.cache } : {},
47913
+ ...stats.tasks !== void 0 ? { tasks: stats.tasks } : {},
47396
47914
  ...denied.length > 0 ? { deniedTools: denied } : {},
47397
47915
  ...plan2 !== void 0 ? {
47398
47916
  planPending: {
@@ -47439,6 +47957,13 @@ async function runPrintMode(runtime, context) {
47439
47957
  if (denied.length > 0) return ExitCode.ToolDenied;
47440
47958
  return ExitCode.Ok;
47441
47959
  }
47960
+ async function waitBackgroundTasks(session, stopped, io) {
47961
+ const registry2 = registryOf(session.state.sessionId);
47962
+ if (registry2 === void 0) return;
47963
+ const running = registry2.list().filter((task) => task.status === "running").length;
47964
+ if (running > 0) io.stderr(msg().print.print.waitingTasks(running));
47965
+ await Promise.race([registry2.settled(), stopped]);
47966
+ }
47442
47967
  function describePlanPending(plan2) {
47443
47968
  const where = plan2.filePath ?? plan2.planId;
47444
47969
  return msg().print.print.planPending(plan2.version, where);
@@ -47467,6 +47992,7 @@ var init_print_mode = __esm({
47467
47992
  init_shared2();
47468
47993
  init_json_event();
47469
47994
  init_limits();
47995
+ init_subagent_registry();
47470
47996
  init_image_input();
47471
47997
  init_i18n();
47472
47998
  STDIN_WAIT_HINT_MS = 3e3;
@@ -48266,6 +48792,59 @@ var init_tasks_report = __esm({
48266
48792
  }
48267
48793
  });
48268
48794
 
48795
+ // src/modes/interactive/task-background.ts
48796
+ function blockingTasks2(session) {
48797
+ return registryOf(session.state.sessionId)?.blocking() ?? [];
48798
+ }
48799
+ function backgroundTasks(session, taskId) {
48800
+ if (taskId === void 0 && blockingTasks2(session).length === 0) return [];
48801
+ return session.backgroundTask(taskId, "user");
48802
+ }
48803
+ function runningBackground(session) {
48804
+ const registry2 = registryOf(session.state.sessionId);
48805
+ if (registry2 === void 0) return [];
48806
+ return registry2.list().filter((task) => task.status === "running" && task.background).map((task) => task.taskId);
48807
+ }
48808
+ function backgroundedText2(ids) {
48809
+ const m = msg().agents.background;
48810
+ return ids.length === 0 ? m.none : m.done(ids.join(", "));
48811
+ }
48812
+ function keyLabel(keys, action) {
48813
+ const id = keys.keys(action)[0];
48814
+ if (id === void 0) return void 0;
48815
+ return id.split("+").map(
48816
+ (part) => part.length === 1 ? part.toUpperCase() : part[0].toUpperCase() + part.slice(1)
48817
+ ).join("+");
48818
+ }
48819
+ var STOP_CONFIRM_MS, StopArm;
48820
+ var init_task_background = __esm({
48821
+ "src/modes/interactive/task-background.ts"() {
48822
+ "use strict";
48823
+ init_subagent_registry();
48824
+ init_i18n();
48825
+ STOP_CONFIRM_MS = 1500;
48826
+ StopArm = class {
48827
+ constructor(windowMs = STOP_CONFIRM_MS) {
48828
+ this.windowMs = windowMs;
48829
+ }
48830
+ windowMs;
48831
+ armed;
48832
+ press(taskId, now) {
48833
+ const armed = this.armed;
48834
+ if (armed !== void 0 && armed.taskId === taskId && now - armed.at < this.windowMs) {
48835
+ this.armed = void 0;
48836
+ return "stop";
48837
+ }
48838
+ this.armed = { taskId, at: now };
48839
+ return "arm";
48840
+ }
48841
+ reset() {
48842
+ this.armed = void 0;
48843
+ }
48844
+ };
48845
+ }
48846
+ });
48847
+
48269
48848
  // src/modes/commands-core.ts
48270
48849
  function command(name, key, args) {
48271
48850
  const info2 = {
@@ -48313,10 +48892,13 @@ function cacheCommand(session, args) {
48313
48892
  }
48314
48893
  throw new AmaError("invalid_arguments", msg().report.command.cacheUsage);
48315
48894
  }
48316
- async function tasksCommand(sessionId, args) {
48895
+ async function tasksCommand(session, args) {
48896
+ const sessionId = session.state.sessionId;
48317
48897
  const [first, second, extra] = args.split(/\s+/).filter((s2) => s2 !== "");
48318
48898
  const now = Date.now();
48319
48899
  if (first === void 0) return describeTasks(sessionId, now);
48900
+ if (first === "bg" && extra === void 0)
48901
+ return backgroundedText2(backgroundTasks(session, second));
48320
48902
  if (first === "stop" && second !== void 0 && extra === void 0) {
48321
48903
  await stopTask(sessionId, second);
48322
48904
  return msg().report.command.taskStopped(second);
@@ -48441,7 +49023,7 @@ async function runSlashCommand(line2, ctx) {
48441
49023
  case "plan":
48442
49024
  return planCommand(session, args, ctx);
48443
49025
  case "tasks":
48444
- return { kind: "handled", message: await tasksCommand(session.state.sessionId, args) };
49026
+ return { kind: "handled", message: await tasksCommand(session, args) };
48445
49027
  case "agents":
48446
49028
  return { kind: "handled", message: describeAgents(session.state.sessionId) };
48447
49029
  case "paste": {
@@ -48475,6 +49057,7 @@ var init_commands_core = __esm({
48475
49057
  init_plan_command();
48476
49058
  init_rewind_command();
48477
49059
  init_tasks_report();
49060
+ init_task_background();
48478
49061
  init_session_report();
48479
49062
  init_i18n();
48480
49063
  BUILTIN_COMMANDS = [
@@ -48493,7 +49076,7 @@ var init_commands_core = __esm({
48493
49076
  command("cache", "cache", "[warm off|streaming|idle | fingerprint]"),
48494
49077
  command("statusline", "statusline", "[full|compact]"),
48495
49078
  command("plan", "plan", { key: "planArgs" }),
48496
- command("tasks", "tasks", "[id] | stop <id>"),
49079
+ command("tasks", "tasks", "[id] | stop <id> | bg [id]"),
48497
49080
  command("agents", "agents"),
48498
49081
  command("paste", "paste"),
48499
49082
  command("exit", "exit"),
@@ -48550,9 +49133,9 @@ async function startRun(ctx, run7) {
48550
49133
  const session = ctx.session();
48551
49134
  let settled = false;
48552
49135
  let off = () => void 0;
48553
- const started = new Promise((resolve38) => {
49136
+ const started = new Promise((resolve39) => {
48554
49137
  off = session.subscribe((event) => {
48555
- if (event.type === "before_agent_start" || event.type === "agent_start") resolve38("started");
49138
+ if (event.type === "before_agent_start" || event.type === "agent_start") resolve39("started");
48556
49139
  });
48557
49140
  });
48558
49141
  const running = run7().finally(() => {
@@ -48635,11 +49218,11 @@ var init_commands = __esm({
48635
49218
  this.early.delete(request.requestId);
48636
49219
  return Promise.resolve(ready);
48637
49220
  }
48638
- return new Promise((resolve38) => {
49221
+ return new Promise((resolve39) => {
48639
49222
  const done = (decision) => {
48640
49223
  this.pending.delete(request.requestId);
48641
49224
  signal.removeEventListener("abort", onAbort);
48642
- resolve38(decision);
49225
+ resolve39(decision);
48643
49226
  };
48644
49227
  const onAbort = () => done(void 0);
48645
49228
  if (signal.aborted) return done(void 0);
@@ -48649,9 +49232,9 @@ var init_commands = __esm({
48649
49232
  }
48650
49233
  /** 返回 false = 当前没有在等这个 requestId(已暂存,稍后被问到时生效)。 */
48651
49234
  respond(requestId, decision) {
48652
- const resolve38 = this.pending.get(requestId);
48653
- if (resolve38 !== void 0) {
48654
- resolve38(decision);
49235
+ const resolve39 = this.pending.get(requestId);
49236
+ if (resolve39 !== void 0) {
49237
+ resolve39(decision);
48655
49238
  return true;
48656
49239
  }
48657
49240
  this.early.set(requestId, decision);
@@ -48659,7 +49242,7 @@ var init_commands = __esm({
48659
49242
  }
48660
49243
  /** 关 stdin / 撤下时:全部弃权(会话按无人作答 deny)。 */
48661
49244
  cancelAll() {
48662
- for (const resolve38 of [...this.pending.values()]) resolve38(void 0);
49245
+ for (const resolve39 of [...this.pending.values()]) resolve39(void 0);
48663
49246
  this.early.clear();
48664
49247
  }
48665
49248
  };
@@ -48818,7 +49401,14 @@ var init_commands = __esm({
48818
49401
  get_tasks: async (_p, ctx) => ({ tasks: [...ctx.tasks?.()?.list() ?? []] }),
48819
49402
  get_agents: async (_p, ctx) => ({ agents: [...ctx.agents?.() ?? []] }),
48820
49403
  // [W6-T2] 轨迹(docs/wave6-plan.md §2.6;分页 / 增量 / 脱敏见 trace/query.ts)
48821
- get_trace: async (p, ctx) => sessionTrace(ctx.session(), p ?? {})
49404
+ get_trace: async (p, ctx) => sessionTrace(ctx.session(), p ?? {}),
49405
+ // [W7-B2] 前台任务转后台:工具调用立即返回、任务继续,完成后照常通知
49406
+ background_task: async (p, ctx) => {
49407
+ const taskId = p?.taskId;
49408
+ if (taskId !== void 0 && typeof taskId !== "string")
49409
+ throw new AmaError("invalid_arguments", msg().print.rpc.badTaskId);
49410
+ return { backgrounded: ctx.session().backgroundTask(taskId) };
49411
+ }
48822
49412
  };
48823
49413
  RPC_COMMAND_TYPES = Object.keys(handlers);
48824
49414
  }
@@ -48937,7 +49527,7 @@ async function runRpcMode(runtime, context, options = {}) {
48937
49527
  const task = respond(command3).finally(() => inflight2.delete(task));
48938
49528
  inflight2.add(task);
48939
49529
  };
48940
- return new Promise((resolve38) => {
49530
+ return new Promise((resolve39) => {
48941
49531
  let finished = false;
48942
49532
  const finish2 = async (code, abort) => {
48943
49533
  if (finished) return;
@@ -48953,7 +49543,7 @@ async function runRpcMode(runtime, context, options = {}) {
48953
49543
  unsubscribe();
48954
49544
  runtime.notifier.set(void 0);
48955
49545
  await writing;
48956
- resolve38(code);
49546
+ resolve39(code);
48957
49547
  };
48958
49548
  const reader = createLineReader(stdin, onLine, () => void finish2(ExitCode.Ok, false));
48959
49549
  const offSignals = onTerminationSignals((code) => {
@@ -49023,8 +49613,8 @@ function startupScreenLevel(runtime) {
49023
49613
  return runtime.config.ui?.quietStartup ?? "normal";
49024
49614
  }
49025
49615
  function trustSourceLabel(source) {
49026
- const labels = msg().cli.startupScreen.trustSource;
49027
- return source === "trust-file" ? labels.trustFile : labels[source];
49616
+ const labels2 = msg().cli.startupScreen.trustSource;
49617
+ return source === "trust-file" ? labels2.trustFile : labels2[source];
49028
49618
  }
49029
49619
  function tildePath(path, home) {
49030
49620
  if (home === void 0 || home === "") return path;
@@ -49092,7 +49682,7 @@ function blockedReason(spec2, value, scope) {
49092
49682
  }
49093
49683
  function settingRows(snapshot2, scope, theme) {
49094
49684
  const s2 = msg().settings;
49095
- const labels = s2.labels;
49685
+ const labels2 = s2.labels;
49096
49686
  const rows = settingsRegistry().map((spec2) => {
49097
49687
  const value = getConfigValue(snapshot2, spec2.key);
49098
49688
  const locked = overriddenFor(value, scope) !== void 0;
@@ -49107,7 +49697,7 @@ function settingRows(snapshot2, scope, theme) {
49107
49697
  const row3 = {
49108
49698
  id: spec2.key,
49109
49699
  group: s2.groups[spec2.group],
49110
- label: labels[spec2.key] ?? spec2.key,
49700
+ label: labels2[spec2.key] ?? spec2.key,
49111
49701
  value: settingValueText(spec2, value, theme),
49112
49702
  tag,
49113
49703
  search: [...spec2.options ?? [], settingDoc(spec2.key) ?? ""].join(" ")
@@ -49342,14 +49932,14 @@ var init_config_panel = __esm({
49342
49932
 
49343
49933
  // src/modes/interactive/confirm-dialog.ts
49344
49934
  function openChoice(host, spec2) {
49345
- return new Promise((resolve38) => {
49935
+ return new Promise((resolve39) => {
49346
49936
  let handle;
49347
49937
  let settled = false;
49348
49938
  const dialog = new ChoiceDialog(spec2, host, (index2) => {
49349
49939
  if (settled) return;
49350
49940
  settled = true;
49351
49941
  handle?.hide();
49352
- resolve38(index2);
49942
+ resolve39(index2);
49353
49943
  });
49354
49944
  handle = host.showOverlay(
49355
49945
  new Box(dialog, {
@@ -49541,6 +50131,12 @@ var init_subagent_view = __esm({
49541
50131
  }
49542
50132
  return state;
49543
50133
  }
50134
+ case "subagent_background": {
50135
+ const state = this.tasks.get(event.taskId);
50136
+ if (state === void 0) return void 0;
50137
+ state.background = true;
50138
+ return state;
50139
+ }
49544
50140
  case "subagent_end": {
49545
50141
  const state = this.tasks.get(event.taskId);
49546
50142
  if (state === void 0) return void 0;
@@ -49613,8 +50209,8 @@ function exitSummaryLines(stats, elapsedMs, theme) {
49613
50209
  return lines2;
49614
50210
  }
49615
50211
  function originLabel(origin) {
49616
- const labels = msg().interactive.view.message.origin;
49617
- return labels[origin] ?? origin;
50212
+ const labels2 = msg().interactive.view.message.origin;
50213
+ return labels2[origin] ?? origin;
49618
50214
  }
49619
50215
  function displayText(text) {
49620
50216
  return text.includes("proposed_plan>") ? text.replace(PLAN_TAG_LINE, "") : text;
@@ -50027,11 +50623,11 @@ function pickerFooter(arrows = "↑↓") {
50027
50623
  return msg().panels.picker.footer(arrows);
50028
50624
  }
50029
50625
  function openPicker(host, spec2) {
50030
- return new Promise((resolve38) => {
50626
+ return new Promise((resolve39) => {
50031
50627
  let handle;
50032
50628
  const close = (item) => {
50033
50629
  handle?.hide();
50034
- resolve38(item);
50630
+ resolve39(item);
50035
50631
  };
50036
50632
  const list5 = new SelectList(spec2.items, {
50037
50633
  theme: host.theme,
@@ -51003,8 +51599,8 @@ var init_line_editor = __esm({
51003
51599
  ask(question, fallback) {
51004
51600
  this.hide();
51005
51601
  this.options.write(question);
51006
- return new Promise((resolve38) => {
51007
- this.question = { resolve: resolve38, fallback };
51602
+ return new Promise((resolve39) => {
51603
+ this.question = { resolve: resolve39, fallback };
51008
51604
  });
51009
51605
  }
51010
51606
  feed(data2) {
@@ -51323,6 +51919,12 @@ var init_tool_view = __esm({
51323
51919
  );
51324
51920
  }
51325
51921
  const result = this.result;
51922
+ const moved = this.backgrounded();
51923
+ if (moved !== void 0) {
51924
+ const m = msg().agents.background;
51925
+ const text2 = moved ? m.moved(formatElapsed(this.elapsedMs)) : m.started;
51926
+ return truncateToWidth(lead + theme.fg("muted", text2), width2);
51927
+ }
51326
51928
  const text = resultSummary(
51327
51929
  {
51328
51930
  name: this.toolName,
@@ -51337,6 +51939,17 @@ var init_tool_view = __esm({
51337
51939
  );
51338
51940
  return truncateToWidth(lead + text, width2);
51339
51941
  }
51942
+ /**
51943
+ * [W7-C] task 已返回、任务仍在后台跑:true = 前台转后台(固定英文结果文本),false = 直接后台;
51944
+ * 其它情况 undefined。
51945
+ */
51946
+ backgrounded() {
51947
+ const result = this.result;
51948
+ if (this.toolName !== "task" || this.state !== "done" || result === void 0) return void 0;
51949
+ const details = result.details;
51950
+ if (details?.status !== "running") return void 0;
51951
+ return /moved to the background/i.test(contentText3(result.content));
51952
+ }
51340
51953
  /** 后台子 Agent:结果摘要下一行跟随状态 ` ↳ t2 explore · 完成 2m · 7 轮 …`。 */
51341
51954
  followLine(width2) {
51342
51955
  const sub = this.subagentState();
@@ -51429,6 +52042,7 @@ var init_tool_view = __esm({
51429
52042
  }
51430
52043
  return out;
51431
52044
  }
52045
+ if (!this.expanded && this.backgrounded() !== void 0) return out;
51432
52046
  const custom = this.customResult(width2);
51433
52047
  if (custom !== void 0) return [...out, ...custom];
51434
52048
  const diff = this.state === "done" ? diffOf(this.result) : void 0;
@@ -51808,7 +52422,7 @@ var init_approval_dialog = __esm({
51808
52422
  }
51809
52423
  ask(request, signal) {
51810
52424
  if (signal.aborted) return Promise.resolve(void 0);
51811
- return new Promise((resolve38) => {
52425
+ return new Promise((resolve39) => {
51812
52426
  let handle;
51813
52427
  let settled = false;
51814
52428
  const settle = (decision) => {
@@ -51819,7 +52433,7 @@ var init_approval_dialog = __esm({
51819
52433
  this.open--;
51820
52434
  this.host.onClose?.();
51821
52435
  this.host.report?.(request, decision ?? "cancelled");
51822
- resolve38(decision);
52436
+ resolve39(decision);
51823
52437
  };
51824
52438
  const onAbort = () => settle(void 0);
51825
52439
  const dialog = new ApprovalDialog(request, this.host, settle);
@@ -51874,6 +52488,10 @@ function backgroundJobText(event) {
51874
52488
  return m.backgroundStopped(event.jobId, short2);
51875
52489
  }
51876
52490
  }
52491
+ function subagentBackgroundText(event, agent) {
52492
+ const m = msg().agents.background;
52493
+ return event.reason === "timeout" ? m.timeout(event.taskId, agent) : m.host(event.taskId, agent);
52494
+ }
51877
52495
  var ImageBudgetNotices, LIMIT_WARNING;
51878
52496
  var init_event_notices = __esm({
51879
52497
  "src/modes/interactive/event-notices.ts"() {
@@ -52249,9 +52867,9 @@ ${bypassRiskLines().map((l) => ` ${l}
52249
52867
  return ed.ask(bypassLineQuestion(), "n").then((answer) => answer === "y");
52250
52868
  }, session.state.permissionMode === BYPASS_MODE);
52251
52869
  runtime.approvals.setUiBroker({
52252
- ask: (request, signal) => new Promise((resolve38) => {
52253
- if (signal.aborted) return resolve38(void 0);
52254
- signal.addEventListener("abort", () => resolve38(void 0), { once: true });
52870
+ ask: (request, signal) => new Promise((resolve39) => {
52871
+ if (signal.aborted) return resolve39(void 0);
52872
+ signal.addEventListener("abort", () => resolve39(void 0), { once: true });
52255
52873
  const preview = previewDisplayLines(request.preview).map((l) => ` ${l}
52256
52874
  `);
52257
52875
  const question = approvalQuestion(
@@ -52260,7 +52878,7 @@ ${bypassRiskLines().map((l) => ` ${l}
52260
52878
  );
52261
52879
  void ed.ask(`
52262
52880
  ${preview.join("")}${question}`, "n").then((answer) => {
52263
- resolve38(answer === "y" ? "allow" : answer === "a" ? "allow_session" : "deny");
52881
+ resolve39(answer === "y" ? "allow" : answer === "a" ? "allow_session" : "deny");
52264
52882
  if (!busy) ed.render();
52265
52883
  });
52266
52884
  })
@@ -52269,7 +52887,7 @@ ${preview.join("")}${question}`, "n").then((answer) => {
52269
52887
  `));
52270
52888
  let busy = false;
52271
52889
  let lastInterrupt = 0;
52272
- return new Promise((resolve38) => {
52890
+ return new Promise((resolve39) => {
52273
52891
  let finished = false;
52274
52892
  const finish2 = (code) => {
52275
52893
  if (finished) return;
@@ -52283,7 +52901,7 @@ ${preview.join("")}${question}`, "n").then((answer) => {
52283
52901
  runtime.notifier.set(void 0);
52284
52902
  void session.abort().catch(() => void 0).finally(() => {
52285
52903
  unsubscribe();
52286
- resolve38(code);
52904
+ resolve39(code);
52287
52905
  });
52288
52906
  };
52289
52907
  const run7 = (text) => {
@@ -52315,6 +52933,10 @@ ${msg().interactive.line.ctrlCAgain}
52315
52933
  continue;
52316
52934
  }
52317
52935
  if (busy) {
52936
+ if (/^\/tasks\s+bg(\s+\S+)?\s*$/.test(action.text.trim())) {
52937
+ void handle(action.text.trim());
52938
+ continue;
52939
+ }
52318
52940
  if (action.text.trim() !== "") {
52319
52941
  void session.steer(action.text).catch(() => void 0);
52320
52942
  out(`${msg().interactive.line.steer(action.text)}
@@ -52342,7 +52964,7 @@ ${msg().interactive.line.ctrlCAgain}
52342
52964
  });
52343
52965
  }
52344
52966
  function runPiped(stdin, context, handle, session, unsubscribe, failed4) {
52345
- return new Promise((resolve38) => {
52967
+ return new Promise((resolve39) => {
52346
52968
  let chain = Promise.resolve(void 0);
52347
52969
  let finished = false;
52348
52970
  const finish2 = (code) => {
@@ -52352,7 +52974,7 @@ function runPiped(stdin, context, handle, session, unsubscribe, failed4) {
52352
52974
  offSignals();
52353
52975
  stdin.pause?.();
52354
52976
  unsubscribe();
52355
- resolve38(code);
52977
+ resolve39(code);
52356
52978
  };
52357
52979
  const enqueue = (line2) => {
52358
52980
  chain = chain.then((state) => state === "exit" ? state : handle(line2));
@@ -52573,7 +53195,11 @@ var init_agent_bar = __esm({
52573
53195
  const hidden = rows.length - shown2.length;
52574
53196
  const tail = [];
52575
53197
  if (hidden > 0) tail.push(m.bar.more(hidden));
52576
- if (focused) tail.push(m.bar.keys(t.glyphs.arrowUp, t.glyphs.arrowDown));
53198
+ if (focused) {
53199
+ const [up, down] = [t.glyphs.arrowUp, t.glyphs.arrowDown];
53200
+ const full = [...tail, m.bar.keysFull(up, down)].join(" · ");
53201
+ tail.push(visibleWidth(full) <= width2 ? m.bar.keysFull(up, down) : m.bar.keys(up, down));
53202
+ }
52577
53203
  if (tail.length > 0)
52578
53204
  out.push(truncateToWidth(t.fg("dim", tail.join(" · ")), width2, t.glyphs.ellipsis));
52579
53205
  return out;
@@ -52584,7 +53210,9 @@ var init_agent_bar = __esm({
52584
53210
  const agent = agentLabel(row3);
52585
53211
  const lead = focused ? (selected ? t.fg("accent", g.prompt) : " ") + " " : "";
52586
53212
  const marker = t.fg(statusColor(row3.status), g.tool);
52587
- const id = selected ? t.bold(row3.taskId) : row3.taskId;
53213
+ const approval = row3.status === "approval";
53214
+ const plainId = selected || approval ? t.bold(row3.taskId) : row3.taskId;
53215
+ const id = approval ? t.fg("warning", plainId) : plainId;
52588
53216
  const description = row3.description.replace(/\s+/g, " ").trim();
52589
53217
  const text = `${lead}${marker} ${id} ${t.fg("tool", agent)}` + t.fg("dim", " · ") + t.fg(row3.status === "approval" ? "warning" : "muted", rowFacts(row3)) + (description === "" ? "" : ` ${t.fg("dim", description)}`);
52590
53218
  return truncateToWidth(text, width2, g.ellipsis);
@@ -52782,6 +53410,7 @@ var init_agent_view = __esm({
52782
53410
  init_session_report();
52783
53411
  init_agent_bar();
52784
53412
  init_agent_transcript();
53413
+ init_task_background();
52785
53414
  AgentView = class {
52786
53415
  constructor(taskId, deps) {
52787
53416
  this.taskId = taskId;
@@ -52897,7 +53526,10 @@ var init_agent_view = __esm({
52897
53526
  const taskId = this.taskId;
52898
53527
  if (trimmed.startsWith("/")) {
52899
53528
  const stop = /^\/tasks\s+stop(?:\s+(\S+))?\s*$/.exec(trimmed);
52900
- if (stop === null) this.flash = m.commandsHere;
53529
+ const bg = /^\/tasks\s+bg(?:\s+(\S+))?\s*$/.exec(trimmed);
53530
+ if (bg !== null && this.deps.background !== void 0)
53531
+ this.flash = backgroundedText2(this.deps.background(bg[1] ?? taskId));
53532
+ else if (stop === null) this.flash = m.commandsHere;
52901
53533
  else {
52902
53534
  const target = stop[1] ?? taskId;
52903
53535
  try {
@@ -52926,6 +53558,13 @@ var init_agent_view = __esm({
52926
53558
  const empty = this.editor.isEmpty();
52927
53559
  const id = parseKey(data2)?.id;
52928
53560
  this.flash = void 0;
53561
+ if (keys.matches(data2, "app.tasks.background") && this.deps.background !== void 0) {
53562
+ const moved = this.deps.background(this.taskId);
53563
+ if (moved.length > 0) {
53564
+ this.flash = backgroundedText2(moved);
53565
+ return;
53566
+ }
53567
+ }
52929
53568
  if (keys.matches(data2, "tui.select.cancel") && !this.editor.isCompletionOpen) {
52930
53569
  if (!empty) this.editor.clear();
52931
53570
  else this.deps.close();
@@ -53364,7 +54003,7 @@ function mainOptions(ellipsis) {
53364
54003
  return [m.optionApprove, m.optionApproveFresh, m.optionRevise(ellipsis), m.optionReject];
53365
54004
  }
53366
54005
  function openPlanDialog(host, input2) {
53367
- return new Promise((resolve38) => {
54006
+ return new Promise((resolve39) => {
53368
54007
  let handle;
53369
54008
  let settled = false;
53370
54009
  const dialog = new PlanDialog(input2, host, (choice3) => {
@@ -53372,7 +54011,7 @@ function openPlanDialog(host, input2) {
53372
54011
  settled = true;
53373
54012
  handle?.hide();
53374
54013
  host.onClose?.();
53375
- resolve38(choice3);
54014
+ resolve39(choice3);
53376
54015
  });
53377
54016
  host.onOpen?.();
53378
54017
  handle = host.showOverlay(
@@ -53579,10 +54218,10 @@ var init_plan_dialog = __esm({
53579
54218
  if (hidden > 0) out.push(theme.fg("dim", m.moreSteps(theme.glyphs.ellipsis, hidden)));
53580
54219
  return out;
53581
54220
  }
53582
- optionLines(labels, selected, width2) {
54221
+ optionLines(labels2, selected, width2) {
53583
54222
  const { theme } = this.host;
53584
54223
  const g = theme.glyphs;
53585
- return labels.map((label2, i) => {
54224
+ return labels2.map((label2, i) => {
53586
54225
  const on = i === selected;
53587
54226
  const text = `${on ? g.prompt : " "} ${i + 1}. ${label2}`;
53588
54227
  const line2 = padToWidth(
@@ -53754,6 +54393,7 @@ var init_agent_ui = __esm({
53754
54393
  init_external_editor();
53755
54394
  init_plan_flow();
53756
54395
  init_subagent_view();
54396
+ init_task_background();
53757
54397
  init_tasks_report();
53758
54398
  TASK_NOTICE = /^\[task (t\d+)\] ([\s\S]*)$/;
53759
54399
  AgentUi = class {
@@ -53796,7 +54436,7 @@ var init_agent_ui = __esm({
53796
54436
  /** [W6-A] key-dispatch.ts 的 Agent 栏入口。 */
53797
54437
  keys = {
53798
54438
  handleKey: (data2) => this.barKey(data2),
53799
- focus: (data2) => this.focusFromKey(data2)
54439
+ focus: () => this.focusFromKey()
53800
54440
  };
53801
54441
  view;
53802
54442
  /** 等审批的请求 → 任务。 */
@@ -53804,6 +54444,10 @@ var init_agent_ui = __esm({
53804
54444
  restoreLog;
53805
54445
  /** 后台子 Agent 运行中:每秒重画跟随行的耗时(主会话空闲时 Loader 不转)。 */
53806
54446
  ticker;
54447
+ /** [W7-C] 后台任务的审批停靠(approval-ui.ts 建好后接上)。 */
54448
+ dock;
54449
+ /** [W7-C] 栏内 `x` 的双击确认。 */
54450
+ stopArm = new StopArm();
53807
54451
  keybindings() {
53808
54452
  return this.deps.dialog.keybindings ?? defaultKeybindings;
53809
54453
  }
@@ -53820,6 +54464,7 @@ var init_agent_ui = __esm({
53820
54464
  if (viewing !== void 0) this.deps.notice("info", msg().agents.view.removed(viewing));
53821
54465
  this.bar.reset();
53822
54466
  this.approvals.clear();
54467
+ this.dock?.reset();
53823
54468
  this.deps.subagents.clear();
53824
54469
  this.stopTicker();
53825
54470
  this.plan.attach(session);
@@ -53875,9 +54520,12 @@ var init_agent_ui = __esm({
53875
54520
  switch (event.type) {
53876
54521
  case "subagent_start":
53877
54522
  case "subagent_update":
54523
+ case "subagent_background":
53878
54524
  case "subagent_end": {
53879
54525
  const state = this.deps.subagents.onEvent(event);
53880
54526
  if (state === void 0) return true;
54527
+ if (event.type === "subagent_background" && event.reason !== "user")
54528
+ this.deps.notice("info", subagentBackgroundText(event, state.agent));
53881
54529
  this.deps.tools().get(state.parentToolCallId)?.refresh();
53882
54530
  if (event.type === "subagent_start" && this.view?.component.task === event.taskId)
53883
54531
  this.view.component.attach();
@@ -53898,11 +54546,18 @@ var init_agent_ui = __esm({
53898
54546
  case "permission_request": {
53899
54547
  const taskId = event.context?.taskId;
53900
54548
  if (taskId !== void 0) this.approvals.set(event.requestId, taskId);
54549
+ this.dock?.observe(event);
53901
54550
  return false;
53902
54551
  }
53903
54552
  case "permission_resolved":
53904
54553
  this.approvals.delete(event.requestId);
54554
+ this.dock?.observe(event);
54555
+ return false;
54556
+ case "agent_settled": {
54557
+ const dock = this.dock;
54558
+ if (dock !== void 0 && dock.size > 0) setTimeout(() => dock.poke(), 0);
53905
54559
  return false;
54560
+ }
53906
54561
  default:
53907
54562
  return false;
53908
54563
  }
@@ -53947,19 +54602,31 @@ var init_agent_ui = __esm({
53947
54602
  }
53948
54603
  this.deps.render();
53949
54604
  }
53950
- /** `app.agents.focus`(输入为空):`↓` 只在栏可见时进入,其它键(`Ctrl+B`)有任务即可。 */
53951
- focusFromKey(data2) {
53952
- if (this.view !== void 0 || !this.barEnabled()) return false;
53953
- if (parseKey(data2)?.id === "down" && !this.bar.visible) return false;
53954
- if (!this.bar.focus()) return false;
54605
+ /**
54606
+ * `app.agents.focus`:输入为空且有任务即进栏(与 `/tasks` 一致,不要求栏可见)[W7-A];
54607
+ * 否则返回落空原因——有字(有任务时)、栏关闭(有任务时)、没有任务(栏开着时)。
54608
+ */
54609
+ focusFromKey() {
54610
+ if (this.view !== void 0) return false;
54611
+ const hasTasks = this.bar.all().length > 0;
54612
+ if (!this.barEnabled()) return hasTasks && this.deps.editor.isEmpty() ? "disabled" : false;
54613
+ if (!this.deps.editor.isEmpty()) return hasTasks ? "busy-input" : false;
54614
+ if (!this.bar.focus()) return "empty";
53955
54615
  this.deps.render();
53956
54616
  return true;
53957
54617
  }
54618
+ /** [W7-A] 运行提示行要不要带「↓ Agent 栏」:栏开着、有该显示的任务、没在栏里或视图里。 */
54619
+ get reachable() {
54620
+ return this.view === void 0 && !this.bar.focused && this.bar.visible;
54621
+ }
53958
54622
  /** 栏聚焦时的按键;不在栏里返回 false。 */
53959
54623
  barKey(data2) {
53960
54624
  if (!this.bar.focused) return false;
53961
54625
  const keys = this.keybindings();
53962
- if (keys.matches(data2, "tui.select.up")) {
54626
+ if (data2 !== "x") this.stopArm.reset();
54627
+ if (data2 === "b" || keys.matches(data2, "app.tasks.background")) this.backgroundSelected();
54628
+ else if (data2 === "x") void this.stopSelected();
54629
+ else if (keys.matches(data2, "tui.select.up")) {
53963
54630
  if (!this.bar.move(-1)) this.bar.blur();
53964
54631
  } else if (keys.matches(data2, "tui.select.down")) this.bar.move(1);
53965
54632
  else if (keys.matches(data2, "tui.editor.submit")) {
@@ -53975,6 +54642,33 @@ var init_agent_ui = __esm({
53975
54642
  this.deps.render();
53976
54643
  return true;
53977
54644
  }
54645
+ /** [W7-C] 栏内 `b` / `Ctrl+B`:转后台选中的任务(只对前台运行中的有效)。 */
54646
+ backgroundSelected() {
54647
+ const taskId = this.bar.selected();
54648
+ if (taskId === void 0) return;
54649
+ const moved = this.deps.session().backgroundTask(taskId, "user");
54650
+ this.deps.hint(
54651
+ moved.length > 0 ? backgroundedText2(moved) : msg().agents.background.notForeground(taskId)
54652
+ );
54653
+ }
54654
+ /** [W7-C] 栏内 `x`:第一次提示「再按 x 停止」,1.5 s 内再按停止。 */
54655
+ async stopSelected() {
54656
+ const taskId = this.bar.selected();
54657
+ if (taskId === void 0) return;
54658
+ const sessionId = this.deps.session().state.sessionId;
54659
+ if (taskRegistryView(sessionId)?.get(taskId)?.status !== "running") {
54660
+ this.stopArm.reset();
54661
+ this.deps.hint(msg().agents.stop.ended(taskId));
54662
+ return;
54663
+ }
54664
+ if (this.stopArm.press(taskId, this.deps.now()) === "arm") {
54665
+ this.deps.hint(msg().agents.stop.confirm(taskId));
54666
+ return;
54667
+ }
54668
+ this.deps.hint(msg().agents.view.stopped(taskId));
54669
+ await stopTask(sessionId, taskId);
54670
+ this.deps.render();
54671
+ }
53978
54672
  /** `/tasks <id>` 与栏里 Enter:打开子 Agent 视图。 */
53979
54673
  openView(taskId) {
53980
54674
  const sessionId = this.deps.session().state.sessionId;
@@ -53996,6 +54690,7 @@ var init_agent_ui = __esm({
53996
54690
  siblings: () => this.bar.all().map((row3) => row3.taskId),
53997
54691
  close: () => this.closeView(),
53998
54692
  stop: (id) => stopTask(this.deps.session().state.sessionId, id),
54693
+ background: (id) => this.deps.session().backgroundTask(id, "user"),
53999
54694
  messages: {
54000
54695
  ...ui.showThinking !== void 0 ? { showThinking: ui.showThinking } : {},
54001
54696
  ...ui.markdown !== void 0 ? { markdown: ui.markdown } : {},
@@ -54005,6 +54700,7 @@ var init_agent_ui = __esm({
54005
54700
  this.bar.blur();
54006
54701
  const handle = this.deps.tui.showOverlay(component, { anchor: "bottom" });
54007
54702
  this.view = { component, handle };
54703
+ this.dock?.poke();
54008
54704
  this.deps.render();
54009
54705
  }
54010
54706
  get viewing() {
@@ -54080,6 +54776,169 @@ var init_agent_ui = __esm({
54080
54776
  }
54081
54777
  });
54082
54778
 
54779
+ // src/modes/interactive/approval-dock.ts
54780
+ var DOCK_POLL_MS, ApprovalDock;
54781
+ var init_approval_dock = __esm({
54782
+ "src/modes/interactive/approval-dock.ts"() {
54783
+ "use strict";
54784
+ DOCK_POLL_MS = 250;
54785
+ ApprovalDock = class {
54786
+ constructor(inner, host) {
54787
+ this.inner = inner;
54788
+ this.host = host;
54789
+ }
54790
+ inner;
54791
+ host;
54792
+ docked = [];
54793
+ /** 已发出、还没答复的不可停靠请求(主会话 / 前台任务)。 */
54794
+ others = /* @__PURE__ */ new Set();
54795
+ timer;
54796
+ ask(request, signal) {
54797
+ const taskId = request.context?.taskId;
54798
+ if (taskId === void 0 || signal.aborted || !this.host.dockable(taskId) || this.others.size > 0 || this.host.ready(taskId))
54799
+ return this.inner.ask(request, signal);
54800
+ return new Promise((resolve39) => {
54801
+ const entry = {
54802
+ taskId,
54803
+ request,
54804
+ signal,
54805
+ resolve: resolve39,
54806
+ onAbort: () => {
54807
+ this.remove(entry);
54808
+ resolve39(void 0);
54809
+ }
54810
+ };
54811
+ signal.addEventListener("abort", entry.onAbort, { once: true });
54812
+ this.docked.push(entry);
54813
+ this.startPolling();
54814
+ this.host.changed();
54815
+ });
54816
+ }
54817
+ /** 停靠中的任务。 */
54818
+ tasks() {
54819
+ return new Set(this.docked.map((entry) => entry.taskId));
54820
+ }
54821
+ get size() {
54822
+ return this.docked.length;
54823
+ }
54824
+ /** 会话事件:记下不可停靠的请求;停靠期间来了这种请求就放出停靠的(见文件头)。 */
54825
+ observe(event) {
54826
+ if (event.type === "permission_request") {
54827
+ const taskId = event.context?.taskId;
54828
+ if (taskId !== void 0 && this.host.dockable(taskId)) return;
54829
+ this.others.add(event.requestId);
54830
+ this.poke();
54831
+ } else if (event.type === "permission_resolved") this.others.delete(event.requestId);
54832
+ }
54833
+ /** 对一次弹出条件(定时器、视图打开、回合结束)。 */
54834
+ poke() {
54835
+ for (const entry of [...this.docked])
54836
+ if (this.others.size > 0 || this.host.ready(entry.taskId)) this.release(entry);
54837
+ }
54838
+ /** 换会话:旧会话的未答复请求不再有 `permission_resolved` 到这里。 */
54839
+ reset() {
54840
+ this.others.clear();
54841
+ }
54842
+ /** 退出:停靠的请求按 undefined 交回链(链按拒绝处理)。 */
54843
+ dispose() {
54844
+ for (const entry of [...this.docked]) entry.onAbort();
54845
+ this.others.clear();
54846
+ }
54847
+ release(entry) {
54848
+ this.remove(entry);
54849
+ entry.resolve(this.inner.ask(entry.request, entry.signal));
54850
+ }
54851
+ remove(entry) {
54852
+ const at = this.docked.indexOf(entry);
54853
+ if (at < 0) return;
54854
+ this.docked.splice(at, 1);
54855
+ entry.signal.removeEventListener("abort", entry.onAbort);
54856
+ if (this.docked.length === 0) this.stopPolling();
54857
+ this.host.changed();
54858
+ }
54859
+ startPolling() {
54860
+ if (this.timer !== void 0) return;
54861
+ this.timer = setInterval(() => this.poke(), this.host.pollMs ?? DOCK_POLL_MS);
54862
+ this.timer.unref?.();
54863
+ }
54864
+ stopPolling() {
54865
+ if (this.timer !== void 0) clearInterval(this.timer);
54866
+ this.timer = void 0;
54867
+ }
54868
+ };
54869
+ }
54870
+ });
54871
+
54872
+ // src/modes/interactive/approval-ui.ts
54873
+ function approvalOverlayHooks(deps) {
54874
+ const { tui, editor, indicator } = deps;
54875
+ return {
54876
+ showOverlay: (component) => tui.showOverlay(component, { anchor: "bottom" }),
54877
+ onOpen: () => {
54878
+ editor.disableSubmit = true;
54879
+ deps.tools().setAwaiting(true);
54880
+ indicator.setApproval(true);
54881
+ },
54882
+ onClose: () => {
54883
+ editor.disableSubmit = false;
54884
+ deps.tools().setAwaiting(false);
54885
+ indicator.setApproval(false);
54886
+ }
54887
+ };
54888
+ }
54889
+ function createApprovalUi(deps) {
54890
+ const { theme, keys, tui, editor, indicator, agentUi, hooks, notice } = deps;
54891
+ const taskInfo = (taskId) => taskRegistryView(deps.session().state.sessionId)?.get(taskId);
54892
+ const taskAgent = (taskId) => taskInfo(taskId)?.agent;
54893
+ const dialog = new ApprovalDialogBroker({
54894
+ theme,
54895
+ keybindings: keys,
54896
+ cwd: deps.session().state.cwd,
54897
+ permissionMode: () => deps.session().state.permissionMode,
54898
+ taskAgent,
54899
+ externalRunner: (agent) => agentUi.merge.externalRunner(agent),
54900
+ ...hooks,
54901
+ report: (request, outcome) => {
54902
+ if (outcome === "deny" || outcome === "cancelled") {
54903
+ const text = approvalOutcomeText(request, outcome, taskAgent);
54904
+ notice(outcome === "deny" ? "info" : "warn", text);
54905
+ }
54906
+ }
54907
+ });
54908
+ const dock = new ApprovalDock(dialog, {
54909
+ dockable: (taskId) => {
54910
+ const info2 = taskInfo(taskId);
54911
+ return info2?.status === "running" && info2.background === true;
54912
+ },
54913
+ ready: (taskId) => agentUi.viewing === taskId || !tui.hasOverlay && !indicator.busy && editor.isEmpty(),
54914
+ changed: () => {
54915
+ indicator.sync();
54916
+ deps.render();
54917
+ }
54918
+ });
54919
+ agentUi.dock = dock;
54920
+ indicator.docked = () => dock.size > 0;
54921
+ const broker = mergingBroker(
54922
+ dock,
54923
+ agentUi.merge,
54924
+ (request) => notice(
54925
+ "info",
54926
+ msg().interactive.app.followed(approvalOutcomeText(request, "allow", taskAgent))
54927
+ )
54928
+ );
54929
+ return { broker, dock };
54930
+ }
54931
+ var init_approval_ui = __esm({
54932
+ "src/modes/interactive/approval-ui.ts"() {
54933
+ "use strict";
54934
+ init_subagent_registry();
54935
+ init_i18n();
54936
+ init_approval_dialog();
54937
+ init_approval_dock();
54938
+ init_approval_merge();
54939
+ }
54940
+ });
54941
+
54083
54942
  // src/modes/interactive/commands.ts
54084
54943
  function command2(name, key) {
54085
54944
  return {
@@ -54355,7 +55214,7 @@ var init_commands2 = __esm({
54355
55214
  await ui.agentBar();
54356
55215
  return true;
54357
55216
  }
54358
- if (parts.length === 1 && parts[0] !== "stop" && ui.agentView !== void 0) {
55217
+ if (parts.length === 1 && parts[0] !== "stop" && parts[0] !== "bg" && ui.agentView !== void 0) {
54359
55218
  await ui.agentView(parts[0]);
54360
55219
  return true;
54361
55220
  }
@@ -54552,6 +55411,28 @@ function parseNumstat(text) {
54552
55411
  }
54553
55412
  return { insertions, deletions };
54554
55413
  }
55414
+ function parseLeftRight(text) {
55415
+ const match = /^\s*(\d+)\s+(\d+)\s*$/.exec(text);
55416
+ return match === null ? void 0 : { behind: Number(match[1]), ahead: Number(match[2]) };
55417
+ }
55418
+ async function hasUpstream(cwd, branch) {
55419
+ const gitDir = await findGitDir(cwd);
55420
+ if (gitDir === void 0) return false;
55421
+ const common = (await (0, import_promises24.readFile)((0, import_node_path94.join)(gitDir, "commondir"), "utf8").catch(() => void 0))?.trim();
55422
+ const dir = common === void 0 ? gitDir : (0, import_node_path94.isAbsolute)(common) ? common : (0, import_node_path94.resolve)(gitDir, common);
55423
+ const text = await (0, import_promises24.readFile)((0, import_node_path94.join)(dir, "config"), "utf8").catch(() => "");
55424
+ let inBranch = false;
55425
+ for (const raw of text.split("\n")) {
55426
+ const line2 = raw.trim();
55427
+ const section = /^\[\s*([^\]\s"]+)(?:\s+"((?:[^"\\]|\\.)*)")?\s*\]/.exec(line2);
55428
+ if (section !== null) {
55429
+ inBranch = section[1]?.toLowerCase() === "branch" && section[2] === branch;
55430
+ continue;
55431
+ }
55432
+ if (inBranch && /^merge\s*=\s*\S/i.test(line2)) return true;
55433
+ }
55434
+ return false;
55435
+ }
54555
55436
  async function readHead2(cwd) {
54556
55437
  const head = await readGitHead(cwd);
54557
55438
  if (head !== void 0) {
@@ -54576,7 +55457,7 @@ var init_info = __esm({
54576
55457
  init_git_head();
54577
55458
  GIT_NUMSTAT_INTERVAL_MS = 1e4;
54578
55459
  GIT_NUMSTAT_TIMEOUT_MS = 2e3;
54579
- sameInfo = (a, b) => a?.branch === b?.branch && a?.shortHead === b?.shortHead && a?.insertions === b?.insertions && a?.deletions === b?.deletions && a === void 0 === (b === void 0);
55460
+ sameInfo = (a, b) => a?.branch === b?.branch && a?.shortHead === b?.shortHead && a?.insertions === b?.insertions && a?.deletions === b?.deletions && a?.ahead === b?.ahead && a?.behind === b?.behind && a === void 0 === (b === void 0);
54580
55461
  GitInfoWatcher = class {
54581
55462
  constructor(cwd, deps = {}) {
54582
55463
  this.cwd = cwd;
@@ -54586,6 +55467,7 @@ var init_info = __esm({
54586
55467
  this.timeoutMs = deps.timeoutMs ?? GIT_NUMSTAT_TIMEOUT_MS;
54587
55468
  this.env = deps.env ?? process.env;
54588
55469
  this.numstatDisabled = this.env["AMA_STATUS_GIT"] === "0";
55470
+ this.upstreamDisabled = this.numstatDisabled;
54589
55471
  }
54590
55472
  cwd;
54591
55473
  info;
@@ -54602,6 +55484,8 @@ var init_info = __esm({
54602
55484
  headAgain = false;
54603
55485
  disposed = false;
54604
55486
  diff;
55487
+ upstream;
55488
+ upstreamDisabled;
54605
55489
  current() {
54606
55490
  return this.info;
54607
55491
  }
@@ -54639,7 +55523,8 @@ var init_info = __esm({
54639
55523
  update(head) {
54640
55524
  const next = head === void 0 ? void 0 : {
54641
55525
  ...head,
54642
- ...this.diff !== void 0 && head.shortHead !== void 0 ? this.diff : {}
55526
+ ...this.diff !== void 0 && head.shortHead !== void 0 ? this.diff : {},
55527
+ ...this.upstream !== void 0 && head.branch !== void 0 ? this.upstream : {}
54643
55528
  };
54644
55529
  if (sameInfo(this.info, next)) return;
54645
55530
  this.info = next;
@@ -54652,42 +55537,60 @@ var init_info = __esm({
54652
55537
  if (at - this.lastNumstatAt < this.minIntervalMs) return Promise.resolve();
54653
55538
  this.lastNumstatAt = at;
54654
55539
  this.numstatRunning = true;
54655
- return this.numstat().then((diff) => {
54656
- this.numstatRunning = false;
55540
+ return this.numstat().then(async (diff) => {
54657
55541
  if (this.disposed) return;
54658
55542
  if (diff === void 0) {
54659
55543
  this.numstatDisabled = true;
54660
55544
  this.diff = void 0;
54661
55545
  } else this.diff = diff;
55546
+ await this.leftRight();
55547
+ this.numstatRunning = false;
55548
+ if (this.disposed) return;
54662
55549
  const info2 = this.info;
54663
55550
  if (info2 === void 0) return;
54664
- const { insertions: _i, deletions: _d, ...head } = info2;
55551
+ const { insertions: _i, deletions: _d, ahead: _a, behind: _b, ...head } = info2;
54665
55552
  this.update(head);
54666
55553
  });
54667
55554
  }
54668
- numstat() {
54669
- return new Promise((resolve38) => {
55555
+ /** 领先 / 落后上游:只在配置里有上游时起进程;超时本会话停用,非零退出只省略这一次。 */
55556
+ async leftRight() {
55557
+ const branch = this.info?.branch;
55558
+ if (this.upstreamDisabled || branch === void 0 || !await hasUpstream(this.cwd, branch)) {
55559
+ this.upstream = void 0;
55560
+ return;
55561
+ }
55562
+ const result = await this.run(["rev-list", "--left-right", "--count", "@{upstream}...HEAD"]);
55563
+ if (result === "timeout") this.upstreamDisabled = true;
55564
+ this.upstream = typeof result === "object" && result.code === 0 ? parseLeftRight(result.out) : void 0;
55565
+ }
55566
+ async numstat() {
55567
+ const result = await this.run(["diff", "--numstat", "HEAD"]);
55568
+ return typeof result === "object" && result.code === 0 ? parseNumstat(result.out) : void 0;
55569
+ }
55570
+ /** 后台起一次 git;起不来为 undefined,超时杀掉并返回 `"timeout"`。 */
55571
+ run(args) {
55572
+ return new Promise((resolve39) => {
54670
55573
  let settled = false;
54671
55574
  let timer;
54672
55575
  const done = (value) => {
54673
55576
  if (settled) return;
54674
55577
  settled = true;
54675
55578
  clearTimeout(timer);
54676
- resolve38(value);
55579
+ resolve39(value);
54677
55580
  };
54678
55581
  let child;
54679
55582
  try {
54680
- child = this.spawn("git", ["diff", "--numstat", "HEAD"], {
55583
+ child = this.spawn("git", args, {
54681
55584
  cwd: this.cwd,
54682
55585
  env: { ...this.env, GIT_OPTIONAL_LOCKS: "0" }
54683
55586
  });
54684
55587
  } catch {
54685
- resolve38(void 0);
55588
+ resolve39(void 0);
54686
55589
  return;
54687
55590
  }
54688
55591
  timer = setTimeout(() => {
54689
55592
  child.kill("SIGKILL");
54690
- done(void 0);
55593
+ done("timeout");
54691
55594
  }, this.timeoutMs);
54692
55595
  timer.unref?.();
54693
55596
  let out = "";
@@ -54696,7 +55599,7 @@ var init_info = __esm({
54696
55599
  out += String(chunk);
54697
55600
  });
54698
55601
  child.on("error", () => done(void 0));
54699
- child.on("close", (code) => done(code === 0 ? parseNumstat(out) : void 0));
55602
+ child.on("close", (code) => done({ code, out }));
54700
55603
  });
54701
55604
  }
54702
55605
  };
@@ -54755,32 +55658,40 @@ var init_status_line = __esm({
54755
55658
  const right = (text, priority) => {
54756
55659
  if (text !== void 0) parts.push({ text, priority, zone: "right" });
54757
55660
  };
55661
+ const blue = (text) => theme.fg("accent", text);
55662
+ const purple = (text) => theme.fg("tool", text);
55663
+ const amountText = (tokens, ms) => `${blue(formatTokens3(tokens))}${dim(" tok / ")}${blue(formatSeconds2(ms))}`;
54758
55664
  let tps;
54759
55665
  let amount;
54760
55666
  if (live2 !== void 0) {
54761
55667
  tps = live2.tps;
54762
- amount = `${formatTokens3(live2.outputTokens)} tok / ${formatSeconds2(live2.elapsedMs)}`;
55668
+ amount = amountText(live2.outputTokens, live2.elapsedMs);
54763
55669
  } else if (last?.doneAt !== void 0) {
54764
55670
  tps = last.tps;
54765
55671
  if (last.outputTokens !== void 0 && last.firstTokenAt !== void 0) {
54766
- amount = `${formatTokens3(last.outputTokens)} tok / ${formatSeconds2(last.doneAt - last.firstTokenAt)}`;
55672
+ amount = amountText(last.outputTokens, last.doneAt - last.firstTokenAt);
54767
55673
  }
54768
55674
  }
54769
55675
  const label2 = live2 !== void 0 ? theme.fg("accent", "tps:") : dim("tps:");
54770
55676
  left(
54771
- `${label2} ${dim(tps === void 0 ? "—" : `${formatRate(tps)} tok/s`)}`,
55677
+ `${label2} ${tps === void 0 ? dim("—") : `${purple(formatRate(tps))}${dim(" tok/s")}`}`,
54772
55678
  void 0,
54773
55679
  RESERVE.tps
54774
55680
  );
54775
55681
  if (amount !== void 0) {
54776
- left(dim(amount), PRIORITY.amount, RESERVE.amount, { lead: ` ${theme.glyphs.dot} ` });
55682
+ left(amount, PRIORITY.amount, RESERVE.amount, { lead: ` ${theme.glyphs.dot} ` });
54777
55683
  }
54778
55684
  const stat16 = { group: "stats", lead: " " };
54779
55685
  if (telemetry?.avgTps !== void 0) {
54780
- left(dim(`avg ${formatRate(telemetry.avgTps)}`), PRIORITY.avg, RESERVE.avg, stat16);
55686
+ left(`${dim("avg ")}${blue(formatRate(telemetry.avgTps))}`, PRIORITY.avg, RESERVE.avg, stat16);
54781
55687
  }
54782
55688
  if (last?.ttftMs !== void 0) {
54783
- left(dim(`ttft ${formatSeconds2(last.ttftMs)}`), PRIORITY.ttft, RESERVE.ttft, stat16);
55689
+ left(
55690
+ `${dim("ttft ")}${purple(formatSeconds2(last.ttftMs))}`,
55691
+ PRIORITY.ttft,
55692
+ RESERVE.ttft,
55693
+ stat16
55694
+ );
54784
55695
  }
54785
55696
  const usage = usageItems(stats, this.bar.queued(), this.source, theme);
54786
55697
  right(usage.tokens, PRIORITY.tokens);
@@ -54822,7 +55733,7 @@ function statusLineText(mode) {
54822
55733
  const m = msg().interactive.statusLine;
54823
55734
  return mode === "full" ? m.full : m.compact;
54824
55735
  }
54825
- var import_node_path95, WRITE_TOOLS3, HintLine, StatusArea;
55736
+ var import_node_path95, WRITE_TOOLS3, QUOTA_TICK_MS, HintLine, StatusArea;
54826
55737
  var init_status_area = __esm({
54827
55738
  "src/modes/interactive/status-area.ts"() {
54828
55739
  "use strict";
@@ -54837,7 +55748,9 @@ var init_status_area = __esm({
54837
55748
  init_tui2();
54838
55749
  init_status_bar();
54839
55750
  init_status_line();
55751
+ init_status_quota();
54840
55752
  WRITE_TOOLS3 = /* @__PURE__ */ new Set(["write", "edit", "bash", "task"]);
55753
+ QUOTA_TICK_MS = 6e4;
54841
55754
  HintLine = class {
54842
55755
  text = "";
54843
55756
  setText(text) {
@@ -54874,16 +55787,26 @@ var init_status_area = __esm({
54874
55787
  // S2:bash 沙箱生效(配置 + 本机能力,与 compose 同一结论)且 bash 工具活动
54875
55788
  bashSandbox: () => (this.bashSandbox ??= resolveBashSandbox(runtime.config.sandbox, {
54876
55789
  status: osSandboxStatus()
54877
- }).active) && deps.session().getTools().some((tool) => tool.name === "bash")
55790
+ }).active) && deps.session().getTools().some((tool) => tool.name === "bash"),
55791
+ quota: () => this.quotaView()
54878
55792
  };
54879
55793
  this.bar = new StatusBar(source, deps.theme);
54880
55794
  this.rate = new StatusLine(this.bar, source, deps.theme);
55795
+ this.quota = new QuotaLine(
55796
+ { layout: () => this.mode, quota: () => this.quotaView(), now: () => deps.now() },
55797
+ deps.theme
55798
+ );
54881
55799
  this.watchGit();
55800
+ this.syncTicker();
54882
55801
  }
54883
55802
  deps;
54884
55803
  hint = new HintLine();
54885
55804
  bar;
54886
55805
  rate;
55806
+ /** [W6] 订阅配额行(full 第三行)。 */
55807
+ quota;
55808
+ lastQuota;
55809
+ ticker;
54887
55810
  mode;
54888
55811
  startedAt;
54889
55812
  git;
@@ -54893,6 +55816,38 @@ var init_status_area = __esm({
54893
55816
  /** [W5-U] 模型回退中(`model_fallback` 到切回主模型)。 */
54894
55817
  fallback;
54895
55818
  bashSandbox;
55819
+ /** [W6] 当前模型走 ChatGPT 订阅时的配额;codex 还没数据为 pending,SIWC 没数据或非订阅模型不显示。 */
55820
+ quotaView() {
55821
+ const model = this.deps.session().state.model;
55822
+ if (model === void 0) return void 0;
55823
+ const backend = this.backendOf(formatModelRef(model));
55824
+ if (backend === void 0) return void 0;
55825
+ const quota = this.lastQuota ?? this.deps.session().getStats().subscription?.quota;
55826
+ if (quota !== void 0 && quota.provider === model.provider) return { quota };
55827
+ return backend === "codex" ? "pending" : void 0;
55828
+ }
55829
+ backendMemo;
55830
+ /** 模型的 ChatGPT 订阅后端(渠道 compat `chatgptBackend`);按引用记住上次结果。 */
55831
+ backendOf(ref) {
55832
+ if (this.backendMemo?.ref === ref) return this.backendMemo.backend;
55833
+ const found = this.deps.runtime.providers.findModel(ref);
55834
+ const value = found.ok ? found.model.compat?.chatgptBackend : void 0;
55835
+ const backend = value === "codex" || value === "siwc" ? value : void 0;
55836
+ this.backendMemo = { ref, backend };
55837
+ return backend;
55838
+ }
55839
+ /** 第三行显示且有重置时间时每分钟重画;否则不挂计时器。 */
55840
+ syncTicker() {
55841
+ const view = this.mode === "full" ? this.quotaView() : void 0;
55842
+ const ticking = typeof view === "object" && (view.quota.primary?.resetsAt !== void 0 || view.quota.secondary?.resetsAt !== void 0);
55843
+ if (ticking && this.ticker === void 0) {
55844
+ this.ticker = setInterval(() => this.deps.render(), QUOTA_TICK_MS);
55845
+ this.ticker.unref?.();
55846
+ } else if (!ticking && this.ticker !== void 0) {
55847
+ clearInterval(this.ticker);
55848
+ this.ticker = void 0;
55849
+ }
55850
+ }
54896
55851
  layout() {
54897
55852
  return this.mode;
54898
55853
  }
@@ -54903,6 +55858,7 @@ var init_status_area = __esm({
54903
55858
  setLayout(mode) {
54904
55859
  if (mode === this.mode) return;
54905
55860
  this.mode = mode;
55861
+ this.syncTicker();
54906
55862
  (this.deps.redraw ?? this.deps.render)();
54907
55863
  }
54908
55864
  toggle() {
@@ -54918,6 +55874,7 @@ var init_status_area = __esm({
54918
55874
  this.fallback = void 0;
54919
55875
  this.watchGit();
54920
55876
  this.bar.refresh();
55877
+ this.syncTicker();
54921
55878
  }
54922
55879
  refreshGit() {
54923
55880
  void this.git?.refresh();
@@ -54928,6 +55885,10 @@ var init_status_area = __esm({
54928
55885
  case "telemetry_tick":
54929
55886
  this.bar.refresh();
54930
55887
  return true;
55888
+ case "quota_update":
55889
+ this.lastQuota = event;
55890
+ this.syncTicker();
55891
+ return true;
54931
55892
  case "agent_settled":
54932
55893
  case "session_rewound":
54933
55894
  this.refreshGit();
@@ -54947,6 +55908,7 @@ var init_status_area = __esm({
54947
55908
  if (this.fallback !== void 0 && model !== void 0) {
54948
55909
  if (formatModelRef(model) === this.fallback.from) this.fallback = void 0;
54949
55910
  }
55911
+ this.syncTicker();
54950
55912
  return false;
54951
55913
  }
54952
55914
  default:
@@ -54954,6 +55916,11 @@ var init_status_area = __esm({
54954
55916
  }
54955
55917
  }
54956
55918
  dispose() {
55919
+ this.disposeGit();
55920
+ if (this.ticker !== void 0) clearInterval(this.ticker);
55921
+ this.ticker = void 0;
55922
+ }
55923
+ disposeGit() {
54957
55924
  this.offGit();
54958
55925
  this.git?.dispose();
54959
55926
  this.git = void 0;
@@ -54966,7 +55933,7 @@ var init_status_area = __esm({
54966
55933
  watchGit() {
54967
55934
  const cwd = this.deps.session().state.cwd;
54968
55935
  if (cwd === this.gitCwd && this.git !== void 0) return;
54969
- this.dispose();
55936
+ this.disposeGit();
54970
55937
  this.gitCwd = cwd;
54971
55938
  const git = new GitInfoWatcher(cwd, this.deps.env !== void 0 ? { env: this.deps.env } : {});
54972
55939
  this.git = git;
@@ -54982,6 +55949,8 @@ function createKeyDispatch(deps) {
54982
55949
  const { keys, editor, tools, status } = deps;
54983
55950
  let ctrlCArmedAt = Number.NEGATIVE_INFINITY;
54984
55951
  const esc = new DoubleEscape(deps.doubleEscMs);
55952
+ let busyInputHinted = false;
55953
+ let focusHintShown = false;
54985
55954
  const doubleEscape = () => {
54986
55955
  switch (esc.press(deps.now(), editor.isEmpty())) {
54987
55956
  case "arm-rewind":
@@ -55039,8 +56008,32 @@ ${current3}`);
55039
56008
  if (typeof answer === "boolean") settle(answer);
55040
56009
  else void answer.then(settle);
55041
56010
  };
56011
+ const atLastLine = () => editor.cursor.line >= editor.getText().split("\n").length - 1;
56012
+ const focusAgents = (agents, data2) => {
56013
+ if (editor.isCompletionOpen || editor.isBrowsingHistory) return false;
56014
+ const result = agents.focus(data2);
56015
+ if (result === true) {
56016
+ if (focusHintShown) deps.showHint("");
56017
+ focusHintShown = false;
56018
+ return true;
56019
+ }
56020
+ const m = msg().agents;
56021
+ let text;
56022
+ if (result === "busy-input") {
56023
+ if (busyInputHinted || !atLastLine()) return false;
56024
+ busyInputHinted = true;
56025
+ text = m.focus.busyInput(deps.downGlyph ?? "↓");
56026
+ } else if (result === "disabled") text = m.focus.disabled;
56027
+ else if (result === "empty") text = m.bar.empty;
56028
+ if (text !== void 0) {
56029
+ deps.showHint(text, FOCUS_HINT_MS);
56030
+ focusHintShown = true;
56031
+ }
56032
+ return false;
56033
+ };
55042
56034
  return (data2) => {
55043
56035
  if (deps.inactive()) return false;
56036
+ if (editor.isEmpty()) busyInputHinted = false;
55044
56037
  const is = (action) => keys.matches(data2, action);
55045
56038
  if (!is("app.rewind")) esc.reset();
55046
56039
  const agents = deps.agents?.();
@@ -55048,8 +56041,15 @@ ${current3}`);
55048
56041
  esc.reset();
55049
56042
  return true;
55050
56043
  }
55051
- if (agents !== void 0 && is("app.agents.focus") && editor.isEmpty() && !editor.isCompletionOpen && agents.focus(data2))
55052
- return true;
56044
+ if (agents !== void 0 && is("app.agents.focus") && focusAgents(agents, data2)) return true;
56045
+ if (is("app.tasks.background") && deps.backgroundTasks !== void 0) {
56046
+ const moved = deps.backgroundTasks();
56047
+ if (moved.length > 0) {
56048
+ esc.reset();
56049
+ deps.showHint(backgroundedText2(moved), FOCUS_HINT_MS);
56050
+ return true;
56051
+ }
56052
+ }
55053
56053
  if (is("app.clear")) {
55054
56054
  if (!editor.isEmpty()) {
55055
56055
  editor.clear();
@@ -55067,7 +56067,10 @@ ${current3}`);
55067
56067
  if (is("app.interrupt") && !editor.isCompletionOpen && deps.busy()) {
55068
56068
  esc.reset();
55069
56069
  interrupt();
55070
- deps.showHint(msg().interactive.keys.interrupted);
56070
+ const left = deps.runningBackground?.() ?? [];
56071
+ deps.showHint(
56072
+ left.length > 0 ? msg().agents.background.interrupted(left.join(", ")) : msg().interactive.keys.interrupted
56073
+ );
55071
56074
  deps.onInterrupted?.(editor.isEmpty());
55072
56075
  return true;
55073
56076
  }
@@ -55120,7 +56123,7 @@ ${current3}`);
55120
56123
  return false;
55121
56124
  };
55122
56125
  }
55123
- var DOUBLE_CTRL_C_MS;
56126
+ var DOUBLE_CTRL_C_MS, FOCUS_HINT_MS;
55124
56127
  var init_key_dispatch = __esm({
55125
56128
  "src/modes/interactive/key-dispatch.ts"() {
55126
56129
  "use strict";
@@ -55128,19 +56131,21 @@ var init_key_dispatch = __esm({
55128
56131
  init_exit_codes();
55129
56132
  init_modes();
55130
56133
  init_double_esc();
56134
+ init_task_background();
55131
56135
  init_status_area();
55132
56136
  DOUBLE_CTRL_C_MS = 1500;
56137
+ FOCUS_HINT_MS = 3e3;
55133
56138
  }
55134
56139
  });
55135
56140
 
55136
56141
  // src/modes/interactive/model-picker.ts
55137
56142
  async function openModelPicker(host, options) {
55138
56143
  const catalog = await loadModelCatalog(options.providers);
55139
- return new Promise((resolve38) => {
56144
+ return new Promise((resolve39) => {
55140
56145
  let handle;
55141
56146
  const view = new ModelPickerView(host, catalog, options, (ref) => {
55142
56147
  handle?.hide();
55143
- resolve38(ref);
56148
+ resolve39(ref);
55144
56149
  });
55145
56150
  const width2 = Math.max(20, Math.min(host.columns() - 2, 72));
55146
56151
  handle = host.showOverlay(new Box(view, { title: options.title, theme: host.theme }), {
@@ -55341,7 +56346,7 @@ function rewindListItems(points, cache3, highlighted, now, ascii = false) {
55341
56346
  });
55342
56347
  }
55343
56348
  function openRewindList(host, points, cache3) {
55344
- return new Promise((resolve38) => {
56349
+ return new Promise((resolve39) => {
55345
56350
  const ascii = host.theme.glyphs.ascii;
55346
56351
  let highlighted = points.at(-1)?.entryId;
55347
56352
  let refreshing = false;
@@ -55357,7 +56362,7 @@ function openRewindList(host, points, cache3) {
55357
56362
  const close = (point) => {
55358
56363
  closed = true;
55359
56364
  handle.hide();
55360
- resolve38(point);
56365
+ resolve39(point);
55361
56366
  };
55362
56367
  const highlight2 = (entryId) => {
55363
56368
  highlighted = entryId;
@@ -55474,8 +56479,8 @@ function rewindOptions(model, ascii = false) {
55474
56479
  }
55475
56480
  function conflictOptions() {
55476
56481
  const m = msg().rewind.panel;
55477
- const labels = { skip: m.conflictSkip, overwrite: m.conflictOverwrite, back: m.conflictBack };
55478
- return CONFLICT_VALUES.map((value) => ({ value, label: labels[value] }));
56482
+ const labels2 = { skip: m.conflictSkip, overwrite: m.conflictOverwrite, back: m.conflictBack };
56483
+ return CONFLICT_VALUES.map((value) => ({ value, label: labels2[value] }));
55479
56484
  }
55480
56485
  var COMPACT_WIDTH4, MESSAGE_LINES, CONFLICT_VALUES, Lines4, RewindPanel;
55481
56486
  var init_rewind_panel = __esm({
@@ -55729,7 +56734,7 @@ var init_rewind_panel = __esm({
55729
56734
 
55730
56735
  // src/modes/interactive/rewind-flow.ts
55731
56736
  function openRewindPanel(host, model) {
55732
- return new Promise((resolve38) => {
56737
+ return new Promise((resolve39) => {
55733
56738
  let handle;
55734
56739
  const panel = new RewindPanel(
55735
56740
  model,
@@ -55740,7 +56745,7 @@ function openRewindPanel(host, model) {
55740
56745
  },
55741
56746
  (choice3) => {
55742
56747
  handle?.hide();
55743
- resolve38(choice3);
56748
+ resolve39(choice3);
55744
56749
  }
55745
56750
  );
55746
56751
  handle = host.showOverlay(panel, { anchor: "bottom" });
@@ -55869,7 +56874,7 @@ var init_rewind_flow = __esm({
55869
56874
 
55870
56875
  // src/modes/interactive/trace-view.ts
55871
56876
  function openTraceView(host, taskId) {
55872
- return new Promise((resolve38) => {
56877
+ return new Promise((resolve39) => {
55873
56878
  let handle;
55874
56879
  let timer;
55875
56880
  let unsubscribe;
@@ -55879,12 +56884,12 @@ function openTraceView(host, taskId) {
55879
56884
  unsubscribe?.();
55880
56885
  handle?.hide();
55881
56886
  host.render?.();
55882
- resolve38();
56887
+ resolve39();
55883
56888
  };
55884
56889
  const view = new TraceView(host, taskId, finish2);
55885
56890
  if (!view.found) {
55886
56891
  host.notice("warn", msg().trace.taskNotFound(taskId));
55887
- resolve38();
56892
+ resolve39();
55888
56893
  return;
55889
56894
  }
55890
56895
  const throttle = host.refreshMs ?? TRACE_REFRESH_MS;
@@ -56149,6 +57154,153 @@ var init_trace_view = __esm({
56149
57154
  }
56150
57155
  });
56151
57156
 
57157
+ // src/modes/interactive/startup-logo.ts
57158
+ function logoRows(theme) {
57159
+ return theme.glyphs.ascii ? LOGO_ASCII : LOGO_BLOCK;
57160
+ }
57161
+ function letterColor(col, ascii) {
57162
+ const starts = LETTER_STARTS[ascii ? "ascii" : "block"];
57163
+ let index2 = 0;
57164
+ for (let i = 0; i < starts.length; i++) if (col >= starts[i]) index2 = i;
57165
+ return GRADIENT[index2];
57166
+ }
57167
+ function logoWidth(rows) {
57168
+ return Math.max(...rows.map((row3) => [...row3].length));
57169
+ }
57170
+ function sweepFrames(theme) {
57171
+ return logoWidth(logoRows(theme)) + SWEEP_BAND;
57172
+ }
57173
+ function styleAt(col, ascii, sweep) {
57174
+ const settled = { color: letterColor(col, ascii), bold: true };
57175
+ if (sweep === void 0 || col <= sweep - SWEEP_BAND) return settled;
57176
+ if (col <= sweep) return { color: "text", bold: true };
57177
+ return { color: "dim", bold: false };
57178
+ }
57179
+ function paintLogo(theme, sweep) {
57180
+ const rows = logoRows(theme);
57181
+ const width2 = logoWidth(rows);
57182
+ return rows.map((row3) => {
57183
+ const chars = [...row3.padEnd(width2)];
57184
+ let out = "";
57185
+ let run7 = "";
57186
+ let runStyle;
57187
+ const flush = () => {
57188
+ if (run7 === "") return;
57189
+ if (runStyle === void 0) out += run7;
57190
+ else {
57191
+ const colored = theme.fg(runStyle.color, run7);
57192
+ out += runStyle.bold ? theme.bold(colored) : colored;
57193
+ }
57194
+ run7 = "";
57195
+ };
57196
+ chars.forEach((ch2, col) => {
57197
+ const style = ch2 === " " ? void 0 : styleAt(col, theme.glyphs.ascii, sweep);
57198
+ if (style?.color !== runStyle?.color || style?.bold !== runStyle?.bold) {
57199
+ flush();
57200
+ runStyle = style;
57201
+ }
57202
+ run7 += ch2;
57203
+ });
57204
+ flush();
57205
+ return out;
57206
+ });
57207
+ }
57208
+ function shouldAnimate(gate) {
57209
+ const ci = gate.env["CI"];
57210
+ return gate.animation !== false && gate.hasLogo && gate.colors > 0 && gate.io.stdoutIsTTY && !gate.host && (gate.prompt ?? "").trim() === "" && (ci === void 0 || ci === "" || ci === "0" || ci === "false") && gate.rows >= MIN_ANIMATION_ROWS;
57211
+ }
57212
+ function playStartupAnimation(tui, header, theme, conditions) {
57213
+ const term = tui.terminal;
57214
+ const columns = term.columns;
57215
+ if (header === void 0) return void 0;
57216
+ const gate = {
57217
+ ...conditions,
57218
+ hasLogo: header.hasLogo(term.columns),
57219
+ colors: theme.caps.colors,
57220
+ rows: term.rows
57221
+ };
57222
+ const fits = () => tui.render(term.columns).length <= term.rows;
57223
+ if (!shouldAnimate(gate) || !fits()) return void 0;
57224
+ let offInput = () => void 0;
57225
+ const animation = new LogoAnimation(header, {
57226
+ frames: sweepFrames(theme),
57227
+ render: () => tui.requestRender(),
57228
+ // 改了尺寸(差分渲染要整屏重画)也立即定格
57229
+ canContinue: () => term.columns === columns && fits(),
57230
+ onDone: () => offInput()
57231
+ });
57232
+ offInput = tui.addInputListener(() => {
57233
+ animation.finish();
57234
+ return false;
57235
+ });
57236
+ animation.start();
57237
+ return animation;
57238
+ }
57239
+ var LOGO_BLOCK, LOGO_ASCII, SWEEP_BAND, FRAME_MS, GRADIENT, LETTER_STARTS, LogoAnimation, MIN_ANIMATION_ROWS;
57240
+ var init_startup_logo = __esm({
57241
+ "src/modes/interactive/startup-logo.ts"() {
57242
+ "use strict";
57243
+ LOGO_BLOCK = [
57244
+ " ▄███▄ ██▄ ▄██ ▄███▄",
57245
+ "██▀ ▀██ ███▄ ▄███ ██▀ ▀██",
57246
+ "███████ ██ ▀█▀ ██ ███████",
57247
+ "██ ██ ██ ██ ██ ██",
57248
+ "▀▀ ▀▀ ▀▀ ▀▀ ▀▀ ▀▀"
57249
+ ];
57250
+ LOGO_ASCII = [
57251
+ " _ __ __ _",
57252
+ " / \\ | \\/ | / \\",
57253
+ " / _ \\ | |\\/| | / _ \\",
57254
+ " / ___ \\| | | |/ ___ \\",
57255
+ "/_/ \\_\\_| |_/_/ \\_\\"
57256
+ ];
57257
+ SWEEP_BAND = 3;
57258
+ FRAME_MS = 32;
57259
+ GRADIENT = ["accent", "user", "tool"];
57260
+ LETTER_STARTS = { block: [0, 8, 18], ascii: [0, 8, 16] };
57261
+ LogoAnimation = class {
57262
+ constructor(target, options) {
57263
+ this.target = target;
57264
+ this.options = options;
57265
+ }
57266
+ target;
57267
+ options;
57268
+ frame = 0;
57269
+ timer;
57270
+ done = false;
57271
+ get running() {
57272
+ return !this.done && this.timer !== void 0;
57273
+ }
57274
+ start() {
57275
+ if (this.done || this.timer !== void 0) return;
57276
+ this.step();
57277
+ }
57278
+ step() {
57279
+ this.timer = void 0;
57280
+ if (this.frame >= this.options.frames || this.options.canContinue?.() === false) {
57281
+ this.finish();
57282
+ return;
57283
+ }
57284
+ this.target.setSweep(this.frame++);
57285
+ this.options.render();
57286
+ this.timer = setTimeout(() => this.step(), this.options.intervalMs ?? FRAME_MS);
57287
+ this.timer.unref?.();
57288
+ }
57289
+ /** 立即定格(按键、退出、走完);重复调用无副作用。 */
57290
+ finish() {
57291
+ if (this.done) return;
57292
+ this.done = true;
57293
+ if (this.timer !== void 0) clearTimeout(this.timer);
57294
+ this.timer = void 0;
57295
+ this.target.setSweep(void 0);
57296
+ this.options.render();
57297
+ this.options.onDone?.();
57298
+ }
57299
+ };
57300
+ MIN_ANIMATION_ROWS = 16;
57301
+ }
57302
+ });
57303
+
56152
57304
  // src/modes/interactive/startup-header.ts
56153
57305
  function truncateLeft(text, width2, ellipsis = "…") {
56154
57306
  if (visibleWidth(text) <= width2) return text;
@@ -56165,19 +57317,26 @@ function truncateLeft(text, width2, ellipsis = "…") {
56165
57317
  }
56166
57318
  return ellipsis + out;
56167
57319
  }
57320
+ function fitSegments(text, width2) {
57321
+ const parts = text.split(" · ");
57322
+ while (parts.length > 1 && visibleWidth(parts.join(" · ")) > width2) parts.pop();
57323
+ return parts.join(" · ");
57324
+ }
56168
57325
  function withoutChannel(model) {
56169
57326
  const at = model.indexOf("@");
56170
57327
  return at === -1 ? model : model.slice(0, at);
56171
57328
  }
56172
- var HEADER_BOX_MIN_WIDTH, HEADER_BOX_MAX_WIDTH, StartupHeader;
57329
+ var HEADER_SIDE_MIN_WIDTH, HEADER_LOGO_MIN_WIDTH, GAP, StartupHeader;
56173
57330
  var init_startup_header = __esm({
56174
57331
  "src/modes/interactive/startup-header.ts"() {
56175
57332
  "use strict";
56176
57333
  init_i18n();
56177
57334
  init_modes();
56178
57335
  init_tui2();
56179
- HEADER_BOX_MIN_WIDTH = 56;
56180
- HEADER_BOX_MAX_WIDTH = 64;
57336
+ init_startup_logo();
57337
+ HEADER_SIDE_MIN_WIDTH = 72;
57338
+ HEADER_LOGO_MIN_WIDTH = 48;
57339
+ GAP = 3;
56181
57340
  StartupHeader = class {
56182
57341
  constructor(info2, options) {
56183
57342
  this.info = info2;
@@ -56186,9 +57345,19 @@ var init_startup_header = __esm({
56186
57345
  info;
56187
57346
  options;
56188
57347
  cache = null;
57348
+ sweep;
56189
57349
  get theme() {
56190
57350
  return this.options.theme;
56191
57351
  }
57352
+ /** 这个宽度下是否画字符画(动画只在画了时播放)。 */
57353
+ hasLogo(width2) {
57354
+ return this.options.level === "normal" && this.options.logo !== "off" && this.options.compact !== true && width2 >= HEADER_LOGO_MIN_WIDTH;
57355
+ }
57356
+ setSweep(sweep) {
57357
+ if (sweep === this.sweep) return;
57358
+ this.sweep = sweep;
57359
+ this.cache = null;
57360
+ }
56192
57361
  title() {
56193
57362
  const t = this.theme;
56194
57363
  return `${t.fg("accent", t.glyphs.thinking)} ${t.bold(`ama ${this.info.version}`)}`;
@@ -56217,7 +57386,11 @@ var init_startup_header = __esm({
56217
57386
  }
56218
57387
  render(width2) {
56219
57388
  if (this.cache?.width === width2) return this.cache.lines;
56220
- const lines2 = this.options.level === "header" ? [this.headerLine(width2)] : this.options.compact === true || width2 < HEADER_BOX_MIN_WIDTH ? this.plainLines(width2) : this.boxLines(width2);
57389
+ let lines2;
57390
+ if (this.options.level === "header") lines2 = [this.headerLine(width2)];
57391
+ else if (width2 < HEADER_LOGO_MIN_WIDTH) lines2 = this.narrowLines(width2);
57392
+ else if (!this.hasLogo(width2)) lines2 = this.infoLines(width2, this.title());
57393
+ else lines2 = this.logoLines(width2);
56221
57394
  this.cache = { width: width2, lines: lines2 };
56222
57395
  return lines2;
56223
57396
  }
@@ -56232,64 +57405,63 @@ var init_startup_header = __esm({
56232
57405
  ];
56233
57406
  return truncateToWidth(parts.join(sep5), width2);
56234
57407
  }
56235
- plainLines(width2) {
57408
+ /** < 48 列:两行(版本 · 模型 · 思考 / 模式 · 目录 · 信任),有警告再加一行。 */
57409
+ narrowLines(width2) {
56236
57410
  const t = this.theme;
56237
57411
  const sep5 = t.fg("dim", " · ");
56238
- const ellipsis = t.glyphs.ellipsis;
56239
57412
  const { info: info2 } = this;
56240
- const lines2 = [this.title()];
56241
- lines2.push(t.fg("accent", withoutChannel(info2.model)) + sep5 + info2.thinking);
57413
+ const lines2 = [
57414
+ [this.title(), t.fg("accent", withoutChannel(info2.model)), info2.thinking].join(sep5)
57415
+ ];
57416
+ const mode = this.modeText();
56242
57417
  const trust = this.trustText(false);
56243
- const room = Math.max(4, width2 - visibleWidth(trust) - 3);
56244
- lines2.push(truncateLeft(info2.cwd, room, ellipsis) + sep5 + trust);
56245
- const mode = [this.modeText(), info2.preset, this.codemodeText()].filter(
56246
- (p) => p !== void 0
56247
- );
56248
- lines2.push(mode.join(sep5));
56249
- const loaded = this.loadedText();
56250
- if (loaded !== void 0) lines2.push(loaded);
56251
- const m = msg().interactive.startup.header;
56252
- if (info2.host !== void 0) lines2.push(m.host(info2.host));
56253
- if (info2.warnings > 0) lines2.push(t.fg("warning", m.warnings(info2.warnings)));
56254
- lines2.push(t.fg("dim", m.hintCompact));
57418
+ const room = Math.max(4, width2 - visibleWidth(mode) - visibleWidth(trust) - 6);
57419
+ lines2.push([mode, truncateLeft(info2.cwd, room, t.glyphs.ellipsis), trust].join(sep5));
57420
+ if (info2.warnings > 0) {
57421
+ lines2.push(t.fg("warning", msg().interactive.startup.header.warnings(info2.warnings)));
57422
+ }
56255
57423
  return lines2.map((line2) => truncateToWidth(line2, width2));
56256
57424
  }
56257
- boxLines(width2) {
57425
+ /** 信息列:首行 `title`,其后模型、目录、模式、可选行、按键提示;每行截到 `width`。 */
57426
+ infoLines(width2, title) {
56258
57427
  const t = this.theme;
56259
57428
  const sep5 = t.fg("dim", " · ");
56260
57429
  const { info: info2 } = this;
56261
57430
  const m = msg().interactive.startup.header;
56262
- const boxWidth = Math.min(width2, HEADER_BOX_MAX_WIDTH);
56263
- const keyWidth = Math.max(
56264
- 6,
56265
- ...[m.keyModel, m.keyDir, m.keyMode, m.keyLoaded, m.keyHost, m.keyWarnings].map(
56266
- (k) => visibleWidth(k)
56267
- )
56268
- );
56269
- const valueWidth = boxWidth - 4 - keyWidth - 1;
56270
57431
  const trust = this.trustText(true);
56271
- const cwdRoom = Math.max(4, valueWidth - visibleWidth(trust) - 3);
56272
- const rows = [
56273
- { key: m.keyModel, value: t.fg("accent", info2.model) + sep5 + m.thinking(info2.thinking) },
56274
- { key: m.keyDir, value: truncateLeft(info2.cwd, cwdRoom, t.glyphs.ellipsis) + sep5 + trust },
56275
- {
56276
- key: m.keyMode,
56277
- value: [this.modeText(), m.preset(info2.preset), this.codemodeText()].filter((p) => p !== void 0).join(sep5)
56278
- }
57432
+ const cwdRoom = Math.max(4, width2 - visibleWidth(trust) - 3);
57433
+ const lines2 = [
57434
+ title,
57435
+ t.fg("accent", info2.model) + sep5 + m.thinking(info2.thinking),
57436
+ truncateLeft(info2.cwd, cwdRoom, t.glyphs.ellipsis) + sep5 + trust,
57437
+ [this.modeText(), m.preset(info2.preset), this.codemodeText()].filter((p) => p !== void 0).join(sep5)
56279
57438
  ];
56280
57439
  const loaded = this.loadedText();
56281
- if (loaded !== void 0) rows.push({ key: m.keyLoaded, value: loaded });
56282
- if (info2.host !== void 0) rows.push({ key: m.keyHost, value: info2.host });
56283
- if (info2.warnings > 0) {
56284
- rows.push({ key: m.keyWarnings, value: t.fg("warning", m.warningsValue(info2.warnings)) });
57440
+ if (loaded !== void 0) lines2.push(loaded);
57441
+ if (info2.host !== void 0) lines2.push(m.host(info2.host));
57442
+ if (info2.warnings > 0) lines2.push(t.fg("warning", m.warnings(info2.warnings)));
57443
+ lines2.push(t.fg("dim", fitSegments(m.hint, width2)));
57444
+ return lines2.map((line2) => truncateToWidth(line2, width2));
57445
+ }
57446
+ /** 字符画 + 信息列:够宽并排(顶端对齐),否则上下叠放。 */
57447
+ logoLines(width2) {
57448
+ const t = this.theme;
57449
+ const logo = paintLogo(t, this.sweep);
57450
+ const logoCols = logoWidth(logoRows(t));
57451
+ const title = `${t.bold("ama")} ${t.fg("dim", this.info.version)}`;
57452
+ if (width2 < HEADER_SIDE_MIN_WIDTH) {
57453
+ return [...logo, "", ...this.infoLines(width2, title)];
57454
+ }
57455
+ const info2 = this.infoLines(width2 - logoCols - GAP, title);
57456
+ const rows = Math.max(logo.length, info2.length);
57457
+ const blank = " ".repeat(logoCols);
57458
+ const gap = " ".repeat(GAP);
57459
+ const lines2 = [];
57460
+ for (let i = 0; i < rows; i++) {
57461
+ const right = info2[i];
57462
+ lines2.push(right === void 0 ? logo[i] ?? "" : (logo[i] ?? blank) + gap + right);
56285
57463
  }
56286
- const padded = rows.map((row3) => ({ ...row3, key: padToWidth(row3.key, keyWidth) }));
56287
- const kv = new KeyValue(padded, { theme: t, gap: 1, maxKeyRatio: 1 });
56288
- const body = {
56289
- render: (inner) => [this.title(), "", ...kv.render(inner), "", t.fg("dim", m.hint)],
56290
- invalidate: () => kv.invalidate()
56291
- };
56292
- return new Box(body, { theme: t }).render(boxWidth);
57464
+ return lines2;
56293
57465
  }
56294
57466
  invalidate() {
56295
57467
  this.cache = null;
@@ -56328,6 +57500,10 @@ var init_run_indicator = __esm({
56328
57500
  RunIndicator = class {
56329
57501
  constructor(deps) {
56330
57502
  this.deps = deps;
57503
+ deps.loader.onFrame(() => {
57504
+ if (this.busy && !this.approval && this.optionalHints().join("\n") !== this.hintKey)
57505
+ this.applyVerb();
57506
+ });
56331
57507
  }
56332
57508
  deps;
56333
57509
  running = false;
@@ -56335,6 +57511,23 @@ var init_run_indicator = __esm({
56335
57511
  approval = false;
56336
57512
  retry;
56337
57513
  stream;
57514
+ /** [W7-A] 有该显示的子 Agent 任务(interactive-mode 在 AgentUi 建好后接上)。 */
57515
+ agents = () => false;
57516
+ /** [W7-C] 有阻塞中的前台任务(Ctrl+B 可转后台)。 */
57517
+ background = () => false;
57518
+ /** [W7-C] 有停靠在 Agent 栏的审批。 */
57519
+ docked = () => false;
57520
+ hintKey = "";
57521
+ /** 可丢弃的按键提示(按丢弃的逆序排列)。 */
57522
+ optionalHints() {
57523
+ const down = this.deps.theme.glyphs.arrowDown;
57524
+ const key = this.deps.backgroundKey;
57525
+ const out = [];
57526
+ if (key !== void 0 && this.background()) out.push(msg().agents.background.runHint(key));
57527
+ if (this.docked()) out.push(msg().agents.approval.runHint(down));
57528
+ else if (this.agents()) out.push(msg().agents.bar.runHint(down));
57529
+ return out;
57530
+ }
56338
57531
  get busy() {
56339
57532
  return this.running || this.compacting;
56340
57533
  }
@@ -56413,17 +57606,21 @@ var init_run_indicator = __esm({
56413
57606
  const m = msg().interactive.view.run;
56414
57607
  const esc = [m.esc];
56415
57608
  if (this.approval) {
57609
+ this.hintKey = "";
56416
57610
  loader.setVerb(m.awaitingApproval);
56417
57611
  return;
56418
57612
  }
57613
+ const hints = this.optionalHints();
57614
+ this.hintKey = hints.join("\n");
57615
+ const optional = hints.length > 0 ? { optional: hints } : {};
56419
57616
  const running = tools.running();
56420
57617
  const top = running.filter((view) => !running.some((p) => p.children.includes(view)));
56421
57618
  if (top.length === 1) {
56422
- loader.setVerb(m.runningTool(top[0].toolName), esc);
57619
+ loader.setVerb(m.runningTool(top[0].toolName), esc, optional);
56423
57620
  return;
56424
57621
  }
56425
57622
  if (top.length > 1) {
56426
- loader.setVerb(m.runningTools(top.length), esc);
57623
+ loader.setVerb(m.runningTools(top.length), esc, optional);
56427
57624
  return;
56428
57625
  }
56429
57626
  if (this.retry !== void 0) {
@@ -56434,15 +57631,15 @@ var init_run_indicator = __esm({
56434
57631
  return;
56435
57632
  }
56436
57633
  if (this.compacting) {
56437
- loader.setVerb(m.compacting, esc);
57634
+ loader.setVerb(m.compacting, esc, optional);
56438
57635
  return;
56439
57636
  }
56440
57637
  const stream5 = this.stream;
56441
57638
  if (stream5 !== void 0 && !stream5.thinking && stream5.tokens > 0) {
56442
- loader.setVerb(m.replying, [`↓≈${formatTokens3(stream5.tokens)}`, ...esc]);
57639
+ loader.setVerb(m.replying, [`↓≈${formatTokens3(stream5.tokens)}`, ...esc], optional);
56443
57640
  return;
56444
57641
  }
56445
- loader.setVerb(m.thinking, esc);
57642
+ loader.setVerb(m.thinking, esc, optional);
56446
57643
  }
56447
57644
  };
56448
57645
  QueueView = class extends Text {
@@ -56481,7 +57678,10 @@ function createSessionEventHandler(deps) {
56481
57678
  const { view, tools, status, area, agentUi, indicator, session, setQueue, notice, render } = deps;
56482
57679
  const images = new ImageBudgetNotices((text) => notice("info", text));
56483
57680
  return (event) => {
56484
- if (area.onEvent(event) || agentUi().onEvent(event)) return render();
57681
+ if (area.onEvent(event) || agentUi().onEvent(event)) {
57682
+ if (event.type === "subagent_start" || event.type === "subagent_end") indicator.sync();
57683
+ return render();
57684
+ }
56485
57685
  images.onEvent(event);
56486
57686
  switch (event.type) {
56487
57687
  case "agent_settled":
@@ -56677,6 +57877,7 @@ function runInteractiveMode(runtime, context, options = {}) {
56677
57877
  tui.addChild(hint);
56678
57878
  tui.addChild(area.rate);
56679
57879
  tui.addChild(status);
57880
+ tui.addChild(area.quota);
56680
57881
  let finished = false;
56681
57882
  let hintTimer;
56682
57883
  let resolveExit = () => void 0;
@@ -56698,7 +57899,11 @@ function runInteractiveMode(runtime, context, options = {}) {
56698
57899
  }
56699
57900
  render();
56700
57901
  };
56701
- const indicator = new RunIndicator({ theme, loader, slot: loaderSlot, tools, render });
57902
+ const backgroundKey = keyLabel(keys, "app.tasks.background");
57903
+ const indicator = new RunIndicator({
57904
+ ...{ theme, loader, slot: loaderSlot, tools, render },
57905
+ ...backgroundKey !== void 0 ? { backgroundKey } : {}
57906
+ });
56702
57907
  const setQueue = (steering, followUp) => {
56703
57908
  status.setQueue(steering.length, followUp.length);
56704
57909
  queueView.setQueue(steering, followUp);
@@ -56720,7 +57925,7 @@ function runInteractiveMode(runtime, context, options = {}) {
56720
57925
  const startupLevel = startupScreenLevel(runtime);
56721
57926
  const home = env["HOME"] ?? env["USERPROFILE"];
56722
57927
  const info2 = startupInfo(runtime, home);
56723
- const header = (level) => new StartupHeader(info2, { theme, level, ...ui.compact === true ? { compact: true } : {} });
57928
+ const header = (level) => new StartupHeader(info2, { theme, level, logo: ui.logo, compact: ui.compact });
56724
57929
  const resetView = () => {
56725
57930
  view.reset();
56726
57931
  if (startupLevel !== "silent") view.add(header("header"));
@@ -56893,22 +58098,13 @@ function runInteractiveMode(runtime, context, options = {}) {
56893
58098
  runCommand: (line2) => void runCommand2(line2),
56894
58099
  exit: (code) => exit(code),
56895
58100
  onInterrupted: (empty) => void rewind.afterInterrupt(empty),
56896
- agents: () => agentUi.keys
58101
+ agents: () => agentUi.keys,
58102
+ downGlyph: theme.glyphs.arrowDown,
58103
+ backgroundTasks: () => backgroundTasks(session),
58104
+ runningBackground: () => runningBackground(session)
56897
58105
  })
56898
58106
  );
56899
- const overlayHooks = {
56900
- showOverlay: (component) => tui.showOverlay(component, { anchor: "bottom" }),
56901
- onOpen: () => {
56902
- editor.disableSubmit = true;
56903
- tools.setAwaiting(true);
56904
- indicator.setApproval(true);
56905
- },
56906
- onClose: () => {
56907
- editor.disableSubmit = false;
56908
- tools.setAwaiting(false);
56909
- indicator.setApproval(false);
56910
- }
56911
- };
58107
+ const overlayHooks = approvalOverlayHooks({ tui, editor, tools: () => tools, indicator });
56912
58108
  const agentUi = new AgentUi({
56913
58109
  theme,
56914
58110
  tui,
@@ -56931,33 +58127,18 @@ function runInteractiveMode(runtime, context, options = {}) {
56931
58127
  ...options.clipboard !== void 0 ? { clipboard: options.clipboard } : {},
56932
58128
  area
56933
58129
  });
56934
- const taskAgent = (taskId) => taskRegistryView(session.state.sessionId)?.get(taskId)?.agent;
56935
- const broker = mergingBroker(
56936
- new ApprovalDialogBroker({
56937
- theme,
56938
- keybindings: keys,
56939
- cwd: session.state.cwd,
56940
- permissionMode: () => session.state.permissionMode,
56941
- taskAgent,
56942
- externalRunner: (agent) => agentUi.merge.externalRunner(agent),
56943
- ...overlayHooks,
56944
- report: (request, outcome) => {
56945
- if (outcome === "deny" || outcome === "cancelled") {
56946
- const text = approvalOutcomeText(request, outcome, taskAgent);
56947
- notice(outcome === "deny" ? "info" : "warn", text);
56948
- }
56949
- }
56950
- }),
56951
- agentUi.merge,
56952
- (request) => notice(
56953
- "info",
56954
- msg().interactive.app.followed(approvalOutcomeText(request, "allow", taskAgent))
56955
- )
56956
- );
58130
+ indicator.agents = () => agentUi.reachable;
58131
+ const { broker, dock } = createApprovalUi({
58132
+ ...{ theme, keys, tui, editor, indicator, agentUi, notice, render },
58133
+ hooks: overlayHooks,
58134
+ session: () => session
58135
+ });
58136
+ indicator.background = () => blockingTasks2(session).length > 0;
56957
58137
  let offSignals = () => void 0;
56958
58138
  function exit(code) {
56959
58139
  if (finished) return;
56960
58140
  finished = true;
58141
+ logoAnimation?.finish();
56961
58142
  if (hintTimer !== void 0) clearTimeout(hintTimer);
56962
58143
  loader.stop();
56963
58144
  area.dispose();
@@ -56966,6 +58147,7 @@ function runInteractiveMode(runtime, context, options = {}) {
56966
58147
  tui.addChild(view);
56967
58148
  offSignals();
56968
58149
  agentUi.detach();
58150
+ dock.dispose();
56969
58151
  runtime.approvals.setUiBroker(void 0);
56970
58152
  runtime.notifier.set(void 0);
56971
58153
  tui.stop();
@@ -56975,12 +58157,14 @@ function runInteractiveMode(runtime, context, options = {}) {
56975
58157
  resolveExit(code);
56976
58158
  });
56977
58159
  }
56978
- if (startupLevel !== "silent") view.add(header(startupLevel));
58160
+ const startHeader = startupLevel === "silent" ? void 0 : header(startupLevel);
58161
+ let logoAnimation;
58162
+ if (startHeader !== void 0) view.add(startHeader);
56979
58163
  for (const warning of startupWarnings) view.addNotice("warn", warning);
56980
58164
  replay();
56981
58165
  status.refresh();
56982
- return new Promise((resolve38, reject2) => {
56983
- resolveExit = resolve38;
58166
+ return new Promise((resolve39, reject2) => {
58167
+ resolveExit = resolve39;
56984
58168
  try {
56985
58169
  tui.start();
56986
58170
  } catch (error2) {
@@ -57001,6 +58185,8 @@ function runInteractiveMode(runtime, context, options = {}) {
57001
58185
  agentUi.attach(session);
57002
58186
  offSignals = onTerminationSignals((code) => exit(code));
57003
58187
  tui.setFocus(editor);
58188
+ const gate = { animation: ui.animation, host: info2.host !== void 0, env, ...context };
58189
+ logoAnimation = playStartupAnimation(tui, startHeader, theme, gate);
57004
58190
  options.onReady?.({
57005
58191
  tui,
57006
58192
  editor,
@@ -57024,7 +58210,6 @@ var init_interactive_mode = __esm({
57024
58210
  init_image_input();
57025
58211
  import_node_path97 = require("node:path");
57026
58212
  init_session2();
57027
- init_subagent_registry();
57028
58213
  init_compose_session();
57029
58214
  init_startup_screen();
57030
58215
  init_errors();
@@ -57033,8 +58218,7 @@ var init_interactive_mode = __esm({
57033
58218
  init_bypass();
57034
58219
  init_shared2();
57035
58220
  init_agent_ui();
57036
- init_approval_dialog();
57037
- init_approval_merge();
58221
+ init_approval_ui();
57038
58222
  init_commands2();
57039
58223
  init_memory_panel();
57040
58224
  init_completion();
@@ -57047,12 +58231,14 @@ var init_interactive_mode = __esm({
57047
58231
  init_rewind_flow();
57048
58232
  init_trace_view();
57049
58233
  init_startup_header();
58234
+ init_startup_logo();
57050
58235
  init_run_indicator();
57051
58236
  init_status_area();
57052
58237
  init_session_events();
57053
58238
  init_subagent_view();
57054
58239
  init_terminal_setup();
57055
58240
  init_tool_view();
58241
+ init_task_background();
57056
58242
  HINT_MS = 2500;
57057
58243
  }
57058
58244
  });
@@ -57106,7 +58292,7 @@ function createTextStartupUi(io) {
57106
58292
  io.write([...lines2, question].join("\n"));
57107
58293
  return readOneLine(io.stdin);
57108
58294
  };
57109
- const list5 = (values, labels) => values.map((_, i) => ` ${i + 1}. ${labels[i]}`);
58295
+ const list5 = (values, labels2) => values.map((_, i) => ` ${i + 1}. ${labels2[i]}`);
57110
58296
  return {
57111
58297
  async promptTrust(cwd, resources) {
57112
58298
  const m = msg().report.startup;
@@ -57121,11 +58307,11 @@ function createTextStartupUi(io) {
57121
58307
  if (items.length === 0) return void 0;
57122
58308
  const sorted = [...items].sort((a, b) => b.modifiedAt.localeCompare(a.modifiedAt));
57123
58309
  const ids = sorted.map((item) => item.id);
57124
- const labels = sorted.map(
58310
+ const labels2 = sorted.map(
57125
58311
  (item) => `${item.id.slice(0, 8)} ${(item.name ?? item.firstPrompt ?? "").replace(/\s+/g, " ").slice(0, 60)}`
57126
58312
  );
57127
58313
  const m = msg().report.startup;
57128
- return choose(await ask([m.resumeQuestion, ...list5(ids, labels)], m.resumeAnswer), ids);
58314
+ return choose(await ask([m.resumeQuestion, ...list5(ids, labels2)], m.resumeAnswer), ids);
57129
58315
  },
57130
58316
  async pickModel(providers, reason) {
57131
58317
  const refs = [];
@@ -58903,15 +60089,15 @@ function browserCommand(url, platform = process.platform) {
58903
60089
  return { command: "cmd", args: ["/c", "start", '""', url.replace(/&/g, "^&")] };
58904
60090
  return { command: "xdg-open", args: [url] };
58905
60091
  }
58906
- var openBrowser = (url) => new Promise((resolve38) => {
60092
+ var openBrowser = (url) => new Promise((resolve39) => {
58907
60093
  const { command: command3, args } = browserCommand(url);
58908
60094
  try {
58909
60095
  const child = (0, import_node_child_process12.spawn)(command3, args, { stdio: "ignore", detached: true, windowsHide: true });
58910
- child.once("error", () => resolve38(false));
58911
- child.once("exit", (code) => resolve38(code === 0 || code === null));
60096
+ child.once("error", () => resolve39(false));
60097
+ child.once("exit", (code) => resolve39(code === 0 || code === null));
58912
60098
  child.unref();
58913
60099
  } catch {
58914
- resolve38(false);
60100
+ resolve39(false);
58915
60101
  }
58916
60102
  });
58917
60103
 
@@ -58938,7 +60124,7 @@ function listed(item) {
58938
60124
  const visibility = item["visibility"];
58939
60125
  return visibility === void 0 || visibility === "list";
58940
60126
  }
58941
- function codexMetadata(item) {
60127
+ function backendMetadata(item) {
58942
60128
  const out = {};
58943
60129
  const window2 = item["context_window"];
58944
60130
  if (typeof window2 === "number" && Number.isInteger(window2) && window2 > 0)
@@ -58978,7 +60164,7 @@ async function listChatGptModels(fetchFn, baseUrl, auth) {
58978
60164
  out.push({
58979
60165
  id,
58980
60166
  ...typeof name === "string" && name !== "" ? { name } : {},
58981
- ...auth.flavor === "codex" ? codexMetadata(item) : {}
60167
+ ...backendMetadata(item)
58982
60168
  });
58983
60169
  }
58984
60170
  return out;
@@ -59080,7 +60266,7 @@ function parseCallbackUrl(input2, state) {
59080
60266
  return codeFromParams(params, state);
59081
60267
  }
59082
60268
  function listen(server, port) {
59083
- return new Promise((resolve38, reject2) => {
60269
+ return new Promise((resolve39, reject2) => {
59084
60270
  const onError = (error2) => {
59085
60271
  server.off("listening", onListening);
59086
60272
  reject2(error2);
@@ -59088,7 +60274,7 @@ function listen(server, port) {
59088
60274
  const onListening = () => {
59089
60275
  server.off("error", onError);
59090
60276
  const address = server.address();
59091
- resolve38(typeof address === "object" && address !== null ? address.port : port);
60277
+ resolve39(typeof address === "object" && address !== null ? address.port : port);
59092
60278
  };
59093
60279
  server.once("error", onError);
59094
60280
  server.once("listening", onListening);
@@ -59099,8 +60285,8 @@ async function startCallbackServer(options) {
59099
60285
  const path = options.path ?? CALLBACK_PATH;
59100
60286
  const pages = options.pages ?? DEFAULT_PAGES;
59101
60287
  let settle;
59102
- const result = new Promise((resolve38, reject2) => {
59103
- settle = { resolve: resolve38, reject: reject2 };
60288
+ const result = new Promise((resolve39, reject2) => {
60289
+ settle = { resolve: resolve39, reject: reject2 };
59104
60290
  });
59105
60291
  result.catch(() => {
59106
60292
  });
@@ -59220,9 +60406,9 @@ async function loginPaste(preset, deps) {
59220
60406
  }
59221
60407
  var DEVICE_MAX_WAIT_MS = 15 * 6e4;
59222
60408
  function defaultSleep(ms, signal) {
59223
- return new Promise((resolve38, reject2) => {
60409
+ return new Promise((resolve39, reject2) => {
59224
60410
  if (signal?.aborted) return reject2(new AmaError("aborted", "login aborted"));
59225
- const timer = setTimeout(resolve38, ms);
60411
+ const timer = setTimeout(resolve39, ms);
59226
60412
  signal?.addEventListener(
59227
60413
  "abort",
59228
60414
  () => {
@@ -61053,12 +62239,12 @@ var RATE_LIMIT_RETRY_MS = 2e3;
61053
62239
  var RECOVER_AFTER = 4;
61054
62240
  var FATAL_STATUS = /^(?:HTTP )?(?:401|403|429)\b/;
61055
62241
  var RATE_LIMITED = /^(?:HTTP )?429\b/;
61056
- var sleep5 = (ms, signal) => new Promise((resolve38) => {
62242
+ var sleep5 = (ms, signal) => new Promise((resolve39) => {
61057
62243
  const timer = setTimeout(done, ms);
61058
62244
  function done() {
61059
62245
  clearTimeout(timer);
61060
62246
  signal?.removeEventListener("abort", done);
61061
- resolve38();
62247
+ resolve39();
61062
62248
  }
61063
62249
  signal?.addEventListener("abort", done, { once: true });
61064
62250
  });
@@ -61185,7 +62371,7 @@ var ProbeScheduler = class {
61185
62371
  async run(count2, task, onDone) {
61186
62372
  const results = new Array(count2).fill(void 0);
61187
62373
  let next = 0;
61188
- await new Promise((resolve38, reject2) => {
62374
+ await new Promise((resolve39, reject2) => {
61189
62375
  const pump = () => {
61190
62376
  while (this.stopReason === void 0 && next < count2 && this.active < this.limit) {
61191
62377
  const index2 = next++;
@@ -61205,7 +62391,7 @@ var ProbeScheduler = class {
61205
62391
  }
61206
62392
  );
61207
62393
  }
61208
- if (this.active === 0 && (next >= count2 || this.stopReason !== void 0)) resolve38();
62394
+ if (this.active === 0 && (next >= count2 || this.stopReason !== void 0)) resolve39();
61209
62395
  };
61210
62396
  pump();
61211
62397
  });
@@ -61443,10 +62629,15 @@ async function run6(ctx) {
61443
62629
  const fields = match !== void 0 ? modelsDevFields(match.model) : void 0;
61444
62630
  if (fields?.toolCall === false) noTools.add(model.id);
61445
62631
  if (match === void 0) unmatched.add(model.id);
61446
- const meta2 = fields === void 0 ? m.unmatched : ` ctx ${compactTokens(fields.contextWindow)} · out ${compactTokens(fields.maxTokens)}${fields.input?.includes("image") ? m.image : ""}${fields.reasoning ? m.reasoning : ""}${fields.toolCall === false ? m.noTools : ""} · ${matchLabel(match)}`;
62632
+ const backendCtx = model.contextWindow !== void 0 ? ` ctx ${compactTokens(model.contextWindow)}${m.fromBackend}` : void 0;
62633
+ const meta2 = fields === void 0 ? `${backendCtx ?? ""}${m.unmatched}` : `${backendCtx ?? ` ctx ${compactTokens(fields.contextWindow)}`} · out ${compactTokens(fields.maxTokens)}${fields.input?.includes("image") ? m.image : ""}${fields.reasoning ? m.reasoning : ""}${fields.toolCall === false ? m.noTools : ""} · ${matchLabel(match)}`;
61447
62634
  io.stdout(` ${model.id}${known !== void 0 ? m.configured(known.api) : ""}${meta2}
61448
62635
  `);
61449
62636
  }
62637
+ if (provider.id === CHATGPT_PROVIDER_ID) {
62638
+ const missing = found.filter((x) => x.contextWindow === void 0).map((x) => x.id);
62639
+ if (missing.length > 0) io.stdout(m.noBackendWindow(missing.join(", ")));
62640
+ }
61450
62641
  const write2 = ctx.flags.has("write");
61451
62642
  if (!ctx.flags.has("probe") || provider.id === CHATGPT_PROVIDER_ID) {
61452
62643
  if (write2)
@@ -61602,13 +62793,13 @@ async function disable(ctx) {
61602
62793
  }
61603
62794
  async function listEnabled(ctx) {
61604
62795
  const m = msg().subcommands.modelsEnabled;
61605
- const labels = msg().panels.model;
62796
+ const labels2 = msg().panels.model;
61606
62797
  const list5 = enabledList(ctx.level.merged.config.models?.enabled);
61607
62798
  const status = async (providerId) => {
61608
62799
  const provider = ctx.registry.get(providerId);
61609
62800
  if (provider === void 0) return "?";
61610
62801
  const access = await providerAccess(ctx.registry, provider);
61611
- return access === "none" ? labels.noKey : labels[access];
62802
+ return access === "none" ? labels2.noKey : labels2[access];
61612
62803
  };
61613
62804
  if (list5 !== void 0) {
61614
62805
  ctx.io.stdout(m.listHeader(ctx.level.userConfigPath));
@@ -61630,7 +62821,7 @@ async function listEnabled(ctx) {
61630
62821
  for (const provider of ctx.registry.list()) {
61631
62822
  const access = await providerAccess(ctx.registry, provider);
61632
62823
  if (!accessReady(access)) continue;
61633
- const label2 = access === "none" ? labels.noKey : labels[access];
62824
+ const label2 = access === "none" ? labels2.noKey : labels2[access];
61634
62825
  for (const model of provider.models) {
61635
62826
  ctx.io.stdout(` ${provider.id}/${model.id} ${label2}
61636
62827
  `);
@@ -63887,19 +65078,19 @@ function redactedLookup(lookup) {
63887
65078
  }
63888
65079
  function axisPoints(trace) {
63889
65080
  const t0 = trace.startedAt;
63890
- const windows = [];
65081
+ const windows2 = [];
63891
65082
  for (const turn of trace.turns) {
63892
65083
  const span = turnSpan(turn);
63893
- if (span !== void 0) windows.push([span.start - t0, span.end - t0]);
65084
+ if (span !== void 0) windows2.push([span.start - t0, span.end - t0]);
63894
65085
  }
63895
65086
  for (const aux of trace.aux)
63896
65087
  if (aux.startedAt !== void 0)
63897
- windows.push([aux.startedAt - t0, (aux.endedAt ?? aux.startedAt) - t0]);
63898
- windows.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
65088
+ windows2.push([aux.startedAt - t0, (aux.endedAt ?? aux.startedAt) - t0]);
65089
+ windows2.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
63899
65090
  const points = [[0, 0]];
63900
65091
  let raw = 0;
63901
65092
  let mapped = 0;
63902
- for (const [start, end] of windows) {
65093
+ for (const [start, end] of windows2) {
63903
65094
  if (end <= raw) continue;
63904
65095
  const from = Math.max(start, raw);
63905
65096
  if (from > raw) {
@@ -64384,14 +65575,14 @@ var registeredDeps;
64384
65575
  function readStdinDefault() {
64385
65576
  const stdin = process.stdin;
64386
65577
  if (!stdin.isTTY) {
64387
- return new Promise((resolve38, reject2) => {
65578
+ return new Promise((resolve39, reject2) => {
64388
65579
  const chunks2 = [];
64389
65580
  stdin.on("data", (chunk) => chunks2.push(chunk));
64390
- stdin.once("end", () => resolve38(Buffer.concat(chunks2).toString("utf8")));
65581
+ stdin.once("end", () => resolve39(Buffer.concat(chunks2).toString("utf8")));
64391
65582
  stdin.once("error", reject2);
64392
65583
  });
64393
65584
  }
64394
- return new Promise((resolve38) => {
65585
+ return new Promise((resolve39) => {
64395
65586
  let line2 = "";
64396
65587
  stdin.setRawMode(true);
64397
65588
  stdin.resume();
@@ -64401,14 +65592,14 @@ function readStdinDefault() {
64401
65592
  stdin.off("data", onData);
64402
65593
  stdin.setRawMode(false);
64403
65594
  stdin.pause();
64404
- resolve38(line2);
65595
+ resolve39(line2);
64405
65596
  return;
64406
65597
  }
64407
65598
  if (ch2 === "") {
64408
65599
  stdin.off("data", onData);
64409
65600
  stdin.setRawMode(false);
64410
65601
  stdin.pause();
64411
- resolve38("");
65602
+ resolve39("");
64412
65603
  return;
64413
65604
  }
64414
65605
  if (ch2 === "" || ch2 === "\b") line2 = line2.slice(0, -1);
@@ -64432,7 +65623,7 @@ function stdinKindDefault() {
64432
65623
  }
64433
65624
  function readStdinFirstByteDefault(timeoutMs) {
64434
65625
  const stdin = process.stdin;
64435
- return new Promise((resolve38, reject2) => {
65626
+ return new Promise((resolve39, reject2) => {
64436
65627
  const chunks2 = [];
64437
65628
  let timer;
64438
65629
  const cleanup = () => {
@@ -64448,7 +65639,7 @@ function readStdinFirstByteDefault(timeoutMs) {
64448
65639
  };
64449
65640
  const onEnd = () => {
64450
65641
  cleanup();
64451
- resolve38(Buffer.concat(chunks2).toString("utf8"));
65642
+ resolve39(Buffer.concat(chunks2).toString("utf8"));
64452
65643
  };
64453
65644
  const onError = (error2) => {
64454
65645
  cleanup();
@@ -64458,7 +65649,7 @@ function readStdinFirstByteDefault(timeoutMs) {
64458
65649
  cleanup();
64459
65650
  stdin.pause();
64460
65651
  stdin.destroy();
64461
- resolve38(void 0);
65652
+ resolve39(void 0);
64462
65653
  }, timeoutMs);
64463
65654
  stdin.on("data", onData);
64464
65655
  stdin.once("end", onEnd);