@armadra/agent 0.6.1 → 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 (152) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/CHANGELOG.zh-CN.md +56 -0
  3. package/README.md +168 -575
  4. package/README.zh-CN.md +165 -596
  5. package/dist/agent/session-settings.d.ts +4 -1
  6. package/dist/agent/session-settings.js +7 -2
  7. package/dist/agent/session.d.ts +6 -0
  8. package/dist/agent/session.js +8 -0
  9. package/dist/agent/subagent-background.d.ts +75 -0
  10. package/dist/agent/subagent-background.js +209 -0
  11. package/dist/agent/subagent-registry.d.ts +24 -5
  12. package/dist/agent/subagent-registry.js +64 -92
  13. package/dist/agent/types-w5.d.ts +11 -1
  14. package/dist/agent/types.d.ts +5 -0
  15. package/dist/agents/builtin.js +0 -1
  16. package/dist/agents/external.js +0 -1
  17. package/dist/agents/parse.js +4 -3
  18. package/dist/agents/result.d.ts +7 -1
  19. package/dist/agents/result.js +20 -1
  20. package/dist/agents/task-control.d.ts +13 -2
  21. package/dist/agents/task-record.d.ts +13 -1
  22. package/dist/agents/task-record.js +32 -0
  23. package/dist/agents/types.d.ts +5 -1
  24. package/dist/ai/apis/chatgpt-backend.d.ts +7 -2
  25. package/dist/ai/apis/chatgpt-backend.js +12 -4
  26. package/dist/ai/providers/discovered-cache.d.ts +29 -12
  27. package/dist/ai/providers/discovered-cache.js +72 -12
  28. package/dist/ai/providers/registry.js +7 -6
  29. package/dist/ai/types.d.ts +5 -0
  30. package/dist/auth/chatgpt/backend-client.d.ts +14 -4
  31. package/dist/auth/chatgpt/backend-client.js +41 -8
  32. package/dist/auth/chatgpt/cli.d.ts +2 -1
  33. package/dist/auth/chatgpt/cli.js +14 -7
  34. package/dist/auth/chatgpt/follow.d.ts +16 -0
  35. package/dist/auth/chatgpt/follow.js +51 -0
  36. package/dist/auth/chatgpt/presets.d.ts +8 -0
  37. package/dist/auth/chatgpt/presets.js +12 -0
  38. package/dist/auth/testing/fake-oauth.d.ts +4 -1
  39. package/dist/auth/testing/fake-oauth.js +24 -1
  40. package/dist/bundle/ama.cjs +2016 -664
  41. package/dist/cli/compose-agents.d.ts +2 -1
  42. package/dist/cli/compose-agents.js +11 -2
  43. package/dist/cli/compose-providers.d.ts +2 -1
  44. package/dist/cli/compose-providers.js +6 -3
  45. package/dist/cli/startup-steps.js +4 -1
  46. package/dist/cli/subcommands/models-discover.d.ts +5 -2
  47. package/dist/cli/subcommands/models-discover.js +52 -10
  48. package/dist/config/json-schema.js +10 -2
  49. package/dist/config/key-docs.js +9 -3
  50. package/dist/config/merge.d.ts +1 -1
  51. package/dist/config/merge.js +20 -3
  52. package/dist/config/schema-w5.js +4 -2
  53. package/dist/config/schema-w6.d.ts +1 -1
  54. package/dist/config/schema-w6.js +2 -1
  55. package/dist/config/schema.js +2 -0
  56. package/dist/config/settings-registry.js +4 -0
  57. package/dist/config/types-w5.d.ts +10 -0
  58. package/dist/config/types-w5.js +2 -0
  59. package/dist/config/types-w6.d.ts +5 -0
  60. package/dist/config/types.d.ts +3 -1
  61. package/dist/git/info.d.ts +19 -0
  62. package/dist/git/info.js +64 -8
  63. package/dist/i18n/catalog.d.ts +52 -8
  64. package/dist/i18n/messages/agents.d.ts +56 -0
  65. package/dist/i18n/messages/agents.js +58 -2
  66. package/dist/i18n/messages/auth.d.ts +12 -0
  67. package/dist/i18n/messages/auth.js +20 -0
  68. package/dist/i18n/messages/config-keys.d.ts +8 -0
  69. package/dist/i18n/messages/config-keys.js +18 -10
  70. package/dist/i18n/messages/config.d.ts +8 -0
  71. package/dist/i18n/messages/interactive-startup.d.ts +0 -16
  72. package/dist/i18n/messages/interactive-startup.js +0 -16
  73. package/dist/i18n/messages/interactive.d.ts +23 -16
  74. package/dist/i18n/messages/interactive.js +25 -0
  75. package/dist/i18n/messages/print.d.ts +4 -0
  76. package/dist/i18n/messages/print.js +4 -0
  77. package/dist/i18n/messages/report.js +4 -4
  78. package/dist/i18n/messages/settings.d.ts +6 -0
  79. package/dist/i18n/messages/settings.js +6 -0
  80. package/dist/i18n/messages/subcommands-config.d.ts +4 -0
  81. package/dist/i18n/messages/subcommands-config.js +4 -0
  82. package/dist/i18n/messages/subcommands.d.ts +4 -0
  83. package/dist/index.d.ts +1 -0
  84. package/dist/modes/commands-core.js +8 -4
  85. package/dist/modes/interactive/agent-bar.d.ts +3 -1
  86. package/dist/modes/interactive/agent-bar.js +12 -5
  87. package/dist/modes/interactive/agent-ui.d.ts +21 -3
  88. package/dist/modes/interactive/agent-ui.js +81 -12
  89. package/dist/modes/interactive/agent-view.d.ts +4 -0
  90. package/dist/modes/interactive/agent-view.js +14 -1
  91. package/dist/modes/interactive/approval-dock.d.ts +51 -0
  92. package/dist/modes/interactive/approval-dock.js +112 -0
  93. package/dist/modes/interactive/approval-ui.d.ts +43 -0
  94. package/dist/modes/interactive/approval-ui.js +64 -0
  95. package/dist/modes/interactive/commands.js +4 -1
  96. package/dist/modes/interactive/event-notices.d.ts +6 -1
  97. package/dist/modes/interactive/event-notices.js +7 -1
  98. package/dist/modes/interactive/interactive-mode.d.ts +4 -2
  99. package/dist/modes/interactive/interactive-mode.js +33 -38
  100. package/dist/modes/interactive/key-dispatch.d.ts +21 -4
  101. package/dist/modes/interactive/key-dispatch.js +60 -8
  102. package/dist/modes/interactive/line/line-mode.js +5 -0
  103. package/dist/modes/interactive/run-indicator.d.ts +15 -0
  104. package/dist/modes/interactive/run-indicator.js +37 -5
  105. package/dist/modes/interactive/session-events.js +5 -1
  106. package/dist/modes/interactive/startup-header.d.ts +29 -14
  107. package/dist/modes/interactive/startup-header.js +93 -59
  108. package/dist/modes/interactive/startup-logo.d.ts +83 -0
  109. package/dist/modes/interactive/startup-logo.js +183 -0
  110. package/dist/modes/interactive/status-area.d.ts +18 -0
  111. package/dist/modes/interactive/status-area.js +67 -1
  112. package/dist/modes/interactive/status-bar.d.ts +9 -1
  113. package/dist/modes/interactive/status-bar.js +43 -11
  114. package/dist/modes/interactive/status-line.d.ts +2 -0
  115. package/dist/modes/interactive/status-line.js +11 -6
  116. package/dist/modes/interactive/status-quota.d.ts +44 -0
  117. package/dist/modes/interactive/status-quota.js +135 -0
  118. package/dist/modes/interactive/subagent-view.d.ts +1 -0
  119. package/dist/modes/interactive/subagent-view.js +8 -0
  120. package/dist/modes/interactive/task-background.d.ts +31 -0
  121. package/dist/modes/interactive/task-background.js +68 -0
  122. package/dist/modes/interactive/tool-view.d.ts +7 -1
  123. package/dist/modes/interactive/tool-view.js +24 -1
  124. package/dist/modes/print/print-mode.d.ts +11 -0
  125. package/dist/modes/print/print-mode.js +36 -1
  126. package/dist/modes/rpc/commands.d.ts +2 -1
  127. package/dist/modes/rpc/commands.js +9 -1
  128. package/dist/rpc.d.ts +13 -0
  129. package/dist/rpc.js +3 -0
  130. package/dist/tools/task-ctl.d.ts +2 -0
  131. package/dist/tools/task-ctl.js +7 -2
  132. package/dist/tools/task.d.ts +11 -0
  133. package/dist/tools/task.js +20 -2
  134. package/dist/tui/components/editor.d.ts +2 -0
  135. package/dist/tui/components/editor.js +4 -0
  136. package/dist/tui/components/loader.d.ts +5 -1
  137. package/dist/tui/components/loader.js +18 -5
  138. package/dist/tui/keybindings.d.ts +8 -3
  139. package/dist/tui/keybindings.js +8 -3
  140. package/docs/agents.md +52 -28
  141. package/docs/en/host-api.md +5 -1
  142. package/docs/en/providers.md +8 -4
  143. package/docs/en/rpc.md +19 -7
  144. package/docs/en/sessions.md +3 -1
  145. package/docs/en/tui.md +84 -66
  146. package/docs/host-api.md +5 -1
  147. package/docs/providers.md +29 -12
  148. package/docs/rpc.md +19 -7
  149. package/docs/sessions.md +2 -1
  150. package/docs/tui-design.md +42 -29
  151. package/docs/tui.md +67 -49
  152. 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
  }
