@co0ontty/wand 4.6.1 → 4.7.0-beta.gd325de7
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/dist/build-info.json +4 -4
- package/dist/cli.js +6 -3
- package/dist/config.d.ts +6 -2
- package/dist/config.js +59 -1
- package/dist/distribution-manager.d.ts +5 -0
- package/dist/distribution-manager.js +9 -1
- package/dist/git-quick-commit.d.ts +2 -0
- package/dist/git-quick-commit.js +81 -13
- package/dist/npm-update-utils.js +8 -4
- package/dist/process-manager.js +1 -1
- package/dist/prompt-optimizer.d.ts +2 -1
- package/dist/prompt-optimizer.js +3 -2
- package/dist/server-settings-routes.js +41 -2
- package/dist/server.js +2 -8
- package/dist/session-ai-context.d.ts +3 -2
- package/dist/session-ai-context.js +8 -0
- package/dist/session-topic.d.ts +2 -1
- package/dist/session-topic.js +5 -2
- package/dist/structured-session-manager.js +1 -1
- package/dist/system-ai.d.ts +9 -0
- package/dist/system-ai.js +166 -0
- package/dist/tui/commands.js +1 -16
- package/dist/types.d.ts +17 -0
- package/dist/version-utils.d.ts +5 -0
- package/dist/version-utils.js +14 -0
- package/dist/web-ui/content/scripts.js +38 -38
- 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/package.json +1 -1
package/dist/build-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
3
|
-
"builtAt": "2026-07-
|
|
4
|
-
"version": "4.
|
|
5
|
-
"channel": "
|
|
2
|
+
"commit": "d325de70c28d8eda24f741294c3de61e96ee27e7",
|
|
3
|
+
"builtAt": "2026-07-16T11:22:44.578Z",
|
|
4
|
+
"version": "4.7.0-beta.gd325de7",
|
|
5
|
+
"channel": "beta"
|
|
6
6
|
}
|
package/dist/cli.js
CHANGED
|
@@ -96,9 +96,7 @@ async function main() {
|
|
|
96
96
|
break;
|
|
97
97
|
}
|
|
98
98
|
case "config:show": {
|
|
99
|
-
// 展示合并后的视图(JSON 部署字段 + DB
|
|
100
|
-
// 显示是否已自定义("<set>" / "change-me"),避免误把真密码截图分享出去;
|
|
101
|
-
// 想看真值就直接读 DB(sqlite3 wand.db "SELECT * FROM app_config WHERE key='password'")。
|
|
99
|
+
// 展示合并后的视图(JSON 部署字段 + DB 偏好字段),但绝不打印运行时密钥。
|
|
102
100
|
const { ensureDatabaseFile, resolveDatabasePath, WandStorage } = await import("./storage.js");
|
|
103
101
|
const dbPath = resolveDatabasePath(configPath);
|
|
104
102
|
ensureDatabaseFile(dbPath);
|
|
@@ -108,6 +106,11 @@ async function main() {
|
|
|
108
106
|
const display = {
|
|
109
107
|
...config,
|
|
110
108
|
password: config.password === "change-me" ? "change-me" : "<set>",
|
|
109
|
+
appSecret: config.appSecret ? "<set>" : "",
|
|
110
|
+
systemAi: config.systemAi ? {
|
|
111
|
+
...config.systemAi,
|
|
112
|
+
apiKey: config.systemAi.apiKey ? "<set>" : "",
|
|
113
|
+
} : undefined,
|
|
111
114
|
};
|
|
112
115
|
process.stdout.write(`${JSON.stringify(display, null, 2)}\n`);
|
|
113
116
|
}
|
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", "commitCli", "commitModel", "defaultThinkingEffort", "structuredRunner", "language", "cardDefaults", "inheritEnv"];
|
|
10
|
+
export declare const PREFERENCE_KEYS: readonly ["defaultProvider", "defaultSessionKind", "defaultMode", "defaultCwd", "defaultModel", "defaultCodexModel", "defaultOpenCodeModel", "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;
|
|
@@ -37,7 +37,11 @@ export declare function migrateLegacyPreferencesToDb(rawJsonInput: Partial<WandC
|
|
|
37
37
|
*/
|
|
38
38
|
export declare function applyStoragePreferences(config: WandConfig, storage: WandStorage): WandConfig;
|
|
39
39
|
/** Write a single preference value to DB and (in-place) update the live config object. */
|
|
40
|
-
export declare function writePreferenceToStorage(config: WandConfig, storage: WandStorage, key: PreferenceKey, value: unknown
|
|
40
|
+
export declare function writePreferenceToStorage(config: WandConfig, storage: WandStorage, key: PreferenceKey, value: unknown, options?: {
|
|
41
|
+
deferCommitAiValidation?: boolean;
|
|
42
|
+
}): void;
|
|
43
|
+
/** Validate the cross-field contract for Commit's direct-API source. */
|
|
44
|
+
export declare function validateCommitAiConfig(config: Pick<WandConfig, "commitAiSource" | "systemAi">): void;
|
|
41
45
|
export declare function normalizeCardDefaults(input: unknown): CardExpandDefaults;
|
|
42
46
|
export declare function isExecutionMode(value: unknown): value is ExecutionMode;
|
|
43
47
|
export declare function getProviderDefaultModels(config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel">): {
|
package/dist/config.js
CHANGED
|
@@ -4,6 +4,7 @@ import { chmod, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promi
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import process from "node:process";
|
|
6
6
|
import { isRunningAsRoot } from "./env-utils.js";
|
|
7
|
+
import { normalizeSystemAiConfig } from "./system-ai.js";
|
|
7
8
|
function isThinkingEffort(value) {
|
|
8
9
|
return value === "off"
|
|
9
10
|
|| value === "standard"
|
|
@@ -30,6 +31,8 @@ export const PREFERENCE_KEYS = [
|
|
|
30
31
|
"defaultOpenCodeModel",
|
|
31
32
|
"commitCli",
|
|
32
33
|
"commitModel",
|
|
34
|
+
"commitAiSource",
|
|
35
|
+
"systemAi",
|
|
33
36
|
"defaultThinkingEffort",
|
|
34
37
|
"structuredRunner",
|
|
35
38
|
"language",
|
|
@@ -70,6 +73,16 @@ export const defaultConfig = () => ({
|
|
|
70
73
|
defaultOpenCodeModel: "",
|
|
71
74
|
commitCli: "claude",
|
|
72
75
|
commitModel: "",
|
|
76
|
+
commitAiSource: "cli",
|
|
77
|
+
systemAi: {
|
|
78
|
+
enabled: false,
|
|
79
|
+
protocol: "openai",
|
|
80
|
+
baseUrl: "",
|
|
81
|
+
apiKey: "",
|
|
82
|
+
model: "",
|
|
83
|
+
authHeader: "bearer",
|
|
84
|
+
source: "custom",
|
|
85
|
+
},
|
|
73
86
|
defaultThinkingEffort: "off",
|
|
74
87
|
structuredRunner: "cli",
|
|
75
88
|
inheritEnv: true,
|
|
@@ -310,6 +323,15 @@ export function applyStoragePreferences(config, storage) {
|
|
|
310
323
|
if (typeof v === "string")
|
|
311
324
|
config.commitModel = v.trim();
|
|
312
325
|
}
|
|
326
|
+
if (storage.hasPreference(preferenceStorageKey("commitAiSource"))) {
|
|
327
|
+
const v = storage.getPreference(preferenceStorageKey("commitAiSource"), defaults.commitAiSource ?? "cli");
|
|
328
|
+
if (v === "cli" || v === "api")
|
|
329
|
+
config.commitAiSource = v;
|
|
330
|
+
}
|
|
331
|
+
if (storage.hasPreference(preferenceStorageKey("systemAi"))) {
|
|
332
|
+
const v = storage.getPreference(preferenceStorageKey("systemAi"), defaults.systemAi);
|
|
333
|
+
config.systemAi = normalizeSystemAiConfig(v, defaults.systemAi);
|
|
334
|
+
}
|
|
313
335
|
if (storage.hasPreference(preferenceStorageKey("defaultThinkingEffort"))) {
|
|
314
336
|
const v = storage.getPreference(preferenceStorageKey("defaultThinkingEffort"), defaults.defaultThinkingEffort ?? "off");
|
|
315
337
|
if (isThinkingEffort(v))
|
|
@@ -333,10 +355,11 @@ export function applyStoragePreferences(config, storage) {
|
|
|
333
355
|
const v = storage.getPreference(preferenceStorageKey("inheritEnv"), defaults.inheritEnv ?? true);
|
|
334
356
|
config.inheritEnv = v === false ? false : true;
|
|
335
357
|
}
|
|
358
|
+
validateCommitAiConfig(config);
|
|
336
359
|
return config;
|
|
337
360
|
}
|
|
338
361
|
/** Write a single preference value to DB and (in-place) update the live config object. */
|
|
339
|
-
export function writePreferenceToStorage(config, storage, key, value) {
|
|
362
|
+
export function writePreferenceToStorage(config, storage, key, value, options = {}) {
|
|
340
363
|
const dbKey = preferenceStorageKey(key);
|
|
341
364
|
switch (key) {
|
|
342
365
|
case "defaultProvider": {
|
|
@@ -397,6 +420,31 @@ export function writePreferenceToStorage(config, storage, key, value) {
|
|
|
397
420
|
config.commitModel = v;
|
|
398
421
|
break;
|
|
399
422
|
}
|
|
423
|
+
case "commitAiSource": {
|
|
424
|
+
if (value !== "cli" && value !== "api")
|
|
425
|
+
throw new Error(`无效 commit AI 来源: ${String(value)}`);
|
|
426
|
+
if (!options.deferCommitAiValidation) {
|
|
427
|
+
validateCommitAiConfig({ ...config, commitAiSource: value });
|
|
428
|
+
}
|
|
429
|
+
storage.setPreference(dbKey, value);
|
|
430
|
+
config.commitAiSource = value;
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
433
|
+
case "systemAi": {
|
|
434
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
435
|
+
throw new Error("systemAi 必须是对象。");
|
|
436
|
+
}
|
|
437
|
+
const normalized = normalizeSystemAiConfig(value, config.systemAi ?? defaultConfig().systemAi);
|
|
438
|
+
if (normalized.enabled && (!normalized.baseUrl || !normalized.apiKey || !normalized.model)) {
|
|
439
|
+
throw new Error("启用系统 AI API 时,地址、API Key 和模型不能为空。");
|
|
440
|
+
}
|
|
441
|
+
if (!options.deferCommitAiValidation) {
|
|
442
|
+
validateCommitAiConfig({ ...config, systemAi: normalized });
|
|
443
|
+
}
|
|
444
|
+
storage.setPreference(dbKey, normalized);
|
|
445
|
+
config.systemAi = normalized;
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
400
448
|
case "defaultThinkingEffort": {
|
|
401
449
|
if (!isThinkingEffort(value))
|
|
402
450
|
throw new Error(`无效思考深度: ${String(value)}`);
|
|
@@ -435,6 +483,15 @@ export function writePreferenceToStorage(config, storage, key, value) {
|
|
|
435
483
|
}
|
|
436
484
|
}
|
|
437
485
|
}
|
|
486
|
+
/** Validate the cross-field contract for Commit's direct-API source. */
|
|
487
|
+
export function validateCommitAiConfig(config) {
|
|
488
|
+
if (config.commitAiSource !== "api")
|
|
489
|
+
return;
|
|
490
|
+
const directApi = config.systemAi;
|
|
491
|
+
if (!directApi?.baseUrl || !directApi.apiKey || !directApi.model) {
|
|
492
|
+
throw new Error("选择直连 API 生成 Commit 时,必须先填写 API 地址、API Key 和模型。");
|
|
493
|
+
}
|
|
494
|
+
}
|
|
438
495
|
function defaultCardExpandDefaults() {
|
|
439
496
|
return {
|
|
440
497
|
editCards: false,
|
|
@@ -578,6 +635,7 @@ function mergeWithDefaults(input) {
|
|
|
578
635
|
defaultOpenCodeModel: typeof input.defaultOpenCodeModel === "string" ? input.defaultOpenCodeModel.trim() : defaults.defaultOpenCodeModel,
|
|
579
636
|
commitCli: input.commitCli === "codex" || input.commitCli === "opencode" ? input.commitCli : "claude",
|
|
580
637
|
commitModel: typeof input.commitModel === "string" ? input.commitModel.trim() : defaults.commitModel,
|
|
638
|
+
commitAiSource: input.commitAiSource === "api" ? "api" : "cli",
|
|
581
639
|
defaultThinkingEffort: isThinkingEffort(input.defaultThinkingEffort) ? input.defaultThinkingEffort : "off",
|
|
582
640
|
structuredRunner: (input.structuredRunner === "sdk" || input.structuredRunner === "cli") ? input.structuredRunner : defaults.structuredRunner,
|
|
583
641
|
inheritEnv: typeof input.inheritEnv === "boolean" ? input.inheritEnv : (defaults.inheritEnv ?? true),
|
|
@@ -29,6 +29,11 @@ export interface DistributionManagerOptions {
|
|
|
29
29
|
fetch?: typeof fetch;
|
|
30
30
|
now?: () => number;
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* GitHub Release 正文还包含 Android/macOS/iOS 的安装指引;它们属于发布页,
|
|
34
|
+
* 不该出现在 Android 的更新弹窗。保留分隔线前的变更摘要,并兼容旧版正文。
|
|
35
|
+
*/
|
|
36
|
+
export declare function extractUpdateSummary(releaseBody: string): string;
|
|
32
37
|
export declare class DistributionManager {
|
|
33
38
|
private readonly options;
|
|
34
39
|
private readonly fetchImpl;
|
|
@@ -2,6 +2,14 @@ import { mkdir, readdir, readFile, stat } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { compareApkInstallOrder, compareSemver, extractSemver } from "./version-utils.js";
|
|
4
4
|
const GITHUB_CACHE_TTL_MS = 10 * 60 * 1000;
|
|
5
|
+
/**
|
|
6
|
+
* GitHub Release 正文还包含 Android/macOS/iOS 的安装指引;它们属于发布页,
|
|
7
|
+
* 不该出现在 Android 的更新弹窗。保留分隔线前的变更摘要,并兼容旧版正文。
|
|
8
|
+
*/
|
|
9
|
+
export function extractUpdateSummary(releaseBody) {
|
|
10
|
+
const summary = releaseBody.split(/\r?\n---\s*(?:\r?\n|$)/, 1)[0]?.trim() ?? "";
|
|
11
|
+
return summary.slice(0, 500);
|
|
12
|
+
}
|
|
5
13
|
function asRecord(value) {
|
|
6
14
|
return value && typeof value === "object" ? value : null;
|
|
7
15
|
}
|
|
@@ -215,7 +223,7 @@ export class DistributionManager {
|
|
|
215
223
|
downloadUrl: hit.asset.browser_download_url,
|
|
216
224
|
fileName: hit.asset.name,
|
|
217
225
|
size: hit.asset.size,
|
|
218
|
-
...(extension === ".apk" && hit.body ? { releaseNotes: hit.body
|
|
226
|
+
...(extension === ".apk" && hit.body ? { releaseNotes: extractUpdateSummary(hit.body) } : {}),
|
|
219
227
|
};
|
|
220
228
|
this.githubCache.set(extension, { asset, timestamp: this.now() });
|
|
221
229
|
return asset;
|
|
@@ -10,6 +10,7 @@ interface QuickCommitOptions {
|
|
|
10
10
|
model?: string | null;
|
|
11
11
|
thinkingEffort?: SessionSnapshot["thinkingEffort"];
|
|
12
12
|
inheritEnv?: boolean;
|
|
13
|
+
systemAi?: import("./types.js").SystemAiConfig;
|
|
13
14
|
autoMessage: boolean;
|
|
14
15
|
customMessage?: string;
|
|
15
16
|
tag?: string;
|
|
@@ -27,6 +28,7 @@ interface QuickCommitAiOptions {
|
|
|
27
28
|
model?: string | null;
|
|
28
29
|
thinkingEffort?: SessionSnapshot["thinkingEffort"];
|
|
29
30
|
inheritEnv?: boolean;
|
|
31
|
+
systemAi?: import("./types.js").SystemAiConfig;
|
|
30
32
|
}
|
|
31
33
|
export declare class QuickCommitError extends Error {
|
|
32
34
|
readonly code: QuickCommitErrorCode;
|
package/dist/git-quick-commit.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { ClaudeRunError, runClaudePrint } from "./claude-sdk-runner.js";
|
|
4
|
+
import { callSystemAiText } from "./system-ai.js";
|
|
4
5
|
import { buildChildEnv } from "./env-utils.js";
|
|
5
6
|
import { runGit as runGitBase, runGitAsync as runGitAsyncBase, runGitRaw as runGitRawBase, runGitRawAsync as runGitRawAsyncBase, getGitErrorMessage, } from "./git-utils.js";
|
|
6
7
|
import { thinkingEffortToClaudeCliEffort, thinkingEffortToCodexReasoningEffort, thinkingEffortToOpenCodeVariant } from "./structured-provider-common.js";
|
|
@@ -81,6 +82,58 @@ async function resolvePushRemoteAsync(cwd) {
|
|
|
81
82
|
catch { /* use origin */ }
|
|
82
83
|
return "origin";
|
|
83
84
|
}
|
|
85
|
+
function githubSshPushUrl(remoteUrl) {
|
|
86
|
+
try {
|
|
87
|
+
const parsed = new URL(remoteUrl);
|
|
88
|
+
if (parsed.protocol !== "https:" || parsed.hostname.toLowerCase() !== "github.com")
|
|
89
|
+
return undefined;
|
|
90
|
+
const path = decodeURIComponent(parsed.pathname).replace(/^\/+|\/+$/g, "");
|
|
91
|
+
const parts = path.split("/");
|
|
92
|
+
if (parts.length !== 2 || parts.some((part) => !part))
|
|
93
|
+
return undefined;
|
|
94
|
+
return `git@github.com:${parts[0]}/${parts[1]}`;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function isHttpsCredentialError(error) {
|
|
101
|
+
return /could not read (?:User|Pass)name|authentication failed|terminal prompts disabled|credential[^\n]*(?:failed|missing)|http basic: access denied/i
|
|
102
|
+
.test(getGitErrorMessage(error));
|
|
103
|
+
}
|
|
104
|
+
async function resolveRemotePushUrl(cwd, remote) {
|
|
105
|
+
try {
|
|
106
|
+
return await runGitAsync(["remote", "get-url", "--push", remote], cwd);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return remote;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Push explicit refs through one transport seam. A Wand service deliberately disables interactive
|
|
114
|
+
* credential prompts, so a GitHub HTTPS remote that has no non-interactive credential would
|
|
115
|
+
* otherwise fail even when the same machine is already configured for GitHub SSH.
|
|
116
|
+
*/
|
|
117
|
+
async function pushRemoteRefs(cwd, remote, options = [], refs = []) {
|
|
118
|
+
try {
|
|
119
|
+
await runGitAsync(["push", ...options, remote, ...refs], cwd, GIT_PUSH_TIMEOUT_MS);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
if (!isHttpsCredentialError(error))
|
|
124
|
+
throw error;
|
|
125
|
+
const configuredUrl = await resolveRemotePushUrl(cwd, remote);
|
|
126
|
+
const sshUrl = githubSshPushUrl(configuredUrl);
|
|
127
|
+
if (!sshUrl)
|
|
128
|
+
throw error;
|
|
129
|
+
try {
|
|
130
|
+
await runGitAsync(["push", ...options, sshUrl, ...refs], cwd, GIT_PUSH_TIMEOUT_MS);
|
|
131
|
+
}
|
|
132
|
+
catch (sshError) {
|
|
133
|
+
throw new Error(`${getGitErrorMessage(error)}\nGitHub SSH 回退也失败:${getGitErrorMessage(sshError)}`, { cause: sshError });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
84
137
|
function unquotePath(raw) {
|
|
85
138
|
if (raw.startsWith("\"") && raw.endsWith("\"")) {
|
|
86
139
|
return raw.slice(1, -1).replace(/\\"/g, "\"").replace(/\\\\/g, "\\");
|
|
@@ -553,6 +606,8 @@ async function callOpenCodeText(prompt, cwd, opts) {
|
|
|
553
606
|
return text;
|
|
554
607
|
}
|
|
555
608
|
async function callAiText(prompt, cwd, language, opts) {
|
|
609
|
+
if (opts.systemAi?.enabled)
|
|
610
|
+
return callSystemAiText(prompt, opts.systemAi);
|
|
556
611
|
const provider = normalizeProvider(opts.provider);
|
|
557
612
|
if (provider === "codex") {
|
|
558
613
|
return callCodexText(prompt, cwd, opts);
|
|
@@ -751,21 +806,21 @@ async function doPush(opts) {
|
|
|
751
806
|
try {
|
|
752
807
|
if (pushCommits) {
|
|
753
808
|
if (hasUpstream) {
|
|
754
|
-
await
|
|
809
|
+
await pushRemoteRefs(cwd, pushRemote, [recurseFlag]);
|
|
755
810
|
}
|
|
756
811
|
else {
|
|
757
|
-
await
|
|
812
|
+
await pushRemoteRefs(cwd, pushRemote, ["-u", recurseFlag], ["HEAD"]);
|
|
758
813
|
}
|
|
759
814
|
pushedCommits = true;
|
|
760
815
|
}
|
|
761
816
|
if (pushTags) {
|
|
762
817
|
if (Array.isArray(pushTags)) {
|
|
763
818
|
for (const name of pushTags) {
|
|
764
|
-
await
|
|
819
|
+
await pushRemoteRefs(cwd, pushRemote, [], [`refs/tags/${name}`]);
|
|
765
820
|
}
|
|
766
821
|
}
|
|
767
822
|
else {
|
|
768
|
-
await
|
|
823
|
+
await pushRemoteRefs(cwd, pushRemote, ["--tags"]);
|
|
769
824
|
}
|
|
770
825
|
pushedTags = true;
|
|
771
826
|
}
|
|
@@ -1012,7 +1067,7 @@ async function pushSubmodules(parentCwd, subInfos, opts) {
|
|
|
1012
1067
|
continue;
|
|
1013
1068
|
}
|
|
1014
1069
|
try {
|
|
1015
|
-
await
|
|
1070
|
+
await pushRemoteRefs(subCwd, info.remote, [], [`HEAD:refs/heads/${info.branch}`]);
|
|
1016
1071
|
}
|
|
1017
1072
|
catch (error) {
|
|
1018
1073
|
errors.push(`submodule ${info.path} 推送失败:${getGitErrorMessage(error)}`);
|
|
@@ -1020,7 +1075,7 @@ async function pushSubmodules(parentCwd, subInfos, opts) {
|
|
|
1020
1075
|
}
|
|
1021
1076
|
if (opts.pushTags && opts.tagName) {
|
|
1022
1077
|
try {
|
|
1023
|
-
await
|
|
1078
|
+
await pushRemoteRefs(subCwd, info.remote, [], [`refs/tags/${opts.tagName}`]);
|
|
1024
1079
|
}
|
|
1025
1080
|
catch (error) {
|
|
1026
1081
|
errors.push(`submodule ${info.path} 推送 tag 失败:${getGitErrorMessage(error)}`);
|
|
@@ -1191,7 +1246,11 @@ async function runQuickCommitFallbackCli(opts, priorError) {
|
|
|
1191
1246
|
pushed: false,
|
|
1192
1247
|
};
|
|
1193
1248
|
}
|
|
1194
|
-
function shouldFallbackToCli(error) {
|
|
1249
|
+
function shouldFallbackToCli(error, opts) {
|
|
1250
|
+
// An explicit direct-API selection is a hard boundary: never execute a local
|
|
1251
|
+
// provider CLI (especially the permission-bypassing fallback) behind it.
|
|
1252
|
+
if (opts.systemAi?.enabled)
|
|
1253
|
+
return false;
|
|
1195
1254
|
return ![
|
|
1196
1255
|
"CWD_MISSING",
|
|
1197
1256
|
"NO_CWD",
|
|
@@ -1209,7 +1268,7 @@ export async function runQuickCommitWithFallback(opts) {
|
|
|
1209
1268
|
return await runQuickCommit(opts);
|
|
1210
1269
|
}
|
|
1211
1270
|
catch (error) {
|
|
1212
|
-
if (error instanceof QuickCommitError && shouldFallbackToCli(error)) {
|
|
1271
|
+
if (error instanceof QuickCommitError && shouldFallbackToCli(error, opts)) {
|
|
1213
1272
|
return runQuickCommitFallbackCli(opts, error.message);
|
|
1214
1273
|
}
|
|
1215
1274
|
throw error;
|
|
@@ -1222,6 +1281,7 @@ export async function runQuickCommit(opts) {
|
|
|
1222
1281
|
model: opts.model,
|
|
1223
1282
|
thinkingEffort: opts.thinkingEffort,
|
|
1224
1283
|
inheritEnv: opts.inheritEnv,
|
|
1284
|
+
systemAi: opts.systemAi,
|
|
1225
1285
|
};
|
|
1226
1286
|
await assertGitWorkTreeAsync(cwd);
|
|
1227
1287
|
// 先 add 一次让我们能在 collectStagedDiff 看到完整改动(包含 submodule 指针),
|
|
@@ -1312,6 +1372,13 @@ export async function runQuickCommit(opts) {
|
|
|
1312
1372
|
if (!tagName && autoTag) {
|
|
1313
1373
|
tagName = await generateTagAfterCommit(cwd, language, message, ai);
|
|
1314
1374
|
}
|
|
1375
|
+
let submodulePushInfos = submoduleOutcome.commits;
|
|
1376
|
+
if (submodule) {
|
|
1377
|
+
// The selected scope is the declared submodule set, not merely the submodules dirtied by this
|
|
1378
|
+
// request. A clean submodule can still have an unpushed HEAD or a pre-existing pointer change.
|
|
1379
|
+
const collected = await collectSubmodulesForPush(cwd);
|
|
1380
|
+
submodulePushInfos = collected.infos;
|
|
1381
|
+
}
|
|
1315
1382
|
if (tagName) {
|
|
1316
1383
|
try {
|
|
1317
1384
|
await runGitAsync(["tag", tagName], cwd);
|
|
@@ -1319,9 +1386,10 @@ export async function runQuickCommit(opts) {
|
|
|
1319
1386
|
catch (error) {
|
|
1320
1387
|
throw new QuickCommitError(`git tag 失败:${getGitErrorMessage(error)}`, "GIT_TAG_FAILED");
|
|
1321
1388
|
}
|
|
1322
|
-
// 纳入 submodule
|
|
1323
|
-
|
|
1324
|
-
|
|
1389
|
+
// 纳入 submodule 时给全部声明的 submodule 当前 HEAD 打同名 tag。这样纯指针变化或
|
|
1390
|
+
// 已提前提交但尚未推送的 submodule 也不会漏掉发布 tag。
|
|
1391
|
+
if (submodule && submodulePushInfos.length > 0) {
|
|
1392
|
+
await tagSubmodules(cwd, submodulePushInfos, tagName);
|
|
1325
1393
|
}
|
|
1326
1394
|
}
|
|
1327
1395
|
let pushed = false;
|
|
@@ -1330,10 +1398,10 @@ export async function runQuickCommit(opts) {
|
|
|
1330
1398
|
// 纳入 submodule:先把各 submodule 的 HEAD(+ 同名 tag)分别推到各自远端分支,
|
|
1331
1399
|
// 解决 detached HEAD 无法被父仓库 on-demand 递归推送的问题;父仓库随后用
|
|
1332
1400
|
// recurse=no 单独推(submodule 已就绪)。否则父仓库用 recurse=check 做安全校验。
|
|
1333
|
-
const includeSub = !!submodule &&
|
|
1401
|
+
const includeSub = !!submodule && submodulePushInfos.length > 0;
|
|
1334
1402
|
let subPushErrors = [];
|
|
1335
1403
|
if (includeSub) {
|
|
1336
|
-
const subPush = await pushSubmodules(cwd,
|
|
1404
|
+
const subPush = await pushSubmodules(cwd, submodulePushInfos, { pushTags: !!tagName, tagName });
|
|
1337
1405
|
subPushErrors = subPush.errors;
|
|
1338
1406
|
}
|
|
1339
1407
|
if (subPushErrors.length > 0) {
|
package/dist/npm-update-utils.js
CHANGED
|
@@ -19,6 +19,7 @@ import process from "node:process";
|
|
|
19
19
|
import { promisify } from "node:util";
|
|
20
20
|
import { whichSync } from "./path-repair.js";
|
|
21
21
|
import { getErrorMessage } from "./error-utils.js";
|
|
22
|
+
import { compareWandInstallOrder, extractSemver } from "./version-utils.js";
|
|
22
23
|
const execFileAsync = promisify(execFile);
|
|
23
24
|
export const PACKAGE_NAME = "@co0ontty/wand";
|
|
24
25
|
const PACKAGE_SCOPE = "@co0ontty";
|
|
@@ -53,10 +54,13 @@ function computeUpdateAvailable(currentVersion, latestVersion, channel) {
|
|
|
53
54
|
const target = channel === "stable"
|
|
54
55
|
? getStableTagVersion(latestVersion)
|
|
55
56
|
: cleanVersion(latestVersion);
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
|
|
57
|
+
// Invalid/manual labels cannot be ordered safely, so offer the selected
|
|
58
|
+
// dist-tag. Valid versions only update forward. In particular, Wand's local
|
|
59
|
+
// X.Y.Z-debug.* build represents code after X.Y.Z and must not be replaced
|
|
60
|
+
// by the same stable release as an apparent "upgrade".
|
|
61
|
+
if (!extractSemver(current) || !extractSemver(target))
|
|
62
|
+
return current !== target;
|
|
63
|
+
return compareWandInstallOrder(target, current) > 0;
|
|
60
64
|
}
|
|
61
65
|
export function buildPackageUpdateInfo(currentVersion, channel, latestVersion) {
|
|
62
66
|
const latest = latestVersion?.trim() || null;
|
package/dist/process-manager.js
CHANGED
|
@@ -1564,7 +1564,7 @@ export class ProcessManager extends EventEmitter {
|
|
|
1564
1564
|
if (this.disposed || !prompt || !record || record.title || this.topicRequests.has(id))
|
|
1565
1565
|
return;
|
|
1566
1566
|
this.topicRequests.add(id);
|
|
1567
|
-
void generateSessionTopic(prompt, record.cwd, this.config.language)
|
|
1567
|
+
void generateSessionTopic(prompt, record.cwd, this.config.language, this.config.systemAi)
|
|
1568
1568
|
.then(({ title, description }) => {
|
|
1569
1569
|
if (!this.disposed && this.sessions.has(id))
|
|
1570
1570
|
this.setSessionTopic(id, title, description);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { SystemAiConfig } from "./types.js";
|
|
1
2
|
export declare class PromptOptimizeError extends Error {
|
|
2
3
|
readonly code: string;
|
|
3
4
|
constructor(message: string, code: string);
|
|
4
5
|
}
|
|
5
|
-
export declare function optimizePrompt(rawText: string, language: string, cwd?: string): Promise<string>;
|
|
6
|
+
export declare function optimizePrompt(rawText: string, language: string, cwd?: string, systemAi?: SystemAiConfig): Promise<string>;
|
package/dist/prompt-optimizer.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ClaudeRunError, runClaudePrint } from "./claude-sdk-runner.js";
|
|
2
|
+
import { callSystemAiText } from "./system-ai.js";
|
|
2
3
|
const CLAUDE_TIMEOUT_MS = 60_000;
|
|
3
4
|
const MAX_INPUT_LENGTH = 8000;
|
|
4
5
|
export class PromptOptimizeError extends Error {
|
|
@@ -41,7 +42,7 @@ function buildOptimizePrompt(userInput, language) {
|
|
|
41
42
|
userInput,
|
|
42
43
|
].join("\n");
|
|
43
44
|
}
|
|
44
|
-
export async function optimizePrompt(rawText, language, cwd) {
|
|
45
|
+
export async function optimizePrompt(rawText, language, cwd, systemAi) {
|
|
45
46
|
const text = (rawText || "").trim();
|
|
46
47
|
if (!text) {
|
|
47
48
|
throw new PromptOptimizeError("请先输入要优化的内容。", "EMPTY_INPUT");
|
|
@@ -50,7 +51,7 @@ export async function optimizePrompt(rawText, language, cwd) {
|
|
|
50
51
|
throw new PromptOptimizeError(`输入过长(${text.length} 字符),请缩短到 ${MAX_INPUT_LENGTH} 以内。`, "INPUT_TOO_LONG");
|
|
51
52
|
}
|
|
52
53
|
const prompt = buildOptimizePrompt(text, language);
|
|
53
|
-
const raw = await callClaudeText(prompt, cwd, language);
|
|
54
|
+
const raw = systemAi?.enabled ? await callSystemAiText(prompt, systemAi) : await callClaudeText(prompt, cwd, language);
|
|
54
55
|
const cleaned = raw
|
|
55
56
|
.replace(/^```[a-zA-Z]*\n?/, "")
|
|
56
57
|
.replace(/\n?```$/, "")
|
|
@@ -3,14 +3,20 @@ import path from "node:path";
|
|
|
3
3
|
import { buildChildEnv } from "./env-utils.js";
|
|
4
4
|
import { getErrorMessage } from "./error-utils.js";
|
|
5
5
|
import { asyncRoute } from "./express-async.js";
|
|
6
|
-
import { getProviderDefaultModels, PREFERENCE_KEYS, saveConfig, writePreferenceToStorage, } from "./config.js";
|
|
6
|
+
import { getProviderDefaultModels, PREFERENCE_KEYS, saveConfig, validateCommitAiConfig, writePreferenceToStorage, } from "./config.js";
|
|
7
7
|
import { getCachedModels, refreshModels } from "./models.js";
|
|
8
8
|
import { DEPLOYMENT_CONFIG_KEYS } from "./runtime-config.js";
|
|
9
|
+
import { discoverCliSystemAiConfig, normalizeSystemAiConfig } from "./system-ai.js";
|
|
9
10
|
function publicConfig(config) {
|
|
10
11
|
const { password: _password, appSecret: _appSecret, ...safe } = config;
|
|
11
12
|
const defaultModels = getProviderDefaultModels(config);
|
|
12
13
|
return {
|
|
13
14
|
...safe,
|
|
15
|
+
systemAi: safe.systemAi ? {
|
|
16
|
+
...safe.systemAi,
|
|
17
|
+
apiKey: "",
|
|
18
|
+
hasApiKey: Boolean(safe.systemAi.apiKey),
|
|
19
|
+
} : undefined,
|
|
14
20
|
defaultModel: defaultModels.claude,
|
|
15
21
|
defaultCodexModel: defaultModels.codex,
|
|
16
22
|
defaultOpenCodeModel: defaultModels.opencode,
|
|
@@ -104,6 +110,24 @@ export function registerSettingsRoutes(app, deps) {
|
|
|
104
110
|
});
|
|
105
111
|
res.json({ inheritEnv, total: entries.length, reveal, entries });
|
|
106
112
|
});
|
|
113
|
+
app.post("/api/settings/system-ai/import", requireAdmin, (req, res) => {
|
|
114
|
+
const body = (req.body ?? {});
|
|
115
|
+
const source = body.source === "codex" || body.source === "opencode" || body.source === "claude"
|
|
116
|
+
? body.source
|
|
117
|
+
: config.commitCli;
|
|
118
|
+
const imported = discoverCliSystemAiConfig(source);
|
|
119
|
+
if (!imported) {
|
|
120
|
+
res.status(404).json({ error: "没有在已配置的 CLI 文件中找到可直连的 API 地址、密钥和模型。" });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const candidate = runtimeConfig.createCandidate();
|
|
124
|
+
writePreferenceToStorage(candidate, storage, "systemAi", {
|
|
125
|
+
...imported,
|
|
126
|
+
enabled: candidate.systemAi?.enabled === true,
|
|
127
|
+
});
|
|
128
|
+
runtimeConfig.commit(candidate, new Set(["systemAi"]));
|
|
129
|
+
res.json({ ok: true, systemAi: (publicConfig(candidate).systemAi) });
|
|
130
|
+
});
|
|
107
131
|
app.get("/api/app-connect-code", requireAdmin, (req, res) => {
|
|
108
132
|
res.json(deps.resolveAppConnectCode(req));
|
|
109
133
|
});
|
|
@@ -117,7 +141,9 @@ export function registerSettingsRoutes(app, deps) {
|
|
|
117
141
|
setPreference(key, value) { stagedPreferences.push({ key, value }); },
|
|
118
142
|
};
|
|
119
143
|
const stagePreference = (field, value) => {
|
|
120
|
-
writePreferenceToStorage(candidateConfig, stagingStorage, field, value
|
|
144
|
+
writePreferenceToStorage(candidateConfig, stagingStorage, field, value, {
|
|
145
|
+
deferCommitAiValidation: true,
|
|
146
|
+
});
|
|
121
147
|
stagedPreferenceFields.add(field);
|
|
122
148
|
};
|
|
123
149
|
let touchedDeployField = false;
|
|
@@ -159,12 +185,25 @@ export function registerSettingsRoutes(app, deps) {
|
|
|
159
185
|
if (Object.hasOwn(body.defaultModels, "opencode"))
|
|
160
186
|
stagePreference("defaultOpenCodeModel", body.defaultModels.opencode);
|
|
161
187
|
}
|
|
188
|
+
if (body.systemAi !== undefined) {
|
|
189
|
+
if (!body.systemAi || typeof body.systemAi !== "object" || Array.isArray(body.systemAi)) {
|
|
190
|
+
throw new Error("systemAi 必须是对象。");
|
|
191
|
+
}
|
|
192
|
+
const previous = candidateConfig.systemAi;
|
|
193
|
+
const apiKey = typeof body.systemAi.apiKey === "string" && body.systemAi.apiKey.trim()
|
|
194
|
+
? body.systemAi.apiKey.trim()
|
|
195
|
+
: previous?.apiKey ?? "";
|
|
196
|
+
stagePreference("systemAi", normalizeSystemAiConfig({ ...previous, ...body.systemAi, apiKey }, previous));
|
|
197
|
+
}
|
|
162
198
|
for (const field of PREFERENCE_KEYS) {
|
|
199
|
+
if (field === "systemAi")
|
|
200
|
+
continue;
|
|
163
201
|
const value = body[field];
|
|
164
202
|
if (!(field in body) || value === undefined)
|
|
165
203
|
continue;
|
|
166
204
|
stagePreference(field, value);
|
|
167
205
|
}
|
|
206
|
+
validateCommitAiConfig(candidateConfig);
|
|
168
207
|
}
|
|
169
208
|
catch (error) {
|
|
170
209
|
res.status(400).json({ error: getErrorMessage(error, "配置校验失败。") });
|
package/dist/server.js
CHANGED
|
@@ -59,18 +59,12 @@ async function checkLatestPackageVersion(channel, forceRefresh = false) {
|
|
|
59
59
|
const now = Date.now();
|
|
60
60
|
const cached = packageUpdateCache.get(channel);
|
|
61
61
|
if (!forceRefresh && cached && now - cached.timestamp < CACHE_TTL_MS) {
|
|
62
|
-
return
|
|
62
|
+
return cached.info;
|
|
63
63
|
}
|
|
64
64
|
const info = await checkPackageUpdateAsync(PKG_VERSION, channel);
|
|
65
65
|
if (info.latest) {
|
|
66
66
|
packageUpdateCache.set(channel, { info, timestamp: now });
|
|
67
67
|
}
|
|
68
|
-
return applyLocalBuildUpdateOverride(info);
|
|
69
|
-
}
|
|
70
|
-
function applyLocalBuildUpdateOverride(info) {
|
|
71
|
-
if (info.channel === "stable" && BUILD_INFO.channel === "beta" && info.latest) {
|
|
72
|
-
return { ...info, updateAvailable: true };
|
|
73
|
-
}
|
|
74
68
|
return info;
|
|
75
69
|
}
|
|
76
70
|
/** 读取 dist/build-info.json(由 scripts/stamp-build-info.js 在 build 时生成)。 */
|
|
@@ -924,7 +918,7 @@ export async function startServer(config, configPath, options = {}) {
|
|
|
924
918
|
cwd = snap.cwd;
|
|
925
919
|
}
|
|
926
920
|
try {
|
|
927
|
-
const optimized = await optimizePrompt(text, config.language ?? "", cwd);
|
|
921
|
+
const optimized = await optimizePrompt(text, config.language ?? "", cwd, config.systemAi);
|
|
928
922
|
res.json({ optimized });
|
|
929
923
|
}
|
|
930
924
|
catch (error) {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import type { SessionProvider, SessionSnapshot, WandConfig } from "./types.js";
|
|
1
|
+
import type { SessionProvider, SessionSnapshot, SystemAiConfig, WandConfig } from "./types.js";
|
|
2
2
|
export interface SessionAiContext {
|
|
3
3
|
provider: SessionProvider;
|
|
4
4
|
model?: string;
|
|
5
5
|
thinkingEffort: SessionSnapshot["thinkingEffort"];
|
|
6
6
|
inheritEnv?: boolean;
|
|
7
|
+
systemAi?: SystemAiConfig;
|
|
7
8
|
}
|
|
8
9
|
/**
|
|
9
10
|
* Resolve the provider from every representation used by current and legacy
|
|
@@ -14,4 +15,4 @@ export declare function resolveSessionProvider(snapshot: Pick<SessionSnapshot, "
|
|
|
14
15
|
/** Build the provider-specific settings used by session-adjacent AI actions. */
|
|
15
16
|
export declare function resolveSessionAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultThinkingEffort" | "inheritEnv">): SessionAiContext;
|
|
16
17
|
/** Build the AI context for quick-commit actions from their global preferences. */
|
|
17
|
-
export declare function resolveCommitAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultThinkingEffort" | "inheritEnv" | "commitCli" | "commitModel">): SessionAiContext;
|
|
18
|
+
export declare function resolveCommitAiContext(snapshot: Pick<SessionSnapshot, "provider" | "structuredState" | "runner" | "command" | "selectedModel" | "thinkingEffort">, config: Pick<WandConfig, "defaultModel" | "defaultCodexModel" | "defaultOpenCodeModel" | "defaultThinkingEffort" | "inheritEnv" | "commitCli" | "commitModel" | "commitAiSource" | "systemAi">): SessionAiContext;
|
|
@@ -41,9 +41,17 @@ export function resolveSessionAiContext(snapshot, config) {
|
|
|
41
41
|
/** Build the AI context for quick-commit actions from their global preferences. */
|
|
42
42
|
export function resolveCommitAiContext(snapshot, config) {
|
|
43
43
|
const sessionContext = resolveSessionAiContext(snapshot, config);
|
|
44
|
+
const directApi = config.systemAi ?? {
|
|
45
|
+
enabled: true,
|
|
46
|
+
protocol: "openai",
|
|
47
|
+
baseUrl: "",
|
|
48
|
+
apiKey: "",
|
|
49
|
+
model: "",
|
|
50
|
+
};
|
|
44
51
|
return {
|
|
45
52
|
...sessionContext,
|
|
46
53
|
provider: config.commitCli === "codex" || config.commitCli === "opencode" ? config.commitCli : "claude",
|
|
47
54
|
model: normalizeModel(config.commitModel),
|
|
55
|
+
...(config.commitAiSource === "api" ? { systemAi: { ...directApi, enabled: true } } : {}),
|
|
48
56
|
};
|
|
49
57
|
}
|
package/dist/session-topic.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { SystemAiConfig } from "./types.js";
|
|
1
2
|
export interface SessionTopic {
|
|
2
3
|
title: string;
|
|
3
4
|
description: string;
|
|
4
5
|
}
|
|
5
|
-
export declare function generateSessionTopic(userMessage: string, cwd?: string, language?: string): Promise<SessionTopic>;
|
|
6
|
+
export declare function generateSessionTopic(userMessage: string, cwd?: string, language?: string, systemAi?: SystemAiConfig): Promise<SessionTopic>;
|