@link-assistant/hive-mind 1.78.2 → 1.78.4

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.78.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 798c352: Fix Playwright MCP availability detection so pending or unavailable server status no longer enables browser automation hints, surface pending status in interactive session comments, and harden Docker verification so Playwright MCP/CLI availability is checked instead of only grepping for a registration.
8
+
9
+ ## 1.78.3
10
+
11
+ ### Patch Changes
12
+
13
+ - b346808: Use the latest gh-upload-log package for attached log uploads and rely on its default auto mode/shared repository fallback instead of passing explicit strategy flags.
14
+
3
15
  ## 1.78.2
4
16
 
5
17
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.78.2",
3
+ "version": "1.78.4",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -22,7 +22,7 @@ import { buildSolveResumeCommand } from './solve.resume-command.lib.mjs'; // Iss
22
22
  import { SESSION_FORCE_KILLED_MARKER, postTrackedComment } from './tool-comments.lib.mjs'; // Issue #1625
23
23
  import { handleClaudeRuntimeSwitch } from './claude.runtime-switch.lib.mjs'; // see issue #1141
24
24
  import { CLAUDE_MODELS as availableModels } from './models/index.mjs'; // Issue #1221
25
- import { buildMcpConfigWithoutPlaywright } from './playwright-mcp.lib.mjs';
25
+ import { buildMcpConfigWithoutPlaywright, ensureClaudePlaywrightMcpServer } from './playwright-mcp.lib.mjs';
26
26
  import { resolveClaudeSessionToolFlags } from './useless-tools.lib.mjs';
27
27
  import { ensureClaudeQuietConfig } from './claude-quiet-config.lib.mjs';
28
28
  import { fetchModelInfo } from './model-info.lib.mjs';
@@ -268,17 +268,7 @@ export const resolveThinkingSettings = async (argv, log) => {
268
268
  return { thinkingBudget, thinkLevel, translation, isNewVersion, maxBudget };
269
269
  };
270
270
  /** Check if Playwright MCP is available and connected to Claude @returns {Promise<boolean>} */
271
- export const checkPlaywrightMcpAvailability = async () => {
272
- try {
273
- const result = await $`timeout 5 claude mcp list 2>&1`.catch(() => null);
274
- if (!result || result.code !== 0) return false;
275
- const output = result.stdout?.toString() || '';
276
- if (output.toLowerCase().includes('playwright')) return true;
277
- return false;
278
- } catch {
279
- return false;
280
- }
281
- };
271
+ export const checkPlaywrightMcpAvailability = ensureClaudePlaywrightMcpServer;
282
272
  /** Execute Claude with all prompts and settings - main entry point */
283
273
  export const executeClaude = async params => {
284
274
  const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, mergeStateStatus, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, log, setLogFile, getLogFile, formatAligned, getResourceSnapshot, claudePath, $ } = params;
package/src/codex.lib.mjs CHANGED
@@ -24,7 +24,7 @@ import { sanitizeObjectStrings } from './unicode-sanitization.lib.mjs';
24
24
  import { mapModelToId, resolveCodexReasoningEffort } from './codex.options.lib.mjs';
25
25
  import { createInteractiveHandler } from './interactive-mode.lib.mjs';
26
26
  import { initProgressMonitoring } from './solve.progress-monitoring.lib.mjs';
27
- import { getCodexPlaywrightMcpDisableConfigArgs } from './playwright-mcp.lib.mjs';
27
+ import { ensureCodexPlaywrightMcpServer, getCodexPlaywrightMcpDisableConfigArgs } from './playwright-mcp.lib.mjs';
28
28
  import { fetchModelInfo } from './model-info.lib.mjs';
29
29
  import { defaultModels } from './models/index.mjs';
30
30
  import { classifyRetryableError, getRetryDelayMs, maybeSwitchToFallbackModel, waitWithCountdown } from './tool-retry.lib.mjs';
@@ -573,16 +573,7 @@ export const handleCodexRuntimeSwitch = async () => {
573
573
  };
574
574
 
575
575
  /** Check if Playwright MCP is available and connected to Codex @returns {Promise<boolean>} */
