@playdrop/playdrop-cli 0.12.26 → 0.12.28

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 (38) hide show
  1. package/config/client-meta.json +1 -1
  2. package/dist/apps/build.js +43 -0
  3. package/dist/apps/index.d.ts +2 -1
  4. package/dist/apps/index.js +1 -0
  5. package/dist/apps/upload.d.ts +2 -1
  6. package/dist/apps/upload.js +1 -0
  7. package/dist/commands/devRuntimeAssets.js +26 -124
  8. package/dist/commands/ownedRuntimeImageValidation.d.ts +7 -0
  9. package/dist/commands/ownedRuntimeImageValidation.js +76 -0
  10. package/dist/commands/upload.d.ts +7 -1
  11. package/dist/commands/upload.js +79 -402
  12. package/dist/commands/worker.d.ts +16 -0
  13. package/dist/commands/worker.js +164 -35
  14. package/dist/listingPreflight.d.ts +1 -0
  15. package/dist/listingPreflight.js +17 -0
  16. package/node_modules/@playdrop/api-client/dist/client.d.ts +2 -1
  17. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  18. package/node_modules/@playdrop/api-client/dist/core/errors.d.ts.map +1 -1
  19. package/node_modules/@playdrop/api-client/dist/core/errors.js +3 -1
  20. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +2 -1
  21. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
  22. package/node_modules/@playdrop/api-client/dist/domains/apps.js +11 -0
  23. package/node_modules/@playdrop/api-client/dist/index.d.ts +2 -1
  24. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  25. package/node_modules/@playdrop/api-client/dist/index.js +5 -0
  26. package/node_modules/@playdrop/config/client-meta.json +1 -1
  27. package/node_modules/@playdrop/types/dist/agent-task-playtest.d.ts +23 -0
  28. package/node_modules/@playdrop/types/dist/agent-task-playtest.d.ts.map +1 -0
  29. package/node_modules/@playdrop/types/dist/agent-task-playtest.js +84 -0
  30. package/node_modules/@playdrop/types/dist/api.d.ts +13 -0
  31. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  32. package/node_modules/@playdrop/types/dist/index.d.ts +1 -0
  33. package/node_modules/@playdrop/types/dist/index.d.ts.map +1 -1
  34. package/node_modules/@playdrop/types/dist/index.js +3 -1
  35. package/node_modules/@playdrop/types/dist/version.d.ts +4 -2
  36. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  37. package/node_modules/@playdrop/types/dist/version.js +13 -3
  38. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "0.12.8",
3
- "build": 24,
3
+ "build": 31,
4
4
  "runtimeSdkVersion": "0.12.5",
5
5
  "runtimeSdkBuild": 2,
