@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
package/docs/guides/hooks.md
CHANGED
|
@@ -54,6 +54,12 @@
|
|
|
54
54
|
|
|
55
55
|
每个事件只接受上表的决策;`deny` 与 `block` 在两类事件间互换(`UserPromptSubmit` 返回 `deny` 视为 `block`,`PreToolUse` 返回 `block` 视为 `deny`),其它不接受的决策忽略并记 warning。
|
|
56
56
|
|
|
57
|
+
**范围**:
|
|
58
|
+
|
|
59
|
+
- `task` 子会话(`depth: 1`)以自己的 `sessionId` / `sessionFile` 跑 Hook:它内部的每次工具调用照常触发 PreToolUse / PostToolUse,将要结束时触发 `SubagentStop`(不是 `Stop`);它**不触发** `SessionStart` / `SessionEnd`——子会话的开始与结束由父会话的 `PreToolUse(task)` 与子会话的 `SubagentStop` 表达。
|
|
60
|
+
- 外部 Agent(`task(agent="claude")` 等)内部的工具调用在外部 CLI 自己的策略下运行,**不经 ama 的 Hook**;ama 这边只有父会话的 `PreToolUse(task)` / `PostToolUse(task)`,外部 Agent 也不触发 `SubagentStop`。
|
|
61
|
+
- codemode 脚本里的每次内层调用逐个触发(见下文「输入」的 codemode 段)。
|
|
62
|
+
|
|
57
63
|
任何事件的 stdout 都可以带 `"continue": false`:请求结束当前运行(reason 显示给用户)。`"suppressOutput": true` 被接受并合并,但当前界面本来就不显示 Hook 的 stdout,暂无可见效果。
|
|
58
64
|
|
|
59
65
|
## 输入(stdin)
|
|
@@ -146,14 +152,15 @@ Hook 在权限管线之前,是因为它是用户策略(可以改输入、可
|
|
|
146
152
|
|
|
147
153
|
只对 `PreToolUse` / `PostToolUse` 生效,其它事件忽略 matcher。
|
|
148
154
|
|
|
149
|
-
| 写法
|
|
150
|
-
|
|
|
151
|
-
| 缺省、`""`、`*`
|
|
152
|
-
| `bash`
|
|
153
|
-
| `write\|edit`
|
|
154
|
-
| `canvas_*`
|
|
155
|
-
| `bash(git push*)`
|
|
156
|
-
|
|
|
155
|
+
| 写法 | 匹配 |
|
|
156
|
+
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
157
|
+
| 缺省、`""`、`*` | 全部工具 |
|
|
158
|
+
| `bash` | 精确匹配工具名 |
|
|
159
|
+
| `write\|edit` | 多选(括号外的 `\|` 分隔) |
|
|
160
|
+
| `canvas_*` | glob:`*` 任意字符串、`?` 单个字符 |
|
|
161
|
+
| `bash(git push*)` | 先匹配工具名,再对参数文本做 glob:bash 是 `command`,文件类工具是 `path`,其它工具是输入的 JSON 文本 |
|
|
162
|
+
| `web_fetch(*docs.example.org*)` | `web_fetch` 的参数文本是整个 `url`(含协议与查询串,`*` 跨 `/`);与权限规则 `web_fetch(<pattern>)` 的 `host/path` 语义不同 |
|
|
163
|
+
| `/regex/flags` | 对工具名做正则 |
|
|
157
164
|
|
|
158
165
|
## 运行环境
|
|
159
166
|
|
|
@@ -171,6 +178,77 @@ Hook 在权限管线之前,是因为它是用户策略(可以改输入、可
|
|
|
171
178
|
|
|
172
179
|
每条 Hook 结束后发 `hook_executed{event, command, exitCode, durationMs}`(RPC 事件与宿主事件都有),超时或被信号杀死时 `exitCode` 为 null。
|
|
173
180
|
|
|
181
|
+
## 按能力接入
|
|
182
|
+
|
|
183
|
+
各项能力都用上面的事件接入,没有专门的事件。下表是对应关系,再往下是一份把各行放在一起的 hooks.json。
|
|
184
|
+
|
|
185
|
+
| 能力 | 事件与 matcher | 能做什么 |
|
|
186
|
+
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
187
|
+
| `web_fetch` | `PreToolUse` / `PostToolUse`,matcher `web_fetch` 或 `web_fetch(*docs.example.org*)`(括号内对 `toolInput.url` 文本做 glob) | PreToolUse 拒绝、改为询问或用 `updatedInput` 改 URL;PostToolUse 用 `additionalContext` 给抓回的正文加备注,`decision: "block"` 整页拒收 |
|
|
188
|
+
| 子 Agent 开始 | `PreToolUse`,matcher `task`;按类型筛用 `task(*"agent":"explore"*)`(括号内是输入的 JSON 文本) | 输入含 `agent`、`prompt`、`model`、`background`:可以 deny,也可以用 `updatedInput` 改 prompt 或换 agent |
|
|
189
|
+
| 子 Agent 结束 | `SubagentStop`(输入带 `lastAssistantText`) | `decision: "block"` + `reason`(或退出码 2)让子会话带着 reason 再跑一轮 |
|
|
190
|
+
| 子 Agent 内部的调用 | 照常的 `PreToolUse` / `PostToolUse`,输入 `depth: 1` | 与主会话相同;脚本按 `depth` 区分主会话与子会话 |
|
|
191
|
+
| 后台任务完成 | `SubagentStop`(后台的 ama 子会话结束时同样触发) | 桌面通知、记日志;没有单独的「后台任务完成」通知种类 |
|
|
192
|
+
| 外部 Agent | `PreToolUse` / `PostToolUse`,matcher `task(*"agent":"claude"*)` | 拦截或改写委派本身;外部 Agent 内部的调用不经 Hook(见「范围」) |
|
|
193
|
+
| codemode 编排子 Agent | 脚本里每个 `tools.task(...)` 都是一对 `PreToolUse(task)` / `PostToolUse(task)`,输入带 `viaCodemode` 与 `parentToolCallId` | 与模型直接调用 `task` 相同;按 `parentToolCallId` 把同一个脚本发起的任务归到一起 |
|
|
194
|
+
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"version": 1,
|
|
198
|
+
"hooks": {
|
|
199
|
+
"PreToolUse": [
|
|
200
|
+
{
|
|
201
|
+
"matcher": "web_fetch",
|
|
202
|
+
"hooks": [{ "type": "command", "command": "./.ama/fetch-guard.sh" }]
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"matcher": "task(*\"agent\":\"claude\"*)",
|
|
206
|
+
"hooks": [{ "type": "command", "command": "./.ama/log-delegation.sh" }]
|
|
207
|
+
}
|
|
208
|
+
],
|
|
209
|
+
"PostToolUse": [
|
|
210
|
+
{
|
|
211
|
+
"matcher": "web_fetch(*docs.example.org*)",
|
|
212
|
+
"hooks": [
|
|
213
|
+
{
|
|
214
|
+
"type": "command",
|
|
215
|
+
"command": "echo '{\"additionalContext\":\"docs.example.org 的页面对应最新版本,旧版本另查\"}'"
|
|
216
|
+
}
|
|
217
|
+
]
|
|
218
|
+
}
|
|
219
|
+
],
|
|
220
|
+
"SubagentStop": [{ "hooks": [{ "type": "command", "command": "./.ama/subagent-tests.sh" }] }]
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`web_fetch` 行随 `web_fetch` 工具一起提供(matcher 括号内取 `url` 的改动在同一批里)。它的 matcher 只认 URL 文本上的 `*` / `?` glob;权限规则 `web_fetch(<glob>)` 是另一套写法(对 `host/path` 匹配,裸域名含子域),见 [permissions.md](permissions.md)。只想放行少数站点时优先用权限规则或 `allowlist` 模式,Hook 适合需要脚本判断的情形。
|
|
226
|
+
|
|
227
|
+
只允许抓取文档站(`.ama/fetch-guard.sh`,配在上面的 `PreToolUse` `web_fetch` 下):
|
|
228
|
+
|
|
229
|
+
```sh
|
|
230
|
+
#!/bin/sh
|
|
231
|
+
url=$(node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>console.log(JSON.parse(s).toolInput.url))')
|
|
232
|
+
case "$url" in
|
|
233
|
+
https://docs.example.org/*|https://registry.npmjs.org/*) exit 0 ;;
|
|
234
|
+
esac
|
|
235
|
+
echo "只允许抓取文档站:$url" >&2
|
|
236
|
+
exit 2
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
子任务结束时跑测试,失败就让它接着修(`.ama/subagent-tests.sh`,配在 `SubagentStop` 下):
|
|
240
|
+
|
|
241
|
+
```sh
|
|
242
|
+
#!/bin/sh
|
|
243
|
+
input=$(cat)
|
|
244
|
+
# 已经被本 Hook 续跑过一次:放行,避免循环
|
|
245
|
+
case "$input" in *'"stopHookActive":true'*) exit 0 ;; esac
|
|
246
|
+
out=$(npm test --silent 2>&1) && exit 0
|
|
247
|
+
echo "Tests failed; fix them before reporting back:" >&2
|
|
248
|
+
printf '%s\n' "$out" | tail -n 20 >&2
|
|
249
|
+
exit 2
|
|
250
|
+
```
|
|
251
|
+
|
|
174
252
|
## 示例
|
|
175
253
|
|
|
176
254
|
阻止推送到 main:
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
|
|
35
35
|
### 审批对话框的来源标注
|
|
36
36
|
|
|
37
|
-
审批不只来自主会话。对话框(以及 RPC `permission_request.context`)标出请求从哪来,选项都只有「允许 / 本会话允许同类 /
|
|
37
|
+
审批不只来自主会话。对话框(以及 RPC `permission_request.context`)标出请求从哪来,选项都只有「允许 / 本会话允许同类 / 拒绝」(`web_fetch` 的第二项是「本会话允许 `<主机>`」):
|
|
38
38
|
|
|
39
39
|
| 来源 | 标题前缀 | 正文 | 谁来判定 |
|
|
40
40
|
| ------------------------------------------ | ----------------------------------------- | --------------------------------------------- | -------------------------------------------------------------------------- |
|
|
@@ -66,7 +66,28 @@ plan 模式的第 ③ 步看输入(实现 `src/permissions/pipeline.ts` 的 `p
|
|
|
66
66
|
| `tree`、`file`、`jq` | `tree -o`、`file -C`、`jq -i` 不算 |
|
|
67
67
|
| `git status / log / show / diff / rev-parse / blame / ls-files / branch` | git 全局选项只允许 `-C`、`--no-pager`;`--output`、`--ext-diff`、`--textconv` 与改分支的选项不算 |
|
|
68
68
|
|
|
69
|
-
比 auto 的安全名单窄:不含测试 / 构建运行器(`npm test`、`cargo build`
|
|
69
|
+
比 auto 的安全名单窄:不含测试 / 构建运行器(`npm test`、`cargo build` 会执行项目脚本)。`env` / `printenv`(会把环境变量里的密钥打进上下文)两份名单都没有。读机密路径(`cat .env`)不算只读。
|
|
70
|
+
|
|
71
|
+
## 网络类:web_fetch
|
|
72
|
+
|
|
73
|
+
`web_fetch` 是唯一的联网工具(只读取一个 URL,不做搜索),权限类 `network`:
|
|
74
|
+
|
|
75
|
+
| 模式 | `web_fetch` |
|
|
76
|
+
| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
77
|
+
| `default`、`auto-edit` | 询问;审批第二项是「本会话允许 `<主机>`」,之后同一主机(不分路径、端口与大小写)不再询问 |
|
|
78
|
+
| `plan` | 询问(只读但能把数据带出去;调研常要看文档,所以不是拒绝)。只读子 Agent 里按拒绝 |
|
|
79
|
+
| `allowlist` | 只放行 allow 规则命中的,其余拒绝 |
|
|
80
|
+
| `auto` | 规则层:URL 不是公网 http(s)(本机名、私网 / 元数据 IP、`file:`、带账号密码)直接拒绝;allow 规则、Hook allow、会话记忆放行;其余交分类器(公开文档、包注册表页算常规,查询串像密钥或长随机串时询问) |
|
|
81
|
+
| `full-auto` | 放行 |
|
|
82
|
+
|
|
83
|
+
无人值守(`-p`)时询问一律拒绝,用 `--allow 'web_fetch(<域名>)'` 放行。
|
|
84
|
+
|
|
85
|
+
- **规则** `web_fetch(<pattern>)` 对 `host/path`(不含协议、端口与查询串,主机名小写)匹配:不含 `/` 的只比主机,裸域名 `example.com` 命中它自己与全部子域,`*.example.com` 只命中子域;含 `/` 的对整个 `host/path` 做 glob,`*` 不跨 `/`、`**` 跨(`docs.example.com/api/**`)。deny 规则在所有模式下先判定:`permission.deny: ["web_fetch(*.internal.example.com)"]`。
|
|
86
|
+
- **执行层守卫**(所有模式都生效,`full-auto` 也一样):只收 `http:` / `https:`,拒带账号密码的 URL;URL ≤ 2048 字符、查询串 ≤ 1024;主机名必须含 `.`(`localhost`、`*.localhost`、无点主机一律拒绝,本机服务请走 bash 审批);解析出的**每一个**地址都不得落在环回、`0.0.0.0/8`、RFC 1918、`100.64/10`、链路本地(含云元数据 `169.254.169.254`、`100.100.100.200`、`fd00:ec2::254`)、保留与组播段、IPv6 ULA `fc00::/7`、`fe80::/10`、`fec0::/10`、`ff00::/8`,IPv4 映射 / NAT64 / 6to4 里嵌的 IPv4 按 IPv4 再判;连接钉在检查过的地址上,防解析后被换成内网(DNS 重绑定)。
|
|
87
|
+
- **重定向**:同主机(同协议同端口,或 http → https 升级)自动跟,最多 5 跳,每跳重新检查;跨主机不跟,结果告诉模型目标 URL,再调用一次要重新过权限。
|
|
88
|
+
- **限制**:总超时 30 s、首字节 15 s、响应体(解压后)≤ 5 MB;只发 GET,不带 cookie 与自定义头;输出缺省截到 50 000 字符(最多 200 000),截断时全文写到会话的 `outputs/<调用 id>.txt`,模型用 `offset` 续读。结果首行固定 `Content fetched from <URL> (untrusted):`,提醒模型这是外部内容。
|
|
89
|
+
- **关闭**:`tools.disabled: ["web_fetch"]`(用户、profile、项目级都可以)、`tools.default: ["-web_fetch"]`、宿主 `tools.disable("web_fetch")`,或 `permission.deny: ["web_fetch"]`。没有新增配置键与环境变量。
|
|
90
|
+
- `web_fetch` 在 ama 进程里运行,不受 bash 的 OS 沙箱与 `sandbox.network` 约束,也不走 `HTTPS_PROXY`(代理会自己解析主机名,钉不住地址);见 [sandbox.md](sandbox.md)「已知绕过」。
|
|
70
91
|
|
|
71
92
|
## 判定顺序
|
|
72
93
|
|
|
@@ -132,6 +153,7 @@ default / auto-edit 下,满足下面全部条件的 bash 调用免审批(`Pe
|
|
|
132
153
|
| 机密路径 | 读或写 `.env`、`.env.*`(`.env.example` / `.sample` / `.template` / `.dist` 除外)、`.ssh/`、`.gnupg/`、`.aws/`、`.kube/config`、`.docker/config.json`、`.netrc`、`.pgpass`、私钥(`id_rsa` 等、`*.pem`、`*.key`、`*.p12`、`*.pfx`、`*.jks`)、ama 的 `auth.json` |
|
|
133
154
|
| 受保护写入 | 写 `.git/` 内部、项目里的 `.ama/`(可能改 Hook 与配置)、项目目录外的任何路径(`/dev/null` 等除外) |
|
|
134
155
|
| 网络命令 | `curl`、`wget`、`ssh`、`scp`、`rsync`、`nc`、`gh`;`git push / pull / fetch / clone`;`npm / pnpm / yarn / bun install / add / ci / update / publish / dlx`、`npx`;`pip install`、`cargo install / publish`、`go get / install`、`brew / apt install`、`docker pull / push / login` 等 |
|
|
156
|
+
| 联网读取 | `web_fetch` 的 URL 不是公网 http(s):直接**拒绝**(不询问,见[网络类](#网络类web_fetch)) |
|
|
135
157
|
| 删除与回退 | `rm -r` / `rm -f`、`find -delete`、`git clean`、`git checkout -- …` / `git restore`、`git stash drop / clear`、`shred`、`truncate` |
|
|
136
158
|
| bash 里的路径 | 重定向目标(`>`、`>>`、`&>`、`tee`)、`cp` / `mv` / `mkdir` / `touch` / `ln` / `chmod` 的目标在项目外或受保护;命令参数里出现机密路径(`cat .env`) |
|
|
137
159
|
|
|
@@ -152,15 +174,15 @@ default / auto-edit 下,满足下面全部条件的 bash 调用免审批(`Pe
|
|
|
152
174
|
|
|
153
175
|
安全名单(`src/permissions/auto-safe.ts`,每条都有正反例测试):
|
|
154
176
|
|
|
155
|
-
| 命令
|
|
156
|
-
|
|
|
157
|
-
| `ls`、`cat`、`head`、`tail`、`wc`、`grep`、`egrep`、`fgrep`、`rg`、`pwd`、`echo`、`printf`、`which`、`
|
|
158
|
-
| `find`
|
|
159
|
-
| `git status / diff / log / show / rev-parse / blame / ls-files`(`diff / log / show` 不带 `--output`、`--ext-diff`、`--textconv`;`rg` 不带 `--pre`;`sort` 不带 `-o`;`date` 不带 `-s`)
|
|
160
|
-
| `git branch`
|
|
161
|
-
| `npm / pnpm / yarn test`、`… run test / lint / typecheck / build`、`pnpm / yarn lint / typecheck / build`、`npm t`
|
|
162
|
-
| `node --test`、`tsc --noEmit`、`vitest run`、`pnpm vitest run`、`pnpm exec vitest run`、`pytest`、`python -m pytest`
|
|
163
|
-
| `cargo test / check / build / clippy`、`go test / build / vet`、`make test / check / lint / build`
|
|
177
|
+
| 命令 | 限制 |
|
|
178
|
+
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
179
|
+
| `ls`、`cat`、`head`、`tail`、`wc`、`grep`、`egrep`、`fgrep`、`rg`、`pwd`、`echo`、`printf`、`which`、`true`、`false`、`sort`、`cut`、`tr`、`diff`、`basename`、`dirname`、`realpath`、`stat`、`file`、`du`、`df`、`date`、`whoami`、`uname`、`cd` / `pushd` / `popd`(之后的相对路径按新目录解析) | `env FOO=1 cmd` 按 `cmd` 判断;`env` / `printenv` 会打印整个环境(含密钥),不在名单里 |
|
|
180
|
+
| `find` | 不带 `-exec`、`-execdir`、`-ok`、`-okdir`、`-delete`、`-fprint*`、`-fls` |
|
|
181
|
+
| `git status / diff / log / show / rev-parse / blame / ls-files`(`diff / log / show` 不带 `--output`、`--ext-diff`、`--textconv`;`rg` 不带 `--pre`;`sort` 不带 `-o`;`date` 不带 `-s`) | 子命令前只允许 `-C dir`、`--no-pager`(`-c` 能改 pager,不算安全) |
|
|
182
|
+
| `git branch` | 只列出:不带 `-d / -D / --delete / -m / -M / -c / -C / -f / -u` 等 |
|
|
183
|
+
| `npm / pnpm / yarn test`、`… run test / lint / typecheck / build`、`pnpm / yarn lint / typecheck / build`、`npm t` | |
|
|
184
|
+
| `node --test`、`tsc --noEmit`、`vitest run`、`pnpm vitest run`、`pnpm exec vitest run`、`pytest`、`python -m pytest` | |
|
|
185
|
+
| `cargo test / check / build / clippy`、`go test / build / vet`、`make test / check / lint / build` | |
|
|
164
186
|
|
|
165
187
|
扩展:用户级配置 `permission.autoSafeCommands` 追加,例如 `["just test", "bun test", "make fmt"]`——按词前缀匹配(`just test` 命中 `just test --verbose`);含 `*` 的按通配匹配整段(`bun run test*`)。项目级配置不能追加(放宽)。追加的命令照样先过①:网络、删除、项目外写入仍然询问。
|
|
166
188
|
|
package/docs/guides/sandbox.md
CHANGED
|
@@ -204,6 +204,12 @@ which needs the user's approval.]`。按文本猜测,可能误报(同样的
|
|
|
204
204
|
文件,不是终端。不用 `--unshare-pid`:沙箱里能看到并向同用户的进程发信号。
|
|
205
205
|
- 后台 bash 由 ama 打开输出文件再交给子进程,输出目录无论在不在可写列表都能写。
|
|
206
206
|
- SDK(`createAgent`)不读 `sandbox.bash`,bash 不包装、权限照旧。
|
|
207
|
+
- **`web_fetch` 不在沙箱里**:它在 ama 进程内发请求,`sandbox.network: deny` 只断 bash 的网,不断 `web_fetch`。
|
|
208
|
+
这是有意的分工:bash 断网换免审批,联网读取改走显式受控的 `web_fetch`(权限类 `network`,default 询问、`-p`
|
|
209
|
+
拒绝、执行层拒私网与元数据地址,见 [permissions.md](permissions.md)「网络类」)。外泄面:获批的 `web_fetch`
|
|
210
|
+
能把上下文里的内容拼进 URL 的路径与查询串带出去(查询串 ≤ 1024 字符);`auto` 的分类器对像密钥的查询串询问,
|
|
211
|
+
但它不是安全边界。不需要联网时用 `tools.disabled: ["web_fetch"]` 或 `permission.deny: ["web_fetch"]` 关掉,
|
|
212
|
+
只允许少数站点时配 `--permission-mode allowlist` 加 `--allow 'web_fetch(<域名>)'`。
|
|
207
213
|
|
|
208
214
|
### 测试
|
|
209
215
|
|
package/docs/reference/acp.md
CHANGED
|
@@ -153,10 +153,10 @@ stdin 关闭后等已开始的运行结束再退出(0);`ama` 进程的 std
|
|
|
153
153
|
|
|
154
154
|
模型经 `task(agent="acp:<程序>")` 使用 ACP Agent(ama 自己是 `task(agent="acp:ama")`,见 [agents.md](../guides/agents.md)「在 task 里使用」)。
|
|
155
155
|
|
|
156
|
-
`AcpClient`(`@armadra/agent/acp`):`initialize`、`newSession`、`resumeSession`(优先,不回放)、`loadSession`、`listSessions`、`closeSession`、`prompt`、`setMode`、`cancel`。
|
|
156
|
+
`AcpClient`(`@armadra/agent/acp`):`initialize`、`authenticate`、`logout`、`newSession`、`resumeSession`(优先,不回放)、`loadSession`、`listSessions`、`listAllSessions`、`closeSession`、`prompt`、`setMode`、`cancel`。
|
|
157
157
|
|
|
158
158
|
- 声明的客户端能力:`session.configOptions: {}`(接 select 型配置项,不声明 boolean);不声明 `fs`(Agent 发来的 `fs/*` 请求回 method not found);`terminal` 只在构造参数给了
|
|
159
|
-
`onTerminal`
|
|
159
|
+
`onTerminal` 时声明(见下),`auth.terminal` 缺省也不声明(ama 没有可借给 Agent 的交互终端;宿主有交互终端时构造参数给 `authTerminal: true`,见下)。
|
|
160
160
|
- 线路两侧开 `$/cancel_request`:本端请求的 `signal` 在发出后 abort 会通知 Agent 撤回;Agent 撤回挂起的
|
|
161
161
|
`session/request_permission` / `elicitation/create` 时处理器的 `signal` abort,答 `cancelled` / `cancel`(处理器之后给的选择不作数)。
|
|
162
162
|
- `session/request_permission` 交给 `onPermission`;没有处理器时回首个 `reject_once`(无人值守)。
|
|
@@ -175,7 +175,11 @@ stdin 关闭后等已开始的运行结束再退出(0);`ama` 进程的 std
|
|
|
175
175
|
`clientCapabilities.terminal: true`,并把 `terminal/create|output|wait_for_exit|kill|release` 连同原样参数交给它,返回值即答复,
|
|
176
176
|
抛 `RpcError` 回对应错误;没给时声明 `terminal: false`、请求回 method not found(与旧版相同)。权限、cwd 限制与输出上限由宿主负责;
|
|
177
177
|
ama 自己作客户端时不给处理器。
|
|
178
|
-
- `
|
|
178
|
+
- 认证:`authenticate(methodId, signal?)` 发 `authenticate { methodId }`;`logout(signal?)` 发 `logout`(Agent 声明 `agentCapabilities.auth.logout` 才可用,`supportsLogout()`)。`authMethods` 保留 `initialize` 答的 id、name、description、type,terminal 型还有 `args`、`env`(`isTerminalAuthMethod` 判断类型)。terminal 型方法规范禁止经 `authenticate`:已知是 terminal 型时 `authenticate` 本地抛 -32602、不发请求,由宿主在终端里跑 agent 命令加该方法的 `args`、`env`,退出码 0 即成功。构造参数 `authTerminal: true` 才声明 `clientCapabilities.auth.terminal`,Agent 据此才给 terminal 型方法。开会话或发提示遇 -32000(auth_required)用 `isAuthRequiredError(error)` 判断,再把 `authMethods` 交给用户。ama 自己作客户端不给 `authTerminal`、不替人登录。
|
|
179
|
+
- `elicitation/complete`(Agent → 客户端通知,URL 型 elicitation 在带外完成):构造参数给了 `onElicitationComplete({ elicitationId })` 才接,并把 `initialize` 的 `elicitation` 声明成 `{ form: {}, url: {} }`(只给 `onElicitation` 时仍是 `{}`)。同一 `elicitationId` 的请求若还没答,它处理器的 `signal` 随之 abort、答 `{ action: "cancel" }`,宿主据此收掉卡片;参数里没有字符串 `elicitationId` 的通知被忽略。
|
|
180
|
+
- 表单字段:除 `string` / `number` / `integer` / `boolean` 外,`type: "array"` 是字符串多选(`items: { type: "string", enum }` 或带标题的 `items: { anyOf: [{ const, title }] }`,可带 `minItems` / `maxItems`),`string` 字段可带 `oneOf: [{ const, title }]`(带标题的单选)。答复 `content` 里多选值是字符串数组。`multiSelectOptions(field)` / `singleSelectOptions(field)` 把这几种写法收成 `{ const, title }` 列表(不是对应控件返回 undefined);不认识的 `type` 原样保留,不要当已知控件渲染。
|
|
181
|
+
- 会话管理:`listSessions(cwd?, cursor?, signal?)` 发 `session/list`(`supportsList()`),`listAllSessions(cwd?, signal?, maxPages = 100)` 跟 `nextCursor` 翻页取全;`closeSession(sessionId, signal?)` 发 `session/close`(`supportsClose()`)。规范要求关闭等同先取消,所以 `closeSession` 先让该会话挂起的权限请求答 `cancelled`、elicitation 答 `cancel`,别的会话不受影响。Agent 没声明能力会答 -32601,需要登录时答 -32000,错误码都原样抛出。
|
|
182
|
+
- `AcpClient.features`:`{ mcpServers, elicitation, configOptions, terminal, auth, elicitationComplete }`,宿主据此做特性检测。
|
|
179
183
|
|
|
180
184
|
`AcpDriver` 在客户端之上实现驱动契约(`AgentDriver`):
|
|
181
185
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 宿主适配器 API(@armadra/agent/host)
|
|
2
2
|
|
|
3
|
-
宿主适配器是一个本地 JS 模块,ama 启动时加载它并交给它一个 `HostApi`:它可以注册工具、追加系统提示、观察事件、回答审批、注入用户消息、在界面上显示通知与状态。Armadra 画布就是以宿主适配器的形式接入的(画布工具 `canvas_*` / `context_*` 都由适配器注册)。类型定义在 `src/host/types.ts`,从 `@armadra/agent/host` 导出,`HOST_API_VERSION = 1
|
|
3
|
+
宿主适配器是一个本地 JS 模块,ama 启动时加载它并交给它一个 `HostApi`:它可以注册工具、追加系统提示、观察事件、回答审批、注入用户消息、在界面上显示通知与状态。Armadra 画布就是以宿主适配器的形式接入的(画布工具 `canvas_*` / `context_*` 都由适配器注册)。类型定义在 `src/host/types.ts`,从 `@armadra/agent/host` 导出,`HOST_API_VERSION = 1`、`HOST_API_MINOR = 1`(host-api 1.1,见「版本」)。设计依据见 [design.md](../design/design.md) §6.2、§6.3、§11.1 第 13 步。
|
|
4
4
|
|
|
5
5
|
## 模块形状
|
|
6
6
|
|
|
@@ -46,26 +46,40 @@ export function create(api) {
|
|
|
46
46
|
| `create()` 抛错或 10 秒内未返回 | 6 |
|
|
47
47
|
| 返回的适配器缺 `id` | 6 |
|
|
48
48
|
|
|
49
|
+
## 版本
|
|
50
|
+
|
|
51
|
+
- **主版本** `HOST_API_VERSION`(`api.version`):宿主导出的 `hostApi` 必须与它严格相等,只在破坏性变化(删改已有成员或语义)时递增,并要求 ama 破坏性版本升级(`pnpm release:check`)。
|
|
52
|
+
- **小版本** `HOST_API_MINOR`(`api.minor`):只加不改的可选成员每加一批递增一次,旧宿主不受影响,不改主版本。旧运行时没有 `api.minor`,按 0 算。
|
|
53
|
+
- 宿主按成员是否存在做特性探测(如 `typeof api.runners?.onDone === "function"`),比按 `api.agent.version` 比较更可靠。
|
|
54
|
+
|
|
55
|
+
| 小版本 | ama 起始版本 | 新增 |
|
|
56
|
+
| ------ | ------------ | ----------------------------------------------------------------- |
|
|
57
|
+
| 0 | — | 现有全部成员(`cache?`、`runners?.provide` 等可选面同样要先判断) |
|
|
58
|
+
| 1 | 未发布 | `minor`、`runners?.onDone`(见「runner 完成事件」) |
|
|
59
|
+
|
|
49
60
|
## HostApi
|
|
50
61
|
|
|
51
|
-
| 成员 | 说明
|
|
52
|
-
| ----------------------------------------------- |
|
|
53
|
-
| `version` | `HOST_API_VERSION`
|
|
54
|
-
| `agent` | `{ name: "ama", version }`
|
|
55
|
-
| `env` | 启动时环境变量的冻结副本
|
|
56
|
-
| `mode` | `interactive` / `line` / `print` / `rpc`
|
|
57
|
-
| `session.id()` / `file()` / `cwd()` / `model()` | 当前会话(换会话后跟随新会话);`file()` 在首次请求落盘前为 `undefined`
|
|
58
|
-
| `tools.register(tool)` | 注册工具(形状见下);名字须匹配 `^[a-z][a-z0-9_]{1,63}$`,同名已存在抛 `tool_exists`。建议加前缀(`canvas_*`)
|
|
59
|
-
| `tools.disable(name)` | 隐藏内置工具(如 Armadra 禁用 `task`),系统提示的工具节随之不再列它
|
|
60
|
-
| `tools.list()` | 当前全部工具名
|
|
61
|
-
| `instructions.add(source)` | 追加到系统提示最后的 `host` 节;`{ kind: "file", path }` 或 `{ kind: "text", text, name? }`
|
|
62
|
-
| `events.on(name, handler)` | 观察事件(下表),返回注销函数
|
|
63
|
-
| `approvals.setBroker(broker)` | 设置审批回答者(见「审批」)
|
|
64
|
-
| `messages.sendUser(text, origin?)` | 以 user 消息注入:空闲时开始一次运行(`"started"`),运行中按 steer 入队(`"queued"`);`origin` 缺省 `"host"`,落盘在消息上,界面标 `↳ host`
|
|
65
|
-
| `ui.notify(message, level?)` | 交互 / line 模式进消息区;rpc 模式变成 `notification` 事件(stderr 另有一份);print 模式写 stderr
|
|
66
|
-
| `ui.setStatus(key, text?)` | 状态栏的宿主项;`text` 为空或缺省则移除该键
|
|
67
|
-
| `log(level, message, detail?)` | 日志;`warn` / `error` 写 stderr
|
|
68
|
-
| `cache?.onWarmingDecision(handler)` | 缓存保温的否决钩子(见「缓存保温」);可选面,旧版本运行时没有它,用前判断 `api.cache !== undefined`
|
|
62
|
+
| 成员 | 说明 |
|
|
63
|
+
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
64
|
+
| `version` | `HOST_API_VERSION` |
|
|
65
|
+
| `agent` | `{ name: "ama", version }` |
|
|
66
|
+
| `env` | 启动时环境变量的冻结副本 |
|
|
67
|
+
| `mode` | `interactive` / `line` / `print` / `rpc` |
|
|
68
|
+
| `session.id()` / `file()` / `cwd()` / `model()` | 当前会话(换会话后跟随新会话);`file()` 在首次请求落盘前为 `undefined` |
|
|
69
|
+
| `tools.register(tool)` | 注册工具(形状见下);名字须匹配 `^[a-z][a-z0-9_]{1,63}$`,同名已存在抛 `tool_exists`。建议加前缀(`canvas_*`) |
|
|
70
|
+
| `tools.disable(name)` | 隐藏内置工具(如 Armadra 禁用 `task`),系统提示的工具节随之不再列它 |
|
|
71
|
+
| `tools.list()` | 当前全部工具名 |
|
|
72
|
+
| `instructions.add(source)` | 追加到系统提示最后的 `host` 节;`{ kind: "file", path }` 或 `{ kind: "text", text, name? }` |
|
|
73
|
+
| `events.on(name, handler)` | 观察事件(下表),返回注销函数 |
|
|
74
|
+
| `approvals.setBroker(broker)` | 设置审批回答者(见「审批」) |
|
|
75
|
+
| `messages.sendUser(text, origin?)` | 以 user 消息注入:空闲时开始一次运行(`"started"`),运行中按 steer 入队(`"queued"`);`origin` 缺省 `"host"`,落盘在消息上,界面标 `↳ host` |
|
|
76
|
+
| `ui.notify(message, level?)` | 交互 / line 模式进消息区;rpc 模式变成 `notification` 事件(stderr 另有一份);print 模式写 stderr |
|
|
77
|
+
| `ui.setStatus(key, text?)` | 状态栏的宿主项;`text` 为空或缺省则移除该键 |
|
|
78
|
+
| `log(level, message, detail?)` | 日志;`warn` / `error` 写 stderr |
|
|
79
|
+
| `cache?.onWarmingDecision(handler)` | 缓存保温的否决钩子(见「缓存保温」);可选面,旧版本运行时没有它,用前判断 `api.cache !== undefined` |
|
|
80
|
+
| `runners?.provide(runner)` | 注入子 Agent runner:`id` 即 `task(agent=<id>)` 的名字,`description` 进 task 工具描述;有宿主时 ama 不自起外部 CLI,只认这里注入的;返回注销函数 |
|
|
81
|
+
| `runners?.onDone(handler)` | 宿主 runner 每轮结束时回调(见「runner 完成事件」),返回注销函数;host-api 1.1 起,用前判断 `typeof api.runners?.onDone === "function"` |
|
|
82
|
+
| `minor?` | `HOST_API_MINOR`(见「版本」);旧运行时缺省 |
|
|
69
83
|
|
|
70
84
|
`create()` 期间会话还没组装完:`session.*` 返回启动时确定的值,`sendUser` 会以 `busy` 拒绝——要在启动时发消息,等 `session_start` 事件之后再调用。
|
|
71
85
|
|
|
@@ -99,6 +113,8 @@ interface ToolResult {
|
|
|
99
113
|
|
|
100
114
|
宿主工具与内置工具走同一条路径:schema 校验 → 命令式 Hook PreToolUse → 权限管线(按 `permission` 分类)→ 审批 → 执行 → PostToolUse。在 codemode 脚本里也能以 `tools.<name>()` 调用。
|
|
101
115
|
|
|
116
|
+
导出的 `ToolPermission` 类型多了 `"network"`(内置 `web_fetch` 用,按 execute 判定、plan 下询问,见 [permissions.md](../guides/permissions.md)「网络类」);宿主经 `tools.register` 注册的工具仍只接受 `read` / `write` / `execute`,`HOST_API_VERSION` 不变。
|
|
117
|
+
|
|
102
118
|
## 事件
|
|
103
119
|
|
|
104
120
|
`events.on` 的处理器只观察:抛错只记日志,不影响运行;处理器依次调用并等待,`session_shutdown` 被 await(退出前可以做清理)。
|
|
@@ -152,6 +168,32 @@ interface WarmDecision {
|
|
|
152
168
|
|
|
153
169
|
返回 `"warm"` / `"stop"`(可以是 Promise)。返回 `"stop"` 即不发并停止本轮保温(内置决策本是 warm 时,停止原因记为 `declined`);返回 `"warm"` 可以覆盖内置的 stop。处理器出错回落内置决策。注册多个时最后注册且未注销的那个生效;返回值是注销函数。保温机制本身见 [providers.md](../guides/providers.md)「缓存」。
|
|
154
170
|
|
|
171
|
+
## runner 完成事件
|
|
172
|
+
|
|
173
|
+
`api.runners.onDone(handler)`(host-api 1.1):经 `runners.provide` 注入的 runner 每跑完一轮,以下面的载荷调用一次 `handler`。一轮 = `start()` 之后的第一轮,或 `send` / `interrupt` 之后的下一轮;以 ama 对该轮 `wait()` 的落定为准(返回、抛错、或任务被中断)。
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
interface RunnerDoneEvent {
|
|
177
|
+
runner: string; // 宿主 runner 的 id(即 task(agent=<id>))
|
|
178
|
+
taskId?: string; // ama 的任务 id(SubagentRunRequest.taskId);直接调用 runner 时缺省
|
|
179
|
+
sessionId: string; // runner 自己的会话 id(RunnerHandle.id)
|
|
180
|
+
round: number; // 第几轮,从 1 起
|
|
181
|
+
status: "completed" | "failed" | "aborted" | "max_turns" | "interrupted";
|
|
182
|
+
isError: boolean; // status 为 failed 或 aborted
|
|
183
|
+
text?: string; // wait() 返回的结果文本
|
|
184
|
+
error?: string; // wait() 抛出的错误信息
|
|
185
|
+
startedAt: number; // 这一轮开始(start / send / interrupt 的时刻),epoch 毫秒
|
|
186
|
+
endedAt: number; // 落定时刻
|
|
187
|
+
durationMs: number;
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
- `status` 与任务注册表一致:任务的 `signal` 已 abort 或调过 `stop()` → `aborted`;否则取结果自报的终态(`max_turns` / `interrupted` 等);再按 `isError` 分 `failed` / `completed`。`wait()` 抛错且未中断 → `failed`,带 `error`。
|
|
192
|
+
- 只观察:处理器不被 await,抛错(含 Promise 拒绝)只经 `log` 记一条 error,不影响 `wait()` 的结果与其它处理器。
|
|
193
|
+
- 同一轮里 ama 可能多次调用 `wait()`(任务入口与注册表各一次),事件只发一次;该轮落定后、没有新的 `send` / `interrupt` 时再调用 `wait()` 不再发。运行中的 `interrupt` 由当前这一轮跟到结束,不另起一轮。
|
|
194
|
+
- 只覆盖宿主注入的 runner;ama 自己的子会话(`general` / `explore` / `plan`)仍看 `subagent_start` / `subagent_end` 事件。
|
|
195
|
+
- 用途:宿主不必轮询「协调者是否还在等这个任务」——收到事件即知道 ama 这一侧的任务结束了。
|
|
196
|
+
|
|
155
197
|
## 退出
|
|
156
198
|
|
|
157
199
|
- 进程退出:`session_shutdown` 事件(await)→ SessionEnd Hook(`reason: "exit"`)→ `adapter.dispose()` → 会话 dispose。`dispose` 抛错只记 warning。
|
package/docs/reference/rpc.md
CHANGED
|
@@ -119,13 +119,13 @@
|
|
|
119
119
|
|
|
120
120
|
### 工具、权限、发现
|
|
121
121
|
|
|
122
|
-
| 命令 | 参数 | `data`
|
|
123
|
-
| --------------------- | ---------------------------------------------------------------------- |
|
|
124
|
-
| `get_tools` | — | `{ tools: { name, description, parameters, permission, active }[] }`(注册表全部工具,`active` 表示模型当前能看到)
|
|
125
|
-
| `set_active_tools` | `names: string[]` | `{ names }`(生效后的活动工具名)
|
|
126
|
-
| `set_permission_mode` | `mode: plan \| allowlist \| default \| auto-edit \| auto \| full-auto` | `{ mode }`(未知模式 → `invalid_arguments`)
|
|
127
|
-
| `get_commands` | — | `{ commands: { name, description?, source: "builtin" \| "template" \| "skill" }[] }`;Skill 名写作 `skill
|
|
128
|
-
| `get_skills` | — | `{ skills: { name, description, location, … }[] }`(已发现的 Skill;`location` 是 SKILL.md 路径)
|
|
122
|
+
| 命令 | 参数 | `data` |
|
|
123
|
+
| --------------------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
124
|
+
| `get_tools` | — | `{ tools: { name, description, parameters, permission, active }[] }`(注册表全部工具,`active` 表示模型当前能看到) |
|
|
125
|
+
| `set_active_tools` | `names: string[]` | `{ names }`(生效后的活动工具名) |
|
|
126
|
+
| `set_permission_mode` | `mode: plan \| allowlist \| default \| auto-edit \| auto \| full-auto` | `{ mode }`(未知模式 → `invalid_arguments`) |
|
|
127
|
+
| `get_commands` | — | `{ commands: { name, description?, source: "builtin" \| "template" \| "skill" }[] }`;Skill 名写作 `skill:<名>`;只列当前可用的内置命令(记忆关闭时没有 `memory`,task 不可调时没有 `tasks`) |
|
|
128
|
+
| `get_skills` | — | `{ skills: { name, description, location, … }[] }`(已发现的 Skill;`location` 是 SKILL.md 路径) |
|
|
129
129
|
|
|
130
130
|
### 计划与任务(第五波)
|
|
131
131
|
|