@forgeax/game 0.3.5 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/main.ts
4
- import { resolve as resolve15 } from "node:path";
4
+ import { resolve as resolve17 } from "node:path";
5
5
 
6
6
  // src/mcp/protocol.ts
7
7
  var MCP_PROTOCOL_VERSION = "2024-11-05";
@@ -427,7 +427,8 @@ reasoning and game-code edits; the plugin owns the exact Engine Preview adapter.
427
427
 
428
428
  In an empty standalone directory, \`forgeax-game init\` creates the released Engine
429
429
  game and installs this guidance. In an existing exact Engine game it refreshes the
430
- binding idempotently. Standalone game source belongs in \`<game-root>/src\`; Studio's
430
+ binding idempotently. Follow \`forge.json\` and the installed Engine authoring layout
431
+ (v2 uses \`assets/\` and \`plugins[]\`), rather than prescribing \`src/\`. Studio's
431
432
  \`.forgeax/games/<slug>\` layout is a different hosted-project boundary and must not be
432
433
  invented here. Before claiming a requested game complete, replace the Empty template
433
434
  identity in \`forge.json\`, \`package.json\`, and README, keep tests/controls accurate,
@@ -438,7 +439,12 @@ transition or rule; renaming the Empty template test is not completion.
438
439
  Any run-tool error means the game is not previewed: never curl, reuse, open, or report
439
440
  an existing localhost port. HTTP 200 is not ownership evidence. Only a successful tool
440
441
  result containing \`preview.status: ready\`, root, build digest, instance ID, and its own
441
- \`preview_url\` authorizes a Preview claim.
442
+ \`preview_url\` authorizes a Preview claim, not a gameplay-verification claim.
443
+ For playable-game requests, check the requested interactions and visible feedback
444
+ with an available browser tool. If unavailable, report gameplay as unverified and
445
+ provide manual checks; do not stop useful implementation or invent test results.
446
+ Use the installed Engine contract for Host access and UI lifecycle, and verify
447
+ requested UI is mounted rather than silently skipping it when a Host lookup fails.
442
448
  Never substitute Studio, Editor, PATH, or a source checkout for the installed Engine
443
449
  release.`;
444
450
 
@@ -2269,6 +2275,7 @@ not running check requested; no Engine child was launched.`;
2269
2275
  `preview.state_file: ${result.paths.state}`,
2270
2276
  `preview.stdout_log: ${result.paths.stdout}`,
2271
2277
  `preview.stderr_log: ${result.paths.stderr}`,
2278
+ "This tool verifies Engine build and Preview ownership only; gameplay, input, and visible UI have not been tested by this tool.",
2272
2279
  "Open only this returned loopback URL. HTTP availability without this exact verified identity is not Preview evidence."
2273
2280
  ].join(`
