@co0ontty/wand 4.52.0 → 4.53.0-beta.gdd763b2
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/distribution-manager.js +2 -2
- package/dist/ios-ota.d.ts +42 -0
- package/dist/ios-ota.js +440 -0
- package/dist/models.d.ts +8 -0
- package/dist/models.js +47 -6
- package/dist/process-manager.js +4 -2
- package/dist/server-session-routes.js +8 -1
- package/dist/server-update-routes.js +135 -1
- package/dist/server-workspace-routes.js +40 -17
- package/dist/session-transport.js +2 -2
- package/dist/web-ui/content/scripts.js +67 -67
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/embedded-assets.js +2 -2
- package/dist/ws-broadcast.js +29 -8
- package/package.json +1 -1
|
@@ -3,8 +3,9 @@ import { pipeline } from "node:stream/promises";
|
|
|
3
3
|
import { getErrorMessage } from "./error-utils.js";
|
|
4
4
|
import { asyncRoute } from "./express-async.js";
|
|
5
5
|
import { checkProviderCliUpdates, updateProviderClis, verifyProviderCliUpdateResults, } from "./provider-cli-updater.js";
|
|
6
|
+
import { absoluteUrl, buildIosInstallPage, buildIosOtaManifest, buildItmsServicesUrl, collectIosOtaBlockers, formatIosAssetSize, inspectIpa, normalizePublicOrigin, publicOriginFromRequest, } from "./ios-ota.js";
|
|
6
7
|
import { streamFileWithRange } from "./server-file-routes.js";
|
|
7
|
-
import { compareApkInstallOrder, compareSemver } from "./version-utils.js";
|
|
8
|
+
import { compareApkInstallOrder, compareSemver, compareWandInstallOrder } from "./version-utils.js";
|
|
8
9
|
import { canUseDetachedUpdateHelper, startDetachedUpdateHelper } from "./update-helper.js";
|
|
9
10
|
const GITHUB_PROXY_TIMEOUT_MS = 5 * 60 * 1000;
|
|
10
11
|
async function proxyGitHubApk(_req, res, asset, fetchImpl = fetch) {
|
|
@@ -160,6 +161,78 @@ export function registerPublicUpdateRoutes(app, deps) {
|
|
|
160
161
|
readErrorMessage: "读取 DMG 文件失败。",
|
|
161
162
|
});
|
|
162
163
|
}));
|
|
164
|
+
app.get("/api/ios-ipa-update", asyncRoute(async (req, res) => {
|
|
165
|
+
const currentVersion = typeof req.query.currentVersion === "string" ? req.query.currentVersion.trim() : "";
|
|
166
|
+
if (!currentVersion) {
|
|
167
|
+
res.status(400).json({ error: "Missing currentVersion query parameter." });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const payload = await resolveIosOtaPayload(req, deps);
|
|
171
|
+
if (!payload) {
|
|
172
|
+
res.json({
|
|
173
|
+
updateAvailable: false,
|
|
174
|
+
currentVersion,
|
|
175
|
+
latestVersion: null,
|
|
176
|
+
downloadUrl: null,
|
|
177
|
+
installUrl: null,
|
|
178
|
+
manifestUrl: null,
|
|
179
|
+
installPageUrl: null,
|
|
180
|
+
fileName: null,
|
|
181
|
+
size: null,
|
|
182
|
+
source: null,
|
|
183
|
+
signed: false,
|
|
184
|
+
otaReady: false,
|
|
185
|
+
otaBlockers: [],
|
|
186
|
+
bundleId: null,
|
|
187
|
+
});
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const updateAvailable = compareWandInstallOrder(payload.latestVersion, currentVersion) > 0;
|
|
191
|
+
res.json({
|
|
192
|
+
updateAvailable,
|
|
193
|
+
currentVersion,
|
|
194
|
+
latestVersion: payload.latestVersion,
|
|
195
|
+
downloadUrl: updateAvailable ? payload.downloadUrl : null,
|
|
196
|
+
installUrl: updateAvailable ? payload.installUrl : null,
|
|
197
|
+
manifestUrl: payload.manifestUrl,
|
|
198
|
+
installPageUrl: payload.installPageUrl,
|
|
199
|
+
fileName: updateAvailable ? payload.fileName : null,
|
|
200
|
+
size: updateAvailable ? payload.size : null,
|
|
201
|
+
source: payload.source,
|
|
202
|
+
signed: payload.signed,
|
|
203
|
+
otaReady: payload.otaReady,
|
|
204
|
+
otaBlockers: payload.otaBlockers,
|
|
205
|
+
bundleId: payload.bundleId,
|
|
206
|
+
});
|
|
207
|
+
}));
|
|
208
|
+
app.get("/ios/manifest.plist", asyncRoute(async (req, res) => {
|
|
209
|
+
const payload = await resolveIosOtaPayload(req, deps);
|
|
210
|
+
if (!payload) {
|
|
211
|
+
res.status(404).type("text/plain").send("当前没有可安装的 IPA 文件。");
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
res.setHeader("Content-Type", "text/xml; charset=utf-8");
|
|
215
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
216
|
+
res.send(payload.manifest);
|
|
217
|
+
}));
|
|
218
|
+
app.get("/ios/install", asyncRoute(async (req, res) => {
|
|
219
|
+
const payload = await resolveIosOtaPayload(req, deps);
|
|
220
|
+
if (!payload) {
|
|
221
|
+
res.status(404).type("text/plain").send("当前没有可安装的 IPA 文件。");
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
res.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
225
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
226
|
+
res.send(buildIosInstallPage({
|
|
227
|
+
title: payload.title,
|
|
228
|
+
version: payload.latestVersion,
|
|
229
|
+
sizeLabel: formatIosAssetSize(payload.size),
|
|
230
|
+
installUrl: payload.installUrl ?? "#",
|
|
231
|
+
downloadUrl: payload.downloadUrl,
|
|
232
|
+
signed: payload.signed,
|
|
233
|
+
https: payload.origin.startsWith("https://"),
|
|
234
|
+
}));
|
|
235
|
+
}));
|
|
163
236
|
app.get("/ios/download", asyncRoute(async (req, res) => {
|
|
164
237
|
const asset = await deps.resolveIosDownload();
|
|
165
238
|
if (!asset) {
|
|
@@ -175,6 +248,67 @@ export function registerPublicUpdateRoutes(app, deps) {
|
|
|
175
248
|
});
|
|
176
249
|
}));
|
|
177
250
|
}
|
|
251
|
+
async function resolveIosOtaPayload(req, deps) {
|
|
252
|
+
const latest = await deps.resolveLatestIpa();
|
|
253
|
+
const local = await deps.resolveIosDownload();
|
|
254
|
+
if (!latest && !local)
|
|
255
|
+
return null;
|
|
256
|
+
const origin = normalizePublicOrigin(typeof req.query.origin === "string" ? req.query.origin : undefined)
|
|
257
|
+
?? publicOriginFromRequest(req)
|
|
258
|
+
?? "";
|
|
259
|
+
const source = local ? "local" : (latest?.source ?? "github");
|
|
260
|
+
const fileName = local?.fileName ?? latest?.fileName ?? "wand.ipa";
|
|
261
|
+
const size = local?.size ?? latest?.size ?? 0;
|
|
262
|
+
const latestVersion = latest?.version ?? "0.0.0";
|
|
263
|
+
const metadata = local
|
|
264
|
+
? await inspectIpa(local.filePath, { version: latestVersion }).catch(() => ({
|
|
265
|
+
bundleId: "com.wand.app",
|
|
266
|
+
bundleVersion: latestVersion,
|
|
267
|
+
bundleBuild: latestVersion,
|
|
268
|
+
title: "Wand",
|
|
269
|
+
signed: false,
|
|
270
|
+
}))
|
|
271
|
+
: {
|
|
272
|
+
bundleId: "com.wand.app",
|
|
273
|
+
bundleVersion: latestVersion,
|
|
274
|
+
bundleBuild: latestVersion,
|
|
275
|
+
title: "Wand",
|
|
276
|
+
signed: false,
|
|
277
|
+
};
|
|
278
|
+
const downloadPath = latest?.downloadUrl ?? "/ios/download";
|
|
279
|
+
const downloadUrl = origin ? absoluteUrl(origin, downloadPath) : downloadPath;
|
|
280
|
+
const manifestUrl = origin ? absoluteUrl(origin, "/ios/manifest.plist") : "/ios/manifest.plist";
|
|
281
|
+
const installPageUrl = origin ? absoluteUrl(origin, "/ios/install") : "/ios/install";
|
|
282
|
+
const blockers = collectIosOtaBlockers({
|
|
283
|
+
signed: metadata.signed,
|
|
284
|
+
origin,
|
|
285
|
+
source,
|
|
286
|
+
});
|
|
287
|
+
const otaReady = blockers.length === 0;
|
|
288
|
+
const manifest = buildIosOtaManifest({
|
|
289
|
+
ipaUrl: downloadUrl,
|
|
290
|
+
bundleId: metadata.bundleId,
|
|
291
|
+
bundleVersion: metadata.bundleVersion,
|
|
292
|
+
title: metadata.title,
|
|
293
|
+
});
|
|
294
|
+
return {
|
|
295
|
+
latestVersion: metadata.bundleVersion || latestVersion,
|
|
296
|
+
downloadUrl,
|
|
297
|
+
installUrl: origin ? buildItmsServicesUrl(manifestUrl) : null,
|
|
298
|
+
manifestUrl,
|
|
299
|
+
installPageUrl,
|
|
300
|
+
fileName,
|
|
301
|
+
size,
|
|
302
|
+
source,
|
|
303
|
+
signed: metadata.signed,
|
|
304
|
+
otaReady,
|
|
305
|
+
otaBlockers: blockers,
|
|
306
|
+
bundleId: metadata.bundleId,
|
|
307
|
+
title: metadata.title,
|
|
308
|
+
origin,
|
|
309
|
+
manifest,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
178
312
|
export class ServerUpdateState {
|
|
179
313
|
providerCliUpdateCache = null;
|
|
180
314
|
providerCliUpdateInFlight = false;
|
|
@@ -16,6 +16,29 @@ function workspaceSessionTitle(session, names = {}) {
|
|
|
16
16
|
cwd: session.cwd,
|
|
17
17
|
}));
|
|
18
18
|
}
|
|
19
|
+
function liveSession(session, registry) {
|
|
20
|
+
return registry?.getLatest(session.id) ?? session;
|
|
21
|
+
}
|
|
22
|
+
function workspaceSessionSummary(session, names = {}, registry) {
|
|
23
|
+
const live = liveSession(session, registry);
|
|
24
|
+
return {
|
|
25
|
+
id: live.id,
|
|
26
|
+
provider: live.provider,
|
|
27
|
+
sessionKind: live.sessionKind,
|
|
28
|
+
runner: live.runner,
|
|
29
|
+
command: live.command,
|
|
30
|
+
title: workspaceSessionTitle(live, names),
|
|
31
|
+
status: live.status,
|
|
32
|
+
ptyBusy: live.ptyBusy === true,
|
|
33
|
+
providerCliActive: live.providerCliActive,
|
|
34
|
+
inFlight: live.structuredState?.inFlight === true,
|
|
35
|
+
cwd: live.cwd,
|
|
36
|
+
startedAt: live.startedAt,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function tasksRevision(groups) {
|
|
40
|
+
return crypto.createHash("sha256").update(JSON.stringify(groups)).digest("base64url");
|
|
41
|
+
}
|
|
19
42
|
function parseDefaultProvider(value) {
|
|
20
43
|
return typeof value === "string" && PROVIDERS.has(value) ? value : undefined;
|
|
21
44
|
}
|
|
@@ -196,8 +219,8 @@ export function registerWorkspaceRoutes(app, storage, sessions) {
|
|
|
196
219
|
res.json({
|
|
197
220
|
...workspaceWithCounts(storage, workspace),
|
|
198
221
|
sessions: storage.listSessionsByWorkspace(workspace.id).map((session) => ({
|
|
199
|
-
...session,
|
|
200
|
-
|
|
222
|
+
...workspaceSessionSummary(session, { workspaceName: workspace.name }, sessions),
|
|
223
|
+
workspaceTaskId: session.workspaceTaskId,
|
|
201
224
|
})),
|
|
202
225
|
});
|
|
203
226
|
});
|
|
@@ -288,17 +311,7 @@ export function registerWorkspaceRoutes(app, storage, sessions) {
|
|
|
288
311
|
const sessionLimit = parseBoundedCount(req.query.maxSessions);
|
|
289
312
|
backfillSessionWorkspaces(storage);
|
|
290
313
|
const workspaces = storage.listWorkspaces();
|
|
291
|
-
const summarize = (session, names = {}) => (
|
|
292
|
-
id: session.id,
|
|
293
|
-
provider: session.provider,
|
|
294
|
-
sessionKind: session.sessionKind,
|
|
295
|
-
runner: session.runner,
|
|
296
|
-
command: session.command,
|
|
297
|
-
title: workspaceSessionTitle(session, names),
|
|
298
|
-
status: session.status,
|
|
299
|
-
cwd: session.cwd,
|
|
300
|
-
startedAt: session.startedAt,
|
|
301
|
-
});
|
|
314
|
+
const summarize = (session, names = {}) => workspaceSessionSummary(session, names, sessions);
|
|
302
315
|
const visibleWorkspaces = workspaceFilter
|
|
303
316
|
? workspaces.filter((workspace) => workspace.id === workspaceFilter)
|
|
304
317
|
: workspaces;
|
|
@@ -369,7 +382,17 @@ export function registerWorkspaceRoutes(app, storage, sessions) {
|
|
|
369
382
|
workspaceName: group.workspaceName,
|
|
370
383
|
}));
|
|
371
384
|
}
|
|
372
|
-
|
|
385
|
+
const payload = [...groups.values()];
|
|
386
|
+
const revision = tasksRevision(payload);
|
|
387
|
+
if (typeof req.query.revision === "string") {
|
|
388
|
+
if (req.query.revision === revision) {
|
|
389
|
+
res.json({ unchanged: true, revision, groups: [] });
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
res.json({ unchanged: false, revision, groups: payload });
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
res.json(payload);
|
|
373
396
|
});
|
|
374
397
|
// 列出某工作空间下的任务
|
|
375
398
|
app.get("/api/workspaces/:id/tasks", (req, res) => {
|
|
@@ -510,11 +533,11 @@ export function registerWorkspaceRoutes(app, storage, sessions) {
|
|
|
510
533
|
...task,
|
|
511
534
|
cwd: task.worktree?.path ?? workspace?.cwd ?? "",
|
|
512
535
|
sessions: storage.listSessionsByWorkspaceTask(task.id).map((session) => ({
|
|
513
|
-
...session,
|
|
514
|
-
title: workspaceSessionTitle(session, {
|
|
536
|
+
...workspaceSessionSummary(session, {
|
|
515
537
|
taskName: task.name,
|
|
516
538
|
workspaceName: workspace?.name,
|
|
517
|
-
}),
|
|
539
|
+
}, sessions),
|
|
540
|
+
workspaceTaskId: task.id,
|
|
518
541
|
})),
|
|
519
542
|
});
|
|
520
543
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { enrichStructuredMessages, WAND_PROTOCOL_VERSION } from "./structured-client-protocol.js";
|
|
2
|
-
import { shouldAcceptGeneratedSessionTitle, summarizeSessionTitleFromInput, } from "./session-topic.js";
|
|
2
|
+
import { collectSessionTopicBlocklist, shouldAcceptGeneratedSessionTitle, summarizeSessionTitleFromInput, } from "./session-topic.js";
|
|
3
3
|
export const SESSION_TRANSPORT_OUTPUT_LIMIT = 200_000;
|
|
4
4
|
function cleanDisplayTitle(value) {
|
|
5
5
|
return value?.replace(/\s+/g, " ").trim().slice(0, 40) ?? "";
|
|
@@ -61,7 +61,7 @@ function sessionBase(snapshot) {
|
|
|
61
61
|
autoApprovePermissions: snapshot.autoApprovePermissions,
|
|
62
62
|
approvalStats: snapshot.approvalStats,
|
|
63
63
|
summary: snapshot.summary,
|
|
64
|
-
title: resolveSessionDisplayTitle(snapshot),
|
|
64
|
+
title: resolveSessionDisplayTitle(snapshot, collectSessionTopicBlocklist({ cwd: snapshot.cwd })),
|
|
65
65
|
description: snapshot.description,
|
|
66
66
|
titleGenerating: snapshot.titleGenerating,
|
|
67
67
|
currentTaskTitle: snapshot.currentTaskTitle,
|