@playdrop/playdrop-cli 0.14.0 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/client-meta.json +2 -2
- package/dist/apps/loadCheck.d.ts +2 -21
- package/dist/apps/loadCheck.js +22 -106
- package/dist/apps/surfaceProfiles.d.ts +1 -0
- package/dist/apps/surfaceProfiles.js +5 -4
- package/dist/apps/validate.d.ts +1 -1
- package/dist/apps/validate.js +2 -420
- package/dist/catalogue.js +32 -1
- package/dist/commands/captureListing.d.ts +13 -0
- package/dist/commands/captureListing.js +187 -0
- package/dist/commands/check.d.ts +11 -1
- package/dist/commands/check.js +19 -39
- package/dist/commands/detail.js +35 -2
- package/dist/commands/review.d.ts +11 -1
- package/dist/commands/review.js +20 -13
- package/dist/commands/upload.d.ts +1 -7
- package/dist/commands/upload.js +20 -462
- package/dist/commands/validate.js +0 -3
- package/dist/commands/worker.d.ts +1 -3
- package/dist/commands/worker.js +37 -37
- package/dist/index.js +2 -4
- package/dist/listingPreflight.d.ts +3 -0
- package/dist/listingPreflight.js +37 -10
- package/node_modules/@playdrop/config/client-meta.json +2 -2
- package/node_modules/@playdrop/types/dist/app-playtest-tape.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/app-playtest-tape.js +23 -15
- package/package.json +1 -1
package/dist/commands/worker.js
CHANGED
|
@@ -35,7 +35,6 @@ exports.classifyWorkerEventDrainError = classifyWorkerEventDrainError;
|
|
|
35
35
|
exports.isWorkerTransientNetworkError = isWorkerTransientNetworkError;
|
|
36
36
|
exports.classifyWorkerHeartbeatError = classifyWorkerHeartbeatError;
|
|
37
37
|
exports.assertWorkerClaimErrorRetryable = assertWorkerClaimErrorRetryable;
|
|
38
|
-
exports.resolvePlaydropAssetRequirementFromText = resolvePlaydropAssetRequirementFromText;
|
|
39
38
|
exports.resolvePlaywrightBrowsersPathFromExecutable = resolvePlaywrightBrowsersPathFromExecutable;
|
|
40
39
|
exports.loadWorkerEnvFile = loadWorkerEnvFile;
|
|
41
40
|
exports.buildWorkerCapabilities = buildWorkerCapabilities;
|
|
@@ -686,6 +685,18 @@ function normalizeWorkerInstrumentSurface(value) {
|
|
|
686
685
|
? value
|
|
687
686
|
: null;
|
|
688
687
|
}
|
|
688
|
+
const LEGACY_REVIEW_SURFACE_PRIORITY = [
|
|
689
|
+
'MOBILE_PORTRAIT',
|
|
690
|
+
'MOBILE_LANDSCAPE',
|
|
691
|
+
'DESKTOP',
|
|
692
|
+
];
|
|
693
|
+
function normalizeWorkerInstrumentSurfaceTargets(value) {
|
|
694
|
+
if (!Array.isArray(value))
|
|
695
|
+
return [];
|
|
696
|
+
return Array.from(new Set(value
|
|
697
|
+
.map(normalizeWorkerInstrumentSurface)
|
|
698
|
+
.filter((surface) => Boolean(surface))));
|
|
699
|
+
}
|
|
689
700
|
function resolveWorkerInstrumentSurface(taskContext) {
|
|
690
701
|
const playdrop = taskContext.metadata.playdrop;
|
|
691
702
|
if (!playdrop || typeof playdrop !== 'object' || Array.isArray(playdrop)) {
|
|
@@ -714,7 +725,12 @@ function resolveWorkerInstrumentSurface(taskContext) {
|
|
|
714
725
|
if (!reviewTarget || typeof reviewTarget !== 'object' || Array.isArray(reviewTarget)) {
|
|
715
726
|
return null;
|
|
716
727
|
}
|
|
717
|
-
|
|
728
|
+
const target = reviewTarget;
|
|
729
|
+
const primarySurface = normalizeWorkerInstrumentSurface(target.primarySurface);
|
|
730
|
+
if (primarySurface)
|
|
731
|
+
return primarySurface;
|
|
732
|
+
const surfaceTargets = normalizeWorkerInstrumentSurfaceTargets(target.surfaceTargets);
|
|
733
|
+
return LEGACY_REVIEW_SURFACE_PRIORITY.find((surface) => surfaceTargets.includes(surface)) ?? null;
|
|
718
734
|
}
|
|
719
735
|
if (taskContext.kind === 'GAME_EVAL') {
|
|
720
736
|
const evalTarget = metadata.evalTarget;
|
|
@@ -958,12 +974,15 @@ function normalizeTaskClaimSlugPrimarySurface(value) {
|
|
|
958
974
|
}
|
|
959
975
|
return primarySurface;
|
|
960
976
|
}
|
|
961
|
-
function
|
|
977
|
+
function resolveTaskWorkspaceInputPath(filePath, workspaceDir) {
|
|
978
|
+
return node_path_1.default.isAbsolute(filePath) ? node_path_1.default.normalize(filePath) : node_path_1.default.resolve(workspaceDir, filePath);
|
|
979
|
+
}
|
|
980
|
+
function readTaskNextStepsFile(filePath, workspaceDir = node_process_1.default.cwd()) {
|
|
962
981
|
const normalizedPath = typeof filePath === 'string' ? filePath.trim() : '';
|
|
963
982
|
if (!normalizedPath) {
|
|
964
983
|
return undefined;
|
|
965
984
|
}
|
|
966
|
-
const resolved =
|
|
985
|
+
const resolved = resolveTaskWorkspaceInputPath(normalizedPath, workspaceDir);
|
|
967
986
|
if (!(0, node_fs_1.existsSync)(resolved)) {
|
|
968
987
|
throw new Error('invalid_agent_task_next_steps_file');
|
|
969
988
|
}
|
|
@@ -1038,7 +1057,7 @@ async function recordRejectedNextStepsFile(input) {
|
|
|
1038
1057
|
}
|
|
1039
1058
|
async function readTaskNextStepsFileForCommand(input) {
|
|
1040
1059
|
try {
|
|
1041
|
-
return readTaskNextStepsFile(input.filePath);
|
|
1060
|
+
return readTaskNextStepsFile(input.filePath, input.workspaceDir);
|
|
1042
1061
|
}
|
|
1043
1062
|
catch (error) {
|
|
1044
1063
|
const code = error instanceof Error ? error.message : String(error);
|
|
@@ -1609,9 +1628,6 @@ function assertWorkerClaimErrorRetryable(error) {
|
|
|
1609
1628
|
throw new Error(`worker_claim_forbidden: the server rejected this worker session (${error.message}).`);
|
|
1610
1629
|
}
|
|
1611
1630
|
}
|
|
1612
|
-
function resolvePlaydropAssetRequirementFromText(value) {
|
|
1613
|
-
return (0, upload_1.resolveWorkerPlaydropAssetRequirement)(value);
|
|
1614
|
-
}
|
|
1615
1631
|
function readCodexAuthenticated(homeDir = node_os_1.default.homedir()) {
|
|
1616
1632
|
try {
|
|
1617
1633
|
const parsed = JSON.parse((0, node_fs_1.readFileSync)(node_path_1.default.join(homeDir, '.codex', 'auth.json'), 'utf8'));
|
|
@@ -5518,8 +5534,6 @@ async function uploadTask(options = {}) {
|
|
|
5518
5534
|
expectedSubtitle: taskContext.outputSubtitle,
|
|
5519
5535
|
expectedPrimarySurface: taskContext.outputPrimarySurface,
|
|
5520
5536
|
remixSourceRef: taskContext.remixSourceRef ?? null,
|
|
5521
|
-
playdropAssetRequirement: (0, upload_1.resolveWorkerPlaydropAssetRequirement)(taskContext.creatorRequest),
|
|
5522
|
-
creatorRequest: taskContext.creatorRequest,
|
|
5523
5537
|
projectDir,
|
|
5524
5538
|
creatorUsername: taskContext.creatorUsername,
|
|
5525
5539
|
apiBase: ctx.envConfig.apiBase,
|
|
@@ -5559,8 +5573,6 @@ async function uploadTask(options = {}) {
|
|
|
5559
5573
|
expectedSubtitle: taskContext.outputSubtitle,
|
|
5560
5574
|
expectedPrimarySurface: taskContext.outputPrimarySurface,
|
|
5561
5575
|
remixSourceRef: taskContext.remixSourceRef ?? null,
|
|
5562
|
-
playdropAssetRequirement: (0, upload_1.resolveWorkerPlaydropAssetRequirement)(taskContext.creatorRequest),
|
|
5563
|
-
creatorRequest: taskContext.creatorRequest,
|
|
5564
5576
|
projectDir,
|
|
5565
5577
|
creatorUsername: taskContext.creatorUsername,
|
|
5566
5578
|
apiBase: ctx.envConfig.apiBase,
|
|
@@ -5643,6 +5655,7 @@ async function claimSlugTask(options) {
|
|
|
5643
5655
|
}
|
|
5644
5656
|
async function completeTask(options) {
|
|
5645
5657
|
const taskContext = readTaskContextFile();
|
|
5658
|
+
const workspaceDir = resolveTaskWorkspaceDir();
|
|
5646
5659
|
if (taskContext.kind === 'GAME_REVIEW' || taskContext.kind === 'GAME_EVAL') {
|
|
5647
5660
|
throw new Error('task_done_not_allowed_for_game_review');
|
|
5648
5661
|
}
|
|
@@ -5668,6 +5681,7 @@ async function completeTask(options) {
|
|
|
5668
5681
|
}
|
|
5669
5682
|
const nextSteps = await readTaskNextStepsFileForCommand({
|
|
5670
5683
|
filePath: options.nextSteps,
|
|
5684
|
+
workspaceDir,
|
|
5671
5685
|
command: 'task done',
|
|
5672
5686
|
ctx,
|
|
5673
5687
|
taskContext,
|
|
@@ -5675,7 +5689,7 @@ async function completeTask(options) {
|
|
|
5675
5689
|
if (nextSteps === NEXT_STEPS_READ_FAILED) {
|
|
5676
5690
|
return;
|
|
5677
5691
|
}
|
|
5678
|
-
const platformFeedback = readPlatformFeedbackFile(options.platformFeedbackFile);
|
|
5692
|
+
const platformFeedback = readPlatformFeedbackFile(options.platformFeedbackFile, workspaceDir);
|
|
5679
5693
|
try {
|
|
5680
5694
|
await ctx.client.workerCompleteAgentTask(taskContext.taskId, {
|
|
5681
5695
|
taskToken: taskContext.taskToken,
|
|
@@ -5714,18 +5728,6 @@ async function completeTask(options) {
|
|
|
5714
5728
|
}
|
|
5715
5729
|
(0, output_1.printSuccess)('Task marked done.');
|
|
5716
5730
|
}
|
|
5717
|
-
function normalizeReviewSubmitState(value) {
|
|
5718
|
-
const normalized = typeof value === 'string' ? value.trim().toUpperCase() : '';
|
|
5719
|
-
if (normalized !== 'PASSED'
|
|
5720
|
-
&& normalized !== 'GOOD'
|
|
5721
|
-
&& normalized !== 'EXCELLENT'
|
|
5722
|
-
&& normalized !== 'LOW_QUALITY'
|
|
5723
|
-
&& normalized !== 'FAILED'
|
|
5724
|
-
&& normalized !== 'ERROR') {
|
|
5725
|
-
throw new Error('invalid_review_state');
|
|
5726
|
-
}
|
|
5727
|
-
return normalized;
|
|
5728
|
-
}
|
|
5729
5731
|
function readRequiredTextFile(filePath, code) {
|
|
5730
5732
|
const normalizedPath = typeof filePath === 'string' ? filePath.trim() : '';
|
|
5731
5733
|
if (!normalizedPath) {
|
|
@@ -5753,13 +5755,13 @@ function readOptionalTextFile(filePath) {
|
|
|
5753
5755
|
const content = (0, node_fs_1.readFileSync)(resolved, 'utf8').trim();
|
|
5754
5756
|
return content || null;
|
|
5755
5757
|
}
|
|
5756
|
-
function readPlatformFeedbackFile(filePath) {
|
|
5758
|
+
function readPlatformFeedbackFile(filePath, workspaceDir) {
|
|
5757
5759
|
const normalizedPath = typeof filePath === 'string' ? filePath.trim() : '';
|
|
5758
5760
|
if (!normalizedPath) {
|
|
5759
5761
|
return null;
|
|
5760
5762
|
}
|
|
5761
5763
|
try {
|
|
5762
|
-
const resolved =
|
|
5764
|
+
const resolved = resolveTaskWorkspaceInputPath(normalizedPath, workspaceDir);
|
|
5763
5765
|
if (!(0, node_fs_1.existsSync)(resolved)) {
|
|
5764
5766
|
throw new Error('file_missing');
|
|
5765
5767
|
}
|
|
@@ -5839,27 +5841,23 @@ function resolveReviewEvidenceDir(evidenceDir) {
|
|
|
5839
5841
|
}
|
|
5840
5842
|
async function submitReviewTask(options) {
|
|
5841
5843
|
const taskContext = readTaskContextFile();
|
|
5844
|
+
const workspaceDir = resolveTaskWorkspaceDir();
|
|
5842
5845
|
if (taskContext.kind !== 'GAME_REVIEW') {
|
|
5843
5846
|
throw new Error('task_submit_review_requires_game_review');
|
|
5844
5847
|
}
|
|
5845
|
-
const state =
|
|
5848
|
+
const state = (0, review_1.normalizeTerminalReviewState)(options.state);
|
|
5846
5849
|
const reviewMessageFile = resolveReviewFilePath(options.messageFile, 'invalid_review_message_file');
|
|
5847
|
-
const creatorFeedbackFile =
|
|
5848
|
-
? null
|
|
5849
|
-
: resolveReviewFilePath(options.creatorFeedbackFile, 'invalid_creator_feedback_file');
|
|
5850
|
+
const creatorFeedbackFile = resolveReviewFilePath(options.creatorFeedbackFile, 'invalid_creator_feedback_file');
|
|
5850
5851
|
const evidenceDir = resolveReviewEvidenceDir(options.evidenceDir);
|
|
5851
5852
|
const reviewMessage = readRequiredTextFile(reviewMessageFile, 'invalid_review_message_file');
|
|
5852
|
-
const creatorFeedback =
|
|
5853
|
-
|
|
5854
|
-
: readOptionalTextFile(creatorFeedbackFile ?? undefined);
|
|
5855
|
-
const platformFeedback = readPlatformFeedbackFile(options.platformFeedbackFile);
|
|
5853
|
+
const creatorFeedback = readOptionalTextFile(creatorFeedbackFile);
|
|
5854
|
+
const platformFeedback = readPlatformFeedbackFile(options.platformFeedbackFile, workspaceDir);
|
|
5856
5855
|
await (0, review_1.validateGameReviewResult)({
|
|
5857
5856
|
reviewState: state,
|
|
5858
5857
|
reviewMessage,
|
|
5859
5858
|
creatorFeedback: creatorFeedback ?? '',
|
|
5860
5859
|
evidenceDir,
|
|
5861
5860
|
});
|
|
5862
|
-
const workspaceDir = resolveTaskWorkspaceDir();
|
|
5863
5861
|
const instrumentEvidence = (0, instrument_evidence_1.readAndValidateInstrumentEvidenceManifest)({ workspaceDir, evidenceDir });
|
|
5864
5862
|
const evidenceFiles = readReviewEvidenceFiles(evidenceDir, workspaceDir);
|
|
5865
5863
|
const ctx = await resolveTaskCommandContext('task submit-review', options.env, taskContext);
|
|
@@ -5868,6 +5866,7 @@ async function submitReviewTask(options) {
|
|
|
5868
5866
|
}
|
|
5869
5867
|
const nextSteps = await readTaskNextStepsFileForCommand({
|
|
5870
5868
|
filePath: options.nextSteps,
|
|
5869
|
+
workspaceDir,
|
|
5871
5870
|
command: 'task submit-review',
|
|
5872
5871
|
ctx,
|
|
5873
5872
|
taskContext,
|
|
@@ -6005,11 +6004,12 @@ async function failTask(options) {
|
|
|
6005
6004
|
return;
|
|
6006
6005
|
}
|
|
6007
6006
|
const taskContext = readTaskContextFile();
|
|
6007
|
+
const workspaceDir = resolveTaskWorkspaceDir();
|
|
6008
6008
|
const ctx = await resolveTaskCommandContext('task fail', options.env, taskContext);
|
|
6009
6009
|
if (!ctx) {
|
|
6010
6010
|
return;
|
|
6011
6011
|
}
|
|
6012
|
-
const platformFeedback = readPlatformFeedbackFile(options.platformFeedbackFile);
|
|
6012
|
+
const platformFeedback = readPlatformFeedbackFile(options.platformFeedbackFile, workspaceDir);
|
|
6013
6013
|
await ctx.client.workerFailAgentTask(taskContext.taskId, {
|
|
6014
6014
|
taskToken: taskContext.taskToken,
|
|
6015
6015
|
error: message,
|
package/dist/index.js
CHANGED
|
@@ -289,7 +289,7 @@ task
|
|
|
289
289
|
task
|
|
290
290
|
.command('submit-review')
|
|
291
291
|
.description('Submit the active game review task result')
|
|
292
|
-
.requiredOption('--state <state>', '
|
|
292
|
+
.requiredOption('--state <state>', 'Review outcome: Blocked, Limited, Passed, Good, or Excellent')
|
|
293
293
|
.requiredOption('--message-file <path>', 'Internal assessment file')
|
|
294
294
|
.option('--creator-feedback-file <path>', 'Creator feedback file')
|
|
295
295
|
.option('--platform-feedback-file <path>', 'Markdown feedback about the PlayDrop platform')
|
|
@@ -339,7 +339,7 @@ const review = program.command('review').description('Review helper commands for
|
|
|
339
339
|
review
|
|
340
340
|
.command('validate-result')
|
|
341
341
|
.description('Validate a canonical game review result')
|
|
342
|
-
.requiredOption('--state <state>', '
|
|
342
|
+
.requiredOption('--state <state>', 'Review outcome: Blocked, Limited, Passed, Good, or Excellent')
|
|
343
343
|
.requiredOption('--message-file <path>', 'Internal assessment file')
|
|
344
344
|
.option('--creator-feedback-file <path>', 'Creator feedback file')
|
|
345
345
|
.requiredOption('--evidence-dir <path>', 'Review evidence directory')
|
|
@@ -1038,14 +1038,12 @@ project
|
|
|
1038
1038
|
.option('--app <name>', 'App name when the target is a workspace')
|
|
1039
1039
|
.option('--timeout <seconds>', 'Load-check timeout in seconds')
|
|
1040
1040
|
.option('--screenshot <path>', 'Screenshot output path')
|
|
1041
|
-
.option('--actions <path>', 'JSON array of click {x,y}, press {key}, and wait {ms <= 60000} actions')
|
|
1042
1041
|
.option('--tape <surface>', 'Run matched idle and catalogue playtest-tape captures on a declared surface')
|
|
1043
1042
|
.action(async (target, opts = {}) => {
|
|
1044
1043
|
await (0, check_1.check)(target, {
|
|
1045
1044
|
app: opts.app,
|
|
1046
1045
|
timeout: opts.timeout,
|
|
1047
1046
|
screenshot: opts.screenshot,
|
|
1048
|
-
actions: opts.actions,
|
|
1049
1047
|
tape: opts.tape,
|
|
1050
1048
|
});
|
|
1051
1049
|
});
|
|
@@ -17,6 +17,9 @@ export declare function readMediaCaptureProof(input: {
|
|
|
17
17
|
}): RecordAppUploadMediaCaptureRequest;
|
|
18
18
|
export declare function readWorkerUploadPreflightContext(projectDir: string): WorkerUploadPreflightContext | null;
|
|
19
19
|
export declare function assertAgentGameHeroListingPreflight(task: AppTask): void;
|
|
20
|
+
export declare function assertAgentGameIconListingPreflight(task: AppTask): void;
|
|
21
|
+
export declare function assertAgentGameVideoListingPreflight(task: AppTask): void;
|
|
22
|
+
export declare function assertAgentGamePreviewListingPreflight(task: AppTask): void;
|
|
20
23
|
export declare function assertAgentGameScreenshotListingPreflight(task: AppTask): void;
|
|
21
24
|
export declare function assertAgentGameCaptureReportPreflight(task: AppTask, options: {
|
|
22
25
|
requireCaptureReport: boolean;
|
package/dist/listingPreflight.js
CHANGED
|
@@ -3,6 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.readMediaCaptureProof = readMediaCaptureProof;
|
|
4
4
|
exports.readWorkerUploadPreflightContext = readWorkerUploadPreflightContext;
|
|
5
5
|
exports.assertAgentGameHeroListingPreflight = assertAgentGameHeroListingPreflight;
|
|
6
|
+
exports.assertAgentGameIconListingPreflight = assertAgentGameIconListingPreflight;
|
|
7
|
+
exports.assertAgentGameVideoListingPreflight = assertAgentGameVideoListingPreflight;
|
|
8
|
+
exports.assertAgentGamePreviewListingPreflight = assertAgentGamePreviewListingPreflight;
|
|
6
9
|
exports.assertAgentGameScreenshotListingPreflight = assertAgentGameScreenshotListingPreflight;
|
|
7
10
|
exports.assertAgentGameCaptureReportPreflight = assertAgentGameCaptureReportPreflight;
|
|
8
11
|
exports.assertAgentGameListingPreflight = assertAgentGameListingPreflight;
|
|
@@ -37,9 +40,12 @@ function readPngDimensions(filePath, errorCode = 'agent_task_listing_hero_art_in
|
|
|
37
40
|
}
|
|
38
41
|
return { width, height };
|
|
39
42
|
}
|
|
40
|
-
function assertListingIcon(task) {
|
|
43
|
+
function assertListingIcon(task, required = false) {
|
|
41
44
|
const iconPath = task.listing?.iconPath;
|
|
42
45
|
if (!iconPath) {
|
|
46
|
+
if (required) {
|
|
47
|
+
throw new Error('agent_task_listing_icon_missing: agent game listings must include a dedicated square PNG through listing.icon.');
|
|
48
|
+
}
|
|
43
49
|
return;
|
|
44
50
|
}
|
|
45
51
|
const icon = readPngDimensions(iconPath, 'listing_icon_invalid');
|
|
@@ -116,15 +122,6 @@ function assertScreenshotPath(task, filePath, field) {
|
|
|
116
122
|
function assertListingScreenshots(task) {
|
|
117
123
|
const portraitPaths = task.listing?.screenshotPortraitPaths ?? [];
|
|
118
124
|
const landscapePaths = task.listing?.screenshotLandscapePaths ?? [];
|
|
119
|
-
if (portraitPaths.length === 0 && landscapePaths.length === 0) {
|
|
120
|
-
throw new Error('agent_task_listing_screenshots_missing: new game tasks must include listing.screenshotsPortrait and/or listing.screenshotsLandscape PNG marketing images.');
|
|
121
|
-
}
|
|
122
|
-
if (task.surfaceTargets.includes('MOBILE_PORTRAIT') && portraitPaths.length === 0) {
|
|
123
|
-
throw new Error('agent_task_listing_screenshots_missing: mobile portrait games must include at least one listing.screenshotsPortrait PNG marketing image.');
|
|
124
|
-
}
|
|
125
|
-
if ((task.surfaceTargets.includes('DESKTOP') || task.surfaceTargets.includes('MOBILE_LANDSCAPE')) && landscapePaths.length === 0) {
|
|
126
|
-
throw new Error('agent_task_listing_screenshots_missing: desktop or landscape games must include at least one listing.screenshotsLandscape PNG marketing image.');
|
|
127
|
-
}
|
|
128
125
|
for (const filePath of portraitPaths) {
|
|
129
126
|
assertScreenshotPath(task, filePath, 'listing.screenshotsPortrait');
|
|
130
127
|
readPngDimensions(filePath);
|
|
@@ -134,6 +131,18 @@ function assertListingScreenshots(task) {
|
|
|
134
131
|
readPngDimensions(filePath);
|
|
135
132
|
}
|
|
136
133
|
}
|
|
134
|
+
function assertListingGameplayVideo(task) {
|
|
135
|
+
const videoCount = (task.listing?.videoPortraitPaths?.length ?? 0)
|
|
136
|
+
+ (task.listing?.videoLandscapePaths?.length ?? 0);
|
|
137
|
+
if (videoCount === 0) {
|
|
138
|
+
throw new Error('agent_task_listing_video_missing: agent game listings must include at least one real gameplay video through listing.videosPortrait or listing.videosLandscape.');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function assertListingPreviewMode(task) {
|
|
142
|
+
if (task.previewable !== true) {
|
|
143
|
+
throw new Error('agent_task_listing_preview_missing: agent game listings must set previewable: true and implement the real gameplay preview contract before capture.');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
137
146
|
function isSha256Hex(value) {
|
|
138
147
|
return typeof value === 'string' && /^[a-f0-9]{64}$/i.test(value.trim());
|
|
139
148
|
}
|
|
@@ -158,6 +167,12 @@ function readMediaCaptureProof(input) {
|
|
|
158
167
|
if (!binding || typeof binding !== 'object' || Array.isArray(binding)) {
|
|
159
168
|
throw new Error(`agent_task_media_capture_report_invalid: ${reportPath} is missing binding.`);
|
|
160
169
|
}
|
|
170
|
+
if (report.appName !== input.task.name) {
|
|
171
|
+
throw new Error(`agent_task_media_capture_wrong_app: listing.captureReport is for ${String(report.appName || 'unknown')}, not ${input.task.name}; rerun "playdrop project capture".`);
|
|
172
|
+
}
|
|
173
|
+
if (binding.pixelValidation !== 'passed') {
|
|
174
|
+
throw new Error(`agent_task_media_capture_pixels_unvalidated: ${reportPath} must come from a capture that passed wrong-game, black-frame, and motion checks.`);
|
|
175
|
+
}
|
|
161
176
|
if (input.expectedRuntimeBundleHash) {
|
|
162
177
|
const capturedRuntimeBundleHash = typeof binding.runtimeBundleHash === 'string'
|
|
163
178
|
? binding.runtimeBundleHash.trim().toLowerCase()
|
|
@@ -309,6 +324,15 @@ function readWorkerUploadPreflightContext(projectDir) {
|
|
|
309
324
|
function assertAgentGameHeroListingPreflight(task) {
|
|
310
325
|
assertHeroListingArt(task);
|
|
311
326
|
}
|
|
327
|
+
function assertAgentGameIconListingPreflight(task) {
|
|
328
|
+
assertListingIcon(task, true);
|
|
329
|
+
}
|
|
330
|
+
function assertAgentGameVideoListingPreflight(task) {
|
|
331
|
+
assertListingGameplayVideo(task);
|
|
332
|
+
}
|
|
333
|
+
function assertAgentGamePreviewListingPreflight(task) {
|
|
334
|
+
assertListingPreviewMode(task);
|
|
335
|
+
}
|
|
312
336
|
function assertAgentGameScreenshotListingPreflight(task) {
|
|
313
337
|
assertListingScreenshots(task);
|
|
314
338
|
}
|
|
@@ -322,7 +346,10 @@ function assertAgentGameCaptureReportPreflight(task, options) {
|
|
|
322
346
|
}
|
|
323
347
|
}
|
|
324
348
|
function assertAgentGameListingPreflight(task, options) {
|
|
349
|
+
assertAgentGameIconListingPreflight(task);
|
|
325
350
|
assertAgentGameHeroListingPreflight(task);
|
|
351
|
+
assertAgentGamePreviewListingPreflight(task);
|
|
352
|
+
assertAgentGameVideoListingPreflight(task);
|
|
326
353
|
assertAgentGameScreenshotListingPreflight(task);
|
|
327
354
|
assertAgentGameCaptureReportPreflight(task, options);
|
|
328
355
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-playtest-tape.d.ts","sourceRoot":"","sources":["../src/app-playtest-tape.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,UAAU,EAEhB,MAAM,UAAU,CAAC;AAElB,eAAO,MAAM,yBAAyB,EAAG,CAAU,CAAC;AACpD,eAAO,MAAM,oCAAoC,KAAK,CAAC;AAEvD,eAAO,MAAM,gCAAgC,0CAA2C,CAAC;AACzF,MAAM,MAAM,sBAAsB,GAAG,OAAO,gCAAgC,CAAC,MAAM,CAAC,CAAC;AASrF,wBAAgB,kCAAkC,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAEtG;AAED,eAAO,MAAM,uCAAuC,sFAK1C,CAAC;AAEX,MAAM,MAAM,4BAA4B,GAAG,OAAO,uCAAuC,CAAC,MAAM,CAAC,CAAC;AAElG,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,EAAE,4BAA4B,CAAC;IACnC,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,KAAK,qBAAqB,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,qBAAqB,GAAG;IAC5D,IAAI,EAAE,aAAa,GAAG,aAAa,GAAG,WAAW,CAAC;IAClD,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,qBAAqB,GAAG;IACxD,IAAI,EAAE,KAAK,CAAC;IACZ,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG,qBAAqB,GAAG;IAC7D,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAC5B,uBAAuB,GACvB,mBAAmB,GACnB,wBAAwB,CAAC;AAE7B,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,OAAO,yBAAyB,CAAC;IAC1C,WAAW,EAAE,sBAAsB,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,cAAc,EAAE,wBAAwB,EAAE,CAAC;IAC3C,MAAM,EAAE,oBAAoB,EAAE,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC;AAE5E,MAAM,MAAM,+BAA+B,GACvC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,gBAAgB,CAAA;CAAE,GACrC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"app-playtest-tape.d.ts","sourceRoot":"","sources":["../src/app-playtest-tape.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,UAAU,EAEhB,MAAM,UAAU,CAAC;AAElB,eAAO,MAAM,yBAAyB,EAAG,CAAU,CAAC;AACpD,eAAO,MAAM,oCAAoC,KAAK,CAAC;AAEvD,eAAO,MAAM,gCAAgC,0CAA2C,CAAC;AACzF,MAAM,MAAM,sBAAsB,GAAG,OAAO,gCAAgC,CAAC,MAAM,CAAC,CAAC;AASrF,wBAAgB,kCAAkC,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAEtG;AAED,eAAO,MAAM,uCAAuC,sFAK1C,CAAC;AAEX,MAAM,MAAM,4BAA4B,GAAG,OAAO,uCAAuC,CAAC,MAAM,CAAC,CAAC;AAElG,MAAM,MAAM,wBAAwB,GAAG;IACrC,IAAI,EAAE,4BAA4B,CAAC;IACnC,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,KAAK,qBAAqB,GAAG;IAC3B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,qBAAqB,GAAG;IAC5D,IAAI,EAAE,aAAa,GAAG,aAAa,GAAG,WAAW,CAAC;IAClD,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,qBAAqB,GAAG;IACxD,IAAI,EAAE,KAAK,CAAC;IACZ,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG,qBAAqB,GAAG;IAC7D,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAC5B,uBAAuB,GACvB,mBAAmB,GACnB,wBAAwB,CAAC;AAE7B,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,OAAO,yBAAyB,CAAC;IAC1C,WAAW,EAAE,sBAAsB,CAAC;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,cAAc,EAAE,wBAAwB,EAAE,CAAC;IAC3C,MAAM,EAAE,oBAAoB,EAAE,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC,CAAC;AAE5E,MAAM,MAAM,+BAA+B,GACvC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,gBAAgB,CAAA;CAAE,GACrC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAgQjC,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,OAAO,EACd,OAAO,GAAE;IACP,cAAc,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IACvC,eAAe,CAAC,EAAE,OAAO,CAAC;CACtB,GACL,+BAA+B,CAoCjC"}
|
|
@@ -106,7 +106,8 @@ function normalizeEvent(value, surface, durationMs, index) {
|
|
|
106
106
|
if (surface !== "DESKTOP") {
|
|
107
107
|
throw new Error(`keyboard_input_not_allowed_on_mobile_tape:${surface}:${index}`);
|
|
108
108
|
}
|
|
109
|
-
const
|
|
109
|
+
const rawKey = typeof value["key"] === "string" ? value["key"] : "";
|
|
110
|
+
const key = rawKey === " " ? rawKey : rawKey.trim();
|
|
110
111
|
if (!key || key.length > 64) {
|
|
111
112
|
throw new Error(`invalid_playtest_tape_key:${surface}:${index}`);
|
|
112
113
|
}
|
|
@@ -242,30 +243,37 @@ function normalizeTape(value, surface) {
|
|
|
242
243
|
if (primaryVerb === "tap")
|
|
243
244
|
return event.type === "tap";
|
|
244
245
|
if (primaryVerb === "key")
|
|
245
|
-
return event.type === "keyDown"
|
|
246
|
+
return event.type === "keyDown";
|
|
246
247
|
return event.type === "pointerDown" || event.type === "pointerMove" || event.type === "pointerUp";
|
|
247
248
|
};
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
throw new Error(`playtest_tape_primary_verb_event_mismatch:${surface}:${primaryVerb}:${startOnlyEventCount + mismatchedEventIndex}`);
|
|
249
|
+
if (!postStartEvents.some(postStartEventMatchesVerb)) {
|
|
250
|
+
throw new Error(`playtest_tape_primary_verb_events_required:${surface}:${primaryVerb}`);
|
|
251
251
|
}
|
|
252
252
|
if (primaryVerb === "swipe" || primaryVerb === "drag") {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
253
|
+
const openGestures = /* @__PURE__ */ new Set();
|
|
254
|
+
const movedGestures = /* @__PURE__ */ new Set();
|
|
255
|
+
let completePrimaryGesture = false;
|
|
256
|
+
for (const event of postStartEvents) {
|
|
257
|
+
if (event.type !== "pointerDown" && event.type !== "pointerMove" && event.type !== "pointerUp")
|
|
258
|
+
continue;
|
|
259
|
+
const pointerId = event.pointerId ?? 0;
|
|
257
260
|
if (event.type === "pointerDown") {
|
|
258
|
-
|
|
259
|
-
|
|
261
|
+
openGestures.add(pointerId);
|
|
262
|
+
movedGestures.delete(pointerId);
|
|
260
263
|
} else if (event.type === "pointerMove") {
|
|
261
|
-
|
|
264
|
+
if (openGestures.has(pointerId))
|
|
265
|
+
movedGestures.add(pointerId);
|
|
262
266
|
} else if (event.type === "pointerUp") {
|
|
263
|
-
if (
|
|
264
|
-
|
|
267
|
+
if (openGestures.has(pointerId) && movedGestures.has(pointerId)) {
|
|
268
|
+
completePrimaryGesture = true;
|
|
265
269
|
}
|
|
266
|
-
|
|
270
|
+
openGestures.delete(pointerId);
|
|
271
|
+
movedGestures.delete(pointerId);
|
|
267
272
|
}
|
|
268
273
|
}
|
|
274
|
+
if (!completePrimaryGesture) {
|
|
275
|
+
throw new Error(`playtest_tape_primary_verb_gesture_required:${surface}:${primaryVerb}`);
|
|
276
|
+
}
|
|
269
277
|
}
|
|
270
278
|
return {
|
|
271
279
|
version: APP_PLAYTEST_TAPE_VERSION,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playdrop/playdrop-cli",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.1",
|
|
4
4
|
"description": "Official Playdrop CLI for publishing browser games, creator apps, and AI-generated game assets on playdrop.ai",
|
|
5
5
|
"homepage": "https://www.playdrop.ai",
|
|
6
6
|
"repository": {
|