@playdrop/playdrop-cli 0.10.21 → 0.11.4
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 +1 -1
- package/dist/appUrls.d.ts +3 -0
- package/dist/appUrls.js +12 -0
- package/dist/apps/launchCheck.d.ts +8 -1
- package/dist/apps/launchCheck.js +96 -10
- package/dist/apps/upload.js +5 -0
- package/dist/apps/validate.js +0 -1
- package/dist/captureRuntime.js +8 -1
- package/dist/catalogue.d.ts +3 -1
- package/dist/catalogue.js +12 -0
- package/dist/commands/captureRemote.d.ts +1 -0
- package/dist/commands/captureRemote.js +45 -1
- package/dist/commands/upload.js +346 -101
- package/dist/commands/worker.d.ts +12 -1
- package/dist/commands/worker.js +228 -26
- package/dist/index.js +10 -0
- package/node_modules/@playdrop/api-client/dist/client.d.ts +3 -1
- package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts +3 -1
- package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.d.ts.map +1 -1
- package/node_modules/@playdrop/api-client/dist/domains/agent-tasks.js +23 -0
- package/node_modules/@playdrop/config/client-meta.json +1 -1
- package/node_modules/@playdrop/types/dist/api.d.ts +89 -1
- package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/api.js +17 -1
- package/node_modules/@playdrop/types/dist/version.d.ts +22 -0
- package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/version.js +108 -0
- package/package.json +1 -1
package/dist/commands/upload.js
CHANGED
|
@@ -50,6 +50,8 @@ const taskUtils_1 = require("../taskUtils");
|
|
|
50
50
|
const uploadLog_1 = require("../uploadLog");
|
|
51
51
|
const upload_content_1 = require("./upload-content");
|
|
52
52
|
const appUrls_1 = require("../appUrls");
|
|
53
|
+
const launchCheck_1 = require("../apps/launchCheck");
|
|
54
|
+
const devRuntimeAssets_1 = require("./devRuntimeAssets");
|
|
53
55
|
const upload_graph_1 = require("./upload-graph");
|
|
54
56
|
function normalizeSavedItemKinds(kinds) {
|
|
55
57
|
return [...new Set(Array.isArray(kinds) ? kinds : [])].sort();
|
|
@@ -1250,9 +1252,6 @@ function appTaskSatisfiesPlaydropAssetRequirement(task, requirement) {
|
|
|
1250
1252
|
});
|
|
1251
1253
|
return hasFirstPartyPack || hasFirstPartyAsset || task.ownedAssets.length > 0;
|
|
1252
1254
|
}
|
|
1253
|
-
function appTaskHasRequiredNewGameAssets(task) {
|
|
1254
|
-
return task.uses.packs.length > 0 || task.uses.assets.length > 0 || task.ownedAssets.length > 0;
|
|
1255
|
-
}
|
|
1256
1255
|
function isUnsafeDirectPlaydropAiImageAsset(detail, parsedRevision) {
|
|
1257
1256
|
const asset = detail?.asset;
|
|
1258
1257
|
if (!asset || asset.category !== 'IMAGE') {
|
|
@@ -1285,7 +1284,7 @@ async function assertNewGameDirectPlaydropAssetsAreRuntimeSafe(input) {
|
|
|
1285
1284
|
}
|
|
1286
1285
|
}
|
|
1287
1286
|
if (unsafeRefs.length > 0) {
|
|
1288
|
-
throw new Error(`agent_task_playdrop_direct_ai_image_asset_unsafe: direct PlayDrop AI JPEG image assets cannot be used as new-game runtime sprites because their backgrounds are baked into the image. Use a PlayDrop asset pack or create owned PNG assets through the PlayDrop
|
|
1287
|
+
throw new Error(`agent_task_playdrop_direct_ai_image_asset_unsafe: direct PlayDrop AI JPEG image assets cannot be used as new-game runtime sprites because their backgrounds are baked into the image. Use a PlayDrop asset pack or create clean owned PNG assets through the PlayDrop make-assets workflow instead. Unsafe refs: ${unsafeRefs.join(', ')}`);
|
|
1289
1288
|
}
|
|
1290
1289
|
}
|
|
1291
1290
|
function isOwnedRuntimeImageAsset(task, ownedAsset) {
|
|
@@ -1605,7 +1604,7 @@ async function assertNewGameOwnedRuntimeImageAssetsAreClean(task) {
|
|
|
1605
1604
|
}
|
|
1606
1605
|
}
|
|
1607
1606
|
if (unsafeAssets.length > 0) {
|
|
1608
|
-
throw new Error(`agent_task_owned_image_asset_checkerboard_background: owned runtime image assets must not contain baked transparency checkerboard, matte-color, or preview backgrounds. Recreate the asset through the PlayDrop
|
|
1607
|
+
throw new Error(`agent_task_owned_image_asset_checkerboard_background: owned runtime image assets must not contain baked transparency checkerboard, matte-color, or preview backgrounds. Recreate the asset through the PlayDrop make-assets workflow, use a cleaner PlayDrop asset, or use the image as non-runtime listing art only. Unsafe owned assets: ${unsafeAssets.join(', ')}`);
|
|
1609
1608
|
}
|
|
1610
1609
|
}
|
|
1611
1610
|
const RUNTIME_SOURCE_EXTENSIONS = new Set([
|
|
@@ -1673,6 +1672,95 @@ function hasDisallowedGameplayAssetFallback(source) {
|
|
|
1673
1672
|
const executableSource = stripRuntimeSourceCommentsAndStrings(source);
|
|
1674
1673
|
return /\bdrawFallback[A-Za-z0-9_]*\b|\bfallback(?:Image|Sprite|Asset|Draw|Visual|Character|Obstacle|Collectible|Prop|Enemy|Item|Piñata|Pinata|Candy)?\b|\bplaceholder(?:Image|Sprite|Asset|Draw|Visual|Character|Obstacle|Collectible|Prop|Enemy|Item)?\b|img\.onerror\s*=\s*(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>\s*(?:\{[^}]*\bresolve\s*\(|\bresolve\s*\(|\{[^}]*\breturn\b)|if\s*\([^)]*!url[^)]*\)\s*\{[^}]*\bresolve\s*\([^}]*\breturn\b|if\s*\([^)]*!url[^)]*\)\s*(?:return|continue|resolve\s*\()/i.test(executableSource);
|
|
1675
1674
|
}
|
|
1675
|
+
function formatRuntimeSourceLocation(runtimeSource, offset) {
|
|
1676
|
+
const location = resolveRuntimeSourceLocation(runtimeSource, offset);
|
|
1677
|
+
if (!location) {
|
|
1678
|
+
return 'unknown source location';
|
|
1679
|
+
}
|
|
1680
|
+
return `${location.path}:${location.line}:${location.column}`;
|
|
1681
|
+
}
|
|
1682
|
+
function findRuntimeSourcePattern(runtimeSource, pattern) {
|
|
1683
|
+
const match = pattern.exec(runtimeSource.source);
|
|
1684
|
+
if (!match) {
|
|
1685
|
+
return null;
|
|
1686
|
+
}
|
|
1687
|
+
return {
|
|
1688
|
+
match: match[0],
|
|
1689
|
+
location: formatRuntimeSourceLocation(runtimeSource, match.index),
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1692
|
+
function assertNoLaunchCheckConditionalSource(runtimeSource) {
|
|
1693
|
+
const directMarker = findRuntimeSourcePattern(runtimeSource, /\b(?:launchCheck|launch-check|upload-check|previewToken|localDevPort)\b/i);
|
|
1694
|
+
if (directMarker) {
|
|
1695
|
+
throw new Error(`agent_task_launch_check_conditional_disallowed: game source must not branch on PlayDrop validation markers. First match "${directMarker.match}" at ${directMarker.location}.`);
|
|
1696
|
+
}
|
|
1697
|
+
const environmentSniff = findRuntimeSourcePattern(runtimeSource, /(?:document\.referrer|window\.location|\blocation\.(?:href|search|origin)|new\s+URLSearchParams)[\s\S]{0,320}(?:localhost|127\.0\.0\.1|_auth\/creators|\/upload-check\/)|(?:localhost|127\.0\.0\.1|_auth\/creators|\/upload-check\/)[\s\S]{0,320}(?:document\.referrer|window\.location|\blocation\.(?:href|search|origin)|new\s+URLSearchParams)/i);
|
|
1698
|
+
if (environmentSniff) {
|
|
1699
|
+
throw new Error(`agent_task_launch_check_conditional_disallowed: game source must not sniff validation or local route context. First match at ${environmentSniff.location}.`);
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
function hasProceduralAssetStrategy(task) {
|
|
1703
|
+
const strategy = task.design?.assetStrategy;
|
|
1704
|
+
return strategy?.value === 'procedural';
|
|
1705
|
+
}
|
|
1706
|
+
function hasDeclaredRuntimeAssetDependency(task) {
|
|
1707
|
+
return task.uses.packs.length > 0
|
|
1708
|
+
|| task.uses.assets.length > 0
|
|
1709
|
+
|| task.ownedAssets.some((ownedAsset) => !isListingOwnedAsset(task, ownedAsset));
|
|
1710
|
+
}
|
|
1711
|
+
function assertNewGameAssetStrategy(task) {
|
|
1712
|
+
if (hasDeclaredRuntimeAssetDependency(task)) {
|
|
1713
|
+
return;
|
|
1714
|
+
}
|
|
1715
|
+
if (hasProceduralAssetStrategy(task)) {
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
throw new Error('agent_task_asset_strategy_missing: new game tasks must either declare true runtime assets/packs or set design.assetStrategy to { "value": "procedural", "note": "..." } for an honest procedural draft.');
|
|
1719
|
+
}
|
|
1720
|
+
function isListingOwnedAsset(task, ownedAsset) {
|
|
1721
|
+
const primaryPath = ownedAsset.filePaths?.primary;
|
|
1722
|
+
if (!primaryPath) {
|
|
1723
|
+
return false;
|
|
1724
|
+
}
|
|
1725
|
+
const relativePath = primaryPath.slice((0, node_path_1.resolve)(task.projectDir).length + 1).replace(/\\/g, '/');
|
|
1726
|
+
return relativePath.startsWith('assets/marketing/') || relativePath.startsWith('listing/');
|
|
1727
|
+
}
|
|
1728
|
+
function isAudioOwnedAsset(ownedAsset) {
|
|
1729
|
+
return ownedAsset.category === 'AUDIO';
|
|
1730
|
+
}
|
|
1731
|
+
function sourceMentionsRuntimeKey(source, runtimeKey) {
|
|
1732
|
+
return source.includes(runtimeKey);
|
|
1733
|
+
}
|
|
1734
|
+
function hasRuntimeAssetLoadOrRenderPath(source) {
|
|
1735
|
+
return /\bsdk\.assets\b|\.resolveAppAsset\s*\(|\.listAppAssets\s*\(/.test(source)
|
|
1736
|
+
&& /\bnew\s+(?:Image|Audio)\s*\(|createImageBitmap\s*\(|\.src\s*=|\.load(?:Async)?\s*\(|\.drawImage\s*\(|\.add\.image\s*\(|\.load\.image\s*\(|\.textures\.add|\.sound\.add|\.scene\.add\s*\(/i.test(source);
|
|
1737
|
+
}
|
|
1738
|
+
function assertAudioAssetsAreNonBlocking(task, runtimeSource) {
|
|
1739
|
+
const source = runtimeSource.source;
|
|
1740
|
+
for (const ownedAsset of task.ownedAssets) {
|
|
1741
|
+
if (!isAudioOwnedAsset(ownedAsset)) {
|
|
1742
|
+
continue;
|
|
1743
|
+
}
|
|
1744
|
+
const runtimeKey = ownedAsset.runtimeKey?.trim() ?? '';
|
|
1745
|
+
const name = ownedAsset.name.trim();
|
|
1746
|
+
if (!runtimeKey || (!source.includes(runtimeKey) && !source.includes(name))) {
|
|
1747
|
+
continue;
|
|
1748
|
+
}
|
|
1749
|
+
const lowerRuntimeKey = runtimeKey.toLowerCase();
|
|
1750
|
+
const lowerName = name.toLowerCase();
|
|
1751
|
+
const executableSource = stripRuntimeSourceCommentsAndStrings(source);
|
|
1752
|
+
const fatalAudioPattern = /\bthrow\s+new\s+Error\s*\([^)]*(?:missing|asset|sound|audio|sfx)[^)]*\)|\bthrow\s+new\s+Error\s*\([^)]*\)|Promise\.reject\s*\([^)]*(?:missing|asset|sound|audio|sfx)[^)]*\)/ig;
|
|
1753
|
+
let match;
|
|
1754
|
+
while ((match = fatalAudioPattern.exec(executableSource)) !== null) {
|
|
1755
|
+
const start = Math.max(0, match.index - 800);
|
|
1756
|
+
const end = Math.min(source.length, match.index + 800);
|
|
1757
|
+
const nearby = source.slice(start, end).toLowerCase();
|
|
1758
|
+
if (nearby.includes(lowerRuntimeKey) || nearby.includes(lowerName)) {
|
|
1759
|
+
throw new Error(`agent_task_audio_asset_boot_fatal_disallowed: audio SFX owned asset "${ownedAsset.name}" must not block play when it fails to load; log a console warning and continue.`);
|
|
1760
|
+
}
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1676
1764
|
function hasRuntimeAssetUrlExtensionAssumption(source) {
|
|
1677
1765
|
return /(?:\.url|\.href|\burl\b)[^;\n]{0,220}(?:\\\.gl(?:b|tf)|\.gl(?:b|tf)|\.endsWith\s*\(\s*['"]\.gl(?:b|tf)['"]\s*\)|\.includes\s*\(\s*['"]\.gl(?:b|tf)['"]\s*\)|\.match\s*\([^)]*\.gl(?:b|tf))/i.test(source);
|
|
1678
1766
|
}
|
|
@@ -1873,15 +1961,64 @@ function assertRequestedPlaydropAssetsAreUsedAtRuntime(input) {
|
|
|
1873
1961
|
}
|
|
1874
1962
|
}
|
|
1875
1963
|
}
|
|
1876
|
-
function assertNewGameDeclaredAssetsAreUsedAtRuntime(
|
|
1877
|
-
const
|
|
1964
|
+
async function assertNewGameDeclaredAssetsAreUsedAtRuntime(input) {
|
|
1965
|
+
const runtimeSource = readRuntimeSourceText(input.task.projectDir);
|
|
1966
|
+
const source = runtimeSource.source;
|
|
1967
|
+
assertNoLaunchCheckConditionalSource(runtimeSource);
|
|
1968
|
+
assertNewGameAssetStrategy(input.task);
|
|
1969
|
+
if (!hasDeclaredRuntimeAssetDependency(input.task)) {
|
|
1970
|
+
return;
|
|
1971
|
+
}
|
|
1878
1972
|
if (!/\bsdk\.assets\b|\.resolveAppAsset\s*\(|\.listAppAssets\s*\(/.test(source)) {
|
|
1879
1973
|
throw new Error('agent_task_game_assets_runtime_missing: new game tasks must load declared gameplay assets through sdk.assets at runtime.');
|
|
1880
1974
|
}
|
|
1881
|
-
if (task.uses.packs.length > 0 && !/\.listAppAssets\s*\(/.test(source)) {
|
|
1975
|
+
if (input.task.uses.packs.length > 0 && !/\.listAppAssets\s*\(/.test(source)) {
|
|
1882
1976
|
throw new Error('agent_task_game_pack_runtime_missing: new game tasks that declare uses.packs must enumerate pack assets with sdk.assets.listAppAssets().');
|
|
1883
1977
|
}
|
|
1884
|
-
|
|
1978
|
+
if (!hasRuntimeAssetLoadOrRenderPath(source)) {
|
|
1979
|
+
throw new Error('agent_task_game_assets_runtime_missing: declared game assets must be loaded and rendered or played at runtime, not only listed in catalogue.json.');
|
|
1980
|
+
}
|
|
1981
|
+
if (input.task.uses.packs.length > 0 && !/\bassetRef\b|\bsourceType\b|\bcontentType\b/i.test(source)) {
|
|
1982
|
+
throw new Error('agent_task_game_pack_runtime_missing: new game tasks that declare uses.packs must select pack assets from the runtime manifest by assetRef/sourceType/contentType before rendering them.');
|
|
1983
|
+
}
|
|
1984
|
+
const manifest = input.task.ownedAssets.length > 0
|
|
1985
|
+
? await (0, devRuntimeAssets_1.buildDevRuntimeAssetManifest)({
|
|
1986
|
+
client: input.client,
|
|
1987
|
+
apiBase: input.apiBase,
|
|
1988
|
+
task: {
|
|
1989
|
+
...input.task,
|
|
1990
|
+
uses: { assets: [], packs: [] },
|
|
1991
|
+
},
|
|
1992
|
+
creatorUsername: input.creatorUsername,
|
|
1993
|
+
appBaseUrl: `http://127.0.0.1/apps/dev/${encodeURIComponent(input.creatorUsername)}/${encodeURIComponent((0, appUrls_1.getAppTypeSlug)(input.task.type ?? 'GAME'))}/${encodeURIComponent(input.task.name)}/`,
|
|
1994
|
+
})
|
|
1995
|
+
: null;
|
|
1996
|
+
const runtimeKeys = new Set((manifest?.response.assets ?? [])
|
|
1997
|
+
.map((asset) => typeof asset.runtimeKey === 'string' ? asset.runtimeKey.trim() : '')
|
|
1998
|
+
.filter(Boolean));
|
|
1999
|
+
for (const ownedAsset of input.task.ownedAssets) {
|
|
2000
|
+
const runtimeKey = ownedAsset.runtimeKey?.trim() ?? '';
|
|
2001
|
+
if (!runtimeKey) {
|
|
2002
|
+
throw new Error(`agent_task_owned_asset_runtime_key_missing: owned asset "${ownedAsset.name}" must declare a runtimeKey.`);
|
|
2003
|
+
}
|
|
2004
|
+
if (!runtimeKeys.has(runtimeKey)) {
|
|
2005
|
+
throw new Error(`agent_task_owned_asset_runtime_manifest_missing: owned asset "${ownedAsset.name}" runtimeKey "${runtimeKey}" was not present in the runtime manifest.`);
|
|
2006
|
+
}
|
|
2007
|
+
if (isListingOwnedAsset(input.task, ownedAsset)) {
|
|
2008
|
+
continue;
|
|
2009
|
+
}
|
|
2010
|
+
if (!sourceMentionsRuntimeKey(source, runtimeKey)) {
|
|
2011
|
+
throw new Error(`agent_task_owned_asset_runtime_key_unused: owned asset "${ownedAsset.name}" runtimeKey "${runtimeKey}" does not appear in runtime source.`);
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
for (const dependency of input.task.uses.assets) {
|
|
2015
|
+
const runtimeKey = dependency.runtimeKey?.trim() ?? '';
|
|
2016
|
+
if (runtimeKey && !sourceMentionsRuntimeKey(source, runtimeKey)) {
|
|
2017
|
+
throw new Error(`agent_task_declared_asset_runtime_key_unused: declared asset "${dependency.ref}" runtimeKey "${runtimeKey}" does not appear in runtime source.`);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
assertAudioAssetsAreNonBlocking(input.task, runtimeSource);
|
|
2021
|
+
const hasDirectGameplayAssets = input.task.ownedAssets.some((ownedAsset) => !isListingOwnedAsset(input.task, ownedAsset)) || input.task.uses.assets.length > 0;
|
|
1885
2022
|
if (hasDirectGameplayAssets && hasDisallowedGameplayAssetFallback(source)) {
|
|
1886
2023
|
throw new Error('agent_task_game_asset_fallback_disallowed: new game tasks must fail clearly when declared gameplay assets cannot load instead of rendering placeholder or fallback visuals.');
|
|
1887
2024
|
}
|
|
@@ -1909,79 +2046,6 @@ function readPngDimensions(filePath) {
|
|
|
1909
2046
|
}
|
|
1910
2047
|
return { width, height };
|
|
1911
2048
|
}
|
|
1912
|
-
function readPngTextMetadata(filePath) {
|
|
1913
|
-
const buffer = (0, node_fs_1.readFileSync)(filePath);
|
|
1914
|
-
const metadata = {};
|
|
1915
|
-
if (buffer.length < 24) {
|
|
1916
|
-
return metadata;
|
|
1917
|
-
}
|
|
1918
|
-
let offset = 8;
|
|
1919
|
-
while (offset + 12 <= buffer.length) {
|
|
1920
|
-
const length = buffer.readUInt32BE(offset);
|
|
1921
|
-
const dataStart = offset + 8;
|
|
1922
|
-
const dataEnd = dataStart + length;
|
|
1923
|
-
const chunkEnd = dataEnd + 4;
|
|
1924
|
-
if (chunkEnd > buffer.length) {
|
|
1925
|
-
break;
|
|
1926
|
-
}
|
|
1927
|
-
const type = buffer.toString('ascii', offset + 4, offset + 8);
|
|
1928
|
-
if (type === 'tEXt') {
|
|
1929
|
-
const chunkData = buffer.subarray(dataStart, dataEnd);
|
|
1930
|
-
const separatorOffset = chunkData.indexOf(0);
|
|
1931
|
-
if (separatorOffset > 0) {
|
|
1932
|
-
const keyword = chunkData.toString('latin1', 0, separatorOffset).trim();
|
|
1933
|
-
const value = chunkData.toString('latin1', separatorOffset + 1).trim();
|
|
1934
|
-
if (keyword && value) {
|
|
1935
|
-
metadata[keyword] = value;
|
|
1936
|
-
}
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
|
-
offset = chunkEnd;
|
|
1940
|
-
if (type === 'IEND') {
|
|
1941
|
-
break;
|
|
1942
|
-
}
|
|
1943
|
-
}
|
|
1944
|
-
return metadata;
|
|
1945
|
-
}
|
|
1946
|
-
function normalizeHeroPromptText(value) {
|
|
1947
|
-
return value.normalize('NFKC').replace(/\s+/g, ' ').trim().toLowerCase();
|
|
1948
|
-
}
|
|
1949
|
-
function heroPromptContainsRequiredTitleInstruction(prompt, displayName) {
|
|
1950
|
-
const normalizedPrompt = normalizeHeroPromptText(prompt);
|
|
1951
|
-
const normalizedDisplayName = normalizeHeroPromptText(displayName);
|
|
1952
|
-
if (!normalizedDisplayName || !normalizedPrompt.includes(normalizedDisplayName)) {
|
|
1953
|
-
return false;
|
|
1954
|
-
}
|
|
1955
|
-
const hasTitleLanguage = /\btitle\b|\blogo\b|\bwordmark\b/.test(normalizedPrompt);
|
|
1956
|
-
const hasProminenceLanguage = /\bfront[-\s]?and[-\s]?center\b|\bfront center\b|\bcenter(?:ed)?\b|\bprominent\b|\blarge readable\b/.test(normalizedPrompt);
|
|
1957
|
-
return hasTitleLanguage && hasProminenceLanguage && heroPromptForbidsExtraReadableText(prompt);
|
|
1958
|
-
}
|
|
1959
|
-
function heroPromptForbidsExtraReadableText(prompt) {
|
|
1960
|
-
const normalizedPrompt = normalizeHeroPromptText(prompt);
|
|
1961
|
-
return /\bno\b[^.]{0,180}\b(?:extra|other|additional)\b[^.]{0,180}\b(?:readable\s+)?(?:text|words|letters|numbers|banners|signs|captions|labels|dates|taglines|subtitles)\b/i.test(normalizedPrompt)
|
|
1962
|
-
|| /\bno\b[^.]{0,180}\b(?:readable\s+)?(?:text|words|letters|numbers|typography|writing|banners|signs|signage|captions|labels|logos|titles|dates|taglines|subtitles)\b/i.test(normalizedPrompt)
|
|
1963
|
-
|| /\bno\b[^.]{0,180}\b(?:readable\s+)?(?:text|words|letters|numbers|banners|signs|captions|labels|dates|taglines|subtitles)\b[^.]{0,180}\banywhere\b/i.test(normalizedPrompt)
|
|
1964
|
-
|| /\bforbid\b[^.]{0,180}\b(?:all\s+)?(?:readable\s+)?(?:text|words|letters|numbers|typography|writing|banners|signs|signage|captions|labels|logos|titles|dates|taglines|subtitles)\b/i.test(normalizedPrompt)
|
|
1965
|
-
|| /\b(?:text|word|letter|typography|writing)[-\s]?free\b/i.test(normalizedPrompt)
|
|
1966
|
-
|| /\bwithout\b[^.]{0,180}\b(?:any\s+)?(?:readable\s+)?(?:text|words|letters|numbers|typography|writing|banners|signs|signage|captions|labels|logos|titles|dates|taglines|subtitles)\b/i.test(normalizedPrompt)
|
|
1967
|
-
|| /\b(?:do\s+not|don't|dont|never)\b[^.]{0,180}\b(?:include|add|render|show|place|draw|write)\b[^.]{0,180}\b(?:any\s+)?(?:readable\s+)?(?:text|words|letters|numbers|typography|writing|banners|signs|signage|captions|labels|logos|titles|dates|taglines|subtitles)\b/i.test(normalizedPrompt)
|
|
1968
|
-
|| /\b(?:avoid|exclude|omit|remove)\b[^.]{0,180}\b(?:all\s+|any\s+)?(?:readable\s+)?(?:text|words|letters|numbers|typography|writing|banners|signs|signage|captions|labels|logos|titles|dates|taglines|subtitles)\b/i.test(normalizedPrompt);
|
|
1969
|
-
}
|
|
1970
|
-
function assertNewGameHeroListingPromptMetadata(input) {
|
|
1971
|
-
const metadata = readPngTextMetadata(input.filePath);
|
|
1972
|
-
const tool = metadata['playdrop:cliOutputTool'] ?? '';
|
|
1973
|
-
const prompt = metadata['playdrop:cliOutputPrompt'] ?? '';
|
|
1974
|
-
const titleTool = metadata['playdrop:listingTitleTool'] ?? '';
|
|
1975
|
-
if (titleTool === 'playdrop compose-listing-title') {
|
|
1976
|
-
throw new Error(`agent_task_listing_hero_art_composed_title_disallowed: ${input.field} must be generated directly by PlayDrop AI with the exact display name "${input.displayName}" integrated front and center, not added afterward by a title overlay script.`);
|
|
1977
|
-
}
|
|
1978
|
-
if (tool !== 'playdrop ai create image --output' || !prompt) {
|
|
1979
|
-
throw new Error(`agent_task_listing_hero_art_metadata_missing: ${input.field} must be produced by "playdrop ai create image --output" or by the staged listing title composition script so PlayDrop can verify listing-art metadata.`);
|
|
1980
|
-
}
|
|
1981
|
-
if (!heroPromptContainsRequiredTitleInstruction(prompt, input.displayName)) {
|
|
1982
|
-
throw new Error(`agent_task_listing_hero_art_title_prompt_missing: ${input.field} must be generated from a prompt containing the exact display name "${input.displayName}" as prominent front-and-center title/logo text and forbidding all extra readable text.`);
|
|
1983
|
-
}
|
|
1984
|
-
}
|
|
1985
2049
|
function readAgentTaskCatalogueApp(task) {
|
|
1986
2050
|
let data;
|
|
1987
2051
|
try {
|
|
@@ -2020,10 +2084,6 @@ function assertNewGameHeroListingPath(task, filePath, field) {
|
|
|
2020
2084
|
}
|
|
2021
2085
|
}
|
|
2022
2086
|
function assertNewGameHeroListingArt(task) {
|
|
2023
|
-
const app = readAgentTaskCatalogueApp(task);
|
|
2024
|
-
const displayName = typeof app.displayName === 'string' && app.displayName.trim()
|
|
2025
|
-
? app.displayName.trim()
|
|
2026
|
-
: task.name;
|
|
2027
2087
|
const heroPortraitPath = task.listing?.heroPortraitPath;
|
|
2028
2088
|
const heroLandscapePath = task.listing?.heroLandscapePath;
|
|
2029
2089
|
if (!heroPortraitPath || !heroLandscapePath) {
|
|
@@ -2040,18 +2100,173 @@ function assertNewGameHeroListingArt(task) {
|
|
|
2040
2100
|
if (landscape.width < MIN_HERO_LANDSCAPE_WIDTH || landscape.height < MIN_HERO_LANDSCAPE_HEIGHT || landscape.width <= landscape.height) {
|
|
2041
2101
|
throw new Error(`agent_task_listing_hero_art_invalid: listing.heroLandscape must be a landscape PNG at least ${MIN_HERO_LANDSCAPE_WIDTH}x${MIN_HERO_LANDSCAPE_HEIGHT}.`);
|
|
2042
2102
|
}
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2103
|
+
}
|
|
2104
|
+
function assertListingScreenshotPath(task, filePath, field) {
|
|
2105
|
+
const relativePath = (0, node_path_1.relative)((0, node_path_1.resolve)(task.projectDir), (0, node_path_1.resolve)(filePath)).replace(/\\/g, '/');
|
|
2106
|
+
const requiredPrefix = field === 'listing.screenshotsPortrait'
|
|
2107
|
+
? 'assets/marketing/playdrop/screenshots/portrait/'
|
|
2108
|
+
: 'assets/marketing/playdrop/screenshots/landscape/';
|
|
2109
|
+
if (relativePath === '' || relativePath === '..' || relativePath.startsWith('../') || relativePath.startsWith('/')) {
|
|
2110
|
+
throw new Error(`agent_task_listing_screenshots_path_invalid: ${field} entries must point to PNG files inside the project under ${requiredPrefix}.`);
|
|
2111
|
+
}
|
|
2112
|
+
if (!relativePath.startsWith(requiredPrefix) || (0, node_path_1.extname)(relativePath).toLowerCase() !== '.png') {
|
|
2113
|
+
throw new Error(`agent_task_listing_screenshots_path_invalid: ${field} entries must point to PNG files under ${requiredPrefix}.`);
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
function assertNewGameListingScreenshots(task) {
|
|
2117
|
+
const listing = task.listing;
|
|
2118
|
+
const portraitPaths = listing?.screenshotPortraitPaths ?? [];
|
|
2119
|
+
const landscapePaths = listing?.screenshotLandscapePaths ?? [];
|
|
2120
|
+
if (portraitPaths.length === 0 && landscapePaths.length === 0) {
|
|
2121
|
+
throw new Error('agent_task_listing_screenshots_missing: new game tasks must include listing.screenshotsPortrait and/or listing.screenshotsLandscape PNG captures.');
|
|
2122
|
+
}
|
|
2123
|
+
if (task.surfaceTargets.includes('MOBILE_PORTRAIT') && portraitPaths.length === 0) {
|
|
2124
|
+
throw new Error('agent_task_listing_screenshots_missing: mobile portrait games must include at least one listing.screenshotsPortrait PNG capture.');
|
|
2125
|
+
}
|
|
2126
|
+
if ((task.surfaceTargets.includes('DESKTOP') || task.surfaceTargets.includes('MOBILE_LANDSCAPE')) && landscapePaths.length === 0) {
|
|
2127
|
+
throw new Error('agent_task_listing_screenshots_missing: desktop or landscape games must include at least one listing.screenshotsLandscape PNG capture.');
|
|
2128
|
+
}
|
|
2129
|
+
for (const filePath of portraitPaths) {
|
|
2130
|
+
assertListingScreenshotPath(task, filePath, 'listing.screenshotsPortrait');
|
|
2131
|
+
readPngDimensions(filePath);
|
|
2132
|
+
}
|
|
2133
|
+
for (const filePath of landscapePaths) {
|
|
2134
|
+
assertListingScreenshotPath(task, filePath, 'listing.screenshotsLandscape');
|
|
2135
|
+
readPngDimensions(filePath);
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
function readPlaytestEvidenceManifest(task) {
|
|
2139
|
+
const manifestPath = (0, node_path_1.join)(task.projectDir, 'playtest-evidence.json');
|
|
2140
|
+
if (!(0, node_fs_1.existsSync)(manifestPath)) {
|
|
2141
|
+
throw new Error(formatMissingPlaytestEvidenceManifestError('new game tasks must include playtest-evidence.json before upload.'));
|
|
2142
|
+
}
|
|
2143
|
+
try {
|
|
2144
|
+
const value = JSON.parse((0, node_fs_1.readFileSync)(manifestPath, 'utf8'));
|
|
2145
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
2146
|
+
throw new Error('manifest must be a JSON object');
|
|
2147
|
+
}
|
|
2148
|
+
return value;
|
|
2149
|
+
}
|
|
2150
|
+
catch (error) {
|
|
2151
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2152
|
+
throw new Error(formatPlaytestEvidenceManifestError(`playtest-evidence.json must be valid JSON. ${reason}`));
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
const PLAYTEST_EVIDENCE_MANIFEST_HELP = 'Expected shape: {"version":1,"entries":[{"environment":"local","url":"http://localhost:8080/...","surface":"desktop","checkedAt":"2026-07-05T00:00:00.000Z","captures":["assets/marketing/playdrop/screenshots/landscape/gameplay.png"],"actions":["start game","verify input works","reach loop progression or loss"],"statesObserved":["ready","input works","loop progressed"],"consoleErrors":[]}]} Include at least one non-final entry whose checkedAt is after the last source code change. The uploader appends the final published-route entry automatically.';
|
|
2156
|
+
function formatMissingPlaytestEvidenceManifestError(message) {
|
|
2157
|
+
return `agent_task_playtest_manifest_missing: ${message} ${PLAYTEST_EVIDENCE_MANIFEST_HELP}`;
|
|
2158
|
+
}
|
|
2159
|
+
function formatPlaytestEvidenceManifestError(message) {
|
|
2160
|
+
return `agent_task_playtest_manifest_invalid: ${message} ${PLAYTEST_EVIDENCE_MANIFEST_HELP}`;
|
|
2161
|
+
}
|
|
2162
|
+
function assertStringArrayField(value, field, options) {
|
|
2163
|
+
if (!Array.isArray(value)) {
|
|
2164
|
+
throw new Error(formatPlaytestEvidenceManifestError(`${field} must be an array.`));
|
|
2165
|
+
}
|
|
2166
|
+
const strings = value.map((entry) => typeof entry === 'string' ? entry.trim() : '');
|
|
2167
|
+
if (strings.some((entry) => entry.length === 0)) {
|
|
2168
|
+
throw new Error(formatPlaytestEvidenceManifestError(`${field} entries must be non-empty strings.`));
|
|
2169
|
+
}
|
|
2170
|
+
if (!options?.allowEmpty && strings.length === 0) {
|
|
2171
|
+
throw new Error(formatPlaytestEvidenceManifestError(`${field} must include at least one entry.`));
|
|
2172
|
+
}
|
|
2173
|
+
return strings;
|
|
2174
|
+
}
|
|
2175
|
+
function assertPlaytestCapturePaths(task, captures) {
|
|
2176
|
+
for (const capture of captures) {
|
|
2177
|
+
const capturePath = (0, node_path_1.resolve)(task.projectDir, capture);
|
|
2178
|
+
const relativePath = (0, node_path_1.relative)((0, node_path_1.resolve)(task.projectDir), capturePath).replace(/\\/g, '/');
|
|
2179
|
+
if (relativePath === '' || relativePath === '..' || relativePath.startsWith('../') || relativePath.startsWith('/')) {
|
|
2180
|
+
throw new Error(formatPlaytestEvidenceManifestError('capture paths must stay inside the project.'));
|
|
2181
|
+
}
|
|
2182
|
+
if (!(0, node_fs_1.existsSync)(capturePath) || !(0, node_fs_1.statSync)(capturePath).isFile()) {
|
|
2183
|
+
throw new Error(formatPlaytestEvidenceManifestError(`capture file "${capture}" does not exist.`));
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
function assertNewGamePlaytestEvidenceManifest(task) {
|
|
2188
|
+
const manifest = readPlaytestEvidenceManifest(task);
|
|
2189
|
+
const entries = Array.isArray(manifest.entries) ? manifest.entries : [];
|
|
2190
|
+
if (entries.length === 0) {
|
|
2191
|
+
throw new Error(formatPlaytestEvidenceManifestError('playtest-evidence.json entries must include a non-final self-playtest before upload.'));
|
|
2192
|
+
}
|
|
2193
|
+
const sourceLastModifiedAt = readRuntimeSourceLastModifiedAt(task.projectDir);
|
|
2194
|
+
const latestSourceMs = sourceLastModifiedAt ? Date.parse(sourceLastModifiedAt) : null;
|
|
2195
|
+
let hasCurrentSelfPlaytest = false;
|
|
2196
|
+
entries.forEach((entry, index) => {
|
|
2197
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
2198
|
+
throw new Error(formatPlaytestEvidenceManifestError(`entries[${index}] must be an object.`));
|
|
2199
|
+
}
|
|
2200
|
+
const record = entry;
|
|
2201
|
+
const environment = typeof record.environment === 'string' ? record.environment.trim() : '';
|
|
2202
|
+
const url = typeof record.url === 'string' ? record.url.trim() : '';
|
|
2203
|
+
const surface = typeof record.surface === 'string' ? record.surface.trim() : '';
|
|
2204
|
+
const checkedAt = typeof record.checkedAt === 'string' ? record.checkedAt.trim() : '';
|
|
2205
|
+
if (!environment || !url || !surface || !checkedAt) {
|
|
2206
|
+
throw new Error(formatPlaytestEvidenceManifestError(`entries[${index}] must include environment, url, surface, and checkedAt.`));
|
|
2207
|
+
}
|
|
2208
|
+
const checkedAtMs = Date.parse(checkedAt);
|
|
2209
|
+
if (!Number.isFinite(checkedAtMs)) {
|
|
2210
|
+
throw new Error(formatPlaytestEvidenceManifestError(`entries[${index}].checkedAt must be an ISO date string.`));
|
|
2211
|
+
}
|
|
2212
|
+
const isFinalGateEntry = environment === 'final';
|
|
2213
|
+
const captures = assertStringArrayField(record.captures, `entries[${index}].captures`, { allowEmpty: isFinalGateEntry });
|
|
2214
|
+
assertStringArrayField(record.actions, `entries[${index}].actions`);
|
|
2215
|
+
assertStringArrayField(record.statesObserved, `entries[${index}].statesObserved`, { allowEmpty: isFinalGateEntry });
|
|
2216
|
+
assertStringArrayField(record.consoleErrors, `entries[${index}].consoleErrors`, { allowEmpty: true });
|
|
2217
|
+
assertPlaytestCapturePaths(task, captures);
|
|
2218
|
+
if (!isFinalGateEntry && (latestSourceMs === null || checkedAtMs >= latestSourceMs)) {
|
|
2219
|
+
hasCurrentSelfPlaytest = true;
|
|
2220
|
+
}
|
|
2054
2221
|
});
|
|
2222
|
+
if (!hasCurrentSelfPlaytest) {
|
|
2223
|
+
throw new Error(formatPlaytestEvidenceManifestError(`playtest-evidence.json must include a non-final self-playtest checkedAt after the last source change${sourceLastModifiedAt ? ` (${sourceLastModifiedAt})` : ''}.`));
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
function writeFinalPublishedRouteEvidence(input) {
|
|
2227
|
+
const manifest = readPlaytestEvidenceManifest(input.task);
|
|
2228
|
+
const entries = Array.isArray(manifest.entries) ? manifest.entries : [];
|
|
2229
|
+
const finalEntry = {
|
|
2230
|
+
environment: 'final',
|
|
2231
|
+
url: input.url,
|
|
2232
|
+
surface: 'DESKTOP',
|
|
2233
|
+
captures: [],
|
|
2234
|
+
actions: ['open owner draft play URL'],
|
|
2235
|
+
statesObserved: input.status === 'PASSED' ? ['ready', 'nonblank-first-frame'] : [],
|
|
2236
|
+
consoleErrors: input.message ? [input.message] : [],
|
|
2237
|
+
checkedAt: input.checkedAt,
|
|
2238
|
+
sourceLastModifiedAt: readRuntimeSourceLastModifiedAt(input.task.projectDir),
|
|
2239
|
+
};
|
|
2240
|
+
const nextManifest = {
|
|
2241
|
+
...manifest,
|
|
2242
|
+
version: 1,
|
|
2243
|
+
entries: [...entries, finalEntry],
|
|
2244
|
+
finalPublishedRoute: finalEntry,
|
|
2245
|
+
};
|
|
2246
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(input.task.projectDir, 'playtest-evidence.json'), `${JSON.stringify(nextManifest, null, 2)}\n`, 'utf8');
|
|
2247
|
+
}
|
|
2248
|
+
function readRuntimeSourceLastModifiedAt(rootDir) {
|
|
2249
|
+
const root = (0, node_path_1.resolve)(rootDir);
|
|
2250
|
+
let latest = 0;
|
|
2251
|
+
const walk = (dir) => {
|
|
2252
|
+
for (const entry of (0, node_fs_1.readdirSync)(dir, { withFileTypes: true })) {
|
|
2253
|
+
const absolute = (0, node_path_1.join)(dir, entry.name);
|
|
2254
|
+
const relativePath = absolute.slice(root.length + 1).replace(/\\/g, '/');
|
|
2255
|
+
if (entry.isDirectory()) {
|
|
2256
|
+
if (RUNTIME_SOURCE_EXCLUDED_DIRS.has(entry.name) || RUNTIME_SOURCE_EXCLUDED_DIRS.has(relativePath)) {
|
|
2257
|
+
continue;
|
|
2258
|
+
}
|
|
2259
|
+
walk(absolute);
|
|
2260
|
+
continue;
|
|
2261
|
+
}
|
|
2262
|
+
if (!entry.isFile() || !RUNTIME_SOURCE_EXTENSIONS.has((0, node_path_1.extname)(entry.name).toLowerCase())) {
|
|
2263
|
+
continue;
|
|
2264
|
+
}
|
|
2265
|
+
latest = Math.max(latest, (0, node_fs_1.statSync)(absolute).mtimeMs);
|
|
2266
|
+
}
|
|
2267
|
+
};
|
|
2268
|
+
walk(root);
|
|
2269
|
+
return latest > 0 ? new Date(latest).toISOString() : null;
|
|
2055
2270
|
}
|
|
2056
2271
|
// Task upload path used by "playdrop task upload": same internal pipeline as
|
|
2057
2272
|
// "playdrop project publish", but always as one PRIVATE draft version for the
|
|
@@ -2101,9 +2316,6 @@ async function publishWorkerAppProject(input) {
|
|
|
2101
2316
|
}
|
|
2102
2317
|
if (input.kind === 'NEW_GAME' || input.kind === 'REMIX_GAME') {
|
|
2103
2318
|
assertNewGameHeroListingArt(task);
|
|
2104
|
-
if (!appTaskHasRequiredNewGameAssets(task)) {
|
|
2105
|
-
throw new Error('agent_task_game_assets_missing: new game tasks must declare at least one real owned asset, PlayDrop asset, or PlayDrop asset pack in catalogue.json.');
|
|
2106
|
-
}
|
|
2107
2319
|
}
|
|
2108
2320
|
if (!appTaskSatisfiesPlaydropAssetRequirement(task, input.playdropAssetRequirement)) {
|
|
2109
2321
|
throw new Error('agent_task_playdrop_asset_dependency_missing: the creator asked for PlayDrop assets, but catalogue.json does not declare a matching uses.assets, uses.packs, or ownedAssets dependency.');
|
|
@@ -2114,13 +2326,20 @@ async function publishWorkerAppProject(input) {
|
|
|
2114
2326
|
task,
|
|
2115
2327
|
});
|
|
2116
2328
|
await assertNewGameOwnedRuntimeImageAssetsAreClean(task);
|
|
2117
|
-
assertNewGameDeclaredAssetsAreUsedAtRuntime(task);
|
|
2118
2329
|
}
|
|
2119
2330
|
assertRequestedPlaydropAssetsAreUsedAtRuntime({
|
|
2120
2331
|
task,
|
|
2121
2332
|
requirement: input.playdropAssetRequirement,
|
|
2122
2333
|
creatorRequest: input.creatorRequest,
|
|
2123
2334
|
});
|
|
2335
|
+
if (input.kind === 'NEW_GAME' || input.kind === 'REMIX_GAME') {
|
|
2336
|
+
await assertNewGameDeclaredAssetsAreUsedAtRuntime({
|
|
2337
|
+
client: input.client,
|
|
2338
|
+
apiBase: input.apiBase,
|
|
2339
|
+
creatorUsername,
|
|
2340
|
+
task,
|
|
2341
|
+
});
|
|
2342
|
+
}
|
|
2124
2343
|
await assertDeclared3dPlaydropPacksContainRuntimeModels({
|
|
2125
2344
|
client: input.client,
|
|
2126
2345
|
task,
|
|
@@ -2135,6 +2354,10 @@ async function publishWorkerAppProject(input) {
|
|
|
2135
2354
|
if (typeof app.id !== 'number' || !Number.isInteger(app.id) || app.id <= 0) {
|
|
2136
2355
|
throw new Error(`App "${task.name}" registration did not return an app id.`);
|
|
2137
2356
|
}
|
|
2357
|
+
if (input.kind === 'NEW_GAME' || input.kind === 'REMIX_GAME') {
|
|
2358
|
+
assertNewGameListingScreenshots(task);
|
|
2359
|
+
assertNewGamePlaytestEvidenceManifest(task);
|
|
2360
|
+
}
|
|
2138
2361
|
const { upload: uploadResult, warnings } = await (0, apps_1.runAppPipeline)(input.client, task, {
|
|
2139
2362
|
creatorUsername,
|
|
2140
2363
|
apiBase: input.apiBase,
|
|
@@ -2155,6 +2378,28 @@ async function publishWorkerAppProject(input) {
|
|
|
2155
2378
|
if (typeof uploadResult.versionNodeId !== 'string' || uploadResult.versionNodeId.trim().length === 0) {
|
|
2156
2379
|
throw new Error(`App "${task.name}" upload did not return versionNodeId.`);
|
|
2157
2380
|
}
|
|
2381
|
+
const finalPlayUrl = (0, appUrls_1.buildPlatformPrivateDraftPlayUrl)(input.webBase ?? null, {
|
|
2382
|
+
creatorUsername,
|
|
2383
|
+
appName: task.name,
|
|
2384
|
+
appType: task.type ?? 'GAME',
|
|
2385
|
+
version: uploadResult.version,
|
|
2386
|
+
});
|
|
2387
|
+
const finalPlayCheck = await (0, launchCheck_1.runPublishedHostedPlayCheck)({
|
|
2388
|
+
targetUrl: finalPlayUrl,
|
|
2389
|
+
surfaceTargets: task.surfaceTargets,
|
|
2390
|
+
token: input.token,
|
|
2391
|
+
currentUser: input.user,
|
|
2392
|
+
});
|
|
2393
|
+
writeFinalPublishedRouteEvidence({
|
|
2394
|
+
task,
|
|
2395
|
+
url: finalPlayUrl,
|
|
2396
|
+
checkedAt: finalPlayCheck.checkedAt,
|
|
2397
|
+
status: finalPlayCheck.status,
|
|
2398
|
+
message: finalPlayCheck.message,
|
|
2399
|
+
});
|
|
2400
|
+
if (finalPlayCheck.status !== 'PASSED') {
|
|
2401
|
+
throw new Error((0, launchCheck_1.formatHostedLaunchCheckFailure)(task.name, finalPlayCheck, 'final'));
|
|
2402
|
+
}
|
|
2158
2403
|
return {
|
|
2159
2404
|
appId: app.id,
|
|
2160
2405
|
appVersionId: uploadResult.versionId,
|
|
@@ -5,7 +5,7 @@ import { type LoggedProcessResult, type LoggedProcessTranscriptChunk } from './w
|
|
|
5
5
|
export { DEFAULT_CODEX_TIMEOUT_MS, DEFAULT_WORKER_TOKEN_CAP, assertWorkerTokenUsageWithinCap, buildCodexExecArgs, buildClaudeExecArgs, buildClaudePermissionSettings, buildWorkerChildEnv, extractAgentTokenUsage, extractCodexRolloutTokenUsage, extractCodexThreadId, extractCodexTokensUsed, readEnvBoolean, readCodexSandboxMode, readCodexRolloutTokenUsageForThread, runLoggedProcess, } from './worker/runtime';
|
|
6
6
|
export type { AgentTokenUsage, LoggedProcessResult, LoggedProcessTranscriptChunk, } from './worker/runtime';
|
|
7
7
|
export declare const WORKER_SESSION_EXPIRED_MESSAGE = "worker session expired: run \"playdrop auth login\" and start the worker again";
|
|
8
|
-
export declare const WORKER_CONTEXT_COMMAND_NOT_ALLOWED_MESSAGE = "worker_context_command_not_allowed: inside a PlayDrop worker task only task progress, task claim-slug, task upload/done/fail, assigned app scaffolding, project validation/build/dev/capture, read-only catalogue/documentation lookup, and PlayDrop AI generation are permitted.";
|
|
8
|
+
export declare const WORKER_CONTEXT_COMMAND_NOT_ALLOWED_MESSAGE = "worker_context_command_not_allowed: inside a PlayDrop worker task only task progress, task claim-slug, task upload/done/fail, assigned app scaffolding, project validation/build/dev/capture, read-only help/catalogue/documentation lookup, and PlayDrop AI generation are permitted.";
|
|
9
9
|
type WorkerStartOptions = {
|
|
10
10
|
env?: string;
|
|
11
11
|
once?: boolean;
|
|
@@ -77,6 +77,7 @@ type TaskCatalogueReportOptions = {
|
|
|
77
77
|
type TaskCompleteOptions = {
|
|
78
78
|
env?: string;
|
|
79
79
|
nextSteps?: string;
|
|
80
|
+
summary?: string;
|
|
80
81
|
};
|
|
81
82
|
type TaskClaimSlugOptions = {
|
|
82
83
|
appName?: string;
|
|
@@ -98,6 +99,10 @@ type TaskSubmitReviewOptions = {
|
|
|
98
99
|
evidenceDir?: string;
|
|
99
100
|
nextSteps?: string;
|
|
100
101
|
};
|
|
102
|
+
type TaskSubmitEvalOptions = {
|
|
103
|
+
env?: string;
|
|
104
|
+
resultFile?: string;
|
|
105
|
+
};
|
|
101
106
|
export type WorkerHealthAlertInput = {
|
|
102
107
|
state: 'started' | 'stopped' | 'crashed';
|
|
103
108
|
env: string;
|
|
@@ -157,6 +162,10 @@ export declare function stagePlaydropPluginReferences(input: {
|
|
|
157
162
|
pluginRoot: string;
|
|
158
163
|
kind: AgentTaskKind;
|
|
159
164
|
}): Promise<string[]>;
|
|
165
|
+
export declare function stageAssignmentPluginBundle(input: {
|
|
166
|
+
workspaceDir: string;
|
|
167
|
+
pluginBundle: NonNullable<WorkerAgentTaskAssignmentResponse['pluginBundle']>;
|
|
168
|
+
}): Promise<string>;
|
|
160
169
|
export declare function discoverWorkerProjectRoot(workspaceDir: string): string;
|
|
161
170
|
export declare function readWorkerTaskState(): WorkerTaskState | null;
|
|
162
171
|
export declare function buildWorkerHealthAlertText(input: WorkerHealthAlertInput): string;
|
|
@@ -181,6 +190,7 @@ export declare function buildWorkerCapabilities(input: string | {
|
|
|
181
190
|
} | null;
|
|
182
191
|
maxParallelTasks?: number | null;
|
|
183
192
|
runningTaskCount?: number | null;
|
|
193
|
+
providerAccountLabel?: string | null;
|
|
184
194
|
}): AgentWorkerCapabilities;
|
|
185
195
|
export declare function buildWorkerClaimBody(input: {
|
|
186
196
|
workerKey: string;
|
|
@@ -254,4 +264,5 @@ export declare function readReviewEvidenceFiles(evidenceDir: string | undefined)
|
|
|
254
264
|
contentBase64: string;
|
|
255
265
|
}>;
|
|
256
266
|
export declare function submitReviewTask(options: TaskSubmitReviewOptions): Promise<void>;
|
|
267
|
+
export declare function submitEvalTask(options: TaskSubmitEvalOptions): Promise<void>;
|
|
257
268
|
export declare function failTask(options: TaskFailOptions): Promise<void>;
|