@co0ontty/wand 4.79.0 → 4.80.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 +18 -6
- package/dist/agent-dispatch.d.ts +3 -0
- package/dist/agent-dispatch.js +5 -0
- package/dist/ai-team-runner.js +4 -2
- package/dist/ai-team-types.d.ts +34 -0
- package/dist/ai-team-types.js +12 -1
- package/dist/build-info.json +3 -3
- package/dist/config.d.ts +4 -3
- package/dist/config.js +25 -0
- package/dist/git-quick-commit.d.ts +9 -8
- package/dist/git-quick-commit.js +123 -8
- package/dist/models.d.ts +3 -0
- package/dist/models.js +24 -2
- package/dist/process-manager.js +16 -3
- package/dist/provider-cli-updater.d.ts +1 -1
- package/dist/provider-cli-updater.js +12 -1
- package/dist/server-ai-team-routes.js +4 -1
- package/dist/server-employee-routes.d.ts +22 -0
- package/dist/server-employee-routes.js +277 -0
- package/dist/server-session-routes.js +38 -13
- package/dist/server-settings-routes.js +5 -0
- package/dist/server-task-routes.d.ts +2 -0
- package/dist/server-task-routes.js +174 -22
- package/dist/server-update-routes.js +1 -1
- package/dist/server-workspace-routes.js +3 -0
- package/dist/server.js +16 -3
- package/dist/session-ai-context.d.ts +7 -2
- package/dist/session-ai-context.js +36 -9
- package/dist/session-provider.d.ts +6 -1
- package/dist/session-provider.js +27 -1
- package/dist/session-transport.js +4 -0
- package/dist/silicon-employee-dispatch.d.ts +9 -0
- package/dist/silicon-employee-dispatch.js +13 -0
- package/dist/silicon-employee-draft.d.ts +36 -0
- package/dist/silicon-employee-draft.js +146 -0
- package/dist/storage.d.ts +19 -2
- package/dist/storage.js +158 -7
- package/dist/structured-gemini-adapter.d.ts +40 -0
- package/dist/structured-gemini-adapter.js +198 -0
- package/dist/structured-provider-common.d.ts +2 -1
- package/dist/structured-provider-common.js +7 -2
- package/dist/structured-session-manager.d.ts +10 -0
- package/dist/structured-session-manager.js +106 -14
- package/dist/system-ai.js +1 -1
- package/dist/system-employee.d.ts +35 -0
- package/dist/system-employee.js +81 -0
- package/dist/task-types.d.ts +8 -1
- package/dist/task-types.js +7 -0
- package/dist/types.d.ts +23 -4
- package/dist/web-ui/content/ai-teams.js +13 -13
- package/dist/web-ui/content/scripts.js +64 -64
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/content/tailwind.css +3 -0
- package/dist/web-ui/embedded-assets.js +3 -3
- package/dist/web-ui/provider-identity.d.ts +2 -1
- package/dist/web-ui/provider-identity.js +10 -1
- package/dist/web-ui/session-activity.js +1 -1
- package/package.json +1 -1
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/), with terminal and structured conversation views, persistent resumable sessions, permission controls, file browsing, and native clients for multiple platforms.
|
|
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/), as well as [Gemini CLI](https://github.com/google-gemini/gemini-cli), 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, and
|
|
51
|
+
- **Multiple providers** — create PTY or structured sessions for Claude Code, Codex, OpenCode, Grok, Qoder CLI, Pi, and Gemini CLI
|
|
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
|
|
|
@@ -65,7 +65,7 @@ wand config:password
|
|
|
65
65
|
- **Native clients** — Android, iOS, and macOS clients with encrypted connection codes and update checks
|
|
66
66
|
- **HTTPS** — optional self-signed certificates for remote or mobile access
|
|
67
67
|
- **Update channels** — built-in stable/beta update checks and upgrade prompts
|
|
68
|
-
- **CLI updates** — check and quickly update Claude Code, Codex, OpenCode, Qoder CLI, and
|
|
68
|
+
- **CLI updates** — check and quickly update Claude Code, Codex, OpenCode, Qoder CLI, Pi, and Gemini CLI from server settings, with optional automatic updates
|
|
69
69
|
|
|
70
70
|
### Configuration
|
|
71
71
|
|
|
@@ -150,11 +150,17 @@ npm uninstall -g opencode
|
|
|
150
150
|
npm install -g opencode-ai@latest
|
|
151
151
|
```
|
|
152
152
|
|
|
153
|
+
Gemini structured sessions require `@google/gemini-cli` 0.11 or newer (the release that added headless `--output-format stream-json`):
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
npm install -g @google/gemini-cli@latest
|
|
157
|
+
```
|
|
158
|
+
|
|
153
159
|
## 中文
|
|
154
160
|
|
|
155
161
|
### 概览
|
|
156
162
|
|
|
157
|
-
通过浏览器远程访问和管理本地 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)
|
|
163
|
+
通过浏览器远程访问和管理本地 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/) 和 [Gemini CLI](https://github.com/google-gemini/gemini-cli),提供终端和结构化对话双视图、会话持久化与恢复、权限管控、文件浏览和多平台客户端。
|
|
158
164
|
|
|
159
165
|
### 安装
|
|
160
166
|
|
|
@@ -191,12 +197,18 @@ npm uninstall -g opencode
|
|
|
191
197
|
npm install -g opencode-ai@latest
|
|
192
198
|
```
|
|
193
199
|
|
|
200
|
+
Gemini 结构化会话需要 `@google/gemini-cli` 0.11 或更高版本(该版本才加入 headless 的 `--output-format stream-json`):
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
npm install -g @google/gemini-cli@latest
|
|
204
|
+
```
|
|
205
|
+
|
|
194
206
|
### 功能
|
|
195
207
|
|
|
196
208
|
#### 核心
|
|
197
209
|
|
|
198
210
|
- **双视图模式** — 终端原始输出和结构化对话视图可随时切换,同一会话两种呈现
|
|
199
|
-
- **多 Provider 支持** — Claude Code、Codex、OpenCode、Grok、Qoder CLI 和
|
|
211
|
+
- **多 Provider 支持** — Claude Code、Codex、OpenCode、Grok、Qoder CLI、Pi 和 Gemini CLI 均可创建 PTY 或结构化会话
|
|
200
212
|
- **会话管理** — 创建、归档并携带原生上下文恢复 Claude、Codex、OpenCode、Grok、Qoder 会话;支持恢复 Claude/Codex 原生历史记录;会话列表显示摘要
|
|
201
213
|
- **权限控制** — 可视化权限提示,支持逐次确认、单次批准、本轮记忆等策略
|
|
202
214
|
|
|
@@ -214,7 +226,7 @@ npm install -g opencode-ai@latest
|
|
|
214
226
|
- **多平台客户端** — Android / iOS / macOS 原生客户端,支持加密连接码分发、自动更新检查
|
|
215
227
|
- **HTTPS** — 可选自签证书,适合远程或移动端访问
|
|
216
228
|
- **版本管理** — 内置更新检查与升级提示,支持 stable/beta 双通道
|
|
217
|
-
- **CLI 更新** — 在服务端设置中检查并快速更新 Claude Code、Codex、OpenCode、Qoder CLI 和
|
|
229
|
+
- **CLI 更新** — 在服务端设置中检查并快速更新 Claude Code、Codex、OpenCode、Qoder CLI、Pi 和 Gemini CLI,也可开启自动更新
|
|
218
230
|
|
|
219
231
|
### 配置
|
|
220
232
|
|
package/dist/agent-dispatch.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { SessionRegistry } from "./session-registry.js";
|
|
|
3
3
|
import type { WandStorage } from "./storage.js";
|
|
4
4
|
import type { StructuredSessionManager } from "./structured-session-manager.js";
|
|
5
5
|
import type { WandTask, WandTaskAgent } from "./task-types.js";
|
|
6
|
+
import type { SiliconEmployee } from "./ai-team-types.js";
|
|
6
7
|
import type { SessionSnapshot, WandConfig } from "./types.js";
|
|
7
8
|
export interface AgentDispatchDeps {
|
|
8
9
|
storage: WandStorage;
|
|
@@ -32,6 +33,8 @@ export declare function dispatchAgentForTask(deps: AgentDispatchDeps, input: {
|
|
|
32
33
|
prompt: string;
|
|
33
34
|
automationId: string;
|
|
34
35
|
systemPrompt?: string;
|
|
36
|
+
employee?: SiliconEmployee;
|
|
37
|
+
employeeCandidateIndex?: number;
|
|
35
38
|
}): Promise<AgentDispatchResult>;
|
|
36
39
|
/**
|
|
37
40
|
* 给已有会话追加一条消息。结构化会话走 sendMessage(不等整轮结束);
|
package/dist/agent-dispatch.js
CHANGED
|
@@ -50,6 +50,11 @@ export async function dispatchAgentForTask(deps, input) {
|
|
|
50
50
|
sessionSource: "automation",
|
|
51
51
|
automationId: input.automationId,
|
|
52
52
|
systemPrompt,
|
|
53
|
+
employeeId: input.employee?.id,
|
|
54
|
+
employeeName: input.employee?.name,
|
|
55
|
+
employeeAvatar: input.employee?.avatar,
|
|
56
|
+
employeeCandidates: input.employee?.agents,
|
|
57
|
+
employeeCandidateIndex: input.employeeCandidateIndex,
|
|
53
58
|
workspaceId: group.workspaceId,
|
|
54
59
|
workspaceTaskId: group.workspaceTaskId,
|
|
55
60
|
});
|
package/dist/ai-team-runner.js
CHANGED
|
@@ -913,12 +913,14 @@ export class AiTeamRunner {
|
|
|
913
913
|
return;
|
|
914
914
|
}
|
|
915
915
|
// 负责人的模型报错时停下来等用户(换模型或稍后回复即可接着来),不当成格式错误空转重试。
|
|
916
|
+
// 按真实角色身份发出正常对话气泡并展示完整报错,而不是次级 notice 小字。
|
|
916
917
|
const leader = leaderOf(run.team);
|
|
917
918
|
if (outcome.kind === "failed" && outcome.sessionError) {
|
|
918
919
|
this.saveStep({ ...step, status: "failed", report: outcome.text, endedAt });
|
|
919
920
|
this.postTurn(run, {
|
|
920
|
-
role: "assistant",
|
|
921
|
-
|
|
921
|
+
role: "assistant",
|
|
922
|
+
author: chatAuthor(leader, step.sessionId, this.actualAgent(leader, step)),
|
|
923
|
+
content: chatText(`我的模型调用失败,无法继续处理任务:\n\n\`\`\`\n${outcome.text}\n\`\`\`\n\n可以去团队设置页面为我更换可用模型,或者在群里回复我继续重试。`),
|
|
922
924
|
});
|
|
923
925
|
this.setStatus(run, "waiting_user", `负责人出错:${outcome.text}`);
|
|
924
926
|
return;
|
package/dist/ai-team-types.d.ts
CHANGED
|
@@ -55,7 +55,40 @@ export interface StepDispatchInfo {
|
|
|
55
55
|
}>;
|
|
56
56
|
}
|
|
57
57
|
/** 上传头像在前端缩成小图后的上限(data URL 字符数)。 */
|
|
58
|
+
export interface SiliconEmployee {
|
|
59
|
+
id: string;
|
|
60
|
+
name: string;
|
|
61
|
+
duty: string;
|
|
62
|
+
prompt: string;
|
|
63
|
+
avatar: string;
|
|
64
|
+
agents: WandTaskAgent[];
|
|
65
|
+
/**
|
|
66
|
+
* 非空 = Wand 内置员工(如 "wand-ops" 系统运维):名字/职责/人设/头像锁定,
|
|
67
|
+
* 不可归档、不可删除,只有执行候选由用户维护。
|
|
68
|
+
*/
|
|
69
|
+
systemKey?: string;
|
|
70
|
+
archivedAt?: string;
|
|
71
|
+
createdAt: string;
|
|
72
|
+
updatedAt: string;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* AI 按自然语言期望起草的员工配置:只带一个首选执行候选,
|
|
76
|
+
* 其余手动字段(头像、备用候选)仍由表单和「高级配置」负责。
|
|
77
|
+
*/
|
|
78
|
+
export interface SiliconEmployeeDraft {
|
|
79
|
+
name: string;
|
|
80
|
+
duty: string;
|
|
81
|
+
prompt: string;
|
|
82
|
+
agent: WandTaskAgent;
|
|
83
|
+
}
|
|
58
84
|
export declare const AI_TEAM_AVATAR_MAX_CHARS = 60000;
|
|
85
|
+
export declare const SILICON_EMPLOYEE_AVATAR_MAX_CHARS = 60000;
|
|
86
|
+
export declare const SYSTEM_EMPLOYEE_KEY = "wand-ops";
|
|
87
|
+
export declare const SYSTEM_EMPLOYEE_ID = "e_wand_ops";
|
|
88
|
+
export declare const SYSTEM_EMPLOYEE_NAME = "\u52E4\u52B3\u7684\u521D\u4E8C";
|
|
89
|
+
/** 员工列表 / 设置页上的标识。 */
|
|
90
|
+
export declare const SYSTEM_EMPLOYEE_TAG = "\u7CFB\u7EDF\u8FD0\u7EF4";
|
|
91
|
+
export declare function isSystemSiliconEmployee(employee: Pick<SiliconEmployee, "systemKey"> | null | undefined): boolean;
|
|
59
92
|
export interface AiTeam {
|
|
60
93
|
id: string;
|
|
61
94
|
name: string;
|
|
@@ -102,6 +135,7 @@ export interface AiTeamRun {
|
|
|
102
135
|
*/
|
|
103
136
|
export interface AiTeamRunChatMarker {
|
|
104
137
|
runId: string;
|
|
138
|
+
teamId: string;
|
|
105
139
|
teamName: string;
|
|
106
140
|
memberCount: number;
|
|
107
141
|
}
|
package/dist/ai-team-types.js
CHANGED
|
@@ -25,8 +25,19 @@ export function memberAgents(member) {
|
|
|
25
25
|
export function agentKey(agent) {
|
|
26
26
|
return `${agent.provider}|${agent.model}|${agent.thinkingEffort}|${agent.mode}|${agent.kind}`;
|
|
27
27
|
}
|
|
28
|
-
/** 上传头像在前端缩成小图后的上限(data URL 字符数)。 */
|
|
29
28
|
export const AI_TEAM_AVATAR_MAX_CHARS = 60_000;
|
|
29
|
+
export const SILICON_EMPLOYEE_AVATAR_MAX_CHARS = 60_000;
|
|
30
|
+
// ============ 内置「系统运维」员工 ============
|
|
31
|
+
// 纯常量与判定放在这里:浏览器端要拿 Tag 和判定,不能把 server-only 的
|
|
32
|
+
// system-employee.ts(依赖 node:fs)拉进前端 bundle。
|
|
33
|
+
export const SYSTEM_EMPLOYEE_KEY = "wand-ops";
|
|
34
|
+
export const SYSTEM_EMPLOYEE_ID = "e_wand_ops";
|
|
35
|
+
export const SYSTEM_EMPLOYEE_NAME = "勤劳的初二";
|
|
36
|
+
/** 员工列表 / 设置页上的标识。 */
|
|
37
|
+
export const SYSTEM_EMPLOYEE_TAG = "系统运维";
|
|
38
|
+
export function isSystemSiliconEmployee(employee) {
|
|
39
|
+
return employee?.systemKey === SYSTEM_EMPLOYEE_KEY;
|
|
40
|
+
}
|
|
30
41
|
export const AI_TEAM_ACTIVE_RUN_STATUSES = ["running", "awaiting_approval", "waiting_user"];
|
|
31
42
|
/** 终态:不会再有 live 变化,runner 据此清掉该 run 的推送指纹,避免长跑服务按 run 数攒字符串。 */
|
|
32
43
|
export const AI_TEAM_TERMINAL_RUN_STATUSES = ["done", "failed", "stopped"];
|
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-09-
|
|
4
|
-
"version": "4.
|
|
2
|
+
"commit": "f8e0c448bbfdf5623cc9dc79e12b46984d1d21c6",
|
|
3
|
+
"builtAt": "2026-09-30T06:54:45.317Z",
|
|
4
|
+
"version": "4.80.0",
|
|
5
5
|
"channel": "stable"
|
|
6
6
|
}
|
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 declare const PREFERENCE_KEYS: readonly ["defaultProvider", "defaultSessionKind", "defaultTaskWorktree", "defaultMode", "defaultCwd", "defaultModel", "defaultCodexModel", "defaultOpenCodeModel", "defaultGrokModel", "defaultQoderModel", "defaultPiModel", "commitCli", "commitModel", "commitAiSource", "systemAi", "systemAiCli", "systemAiModel", "defaultThinkingEffort", "structuredRunner", "language", "cardDefaults", "inheritEnv"];
|
|
10
|
+
export declare const PREFERENCE_KEYS: readonly ["defaultProvider", "defaultSessionKind", "defaultTaskWorktree", "defaultMode", "defaultCwd", "defaultModel", "defaultCodexModel", "defaultOpenCodeModel", "defaultGrokModel", "defaultQoderModel", "defaultPiModel", "defaultGeminiModel", "commitCli", "commitModel", "commitAiSource", "systemAi", "systemAiCli", "systemAiModel", "defaultThinkingEffort", "structuredRunner", "language", "cardDefaults", "inheritEnv"];
|
|
11
11
|
export type PreferenceKey = (typeof PREFERENCE_KEYS)[number];
|
|
12
12
|
export declare function isPreferenceKey(key: string): key is PreferenceKey;
|
|
13
13
|
/**
|
|
@@ -47,12 +47,13 @@ export declare function writePreferenceToStorage(config: WandConfig, storage: Wa
|
|
|
47
47
|
*/
|
|
48
48
|
export declare function validateCommitAiConfig(_config: Pick<WandConfig, "commitAiSource" | "systemAi">): void;
|
|
49
49
|
export declare function isExecutionMode(value: unknown): value is ExecutionMode;
|
|
50
|
-
export declare function getProviderDefaultModels(config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultPiModel">): {
|
|
50
|
+
export declare function getProviderDefaultModels(config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultPiModel" | "defaultGeminiModel">): {
|
|
51
51
|
claude: string;
|
|
52
52
|
codex: string;
|
|
53
53
|
opencode: string;
|
|
54
54
|
grok: string;
|
|
55
55
|
qoder: string;
|
|
56
56
|
pi: string;
|
|
57
|
+
gemini: string;
|
|
57
58
|
};
|
|
58
|
-
export declare function getDefaultModelForProvider(config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultPiModel">, provider: SessionProvider | undefined): string;
|
|
59
|
+
export declare function getDefaultModelForProvider(config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultPiModel" | "defaultGeminiModel">, provider: SessionProvider | undefined): string;
|
package/dist/config.js
CHANGED
|
@@ -29,6 +29,7 @@ export const PREFERENCE_KEYS = [
|
|
|
29
29
|
"defaultGrokModel",
|
|
30
30
|
"defaultQoderModel",
|
|
31
31
|
"defaultPiModel",
|
|
32
|
+
"defaultGeminiModel",
|
|
32
33
|
"commitCli",
|
|
33
34
|
"commitModel",
|
|
34
35
|
"commitAiSource",
|
|
@@ -102,6 +103,7 @@ export const defaultConfig = () => ({
|
|
|
102
103
|
defaultGrokModel: "",
|
|
103
104
|
defaultQoderModel: "",
|
|
104
105
|
defaultPiModel: "",
|
|
106
|
+
defaultGeminiModel: "",
|
|
105
107
|
commitCli: "claude",
|
|
106
108
|
commitModel: "",
|
|
107
109
|
commitAiSource: "cli",
|
|
@@ -225,6 +227,14 @@ export async function loadConfigWithStorage(configPath, storage) {
|
|
|
225
227
|
reconcileAppSecret(config, storage);
|
|
226
228
|
// password: DB 优先映射到 runtime config,让 config:show 与 server.ts 都看到真值
|
|
227
229
|
applyStoragePassword(config, storage);
|
|
230
|
+
// 内置「系统运维」员工:Wand 自有 AI 调用(commit message / 标题 / 提示词优化 /
|
|
231
|
+
// 员工起草)都由它执行。这里只做幂等补齐——首次按用户已有的系统 AI 工具与默认
|
|
232
|
+
// provider 建首条候选,之后候选由用户在员工设置里维护,不再回写 config。
|
|
233
|
+
storage.ensureSystemSiliconEmployee({
|
|
234
|
+
cli: config.systemAiCli,
|
|
235
|
+
model: config.systemAiModel,
|
|
236
|
+
provider: config.defaultProvider,
|
|
237
|
+
});
|
|
228
238
|
// 如果 JSON 里有偏好字段(说明是老版本配置或刚迁移),重写一次干净版本
|
|
229
239
|
const hasLegacyPrefs = PREFERENCE_KEYS.some((key) => key in rawInput);
|
|
230
240
|
const hasLegacySecrets = "password" in rawInput || "appSecret" in rawInput;
|
|
@@ -366,6 +376,11 @@ export function applyStoragePreferences(config, storage) {
|
|
|
366
376
|
if (typeof v === "string")
|
|
367
377
|
config.defaultPiModel = v.trim();
|
|
368
378
|
}
|
|
379
|
+
if (storage.hasPreference(preferenceStorageKey("defaultGeminiModel"))) {
|
|
380
|
+
const v = storage.getPreference(preferenceStorageKey("defaultGeminiModel"), defaults.defaultGeminiModel ?? "");
|
|
381
|
+
if (typeof v === "string")
|
|
382
|
+
config.defaultGeminiModel = v.trim();
|
|
383
|
+
}
|
|
369
384
|
if (storage.hasPreference(preferenceStorageKey("commitCli"))) {
|
|
370
385
|
const v = storage.getPreference(preferenceStorageKey("commitCli"), defaults.commitCli ?? "claude");
|
|
371
386
|
if (v === "claude" || v === "codex" || v === "opencode")
|
|
@@ -495,6 +510,12 @@ export function writePreferenceToStorage(config, storage, key, value, options =
|
|
|
495
510
|
config.defaultPiModel = v;
|
|
496
511
|
break;
|
|
497
512
|
}
|
|
513
|
+
case "defaultGeminiModel": {
|
|
514
|
+
const v = typeof value === "string" ? value.trim() : "";
|
|
515
|
+
storage.setPreference(dbKey, v);
|
|
516
|
+
config.defaultGeminiModel = v;
|
|
517
|
+
break;
|
|
518
|
+
}
|
|
498
519
|
case "commitCli": {
|
|
499
520
|
if (value !== "claude" && value !== "codex" && value !== "opencode")
|
|
500
521
|
throw new Error(`无效 commit CLI: ${value}`);
|
|
@@ -791,6 +812,7 @@ function mergeWithDefaults(input) {
|
|
|
791
812
|
defaultGrokModel: typeof input.defaultGrokModel === "string" ? input.defaultGrokModel.trim() : defaults.defaultGrokModel,
|
|
792
813
|
defaultQoderModel: typeof input.defaultQoderModel === "string" ? input.defaultQoderModel.trim() : defaults.defaultQoderModel,
|
|
793
814
|
defaultPiModel: typeof input.defaultPiModel === "string" ? input.defaultPiModel.trim() : defaults.defaultPiModel,
|
|
815
|
+
defaultGeminiModel: typeof input.defaultGeminiModel === "string" ? input.defaultGeminiModel.trim() : defaults.defaultGeminiModel,
|
|
794
816
|
commitCli: input.commitCli === "codex" || input.commitCli === "opencode" ? input.commitCli : "claude",
|
|
795
817
|
commitModel: typeof input.commitModel === "string" ? input.commitModel.trim() : defaults.commitModel,
|
|
796
818
|
commitAiSource: input.commitAiSource === "api" ? "api" : "cli",
|
|
@@ -820,6 +842,7 @@ export function getProviderDefaultModels(config) {
|
|
|
820
842
|
grok: configuredModelId(config.defaultGrokModel),
|
|
821
843
|
qoder: configuredModelId(config.defaultQoderModel),
|
|
822
844
|
pi: configuredModelId(config.defaultPiModel),
|
|
845
|
+
gemini: configuredModelId(config.defaultGeminiModel),
|
|
823
846
|
};
|
|
824
847
|
}
|
|
825
848
|
export function getDefaultModelForProvider(config, provider) {
|
|
@@ -834,6 +857,8 @@ export function getDefaultModelForProvider(config, provider) {
|
|
|
834
857
|
return defaults.qoder;
|
|
835
858
|
if (provider === "pi")
|
|
836
859
|
return defaults.pi;
|
|
860
|
+
if (provider === "gemini")
|
|
861
|
+
return defaults.gemini;
|
|
837
862
|
return defaults.claude;
|
|
838
863
|
}
|
|
839
864
|
function normalizePresetCommand(command) {
|
|
@@ -3,14 +3,9 @@ import { GitStatusResult, PushResult, QuickCommitResult, SessionProvider, Sessio
|
|
|
3
3
|
export type QuickCommitErrorCode = "CWD_MISSING" | "NO_CWD" | "NOT_A_GIT_REPO" | "NO_COMMIT" | "NOTHING_TO_COMMIT" | "NOTHING_TO_PUSH" | "EMPTY_MESSAGE" | "EMPTY_TAG" | "EMPTY_AI_MESSAGE" | "AI_FALLBACK_FAILED" | "INVALID_AI_TAG" | "TAG_EXISTS" | "GIT_ADD_FAILED" | "GIT_DIFF_FAILED" | "GIT_COMMIT_FAILED" | "GIT_TAG_FAILED" | "CLAUDE_CLI_MISSING" | "CLAUDE_CLI_FAILED" | "CLAUDE_TIMEOUT";
|
|
4
4
|
/** Non-blocking status implementation used by HTTP routes. */
|
|
5
5
|
export declare function getGitStatusAsync(cwd: string): Promise<GitStatusResult>;
|
|
6
|
-
interface QuickCommitOptions extends CommitInputOptions {
|
|
6
|
+
interface QuickCommitOptions extends CommitInputOptions, QuickCommitAiOptions {
|
|
7
7
|
cwd: string;
|
|
8
8
|
language: string;
|
|
9
|
-
provider?: SessionProvider;
|
|
10
|
-
model?: string | null;
|
|
11
|
-
thinkingEffort?: SessionSnapshot["thinkingEffort"];
|
|
12
|
-
inheritEnv?: boolean;
|
|
13
|
-
systemAi?: import("./types.js").SystemAiConfig;
|
|
14
9
|
autoMessage: boolean;
|
|
15
10
|
customMessage?: string;
|
|
16
11
|
tag?: string;
|
|
@@ -29,6 +24,10 @@ export interface QuickCommitAiOptions {
|
|
|
29
24
|
thinkingEffort?: SessionSnapshot["thinkingEffort"];
|
|
30
25
|
inheritEnv?: boolean;
|
|
31
26
|
systemAi?: import("./types.js").SystemAiConfig;
|
|
27
|
+
/** 内置「系统运维」员工的角色设定:作为系统提示前缀注入。 */
|
|
28
|
+
opsPersona?: string;
|
|
29
|
+
/** CLI 降级链(按顺序);未设置时只用 provider/model 这一次调用。 */
|
|
30
|
+
cliCandidates?: import("./types.js").AiCliCandidate[];
|
|
32
31
|
}
|
|
33
32
|
export declare class QuickCommitError extends Error {
|
|
34
33
|
readonly code: QuickCommitErrorCode;
|
|
@@ -44,10 +43,12 @@ export interface CommitInputOptions {
|
|
|
44
43
|
iterationDigest?: string;
|
|
45
44
|
includeDiff?: boolean;
|
|
46
45
|
}
|
|
46
|
+
/** 角色设定走系统提示前缀;任务自己的输出格式与约束必须排在后面。 */
|
|
47
|
+
export declare function withOpsPersona(request: AiTextRequest, persona?: string): AiTextRequest;
|
|
47
48
|
/**
|
|
48
49
|
* Run a lightweight AI request through the selected Commit source. API mode
|
|
49
|
-
* exhausts its profile chain and then falls back once to the
|
|
50
|
-
* CLI. CLI mode
|
|
50
|
+
* exhausts its profile chain and then falls back once to the system employee's
|
|
51
|
+
* CLI chain. CLI mode walks that chain in order.
|
|
51
52
|
*/
|
|
52
53
|
export declare function callConfiguredAiText(request: AiTextRequest, cwd: string, language: string, opts: QuickCommitAiOptions): Promise<string>;
|
|
53
54
|
export interface GenerateCommitMessageResult {
|
package/dist/git-quick-commit.js
CHANGED
|
@@ -3,7 +3,7 @@ import { spawn } from "node:child_process";
|
|
|
3
3
|
import { ClaudeRunError, runClaudePrint } from "./claude-sdk-runner.js";
|
|
4
4
|
import { callSystemAiTextWithFallback } from "./system-ai.js";
|
|
5
5
|
import { buildChildEnv } from "./env-utils.js";
|
|
6
|
-
import { isSessionProvider } from "./session-provider.js";
|
|
6
|
+
import { isSessionProvider, providerCliInstalled } from "./session-provider.js";
|
|
7
7
|
import { runGitAsync as runGitAsyncBase, runGitRawAsync as runGitRawAsyncBase, getGitErrorMessage, } from "./git-utils.js";
|
|
8
8
|
import { composeSystemFallback, systemPromptFlag, thinkingEffortToClaudeCliEffort, thinkingEffortToCodexReasoningEffort, thinkingEffortToGrokEffort, thinkingEffortToOpenCodeVariant, thinkingEffortToQoderEffort, thinkingEffortToPiLevel, } from "./structured-provider-common.js";
|
|
9
9
|
/** provider 缺省值:无法识别时回落到 Claude。 */
|
|
@@ -422,6 +422,23 @@ function extractPiText(stdout) {
|
|
|
422
422
|
}
|
|
423
423
|
return text.trim();
|
|
424
424
|
}
|
|
425
|
+
/** Gemini `--output-format stream-json`:assistant message 是增量 chunk,拼接即可。 */
|
|
426
|
+
function extractGeminiText(stdout) {
|
|
427
|
+
let text = "";
|
|
428
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
429
|
+
const trimmed = line.trim();
|
|
430
|
+
if (!trimmed.startsWith("{"))
|
|
431
|
+
continue;
|
|
432
|
+
try {
|
|
433
|
+
const event = JSON.parse(trimmed);
|
|
434
|
+
if (event.type === "message" && event.role === "assistant" && typeof event.content === "string") {
|
|
435
|
+
text += event.content;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
catch { /* ignore non-protocol stdout */ }
|
|
439
|
+
}
|
|
440
|
+
return text.trim();
|
|
441
|
+
}
|
|
425
442
|
function buildPiTextArgs(request, opts, allowTools = false) {
|
|
426
443
|
const args = ["--mode", "json", "--print", "--no-session"];
|
|
427
444
|
if (!allowTools)
|
|
@@ -449,6 +466,23 @@ function buildGrokTextArgs(request, opts, allowTools = false) {
|
|
|
449
466
|
args.push("--always-approve");
|
|
450
467
|
return args;
|
|
451
468
|
}
|
|
469
|
+
/**
|
|
470
|
+
* Gemini CLI 没有 `--append-system-prompt`,规则走 `contentWithSystemPrompt` 并进内容;
|
|
471
|
+
* headless 下必须带 `--skip-trust`,否则未信任的工作目录会直接以 55 退出。
|
|
472
|
+
*
|
|
473
|
+
* `-p ""` 只把 CLI 钉在 headless 模式,真正的 prompt 走 stdin:commit diff 可能很大,
|
|
474
|
+
* 塞进 argv 会撞 ARG_MAX。Gemini 没有 Codex `--ephemeral` 那样的临时会话开关,
|
|
475
|
+
* 一次性调用会留在它自己的聊天记录里(Wand 不扫描 Gemini 历史,不会误当可恢复会话)。
|
|
476
|
+
*/
|
|
477
|
+
function buildGeminiTextArgs(opts, allowTools = false) {
|
|
478
|
+
const args = ["-p", "", "--output-format", "stream-json", "--skip-trust"];
|
|
479
|
+
const model = opts.model?.trim();
|
|
480
|
+
if (model && model !== "default")
|
|
481
|
+
args.push("--model", model);
|
|
482
|
+
if (allowTools)
|
|
483
|
+
args.push("--approval-mode", "yolo");
|
|
484
|
+
return args;
|
|
485
|
+
}
|
|
452
486
|
function buildQoderTextArgs(request, opts, allowTools = false) {
|
|
453
487
|
const args = ["-p", request.prompt, "--output-format", "stream-json", "--no-session-persistence"];
|
|
454
488
|
const model = opts.model?.trim();
|
|
@@ -595,6 +629,17 @@ async function callPiText(request, cwd, opts) {
|
|
|
595
629
|
throw new QuickCommitError("Pi 返回了空的 commit message。", "EMPTY_AI_MESSAGE");
|
|
596
630
|
return text;
|
|
597
631
|
}
|
|
632
|
+
async function callGeminiText(request, cwd, opts) {
|
|
633
|
+
const stdout = await runCliText("gemini", buildGeminiTextArgs(opts), contentWithSystemPrompt("gemini", request), {
|
|
634
|
+
cwd,
|
|
635
|
+
timeoutMs: CODEX_MESSAGE_TIMEOUT_MS,
|
|
636
|
+
inheritEnv: opts.inheritEnv,
|
|
637
|
+
});
|
|
638
|
+
const text = extractGeminiText(stdout);
|
|
639
|
+
if (!text)
|
|
640
|
+
throw new QuickCommitError("Gemini 返回了空的 commit message。", "EMPTY_AI_MESSAGE");
|
|
641
|
+
return text;
|
|
642
|
+
}
|
|
598
643
|
async function callCliAiText(request, cwd, language, opts) {
|
|
599
644
|
const provider = defaultProvider(opts.provider);
|
|
600
645
|
if (provider === "codex") {
|
|
@@ -608,6 +653,8 @@ async function callCliAiText(request, cwd, language, opts) {
|
|
|
608
653
|
return callQoderText(request, cwd, opts);
|
|
609
654
|
if (provider === "pi")
|
|
610
655
|
return callPiText(request, cwd, opts);
|
|
656
|
+
if (provider === "gemini")
|
|
657
|
+
return callGeminiText(request, cwd, opts);
|
|
611
658
|
return callClaudeText(request, cwd, language, opts);
|
|
612
659
|
}
|
|
613
660
|
async function callDirectApiText(request, systemAi) {
|
|
@@ -621,28 +668,89 @@ function aiFallbackFailed(primary, primaryError, fallbackError) {
|
|
|
621
668
|
const fallback = primary === "直连 API" ? "CLI" : "直连 API";
|
|
622
669
|
return new QuickCommitError(`${primary} 与 ${fallback} 均失败:${getGitErrorMessage(primaryError)};${getGitErrorMessage(fallbackError)}`, "AI_FALLBACK_FAILED");
|
|
623
670
|
}
|
|
671
|
+
/**
|
|
672
|
+
* 多候选降级时给整条链的时间上限。原生客户端的请求宽限约 180s,
|
|
673
|
+
* 候选再多也不能把整个请求拖死;单次仍受各自 CLI 超时保护。
|
|
674
|
+
*/
|
|
675
|
+
const CLI_CHAIN_BUDGET_MS = 150_000;
|
|
676
|
+
/** 角色设定走系统提示前缀;任务自己的输出格式与约束必须排在后面。 */
|
|
677
|
+
export function withOpsPersona(request, persona) {
|
|
678
|
+
const trimmed = (persona ?? "").trim();
|
|
679
|
+
if (!trimmed)
|
|
680
|
+
return request;
|
|
681
|
+
const task = (request.system ?? "").trim();
|
|
682
|
+
return { ...request, system: task ? `${trimmed}\n\n${task}` : trimmed };
|
|
683
|
+
}
|
|
684
|
+
/** 链上的每条候选变成一次普通的 CLI 文本调用参数。 */
|
|
685
|
+
function candidateOptions(base, candidate) {
|
|
686
|
+
return {
|
|
687
|
+
...base,
|
|
688
|
+
provider: candidate.provider,
|
|
689
|
+
model: candidate.model ?? null,
|
|
690
|
+
thinkingEffort: candidate.thinkingEffort ?? base.thinkingEffort ?? undefined,
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
/** 没有候选链时,把当前 provider/model 当作唯一的候选。 */
|
|
694
|
+
function singleCandidate(opts) {
|
|
695
|
+
return {
|
|
696
|
+
provider: defaultProvider(opts.provider),
|
|
697
|
+
model: opts.model ?? undefined,
|
|
698
|
+
thinkingEffort: opts.thinkingEffort ?? undefined,
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* 按顺序尝试候选:跳过没安装的 CLI,逐条收集错误,成功即返回。
|
|
703
|
+
* 只有一条候选时保持原行为(错误原样抛出,不摘要)。
|
|
704
|
+
*/
|
|
705
|
+
async function callCliCandidates(request, cwd, language, opts) {
|
|
706
|
+
const chain = opts.cliCandidates?.length ? opts.cliCandidates : [singleCandidate(opts)];
|
|
707
|
+
if (chain.length === 1) {
|
|
708
|
+
return callCliAiText(request, cwd, language, candidateOptions(opts, chain[0]));
|
|
709
|
+
}
|
|
710
|
+
const installed = chain.filter((candidate) => providerCliInstalled(candidate.provider));
|
|
711
|
+
const attempts = installed.length ? installed : chain;
|
|
712
|
+
const deadline = Date.now() + CLI_CHAIN_BUDGET_MS;
|
|
713
|
+
const errors = [];
|
|
714
|
+
for (const candidate of attempts) {
|
|
715
|
+
if (Date.now() > deadline) {
|
|
716
|
+
errors.push("已超过等待上限");
|
|
717
|
+
break;
|
|
718
|
+
}
|
|
719
|
+
try {
|
|
720
|
+
const text = await callCliAiText(request, cwd, language, candidateOptions(opts, candidate));
|
|
721
|
+
if (text.trim())
|
|
722
|
+
return text;
|
|
723
|
+
errors.push(`${candidate.provider}: 返回空结果`);
|
|
724
|
+
}
|
|
725
|
+
catch (error) {
|
|
726
|
+
errors.push(`${candidate.provider}: ${getGitErrorMessage(error)}`);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
throw new QuickCommitError(`所有 CLI 候选均失败:${errors.join(";")}`, "AI_FALLBACK_FAILED");
|
|
730
|
+
}
|
|
624
731
|
/**
|
|
625
732
|
* Run a lightweight AI request through the selected Commit source. API mode
|
|
626
|
-
* exhausts its profile chain and then falls back once to the
|
|
627
|
-
* CLI. CLI mode
|
|
733
|
+
* exhausts its profile chain and then falls back once to the system employee's
|
|
734
|
+
* CLI chain. CLI mode walks that chain in order.
|
|
628
735
|
*/
|
|
629
736
|
export async function callConfiguredAiText(request, cwd, language, opts) {
|
|
737
|
+
const effective = withOpsPersona(request, opts.opsPersona);
|
|
630
738
|
if (opts.systemAi?.enabled) {
|
|
631
739
|
try {
|
|
632
|
-
return await callDirectApiText(
|
|
740
|
+
return await callDirectApiText(effective, opts.systemAi);
|
|
633
741
|
}
|
|
634
742
|
catch (apiError) {
|
|
635
743
|
try {
|
|
636
744
|
// The user selected the direct API first. If it is unavailable or
|
|
637
|
-
// empty, retry this exact request through their selected CLI.
|
|
638
|
-
return await
|
|
745
|
+
// empty, retry this exact request through their selected CLI chain.
|
|
746
|
+
return await callCliCandidates(effective, cwd, language, opts);
|
|
639
747
|
}
|
|
640
748
|
catch (cliError) {
|
|
641
749
|
throw aiFallbackFailed("直连 API", apiError, cliError);
|
|
642
750
|
}
|
|
643
751
|
}
|
|
644
752
|
}
|
|
645
|
-
return
|
|
753
|
+
return callCliCandidates(effective, cwd, language, opts);
|
|
646
754
|
}
|
|
647
755
|
/** Read the unstaged + staged tree without touching the index. */
|
|
648
756
|
async function collectWorkingTreeDiff(cwd) {
|
|
@@ -1276,7 +1384,7 @@ async function getLatestTagAtHead(cwd) {
|
|
|
1276
1384
|
async function runQuickCommitFallbackCli(opts, priorError) {
|
|
1277
1385
|
await assertGitWorkTreeAsync(opts.cwd);
|
|
1278
1386
|
const beforeHead = await getHead(opts.cwd);
|
|
1279
|
-
const request = buildFallbackPrompt(opts, priorError);
|
|
1387
|
+
const request = withOpsPersona(buildFallbackPrompt(opts, priorError), opts.opsPersona);
|
|
1280
1388
|
const provider = defaultProvider(opts.provider);
|
|
1281
1389
|
if (provider === "codex") {
|
|
1282
1390
|
const args = ["exec", "--ephemeral", "--json", "--color", "never", "--skip-git-repo-check", "--dangerously-bypass-approvals-and-sandbox"];
|
|
@@ -1328,6 +1436,13 @@ async function runQuickCommitFallbackCli(opts, priorError) {
|
|
|
1328
1436
|
inheritEnv: opts.inheritEnv,
|
|
1329
1437
|
});
|
|
1330
1438
|
}
|
|
1439
|
+
else if (provider === "gemini") {
|
|
1440
|
+
await runCliText("gemini", buildGeminiTextArgs(opts, true), contentWithSystemPrompt("gemini", request), {
|
|
1441
|
+
cwd: opts.cwd,
|
|
1442
|
+
timeoutMs: QUICK_COMMIT_CLI_TIMEOUT_MS,
|
|
1443
|
+
inheritEnv: opts.inheritEnv,
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1331
1446
|
else {
|
|
1332
1447
|
const args = [
|
|
1333
1448
|
"-p",
|
package/dist/models.d.ts
CHANGED
|
@@ -49,6 +49,8 @@ export interface ProviderThinkingEfforts {
|
|
|
49
49
|
grok: ThinkingEffortLevel[];
|
|
50
50
|
qoder: ThinkingEffortLevel[];
|
|
51
51
|
pi: ThinkingEffortLevel[];
|
|
52
|
+
/** Gemini CLI 没有思考档位开关,只保留「不覆盖」一项,不假装能调。 */
|
|
53
|
+
gemini: ThinkingEffortLevel[];
|
|
52
54
|
}
|
|
53
55
|
export interface ModelCache {
|
|
54
56
|
models: ClaudeModelInfo[];
|
|
@@ -57,6 +59,7 @@ export interface ModelCache {
|
|
|
57
59
|
grokModels: ClaudeModelInfo[];
|
|
58
60
|
qoderModels: ClaudeModelInfo[];
|
|
59
61
|
piModels: ClaudeModelInfo[];
|
|
62
|
+
geminiModels: ClaudeModelInfo[];
|
|
60
63
|
thinkingEfforts: ProviderThinkingEfforts;
|
|
61
64
|
claudeVersion: string | null;
|
|
62
65
|
opencodeVersion: string | null;
|