@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 +12 -0
- package/package.json +1 -1
- package/src/claude.lib.mjs +2 -12
- package/src/codex.lib.mjs +2 -11
- package/src/github.lib.mjs +1 -1
- package/src/interactive-mcp-status.lib.mjs +44 -0
- package/src/interactive-mode.lib.mjs +5 -5
- package/src/log-upload.lib.mjs +66 -20
- package/src/playwright-mcp.lib.mjs +92 -0
- package/src/solve.mjs +12 -24
- package/src/version-info.lib.mjs +3 -2
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
package/src/claude.lib.mjs
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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 => {
|
package/src/github.lib.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
package/src/log-upload.lib.mjs
CHANGED
|
@@ -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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
149
|
+
const commandArgs = buildGhUploadLogArgs({
|
|
150
|
+
logFile,
|
|
151
|
+
isPublic,
|
|
152
|
+
description,
|
|
153
|
+
verbose,
|
|
154
|
+
});
|
|
98
155
|
|
|
99
156
|
if (verbose) {
|
|
100
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
package/src/version-info.lib.mjs
CHANGED
|
@@ -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');
|