@orion-agents/orion-code 0.1.2 → 0.1.4

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 (237) hide show
  1. package/README.md +96 -3
  2. package/README.zh-CN.md +109 -4
  3. package/dist/commands/index.d.ts.map +1 -1
  4. package/dist/commands/index.js +168 -54
  5. package/dist/commands/index.js.map +1 -1
  6. package/dist/commands/types.d.ts +3 -1
  7. package/dist/commands/types.d.ts.map +1 -1
  8. package/dist/commands/types.js.map +1 -1
  9. package/dist/framework/query.d.ts +3 -0
  10. package/dist/framework/query.d.ts.map +1 -1
  11. package/dist/framework/query.js +1 -0
  12. package/dist/framework/query.js.map +1 -1
  13. package/dist/framework/tool-scheduler.d.ts +61 -1
  14. package/dist/framework/tool-scheduler.d.ts.map +1 -1
  15. package/dist/framework/tool-scheduler.js +165 -47
  16. package/dist/framework/tool-scheduler.js.map +1 -1
  17. package/dist/framework/tool.d.ts +9 -0
  18. package/dist/framework/tool.d.ts.map +1 -1
  19. package/dist/framework/tool.js +2 -0
  20. package/dist/framework/tool.js.map +1 -1
  21. package/dist/ink-ui/screens/ReplScreen.d.ts +2 -1
  22. package/dist/ink-ui/screens/ReplScreen.d.ts.map +1 -1
  23. package/dist/ink-ui/screens/ReplScreen.js +65 -0
  24. package/dist/ink-ui/screens/ReplScreen.js.map +1 -1
  25. package/dist/memory/embeddings.d.ts.map +1 -1
  26. package/dist/memory/embeddings.js +3 -2
  27. package/dist/memory/embeddings.js.map +1 -1
  28. package/dist/memory/semantic-search.d.ts.map +1 -1
  29. package/dist/memory/semantic-search.js +2 -1
  30. package/dist/memory/semantic-search.js.map +1 -1
  31. package/dist/memory/vector-store.d.ts.map +1 -1
  32. package/dist/memory/vector-store.js +7 -3
  33. package/dist/memory/vector-store.js.map +1 -1
  34. package/dist/migration/migrate.d.ts.map +1 -1
  35. package/dist/migration/migrate.js +7 -11
  36. package/dist/migration/migrate.js.map +1 -1
  37. package/dist/print-ui/launch.d.ts +2 -1
  38. package/dist/print-ui/launch.d.ts.map +1 -1
  39. package/dist/print-ui/launch.js +7 -0
  40. package/dist/print-ui/launch.js.map +1 -1
  41. package/dist/runtime/agent-runtime-controller.d.ts +1 -0
  42. package/dist/runtime/agent-runtime-controller.d.ts.map +1 -1
  43. package/dist/runtime/agent-runtime-controller.js +24 -3
  44. package/dist/runtime/agent-runtime-controller.js.map +1 -1
  45. package/dist/runtime/agent-runtime-protocol.d.ts +16 -1
  46. package/dist/runtime/agent-runtime-protocol.d.ts.map +1 -1
  47. package/dist/runtime/agent-runtime-protocol.js +12 -0
  48. package/dist/runtime/agent-runtime-protocol.js.map +1 -1
  49. package/dist/runtime/chat-controller.d.ts +6 -0
  50. package/dist/runtime/chat-controller.d.ts.map +1 -1
  51. package/dist/runtime/chat-controller.js +65 -2
  52. package/dist/runtime/chat-controller.js.map +1 -1
  53. package/dist/runtime/goals/tools.d.ts.map +1 -1
  54. package/dist/runtime/goals/tools.js +14 -14
  55. package/dist/runtime/goals/tools.js.map +1 -1
  56. package/dist/runtime/goals/types.d.ts +6 -0
  57. package/dist/runtime/goals/types.d.ts.map +1 -1
  58. package/dist/runtime/goals/types.js.map +1 -1
  59. package/dist/runtime/model-coordinator.d.ts.map +1 -1
  60. package/dist/runtime/model-coordinator.js.map +1 -1
  61. package/dist/runtime/subagents/child-executor-guard.d.ts.map +1 -1
  62. package/dist/runtime/subagents/child-executor-guard.js +4 -0
  63. package/dist/runtime/subagents/child-executor-guard.js.map +1 -1
  64. package/dist/runtime/subagents/index.d.ts +10 -3
  65. package/dist/runtime/subagents/index.d.ts.map +1 -1
  66. package/dist/runtime/subagents/index.js +24 -1
  67. package/dist/runtime/subagents/index.js.map +1 -1
  68. package/dist/runtime/subagents/provider-gate.d.ts.map +1 -1
  69. package/dist/runtime/subagents/provider-gate.js +0 -1
  70. package/dist/runtime/subagents/provider-gate.js.map +1 -1
  71. package/dist/runtime/subagents/research-artifact.d.ts +94 -0
  72. package/dist/runtime/subagents/research-artifact.d.ts.map +1 -0
  73. package/dist/runtime/subagents/research-artifact.js +164 -0
  74. package/dist/runtime/subagents/research-artifact.js.map +1 -0
  75. package/dist/runtime/subagents/research-citation.d.ts +66 -0
  76. package/dist/runtime/subagents/research-citation.d.ts.map +1 -0
  77. package/dist/runtime/subagents/research-citation.js +185 -0
  78. package/dist/runtime/subagents/research-citation.js.map +1 -0
  79. package/dist/runtime/subagents/research-contract.d.ts +69 -0
  80. package/dist/runtime/subagents/research-contract.d.ts.map +1 -0
  81. package/dist/runtime/subagents/research-contract.js +232 -0
  82. package/dist/runtime/subagents/research-contract.js.map +1 -0
  83. package/dist/runtime/subagents/research-quality.d.ts +47 -0
  84. package/dist/runtime/subagents/research-quality.d.ts.map +1 -0
  85. package/dist/runtime/subagents/research-quality.js +125 -0
  86. package/dist/runtime/subagents/research-quality.js.map +1 -0
  87. package/dist/runtime/subagents/research-renderer.d.ts +79 -0
  88. package/dist/runtime/subagents/research-renderer.d.ts.map +1 -0
  89. package/dist/runtime/subagents/research-renderer.js +154 -0
  90. package/dist/runtime/subagents/research-renderer.js.map +1 -0
  91. package/dist/runtime/subagents/research-types.d.ts +105 -0
  92. package/dist/runtime/subagents/research-types.d.ts.map +1 -0
  93. package/dist/runtime/subagents/research-types.js +16 -0
  94. package/dist/runtime/subagents/research-types.js.map +1 -0
  95. package/dist/runtime/subagents/runtime-integration.d.ts +1 -1
  96. package/dist/runtime/subagents/runtime-integration.d.ts.map +1 -1
  97. package/dist/runtime/subagents/runtime-integration.js.map +1 -1
  98. package/dist/runtime/subagents/supervisor.d.ts +1 -1
  99. package/dist/runtime/subagents/supervisor.d.ts.map +1 -1
  100. package/dist/runtime/subagents/supervisor.js +1 -1
  101. package/dist/runtime/subagents/supervisor.js.map +1 -1
  102. package/dist/runtime/subagents/web-research-adapter.d.ts +83 -0
  103. package/dist/runtime/subagents/web-research-adapter.d.ts.map +1 -0
  104. package/dist/runtime/subagents/web-research-adapter.js +262 -0
  105. package/dist/runtime/subagents/web-research-adapter.js.map +1 -0
  106. package/dist/runtime/turn-controller.d.ts.map +1 -1
  107. package/dist/runtime/turn-controller.js +3 -1
  108. package/dist/runtime/turn-controller.js.map +1 -1
  109. package/dist/runtime/ui-events.d.ts +21 -0
  110. package/dist/runtime/ui-events.d.ts.map +1 -1
  111. package/dist/runtime/ui-events.js.map +1 -1
  112. package/dist/services/auth/auth.d.ts.map +1 -1
  113. package/dist/services/auth/auth.js +17 -4
  114. package/dist/services/auth/auth.js.map +1 -1
  115. package/dist/services/concurrent-sessions.d.ts.map +1 -1
  116. package/dist/services/concurrent-sessions.js +33 -17
  117. package/dist/services/concurrent-sessions.js.map +1 -1
  118. package/dist/services/config-dir.d.ts.map +1 -1
  119. package/dist/services/config-dir.js +19 -5
  120. package/dist/services/config-dir.js.map +1 -1
  121. package/dist/services/config.d.ts.map +1 -1
  122. package/dist/services/config.js +2 -1
  123. package/dist/services/config.js.map +1 -1
  124. package/dist/services/global-config.d.ts +31 -0
  125. package/dist/services/global-config.d.ts.map +1 -1
  126. package/dist/services/global-config.js.map +1 -1
  127. package/dist/services/goal-storage.d.ts.map +1 -1
  128. package/dist/services/goal-storage.js +22 -8
  129. package/dist/services/goal-storage.js.map +1 -1
  130. package/dist/services/llm.d.ts +17 -0
  131. package/dist/services/llm.d.ts.map +1 -1
  132. package/dist/services/llm.js +88 -31
  133. package/dist/services/llm.js.map +1 -1
  134. package/dist/services/model-catalog.d.ts +2 -0
  135. package/dist/services/model-catalog.d.ts.map +1 -1
  136. package/dist/services/model-catalog.js +25 -15
  137. package/dist/services/model-catalog.js.map +1 -1
  138. package/dist/services/model-context.d.ts.map +1 -1
  139. package/dist/services/model-context.js +36 -0
  140. package/dist/services/model-context.js.map +1 -1
  141. package/dist/services/model-registry.d.ts.map +1 -1
  142. package/dist/services/model-registry.js +32 -0
  143. package/dist/services/model-registry.js.map +1 -1
  144. package/dist/services/provider-resilience/coordinator.d.ts +1 -1
  145. package/dist/services/provider-resilience/coordinator.d.ts.map +1 -1
  146. package/dist/services/provider-resilience/coordinator.js +4 -4
  147. package/dist/services/provider-resilience/coordinator.js.map +1 -1
  148. package/dist/services/provider-resilience/error-classifier.js +20 -8
  149. package/dist/services/provider-resilience/error-classifier.js.map +1 -1
  150. package/dist/services/provider-resilience/stream-recovery.d.ts +1 -1
  151. package/dist/services/provider-resilience/stream-recovery.d.ts.map +1 -1
  152. package/dist/services/provider-resilience/stream-recovery.js +1 -1
  153. package/dist/services/provider-resilience/stream-recovery.js.map +1 -1
  154. package/dist/services/session-storage.d.ts.map +1 -1
  155. package/dist/services/session-storage.js +45 -14
  156. package/dist/services/session-storage.js.map +1 -1
  157. package/dist/services/storage-maintenance.d.ts.map +1 -1
  158. package/dist/services/storage-maintenance.js +44 -9
  159. package/dist/services/storage-maintenance.js.map +1 -1
  160. package/dist/services/task-manager.d.ts +3 -3
  161. package/dist/services/task-manager.d.ts.map +1 -1
  162. package/dist/services/tool-allowlist.d.ts +100 -0
  163. package/dist/services/tool-allowlist.d.ts.map +1 -0
  164. package/dist/services/tool-allowlist.js +250 -0
  165. package/dist/services/tool-allowlist.js.map +1 -0
  166. package/dist/services/web-search-adapters.d.ts.map +1 -1
  167. package/dist/services/web-search-adapters.js +48 -18
  168. package/dist/services/web-search-adapters.js.map +1 -1
  169. package/dist/services/web-search-mcp.d.ts.map +1 -1
  170. package/dist/services/web-search-mcp.js +27 -11
  171. package/dist/services/web-search-mcp.js.map +1 -1
  172. package/dist/services/yaml-config.d.ts.map +1 -1
  173. package/dist/services/yaml-config.js +2 -1
  174. package/dist/services/yaml-config.js.map +1 -1
  175. package/dist/skills/loader.d.ts.map +1 -1
  176. package/dist/skills/loader.js +3 -2
  177. package/dist/skills/loader.js.map +1 -1
  178. package/dist/terminal-ui/launch.d.ts +22 -2
  179. package/dist/terminal-ui/launch.d.ts.map +1 -1
  180. package/dist/terminal-ui/launch.js +150 -1
  181. package/dist/terminal-ui/launch.js.map +1 -1
  182. package/dist/terminal-ui/raw-editor.js.map +1 -1
  183. package/dist/tools/bash_security.d.ts +50 -18
  184. package/dist/tools/bash_security.d.ts.map +1 -1
  185. package/dist/tools/bash_security.js +394 -61
  186. package/dist/tools/bash_security.js.map +1 -1
  187. package/dist/tools/git.d.ts +4 -0
  188. package/dist/tools/git.d.ts.map +1 -1
  189. package/dist/tools/git.js +310 -3
  190. package/dist/tools/git.js.map +1 -1
  191. package/dist/tools/index.d.ts +1 -1
  192. package/dist/tools/index.d.ts.map +1 -1
  193. package/dist/tools/index.js +87 -25
  194. package/dist/tools/index.js.map +1 -1
  195. package/dist/tools/mcp.d.ts +8 -2
  196. package/dist/tools/mcp.d.ts.map +1 -1
  197. package/dist/tools/mcp.js +44 -23
  198. package/dist/tools/mcp.js.map +1 -1
  199. package/dist/tools/sandbox.d.ts +139 -0
  200. package/dist/tools/sandbox.d.ts.map +1 -0
  201. package/dist/tools/sandbox.js +485 -0
  202. package/dist/tools/sandbox.js.map +1 -0
  203. package/dist/tools/web.d.ts.map +1 -1
  204. package/dist/tools/web.js +113 -42
  205. package/dist/tools/web.js.map +1 -1
  206. package/dist/tui-core/frame.js.map +1 -1
  207. package/dist/tui-ui/launch.d.ts.map +1 -1
  208. package/dist/tui-ui/launch.js +46 -8
  209. package/dist/tui-ui/launch.js.map +1 -1
  210. package/dist/tui-ui/layout.d.ts.map +1 -1
  211. package/dist/tui-ui/layout.js +62 -0
  212. package/dist/tui-ui/layout.js.map +1 -1
  213. package/dist/tui-ui/runner.d.ts +6 -0
  214. package/dist/tui-ui/runner.d.ts.map +1 -1
  215. package/dist/tui-ui/runner.js +81 -1
  216. package/dist/tui-ui/runner.js.map +1 -1
  217. package/dist/tui-ui/state.d.ts +19 -1
  218. package/dist/tui-ui/state.d.ts.map +1 -1
  219. package/dist/tui-ui/state.js +35 -7
  220. package/dist/tui-ui/state.js.map +1 -1
  221. package/dist/tui-ui/terminal-image.d.ts +3 -13
  222. package/dist/tui-ui/terminal-image.d.ts.map +1 -1
  223. package/dist/tui-ui/terminal-image.js +5 -160
  224. package/dist/tui-ui/terminal-image.js.map +1 -1
  225. package/dist/utils/debug-log.d.ts +52 -0
  226. package/dist/utils/debug-log.d.ts.map +1 -0
  227. package/dist/utils/debug-log.js +91 -0
  228. package/dist/utils/debug-log.js.map +1 -0
  229. package/dist/utils/errors.d.ts +25 -0
  230. package/dist/utils/errors.d.ts.map +1 -0
  231. package/dist/utils/errors.js +47 -0
  232. package/dist/utils/errors.js.map +1 -0
  233. package/dist/utils/mask.d.ts +28 -0
  234. package/dist/utils/mask.d.ts.map +1 -0
  235. package/dist/utils/mask.js +54 -0
  236. package/dist/utils/mask.js.map +1 -0
  237. 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.2 — Goal continuity and evidence audit
