@forgeax/game 0.3.6 → 0.3.8

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.
@@ -0,0 +1,2090 @@
1
+ // extensions/asset3d/src/credentials.ts
2
+ import { chmodSync, existsSync, lstatSync, readFileSync, statSync, unlinkSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { dirname as dirname2, isAbsolute, resolve } from "node:path";
5
+
6
+ // extensions/asset3d/src/fs.ts
7
+ import { closeSync, fsyncSync, mkdirSync, openSync, renameSync, writeFileSync } from "node:fs";
8
+ import { dirname } from "node:path";
9
+ function ensurePrivateDir(path) {
10
+ mkdirSync(path, { recursive: true, mode: 448 });
11
+ }
12
+ function atomicWrite(path, data, mode = 384) {
13
+ ensurePrivateDir(dirname(path));
14
+ const temp = `${path}.tmp-${process.pid}-${crypto.randomUUID()}`;
15
+ const fd = openSync(temp, "wx", mode);
16
+ try {
17
+ writeFileSync(fd, data);
18
+ fsyncSync(fd);
19
+ } finally {
20
+ closeSync(fd);
21
+ }
22
+ renameSync(temp, path);
23
+ const dirFd = openSync(dirname(path), "r");
24
+ try {
25
+ fsyncSync(dirFd);
26
+ } finally {
27
+ closeSync(dirFd);
28
+ }
29
+ }
30
+
31
+ // extensions/asset3d/src/credentials.ts
32
+ var AW_CREDENTIAL_SCHEMA = "forgeax.asset3d-credential/1.0.0";
33
+ var AW_KEY_ENV = "FORGEAX_ASSET3D_AW_SANDBOX_KEY";
34
+ var MAX_CREDENTIAL_BYTES = 4096;
35
+ function validateKey(value) {
36
+ if (!value || value.length > 2048 || [...value].some((character) => {
37
+ const code = character.charCodeAt(0);
38
+ return code < 32 || code === 127;
39
+ })) {
40
+ throw new Error("asset3d_api_key_invalid: expected a non-empty printable key");
41
+ }
42
+ return value;
43
+ }
44
+ function defaultAwCredentialFile() {
45
+ const configured = process.env.FORGEAX_ASSET3D_CREDENTIAL_FILE;
46
+ return resolve(configured || resolve(homedir(), ".forgeax", "credentials", "asset3d-aw.json"));
47
+ }
48
+ function readAwCredential(pathInput) {
49
+ const path = resolve(pathInput);
50
+ if (!isAbsolute(pathInput))
51
+ throw new Error("asset3d_credential_path_invalid: absolute path required");
52
+ if (!existsSync(path))
53
+ return;
54
+ try {
55
+ const metadata = lstatSync(path);
56
+ const wrongOwner = typeof process.getuid === "function" && metadata.uid !== process.getuid();
57
+ if (!metadata.isFile() || metadata.isSymbolicLink() || wrongOwner)
58
+ throw new Error;
59
+ if ((metadata.mode & 63) !== 0 || metadata.size < 1 || metadata.size > MAX_CREDENTIAL_BYTES)
60
+ throw new Error;
61
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
62
+ if (Object.keys(parsed).sort().join(",") !== "provider,sandboxKey,schemaVersion")
63
+ throw new Error;
64
+ if (parsed.schemaVersion !== AW_CREDENTIAL_SCHEMA || parsed.provider !== "aw" || typeof parsed.sandboxKey !== "string")
65
+ throw new Error;
66
+ return validateKey(parsed.sandboxKey);
67
+ } catch {
68
+ throw new Error("asset3d_credential_invalid: credential file must be an owned 0600 regular file with the supported schema");
69
+ }
70
+ }
71
+ async function promptAwKey() {
72
+ if (!process.stdin.isTTY || !process.stdout.isTTY || typeof process.stdin.setRawMode !== "function") {
73
+ throw new Error(`asset3d_api_key_required: set ${AW_KEY_ENV} or rerun in an interactive terminal`);
74
+ }
75
+ process.stdout.write("AW Asset3D Sandbox Key (input hidden): ");
76
+ const input = process.stdin;
77
+ const previousRaw = input.isRaw;
78
+ input.setRawMode(true);
79
+ input.resume();
80
+ input.setEncoding("utf8");
81
+ try {
82
+ const value = await new Promise((resolveValue, reject) => {
83
+ let collected = "";
84
+ const onData = (chunk) => {
85
+ for (const character of chunk) {
86
+ if (character === "\x03") {
87
+ input.off("data", onData);
88
+ reject(new Error("asset3d_api_key_input_cancelled"));
89
+ return;
90
+ }
91
+ if (character === "\r" || character === `
92
+ `) {
93
+ input.off("data", onData);
94
+ resolveValue(collected);
95
+ return;
96
+ }
97
+ if (character === "" || character === "\b")
98
+ collected = collected.slice(0, -1);
99
+ else
100
+ collected += character;
101
+ }
102
+ };
103
+ input.on("data", onData);
104
+ });
105
+ process.stdout.write(`
106
+ `);
107
+ return validateKey(value);
108
+ } finally {
109
+ input.setRawMode(previousRaw ?? false);
110
+ input.pause();
111
+ }
112
+ }
113
+ async function acquireAwKey(path, interactive = true) {
114
+ const fromEnvironment = process.env[AW_KEY_ENV];
115
+ if (fromEnvironment)
116
+ return { key: validateKey(fromEnvironment), source: "environment" };
117
+ const stored = readAwCredential(path);
118
+ if (stored)
119
+ return { key: stored, source: "stored" };
120
+ if (!interactive)
121
+ throw new Error("asset3d_api_key_required");
122
+ return { key: await promptAwKey(), source: "prompt" };
123
+ }
124
+ function writeAwCredential(pathInput, keyInput) {
125
+ if (!isAbsolute(pathInput))
126
+ throw new Error("asset3d_credential_path_invalid: absolute path required");
127
+ const path = resolve(pathInput);
128
+ const key = validateKey(keyInput);
129
+ const parent = dirname2(path);
130
+ ensurePrivateDir(parent);
131
+ const parentMetadata = lstatSync(parent);
132
+ const wrongParentOwner = typeof process.getuid === "function" && parentMetadata.uid !== process.getuid();
133
+ if (!parentMetadata.isDirectory() || parentMetadata.isSymbolicLink() || wrongParentOwner) {
134
+ throw new Error("asset3d_credential_path_invalid: parent must be an owned regular directory");
135
+ }
136
+ chmodSync(parent, 448);
137
+ const existed = existsSync(path);
138
+ if (existed)
139
+ readAwCredential(path);
140
+ const previous = existed ? readFileSync(path) : undefined;
141
+ const previousMode = existed ? statSync(path).mode & 511 : undefined;
142
+ const bytes = `${JSON.stringify({ schemaVersion: AW_CREDENTIAL_SCHEMA, provider: "aw", sandboxKey: key }, null, 2)}
143
+ `;
144
+ const changed = !previous || !previous.equals(Buffer.from(bytes));
145
+ if (changed)
146
+ atomicWrite(path, bytes, 384);
147
+ chmodSync(path, 384);
148
+ let active = true;
149
+ return {
150
+ path,
151
+ changed,
152
+ commit() {
153
+ active = false;
154
+ },
155
+ rollback() {
156
+ if (!active || !changed)
157
+ return;
158
+ if (previous) {
159
+ atomicWrite(path, previous, previousMode ?? 384);
160
+ chmodSync(path, previousMode ?? 384);
161
+ } else {
162
+ unlinkSync(path);
163
+ }
164
+ active = false;
165
+ }
166
+ };
167
+ }
168
+
169
+ // extensions/asset3d/src/origins.ts
170
+ import { domainToASCII } from "node:url";
171
+
172
+ // extensions/asset3d/src/constants.ts
173
+ import { createHash } from "node:crypto";
174
+ var PROVIDER_RESULT_SCHEMA = "forgeax.asset3d-search-result/2.0.0";
175
+ var PROVIDER_RECEIPT_SCHEMA = "forgeax.asset3d-search-receipt/2.0.0";
176
+ var INSTALL_SCHEMA = "forgeax.asset3d-install/2.0.0";
177
+ var TRANSACTION_SCHEMA = "forgeax.asset3d-transaction/2.0.0";
178
+ var PROVENANCE_SCHEMA = "forgeax.asset3d-provenance/2.0.0";
179
+ var MAX_JSON_BYTES = 1024 * 1024;
180
+ var SKILL_MOUNTS = Object.freeze({
181
+ codex: ".agents/skills",
182
+ claude: ".claude/skills",
183
+ cursor: ".cursor/skills",
184
+ trae: ".trae/skills",
185
+ codebuddy: ".codebuddy/skills",
186
+ windsurf: ".codeium/windsurf/skills",
187
+ vscode: ".vscode/skills",
188
+ zcode: ".zcode/skills",
189
+ opencode: ".config/opencode/skills"
190
+ });
191
+ function sha256(value) {
192
+ return createHash("sha256").update(value).digest("hex");
193
+ }
194
+ function canonicalJson(value) {
195
+ if (value === null || typeof value !== "object")
196
+ return JSON.stringify(value);
197
+ if (Array.isArray(value))
198
+ return `[${value.map(canonicalJson).join(",")}]`;
199
+ return `{${Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`).join(",")}}`;
200
+ }
201
+
202
+ // extensions/asset3d/src/origins.ts
203
+ function canonicalIpv4(host) {
204
+ const parts = host.split(".");
205
+ if (parts.length !== 4 || parts.some((part) => !/^\d{1,3}$/.test(part)))
206
+ return;
207
+ const values = parts.map(Number);
208
+ if (values.some((value) => value > 255))
209
+ throw new Error("download_origin_invalid: invalid IPv4 address");
210
+ return values.join(".");
211
+ }
212
+ function canonicalizeOrigins(inputs) {
213
+ if (inputs.length < 1 || inputs.length > 8) {
214
+ throw new Error("download_origin_count_invalid: expected 1..8 --download-origin values");
215
+ }
216
+ const values = inputs.map((input) => {
217
+ const lexical = /^(https?):\/\/(\[[0-9A-Fa-f:.]+\]|[^:/?#@]+):(\d{1,5})$/.exec(input);
218
+ if (!lexical) {
219
+ throw new Error("download_origin_invalid: expected exact scheme://host:port without path, query, fragment, or userinfo");
220
+ }
221
+ let parsed;
222
+ try {
223
+ parsed = new URL(input);
224
+ } catch {
225
+ throw new Error("download_origin_invalid: malformed URL");
226
+ }
227
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
228
+ throw new Error("download_origin_invalid: http or https required");
229
+ if (parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash) {
230
+ throw new Error("download_origin_invalid: userinfo/path/query/fragment is forbidden");
231
+ }
232
+ const port = Number(lexical[3]);
233
+ if (!Number.isInteger(port) || port < 1 || port > 65535)
234
+ throw new Error("download_origin_invalid: explicit port must be 1..65535");
235
+ let host = lexical[2];
236
+ if (host === "*" || host.includes("*"))
237
+ throw new Error("download_origin_invalid: wildcard host is forbidden");
238
+ if (host.startsWith("[") && host.endsWith("]")) {
239
+ const normalized = new URL(`${parsed.protocol}//${host}:${port}`).hostname;
240
+ host = normalized.startsWith("[") ? normalized.toLowerCase() : `[${normalized.toLowerCase()}]`;
241
+ } else {
242
+ host = canonicalIpv4(host) ?? domainToASCII(host.replace(/\.$/, "")).toLowerCase();
243
+ if (!host)
244
+ throw new Error("download_origin_invalid: host cannot be canonicalized");
245
+ }
246
+ return `${lexical[1]}://${host}:${port}`;
247
+ }).sort();
248
+ if (new Set(values).size !== values.length)
249
+ throw new Error("download_origin_duplicate: canonical duplicates are forbidden");
250
+ const compactJson = canonicalJson(values);
251
+ return { values, compactJson, digest: sha256(compactJson) };
252
+ }
253
+
254
+ // extensions/asset3d/src/aw-access.ts
255
+ var AW_SERVICE_PATH = "/trpc.oasismetric.omcontentserver.http";
256
+ var DEFAULT_AW_PUBLIC_SERVICE_ROOT = "http://lb-pl74wsqg-5wi8ujmy1fq2746r.clb.usw-tencentclb.com:8008/trpc.oasismetric.omcontentserver.http";
257
+ function normalizeAssetLibraryServiceRoot(input) {
258
+ let parsed;
259
+ try {
260
+ parsed = new URL(input);
261
+ } catch {
262
+ throw new Error("asset3d_base_url_invalid");
263
+ }
264
+ if (!["http:", "https:"].includes(parsed.protocol) || parsed.username || parsed.password || parsed.search || parsed.hash) {
265
+ throw new Error("asset3d_base_url_invalid");
266
+ }
267
+ let path = parsed.pathname.replace(/\/+$/, "");
268
+ if (path.endsWith(AW_SERVICE_PATH + "/HybridSearch"))
269
+ path = path.slice(0, -"/HybridSearch".length);
270
+ else if (!path.endsWith(AW_SERVICE_PATH))
271
+ path += AW_SERVICE_PATH;
272
+ parsed.pathname = path;
273
+ return parsed.toString().replace(/\/$/, "");
274
+ }
275
+ function resolveAssetLibrarySelection(options = {}) {
276
+ const library = options.library || process.env.FORGEAX_ASSET_LIBRARY || "aw";
277
+ if (library !== "aw" && library !== "ea")
278
+ throw new Error("asset3d_library_invalid: expected aw or ea");
279
+ const base = options.baseUrl || process.env.FORGEAX_ASSET_LIBRARY_BASE_URL || (library === "aw" ? DEFAULT_AW_PUBLIC_SERVICE_ROOT : undefined);
280
+ if (!base)
281
+ throw new Error("FORGEAX_ASSET_LIBRARY_BASE_URL is required when FORGEAX_ASSET_LIBRARY=ea. Set the EA gateway URL explicitly.");
282
+ return { library, serviceRoot: normalizeAssetLibraryServiceRoot(base) };
283
+ }
284
+ function downloadOrigin(input) {
285
+ let url;
286
+ try {
287
+ url = new URL(input);
288
+ } catch {
289
+ throw new Error("asset3d_download_url_invalid");
290
+ }
291
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.hash) {
292
+ throw new Error("asset3d_download_url_invalid");
293
+ }
294
+ return `${url.protocol}//${url.hostname}:${url.port || (url.protocol === "https:" ? "443" : "80")}`;
295
+ }
296
+ async function boundedResponse(response, limit) {
297
+ if (!response.body)
298
+ throw new Error("asset3d_response_invalid");
299
+ const reader = response.body.getReader();
300
+ const chunks = [];
301
+ let size = 0;
302
+ try {
303
+ for (;; ) {
304
+ const part = await reader.read();
305
+ if (part.done)
306
+ break;
307
+ size += part.value.byteLength;
308
+ if (size > limit)
309
+ throw new Error("asset3d_response_too_large");
310
+ chunks.push(part.value);
311
+ }
312
+ } finally {
313
+ await reader.cancel().catch(() => {});
314
+ }
315
+ return Buffer.concat(chunks);
316
+ }
317
+ async function searchLibrary(config, query) {
318
+ if (!query.trim() || query.length > 200)
319
+ throw new Error("asset3d_query_invalid");
320
+ const key = readAwCredential(config.credentialFile);
321
+ if (!key)
322
+ throw new Error("asset3d_api_key_required");
323
+ let response;
324
+ try {
325
+ response = await fetch(normalizeAssetLibraryServiceRoot(config.serviceRoot) + "/HybridSearch", {
326
+ method: "POST",
327
+ redirect: "error",
328
+ signal: AbortSignal.timeout(30000),
329
+ headers: { "Content-Type": "application/json", "X-Sandbox-Key": key },
330
+ body: JSON.stringify({
331
+ depot_name: config.library,
332
+ asset_type: 1,
333
+ req_content_type: 0,
334
+ content: query,
335
+ similarity_score: 0,
336
+ page_size: 10
337
+ })
338
+ });
339
+ } catch {
340
+ throw new Error("asset3d_service_unreachable");
341
+ }
342
+ if (response.status === 401 || response.status === 403)
343
+ throw new Error("asset3d_access_denied");
344
+ if (!response.ok)
345
+ throw new Error(`asset3d_service_http_${response.status}`);
346
+ let value;
347
+ try {
348
+ value = JSON.parse((await boundedResponse(response, 1024 * 1024)).toString());
349
+ } catch {
350
+ throw new Error("asset3d_search_response_invalid");
351
+ }
352
+ if (!value || value.ret !== undefined && value.ret !== 0 || !Array.isArray(value.asset_list) || value.asset_list.length > 100) {
353
+ throw new Error("asset3d_search_response_invalid");
354
+ }
355
+ const seen = new Set;
356
+ return value.asset_list.slice(0, 10).map((asset) => {
357
+ if (!asset || typeof asset.id !== "string" || !/^[a-zA-Z0-9_-][a-zA-Z0-9._-]{0,127}$/.test(asset.id) || seen.has(asset.id) || typeof asset.name !== "string" || !asset.name || asset.name.length > 128 || typeof asset.res_url !== "string")
358
+ throw new Error("asset3d_search_response_invalid");
359
+ seen.add(asset.id);
360
+ downloadOrigin(asset.res_url);
361
+ const path = new URL(asset.res_url).pathname;
362
+ const format = String(asset.file_format || asset.format || path.split(".").pop() || "unknown").toLowerCase();
363
+ return { assetId: asset.id, name: asset.name, format, downloadUrl: asset.res_url };
364
+ });
365
+ }
366
+ async function checkAssetLibraryAccess(config) {
367
+ const candidates = await searchLibrary(config, "tree");
368
+ if (!candidates.length)
369
+ throw new Error("asset3d_access_validation_inconclusive: no download origin returned");
370
+ const origins = canonicalizeOrigins([...new Set(candidates.map((c) => downloadOrigin(c.downloadUrl)))]);
371
+ return { serviceRoot: config.serviceRoot, authentication: "sandbox-key", downloadOrigins: origins.values };
372
+ }
373
+
374
+ // extensions/asset3d/src/library.ts
375
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "node:fs";
376
+ import { dirname as dirname4, resolve as resolve7 } from "node:path";
377
+
378
+ // node_modules/fflate/esm/index.mjs
379
+ import { createRequire } from "module";
380
+ var require2 = createRequire("/");
381
+ var Worker;
382
+ try {
383
+ Worker = require2("worker_threads").Worker;
384
+ } catch (e) {}
385
+ var u8 = Uint8Array;
386
+ var u16 = Uint16Array;
387
+ var i32 = Int32Array;
388
+ var fleb = new u8([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0, 0]);
389
+ var fdeb = new u8([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0]);
390
+ var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
391
+ var freb = function(eb, start) {
392
+ var b = new u16(31);
393
+ for (var i = 0;i < 31; ++i) {
394
+ b[i] = start += 1 << eb[i - 1];
395
+ }
396
+ var r = new i32(b[30]);
397
+ for (var i = 1;i < 30; ++i) {
398
+ for (var j = b[i];j < b[i + 1]; ++j) {
399
+ r[j] = j - b[i] << 5 | i;
400
+ }
401
+ }
402
+ return { b, r };
403
+ };
404
+ var _a = freb(fleb, 2);
405
+ var fl = _a.b;
406
+ var revfl = _a.r;
407
+ fl[28] = 258, revfl[258] = 28;
408
+ var _b = freb(fdeb, 0);
409
+ var fd = _b.b;
410
+ var revfd = _b.r;
411
+ var rev = new u16(32768);
412
+ for (i = 0;i < 32768; ++i) {
413
+ x = (i & 43690) >> 1 | (i & 21845) << 1;
414
+ x = (x & 52428) >> 2 | (x & 13107) << 2;
415
+ x = (x & 61680) >> 4 | (x & 3855) << 4;
416
+ rev[i] = ((x & 65280) >> 8 | (x & 255) << 8) >> 1;
417
+ }
418
+ var x;
419
+ var i;
420
+ var hMap = function(cd, mb, r) {
421
+ var s = cd.length;
422
+ var i2 = 0;
423
+ var l = new u16(mb);
424
+ for (;i2 < s; ++i2) {
425
+ if (cd[i2])
426
+ ++l[cd[i2] - 1];
427
+ }
428
+ var le = new u16(mb);
429
+ for (i2 = 1;i2 < mb; ++i2) {
430
+ le[i2] = le[i2 - 1] + l[i2 - 1] << 1;
431
+ }
432
+ var co;
433
+ if (r) {
434
+ co = new u16(1 << mb);
435
+ var rvb = 15 - mb;
436
+ for (i2 = 0;i2 < s; ++i2) {
437
+ if (cd[i2]) {
438
+ var sv = i2 << 4 | cd[i2];
439
+ var r_1 = mb - cd[i2];
440
+ var v = le[cd[i2] - 1]++ << r_1;
441
+ for (var m = v | (1 << r_1) - 1;v <= m; ++v) {
442
+ co[rev[v] >> rvb] = sv;
443
+ }
444
+ }
445
+ }
446
+ } else {
447
+ co = new u16(s);
448
+ for (i2 = 0;i2 < s; ++i2) {
449
+ if (cd[i2]) {
450
+ co[i2] = rev[le[cd[i2] - 1]++] >> 15 - cd[i2];
451
+ }
452
+ }
453
+ }
454
+ return co;
455
+ };
456
+ var flt = new u8(288);
457
+ for (i = 0;i < 144; ++i)
458
+ flt[i] = 8;
459
+ var i;
460
+ for (i = 144;i < 256; ++i)
461
+ flt[i] = 9;
462
+ var i;
463
+ for (i = 256;i < 280; ++i)
464
+ flt[i] = 7;
465
+ var i;
466
+ for (i = 280;i < 288; ++i)
467
+ flt[i] = 8;
468
+ var i;
469
+ var fdt = new u8(32);
470
+ for (i = 0;i < 32; ++i)
471
+ fdt[i] = 5;
472
+ var i;
473
+ var flrm = /* @__PURE__ */ hMap(flt, 9, 1);
474
+ var fdrm = /* @__PURE__ */ hMap(fdt, 5, 1);
475
+ var max = function(a) {
476
+ var m = a[0];
477
+ for (var i2 = 1;i2 < a.length; ++i2) {
478
+ if (a[i2] > m)
479
+ m = a[i2];
480
+ }
481
+ return m;
482
+ };
483
+ var bits = function(d, p, m) {
484
+ var o = p / 8 | 0;
485
+ return (d[o] | d[o + 1] << 8) >> (p & 7) & m;
486
+ };
487
+ var bits16 = function(d, p) {
488
+ var o = p / 8 | 0;
489
+ return (d[o] | d[o + 1] << 8 | d[o + 2] << 16) >> (p & 7);
490
+ };
491
+ var shft = function(p) {
492
+ return (p + 7) / 8 | 0;
493
+ };
494
+ var slc = function(v, s, e) {
495
+ if (s == null || s < 0)
496
+ s = 0;
497
+ if (e == null || e > v.length)
498
+ e = v.length;
499
+ return new u8(v.subarray(s, e));
500
+ };
501
+ var ec = [
502
+ "unexpected EOF",
503
+ "invalid block type",
504
+ "invalid length/literal",
505
+ "invalid distance",
506
+ "stream finished",
507
+ "no stream handler",
508
+ ,
509
+ "no callback",
510
+ "invalid UTF-8 data",
511
+ "extra field too long",
512
+ "date not in range 1980-2099",
513
+ "filename too long",
514
+ "stream finishing",
515
+ "invalid zip data"
516
+ ];
517
+ var err = function(ind, msg, nt) {
518
+ var e = new Error(msg || ec[ind]);
519
+ e.code = ind;
520
+ if (Error.captureStackTrace)
521
+ Error.captureStackTrace(e, err);
522
+ if (!nt)
523
+ throw e;
524
+ return e;
525
+ };
526
+ var inflt = function(dat, st, buf, dict) {
527
+ var sl = dat.length, dl = dict ? dict.length : 0;
528
+ if (!sl || st.f && !st.l)
529
+ return buf || new u8(0);
530
+ var noBuf = !buf;
531
+ var resize = noBuf || st.i != 2;
532
+ var noSt = st.i;
533
+ if (noBuf)
534
+ buf = new u8(sl * 3);
535
+ var cbuf = function(l2) {
536
+ var bl = buf.length;
537
+ if (l2 > bl) {
538
+ var nbuf = new u8(Math.max(bl * 2, l2));
539
+ nbuf.set(buf);
540
+ buf = nbuf;
541
+ }
542
+ };
543
+ var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n;
544
+ var tbts = sl * 8;
545
+ do {
546
+ if (!lm) {
547
+ final = bits(dat, pos, 1);
548
+ var type = bits(dat, pos + 1, 3);
549
+ pos += 3;
550
+ if (!type) {
551
+ var s = shft(pos) + 4, l = dat[s - 4] | dat[s - 3] << 8, t = s + l;
552
+ if (t > sl) {
553
+ if (noSt)
554
+ err(0);
555
+ break;
556
+ }
557
+ if (resize)
558
+ cbuf(bt + l);
559
+ buf.set(dat.subarray(s, t), bt);
560
+ st.b = bt += l, st.p = pos = t * 8, st.f = final;
561
+ continue;
562
+ } else if (type == 1)
563
+ lm = flrm, dm = fdrm, lbt = 9, dbt = 5;
564
+ else if (type == 2) {
565
+ var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4;
566
+ var tl = hLit + bits(dat, pos + 5, 31) + 1;
567
+ pos += 14;
568
+ var ldt = new u8(tl);
569
+ var clt = new u8(19);
570
+ for (var i2 = 0;i2 < hcLen; ++i2) {
571
+ clt[clim[i2]] = bits(dat, pos + i2 * 3, 7);
572
+ }
573
+ pos += hcLen * 3;
574
+ var clb = max(clt), clbmsk = (1 << clb) - 1;
575
+ var clm = hMap(clt, clb, 1);
576
+ for (var i2 = 0;i2 < tl; ) {
577
+ var r = clm[bits(dat, pos, clbmsk)];
578
+ pos += r & 15;
579
+ var s = r >> 4;
580
+ if (s < 16) {
581
+ ldt[i2++] = s;
582
+ } else {
583
+ var c = 0, n = 0;
584
+ if (s == 16)
585
+ n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i2 - 1];
586
+ else if (s == 17)
587
+ n = 3 + bits(dat, pos, 7), pos += 3;
588
+ else if (s == 18)
589
+ n = 11 + bits(dat, pos, 127), pos += 7;
590
+ while (n--)
591
+ ldt[i2++] = c;
592
+ }
593
+ }
594
+ var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit);
595
+ lbt = max(lt);
596
+ dbt = max(dt);
597
+ lm = hMap(lt, lbt, 1);
598
+ dm = hMap(dt, dbt, 1);
599
+ } else
600
+ err(1);
601
+ if (pos > tbts) {
602
+ if (noSt)
603
+ err(0);
604
+ break;
605
+ }
606
+ }
607
+ if (resize)
608
+ cbuf(bt + 131072);
609
+ var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1;
610
+ var lpos = pos;
611
+ for (;; lpos = pos) {
612
+ var c = lm[bits16(dat, pos) & lms], sym = c >> 4;
613
+ pos += c & 15;
614
+ if (pos > tbts) {
615
+ if (noSt)
616
+ err(0);
617
+ break;
618
+ }
619
+ if (!c)
620
+ err(2);
621
+ if (sym < 256)
622
+ buf[bt++] = sym;
623
+ else if (sym == 256) {
624
+ lpos = pos, lm = null;
625
+ break;
626
+ } else {
627
+ var add = sym - 254;
628
+ if (sym > 264) {
629
+ var i2 = sym - 257, b = fleb[i2];
630
+ add = bits(dat, pos, (1 << b) - 1) + fl[i2];
631
+ pos += b;
632
+ }
633
+ var d = dm[bits16(dat, pos) & dms], dsym = d >> 4;
634
+ if (!d)
635
+ err(3);
636
+ pos += d & 15;
637
+ var dt = fd[dsym];
638
+ if (dsym > 3) {
639
+ var b = fdeb[dsym];
640
+ dt += bits16(dat, pos) & (1 << b) - 1, pos += b;
641
+ }
642
+ if (pos > tbts) {
643
+ if (noSt)
644
+ err(0);
645
+ break;
646
+ }
647
+ if (resize)
648
+ cbuf(bt + 131072);
649
+ var end = bt + add;
650
+ if (bt < dt) {
651
+ var shift = dl - dt, dend = Math.min(dt, end);
652
+ if (shift + bt < 0)
653
+ err(3);
654
+ for (;bt < dend; ++bt)
655
+ buf[bt] = dict[shift + bt];
656
+ }
657
+ for (;bt < end; ++bt)
658
+ buf[bt] = buf[bt - dt];
659
+ }
660
+ }
661
+ st.l = lm, st.p = lpos, st.b = bt, st.f = final;
662
+ if (lm)
663
+ final = 1, st.m = lbt, st.d = dm, st.n = dbt;
664
+ } while (!final);
665
+ return bt != buf.length && noBuf ? slc(buf, 0, bt) : buf.subarray(0, bt);
666
+ };
667
+ var et = /* @__PURE__ */ new u8(0);
668
+ var b2 = function(d, b) {
669
+ return d[b] | d[b + 1] << 8;
670
+ };
671
+ var b4 = function(d, b) {
672
+ return (d[b] | d[b + 1] << 8 | d[b + 2] << 16 | d[b + 3] << 24) >>> 0;
673
+ };
674
+ var b8 = function(d, b) {
675
+ return b4(d, b) + b4(d, b + 4) * 4294967296;
676
+ };
677
+ function inflateSync(data, opts) {
678
+ return inflt(data, { i: 2 }, opts && opts.out, opts && opts.dictionary);
679
+ }
680
+ var td = typeof TextDecoder != "undefined" && /* @__PURE__ */ new TextDecoder;
681
+ var tds = 0;
682
+ try {
683
+ td.decode(et, { stream: true });
684
+ tds = 1;
685
+ } catch (e) {}
686
+ var dutf8 = function(d) {
687
+ for (var r = "", i2 = 0;; ) {
688
+ var c = d[i2++];
689
+ var eb = (c > 127) + (c > 223) + (c > 239);
690
+ if (i2 + eb > d.length)
691
+ return { s: r, r: slc(d, i2 - 1) };
692
+ if (!eb)
693
+ r += String.fromCharCode(c);
694
+ else if (eb == 3) {
695
+ c = ((c & 15) << 18 | (d[i2++] & 63) << 12 | (d[i2++] & 63) << 6 | d[i2++] & 63) - 65536, r += String.fromCharCode(55296 | c >> 10, 56320 | c & 1023);
696
+ } else if (eb & 1)
697
+ r += String.fromCharCode((c & 31) << 6 | d[i2++] & 63);
698
+ else
699
+ r += String.fromCharCode((c & 15) << 12 | (d[i2++] & 63) << 6 | d[i2++] & 63);
700
+ }
701
+ };
702
+ function strFromU8(dat, latin1) {
703
+ if (latin1) {
704
+ var r = "";
705
+ for (var i2 = 0;i2 < dat.length; i2 += 16384)
706
+ r += String.fromCharCode.apply(null, dat.subarray(i2, i2 + 16384));
707
+ return r;
708
+ } else if (td) {
709
+ return td.decode(dat);
710
+ } else {
711
+ var _a2 = dutf8(dat), s = _a2.s, r = _a2.r;
712
+ if (r.length)
713
+ err(8);
714
+ return s;
715
+ }
716
+ }
717
+ var slzh = function(d, b) {
718
+ return b + 30 + b2(d, b + 26) + b2(d, b + 28);
719
+ };
720
+ var zh = function(d, b, z) {
721
+ var fnl = b2(d, b + 28), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl, bs = b4(d, b + 20);
722
+ var _a2 = z && bs == 4294967295 ? z64e(d, es) : [bs, b4(d, b + 24), b4(d, b + 42)], sc = _a2[0], su = _a2[1], off = _a2[2];
723
+ return [b2(d, b + 10), sc, su, fn, es + b2(d, b + 30) + b2(d, b + 32), off];
724
+ };
725
+ var z64e = function(d, b) {
726
+ for (;b2(d, b) != 1; b += 4 + b2(d, b + 2))
727
+ ;
728
+ return [b8(d, b + 12), b8(d, b + 4), b8(d, b + 20)];
729
+ };
730
+ function unzipSync(data, opts) {
731
+ var files = {};
732
+ var e = data.length - 22;
733
+ for (;b4(data, e) != 101010256; --e) {
734
+ if (!e || data.length - e > 65558)
735
+ err(13);
736
+ }
737
+ var c = b2(data, e + 8);
738
+ if (!c)
739
+ return {};
740
+ var o = b4(data, e + 16);
741
+ var z = o == 4294967295 || c == 65535;
742
+ if (z) {
743
+ var ze = b4(data, e - 12);
744
+ z = b4(data, ze) == 101075792;
745
+ if (z) {
746
+ c = b4(data, ze + 32);
747
+ o = b4(data, ze + 48);
748
+ }
749
+ }
750
+ var fltr = opts && opts.filter;
751
+ for (var i2 = 0;i2 < c; ++i2) {
752
+ var _a2 = zh(data, o, z), c_2 = _a2[0], sc = _a2[1], su = _a2[2], fn = _a2[3], no = _a2[4], off = _a2[5], b = slzh(data, off);
753
+ o = no;
754
+ if (!fltr || fltr({
755
+ name: fn,
756
+ size: sc,
757
+ originalSize: su,
758
+ compression: c_2
759
+ })) {
760
+ if (!c_2)
761
+ files[fn] = slc(data, b, b + sc);
762
+ else if (c_2 == 8)
763
+ files[fn] = inflateSync(data.subarray(b, b + sc), { out: new u8(su) });
764
+ else
765
+ err(14, "unknown compression type " + c_2);
766
+ }
767
+ }
768
+ return files;
769
+ }
770
+
771
+ // extensions/asset3d/src/install.ts
772
+ import { readFileSync as readFileSync2 } from "node:fs";
773
+ import { resolve as resolve2 } from "node:path";
774
+ function readAsset3dConfig(root) {
775
+ const bytes = readFileSync2(resolve2(root, ".forgeax/extensions/asset3d/config.json"));
776
+ if (bytes.length > 65536)
777
+ throw new Error("asset3d_config_invalid");
778
+ const value = JSON.parse(bytes.toString());
779
+ if (value.schemaVersion !== INSTALL_SCHEMA || !["aw", "ea"].includes(value.library) || typeof value.credentialFile !== "string" || !Array.isArray(value.downloadOrigins) || normalizeAssetLibraryServiceRoot(value.serviceRoot) !== value.serviceRoot || canonicalizeOrigins(value.downloadOrigins).digest !== value.originSetDigest)
780
+ throw new Error("asset3d_config_invalid");
781
+ return value;
782
+ }
783
+
784
+ // extensions/asset3d/src/schema.ts
785
+ var ID = /^[A-Za-z0-9._-]{1,128}$/;
786
+ var DIGEST = /^[a-f0-9]{64}$/;
787
+ var ERROR_CODES = new Set([
788
+ "asset_not_found",
789
+ "asset_identity_missing",
790
+ "search_timeout",
791
+ "search_upstream_error",
792
+ "download_timeout",
793
+ "download_origin_rejected",
794
+ "download_too_large",
795
+ "archive_rejected",
796
+ "conversion_failed",
797
+ "digest_failed",
798
+ "batch_timeout",
799
+ "internal_error"
800
+ ]);
801
+ var ROLES = new Set(["primary-pack", "auxiliary-pack", "primary-model", "animation", "auxiliary-model", "texture", "metadata"]);
802
+ function exactKeys(record, allowed, field) {
803
+ const extras = Object.keys(record).filter((key) => !allowed.includes(key));
804
+ if (extras.length)
805
+ throw new Error(`provider_result_invalid: unknown ${field} fields`);
806
+ }
807
+ function safeRelativePath(value) {
808
+ if (typeof value !== "string" || value.length < 1 || value.length > 512 || value.startsWith("/") || value.includes("\\")) {
809
+ throw new Error("provider_result_invalid: manifest path must be relative POSIX");
810
+ }
811
+ const parts = value.split("/");
812
+ if (parts.some((part) => !part || part === "." || part === ".."))
813
+ throw new Error("provider_result_invalid: unsafe manifest path");
814
+ return value;
815
+ }
816
+ function number(value, min, max2, field) {
817
+ if (!Number.isInteger(value) || value < min || value > max2)
818
+ throw new Error(`provider_result_invalid: ${field}`);
819
+ return value;
820
+ }
821
+ function text(value, min, max2, field) {
822
+ if (typeof value !== "string" || [...value].length < min || [...value].length > max2)
823
+ throw new Error(`provider_result_invalid: ${field}`);
824
+ return value;
825
+ }
826
+ function aggregate(entries) {
827
+ const bytes = entries.map((entry) => `${entry.path}\x00${entry.bytes}\x00${entry.sha256}
828
+ `).join("");
829
+ return sha256(bytes);
830
+ }
831
+ function parseProviderResult(input, expectedVersion, expectedOriginSetDigest) {
832
+ const bytes = typeof input === "string" ? Buffer.byteLength(input) : input.byteLength;
833
+ if (bytes > MAX_JSON_BYTES)
834
+ throw new Error("provider_result_too_large: stdin exceeds 1 MiB");
835
+ let raw;
836
+ try {
837
+ raw = JSON.parse(typeof input === "string" ? input : Buffer.from(input).toString("utf8"));
838
+ } catch {
839
+ throw new Error("provider_result_invalid: stdin is not JSON");
840
+ }
841
+ if (!raw || typeof raw !== "object" || Array.isArray(raw))
842
+ throw new Error("provider_result_invalid: top-level object required");
843
+ const root = raw;
844
+ exactKeys(root, ["schemaVersion", "total", "succeeded", "failed", "results", "receipt"], "top-level");
845
+ if (root.schemaVersion !== PROVIDER_RESULT_SCHEMA)
846
+ throw new Error("provider_result_invalid: schemaVersion");
847
+ const total = number(root.total, 1, 16, "total");
848
+ const succeeded = number(root.succeeded, 0, total, "succeeded");
849
+ const failed = number(root.failed, 0, total, "failed");
850
+ if (succeeded + failed !== total || !Array.isArray(root.results) || root.results.length !== total)
851
+ throw new Error("provider_result_invalid: counts");
852
+ let receipt;
853
+ if (root.receipt !== undefined) {
854
+ if (!root.receipt || typeof root.receipt !== "object" || Array.isArray(root.receipt)) {
855
+ throw new Error("provider_result_invalid: receipt object required");
856
+ }
857
+ const value = root.receipt;
858
+ exactKeys(value, ["schemaVersion", "provider", "adapterVersion", "originSetDigest"], "receipt");
859
+ if (value.schemaVersion !== PROVIDER_RECEIPT_SCHEMA || value.provider !== "ea-3d" || value.adapterVersion !== expectedVersion || value.originSetDigest !== expectedOriginSetDigest) {
860
+ throw new Error("provider_result_invalid: receipt identity");
861
+ }
862
+ receipt = {
863
+ schemaVersion: PROVIDER_RECEIPT_SCHEMA,
864
+ provider: "ea-3d",
865
+ adapterVersion: expectedVersion,
866
+ originSetDigest: expectedOriginSetDigest
867
+ };
868
+ }
869
+ if (succeeded > 0 && receipt === undefined)
870
+ throw new Error("provider_result_invalid: success receipt required");
871
+ const indices = new Set;
872
+ let okCount = 0;
873
+ const results = root.results.map((candidate) => {
874
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate))
875
+ throw new Error("provider_result_invalid: result row");
876
+ const row = candidate;
877
+ const queryIndex = number(row.queryIndex, 0, total - 1, "queryIndex");
878
+ if (indices.has(queryIndex))
879
+ throw new Error("provider_result_invalid: duplicate queryIndex");
880
+ indices.add(queryIndex);
881
+ const query = text(row.query, 1, 200, "query");
882
+ if (row.status === "error") {
883
+ exactKeys(row, ["status", "queryIndex", "query", "code", "retryable", "message"], "error");
884
+ if (typeof row.code !== "string" || !ERROR_CODES.has(row.code) || typeof row.retryable !== "boolean")
885
+ throw new Error("provider_result_invalid: error row");
886
+ const message = text(row.message, 0, 256, "message");
887
+ return { status: "error", queryIndex, query, code: row.code, retryable: row.retryable, message };
888
+ }
889
+ if (row.status !== "ok")
890
+ throw new Error("provider_result_invalid: status");
891
+ const isPack = row.deliveredFormat === "pack";
892
+ const primaryKey = isPack ? "primaryPack" : "primaryModel";
893
+ const primaryRole = isPack ? "primary-pack" : "primary-model";
894
+ exactKeys(row, ["status", "queryIndex", "query", "provider", "providerAssetId", "assetName", "deliveredFormat", "sha256", "bytes", primaryKey, "manifest", "originSetDigest", "downloaded_to"], "success");
895
+ okCount++;
896
+ if (row.provider !== "ea-3d" || row.deliveredFormat !== "glb" && !isPack || typeof row.providerAssetId !== "string" || !ID.test(row.providerAssetId) || row.providerAssetId === "." || row.providerAssetId === "..") {
897
+ throw new Error("provider_result_invalid: success identity");
898
+ }
899
+ const assetName = text(row.assetName, 1, 128, "assetName");
900
+ const itemBytes = number(row.bytes, 1, 268435456, "bytes");
901
+ if (typeof row.sha256 !== "string" || !DIGEST.test(row.sha256))
902
+ throw new Error("provider_result_invalid: aggregate digest");
903
+ if (!Array.isArray(row.manifest) || row.manifest.length < 1 || row.manifest.length > 1024)
904
+ throw new Error("provider_result_invalid: manifest count");
905
+ const seen = new Set;
906
+ const manifest = row.manifest.map((entry) => {
907
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
908
+ throw new Error("provider_result_invalid: manifest entry");
909
+ const item = entry;
910
+ exactKeys(item, ["path", "role", "bytes", "sha256"], "manifest");
911
+ const path = safeRelativePath(item.path);
912
+ if (seen.has(path))
913
+ throw new Error("provider_result_invalid: duplicate manifest path");
914
+ seen.add(path);
915
+ if (typeof item.role !== "string" || !ROLES.has(item.role))
916
+ throw new Error("provider_result_invalid: manifest role");
917
+ const role = item.role;
918
+ const entryBytes = number(item.bytes, 1, 134217728, "manifest bytes");
919
+ if (typeof item.sha256 !== "string" || !DIGEST.test(item.sha256))
920
+ throw new Error("provider_result_invalid: manifest digest");
921
+ if ((role === "primary-model" || role === "animation" || role === "auxiliary-model") && !path.toLowerCase().endsWith(".glb")) {
922
+ throw new Error("provider_result_invalid: model entry must be GLB");
923
+ }
924
+ if ((role === "primary-pack" || role === "auxiliary-pack") && (!isPack || !/\.pack\.(json|ts)$/i.test(path))) {
925
+ throw new Error("provider_result_invalid: pack entry must be authored Pack");
926
+ }
927
+ if (isPack && role === "primary-model")
928
+ throw new Error("provider_result_invalid: multiple primary formats");
929
+ return { path, role, bytes: entryBytes, sha256: item.sha256 };
930
+ });
931
+ const sorted = [...manifest].sort((left, right) => Buffer.from(left.path).compare(Buffer.from(right.path)));
932
+ if (manifest.some((entry, index) => entry.path !== sorted[index].path))
933
+ throw new Error("provider_result_invalid: manifest must be UTF-8 path sorted");
934
+ const primary = manifest.filter((entry) => entry.role === primaryRole);
935
+ if (primary.length !== 1 || row[primaryKey] !== primary[0].path)
936
+ throw new Error("provider_result_invalid: primary model");
937
+ if (manifest.reduce((sum, entry) => sum + entry.bytes, 0) !== itemBytes || aggregate(manifest) !== row.sha256) {
938
+ throw new Error("provider_result_invalid: byte or aggregate digest mismatch");
939
+ }
940
+ if (row.originSetDigest !== expectedOriginSetDigest)
941
+ throw new Error("provider_result_invalid: originSetDigest identity");
942
+ return {
943
+ status: "ok",
944
+ queryIndex,
945
+ query,
946
+ provider: "ea-3d",
947
+ providerAssetId: row.providerAssetId,
948
+ assetName,
949
+ sha256: row.sha256,
950
+ bytes: itemBytes,
951
+ ...isPack ? { deliveredFormat: "pack", primaryPack: row.primaryPack } : { deliveredFormat: "glb", primaryModel: row.primaryModel },
952
+ manifest,
953
+ originSetDigest: expectedOriginSetDigest,
954
+ ...row.downloaded_to === undefined ? {} : { downloaded_to: row.downloaded_to }
955
+ };
956
+ });
957
+ if (okCount !== succeeded || total - okCount !== failed)
958
+ throw new Error("provider_result_invalid: status counts");
959
+ return { schemaVersion: PROVIDER_RESULT_SCHEMA, total, succeeded, failed, results, ...receipt ? { receipt } : {} };
960
+ }
961
+
962
+ // extensions/asset3d/src/transaction.ts
963
+ import { spawnSync } from "node:child_process";
964
+ import { randomUUID } from "node:crypto";
965
+ import {
966
+ closeSync as closeSync2,
967
+ constants,
968
+ cpSync,
969
+ existsSync as existsSync4,
970
+ fstatSync,
971
+ lstatSync as lstatSync5,
972
+ mkdirSync as mkdirSync2,
973
+ openSync as openSync2,
974
+ readFileSync as readFileSync6,
975
+ readdirSync as readdirSync2,
976
+ realpathSync as realpathSync4,
977
+ renameSync as renameSync2,
978
+ rmSync as rmSync2,
979
+ statSync as statSync2,
980
+ unlinkSync as unlinkSync2,
981
+ writeFileSync as writeFileSync3
982
+ } from "node:fs";
983
+ import { isAbsolute as isAbsolute5, relative as relative4, resolve as resolve6, sep as sep4 } from "node:path";
984
+
985
+ // src/engine/release.ts
986
+ import { existsSync as existsSync3, lstatSync as lstatSync3, readFileSync as readFileSync4, realpathSync as realpathSync2 } from "node:fs";
987
+ import { isAbsolute as isAbsolute3, relative as relative2, resolve as resolve4, sep as sep2 } from "node:path";
988
+
989
+ // src/engine/constants.ts
990
+ var ENGINE_VERSION = "0.1.26";
991
+ var ENGINE_COMMIT = "f3d0db12405e168e4204e32a9cde32e0df8d87ae";
992
+ var ENGINE_SDK_PACKAGE = "@forgeax/engine-sdk";
993
+ var PNPM_VERSION = "11.7.0";
994
+
995
+ // src/engine/carrier.ts
996
+ import {
997
+ chmodSync as chmodSync2,
998
+ existsSync as existsSync2,
999
+ lstatSync as lstatSync2,
1000
+ mkdtempSync,
1001
+ readFileSync as readFileSync3,
1002
+ readdirSync,
1003
+ realpathSync,
1004
+ rmSync,
1005
+ writeFileSync as writeFileSync2
1006
+ } from "node:fs";
1007
+ import { createRequire as createRequire2 } from "node:module";
1008
+ import { basename, delimiter, dirname as dirname3, isAbsolute as isAbsolute2, relative, resolve as resolve3, sep } from "node:path";
1009
+ import { fileURLToPath } from "node:url";
1010
+ var SDK_MANIFEST_SCHEMA = "1.7.0";
1011
+ var SDK_CLI_RELATIVE = ["bin", "forgeax.mjs"];
1012
+ function packageJson(path) {
1013
+ try {
1014
+ const value = JSON.parse(readFileSync3(path, "utf8"));
1015
+ if (value === null || typeof value !== "object" || Array.isArray(value))
1016
+ throw new Error("not-an-object");
1017
+ return value;
1018
+ } catch (error) {
1019
+ throw new Error(`engine_sdk_manifest_invalid: cannot read ${path}: ${error instanceof Error ? error.message : String(error)}`);
1020
+ }
1021
+ }
1022
+ function confined(root, candidate) {
1023
+ const rel = relative(root, candidate);
1024
+ return rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute2(rel);
1025
+ }
1026
+ function canonicalRegularFile(path, code, root) {
1027
+ if (!existsSync2(path) || !lstatSync2(path).isFile())
1028
+ throw new Error(`${code}: ${path}`);
1029
+ const canonical = realpathSync(path);
1030
+ if (!confined(root, canonical))
1031
+ throw new Error(`${code}_escape: ${canonical}`);
1032
+ return canonical;
1033
+ }
1034
+ function pluginRootFromModule() {
1035
+ let cursor = resolve3(dirname3(fileURLToPath(import.meta.url)));
1036
+ for (;; ) {
1037
+ const manifestPath = resolve3(cursor, "package.json");
1038
+ try {
1039
+ if (packageJson(manifestPath).name === "@forgeax/game")
1040
+ return realpathSync(cursor);
1041
+ } catch {}
1042
+ const parent = dirname3(cursor);
1043
+ if (parent === cursor)
1044
+ throw new Error("engine_sdk_plugin_missing: cannot locate @forgeax/game package root");
1045
+ cursor = parent;
1046
+ }
1047
+ }
1048
+ function manifestFromEntry(entry, name) {
1049
+ let cursor = resolve3(dirname3(entry));
1050
+ for (;; ) {
1051
+ const manifestPath = resolve3(cursor, "package.json");
1052
+ try {
1053
+ if (packageJson(manifestPath).name === name)
1054
+ return realpathSync(manifestPath);
1055
+ } catch {}
1056
+ const parent = dirname3(cursor);
1057
+ if (parent === cursor)
1058
+ throw new Error(`engine_sdk_dependency_invalid: ${name} package.json was not found`);
1059
+ cursor = parent;
1060
+ }
1061
+ }
1062
+ function installationRoot(pluginRoot) {
1063
+ let cursor = pluginRoot;
1064
+ let found;
1065
+ for (;; ) {
1066
+ if (basename(cursor) === "node_modules")
1067
+ found = cursor;
1068
+ const parent = dirname3(cursor);
1069
+ if (parent === cursor)
1070
+ break;
1071
+ cursor = parent;
1072
+ }
1073
+ if (found === undefined) {
1074
+ throw new Error(`engine_sdk_install_root_missing: ${pluginRoot} is not inside a node_modules installation`);
1075
+ }
1076
+ return realpathSync(found);
1077
+ }
1078
+ function resolveDependency(pluginRoot, installRoot, name, missingCode) {
1079
+ const packageRequire = createRequire2(resolve3(pluginRoot, "package.json"));
1080
+ let manifestPath;
1081
+ try {
1082
+ try {
1083
+ manifestPath = packageRequire.resolve(`${name}/package.json`, { paths: [pluginRoot] });
1084
+ } catch {
1085
+ manifestPath = manifestFromEntry(packageRequire.resolve(name, { paths: [pluginRoot] }), name);
1086
+ }
1087
+ } catch (error) {
1088
+ throw new Error(`${missingCode}: ${name} is not installed in the Game Plugin dependency graph${error instanceof Error ? ` (${error.message})` : ""}`);
1089
+ }
1090
+ const canonicalManifest = realpathSync(manifestPath);
1091
+ const root = realpathSync(dirname3(canonicalManifest));
1092
+ if (!confined(installRoot, pluginRoot) || !confined(installRoot, root)) {
1093
+ throw new Error(`engine_sdk_dependency_escape: ${name} resolves outside the Game Plugin installation root`);
1094
+ }
1095
+ return { root, manifest: packageJson(canonicalManifest) };
1096
+ }
1097
+ function sdkManifest(path) {
1098
+ const value = packageJson(path);
1099
+ const packageEntries = Array.isArray(value.packages) ? value.packages : [];
1100
+ const requiredPackages = new Map;
1101
+ const packageNames = new Set;
1102
+ let packagesValid = Array.isArray(value.packages);
1103
+ for (const entry of packageEntries) {
1104
+ if (entry === null || typeof entry !== "object" || Array.isArray(entry)) {
1105
+ packagesValid = false;
1106
+ continue;
1107
+ }
1108
+ const name = entry.name;
1109
+ const version = entry.version;
1110
+ if (typeof name !== "string" || typeof version !== "string" || packageNames.has(name)) {
1111
+ packagesValid = false;
1112
+ } else {
1113
+ packageNames.add(name);
1114
+ requiredPackages.set(name, version);
1115
+ }
1116
+ }
1117
+ if (!packagesValid || value.schemaVersion !== SDK_MANIFEST_SCHEMA || value.sdkVersion !== ENGINE_VERSION || value.engineCommit !== ENGINE_COMMIT || value.requirements?.pnpm !== PNPM_VERSION || requiredPackages.get("@forgeax/engine") !== ENGINE_VERSION || requiredPackages.get("@forgeax/engine-devkit") !== ENGINE_VERSION) {
1118
+ throw new Error("engine_sdk_manifest_mismatch: carrier does not identify the approved Engine/DevKit/pnpm set");
1119
+ }
1120
+ return value;
1121
+ }
1122
+ function packageBin(manifest, name) {
1123
+ if (typeof manifest.bin === "string")
1124
+ return manifest.bin;
1125
+ if (manifest.bin !== null && typeof manifest.bin === "object") {
1126
+ const value = manifest.bin[name];
1127
+ if (typeof value === "string" && value.length > 0)
1128
+ return value;
1129
+ }
1130
+ throw new Error(`engine_sdk_cli_invalid: ${name} package does not declare a ${name} binary`);
1131
+ }
1132
+ function resolveGamePluginCarrier(options = {}) {
1133
+ const configuredRoot = resolve3(options.pluginRoot ?? pluginRootFromModule());
1134
+ let pluginRoot;
1135
+ try {
1136
+ pluginRoot = realpathSync(configuredRoot);
1137
+ } catch (error) {
1138
+ throw new Error(`engine_sdk_plugin_missing: cannot read Game Plugin root ${configuredRoot}${error instanceof Error ? ` (${error.message})` : ""}`);
1139
+ }
1140
+ const plugin = packageJson(resolve3(pluginRoot, "package.json"));
1141
+ const installRoot = installationRoot(pluginRoot);
1142
+ if (!confined(installRoot, pluginRoot)) {
1143
+ throw new Error(`engine_sdk_plugin_escape: ${pluginRoot} is outside its installation root`);
1144
+ }
1145
+ if (plugin.name !== "@forgeax/game")
1146
+ throw new Error(`engine_sdk_plugin_invalid: ${pluginRoot}`);
1147
+ if (plugin.dependencies?.[ENGINE_SDK_PACKAGE] !== ENGINE_VERSION) {
1148
+ throw new Error(`engine_sdk_dependency_mismatch: ${ENGINE_SDK_PACKAGE} must be ${ENGINE_VERSION}`);
1149
+ }
1150
+ if (plugin.dependencies?.pnpm !== PNPM_VERSION) {
1151
+ throw new Error(`pnpm_dependency_mismatch: pnpm must be ${PNPM_VERSION}`);
1152
+ }
1153
+ const carrier = resolveDependency(pluginRoot, installRoot, ENGINE_SDK_PACKAGE, "engine_sdk_carrier_missing");
1154
+ if (carrier.manifest.name !== ENGINE_SDK_PACKAGE || carrier.manifest.version !== ENGINE_VERSION) {
1155
+ throw new Error(`engine_sdk_carrier_mismatch: installed SDK carrier must be ${ENGINE_SDK_PACKAGE}@${ENGINE_VERSION}`);
1156
+ }
1157
+ const sdkRoot = resolve3(carrier.root, "sdk");
1158
+ if (!existsSync2(sdkRoot) || !lstatSync2(sdkRoot).isDirectory()) {
1159
+ throw new Error(`engine_sdk_root_missing: ${sdkRoot}`);
1160
+ }
1161
+ const canonicalSdkRoot = realpathSync(sdkRoot);
1162
+ if (!confined(carrier.root, canonicalSdkRoot))
1163
+ throw new Error("engine_sdk_root_escape: SDK root escaped carrier");
1164
+ const manifest = sdkManifest(resolve3(canonicalSdkRoot, "sdk-manifest.json"));
1165
+ const cliPath = canonicalRegularFile(resolve3(canonicalSdkRoot, ...SDK_CLI_RELATIVE), "engine_sdk_cli_invalid", canonicalSdkRoot);
1166
+ const pnpm = resolveDependency(pluginRoot, installRoot, "pnpm", "pnpm_missing");
1167
+ if (pnpm.manifest.name !== "pnpm" || pnpm.manifest.version !== PNPM_VERSION) {
1168
+ throw new Error(`pnpm_version_mismatch: installed pnpm must be ${PNPM_VERSION}`);
1169
+ }
1170
+ const pnpmCliPath = canonicalRegularFile(resolve3(pnpm.root, packageBin(pnpm.manifest, "pnpm")), "pnpm_cli_invalid", pnpm.root);
1171
+ return {
1172
+ pluginRoot,
1173
+ root: carrier.root,
1174
+ sdkRoot: canonicalSdkRoot,
1175
+ cliPath,
1176
+ pnpmRoot: pnpm.root,
1177
+ pnpmCliPath,
1178
+ sdkManifest: manifest
1179
+ };
1180
+ }
1181
+
1182
+ // src/engine/release.ts
1183
+ function readManifest(path) {
1184
+ let value;
1185
+ try {
1186
+ value = JSON.parse(readFileSync4(path, "utf8"));
1187
+ } catch (error) {
1188
+ throw new Error(`engine_release_manifest_invalid: cannot read ${path}: ${error instanceof Error ? error.message : String(error)}`);
1189
+ }
1190
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
1191
+ throw new Error(`engine_release_manifest_invalid: ${path} is not a JSON object`);
1192
+ }
1193
+ return value;
1194
+ }
1195
+ function confined2(root, candidate) {
1196
+ const rel = relative2(root, candidate);
1197
+ return rel === "" || rel !== ".." && !rel.startsWith(`..${sep2}`) && !isAbsolute3(rel);
1198
+ }
1199
+ function exactPackage(root, name) {
1200
+ const parts = name.slice(1).split("/");
1201
+ const packageRoot = resolve4(root, "node_modules", `@${parts[0]}`, parts[1]);
1202
+ const manifestPath = resolve4(packageRoot, "package.json");
1203
+ if (!existsSync3(manifestPath) || !lstatSync3(manifestPath).isFile()) {
1204
+ throw new Error(`engine_release_missing: ${name} is not installed under ${root}`);
1205
+ }
1206
+ const canonicalRoot = realpathSync2(packageRoot);
1207
+ const canonicalModules = realpathSync2(resolve4(root, "node_modules"));
1208
+ if (!confined2(canonicalModules, canonicalRoot)) {
1209
+ throw new Error(`engine_release_escape: ${name} resolves outside the game node_modules tree`);
1210
+ }
1211
+ return { root: canonicalRoot, manifest: readManifest(realpathSync2(manifestPath)) };
1212
+ }
1213
+ function resolveEngineRelease(gameRoot, options = {}) {
1214
+ const canonicalGameRoot = realpathSync2(resolve4(gameRoot));
1215
+ const gameManifest = readManifest(resolve4(canonicalGameRoot, "package.json"));
1216
+ const declared = gameManifest.dependencies?.["@forgeax/engine"];
1217
+ if (declared !== ENGINE_VERSION) {
1218
+ throw new Error(`engine_release_mismatch: game declares @forgeax/engine=${String(declared)}, expected ${ENGINE_VERSION}`);
1219
+ }
1220
+ const engine = exactPackage(canonicalGameRoot, "@forgeax/engine");
1221
+ if (engine.manifest.name !== "@forgeax/engine" || engine.manifest.version !== ENGINE_VERSION) {
1222
+ throw new Error(`engine_release_mismatch: installed Engine must be @forgeax/engine@${ENGINE_VERSION} from ${ENGINE_COMMIT}`);
1223
+ }
1224
+ const carrier = resolveGamePluginCarrier(options);
1225
+ const carrierCommit = carrier.sdkManifest.engineCommit;
1226
+ if (carrierCommit !== ENGINE_COMMIT) {
1227
+ throw new Error(`engine_release_mismatch: SDK carrier does not identify Engine commit ${ENGINE_COMMIT}`);
1228
+ }
1229
+ const declaredCommit = engine.manifest.forgeax?.engineCommit;
1230
+ if (declaredCommit !== undefined && declaredCommit !== carrierCommit) {
1231
+ throw new Error(`engine_release_mismatch: installed Engine declares ${String(declaredCommit)}, expected ${String(carrierCommit)}`);
1232
+ }
1233
+ const cliPath = resolve4(engine.root, "dist", "bin", "forgeax.mjs");
1234
+ if (!existsSync3(cliPath) || !lstatSync3(cliPath).isFile()) {
1235
+ throw new Error(`engine_cli_missing: ${cliPath}`);
1236
+ }
1237
+ const canonicalCli = realpathSync2(cliPath);
1238
+ if (!confined2(engine.root, canonicalCli)) {
1239
+ throw new Error("engine_cli_escape: Engine CLI resolves outside @forgeax/engine");
1240
+ }
1241
+ return {
1242
+ gameRoot: canonicalGameRoot,
1243
+ packageRoot: engine.root,
1244
+ cliPath: canonicalCli,
1245
+ carrierRoot: carrier.root,
1246
+ version: ENGINE_VERSION,
1247
+ commit: ENGINE_COMMIT
1248
+ };
1249
+ }
1250
+
1251
+ // src/run/engine-preview.ts
1252
+ var ENVELOPE_LIMIT = 1024 * 1024;
1253
+ var LOG_LIMIT = 8 * 1024 * 1024;
1254
+ var trackedStates = new Map;
1255
+ var liveChildren = new Map;
1256
+ var directChildren = new Set;
1257
+ function parseEnvelope(stdout, command) {
1258
+ if (Buffer.byteLength(stdout, "utf8") > ENVELOPE_LIMIT + 1) {
1259
+ throw new Error(`${command}_envelope_too_large`);
1260
+ }
1261
+ const lines = stdout.split(/\r?\n/).filter((line) => line.trim().length > 0);
1262
+ let envelope;
1263
+ let envelopeIndex = -1;
1264
+ for (const [index, line] of lines.entries()) {
1265
+ let parsed;
1266
+ try {
1267
+ parsed = JSON.parse(line);
1268
+ } catch {
1269
+ continue;
1270
+ }
1271
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
1272
+ throw new Error(`${command}_envelope_invalid: unexpected JSON frame`);
1273
+ }
1274
+ if (envelope !== undefined)
1275
+ throw new Error(`${command}_envelope_invalid: multiple JSON frames`);
1276
+ envelope = parsed;
1277
+ envelopeIndex = index;
1278
+ }
1279
+ if (envelope === undefined)
1280
+ throw new Error(`${command}_envelope_invalid: JSON frame is missing`);
1281
+ if (envelopeIndex !== lines.length - 1) {
1282
+ throw new Error(`${command}_envelope_invalid: diagnostics after JSON frame`);
1283
+ }
1284
+ if (envelope.schemaVersion !== "1.0.0" || envelope.command !== command || typeof envelope.ok !== "boolean") {
1285
+ throw new Error(`${command}_envelope_invalid: wrong schema or command`);
1286
+ }
1287
+ if (!envelope.ok) {
1288
+ const error = envelope.error && typeof envelope.error === "object" ? JSON.stringify(envelope.error) : "unknown Engine failure";
1289
+ throw new Error(`${command}_failed: ${error}`);
1290
+ }
1291
+ return envelope;
1292
+ }
1293
+
1294
+ // extensions/asset3d/src/pack-readback.ts
1295
+ import { lstatSync as lstatSync4, readFileSync as readFileSync5, realpathSync as realpathSync3 } from "node:fs";
1296
+ import { isAbsolute as isAbsolute4, relative as relative3, resolve as resolve5, sep as sep3 } from "node:path";
1297
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
1298
+ function object(value) {
1299
+ if (!value || typeof value !== "object" || Array.isArray(value))
1300
+ throw new Error("engine_pack_readback_invalid");
1301
+ return value;
1302
+ }
1303
+ function readPackBuildCatalog(projectRoot, sourcePaths, buildValue) {
1304
+ const build = object(buildValue);
1305
+ const runtime = object(build.runtime);
1306
+ if (build.schemaVersion !== "1.0.0" || !Array.isArray(build.artifacts) || build.artifacts.length > 1e4) {
1307
+ throw new Error("engine_pack_build_manifest_invalid");
1308
+ }
1309
+ const dist = resolve5(projectRoot, "dist");
1310
+ const canonicalDist = realpathSync3(dist);
1311
+ if (canonicalDist !== resolve5(realpathSync3(projectRoot), "dist") || lstatSync4(dist).isSymbolicLink()) {
1312
+ throw new Error("engine_pack_build_path_escape");
1313
+ }
1314
+ const artifacts = new Map;
1315
+ let total = 0;
1316
+ for (const raw of build.artifacts) {
1317
+ const entry = object(raw);
1318
+ const path = safeRelativePath(entry.path);
1319
+ if (artifacts.has(path) || !Number.isSafeInteger(entry.bytes) || entry.bytes < 0 || entry.bytes > 256 * 1024 * 1024 || typeof entry.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(entry.sha256)) {
1320
+ throw new Error("engine_pack_build_manifest_invalid");
1321
+ }
1322
+ total += entry.bytes;
1323
+ if (total > 1024 * 1024 * 1024)
1324
+ throw new Error("engine_pack_build_too_large");
1325
+ const file = resolve5(dist, path);
1326
+ const rel = relative3(canonicalDist, realpathSync3(file));
1327
+ if (isAbsolute4(rel) || rel === ".." || rel.startsWith(`..${sep3}`))
1328
+ throw new Error("engine_pack_build_path_escape");
1329
+ const stat = lstatSync4(file);
1330
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size !== entry.bytes)
1331
+ throw new Error("engine_pack_build_file_invalid");
1332
+ const bytes = readFileSync5(file);
1333
+ if (sha256(bytes) !== entry.sha256)
1334
+ throw new Error("engine_pack_build_digest_mismatch");
1335
+ artifacts.set(path, path.endsWith(".json") ? bytes : Buffer.alloc(0));
1336
+ }
1337
+ const indexPath = safeRelativePath(runtime.packIndexUrl);
1338
+ const indexBytes = artifacts.get(indexPath);
1339
+ if (!indexBytes || indexBytes.length > 16 * 1024 * 1024)
1340
+ throw new Error("engine_pack_catalog_missing");
1341
+ const index = JSON.parse(indexBytes.toString("utf8"));
1342
+ if (!Array.isArray(index))
1343
+ throw new Error("engine_pack_catalog_invalid");
1344
+ const sources = new Set(sourcePaths);
1345
+ const rows = [];
1346
+ const seen = new Set;
1347
+ const inspect = [];
1348
+ for (const raw of index) {
1349
+ const row = object(raw);
1350
+ if (typeof row.sourcePath !== "string" || !sources.has(row.sourcePath))
1351
+ continue;
1352
+ if (typeof row.guid !== "string" || !UUID.test(row.guid) || seen.has(row.guid) || typeof row.kind !== "string" || typeof row.packageUrl !== "string" || !row.packageUrl.startsWith("/") || row.packageUrl.startsWith("//")) {
1353
+ throw new Error("engine_pack_catalog_invalid");
1354
+ }
1355
+ seen.add(row.guid);
1356
+ const packagePath = safeRelativePath(row.packageUrl.slice(1));
1357
+ const bytes = artifacts.get(packagePath);
1358
+ if (!bytes || bytes.length > 16 * 1024 * 1024)
1359
+ throw new Error("engine_pack_product_missing");
1360
+ const pack = object(JSON.parse(bytes.toString("utf8")));
1361
+ if (!Array.isArray(pack.assets))
1362
+ throw new Error("engine_pack_product_invalid");
1363
+ const matches = pack.assets.map(object).filter((asset) => asset.guid === row.guid && asset.kind === row.kind);
1364
+ if (matches.length !== 1)
1365
+ throw new Error("engine_pack_product_guid_missing");
1366
+ rows.push({
1367
+ guid: row.guid,
1368
+ kind: row.kind,
1369
+ source: row.sourcePath,
1370
+ sourcePath: row.sourcePath,
1371
+ ...typeof row.name === "string" ? { name: row.name } : {},
1372
+ packageUrl: row.packageUrl
1373
+ });
1374
+ inspect.push({ guid: row.guid, kind: row.kind, packagePath, packageSha256: sha256(bytes), verified: true });
1375
+ }
1376
+ if (!rows.some((row) => row.kind === "scene" || row.kind === "mesh"))
1377
+ throw new Error("engine_pack_readback_missing_renderable");
1378
+ return {
1379
+ authority: "engine-build-catalog",
1380
+ verify: { artifacts: artifacts.size, bytes: total, catalogSha256: sha256(indexBytes) },
1381
+ list: rows,
1382
+ rows,
1383
+ inspect
1384
+ };
1385
+ }
1386
+
1387
+ // extensions/asset3d/src/transaction.ts
1388
+ function confined3(root, candidate) {
1389
+ const rel = relative4(root, candidate);
1390
+ return rel === "" || !isAbsolute5(rel) && rel !== ".." && !rel.startsWith(`..${sep4}`);
1391
+ }
1392
+ function forgeaxRoot(projectRoot) {
1393
+ return resolve6(projectRoot, ".forgeax/extensions/asset3d/data");
1394
+ }
1395
+ function journalPath(projectRoot, execution) {
1396
+ return resolve6(forgeaxRoot(projectRoot), "asset3d-transactions", `${execution}.json`);
1397
+ }
1398
+ function providerResultPath(projectRoot, execution) {
1399
+ return resolve6(forgeaxRoot(projectRoot), "asset3d-results", `${execution}.json`);
1400
+ }
1401
+ var readInstall = readAsset3dConfig;
1402
+ function readJournal(projectRoot, execution) {
1403
+ if (!/^[0-9a-f-]{36}$/.test(execution))
1404
+ throw new Error("asset3d_execution_invalid");
1405
+ let journal;
1406
+ try {
1407
+ journal = JSON.parse(readFileSync6(journalPath(projectRoot, execution), "utf8"));
1408
+ } catch {
1409
+ throw new Error("asset3d_execution_not_found");
1410
+ }
1411
+ if (journal.schemaVersion !== TRANSACTION_SCHEMA || journal.execution !== execution)
1412
+ throw new Error("asset3d_journal_invalid");
1413
+ return journal;
1414
+ }
1415
+ function updateJournal(projectRoot, journal, patch) {
1416
+ const next = { ...journal, ...patch };
1417
+ atomicWrite(journalPath(projectRoot, journal.execution), `${JSON.stringify(next, null, 2)}
1418
+ `);
1419
+ return next;
1420
+ }
1421
+ function beginAsset3d(projectRootInput, queries, options = {}) {
1422
+ const projectRoot = realpathSync4(projectRootInput);
1423
+ if (queries.length < 1 || queries.length > 16 || queries.some((query) => [...query].length < 1 || [...query].length > 200)) {
1424
+ throw new Error("asset3d_queries_invalid: expected 1..16 queries of 1..200 characters");
1425
+ }
1426
+ const install = readInstall(projectRoot);
1427
+ const release = resolveEngineRelease(projectRoot, options.carrierPluginRoot === undefined ? {} : { pluginRoot: options.carrierPluginRoot });
1428
+ const execution = randomUUID();
1429
+ const relativeOutput = `workspace/asset3d/${execution}`;
1430
+ const quarantine = resolve6(projectRoot, ".forgeax/extensions/asset3d/data", "asset3d-quarantine");
1431
+ const allowedQuarantineRoot = resolve6(quarantine, relativeOutput);
1432
+ ensurePrivateDir(resolve6(projectRoot, ".forgeax/extensions/asset3d/data", "asset3d-transactions"));
1433
+ ensurePrivateDir(allowedQuarantineRoot);
1434
+ const journal = {
1435
+ schemaVersion: TRANSACTION_SCHEMA,
1436
+ execution,
1437
+ queryDigest: sha256(canonicalJson(queries)),
1438
+ adapterVersion: install.adapterVersion,
1439
+ engineVersion: release.version,
1440
+ engineCommit: release.commit,
1441
+ allowedQuarantineRoot,
1442
+ requestedCount: queries.length,
1443
+ createdAt: new Date().toISOString(),
1444
+ state: "begun"
1445
+ };
1446
+ atomicWrite(journalPath(projectRoot, execution), `${JSON.stringify(journal, null, 2)}
1447
+ `);
1448
+ return { execution, output_dir: relativeOutput };
1449
+ }
1450
+ function asset3dSearchOutputDir(projectRootInput, execution, queries) {
1451
+ const projectRoot = realpathSync4(projectRootInput);
1452
+ const journal = readJournal(projectRoot, execution);
1453
+ if (journal.state !== "begun") {
1454
+ throw new Error(`asset3d_execution_state_invalid: ${journal.state}`);
1455
+ }
1456
+ if (queries.length !== journal.requestedCount || sha256(canonicalJson(queries)) !== journal.queryDigest) {
1457
+ throw new Error("asset3d_search_query_identity_mismatch");
1458
+ }
1459
+ const outputDir = `workspace/asset3d/${execution}`;
1460
+ const expectedRoot = resolve6(forgeaxRoot(projectRoot), "asset3d-quarantine", outputDir);
1461
+ if (journal.allowedQuarantineRoot !== expectedRoot || !existsSync4(expectedRoot) || realpathSync4(expectedRoot) !== expectedRoot) {
1462
+ throw new Error("asset3d_search_output_identity_mismatch");
1463
+ }
1464
+ return outputDir;
1465
+ }
1466
+ function recordAsset3dProviderResult(projectRootInput, execution, providerResult) {
1467
+ const projectRoot = realpathSync4(projectRootInput);
1468
+ const journal = readJournal(projectRoot, execution);
1469
+ if (journal.state !== "begun") {
1470
+ throw new Error(`asset3d_execution_state_invalid: ${journal.state}`);
1471
+ }
1472
+ const bytes = Buffer.byteLength(providerResult);
1473
+ if (bytes < 1 || bytes > MAX_JSON_BYTES) {
1474
+ throw new Error("provider_result_too_large: expected 1 byte..1 MiB");
1475
+ }
1476
+ ensurePrivateDir(resolve6(forgeaxRoot(projectRoot), "asset3d-results"));
1477
+ atomicWrite(providerResultPath(projectRoot, execution), providerResult, 384);
1478
+ updateJournal(projectRoot, journal, { state: "provider_complete" });
1479
+ }
1480
+ function fileBytesChecked(root, entry) {
1481
+ const source = resolve6(root, entry.path);
1482
+ if (!confined3(root, source))
1483
+ throw new Error("asset_manifest_escape");
1484
+ const canonicalRoot = realpathSync4(root);
1485
+ const canonicalSource = realpathSync4(source);
1486
+ if (!confined3(canonicalRoot, canonicalSource))
1487
+ throw new Error("asset_manifest_escape");
1488
+ const before = lstatSync5(source);
1489
+ if (!before.isFile() || before.isSymbolicLink())
1490
+ throw new Error("asset_manifest_not_regular");
1491
+ const fd2 = openSync2(source, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
1492
+ try {
1493
+ const opened = fstatSync(fd2);
1494
+ if (!opened.isFile() || opened.size !== entry.bytes)
1495
+ throw new Error("asset_manifest_bytes_mismatch");
1496
+ const bytes = readFileSync6(fd2);
1497
+ if (sha256(bytes) !== entry.sha256)
1498
+ throw new Error("asset_manifest_digest_mismatch");
1499
+ return bytes;
1500
+ } finally {
1501
+ closeSync2(fd2);
1502
+ }
1503
+ }
1504
+ function acquireLock(projectRoot, assetId, timeoutMs) {
1505
+ const root = resolve6(forgeaxRoot(projectRoot), "asset3d-locks");
1506
+ ensurePrivateDir(root);
1507
+ const path = resolve6(root, `${assetId}.lock`);
1508
+ const token = randomUUID();
1509
+ const deadline = Date.now() + timeoutMs;
1510
+ for (;; ) {
1511
+ try {
1512
+ const fd2 = openSync2(path, "wx", 384);
1513
+ writeFileSync3(fd2, `${JSON.stringify({ token, pid: process.pid, acquiredAt: new Date().toISOString() })}
1514
+ `);
1515
+ closeSync2(fd2);
1516
+ return () => {
1517
+ try {
1518
+ const current = JSON.parse(readFileSync6(path, "utf8"));
1519
+ if (current.token === token)
1520
+ unlinkSync2(path);
1521
+ } catch {}
1522
+ };
1523
+ } catch (error) {
1524
+ if (error.code !== "EEXIST")
1525
+ throw error;
1526
+ if (Date.now() >= deadline)
1527
+ throw new Error("asset_busy");
1528
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, Math.min(100, deadline - Date.now()));
1529
+ }
1530
+ }
1531
+ }
1532
+ function engineCommand(projectRoot, args, carrierPluginRoot) {
1533
+ const release = resolveEngineRelease(projectRoot, carrierPluginRoot === undefined ? {} : { pluginRoot: carrierPluginRoot });
1534
+ const result = spawnSync(process.execPath, [release.cliPath, ...args], {
1535
+ cwd: projectRoot,
1536
+ encoding: "utf8",
1537
+ maxBuffer: 1024 * 1024 + 1,
1538
+ timeout: 150000
1539
+ });
1540
+ const stdout = result.stdout ?? "";
1541
+ if (Buffer.byteLength(stdout) > 1024 * 1024)
1542
+ throw new Error("engine_envelope_too_large");
1543
+ const lines = stdout.split(`
1544
+ `).filter((line) => line.length > 0);
1545
+ if (lines.length !== 1 || !stdout.endsWith(`
1546
+ `))
1547
+ throw new Error("engine_terminal_envelope_invalid");
1548
+ let envelope;
1549
+ try {
1550
+ envelope = JSON.parse(lines[0]);
1551
+ } catch {
1552
+ throw new Error("engine_terminal_envelope_invalid");
1553
+ }
1554
+ if (envelope.schemaVersion !== "1.0.0" || typeof envelope.command !== "string" || typeof envelope.ok !== "boolean") {
1555
+ throw new Error("engine_terminal_envelope_invalid");
1556
+ }
1557
+ if (result.status !== 0 || envelope.ok !== true) {
1558
+ throw new Error(`engine_${String(envelope.command).replace(".", "_")}_failed:${canonicalJson(envelope.error ?? { exitCode: result.status })}`);
1559
+ }
1560
+ return envelope;
1561
+ }
1562
+ function engineAddedRows(projectRoot, asset) {
1563
+ let subAssets = asset.subAssets;
1564
+ if (!Array.isArray(subAssets)) {
1565
+ if (typeof asset.metaPath !== "string")
1566
+ throw new Error("engine_readback_missing_meta");
1567
+ const metaPath = resolve6(projectRoot, asset.metaPath);
1568
+ if (!confined3(projectRoot, metaPath))
1569
+ throw new Error("engine_readback_meta_escape");
1570
+ const canonical = realpathSync4(metaPath);
1571
+ const info = lstatSync5(metaPath);
1572
+ if (!confined3(projectRoot, canonical) || !info.isFile() || info.isSymbolicLink()) {
1573
+ throw new Error("engine_readback_meta_invalid");
1574
+ }
1575
+ let meta;
1576
+ try {
1577
+ meta = JSON.parse(readFileSync6(metaPath, "utf8"));
1578
+ } catch {
1579
+ throw new Error("engine_readback_meta_invalid");
1580
+ }
1581
+ if (!Array.isArray(meta.subAssets))
1582
+ throw new Error("engine_readback_missing_subassets");
1583
+ subAssets = meta.subAssets.filter((row) => row !== null && typeof row === "object" && !Array.isArray(row));
1584
+ }
1585
+ const projectPath = (value) => {
1586
+ if (typeof value !== "string")
1587
+ return value;
1588
+ const absolute = resolve6(projectRoot, value);
1589
+ if (!confined3(projectRoot, absolute))
1590
+ throw new Error("engine_readback_path_escape");
1591
+ return relative4(projectRoot, absolute).split(sep4).join("/");
1592
+ };
1593
+ return subAssets.map((row) => ({ source: projectPath(asset.source), metaPath: projectPath(asset.metaPath), reused: asset.reused, ...row }));
1594
+ }
1595
+ function engineReadback(projectRoot, assetRelative, add, carrierPluginRoot) {
1596
+ const verify = engineCommand(projectRoot, ["asset", "verify", "--json"], carrierPluginRoot);
1597
+ const list = engineCommand(projectRoot, ["asset", "list", "--json"], carrierPluginRoot);
1598
+ const addAssets = add?.value?.assets ?? [];
1599
+ const rows = addAssets.flatMap((asset) => engineAddedRows(projectRoot, asset));
1600
+ const guids = [...new Set(rows.flatMap((row) => typeof row.guid === "string" ? [row.guid] : []))];
1601
+ if (add && guids.length === 0)
1602
+ throw new Error("engine_readback_missing_guid");
1603
+ const inspect = guids.map((guid) => engineCommand(projectRoot, ["asset", "inspect", guid, "--json"], carrierPluginRoot).value);
1604
+ const listed = Array.isArray(list.value) ? list.value : Array.isArray(list.value?.assets) ? list.value.assets : [];
1605
+ for (const guid of guids)
1606
+ if (!listed.some((entry) => entry.guid === guid))
1607
+ throw new Error("engine_catalog_readback_missing");
1608
+ return { ...add ? { add: add.value } : {}, verify: verify.value, list: listed, inspect, rows };
1609
+ }
1610
+ function packReadback(projectRoot, item, carrierPluginRoot) {
1611
+ const release = resolveEngineRelease(projectRoot, carrierPluginRoot === undefined ? {} : { pluginRoot: carrierPluginRoot });
1612
+ const result = spawnSync(process.execPath, [release.cliPath, "build", "--json"], {
1613
+ cwd: projectRoot,
1614
+ encoding: "utf8",
1615
+ timeout: 150000,
1616
+ maxBuffer: 8 * 1024 * 1024
1617
+ });
1618
+ if (result.error || result.status !== 0)
1619
+ throw new Error("engine_pack_build_failed");
1620
+ const envelope = parseEnvelope(result.stdout, "build");
1621
+ const prefix = `assets/3d/ea-3d/${item.providerAssetId}/`;
1622
+ return readPackBuildCatalog(projectRoot, item.manifest.map((entry) => prefix + entry.path), envelope.value);
1623
+ }
1624
+ function priorProvenance(destination) {
1625
+ try {
1626
+ return JSON.parse(readFileSync6(resolve6(destination, ".forgeax-asset.json"), "utf8"));
1627
+ } catch {
1628
+ return;
1629
+ }
1630
+ }
1631
+ function liveFilesMatch(destination, item) {
1632
+ return item.manifest.every((entry) => {
1633
+ const path = resolve6(destination, entry.path);
1634
+ try {
1635
+ return confined3(destination, path) && statSync2(path).isFile() && statSync2(path).size === entry.bytes && sha256(readFileSync6(path)) === entry.sha256;
1636
+ } catch {
1637
+ return false;
1638
+ }
1639
+ });
1640
+ }
1641
+ function quarantineFiles(root, directory = root) {
1642
+ const files = [];
1643
+ for (const name of readdirSync2(directory)) {
1644
+ const path = resolve6(directory, name);
1645
+ const info = lstatSync5(path);
1646
+ if (info.isSymbolicLink())
1647
+ throw new Error("asset_quarantine_symlink_rejected");
1648
+ if (info.isDirectory())
1649
+ files.push(...quarantineFiles(root, path));
1650
+ else if (info.isFile())
1651
+ files.push(relative4(root, path).split(sep4).join("/"));
1652
+ else
1653
+ throw new Error("asset_quarantine_special_file_rejected");
1654
+ }
1655
+ return files;
1656
+ }
1657
+ function validateQuarantine(journal, items) {
1658
+ const root = realpathSync4(journal.allowedQuarantineRoot);
1659
+ const expected = items.flatMap((item) => item.manifest.map((entry) => entry.path)).sort();
1660
+ if (new Set(expected).size !== expected.length)
1661
+ throw new Error("provider_result_invalid: cross-item manifest collision");
1662
+ const actual = quarantineFiles(root).sort();
1663
+ if (canonicalJson(actual) !== canonicalJson(expected))
1664
+ throw new Error("asset_quarantine_undeclared_file");
1665
+ }
1666
+ function stageItem(projectRoot, journal, item, destination, prior) {
1667
+ const root = realpathSync4(journal.allowedQuarantineRoot);
1668
+ const stageRoot = resolve6(forgeaxRoot(projectRoot), "asset3d-staging", journal.execution, item.providerAssetId);
1669
+ rmSync2(stageRoot, { recursive: true, force: true });
1670
+ ensurePrivateDir(resolve6(stageRoot, ".."));
1671
+ if (existsSync4(destination))
1672
+ cpSync(destination, stageRoot, { recursive: true, errorOnExist: true, force: false });
1673
+ else
1674
+ ensurePrivateDir(stageRoot);
1675
+ const nextPaths = new Set(item.manifest.map((entry) => entry.path));
1676
+ const previousFiles = Array.isArray(prior?.files) ? prior.files : [];
1677
+ for (const previous of previousFiles) {
1678
+ if (typeof previous.relativePath !== "string" || nextPaths.has(previous.relativePath))
1679
+ continue;
1680
+ const stale = resolve6(stageRoot, previous.relativePath);
1681
+ if (!confined3(stageRoot, stale))
1682
+ throw new Error("asset_provenance_path_escape");
1683
+ rmSync2(stale, { force: true });
1684
+ rmSync2(`${stale}.meta.json`, { force: true });
1685
+ }
1686
+ for (const entry of item.manifest) {
1687
+ const target = resolve6(stageRoot, entry.path);
1688
+ if (!confined3(stageRoot, target))
1689
+ throw new Error("asset_manifest_escape");
1690
+ mkdirSync2(resolve6(target, ".."), { recursive: true, mode: 448 });
1691
+ atomicWrite(target, fileBytesChecked(root, entry), 384);
1692
+ }
1693
+ return stageRoot;
1694
+ }
1695
+ function provenance(item, install, readback, refreshed) {
1696
+ const rows = readback.rows;
1697
+ const sourcePrefix = `assets/3d/ea-3d/${item.providerAssetId}/`;
1698
+ return {
1699
+ schemaVersion: PROVENANCE_SCHEMA,
1700
+ provider: "ea-3d",
1701
+ providerAssetId: item.providerAssetId,
1702
+ adapterVersion: install.adapterVersion,
1703
+ originSetDigest: install.originSetDigest,
1704
+ aggregateSha256: item.sha256,
1705
+ bytes: item.bytes,
1706
+ deliveredFormat: item.deliveredFormat,
1707
+ ...item.deliveredFormat === "pack" ? { primaryPack: item.primaryPack } : { primaryModel: item.primaryModel },
1708
+ engine: { version: ENGINE_VERSION, commit: ENGINE_COMMIT },
1709
+ refreshed,
1710
+ files: item.manifest.map((entry) => ({
1711
+ relativePath: entry.path,
1712
+ role: entry.role,
1713
+ sha256: entry.sha256,
1714
+ bytes: entry.bytes,
1715
+ engineRows: rows.filter((row) => row.source === `${sourcePrefix}${entry.path}`)
1716
+ })),
1717
+ catalog: { verify: readback.verify, list: readback.list, inspect: readback.inspect }
1718
+ };
1719
+ }
1720
+ function commitItem(projectRoot, initialJournal, install, item, refresh, lockTimeoutMs, carrierPluginRoot) {
1721
+ const destination = resolve6(projectRoot, "assets", "3d", "ea-3d", item.providerAssetId);
1722
+ const assetRoot = resolve6(projectRoot, "assets", "3d", "ea-3d");
1723
+ ensurePrivateDir(assetRoot);
1724
+ if (!confined3(assetRoot, destination))
1725
+ throw new Error("asset_destination_escape");
1726
+ const releaseLock = acquireLock(projectRoot, item.providerAssetId, lockTimeoutMs);
1727
+ let journal = initialJournal;
1728
+ try {
1729
+ const prior = priorProvenance(destination);
1730
+ const priorDigest = typeof prior?.aggregateSha256 === "string" ? prior.aggregateSha256 : undefined;
1731
+ if (priorDigest === item.sha256 && liveFilesMatch(destination, item)) {
1732
+ const priorRows = Array.isArray(prior?.files) ? prior.files.flatMap((entry) => entry.engineRows ?? []) : [];
1733
+ const baseReadback = item.deliveredFormat === "pack" ? packReadback(projectRoot, item, carrierPluginRoot) : engineReadback(projectRoot, `assets/3d/ea-3d/${item.providerAssetId}`, undefined, carrierPluginRoot);
1734
+ const guids = priorRows.flatMap((row) => typeof row.guid === "string" ? [row.guid] : []);
1735
+ const listed = Array.isArray(baseReadback.list) ? baseReadback.list : [];
1736
+ if (guids.length === 0 || guids.some((guid) => !listed.some((entry) => entry.guid === guid)))
1737
+ throw new Error("asset_reuse_readback_failed");
1738
+ const readback = item.deliveredFormat === "pack" ? baseReadback : { ...baseReadback, inspect: guids.map((guid) => engineCommand(projectRoot, ["asset", "inspect", guid, "--json"], carrierPluginRoot).value) };
1739
+ journal = updateJournal(projectRoot, journal, { state: "committed", previousDigest: priorDigest, newDigest: item.sha256 });
1740
+ return { providerAssetId: item.providerAssetId, digest: item.sha256, bytes: item.bytes, reused: true, refreshed: false, sourcePath: relative4(projectRoot, destination), provenancePath: relative4(projectRoot, resolve6(destination, ".forgeax-asset.json")), engine: { version: ENGINE_VERSION, commit: ENGINE_COMMIT }, catalog: readback, rows: priorRows };
1741
+ }
1742
+ if (priorDigest && priorDigest !== item.sha256 && !refresh)
1743
+ throw new Error("asset_changed");
1744
+ const stage = stageItem(projectRoot, journal, item, destination, prior);
1745
+ const backup = resolve6(forgeaxRoot(projectRoot), "asset3d-backups", journal.execution, item.providerAssetId);
1746
+ ensurePrivateDir(resolve6(backup, ".."));
1747
+ journal = updateJournal(projectRoot, journal, { state: "committing", destination, backupPath: existsSync4(destination) ? backup : undefined, previousDigest: priorDigest, newDigest: item.sha256 });
1748
+ if (existsSync4(destination))
1749
+ renameSync2(destination, backup);
1750
+ renameSync2(stage, destination);
1751
+ try {
1752
+ const assetRelative = `assets/3d/ea-3d/${item.providerAssetId}`;
1753
+ const readback = item.deliveredFormat === "pack" ? packReadback(projectRoot, item, carrierPluginRoot) : engineReadback(projectRoot, assetRelative, engineCommand(projectRoot, ["asset", "add", assetRelative, "--reimport-policy", "semantic-only", "--json"], carrierPluginRoot), carrierPluginRoot);
1754
+ if (item.deliveredFormat === "pack" && !liveFilesMatch(destination, item))
1755
+ throw new Error("engine_pack_source_changed");
1756
+ atomicWrite(resolve6(destination, ".forgeax-asset.json"), `${JSON.stringify(provenance(item, install, readback, priorDigest !== undefined), null, 2)}
1757
+ `);
1758
+ journal = updateJournal(projectRoot, journal, { state: "engine_verified" });
1759
+ if (existsSync4(backup))
1760
+ rmSync2(backup, { recursive: true, force: true });
1761
+ journal = updateJournal(projectRoot, journal, { state: "committed" });
1762
+ return { providerAssetId: item.providerAssetId, digest: item.sha256, bytes: item.bytes, reused: false, refreshed: priorDigest !== undefined, sourcePath: assetRelative, provenancePath: `${assetRelative}/.forgeax-asset.json`, engine: { version: ENGINE_VERSION, commit: ENGINE_COMMIT }, catalog: readback, rows: readback.rows };
1763
+ } catch (error) {
1764
+ rmSync2(destination, { recursive: true, force: true });
1765
+ if (existsSync4(backup))
1766
+ renameSync2(backup, destination);
1767
+ updateJournal(projectRoot, journal, { state: "rolled_back", error: error instanceof Error ? error.message.slice(0, 256) : "engine failure" });
1768
+ throw error;
1769
+ }
1770
+ } finally {
1771
+ releaseLock();
1772
+ }
1773
+ }
1774
+ function commitAsset3d(options) {
1775
+ const projectRoot = realpathSync4(options.projectRoot);
1776
+ const install = readInstall(projectRoot);
1777
+ let journal = readJournal(projectRoot, options.execution);
1778
+ if (journal.state !== "begun" && journal.state !== "provider_complete" && journal.state !== "validated") {
1779
+ throw new Error(`asset3d_execution_state_invalid: ${journal.state}`);
1780
+ }
1781
+ if (journal.adapterVersion !== install.adapterVersion || journal.engineCommit !== ENGINE_COMMIT || journal.engineVersion !== ENGINE_VERSION) {
1782
+ throw new Error("asset3d_execution_identity_mismatch");
1783
+ }
1784
+ let providerResult = options.providerResult;
1785
+ if (providerResult === undefined) {
1786
+ try {
1787
+ providerResult = readFileSync6(providerResultPath(projectRoot, options.execution));
1788
+ } catch {
1789
+ throw new Error("asset3d_import_result_missing");
1790
+ }
1791
+ }
1792
+ let result;
1793
+ try {
1794
+ result = parseProviderResult(providerResult, install.adapterVersion, install.originSetDigest);
1795
+ if (result.total !== journal.requestedCount)
1796
+ throw new Error("provider_result_invalid: requested count mismatch");
1797
+ const orderedQueries = [...result.results].sort((left, right) => left.queryIndex - right.queryIndex).map((item) => item.query);
1798
+ if (sha256(canonicalJson(orderedQueries)) !== journal.queryDigest) {
1799
+ throw new Error("provider_result_invalid: query identity mismatch");
1800
+ }
1801
+ const successes = result.results.filter((item) => item.status === "ok");
1802
+ validateQuarantine(journal, successes);
1803
+ journal = updateJournal(projectRoot, journal, { state: "validated" });
1804
+ } catch (error) {
1805
+ rmSync2(journal.allowedQuarantineRoot, { recursive: true, force: true });
1806
+ rmSync2(providerResultPath(projectRoot, options.execution), { force: true });
1807
+ updateJournal(projectRoot, journal, {
1808
+ state: "failed",
1809
+ error: error instanceof Error ? error.message.slice(0, 256) : "provider validation failed"
1810
+ });
1811
+ throw error;
1812
+ }
1813
+ const terminal = [];
1814
+ for (const item of result.results) {
1815
+ if (item.status === "error") {
1816
+ terminal.push(item);
1817
+ continue;
1818
+ }
1819
+ try {
1820
+ terminal.push({ status: "ok", ...commitItem(projectRoot, journal, install, item, options.refresh === true, options.lockTimeoutMs ?? 30000, options.carrierPluginRoot) });
1821
+ } catch (error) {
1822
+ terminal.push({ status: "error", providerAssetId: item.providerAssetId, code: error instanceof Error ? error.message.split(":", 1)[0] : "internal_error", retryable: false });
1823
+ }
1824
+ }
1825
+ rmSync2(journal.allowedQuarantineRoot, { recursive: true, force: true });
1826
+ rmSync2(providerResultPath(projectRoot, options.execution), { force: true });
1827
+ const failed = terminal.filter((entry) => entry.status === "error").length;
1828
+ updateJournal(projectRoot, journal, { state: failed === 0 ? "complete" : "failed" });
1829
+ return {
1830
+ schemaVersion: "forgeax.asset3d-commit-result/1.0.0",
1831
+ execution: journal.execution,
1832
+ succeeded: terminal.filter((entry) => entry.status === "ok").length,
1833
+ failed,
1834
+ results: terminal
1835
+ };
1836
+ }
1837
+ function abortAsset3d(projectRootInput, execution) {
1838
+ const projectRoot = realpathSync4(projectRootInput);
1839
+ const journal = readJournal(projectRoot, execution);
1840
+ if (journal.state === "committed" || journal.state === "complete" || journal.state === "engine_verified") {
1841
+ throw new Error("asset3d_execution_already_committed");
1842
+ }
1843
+ rmSync2(journal.allowedQuarantineRoot, { recursive: true, force: true });
1844
+ rmSync2(providerResultPath(projectRoot, execution), { force: true });
1845
+ updateJournal(projectRoot, journal, { state: "aborted" });
1846
+ return { execution, aborted: true };
1847
+ }
1848
+ function doctorAsset3d(projectRootInput, options = {}) {
1849
+ const projectRoot = realpathSync4(projectRootInput);
1850
+ const install = readInstall(projectRoot);
1851
+ const release = resolveEngineRelease(projectRoot, options.carrierPluginRoot === undefined ? {} : { pluginRoot: options.carrierPluginRoot });
1852
+ const recovered = [];
1853
+ const root = resolve6(forgeaxRoot(projectRoot), "asset3d-transactions");
1854
+ if (existsSync4(root))
1855
+ for (const file of readdirSync2(root).filter((name) => name.endsWith(".json"))) {
1856
+ try {
1857
+ const path = resolve6(root, file);
1858
+ const journal = JSON.parse(readFileSync6(path, "utf8"));
1859
+ if (journal.schemaVersion !== TRANSACTION_SCHEMA || journal.state !== "committing" || !journal.destination)
1860
+ continue;
1861
+ const assetsRoot = resolve6(projectRoot, "assets", "3d", "ea-3d");
1862
+ const backupsRoot = resolve6(forgeaxRoot(projectRoot), "asset3d-backups");
1863
+ if (!confined3(assetsRoot, journal.destination) || journal.backupPath && !confined3(backupsRoot, journal.backupPath))
1864
+ continue;
1865
+ rmSync2(journal.destination, { recursive: true, force: true });
1866
+ if (journal.backupPath && existsSync4(journal.backupPath))
1867
+ renameSync2(journal.backupPath, journal.destination);
1868
+ updateJournal(projectRoot, journal, { state: "rolled_back", error: "stale_committing_recovered" });
1869
+ recovered.push(journal.execution);
1870
+ } catch {}
1871
+ }
1872
+ return { installed: true, recovered, engine: { version: release.version, commit: release.commit }, adapter: { version: install.adapterVersion, transport: "http" }, access: "not_checked" };
1873
+ }
1874
+
1875
+ // extensions/asset3d/src/library.ts
1876
+ var MAX_FILE = 128 * 1024 * 1024;
1877
+ var MAX_TOTAL = 256 * 1024 * 1024;
1878
+ function unpackAsset(bytes, format) {
1879
+ const seen = new Set;
1880
+ let total = 0;
1881
+ const check = (path2, size) => {
1882
+ safeRelativePath(path2);
1883
+ if (/[\x00-\x1f:]/.test(path2) || path2.split("/").some((p) => /[. ]$/.test(p) || /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\.|$)/i.test(p))) {
1884
+ throw new Error("asset3d_archive_path_invalid");
1885
+ }
1886
+ const key = path2.normalize("NFC").toLowerCase();
1887
+ if (seen.has(key) || seen.size >= 1024 || size < 1 || size > MAX_FILE || (total += size) > MAX_TOTAL) {
1888
+ throw new Error("asset3d_archive_limits");
1889
+ }
1890
+ seen.add(key);
1891
+ };
1892
+ if (format === "zip") {
1893
+ let files;
1894
+ try {
1895
+ files = unzipSync(bytes, { filter(entry) {
1896
+ if (entry.name.endsWith("/")) {
1897
+ safeRelativePath(entry.name.slice(0, -1));
1898
+ return false;
1899
+ }
1900
+ check(entry.name, entry.originalSize);
1901
+ return true;
1902
+ } });
1903
+ } catch {
1904
+ throw new Error("asset3d_archive_rejected");
1905
+ }
1906
+ for (const path2 of Object.keys(files)) {
1907
+ let parent = path2;
1908
+ while (parent.includes("/")) {
1909
+ parent = parent.slice(0, parent.lastIndexOf("/"));
1910
+ if (seen.has(parent.normalize("NFC").toLowerCase()))
1911
+ throw new Error("asset3d_archive_path_conflict");
1912
+ }
1913
+ if (!files[path2].length || files[path2].length > MAX_FILE)
1914
+ throw new Error("asset3d_archive_limits");
1915
+ }
1916
+ return files;
1917
+ }
1918
+ if (!["glb", "pack.ts", "pack.json"].includes(format))
1919
+ throw new Error("asset3d_format_unsupported");
1920
+ const path = "asset." + format;
1921
+ check(path, bytes.length);
1922
+ return { [path]: bytes };
1923
+ }
1924
+ function manifestFor(files, prefix) {
1925
+ const paths = Object.keys(files).sort((a, b) => Buffer.from(a).compare(Buffer.from(b)));
1926
+ const packs = paths.filter((p) => /\.pack\.(ts|json)$/i.test(p));
1927
+ const models = paths.filter((p) => /\.glb$/i.test(p));
1928
+ const primary = packs[0] ?? models.find((p) => /(^|\/)SM_/i.test(p)) ?? models[0];
1929
+ if (!primary)
1930
+ throw new Error("asset3d_supported_source_missing");
1931
+ for (const path of models) {
1932
+ const bytes = Buffer.from(files[path]);
1933
+ if (bytes.length < 12 || bytes.toString("ascii", 0, 4) !== "glTF" || bytes.readUInt32LE(4) !== 2 || bytes.readUInt32LE(8) !== bytes.length) {
1934
+ throw new Error("asset3d_glb_invalid");
1935
+ }
1936
+ }
1937
+ const manifest = paths.map((path) => ({
1938
+ path: prefix + "/" + path,
1939
+ role: packs.includes(path) ? path === primary ? "primary-pack" : "auxiliary-pack" : models.includes(path) ? path === primary ? "primary-model" : "auxiliary-model" : /\.(png|jpg|jpeg|webp|ktx2)$/i.test(path) ? "texture" : "metadata",
1940
+ bytes: files[path].length,
1941
+ sha256: sha256(files[path])
1942
+ }));
1943
+ return {
1944
+ manifest,
1945
+ primary: prefix + "/" + primary,
1946
+ deliveredFormat: packs.length ? "pack" : "glb",
1947
+ bytes: manifest.reduce((n, e) => n + e.bytes, 0),
1948
+ sha256: sha256(manifest.map((e) => `${e.path}\x00${e.bytes}\x00${e.sha256}
1949
+ `).join(""))
1950
+ };
1951
+ }
1952
+ async function download(config, candidate) {
1953
+ if (!config.downloadOrigins.includes(downloadOrigin(candidate.downloadUrl))) {
1954
+ throw new Error("asset3d_download_origin_rejected: enable again to refresh allowed origins");
1955
+ }
1956
+ let response;
1957
+ try {
1958
+ response = await fetch(candidate.downloadUrl, { redirect: "error", signal: AbortSignal.timeout(120000) });
1959
+ } catch {
1960
+ throw new Error("asset3d_download_unreachable");
1961
+ }
1962
+ if (!response.ok)
1963
+ throw new Error(`asset3d_download_http_${response.status}`);
1964
+ const bytes = await boundedResponse(response, MAX_TOTAL);
1965
+ const path = new URL(candidate.downloadUrl).pathname;
1966
+ const format = /\.pack\.(ts|json)$/i.exec(path)?.[0].slice(1) ?? candidate.format.replace(/^\./, "");
1967
+ return unpackAsset(bytes, format);
1968
+ }
1969
+ async function candidatesAsset3d(root, query) {
1970
+ const candidates = await searchLibrary(readAsset3dConfig(root), query);
1971
+ return { candidates: candidates.map(({ assetId, name, format }) => ({ assetId, name, format })) };
1972
+ }
1973
+ async function importAsset3d(root, query, assetId) {
1974
+ const config = readAsset3dConfig(root);
1975
+ const candidate = (await searchLibrary(config, query)).find((c) => c.assetId === assetId);
1976
+ if (!candidate)
1977
+ throw new Error("asset3d_candidate_not_found: select an ID returned for this query");
1978
+ const files = await download(config, candidate);
1979
+ const built = manifestFor(files, candidate.assetId);
1980
+ const started = beginAsset3d(root, [query]);
1981
+ try {
1982
+ const output = asset3dSearchOutputDir(root, started.execution, [query]);
1983
+ for (const [path, bytes] of Object.entries(files)) {
1984
+ const target = resolve7(root, ".forgeax/extensions/asset3d/data/asset3d-quarantine", output, candidate.assetId, path);
1985
+ mkdirSync3(dirname4(target), { recursive: true, mode: 448 });
1986
+ writeFileSync4(target, bytes, { flag: "wx", mode: 384 });
1987
+ }
1988
+ const result = {
1989
+ schemaVersion: PROVIDER_RESULT_SCHEMA,
1990
+ total: 1,
1991
+ succeeded: 1,
1992
+ failed: 0,
1993
+ receipt: {
1994
+ schemaVersion: PROVIDER_RECEIPT_SCHEMA,
1995
+ provider: "ea-3d",
1996
+ adapterVersion: config.adapterVersion,
1997
+ originSetDigest: config.originSetDigest
1998
+ },
1999
+ results: [{
2000
+ status: "ok",
2001
+ queryIndex: 0,
2002
+ query,
2003
+ provider: "ea-3d",
2004
+ providerAssetId: assetId,
2005
+ assetName: candidate.name,
2006
+ sha256: built.sha256,
2007
+ bytes: built.bytes,
2008
+ manifest: built.manifest,
2009
+ originSetDigest: config.originSetDigest,
2010
+ ...built.deliveredFormat === "pack" ? { deliveredFormat: "pack", primaryPack: built.primary } : { deliveredFormat: "glb", primaryModel: built.primary }
2011
+ }]
2012
+ };
2013
+ recordAsset3dProviderResult(root, started.execution, JSON.stringify(result));
2014
+ const committed = commitAsset3d({ projectRoot: root, execution: started.execution });
2015
+ return { ...committed, deliveredFormat: built.deliveredFormat };
2016
+ } catch (error) {
2017
+ try {
2018
+ abortAsset3d(root, started.execution);
2019
+ } catch {}
2020
+ throw error;
2021
+ }
2022
+ }
2023
+ function parseAsset3dArgs(args, importing) {
2024
+ const allowed = importing ? ["--query", "--asset-id"] : ["--query"];
2025
+ const values = {};
2026
+ for (let i2 = 0;i2 < args.length; i2++) {
2027
+ if (args[i2] === "--json")
2028
+ continue;
2029
+ const name = args[i2], value = args[++i2];
2030
+ if (!allowed.includes(name) || values[name] || !value || value.startsWith("--"))
2031
+ throw new Error("asset3d_arguments_invalid");
2032
+ values[name] = value;
2033
+ }
2034
+ if (allowed.some((name) => !values[name]))
2035
+ throw new Error("asset3d_arguments_invalid");
2036
+ return { query: values["--query"], assetId: values["--asset-id"] };
2037
+ }
2038
+
2039
+ // extensions/asset3d/cli.ts
2040
+ async function check(context, args) {
2041
+ let library;
2042
+ let baseUrl;
2043
+ for (let i2 = 0;i2 < args.length; i2++) {
2044
+ const option = args[i2];
2045
+ if (option === "--json")
2046
+ continue;
2047
+ if (option === "--library" && ["aw", "ea"].includes(args[i2 + 1]))
2048
+ library = args[++i2];
2049
+ else if (option === "--base-url" && args[i2 + 1])
2050
+ baseUrl = args[++i2];
2051
+ else
2052
+ throw new Error("asset3d_arguments_invalid: enable [--library aw|ea] [--base-url URL]");
2053
+ }
2054
+ const selected = resolveAssetLibrarySelection({ library, baseUrl });
2055
+ const credentialFile = defaultAwCredentialFile();
2056
+ const credential = await acquireAwKey(credentialFile, !args.includes("--json"));
2057
+ const pending = writeAwCredential(credentialFile, credential.key);
2058
+ try {
2059
+ const access = await checkAssetLibraryAccess({ ...selected, credentialFile });
2060
+ pending.commit();
2061
+ return {
2062
+ schemaVersion: INSTALL_SCHEMA,
2063
+ adapterVersion: context.packageVersion,
2064
+ ...selected,
2065
+ credentialFile,
2066
+ downloadOrigins: access.downloadOrigins,
2067
+ originSetDigest: canonicalizeOrigins(access.downloadOrigins).digest
2068
+ };
2069
+ } catch (error) {
2070
+ pending.rollback();
2071
+ throw error;
2072
+ }
2073
+ }
2074
+ async function run(context, args) {
2075
+ const [operation, ...rest] = args;
2076
+ if (operation === "doctor") {
2077
+ if (rest.some((arg) => arg !== "--json"))
2078
+ throw new Error("asset3d_arguments_invalid");
2079
+ return doctorAsset3d(context.projectRoot);
2080
+ }
2081
+ if (operation === "candidates" || operation === "import") {
2082
+ const parsed = parseAsset3dArgs(rest, operation === "import");
2083
+ return operation === "candidates" ? candidatesAsset3d(context.projectRoot, parsed.query) : importAsset3d(context.projectRoot, parsed.query, parsed.assetId);
2084
+ }
2085
+ throw new Error("asset3d_arguments_invalid: expected candidates, import, or doctor");
2086
+ }
2087
+ export {
2088
+ check,
2089
+ run
2090
+ };