@playdrop/playdrop-cli 0.12.38 → 0.12.39

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 (34) hide show
  1. package/config/client-meta.json +2 -2
  2. package/dist/apps/build.d.ts +3 -1
  3. package/dist/apps/build.js +4 -0
  4. package/dist/apps/loadCheck.d.ts +9 -2
  5. package/dist/apps/loadCheck.js +167 -32
  6. package/dist/apps/surfaceProfiles.d.ts +10 -0
  7. package/dist/apps/surfaceProfiles.js +64 -0
  8. package/dist/apps/upload.js +2 -0
  9. package/dist/catalogue.d.ts +7 -1
  10. package/dist/catalogue.js +27 -0
  11. package/dist/commands/check.d.ts +1 -0
  12. package/dist/commands/check.js +68 -2
  13. package/dist/commands/upload.d.ts +1 -0
  14. package/dist/commands/upload.js +18 -0
  15. package/dist/commands/worker/runtime.d.ts +6 -1
  16. package/dist/commands/worker/runtime.js +20 -3
  17. package/dist/commands/worker.d.ts +23 -3
  18. package/dist/commands/worker.js +98 -5
  19. package/dist/index.js +2 -0
  20. package/node_modules/@playdrop/config/client-meta.json +2 -2
  21. package/node_modules/@playdrop/types/dist/api.d.ts +15 -0
  22. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  23. package/node_modules/@playdrop/types/dist/app-playtest-tape.d.ts +49 -0
  24. package/node_modules/@playdrop/types/dist/app-playtest-tape.d.ts.map +1 -0
  25. package/node_modules/@playdrop/types/dist/app-playtest-tape.js +242 -0
  26. package/node_modules/@playdrop/types/dist/index.d.ts +1 -0
  27. package/node_modules/@playdrop/types/dist/index.d.ts.map +1 -1
  28. package/node_modules/@playdrop/types/dist/index.js +2 -0
  29. package/node_modules/@playdrop/types/dist/instrument-evidence.d.ts +3 -0
  30. package/node_modules/@playdrop/types/dist/instrument-evidence.d.ts.map +1 -1
  31. package/node_modules/@playdrop/types/dist/instrument-evidence.js +56 -5
  32. package/node_modules/@playdrop/types/dist/version.d.ts +5 -0
  33. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  34. package/package.json +1 -1
@@ -4,6 +4,7 @@ exports.resolveUploadOwner = resolveUploadOwner;
4
4
  exports.resolveDefaultUploadCreator = resolveDefaultUploadCreator;
5
5
  exports.upload = upload;
6
6
  exports.resolveWorkerPlaydropAssetRequirement = resolveWorkerPlaydropAssetRequirement;
7
+ exports.assertAgentNewGamePlaytestContract = assertAgentNewGamePlaytestContract;
7
8
  exports.assertTaskPlaytestEvidenceManifest = assertTaskPlaytestEvidenceManifest;
8
9
  exports.assertWorkerAppLocalUploadPreflight = assertWorkerAppLocalUploadPreflight;
9
10
  exports.publishWorkerAppProject = publishWorkerAppProject;
@@ -1222,6 +1223,22 @@ function resolveWorkerPlaydropAssetRequirement(value) {
1222
1223
  }
1223
1224
  return null;
1224
1225
  }
