@playdrop/playdrop-cli 0.14.0 → 0.14.2
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/config/client-meta.json +2 -2
- package/dist/apps/loadCheck.d.ts +2 -21
- package/dist/apps/loadCheck.js +22 -106
- package/dist/apps/surfaceProfiles.d.ts +1 -0
- package/dist/apps/surfaceProfiles.js +5 -4
- package/dist/apps/validate.d.ts +1 -1
- package/dist/apps/validate.js +2 -420
- package/dist/catalogue.js +32 -1
- package/dist/commands/captureListing.d.ts +13 -0
- package/dist/commands/captureListing.js +187 -0
- package/dist/commands/check.d.ts +11 -1
- package/dist/commands/check.js +19 -39
- package/dist/commands/detail.js +35 -2
- package/dist/commands/review.d.ts +11 -1
- package/dist/commands/review.js +20 -13
- package/dist/commands/upload.d.ts +1 -7
- package/dist/commands/upload.js +20 -462
- package/dist/commands/validate.js +0 -3
- package/dist/commands/worker.d.ts +1 -3
- package/dist/commands/worker.js +39 -37
- package/dist/index.js +2 -4
- package/dist/listingPreflight.d.ts +3 -0
- package/dist/listingPreflight.js +37 -10
- package/node_modules/@playdrop/config/client-meta.json +2 -2
- package/node_modules/@playdrop/types/dist/app-playtest-tape.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/app-playtest-tape.js +23 -15
- package/package.json +1 -1
package/dist/commands/upload.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.resolveUploadOwner = resolveUploadOwner;
|
|
4
4
|
exports.resolveDefaultUploadCreator = resolveDefaultUploadCreator;
|
|
5
5
|
exports.upload = upload;
|
|
6
|
-
exports.
|
|
6
|
+
exports.assertAgentPrimarySurfaceContract = assertAgentPrimarySurfaceContract;
|
|
7
7
|
exports.assertAgentNewGamePlaytestContract = assertAgentNewGamePlaytestContract;
|
|
8
8
|
exports.assertWorkerAppLocalUploadPreflight = assertWorkerAppLocalUploadPreflight;
|
|
9
9
|
exports.preflightWorkerAppProject = preflightWorkerAppProject;
|
|
@@ -27,7 +27,6 @@ const uploadLog_1 = require("../uploadLog");
|
|
|
27
27
|
const workerAppProject_1 = require("../workerAppProject");
|
|
28
28
|
const upload_content_1 = require("./upload-content");
|
|
29
29
|
const appUrls_1 = require("../appUrls");
|
|
30
|
-
const devRuntimeAssets_1 = require("./devRuntimeAssets");
|
|
31
30
|
const ownedRuntimeImageValidation_1 = require("./ownedRuntimeImageValidation");
|
|
32
31
|
const listingPreflight_1 = require("../listingPreflight");
|
|
33
32
|
const upload_graph_1 = require("./upload-graph");
|
|
@@ -1247,26 +1246,16 @@ async function upload(pathOrName, options) {
|
|
|
1247
1246
|
(0, uploadLog_1.printTaskSummary)(results, warnings, { action: 'upload', environment: env });
|
|
1248
1247
|
(0, uploadLog_1.printPublishedAppNextSteps)(results);
|
|
1249
1248
|
}
|
|
1250
|
-
function
|
|
1251
|
-
const text = typeof value === 'string' ? value.toLowerCase() : '';
|
|
1252
|
-
if (!text.trim() || !/\bplay\s*drop\b|\bplaydrop\b/.test(text)) {
|
|
1253
|
-
return null;
|
|
1254
|
-
}
|
|
1255
|
-
if (/\basset\s*pack(s)?\b|\bpack(s)?\b/.test(text)) {
|
|
1256
|
-
return 'PACK';
|
|
1257
|
-
}
|
|
1258
|
-
if (/\basset(s)?\b|\bsprite(s)?\b|\bmodel(s)?\b|\bart\b|\bcharacter(s)?\b|\bprop(s)?\b/.test(text)) {
|
|
1259
|
-
return 'ASSET_OR_PACK';
|
|
1260
|
-
}
|
|
1261
|
-
return null;
|
|
1262
|
-
}
|
|
1263
|
-
function assertAgentNewGamePlaytestContract(task) {
|
|
1249
|
+
function assertAgentPrimarySurfaceContract(task, kind) {
|
|
1264
1250
|
if (!task.primarySurface) {
|
|
1265
|
-
throw new Error(
|
|
1251
|
+
throw new Error(`agent_task_primary_surface_required: ${kind} catalogue.json must declare primarySurface.`);
|
|
1266
1252
|
}
|
|
1267
1253
|
if (!task.surfaceTargets.includes(task.primarySurface)) {
|
|
1268
1254
|
throw new Error(`agent_task_primary_surface_not_supported:${task.primarySurface}`);
|
|
1269
1255
|
}
|
|
1256
|
+
}
|
|
1257
|
+
function assertAgentNewGamePlaytestContract(task) {
|
|
1258
|
+
assertAgentPrimarySurfaceContract(task, 'NEW_GAME');
|
|
1270
1259
|
if (!task.playtestTapes) {
|
|
1271
1260
|
throw new Error('agent_task_playtest_tapes_required: NEW_GAME catalogue.json must declare one playtest tape per supported surface.');
|
|
1272
1261
|
}
|
|
@@ -1277,424 +1266,6 @@ function assertAgentNewGamePlaytestContract(task) {
|
|
|
1277
1266
|
}
|
|
1278
1267
|
}
|
|
1279
1268
|
}
|
|
1280
|
-
function appTaskSatisfiesPlaydropAssetRequirement(task, requirement) {
|
|
1281
|
-
if (!requirement) {
|
|
1282
|
-
return true;
|
|
1283
|
-
}
|
|
1284
|
-
const hasFirstPartyPack = task.uses.packs.some((ref) => {
|
|
1285
|
-
const parsed = (0, types_1.parseContentVersionRef)(ref);
|
|
1286
|
-
return parsed?.kind === 'pack' && parsed.creatorUsername.toLowerCase() === 'playdrop';
|
|
1287
|
-
});
|
|
1288
|
-
if (requirement === 'PACK') {
|
|
1289
|
-
return hasFirstPartyPack;
|
|
1290
|
-
}
|
|
1291
|
-
const hasFirstPartyAsset = task.uses.assets.some((dependency) => {
|
|
1292
|
-
const parsed = (0, types_1.parseContentVersionRef)(dependency.ref);
|
|
1293
|
-
return parsed?.kind === 'asset' && parsed.creatorUsername.toLowerCase() === 'playdrop';
|
|
1294
|
-
});
|
|
1295
|
-
return hasFirstPartyPack || hasFirstPartyAsset || task.ownedAssets.length > 0;
|
|
1296
|
-
}
|
|
1297
|
-
const RUNTIME_SOURCE_EXTENSIONS = new Set([
|
|
1298
|
-
'.html',
|
|
1299
|
-
'.js',
|
|
1300
|
-
'.mjs',
|
|
1301
|
-
'.cjs',
|
|
1302
|
-
'.ts',
|
|
1303
|
-
'.tsx',
|
|
1304
|
-
'.jsx',
|
|
1305
|
-
'.css',
|
|
1306
|
-
]);
|
|
1307
|
-
const RUNTIME_SOURCE_EXCLUDED_DIRS = new Set([
|
|
1308
|
-
'.git',
|
|
1309
|
-
'.playdrop',
|
|
1310
|
-
'node_modules',
|
|
1311
|
-
'dist',
|
|
1312
|
-
'build',
|
|
1313
|
-
'.playdrop-task-events',
|
|
1314
|
-
'listing',
|
|
1315
|
-
'assets/marketing',
|
|
1316
|
-
]);
|
|
1317
|
-
function has3dIntent(value) {
|
|
1318
|
-
return /\b3d\b|\bthree[-\s]?d\b|\bthree\.js\b|\bglb\b|\bgltf\b/i.test(value ?? '');
|
|
1319
|
-
}
|
|
1320
|
-
function blankPreservingNewlines(value) {
|
|
1321
|
-
return value.replace(/[^\n\r]/g, ' ');
|
|
1322
|
-
}
|
|
1323
|
-
function stripRuntimeSourceCommentsAndStrings(source) {
|
|
1324
|
-
return source
|
|
1325
|
-
.replace(/\/\*[\s\S]*?\*\//g, blankPreservingNewlines)
|
|
1326
|
-
.replace(/`(?:\\[\s\S]|[^`\\])*`/g, blankPreservingNewlines)
|
|
1327
|
-
.replace(/'(?:\\.|[^'\\])*'/g, blankPreservingNewlines)
|
|
1328
|
-
.replace(/"(?:\\.|[^"\\])*"/g, blankPreservingNewlines)
|
|
1329
|
-
.replace(/\/\/[^\n\r]*/g, blankPreservingNewlines);
|
|
1330
|
-
}
|
|
1331
|
-
function hasDisallowed3dPlaydropAssetFallback(source) {
|
|
1332
|
-
const executableSource = stripRuntimeSourceCommentsAndStrings(source);
|
|
1333
|
-
return /\bemergency\s+mesh\b|\bemergency[A-Z][A-Za-z]*Mesh\b|\bbuildEmergencyMesh\b|\bfallback(?:Mesh|Model|Geometry|Material|Mats?|Size|Character|Obstacle|Enemy|Collectible)?\b|\bfallback\s+(?:mesh|model|geometry|material|character|obstacle|enemy|collectible)\b|\bplaceholder(?:Mesh|Model|Geometry|Material|Character|Obstacle|Enemy|Collectible)?\b|\bplaceholder\s+(?:mesh|model|geometry|material|character|obstacle|enemy|collectible)\b|catch\s*\([^)]*\)\s*=>\s*[^;\n]*(?:new\s+THREE\.(?:Box|Sphere|Capsule|Cone|Cylinder)Geometry|fallback|placeholder|emergency)|if\s*\([^)]*!url[^)]*\)\s*return\s+(?:new\s+THREE\.(?:Box|Sphere|Capsule|Cone|Cylinder)Geometry|fallback|placeholder|emergency)/i.test(executableSource);
|
|
1334
|
-
}
|
|
1335
|
-
function findDisallowedVisible3dPrimitiveGeometry(runtimeSource) {
|
|
1336
|
-
const executableSource = stripRuntimeSourceCommentsAndStrings(runtimeSource.source);
|
|
1337
|
-
const primitivePattern = /new\s+THREE\.(?:Box|Sphere|Capsule|Cone|Cylinder|Torus|TorusKnot|Dodecahedron|Icosahedron|Octahedron|Tetrahedron|Ring)Geometry\s*\(/ig;
|
|
1338
|
-
let match;
|
|
1339
|
-
while ((match = primitivePattern.exec(executableSource)) !== null) {
|
|
1340
|
-
const before = executableSource.slice(Math.max(0, match.index - 220), match.index);
|
|
1341
|
-
const after = executableSource.slice(match.index, Math.min(executableSource.length, match.index + 420));
|
|
1342
|
-
const meshStart = before.lastIndexOf('new THREE.Mesh');
|
|
1343
|
-
const meshWindow = `${meshStart >= 0 ? before.slice(meshStart) : before}${after}`;
|
|
1344
|
-
if (/\bvisible\s*:\s*false\b/i.test(meshWindow)) {
|
|
1345
|
-
continue;
|
|
1346
|
-
}
|
|
1347
|
-
const location = resolveRuntimeSourceLocation(runtimeSource, match.index);
|
|
1348
|
-
const constructorName = match[0]
|
|
1349
|
-
.replace(/^new\s+THREE\./i, 'THREE.')
|
|
1350
|
-
.replace(/\s*\($/, '');
|
|
1351
|
-
if (location) {
|
|
1352
|
-
return `${constructorName} at ${location.path}:${location.line}:${location.column}`;
|
|
1353
|
-
}
|
|
1354
|
-
return constructorName;
|
|
1355
|
-
}
|
|
1356
|
-
return null;
|
|
1357
|
-
}
|
|
1358
|
-
function hasDisallowedGameplayAssetFallback(source) {
|
|
1359
|
-
const executableSource = stripRuntimeSourceCommentsAndStrings(source);
|
|
1360
|
-
return /\bdrawFallback[A-Za-z0-9_]*\b|\bfallback(?:Image|Sprite|Asset|Draw|Visual|Character|Obstacle|Collectible|Prop|Enemy|Item|Piñata|Pinata|Candy)?\b|\bplaceholder(?:Image|Sprite|Asset|Draw|Visual|Character|Obstacle|Collectible|Prop|Enemy|Item)?\b|img\.onerror\s*=\s*(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>\s*(?:\{[^}]*\bresolve\s*\(|\bresolve\s*\(|\{[^}]*\breturn\b)|if\s*\([^)]*!url[^)]*\)\s*\{[^}]*\bresolve\s*\([^}]*\breturn\b|if\s*\([^)]*!url[^)]*\)\s*(?:return|continue|resolve\s*\()/i.test(executableSource);
|
|
1361
|
-
}
|
|
1362
|
-
function formatRuntimeSourceLocation(runtimeSource, offset) {
|
|
1363
|
-
const location = resolveRuntimeSourceLocation(runtimeSource, offset);
|
|
1364
|
-
if (!location) {
|
|
1365
|
-
return 'unknown source location';
|
|
1366
|
-
}
|
|
1367
|
-
return `${location.path}:${location.line}:${location.column}`;
|
|
1368
|
-
}
|
|
1369
|
-
function findRuntimeSourcePattern(runtimeSource, pattern) {
|
|
1370
|
-
const match = pattern.exec(runtimeSource.source);
|
|
1371
|
-
if (!match) {
|
|
1372
|
-
return null;
|
|
1373
|
-
}
|
|
1374
|
-
return {
|
|
1375
|
-
match: match[0],
|
|
1376
|
-
location: formatRuntimeSourceLocation(runtimeSource, match.index),
|
|
1377
|
-
};
|
|
1378
|
-
}
|
|
1379
|
-
function assertNoLaunchCheckConditionalSource(runtimeSource) {
|
|
1380
|
-
const directMarker = findRuntimeSourcePattern(runtimeSource, /\b(?:launchCheck|launch-check|upload-check|previewToken|localDevPort)\b/i);
|
|
1381
|
-
if (directMarker) {
|
|
1382
|
-
throw new Error(`agent_task_launch_check_conditional_disallowed: game source must not branch on PlayDrop validation markers. First match "${directMarker.match}" at ${directMarker.location}.`);
|
|
1383
|
-
}
|
|
1384
|
-
const environmentSniff = findRuntimeSourcePattern(runtimeSource, /(?:document\.referrer|window\.location|\blocation\.(?:href|search|origin)|new\s+URLSearchParams)[\s\S]{0,320}(?:localhost|127\.0\.0\.1|_auth\/creators|\/upload-check\/)|(?:localhost|127\.0\.0\.1|_auth\/creators|\/upload-check\/)[\s\S]{0,320}(?:document\.referrer|window\.location|\blocation\.(?:href|search|origin)|new\s+URLSearchParams)/i);
|
|
1385
|
-
if (environmentSniff) {
|
|
1386
|
-
throw new Error(`agent_task_launch_check_conditional_disallowed: game source must not sniff validation or local route context. First match at ${environmentSniff.location}.`);
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
function hasDeclaredRuntimeAssetDependency(task) {
|
|
1390
|
-
return task.uses.packs.length > 0
|
|
1391
|
-
|| task.uses.assets.length > 0
|
|
1392
|
-
|| task.ownedAssets.some((ownedAsset) => !isListingOwnedAsset(task, ownedAsset));
|
|
1393
|
-
}
|
|
1394
|
-
function isListingOwnedAsset(task, ownedAsset) {
|
|
1395
|
-
const primaryPath = ownedAsset.filePaths?.primary;
|
|
1396
|
-
if (!primaryPath) {
|
|
1397
|
-
return false;
|
|
1398
|
-
}
|
|
1399
|
-
const relativePath = primaryPath.slice((0, node_path_1.resolve)(task.projectDir).length + 1).replace(/\\/g, '/');
|
|
1400
|
-
return relativePath.startsWith('assets/marketing/') || relativePath.startsWith('listing/');
|
|
1401
|
-
}
|
|
1402
|
-
function isAudioOwnedAsset(ownedAsset) {
|
|
1403
|
-
return ownedAsset.category === 'AUDIO';
|
|
1404
|
-
}
|
|
1405
|
-
function sourceMentionsRuntimeKey(source, runtimeKey) {
|
|
1406
|
-
return source.includes(runtimeKey);
|
|
1407
|
-
}
|
|
1408
|
-
function hasRuntimeAssetLoadOrRenderPath(source) {
|
|
1409
|
-
return /\bsdk\.assets\b|\.resolveAppAsset\s*\(|\.listAppAssets\s*\(/.test(source)
|
|
1410
|
-
&& /\bnew\s+(?:Image|Audio)\s*\(|createImageBitmap\s*\(|\.src\s*=|\.load(?:Async)?\s*\(|\.drawImage\s*\(|\.add\.image\s*\(|\.load\.image\s*\(|\.textures\.add|\.sound\.add|\.scene\.add\s*\(/i.test(source);
|
|
1411
|
-
}
|
|
1412
|
-
function assertAudioAssetsAreNonBlocking(task, runtimeSource) {
|
|
1413
|
-
const source = runtimeSource.source;
|
|
1414
|
-
for (const ownedAsset of task.ownedAssets) {
|
|
1415
|
-
if (!isAudioOwnedAsset(ownedAsset)) {
|
|
1416
|
-
continue;
|
|
1417
|
-
}
|
|
1418
|
-
const runtimeKey = ownedAsset.runtimeKey?.trim() ?? '';
|
|
1419
|
-
const name = ownedAsset.name.trim();
|
|
1420
|
-
if (!runtimeKey || (!source.includes(runtimeKey) && !source.includes(name))) {
|
|
1421
|
-
continue;
|
|
1422
|
-
}
|
|
1423
|
-
const lowerRuntimeKey = runtimeKey.toLowerCase();
|
|
1424
|
-
const lowerName = name.toLowerCase();
|
|
1425
|
-
const executableSource = stripRuntimeSourceCommentsAndStrings(source);
|
|
1426
|
-
const fatalAudioPattern = /\bthrow\s+new\s+Error\s*\([^)]*(?:missing|asset|sound|audio|sfx)[^)]*\)|\bthrow\s+new\s+Error\s*\([^)]*\)|Promise\.reject\s*\([^)]*(?:missing|asset|sound|audio|sfx)[^)]*\)/ig;
|
|
1427
|
-
let match;
|
|
1428
|
-
while ((match = fatalAudioPattern.exec(executableSource)) !== null) {
|
|
1429
|
-
const start = Math.max(0, match.index - 800);
|
|
1430
|
-
const end = Math.min(source.length, match.index + 800);
|
|
1431
|
-
const nearby = source.slice(start, end).toLowerCase();
|
|
1432
|
-
if (nearby.includes(lowerRuntimeKey) || nearby.includes(lowerName)) {
|
|
1433
|
-
throw new Error(`agent_task_audio_asset_boot_fatal_disallowed: audio SFX owned asset "${ownedAsset.name}" must not block play when it fails to load; log a console warning and continue.`);
|
|
1434
|
-
}
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
|
-
}
|
|
1438
|
-
function hasRuntimeAssetUrlExtensionAssumption(source) {
|
|
1439
|
-
return /(?:\.url|\.href|\burl\b)[^;\n]{0,220}(?:\\\.gl(?:b|tf)|\.gl(?:b|tf)|\.endsWith\s*\(\s*['"]\.gl(?:b|tf)['"]\s*\)|\.includes\s*\(\s*['"]\.gl(?:b|tf)['"]\s*\)|\.match\s*\([^)]*\.gl(?:b|tf))/i.test(source);
|
|
1440
|
-
}
|
|
1441
|
-
function hasPackRuntimeManifestShape(source) {
|
|
1442
|
-
return /\bassetRef\b/.test(source) && /\bcontentType\b/.test(source);
|
|
1443
|
-
}
|
|
1444
|
-
function manifestHasGltfFile(fileManifest) {
|
|
1445
|
-
const files = fileManifest && typeof fileManifest === 'object' && Array.isArray(fileManifest.files)
|
|
1446
|
-
? fileManifest.files
|
|
1447
|
-
: [];
|
|
1448
|
-
return files.some((file) => {
|
|
1449
|
-
const contentType = typeof file?.contentType === 'string' ? file.contentType.toLowerCase() : '';
|
|
1450
|
-
return contentType === 'model/gltf-binary' || contentType === 'model/gltf+json';
|
|
1451
|
-
});
|
|
1452
|
-
}
|
|
1453
|
-
function assetVersionHasGltfFile(version) {
|
|
1454
|
-
if (version.format === 'GLB' || version.format === 'GLTF') {
|
|
1455
|
-
return true;
|
|
1456
|
-
}
|
|
1457
|
-
const primaryContentType = typeof version.primaryContentType === 'string'
|
|
1458
|
-
? version.primaryContentType.toLowerCase()
|
|
1459
|
-
: '';
|
|
1460
|
-
return primaryContentType === 'model/gltf-binary'
|
|
1461
|
-
|| primaryContentType === 'model/gltf+json'
|
|
1462
|
-
|| manifestHasGltfFile(version.fileManifest);
|
|
1463
|
-
}
|
|
1464
|
-
async function findRemoteAssetVersionForRef(client, ref) {
|
|
1465
|
-
let offset = 0;
|
|
1466
|
-
for (let page = 0; page < 200; page += 1) {
|
|
1467
|
-
const response = await client.listAssetVersions(ref.creatorUsername, ref.name, {
|
|
1468
|
-
limit: 100,
|
|
1469
|
-
offset,
|
|
1470
|
-
});
|
|
1471
|
-
const version = (response.versions ?? []).find((entry) => entry.revision === ref.revision);
|
|
1472
|
-
if (version) {
|
|
1473
|
-
return version;
|
|
1474
|
-
}
|
|
1475
|
-
if (!response.pagination?.hasMore) {
|
|
1476
|
-
break;
|
|
1477
|
-
}
|
|
1478
|
-
offset += response.pagination.limit;
|
|
1479
|
-
}
|
|
1480
|
-
return null;
|
|
1481
|
-
}
|
|
1482
|
-
async function assertDeclared3dPlaydropPacksContainRuntimeModels(input) {
|
|
1483
|
-
if (!has3dIntent(input.creatorRequest) || input.task.uses.packs.length === 0) {
|
|
1484
|
-
return;
|
|
1485
|
-
}
|
|
1486
|
-
const inspectedRefs = [];
|
|
1487
|
-
for (const rawRef of input.task.uses.packs) {
|
|
1488
|
-
const parsed = (0, types_1.parseContentVersionRef)(rawRef);
|
|
1489
|
-
if (!parsed || parsed.kind !== 'pack') {
|
|
1490
|
-
throw new Error(`agent_task_playdrop_3d_pack_ref_invalid: invalid pack ref "${rawRef}".`);
|
|
1491
|
-
}
|
|
1492
|
-
inspectedRefs.push(rawRef);
|
|
1493
|
-
let offset = 0;
|
|
1494
|
-
let packVersion = null;
|
|
1495
|
-
for (let page = 0; page < 200; page += 1) {
|
|
1496
|
-
const response = await input.client.listAssetPackVersions(parsed.creatorUsername, parsed.name, {
|
|
1497
|
-
limit: 100,
|
|
1498
|
-
offset,
|
|
1499
|
-
});
|
|
1500
|
-
packVersion = (response.versions ?? []).find((entry) => entry.version === parsed.version) ?? null;
|
|
1501
|
-
if (packVersion || !response.pagination?.hasMore) {
|
|
1502
|
-
break;
|
|
1503
|
-
}
|
|
1504
|
-
offset += response.pagination.limit;
|
|
1505
|
-
}
|
|
1506
|
-
if (!packVersion) {
|
|
1507
|
-
throw new Error(`agent_task_playdrop_3d_pack_models_missing: could not inspect exact pack version "${rawRef}".`);
|
|
1508
|
-
}
|
|
1509
|
-
const modelCount = Number(packVersion.assetCategoryBreakdown?.MODEL_3D ?? 0);
|
|
1510
|
-
if (!Number.isFinite(modelCount) || modelCount <= 0) {
|
|
1511
|
-
continue;
|
|
1512
|
-
}
|
|
1513
|
-
for (const assetRef of packVersion.assets ?? []) {
|
|
1514
|
-
const version = await findRemoteAssetVersionForRef(input.client, assetRef);
|
|
1515
|
-
if (version && assetVersionHasGltfFile(version)) {
|
|
1516
|
-
return;
|
|
1517
|
-
}
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
throw new Error(`agent_task_playdrop_3d_pack_models_missing: declared 3D PlayDrop pack refs do not expose model/gltf-binary or model/gltf+json files. Inspected: ${inspectedRefs.join(', ')}.`);
|
|
1521
|
-
}
|
|
1522
|
-
function collectLineStarts(source) {
|
|
1523
|
-
const starts = [0];
|
|
1524
|
-
for (let index = 0; index < source.length; index += 1) {
|
|
1525
|
-
if (source[index] === '\n') {
|
|
1526
|
-
starts.push(index + 1);
|
|
1527
|
-
}
|
|
1528
|
-
}
|
|
1529
|
-
return starts;
|
|
1530
|
-
}
|
|
1531
|
-
function resolveRuntimeSourceLocation(runtimeSource, offset) {
|
|
1532
|
-
let entry = null;
|
|
1533
|
-
for (const candidate of runtimeSource.offsetEntries) {
|
|
1534
|
-
if (candidate.start <= offset) {
|
|
1535
|
-
entry = candidate;
|
|
1536
|
-
continue;
|
|
1537
|
-
}
|
|
1538
|
-
break;
|
|
1539
|
-
}
|
|
1540
|
-
if (!entry) {
|
|
1541
|
-
return null;
|
|
1542
|
-
}
|
|
1543
|
-
const localOffset = Math.max(0, offset - entry.start);
|
|
1544
|
-
let lineIndex = 0;
|
|
1545
|
-
for (let index = 0; index < entry.lineStarts.length; index += 1) {
|
|
1546
|
-
if (entry.lineStarts[index] <= localOffset) {
|
|
1547
|
-
lineIndex = index;
|
|
1548
|
-
continue;
|
|
1549
|
-
}
|
|
1550
|
-
break;
|
|
1551
|
-
}
|
|
1552
|
-
const lineStart = entry.lineStarts[lineIndex] ?? 0;
|
|
1553
|
-
return {
|
|
1554
|
-
path: entry.path,
|
|
1555
|
-
line: lineIndex + 1,
|
|
1556
|
-
column: localOffset - lineStart + 1,
|
|
1557
|
-
};
|
|
1558
|
-
}
|
|
1559
|
-
function readRuntimeSourceText(rootDir) {
|
|
1560
|
-
const root = (0, node_path_1.resolve)(rootDir);
|
|
1561
|
-
const chunks = [];
|
|
1562
|
-
const offsetEntries = [];
|
|
1563
|
-
let totalBytes = 0;
|
|
1564
|
-
const maxBytes = 1000000;
|
|
1565
|
-
const walk = (dir) => {
|
|
1566
|
-
if (totalBytes >= maxBytes) {
|
|
1567
|
-
return;
|
|
1568
|
-
}
|
|
1569
|
-
for (const entry of (0, node_fs_1.readdirSync)(dir, { withFileTypes: true })) {
|
|
1570
|
-
const absolute = (0, node_path_1.join)(dir, entry.name);
|
|
1571
|
-
const relative = absolute.slice(root.length + 1).replace(/\\/g, '/');
|
|
1572
|
-
if (entry.isDirectory()) {
|
|
1573
|
-
if (RUNTIME_SOURCE_EXCLUDED_DIRS.has(entry.name) || RUNTIME_SOURCE_EXCLUDED_DIRS.has(relative)) {
|
|
1574
|
-
continue;
|
|
1575
|
-
}
|
|
1576
|
-
walk(absolute);
|
|
1577
|
-
continue;
|
|
1578
|
-
}
|
|
1579
|
-
if (!entry.isFile() || !RUNTIME_SOURCE_EXTENSIONS.has((0, node_path_1.extname)(entry.name).toLowerCase())) {
|
|
1580
|
-
continue;
|
|
1581
|
-
}
|
|
1582
|
-
const stats = (0, node_fs_1.statSync)(absolute);
|
|
1583
|
-
if (stats.size > 300000) {
|
|
1584
|
-
continue;
|
|
1585
|
-
}
|
|
1586
|
-
totalBytes += stats.size;
|
|
1587
|
-
const source = (0, node_fs_1.readFileSync)(absolute, 'utf8');
|
|
1588
|
-
const prefix = chunks.length > 0 ? '\n' : '';
|
|
1589
|
-
const start = chunks.join('').length + prefix.length;
|
|
1590
|
-
chunks.push(prefix, source);
|
|
1591
|
-
offsetEntries.push({
|
|
1592
|
-
path: relative,
|
|
1593
|
-
start,
|
|
1594
|
-
lineStarts: collectLineStarts(source),
|
|
1595
|
-
});
|
|
1596
|
-
if (totalBytes >= maxBytes) {
|
|
1597
|
-
return;
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
};
|
|
1601
|
-
walk(root);
|
|
1602
|
-
return {
|
|
1603
|
-
source: chunks.join(''),
|
|
1604
|
-
offsetEntries,
|
|
1605
|
-
};
|
|
1606
|
-
}
|
|
1607
|
-
function assertRequestedPlaydropAssetsAreUsedAtRuntime(input) {
|
|
1608
|
-
if (!input.requirement) {
|
|
1609
|
-
return;
|
|
1610
|
-
}
|
|
1611
|
-
const runtimeSource = readRuntimeSourceText(input.task.projectDir);
|
|
1612
|
-
const source = runtimeSource.source;
|
|
1613
|
-
if (!/\bsdk\.assets\b|\.resolveAppAsset\s*\(|\.listAppAssets\s*\(/.test(source)) {
|
|
1614
|
-
throw new Error('agent_task_playdrop_asset_runtime_missing: the creator asked for PlayDrop assets, but the app source does not load runtime assets through sdk.assets.');
|
|
1615
|
-
}
|
|
1616
|
-
if (input.requirement === 'PACK' && !/\.listAppAssets\s*\(/.test(source)) {
|
|
1617
|
-
throw new Error('agent_task_playdrop_pack_runtime_missing: the creator asked for a PlayDrop asset pack, but the app source does not enumerate pack assets with sdk.assets.listAppAssets().');
|
|
1618
|
-
}
|
|
1619
|
-
if (has3dIntent(input.creatorRequest) && input.task.uses.packs.length > 0 && !/\bGLTFLoader\b|\.gltf\b|\.glb\b|model\/gltf/i.test(source)) {
|
|
1620
|
-
throw new Error('agent_task_playdrop_3d_asset_runtime_missing: the creator asked for a 3D PlayDrop asset pack, but the app source does not load GLB/GLTF models at runtime.');
|
|
1621
|
-
}
|
|
1622
|
-
if (has3dIntent(input.creatorRequest) && (input.task.uses.packs.length > 0 || input.task.uses.assets.length > 0) && hasRuntimeAssetUrlExtensionAssumption(source)) {
|
|
1623
|
-
throw new Error('agent_task_playdrop_3d_url_extension_assumption: PlayDrop runtime asset URLs do not need to end in .glb or .gltf; choose GLB/GLTF files using file.contentType from the SDK asset manifest.');
|
|
1624
|
-
}
|
|
1625
|
-
if (has3dIntent(input.creatorRequest) && input.task.uses.packs.length > 0 && !hasPackRuntimeManifestShape(source)) {
|
|
1626
|
-
throw new Error('agent_task_playdrop_pack_manifest_shape_invalid: 3D PlayDrop pack runtime code must select pack files through asset.assetRef and file.contentType from sdk.assets.listAppAssets().');
|
|
1627
|
-
}
|
|
1628
|
-
if (has3dIntent(input.creatorRequest) && input.task.uses.packs.length > 0 && hasDisallowed3dPlaydropAssetFallback(source)) {
|
|
1629
|
-
throw new Error('agent_task_playdrop_3d_asset_fallback_disallowed: the creator asked for a 3D PlayDrop asset pack, but the app source includes emergency or placeholder mesh fallbacks instead of failing when pack models are unavailable.');
|
|
1630
|
-
}
|
|
1631
|
-
if (has3dIntent(input.creatorRequest) && input.task.uses.packs.length > 0) {
|
|
1632
|
-
const primitiveGeometry = findDisallowedVisible3dPrimitiveGeometry(runtimeSource);
|
|
1633
|
-
if (primitiveGeometry) {
|
|
1634
|
-
throw new Error(`agent_task_playdrop_3d_primitive_geometry_disallowed: the creator asked for a 3D PlayDrop asset pack, but the app source includes visible Three.js primitive geometry instead of real pack models for gameplay visuals. First match: ${primitiveGeometry}.`);
|
|
1635
|
-
}
|
|
1636
|
-
}
|
|
1637
|
-
}
|
|
1638
|
-
async function assertNewGameDeclaredAssetsAreUsedAtRuntime(input) {
|
|
1639
|
-
const runtimeSource = readRuntimeSourceText(input.task.projectDir);
|
|
1640
|
-
const source = runtimeSource.source;
|
|
1641
|
-
assertNoLaunchCheckConditionalSource(runtimeSource);
|
|
1642
|
-
if (!hasDeclaredRuntimeAssetDependency(input.task)) {
|
|
1643
|
-
return;
|
|
1644
|
-
}
|
|
1645
|
-
if (!/\bsdk\.assets\b|\.resolveAppAsset\s*\(|\.listAppAssets\s*\(/.test(source)) {
|
|
1646
|
-
throw new Error('agent_task_game_assets_runtime_missing: new game tasks must load declared gameplay assets through sdk.assets at runtime.');
|
|
1647
|
-
}
|
|
1648
|
-
if (input.task.uses.packs.length > 0 && !/\.listAppAssets\s*\(/.test(source)) {
|
|
1649
|
-
throw new Error('agent_task_game_pack_runtime_missing: new game tasks that declare uses.packs must enumerate pack assets with sdk.assets.listAppAssets().');
|
|
1650
|
-
}
|
|
1651
|
-
if (!hasRuntimeAssetLoadOrRenderPath(source)) {
|
|
1652
|
-
throw new Error('agent_task_game_assets_runtime_missing: declared game assets must be loaded and rendered or played at runtime, not only listed in catalogue.json.');
|
|
1653
|
-
}
|
|
1654
|
-
if (input.task.uses.packs.length > 0 && !/\bassetRef\b|\bsourceType\b|\bcontentType\b/i.test(source)) {
|
|
1655
|
-
throw new Error('agent_task_game_pack_runtime_missing: new game tasks that declare uses.packs must select pack assets from the runtime manifest by assetRef/sourceType/contentType before rendering them.');
|
|
1656
|
-
}
|
|
1657
|
-
const manifest = input.task.ownedAssets.length > 0
|
|
1658
|
-
? await (0, devRuntimeAssets_1.buildDevRuntimeAssetManifest)({
|
|
1659
|
-
apiBase: 'http://127.0.0.1',
|
|
1660
|
-
task: {
|
|
1661
|
-
...input.task,
|
|
1662
|
-
uses: { assets: [], packs: [] },
|
|
1663
|
-
},
|
|
1664
|
-
creatorUsername: input.creatorUsername,
|
|
1665
|
-
appBaseUrl: `http://127.0.0.1/apps/dev/${encodeURIComponent(input.creatorUsername)}/${encodeURIComponent((0, appUrls_1.getAppTypeSlug)(input.task.type ?? 'GAME'))}/${encodeURIComponent(input.task.name)}/`,
|
|
1666
|
-
})
|
|
1667
|
-
: null;
|
|
1668
|
-
const runtimeKeys = new Set((manifest?.response.assets ?? [])
|
|
1669
|
-
.map((asset) => typeof asset.runtimeKey === 'string' ? asset.runtimeKey.trim() : '')
|
|
1670
|
-
.filter(Boolean));
|
|
1671
|
-
for (const ownedAsset of input.task.ownedAssets) {
|
|
1672
|
-
const runtimeKey = ownedAsset.runtimeKey?.trim() ?? '';
|
|
1673
|
-
if (!runtimeKey) {
|
|
1674
|
-
throw new Error(`agent_task_owned_asset_runtime_key_missing: owned asset "${ownedAsset.name}" must declare a runtimeKey.`);
|
|
1675
|
-
}
|
|
1676
|
-
if (!runtimeKeys.has(runtimeKey)) {
|
|
1677
|
-
throw new Error(`agent_task_owned_asset_runtime_manifest_missing: owned asset "${ownedAsset.name}" runtimeKey "${runtimeKey}" was not present in the runtime manifest.`);
|
|
1678
|
-
}
|
|
1679
|
-
if (isListingOwnedAsset(input.task, ownedAsset)) {
|
|
1680
|
-
continue;
|
|
1681
|
-
}
|
|
1682
|
-
if (!sourceMentionsRuntimeKey(source, runtimeKey)) {
|
|
1683
|
-
throw new Error(`agent_task_owned_asset_runtime_key_unused: owned asset "${ownedAsset.name}" runtimeKey "${runtimeKey}" does not appear in runtime source.`);
|
|
1684
|
-
}
|
|
1685
|
-
}
|
|
1686
|
-
for (const dependency of input.task.uses.assets) {
|
|
1687
|
-
const runtimeKey = dependency.runtimeKey?.trim() ?? '';
|
|
1688
|
-
if (runtimeKey && !sourceMentionsRuntimeKey(source, runtimeKey)) {
|
|
1689
|
-
throw new Error(`agent_task_declared_asset_runtime_key_unused: declared asset "${dependency.ref}" runtimeKey "${runtimeKey}" does not appear in runtime source.`);
|
|
1690
|
-
}
|
|
1691
|
-
}
|
|
1692
|
-
assertAudioAssetsAreNonBlocking(input.task, runtimeSource);
|
|
1693
|
-
const hasDirectGameplayAssets = input.task.ownedAssets.some((ownedAsset) => !isListingOwnedAsset(input.task, ownedAsset)) || input.task.uses.assets.length > 0;
|
|
1694
|
-
if (hasDirectGameplayAssets && hasDisallowedGameplayAssetFallback(source)) {
|
|
1695
|
-
throw new Error('agent_task_game_asset_fallback_disallowed: new game tasks must fail clearly when declared gameplay assets cannot load instead of rendering placeholder or fallback visuals.');
|
|
1696
|
-
}
|
|
1697
|
-
}
|
|
1698
1269
|
async function collectUploadPreflightErrors(checks) {
|
|
1699
1270
|
const errors = [];
|
|
1700
1271
|
for (const check of checks) {
|
|
@@ -1710,13 +1281,13 @@ async function collectUploadPreflightErrors(checks) {
|
|
|
1710
1281
|
function assertAgentTaskReleaseNotes(task) {
|
|
1711
1282
|
const releaseNotes = task.releaseNotes?.trim() ?? '';
|
|
1712
1283
|
if (!releaseNotes) {
|
|
1713
|
-
throw new Error('agent_task_release_notes_missing');
|
|
1284
|
+
throw new Error('agent_task_release_notes_missing: catalogue releaseNotes must contain one line with 1..240 characters.');
|
|
1714
1285
|
}
|
|
1715
1286
|
if (/[\r\n]/.test(releaseNotes)) {
|
|
1716
|
-
throw new Error(
|
|
1287
|
+
throw new Error(`agent_task_release_notes_multiline: releaseNotes has ${releaseNotes.split(/\r?\n/).length} lines; expected one line with 1..240 characters.`);
|
|
1717
1288
|
}
|
|
1718
1289
|
if (releaseNotes.length > 240) {
|
|
1719
|
-
throw new Error(
|
|
1290
|
+
throw new Error(`agent_task_release_notes_too_long: releaseNotes length=${releaseNotes.length}; expected 1..240 characters on one line.`);
|
|
1720
1291
|
}
|
|
1721
1292
|
}
|
|
1722
1293
|
async function assertWorkerAppLocalUploadPreflight(input) {
|
|
@@ -1727,23 +1298,18 @@ async function assertWorkerAppLocalUploadPreflight(input) {
|
|
|
1727
1298
|
&& (0, node_fs_1.existsSync)(existingCaptureReport);
|
|
1728
1299
|
const errors = await collectUploadPreflightErrors([
|
|
1729
1300
|
() => assertAgentTaskReleaseNotes(input.task),
|
|
1730
|
-
...(complete
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1301
|
+
...(complete
|
|
1302
|
+
? [
|
|
1303
|
+
() => (0, listingPreflight_1.assertAgentGameIconListingPreflight)(input.task),
|
|
1304
|
+
() => (0, listingPreflight_1.assertAgentGameHeroListingPreflight)(input.task),
|
|
1305
|
+
() => (0, listingPreflight_1.assertAgentGamePreviewListingPreflight)(input.task),
|
|
1306
|
+
() => (0, listingPreflight_1.assertAgentGameVideoListingPreflight)(input.task),
|
|
1307
|
+
]
|
|
1308
|
+
: []),
|
|
1309
|
+
...(input.kind === 'NEW_GAME'
|
|
1310
|
+
? [() => assertAgentNewGamePlaytestContract(input.task)]
|
|
1311
|
+
: [() => assertAgentPrimarySurfaceContract(input.task, input.kind)]),
|
|
1737
1312
|
() => (0, ownedRuntimeImageValidation_1.assertOwnedRuntimeImagesAreValid)(input.task),
|
|
1738
|
-
() => assertRequestedPlaydropAssetsAreUsedAtRuntime({
|
|
1739
|
-
task: input.task,
|
|
1740
|
-
requirement: input.playdropAssetRequirement,
|
|
1741
|
-
creatorRequest: input.creatorRequest,
|
|
1742
|
-
}),
|
|
1743
|
-
() => assertNewGameDeclaredAssetsAreUsedAtRuntime({
|
|
1744
|
-
creatorUsername: input.creatorUsername?.trim() ?? '',
|
|
1745
|
-
task: input.task,
|
|
1746
|
-
}),
|
|
1747
1313
|
...(complete
|
|
1748
1314
|
? [
|
|
1749
1315
|
() => (0, listingPreflight_1.assertAgentGameScreenshotListingPreflight)(input.task),
|
|
@@ -1825,17 +1391,9 @@ async function prepareWorkerAppProject(input, stage) {
|
|
|
1825
1391
|
task,
|
|
1826
1392
|
kind: input.kind,
|
|
1827
1393
|
executionTarget: input.executionTarget,
|
|
1828
|
-
creatorUsername,
|
|
1829
|
-
playdropAssetRequirement: input.playdropAssetRequirement,
|
|
1830
|
-
creatorRequest: input.creatorRequest,
|
|
1831
1394
|
stage,
|
|
1832
1395
|
expectedRuntimeBundleHash: artifacts?.bundleHash,
|
|
1833
1396
|
});
|
|
1834
|
-
await assertDeclared3dPlaydropPacksContainRuntimeModels({
|
|
1835
|
-
client: input.client,
|
|
1836
|
-
task,
|
|
1837
|
-
creatorRequest: input.creatorRequest,
|
|
1838
|
-
});
|
|
1839
1397
|
return {
|
|
1840
1398
|
task,
|
|
1841
1399
|
artifacts,
|
|
@@ -199,9 +199,6 @@ async function validate(pathOrName, options = {}) {
|
|
|
199
199
|
task,
|
|
200
200
|
kind: workerContext.kind,
|
|
201
201
|
executionTarget: workerContext.target,
|
|
202
|
-
creatorUsername: workerContext.creatorUsername,
|
|
203
|
-
creatorRequest: workerContext.creatorRequest,
|
|
204
|
-
playdropAssetRequirement: (0, upload_1.resolveWorkerPlaydropAssetRequirement)(workerContext.creatorRequest),
|
|
205
202
|
});
|
|
206
203
|
}
|
|
207
204
|
else {
|
|
@@ -2,7 +2,6 @@ import type { ApiClient } from '@playdrop/api-client';
|
|
|
2
2
|
import { type AgentAvailabilityFailureReason, type AgentExecutionTarget, type AgentRuntime, type AgentTaskKind, type AgentTaskNextStepSuggestion, type AgentTaskResponse, type AppSurface, type AgentWorkerCapabilities, type AgentWorkerResponse, type AgentWorkerUpdatePolicyResponse, type JsonValue, type WorkerTaskAssignmentV2, type WorkerTaskPluginV2, type WorkerClaimAgentTaskResponse } from '@playdrop/types';
|
|
3
3
|
import { loadConfig } from '../config';
|
|
4
4
|
import { runShell } from '../shellProbe';
|
|
5
|
-
import { type WorkerPlaydropAssetRequirement } from './upload';
|
|
6
5
|
import { type WorkerInstrumentSurface, type LoggedProcessResult, type LoggedProcessTranscriptChunk } from './worker/runtime';
|
|
7
6
|
export { assertWorkerTokenUsageWithinCap, buildClaudeExecArgs, buildClaudePermissionSettings, buildCleanRoomChildEnv, buildCodexExecArgs, buildPlaywrightMcpCommand, buildWorkerChildEnv, DEFAULT_CODEX_TIMEOUT_MS, DEFAULT_CREATOR_AGENT_TIMEOUT_MS, DEFAULT_CREATOR_UPLOAD_RECOVERY_TIMEOUT_MS, DEFAULT_WORKER_TOKEN_CAP, extractAgentTokenUsage, extractCodexRolloutTokenUsage, extractClaudeSessionId, extractCodexThreadId, extractCodexTokensUsed, readCodexRolloutTokenUsageForThread, readCodexSandboxMode, readEnvBoolean, runLoggedProcess } from './worker/runtime';
|
|
8
7
|
export type { AgentTokenUsage, LoggedProcessResult, LoggedProcessTranscriptChunk } from './worker/runtime';
|
|
@@ -215,7 +214,7 @@ export declare function appendWorkerTranscriptChunks(input: {
|
|
|
215
214
|
chunks: LoggedProcessTranscriptChunk[];
|
|
216
215
|
batchSize?: number;
|
|
217
216
|
}): Promise<void>;
|
|
218
|
-
export declare function readTaskNextStepsFile(filePath: string | undefined): AgentTaskNextStepSuggestion[] | undefined;
|
|
217
|
+
export declare function readTaskNextStepsFile(filePath: string | undefined, workspaceDir?: string): AgentTaskNextStepSuggestion[] | undefined;
|
|
219
218
|
export declare function assertTaskUploadResultMatchesContext(input: {
|
|
220
219
|
taskContext: {
|
|
221
220
|
taskId: number;
|
|
@@ -256,7 +255,6 @@ export declare function classifyWorkerEventDrainError(error: unknown): 'session_
|
|
|
256
255
|
export declare function isWorkerTransientNetworkError(error: unknown): boolean;
|
|
257
256
|
export declare function classifyWorkerHeartbeatError(error: unknown): 'session_expired' | 'lease_invalid' | 'transient_network' | null;
|
|
258
257
|
export declare function assertWorkerClaimErrorRetryable(error: unknown): void;
|
|
259
|
-
export declare function resolvePlaydropAssetRequirementFromText(value: unknown): WorkerPlaydropAssetRequirement | null;
|
|
260
258
|
export declare function resolvePlaywrightBrowsersPathFromExecutable(executablePath: string): string;
|
|
261
259
|
export declare function loadWorkerEnvFile(envName: string | undefined, cwd?: string): string | null;
|
|
262
260
|
export declare function buildWorkerCapabilities(input: {
|