beauticode-dsh 1.0.19 → 1.0.21
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/client.js +1312 -1103
- package/package.json +1 -1
- package/vendor/core/apply-transaction.js +24 -2
- package/vendor/core/background-store.js +5 -1
- package/vendor/core/media-validation.js +33 -0
package/package.json
CHANGED
|
@@ -3,6 +3,13 @@ import path from "node:path";
|
|
|
3
3
|
import { MAX_INLINE_DATA_URL_BYTES } from "./constants.js";
|
|
4
4
|
import { isLocalBackgroundSource, resolveBackgroundImagePath, resolveBackgroundVideoPath, } from "./media-source.js";
|
|
5
5
|
import { detectImageMime } from "./media-validation.js";
|
|
6
|
+
// Cold-first-access symptoms reported by the renderer while the OS is still
|
|
7
|
+
// scanning or caching a large local video. The stalled attempt itself warms
|
|
8
|
+
// the file and media pipeline, so one immediate retry usually renders.
|
|
9
|
+
const RETRYABLE_RENDER_VERIFY_PATTERN = /首帧|Range 探针/;
|
|
10
|
+
function isRetryableRenderVerifyFailure(status, reason) {
|
|
11
|
+
return status === "fail" && typeof reason === "string" && RETRYABLE_RENDER_VERIFY_PATTERN.test(reason);
|
|
12
|
+
}
|
|
6
13
|
/**
|
|
7
14
|
* Minimal baseline only — live Codex path injects packages/adapter-codex
|
|
8
15
|
* renderer/background.css (full-window main-surface transparency).
|
|
@@ -115,17 +122,32 @@ export class ApplyTransaction {
|
|
|
115
122
|
const payload = await measure("buildPayload", () => this.#buildPayload(manifest, staged, input.type === "video" ? input.startAt : undefined));
|
|
116
123
|
runtimeVideoPath = payload.video?.localPath ?? null;
|
|
117
124
|
await measure("hostApply", () => this.host.apply(payload));
|
|
118
|
-
|
|
125
|
+
let verify = await measure("rendererVerify", () => this.host.verify({
|
|
119
126
|
generation: manifest.generation,
|
|
120
127
|
media: manifest.background?.type ?? "clear",
|
|
121
128
|
}, { deadlineMs: this.verifyDeadlineMs }));
|
|
129
|
+
if (verify.status !== "pass" &&
|
|
130
|
+
manifest.background?.type === "video" &&
|
|
131
|
+
isRetryableRenderVerifyFailure(verify.status, verify.reason)) {
|
|
132
|
+
await measure("hostApplyRetry", () => this.host.apply(payload));
|
|
133
|
+
verify = await measure("rendererVerifyRetry", () => this.host.verify({
|
|
134
|
+
generation: manifest.generation,
|
|
135
|
+
media: manifest.background?.type ?? "clear",
|
|
136
|
+
}, { deadlineMs: this.verifyDeadlineMs }));
|
|
137
|
+
}
|
|
122
138
|
if (verify.status !== "pass") {
|
|
123
139
|
await measure("rollback", () => this.#rollback(snapshot, staged));
|
|
124
140
|
staged = null;
|
|
125
141
|
snapshot = null;
|
|
142
|
+
// Field reports used to show only the renderer's final state.
|
|
143
|
+
// Appending the server-side phase costs shows which stage ate the
|
|
144
|
+
// deadline without needing the local import-timing log.
|
|
145
|
+
const phaseSummary = Object.entries(phases)
|
|
146
|
+
.map(([name, ms]) => `${name}=${ms}ms`)
|
|
147
|
+
.join(",");
|
|
126
148
|
return {
|
|
127
149
|
ok: false,
|
|
128
|
-
error: `Live verify did not pass (${verify.status}): ${verify.reason}`,
|
|
150
|
+
error: `Live verify did not pass (${verify.status}): ${verify.reason};事务阶段=${phaseSummary}`,
|
|
129
151
|
rolledBack: true,
|
|
130
152
|
sourceMode,
|
|
131
153
|
timings: timings(),
|
|
@@ -3,7 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
4
|
import crypto from "node:crypto";
|
|
5
5
|
import { COMMIT_MARKER_NAME, COMMIT_MARKER_STALE_MS, DEFAULT_VIDEO_BASENAME, MANIFEST_NAME, SAVED_META_NAME, SCHEMA_ID, } from "./constants.js";
|
|
6
|
-
import { assertSafeBasename, MediaValidationError, validateImageFile, validateVideoFile, } from "./media-validation.js";
|
|
6
|
+
import { assertSafeBasename, MediaValidationError, validateImageFile, validateVideoFile, warmVideoFileReads, } from "./media-validation.js";
|
|
7
7
|
import { copyFileAtomic, emptyDir, ensureDataLayout, isPathInsideRoot, resolveDataPaths, rmrf, } from "./paths.js";
|
|
8
8
|
import { normalizeBackgroundEffects } from "./types.js";
|
|
9
9
|
import { isLocalBackgroundSource, managedPrimaryFile, resolveBackgroundImagePath, resolveBackgroundVideoPath, } from "./media-source.js";
|
|
@@ -289,6 +289,10 @@ export class BackgroundStore {
|
|
|
289
289
|
return null;
|
|
290
290
|
if (isLocalBackgroundSource(manifest.background)) {
|
|
291
291
|
await validateVideoFile(source, { mode: "fast" });
|
|
292
|
+
// Zero-copy local videos stream straight from the original path, so
|
|
293
|
+
// the first range reads happen under the renderer verify deadline.
|
|
294
|
+
// Warm the header and moov-tail before that clock starts.
|
|
295
|
+
await warmVideoFileReads(source);
|
|
292
296
|
return source;
|
|
293
297
|
}
|
|
294
298
|
const cached = this.#runtimeVideoCache.get(manifest.generation);
|
|
@@ -222,6 +222,39 @@ export async function validateVideoFile(filePath, opts = {}) {
|
|
|
222
222
|
ctimeMs: inspected.ctimeMs,
|
|
223
223
|
};
|
|
224
224
|
}
|
|
225
|
+
const WARM_VIDEO_HEAD_BYTES = 2 * 1024 * 1024;
|
|
226
|
+
const WARM_VIDEO_TAIL_BYTES = 4 * 1024 * 1024;
|
|
227
|
+
/**
|
|
228
|
+
* Best-effort warm read of the byte ranges a cold <video> pipeline requests
|
|
229
|
+
* first: the container header plus the file tail where non-faststart MP4s
|
|
230
|
+
* keep their moov atom. On a fresh machine the first read of a large local
|
|
231
|
+
* video can stall in a synchronous antivirus scan long past the renderer
|
|
232
|
+
* verify deadline; touching these regions during staging keeps that cost
|
|
233
|
+
* outside the timed apply. Warmup must never block or fail an import.
|
|
234
|
+
*/
|
|
235
|
+
export async function warmVideoFileReads(filePath) {
|
|
236
|
+
try {
|
|
237
|
+
const handle = await fs.open(filePath, "r");
|
|
238
|
+
try {
|
|
239
|
+
const size = (await handle.stat()).size;
|
|
240
|
+
const headLength = Math.min(WARM_VIDEO_HEAD_BYTES, size);
|
|
241
|
+
const tailLength = Math.min(WARM_VIDEO_TAIL_BYTES, Math.max(0, size - headLength));
|
|
242
|
+
const buffer = Buffer.allocUnsafe(Math.max(headLength, tailLength, 1));
|
|
243
|
+
if (headLength > 0) {
|
|
244
|
+
await handle.read(buffer, 0, headLength, 0);
|
|
245
|
+
}
|
|
246
|
+
if (tailLength > 0) {
|
|
247
|
+
await handle.read(buffer, 0, tailLength, size - tailLength);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
await handle.close();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
/* Advisory only; correctness of the import does not depend on warmup. */
|
|
256
|
+
}
|
|
257
|
+
}
|
|
225
258
|
/** Basename-only guard for names stored inside active/staging trees. */
|
|
226
259
|
export function assertSafeBasename(name, label) {
|
|
227
260
|
if (typeof name !== "string" || name.length < 1 || name.length > 128) {
|