5
+ > v0.1.4 — Goal continuity, model config & shell sandbox POC
6
6
 
7
7
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
8
8
  [![Node.js](https://img.shields.io/badge/node-%3E%3D20.0-green.svg)](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.2 release:
39
- npm install -g @orion-agents/orion-code@0.1.2
38
+ # Install the v0.1.4 release:
39
+ npm install -g @orion-agents/orion-code@0.1.4
40
40
 
41
41
  # Or run from a checked-out source tree:
42
42
  npm ci
@@ -73,6 +73,10 @@ orion -p "review the current git diff"
73
73
  echo "summarize this project" | orion --print
74
74
  ```
75
75
 
76
+ > **Pre-release note.** `0.1.4` is the in-development line on the `v0.1.4` branch and is
77
+ > not on npm yet — `0.1.3` is the current published release. Until `v0.1.4` is tagged and
78
+ > published, install from a source checkout. This note is removed at release.
79
+
76
80
  ### TUI startup banner
77
81
 
78
82
  The default TUI displays a portable sky-blue pixel banner: a compact landscape
@@ -147,6 +151,78 @@ Configuration: `~/.orion-code/orion.json` | Priority: `CLI flags > config > env
147
151
  The renderer is a runtime choice rather than a persisted setting: `orion` starts
148
152
  the TUI, while `--ui terminal` selects the technical renderer.
149
153
 
154
+ ### Project tool rules (`allowedTools`)
155
+
156
+ Per-project permission rules live under `projects["<absolute path>"].allowedTools`.
157
+ They are scoped to a single repository and are evaluated on top of each tool's own
158
+ policy — they can tighten the gate, never loosen it.
159
+
160
+ ```json
161
+ {
162
+ "projects": {
163
+ "/Users/me/work/api": {
164
+ "allowedTools": [
165
+ "exec_command(git status*)",
166
+ "exec_command(npm test*)",
167
+ "ask:exec_command(git push*)",
168
+ "deny:read_file(*.env)"
169
+ ]
170
+ }
171
+ }
172
+ }
173
+ ```
174
+
175
+ - Grammar: `[allow|ask|deny:]tool[(subject glob)]`; `*` matches any run of characters,
176
+ `?` a single one, and `*` as the tool name matches every tool.
177
+ - The subject is the call's `command` / `file_path` / `path` / `url` / `pattern` / `query`
178
+ argument, whichever comes first.
179
+ - Conflicts resolve most-restrictive-first (`deny` > `ask` > `allow`), independent of order.
180
+ - `allow` only skips an interactive confirmation. It never overrides a tool's own `deny`,
181
+ never escapes plan mode, and never auto-approves a destructive invocation.
182
+ - Malformed entries are ignored and listed by `/config` rather than silently applied.
183
+
184
+ ### Shell execution sandbox (`sandbox`)
185
+
186
+ `exec_command` can run inside an OS-level sandbox. This is a **security POC**: the
187
+ backend is probed at runtime and a configured-but-unusable sandbox **fails closed**
188
+ (refuses to run) rather than silently degrading to an unsandboxed execution.
189
+
190
+ ```json
191
+ {
192
+ "sandbox": {
193
+ "profile": "none",
194
+ "backend": "auto",
195
+ "allowNetwork": false,
196
+ "writableRoots": [],
197
+ "image": "alpine:latest"
198
+ }
199
+ }
200
+ ```
201
+
202
+ - **Ownership** — `sandbox` lives in the global config (`~/.orion-code/orion.json`)
203
+ and can be overridden per project under `projects["<path>"].sandbox`. Project
204
+ keys win individually; absent keys inherit the global value.
205
+ - **`profile`** — `none` (default, no isolation, identical to the legacy `sh -c`),
206
+ `read-only` (no writes anywhere, no network), or `workspace-write` (writes
207
+ confined to the workspace + temp dirs, network still blocked unless
208
+ `allowNetwork: true`). An unknown value (e.g. written by a newer Orion) is a
209
+ hard failure, never a downgrade to `none`.
210
+ - **`backend`** — `auto` (default, picks the first usable backend), `seatbelt`
211
+ (macOS `sandbox-exec`), `bubblewrap` (Linux `bwrap`), or `docker`. Each backend
212
+ is validated with a real execution probe; `which`-based guessing is never used.
213
+ - **`image`** — required only for `docker`; names the container image to run in.
214
+ - **`writableRoots`** — extra host roots the `workspace-write` profile may write
215
+ to (in addition to the cwd and temp). Non-string / empty entries are dropped.
216
+ - **Migration / rollback** — adding the `sandbox` key is backward compatible: the
217
+ default `profile: "none"` reproduces the previous behaviour exactly. Removing the
218
+ key (or reverting to a pre-sandbox version) is a safe rollback; older Orion
219
+ simply ignores the unknown field. No secrets are stored, so no redaction is
220
+ required.
221
+ - **Known limitations** — `seatbelt` cannot be nested inside another sandbox (e.g.
222
+ when Orion itself runs inside an app sandbox); `docker` runs the command in a
223
+ separate process tree and only constrains what is bind-mounted (the image rootfs
224
+ is made read-only via `--read-only`); none of the backends constrain CPU/memory.
225
+
150
226
  ## Interactive Commands
151
227
 
152
228
  | Command | Description |
@@ -204,6 +280,23 @@ See the [v0.1.2 release notes](https://github.com/orion-agents/orion-code/blob/m
204
280
  [Goal evidence and recovery guide](https://github.com/orion-agents/orion-code/blob/main/docs/goals/goal-evidence-and-recovery.md),
205
281
  and [execution plan](https://github.com/orion-agents/orion-code/blob/main/docs/plan/v0.1.2-execution-plan.md).
206
282
 
283
+ ## Research-to-Evidence (v0.1.4, experimental)
284
+
285
+ > **Status: experimental.** Turns the read-only `research` subagent output into a **traceable, recoverable** research→evidence loop.
286
+
287
+ Hard guarantees (any violation is a No-Go; never published):
288
+
289
+ - **Only specialized WebSearch / WebFetch** — no generic MCP, no write/exec grant.
290
+ - **SSRF / DNS-rebind / redirect / body-size / timeout** re-use the existing guards: a lexical SSRF pre-check at selection, and the full per-hop guard set delegated to the real WebFetch tool.
291
+ - A **claim must bind a source** to reach `observed`; a claim with no independent verification stays `partial`/`unmet` and is never counted as verified/complete.
292
+ - A **security-gate failure becomes `blocked`/`failed` with a structured reason** — never faked as a hit; provider fallback only swaps the provider, never downgrades a source status or writes a failure as a success.
293
+ - Research evidence (web) and execution-verification evidence (file / test / build / file facts) are **kept distinct**; a web summary cannot stand in for execution verification.
294
+ - Packet + source metadata are saved **atomically with a CAS token**; project / session / Goal scope isolation; resume only derives state and never replays external side effects; old schema versions fail closed.
295
+
296
+ Modules: `src/runtime/subagents/{research-types,research-contract,research-citation,web-research-adapter,research-renderer,research-artifact,research-quality}.ts`.
297
+
298
+ > Real-terminal (PTY) and external-state evidence are marked `not_run` in CI; local tests are not treated as release completion.
299
+
207
300
  ## Development
208
301
 
209
302
  ```bash
package/README.zh-CN.md CHANGED
@@ -3,7 +3,7 @@
3
3
  > **Orion Code — 通用 Agent 驾驭框架**
4
4
  > 一个 CLI 驱动的编码 Agent,具备安全边界、工具编排、记忆系统和上下文管理。
5
5
  >
6
- > v0.1.2 — Goal 连续性与证据审计
6
+ > v0.1.4 — Goal 连续性、模型配置与命令沙箱 POC
7
7
 
8
8
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
9
9
  [![Node.js](https://img.shields.io/badge/node-%3E%3D20.0-green.svg)](https://nodejs.org)
@@ -104,16 +104,19 @@ npm start -- --print "review the current git diff"
104
104
 
105
105
  ### 全局安装
106
106
 
107
- 固定安装 v0.1.2
107
+ 固定安装 v0.1.4
108
108
 
109
109
  ```bash
110
- npm install -g @orion-agents/orion-code@0.1.2
110
+ npm install -g @orion-agents/orion-code@0.1.4
111
111
  # 任意目录运行
112
112
  orion
113
113
  ```
114
114
 
115
115
  也可以在源码工作树中使用 `npm ci && npm run build && npm start`。
116
116
 
117
+ > **预发布说明**:`0.1.4` 是 `v0.1.4` 分支上的开发版本,尚未发布到 npm——当前已发布版本
118
+ > 仍是 `0.1.3`。在 `v0.1.4` 打 tag 并发布之前,请从源码工作树安装。该说明将在发版时移除。
119
+
117
120
  公众体验、交互优化和新增工作流优先落在 TUI。`terminal-ui` 不作为与
118
121
  TUI 并行发展的公众产品;Ink 只保留迁移期兼容,不再增加产品能力。
119
122
 
@@ -173,6 +176,69 @@ v0.1.2 只覆盖单 Session、单 Active Goal,不承诺多 Goal 调度或无
173
176
  CLI 参数 > ~/.orion-code/orion.json > 环境变量 > 内部默认
174
177
  ```
175
178
 
179
+ ### 项目级工具规则(`allowedTools`)
180
+
181
+ 权限规则写在 `projects["<项目绝对路径>"].allowedTools`,只作用于该仓库。规则叠加在
182
+ 工具自身的权限策略之上:**只能收紧,不能放宽**。
183
+
184
+ ```json
185
+ {
186
+ "projects": {
187
+ "/Users/me/work/api": {
188
+ "allowedTools": [
189
+ "exec_command(git status*)",
190
+ "exec_command(npm test*)",
191
+ "ask:exec_command(git push*)",
192
+ "deny:read_file(*.env)"
193
+ ]
194
+ }
195
+ }
196
+ }
197
+ ```
198
+
199
+ - 语法:`[allow|ask|deny:]工具名[(参数通配)]`;`*` 匹配任意字符串,`?` 匹配单个字符,
200
+ 工具名写 `*` 表示匹配所有工具。
201
+ - 参数通配匹配的是调用参数中的 `command` / `file_path` / `path` / `url` / `pattern` /
202
+ `query`(按此顺序取第一个非空字符串)。
203
+ - 冲突时按“最严格优先”解析:`deny` > `ask` > `allow`,与书写顺序无关。
204
+ - `allow` 仅用于免去交互确认,**不会**覆盖工具自身的 `deny`、**不会**突破 plan 模式的
205
+ 只读约束,也**不会**自动放行破坏性调用。
206
+ - 无法解析的条目会被忽略而不是静默放行,可通过 `/config` 查看解析结果与非法条目。
207
+
208
+ ### 命令执行沙箱(`sandbox`)
209
+
210
+ `exec_command` 可在操作系统级沙箱内执行。这是**安全 POC**:后端在运行
211
+ 时实测探测,已配置但不可用的沙箱会**fail closed**(拒绝执行),而不会静默降级为
212
+ 无沙箱执行。
213
+
214
+ ```json
215
+ {
216
+ "sandbox": {
217
+ "profile": "none",
218
+ "backend": "auto",
219
+ "allowNetwork": false,
220
+ "writableRoots": [],
221
+ "image": "alpine:latest"
222
+ }
223
+ }
224
+ ```
225
+
226
+ - **归属** — `sandbox` 位于全局配置(`~/.orion-code/orion.json`),并可在项目级
227
+ `projects["<项目绝对路径>"].sandbox` 覆盖。项目级按 key 单独覆盖,缺省 key 继承全局。
228
+ - **`profile`** — `none`(默认,不隔离,等价于旧版 `sh -c`)、`read-only`(任何位置不可写、无网络)、
229
+ `workspace-write`(写权限限定在工作区与临时目录,除非 `allowNetwork: true` 否则仍禁网)。
230
+ 未知取值(如新版 Orion 写入的更严格值)一律硬失败,绝不降级为 `none`。
231
+ - **`backend`** — `auto`(默认,自动选首个可用后端)、`seatbelt`(macOS `sandbox-exec`)、
232
+ `bubblewrap`(Linux `bwrap`)或 `docker`。每个后端都用真实执行探针校验,从不靠 `which` 推断。
233
+ - **`image`** — 仅 `docker` 需要,指定运行所用的容器镜像。
234
+ - **`writableRoots`** — `workspace-write` 可写入的额外宿主根目录(除工作区与临时目录外)。
235
+ 非字符串 / 空项会被丢弃。
236
+ - **迁移 / 回滚** — 新增 `sandbox` 完全向后兼容:默认 `profile: "none"` 与旧行为完全一致。
237
+ 删除该 key(或回退到沙箱前的版本)是安全回滚;旧版 Orion 会忽略该未知字段。不涉及任何敏感信息,无需脱敏。
238
+ - **已知限制** — `seatbelt` 不能嵌套在另一沙箱内(例如 Orion 自身运行在应用沙箱中时);
239
+ `docker` 在独立进程树中运行命令,只约束被 bind-mount 的部分(镜像根文件系统已通过 `--read-only` 设为只读);
240
+ 各后端均不限制 CPU / 内存。
241
+
176
242
  ### 环境变量
177
243
 
178
244
  | 变量 | 默认值 | 说明 |
@@ -399,7 +465,24 @@ orion-code/
399
465
 
400
466
  ## 版本历史
401
467
 
402
- ### v0.1.2(当前版本)
468
+ ### v0.1.4(开发中,未发布)
469
+
470
+ - 版本元数据对齐:`package.json` / `package-lock.json` 升至 `0.1.4`,`orion --version`、
471
+ 出站 `User-Agent` 与崩溃/遥测上报不再误报 `0.1.3`;
472
+ - 类型安全推进:外部边界(LLM provider、MCP wire、工具返回)由 `any` 收窄为 `unknown` +
473
+ 类型守卫,顺带修复被 `any` 掩盖的真实缺陷;
474
+ - 可靠性:持久化与鉴权路径不再静默吞掉异常,ESLint 启用 `no-empty` 门禁。
475
+
476
+ ### v0.1.3(最新已发布版本)
477
+
478
+ - `/model` 与 `/models` 拆分:`/model` 只显示当前模型信息,`/models` 承接交互式切换;
479
+ - `modelRegistry` 成为模型展示与切换的唯一事实源,静态 catalog 退为 legacy fallback;
480
+ - 内置 catalog 补全(火山方舟 / xf-yun / deepseek 等),未配置上下文时正确回退;
481
+ - 命令沙箱 POC、只读 Git 工具与工具 allowlist 的安全基线。
482
+
483
+ > 完整变更与交付状态(已发布 / 已合并 / 候选)以 `CHANGELOG.md` 为准。
484
+
485
+ ### v0.1.2(已发布)
403
486
 
404
487
  - 单 Session、单 Active Goal 的 typed continuation;
405
488
  - restart/resume 后安全暂停,必须显式恢复;
@@ -419,6 +502,25 @@ orion-code/
419
502
 
420
503
  ---
421
504
 
505
+ ## Research-to-Evidence(v0.1.4,实验性)
506
+
507
+ > **状态标记:experimental(实验性)。** 把只读 `research` 子 agent 的结果转化为**可追踪、可恢复**的研究→证据闭环。
508
+
509
+ 核心保证(违反任一即 No-Go,绝不发布):
510
+
511
+ - **只接专用 WebSearch / WebFetch**,不暴露通用 MCP、不授予 write/exec。
512
+ - **SSRF / DNS 重解析 / 重定向 / 响应大小 / 超时** 全部走既有守卫:选择期做 lexical SSRF 预检,逐跳防护委托给真实 WebFetch 工具。
513
+ - **claim 必须有 source 绑定**才能进入 `observed`;无独立验证的 claim 永远停在 `partial`/`unmet`,绝不被算作已验证/完成。
514
+ - **安全闸门失败 → `blocked`/`failed` + 结构化原因**,绝不伪装成命中;provider fallback 只换 provider,不降级 source status,也不把失败写成成功。
515
+ - 研究证据(web)与执行验证证据(file / 测试 / 构建 / 文件事实)**明确区分**,web 摘要不能替代执行验证。
516
+ - 包与源元数据**原子 CAS 保存**,project / session / Goal 作用域隔离;恢复只推导状态、不重放外部副作用;旧 schema 版本 fail-closed。
517
+
518
+ 模块:`src/runtime/subagents/{research-types,research-contract,research-citation,web-research-adapter,research-renderer,research-artifact,research-quality}.ts`。
519
+
520
+ > 真实终端(PTY)与外部状态证据在 CI 中标记为 `not_run`;本地测试不视为发布完成。
521
+
522
+ ---
523
+
422
524
  ## 开发
423
525
 
424
526
  ```bash
@@ -439,6 +541,9 @@ npm run lint
439
541
 
440
542
  # 格式化
441
543
  npm run format
544
+
545
+ # 发布前只读检查(不会 tag / push / publish)
546
+ npm run release:check
442
547
  ```
443
548
 
444
549
  ---
@@ -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;AA8RjB,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,eAAe,GAAG,SAAS,GAAG,MAAM,CAErF;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,YAAY,EAAE,GAAG,YAAY,EAAE,CASrE;AAikDD,iBAAe,UAAU,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAoVpF;AAixDD,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"}
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"}
@@ -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 formatModelAliasHelp() {
111
- return Object.keys((0, model_catalog_1.getModelCatalogAliases)()).sort().join(', ');
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: ctx.config.apiKey ? `${ctx.config.apiKey.slice(0, 7)}***` : '(not set)',
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 = (0, model_context_1.resolveModelContext)(currentModel);
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 (aliasEntry) {
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(DIM('Use /model <name|alias> to switch, e.g. /model sonnet'));
1401
- console.log();
1402
- return { success: true };
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 resolvedModel = (0, model_catalog_1.resolveModelAlias)(args);
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: resolvedModel });
1430
- console.log(SUCCESS(`✔ Model changed to ${BRAND(resolvedModel)}`));
1431
- const contextInfo = (0, model_context_1.resolveModelContext)(resolvedModel);
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|list|help]',
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: ['permissions', 'perm'],
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',