Files
shannon/apps/worker/src/ai/playwright-config-writer.ts
T
ezl-keygraph 1af42339b9 feat(auth): auth-validation preflight + email_login credentials (#335)
* feat(preflight): add credential validation activity

* refactor(preflight): tighten error retryability and dedup failure-point enum

* refactor(preflight): extract resolvePromptDir helper and cap failure_detail at 250 chars

* refactor(preflight): inline validator rules into intro paragraph

* refactor(preflight): restyle validator prompt with XML tags and tool list

* chore(preflight): bump auth validation timeout to 10 minutes

* feat: provision playwright stealth config for browser auto-discovery

* feat(stealth): strengthen browser fingerprint with chrome.runtime and realistic plugins

* feat(prompts): add pipeline-testing stub for validate-authentication

* refactor(stealth): swap zx for node:fs in playwright-config-writer

* feat(auth): add email_login credentials with login-flow substitution

* fix(auth): propagate email_login through credentials sanitizer

* fix(config): drop dangerous-pattern check on credentials.password

* feat(auth-validation): instruct agent to mask sensitive values in failure_detail

* docs(auth): document email_login credentials for magic-link and email-OTP flows

* docs(auth): add login_flow authoring guide with placeholder reference

* feat(auth): make credentials.password optional for passwordless flows

* docs(auth): drop redundant placeholder hint from login_flow examples
2026-05-20 03:46:56 +05:30

91 lines
3.5 KiB
TypeScript

// Copyright (C) 2025 Keygraph, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License version 3
// as published by the Free Software Foundation.
/**
* Writes <sourceDir>/.playwright/cli.config.json with stealth defaults so
* `playwright-cli open` auto-loads them from the agent's cwd. Skipped when a
* config already exists so user-provided files are never clobbered.
*
* NOTE: Playwright's MCP browser config treats `initScript` entries as file
* paths, not inline source. The stealth script is written alongside the config
* and referenced by absolute path. Inline strings silently fail the daemon.
*/
import fs from 'node:fs/promises';
import path from 'node:path';
async function pathExists(p: string): Promise<boolean> {
try {
await fs.access(p);
return true;
} catch {
return false;
}
}
const STEALTH_INIT_SCRIPT = `delete Object.getPrototypeOf(navigator).webdriver;
Object.defineProperty(navigator, 'plugins', {
get: () => {
const arr = [
{ name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
{ name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
{ name: 'Native Client', filename: 'internal-nacl-plugin', description: '' },
];
arr.__proto__ = PluginArray.prototype;
return arr;
},
});
window.chrome = window.chrome || {};
window.chrome.runtime = window.chrome.runtime || {
PlatformOs: { MAC: 'mac', WIN: 'win', ANDROID: 'android', CROS: 'cros', LINUX: 'linux', OPENBSD: 'openbsd' },
PlatformArch: { ARM: 'arm', X86_32: 'x86-32', X86_64: 'x86-64' },
PlatformNaclArch: { ARM: 'arm', X86_32: 'x86-32', X86_64: 'x86-64' },
RequestUpdateCheckStatus: { THROTTLED: 'throttled', NO_UPDATE: 'no_update', UPDATE_AVAILABLE: 'update_available' },
OnInstalledReason: { INSTALL: 'install', UPDATE: 'update', CHROME_UPDATE: 'chrome_update', SHARED_MODULE_UPDATE: 'shared_module_update' },
OnRestartRequiredReason: { APP_UPDATE: 'app_update', OS_UPDATE: 'os_update', PERIODIC: 'periodic' },
};
`;
function buildStealthConfig(initScriptPath: string) {
return {
browser: {
browserName: 'chromium',
launchOptions: {
headless: true,
args: ['--disable-blink-features=AutomationControlled'],
ignoreDefaultArgs: ['--enable-automation'],
},
contextOptions: {
viewport: { width: 1920, height: 1080 },
locale: 'en-US',
extraHTTPHeaders: { 'Accept-Language': 'en-US,en;q=0.9' },
userAgent:
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
},
initScript: [initScriptPath],
},
};
}
export type StealthConfigWriteResult = 'wrote' | 'skipped-existing';
export async function writePlaywrightStealthConfig(
sourceDir: string,
): Promise<{ result: StealthConfigWriteResult; configPath: string }> {
const playwrightDir = path.join(sourceDir, '.playwright');
const configPath = path.join(playwrightDir, 'cli.config.json');
if (await pathExists(configPath)) {
return { result: 'skipped-existing', configPath };
}
const initScriptPath = path.join(playwrightDir, 'scripts', 'stealth.js');
await fs.mkdir(path.dirname(initScriptPath), { recursive: true });
await fs.writeFile(initScriptPath, STEALTH_INIT_SCRIPT);
await fs.writeFile(configPath, JSON.stringify(buildStealthConfig(initScriptPath), null, 2));
return { result: 'wrote', configPath };
}