@playdrop/playdrop-cli 0.13.6 → 0.13.8

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.
@@ -5,11 +5,9 @@ exports.resolveDefaultUploadCreator = resolveDefaultUploadCreator;
5
5
  exports.upload = upload;
6
6
  exports.resolveWorkerPlaydropAssetRequirement = resolveWorkerPlaydropAssetRequirement;
7
7
  exports.assertAgentNewGamePlaytestContract = assertAgentNewGamePlaytestContract;
8
- exports.assertTaskPlaytestEvidenceManifest = assertTaskPlaytestEvidenceManifest;
9
8
  exports.assertWorkerAppLocalUploadPreflight = assertWorkerAppLocalUploadPreflight;
10
9
  exports.publishWorkerAppProject = publishWorkerAppProject;
11
10
  exports.publishStaticHtmlProject = publishStaticHtmlProject;
12
- const node_crypto_1 = require("node:crypto");
13
11
  const node_fs_1 = require("node:fs");
14
12
  const node_path_1 = require("node:path");
15
13
  const semver_1 = require("semver");
@@ -1271,12 +1269,6 @@ function assertAgentNewGamePlaytestContract(task) {
1271
1269
  if (!tape) {
1272
1270
  throw new Error(`agent_task_playtest_tape_missing:${surface}`);
1273
1271
  }
1274
- if (tape.startOnlyEventCount <= 0) {
1275
- throw new Error(`agent_task_playtest_start_only_required:${surface}: the tape must include an explicit startup action.`);
1276
- }
1277
- if (tape.events[0]?.atMs !== 0) {
1278
- throw new Error(`agent_task_playtest_ready_relative_timing_required:${surface}: the first startup event must run at 0 ms after sdk.host.ready().`);
1279
- }
1280
1272
  }
1281
1273
  }
1282
1274
  const WORKER_SUPERVISOR_SOURCE_EXCLUDES = [
@@ -1710,23 +1702,6 @@ async function assertNewGameDeclaredAssetsAreUsedAtRuntime(input) {
1710
1702
  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.');
1711
1703
  }
1712
1704
  }
