@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/README.md +7 -2
- package/assets/asset3d/adapter/SKILL.md +56 -0
- package/assets/asset3d/provider/{asset3d-search-provider-c181c48fbffc933a7ce9a0836f7878ca5e6d77e1-darwin-arm64.tar.gz → asset3d-search-provider-a274238712c1dced99e4aa3148a0d15904ce2a90-darwin-arm64.tar.gz} +0 -0
- package/assets/asset3d/provider/{asset3d-search-provider-c181c48fbffc933a7ce9a0836f7878ca5e6d77e1-linux-x64.tar.gz → asset3d-search-provider-a274238712c1dced99e4aa3148a0d15904ce2a90-linux-x64.tar.gz} +0 -0
- package/assets/asset3d/schemas/result-1.0.0.json +122 -3
- package/assets/skills/forgeax-game/SKILL.md +16 -3
- package/dist/main.js +352 -127
- package/docs/asset3d.md +13 -1
- package/package.json +2 -2
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
|
|
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.
|
|
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
|
|
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
|
|
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 = "
|
|
4280
|
+
var RESULT_SCHEMA_SHA256 = "f97a9aa8b2ce0662c37d314211aae52951cbd0b76b3a7c140dc608be4712c415";
|
|
4274
4281
|
var RECEIPT_SCHEMA_SHA256 = "e0dc9e9fe9872f09af9fca6d0c17d22aca63c2b546c55c0b669c581f7059b9c4";
|
|
4275
|
-
var ASSET3D_PROVIDER_COMMIT = "
|
|
4282
|
+
var ASSET3D_PROVIDER_COMMIT = "a274238712c1dced99e4aa3148a0d15904ce2a90";
|
|
4276
4283
|
var BUNDLED_ASSET3D_PROVIDERS = Object.freeze({
|
|
4277
4284
|
"darwin-arm64": {
|
|
4278
|
-
sha256: "
|
|
4279
|
-
relativePath: "asset3d/provider/asset3d-search-provider-
|
|
4285
|
+
sha256: "4f2d971c037a32eaed20e51e13a88bff91d2ea8f4d37894526c089376c3bacba",
|
|
4286
|
+
relativePath: "asset3d/provider/asset3d-search-provider-a274238712c1dced99e4aa3148a0d15904ce2a90-darwin-arm64.tar.gz"
|
|
4280
4287
|
},
|
|
4281
4288
|
"linux-x64": {
|
|
4282
|
-
sha256: "
|
|
4283
|
-
relativePath: "asset3d/provider/asset3d-search-provider-
|
|
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
|
|
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
|
|
4968
|
+
lstatSync as lstatSync9,
|
|
4961
4969
|
mkdirSync as mkdirSync10,
|
|
4962
4970
|
openSync as openSync4,
|
|
4963
|
-
readFileSync as
|
|
4971
|
+
readFileSync as readFileSync14,
|
|
4964
4972
|
readdirSync as readdirSync7,
|
|
4965
|
-
realpathSync as
|
|
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
|
|
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
|
-
|
|
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 ===
|
|
5117
|
-
if (primary.length !== 1 || row
|
|
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 =
|
|
5148
|
-
return rel === "" || !
|
|
5255
|
+
const rel = relative10(root, candidate);
|
|
5256
|
+
return rel === "" || !isAbsolute5(rel) && rel !== ".." && !rel.startsWith(`..${sep7}`);
|
|
5149
5257
|
}
|
|
5150
5258
|
function forgeaxRoot(projectRoot) {
|
|
5151
|
-
return
|
|
5259
|
+
return resolve12(projectRoot, ".forgeax");
|
|
5152
5260
|
}
|
|
5153
5261
|
function journalPath(projectRoot, execution) {
|
|
5154
|
-
return
|
|
5262
|
+
return resolve12(forgeaxRoot(projectRoot), "asset3d-transactions", `${execution}.json`);
|
|
5155
5263
|
}
|
|
5156
5264
|
function providerResultPath(projectRoot, execution) {
|
|
5157
|
-
return
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
5199
|
-
const allowedQuarantineRoot =
|
|
5200
|
-
ensurePrivateDir(
|
|
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 =
|
|
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 =
|
|
5229
|
-
if (journal.allowedQuarantineRoot !== expectedRoot || !existsSync9(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 =
|
|
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(
|
|
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 =
|
|
5357
|
+
const source = resolve12(root, entry.path);
|
|
5250
5358
|
if (!confined4(root, source))
|
|
5251
5359
|
throw new Error("asset_manifest_escape");
|
|
5252
|
-
const canonicalRoot =
|
|
5253
|
-
const canonicalSource =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
5381
|
+
const root = resolve12(forgeaxRoot(projectRoot), "asset3d-locks");
|
|
5274
5382
|
ensurePrivateDir(root);
|
|
5275
|
-
const path =
|
|
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(
|
|
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 =
|
|
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 =
|
|
5339
|
-
const info =
|
|
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(
|
|
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 =
|
|
5464
|
+
const absolute = resolve12(projectRoot, value);
|
|
5357
5465
|
if (!confined4(projectRoot, absolute))
|
|
5358
5466
|
throw new Error("engine_readback_path_escape");
|
|
5359
|
-
return
|
|
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:
|
|
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(
|
|
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 =
|
|
5509
|
+
const path = resolve12(destination, entry.path);
|
|
5388
5510
|
try {
|
|
5389
|
-
return confined4(destination, path) && statSync6(path).isFile() && statSync6(path).size === entry.bytes && sha2562(
|
|
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 =
|
|
5399
|
-
const info =
|
|
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(
|
|
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 =
|
|
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 =
|
|
5422
|
-
const stageRoot =
|
|
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(
|
|
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 =
|
|
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 =
|
|
5563
|
+
const target = resolve12(stageRoot, entry.path);
|
|
5442
5564
|
if (!confined4(stageRoot, target))
|
|
5443
5565
|
throw new Error("asset_manifest_escape");
|
|
5444
|
-
mkdirSync10(
|
|
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
|
-
|
|
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 =
|
|
5475
|
-
const assetRoot =
|
|
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:
|
|
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 =
|
|
5499
|
-
ensurePrivateDir(
|
|
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
|
|
5507
|
-
|
|
5508
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
5610
|
-
const journal = JSON.parse(
|
|
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 =
|
|
5614
|
-
const backupsRoot =
|
|
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(
|
|
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
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
5842
|
+
return resolve14(configured || resolve14(homedir4(), ".forgeax", "credentials", "asset3d-aw.json"));
|
|
5719
5843
|
}
|
|
5720
5844
|
function readAwCredential(pathInput) {
|
|
5721
|
-
const path =
|
|
5722
|
-
if (!
|
|
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 =
|
|
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(
|
|
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 (!
|
|
5919
|
+
if (!isAbsolute6(pathInput))
|
|
5796
5920
|
throw new Error("asset3d_credential_path_invalid: absolute path required");
|
|
5797
|
-
const path =
|
|
5921
|
+
const path = resolve14(pathInput);
|
|
5798
5922
|
const key = validateKey(keyInput);
|
|
5799
5923
|
const parent = dirname11(path);
|
|
5800
5924
|
ensurePrivateDir(parent);
|
|
5801
|
-
const parentMetadata =
|
|
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 ?
|
|
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)
|
|
6088
|
+
if (!Array.isArray(args.queries)) {
|
|
5965
6089
|
throw new Error("asset3d_queries_invalid");
|
|
5966
6090
|
}
|
|
5967
|
-
const
|
|
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((
|
|
6218
|
+
return await new Promise((resolve15, reject) => {
|
|
6084
6219
|
child.once("error", reject);
|
|
6085
|
-
child.once("close", (code, signal) =>
|
|
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
|
|
6091
|
-
|
|
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 =
|
|
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 = { ...
|
|
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(
|
|
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 =
|
|
6188
|
-
const existing = existsSync12(path) ?
|
|
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
|
-
|
|
6413
|
+
writeFileSync12(path, content);
|
|
6193
6414
|
return { path, changed: true };
|
|
6194
6415
|
}
|
|
6195
6416
|
function removeAgentsBlock(root) {
|
|
6196
|
-
const path =
|
|
6417
|
+
const path = join12(root, "AGENTS.md");
|
|
6197
6418
|
if (!existsSync12(path))
|
|
6198
6419
|
return { path, changed: false };
|
|
6199
|
-
const existing =
|
|
6420
|
+
const existing = readFileSync17(path, "utf8");
|
|
6200
6421
|
const content = removeBlock(existing);
|
|
6201
6422
|
if (content === existing)
|
|
6202
6423
|
return { path, changed: false };
|
|
6203
|
-
|
|
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
|
-
|
|
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: ${
|
|
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
|
|
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 =
|
|
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,
|
|
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 =
|
|
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:
|
|
7257
|
+
return { transport, host, port, root: resolve17(root), requireAuth, allowedOrigins };
|
|
7033
7258
|
}
|
|
7034
7259
|
async function runMcp(args) {
|
|
7035
7260
|
const options = parseMcpArgs(args);
|