@playdrop/playdrop-cli 0.13.17 → 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 +22 -0
- package/dist/commands/captureListing.js +216 -7
- 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 +43 -39
- package/dist/index.js +2 -4
- package/dist/listingPreflight.d.ts +3 -0
- package/dist/listingPreflight.js +48 -24
- 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'));
|
|
@@ -2584,7 +2600,8 @@ function buildAgentFailureCode(agent, result, request) {
|
|
|
2584
2600
|
if (providerStatus === 529 || (/\b529\b/.test(combinedOutput) && /\boverloaded?\b/i.test(combinedOutput))) {
|
|
2585
2601
|
return `agent_provider_overloaded:${provider}:529`;
|
|
2586
2602
|
}
|
|
2587
|
-
if (/\boverloaded?\b/i.test(combinedOutput)
|
|
2603
|
+
if (/\boverloaded?\b/i.test(combinedOutput)
|
|
2604
|
+
|| /\b(?:selected\s+)?model\s+is\s+at\s+capacity\b/i.test(combinedOutput)) {
|
|
2588
2605
|
return `agent_provider_overloaded:${provider}`;
|
|
2589
2606
|
}
|
|
2590
2607
|
if (providerStatus === 429
|
|
@@ -5427,7 +5444,10 @@ async function materialTask(options) {
|
|
|
5427
5444
|
}
|
|
5428
5445
|
catch (error) {
|
|
5429
5446
|
const message = error instanceof Error ? error.message : String(error);
|
|
5430
|
-
|
|
5447
|
+
const suggestion = message.startsWith('task_material_asset_background_not_removed:')
|
|
5448
|
+
? ' Remove the background and retry with --asset, or omit --asset if the image is honestly useful as a full frame.'
|
|
5449
|
+
: '';
|
|
5450
|
+
console.warn(`Warning: task material was not shared: ${message}.${suggestion}`);
|
|
5431
5451
|
node_process_1.default.exitCode = 0;
|
|
5432
5452
|
}
|
|
5433
5453
|
}
|
|
@@ -5514,8 +5534,6 @@ async function uploadTask(options = {}) {
|
|
|
5514
5534
|
expectedSubtitle: taskContext.outputSubtitle,
|
|
5515
5535
|
expectedPrimarySurface: taskContext.outputPrimarySurface,
|
|
5516
5536
|
remixSourceRef: taskContext.remixSourceRef ?? null,
|
|
5517
|
-
playdropAssetRequirement: (0, upload_1.resolveWorkerPlaydropAssetRequirement)(taskContext.creatorRequest),
|
|
5518
|
-
creatorRequest: taskContext.creatorRequest,
|
|
5519
5537
|
projectDir,
|
|
5520
5538
|
creatorUsername: taskContext.creatorUsername,
|
|
5521
5539
|
apiBase: ctx.envConfig.apiBase,
|
|
@@ -5555,8 +5573,6 @@ async function uploadTask(options = {}) {
|
|
|
5555
5573
|
expectedSubtitle: taskContext.outputSubtitle,
|
|
5556
5574
|
expectedPrimarySurface: taskContext.outputPrimarySurface,
|
|
5557
5575
|
remixSourceRef: taskContext.remixSourceRef ?? null,
|
|
5558
|
-
playdropAssetRequirement: (0, upload_1.resolveWorkerPlaydropAssetRequirement)(taskContext.creatorRequest),
|
|
5559
|
-
creatorRequest: taskContext.creatorRequest,
|
|
5560
5576
|
projectDir,
|
|
5561
5577
|
creatorUsername: taskContext.creatorUsername,
|
|
5562
5578
|
apiBase: ctx.envConfig.apiBase,
|
|
@@ -5639,6 +5655,7 @@ async function claimSlugTask(options) {
|
|
|
5639
5655
|
}
|
|
5640
5656
|
async function completeTask(options) {
|
|
5641
5657
|
const taskContext = readTaskContextFile();
|
|
5658
|
+
const workspaceDir = resolveTaskWorkspaceDir();
|
|
5642
5659
|
if (taskContext.kind === 'GAME_REVIEW' || taskContext.kind === 'GAME_EVAL') {
|
|
5643
5660
|
throw new Error('task_done_not_allowed_for_game_review');
|
|
5644
5661
|
}
|
|
@@ -5664,6 +5681,7 @@ async function completeTask(options) {
|
|
|
5664
5681
|
}
|
|
5665
5682
|
const nextSteps = await readTaskNextStepsFileForCommand({
|
|
5666
5683
|
filePath: options.nextSteps,
|
|
5684
|
+
workspaceDir,
|
|
5667
5685
|
command: 'task done',
|
|
5668
5686
|
ctx,
|
|
5669
5687
|
taskContext,
|
|
@@ -5671,7 +5689,7 @@ async function completeTask(options) {
|
|
|
5671
5689
|
if (nextSteps === NEXT_STEPS_READ_FAILED) {
|
|
5672
5690
|
return;
|
|
5673
5691
|
}
|
|
5674
|
-
const platformFeedback = readPlatformFeedbackFile(options.platformFeedbackFile);
|
|
5692
|
+
const platformFeedback = readPlatformFeedbackFile(options.platformFeedbackFile, workspaceDir);
|
|
5675
5693
|
try {
|
|
5676
5694
|
await ctx.client.workerCompleteAgentTask(taskContext.taskId, {
|
|
5677
5695
|
taskToken: taskContext.taskToken,
|
|
@@ -5710,18 +5728,6 @@ async function completeTask(options) {
|
|
|
5710
5728
|
}
|
|
5711
5729
|
(0, output_1.printSuccess)('Task marked done.');
|
|
5712
5730
|
}
|
|
5713
|
-
function normalizeReviewSubmitState(value) {
|
|
5714
|
-
const normalized = typeof value === 'string' ? value.trim().toUpperCase() : '';
|
|
5715
|
-
if (normalized !== 'PASSED'
|
|
5716
|
-
&& normalized !== 'GOOD'
|
|
5717
|
-
&& normalized !== 'EXCELLENT'
|
|
5718
|
-
&& normalized !== 'LOW_QUALITY'
|
|
5719
|
-
&& normalized !== 'FAILED'
|
|
5720
|
-
&& normalized !== 'ERROR') {
|
|
5721
|
-
throw new Error('invalid_review_state');
|
|
5722
|
-
}
|
|
5723
|
-
return normalized;
|
|
5724
|
-
}
|
|
5725
5731
|
function readRequiredTextFile(filePath, code) {
|
|
5726
5732
|
const normalizedPath = typeof filePath === 'string' ? filePath.trim() : '';
|
|
5727
5733
|
if (!normalizedPath) {
|
|
@@ -5749,13 +5755,13 @@ function readOptionalTextFile(filePath) {
|
|
|
5749
5755
|
const content = (0, node_fs_1.readFileSync)(resolved, 'utf8').trim();
|
|
5750
5756
|
return content || null;
|
|
5751
5757
|
}
|
|
5752
|
-
function readPlatformFeedbackFile(filePath) {
|
|
5758
|
+
function readPlatformFeedbackFile(filePath, workspaceDir) {
|
|
5753
5759
|
const normalizedPath = typeof filePath === 'string' ? filePath.trim() : '';
|
|
5754
5760
|
if (!normalizedPath) {
|
|
5755
5761
|
return null;
|
|
5756
5762
|
}
|
|
5757
5763
|
try {
|
|
5758
|
-
const resolved =
|
|
5764
|
+
const resolved = resolveTaskWorkspaceInputPath(normalizedPath, workspaceDir);
|
|
5759
5765
|
if (!(0, node_fs_1.existsSync)(resolved)) {
|
|
5760
5766
|
throw new Error('file_missing');
|
|
5761
5767
|
}
|
|
@@ -5835,27 +5841,23 @@ function resolveReviewEvidenceDir(evidenceDir) {
|
|
|
5835
5841
|
}
|
|
5836
5842
|
async function submitReviewTask(options) {
|
|
5837
5843
|
const taskContext = readTaskContextFile();
|
|
5844
|
+
const workspaceDir = resolveTaskWorkspaceDir();
|
|
5838
5845
|
if (taskContext.kind !== 'GAME_REVIEW') {
|
|
5839
5846
|
throw new Error('task_submit_review_requires_game_review');
|
|
5840
5847
|
}
|
|
5841
|
-
const state =
|
|
5848
|
+
const state = (0, review_1.normalizeTerminalReviewState)(options.state);
|
|
5842
5849
|
const reviewMessageFile = resolveReviewFilePath(options.messageFile, 'invalid_review_message_file');
|
|
5843
|
-
const creatorFeedbackFile =
|
|
5844
|
-
? null
|
|
5845
|
-
: resolveReviewFilePath(options.creatorFeedbackFile, 'invalid_creator_feedback_file');
|
|
5850
|
+
const creatorFeedbackFile = resolveReviewFilePath(options.creatorFeedbackFile, 'invalid_creator_feedback_file');
|
|
5846
5851
|
const evidenceDir = resolveReviewEvidenceDir(options.evidenceDir);
|
|
5847
5852
|
const reviewMessage = readRequiredTextFile(reviewMessageFile, 'invalid_review_message_file');
|
|
5848
|
-
const creatorFeedback =
|
|
5849
|
-
|
|
5850
|
-
: readOptionalTextFile(creatorFeedbackFile ?? undefined);
|
|
5851
|
-
const platformFeedback = readPlatformFeedbackFile(options.platformFeedbackFile);
|
|
5853
|
+
const creatorFeedback = readOptionalTextFile(creatorFeedbackFile);
|
|
5854
|
+
const platformFeedback = readPlatformFeedbackFile(options.platformFeedbackFile, workspaceDir);
|
|
5852
5855
|
await (0, review_1.validateGameReviewResult)({
|
|
5853
5856
|
reviewState: state,
|
|
5854
5857
|
reviewMessage,
|
|
5855
5858
|
creatorFeedback: creatorFeedback ?? '',
|
|
5856
5859
|
evidenceDir,
|
|
5857
5860
|
});
|
|
5858
|
-
const workspaceDir = resolveTaskWorkspaceDir();
|
|
5859
5861
|
const instrumentEvidence = (0, instrument_evidence_1.readAndValidateInstrumentEvidenceManifest)({ workspaceDir, evidenceDir });
|
|
5860
5862
|
const evidenceFiles = readReviewEvidenceFiles(evidenceDir, workspaceDir);
|
|
5861
5863
|
const ctx = await resolveTaskCommandContext('task submit-review', options.env, taskContext);
|
|
@@ -5864,6 +5866,7 @@ async function submitReviewTask(options) {
|
|
|
5864
5866
|
}
|
|
5865
5867
|
const nextSteps = await readTaskNextStepsFileForCommand({
|
|
5866
5868
|
filePath: options.nextSteps,
|
|
5869
|
+
workspaceDir,
|
|
5867
5870
|
command: 'task submit-review',
|
|
5868
5871
|
ctx,
|
|
5869
5872
|
taskContext,
|
|
@@ -6001,11 +6004,12 @@ async function failTask(options) {
|
|
|
6001
6004
|
return;
|
|
6002
6005
|
}
|
|
6003
6006
|
const taskContext = readTaskContextFile();
|
|
6007
|
+
const workspaceDir = resolveTaskWorkspaceDir();
|
|
6004
6008
|
const ctx = await resolveTaskCommandContext('task fail', options.env, taskContext);
|
|
6005
6009
|
if (!ctx) {
|
|
6006
6010
|
return;
|
|
6007
6011
|
}
|
|
6008
|
-
const platformFeedback = readPlatformFeedbackFile(options.platformFeedbackFile);
|
|
6012
|
+
const platformFeedback = readPlatformFeedbackFile(options.platformFeedbackFile, workspaceDir);
|
|
6009
6013
|
await ctx.client.workerFailAgentTask(taskContext.taskId, {
|
|
6010
6014
|
taskToken: taskContext.taskToken,
|
|
6011
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 captures.');
|
|
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 capture.');
|
|
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 capture.');
|
|
127
|
-
}
|
|
128
125
|
for (const filePath of portraitPaths) {
|
|
129
126
|
assertScreenshotPath(task, filePath, 'listing.screenshotsPortrait');
|
|
130
127
|
readPngDimensions(filePath);
|
|
@@ -134,13 +131,23 @@ 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
|
}
|
|
140
|
-
function
|
|
141
|
-
return fileKey.startsWith('
|
|
142
|
-
|| fileKey.startsWith('screenshotsLandscape:')
|
|
143
|
-
|| fileKey.startsWith('videosPortrait:')
|
|
149
|
+
function isCaptureVideoFileKey(fileKey) {
|
|
150
|
+
return fileKey.startsWith('videosPortrait:')
|
|
144
151
|
|| fileKey.startsWith('videosLandscape:');
|
|
145
152
|
}
|
|
146
153
|
function readMediaCaptureProof(input) {
|
|
@@ -160,6 +167,12 @@ function readMediaCaptureProof(input) {
|
|
|
160
167
|
if (!binding || typeof binding !== 'object' || Array.isArray(binding)) {
|
|
161
168
|
throw new Error(`agent_task_media_capture_report_invalid: ${reportPath} is missing binding.`);
|
|
162
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
|
+
}
|
|
163
176
|
if (input.expectedRuntimeBundleHash) {
|
|
164
177
|
const capturedRuntimeBundleHash = typeof binding.runtimeBundleHash === 'string'
|
|
165
178
|
? binding.runtimeBundleHash.trim().toLowerCase()
|
|
@@ -175,20 +188,21 @@ function readMediaCaptureProof(input) {
|
|
|
175
188
|
if (!rawArtifactHashes || typeof rawArtifactHashes !== 'object' || Array.isArray(rawArtifactHashes)) {
|
|
176
189
|
throw new Error(`agent_task_media_capture_report_invalid: ${reportPath} binding.artifactHashes must be an object.`);
|
|
177
190
|
}
|
|
178
|
-
const captureArtifactHashEntries = Object.
|
|
179
|
-
.
|
|
191
|
+
const captureArtifactHashEntries = Object.entries(rawArtifactHashes)
|
|
192
|
+
.filter(([artifactKey]) => (0, node_path_1.extname)(artifactKey).toLowerCase() === '.mp4')
|
|
193
|
+
.map(([, value]) => typeof value === 'string' ? value.trim().toLowerCase() : '')
|
|
180
194
|
.filter((value) => isSha256Hex(value));
|
|
181
195
|
const captureArtifactHashes = new Set(captureArtifactHashEntries);
|
|
182
196
|
if (captureArtifactHashEntries.length === 0) {
|
|
183
|
-
throw new Error(`agent_task_media_capture_report_invalid: ${reportPath} binding.artifactHashes
|
|
197
|
+
throw new Error(`agent_task_media_capture_report_invalid: ${reportPath} binding.artifactHashes must include at least one MP4 gameplay video.`);
|
|
184
198
|
}
|
|
185
199
|
const captureArtifactHashCounts = new Map();
|
|
186
200
|
for (const hash of captureArtifactHashEntries) {
|
|
187
201
|
captureArtifactHashCounts.set(hash, (captureArtifactHashCounts.get(hash) ?? 0) + 1);
|
|
188
202
|
}
|
|
189
|
-
const mediaFiles = input.preparedSessionFiles.filter((file) =>
|
|
203
|
+
const mediaFiles = input.preparedSessionFiles.filter((file) => isCaptureVideoFileKey(file.fileKey));
|
|
190
204
|
if (mediaFiles.length === 0) {
|
|
191
|
-
throw new Error(`agent_task_media_capture_artifacts_missing: ${input.task.name} must upload recorder
|
|
205
|
+
throw new Error(`agent_task_media_capture_artifacts_missing: ${input.task.name} must upload recorder gameplay video in listing.videos*.`);
|
|
192
206
|
}
|
|
193
207
|
const uploadedMediaHashCounts = new Map();
|
|
194
208
|
for (const file of mediaFiles) {
|
|
@@ -197,21 +211,19 @@ function readMediaCaptureProof(input) {
|
|
|
197
211
|
const missingUploadedCaptureArtifacts = [...captureArtifactHashCounts.entries()]
|
|
198
212
|
.filter(([hash, expectedCount]) => (uploadedMediaHashCounts.get(hash) ?? 0) < expectedCount);
|
|
199
213
|
if (missingUploadedCaptureArtifacts.length > 0) {
|
|
200
|
-
throw new Error(`agent_task_media_capture_artifact_missing_from_listing: ${input.task.name} must list every recorder
|
|
214
|
+
throw new Error(`agent_task_media_capture_artifact_missing_from_listing: ${input.task.name} must list every recorder gameplay video from listing.captureReport in catalogue.json.`);
|
|
201
215
|
}
|
|
202
|
-
let hasStill = false;
|
|
203
216
|
let hasVideo = false;
|
|
204
217
|
const artifactHashes = {};
|
|
205
218
|
for (const file of mediaFiles) {
|
|
206
219
|
if (!captureArtifactHashes.has(file.sha256)) {
|
|
207
220
|
throw new Error(`agent_task_media_capture_artifact_mismatch: ${file.fileKey} was not produced by listing.captureReport for ${input.task.name}.`);
|
|
208
221
|
}
|
|
209
|
-
hasStill = hasStill || file.fileKey.startsWith('screenshots');
|
|
210
222
|
hasVideo = hasVideo || file.fileKey.startsWith('videos');
|
|
211
223
|
artifactHashes[file.fileKey] = file.sha256;
|
|
212
224
|
}
|
|
213
|
-
if (!
|
|
214
|
-
throw new Error(`agent_task_media_capture_artifacts_missing: ${input.task.name} must upload at least one recorder
|
|
225
|
+
if (!hasVideo) {
|
|
226
|
+
throw new Error(`agent_task_media_capture_artifacts_missing: ${input.task.name} must upload at least one recorder gameplay video.`);
|
|
215
227
|
}
|
|
216
228
|
const capturedSurfaces = Array.isArray(binding.capturedSurfaces)
|
|
217
229
|
? Array.from(new Set(binding.capturedSurfaces
|
|
@@ -312,6 +324,15 @@ function readWorkerUploadPreflightContext(projectDir) {
|
|
|
312
324
|
function assertAgentGameHeroListingPreflight(task) {
|
|
313
325
|
assertHeroListingArt(task);
|
|
314
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
|
+
}
|
|
315
336
|
function assertAgentGameScreenshotListingPreflight(task) {
|
|
316
337
|
assertListingScreenshots(task);
|
|
317
338
|
}
|
|
@@ -325,7 +346,10 @@ function assertAgentGameCaptureReportPreflight(task, options) {
|
|
|
325
346
|
}
|
|
326
347
|
}
|
|
327
348
|
function assertAgentGameListingPreflight(task, options) {
|
|
349
|
+
assertAgentGameIconListingPreflight(task);
|
|
328
350
|
assertAgentGameHeroListingPreflight(task);
|
|
351
|
+
assertAgentGamePreviewListingPreflight(task);
|
|
352
|
+
assertAgentGameVideoListingPreflight(task);
|
|
329
353
|
assertAgentGameScreenshotListingPreflight(task);
|
|
330
354
|
assertAgentGameCaptureReportPreflight(task, options);
|
|
331
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.
|
|
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": {
|