1226
+ function assertAgentNewGamePlaytestContract(task) {
1227
+ if (!task.primarySurface) {
1228
+ throw new Error('agent_task_primary_surface_required: NEW_GAME catalogue.json must declare primarySurface.');
1229
+ }
1230
+ if (!task.surfaceTargets.includes(task.primarySurface)) {
1231
+ throw new Error(`agent_task_primary_surface_not_supported:${task.primarySurface}`);
1232
+ }
1233
+ if (!task.playtestTapes) {
1234
+ throw new Error('agent_task_playtest_tapes_required: NEW_GAME catalogue.json must declare one playtest tape per supported surface.');
1235
+ }
1236
+ for (const surface of task.surfaceTargets) {
1237
+ if (!task.playtestTapes[surface]) {
1238
+ throw new Error(`agent_task_playtest_tape_missing:${surface}`);
1239
+ }
1240
+ }
1241
+ }
1225
1242
  const WORKER_SUPERVISOR_SOURCE_EXCLUDES = [
1226
1243
  'PLAYDROP_TASK.md',
1227
1244
  'AGENTS.md',
@@ -1885,6 +1902,7 @@ async function assertWorkerAppLocalUploadPreflight(input) {
1885
1902
  () => assertGreyboxReport(input.task),
1886
1903
  () => (0, listingPreflight_1.assertAgentGameHeroListingPreflight)(input.task),
1887
1904
  ...(input.kind === 'NEW_GAME' ? [() => (0, listingPreflight_1.assertAgentNewGameArtDirectionPreflight)(input.task)] : []),
1905
+ ...(input.kind === 'NEW_GAME' ? [() => assertAgentNewGamePlaytestContract(input.task)] : []),
1888
1906
  () => {
1889
1907
  if (!appTaskSatisfiesPlaydropAssetRequirement(input.task, input.playdropAssetRequirement)) {
1890
1908
  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.');
@@ -77,7 +77,12 @@ export declare function buildClaudeExecArgs(input: {
77
77
  pluginDir?: string | null;
78
78
  }): string[];
79
79
  export declare const PLAYWRIGHT_MCP_VERSION = "0.0.78";
80
- export declare function buildPlaywrightMcpServerConfig(): Record<string, unknown>;
80
+ export type WorkerInstrumentSurface = 'DESKTOP' | 'MOBILE_LANDSCAPE' | 'MOBILE_PORTRAIT';
81
+ export declare function buildPlaywrightMcpCommand(surface?: WorkerInstrumentSurface | null): {
82
+ command: string;
83
+ args: string[];
84
+ };
85
+ export declare function buildPlaywrightMcpServerConfig(surface?: WorkerInstrumentSurface | null): Record<string, unknown>;
81
86
  export declare function buildClaudePermissionSettings(input?: {
82
87
  workspaceDir?: string | null;
83
88
  denyReadRoots?: string[];
@@ -17,6 +17,7 @@ exports.runLoggedProcess = runLoggedProcess;
17
17
  exports.buildCodexExecArgs = buildCodexExecArgs;
18
18
  exports.readCodexSandboxMode = readCodexSandboxMode;
19
19
  exports.buildClaudeExecArgs = buildClaudeExecArgs;
20
+ exports.buildPlaywrightMcpCommand = buildPlaywrightMcpCommand;
20
21
  exports.buildPlaywrightMcpServerConfig = buildPlaywrightMcpServerConfig;
21
22
  exports.buildClaudePermissionSettings = buildClaudePermissionSettings;
22
23
  exports.buildClaudeDeniedPermissionRules = buildClaudeDeniedPermissionRules;
@@ -25,6 +26,7 @@ const node_child_process_1 = require("node:child_process");
25
26
  const promises_1 = require("node:fs/promises");
26
27
  const node_os_1 = __importDefault(require("node:os"));
27
28
  const node_path_1 = __importDefault(require("node:path"));
29
+ const surfaceProfiles_1 = require("../../apps/surfaceProfiles");
28
30
  const CHILD_KILL_GRACE_MS = 10000;
29
31
  exports.DEFAULT_CODEX_TIMEOUT_MS = 2 * 60 * 60 * 1000;
30
32
  exports.DEFAULT_WORKER_TOKEN_CAP = 20000000;
@@ -647,12 +649,27 @@ const CLAUDE_BASE_TOOL_NAMES = [
647
649
  // never through @latest drift (see the 2026-07-13 auto-update incident).
648
650
  exports.PLAYWRIGHT_MCP_VERSION = '0.0.78';
649
651
  const PLAYWRIGHT_MCP_ALLOWED_TOOL_RULE = 'mcp__playwright';
650
- function buildPlaywrightMcpServerConfig() {
652
+ function buildPlaywrightMcpCommand(surface) {
653
+ const surfaceArgs = surface ? (0, surfaceProfiles_1.buildPlaywrightMcpSurfaceArgs)(surface) : [];
654
+ return {
655
+ command: 'npx',
656
+ args: [
657
+ '-y',
658
+ `@playwright/mcp@${exports.PLAYWRIGHT_MCP_VERSION}`,
659
+ '--browser',
660
+ 'chrome',
661
+ '--isolated',
662
+ ...surfaceArgs,
663
+ ],
664
+ };
665
+ }
666
+ function buildPlaywrightMcpServerConfig(surface) {
667
+ const server = buildPlaywrightMcpCommand(surface);
651
668
  return {
652
669
  mcpServers: {
653
670
  playwright: {
654
- command: 'npx',
655
- args: ['-y', `@playwright/mcp@${exports.PLAYWRIGHT_MCP_VERSION}`, '--browser', 'chrome', '--isolated'],
671
+ command: server.command,
672
+ args: server.args,
656
673
  },
657
674
  },
658
675
  };
@@ -1,10 +1,10 @@
1
1
  import type { ApiClient } from '@playdrop/api-client';
2
- import { type AgentExecutionTarget, type AgentRuntime, type AgentTaskNextStepSuggestion, type AgentTaskResponse, type AgentWorkerCapabilities, type AgentWorkerResponse, type AgentWorkerUpdatePolicyResponse, type WorkerTaskAssignmentV2, type WorkerTaskPluginV2, type WorkerClaimAgentTaskResponse } from '@playdrop/types';
2
+ import { type AgentExecutionTarget, type AgentRuntime, type AgentTaskKind, type AgentTaskNextStepSuggestion, type AgentTaskResponse, type AgentWorkerCapabilities, type AgentWorkerResponse, type AgentWorkerUpdatePolicyResponse, type JsonValue, type WorkerTaskAssignmentV2, type WorkerTaskPluginV2, type WorkerClaimAgentTaskResponse } from '@playdrop/types';
3
3
  import { loadConfig } from '../config';
4
4
  import { runShell } from '../shellProbe';
5
5
  import { type WorkerPlaydropAssetRequirement } from './upload';
6
- import { type LoggedProcessResult, type LoggedProcessTranscriptChunk } from './worker/runtime';
7
- export { assertWorkerTokenUsageWithinCap, buildClaudeExecArgs, buildClaudePermissionSettings, buildCodexExecArgs, buildWorkerChildEnv, DEFAULT_CODEX_TIMEOUT_MS, DEFAULT_WORKER_TOKEN_CAP, extractAgentTokenUsage, extractCodexRolloutTokenUsage, extractCodexThreadId, extractCodexTokensUsed, readCodexRolloutTokenUsageForThread, readCodexSandboxMode, readEnvBoolean, runLoggedProcess } from './worker/runtime';
6
+ import { type WorkerInstrumentSurface, type LoggedProcessResult, type LoggedProcessTranscriptChunk } from './worker/runtime';
7
+ export { assertWorkerTokenUsageWithinCap, buildClaudeExecArgs, buildClaudePermissionSettings, buildCodexExecArgs, buildPlaywrightMcpCommand, buildWorkerChildEnv, DEFAULT_CODEX_TIMEOUT_MS, DEFAULT_WORKER_TOKEN_CAP, extractAgentTokenUsage, extractCodexRolloutTokenUsage, extractCodexThreadId, extractCodexTokensUsed, readCodexRolloutTokenUsageForThread, readCodexSandboxMode, readEnvBoolean, runLoggedProcess } from './worker/runtime';
8
8
  export type { AgentTokenUsage, LoggedProcessResult, LoggedProcessTranscriptChunk } from './worker/runtime';
9
9
  export declare const WORKER_SESSION_EXPIRED_MESSAGE = "worker session expired: run \"playdrop auth login\" and start the worker again";
10
10
  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/check/capture, read-only help/catalogue/documentation lookup, and PlayDrop AI generation are permitted.";
@@ -127,6 +127,24 @@ type WorkerTaskState = {
127
127
  leaseExpiresAt: string | null;
128
128
  claimedAt: string;
129
129
  };
130
+ type WorkerTaskContextFile = {
131
+ taskId: number;
132
+ attempt: number;
133
+ taskToken: string;
134
+ taskType: string;
135
+ metadata: Record<string, JsonValue>;
136
+ kind: AgentTaskKind;
137
+ creatorUsername: string;
138
+ creatorRequest: string;
139
+ target: AgentExecutionTarget;
140
+ outputAppName: string | null;
141
+ outputVersion: string;
142
+ remixSourceRef?: string | null;
143
+ allowedTemplateKeys?: string[];
144
+ reviewAppVersionId?: number | null;
145
+ devPort: number;
146
+ env: string;
147
+ };
130
148
  type WorkerCodexModel = {
131
149
  model: string;
132
150
  reasoningEffort: string;
@@ -148,6 +166,7 @@ export declare function claimBackoffDelayMs(currentMs: number, random?: () => nu
148
166
  export declare function resolveWorkerHomeDir(): string;
149
167
  export declare const WORKER_TASK_WORKSPACE_RETENTION = 20;
150
168
  export declare function pruneWorkerTaskWorkspaces(incomingWorkspaceDir: string, retention?: number): Promise<string[]>;
169
+ export declare function resolveWorkerInstrumentSurface(taskContext: Pick<WorkerTaskContextFile, 'kind' | 'metadata'>): WorkerInstrumentSurface | null;
151
170
  export declare function hasAgentTaskUploadedArtifact(task: Pick<AgentTaskResponse, 'appId' | 'appVersionId'>): boolean;
152
171
  export declare function appendWorkerTranscriptChunks(input: {
153
172
  client: Pick<ApiClient, 'workerAppendAgentTaskTranscriptChunks'>;
@@ -177,6 +196,7 @@ export declare function assertAssignmentPluginBundleExtractionBounds(buffer: Buf
177
196
  export declare function stageAssignmentPluginBundle(input: {
178
197
  workspaceDir: string;
179
198
  pluginBundle: WorkerTaskPluginV2;
199
+ taskKind?: AgentTaskKind;
180
200
  }): Promise<string>;
181
201
  export declare function resolveDevPluginWorkingTree(input: {
182
202
  workingTree?: string;
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.WORKER_TASK_WORKSPACE_RETENTION = exports.WORKER_CONTEXT_COMMAND_NOT_ALLOWED_MESSAGE = exports.WORKER_SESSION_EXPIRED_MESSAGE = exports.runLoggedProcess = exports.readEnvBoolean = exports.readCodexSandboxMode = exports.readCodexRolloutTokenUsageForThread = exports.extractCodexTokensUsed = exports.extractCodexThreadId = exports.extractCodexRolloutTokenUsage = exports.extractAgentTokenUsage = exports.DEFAULT_WORKER_TOKEN_CAP = exports.DEFAULT_CODEX_TIMEOUT_MS = exports.buildWorkerChildEnv = exports.buildCodexExecArgs = exports.buildClaudePermissionSettings = exports.buildClaudeExecArgs = exports.assertWorkerTokenUsageWithinCap = void 0;
6
+ exports.WORKER_TASK_WORKSPACE_RETENTION = exports.WORKER_CONTEXT_COMMAND_NOT_ALLOWED_MESSAGE = exports.WORKER_SESSION_EXPIRED_MESSAGE = exports.runLoggedProcess = exports.readEnvBoolean = exports.readCodexSandboxMode = exports.readCodexRolloutTokenUsageForThread = exports.extractCodexTokensUsed = exports.extractCodexThreadId = exports.extractCodexRolloutTokenUsage = exports.extractAgentTokenUsage = exports.DEFAULT_WORKER_TOKEN_CAP = exports.DEFAULT_CODEX_TIMEOUT_MS = exports.buildWorkerChildEnv = exports.buildPlaywrightMcpCommand = exports.buildCodexExecArgs = exports.buildClaudePermissionSettings = exports.buildClaudeExecArgs = exports.assertWorkerTokenUsageWithinCap = void 0;
7
7
  exports.allocateWorkerDevPort = allocateWorkerDevPort;
8
8
  exports.isWorkerContextCommandAllowed = isWorkerContextCommandAllowed;
9
9
  exports.resolveWorkerExecutionTargetFromRole = resolveWorkerExecutionTargetFromRole;
@@ -12,6 +12,7 @@ exports.nextClaimBackoffMs = nextClaimBackoffMs;
12
12
  exports.claimBackoffDelayMs = claimBackoffDelayMs;
13
13
  exports.resolveWorkerHomeDir = resolveWorkerHomeDir;
14
14
  exports.pruneWorkerTaskWorkspaces = pruneWorkerTaskWorkspaces;
15
+ exports.resolveWorkerInstrumentSurface = resolveWorkerInstrumentSurface;
15
16
  exports.hasAgentTaskUploadedArtifact = hasAgentTaskUploadedArtifact;
16
17
  exports.appendWorkerTranscriptChunks = appendWorkerTranscriptChunks;
17
18
  exports.readTaskNextStepsFile = readTaskNextStepsFile;
@@ -110,6 +111,7 @@ Object.defineProperty(exports, "assertWorkerTokenUsageWithinCap", { enumerable:
110
111
  Object.defineProperty(exports, "buildClaudeExecArgs", { enumerable: true, get: function () { return runtime_2.buildClaudeExecArgs; } });
111
112
  Object.defineProperty(exports, "buildClaudePermissionSettings", { enumerable: true, get: function () { return runtime_2.buildClaudePermissionSettings; } });
112
113
  Object.defineProperty(exports, "buildCodexExecArgs", { enumerable: true, get: function () { return runtime_2.buildCodexExecArgs; } });
114
+ Object.defineProperty(exports, "buildPlaywrightMcpCommand", { enumerable: true, get: function () { return runtime_2.buildPlaywrightMcpCommand; } });
113
115
  Object.defineProperty(exports, "buildWorkerChildEnv", { enumerable: true, get: function () { return runtime_2.buildWorkerChildEnv; } });
114
116
  Object.defineProperty(exports, "DEFAULT_CODEX_TIMEOUT_MS", { enumerable: true, get: function () { return runtime_2.DEFAULT_CODEX_TIMEOUT_MS; } });
115
117
  Object.defineProperty(exports, "DEFAULT_WORKER_TOKEN_CAP", { enumerable: true, get: function () { return runtime_2.DEFAULT_WORKER_TOKEN_CAP; } });
@@ -149,6 +151,7 @@ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
149
151
  exports.WORKER_SESSION_EXPIRED_MESSAGE = 'worker session expired: run "playdrop auth login" and start the worker again';
150
152
  const WORKER_CONTEXT_ALLOWED_COMMANDS = [
151
153
  ['task', 'context'],
154
+ ['task', 'help'],
152
155
  ['task', 'report'],
153
156
  ['task', 'report-catalogue'],
154
157
  ['task', 'claim-slug'],
@@ -171,6 +174,7 @@ const WORKER_CONTEXT_ALLOWED_COMMANDS = [
171
174
  ['search'],
172
175
  ['detail'],
173
176
  ['versions', 'browse'],
177
+ ['tags', 'browse'],
174
178
  ['documentation', 'browse'],
175
179
  ['documentation', 'read'],
176
180
  ['help'],
@@ -568,6 +572,48 @@ function buildWorkerTaskContextV2(assignment) {
568
572
  target: assignment.task.executionTarget,
569
573
  });
570
574
  }
575
+ function normalizeWorkerInstrumentSurface(value) {
576
+ return value === 'DESKTOP' || value === 'MOBILE_LANDSCAPE' || value === 'MOBILE_PORTRAIT'
577
+ ? value
578
+ : null;
579
+ }
580
+ function resolveWorkerInstrumentSurface(taskContext) {
581
+ const playdrop = taskContext.metadata.playdrop;
582
+ if (!playdrop || typeof playdrop !== 'object' || Array.isArray(playdrop)) {
583
+ return null;
584
+ }
585
+ const metadata = playdrop;
586
+ if (taskContext.kind === 'GAME_REVIEW') {
587
+ const reviewTarget = metadata.reviewTarget;
588
+ if (!reviewTarget || typeof reviewTarget !== 'object' || Array.isArray(reviewTarget)) {
589
+ return null;
590
+ }
591
+ return normalizeWorkerInstrumentSurface(reviewTarget.primarySurface);
592
+ }
593
+ if (taskContext.kind === 'GAME_EVAL') {
594
+ const evalTarget = metadata.evalTarget;
595
+ if (!evalTarget || typeof evalTarget !== 'object' || Array.isArray(evalTarget)) {
596
+ return null;
597
+ }
598
+ const targets = evalTarget.targets;
599
+ if (!Array.isArray(targets) || targets.length === 0) {
600
+ return null;
601
+ }
602
+ const surfaces = new Set();
603
+ for (const target of targets) {
604
+ if (!target || typeof target !== 'object' || Array.isArray(target)) {
605
+ return null;
606
+ }
607
+ const surface = normalizeWorkerInstrumentSurface(target.primarySurface);
608
+ if (!surface) {
609
+ return null;
610
+ }
611
+ surfaces.add(surface);
612
+ }
613
+ return surfaces.size === 1 ? Array.from(surfaces)[0] ?? null : null;
614
+ }
615
+ return null;
616
+ }
571
617
  function workerTaskContextPath(workspaceDir) {
572
618
  return node_path_1.default.join(workspaceDir, '.playdrop', 'task.json');
573
619
  }
@@ -1063,9 +1109,24 @@ function readAgentBundleManifest(extractedRoot) {
1063
1109
  if (!destinations.has('CLAUDE.md')) {
1064
1110
  throw new Error('agent_bundle_claude_file_missing');
1065
1111
  }
1112
+ const taskAgentOverlays = manifest.workspace.taskAgentOverlays;
1113
+ if (taskAgentOverlays !== undefined) {
1114
+ if (!taskAgentOverlays || typeof taskAgentOverlays !== 'object' || Array.isArray(taskAgentOverlays)) {
1115
+ throw new Error('agent_bundle_task_agent_overlays_invalid');
1116
+ }
1117
+ for (const [rawKind, rawSource] of Object.entries(taskAgentOverlays)) {
1118
+ if (!WORKER_SUPPORTED_KINDS.includes(rawKind)) {
1119
+ throw new Error(`agent_bundle_task_agent_overlay_kind_invalid:${rawKind}`);
1120
+ }
1121
+ const source = normalizeAgentBundlePath(extractedRoot, rawSource, 'agent_bundle_task_agent_overlay_source_invalid');
1122
+ if (!isFile(source)) {
1123
+ throw new Error(`agent_bundle_task_agent_overlay_source_missing:${rawSource ?? ''}`);
1124
+ }
1125
+ }
1126
+ }
1066
1127
  return { manifest, pluginRoot };
1067
1128
  }
1068
- async function stageAgentBundleWorkspaceFiles(extractedRoot, workspaceDir) {
1129
+ async function stageAgentBundleWorkspaceFiles(extractedRoot, workspaceDir, taskKind) {
1069
1130
  const resolved = readAgentBundleManifest(extractedRoot);
1070
1131
  for (const entry of resolved.manifest.workspace.files) {
1071
1132
  const source = normalizeAgentBundlePath(extractedRoot, entry.source, 'agent_bundle_workspace_source_invalid');
@@ -1073,6 +1134,16 @@ async function stageAgentBundleWorkspaceFiles(extractedRoot, workspaceDir) {
1073
1134
  await (0, promises_1.mkdir)(node_path_1.default.dirname(destination), { recursive: true });
1074
1135
  await (0, promises_1.copyFile)(source, destination);
1075
1136
  }
1137
+ const overlaySource = taskKind
1138
+ ? resolved.manifest.workspace.taskAgentOverlays?.[taskKind]
1139
+ : null;
1140
+ if (overlaySource) {
1141
+ const agentsPath = resolveWorkspaceFileDestination(workspaceDir, 'AGENTS.md');
1142
+ const overlayPath = normalizeAgentBundlePath(extractedRoot, overlaySource, 'agent_bundle_task_agent_overlay_source_invalid');
1143
+ const base = (0, node_fs_1.readFileSync)(agentsPath, 'utf8').trimEnd();
1144
+ const overlay = (0, node_fs_1.readFileSync)(overlayPath, 'utf8').trim();
1145
+ await (0, promises_1.writeFile)(agentsPath, `${base}\n\n${overlay}\n`, 'utf8');
1146
+ }
1076
1147
  return resolved.pluginRoot;
1077
1148
  }
1078
1149
  async function stageAssignmentPluginBundle(input) {
@@ -1128,7 +1199,7 @@ async function stageAssignmentPluginBundle(input) {
1128
1199
  await (0, promises_1.mkdir)(node_path_1.default.dirname(destination), { recursive: true });
1129
1200
  await (0, promises_1.writeFile)(destination, Buffer.from(content));
1130
1201
  }
1131
- return await stageAgentBundleWorkspaceFiles(pluginRoot, input.workspaceDir);
1202
+ return await stageAgentBundleWorkspaceFiles(pluginRoot, input.workspaceDir, input.taskKind);
1132
1203
  }
1133
1204
  function resolveDevPluginWorkingTree(input) {
1134
1205
  const workingTree = input.workingTree?.trim();
@@ -1947,8 +2018,22 @@ async function runCodex(input) {
1947
2018
  env: pluginEnv,
1948
2019
  });
1949
2020
  }
2021
+ if (input.enablePlaywrightMcp) {
2022
+ const server = (0, runtime_1.buildPlaywrightMcpCommand)(input.instrumentSurface);
2023
+ await execFileAsync('codex', ['mcp', 'add', 'playwright', '--', server.command, ...server.args], {
2024
+ cwd: input.workspaceDir,
2025
+ env: {
2026
+ ...node_process_1.default.env,
2027
+ HOME: homeDir,
2028
+ CODEX_HOME: codexHomeDir,
2029
+ },
2030
+ });
2031
+ }
1950
2032
  let observedCodexThreadId = null;
1951
2033
  let codexThreadProbe = '';
2034
+ const screenshotCollector = input.enablePlaywrightMcp
2035
+ ? (0, instrument_evidence_1.createWorkerInstrumentScreenshotCollector)(input.workspaceDir)
2036
+ : null;
1952
2037
  const result = await (0, runtime_1.runLoggedProcess)({
1953
2038
  command: 'codex',
1954
2039
  args: (0, runtime_1.buildCodexExecArgs)({
@@ -1982,9 +2067,13 @@ async function runCodex(input) {
1982
2067
  }
1983
2068
  await input.onTranscriptChunks(chunks);
1984
2069
  },
2070
+ ...(screenshotCollector
2071
+ ? { onStdoutText: (text) => screenshotCollector.push(text) > 0 }
2072
+ : {}),
1985
2073
  transcriptFlushIntervalMs: runtime_1.DEFAULT_TRANSCRIPT_FLUSH_INTERVAL_MS,
1986
2074
  onChild: input.onChild,
1987
2075
  });
2076
+ screenshotCollector?.finish();
1988
2077
  observedCodexThreadId = observedCodexThreadId
1989
2078
  ?? (0, runtime_1.extractCodexThreadId)(`${result.stdout}\n${result.stderr}\n${result.outputTail}`);
1990
2079
  if (!observedCodexThreadId) {
@@ -2027,7 +2116,7 @@ async function runClaude(input) {
2027
2116
  if (input.enablePlaywrightMcp) {
2028
2117
  playwrightMcpConfigPath = node_path_1.default.join(input.workspaceDir, '.playdrop', 'claude-run', 'playwright-mcp.json');
2029
2118
  (0, node_fs_1.mkdirSync)(node_path_1.default.dirname(playwrightMcpConfigPath), { recursive: true });
2030
- (0, node_fs_1.writeFileSync)(playwrightMcpConfigPath, `${JSON.stringify((0, runtime_1.buildPlaywrightMcpServerConfig)(), null, 2)}\n`, 'utf8');
2119
+ (0, node_fs_1.writeFileSync)(playwrightMcpConfigPath, `${JSON.stringify((0, runtime_1.buildPlaywrightMcpServerConfig)(input.instrumentSurface), null, 2)}\n`, 'utf8');
2031
2120
  }
2032
2121
  const result = await (0, runtime_1.runLoggedProcess)({
2033
2122
  command: 'claude',
@@ -4033,11 +4122,12 @@ async function startWorker(options = {}) {
4033
4122
  };
4034
4123
  await stageAssignmentWorkspaceV2(workspaceDir, assignment.workspace);
4035
4124
  const assignmentPluginRoot = devPluginWorkingTree
4036
- ? await stageAgentBundleWorkspaceFiles(devPluginWorkingTree, workspaceDir)
4125
+ ? await stageAgentBundleWorkspaceFiles(devPluginWorkingTree, workspaceDir, task.kind)
4037
4126
  : assignment.plugin
4038
4127
  ? await stageAssignmentPluginBundle({
4039
4128
  workspaceDir,
4040
4129
  pluginBundle: assignment.plugin,
4130
+ taskKind: task.kind,
4041
4131
  })
4042
4132
  : null;
4043
4133
  availableSkillPaths = listPluginSkillPaths(assignmentPluginRoot);
@@ -4124,6 +4214,8 @@ async function startWorker(options = {}) {
4124
4214
  devPort,
4125
4215
  codexModel: codexModel ?? resolveCodexModel(assignment.agent.model),
4126
4216
  taskPluginRoot: assignmentPluginRoot,
4217
+ enablePlaywrightMcp: assignment.agent.browser === 'PLAYWRIGHT',
4218
+ instrumentSurface: resolveWorkerInstrumentSurface(taskContext),
4127
4219
  imagePaths: imageAttachments.map((attachment) => attachment.path),
4128
4220
  onTranscriptChunks: async (chunks) => {
4129
4221
  await appendTranscriptChunks(chunks);
@@ -4141,6 +4233,7 @@ async function startWorker(options = {}) {
4141
4233
  prompt,
4142
4234
  enableChrome: assignment.agent.browser === 'CHROME',
4143
4235
  enablePlaywrightMcp: assignment.agent.browser === 'PLAYWRIGHT',
4236
+ instrumentSurface: resolveWorkerInstrumentSurface(taskContext),
4144
4237
  envName: env,
4145
4238
  workerUsername: username,
4146
4239
  taskId: task.id,
package/dist/index.js CHANGED
@@ -1021,12 +1021,14 @@ project
1021
1021
  .option('--timeout <seconds>', 'Load-check timeout in seconds')
1022
1022
  .option('--screenshot <path>', 'Screenshot output path')
1023
1023
  .option('--actions <path>', 'JSON action script to dispatch after the game frame is ready')
1024
+ .option('--tape <surface>', 'Run matched idle and catalogue playtest-tape captures on a declared surface')
1024
1025
  .action(async (target, opts = {}) => {
1025
1026
  await (0, check_1.check)(target, {
1026
1027
  app: opts.app,
1027
1028
  timeout: opts.timeout,
1028
1029
  screenshot: opts.screenshot,
1029
1030
  actions: opts.actions,
1031
+ tape: opts.tape,
1030
1032
  });
1031
1033
  });
1032
1034
  const projectCapture = project.command('capture').description('Record game media with the native macOS recorder');
@@ -1,7 +1,7 @@
1
1
  {
2
- "version": "0.12.38",
2
+ "version": "0.12.39",
3
3
  "build": 1,
4
- "runtimeSdkVersion": "0.12.38",
4
+ "runtimeSdkVersion": "0.12.39",
5
5
  "runtimeSdkBuild": 1,
6
6
  "clients": {
7
7
  "all": {
@@ -1,5 +1,6 @@
1
1
  import type { AppAuthFilter, AppControllerFilter, AppSurfaceFilter } from './app-capability-filters.js';
2
2
  import type { AppOrigin, AppSurface, AppType } from './app.js';
3
+ import type { AppPlaytestTapes } from './app-playtest-tape.js';
3
4
  import type { AssetPackResponse } from './asset-pack.js';
4
5
  import type { AppMetadataAssetSpecSupport, AssetSpecListResponse, AssetSpecResponse, AssetSpecSearchResult, AssetSpecVersionResponse, AssetSpecVersionsListResponse } from './asset-spec.js';
5
6
  import type { AssetCategory, AssetResponse } from './asset.js';
@@ -480,6 +481,10 @@ export interface GameEvalTargetInput {
480
481
  creatorPrompt: string;
481
482
  primarySurface: AppSurface;
482
483
  }
484
+ export interface GameEvalRequirements {
485
+ declaredPrimarySurface?: boolean;
486
+ completePlaytestTapes?: boolean;
487
+ }
483
488
  export interface CreateGameEvalAgentTaskRequest {
484
489
  mode: GameEvalMode;
485
490
  agent: AgentRuntime;
@@ -488,6 +493,7 @@ export interface CreateGameEvalAgentTaskRequest {
488
493
  judgeInstructionSha256: string;
489
494
  tieBand?: number | null;
490
495
  targets: GameEvalTargetInput[];
496
+ requirements?: GameEvalRequirements;
491
497
  agentBundleSha?: string;
492
498
  }
493
499
  export interface CreateGameEvalAgentTaskResponse {
@@ -825,6 +831,13 @@ export interface GameEvalDimensionResult {
825
831
  export interface GameEvalTargetResult {
826
832
  targetId: string;
827
833
  overall: number;
834
+ tapeCheck?: {
835
+ surface: AppSurface;
836
+ idleOutcome: string;
837
+ tapeOutcome: string;
838
+ criterion: string;
839
+ passed: boolean;
840
+ };
828
841
  dimensions: Record<GameEvalDimension, GameEvalDimensionResult>;
829
842
  comments: string;
830
843
  evidence?: Record<string, unknown>;
@@ -1075,6 +1088,8 @@ export interface AppResponse {
1075
1088
  entryPoint?: string | null;
1076
1089
  /** Surface targets from current version (undefined for draft apps with no version) */
1077
1090
  surfaceTargets?: AppSurface[];
1091
+ primarySurface?: AppSurface | null;
1092
+ playtestTapes?: AppPlaytestTapes | null;
1078
1093
  /** Asset spec support declarations from the current version */
1079
1094
  assetSpecSupport?: AppMetadataAssetSpecSupport[];
1080
1095
  bundleSize?: number | null;