2274
2281
  `);
@@ -3226,9 +3233,9 @@ async function startHttpMcpServer(spec, options) {
3226
3233
  }
3227
3234
 
3228
3235
  // src/cli/dispatch.ts
3229
- import { existsSync as existsSync12, readFileSync as readFileSync15, writeFileSync as writeFileSync11 } from "node:fs";
3236
+ import { existsSync as existsSync12, readFileSync as readFileSync17, writeFileSync as writeFileSync12 } from "node:fs";
3230
3237
  import { arch as arch2, homedir as homedir5, platform as platform2 } from "node:os";
3231
- import { join as join11, resolve as resolve14 } from "node:path";
3238
+ import { join as join12, resolve as resolve16 } from "node:path";
3232
3239
 
3233
3240
  // src/install/clients.ts
3234
3241
  import { homedir as homedir2 } from "node:os";
@@ -4270,17 +4277,17 @@ var PROVIDER_BUNDLE_SCHEMA = "forgeax.asset3d-provider-bundle/1.0.0";
4270
4277
  var PROVIDER_RESULT_SCHEMA = "forgeax.asset3d-search-result/1.0.0";
4271
4278
  var PROVIDER_RECEIPT_SCHEMA = "forgeax.asset3d-search-receipt/1.0.0";
4272
4279
  var K0_PACKAGE_SHA256 = "4937fe00e2b919319c70c82db53ebe359b275ed662c026153ecb97f7601262b4";
4273
- var RESULT_SCHEMA_SHA256 = "ab6e5e1e794d5428efef362fe32d4c54b6541e12539bae5f6ff27680c9f380ba";
4280
+ var RESULT_SCHEMA_SHA256 = "f97a9aa8b2ce0662c37d314211aae52951cbd0b76b3a7c140dc608be4712c415";
4274
4281
  var RECEIPT_SCHEMA_SHA256 = "e0dc9e9fe9872f09af9fca6d0c17d22aca63c2b546c55c0b669c581f7059b9c4";
4275
- var ASSET3D_PROVIDER_COMMIT = "c181c48fbffc933a7ce9a0836f7878ca5e6d77e1";
4282
+ var ASSET3D_PROVIDER_COMMIT = "a274238712c1dced99e4aa3148a0d15904ce2a90";
4276
4283
  var BUNDLED_ASSET3D_PROVIDERS = Object.freeze({
4277
4284
  "darwin-arm64": {
4278
- sha256: "9492c507a3afe5cafcd50b5f782b313c8716c2e52f5def06592c6a100c0459db",
4279
- relativePath: "asset3d/provider/asset3d-search-provider-c181c48fbffc933a7ce9a0836f7878ca5e6d77e1-darwin-arm64.tar.gz"
4285
+ sha256: "4f2d971c037a32eaed20e51e13a88bff91d2ea8f4d37894526c089376c3bacba",
4286
+ relativePath: "asset3d/provider/asset3d-search-provider-a274238712c1dced99e4aa3148a0d15904ce2a90-darwin-arm64.tar.gz"
4280
4287
  },
4281
4288
  "linux-x64": {
4282
- sha256: "e6ac3df76c8b82f9fe2c063dd3f221324a9dad5176fd52535a5144b0ff6002d8",
4283
- relativePath: "asset3d/provider/asset3d-search-provider-c181c48fbffc933a7ce9a0836f7878ca5e6d77e1-linux-x64.tar.gz"
4289
+ sha256: "af97e7e267b558960156175c840c3c2d2cd7aabf68f1f72c7d64b6d50742a79e",
4290
+ relativePath: "asset3d/provider/asset3d-search-provider-a274238712c1dced99e4aa3148a0d15904ce2a90-linux-x64.tar.gz"
4284
4291
  }
4285
4292
  });
4286
4293
  var INSTALL_SCHEMA = "forgeax.asset3d-install/1.0.0";
@@ -4675,7 +4682,8 @@ function installSkills(projectRoot, tgz, clients, previous) {
4675
4682
  for (const client of clients) {
4676
4683
  const mount = SKILL_MOUNTS[client];
4677
4684
  const destination = resolve10(projectRoot, mount, SKILL_ID);
4678
- const projected = [...K0_FILES.map((file) => [file, extractK0(tgz, file)]), ["SKILL.md", extractK0(tgz, "PLATFORM.md")]];
4685
+ const adapter = readFileSync12(resolve10(dirname10(tgz), "adapter", "SKILL.md"));
4686
+ const projected = [...K0_FILES.map((file) => [`legacy/${file}`, extractK0(tgz, file)]), ["SKILL.md", adapter]];
4679
4687
  if (existsSync8(destination)) {
4680
4688
  const priorOwned = new Set(previous?.skills.filter((entry) => confined3(destination, entry.path)).map((entry) => entry.path) ?? []);
4681
4689
  const unexpected = filesUnder2(destination).filter((path) => !priorOwned.has(path));
@@ -4957,19 +4965,19 @@ import {
4957
4965
  cpSync as cpSync2,
4958
4966
  existsSync as existsSync9,
4959
4967
  fstatSync,
4960
- lstatSync as lstatSync8,
4968
+ lstatSync as lstatSync9,
4961
4969
  mkdirSync as mkdirSync10,
4962
4970
  openSync as openSync4,
4963
- readFileSync as readFileSync13,
4971
+ readFileSync as readFileSync14,
4964
4972
  readdirSync as readdirSync7,
4965
- realpathSync as realpathSync8,
4973
+ realpathSync as realpathSync9,
4966
4974
  renameSync as renameSync7,
4967
4975
  rmSync as rmSync6,
4968
4976
  statSync as statSync6,
4969
4977
  unlinkSync as unlinkSync3,
4970
4978
  writeFileSync as writeFileSync10
4971
4979
  } from "node:fs";
4972
- import { isAbsolute as isAbsolute4, relative as relative9, resolve as resolve11, sep as sep6 } from "node:path";
4980
+ import { isAbsolute as isAbsolute5, relative as relative10, resolve as resolve12, sep as sep7 } from "node:path";
4973
4981
 
4974
4982
  // src/asset3d/schema.ts
4975
4983
  var ID = /^[A-Za-z0-9._-]{1,128}$/;
@@ -4988,7 +4996,7 @@ var ERROR_CODES = new Set([
4988
4996
  "batch_timeout",
4989
4997
  "internal_error"
4990
4998
  ]);
4991
- var ROLES = new Set(["primary-model", "animation", "auxiliary-model", "texture", "metadata"]);
4999
+ var ROLES = new Set(["primary-pack", "auxiliary-pack", "primary-model", "animation", "auxiliary-model", "texture", "metadata"]);
4992
5000
  function exactKeys(record, allowed, field) {
4993
5001
  const extras = Object.keys(record).filter((key) => !allowed.includes(key));
4994
5002
  if (extras.length)
@@ -5078,9 +5086,12 @@ function parseProviderResult(input, expectedCommit, expectedOriginSetDigest) {
5078
5086
  }
5079
5087
  if (row.status !== "ok")
5080
5088
  throw new Error("provider_result_invalid: status");
5081
- exactKeys(row, ["status", "queryIndex", "query", "provider", "providerAssetId", "assetName", "deliveredFormat", "sha256", "bytes", "primaryModel", "manifest", "originSetDigest", "downloaded_to"], "success");
5089
+ const isPack = row.deliveredFormat === "pack";
5090
+ const primaryKey = isPack ? "primaryPack" : "primaryModel";
5091
+ const primaryRole = isPack ? "primary-pack" : "primary-model";
5092
+ exactKeys(row, ["status", "queryIndex", "query", "provider", "providerAssetId", "assetName", "deliveredFormat", "sha256", "bytes", primaryKey, "manifest", "originSetDigest", "downloaded_to"], "success");
5082
5093
  okCount++;
5083
- if (row.provider !== "ea-3d" || row.deliveredFormat !== "glb" || typeof row.providerAssetId !== "string" || !ID.test(row.providerAssetId) || row.providerAssetId === "." || row.providerAssetId === "..") {
5094
+ if (row.provider !== "ea-3d" || row.deliveredFormat !== "glb" && !isPack || typeof row.providerAssetId !== "string" || !ID.test(row.providerAssetId) || row.providerAssetId === "." || row.providerAssetId === "..") {
5084
5095
  throw new Error("provider_result_invalid: success identity");
5085
5096
  }
5086
5097
  const assetName = text(row.assetName, 1, 128, "assetName");
@@ -5108,13 +5119,18 @@ function parseProviderResult(input, expectedCommit, expectedOriginSetDigest) {
5108
5119
  if ((role === "primary-model" || role === "animation" || role === "auxiliary-model") && !path.toLowerCase().endsWith(".glb")) {
5109
5120
  throw new Error("provider_result_invalid: model entry must be GLB");
5110
5121
  }
5122
+ if ((role === "primary-pack" || role === "auxiliary-pack") && (!isPack || !/\.pack\.(json|ts)$/i.test(path))) {
5123
+ throw new Error("provider_result_invalid: pack entry must be authored Pack");
5124
+ }
5125
+ if (isPack && role === "primary-model")
5126
+ throw new Error("provider_result_invalid: multiple primary formats");
5111
5127
  return { path, role, bytes: entryBytes, sha256: item.sha256 };
5112
5128
  });
5113
5129
  const sorted = [...manifest].sort((left, right) => Buffer.from(left.path).compare(Buffer.from(right.path)));
5114
5130
  if (manifest.some((entry, index) => entry.path !== sorted[index].path))
5115
5131
  throw new Error("provider_result_invalid: manifest must be UTF-8 path sorted");
5116
- const primary = manifest.filter((entry) => entry.role === "primary-model");
5117
- if (primary.length !== 1 || row.primaryModel !== primary[0].path)
5132
+ const primary = manifest.filter((entry) => entry.role === primaryRole);
5133
+ if (primary.length !== 1 || row[primaryKey] !== primary[0].path)
5118
5134
  throw new Error("provider_result_invalid: primary model");
5119
5135
  if (manifest.reduce((sum, entry) => sum + entry.bytes, 0) !== itemBytes || aggregate(manifest) !== row.sha256) {
5120
5136
  throw new Error("provider_result_invalid: byte or aggregate digest mismatch");
@@ -5128,10 +5144,9 @@ function parseProviderResult(input, expectedCommit, expectedOriginSetDigest) {
5128
5144
  provider: "ea-3d",
5129
5145
  providerAssetId: row.providerAssetId,
5130
5146
  assetName,
5131
- deliveredFormat: "glb",
5132
5147
  sha256: row.sha256,
5133
5148
  bytes: itemBytes,
5134
- primaryModel: row.primaryModel,
5149
+ ...isPack ? { deliveredFormat: "pack", primaryPack: row.primaryPack } : { deliveredFormat: "glb", primaryModel: row.primaryModel },
5135
5150
  manifest,
5136
5151
  originSetDigest: expectedOriginSetDigest,
5137
5152
  ...row.downloaded_to === undefined ? {} : { downloaded_to: row.downloaded_to }
@@ -5142,24 +5157,117 @@ function parseProviderResult(input, expectedCommit, expectedOriginSetDigest) {
5142
5157
  return { schemaVersion: PROVIDER_RESULT_SCHEMA, total, succeeded, failed, results, ...receipt ? { receipt } : {} };
5143
5158
  }
5144
5159
 
5160
+ // src/asset3d/pack-readback.ts
5161
+ import { lstatSync as lstatSync8, readFileSync as readFileSync13, realpathSync as realpathSync8 } from "node:fs";
5162
+ import { isAbsolute as isAbsolute4, relative as relative9, resolve as resolve11, sep as sep6 } from "node:path";
5163
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
5164
+ function object(value) {
5165
+ if (!value || typeof value !== "object" || Array.isArray(value))
5166
+ throw new Error("engine_pack_readback_invalid");
5167
+ return value;
5168
+ }
5169
+ function readPackBuildCatalog(projectRoot, sourcePaths, buildValue) {
5170
+ const build = object(buildValue);
5171
+ const runtime = object(build.runtime);
5172
+ if (build.schemaVersion !== "1.0.0" || !Array.isArray(build.artifacts) || build.artifacts.length > 1e4) {
5173
+ throw new Error("engine_pack_build_manifest_invalid");
5174
+ }
5175
+ const dist = resolve11(projectRoot, "dist");
5176
+ const canonicalDist = realpathSync8(dist);
5177
+ if (canonicalDist !== resolve11(realpathSync8(projectRoot), "dist") || lstatSync8(dist).isSymbolicLink()) {
5178
+ throw new Error("engine_pack_build_path_escape");
5179
+ }
5180
+ const artifacts = new Map;
5181
+ let total = 0;
5182
+ for (const raw of build.artifacts) {
5183
+ const entry = object(raw);
5184
+ const path = safeRelativePath(entry.path);
5185
+ 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)) {
5186
+ throw new Error("engine_pack_build_manifest_invalid");
5187
+ }
5188
+ total += entry.bytes;
5189
+ if (total > 1024 * 1024 * 1024)
5190
+ throw new Error("engine_pack_build_too_large");
5191
+ const file = resolve11(dist, path);
5192
+ const rel = relative9(canonicalDist, realpathSync8(file));
5193
+ if (isAbsolute4(rel) || rel === ".." || rel.startsWith(`..${sep6}`))
5194
+ throw new Error("engine_pack_build_path_escape");
5195
+ const stat = lstatSync8(file);
5196
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size !== entry.bytes)
5197
+ throw new Error("engine_pack_build_file_invalid");
5198
+ const bytes = readFileSync13(file);
5199
+ if (sha2562(bytes) !== entry.sha256)
5200
+ throw new Error("engine_pack_build_digest_mismatch");
5201
+ artifacts.set(path, path.endsWith(".json") ? bytes : Buffer.alloc(0));
5202
+ }
5203
+ const indexPath = safeRelativePath(runtime.packIndexUrl);
5204
+ const indexBytes = artifacts.get(indexPath);
5205
+ if (!indexBytes || indexBytes.length > 16 * 1024 * 1024)
5206
+ throw new Error("engine_pack_catalog_missing");
5207
+ const index = JSON.parse(indexBytes.toString("utf8"));
5208
+ if (!Array.isArray(index))
5209
+ throw new Error("engine_pack_catalog_invalid");
5210
+ const sources = new Set(sourcePaths);
5211
+ const rows = [];
5212
+ const seen = new Set;
5213
+ const inspect = [];
5214
+ for (const raw of index) {
5215
+ const row = object(raw);
5216
+ if (typeof row.sourcePath !== "string" || !sources.has(row.sourcePath))
5217
+ continue;
5218
+ 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("//")) {
5219
+ throw new Error("engine_pack_catalog_invalid");
5220
+ }
5221
+ seen.add(row.guid);
5222
+ const packagePath = safeRelativePath(row.packageUrl.slice(1));
5223
+ const bytes = artifacts.get(packagePath);
5224
+ if (!bytes || bytes.length > 16 * 1024 * 1024)
5225
+ throw new Error("engine_pack_product_missing");
5226
+ const pack = object(JSON.parse(bytes.toString("utf8")));
5227
+ if (!Array.isArray(pack.assets))
5228
+ throw new Error("engine_pack_product_invalid");
5229
+ const matches = pack.assets.map(object).filter((asset) => asset.guid === row.guid && asset.kind === row.kind);
5230
+ if (matches.length !== 1)
5231
+ throw new Error("engine_pack_product_guid_missing");
5232
+ rows.push({
5233
+ guid: row.guid,
5234
+ kind: row.kind,
5235
+ source: row.sourcePath,
5236
+ sourcePath: row.sourcePath,
5237
+ ...typeof row.name === "string" ? { name: row.name } : {},
5238
+ packageUrl: row.packageUrl
5239
+ });
5240
+ inspect.push({ guid: row.guid, kind: row.kind, packagePath, packageSha256: sha2562(bytes), verified: true });
5241
+ }
5242
+ if (!rows.some((row) => row.kind === "scene" || row.kind === "mesh"))
5243
+ throw new Error("engine_pack_readback_missing_renderable");
5244
+ return {
5245
+ authority: "engine-build-catalog",
5246
+ verify: { artifacts: artifacts.size, bytes: total, catalogSha256: sha2562(indexBytes) },
5247
+ list: rows,
5248
+ rows,
5249
+ inspect
5250
+ };
5251
+ }
5252
+
5145
5253
  // src/asset3d/transaction.ts
5146
5254
  function confined4(root, candidate) {
5147
- const rel = relative9(root, candidate);
5148
- return rel === "" || !isAbsolute4(rel) && rel !== ".." && !rel.startsWith(`..${sep6}`);
5255
+ const rel = relative10(root, candidate);
5256
+ return rel === "" || !isAbsolute5(rel) && rel !== ".." && !rel.startsWith(`..${sep7}`);
5149
5257
  }
5150
5258
  function forgeaxRoot(projectRoot) {
5151
- return resolve11(projectRoot, ".forgeax");
5259
+ return resolve12(projectRoot, ".forgeax");
5152
5260
  }
5153
5261
  function journalPath(projectRoot, execution) {
5154
- return resolve11(forgeaxRoot(projectRoot), "asset3d-transactions", `${execution}.json`);
5262
+ return resolve12(forgeaxRoot(projectRoot), "asset3d-transactions", `${execution}.json`);
5155
5263
  }
5156
5264
  function providerResultPath(projectRoot, execution) {
5157
- return resolve11(forgeaxRoot(projectRoot), "asset3d-results", `${execution}.json`);
5265
+ return resolve12(forgeaxRoot(projectRoot), "asset3d-results", `${execution}.json`);
5158
5266
  }
5159
5267
  function readInstall(projectRoot) {
5160
5268
  let manifest;
5161
5269
  try {
5162
- manifest = JSON.parse(readFileSync13(resolve11(forgeaxRoot(projectRoot), "asset3d-install.json"), "utf8"));
5270
+ manifest = JSON.parse(readFileSync14(resolve12(forgeaxRoot(projectRoot), "asset3d-install.json"), "utf8"));
5163
5271
  } catch {
5164
5272
  throw new Error("asset3d_not_installed");
5165
5273
  }
@@ -5172,7 +5280,7 @@ function readJournal(projectRoot, execution) {
5172
5280
  throw new Error("asset3d_execution_invalid");
5173
5281
  let journal;
5174
5282
  try {
5175
- journal = JSON.parse(readFileSync13(journalPath(projectRoot, execution), "utf8"));
5283
+ journal = JSON.parse(readFileSync14(journalPath(projectRoot, execution), "utf8"));
5176
5284
  } catch {
5177
5285
  throw new Error("asset3d_execution_not_found");
5178
5286
  }
@@ -5187,7 +5295,7 @@ function updateJournal(projectRoot, journal, patch) {
5187
5295
  return next;
5188
5296
  }
5189
5297
  function beginAsset3d(projectRootInput, queries, options = {}) {
5190
- const projectRoot = realpathSync8(projectRootInput);
5298
+ const projectRoot = realpathSync9(projectRootInput);
5191
5299
  if (queries.length < 1 || queries.length > 16 || queries.some((query) => [...query].length < 1 || [...query].length > 200)) {
5192
5300
  throw new Error("asset3d_queries_invalid: expected 1..16 queries of 1..200 characters");
5193
5301
  }
@@ -5195,9 +5303,9 @@ function beginAsset3d(projectRootInput, queries, options = {}) {
5195
5303
  const release = resolveEngineRelease(projectRoot, options.carrierPluginRoot === undefined ? {} : { pluginRoot: options.carrierPluginRoot });
5196
5304
  const execution = randomUUID4();
5197
5305
  const relativeOutput = `workspace/asset3d/${execution}`;
5198
- const quarantine = resolve11(projectRoot, ".forgeax", "asset3d-quarantine");
5199
- const allowedQuarantineRoot = resolve11(quarantine, relativeOutput);
5200
- ensurePrivateDir(resolve11(projectRoot, ".forgeax", "asset3d-transactions"));
5306
+ const quarantine = resolve12(projectRoot, ".forgeax", "asset3d-quarantine");
5307
+ const allowedQuarantineRoot = resolve12(quarantine, relativeOutput);
5308
+ ensurePrivateDir(resolve12(projectRoot, ".forgeax", "asset3d-transactions"));
5201
5309
  ensurePrivateDir(allowedQuarantineRoot);
5202
5310
  const journal = {
5203
5311
  schemaVersion: TRANSACTION_SCHEMA,
@@ -5216,7 +5324,7 @@ function beginAsset3d(projectRootInput, queries, options = {}) {
5216
5324
  return { execution, output_dir: relativeOutput };
5217
5325
  }
5218
5326
  function asset3dSearchOutputDir(projectRootInput, execution, queries) {
5219
- const projectRoot = realpathSync8(projectRootInput);
5327
+ const projectRoot = realpathSync9(projectRootInput);
5220
5328
  const journal = readJournal(projectRoot, execution);
5221
5329
  if (journal.state !== "begun") {
5222
5330
  throw new Error(`asset3d_execution_state_invalid: ${journal.state}`);
@@ -5225,14 +5333,14 @@ function asset3dSearchOutputDir(projectRootInput, execution, queries) {
5225
5333
  throw new Error("asset3d_search_query_identity_mismatch");
5226
5334
  }
5227
5335
  const outputDir = `workspace/asset3d/${execution}`;
5228
- const expectedRoot = resolve11(forgeaxRoot(projectRoot), "asset3d-quarantine", outputDir);
5229
- if (journal.allowedQuarantineRoot !== expectedRoot || !existsSync9(expectedRoot) || realpathSync8(expectedRoot) !== expectedRoot) {
5336
+ const expectedRoot = resolve12(forgeaxRoot(projectRoot), "asset3d-quarantine", outputDir);
5337
+ if (journal.allowedQuarantineRoot !== expectedRoot || !existsSync9(expectedRoot) || realpathSync9(expectedRoot) !== expectedRoot) {
5230
5338
  throw new Error("asset3d_search_output_identity_mismatch");
5231
5339
  }
5232
5340
  return outputDir;
5233
5341
  }
5234
5342
  function recordAsset3dProviderResult(projectRootInput, execution, providerResult) {
5235
- const projectRoot = realpathSync8(projectRootInput);
5343
+ const projectRoot = realpathSync9(projectRootInput);
5236
5344
  const journal = readJournal(projectRoot, execution);
5237
5345
  if (journal.state !== "begun") {
5238
5346
  throw new Error(`asset3d_execution_state_invalid: ${journal.state}`);
@@ -5241,19 +5349,19 @@ function recordAsset3dProviderResult(projectRootInput, execution, providerResult
5241
5349
  if (bytes < 1 || bytes > MAX_JSON_BYTES) {
5242
5350
  throw new Error("provider_result_too_large: expected 1 byte..1 MiB");
5243
5351
  }
5244
- ensurePrivateDir(resolve11(forgeaxRoot(projectRoot), "asset3d-results"));
5352
+ ensurePrivateDir(resolve12(forgeaxRoot(projectRoot), "asset3d-results"));
5245
5353
  atomicWrite(providerResultPath(projectRoot, execution), providerResult, 384);
5246
5354
  updateJournal(projectRoot, journal, { state: "provider_complete" });
5247
5355
  }
5248
5356
  function fileBytesChecked(root, entry) {
5249
- const source = resolve11(root, entry.path);
5357
+ const source = resolve12(root, entry.path);
5250
5358
  if (!confined4(root, source))
5251
5359
  throw new Error("asset_manifest_escape");
5252
- const canonicalRoot = realpathSync8(root);
5253
- const canonicalSource = realpathSync8(source);
5360
+ const canonicalRoot = realpathSync9(root);
5361
+ const canonicalSource = realpathSync9(source);
5254
5362
  if (!confined4(canonicalRoot, canonicalSource))
5255
5363
  throw new Error("asset_manifest_escape");
5256
- const before = lstatSync8(source);
5364
+ const before = lstatSync9(source);
5257
5365
  if (!before.isFile() || before.isSymbolicLink())
5258
5366
  throw new Error("asset_manifest_not_regular");
5259
5367
  const fd = openSync4(source, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
@@ -5261,7 +5369,7 @@ function fileBytesChecked(root, entry) {
5261
5369
  const opened = fstatSync(fd);
5262
5370
  if (!opened.isFile() || opened.size !== entry.bytes)
5263
5371
  throw new Error("asset_manifest_bytes_mismatch");
5264
- const bytes = readFileSync13(fd);
5372
+ const bytes = readFileSync14(fd);
5265
5373
  if (sha2562(bytes) !== entry.sha256)
5266
5374
  throw new Error("asset_manifest_digest_mismatch");
5267
5375
  return bytes;
@@ -5270,9 +5378,9 @@ function fileBytesChecked(root, entry) {
5270
5378
  }
5271
5379
  }
5272
5380
  function acquireLock2(projectRoot, assetId, timeoutMs) {
5273
- const root = resolve11(forgeaxRoot(projectRoot), "asset3d-locks");
5381
+ const root = resolve12(forgeaxRoot(projectRoot), "asset3d-locks");
5274
5382
  ensurePrivateDir(root);
5275
- const path = resolve11(root, `${assetId}.lock`);
5383
+ const path = resolve12(root, `${assetId}.lock`);
5276
5384
  const token = randomUUID4();
5277
5385
  const deadline = Date.now() + timeoutMs;
5278
5386
  for (;; ) {
@@ -5283,7 +5391,7 @@ function acquireLock2(projectRoot, assetId, timeoutMs) {
5283
5391
  closeSync3(fd);
5284
5392
  return () => {
5285
5393
  try {
5286
- const current = JSON.parse(readFileSync13(path, "utf8"));
5394
+ const current = JSON.parse(readFileSync14(path, "utf8"));
5287
5395
  if (current.token === token)
5288
5396
  unlinkSync3(path);
5289
5397
  } catch {}
@@ -5332,17 +5440,17 @@ function engineAddedRows(projectRoot, asset) {
5332
5440
  if (!Array.isArray(subAssets)) {
5333
5441
  if (typeof asset.metaPath !== "string")
5334
5442
  throw new Error("engine_readback_missing_meta");
5335
- const metaPath = resolve11(projectRoot, asset.metaPath);
5443
+ const metaPath = resolve12(projectRoot, asset.metaPath);
5336
5444
  if (!confined4(projectRoot, metaPath))
5337
5445
  throw new Error("engine_readback_meta_escape");
5338
- const canonical = realpathSync8(metaPath);
5339
- const info = lstatSync8(metaPath);
5446
+ const canonical = realpathSync9(metaPath);
5447
+ const info = lstatSync9(metaPath);
5340
5448
  if (!confined4(projectRoot, canonical) || !info.isFile() || info.isSymbolicLink()) {
5341
5449
  throw new Error("engine_readback_meta_invalid");
5342
5450
  }
5343
5451
  let meta;
5344
5452
  try {
5345
- meta = JSON.parse(readFileSync13(metaPath, "utf8"));
5453
+ meta = JSON.parse(readFileSync14(metaPath, "utf8"));
5346
5454
  } catch {
5347
5455
  throw new Error("engine_readback_meta_invalid");
5348
5456
  }
@@ -5353,10 +5461,10 @@ function engineAddedRows(projectRoot, asset) {
5353
5461
  const projectPath = (value) => {
5354
5462
  if (typeof value !== "string")
5355
5463
  return value;
5356
- const absolute = resolve11(projectRoot, value);
5464
+ const absolute = resolve12(projectRoot, value);
5357
5465
  if (!confined4(projectRoot, absolute))
5358
5466
  throw new Error("engine_readback_path_escape");
5359
- return relative9(projectRoot, absolute).split(sep6).join("/");
5467
+ return relative10(projectRoot, absolute).split(sep7).join("/");
5360
5468
  };
5361
5469
  return subAssets.map((row) => ({ source: projectPath(asset.source), metaPath: projectPath(asset.metaPath), reused: asset.reused, ...row }));
5362
5470
  }
@@ -5369,24 +5477,38 @@ function engineReadback(projectRoot, assetRelative, add, carrierPluginRoot) {
5369
5477
  if (add && guids.length === 0)
5370
5478
  throw new Error("engine_readback_missing_guid");
5371
5479
  const inspect = guids.map((guid) => engineCommand(projectRoot, ["asset", "inspect", guid, "--json"], carrierPluginRoot).value);
5372
- const listed = Array.isArray(list.value) ? list.value : [];
5480
+ const listed = Array.isArray(list.value) ? list.value : Array.isArray(list.value?.assets) ? list.value.assets : [];
5373
5481
  for (const guid of guids)
5374
5482
  if (!listed.some((entry) => entry.guid === guid))
5375
5483
  throw new Error("engine_catalog_readback_missing");
5376
- return { ...add ? { add: add.value } : {}, verify: verify.value, list: list.value, inspect, rows };
5484
+ return { ...add ? { add: add.value } : {}, verify: verify.value, list: listed, inspect, rows };
5485
+ }
5486
+ function packReadback(projectRoot, item, carrierPluginRoot) {
5487
+ const release = resolveEngineRelease(projectRoot, carrierPluginRoot === undefined ? {} : { pluginRoot: carrierPluginRoot });
5488
+ const result = spawnSync4(process.execPath, [release.cliPath, "build", "--json"], {
5489
+ cwd: projectRoot,
5490
+ encoding: "utf8",
5491
+ timeout: 150000,
5492
+ maxBuffer: 8 * 1024 * 1024
5493
+ });
5494
+ if (result.error || result.status !== 0)
5495
+ throw new Error("engine_pack_build_failed");
5496
+ const envelope = parseEnvelope(result.stdout, "build");
5497
+ const prefix = `assets/3d/ea-3d/${item.providerAssetId}/`;
5498
+ return readPackBuildCatalog(projectRoot, item.manifest.map((entry) => prefix + entry.path), envelope.value);
5377
5499
  }
5378
5500
  function priorProvenance(destination) {
5379
5501
  try {
5380
- return JSON.parse(readFileSync13(resolve11(destination, ".forgeax-asset.json"), "utf8"));
5502
+ return JSON.parse(readFileSync14(resolve12(destination, ".forgeax-asset.json"), "utf8"));
5381
5503
  } catch {
5382
5504
  return;
5383
5505
  }
5384
5506
  }
5385
5507
  function liveFilesMatch(destination, item) {
5386
5508
  return item.manifest.every((entry) => {
5387
- const path = resolve11(destination, entry.path);
5509
+ const path = resolve12(destination, entry.path);
5388
5510
  try {
5389
- return confined4(destination, path) && statSync6(path).isFile() && statSync6(path).size === entry.bytes && sha2562(readFileSync13(path)) === entry.sha256;
5511
+ return confined4(destination, path) && statSync6(path).isFile() && statSync6(path).size === entry.bytes && sha2562(readFileSync14(path)) === entry.sha256;
5390
5512
  } catch {
5391
5513
  return false;
5392
5514
  }
@@ -5395,21 +5517,21 @@ function liveFilesMatch(destination, item) {
5395
5517
  function quarantineFiles(root, directory = root) {
5396
5518
  const files = [];
5397
5519
  for (const name of readdirSync7(directory)) {
5398
- const path = resolve11(directory, name);
5399
- const info = lstatSync8(path);
5520
+ const path = resolve12(directory, name);
5521
+ const info = lstatSync9(path);
5400
5522
  if (info.isSymbolicLink())
5401
5523
  throw new Error("asset_quarantine_symlink_rejected");
5402
5524
  if (info.isDirectory())
5403
5525
  files.push(...quarantineFiles(root, path));
5404
5526
  else if (info.isFile())
5405
- files.push(relative9(root, path).split(sep6).join("/"));
5527
+ files.push(relative10(root, path).split(sep7).join("/"));
5406
5528
  else
5407
5529
  throw new Error("asset_quarantine_special_file_rejected");
5408
5530
  }
5409
5531
  return files;
5410
5532
  }
5411
5533
  function validateQuarantine(journal, items) {
5412
- const root = realpathSync8(journal.allowedQuarantineRoot);
5534
+ const root = realpathSync9(journal.allowedQuarantineRoot);
5413
5535
  const expected = items.flatMap((item) => item.manifest.map((entry) => entry.path)).sort();
5414
5536
  if (new Set(expected).size !== expected.length)
5415
5537
  throw new Error("provider_result_invalid: cross-item manifest collision");
@@ -5418,10 +5540,10 @@ function validateQuarantine(journal, items) {
5418
5540
  throw new Error("asset_quarantine_undeclared_file");
5419
5541
  }
5420
5542
  function stageItem(projectRoot, journal, item, destination, prior) {
5421
- const root = realpathSync8(journal.allowedQuarantineRoot);
5422
- const stageRoot = resolve11(forgeaxRoot(projectRoot), "asset3d-staging", journal.execution, item.providerAssetId);
5543
+ const root = realpathSync9(journal.allowedQuarantineRoot);
5544
+ const stageRoot = resolve12(forgeaxRoot(projectRoot), "asset3d-staging", journal.execution, item.providerAssetId);
5423
5545
  rmSync6(stageRoot, { recursive: true, force: true });
5424
- ensurePrivateDir(resolve11(stageRoot, ".."));
5546
+ ensurePrivateDir(resolve12(stageRoot, ".."));
5425
5547
  if (existsSync9(destination))
5426
5548
  cpSync2(destination, stageRoot, { recursive: true, errorOnExist: true, force: false });
5427
5549
  else
@@ -5431,17 +5553,17 @@ function stageItem(projectRoot, journal, item, destination, prior) {
5431
5553
  for (const previous of previousFiles) {
5432
5554
  if (typeof previous.relativePath !== "string" || nextPaths.has(previous.relativePath))
5433
5555
  continue;
5434
- const stale = resolve11(stageRoot, previous.relativePath);
5556
+ const stale = resolve12(stageRoot, previous.relativePath);
5435
5557
  if (!confined4(stageRoot, stale))
5436
5558
  throw new Error("asset_provenance_path_escape");
5437
5559
  rmSync6(stale, { force: true });
5438
5560
  rmSync6(`${stale}.meta.json`, { force: true });
5439
5561
  }
5440
5562
  for (const entry of item.manifest) {
5441
- const target = resolve11(stageRoot, entry.path);
5563
+ const target = resolve12(stageRoot, entry.path);
5442
5564
  if (!confined4(stageRoot, target))
5443
5565
  throw new Error("asset_manifest_escape");
5444
- mkdirSync10(resolve11(target, ".."), { recursive: true, mode: 448 });
5566
+ mkdirSync10(resolve12(target, ".."), { recursive: true, mode: 448 });
5445
5567
  atomicWrite(target, fileBytesChecked(root, entry), 384);
5446
5568
  }
5447
5569
  return stageRoot;
@@ -5457,7 +5579,8 @@ function provenance(item, install, readback, refreshed) {
5457
5579
  originSetDigest: install.originSetDigest,
5458
5580
  aggregateSha256: item.sha256,
5459
5581
  bytes: item.bytes,
5460
- primaryModel: item.primaryModel,
5582
+ deliveredFormat: item.deliveredFormat,
5583
+ ...item.deliveredFormat === "pack" ? { primaryPack: item.primaryPack } : { primaryModel: item.primaryModel },
5461
5584
  engine: { version: ENGINE_VERSION, commit: ENGINE_COMMIT },
5462
5585
  refreshed,
5463
5586
  files: item.manifest.map((entry) => ({
@@ -5471,8 +5594,8 @@ function provenance(item, install, readback, refreshed) {
5471
5594
  };
5472
5595
  }
5473
5596
  function commitItem(projectRoot, initialJournal, install, item, refresh, lockTimeoutMs, carrierPluginRoot) {
5474
- const destination = resolve11(projectRoot, "assets", "3d", "ea-3d", item.providerAssetId);
5475
- const assetRoot = resolve11(projectRoot, "assets", "3d", "ea-3d");
5597
+ const destination = resolve12(projectRoot, "assets", "3d", "ea-3d", item.providerAssetId);
5598
+ const assetRoot = resolve12(projectRoot, "assets", "3d", "ea-3d");
5476
5599
  ensurePrivateDir(assetRoot);
5477
5600
  if (!confined4(assetRoot, destination))
5478
5601
  throw new Error("asset_destination_escape");
@@ -5483,29 +5606,30 @@ function commitItem(projectRoot, initialJournal, install, item, refresh, lockTim
5483
5606
  const priorDigest = typeof prior?.aggregateSha256 === "string" ? prior.aggregateSha256 : undefined;
5484
5607
  if (priorDigest === item.sha256 && liveFilesMatch(destination, item)) {
5485
5608
  const priorRows = Array.isArray(prior?.files) ? prior.files.flatMap((entry) => entry.engineRows ?? []) : [];
5486
- const baseReadback = engineReadback(projectRoot, `assets/3d/ea-3d/${item.providerAssetId}`, undefined, carrierPluginRoot);
5609
+ const baseReadback = item.deliveredFormat === "pack" ? packReadback(projectRoot, item, carrierPluginRoot) : engineReadback(projectRoot, `assets/3d/ea-3d/${item.providerAssetId}`, undefined, carrierPluginRoot);
5487
5610
  const guids = priorRows.flatMap((row) => typeof row.guid === "string" ? [row.guid] : []);
5488
5611
  const listed = Array.isArray(baseReadback.list) ? baseReadback.list : [];
5489
5612
  if (guids.length === 0 || guids.some((guid) => !listed.some((entry) => entry.guid === guid)))
5490
5613
  throw new Error("asset_reuse_readback_failed");
5491
- const readback = { ...baseReadback, inspect: guids.map((guid) => engineCommand(projectRoot, ["asset", "inspect", guid, "--json"], carrierPluginRoot).value) };
5614
+ const readback = item.deliveredFormat === "pack" ? baseReadback : { ...baseReadback, inspect: guids.map((guid) => engineCommand(projectRoot, ["asset", "inspect", guid, "--json"], carrierPluginRoot).value) };
5492
5615
  journal = updateJournal(projectRoot, journal, { state: "committed", previousDigest: priorDigest, newDigest: item.sha256 });
5493
- return { providerAssetId: item.providerAssetId, digest: item.sha256, bytes: item.bytes, reused: true, refreshed: false, sourcePath: relative9(projectRoot, destination), provenancePath: relative9(projectRoot, resolve11(destination, ".forgeax-asset.json")), engine: { version: ENGINE_VERSION, commit: ENGINE_COMMIT }, catalog: readback, rows: priorRows };
5616
+ return { providerAssetId: item.providerAssetId, digest: item.sha256, bytes: item.bytes, reused: true, refreshed: false, sourcePath: relative10(projectRoot, destination), provenancePath: relative10(projectRoot, resolve12(destination, ".forgeax-asset.json")), engine: { version: ENGINE_VERSION, commit: ENGINE_COMMIT }, catalog: readback, rows: priorRows };
5494
5617
  }
5495
5618
  if (priorDigest && priorDigest !== item.sha256 && !refresh)
5496
5619
  throw new Error("asset_changed");
5497
5620
  const stage = stageItem(projectRoot, journal, item, destination, prior);
5498
- const backup = resolve11(forgeaxRoot(projectRoot), "asset3d-backups", journal.execution, item.providerAssetId);
5499
- ensurePrivateDir(resolve11(backup, ".."));
5621
+ const backup = resolve12(forgeaxRoot(projectRoot), "asset3d-backups", journal.execution, item.providerAssetId);
5622
+ ensurePrivateDir(resolve12(backup, ".."));
5500
5623
  journal = updateJournal(projectRoot, journal, { state: "committing", destination, backupPath: existsSync9(destination) ? backup : undefined, previousDigest: priorDigest, newDigest: item.sha256 });
5501
5624
  if (existsSync9(destination))
5502
5625
  renameSync7(destination, backup);
5503
5626
  renameSync7(stage, destination);
5504
5627
  try {
5505
5628
  const assetRelative = `assets/3d/ea-3d/${item.providerAssetId}`;
5506
- const add = engineCommand(projectRoot, ["asset", "add", assetRelative, "--reimport-policy", "semantic-only", "--json"], carrierPluginRoot);
5507
- const readback = engineReadback(projectRoot, assetRelative, add, carrierPluginRoot);
5508
- atomicWrite(resolve11(destination, ".forgeax-asset.json"), `${JSON.stringify(provenance(item, install, readback, priorDigest !== undefined), null, 2)}
5629
+ const readback = item.deliveredFormat === "pack" ? packReadback(projectRoot, item, carrierPluginRoot) : engineReadback(projectRoot, assetRelative, engineCommand(projectRoot, ["asset", "add", assetRelative, "--reimport-policy", "semantic-only", "--json"], carrierPluginRoot), carrierPluginRoot);
5630
+ if (item.deliveredFormat === "pack" && !liveFilesMatch(destination, item))
5631
+ throw new Error("engine_pack_source_changed");
5632
+ atomicWrite(resolve12(destination, ".forgeax-asset.json"), `${JSON.stringify(provenance(item, install, readback, priorDigest !== undefined), null, 2)}
5509
5633
  `);
