@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,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 后台任务的审批停靠(docs/agents-concurrency-plan.md §2.7、D6)。[W7-C]
|
|
3
|
+
*
|
|
4
|
+
* 套在审批对话框 broker 外面:请求带 `context.taskId` 且该任务在后台运行(含转后台)时,主会话忙、输入框有
|
|
5
|
+
* 草稿或已有覆盖层就不弹框,先「停靠」——Agent 栏该任务行显示「等待审批」(agent-ui.ts 按
|
|
6
|
+
* `permission_request` 记下),运行提示行附「↓ 处理审批」;满足下列任一条件立即弹出:
|
|
7
|
+
* - 主会话空闲、输入为空、没有覆盖层(`ready`,每 250 ms 与相关事件后各对一次);
|
|
8
|
+
* - 正在子 Agent 视图里看这个任务(栏里 Enter 打开视图即弹出);
|
|
9
|
+
* - 停靠期间又来了不能停靠的请求(主会话自己的、前台任务的):审批链是串行的,它排在停靠的请求后面,
|
|
10
|
+
* 不放出来就会一直等,所以先弹停靠的那个——主会话与前台任务的审批照旧立即弹出。
|
|
11
|
+
*
|
|
12
|
+
* 停靠期间子任务阻塞在权限等待;超时 / 取消(链的 signal)照旧,停靠项随之撤掉、按 undefined 交回链。
|
|
13
|
+
* 前台任务与主会话的请求直接交给内层 broker,行为不变。
|
|
14
|
+
*/
|
|
15
|
+
export const DOCK_POLL_MS = 250;
|
|
16
|
+
export class ApprovalDock {
|
|
17
|
+
inner;
|
|
18
|
+
host;
|
|
19
|
+
docked = [];
|
|
20
|
+
/** 已发出、还没答复的不可停靠请求(主会话 / 前台任务)。 */
|
|
21
|
+
others = new Set();
|
|
22
|
+
timer;
|
|
23
|
+
constructor(inner, host) {
|
|
24
|
+
this.inner = inner;
|
|
25
|
+
this.host = host;
|
|
26
|
+
}
|
|
27
|
+
ask(request, signal) {
|
|
28
|
+
const taskId = request.context?.taskId;
|
|
29
|
+
if (taskId === undefined ||
|
|
30
|
+
signal.aborted ||
|
|
31
|
+
!this.host.dockable(taskId) ||
|
|
32
|
+
this.others.size > 0 ||
|
|
33
|
+
this.host.ready(taskId))
|
|
34
|
+
return this.inner.ask(request, signal);
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
const entry = {
|
|
37
|
+
taskId,
|
|
38
|
+
request,
|
|
39
|
+
signal,
|
|
40
|
+
resolve,
|
|
41
|
+
onAbort: () => {
|
|
42
|
+
this.remove(entry);
|
|
43
|
+
resolve(undefined);
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
signal.addEventListener("abort", entry.onAbort, { once: true });
|
|
47
|
+
this.docked.push(entry);
|
|
48
|
+
this.startPolling();
|
|
49
|
+
this.host.changed();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/** 停靠中的任务。 */
|
|
53
|
+
tasks() {
|
|
54
|
+
return new Set(this.docked.map((entry) => entry.taskId));
|
|
55
|
+
}
|
|
56
|
+
get size() {
|
|
57
|
+
return this.docked.length;
|
|
58
|
+
}
|
|
59
|
+
/** 会话事件:记下不可停靠的请求;停靠期间来了这种请求就放出停靠的(见文件头)。 */
|
|
60
|
+
observe(event) {
|
|
61
|
+
if (event.type === "permission_request") {
|
|
62
|
+
const taskId = event.context?.taskId;
|
|
63
|
+
if (taskId !== undefined && this.host.dockable(taskId))
|
|
64
|
+
return;
|
|
65
|
+
this.others.add(event.requestId);
|
|
66
|
+
this.poke();
|
|
67
|
+
}
|
|
68
|
+
else if (event.type === "permission_resolved")
|
|
69
|
+
this.others.delete(event.requestId);
|
|
70
|
+
}
|
|
71
|
+
/** 对一次弹出条件(定时器、视图打开、回合结束)。 */
|
|
72
|
+
poke() {
|
|
73
|
+
for (const entry of [...this.docked])
|
|
74
|
+
if (this.others.size > 0 || this.host.ready(entry.taskId))
|
|
75
|
+
this.release(entry);
|
|
76
|
+
}
|
|
77
|
+
/** 换会话:旧会话的未答复请求不再有 `permission_resolved` 到这里。 */
|
|
78
|
+
reset() {
|
|
79
|
+
this.others.clear();
|
|
80
|
+
}
|
|
81
|
+
/** 退出:停靠的请求按 undefined 交回链(链按拒绝处理)。 */
|
|
82
|
+
dispose() {
|
|
83
|
+
for (const entry of [...this.docked])
|
|
84
|
+
entry.onAbort();
|
|
85
|
+
this.others.clear();
|
|
86
|
+
}
|
|
87
|
+
release(entry) {
|
|
88
|
+
this.remove(entry);
|
|
89
|
+
entry.resolve(this.inner.ask(entry.request, entry.signal));
|
|
90
|
+
}
|
|
91
|
+
remove(entry) {
|
|
92
|
+
const at = this.docked.indexOf(entry);
|
|
93
|
+
if (at < 0)
|
|
94
|
+
return;
|
|
95
|
+
this.docked.splice(at, 1);
|
|
96
|
+
entry.signal.removeEventListener("abort", entry.onAbort);
|
|
97
|
+
if (this.docked.length === 0)
|
|
98
|
+
this.stopPolling();
|
|
99
|
+
this.host.changed();
|
|
100
|
+
}
|
|
101
|
+
startPolling() {
|
|
102
|
+
if (this.timer !== undefined)
|
|
103
|
+
return;
|
|
104
|
+
this.timer = setInterval(() => this.poke(), this.host.pollMs ?? DOCK_POLL_MS);
|
|
105
|
+
this.timer.unref?.();
|
|
106
|
+
}
|
|
107
|
+
stopPolling() {
|
|
108
|
+
if (this.timer !== undefined)
|
|
109
|
+
clearInterval(this.timer);
|
|
110
|
+
this.timer = undefined;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 交互界面的审批接线,从 interactive-mode.ts 拆出(保持装配文件 ≤ 600 行)。[W7-C]
|
|
3
|
+
*
|
|
4
|
+
* broker 由外到内:首次运行合并(approval-merge.ts)→ 后台任务停靠(approval-dock.ts)→ 底部对话框
|
|
5
|
+
* (approval-dialog.ts)。对话框打开 / 关闭时禁用编辑器提交、标记运行中的工具「等待确认」、运行提示行换
|
|
6
|
+
* 「等待确认」。
|
|
7
|
+
*/
|
|
8
|
+
import type { AgentSession } from "../../agent/types.js";
|
|
9
|
+
import type { ApprovalBroker } from "../../permissions/types.js";
|
|
10
|
+
import type { Component, Editor, Keybindings, OverlayHandle, TUI, Theme } from "../../tui.js";
|
|
11
|
+
import type { AgentUi } from "./agent-ui.js";
|
|
12
|
+
import { ApprovalDock } from "./approval-dock.js";
|
|
13
|
+
import type { NoticeLevel } from "./message-view.js";
|
|
14
|
+
import type { RunIndicator } from "./run-indicator.js";
|
|
15
|
+
import type { ToolTracker } from "./tool-view.js";
|
|
16
|
+
export interface OverlayHooks {
|
|
17
|
+
showOverlay(component: Component): OverlayHandle;
|
|
18
|
+
onOpen(): void;
|
|
19
|
+
onClose(): void;
|
|
20
|
+
}
|
|
21
|
+
export declare function approvalOverlayHooks(deps: {
|
|
22
|
+
tui: TUI;
|
|
23
|
+
editor: Editor;
|
|
24
|
+
tools(): ToolTracker;
|
|
25
|
+
indicator: RunIndicator;
|
|
26
|
+
}): OverlayHooks;
|
|
27
|
+
export interface ApprovalUiDeps {
|
|
28
|
+
theme: Theme;
|
|
29
|
+
keys: Keybindings;
|
|
30
|
+
tui: TUI;
|
|
31
|
+
editor: Editor;
|
|
32
|
+
indicator: RunIndicator;
|
|
33
|
+
agentUi: AgentUi;
|
|
34
|
+
hooks: OverlayHooks;
|
|
35
|
+
session(): AgentSession;
|
|
36
|
+
notice(level: NoticeLevel, text: string): void;
|
|
37
|
+
render(): void;
|
|
38
|
+
}
|
|
39
|
+
/** 组装界面 broker,并把停靠接到 Agent 栏与运行提示行。 */
|
|
40
|
+
export declare function createApprovalUi(deps: ApprovalUiDeps): {
|
|
41
|
+
broker: ApprovalBroker;
|
|
42
|
+
dock: ApprovalDock;
|
|
43
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 交互界面的审批接线,从 interactive-mode.ts 拆出(保持装配文件 ≤ 600 行)。[W7-C]
|
|
3
|
+
*
|
|
4
|
+
* broker 由外到内:首次运行合并(approval-merge.ts)→ 后台任务停靠(approval-dock.ts)→ 底部对话框
|
|
5
|
+
* (approval-dialog.ts)。对话框打开 / 关闭时禁用编辑器提交、标记运行中的工具「等待确认」、运行提示行换
|
|
6
|
+
* 「等待确认」。
|
|
7
|
+
*/
|
|
8
|
+
import { taskRegistryView } from "../../agent/subagent-registry.js";
|
|
9
|
+
import { msg } from "../../i18n/index.js";
|
|
10
|
+
import { ApprovalDialogBroker, approvalOutcomeText } from "./approval-dialog.js";
|
|
11
|
+
import { ApprovalDock } from "./approval-dock.js";
|
|
12
|
+
import { mergingBroker } from "./approval-merge.js";
|
|
13
|
+
export function approvalOverlayHooks(deps) {
|
|
14
|
+
const { tui, editor, indicator } = deps;
|
|
15
|
+
return {
|
|
16
|
+
showOverlay: (component) => tui.showOverlay(component, { anchor: "bottom" }),
|
|
17
|
+
onOpen: () => {
|
|
18
|
+
editor.disableSubmit = true;
|
|
19
|
+
deps.tools().setAwaiting(true);
|
|
20
|
+
indicator.setApproval(true);
|
|
21
|
+
},
|
|
22
|
+
onClose: () => {
|
|
23
|
+
editor.disableSubmit = false;
|
|
24
|
+
deps.tools().setAwaiting(false);
|
|
25
|
+
indicator.setApproval(false);
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** 组装界面 broker,并把停靠接到 Agent 栏与运行提示行。 */
|
|
30
|
+
export function createApprovalUi(deps) {
|
|
31
|
+
const { theme, keys, tui, editor, indicator, agentUi, hooks, notice } = deps;
|
|
32
|
+
const taskInfo = (taskId) => taskRegistryView(deps.session().state.sessionId)?.get(taskId);
|
|
33
|
+
const taskAgent = (taskId) => taskInfo(taskId)?.agent;
|
|
34
|
+
const dialog = new ApprovalDialogBroker({
|
|
35
|
+
theme,
|
|
36
|
+
keybindings: keys,
|
|
37
|
+
cwd: deps.session().state.cwd,
|
|
38
|
+
permissionMode: () => deps.session().state.permissionMode,
|
|
39
|
+
taskAgent,
|
|
40
|
+
externalRunner: (agent) => agentUi.merge.externalRunner(agent),
|
|
41
|
+
...hooks,
|
|
42
|
+
report: (request, outcome) => {
|
|
43
|
+
if (outcome === "deny" || outcome === "cancelled") {
|
|
44
|
+
const text = approvalOutcomeText(request, outcome, taskAgent);
|
|
45
|
+
notice(outcome === "deny" ? "info" : "warn", text);
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
const dock = new ApprovalDock(dialog, {
|
|
50
|
+
dockable: (taskId) => {
|
|
51
|
+
const info = taskInfo(taskId);
|
|
52
|
+
return info?.status === "running" && info.background === true;
|
|
53
|
+
},
|
|
54
|
+
ready: (taskId) => agentUi.viewing === taskId || (!tui.hasOverlay && !indicator.busy && editor.isEmpty()),
|
|
55
|
+
changed: () => {
|
|
56
|
+
indicator.sync();
|
|
57
|
+
deps.render();
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
agentUi.dock = dock;
|
|
61
|
+
indicator.docked = () => dock.size > 0;
|
|
62
|
+
const broker = mergingBroker(dock, agentUi.merge, (request) => notice("info", msg().interactive.app.followed(approvalOutcomeText(request, "allow", taskAgent))));
|
|
63
|
+
return { broker, dock };
|
|
64
|
+
}
|
|
@@ -72,7 +72,10 @@ export const PANEL_COMMANDS = {
|
|
|
72
72
|
await ui.agentBar();
|
|
73
73
|
return true;
|
|
74
74
|
}
|
|
75
|
-
if (parts.length === 1 &&
|
|
75
|
+
if (parts.length === 1 &&
|
|
76
|
+
parts[0] !== "stop" &&
|
|
77
|
+
parts[0] !== "bg" &&
|
|
78
|
+
ui.agentView !== undefined) {
|
|
76
79
|
await ui.agentView(parts[0]);
|
|
77
80
|
return true;
|
|
78
81
|
}
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* - W5-I:`entry_appended` 里 `reason: "image_budget"` 的 `context_edit`(一次降级会连写多条,每条一个消息)
|
|
5
5
|
* 合并成一条「已省略 N 张早期图片以符合请求上限」,N 按占位文本数;
|
|
6
|
-
* - W5-H1:`compaction_end.error` 的「compaction did not shrink
|
|
6
|
+
* - W5-H1:`compaction_end.error` 的「compaction did not shrink」换成中文说明(其余错误原样);
|
|
7
|
+
* - W7-C:`subagent_background`(超时自动 / 宿主转后台)一行提示。
|
|
7
8
|
*/
|
|
8
9
|
import type { SessionEvent } from "../../agent/types.js";
|
|
9
10
|
/** `compaction_end.error` 的显示文本。 */
|
|
@@ -30,3 +31,7 @@ export declare function modelFallbackText(event: Extract<SessionEvent, {
|
|
|
30
31
|
export declare function backgroundJobText(event: Extract<SessionEvent, {
|
|
31
32
|
type: "background_job";
|
|
32
33
|
}>): string;
|
|
34
|
+
/** [W7-C] `subagent_background`:超时自动 / 宿主转后台的一行提示(人按 Ctrl+B 的已有底部提示)。 */
|
|
35
|
+
export declare function subagentBackgroundText(event: Extract<SessionEvent, {
|
|
36
|
+
type: "subagent_background";
|
|
37
|
+
}>, agent: string): string;
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* - W5-I:`entry_appended` 里 `reason: "image_budget"` 的 `context_edit`(一次降级会连写多条,每条一个消息)
|
|
5
5
|
* 合并成一条「已省略 N 张早期图片以符合请求上限」,N 按占位文本数;
|
|
6
|
-
* - W5-H1:`compaction_end.error` 的「compaction did not shrink
|
|
6
|
+
* - W5-H1:`compaction_end.error` 的「compaction did not shrink」换成中文说明(其余错误原样);
|
|
7
|
+
* - W7-C:`subagent_background`(超时自动 / 宿主转后台)一行提示。
|
|
7
8
|
*/
|
|
8
9
|
import { msg } from "../../i18n/index.js";
|
|
9
10
|
import { IMAGE_OMITTED_FOR_BUDGET, IMAGE_OMITTED_TOO_LARGE, } from "../../compaction/image-budget.js";
|
|
@@ -79,3 +80,8 @@ export function backgroundJobText(event) {
|
|
|
79
80
|
return m.backgroundStopped(event.jobId, short);
|
|
80
81
|
}
|
|
81
82
|
}
|
|
83
|
+
/** [W7-C] `subagent_background`:超时自动 / 宿主转后台的一行提示(人按 Ctrl+B 的已有底部提示)。 */
|
|
84
|
+
export function subagentBackgroundText(event, agent) {
|
|
85
|
+
const m = msg().agents.background;
|
|
86
|
+
return event.reason === "timeout" ? m.timeout(event.taskId, agent) : m.host(event.taskId, agent);
|
|
87
|
+
}
|
|
@@ -16,8 +16,10 @@
|
|
|
16
16
|
* 切换(只影响本会话)。
|
|
17
17
|
* - 第五波(W5-U,agent-ui.ts):计划审批框、子 Agent 折叠视图、`/tasks` `/agents` `/paste` 与 Ctrl+V、审批来源
|
|
18
18
|
* 标注与外部 Agent 首次运行的合并确认(approval-merge.ts)、harness 提示。
|
|
19
|
-
* -
|
|
20
|
-
*
|
|
19
|
+
* - [W7-C] 审批接线拆到 approval-ui.ts(合并 → 后台任务停靠 → 对话框);`Ctrl+B` 转后台与 Esc 提示的数据来自
|
|
20
|
+
* task-background.ts。
|
|
21
|
+
* - 启动头按 `ui.quietStartup`(startup-header.ts):normal「AMA」字符画 + 信息列(启动时点亮扫描一次,
|
|
22
|
+
* startup-logo.ts;窄屏 / `ui.logo: off` / `ui.compact` 不画字符画),header 一行,silent 不输出。
|
|
21
23
|
*/
|
|
22
24
|
import type { AgentSession } from "../../agent/types.js";
|
|
23
25
|
import type { ModeContext } from "../../cli/deps.js";
|
|
@@ -16,13 +16,14 @@
|
|
|
16
16
|
* 切换(只影响本会话)。
|
|
17
17
|
* - 第五波(W5-U,agent-ui.ts):计划审批框、子 Agent 折叠视图、`/tasks` `/agents` `/paste` 与 Ctrl+V、审批来源
|
|
18
18
|
* 标注与外部 Agent 首次运行的合并确认(approval-merge.ts)、harness 提示。
|
|
19
|
-
* -
|
|
20
|
-
*
|
|
19
|
+
* - [W7-C] 审批接线拆到 approval-ui.ts(合并 → 后台任务停靠 → 对话框);`Ctrl+B` 转后台与 Esc 提示的数据来自
|
|
20
|
+
* task-background.ts。
|
|
21
|
+
* - 启动头按 `ui.quietStartup`(startup-header.ts):normal「AMA」字符画 + 信息列(启动时点亮扫描一次,
|
|
22
|
+
* startup-logo.ts;窄屏 / `ui.logo: off` / `ui.compact` 不画字符画),header 一行,silent 不输出。
|
|
21
23
|
*/
|
|
22
24
|
import { promptImages, sessionModel } from "../image-input.js";
|
|
23
25
|
import { join } from "node:path";
|
|
24
26
|
import { AgentSessionImpl } from "../../agent/session.js";
|
|
25
|
-
import { taskRegistryView } from "../../agent/subagent-registry.js";
|
|
26
27
|
import { currentSession, switchSession } from "../../cli/compose-session.js";
|
|
27
28
|
import { startupInfo, startupScreenLevel, tildePath } from "../../cli/startup-screen.js";
|
|
28
29
|
import { AmaError, isAmaError } from "../../errors.js";
|
|
@@ -31,8 +32,7 @@ import { Container, Editor, Loader, Spacer, TUI, Text, createTheme, detectCapabi
|
|
|
31
32
|
import { BYPASS_MODE, createBypassGate } from "../../permissions/bypass.js";
|
|
32
33
|
import { onTerminationSignals } from "../shared.js";
|
|
33
34
|
import { AgentUi, agentCommandHooks } from "./agent-ui.js";
|
|
34
|
-
import {
|
|
35
|
-
import { mergingBroker } from "./approval-merge.js";
|
|
35
|
+
import { approvalOverlayHooks, createApprovalUi } from "./approval-ui.js";
|
|
36
36
|
import { ALL_COMMANDS, runInteractiveCommand } from "./commands.js";
|
|
37
37
|
import { memoryPanelFor } from "./memory-panel.js";
|
|
38
38
|
import { InteractiveCompletion } from "./completion.js";
|
|
@@ -45,12 +45,14 @@ import { modelPickerFor } from "./model-picker.js";
|
|
|
45
45
|
import { createRewindFlow } from "./rewind-flow.js";
|
|
46
46
|
import { openTraceView } from "./trace-view.js";
|
|
47
47
|
import { StartupHeader } from "./startup-header.js";
|
|
48
|
+
import { playStartupAnimation } from "./startup-logo.js";
|
|
48
49
|
import { QueueView, RunIndicator } from "./run-indicator.js";
|
|
49
50
|
import { StatusArea, statusLineSlash } from "./status-area.js";
|
|
50
51
|
import { createSessionEventHandler } from "./session-events.js";
|
|
51
52
|
import { SubagentTracker } from "./subagent-view.js";
|
|
52
53
|
import { loadKeys, processTerminal } from "./terminal-setup.js";
|
|
53
54
|
import { ToolTracker } from "./tool-view.js";
|
|
55
|
+
import { backgroundTasks, blockingTasks, keyLabel, runningBackground } from "./task-background.js";
|
|
54
56
|
const HINT_MS = 2500;
|
|
55
57
|
export function runInteractiveMode(runtime, context, options = {}) {
|
|
56
58
|
const terminal = options.terminal ?? processTerminal();
|
|
@@ -131,6 +133,7 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
131
133
|
tui.addChild(hint);
|
|
132
134
|
tui.addChild(area.rate);
|
|
133
135
|
tui.addChild(status);
|
|
136
|
+
tui.addChild(area.quota);
|
|
134
137
|
// ---- 小工具 ---------------------------------------------------------------
|
|
135
138
|
let finished = false;
|
|
136
139
|
let hintTimer;
|
|
@@ -154,7 +157,11 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
154
157
|
}
|
|
155
158
|
render();
|
|
156
159
|
};
|
|
157
|
-
const
|
|
160
|
+
const backgroundKey = keyLabel(keys, "app.tasks.background");
|
|
161
|
+
const indicator = new RunIndicator({
|
|
162
|
+
...{ theme, loader, slot: loaderSlot, tools, render },
|
|
163
|
+
...(backgroundKey !== undefined ? { backgroundKey } : {}),
|
|
164
|
+
});
|
|
158
165
|
const setQueue = (steering, followUp) => {
|
|
159
166
|
status.setQueue(steering.length, followUp.length);
|
|
160
167
|
queueView.setQueue(steering, followUp);
|
|
@@ -177,7 +184,7 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
177
184
|
const startupLevel = startupScreenLevel(runtime);
|
|
178
185
|
const home = env["HOME"] ?? env["USERPROFILE"];
|
|
179
186
|
const info = startupInfo(runtime, home);
|
|
180
|
-
const header = (level) => new StartupHeader(info, { theme, level,
|
|
187
|
+
const header = (level) => new StartupHeader(info, { theme, level, logo: ui.logo, compact: ui.compact });
|
|
181
188
|
/** 清空消息区(切换会话、/tree):只留一行头。 */
|
|
182
189
|
const resetView = () => {
|
|
183
190
|
view.reset();
|
|
@@ -358,21 +365,12 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
358
365
|
exit: (code) => exit(code),
|
|
359
366
|
onInterrupted: (empty) => void rewind.afterInterrupt(empty),
|
|
360
367
|
agents: () => agentUi.keys,
|
|
368
|
+
downGlyph: theme.glyphs.arrowDown,
|
|
369
|
+
backgroundTasks: () => backgroundTasks(session),
|
|
370
|
+
runningBackground: () => runningBackground(session),
|
|
361
371
|
}));
|
|
362
372
|
// ---- 晚绑定 ---------------------------------------------------------------
|
|
363
|
-
const overlayHooks = {
|
|
364
|
-
showOverlay: (component) => tui.showOverlay(component, { anchor: "bottom" }),
|
|
365
|
-
onOpen: () => {
|
|
366
|
-
editor.disableSubmit = true;
|
|
367
|
-
tools.setAwaiting(true);
|
|
368
|
-
indicator.setApproval(true);
|
|
369
|
-
},
|
|
370
|
-
onClose: () => {
|
|
371
|
-
editor.disableSubmit = false;
|
|
372
|
-
tools.setAwaiting(false);
|
|
373
|
-
indicator.setApproval(false);
|
|
374
|
-
},
|
|
375
|
-
};
|
|
373
|
+
const overlayHooks = approvalOverlayHooks({ tui, editor, tools: () => tools, indicator });
|
|
376
374
|
const agentUi = new AgentUi({
|
|
377
375
|
theme,
|
|
378
376
|
tui,
|
|
@@ -395,28 +393,20 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
395
393
|
...(options.clipboard !== undefined ? { clipboard: options.clipboard } : {}),
|
|
396
394
|
area,
|
|
397
395
|
});
|
|
398
|
-
|
|
399
|
-
const broker =
|
|
400
|
-
theme,
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
externalRunner: (agent) => agentUi.merge.externalRunner(agent),
|
|
406
|
-
...overlayHooks,
|
|
407
|
-
report: (request, outcome) => {
|
|
408
|
-
if (outcome === "deny" || outcome === "cancelled") {
|
|
409
|
-
const text = approvalOutcomeText(request, outcome, taskAgent);
|
|
410
|
-
notice(outcome === "deny" ? "info" : "warn", text);
|
|
411
|
-
}
|
|
412
|
-
},
|
|
413
|
-
}), agentUi.merge, (request) => notice("info", msg().interactive.app.followed(approvalOutcomeText(request, "allow", taskAgent))));
|
|
396
|
+
indicator.agents = () => agentUi.reachable;
|
|
397
|
+
const { broker, dock } = createApprovalUi({
|
|
398
|
+
...{ theme, keys, tui, editor, indicator, agentUi, notice, render },
|
|
399
|
+
hooks: overlayHooks,
|
|
400
|
+
session: () => session,
|
|
401
|
+
});
|
|
402
|
+
indicator.background = () => blockingTasks(session).length > 0;
|
|
414
403
|
// ---- 启动与退出 -----------------------------------------------------------
|
|
415
404
|
let offSignals = () => undefined;
|
|
416
405
|
function exit(code) {
|
|
417
406
|
if (finished)
|
|
418
407
|
return;
|
|
419
408
|
finished = true;
|
|
409
|
+
logoAnimation?.finish();
|
|
420
410
|
if (hintTimer !== undefined)
|
|
421
411
|
clearTimeout(hintTimer);
|
|
422
412
|
loader.stop();
|
|
@@ -427,6 +417,7 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
427
417
|
tui.addChild(view);
|
|
428
418
|
offSignals();
|
|
429
419
|
agentUi.detach();
|
|
420
|
+
dock.dispose();
|
|
430
421
|
runtime.approvals.setUiBroker(undefined);
|
|
431
422
|
runtime.notifier.set(undefined);
|
|
432
423
|
tui.stop();
|
|
@@ -439,8 +430,10 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
439
430
|
resolveExit(code);
|
|
440
431
|
});
|
|
441
432
|
}
|
|
442
|
-
|
|
443
|
-
|
|
433
|
+
const startHeader = startupLevel === "silent" ? undefined : header(startupLevel);
|
|
434
|
+
let logoAnimation;
|
|
435
|
+
if (startHeader !== undefined)
|
|
436
|
+
view.add(startHeader);
|
|
444
437
|
for (const warning of startupWarnings)
|
|
445
438
|
view.addNotice("warn", warning);
|
|
446
439
|
replay();
|
|
@@ -460,6 +453,8 @@ export function runInteractiveMode(runtime, context, options = {}) {
|
|
|
460
453
|
agentUi.attach(session);
|
|
461
454
|
offSignals = onTerminationSignals((code) => exit(code));
|
|
462
455
|
tui.setFocus(editor);
|
|
456
|
+
const gate = { animation: ui.animation, host: info.host !== undefined, env, ...context };
|
|
457
|
+
logoAnimation = playStartupAnimation(tui, startHeader, theme, gate);
|
|
463
458
|
options.onReady?.({
|
|
464
459
|
tui,
|
|
465
460
|
editor,
|
|
@@ -10,20 +10,31 @@
|
|
|
10
10
|
* [RW-C] 空闲时双击 Esc(`app.rewind`,double-esc.ts):输入框为空打开回滚列表,有字则清空并存进
|
|
11
11
|
* 输入历史;运行中 Esc 仍为中断,中断后交给 `onInterrupted`(中断即撤回)。
|
|
12
12
|
*
|
|
13
|
-
* [W6-A] Agent 栏(agent-ui.ts):栏聚焦时它先收键(Esc
|
|
14
|
-
* `app.agents.focus`(缺省
|
|
13
|
+
* [W6-A] Agent 栏(agent-ui.ts):栏聚焦时它先收键(Esc 是「返回」,不中断、不回滚);补全没开、没在浏览
|
|
14
|
+
* 历史时 `app.agents.focus`(缺省 `↓`)先问它要不要进栏,不要就照常交给编辑器(下移、历史下一条)。
|
|
15
|
+
* [W7-A] 落空给一行提示:输入有字(每段草稿一次,光标在末行时)、栏关闭、没有任务。
|
|
16
|
+
* [W7-C] `app.tasks.background`(缺省 `Ctrl+B`):有阻塞中的前台任务时全部转后台(不看输入框),底部提示
|
|
17
|
+
* 「已转后台:t2 …」;没有时落回编辑器(光标左移),不吞键。Esc 中断只连带前台任务,仍在运行的后台任务
|
|
18
|
+
* 写进中断提示。
|
|
15
19
|
*/
|
|
16
20
|
import type { AgentSession } from "../../agent/types.js";
|
|
17
21
|
import type { PermissionMode } from "../../permissions/types.js";
|
|
18
22
|
import type { Editor, Keybindings } from "../../tui.js";
|
|
19
23
|
import type { StatusBar } from "./status-bar.js";
|
|
20
24
|
import type { ToolTracker } from "./tool-view.js";
|
|
25
|
+
/** [W7-A] 进栏落空提示的停留时间。 */
|
|
26
|
+
export declare const FOCUS_HINT_MS = 3000;
|
|
27
|
+
/**
|
|
28
|
+
* [W7-A] 进栏键的结果:true 已进栏;false 不管(交给编辑器,不提示);其余是落空原因——
|
|
29
|
+
* `busy-input` 输入框有字、`disabled` `ui.agentBar: "off"`(有任务时)、`empty` 没有任务。
|
|
30
|
+
*/
|
|
31
|
+
export type AgentFocusResult = boolean | "busy-input" | "disabled" | "empty";
|
|
21
32
|
/** [W6-A] Agent 栏的按键入口。 */
|
|
22
33
|
export interface AgentKeys {
|
|
23
34
|
/** 栏聚焦时先消费按键;返回 true 已处理。 */
|
|
24
35
|
handleKey(data: string): boolean;
|
|
25
|
-
/** `app.agents.focus
|
|
26
|
-
focus(data: string):
|
|
36
|
+
/** `app.agents.focus`:输入为空且有任务时进栏返回 true,否则返回落空原因(按键仍交给编辑器)。 */
|
|
37
|
+
focus(data: string): AgentFocusResult;
|
|
27
38
|
}
|
|
28
39
|
export interface KeyDispatchDeps {
|
|
29
40
|
keys: Keybindings;
|
|
@@ -58,6 +69,12 @@ export interface KeyDispatchDeps {
|
|
|
58
69
|
doubleEscMs?: number;
|
|
59
70
|
/** [W6-A] Agent 栏(晚绑定)。 */
|
|
60
71
|
agents?(): AgentKeys | undefined;
|
|
72
|
+
/** [W7-A] 提示里的 `↓` 字形(ASCII 主题 `v`;缺省 `↓`)。 */
|
|
73
|
+
downGlyph?: string;
|
|
74
|
+
/** [W7-C] 有阻塞中的前台任务时全部转后台,返回转了的 taskId;没有返回空(按键交给编辑器)。 */
|
|
75
|
+
backgroundTasks?(): string[];
|
|
76
|
+
/** [W7-C] 仍在运行的后台任务(Esc 中断的提示里写明不受影响)。 */
|
|
77
|
+
runningBackground?(): string[];
|
|
61
78
|
}
|
|
62
79
|
/** 返回输入监听器:已处理返回 true,交给编辑器返回 false。 */
|
|
63
80
|
export declare function createKeyDispatch(deps: KeyDispatchDeps): (data: string) => boolean;
|
|
@@ -10,20 +10,31 @@
|
|
|
10
10
|
* [RW-C] 空闲时双击 Esc(`app.rewind`,double-esc.ts):输入框为空打开回滚列表,有字则清空并存进
|
|
11
11
|
* 输入历史;运行中 Esc 仍为中断,中断后交给 `onInterrupted`(中断即撤回)。
|
|
12
12
|
*
|
|
13
|
-
* [W6-A] Agent 栏(agent-ui.ts):栏聚焦时它先收键(Esc
|
|
14
|
-
* `app.agents.focus`(缺省
|
|
13
|
+
* [W6-A] Agent 栏(agent-ui.ts):栏聚焦时它先收键(Esc 是「返回」,不中断、不回滚);补全没开、没在浏览
|
|
14
|
+
* 历史时 `app.agents.focus`(缺省 `↓`)先问它要不要进栏,不要就照常交给编辑器(下移、历史下一条)。
|
|
15
|
+
* [W7-A] 落空给一行提示:输入有字(每段草稿一次,光标在末行时)、栏关闭、没有任务。
|
|
16
|
+
* [W7-C] `app.tasks.background`(缺省 `Ctrl+B`):有阻塞中的前台任务时全部转后台(不看输入框),底部提示
|
|
17
|
+
* 「已转后台:t2 …」;没有时落回编辑器(光标左移),不吞键。Esc 中断只连带前台任务,仍在运行的后台任务
|
|
18
|
+
* 写进中断提示。
|
|
15
19
|
*/
|
|
16
20
|
import { msg } from "../../i18n/index.js";
|
|
17
21
|
import { ExitCode } from "../../cli/exit-codes.js";
|
|
18
22
|
import { nextCycleMode, permissionModeLabel } from "../../permissions/modes.js";
|
|
19
23
|
import { DOUBLE_ESC_HINT_MS, DoubleEscape } from "./double-esc.js";
|
|
24
|
+
import { backgroundedText } from "./task-background.js";
|
|
20
25
|
import { statusLineText } from "./status-area.js";
|
|
21
26
|
const DOUBLE_CTRL_C_MS = 1500;
|
|
27
|
+
/** [W7-A] 进栏落空提示的停留时间。 */
|
|
28
|
+
export const FOCUS_HINT_MS = 3000;
|
|
22
29
|
/** 返回输入监听器:已处理返回 true,交给编辑器返回 false。 */
|
|
23
30
|
export function createKeyDispatch(deps) {
|
|
24
31
|
const { keys, editor, tools, status } = deps;
|
|
25
32
|
let ctrlCArmedAt = Number.NEGATIVE_INFINITY;
|
|
26
33
|
const esc = new DoubleEscape(deps.doubleEscMs);
|
|
34
|
+
/** 本段草稿已给过「输入框有字」提示(输入清空后重置)。 */
|
|
35
|
+
let busyInputHinted = false;
|
|
36
|
+
/** 底部正显示着进栏落空提示(进栏后撤掉)。 */
|
|
37
|
+
let focusHintShown = false;
|
|
27
38
|
const doubleEscape = () => {
|
|
28
39
|
switch (esc.press(deps.now(), editor.isEmpty())) {
|
|
29
40
|
case "arm-rewind":
|
|
@@ -88,9 +99,43 @@ export function createKeyDispatch(deps) {
|
|
|
88
99
|
else
|
|
89
100
|
void answer.then(settle);
|
|
90
101
|
};
|
|
102
|
+
/** 光标在最后一行(`↓` 不再下移,只会到行尾)。 */
|
|
103
|
+
const atLastLine = () => editor.cursor.line >= editor.getText().split("\n").length - 1;
|
|
104
|
+
/** `app.agents.focus`:进栏返回 true;落空给提示后返回 false(按键照常交给编辑器)。 */
|
|
105
|
+
const focusAgents = (agents, data) => {
|
|
106
|
+
if (editor.isCompletionOpen || editor.isBrowsingHistory)
|
|
107
|
+
return false;
|
|
108
|
+
const result = agents.focus(data);
|
|
109
|
+
if (result === true) {
|
|
110
|
+
// 进栏了:撤掉之前的落空提示
|
|
111
|
+
if (focusHintShown)
|
|
112
|
+
deps.showHint("");
|
|
113
|
+
focusHintShown = false;
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
const m = msg().agents;
|
|
117
|
+
let text;
|
|
118
|
+
if (result === "busy-input") {
|
|
119
|
+
if (busyInputHinted || !atLastLine())
|
|
120
|
+
return false;
|
|
121
|
+
busyInputHinted = true;
|
|
122
|
+
text = m.focus.busyInput(deps.downGlyph ?? "↓");
|
|
123
|
+
}
|
|
124
|
+
else if (result === "disabled")
|
|
125
|
+
text = m.focus.disabled;
|
|
126
|
+
else if (result === "empty")
|
|
127
|
+
text = m.bar.empty;
|
|
128
|
+
if (text !== undefined) {
|
|
129
|
+
deps.showHint(text, FOCUS_HINT_MS);
|
|
130
|
+
focusHintShown = true;
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
};
|
|
91
134
|
return (data) => {
|
|
92
135
|
if (deps.inactive())
|
|
93
136
|
return false;
|
|
137
|
+
if (editor.isEmpty())
|
|
138
|
+
busyInputHinted = false;
|
|
94
139
|
const is = (action) => keys.matches(data, action);
|
|
95
140
|
if (!is("app.rewind"))
|
|
96
141
|
esc.reset();
|
|
@@ -99,12 +144,16 @@ export function createKeyDispatch(deps) {
|
|
|
99
144
|
esc.reset();
|
|
100
145
|
return true;
|
|
101
146
|
}
|
|
102
|
-
if (agents !== undefined &&
|
|
103
|
-
is("app.agents.focus") &&
|
|
104
|
-
editor.isEmpty() &&
|
|
105
|
-
!editor.isCompletionOpen &&
|
|
106
|
-
agents.focus(data))
|
|
147
|
+
if (agents !== undefined && is("app.agents.focus") && focusAgents(agents, data))
|
|
107
148
|
return true;
|
|
149
|
+
if (is("app.tasks.background") && deps.backgroundTasks !== undefined) {
|
|
150
|
+
const moved = deps.backgroundTasks();
|
|
151
|
+
if (moved.length > 0) {
|
|
152
|
+
esc.reset();
|
|
153
|
+
deps.showHint(backgroundedText(moved), FOCUS_HINT_MS);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
108
157
|
if (is("app.clear")) {
|
|
109
158
|
if (!editor.isEmpty()) {
|
|
110
159
|
editor.clear();
|
|
@@ -124,7 +173,10 @@ export function createKeyDispatch(deps) {
|
|
|
124
173
|
if (is("app.interrupt") && !editor.isCompletionOpen && deps.busy()) {
|
|
125
174
|
esc.reset();
|
|
126
175
|
interrupt();
|
|
127
|
-
deps.
|
|
176
|
+
const left = deps.runningBackground?.() ?? [];
|
|
177
|
+
deps.showHint(left.length > 0
|
|
178
|
+
? msg().agents.background.interrupted(left.join(", "))
|
|
179
|
+
: msg().interactive.keys.interrupted);
|
|
128
180
|
deps.onInterrupted?.(editor.isEmpty());
|
|
129
181
|
return true;
|
|
130
182
|
}
|
|
@@ -194,6 +194,11 @@ export async function runLineMode(runtime, context, options = {}) {
|
|
|
194
194
|
continue;
|
|
195
195
|
}
|
|
196
196
|
if (busy) {
|
|
197
|
+
// [W7-C] 运行中 `/tasks bg [id]` 照样是命令(转后台阻塞中的前台任务),不当插话
|
|
198
|
+
if (/^\/tasks\s+bg(\s+\S+)?\s*$/.test(action.text.trim())) {
|
|
199
|
+
void handle(action.text.trim());
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
197
202
|
if (action.text.trim() !== "") {
|
|
198
203
|
void session.steer(action.text).catch(() => undefined);
|
|
199
204
|
out(`${msg().interactive.line.steer(action.text)}\n`);
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
* 助手在流式输出 → 末块是思考 `思考中`,否则 `回复中 · ↓≈1.2k`(本条输出的估算 token,4 字符 ≈ 1)
|
|
10
10
|
* 其余(等首个 token)→ `思考中`
|
|
11
11
|
* 运行或压缩时 Loader 才挂进槽位;附加项 `Esc 中断`。
|
|
12
|
+
* [W7-A] 有该显示的子 Agent 任务时再带可丢弃的 `↓ Agent 栏`(窄屏先丢它);任务随时出现 / 结束,
|
|
13
|
+
* 每帧对一次,变了才换动词。
|
|
14
|
+
* [W7-C] 有阻塞中的前台任务时可丢弃项前面加 `Ctrl+B 转后台`;有停靠的审批时 `↓ Agent 栏` 换成
|
|
15
|
+
* `↓ 处理审批`。顺序 `Esc 中断 · Ctrl+B 转后台 · ↓ Agent 栏`,窄屏从后往前丢。
|
|
12
16
|
*
|
|
13
17
|
* 排队消息:缩进 2 列(挂在当前回合下),`↳ 插话 / 之后 文本` 整体 muted、标签 dim;超过 3 条首行
|
|
14
18
|
* `… 另 N 条`;末行 `Alt+↑ 取回 · Esc 回填并中断`。
|
|
@@ -24,6 +28,8 @@ export interface RunIndicatorDeps {
|
|
|
24
28
|
slot: Container;
|
|
25
29
|
tools: ToolTracker;
|
|
26
30
|
render(): void;
|
|
31
|
+
/** [W7-C] `app.tasks.background` 的按键标签(`Ctrl+B`);没有绑定时不提示。 */
|
|
32
|
+
backgroundKey?: string;
|
|
27
33
|
}
|
|
28
34
|
export declare class RunIndicator {
|
|
29
35
|
private readonly deps;
|
|
@@ -32,7 +38,16 @@ export declare class RunIndicator {
|
|
|
32
38
|
private approval;
|
|
33
39
|
private retry;
|
|
34
40
|
private stream;
|
|
41
|
+
/** [W7-A] 有该显示的子 Agent 任务(interactive-mode 在 AgentUi 建好后接上)。 */
|
|
42
|
+
agents: () => boolean;
|
|
43
|
+
/** [W7-C] 有阻塞中的前台任务(Ctrl+B 可转后台)。 */
|
|
44
|
+
background: () => boolean;
|
|
45
|
+
/** [W7-C] 有停靠在 Agent 栏的审批。 */
|
|
46
|
+
docked: () => boolean;
|
|
47
|
+
private hintKey;
|
|
35
48
|
constructor(deps: RunIndicatorDeps);
|
|
49
|
+
/** 可丢弃的按键提示(按丢弃的逆序排列)。 */
|
|
50
|
+
private optionalHints;
|
|
36
51
|
get busy(): boolean;
|
|
37
52
|
setApproval(open: boolean): void;
|
|
38
53
|
/** 会话切换后清零。 */
|