6
6
  "clients": {
@@ -226,8 +226,41 @@ function appendIncludedProjectFile(rootDir, includePath, excludedRelativeFiles,
226
226
  results.push(record);
227
227
  fileByRelativePath.set(record.relativePath, record);
228
228
  }
229
+ function appendIncludedProjectDirectory(rootDir, includeDirectory, excludedRelativeFiles, fileByRelativePath, results, enforceReservedBundleNames) {
230
+ const normalizedDirectory = (0, node_path_1.normalize)(includeDirectory).split(node_path_1.sep).join('/').replace(/\/+$/, '');
231
+ if (!normalizedDirectory || normalizedDirectory === '.' || normalizedDirectory.startsWith('..')) {
232
+ throw new Error(`[apps][build] Invalid include directory path "${includeDirectory}".`);
233
+ }
234
+ const absoluteDirectory = (0, node_path_1.resolve)(rootDir, normalizedDirectory);
235
+ if (!isPathWithinRoot(rootDir, absoluteDirectory)) {
236
+ throw new Error(`[apps][build] Included directory "${includeDirectory}" escapes the project root.`);
237
+ }
238
+ if (!(0, node_fs_1.existsSync)(absoluteDirectory) || !(0, node_fs_1.statSync)(absoluteDirectory).isDirectory()) {
239
+ return;
240
+ }
241
+ const stack = [absoluteDirectory];
242
+ while (stack.length > 0) {
243
+ const current = stack.pop();
244
+ for (const entry of readProjectDirEntries(current)) {
245
+ const absolutePath = (0, node_path_1.join)(current, entry.name);
246
+ const relativePath = normalizeRelativePath(rootDir, absolutePath);
247
+ if (isExcludedRelativePath(entry.isDirectory() ? `${relativePath}/` : relativePath, excludedRelativeFiles)) {
248
+ continue;
249
+ }
250
+ if (entry.isDirectory()) {
251
+ stack.push(absolutePath);
252
+ continue;
253
+ }
254
+ if (!entry.isFile()) {
255
+ continue;
256
+ }
257
+ appendIncludedProjectFile(rootDir, relativePath, excludedRelativeFiles, fileByRelativePath, results, enforceReservedBundleNames);
258
+ }
259
+ }
260
+ }
229
261
  function collectProjectFiles(rootDir, rules, options) {
230
262
  const includeRelativeFiles = options?.includeRelativeFiles ?? [];
263
+ const includeRelativeDirectories = options?.includeRelativeDirectories ?? [];
231
264
  const excludeRelativeFiles = new Set(Array.from(options?.excludeRelativeFiles ?? [])
232
265
  .map((filePath) => (0, node_path_1.normalize)(filePath).split(node_path_1.sep).join('/'))
233
266
  .filter((filePath) => filePath.length > 0 && filePath !== '.'));
@@ -285,6 +318,9 @@ function collectProjectFiles(rootDir, rules, options) {
285
318
  for (const includePath of includeRelativeFiles) {
286
319
  appendIncludedProjectFile(rootDir, includePath, excludeRelativeFiles, fileByRelativePath, results, enforceReservedBundleNames);
287
320
  }
321
+ for (const includeDirectory of includeRelativeDirectories) {
322
+ appendIncludedProjectDirectory(rootDir, includeDirectory, excludeRelativeFiles, fileByRelativePath, results, enforceReservedBundleNames);
323
+ }
288
324
  results.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
289
325
  return results;
290
326
  }
@@ -496,6 +532,11 @@ function createZipArchive(entries) {
496
532
  function createSourceArchive(task) {
497
533
  const rules = buildIgnoreRules(task.projectDir);
498
534
  const includeFiles = ['README.md', 'AGENTS.md'];
535
+ for (const heroPath of [task.listing?.heroPortraitPath, task.listing?.heroLandscapePath]) {
536
+ if (heroPath) {
537
+ includeFiles.push(normalizeRelativePath(task.projectDir, heroPath));
538
+ }
539
+ }
499
540
  const excludedFiles = task.sourceArchiveExcludeRelativeFiles ?? [];
500
541
  if (task.catalogueAbsolutePath && (0, node_fs_1.existsSync)(task.catalogueAbsolutePath)) {
501
542
  const relativeCataloguePath = normalizeRelativePath(task.projectDir, task.catalogueAbsolutePath) || 'catalogue.json';
@@ -509,6 +550,7 @@ function createSourceArchive(task) {
509
550
  }
510
551
  const entries = collectProjectFiles(task.projectDir, rules, {
511
552
  includeRelativeFiles: includeFiles,
553
+ includeRelativeDirectories: ['assets/art-direction'],
512
554
  excludeRelativeFiles: excludedFiles,
513
555
  });
514
556
  const buffer = createZipArchive(entries);
@@ -519,6 +561,7 @@ function createSourceArchive(task) {
519
561
  }
520
562
  const entries = collectProjectFiles(task.projectDir, rules, {
521
563
  includeRelativeFiles: includeFiles,
564
+ includeRelativeDirectories: ['assets/art-direction'],
522
565
  excludeRelativeFiles: excludedFiles,
523
566
  });
524
567
  const buffer = createZipArchive(entries);
@@ -1,5 +1,5 @@
1
1
  import type { ApiClient } from '@playdrop/api-client';
2
- import type { UserResponse } from '@playdrop/types';
2
+ import type { AgentTaskPlaytestProof, UserResponse } from '@playdrop/types';
3
3
  import type { AppTask } from '../catalogue';
4
4
  import type { TaskCaptureSession } from '../appUrls';
5
5
  import { collectAppValidationWarnings, validateAppTask, runFormatScript } from './validate';
@@ -23,6 +23,7 @@ export type AppPipelineOptions = {
23
23
  loadCheckTimeoutMs?: number;
24
24
  ensureRegisteredAppShell?: boolean;
25
25
  agentTaskId?: number;
26
+ playtestProof?: AgentTaskPlaytestProof;
26
27
  mediaCaptureRequired?: boolean;
27
28
  captureSession?: TaskCaptureSession | null;
28
29
  };
@@ -64,6 +64,7 @@ async function runAppPipeline(client, task, options) {
64
64
  runStagedUploadLoadCheck: options?.runStagedUploadLoadCheck,
65
65
  loadCheckTimeoutMs: options?.loadCheckTimeoutMs,
66
66
  agentTaskId: options?.agentTaskId,
67
+ playtestProof: options?.playtestProof,
67
68
  mediaCaptureRequired: options?.mediaCaptureRequired,
68
69
  captureSession: options?.captureSession,
69
70
  };
@@ -1,5 +1,5 @@
1
1
  import type { ApiClient } from '@playdrop/api-client';
2
- import type { UserResponse } from '@playdrop/types';
2
+ import type { AgentTaskPlaytestProof, UserResponse } from '@playdrop/types';
3
3
  import type { AppTask } from '../catalogue';
4
4
  import type { TaskCaptureSession } from '../appUrls';
5
5
  import type { AppBuildArtifacts } from './build';
@@ -21,6 +21,7 @@ export type AppUploadOptions = {
21
21
  runStagedUploadLoadCheck?: boolean;
22
22
  loadCheckTimeoutMs?: number;
23
23
  agentTaskId?: number;
24
+ playtestProof?: AgentTaskPlaytestProof;
24
25
  mediaCaptureRequired?: boolean;
25
26
  captureSession?: TaskCaptureSession | null;
26
27
  };
@@ -232,6 +232,7 @@ async function uploadAppVersion(client, task, artifacts, options) {
232
232
  const initializeRequest = {
233
233
  version: task.version,
234
234
  agentTaskId: options?.agentTaskId,
235
+ playtestProof: options?.playtestProof,
235
236
  releaseNotes: task.releaseNotes,
236
237
  visibility: task.versionVisibility,
237
238
  setAsCurrent: task.versionVisibility === 'PRIVATE' ? false : undefined,
@@ -37,6 +37,7 @@ const CONTENT_TYPE_BY_EXTENSION = {
37
37
  '.png': 'image/png',
38
38
  '.jpg': 'image/jpeg',
39
39
  '.jpeg': 'image/jpeg',
40
+ '.gif': 'image/gif',
40
41
  '.svg': 'image/svg+xml',
41
42
  '.webp': 'image/webp',
42
43
  '.glb': 'model/gltf-binary',
@@ -52,25 +53,6 @@ function inferContentType(filePath) {
52
53
  const extension = (0, node_path_1.extname)(filePath).toLowerCase();
53
54
  return CONTENT_TYPE_BY_EXTENSION[extension] ?? null;
54
55
  }
55
- function normalizeManifestFiles(fileManifest) {
56
- const files = fileManifest && typeof fileManifest === 'object' && Array.isArray(fileManifest.files)
57
- ? fileManifest.files
58
- : [];
59
- return files
60
- .filter((entry) => Boolean(entry && typeof entry === 'object'))
61
- .map((entry) => ({
62
- role: typeof entry.role === 'string' ? entry.role.trim().toLowerCase() : '',
63
- key: typeof entry.key === 'string' ? entry.key.trim() : '',
64
- contentType: typeof entry.contentType === 'string' ? entry.contentType : null,
65
- sizeBytes: typeof entry.sizeBytes === 'number' && Number.isFinite(entry.sizeBytes) ? entry.sizeBytes : null,
66
- sha256: typeof entry.sha256 === 'string' ? entry.sha256 : null,
67
- }))
68
- .filter((entry) => entry.role.length > 0);
69
- }
70
- function buildPublishedAssetFileUrl(apiBase, creatorUsername, assetName, revision, role) {
71
- const path = `/assets/${encodeURIComponent(creatorUsername)}/${encodeURIComponent(assetName)}/versions/${encodeURIComponent(`r${revision}`)}/file?role=${encodeURIComponent(role)}`;
72
- return new URL(path, apiBase).toString();
73
- }
74
56
  function buildSyntheticLocalAssetRef(creatorUsername, appName, ownedAssetName) {
75
57
  return (0, types_1.formatContentVersionRef)({
76
58
  kind: 'asset',
@@ -87,86 +69,6 @@ function buildLocalDevRuntimeAssetFileUrl(appBaseUrl, ownedAssetName, role) {
87
69
  const base = appBaseUrl.endsWith('/') ? appBaseUrl.slice(0, -1) : appBaseUrl;
88
70
  return `${base}/_playdrop/runtime-assets/files/${encodeURIComponent(ownedAssetName)}?role=${encodeURIComponent(role)}`;
89
71
  }
90
- async function findAssetVersionByRevision(client, creatorUsername, assetName, revision) {
91
- let offset = 0;
92
- for (let page = 0; page < 200; page += 1) {
93
- const response = await client.listAssetVersions(creatorUsername, assetName, {
94
- limit: 100,
95
- offset,
96
- });
97
- const version = (response.versions ?? []).find((entry) => entry.revision === revision);
98
- if (version) {
99
- return version;
100
- }
101
- if (!response.pagination?.hasMore) {
102
- break;
103
- }
104
- offset += response.pagination.limit;
105
- }
106
- throw new Error(`dev_runtime_asset_version_not_found:${creatorUsername}/${assetName}@r${revision}`);
107
- }
108
- async function findPackVersion(client, creatorUsername, packName, versionString) {
109
- let offset = 0;
110
- for (let page = 0; page < 200; page += 1) {
111
- const response = await client.listAssetPackVersions(creatorUsername, packName, {
112
- limit: 100,
113
- offset,
114
- });
115
- const version = (response.versions ?? []).find((entry) => entry.version === versionString);
116
- if (version) {
117
- return version;
118
- }
119
- if (!response.pagination?.hasMore) {
120
- break;
121
- }
122
- offset += response.pagination.limit;
123
- }
124
- throw new Error(`dev_runtime_pack_version_not_found:${creatorUsername}/${packName}@${versionString}`);
125
- }
126
- function createPublishedAssetSummary(input) {
127
- const files = normalizeManifestFiles(input.version.fileManifest).map((file) => ({
128
- role: file.role,
129
- url: buildPublishedAssetFileUrl(input.apiBase, input.creatorUsername, input.assetName, input.revision, file.role),
130
- contentType: file.contentType ?? null,
131
- sizeBytes: file.sizeBytes ?? null,
132
- sha256: file.sha256 ?? null,
133
- }));
134
- if (files.length === 0) {
135
- throw new Error(`dev_runtime_asset_files_missing:${input.creatorUsername}/${input.assetName}@r${input.revision}`);
136
- }
137
- return {
138
- assetRef: (0, types_1.formatContentVersionRef)({
139
- kind: 'asset',
140
- creatorUsername: input.creatorUsername,
141
- name: input.assetName,
142
- revision: input.revision,
143
- }),
144
- runtimeKey: input.runtimeKey?.trim() || null,
145
- sourceType: input.sourceType,
146
- ...(input.sourcePackRef ? { sourcePackRef: input.sourcePackRef } : {}),
147
- files,
148
- };
149
- }
150
- async function resolvePublishedAssetSummary(client, apiBase, assetRef, options) {
151
- const parsed = (0, types_1.parseContentVersionRef)(assetRef);
152
- if (!parsed || parsed.kind !== 'asset') {
153
- throw new Error(`dev_runtime_asset_ref_invalid:${assetRef}`);
154
- }
155
- const version = await findAssetVersionByRevision(client, parsed.creatorUsername, parsed.name, parsed.revision);
156
- if (version.visibility !== 'PUBLIC') {
157
- throw new Error(`dev_runtime_asset_not_public:${assetRef}`);
158
- }
159
- return createPublishedAssetSummary({
160
- apiBase,
161
- creatorUsername: parsed.creatorUsername,
162
- assetName: parsed.name,
163
- revision: parsed.revision,
164
- version,
165
- runtimeKey: options.runtimeKey,
166
- sourceType: options.sourceType,
167
- sourcePackRef: options.sourcePackRef,
168
- });
169
- }
170
72
  function createLocalOwnedAssetSummary(task, creatorUsername, appBaseUrl, ownedAsset) {
171
73
  const files = {};
172
74
  const runtimeFiles = [];
@@ -260,41 +162,41 @@ async function buildDevRuntimeAssetManifest(input) {
260
162
  assets.push(localAsset.asset);
261
163
  ownedAssetFiles[ownedAsset.name] = localAsset.files;
262
164
  }
263
- for (const dependency of input.task.uses.assets) {
165
+ const directDependencies = input.task.uses.assets.map((dependency) => {
264
166
  assertRuntimeKeyAvailable(seenRuntimeKeys, dependency.runtimeKey, dependency.ref);
265
167
  const parsedDependencyRef = (0, types_1.parseContentVersionRef)(dependency.ref);
266
168
  if (!parsedDependencyRef || parsedDependencyRef.kind !== 'asset') {
267
169
  throw new Error(`dev_runtime_asset_ref_invalid:${dependency.ref}`);
268
170
  }
269
- const dependencyVersion = await findAssetVersionByRevision(input.client, parsedDependencyRef.creatorUsername, parsedDependencyRef.name, parsedDependencyRef.revision);
270
- assertCustomAssetVersionSupported(input.task, dependency.ref, dependencyVersion);
271
- assets.push(await resolvePublishedAssetSummary(input.client, input.apiBase, dependency.ref, {
272
- runtimeKey: dependency.runtimeKey,
273
- sourceType: 'DIRECT',
274
- }));
275
- }
276
- for (const packRef of input.task.uses.packs) {
171
+ return {
172
+ ref: (0, types_1.formatContentVersionRef)(parsedDependencyRef),
173
+ runtimeKey: dependency.runtimeKey?.trim() || null,
174
+ };
175
+ });
176
+ const packDependencies = input.task.uses.packs.map((packRef) => {
277
177
  const parsedPackRef = (0, types_1.parseContentVersionRef)(packRef);
278
178
  if (!parsedPackRef || parsedPackRef.kind !== 'pack') {
279
179
  throw new Error(`dev_runtime_pack_ref_invalid:${packRef}`);
280
180
  }
281
- const packVersion = await findPackVersion(input.client, parsedPackRef.creatorUsername, parsedPackRef.name, parsedPackRef.version);
282
- if (packVersion.visibility !== 'PUBLIC') {
283
- throw new Error(`dev_runtime_pack_not_public:${packRef}`);
284
- }
285
- for (const member of packVersion.assets) {
286
- const memberRef = (0, types_1.formatContentVersionRef)({
287
- kind: 'asset',
288
- creatorUsername: member.creatorUsername,
289
- name: member.name,
290
- revision: member.revision,
181
+ return (0, types_1.formatContentVersionRef)(parsedPackRef);
182
+ });
183
+ if (directDependencies.length > 0 || packDependencies.length > 0) {
184
+ const resolved = await input.client.resolveRuntimeAssets({
185
+ assets: directDependencies,
186
+ packs: packDependencies,
187
+ });
188
+ for (const entry of resolved.assets) {
189
+ if (entry.sourceType !== 'DIRECT' && entry.sourceType !== 'PACK') {
190
+ throw new Error(`dev_runtime_asset_source_type_invalid:${entry.assetRef}:${entry.sourceType}`);
191
+ }
192
+ assertCustomAssetVersionSupported(input.task, entry.assetRef, entry);
193
+ assets.push({
194
+ assetRef: entry.assetRef,
195
+ runtimeKey: entry.runtimeKey,
196
+ sourceType: entry.sourceType,
197
+ ...(entry.sourcePackRef ? { sourcePackRef: entry.sourcePackRef } : {}),
198
+ files: entry.files,
291
199
  });
292
- const memberVersion = await findAssetVersionByRevision(input.client, member.creatorUsername, member.name, member.revision);
293
- assertCustomAssetVersionSupported(input.task, memberRef, memberVersion);
294
- assets.push(await resolvePublishedAssetSummary(input.client, input.apiBase, memberRef, {
295
- sourceType: 'PACK',
296
- sourcePackRef: packRef,
297
- }));
298
200
  }
299
201
  }
300
202
  return {
@@ -0,0 +1,7 @@
1
+ import type { AppTask } from '../catalogue';
2
+ export declare function validateOwnedRuntimeImageFile(input: {
3
+ assetName: string;
4
+ filePath: string;
5
+ declaredFormat?: string;
6
+ }): Promise<void>;
7
+ export declare function assertOwnedRuntimeImagesAreValid(task: AppTask): Promise<void>;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.validateOwnedRuntimeImageFile = validateOwnedRuntimeImageFile;
7
+ exports.assertOwnedRuntimeImagesAreValid = assertOwnedRuntimeImagesAreValid;
8
+ const node_fs_1 = require("node:fs");
9
+ const node_path_1 = require("node:path");
10
+ const sharp_1 = __importDefault(require("sharp"));
11
+ const IMAGE_BY_EXTENSION = {
12
+ '.png': { format: 'png', contentType: 'image/png' },
13
+ '.jpg': { format: 'jpeg', contentType: 'image/jpeg' },
14
+ '.jpeg': { format: 'jpeg', contentType: 'image/jpeg' },
15
+ '.webp': { format: 'webp', contentType: 'image/webp' },
16
+ '.gif': { format: 'gif', contentType: 'image/gif' },
17
+ '.svg': { format: 'svg', contentType: 'image/svg+xml' },
18
+ };
19
+ const SHARP_FORMAT_BY_DECLARED_FORMAT = {
20
+ PNG: 'png',
21
+ JPEG: 'jpeg',
22
+ WEBP: 'webp',
23
+ GIF: 'gif',
24
+ };
25
+ async function validateOwnedRuntimeImageFile(input) {
26
+ let stats;
27
+ try {
28
+ stats = (0, node_fs_1.statSync)(input.filePath);
29
+ }
30
+ catch {
31
+ throw new Error(`agent_task_owned_image_file_missing:${input.assetName}:${input.filePath}`);
32
+ }
33
+ if (!stats.isFile()) {
34
+ throw new Error(`agent_task_owned_image_file_missing:${input.assetName}:${input.filePath}`);
35
+ }
36
+ const extension = (0, node_path_1.extname)(input.filePath).toLowerCase();
37
+ const expected = IMAGE_BY_EXTENSION[extension];
38
+ if (!expected) {
39
+ throw new Error(`agent_task_owned_image_format_unsupported:${input.assetName}:${extension || 'missing_extension'}`);
40
+ }
41
+ let metadata;
42
+ try {
43
+ metadata = await (0, sharp_1.default)(input.filePath, { failOn: 'error' }).metadata();
44
+ }
45
+ catch {
46
+ throw new Error(`agent_task_owned_image_decode_failed:${input.assetName}:${input.filePath}`);
47
+ }
48
+ if (!Number.isInteger(metadata.width) || Number(metadata.width) <= 0
49
+ || !Number.isInteger(metadata.height) || Number(metadata.height) <= 0) {
50
+ throw new Error(`agent_task_owned_image_dimensions_invalid:${input.assetName}:${input.filePath}`);
51
+ }
52
+ if (metadata.format !== expected.format) {
53
+ throw new Error(`agent_task_owned_image_format_mismatch:${input.assetName}:extension=${extension}:contentType=${expected.contentType}:decoded=${metadata.format ?? 'unknown'}`);
54
+ }
55
+ const declaredFormat = input.declaredFormat?.trim().toUpperCase() ?? '';
56
+ const declaredSharpFormat = SHARP_FORMAT_BY_DECLARED_FORMAT[declaredFormat];
57
+ if (declaredSharpFormat && declaredSharpFormat !== metadata.format) {
58
+ throw new Error(`agent_task_owned_image_format_mismatch:${input.assetName}:declared=${declaredFormat}:decoded=${metadata.format}`);
59
+ }
60
+ }
61
+ async function assertOwnedRuntimeImagesAreValid(task) {
62
+ for (const ownedAsset of task.ownedAssets) {
63
+ if (!ownedAsset.runtimeKey || ownedAsset.category !== 'IMAGE') {
64
+ continue;
65
+ }
66
+ const primaryPath = ownedAsset.filePaths?.primary;
67
+ if (!primaryPath) {
68
+ throw new Error(`agent_task_owned_image_file_missing:${ownedAsset.name}:primary`);
69
+ }
70
+ await validateOwnedRuntimeImageFile({
71
+ assetName: ownedAsset.name,
72
+ filePath: primaryPath,
73
+ declaredFormat: ownedAsset.format,
74
+ });
75
+ }
76
+ }
@@ -1,10 +1,13 @@
1
1
  import type { ApiClient } from '@playdrop/api-client';
2
- import type { AgentExecutionTarget, UserResponse } from '@playdrop/types';
2
+ import type { AgentExecutionTarget, AgentTaskPlaytestProof, UserResponse } from '@playdrop/types';
3
+ import { type AppTask } from '../catalogue';
3
4
  export type UploadCommandOptions = {
4
5
  env?: string;
5
6
  skipReview?: boolean;
6
7
  clearTags?: boolean;
7
8
  };
9
+ export declare function resolveUploadOwner(actorUsername: string | null | undefined, workspaceOwnerUsername: string | null | undefined): string;
10
+ export declare function resolveDefaultUploadCreator(owner: string | null | undefined, actorUsername: string | null | undefined): string;
8
11
  export declare function upload(pathOrName: string, options?: UploadCommandOptions): Promise<void>;
9
12
  export type WorkerAppPublishInput = {
10
13
  client: ApiClient;
@@ -33,4 +36,7 @@ export type WorkerAppPublishResult = {
33
36
  warnings: string[];
34
37
  };
35
38
  export type WorkerPlaydropAssetRequirement = 'PACK' | 'ASSET_OR_PACK';
39
+ export declare function assertTaskPlaytestEvidenceManifest(task: AppTask, options?: {
40
+ requireOutcomeProof?: boolean;
41
+ }): AgentTaskPlaytestProof | null;
36
42
  export declare function publishWorkerAppProject(input: WorkerAppPublishInput): Promise<WorkerAppPublishResult>;