@playdrop/playdrop-cli 0.15.5 → 0.15.9

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 (74) hide show
  1. package/config/client-meta.json +2 -2
  2. package/dist/apps/index.d.ts +0 -1
  3. package/dist/apps/index.js +0 -1
  4. package/dist/apps/loadCheck.d.ts +2 -1
  5. package/dist/apps/loadCheck.js +15 -7
  6. package/dist/apps/staticHtml.js +1 -0
  7. package/dist/apps/upload.d.ts +0 -1
  8. package/dist/apps/upload.js +19 -12
  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 +22 -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 +29 -1
  24. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  25. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +4 -1
  26. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
  27. package/node_modules/@playdrop/api-client/dist/domains/admin.js +31 -2
  28. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts +2 -1
  29. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts.map +1 -1
  30. package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.js +12 -0
  31. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +9 -1
  32. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
  33. package/node_modules/@playdrop/api-client/dist/domains/apps.js +66 -0
  34. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts +1 -0
  35. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts.map +1 -1
  36. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.js +3 -0
  37. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts +1 -0
  38. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts.map +1 -1
  39. package/node_modules/@playdrop/api-client/dist/domains/assets.js +3 -0
  40. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts +15 -1
  41. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -1
  42. package/node_modules/@playdrop/api-client/dist/domains/payments.js +58 -0
  43. package/node_modules/@playdrop/api-client/dist/index.d.ts +30 -1
  44. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  45. package/node_modules/@playdrop/api-client/dist/index.js +65 -0
  46. package/node_modules/@playdrop/config/client-meta.json +2 -2
  47. package/node_modules/@playdrop/config/dist/src/creator-plans.d.ts +18 -0
  48. package/node_modules/@playdrop/config/dist/src/creator-plans.d.ts.map +1 -0
  49. package/node_modules/@playdrop/config/dist/src/creator-plans.js +117 -0
  50. package/node_modules/@playdrop/config/dist/src/game-arena.d.ts +1 -1
  51. package/node_modules/@playdrop/config/dist/src/game-arena.js +1 -1
  52. package/node_modules/@playdrop/config/dist/src/index.d.ts +1 -0
  53. package/node_modules/@playdrop/config/dist/src/index.d.ts.map +1 -1
  54. package/node_modules/@playdrop/config/dist/src/index.js +1 -0
  55. package/node_modules/@playdrop/config/dist/test/creator-plans.test.d.ts +2 -0
  56. package/node_modules/@playdrop/config/dist/test/creator-plans.test.d.ts.map +1 -0
  57. package/node_modules/@playdrop/config/dist/test/creator-plans.test.js +28 -0
  58. package/node_modules/@playdrop/config/dist/test/game-arena.test.js +3 -3
  59. package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
  60. package/node_modules/@playdrop/types/dist/api.d.ts +133 -14
  61. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  62. package/node_modules/@playdrop/types/dist/api.js +20 -2
  63. package/node_modules/@playdrop/types/dist/app-review.d.ts +2 -0
  64. package/node_modules/@playdrop/types/dist/app-review.d.ts.map +1 -1
  65. package/node_modules/@playdrop/types/dist/app-review.js +31 -2
  66. package/node_modules/@playdrop/types/dist/index.d.ts +1 -0
  67. package/node_modules/@playdrop/types/dist/index.d.ts.map +1 -1
  68. package/node_modules/@playdrop/types/dist/index.js +3 -1
  69. package/node_modules/@playdrop/types/dist/release-smoke.d.ts +3 -0
  70. package/node_modules/@playdrop/types/dist/release-smoke.d.ts.map +1 -0
  71. package/node_modules/@playdrop/types/dist/release-smoke.js +35 -0
  72. package/node_modules/@playdrop/types/dist/version.d.ts +5 -0
  73. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  74. package/package.json +1 -1
