@orion-agents/orion-code 0.1.2 → 0.1.3
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/README.md +75 -3
- package/README.zh-CN.md +79 -4
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +168 -54
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/types.d.ts +3 -1
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js.map +1 -1
- package/dist/framework/query.d.ts +3 -0
- package/dist/framework/query.d.ts.map +1 -1
- package/dist/framework/query.js +1 -0
- package/dist/framework/query.js.map +1 -1
- package/dist/framework/tool-scheduler.d.ts +61 -1
- package/dist/framework/tool-scheduler.d.ts.map +1 -1
- package/dist/framework/tool-scheduler.js +165 -47
- package/dist/framework/tool-scheduler.js.map +1 -1
- package/dist/framework/tool.d.ts +6 -0
- package/dist/framework/tool.d.ts.map +1 -1
- package/dist/framework/tool.js +2 -0
- package/dist/framework/tool.js.map +1 -1
- package/dist/ink-ui/screens/ReplScreen.d.ts +2 -1
- package/dist/ink-ui/screens/ReplScreen.d.ts.map +1 -1
- package/dist/ink-ui/screens/ReplScreen.js +65 -0
- package/dist/ink-ui/screens/ReplScreen.js.map +1 -1
- package/dist/print-ui/launch.d.ts +2 -1
- package/dist/print-ui/launch.d.ts.map +1 -1
- package/dist/print-ui/launch.js +7 -0
- package/dist/print-ui/launch.js.map +1 -1
- package/dist/runtime/agent-runtime-controller.d.ts +1 -0
- package/dist/runtime/agent-runtime-controller.d.ts.map +1 -1
- package/dist/runtime/agent-runtime-controller.js +24 -3
- package/dist/runtime/agent-runtime-controller.js.map +1 -1
- package/dist/runtime/agent-runtime-protocol.d.ts +13 -1
- package/dist/runtime/agent-runtime-protocol.d.ts.map +1 -1
- package/dist/runtime/agent-runtime-protocol.js +6 -0
- package/dist/runtime/agent-runtime-protocol.js.map +1 -1
- package/dist/runtime/chat-controller.d.ts +6 -0
- package/dist/runtime/chat-controller.d.ts.map +1 -1
- package/dist/runtime/chat-controller.js +13 -0
- package/dist/runtime/chat-controller.js.map +1 -1
- package/dist/runtime/goals/tools.d.ts.map +1 -1
- package/dist/runtime/goals/tools.js +14 -14
- package/dist/runtime/goals/tools.js.map +1 -1
- package/dist/runtime/goals/types.d.ts +6 -0
- package/dist/runtime/goals/types.d.ts.map +1 -1
- package/dist/runtime/goals/types.js.map +1 -1
- package/dist/runtime/turn-controller.d.ts.map +1 -1
- package/dist/runtime/turn-controller.js +3 -1
- package/dist/runtime/turn-controller.js.map +1 -1
- package/dist/runtime/ui-events.d.ts +17 -0
- package/dist/runtime/ui-events.d.ts.map +1 -1
- package/dist/runtime/ui-events.js.map +1 -1
- package/dist/services/config.d.ts.map +1 -1
- package/dist/services/config.js +2 -1
- package/dist/services/config.js.map +1 -1
- package/dist/services/global-config.d.ts +31 -0
- package/dist/services/global-config.d.ts.map +1 -1
- package/dist/services/global-config.js.map +1 -1
- package/dist/services/model-catalog.d.ts +2 -0
- package/dist/services/model-catalog.d.ts.map +1 -1
- package/dist/services/model-catalog.js +25 -15
- package/dist/services/model-catalog.js.map +1 -1
- package/dist/services/model-context.d.ts.map +1 -1
- package/dist/services/model-context.js +36 -0
- package/dist/services/model-context.js.map +1 -1
- package/dist/services/model-registry.d.ts.map +1 -1
- package/dist/services/model-registry.js +32 -0
- package/dist/services/model-registry.js.map +1 -1
- package/dist/services/tool-allowlist.d.ts +100 -0
- package/dist/services/tool-allowlist.d.ts.map +1 -0
- package/dist/services/tool-allowlist.js +250 -0
- package/dist/services/tool-allowlist.js.map +1 -0
- package/dist/terminal-ui/launch.d.ts +22 -2
- package/dist/terminal-ui/launch.d.ts.map +1 -1
- package/dist/terminal-ui/launch.js +145 -1
- package/dist/terminal-ui/launch.js.map +1 -1
- package/dist/tools/git.d.ts +4 -0
- package/dist/tools/git.d.ts.map +1 -1
- package/dist/tools/git.js +310 -3
- package/dist/tools/git.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +56 -7
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/sandbox.d.ts +139 -0
- package/dist/tools/sandbox.d.ts.map +1 -0
- package/dist/tools/sandbox.js +485 -0
- package/dist/tools/sandbox.js.map +1 -0
- package/dist/tools/web.d.ts.map +1 -1
- package/dist/tools/web.js +113 -42
- package/dist/tools/web.js.map +1 -1
- package/dist/tui-ui/launch.d.ts.map +1 -1
- package/dist/tui-ui/launch.js +46 -8
- package/dist/tui-ui/launch.js.map +1 -1
- package/dist/tui-ui/layout.d.ts.map +1 -1
- package/dist/tui-ui/layout.js +62 -0
- package/dist/tui-ui/layout.js.map +1 -1
- package/dist/tui-ui/runner.d.ts +6 -0
- package/dist/tui-ui/runner.d.ts.map +1 -1
- package/dist/tui-ui/runner.js +81 -1
- package/dist/tui-ui/runner.js.map +1 -1
- package/dist/tui-ui/state.d.ts +19 -1
- package/dist/tui-ui/state.d.ts.map +1 -1
- package/dist/tui-ui/state.js +35 -7
- package/dist/tui-ui/state.js.map +1 -1
- package/dist/tui-ui/terminal-image.d.ts +3 -13
- package/dist/tui-ui/terminal-image.d.ts.map +1 -1
- package/dist/tui-ui/terminal-image.js +5 -160
- package/dist/tui-ui/terminal-image.js.map +1 -1
- package/dist/utils/mask.d.ts +28 -0
- package/dist/utils/mask.d.ts.map +1 -0
- package/dist/utils/mask.js +54 -0
- package/dist/utils/mask.js.map +1 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **Goal-driven coding agent for the terminal.**
|
|
4
4
|
>
|
|
5
|
-
> v0.1.
|
|
5
|
+
> v0.1.3 — Goal continuity, model config & shell sandbox POC
|
|
6
6
|
|
|
7
7
|
[](LICENSE)
|
|
8
8
|
[](https://nodejs.org)
|
|
@@ -35,8 +35,8 @@ Orion Code is a terminal-based coding agent. It wraps LLM APIs in a harness of s
|
|
|
35
35
|
### Install & Run
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
# Install the v0.1.
|
|
39
|
-
npm install -g @orion-agents/orion-code@0.1.
|
|
38
|
+
# Install the v0.1.3 release:
|
|
39
|
+
npm install -g @orion-agents/orion-code@0.1.3
|
|
40
40
|
|
|
41
41
|
# Or run from a checked-out source tree:
|
|
42
42
|
npm ci
|
|
@@ -147,6 +147,78 @@ Configuration: `~/.orion-code/orion.json` | Priority: `CLI flags > config > env
|
|
|
147
147
|
The renderer is a runtime choice rather than a persisted setting: `orion` starts
|
|
148
148
|
the TUI, while `--ui terminal` selects the technical renderer.
|
|
149
149
|
|
|
150
|
+
### Project tool rules (`allowedTools`)
|
|
151
|
+
|
|
152
|
+
Per-project permission rules live under `projects["<absolute path>"].allowedTools`.
|
|
153
|
+
They are scoped to a single repository and are evaluated on top of each tool's own
|
|
154
|
+
policy — they can tighten the gate, never loosen it.
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"projects": {
|
|
159
|
+
"/Users/me/work/api": {
|
|
160
|
+
"allowedTools": [
|
|
161
|
+
"exec_command(git status*)",
|
|
162
|
+
"exec_command(npm test*)",
|
|
163
|
+
"ask:exec_command(git push*)",
|
|
164
|
+
"deny:read_file(*.env)"
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
- Grammar: `[allow|ask|deny:]tool[(subject glob)]`; `*` matches any run of characters,
|
|
172
|
+
`?` a single one, and `*` as the tool name matches every tool.
|
|
173
|
+
- The subject is the call's `command` / `file_path` / `path` / `url` / `pattern` / `query`
|
|
174
|
+
argument, whichever comes first.
|
|
175
|
+
- Conflicts resolve most-restrictive-first (`deny` > `ask` > `allow`), independent of order.
|
|
176
|
+
- `allow` only skips an interactive confirmation. It never overrides a tool's own `deny`,
|
|
177
|
+
never escapes plan mode, and never auto-approves a destructive invocation.
|
|
178
|
+
- Malformed entries are ignored and listed by `/config` rather than silently applied.
|
|
179
|
+
|
|
180
|
+
### Shell execution sandbox (`sandbox`)
|
|
181
|
+
|
|
182
|
+
`exec_command` can run inside an OS-level sandbox. This is a **security POC**: the
|
|
183
|
+
backend is probed at runtime and a configured-but-unusable sandbox **fails closed**
|
|
184
|
+
(refuses to run) rather than silently degrading to an unsandboxed execution.
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
{
|
|
188
|
+
"sandbox": {
|
|
189
|
+
"profile": "none",
|
|
190
|
+
"backend": "auto",
|
|
191
|
+
"allowNetwork": false,
|
|
192
|
+
"writableRoots": [],
|
|
193
|
+
"image": "alpine:latest"
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
- **Ownership** — `sandbox` lives in the global config (`~/.orion-code/orion.json`)
|
|
199
|
+
and can be overridden per project under `projects["<path>"].sandbox`. Project
|
|
200
|
+
keys win individually; absent keys inherit the global value.
|
|
201
|
+
- **`profile`** — `none` (default, no isolation, identical to the legacy `sh -c`),
|
|
202
|
+
`read-only` (no writes anywhere, no network), or `workspace-write` (writes
|
|
203
|
+
confined to the workspace + temp dirs, network still blocked unless
|
|
204
|
+
`allowNetwork: true`). An unknown value (e.g. written by a newer Orion) is a
|
|
205
|
+
hard failure, never a downgrade to `none`.
|
|
206
|
+
- **`backend`** — `auto` (default, picks the first usable backend), `seatbelt`
|
|
207
|
+
(macOS `sandbox-exec`), `bubblewrap` (Linux `bwrap`), or `docker`. Each backend
|
|
208
|
+
is validated with a real execution probe; `which`-based guessing is never used.
|
|
209
|
+
- **`image`** — required only for `docker`; names the container image to run in.
|
|
210
|
+
- **`writableRoots`** — extra host roots the `workspace-write` profile may write
|
|
211
|
+
to (in addition to the cwd and temp). Non-string / empty entries are dropped.
|
|
212
|
+
- **Migration / rollback** — adding the `sandbox` key is backward compatible: the
|
|
213
|
+
default `profile: "none"` reproduces the previous behaviour exactly. Removing the
|
|
214
|
+
key (or reverting to a pre-sandbox version) is a safe rollback; older Orion
|
|
215
|
+
simply ignores the unknown field. No secrets are stored, so no redaction is
|
|
216
|
+
required.
|
|
217
|
+
- **Known limitations** — `seatbelt` cannot be nested inside another sandbox (e.g.
|
|
218
|
+
when Orion itself runs inside an app sandbox); `docker` runs the command in a
|
|
219
|
+
separate process tree and only constrains what is bind-mounted (the image rootfs
|
|
220
|
+
is made read-only via `--read-only`); none of the backends constrain CPU/memory.
|
|
221
|
+
|
|
150
222
|
## Interactive Commands
|
|
151
223
|
|
|
152
224
|
| Command | Description |
|
package/README.zh-CN.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> **Orion Code — 通用 Agent 驾驭框架**
|
|
4
4
|
> 一个 CLI 驱动的编码 Agent,具备安全边界、工具编排、记忆系统和上下文管理。
|
|
5
5
|
>
|
|
6
|
-
> v0.1.
|
|
6
|
+
> v0.1.3 — Goal 连续性、模型配置与命令沙箱 POC
|
|
7
7
|
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](https://nodejs.org)
|
|
@@ -104,10 +104,10 @@ npm start -- --print "review the current git diff"
|
|
|
104
104
|
|
|
105
105
|
### 全局安装
|
|
106
106
|
|
|
107
|
-
固定安装 v0.1.
|
|
107
|
+
固定安装 v0.1.3:
|
|
108
108
|
|
|
109
109
|
```bash
|
|
110
|
-
npm install -g @orion-agents/orion-code@0.1.
|
|
110
|
+
npm install -g @orion-agents/orion-code@0.1.3
|
|
111
111
|
# 任意目录运行
|
|
112
112
|
orion
|
|
113
113
|
```
|
|
@@ -173,6 +173,69 @@ v0.1.2 只覆盖单 Session、单 Active Goal,不承诺多 Goal 调度或无
|
|
|
173
173
|
CLI 参数 > ~/.orion-code/orion.json > 环境变量 > 内部默认
|
|
174
174
|
```
|
|
175
175
|
|
|
176
|
+
### 项目级工具规则(`allowedTools`)
|
|
177
|
+
|
|
178
|
+
权限规则写在 `projects["<项目绝对路径>"].allowedTools`,只作用于该仓库。规则叠加在
|
|
179
|
+
工具自身的权限策略之上:**只能收紧,不能放宽**。
|
|
180
|
+
|
|
181
|
+
```json
|
|
182
|
+
{
|
|
183
|
+
"projects": {
|
|
184
|
+
"/Users/me/work/api": {
|
|
185
|
+
"allowedTools": [
|
|
186
|
+
"exec_command(git status*)",
|
|
187
|
+
"exec_command(npm test*)",
|
|
188
|
+
"ask:exec_command(git push*)",
|
|
189
|
+
"deny:read_file(*.env)"
|
|
190
|
+
]
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
- 语法:`[allow|ask|deny:]工具名[(参数通配)]`;`*` 匹配任意字符串,`?` 匹配单个字符,
|
|
197
|
+
工具名写 `*` 表示匹配所有工具。
|
|
198
|
+
- 参数通配匹配的是调用参数中的 `command` / `file_path` / `path` / `url` / `pattern` /
|
|
199
|
+
`query`(按此顺序取第一个非空字符串)。
|
|
200
|
+
- 冲突时按“最严格优先”解析:`deny` > `ask` > `allow`,与书写顺序无关。
|
|
201
|
+
- `allow` 仅用于免去交互确认,**不会**覆盖工具自身的 `deny`、**不会**突破 plan 模式的
|
|
202
|
+
只读约束,也**不会**自动放行破坏性调用。
|
|
203
|
+
- 无法解析的条目会被忽略而不是静默放行,可通过 `/config` 查看解析结果与非法条目。
|
|
204
|
+
|
|
205
|
+
### 命令执行沙箱(`sandbox`)
|
|
206
|
+
|
|
207
|
+
`exec_command` 可在操作系统级沙箱内执行。这是**安全 POC**:后端在运行
|
|
208
|
+
时实测探测,已配置但不可用的沙箱会**fail closed**(拒绝执行),而不会静默降级为
|
|
209
|
+
无沙箱执行。
|
|
210
|
+
|
|
211
|
+
```json
|
|
212
|
+
{
|
|
213
|
+
"sandbox": {
|
|
214
|
+
"profile": "none",
|
|
215
|
+
"backend": "auto",
|
|
216
|
+
"allowNetwork": false,
|
|
217
|
+
"writableRoots": [],
|
|
218
|
+
"image": "alpine:latest"
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
- **归属** — `sandbox` 位于全局配置(`~/.orion-code/orion.json`),并可在项目级
|
|
224
|
+
`projects["<项目绝对路径>"].sandbox` 覆盖。项目级按 key 单独覆盖,缺省 key 继承全局。
|
|
225
|
+
- **`profile`** — `none`(默认,不隔离,等价于旧版 `sh -c`)、`read-only`(任何位置不可写、无网络)、
|
|
226
|
+
`workspace-write`(写权限限定在工作区与临时目录,除非 `allowNetwork: true` 否则仍禁网)。
|
|
227
|
+
未知取值(如新版 Orion 写入的更严格值)一律硬失败,绝不降级为 `none`。
|
|
228
|
+
- **`backend`** — `auto`(默认,自动选首个可用后端)、`seatbelt`(macOS `sandbox-exec`)、
|
|
229
|
+
`bubblewrap`(Linux `bwrap`)或 `docker`。每个后端都用真实执行探针校验,从不靠 `which` 推断。
|
|
230
|
+
- **`image`** — 仅 `docker` 需要,指定运行所用的容器镜像。
|
|
231
|
+
- **`writableRoots`** — `workspace-write` 可写入的额外宿主根目录(除工作区与临时目录外)。
|
|
232
|
+
非字符串 / 空项会被丢弃。
|
|
233
|
+
- **迁移 / 回滚** — 新增 `sandbox` 完全向后兼容:默认 `profile: "none"` 与旧行为完全一致。
|
|
234
|
+
删除该 key(或回退到沙箱前的版本)是安全回滚;旧版 Orion 会忽略该未知字段。不涉及任何敏感信息,无需脱敏。
|
|
235
|
+
- **已知限制** — `seatbelt` 不能嵌套在另一沙箱内(例如 Orion 自身运行在应用沙箱中时);
|
|
236
|
+
`docker` 在独立进程树中运行命令,只约束被 bind-mount 的部分(镜像根文件系统已通过 `--read-only` 设为只读);
|
|
237
|
+
各后端均不限制 CPU / 内存。
|
|
238
|
+
|
|
176
239
|
### 环境变量
|
|
177
240
|
|
|
178
241
|
| 变量 | 默认值 | 说明 |
|
|
@@ -399,7 +462,16 @@ orion-code/
|
|
|
399
462
|
|
|
400
463
|
## 版本历史
|
|
401
464
|
|
|
402
|
-
### v0.1.
|
|
465
|
+
### v0.1.3(当前版本)
|
|
466
|
+
|
|
467
|
+
- `/model` 与 `/models` 拆分:`/model` 只显示当前模型信息,`/models` 承接交互式切换;
|
|
468
|
+
- `modelRegistry` 成为模型展示与切换的唯一事实源,静态 catalog 退为 legacy fallback;
|
|
469
|
+
- 内置 catalog 补全(火山方舟 / xf-yun / deepseek 等),未配置上下文时正确回退;
|
|
470
|
+
- 命令沙箱 POC、只读 Git 工具与工具 allowlist 的安全基线。
|
|
471
|
+
|
|
472
|
+
> 完整变更与交付状态(已发布 / 已合并 / 候选)以 `CHANGELOG.md` 为准。
|
|
473
|
+
|
|
474
|
+
### v0.1.2(已发布)
|
|
403
475
|
|
|
404
476
|
- 单 Session、单 Active Goal 的 typed continuation;
|
|
405
477
|
- restart/resume 后安全暂停,必须显式恢复;
|
|
@@ -439,6 +511,9 @@ npm run lint
|
|
|
439
511
|
|
|
440
512
|
# 格式化
|
|
441
513
|
npm run format
|
|
514
|
+
|
|
515
|
+
# 发布前只读检查(不会 tag / push / publish)
|
|
516
|
+
npm run release:check
|
|
442
517
|
```
|
|
443
518
|
|
|
444
519
|
---
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAIL,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,aAAa,EAEnB,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/commands/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EAIL,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,aAAa,EAEnB,MAAM,SAAS,CAAC;AA4UjB,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,eAAe,GAAG,SAAS,GAAG,MAAM,CAErF;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,YAAY,EAAE,CASrE;AA+nDD,iBAAe,UAAU,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAqVpF;AAyyDD,wBAAgB,WAAW,IAAI,YAAY,EAAE,CAE5C;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,cAAc,CAAC,YAAY,CAAC,GAAG,YAAY,EAAE,CAQ1F;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAElE;AAED,wBAAgB,eAAe,IAAI,MAAM,EAAE,CAE1C;AAED,OAAO,EAAE,UAAU,IAAI,WAAW,EAAE,CAAC"}
|
package/dist/commands/index.js
CHANGED
|
@@ -21,6 +21,7 @@ const ui_view_model_1 = require("../runtime/ui-view-model");
|
|
|
21
21
|
const task_manager_1 = require("../services/task-manager");
|
|
22
22
|
const agent_runner_1 = require("../services/agent-runner");
|
|
23
23
|
const config_1 = require("../services/config");
|
|
24
|
+
const tool_allowlist_1 = require("../services/tool-allowlist");
|
|
24
25
|
const box_1 = require("../ui/box");
|
|
25
26
|
const stream_markdown_1 = require("../ui/stream-markdown");
|
|
26
27
|
const progress_1 = require("../ui/progress");
|
|
@@ -40,7 +41,9 @@ const prompt_context_1 = require("../services/prompt-context");
|
|
|
40
41
|
const doctor_1 = require("../services/doctor");
|
|
41
42
|
const model_context_1 = require("../services/model-context");
|
|
42
43
|
const token_estimate_1 = require("../utils/token-estimate");
|
|
44
|
+
const mask_1 = require("../utils/mask");
|
|
43
45
|
const model_catalog_1 = require("../services/model-catalog");
|
|
46
|
+
const model_registry_1 = require("../services/model-registry");
|
|
44
47
|
const workspace_diff_1 = require("../services/workspace-diff");
|
|
45
48
|
const commit_plan_1 = require("../services/commit-plan");
|
|
46
49
|
const tool_artifacts_1 = require("../core/tool-artifacts");
|
|
@@ -107,8 +110,42 @@ function formatRendererStatus(ctx) {
|
|
|
107
110
|
: DIM('custom');
|
|
108
111
|
return `${BRAND(snapshot.renderer.name)} ${status} ${DIM(snapshot.renderer.capabilityLabels.join(', '))}`;
|
|
109
112
|
}
|
|
110
|
-
function
|
|
111
|
-
|
|
113
|
+
function getProfileByModelId(registry, modelId) {
|
|
114
|
+
if (!registry)
|
|
115
|
+
return undefined;
|
|
116
|
+
const normalized = modelId.trim();
|
|
117
|
+
if (!normalized)
|
|
118
|
+
return undefined;
|
|
119
|
+
if (registry.profiles.has(normalized))
|
|
120
|
+
return registry.profiles.get(normalized);
|
|
121
|
+
for (const profile of registry.profiles.values()) {
|
|
122
|
+
if (modelId === profile.model)
|
|
123
|
+
return profile;
|
|
124
|
+
}
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
function resolveModelFromRegistry(config, selector) {
|
|
128
|
+
if (!config.modelRegistry)
|
|
129
|
+
return undefined;
|
|
130
|
+
const trimmed = selector.trim();
|
|
131
|
+
if (!trimmed)
|
|
132
|
+
return undefined;
|
|
133
|
+
return (0, model_registry_1.lookupProfile)(config.modelRegistry, trimmed) ?? undefined;
|
|
134
|
+
}
|
|
135
|
+
function listConfiguredModelCatalogEntries(registry) {
|
|
136
|
+
if (!registry)
|
|
137
|
+
return [];
|
|
138
|
+
return registry.enabledProfiles.map(profile => {
|
|
139
|
+
const provider = registry.providers.get(profile.provider);
|
|
140
|
+
return {
|
|
141
|
+
name: profile.id,
|
|
142
|
+
alias: profile.aliases?.[0],
|
|
143
|
+
provider: provider?.displayName ?? profile.provider,
|
|
144
|
+
contextWindow: profile.resolvedContextWindow,
|
|
145
|
+
maxOutputTokens: profile.resolvedMaxOutputTokens,
|
|
146
|
+
source: `${profile.contextSource}/${profile.outputSource}`,
|
|
147
|
+
};
|
|
148
|
+
});
|
|
112
149
|
}
|
|
113
150
|
function formatLoopBudgetSource(stats) {
|
|
114
151
|
const source = stats.loopBudgetSource ?? 'unknown';
|
|
@@ -1319,19 +1356,35 @@ function showConfig(ctx) {
|
|
|
1319
1356
|
console.log();
|
|
1320
1357
|
console.log(HEADER('Configuration'));
|
|
1321
1358
|
console.log(DIM('─'.repeat(40)));
|
|
1359
|
+
const allowlist = (0, tool_allowlist_1.resolveProjectToolAllowlist)(ctx.cwd);
|
|
1360
|
+
const allowlistSummary = allowlist.rules.length === 0 && allowlist.invalid.length === 0
|
|
1361
|
+
? '(none)'
|
|
1362
|
+
: `${allowlist.rules.length} rule(s)` +
|
|
1363
|
+
(allowlist.invalid.length > 0 ? `, ${allowlist.invalid.length} invalid (ignored)` : '');
|
|
1322
1364
|
const summary = {
|
|
1323
1365
|
name: ctx.config.name,
|
|
1324
1366
|
model: ctx.config.model,
|
|
1325
1367
|
apiBaseUrl: ctx.config.apiBaseUrl || '(default OpenAI)',
|
|
1326
|
-
apiKey:
|
|
1368
|
+
apiKey: (0, mask_1.maskSecret)(ctx.config.apiKey),
|
|
1327
1369
|
mode: ctx.config.mode,
|
|
1328
1370
|
logLevel: ctx.config.logLevel,
|
|
1329
1371
|
toolConfirmation: ctx.config.toolConfirmation,
|
|
1372
|
+
allowedTools: allowlistSummary,
|
|
1330
1373
|
};
|
|
1331
1374
|
const llmSummary = ctx.llm?.getConfigSummary() ?? {};
|
|
1332
1375
|
for (const [key, val] of Object.entries(summary)) {
|
|
1333
1376
|
console.log(` ${ACCENT(key.padEnd(16))} ${DIM(val)}`);
|
|
1334
1377
|
}
|
|
1378
|
+
if (allowlist.rules.length > 0 || allowlist.invalid.length > 0) {
|
|
1379
|
+
console.log();
|
|
1380
|
+
console.log(HEADER(' Project allowedTools:'));
|
|
1381
|
+
for (const rule of allowlist.rules) {
|
|
1382
|
+
console.log(` ${ACCENT(rule.effect.padEnd(6))} ${DIM(rule.tool)}${DIM(rule.pattern ? `(${rule.pattern})` : '')}`);
|
|
1383
|
+
}
|
|
1384
|
+
for (const entry of allowlist.invalid) {
|
|
1385
|
+
console.log(` ${WARN('invalid')} ${DIM(entry)}`);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1335
1388
|
console.log();
|
|
1336
1389
|
console.log(HEADER(' LLM Settings:'));
|
|
1337
1390
|
for (const [key, val] of Object.entries(llmSummary)) {
|
|
@@ -1346,14 +1399,29 @@ function handleModel(ctx, args) {
|
|
|
1346
1399
|
if (!args || trimmedArgs === '?' || trimmedArgs === 'info') {
|
|
1347
1400
|
console.log();
|
|
1348
1401
|
if (ctx.llm) {
|
|
1349
|
-
const currentModel = ctx.llm.getModel();
|
|
1402
|
+
const currentModel = ctx.store.getSnapshot().currentModel || ctx.llm.getModel();
|
|
1403
|
+
const profile = getProfileByModelId(ctx.config.modelRegistry, currentModel);
|
|
1350
1404
|
const aliasEntry = (0, model_catalog_1.getModelCatalogEntry)(currentModel);
|
|
1351
|
-
const contextInfo =
|
|
1405
|
+
const contextInfo = profile
|
|
1406
|
+
? {
|
|
1407
|
+
id: profile.model,
|
|
1408
|
+
label: profile.displayName || profile.id,
|
|
1409
|
+
contextWindow: profile.resolvedContextWindow,
|
|
1410
|
+
maxOutputTokens: profile.resolvedMaxOutputTokens,
|
|
1411
|
+
source: 'config',
|
|
1412
|
+
matchedId: profile.model,
|
|
1413
|
+
}
|
|
1414
|
+
: (0, model_context_1.resolveModelContext)(currentModel);
|
|
1352
1415
|
const compactStats = getCommandAutoCompact(ctx, currentModel).getStats();
|
|
1353
1416
|
console.log(HEADER('Current Model'));
|
|
1354
1417
|
console.log(DIM('─'.repeat(40)));
|
|
1355
1418
|
console.log(` Model ${BRAND(currentModel)}`);
|
|
1356
|
-
if (
|
|
1419
|
+
if (profile) {
|
|
1420
|
+
console.log(` Alias ${ACCENT(profile.aliases?.[0] ? `(${profile.aliases[0]})` : 'none')}`);
|
|
1421
|
+
const provider = ctx.config.modelRegistry?.providers.get(profile.provider);
|
|
1422
|
+
console.log(` Provider ${DIM(provider?.displayName || profile.provider)}`);
|
|
1423
|
+
}
|
|
1424
|
+
else if (aliasEntry) {
|
|
1357
1425
|
console.log(` Alias ${ACCENT(aliasEntry.alias)}`);
|
|
1358
1426
|
console.log(` Provider ${DIM(aliasEntry.provider)}`);
|
|
1359
1427
|
}
|
|
@@ -1370,49 +1438,12 @@ function handleModel(ctx, args) {
|
|
|
1370
1438
|
console.log();
|
|
1371
1439
|
return { success: true };
|
|
1372
1440
|
}
|
|
1373
|
-
//
|
|
1374
|
-
if (trimmedArgs === 'list' || trimmedArgs === 'ls') {
|
|
1375
|
-
console.log();
|
|
1376
|
-
console.log(HEADER('Available Models'));
|
|
1377
|
-
console.log(DIM('─'.repeat(40)));
|
|
1378
|
-
const currentModel = ctx.llm?.getModel() || '';
|
|
1379
|
-
const modelPicker = (0, ui_view_model_1.createModelPickerState)({
|
|
1380
|
-
currentModel,
|
|
1381
|
-
models: (0, model_catalog_1.listModelCatalogEntries)().map(model => {
|
|
1382
|
-
const contextInfo = (0, model_context_1.resolveModelContext)(model.name);
|
|
1383
|
-
return {
|
|
1384
|
-
...model,
|
|
1385
|
-
contextWindow: contextInfo.contextWindow,
|
|
1386
|
-
maxOutputTokens: contextInfo.maxOutputTokens,
|
|
1387
|
-
source: contextInfo.source,
|
|
1388
|
-
};
|
|
1389
|
-
}),
|
|
1390
|
-
});
|
|
1391
|
-
for (const item of modelPicker.visibleItems) {
|
|
1392
|
-
const marker = item.isCurrent ? SUCCESS('●') : DIM('○');
|
|
1393
|
-
const alias = item.alias ? `(${item.alias})` : '';
|
|
1394
|
-
const context = `${formatTokenCount(item.contextWindow ?? 0)} ctx`;
|
|
1395
|
-
const current = item.isCurrent ? BRAND('(current)') : '';
|
|
1396
|
-
console.log(` ${marker} ${ACCENT(item.name)} ${DIM(alias)} ${DIM(context)} ${current}`);
|
|
1397
|
-
console.log(` ${DIM(item.provider ?? 'unknown')}`);
|
|
1398
|
-
}
|
|
1441
|
+
// /model list|ls|help 已移除,统一由 /models 承接(交互式选择切换)
|
|
1442
|
+
if (trimmedArgs === 'list' || trimmedArgs === 'ls' || trimmedArgs === 'help') {
|
|
1399
1443
|
console.log();
|
|
1400
|
-
console.log(
|
|
1401
|
-
console.log();
|
|
1402
|
-
|
|
1403
|
-
}
|
|
1404
|
-
// 显示帮助
|
|
1405
|
-
if (trimmedArgs === 'help') {
|
|
1406
|
-
console.log();
|
|
1407
|
-
console.log(HEADER('/model Command Help'));
|
|
1408
|
-
console.log(DIM('─'.repeat(40)));
|
|
1409
|
-
console.log();
|
|
1410
|
-
console.log(` ${ACCENT('/model')} Show current model`);
|
|
1411
|
-
console.log(` ${ACCENT('/model list')} Show available models`);
|
|
1412
|
-
console.log(` ${ACCENT('/model <name>')} Switch to specific model`);
|
|
1413
|
-
console.log(` ${ACCENT('/model <alias>')} Switch using alias (opus, sonnet, haiku)`);
|
|
1414
|
-
console.log();
|
|
1415
|
-
console.log(DIM(`Aliases: ${formatModelAliasHelp()}`));
|
|
1444
|
+
console.log(WARN(`/model ${trimmedArgs} was removed.`));
|
|
1445
|
+
console.log(DIM('Use /models to switch models interactively,'));
|
|
1446
|
+
console.log(DIM('or /model <name|alias> to switch directly, e.g. /model sonnet'));
|
|
1416
1447
|
console.log();
|
|
1417
1448
|
return { success: true };
|
|
1418
1449
|
}
|
|
@@ -1423,16 +1454,75 @@ function handleModel(ctx, args) {
|
|
|
1423
1454
|
return { success: false };
|
|
1424
1455
|
}
|
|
1425
1456
|
// 解析别名
|
|
1426
|
-
const
|
|
1457
|
+
const registryProfile = resolveModelFromRegistry(ctx.config, args);
|
|
1458
|
+
const resolvedModel = registryProfile ? registryProfile.model : (0, model_catalog_1.resolveModelAlias)(args);
|
|
1459
|
+
const resolvedProfileId = registryProfile ? registryProfile.id : resolvedModel;
|
|
1427
1460
|
ctx.llm.setModel(resolvedModel);
|
|
1428
1461
|
getCommandAutoCompact(ctx, resolvedModel);
|
|
1429
|
-
ctx.store.setState({ currentModel:
|
|
1430
|
-
console.log(SUCCESS(`✔ Model changed to ${BRAND(
|
|
1431
|
-
const contextInfo =
|
|
1462
|
+
ctx.store.setState({ currentModel: resolvedProfileId });
|
|
1463
|
+
console.log(SUCCESS(`✔ Model changed to ${BRAND(resolvedProfileId)}`));
|
|
1464
|
+
const contextInfo = registryProfile
|
|
1465
|
+
? {
|
|
1466
|
+
contextWindow: registryProfile.resolvedContextWindow,
|
|
1467
|
+
maxOutputTokens: registryProfile.resolvedMaxOutputTokens,
|
|
1468
|
+
source: 'resolved',
|
|
1469
|
+
}
|
|
1470
|
+
: (0, model_context_1.resolveModelContext)(resolvedModel);
|
|
1432
1471
|
console.log(DIM(` Context window ${formatTokenCount(contextInfo.contextWindow)} tokens (${contextInfo.source})`));
|
|
1433
1472
|
console.log();
|
|
1434
1473
|
return { success: true };
|
|
1435
1474
|
}
|
|
1475
|
+
/**
|
|
1476
|
+
* /models — 交互式选择切换模型。
|
|
1477
|
+
* 交互式渲染器返回结构化 modelPicker 请求(渲染器弹出选择层,选中即 /model <id>);
|
|
1478
|
+
* 非交互式渲染器直接打印候选列表并提示 /model <name|alias>。
|
|
1479
|
+
*/
|
|
1480
|
+
function handleModels(ctx, _args) {
|
|
1481
|
+
console.log();
|
|
1482
|
+
const currentModel = ctx.store.getSnapshot().currentModel || ctx.llm?.getModel() || '';
|
|
1483
|
+
const configuredModels = listConfiguredModelCatalogEntries(ctx.config.modelRegistry);
|
|
1484
|
+
const candidates = configuredModels.length > 0
|
|
1485
|
+
? configuredModels
|
|
1486
|
+
: (0, model_catalog_1.listModelCatalogEntries)().map(model => {
|
|
1487
|
+
const contextInfo = (0, model_context_1.resolveModelContext)(model.name);
|
|
1488
|
+
return {
|
|
1489
|
+
name: model.name,
|
|
1490
|
+
alias: model.alias,
|
|
1491
|
+
provider: model.provider,
|
|
1492
|
+
contextWindow: contextInfo.contextWindow,
|
|
1493
|
+
maxOutputTokens: contextInfo.maxOutputTokens,
|
|
1494
|
+
source: contextInfo.source,
|
|
1495
|
+
};
|
|
1496
|
+
});
|
|
1497
|
+
const ui = commandUICapabilities(ctx);
|
|
1498
|
+
if (ui.structuredPickers) {
|
|
1499
|
+
return {
|
|
1500
|
+
success: true,
|
|
1501
|
+
modelPicker: {
|
|
1502
|
+
models: candidates,
|
|
1503
|
+
currentModel,
|
|
1504
|
+
title: 'Switch Model',
|
|
1505
|
+
maxVisibleItems: 12,
|
|
1506
|
+
},
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
// 非交互式渲染器:打印候选列表
|
|
1510
|
+
console.log(HEADER('Available Models'));
|
|
1511
|
+
console.log(DIM('─'.repeat(40)));
|
|
1512
|
+
const modelPicker = (0, ui_view_model_1.createModelPickerState)({ currentModel, models: candidates });
|
|
1513
|
+
for (const item of modelPicker.visibleItems) {
|
|
1514
|
+
const marker = item.isCurrent ? SUCCESS('●') : DIM('○');
|
|
1515
|
+
const alias = item.alias ? `(${item.alias})` : '';
|
|
1516
|
+
const context = `${formatTokenCount(item.contextWindow ?? 0)} ctx`;
|
|
1517
|
+
const current = item.isCurrent ? BRAND('(current)') : '';
|
|
1518
|
+
console.log(` ${marker} ${ACCENT(item.name)} ${DIM(alias)} ${DIM(context)} ${current}`);
|
|
1519
|
+
console.log(` ${DIM(item.provider ?? 'unknown')}`);
|
|
1520
|
+
}
|
|
1521
|
+
console.log();
|
|
1522
|
+
console.log(DIM('Use /model <name|alias> to switch, e.g. /model sonnet'));
|
|
1523
|
+
console.log();
|
|
1524
|
+
return { success: true };
|
|
1525
|
+
}
|
|
1436
1526
|
function normalizePermissionMode(raw) {
|
|
1437
1527
|
const value = raw.trim().toLowerCase();
|
|
1438
1528
|
if (!value)
|
|
@@ -1733,6 +1823,7 @@ async function handleChat(ctx, input) {
|
|
|
1733
1823
|
costTracker: snapshot.costTracker,
|
|
1734
1824
|
permissionMode: snapshot.permissionMode,
|
|
1735
1825
|
toolConfirmation: ctx.config.toolConfirmation,
|
|
1826
|
+
toolAllowlist: (0, tool_allowlist_1.resolveProjectToolAllowlist)(ctx.cwd).evaluator,
|
|
1736
1827
|
toolContext: {
|
|
1737
1828
|
cwd: ctx.cwd,
|
|
1738
1829
|
config: {
|
|
@@ -3184,7 +3275,7 @@ const COMMANDS = [
|
|
|
3184
3275
|
{
|
|
3185
3276
|
name: 'model',
|
|
3186
3277
|
description: 'Show or change the current model',
|
|
3187
|
-
argumentHint: '[model|
|
|
3278
|
+
argumentHint: '[model|info]',
|
|
3188
3279
|
category: 'model',
|
|
3189
3280
|
priority: 10,
|
|
3190
3281
|
type: 'builtin',
|
|
@@ -3192,9 +3283,20 @@ const COMMANDS = [
|
|
|
3192
3283
|
risk: 'state-write',
|
|
3193
3284
|
execute: (ctx, args) => handleModel(ctx, args),
|
|
3194
3285
|
},
|
|
3286
|
+
{
|
|
3287
|
+
name: 'models',
|
|
3288
|
+
description: 'Switch the current model interactively',
|
|
3289
|
+
argumentHint: '',
|
|
3290
|
+
category: 'model',
|
|
3291
|
+
priority: 10,
|
|
3292
|
+
type: 'builtin',
|
|
3293
|
+
execution: 'builtin',
|
|
3294
|
+
risk: 'state-write',
|
|
3295
|
+
execute: (ctx, args) => handleModels(ctx, args),
|
|
3296
|
+
},
|
|
3195
3297
|
{
|
|
3196
3298
|
name: 'mode',
|
|
3197
|
-
aliases: ['
|
|
3299
|
+
aliases: ['perm'],
|
|
3198
3300
|
description: 'Show or change tool permission mode',
|
|
3199
3301
|
argumentHint: '[default|accept-edits|plan|auto|next]',
|
|
3200
3302
|
category: 'model',
|
|
@@ -3263,6 +3365,18 @@ const COMMANDS = [
|
|
|
3263
3365
|
rendererScope: ['tui'],
|
|
3264
3366
|
execute: () => ({ success: true }),
|
|
3265
3367
|
},
|
|
3368
|
+
{
|
|
3369
|
+
name: 'permissions',
|
|
3370
|
+
description: 'Set the tool confirmation policy (TUI). No argument opens a picker; with an argument applies immediately.',
|
|
3371
|
+
argumentHint: '[allow|ask|deny]',
|
|
3372
|
+
category: 'system',
|
|
3373
|
+
priority: 33,
|
|
3374
|
+
type: 'builtin',
|
|
3375
|
+
execution: 'renderer-local',
|
|
3376
|
+
risk: 'state-write',
|
|
3377
|
+
rendererScope: ['tui'],
|
|
3378
|
+
execute: () => ({ success: true }),
|
|
3379
|
+
},
|
|
3266
3380
|
{
|
|
3267
3381
|
name: 'redraw',
|
|
3268
3382
|
description: 'Redraw the TUI-owned live region',
|