@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.
- package/CHANGELOG.md +63 -0
- package/CHANGELOG.zh-CN.md +44 -0
- package/README.md +168 -575
- package/README.zh-CN.md +165 -596
- package/dist/agent/session.d.ts +6 -0
- package/dist/agent/session.js +8 -0
- package/dist/agent/subagent-background.d.ts +75 -0
- package/dist/agent/subagent-background.js +209 -0
- package/dist/agent/subagent-registry.d.ts +24 -5
- package/dist/agent/subagent-registry.js +64 -92
- package/dist/agent/types-w5.d.ts +11 -1
- package/dist/agent/types.d.ts +5 -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 +13 -1
- package/dist/agents/task-record.js +32 -0
- package/dist/agents/types.d.ts +5 -1
- package/dist/ai/providers/discovered-cache.d.ts +10 -4
- package/dist/ai/providers/discovered-cache.js +30 -14
- package/dist/auth/chatgpt/backend-client.d.ts +6 -5
- package/dist/auth/chatgpt/backend-client.js +8 -7
- package/dist/bundle/ama.cjs +1691 -500
- 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 +9 -2
- package/dist/config/key-docs.js +2 -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 +2 -0
- package/dist/config/settings-registry.js +4 -0
- package/dist/config/types-w5.d.ts +10 -0
- package/dist/config/types-w5.js +2 -0
- package/dist/config/types.d.ts +3 -1
- package/dist/git/info.d.ts +19 -0
- package/dist/git/info.js +64 -8
- package/dist/i18n/catalog.d.ts +45 -8
- package/dist/i18n/messages/agents.d.ts +56 -0
- package/dist/i18n/messages/agents.js +58 -2
- package/dist/i18n/messages/config-keys.d.ts +6 -0
- package/dist/i18n/messages/config-keys.js +16 -10
- package/dist/i18n/messages/config.d.ts +6 -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.d.ts +23 -16
- package/dist/i18n/messages/interactive.js +25 -0
- package/dist/i18n/messages/print.d.ts +4 -0
- package/dist/i18n/messages/print.js +4 -0
- package/dist/i18n/messages/report.js +4 -4
- package/dist/i18n/messages/settings.d.ts +6 -0
- package/dist/i18n/messages/settings.js +6 -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 +8 -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 +81 -12
- package/dist/modes/interactive/agent-view.d.ts +4 -0
- package/dist/modes/interactive/agent-view.js +14 -1
- 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 +33 -38
- package/dist/modes/interactive/key-dispatch.d.ts +21 -4
- package/dist/modes/interactive/key-dispatch.js +60 -8
- package/dist/modes/interactive/line/line-mode.js +5 -0
- package/dist/modes/interactive/run-indicator.d.ts +15 -0
- package/dist/modes/interactive/run-indicator.js +37 -5
- 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 +9 -1
- package/dist/modes/interactive/status-bar.js +43 -11
- package/dist/modes/interactive/status-line.d.ts +2 -0
- package/dist/modes/interactive/status-line.js +11 -6
- package/dist/modes/interactive/status-quota.d.ts +44 -0
- package/dist/modes/interactive/status-quota.js +135 -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 +2 -1
- package/dist/modes/rpc/commands.js +9 -1
- package/dist/rpc.d.ts +13 -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/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 +8 -3
- package/dist/tui/keybindings.js +8 -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 +19 -7
- package/docs/en/sessions.md +3 -1
- package/docs/en/tui.md +84 -66
- package/docs/host-api.md +5 -1
- package/docs/providers.md +4 -2
- package/docs/rpc.md +19 -7
- package/docs/sessions.md +2 -1
- package/docs/tui-design.md +42 -29
- package/docs/tui.md +67 -49
- package/package.json +1 -1
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 前台子 Agent 任务转后台与栏内停止的界面接线(docs/agents-concurrency-plan.md §2.4、§2.8)。[W7-C]
|
|
3
|
+
*
|
|
4
|
+
* - `Ctrl+B`(`app.tasks.background`):有阻塞中的前台任务(`registry.blocking()`,含 `task_ctl wait`)时
|
|
5
|
+
* 全部转后台(`session.backgroundTask(undefined, "user")`),不看输入框;没有时落回编辑器。
|
|
6
|
+
* - 栏内 `b` / `Ctrl+B`:转后台选中的任务;`x`:1.5 s 内连按两次停止(`StopArm`)。
|
|
7
|
+
* - `/tasks bg [id]`(commands-core.ts,line 模式同):同 Ctrl+B / 指定任务。
|
|
8
|
+
* - Esc 中断只连带前台任务:仍在运行的后台任务写进中断提示(`runningBackground`)。
|
|
9
|
+
*/
|
|
10
|
+
import { registryOf } from "../../agent/subagent-registry.js";
|
|
11
|
+
import { msg } from "../../i18n/index.js";
|
|
12
|
+
/** 栏内 `x` 两次之间的最长间隔。 */
|
|
13
|
+
export const STOP_CONFIRM_MS = 1500;
|
|
14
|
+
/** 正在阻塞父回合的任务(前台运行中、或有 `task_ctl wait` 在等)。 */
|
|
15
|
+
export function blockingTasks(session) {
|
|
16
|
+
return registryOf(session.state.sessionId)?.blocking() ?? [];
|
|
17
|
+
}
|
|
18
|
+
/** 人工转后台:不给 taskId = 全部阻塞中的任务(没有时不调用,返回空)。 */
|
|
19
|
+
export function backgroundTasks(session, taskId) {
|
|
20
|
+
if (taskId === undefined && blockingTasks(session).length === 0)
|
|
21
|
+
return [];
|
|
22
|
+
return session.backgroundTask(taskId, "user");
|
|
23
|
+
}
|
|
24
|
+
/** 仍在运行的后台任务(Esc 中断不影响它们)。 */
|
|
25
|
+
export function runningBackground(session) {
|
|
26
|
+
const registry = registryOf(session.state.sessionId);
|
|
27
|
+
if (registry === undefined)
|
|
28
|
+
return [];
|
|
29
|
+
return registry
|
|
30
|
+
.list()
|
|
31
|
+
.filter((task) => task.status === "running" && task.background)
|
|
32
|
+
.map((task) => task.taskId);
|
|
33
|
+
}
|
|
34
|
+
/** 转后台后的一行提示;没有转任何任务时给「没有可转的」。 */
|
|
35
|
+
export function backgroundedText(ids) {
|
|
36
|
+
const m = msg().agents.background;
|
|
37
|
+
return ids.length === 0 ? m.none : m.done(ids.join(", "));
|
|
38
|
+
}
|
|
39
|
+
/** `ctrl+b` → `Ctrl+B`(提示行用);动作没有绑定时 undefined。 */
|
|
40
|
+
export function keyLabel(keys, action) {
|
|
41
|
+
const id = keys.keys(action)[0];
|
|
42
|
+
if (id === undefined)
|
|
43
|
+
return undefined;
|
|
44
|
+
return id
|
|
45
|
+
.split("+")
|
|
46
|
+
.map((part) => part.length === 1 ? part.toUpperCase() : part[0].toUpperCase() + part.slice(1))
|
|
47
|
+
.join("+");
|
|
48
|
+
}
|
|
49
|
+
/** 栏内 `x` 的双击确认:第一次 `arm`(提示「再按 x 停止」),间隔内对同一任务再按为 `stop`。 */
|
|
50
|
+
export class StopArm {
|
|
51
|
+
windowMs;
|
|
52
|
+
armed;
|
|
53
|
+
constructor(windowMs = STOP_CONFIRM_MS) {
|
|
54
|
+
this.windowMs = windowMs;
|
|
55
|
+
}
|
|
56
|
+
press(taskId, now) {
|
|
57
|
+
const armed = this.armed;
|
|
58
|
+
if (armed !== undefined && armed.taskId === taskId && now - armed.at < this.windowMs) {
|
|
59
|
+
this.armed = undefined;
|
|
60
|
+
return "stop";
|
|
61
|
+
}
|
|
62
|
+
this.armed = { taskId, at: now };
|
|
63
|
+
return "arm";
|
|
64
|
+
}
|
|
65
|
+
reset() {
|
|
66
|
+
this.armed = undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* - 运行中摘要行 `⎿ ⠋ 运行中 · 4s`:spinner 与底部 Loader 同帧(ToolTracker.tick 由 Loader.onFrame 驱动),
|
|
12
12
|
* 标题行不变;bash 流式显示尾部 8 行(muted)。task 显示 `子 Agent · 运行中 1m05s`;有 `subagent_*`
|
|
13
13
|
* 事件时(W5-U,subagent-view.ts)改为 `explore · 运行中 1m05s · 3 轮 · 最近 3 个工具 · ↑↓`,后台任务
|
|
14
|
-
* 在结果摘要下多一行跟随状态。
|
|
14
|
+
* 在结果摘要下多一行跟随状态。[W7-C] 工具已返回而任务仍在后台跑(`details.status: "running"`):摘要行
|
|
15
|
+
* `⎿ 已转后台 · 12s`(前台转后台)/ `⎿ 后台运行`(直接后台),给模型的说明文字折叠时不显示。
|
|
15
16
|
* - 折叠(缺省):结果前 3 行;edit 显示 `details.diff`(`@@` dim、`+` success、`-` error、上下文 muted,
|
|
16
17
|
* 宽 ≥ 60 时带行号列),前 12 行;`Ctrl+O` 展开全部(上限 400 行,长行折行)。
|
|
17
18
|
* - 嵌套:带 `parentToolCallId` 的调用(codemode 脚本里的 `tools.*`)挂在外层调用下、右移 4 列;折叠时只列
|
|
@@ -85,6 +86,11 @@ export declare class ToolView implements Component {
|
|
|
85
86
|
header(width: number): string;
|
|
86
87
|
/** ` ⎿ 摘要`:运行中带 spinner 与秒数,完成后是结果摘要。 */
|
|
87
88
|
summaryLine(width: number): string | undefined;
|
|
89
|
+
/**
|
|
90
|
+
* [W7-C] task 已返回、任务仍在后台跑:true = 前台转后台(固定英文结果文本),false = 直接后台;
|
|
91
|
+
* 其它情况 undefined。
|
|
92
|
+
*/
|
|
93
|
+
private backgrounded;
|
|
88
94
|
/** 后台子 Agent:结果摘要下一行跟随状态 ` ↳ t2 explore · 完成 2m · 7 轮 …`。 */
|
|
89
95
|
private followLine;
|
|
90
96
|
private custom;
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* - 运行中摘要行 `⎿ ⠋ 运行中 · 4s`:spinner 与底部 Loader 同帧(ToolTracker.tick 由 Loader.onFrame 驱动),
|
|
12
12
|
* 标题行不变;bash 流式显示尾部 8 行(muted)。task 显示 `子 Agent · 运行中 1m05s`;有 `subagent_*`
|
|
13
13
|
* 事件时(W5-U,subagent-view.ts)改为 `explore · 运行中 1m05s · 3 轮 · 最近 3 个工具 · ↑↓`,后台任务
|
|
14
|
-
* 在结果摘要下多一行跟随状态。
|
|
14
|
+
* 在结果摘要下多一行跟随状态。[W7-C] 工具已返回而任务仍在后台跑(`details.status: "running"`):摘要行
|
|
15
|
+
* `⎿ 已转后台 · 12s`(前台转后台)/ `⎿ 后台运行`(直接后台),给模型的说明文字折叠时不显示。
|
|
15
16
|
* - 折叠(缺省):结果前 3 行;edit 显示 `details.diff`(`@@` dim、`+` success、`-` error、上下文 muted,
|
|
16
17
|
* 宽 ≥ 60 时带行号列),前 12 行;`Ctrl+O` 展开全部(上限 400 行,长行折行)。
|
|
17
18
|
* - 嵌套:带 `parentToolCallId` 的调用(codemode 脚本里的 `tools.*`)挂在外层调用下、右移 4 列;折叠时只列
|
|
@@ -176,6 +177,12 @@ export class ToolView {
|
|
|
176
177
|
return truncateToWidth(lead + theme.fg("accent", frame) + " " + theme.fg("muted", what), width);
|
|
177
178
|
}
|
|
178
179
|
const result = this.result;
|
|
180
|
+
const moved = this.backgrounded();
|
|
181
|
+
if (moved !== undefined) {
|
|
182
|
+
const m = msg().agents.background;
|
|
183
|
+
const text = moved ? m.moved(formatElapsed(this.elapsedMs)) : m.started;
|
|
184
|
+
return truncateToWidth(lead + theme.fg("muted", text), width);
|
|
185
|
+
}
|
|
179
186
|
const text = resultSummary({
|
|
180
187
|
name: this.toolName,
|
|
181
188
|
args: this.args,
|
|
@@ -187,6 +194,19 @@ export class ToolView {
|
|
|
187
194
|
}, theme);
|
|
188
195
|
return truncateToWidth(lead + text, width);
|
|
189
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* [W7-C] task 已返回、任务仍在后台跑:true = 前台转后台(固定英文结果文本),false = 直接后台;
|
|
199
|
+
* 其它情况 undefined。
|
|
200
|
+
*/
|
|
201
|
+
backgrounded() {
|
|
202
|
+
const result = this.result;
|
|
203
|
+
if (this.toolName !== "task" || this.state !== "done" || result === undefined)
|
|
204
|
+
return undefined;
|
|
205
|
+
const details = result.details;
|
|
206
|
+
if (details?.status !== "running")
|
|
207
|
+
return undefined;
|
|
208
|
+
return /moved to the background/i.test(contentText(result.content));
|
|
209
|
+
}
|
|
190
210
|
/** 后台子 Agent:结果摘要下一行跟随状态 ` ↳ t2 explore · 完成 2m · 7 轮 …`。 */
|
|
191
211
|
followLine(width) {
|
|
192
212
|
const sub = this.subagentState();
|
|
@@ -292,6 +312,9 @@ export class ToolView {
|
|
|
292
312
|
}
|
|
293
313
|
return out;
|
|
294
314
|
}
|
|
315
|
+
// [W7-C] 后台运行中的 task:结果是给模型的说明,折叠时不显示(跟随行已有状态)
|
|
316
|
+
if (!this.expanded && this.backgrounded() !== undefined)
|
|
317
|
+
return out;
|
|
295
318
|
const custom = this.customResult(width);
|
|
296
319
|
if (custom !== undefined)
|
|
297
320
|
return [...out, ...custom];
|
|
@@ -21,12 +21,17 @@
|
|
|
21
21
|
* - [W5-H2] plan 模式产出计划、`plan.unattended: stop`(缺省)没人审批:stderr 一行(计划文件与审批办法)、
|
|
22
22
|
* json 带 `planPending{planId, version, filePath}`、退出码 9。
|
|
23
23
|
* - [W5-H2] `--image` / `@图片` 超限时按 config `images.resize` 缩放(缺省 auto)。
|
|
24
|
+
* - [W7-B2] 后台子 Agent(显式 `background:true` 或 `subagents.background: always`):主回合结束后若还有任务
|
|
25
|
+
* 在跑或通知待投递,stderr 一行提示并等它们结束、跑完通知回合再输出(最终文本 = 最后一条助手回复);
|
|
26
|
+
* 受 `--max-turns` / `--max-cost` / `limits.*` 约束(到限即停止等待,退出码 8),SIGINT / SIGTERM 照常中止
|
|
27
|
+
* (未结束的任务随会话关闭被停止);json 结果带 `tasks`(同 `getStats().tasks`)。
|
|
24
28
|
* - 退出码:最终助手消息 `error / aborted` 或提示被拒 → 1;有工具调用被拒 → 7;到达预算 → 8;
|
|
25
29
|
* 计划待审批 → 9;SIGINT 130、SIGTERM 143(先 abort)。
|
|
26
30
|
*/
|
|
27
31
|
import type { CliIo, ModeContext } from "../../cli/deps.js";
|
|
28
32
|
import type { Runtime } from "../../cli/runtime.js";
|
|
29
33
|
import type { LimitReachedEvent, PlanProposedEvent } from "../../agent/types.js";
|
|
34
|
+
import type { AgentSession } from "../../agent/types.js";
|
|
30
35
|
export declare function joinPrompt(argument: string | undefined, piped: string): string;
|
|
31
36
|
/** 等 stdin 多久后提示一次「正在等待」。 */
|
|
32
37
|
export declare const STDIN_WAIT_HINT_MS = 3000;
|
|
@@ -39,6 +44,12 @@ export declare function stdinWaitMs(env: Readonly<Record<string, string | undefi
|
|
|
39
44
|
*/
|
|
40
45
|
export declare function readPromptStdin(io: CliIo, prompt: string | undefined, mode: "auto" | "explicit" | "off", hintMs?: number): Promise<string>;
|
|
41
46
|
export declare function runPrintMode(runtime: Runtime, context: ModeContext): Promise<number>;
|
|
47
|
+
/**
|
|
48
|
+
* [W7-B2] 主回合结束后等后台任务与它们的通知回合(docs/agents-concurrency-plan.md §2.6、§6 Q5)。
|
|
49
|
+
* `settled()`(subagent-background.ts)等到没有运行中任务且通知投递链(含通知回合)结束;`stopped`:SIGINT /
|
|
50
|
+
* SIGTERM、stdout 关闭或预算到限时 resolve,立即停止等待。没有子 Agent 注册表时直接返回。
|
|
51
|
+
*/
|
|
52
|
+
export declare function waitBackgroundTasks(session: AgentSession, stopped: Promise<void>, io: Pick<CliIo, "stderr">): Promise<void>;
|
|
42
53
|
/** stderr 一行:计划已落盘待审批(-p 不替人批准)。 */
|
|
43
54
|
export declare function describePlanPending(plan: PlanProposedEvent): string;
|
|
44
55
|
/** stderr 一行:哪个预算到限。 */
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
* - [W5-H2] plan 模式产出计划、`plan.unattended: stop`(缺省)没人审批:stderr 一行(计划文件与审批办法)、
|
|
22
22
|
* json 带 `planPending{planId, version, filePath}`、退出码 9。
|
|
23
23
|
* - [W5-H2] `--image` / `@图片` 超限时按 config `images.resize` 缩放(缺省 auto)。
|
|
24
|
+
* - [W7-B2] 后台子 Agent(显式 `background:true` 或 `subagents.background: always`):主回合结束后若还有任务
|
|
25
|
+
* 在跑或通知待投递,stderr 一行提示并等它们结束、跑完通知回合再输出(最终文本 = 最后一条助手回复);
|
|
26
|
+
* 受 `--max-turns` / `--max-cost` / `limits.*` 约束(到限即停止等待,退出码 8),SIGINT / SIGTERM 照常中止
|
|
27
|
+
* (未结束的任务随会话关闭被停止);json 结果带 `tasks`(同 `getStats().tasks`)。
|
|
24
28
|
* - 退出码:最终助手消息 `error / aborted` 或提示被拒 → 1;有工具调用被拒 → 7;到达预算 → 8;
|
|
25
29
|
* 计划待审批 → 9;SIGINT 130、SIGTERM 143(先 abort)。
|
|
26
30
|
*/
|
|
@@ -29,6 +33,7 @@ import { ExitCode } from "../../cli/exit-codes.js";
|
|
|
29
33
|
import { errorText, lastAssistant, onStdoutClosed, onTerminationSignals } from "../shared.js";
|
|
30
34
|
import { toJsonLine, toWireEvent } from "./json-event.js";
|
|
31
35
|
import { formatUsd } from "../../agent/limits.js";
|
|
36
|
+
import { registryOf } from "../../agent/subagent-registry.js";
|
|
32
37
|
import { promptImages, sessionModel } from "../image-input.js";
|
|
33
38
|
import { msg } from "../../i18n/index.js";
|
|
34
39
|
export function joinPrompt(argument, piped) {
|
|
@@ -118,9 +123,15 @@ export async function runPrintMode(runtime, context) {
|
|
|
118
123
|
const denied = [];
|
|
119
124
|
let limit;
|
|
120
125
|
let plan;
|
|
126
|
+
let stopWaiting = () => undefined;
|
|
127
|
+
const stopped = new Promise((resolve) => {
|
|
128
|
+
stopWaiting = resolve;
|
|
129
|
+
});
|
|
121
130
|
const unsubscribe = session.subscribe((event) => {
|
|
122
|
-
if (event.type === "limit_reached")
|
|
131
|
+
if (event.type === "limit_reached") {
|
|
123
132
|
limit ??= event;
|
|
133
|
+
stopWaiting();
|
|
134
|
+
}
|
|
124
135
|
else if (event.type === "plan_proposed")
|
|
125
136
|
plan = event;
|
|
126
137
|
else if (event.type === "plan_resolved" && event.planId === plan?.planId)
|
|
@@ -139,16 +150,25 @@ export async function runPrintMode(runtime, context) {
|
|
|
139
150
|
let signalled;
|
|
140
151
|
const offSignals = onTerminationSignals((code) => {
|
|
141
152
|
signalled ??= code;
|
|
153
|
+
stopWaiting();
|
|
142
154
|
void session.abort();
|
|
143
155
|
});
|
|
144
156
|
let stdoutClosed = false;
|
|
145
157
|
const offEpipe = onStdoutClosed(() => {
|
|
146
158
|
stdoutClosed = true;
|
|
159
|
+
stopWaiting();
|
|
147
160
|
void session.abort();
|
|
148
161
|
});
|
|
149
162
|
let failure;
|
|
150
163
|
try {
|
|
151
164
|
await session.prompt(prompt, images.length > 0 ? { images } : {});
|
|
165
|
+
const ended = lastAssistant(session)?.stopReason;
|
|
166
|
+
if (signalled === undefined &&
|
|
167
|
+
!stdoutClosed &&
|
|
168
|
+
limit === undefined &&
|
|
169
|
+
ended !== "error" &&
|
|
170
|
+
ended !== "aborted")
|
|
171
|
+
await waitBackgroundTasks(session, stopped, io);
|
|
152
172
|
}
|
|
153
173
|
catch (error) {
|
|
154
174
|
failure = errorText(error);
|
|
@@ -183,6 +203,7 @@ export async function runPrintMode(runtime, context) {
|
|
|
183
203
|
cost: stats.cost,
|
|
184
204
|
cacheHitRate: stats.cacheHitRate,
|
|
185
205
|
...(stats.cache !== undefined ? { cache: stats.cache } : {}),
|
|
206
|
+
...(stats.tasks !== undefined ? { tasks: stats.tasks } : {}),
|
|
186
207
|
...(denied.length > 0 ? { deniedTools: denied } : {}),
|
|
187
208
|
...(plan !== undefined
|
|
188
209
|
? {
|
|
@@ -230,6 +251,20 @@ export async function runPrintMode(runtime, context) {
|
|
|
230
251
|
return ExitCode.ToolDenied;
|
|
231
252
|
return ExitCode.Ok;
|
|
232
253
|
}
|
|
254
|
+
/**
|
|
255
|
+
* [W7-B2] 主回合结束后等后台任务与它们的通知回合(docs/agents-concurrency-plan.md §2.6、§6 Q5)。
|
|
256
|
+
* `settled()`(subagent-background.ts)等到没有运行中任务且通知投递链(含通知回合)结束;`stopped`:SIGINT /
|
|
257
|
+
* SIGTERM、stdout 关闭或预算到限时 resolve,立即停止等待。没有子 Agent 注册表时直接返回。
|
|
258
|
+
*/
|
|
259
|
+
export async function waitBackgroundTasks(session, stopped, io) {
|
|
260
|
+
const registry = registryOf(session.state.sessionId);
|
|
261
|
+
if (registry === undefined)
|
|
262
|
+
return;
|
|
263
|
+
const running = registry.list().filter((task) => task.status === "running").length;
|
|
264
|
+
if (running > 0)
|
|
265
|
+
io.stderr(msg().print.print.waitingTasks(running));
|
|
266
|
+
await Promise.race([registry.settled(), stopped]);
|
|
267
|
+
}
|
|
233
268
|
/** stderr 一行:计划已落盘待审批(-p 不替人批准)。 */
|
|
234
269
|
export function describePlanPending(plan) {
|
|
235
270
|
const where = plan.filePath ?? plan.planId;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* RPC 命令分派表(设计 §13.2、src/rpc.ts
|
|
2
|
+
* RPC 命令分派表(设计 §13.2、src/rpc.ts 的命令表,现 44 条)。[B6]
|
|
3
3
|
*
|
|
4
4
|
* - 提示类(prompt / steer / follow_up)不等运行结束:会话开始运行(`before_agent_start` /
|
|
5
5
|
* `agent_start`)、入队或被处理后立刻应答 `{ disposition }`;运行中的进展走事件。
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* - [W5-F] 计划:声明 `plans` 能力后计划审批交客户端(`plan_proposed` → `plan_response`),未声明按
|
|
12
12
|
* `plan.unattended`;`approve_fresh` 在这里新建会话并以计划全文开新回合。`get_tasks / get_agents`
|
|
13
13
|
* 读 `RpcContext.tasks / agents`(W5-G 的注册表与发现结果;未装配时回空表)。
|
|
14
|
+
* - [W7-B2] `background_task { taskId? }` → `{ backgrounded }`(`session.backgroundTask`,B1)。
|
|
14
15
|
*/
|
|
15
16
|
import type { AgentInfo } from "../../agents/types.js";
|
|
16
17
|
import type { TaskRegistryView } from "../../tools/types.js";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* RPC 命令分派表(设计 §13.2、src/rpc.ts
|
|
2
|
+
* RPC 命令分派表(设计 §13.2、src/rpc.ts 的命令表,现 44 条)。[B6]
|
|
3
3
|
*
|
|
4
4
|
* - 提示类(prompt / steer / follow_up)不等运行结束:会话开始运行(`before_agent_start` /
|
|
5
5
|
* `agent_start`)、入队或被处理后立刻应答 `{ disposition }`;运行中的进展走事件。
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* - [W5-F] 计划:声明 `plans` 能力后计划审批交客户端(`plan_proposed` → `plan_response`),未声明按
|
|
12
12
|
* `plan.unattended`;`approve_fresh` 在这里新建会话并以计划全文开新回合。`get_tasks / get_agents`
|
|
13
13
|
* 读 `RpcContext.tasks / agents`(W5-G 的注册表与发现结果;未装配时回空表)。
|
|
14
|
+
* - [W7-B2] `background_task { taskId? }` → `{ backgrounded }`(`session.backgroundTask`,B1)。
|
|
14
15
|
*/
|
|
15
16
|
import { formatModelRef } from "../../ai/providers/channels.js";
|
|
16
17
|
import { AgentSessionImpl } from "../../agent/session.js";
|
|
@@ -312,5 +313,12 @@ export const handlers = {
|
|
|
312
313
|
get_agents: async (_p, ctx) => ({ agents: [...(ctx.agents?.() ?? [])] }),
|
|
313
314
|
// [W6-T2] 轨迹(docs/wave6-plan.md §2.6;分页 / 增量 / 脱敏见 trace/query.ts)
|
|
314
315
|
get_trace: async (p, ctx) => sessionTrace(ctx.session(), p ?? {}),
|
|
316
|
+
// [W7-B2] 前台任务转后台:工具调用立即返回、任务继续,完成后照常通知
|
|
317
|
+
background_task: async (p, ctx) => {
|
|
318
|
+
const taskId = p?.taskId;
|
|
319
|
+
if (taskId !== undefined && typeof taskId !== "string")
|
|
320
|
+
throw new AmaError("invalid_arguments", msg().print.rpc.badTaskId);
|
|
321
|
+
return { backgrounded: ctx.session().backgroundTask(taskId) };
|
|
322
|
+
},
|
|
315
323
|
};
|
|
316
324
|
export const RPC_COMMAND_TYPES = Object.keys(handlers);
|
package/dist/rpc.d.ts
CHANGED
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
* - [W6-C0] 第六波(docs/wave6-plan.md §2.6、§4.4):命令 `get_trace`(参数 `RpcGetTraceParams`、返回
|
|
20
20
|
* `RpcW6Results`,W6-T2 实现,之前回 `not_implemented`);事件 `quota_update`;`permission_request.context`
|
|
21
21
|
* 可带 `toolCallId`;`keySource` 可为 `oauth`。`RPC_PROTOCOL_VERSION` 不变。
|
|
22
|
+
* - [W7-B2] 后台子 Agent(docs/agents-concurrency-plan.md §2.5):命令 `background_task { taskId? }` →
|
|
23
|
+
* `RpcW7Results`(无 taskId = 全部前台运行中任务;对已结束 / 已在后台的任务回空表);事件
|
|
24
|
+
* `subagent_background` 由 `SessionEvent` 派生。`RPC_PROTOCOL_VERSION` 不变。
|
|
22
25
|
*/
|
|
23
26
|
import type { AssistantEvent, ImageBlock, ModelThinkingLevel, Usage } from "./ai/types.js";
|
|
24
27
|
import type { PlanData, PlanDecisionKind, QueueMode, SessionEvent, TodoItemView } from "./agent/types.js";
|
|
@@ -139,6 +142,16 @@ export interface RpcCommandMap {
|
|
|
139
142
|
get_tasks: NoParams;
|
|
140
143
|
get_agents: NoParams;
|
|
141
144
|
get_trace: RpcGetTraceParams;
|
|
145
|
+
background_task: {
|
|
146
|
+
taskId?: string;
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/** [W7-B2] 第七波命令成功时的 `data` 形状。 */
|
|
150
|
+
export interface RpcW7Results {
|
|
151
|
+
/** 实际转了后台的 taskId(含被打断的 `task_ctl wait`);没有可转的为空表。 */
|
|
152
|
+
background_task: {
|
|
153
|
+
backgrounded: string[];
|
|
154
|
+
};
|
|
142
155
|
}
|
|
143
156
|
/** [W6-C0] `get_trace` 的参数。 */
|
|
144
157
|
export interface RpcGetTraceParams {
|
package/dist/rpc.js
CHANGED
|
@@ -19,5 +19,8 @@
|
|
|
19
19
|
* - [W6-C0] 第六波(docs/wave6-plan.md §2.6、§4.4):命令 `get_trace`(参数 `RpcGetTraceParams`、返回
|
|
20
20
|
* `RpcW6Results`,W6-T2 实现,之前回 `not_implemented`);事件 `quota_update`;`permission_request.context`
|
|
21
21
|
* 可带 `toolCallId`;`keySource` 可为 `oauth`。`RPC_PROTOCOL_VERSION` 不变。
|
|
22
|
+
* - [W7-B2] 后台子 Agent(docs/agents-concurrency-plan.md §2.5):命令 `background_task { taskId? }` →
|
|
23
|
+
* `RpcW7Results`(无 taskId = 全部前台运行中任务;对已结束 / 已在后台的任务回空表);事件
|
|
24
|
+
* `subagent_background` 由 `SessionEvent` 派生。`RPC_PROTOCOL_VERSION` 不变。
|
|
22
25
|
*/
|
|
23
26
|
export const RPC_PROTOCOL_VERSION = 1;
|
package/dist/tools/task-ctl.d.ts
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* 与父一致,运行时拒绝;
|
|
8
8
|
* - `wait` / `output` 是轮询类调用(`annotations.pollable`,重复调用检测豁免);
|
|
9
9
|
* - `send` = 续聊放后台:等价于 `task{taskId, prompt, background:true}`,完成后同样收到通知。
|
|
10
|
+
* - [W7-B1] `wait` 被转后台(`background()`,TUI `Ctrl+B` / RPC `background_task`)打断时立即返回固定
|
|
11
|
+
* 文案,要求模型不要再等。
|
|
10
12
|
*/
|
|
11
13
|
import type { ToolDefinition } from "./types.js";
|
|
12
14
|
export declare const TASK_CTL_TOOL = "task_ctl";
|
package/dist/tools/task-ctl.js
CHANGED
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
* 与父一致,运行时拒绝;
|
|
8
8
|
* - `wait` / `output` 是轮询类调用(`annotations.pollable`,重复调用检测豁免);
|
|
9
9
|
* - `send` = 续聊放后台:等价于 `task{taskId, prompt, background:true}`,完成后同样收到通知。
|
|
10
|
+
* - [W7-B1] `wait` 被转后台(`background()`,TUI `Ctrl+B` / RPC `background_task`)打断时立即返回固定
|
|
11
|
+
* 文案,要求模型不要再等。
|
|
10
12
|
*/
|
|
11
|
-
import { capTaskText, formatTokens } from "../agents/result.js";
|
|
13
|
+
import { capTaskText, formatTokens, waitDetachedText } from "../agents/result.js";
|
|
12
14
|
import { taskControl } from "../agents/task-control.js";
|
|
13
15
|
export const TASK_CTL_TOOL = "task_ctl";
|
|
14
16
|
export const DEFAULT_WAIT_MS = 30_000;
|
|
@@ -96,7 +98,10 @@ export function createTaskCtlTool() {
|
|
|
96
98
|
}
|
|
97
99
|
case "wait": {
|
|
98
100
|
const timeout = Math.min(Math.max(input.timeoutMs ?? DEFAULT_WAIT_MS, 0), MAX_WAIT_MS);
|
|
99
|
-
const
|
|
101
|
+
const signal = control.detachSignal(taskId);
|
|
102
|
+
const result = await control.wait(taskId, timeout, { signal });
|
|
103
|
+
if (result === undefined && signal.aborted)
|
|
104
|
+
return { content: waitDetachedText(taskId) };
|
|
100
105
|
if (result === undefined) {
|
|
101
106
|
const turns = control.get(taskId)?.turns ?? 0;
|
|
102
107
|
return {
|
package/dist/tools/task.d.ts
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* - `agent`:子 Agent 类型(内置 general / explore / plan、`.ama/agents/*.md`)或外部 Agent;缺省
|
|
6
6
|
* general。描述里列出可用类型(catalog 由 compose-agents.ts 经 `bindTaskAgents` 绑定;没绑定时
|
|
7
7
|
* 只列内置类型)。
|
|
8
|
+
* - [W7-B1] 缺省前台 / 后台由 `subagents.background` 决定(交互 / RPC / ACP 后台,`-p` 前台),描述随之
|
|
9
|
+
* 二选一(`bindTaskBackground`);前台任务可被 `background()` 转后台,工具立即以固定文案返回。
|
|
8
10
|
* - `background`:立即返回 taskId,完成后父会话收到 `<task-notification>`;`taskId`:向已有子会话
|
|
9
11
|
* 追加消息(续聊,忽略 agent / tools / model);`isolation: "worktree"`:在独立 git worktree 里跑。
|
|
10
12
|
* - **parallel**:同一回复里的多个 task 真并行(会话注册表的池限流,缺省 4);与 sequential 工具
|
|
@@ -33,9 +35,18 @@ export interface TaskInput {
|
|
|
33
35
|
/** 预留(并发上限在会话的任务注册表,见 agent/subagent-registry.ts)。 */
|
|
34
36
|
export interface TaskToolOptions {
|
|
35
37
|
}
|
|
38
|
+
/** 缺省前台(`-p`、`subagents.background: never`)时的描述。 */
|
|
39
|
+
export declare const FOREGROUND_DESCRIPTION: string;
|
|
40
|
+
/** [W7-B1] 缺省后台(交互 / RPC / ACP,docs/agents-concurrency-plan.md §2.6)时的描述。 */
|
|
41
|
+
export declare const BACKGROUND_DESCRIPTION: string;
|
|
36
42
|
/** 描述里类型清单的标题(清单本身另有 400 token 预算,见 agents/catalog.ts)。 */
|
|
37
43
|
export declare const TASK_AGENTS_HEADING = "\nAgents:\n";
|
|
38
44
|
/** 组装根把会话的类型目录绑到 task 工具上(描述里的类型清单)。 */
|
|
39
45
|
export declare function bindTaskAgents(tool: ToolDefinition | undefined, catalog: AgentCatalog): void;
|
|
46
|
+
/**
|
|
47
|
+
* [W7-B1] 组装根把本进程的缺省前台 / 后台(`subagents.background` 按运行模式解析后)绑到 task 工具:
|
|
48
|
+
* 决定描述用哪一版。会话内不变(缓存前缀稳定);没绑定按缺省前台。
|
|
49
|
+
*/
|
|
50
|
+
export declare function bindTaskBackground(tool: ToolDefinition | undefined, background: boolean): void;
|
|
40
51
|
export declare function buildSubagentRequest(input: TaskInput, ctx: ToolContext): SubagentRequest | string;
|
|
41
52
|
export declare function createTaskTool(_options?: TaskToolOptions): ToolDefinition<TaskInput>;
|
package/dist/tools/task.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* - `agent`:子 Agent 类型(内置 general / explore / plan、`.ama/agents/*.md`)或外部 Agent;缺省
|
|
6
6
|
* general。描述里列出可用类型(catalog 由 compose-agents.ts 经 `bindTaskAgents` 绑定;没绑定时
|
|
7
7
|
* 只列内置类型)。
|
|
8
|
+
* - [W7-B1] 缺省前台 / 后台由 `subagents.background` 决定(交互 / RPC / ACP 后台,`-p` 前台),描述随之
|
|
9
|
+
* 二选一(`bindTaskBackground`);前台任务可被 `background()` 转后台,工具立即以固定文案返回。
|
|
8
10
|
* - `background`:立即返回 taskId,完成后父会话收到 `<task-notification>`;`taskId`:向已有子会话
|
|
9
11
|
* 追加消息(续聊,忽略 agent / tools / model);`isolation: "worktree"`:在独立 git worktree 里跑。
|
|
10
12
|
* - **parallel**:同一回复里的多个 task 真并行(会话注册表的池限流,缺省 4);与 sequential 工具
|
|
@@ -24,17 +26,32 @@ const THINKING = [
|
|
|
24
26
|
"high",
|
|
25
27
|
"xhigh",
|
|
26
28
|
];
|
|
27
|
-
|
|
29
|
+
/** 缺省前台(`-p`、`subagents.background: never`)时的描述。 */
|
|
30
|
+
export const FOREGROUND_DESCRIPTION = "Delegate to a sub-agent (fresh context; give full instructions). Returns its final report. " +
|
|
28
31
|
"Tasks in one reply run in parallel; writers should use isolation worktree. background: " +
|
|
29
32
|
"returns a taskId now (see task_ctl); taskId: continue that task.";
|
|
33
|
+
/** [W7-B1] 缺省后台(交互 / RPC / ACP,docs/agents-concurrency-plan.md §2.6)时的描述。 */
|
|
34
|
+
export const BACKGROUND_DESCRIPTION = "Delegate to a sub-agent (fresh context; give full instructions). Runs in the background by " +
|
|
35
|
+
"default: returns a taskId; a <task-notification> follows when done, so keep working. " +
|
|
36
|
+
"background:false if your next step needs the result. taskId: continue a task. Tasks in one " +
|
|
37
|
+
"reply run in parallel; writers use isolation worktree.";
|
|
30
38
|
/** 描述里类型清单的标题(清单本身另有 400 token 预算,见 agents/catalog.ts)。 */
|
|
31
39
|
export const TASK_AGENTS_HEADING = "\nAgents:\n";
|
|
32
40
|
const catalogs = new WeakMap();
|
|
41
|
+
const backgrounds = new WeakMap();
|
|
33
42
|
/** 组装根把会话的类型目录绑到 task 工具上(描述里的类型清单)。 */
|
|
34
43
|
export function bindTaskAgents(tool, catalog) {
|
|
35
44
|
if (tool !== undefined)
|
|
36
45
|
catalogs.set(tool, catalog);
|
|
37
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* [W7-B1] 组装根把本进程的缺省前台 / 后台(`subagents.background` 按运行模式解析后)绑到 task 工具:
|
|
49
|
+
* 决定描述用哪一版。会话内不变(缓存前缀稳定);没绑定按缺省前台。
|
|
50
|
+
*/
|
|
51
|
+
export function bindTaskBackground(tool, background) {
|
|
52
|
+
if (tool !== undefined)
|
|
53
|
+
backgrounds.set(tool, background);
|
|
54
|
+
}
|
|
38
55
|
function fail(message) {
|
|
39
56
|
return { content: message, isError: true };
|
|
40
57
|
}
|
|
@@ -87,7 +104,8 @@ export function createTaskTool(_options = {}) {
|
|
|
87
104
|
label: "Task",
|
|
88
105
|
get description() {
|
|
89
106
|
const catalog = catalogs.get(tool) ?? new AgentCatalog();
|
|
90
|
-
|
|
107
|
+
const base = backgrounds.get(tool) === true ? BACKGROUND_DESCRIPTION : FOREGROUND_DESCRIPTION;
|
|
108
|
+
return `${base}${TASK_AGENTS_HEADING}${catalog.describe()}`;
|
|
91
109
|
},
|
|
92
110
|
parameters: {
|
|
93
111
|
type: "object",
|
|
@@ -88,6 +88,8 @@ export declare class Editor implements Component, Focusable {
|
|
|
88
88
|
col: number;
|
|
89
89
|
};
|
|
90
90
|
get isCompletionOpen(): boolean;
|
|
91
|
+
/** 正在用 ↑↓ 浏览输入历史(编辑后即退出)。 */
|
|
92
|
+
get isBrowsingHistory(): boolean;
|
|
91
93
|
getHistory(): readonly string[];
|
|
92
94
|
setHistory(entries: readonly string[]): void;
|
|
93
95
|
addToHistory(text: string): void;
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* 运行指示(设计 §12.1、§12.6;终端界面视觉设计 v1 §3.7)。[B4]
|
|
3
3
|
*
|
|
4
4
|
* 一行 `⠋ 动词 · 已用时 · 附加项…`:spinner `accent`、动词正文色、其余 `dim`,以 ` · ` 连接。
|
|
5
|
-
* - `setVerb(verb, extras, { elapsed })` 换动词(思考中 / 回复中 / 运行 bash / 等待确认……);
|
|
5
|
+
* - `setVerb(verb, extras, { elapsed, optional })` 换动词(思考中 / 回复中 / 运行 bash / 等待确认……);
|
|
6
|
+
* `optional` 是排在附加项之后的可丢弃项(按键提示),整行放不下时从后往前丢,而不是被截断;
|
|
6
7
|
* `setMessage(text)` 是只换动词的旧接口。
|
|
7
8
|
* - 帧取 `theme.glyphs.spinner`(Unicode 10 帧 80 ms、ASCII 4 帧 250 ms);`frame` 暴露当前帧字形,
|
|
8
9
|
* `onFrame` 让别的组件(运行中的工具摘要行)与 Loader 同帧换字,保证一帧只多改一行。
|
|
@@ -27,6 +28,8 @@ export interface LoaderOptions {
|
|
|
27
28
|
export interface LoaderVerbOptions {
|
|
28
29
|
/** 本动词是否显示已用时(缺省跟随 `showElapsed`)。 */
|
|
29
30
|
elapsed?: boolean;
|
|
31
|
+
/** 可丢弃的附加项:宽度不够时从后往前整项丢掉(缺省没有)。 */
|
|
32
|
+
optional?: readonly string[];
|
|
30
33
|
}
|
|
31
34
|
export declare function formatElapsed(ms: number): string;
|
|
32
35
|
export declare class Loader implements Component {
|
|
@@ -37,6 +40,7 @@ export declare class Loader implements Component {
|
|
|
37
40
|
private startedAt;
|
|
38
41
|
private verb;
|
|
39
42
|
private extras;
|
|
43
|
+
private optional;
|
|
40
44
|
private verbElapsed;
|
|
41
45
|
private lastElapsed;
|
|
42
46
|
private readonly listeners;
|
|
@@ -2,17 +2,21 @@
|
|
|
2
2
|
* 运行指示(设计 §12.1、§12.6;终端界面视觉设计 v1 §3.7)。[B4]
|
|
3
3
|
*
|
|
4
4
|
* 一行 `⠋ 动词 · 已用时 · 附加项…`:spinner `accent`、动词正文色、其余 `dim`,以 ` · ` 连接。
|
|
5
|
-
* - `setVerb(verb, extras, { elapsed })` 换动词(思考中 / 回复中 / 运行 bash / 等待确认……);
|
|
5
|
+
* - `setVerb(verb, extras, { elapsed, optional })` 换动词(思考中 / 回复中 / 运行 bash / 等待确认……);
|
|
6
|
+
* `optional` 是排在附加项之后的可丢弃项(按键提示),整行放不下时从后往前丢,而不是被截断;
|
|
6
7
|
* `setMessage(text)` 是只换动词的旧接口。
|
|
7
8
|
* - 帧取 `theme.glyphs.spinner`(Unicode 10 帧 80 ms、ASCII 4 帧 250 ms);`frame` 暴露当前帧字形,
|
|
8
9
|
* `onFrame` 让别的组件(运行中的工具摘要行)与 Loader 同帧换字,保证一帧只多改一行。
|
|
9
10
|
* - `animation: false`:spinner 固定为 `glyphs.spinnerStatic`,定时器每秒一次,已用时变了才请求重绘。
|
|
10
11
|
* - `stop()` 后不再触发渲染。时钟可注入便于测试。
|
|
11
12
|
*/
|
|
12
|
-
import { truncateToWidth } from "../ansi.js";
|
|
13
|
+
import { truncateToWidth, visibleWidth } from "../ansi.js";
|
|
13
14
|
import { UNICODE_GLYPHS } from "../glyphs.js";
|
|
14
15
|
/** Unicode 帧表(兼容旧导出;实际帧取 `theme.glyphs.spinner`)。 */
|
|
15
16
|
export const LOADER_FRAMES = UNICODE_GLYPHS.spinner;
|
|
17
|
+
function sameList(a, b) {
|
|
18
|
+
return a.length === b.length && a.every((e, i) => e === b[i]);
|
|
19
|
+
}
|
|
16
20
|
export function formatElapsed(ms) {
|
|
17
21
|
const total = Math.max(0, Math.floor(ms / 1000));
|
|
18
22
|
if (total < 60)
|
|
@@ -29,6 +33,7 @@ export class Loader {
|
|
|
29
33
|
startedAt;
|
|
30
34
|
verb;
|
|
31
35
|
extras = [];
|
|
36
|
+
optional = [];
|
|
32
37
|
verbElapsed;
|
|
33
38
|
lastElapsed = "";
|
|
34
39
|
listeners = new Set();
|
|
@@ -65,12 +70,13 @@ export class Loader {
|
|
|
65
70
|
setVerb(verb, extras = [], options = {}) {
|
|
66
71
|
const same = verb === this.verb &&
|
|
67
72
|
options.elapsed === this.verbElapsed &&
|
|
68
|
-
extras
|
|
69
|
-
|
|
73
|
+
sameList(extras, this.extras) &&
|
|
74
|
+
sameList(options.optional ?? [], this.optional);
|
|
70
75
|
if (same)
|
|
71
76
|
return;
|
|
72
77
|
this.verb = verb;
|
|
73
78
|
this.extras = [...extras];
|
|
79
|
+
this.optional = [...(options.optional ?? [])];
|
|
74
80
|
this.verbElapsed = options.elapsed;
|
|
75
81
|
this.requestRender();
|
|
76
82
|
}
|
|
@@ -142,7 +148,14 @@ export class Loader {
|
|
|
142
148
|
for (const extra of this.extras)
|
|
143
149
|
parts.push(dim(extra));
|
|
144
150
|
const spinner = theme ? theme.fg("accent", this.frame) : this.frame;
|
|
145
|
-
|
|
151
|
+
const line = (items) => `${spinner} ${items.join(dim(" · "))}`;
|
|
152
|
+
for (const extra of this.optional) {
|
|
153
|
+
const next = [...parts, dim(extra)];
|
|
154
|
+
if (visibleWidth(line(next)) > width)
|
|
155
|
+
break;
|
|
156
|
+
parts.push(dim(extra));
|
|
157
|
+
}
|
|
158
|
+
return [truncateToWidth(line(parts), width)];
|
|
146
159
|
}
|
|
147
160
|
invalidate() { }
|
|
148
161
|
}
|
|
@@ -45,10 +45,15 @@ export declare const DEFAULT_KEYBINDINGS: {
|
|
|
45
45
|
/** [W5-U] 粘贴剪贴板图片(写进数据目录,输入框插入 `@路径`)。 */
|
|
46
46
|
readonly "app.paste.image": readonly ["ctrl+v"];
|
|
47
47
|
/**
|
|
48
|
-
* [W6-A] 进入 Agent
|
|
49
|
-
*
|
|
48
|
+
* [W6-A / W7-A] 进入 Agent 栏:输入为空、补全未开、有子 Agent 任务即可(不要求栏可见);有字时 `↓` 仍是
|
|
49
|
+
* 下移 / 历史,并给一次提示。`Ctrl+B` 不再进栏(tmux 缺省前缀会吃掉它),留给 `app.tasks.background`。
|
|
50
50
|
*/
|
|
51
|
-
readonly "app.agents.focus": readonly ["
|
|
51
|
+
readonly "app.agents.focus": readonly ["down"];
|
|
52
|
+
/**
|
|
53
|
+
* [W7-C] 有阻塞中的前台子 Agent 任务(或 `task_ctl wait`)时全部转后台,不看输入框;没有时落回编辑器
|
|
54
|
+
* (`tui.editor.cursorLeft` 的 `ctrl+b`)。tmux 里按 `C-b C-b` 透传。
|
|
55
|
+
*/
|
|
56
|
+
readonly "app.tasks.background": readonly ["ctrl+b"];
|
|
52
57
|
};
|
|
53
58
|
export type ActionId = keyof typeof DEFAULT_KEYBINDINGS;
|
|
54
59
|
export type KeybindingOverrides = Partial<Record<ActionId, readonly string[]>>;
|
package/dist/tui/keybindings.js
CHANGED
|
@@ -48,10 +48,15 @@ export const DEFAULT_KEYBINDINGS = {
|
|
|
48
48
|
/** [W5-U] 粘贴剪贴板图片(写进数据目录,输入框插入 `@路径`)。 */
|
|
49
49
|
"app.paste.image": ["ctrl+v"],
|
|
50
50
|
/**
|
|
51
|
-
* [W6-A] 进入 Agent
|
|
52
|
-
*
|
|
51
|
+
* [W6-A / W7-A] 进入 Agent 栏:输入为空、补全未开、有子 Agent 任务即可(不要求栏可见);有字时 `↓` 仍是
|
|
52
|
+
* 下移 / 历史,并给一次提示。`Ctrl+B` 不再进栏(tmux 缺省前缀会吃掉它),留给 `app.tasks.background`。
|
|
53
53
|
*/
|
|
54
|
-
"app.agents.focus": ["
|
|
54
|
+
"app.agents.focus": ["down"],
|
|
55
|
+
/**
|
|
56
|
+
* [W7-C] 有阻塞中的前台子 Agent 任务(或 `task_ctl wait`)时全部转后台,不看输入框;没有时落回编辑器
|
|
57
|
+
* (`tui.editor.cursorLeft` 的 `ctrl+b`)。tmux 里按 `C-b C-b` 透传。
|
|
58
|
+
*/
|
|
59
|
+
"app.tasks.background": ["ctrl+b"],
|
|
55
60
|
};
|
|
56
61
|
export function isActionId(id) {
|
|
57
62
|
return Object.prototype.hasOwnProperty.call(DEFAULT_KEYBINDINGS, id);
|