@armadra/agent 0.10.0 → 0.12.0
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 +31 -0
- package/CHANGELOG.zh-CN.md +31 -0
- package/README.md +5 -0
- package/README.zh-CN.md +4 -0
- package/dist/acp.d.ts +2 -2
- package/dist/acp.js +2 -2
- package/dist/bundle/ama.cjs +1730 -446
- package/dist/cli/compose-agents.js +8 -0
- package/dist/cli/compose.d.ts +3 -0
- package/dist/cli/compose.js +1 -0
- package/dist/drivers/acp/client.d.ts +54 -5
- package/dist/drivers/acp/client.js +96 -9
- package/dist/drivers/acp/types.d.ts +69 -3
- package/dist/drivers/acp/types.js +30 -1
- package/dist/drivers/agents.js +1 -1
- package/dist/drivers/host-runners.d.ts +26 -1
- package/dist/drivers/host-runners.js +147 -0
- package/dist/hooks/matcher.d.ts +2 -2
- package/dist/hooks/matcher.js +3 -3
- package/dist/host/api-impl.d.ts +3 -0
- package/dist/host/api-impl.js +7 -2
- package/dist/host/types.d.ts +37 -1
- package/dist/host/types.js +5 -0
- package/dist/host.d.ts +1 -1
- package/dist/host.js +1 -1
- package/dist/i18n/catalog.d.ts +5 -0
- package/dist/i18n/messages/approval.d.ts +5 -0
- package/dist/i18n/messages/approval.js +5 -0
- package/dist/i18n/messages/drivers.d.ts +2 -0
- package/dist/i18n/messages/drivers.js +2 -0
- package/dist/i18n/messages/interactive-view.d.ts +5 -0
- package/dist/i18n/messages/interactive-view.js +5 -0
- package/dist/i18n/messages/interactive.d.ts +4 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/modes/commands-core.d.ts +7 -0
- package/dist/modes/commands-core.js +23 -5
- package/dist/modes/interactive/approval-dialog.js +17 -4
- package/dist/modes/interactive/commands.d.ts +2 -0
- package/dist/modes/interactive/commands.js +5 -1
- package/dist/modes/interactive/interactive-mode.d.ts +7 -0
- package/dist/modes/interactive/interactive-mode.js +14 -6
- package/dist/modes/interactive/tool-summary.d.ts +2 -0
- package/dist/modes/interactive/tool-summary.js +27 -0
- package/dist/modes/rpc/commands.js +4 -4
- package/dist/permissions/auto-safe.d.ts +3 -0
- package/dist/permissions/auto-safe.js +10 -3
- package/dist/permissions/classifier.js +1 -1
- package/dist/permissions/network-class.d.ts +28 -0
- package/dist/permissions/network-class.js +91 -0
- package/dist/permissions/pipeline.d.ts +1 -1
- package/dist/permissions/pipeline.js +24 -7
- package/dist/permissions/preview.js +3 -0
- package/dist/permissions/readonly-bash.d.ts +2 -2
- package/dist/permissions/readonly-bash.js +2 -2
- package/dist/permissions/rules.d.ts +1 -0
- package/dist/permissions/rules.js +5 -1
- package/dist/tools/presets.d.ts +8 -1
- package/dist/tools/presets.js +12 -3
- package/dist/tools/registry.d.ts +3 -1
- package/dist/tools/registry.js +4 -2
- package/dist/tools/task-ctl.d.ts +9 -0
- package/dist/tools/task-ctl.js +22 -3
- package/dist/tools/types.d.ts +4 -1
- package/dist/tools/web/fetch.d.ts +48 -0
- package/dist/tools/web/fetch.js +307 -0
- package/dist/tools/web/html-to-markdown.d.ts +31 -0
- package/dist/tools/web/html-to-markdown.js +522 -0
- package/dist/tools/web/url-guard.d.ts +59 -0
- package/dist/tools/web/url-guard.js +221 -0
- package/docs/en/guides/permissions.md +33 -11
- package/docs/en/reference/acp.md +7 -3
- package/docs/en/reference/host-api.md +61 -19
- package/docs/en/reference/rpc.md +7 -7
- package/docs/guides/agents.md +18 -1
- package/docs/guides/hooks.md +86 -8
- package/docs/guides/permissions.md +33 -11
- package/docs/guides/sandbox.md +6 -0
- package/docs/reference/acp.md +7 -3
- package/docs/reference/host-api.md +61 -19
- package/docs/reference/rpc.md +7 -7
- package/package.json +1 -1
|
@@ -4,12 +4,41 @@
|
|
|
4
4
|
* 一个宿主适配器一份(host/api-impl.ts 建,经 {@link ExternalAgents} 的 `hostRunners` 选项交给每个
|
|
5
5
|
* 主会话):宿主可以在 `create()` 里或之后任何时候 `provide`,同名替换;返回的函数注销。
|
|
6
6
|
* `onChange` 供 `/agents` / RPC `get_agents` 的缓存刷新。
|
|
7
|
+
*
|
|
8
|
+
* [#226] `onDone`(`HostApi.runners.onDone`):任务入口经 {@link HostRunnerRegistry.resolve} 拿到的是
|
|
9
|
+
* 观察包装——句柄的每一轮(`start` 之后、每次 `send` / `interrupt` / `message` 之后)在 `wait()` 落定时
|
|
10
|
+
* 发一次 {@link RunnerDoneEvent}。包装只观察:`wait()` 的结果与异常原样返回,其余成员原样转给宿主句柄;
|
|
11
|
+
* 同一轮里多次 `wait()` 只发一次,落定后没有新一轮的 `wait()` 不再发。
|
|
7
12
|
*/
|
|
8
13
|
import { AmaError } from "../errors.js";
|
|
9
14
|
import { msg } from "../i18n/index.js";
|
|
15
|
+
const TERMINAL = [
|
|
16
|
+
"completed",
|
|
17
|
+
"failed",
|
|
18
|
+
"aborted",
|
|
19
|
+
"max_turns",
|
|
20
|
+
"interrupted",
|
|
21
|
+
];
|
|
22
|
+
/** 与任务注册表的终态判定一致:中断优先,其次结果自报的终态,再按 `isError`。 */
|
|
23
|
+
function statusOf(result, aborted) {
|
|
24
|
+
if (aborted)
|
|
25
|
+
return "aborted";
|
|
26
|
+
const own = result.status;
|
|
27
|
+
if (own !== undefined && TERMINAL.includes(own))
|
|
28
|
+
return own;
|
|
29
|
+
return result.isError ? "failed" : "completed";
|
|
30
|
+
}
|
|
10
31
|
export class HostRunnerRegistry {
|
|
11
32
|
runners = new Map();
|
|
12
33
|
listeners = new Set();
|
|
34
|
+
doneHandlers = new Set();
|
|
35
|
+
observed = new WeakMap();
|
|
36
|
+
now;
|
|
37
|
+
log;
|
|
38
|
+
constructor(options = {}) {
|
|
39
|
+
this.now = options.now ?? Date.now;
|
|
40
|
+
this.log = options.log ?? (() => undefined);
|
|
41
|
+
}
|
|
13
42
|
provide(runner) {
|
|
14
43
|
if (typeof runner?.id !== "string" || runner.id === "" || typeof runner.start !== "function")
|
|
15
44
|
throw new AmaError("invalid_arguments", msg().drivers.host.runnerNeedsIdStart);
|
|
@@ -25,6 +54,18 @@ export class HostRunnerRegistry {
|
|
|
25
54
|
get(id) {
|
|
26
55
|
return this.runners.get(id);
|
|
27
56
|
}
|
|
57
|
+
/** [#226] 任务入口用:同 {@link get},但返回发 `onDone` 的观察包装(同一 runner 包装复用)。 */
|
|
58
|
+
resolve(id) {
|
|
59
|
+
const runner = this.runners.get(id);
|
|
60
|
+
if (runner === undefined)
|
|
61
|
+
return undefined;
|
|
62
|
+
let wrapped = this.observed.get(runner);
|
|
63
|
+
if (wrapped === undefined) {
|
|
64
|
+
wrapped = this.observe(runner);
|
|
65
|
+
this.observed.set(runner, wrapped);
|
|
66
|
+
}
|
|
67
|
+
return wrapped;
|
|
68
|
+
}
|
|
28
69
|
list() {
|
|
29
70
|
return [...this.runners.values()];
|
|
30
71
|
}
|
|
@@ -33,6 +74,13 @@ export class HostRunnerRegistry {
|
|
|
33
74
|
this.listeners.add(listener);
|
|
34
75
|
return () => void this.listeners.delete(listener);
|
|
35
76
|
}
|
|
77
|
+
/** [#226] 每轮结束时回调(`HostApi.runners.onDone`);返回注销函数。 */
|
|
78
|
+
onDone(handler) {
|
|
79
|
+
if (typeof handler !== "function")
|
|
80
|
+
throw new AmaError("invalid_arguments", msg().drivers.host.runnerDoneNeedsFunction);
|
|
81
|
+
this.doneHandlers.add(handler);
|
|
82
|
+
return () => void this.doneHandlers.delete(handler);
|
|
83
|
+
}
|
|
36
84
|
changed() {
|
|
37
85
|
for (const listener of [...this.listeners]) {
|
|
38
86
|
try {
|
|
@@ -43,4 +91,103 @@ export class HostRunnerRegistry {
|
|
|
43
91
|
}
|
|
44
92
|
}
|
|
45
93
|
}
|
|
94
|
+
emitDone(event) {
|
|
95
|
+
for (const handler of [...this.doneHandlers]) {
|
|
96
|
+
try {
|
|
97
|
+
const out = handler(event);
|
|
98
|
+
if (out instanceof Promise)
|
|
99
|
+
out.catch((error) => this.fail(error));
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
this.fail(error);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
fail(error) {
|
|
107
|
+
this.log(msg().drivers.host.handlerFailed("runners.onDone"), error);
|
|
108
|
+
}
|
|
109
|
+
observe(runner) {
|
|
110
|
+
return new Proxy(runner, {
|
|
111
|
+
get: (target, prop) => {
|
|
112
|
+
if (prop !== "start")
|
|
113
|
+
return forward(target, prop);
|
|
114
|
+
return async (request) => this.observeHandle(target.id, request, this.now(), await target.start(request));
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
observeHandle(runner, request, startedAt, handle) {
|
|
119
|
+
let round = 0;
|
|
120
|
+
let fresh = true;
|
|
121
|
+
let pending = false;
|
|
122
|
+
let stopped = false;
|
|
123
|
+
let roundStart = startedAt;
|
|
124
|
+
const activity = () => {
|
|
125
|
+
if (pending)
|
|
126
|
+
return;
|
|
127
|
+
fresh = true;
|
|
128
|
+
roundStart = this.now();
|
|
129
|
+
};
|
|
130
|
+
const settle = (at, done) => {
|
|
131
|
+
pending = false;
|
|
132
|
+
const endedAt = this.now();
|
|
133
|
+
const event = {
|
|
134
|
+
runner,
|
|
135
|
+
sessionId: handle.id,
|
|
136
|
+
round,
|
|
137
|
+
status: done.status,
|
|
138
|
+
isError: done.status === "failed" || done.status === "aborted",
|
|
139
|
+
startedAt: at,
|
|
140
|
+
endedAt,
|
|
141
|
+
durationMs: Math.max(0, endedAt - at),
|
|
142
|
+
};
|
|
143
|
+
if (request.taskId !== undefined)
|
|
144
|
+
event.taskId = request.taskId;
|
|
145
|
+
if (done.text !== undefined)
|
|
146
|
+
event.text = done.text;
|
|
147
|
+
if (done.error !== undefined)
|
|
148
|
+
event.error = done.error;
|
|
149
|
+
this.emitDone(event);
|
|
150
|
+
};
|
|
151
|
+
const wait = () => {
|
|
152
|
+
const result = handle.wait();
|
|
153
|
+
if (!fresh)
|
|
154
|
+
return result;
|
|
155
|
+
fresh = false;
|
|
156
|
+
pending = true;
|
|
157
|
+
stopped = false;
|
|
158
|
+
round += 1;
|
|
159
|
+
const at = roundStart;
|
|
160
|
+
const aborted = () => stopped || request.signal.aborted;
|
|
161
|
+
result.then((value) => settle(at, { status: statusOf(value, aborted()), text: value.text }), (error) => settle(at, {
|
|
162
|
+
status: aborted() ? "aborted" : "failed",
|
|
163
|
+
error: error instanceof Error ? error.message : String(error),
|
|
164
|
+
}));
|
|
165
|
+
return result;
|
|
166
|
+
};
|
|
167
|
+
return new Proxy(handle, {
|
|
168
|
+
get: (target, prop) => {
|
|
169
|
+
const value = forward(target, prop);
|
|
170
|
+
if (prop === "wait")
|
|
171
|
+
return wait;
|
|
172
|
+
if (prop === "stop" && typeof value === "function")
|
|
173
|
+
return (...args) => {
|
|
174
|
+
if (pending)
|
|
175
|
+
stopped = true;
|
|
176
|
+
return value(...args);
|
|
177
|
+
};
|
|
178
|
+
if ((prop === "send" || prop === "interrupt" || prop === "message") &&
|
|
179
|
+
typeof value === "function")
|
|
180
|
+
return (...args) => {
|
|
181
|
+
activity();
|
|
182
|
+
return value(...args);
|
|
183
|
+
};
|
|
184
|
+
return value;
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/** 原样读宿主对象的成员:getter 以原对象为 this,方法绑定到原对象。 */
|
|
190
|
+
function forward(target, prop) {
|
|
191
|
+
const value = Reflect.get(target, prop, target);
|
|
192
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
46
193
|
}
|
package/dist/hooks/matcher.d.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 语法:缺省 / 空串 / `*` 匹配全部;`bash` 精确;`write|edit` 多选;`canvas_*` glob;
|
|
5
5
|
* `bash(git push*)` 先匹配工具名,再对参数文本做 glob;`/regex/flags` 对工具名做正则。
|
|
6
|
-
* 括号内的文本:bash → `input.command`;文件类工具 → `input.path`(或 `file_path`);
|
|
7
|
-
*
|
|
6
|
+
* 括号内的文本:bash → `input.command`;文件类工具 → `input.path`(或 `file_path` / `pattern`);
|
|
7
|
+
* web_fetch → `input.url`(整个 URL,与权限规则的 `host/path` 语义不同);其它工具 → 输入 JSON 文本。`|` 只在括号外分隔候选。只在 Pre/PostToolUse 生效。
|
|
8
8
|
*/
|
|
9
9
|
export type ToolMatcher = (toolName: string, input: unknown) => boolean;
|
|
10
10
|
/** glob → 正则:`*` 任意字符串(含 `/`),`?` 单个字符,其余字面量。 */
|
package/dist/hooks/matcher.js
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 语法:缺省 / 空串 / `*` 匹配全部;`bash` 精确;`write|edit` 多选;`canvas_*` glob;
|
|
5
5
|
* `bash(git push*)` 先匹配工具名,再对参数文本做 glob;`/regex/flags` 对工具名做正则。
|
|
6
|
-
* 括号内的文本:bash → `input.command`;文件类工具 → `input.path`(或 `file_path`);
|
|
7
|
-
*
|
|
6
|
+
* 括号内的文本:bash → `input.command`;文件类工具 → `input.path`(或 `file_path` / `pattern`);
|
|
7
|
+
* web_fetch → `input.url`(整个 URL,与权限规则的 `host/path` 语义不同);其它工具 → 输入 JSON 文本。`|` 只在括号外分隔候选。只在 Pre/PostToolUse 生效。
|
|
8
8
|
*/
|
|
9
9
|
/** glob → 正则:`*` 任意字符串(含 `/`),`?` 单个字符,其余字面量。 */
|
|
10
10
|
export function globToRegExp(glob) {
|
|
@@ -46,7 +46,7 @@ export function argumentText(toolName, input) {
|
|
|
46
46
|
const record = input;
|
|
47
47
|
if (toolName === "bash" && typeof record["command"] === "string")
|
|
48
48
|
return record["command"];
|
|
49
|
-
for (const key of ["path", "file_path", "pattern"]) {
|
|
49
|
+
for (const key of ["path", "file_path", "pattern", "url"]) {
|
|
50
50
|
const value = record[key];
|
|
51
51
|
if (typeof value === "string")
|
|
52
52
|
return value;
|
package/dist/host/api-impl.d.ts
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
* [W5-EG] `runners.provide`(docs/history/wave5-plan.md §5.5,D17):宿主注入的 runner 进本适配器的
|
|
10
10
|
* {@link HostRunnerRegistry}(跨会话共享),每个主会话的外部 Agent 入口(agents/external.ts)经
|
|
11
11
|
* {@link hostRunnersOf} 取到它;有宿主时 ama 不自 spawn 外部 CLI,只认这里注入的。
|
|
12
|
+
*
|
|
13
|
+
* [#226] `runners.onDone` 与 `minor`(host-api 1.1):每轮结束的事件由 registry 的观察包装发出,
|
|
14
|
+
* 处理器异常走本适配器的 `log`。
|
|
12
15
|
*/
|
|
13
16
|
import { HostRunnerRegistry } from "../drivers/host-runners.js";
|
|
14
17
|
import type { AgentEventName, AgentEvents, ApprovalBroker, HostAdapter, HostAdapterHandle, HostApi, HostMode, InstructionSource } from "./types.js";
|
package/dist/host/api-impl.js
CHANGED
|
@@ -9,11 +9,14 @@
|
|
|
9
9
|
* [W5-EG] `runners.provide`(docs/history/wave5-plan.md §5.5,D17):宿主注入的 runner 进本适配器的
|
|
10
10
|
* {@link HostRunnerRegistry}(跨会话共享),每个主会话的外部 Agent 入口(agents/external.ts)经
|
|
11
11
|
* {@link hostRunnersOf} 取到它;有宿主时 ama 不自 spawn 外部 CLI,只认这里注入的。
|
|
12
|
+
*
|
|
13
|
+
* [#226] `runners.onDone` 与 `minor`(host-api 1.1):每轮结束的事件由 registry 的观察包装发出,
|
|
14
|
+
* 处理器异常走本适配器的 `log`。
|
|
12
15
|
*/
|
|
13
16
|
import { HostRunnerRegistry } from "../drivers/host-runners.js";
|
|
14
17
|
import { AmaError } from "../errors.js";
|
|
15
18
|
import { AMA_VERSION } from "../version.js";
|
|
16
|
-
import { HOST_API_VERSION } from "./types.js";
|
|
19
|
+
import { HOST_API_MINOR, HOST_API_VERSION } from "./types.js";
|
|
17
20
|
import { msg } from "../i18n/index.js";
|
|
18
21
|
/** 进程内事件总线(宿主观察 AgentEvents)。 */
|
|
19
22
|
export class AgentEventBus {
|
|
@@ -76,15 +79,16 @@ export function createHostApi(deps) {
|
|
|
76
79
|
let broker;
|
|
77
80
|
const warmingHandlers = [];
|
|
78
81
|
let notify = deps.notify;
|
|
79
|
-
const runners = new HostRunnerRegistry();
|
|
80
82
|
const writeErr = deps.stderr ?? ((text) => void process.stderr.write(text));
|
|
81
83
|
const log = deps.log ??
|
|
82
84
|
((level, message) => {
|
|
83
85
|
if (level === "warn" || level === "error")
|
|
84
86
|
writeErr(`ama: [host] ${message}\n`);
|
|
85
87
|
});
|
|
88
|
+
const runners = new HostRunnerRegistry({ log: (message, error) => log("error", message, error) });
|
|
86
89
|
const api = Object.freeze({
|
|
87
90
|
version: HOST_API_VERSION,
|
|
91
|
+
minor: HOST_API_MINOR,
|
|
88
92
|
agent: Object.freeze({ name: "ama", version: AMA_VERSION }),
|
|
89
93
|
env: Object.freeze({ ...(deps.env ?? process.env) }),
|
|
90
94
|
mode: deps.mode,
|
|
@@ -168,6 +172,7 @@ export function createHostApi(deps) {
|
|
|
168
172
|
}),
|
|
169
173
|
runners: Object.freeze({
|
|
170
174
|
provide: (runner) => runners.provide(runner),
|
|
175
|
+
onDone: (handler) => runners.onDone(handler),
|
|
171
176
|
}),
|
|
172
177
|
});
|
|
173
178
|
hostRunnerRegistries.set(api, runners);
|
package/dist/host/types.d.ts
CHANGED
|
@@ -14,11 +14,14 @@
|
|
|
14
14
|
* 以 `task(agent=…)` 入口出现;有宿主时内置外部 runner 一律不可用,实现归 W5-E / W5-G);
|
|
15
15
|
* `AgentEvents` 加 `subagent_start / subagent_end / plan_proposed / plan_resolved`。
|
|
16
16
|
* HOST_API_VERSION 不变。
|
|
17
|
+
* - (#226)host-api 1.1:可选面 `HostApi.runners.onDone`(宿主 runner 每轮结束的 {@link RunnerDoneEvent})
|
|
18
|
+
* 与 `HostApi.minor`。主版本 `HOST_API_VERSION` 要求宿主严格相等,只在破坏性变化时改;只加不改的
|
|
19
|
+
* 增量递增 {@link HOST_API_MINOR},宿主按成员是否存在做特性探测。
|
|
17
20
|
*/
|
|
18
21
|
import type { CacheMiss, WarmingDecisionHandler } from "../ai/cache/types.js";
|
|
19
22
|
import type { HookEvent } from "../hooks/types.js";
|
|
20
23
|
import type { ApprovalBroker, ApprovalDecision } from "../permissions/types.js";
|
|
21
|
-
import type { SubagentRunner, ToolDefinition } from "../tools/types.js";
|
|
24
|
+
import type { SubagentRunner, SubagentStatus, ToolDefinition } from "../tools/types.js";
|
|
22
25
|
import type { QuotaUpdateEvent } from "../agent/types-w6.js";
|
|
23
26
|
import type { PlanProposedEvent, PlanResolvedEvent, SubagentEndEvent, SubagentStartEvent } from "../agent/types-w5.js";
|
|
24
27
|
export type { ActionPreview, ActionPreviewTarget, ApprovalBroker, ApprovalDecision, ApprovalReason, ApprovalRequest, ApprovalRequestContext, } from "../permissions/types.js";
|
|
@@ -27,6 +30,8 @@ export type { JsonSchema } from "../ai/types.js";
|
|
|
27
30
|
export type { CacheMiss, WarmDecision, WarmingDecisionHandler } from "../ai/cache/types.js";
|
|
28
31
|
export declare const HOST_API_VERSION: 1;
|
|
29
32
|
export type HostApiVersion = typeof HOST_API_VERSION;
|
|
33
|
+
/** host-api 小版本:只加不改的可选面每加一批递增(1 = `runners.onDone`);旧运行时没有 `api.minor`,按 0 算。 */
|
|
34
|
+
export declare const HOST_API_MINOR: 1;
|
|
30
35
|
export type HostMode = "interactive" | "line" | "print" | "rpc";
|
|
31
36
|
export type InstructionSource = {
|
|
32
37
|
kind: "file";
|
|
@@ -123,9 +128,35 @@ export interface AgentEvents {
|
|
|
123
128
|
export type HostRunner = SubagentRunner & {
|
|
124
129
|
readonly description: string;
|
|
125
130
|
};
|
|
131
|
+
/**
|
|
132
|
+
* [#226] 宿主 runner 的一轮结束(`runners.onDone`):`start()` 之后第一轮、每次续聊(`send` / `interrupt`)
|
|
133
|
+
* 之后的下一轮各一次。`wait()` 返回时带 `text`,抛错时带 `error`;中断(任务的 signal abort 或 `stop()`)
|
|
134
|
+
* 时 `status` 为 `aborted`。时间都是 epoch 毫秒。
|
|
135
|
+
*/
|
|
136
|
+
export interface RunnerDoneEvent {
|
|
137
|
+
/** 宿主 runner 的 id(即 `task(agent=<id>)`)。 */
|
|
138
|
+
runner: string;
|
|
139
|
+
/** ama 的任务 id(`SubagentRunRequest.taskId`);直接调用 runner 时缺省。 */
|
|
140
|
+
taskId?: string;
|
|
141
|
+
/** runner 自己的会话 id(`RunnerHandle.id`)。 */
|
|
142
|
+
sessionId: string;
|
|
143
|
+
/** 第几轮,从 1 起。 */
|
|
144
|
+
round: number;
|
|
145
|
+
status: SubagentStatus;
|
|
146
|
+
isError: boolean;
|
|
147
|
+
/** `wait()` 返回的结果文本。 */
|
|
148
|
+
text?: string;
|
|
149
|
+
/** `wait()` 抛出的错误信息。 */
|
|
150
|
+
error?: string;
|
|
151
|
+
startedAt: number;
|
|
152
|
+
endedAt: number;
|
|
153
|
+
durationMs: number;
|
|
154
|
+
}
|
|
126
155
|
export type AgentEventName = keyof AgentEvents;
|
|
127
156
|
export interface HostApi {
|
|
128
157
|
readonly version: HostApiVersion;
|
|
158
|
+
/** [#226] host-api 小版本({@link HOST_API_MINOR});旧版本运行时没有它,按 0 算。 */
|
|
159
|
+
readonly minor?: number;
|
|
129
160
|
readonly agent: {
|
|
130
161
|
readonly name: "ama";
|
|
131
162
|
readonly version: string;
|
|
@@ -181,6 +212,11 @@ export interface HostApi {
|
|
|
181
212
|
*/
|
|
182
213
|
readonly runners?: {
|
|
183
214
|
provide(runner: HostRunner): () => void;
|
|
215
|
+
/**
|
|
216
|
+
* [#226] 宿主 runner 每轮结束时调用(只观察:抛错只记日志,不影响 `wait()` 的结果);返回值用于注销。
|
|
217
|
+
* 可选面(host-api 1.1):旧版本运行时没有它,用前判断 `typeof api.runners?.onDone === "function"`。
|
|
218
|
+
*/
|
|
219
|
+
onDone?(handler: (event: RunnerDoneEvent) => void | Promise<void>): () => void;
|
|
184
220
|
};
|
|
185
221
|
}
|
|
186
222
|
/** Runtime 持有的已激活适配器。 */
|
package/dist/host/types.js
CHANGED
|
@@ -14,5 +14,10 @@
|
|
|
14
14
|
* 以 `task(agent=…)` 入口出现;有宿主时内置外部 runner 一律不可用,实现归 W5-E / W5-G);
|
|
15
15
|
* `AgentEvents` 加 `subagent_start / subagent_end / plan_proposed / plan_resolved`。
|
|
16
16
|
* HOST_API_VERSION 不变。
|
|
17
|
+
* - (#226)host-api 1.1:可选面 `HostApi.runners.onDone`(宿主 runner 每轮结束的 {@link RunnerDoneEvent})
|
|
18
|
+
* 与 `HostApi.minor`。主版本 `HOST_API_VERSION` 要求宿主严格相等,只在破坏性变化时改;只加不改的
|
|
19
|
+
* 增量递增 {@link HOST_API_MINOR},宿主按成员是否存在做特性探测。
|
|
17
20
|
*/
|
|
18
21
|
export const HOST_API_VERSION = 1;
|
|
22
|
+
/** host-api 小版本:只加不改的可选面每加一批递增(1 = `runners.onDone`);旧运行时没有 `api.minor`,按 0 算。 */
|
|
23
|
+
export const HOST_API_MINOR = 1;
|
package/dist/host.d.ts
CHANGED
package/dist/host.js
CHANGED
package/dist/i18n/catalog.d.ts
CHANGED
|
@@ -642,6 +642,8 @@ declare const en: {
|
|
|
642
642
|
codemode: (calls: number, lines: number) => string;
|
|
643
643
|
done: string;
|
|
644
644
|
outputLines: (n: number) => string;
|
|
645
|
+
fetched: (title: string | undefined, size: string, format: string) => string;
|
|
646
|
+
fetchRedirect: (url: string) => string;
|
|
645
647
|
awaiting: (glyph: string) => string;
|
|
646
648
|
subagentRunning: (elapsed: string) => string;
|
|
647
649
|
running: (elapsed: string) => string;
|
|
@@ -741,6 +743,7 @@ declare const en: {
|
|
|
741
743
|
option: {
|
|
742
744
|
allow: string;
|
|
743
745
|
allowSession: string;
|
|
746
|
+
allowSessionHost: (host: string) => string;
|
|
744
747
|
deny: string;
|
|
745
748
|
};
|
|
746
749
|
hint: (arrows: string) => string;
|
|
@@ -748,6 +751,7 @@ declare const en: {
|
|
|
748
751
|
outcome: {
|
|
749
752
|
allow: (what: string) => string;
|
|
750
753
|
allowSession: (what: string) => string;
|
|
754
|
+
allowSessionHost: (what: string, host: string) => string;
|
|
751
755
|
deny: (what: string) => string;
|
|
752
756
|
cancelled: (what: string) => string;
|
|
753
757
|
};
|
|
@@ -1645,6 +1649,7 @@ declare const en: {
|
|
|
1645
1649
|
brokerNeedsAsk: string;
|
|
1646
1650
|
warmingNeedsFunction: string;
|
|
1647
1651
|
runnerNeedsIdStart: string;
|
|
1652
|
+
runnerDoneNeedsFunction: string;
|
|
1648
1653
|
notAdapter: (label: string) => string;
|
|
1649
1654
|
versionMismatch: (label: string, found: string, needed: number) => string;
|
|
1650
1655
|
noCreate: (label: string) => string;
|
|
@@ -40,6 +40,8 @@ export declare const en: {
|
|
|
40
40
|
option: {
|
|
41
41
|
allow: string;
|
|
42
42
|
allowSession: string;
|
|
43
|
+
/** web_fetch:本会话记住的是主机。 */
|
|
44
|
+
allowSessionHost: (host: string) => string;
|
|
43
45
|
deny: string;
|
|
44
46
|
};
|
|
45
47
|
hint: (arrows: string) => string;
|
|
@@ -48,6 +50,7 @@ export declare const en: {
|
|
|
48
50
|
outcome: {
|
|
49
51
|
allow: (what: string) => string;
|
|
50
52
|
allowSession: (what: string) => string;
|
|
53
|
+
allowSessionHost: (what: string, host: string) => string;
|
|
51
54
|
deny: (what: string) => string;
|
|
52
55
|
cancelled: (what: string) => string;
|
|
53
56
|
};
|
|
@@ -85,6 +88,7 @@ export declare const zh: {
|
|
|
85
88
|
option: {
|
|
86
89
|
allow: string;
|
|
87
90
|
allowSession: string;
|
|
91
|
+
allowSessionHost: (host: string) => string;
|
|
88
92
|
deny: string;
|
|
89
93
|
};
|
|
90
94
|
hint: (arrows: string) => string;
|
|
@@ -92,6 +96,7 @@ export declare const zh: {
|
|
|
92
96
|
outcome: {
|
|
93
97
|
allow: (what: string) => string;
|
|
94
98
|
allowSession: (what: string) => string;
|
|
99
|
+
allowSessionHost: (what: string, host: string) => string;
|
|
95
100
|
deny: (what: string) => string;
|
|
96
101
|
cancelled: (what: string) => string;
|
|
97
102
|
};
|
|
@@ -41,6 +41,8 @@ export const en = {
|
|
|
41
41
|
option: {
|
|
42
42
|
allow: "Allow",
|
|
43
43
|
allowSession: "Allow for this session",
|
|
44
|
+
/** web_fetch:本会话记住的是主机。 */
|
|
45
|
+
allowSessionHost: (host) => `Allow ${host} for this session`,
|
|
44
46
|
deny: "Deny",
|
|
45
47
|
},
|
|
46
48
|
hint: (arrows) => `${arrows} select · Enter confirm · v full input`,
|
|
@@ -49,6 +51,7 @@ export const en = {
|
|
|
49
51
|
outcome: {
|
|
50
52
|
allow: (what) => `Allowed ${what}`,
|
|
51
53
|
allowSession: (what) => `Allowed ${what} (similar calls won't ask again this session)`,
|
|
54
|
+
allowSessionHost: (what, host) => `Allowed ${what} (${host} won't ask again this session)`,
|
|
52
55
|
deny: (what) => `Denied ${what}`,
|
|
53
56
|
cancelled: (what) => `Approval cancelled (timeout or interrupt): ${what}`,
|
|
54
57
|
},
|
|
@@ -86,6 +89,7 @@ export const zh = {
|
|
|
86
89
|
option: {
|
|
87
90
|
allow: "允许",
|
|
88
91
|
allowSession: "本会话允许同类",
|
|
92
|
+
allowSessionHost: (host) => `本会话允许 ${host}`,
|
|
89
93
|
deny: "拒绝",
|
|
90
94
|
},
|
|
91
95
|
hint: (arrows) => `${arrows} 选择 · Enter 确认 · v 完整输入`,
|
|
@@ -93,6 +97,7 @@ export const zh = {
|
|
|
93
97
|
outcome: {
|
|
94
98
|
allow: (what) => `已允许 ${what}`,
|
|
95
99
|
allowSession: (what) => `已允许 ${what}(本会话同类不再询问)`,
|
|
100
|
+
allowSessionHost: (what, host) => `已允许 ${what}(本会话 ${host} 不再询问)`,
|
|
96
101
|
deny: (what) => `已拒绝 ${what}`,
|
|
97
102
|
cancelled: (what) => `审批已取消(超时或中断):${what}`,
|
|
98
103
|
},
|
|
@@ -57,6 +57,7 @@ export declare const en: {
|
|
|
57
57
|
brokerNeedsAsk: string;
|
|
58
58
|
warmingNeedsFunction: string;
|
|
59
59
|
runnerNeedsIdStart: string;
|
|
60
|
+
runnerDoneNeedsFunction: string;
|
|
60
61
|
notAdapter: (label: string) => string;
|
|
61
62
|
versionMismatch: (label: string, found: string, needed: number) => string;
|
|
62
63
|
noCreate: (label: string) => string;
|
|
@@ -115,6 +116,7 @@ export declare const zh: {
|
|
|
115
116
|
brokerNeedsAsk: string;
|
|
116
117
|
warmingNeedsFunction: string;
|
|
117
118
|
runnerNeedsIdStart: string;
|
|
119
|
+
runnerDoneNeedsFunction: string;
|
|
118
120
|
notAdapter: (label: string) => string;
|
|
119
121
|
versionMismatch: (label: string, found: string, needed: number) => string;
|
|
120
122
|
noCreate: (label: string) => string;
|
|
@@ -58,6 +58,7 @@ export const en = {
|
|
|
58
58
|
brokerNeedsAsk: "approvals.setBroker: broker needs ask()",
|
|
59
59
|
warmingNeedsFunction: "cache.onWarmingDecision: needs a function",
|
|
60
60
|
runnerNeedsIdStart: "runners.provide: needs a runner with id and start",
|
|
61
|
+
runnerDoneNeedsFunction: "runners.onDone: needs a function",
|
|
61
62
|
notAdapter: (label) => `${label}: not a host adapter module (missing hostApi / create export)`,
|
|
62
63
|
versionMismatch: (label, found, needed) => `${label}: host adapter hostApi=${found}, ama needs ${needed}`,
|
|
63
64
|
noCreate: (label) => `${label}: missing create(api) function`,
|
|
@@ -116,6 +117,7 @@ export const zh = {
|
|
|
116
117
|
brokerNeedsAsk: "approvals.setBroker:broker 需要 ask()",
|
|
117
118
|
warmingNeedsFunction: "cache.onWarmingDecision:需要函数",
|
|
118
119
|
runnerNeedsIdStart: "runners.provide:需要带 id 与 start 的 runner",
|
|
120
|
+
runnerDoneNeedsFunction: "runners.onDone:需要函数",
|
|
119
121
|
notAdapter: (label) => `${label}: 不是宿主适配器模块(缺少 hostApi / create 导出)`,
|
|
120
122
|
versionMismatch: (label, found, needed) => `${label}: 宿主适配器 hostApi=${found},ama 需要 ${needed}`,
|
|
121
123
|
noCreate: (label) => `${label}: 缺少 create(api) 函数`,
|
|
@@ -70,6 +70,9 @@ export declare const en: {
|
|
|
70
70
|
codemode: (calls: number, lines: number) => string;
|
|
71
71
|
done: string;
|
|
72
72
|
outputLines: (n: number) => string;
|
|
73
|
+
/** web_fetch:`<标题> · <大小> · markdown|text|json|binary`。 */
|
|
74
|
+
fetched: (title: string | undefined, size: string, format: string) => string;
|
|
75
|
+
fetchRedirect: (url: string) => string;
|
|
73
76
|
awaiting: (glyph: string) => string;
|
|
74
77
|
subagentRunning: (elapsed: string) => string;
|
|
75
78
|
running: (elapsed: string) => string;
|
|
@@ -139,6 +142,8 @@ export declare const zh: {
|
|
|
139
142
|
codemode: (calls: number, lines: number) => string;
|
|
140
143
|
done: string;
|
|
141
144
|
outputLines: (n: number) => string;
|
|
145
|
+
fetched: (title: string | undefined, size: string, format: string) => string;
|
|
146
|
+
fetchRedirect: (url: string) => string;
|
|
142
147
|
awaiting: (glyph: string) => string;
|
|
143
148
|
subagentRunning: (elapsed: string) => string;
|
|
144
149
|
running: (elapsed: string) => string;
|
|
@@ -73,6 +73,9 @@ export const en = {
|
|
|
73
73
|
codemode: (calls, lines) => `${plural(calls, "inner call")} · script printed ${plural(lines, "line")}`,
|
|
74
74
|
done: "done",
|
|
75
75
|
outputLines: (n) => `${plural(n, "line")} of output`,
|
|
76
|
+
/** web_fetch:`<标题> · <大小> · markdown|text|json|binary`。 */
|
|
77
|
+
fetched: (title, size, format) => [title, size, format].filter((p) => p !== undefined && p !== "").join(" · "),
|
|
78
|
+
fetchRedirect: (url) => `redirected to ${url} (not followed)`,
|
|
76
79
|
awaiting: (glyph) => `${glyph} awaiting approval`,
|
|
77
80
|
subagentRunning: (elapsed) => `subagent · running ${elapsed}`,
|
|
78
81
|
running: (elapsed) => `running · ${elapsed}`,
|
|
@@ -142,6 +145,8 @@ export const zh = {
|
|
|
142
145
|
codemode: (calls, lines) => `${calls} 个内层调用 · 脚本输出 ${lines} 行`,
|
|
143
146
|
done: "完成",
|
|
144
147
|
outputLines: (n) => `${n} 行输出`,
|
|
148
|
+
fetched: (title, size, format) => [title, size, format].filter((p) => p !== undefined && p !== "").join(" · "),
|
|
149
|
+
fetchRedirect: (url) => `重定向到 ${url}(未跟随)`,
|
|
145
150
|
awaiting: (glyph) => `${glyph} 等待确认`,
|
|
146
151
|
subagentRunning: (elapsed) => `子 Agent · 运行中 ${elapsed}`,
|
|
147
152
|
running: (elapsed) => `运行中 · ${elapsed}`,
|
|
@@ -196,6 +196,8 @@ export declare const en: {
|
|
|
196
196
|
codemode: (calls: number, lines: number) => string;
|
|
197
197
|
done: string;
|
|
198
198
|
outputLines: (n: number) => string;
|
|
199
|
+
fetched: (title: string | undefined, size: string, format: string) => string;
|
|
200
|
+
fetchRedirect: (url: string) => string;
|
|
199
201
|
awaiting: (glyph: string) => string;
|
|
200
202
|
subagentRunning: (elapsed: string) => string;
|
|
201
203
|
running: (elapsed: string) => string;
|
|
@@ -444,6 +446,8 @@ export declare const zh: {
|
|
|
444
446
|
codemode: (calls: number, lines: number) => string;
|
|
445
447
|
done: string;
|
|
446
448
|
outputLines: (n: number) => string;
|
|
449
|
+
fetched: (title: string | undefined, size: string, format: string) => string;
|
|
450
|
+
fetchRedirect: (url: string) => string;
|
|
447
451
|
awaiting: (glyph: string) => string;
|
|
448
452
|
subagentRunning: (elapsed: string) => string;
|
|
449
453
|
running: (elapsed: string) => string;
|
package/dist/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export { AMA_VERSION } from "./version.js";
|
|
8
8
|
export { AmaError, StartupError, isAmaError } from "./errors.js";
|
|
9
|
-
export { HOST_API_VERSION } from "./host/types.js";
|
|
9
|
+
export { HOST_API_MINOR, HOST_API_VERSION } from "./host/types.js";
|
|
10
10
|
export { RPC_PROTOCOL_VERSION } from "./rpc.js";
|
|
11
11
|
export { SESSION_FORMAT_VERSION } from "./session/types.js";
|
|
12
12
|
export { HOOK_EVENTS } from "./hooks/types.js";
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// 值
|
|
8
8
|
export { AMA_VERSION } from "./version.js";
|
|
9
9
|
export { AmaError, StartupError, isAmaError } from "./errors.js";
|
|
10
|
-
export { HOST_API_VERSION } from "./host/types.js";
|
|
10
|
+
export { HOST_API_MINOR, HOST_API_VERSION } from "./host/types.js";
|
|
11
11
|
export { RPC_PROTOCOL_VERSION } from "./rpc.js";
|
|
12
12
|
export { SESSION_FORMAT_VERSION } from "./session/types.js";
|
|
13
13
|
export { HOOK_EVENTS } from "./hooks/types.js";
|
|
@@ -58,8 +58,15 @@ export interface CommandInfo {
|
|
|
58
58
|
name: string;
|
|
59
59
|
args?: string;
|
|
60
60
|
description: string;
|
|
61
|
+
/** 只在该能力可用时列出(`/help`、补全、RPC `get_commands`);输入时照常分派。 */
|
|
62
|
+
requires?: "memory" | "task";
|
|
61
63
|
}
|
|
62
64
|
export declare const BUILTIN_COMMANDS: readonly CommandInfo[];
|
|
65
|
+
/**
|
|
66
|
+
* 能力关闭的命令不列出(capability-off.test.ts 锁定):memory = 记忆工具已注册(`memory.enabled`);
|
|
67
|
+
* task = 模型能直接调 `task`,或能经 codemode 脚本调(`task` 已注册且 codemode 在活动集)。
|
|
68
|
+
*/
|
|
69
|
+
export declare function availableCommands(runtime: Pick<Runtime, "tools">, session: Pick<AgentSession, "getTools">, list?: readonly CommandInfo[]): CommandInfo[];
|
|
63
70
|
export declare function parseSlash(line: string): {
|
|
64
71
|
name: string;
|
|
65
72
|
args: string;
|
|
@@ -66,7 +66,9 @@ export const BUILTIN_COMMANDS = [
|
|
|
66
66
|
command("cache", "cache", "[warm off|streaming|idle | fingerprint]"),
|
|
67
67
|
command("statusline", "statusline", "[full|compact]"),
|
|
68
68
|
command("plan", "plan", { key: "planArgs" }),
|
|
69
|
-
command("tasks", "tasks", "[id] | stop <id> | bg [id]"),
|
|
69
|
+
Object.assign(command("tasks", "tasks", "[id] | stop <id> | bg [id]"), {
|
|
70
|
+
requires: "task",
|
|
71
|
+
}),
|
|
70
72
|
command("agents", "agents"),
|
|
71
73
|
command("paste", "paste"),
|
|
72
74
|
command("interrupt", "interrupt", { key: "interruptArgs" }),
|
|
@@ -74,8 +76,24 @@ export const BUILTIN_COMMANDS = [
|
|
|
74
76
|
// [W6-C0] 第六波(W6-S / W6-T1 / W6-M 实现)
|
|
75
77
|
command("config", "config", "[key=value]"),
|
|
76
78
|
command("trace", "trace", { key: "traceArgs" }),
|
|
77
|
-
command("memory", "memory", { key: "memoryArgs" }),
|
|
79
|
+
Object.assign(command("memory", "memory", { key: "memoryArgs" }), {
|
|
80
|
+
requires: "memory",
|
|
81
|
+
}),
|
|
78
82
|
];
|
|
83
|
+
/**
|
|
84
|
+
* 能力关闭的命令不列出(capability-off.test.ts 锁定):memory = 记忆工具已注册(`memory.enabled`);
|
|
85
|
+
* task = 模型能直接调 `task`,或能经 codemode 脚本调(`task` 已注册且 codemode 在活动集)。
|
|
86
|
+
*/
|
|
87
|
+
export function availableCommands(runtime, session, list = BUILTIN_COMMANDS) {
|
|
88
|
+
const active = new Set(session.getTools().map((t) => t.name));
|
|
89
|
+
const registered = new Set(runtime.tools.list());
|
|
90
|
+
const has = (requires) => requires === "memory"
|
|
91
|
+
? registered.has("memory")
|
|
92
|
+
: requires === "task"
|
|
93
|
+
? active.has("task") || (active.has("codemode") && registered.has("task"))
|
|
94
|
+
: true;
|
|
95
|
+
return list.filter((c) => has(c.requires));
|
|
96
|
+
}
|
|
79
97
|
const ALIASES = { quit: "exit", q: "exit", "?": "help" };
|
|
80
98
|
export function parseSlash(line) {
|
|
81
99
|
const m = /^\/([A-Za-z?][\w-]*)(?:\s+([\s\S]*))?$/.exec(line.trim());
|
|
@@ -84,8 +102,8 @@ export function parseSlash(line) {
|
|
|
84
102
|
const raw = m[1].toLowerCase();
|
|
85
103
|
return { name: ALIASES[raw] ?? raw, args: (m[2] ?? "").trim() };
|
|
86
104
|
}
|
|
87
|
-
function helpText() {
|
|
88
|
-
const lines =
|
|
105
|
+
function helpText(ctx) {
|
|
106
|
+
const lines = availableCommands(ctx.runtime, ctx.session()).map((c) => `/${c.name}${c.args !== undefined ? ` ${c.args}` : ""} ${c.description}`);
|
|
89
107
|
const m = msg().report.commands;
|
|
90
108
|
return [...lines, m.skillLine, m.templateLine].join("\n");
|
|
91
109
|
}
|
|
@@ -142,7 +160,7 @@ export async function runSlashCommand(line, ctx) {
|
|
|
142
160
|
const session = ctx.session();
|
|
143
161
|
switch (name) {
|
|
144
162
|
case "help":
|
|
145
|
-
return { kind: "handled", message: helpText() };
|
|
163
|
+
return { kind: "handled", message: helpText(ctx) };
|
|
146
164
|
case "exit":
|
|
147
165
|
return { kind: "exit" };
|
|
148
166
|
case "new": {
|