@playdrop/playdrop-cli 0.12.17 → 0.12.20
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/runtime.d.ts +1 -0
- package/dist/commands/worker/runtime.js +11 -0
- package/dist/commands/worker.d.ts +3 -3
- package/dist/commands/worker.js +52 -69
- package/node_modules/@playdrop/config/client-meta.json +1 -1
- package/node_modules/@playdrop/types/dist/instrument-evidence.d.ts +4 -2
- package/node_modules/@playdrop/types/dist/instrument-evidence.d.ts.map +1 -1
- package/node_modules/@playdrop/types/dist/instrument-evidence.js +28 -10
- package/package.json +1 -1
package/config/client-meta.json
CHANGED
|
@@ -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)) {
|
|
@@ -137,6 +137,8 @@ export declare function resolveWorkerClaimTaskAssignment(claim: WorkerClaimAgent
|
|
|
137
137
|
export declare function nextClaimBackoffMs(currentMs: number): number;
|
|
138
138
|
export declare function claimBackoffDelayMs(currentMs: number, random?: () => number): number;
|
|
139
139
|
export declare function resolveWorkerHomeDir(): string;
|
|
140
|
+
export declare const WORKER_TASK_WORKSPACE_RETENTION = 20;
|
|
141
|
+
export declare function pruneWorkerTaskWorkspaces(incomingWorkspaceDir: string, retention?: number): Promise<string[]>;
|
|
140
142
|
export declare function hasAgentTaskUploadedArtifact(task: Pick<AgentTaskResponse, 'appId' | 'appVersionId'>): boolean;
|
|
141
143
|
export declare function appendWorkerTranscriptChunks(input: {
|
|
142
144
|
client: Pick<ApiClient, 'workerAppendAgentTaskTranscriptChunks'>;
|
|
@@ -247,11 +249,9 @@ export declare function prepareClaudeRunConfig(input: {
|
|
|
247
249
|
workspaceDir: string;
|
|
248
250
|
workerUsername: string;
|
|
249
251
|
envName: string;
|
|
250
|
-
sourceCredentialsPath?: string;
|
|
251
252
|
playdropConfig?: ReturnType<typeof loadConfig>;
|
|
252
253
|
}): Promise<{
|
|
253
|
-
|
|
254
|
-
claudeConfigDir: string;
|
|
254
|
+
playdropConfigPath: string;
|
|
255
255
|
}>;
|
|
256
256
|
export declare function removeAgentRunCredentials(workspaceDir: string): Promise<void>;
|
|
257
257
|
export declare function removeStaleAgentRunCredentials(workerHomeDir?: string): Promise<void>;
|
package/dist/commands/worker.js
CHANGED
|
@@ -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_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.buildCodexExecArgs = exports.buildClaudePermissionSettings = exports.buildClaudeExecArgs = exports.assertWorkerTokenUsageWithinCap = void 0;
|
|
7
7
|
exports.allocateWorkerDevPort = allocateWorkerDevPort;
|
|
8
8
|
exports.isWorkerContextCommandAllowed = isWorkerContextCommandAllowed;
|
|
9
9
|
exports.resolveWorkerExecutionTargetFromRole = resolveWorkerExecutionTargetFromRole;
|
|
@@ -11,6 +11,7 @@ exports.resolveWorkerClaimTaskAssignment = resolveWorkerClaimTaskAssignment;
|
|
|
11
11
|
exports.nextClaimBackoffMs = nextClaimBackoffMs;
|
|
12
12
|
exports.claimBackoffDelayMs = claimBackoffDelayMs;
|
|
13
13
|
exports.resolveWorkerHomeDir = resolveWorkerHomeDir;
|
|
14
|
+
exports.pruneWorkerTaskWorkspaces = pruneWorkerTaskWorkspaces;
|
|
14
15
|
exports.hasAgentTaskUploadedArtifact = hasAgentTaskUploadedArtifact;
|
|
15
16
|
exports.appendWorkerTranscriptChunks = appendWorkerTranscriptChunks;
|
|
16
17
|
exports.readTaskNextStepsFile = readTaskNextStepsFile;
|
|
@@ -412,6 +413,33 @@ function workerStateFilePath() {
|
|
|
412
413
|
function workerTaskWorkspaceDirFromAssignment(assignment) {
|
|
413
414
|
return node_path_1.default.join(resolveWorkerHomeDir(), 'tasks', assignment.workspace.folderName);
|
|
414
415
|
}
|
|
416
|
+
exports.WORKER_TASK_WORKSPACE_RETENTION = 20;
|
|
417
|
+
// Task workspaces (including retained failed ones) otherwise grow without bound on a long-lived
|
|
418
|
+
// worker host. Keep only the most recently touched ones; the incoming task's own directory is
|
|
419
|
+
// never a prune candidate.
|
|
420
|
+
async function pruneWorkerTaskWorkspaces(incomingWorkspaceDir, retention = exports.WORKER_TASK_WORKSPACE_RETENTION) {
|
|
421
|
+
const tasksRoot = node_path_1.default.dirname(node_path_1.default.resolve(incomingWorkspaceDir));
|
|
422
|
+
if (!(0, node_fs_1.existsSync)(tasksRoot)) {
|
|
423
|
+
return [];
|
|
424
|
+
}
|
|
425
|
+
const incoming = node_path_1.default.resolve(incomingWorkspaceDir);
|
|
426
|
+
const entries = (0, node_fs_1.readdirSync)(tasksRoot, { withFileTypes: true })
|
|
427
|
+
.filter((entry) => entry.isDirectory())
|
|
428
|
+
.map((entry) => node_path_1.default.join(tasksRoot, entry.name))
|
|
429
|
+
.filter((dir) => dir !== incoming)
|
|
430
|
+
.map((dir) => ({ dir, mtimeMs: (0, node_fs_1.statSync)(dir).mtimeMs }))
|
|
431
|
+
.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
432
|
+
const stale = entries.slice(Math.max(0, retention - 1));
|
|
433
|
+
const removed = [];
|
|
434
|
+
for (const entry of stale) {
|
|
435
|
+
await (0, promises_1.rm)(entry.dir, { recursive: true, force: true });
|
|
436
|
+
removed.push(entry.dir);
|
|
437
|
+
}
|
|
438
|
+
if (removed.length > 0) {
|
|
439
|
+
console.error(`pruned ${removed.length} stale task workspace(s), keeping the ${retention} most recent`);
|
|
440
|
+
}
|
|
441
|
+
return removed;
|
|
442
|
+
}
|
|
415
443
|
function buildWorkerTaskContext(claim, assignment) {
|
|
416
444
|
const task = claim.task;
|
|
417
445
|
if (!task) {
|
|
@@ -1930,78 +1958,32 @@ async function stageWorkerPlaydropSession(input) {
|
|
|
1930
1958
|
},
|
|
1931
1959
|
}, null, 2), { mode: 0o600 });
|
|
1932
1960
|
await (0, promises_1.chmod)(playdropConfigPath, 0o600);
|
|
1961
|
+
return playdropConfigPath;
|
|
1933
1962
|
}
|
|
1934
1963
|
async function prepareClaudeRunConfig(input) {
|
|
1935
1964
|
const runRoot = node_path_1.default.join(input.workspaceDir, '.playdrop', 'claude-run');
|
|
1936
1965
|
const homeDir = node_path_1.default.join(runRoot, 'home');
|
|
1937
|
-
const claudeConfigDir = node_path_1.default.join(runRoot, 'config');
|
|
1938
1966
|
await (0, promises_1.rm)(runRoot, { recursive: true, force: true });
|
|
1939
1967
|
await (0, promises_1.mkdir)(runRoot, { recursive: true, mode: 0o700 });
|
|
1940
1968
|
await (0, promises_1.chmod)(runRoot, 0o700);
|
|
1941
1969
|
await (0, promises_1.mkdir)(homeDir, { mode: 0o700 });
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
const result = await execFileAsync('security', [
|
|
1960
|
-
'find-generic-password',
|
|
1961
|
-
'-s',
|
|
1962
|
-
'Claude Code-credentials',
|
|
1963
|
-
'-w',
|
|
1964
|
-
], { maxBuffer: 1024 * 1024 });
|
|
1965
|
-
rawCredentials = result.stdout;
|
|
1966
|
-
}
|
|
1967
|
-
catch {
|
|
1968
|
-
throw new Error('worker_claude_credentials_missing:macos_keychain');
|
|
1969
|
-
}
|
|
1970
|
-
}
|
|
1971
|
-
else {
|
|
1972
|
-
throw new Error(`worker_claude_credentials_missing:${defaultCredentialsPath}`);
|
|
1973
|
-
}
|
|
1974
|
-
let credentials;
|
|
1975
|
-
try {
|
|
1976
|
-
credentials = JSON.parse(rawCredentials);
|
|
1977
|
-
}
|
|
1978
|
-
catch {
|
|
1979
|
-
throw new Error('worker_claude_credentials_invalid_json');
|
|
1980
|
-
}
|
|
1981
|
-
const credentialObject = typeof credentials === 'object' && credentials !== null && !Array.isArray(credentials)
|
|
1982
|
-
? credentials
|
|
1983
|
-
: null;
|
|
1984
|
-
const oauthValue = credentialObject?.claudeAiOauth;
|
|
1985
|
-
const claudeAiOauth = typeof oauthValue === 'object' && oauthValue !== null && !Array.isArray(oauthValue)
|
|
1986
|
-
? oauthValue
|
|
1987
|
-
: null;
|
|
1988
|
-
if (!claudeAiOauth
|
|
1989
|
-
|| typeof claudeAiOauth.accessToken !== 'string'
|
|
1990
|
-
|| !claudeAiOauth.accessToken.trim()
|
|
1991
|
-
|| typeof claudeAiOauth.refreshToken !== 'string'
|
|
1992
|
-
|| !claudeAiOauth.refreshToken.trim()) {
|
|
1993
|
-
throw new Error('worker_claude_oauth_credentials_missing');
|
|
1994
|
-
}
|
|
1995
|
-
const stagedCredentialsPath = node_path_1.default.join(claudeConfigDir, '.credentials.json');
|
|
1996
|
-
await (0, promises_1.writeFile)(stagedCredentialsPath, `${JSON.stringify({ claudeAiOauth })}\n`, { mode: 0o600 });
|
|
1997
|
-
await (0, promises_1.chmod)(stagedCredentialsPath, 0o600);
|
|
1998
|
-
await stageWorkerPlaydropSession({
|
|
1999
|
-
homeDir,
|
|
2000
|
-
workerUsername: input.workerUsername,
|
|
2001
|
-
envName: input.envName,
|
|
2002
|
-
playdropConfig: input.playdropConfig,
|
|
2003
|
-
});
|
|
2004
|
-
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
|
+
};
|
|
2005
1987
|
}
|
|
2006
1988
|
async function removeAgentRunCredentials(workspaceDir) {
|
|
2007
1989
|
await Promise.all([
|
|
@@ -2096,7 +2078,7 @@ async function runCodex(input) {
|
|
|
2096
2078
|
};
|
|
2097
2079
|
}
|
|
2098
2080
|
async function runClaude(input) {
|
|
2099
|
-
const {
|
|
2081
|
+
const { playdropConfigPath } = await prepareClaudeRunConfig({
|
|
2100
2082
|
workspaceDir: input.workspaceDir,
|
|
2101
2083
|
workerUsername: input.workerUsername,
|
|
2102
2084
|
envName: input.envName,
|
|
@@ -2107,7 +2089,8 @@ async function runClaude(input) {
|
|
|
2107
2089
|
: null;
|
|
2108
2090
|
let playwrightMcpConfigPath = null;
|
|
2109
2091
|
if (input.enablePlaywrightMcp) {
|
|
2110
|
-
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 });
|
|
2111
2094
|
(0, node_fs_1.writeFileSync)(playwrightMcpConfigPath, `${JSON.stringify((0, runtime_1.buildPlaywrightMcpServerConfig)(), null, 2)}\n`, 'utf8');
|
|
2112
2095
|
}
|
|
2113
2096
|
const result = await (0, runtime_1.runLoggedProcess)({
|
|
@@ -2129,8 +2112,7 @@ async function runClaude(input) {
|
|
|
2129
2112
|
envName: input.envName,
|
|
2130
2113
|
eventDir: input.eventDir,
|
|
2131
2114
|
devPort: input.devPort,
|
|
2132
|
-
|
|
2133
|
-
claudeConfigDir,
|
|
2115
|
+
playdropConfigPath,
|
|
2134
2116
|
playwrightBrowsersPath: resolveWorkerPlaywrightBrowsersPath(),
|
|
2135
2117
|
}),
|
|
2136
2118
|
stdin: input.prompt,
|
|
@@ -3870,6 +3852,7 @@ async function startWorker(options = {}) {
|
|
|
3870
3852
|
reasoningEffort = codexModel?.reasoningEffort ?? claudeModel?.effort ?? null;
|
|
3871
3853
|
const taskContext = buildWorkerTaskContext(claim, assignment);
|
|
3872
3854
|
workspaceDir = workerTaskWorkspaceDirFromAssignment(assignment);
|
|
3855
|
+
await pruneWorkerTaskWorkspaces(workspaceDir);
|
|
3873
3856
|
await (0, promises_1.rm)(workspaceDir, { recursive: true, force: true });
|
|
3874
3857
|
await (0, promises_1.mkdir)(workspaceDir, { recursive: true });
|
|
3875
3858
|
const eventDir = node_path_1.default.join(workspaceDir, '.playdrop-task-events');
|
|
@@ -28,7 +28,9 @@ export type InstrumentEvidenceManifest = {
|
|
|
28
28
|
};
|
|
29
29
|
export declare const PLAYWRIGHT_MCP_SCREENSHOT_TOOL = "mcp__playwright__browser_take_screenshot";
|
|
30
30
|
export declare const PLAYWRIGHT_MCP_CONTROLLED_TAB_ID = 1;
|
|
31
|
+
export declare const PLAYWRIGHT_MCP_SCREENSHOT_BUDGET = 100;
|
|
31
32
|
export declare function isInstrumentScreenshotId(value: string): boolean;
|
|
33
|
+
export declare function normalizePlaywrightScreenshotId(value: unknown): string | null;
|
|
32
34
|
export declare class ClaudeChromeScreenshotStreamParser {
|
|
33
35
|
private readonly onScreenshot;
|
|
34
36
|
private buffer;
|
|
@@ -44,9 +46,9 @@ export declare class ClaudeChromeScreenshotStreamParser {
|
|
|
44
46
|
export declare class PlaywrightMcpScreenshotStreamParser {
|
|
45
47
|
private readonly onScreenshot;
|
|
46
48
|
private buffer;
|
|
47
|
-
private readonly
|
|
49
|
+
private readonly pendingScreenshotFilenames;
|
|
48
50
|
private readonly handledToolUseIds;
|
|
49
|
-
private
|
|
51
|
+
private screenshotCallCount;
|
|
50
52
|
constructor(onScreenshot: (screenshot: ClaudeChromeScreenshot) => void);
|
|
51
53
|
push(chunk: string): number;
|
|
52
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,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE/D;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,
|
|
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"}
|
|
@@ -20,9 +20,11 @@ __export(instrument_evidence_exports, {
|
|
|
20
20
|
ClaudeChromeScreenshotStreamParser: () => ClaudeChromeScreenshotStreamParser,
|
|
21
21
|
INSTRUMENT_EVIDENCE_NAMES: () => INSTRUMENT_EVIDENCE_NAMES,
|
|
22
22
|
PLAYWRIGHT_MCP_CONTROLLED_TAB_ID: () => PLAYWRIGHT_MCP_CONTROLLED_TAB_ID,
|
|
23
|
+
PLAYWRIGHT_MCP_SCREENSHOT_BUDGET: () => PLAYWRIGHT_MCP_SCREENSHOT_BUDGET,
|
|
23
24
|
PLAYWRIGHT_MCP_SCREENSHOT_TOOL: () => PLAYWRIGHT_MCP_SCREENSHOT_TOOL,
|
|
24
25
|
PlaywrightMcpScreenshotStreamParser: () => PlaywrightMcpScreenshotStreamParser,
|
|
25
|
-
isInstrumentScreenshotId: () => isInstrumentScreenshotId
|
|
26
|
+
isInstrumentScreenshotId: () => isInstrumentScreenshotId,
|
|
27
|
+
normalizePlaywrightScreenshotId: () => normalizePlaywrightScreenshotId
|
|
26
28
|
});
|
|
27
29
|
module.exports = __toCommonJS(instrument_evidence_exports);
|
|
28
30
|
const INSTRUMENT_EVIDENCE_NAMES = [
|
|
@@ -36,10 +38,15 @@ const SCREENSHOT_ID_IN_TEXT_PATTERN = /\bID:\s*(ss_[A-Za-z0-9_-]+)\b/g;
|
|
|
36
38
|
const MAX_STREAM_LINE_LENGTH = 20 * 1024 * 1024;
|
|
37
39
|
const PLAYWRIGHT_MCP_SCREENSHOT_TOOL = "mcp__playwright__browser_take_screenshot";
|
|
38
40
|
const PLAYWRIGHT_MCP_CONTROLLED_TAB_ID = 1;
|
|
39
|
-
const
|
|
41
|
+
const PLAYWRIGHT_MCP_SCREENSHOT_BUDGET = 100;
|
|
42
|
+
const PLAYWRIGHT_SCREENSHOT_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}\.(png|jpe?g)$/;
|
|
40
43
|
function isInstrumentScreenshotId(value) {
|
|
41
44
|
return SCREENSHOT_ID_PATTERN.test(value) || PLAYWRIGHT_SCREENSHOT_ID_PATTERN.test(value);
|
|
42
45
|
}
|
|
46
|
+
function normalizePlaywrightScreenshotId(value) {
|
|
47
|
+
const id = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
48
|
+
return PLAYWRIGHT_SCREENSHOT_ID_PATTERN.test(id) ? id : null;
|
|
49
|
+
}
|
|
43
50
|
function asRecord(value) {
|
|
44
51
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
45
52
|
}
|
|
@@ -221,9 +228,9 @@ class ClaudeChromeScreenshotStreamParser {
|
|
|
221
228
|
class PlaywrightMcpScreenshotStreamParser {
|
|
222
229
|
onScreenshot;
|
|
223
230
|
buffer = "";
|
|
224
|
-
|
|
231
|
+
pendingScreenshotFilenames = /* @__PURE__ */ new Map();
|
|
225
232
|
handledToolUseIds = /* @__PURE__ */ new Set();
|
|
226
|
-
|
|
233
|
+
screenshotCallCount = 0;
|
|
227
234
|
constructor(onScreenshot) {
|
|
228
235
|
this.onScreenshot = onScreenshot;
|
|
229
236
|
}
|
|
@@ -287,7 +294,16 @@ class PlaywrightMcpScreenshotStreamParser {
|
|
|
287
294
|
if (item?.["type"] !== "tool_use" || !id || name !== PLAYWRIGHT_MCP_SCREENSHOT_TOOL) {
|
|
288
295
|
continue;
|
|
289
296
|
}
|
|
290
|
-
this.
|
|
297
|
+
this.screenshotCallCount += 1;
|
|
298
|
+
if (this.screenshotCallCount > PLAYWRIGHT_MCP_SCREENSHOT_BUDGET) {
|
|
299
|
+
throw new Error(`playwright_screenshot_budget_exceeded:${PLAYWRIGHT_MCP_SCREENSHOT_BUDGET}`);
|
|
300
|
+
}
|
|
301
|
+
const input = asRecord(item?.["input"]);
|
|
302
|
+
const filename = normalizePlaywrightScreenshotId(input?.["filename"]);
|
|
303
|
+
if (!filename) {
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
this.pendingScreenshotFilenames.set(id, filename);
|
|
291
307
|
}
|
|
292
308
|
}
|
|
293
309
|
recordToolResults(payload) {
|
|
@@ -300,11 +316,12 @@ class PlaywrightMcpScreenshotStreamParser {
|
|
|
300
316
|
continue;
|
|
301
317
|
}
|
|
302
318
|
const toolUseId = typeof toolResult["tool_use_id"] === "string" ? toolResult["tool_use_id"].trim() : "";
|
|
303
|
-
|
|
319
|
+
const screenshotId = this.pendingScreenshotFilenames.get(toolUseId);
|
|
320
|
+
if (!screenshotId || this.handledToolUseIds.has(toolUseId)) {
|
|
304
321
|
continue;
|
|
305
322
|
}
|
|
306
323
|
this.handledToolUseIds.add(toolUseId);
|
|
307
|
-
this.
|
|
324
|
+
this.pendingScreenshotFilenames.delete(toolUseId);
|
|
308
325
|
const resultContent = Array.isArray(toolResult["content"]) ? toolResult["content"] : [];
|
|
309
326
|
const images = resultContent.map(asRecord).filter((item) => item?.["type"] === "image");
|
|
310
327
|
if (images.length === 0) {
|
|
@@ -319,9 +336,8 @@ class PlaywrightMcpScreenshotStreamParser {
|
|
|
319
336
|
if (mediaType !== "image/jpeg" && mediaType !== "image/png" || !dataBase64) {
|
|
320
337
|
throw new Error(`invalid_playwright_screenshot_payload:${toolUseId}`);
|
|
321
338
|
}
|
|
322
|
-
this.sequence += 1;
|
|
323
339
|
this.onScreenshot({
|
|
324
|
-
screenshotId
|
|
340
|
+
screenshotId,
|
|
325
341
|
tabId: PLAYWRIGHT_MCP_CONTROLLED_TAB_ID,
|
|
326
342
|
toolUseId,
|
|
327
343
|
mediaType,
|
|
@@ -339,7 +355,9 @@ class PlaywrightMcpScreenshotStreamParser {
|
|
|
339
355
|
ClaudeChromeScreenshotStreamParser,
|
|
340
356
|
INSTRUMENT_EVIDENCE_NAMES,
|
|
341
357
|
PLAYWRIGHT_MCP_CONTROLLED_TAB_ID,
|
|
358
|
+
PLAYWRIGHT_MCP_SCREENSHOT_BUDGET,
|
|
342
359
|
PLAYWRIGHT_MCP_SCREENSHOT_TOOL,
|
|
343
360
|
PlaywrightMcpScreenshotStreamParser,
|
|
344
|
-
isInstrumentScreenshotId
|
|
361
|
+
isInstrumentScreenshotId,
|
|
362
|
+
normalizePlaywrightScreenshotId
|
|
345
363
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playdrop/playdrop-cli",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.20",
|
|
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": {
|