@co0ontty/wand 4.93.0 → 4.94.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/README.md +25 -30
- package/dist/ai-text-runner.d.ts +2 -10
- package/dist/ai-text-runner.js +13 -74
- package/dist/build-info.json +3 -3
- package/dist/cli.js +47 -9
- package/dist/config.d.ts +2 -3
- package/dist/config.js +26 -5
- package/dist/core-status-cli.d.ts +1 -1
- package/dist/core-status-cli.js +3 -1
- package/dist/daemon-connection.d.ts +10 -0
- package/dist/daemon-connection.js +28 -0
- package/dist/diagnostic-logs.d.ts +10 -0
- package/dist/diagnostic-logs.js +121 -0
- package/dist/employee-text.js +1 -3
- package/dist/git-quick-commit.d.ts +1 -1
- package/dist/git-quick-commit.js +2 -9
- package/dist/layout-tree.d.ts +3 -1
- package/dist/layout-tree.js +8 -0
- package/dist/local-model-process.js +20 -0
- package/dist/local-model-setup.js +5 -3
- package/dist/managed-temp.d.ts +32 -0
- package/dist/managed-temp.js +176 -0
- package/dist/middleware/api-scope.js +1 -0
- package/dist/models/shared.d.ts +0 -6
- package/dist/models/shared.js +0 -12
- package/dist/models/thinking.d.ts +0 -2
- package/dist/models/thinking.js +0 -2
- package/dist/models.d.ts +0 -1
- package/dist/models.js +3 -11
- package/dist/native-session-title.d.ts +1 -1
- package/dist/native-session-title.js +1 -1
- package/dist/pi-model-discovery.js +5 -17
- package/dist/pi-resource-recommendation.d.ts +1 -1
- package/dist/pi-resource-recommendation.js +129 -42
- package/dist/process-manager-native-history.js +1 -6
- package/dist/process-manager.d.ts +0 -1
- package/dist/process-manager.js +5 -18
- package/dist/provider-catalog.d.ts +1 -2
- package/dist/provider-catalog.js +1 -6
- package/dist/provider-cli-updater.js +1 -9
- package/dist/provider-history-scanner.d.ts +0 -3
- package/dist/provider-history-scanner.js +0 -26
- package/dist/render-daemon-client.js +6 -29
- package/dist/render-structured-client.js +2 -7
- package/dist/retention.js +1 -6
- package/dist/run-command.d.ts +5 -0
- package/dist/run-command.js +14 -1
- package/dist/server-session-lifecycle-routes.js +1 -1
- package/dist/server-storage-cleanup-routes.d.ts +3 -0
- package/dist/server-storage-cleanup-routes.js +19 -0
- package/dist/server-workspace-routes.d.ts +4 -1
- package/dist/server-workspace-routes.js +75 -9
- package/dist/server.js +21 -16
- package/dist/service-command-privilege.d.ts +17 -0
- package/dist/service-command-privilege.js +55 -0
- package/dist/service-manager/index.d.ts +3 -0
- package/dist/service-manager/index.js +3 -0
- package/dist/service-manager/launchd.d.ts +1 -0
- package/dist/service-manager/launchd.js +15 -0
- package/dist/service-manager/systemd.d.ts +1 -0
- package/dist/service-manager/systemd.js +11 -0
- package/dist/session-ai-context.js +17 -6
- package/dist/session-completion-state.d.ts +2 -0
- package/dist/session-completion-state.js +6 -2
- package/dist/session-logger.js +7 -1
- package/dist/session-model.d.ts +4 -0
- package/dist/session-model.js +13 -4
- package/dist/session-registry.d.ts +1 -1
- package/dist/session-registry.js +0 -3
- package/dist/silicon-employee-draft.js +0 -1
- package/dist/speech-service.js +18 -8
- package/dist/storage/mappers.js +6 -10
- package/dist/storage/migrations.js +14 -0
- package/dist/storage/uploads-repository.d.ts +36 -0
- package/dist/storage/uploads-repository.js +98 -0
- package/dist/storage-cleanup.d.ts +50 -0
- package/dist/storage-cleanup.js +344 -0
- package/dist/storage.d.ts +3 -1
- package/dist/storage.js +4 -0
- package/dist/structured-failure.d.ts +2 -0
- package/dist/structured-failure.js +9 -0
- package/dist/structured-grok-adapter.js +4 -4
- package/dist/structured-opencode-adapter.js +4 -9
- package/dist/structured-provider-common.d.ts +1 -2
- package/dist/structured-provider-common.js +3 -6
- package/dist/structured-session-manager-replay.js +2 -6
- package/dist/structured-session-manager.d.ts +1 -2
- package/dist/structured-session-manager.js +32 -25
- package/dist/task-organize-contract.d.ts +41 -0
- package/dist/task-organize-contract.js +4 -0
- package/dist/task-title-organize-service.d.ts +21 -0
- package/dist/task-title-organize-service.js +168 -0
- package/dist/task-title-organize.d.ts +77 -0
- package/dist/task-title-organize.js +346 -0
- package/dist/terminal-daemon-client.js +10 -48
- package/dist/time-utils.d.ts +2 -0
- package/dist/time-utils.js +7 -0
- package/dist/tui/attach.js +6 -5
- package/dist/tui/commands.d.ts +4 -1
- package/dist/tui/commands.js +36 -25
- package/dist/tui/index.js +2 -2
- package/dist/tui/service-actions.js +5 -4
- package/dist/tui/service-command.d.ts +6 -0
- package/dist/tui/service-command.js +40 -0
- package/dist/tui/service-panel.js +9 -8
- package/dist/types.d.ts +4 -4
- package/dist/upload-reference-guard.d.ts +4 -0
- package/dist/upload-reference-guard.js +29 -0
- package/dist/upload-routes.d.ts +4 -0
- package/dist/upload-routes.js +121 -21
- package/dist/user-profile.d.ts +1 -1
- package/dist/user-profile.js +8 -3
- package/dist/web-ui/content/ai-teams.js +9 -9
- package/dist/web-ui/content/module-manifest.json +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/chat-render-EODYMQF6.js → 0051bd3d55f10ab7/chat-render-RY2I2CF3.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/chunk-ZTEQSXPA.js → 0051bd3d55f10ab7/chunk-2F6J2MKX.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/chunk-A33YPCY6.js → 0051bd3d55f10ab7/chunk-7AFM245W.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/chunk-QNUWUMKW.js → 0051bd3d55f10ab7/chunk-MHX2QXIS.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/chunk-WGADUEIT.js → 0051bd3d55f10ab7/chunk-RAAPETNH.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/chunk-I3UYDQLS.js → 0051bd3d55f10ab7/chunk-RXKBHYUR.js} +2 -2
- package/dist/web-ui/content/modules/{2548dbe0d0783577/chunk-3TCUG766.js → 0051bd3d55f10ab7/chunk-U4EDP4TI.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/chunk-HK7RBU3M.js → 0051bd3d55f10ab7/chunk-WHXQR7JT.js} +1 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/employee-card-NV34TZIH.js +1 -0
- package/dist/web-ui/content/modules/{2548dbe0d0783577/host-2PU6TV2I.js → 0051bd3d55f10ab7/host-2IVIZ2YC.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/host-BPYNMKFC.js → 0051bd3d55f10ab7/host-AZXNFCRW.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/host-HZIKPWYY.js → 0051bd3d55f10ab7/host-EPEQX22R.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/host-YELXZID4.js → 0051bd3d55f10ab7/host-FZYIMOLC.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/host-OGD6AWHL.js → 0051bd3d55f10ab7/host-G6GL4SNL.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/host-E3SCPOIU.js → 0051bd3d55f10ab7/host-PEILC5NU.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/host-OWD32FZ3.js → 0051bd3d55f10ab7/host-PHHNGVNL.js} +2 -2
- package/dist/web-ui/content/modules/{2548dbe0d0783577/host-CXDAFMXX.js → 0051bd3d55f10ab7/host-Q524TMSR.js} +1 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-RO5MB6RB.js +69 -0
- package/dist/web-ui/content/modules/{2548dbe0d0783577/host-J5XRAQFP.js → 0051bd3d55f10ab7/host-WOQRK6NG.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/host-HKXE2DOT.js → 0051bd3d55f10ab7/host-XNIR3NTJ.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/host-4E4H4JZJ.js → 0051bd3d55f10ab7/host-XRVOIEP6.js} +1 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-ZDKOQEMS.js +1 -0
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/host-bridge-PMQZRG5G.js +1 -0
- package/dist/web-ui/content/modules/{2548dbe0d0783577/library-bridge-Y3Y24CPQ.js → 0051bd3d55f10ab7/library-bridge-DTMQIHVJ.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/page-GQBUU7T7.js → 0051bd3d55f10ab7/page-PY4JUL4W.js} +1 -1
- package/dist/web-ui/content/modules/{2548dbe0d0783577/panel-PG662OC5.js → 0051bd3d55f10ab7/panel-BBQFC55Q.js} +1 -1
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/runtime.js +974 -0
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/task-board-host-PXZSWBBG.js +2 -0
- package/dist/web-ui/content/modules/0051bd3d55f10ab7/workspace-agent-picker-DJSECSY6.js +1 -0
- package/dist/web-ui/content/modules/{2548dbe0d0783577/workspace-window-HV4T3WKR.js → 0051bd3d55f10ab7/workspace-window-MNMKSOBG.js} +1 -1
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/embedded-assets.d.ts +27 -27
- package/dist/web-ui/embedded-assets.js +4 -4
- package/dist/web-ui/provider-identity.d.ts +4 -5
- package/dist/web-ui/provider-identity.js +4 -10
- package/dist/web-ui/react/workspaces/sidebar-manage.d.ts +39 -0
- package/dist/web-ui/react/workspaces/sidebar-manage.js +149 -0
- package/dist/web-ui/react/workspaces/types.d.ts +408 -0
- package/dist/web-ui/react/workspaces/types.js +16 -0
- package/dist/web-ui/session-activity.js +1 -1
- package/package.json +2 -2
- package/dist/structured-gemini-adapter.d.ts +0 -40
- package/dist/structured-gemini-adapter.js +0 -208
- package/dist/web-ui/content/modules/2548dbe0d0783577/chunk-XGVT4ZWT.js +0 -1
- package/dist/web-ui/content/modules/2548dbe0d0783577/employee-card-LJ3TX72B.js +0 -1
- package/dist/web-ui/content/modules/2548dbe0d0783577/host-ITV2QZ4A.js +0 -1
- package/dist/web-ui/content/modules/2548dbe0d0783577/host-XNWC7KPR.js +0 -69
- package/dist/web-ui/content/modules/2548dbe0d0783577/host-bridge-G5HEVADT.js +0 -1
- package/dist/web-ui/content/modules/2548dbe0d0783577/runtime.js +0 -946
- package/dist/web-ui/content/modules/2548dbe0d0783577/task-board-host-RAN2Z2KR.js +0 -2
- package/dist/web-ui/content/modules/2548dbe0d0783577/workspace-agent-picker-FXLH6N5K.js +0 -1
- /package/dist/web-ui/content/modules/{2548dbe0d0783577/chunk-YX475LXO.js → 0051bd3d55f10ab7/chunk-6C6A2RHG.js} +0 -0
- /package/dist/web-ui/content/modules/{2548dbe0d0783577/chunk-NP35GDQ3.js → 0051bd3d55f10ab7/chunk-ECWYA3U5.js} +0 -0
- /package/dist/web-ui/content/modules/{2548dbe0d0783577/chunk-47EVOSJS.js → 0051bd3d55f10ab7/chunk-T35D57FW.js} +0 -0
- /package/dist/web-ui/content/modules/{2548dbe0d0783577 → 0051bd3d55f10ab7}/chunk-YOXXNXOX.js +0 -0
- /package/dist/web-ui/content/modules/{2548dbe0d0783577/chunk-C3K34WJD.js → 0051bd3d55f10ab7/chunk-YQJ6XYV7.js} +0 -0
- /package/dist/web-ui/content/modules/{2548dbe0d0783577/host-J6HBDK4A.js → 0051bd3d55f10ab7/host-2AQAIUNO.js} +0 -0
- /package/dist/web-ui/content/modules/{2548dbe0d0783577 → 0051bd3d55f10ab7}/sha256-ANXV72UC.js +0 -0
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
### Overview
|
|
13
13
|
|
|
14
|
-
Wand is a web console for remotely accessing and managing local CLI tools from a browser. It supports [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Codex](https://github.com/openai/codex), [OpenCode](https://opencode.ai/), [Grok Build](https://grok.com/), [Qoder CLI](https://docs.qoder.com/en/cli/quick-start), and [Pi](https://pi.dev/),
|
|
14
|
+
Wand is a web console for remotely accessing and managing local CLI tools from a browser. It supports [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Codex](https://github.com/openai/codex), [OpenCode](https://opencode.ai/), [Grok Build](https://grok.com/), [Qoder CLI](https://docs.qoder.com/en/cli/quick-start), and [Pi](https://pi.dev/), with terminal and structured conversation views, persistent resumable sessions, permission controls, file browsing, and native clients for multiple platforms.
|
|
15
15
|
|
|
16
16
|
The browser password manager extension source lives in `browser-extension/`.
|
|
17
17
|
|
|
@@ -48,7 +48,7 @@ wand config:password
|
|
|
48
48
|
#### Core
|
|
49
49
|
|
|
50
50
|
- **Dual view modes** — switch between raw terminal output and a structured conversation view for the same session
|
|
51
|
-
- **Multiple providers** — create PTY or structured sessions for Claude Code, Codex, OpenCode, Grok, Qoder CLI,
|
|
51
|
+
- **Multiple providers** — create PTY or structured sessions for Claude Code, Codex, OpenCode, Grok, Qoder CLI, and Pi
|
|
52
52
|
- **Session management** — create, archive, and resume Claude, Codex, OpenCode, Grok, and Qoder sessions with their provider-native context; restore Claude/Codex native history; show summaries in the session list
|
|
53
53
|
- **Permission control** — visual permission prompts with one-time approval, per-turn memory, and related policies
|
|
54
54
|
|
|
@@ -66,7 +66,7 @@ wand config:password
|
|
|
66
66
|
- **Native clients** — Android, iOS, and macOS clients with encrypted connection codes and update checks
|
|
67
67
|
- **HTTPS** — optional self-signed certificates for remote or mobile access
|
|
68
68
|
- **Update channels** — built-in stable/beta update checks and upgrade prompts
|
|
69
|
-
- **CLI updates** — check and quickly update Claude Code, Codex, OpenCode, Qoder CLI,
|
|
69
|
+
- **CLI updates** — check and quickly update Claude Code, Codex, OpenCode, Qoder CLI, and Pi from server settings, with optional automatic updates
|
|
70
70
|
|
|
71
71
|
Stable subscribers receive npm `latest`; Beta subscribers receive the newer of `latest`
|
|
72
72
|
and `beta`, including a stable release that supersedes its prerelease. Installing stable
|
|
@@ -113,16 +113,16 @@ Web and Android show inference calls as standalone local-decision cards, outside
|
|
|
113
113
|
|
|
114
114
|
### System Service
|
|
115
115
|
|
|
116
|
-
The default service mode is system-wide: Linux writes `/etc/systemd/system/wand.service`, and macOS writes `/Library/LaunchDaemons/com.wand.web.plist`.
|
|
116
|
+
The default service mode is system-wide: Linux writes `/etc/systemd/system/wand.service`, and macOS writes `/Library/LaunchDaemons/com.wand.web.plist`. System service changes require administrator permission. Wand automatically invokes sudo and lets you enter the password in the terminal; it never reads or stores the password. Without an interactive terminal, only already-authorized noninteractive sudo is allowed. Restart waits for active Core turns before gracefully restarting Server, leaving Render/terminald running (`--timeout <seconds>` cancels on timeout).
|
|
117
117
|
|
|
118
118
|
```bash
|
|
119
|
-
|
|
120
|
-
wand service:status
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
wand service:logs
|
|
125
|
-
|
|
119
|
+
wand service:install # Register and start; automatically request sudo
|
|
120
|
+
wand service:status # Show status
|
|
121
|
+
wand service:start # Start
|
|
122
|
+
wand service:stop # Stop
|
|
123
|
+
wand service:restart # Safely restart; automatically request sudo
|
|
124
|
+
wand service:logs # Show recent logs
|
|
125
|
+
wand service:uninstall # Stop and remove the service
|
|
126
126
|
```
|
|
127
127
|
|
|
128
128
|
Use `--user` for a user-level service:
|
|
@@ -180,17 +180,13 @@ npm uninstall -g opencode
|
|
|
180
180
|
npm install -g opencode-ai@latest
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
-
Gemini structured sessions require `@google/gemini-cli` 0.11 or newer (the release that added headless `--output-format stream-json`):
|
|
184
|
-
|
|
185
|
-
```bash
|
|
186
|
-
npm install -g @google/gemini-cli@latest
|
|
187
183
|
```
|
|
188
184
|
|
|
189
185
|
## 中文
|
|
190
186
|
|
|
191
187
|
### 概览
|
|
192
188
|
|
|
193
|
-
通过浏览器远程访问和管理本地 CLI 工具的 Web 控制台。支持 [Claude Code](https://docs.anthropic.com/en/docs/claude-code)、[Codex](https://github.com/openai/codex)、[OpenCode](https://opencode.ai/)、[Grok Build](https://grok.com/)、[Qoder CLI](https://docs.qoder.com/zh/cli/quick-start)、[Pi](https://pi.dev/)
|
|
189
|
+
通过浏览器远程访问和管理本地 CLI 工具的 Web 控制台。支持 [Claude Code](https://docs.anthropic.com/en/docs/claude-code)、[Codex](https://github.com/openai/codex)、[OpenCode](https://opencode.ai/)、[Grok Build](https://grok.com/)、[Qoder CLI](https://docs.qoder.com/zh/cli/quick-start)、[Pi](https://pi.dev/),提供终端和结构化对话双视图、会话持久化与恢复、权限管控、文件浏览和多平台客户端。
|
|
194
190
|
|
|
195
191
|
### 安装
|
|
196
192
|
|
|
@@ -227,10 +223,6 @@ npm uninstall -g opencode
|
|
|
227
223
|
npm install -g opencode-ai@latest
|
|
228
224
|
```
|
|
229
225
|
|
|
230
|
-
Gemini 结构化会话需要 `@google/gemini-cli` 0.11 或更高版本(该版本才加入 headless 的 `--output-format stream-json`):
|
|
231
|
-
|
|
232
|
-
```bash
|
|
233
|
-
npm install -g @google/gemini-cli@latest
|
|
234
226
|
```
|
|
235
227
|
|
|
236
228
|
### 功能
|
|
@@ -238,7 +230,7 @@ npm install -g @google/gemini-cli@latest
|
|
|
238
230
|
#### 核心
|
|
239
231
|
|
|
240
232
|
- **双视图模式** — 终端原始输出和结构化对话视图可随时切换,同一会话两种呈现
|
|
241
|
-
- **多 Provider 支持** — Claude Code、Codex、OpenCode、Grok、Qoder CLI
|
|
233
|
+
- **多 Provider 支持** — Claude Code、Codex、OpenCode、Grok、Qoder CLI 和 Pi 均可创建 PTY 或结构化会话
|
|
242
234
|
- **会话管理** — 创建、归档并携带原生上下文恢复 Claude、Codex、OpenCode、Grok、Qoder 会话;支持恢复 Claude/Codex 原生历史记录;会话列表显示摘要
|
|
243
235
|
- **权限控制** — 可视化权限提示,支持逐次确认、单次批准、本轮记忆等策略
|
|
244
236
|
|
|
@@ -282,7 +274,7 @@ master 的 Beta 发布只写入 `beta`,版本为下一补丁号加
|
|
|
282
274
|
版本基数同时检查正式 Git tags 和 npm latest,兼容未推送 Git 的本地正式发布。
|
|
283
275
|
Beta 标签缺失或无效时,Beta 订阅仍可接收有效正式版;正式通道始终不选择 beta。
|
|
284
276
|
两个候选都无效或 npm 不可达时不安装。
|
|
285
|
-
- **CLI 更新** — 在服务端设置中检查并快速更新 Claude Code、Codex、OpenCode、Qoder CLI
|
|
277
|
+
- **CLI 更新** — 在服务端设置中检查并快速更新 Claude Code、Codex、OpenCode、Qoder CLI 和 Pi,也可开启自动更新
|
|
286
278
|
|
|
287
279
|
### 配置
|
|
288
280
|
|
|
@@ -334,16 +326,16 @@ Web/Android 的推理调用独立显示为「本地决策」卡,不混入普
|
|
|
334
326
|
|
|
335
327
|
### 系统服务
|
|
336
328
|
|
|
337
|
-
默认走 **system-wide**:Linux 写 `/etc/systemd/system/wand.service`,macOS 写 `/Library/LaunchDaemons/com.wand.web.plist`。开机自启、不依赖 login session、`service wand` / `systemctl status wand`
|
|
329
|
+
默认走 **system-wide**:Linux 写 `/etc/systemd/system/wand.service`,macOS 写 `/Library/LaunchDaemons/com.wand.web.plist`。开机自启、不依赖 login session、`service wand` / `systemctl status wand` 这些老命令都能用。系统服务变更需要管理员权限,Wand 会自动申请 sudo,让你在终端输入密码(不显示、不读取、不保存)。无交互终端时仅允许已经授权的非交互 sudo。重启会先等待 Core 回合排空,再优雅重启 Server,保留 Render/terminald;可用 `--timeout <秒数>` 限制等待时间,超时不停止服务。
|
|
338
330
|
|
|
339
331
|
```bash
|
|
340
|
-
|
|
341
|
-
wand service:status
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
wand service:logs
|
|
346
|
-
|
|
332
|
+
wand service:install # 注册并启动(自动申请 sudo)
|
|
333
|
+
wand service:status # 查状态(active / inactive / failed) — 读取不要 sudo
|
|
334
|
+
wand service:start # 启动
|
|
335
|
+
wand service:stop # 停止
|
|
336
|
+
wand service:restart # 安全重启(自动申请 sudo)
|
|
337
|
+
wand service:logs # 看最近日志(--lines N 调整行数)
|
|
338
|
+
wand service:uninstall # 卸载(停服 + 删 unit)
|
|
347
339
|
```
|
|
348
340
|
|
|
349
341
|
不想用 sudo?传 `--user` 切到 user-level(写 `~/.config/systemd/user/wand.service` 或 `~/Library/LaunchAgents/`):
|
|
@@ -414,6 +406,9 @@ git submodule update --init -- render-bin
|
|
|
414
406
|
macOS 每日清理历史归档编译中间物(保留7天)用 `npm run clean:build-cache:install -- --install`;
|
|
415
407
|
范围、并发保护、日志和停用方式见[自动清理说明](docs/build-cache-cleanup.md)。不修改Git hook,不自动删除现用原生构建树。
|
|
416
408
|
|
|
409
|
+
通过 npm 安装后,运行存储维护默认在后台自动执行:回收过期临时残留与无引用的上传文件,限制诊断日志大小并清理过期轮转日志。无需源码仓库或日常手动操作,保留会话历史和正在使用的文件。
|
|
410
|
+
归属判断、保留时间与可选管理员诊断入口见[运行存储自动维护](docs/storage-cleanup.md)。
|
|
411
|
+
|
|
417
412
|
数据存储在 `~/.wand/` 下:`config.json`(配置)、`wand.db`(SQLite)、`sessions/`(日志)。
|
|
418
413
|
|
|
419
414
|
### License
|
package/dist/ai-text-runner.d.ts
CHANGED
|
@@ -27,15 +27,6 @@ type CliAiTextOptions = QuickCommitAiOptions & {
|
|
|
27
27
|
export declare function normalizeAiText(raw: string): string;
|
|
28
28
|
export declare function buildPiTextArgs(request: AiTextRequest, opts: QuickCommitAiOptions, allowTools?: boolean): string[];
|
|
29
29
|
export declare function buildGrokTextArgs(request: AiTextRequest, opts: QuickCommitAiOptions, allowTools?: boolean): string[];
|
|
30
|
-
/**
|
|
31
|
-
* Gemini CLI 没有 `--append-system-prompt`,规则走 `contentWithSystemPrompt` 并进内容;
|
|
32
|
-
* headless 下必须带 `--skip-trust`,否则未信任的工作目录会直接以 55 退出。
|
|
33
|
-
*
|
|
34
|
-
* `-p ""` 只把 CLI 钉在 headless 模式,真正的 prompt 走 stdin:commit diff 可能很大,
|
|
35
|
-
* 塞进 argv 会撞 ARG_MAX。Gemini 没有 Codex `--ephemeral` 那样的临时会话开关,
|
|
36
|
-
* 一次性调用会留在它自己的聊天记录里(Wand 不扫描 Gemini 历史,不会误当可恢复会话)。
|
|
37
|
-
*/
|
|
38
|
-
export declare function buildGeminiTextArgs(opts: QuickCommitAiOptions, allowTools?: boolean): string[];
|
|
39
30
|
export declare function buildQoderTextArgs(request: AiTextRequest, opts: QuickCommitAiOptions, allowTools?: boolean): string[];
|
|
40
31
|
/** provider 自己的系统提示开关 + 规则文本;没有开关的 provider 返回空。 */
|
|
41
32
|
export declare function systemPromptArgsFrom(provider: SessionProvider, system: string): string[];
|
|
@@ -47,8 +38,9 @@ export declare function runCliText(command: string, args: string[], prompt: stri
|
|
|
47
38
|
inheritEnv?: boolean;
|
|
48
39
|
signal?: AbortSignal;
|
|
49
40
|
env?: (base: NodeJS.ProcessEnv) => NodeJS.ProcessEnv;
|
|
41
|
+
temporaryDirectory?: string;
|
|
42
|
+
onChildClose?: () => void;
|
|
50
43
|
}): Promise<string>;
|
|
51
|
-
export declare function assertGeminiTextCapability(): void;
|
|
52
44
|
export declare function callCliAiText(request: AiTextRequest, cwd: string, language: string, opts: CliAiTextOptions): Promise<string>;
|
|
53
45
|
/** 角色设定走系统提示前缀;任务自己的输出格式与约束必须排在后面。 */
|
|
54
46
|
export declare function withOpsPersona(request: AiTextRequest, persona?: string): AiTextRequest;
|
package/dist/ai-text-runner.js
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { existsSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
6
3
|
import { StringDecoder } from "node:string_decoder";
|
|
7
4
|
import { appendDiagnosticTail, MAX_AI_TEXT_BYTES } from "./bounded-process-output.js";
|
|
8
5
|
import { QuickCommitError } from "./command-error.js";
|
|
6
|
+
import { registerTemporaryChild } from "./managed-temp.js";
|
|
9
7
|
import { abortableEmployeeText, prepareEmployeeTextCandidate } from "./employee-text.js";
|
|
10
8
|
import { buildChildEnv } from "./env-utils.js";
|
|
11
9
|
import { getGitErrorMessage } from "./git-utils.js";
|
|
@@ -112,12 +110,6 @@ function assertCliProtocolSucceeded(stdout, provider) {
|
|
|
112
110
|
? failed(message.errorMessage) : null;
|
|
113
111
|
}
|
|
114
112
|
}
|
|
115
|
-
else if (provider === "gemini") {
|
|
116
|
-
if (event.type === "error" && event.severity === "error")
|
|
117
|
-
failure = failed(event);
|
|
118
|
-
if (event.type === "result")
|
|
119
|
-
failure = event.status === "error" ? failed(event) : null;
|
|
120
|
-
}
|
|
121
113
|
else if ((provider === "grok" || provider === "opencode") && event.type === "error") {
|
|
122
114
|
failure = failed(event);
|
|
123
115
|
}
|
|
@@ -237,24 +229,6 @@ function extractPiText(stdout) {
|
|
|
237
229
|
}
|
|
238
230
|
return text.trim();
|
|
239
231
|
}
|
|
240
|
-
/** Gemini `--output-format stream-json`:assistant message 是增量 chunk,拼接即可。 */
|
|
241
|
-
function extractGeminiText(stdout) {
|
|
242
|
-
assertCliProtocolSucceeded(stdout, "gemini");
|
|
243
|
-
let text = "";
|
|
244
|
-
for (const line of stdout.split(/\r?\n/)) {
|
|
245
|
-
const trimmed = line.trim();
|
|
246
|
-
if (!trimmed.startsWith("{"))
|
|
247
|
-
continue;
|
|
248
|
-
try {
|
|
249
|
-
const event = JSON.parse(trimmed);
|
|
250
|
-
if (event.type === "message" && event.role === "assistant" && typeof event.content === "string") {
|
|
251
|
-
text += event.content;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
catch { /* ignore non-protocol stdout */ }
|
|
255
|
-
}
|
|
256
|
-
return text.trim();
|
|
257
|
-
}
|
|
258
232
|
export function buildPiTextArgs(request, opts, allowTools = false) {
|
|
259
233
|
const args = ["--mode", "json", "--print", "--no-session"];
|
|
260
234
|
if (!allowTools)
|
|
@@ -284,23 +258,6 @@ export function buildGrokTextArgs(request, opts, allowTools = false) {
|
|
|
284
258
|
args.push("--tools", "", "--disallowed-tools", "run_terminal_cmd,read_file,search_replace,grep,list_dir,web_search,web_fetch,todo_write,task,Agent", "--deny", "MCPTool", "--disable-web-search", "--no-subagents");
|
|
285
259
|
return args;
|
|
286
260
|
}
|
|
287
|
-
/**
|
|
288
|
-
* Gemini CLI 没有 `--append-system-prompt`,规则走 `contentWithSystemPrompt` 并进内容;
|
|
289
|
-
* headless 下必须带 `--skip-trust`,否则未信任的工作目录会直接以 55 退出。
|
|
290
|
-
*
|
|
291
|
-
* `-p ""` 只把 CLI 钉在 headless 模式,真正的 prompt 走 stdin:commit diff 可能很大,
|
|
292
|
-
* 塞进 argv 会撞 ARG_MAX。Gemini 没有 Codex `--ephemeral` 那样的临时会话开关,
|
|
293
|
-
* 一次性调用会留在它自己的聊天记录里(Wand 不扫描 Gemini 历史,不会误当可恢复会话)。
|
|
294
|
-
*/
|
|
295
|
-
export function buildGeminiTextArgs(opts, allowTools = false) {
|
|
296
|
-
const args = ["-p", "", "--output-format", "stream-json", "--skip-trust"];
|
|
297
|
-
const model = opts.model?.trim();
|
|
298
|
-
if (model && model !== "default")
|
|
299
|
-
args.push("--model", model);
|
|
300
|
-
if (allowTools)
|
|
301
|
-
args.push("--approval-mode", "yolo");
|
|
302
|
-
return args;
|
|
303
|
-
}
|
|
304
261
|
export function buildQoderTextArgs(request, opts, allowTools = false) {
|
|
305
262
|
const args = ["-p", request.prompt, "--output-format", "stream-json", "--no-session-persistence"];
|
|
306
263
|
const model = opts.model?.trim();
|
|
@@ -337,6 +294,17 @@ export function runCliText(command, args, prompt, opts) {
|
|
|
337
294
|
env: opts.env ? opts.env(baseEnv) : baseEnv,
|
|
338
295
|
stdio: ["pipe", "pipe", "pipe"],
|
|
339
296
|
});
|
|
297
|
+
if (opts.temporaryDirectory) {
|
|
298
|
+
try {
|
|
299
|
+
registerTemporaryChild(opts.temporaryDirectory, child.pid);
|
|
300
|
+
}
|
|
301
|
+
catch {
|
|
302
|
+
child.on("error", () => { });
|
|
303
|
+
child.once("close", () => { reject(new Error("AI text temporary lease unavailable")); });
|
|
304
|
+
child.kill("SIGKILL");
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
340
308
|
let stdout = "";
|
|
341
309
|
let stdoutBytes = 0;
|
|
342
310
|
const stdoutDecoder = new StringDecoder("utf8");
|
|
@@ -386,6 +354,7 @@ export function runCliText(command, args, prompt, opts) {
|
|
|
386
354
|
});
|
|
387
355
|
child.on("close", (code) => {
|
|
388
356
|
cleanup();
|
|
357
|
+
opts.onChildClose?.();
|
|
389
358
|
if (settled)
|
|
390
359
|
return;
|
|
391
360
|
settled = true;
|
|
@@ -502,34 +471,6 @@ async function callPiText(request, cwd, opts) {
|
|
|
502
471
|
throw new QuickCommitError("Pi 返回了空的 commit message。", "EMPTY_AI_MESSAGE");
|
|
503
472
|
return text;
|
|
504
473
|
}
|
|
505
|
-
export function assertGeminiTextCapability() {
|
|
506
|
-
// Gemini ignores supplemental admin policy when a central policy exists.
|
|
507
|
-
// Refuse that unsupported text-only path rather than launching without deny.
|
|
508
|
-
const systemPolicies = process.platform === "darwin" ? "/Library/Application Support/GeminiCli/policies"
|
|
509
|
-
: process.platform === "win32" ? join(process.env.ProgramData ?? "C:\\ProgramData", "gemini-cli", "policies") : "/etc/gemini-cli/policies";
|
|
510
|
-
if (existsSync(systemPolicies) && readdirSync(systemPolicies).some(file => file.endsWith(".toml"))) {
|
|
511
|
-
throw new QuickCommitError("Gemini 中央策略阻止注入纯文本工具边界,请配置可用的文本候选。", "AI_FALLBACK_FAILED");
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
async function callGeminiText(request, cwd, opts) {
|
|
515
|
-
assertGeminiTextCapability();
|
|
516
|
-
const root = mkdtempSync(join(tmpdir(), "wand-gemini-text-"));
|
|
517
|
-
try {
|
|
518
|
-
const policy = join(root, "text-only.toml");
|
|
519
|
-
writeFileSync(policy, '[[rule]]\ntoolName = "*"\ndecision = "deny"\npriority = 999\n', { mode: 0o600 });
|
|
520
|
-
const args = [...buildGeminiTextArgs(opts), "--admin-policy", policy, "--extensions", "none", "--allowed-mcp-server-names", ""];
|
|
521
|
-
const stdout = await runCliText("gemini", args, contentWithSystemPrompt("gemini", request), {
|
|
522
|
-
cwd, timeoutMs: cliTextTimeoutMs(CODEX_MESSAGE_TIMEOUT_MS, opts), inheritEnv: opts.inheritEnv, signal: opts.signal,
|
|
523
|
-
});
|
|
524
|
-
const text = extractGeminiText(stdout);
|
|
525
|
-
if (!text)
|
|
526
|
-
throw new QuickCommitError("Gemini 返回了空的 commit message。", "EMPTY_AI_MESSAGE");
|
|
527
|
-
return text;
|
|
528
|
-
}
|
|
529
|
-
finally {
|
|
530
|
-
rmSync(root, { recursive: true, force: true });
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
474
|
export async function callCliAiText(request, cwd, language, opts) {
|
|
534
475
|
const provider = defaultProvider(opts.provider);
|
|
535
476
|
if (provider === "codex") {
|
|
@@ -543,8 +484,6 @@ export async function callCliAiText(request, cwd, language, opts) {
|
|
|
543
484
|
return callQoderText(request, cwd, opts);
|
|
544
485
|
if (provider === "pi")
|
|
545
486
|
return callPiText(request, cwd, opts);
|
|
546
|
-
if (provider === "gemini")
|
|
547
|
-
return callGeminiText(request, cwd, opts);
|
|
548
487
|
return callClaudeText(request, cwd, language, opts);
|
|
549
488
|
}
|
|
550
489
|
/**
|
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-10-15T00:
|
|
4
|
-
"version": "4.
|
|
2
|
+
"commit": "4323ba1516bf4465bd3e239e67508a972f01a3e4",
|
|
3
|
+
"builtAt": "2026-10-15T00:50:47.298Z",
|
|
4
|
+
"version": "4.94.0",
|
|
5
5
|
"channel": "stable"
|
|
6
6
|
}
|
package/dist/cli.js
CHANGED
|
@@ -29,6 +29,9 @@ async function main() {
|
|
|
29
29
|
await runAttach(live, configPath, useTui);
|
|
30
30
|
break;
|
|
31
31
|
}
|
|
32
|
+
const { installServiceDiagnosticRotation } = await import("./diagnostic-logs.js");
|
|
33
|
+
const { dirname } = await import("node:path");
|
|
34
|
+
installServiceDiagnosticRotation(dirname(configPath), "web");
|
|
32
35
|
const { ensureNodePtyHelperExecutable } = await import("./ensure-node-pty-helper.js");
|
|
33
36
|
ensureNodePtyHelperExecutable();
|
|
34
37
|
const { isPortInUseError, startServer } = await import("./server.js");
|
|
@@ -93,6 +96,9 @@ async function main() {
|
|
|
93
96
|
break;
|
|
94
97
|
}
|
|
95
98
|
case "terminald": {
|
|
99
|
+
const { installServiceDiagnosticRotation } = await import("./diagnostic-logs.js");
|
|
100
|
+
const { dirname } = await import("node:path");
|
|
101
|
+
installServiceDiagnosticRotation(dirname(configPath), "terminald");
|
|
96
102
|
const { ensureNodePtyHelperExecutable } = await import("./ensure-node-pty-helper.js");
|
|
97
103
|
ensureNodePtyHelperExecutable();
|
|
98
104
|
const { runTerminalDaemon } = await import("./terminal-daemon-server.js");
|
|
@@ -103,6 +109,31 @@ async function main() {
|
|
|
103
109
|
process.stdout.write(`${configPath}\n`);
|
|
104
110
|
break;
|
|
105
111
|
}
|
|
112
|
+
case "storage:cleanup": {
|
|
113
|
+
const options = args.slice(1);
|
|
114
|
+
for (let i = 0; i < options.length; i++) {
|
|
115
|
+
if (options[i] === "-c" || options[i] === "--config") {
|
|
116
|
+
if (!options[++i] || options[i].startsWith("-"))
|
|
117
|
+
throw new Error("Missing config path.");
|
|
118
|
+
}
|
|
119
|
+
else if (options[i] !== "--apply")
|
|
120
|
+
throw new Error("Usage: wand storage:cleanup [--apply] [-c <config>]");
|
|
121
|
+
}
|
|
122
|
+
const { createStorageCleanup } = await import("./storage-cleanup.js");
|
|
123
|
+
const { dirname } = await import("node:path");
|
|
124
|
+
const { openUploadCleanupStore } = await import("./storage/uploads-repository.js");
|
|
125
|
+
const { resolveDatabasePath } = await import("./storage.js");
|
|
126
|
+
const database = openUploadCleanupStore(resolveDatabasePath(configPath), options.includes("--apply"));
|
|
127
|
+
const cleanup = createStorageCleanup(dirname(configPath), { uploads: database?.uploads });
|
|
128
|
+
try {
|
|
129
|
+
process.stdout.write(`${JSON.stringify(await cleanup.run(options.includes("--apply")), null, 2)}\n`);
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
await cleanup.stop();
|
|
133
|
+
database?.close();
|
|
134
|
+
}
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
106
137
|
case "config:show": {
|
|
107
138
|
// 展示合并后的视图(JSON 部署字段 + DB 偏好字段),但绝不打印运行时密钥。
|
|
108
139
|
const { ensureDatabaseFile, resolveDatabasePath, WandStorage } = await import("./storage.js");
|
|
@@ -279,6 +310,7 @@ Commands:
|
|
|
279
310
|
wand config:show Print current config
|
|
280
311
|
wand config:password Print current login password
|
|
281
312
|
wand config:set Update a simple config value
|
|
313
|
+
wand storage:cleanup [--apply] Inspect/reclaim abandoned Wand runtime storage
|
|
282
314
|
|
|
283
315
|
Agent runtime:
|
|
284
316
|
wand session:list List sessions as JSON
|
|
@@ -821,12 +853,13 @@ function setConfigValue(config, key, value) {
|
|
|
821
853
|
port: Number(value)
|
|
822
854
|
};
|
|
823
855
|
case "https":
|
|
856
|
+
case "autoCleanupTemporaryFiles":
|
|
824
857
|
if (value !== "true" && value !== "false") {
|
|
825
|
-
throw new Error(
|
|
858
|
+
throw new Error(`${key} must be 'true' or 'false'`);
|
|
826
859
|
}
|
|
827
860
|
return {
|
|
828
861
|
...config,
|
|
829
|
-
|
|
862
|
+
[key]: value === "true"
|
|
830
863
|
};
|
|
831
864
|
case "publicOrigin": {
|
|
832
865
|
if (!value)
|
|
@@ -861,13 +894,17 @@ function setConfigValue(config, key, value) {
|
|
|
861
894
|
* 都在 tui/commands.ts 内部处理。
|
|
862
895
|
*/
|
|
863
896
|
async function runServiceCommand(command, args, configPath) {
|
|
897
|
+
const { elevateServiceCommand, resolveServiceCommandScope } = await import("./service-command-privilege.js");
|
|
898
|
+
const scope = resolveServiceCommandScope(command, args);
|
|
899
|
+
if (command === "service:restart" && !readFlagValue(args, "-c") && !readFlagValue(args, "--config")) {
|
|
900
|
+
const { readServiceConfigPath } = await import("./service-manager/index.js");
|
|
901
|
+
configPath = readServiceConfigPath(scope) ?? configPath;
|
|
902
|
+
}
|
|
903
|
+
const elevated = await elevateServiceCommand(command, args, configPath, scope);
|
|
904
|
+
if (elevated !== null)
|
|
905
|
+
return elevated;
|
|
864
906
|
const { installService, uninstallService, serviceStart, serviceStop, serviceRestart, serviceStatus, serviceLogs, } = await import("./tui/commands.js");
|
|
865
907
|
const verbose = args.includes("--verbose");
|
|
866
|
-
// --user / --system 决定 scope;不传走库里 default(= system)。
|
|
867
|
-
// 同时传 --user 和 --system 时 --user 胜(更"友好"那一个不需要 root)。
|
|
868
|
-
const wantUser = args.includes("--user");
|
|
869
|
-
const wantSystem = args.includes("--system");
|
|
870
|
-
const scope = wantUser ? "user" : (wantSystem ? "system" : undefined);
|
|
871
908
|
switch (command) {
|
|
872
909
|
case "service:install": {
|
|
873
910
|
const result = installService({ configPath, scope });
|
|
@@ -897,7 +934,8 @@ async function runServiceCommand(command, args, configPath) {
|
|
|
897
934
|
return result.ok ? 0 : 1;
|
|
898
935
|
}
|
|
899
936
|
case "service:restart": {
|
|
900
|
-
const
|
|
937
|
+
const timeoutSeconds = Number(readFlagValue(args, "--timeout") ?? 0);
|
|
938
|
+
const result = await serviceRestart({ scope, configPath, timeoutSeconds });
|
|
901
939
|
printServiceResult(result, verbose);
|
|
902
940
|
return result.ok ? 0 : 1;
|
|
903
941
|
}
|
|
@@ -929,7 +967,7 @@ async function runServiceCommand(command, args, configPath) {
|
|
|
929
967
|
function printServiceResult(result, verbose) {
|
|
930
968
|
const prefix = result.ok ? "[wand]" : "[wand] ✗";
|
|
931
969
|
process.stdout.write(`${prefix} ${result.message}\n`);
|
|
932
|
-
if (verbose && result.detail) {
|
|
970
|
+
if ((verbose || !result.ok) && result.detail) {
|
|
933
971
|
process.stdout.write(result.detail + "\n");
|
|
934
972
|
}
|
|
935
973
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type { WandStorage } from "./storage.js";
|
|
|
7
7
|
* 升级路径:老 JSON 里仍存有这些字段时,首次启动会被搬到 DB(见 migrateLegacyPreferencesToDb),
|
|
8
8
|
* 然后下一次 saveConfig 写回 JSON 时它们会被剥离(见 stripPreferenceFields)。
|
|
9
9
|
*/
|
|
10
|
-
export type PreferenceKey = "defaultProvider" | "defaultSessionKind" | "defaultEngine" | "defaultTaskWorktree" | "defaultMode" | "defaultCwd" | "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultPiModel" | "
|
|
10
|
+
export type PreferenceKey = "defaultProvider" | "defaultSessionKind" | "defaultEngine" | "defaultTaskWorktree" | "defaultMode" | "defaultCwd" | "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultPiModel" | "modelGroups" | "commitCli" | "commitModel" | "systemAiCli" | "systemAiModel" | "defaultThinkingEffort" | "language" | "cardDefaults" | "inheritEnv" | "taskRetention" | "userProfile" | "localDecision";
|
|
11
11
|
/**
|
|
12
12
|
* 每个 provider 的默认模型偏好字段。新增 provider 时只改这一处:
|
|
13
13
|
* 偏好读写、环境归一、默认模型投影都从这张表派生。
|
|
@@ -19,7 +19,6 @@ export declare const PROVIDER_MODEL_PREFERENCE_FIELDS: {
|
|
|
19
19
|
readonly grok: "defaultGrokModel";
|
|
20
20
|
readonly qoder: "defaultQoderModel";
|
|
21
21
|
readonly pi: "defaultPiModel";
|
|
22
|
-
readonly gemini: "defaultGeminiModel";
|
|
23
22
|
};
|
|
24
23
|
export type ProviderModelField = (typeof PROVIDER_MODEL_PREFERENCE_FIELDS)[SessionProvider];
|
|
25
24
|
export declare function isPreferenceKey(key: string): key is PreferenceKey;
|
|
@@ -74,7 +73,7 @@ export interface PreferenceSpec<T> {
|
|
|
74
73
|
};
|
|
75
74
|
readonly hot: true;
|
|
76
75
|
}
|
|
77
|
-
declare const TRIMMED_STRING_PREFERENCE_KEYS: readonly ["defaultModel", "defaultCodexModel", "defaultOpenCodeModel", "defaultGrokModel", "defaultQoderModel", "defaultPiModel", "
|
|
76
|
+
declare const TRIMMED_STRING_PREFERENCE_KEYS: readonly ["defaultModel", "defaultCodexModel", "defaultOpenCodeModel", "defaultGrokModel", "defaultQoderModel", "defaultPiModel", "commitModel", "systemAiModel", "language"];
|
|
78
77
|
export type TrimmedStringPreferenceKey = (typeof TRIMMED_STRING_PREFERENCE_KEYS)[number];
|
|
79
78
|
/** Every preference key, in schema order. */
|
|
80
79
|
export declare const PREFERENCE_KEYS: PreferenceKey[];
|
package/dist/config.js
CHANGED
|
@@ -24,7 +24,6 @@ export const PROVIDER_MODEL_PREFERENCE_FIELDS = {
|
|
|
24
24
|
grok: "defaultGrokModel",
|
|
25
25
|
qoder: "defaultQoderModel",
|
|
26
26
|
pi: "defaultPiModel",
|
|
27
|
-
gemini: "defaultGeminiModel",
|
|
28
27
|
};
|
|
29
28
|
const PROVIDER_MODEL_FIELDS = Object.values(PROVIDER_MODEL_PREFERENCE_FIELDS);
|
|
30
29
|
function preferenceStorageKey(key) {
|
|
@@ -59,6 +58,7 @@ export const defaultConfig = () => ({
|
|
|
59
58
|
host: "127.0.0.1",
|
|
60
59
|
port: 8443,
|
|
61
60
|
https: false,
|
|
61
|
+
autoCleanupTemporaryFiles: true,
|
|
62
62
|
password: "change-me",
|
|
63
63
|
shell: resolveDefaultShell(),
|
|
64
64
|
startupCommands: [],
|
|
@@ -169,7 +169,14 @@ export async function loadConfigWithStorage(configPath, storage) {
|
|
|
169
169
|
}
|
|
170
170
|
migrateLegacyPreferencesToDb(rawInput, storage);
|
|
171
171
|
migrateLegacyPasswordToDb(rawInput, storage);
|
|
172
|
-
|
|
172
|
+
// DB preferences are authoritative, including a user correction of an old
|
|
173
|
+
// JSON tool selection. Do not validate the shadowed legacy JSON value.
|
|
174
|
+
const mergeInput = { ...rawInput };
|
|
175
|
+
for (const key of EXECUTION_TOOL_PREFERENCES) {
|
|
176
|
+
if (storage.hasPreference(preferenceStorageKey(key)))
|
|
177
|
+
delete mergeInput[key];
|
|
178
|
+
}
|
|
179
|
+
const config = mergeWithDefaults(mergeInput);
|
|
173
180
|
applyStoragePreferences(config, storage);
|
|
174
181
|
// appSecret: DB 是权威源。
|
|
175
182
|
// - DB 有 → 直接覆盖 runtime config(即使 mergeWithDefaults 临时生成了新的,也以 DB 为准)
|
|
@@ -306,7 +313,7 @@ function enumSpec(key, options) {
|
|
|
306
313
|
}
|
|
307
314
|
const TRIMMED_STRING_PREFERENCE_KEYS = [
|
|
308
315
|
"defaultModel", "defaultCodexModel", "defaultOpenCodeModel", "defaultGrokModel", "defaultQoderModel",
|
|
309
|
-
"defaultPiModel", "
|
|
316
|
+
"defaultPiModel", "commitModel", "systemAiModel", "language",
|
|
310
317
|
];
|
|
311
318
|
function isOneOf(options) {
|
|
312
319
|
return (value) => typeof value === "string" && options.includes(value);
|
|
@@ -364,7 +371,6 @@ const PREFERENCE_SPECS = {
|
|
|
364
371
|
defaultGrokModel: trimmedStringSpec("defaultGrokModel", () => ""),
|
|
365
372
|
defaultQoderModel: trimmedStringSpec("defaultQoderModel", () => ""),
|
|
366
373
|
defaultPiModel: trimmedStringSpec("defaultPiModel", () => ""),
|
|
367
|
-
defaultGeminiModel: trimmedStringSpec("defaultGeminiModel", () => ""),
|
|
368
374
|
modelGroups: {
|
|
369
375
|
storageKey: preferenceStorageKey("modelGroups"),
|
|
370
376
|
default: () => [],
|
|
@@ -472,6 +478,13 @@ export function preferenceSpec(key) {
|
|
|
472
478
|
}
|
|
473
479
|
/** Keys applied to the live process config without restart (all of them today). */
|
|
474
480
|
export const HOT_PREFERENCE_KEYS = PREFERENCE_KEYS.filter((key) => PREFERENCE_SPECS[key].hot);
|
|
481
|
+
const EXECUTION_TOOL_PREFERENCES = ["defaultProvider", "systemAiCli", "commitCli"];
|
|
482
|
+
/** Removed/unknown explicit tools must never silently become another executable. */
|
|
483
|
+
function assertSupportedToolPreference(key, value) {
|
|
484
|
+
if (typeof value === "string" && value.trim() && !PREFERENCE_SPECS[key].read(value)) {
|
|
485
|
+
throw new Error(`不支持的执行工具偏好 ${key}: ${value}。请重新选择受支持的工具;原配置与历史已保留。`);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
475
488
|
/** 用 DB 里的偏好覆盖 config 对象(in-place),返回同一个引用。DB 里没有的字段不动,保留默认值。 */
|
|
476
489
|
export function applyStoragePreferences(config, storage) {
|
|
477
490
|
const target = config;
|
|
@@ -479,7 +492,11 @@ export function applyStoragePreferences(config, storage) {
|
|
|
479
492
|
const spec = PREFERENCE_SPECS[key];
|
|
480
493
|
if (!storage.hasPreference(spec.storageKey))
|
|
481
494
|
continue;
|
|
482
|
-
const
|
|
495
|
+
const raw = storage.getPreference(spec.storageKey, spec.default());
|
|
496
|
+
if (EXECUTION_TOOL_PREFERENCES.includes(key)) {
|
|
497
|
+
assertSupportedToolPreference(key, raw);
|
|
498
|
+
}
|
|
499
|
+
const result = spec.read(raw);
|
|
483
500
|
if (result)
|
|
484
501
|
target[key] = result.value;
|
|
485
502
|
}
|
|
@@ -649,12 +666,16 @@ function normalizeStructuredChatPersona(input) {
|
|
|
649
666
|
return { user, assistant };
|
|
650
667
|
}
|
|
651
668
|
function mergeWithDefaults(input) {
|
|
669
|
+
for (const key of EXECUTION_TOOL_PREFERENCES)
|
|
670
|
+
assertSupportedToolPreference(key, input[key]);
|
|
652
671
|
const defaults = defaultConfig();
|
|
653
672
|
return {
|
|
654
673
|
...defaults,
|
|
655
674
|
...input,
|
|
656
675
|
// Ensure https is boolean
|
|
657
676
|
https: typeof input.https === "boolean" ? input.https : defaults.https,
|
|
677
|
+
autoCleanupTemporaryFiles: typeof input.autoCleanupTemporaryFiles === "boolean"
|
|
678
|
+
? input.autoCleanupTemporaryFiles : defaults.autoCleanupTemporaryFiles,
|
|
658
679
|
browserExtensionIds: Array.isArray(input.browserExtensionIds)
|
|
659
680
|
? input.browserExtensionIds.filter((id) => typeof id === "string").map((id) => id.trim()).filter(Boolean)
|
|
660
681
|
: undefined,
|
|
@@ -8,4 +8,4 @@ export interface CoreStatus {
|
|
|
8
8
|
export declare function remainingCoreRequestBudget(deadline: number): number;
|
|
9
9
|
export declare function checkCoreStatus(configPath: string): Promise<void>;
|
|
10
10
|
/** Zero means wait indefinitely. A timeout/error NEVER runs the stop command. */
|
|
11
|
-
export declare function waitForCoreTurnsComplete(configPath: string, timeoutSeconds?: number, stopCommand?: string[], allowStopped?: boolean): Promise<number>;
|
|
11
|
+
export declare function waitForCoreTurnsComplete(configPath: string, timeoutSeconds?: number, stopCommand?: string[] | (() => Promise<number>), allowStopped?: boolean): Promise<number>;
|
package/dist/core-status-cli.js
CHANGED
|
@@ -181,7 +181,7 @@ export async function checkCoreStatus(configPath) {
|
|
|
181
181
|
export async function waitForCoreTurnsComplete(configPath, timeoutSeconds = 0, stopCommand, allowStopped = false) {
|
|
182
182
|
if (!Number.isFinite(timeoutSeconds) || timeoutSeconds < 0)
|
|
183
183
|
throw new Error("--timeout 必须是非负秒数(0 表示一直等待)。");
|
|
184
|
-
if (stopCommand && !stopCommand.length)
|
|
184
|
+
if (Array.isArray(stopCommand) && !stopCommand.length)
|
|
185
185
|
throw new Error("缺少等待完成后要执行的停止命令。");
|
|
186
186
|
const deadline = timeoutSeconds > 0 ? Date.now() + timeoutSeconds * 1000 : Infinity;
|
|
187
187
|
const control = await openControl(configPath, deadline);
|
|
@@ -240,6 +240,8 @@ export async function waitForCoreTurnsComplete(configPath, timeoutSeconds = 0, s
|
|
|
240
240
|
console.log("[wand] 原生 Core 回合已全部完成,可以停止服务。");
|
|
241
241
|
if (!stopCommand)
|
|
242
242
|
return 0;
|
|
243
|
+
if (typeof stopCommand === "function")
|
|
244
|
+
return await stopCommand();
|
|
243
245
|
return await new Promise((resolve, reject) => {
|
|
244
246
|
const child = spawn(stopCommand[0], stopCommand.slice(1), { stdio: "inherit" });
|
|
245
247
|
child.once("error", reject);
|
|
@@ -4,6 +4,16 @@ export declare function rejectPendingRequests<T extends {
|
|
|
4
4
|
timer: NodeJS.Timeout;
|
|
5
5
|
reject(error: Error): void;
|
|
6
6
|
}>(pending: Map<number, T>, error: Error): void;
|
|
7
|
+
/** Replay a retained terminal event asynchronously; disposal cancels queued delivery. */
|
|
8
|
+
export declare function subscribeWithPendingEvent<Event>(listeners: Set<(event: Event) => void>, listener: (event: Event) => void, pending: Event | null): {
|
|
9
|
+
dispose(): void;
|
|
10
|
+
};
|
|
11
|
+
/** A queued batch belongs to this registration; the owner has already taken it from its buffer. */
|
|
12
|
+
export declare function subscribeWithBufferedEvents<Event>(listeners: Set<(event: Event) => void>, listener: (event: Event) => void, pending: readonly Event[]): {
|
|
13
|
+
dispose(): void;
|
|
14
|
+
};
|
|
15
|
+
/** Snapshot listeners once so re-entrant subscription changes cannot alter the current delivery. */
|
|
16
|
+
export declare function emitToSubscribedListeners<Event>(listeners: Set<(event: Event) => void>, event: Event): boolean;
|
|
7
17
|
/** Bound connect attempts, including sockets that close before the handshake. */
|
|
8
18
|
export declare function waitForDaemonSocket(socket: net.Socket): Promise<void>;
|
|
9
19
|
/** Probe the application, not just the fd: a half-open socket may still be writable. */
|
|
@@ -10,6 +10,34 @@ export function rejectPendingRequests(pending, error) {
|
|
|
10
10
|
}
|
|
11
11
|
pending.clear();
|
|
12
12
|
}
|
|
13
|
+
/** Replay a retained terminal event asynchronously; disposal cancels queued delivery. */
|
|
14
|
+
export function subscribeWithPendingEvent(listeners, listener, pending) {
|
|
15
|
+
listeners.add(listener);
|
|
16
|
+
if (pending)
|
|
17
|
+
queueMicrotask(() => { if (listeners.has(listener))
|
|
18
|
+
listener(pending); });
|
|
19
|
+
return { dispose: () => listeners.delete(listener) };
|
|
20
|
+
}
|
|
21
|
+
/** A queued batch belongs to this registration; the owner has already taken it from its buffer. */
|
|
22
|
+
export function subscribeWithBufferedEvents(listeners, listener, pending) {
|
|
23
|
+
const subscription = subscribeWithPendingEvent(listeners, listener, null);
|
|
24
|
+
if (pending.length > 0)
|
|
25
|
+
queueMicrotask(() => {
|
|
26
|
+
if (!listeners.has(listener))
|
|
27
|
+
return;
|
|
28
|
+
for (const event of pending)
|
|
29
|
+
listener(event);
|
|
30
|
+
});
|
|
31
|
+
return subscription;
|
|
32
|
+
}
|
|
33
|
+
/** Snapshot listeners once so re-entrant subscription changes cannot alter the current delivery. */
|
|
34
|
+
export function emitToSubscribedListeners(listeners, event) {
|
|
35
|
+
if (listeners.size === 0)
|
|
36
|
+
return false;
|
|
37
|
+
for (const listener of Array.from(listeners))
|
|
38
|
+
listener(event);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
13
41
|
/** Bound connect attempts, including sockets that close before the handshake. */
|
|
14
42
|
export function waitForDaemonSocket(socket) {
|
|
15
43
|
return new Promise((resolve, reject) => {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const DIAGNOSTIC_LOG_MAX_BYTES: number;
|
|
2
|
+
export declare const DIAGNOSTIC_LOG_ROTATIONS = 3;
|
|
3
|
+
export declare const DIAGNOSTIC_LOG_RETENTION_DAYS = 14;
|
|
4
|
+
export declare const DIAGNOSTIC_RECEIPT_SUFFIX = ".wand-log-receipt.json";
|
|
5
|
+
export declare function diagnosticReceiptMatches(file: string): boolean;
|
|
6
|
+
/** Only redundant diagnostics: PTY history and native protocol sources never enter this writer. */
|
|
7
|
+
export declare function appendBoundedDiagnostic(file: string, data: string | Buffer, maxBytes?: number, rotations?: number): void;
|
|
8
|
+
/** Attach only to launchd/file redirections matching this instance; keep TTYs and journald untouched. */
|
|
9
|
+
export declare function installServiceDiagnosticRotation(configDir: string, kind: "web" | "terminald"): () => void;
|
|
10
|
+
export declare function isDiagnosticArchive(name: string): boolean;
|