@playdrop/playdrop-cli 0.15.6 → 0.15.11

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.
Files changed (78) hide show
  1. package/config/client-meta.json +2 -2
  2. package/dist/apps/index.d.ts +2 -3
  3. package/dist/apps/index.js +2 -2
  4. package/dist/apps/loadCheck.d.ts +4 -1
  5. package/dist/apps/loadCheck.js +64 -7
  6. package/dist/apps/staticHtml.js +1 -0
  7. package/dist/apps/upload.d.ts +38 -1
  8. package/dist/apps/upload.js +58 -44
  9. package/dist/catalogue.d.ts +3 -0
  10. package/dist/catalogue.js +26 -0
  11. package/dist/commands/create.js +45 -16
  12. package/dist/commands/devRuntimeAssets.d.ts +1 -0
  13. package/dist/commands/devRuntimeAssets.js +2 -0
  14. package/dist/commands/review.js +0 -3
  15. package/dist/commands/upload.d.ts +2 -3
  16. package/dist/commands/upload.js +23 -42
  17. package/dist/commands/validate.js +0 -4
  18. package/dist/commands/worker.d.ts +7 -1
  19. package/dist/commands/worker.js +65 -57
  20. package/dist/index.js +1 -1
  21. package/dist/listingPreflight.d.ts +1 -24
  22. package/dist/listingPreflight.js +3 -288
  23. package/node_modules/@playdrop/api-client/dist/client.d.ts +25 -2
  24. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  25. package/node_modules/@playdrop/api-client/dist/client.js +11 -0
  26. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +4 -1
  27. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
  28. package/node_modules/@playdrop/api-client/dist/domains/admin.js +31 -2
  29. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts +2 -1
  30. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts.map +1 -1
  31. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.js +12 -0
  32. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +9 -1
  33. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
  34. package/node_modules/@playdrop/api-client/dist/domains/apps.js +66 -0
  35. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts +1 -0
  36. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts.map +1 -1
  37. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.js +3 -0
  38. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts +1 -0
  39. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts.map +1 -1
  40. package/node_modules/@playdrop/api-client/dist/domains/assets.js +3 -0
  41. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts +10 -2
  42. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -1
  43. package/node_modules/@playdrop/api-client/dist/domains/payments.js +66 -1
  44. package/node_modules/@playdrop/api-client/dist/index.d.ts +27 -5
  45. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  46. package/node_modules/@playdrop/api-client/dist/index.js +73 -2
  47. package/node_modules/@playdrop/config/client-meta.json +2 -2
  48. package/node_modules/@playdrop/config/dist/src/creator-plans.d.ts +18 -0
  49. package/node_modules/@playdrop/config/dist/src/creator-plans.d.ts.map +1 -0
  50. package/node_modules/@playdrop/config/dist/src/creator-plans.js +117 -0
  51. package/node_modules/@playdrop/config/dist/src/game-arena.d.ts +1 -1
  52. package/node_modules/@playdrop/config/dist/src/game-arena.js +1 -1
  53. package/node_modules/@playdrop/config/dist/src/index.d.ts +1 -0
  54. package/node_modules/@playdrop/config/dist/src/index.d.ts.map +1 -1
  55. package/node_modules/@playdrop/config/dist/src/index.js +1 -0
  56. package/node_modules/@playdrop/config/dist/test/creator-plans.test.d.ts +2 -0
  57. package/node_modules/@playdrop/config/dist/test/creator-plans.test.d.ts.map +1 -0
  58. package/node_modules/@playdrop/config/dist/test/creator-plans.test.js +28 -0
  59. package/node_modules/@playdrop/config/dist/test/game-arena.test.js +3 -3
  60. package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
  61. package/node_modules/@playdrop/types/dist/api.d.ts +153 -15
  62. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  63. package/node_modules/@playdrop/types/dist/api.js +27 -2
  64. package/node_modules/@playdrop/types/dist/app-review.d.ts +2 -0
  65. package/node_modules/@playdrop/types/dist/app-review.d.ts.map +1 -1
  66. package/node_modules/@playdrop/types/dist/app-review.js +31 -2
  67. package/node_modules/@playdrop/types/dist/index.d.ts +2 -0
  68. package/node_modules/@playdrop/types/dist/index.d.ts.map +1 -1
  69. package/node_modules/@playdrop/types/dist/index.js +5 -1
  70. package/node_modules/@playdrop/types/dist/locale.d.ts +5 -0
  71. package/node_modules/@playdrop/types/dist/locale.d.ts.map +1 -0
  72. package/node_modules/@playdrop/types/dist/locale.js +47 -0
  73. package/node_modules/@playdrop/types/dist/release-smoke.d.ts +3 -0
  74. package/node_modules/@playdrop/types/dist/release-smoke.d.ts.map +1 -0
  75. package/node_modules/@playdrop/types/dist/release-smoke.js +35 -0
  76. package/node_modules/@playdrop/types/dist/version.d.ts +5 -0
  77. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  78. package/package.json +1 -1
@@ -20,6 +20,7 @@ const catalogue_utils_1 = require("../catalogue-utils");
20
20
  const clientInfo_1 = require("../clientInfo");
21
21
  const commandContext_1 = require("../commandContext");
22
22
  const http_1 = require("../http");
23
+ const listingPreflight_1 = require("../listingPreflight");
23
24
  const messages_1 = require("../messages");