@@ -12,7 +12,8 @@
12
12
  * - [W6-O] ChatGPT 登录(docs/wave6-plan.md D14):`chatgpt` 的缺省渠道按 auth.json 条目的 flavor 定(用户写了
13
13
  * `defaultChannel` 时不动);`AMA_CHATGPT_BASE_URL` 改该渠道地址;`auth.chatgpt.originator` 改 codex 渠道的
14
14
  * `originator` 头;OAuth 刷新拿到 `auth.chatgpt` 配置与同一份 env。
15
- * - 发现缓存(`<dataDir>/models/discovered/<provider>.json`)并入模型表为空的供应商(discovered-cache.ts)。
15
+ * - 发现缓存(`<dataDir>/models/discovered/<provider>.json`)并入模型表为空的供应商(discovered-cache.ts);
16
+ * chatgpt 的缓存 flavor 与当前登录不符时视为过期、不并入。
16
17
  */
17
18
  import type { ApiRegistry } from "../ai/apis/api.js";
18
19
  import { ProviderRegistry } from "../ai/providers/registry.js";
@@ -12,7 +12,8 @@
12
12
  * - [W6-O] ChatGPT 登录(docs/wave6-plan.md D14):`chatgpt` 的缺省渠道按 auth.json 条目的 flavor 定(用户写了
13
13
  * `defaultChannel` 时不动);`AMA_CHATGPT_BASE_URL` 改该渠道地址;`auth.chatgpt.originator` 改 codex 渠道的
