@playdrop/playdrop-cli 0.12.18 → 0.12.21

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "0.12.8",
3
- "build": 11,
3
+ "build": 14,
4
4
  "runtimeSdkVersion": "0.12.5",
5
5
  "runtimeSdkBuild": 2,
6
6
  "clients": {
@@ -9,7 +9,7 @@ export declare function saveInstrumentEvidenceCapture(input: {
9
9
  evidenceDir: string;
10
10
  groupId: string;
11
11
  name: string;
12
- screenshotId: string;
12
+ screenshotId?: string;
13
13
  }): Promise<InstrumentEvidenceCapture>;
14
14
  export declare function readAndValidateInstrumentEvidenceManifest(input: {
15
15
  workspaceDir: string;
@@ -99,6 +99,35 @@ function createWorkerInstrumentScreenshotCollector(workspaceDir) {
99
99
  finish: () => chromeParser.finish() + playwrightParser.finish(),
100
100
  };
101
101
  }
102
+ function resolveLatestUnusedScreenshotId(workspaceDir, index) {
103
+ const used = new Set();
104
+ const evidenceRoot = node_path_1.default.resolve(workspaceDir);
105
+ const walk = (dir) => {
106
+ for (const entry of (0, node_fs_1.existsSync)(dir) ? (0, node_fs_1.readdirSync)(dir, { withFileTypes: true }) : []) {
107
+ const entryPath = node_path_1.default.join(dir, entry.name);
108
+ if (entry.isDirectory()) {
109
+ walk(entryPath);
110
+ }
111
+ else if (entry.name === exports.INSTRUMENT_EVIDENCE_MANIFEST_FILE) {
112
+ const manifest = readJsonRecord(entryPath, 'invalid_instrument_evidence_manifest');
113
+ for (const capture of Object.values(manifest.captures ?? {})) {
114
+ if (capture?.screenshotId) {
115
+ used.add(capture.screenshotId);
116
+ }
117
+ }
118
+ }
119
+ }
120
+ };
121
+ walk(evidenceRoot);
122
+ const candidates = Object.values(index.screenshots)
123
+ .filter((screenshot) => !used.has(screenshot.screenshotId))
124
+ .sort((a, b) => a.capturedAt.localeCompare(b.capturedAt));
125
+ const latest = candidates.at(-1);
126
+ if (!latest) {
127
+ throw new Error('instrument_screenshot_none_available');
128
+ }
129
+ return latest.screenshotId;
130
+ }
102
131
  function normalizeEvidenceName(value) {
103
132
  const normalized = value.trim();
104
133
  if (!types_1.INSTRUMENT_EVIDENCE_NAMES.includes(normalized)) {
@@ -131,8 +160,11 @@ function readManifestIfPresent(evidenceDir) {
131
160
  async function saveInstrumentEvidenceCapture(input) {
132
161
  const evidenceName = normalizeEvidenceName(input.name);
133
162
  const groupId = normalizeGroupId(input.groupId);
134
- const screenshotId = input.screenshotId.trim();
135
163
  const index = readScreenshotIndex(input.workspaceDir);
164
+ const requestedId = input.screenshotId?.trim() ?? '';
165
+ // With no id, persist the capture the judge just took: the most recent one not already used as
166
+ // evidence. The judge never has to track ids, and a failed capture simply is not there to pick.
167
+ const screenshotId = requestedId || resolveLatestUnusedScreenshotId(input.workspaceDir, index);
136
168
  const screenshot = index.screenshots[screenshotId];
137
169
  if (!screenshot) {
138
170
  throw new Error(`instrument_screenshot_not_found:${screenshotId}`);
@@ -96,6 +96,7 @@ export declare function buildWorkerChildEnv(input: {
96
96
  homeDir?: string;
97
97
  codexHomeDir?: string;
98
98
  claudeConfigDir?: string;
99
+ playdropConfigPath?: string;
99
100
  playwrightBrowsersPath?: string;
100
101
  baseEnv?: NodeJS.ProcessEnv;
101
102
  }): NodeJS.ProcessEnv;
@@ -712,6 +712,17 @@ function buildWorkerChildEnv(input) {
712
712
  }
713
713
  child.CLAUDE_CONFIG_DIR = claudeConfigDir;
714
714
  }
715
+ // Claude sessions inherit the host's real HOME and Claude config on purpose: the machine is
716
+ // logged in with the Claude CLI and we call it exactly as a human does. The worker never
717
+ // stages, copies, or refreshes Claude credentials. The agent's `playdrop` CLI is still scoped
718
+ // to a task session through PLAYDROP_CONFIG_PATH rather than by hijacking HOME.
719
+ if (input.playdropConfigPath !== undefined) {
720
+ const playdropConfigPath = input.playdropConfigPath.trim();
721
+ if (!playdropConfigPath || !node_path_1.default.isAbsolute(playdropConfigPath)) {
722
+ throw new Error('invalid_worker_child_playdrop_config_path');
723
+ }
724
+ child.PLAYDROP_CONFIG_PATH = playdropConfigPath;
725
+ }
715
726
  if (input.playwrightBrowsersPath !== undefined) {
716
727
  const playwrightBrowsersPath = input.playwrightBrowsersPath.trim();
717
728
  if (!playwrightBrowsersPath || !node_path_1.default.isAbsolute(playwrightBrowsersPath)) {
@@ -249,11 +249,9 @@ export declare function prepareClaudeRunConfig(input: {
249
249
  workspaceDir: string;
250
250
  workerUsername: string;
251
251
  envName: string;
252
- sourceCredentialsPath?: string;
253
252
  playdropConfig?: ReturnType<typeof loadConfig>;
254
253
  }): Promise<{
255
- homeDir: string;
256
- claudeConfigDir: string;
254
+ playdropConfigPath: string;
257
255
  }>;
258
256
  export declare function removeAgentRunCredentials(workspaceDir: string): Promise<void>;
259
257
  export declare function removeStaleAgentRunCredentials(workerHomeDir?: string): Promise<void>;
@@ -1958,78 +1958,32 @@ async function stageWorkerPlaydropSession(input) {
1958
1958
  },
1959
1959
  }, null, 2), { mode: 0o600 });
1960
1960
  await (0, promises_1.chmod)(playdropConfigPath, 0o600);
1961
+ return playdropConfigPath;
1961
1962
  }
1962
1963
  async function prepareClaudeRunConfig(input) {
1963
1964
  const runRoot = node_path_1.default.join(input.workspaceDir, '.playdrop', 'claude-run');
1964
1965
  const homeDir = node_path_1.default.join(runRoot, 'home');
1965
- const claudeConfigDir = node_path_1.default.join(runRoot, 'config');
1966
1966
  await (0, promises_1.rm)(runRoot, { recursive: true, force: true });
1967
1967
  await (0, promises_1.mkdir)(runRoot, { recursive: true, mode: 0o700 });
1968
1968
  await (0, promises_1.chmod)(runRoot, 0o700);
1969
1969
  await (0, promises_1.mkdir)(homeDir, { mode: 0o700 });
1970
- await (0, promises_1.mkdir)(claudeConfigDir, { mode: 0o700 });
1971
- const configuredCredentialsPath = input.sourceCredentialsPath?.trim()
1972
- || node_process_1.default.env.PLAYDROP_WORKER_CLAUDE_CREDENTIALS_FILE?.trim();
1973
- const defaultCredentialsPath = node_path_1.default.join(node_os_1.default.homedir(), '.claude', '.credentials.json');
1974
- let rawCredentials;
1975
- if (configuredCredentialsPath) {
1976
- const sourceCredentialsPath = node_path_1.default.resolve(configuredCredentialsPath);
1977
- if (!isFile(sourceCredentialsPath)) {
1978
- throw new Error(`worker_claude_credentials_missing:${sourceCredentialsPath}`);
1979
- }
1980
- rawCredentials = (0, node_fs_1.readFileSync)(sourceCredentialsPath, 'utf8');
1981
- }
1982
- else if (isFile(defaultCredentialsPath)) {
1983
- rawCredentials = (0, node_fs_1.readFileSync)(defaultCredentialsPath, 'utf8');
1984
- }
1985
- else if (node_process_1.default.platform === 'darwin') {
1986
- try {
1987
- const result = await execFileAsync('security', [
1988
- 'find-generic-password',
1989
- '-s',
1990
- 'Claude Code-credentials',
1991
- '-w',
1992
- ], { maxBuffer: 1024 * 1024 });
1993
- rawCredentials = result.stdout;
1994
- }
1995
- catch {
1996
- throw new Error('worker_claude_credentials_missing:macos_keychain');
1997
- }
1998
- }
1999
- else {
2000
- throw new Error(`worker_claude_credentials_missing:${defaultCredentialsPath}`);
2001
- }
2002
- let credentials;
2003
- try {
2004
- credentials = JSON.parse(rawCredentials);
2005
- }
2006
- catch {
2007
- throw new Error('worker_claude_credentials_invalid_json');
2008
- }
2009
- const credentialObject = typeof credentials === 'object' && credentials !== null && !Array.isArray(credentials)
2010
- ? credentials
2011
- : null;
2012
- const oauthValue = credentialObject?.claudeAiOauth;
2013
- const claudeAiOauth = typeof oauthValue === 'object' && oauthValue !== null && !Array.isArray(oauthValue)
2014
- ? oauthValue
2015
- : null;
2016
- if (!claudeAiOauth
2017
- || typeof claudeAiOauth.accessToken !== 'string'
2018
- || !claudeAiOauth.accessToken.trim()
2019
- || typeof claudeAiOauth.refreshToken !== 'string'
2020
- || !claudeAiOauth.refreshToken.trim()) {
2021
- throw new Error('worker_claude_oauth_credentials_missing');
2022
- }
2023
- const stagedCredentialsPath = node_path_1.default.join(claudeConfigDir, '.credentials.json');
2024
- await (0, promises_1.writeFile)(stagedCredentialsPath, `${JSON.stringify({ claudeAiOauth })}\n`, { mode: 0o600 });
2025
- await (0, promises_1.chmod)(stagedCredentialsPath, 0o600);
2026
- await stageWorkerPlaydropSession({
2027
- homeDir,
2028
- workerUsername: input.workerUsername,
2029
- envName: input.envName,
2030
- playdropConfig: input.playdropConfig,
2031
- });
2032
- return { homeDir, claudeConfigDir };
1970
+ // The worker does NOT manage Claude authentication. The host is logged in with the Claude CLI;
1971
+ // we call `claude` and let it use the machine's own login exactly as a human would.
1972
+ //
1973
+ // We used to copy the OAuth credential into this per-task config dir. That is what logged the
1974
+ // machine out: the copy refreshed on first use after the 8h access-token expiry, Anthropic
1975
+ // rotated the refresh token (invalidating the one still in the keychain), and the refreshed
1976
+ // credential was thrown away with the task workspace. The host was then permanently logged out.
1977
+ // Observed twice in production (2026-07-13 19:50, 2026-07-14 13:00:23, the latter three seconds
1978
+ // after task 449's Claude exited). Never stage, copy, or write Claude credentials here.
1979
+ return {
1980
+ playdropConfigPath: await stageWorkerPlaydropSession({
1981
+ homeDir,
1982
+ workerUsername: input.workerUsername,
1983
+ envName: input.envName,
1984
+ playdropConfig: input.playdropConfig,
1985
+ }),
1986
+ };
2033
1987
  }
2034
1988
  async function removeAgentRunCredentials(workspaceDir) {
2035
1989
  await Promise.all([
@@ -2124,7 +2078,7 @@ async function runCodex(input) {
2124
2078
  };
2125
2079
  }
2126
2080
  async function runClaude(input) {
2127
- const { homeDir, claudeConfigDir } = await prepareClaudeRunConfig({
2081
+ const { playdropConfigPath } = await prepareClaudeRunConfig({
2128
2082
  workspaceDir: input.workspaceDir,
2129
2083
  workerUsername: input.workerUsername,
2130
2084
  envName: input.envName,
@@ -2135,7 +2089,8 @@ async function runClaude(input) {
2135
2089
  : null;
2136
2090
  let playwrightMcpConfigPath = null;
2137
2091
  if (input.enablePlaywrightMcp) {
2138
- playwrightMcpConfigPath = node_path_1.default.join(claudeConfigDir, 'playwright-mcp.json');
2092
+ playwrightMcpConfigPath = node_path_1.default.join(input.workspaceDir, '.playdrop', 'claude-run', 'playwright-mcp.json');
2093
+ (0, node_fs_1.mkdirSync)(node_path_1.default.dirname(playwrightMcpConfigPath), { recursive: true });
2139
2094
  (0, node_fs_1.writeFileSync)(playwrightMcpConfigPath, `${JSON.stringify((0, runtime_1.buildPlaywrightMcpServerConfig)(), null, 2)}\n`, 'utf8');
2140
2095
  }
2141
2096
  const result = await (0, runtime_1.runLoggedProcess)({
@@ -2157,8 +2112,7 @@ async function runClaude(input) {
2157
2112
  envName: input.envName,
2158
2113
  eventDir: input.eventDir,
2159
2114
  devPort: input.devPort,
2160
- homeDir,
2161
- claudeConfigDir,
2115
+ playdropConfigPath,
2162
2116
  playwrightBrowsersPath: resolveWorkerPlaywrightBrowsersPath(),
2163
2117
  }),
2164
2118
  stdin: input.prompt,
package/dist/index.js CHANGED
@@ -287,9 +287,9 @@ task
287
287
  });
288
288
  task
289
289
  .command('evidence')
290
- .description('Persist a Claude Chrome screenshot as task evidence')
290
+ .description('Persist the screenshot you just took as task evidence')
291
291
  .requiredOption('--name <name>', 'Evidence state: first-frame, core, win, or loss')
292
- .requiredOption('--screenshot-id <id>', 'Claude Chrome screenshot id')
292
+ .option('--screenshot-id <id>', 'Screenshot id; omit to persist the capture you just took')
293
293
  .requiredOption('--evidence-dir <path>', 'Evidence output directory')
294
294
  .option('--group <id>', 'Evidence group id; required for GAME_EVAL')
295
295
  .action(async (opts) => {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "0.12.8",
3
- "build": 11,
3
+ "build": 14,
4
4
  "runtimeSdkVersion": "0.12.5",
5
5
  "runtimeSdkBuild": 2,
6
6
  "clients": {
@@ -30,7 +30,6 @@ export declare const PLAYWRIGHT_MCP_SCREENSHOT_TOOL = "mcp__playwright__browser_
30
30
  export declare const PLAYWRIGHT_MCP_CONTROLLED_TAB_ID = 1;
31
31
  export declare const PLAYWRIGHT_MCP_SCREENSHOT_BUDGET = 100;
32
32
  export declare function isInstrumentScreenshotId(value: string): boolean;
33
- export declare function normalizePlaywrightScreenshotId(value: unknown): string | null;
34
33
  export declare class ClaudeChromeScreenshotStreamParser {
35
34
  private readonly onScreenshot;
36
35
  private buffer;
@@ -46,9 +45,10 @@ export declare class ClaudeChromeScreenshotStreamParser {
46
45
  export declare class PlaywrightMcpScreenshotStreamParser {
47
46
  private readonly onScreenshot;
48
47
  private buffer;
49
- private readonly pendingScreenshotFilenames;
48
+ private readonly pendingScreenshotToolUseIds;
50
49
  private readonly handledToolUseIds;
51
50
  private screenshotCallCount;
51
+ private sequence;
52
52
  constructor(onScreenshot: (screenshot: ClaudeChromeScreenshot) => void);
53
53
  push(chunk: string): number;
54
54
  finish(): number;
@@ -1 +1 @@
1
- {"version":3,"file":"instrument-evidence.d.ts","sourceRoot":"","sources":["../src/instrument-evidence.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,yBAAyB,iDAK5B,CAAC;AAEX,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhF,MAAM,MAAM,sBAAsB,GAAG;IACnC,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,YAAY,GAAG,WAAW,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,sBAAsB,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,YAAY,GAAG,WAAW,CAAC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,OAAO,EAAE,CAAC,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,sBAAsB,EAAE,yBAAyB,CAAC,CAAC,CAAC;CAC9E,CAAC;AAMF,eAAO,MAAM,8BAA8B,6CAA6C,CAAC;AACzF,eAAO,MAAM,gCAAgC,IAAI,CAAC;AAGlD,eAAO,MAAM,gCAAgC,MAAM,CAAC;AAKpD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE/D;AAED,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAG7E;AA+CD,qBAAa,kCAAkC;IAKjC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAJzC,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAA+B;IACzE,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAqB;gBAE7B,YAAY,EAAE,CAAC,UAAU,EAAE,sBAAsB,KAAK,IAAI;IAEvF,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAqB3B,MAAM,IAAI,MAAM;IAMhB,OAAO,CAAC,WAAW;IA0BnB,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,iBAAiB;CAiE1B;AAMD,qBAAa,mCAAmC;IAMlC,OAAO,CAAC,QAAQ,CAAC,YAAY;IALzC,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAA6B;IACxE,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAqB;IACvD,OAAO,CAAC,mBAAmB,CAAK;gBAEH,YAAY,EAAE,CAAC,UAAU,EAAE,sBAAsB,KAAK,IAAI;IAEvF,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAqB3B,MAAM,IAAI,MAAM;IAMhB,OAAO,CAAC,WAAW;IA0BnB,OAAO,CAAC,aAAa;IAwBrB,OAAO,CAAC,iBAAiB;CA+C1B"}
1
+ {"version":3,"file":"instrument-evidence.d.ts","sourceRoot":"","sources":["../src/instrument-evidence.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,yBAAyB,iDAK5B,CAAC;AAEX,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhF,MAAM,MAAM,sBAAsB,GAAG;IACnC,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,YAAY,GAAG,WAAW,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,sBAAsB,CAAC;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,YAAY,GAAG,WAAW,CAAC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,OAAO,EAAE,CAAC,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,sBAAsB,EAAE,yBAAyB,CAAC,CAAC,CAAC;CAC9E,CAAC;AAMF,eAAO,MAAM,8BAA8B,6CAA6C,CAAC;AACzF,eAAO,MAAM,gCAAgC,IAAI,CAAC;AAGlD,eAAO,MAAM,gCAAgC,MAAM,CAAC;AAKpD,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE/D;AAiDD,qBAAa,kCAAkC;IAKjC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAJzC,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAA+B;IACzE,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAqB;gBAE7B,YAAY,EAAE,CAAC,UAAU,EAAE,sBAAsB,KAAK,IAAI;IAEvF,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAqB3B,MAAM,IAAI,MAAM;IAMhB,OAAO,CAAC,WAAW;IA0BnB,OAAO,CAAC,aAAa;IAkBrB,OAAO,CAAC,iBAAiB;CAiE1B;AAMD,qBAAa,mCAAmC;IAOlC,OAAO,CAAC,QAAQ,CAAC,YAAY;IANzC,OAAO,CAAC,MAAM,CAAM;IACpB,OAAO,CAAC,QAAQ,CAAC,2BAA2B,CAAqB;IACjE,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAqB;IACvD,OAAO,CAAC,mBAAmB,CAAK;IAChC,OAAO,CAAC,QAAQ,CAAK;gBAEQ,YAAY,EAAE,CAAC,UAAU,EAAE,sBAAsB,KAAK,IAAI;IAEvF,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAqB3B,MAAM,IAAI,MAAM;IAMhB,OAAO,CAAC,WAAW;IA0BnB,OAAO,CAAC,aAAa;IAsBrB,OAAO,CAAC,iBAAiB;CA+C1B"}
@@ -23,8 +23,7 @@ __export(instrument_evidence_exports, {
23
23
  PLAYWRIGHT_MCP_SCREENSHOT_BUDGET: () => PLAYWRIGHT_MCP_SCREENSHOT_BUDGET,
24
24
  PLAYWRIGHT_MCP_SCREENSHOT_TOOL: () => PLAYWRIGHT_MCP_SCREENSHOT_TOOL,
25
25
  PlaywrightMcpScreenshotStreamParser: () => PlaywrightMcpScreenshotStreamParser,
26
- isInstrumentScreenshotId: () => isInstrumentScreenshotId,
27
- normalizePlaywrightScreenshotId: () => normalizePlaywrightScreenshotId
26
+ isInstrumentScreenshotId: () => isInstrumentScreenshotId
28
27
  });
29
28
  module.exports = __toCommonJS(instrument_evidence_exports);
30
29
  const INSTRUMENT_EVIDENCE_NAMES = [
@@ -39,14 +38,10 @@ const MAX_STREAM_LINE_LENGTH = 20 * 1024 * 1024;
39
38
  const PLAYWRIGHT_MCP_SCREENSHOT_TOOL = "mcp__playwright__browser_take_screenshot";
40
39
  const PLAYWRIGHT_MCP_CONTROLLED_TAB_ID = 1;
41
40
  const PLAYWRIGHT_MCP_SCREENSHOT_BUDGET = 100;
42
- const PLAYWRIGHT_SCREENSHOT_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}\.(png|jpe?g)$/;
41
+ const PLAYWRIGHT_SCREENSHOT_ID_PATTERN = /^pw-[1-9][0-9]*$/;
43
42
  function isInstrumentScreenshotId(value) {
44
43
  return SCREENSHOT_ID_PATTERN.test(value) || PLAYWRIGHT_SCREENSHOT_ID_PATTERN.test(value);
45
44
  }
46
- function normalizePlaywrightScreenshotId(value) {
47
- const id = typeof value === "string" ? value.trim().toLowerCase() : "";
48
- return PLAYWRIGHT_SCREENSHOT_ID_PATTERN.test(id) ? id : null;
49
- }
50
45
  function asRecord(value) {
51
46
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
52
47
  }
@@ -228,9 +223,10 @@ class ClaudeChromeScreenshotStreamParser {
228
223
  class PlaywrightMcpScreenshotStreamParser {
229
224
  onScreenshot;
230
225
  buffer = "";
231
- pendingScreenshotFilenames = /* @__PURE__ */ new Map();
226
+ pendingScreenshotToolUseIds = /* @__PURE__ */ new Set();
232
227
  handledToolUseIds = /* @__PURE__ */ new Set();
233
228
  screenshotCallCount = 0;
229
+ sequence = 0;
234
230
  constructor(onScreenshot) {
235
231
  this.onScreenshot = onScreenshot;
236
232
  }
@@ -298,12 +294,7 @@ class PlaywrightMcpScreenshotStreamParser {
298
294
  if (this.screenshotCallCount > PLAYWRIGHT_MCP_SCREENSHOT_BUDGET) {
299
295
  throw new Error(`playwright_screenshot_budget_exceeded:${PLAYWRIGHT_MCP_SCREENSHOT_BUDGET}`);
300
296
  }
301
- const input = asRecord(item?.["input"]);
302
- const filename = normalizePlaywrightScreenshotId(input?.["filename"]);
303
- if (!filename) {
304
- continue;
305
- }
306
- this.pendingScreenshotFilenames.set(id, filename);
297
+ this.pendingScreenshotToolUseIds.add(id);
307
298
  }
308
299
  }
309
300
  recordToolResults(payload) {
@@ -316,12 +307,11 @@ class PlaywrightMcpScreenshotStreamParser {
316
307
  continue;
317
308
  }
318
309
  const toolUseId = typeof toolResult["tool_use_id"] === "string" ? toolResult["tool_use_id"].trim() : "";
319
- const screenshotId = this.pendingScreenshotFilenames.get(toolUseId);
320
- if (!screenshotId || this.handledToolUseIds.has(toolUseId)) {
310
+ if (!this.pendingScreenshotToolUseIds.has(toolUseId) || this.handledToolUseIds.has(toolUseId)) {
321
311
  continue;
322
312
  }
323
313
  this.handledToolUseIds.add(toolUseId);
324
- this.pendingScreenshotFilenames.delete(toolUseId);
314
+ this.pendingScreenshotToolUseIds.delete(toolUseId);
325
315
  const resultContent = Array.isArray(toolResult["content"]) ? toolResult["content"] : [];
326
316
  const images = resultContent.map(asRecord).filter((item) => item?.["type"] === "image");
327
317
  if (images.length === 0) {
@@ -336,8 +326,9 @@ class PlaywrightMcpScreenshotStreamParser {
336
326
  if (mediaType !== "image/jpeg" && mediaType !== "image/png" || !dataBase64) {
337
327
  throw new Error(`invalid_playwright_screenshot_payload:${toolUseId}`);
338
328
  }
329
+ this.sequence += 1;
339
330
  this.onScreenshot({
340
- screenshotId,
331
+ screenshotId: `pw-${this.sequence}`,
341
332
  tabId: PLAYWRIGHT_MCP_CONTROLLED_TAB_ID,
342
333
  toolUseId,
343
334
  mediaType,
@@ -358,6 +349,5 @@ class PlaywrightMcpScreenshotStreamParser {
358
349
  PLAYWRIGHT_MCP_SCREENSHOT_BUDGET,
359
350
  PLAYWRIGHT_MCP_SCREENSHOT_TOOL,
360
351
  PlaywrightMcpScreenshotStreamParser,
361
- isInstrumentScreenshotId,
362
- normalizePlaywrightScreenshotId
352
+ isInstrumentScreenshotId
363
353
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playdrop/playdrop-cli",
3
- "version": "0.12.18",
3
+ "version": "0.12.21",
4
4
  "description": "Official Playdrop CLI for publishing browser games, creator apps, and AI-generated game assets on playdrop.ai",
5
5
  "homepage": "https://www.playdrop.ai",
6
6
  "repository": {