@link-assistant/hive-mind 1.52.1 → 1.53.0
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/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/src/agent.lib.mjs +18 -11
- package/src/agent.prompts.lib.mjs +18 -0
- package/src/claude.lib.mjs +12 -2
- package/src/claude.prompts.lib.mjs +1 -0
- package/src/codex.lib.mjs +5 -0
- package/src/codex.prompts.lib.mjs +1 -0
- package/src/opencode.lib.mjs +15 -0
- package/src/opencode.prompts.lib.mjs +18 -0
- package/src/playwright-mcp.lib.mjs +298 -0
- package/src/solve.config.lib.mjs +6 -1
- package/src/solve.mjs +24 -27
- package/src/solve.restart-shared.lib.mjs +29 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.53.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 906f61e: Add Playwright MCP browser automation fallback hints to all tools (opencode, agent), WebSearch fallback guidance to all tools (claude, codex, opencode, agent), and --no-playwright-mcp flag to physically disable Playwright MCP server connection per session without affecting global registration.
|
|
8
|
+
|
|
3
9
|
## 1.52.1
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/package.json
CHANGED
package/src/agent.lib.mjs
CHANGED
|
@@ -20,6 +20,7 @@ import { detectUsageLimit, formatUsageLimitMessage } from './usage-limit.lib.mjs
|
|
|
20
20
|
import { sanitizeObjectStrings } from './unicode-sanitization.lib.mjs';
|
|
21
21
|
import Decimal from 'decimal.js-light';
|
|
22
22
|
import { agentModels, defaultModels, freeToBaseModelMap } from './models/index.mjs';
|
|
23
|
+
import { checkPlaywrightMcpPackageAvailability, getAgentPlaywrightMcpDisableEnv } from './playwright-mcp.lib.mjs';
|
|
23
24
|
import { createAgentTokenUsage, accumulateAgentStepFinishUsage, parseAgentTokenUsage } from './agent-token-usage.lib.mjs';
|
|
24
25
|
|
|
25
26
|
export { createAgentTokenUsage, accumulateAgentStepFinishUsage, parseAgentTokenUsage };
|
|
@@ -320,6 +321,9 @@ export const handleAgentRuntimeSwitch = async () => {
|
|
|
320
321
|
await log('ℹ️ Agent runtime handling not required for this operation');
|
|
321
322
|
};
|
|
322
323
|
|
|
324
|
+
/** Check if Playwright MCP is available for Agent @returns {Promise<boolean>} */
|
|
325
|
+
export const checkPlaywrightMcpAvailability = checkPlaywrightMcpPackageAvailability;
|
|
326
|
+
|
|
323
327
|
// Main function to execute Agent with prompts and settings
|
|
324
328
|
export const executeAgent = async params => {
|
|
325
329
|
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, mergeStateStatus, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, log, formatAligned, getResourceSnapshot, agentPath = 'agent', $ } = params;
|
|
@@ -439,6 +443,19 @@ export const executeAgentCommand = async params => {
|
|
|
439
443
|
await log(` Memory: ${resourcesBefore.memory.split('\n')[1]}`, { verbose: true });
|
|
440
444
|
await log(` Load: ${resourcesBefore.load}`, { verbose: true });
|
|
441
445
|
|
|
446
|
+
// Issue #1521: Build environment for agent process.
|
|
447
|
+
// Pass LINK_ASSISTANT_AGENT_VERBOSE env var when --verbose is enabled so verbose logging is initialized at module load time.
|
|
448
|
+
const agentEnv = { ...process.env };
|
|
449
|
+
if (argv.verbose) {
|
|
450
|
+
agentEnv.LINK_ASSISTANT_AGENT_VERBOSE = 'true';
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Apply Playwright MCP session state before launching Agent.
|
|
454
|
+
if (argv.playwrightMcp === false) {
|
|
455
|
+
Object.assign(agentEnv, await getAgentPlaywrightMcpDisableEnv({ env: agentEnv, cwd: tempDir, log }));
|
|
456
|
+
await log('🎭 Playwright MCP physically disabled for this Agent session via --no-playwright-mcp', { verbose: true });
|
|
457
|
+
}
|
|
458
|
+
|
|
442
459
|
// Build Agent command
|
|
443
460
|
let execCommand;
|
|
444
461
|
|
|
@@ -473,17 +490,6 @@ export const executeAgentCommand = async params => {
|
|
|
473
490
|
// Pipe the prompt file to agent via stdin
|
|
474
491
|
// Use agentArgs which includes --model and optionally --verbose
|
|
475
492
|
|
|
476
|
-
// Issue #1521: Build environment for agent process
|
|
477
|
-
// Pass LINK_ASSISTANT_AGENT_VERBOSE env var when --verbose is enabled
|
|
478
|
-
// This ensures Flag.LINK_ASSISTANT_AGENT_VERBOSE is true at module load time inside the agent,
|
|
479
|
-
// which is required for HTTP request/response logging to work.
|
|
480
|
-
// The --verbose CLI flag alone is not sufficient because the agent's Flag module
|
|
481
|
-
// reads the env var at initialization, before yargs middleware calls Flag.setVerbose().
|
|
482
|
-
const agentEnv = { ...process.env };
|
|
483
|
-
if (argv.verbose) {
|
|
484
|
-
agentEnv.LINK_ASSISTANT_AGENT_VERBOSE = 'true';
|
|
485
|
-
}
|
|
486
|
-
|
|
487
493
|
execCommand = $({
|
|
488
494
|
cwd: tempDir,
|
|
489
495
|
mirror: false,
|
|
@@ -1041,6 +1047,7 @@ export const checkForUncommittedChanges = async (tempDir, owner, repo, branchNam
|
|
|
1041
1047
|
export default {
|
|
1042
1048
|
validateAgentConnection,
|
|
1043
1049
|
handleAgentRuntimeSwitch,
|
|
1050
|
+
checkPlaywrightMcpAvailability,
|
|
1044
1051
|
executeAgent,
|
|
1045
1052
|
executeAgentCommand,
|
|
1046
1053
|
checkForUncommittedChanges,
|
|
@@ -225,6 +225,24 @@ GitHub CLI command patterns.
|
|
|
225
225
|
- When adding issue comment, use gh issue comment NUMBER --body "text" --repo OWNER/REPO.
|
|
226
226
|
- When viewing PR details, use gh pr view NUMBER --repo OWNER/REPO.
|
|
227
227
|
- When filtering with jq, use gh api repos/${owner}/${repo}/pulls/${prNumber}/comments --paginate --jq 'reverse | .[0:5]'.${
|
|
228
|
+
argv && argv.promptPlaywrightMcp
|
|
229
|
+
? `
|
|
230
|
+
|
|
231
|
+
Playwright MCP usage (browser automation via MCP tools).
|
|
232
|
+
- When you develop frontend web applications (HTML, CSS, JavaScript, React, Vue, Angular, etc.), use Playwright MCP tools to test the UI in a real browser.
|
|
233
|
+
- When WebFetch tool fails to retrieve expected content (e.g., returns empty content, JavaScript-rendered pages, or login-protected pages), use Playwright MCP tools (browser_navigate, browser_snapshot) as a fallback for web browsing.
|
|
234
|
+
- When WebSearch tool fails or returns insufficient results, use Playwright MCP tools (browser_navigate, browser_snapshot) as a fallback for internet search.
|
|
235
|
+
- When you need to interact with dynamic web pages that require JavaScript execution, use Playwright MCP tools.
|
|
236
|
+
- When you need to visually verify how a web page looks or take screenshots, use browser_take_screenshot from Playwright MCP.
|
|
237
|
+
- When you need to fill forms, click buttons, or perform user interactions on web pages, use Playwright MCP tools (browser_click, browser_type, browser_fill_form).
|
|
238
|
+
- When you need to test responsive design or different viewport sizes, use browser_resize from Playwright MCP.
|
|
239
|
+
- When you finish using the browser, close it with browser_close to free resources.
|
|
240
|
+
- When reproducing UI bugs, use browser_take_screenshot to capture the problem state before implementing any fix.
|
|
241
|
+
- When fixing UI bugs, take before/after screenshots to provide visual evidence of the fix for human verification.
|
|
242
|
+
- When creating UI tests, save baseline screenshots to the repository for visual regression testing.
|
|
243
|
+
- When verifying UI fixes, compare screenshots to ensure the fix does not introduce unintended visual changes.`
|
|
244
|
+
: ''
|
|
245
|
+
}${
|
|
228
246
|
modelSupportsVision
|
|
229
247
|
? `
|
|
230
248
|
|
package/src/claude.lib.mjs
CHANGED
|
@@ -18,6 +18,7 @@ import { displayBudgetStats, createEmptySubSessionUsage, accumulateModelUsage, d
|
|
|
18
18
|
import { buildClaudeResumeCommand } from './claude.command-builder.lib.mjs';
|
|
19
19
|
import { handleClaudeRuntimeSwitch } from './claude.runtime-switch.lib.mjs'; // see issue #1141
|
|
20
20
|
import { CLAUDE_MODELS as availableModels } from './models/index.mjs'; // Issue #1221
|
|
21
|
+
import { buildMcpConfigWithoutPlaywright } from './playwright-mcp.lib.mjs';
|
|
21
22
|
export { availableModels }; // Re-export for backward compatibility
|
|
22
23
|
const showResumeCommand = async (sessionId, tempDir, claudePath, model, log) => {
|
|
23
24
|
if (!sessionId || !tempDir) return;
|
|
@@ -772,6 +773,14 @@ export const executeClaudeCommand = async params => {
|
|
|
772
773
|
await log(`🔄 Resuming from session: ${argv.resume}`);
|
|
773
774
|
claudeArgs = `--resume ${argv.resume} ${claudeArgs}`;
|
|
774
775
|
}
|
|
776
|
+
let mcpConfigPath = null;
|
|
777
|
+
if (argv.playwrightMcp === false) {
|
|
778
|
+
mcpConfigPath = await buildMcpConfigWithoutPlaywright(log);
|
|
779
|
+
if (mcpConfigPath) {
|
|
780
|
+
claudeArgs += ` --strict-mcp-config --mcp-config "${mcpConfigPath}"`;
|
|
781
|
+
await log('🎭 Playwright MCP physically disabled for this session via --strict-mcp-config', { verbose: true });
|
|
782
|
+
}
|
|
783
|
+
}
|
|
775
784
|
claudeArgs += ` -p "${escapedPrompt}" --append-system-prompt "${escapedSystemPrompt}"`;
|
|
776
785
|
const fullCommand = `(cd "${tempDir}" && ${claudePath} ${claudeArgs} | jq -c .)`;
|
|
777
786
|
await log(`\n${formatAligned('📝', 'Raw command:', '')}`);
|
|
@@ -795,11 +804,12 @@ export const executeClaudeCommand = async params => {
|
|
|
795
804
|
if (!isNewVersion && thinkLevel) await log(`📊 Thinking level (via keywords): ${thinkLevel}`, { verbose: true });
|
|
796
805
|
}
|
|
797
806
|
const simpleEscapedSystem = systemPrompt.replace(/"/g, '\\"');
|
|
807
|
+
const mcpDisableArgs = mcpConfigPath ? ['--strict-mcp-config', '--mcp-config', mcpConfigPath] : [];
|
|
798
808
|
if (argv.resume) {
|
|
799
809
|
const simpleEscapedPrompt = prompt.replace(/"/g, '\\"');
|
|
800
|
-
execCommand = $({ cwd: tempDir, mirror: false, env: claudeEnv })`${claudePath} --resume ${argv.resume} --output-format stream-json --verbose --dangerously-skip-permissions --model ${effectiveModel} -p "${simpleEscapedPrompt}" --append-system-prompt "${simpleEscapedSystem}"`;
|
|
810
|
+
execCommand = $({ cwd: tempDir, mirror: false, env: claudeEnv })`${claudePath} --resume ${argv.resume} --output-format stream-json --verbose --dangerously-skip-permissions --model ${effectiveModel} ${mcpDisableArgs} -p "${simpleEscapedPrompt}" --append-system-prompt "${simpleEscapedSystem}"`;
|
|
801
811
|
} else {
|
|
802
|
-
execCommand = $({ cwd: tempDir, stdin: prompt, mirror: false, env: claudeEnv })`${claudePath} --output-format stream-json --verbose --dangerously-skip-permissions --model ${effectiveModel} --append-system-prompt "${simpleEscapedSystem}"`;
|
|
812
|
+
execCommand = $({ cwd: tempDir, stdin: prompt, mirror: false, env: claudeEnv })`${claudePath} --output-format stream-json --verbose --dangerously-skip-permissions --model ${effectiveModel} ${mcpDisableArgs} --append-system-prompt "${simpleEscapedSystem}"`;
|
|
803
813
|
}
|
|
804
814
|
await log(`${formatAligned('📋', 'Command details:', '')}`);
|
|
805
815
|
await log(formatAligned('📂', 'Working directory:', tempDir, 2));
|
|
@@ -268,6 +268,7 @@ GitHub CLI command patterns.
|
|
|
268
268
|
Playwright MCP usage (browser automation via mcp__playwright__* tools).
|
|
269
269
|
- When you develop frontend web applications (HTML, CSS, JavaScript, React, Vue, Angular, etc.), use Playwright MCP tools to test the UI in a real browser.
|
|
270
270
|
- When WebFetch tool fails to retrieve expected content (e.g., returns empty content, JavaScript-rendered pages, or login-protected pages), use Playwright MCP tools (browser_navigate, browser_snapshot) as a fallback for web browsing.
|
|
271
|
+
- When WebSearch tool fails or returns insufficient results, use Playwright MCP tools (browser_navigate, browser_snapshot) as a fallback for internet search.
|
|
271
272
|
- When you need to interact with dynamic web pages that require JavaScript execution, use Playwright MCP tools.
|
|
272
273
|
- When you need to visually verify how a web page looks or take screenshots, use browser_take_screenshot from Playwright MCP.
|
|
273
274
|
- When you need to fill forms, click buttons, or perform user interactions on web pages, use Playwright MCP tools (browser_click, browser_type, browser_fill_form).
|
package/src/codex.lib.mjs
CHANGED
|
@@ -21,6 +21,7 @@ import { sanitizeObjectStrings } from './unicode-sanitization.lib.mjs';
|
|
|
21
21
|
import { mapModelToId, resolveCodexReasoningEffort } from './codex.options.lib.mjs';
|
|
22
22
|
import { createInteractiveHandler } from './interactive-mode.lib.mjs';
|
|
23
23
|
import { initProgressMonitoring } from './solve.progress-monitoring.lib.mjs';
|
|
24
|
+
import { getCodexPlaywrightMcpDisableConfigArgs } from './playwright-mcp.lib.mjs';
|
|
24
25
|
|
|
25
26
|
const CODEX_USAGE_FIELD_NAMES = ['input_tokens', 'cached_input_tokens', 'output_tokens', 'cache_write_tokens', 'cache_creation_input_tokens', 'reasoning_tokens', 'input_tokens_details.cached_tokens', 'input_tokens_details.cache_read_tokens', 'input_tokens_details.cache_write_tokens', 'input_tokens_details.cache_creation_tokens', 'input_tokens_details.cache_creation_input_tokens', 'output_tokens_details.reasoning_tokens'];
|
|
26
27
|
const getCodexExecEnv = (verbose = false) => (verbose ? { ...process.env, RUST_LOG: 'debug' } : { ...process.env });
|
|
@@ -584,6 +585,10 @@ export const executeCodexCommand = async params => {
|
|
|
584
585
|
} else {
|
|
585
586
|
codexArgs += ` --model ${shellQuote(mappedModel)}`;
|
|
586
587
|
}
|
|
588
|
+
const codexPlaywrightMcpDisableConfigArgs = argv.playwrightMcp === false ? await getCodexPlaywrightMcpDisableConfigArgs(log) : [];
|
|
589
|
+
for (const arg of codexPlaywrightMcpDisableConfigArgs) {
|
|
590
|
+
codexArgs += ` ${shellQuote(arg)}`;
|
|
591
|
+
}
|
|
587
592
|
codexArgs += ` --json --skip-git-repo-check -o ${shellQuote(lastMessageFile)} -c ${shellQuote(`model_reasoning_effort=${reasoningEffort}`)} -c ${shellQuote('model_reasoning_summary=auto')} --dangerously-bypass-approvals-and-sandbox`;
|
|
588
593
|
|
|
589
594
|
const fullCommand = `(cd ${shellQuote(tempDir)} && cat ${shellQuote(promptFile)} | ${codexPath} ${codexArgs})`;
|
|
@@ -260,6 +260,7 @@ GitHub CLI command patterns.
|
|
|
260
260
|
Playwright MCP usage (browser automation via MCP tools).
|
|
261
261
|
- When you develop frontend web applications or debug UI issues, use Playwright MCP tools to test the UI in a real browser.
|
|
262
262
|
- When simple fetch-based browsing is insufficient for dynamic pages, use Playwright MCP browser automation as a fallback.
|
|
263
|
+
- When WebSearch tool fails or returns insufficient results, use Playwright MCP browser automation as a fallback for internet search.
|
|
263
264
|
- When reproducing or verifying UI bugs, take before/after screenshots and close the browser when finished.`
|
|
264
265
|
: ''
|
|
265
266
|
}${
|
package/src/opencode.lib.mjs
CHANGED
|
@@ -19,6 +19,7 @@ import { timeouts } from './config.lib.mjs';
|
|
|
19
19
|
import { detectUsageLimit, formatUsageLimitMessage } from './usage-limit.lib.mjs';
|
|
20
20
|
import { sanitizeObjectStrings } from './unicode-sanitization.lib.mjs';
|
|
21
21
|
import { opencodeModels, defaultModels } from './models/index.mjs';
|
|
22
|
+
import { checkPlaywrightMcpPackageAvailability, getOpenCodePlaywrightMcpDisableEnv } from './playwright-mcp.lib.mjs';
|
|
22
23
|
import { createAgentTokenUsage, accumulateAgentStepFinishUsage, parseAgentTokenUsage as parseOpenCodeTokenUsage } from './agent-token-usage.lib.mjs';
|
|
23
24
|
import { calculateAgentPricing } from './agent.lib.mjs';
|
|
24
25
|
|
|
@@ -101,6 +102,9 @@ export const handleOpenCodeRuntimeSwitch = async () => {
|
|
|
101
102
|
await log('ℹ️ OpenCode runtime handling not required for this operation');
|
|
102
103
|
};
|
|
103
104
|
|
|
105
|
+
/** Check if Playwright MCP is available for OpenCode @returns {Promise<boolean>} */
|
|
106
|
+
export const checkPlaywrightMcpAvailability = checkPlaywrightMcpPackageAvailability;
|
|
107
|
+
|
|
104
108
|
// Main function to execute OpenCode with prompts and settings
|
|
105
109
|
export const executeOpenCode = async params => {
|
|
106
110
|
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, mergeStateStatus, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, log, formatAligned, getResourceSnapshot, opencodePath = 'opencode', $ } = params;
|
|
@@ -241,6 +245,14 @@ export const executeOpenCodeCommand = async params => {
|
|
|
241
245
|
await log(` Memory: ${resourcesBefore.memory.split('\n')[1]}`, { verbose: true });
|
|
242
246
|
await log(` Load: ${resourcesBefore.load}`, { verbose: true });
|
|
243
247
|
|
|
248
|
+
const opencodeEnv = { ...process.env };
|
|
249
|
+
|
|
250
|
+
// Apply Playwright MCP session state before launching OpenCode.
|
|
251
|
+
if (argv.playwrightMcp === false) {
|
|
252
|
+
Object.assign(opencodeEnv, await getOpenCodePlaywrightMcpDisableEnv({ env: opencodeEnv, cwd: tempDir, log }));
|
|
253
|
+
await log('🎭 Playwright MCP physically disabled for this OpenCode session via --no-playwright-mcp', { verbose: true });
|
|
254
|
+
}
|
|
255
|
+
|
|
244
256
|
// Build OpenCode command
|
|
245
257
|
let execCommand;
|
|
246
258
|
|
|
@@ -288,11 +300,13 @@ export const executeOpenCodeCommand = async params => {
|
|
|
288
300
|
execCommand = $({
|
|
289
301
|
cwd: tempDir,
|
|
290
302
|
mirror: false,
|
|
303
|
+
env: opencodeEnv,
|
|
291
304
|
})`cat ${promptFile} | ${opencodePath} run --format json --resume ${argv.resume} --model ${mappedModel}`;
|
|
292
305
|
} else {
|
|
293
306
|
execCommand = $({
|
|
294
307
|
cwd: tempDir,
|
|
295
308
|
mirror: false,
|
|
309
|
+
env: opencodeEnv,
|
|
296
310
|
})`cat ${promptFile} | ${opencodePath} run --format json --model ${mappedModel}`;
|
|
297
311
|
}
|
|
298
312
|
|
|
@@ -653,6 +667,7 @@ export const checkForUncommittedChanges = async (tempDir, owner, repo, branchNam
|
|
|
653
667
|
export default {
|
|
654
668
|
validateOpenCodeConnection,
|
|
655
669
|
handleOpenCodeRuntimeSwitch,
|
|
670
|
+
checkPlaywrightMcpAvailability,
|
|
656
671
|
executeOpenCode,
|
|
657
672
|
executeOpenCodeCommand,
|
|
658
673
|
checkForUncommittedChanges,
|
|
@@ -226,6 +226,24 @@ GitHub CLI command patterns.
|
|
|
226
226
|
- When adding issue comment, use gh issue comment NUMBER --body "text" --repo OWNER/REPO.
|
|
227
227
|
- When viewing PR details, use gh pr view NUMBER --repo OWNER/REPO.
|
|
228
228
|
- When filtering with jq, use gh api repos/${owner}/${repo}/pulls/${prNumber}/comments --paginate --jq 'reverse | .[0:5]'.${
|
|
229
|
+
argv && argv.promptPlaywrightMcp
|
|
230
|
+
? `
|
|
231
|
+
|
|
232
|
+
Playwright MCP usage (browser automation via MCP tools).
|
|
233
|
+
- When you develop frontend web applications (HTML, CSS, JavaScript, React, Vue, Angular, etc.), use Playwright MCP tools to test the UI in a real browser.
|
|
234
|
+
- When WebFetch tool fails to retrieve expected content (e.g., returns empty content, JavaScript-rendered pages, or login-protected pages), use Playwright MCP tools (browser_navigate, browser_snapshot) as a fallback for web browsing.
|
|
235
|
+
- When WebSearch tool fails or returns insufficient results, use Playwright MCP tools (browser_navigate, browser_snapshot) as a fallback for internet search.
|
|
236
|
+
- When you need to interact with dynamic web pages that require JavaScript execution, use Playwright MCP tools.
|
|
237
|
+
- When you need to visually verify how a web page looks or take screenshots, use browser_take_screenshot from Playwright MCP.
|
|
238
|
+
- When you need to fill forms, click buttons, or perform user interactions on web pages, use Playwright MCP tools (browser_click, browser_type, browser_fill_form).
|
|
239
|
+
- When you need to test responsive design or different viewport sizes, use browser_resize from Playwright MCP.
|
|
240
|
+
- When you finish using the browser, close it with browser_close to free resources.
|
|
241
|
+
- When reproducing UI bugs, use browser_take_screenshot to capture the problem state before implementing any fix.
|
|
242
|
+
- When fixing UI bugs, take before/after screenshots to provide visual evidence of the fix for human verification.
|
|
243
|
+
- When creating UI tests, save baseline screenshots to the repository for visual regression testing.
|
|
244
|
+
- When verifying UI fixes, compare screenshots to ensure the fix does not introduce unintended visual changes.`
|
|
245
|
+
: ''
|
|
246
|
+
}${
|
|
229
247
|
modelSupportsVision
|
|
230
248
|
? `
|
|
231
249
|
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Playwright MCP session-level disable/restore utilities.
|
|
3
|
+
if (typeof globalThis.use === 'undefined') {
|
|
4
|
+
globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
|
|
5
|
+
}
|
|
6
|
+
const { $ } = await use('command-stream');
|
|
7
|
+
const fs = (await use('fs')).promises;
|
|
8
|
+
const os = await use('os');
|
|
9
|
+
const path = (await use('path')).default;
|
|
10
|
+
|
|
11
|
+
export const getCommandResultCode = result => result?.code ?? result?.exitCode ?? null;
|
|
12
|
+
|
|
13
|
+
export const getCommandResultOutput = result => `${result?.stdout?.toString() || ''}${result?.stderr?.toString() || ''}`;
|
|
14
|
+
|
|
15
|
+
export const isCommandResultSuccess = result => getCommandResultCode(result) === 0;
|
|
16
|
+
|
|
17
|
+
export const checkPlaywrightMcpPackageAvailability = async () => {
|
|
18
|
+
try {
|
|
19
|
+
const result = await $`timeout 5 npx --no-install @playwright/mcp --help 2>&1`.catch(() => null);
|
|
20
|
+
if (isCommandResultSuccess(result)) return true;
|
|
21
|
+
const npmResult = await $`timeout 5 npm ls -g @playwright/mcp 2>&1`.catch(() => null);
|
|
22
|
+
return getCommandResultOutput(npmResult).includes('@playwright/mcp');
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const parseCodexMcpServerNames = output =>
|
|
29
|
+
output
|
|
30
|
+
.split(/\r?\n/)
|
|
31
|
+
.map(line => line.trim())
|
|
32
|
+
.filter(line => line && !line.startsWith('Name '))
|
|
33
|
+
.map(line => line.split(/\s+/)[0])
|
|
34
|
+
.filter(name => /^[A-Za-z0-9_-]+$/.test(name));
|
|
35
|
+
|
|
36
|
+
export const getCodexPlaywrightMcpDisableConfigArgs = async log => {
|
|
37
|
+
try {
|
|
38
|
+
const result = await $`timeout 5 codex mcp list 2>&1`.catch(() => null);
|
|
39
|
+
if (!isCommandResultSuccess(result)) return [];
|
|
40
|
+
const names = parseCodexMcpServerNames(getCommandResultOutput(result)).filter(name => name.toLowerCase().includes('playwright'));
|
|
41
|
+
if (names.length === 0) {
|
|
42
|
+
if (log) await log('🎭 No Codex Playwright MCP server registration found to disable for this session', { verbose: true });
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
if (log) {
|
|
46
|
+
await log(`🎭 Playwright MCP disabled for this Codex session via config override: ${names.join(', ')}`, {
|
|
47
|
+
verbose: true,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return names.flatMap(name => ['-c', `mcp_servers.${name}.enabled=false`]);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
if (log) await log(`⚠️ Could not build Codex Playwright MCP disable override: ${err.message}`, { verbose: true });
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const isPlainObject = value => value && typeof value === 'object' && !Array.isArray(value);
|
|
58
|
+
|
|
59
|
+
const mergeDeep = (base, override) => {
|
|
60
|
+
const result = { ...(isPlainObject(base) ? base : {}) };
|
|
61
|
+
for (const [key, value] of Object.entries(isPlainObject(override) ? override : {})) {
|
|
62
|
+
result[key] = isPlainObject(result[key]) && isPlainObject(value) ? mergeDeep(result[key], value) : value;
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const stripJsonComments = input => {
|
|
68
|
+
let output = '';
|
|
69
|
+
let inString = false;
|
|
70
|
+
let quote = '';
|
|
71
|
+
let escaped = false;
|
|
72
|
+
for (let index = 0; index < input.length; index++) {
|
|
73
|
+
const char = input[index];
|
|
74
|
+
const next = input[index + 1];
|
|
75
|
+
if (inString) {
|
|
76
|
+
output += char;
|
|
77
|
+
if (escaped) {
|
|
78
|
+
escaped = false;
|
|
79
|
+
} else if (char === '\\') {
|
|
80
|
+
escaped = true;
|
|
81
|
+
} else if (char === quote) {
|
|
82
|
+
inString = false;
|
|
83
|
+
}
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
if (char === '"' || char === "'") {
|
|
87
|
+
inString = true;
|
|
88
|
+
quote = char;
|
|
89
|
+
output += char;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (char === '/' && next === '/') {
|
|
93
|
+
while (index < input.length && input[index] !== '\n') index++;
|
|
94
|
+
output += '\n';
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (char === '/' && next === '*') {
|
|
98
|
+
index += 2;
|
|
99
|
+
while (index < input.length && !(input[index] === '*' && input[index + 1] === '/')) index++;
|
|
100
|
+
index++;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
output += char;
|
|
104
|
+
}
|
|
105
|
+
return output;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const stripTrailingCommas = input => {
|
|
109
|
+
let output = '';
|
|
110
|
+
let inString = false;
|
|
111
|
+
let quote = '';
|
|
112
|
+
let escaped = false;
|
|
113
|
+
for (let index = 0; index < input.length; index++) {
|
|
114
|
+
const char = input[index];
|
|
115
|
+
if (inString) {
|
|
116
|
+
output += char;
|
|
117
|
+
if (escaped) {
|
|
118
|
+
escaped = false;
|
|
119
|
+
} else if (char === '\\') {
|
|
120
|
+
escaped = true;
|
|
121
|
+
} else if (char === quote) {
|
|
122
|
+
inString = false;
|
|
123
|
+
}
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (char === '"' || char === "'") {
|
|
127
|
+
inString = true;
|
|
128
|
+
quote = char;
|
|
129
|
+
output += char;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (char === ',') {
|
|
133
|
+
let lookahead = index + 1;
|
|
134
|
+
while (/\s/.test(input[lookahead] || '')) lookahead++;
|
|
135
|
+
if (input[lookahead] === '}' || input[lookahead] === ']') continue;
|
|
136
|
+
}
|
|
137
|
+
output += char;
|
|
138
|
+
}
|
|
139
|
+
return output;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const parseConfigContent = content => {
|
|
143
|
+
if (!content || typeof content !== 'string') return {};
|
|
144
|
+
try {
|
|
145
|
+
return JSON.parse(content);
|
|
146
|
+
} catch {
|
|
147
|
+
try {
|
|
148
|
+
return JSON.parse(stripTrailingCommas(stripJsonComments(content)));
|
|
149
|
+
} catch {
|
|
150
|
+
return {};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const readConfigFile = async filePath => {
|
|
156
|
+
const content = await fs.readFile(filePath, 'utf-8').catch(() => null);
|
|
157
|
+
return parseConfigContent(content);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const pathExists = async filePath =>
|
|
161
|
+
fs
|
|
162
|
+
.stat(filePath)
|
|
163
|
+
.then(() => true)
|
|
164
|
+
.catch(() => false);
|
|
165
|
+
|
|
166
|
+
const findUpConfigPaths = async (startDir, filenames) => {
|
|
167
|
+
const results = [];
|
|
168
|
+
let dir = startDir || process.cwd();
|
|
169
|
+
while (dir) {
|
|
170
|
+
for (const file of filenames) results.push(path.join(dir, file));
|
|
171
|
+
if (await pathExists(path.join(dir, '.git'))) break;
|
|
172
|
+
const parent = path.dirname(dir);
|
|
173
|
+
if (parent === dir) break;
|
|
174
|
+
dir = parent;
|
|
175
|
+
}
|
|
176
|
+
return results;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const configFilesInDir = dir => (dir ? ['config.json', 'opencode.json', 'opencode.jsonc'].map(file => path.join(dir, file)) : []);
|
|
180
|
+
|
|
181
|
+
const isPlaywrightMcpEntry = (name, config) => {
|
|
182
|
+
const haystack = `${name || ''} ${JSON.stringify(config || {})}`.toLowerCase();
|
|
183
|
+
return haystack.includes('playwright');
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export const collectPlaywrightMcpServerNames = (...configs) => {
|
|
187
|
+
const names = new Set();
|
|
188
|
+
for (const config of configs.flat()) {
|
|
189
|
+
if (!isPlainObject(config?.mcp)) continue;
|
|
190
|
+
for (const [name, mcpConfig] of Object.entries(config.mcp)) {
|
|
191
|
+
if (isPlaywrightMcpEntry(name, mcpConfig)) names.add(name);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return [...names];
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
export const buildPlaywrightMcpDisableConfig = (serverNames = []) => {
|
|
198
|
+
const names = [...new Set(['playwright', ...serverNames].filter(Boolean))];
|
|
199
|
+
const mcp = {};
|
|
200
|
+
const tools = {
|
|
201
|
+
'*playwright*': false,
|
|
202
|
+
'mcp__playwright__*': false,
|
|
203
|
+
};
|
|
204
|
+
for (const name of names) {
|
|
205
|
+
mcp[name] = {
|
|
206
|
+
type: 'local',
|
|
207
|
+
command: ['npx', '-y', '@playwright/mcp@latest'],
|
|
208
|
+
enabled: false,
|
|
209
|
+
};
|
|
210
|
+
tools[`${name}_*`] = false;
|
|
211
|
+
tools[`mcp__${name}__*`] = false;
|
|
212
|
+
}
|
|
213
|
+
return {
|
|
214
|
+
$schema: 'https://opencode.ai/config.json',
|
|
215
|
+
mcp,
|
|
216
|
+
tools,
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
export const mergePlaywrightMcpDisableConfigContent = (existingContent = '', serverNames = []) => {
|
|
221
|
+
const existingConfig = parseConfigContent(existingContent);
|
|
222
|
+
const detectedNames = collectPlaywrightMcpServerNames(existingConfig);
|
|
223
|
+
const disableConfig = buildPlaywrightMcpDisableConfig([...serverNames, ...detectedNames]);
|
|
224
|
+
return JSON.stringify(mergeDeep(existingConfig, disableConfig), null, 2);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const collectPlaywrightMcpServerNamesFromFiles = async filePaths => {
|
|
228
|
+
const configs = [];
|
|
229
|
+
for (const filePath of filePaths) configs.push(await readConfigFile(filePath));
|
|
230
|
+
return collectPlaywrightMcpServerNames(configs);
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const getConfigHome = env => env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
|
|
234
|
+
|
|
235
|
+
const getOpenCodeConfigFilePaths = async ({ env = process.env, cwd = process.cwd() } = {}) => [...configFilesInDir(path.join(getConfigHome(env), 'opencode')), ...(env.OPENCODE_CONFIG ? [env.OPENCODE_CONFIG] : []), ...(await findUpConfigPaths(cwd, ['opencode.jsonc', 'opencode.json'])), ...configFilesInDir(env.OPENCODE_CONFIG_DIR)];
|
|
236
|
+
|
|
237
|
+
const getAgentConfigFilePaths = async ({ env = process.env, cwd = process.cwd() } = {}) => [...configFilesInDir(path.join(getConfigHome(env), 'link-assistant-agent')), ...(env.LINK_ASSISTANT_AGENT_CONFIG ? [env.LINK_ASSISTANT_AGENT_CONFIG] : []), ...(env.OPENCODE_CONFIG ? [env.OPENCODE_CONFIG] : []), ...(await findUpConfigPaths(cwd, ['opencode.jsonc', 'opencode.json'])), ...configFilesInDir(path.join(cwd, '.link-assistant-agent')), ...configFilesInDir(path.join(cwd, '.opencode')), ...configFilesInDir(env.LINK_ASSISTANT_AGENT_CONFIG_DIR), ...configFilesInDir(env.OPENCODE_CONFIG_DIR)];
|
|
238
|
+
|
|
239
|
+
export const getOpenCodePlaywrightMcpDisableEnv = async ({ env = process.env, cwd = process.cwd(), includeConfigFiles = true, log } = {}) => {
|
|
240
|
+
const inlineConfig = env.OPENCODE_CONFIG_CONTENT || '';
|
|
241
|
+
const names = collectPlaywrightMcpServerNames(parseConfigContent(inlineConfig));
|
|
242
|
+
if (includeConfigFiles) {
|
|
243
|
+
names.push(...(await collectPlaywrightMcpServerNamesFromFiles(await getOpenCodeConfigFilePaths({ env, cwd }))));
|
|
244
|
+
}
|
|
245
|
+
const uniqueNames = [...new Set(names)];
|
|
246
|
+
const displayNames = [...new Set(['playwright', ...uniqueNames])];
|
|
247
|
+
if (log) await log(`🎭 OpenCode Playwright MCP disabled through OPENCODE_CONFIG_CONTENT for: ${displayNames.join(', ')}`, { verbose: true });
|
|
248
|
+
return {
|
|
249
|
+
OPENCODE_CONFIG_CONTENT: mergePlaywrightMcpDisableConfigContent(inlineConfig, uniqueNames),
|
|
250
|
+
};
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
export const getAgentPlaywrightMcpDisableEnv = async ({ env = process.env, cwd = process.cwd(), includeConfigFiles = true, log } = {}) => {
|
|
254
|
+
const agentInlineConfig = env.LINK_ASSISTANT_AGENT_CONFIG_CONTENT || env.OPENCODE_CONFIG_CONTENT || '';
|
|
255
|
+
const names = collectPlaywrightMcpServerNames(parseConfigContent(agentInlineConfig), parseConfigContent(env.OPENCODE_CONFIG_CONTENT || ''));
|
|
256
|
+
if (includeConfigFiles) {
|
|
257
|
+
names.push(...(await collectPlaywrightMcpServerNamesFromFiles(await getAgentConfigFilePaths({ env, cwd }))));
|
|
258
|
+
}
|
|
259
|
+
const uniqueNames = [...new Set(names)];
|
|
260
|
+
const displayNames = [...new Set(['playwright', ...uniqueNames])];
|
|
261
|
+
const configContent = mergePlaywrightMcpDisableConfigContent(agentInlineConfig, uniqueNames);
|
|
262
|
+
if (log) await log(`🎭 Agent Playwright MCP disabled through LINK_ASSISTANT_AGENT_CONFIG_CONTENT for: ${displayNames.join(', ')}`, { verbose: true });
|
|
263
|
+
return {
|
|
264
|
+
LINK_ASSISTANT_AGENT_CONFIG_CONTENT: configContent,
|
|
265
|
+
OPENCODE_CONFIG_CONTENT: mergePlaywrightMcpDisableConfigContent(env.OPENCODE_CONFIG_CONTENT || agentInlineConfig, uniqueNames),
|
|
266
|
+
};
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
/** Build a temporary MCP config JSON excluding Playwright, for use with --strict-mcp-config */
|
|
270
|
+
export const buildMcpConfigWithoutPlaywright = async log => {
|
|
271
|
+
try {
|
|
272
|
+
const claudeJsonPath = path.join(os.homedir(), '.claude.json');
|
|
273
|
+
const claudeJson = JSON.parse(await fs.readFile(claudeJsonPath, 'utf-8'));
|
|
274
|
+
const mcpServers = claudeJson.mcpServers || {};
|
|
275
|
+
const filtered = {};
|
|
276
|
+
for (const [name, config] of Object.entries(mcpServers)) {
|
|
277
|
+
if (name.toLowerCase().includes('playwright')) continue;
|
|
278
|
+
filtered[name] = config;
|
|
279
|
+
}
|
|
280
|
+
const tempConfigPath = path.join(os.tmpdir(), `claude-mcp-no-playwright-${Date.now()}-${process.pid}.json`);
|
|
281
|
+
await fs.writeFile(tempConfigPath, JSON.stringify({ mcpServers: filtered }, null, 2));
|
|
282
|
+
if (log) await log(`🎭 Created filtered MCP config (without Playwright): ${tempConfigPath}`, { verbose: true });
|
|
283
|
+
return tempConfigPath;
|
|
284
|
+
} catch (err) {
|
|
285
|
+
if (log) await log(`⚠️ Could not build filtered MCP config: ${err.message}`, { verbose: true });
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
/** Cascade --no-playwright-mcp to disable related flags */
|
|
291
|
+
export const cascadePlaywrightMcpDisable = async (argv, log) => {
|
|
292
|
+
if (argv.playwrightMcp === false) {
|
|
293
|
+
if (log) await log('🎭 Playwright MCP physically disabled via --no-playwright-mcp', { verbose: true });
|
|
294
|
+
argv.promptPlaywrightMcp = false;
|
|
295
|
+
argv.playwrightMcpAutoCleanup = false;
|
|
296
|
+
if (log) await log('ℹ️ --prompt-playwright-mcp and --playwright-mcp-auto-cleanup also disabled', { verbose: true });
|
|
297
|
+
}
|
|
298
|
+
};
|
package/src/solve.config.lib.mjs
CHANGED
|
@@ -366,7 +366,7 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
366
366
|
},
|
|
367
367
|
'prompt-playwright-mcp': {
|
|
368
368
|
type: 'boolean',
|
|
369
|
-
description: 'Enable Playwright MCP browser automation hints in system prompt (enabled by default, only takes effect if Playwright MCP is installed). Use --no-prompt-playwright-mcp to disable. Supported for --tool claude and --tool
|
|
369
|
+
description: 'Enable Playwright MCP browser automation hints in system prompt (enabled by default, only takes effect if Playwright MCP is installed). Use --no-prompt-playwright-mcp to disable. Supported for --tool claude, --tool codex, --tool opencode, and --tool agent.',
|
|
370
370
|
default: true,
|
|
371
371
|
},
|
|
372
372
|
'prompt-check-sibling-pull-requests': {
|
|
@@ -384,6 +384,11 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
384
384
|
description: 'Path to examples folder used in system prompt. Set to empty string to disable examples folder prompt. Default: ./examples',
|
|
385
385
|
default: './examples',
|
|
386
386
|
},
|
|
387
|
+
'playwright-mcp': {
|
|
388
|
+
type: 'boolean',
|
|
389
|
+
description: 'Enable Playwright MCP server connection for this session (enabled by default). Use --no-playwright-mcp to physically disable the Playwright MCP server without affecting the global MCP registration. When disabled, also disables --prompt-playwright-mcp and --playwright-mcp-auto-cleanup. Supported for --tool claude, --tool codex, --tool opencode, and --tool agent.',
|
|
390
|
+
default: true,
|
|
391
|
+
},
|
|
387
392
|
'playwright-mcp-auto-cleanup': {
|
|
388
393
|
type: 'boolean',
|
|
389
394
|
description: 'Automatically remove .playwright-mcp/ folder before checking for uncommitted changes. This prevents browser automation artifacts from triggering auto-restart. Use --no-playwright-mcp-auto-cleanup to keep the folder for debugging.',
|
package/src/solve.mjs
CHANGED
|
@@ -707,12 +707,31 @@ try {
|
|
|
707
707
|
$,
|
|
708
708
|
});
|
|
709
709
|
|
|
710
|
+
const { cascadePlaywrightMcpDisable } = await import('./playwright-mcp.lib.mjs');
|
|
711
|
+
await cascadePlaywrightMcpDisable(argv, log);
|
|
712
|
+
|
|
713
|
+
async function resolvePlaywrightMcp(checkFn) {
|
|
714
|
+
if (argv.playwrightMcp === false) return;
|
|
715
|
+
if (argv.promptPlaywrightMcp) {
|
|
716
|
+
const available = await checkFn();
|
|
717
|
+
if (available) {
|
|
718
|
+
await log('🎭 Playwright MCP detected - enabling browser automation hints', { verbose: true });
|
|
719
|
+
} else {
|
|
720
|
+
await log('ℹ️ Playwright MCP not detected - browser automation hints will be disabled', { verbose: true });
|
|
721
|
+
argv.promptPlaywrightMcp = false;
|
|
722
|
+
}
|
|
723
|
+
} else {
|
|
724
|
+
await log('ℹ️ Playwright MCP explicitly disabled via --no-prompt-playwright-mcp', { verbose: true });
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
710
728
|
// Execute tool command with all prompts and settings
|
|
711
729
|
let toolResult;
|
|
712
730
|
if (argv.tool === 'opencode') {
|
|
713
731
|
const opencodeLib = await import('./opencode.lib.mjs');
|
|
714
|
-
const { executeOpenCode } = opencodeLib;
|
|
732
|
+
const { executeOpenCode, checkPlaywrightMcpAvailability: checkOpenCodePlaywrightMcp } = opencodeLib;
|
|
715
733
|
const opencodePath = process.env.OPENCODE_PATH || 'opencode';
|
|
734
|
+
await resolvePlaywrightMcp(checkOpenCodePlaywrightMcp);
|
|
716
735
|
|
|
717
736
|
toolResult = await executeOpenCode({
|
|
718
737
|
issueUrl,
|
|
@@ -742,18 +761,7 @@ try {
|
|
|
742
761
|
const codexLib = await import('./codex.lib.mjs');
|
|
743
762
|
const { executeCodex, checkPlaywrightMcpAvailability } = codexLib;
|
|
744
763
|
const codexPath = process.env.CODEX_PATH || 'codex';
|
|
745
|
-
|
|
746
|
-
if (argv.promptPlaywrightMcp) {
|
|
747
|
-
const playwrightMcpAvailable = await checkPlaywrightMcpAvailability();
|
|
748
|
-
if (playwrightMcpAvailable) {
|
|
749
|
-
await log('🎭 Playwright MCP detected - enabling browser automation hints', { verbose: true });
|
|
750
|
-
} else {
|
|
751
|
-
await log('ℹ️ Playwright MCP not detected - browser automation hints will be disabled', { verbose: true });
|
|
752
|
-
argv.promptPlaywrightMcp = false;
|
|
753
|
-
}
|
|
754
|
-
} else {
|
|
755
|
-
await log('ℹ️ Playwright MCP explicitly disabled via --no-prompt-playwright-mcp', { verbose: true });
|
|
756
|
-
}
|
|
764
|
+
await resolvePlaywrightMcp(checkPlaywrightMcpAvailability);
|
|
757
765
|
|
|
758
766
|
toolResult = await executeCodex({
|
|
759
767
|
issueUrl,
|
|
@@ -781,8 +789,9 @@ try {
|
|
|
781
789
|
});
|
|
782
790
|
} else if (argv.tool === 'agent') {
|
|
783
791
|
const agentLib = await import('./agent.lib.mjs');
|
|
784
|
-
const { executeAgent } = agentLib;
|
|
792
|
+
const { executeAgent, checkPlaywrightMcpAvailability: checkAgentPlaywrightMcp } = agentLib;
|
|
785
793
|
const agentPath = process.env.AGENT_PATH || 'agent';
|
|
794
|
+
await resolvePlaywrightMcp(checkAgentPlaywrightMcp);
|
|
786
795
|
|
|
787
796
|
toolResult = await executeAgent({
|
|
788
797
|
issueUrl,
|
|
@@ -810,20 +819,8 @@ try {
|
|
|
810
819
|
});
|
|
811
820
|
} else {
|
|
812
821
|
// Default to Claude
|
|
813
|
-
// Check for Playwright MCP availability if using Claude tool
|
|
814
822
|
if (argv.tool === 'claude' || !argv.tool) {
|
|
815
|
-
|
|
816
|
-
if (argv.promptPlaywrightMcp) {
|
|
817
|
-
const playwrightMcpAvailable = await checkPlaywrightMcpAvailability();
|
|
818
|
-
if (playwrightMcpAvailable) {
|
|
819
|
-
await log('🎭 Playwright MCP detected - enabling browser automation hints', { verbose: true });
|
|
820
|
-
} else {
|
|
821
|
-
await log('ℹ️ Playwright MCP not detected - browser automation hints will be disabled', { verbose: true });
|
|
822
|
-
argv.promptPlaywrightMcp = false;
|
|
823
|
-
}
|
|
824
|
-
} else {
|
|
825
|
-
await log('ℹ️ Playwright MCP explicitly disabled via --no-prompt-playwright-mcp', { verbose: true });
|
|
826
|
-
}
|
|
823
|
+
await resolvePlaywrightMcp(checkPlaywrightMcpAvailability);
|
|
827
824
|
}
|
|
828
825
|
const claudeResult = await executeClaude({
|
|
829
826
|
issueUrl,
|
|
@@ -178,13 +178,28 @@ export const executeToolIteration = async params => {
|
|
|
178
178
|
const memoryCheck = await import('./memory-check.mjs');
|
|
179
179
|
const { getResourceSnapshot } = memoryCheck;
|
|
180
180
|
|
|
181
|
+
const { cascadePlaywrightMcpDisable } = await import('./playwright-mcp.lib.mjs');
|
|
182
|
+
await cascadePlaywrightMcpDisable(argv, log);
|
|
183
|
+
|
|
181
184
|
let toolResult;
|
|
182
185
|
if (argv.tool === 'opencode') {
|
|
183
186
|
// Use OpenCode
|
|
184
187
|
const opencodeExecLib = await import('./opencode.lib.mjs');
|
|
185
|
-
const { executeOpenCode } = opencodeExecLib;
|
|
188
|
+
const { executeOpenCode, checkPlaywrightMcpAvailability } = opencodeExecLib;
|
|
186
189
|
const opencodePath = argv.opencodePath || 'opencode';
|
|
187
190
|
|
|
191
|
+
if (argv.promptPlaywrightMcp) {
|
|
192
|
+
const playwrightMcpAvailable = await checkPlaywrightMcpAvailability();
|
|
193
|
+
if (playwrightMcpAvailable) {
|
|
194
|
+
await log('🎭 Playwright MCP detected - enabling browser automation hints', { verbose: true });
|
|
195
|
+
} else {
|
|
196
|
+
await log('ℹ️ Playwright MCP not detected - browser automation hints will be disabled', { verbose: true });
|
|
197
|
+
argv.promptPlaywrightMcp = false;
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
await log('ℹ️ Playwright MCP explicitly disabled via --no-prompt-playwright-mcp', { verbose: true });
|
|
201
|
+
}
|
|
202
|
+
|
|
188
203
|
toolResult = await executeOpenCode({
|
|
189
204
|
issueUrl,
|
|
190
205
|
issueNumber,
|
|
@@ -249,9 +264,21 @@ export const executeToolIteration = async params => {
|
|
|
249
264
|
} else if (argv.tool === 'agent') {
|
|
250
265
|
// Use Agent
|
|
251
266
|
const agentExecLib = await import('./agent.lib.mjs');
|
|
252
|
-
const { executeAgent } = agentExecLib;
|
|
267
|
+
const { executeAgent, checkPlaywrightMcpAvailability } = agentExecLib;
|
|
253
268
|
const agentPath = argv.agentPath || 'agent';
|
|
254
269
|
|
|
270
|
+
if (argv.promptPlaywrightMcp) {
|
|
271
|
+
const playwrightMcpAvailable = await checkPlaywrightMcpAvailability();
|
|
272
|
+
if (playwrightMcpAvailable) {
|
|
273
|
+
await log('🎭 Playwright MCP detected - enabling browser automation hints', { verbose: true });
|
|
274
|
+
} else {
|
|
275
|
+
await log('ℹ️ Playwright MCP not detected - browser automation hints will be disabled', { verbose: true });
|
|
276
|
+
argv.promptPlaywrightMcp = false;
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
await log('ℹ️ Playwright MCP explicitly disabled via --no-prompt-playwright-mcp', { verbose: true });
|
|
280
|
+
}
|
|
281
|
+
|
|
255
282
|
toolResult = await executeAgent({
|
|
256
283
|
issueUrl,
|
|
257
284
|
issueNumber,
|