14
14
  * `originator` 头;OAuth 刷新拿到 `auth.chatgpt` 配置与同一份 env。
15
- * - 发现缓存(`<dataDir>/models/discovered/<provider>.json`)并入模型表为空的供应商(discovered-cache.ts)。
15
+ * - 发现缓存(`<dataDir>/models/discovered/<provider>.json`)并入模型表为空的供应商(discovered-cache.ts);
16
+ * chatgpt 的缓存 flavor 与当前登录不符时视为过期、不并入。
16
17
  */
17
18
  import { loadModelsDevIndex } from "../ai/providers/models-dev-cache.js";
18
19
  import { ProviderRegistry, discoverLocalModels } from "../ai/providers/registry.js";
@@ -108,8 +109,10 @@ export async function buildProviderRegistry(input, options = {}) {
108
109
  registry = make({ provider, apiKey: cli.apiKey });
109
110
  }
110
111
  // 发现缓存(`ama auth login chatgpt` / `ama models discover chatgpt`)并入模型表为空的供应商
111
- if (dataDir !== undefined)
112
- mergeDiscoveredModels(registry, dataDir, lazyIndex(() => loadModelsDevIndex(dataDir)));
112
+ if (dataDir !== undefined) {
113
+ const flavor = readOAuthEntry(input.authFile, CHATGPT_PROVIDER_ID)?.flavor;
114
+ mergeDiscoveredModels(registry, dataDir, lazyIndex(() => loadModelsDevIndex(dataDir)), (id) => (id === CHATGPT_PROVIDER_ID ? flavor : undefined));
115
+ }
113
116
  const probe = options.probeLocal ?? env[NO_LOCAL_PROBE_ENV] !== "1";