@@ -1,7 +1,7 @@
1
1
  {
2
- "version": "0.15.5",
2
+ "version": "0.15.9",
3
3
  "build": 1,
4
- "runtimeSdkVersion": "0.15.5",
4
+ "runtimeSdkVersion": "0.15.9",
5
5
  "runtimeSdkBuild": 1,
6
6
  "clients": {
7
7
  "all": {
@@ -25,7 +25,6 @@ export type AppPipelineOptions = {
25
25
  ensureRegisteredAppShell?: boolean;
26
26
  agentTaskId?: number;
27
27
  playtestProof?: AgentTaskPlaytestProof;
28
- mediaCaptureRequired?: boolean;
29
28
  playtestSmokeCheckRequired?: boolean;
30
29
  captureSession?: TaskCaptureSession | null;
31
30
  artifacts?: AppBuildArtifacts;
@@ -66,7 +66,6 @@ async function runAppPipeline(client, task, options) {
66
66
  loadCheckTimeoutMs: options?.loadCheckTimeoutMs,
67
67
  agentTaskId: options?.agentTaskId,
68
68
  playtestProof: options?.playtestProof,
69
- mediaCaptureRequired: options?.mediaCaptureRequired,
70
69
  playtestSmokeCheckRequired: options?.playtestSmokeCheckRequired,
71
70
  captureSession: options?.captureSession,
72
71
  };
@@ -38,7 +38,7 @@ export declare function isSoftwareWebGlRenderer(renderer: string | null | undefi
38
38
  export declare function dispatchPlaytestTapeToFrame(page: Page, frame: Frame, surface: AppSurface, tape: AppPlaytestTape, hooks?: {
39
39
  onBeforeEvent?: (eventIndex: number, event: AppPlaytestTapeEvent) => void;
40
40
  }): Promise<void>;
41
- export declare function formatHostedLoadCheckFailure(taskName: string, result: HostedLoadCheckResult, scope?: 'local' | 'staged' | 'final'): string;
41
+ export declare function formatHostedLoadCheckFailure(taskName: string, result: HostedLoadCheckResult, scope?: 'local' | 'staged' | 'editor' | 'final'): string;
42
42
  export declare function redactHostedLoadCheckSecrets(text: string, sourceUrl: string): string;
43
43
  export declare function runLocalHostedLoadCheck(input: {
44
44
  client: ApiClient;
@@ -71,6 +71,7 @@ export declare function runUploadedHostedLoadCheck(input: {
71
71
  gameFrameScreenshotPath?: string | null;
72
72
  playtestTape?: AppPlaytestTape;
73
73
  postReadyWaitMs?: number;
74
+ phase?: 'play' | 'editor';
74
75
  }): Promise<HostedLoadCheckResult>;
75
76
  export declare function comparePlaytestFrames(firstPath: string, secondPath: string, thresholds?: {
76
77
  meanDeltaMin: number;
@@ -489,9 +489,11 @@ function normalizeLoadCheckFailure(error, artifactFingerprint) {
489
489
  function formatHostedLoadCheckFailure(taskName, result, scope = 'local') {
490
490
  const scopeLabel = scope === 'final'
491
491
  ? 'final owner play route'
492
- : scope === 'staged'
493
- ? 'staged upload preview'
494
- : 'local hosted preview';
492
+ : scope === 'editor'
493
+ ? 'staged Editor phase'
494
+ : scope === 'staged'
495
+ ? 'staged upload preview'
496
+ : 'local hosted preview';
495
497
  const errorCode = result.errorCode?.trim() || 'startup_failure';
496
498
  const message = normalizeLoadCheckMessage(result.message);
497
499
  return `hosted_load_check_failed: ${taskName} failed the ${scopeLabel} check (${errorCode}). ${message}`;
@@ -917,6 +919,12 @@ function appendLoadCheckParams(targetUrl, devAuth) {
917
919
  }
918
920
  async function runUploadedHostedLoadCheck(input) {
919
921
  const preview = await input.client.fetchAppUploadLaunchCheckPreview(input.creatorUsername, input.task.name, input.sessionId);
922
+ const withRequestedPhase = (rawUrl) => {
923
+ const url = new URL(rawUrl);
924
+ if (input.phase === 'editor')
925
+ url.searchParams.set('hostPhase', 'editor');
926
+ return url.toString();
927
+ };
920
928
  const captureSurface = resolveLoadCheckSurface(preview.localAppMetadata.surfaceTargets, input.task.primarySurface);
921
929
  const contextOptions = cloneLoadCheckContextOptions(captureSurface);
922
930
  const anonymousExpectedState = resolveAnonymousHostedLaunchState({
@@ -925,7 +933,7 @@ async function runUploadedHostedLoadCheck(input) {
925
933
  });
926
934
  let result;
927
935
  if (input.captureSession) {
928
- const targetUrl = new URL(preview.launchUrl);
936
+ const targetUrl = new URL(withRequestedPhase(preview.launchUrl));
929
937
  (0, appUrls_1.appendTaskCaptureSession)(targetUrl, input.captureSession);
930
938
  result = await runHostedLoadCheck({
931
939
  targetUrl: targetUrl.toString(),
@@ -947,7 +955,7 @@ async function runUploadedHostedLoadCheck(input) {
947
955
  }
948
956
  else {
949
957
  const anonymousGateResult = await runHostedLoadCheck({
950
- targetUrl: appendLoadCheckParams(preview.launchUrl, 'anonymous'),
958
+ targetUrl: withRequestedPhase(appendLoadCheckParams(preview.launchUrl, 'anonymous')),
951
959
  artifactFingerprint: preview.session.manifestHash,
952
960
  timeoutMs: input.timeoutMs,
953
961
  expectedHostedLaunchState: anonymousExpectedState,
@@ -958,7 +966,7 @@ async function runUploadedHostedLoadCheck(input) {
958
966
  }
959
967
  else {
960
968
  result = await runHostedLoadCheck({
961
- targetUrl: appendLoadCheckParams(preview.launchUrl, 'viewer'),
969
+ targetUrl: withRequestedPhase(appendLoadCheckParams(preview.launchUrl, 'viewer')),
962
970
  artifactFingerprint: preview.session.manifestHash,
963
971
  timeoutMs: input.timeoutMs,
964
972
  expectedHostedLaunchState: resolvePostAuthHostedLaunchState(preview.localAppMetadata.controllerMode),
@@ -978,7 +986,7 @@ async function runUploadedHostedLoadCheck(input) {
978
986
  }
979
987
  else {
980
988
  result = await runHostedLoadCheck({
981
- targetUrl: appendLoadCheckParams(preview.launchUrl, 'anonymous'),
989
+ targetUrl: withRequestedPhase(appendLoadCheckParams(preview.launchUrl, 'anonymous')),
982
990
  artifactFingerprint: preview.session.manifestHash,
983
991
  timeoutMs: input.timeoutMs,
984
992
  expectedHostedLaunchState: anonymousExpectedState,
@@ -257,6 +257,7 @@ function buildStaticAppTask(input) {
257
257
  hostingMode: 'HOSTED',
258
258
  authMode: 'NONE',
259
259
  previewable: true,
260
+ editorSupported: false,
260
261
  ...(buildListing(input.metadata.imagePath, input.primarySurface)
261
262
  ? { listing: buildListing(input.metadata.imagePath, input.primarySurface) }
262
263
  : {}),
@@ -22,7 +22,6 @@ export type AppUploadOptions = {
22
22
  loadCheckTimeoutMs?: number;
23
23
  agentTaskId?: number;
24
24
  playtestProof?: AgentTaskPlaytestProof;
25
- mediaCaptureRequired?: boolean;
26
25
  playtestSmokeCheckRequired?: boolean;
27
26
  captureSession?: TaskCaptureSession | null;
28
27
  };
@@ -8,7 +8,6 @@ const node_fs_1 = require("node:fs");
8
8
  const node_path_1 = require("node:path");
9
9
  const http_1 = require("../http");
10
10
  const loadCheck_1 = require("./loadCheck");
11
- const listingPreflight_1 = require("../listingPreflight");
12
11
  const EXTENSION_TO_MIME = {
13
12
  '.png': 'image/png',
14
13
  '.jpg': 'image/jpeg',
@@ -259,6 +258,7 @@ async function uploadAppVersion(client, task, artifacts, options) {
259
258
  authMode: task.authMode,
260
259
  controllerMode: task.controllerMode,
261
260
  previewable: task.previewable,
261
+ editorSupported: task.editorSupported,
262
262
  skipReview: options?.skipReview,
263
263
  displayName: task.displayName,
264
264
  subtitle: task.subtitle,
@@ -346,15 +346,6 @@ async function uploadAppVersion(client, task, artifacts, options) {
346
346
  })),
347
347
  });
348
348
  }
349
- if (options?.mediaCaptureRequired === true
350
- && task.hostingMode !== 'EXTERNAL'
351
- && !task.externalUrl) {
352
- const mediaCaptureProof = (0, listingPreflight_1.readMediaCaptureProof)({
353
- task,
354
- preparedSessionFiles,
355
- });
356
- await client.recordAppUploadMediaCapture(creatorUsername, task.name, sessionId, mediaCaptureProof);
357
- }
358
349
  if (task.hostingMode !== 'EXTERNAL'
359
350
  && !task.externalUrl
360
351
  && options?.playtestSmokeCheckRequired) {
@@ -363,7 +354,7 @@ async function uploadAppVersion(client, task, artifacts, options) {
363
354
  creatorUsername,
364
355
  task,
365
356
  sessionId,
366
- timeoutMs: options.loadCheckTimeoutMs,
357
+ timeoutMs: options?.loadCheckTimeoutMs,
367
358
  token: options?.token ?? null,
368
359
  currentUser: options?.user ?? null,
369
360
  captureSession: options?.captureSession,
@@ -377,7 +368,7 @@ async function uploadAppVersion(client, task, artifacts, options) {
377
368
  creatorUsername,
378
369
  task,
379
370
  sessionId,
380
- timeoutMs: options.loadCheckTimeoutMs,
371
+ timeoutMs: options?.loadCheckTimeoutMs,
381
372
  token: options?.token ?? null,
382
373
  currentUser: options?.user ?? null,
383
374
  captureSession: options?.captureSession,
@@ -386,6 +377,22 @@ async function uploadAppVersion(client, task, artifacts, options) {
386
377
  throw new Error((0, loadCheck_1.formatHostedLoadCheckFailure)(task.name, loadCheck, 'staged'));
387
378
  }
388
379
  }
380
+ if (task.editorSupported && task.hostingMode !== 'EXTERNAL' && !task.externalUrl) {
381
+ const editorLoadCheck = await (0, loadCheck_1.runUploadedHostedLoadCheck)({
382
+ client,
383
+ creatorUsername,
384
+ task,
385
+ sessionId,
386
+ timeoutMs: options?.loadCheckTimeoutMs,
387
+ token: options?.token ?? null,
388
+ currentUser: options?.user ?? null,
389
+ captureSession: options?.captureSession,
390
+ phase: 'editor',
391
+ });
392
+ if (editorLoadCheck.status !== 'PASSED') {
393
+ throw new Error((0, loadCheck_1.formatHostedLoadCheckFailure)(task.name, editorLoadCheck, 'editor'));
394
+ }
395
+ }
389
396
  }
390
397
  const finalized = initialized.status === 'finalized'
391
398
  ? initialized.finalized
@@ -63,6 +63,7 @@ export type AppCatalogueEntry = {
63
63
  authMode?: string;
64
64
  controllerMode?: string;
65
65
  previewable?: boolean;
66
+ editorSupported?: boolean;
66
67
  externalUrl?: string;
67
68
  listing?: AppListingConfig;
68
69
  username?: string;
@@ -210,6 +211,7 @@ export type AppTask = {
210
211
  authMode?: AppAuthMode;
211
212
  controllerMode?: AppControllerMode;
212
213
  previewable?: boolean;
214
+ editorSupported?: boolean;
213
215
  externalUrl?: string;
214
216
  listing?: ResolvedListingAssets;
215
217
  username?: string;
@@ -368,6 +370,7 @@ export type ValidatedAppMetadata = {
368
370
  surfaceTargetMap: SurfaceTargetsMap;
369
371
  primarySurface?: AppSurface;
370
372
  playtestTapes?: AppPlaytestTapes;
373
+ editorSupported?: boolean;
371
374
  missingFields: string[];
372
375
  warnings: string[];
373
376
  errors: string[];
package/dist/catalogue.js CHANGED
@@ -948,6 +948,18 @@ function validateAppMetadata(entry) {
948
948
  warnings.push(`description is ${normalizedDescription.length} characters and ${wordCount} words. Aim for at least ${exports.APP_DESCRIPTION_RECOMMENDED_MIN_CHARACTERS} characters and ${exports.APP_DESCRIPTION_RECOMMENDED_MIN_WORDS} words describing what the player does, the objective, and the game's distinctive hook.`);
949
949
  }
950
950
  }
951
+ let editorSupported;
952
+ if (Object.prototype.hasOwnProperty.call(entry, 'editorSupported')) {
953
+ if (typeof entry.editorSupported !== 'boolean') {
954
+ errors.push('editorSupported must be true or false');
955
+ }
956
+ else {
957
+ editorSupported = entry.editorSupported;
958
+ if (editorSupported && entry.hostingMode === 'EXTERNAL') {
959
+ errors.push('editorSupported can only be true for HOSTED apps, not external apps');
960
+ }
961
+ }
962
+ }
951
963
  let surfaceTargetsList = [];
952
964
  let surfaceTargetsMap = { desktop: false, mobileLandscape: false, mobilePortrait: false };
953
965
  try {
@@ -992,6 +1004,7 @@ function validateAppMetadata(entry) {
992
1004
  surfaceTargetMap: surfaceTargetsMap,
993
1005
  primarySurface,
994
1006
  playtestTapes,
1007
+ editorSupported,
995
1008
  missingFields,
996
1009
  warnings,
997
1010
  errors,
@@ -1204,6 +1217,14 @@ function buildAppTasks(rootDir, catalogues, options) {
1204
1217
  }
1205
1218
  previewable = Boolean(entry.previewable);
1206
1219
  }
1220
+ let editorSupported;
1221
+ if (Object.prototype.hasOwnProperty.call(entry, 'editorSupported')) {
1222
+ if (typeof entry.editorSupported !== 'boolean') {
1223
+ errors.push(`[${label}] Skipping ${rawName}: editorSupported must be true or false.`);
1224
+ continue;
1225
+ }
1226
+ editorSupported = Boolean(entry.editorSupported);
1227
+ }
1207
1228
  let externalUrl;
1208
1229
  const rawExternalUrl = typeof entry.externalUrl === 'string' ? entry.externalUrl.trim() : '';
1209
1230
  if (rawExternalUrl) {
@@ -1226,6 +1247,10 @@ function buildAppTasks(rootDir, catalogues, options) {
1226
1247
  errors.push(`[${label}] Skipping ${rawName}: previewable can only be true for HOSTED apps.`);
1227
1248
  continue;
1228
1249
  }
1250
+ if ((hostingMode ?? 'HOSTED') === 'EXTERNAL' && editorSupported) {
1251
+ errors.push(`[${label}] Skipping ${rawName}: editorSupported can only be true for HOSTED apps.`);
1252
+ continue;
1253
+ }
1229
1254
  const defaultLicense = (hostingMode ?? 'HOSTED') === 'EXTERNAL' ? 'CLOSED' : types_1.DEFAULT_CONTENT_LICENSE;
1230
1255
  const license = normalizeContentLicenseValue(entry.license, errors, `${label} app "${rawName}"`, APP_ALLOWED_LICENSES, defaultLicense);
1231
1256
  if (!license) {
@@ -1600,6 +1625,7 @@ function buildAppTasks(rootDir, catalogues, options) {
1600
1625
  authMode,
1601
1626
  controllerMode,
1602
1627
  previewable,
1628
+ editorSupported,
1603
1629
  externalUrl,
1604
1630
  listing,
1605
1631
  // Admin-only field
@@ -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,11 @@ 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);
1409
1390
  return {
1410
1391
  appName: prepared.task.name,
1411
1392
  creatorUsername: prepared.creatorUsername,
@@ -1416,7 +1397,7 @@ async function preflightWorkerAppProject(input) {
1416
1397
  // "playdrop project publish", but always as one PRIVATE draft version for the
1417
1398
  // task's creator, with browser launch checks on before the version is created.
1418
1399
  async function publishWorkerAppProject(input) {
1419
- const prepared = await prepareWorkerAppProject(input, 'COMPLETE');
1400
+ const prepared = await prepareWorkerAppProject(input);
1420
1401
  const { task, artifacts, creatorUsername } = prepared;
1421
1402
  const { app } = await (0, registration_1.ensureRegisteredHostedAppShell)({
1422
1403
  client: input.client,
@@ -1446,7 +1427,6 @@ async function publishWorkerAppProject(input) {
1446
1427
  runStagedUploadLoadCheck: true,
1447
1428
  ensureRegisteredAppShell: true,
1448
1429
  agentTaskId: input.taskId,
1449
- mediaCaptureRequired: input.executionTarget === 'FIRST_PARTY',
1450
1430
  playtestSmokeCheckRequired: input.kind === 'NEW_GAME',
1451
1431
  captureSession,
1452
1432
  ...(artifacts ? { artifacts } : {}),
@@ -1536,8 +1516,8 @@ async function publishStaticHtmlProject(input) {
1536
1516
  runStagedUploadLoadCheck: true,
1537
1517
  ensureRegisteredAppShell: true,
1538
1518
  agentTaskId: input.taskId,
1539
- mediaCaptureRequired: false,
1540
1519
  playtestSmokeCheckRequired: false,
1520
+ skipReview: input.skipReview,
1541
1521
  captureSession,
1542
1522
  artifacts: prepared.artifacts,
1543
1523
  });
@@ -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: {