576
- export const checkPlaywrightMcpAvailability = async () => {
577
- try {
578
- const result = await $`timeout 5 codex mcp list 2>&1`.catch(() => null);
579
- if (!result || result.code !== 0) return false;
580
- const output = `${result.stdout?.toString() || ''}${result.stderr?.toString() || ''}`;
581
- return output.toLowerCase().includes('playwright');
582
- } catch {
583
- return false;
584
- }
585
- };
576
+ export const checkPlaywrightMcpAvailability = ensureCodexPlaywrightMcpServer;
586
577
 
587
578
  // Main function to execute Codex with prompts and settings
588
579
  export const executeCodex = async params => {
@@ -631,7 +631,7 @@ ${logContent}
631
631
  // Use the original sanitized content for upload since it's a plain text file
632
632
  await fs.writeFile(tempLogFile, await sanitizeLogContent(rawLogContent));
633
633
 
634
- // Use gh-upload-log to upload the log file
634
+ // Use gh-upload-log default auto mode and shared repository fallback.
635
635
  const uploadDescription = `Solution draft log for https://github.com/${owner}/${repo}/${targetType === 'pr' ? 'pull' : 'issues'}/${targetNumber}`;
636
636
  const uploadResult = await uploadLogWithGhUploadLog({
637
637
  logFile: tempLogFile,
@@ -0,0 +1,44 @@
1
+ const PLAYWRIGHT_TOOL_PREFIX = 'mcp__playwright__';
2
+
3
+ export const isUnavailableMcpStatus = status => {
4
+ const normalized = String(status || '').toLowerCase();
5
+ return /\b(pending|disabled|failed|error|disconnected|not[-_\s]+connected|unavailable|timed[-_\s]+out)\b|(?:^|[^a-z0-9_-])timeout(?:$|[^a-z0-9_-])/.test(normalized);
6
+ };
7
+
8
+ export const hasPlaywrightMcpTools = tools => (Array.isArray(tools) ? tools : []).some(tool => String(tool || '').startsWith(PLAYWRIGHT_TOOL_PREFIX));
9
+
10
+ export const formatInteractiveMcpServerStatus = server => {
11
+ const name = server?.name || 'unknown';
12
+ const status = String(server?.status || 'unknown').trim() || 'unknown';
13
+ const normalizedStatus = status.toLowerCase();
14
+ let displayStatus = status;
15
+
16
+ if (normalizedStatus === 'pending') {
17
+ displayStatus = 'pending - not connected; MCP tools unavailable';
18
+ } else if (isUnavailableMcpStatus(status)) {
19
+ displayStatus = `${status} - MCP tools unavailable`;
20
+ }
21
+
22
+ return `\`${name}\` (${displayStatus})`;
23
+ };
24
+
25
+ export const getInteractiveMcpDiagnostics = (mcpServers = [], tools = []) => {
26
+ const servers = Array.isArray(mcpServers) ? mcpServers : [];
27
+ const diagnostics = [];
28
+
29
+ for (const server of servers) {
30
+ const name = String(server?.name || '').toLowerCase();
31
+ if (!name.includes('playwright')) continue;
32
+ if (!isUnavailableMcpStatus(server?.status)) continue;
33
+ if (hasPlaywrightMcpTools(tools)) continue;
34
+
35
+ diagnostics.push(`âš ī¸ Playwright MCP server is ${server?.status || 'unknown'}, but no \`${PLAYWRIGHT_TOOL_PREFIX}*\` browser tools were exposed. Browser automation hints are disabled until the MCP client reports the server as connected.`);
36
+ }
37
+
38
+ return diagnostics;
39
+ };
40
+
41
+ export const formatInteractiveMcpServersList = (mcpServers = []) => {
42
+ const servers = Array.isArray(mcpServers) ? mcpServers : [];
43
+ return servers.length > 0 ? servers.map(formatInteractiveMcpServerStatus).join(', ') : '_None_';
44
+ };
@@ -39,6 +39,7 @@ import { createCodexEventHandlers } from './interactive-codex-events.lib.mjs';
39
39
  import { createSystemLifecycleHandlers } from './interactive-system-events.lib.mjs';
40
40
  // Issue #1843: turn base64 image tool-results into inline PR-comment images.
41
41
  import { createImageRenderer, extractImagePayload, isImageNode } from './interactive-image-render.lib.mjs';
42
+ import { formatInteractiveMcpServersList, getInteractiveMcpDiagnostics } from './interactive-mcp-status.lib.mjs';
42
43
  // Issue #1625: track interactive-mode comment IDs so they're excluded from
43
44
  // the "did the AI post anything?" check in checkForAiCreatedComments().
44
45
  // Use the session-started marker as the single source of truth for the
@@ -411,7 +412,9 @@ export const createInteractiveHandler = options => {
411
412
 
412
413
  // Format MCP servers
413
414
  const mcpServers = data.mcp_servers || [];
414
- const mcpServersList = mcpServers.length > 0 ? mcpServers.map(s => `\`${s.name}\` (${s.status || 'unknown'})`).join(', ') : '_None_';
415
+ const mcpServersList = formatInteractiveMcpServersList(mcpServers);
416
+ const mcpDiagnostics = getInteractiveMcpDiagnostics(mcpServers, tools);
417
+ const mcpDiagnosticsBlock = mcpDiagnostics.length > 0 ? `\n${mcpDiagnostics.map(message => `> ${message}`).join('\n')}\n` : '';
415
418
 
416
419
  // Format slash commands
417
420
  const slashCommands = data.slash_commands || [];
@@ -434,6 +437,7 @@ export const createInteractiveHandler = options => {
434
437
  | **MCP Servers** | ${mcpServersList} |
435
438
  | **Slash Commands** | ${slashCommandsList} |
436
439
  | **Agents** | ${agentsList} |
440
+ ${mcpDiagnosticsBlock}
437
441
 
438
442
  ---
439
443
 
@@ -1374,10 +1378,6 @@ export const validateInteractiveModeConfig = async (argv, log) => {
1374
1378
  return false;
1375
1379
  }
1376
1380
 
1377
- // Check PR requirement
1378
- // Note: This should be called after PR is created/determined
1379
- // The actual PR number check happens during execution
1380
-
1381
1381
  await log('🔌 Interactive mode: ENABLED (experimental)', { level: 'info' });
1382
1382
  await log(` ${argv.tool || 'claude'} output will be posted as PR comments in real-time.`, { level: 'info' });
1383
1383
 
@@ -28,6 +28,63 @@ const summarizeCommandOutput = value => {
28
28
  return text.length > 500 ? `${text.slice(0, 500)}... [truncated ${text.length - 500} chars]` : text;
29
29
  };
30
30
 
31
+ export const buildGhUploadLogArgs = ({ logFile, isPublic, description, verbose = false }) => {
32
+ if (!logFile) {
33
+ throw new Error('logFile is required for gh-upload-log');
34
+ }
35
+
36
+ const args = [logFile, isPublic ? '--public' : '--private'];
37
+
38
+ if (description) {
39
+ args.push('--description', description);
40
+ }
41
+ if (verbose) {
42
+ args.push('--verbose');
43
+ }
44
+
45
+ return args;
46
+ };
47
+
48
+ const quoteShellArg = value => {
49
+ const text = String(value);
50
+ if (/^[A-Za-z0-9_./:=@+-]+$/u.test(text)) return text;
51
+ return `"${text.replace(/(["\\$`])/gu, '\\$1')}"`;
52
+ };
53
+
54
+ const formatGhUploadLogCommand = args => `gh-upload-log ${args.map(quoteShellArg).join(' ')}`;
55
+
56
+ const runGhUploadLogCommand = async args => {
57
+ const { spawn } = await use('child_process');
58
+
59
+ return new Promise(resolve => {
60
+ const child = spawn('gh-upload-log', args, { stdio: ['ignore', 'pipe', 'pipe'] });
61
+ let stdout = '';
62
+ let stderr = '';
63
+ let settled = false;
64
+
65
+ const settle = value => {
66
+ if (!settled) {
67
+ settled = true;
68
+ resolve(value);
69
+ }
70
+ };
71
+
72
+ child.stdout?.on('data', chunk => {
73
+ stdout += chunk.toString();
74
+ });
75
+ child.stderr?.on('data', chunk => {
76
+ stderr += chunk.toString();
77
+ });
78
+ child.on('error', error => {
79
+ const errorText = stderr ? `${stderr}\n${error.message}` : error.message;
80
+ settle({ code: error.code === 'ENOENT' ? 127 : 1, stdout, stderr: errorText });
81
+ });
82
+ child.on('close', code => {
83
+ settle({ code: code ?? 1, stdout, stderr });
84
+ });
85
+ });
86
+ };
87
+
31
88
  export const parseGhUploadLogOutput = outputValue => {
32
89
  const output = outputValue?.toString?.() || '';
33
90
  const parsed = {
@@ -89,30 +146,18 @@ export const uploadLogWithGhUploadLog = async ({ logFile, isPublic, description,
89
146
  const result = { success: false, url: null, rawUrl: null, type: null, chunks: 1 };
90
147
 
91
148
  try {
92
- // Build command flags
93
- // IMPORTANT: When using command-stream's $ template tag, each ${} interpolation is treated
94
- // as a single argument. DO NOT use commandArgs.join(' ') as it will make all flags part
95
- // of the first positional argument, causing "File does not exist" errors.
96
- // See case study: docs/case-studies/issue-1096/README.md
97
- const publicFlag = isPublic ? '--public' : '--private';
149
+ const commandArgs = buildGhUploadLogArgs({
150
+ logFile,
151
+ isPublic,
152
+ description,
153
+ verbose,
154
+ });
98
155
 
99
156
  if (verbose) {
100
- const descDisplay = description ? ` --description "${description}"` : '';
101
- await log(` 📤 Running: gh-upload-log "${logFile}" ${publicFlag}${descDisplay} --verbose`, { verbose: true });
157
+ await log(` 📤 Running: ${formatGhUploadLogCommand(commandArgs)}`, { verbose: true });
102
158
  }
103
159
 
104
- // Execute command with separate interpolations for each argument
105
- // Each ${} is properly passed as a separate argument to the shell
106
- let uploadResult;
107
- if (description && verbose) {
108
- uploadResult = await $`gh-upload-log ${logFile} ${publicFlag} --description ${description} --verbose`;
109
- } else if (description) {
110
- uploadResult = await $`gh-upload-log ${logFile} ${publicFlag} --description ${description}`;
111
- } else if (verbose) {
112
- uploadResult = await $`gh-upload-log ${logFile} ${publicFlag} --verbose`;
113
- } else {
114
- uploadResult = await $`gh-upload-log ${logFile} ${publicFlag}`;
115
- }
160
+ const uploadResult = await runGhUploadLogCommand(commandArgs);
116
161
  const output = (uploadResult.stdout?.toString() || '') + (uploadResult.stderr?.toString() || '');
117
162
 
118
163
  if (uploadResult.code !== 0) {
@@ -252,5 +297,6 @@ export const uploadLogWithGhUploadLog = async ({ logFile, isPublic, description,
252
297
  // Export all functions as default object too
253
298
  export default {
254
299
  parseGhUploadLogOutput,
300
+ buildGhUploadLogArgs,
255
301
  uploadLogWithGhUploadLog,
256
302
  };
@@ -15,6 +15,21 @@ export const getCommandResultOutput = result => `${result?.stdout?.toString() ||
15
15
 
16
16
  export const isCommandResultSuccess = result => getCommandResultCode(result) === 0;
17
17
 
18
+ export const PLAYWRIGHT_MCP_UNAVAILABLE_PATTERN = /\b(pending|disabled|failed|error|disconnected|not[-_\s]+connected|unavailable|timed[-_\s]+out)\b|(?:^|[^A-Za-z0-9_-])timeout(?:$|[^A-Za-z0-9_-])|\benabled\s*[:=]?\s*false\b/i;
19
+ export const PLAYWRIGHT_MCP_CONNECTED_PATTERN = /\b(connected|enabled)\b|[✓✔]/i;
20
+
21
+ export const getPlaywrightMcpListRows = output =>
22
+ String(output || '')
23
+ .split(/\r?\n/)
24
+ .map(line => line.trim())
25
+ .filter(line => line.toLowerCase().includes('playwright'));
26
+
27
+ export const hasConnectedPlaywrightMcpServer = output => {
28
+ const rows = getPlaywrightMcpListRows(output);
29
+ if (rows.length === 0) return false;
30
+ return rows.some(row => PLAYWRIGHT_MCP_CONNECTED_PATTERN.test(row) && !PLAYWRIGHT_MCP_UNAVAILABLE_PATTERN.test(row));
31
+ };
32
+
18
33
  export const checkPlaywrightMcpPackageAvailability = async () => {
19
34
  try {
20
35
  const result = await $`timeout 5 npx --no-install @playwright/mcp --help 2>&1`.catch(() => null);
@@ -26,6 +41,83 @@ export const checkPlaywrightMcpPackageAvailability = async () => {
26
41
  }
27
42
  };
28
43
 
44
+ export const ensureConnectedPlaywrightMcpServer = async ({ list, add, hasPackage = checkPlaywrightMcpPackageAvailability }) => {
45
+ try {
46
+ const result = await list().catch(() => null);
47
+ if (!isCommandResultSuccess(result)) return false;
48
+ const output = getCommandResultOutput(result);
49
+ if (hasConnectedPlaywrightMcpServer(output)) return true;
50
+ if (getPlaywrightMcpListRows(output).length > 0) return false;
51
+ if (!(await hasPackage())) return false;
52
+
53
+ await add().catch(() => null);
54
+ const retryResult = await list().catch(() => null);
55
+ return isCommandResultSuccess(retryResult) && hasConnectedPlaywrightMcpServer(getCommandResultOutput(retryResult));
56
+ } catch {
57
+ return false;
58
+ }
59
+ };
60
+
61
+ export const ensureClaudePlaywrightMcpServer = async () =>
62
+ ensureConnectedPlaywrightMcpServer({
63
+ list: () => $`timeout 5 claude mcp list 2>&1`,
64
+ add: () => $`claude mcp add playwright -s user -- npx -y @playwright/mcp@latest --isolated --headless --no-sandbox --timeout-action=600000 --viewport-size 1920x1080`,
65
+ });
66
+
67
+ export const ensureCodexPlaywrightMcpServer = async () =>
68
+ ensureConnectedPlaywrightMcpServer({
69
+ list: () => $`timeout 5 codex mcp list 2>&1`,
70
+ add: () => $`codex mcp add playwright -- npx -y @playwright/mcp@latest --isolated --headless --no-sandbox --timeout-action=600000 --viewport-size 1920x1080`,
71
+ });
72
+
73
+ const SOLVE_PLAYWRIGHT_MCP_CHECKS = {
74
+ claude: ensureClaudePlaywrightMcpServer,
75
+ codex: ensureCodexPlaywrightMcpServer,
76
+ opencode: checkPlaywrightMcpPackageAvailability,
77
+ agent: checkPlaywrightMcpPackageAvailability,
78
+ gemini: checkPlaywrightMcpPackageAvailability,
79
+ qwen: checkPlaywrightMcpPackageAvailability,
80
+ };
81
+
82
+ const SOLVE_PLAYWRIGHT_MCP_LABELS = {
83
+ claude: 'Claude Code',
84
+ codex: 'Codex',
85
+ opencode: 'OpenCode',
86
+ agent: 'Agent',
87
+ gemini: 'Gemini',
88
+ qwen: 'Qwen Code',
89
+ };
90
+
91
+ export const getSolvePlaywrightMcpCheckTool = (argv = {}) => argv.tool || 'claude';
92
+
93
+ export const ensureSolvePlaywrightMcpReady = async ({ argv = {}, log = async () => {}, checks = SOLVE_PLAYWRIGHT_MCP_CHECKS } = {}) => {
94
+ if (argv.playwrightMcp === false) {
95
+ await log('🎭 Playwright MCP preflight skipped because --no-playwright-mcp is set', { verbose: true });
96
+ return { ok: true, checkedTools: [], skipped: true };
97
+ }
98
+
99
+ const tool = getSolvePlaywrightMcpCheckTool(argv);
100
+ const label = SOLVE_PLAYWRIGHT_MCP_LABELS[tool] || tool;
101
+ const checkFn = checks[tool] || SOLVE_PLAYWRIGHT_MCP_CHECKS[tool] || checkPlaywrightMcpPackageAvailability;
102
+ await log(`🎭 Checking Playwright MCP preflight for ${label}...`, { verbose: true });
103
+
104
+ try {
105
+ if (await checkFn()) {
106
+ await log(`🎭 Playwright MCP ready for ${label}`, { verbose: true });
107
+ return { ok: true, checkedTools: [tool], skipped: false };
108
+ }
109
+ } catch (error) {
110
+ await log(`âš ī¸ Playwright MCP preflight check threw for ${label}: ${error.message}`, { verbose: true });
111
+ }
112
+
113
+ await log('', { level: 'error' });
114
+ await log(`❌ Playwright MCP preflight failed for ${label}`, { level: 'error' });
115
+ await log(' Playwright support is enabled by default, so solve stops before starting an AI working session.', { level: 'error' });
116
+ await log(' Fix the MCP registration or run with --no-playwright-mcp only when browser automation is intentionally disabled.', { level: 'error' });
117
+ await log('');
118
+ return { ok: false, checkedTools: [tool], skipped: false };
119
+ };
120
+
29
121
  export const parseCodexMcpServerNames = output =>
30
122
  output
31
123
  .split(/\r?\n/)
package/src/solve.mjs CHANGED
@@ -33,7 +33,7 @@ const { setupTempDirectory, cleanupTempDirectory } = repository;
33
33
  const results = await import('./solve.results.lib.mjs');
34
34
  const { cleanupClaudeFile, showSessionSummary, verifyResults, buildClaudeResumeCommand, buildClaudeAutonomousResumeCommand, buildSolveResumeCommand, maybeAttachWorkingSessionSummary, verifyPullRequestIssueLinkAfterAutoRestart } = results;
35
35
  const claudeLib = await import('./claude.lib.mjs');
36
- const { executeClaude, checkPlaywrightMcpAvailability } = claudeLib;
36
+ const { executeClaude } = claudeLib;
37
37
  const githubLinking = await import('./github-linking.lib.mjs');
38
38
  const { extractLinkedIssueNumber } = githubLinking;
39
39
  const usageLimitLib = await import('./usage-limit.lib.mjs');
@@ -244,9 +244,20 @@ if (argv.planModel) {
244
244
 
245
245
  // Perform all system checks (skip tool connection check in dry-run or when --skip-tool-connection-check; model validation always runs)
246
246
  const skipToolConnectionCheck = argv.dryRun || argv.skipToolConnectionCheck || argv.toolConnectionCheck === false;
247
+ const { cascadePlaywrightMcpDisable, ensureSolvePlaywrightMcpReady } = await import('./playwright-mcp.lib.mjs');
248
+ await cascadePlaywrightMcpDisable(argv, log);
247
249
  if (!(await performSystemChecks(argv.minDiskSpace || 2048, skipToolConnectionCheck, argv.model, argv))) {
248
250
  await safeExit(1, 'System checks failed');
249
251
  }
252
+ // Playwright MCP preflight is local/free and stays independent from paid tool connection checks.
253
+ if (!argv.dryRun && argv.playwrightMcp !== false) {
254
+ const playwrightMcpPreflight = await ensureSolvePlaywrightMcpReady({ argv, log });
255
+ if (!playwrightMcpPreflight.ok) {
256
+ await safeExit(1, 'Playwright MCP preflight failed');
257
+ }
258
+ } else if (argv.dryRun && argv.playwrightMcp !== false) {
259
+ await log('⏊ Skipping Playwright MCP preflight (dry-run mode)', { verbose: true });
260
+ }
250
261
  // URL validation debug logging
251
262
  if (argv.verbose) {
252
263
  await log('📋 URL validation:', { verbose: true });
@@ -692,24 +703,6 @@ try {
692
703
  $,
693
704
  });
694
705
 
695
- const { cascadePlaywrightMcpDisable } = await import('./playwright-mcp.lib.mjs');
696
- await cascadePlaywrightMcpDisable(argv, log);
697
-
698
- async function resolvePlaywrightMcp(checkFn) {
699
- if (argv.playwrightMcp === false) return;
700
- if (argv.promptPlaywrightMcp) {
701
- const available = await checkFn();
702
- if (available) {
703
- await log('🎭 Playwright MCP detected - enabling browser automation hints', { verbose: true });
704
- } else {
705
- await log('â„šī¸ Playwright MCP not detected - browser automation hints will be disabled', { verbose: true });
706
- argv.promptPlaywrightMcp = false;
707
- }
708
- } else {
709
- await log('â„šī¸ Playwright MCP explicitly disabled via --no-prompt-playwright-mcp', { verbose: true });
710
- }
711
- }
712
-
713
706
  // Execute tool command with all prompts and settings
714
707
  let toolResult;
715
708
 
@@ -734,7 +727,6 @@ try {
734
727
  }
735
728
 
736
729
  await log(`\n[agent-commander] Using agent-commander for ${argv.tool || 'claude'} execution`);
737
- await agentCommanderLib.resolvePlaywrightMcpForAgentCommander({ argv, log, tool: argv.tool || 'claude' });
738
730
 
739
731
  toolResult = await agentCommanderLib.executeWithAgentCommander({
740
732
  issueUrl,
@@ -768,7 +760,6 @@ try {
768
760
  qwen: { lib: './qwen.lib.mjs', execFn: 'executeQwen', envVar: 'QWEN_PATH', defaultBin: 'qwen', pathKey: 'qwenPath' },
769
761
  }[argv.tool];
770
762
  const toolLib = await import(toolDispatch.lib);
771
- await resolvePlaywrightMcp(toolLib.checkPlaywrightMcpAvailability);
772
763
 
773
764
  toolResult = await toolLib[toolDispatch.execFn]({
774
765
  issueUrl,
@@ -796,9 +787,6 @@ try {
796
787
  });
797
788
  } else {
798
789
  // Default to Claude
799
- if (argv.tool === 'claude' || !argv.tool) {
800
- await resolvePlaywrightMcp(checkPlaywrightMcpAvailability);
801
- }
802
790
  const claudeResult = await executeClaude({
803
791
  issueUrl,
804
792
  issueNumber,
@@ -9,6 +9,7 @@
9
9
 
10
10
  import { getVersion } from './version.lib.mjs';
11
11
  import { t } from './i18n.lib.mjs';
12
+ import { hasConnectedPlaywrightMcpServer } from './playwright-mcp.lib.mjs';
12
13
  import { exec } from 'child_process';
13
14
  import { promisify } from 'util';
14
15
 
@@ -1221,8 +1222,8 @@ export function formatVersionMessage(versions, options = {}) {
1221
1222
  // Playwright MCP: show version with Claude Code and Codex connection status inline
1222
1223
  if (versions.playwrightMcp) {
1223
1224
  const mcpVersion = parseVersion('playwrightMcp', versions.playwrightMcp);
1224
- const claudeStatus = versions.playwrightMcpClaudeStatus ? vt('connected', {}, vOptions) : vt('not_connected', {}, vOptions);
1225
- const codexStatus = versions.playwrightMcpCodexStatus ? vt('connected', {}, vOptions) : vt('not_connected', {}, vOptions);
1225
+ const claudeStatus = hasConnectedPlaywrightMcpServer(versions.playwrightMcpClaudeStatus) ? vt('connected', {}, vOptions) : vt('not_connected', {}, vOptions);
1226
+ const codexStatus = hasConnectedPlaywrightMcpServer(versions.playwrightMcpCodexStatus) ? vt('connected', {}, vOptions) : vt('not_connected', {}, vOptions);
1226
1227
  browserAutoLines.push(`â€ĸ Playwright MCP: \`${mcpVersion} | Claude Code: ${claudeStatus} | Codex: ${codexStatus}\``);
1227
1228
  }
1228
1229
  addVersionLine(browserAutoLines, 'Puppeteer Browsers', versions.puppeteerBrowsers, 'puppeteerBrowsers');