@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
|
@@ -25,6 +25,7 @@ export const en = {
|
|
|
25
25
|
"ui.showThinking": "Thinking display",
|
|
26
26
|
"ui.compact": "Compact layout",
|
|
27
27
|
"ui.animation": "Spinner animation",
|
|
28
|
+
"ui.logo": "Startup logo",
|
|
28
29
|
"ui.statusLine": "Status line",
|
|
29
30
|
"ui.restoreOnCancel": "Restore on cancel",
|
|
30
31
|
"ui.quietStartup": "Startup screen",
|
|
@@ -84,6 +85,8 @@ export const en = {
|
|
|
84
85
|
"agents.sessionBudgetUsd": "External agent budget",
|
|
85
86
|
"subagents.maxConcurrent": "Subagents in parallel",
|
|
86
87
|
"subagents.maxPending": "Subagent queue limit",
|
|
88
|
+
"subagents.background": "Subagents in background",
|
|
89
|
+
"subagents.autoBackgroundAfterMs": "Auto-background after (ms)",
|
|
87
90
|
"memory.enabled": "Memory",
|
|
88
91
|
"memory.subagents": "Memory in subagents",
|
|
89
92
|
},
|
|
@@ -193,6 +196,7 @@ export const zh = {
|
|
|
193
196
|
"ui.showThinking": "思考内容",
|
|
194
197
|
"ui.compact": "紧凑布局",
|
|
195
198
|
"ui.animation": "spinner 动画",
|
|
199
|
+
"ui.logo": "启动字符画",
|
|
196
200
|
"ui.statusLine": "底部信息行",
|
|
197
201
|
"ui.restoreOnCancel": "中断后放回原消息",
|
|
198
202
|
"ui.quietStartup": "启动画面",
|
|
@@ -252,6 +256,8 @@ export const zh = {
|
|
|
252
256
|
"agents.sessionBudgetUsd": "外部 Agent 预算",
|
|
253
257
|
"subagents.maxConcurrent": "子 Agent 并发",
|
|
254
258
|
"subagents.maxPending": "子 Agent 排队上限",
|
|
259
|
+
"subagents.background": "子 Agent 缺省后台",
|
|
260
|
+
"subagents.autoBackgroundAfterMs": "自动转后台(毫秒)",
|
|
255
261
|
"memory.enabled": "记忆",
|
|
256
262
|
"memory.subagents": "子 Agent 的记忆",
|
|
257
263
|
},
|
|
@@ -48,6 +48,8 @@ export declare const en: {
|
|
|
48
48
|
listFailed: (provider: string, error: string) => string;
|
|
49
49
|
found: (provider: string, count: number, url: string) => string;
|
|
50
50
|
unmatched: string;
|
|
51
|
+
fromBackend: string;
|
|
52
|
+
noBackendWindow: (ids: string) => string;
|
|
51
53
|
image: string;
|
|
52
54
|
reasoning: string;
|
|
53
55
|
noTools: string;
|
|
@@ -101,6 +103,8 @@ export declare const zh: {
|
|
|
101
103
|
listFailed: (provider: string, error: string) => string;
|
|
102
104
|
found: (provider: string, count: number, url: string) => string;
|
|
103
105
|
unmatched: string;
|
|
106
|
+
fromBackend: string;
|
|
107
|
+
noBackendWindow: (ids: string) => string;
|
|
104
108
|
image: string;
|
|
105
109
|
reasoning: string;
|
|
106
110
|
noTools: string;
|
|
@@ -56,6 +56,8 @@ export const en = {
|
|
|
56
56
|
listFailed: (provider, error) => `ama: failed to fetch the model list of ${provider}: ${error}\n`,
|
|
57
57
|
found: (provider, count, url) => `${provider}: found ${plural(count, "model")} (${url})\n`,
|
|
58
58
|
unmatched: " models.dev: no match",
|
|
59
|
+
fromBackend: " (backend)",
|
|
60
|
+
noBackendWindow: (ids) => `The backend did not report a context window for ${ids}; using models.dev (128k when unmatched)\n`,
|
|
59
61
|
image: " · image",
|
|
60
62
|
reasoning: " · reasoning",
|
|
61
63
|
noTools: " · no tool calling",
|
|
@@ -116,6 +118,8 @@ export const zh = {
|
|
|
116
118
|
listFailed: (provider, error) => `ama: ${provider} 模型列表获取失败:${error}\n`,
|
|
117
119
|
found: (provider, count, url) => `${provider}:发现 ${count} 个模型(${url})\n`,
|
|
118
120
|
unmatched: " models.dev 未匹配",
|
|
121
|
+
fromBackend: "(后端)",
|
|
122
|
+
noBackendWindow: (ids) => `后端没有给出 ${ids} 的上下文窗口,改用 models.dev 的值(未匹配时按 128k)\n`,
|
|
119
123
|
image: " · 图片",
|
|
120
124
|
reasoning: " · 思考",
|
|
121
125
|
noTools: " · 不支持工具调用",
|
|
@@ -280,6 +280,8 @@ export declare const en: {
|
|
|
280
280
|
listFailed: (provider: string, error: string) => string;
|
|
281
281
|
found: (provider: string, count: number, url: string) => string;
|
|
282
282
|
unmatched: string;
|
|
283
|
+
fromBackend: string;
|
|
284
|
+
noBackendWindow: (ids: string) => string;
|
|
283
285
|
image: string;
|
|
284
286
|
reasoning: string;
|
|
285
287
|
noTools: string;
|
|
@@ -564,6 +566,8 @@ export declare const zh: {
|
|
|
564
566
|
listFailed: (provider: string, error: string) => string;
|
|
565
567
|
found: (provider: string, count: number, url: string) => string;
|
|
566
568
|
unmatched: string;
|
|
569
|
+
fromBackend: string;
|
|
570
|
+
noBackendWindow: (ids: string) => string;
|
|
567
571
|
image: string;
|
|
568
572
|
reasoning: string;
|
|
569
573
|
noTools: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -38,6 +38,7 @@ export { createAgentSession, createRuntime } from "./sdk.js";
|
|
|
38
38
|
export type { CreateSessionOptions, RuntimeOptions, SessionAuth } from "./sdk.js";
|
|
39
39
|
export type { PlanDecision, PlanResponse, PlanResponseResult, SdkAgentSession, SessionPlanApi, SessionPlanOptions, } from "./sdk.js";
|
|
40
40
|
export { AgentSessionImpl } from "./agent/session.js";
|
|
41
|
+
export type { BackgroundReason } from "./agent/subagent-background.js";
|
|
41
42
|
export { SessionManager } from "./session/manager.js";
|
|
42
43
|
export { ProviderRegistry } from "./ai/providers/registry.js";
|
|
43
44
|
export { FakeProvider } from "./ai/fake/fake-provider.js";
|
|
@@ -22,6 +22,7 @@ import { pasteImage } from "./interactive/clipboard-paste.js";
|
|
|
22
22
|
import { planCommand } from "./interactive/plan-command.js";
|
|
23
23
|
import { rewindCommand } from "./interactive/rewind-command.js";
|
|
24
24
|
import { describeAgents, describeTaskOutput, describeTasks, stopTask, } from "./interactive/tasks-report.js";
|
|
25
|
+
import { backgroundTasks, backgroundedText } from "./interactive/task-background.js";
|
|
25
26
|
import { describeCache, describeFingerprint, describeSession } from "./session-report.js";
|
|
26
27
|
import { msg } from "../i18n/index.js";
|
|
27
28
|
export { describeSession } from "./session-report.js";
|
|
@@ -63,7 +64,7 @@ export const BUILTIN_COMMANDS = [
|
|
|
63
64
|
command("cache", "cache", "[warm off|streaming|idle | fingerprint]"),
|
|
64
65
|
command("statusline", "statusline", "[full|compact]"),
|
|
65
66
|
command("plan", "plan", { key: "planArgs" }),
|
|
66
|
-
command("tasks", "tasks", "[id] | stop <id>"),
|
|
67
|
+
command("tasks", "tasks", "[id] | stop <id> | bg [id]"),
|
|
67
68
|
command("agents", "agents"),
|
|
68
69
|
command("paste", "paste"),
|
|
69
70
|
command("exit", "exit"),
|
|
@@ -110,12 +111,15 @@ function cacheCommand(session, args) {
|
|
|
110
111
|
}
|
|
111
112
|
throw new AmaError("invalid_arguments", msg().report.command.cacheUsage);
|
|
112
113
|
}
|
|
113
|
-
/** `/tasks`、`/tasks <id>`、`/tasks stop <id
|
|
114
|
-
async function tasksCommand(
|
|
114
|
+
/** `/tasks`、`/tasks <id>`、`/tasks stop <id>`、`/tasks bg [id]`([W7-C] 无 id = 全部前台任务)。 */
|
|
115
|
+
async function tasksCommand(session, args) {
|
|
116
|
+
const sessionId = session.state.sessionId;
|
|
115
117
|
const [first, second, extra] = args.split(/\s+/).filter((s) => s !== "");
|
|
116
118
|
const now = Date.now();
|
|
117
119
|
if (first === undefined)
|
|
118
120
|
return describeTasks(sessionId, now);
|
|
121
|
+
if (first === "bg" && extra === undefined)
|
|
122
|
+
return backgroundedText(backgroundTasks(session, second));
|
|
119
123
|
if (first === "stop" && second !== undefined && extra === undefined) {
|
|
120
124
|
await stopTask(sessionId, second);
|
|
121
125
|
return msg().report.command.taskStopped(second);
|
|
@@ -241,7 +245,7 @@ export async function runSlashCommand(line, ctx) {
|
|
|
241
245
|
case "plan":
|
|
242
246
|
return planCommand(session, args, ctx);
|
|
243
247
|
case "tasks":
|
|
244
|
-
return { kind: "handled", message: await tasksCommand(session
|
|
248
|
+
return { kind: "handled", message: await tasksCommand(session, args) };
|
|
245
249
|
case "agents":
|
|
246
250
|
return { kind: "handled", message: describeAgents(session.state.sessionId) };
|
|
247
251
|
case "paste": {
|
|
@@ -13,7 +13,9 @@
|
|
|
13
13
|
* - 显示条件:有排队 / 运行中 / 等审批的任务,或本会话里看着结束、还没查看过的任务(结束后最多 10 分钟);
|
|
14
14
|
* `ui.agentBar: "off"`(嵌入宿主缺省)整个不显示。
|
|
15
15
|
* - 聚焦(`Ctrl+B` / 空输入 `↓` / `/tasks`):列出本会话全部任务,`›` 标当前项,窗口跟着选择滚动,
|
|
16
|
-
*
|
|
16
|
+
* 末行是按键提示(`↑↓ 选择 · Enter 打开 · b 转后台 · x 停止 · Esc 返回`,放不下时丢 b / x 说明)。
|
|
17
|
+
* 按键处理在 agent-ui.ts。
|
|
18
|
+
* - [W7-C] 等审批的行(含停靠在栏里的后台任务审批)整行警告色。
|
|
17
19
|
* - 状态来自任务注册表(`TaskInfo`、`live().queued`)、子 Agent 折叠视图的跟踪(轮数、最近工具、用时,界面时钟)
|
|
18
20
|
* 与 `permission_request.context.taskId`(等审批)。
|
|
19
21
|
*/
|
|
@@ -13,12 +13,14 @@
|
|
|
13
13
|
* - 显示条件:有排队 / 运行中 / 等审批的任务,或本会话里看着结束、还没查看过的任务(结束后最多 10 分钟);
|
|
14
14
|
* `ui.agentBar: "off"`(嵌入宿主缺省)整个不显示。
|
|
15
15
|
* - 聚焦(`Ctrl+B` / 空输入 `↓` / `/tasks`):列出本会话全部任务,`›` 标当前项,窗口跟着选择滚动,
|
|
16
|
-
*
|
|
16
|
+
* 末行是按键提示(`↑↓ 选择 · Enter 打开 · b 转后台 · x 停止 · Esc 返回`,放不下时丢 b / x 说明)。
|
|
17
|
+
* 按键处理在 agent-ui.ts。
|
|
18
|
+
* - [W7-C] 等审批的行(含停靠在栏里的后台任务审批)整行警告色。
|
|
17
19
|
* - 状态来自任务注册表(`TaskInfo`、`live().queued`)、子 Agent 折叠视图的跟踪(轮数、最近工具、用时,界面时钟)
|
|
18
20
|
* 与 `permission_request.context.taskId`(等审批)。
|
|
19
21
|
*/
|
|
20
22
|
import { msg } from "../../i18n/index.js";
|
|
21
|
-
import { formatElapsed, truncateToWidth } from "../../tui.js";
|
|
23
|
+
import { formatElapsed, truncateToWidth, visibleWidth, } from "../../tui.js";
|
|
22
24
|
export const BAR_ROWS = 3;
|
|
23
25
|
/** 结束后未查看的任务在栏里保留多久。 */
|
|
24
26
|
export const BAR_RETAIN_MS = 10 * 60_000;
|
|
@@ -224,8 +226,11 @@ export class AgentBar {
|
|
|
224
226
|
const tail = [];
|
|
225
227
|
if (hidden > 0)
|
|
226
228
|
tail.push(m.bar.more(hidden));
|
|
227
|
-
if (focused)
|
|
228
|
-
|
|
229
|
+
if (focused) {
|
|
230
|
+
const [up, down] = [t.glyphs.arrowUp, t.glyphs.arrowDown];
|
|
231
|
+
const full = [...tail, m.bar.keysFull(up, down)].join(" · ");
|
|
232
|
+
tail.push(visibleWidth(full) <= width ? m.bar.keysFull(up, down) : m.bar.keys(up, down));
|
|
233
|
+
}
|
|
229
234
|
if (tail.length > 0)
|
|
230
235
|
out.push(truncateToWidth(t.fg("dim", tail.join(" · ")), width, t.glyphs.ellipsis));
|
|
231
236
|
return out;
|
|
@@ -236,7 +241,9 @@ export class AgentBar {
|
|
|
236
241
|
const agent = agentLabel(row);
|
|
237
242
|
const lead = focused ? (selected ? t.fg("accent", g.prompt) : " ") + " " : "";
|
|
238
243
|
const marker = t.fg(statusColor(row.status), g.tool);
|
|
239
|
-
const
|
|
244
|
+
const approval = row.status === "approval";
|
|
245
|
+
const plainId = selected || approval ? t.bold(row.taskId) : row.taskId;
|
|
246
|
+
const id = approval ? t.fg("warning", plainId) : plainId;
|
|
240
247
|
const description = row.description.replace(/\s+/g, " ").trim();
|
|
241
248
|
const text = `${lead}${marker} ${id} ${t.fg("tool", agent)}` +
|
|
242
249
|
t.fg("dim", " · ") +
|
|
@@ -13,10 +13,14 @@
|
|
|
13
13
|
* 事件;这里在会话的 `options.log` 外面套一层,只把这类行转到消息区,其余照原样写日志。
|
|
14
14
|
*
|
|
15
15
|
* [W6-A] Agent 栏(agent-bar.ts,提示行之下、状态行之上)与子 Agent 视图(agent-view.ts,底部覆盖层):
|
|
16
|
-
* - 键位:空输入时
|
|
17
|
-
*
|
|
16
|
+
* - 键位:空输入时 `↓`(有任务即可,不要求栏可见;`app.agents.focus`,key-dispatch.ts 先问这里)进栏;
|
|
17
|
+
* 有字 / 栏关闭 / 没有任务时返回落空原因,由 key-dispatch 给提示 [W7-A];
|
|
18
|
+
* 栏内 ↑↓ 选、Enter 打开视图、Esc 返回,可打印字符回到输入框;
|
|
18
19
|
* - `/tasks` 无参 = 聚焦栏(`ui.agentBar: "off"` 时仍是原来的选择器),`/tasks <id>` = 直接进视图;
|
|
19
20
|
* - 等审批的任务按 `permission_request.context.taskId` 记下(栏与视图标题显示「等待审批」)。
|
|
21
|
+
* [W7-C] 栏内 `b` / `Ctrl+B` 转后台选中的任务、`x` 连按两次停止(task-background.ts);`subagent_background`
|
|
22
|
+
* 刷新工具行(后台样式)并对超时 / 宿主转的给一行提示;后台任务的审批停靠(approval-dock.ts):打开该任务
|
|
23
|
+
* 的视图即弹出,`permission_request` / `permission_resolved` 交给停靠判断。
|
|
20
24
|
*/
|
|
21
25
|
import type { AgentSession, SessionEvent } from "../../agent/types.js";
|
|
22
26
|
import type { SwitchRequest } from "../../cli/compose-session.js";
|
|
@@ -27,6 +31,7 @@ import type { CommandUi } from "./commands.js";
|
|
|
27
31
|
import type { AgentKeys } from "./key-dispatch.js";
|
|
28
32
|
import type { StatusArea } from "./status-area.js";
|
|
29
33
|
import { FirstRunMerge } from "./approval-merge.js";
|
|
34
|
+
import type { ApprovalDock } from "./approval-dock.js";
|
|
30
35
|
import type { NoticeLevel } from "./message-view.js";
|
|
31
36
|
import type { PickerSpec } from "./pickers.js";
|
|
32
37
|
import { PlanFlow } from "./plan-flow.js";
|
|
@@ -76,6 +81,10 @@ export declare class AgentUi {
|
|
|
76
81
|
private restoreLog;
|
|
77
82
|
/** 后台子 Agent 运行中:每秒重画跟随行的耗时(主会话空闲时 Loader 不转)。 */
|
|
78
83
|
private ticker;
|
|
84
|
+
/** [W7-C] 后台任务的审批停靠(approval-ui.ts 建好后接上)。 */
|
|
85
|
+
dock: ApprovalDock | undefined;
|
|
86
|
+
/** [W7-C] 栏内 `x` 的双击确认。 */
|
|
87
|
+
private readonly stopArm;
|
|
79
88
|
constructor(deps: AgentUiDeps);
|
|
80
89
|
private keybindings;
|
|
81
90
|
private barEnabled;
|
|
@@ -93,10 +102,19 @@ export declare class AgentUi {
|
|
|
93
102
|
private showPlan;
|
|
94
103
|
/** `/tasks`(无参):聚焦 Agent 栏;栏关闭(嵌入宿主缺省)时仍是选择器。 */
|
|
95
104
|
focusBar(): Promise<void>;
|
|
96
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* `app.agents.focus`:输入为空且有任务即进栏(与 `/tasks` 一致,不要求栏可见)[W7-A];
|
|
107
|
+
* 否则返回落空原因——有字(有任务时)、栏关闭(有任务时)、没有任务(栏开着时)。
|
|
108
|
+
*/
|
|
97
109
|
private focusFromKey;
|
|
110
|
+
/** [W7-A] 运行提示行要不要带「↓ Agent 栏」:栏开着、有该显示的任务、没在栏里或视图里。 */
|
|
111
|
+
get reachable(): boolean;
|
|
98
112
|
/** 栏聚焦时的按键;不在栏里返回 false。 */
|
|
99
113
|
private barKey;
|
|
114
|
+
/** [W7-C] 栏内 `b` / `Ctrl+B`:转后台选中的任务(只对前台运行中的有效)。 */
|
|
115
|
+
private backgroundSelected;
|
|
116
|
+
/** [W7-C] 栏内 `x`:第一次提示「再按 x 停止」,1.5 s 内再按停止。 */
|
|
117
|
+
private stopSelected;
|
|
100
118
|
/** `/tasks <id>` 与栏里 Enter:打开子 Agent 视图。 */
|
|
101
119
|
openView(taskId: string): void;
|
|
102
120
|
get viewing(): string | undefined;
|
|
@@ -13,24 +13,29 @@
|
|
|
13
13
|
* 事件;这里在会话的 `options.log` 外面套一层,只把这类行转到消息区,其余照原样写日志。
|
|
14
14
|
*
|
|
15
15
|
* [W6-A] Agent 栏(agent-bar.ts,提示行之下、状态行之上)与子 Agent 视图(agent-view.ts,底部覆盖层):
|
|
16
|
-
* - 键位:空输入时
|
|
17
|
-
*
|
|
16
|
+
* - 键位:空输入时 `↓`(有任务即可,不要求栏可见;`app.agents.focus`,key-dispatch.ts 先问这里)进栏;
|
|
17
|
+
* 有字 / 栏关闭 / 没有任务时返回落空原因,由 key-dispatch 给提示 [W7-A];
|
|
18
|
+
* 栏内 ↑↓ 选、Enter 打开视图、Esc 返回,可打印字符回到输入框;
|
|
18
19
|
* - `/tasks` 无参 = 聚焦栏(`ui.agentBar: "off"` 时仍是原来的选择器),`/tasks <id>` = 直接进视图;
|
|
19
20
|
* - 等审批的任务按 `permission_request.context.taskId` 记下(栏与视图标题显示「等待审批」)。
|
|
21
|
+
* [W7-C] 栏内 `b` / `Ctrl+B` 转后台选中的任务、`x` 连按两次停止(task-background.ts);`subagent_background`
|
|
22
|
+
* 刷新工具行(后台样式)并对超时 / 宿主转的给一行提示;后台任务的审批停靠(approval-dock.ts):打开该任务
|
|
23
|
+
* 的视图即弹出,`permission_request` / `permission_resolved` 交给停靠判断。
|
|
20
24
|
*/
|
|
21
25
|
import { registryOf, taskRegistryView } from "../../agent/subagent-registry.js";
|
|
22
26
|
import { planController } from "../../plan/controller.js";
|
|
23
27
|
import { msg } from "../../i18n/index.js";
|
|
24
|
-
import { defaultKeybindings, isPrintableText,
|
|
28
|
+
import { defaultKeybindings, isPrintableText, } from "../../tui.js";
|
|
25
29
|
import { AgentBar } from "./agent-bar.js";
|
|
26
30
|
import { AgentView } from "./agent-view.js";
|
|
27
31
|
import { agentsPanel, planPanel, taskOutputPanel } from "./agent-panels.js";
|
|
28
32
|
import { FirstRunMerge } from "./approval-merge.js";
|
|
29
33
|
import { pasteImage } from "./clipboard-paste.js";
|
|
30
|
-
import { backgroundJobText, limitReachedText, modelFallbackText } from "./event-notices.js";
|
|
34
|
+
import { backgroundJobText, limitReachedText, modelFallbackText, subagentBackgroundText, } from "./event-notices.js";
|
|
31
35
|
import { editExternally } from "./external-editor.js";
|
|
32
36
|
import { PlanFlow } from "./plan-flow.js";
|
|
33
37
|
import { backgroundEndText, SubagentTracker } from "./subagent-view.js";
|
|
38
|
+
import { backgroundedText, StopArm } from "./task-background.js";
|
|
34
39
|
import { listTasks, stopTask, taskLine, taskOutput } from "./tasks-report.js";
|
|
35
40
|
/** [W6-A] `CommandUi` 的 Agent 栏 / 视图钩子(`/tasks`、`/tasks <id>`);晚绑定,装配时 agentUi 还没建。 */
|
|
36
41
|
export function agentCommandHooks(get) {
|
|
@@ -49,7 +54,7 @@ export class AgentUi {
|
|
|
49
54
|
/** [W6-A] key-dispatch.ts 的 Agent 栏入口。 */
|
|
50
55
|
keys = {
|
|
51
56
|
handleKey: (data) => this.barKey(data),
|
|
52
|
-
focus: (
|
|
57
|
+
focus: () => this.focusFromKey(),
|
|
53
58
|
};
|
|
54
59
|
view;
|
|
55
60
|
/** 等审批的请求 → 任务。 */
|
|
@@ -57,6 +62,10 @@ export class AgentUi {
|
|
|
57
62
|
restoreLog;
|
|
58
63
|
/** 后台子 Agent 运行中:每秒重画跟随行的耗时(主会话空闲时 Loader 不转)。 */
|
|
59
64
|
ticker;
|
|
65
|
+
/** [W7-C] 后台任务的审批停靠(approval-ui.ts 建好后接上)。 */
|
|
66
|
+
dock;
|
|
67
|
+
/** [W7-C] 栏内 `x` 的双击确认。 */
|
|
68
|
+
stopArm = new StopArm();
|
|
60
69
|
constructor(deps) {
|
|
61
70
|
this.deps = deps;
|
|
62
71
|
this.merge = new FirstRunMerge({ sessionId: () => deps.session().state.sessionId });
|
|
@@ -107,6 +116,7 @@ export class AgentUi {
|
|
|
107
116
|
this.deps.notice("info", msg().agents.view.removed(viewing));
|
|
108
117
|
this.bar.reset();
|
|
109
118
|
this.approvals.clear();
|
|
119
|
+
this.dock?.reset();
|
|
110
120
|
this.deps.subagents.clear();
|
|
111
121
|
this.stopTicker();
|
|
112
122
|
this.plan.attach(session);
|
|
@@ -171,10 +181,14 @@ export class AgentUi {
|
|
|
171
181
|
switch (event.type) {
|
|
172
182
|
case "subagent_start":
|
|
173
183
|
case "subagent_update":
|
|
184
|
+
case "subagent_background":
|
|
174
185
|
case "subagent_end": {
|
|
175
186
|
const state = this.deps.subagents.onEvent(event);
|
|
176
187
|
if (state === undefined)
|
|
177
188
|
return true;
|
|
189
|
+
// [W7-C] 人按的已有底部提示;超时 / 宿主转的留一行
|
|
190
|
+
if (event.type === "subagent_background" && event.reason !== "user")
|
|
191
|
+
this.deps.notice("info", subagentBackgroundText(event, state.agent));
|
|
178
192
|
this.deps.tools().get(state.parentToolCallId)?.refresh();
|
|
179
193
|
// 视图里看的任务又开跑(续聊、被释放后重开):重新接上它的子会话
|
|
180
194
|
if (event.type === "subagent_start" && this.view?.component.task === event.taskId)
|
|
@@ -198,11 +212,20 @@ export class AgentUi {
|
|
|
198
212
|
const taskId = event.context?.taskId;
|
|
199
213
|
if (taskId !== undefined)
|
|
200
214
|
this.approvals.set(event.requestId, taskId);
|
|
215
|
+
this.dock?.observe(event);
|
|
201
216
|
return false;
|
|
202
217
|
}
|
|
203
218
|
case "permission_resolved":
|
|
204
219
|
this.approvals.delete(event.requestId);
|
|
220
|
+
this.dock?.observe(event);
|
|
205
221
|
return false;
|
|
222
|
+
case "agent_settled": {
|
|
223
|
+
// 主会话空闲了:停靠的审批看能不能弹(运行指示要等这个事件分派完才变空闲)
|
|
224
|
+
const dock = this.dock;
|
|
225
|
+
if (dock !== undefined && dock.size > 0)
|
|
226
|
+
setTimeout(() => dock.poke(), 0);
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
206
229
|
default:
|
|
207
230
|
return false;
|
|
208
231
|
}
|
|
@@ -250,23 +273,39 @@ export class AgentUi {
|
|
|
250
273
|
}
|
|
251
274
|
this.deps.render();
|
|
252
275
|
}
|
|
253
|
-
/**
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
276
|
+
/**
|
|
277
|
+
* `app.agents.focus`:输入为空且有任务即进栏(与 `/tasks` 一致,不要求栏可见)[W7-A];
|
|
278
|
+
* 否则返回落空原因——有字(有任务时)、栏关闭(有任务时)、没有任务(栏开着时)。
|
|
279
|
+
*/
|
|
280
|
+
focusFromKey() {
|
|
281
|
+
if (this.view !== undefined)
|
|
258
282
|
return false;
|
|
283
|
+
const hasTasks = this.bar.all().length > 0;
|
|
284
|
+
if (!this.barEnabled())
|
|
285
|
+
return hasTasks && this.deps.editor.isEmpty() ? "disabled" : false;
|
|
286
|
+
if (!this.deps.editor.isEmpty())
|
|
287
|
+
return hasTasks ? "busy-input" : false;
|
|
259
288
|
if (!this.bar.focus())
|
|
260
|
-
return
|
|
289
|
+
return "empty";
|
|
261
290
|
this.deps.render();
|
|
262
291
|
return true;
|
|
263
292
|
}
|
|
293
|
+
/** [W7-A] 运行提示行要不要带「↓ Agent 栏」:栏开着、有该显示的任务、没在栏里或视图里。 */
|
|
294
|
+
get reachable() {
|
|
295
|
+
return this.view === undefined && !this.bar.focused && this.bar.visible;
|
|
296
|
+
}
|
|
264
297
|
/** 栏聚焦时的按键;不在栏里返回 false。 */
|
|
265
298
|
barKey(data) {
|
|
266
299
|
if (!this.bar.focused)
|
|
267
300
|
return false;
|
|
268
301
|
const keys = this.keybindings();
|
|
269
|
-
if (
|
|
302
|
+
if (data !== "x")
|
|
303
|
+
this.stopArm.reset();
|
|
304
|
+
if (data === "b" || keys.matches(data, "app.tasks.background"))
|
|
305
|
+
this.backgroundSelected();
|
|
306
|
+
else if (data === "x")
|
|
307
|
+
void this.stopSelected();
|
|
308
|
+
else if (keys.matches(data, "tui.select.up")) {
|
|
270
309
|
if (!this.bar.move(-1))
|
|
271
310
|
this.bar.blur();
|
|
272
311
|
}
|
|
@@ -288,6 +327,33 @@ export class AgentUi {
|
|
|
288
327
|
this.deps.render();
|
|
289
328
|
return true;
|
|
290
329
|
}
|
|
330
|
+
/** [W7-C] 栏内 `b` / `Ctrl+B`:转后台选中的任务(只对前台运行中的有效)。 */
|
|
331
|
+
backgroundSelected() {
|
|
332
|
+
const taskId = this.bar.selected();
|
|
333
|
+
if (taskId === undefined)
|
|
334
|
+
return;
|
|
335
|
+
const moved = this.deps.session().backgroundTask(taskId, "user");
|
|
336
|
+
this.deps.hint(moved.length > 0 ? backgroundedText(moved) : msg().agents.background.notForeground(taskId));
|
|
337
|
+
}
|
|
338
|
+
/** [W7-C] 栏内 `x`:第一次提示「再按 x 停止」,1.5 s 内再按停止。 */
|
|
339
|
+
async stopSelected() {
|
|
340
|
+
const taskId = this.bar.selected();
|
|
341
|
+
if (taskId === undefined)
|
|
342
|
+
return;
|
|
343
|
+
const sessionId = this.deps.session().state.sessionId;
|
|
344
|
+
if (taskRegistryView(sessionId)?.get(taskId)?.status !== "running") {
|
|
345
|
+
this.stopArm.reset();
|
|
346
|
+
this.deps.hint(msg().agents.stop.ended(taskId));
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (this.stopArm.press(taskId, this.deps.now()) === "arm") {
|
|
350
|
+
this.deps.hint(msg().agents.stop.confirm(taskId));
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
this.deps.hint(msg().agents.view.stopped(taskId));
|
|
354
|
+
await stopTask(sessionId, taskId);
|
|
355
|
+
this.deps.render();
|
|
356
|
+
}
|
|
291
357
|
/** `/tasks <id>` 与栏里 Enter:打开子 Agent 视图。 */
|
|
292
358
|
openView(taskId) {
|
|
293
359
|
const sessionId = this.deps.session().state.sessionId;
|
|
@@ -309,6 +375,7 @@ export class AgentUi {
|
|
|
309
375
|
siblings: () => this.bar.all().map((row) => row.taskId),
|
|
310
376
|
close: () => this.closeView(),
|
|
311
377
|
stop: (id) => stopTask(this.deps.session().state.sessionId, id),
|
|
378
|
+
background: (id) => this.deps.session().backgroundTask(id, "user"),
|
|
312
379
|
messages: {
|
|
313
380
|
...(ui.showThinking !== undefined ? { showThinking: ui.showThinking } : {}),
|
|
314
381
|
...(ui.markdown !== undefined ? { markdown: ui.markdown } : {}),
|
|
@@ -319,6 +386,8 @@ export class AgentUi {
|
|
|
319
386
|
this.bar.blur();
|
|
320
387
|
const handle = this.deps.tui.showOverlay(component, { anchor: "bottom" });
|
|
321
388
|
this.view = { component, handle };
|
|
389
|
+
// [W7-C] 正在看的任务有停靠的审批:立即弹出(盖在视图上)
|
|
390
|
+
this.dock?.poke();
|
|
322
391
|
this.deps.render();
|
|
323
392
|
}
|
|
324
393
|
get viewing() {
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
* `↑` / PgUp 上翻(暂停跟随)、`↓` / PgDn 下翻、End(暂停时也可 `f`)回到跟随;输入非空时 Esc 清空。
|
|
17
17
|
* Esc 不中断父会话,停止子任务用 `/tasks stop <id>`(视图里只认这一条命令)。
|
|
18
18
|
* 所查看的任务在等审批时标题显示「等待审批」,审批框照常以覆盖层弹在视图上面。
|
|
19
|
+
* [W7-C] `Ctrl+B`(`app.tasks.background`)转后台正在看的任务(只对前台任务有效,否则落回编辑器);
|
|
20
|
+
* 视图里另认 `/tasks bg [id]`。
|
|
19
21
|
*/
|
|
20
22
|
import { type Component, type Focusable, type Keybindings, type Theme } from "../../tui.js";
|
|
21
23
|
import { type TaskSource } from "./agent-bar.js";
|
|
@@ -37,6 +39,8 @@ export interface AgentViewDeps {
|
|
|
37
39
|
onSwitch?(taskId: string): void;
|
|
38
40
|
close(): void;
|
|
39
41
|
stop(taskId: string): Promise<void>;
|
|
42
|
+
/** [W7-C] 人工转后台;返回转了的 taskId。 */
|
|
43
|
+
background?(taskId: string): string[];
|
|
40
44
|
/** 消息区的显示选项(思考块、Markdown、紧凑)。 */
|
|
41
45
|
messages?: Omit<MessageViewOptions, "theme">;
|
|
42
46
|
}
|
|
@@ -16,12 +16,15 @@
|
|
|
16
16
|
* `↑` / PgUp 上翻(暂停跟随)、`↓` / PgDn 下翻、End(暂停时也可 `f`)回到跟随;输入非空时 Esc 清空。
|
|
17
17
|
* Esc 不中断父会话,停止子任务用 `/tasks stop <id>`(视图里只认这一条命令)。
|
|
18
18
|
* 所查看的任务在等审批时标题显示「等待审批」,审批框照常以覆盖层弹在视图上面。
|
|
19
|
+
* [W7-C] `Ctrl+B`(`app.tasks.background`)转后台正在看的任务(只对前台任务有效,否则落回编辑器);
|
|
20
|
+
* 视图里另认 `/tasks bg [id]`。
|
|
19
21
|
*/
|
|
20
22
|
import { msg } from "../../i18n/index.js";
|
|
21
23
|
import { Editor, isPrintableText, parseKey, truncateToWidth, visibleWidth, } from "../../tui.js";
|
|
22
24
|
import { formatTokenCount } from "../session-report.js";
|
|
23
25
|
import { agentLabel, rowFacts, statusColor, taskRow } from "./agent-bar.js";
|
|
24
26
|
import { AmaTranscript, ExternalTranscript, readBranch, } from "./agent-transcript.js";
|
|
27
|
+
import { backgroundedText } from "./task-background.js";
|
|
25
28
|
export class AgentView {
|
|
26
29
|
taskId;
|
|
27
30
|
deps;
|
|
@@ -142,7 +145,10 @@ export class AgentView {
|
|
|
142
145
|
const taskId = this.taskId;
|
|
143
146
|
if (trimmed.startsWith("/")) {
|
|
144
147
|
const stop = /^\/tasks\s+stop(?:\s+(\S+))?\s*$/.exec(trimmed);
|
|
145
|
-
|
|
148
|
+
const bg = /^\/tasks\s+bg(?:\s+(\S+))?\s*$/.exec(trimmed);
|
|
149
|
+
if (bg !== null && this.deps.background !== undefined)
|
|
150
|
+
this.flash = backgroundedText(this.deps.background(bg[1] ?? taskId));
|
|
151
|
+
else if (stop === null)
|
|
146
152
|
this.flash = m.commandsHere;
|
|
147
153
|
else {
|
|
148
154
|
const target = stop[1] ?? taskId;
|
|
@@ -175,6 +181,13 @@ export class AgentView {
|
|
|
175
181
|
const empty = this.editor.isEmpty();
|
|
176
182
|
const id = parseKey(data)?.id;
|
|
177
183
|
this.flash = undefined;
|
|
184
|
+
if (keys.matches(data, "app.tasks.background") && this.deps.background !== undefined) {
|
|
185
|
+
const moved = this.deps.background(this.taskId);
|
|
186
|
+
if (moved.length > 0) {
|
|
187
|
+
this.flash = backgroundedText(moved);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
178
191
|
if (keys.matches(data, "tui.select.cancel") && !this.editor.isCompletionOpen) {
|
|
179
192
|
if (!empty)
|
|
180
193
|
this.editor.clear();
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
import type { SessionEvent } from "../../agent/types.js";
|
|
16
|
+
import type { ApprovalBroker, ApprovalDecision, ApprovalRequest } from "../../permissions/types.js";
|
|
17
|
+
export declare const DOCK_POLL_MS = 250;
|
|
18
|
+
export interface DockHost {
|
|
19
|
+
/** 任务在后台运行(停靠候选)。 */
|
|
20
|
+
dockable(taskId: string): boolean;
|
|
21
|
+
/** 现在可以弹出这个任务的审批(主会话空闲且空输入且无覆盖层,或正在看它的视图)。 */
|
|
22
|
+
ready(taskId: string): boolean;
|
|
23
|
+
/** 停靠集合变了(栏与运行提示行重画)。 */
|
|
24
|
+
changed(): void;
|
|
25
|
+
pollMs?: number;
|
|
26
|
+
}
|
|
27
|
+
export declare class ApprovalDock implements ApprovalBroker {
|
|
28
|
+
private readonly inner;
|
|
29
|
+
private readonly host;
|
|
30
|
+
private readonly docked;
|
|
31
|
+
/** 已发出、还没答复的不可停靠请求(主会话 / 前台任务)。 */
|
|
32
|
+
private readonly others;
|
|
33
|
+
private timer;
|
|
34
|
+
constructor(inner: ApprovalBroker, host: DockHost);
|
|
35
|
+
ask(request: ApprovalRequest, signal: AbortSignal): Promise<ApprovalDecision | undefined>;
|
|
36
|
+
/** 停靠中的任务。 */
|
|
37
|
+
tasks(): ReadonlySet<string>;
|
|
38
|
+
get size(): number;
|
|
39
|
+
/** 会话事件:记下不可停靠的请求;停靠期间来了这种请求就放出停靠的(见文件头)。 */
|
|
40
|
+
observe(event: SessionEvent): void;
|
|
41
|
+
/** 对一次弹出条件(定时器、视图打开、回合结束)。 */
|
|
42
|
+
poke(): void;
|
|
43
|
+
/** 换会话:旧会话的未答复请求不再有 `permission_resolved` 到这里。 */
|
|
44
|
+
reset(): void;
|
|
45
|
+
/** 退出:停靠的请求按 undefined 交回链(链按拒绝处理)。 */
|
|
46
|
+
dispose(): void;
|
|
47
|
+
private release;
|
|
48
|
+
private remove;
|
|
49
|
+
private startPolling;
|
|
50
|
+
private stopPolling;
|
|
51
|
+
}
|