@armadra/agent 0.6.2 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +85 -0
- package/CHANGELOG.zh-CN.md +59 -0
- package/README.md +168 -575
- package/README.zh-CN.md +165 -596
- package/dist/agent/queue.d.ts +9 -0
- package/dist/agent/queue.js +27 -0
- package/dist/agent/session-subagent.d.ts +1 -0
- package/dist/agent/session-subagent.js +9 -0
- package/dist/agent/session.d.ts +11 -0
- package/dist/agent/session.js +29 -2
- package/dist/agent/subagent-background.d.ts +75 -0
- package/dist/agent/subagent-background.js +209 -0
- package/dist/agent/subagent-direct.d.ts +5 -2
- package/dist/agent/subagent-direct.js +34 -1
- package/dist/agent/subagent-registry.d.ts +26 -6
- package/dist/agent/subagent-registry.js +66 -94
- package/dist/agent/types-w5.d.ts +11 -1
- package/dist/agent/types.d.ts +12 -0
- package/dist/agents/builtin.js +0 -1
- package/dist/agents/external.js +0 -1
- package/dist/agents/parse.js +4 -3
- package/dist/agents/result.d.ts +7 -1
- package/dist/agents/result.js +20 -1
- package/dist/agents/task-control.d.ts +13 -2
- package/dist/agents/task-record.d.ts +16 -4
- package/dist/agents/task-record.js +32 -0
- package/dist/agents/types.d.ts +5 -1
- package/dist/ai/apis/chatgpt-rate-limits.js +7 -2
- package/dist/ai/providers/discovered-cache.d.ts +10 -4
- package/dist/ai/providers/discovered-cache.js +30 -14
- package/dist/ai/types.d.ts +2 -1
- package/dist/auth/chatgpt/backend-client.d.ts +6 -5
- package/dist/auth/chatgpt/backend-client.js +8 -7
- package/dist/bundle/ama.cjs +2034 -552
- package/dist/cli/compose-agents.d.ts +2 -1
- package/dist/cli/compose-agents.js +11 -2
- package/dist/cli/subcommands/models-discover.d.ts +1 -0
- package/dist/cli/subcommands/models-discover.js +12 -2
- package/dist/config/json-schema.js +10 -2
- package/dist/config/key-docs.js +9 -2
- package/dist/config/merge.d.ts +1 -1
- package/dist/config/merge.js +20 -3
- package/dist/config/schema-w5.js +4 -2
- package/dist/config/schema.js +4 -0
- package/dist/config/settings-registry.js +5 -0
- package/dist/config/types-w5.d.ts +10 -0
- package/dist/config/types-w5.js +2 -0
- package/dist/config/types.d.ts +5 -1
- package/dist/drivers/runner.js +29 -2
- package/dist/git/info.d.ts +19 -0
- package/dist/git/info.js +64 -8
- package/dist/i18n/catalog.d.ts +58 -8
- package/dist/i18n/messages/agents.d.ts +60 -0
- package/dist/i18n/messages/agents.js +62 -2
- package/dist/i18n/messages/config-keys.d.ts +8 -0
- package/dist/i18n/messages/config-keys.js +18 -10
- package/dist/i18n/messages/config.d.ts +8 -0
- package/dist/i18n/messages/interactive-startup.d.ts +0 -16
- package/dist/i18n/messages/interactive-startup.js +0 -16
- package/dist/i18n/messages/interactive-view.d.ts +8 -0
- package/dist/i18n/messages/interactive-view.js +10 -0
- package/dist/i18n/messages/interactive.d.ts +33 -16
- package/dist/i18n/messages/interactive.js +31 -0
- package/dist/i18n/messages/print.d.ts +4 -0
- package/dist/i18n/messages/print.js +4 -0
- package/dist/i18n/messages/report.d.ts +8 -0
- package/dist/i18n/messages/report.js +12 -4
- package/dist/i18n/messages/settings.d.ts +8 -0
- package/dist/i18n/messages/settings.js +8 -0
- package/dist/i18n/messages/subcommands-config.d.ts +4 -0
- package/dist/i18n/messages/subcommands-config.js +4 -0
- package/dist/i18n/messages/subcommands.d.ts +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/modes/commands-core.js +19 -4
- package/dist/modes/interactive/agent-bar.d.ts +3 -1
- package/dist/modes/interactive/agent-bar.js +12 -5
- package/dist/modes/interactive/agent-ui.d.ts +21 -3
- package/dist/modes/interactive/agent-ui.js +82 -12
- package/dist/modes/interactive/agent-view.d.ts +9 -0
- package/dist/modes/interactive/agent-view.js +28 -4
- package/dist/modes/interactive/approval-dock.d.ts +51 -0
- package/dist/modes/interactive/approval-dock.js +112 -0
- package/dist/modes/interactive/approval-ui.d.ts +43 -0
- package/dist/modes/interactive/approval-ui.js +64 -0
- package/dist/modes/interactive/commands.js +4 -1
- package/dist/modes/interactive/event-notices.d.ts +6 -1
- package/dist/modes/interactive/event-notices.js +7 -1
- package/dist/modes/interactive/interactive-mode.d.ts +4 -2
- package/dist/modes/interactive/interactive-mode.js +46 -39
- package/dist/modes/interactive/interrupt-send.d.ts +25 -0
- package/dist/modes/interactive/interrupt-send.js +32 -0
- package/dist/modes/interactive/key-dispatch.d.ts +29 -5
- package/dist/modes/interactive/key-dispatch.js +97 -8
- package/dist/modes/interactive/line/line-mode.d.ts +1 -0
- package/dist/modes/interactive/line/line-mode.js +34 -7
- package/dist/modes/interactive/run-indicator.d.ts +34 -2
- package/dist/modes/interactive/run-indicator.js +62 -8
- package/dist/modes/interactive/session-events.js +5 -1
- package/dist/modes/interactive/startup-header.d.ts +29 -14
- package/dist/modes/interactive/startup-header.js +93 -59
- package/dist/modes/interactive/startup-logo.d.ts +83 -0
- package/dist/modes/interactive/startup-logo.js +183 -0
- package/dist/modes/interactive/status-area.d.ts +18 -0
- package/dist/modes/interactive/status-area.js +67 -1
- package/dist/modes/interactive/status-bar.d.ts +13 -2
- package/dist/modes/interactive/status-bar.js +60 -17
- package/dist/modes/interactive/status-line.d.ts +11 -8
- package/dist/modes/interactive/status-line.js +52 -39
- package/dist/modes/interactive/status-quota.d.ts +58 -0
- package/dist/modes/interactive/status-quota.js +155 -0
- package/dist/modes/interactive/subagent-view.d.ts +1 -0
- package/dist/modes/interactive/subagent-view.js +8 -0
- package/dist/modes/interactive/task-background.d.ts +31 -0
- package/dist/modes/interactive/task-background.js +68 -0
- package/dist/modes/interactive/tool-view.d.ts +7 -1
- package/dist/modes/interactive/tool-view.js +24 -1
- package/dist/modes/print/print-mode.d.ts +11 -0
- package/dist/modes/print/print-mode.js +36 -1
- package/dist/modes/rpc/commands.d.ts +4 -1
- package/dist/modes/rpc/commands.js +24 -2
- package/dist/rpc.d.ts +20 -0
- package/dist/rpc.js +3 -0
- package/dist/tools/task-ctl.d.ts +2 -0
- package/dist/tools/task-ctl.js +7 -2
- package/dist/tools/task.d.ts +11 -0
- package/dist/tools/task.js +20 -2
- package/dist/tools/types.d.ts +6 -0
- package/dist/tui/components/editor.d.ts +2 -0
- package/dist/tui/components/editor.js +4 -0
- package/dist/tui/components/loader.d.ts +5 -1
- package/dist/tui/components/loader.js +18 -5
- package/dist/tui/keybindings.d.ts +15 -3
- package/dist/tui/keybindings.js +15 -3
- package/docs/agents.md +52 -28
- package/docs/en/host-api.md +5 -1
- package/docs/en/providers.md +1 -1
- package/docs/en/rpc.md +28 -14
- package/docs/en/sessions.md +3 -1
- package/docs/en/tui.md +106 -70
- package/docs/host-api.md +5 -1
- package/docs/providers.md +4 -2
- package/docs/rpc.md +28 -14
- package/docs/session-format.md +1 -1
- package/docs/sessions.md +2 -1
- package/docs/tui-design.md +42 -29
- package/docs/tui.md +106 -70
- package/package.json +1 -1
|
@@ -13,19 +13,21 @@
|
|
|
13
13
|
* - 事件 `subagent_start / update / end`;父会话 `custom{ama.task}` 记任务快照(带 `status`),
|
|
14
14
|
* resume 时据此重建(未完成标 `interrupted`)。
|
|
15
15
|
* - [W6-A] 子 Agent 视图的 `live()` / `message()`(实现在 subagent-direct.ts)。
|
|
16
|
+
* - [W7-B1] 缺省后台、前台转后台 `background()`、通知投递与 `settled()`(subagent-background.ts)。
|
|
16
17
|
*/
|
|
17
18
|
import { tmpdir } from "node:os";
|
|
18
19
|
import { join } from "node:path";
|
|
19
20
|
import { AmaError } from "../errors.js";
|
|
20
21
|
import { DEFAULT_AGENT } from "../agents/builtin.js";
|
|
21
22
|
import { AgentCatalog, resolveAgentModel } from "../agents/catalog.js";
|
|
22
|
-
import { MAX_TURNS_NOTE, capTaskText,
|
|
23
|
-
import { SubagentPool, TASK_CUSTOM_TYPE, applyRunnerEvent, flushText, newRecord, rebuildRecords, recordData, } from "../agents/task-record.js";
|
|
23
|
+
import { MAX_TURNS_NOTE, capTaskText, writeTaskOutput } from "../agents/result.js";
|
|
24
|
+
import { SubagentPool, TASK_CUSTOM_TYPE, applyRunnerEvent, flushText, newRecord, rebuildRecords, recordData, startEvent, taskStats, } from "../agents/task-record.js";
|
|
24
25
|
import { registerTaskControl, unregisterTaskControl, } from "../agents/task-control.js";
|
|
25
26
|
import { ZERO_USAGE } from "./loop.js";
|
|
26
27
|
import { createWorktree, finishWorktree } from "./worktree.js";
|
|
27
28
|
import { appendTraceEntry } from "./session-trace-writer.js";
|
|
28
29
|
import { directMessage, drainDirect, liveOf } from "./subagent-direct.js";
|
|
30
|
+
import { TaskNotifier, WaitDetach, backgroundRecords, blockingTasks, foregroundWaiter, settleTasks, startedResult, waitForTask, } from "./subagent-background.js";
|
|
29
31
|
export const DEFAULT_SUBAGENT_CONCURRENCY = 4;
|
|
30
32
|
export const DEFAULT_MAX_PENDING = 16;
|
|
31
33
|
export const DEFAULT_RETAINED = 16;
|
|
@@ -71,7 +73,8 @@ export class SubagentRegistry {
|
|
|
71
73
|
retained = [];
|
|
72
74
|
seq = 0;
|
|
73
75
|
disposed = false;
|
|
74
|
-
|
|
76
|
+
notifier;
|
|
77
|
+
waits = new WaitDetach();
|
|
75
78
|
constructor(host, env) {
|
|
76
79
|
this.host = host;
|
|
77
80
|
this.env = env;
|
|
@@ -80,6 +83,7 @@ export class SubagentRegistry {
|
|
|
80
83
|
const rebuilt = rebuildRecords(host.manager.branch(), this.catalog, host.cwd);
|
|
81
84
|
this.tasks = rebuilt.records;
|
|
82
85
|
this.seq = rebuilt.seq;
|
|
86
|
+
this.notifier = new TaskNotifier(host, () => this.disposed);
|
|
83
87
|
}
|
|
84
88
|
now() {
|
|
85
89
|
return this.env.now?.() ?? Date.now();
|
|
@@ -95,39 +99,48 @@ export class SubagentRegistry {
|
|
|
95
99
|
return record === undefined ? undefined : { ...record.info };
|
|
96
100
|
}
|
|
97
101
|
stats() {
|
|
98
|
-
|
|
99
|
-
return undefined;
|
|
100
|
-
const byStatus = {};
|
|
101
|
-
let running = 0;
|
|
102
|
-
for (const { info } of this.tasks.values()) {
|
|
103
|
-
if (info.status === "running")
|
|
104
|
-
running++;
|
|
105
|
-
else
|
|
106
|
-
byStatus[info.status] = (byStatus[info.status] ?? 0) + 1;
|
|
107
|
-
}
|
|
108
|
-
return { total: this.tasks.size, running, byStatus };
|
|
102
|
+
return taskStats(this.tasks.values());
|
|
109
103
|
}
|
|
110
104
|
/** 运行中任务的已有输出(`task_ctl output`);结束后为最终文本。 */
|
|
111
105
|
output(taskId) {
|
|
112
106
|
return this.tasks.get(taskId)?.text;
|
|
113
107
|
}
|
|
114
|
-
/**
|
|
115
|
-
async wait(taskId, timeoutMs) {
|
|
108
|
+
/** 等结束;超时或 signal(缺省本任务的转后台信号,`background()` 触发)返回 undefined。[W7-B1] */
|
|
109
|
+
async wait(taskId, timeoutMs, options = {}) {
|
|
116
110
|
const record = this.tasks.get(taskId);
|
|
117
111
|
if (record === undefined)
|
|
118
112
|
throw new AmaError("task_not_found", `unknown task ${taskId}`);
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
113
|
+
return waitForTask(record, timeoutMs, options.signal ?? this.waits.signal(taskId), this.waits);
|
|
114
|
+
}
|
|
115
|
+
/** [W7-B1] `task_ctl wait` 用的打断信号(`background()` 触发后换新)。 */
|
|
116
|
+
detachSignal(taskId) {
|
|
117
|
+
return this.waits.signal(taskId);
|
|
118
|
+
}
|
|
119
|
+
/** [W7-B1] 正在阻塞父回合的任务:运行中的前台任务与有 `task_ctl wait` 在等的任务。 */
|
|
120
|
+
blocking() {
|
|
121
|
+
return blockingTasks(this.tasks.values(), this.waits);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* [W7-B1] 前台任务转后台(§2.5,subagent-background.ts),并打断等它的 `task_ctl wait`;不给 taskId =
|
|
125
|
+
* 全部。返回被转后台或被打断等待的 taskId(不在运行时为空)。
|
|
126
|
+
*/
|
|
127
|
+
background(taskId, reason = "user") {
|
|
128
|
+
if (this.disposed)
|
|
129
|
+
return [];
|
|
130
|
+
const one = taskId === undefined ? undefined : this.tasks.get(taskId);
|
|
131
|
+
const records = taskId === undefined ? [...this.tasks.values()] : one === undefined ? [] : [one];
|
|
132
|
+
const moved = backgroundRecords(records, reason, {
|
|
133
|
+
emit: (event) => this.host.emit(event),
|
|
134
|
+
persist: (record) => this.persist(record),
|
|
135
|
+
notify: (record, result) => this.notifier.notify(record, result),
|
|
136
|
+
afterMs: this.env.autoBackgroundAfterMs ?? 0,
|
|
137
|
+
outputFile: (id) => this.outputFileFor(id),
|
|
124
138
|
});
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
139
|
+
return [...new Set([...moved, ...this.waits.detach(taskId)])];
|
|
140
|
+
}
|
|
141
|
+
/** [W7-B1] `-p` 收尾:等到没有运行中的任务、通知回合也已跑完;会话关闭立即返回。 */
|
|
142
|
+
settled() {
|
|
143
|
+
return settleTasks(this.tasks, this.notifier, () => this.disposed);
|
|
131
144
|
}
|
|
132
145
|
/** [W6-A] 子 Agent 视图读的实时数据;未知任务 undefined。 */
|
|
133
146
|
live(taskId) {
|
|
@@ -135,11 +148,11 @@ export class SubagentRegistry {
|
|
|
135
148
|
return record === undefined ? undefined : liveOf(record);
|
|
136
149
|
}
|
|
137
150
|
/** [W6-A] 人在子 Agent 视图里发的消息(subagent-direct.ts)。 */
|
|
138
|
-
async message(taskId, text) {
|
|
151
|
+
async message(taskId, text, interrupt = false) {
|
|
139
152
|
const record = this.tasks.get(taskId);
|
|
140
153
|
if (record === undefined)
|
|
141
154
|
throw new AmaError("task_not_found", `unknown task ${taskId}`);
|
|
142
|
-
return directMessage(record, text, this.host);
|
|
155
|
+
return directMessage(record, text, this.host, interrupt);
|
|
143
156
|
}
|
|
144
157
|
async stop(taskId) {
|
|
145
158
|
const record = this.tasks.get(taskId);
|
|
@@ -170,7 +183,7 @@ export class SubagentRegistry {
|
|
|
170
183
|
const model = resolveAgentModel(agent, request.model, this.env.modelConfig ?? {});
|
|
171
184
|
if (model.warning !== undefined)
|
|
172
185
|
this.host.log("warn", `agent ${name}: ${model.warning}`);
|
|
173
|
-
const background = request.background ?? agent.background;
|
|
186
|
+
const background = request.background ?? agent.background ?? this.env.background ?? false;
|
|
174
187
|
const taskId = `t${++this.seq}`;
|
|
175
188
|
const record = newRecord({
|
|
176
189
|
taskId,
|
|
@@ -197,7 +210,8 @@ export class SubagentRegistry {
|
|
|
197
210
|
const resumable = record.agent.runner === "ama" ? ref?.sessionFile !== undefined : ref !== undefined;
|
|
198
211
|
if (record.handle === undefined && !resumable)
|
|
199
212
|
return Promise.resolve(failed(`Task ${taskId} cannot be continued (its session was not saved).`));
|
|
200
|
-
|
|
213
|
+
const background = request.background ?? record.agent.background ?? this.env.background;
|
|
214
|
+
return this.launch(record, request, ama, background ?? false);
|
|
201
215
|
}
|
|
202
216
|
launch(record, request, ama, background) {
|
|
203
217
|
const controller = new AbortController();
|
|
@@ -217,33 +231,35 @@ export class SubagentRegistry {
|
|
|
217
231
|
else
|
|
218
232
|
parentSignal?.addEventListener("abort", onParentAbort, { once: true });
|
|
219
233
|
this.persist(record);
|
|
234
|
+
let timer;
|
|
220
235
|
const running = this.execute(record, request, ama, controller.signal).finally(() => {
|
|
236
|
+
clearTimeout(timer);
|
|
221
237
|
parentSignal?.removeEventListener("abort", onParentAbort);
|
|
238
|
+
delete record.foregroundWaiter;
|
|
222
239
|
delete record.running;
|
|
223
240
|
delete record.controller;
|
|
224
241
|
if (!this.disposed)
|
|
225
242
|
drainDirect(record, this.host);
|
|
226
243
|
});
|
|
227
244
|
record.running = running;
|
|
228
|
-
if (!background)
|
|
229
|
-
return running;
|
|
230
|
-
void running.then((result) => this.notify(record, result));
|
|
231
|
-
return Promise.resolve(this.startedResult(record));
|
|
232
|
-
}
|
|
233
|
-
startedResult(record) {
|
|
234
|
-
const result = {
|
|
235
|
-
text: `Started background task ${record.info.taskId} (agent ${record.agent.name}). ` +
|
|
236
|
-
"A <task-notification> arrives when it finishes; use task_ctl to wait, stop or read output.",
|
|
237
|
-
usage: { ...ZERO_USAGE },
|
|
238
|
-
stopReason: "stop",
|
|
239
|
-
isError: false,
|
|
240
|
-
taskId: record.info.taskId,
|
|
241
|
-
status: "running",
|
|
242
|
-
};
|
|
243
245
|
const file = this.outputFileFor(record.info.taskId);
|
|
244
|
-
if (
|
|
245
|
-
result.
|
|
246
|
-
|
|
246
|
+
if (background) {
|
|
247
|
+
void running.then((result) => this.notifier.notify(record, result));
|
|
248
|
+
return Promise.resolve(startedResult(record, file));
|
|
249
|
+
}
|
|
250
|
+
// [W7-B1] 前台:转后台(background())时等待者先行 resolve,工具调用立即返回
|
|
251
|
+
const { waiter, promise } = foregroundWaiter(() => {
|
|
252
|
+
clearTimeout(timer);
|
|
253
|
+
parentSignal?.removeEventListener("abort", onParentAbort);
|
|
254
|
+
delete record.onUpdate;
|
|
255
|
+
});
|
|
256
|
+
record.foregroundWaiter = waiter;
|
|
257
|
+
const after = this.env.autoBackgroundAfterMs ?? 0;
|
|
258
|
+
if (after > 0) {
|
|
259
|
+
timer = setTimeout(() => this.background(record.info.taskId, "timeout"), after);
|
|
260
|
+
timer.unref?.();
|
|
261
|
+
}
|
|
262
|
+
return Promise.race([running, promise]);
|
|
247
263
|
}
|
|
248
264
|
async execute(record, request, ama, signal) {
|
|
249
265
|
record.queued = true;
|
|
@@ -332,21 +348,7 @@ export class SubagentRegistry {
|
|
|
332
348
|
return handle;
|
|
333
349
|
}
|
|
334
350
|
emitStart(record, handle) {
|
|
335
|
-
|
|
336
|
-
type: "subagent_start",
|
|
337
|
-
taskId: record.info.taskId,
|
|
338
|
-
parentToolCallId: record.parentToolCallId,
|
|
339
|
-
agent: record.agent.name,
|
|
340
|
-
runner: record.agent.runner,
|
|
341
|
-
description: record.info.description,
|
|
342
|
-
background: record.info.background,
|
|
343
|
-
cwd: record.cwd,
|
|
344
|
-
};
|
|
345
|
-
if (handle.model !== undefined)
|
|
346
|
-
event.model = handle.model;
|
|
347
|
-
if (handle.sessionFile !== undefined)
|
|
348
|
-
event.sessionFile = handle.sessionFile;
|
|
349
|
-
this.host.emit(event);
|
|
351
|
+
this.host.emit(startEvent(record, handle));
|
|
350
352
|
}
|
|
351
353
|
sink() {
|
|
352
354
|
return {
|
|
@@ -425,36 +427,6 @@ export class SubagentRegistry {
|
|
|
425
427
|
this.retain(info.taskId);
|
|
426
428
|
return final;
|
|
427
429
|
}
|
|
428
|
-
notify(record, result) {
|
|
429
|
-
// 被停止的任务(task_ctl stop、会话关闭)不再通知:发起停止的一方已经知道
|
|
430
|
-
if (this.disposed || result.status === "aborted")
|
|
431
|
-
return;
|
|
432
|
-
const info = record.info;
|
|
433
|
-
const text = taskNotification({
|
|
434
|
-
taskId: info.taskId,
|
|
435
|
-
agent: info.agent,
|
|
436
|
-
status: (result.status ?? "completed"),
|
|
437
|
-
...(info.turns === undefined ? {} : { turns: info.turns }),
|
|
438
|
-
...(info.usage === undefined ? {} : { usage: info.usage }),
|
|
439
|
-
...(info.outputFile === undefined ? {} : { outputFile: info.outputFile }),
|
|
440
|
-
report: result.text,
|
|
441
|
-
});
|
|
442
|
-
const host = this.host;
|
|
443
|
-
const followUp = host.followUp;
|
|
444
|
-
if (followUp === undefined) {
|
|
445
|
-
host.log("warn", `task ${info.taskId} finished but the session cannot be notified`);
|
|
446
|
-
return;
|
|
447
|
-
}
|
|
448
|
-
// 父空闲时投递(开新回合);父正忙则等这一周期结束再投——周期收尾阶段入队的 followUp 会滞留到
|
|
449
|
-
// 下一次提示。串行投递保证按完成顺序到达。
|
|
450
|
-
this.delivery = this.delivery
|
|
451
|
-
.then(async () => {
|
|
452
|
-
await host.waitForIdle?.();
|
|
453
|
-
if (!this.disposed)
|
|
454
|
-
await followUp.call(host, text, { origin: "task" });
|
|
455
|
-
})
|
|
456
|
-
.catch((error) => host.log("warn", `task notification failed: ${String(error)}`));
|
|
457
|
-
}
|
|
458
430
|
// -------------------------------------------------------------------------
|
|
459
431
|
// 落盘、保留与重建
|
|
460
432
|
// -------------------------------------------------------------------------
|
package/dist/agent/types-w5.d.ts
CHANGED
|
@@ -80,6 +80,16 @@ export interface SubagentEndEvent {
|
|
|
80
80
|
changed: boolean;
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* [W7-B1] 前台任务转后台(docs/agents-concurrency-plan.md §2.5):`user` = 人(TUI `Ctrl+B` 等),
|
|
85
|
+
* `timeout` = `subagents.autoBackgroundAfterMs` 到时,`host` = 宿主 / SDK(`session.backgroundTask`)。
|
|
86
|
+
*/
|
|
87
|
+
export interface SubagentBackgroundEvent {
|
|
88
|
+
type: "subagent_background";
|
|
89
|
+
taskId: string;
|
|
90
|
+
parentToolCallId: string;
|
|
91
|
+
reason: "user" | "timeout" | "host";
|
|
92
|
+
}
|
|
83
93
|
export interface PlanStep {
|
|
84
94
|
/** `S1`、`S2` …;编号列表时按序号生成。 */
|
|
85
95
|
id: string;
|
|
@@ -161,7 +171,7 @@ export type SessionEventW5 =
|
|
|
161
171
|
/** 流式中 ≤ 2 Hz(`ui.animation: false` 时不发);数据从 `getStats().telemetry` 取。 */
|
|
162
172
|
{
|
|
163
173
|
type: "telemetry_tick";
|
|
164
|
-
} | SubagentStartEvent | SubagentUpdateEvent | SubagentEndEvent | PlanProposedEvent | PlanResolvedEvent | TodoUpdatedEvent | LimitReachedEvent | ModelFallbackEvent | BackgroundJobEvent;
|
|
174
|
+
} | SubagentStartEvent | SubagentUpdateEvent | SubagentEndEvent | SubagentBackgroundEvent | PlanProposedEvent | PlanResolvedEvent | TodoUpdatedEvent | LimitReachedEvent | ModelFallbackEvent | BackgroundJobEvent;
|
|
165
175
|
/** 外部 Agent 记账(`custom{ama.agent-usage}` 汇总,§5.4)。 */
|
|
166
176
|
export interface ExternalAgentStats {
|
|
167
177
|
byAgent: Record<string, {
|
package/dist/agent/types.d.ts
CHANGED
|
@@ -303,11 +303,18 @@ export interface PromptOptions {
|
|
|
303
303
|
streamingBehavior?: "steer" | "followUp";
|
|
304
304
|
/** 落盘到 user 消息的 origin(宿主注入为 "host");缺省:空闲时不填,入队时为 streamingBehavior。 */
|
|
305
305
|
origin?: MessageOrigin;
|
|
306
|
+
/**
|
|
307
|
+
* 打断并立即发送:运行中先取走排队的 steer、中止当前回合(工具按 abort 收尾),再以「steer… + 本条」
|
|
308
|
+
* 开新回合(origin 缺省 `"interrupt"`);空闲时等同不填。优先于 `streamingBehavior`。
|
|
309
|
+
*/
|
|
310
|
+
interrupt?: boolean;
|
|
306
311
|
}
|
|
307
312
|
/** steer / followUp 的可选项。 */
|
|
308
313
|
export interface EnqueueOptions {
|
|
309
314
|
/** 缺省为 "steer" / "followUp";`"user"` 表示普通用户输入(不写 origin)。 */
|
|
310
315
|
origin?: MessageOrigin;
|
|
316
|
+
/** 打断并立即发送(同 `PromptOptions.interrupt`;运行中 origin 缺省 `"interrupt"`)。 */
|
|
317
|
+
interrupt?: boolean;
|
|
311
318
|
}
|
|
312
319
|
export interface SessionState {
|
|
313
320
|
isStreaming: boolean;
|
|
@@ -397,6 +404,11 @@ export interface AgentSession {
|
|
|
397
404
|
steering: string[];
|
|
398
405
|
followUp: string[];
|
|
399
406
|
};
|
|
407
|
+
/**
|
|
408
|
+
* [W7-B1] 阻塞中的前台子 Agent 任务转后台(不给 taskId = 全部;docs/agents-concurrency-plan.md §2.5)。
|
|
409
|
+
* `reason` 缺省 `"host"`(宿主 / SDK);TUI 的人工操作传 `"user"`。返回被转后台或被打断等待的 taskId。
|
|
410
|
+
*/
|
|
411
|
+
backgroundTask(taskId?: string, reason?: "user" | "timeout" | "host"): string[];
|
|
400
412
|
subscribe(listener: (event: SessionEvent) => void): () => void;
|
|
401
413
|
compact(instructions?: string): Promise<CompactionResult>;
|
|
402
414
|
fork(entryId: string): Promise<AgentSession>;
|
package/dist/agents/builtin.js
CHANGED
package/dist/agents/external.js
CHANGED
package/dist/agents/parse.js
CHANGED
|
@@ -85,8 +85,8 @@ export function parseAgentDefinition(text, filePath, source) {
|
|
|
85
85
|
const isolation = data["isolation"] ?? "none";
|
|
86
86
|
if (isolation !== "none" && isolation !== "worktree")
|
|
87
87
|
return fail("isolation must be none or worktree");
|
|
88
|
-
const background = data["background"]
|
|
89
|
-
if (typeof background !== "boolean")
|
|
88
|
+
const background = data["background"];
|
|
89
|
+
if (background !== undefined && typeof background !== "boolean")
|
|
90
90
|
return fail("background must be true or false");
|
|
91
91
|
if (runner !== "ama") {
|
|
92
92
|
const ignored = ["tools", "disallowed-tools", "permission-mode"].filter((key) => data[key] !== undefined);
|
|
@@ -100,12 +100,13 @@ export function parseAgentDefinition(text, filePath, source) {
|
|
|
100
100
|
model: model.trim(),
|
|
101
101
|
maxTurns,
|
|
102
102
|
isolation,
|
|
103
|
-
background,
|
|
104
103
|
runner,
|
|
105
104
|
prompt: fm.body.trim(),
|
|
106
105
|
source,
|
|
107
106
|
filePath,
|
|
108
107
|
};
|
|
108
|
+
if (background !== undefined)
|
|
109
|
+
agent.background = background;
|
|
109
110
|
if (tools !== undefined)
|
|
110
111
|
agent.tools = tools;
|
|
111
112
|
if (disallowedTools !== undefined)
|
package/dist/agents/result.d.ts
CHANGED
|
@@ -28,5 +28,11 @@ export interface NotificationInput {
|
|
|
28
28
|
report: string;
|
|
29
29
|
}
|
|
30
30
|
export declare function taskNotification(input: NotificationInput): string;
|
|
31
|
+
export declare const NOTIFY_HINT = "A <task-notification> arrives when it finishes; use task_ctl to wait, stop or read output.";
|
|
32
|
+
export declare function startedText(taskId: string, agent: string): string;
|
|
33
|
+
/** 前台转后台的工具结果(§2.5.3):固定英文;task 工具在前面加 `[task tN] `。 */
|
|
34
|
+
export declare function backgroundedText(reason: "user" | "timeout" | "host", afterMs: number): string;
|
|
35
|
+
/** `task_ctl wait` 被转后台打断时的文案(§2.5.4)。 */
|
|
36
|
+
export declare function waitDetachedText(taskId: string): string;
|
|
31
37
|
/** 系统提示 rules 节的一句(task 工具的 promptGuidelines;会话开始即固定)。 */
|
|
32
|
-
export declare const TASK_NOTIFICATION_RULE
|
|
38
|
+
export declare const TASK_NOTIFICATION_RULE: string;
|
package/dist/agents/result.js
CHANGED
|
@@ -63,5 +63,24 @@ export function taskNotification(input) {
|
|
|
63
63
|
attrs.push(`outputFile="${attr(input.outputFile)}"`);
|
|
64
64
|
return `<task-notification ${attrs.join(" ")}>\n${input.report}\n</task-notification>`;
|
|
65
65
|
}
|
|
66
|
+
// [W7-B1] 后台任务的固定英文文案(docs/agents-concurrency-plan.md §2.5;工具结果不进缓存前缀,但要
|
|
67
|
+
// 稳定,便于测试与模型学习)。
|
|
68
|
+
export const NOTIFY_HINT = "A <task-notification> arrives when it finishes; use task_ctl to wait, stop or read output.";
|
|
69
|
+
export function startedText(taskId, agent) {
|
|
70
|
+
return `Started background task ${taskId} (agent ${agent}). ${NOTIFY_HINT}`;
|
|
71
|
+
}
|
|
72
|
+
/** 前台转后台的工具结果(§2.5.3):固定英文;task 工具在前面加 `[task tN] `。 */
|
|
73
|
+
export function backgroundedText(reason, afterMs) {
|
|
74
|
+
const head = reason === "timeout"
|
|
75
|
+
? `Still running after ${Math.round(afterMs / 1000)}s and moved to the background`
|
|
76
|
+
: "Moved to the background by the user";
|
|
77
|
+
return `${head}; it was not interrupted. ${NOTIFY_HINT} Do not wait for it unless the user asks.`;
|
|
78
|
+
}
|
|
79
|
+
/** `task_ctl wait` 被转后台打断时的文案(§2.5.4)。 */
|
|
80
|
+
export function waitDetachedText(taskId) {
|
|
81
|
+
return (`Task ${taskId} is still running and was moved to the background by the user; do not wait ` +
|
|
82
|
+
"again — a <task-notification> arrives when it finishes.");
|
|
83
|
+
}
|
|
66
84
|
/** 系统提示 rules 节的一句(task 工具的 promptGuidelines;会话开始即固定)。 */
|
|
67
|
-
export const TASK_NOTIFICATION_RULE = "A <task-notification>
|
|
85
|
+
export const TASK_NOTIFICATION_RULE = "A <task-notification> is a background task's report, not the user speaking; never sleep or " +
|
|
86
|
+
"poll for it.";
|
|
@@ -8,9 +8,20 @@ import type { SubagentResult, TaskRegistryView } from "../tools/types.js";
|
|
|
8
8
|
export interface TaskControl extends TaskRegistryView {
|
|
9
9
|
/** 运行中是已有输出,结束后是最终文本。 */
|
|
10
10
|
output(taskId: string): string | undefined;
|
|
11
|
-
/**
|
|
12
|
-
|
|
11
|
+
/**
|
|
12
|
+
* 等结束、超时或 `signal` 触发(后两者返回 undefined);未知 taskId 抛 `task_not_found`。
|
|
13
|
+
* [W7-B1] 不给 signal 时由 `background()` 打断(见 `detachSignal`)。
|
|
14
|
+
*/
|
|
15
|
+
wait(taskId: string, timeoutMs: number, options?: {
|
|
16
|
+
signal?: AbortSignal;
|
|
17
|
+
}): Promise<SubagentResult | undefined>;
|
|
13
18
|
stop(taskId: string): Promise<SubagentResult | undefined>;
|
|
19
|
+
/** [W7-B1] 前台任务转后台(不给 taskId = 全部);返回被转后台或被打断等待的 taskId。 */
|
|
20
|
+
background(taskId?: string): string[];
|
|
21
|
+
/** [W7-B1] 该任务的转后台信号:`background()` 触发后 abort(下次取到新的)。 */
|
|
22
|
+
detachSignal(taskId: string): AbortSignal;
|
|
23
|
+
/** [W7-B1] 正在阻塞父回合的任务(前台运行中、或有 `task_ctl wait` 在等)。 */
|
|
24
|
+
blocking(): string[];
|
|
14
25
|
}
|
|
15
26
|
export declare function registerTaskControl(sessionId: string, control: TaskControl): void;
|
|
16
27
|
export declare function unregisterTaskControl(sessionId: string, control: TaskControl): void;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* `custom{ama.task}` 快照的写入形状与 resume 重建(docs/wave5-plan.md §7.4–§7.5)。[W5-G]
|
|
4
4
|
* 编排在 agent/subagent-registry.ts。
|
|
5
5
|
*/
|
|
6
|
-
import type { SubagentUpdateEvent } from "../agent/types-w5.js";
|
|
6
|
+
import type { SessionTaskStats, SubagentStartEvent, SubagentUpdateEvent } from "../agent/types-w5.js";
|
|
7
7
|
import type { MessageOrigin } from "../ai/types.js";
|
|
8
8
|
import type { SessionEvent } from "../agent/types.js";
|
|
9
9
|
import type { TraceExternalTurnData } from "../trace/types.js";
|
|
@@ -67,10 +67,10 @@ export type TaskHandle = RunnerHandle & {
|
|
|
67
67
|
recent?(): readonly ExternalDisplayEvent[];
|
|
68
68
|
/**
|
|
69
69
|
* [W6-A] 人在子 Agent 视图里直接发的消息(子会话 user 消息 `origin: "direct"`):`followUp` = 本轮运行中
|
|
70
|
-
* 排到回合结束(不在运行中抛 `task_idle`,由注册表排队到运行结束后续聊);`send` = 空闲时开新一轮(同 `send
|
|
71
|
-
* ama 子会话提供。
|
|
70
|
+
* 排到回合结束(不在运行中抛 `task_idle`,由注册表排队到运行结束后续聊);`send` = 空闲时开新一轮(同 `send`);
|
|
71
|
+
* `interrupt` = 打断子会话当前回合并立即以它开新回合(不在运行中同样抛 `task_idle`)。ama 子会话提供。
|
|
72
72
|
*/
|
|
73
|
-
message?(text: string, when: "followUp" | "send"): Promise<void>;
|
|
73
|
+
message?(text: string, when: "followUp" | "send" | "interrupt"): Promise<void>;
|
|
74
74
|
};
|
|
75
75
|
/** [W6-A] 外部 Agent 环形缓冲的上限(docs/wave6-plan.md D2)。 */
|
|
76
76
|
export declare const RING_MAX_EVENTS = 2000;
|
|
@@ -130,6 +130,14 @@ export interface TaskRecord {
|
|
|
130
130
|
direct?: string[];
|
|
131
131
|
/** [W6-A] 下一次续聊是人在视图里发的(子会话 user 消息记 `origin: "direct"`)。 */
|
|
132
132
|
directNext?: boolean;
|
|
133
|
+
/**
|
|
134
|
+
* [W7-B1] 前台等待者:launch 时建,转后台时 resolve(工具调用先返回);结束时删掉。
|
|
135
|
+
* `detachParent` 解绑父 signal 与进度回调(父回合 Esc 不再连带停止它)。
|
|
136
|
+
*/
|
|
137
|
+
foregroundWaiter?: {
|
|
138
|
+
resolve(result: SubagentResult): void;
|
|
139
|
+
detachParent(): void;
|
|
140
|
+
};
|
|
133
141
|
}
|
|
134
142
|
export declare function newRecord(info: TaskInfo, agent: AgentDefinition, parentToolCallId: string, cwd: string, isolation?: "none" | "worktree"): TaskRecord;
|
|
135
143
|
export interface ProgressSink {
|
|
@@ -142,6 +150,10 @@ export interface ProgressSink {
|
|
|
142
150
|
export declare function flushText(record: TaskRecord, sink: ProgressSink): void;
|
|
143
151
|
/** runner 进度 → 记录与 `subagent_update`(文本 ≥ 250 ms 合并,轮次与结束时冲刷)。 */
|
|
144
152
|
export declare function applyRunnerEvent(record: TaskRecord, event: SubagentEvent, sink: ProgressSink): void;
|
|
153
|
+
/** `subagent_start` 事件(新开或续聊都发)。 */
|
|
154
|
+
export declare function startEvent(record: TaskRecord, handle: TaskHandle): SubagentStartEvent;
|
|
155
|
+
/** `getStats().tasks`:没有任务时 undefined。 */
|
|
156
|
+
export declare function taskStats(records: Iterable<TaskRecord>): SessionTaskStats | undefined;
|
|
145
157
|
/** 父会话 `custom{ama.task}` 的 data:TaskInfo + 父 toolCallId + 运行目录(带 `status`)。 */
|
|
146
158
|
export declare function recordData(record: TaskRecord): Record<string, unknown>;
|
|
147
159
|
/** 从父会话分支重建(同一 taskId 取最后一条;未结束的标 interrupted);返回最大序号。 */
|
|
@@ -209,6 +209,38 @@ function recordDisplay(record, event, now) {
|
|
|
209
209
|
return;
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
|
+
/** `subagent_start` 事件(新开或续聊都发)。 */
|
|
213
|
+
export function startEvent(record, handle) {
|
|
214
|
+
const event = {
|
|
215
|
+
type: "subagent_start",
|
|
216
|
+
taskId: record.info.taskId,
|
|
217
|
+
parentToolCallId: record.parentToolCallId,
|
|
218
|
+
agent: record.agent.name,
|
|
219
|
+
runner: record.agent.runner,
|
|
220
|
+
description: record.info.description,
|
|
221
|
+
background: record.info.background,
|
|
222
|
+
cwd: record.cwd,
|
|
223
|
+
};
|
|
224
|
+
if (handle.model !== undefined)
|
|
225
|
+
event.model = handle.model;
|
|
226
|
+
if (handle.sessionFile !== undefined)
|
|
227
|
+
event.sessionFile = handle.sessionFile;
|
|
228
|
+
return event;
|
|
229
|
+
}
|
|
230
|
+
/** `getStats().tasks`:没有任务时 undefined。 */
|
|
231
|
+
export function taskStats(records) {
|
|
232
|
+
const byStatus = {};
|
|
233
|
+
let total = 0;
|
|
234
|
+
let running = 0;
|
|
235
|
+
for (const { info } of records) {
|
|
236
|
+
total++;
|
|
237
|
+
if (info.status === "running")
|
|
238
|
+
running++;
|
|
239
|
+
else
|
|
240
|
+
byStatus[info.status] = (byStatus[info.status] ?? 0) + 1;
|
|
241
|
+
}
|
|
242
|
+
return total === 0 ? undefined : { total, running, byStatus };
|
|
243
|
+
}
|
|
212
244
|
/** 父会话 `custom{ama.task}` 的 data:TaskInfo + 父 toolCallId + 运行目录(带 `status`)。 */
|
|
213
245
|
export function recordData(record) {
|
|
214
246
|
return { ...record.info, parentToolCallId: record.parentToolCallId, cwd: record.cwd };
|
package/dist/agents/types.d.ts
CHANGED
|
@@ -25,7 +25,11 @@ export interface AgentDefinition {
|
|
|
25
25
|
/** 缺省 30。 */
|
|
26
26
|
maxTurns: number;
|
|
27
27
|
isolation: "none" | "worktree";
|
|
28
|
-
|
|
28
|
+
/**
|
|
29
|
+
* 显式覆盖缺省前台 / 后台;不设由 `subagents.background` 决定([W7-B1],优先级:调用参数 > 类型
|
|
30
|
+
* 定义 > 配置)。
|
|
31
|
+
*/
|
|
32
|
+
background?: boolean;
|
|
29
33
|
runner: AgentRunnerSpec;
|
|
30
34
|
/** 正文:追加到子会话系统提示末尾的角色说明。 */
|
|
31
35
|
prompt: string;
|
|
@@ -20,18 +20,23 @@ function obj(value) {
|
|
|
20
20
|
? value
|
|
21
21
|
: undefined;
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* 一个窗口;时长 ≤ 0 视同缺失。[W7] 用量 0、无时长、无重置时间的窗口是服务端的「没有这个窗口」(套餐只有一个
|
|
25
|
+
* 窗口时另一个槽位送全 0),返回 undefined——0.6.3 把它当成窗口渲染出 `0d: 0.0%`。
|
|
26
|
+
*/
|
|
23
27
|
function window(used, minutes, resetSeconds) {
|
|
24
28
|
const usedPercent = num(used);
|
|
25
29
|
if (usedPercent === undefined)
|
|
26
30
|
return undefined;
|
|
27
31
|
const out = { usedPercent: Math.max(0, Math.min(100, usedPercent)) };
|
|
28
32
|
const m = num(minutes);
|
|
29
|
-
if (m !== undefined)
|
|
33
|
+
if (m !== undefined && m > 0)
|
|
30
34
|
out.windowMinutes = m;
|
|
31
35
|
const reset = num(resetSeconds);
|
|
32
36
|
if (reset !== undefined && reset > 0)
|
|
33
37
|
out.resetsAt = reset * 1000;
|
|
34
|
-
|
|
38
|
+
const empty = out.usedPercent === 0 && out.windowMinutes === undefined && out.resetsAt === undefined;
|
|
39
|
+
return empty ? undefined : out;
|
|
35
40
|
}
|
|
36
41
|
function snapshot(primary, secondary, plan) {
|
|
37
42
|
const out = {};
|
|
@@ -3,9 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 模型表为空、接受任意 slug 的供应商(`chatgpt` 的订阅后端)在选择器里原本一个模型都看不到。
|
|
5
5
|
* `ama auth login chatgpt` 成功后与 `ama models discover chatgpt` 把账户可用的 slug 写进这里;组装注册表时
|
|
6
|
-
* (`mergeDiscoveredModels
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* (`mergeDiscoveredModels`)并入**模型表仍为空**的供应商。`ama auth logout chatgpt` 删掉它。只存 slug、显示名、
|
|
7
|
+
* 后端给的元数据(上下文窗口、输入模态、推理强度)、flavor 与时间戳——没有 token、账户 id。
|
|
8
|
+
*
|
|
9
|
+
* 元数据优先级:后端明确给的 > models.dev(快照 ⊕ 用户刷新)> 自定义缺省。上下文窗口尤其如此:订阅后端的生效
|
|
10
|
+
* 窗口(如 codex 的 272k)可能远小于 models.dev 记的 API 版窗口(1.1M),按后者算压缩阈值会让请求超窗被拒。
|
|
11
|
+
* 旧版本写的缓存(siwc 不含上下文窗口)照旧用 models.dev;`chatgpt` 两边都没有时用保守缺省
|
|
12
|
+
* `SUBSCRIPTION_FALLBACK_CONTEXT_WINDOW`,不让自动压缩关闭。
|
|
9
13
|
*
|
|
10
14
|
* ChatGPT 的缓存带登录 flavor;组装时与当前登录的 flavor 不符视为过期、不并入(选择器提示重新发现)。
|
|
11
15
|
*/
|
|
@@ -44,7 +48,9 @@ export declare function writeDiscoveredCache(dataDir: string, provider: string,
|
|
|
44
48
|
export declare function clearDiscoveredCache(dataDir: string, provider: string): boolean;
|
|
45
49
|
/** 后端给的推理强度 → 思考级别映射(`none` 对应 off;没列出的级别不支持)。 */
|
|
46
50
|
export declare function thinkingMapOf(levels: readonly string[]): Partial<Record<ModelThinkingLevel, string | null>>;
|
|
47
|
-
/**
|
|
51
|
+
/** 订阅后端:models.dev 与后端都没给上下文窗口时的保守缺省。 */
|
|
52
|
+
export declare const SUBSCRIPTION_FALLBACK_CONTEXT_WINDOW = 128000;
|
|
53
|
+
/** 缓存里的模型 → 注册表模型(后端给的元数据优先,其次 models.dev,再次自定义缺省)。 */
|
|
48
54
|
export declare function discoveredModels(file: DiscoveredCacheFile, provider: ProviderData, modelsDev: () => ModelsDevIndex | undefined): Model[];
|
|
49
55
|
/** 注册表里能追加模型的最小接口(`ProviderRegistry.addModels`)。 */
|
|
50
56
|
export interface DiscoveredTarget {
|