@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.
- package/config/client-meta.json +1 -1
- package/dist/commands/worker/instrument-evidence.d.ts +1 -1
- package/dist/commands/worker/instrument-evidence.js +33 -1
- package/dist/commands/worker/runtime.d.ts +1 -0
- package/dist/commands/worker/runtime.js +11 -0
- package/dist/commands/worker.d.ts +1 -3
- package/dist/commands/worker.js +22 -68
- package/dist/index.js +2 -2
- package/node_modules/@playdrop/config/client-meta.json +1 -1
- package/node_modules/@playdrop/types/dist/instrument-evidence.d.ts +2 -2
- package/node_modules/@playdrop/types/dist/instrument-evidence.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/instrument-evidence.js +10 -20
- package/package.json +1 -1
package/config/client-meta.json
CHANGED
|
@@ -9,7 +9,7 @@ export declare function saveInstrumentEvidenceCapture(input: {
|
|
|
9
9
|
evidenceDir: string;
|
|
10
10
|
groupId: string;
|
|
11
11
|
name: string;
|
|
12
|
-
screenshotId
|
|
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}`);
|
|
@@ -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
|
-
|
|
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>;
|
package/dist/commands/worker.js
CHANGED
|
@@ -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
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
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 {
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
.
|
|
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) => {
|
|
@@ -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
|
|
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;
|
|
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 = /^[
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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": {
|