24
25
  const loadCheck_1 = require("../apps/loadCheck");
25
26
  const devShared_1 = require("./devShared");
@@ -252,6 +253,28 @@ function makeDefaultAssetSpecEntry(name) {
252
253
  visibility: 'PUBLIC',
253
254
  };
254
255
  }
256
+ function resolveTaskScaffoldOutputVersion(startDir = process.cwd()) {
257
+ const context = (0, listingPreflight_1.readWorkerUploadPreflightContext)(startDir);
258
+ if (!context || (context.kind !== 'NEW_GAME' && context.kind !== 'REMIX_GAME')) {
259
+ return null;
260
+ }
261
+ if (!context.outputVersion) {
262
+ throw new Error('agent_task_output_version_missing: task.metadata.playdrop.output.version is required when scaffolding a game task.');
263
+ }
264
+ return context.outputVersion;
265
+ }
266
+ function forceScaffoldedCatalogueVersion(cataloguePath, appName, outputVersion) {
267
+ const parsed = readCatalogue(cataloguePath);
268
+ if (!parsed || !Array.isArray(parsed.apps)) {
269
+ throw new Error(`agent_task_scaffold_catalogue_invalid: ${cataloguePath} must contain an apps array.`);
270
+ }
271
+ const app = parsed.apps.find((entry) => entry?.name === appName);
272
+ if (!app) {
273
+ throw new Error(`agent_task_scaffold_app_missing: ${cataloguePath} must contain app "${appName}".`);
274
+ }
275
+ app.version = outputVersion;
276
+ (0, node_fs_1.writeFileSync)(cataloguePath, `${JSON.stringify(parsed, null, 2)}\n`);
277
+ }
255
278
  function buildCatalogueEntry(name, metadata, sourceInfo, relativeFilePath) {
256
279
  const entry = makeDefaultAppEntry(name, relativeFilePath);
257
280
  if (!metadata) {
@@ -1133,8 +1156,10 @@ async function create(name, options = {}) {
1133
1156
  if (!ctx) {
1134
1157
  return;
1135
1158
  }
1136
- const envName = ctx.env;
1137
1159
  const currentDir = process.cwd();
1160
+ const taskScaffoldOutputVersion = !reuseExistingApp
1161
+ ? resolveTaskScaffoldOutputVersion(currentDir)
1162
+ : null;
1138
1163
  const localCataloguePath = (0, node_path_1.resolve)(currentDir, CATALOGUE_FILENAME);
1139
1164
  let initSummary = null;
1140
1165
  let workspaceCatalogueResult = null;
@@ -1365,6 +1390,21 @@ async function create(name, options = {}) {
1365
1390
  return;
1366
1391
  }
1367
1392
  }
1393
+ if (taskScaffoldOutputVersion) {
1394
+ const scaffoldedCataloguePaths = new Set();
1395
+ if (projectCatalogueResult?.updated) {
1396
+ scaffoldedCataloguePaths.add((0, node_path_1.resolve)(projectCatalogueResult.path));
1397
+ }
1398
+ if (workspaceCatalogueResult && workspaceCatalogueUpdateSucceeded) {
1399
+ scaffoldedCataloguePaths.add((0, node_path_1.resolve)(workspaceCatalogueResult.path));
1400
+ }
1401
+ if (scaffoldedCataloguePaths.size === 0) {
1402
+ throw new Error('agent_task_scaffold_catalogue_missing: scaffolding did not produce a catalogue to version.');
1403
+ }
1404
+ for (const scaffoldedCataloguePath of scaffoldedCataloguePaths) {
1405
+ forceScaffoldedCatalogueVersion(scaffoldedCataloguePath, name, taskScaffoldOutputVersion);
1406
+ }
1407
+ }
1368
1408
  let registrationTask = null;
1369
1409
  if (reuseExistingApp && existingTask) {
1370
1410
  registrationTask = existingTask;
@@ -1455,6 +1495,7 @@ async function create(name, options = {}) {
1455
1495
  name,
1456
1496
  displayName,
1457
1497
  ...(Number.isInteger(taskId) && Number(taskId) > 0 ? { agentTaskId: Number(taskId) } : {}),
1498
+ ...(sourceInfo.remixRef ? { remixRef: sourceInfo.remixRef } : {}),
1458
1499
  };
1459
1500
  let creatorUsername = '';
1460
1501
  try {
@@ -1487,7 +1528,6 @@ async function create(name, options = {}) {
1487
1528
  }
1488
1529
  throw error;
1489
1530
  }
1490
- let createConflict = false;
1491
1531
  try {
1492
1532
  await client.createApp(creatorUsername, createPayload);
1493
1533
  console.log(`Registered ${displayName} with the Playdrop API.`);
@@ -1503,17 +1543,9 @@ async function create(name, options = {}) {
1503
1543
  return;
1504
1544
  }
1505
1545
  if (error instanceof types_1.ApiError) {
1506
- if (error.status === 409) {
1507
- createConflict = true;
1508
- console.log(`ℹ️ App ${name} already exists on ${envName}. Updating metadata from the local catalogue.`);
1509
- }
1510
- else {
1511
- (0, messages_1.printErrorWithHelp)(`Failed to register ${name} (status ${error.status}).`, [
1512
- 'Ensure your account has creator permissions and retry once the issue is resolved.'
1513
- ], { command: 'project create app' });
1514
- process.exitCode = 1;
1515
- return;
1516
- }
1546
+ (0, messages_1.printErrorWithHelp)(`Failed to register ${name} (status ${error.status}: ${error.code}).`, ['Resolve the API error, then retry the same project create command.'], { command: 'project create app' });
1547
+ process.exitCode = 1;
1548
+ return;
1517
1549
  }
1518
1550
  else if (error instanceof TypeError) {
1519
1551
  (0, messages_1.printNetworkIssue)('Could not reach the Playdrop API to register the app.', 'project create app');
@@ -1535,9 +1567,6 @@ async function create(name, options = {}) {
1535
1567
  if (Object.keys(updatePayload).length > 0) {
1536
1568
  try {
1537
1569
  await client.updateApp(creatorUsername, name, updatePayload);
1538
- if (createConflict) {
1539
- console.log(`Updated ${name} metadata on the Playdrop API.`);
1540
- }
1541
1570
  }
1542
1571
  catch (error) {
1543
1572
  if (error instanceof http_1.CLIUnsupportedClientError) {
@@ -12,6 +12,7 @@ export type LocalDevAppMetadata = {
12
12
  authMode: AppAuthMode;
13
13
  controllerMode: AppControllerMode;
14
14
  previewable: boolean;
15
+ editorSupported: boolean;
15
16
  assetSpecSupport: AppMetadataAssetSpecSupport[];
16
17
  version: string | null;
17
18
  tweakValues?: TweakValues;
@@ -20,6 +20,7 @@ function createEmptyDevRuntimeAssetManifest() {
20
20
  authMode: types_1.DEFAULT_APP_AUTH_MODE,
21
21
  controllerMode: types_1.DEFAULT_APP_CONTROLLER_MODE,
22
22
  previewable: types_1.DEFAULT_APP_PREVIEWABLE,
23
+ editorSupported: false,
23
24
  assetSpecSupport: [],
24
25
  version: null,
25
26
  },
@@ -118,6 +119,7 @@ function buildLocalAppMetadata(task) {
118
119
  authMode: task.authMode ?? types_1.DEFAULT_APP_AUTH_MODE,
119
120
  controllerMode: task.controllerMode ?? types_1.DEFAULT_APP_CONTROLLER_MODE,
120
121
  previewable: task.previewable ?? types_1.DEFAULT_APP_PREVIEWABLE,
122
+ editorSupported: task.editorSupported ?? false,
121
123
  assetSpecSupport: task.assetSpecSupport.map((entry) => ({
122
124
  assetSpecRef: entry.assetSpec,
123
125
  versionRange: entry.versionRange,
@@ -87,9 +87,6 @@ function normalizeReviewText(...values) {
87
87
  .trim();
88
88
  }
89
89
  function parseScores(reviewMessage) {
90
- if (/\/5\b/.test(reviewMessage)) {
91
- throw new Error('review_scores_must_use_10_point_scale');
92
- }
93
90
  const scores = {};
94
91
  for (const criterion of exports.REVIEW_CRITERIA) {
95
92
  const escaped = criterion.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
@@ -51,6 +51,7 @@ export type StaticHtmlPublishInput = {
51
51
  taskToken?: string;
52
52
  claimedAppName?: string | null;
53
53
  devRouterPort?: number;
54
+ skipReview?: boolean;
54
55
  };
55
56
  export type StaticHtmlPublishResult = WorkerAppPublishResult & {
56
57
  sourceHash: string;
@@ -62,12 +63,10 @@ export type StaticHtmlPublishResult = WorkerAppPublishResult & {
62
63
  };
63
64
  export declare function assertAgentPrimarySurfaceContract(task: AppTask, kind: 'NEW_GAME' | 'REMIX_GAME' | 'GAME_UPDATE'): void;
64
65
  export declare function assertAgentNewGamePlaytestContract(task: AppTask): void;
66
+ export declare function assertAgentNewGameListingIdentity(task: AppTask): void;
65
67
  export declare function assertWorkerAppLocalUploadPreflight(input: {
66
68
  task: AppTask;
67
69
  kind: 'NEW_GAME' | 'REMIX_GAME' | 'GAME_UPDATE';
68
- executionTarget?: AgentExecutionTarget;
69
- stage?: 'PRE_CAPTURE' | 'COMPLETE';
70
- expectedRuntimeBundleHash?: string;
71
70
  }): Promise<void>;
72
71
  export type WorkerAppPreflightResult = {
73
72
  appName: string;
@@ -5,6 +5,7 @@ exports.resolveDefaultUploadCreator = resolveDefaultUploadCreator;
5
5
  exports.upload = upload;
6
6
  exports.assertAgentPrimarySurfaceContract = assertAgentPrimarySurfaceContract;
7
7
  exports.assertAgentNewGamePlaytestContract = assertAgentNewGamePlaytestContract;
8
+ exports.assertAgentNewGameListingIdentity = assertAgentNewGameListingIdentity;
8
9
  exports.assertWorkerAppLocalUploadPreflight = assertWorkerAppLocalUploadPreflight;
9
10
  exports.preflightWorkerAppProject = preflightWorkerAppProject;
10
11
  exports.publishWorkerAppProject = publishWorkerAppProject;
@@ -28,7 +29,6 @@ const workerAppProject_1 = require("../workerAppProject");
28
29
  const upload_content_1 = require("./upload-content");
29
30
  const appUrls_1 = require("../appUrls");
30
31
  const ownedRuntimeImageValidation_1 = require("./ownedRuntimeImageValidation");
31
- const listingPreflight_1 = require("../listingPreflight");
32
32
  const upload_graph_1 = require("./upload-graph");
33
33
  function normalizeSavedItemKinds(kinds) {
34
34
  return [...new Set(Array.isArray(kinds) ? kinds : [])].sort();
@@ -841,7 +841,6 @@ function appendTaskRelations(graphState, fromNodeId, relations, contextLabel) {
841
841
  });
842
842
  }
843
843
  async function uploadAppTask(state, task, taskCreator, options) {
844
- (0, listingPreflight_1.assertProjectListingPreflight)(task);
845
844
  const { upload, warnings } = await (0, apps_1.runAppPipeline)(state.client, task, {
846
845
  skipReview: options?.skipReview,
847
846
  clearTags: options?.clearTags,
@@ -1266,6 +1265,18 @@ function assertAgentNewGamePlaytestContract(task) {
1266
1265
  }
1267
1266
  }
1268
1267
  }
1268
+ function assertAgentNewGameListingIdentity(task) {
1269
+ const missing = [];
1270
+ if (!task.listing?.iconPath)
1271
+ missing.push('listing.icon');
1272
+ if (!task.listing?.heroPortraitPath)
1273
+ missing.push('listing.hero.portrait');
1274
+ if (!task.listing?.heroLandscapePath)
1275
+ missing.push('listing.hero.landscape');
1276
+ if (missing.length > 0) {
1277
+ throw new Error(`agent_task_listing_identity_required: NEW_GAME catalogue.json must provide ${missing.join(', ')}.`);
1278
+ }
1279
+ }
1269
1280
  async function collectUploadPreflightErrors(checks) {
1270
1281
  const errors = [];
1271
1282
  for (const check of checks) {
@@ -1291,42 +1302,15 @@ function assertAgentTaskReleaseNotes(task) {
1291
1302
  }
1292
1303
  }
1293
1304
  async function assertWorkerAppLocalUploadPreflight(input) {
1294
- const complete = input.stage !== 'PRE_CAPTURE';
1295
- const existingCaptureReport = input.task.listing?.captureReportPath;
1296
- const shouldCheckExistingCapture = !complete
1297
- && Boolean(existingCaptureReport)
1298
- && (0, node_fs_1.existsSync)(existingCaptureReport);
1299
1305
  const errors = await collectUploadPreflightErrors([
1300
1306
  () => assertAgentTaskReleaseNotes(input.task),
1301
- ...(complete
1307
+ ...(input.kind === 'NEW_GAME'
1302
1308
  ? [
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),
1309
+ () => assertAgentNewGamePlaytestContract(input.task),
1310
+ () => assertAgentNewGameListingIdentity(input.task),
1307
1311
  ]
1308
- : []),
1309
- ...(input.kind === 'NEW_GAME'
1310
- ? [() => assertAgentNewGamePlaytestContract(input.task)]
1311
1312
  : [() => assertAgentPrimarySurfaceContract(input.task, input.kind)]),
1312
1313
  () => (0, ownedRuntimeImageValidation_1.assertOwnedRuntimeImagesAreValid)(input.task),
1313
- ...(complete
1314
- ? [
1315
- () => (0, listingPreflight_1.assertAgentGameScreenshotListingPreflight)(input.task),
1316
- () => (0, listingPreflight_1.assertAgentGameCaptureReportPreflight)(input.task, {
1317
- requireCaptureReport: input.executionTarget === 'FIRST_PARTY',
1318
- expectedRuntimeBundleHash: input.expectedRuntimeBundleHash,
1319
- }),
1320
- ]
1321
- : []),
1322
- ...(shouldCheckExistingCapture
1323
- ? [
1324
- () => (0, listingPreflight_1.assertAgentGameCaptureReportPreflight)(input.task, {
1325
- requireCaptureReport: false,
1326
- expectedRuntimeBundleHash: input.expectedRuntimeBundleHash,
1327
- }),
1328
- ]
1329
- : []),
1330
1314
  ]);
1331
1315
  if (errors.length === 1) {
1332
1316
  throw new Error(errors[0]);
@@ -1335,7 +1319,7 @@ async function assertWorkerAppLocalUploadPreflight(input) {
1335
1319
  throw new Error(`agent_task_upload_preflight_failed:\n${errors.map((error, index) => `${index + 1}. ${error}`).join('\n')}`);
1336
1320
  }
1337
1321
  }
1338
- async function prepareWorkerAppProject(input, stage) {
1322
+ async function prepareWorkerAppProject(input) {
1339
1323
  const creatorUsername = input.creatorUsername.trim();
1340
1324
  if (!creatorUsername) {
1341
1325
  throw new Error('agent_task_creator_username_missing');
@@ -1390,9 +1374,6 @@ async function prepareWorkerAppProject(input, stage) {
1390
1374
  await assertWorkerAppLocalUploadPreflight({
1391
1375
  task,
1392
1376
  kind: input.kind,
1393
- executionTarget: input.executionTarget,
1394
- stage,
1395
- expectedRuntimeBundleHash: artifacts?.bundleHash,
1396
1377
  });
1397
1378
  return {
1398
1379
  task,
@@ -1401,11 +1382,12 @@ async function prepareWorkerAppProject(input, stage) {
1401
1382
  creatorUsername,
1402
1383
  };
1403
1384
  }
1404
- // Runs every worker upload check that can be proven before final recorder
1405
- // output exists. It intentionally performs no app registration, metadata
1385
+ // Runs every worker upload check without publishing. It intentionally performs
1386
+ // no app registration, metadata
1406
1387
  // update, upload-session creation, file upload, or version creation.
1407
1388
  async function preflightWorkerAppProject(input) {
1408
- const prepared = await prepareWorkerAppProject(input, 'PRE_CAPTURE');
1389
+ const prepared = await prepareWorkerAppProject(input);
1390
+ await (0, apps_1.prepareAppUploadFiles)(prepared.task, prepared.artifacts);
1409
1391
  return {
1410
1392
  appName: prepared.task.name,
1411
1393
  creatorUsername: prepared.creatorUsername,
@@ -1416,7 +1398,7 @@ async function preflightWorkerAppProject(input) {
1416
1398
  // "playdrop project publish", but always as one PRIVATE draft version for the
1417
1399
  // task's creator, with browser launch checks on before the version is created.
1418
1400
  async function publishWorkerAppProject(input) {
1419
- const prepared = await prepareWorkerAppProject(input, 'COMPLETE');
1401
+ const prepared = await prepareWorkerAppProject(input);
1420
1402
  const { task, artifacts, creatorUsername } = prepared;
1421
1403
  const { app } = await (0, registration_1.ensureRegisteredHostedAppShell)({
1422
1404
  client: input.client,
@@ -1446,7 +1428,6 @@ async function publishWorkerAppProject(input) {
1446
1428
  runStagedUploadLoadCheck: true,
1447
1429
  ensureRegisteredAppShell: true,
1448
1430
  agentTaskId: input.taskId,
1449
- mediaCaptureRequired: input.executionTarget === 'FIRST_PARTY',
1450
1431
  playtestSmokeCheckRequired: input.kind === 'NEW_GAME',
1451
1432
  captureSession,
1452
1433
  ...(artifacts ? { artifacts } : {}),
@@ -1536,8 +1517,8 @@ async function publishStaticHtmlProject(input) {
1536
1517
  runStagedUploadLoadCheck: true,
1537
1518
  ensureRegisteredAppShell: true,
1538
1519
  agentTaskId: input.taskId,
1539
- mediaCaptureRequired: false,
1540
1520
  playtestSmokeCheckRequired: false,
1521
+ skipReview: input.skipReview,
1541
1522
  captureSession,
1542
1523
  artifacts: prepared.artifacts,
1543
1524
  });
@@ -212,12 +212,8 @@ async function validate(pathOrName, options = {}) {
212
212
  await (0, upload_1.assertWorkerAppLocalUploadPreflight)({
213
213
  task,
214
214
  kind: workerContext.kind,
215
- executionTarget: workerContext.target,
216
215
  });
217
216
  }
218
- else {
219
- (0, listingPreflight_1.assertProjectListingPreflight)(task);
220
- }
221
217
  await (0, apps_1.validateAppTask)(task);
222
218
  (0, apps_1.collectAppValidationWarnings)(task, 'source').forEach((warning) => warnings.add(warning));
223
219
  }
@@ -1,11 +1,16 @@
1
1
  import type { ApiClient } from '@playdrop/api-client';
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';
2
+ import { RELEASE_SMOKE_FILE_NAME, 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
5
  import { type WorkerInstrumentSurface, type LoggedProcessResult, type LoggedProcessTranscriptChunk } from './worker/runtime';
6
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';
7
7
  export type { AgentTokenUsage, LoggedProcessResult, LoggedProcessTranscriptChunk } from './worker/runtime';
8
8
  export declare function usesCreatorRecovery(kind: AgentTaskKind): boolean;
9
+ export declare function buildDeterministicReleaseSmokeArtifact(): {
10
+ fileName: typeof RELEASE_SMOKE_FILE_NAME;
11
+ content: Buffer;
12
+ sha256: string;
13
+ };
9
14
  export declare function buildCreatorRecoveryPrompt(input: {
10
15
  prompt: string;
11
16
  kind: AgentTaskKind;
@@ -204,6 +209,7 @@ export declare function prepareClaimedTaskWorkspace(input: {
204
209
  }): Promise<{
205
210
  resumed: boolean;
206
211
  }>;
212
+ export declare function buildWorkerTaskContextV2(assignment: WorkerTaskAssignmentV2): Omit<WorkerTaskContextFile, 'env' | 'devPort'>;
207
213
  export declare function resolveWorkerInstrumentSurface(taskContext: Pick<WorkerTaskContextFile, 'kind' | 'metadata'>): WorkerInstrumentSurface | null;
208
214
  export declare function hasAgentTaskUploadedArtifact(task: Pick<AgentTaskResponse, 'appId' | 'appVersionId'>): boolean;
209
215
  export declare function appendWorkerTranscriptChunks(input: {
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.WORKER_TASK_WORKSPACE_RETENTION = exports.WORKER_CONTEXT_COMMAND_NOT_ALLOWED_MESSAGE = exports.WORKER_SESSION_EXPIRED_MESSAGE = exports.runLoggedProcess = exports.readEnvBoolean = exports.readCodexSandboxMode = exports.readCodexRolloutTokenUsageForThread = exports.extractCodexTokensUsed = exports.extractCodexThreadId = exports.extractClaudeSessionId = exports.extractCodexRolloutTokenUsage = exports.extractAgentTokenUsage = exports.DEFAULT_WORKER_TOKEN_CAP = exports.DEFAULT_CREATOR_UPLOAD_RECOVERY_TIMEOUT_MS = exports.DEFAULT_CREATOR_AGENT_TIMEOUT_MS = exports.DEFAULT_CODEX_TIMEOUT_MS = exports.buildWorkerChildEnv = exports.buildPlaywrightMcpCommand = exports.buildCodexExecArgs = exports.buildCleanRoomChildEnv = exports.buildClaudePermissionSettings = exports.buildClaudeExecArgs = exports.assertWorkerTokenUsageWithinCap = void 0;
7
7
  exports.usesCreatorRecovery = usesCreatorRecovery;
8
+ exports.buildDeterministicReleaseSmokeArtifact = buildDeterministicReleaseSmokeArtifact;
8
9
  exports.buildCreatorRecoveryPrompt = buildCreatorRecoveryPrompt;
9
10
  exports.buildGameEvalRetryPrompt = buildGameEvalRetryPrompt;
10
11
  exports.resolveInitialCreatorProgress = resolveInitialCreatorProgress;
@@ -17,6 +18,7 @@ exports.claimBackoffDelayMs = claimBackoffDelayMs;
17
18
  exports.resolveWorkerHomeDir = resolveWorkerHomeDir;
18
19
  exports.pruneWorkerTaskWorkspaces = pruneWorkerTaskWorkspaces;
19
20
  exports.prepareClaimedTaskWorkspace = prepareClaimedTaskWorkspace;
21
+ exports.buildWorkerTaskContextV2 = buildWorkerTaskContextV2;
20
22
  exports.resolveWorkerInstrumentSurface = resolveWorkerInstrumentSurface;
21
23
  exports.hasAgentTaskUploadedArtifact = hasAgentTaskUploadedArtifact;
22
24
  exports.appendWorkerTranscriptChunks = appendWorkerTranscriptChunks;
@@ -111,7 +113,6 @@ const commandContext_1 = require("../commandContext");
111
113
  const config_1 = require("../config");
112
114
  const messages_1 = require("../messages");
113
115
  const output_1 = require("../output");
114
- const recorderRelease_1 = require("../recorderRelease");
115
116
  const shellProbe_1 = require("../shellProbe");
116
117
  const review_1 = require("./review");
117
118
  const upload_1 = require("./upload");
@@ -152,7 +153,7 @@ const FAIL_REPORT_RETRY_DELAY_MS = 2000;
152
153
  const WORKER_TRANSCRIPT_CHUNK_BATCH_SIZE = 100;
153
154
  const SLACK_API_BASE_URL = 'https://slack.com/api';
154
155
  const WORKER_WRAPPER_CONTRACT_VERSION = 3;
155
- const WORKER_SUPPORTED_KINDS = ['NEW_GAME', 'STATIC_GAME', 'REMIX_GAME', 'GAME_UPDATE', 'GAME_REVIEW', 'GAME_EVAL'];
156
+ const WORKER_SUPPORTED_KINDS = ['NEW_GAME', 'STATIC_GAME', 'REMIX_GAME', 'GAME_UPDATE', 'GAME_REVIEW', 'GAME_EVAL', 'RELEASE_SMOKE'];
156
157
  const GAME_CREATION_TASK_KINDS = new Set(['NEW_GAME', 'STATIC_GAME', 'REMIX_GAME', 'GAME_UPDATE']);
157
158
  const AGENT_MANAGED_UPLOAD_TASK_KINDS = new Set(['NEW_GAME', 'REMIX_GAME', 'GAME_UPDATE']);
158
159
  const requireFromWorker = (0, node_module_1.createRequire)(__filename);
@@ -168,6 +169,14 @@ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
168
169
  function usesCreatorRecovery(kind) {
169
170
  return GAME_CREATION_TASK_KINDS.has(kind);
170
171
  }
172
+ function buildDeterministicReleaseSmokeArtifact() {
173
+ const content = Buffer.from(types_1.RELEASE_SMOKE_HTML, 'utf8');
174
+ return {
175
+ fileName: types_1.RELEASE_SMOKE_FILE_NAME,
176
+ content,
177
+ sha256: (0, node_crypto_1.createHash)('sha256').update(content).digest('hex'),
178
+ };
179
+ }
171
180
  function buildCreatorRecoveryPrompt(input) {
172
181
  const prompt = input.prompt.trim();
173
182
  if (!prompt) {
@@ -610,7 +619,7 @@ function buildWorkerTaskContext(input) {
610
619
  }
611
620
  const metadata = playdrop;
612
621
  const kind = metadata.kind;
613
- if (kind !== 'NEW_GAME' && kind !== 'STATIC_GAME' && kind !== 'REMIX_GAME' && kind !== 'GAME_UPDATE' && kind !== 'GAME_REVIEW' && kind !== 'GAME_EVAL') {
622
+ if (kind !== 'NEW_GAME' && kind !== 'STATIC_GAME' && kind !== 'REMIX_GAME' && kind !== 'GAME_UPDATE' && kind !== 'GAME_REVIEW' && kind !== 'GAME_EVAL' && kind !== 'RELEASE_SMOKE') {
614
623
  throw new Error('agent_task_context_kind_missing');
615
624
  }
616
625
  const creatorUsername = typeof metadata.creatorUsername === 'string'
@@ -3004,45 +3013,6 @@ function collectWorkerInfrastructureFailures() {
3004
3013
  catch (error) {
3005
3014
  failures.push(errorCode(error));
3006
3015
  }
3007
- if (node_process_1.default.platform !== 'darwin') {
3008
- return failures;
3009
- }
3010
- if (node_process_1.default.arch !== recorderRelease_1.MAC_WORKER_RECORDER_RELEASE.architecture) {
3011
- failures.push('worker_preflight_recorder_architecture_unsupported');
3012
- return failures;
3013
- }
3014
- if (!workerCommandExists('ffmpeg')) {
3015
- failures.push('worker_preflight_ffmpeg_missing');
3016
- }
3017
- if (!workerCommandExists('ffprobe')) {
3018
- failures.push('worker_preflight_ffprobe_missing');
3019
- }
3020
- const guiSession = (0, shellProbe_1.runShell)('/bin/launchctl print "gui/$(/usr/bin/id -u)"');
3021
- if (guiSession.exitCode !== 0) {
3022
- failures.push('worker_preflight_gui_session_missing');
3023
- }
3024
- const recorderPath = (0, recorderRelease_1.resolveMacWorkerRecorderPath)();
3025
- if (!(0, node_fs_1.existsSync)(recorderPath)) {
3026
- failures.push('worker_preflight_recorder_missing');
3027
- return failures;
3028
- }
3029
- const recorderStat = (0, node_fs_1.statSync)(recorderPath);
3030
- if (!recorderStat.isFile() || (recorderStat.mode & 0o111) === 0) {
3031
- failures.push('worker_preflight_recorder_not_executable');
3032
- return failures;
3033
- }
3034
- const installedSha256 = (0, recorderRelease_1.recorderSha256)((0, node_fs_1.readFileSync)(recorderPath));
3035
- if (installedSha256 !== recorderRelease_1.MAC_WORKER_RECORDER_RELEASE.sha256) {
3036
- failures.push('worker_preflight_recorder_hash_mismatch');
3037
- return failures;
3038
- }
3039
- const permission = (0, node_child_process_1.spawnSync)(recorderPath, ['--check-permission'], {
3040
- encoding: 'utf8',
3041
- timeout: 10000,
3042
- });
3043
- if (permission.status !== 0) {
3044
- failures.push('worker_preflight_screen_recording_permission_missing');
3045
- }
3046
3016
  return failures;
3047
3017
  }
3048
3018
  function describeWorkerPreflightReason(reason) {
@@ -4218,20 +4188,6 @@ async function startWorker(options = {}) {
4218
4188
  try {
4219
4189
  await removeStaleAgentRunCredentials();
4220
4190
  const startupFailures = [];
4221
- if (node_process_1.default.platform === 'darwin') {
4222
- try {
4223
- const recorder = await (0, recorderRelease_1.ensureMacWorkerRecorderInstalled)();
4224
- if (recorder.downloaded) {
4225
- console.log(`Installed PlayDrop macOS recorder ${recorderRelease_1.MAC_WORKER_RECORDER_RELEASE.version} at ${recorder.path}.`);
4226
- }
4227
- }
4228
- catch (error) {
4229
- startupFailures.push(`worker_preflight_recorder_download_failed:${errorMessage(error)}`);
4230
- }
4231
- }
4232
- else if (node_process_1.default.platform === 'win32') {
4233
- console.warn('Native listing recording is not available on Windows yet.');
4234
- }
4235
4191
  const local = collectWorkerLocalStatus();
4236
4192
  startupFailures.push(...local.degradedReasons);
4237
4193
  if (startupFailures.length > 0 || !local.ready || !local.capabilities || !local.playwright?.chromiumInstalled) {
@@ -4548,6 +4504,55 @@ async function startWorker(options = {}) {
4548
4504
  })
4549
4505
  : null;
4550
4506
  availableSkillPaths = listPluginSkillPaths(assignmentPluginRoot);
4507
+ if (task.kind === 'RELEASE_SMOKE') {
4508
+ if (!assignment.plugin || !assignmentPluginRoot) {
4509
+ throw new Error('release_smoke_bundle_missing');
4510
+ }
4511
+ const artifact = buildDeterministicReleaseSmokeArtifact();
4512
+ const artifactPath = node_path_1.default.join(agentWorkspaceDir, artifact.fileName);
4513
+ await (0, promises_1.writeFile)(artifactPath, artifact.content);
4514
+ const storedArtifact = (0, node_fs_1.readFileSync)(artifactPath);
4515
+ if ((0, node_crypto_1.createHash)('sha256').update(storedArtifact).digest('hex') !== artifact.sha256) {
4516
+ throw new Error('release_smoke_workspace_verification_failed');
4517
+ }
4518
+ if (!envConfig.cdnBase) {
4519
+ throw new Error('release_smoke_publish_cdn_base_missing');
4520
+ }
4521
+ const taskClient = (0, apiClient_1.createCliApiClient)({
4522
+ baseUrl: envConfig.apiBase,
4523
+ token,
4524
+ onBehalfCreatorUsername: taskContext.creatorUsername,
4525
+ agentTaskToken: assignment.task.token,
4526
+ agentTaskId: task.id,
4527
+ agentTaskAttempt: taskContext.attempt,
4528
+ });
4529
+ const published = await (0, upload_1.publishStaticHtmlProject)({
4530
+ client: taskClient,
4531
+ taskId: task.id,
4532
+ taskToken: assignment.task.token,
4533
+ projectDir: agentWorkspaceDir,
4534
+ creatorUsername: taskContext.creatorUsername,
4535
+ primarySurface: 'DESKTOP',
4536
+ apiBase: envConfig.apiBase,
4537
+ webBase: envConfig.webBase ?? null,
4538
+ cdnBase: envConfig.cdnBase,
4539
+ devRouterPort: devPort,
4540
+ token,
4541
+ user: me.user,
4542
+ skipReview: true,
4543
+ });
4544
+ await client.workerCompleteReleaseSmoke(task.id, {
4545
+ workerKey,
4546
+ leaseToken,
4547
+ appId: published.appId,
4548
+ appVersionId: published.appVersionId,
4549
+ sourceSha256: published.sourceHash,
4550
+ runtimeSha256: published.runtimeHash,
4551
+ });
4552
+ retainWorkspace = false;
4553
+ console.log(`Worker release smoke ${task.id} completed without an AI agent.`);
4554
+ return;
4555
+ }
4551
4556
  if (cleanRoom) {
4552
4557
  const playdropMetadata = assignment.task.metadata.playdrop;
4553
4558
  const postAgentAction = playdropMetadata && typeof playdropMetadata === 'object' && !Array.isArray(playdropMetadata)
@@ -5538,6 +5543,9 @@ async function uploadTask(options = {}) {
5538
5543
  if (taskContext.kind === 'GAME_REVIEW' || taskContext.kind === 'GAME_EVAL') {
5539
5544
  throw new Error('task_upload_not_allowed_for_game_review');
5540
5545
  }
5546
+ if (taskContext.kind === 'RELEASE_SMOKE') {
5547
+ throw new Error('task_upload_not_allowed_for_release_smoke');
5548
+ }
5541
5549
  const uploadKind = taskContext.kind;
5542
5550
  const workspaceDir = resolveTaskWorkspaceDir();
5543
5551
  if (options.preflightOnly) {
@@ -5567,7 +5575,7 @@ async function uploadTask(options = {}) {
5567
5575
  for (const warning of result.warnings) {
5568
5576
  console.error(`Preflight warning: ${warning}`);
5569
5577
  }
5570
- (0, output_1.printSuccess)(`Task upload preflight passed for ${result.creatorUsername}/${result.appName}. Final capture can start.`);
5578
+ (0, output_1.printSuccess)(`Task upload preflight passed for ${result.creatorUsername}/${result.appName}. Upload can start.`);
5571
5579
  return;
5572
5580
  }
5573
5581
  if ((0, node_fs_1.existsSync)(workerTaskUploadResultPath(workspaceDir))) {
package/dist/index.js CHANGED
@@ -273,7 +273,7 @@ task
273
273
  task
274
274
  .command('upload')
275
275
  .description('Upload the active agent task project as a private draft')
276
- .option('--preflight-only', 'Run non-mutating upload checks before final capture')
276
+ .option('--preflight-only', 'Run non-mutating checks without uploading')
277
277
  .option('--env <env>', 'Environment override')
278
278
  .action(async (opts) => {
279
279
  await (0, worker_1.uploadTask)(opts);
@@ -1,35 +1,12 @@
1
- import type { RecordAppUploadMediaCaptureRequest } from '@playdrop/types';
2
- import type { AppTask } from './catalogue';
3
- export type ListingMediaFile = {
4
- fileKey: string;
5
- sha256: string;
6
- };
7
1
  export type WorkerUploadPreflightContext = {
8
2
  kind: 'NEW_GAME' | 'REMIX_GAME' | 'GAME_UPDATE';
9
3
  target: 'FIRST_PARTY' | 'PERSONAL';
10
4
  creatorUsername: string;
11
5
  creatorRequest: string;
6
+ outputVersion?: string;
12
7
  tweaks?: {
13
8
  id: string;
14
9
  values: Record<string, string | number | boolean>;
15
10
  };
16
11
  };
17
- export declare function readMediaCaptureProof(input: {
18
- task: AppTask;
19
- preparedSessionFiles: ListingMediaFile[];
20
- expectedRuntimeBundleHash?: string;
21
- }): RecordAppUploadMediaCaptureRequest;
22
12
  export declare function readWorkerUploadPreflightContext(projectDir: string): WorkerUploadPreflightContext | null;
23
- export declare function assertAgentGameHeroListingPreflight(task: AppTask): void;
24
- export declare function assertAgentGameIconListingPreflight(task: AppTask): void;
25
- export declare function assertAgentGameVideoListingPreflight(task: AppTask): void;
26
- export declare function assertAgentGamePreviewListingPreflight(task: AppTask): void;
27
- export declare function assertAgentGameScreenshotListingPreflight(task: AppTask): void;
28
- export declare function assertAgentGameCaptureReportPreflight(task: AppTask, options: {
29
- requireCaptureReport: boolean;
30
- expectedRuntimeBundleHash?: string;
31
- }): void;
32
- export declare function assertAgentGameListingPreflight(task: AppTask, options: {
33
- requireCaptureReport: boolean;
34
- }): void;
35
- export declare function assertProjectListingPreflight(task: AppTask): void;