@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
@@ -5,7 +5,8 @@
5
5
  * `agentDirs` → config `agents.dirs` → 用户级 → 项目级(需信任)),建类型目录并绑到 task 工具
6
6
  * (描述里的类型清单);工厂给每个根会话装一个扩展:建任务注册表(subagent-registry.ts,并发 /
7
7
  * 排队上限与模型别名取自 config),`getStats().tasks` 汇总,会话 dispose 时停止后台任务。
8
- * task 子会话(depth ≥ 1)与没有 task 工具的会话不装。
8
+ * task 子会话(depth ≥ 1)与没有 task 工具的会话不装。[W7-B1] `subagents.background`(auto:交互 / RPC /
9
+ * ACP 后台、`-p` 前台)解析成本进程的缺省,同时决定 task 描述的版本;`autoBackgroundAfterMs` 透传。
9
10
  *
10
11
  * [W5-EG] 外部 runner(claude / codex / acp:* / 宿主注入)经 agents/external.ts 接入 `runners`:每个主
11
12
  * 会话一个 ExternalAgents(审批接 requestApproval,只交给人);PATH 上的 claude / codex 登记进类型
@@ -5,7 +5,8 @@
5
5
  * `agentDirs` → config `agents.dirs` → 用户级 → 项目级(需信任)),建类型目录并绑到 task 工具
6
6
  * (描述里的类型清单);工厂给每个根会话装一个扩展:建任务注册表(subagent-registry.ts,并发 /
7
7
  * 排队上限与模型别名取自 config),`getStats().tasks` 汇总,会话 dispose 时停止后台任务。
8
- * task 子会话(depth ≥ 1)与没有 task 工具的会话不装。
8
+ * task 子会话(depth ≥ 1)与没有 task 工具的会话不装。[W7-B1] `subagents.background`(auto:交互 / RPC /
9
+ * ACP 后台、`-p` 前台)解析成本进程的缺省,同时决定 task 描述的版本;`autoBackgroundAfterMs` 透传。
9
10
  *
10
11
  * [W5-EG] 外部 runner(claude / codex / acp:* / 宿主注入)经 agents/external.ts 接入 `runners`:每个主
11
12
  * 会话一个 ExternalAgents(审批接 requestApproval,只交给人);PATH 上的 claude / codex 登记进类型
@@ -17,7 +18,8 @@ import { AgentCatalog } from "../agents/catalog.js";
17
18
  import { agentSources, discoverAgents } from "../agents/discover.js";
18
19
  import { NO_AGENT_PROBE_ENV, SessionExternalAgents, registerExternalAgents, } from "../agents/external.js";
19
20
  import { hostRunnersOf } from "../host/api-impl.js";
20
- import { bindTaskAgents } from "../tools/task.js";
21
+ import { bindTaskAgents, bindTaskBackground } from "../tools/task.js";
22
+ import { resolveTaskBackground } from "../agent/subagent-background.js";
21
23
  import { msg } from "../i18n/index.js";
22
24
  /** 同一装配材料只提示一次(`/new` 等会重新装配会话)。 */
23
25
  const warned = new WeakSet();
@@ -54,6 +56,12 @@ export function subagentEnvironment(deps, catalog) {
54
56
  const pending = config.subagents?.maxPending;
55
57
  if (pending !== undefined)
56
58
  env.maxPending = pending;
59
+ // [W7-B1] 缺省后台与自动转后台(配置键 W7-B2 登记于 config/types-w5.ts)
60
+ const sub = config.subagents;
61
+ env.background = resolveTaskBackground(sub?.background, deps.assembly.unattended === true);
62
+ const after = sub?.autoBackgroundAfterMs;
63
+ if (typeof after === "number" && after > 0)
64
+ env.autoBackgroundAfterMs = after;
57
65
  return env;
58
66
  }
59
67
  /** 外部 Agent 的装配材料(宿主、信任、探测开关取自装配材料与环境)。 */
@@ -108,5 +116,6 @@ export function createSubagentsFactory(deps) {
108
116
  registerExternalAgents(catalog, wiring);
109
117
  bindTaskAgents(task, catalog);
110
118
  const env = subagentEnvironment(deps, catalog);
119
+ bindTaskBackground(task, env.background === true);
111
120
  return ({ core }) => (core.depth > 0 ? undefined : createSubagentExtension(core, env, wiring));
112
121
  }
@@ -21,6 +21,7 @@
21
21
  * `GET /models?client_version=<codexClientVersion>`,另带上下文窗口 / 输入模态 / 推理强度),取 slug 与显示名;
22
22
  * `--probe` 不适用(订阅后端只有一种协议)。结果另写进发现缓存 `<dataDir>/models/discovered/chatgpt.json`
23
23
  * (discovered-cache.ts,0 个也写),`/model` 选择器由此列出;codex 返回 0 个时提示 codexClientVersion 可能过旧。
24
+ * 后端给了上下文窗口就显示它(标「后端」),注册表同样以它为准;没给的列出来,说明改用 models.dev。
24
25
  */
25
26
  import type { Api, Model, ProviderData, ProviderRegistryApi } from "../../ai/types.js";
26
27
  import type { ModelsAction } from "./models.js";
