@co0ontty/wand 3.1.1 → 4.0.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/dist/auth.d.ts +19 -5
- package/dist/auth.js +83 -45
- package/dist/build-info.json +3 -3
- package/dist/cert.d.ts +1 -1
- package/dist/cert.js +124 -74
- package/dist/config.js +25 -8
- package/dist/express-async.d.ts +6 -0
- package/dist/express-async.js +28 -0
- package/dist/git-quick-commit.d.ts +2 -0
- package/dist/git-quick-commit.js +215 -76
- package/dist/git-utils.d.ts +4 -0
- package/dist/git-utils.js +60 -11
- package/dist/git-worktree.d.ts +8 -1
- package/dist/git-worktree.js +406 -41
- package/dist/models.d.ts +34 -4
- package/dist/models.js +334 -48
- package/dist/process-manager.d.ts +22 -30
- package/dist/process-manager.js +374 -441
- package/dist/provider-history-scanner.d.ts +54 -0
- package/dist/provider-history-scanner.js +354 -0
- package/dist/request-limits.d.ts +1 -0
- package/dist/request-limits.js +8 -0
- package/dist/resume-policy.d.ts +2 -0
- package/dist/resume-policy.js +5 -0
- package/dist/runtime-config.d.ts +16 -0
- package/dist/runtime-config.js +49 -0
- package/dist/server-file-routes.d.ts +17 -0
- package/dist/server-file-routes.js +653 -0
- package/dist/server-session-routes.d.ts +16 -3
- package/dist/server-session-routes.js +170 -149
- package/dist/server-settings-routes.d.ts +43 -0
- package/dist/server-settings-routes.js +225 -0
- package/dist/server-update-routes.d.ts +61 -0
- package/dist/server-update-routes.js +215 -0
- package/dist/server.d.ts +6 -4
- package/dist/server.js +350 -1313
- package/dist/session-logger.d.ts +32 -2
- package/dist/session-logger.js +145 -15
- package/dist/session-registry.d.ts +27 -0
- package/dist/session-registry.js +153 -0
- package/dist/session-transport.d.ts +31 -0
- package/dist/session-transport.js +82 -0
- package/dist/storage.d.ts +24 -6
- package/dist/storage.js +291 -44
- package/dist/structured-claude-adapter.d.ts +19 -0
- package/dist/structured-claude-adapter.js +117 -0
- package/dist/structured-codex-adapter.d.ts +3 -0
- package/dist/structured-codex-adapter.js +29 -0
- package/dist/structured-opencode-adapter.d.ts +11 -0
- package/dist/structured-opencode-adapter.js +115 -0
- package/dist/structured-provider-common.d.ts +11 -0
- package/dist/structured-provider-common.js +77 -0
- package/dist/structured-session-manager.d.ts +32 -35
- package/dist/structured-session-manager.js +551 -605
- package/dist/types.d.ts +10 -0
- package/dist/update-helper.js +5 -1
- package/dist/web-ui/content/scripts.js +32 -32
- package/dist/web-ui/embedded-assets.d.ts +1 -1
- package/dist/web-ui/embedded-assets.js +2 -2
- package/dist/ws-broadcast.d.ts +16 -1
- package/dist/ws-broadcast.js +124 -58
- package/package.json +2 -1
package/dist/models.js
CHANGED
|
@@ -1,16 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
4
|
+
import { buildChildEnv } from "./env-utils.js";
|
|
3
5
|
import { extractSemver } from "./version-utils.js";
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
{
|
|
13
|
-
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
const CLAUDE_VERIFICATION_CACHE_KEY = "claude-model-verifications-v1";
|
|
8
|
+
const CLAUDE_VERIFICATION_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
9
|
+
const CLAUDE_PROBE_TIMEOUT_MS = 15_000;
|
|
10
|
+
const MAX_CLAUDE_MODEL_PROBES = 12;
|
|
11
|
+
const CLAUDE_PROBE_CONCURRENCY = 3;
|
|
12
|
+
const MODEL_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
13
|
+
const CLAUDE_BUILTIN_MODELS = [
|
|
14
|
+
{
|
|
15
|
+
id: "default",
|
|
16
|
+
label: "跟随 Claude Code 默认",
|
|
17
|
+
alias: true,
|
|
18
|
+
source: "builtin",
|
|
19
|
+
availability: "default",
|
|
20
|
+
note: "不传 --model 参数",
|
|
21
|
+
},
|
|
22
|
+
{ id: "opus", label: "opus(最新 Opus)", alias: true, source: "builtin", availability: "candidate" },
|
|
23
|
+
{ id: "sonnet", label: "sonnet(最新 Sonnet)", alias: true, source: "builtin", availability: "candidate" },
|
|
24
|
+
{ id: "haiku", label: "haiku(最新 Haiku)", alias: true, source: "builtin", availability: "candidate" },
|
|
14
25
|
];
|
|
15
26
|
const CODEX_FALLBACK_MODELS = [
|
|
16
27
|
{ id: "default", label: "GPT-5.5 · gpt-5.5(Codex 默认)", alias: true },
|
|
@@ -19,40 +30,295 @@ const OPENCODE_FALLBACK_MODELS = [
|
|
|
19
30
|
{ id: "default", label: "跟随 OpenCode 默认", alias: true },
|
|
20
31
|
];
|
|
21
32
|
let cache = null;
|
|
22
|
-
function
|
|
23
|
-
return
|
|
33
|
+
function cloneModels(models) {
|
|
34
|
+
return models.map((model) => ({ ...model }));
|
|
24
35
|
}
|
|
25
|
-
|
|
36
|
+
function defaultCommandRunner(file, args, options) {
|
|
37
|
+
return execFileAsync(file, args, {
|
|
38
|
+
env: options.env,
|
|
39
|
+
timeout: options.timeout,
|
|
40
|
+
maxBuffer: 1024 * 1024,
|
|
41
|
+
}).then(({ stdout, stderr }) => ({ stdout: String(stdout), stderr: String(stderr) }));
|
|
42
|
+
}
|
|
43
|
+
function resolveProbeEnv(options) {
|
|
44
|
+
return options.env ?? buildChildEnv(options.inheritEnv !== false);
|
|
45
|
+
}
|
|
46
|
+
function normalizeClaudeModelId(value) {
|
|
47
|
+
if (typeof value !== "string")
|
|
48
|
+
return null;
|
|
49
|
+
const id = value.trim();
|
|
50
|
+
return MODEL_ID_PATTERN.test(id) ? id : null;
|
|
51
|
+
}
|
|
52
|
+
function formatClaudeModelLabel(id, displayName) {
|
|
53
|
+
const name = displayName?.trim();
|
|
54
|
+
return name && name !== id ? `${name} · ${id}` : id;
|
|
55
|
+
}
|
|
56
|
+
function sourcePriority(source) {
|
|
57
|
+
switch (source) {
|
|
58
|
+
case "configured": return 4;
|
|
59
|
+
case "verified-cache": return 3;
|
|
60
|
+
case "models-api": return 2;
|
|
61
|
+
case "builtin": return 1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function loadClaudeVerifications(storage) {
|
|
65
|
+
if (!storage)
|
|
66
|
+
return [];
|
|
67
|
+
const raw = storage.getConfigValue(CLAUDE_VERIFICATION_CACHE_KEY);
|
|
68
|
+
if (!raw)
|
|
69
|
+
return [];
|
|
26
70
|
try {
|
|
27
|
-
const
|
|
71
|
+
const parsed = JSON.parse(raw);
|
|
72
|
+
if (parsed.version !== 1 || !Array.isArray(parsed.models))
|
|
73
|
+
return [];
|
|
74
|
+
const seen = new Set();
|
|
75
|
+
const models = [];
|
|
76
|
+
for (const entry of parsed.models) {
|
|
77
|
+
const id = normalizeClaudeModelId(entry?.id);
|
|
78
|
+
if (!id || seen.has(id) || typeof entry?.verifiedAt !== "string" || Number.isNaN(Date.parse(entry.verifiedAt))) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
seen.add(id);
|
|
82
|
+
models.push({
|
|
83
|
+
id,
|
|
84
|
+
...(typeof entry.label === "string" && entry.label.trim() ? { label: entry.label.trim() } : {}),
|
|
85
|
+
verifiedAt: entry.verifiedAt,
|
|
86
|
+
claudeVersion: typeof entry.claudeVersion === "string" && entry.claudeVersion.trim()
|
|
87
|
+
? entry.claudeVersion.trim()
|
|
88
|
+
: null,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return models;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function saveClaudeVerifications(storage, models) {
|
|
98
|
+
if (!storage)
|
|
99
|
+
return;
|
|
100
|
+
const sorted = [...models].sort((a, b) => a.id.localeCompare(b.id));
|
|
101
|
+
storage.setConfigValue(CLAUDE_VERIFICATION_CACHE_KEY, JSON.stringify({ version: 1, models: sorted }));
|
|
102
|
+
}
|
|
103
|
+
function verificationAvailability(verification, claudeVersion, now) {
|
|
104
|
+
if (!verification)
|
|
105
|
+
return "candidate";
|
|
106
|
+
const isFresh = now.getTime() - Date.parse(verification.verifiedAt) <= CLAUDE_VERIFICATION_TTL_MS;
|
|
107
|
+
const versionMatches = !claudeVersion || !verification.claudeVersion || claudeVersion === verification.claudeVersion;
|
|
108
|
+
return isFresh && versionMatches ? "verified" : "stale";
|
|
109
|
+
}
|
|
110
|
+
function candidateNote(candidate, availability, verification) {
|
|
111
|
+
if (availability === "verified")
|
|
112
|
+
return "已由 Claude Code 验证";
|
|
113
|
+
if (availability === "stale" && verification)
|
|
114
|
+
return `上次由 Claude Code 验证:${verification.verifiedAt}`;
|
|
115
|
+
if (candidate.source === "models-api")
|
|
116
|
+
return "API 目录候选,尚未验证 Claude Code 可用性";
|
|
117
|
+
if (candidate.source === "configured")
|
|
118
|
+
return "已配置,尚未验证 Claude Code 可用性";
|
|
119
|
+
return "尚未验证 Claude Code 可用性";
|
|
120
|
+
}
|
|
121
|
+
function candidateFromModel(model) {
|
|
122
|
+
const id = normalizeClaudeModelId(model.id);
|
|
123
|
+
if (!id || id === "default")
|
|
124
|
+
return null;
|
|
125
|
+
const source = model.source === "configured" || model.source === "verified-cache" || model.source === "models-api"
|
|
126
|
+
? model.source
|
|
127
|
+
: "builtin";
|
|
128
|
+
return { id, label: model.label || id, alias: model.alias, source };
|
|
129
|
+
}
|
|
130
|
+
function buildClaudeModels(options) {
|
|
131
|
+
const candidates = new Map();
|
|
132
|
+
const add = (candidate) => {
|
|
133
|
+
const id = normalizeClaudeModelId(candidate.id);
|
|
134
|
+
if (!id || id === "default")
|
|
135
|
+
return;
|
|
136
|
+
const normalized = { ...candidate, id, label: candidate.label || id };
|
|
137
|
+
const existing = candidates.get(id);
|
|
138
|
+
if (!existing || sourcePriority(normalized.source) >= sourcePriority(existing.source)) {
|
|
139
|
+
candidates.set(id, normalized);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
for (const model of CLAUDE_BUILTIN_MODELS) {
|
|
143
|
+
const candidate = candidateFromModel(model);
|
|
144
|
+
if (candidate)
|
|
145
|
+
add(candidate);
|
|
146
|
+
}
|
|
147
|
+
for (const model of options.existingModels ?? []) {
|
|
148
|
+
const candidate = candidateFromModel(model);
|
|
149
|
+
if (candidate)
|
|
150
|
+
add(candidate);
|
|
151
|
+
}
|
|
152
|
+
for (const model of options.apiModels ?? []) {
|
|
153
|
+
const id = normalizeClaudeModelId(model.id);
|
|
154
|
+
if (id)
|
|
155
|
+
add({ id, label: formatClaudeModelLabel(id, model.display_name), source: "models-api" });
|
|
156
|
+
}
|
|
157
|
+
for (const verification of options.verifications) {
|
|
158
|
+
add({ id: verification.id, label: verification.label || verification.id, source: "verified-cache" });
|
|
159
|
+
}
|
|
160
|
+
for (const value of options.configuredClaudeModels ?? []) {
|
|
161
|
+
const id = normalizeClaudeModelId(value);
|
|
162
|
+
if (id && id !== "default")
|
|
163
|
+
add({ id, label: id, source: "configured" });
|
|
164
|
+
}
|
|
165
|
+
const verificationById = new Map(options.verifications.map((entry) => [entry.id, entry]));
|
|
166
|
+
const models = [cloneModels(CLAUDE_BUILTIN_MODELS)[0]];
|
|
167
|
+
for (const candidate of candidates.values()) {
|
|
168
|
+
const verification = verificationById.get(candidate.id);
|
|
169
|
+
const availability = verificationAvailability(verification, options.claudeVersion, options.now);
|
|
170
|
+
models.push({
|
|
171
|
+
id: candidate.id,
|
|
172
|
+
label: candidate.label,
|
|
173
|
+
...(candidate.alias ? { alias: true } : {}),
|
|
174
|
+
source: candidate.source,
|
|
175
|
+
availability,
|
|
176
|
+
...(verification ? {
|
|
177
|
+
lastVerifiedAt: verification.verifiedAt,
|
|
178
|
+
...(verification.claudeVersion ? { verifiedWithClaudeVersion: verification.claudeVersion } : {}),
|
|
179
|
+
} : {}),
|
|
180
|
+
...(candidateNote(candidate, availability, verification) ? { note: candidateNote(candidate, availability, verification) } : {}),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return models;
|
|
184
|
+
}
|
|
185
|
+
function createInitialCache(options) {
|
|
186
|
+
const now = options.now?.() ?? new Date();
|
|
187
|
+
return {
|
|
188
|
+
models: buildClaudeModels({
|
|
189
|
+
configuredClaudeModels: options.configuredClaudeModels,
|
|
190
|
+
verifications: loadClaudeVerifications(options.storage),
|
|
191
|
+
claudeVersion: null,
|
|
192
|
+
now,
|
|
193
|
+
}),
|
|
194
|
+
codexModels: cloneModels(CODEX_FALLBACK_MODELS),
|
|
195
|
+
opencodeModels: cloneModels(OPENCODE_FALLBACK_MODELS),
|
|
196
|
+
claudeVersion: null,
|
|
197
|
+
opencodeVersion: null,
|
|
198
|
+
refreshedAt: now.toISOString(),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function refreshCachedClaudeModels(options) {
|
|
202
|
+
if (!cache)
|
|
203
|
+
return;
|
|
204
|
+
const now = options.now?.() ?? new Date();
|
|
205
|
+
cache.models = buildClaudeModels({
|
|
206
|
+
configuredClaudeModels: options.configuredClaudeModels,
|
|
207
|
+
existingModels: cache.models,
|
|
208
|
+
verifications: loadClaudeVerifications(options.storage),
|
|
209
|
+
claudeVersion: cache.claudeVersion,
|
|
210
|
+
now,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
async function probeClaudeVersion(runner, env) {
|
|
214
|
+
try {
|
|
215
|
+
const { stdout } = await runner("claude", ["--version"], { env, timeout: 5000 });
|
|
28
216
|
return extractSemver(stdout) ?? (stdout.trim().slice(0, 64) || null);
|
|
29
217
|
}
|
|
30
218
|
catch {
|
|
31
219
|
return null;
|
|
32
220
|
}
|
|
33
221
|
}
|
|
34
|
-
async function
|
|
222
|
+
async function probeClaudeModel(id, runner, env) {
|
|
223
|
+
try {
|
|
224
|
+
await runner("claude", ["--model", id, "-p", "Reply with exactly: ok"], {
|
|
225
|
+
env,
|
|
226
|
+
timeout: CLAUDE_PROBE_TIMEOUT_MS,
|
|
227
|
+
});
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async function probeCodexModels(runner, env) {
|
|
35
235
|
try {
|
|
36
|
-
const { stdout } = await
|
|
236
|
+
const { stdout } = await runner("codex", ["debug", "models"], { env, timeout: 8000 });
|
|
37
237
|
return parseCodexModels(stdout);
|
|
38
238
|
}
|
|
39
239
|
catch {
|
|
40
|
-
return CODEX_FALLBACK_MODELS
|
|
240
|
+
return cloneModels(CODEX_FALLBACK_MODELS);
|
|
41
241
|
}
|
|
42
242
|
}
|
|
43
|
-
async function probeOpenCode() {
|
|
243
|
+
async function probeOpenCode(runner, env) {
|
|
44
244
|
const [modelsResult, versionResult] = await Promise.allSettled([
|
|
45
|
-
|
|
46
|
-
|
|
245
|
+
runner("opencode", ["models"], { env, timeout: 8000 }),
|
|
246
|
+
runner("opencode", ["--version"], { env, timeout: 5000 }),
|
|
47
247
|
]);
|
|
48
248
|
const models = modelsResult.status === "fulfilled"
|
|
49
249
|
? parseOpenCodeModels(modelsResult.value.stdout)
|
|
50
|
-
: OPENCODE_FALLBACK_MODELS
|
|
250
|
+
: cloneModels(OPENCODE_FALLBACK_MODELS);
|
|
51
251
|
const version = versionResult.status === "fulfilled"
|
|
52
252
|
? extractSemver(versionResult.value.stdout) ?? (versionResult.value.stdout.trim().slice(0, 64) || null)
|
|
53
253
|
: null;
|
|
54
254
|
return { models, version };
|
|
55
255
|
}
|
|
256
|
+
function createOfficialModelsApi(apiKey) {
|
|
257
|
+
const client = new Anthropic({ apiKey });
|
|
258
|
+
return {
|
|
259
|
+
list: () => client.models.list({ limit: 100 }),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
async function listClaudeModelsFromApi(options, env) {
|
|
263
|
+
const apiKey = options.apiKey?.trim() || env.ANTHROPIC_API_KEY?.trim();
|
|
264
|
+
if (!apiKey)
|
|
265
|
+
return [];
|
|
266
|
+
try {
|
|
267
|
+
const api = options.modelsApi ?? createOfficialModelsApi(apiKey);
|
|
268
|
+
const models = [];
|
|
269
|
+
for await (const model of api.list()) {
|
|
270
|
+
const id = normalizeClaudeModelId(model?.id);
|
|
271
|
+
if (id)
|
|
272
|
+
models.push({ id, ...(typeof model.display_name === "string" ? { display_name: model.display_name } : {}) });
|
|
273
|
+
}
|
|
274
|
+
return models;
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
return [];
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function probePriority(model) {
|
|
281
|
+
if (model.source === "configured")
|
|
282
|
+
return 0;
|
|
283
|
+
if (model.availability === "stale")
|
|
284
|
+
return 1;
|
|
285
|
+
if (model.source === "verified-cache")
|
|
286
|
+
return 2;
|
|
287
|
+
if (model.source === "builtin")
|
|
288
|
+
return 3;
|
|
289
|
+
return 4;
|
|
290
|
+
}
|
|
291
|
+
async function verifyClaudeCandidates(models, runner, env) {
|
|
292
|
+
const candidates = models
|
|
293
|
+
.filter((model) => model.id !== "default" && model.availability !== "verified")
|
|
294
|
+
.sort((a, b) => probePriority(a) - probePriority(b) || a.id.localeCompare(b.id))
|
|
295
|
+
.slice(0, MAX_CLAUDE_MODEL_PROBES);
|
|
296
|
+
const verified = new Set();
|
|
297
|
+
let nextIndex = 0;
|
|
298
|
+
const worker = async () => {
|
|
299
|
+
while (nextIndex < candidates.length) {
|
|
300
|
+
const candidate = candidates[nextIndex++];
|
|
301
|
+
if (candidate && await probeClaudeModel(candidate.id, runner, env)) {
|
|
302
|
+
verified.add(candidate.id);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
};
|
|
306
|
+
await Promise.all(Array.from({ length: Math.min(CLAUDE_PROBE_CONCURRENCY, candidates.length) }, worker));
|
|
307
|
+
return verified;
|
|
308
|
+
}
|
|
309
|
+
function mergeVerifications(previous, models, verifiedIds, claudeVersion, now) {
|
|
310
|
+
const byId = new Map(previous.map((entry) => [entry.id, entry]));
|
|
311
|
+
for (const id of verifiedIds) {
|
|
312
|
+
const model = models.find((entry) => entry.id === id);
|
|
313
|
+
byId.set(id, {
|
|
314
|
+
id,
|
|
315
|
+
...(model?.label ? { label: model.label } : {}),
|
|
316
|
+
verifiedAt: now.toISOString(),
|
|
317
|
+
claudeVersion,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
return [...byId.values()];
|
|
321
|
+
}
|
|
56
322
|
/** Parse `opencode models`, whose stable machine-friendly output is one provider/model id per line. */
|
|
57
323
|
export function parseOpenCodeModels(stdout) {
|
|
58
324
|
const ids = Array.from(new Set(stdout
|
|
@@ -60,7 +326,7 @@ export function parseOpenCodeModels(stdout) {
|
|
|
60
326
|
.map((line) => line.replace(/\x1b\[[0-?]*[ -\/]*[@-~]/g, "").trim())
|
|
61
327
|
.filter((line) => /^[a-z0-9][a-z0-9._-]*\/[a-z0-9][a-z0-9._:/-]*$/i.test(line))));
|
|
62
328
|
if (!ids.length)
|
|
63
|
-
return OPENCODE_FALLBACK_MODELS
|
|
329
|
+
return cloneModels(OPENCODE_FALLBACK_MODELS);
|
|
64
330
|
return [
|
|
65
331
|
{ id: "default", label: "跟随 OpenCode 默认", alias: true },
|
|
66
332
|
...ids.map((id) => ({ id, label: id })),
|
|
@@ -71,11 +337,11 @@ export function parseCodexModels(stdout) {
|
|
|
71
337
|
try {
|
|
72
338
|
const data = JSON.parse(stdout);
|
|
73
339
|
const visible = (Array.isArray(data.models) ? data.models : [])
|
|
74
|
-
.filter((
|
|
75
|
-
.filter((
|
|
340
|
+
.filter((model) => typeof model.slug === "string" && model.slug.length > 0)
|
|
341
|
+
.filter((model) => model.visibility === "list")
|
|
76
342
|
.sort((a, b) => (a.priority ?? 99) - (b.priority ?? 99));
|
|
77
343
|
if (!visible.length)
|
|
78
|
-
return CODEX_FALLBACK_MODELS
|
|
344
|
+
return cloneModels(CODEX_FALLBACK_MODELS);
|
|
79
345
|
const defaultModel = visible[0];
|
|
80
346
|
const defaultLabel = formatCodexModelLabel(defaultModel);
|
|
81
347
|
const result = [
|
|
@@ -86,17 +352,17 @@ export function parseCodexModels(stdout) {
|
|
|
86
352
|
...codexReasoningMetadata(defaultModel),
|
|
87
353
|
},
|
|
88
354
|
];
|
|
89
|
-
for (const
|
|
355
|
+
for (const model of visible) {
|
|
90
356
|
result.push({
|
|
91
|
-
id:
|
|
92
|
-
label: formatCodexModelLabel(
|
|
93
|
-
...codexReasoningMetadata(
|
|
357
|
+
id: model.slug,
|
|
358
|
+
label: formatCodexModelLabel(model),
|
|
359
|
+
...codexReasoningMetadata(model),
|
|
94
360
|
});
|
|
95
361
|
}
|
|
96
362
|
return result;
|
|
97
363
|
}
|
|
98
364
|
catch {
|
|
99
|
-
return CODEX_FALLBACK_MODELS
|
|
365
|
+
return cloneModels(CODEX_FALLBACK_MODELS);
|
|
100
366
|
}
|
|
101
367
|
}
|
|
102
368
|
function codexReasoningMetadata(model) {
|
|
@@ -118,32 +384,52 @@ function formatCodexModelLabel(model) {
|
|
|
118
384
|
? `${model.display_name} · ${model.slug}`
|
|
119
385
|
: model.slug;
|
|
120
386
|
}
|
|
121
|
-
export function getCachedModels() {
|
|
387
|
+
export function getCachedModels(options = {}) {
|
|
122
388
|
if (!cache) {
|
|
123
|
-
cache =
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
claudeVersion: null,
|
|
128
|
-
opencodeVersion: null,
|
|
129
|
-
refreshedAt: new Date().toISOString(),
|
|
130
|
-
};
|
|
389
|
+
cache = createInitialCache(options);
|
|
390
|
+
}
|
|
391
|
+
else if (options.storage || options.configuredClaudeModels) {
|
|
392
|
+
refreshCachedClaudeModels(options);
|
|
131
393
|
}
|
|
132
394
|
return cache;
|
|
133
395
|
}
|
|
134
|
-
export async function refreshModels() {
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
396
|
+
export async function refreshModels(options = {}) {
|
|
397
|
+
const now = options.now?.() ?? new Date();
|
|
398
|
+
const env = resolveProbeEnv(options);
|
|
399
|
+
const runner = options.commandRunner ?? defaultCommandRunner;
|
|
400
|
+
const [claudeVersion, codexModels, opencode, apiModels] = await Promise.all([
|
|
401
|
+
probeClaudeVersion(runner, env),
|
|
402
|
+
probeCodexModels(runner, env),
|
|
403
|
+
probeOpenCode(runner, env),
|
|
404
|
+
listClaudeModelsFromApi(options, env),
|
|
139
405
|
]);
|
|
406
|
+
const priorVerifications = loadClaudeVerifications(options.storage);
|
|
407
|
+
const initialModels = buildClaudeModels({
|
|
408
|
+
configuredClaudeModels: options.configuredClaudeModels,
|
|
409
|
+
apiModels,
|
|
410
|
+
verifications: priorVerifications,
|
|
411
|
+
claudeVersion,
|
|
412
|
+
now,
|
|
413
|
+
});
|
|
414
|
+
const verifiedIds = options.verifyClaudeCandidates
|
|
415
|
+
? await verifyClaudeCandidates(initialModels, runner, env)
|
|
416
|
+
: new Set();
|
|
417
|
+
const verifications = mergeVerifications(priorVerifications, initialModels, verifiedIds, claudeVersion, now);
|
|
418
|
+
if (verifiedIds.size > 0)
|
|
419
|
+
saveClaudeVerifications(options.storage, verifications);
|
|
140
420
|
cache = {
|
|
141
|
-
models:
|
|
421
|
+
models: buildClaudeModels({
|
|
422
|
+
configuredClaudeModels: options.configuredClaudeModels,
|
|
423
|
+
apiModels,
|
|
424
|
+
verifications,
|
|
425
|
+
claudeVersion,
|
|
426
|
+
now,
|
|
427
|
+
}),
|
|
142
428
|
codexModels,
|
|
143
429
|
opencodeModels: opencode.models,
|
|
144
|
-
claudeVersion
|
|
430
|
+
claudeVersion,
|
|
145
431
|
opencodeVersion: opencode.version,
|
|
146
|
-
refreshedAt:
|
|
432
|
+
refreshedAt: now.toISOString(),
|
|
147
433
|
};
|
|
148
434
|
return cache;
|
|
149
435
|
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { EventEmitter } from "node:events";
|
|
2
2
|
import { WandStorage } from "./storage.js";
|
|
3
3
|
import { ExecutionMode, ProcessEventHandler, SessionProvider, SessionSnapshot, SessionSource, WandConfig } from "./types.js";
|
|
4
|
+
import { type PermissionResolution } from "./claude-pty-bridge.js";
|
|
5
|
+
import { type ClaudeHistorySession, type CodexHistorySession } from "./provider-history-scanner.js";
|
|
6
|
+
export type { ClaudeHistorySession, CodexHistorySession } from "./provider-history-scanner.js";
|
|
7
|
+
/** Exported for focused policy tests. */
|
|
8
|
+
export declare function isCommandAllowedByPrefixes(command: string, allowedPrefixes: readonly string[]): boolean;
|
|
4
9
|
export type { ProcessEvent, ProcessEventHandler } from "./types.js";
|
|
5
10
|
export declare class SessionInputError extends Error {
|
|
6
11
|
readonly code: "SESSION_NOT_FOUND" | "SESSION_NOT_RUNNING" | "SESSION_NO_PTY";
|
|
@@ -8,49 +13,30 @@ export declare class SessionInputError extends Error {
|
|
|
8
13
|
readonly sessionStatus?: SessionSnapshot["status"] | undefined;
|
|
9
14
|
constructor(message: string, code: "SESSION_NOT_FOUND" | "SESSION_NOT_RUNNING" | "SESSION_NO_PTY", sessionId: string, sessionStatus?: SessionSnapshot["status"] | undefined);
|
|
10
15
|
}
|
|
11
|
-
/** A Claude Code session discovered by scanning ~/.claude/projects/ directories. */
|
|
12
|
-
export interface ClaudeHistorySession {
|
|
13
|
-
claudeSessionId: string;
|
|
14
|
-
projectDir: string;
|
|
15
|
-
cwd: string;
|
|
16
|
-
firstUserMessage: string;
|
|
17
|
-
timestamp: string;
|
|
18
|
-
mtimeMs: number;
|
|
19
|
-
hasConversation: boolean;
|
|
20
|
-
managedByWand: boolean;
|
|
21
|
-
}
|
|
22
|
-
/** A Codex session discovered by scanning ~/.codex/sessions/ rollout files. */
|
|
23
|
-
export interface CodexHistorySession {
|
|
24
|
-
/** Codex thread id(存进 claudeSessionId 字段以复用前端/路由)。 */
|
|
25
|
-
claudeSessionId: string;
|
|
26
|
-
cwd: string;
|
|
27
|
-
firstUserMessage: string;
|
|
28
|
-
firstUserAt: string;
|
|
29
|
-
timestamp: string;
|
|
30
|
-
mtimeMs: number;
|
|
31
|
-
hasUser: boolean;
|
|
32
|
-
hasConversation: boolean;
|
|
33
|
-
managedByWand: boolean;
|
|
34
|
-
provider: "codex";
|
|
35
|
-
}
|
|
36
16
|
export declare class ProcessManager extends EventEmitter {
|
|
37
17
|
private readonly config;
|
|
38
18
|
private readonly storage;
|
|
39
19
|
private readonly sessions;
|
|
40
20
|
private readonly logger;
|
|
21
|
+
private readonly providerHistory;
|
|
41
22
|
/** 24h archive scan timer */
|
|
42
23
|
private archiveTimer;
|
|
43
24
|
/** Per-session debounce timers for throttled persist calls */
|
|
44
25
|
private readonly persistDebounceTimers;
|
|
45
26
|
/** Last persisted message state per session — used to skip redundant message writes */
|
|
46
27
|
private readonly lastPersistedMessageState;
|
|
28
|
+
/** Columns that changed since the last per-session checkpoint. */
|
|
29
|
+
private readonly dirtySessions;
|
|
47
30
|
/** 启动时被识别为孤儿 PTY 并标记为 exited 的旧会话数(旧服务器进程已死) */
|
|
48
31
|
private orphanRecoveredCount;
|
|
49
32
|
private readonly topicRequests;
|
|
33
|
+
private disposed;
|
|
50
34
|
constructor(config: WandConfig, storage: WandStorage, configDir?: string);
|
|
51
35
|
on(_event: "process", listener: ProcessEventHandler): this;
|
|
52
36
|
/** 启动时被识别为孤儿 PTY 并标记为 exited 的旧会话数量(仅用于启动摘要展示)。 */
|
|
53
37
|
getOrphanRecoveredCount(): number;
|
|
38
|
+
/** Stop all live work and flush pending state before storage is closed. */
|
|
39
|
+
dispose(): void;
|
|
54
40
|
private emitEvent;
|
|
55
41
|
private cleanupOldSessions;
|
|
56
42
|
start(command: string, cwd: string | undefined, mode: ExecutionMode, initialInput?: string, opts?: {
|
|
@@ -70,20 +56,19 @@ export declare class ProcessManager extends EventEmitter {
|
|
|
70
56
|
/** Return lightweight snapshots for the session list (no output/messages). */
|
|
71
57
|
listSlim(): SessionSnapshot[];
|
|
72
58
|
hasClaudeSessionFile(cwd: string, claudeSessionId: string): boolean;
|
|
73
|
-
private claudeHistoryCache;
|
|
74
|
-
private static readonly HISTORY_CACHE_TTL_MS;
|
|
75
59
|
listClaudeHistorySessions(): ClaudeHistorySession[];
|
|
76
60
|
deleteClaudeHistoryFiles(sessions: {
|
|
77
61
|
claudeSessionId: string;
|
|
78
62
|
cwd: string;
|
|
79
63
|
}[]): number;
|
|
80
|
-
private codexHistoryCache;
|
|
81
64
|
listCodexHistorySessions(): CodexHistorySession[];
|
|
82
65
|
hasCodexSessionFile(threadId: string): boolean;
|
|
83
66
|
deleteCodexHistoryFiles(threadIds: string[]): number;
|
|
84
67
|
private captureCodexSessionId;
|
|
85
68
|
private captureClaudeSessionId;
|
|
86
69
|
get(id: string): SessionSnapshot | null;
|
|
70
|
+
/** Return only a session owned by this manager, without the SQLite fallback used by get(). */
|
|
71
|
+
getOwned(id: string): SessionSnapshot | null;
|
|
87
72
|
getPtyTranscript(id: string): string | null;
|
|
88
73
|
/**
|
|
89
74
|
* Set the Claude model for an existing PTY session. Persists the selection
|
|
@@ -116,9 +101,15 @@ export declare class ProcessManager extends EventEmitter {
|
|
|
116
101
|
private snapshotSlim;
|
|
117
102
|
private isPermissionBlocked;
|
|
118
103
|
setSessionTopic(id: string, title: string, description: string): SessionSnapshot;
|
|
104
|
+
/**
|
|
105
|
+
* Persist worktree merge progress through the manager that owns the live
|
|
106
|
+
* session record. Returning null lets callers fall back to another owner (or
|
|
107
|
+
* directly to storage for a row that is not currently loaded by a manager).
|
|
108
|
+
*/
|
|
109
|
+
setWorktreeMergeState(id: string, status: SessionSnapshot["worktreeMergeStatus"], info: SessionSnapshot["worktreeMergeInfo"]): SessionSnapshot | null;
|
|
119
110
|
private maybeGenerateSessionTopic;
|
|
120
111
|
private defaultAutonomyPolicy;
|
|
121
|
-
resolveEscalation(id: string, requestId: string, resolution?:
|
|
112
|
+
resolveEscalation(id: string, requestId: string, resolution?: PermissionResolution): SessionSnapshot;
|
|
122
113
|
approvePermission(id: string): SessionSnapshot;
|
|
123
114
|
denyPermission(id: string): SessionSnapshot;
|
|
124
115
|
toggleAutoApprove(id: string): SessionSnapshot;
|
|
@@ -128,8 +119,9 @@ export declare class ProcessManager extends EventEmitter {
|
|
|
128
119
|
* @param resolution - "approve_once", "approve_turn", or "deny"
|
|
129
120
|
* @param requestId - Optional escalation request ID for validation
|
|
130
121
|
*/
|
|
131
|
-
resolvePermission(id: string, resolution:
|
|
122
|
+
resolvePermission(id: string, resolution: PermissionResolution, requestId?: string): SessionSnapshot;
|
|
132
123
|
private persist;
|
|
124
|
+
private markDirty;
|
|
133
125
|
/**
|
|
134
126
|
* Schedule a debounced persist call for the given record.
|
|
135
127
|
* Multiple calls within the debounce window are coalesced into a single write.
|