@armadra/agent 0.6.3 → 0.6.5
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 +29 -0
- package/CHANGELOG.zh-CN.md +21 -0
- 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 +5 -0
- package/dist/agent/session.js +21 -2
- package/dist/agent/subagent-direct.d.ts +5 -2
- package/dist/agent/subagent-direct.js +34 -1
- package/dist/agent/subagent-registry.d.ts +2 -1
- package/dist/agent/subagent-registry.js +2 -2
- package/dist/agent/types.d.ts +7 -0
- package/dist/agents/task-record.d.ts +3 -3
- package/dist/ai/apis/chatgpt-rate-limits.js +7 -2
- package/dist/ai/types.d.ts +2 -1
- package/dist/bundle/ama.cjs +417 -106
- package/dist/config/json-schema.js +1 -0
- package/dist/config/key-docs.js +8 -1
- package/dist/config/schema.js +2 -0
- package/dist/config/settings-registry.js +1 -0
- package/dist/config/types.d.ts +2 -0
- package/dist/drivers/acp/client.d.ts +13 -4
- package/dist/drivers/acp/client.js +17 -6
- package/dist/drivers/acp/types.d.ts +22 -2
- package/dist/drivers/runner.js +29 -2
- package/dist/i18n/catalog.d.ts +13 -0
- package/dist/i18n/messages/agents.d.ts +4 -0
- package/dist/i18n/messages/agents.js +4 -0
- package/dist/i18n/messages/config-keys.d.ts +2 -0
- package/dist/i18n/messages/config-keys.js +2 -0
- package/dist/i18n/messages/config.d.ts +2 -0
- 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 +10 -0
- package/dist/i18n/messages/interactive.js +6 -0
- package/dist/i18n/messages/report.d.ts +8 -0
- package/dist/i18n/messages/report.js +8 -0
- package/dist/i18n/messages/settings.d.ts +2 -0
- package/dist/i18n/messages/settings.js +2 -0
- package/dist/modes/commands-core.js +11 -0
- package/dist/modes/interactive/agent-ui.js +1 -0
- package/dist/modes/interactive/agent-view.d.ts +5 -0
- package/dist/modes/interactive/agent-view.js +14 -3
- package/dist/modes/interactive/interactive-mode.js +13 -1
- 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 +8 -1
- package/dist/modes/interactive/key-dispatch.js +37 -0
- package/dist/modes/interactive/line/line-mode.d.ts +1 -0
- package/dist/modes/interactive/line/line-mode.js +29 -7
- package/dist/modes/interactive/run-indicator.d.ts +19 -2
- package/dist/modes/interactive/run-indicator.js +29 -7
- package/dist/modes/interactive/status-bar.d.ts +4 -1
- package/dist/modes/interactive/status-bar.js +17 -6
- package/dist/modes/interactive/status-line.d.ts +10 -9
- package/dist/modes/interactive/status-line.js +46 -38
- package/dist/modes/interactive/status-quota.d.ts +16 -2
- package/dist/modes/interactive/status-quota.js +54 -34
- package/dist/modes/interactive/task-background.d.ts +2 -2
- package/dist/modes/rpc/commands.d.ts +2 -0
- package/dist/modes/rpc/commands.js +15 -1
- package/dist/rpc.d.ts +7 -0
- package/dist/tools/types.d.ts +6 -0
- package/dist/tui/keybindings.d.ts +7 -0
- package/dist/tui/keybindings.js +7 -0
- package/docs/acp.md +3 -0
- package/docs/en/rpc.md +9 -7
- package/docs/en/tui.md +49 -31
- package/docs/rpc.md +9 -7
- package/docs/session-format.md +1 -1
- package/docs/tui.md +49 -31
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,35 @@ English · [简体中文](CHANGELOG.zh-CN.md)
|
|
|
5
5
|
> This file is in English starting with 0.6.0. Release notes for 0.1 through 0.5.1 are in Chinese in
|
|
6
6
|
> [CHANGELOG.zh-CN.md](CHANGELOG.zh-CN.md). New entries go into both files.
|
|
7
7
|
|
|
8
|
+
## 0.6.5 (2026-10-03)
|
|
9
|
+
|
|
10
|
+
- **`AcpClient` can pass MCP servers when opening a session**: `newSession`, `resumeSession` and `loadSession` take an
|
|
11
|
+
optional third argument `{ mcpServers }` that is forwarded as-is in `session/new|resume|load` (default still `[]`, the
|
|
12
|
+
wire is unchanged when it is omitted). `AcpClient.features.mcpServers` lets a host detect support. ama itself still
|
|
13
|
+
sends none. Docs: docs/acp.md "As a client".
|
|
14
|
+
|
|
15
|
+
## 0.6.4 (2026-10-03)
|
|
16
|
+
|
|
17
|
+
- **Interrupt and send now**: while a run is in progress, Enter still queues the message as a steer (delivered at the next
|
|
18
|
+
delivery point), and the new `Ctrl+X` (key action `app.message.interrupt`) stops the current turn at once (model stream and
|
|
19
|
+
running tools; every tool call keeps exactly one result, `aborted by user`) and immediately starts a new turn with the
|
|
20
|
+
input, queued steers joined in front (`origin: "interrupt"`, `↳ interrupt` in the message area); with an empty input it
|
|
21
|
+
sends the queued steers now. Esc is unchanged. `ui.enterWhileRunning: "queue" | "interrupt"` (default queue, editable in
|
|
22
|
+
`/config`) swaps Enter and `Ctrl+X`. The running line shows `Enter queue · Ctrl+X interrupt & send` while the input box has
|
|
23
|
+
text. The sub-agent view supports the same key: an ama sub-agent stops its turn and starts a new one with the message; an
|
|
24
|
+
external agent is interrupted when its driver can cancel a single turn (ACP, Claude stream-json, Codex) and otherwise the
|
|
25
|
+
message is queued with a hint. Line mode accepts `/interrupt <text>`; RPC `prompt` / `steer` and the SDK take
|
|
26
|
+
`interrupt: true`. The new request keeps the interrupted request as its prefix, so the cache keeps hitting. Docs:
|
|
27
|
+
docs/tui.md, docs/rpc.md.
|
|
28
|
+
- **Status line split into sides; quota labels fixed**: the `full` layout now keeps state and switches on the left and
|
|
29
|
+
metrics and the model on the right. The rate line's left side holds `codemode on` (with `net!`) · sandbox · preset ·
|
|
30
|
+
`→ fallback model` · queue count · host status, and its right side `tps … (avg · ttft) · ↑ ↓ · cache · re-billing · [-]`;
|
|
31
|
+
the status bar keeps the permission mode and the `shift+tab` hint on the left; the quota line is right-aligned. When
|
|
32
|
+
narrow, right-side metrics drop before left-side switches; the permission mode, `tps` and `[-]` never drop; the
|
|
33
|
+
single-line `compact` layout keeps its token order. Quota labels follow the window length: a weekly window sent as
|
|
34
|
+
primary reads `Weekly` instead of `7d:`, and the 5-hour window comes first; the all-zero "no such window" the server
|
|
35
|
+
sends is no longer rendered as `0d: 0.0%` (filtered both when parsing and when rendering). Docs: docs/tui.md.
|
|
36
|
+
|
|
8
37
|
## 0.6.3 (2026-10-03)
|
|
9
38
|
|
|
10
39
|
- **TUI: `Ctrl+B` moves foreground tasks to the background; background approvals dock in the agent bar**: while the main
|
package/CHANGELOG.zh-CN.md
CHANGED
|
@@ -4,6 +4,27 @@
|
|
|
4
4
|
|
|
5
5
|
> 从 0.6.0 起 [CHANGELOG.md](CHANGELOG.md) 为英文,本文件保留中文记录(0.1–0.5.1 的完整历史在此)。新条目两份都要加。
|
|
6
6
|
|
|
7
|
+
## 0.6.5(2026-10-03)
|
|
8
|
+
|
|
9
|
+
- **`AcpClient` 开会话可传 MCP 服务器**:`newSession`、`resumeSession`、`loadSession` 新增可选的第三个参数
|
|
10
|
+
`{ mcpServers }`,原样随 `session/new|resume|load` 发出(缺省仍是 `[]`,不传时线路不变);宿主用
|
|
11
|
+
`AcpClient.features.mcpServers` 检测是否支持。ama 自己仍不传。文档:docs/acp.md「作为客户端」。
|
|
12
|
+
|
|
13
|
+
## 0.6.4(2026-10-03)
|
|
14
|
+
|
|
15
|
+
- **打断并立即发送**:运行中按 Enter 仍是排队插话(等下一个投递点送达),新增 `Ctrl+X`(键位动作 `app.message.interrupt`)立即中止
|
|
16
|
+
当前回合(模型流与正在跑的工具,每个工具调用仍恰有一个结果 `aborted by user`),并马上以输入框的文字开新回合,已排队的插话拼在
|
|
17
|
+
前面(会话里 `origin: "interrupt"`,消息区标 `↳ 打断`);输入为空时把排队的插话立即送出。Esc 语义不变。配置
|
|
18
|
+
`ui.enterWhileRunning: "queue" | "interrupt"`(缺省 queue,`/config` 可改)互换 Enter 与 `Ctrl+X`。输入框有字时运行提示行显示
|
|
19
|
+
`Enter 排队 · Ctrl+X 打断并发送`。子 Agent 视图同样可用:ama 子 Agent 中止本轮后立即以这条消息开新一轮;外部 Agent 的驱动能中断
|
|
20
|
+
单个回合(ACP、Claude stream-json、Codex)时中断后发送,否则退回排队并提示。line 模式认 `/interrupt <文本>`;RPC `prompt` /
|
|
21
|
+
`steer` 与 SDK 增 `interrupt: true`。新请求以被打断的那次请求为前缀,缓存照常命中。文档:docs/tui.md、docs/rpc.md。
|
|
22
|
+
- **状态栏左右分区与配额标签修正**:`full` 布局改为左列「状态 / 开关」、右列「度量与模型」——速率行左区是 `codemode on`(含
|
|
23
|
+
`net!`)· 沙箱 · 预设 · `→ 回退模型` · 排队数 · 宿主状态,右区是 `tps … (avg · ttft) · ↑ ↓ · cache · 重计费 · [-]`;状态栏
|
|
24
|
+
左区仍是权限模式与 `shift+tab` 提示;配额行右对齐。窄屏先丢右区度量、再丢左区开关,权限模式、`tps` 与 `[-]` 不丢;`compact`
|
|
25
|
+
单行的记号顺序不变。配额标签按窗口时长认:周窗口放在 primary 时显示「本周 / Weekly」而不是 `7d:`,5 小时窗口排在前面;
|
|
26
|
+
服务端用全 0 表示的空窗口不再渲染成 `0d: 0.0%`(解析与显示两层都过滤)。文档:docs/tui.md。
|
|
27
|
+
|
|
7
28
|
## 0.6.3(2026-10-03)
|
|
8
29
|
|
|
9
30
|
- **TUI:`Ctrl+B` 前台任务转后台,后台任务审批停靠在 Agent 栏**:主回合在等前台子 Agent 任务(或 `task_ctl wait`)时按
|
package/dist/agent/queue.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* `one-at-a-time`:每个投递点只取一条;`all`:一次取空。abort 不清队列。
|
|
5
5
|
*/
|
|
6
6
|
import type { QueueMode } from "./types.js";
|
|
7
|
+
import type { ImageBlock } from "../ai/types.js";
|
|
7
8
|
import type { AgentMessage } from "../session/types.js";
|
|
8
9
|
export declare class PendingMessageQueue {
|
|
9
10
|
private items;
|
|
@@ -23,3 +24,11 @@ export declare class PendingMessageQueue {
|
|
|
23
24
|
}
|
|
24
25
|
/** 队列消息的显示文本(queue_update 事件用)。 */
|
|
25
26
|
export declare function queuedText(message: AgentMessage): string;
|
|
27
|
+
/**
|
|
28
|
+
* 打断并立即发送的新回合输入:排队的 steer(按入队顺序)在前、本条在后,文字以空行拼接,图片依次保留。
|
|
29
|
+
* 全部为空时 undefined(不打断)。
|
|
30
|
+
*/
|
|
31
|
+
export declare function mergeForInterrupt(queued: readonly AgentMessage[], text: string, images?: readonly ImageBlock[]): {
|
|
32
|
+
text: string;
|
|
33
|
+
images: ImageBlock[];
|
|
34
|
+
} | undefined;
|
package/dist/agent/queue.js
CHANGED
|
@@ -53,3 +53,30 @@ export function queuedText(message) {
|
|
|
53
53
|
return content;
|
|
54
54
|
return content.map((block) => (block.type === "text" ? block.text : "[image]")).join("");
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* 打断并立即发送的新回合输入:排队的 steer(按入队顺序)在前、本条在后,文字以空行拼接,图片依次保留。
|
|
58
|
+
* 全部为空时 undefined(不打断)。
|
|
59
|
+
*/
|
|
60
|
+
export function mergeForInterrupt(queued, text, images = []) {
|
|
61
|
+
const texts = [];
|
|
62
|
+
const merged = [];
|
|
63
|
+
for (const message of queued) {
|
|
64
|
+
if (message.role !== "user")
|
|
65
|
+
continue;
|
|
66
|
+
const { content } = message;
|
|
67
|
+
if (typeof content === "string")
|
|
68
|
+
texts.push(content);
|
|
69
|
+
else {
|
|
70
|
+
texts.push(content.map((block) => (block.type === "text" ? block.text : "")).join(""));
|
|
71
|
+
for (const block of content)
|
|
72
|
+
if (block.type === "image")
|
|
73
|
+
merged.push(block);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
texts.push(text);
|
|
77
|
+
merged.push(...images);
|
|
78
|
+
const parts = texts.filter((part) => part.trim() !== "");
|
|
79
|
+
if (parts.length === 0 && merged.length === 0)
|
|
80
|
+
return undefined;
|
|
81
|
+
return { text: parts.join("\n\n"), images: merged };
|
|
82
|
+
}
|
|
@@ -54,6 +54,7 @@ export interface ChildSession {
|
|
|
54
54
|
readonly messages: readonly AgentMessage[];
|
|
55
55
|
prompt(text: string, options?: {
|
|
56
56
|
origin?: MessageOrigin;
|
|
57
|
+
interrupt?: boolean;
|
|
57
58
|
}): Promise<unknown>;
|
|
58
59
|
/** [W6-A] 视图里直接发的消息(运行中排到回合结束)。 */
|
|
59
60
|
followUp?(text: string, options?: {
|
|
@@ -186,6 +186,8 @@ async function startAmaChild(parent, spec, createChild, run) {
|
|
|
186
186
|
run.signal.addEventListener("abort", onAbort, { once: true });
|
|
187
187
|
let billed = { cacheRead: 0, prompt: 0, reBilled: 0 };
|
|
188
188
|
let active = false;
|
|
189
|
+
/** 视图里「打断并发送」开的回合(runOnce 等它们跑完再收尾)。 */
|
|
190
|
+
const forced = [];
|
|
189
191
|
const runOnce = async (prompt, origin) => {
|
|
190
192
|
const startTurns = turns;
|
|
191
193
|
let error;
|
|
@@ -196,6 +198,8 @@ async function startAmaChild(parent, spec, createChild, run) {
|
|
|
196
198
|
await child.prompt(prompt, origin === undefined ? {} : { origin });
|
|
197
199
|
// [W6-A] 收尾阶段才入队的 followUp(只可能来自视图)留在队列里:接着再跑一轮
|
|
198
200
|
for (;;) {
|
|
201
|
+
while (forced.length > 0)
|
|
202
|
+
await forced.shift();
|
|
199
203
|
const left = child.clearQueue?.().followUp ?? [];
|
|
200
204
|
if (left.length === 0 || run.signal.aborted)
|
|
201
205
|
break;
|
|
@@ -245,6 +249,11 @@ async function startAmaChild(parent, spec, createChild, run) {
|
|
|
245
249
|
}
|
|
246
250
|
if (!active || child.followUp === undefined)
|
|
247
251
|
throw new AmaError("task_idle", "the sub-agent is not running");
|
|
252
|
+
if (when === "interrupt") {
|
|
253
|
+
// 同步登记:被中止的那一轮一结束,runOnce 就接着等这一轮
|
|
254
|
+
forced.push(child.prompt(text, { origin: "direct", interrupt: true }));
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
248
257
|
await child.followUp(text, { origin: "direct" });
|
|
249
258
|
},
|
|
250
259
|
wait: () => current,
|
package/dist/agent/session.d.ts
CHANGED
|
@@ -77,6 +77,11 @@ export declare class AgentSessionImpl implements AgentSession, SessionCore {
|
|
|
77
77
|
private enqueueOrRun;
|
|
78
78
|
steer(text: string, options?: EnqueueOptions): Promise<"queued" | "handled">;
|
|
79
79
|
followUp(text: string, options?: EnqueueOptions): Promise<"queued" | "handled">;
|
|
80
|
+
/**
|
|
81
|
+
* 打断并立即发送:取走排队的 steer、中止当前周期(工具按 abort 收尾),以「steer… + 本条」开新回合;
|
|
82
|
+
* followUp 留在队列。没有可发的内容 → invalid_arguments(不打断)。
|
|
83
|
+
*/
|
|
84
|
+
private interruptWith;
|
|
80
85
|
abort(): Promise<void>;
|
|
81
86
|
waitForIdle(): Promise<void>;
|
|
82
87
|
/**
|
package/dist/agent/session.js
CHANGED
|
@@ -13,7 +13,7 @@ import { AmaError } from "../errors.js";
|
|
|
13
13
|
import { createSessionClassifier } from "./session-classifier.js";
|
|
14
14
|
import { buildProjection } from "../session/projection.js";
|
|
15
15
|
import { Agent } from "./agent.js";
|
|
16
|
-
import { queuedText } from "./queue.js";
|
|
16
|
+
import { mergeForInterrupt, queuedText } from "./queue.js";
|
|
17
17
|
import { resolveRetrySettings } from "./retry.js";
|
|
18
18
|
import { SessionCacheController, resolveCacheSettings } from "./session-cache.js";
|
|
19
19
|
import { CompactionController } from "./session-compaction.js";
|
|
@@ -278,6 +278,8 @@ export class AgentSessionImpl {
|
|
|
278
278
|
// -------------------------------------------------------------------------
|
|
279
279
|
async prompt(text, options = {}) {
|
|
280
280
|
this.assertUsable();
|
|
281
|
+
if (options.interrupt === true && this.cycle !== undefined)
|
|
282
|
+
return this.interruptWith(text, options.images, options.origin);
|
|
281
283
|
if (this.cycle !== undefined) {
|
|
282
284
|
const behavior = options.streamingBehavior;
|
|
283
285
|
if (behavior === undefined) {
|
|
@@ -299,7 +301,9 @@ export class AgentSessionImpl {
|
|
|
299
301
|
}
|
|
300
302
|
async enqueueOrRun(text, queue, options) {
|
|
301
303
|
this.assertUsable();
|
|
302
|
-
|
|
304
|
+
if (options.interrupt === true && this.cycle !== undefined)
|
|
305
|
+
return this.interruptWith(text, undefined, options.origin).then(() => "handled");
|
|
306
|
+
const origin = normalizeOrigin(options.origin ?? (options.interrupt === true ? "user" : queue));
|
|
303
307
|
if (this.cycle === undefined) {
|
|
304
308
|
await this.startCycle((signal) => runPrompt(this.runDeps(), text, undefined, origin, signal));
|
|
305
309
|
return "handled";
|
|
@@ -313,6 +317,21 @@ export class AgentSessionImpl {
|
|
|
313
317
|
followUp(text, options = {}) {
|
|
314
318
|
return this.enqueueOrRun(text, "followUp", options);
|
|
315
319
|
}
|
|
320
|
+
/**
|
|
321
|
+
* 打断并立即发送:取走排队的 steer、中止当前周期(工具按 abort 收尾),以「steer… + 本条」开新回合;
|
|
322
|
+
* followUp 留在队列。没有可发的内容 → invalid_arguments(不打断)。
|
|
323
|
+
*/
|
|
324
|
+
async interruptWith(text, images, origin) {
|
|
325
|
+
const merged = mergeForInterrupt(this.agent.steeringQueue.snapshot(), text, images);
|
|
326
|
+
if (merged === undefined)
|
|
327
|
+
throw new AmaError("invalid_arguments", "nothing to send");
|
|
328
|
+
this.agent.steeringQueue.clear();
|
|
329
|
+
this.emitQueue();
|
|
330
|
+
while (this.cycle !== undefined)
|
|
331
|
+
await this.abort();
|
|
332
|
+
const kind = normalizeOrigin(origin ?? "interrupt");
|
|
333
|
+
return this.startCycle((s) => runPrompt(this.runDeps(), merged.text, merged.images, kind, s));
|
|
334
|
+
}
|
|
316
335
|
async abort() {
|
|
317
336
|
const cycle = this.cycle;
|
|
318
337
|
if (cycle === undefined)
|
|
@@ -7,14 +7,17 @@
|
|
|
7
7
|
* 外部 Agent 运行中 / 还在排队 → 等本次运行结束后续聊(`queued`);已结束 → 同 `task_ctl send` 的后台续聊
|
|
8
8
|
* (`resumed`,完成后父会话照常收 `<task-notification>`)。子会话 user 消息记 `origin: "direct"`;
|
|
9
9
|
* - `drainDirect`:运行结束时把排着的消息合成一条续聊;任务被停止时丢弃(停止的一方不想它再跑)。
|
|
10
|
+
* - 打断并发送(`interrupt`):ama 运行中 → 子会话 `prompt{interrupt}`(中止本轮、立即开新回合,`interrupted`);
|
|
11
|
+
* 外部 Agent 运行中且驱动能中断回合 → 连同排着的消息一起 `handle.interrupt`(`interrupted`);驱动不能中断
|
|
12
|
+
* 或任务还在并发池排队 → 照常排到运行结束(`queuedNoInterrupt`,视图提示);已结束 → 同上的后台续聊。
|
|
10
13
|
*/
|
|
11
14
|
import type { TaskLive, TaskRecord } from "../agents/task-record.js";
|
|
12
15
|
import type { SubagentRequest, SubagentResult } from "../tools/types.js";
|
|
13
|
-
export type DirectReply = "steered" | "queued" | "resumed";
|
|
16
|
+
export type DirectReply = "steered" | "queued" | "resumed" | "interrupted" | "queuedNoInterrupt";
|
|
14
17
|
export interface DirectHost {
|
|
15
18
|
spawnSubagent?(request: SubagentRequest): Promise<SubagentResult>;
|
|
16
19
|
log(level: "debug" | "info" | "warn" | "error", message: string): void;
|
|
17
20
|
}
|
|
18
21
|
export declare function liveOf(record: TaskRecord): TaskLive;
|
|
19
|
-
export declare function directMessage(record: TaskRecord, text: string, host: DirectHost): Promise<DirectReply>;
|
|
22
|
+
export declare function directMessage(record: TaskRecord, text: string, host: DirectHost, interrupt?: boolean): Promise<DirectReply>;
|
|
20
23
|
export declare function drainDirect(record: TaskRecord, host: DirectHost): void;
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
* 外部 Agent 运行中 / 还在排队 → 等本次运行结束后续聊(`queued`);已结束 → 同 `task_ctl send` 的后台续聊
|
|
8
8
|
* (`resumed`,完成后父会话照常收 `<task-notification>`)。子会话 user 消息记 `origin: "direct"`;
|
|
9
9
|
* - `drainDirect`:运行结束时把排着的消息合成一条续聊;任务被停止时丢弃(停止的一方不想它再跑)。
|
|
10
|
+
* - 打断并发送(`interrupt`):ama 运行中 → 子会话 `prompt{interrupt}`(中止本轮、立即开新回合,`interrupted`);
|
|
11
|
+
* 外部 Agent 运行中且驱动能中断回合 → 连同排着的消息一起 `handle.interrupt`(`interrupted`);驱动不能中断
|
|
12
|
+
* 或任务还在并发池排队 → 照常排到运行结束(`queuedNoInterrupt`,视图提示);已结束 → 同上的后台续聊。
|
|
10
13
|
*/
|
|
11
14
|
import { AmaError } from "../errors.js";
|
|
12
15
|
export function liveOf(record) {
|
|
@@ -25,11 +28,17 @@ export function liveOf(record) {
|
|
|
25
28
|
live.entries = () => entries.call(handle);
|
|
26
29
|
return live;
|
|
27
30
|
}
|
|
28
|
-
export async function directMessage(record, text, host) {
|
|
31
|
+
export async function directMessage(record, text, host, interrupt = false) {
|
|
29
32
|
if (text.trim() === "")
|
|
30
33
|
throw new AmaError("invalid_arguments", "empty message");
|
|
31
34
|
if (record.running !== undefined) {
|
|
32
35
|
const handle = record.handle;
|
|
36
|
+
if (interrupt && record.queued !== true && (await interruptRun(record, text)))
|
|
37
|
+
return "interrupted";
|
|
38
|
+
if (interrupt) {
|
|
39
|
+
(record.direct ??= []).push(text);
|
|
40
|
+
return "queuedNoInterrupt";
|
|
41
|
+
}
|
|
33
42
|
if (handle?.message !== undefined && record.queued !== true) {
|
|
34
43
|
try {
|
|
35
44
|
await handle.message(text, "followUp");
|
|
@@ -47,6 +56,30 @@ export async function directMessage(record, text, host) {
|
|
|
47
56
|
await resumeDirect(record, text, host);
|
|
48
57
|
return "resumed";
|
|
49
58
|
}
|
|
59
|
+
/** 打断运行中的任务并立即发送;做不到返回 false(不改动排队)。 */
|
|
60
|
+
async function interruptRun(record, text) {
|
|
61
|
+
const handle = record.handle;
|
|
62
|
+
if (handle?.message !== undefined) {
|
|
63
|
+
try {
|
|
64
|
+
await handle.message(text, "interrupt");
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
if (error instanceof AmaError && error.code === "task_idle")
|
|
69
|
+
return false;
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (handle?.interrupt === undefined)
|
|
74
|
+
return false;
|
|
75
|
+
// 外部 Agent:排着的消息(运行中在视图里发的)在前,一起作为新回合
|
|
76
|
+
const queued = record.direct ?? [];
|
|
77
|
+
const merged = [...queued, text].join("\n\n");
|
|
78
|
+
if (!(await handle.interrupt(merged)))
|
|
79
|
+
return false;
|
|
80
|
+
queued.splice(0);
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
50
83
|
async function resumeDirect(record, text, host) {
|
|
51
84
|
const spawn = host.spawnSubagent;
|
|
52
85
|
if (spawn === undefined)
|
|
@@ -22,6 +22,7 @@ import { type TaskControl } from "../agents/task-control.js";
|
|
|
22
22
|
import type { AgentDefinition, AgentInfo } from "../agents/types.js";
|
|
23
23
|
import type { SessionCore } from "./session-core.js";
|
|
24
24
|
import type { SessionTaskStats } from "./types.js";
|
|
25
|
+
import { type DirectReply } from "./subagent-direct.js";
|
|
25
26
|
import { type BackgroundReason } from "./subagent-background.js";
|
|
26
27
|
export declare const DEFAULT_SUBAGENT_CONCURRENCY = 4;
|
|
27
28
|
export declare const DEFAULT_MAX_PENDING = 16;
|
|
@@ -95,7 +96,7 @@ export declare class SubagentRegistry implements TaskControl {
|
|
|
95
96
|
/** [W6-A] 子 Agent 视图读的实时数据;未知任务 undefined。 */
|
|
96
97
|
live(taskId: string): TaskLive | undefined;
|
|
97
98
|
/** [W6-A] 人在子 Agent 视图里发的消息(subagent-direct.ts)。 */
|
|
98
|
-
message(taskId: string, text: string): Promise<
|
|
99
|
+
message(taskId: string, text: string, interrupt?: boolean): Promise<DirectReply>;
|
|
99
100
|
stop(taskId: string): Promise<SubagentResult | undefined>;
|
|
100
101
|
run(request: SubagentRequest, ama: AmaRunnerFactory): Promise<SubagentResult>;
|
|
101
102
|
private continueTask;
|
|
@@ -148,11 +148,11 @@ export class SubagentRegistry {
|
|
|
148
148
|
return record === undefined ? undefined : liveOf(record);
|
|
149
149
|
}
|
|
150
150
|
/** [W6-A] 人在子 Agent 视图里发的消息(subagent-direct.ts)。 */
|
|
151
|
-
async message(taskId, text) {
|
|
151
|
+
async message(taskId, text, interrupt = false) {
|
|
152
152
|
const record = this.tasks.get(taskId);
|
|
153
153
|
if (record === undefined)
|
|
154
154
|
throw new AmaError("task_not_found", `unknown task ${taskId}`);
|
|
155
|
-
return directMessage(record, text, this.host);
|
|
155
|
+
return directMessage(record, text, this.host, interrupt);
|
|
156
156
|
}
|
|
157
157
|
async stop(taskId) {
|
|
158
158
|
const record = this.tasks.get(taskId);
|
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;
|
|
@@ -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;
|
|
@@ -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 = {};
|
package/dist/ai/types.d.ts
CHANGED
|
@@ -136,7 +136,8 @@ export interface SystemMessage {
|
|
|
136
136
|
toolsRemoved?: string[];
|
|
137
137
|
timestamp: number;
|
|
138
138
|
}
|
|
139
|
-
|
|
139
|
+
/** `interrupt`:打断当前回合并立即发送(`prompt / steer` 的 `interrupt: true`)开的新回合。 */
|
|
140
|
+
export type KnownMessageOrigin = "steer" | "followUp" | "host" | "interrupt";
|
|
140
141
|
export type MessageOrigin = KnownMessageOrigin | (string & {});
|
|
141
142
|
export interface UserMessage {
|
|
142
143
|
role: "user";
|