@armadra/agent 0.6.2 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +85 -0
- package/CHANGELOG.zh-CN.md +59 -0
- package/README.md +168 -575
- package/README.zh-CN.md +165 -596
- package/dist/agent/queue.d.ts +9 -0
- package/dist/agent/queue.js +27 -0
- package/dist/agent/session-subagent.d.ts +1 -0
- package/dist/agent/session-subagent.js +9 -0
- package/dist/agent/session.d.ts +11 -0
- package/dist/agent/session.js +29 -2
- package/dist/agent/subagent-background.d.ts +75 -0
- package/dist/agent/subagent-background.js +209 -0
- package/dist/agent/subagent-direct.d.ts +5 -2
- package/dist/agent/subagent-direct.js +34 -1
- package/dist/agent/subagent-registry.d.ts +26 -6
- package/dist/agent/subagent-registry.js +66 -94
- package/dist/agent/types-w5.d.ts +11 -1
- package/dist/agent/types.d.ts +12 -0
- package/dist/agents/builtin.js +0 -1
- package/dist/agents/external.js +0 -1
- package/dist/agents/parse.js +4 -3
- package/dist/agents/result.d.ts +7 -1
- package/dist/agents/result.js +20 -1
- package/dist/agents/task-control.d.ts +13 -2
- package/dist/agents/task-record.d.ts +16 -4
- package/dist/agents/task-record.js +32 -0
- package/dist/agents/types.d.ts +5 -1
- package/dist/ai/apis/chatgpt-rate-limits.js +7 -2
- package/dist/ai/providers/discovered-cache.d.ts +10 -4
- package/dist/ai/providers/discovered-cache.js +30 -14
- package/dist/ai/types.d.ts +2 -1
- package/dist/auth/chatgpt/backend-client.d.ts +6 -5
- package/dist/auth/chatgpt/backend-client.js +8 -7
- package/dist/bundle/ama.cjs +2034 -552
- package/dist/cli/compose-agents.d.ts +2 -1
- package/dist/cli/compose-agents.js +11 -2
- package/dist/cli/subcommands/models-discover.d.ts +1 -0
- package/dist/cli/subcommands/models-discover.js +12 -2
- package/dist/config/json-schema.js +10 -2
- package/dist/config/key-docs.js +9 -2
- package/dist/config/merge.d.ts +1 -1
- package/dist/config/merge.js +20 -3
- package/dist/config/schema-w5.js +4 -2
- package/dist/config/schema.js +4 -0
- package/dist/config/settings-registry.js +5 -0
- package/dist/config/types-w5.d.ts +10 -0
- package/dist/config/types-w5.js +2 -0
- package/dist/config/types.d.ts +5 -1
- package/dist/drivers/runner.js +29 -2
- package/dist/git/info.d.ts +19 -0
- package/dist/git/info.js +64 -8
- package/dist/i18n/catalog.d.ts +58 -8
- package/dist/i18n/messages/agents.d.ts +60 -0
- package/dist/i18n/messages/agents.js +62 -2
- package/dist/i18n/messages/config-keys.d.ts +8 -0
- package/dist/i18n/messages/config-keys.js +18 -10
- package/dist/i18n/messages/config.d.ts +8 -0
- package/dist/i18n/messages/interactive-startup.d.ts +0 -16
- package/dist/i18n/messages/interactive-startup.js +0 -16
- package/dist/i18n/messages/interactive-view.d.ts +8 -0
- package/dist/i18n/messages/interactive-view.js +10 -0
- package/dist/i18n/messages/interactive.d.ts +33 -16
- package/dist/i18n/messages/interactive.js +31 -0
- package/dist/i18n/messages/print.d.ts +4 -0
- package/dist/i18n/messages/print.js +4 -0
- package/dist/i18n/messages/report.d.ts +8 -0
- package/dist/i18n/messages/report.js +12 -4
- package/dist/i18n/messages/settings.d.ts +8 -0
- package/dist/i18n/messages/settings.js +8 -0
- package/dist/i18n/messages/subcommands-config.d.ts +4 -0
- package/dist/i18n/messages/subcommands-config.js +4 -0
- package/dist/i18n/messages/subcommands.d.ts +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/modes/commands-core.js +19 -4
- package/dist/modes/interactive/agent-bar.d.ts +3 -1
- package/dist/modes/interactive/agent-bar.js +12 -5
- package/dist/modes/interactive/agent-ui.d.ts +21 -3
- package/dist/modes/interactive/agent-ui.js +82 -12
- package/dist/modes/interactive/agent-view.d.ts +9 -0
- package/dist/modes/interactive/agent-view.js +28 -4
- package/dist/modes/interactive/approval-dock.d.ts +51 -0
- package/dist/modes/interactive/approval-dock.js +112 -0
- package/dist/modes/interactive/approval-ui.d.ts +43 -0
- package/dist/modes/interactive/approval-ui.js +64 -0
- package/dist/modes/interactive/commands.js +4 -1
- package/dist/modes/interactive/event-notices.d.ts +6 -1
- package/dist/modes/interactive/event-notices.js +7 -1
- package/dist/modes/interactive/interactive-mode.d.ts +4 -2
- package/dist/modes/interactive/interactive-mode.js +46 -39
- package/dist/modes/interactive/interrupt-send.d.ts +25 -0
- package/dist/modes/interactive/interrupt-send.js +32 -0
- package/dist/modes/interactive/key-dispatch.d.ts +29 -5
- package/dist/modes/interactive/key-dispatch.js +97 -8
- package/dist/modes/interactive/line/line-mode.d.ts +1 -0
- package/dist/modes/interactive/line/line-mode.js +34 -7
- package/dist/modes/interactive/run-indicator.d.ts +34 -2
- package/dist/modes/interactive/run-indicator.js +62 -8
- package/dist/modes/interactive/session-events.js +5 -1
- package/dist/modes/interactive/startup-header.d.ts +29 -14
- package/dist/modes/interactive/startup-header.js +93 -59
- package/dist/modes/interactive/startup-logo.d.ts +83 -0
- package/dist/modes/interactive/startup-logo.js +183 -0
- package/dist/modes/interactive/status-area.d.ts +18 -0
- package/dist/modes/interactive/status-area.js +67 -1
- package/dist/modes/interactive/status-bar.d.ts +13 -2
- package/dist/modes/interactive/status-bar.js +60 -17
- package/dist/modes/interactive/status-line.d.ts +11 -8
- package/dist/modes/interactive/status-line.js +52 -39
- package/dist/modes/interactive/status-quota.d.ts +58 -0
- package/dist/modes/interactive/status-quota.js +155 -0
- package/dist/modes/interactive/subagent-view.d.ts +1 -0
- package/dist/modes/interactive/subagent-view.js +8 -0
- package/dist/modes/interactive/task-background.d.ts +31 -0
- package/dist/modes/interactive/task-background.js +68 -0
- package/dist/modes/interactive/tool-view.d.ts +7 -1
- package/dist/modes/interactive/tool-view.js +24 -1
- package/dist/modes/print/print-mode.d.ts +11 -0
- package/dist/modes/print/print-mode.js +36 -1
- package/dist/modes/rpc/commands.d.ts +4 -1
- package/dist/modes/rpc/commands.js +24 -2
- package/dist/rpc.d.ts +20 -0
- package/dist/rpc.js +3 -0
- package/dist/tools/task-ctl.d.ts +2 -0
- package/dist/tools/task-ctl.js +7 -2
- package/dist/tools/task.d.ts +11 -0
- package/dist/tools/task.js +20 -2
- package/dist/tools/types.d.ts +6 -0
- package/dist/tui/components/editor.d.ts +2 -0
- package/dist/tui/components/editor.js +4 -0
- package/dist/tui/components/loader.d.ts +5 -1
- package/dist/tui/components/loader.js +18 -5
- package/dist/tui/keybindings.d.ts +15 -3
- package/dist/tui/keybindings.js +15 -3
- package/docs/agents.md +52 -28
- package/docs/en/host-api.md +5 -1
- package/docs/en/providers.md +1 -1
- package/docs/en/rpc.md +28 -14
- package/docs/en/sessions.md +3 -1
- package/docs/en/tui.md +106 -70
- package/docs/host-api.md +5 -1
- package/docs/providers.md +4 -2
- package/docs/rpc.md +28 -14
- package/docs/session-format.md +1 -1
- package/docs/sessions.md +2 -1
- package/docs/tui-design.md +42 -29
- package/docs/tui.md +106 -70
- 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({
|
|
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,8 +277,10 @@ 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(),
|
|
283
|
+
enterWhileRunning: oneOf(["queue", "interrupt"]),
|
|
276
284
|
statusLine: oneOf(STATUS_LINE_MODES),
|
|
277
285
|
language: oneOf(LANGUAGE_SETTINGS),
|
|
278
286
|
replyLanguage: str(),
|
package/dist/config/key-docs.js
CHANGED
|
@@ -23,7 +23,14 @@ 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: {
|
|
26
|
+
ui: {
|
|
27
|
+
compact: false,
|
|
28
|
+
logo: "auto",
|
|
29
|
+
animation: true,
|
|
30
|
+
restoreOnCancel: true,
|
|
31
|
+
enterWhileRunning: "queue",
|
|
32
|
+
language: "auto",
|
|
33
|
+
},
|
|
27
34
|
// [W6-C0] 第六波键的缺省(行为由各批次实现)
|
|
28
35
|
memory: {
|
|
29
36
|
enabled: false,
|
|
@@ -48,7 +55,7 @@ const IMPLICIT_DEFAULTS = {
|
|
|
48
55
|
images: { resize: "auto" },
|
|
49
56
|
plan: { bash: "readonly", unattended: "stop" },
|
|
50
57
|
agents: { maxConcurrent: 3, dirs: [] },
|
|
51
|
-
subagents: { maxConcurrent: 4, maxPending: 16 },
|
|
58
|
+
subagents: { maxConcurrent: 4, maxPending: 16, background: "auto", autoBackgroundAfterMs: 0 },
|
|
52
59
|
reminders: { todo: true, fileChanges: true, contextPressure: true, budget: true },
|
|
53
60
|
todo: { reminder: 10 },
|
|
54
61
|
};
|
package/dist/config/merge.d.ts
CHANGED
|
@@ -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
|
*/
|
package/dist/config/merge.js
CHANGED
|
@@ -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] 嵌入宿主以「最后一行 = 状态栏」锚定,底部信息行缺省单行;[
|
|
39
|
-
|
|
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)
|
package/dist/config/schema-w5.js
CHANGED
|
@@ -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);
|
package/dist/config/schema.js
CHANGED
|
@@ -257,8 +257,10 @@ export function validateConfig(value) {
|
|
|
257
257
|
"quietStartup",
|
|
258
258
|
"ascii",
|
|
259
259
|
"compact",
|
|
260
|
+
"logo",
|
|
260
261
|
"animation",
|
|
261
262
|
"restoreOnCancel",
|
|
263
|
+
"enterWhileRunning",
|
|
262
264
|
...W5_UI_KEYS,
|
|
263
265
|
...W6_UI_KEYS,
|
|
264
266
|
], (s, p) => {
|
|
@@ -269,8 +271,10 @@ export function validateConfig(value) {
|
|
|
269
271
|
c.oneOf(s, "quietStartup", p, ["normal", "header", "silent"]);
|
|
270
272
|
c.boolean(s, "ascii", p);
|
|
271
273
|
c.boolean(s, "compact", p);
|
|
274
|
+
c.oneOf(s, "logo", p, ["auto", "off"]);
|
|
272
275
|
c.boolean(s, "animation", p);
|
|
273
276
|
c.boolean(s, "restoreOnCancel", p);
|
|
277
|
+
c.oneOf(s, "enterWhileRunning", p, ["queue", "interrupt"]);
|
|
274
278
|
checkUiW5(c, s, p);
|
|
275
279
|
checkUiW6(c, s, p);
|
|
276
280
|
});
|
|
@@ -31,8 +31,10 @@ 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"),
|
|
37
|
+
row("ui.enterWhileRunning", "ui", "now", "any"),
|
|
36
38
|
row("ui.quietStartup", "ui", "restart", "any"),
|
|
37
39
|
row("ui.ascii", "ui", "restart", "any", { envOverride: "AMA_ASCII" }),
|
|
38
40
|
row("ui.language", "ui", "restart", "any", { envOverride: "AMA_LANG" }),
|
|
@@ -92,6 +94,9 @@ const ROWS = [
|
|
|
92
94
|
row("agents.sessionBudgetUsd", "agents", "nextSession", "deny", { kind: "optionalNumber" }),
|
|
93
95
|
row("subagents.maxConcurrent", "agents", "nextSession", "deny"),
|
|
94
96
|
row("subagents.maxPending", "agents", "nextSession", "deny"),
|
|
97
|
+
// [W7-B2] 改 task 工具描述(缺省后台 / 前台两种文案),故 prefix
|
|
98
|
+
row("subagents.background", "agents", "restart", "any", { prefix: true }),
|
|
99
|
+
row("subagents.autoBackgroundAfterMs", "agents", "restart", "any"),
|
|
95
100
|
// [W6-M]
|
|
96
101
|
row("memory.enabled", "memory", "nextSession", "tighten", { envOverride: "AMA_MEMORY" }),
|
|
97
102
|
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;
|
package/dist/config/types-w5.js
CHANGED
|
@@ -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+$/;
|
package/dist/config/types.d.ts
CHANGED
|
@@ -143,12 +143,16 @@ export interface UiConfig extends UiConfigW6 {
|
|
|
143
143
|
quietStartup?: "normal" | "header" | "silent";
|
|
144
144
|
/** ASCII 字形(`AMA_ASCII=1` 等价);缺省按区域设置 / TERM 自动检测。 */
|
|
145
145
|
ascii?: boolean;
|
|
146
|
-
/**
|
|
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。 */
|
|
151
153
|
restoreOnCancel?: boolean;
|
|
154
|
+
/** 运行中按 Enter:queue(缺省)排队插话,interrupt 打断并立即发送(与 `app.message.interrupt` 互换)。 */
|
|
155
|
+
enterWhileRunning?: "queue" | "interrupt";
|
|
152
156
|
/** [W5-C0] 底部信息行(W5-A):缺省独立终端 full、嵌入宿主(有 profile)compact。 */
|
|
153
157
|
statusLine?: StatusLineMode;
|
|
154
158
|
}
|
package/dist/drivers/runner.js
CHANGED
|
@@ -135,6 +135,16 @@ export class ProcessRunner {
|
|
|
135
135
|
return this.deps;
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* 能中断单个回合、进程留着接着用的驱动(ACP `session/cancel`、Claude stream-json interrupt、Codex
|
|
140
|
+
* `turn/interrupt`);oneshot 每回合一个进程(中断 = 杀进程),宿主驱动不归 ama 管——这两种「打断并发送」退回排队。
|
|
141
|
+
*/
|
|
142
|
+
const TURN_INTERRUPT = new Set([
|
|
143
|
+
"acp",
|
|
144
|
+
"acp-adapter",
|
|
145
|
+
"claude-stream",
|
|
146
|
+
"codex-app-server",
|
|
147
|
+
]);
|
|
138
148
|
class ProcessHandle {
|
|
139
149
|
runner;
|
|
140
150
|
chosen;
|
|
@@ -209,8 +219,25 @@ class ProcessHandle {
|
|
|
209
219
|
}
|
|
210
220
|
this.chain = this.chain.then(() => this.runTurn(text));
|
|
211
221
|
}
|
|
212
|
-
|
|
213
|
-
|
|
222
|
+
/** 跟到链尾:回合进行中被 `interrupt` 接上的下一回合也算在这次运行里。 */
|
|
223
|
+
async wait() {
|
|
224
|
+
for (;;) {
|
|
225
|
+
const chain = this.chain;
|
|
226
|
+
const result = await chain;
|
|
227
|
+
if (chain === this.chain)
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
/** 子 Agent 视图的「打断并发送」:中断当前回合,紧接着以 `text` 开下一回合。 */
|
|
232
|
+
async interrupt(text) {
|
|
233
|
+
const session = this.session;
|
|
234
|
+
if (this.stopped || !this.busy || session === undefined)
|
|
235
|
+
return false;
|
|
236
|
+
if (!TURN_INTERRUPT.has(this.chosen.driver.kind))
|
|
237
|
+
return false;
|
|
238
|
+
this.chain = this.chain.then(() => this.runTurn(text));
|
|
239
|
+
await session.cancel();
|
|
240
|
+
return true;
|
|
214
241
|
}
|
|
215
242
|
async stop() {
|
|
216
243
|
if (this.stopped)
|
package/dist/git/info.d.ts
CHANGED
|
@@ -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
|
-
|
|
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",
|
|
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(
|
|
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
|
|
244
|
+
child.on("close", (code) => done({ code, out }));
|
|
189
245
|
});
|
|
190
246
|
}
|
|
191
247
|
}
|