beauticode-dsh 1.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/README.zh-CN.md +48 -0
- package/agent.mjs +595 -0
- package/atmosphere.js +306 -0
- package/bin/beauticode-dsh +219 -0
- package/cli.js +219 -0
- package/client.js +370 -0
- package/console.js +303 -0
- package/control-client.mjs +445 -0
- package/cordis.patch.yml +4 -0
- package/host-apply.mjs +163 -0
- package/index.mjs +570 -0
- package/package.json +56 -0
- package/presets.mjs +73 -0
- package/themes/internal-beyond/NOTICE.md +9 -0
- package/themes/internal-beyond/bg-canvas-4k.png +0 -0
- package/themes/internal-beyond/bg-canvas.png +0 -0
- package/themes/internal-beyond/bg-infernal.jpg +0 -0
- package/themes/internal-beyond/bg-internal.jpg +0 -0
- package/ui-host.mjs +292 -0
- package/vendor/adapter-dsh/bridge.js +215 -0
- package/vendor/adapter-dsh/host-descriptor.js +15 -0
- package/vendor/adapter-dsh/index.js +6 -0
- package/vendor/adapter-dsh/injector-lock.js +27 -0
- package/vendor/adapter-dsh/package.json +5 -0
- package/vendor/adapter-dsh/session.js +527 -0
- package/vendor/adapter-dsh/token.js +46 -0
- package/vendor/adapter-dsh/tray-handoff.js +54 -0
- package/vendor/core/apply-transaction.js +287 -0
- package/vendor/core/background-store.js +1116 -0
- package/vendor/core/bundled-gallery.js +53 -0
- package/vendor/core/constants.js +47 -0
- package/vendor/core/error-message.js +208 -0
- package/vendor/core/file-lock.js +151 -0
- package/vendor/core/host-session.js +2 -0
- package/vendor/core/index.js +12 -0
- package/vendor/core/media-server.js +544 -0
- package/vendor/core/media-validation.js +244 -0
- package/vendor/core/package.json +5 -0
- package/vendor/core/paths.js +168 -0
- package/vendor/core/types.js +15 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { MAX_INLINE_DATA_URL_BYTES } from "./constants.js";
|
|
4
|
+
import { detectImageMime } from "./media-validation.js";
|
|
5
|
+
/**
|
|
6
|
+
* Minimal baseline only — live Codex path injects packages/adapter-codex
|
|
7
|
+
* renderer/background.css (full-window main-surface transparency).
|
|
8
|
+
* Keep this minimal so offline unit tests still paint a stage.
|
|
9
|
+
*/
|
|
10
|
+
const DEFAULT_CSS = `/* beautiCode background stage baseline */
|
|
11
|
+
#beauticode-bg-stage{
|
|
12
|
+
position:fixed;inset:0;z-index:0;overflow:hidden;pointer-events:none;background:transparent!important;
|
|
13
|
+
}
|
|
14
|
+
#beauticode-bg-stage::before{
|
|
15
|
+
content:"";position:absolute;inset:0;z-index:3;pointer-events:none;background:transparent;
|
|
16
|
+
}
|
|
17
|
+
#beauticode-bg-stage img,#beauticode-bg-stage video{
|
|
18
|
+
position:absolute;inset:0;width:100%;height:100%;object-fit:cover;pointer-events:none;
|
|
19
|
+
}
|
|
20
|
+
#beauticode-bg-stage img{z-index:1;}
|
|
21
|
+
#beauticode-bg-stage video{z-index:2;opacity:0;}
|
|
22
|
+
html[data-bc-active="true"][data-bc-media="video"][data-bc-video-ready="true"] #beauticode-bg-stage video{opacity:1;}
|
|
23
|
+
html[data-bc-active="true"][data-bc-media="video"][data-bc-video-ready="true"] #beauticode-bg-stage img{display:none!important;}
|
|
24
|
+
html[data-bc-active="true"][data-bc-media="video-pending"] #beauticode-bg-stage img{opacity:1;display:block;}
|
|
25
|
+
`;
|
|
26
|
+
/**
|
|
27
|
+
* Orchestrates snapshot → disk commit → media stage → host apply → verify →
|
|
28
|
+
* finalize/rollback. Disk success is never treated as user-visible success when
|
|
29
|
+
* a host applier is configured.
|
|
30
|
+
*
|
|
31
|
+
* Codex Desktop CSP blocks connect-src/img-src/media-src to http://127.0.0.1,
|
|
32
|
+
* so live inject embeds media as data: URLs (CSP allows data: and blob:).
|
|
33
|
+
* Loopback media hub remains staged for diagnostics / non-Codex hosts.
|
|
34
|
+
*/
|
|
35
|
+
export class ApplyTransaction {
|
|
36
|
+
store;
|
|
37
|
+
media;
|
|
38
|
+
host;
|
|
39
|
+
cssText;
|
|
40
|
+
verifyDeadlineMs;
|
|
41
|
+
offline;
|
|
42
|
+
#busy = false;
|
|
43
|
+
constructor(opts) {
|
|
44
|
+
this.store = opts.store;
|
|
45
|
+
this.media = opts.media;
|
|
46
|
+
this.host = opts.host ?? null;
|
|
47
|
+
this.cssText = opts.cssText ?? DEFAULT_CSS;
|
|
48
|
+
this.verifyDeadlineMs = opts.verifyDeadlineMs ?? 30_000;
|
|
49
|
+
this.offline = opts.offline ?? false;
|
|
50
|
+
}
|
|
51
|
+
get busy() {
|
|
52
|
+
return this.#busy;
|
|
53
|
+
}
|
|
54
|
+
async run(input) {
|
|
55
|
+
if (this.#busy) {
|
|
56
|
+
return {
|
|
57
|
+
ok: false,
|
|
58
|
+
error: "Another background apply is already in progress.",
|
|
59
|
+
rolledBack: false,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
this.#busy = true;
|
|
63
|
+
try {
|
|
64
|
+
return await this.store.withExclusiveMutation(() => this.#runExclusive(input));
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
return {
|
|
68
|
+
ok: false,
|
|
69
|
+
error: error instanceof Error ? error.message : String(error),
|
|
70
|
+
rolledBack: false,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
this.#busy = false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async #runExclusive(input) {
|
|
78
|
+
let snapshot = null;
|
|
79
|
+
let staged = null;
|
|
80
|
+
let runtimeVideoPath = null;
|
|
81
|
+
try {
|
|
82
|
+
await this.store.init();
|
|
83
|
+
snapshot = await this.store.snapshot();
|
|
84
|
+
const manifest = await this.store.commitImport(input);
|
|
85
|
+
staged = await this.#stageMediaFor(manifest);
|
|
86
|
+
if (!this.offline && this.host) {
|
|
87
|
+
const payload = await this.#buildPayload(manifest, staged, input.type === "video" ? input.startAt : undefined);
|
|
88
|
+
runtimeVideoPath = payload.video?.localPath ?? null;
|
|
89
|
+
await this.host.apply(payload);
|
|
90
|
+
const verify = await this.host.verify({
|
|
91
|
+
generation: manifest.generation,
|
|
92
|
+
media: manifest.background?.type ?? "clear",
|
|
93
|
+
}, { deadlineMs: this.verifyDeadlineMs });
|
|
94
|
+
if (verify.status !== "pass") {
|
|
95
|
+
await this.#rollback(snapshot, staged);
|
|
96
|
+
staged = null;
|
|
97
|
+
snapshot = null;
|
|
98
|
+
return {
|
|
99
|
+
ok: false,
|
|
100
|
+
error: `Live verify did not pass (${verify.status}): ${verify.reason}`,
|
|
101
|
+
rolledBack: true,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
await this.media.commit(staged);
|
|
106
|
+
await this.store.pruneRuntimeMedia(runtimeVideoPath);
|
|
107
|
+
staged = null;
|
|
108
|
+
if (snapshot) {
|
|
109
|
+
await this.store.clearSnapshot(snapshot);
|
|
110
|
+
snapshot = null;
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
ok: true,
|
|
114
|
+
generation: manifest.generation,
|
|
115
|
+
mode: manifest.background?.type ?? "clear",
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
120
|
+
let rolledBack = false;
|
|
121
|
+
if (snapshot) {
|
|
122
|
+
try {
|
|
123
|
+
await this.#rollback(snapshot, staged);
|
|
124
|
+
staged = null;
|
|
125
|
+
snapshot = null;
|
|
126
|
+
rolledBack = true;
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
await this.#abortPair(staged);
|
|
130
|
+
staged = null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
await this.#abortPair(staged);
|
|
135
|
+
staged = null;
|
|
136
|
+
}
|
|
137
|
+
return { ok: false, error: message, rolledBack };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async #stageMediaFor(manifest) {
|
|
141
|
+
if (!manifest.background) {
|
|
142
|
+
return { image: null, video: null };
|
|
143
|
+
}
|
|
144
|
+
const imagePath = path.join(this.store.paths.activeDir, manifest.background.image);
|
|
145
|
+
const image = await this.media.stage(imagePath);
|
|
146
|
+
let video = null;
|
|
147
|
+
if (manifest.background.type === "video" && manifest.background.video) {
|
|
148
|
+
// DSH streams this handle for the lifetime of the active <video>. On
|
|
149
|
+
// Windows, serving active/background.mp4 directly keeps the active
|
|
150
|
+
// directory open and the next atomic directory swap fails with EPERM.
|
|
151
|
+
const runtimeVideoPath = await this.store.prepareRuntimeVideo(manifest);
|
|
152
|
+
if (!runtimeVideoPath) {
|
|
153
|
+
throw new Error("Video media staging requires a detached runtime copy.");
|
|
154
|
+
}
|
|
155
|
+
video = await this.media.stage(runtimeVideoPath);
|
|
156
|
+
}
|
|
157
|
+
return { image, video };
|
|
158
|
+
}
|
|
159
|
+
async #buildPayload(manifest, staged, videoStartAt) {
|
|
160
|
+
return buildHostApplyPayload(this.store, manifest, staged, this.cssText, videoStartAt);
|
|
161
|
+
}
|
|
162
|
+
async #abortPair(staged) {
|
|
163
|
+
if (!staged)
|
|
164
|
+
return;
|
|
165
|
+
await this.media.abort(staged.video);
|
|
166
|
+
await this.media.abort(staged.image);
|
|
167
|
+
}
|
|
168
|
+
async #rollback(snapshot, staged) {
|
|
169
|
+
await this.#abortPair(staged);
|
|
170
|
+
const restored = await this.store.restoreSnapshot(snapshot);
|
|
171
|
+
let restoredMedia = null;
|
|
172
|
+
try {
|
|
173
|
+
restoredMedia = await this.#stageMediaFor(restored);
|
|
174
|
+
let runtimeVideoPath = null;
|
|
175
|
+
if (!this.offline && this.host) {
|
|
176
|
+
const payload = await this.#buildPayload(restored, restoredMedia);
|
|
177
|
+
runtimeVideoPath = payload.video?.localPath ?? null;
|
|
178
|
+
try {
|
|
179
|
+
await this.host.apply(payload);
|
|
180
|
+
await this.host.verify({
|
|
181
|
+
generation: restored.generation,
|
|
182
|
+
media: restored.background?.type ?? "clear",
|
|
183
|
+
}, { deadlineMs: Math.min(this.verifyDeadlineMs, 5_000) });
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// Disk rollback is authoritative. A stopped/restarting host will be
|
|
187
|
+
// healed from active on its next watch connection.
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
await this.media.commit(restoredMedia);
|
|
191
|
+
await this.store.pruneRuntimeMedia(runtimeVideoPath);
|
|
192
|
+
restoredMedia = null;
|
|
193
|
+
await this.store.clearSnapshot(snapshot);
|
|
194
|
+
}
|
|
195
|
+
finally {
|
|
196
|
+
await this.#abortPair(restoredMedia);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
export async function buildHostApplyPayload(store, manifest, staged, cssText, videoStartAt) {
|
|
201
|
+
if (!manifest.background) {
|
|
202
|
+
return {
|
|
203
|
+
generation: manifest.generation,
|
|
204
|
+
media: "clear",
|
|
205
|
+
imageDataUrl: null,
|
|
206
|
+
imageUrl: null,
|
|
207
|
+
video: null,
|
|
208
|
+
cssText,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const imagePath = path.join(store.paths.activeDir, manifest.background.image);
|
|
212
|
+
const imageDataUrl = await fileToDataUrl(imagePath);
|
|
213
|
+
const imageUrl = staged?.image?.srcUrl ?? null;
|
|
214
|
+
if (manifest.background.type === "image") {
|
|
215
|
+
return {
|
|
216
|
+
generation: manifest.generation,
|
|
217
|
+
media: "image",
|
|
218
|
+
imageDataUrl,
|
|
219
|
+
imageUrl,
|
|
220
|
+
video: null,
|
|
221
|
+
cssText,
|
|
222
|
+
atmosphere: manifest.background.effects ?? null,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
if (!manifest.background.video) {
|
|
226
|
+
throw new Error("Video background is missing a video basename.");
|
|
227
|
+
}
|
|
228
|
+
const videoPath = path.join(store.paths.activeDir, manifest.background.video);
|
|
229
|
+
const runtimeVideoPath = await store.prepareRuntimeVideo(manifest);
|
|
230
|
+
if (!runtimeVideoPath) {
|
|
231
|
+
throw new Error("Video payload requires a detached runtime copy.");
|
|
232
|
+
}
|
|
233
|
+
const videoStat = await fs.stat(videoPath);
|
|
234
|
+
const videoHandle = staged?.video;
|
|
235
|
+
const video = {
|
|
236
|
+
mode: "blob",
|
|
237
|
+
localPath: runtimeVideoPath,
|
|
238
|
+
};
|
|
239
|
+
if (videoHandle?.url)
|
|
240
|
+
video.url = videoHandle.url;
|
|
241
|
+
if (videoHandle?.srcUrl)
|
|
242
|
+
video.srcUrl = videoHandle.srcUrl;
|
|
243
|
+
if (videoHandle?.token)
|
|
244
|
+
video.token = videoHandle.token;
|
|
245
|
+
if (typeof videoStartAt === "number" &&
|
|
246
|
+
Number.isFinite(videoStartAt) &&
|
|
247
|
+
videoStartAt > 0 &&
|
|
248
|
+
videoStartAt <= 24 * 60 * 60) {
|
|
249
|
+
video.startAt = videoStartAt;
|
|
250
|
+
}
|
|
251
|
+
if (videoStat.size <= 256 * 1024) {
|
|
252
|
+
video.dataUrl = await fileToDataUrl(videoPath, "video/mp4");
|
|
253
|
+
}
|
|
254
|
+
return {
|
|
255
|
+
generation: manifest.generation,
|
|
256
|
+
media: "video",
|
|
257
|
+
imageDataUrl,
|
|
258
|
+
imageUrl,
|
|
259
|
+
video,
|
|
260
|
+
cssText,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
const MIME_BY_EXT = {
|
|
264
|
+
".png": "image/png",
|
|
265
|
+
".jpg": "image/jpeg",
|
|
266
|
+
".jpeg": "image/jpeg",
|
|
267
|
+
".webp": "image/webp",
|
|
268
|
+
".avif": "image/avif",
|
|
269
|
+
".mp4": "video/mp4",
|
|
270
|
+
};
|
|
271
|
+
/** Embed a local media file as a CSP-safe data: URL for host inject. */
|
|
272
|
+
export async function fileToDataUrl(filePath, mimeOverride) {
|
|
273
|
+
const bytes = await fs.readFile(filePath);
|
|
274
|
+
if (bytes.byteLength > MAX_INLINE_DATA_URL_BYTES) {
|
|
275
|
+
throw new Error(`Media file too large to embed as data URL (${bytes.byteLength} > ${MAX_INLINE_DATA_URL_BYTES}).`);
|
|
276
|
+
}
|
|
277
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
278
|
+
const detectedImage = !mimeOverride && ext !== ".mp4"
|
|
279
|
+
? detectImageMime(bytes.subarray(0, 64), ext, bytes.byteLength)
|
|
280
|
+
: null;
|
|
281
|
+
const mime = mimeOverride ?? detectedImage?.mime ?? MIME_BY_EXT[ext];
|
|
282
|
+
if (!mime) {
|
|
283
|
+
throw new Error(`Unsupported media extension for data URL: ${ext || "(none)"}`);
|
|
284
|
+
}
|
|
285
|
+
return `data:${mime};base64,${bytes.toString("base64")}`;
|
|
286
|
+
}
|
|
287
|
+
//# sourceMappingURL=apply-transaction.js.map
|