114
117
  if (probe && config.defaultModel === undefined && !anyKeyConfigured(registry)) {
115
118
  await probeLocalProviders(registry, options.probeTimeoutMs ?? LOCAL_PROBE_TIMEOUT_MS);
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { describeLookupFailure } from "../ai/providers/suggest.js";
6
6
  import { formatModelRef } from "../ai/providers/channels.js";
7
+ import { CHATGPT_PROVIDER_ID } from "../auth/chatgpt/presets.js";
7
8
  import { statSync } from "node:fs";
8
9
  import { isAbsolute, resolve } from "node:path";
9
10
  import { AmaError, StartupError, isAmaError } from "../errors.js";
@@ -126,10 +127,12 @@ export async function resolveModel(args, registry, manager, defaultModel, deps,
126
127
  else {
127
128
  const change = lastEntry(manager, (e) => e.type === "model_change");
128
129
  if (change?.type === "model_change") {
130
+ // ChatGPT 的渠道跟随当前登录方式:会话里记的渠道不当显式渠道(可能是换登录前的)
131
+ const channel = change.provider === CHATGPT_PROVIDER_ID ? undefined : change.channel;
129
132
  const found = registry.findModel(formatModelRef({
130
133
  provider: change.provider,
131
134
  id: change.modelId,
132
- ...(change.channel !== undefined ? { channel: change.channel } : {}),
135
+ ...(channel !== undefined ? { channel } : {}),
133
136
  }));
134
137
  if (found.ok)
135
138
  choice = { model: found.model, provider: found.provider };
@@ -18,8 +18,10 @@
18
18
  * 匹配结果;`--write` 跳过 models.dev 标明不支持工具调用的模型。元数据不写进配置——运行时从缓存补。
19
19
  *
20
20
  * [W6-O] `chatgpt`:按登录的 flavor 调对应后端的模型列表(SIWC `GET /v1/models` 筛 `visibility: list`;codex
21
- * `GET /models?client_version=…`),只取 slug 与显示名;`--probe` 不适用(订阅后端只有一种协议)。结果另写进
22
- * 发现缓存 `<dataDir>/models/discovered/chatgpt.json`(discovered-cache.ts),`/model` 选择器由此列出。
21
+ * `GET /models?client_version=<codexClientVersion>`,另带上下文窗口 / 输入模态 / 推理强度),取 slug 与显示名;
22
+ * `--probe` 不适用(订阅后端只有一种协议)。结果另写进发现缓存 `<dataDir>/models/discovered/chatgpt.json`
23
+ * (discovered-cache.ts,0 个也写),`/model` 选择器由此列出;codex 返回 0 个时提示 codexClientVersion 可能过旧。
24
+ * 后端给了上下文窗口就显示它(标「后端」),注册表同样以它为准;没给的列出来,说明改用 models.dev。
23
25
  */
24
26
  import type { Api, Model, ProviderData, ProviderRegistryApi } from "../../ai/types.js";
25
27
  import type { ModelsAction } from "./models.js";
@@ -31,6 +33,7 @@ export declare const PROBE_APIS: readonly Api[];
31
33
  export declare function modelsUrl(provider: ProviderData): string;
32
34
  export declare function discoverModels(provider: ProviderData, apiKey: string | undefined, options?: {
33
35
  timeoutMs?: number;
36
+ codexClientVersion?: string;
34
37
  }): Promise<Model[]>;
35
38
  export declare function probeOrder(provider: ProviderData): Api[];
36
39
  export interface ProbeOptions {
@@ -18,15 +18,17 @@
18
18
  * 匹配结果;`--write` 跳过 models.dev 标明不支持工具调用的模型。元数据不写进配置——运行时从缓存补。
19
19
  *
20
20
  * [W6-O] `chatgpt`:按登录的 flavor 调对应后端的模型列表(SIWC `GET /v1/models` 筛 `visibility: list`;codex
21
- * `GET /models?client_version=…`),只取 slug 与显示名;`--probe` 不适用(订阅后端只有一种协议)。结果另写进
22
- * 发现缓存 `<dataDir>/models/discovered/chatgpt.json`(discovered-cache.ts),`/model` 选择器由此列出。
21
+ * `GET /models?client_version=<codexClientVersion>`,另带上下文窗口 / 输入模态 / 推理强度),取 slug 与显示名;
22
+ * `--probe` 不适用(订阅后端只有一种协议)。结果另写进发现缓存 `<dataDir>/models/discovered/chatgpt.json`
23
+ * (discovered-cache.ts,0 个也写),`/model` 选择器由此列出;codex 返回 0 个时提示 codexClientVersion 可能过旧。
24
+ * 后端给了上下文窗口就显示它(标「后端」),注册表同样以它为准;没给的列出来,说明改用 models.dev。
23
25
  */
24
26
  import { authHeaders, mergeHeaders } from "../../ai/http.js";
25
27
  import { listChatGptModels } from "../../auth/chatgpt/backend-client.js";
26
- import { CHATGPT_PROVIDER_ID } from "../../auth/chatgpt/presets.js";
28
+ import { CHATGPT_PROVIDER_ID, codexClientVersion } from "../../auth/chatgpt/presets.js";
27
29
  import { liveToken } from "../../auth/oauth/live.js";
28
30
  import { discoverLocalModels, materializeModel } from "../../ai/providers/registry.js";
29
- import { writeDiscoveredCache } from "../../ai/providers/discovered-cache.js";
31
+ import { thinkingMapOf, writeDiscoveredCache, } from "../../ai/providers/discovered-cache.js";
30
32
  import { withCustomDefaults } from "../../ai/providers/catalog.js";
31
33
  import { describeModelsDev, loadModelsDevIndex } from "../../ai/providers/models-dev-cache.js";
32
34
  import { matchLabel, modelsDevFields } from "../../ai/providers/models-dev.js";
@@ -65,11 +67,20 @@ export async function discoverModels(provider, apiKey, options = {}) {
65
67
  accessToken: apiKey,
66
68
  accountId: live?.accountId,
67
69
  originator: channel?.headers?.["originator"],
70
+ clientVersion: options.codexClientVersion,
68
71
  });
69
72
  return models.map((m) => {
70
73
  const model = withCustomDefaults({ id: m.id }, provider.id, provider.api);
71
74
  if (m.name !== undefined)
72
75
  model.name = m.name;
76
+ if (m.contextWindow !== undefined)
77
+ model.contextWindow = m.contextWindow;
78
+ if (m.input !== undefined)
79
+ model.input = m.input;
80
+ if (m.reasoningLevels?.some((x) => x !== "none") === true) {
81
+ model.reasoning = true;
82
+ model.thinkingLevelMap = thinkingMapOf(m.reasoningLevels);
83
+ }
73
84
  return model;
74
85
  });
75
86
  }
@@ -191,7 +202,9 @@ async function run(ctx) {
191
202
  }
192
203
  let found;
193
204
  try {
194
- found = await discoverModels(provider, key.apiKey);
205
+ found = await discoverModels(provider, key.apiKey, {
206
+ codexClientVersion: codexClientVersion(ctx.level.merged.config.auth?.chatgpt, io.env),
207
+ });
195
208
  }
196
209
  catch (error) {
197
210
  io.stderr(m.listFailed(provider.id, error.message));
@@ -214,13 +227,22 @@ async function run(ctx) {
214
227
  noTools.add(model.id);
215
228
  if (match === undefined)
216
229
  unmatched.add(model.id);
230
+ // 后端给的上下文窗口优先于 models.dev(订阅后端的生效窗口可能远小于 API 版)
231
+ const backendCtx = model.contextWindow !== undefined
232
+ ? ` ctx ${compactTokens(model.contextWindow)}${m.fromBackend}`
233
+ : undefined;
217
234
  const meta = fields === undefined
218
- ? m.unmatched
219
- : ` ctx ${compactTokens(fields.contextWindow)} · out ${compactTokens(fields.maxTokens)}` +
235
+ ? `${backendCtx ?? ""}${m.unmatched}`
236
+ : `${backendCtx ?? ` ctx ${compactTokens(fields.contextWindow)}`} · out ${compactTokens(fields.maxTokens)}` +
220
237
  `${fields.input?.includes("image") ? m.image : ""}${fields.reasoning ? m.reasoning : ""}` +
221
238
  `${fields.toolCall === false ? m.noTools : ""} · ${matchLabel(match)}`;
222
239
  io.stdout(` ${model.id}${known !== undefined ? m.configured(known.api) : ""}${meta}\n`);
223
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
+ }
224
246
  const write = ctx.flags.has("write");
225
247
  if (!ctx.flags.has("probe") || provider.id === CHATGPT_PROVIDER_ID) {
226
248
  if (write)
@@ -256,17 +278,37 @@ async function run(ctx) {
256
278
  }
257
279
  return ExitCode.Ok;
258
280
  }
259
- /** chatgpt 的发现结果写进发现缓存(选择器与注册表读它);写失败不影响列表输出。 */
281
+ /** 发现得到的模型 → 缓存条目(后端给的元数据一并写入;推理强度由映射表还原)。 */
282
+ function cacheEntry(m) {
283
+ const entry = { id: m.id };
284
+ if (m.name !== m.id)
285
+ entry.name = m.name;
286
+ if (m.contextWindow !== undefined)
287
+ entry.contextWindow = m.contextWindow;
288
+ if (m.input.length !== 1 || m.input[0] !== "text")
289
+ entry.input = [...m.input];
290
+ if (m.reasoning && m.thinkingLevelMap !== undefined)
291
+ entry.reasoningLevels = Object.entries(m.thinkingLevelMap).flatMap(([level, value]) => typeof value !== "string" ? [] : level === "off" ? ["none"] : [level]);
292
+ return entry;
293
+ }
294
+ /**
295
+ * chatgpt 的发现结果写进发现缓存(选择器与注册表读它;0 个也写,免得残留另一 flavor 的缓存);写失败不影响
296
+ * 列表输出。codex 返回 0 个时提示 codexClientVersion 可能过旧。
297
+ */
260
298
  function cacheChatGpt(ctx, found, apiKey) {
299
+ const flavor = liveToken(apiKey)?.flavor;
261
300
  try {
262
- const flavor = liveToken(apiKey)?.flavor;
263
- const models = found.map((m) => ({ id: m.id, ...(m.name !== m.id ? { name: m.name } : {}) }));
301
+ const models = found.map(cacheEntry);
264
302
  const path = writeDiscoveredCache(ctx.level.dataDir, CHATGPT_PROVIDER_ID, { models, flavor });
265
303
  ctx.io.stdout(msg().subcommands.discover.cached(path));
266
304
  }
267
305
  catch (error) {
268
306
  ctx.io.stderr(`ama: ${error.message}\n`);
269
307
  }
308
+ if (found.length === 0 && flavor === "codex") {
309
+ const version = codexClientVersion(ctx.level.merged.config.auth?.chatgpt, ctx.io.env);
310
+ ctx.io.stderr(`${msg().auth.login.codexNoModels(version)}\n`);
311
+ }
270
312
  }
271
313
  export const DISCOVER_ACTION = {
272
314
  usage: "ama models discover <provider> [--probe] [--write] [--limit <n>] [--concurrency <n>] [--probe-timeout <ms>]",
@@ -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) }),
@@ -180,6 +186,7 @@ function w6Sections() {
180
186
  clientId: str(),
181
187
  issuer: str(),
182
188
  originator: str(),
189
+ codexClientVersion: str(),
183
190
  redirectPorts: {
184
191
  type: "array",
185
192
  items: { type: "integer", minimum: 0, maximum: 65535 },
@@ -270,6 +277,7 @@ function buildBaseSchema() {
270
277
  quietStartup: oneOf(["normal", "header", "silent"]),
271
278
  ascii: bool(),
272
279
  compact: bool(),
280
+ logo: oneOf(["auto", "off"]),
273
281
  animation: bool(),
274
282
  restoreOnCancel: bool(),
275
283
  statusLine: oneOf(STATUS_LINE_MODES),
@@ -10,6 +10,7 @@
10
10
  * DISPLAY_DEFAULTS 相同,DISPLAY_DEFAULTS 本身通过 `validateConfig`。
11
11
  */
12
12
  import { DEFAULT_IDLE_TIMEOUT_MS } from "../ai/http.js";
13
+ import { DEFAULT_CODEX_CLIENT_VERSION } from "../auth/chatgpt/presets.js";
13
14
  import { messagesFor, msg } from "../i18n/index.js";
14
15
  import { DEFAULT_INLINE_BUDGET } from "../codemode/declarations.js";
15
16
  import { DEFAULT_CONFIG, mergeConfig } from "./merge.js";
@@ -22,7 +23,7 @@ const IMPLICIT_DEFAULTS = {
22
23
  codemode: { inlineBudget: DEFAULT_INLINE_BUDGET, requireStrict: false },
23
24
  cache: { ...DEFAULT_CACHE_CONFIG },
24
25
  request: { idleTimeoutMs: DEFAULT_IDLE_TIMEOUT_MS },
25
- ui: { compact: false, animation: true, restoreOnCancel: true, language: "auto" },
26
+ ui: { compact: false, logo: "auto", animation: true, restoreOnCancel: true, language: "auto" },
26
27
  // [W6-C0] 第六波键的缺省(行为由各批次实现)
27
28
  memory: {
28
29
  enabled: false,
@@ -33,7 +34,12 @@ const IMPLICIT_DEFAULTS = {
33
34
  subagents: "read",
34
35
  },
35
36
  auth: {
36
- chatgpt: { flavor: "siwc", issuer: "https://auth.openai.com", originator: "codex_cli_rs" },
37
+ chatgpt: {
38
+ flavor: "siwc",
39
+ issuer: "https://auth.openai.com",
40
+ originator: "codex_cli_rs",
41
+ codexClientVersion: DEFAULT_CODEX_CLIENT_VERSION,
42
+ },
37
43
  },
38
44
  checkpoints: { ...DEFAULT_CHECKPOINTS_CONFIG },
39
45
  sandbox: { enabled: "auto", bash: "off", network: "deny", writable: [] },
@@ -42,7 +48,7 @@ const IMPLICIT_DEFAULTS = {
42
48
  images: { resize: "auto" },
43
49
  plan: { bash: "readonly", unattended: "stop" },
44
50
  agents: { maxConcurrent: 3, dirs: [] },
45
- subagents: { maxConcurrent: 4, maxPending: 16 },
51
+ subagents: { maxConcurrent: 4, maxPending: 16, background: "auto", autoBackgroundAfterMs: 0 },
46
52
  reminders: { todo: true, fileChanges: true, contextPressure: true, budget: true },
47
53
  todo: { reminder: 10 },
48
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);
@@ -9,7 +9,7 @@ export declare const W6_CONFIG_KEYS: readonly ["memory", "auth"];
9
9
  /** 已有 `ui` 段新增的键。 */
10
10
  export declare const W6_UI_KEYS: readonly ["language", "replyLanguage", "agentBar"];
11
11
  export declare const MEMORY_KEYS: readonly ["enabled", "scopes", "indexMaxBytes", "fileMaxBytes", "maxFiles", "subagents"];
12
- export declare const CHATGPT_AUTH_KEYS: readonly ["flavor", "clientId", "issuer", "originator", "redirectPorts"];
12
+ export declare const CHATGPT_AUTH_KEYS: readonly ["flavor", "clientId", "issuer", "originator", "codexClientVersion", "redirectPorts"];
13
13
  export declare function checkUiW6(c: Checker, ui: Obj, path: string): void;
14
14
  /** 第六波顶层段的校验(validateConfig 末尾调用)。 */
15
15
  export declare function validateConfigW6(c: Checker, config: Obj): void;
@@ -24,6 +24,7 @@ export const CHATGPT_AUTH_KEYS = [
24
24
  "clientId",
25
25
  "issuer",
26
26
  "originator",
27
+ "codexClientVersion",
27
28
  "redirectPorts",
28
29
  ];
29
30
  export function checkUiW6(c, ui, path) {
@@ -63,7 +64,7 @@ export function validateConfigW6(c, config) {
63
64
  return;
64
65
  c.keys(chatgpt, cp, CHATGPT_AUTH_KEYS);
65
66
  c.oneOf(chatgpt, "flavor", cp, CHATGPT_FLAVORS);
66
- for (const key of ["clientId", "issuer", "originator"])
67
+ for (const key of ["clientId", "issuer", "originator", "codexClientVersion"])
67
68
  c.string(chatgpt, key, cp);
68
69
  checkPorts(c, chatgpt, "redirectPorts", cp);
69
70
  });
@@ -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+$/;
@@ -53,6 +53,11 @@ export interface ChatGptAuthConfig {
53
53
  issuer?: string;
54
54
  /** codex flavor 的 `originator` 请求头,缺省 codex_cli_rs。 */
55
55
  originator?: string;
56
+ /**
57
+ * codex flavor 模型列表的 `client_version`(Codex CLI 版本号,后端按它过滤模型),缺省见
58
+ * `DEFAULT_CODEX_CLIENT_VERSION`;`AMA_CHATGPT_CODEX_CLIENT_VERSION` 优先。
59
+ */
60
+ codexClientVersion?: string;
56
61
  /** 本地回调端口(依次尝试;0 = 任意空闲端口)。 */
57
62
  redirectPorts?: number[];
58
63
  }
@@ -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
  }