@forgeax/game 0.3.5 → 0.3.7
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 +423 -130
- 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
|
`);
|
|
@@ -2938,6 +2945,74 @@ function gameFileTools() {
|
|
|
2938
2945
|
}
|
|
2939
2946
|
];
|
|
2940
2947
|
}
|
|
2948
|
+
// package.json
|
|
2949
|
+
var package_default = {
|
|
2950
|
+
packageManager: "bun@1.4.0",
|
|
2951
|
+
name: "@forgeax/game",
|
|
2952
|
+
version: "0.3.7",
|
|
2953
|
+
private: false,
|
|
2954
|
+
type: "module",
|
|
2955
|
+
description: "@forgeax/game — an MCP/CLI connector for exact released ForgeaX Engine games and Engine-owned Preview.",
|
|
2956
|
+
main: "./dist/main.js",
|
|
2957
|
+
bin: {
|
|
2958
|
+
"forgeax-game": "dist/main.js",
|
|
2959
|
+
game: "dist/main.js"
|
|
2960
|
+
},
|
|
2961
|
+
engines: {
|
|
2962
|
+
node: ">=22.13.0"
|
|
2963
|
+
},
|
|
2964
|
+
publishConfig: {
|
|
2965
|
+
access: "public",
|
|
2966
|
+
registry: "https://registry.npmjs.org/"
|
|
2967
|
+
},
|
|
2968
|
+
files: [
|
|
2969
|
+
"dist",
|
|
2970
|
+
"assets",
|
|
2971
|
+
"docs/asset3d.md",
|
|
2972
|
+
"docs/release-0.3.0.md",
|
|
2973
|
+
"docs/release-0.3.1.md",
|
|
2974
|
+
"docs/release-0.3.2.md",
|
|
2975
|
+
"docs/release-0.3.3.md",
|
|
2976
|
+
"docs/release-0.3.4.md",
|
|
2977
|
+
"docs/release-0.3.5.md",
|
|
2978
|
+
"README.md"
|
|
2979
|
+
],
|
|
2980
|
+
scripts: {
|
|
2981
|
+
build: "bun build.mjs",
|
|
2982
|
+
"release:check": "bun scripts/check-package-artifact.ts",
|
|
2983
|
+
acceptance: "bun scripts/accept-packed-consumer.ts",
|
|
2984
|
+
"acceptance:asset3d": "bun scripts/accept-asset3d-packed-consumer.ts",
|
|
2985
|
+
"verify:asset3d-linux": "bun scripts/verify-linux-asset3d-provider.ts",
|
|
2986
|
+
"release:publish": "bun scripts/publish-package.ts",
|
|
2987
|
+
prepack: "bun build.mjs",
|
|
2988
|
+
typecheck: "tsc --noEmit",
|
|
2989
|
+
test: "bun test test/*.test.ts",
|
|
2990
|
+
start: "bun src/main.ts",
|
|
2991
|
+
"mcp:inspect": "npx @modelcontextprotocol/inspector node dist/main.js"
|
|
2992
|
+
},
|
|
2993
|
+
keywords: [
|
|
2994
|
+
"forgeax",
|
|
2995
|
+
"mcp",
|
|
2996
|
+
"model-context-protocol",
|
|
2997
|
+
"game-development",
|
|
2998
|
+
"codex",
|
|
2999
|
+
"claude-code"
|
|
3000
|
+
],
|
|
3001
|
+
license: "MIT",
|
|
3002
|
+
dependencies: {
|
|
3003
|
+
"@forgeax/engine-sdk": "0.1.26",
|
|
3004
|
+
pnpm: "11.7.0"
|
|
3005
|
+
},
|
|
3006
|
+
devDependencies: {
|
|
3007
|
+
"@types/bun": "^1.2.0",
|
|
3008
|
+
typescript: "^5.9.2"
|
|
3009
|
+
},
|
|
3010
|
+
directories: {
|
|
3011
|
+
doc: "docs",
|
|
3012
|
+
test: "test"
|
|
3013
|
+
},
|
|
3014
|
+
author: ""
|
|
3015
|
+
};
|
|
2941
3016
|
|
|
2942
3017
|
// src/install/release-manifest.ts
|
|
2943
3018
|
var RELEASE_IDENTITY_SCHEMA = "forgeax.game.release-identity/1";
|
|
@@ -2946,7 +3021,7 @@ var RELEASE_IDENTITY_MIME = "application/vnd.forgeax.game-release-identity+json"
|
|
|
2946
3021
|
var RELEASE_IDENTITY = Object.freeze({
|
|
2947
3022
|
schema: RELEASE_IDENTITY_SCHEMA,
|
|
2948
3023
|
gamePackage: "@forgeax/game",
|
|
2949
|
-
gameVersion:
|
|
3024
|
+
gameVersion: package_default.version,
|
|
2950
3025
|
gameBin: "forgeax-game",
|
|
2951
3026
|
engineSdkPackage: ENGINE_SDK_PACKAGE,
|
|
2952
3027
|
engineSdkVersion: ENGINE_VERSION,
|
|
@@ -3226,9 +3301,9 @@ async function startHttpMcpServer(spec, options) {
|
|
|
3226
3301
|
}
|
|
3227
3302
|
|
|
3228
3303
|
// src/cli/dispatch.ts
|
|
3229
|
-
import { existsSync as existsSync12, readFileSync as
|
|
3304
|
+
import { existsSync as existsSync12, readFileSync as readFileSync17, writeFileSync as writeFileSync12 } from "node:fs";
|
|
3230
3305
|
import { arch as arch2, homedir as homedir5, platform as platform2 } from "node:os";
|
|
3231
|
-
import { join as
|
|
3306
|
+
import { join as join12, resolve as resolve16 } from "node:path";
|
|
3232
3307
|
|
|
3233
3308
|
// src/install/clients.ts
|
|
3234
3309
|
import { homedir as homedir2 } from "node:os";
|
|
@@ -3356,7 +3431,7 @@ function launchSpec(mode) {
|
|
|
3356
3431
|
}
|
|
3357
3432
|
return {
|
|
3358
3433
|
command: "npx",
|
|
3359
|
-
args: ["-y", "-p",
|
|
3434
|
+
args: ["-y", "-p", `@forgeax/game@${RELEASE_IDENTITY.gameVersion}`, "forgeax-game", "mcp"]
|
|
3360
3435
|
};
|
|
3361
3436
|
}
|
|
3362
3437
|
function asset3dLaunchSpec(mode) {
|
|
@@ -4270,17 +4345,17 @@ var PROVIDER_BUNDLE_SCHEMA = "forgeax.asset3d-provider-bundle/1.0.0";
|
|
|
4270
4345
|
var PROVIDER_RESULT_SCHEMA = "forgeax.asset3d-search-result/1.0.0";
|
|
4271
4346
|
var PROVIDER_RECEIPT_SCHEMA = "forgeax.asset3d-search-receipt/1.0.0";
|
|
4272
4347
|
var K0_PACKAGE_SHA256 = "4937fe00e2b919319c70c82db53ebe359b275ed662c026153ecb97f7601262b4";
|
|
4273
|
-
var RESULT_SCHEMA_SHA256 = "
|
|
4348
|
+
var RESULT_SCHEMA_SHA256 = "f97a9aa8b2ce0662c37d314211aae52951cbd0b76b3a7c140dc608be4712c415";
|
|
4274
4349
|
var RECEIPT_SCHEMA_SHA256 = "e0dc9e9fe9872f09af9fca6d0c17d22aca63c2b546c55c0b669c581f7059b9c4";
|
|
4275
|
-
var ASSET3D_PROVIDER_COMMIT = "
|
|
4350
|
+
var ASSET3D_PROVIDER_COMMIT = "a274238712c1dced99e4aa3148a0d15904ce2a90";
|
|
4276
4351
|
var BUNDLED_ASSET3D_PROVIDERS = Object.freeze({
|
|
4277
4352
|
"darwin-arm64": {
|
|
4278
|
-
sha256: "
|
|
4279
|
-
relativePath: "asset3d/provider/asset3d-search-provider-
|
|
4353
|
+
sha256: "4f2d971c037a32eaed20e51e13a88bff91d2ea8f4d37894526c089376c3bacba",
|
|
4354
|
+
relativePath: "asset3d/provider/asset3d-search-provider-a274238712c1dced99e4aa3148a0d15904ce2a90-darwin-arm64.tar.gz"
|
|
4280
4355
|
},
|
|
4281
4356
|
"linux-x64": {
|
|
4282
|
-
sha256: "
|
|
4283
|
-
relativePath: "asset3d/provider/asset3d-search-provider-
|
|
4357
|
+
sha256: "af97e7e267b558960156175c840c3c2d2cd7aabf68f1f72c7d64b6d50742a79e",
|
|
4358
|
+
relativePath: "asset3d/provider/asset3d-search-provider-a274238712c1dced99e4aa3148a0d15904ce2a90-linux-x64.tar.gz"
|
|
4284
4359
|
}
|
|
4285
4360
|
});
|
|
4286
4361
|
var INSTALL_SCHEMA = "forgeax.asset3d-install/1.0.0";
|
|
@@ -4675,7 +4750,8 @@ function installSkills(projectRoot, tgz, clients, previous) {
|
|
|
4675
4750
|
for (const client of clients) {
|
|
4676
4751
|
const mount = SKILL_MOUNTS[client];
|
|
4677
4752
|
const destination = resolve10(projectRoot, mount, SKILL_ID);
|
|
4678
|
-
const
|
|
4753
|
+
const adapter = readFileSync12(resolve10(dirname10(tgz), "adapter", "SKILL.md"));
|
|
4754
|
+
const projected = [...K0_FILES.map((file) => [`legacy/${file}`, extractK0(tgz, file)]), ["SKILL.md", adapter]];
|
|
4679
4755
|
if (existsSync8(destination)) {
|
|
4680
4756
|
const priorOwned = new Set(previous?.skills.filter((entry) => confined3(destination, entry.path)).map((entry) => entry.path) ?? []);
|
|
4681
4757
|
const unexpected = filesUnder2(destination).filter((path) => !priorOwned.has(path));
|
|
@@ -4957,19 +5033,19 @@ import {
|
|
|
4957
5033
|
cpSync as cpSync2,
|
|
4958
5034
|
existsSync as existsSync9,
|
|
4959
5035
|
fstatSync,
|
|
4960
|
-
lstatSync as
|
|
5036
|
+
lstatSync as lstatSync9,
|
|
4961
5037
|
mkdirSync as mkdirSync10,
|
|
4962
5038
|
openSync as openSync4,
|
|
4963
|
-
readFileSync as
|
|
5039
|
+
readFileSync as readFileSync14,
|
|
4964
5040
|
readdirSync as readdirSync7,
|
|
4965
|
-
realpathSync as
|
|
5041
|
+
realpathSync as realpathSync9,
|
|
4966
5042
|
renameSync as renameSync7,
|
|
4967
5043
|
rmSync as rmSync6,
|
|
4968
5044
|
statSync as statSync6,
|
|
4969
5045
|
unlinkSync as unlinkSync3,
|
|
4970
5046
|
writeFileSync as writeFileSync10
|
|
4971
5047
|
} from "node:fs";
|
|
4972
|
-
import { isAbsolute as
|
|
5048
|
+
import { isAbsolute as isAbsolute5, relative as relative10, resolve as resolve12, sep as sep7 } from "node:path";
|
|
4973
5049
|
|
|
4974
5050
|
// src/asset3d/schema.ts
|
|
4975
5051
|
var ID = /^[A-Za-z0-9._-]{1,128}$/;
|
|
@@ -4988,7 +5064,7 @@ var ERROR_CODES = new Set([
|
|
|
4988
5064
|
"batch_timeout",
|
|
4989
5065
|
"internal_error"
|
|
4990
5066
|
]);
|
|
4991
|
-
var ROLES = new Set(["primary-model", "animation", "auxiliary-model", "texture", "metadata"]);
|
|
5067
|
+
var ROLES = new Set(["primary-pack", "auxiliary-pack", "primary-model", "animation", "auxiliary-model", "texture", "metadata"]);
|
|
4992
5068
|
function exactKeys(record, allowed, field) {
|
|
4993
5069
|
const extras = Object.keys(record).filter((key) => !allowed.includes(key));
|
|
4994
5070
|
if (extras.length)
|
|
@@ -5078,9 +5154,12 @@ function parseProviderResult(input, expectedCommit, expectedOriginSetDigest) {
|
|
|
5078
5154
|
}
|
|
5079
5155
|
if (row.status !== "ok")
|
|
5080
5156
|
throw new Error("provider_result_invalid: status");
|
|
5081
|
-
|
|
5157
|
+
const isPack = row.deliveredFormat === "pack";
|
|
5158
|
+
const primaryKey = isPack ? "primaryPack" : "primaryModel";
|
|
5159
|
+
const primaryRole = isPack ? "primary-pack" : "primary-model";
|
|
5160
|
+
exactKeys(row, ["status", "queryIndex", "query", "provider", "providerAssetId", "assetName", "deliveredFormat", "sha256", "bytes", primaryKey, "manifest", "originSetDigest", "downloaded_to"], "success");
|
|
5082
5161
|
okCount++;
|
|
5083
|
-
if (row.provider !== "ea-3d" || row.deliveredFormat !== "glb" || typeof row.providerAssetId !== "string" || !ID.test(row.providerAssetId) || row.providerAssetId === "." || row.providerAssetId === "..") {
|
|
5162
|
+
if (row.provider !== "ea-3d" || row.deliveredFormat !== "glb" && !isPack || typeof row.providerAssetId !== "string" || !ID.test(row.providerAssetId) || row.providerAssetId === "." || row.providerAssetId === "..") {
|
|
5084
5163
|
throw new Error("provider_result_invalid: success identity");
|
|
5085
5164
|
}
|
|
5086
5165
|
const assetName = text(row.assetName, 1, 128, "assetName");
|
|
@@ -5108,13 +5187,18 @@ function parseProviderResult(input, expectedCommit, expectedOriginSetDigest) {
|
|
|
5108
5187
|
if ((role === "primary-model" || role === "animation" || role === "auxiliary-model") && !path.toLowerCase().endsWith(".glb")) {
|
|
5109
5188
|
throw new Error("provider_result_invalid: model entry must be GLB");
|
|
5110
5189
|
}
|
|
5190
|
+
if ((role === "primary-pack" || role === "auxiliary-pack") && (!isPack || !/\.pack\.(json|ts)$/i.test(path))) {
|
|
5191
|
+
throw new Error("provider_result_invalid: pack entry must be authored Pack");
|
|
5192
|
+
}
|
|
5193
|
+
if (isPack && role === "primary-model")
|
|
5194
|
+
throw new Error("provider_result_invalid: multiple primary formats");
|
|
5111
5195
|
return { path, role, bytes: entryBytes, sha256: item.sha256 };
|
|
5112
5196
|
});
|
|
5113
5197
|
const sorted = [...manifest].sort((left, right) => Buffer.from(left.path).compare(Buffer.from(right.path)));
|
|
5114
5198
|
if (manifest.some((entry, index) => entry.path !== sorted[index].path))
|
|
5115
5199
|
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
|
|
5200
|
+
const primary = manifest.filter((entry) => entry.role === primaryRole);
|
|
5201
|
+
if (primary.length !== 1 || row[primaryKey] !== primary[0].path)
|
|
5118
5202
|
throw new Error("provider_result_invalid: primary model");
|
|
5119
5203
|
if (manifest.reduce((sum, entry) => sum + entry.bytes, 0) !== itemBytes || aggregate(manifest) !== row.sha256) {
|
|
5120
5204
|
throw new Error("provider_result_invalid: byte or aggregate digest mismatch");
|
|
@@ -5128,10 +5212,9 @@ function parseProviderResult(input, expectedCommit, expectedOriginSetDigest) {
|
|
|
5128
5212
|
provider: "ea-3d",
|
|
5129
5213
|
providerAssetId: row.providerAssetId,
|
|
5130
5214
|
assetName,
|
|
5131
|
-
deliveredFormat: "glb",
|
|
5132
5215
|
sha256: row.sha256,
|
|
5133
5216
|
bytes: itemBytes,
|
|
5134
|
-
primaryModel: row.primaryModel,
|
|
5217
|
+
...isPack ? { deliveredFormat: "pack", primaryPack: row.primaryPack } : { deliveredFormat: "glb", primaryModel: row.primaryModel },
|
|
5135
5218
|
manifest,
|
|
5136
5219
|
originSetDigest: expectedOriginSetDigest,
|
|
5137
5220
|
...row.downloaded_to === undefined ? {} : { downloaded_to: row.downloaded_to }
|
|
@@ -5142,24 +5225,117 @@ function parseProviderResult(input, expectedCommit, expectedOriginSetDigest) {
|
|
|
5142
5225
|
return { schemaVersion: PROVIDER_RESULT_SCHEMA, total, succeeded, failed, results, ...receipt ? { receipt } : {} };
|
|
5143
5226
|
}
|
|
5144
5227
|
|
|
5228
|
+
// src/asset3d/pack-readback.ts
|
|
5229
|
+
import { lstatSync as lstatSync8, readFileSync as readFileSync13, realpathSync as realpathSync8 } from "node:fs";
|
|
5230
|
+
import { isAbsolute as isAbsolute4, relative as relative9, resolve as resolve11, sep as sep6 } from "node:path";
|
|
5231
|
+
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
5232
|
+
function object(value) {
|
|
5233
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
5234
|
+
throw new Error("engine_pack_readback_invalid");
|
|
5235
|
+
return value;
|
|
5236
|
+
}
|
|
5237
|
+
function readPackBuildCatalog(projectRoot, sourcePaths, buildValue) {
|
|
5238
|
+
const build = object(buildValue);
|
|
5239
|
+
const runtime = object(build.runtime);
|
|
5240
|
+
if (build.schemaVersion !== "1.0.0" || !Array.isArray(build.artifacts) || build.artifacts.length > 1e4) {
|
|
5241
|
+
throw new Error("engine_pack_build_manifest_invalid");
|
|
5242
|
+
}
|
|
5243
|
+
const dist = resolve11(projectRoot, "dist");
|
|
5244
|
+
const canonicalDist = realpathSync8(dist);
|
|
5245
|
+
if (canonicalDist !== resolve11(realpathSync8(projectRoot), "dist") || lstatSync8(dist).isSymbolicLink()) {
|
|
5246
|
+
throw new Error("engine_pack_build_path_escape");
|
|
5247
|
+
}
|
|
5248
|
+
const artifacts = new Map;
|
|
5249
|
+
let total = 0;
|
|
5250
|
+
for (const raw of build.artifacts) {
|
|
5251
|
+
const entry = object(raw);
|
|
5252
|
+
const path = safeRelativePath(entry.path);
|
|
5253
|
+
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)) {
|
|
5254
|
+
throw new Error("engine_pack_build_manifest_invalid");
|
|
5255
|
+
}
|
|
5256
|
+
total += entry.bytes;
|
|
5257
|
+
if (total > 1024 * 1024 * 1024)
|
|
5258
|
+
throw new Error("engine_pack_build_too_large");
|
|
5259
|
+
const file = resolve11(dist, path);
|
|
5260
|
+
const rel = relative9(canonicalDist, realpathSync8(file));
|
|
5261
|
+
if (isAbsolute4(rel) || rel === ".." || rel.startsWith(`..${sep6}`))
|
|
5262
|
+
throw new Error("engine_pack_build_path_escape");
|
|
5263
|
+
const stat = lstatSync8(file);
|
|
5264
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size !== entry.bytes)
|
|
5265
|
+
throw new Error("engine_pack_build_file_invalid");
|
|
5266
|
+
const bytes = readFileSync13(file);
|
|
5267
|
+
if (sha2562(bytes) !== entry.sha256)
|
|
5268
|
+
throw new Error("engine_pack_build_digest_mismatch");
|
|
5269
|
+
artifacts.set(path, path.endsWith(".json") ? bytes : Buffer.alloc(0));
|
|
5270
|
+
}
|
|
5271
|
+
const indexPath = safeRelativePath(runtime.packIndexUrl);
|
|
5272
|
+
const indexBytes = artifacts.get(indexPath);
|
|
5273
|
+
if (!indexBytes || indexBytes.length > 16 * 1024 * 1024)
|
|
5274
|
+
throw new Error("engine_pack_catalog_missing");
|
|
5275
|
+
const index = JSON.parse(indexBytes.toString("utf8"));
|
|
5276
|
+
if (!Array.isArray(index))
|
|
5277
|
+
throw new Error("engine_pack_catalog_invalid");
|
|
5278
|
+
const sources = new Set(sourcePaths);
|
|
5279
|
+
const rows = [];
|
|
5280
|
+
const seen = new Set;
|
|
5281
|
+
const inspect = [];
|
|
5282
|
+
for (const raw of index) {
|
|
5283
|
+
const row = object(raw);
|
|
5284
|
+
if (typeof row.sourcePath !== "string" || !sources.has(row.sourcePath))
|
|
5285
|
+
continue;
|
|
5286
|
+
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("//")) {
|
|
5287
|
+
throw new Error("engine_pack_catalog_invalid");
|
|
5288
|
+
}
|
|
5289
|
+
seen.add(row.guid);
|
|
5290
|
+
const packagePath = safeRelativePath(row.packageUrl.slice(1));
|
|
5291
|
+
const bytes = artifacts.get(packagePath);
|
|
5292
|
+
if (!bytes || bytes.length > 16 * 1024 * 1024)
|
|
5293
|
+
throw new Error("engine_pack_product_missing");
|
|
5294
|
+
const pack = object(JSON.parse(bytes.toString("utf8")));
|
|
5295
|
+
if (!Array.isArray(pack.assets))
|
|
5296
|
+
throw new Error("engine_pack_product_invalid");
|
|
5297
|
+
const matches = pack.assets.map(object).filter((asset) => asset.guid === row.guid && asset.kind === row.kind);
|
|
5298
|
+
if (matches.length !== 1)
|
|
5299
|
+
throw new Error("engine_pack_product_guid_missing");
|
|
5300
|
+
rows.push({
|
|
5301
|
+
guid: row.guid,
|
|
5302
|
+
kind: row.kind,
|
|
5303
|
+
source: row.sourcePath,
|
|
5304
|
+
sourcePath: row.sourcePath,
|
|
5305
|
+
...typeof row.name === "string" ? { name: row.name } : {},
|
|
5306
|
+
packageUrl: row.packageUrl
|
|
5307
|
+
});
|
|
5308
|
+
inspect.push({ guid: row.guid, kind: row.kind, packagePath, packageSha256: sha2562(bytes), verified: true });
|
|
5309
|
+
}
|
|
5310
|
+
if (!rows.some((row) => row.kind === "scene" || row.kind === "mesh"))
|
|
5311
|
+
throw new Error("engine_pack_readback_missing_renderable");
|
|
5312
|
+
return {
|
|
5313
|
+
authority: "engine-build-catalog",
|
|
5314
|
+
verify: { artifacts: artifacts.size, bytes: total, catalogSha256: sha2562(indexBytes) },
|
|
5315
|
+
list: rows,
|
|
5316
|
+
rows,
|
|
5317
|
+
inspect
|
|
5318
|
+
};
|
|
5319
|
+
}
|
|
5320
|
+
|
|
5145
5321
|
// src/asset3d/transaction.ts
|
|
5146
5322
|
function confined4(root, candidate) {
|
|
5147
|
-
const rel =
|
|
5148
|
-
return rel === "" || !
|
|
5323
|
+
const rel = relative10(root, candidate);
|
|
5324
|
+
return rel === "" || !isAbsolute5(rel) && rel !== ".." && !rel.startsWith(`..${sep7}`);
|
|
5149
5325
|
}
|
|
5150
5326
|
function forgeaxRoot(projectRoot) {
|
|
5151
|
-
return
|
|
5327
|
+
return resolve12(projectRoot, ".forgeax");
|
|
5152
5328
|
}
|
|
5153
5329
|
function journalPath(projectRoot, execution) {
|
|
5154
|
-
return
|
|
5330
|
+
return resolve12(forgeaxRoot(projectRoot), "asset3d-transactions", `${execution}.json`);
|
|
5155
5331
|
}
|
|
5156
5332
|
function providerResultPath(projectRoot, execution) {
|
|
5157
|
-
return
|
|
5333
|
+
return resolve12(forgeaxRoot(projectRoot), "asset3d-results", `${execution}.json`);
|
|
5158
5334
|
}
|
|
5159
5335
|
function readInstall(projectRoot) {
|
|
5160
5336
|
let manifest;
|
|
5161
5337
|
try {
|
|
5162
|
-
manifest = JSON.parse(
|
|
5338
|
+
manifest = JSON.parse(readFileSync14(resolve12(forgeaxRoot(projectRoot), "asset3d-install.json"), "utf8"));
|
|
5163
5339
|
} catch {
|
|
5164
5340
|
throw new Error("asset3d_not_installed");
|
|
5165
5341
|
}
|
|
@@ -5172,7 +5348,7 @@ function readJournal(projectRoot, execution) {
|
|
|
5172
5348
|
throw new Error("asset3d_execution_invalid");
|
|
5173
5349
|
let journal;
|
|
5174
5350
|
try {
|
|
5175
|
-
journal = JSON.parse(
|
|
5351
|
+
journal = JSON.parse(readFileSync14(journalPath(projectRoot, execution), "utf8"));
|
|
5176
5352
|
} catch {
|
|
5177
5353
|
throw new Error("asset3d_execution_not_found");
|
|
5178
5354
|
}
|
|
@@ -5187,7 +5363,7 @@ function updateJournal(projectRoot, journal, patch) {
|
|
|
5187
5363
|
return next;
|
|
5188
5364
|
}
|
|
5189
5365
|
function beginAsset3d(projectRootInput, queries, options = {}) {
|
|
5190
|
-
const projectRoot =
|
|
5366
|
+
const projectRoot = realpathSync9(projectRootInput);
|
|
5191
5367
|
if (queries.length < 1 || queries.length > 16 || queries.some((query) => [...query].length < 1 || [...query].length > 200)) {
|
|
5192
5368
|
throw new Error("asset3d_queries_invalid: expected 1..16 queries of 1..200 characters");
|
|
5193
5369
|
}
|
|
@@ -5195,9 +5371,9 @@ function beginAsset3d(projectRootInput, queries, options = {}) {
|
|
|
5195
5371
|
const release = resolveEngineRelease(projectRoot, options.carrierPluginRoot === undefined ? {} : { pluginRoot: options.carrierPluginRoot });
|
|
5196
5372
|
const execution = randomUUID4();
|
|
5197
5373
|
const relativeOutput = `workspace/asset3d/${execution}`;
|
|
5198
|
-
const quarantine =
|
|
5199
|
-
const allowedQuarantineRoot =
|
|
5200
|
-
ensurePrivateDir(
|
|
5374
|
+
const quarantine = resolve12(projectRoot, ".forgeax", "asset3d-quarantine");
|
|
5375
|
+
const allowedQuarantineRoot = resolve12(quarantine, relativeOutput);
|
|
5376
|
+
ensurePrivateDir(resolve12(projectRoot, ".forgeax", "asset3d-transactions"));
|
|
5201
5377
|
ensurePrivateDir(allowedQuarantineRoot);
|
|
5202
5378
|
const journal = {
|
|
5203
5379
|
schemaVersion: TRANSACTION_SCHEMA,
|
|
@@ -5216,7 +5392,7 @@ function beginAsset3d(projectRootInput, queries, options = {}) {
|
|
|
5216
5392
|
return { execution, output_dir: relativeOutput };
|
|
5217
5393
|
}
|
|
5218
5394
|
function asset3dSearchOutputDir(projectRootInput, execution, queries) {
|
|
5219
|
-
const projectRoot =
|
|
5395
|
+
const projectRoot = realpathSync9(projectRootInput);
|
|
5220
5396
|
const journal = readJournal(projectRoot, execution);
|
|
5221
5397
|
if (journal.state !== "begun") {
|
|
5222
5398
|
throw new Error(`asset3d_execution_state_invalid: ${journal.state}`);
|
|
@@ -5225,14 +5401,14 @@ function asset3dSearchOutputDir(projectRootInput, execution, queries) {
|
|
|
5225
5401
|
throw new Error("asset3d_search_query_identity_mismatch");
|
|
5226
5402
|
}
|
|
5227
5403
|
const outputDir = `workspace/asset3d/${execution}`;
|
|
5228
|
-
const expectedRoot =
|
|
5229
|
-
if (journal.allowedQuarantineRoot !== expectedRoot || !existsSync9(expectedRoot) ||
|
|
5404
|
+
const expectedRoot = resolve12(forgeaxRoot(projectRoot), "asset3d-quarantine", outputDir);
|
|
5405
|
+
if (journal.allowedQuarantineRoot !== expectedRoot || !existsSync9(expectedRoot) || realpathSync9(expectedRoot) !== expectedRoot) {
|
|
5230
5406
|
throw new Error("asset3d_search_output_identity_mismatch");
|
|
5231
5407
|
}
|
|
5232
5408
|
return outputDir;
|
|
5233
5409
|
}
|
|
5234
5410
|
function recordAsset3dProviderResult(projectRootInput, execution, providerResult) {
|
|
5235
|
-
const projectRoot =
|
|
5411
|
+
const projectRoot = realpathSync9(projectRootInput);
|
|
5236
5412
|
const journal = readJournal(projectRoot, execution);
|
|
5237
5413
|
if (journal.state !== "begun") {
|
|
5238
5414
|
throw new Error(`asset3d_execution_state_invalid: ${journal.state}`);
|
|
@@ -5241,19 +5417,19 @@ function recordAsset3dProviderResult(projectRootInput, execution, providerResult
|
|
|
5241
5417
|
if (bytes < 1 || bytes > MAX_JSON_BYTES) {
|
|
5242
5418
|
throw new Error("provider_result_too_large: expected 1 byte..1 MiB");
|
|
5243
5419
|
}
|
|
5244
|
-
ensurePrivateDir(
|
|
5420
|
+
ensurePrivateDir(resolve12(forgeaxRoot(projectRoot), "asset3d-results"));
|
|
5245
5421
|
atomicWrite(providerResultPath(projectRoot, execution), providerResult, 384);
|
|
5246
5422
|
updateJournal(projectRoot, journal, { state: "provider_complete" });
|
|
5247
5423
|
}
|
|
5248
5424
|
function fileBytesChecked(root, entry) {
|
|
5249
|
-
const source =
|
|
5425
|
+
const source = resolve12(root, entry.path);
|
|
5250
5426
|
if (!confined4(root, source))
|
|
5251
5427
|
throw new Error("asset_manifest_escape");
|
|
5252
|
-
const canonicalRoot =
|
|
5253
|
-
const canonicalSource =
|
|
5428
|
+
const canonicalRoot = realpathSync9(root);
|
|
5429
|
+
const canonicalSource = realpathSync9(source);
|
|
5254
5430
|
if (!confined4(canonicalRoot, canonicalSource))
|
|
5255
5431
|
throw new Error("asset_manifest_escape");
|
|
5256
|
-
const before =
|
|
5432
|
+
const before = lstatSync9(source);
|
|
5257
5433
|
if (!before.isFile() || before.isSymbolicLink())
|
|
5258
5434
|
throw new Error("asset_manifest_not_regular");
|
|
5259
5435
|
const fd = openSync4(source, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
@@ -5261,7 +5437,7 @@ function fileBytesChecked(root, entry) {
|
|
|
5261
5437
|
const opened = fstatSync(fd);
|
|
5262
5438
|
if (!opened.isFile() || opened.size !== entry.bytes)
|
|
5263
5439
|
throw new Error("asset_manifest_bytes_mismatch");
|
|
5264
|
-
const bytes =
|
|
5440
|
+
const bytes = readFileSync14(fd);
|
|
5265
5441
|
if (sha2562(bytes) !== entry.sha256)
|
|
5266
5442
|
throw new Error("asset_manifest_digest_mismatch");
|
|
5267
5443
|
return bytes;
|
|
@@ -5270,9 +5446,9 @@ function fileBytesChecked(root, entry) {
|
|
|
5270
5446
|
}
|
|
5271
5447
|
}
|
|
5272
5448
|
function acquireLock2(projectRoot, assetId, timeoutMs) {
|
|
5273
|
-
const root =
|
|
5449
|
+
const root = resolve12(forgeaxRoot(projectRoot), "asset3d-locks");
|
|
5274
5450
|
ensurePrivateDir(root);
|
|
5275
|
-
const path =
|
|
5451
|
+
const path = resolve12(root, `${assetId}.lock`);
|
|
5276
5452
|
const token = randomUUID4();
|
|
5277
5453
|
const deadline = Date.now() + timeoutMs;
|
|
5278
5454
|
for (;; ) {
|
|
@@ -5283,7 +5459,7 @@ function acquireLock2(projectRoot, assetId, timeoutMs) {
|
|
|
5283
5459
|
closeSync3(fd);
|
|
5284
5460
|
return () => {
|
|
5285
5461
|
try {
|
|
5286
|
-
const current = JSON.parse(
|
|
5462
|
+
const current = JSON.parse(readFileSync14(path, "utf8"));
|
|
5287
5463
|
if (current.token === token)
|
|
5288
5464
|
unlinkSync3(path);
|
|
5289
5465
|
} catch {}
|
|
@@ -5332,17 +5508,17 @@ function engineAddedRows(projectRoot, asset) {
|
|
|
5332
5508
|
if (!Array.isArray(subAssets)) {
|
|
5333
5509
|
if (typeof asset.metaPath !== "string")
|
|
5334
5510
|
throw new Error("engine_readback_missing_meta");
|
|
5335
|
-
const metaPath =
|
|
5511
|
+
const metaPath = resolve12(projectRoot, asset.metaPath);
|
|
5336
5512
|
if (!confined4(projectRoot, metaPath))
|
|
5337
5513
|
throw new Error("engine_readback_meta_escape");
|
|
5338
|
-
const canonical =
|
|
5339
|
-
const info =
|
|
5514
|
+
const canonical = realpathSync9(metaPath);
|
|
5515
|
+
const info = lstatSync9(metaPath);
|
|
5340
5516
|
if (!confined4(projectRoot, canonical) || !info.isFile() || info.isSymbolicLink()) {
|
|
5341
5517
|
throw new Error("engine_readback_meta_invalid");
|
|
5342
5518
|
}
|
|
5343
5519
|
let meta;
|
|
5344
5520
|
try {
|
|
5345
|
-
meta = JSON.parse(
|
|
5521
|
+
meta = JSON.parse(readFileSync14(metaPath, "utf8"));
|
|
5346
5522
|
} catch {
|
|
5347
5523
|
throw new Error("engine_readback_meta_invalid");
|
|
5348
5524
|
}
|
|
@@ -5353,10 +5529,10 @@ function engineAddedRows(projectRoot, asset) {
|
|
|
5353
5529
|
const projectPath = (value) => {
|
|
5354
5530
|
if (typeof value !== "string")
|
|
5355
5531
|
return value;
|
|
5356
|
-
const absolute =
|
|
5532
|
+
const absolute = resolve12(projectRoot, value);
|
|
5357
5533
|
if (!confined4(projectRoot, absolute))
|
|
5358
5534
|
throw new Error("engine_readback_path_escape");
|
|
5359
|
-
return
|
|
5535
|
+
return relative10(projectRoot, absolute).split(sep7).join("/");
|
|
5360
5536
|
};
|
|
5361
5537
|
return subAssets.map((row) => ({ source: projectPath(asset.source), metaPath: projectPath(asset.metaPath), reused: asset.reused, ...row }));
|
|
5362
5538
|
}
|
|
@@ -5369,24 +5545,38 @@ function engineReadback(projectRoot, assetRelative, add, carrierPluginRoot) {
|
|
|
5369
5545
|
if (add && guids.length === 0)
|
|
5370
5546
|
throw new Error("engine_readback_missing_guid");
|
|
5371
5547
|
const inspect = guids.map((guid) => engineCommand(projectRoot, ["asset", "inspect", guid, "--json"], carrierPluginRoot).value);
|
|
5372
|
-
const listed = Array.isArray(list.value) ? list.value : [];
|
|
5548
|
+
const listed = Array.isArray(list.value) ? list.value : Array.isArray(list.value?.assets) ? list.value.assets : [];
|
|
5373
5549
|
for (const guid of guids)
|
|
5374
5550
|
if (!listed.some((entry) => entry.guid === guid))
|
|
5375
5551
|
throw new Error("engine_catalog_readback_missing");
|
|
5376
|
-
return { ...add ? { add: add.value } : {}, verify: verify.value, list:
|
|
5552
|
+
return { ...add ? { add: add.value } : {}, verify: verify.value, list: listed, inspect, rows };
|
|
5553
|
+
}
|
|
5554
|
+
function packReadback(projectRoot, item, carrierPluginRoot) {
|
|
5555
|
+
const release = resolveEngineRelease(projectRoot, carrierPluginRoot === undefined ? {} : { pluginRoot: carrierPluginRoot });
|
|
5556
|
+
const result = spawnSync4(process.execPath, [release.cliPath, "build", "--json"], {
|
|
5557
|
+
cwd: projectRoot,
|
|
5558
|
+
encoding: "utf8",
|
|
5559
|
+
timeout: 150000,
|
|
5560
|
+
maxBuffer: 8 * 1024 * 1024
|
|
5561
|
+
});
|
|
5562
|
+
if (result.error || result.status !== 0)
|
|
5563
|
+
throw new Error("engine_pack_build_failed");
|
|
5564
|
+
const envelope = parseEnvelope(result.stdout, "build");
|
|
5565
|
+
const prefix = `assets/3d/ea-3d/${item.providerAssetId}/`;
|
|
5566
|
+
return readPackBuildCatalog(projectRoot, item.manifest.map((entry) => prefix + entry.path), envelope.value);
|
|
5377
5567
|
}
|
|
5378
5568
|
function priorProvenance(destination) {
|
|
5379
5569
|
try {
|
|
5380
|
-
return JSON.parse(
|
|
5570
|
+
return JSON.parse(readFileSync14(resolve12(destination, ".forgeax-asset.json"), "utf8"));
|
|
5381
5571
|
} catch {
|
|
5382
5572
|
return;
|
|
5383
5573
|
}
|
|
5384
5574
|
}
|
|
5385
5575
|
function liveFilesMatch(destination, item) {
|
|
5386
5576
|
return item.manifest.every((entry) => {
|
|
5387
|
-
const path =
|
|
5577
|
+
const path = resolve12(destination, entry.path);
|
|
5388
5578
|
try {
|
|
5389
|
-
return confined4(destination, path) && statSync6(path).isFile() && statSync6(path).size === entry.bytes && sha2562(
|
|
5579
|
+
return confined4(destination, path) && statSync6(path).isFile() && statSync6(path).size === entry.bytes && sha2562(readFileSync14(path)) === entry.sha256;
|
|
5390
5580
|
} catch {
|
|
5391
5581
|
return false;
|
|
5392
5582
|
}
|
|
@@ -5395,21 +5585,21 @@ function liveFilesMatch(destination, item) {
|
|
|
5395
5585
|
function quarantineFiles(root, directory = root) {
|
|
5396
5586
|
const files = [];
|
|
5397
5587
|
for (const name of readdirSync7(directory)) {
|
|
5398
|
-
const path =
|
|
5399
|
-
const info =
|
|
5588
|
+
const path = resolve12(directory, name);
|
|
5589
|
+
const info = lstatSync9(path);
|
|
5400
5590
|
if (info.isSymbolicLink())
|
|
5401
5591
|
throw new Error("asset_quarantine_symlink_rejected");
|
|
5402
5592
|
if (info.isDirectory())
|
|
5403
5593
|
files.push(...quarantineFiles(root, path));
|
|
5404
5594
|
else if (info.isFile())
|
|
5405
|
-
files.push(
|
|
5595
|
+
files.push(relative10(root, path).split(sep7).join("/"));
|
|
5406
5596
|
else
|
|
5407
5597
|
throw new Error("asset_quarantine_special_file_rejected");
|
|
5408
5598
|
}
|
|
5409
5599
|
return files;
|
|
5410
5600
|
}
|
|
5411
5601
|
function validateQuarantine(journal, items) {
|
|
5412
|
-
const root =
|
|
5602
|
+
const root = realpathSync9(journal.allowedQuarantineRoot);
|
|
5413
5603
|
const expected = items.flatMap((item) => item.manifest.map((entry) => entry.path)).sort();
|
|
5414
5604
|
if (new Set(expected).size !== expected.length)
|
|
5415
5605
|
throw new Error("provider_result_invalid: cross-item manifest collision");
|
|
@@ -5418,10 +5608,10 @@ function validateQuarantine(journal, items) {
|
|
|
5418
5608
|
throw new Error("asset_quarantine_undeclared_file");
|
|
5419
5609
|
}
|
|
5420
5610
|
function stageItem(projectRoot, journal, item, destination, prior) {
|
|
5421
|
-
const root =
|
|
5422
|
-
const stageRoot =
|
|
5611
|
+
const root = realpathSync9(journal.allowedQuarantineRoot);
|
|
5612
|
+
const stageRoot = resolve12(forgeaxRoot(projectRoot), "asset3d-staging", journal.execution, item.providerAssetId);
|
|
5423
5613
|
rmSync6(stageRoot, { recursive: true, force: true });
|
|
5424
|
-
ensurePrivateDir(
|
|
5614
|
+
ensurePrivateDir(resolve12(stageRoot, ".."));
|
|
5425
5615
|
if (existsSync9(destination))
|
|
5426
5616
|
cpSync2(destination, stageRoot, { recursive: true, errorOnExist: true, force: false });
|
|
5427
5617
|
else
|
|
@@ -5431,17 +5621,17 @@ function stageItem(projectRoot, journal, item, destination, prior) {
|
|
|
5431
5621
|
for (const previous of previousFiles) {
|
|
5432
5622
|
if (typeof previous.relativePath !== "string" || nextPaths.has(previous.relativePath))
|
|
5433
5623
|
continue;
|
|
5434
|
-
const stale =
|
|
5624
|
+
const stale = resolve12(stageRoot, previous.relativePath);
|
|
5435
5625
|
if (!confined4(stageRoot, stale))
|
|
5436
5626
|
throw new Error("asset_provenance_path_escape");
|
|
5437
5627
|
rmSync6(stale, { force: true });
|
|
5438
5628
|
rmSync6(`${stale}.meta.json`, { force: true });
|
|
5439
5629
|
}
|
|
5440
5630
|
for (const entry of item.manifest) {
|
|
5441
|
-
const target =
|
|
5631
|
+
const target = resolve12(stageRoot, entry.path);
|
|
5442
5632
|
if (!confined4(stageRoot, target))
|
|
5443
5633
|
throw new Error("asset_manifest_escape");
|
|
5444
|
-
mkdirSync10(
|
|
5634
|
+
mkdirSync10(resolve12(target, ".."), { recursive: true, mode: 448 });
|
|
5445
5635
|
atomicWrite(target, fileBytesChecked(root, entry), 384);
|
|
5446
5636
|
}
|
|
5447
5637
|
return stageRoot;
|
|
@@ -5457,7 +5647,8 @@ function provenance(item, install, readback, refreshed) {
|
|
|
5457
5647
|
originSetDigest: install.originSetDigest,
|
|
5458
5648
|
aggregateSha256: item.sha256,
|
|
5459
5649
|
bytes: item.bytes,
|
|
5460
|
-
|
|
5650
|
+
deliveredFormat: item.deliveredFormat,
|
|
5651
|
+
...item.deliveredFormat === "pack" ? { primaryPack: item.primaryPack } : { primaryModel: item.primaryModel },
|
|
5461
5652
|
engine: { version: ENGINE_VERSION, commit: ENGINE_COMMIT },
|
|
5462
5653
|
refreshed,
|
|
5463
5654
|
files: item.manifest.map((entry) => ({
|
|
@@ -5471,8 +5662,8 @@ function provenance(item, install, readback, refreshed) {
|
|
|
5471
5662
|
};
|
|
5472
5663
|
}
|
|
5473
5664
|
function commitItem(projectRoot, initialJournal, install, item, refresh, lockTimeoutMs, carrierPluginRoot) {
|
|
5474
|
-
const destination =
|
|
5475
|
-
const assetRoot =
|
|
5665
|
+
const destination = resolve12(projectRoot, "assets", "3d", "ea-3d", item.providerAssetId);
|
|
5666
|
+
const assetRoot = resolve12(projectRoot, "assets", "3d", "ea-3d");
|
|
5476
5667
|
ensurePrivateDir(assetRoot);
|
|
5477
5668
|
if (!confined4(assetRoot, destination))
|
|
5478
5669
|
throw new Error("asset_destination_escape");
|
|
@@ -5483,29 +5674,30 @@ function commitItem(projectRoot, initialJournal, install, item, refresh, lockTim
|
|
|
5483
5674
|
const priorDigest = typeof prior?.aggregateSha256 === "string" ? prior.aggregateSha256 : undefined;
|
|
5484
5675
|
if (priorDigest === item.sha256 && liveFilesMatch(destination, item)) {
|
|
5485
5676
|
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);
|
|
5677
|
+
const baseReadback = item.deliveredFormat === "pack" ? packReadback(projectRoot, item, carrierPluginRoot) : engineReadback(projectRoot, `assets/3d/ea-3d/${item.providerAssetId}`, undefined, carrierPluginRoot);
|
|
5487
5678
|
const guids = priorRows.flatMap((row) => typeof row.guid === "string" ? [row.guid] : []);
|
|
5488
5679
|
const listed = Array.isArray(baseReadback.list) ? baseReadback.list : [];
|
|
5489
5680
|
if (guids.length === 0 || guids.some((guid) => !listed.some((entry) => entry.guid === guid)))
|
|
5490
5681
|
throw new Error("asset_reuse_readback_failed");
|
|
5491
|
-
const readback = { ...baseReadback, inspect: guids.map((guid) => engineCommand(projectRoot, ["asset", "inspect", guid, "--json"], carrierPluginRoot).value) };
|
|
5682
|
+
const readback = item.deliveredFormat === "pack" ? baseReadback : { ...baseReadback, inspect: guids.map((guid) => engineCommand(projectRoot, ["asset", "inspect", guid, "--json"], carrierPluginRoot).value) };
|
|
5492
5683
|
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:
|
|
5684
|
+
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
5685
|
}
|
|
5495
5686
|
if (priorDigest && priorDigest !== item.sha256 && !refresh)
|
|
5496
5687
|
throw new Error("asset_changed");
|
|
5497
5688
|
const stage = stageItem(projectRoot, journal, item, destination, prior);
|
|
5498
|
-
const backup =
|
|
5499
|
-
ensurePrivateDir(
|
|
5689
|
+
const backup = resolve12(forgeaxRoot(projectRoot), "asset3d-backups", journal.execution, item.providerAssetId);
|
|
5690
|
+
ensurePrivateDir(resolve12(backup, ".."));
|
|
5500
5691
|
journal = updateJournal(projectRoot, journal, { state: "committing", destination, backupPath: existsSync9(destination) ? backup : undefined, previousDigest: priorDigest, newDigest: item.sha256 });
|
|
5501
5692
|
if (existsSync9(destination))
|
|
5502
5693
|
renameSync7(destination, backup);
|
|
5503
5694
|
renameSync7(stage, destination);
|
|
5504
5695
|
try {
|
|
5505
5696
|
const assetRelative = `assets/3d/ea-3d/${item.providerAssetId}`;
|
|
5506
|
-
const
|
|
5507
|
-
|
|
5508
|
-
|
|
5697
|
+
const readback = item.deliveredFormat === "pack" ? packReadback(projectRoot, item, carrierPluginRoot) : engineReadback(projectRoot, assetRelative, engineCommand(projectRoot, ["asset", "add", assetRelative, "--reimport-policy", "semantic-only", "--json"], carrierPluginRoot), carrierPluginRoot);
|
|
5698
|
+
if (item.deliveredFormat === "pack" && !liveFilesMatch(destination, item))
|
|
5699
|
+
throw new Error("engine_pack_source_changed");
|
|
5700
|
+
atomicWrite(resolve12(destination, ".forgeax-asset.json"), `${JSON.stringify(provenance(item, install, readback, priorDigest !== undefined), null, 2)}
|
|
5509
5701
|
`);
|
|
5510
5702
|
journal = updateJournal(projectRoot, journal, { state: "engine_verified" });
|
|
5511
5703
|
if (existsSync9(backup))
|
|
@@ -5524,7 +5716,7 @@ function commitItem(projectRoot, initialJournal, install, item, refresh, lockTim
|
|
|
5524
5716
|
}
|
|
5525
5717
|
}
|
|
5526
5718
|
function commitAsset3d(options) {
|
|
5527
|
-
const projectRoot =
|
|
5719
|
+
const projectRoot = realpathSync9(options.projectRoot);
|
|
5528
5720
|
const install = readInstall(projectRoot);
|
|
5529
5721
|
let journal = readJournal(projectRoot, options.execution);
|
|
5530
5722
|
if (journal.state !== "begun" && journal.state !== "provider_complete" && journal.state !== "validated") {
|
|
@@ -5536,7 +5728,7 @@ function commitAsset3d(options) {
|
|
|
5536
5728
|
let providerResult = options.providerResult;
|
|
5537
5729
|
if (providerResult === undefined) {
|
|
5538
5730
|
try {
|
|
5539
|
-
providerResult =
|
|
5731
|
+
providerResult = readFileSync14(providerResultPath(projectRoot, options.execution));
|
|
5540
5732
|
} catch {
|
|
5541
5733
|
throw new Error("asset3d_provider_result_missing: call search_asset for this execution first");
|
|
5542
5734
|
}
|
|
@@ -5587,7 +5779,7 @@ function commitAsset3d(options) {
|
|
|
5587
5779
|
};
|
|
5588
5780
|
}
|
|
5589
5781
|
function abortAsset3d(projectRootInput, execution) {
|
|
5590
|
-
const projectRoot =
|
|
5782
|
+
const projectRoot = realpathSync9(projectRootInput);
|
|
5591
5783
|
const journal = readJournal(projectRoot, execution);
|
|
5592
5784
|
if (journal.state === "committed" || journal.state === "complete" || journal.state === "engine_verified") {
|
|
5593
5785
|
throw new Error("asset3d_execution_already_committed");
|
|
@@ -5598,20 +5790,20 @@ function abortAsset3d(projectRootInput, execution) {
|
|
|
5598
5790
|
return { execution, aborted: true };
|
|
5599
5791
|
}
|
|
5600
5792
|
function doctorAsset3d(projectRootInput, options = {}) {
|
|
5601
|
-
const projectRoot =
|
|
5793
|
+
const projectRoot = realpathSync9(projectRootInput);
|
|
5602
5794
|
const install = readInstall(projectRoot);
|
|
5603
5795
|
const release = resolveEngineRelease(projectRoot, options.carrierPluginRoot === undefined ? {} : { pluginRoot: options.carrierPluginRoot });
|
|
5604
5796
|
const recovered = [];
|
|
5605
|
-
const root =
|
|
5797
|
+
const root = resolve12(forgeaxRoot(projectRoot), "asset3d-transactions");
|
|
5606
5798
|
if (existsSync9(root))
|
|
5607
5799
|
for (const file of readdirSync7(root).filter((name) => name.endsWith(".json"))) {
|
|
5608
5800
|
try {
|
|
5609
|
-
const path =
|
|
5610
|
-
const journal = JSON.parse(
|
|
5801
|
+
const path = resolve12(root, file);
|
|
5802
|
+
const journal = JSON.parse(readFileSync14(path, "utf8"));
|
|
5611
5803
|
if (journal.schemaVersion !== TRANSACTION_SCHEMA || journal.state !== "committing" || !journal.destination)
|
|
5612
5804
|
continue;
|
|
5613
|
-
const assetsRoot =
|
|
5614
|
-
const backupsRoot =
|
|
5805
|
+
const assetsRoot = resolve12(projectRoot, "assets", "3d", "ea-3d");
|
|
5806
|
+
const backupsRoot = resolve12(forgeaxRoot(projectRoot), "asset3d-backups");
|
|
5615
5807
|
if (!confined4(assetsRoot, journal.destination) || journal.backupPath && !confined4(backupsRoot, journal.backupPath))
|
|
5616
5808
|
continue;
|
|
5617
5809
|
rmSync6(journal.destination, { recursive: true, force: true });
|
|
@@ -5621,13 +5813,13 @@ function doctorAsset3d(projectRootInput, options = {}) {
|
|
|
5621
5813
|
recovered.push(journal.execution);
|
|
5622
5814
|
} catch {}
|
|
5623
5815
|
}
|
|
5624
|
-
return { installed: true, recovered, engine: { version: release.version, commit: release.commit }, provider: { commit: install.providerCommit, cache: install.providerCache, live: existsSync9(
|
|
5816
|
+
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
5817
|
}
|
|
5626
5818
|
|
|
5627
5819
|
// src/asset3d/aw-access.ts
|
|
5628
5820
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
5629
5821
|
import { existsSync as existsSync10 } from "node:fs";
|
|
5630
|
-
import { resolve as
|
|
5822
|
+
import { resolve as resolve13 } from "node:path";
|
|
5631
5823
|
var AW_SERVICE_PATH = "/trpc.oasismetric.omcontentserver.http";
|
|
5632
5824
|
var ACCESS_CHECK_SCHEMA = "forgeax.asset3d-access-check/1.0.0";
|
|
5633
5825
|
var DEFAULT_AW_PUBLIC_SERVICE_ROOT = "http://lb-pl74wsqg-5wi8ujmy1fq2746r.clb.usw-tencentclb.com:8008/trpc.oasismetric.omcontentserver.http";
|
|
@@ -5660,7 +5852,7 @@ function resolveAssetLibrarySelection(options = {}) {
|
|
|
5660
5852
|
return { library, serviceRoot: normalizeAssetLibraryServiceRoot(configured) };
|
|
5661
5853
|
}
|
|
5662
5854
|
function checkAssetLibraryProviderAccess(options) {
|
|
5663
|
-
const command =
|
|
5855
|
+
const command = resolve13(options.providerCache, "bin", "asset3d-search");
|
|
5664
5856
|
if (!existsSync10(command))
|
|
5665
5857
|
throw new Error("asset3d_provider_not_prepared");
|
|
5666
5858
|
const result = spawnSync5(command, ["--check-aw-access"], {
|
|
@@ -5672,7 +5864,7 @@ function checkAssetLibraryProviderAccess(options) {
|
|
|
5672
5864
|
ASSET3D_CATALOG_BASE_URL: "",
|
|
5673
5865
|
AW_API_BASE_URL: options.serviceRoot,
|
|
5674
5866
|
AW_API_DEPOT_NAME: options.depotName,
|
|
5675
|
-
AW_API_CREDENTIAL_FILE:
|
|
5867
|
+
AW_API_CREDENTIAL_FILE: resolve13(options.credentialFile),
|
|
5676
5868
|
AW_API_SANDBOX_KEY: ""
|
|
5677
5869
|
}
|
|
5678
5870
|
});
|
|
@@ -5698,9 +5890,9 @@ function checkAssetLibraryProviderAccess(options) {
|
|
|
5698
5890
|
}
|
|
5699
5891
|
|
|
5700
5892
|
// src/asset3d/credentials.ts
|
|
5701
|
-
import { chmodSync as chmodSync5, existsSync as existsSync11, lstatSync as
|
|
5893
|
+
import { chmodSync as chmodSync5, existsSync as existsSync11, lstatSync as lstatSync10, readFileSync as readFileSync15, statSync as statSync7, unlinkSync as unlinkSync4 } from "node:fs";
|
|
5702
5894
|
import { homedir as homedir4 } from "node:os";
|
|
5703
|
-
import { dirname as dirname11, isAbsolute as
|
|
5895
|
+
import { dirname as dirname11, isAbsolute as isAbsolute6, resolve as resolve14 } from "node:path";
|
|
5704
5896
|
var AW_CREDENTIAL_SCHEMA = "forgeax.asset3d-credential/1.0.0";
|
|
5705
5897
|
var AW_KEY_ENV = "FORGEAX_ASSET3D_AW_SANDBOX_KEY";
|
|
5706
5898
|
var MAX_CREDENTIAL_BYTES = 4096;
|
|
@@ -5715,22 +5907,22 @@ function validateKey(value) {
|
|
|
5715
5907
|
}
|
|
5716
5908
|
function defaultAwCredentialFile() {
|
|
5717
5909
|
const configured = process.env.FORGEAX_ASSET3D_CREDENTIAL_FILE;
|
|
5718
|
-
return
|
|
5910
|
+
return resolve14(configured || resolve14(homedir4(), ".forgeax", "credentials", "asset3d-aw.json"));
|
|
5719
5911
|
}
|
|
5720
5912
|
function readAwCredential(pathInput) {
|
|
5721
|
-
const path =
|
|
5722
|
-
if (!
|
|
5913
|
+
const path = resolve14(pathInput);
|
|
5914
|
+
if (!isAbsolute6(pathInput))
|
|
5723
5915
|
throw new Error("asset3d_credential_path_invalid: absolute path required");
|
|
5724
5916
|
if (!existsSync11(path))
|
|
5725
5917
|
return;
|
|
5726
5918
|
try {
|
|
5727
|
-
const metadata =
|
|
5919
|
+
const metadata = lstatSync10(path);
|
|
5728
5920
|
const wrongOwner = typeof process.getuid === "function" && metadata.uid !== process.getuid();
|
|
5729
5921
|
if (!metadata.isFile() || metadata.isSymbolicLink() || wrongOwner)
|
|
5730
5922
|
throw new Error;
|
|
5731
5923
|
if ((metadata.mode & 63) !== 0 || metadata.size < 1 || metadata.size > MAX_CREDENTIAL_BYTES)
|
|
5732
5924
|
throw new Error;
|
|
5733
|
-
const parsed = JSON.parse(
|
|
5925
|
+
const parsed = JSON.parse(readFileSync15(path, "utf8"));
|
|
5734
5926
|
if (Object.keys(parsed).sort().join(",") !== "provider,sandboxKey,schemaVersion")
|
|
5735
5927
|
throw new Error;
|
|
5736
5928
|
if (parsed.schemaVersion !== AW_CREDENTIAL_SCHEMA || parsed.provider !== "aw" || typeof parsed.sandboxKey !== "string")
|
|
@@ -5792,13 +5984,13 @@ async function acquireAwKey(path) {
|
|
|
5792
5984
|
return { key: await promptAwKey(), source: "prompt" };
|
|
5793
5985
|
}
|
|
5794
5986
|
function writeAwCredential(pathInput, keyInput) {
|
|
5795
|
-
if (!
|
|
5987
|
+
if (!isAbsolute6(pathInput))
|
|
5796
5988
|
throw new Error("asset3d_credential_path_invalid: absolute path required");
|
|
5797
|
-
const path =
|
|
5989
|
+
const path = resolve14(pathInput);
|
|
5798
5990
|
const key = validateKey(keyInput);
|
|
5799
5991
|
const parent = dirname11(path);
|
|
5800
5992
|
ensurePrivateDir(parent);
|
|
5801
|
-
const parentMetadata =
|
|
5993
|
+
const parentMetadata = lstatSync10(parent);
|
|
5802
5994
|
const wrongParentOwner = typeof process.getuid === "function" && parentMetadata.uid !== process.getuid();
|
|
5803
5995
|
if (!parentMetadata.isDirectory() || parentMetadata.isSymbolicLink() || wrongParentOwner) {
|
|
5804
5996
|
throw new Error("asset3d_credential_path_invalid: parent must be an owned regular directory");
|
|
@@ -5807,7 +5999,7 @@ function writeAwCredential(pathInput, keyInput) {
|
|
|
5807
5999
|
const existed = existsSync11(path);
|
|
5808
6000
|
if (existed)
|
|
5809
6001
|
readAwCredential(path);
|
|
5810
|
-
const previous = existed ?
|
|
6002
|
+
const previous = existed ? readFileSync15(path) : undefined;
|
|
5811
6003
|
const previousMode = existed ? statSync7(path).mode & 511 : undefined;
|
|
5812
6004
|
const bytes = `${JSON.stringify({ schemaVersion: AW_CREDENTIAL_SCHEMA, provider: "aw", sandboxKey: key }, null, 2)}
|
|
5813
6005
|
`;
|
|
@@ -5872,7 +6064,7 @@ async function runDormantAsset3dMcp() {
|
|
|
5872
6064
|
result: {
|
|
5873
6065
|
protocolVersion,
|
|
5874
6066
|
capabilities: { tools: {} },
|
|
5875
|
-
serverInfo: { name: "asset3d-search", version:
|
|
6067
|
+
serverInfo: { name: "asset3d-search", version: RELEASE_IDENTITY.gameVersion }
|
|
5876
6068
|
}
|
|
5877
6069
|
};
|
|
5878
6070
|
} else if (request2.method === "tools/list") {
|
|
@@ -5914,7 +6106,7 @@ function rewriteSearchTool(tool) {
|
|
|
5914
6106
|
const required = Array.isArray(inputSchema.required) ? inputSchema.required.filter((field) => typeof field === "string" && field !== "output_dir") : [];
|
|
5915
6107
|
return {
|
|
5916
6108
|
...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.`,
|
|
6109
|
+
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
6110
|
inputSchema: {
|
|
5919
6111
|
...inputSchema,
|
|
5920
6112
|
properties: {
|
|
@@ -5961,10 +6153,21 @@ function transformAsset3dSearchCall(projectRoot, request2) {
|
|
|
5961
6153
|
if (typeof args.execution !== "string") {
|
|
5962
6154
|
throw new Error("asset3d_execution_required: run `forgeax-game asset3d begin` first");
|
|
5963
6155
|
}
|
|
5964
|
-
if (!Array.isArray(args.queries)
|
|
6156
|
+
if (!Array.isArray(args.queries)) {
|
|
5965
6157
|
throw new Error("asset3d_queries_invalid");
|
|
5966
6158
|
}
|
|
5967
|
-
const
|
|
6159
|
+
const queries = args.queries.map((query) => {
|
|
6160
|
+
if (typeof query === "string")
|
|
6161
|
+
return query;
|
|
6162
|
+
if (query && typeof query === "object" && !Array.isArray(query)) {
|
|
6163
|
+
const selected = query;
|
|
6164
|
+
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)) {
|
|
6165
|
+
return selected.content;
|
|
6166
|
+
}
|
|
6167
|
+
}
|
|
6168
|
+
throw new Error("asset3d_queries_invalid");
|
|
6169
|
+
});
|
|
6170
|
+
const outputDir = asset3dSearchOutputDir(projectRoot, args.execution, queries);
|
|
5968
6171
|
const { execution: _execution, output_dir: _callerOutput, ...providerArguments } = args;
|
|
5969
6172
|
return {
|
|
5970
6173
|
...request2,
|
|
@@ -6080,20 +6283,27 @@ async function runAsset3dMcpProxy(projectRoot, launch) {
|
|
|
6080
6283
|
};
|
|
6081
6284
|
process.once("SIGINT", stopChild);
|
|
6082
6285
|
process.once("SIGTERM", stopChild);
|
|
6083
|
-
return await new Promise((
|
|
6286
|
+
return await new Promise((resolve15, reject) => {
|
|
6084
6287
|
child.once("error", reject);
|
|
6085
|
-
child.once("close", (code, signal) =>
|
|
6288
|
+
child.once("close", (code, signal) => resolve15(code ?? (signal ? 1 : 0)));
|
|
6086
6289
|
});
|
|
6087
6290
|
}
|
|
6088
6291
|
|
|
6089
6292
|
// src/asset3d/host-config.ts
|
|
6090
|
-
function
|
|
6091
|
-
|
|
6293
|
+
function asset3dHostMode(client, root) {
|
|
6294
|
+
return inspectConfig(client, root, launchSpec("local")).state === "current" ? "local" : "npx";
|
|
6295
|
+
}
|
|
6296
|
+
function inspectAsset3dHost(client, root, mode = asset3dHostMode(client, root)) {
|
|
6297
|
+
const launch = asset3dLaunchSpec(mode);
|
|
6092
6298
|
const status = inspectConfig(client, root, launch, ASSET3D_SERVER_KEY);
|
|
6093
6299
|
if (status.state !== "different")
|
|
6094
6300
|
return status;
|
|
6301
|
+
const published = asset3dLaunchSpec("npx");
|
|
6302
|
+
if (mode === "local" && inspectConfig(client, root, published, ASSET3D_SERVER_KEY).state === "current") {
|
|
6303
|
+
return { ...status, state: "outdated" };
|
|
6304
|
+
}
|
|
6095
6305
|
const previous = configuredGameVersion(client, root, ASSET3D_SERVER_KEY);
|
|
6096
|
-
const current =
|
|
6306
|
+
const current = published.args.find((arg) => arg.startsWith("@forgeax/game@"))?.slice("@forgeax/game@".length);
|
|
6097
6307
|
if (!previous || !current || !/^\d+\.\d+\.\d+$/.test(previous) || !/^\d+\.\d+\.\d+$/.test(current))
|
|
6098
6308
|
return status;
|
|
6099
6309
|
const oldParts = previous.split(".").map(Number);
|
|
@@ -6101,7 +6311,7 @@ function inspectAsset3dHost(client, root) {
|
|
|
6101
6311
|
const differing = oldParts.findIndex((part, index) => part !== newParts[index]);
|
|
6102
6312
|
if (differing < 0 || oldParts[differing] >= newParts[differing])
|
|
6103
6313
|
return status;
|
|
6104
|
-
const oldLaunch = { ...
|
|
6314
|
+
const oldLaunch = { ...published, args: published.args.map((arg) => arg === `@forgeax/game@${current}` ? `@forgeax/game@${previous}` : arg) };
|
|
6105
6315
|
if (inspectConfig(client, root, oldLaunch, ASSET3D_SERVER_KEY).state !== "current")
|
|
6106
6316
|
return status;
|
|
6107
6317
|
return { ...status, state: "outdated" };
|
|
@@ -6109,7 +6319,7 @@ function inspectAsset3dHost(client, root) {
|
|
|
6109
6319
|
function refreshAsset3dHost(client, root) {
|
|
6110
6320
|
const status = inspectAsset3dHost(client, root);
|
|
6111
6321
|
if (status.state === "outdated") {
|
|
6112
|
-
const result = applyConfig(client, root, asset3dLaunchSpec(
|
|
6322
|
+
const result = applyConfig(client, root, asset3dLaunchSpec(asset3dHostMode(client, root)), ASSET3D_SERVER_KEY);
|
|
6113
6323
|
process.stdout.write(`UPDATED ${client.label} Asset3D bridge: ${result.path}
|
|
6114
6324
|
`);
|
|
6115
6325
|
} else if (status.state === "different" || status.state === "invalid") {
|
|
@@ -6118,6 +6328,85 @@ function refreshAsset3dHost(client, root) {
|
|
|
6118
6328
|
}
|
|
6119
6329
|
}
|
|
6120
6330
|
|
|
6331
|
+
// src/devkit/engine-mounts.ts
|
|
6332
|
+
import { lstatSync as lstatSync11, readFileSync as readFileSync16, readdirSync as readdirSync8, readlinkSync as readlinkSync2, rmdirSync, unlinkSync as unlinkSync5, writeFileSync as writeFileSync11 } from "node:fs";
|
|
6333
|
+
import { dirname as dirname12, join as join11, resolve as resolve15 } from "node:path";
|
|
6334
|
+
var HOSTS = {
|
|
6335
|
+
".agents/skills": ["codex"],
|
|
6336
|
+
".claude/skills": ["claude"],
|
|
6337
|
+
".cursor/skills": ["cursor"],
|
|
6338
|
+
".codebuddy/skills": ["codebuddy", "workbuddy"],
|
|
6339
|
+
".workbuddy/skills": ["workbuddy"]
|
|
6340
|
+
};
|
|
6341
|
+
function pruneUnselectedEngineMounts(root, clients) {
|
|
6342
|
+
const manifestPath = join11(root, ".forgeax", "skill-install-manifest.json");
|
|
6343
|
+
const regular = (path) => {
|
|
6344
|
+
try {
|
|
6345
|
+
const stat = lstatSync11(path);
|
|
6346
|
+
return stat.isFile() && !stat.isSymbolicLink();
|
|
6347
|
+
} catch {
|
|
6348
|
+
return false;
|
|
6349
|
+
}
|
|
6350
|
+
};
|
|
6351
|
+
const directory = (path) => {
|
|
6352
|
+
try {
|
|
6353
|
+
const stat = lstatSync11(path);
|
|
6354
|
+
return stat.isDirectory() && !stat.isSymbolicLink();
|
|
6355
|
+
} catch {
|
|
6356
|
+
return false;
|
|
6357
|
+
}
|
|
6358
|
+
};
|
|
6359
|
+
if (!directory(join11(root, ".forgeax")) || !regular(manifestPath))
|
|
6360
|
+
return [];
|
|
6361
|
+
let manifest;
|
|
6362
|
+
try {
|
|
6363
|
+
manifest = JSON.parse(readFileSync16(manifestPath, "utf8"));
|
|
6364
|
+
} catch {
|
|
6365
|
+
return [];
|
|
6366
|
+
}
|
|
6367
|
+
if (!manifest || manifest.schemaVersion !== "1.0.0" || manifest.sourceRoot !== "skills" || !Array.isArray(manifest.mounts))
|
|
6368
|
+
return [];
|
|
6369
|
+
const removed = [];
|
|
6370
|
+
for (const mount of manifest.mounts) {
|
|
6371
|
+
if (!mount || typeof mount.root !== "string" || !Object.hasOwn(HOSTS, mount.root))
|
|
6372
|
+
continue;
|
|
6373
|
+
const hosts = HOSTS[mount.root];
|
|
6374
|
+
if (!hosts || hosts.some((host) => clients.includes(host)) || !Array.isArray(mount.skills))
|
|
6375
|
+
continue;
|
|
6376
|
+
if (!mount.skills.length || !mount.skills.every((id) => /^forgeax-engine-[a-z0-9-]+$/.test(id)) || new Set(mount.skills).size !== mount.skills.length)
|
|
6377
|
+
continue;
|
|
6378
|
+
const path = join11(root, mount.root);
|
|
6379
|
+
if (!directory(dirname12(path)) || !directory(path))
|
|
6380
|
+
continue;
|
|
6381
|
+
const expected = [".gitignore", ...mount.skills].sort();
|
|
6382
|
+
if (JSON.stringify(readdirSync8(path).sort()) !== JSON.stringify(expected))
|
|
6383
|
+
continue;
|
|
6384
|
+
const ignore = join11(path, ".gitignore");
|
|
6385
|
+
const expectedIgnore = ["# BEGIN FORGEAX MANAGED SKILLS", ...mount.skills.map((id) => `/${id}`), "# END FORGEAX MANAGED SKILLS", ""].join(`
|
|
6386
|
+
`);
|
|
6387
|
+
if (!regular(ignore) || readFileSync16(ignore, "utf8") !== expectedIgnore)
|
|
6388
|
+
continue;
|
|
6389
|
+
if (!mount.skills.every((id) => {
|
|
6390
|
+
const link = join11(path, id);
|
|
6391
|
+
return lstatSync11(link).isSymbolicLink() && resolve15(path, readlinkSync2(link)) === resolve15(root, "skills", id);
|
|
6392
|
+
}))
|
|
6393
|
+
continue;
|
|
6394
|
+
for (const id of mount.skills)
|
|
6395
|
+
unlinkSync5(join11(path, id));
|
|
6396
|
+
unlinkSync5(ignore);
|
|
6397
|
+
rmdirSync(path);
|
|
6398
|
+
if (readdirSync8(dirname12(path)).length === 0)
|
|
6399
|
+
rmdirSync(dirname12(path));
|
|
6400
|
+
removed.push(mount.root);
|
|
6401
|
+
}
|
|
6402
|
+
if (removed.length) {
|
|
6403
|
+
manifest.mounts = manifest.mounts.filter((mount) => !mount || !removed.includes(mount.root));
|
|
6404
|
+
writeFileSync11(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
6405
|
+
`);
|
|
6406
|
+
}
|
|
6407
|
+
return removed;
|
|
6408
|
+
}
|
|
6409
|
+
|
|
6121
6410
|
// src/cli/dispatch.ts
|
|
6122
6411
|
var HELP = `ForgeaX game development plugin
|
|
6123
6412
|
|
|
@@ -6184,23 +6473,23 @@ function requireProject() {
|
|
|
6184
6473
|
return project.root;
|
|
6185
6474
|
}
|
|
6186
6475
|
function updateAgentsFile(root) {
|
|
6187
|
-
const path =
|
|
6188
|
-
const existing = existsSync12(path) ?
|
|
6476
|
+
const path = join12(root, "AGENTS.md");
|
|
6477
|
+
const existing = existsSync12(path) ? readFileSync17(path, "utf8") : undefined;
|
|
6189
6478
|
const content = upsertBlock(existing, ROUTING_TEXT);
|
|
6190
6479
|
if (content === existing)
|
|
6191
6480
|
return { path, changed: false };
|
|
6192
|
-
|
|
6481
|
+
writeFileSync12(path, content);
|
|
6193
6482
|
return { path, changed: true };
|
|
6194
6483
|
}
|
|
6195
6484
|
function removeAgentsBlock(root) {
|
|
6196
|
-
const path =
|
|
6485
|
+
const path = join12(root, "AGENTS.md");
|
|
6197
6486
|
if (!existsSync12(path))
|
|
6198
6487
|
return { path, changed: false };
|
|
6199
|
-
const existing =
|
|
6488
|
+
const existing = readFileSync17(path, "utf8");
|
|
6200
6489
|
const content = removeBlock(existing);
|
|
6201
6490
|
if (content === existing)
|
|
6202
6491
|
return { path, changed: false };
|
|
6203
|
-
|
|
6492
|
+
writeFileSync12(path, content);
|
|
6204
6493
|
return { path, changed: true };
|
|
6205
6494
|
}
|
|
6206
6495
|
async function installCommand(args) {
|
|
@@ -6270,6 +6559,10 @@ async function initCommand(args) {
|
|
|
6270
6559
|
reportMissingClients(selection.missing);
|
|
6271
6560
|
const hosts = selection.selected;
|
|
6272
6561
|
const devkit = installDevKit(root, hosts);
|
|
6562
|
+
const removedMounts = pruneUnselectedEngineMounts(root, hosts);
|
|
6563
|
+
if (removedMounts.length)
|
|
6564
|
+
process.stdout.write(`Removed unused Engine-generated skill mounts: ${removedMounts.join(", ")}.
|
|
6565
|
+
`);
|
|
6273
6566
|
process.stdout.write(`Bound Engine game ${slug} at ${selectedGame2}.
|
|
6274
6567
|
`);
|
|
6275
6568
|
process.stdout.write(`Engine ${release.version} (${release.commit}).
|
|
@@ -6300,7 +6593,7 @@ async function useCommand(args) {
|
|
|
6300
6593
|
throw new Error(`game ${JSON.stringify(slug)} not found. Available: ${listGames(root).join(", ") || "(none)"}`);
|
|
6301
6594
|
}
|
|
6302
6595
|
if (listGames(root).length > 1) {
|
|
6303
|
-
|
|
6596
|
+
writeFileSync12(join12(root, ".forgeax", "active-game.json"), `${JSON.stringify({ version: 1, slug }, null, 2)}
|
|
6304
6597
|
`, "utf8");
|
|
6305
6598
|
}
|
|
6306
6599
|
process.stdout.write(`Active game: ${slug}
|
|
@@ -6402,7 +6695,7 @@ async function uninstallCommand(args) {
|
|
|
6402
6695
|
const agents = removeAgentsBlock(root);
|
|
6403
6696
|
process.stdout.write(`${agents.changed ? "REMOVED" : "ABSENT "} routing block: ${agents.path}
|
|
6404
6697
|
`);
|
|
6405
|
-
process.stdout.write(`KEPT your games and project metadata: ${
|
|
6698
|
+
process.stdout.write(`KEPT your games and project metadata: ${join12(root, ".forgeax")}
|
|
6406
6699
|
`);
|
|
6407
6700
|
} else {
|
|
6408
6701
|
process.stdout.write(`INFO no ForgeaX project bound; only client configuration was touched.
|
|
@@ -6720,7 +7013,7 @@ async function asset3dCommand(args) {
|
|
|
6720
7013
|
if (selection.selected.length === 0) {
|
|
6721
7014
|
throw new Error("asset3d_client_missing: run `forgeax-game install --ide <client>` before enabling Asset3D");
|
|
6722
7015
|
}
|
|
6723
|
-
const
|
|
7016
|
+
const localMode = selection.selected.some((id) => asset3dHostMode(findClient(id), projectRoot) === "local");
|
|
6724
7017
|
for (const id of selection.selected) {
|
|
6725
7018
|
const client = findClient(id);
|
|
6726
7019
|
if (!client)
|
|
@@ -6743,7 +7036,7 @@ async function asset3dCommand(args) {
|
|
|
6743
7036
|
const bundled = BUNDLED_ASSET3D_PROVIDERS[target];
|
|
6744
7037
|
if (!bundled)
|
|
6745
7038
|
throw new Error(`asset3d_provider_target_unreleased: ${target}`);
|
|
6746
|
-
const providerCache =
|
|
7039
|
+
const providerCache = resolve16(homedir5(), ".forgeax", "providers", "asset3d-search", bundled.sha256);
|
|
6747
7040
|
let preparedCache = providerCache;
|
|
6748
7041
|
if (!existsSync12(providerCache)) {
|
|
6749
7042
|
const providerBundle = packagedAsset3dProvider(bundled.relativePath);
|
|
@@ -6769,14 +7062,14 @@ async function asset3dCommand(args) {
|
|
|
6769
7062
|
awDepotName: library.library,
|
|
6770
7063
|
awCredentialFile: credentialFile,
|
|
6771
7064
|
clients,
|
|
6772
|
-
gamePluginLaunch: launchSpec("npx"),
|
|
7065
|
+
gamePluginLaunch: launchSpec(localMode ? "local" : "npx"),
|
|
6773
7066
|
replaceOwned: true
|
|
6774
7067
|
});
|
|
6775
7068
|
for (const id of selection.selected) {
|
|
6776
7069
|
const client = findClient(id);
|
|
6777
7070
|
if (!client)
|
|
6778
7071
|
throw new Error(`asset3d_client_invalid: ${id}`);
|
|
6779
|
-
const applied = applyConfig(client, projectRoot,
|
|
7072
|
+
const applied = applyConfig(client, projectRoot, asset3dLaunchSpec(asset3dHostMode(client, projectRoot)), ASSET3D_SERVER_KEY);
|
|
6780
7073
|
process.stdout.write(`${applied.changed ? "UPDATED" : "CURRENT"} ${client.label} Asset3D bridge: ${applied.path}
|
|
6781
7074
|
`);
|
|
6782
7075
|
if (applied.changed && client.postInstallNote)
|
|
@@ -6825,7 +7118,7 @@ async function asset3dCommand(args) {
|
|
|
6825
7118
|
else if (arg === "--aw-depot-name")
|
|
6826
7119
|
awDepotName = parseAssetLibraryId(value);
|
|
6827
7120
|
else if (arg === "--aw-credential-file")
|
|
6828
|
-
awCredentialFile =
|
|
7121
|
+
awCredentialFile = resolve16(value);
|
|
6829
7122
|
else
|
|
6830
7123
|
clients = canonicalAsset3dClients(value);
|
|
6831
7124
|
continue;
|
|
@@ -7029,7 +7322,7 @@ function parseMcpArgs(args) {
|
|
|
7029
7322
|
} else
|
|
7030
7323
|
throw new Error(`unknown MCP option: ${arg}`);
|
|
7031
7324
|
}
|
|
7032
|
-
return { transport, host, port, root:
|
|
7325
|
+
return { transport, host, port, root: resolve17(root), requireAuth, allowedOrigins };
|
|
7033
7326
|
}
|
|
7034
7327
|
async function runMcp(args) {
|
|
7035
7328
|
const options = parseMcpArgs(args);
|