1713
- function readPlaytestEvidenceManifest(task) {
1714
- const manifestPath = (0, node_path_1.join)(task.projectDir, 'playtest-evidence.json');
1715
- if (!(0, node_fs_1.existsSync)(manifestPath)) {
1716
- throw new Error(formatMissingPlaytestEvidenceManifestError('task uploads must include playtest-evidence.json before upload.'));
1717
- }
1718
- try {
1719
- const value = JSON.parse((0, node_fs_1.readFileSync)(manifestPath, 'utf8'));
1720
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
1721
- throw new Error('manifest must be a JSON object');
1722
- }
1723
- return value;
1724
- }
1725
- catch (error) {
1726
- const reason = error instanceof Error ? error.message : String(error);
1727
- throw new Error(formatPlaytestEvidenceManifestError(`playtest-evidence.json must be valid JSON. ${reason}`));
1728
- }
1729
- }
1730
1705
  function assertGreyboxReport(task) {
1731
1706
  const reportPath = (0, node_path_1.join)(task.projectDir, 'greybox-report.json');
1732
1707
  if (!(0, node_fs_1.existsSync)(reportPath)) {
@@ -1791,153 +1766,10 @@ async function collectUploadPreflightErrors(checks) {
1791
1766
  }
1792
1767
  return errors;
1793
1768
  }
1794
- 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":["evidence/input.png","evidence/win.png","evidence/loss.png"],"actions":["press ArrowRight","complete the final round","allow health to reach zero"],"statesObserved":["player moved right","victory overlay","defeat overlay"],"consoleErrors":[]}],"proof":{"primaryInput":{"action":"press ArrowRight","observed":"player moved right","capture":"evidence/input.png"},"win":{"action":"complete the final round","observed":"victory overlay appeared","capture":"evidence/win.png"},"loss":{"action":"allow health to reach zero","observed":"defeat overlay appeared","capture":"evidence/loss.png"}}}. NEW_GAME proof requires three distinct captures from a current non-final entry. The uploader appends hashes and the final published-route entry automatically.';
1795
- function formatMissingPlaytestEvidenceManifestError(message) {
1796
- return `agent_task_playtest_manifest_missing: ${message} ${PLAYTEST_EVIDENCE_MANIFEST_HELP}`;
1797
- }
1798
- function formatPlaytestEvidenceManifestError(message) {
1799
- return `agent_task_playtest_manifest_invalid: ${message} ${PLAYTEST_EVIDENCE_MANIFEST_HELP}`;
1800
- }
1801
- function assertStringArrayField(value, field, options) {
1802
- if (!Array.isArray(value)) {
1803
- throw new Error(formatPlaytestEvidenceManifestError(`${field} must be an array.`));
1804
- }
1805
- const strings = value.map((entry) => typeof entry === 'string' ? entry.trim() : '');
1806
- if (strings.some((entry) => entry.length === 0)) {
1807
- throw new Error(formatPlaytestEvidenceManifestError(`${field} entries must be non-empty strings.`));
1808
- }
1809
- if (!options?.allowEmpty && strings.length === 0) {
1810
- throw new Error(formatPlaytestEvidenceManifestError(`${field} must include at least one entry.`));
1811
- }
1812
- return strings;
1813
- }
1814
- function assertPlaytestCapturePaths(task, captures) {
1815
- for (const capture of captures) {
1816
- const capturePath = (0, node_path_1.resolve)(task.projectDir, capture);
1817
- const relativePath = (0, node_path_1.relative)((0, node_path_1.resolve)(task.projectDir), capturePath).replace(/\\/g, '/');
1818
- if (relativePath === '' || relativePath === '..' || relativePath.startsWith('../') || relativePath.startsWith('/')) {
1819
- throw new Error(formatPlaytestEvidenceManifestError('capture paths must stay inside the project.'));
1820
- }
1821
- if (!(0, node_fs_1.existsSync)(capturePath) || !(0, node_fs_1.statSync)(capturePath).isFile()) {
1822
- throw new Error(formatPlaytestEvidenceManifestError(`capture file "${capture}" does not exist.`));
1823
- }
1824
- }
1825
- }
1826
- function assertExactObjectKeys(record, allowedKeys, field) {
1827
- const allowed = new Set(allowedKeys);
1828
- const unexpected = Object.keys(record).find((key) => !allowed.has(key));
1829
- if (unexpected) {
1830
- throw new Error(formatPlaytestEvidenceManifestError(`${field} contains unsupported key "${unexpected}".`));
1831
- }
1832
- }
1833
- function buildRequiredTaskPlaytestProof(task, manifest, currentSelfPlaytestCaptures) {
1834
- if (!manifest.proof || typeof manifest.proof !== 'object' || Array.isArray(manifest.proof)) {
1835
- throw new Error(formatPlaytestEvidenceManifestError('playtest-evidence.json proof must demonstrate primaryInput, win, and loss for NEW_GAME tasks.'));
1836
- }
1837
- const rawProof = manifest.proof;
1838
- assertExactObjectKeys(rawProof, ['primaryInput', 'win', 'loss'], 'proof');
1839
- const proof = { version: 1 };
1840
- for (const momentName of ['primaryInput', 'win', 'loss']) {
1841
- const rawMoment = rawProof[momentName];
1842
- if (!rawMoment || typeof rawMoment !== 'object' || Array.isArray(rawMoment)) {
1843
- throw new Error(formatPlaytestEvidenceManifestError(`proof.${momentName} must include action, observed, and capture.`));
1844
- }
1845
- const moment = rawMoment;
1846
- assertExactObjectKeys(moment, ['action', 'observed', 'capture'], `proof.${momentName}`);
1847
- const action = typeof moment.action === 'string' ? moment.action.trim() : '';
1848
- const observed = typeof moment.observed === 'string' ? moment.observed.trim() : '';
1849
- const capture = typeof moment.capture === 'string' ? moment.capture.trim() : '';
1850
- if (!action || !observed || !capture) {
1851
- throw new Error(formatPlaytestEvidenceManifestError(`proof.${momentName} must include non-empty action, observed, and capture strings.`));
1852
- }
1853
- if (!currentSelfPlaytestCaptures.has(capture)) {
1854
- throw new Error(formatPlaytestEvidenceManifestError(`proof.${momentName}.capture must be listed by a current non-final playtest entry.`));
1855
- }
1856
- const capturePath = (0, node_path_1.resolve)(task.projectDir, capture);
1857
- const captureSha256 = (0, node_crypto_1.createHash)('sha256').update((0, node_fs_1.readFileSync)(capturePath)).digest('hex');
1858
- proof[momentName] = { action, observed, capture, captureSha256 };
1859
- }
1860
- const parsed = (0, types_1.normalizeAgentTaskPlaytestProof)(proof);
1861
- if (!parsed.ok) {
1862
- throw new Error(formatPlaytestEvidenceManifestError(parsed.error));
1863
- }
1864
- return parsed.value;
1865
- }
1866
- function assertTaskPlaytestEvidenceManifest(task, options = {}) {
1867
- const manifest = readPlaytestEvidenceManifest(task);
1868
- if (manifest.version !== 1) {
1869
- throw new Error(formatPlaytestEvidenceManifestError('playtest-evidence.json version must be 1.'));
1870
- }
1871
- const entries = Array.isArray(manifest.entries) ? manifest.entries : [];
1872
- if (entries.length === 0) {
1873
- throw new Error(formatPlaytestEvidenceManifestError('playtest-evidence.json entries must include a non-final self-playtest before upload.'));
1874
- }
1875
- const sourceLastModifiedAt = readRuntimeSourceLastModifiedAt(task.projectDir);
1876
- const latestSourceMs = sourceLastModifiedAt ? Date.parse(sourceLastModifiedAt) : null;
1877
- let hasCurrentSelfPlaytest = false;
1878
- const currentSelfPlaytestCaptures = new Set();
1879
- entries.forEach((entry, index) => {
1880
- if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
1881
- throw new Error(formatPlaytestEvidenceManifestError(`entries[${index}] must be an object.`));
1882
- }
1883
- const record = entry;
1884
- const environment = typeof record.environment === 'string' ? record.environment.trim() : '';
1885
- const url = typeof record.url === 'string' ? record.url.trim() : '';
1886
- const surface = typeof record.surface === 'string' ? record.surface.trim() : '';
1887
- const checkedAt = typeof record.checkedAt === 'string' ? record.checkedAt.trim() : '';
1888
- if (!environment || !url || !surface || !checkedAt) {
1889
- throw new Error(formatPlaytestEvidenceManifestError(`entries[${index}] must include environment, url, surface, and checkedAt.`));
1890
- }
1891
- const checkedAtMs = Date.parse(checkedAt);
1892
- if (!Number.isFinite(checkedAtMs)) {
1893
- throw new Error(formatPlaytestEvidenceManifestError(`entries[${index}].checkedAt must be an ISO date string.`));
1894
- }
1895
- const isFinalGateEntry = environment === 'final';
1896
- const captures = assertStringArrayField(record.captures, `entries[${index}].captures`, { allowEmpty: isFinalGateEntry });
1897
- assertStringArrayField(record.actions, `entries[${index}].actions`);
1898
- assertStringArrayField(record.statesObserved, `entries[${index}].statesObserved`, { allowEmpty: isFinalGateEntry });
1899
- assertStringArrayField(record.consoleErrors, `entries[${index}].consoleErrors`, { allowEmpty: true });
1900
- assertPlaytestCapturePaths(task, captures);
1901
- if (!isFinalGateEntry && (latestSourceMs === null || checkedAtMs >= latestSourceMs)) {
1902
- hasCurrentSelfPlaytest = true;
1903
- captures.forEach((capture) => currentSelfPlaytestCaptures.add(capture));
1904
- }
1905
- });
1906
- if (!hasCurrentSelfPlaytest) {
1907
- throw new Error(formatPlaytestEvidenceManifestError(`playtest-evidence.json must include a non-final self-playtest checkedAt after the last source change${sourceLastModifiedAt ? ` (${sourceLastModifiedAt})` : ''}.`));
1908
- }
1909
- return options.requireOutcomeProof
1910
- ? buildRequiredTaskPlaytestProof(task, manifest, currentSelfPlaytestCaptures)
1911
- : null;
1912
- }
1913
- function readRuntimeSourceLastModifiedAt(rootDir) {
1914
- const root = (0, node_path_1.resolve)(rootDir);
1915
- let latest = 0;
1916
- const walk = (dir) => {
1917
- for (const entry of (0, node_fs_1.readdirSync)(dir, { withFileTypes: true })) {
1918
- const absolute = (0, node_path_1.join)(dir, entry.name);
1919
- const relativePath = absolute.slice(root.length + 1).replace(/\\/g, '/');
1920
- if (entry.isDirectory()) {
1921
- if (RUNTIME_SOURCE_EXCLUDED_DIRS.has(entry.name) || RUNTIME_SOURCE_EXCLUDED_DIRS.has(relativePath)) {
1922
- continue;
1923
- }
1924
- walk(absolute);
1925
- continue;
1926
- }
1927
- if (!entry.isFile() || !RUNTIME_SOURCE_EXTENSIONS.has((0, node_path_1.extname)(entry.name).toLowerCase())) {
1928
- continue;
1929
- }
1930
- latest = Math.max(latest, (0, node_fs_1.statSync)(absolute).mtimeMs);
1931
- }
1932
- };
1933
- walk(root);
1934
- return latest > 0 ? new Date(latest).toISOString() : null;
1935
- }
1936
1769
  async function assertWorkerAppLocalUploadPreflight(input) {
1937
1770
  if (input.kind === 'GAME_UPDATE') {
1938
- return assertTaskPlaytestEvidenceManifest(input.task);
1771
+ return;
1939
1772
  }
1940
- let playtestProof = null;
1941
1773
  const errors = await collectUploadPreflightErrors([
1942
1774
  () => assertGreyboxReport(input.task),
1943
1775
  () => (0, listingPreflight_1.assertAgentGameHeroListingPreflight)(input.task),
@@ -1962,11 +1794,6 @@ async function assertWorkerAppLocalUploadPreflight(input) {
1962
1794
  () => (0, listingPreflight_1.assertAgentGameCaptureReportPreflight)(input.task, {
1963
1795
  requireCaptureReport: input.executionTarget === 'FIRST_PARTY',
1964
1796
  }),
1965
- () => {
1966
- playtestProof = assertTaskPlaytestEvidenceManifest(input.task, {
1967
- requireOutcomeProof: input.kind === 'NEW_GAME',
1968
- });
1969
- },
1970
1797
  ]);
1971
1798
  if (errors.length === 1) {
1972
1799
  throw new Error(errors[0]);
@@ -1974,7 +1801,6 @@ async function assertWorkerAppLocalUploadPreflight(input) {
1974
1801
  if (errors.length > 1) {
1975
1802
  throw new Error(`agent_task_upload_preflight_failed:\n${errors.map((error, index) => `${index + 1}. ${error}`).join('\n')}`);
1976
1803
  }
1977
- return playtestProof;
1978
1804
  }
1979
1805
  // Task upload path used by "playdrop task upload": same internal pipeline as
1980
1806
  // "playdrop project publish", but always as one PRIVATE draft version for the
@@ -2022,7 +1848,7 @@ async function publishWorkerAppProject(input) {
2022
1848
  throw new Error(`agent_task_remix_ref_mismatch:${catalogueRemixRef}:${expectedRemixRef}`);
2023
1849
  }
2024
1850
  }
2025
- const playtestProof = await assertWorkerAppLocalUploadPreflight({
1851
+ await assertWorkerAppLocalUploadPreflight({
2026
1852
  task,
2027
1853
  kind: input.kind,
2028
1854
  executionTarget: input.executionTarget,
@@ -2063,9 +1889,8 @@ async function publishWorkerAppProject(input) {
2063
1889
  runStagedUploadLoadCheck: true,
2064
1890
  ensureRegisteredAppShell: true,
2065
1891
  agentTaskId: input.taskId,
2066
- playtestProof: playtestProof ?? undefined,
2067
1892
  mediaCaptureRequired: input.executionTarget === 'FIRST_PARTY',
2068
- machineCausalGateRequired: input.kind === 'NEW_GAME',
1893
+ playtestSmokeCheckRequired: input.kind === 'NEW_GAME',
2069
1894
  captureSession,
2070
1895
  });
2071
1896
  if (!uploadResult.versionCreated || !uploadResult.version) {
@@ -2140,7 +1965,7 @@ async function publishStaticHtmlProject(input) {
2140
1965
  ensureRegisteredAppShell: true,
2141
1966
  agentTaskId: input.taskId,
2142
1967
  mediaCaptureRequired: false,
2143
- machineCausalGateRequired: false,
1968
+ playtestSmokeCheckRequired: false,
2144
1969
  captureSession,
2145
1970
  artifacts: prepared.artifacts,
2146
1971
  });
@@ -1,7 +1,7 @@
1
1
  {
2
- "version": "0.13.5",
2
+ "version": "0.13.8",
3
3
  "build": 1,
4
- "runtimeSdkVersion": "0.13.5",
4
+ "runtimeSdkVersion": "0.13.8",
5
5
  "runtimeSdkBuild": 1,
6
6
  "clients": {
7
7
  "all": {