@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.
@@ -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.resolveWorkerPlaydropAssetRequirement = resolveWorkerPlaydropAssetRequirement;
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 resolveWorkerPlaydropAssetRequirement(value) {
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('agent_task_primary_surface_required: NEW_GAME catalogue.json must declare primarySurface.');
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('agent_task_release_notes_multiline');
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('agent_task_release_notes_too_long');
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 ? [() => (0, listingPreflight_1.assertAgentGameHeroListingPreflight)(input.task)] : []),
1731
- ...(input.kind === 'NEW_GAME' ? [() => assertAgentNewGamePlaytestContract(input.task)] : []),
1732
- () => {
1733
- if (!appTaskSatisfiesPlaydropAssetRequirement(input.task, input.playdropAssetRequirement)) {
1734
- throw new Error('agent_task_playdrop_asset_dependency_missing: the creator asked for PlayDrop assets, but catalogue.json does not declare a matching uses.assets, uses.packs, or ownedAssets dependency.');
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: {