@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.
Files changed (82) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/CHANGELOG.zh-CN.md +31 -0
  3. package/README.md +5 -0
  4. package/README.zh-CN.md +4 -0
  5. package/dist/acp.d.ts +2 -2
  6. package/dist/acp.js +2 -2
  7. package/dist/bundle/ama.cjs +1730 -446
  8. package/dist/cli/compose-agents.js +8 -0
  9. package/dist/cli/compose.d.ts +3 -0
  10. package/dist/cli/compose.js +1 -0
  11. package/dist/drivers/acp/client.d.ts +54 -5
  12. package/dist/drivers/acp/client.js +96 -9
  13. package/dist/drivers/acp/types.d.ts +69 -3
  14. package/dist/drivers/acp/types.js +30 -1
  15. package/dist/drivers/agents.js +1 -1
  16. package/dist/drivers/host-runners.d.ts +26 -1
  17. package/dist/drivers/host-runners.js +147 -0
  18. package/dist/hooks/matcher.d.ts +2 -2
  19. package/dist/hooks/matcher.js +3 -3
  20. package/dist/host/api-impl.d.ts +3 -0
  21. package/dist/host/api-impl.js +7 -2
  22. package/dist/host/types.d.ts +37 -1
  23. package/dist/host/types.js +5 -0
  24. package/dist/host.d.ts +1 -1
  25. package/dist/host.js +1 -1
  26. package/dist/i18n/catalog.d.ts +5 -0
  27. package/dist/i18n/messages/approval.d.ts +5 -0
  28. package/dist/i18n/messages/approval.js +5 -0
  29. package/dist/i18n/messages/drivers.d.ts +2 -0
  30. package/dist/i18n/messages/drivers.js +2 -0
  31. package/dist/i18n/messages/interactive-view.d.ts +5 -0
  32. package/dist/i18n/messages/interactive-view.js +5 -0
  33. package/dist/i18n/messages/interactive.d.ts +4 -0
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.js +1 -1
  36. package/dist/modes/commands-core.d.ts +7 -0
  37. package/dist/modes/commands-core.js +23 -5
  38. package/dist/modes/interactive/approval-dialog.js +17 -4
  39. package/dist/modes/interactive/commands.d.ts +2 -0
  40. package/dist/modes/interactive/commands.js +5 -1
  41. package/dist/modes/interactive/interactive-mode.d.ts +7 -0
  42. package/dist/modes/interactive/interactive-mode.js +14 -6
  43. package/dist/modes/interactive/tool-summary.d.ts +2 -0
  44. package/dist/modes/interactive/tool-summary.js +27 -0
  45. package/dist/modes/rpc/commands.js +4 -4
  46. package/dist/permissions/auto-safe.d.ts +3 -0
  47. package/dist/permissions/auto-safe.js +10 -3
  48. package/dist/permissions/classifier.js +1 -1
  49. package/dist/permissions/network-class.d.ts +28 -0
  50. package/dist/permissions/network-class.js +91 -0
  51. package/dist/permissions/pipeline.d.ts +1 -1
  52. package/dist/permissions/pipeline.js +24 -7
  53. package/dist/permissions/preview.js +3 -0
  54. package/dist/permissions/readonly-bash.d.ts +2 -2
  55. package/dist/permissions/readonly-bash.js +2 -2
  56. package/dist/permissions/rules.d.ts +1 -0
  57. package/dist/permissions/rules.js +5 -1
  58. package/dist/tools/presets.d.ts +8 -1
  59. package/dist/tools/presets.js +12 -3
  60. package/dist/tools/registry.d.ts +3 -1
  61. package/dist/tools/registry.js +4 -2
  62. package/dist/tools/task-ctl.d.ts +9 -0
  63. package/dist/tools/task-ctl.js +22 -3
  64. package/dist/tools/types.d.ts +4 -1
  65. package/dist/tools/web/fetch.d.ts +48 -0
  66. package/dist/tools/web/fetch.js +307 -0
  67. package/dist/tools/web/html-to-markdown.d.ts +31 -0
  68. package/dist/tools/web/html-to-markdown.js +522 -0
  69. package/dist/tools/web/url-guard.d.ts +59 -0
  70. package/dist/tools/web/url-guard.js +221 -0
  71. package/docs/en/guides/permissions.md +33 -11
  72. package/docs/en/reference/acp.md +7 -3
  73. package/docs/en/reference/host-api.md +61 -19
  74. package/docs/en/reference/rpc.md +7 -7
  75. package/docs/guides/agents.md +18 -1
  76. package/docs/guides/hooks.md +86 -8
  77. package/docs/guides/permissions.md +33 -11
  78. package/docs/guides/sandbox.md +6 -0
  79. package/docs/reference/acp.md +7 -3
  80. package/docs/reference/host-api.md +61 -19
  81. package/docs/reference/rpc.md +7 -7
  82. 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
  }
@@ -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
- * 其它工具 → 输入 JSON 文本。`|` 只在括号外分隔候选。只在 Pre/PostToolUse 生效。
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 → 正则:`*` 任意字符串(含 `/`),`?` 单个字符,其余字面量。 */
@@ -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
- * 其它工具 → 输入 JSON 文本。`|` 只在括号外分隔候选。只在 Pre/PostToolUse 生效。
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;
@@ -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";
@@ -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);
@@ -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 持有的已激活适配器。 */
@@ -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
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * `@armadra/agent/host` 子路径:宿主适配器类型 + HOST_API_VERSION(设计 §6.2、D7)。[B0] 所有。
3
3
  */
4
- export { HOST_API_VERSION } from "./host/types.js";
4
+ export { HOST_API_MINOR, HOST_API_VERSION } from "./host/types.js";
5
5
  export type * from "./host/types.js";
package/dist/host.js CHANGED
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * `@armadra/agent/host` 子路径:宿主适配器类型 + HOST_API_VERSION(设计 §6.2、D7)。[B0] 所有。
3
3
  */
4
- export { HOST_API_VERSION } from "./host/types.js";
4
+ export { HOST_API_MINOR, HOST_API_VERSION } from "./host/types.js";
@@ -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 = BUILTIN_COMMANDS.map((c) => `/${c.name}${c.args !== undefined ? ` ${c.args}` : ""} ${c.description}`);
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": {