@co0ontty/wand 4.27.0 → 4.27.1-beta.gafcd211
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 +6 -6
- package/dist/build-info.json +4 -4
- package/dist/config.d.ts +4 -3
- package/dist/config.js +20 -3
- package/dist/git-quick-commit.js +47 -1
- package/dist/models.d.ts +1 -0
- package/dist/models.js +10 -1
- package/dist/path-repair.js +1 -1
- package/dist/process-manager.js +14 -1
- package/dist/prompt-optimizer.d.ts +2 -2
- package/dist/prompt-optimizer.js +11 -32
- package/dist/provider-cli-updater.d.ts +1 -1
- package/dist/provider-cli-updater.js +8 -0
- package/dist/resume-policy.js +5 -3
- package/dist/server-session-routes.js +5 -3
- package/dist/server-settings-routes.js +5 -0
- package/dist/server-update-routes.js +1 -1
- package/dist/server.js +23 -5
- package/dist/session-ai-context.d.ts +3 -3
- package/dist/session-ai-context.js +14 -18
- package/dist/storage.js +5 -1
- package/dist/structured-pi-adapter.d.ts +11 -0
- package/dist/structured-pi-adapter.js +135 -0
- package/dist/structured-provider-common.d.ts +1 -0
- package/dist/structured-provider-common.js +16 -1
- package/dist/structured-session-manager.d.ts +2 -0
- package/dist/structured-session-manager.js +20 -6
- package/dist/system-ai.js +1 -1
- package/dist/types.d.ts +4 -2
- package/dist/web-ui/content/scripts.js +221 -59
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/embedded-assets.d.ts +1 -1
- 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 +9 -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/),
|
|
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/`; setup and supported baseline features are documented in [docs/browser-extension.md](docs/browser-extension.md).
|
|
17
17
|
|
|
@@ -56,7 +56,7 @@ wand config:password
|
|
|
56
56
|
#### Core
|
|
57
57
|
|
|
58
58
|
- **Dual view modes** — switch between raw terminal output and a structured conversation view for the same session
|
|
59
|
-
- **Multiple providers** — create PTY or structured sessions for Claude Code, Codex, OpenCode, Grok,
|
|
59
|
+
- **Multiple providers** — create PTY or structured sessions for Claude Code, Codex, OpenCode, Grok, Qoder CLI, and Pi
|
|
60
60
|
- **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
|
|
61
61
|
- **Permission control** — visual permission prompts with one-time approval, per-turn memory, and related policies
|
|
62
62
|
|
|
@@ -73,7 +73,7 @@ wand config:password
|
|
|
73
73
|
- **Native clients** — Android, iOS, and macOS clients with encrypted connection codes and update checks
|
|
74
74
|
- **HTTPS** — optional self-signed certificates for remote or mobile access
|
|
75
75
|
- **Update channels** — built-in stable/beta update checks and upgrade prompts
|
|
76
|
-
- **CLI updates** — check and quickly update Claude Code, Codex, and
|
|
76
|
+
- **CLI updates** — check and quickly update Claude Code, Codex, OpenCode, Qoder CLI, and Pi from server settings, with optional automatic updates
|
|
77
77
|
|
|
78
78
|
### Configuration
|
|
79
79
|
|
|
@@ -155,7 +155,7 @@ npm install -g opencode-ai@latest
|
|
|
155
155
|
|
|
156
156
|
### 概览
|
|
157
157
|
|
|
158
|
-
通过浏览器远程访问和管理本地 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/)
|
|
158
|
+
通过浏览器远程访问和管理本地 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/),提供终端和结构化对话双视图、会话持久化与恢复、权限管控、文件浏览和多平台客户端。
|
|
159
159
|
|
|
160
160
|
<p align="center">
|
|
161
161
|
<img src="docs/screenshots/clients-overview.png" width="100%" alt="Web 居中,iOS 和 Android 客户端在两侧的多端详情总览" />
|
|
@@ -205,7 +205,7 @@ npm install -g opencode-ai@latest
|
|
|
205
205
|
#### 核心
|
|
206
206
|
|
|
207
207
|
- **双视图模式** — 终端原始输出和结构化对话视图可随时切换,同一会话两种呈现
|
|
208
|
-
- **多 Provider 支持** — Claude Code、Codex、OpenCode、Grok 和
|
|
208
|
+
- **多 Provider 支持** — Claude Code、Codex、OpenCode、Grok、Qoder CLI 和 Pi 均可创建 PTY 或结构化会话
|
|
209
209
|
- **会话管理** — 创建、归档并携带原生上下文恢复 Claude、Codex、OpenCode、Grok、Qoder 会话;支持恢复 Claude/Codex 原生历史记录;会话列表显示摘要
|
|
210
210
|
- **权限控制** — 可视化权限提示,支持逐次确认、单次批准、本轮记忆等策略
|
|
211
211
|
|
|
@@ -222,7 +222,7 @@ npm install -g opencode-ai@latest
|
|
|
222
222
|
- **多平台客户端** — Android / iOS / macOS 原生客户端,支持加密连接码分发、自动更新检查
|
|
223
223
|
- **HTTPS** — 可选自签证书,适合远程或移动端访问
|
|
224
224
|
- **版本管理** — 内置更新检查与升级提示,支持 stable/beta 双通道
|
|
225
|
-
- **CLI 更新** — 在服务端设置中检查并快速更新 Claude Code、Codex、OpenCode,也可开启自动更新
|
|
225
|
+
- **CLI 更新** — 在服务端设置中检查并快速更新 Claude Code、Codex、OpenCode、Qoder CLI 和 Pi,也可开启自动更新
|
|
226
226
|
|
|
227
227
|
### 截图
|
|
228
228
|
|
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-
|
|
4
|
-
"version": "4.27.
|
|
5
|
-
"channel": "
|
|
2
|
+
"commit": "afcd2110a96cf8e1413df69d7ed1a14e2a76a6b3",
|
|
3
|
+
"builtAt": "2026-08-02T10:51:41.209Z",
|
|
4
|
+
"version": "4.27.1-beta.gafcd211",
|
|
5
|
+
"channel": "beta"
|
|
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", "defaultMode", "defaultCwd", "defaultModel", "defaultCodexModel", "defaultOpenCodeModel", "defaultGrokModel", "defaultQoderModel", "commitCli", "commitModel", "commitAiSource", "systemAi", "defaultThinkingEffort", "structuredRunner", "language", "cardDefaults", "inheritEnv"];
|
|
10
|
+
export declare const PREFERENCE_KEYS: readonly ["defaultProvider", "defaultSessionKind", "defaultMode", "defaultCwd", "defaultModel", "defaultCodexModel", "defaultOpenCodeModel", "defaultGrokModel", "defaultQoderModel", "defaultPiModel", "commitCli", "commitModel", "commitAiSource", "systemAi", "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
|
export declare const defaultConfig: () => WandConfig;
|
|
@@ -48,11 +48,12 @@ export declare function writePreferenceToStorage(config: WandConfig, storage: Wa
|
|
|
48
48
|
export declare function validateCommitAiConfig(_config: Pick<WandConfig, "commitAiSource" | "systemAi">): void;
|
|
49
49
|
export declare function normalizeCardDefaults(input: unknown): CardExpandDefaults;
|
|
50
50
|
export declare function isExecutionMode(value: unknown): value is ExecutionMode;
|
|
51
|
-
export declare function getProviderDefaultModels(config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel">): {
|
|
51
|
+
export declare function getProviderDefaultModels(config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultPiModel">): {
|
|
52
52
|
claude: string;
|
|
53
53
|
codex: string;
|
|
54
54
|
opencode: string;
|
|
55
55
|
grok: string;
|
|
56
56
|
qoder: string;
|
|
57
|
+
pi: string;
|
|
57
58
|
};
|
|
58
|
-
export declare function getDefaultModelForProvider(config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel">, provider: SessionProvider | undefined): string;
|
|
59
|
+
export declare function getDefaultModelForProvider(config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultGrokModel" | "defaultQoderModel" | "defaultPiModel">, provider: SessionProvider | undefined): string;
|
package/dist/config.js
CHANGED
|
@@ -31,6 +31,7 @@ export const PREFERENCE_KEYS = [
|
|
|
31
31
|
"defaultOpenCodeModel",
|
|
32
32
|
"defaultGrokModel",
|
|
33
33
|
"defaultQoderModel",
|
|
34
|
+
"defaultPiModel",
|
|
34
35
|
"commitCli",
|
|
35
36
|
"commitModel",
|
|
36
37
|
"commitAiSource",
|
|
@@ -75,6 +76,7 @@ export const defaultConfig = () => ({
|
|
|
75
76
|
defaultOpenCodeModel: "",
|
|
76
77
|
defaultGrokModel: "",
|
|
77
78
|
defaultQoderModel: "",
|
|
79
|
+
defaultPiModel: "",
|
|
78
80
|
commitCli: "claude",
|
|
79
81
|
commitModel: "",
|
|
80
82
|
commitAiSource: "cli",
|
|
@@ -285,7 +287,7 @@ export function applyStoragePreferences(config, storage) {
|
|
|
285
287
|
const defaults = defaultConfig();
|
|
286
288
|
if (storage.hasPreference(preferenceStorageKey("defaultProvider"))) {
|
|
287
289
|
const v = storage.getPreference(preferenceStorageKey("defaultProvider"), defaults.defaultProvider ?? "claude");
|
|
288
|
-
if (v === "claude" || v === "codex" || v === "opencode" || v === "grok" || v === "qoder")
|
|
290
|
+
if (v === "claude" || v === "codex" || v === "opencode" || v === "grok" || v === "qoder" || v === "pi")
|
|
289
291
|
config.defaultProvider = v;
|
|
290
292
|
}
|
|
291
293
|
if (storage.hasPreference(preferenceStorageKey("defaultSessionKind"))) {
|
|
@@ -328,6 +330,11 @@ export function applyStoragePreferences(config, storage) {
|
|
|
328
330
|
if (typeof v === "string")
|
|
329
331
|
config.defaultQoderModel = v.trim();
|
|
330
332
|
}
|
|
333
|
+
if (storage.hasPreference(preferenceStorageKey("defaultPiModel"))) {
|
|
334
|
+
const v = storage.getPreference(preferenceStorageKey("defaultPiModel"), defaults.defaultPiModel ?? "");
|
|
335
|
+
if (typeof v === "string")
|
|
336
|
+
config.defaultPiModel = v.trim();
|
|
337
|
+
}
|
|
331
338
|
if (storage.hasPreference(preferenceStorageKey("commitCli"))) {
|
|
332
339
|
const v = storage.getPreference(preferenceStorageKey("commitCli"), defaults.commitCli ?? "claude");
|
|
333
340
|
if (v === "claude" || v === "codex" || v === "opencode")
|
|
@@ -378,7 +385,7 @@ export function writePreferenceToStorage(config, storage, key, value, options =
|
|
|
378
385
|
const dbKey = preferenceStorageKey(key);
|
|
379
386
|
switch (key) {
|
|
380
387
|
case "defaultProvider": {
|
|
381
|
-
if (value !== "claude" && value !== "codex" && value !== "opencode" && value !== "grok" && value !== "qoder")
|
|
388
|
+
if (value !== "claude" && value !== "codex" && value !== "opencode" && value !== "grok" && value !== "qoder" && value !== "pi")
|
|
382
389
|
throw new Error(`无效 Provider: ${value}`);
|
|
383
390
|
storage.setPreference(dbKey, value);
|
|
384
391
|
config.defaultProvider = value;
|
|
@@ -434,6 +441,12 @@ export function writePreferenceToStorage(config, storage, key, value, options =
|
|
|
434
441
|
config.defaultQoderModel = v;
|
|
435
442
|
break;
|
|
436
443
|
}
|
|
444
|
+
case "defaultPiModel": {
|
|
445
|
+
const v = typeof value === "string" ? value.trim() : "";
|
|
446
|
+
storage.setPreference(dbKey, v);
|
|
447
|
+
config.defaultPiModel = v;
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
437
450
|
case "commitCli": {
|
|
438
451
|
if (value !== "claude" && value !== "codex" && value !== "opencode")
|
|
439
452
|
throw new Error(`无效 commit CLI: ${value}`);
|
|
@@ -652,13 +665,14 @@ function mergeWithDefaults(input) {
|
|
|
652
665
|
android: normalizeAndroidApkConfig(input.android) ?? defaults.android,
|
|
653
666
|
macos: normalizeMacosDmgConfig(input.macos) ?? defaults.macos,
|
|
654
667
|
cardDefaults: normalizeCardDefaults(input.cardDefaults),
|
|
655
|
-
defaultProvider: input.defaultProvider === "codex" || input.defaultProvider === "opencode" || input.defaultProvider === "grok" || input.defaultProvider === "qoder" ? input.defaultProvider : "claude",
|
|
668
|
+
defaultProvider: input.defaultProvider === "codex" || input.defaultProvider === "opencode" || input.defaultProvider === "grok" || input.defaultProvider === "qoder" || input.defaultProvider === "pi" ? input.defaultProvider : "claude",
|
|
656
669
|
defaultSessionKind: input.defaultSessionKind === "pty" ? "pty" : "structured",
|
|
657
670
|
defaultModel: typeof input.defaultModel === "string" ? input.defaultModel.trim() : defaults.defaultModel,
|
|
658
671
|
defaultCodexModel: typeof input.defaultCodexModel === "string" ? input.defaultCodexModel.trim() : defaults.defaultCodexModel,
|
|
659
672
|
defaultOpenCodeModel: typeof input.defaultOpenCodeModel === "string" ? input.defaultOpenCodeModel.trim() : defaults.defaultOpenCodeModel,
|
|
660
673
|
defaultGrokModel: typeof input.defaultGrokModel === "string" ? input.defaultGrokModel.trim() : defaults.defaultGrokModel,
|
|
661
674
|
defaultQoderModel: typeof input.defaultQoderModel === "string" ? input.defaultQoderModel.trim() : defaults.defaultQoderModel,
|
|
675
|
+
defaultPiModel: typeof input.defaultPiModel === "string" ? input.defaultPiModel.trim() : defaults.defaultPiModel,
|
|
662
676
|
commitCli: input.commitCli === "codex" || input.commitCli === "opencode" ? input.commitCli : "claude",
|
|
663
677
|
commitModel: typeof input.commitModel === "string" ? input.commitModel.trim() : defaults.commitModel,
|
|
664
678
|
commitAiSource: input.commitAiSource === "api" ? "api" : "cli",
|
|
@@ -677,6 +691,7 @@ export function getProviderDefaultModels(config) {
|
|
|
677
691
|
opencode: (config.defaultOpenCodeModel ?? "").trim(),
|
|
678
692
|
grok: (config.defaultGrokModel ?? "").trim(),
|
|
679
693
|
qoder: (config.defaultQoderModel ?? "").trim(),
|
|
694
|
+
pi: (config.defaultPiModel ?? "").trim(),
|
|
680
695
|
};
|
|
681
696
|
}
|
|
682
697
|
export function getDefaultModelForProvider(config, provider) {
|
|
@@ -689,6 +704,8 @@ export function getDefaultModelForProvider(config, provider) {
|
|
|
689
704
|
return defaults.grok;
|
|
690
705
|
if (provider === "qoder")
|
|
691
706
|
return defaults.qoder;
|
|
707
|
+
if (provider === "pi")
|
|
708
|
+
return defaults.pi;
|
|
692
709
|
return defaults.claude;
|
|
693
710
|
}
|
|
694
711
|
function normalizePresetCommand(command) {
|
package/dist/git-quick-commit.js
CHANGED
|
@@ -4,7 +4,7 @@ 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
6
|
import { runGitAsync as runGitAsyncBase, runGitRawAsync as runGitRawAsyncBase, getGitErrorMessage, } from "./git-utils.js";
|
|
7
|
-
import { thinkingEffortToClaudeCliEffort, thinkingEffortToCodexReasoningEffort, thinkingEffortToGrokEffort, thinkingEffortToOpenCodeVariant, thinkingEffortToQoderEffort, } from "./structured-provider-common.js";
|
|
7
|
+
import { thinkingEffortToClaudeCliEffort, thinkingEffortToCodexReasoningEffort, thinkingEffortToGrokEffort, thinkingEffortToOpenCodeVariant, thinkingEffortToQoderEffort, thinkingEffortToPiLevel, } from "./structured-provider-common.js";
|
|
8
8
|
const GIT_TIMEOUT_MS = 1500;
|
|
9
9
|
const GIT_PUSH_TIMEOUT_MS = 30_000;
|
|
10
10
|
const MAX_FILE_ENTRIES = 200;
|
|
@@ -306,6 +306,7 @@ function normalizeProvider(provider) {
|
|
|
306
306
|
|| provider === "opencode"
|
|
307
307
|
|| provider === "grok"
|
|
308
308
|
|| provider === "qoder"
|
|
309
|
+
|| provider === "pi"
|
|
309
310
|
? provider
|
|
310
311
|
: "claude";
|
|
311
312
|
}
|
|
@@ -406,6 +407,35 @@ function extractQoderText(stdout) {
|
|
|
406
407
|
}
|
|
407
408
|
return (resultText || assistantTexts.join("\n")).trim();
|
|
408
409
|
}
|
|
410
|
+
function extractPiText(stdout) {
|
|
411
|
+
let text = "";
|
|
412
|
+
for (const line of stdout.split(/\r?\n/)) {
|
|
413
|
+
try {
|
|
414
|
+
const event = JSON.parse(line);
|
|
415
|
+
if (event.type !== "message_end" || event.message?.role !== "assistant" || !Array.isArray(event.message.content))
|
|
416
|
+
continue;
|
|
417
|
+
text = event.message.content
|
|
418
|
+
.filter((block) => !!block && typeof block === "object" && block.type === "text" && typeof block.text === "string")
|
|
419
|
+
.map((block) => block.text)
|
|
420
|
+
.join("\n");
|
|
421
|
+
}
|
|
422
|
+
catch { /* ignore non-protocol stdout */ }
|
|
423
|
+
}
|
|
424
|
+
return text.trim();
|
|
425
|
+
}
|
|
426
|
+
function buildPiTextArgs(prompt, opts, allowTools = false) {
|
|
427
|
+
const args = ["--mode", "json", "--print", "--no-session"];
|
|
428
|
+
if (!allowTools)
|
|
429
|
+
args.push("--no-tools");
|
|
430
|
+
const model = opts.model?.trim();
|
|
431
|
+
if (model && model !== "default")
|
|
432
|
+
args.push("--model", model);
|
|
433
|
+
const level = thinkingEffortToPiLevel(opts.thinkingEffort ?? "off");
|
|
434
|
+
if (level)
|
|
435
|
+
args.push("--thinking", level);
|
|
436
|
+
args.push(prompt);
|
|
437
|
+
return args;
|
|
438
|
+
}
|
|
409
439
|
function buildGrokTextArgs(prompt, opts, allowTools = false) {
|
|
410
440
|
const args = ["--no-auto-update", "-p", prompt, "--output-format", "streaming-json"];
|
|
411
441
|
const model = opts.model?.trim();
|
|
@@ -532,6 +562,13 @@ async function callQoderText(prompt, cwd, opts) {
|
|
|
532
562
|
throw new QuickCommitError("Qoder 返回了空的 commit message。", "EMPTY_AI_MESSAGE");
|
|
533
563
|
return text;
|
|
534
564
|
}
|
|
565
|
+
async function callPiText(prompt, cwd, opts) {
|
|
566
|
+
const stdout = await runCliText("pi", buildPiTextArgs(prompt, opts), "", { cwd, timeoutMs: CODEX_MESSAGE_TIMEOUT_MS, inheritEnv: opts.inheritEnv });
|
|
567
|
+
const text = extractPiText(stdout);
|
|
568
|
+
if (!text)
|
|
569
|
+
throw new QuickCommitError("Pi 返回了空的 commit message。", "EMPTY_AI_MESSAGE");
|
|
570
|
+
return text;
|
|
571
|
+
}
|
|
535
572
|
async function callCliAiText(prompt, cwd, language, opts) {
|
|
536
573
|
const provider = normalizeProvider(opts.provider);
|
|
537
574
|
if (provider === "codex") {
|
|
@@ -543,6 +580,8 @@ async function callCliAiText(prompt, cwd, language, opts) {
|
|
|
543
580
|
return callGrokText(prompt, cwd, opts);
|
|
544
581
|
if (provider === "qoder")
|
|
545
582
|
return callQoderText(prompt, cwd, opts);
|
|
583
|
+
if (provider === "pi")
|
|
584
|
+
return callPiText(prompt, cwd, opts);
|
|
546
585
|
return callClaudeText(prompt, cwd, language, opts);
|
|
547
586
|
}
|
|
548
587
|
async function callDirectApiText(prompt, systemAi) {
|
|
@@ -1196,6 +1235,13 @@ async function runQuickCommitFallbackCli(opts, priorError) {
|
|
|
1196
1235
|
inheritEnv: opts.inheritEnv,
|
|
1197
1236
|
});
|
|
1198
1237
|
}
|
|
1238
|
+
else if (provider === "pi") {
|
|
1239
|
+
await runCliText("pi", buildPiTextArgs(prompt, opts, true), "", {
|
|
1240
|
+
cwd: opts.cwd,
|
|
1241
|
+
timeoutMs: QUICK_COMMIT_CLI_TIMEOUT_MS,
|
|
1242
|
+
inheritEnv: opts.inheritEnv,
|
|
1243
|
+
});
|
|
1244
|
+
}
|
|
1199
1245
|
else {
|
|
1200
1246
|
const args = [
|
|
1201
1247
|
"-p",
|
package/dist/models.d.ts
CHANGED
package/dist/models.js
CHANGED
|
@@ -53,6 +53,9 @@ const QODER_FALLBACK_MODELS = [
|
|
|
53
53
|
{ id: "performance", label: "Performance" },
|
|
54
54
|
{ id: "ultimate", label: "Ultimate" },
|
|
55
55
|
];
|
|
56
|
+
const PI_FALLBACK_MODELS = [
|
|
57
|
+
{ id: "default", label: "跟随 Pi 默认", alias: true },
|
|
58
|
+
];
|
|
56
59
|
function cloneModels(models) {
|
|
57
60
|
return models.map((model) => ({
|
|
58
61
|
...model,
|
|
@@ -68,6 +71,7 @@ function cloneCache(cache) {
|
|
|
68
71
|
opencodeModels: cloneModels(cache.opencodeModels),
|
|
69
72
|
grokModels: cloneModels(cache.grokModels),
|
|
70
73
|
qoderModels: cloneModels(cache.qoderModels),
|
|
74
|
+
piModels: cloneModels(cache.piModels),
|
|
71
75
|
claudeVersion: cache.claudeVersion,
|
|
72
76
|
opencodeVersion: cache.opencodeVersion,
|
|
73
77
|
refreshedAt: cache.refreshedAt,
|
|
@@ -235,6 +239,7 @@ function createInitialCache(options) {
|
|
|
235
239
|
opencodeModels: cloneModels(OPENCODE_FALLBACK_MODELS),
|
|
236
240
|
grokModels: cloneModels(GROK_FALLBACK_MODELS),
|
|
237
241
|
qoderModels: cloneModels(QODER_FALLBACK_MODELS),
|
|
242
|
+
piModels: cloneModels(PI_FALLBACK_MODELS),
|
|
238
243
|
claudeVersion: null,
|
|
239
244
|
opencodeVersion: null,
|
|
240
245
|
refreshedAt: now.toISOString(),
|
|
@@ -523,6 +528,7 @@ function catalogRevision(cache) {
|
|
|
523
528
|
opencodeModels: cache.opencodeModels,
|
|
524
529
|
grokModels: cache.grokModels,
|
|
525
530
|
qoderModels: cache.qoderModels,
|
|
531
|
+
piModels: cache.piModels,
|
|
526
532
|
claudeVersion: cache.claudeVersion,
|
|
527
533
|
opencodeVersion: cache.opencodeVersion,
|
|
528
534
|
});
|
|
@@ -603,8 +609,9 @@ function parsePersistedModelCatalog(value) {
|
|
|
603
609
|
const opencodeModels = parsePersistedModelList(catalog.opencodeModels);
|
|
604
610
|
const grokModels = parsePersistedModelList(catalog.grokModels);
|
|
605
611
|
const qoderModels = parsePersistedModelList(catalog.qoderModels);
|
|
612
|
+
const piModels = parsePersistedModelList(catalog.piModels);
|
|
606
613
|
const refreshedAt = safePersistedString(catalog.refreshedAt, 64);
|
|
607
|
-
if (!models || !codexModels || !opencodeModels || !grokModels || !qoderModels
|
|
614
|
+
if (!models || !codexModels || !opencodeModels || !grokModels || !qoderModels || !piModels
|
|
608
615
|
|| !refreshedAt || Number.isNaN(Date.parse(refreshedAt))) {
|
|
609
616
|
return null;
|
|
610
617
|
}
|
|
@@ -619,6 +626,7 @@ function parsePersistedModelCatalog(value) {
|
|
|
619
626
|
opencodeModels,
|
|
620
627
|
grokModels,
|
|
621
628
|
qoderModels,
|
|
629
|
+
piModels,
|
|
622
630
|
claudeVersion,
|
|
623
631
|
opencodeVersion,
|
|
624
632
|
refreshedAt,
|
|
@@ -696,6 +704,7 @@ async function discoverModelCache(options, previous) {
|
|
|
696
704
|
opencodeModels: opencodeProbe.models.ok ? opencodeProbe.models.value : cloneModels(previous.opencodeModels),
|
|
697
705
|
grokModels: grokProbe.ok ? grokProbe.value : cloneModels(previous.grokModels),
|
|
698
706
|
qoderModels: qoderProbe.ok ? qoderProbe.value : cloneModels(previous.qoderModels),
|
|
707
|
+
piModels: cloneModels(previous.piModels),
|
|
699
708
|
claudeVersion,
|
|
700
709
|
opencodeVersion: opencodeProbe.version.ok ? opencodeProbe.version.value : previous.opencodeVersion,
|
|
701
710
|
refreshedAt: now.toISOString(),
|
package/dist/path-repair.js
CHANGED
|
@@ -6,7 +6,7 @@ import path from "node:path";
|
|
|
6
6
|
import process from "node:process";
|
|
7
7
|
import { spawn, spawnSync } from "node:child_process";
|
|
8
8
|
/** 关键的 CLI 工具,会被诊断输出。 */
|
|
9
|
-
const PROBE_COMMANDS = ["claude", "codex", "opencode", "grok", "qodercli"];
|
|
9
|
+
const PROBE_COMMANDS = ["claude", "codex", "opencode", "grok", "qodercli", "pi"];
|
|
10
10
|
const DEEP_PROBE_TIMEOUT_MS = 4000;
|
|
11
11
|
/**
|
|
12
12
|
* 构造候选 bin 目录列表(按优先级,前面的更可信)。
|
package/dist/process-manager.js
CHANGED
|
@@ -15,7 +15,7 @@ import { ensureNodePtyHelperExecutable } from "./ensure-node-pty-helper.js";
|
|
|
15
15
|
import { buildLanguageDirective, buildManagedAutonomyDirective } from "./language-prompt.js";
|
|
16
16
|
import { prepareSessionWorktree } from "./git-worktree.js";
|
|
17
17
|
import { getProviderCommandSessionId, getProviderResumeCommandSessionId } from "./resume-policy.js";
|
|
18
|
-
import { normalizeThinkingEffort, thinkingEffortToClaudeCliEffort, thinkingEffortToClaudeSlashEffort, thinkingEffortToCodexReasoningEffort, thinkingEffortToOpenCodeVariant } from "./structured-provider-common.js";
|
|
18
|
+
import { normalizeThinkingEffort, thinkingEffortToClaudeCliEffort, thinkingEffortToClaudeSlashEffort, thinkingEffortToCodexReasoningEffort, thinkingEffortToOpenCodeVariant, thinkingEffortToPiLevel } from "./structured-provider-common.js";
|
|
19
19
|
import { SessionTopicCoordinator } from "./session-topic.js";
|
|
20
20
|
import { getErrorMessage } from "./error-utils.js";
|
|
21
21
|
import { resolveSystemAiContext } from "./session-ai-context.js";
|
|
@@ -28,6 +28,8 @@ function resolveProviderFromCommand(command) {
|
|
|
28
28
|
return "opencode";
|
|
29
29
|
if (/^grok\b/.test(command.trim()))
|
|
30
30
|
return "grok";
|
|
31
|
+
if (/^pi\b/.test(command.trim()))
|
|
32
|
+
return "pi";
|
|
31
33
|
return /^qodercli\b/.test(command.trim()) ? "qoder" : "claude";
|
|
32
34
|
}
|
|
33
35
|
/**
|
|
@@ -2210,6 +2212,17 @@ export class ProcessManager extends EventEmitter {
|
|
|
2210
2212
|
}
|
|
2211
2213
|
return result;
|
|
2212
2214
|
}
|
|
2215
|
+
if (provider === "pi") {
|
|
2216
|
+
let result = command;
|
|
2217
|
+
const trimmedModel = model?.trim();
|
|
2218
|
+
if (trimmedModel && trimmedModel !== "default" && !/--model(?:\s|=)/.test(result)) {
|
|
2219
|
+
result += ` --model '${trimmedModel.replace(/'/g, "'\\''")}'`;
|
|
2220
|
+
}
|
|
2221
|
+
const level = thinkingEffortToPiLevel(thinkingEffort ?? null);
|
|
2222
|
+
if (level && !/--thinking(?:\s|=)/.test(result))
|
|
2223
|
+
result += ` --thinking '${level}'`;
|
|
2224
|
+
return result;
|
|
2225
|
+
}
|
|
2213
2226
|
const isClaudeCmd = /^(?:claude|npx\s+claude|[^\s]+\/claude)(?:\s|$)/.test(command);
|
|
2214
2227
|
if (!isClaudeCmd)
|
|
2215
2228
|
return command;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type QuickCommitAiOptions } from "./git-quick-commit.js";
|
|
2
2
|
export declare class PromptOptimizeError extends Error {
|
|
3
3
|
readonly code: string;
|
|
4
4
|
constructor(message: string, code: string);
|
|
5
5
|
}
|
|
6
|
-
export declare function optimizePrompt(rawText: string, language: string, cwd?: string,
|
|
6
|
+
export declare function optimizePrompt(rawText: string, language: string, cwd?: string, ai?: QuickCommitAiOptions): Promise<string>;
|
package/dist/prompt-optimizer.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { callSystemAiTextWithFallback } from "./system-ai.js";
|
|
3
|
-
const CLAUDE_TIMEOUT_MS = 60_000;
|
|
1
|
+
import { callConfiguredAiText } from "./git-quick-commit.js";
|
|
4
2
|
const MAX_INPUT_LENGTH = 8000;
|
|
5
3
|
export class PromptOptimizeError extends Error {
|
|
6
4
|
code;
|
|
@@ -10,24 +8,6 @@ export class PromptOptimizeError extends Error {
|
|
|
10
8
|
this.name = "PromptOptimizeError";
|
|
11
9
|
}
|
|
12
10
|
}
|
|
13
|
-
async function callClaudeText(prompt, cwd, language) {
|
|
14
|
-
try {
|
|
15
|
-
return await runClaudePrint(prompt, { cwd, timeoutMs: CLAUDE_TIMEOUT_MS, language });
|
|
16
|
-
}
|
|
17
|
-
catch (error) {
|
|
18
|
-
if (error instanceof ClaudeRunError) {
|
|
19
|
-
// 翻译成 prompt-optimizer 自己的话术 + 错误码(与原文案保持一致)。
|
|
20
|
-
if (error.code === "CLAUDE_TIMEOUT") {
|
|
21
|
-
throw new PromptOptimizeError("Claude 优化超时,请稍后重试。", "CLAUDE_TIMEOUT");
|
|
22
|
-
}
|
|
23
|
-
if (error.code === "CLAUDE_EMPTY_RESULT") {
|
|
24
|
-
throw new PromptOptimizeError("Claude 返回了空结果。", "EMPTY_RESULT");
|
|
25
|
-
}
|
|
26
|
-
throw new PromptOptimizeError(error.message, error.code);
|
|
27
|
-
}
|
|
28
|
-
throw error;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
11
|
function buildOptimizePrompt(userInput, language) {
|
|
32
12
|
const lang = (language || "").trim() || "中文";
|
|
33
13
|
return [
|
|
@@ -42,7 +22,7 @@ function buildOptimizePrompt(userInput, language) {
|
|
|
42
22
|
userInput,
|
|
43
23
|
].join("\n");
|
|
44
24
|
}
|
|
45
|
-
export async function optimizePrompt(rawText, language, cwd,
|
|
25
|
+
export async function optimizePrompt(rawText, language, cwd, ai = {}) {
|
|
46
26
|
const text = (rawText || "").trim();
|
|
47
27
|
if (!text) {
|
|
48
28
|
throw new PromptOptimizeError("请先输入要优化的内容。", "EMPTY_INPUT");
|
|
@@ -52,16 +32,15 @@ export async function optimizePrompt(rawText, language, cwd, systemAi) {
|
|
|
52
32
|
}
|
|
53
33
|
const prompt = buildOptimizePrompt(text, language);
|
|
54
34
|
let raw;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
raw = await callSystemAiTextWithFallback(prompt, systemAi);
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
raw = await callClaudeText(prompt, cwd, language);
|
|
61
|
-
}
|
|
35
|
+
try {
|
|
36
|
+
raw = await callConfiguredAiText(prompt, cwd ?? process.cwd(), language, ai);
|
|
62
37
|
}
|
|
63
|
-
|
|
64
|
-
|
|
38
|
+
catch (error) {
|
|
39
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
40
|
+
const code = error && typeof error === "object" && "code" in error && typeof error.code === "string"
|
|
41
|
+
? error.code
|
|
42
|
+
: "AI_OPTIMIZE_FAILED";
|
|
43
|
+
throw new PromptOptimizeError(message, code);
|
|
65
44
|
}
|
|
66
45
|
const cleaned = raw
|
|
67
46
|
.replace(/^```[a-zA-Z]*\n?/, "")
|
|
@@ -69,7 +48,7 @@ export async function optimizePrompt(rawText, language, cwd, systemAi) {
|
|
|
69
48
|
.replace(/^["'`]+|["'`]+$/g, "")
|
|
70
49
|
.trim();
|
|
71
50
|
if (!cleaned) {
|
|
72
|
-
throw new PromptOptimizeError("
|
|
51
|
+
throw new PromptOptimizeError("AI 返回了空结果。", "EMPTY_RESULT");
|
|
73
52
|
}
|
|
74
53
|
return cleaned;
|
|
75
54
|
}
|
|
@@ -44,6 +44,14 @@ const PROVIDER_CLI_SPECS = [
|
|
|
44
44
|
versionArgs: ["--version"],
|
|
45
45
|
updateArgs: ["update"],
|
|
46
46
|
},
|
|
47
|
+
{
|
|
48
|
+
id: "pi",
|
|
49
|
+
label: "Pi CLI",
|
|
50
|
+
command: "pi",
|
|
51
|
+
npmPackage: "@mariozechner/pi-coding-agent",
|
|
52
|
+
versionArgs: ["--version"],
|
|
53
|
+
updateArgs: ["update", "self"],
|
|
54
|
+
},
|
|
47
55
|
];
|
|
48
56
|
function childEnv(options) {
|
|
49
57
|
return options.env ?? buildChildEnv(options.inheritEnv !== false);
|
package/dist/resume-policy.js
CHANGED
|
@@ -18,7 +18,7 @@ function resumeArgumentPattern(provider) {
|
|
|
18
18
|
if (provider === "codex") {
|
|
19
19
|
return new RegExp(`(?:^|\\s)resume\\s+${SAFE_PROVIDER_SESSION_ID_SOURCE}(?=\\s|$)`, "i");
|
|
20
20
|
}
|
|
21
|
-
if (provider === "opencode") {
|
|
21
|
+
if (provider === "opencode" || provider === "pi") {
|
|
22
22
|
return new RegExp(`(?:^|\\s)(?:--session|-s)\\s+${SAFE_PROVIDER_SESSION_ID_SOURCE}(?=\\s|$)`, "i");
|
|
23
23
|
}
|
|
24
24
|
return new RegExp(`(?:^|\\s)(?:--resume|-r)\\s+${SAFE_PROVIDER_SESSION_ID_SOURCE}(?=\\s|$)`, "i");
|
|
@@ -34,13 +34,13 @@ export function getProviderCommandSessionId(provider, command) {
|
|
|
34
34
|
const resumed = getProviderResumeCommandSessionId(provider, command);
|
|
35
35
|
if (resumed)
|
|
36
36
|
return resumed;
|
|
37
|
-
if (provider === "codex" || provider === "opencode")
|
|
37
|
+
if (provider === "codex" || provider === "opencode" || provider === "pi")
|
|
38
38
|
return null;
|
|
39
39
|
return assignedSessionIdPattern().exec(command)?.[1] ?? null;
|
|
40
40
|
}
|
|
41
41
|
function stripProviderResumeArgument(provider, command) {
|
|
42
42
|
const withoutResume = command.replace(resumeArgumentPattern(provider), " ");
|
|
43
|
-
const withoutAssignedId = provider === "codex" || provider === "opencode"
|
|
43
|
+
const withoutAssignedId = provider === "codex" || provider === "opencode" || provider === "pi"
|
|
44
44
|
? withoutResume
|
|
45
45
|
: withoutResume.replace(assignedSessionIdPattern(), " ");
|
|
46
46
|
return withoutAssignedId.replace(/\s+/g, " ").trim();
|
|
@@ -55,5 +55,7 @@ export function buildProviderResumeCommand(provider, command, providerSessionId)
|
|
|
55
55
|
return `${base} resume ${providerSessionId}`;
|
|
56
56
|
if (provider === "opencode")
|
|
57
57
|
return `${base} --session ${providerSessionId}`;
|
|
58
|
+
if (provider === "pi")
|
|
59
|
+
return `${base} --session ${providerSessionId}`;
|
|
58
60
|
return `${base} --resume ${providerSessionId}`;
|
|
59
61
|
}
|
|
@@ -319,6 +319,8 @@ function resolvePtyResumeProvider(snapshot) {
|
|
|
319
319
|
return "grok";
|
|
320
320
|
if (/^qodercli\b/.test(command))
|
|
321
321
|
return "qoder";
|
|
322
|
+
if (/^pi\b/.test(command))
|
|
323
|
+
return "pi";
|
|
322
324
|
return "claude";
|
|
323
325
|
}
|
|
324
326
|
function isPtyProviderCommand(provider, command) {
|
|
@@ -375,7 +377,7 @@ function canAutoResumePtyForInput(snapshot, input) {
|
|
|
375
377
|
return Boolean(snapshot
|
|
376
378
|
&& (snapshot.sessionKind ?? "pty") === "pty"
|
|
377
379
|
&& snapshot.status !== "running"
|
|
378
|
-
&& (snapshot.provider === "claude" || snapshot.provider === "codex" || snapshot.provider === "opencode" || snapshot.provider === "grok" || snapshot.provider === "qoder"
|
|
380
|
+
&& (snapshot.provider === "claude" || snapshot.provider === "codex" || snapshot.provider === "opencode" || snapshot.provider === "grok" || snapshot.provider === "qoder" || snapshot.provider === "pi"
|
|
379
381
|
|| /^(?:claude|codex|opencode|grok|qodercli)\b/.test(snapshot.command.trim()))
|
|
380
382
|
&& snapshot.claudeSessionId
|
|
381
383
|
&& input);
|
|
@@ -418,11 +420,11 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
418
420
|
app.post("/api/structured-sessions", asyncRoute(async (req, res) => {
|
|
419
421
|
const body = req.body;
|
|
420
422
|
try {
|
|
421
|
-
if (body.provider && body.provider !== "claude" && body.provider !== "codex" && body.provider !== "opencode" && body.provider !== "grok" && body.provider !== "qoder") {
|
|
423
|
+
if (body.provider && body.provider !== "claude" && body.provider !== "codex" && body.provider !== "opencode" && body.provider !== "grok" && body.provider !== "qoder" && body.provider !== "pi") {
|
|
422
424
|
res.status(400).json({ error: "结构化会话当前仅支持 Claude、Codex、OpenCode、Grok 或 Qoder provider。" });
|
|
423
425
|
return;
|
|
424
426
|
}
|
|
425
|
-
const provider = body.provider === "codex" || body.provider === "opencode" || body.provider === "grok" || body.provider === "qoder" ? body.provider : "claude";
|
|
427
|
+
const provider = body.provider === "codex" || body.provider === "opencode" || body.provider === "grok" || body.provider === "qoder" || body.provider === "pi" ? body.provider : "claude";
|
|
426
428
|
const rawModel = typeof body.model === "string" ? body.model.trim() : "";
|
|
427
429
|
const origin = parseSessionCreationOrigin(body);
|
|
428
430
|
const snapshot = structured.createSession({
|
|
@@ -25,6 +25,7 @@ function publicConfig(config) {
|
|
|
25
25
|
defaultOpenCodeModel: defaultModels.opencode,
|
|
26
26
|
defaultGrokModel: defaultModels.grok,
|
|
27
27
|
defaultQoderModel: defaultModels.qoder,
|
|
28
|
+
defaultPiModel: defaultModels.pi,
|
|
28
29
|
defaultModels,
|
|
29
30
|
};
|
|
30
31
|
}
|
|
@@ -283,6 +284,8 @@ export function registerSettingsRoutes(app, deps) {
|
|
|
283
284
|
stagePreference("defaultGrokModel", body.defaultModels.grok);
|
|
284
285
|
if (Object.hasOwn(body.defaultModels, "qoder"))
|
|
285
286
|
stagePreference("defaultQoderModel", body.defaultModels.qoder);
|
|
287
|
+
if (Object.hasOwn(body.defaultModels, "pi"))
|
|
288
|
+
stagePreference("defaultPiModel", body.defaultModels.pi);
|
|
286
289
|
}
|
|
287
290
|
if (body.systemAi !== undefined) {
|
|
288
291
|
if (!body.systemAi || typeof body.systemAi !== "object" || Array.isArray(body.systemAi)) {
|
|
@@ -397,6 +400,7 @@ export function registerSettingsRoutes(app, deps) {
|
|
|
397
400
|
defaultOpenCodeModel: defaults.opencode,
|
|
398
401
|
defaultGrokModel: defaults.grok,
|
|
399
402
|
defaultQoderModel: defaults.qoder,
|
|
403
|
+
defaultPiModel: defaults.pi,
|
|
400
404
|
defaultModels: defaults,
|
|
401
405
|
});
|
|
402
406
|
});
|
|
@@ -411,6 +415,7 @@ export function registerSettingsRoutes(app, deps) {
|
|
|
411
415
|
defaultOpenCodeModel: defaults.opencode,
|
|
412
416
|
defaultGrokModel: defaults.grok,
|
|
413
417
|
defaultQoderModel: defaults.qoder,
|
|
418
|
+
defaultPiModel: defaults.pi,
|
|
414
419
|
defaultModels: defaults,
|
|
415
420
|
});
|
|
416
421
|
}
|
|
@@ -126,7 +126,7 @@ export function registerAdminUpdateRoutes(app, deps) {
|
|
|
126
126
|
return;
|
|
127
127
|
}
|
|
128
128
|
const rawIds = Array.isArray(req.body?.ids) ? req.body.ids : [];
|
|
129
|
-
const ids = rawIds.filter((value) => value === "claude" || value === "codex" || value === "opencode" || value === "qoder");
|
|
129
|
+
const ids = rawIds.filter((value) => value === "claude" || value === "codex" || value === "opencode" || value === "qoder" || value === "pi");
|
|
130
130
|
state.providerCliUpdateInFlight = true;
|
|
131
131
|
try {
|
|
132
132
|
const before = await refreshProviderCliUpdateState(state, config);
|