5510
5634
  journal = updateJournal(projectRoot, journal, { state: "engine_verified" });
5511
5635
  if (existsSync9(backup))
@@ -5524,7 +5648,7 @@ function commitItem(projectRoot, initialJournal, install, item, refresh, lockTim
5524
5648
  }
5525
5649
  }
5526
5650
  function commitAsset3d(options) {
5527
- const projectRoot = realpathSync8(options.projectRoot);
5651
+ const projectRoot = realpathSync9(options.projectRoot);
5528
5652
  const install = readInstall(projectRoot);
5529
5653
  let journal = readJournal(projectRoot, options.execution);
5530
5654
  if (journal.state !== "begun" && journal.state !== "provider_complete" && journal.state !== "validated") {
@@ -5536,7 +5660,7 @@ function commitAsset3d(options) {
5536
5660
  let providerResult = options.providerResult;
5537
5661
  if (providerResult === undefined) {
5538
5662
  try {
5539
- providerResult = readFileSync13(providerResultPath(projectRoot, options.execution));
5663
+ providerResult = readFileSync14(providerResultPath(projectRoot, options.execution));
5540
5664
  } catch {
5541
5665
  throw new Error("asset3d_provider_result_missing: call search_asset for this execution first");
5542
5666
  }
@@ -5587,7 +5711,7 @@ function commitAsset3d(options) {
5587
5711
  };
5588
5712
  }
5589
5713
  function abortAsset3d(projectRootInput, execution) {
5590
- const projectRoot = realpathSync8(projectRootInput);
5714
+ const projectRoot = realpathSync9(projectRootInput);
5591
5715
  const journal = readJournal(projectRoot, execution);
5592
5716
  if (journal.state === "committed" || journal.state === "complete" || journal.state === "engine_verified") {
5593
5717
  throw new Error("asset3d_execution_already_committed");
@@ -5598,20 +5722,20 @@ function abortAsset3d(projectRootInput, execution) {
5598
5722
  return { execution, aborted: true };
5599
5723
  }
5600
5724
  function doctorAsset3d(projectRootInput, options = {}) {
5601
- const projectRoot = realpathSync8(projectRootInput);
5725
+ const projectRoot = realpathSync9(projectRootInput);
5602
5726
  const install = readInstall(projectRoot);
5603
5727
  const release = resolveEngineRelease(projectRoot, options.carrierPluginRoot === undefined ? {} : { pluginRoot: options.carrierPluginRoot });
5604
5728
  const recovered = [];
5605
- const root = resolve11(forgeaxRoot(projectRoot), "asset3d-transactions");
5729
+ const root = resolve12(forgeaxRoot(projectRoot), "asset3d-transactions");
5606
5730
  if (existsSync9(root))
5607
5731
  for (const file of readdirSync7(root).filter((name) => name.endsWith(".json"))) {
5608
5732
  try {
5609
- const path = resolve11(root, file);
5610
- const journal = JSON.parse(readFileSync13(path, "utf8"));
5733
+ const path = resolve12(root, file);
5734
+ const journal = JSON.parse(readFileSync14(path, "utf8"));
5611
5735
  if (journal.schemaVersion !== TRANSACTION_SCHEMA || journal.state !== "committing" || !journal.destination)
5612
5736
  continue;
5613
- const assetsRoot = resolve11(projectRoot, "assets", "3d", "ea-3d");
5614
- const backupsRoot = resolve11(forgeaxRoot(projectRoot), "asset3d-backups");
5737
+ const assetsRoot = resolve12(projectRoot, "assets", "3d", "ea-3d");
5738
+ const backupsRoot = resolve12(forgeaxRoot(projectRoot), "asset3d-backups");
5615
5739
  if (!confined4(assetsRoot, journal.destination) || journal.backupPath && !confined4(backupsRoot, journal.backupPath))
5616
5740
  continue;
5617
5741
  rmSync6(journal.destination, { recursive: true, force: true });
@@ -5621,13 +5745,13 @@ function doctorAsset3d(projectRootInput, options = {}) {
5621
5745
  recovered.push(journal.execution);
5622
5746
  } catch {}
5623
5747
  }
5624
- return { installed: true, recovered, engine: { version: release.version, commit: release.commit }, provider: { commit: install.providerCommit, cache: install.providerCache, live: existsSync9(resolve11(install.providerCache, "venv", "bin", "python")) } };
5748
+ return { installed: true, recovered, engine: { version: release.version, commit: release.commit }, provider: { commit: install.providerCommit, cache: install.providerCache, live: existsSync9(resolve12(install.providerCache, "venv", "bin", "python")) } };
5625
5749
  }
5626
5750
 
5627
5751
  // src/asset3d/aw-access.ts
5628
5752
  import { spawnSync as spawnSync5 } from "node:child_process";
5629
5753
  import { existsSync as existsSync10 } from "node:fs";
5630
- import { resolve as resolve12 } from "node:path";
5754
+ import { resolve as resolve13 } from "node:path";
5631
5755
  var AW_SERVICE_PATH = "/trpc.oasismetric.omcontentserver.http";
5632
5756
  var ACCESS_CHECK_SCHEMA = "forgeax.asset3d-access-check/1.0.0";
5633
5757
  var DEFAULT_AW_PUBLIC_SERVICE_ROOT = "http://lb-pl74wsqg-5wi8ujmy1fq2746r.clb.usw-tencentclb.com:8008/trpc.oasismetric.omcontentserver.http";
@@ -5660,7 +5784,7 @@ function resolveAssetLibrarySelection(options = {}) {
5660
5784
  return { library, serviceRoot: normalizeAssetLibraryServiceRoot(configured) };
5661
5785
  }
5662
5786
  function checkAssetLibraryProviderAccess(options) {
5663
- const command = resolve12(options.providerCache, "bin", "asset3d-search");
5787
+ const command = resolve13(options.providerCache, "bin", "asset3d-search");
5664
5788
  if (!existsSync10(command))
5665
5789
  throw new Error("asset3d_provider_not_prepared");
5666
5790
  const result = spawnSync5(command, ["--check-aw-access"], {
@@ -5672,7 +5796,7 @@ function checkAssetLibraryProviderAccess(options) {
5672
5796
  ASSET3D_CATALOG_BASE_URL: "",
5673
5797
  AW_API_BASE_URL: options.serviceRoot,
5674
5798
  AW_API_DEPOT_NAME: options.depotName,
5675
- AW_API_CREDENTIAL_FILE: resolve12(options.credentialFile),
5799
+ AW_API_CREDENTIAL_FILE: resolve13(options.credentialFile),
5676
5800
  AW_API_SANDBOX_KEY: ""
5677
5801
  }
5678
5802
  });
@@ -5698,9 +5822,9 @@ function checkAssetLibraryProviderAccess(options) {
5698
5822
  }
5699
5823
 
5700
5824
  // src/asset3d/credentials.ts
5701
- import { chmodSync as chmodSync5, existsSync as existsSync11, lstatSync as lstatSync9, readFileSync as readFileSync14, statSync as statSync7, unlinkSync as unlinkSync4 } from "node:fs";
5825
+ import { chmodSync as chmodSync5, existsSync as existsSync11, lstatSync as lstatSync10, readFileSync as readFileSync15, statSync as statSync7, unlinkSync as unlinkSync4 } from "node:fs";
5702
5826
  import { homedir as homedir4 } from "node:os";
5703
- import { dirname as dirname11, isAbsolute as isAbsolute5, resolve as resolve13 } from "node:path";
5827
+ import { dirname as dirname11, isAbsolute as isAbsolute6, resolve as resolve14 } from "node:path";
5704
5828
  var AW_CREDENTIAL_SCHEMA = "forgeax.asset3d-credential/1.0.0";
5705
5829
  var AW_KEY_ENV = "FORGEAX_ASSET3D_AW_SANDBOX_KEY";
5706
5830
  var MAX_CREDENTIAL_BYTES = 4096;
@@ -5715,22 +5839,22 @@ function validateKey(value) {
5715
5839
  }
5716
5840
  function defaultAwCredentialFile() {
5717
5841
  const configured = process.env.FORGEAX_ASSET3D_CREDENTIAL_FILE;
5718
- return resolve13(configured || resolve13(homedir4(), ".forgeax", "credentials", "asset3d-aw.json"));
5842
+ return resolve14(configured || resolve14(homedir4(), ".forgeax", "credentials", "asset3d-aw.json"));
5719
5843
  }
5720
5844
  function readAwCredential(pathInput) {
5721
- const path = resolve13(pathInput);
5722
- if (!isAbsolute5(pathInput))
5845
+ const path = resolve14(pathInput);
5846
+ if (!isAbsolute6(pathInput))
5723
5847
  throw new Error("asset3d_credential_path_invalid: absolute path required");
5724
5848
  if (!existsSync11(path))
5725
5849
  return;
5726
5850
  try {
5727
- const metadata = lstatSync9(path);
5851
+ const metadata = lstatSync10(path);
5728
5852
  const wrongOwner = typeof process.getuid === "function" && metadata.uid !== process.getuid();
5729
5853
  if (!metadata.isFile() || metadata.isSymbolicLink() || wrongOwner)
5730
5854
  throw new Error;
5731
5855
  if ((metadata.mode & 63) !== 0 || metadata.size < 1 || metadata.size > MAX_CREDENTIAL_BYTES)
5732
5856
  throw new Error;
5733
- const parsed = JSON.parse(readFileSync14(path, "utf8"));
5857
+ const parsed = JSON.parse(readFileSync15(path, "utf8"));
5734
5858
  if (Object.keys(parsed).sort().join(",") !== "provider,sandboxKey,schemaVersion")
5735
5859
  throw new Error;
5736
5860
  if (parsed.schemaVersion !== AW_CREDENTIAL_SCHEMA || parsed.provider !== "aw" || typeof parsed.sandboxKey !== "string")
@@ -5792,13 +5916,13 @@ async function acquireAwKey(path) {
5792
5916
  return { key: await promptAwKey(), source: "prompt" };
5793
5917
  }
5794
5918
  function writeAwCredential(pathInput, keyInput) {
5795
- if (!isAbsolute5(pathInput))
5919
+ if (!isAbsolute6(pathInput))
5796
5920
  throw new Error("asset3d_credential_path_invalid: absolute path required");
5797
- const path = resolve13(pathInput);
5921
+ const path = resolve14(pathInput);
5798
5922
  const key = validateKey(keyInput);
5799
5923
  const parent = dirname11(path);
5800
5924
  ensurePrivateDir(parent);
5801
- const parentMetadata = lstatSync9(parent);
5925
+ const parentMetadata = lstatSync10(parent);
5802
5926
  const wrongParentOwner = typeof process.getuid === "function" && parentMetadata.uid !== process.getuid();
5803
5927
  if (!parentMetadata.isDirectory() || parentMetadata.isSymbolicLink() || wrongParentOwner) {
5804
5928
  throw new Error("asset3d_credential_path_invalid: parent must be an owned regular directory");
@@ -5807,7 +5931,7 @@ function writeAwCredential(pathInput, keyInput) {
5807
5931
  const existed = existsSync11(path);
5808
5932
  if (existed)
5809
5933
  readAwCredential(path);
5810
- const previous = existed ? readFileSync14(path) : undefined;
5934
+ const previous = existed ? readFileSync15(path) : undefined;
5811
5935
  const previousMode = existed ? statSync7(path).mode & 511 : undefined;
5812
5936
  const bytes = `${JSON.stringify({ schemaVersion: AW_CREDENTIAL_SCHEMA, provider: "aw", sandboxKey: key }, null, 2)}
5813
5937
  `;
@@ -5914,7 +6038,7 @@ function rewriteSearchTool(tool) {
5914
6038
  const required = Array.isArray(inputSchema.required) ? inputSchema.required.filter((field) => typeof field === "string" && field !== "output_dir") : [];
5915
6039
  return {
5916
6040
  ...tool,
5917
- description: `${typeof tool.description === "string" ? `${tool.description} ` : ""}Run forgeax-game asset3d begin first and pass its execution ID. The bridge injects the transaction-owned output directory.`,
6041
+ description: `${typeof tool.description === "string" ? `${tool.description} ` : ""}Run forgeax-game asset3d begin first and pass its execution ID. The bridge injects the transaction-owned output directory. For native Pack preservation select output_format=asset when advertised; glb explicitly requests conversion.`,
5918
6042
  inputSchema: {
5919
6043
  ...inputSchema,
5920
6044
  properties: {
@@ -5961,10 +6085,21 @@ function transformAsset3dSearchCall(projectRoot, request2) {
5961
6085
  if (typeof args.execution !== "string") {
5962
6086
  throw new Error("asset3d_execution_required: run `forgeax-game asset3d begin` first");
5963
6087
  }
5964
- if (!Array.isArray(args.queries) || args.queries.some((query) => typeof query !== "string")) {
6088
+ if (!Array.isArray(args.queries)) {
5965
6089
  throw new Error("asset3d_queries_invalid");
5966
6090
  }
5967
- const outputDir = asset3dSearchOutputDir(projectRoot, args.execution, args.queries);
6091
+ const queries = args.queries.map((query) => {
6092
+ if (typeof query === "string")
6093
+ return query;
6094
+ if (query && typeof query === "object" && !Array.isArray(query)) {
6095
+ const selected = query;
6096
+ if (Object.keys(selected).every((key) => key === "content" || key === "assetId") && typeof selected.content === "string" && typeof selected.assetId === "string" && /^[A-Za-z0-9._-]{1,128}$/.test(selected.assetId) && ![".", ".."].includes(selected.assetId)) {
6097
+ return selected.content;
6098
+ }
6099
+ }
6100
+ throw new Error("asset3d_queries_invalid");
6101
+ });
6102
+ const outputDir = asset3dSearchOutputDir(projectRoot, args.execution, queries);
5968
6103
  const { execution: _execution, output_dir: _callerOutput, ...providerArguments } = args;
5969
6104
  return {
5970
6105
  ...request2,
@@ -6080,20 +6215,27 @@ async function runAsset3dMcpProxy(projectRoot, launch) {
6080
6215
  };
6081
6216
  process.once("SIGINT", stopChild);
6082
6217
  process.once("SIGTERM", stopChild);
6083
- return await new Promise((resolve14, reject) => {
6218
+ return await new Promise((resolve15, reject) => {
6084
6219
  child.once("error", reject);
6085
- child.once("close", (code, signal) => resolve14(code ?? (signal ? 1 : 0)));
6220
+ child.once("close", (code, signal) => resolve15(code ?? (signal ? 1 : 0)));
6086
6221
  });
6087
6222
  }
6088
6223
 
6089
6224
  // src/asset3d/host-config.ts
6090
- function inspectAsset3dHost(client, root) {
6091
- const launch = asset3dLaunchSpec("npx");
6225
+ function asset3dHostMode(client, root) {
6226
+ return inspectConfig(client, root, launchSpec("local")).state === "current" ? "local" : "npx";
6227
+ }
6228
+ function inspectAsset3dHost(client, root, mode = asset3dHostMode(client, root)) {
6229
+ const launch = asset3dLaunchSpec(mode);
6092
6230
  const status = inspectConfig(client, root, launch, ASSET3D_SERVER_KEY);
6093
6231
  if (status.state !== "different")
6094
6232
  return status;
6233
+ const published = asset3dLaunchSpec("npx");
6234
+ if (mode === "local" && inspectConfig(client, root, published, ASSET3D_SERVER_KEY).state === "current") {
6235
+ return { ...status, state: "outdated" };
6236
+ }
6095
6237
  const previous = configuredGameVersion(client, root, ASSET3D_SERVER_KEY);
6096
- const current = launch.args.find((arg) => arg.startsWith("@forgeax/game@"))?.slice("@forgeax/game@".length);
6238
+ const current = published.args.find((arg) => arg.startsWith("@forgeax/game@"))?.slice("@forgeax/game@".length);
6097
6239
  if (!previous || !current || !/^\d+\.\d+\.\d+$/.test(previous) || !/^\d+\.\d+\.\d+$/.test(current))
6098
6240
  return status;
6099
6241
  const oldParts = previous.split(".").map(Number);
@@ -6101,7 +6243,7 @@ function inspectAsset3dHost(client, root) {
6101
6243
  const differing = oldParts.findIndex((part, index) => part !== newParts[index]);
6102
6244
  if (differing < 0 || oldParts[differing] >= newParts[differing])
6103
6245
  return status;
6104
- const oldLaunch = { ...launch, args: launch.args.map((arg) => arg === `@forgeax/game@${current}` ? `@forgeax/game@${previous}` : arg) };
6246
+ const oldLaunch = { ...published, args: published.args.map((arg) => arg === `@forgeax/game@${current}` ? `@forgeax/game@${previous}` : arg) };
6105
6247
  if (inspectConfig(client, root, oldLaunch, ASSET3D_SERVER_KEY).state !== "current")
6106
6248
  return status;
6107
6249
  return { ...status, state: "outdated" };
@@ -6109,7 +6251,7 @@ function inspectAsset3dHost(client, root) {
6109
6251
  function refreshAsset3dHost(client, root) {
6110
6252
  const status = inspectAsset3dHost(client, root);
6111
6253
  if (status.state === "outdated") {
6112
- const result = applyConfig(client, root, asset3dLaunchSpec("npx"), ASSET3D_SERVER_KEY);
6254
+ const result = applyConfig(client, root, asset3dLaunchSpec(asset3dHostMode(client, root)), ASSET3D_SERVER_KEY);
6113
6255
  process.stdout.write(`UPDATED ${client.label} Asset3D bridge: ${result.path}
6114
6256
  `);
6115
6257
  } else if (status.state === "different" || status.state === "invalid") {
@@ -6118,6 +6260,85 @@ function refreshAsset3dHost(client, root) {
6118
6260
  }
6119
6261
  }
6120
6262
 
6263
+ // src/devkit/engine-mounts.ts
6264
+ import { lstatSync as lstatSync11, readFileSync as readFileSync16, readdirSync as readdirSync8, readlinkSync as readlinkSync2, rmdirSync, unlinkSync as unlinkSync5, writeFileSync as writeFileSync11 } from "node:fs";
6265
+ import { dirname as dirname12, join as join11, resolve as resolve15 } from "node:path";
6266
+ var HOSTS = {
6267
+ ".agents/skills": ["codex"],
6268
+ ".claude/skills": ["claude"],
6269
+ ".cursor/skills": ["cursor"],
6270
+ ".codebuddy/skills": ["codebuddy", "workbuddy"],
6271
+ ".workbuddy/skills": ["workbuddy"]
6272
+ };
6273
+ function pruneUnselectedEngineMounts(root, clients) {
6274
+ const manifestPath = join11(root, ".forgeax", "skill-install-manifest.json");
6275
+ const regular = (path) => {
6276
+ try {
6277
+ const stat = lstatSync11(path);
6278
+ return stat.isFile() && !stat.isSymbolicLink();
6279
+ } catch {
6280
+ return false;
6281
+ }
6282
+ };
6283
+ const directory = (path) => {
6284
+ try {
6285
+ const stat = lstatSync11(path);
6286
+ return stat.isDirectory() && !stat.isSymbolicLink();
6287
+ } catch {
6288
+ return false;
6289
+ }
6290
+ };
6291
+ if (!directory(join11(root, ".forgeax")) || !regular(manifestPath))
6292
+ return [];
6293
+ let manifest;
6294
+ try {
6295
+ manifest = JSON.parse(readFileSync16(manifestPath, "utf8"));
6296
+ } catch {
6297
+ return [];
6298
+ }
6299
+ if (!manifest || manifest.schemaVersion !== "1.0.0" || manifest.sourceRoot !== "skills" || !Array.isArray(manifest.mounts))
6300
+ return [];
6301
+ const removed = [];
6302
+ for (const mount of manifest.mounts) {
6303
+ if (!mount || typeof mount.root !== "string" || !Object.hasOwn(HOSTS, mount.root))
6304
+ continue;
6305
+ const hosts = HOSTS[mount.root];
6306
+ if (!hosts || hosts.some((host) => clients.includes(host)) || !Array.isArray(mount.skills))
6307
+ continue;
6308
+ if (!mount.skills.length || !mount.skills.every((id) => /^forgeax-engine-[a-z0-9-]+$/.test(id)) || new Set(mount.skills).size !== mount.skills.length)
6309
+ continue;
6310
+ const path = join11(root, mount.root);
6311
+ if (!directory(dirname12(path)) || !directory(path))
6312
+ continue;
6313
+ const expected = [".gitignore", ...mount.skills].sort();
6314
+ if (JSON.stringify(readdirSync8(path).sort()) !== JSON.stringify(expected))
6315
+ continue;
6316
+ const ignore = join11(path, ".gitignore");
6317
+ const expectedIgnore = ["# BEGIN FORGEAX MANAGED SKILLS", ...mount.skills.map((id) => `/${id}`), "# END FORGEAX MANAGED SKILLS", ""].join(`
6318
+ `);
6319
+ if (!regular(ignore) || readFileSync16(ignore, "utf8") !== expectedIgnore)
6320
+ continue;
6321
+ if (!mount.skills.every((id) => {
6322
+ const link = join11(path, id);
6323
+ return lstatSync11(link).isSymbolicLink() && resolve15(path, readlinkSync2(link)) === resolve15(root, "skills", id);
6324
+ }))
6325
+ continue;
6326
+ for (const id of mount.skills)
6327
+ unlinkSync5(join11(path, id));
6328
+ unlinkSync5(ignore);
6329
+ rmdirSync(path);
6330
+ if (readdirSync8(dirname12(path)).length === 0)
6331
+ rmdirSync(dirname12(path));
6332
+ removed.push(mount.root);
6333
+ }
6334
+ if (removed.length) {
6335
+ manifest.mounts = manifest.mounts.filter((mount) => !mount || !removed.includes(mount.root));
6336
+ writeFileSync11(manifestPath, `${JSON.stringify(manifest, null, 2)}
6337
+ `);
6338
+ }
6339
+ return removed;
6340
+ }
6341
+
6121
6342
  // src/cli/dispatch.ts
6122
6343
  var HELP = `ForgeaX game development plugin
6123
6344
 
@@ -6184,23 +6405,23 @@ function requireProject() {
6184
6405
  return project.root;
6185
6406
  }
6186
6407
  function updateAgentsFile(root) {
6187
- const path = join11(root, "AGENTS.md");
6188
- const existing = existsSync12(path) ? readFileSync15(path, "utf8") : undefined;
6408
+ const path = join12(root, "AGENTS.md");
6409
+ const existing = existsSync12(path) ? readFileSync17(path, "utf8") : undefined;
6189
6410
  const content = upsertBlock(existing, ROUTING_TEXT);
6190
6411
  if (content === existing)
6191
6412
  return { path, changed: false };
6192
- writeFileSync11(path, content);
6413
+ writeFileSync12(path, content);
6193
6414
  return { path, changed: true };
6194
6415
  }
6195
6416
  function removeAgentsBlock(root) {
6196
- const path = join11(root, "AGENTS.md");
6417
+ const path = join12(root, "AGENTS.md");
6197
6418
  if (!existsSync12(path))
6198
6419
  return { path, changed: false };
6199
- const existing = readFileSync15(path, "utf8");
6420
+ const existing = readFileSync17(path, "utf8");
6200
6421
  const content = removeBlock(existing);
6201
6422
  if (content === existing)
6202
6423
  return { path, changed: false };
6203
- writeFileSync11(path, content);
6424
+ writeFileSync12(path, content);
6204
6425
  return { path, changed: true };
6205
6426
  }
6206
6427
  async function installCommand(args) {
@@ -6270,6 +6491,10 @@ async function initCommand(args) {
6270
6491
  reportMissingClients(selection.missing);
6271
6492
  const hosts = selection.selected;
6272
6493
  const devkit = installDevKit(root, hosts);
6494
+ const removedMounts = pruneUnselectedEngineMounts(root, hosts);
6495
+ if (removedMounts.length)
6496
+ process.stdout.write(`Removed unused Engine-generated skill mounts: ${removedMounts.join(", ")}.
6497
+ `);
6273
6498
  process.stdout.write(`Bound Engine game ${slug} at ${selectedGame2}.
6274
6499
  `);
6275
6500
  process.stdout.write(`Engine ${release.version} (${release.commit}).
@@ -6300,7 +6525,7 @@ async function useCommand(args) {
6300
6525
  throw new Error(`game ${JSON.stringify(slug)} not found. Available: ${listGames(root).join(", ") || "(none)"}`);
6301
6526
  }
6302
6527
  if (listGames(root).length > 1) {
6303
- writeFileSync11(join11(root, ".forgeax", "active-game.json"), `${JSON.stringify({ version: 1, slug }, null, 2)}
6528
+ writeFileSync12(join12(root, ".forgeax", "active-game.json"), `${JSON.stringify({ version: 1, slug }, null, 2)}
6304
6529
  `, "utf8");
6305
6530
  }
6306
6531
  process.stdout.write(`Active game: ${slug}
@@ -6402,7 +6627,7 @@ async function uninstallCommand(args) {
6402
6627
  const agents = removeAgentsBlock(root);
6403
6628
  process.stdout.write(`${agents.changed ? "REMOVED" : "ABSENT "} routing block: ${agents.path}
6404
6629
  `);
6405
- process.stdout.write(`KEPT your games and project metadata: ${join11(root, ".forgeax")}
6630
+ process.stdout.write(`KEPT your games and project metadata: ${join12(root, ".forgeax")}
6406
6631
  `);
6407
6632
  } else {
6408
6633
  process.stdout.write(`INFO no ForgeaX project bound; only client configuration was touched.
@@ -6720,7 +6945,7 @@ async function asset3dCommand(args) {
6720
6945
  if (selection.selected.length === 0) {
6721
6946
  throw new Error("asset3d_client_missing: run `forgeax-game install --ide <client>` before enabling Asset3D");
6722
6947
  }
6723
- const bridge = asset3dLaunchSpec("npx");
6948
+ const localMode = selection.selected.some((id) => asset3dHostMode(findClient(id), projectRoot) === "local");
6724
6949
  for (const id of selection.selected) {
6725
6950
  const client = findClient(id);
6726
6951
  if (!client)
@@ -6743,7 +6968,7 @@ async function asset3dCommand(args) {
6743
6968
  const bundled = BUNDLED_ASSET3D_PROVIDERS[target];
6744
6969
  if (!bundled)
6745
6970
  throw new Error(`asset3d_provider_target_unreleased: ${target}`);
6746
- const providerCache = resolve14(homedir5(), ".forgeax", "providers", "asset3d-search", bundled.sha256);
6971
+ const providerCache = resolve16(homedir5(), ".forgeax", "providers", "asset3d-search", bundled.sha256);
6747
6972
  let preparedCache = providerCache;
6748
6973
  if (!existsSync12(providerCache)) {
6749
6974
  const providerBundle = packagedAsset3dProvider(bundled.relativePath);
@@ -6769,14 +6994,14 @@ async function asset3dCommand(args) {
6769
6994
  awDepotName: library.library,
6770
6995
  awCredentialFile: credentialFile,
6771
6996
  clients,
6772
- gamePluginLaunch: launchSpec("npx"),
6997
+ gamePluginLaunch: launchSpec(localMode ? "local" : "npx"),
6773
6998
  replaceOwned: true
6774
6999
  });
6775
7000
  for (const id of selection.selected) {
6776
7001
  const client = findClient(id);
6777
7002
  if (!client)
6778
7003
  throw new Error(`asset3d_client_invalid: ${id}`);
6779
- const applied = applyConfig(client, projectRoot, bridge, ASSET3D_SERVER_KEY);
7004
+ const applied = applyConfig(client, projectRoot, asset3dLaunchSpec(asset3dHostMode(client, projectRoot)), ASSET3D_SERVER_KEY);
6780
7005
  process.stdout.write(`${applied.changed ? "UPDATED" : "CURRENT"} ${client.label} Asset3D bridge: ${applied.path}
6781
7006
  `);
6782
7007
  if (applied.changed && client.postInstallNote)
@@ -6825,7 +7050,7 @@ async function asset3dCommand(args) {
6825
7050
  else if (arg === "--aw-depot-name")
6826
7051
  awDepotName = parseAssetLibraryId(value);
6827
7052
  else if (arg === "--aw-credential-file")
6828
- awCredentialFile = resolve14(value);
7053
+ awCredentialFile = resolve16(value);
6829
7054
  else
6830
7055
  clients = canonicalAsset3dClients(value);
6831
7056
  continue;
@@ -7029,7 +7254,7 @@ function parseMcpArgs(args) {
7029
7254
  } else
7030
7255
  throw new Error(`unknown MCP option: ${arg}`);
7031
7256
  }
7032
- return { transport, host, port, root: resolve15(root), requireAuth, allowedOrigins };
7257
+ return { transport, host, port, root: resolve17(root), requireAuth, allowedOrigins };
7033
7258
  }
7034
7259
  async function runMcp(args) {
7035
7260
  const options = parseMcpArgs(args);