@@ -21,6 +21,7 @@
21
21
  * `GET /models?client_version=<codexClientVersion>`,另带上下文窗口 / 输入模态 / 推理强度),取 slug 与显示名;
22
22
  * `--probe` 不适用(订阅后端只有一种协议)。结果另写进发现缓存 `<dataDir>/models/discovered/chatgpt.json`
23
23
  * (discovered-cache.ts,0 个也写),`/model` 选择器由此列出;codex 返回 0 个时提示 codexClientVersion 可能过旧。
24
+ * 后端给了上下文窗口就显示它(标「后端」),注册表同样以它为准;没给的列出来,说明改用 models.dev。
24
25
  */
25
26
  import { authHeaders, mergeHeaders } from "../../ai/http.js";
26
27
  import { listChatGptModels } from "../../auth/chatgpt/backend-client.js";
@@ -226,13 +227,22 @@ async function run(ctx) {
226
227
  noTools.add(model.id);
227
228
  if (match === undefined)
228
229
  unmatched.add(model.id);
230
+ // 后端给的上下文窗口优先于 models.dev(订阅后端的生效窗口可能远小于 API 版)
231
+ const backendCtx = model.contextWindow !== undefined
232
+ ? ` ctx ${compactTokens(model.contextWindow)}${m.fromBackend}`
233
+ : undefined;
229
234
  const meta = fields === undefined
230
- ? m.unmatched
231
- : ` ctx ${compactTokens(fields.contextWindow)} · out ${compactTokens(fields.maxTokens)}` +
235
+ ? `${backendCtx ?? ""}${m.unmatched}`
236
+ : `${backendCtx ?? ` ctx ${compactTokens(fields.contextWindow)}`} · out ${compactTokens(fields.maxTokens)}` +
232
237
  `${fields.input?.includes("image") ? m.image : ""}${fields.reasoning ? m.reasoning : ""}` +
233
238
  `${fields.toolCall === false ? m.noTools : ""} · ${matchLabel(match)}`;
234
239
  io.stdout(` ${model.id}${known !== undefined ? m.configured(known.api) : ""}${meta}\n`);
235
240
  }
241
+ if (provider.id === CHATGPT_PROVIDER_ID) {
242
+ const missing = found.filter((x) => x.contextWindow === undefined).map((x) => x.id);
243
+ if (missing.length > 0)
244
+ io.stdout(m.noBackendWindow(missing.join(", ")));
245
+ }
236
246
  const write = ctx.flags.has("write");
237
247
  if (!ctx.flags.has("probe") || provider.id === CHATGPT_PROVIDER_ID) {
238
248
  if (write)
@@ -13,7 +13,7 @@ import { defaultFor, isDynamicDefault, keyDoc } from "./key-docs.js";
13
13
  import { HOOK_TIMEOUT_MAX_MS, THINKING_LEVELS } from "./schema.js";
14
14
  import { CACHE_RETENTIONS, CHANNEL_NAME_PATTERN, CHECKPOINT_MODES, SANDBOX_ENABLED_MODES, SANDBOX_NETWORK_MODES, CODEMODE_MODES, IMAGE_RESIZE_MODES, PLAN_BASH_MODES_STRICT_FIRST, PLAN_UNATTENDED_MODES, STATUS_LINE_MODES, TOOLS_PRESET_INPUTS, LANGUAGE_SETTINGS, AGENT_BAR_MODES, CHATGPT_FLAVORS, MEMORY_SCOPES, MEMORY_SUBAGENT_MODES, } from "./types.js";
15
15
  import { AGENT_ID_PATTERN } from "./schema-w5.js";
16
- import { AGENTS_RESERVED_KEYS } from "./types-w5.js";
16
+ import { AGENTS_RESERVED_KEYS, SUBAGENT_BACKGROUND_MODES } from "./types-w5.js";
17
17
  export const CONFIG_SCHEMA_FILE = "config.schema.json";
18
18
  export const CONFIG_SCHEMA_ID = "https://github.com/Owlbay/armadra-agent/config.schema.json";
19
19
  const KNOWN_APIS = [
@@ -150,7 +150,13 @@ function w5Sections() {
150
150
  pattern: `^(${AGENTS_RESERVED_KEYS.join("|")}|${AGENT_ID_PATTERN.source.slice(1, -1)})$`,
151
151
  },
152
152
  }),
153
- subagents: object({ maxConcurrent: num(1, 64), maxPending: num(0, 1024), defaultModel: str() }),
153
+ subagents: object({
154
+ maxConcurrent: num(1, 64),
155
+ maxPending: num(0, 1024),
156
+ defaultModel: str(),
157
+ background: oneOf(SUBAGENT_BACKGROUND_MODES),
158
+ autoBackgroundAfterMs: num(0),
159
+ }),
154
160
  models: object({ aliases: object({ fast: str(), strong: str() }), enabled: strings }),
155
161
  fallbackModel: str(),
156
162
  limits: object({ maxTurns: num(1), maxCostUsd: num(0) }),
@@ -271,6 +277,7 @@ function buildBaseSchema() {
271
277
  quietStartup: oneOf(["normal", "header", "silent"]),
272
278
  ascii: bool(),
273
279
  compact: bool(),
280
+ logo: oneOf(["auto", "off"]),
274
281
  animation: bool(),
275
282
  restoreOnCancel: bool(),
276
283
  statusLine: oneOf(STATUS_LINE_MODES),
@@ -23,7 +23,7 @@ const IMPLICIT_DEFAULTS = {
23
23
  codemode: { inlineBudget: DEFAULT_INLINE_BUDGET, requireStrict: false },
24
24
  cache: { ...DEFAULT_CACHE_CONFIG },
25
25
  request: { idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS },
26
- ui: { compact: false, animation: true, restoreOnCancel: true, language: "auto" },
26
+ ui: { compact: false, logo: "auto", animation: true, restoreOnCancel: true, language: "auto" },
27
27
  // [W6-C0] 第六波键的缺省(行为由各批次实现)
28
28
  memory: {
29
29
  enabled: false,
@@ -48,7 +48,7 @@ const IMPLICIT_DEFAULTS = {
48
48
  images: { resize: "auto" },
49
49
  plan: { bash: "readonly", unattended: "stop" },
50
50
  agents: { maxConcurrent: 3, dirs: [] },
51
- subagents: { maxConcurrent: 4, maxPending: 16 },
51
+ subagents: { maxConcurrent: 4, maxPending: 16, background: "auto", autoBackgroundAfterMs: 0 },
52
52
  reminders: { todo: true, fileChanges: true, contextPressure: true, budget: true },
53
53
  todo: { reminder: 10 },
54
54
  };
@@ -9,7 +9,7 @@
9
9
  * `compaction`、`tools.disabled`、`tools.preset`(只能更严)、`codemode.mode: "off"`、`ui`、
10
10
  * `checkpoints.mode: "off"`、`checkpoints.maxFileBytes`(只能调小)、(W5-C0)`plan.bash`(只能更严)、
11
11
  * `reminders`;`compaction.prune / pruneExclude` 与第五波其余段只认用户级;(W6-C0)`ui.language` /
12
- * `ui.agentBar` 随 `ui` 段、`memory.enabled: false`;`ui.replyLanguage`、`memory` 其余键、`auth` 只认用户级;
12
+ * `ui.agentBar` 随 `ui` 段、`memory.enabled: false`;(W7-B2)`subagents.background / autoBackgroundAfterMs`;`ui.replyLanguage`、`memory` 其余键、`auth` 只认用户级;
13
13
  * 其它字段与放宽项(含 `permission.builtinDeny / autoModel / autoSafeCommands`)被忽略并记 warning。
14
14
  * - 同时产出带来源的权限规则清单(`ruleSpecs`),交给权限管线(B3 的 rules.ts 解析)。
15
15
  */
@@ -9,7 +9,7 @@
9
9
  * `compaction`、`tools.disabled`、`tools.preset`(只能更严)、`codemode.mode: "off"`、`ui`、
10
10
  * `checkpoints.mode: "off"`、`checkpoints.maxFileBytes`(只能调小)、(W5-C0)`plan.bash`(只能更严)、
11
11
  * `reminders`;`compaction.prune / pruneExclude` 与第五波其余段只认用户级;(W6-C0)`ui.language` /
12
- * `ui.agentBar` 随 `ui` 段、`memory.enabled: false`;`ui.replyLanguage`、`memory` 其余键、`auth` 只认用户级;
12
+ * `ui.agentBar` 随 `ui` 段、`memory.enabled: false`;(W7-B2)`subagents.background / autoBackgroundAfterMs`;`ui.replyLanguage`、`memory` 其余键、`auth` 只认用户级;
13
13
  * 其它字段与放宽项(含 `permission.builtinDeny / autoModel / autoSafeCommands`)被忽略并记 warning。
14
14
  * - 同时产出带来源的权限规则清单(`ruleSpecs`),交给权限管线(B3 的 rules.ts 解析)。
15
15
  */
@@ -35,8 +35,9 @@ export const DEFAULT_CONFIG = Object.freeze({
35
35
  });
36
36
  /** 嵌入宿主(有 profile)时的缺省覆盖(§12.10)。 */
37
37
  export const PROFILE_DEFAULTS = Object.freeze({
38
- // [W5-A] 嵌入宿主以「最后一行 = 状态栏」锚定,底部信息行缺省单行;[W6-C0] 宿主自己展示节点,Agent 栏缺省关
39
- ui: { quietStartup: "header", statusLine: "compact", agentBar: "off" },
38
+ // [W5-A] 嵌入宿主以「最后一行 = 状态栏」锚定,底部信息行缺省单行;[W7-A] Agent 栏不再缺省关,
39
+ // 宿主要关就在自己的 profile 写 `ui.agentBar: "off"`
40
+ ui: { quietStartup: "header", statusLine: "compact" },
40
41
  });
41
42
  /** 层名 → 规则来源(RuleSource)。 */
42
43
  const RULE_SOURCE = {
@@ -120,6 +121,22 @@ export function restrictProjectConfig(project, currentMode, label = ".ama/config
120
121
  if (project.reminders !== undefined)
121
122
  accepted.reminders = structuredClone(project.reminders);
122
123
  break;
124
+ case "subagents": {
125
+ // [W7-B2] 前台 / 后台只影响呈现与等待,不放宽权限:项目级可设;并发、模型仍只认用户级
126
+ const subagents = project.subagents ?? {};
127
+ const result = {};
128
+ for (const sub of Object.keys(subagents)) {
129
+ if (sub !== "background" && sub !== "autoBackgroundAfterMs")
130
+ warnings.push(msg().config.merge.projectOnlyKeys(label, "subagents.background / subagents.autoBackgroundAfterMs", `subagents.${sub}`));
131
+ }
132
+ if (subagents.background !== undefined)
133
+ result.background = subagents.background;
134
+ if (subagents.autoBackgroundAfterMs !== undefined)
135
+ result.autoBackgroundAfterMs = subagents.autoBackgroundAfterMs;
136
+ if (Object.keys(result).length > 0)
137
+ accepted.subagents = result;
138
+ break;
139
+ }
123
140
  case "plan": {
124
141
  const plan = restrictPlan(project.plan ?? {}, currentPlanBash, label, warnings);
125
142
  if (plan !== undefined)
@@ -7,7 +7,7 @@
7
7
  import { PERMISSION_MODES_STRICT_FIRST } from "../permissions/types.js";
8
8
  import { msg } from "../i18n/index.js";
9
9
  import { Checker, THINKING_LEVELS, checkSection, join } from "./checker.js";
10
- import { AGENTS_RESERVED_KEYS, IMAGE_RESIZE_MODES, MODELS_ENABLED_REF, PLAN_BASH_MODES_STRICT_FIRST, PLAN_UNATTENDED_MODES, STATUS_LINE_MODES, } from "./types-w5.js";
10
+ import { AGENTS_RESERVED_KEYS, IMAGE_RESIZE_MODES, MODELS_ENABLED_REF, PLAN_BASH_MODES_STRICT_FIRST, PLAN_UNATTENDED_MODES, STATUS_LINE_MODES, SUBAGENT_BACKGROUND_MODES, } from "./types-w5.js";
11
11
  /** 第五波新增的顶层键(schema.ts 的 CONFIG_KEYS 并入)。 */
12
12
  export const W5_CONFIG_KEYS = [
13
13
  "images",
@@ -83,10 +83,12 @@ export function validateConfigW5(c, config) {
83
83
  c.oneOf(s, "thinkingLevel", p, THINKING_LEVELS);
84
84
  });
85
85
  checkAgents(c, config);
86
- checkSection(c, config, "subagents", ["maxConcurrent", "maxPending", "defaultModel"], (s, p) => {
86
+ checkSection(c, config, "subagents", ["maxConcurrent", "maxPending", "defaultModel", "background", "autoBackgroundAfterMs"], (s, p) => {
87
87
  c.number(s, "maxConcurrent", p, 1, 64);
88
88
  c.number(s, "maxPending", p, 0, 1024);
89
89
  c.string(s, "defaultModel", p);
90
+ c.oneOf(s, "background", p, SUBAGENT_BACKGROUND_MODES);
91
+ c.number(s, "autoBackgroundAfterMs", p, 0);
90
92
  });
91
93
  checkSection(c, config, "models", ["aliases", "enabled"], (s, p) => {
92
94
  c.stringArray(s, "enabled", p);
@@ -257,6 +257,7 @@ export function validateConfig(value) {
257
257
  "quietStartup",
258
258
  "ascii",
259
259
  "compact",
260
+ "logo",
260
261
  "animation",
261
262
  "restoreOnCancel",
262
263
  ...W5_UI_KEYS,
@@ -269,6 +270,7 @@ export function validateConfig(value) {
269
270
  c.oneOf(s, "quietStartup", p, ["normal", "header", "silent"]);
270
271
  c.boolean(s, "ascii", p);
271
272
  c.boolean(s, "compact", p);
273
+ c.oneOf(s, "logo", p, ["auto", "off"]);
272
274
  c.boolean(s, "animation", p);
273
275
  c.boolean(s, "restoreOnCancel", p);
274
276
  checkUiW5(c, s, p);
@@ -31,6 +31,7 @@ const ROWS = [
31
31
  row("ui.showThinking", "ui", "now", "any"),
32
32
  row("ui.compact", "ui", "now", "any"),
33
33
  row("ui.animation", "ui", "now", "any"),
34
+ row("ui.logo", "ui", "restart", "any"),
34
35
  row("ui.statusLine", "ui", "now", "any"),
35
36
  row("ui.restoreOnCancel", "ui", "nextSession", "any"),
36
37
  row("ui.quietStartup", "ui", "restart", "any"),
@@ -92,6 +93,9 @@ const ROWS = [
92
93
  row("agents.sessionBudgetUsd", "agents", "nextSession", "deny", { kind: "optionalNumber" }),
93
94
  row("subagents.maxConcurrent", "agents", "nextSession", "deny"),
94
95
  row("subagents.maxPending", "agents", "nextSession", "deny"),
96
+ // [W7-B2] 改 task 工具描述(缺省后台 / 前台两种文案),故 prefix
97
+ row("subagents.background", "agents", "restart", "any", { prefix: true }),
98
+ row("subagents.autoBackgroundAfterMs", "agents", "restart", "any"),
95
99
  // [W6-M]
96
100
  row("memory.enabled", "memory", "nextSession", "tighten", { envOverride: "AMA_MEMORY" }),
97
101
  row("memory.subagents", "memory", "nextSession", "deny"),
@@ -59,6 +59,9 @@ export interface AgentsConfig {
59
59
  }
60
60
  /** 取 `agents.<id>`(保留键返回 undefined)。 */
61
61
  export declare function agentEntry(agents: AgentsConfig | undefined, agentId: string): AgentEntryConfig | undefined;
62
+ /** [W7-B2] 子 Agent 缺省前台还是后台(docs/agents-concurrency-plan.md §2.6)。 */
63
+ export declare const SUBAGENT_BACKGROUND_MODES: readonly ["auto", "always", "never"];
64
+ export type SubagentBackgroundMode = (typeof SUBAGENT_BACKGROUND_MODES)[number];
62
65
  /** [W5-G] ama 自己的子会话。 */
63
66
  export interface SubagentsConfig {
64
67
  /** 同时运行的子会话,缺省 4。 */
@@ -67,6 +70,13 @@ export interface SubagentsConfig {
67
70
  maxPending?: number;
68
71
  /** 子会话缺省模型;不设继承父会话。 */
69
72
  defaultModel?: string;
73
+ /**
74
+ * [W7-B2] task 缺省后台:auto(缺省)= 交互 / RPC / ACP 下后台、`-p` 下前台;always / never 固定。
75
+ * 优先级:调用参数 > 类型定义 > 本键。用户 / 项目 / 宿主级都认。
76
+ */
77
+ background?: SubagentBackgroundMode;
78
+ /** [W7-B2] 前台任务运行超过该毫秒数自动转后台;缺省 0 关闭。用户 / 项目级都认。 */
79
+ autoBackgroundAfterMs?: number;
70
80
  }
71
81
  /** `models.enabled` 的一项:`provider/model[@channel]` 或 `provider/*`。 */
72
82
  export declare const MODELS_ENABLED_REF: RegExp;
@@ -18,5 +18,7 @@ export function agentEntry(agents, agentId) {
18
18
  const entry = agents[agentId];
19
19
  return typeof entry === "object" && !Array.isArray(entry) ? entry : undefined;
20
20
  }
21
+ /** [W7-B2] 子 Agent 缺省前台还是后台(docs/agents-concurrency-plan.md §2.6)。 */
22
+ export const SUBAGENT_BACKGROUND_MODES = ["auto", "always", "never"];
21
23
  /** `models.enabled` 的一项:`provider/model[@channel]` 或 `provider/*`。 */
22
24
  export const MODELS_ENABLED_REF = /^[^/\s]+\/\S+$/;
@@ -143,8 +143,10 @@ export interface UiConfig extends UiConfigW6 {
143
143
  quietStartup?: "normal" | "header" | "silent";
144
144
  /** ASCII 字形(`AMA_ASCII=1` 等价);缺省按区域设置 / TERM 自动检测。 */
145
145
  ascii?: boolean;
146
- /** 消息区块间不空行、启动头无框,缺省 false。 */
146
+ /** 消息区块间不空行、启动头不画字符画,缺省 false。 */
147
147
  compact?: boolean;
148
+ /** 启动头的「AMA」字符画:auto(缺省,宽度够时画)/ off(只画信息列)。 */
149
+ logo?: "auto" | "off";
148
150
  /** false:运行中 spinner 固定为 `·`,缺省 true。 */
149
151
  animation?: boolean;
150
152
  /** 运行中 Esc 中断、本回合还没有任何输出时撤回该回合并回填原消息,缺省 true。 */
@@ -7,6 +7,9 @@
7
7
  * `GIT_OPTIONAL_LOCKS=0`;两次之间 ≥ `minIntervalMs`(缺省 10 s),超过 `timeoutMs`(缺省 2 s)或
8
8
  * 非零退出 / 起不来就省略增删项,并在本实例(本会话)停用 numstat,HEAD 仍照常显示。
9
9
  * `AMA_STATUS_GIT=0` 直接关闭 numstat(排错用)。
10
+ * - [W6] 领先 / 落后上游:当前分支在 git 配置里有 `branch.<名>.merge`(零依赖读 config,不起进程判断)时,
11
+ * numstat 之后同一节流周期再跑 `git rev-list --left-right --count @{upstream}...HEAD`(同样的超时与环境);
12
+ * 没有上游、detached、非零退出就省略;超时则本会话不再统计领先 / 落后。`AMA_STATUS_GIT=0` 同样关闭。
10
13
  * - 非 git 目录 `current()` 为 undefined(状态行整段省略)。
11
14
  * - `refresh()` 在回合边界调用(agent_settled、写类工具结束、回滚、/tree),只发起读取、不阻塞;
12
15
  * 结果有变化时通知 `onChange` 的监听者。
@@ -19,6 +22,9 @@ export interface GitInfo {
19
22
  /** 工作区(含暂存)相对 HEAD;拿不到为 undefined。 */
20
23
  insertions?: number;
21
24
  deletions?: number;
25
+ /** [W6] 领先 / 落后上游的提交数;没有上游为 undefined。 */
26
+ ahead?: number;
27
+ behind?: number;
22
28
  }
23
29
  /** numstat 子进程的最小接口(测试注入)。 */
24
30
  export interface NumstatProcess {
@@ -45,6 +51,13 @@ export declare function parseNumstat(text: string): {
45
51
  insertions: number;
46
52
  deletions: number;
47
53
  };
54
+ /** `git rev-list --left-right --count @{upstream}...HEAD` 的输出:左(上游独有)= 落后,右 = 领先。 */
55
+ export declare function parseLeftRight(text: string): {
56
+ ahead: number;
57
+ behind: number;
58
+ } | undefined;
59
+ /** 分支在 git 配置里有没有上游(`[branch "<名>"]` 段里的 `merge`);worktree 读主仓库的 config。 */
60
+ export declare function hasUpstream(cwd: string, branch: string): Promise<boolean>;
48
61
  export declare class GitInfoWatcher {
49
62
  private readonly cwd;
50
63
  private info;
@@ -61,6 +74,8 @@ export declare class GitInfoWatcher {
61
74
  private headAgain;
62
75
  private disposed;
63
76
  private diff;
77
+ private upstream;
78
+ private upstreamDisabled;
64
79
  constructor(cwd: string, deps?: GitInfoDeps);
65
80
  current(): GitInfo | undefined;
66
81
  /** numstat 已因超时 / 失败 / 环境变量停用。 */
@@ -71,5 +86,9 @@ export declare class GitInfoWatcher {
71
86
  dispose(): void;
72
87
  private update;
73
88
  private maybeNumstat;
89
+ /** 领先 / 落后上游:只在配置里有上游时起进程;超时本会话停用,非零退出只省略这一次。 */
90
+ private leftRight;
74
91
  private numstat;
92
+ /** 后台起一次 git;起不来为 undefined,超时杀掉并返回 `"timeout"`。 */
93
+ private run;
75
94
  }
package/dist/git/info.js CHANGED
@@ -7,13 +7,16 @@
7
7
  * `GIT_OPTIONAL_LOCKS=0`;两次之间 ≥ `minIntervalMs`(缺省 10 s),超过 `timeoutMs`(缺省 2 s)或
8
8
  * 非零退出 / 起不来就省略增删项,并在本实例(本会话)停用 numstat,HEAD 仍照常显示。
9
9
  * `AMA_STATUS_GIT=0` 直接关闭 numstat(排错用)。
10
+ * - [W6] 领先 / 落后上游:当前分支在 git 配置里有 `branch.<名>.merge`(零依赖读 config,不起进程判断)时,
11
+ * numstat 之后同一节流周期再跑 `git rev-list --left-right --count @{upstream}...HEAD`(同样的超时与环境);
12
+ * 没有上游、detached、非零退出就省略;超时则本会话不再统计领先 / 落后。`AMA_STATUS_GIT=0` 同样关闭。
10
13
  * - 非 git 目录 `current()` 为 undefined(状态行整段省略)。
11
14
  * - `refresh()` 在回合边界调用(agent_settled、写类工具结束、回滚、/tree),只发起读取、不阻塞;
12
15
  * 结果有变化时通知 `onChange` 的监听者。
13
16
  */
14
17
  import { spawn as nodeSpawn } from "node:child_process";
15
18
  import { readFile } from "node:fs/promises";
16
- import { join } from "node:path";
19
+ import { isAbsolute, join, resolve } from "node:path";
17
20
  import { findGitDir, readGitHead } from "../checkpoints/git-head.js";
18
21
  export const GIT_NUMSTAT_INTERVAL_MS = 10_000;
19
22
  export const GIT_NUMSTAT_TIMEOUT_MS = 2_000;
@@ -30,6 +33,32 @@ export function parseNumstat(text) {
30
33
  }
31
34
  return { insertions, deletions };
32
35
  }
36
+ /** `git rev-list --left-right --count @{upstream}...HEAD` 的输出:左(上游独有)= 落后,右 = 领先。 */
37
+ export function parseLeftRight(text) {
38
+ const match = /^\s*(\d+)\s+(\d+)\s*$/.exec(text);
39
+ return match === null ? undefined : { behind: Number(match[1]), ahead: Number(match[2]) };
40
+ }
41
+ /** 分支在 git 配置里有没有上游(`[branch "<名>"]` 段里的 `merge`);worktree 读主仓库的 config。 */
42
+ export async function hasUpstream(cwd, branch) {
43
+ const gitDir = await findGitDir(cwd);
44
+ if (gitDir === undefined)
45
+ return false;
46
+ const common = (await readFile(join(gitDir, "commondir"), "utf8").catch(() => undefined))?.trim();
47
+ const dir = common === undefined ? gitDir : isAbsolute(common) ? common : resolve(gitDir, common);
48
+ const text = await readFile(join(dir, "config"), "utf8").catch(() => "");
49
+ let inBranch = false;
50
+ for (const raw of text.split("\n")) {
51
+ const line = raw.trim();
52
+ const section = /^\[\s*([^\]\s"]+)(?:\s+"((?:[^"\\]|\\.)*)")?\s*\]/.exec(line);
53
+ if (section !== null) {
54
+ inBranch = section[1]?.toLowerCase() === "branch" && section[2] === branch;
55
+ continue;
56
+ }
57
+ if (inBranch && /^merge\s*=\s*\S/i.test(line))
58
+ return true;
59
+ }
60
+ return false;
61
+ }
33
62
  async function readHead(cwd) {
34
63
  const head = await readGitHead(cwd);
35
64
  if (head !== undefined) {
@@ -50,6 +79,8 @@ const sameInfo = (a, b) => a?.branch === b?.branch &&
50
79
  a?.shortHead === b?.shortHead &&
51
80
  a?.insertions === b?.insertions &&
52
81
  a?.deletions === b?.deletions &&
82
+ a?.ahead === b?.ahead &&
83
+ a?.behind === b?.behind &&
53
84
  (a === undefined) === (b === undefined);
54
85
  export class GitInfoWatcher {
55
86
  cwd;
@@ -67,6 +98,8 @@ export class GitInfoWatcher {
67
98
  headAgain = false;
68
99
  disposed = false;
69
100
  diff;
101
+ upstream;
102
+ upstreamDisabled;
70
103
  constructor(cwd, deps = {}) {
71
104
  this.cwd = cwd;
72
105
  this.spawn = deps.spawn ?? ((command, args, options) => nodeSpawn(command, args, options));
@@ -75,6 +108,7 @@ export class GitInfoWatcher {
75
108
  this.timeoutMs = deps.timeoutMs ?? GIT_NUMSTAT_TIMEOUT_MS;
76
109
  this.env = deps.env ?? process.env;
77
110
  this.numstatDisabled = this.env["AMA_STATUS_GIT"] === "0";
111
+ this.upstreamDisabled = this.numstatDisabled;
78
112
  }
79
113
  current() {
80
114
  return this.info;
@@ -118,6 +152,7 @@ export class GitInfoWatcher {
118
152
  : {
119
153
  ...head,
120
154
  ...(this.diff !== undefined && head.shortHead !== undefined ? this.diff : {}),
155
+ ...(this.upstream !== undefined && head.branch !== undefined ? this.upstream : {}),
121
156
  };
122
157
  if (sameInfo(this.info, next))
123
158
  return;
@@ -135,8 +170,7 @@ export class GitInfoWatcher {
135
170
  return Promise.resolve();
136
171
  this.lastNumstatAt = at;
137
172
  this.numstatRunning = true;
138
- return this.numstat().then((diff) => {
139
- this.numstatRunning = false;
173
+ return this.numstat().then(async (diff) => {
140
174
  if (this.disposed)
141
175
  return;
142
176
  if (diff === undefined) {
@@ -145,14 +179,36 @@ export class GitInfoWatcher {
145
179
  }
146
180
  else
147
181
  this.diff = diff;
182
+ await this.leftRight();
183
+ this.numstatRunning = false;
184
+ if (this.disposed)
185
+ return;
148
186
  const info = this.info;
149
187
  if (info === undefined)
150
188
  return;
151
- const { insertions: _i, deletions: _d, ...head } = info;
189
+ const { insertions: _i, deletions: _d, ahead: _a, behind: _b, ...head } = info;
152
190
  this.update(head);
153
191
  });
154
192
  }
155
- numstat() {
193
+ /** 领先 / 落后上游:只在配置里有上游时起进程;超时本会话停用,非零退出只省略这一次。 */
194
+ async leftRight() {
195
+ const branch = this.info?.branch;
196
+ if (this.upstreamDisabled || branch === undefined || !(await hasUpstream(this.cwd, branch))) {
197
+ this.upstream = undefined;
198
+ return;
199
+ }
200
+ const result = await this.run(["rev-list", "--left-right", "--count", "@{upstream}...HEAD"]);
201
+ if (result === "timeout")
202
+ this.upstreamDisabled = true;
203
+ this.upstream =
204
+ typeof result === "object" && result.code === 0 ? parseLeftRight(result.out) : undefined;
205
+ }
206
+ async numstat() {
207
+ const result = await this.run(["diff", "--numstat", "HEAD"]);
208
+ return typeof result === "object" && result.code === 0 ? parseNumstat(result.out) : undefined;
209
+ }
210
+ /** 后台起一次 git;起不来为 undefined,超时杀掉并返回 `"timeout"`。 */
211
+ run(args) {
156
212
  return new Promise((resolve) => {
157
213
  let settled = false;
158
214
  let timer;
@@ -165,7 +221,7 @@ export class GitInfoWatcher {
165
221
  };
166
222
  let child;
167
223
  try {
168
- child = this.spawn("git", ["diff", "--numstat", "HEAD"], {
224
+ child = this.spawn("git", args, {
169
225
  cwd: this.cwd,
170
226
  env: { ...this.env, GIT_OPTIONAL_LOCKS: "0" },
171
227
  });
@@ -176,7 +232,7 @@ export class GitInfoWatcher {
176
232
  }
177
233
  timer = setTimeout(() => {
178
234
  child.kill("SIGKILL");
179
- done(undefined);
235
+ done("timeout");
180
236
  }, this.timeoutMs);
181
237
  timer.unref?.();
182
238
  let out = "";
@@ -185,7 +241,7 @@ export class GitInfoWatcher {
185
241
  out += String(chunk);
186
242
  });
187
243
  child.on("error", () => done(undefined));
188
- child.on("close", (code) => done(code === 0 ? parseNumstat(out) : undefined));
244
+ child.on("close", (code) => done({ code, out }));
189
245
  });
190
246
  }
191
247
  }
@@ -447,6 +447,8 @@ declare const en: {
447
447
  listFailed: (provider: string, error: string) => string;
448
448
  found: (provider: string, count: number, url: string) => string;
449
449
  unmatched: string;
450
+ fromBackend: string;
451
+ noBackendWindow: (ids: string) => string;
450
452
  image: string;
451
453
  reasoning: string;
452
454
  noTools: string;
@@ -535,6 +537,17 @@ declare const en: {
535
537
  cacheSilent: string;
536
538
  sandbox: string;
537
539
  cycleHint: string;
540
+ quota: {
541
+ session: string;
542
+ reset: string;
543
+ weekly: string;
544
+ weeklyReset: string;
545
+ window: (duration: string) => string;
546
+ windowReset: (duration: string) => string;
547
+ short5h: string;
548
+ shortWeek: string;
549
+ pending: string;
550
+ };
538
551
  };
539
552
  subagent: {
540
553
  running: (elapsed: string) => string;
@@ -633,16 +646,8 @@ declare const en: {
633
646
  templates: (n: number) => string;
634
647
  host: (host: string) => string;
635
648
  warnings: (n: number) => string;
636
- hintCompact: string;
637
- keyModel: string;
638
- keyDir: string;
639
- keyMode: string;
640
- keyLoaded: string;
641
- keyHost: string;
642
- keyWarnings: string;
643
649
  thinking: (level: string) => string;
644
650
  preset: (preset: string) => string;
645
- warningsValue: (n: number) => string;
646
651
  hint: string;
647
652
  };
648
653
  ui: {
@@ -1249,6 +1254,7 @@ declare const en: {
1249
1254
  needsPrompt: string;
1250
1255
  retry: (attempt: number, max: number, seconds: number, error: string) => string;
1251
1256
  modelFailed: string;
1257
+ waitingTasks: (n: number) => string;
1252
1258
  planPending: (version: number, where: string) => string;
1253
1259
  limitTurns: (limit: number) => string;
1254
1260
  limitCost: (limit: string, spent: string) => string;
@@ -1257,6 +1263,7 @@ declare const en: {
1257
1263
  rpc: {
1258
1264
  unknownCommand: (type: string) => string;
1259
1265
  parseFailed: (error: string) => string;
1266
+ badTaskId: string;
1260
1267
  missingType: string;
1261
1268
  unsupported: string;
1262
1269
  noPlan: string;
@@ -1325,6 +1332,7 @@ declare const en: {
1325
1332
  "ui.quietStartup": string;
1326
1333
  "ui.ascii": string;
1327
1334
  "ui.compact": string;
1335
+ "ui.logo": string;
1328
1336
  "ui.animation": string;
1329
1337
  "ui.restoreOnCancel": string;
1330
1338
  "ui.statusLine": string;
@@ -1381,6 +1389,8 @@ declare const en: {
1381
1389
  "subagents.maxConcurrent": string;
1382
1390
  "subagents.maxPending": string;
1383
1391
  "subagents.defaultModel": string;
1392
+ "subagents.background": string;
1393
+ "subagents.autoBackgroundAfterMs": string;
1384
1394
  models: string;
1385
1395
  "models.aliases": string;
1386
1396
  "models.aliases.fast": string;
@@ -1737,7 +1747,31 @@ declare const en: {
1737
1747
  bar: {
1738
1748
  more: (n: number) => string;
1739
1749
  keys: (up: string, down: string) => string;
1750
+ keysFull: (up: string, down: string) => string;
1740
1751
  empty: string;
1752
+ runHint: (down: string) => string;
1753
+ };
1754
+ focus: {
1755
+ busyInput: (down: string) => string;
1756
+ disabled: string;
1757
+ };
1758
+ background: {
1759
+ done: (ids: string) => string;
1760
+ none: string;
1761
+ notForeground: (taskId: string) => string;
1762
+ runHint: (key: string) => string;
1763
+ timeout: (taskId: string, agent: string) => string;
1764
+ host: (taskId: string, agent: string) => string;
1765
+ moved: (elapsed: string) => string;
1766
+ started: string;
1767
+ interrupted: (ids: string) => string;
1768
+ };
1769
+ stop: {
1770
+ confirm: (taskId: string) => string;
1771
+ ended: (taskId: string) => string;
1772
+ };
1773
+ approval: {
1774
+ runHint: (down: string) => string;
1741
1775
  };
1742
1776
  view: {
1743
1777
  back: string;
@@ -2050,6 +2084,7 @@ declare const en: {
2050
2084
  "ui.showThinking": string;
2051
2085
  "ui.compact": string;
2052
2086
  "ui.animation": string;
2087
+ "ui.logo": string;
2053
2088
  "ui.statusLine": string;
2054
2089
  "ui.restoreOnCancel": string;
2055
2090
  "ui.quietStartup": string;
@@ -2109,6 +2144,8 @@ declare const en: {
2109
2144
  "agents.sessionBudgetUsd": string;
2110
2145
  "subagents.maxConcurrent": string;
2111
2146
  "subagents.maxPending": string;
2147
+ "subagents.background": string;
2148
+ "subagents.autoBackgroundAfterMs": string;
2112
2149
  "memory.enabled": string;
2113
2150
  "memory.subagents": string;
2114
2151
  };