@link-assistant/hive-mind 1.60.0 → 1.62.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.
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Qwen prompts module
3
+ * Handles building prompts for Qwen Code commands
4
+ */
5
+
6
+ import { getArchitectureCareSubPrompt } from './architecture-care.prompts.lib.mjs';
7
+ import { getExperimentsExamplesSubPrompt } from './experiments-examples.prompts.lib.mjs';
8
+ import { getThinkingPromptInstruction } from './thinking-prompt.lib.mjs';
9
+
10
+ /**
11
+ * Build the user prompt for Qwen Code
12
+ * @param {Object} params - Parameters for building the user prompt
13
+ * @returns {string} The formatted user prompt
14
+ */
15
+ export const buildUserPrompt = params => {
16
+ const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv } = params;
17
+
18
+ const promptLines = [];
19
+
20
+ if (isContinueMode) {
21
+ promptLines.push(`Issue to solve: ${issueNumber ? `https://github.com/${owner}/${repo}/issues/${issueNumber}` : `Issue linked to PR #${prNumber}`}`);
22
+ } else {
23
+ promptLines.push(`Issue to solve: ${issueUrl}`);
24
+ }
25
+
26
+ promptLines.push(`Your prepared branch: ${branchName}`);
27
+ promptLines.push(`Your prepared working directory: ${tempDir}`);
28
+
29
+ if (workspaceTmpDir) {
30
+ promptLines.push(`Your prepared tmp directory for logs and downloads: ${workspaceTmpDir}`);
31
+ }
32
+
33
+ if (prUrl) {
34
+ promptLines.push(`Your prepared Pull Request: ${prUrl}`);
35
+ }
36
+
37
+ if (argv && argv.fork && forkedRepo) {
38
+ promptLines.push(`Your forked repository: ${forkedRepo}`);
39
+ promptLines.push(`Original repository (upstream): ${owner}/${repo}`);
40
+
41
+ if (branchName && forkActionsUrl) {
42
+ promptLines.push(`GitHub Actions on your fork: ${forkActionsUrl}`);
43
+ }
44
+ }
45
+
46
+ promptLines.push('');
47
+
48
+ if (isContinueMode && feedbackLines && feedbackLines.length > 0) {
49
+ feedbackLines.forEach(line => promptLines.push(line));
50
+ promptLines.push('');
51
+ }
52
+
53
+ const thinkingPromptInstruction = getThinkingPromptInstruction({ tool: 'qwen', argv });
54
+ if (thinkingPromptInstruction) {
55
+ promptLines.push(thinkingPromptInstruction);
56
+ }
57
+
58
+ promptLines.push(isContinueMode ? 'Continue.' : 'Proceed.');
59
+
60
+ return promptLines.join('\n') + '\n';
61
+ };
62
+
63
+ /**
64
+ * Build the system prompt for Qwen Code
65
+ * @param {Object} params - Parameters for building the prompt
66
+ * @returns {string} The formatted system prompt
67
+ */
68
+ export const buildSystemPrompt = params => {
69
+ const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv, modelSupportsVision, forkedRepo } = params;
70
+
71
+ const screenshotRepoPath = argv?.fork && forkedRepo ? forkedRepo : `${owner}/${repo}`;
72
+
73
+ let workspaceInstructions = '';
74
+ if (workspaceTmpDir) {
75
+ workspaceInstructions = `
76
+ Workspace tmp directory.
77
+ - Use ${workspaceTmpDir} for all temporary files, logs, and downloads.
78
+ - When saving command output to files, save to ${workspaceTmpDir}/command-output.log.
79
+ - When downloading CI logs, save to ${workspaceTmpDir}/ci-logs/.
80
+ - When saving diffs for review, save to ${workspaceTmpDir}/diffs/.
81
+ - When creating debug files, save to ${workspaceTmpDir}/debug/.
82
+
83
+ `;
84
+ }
85
+
86
+ let ciExamples = '';
87
+ if (workspaceTmpDir) {
88
+ ciExamples = `
89
+ CI investigation with workspace tmp directory.
90
+ - When downloading CI run logs:
91
+ gh run view RUN_ID --repo ${owner}/${repo} --log > ${workspaceTmpDir}/ci-logs/run-RUN_ID.log
92
+ - When downloading failed job logs:
93
+ gh run view RUN_ID --repo ${owner}/${repo} --log-failed > ${workspaceTmpDir}/ci-logs/run-RUN_ID-failed.log
94
+ - When listing CI runs with details:
95
+ gh run list --repo ${owner}/${repo} --branch ${branchName} --limit 5 --json databaseId,conclusion,createdAt,headSha > ${workspaceTmpDir}/ci-logs/recent-runs.json
96
+ - When saving PR diff for review:
97
+ gh pr diff ${prNumber} --repo ${owner}/${repo} > ${workspaceTmpDir}/diffs/pr-${prNumber}.diff
98
+ - When saving command output with stderr:
99
+ npm test 2>&1 | tee ${workspaceTmpDir}/test-output.log
100
+ - When investigating issue details:
101
+ gh issue view ${issueNumber} --repo ${owner}/${repo} --json body,comments > ${workspaceTmpDir}/issue-${issueNumber}.json
102
+
103
+ `;
104
+ }
105
+
106
+ return `You are an AI issue solver using Qwen Code.
107
+
108
+ General guidelines.
109
+ - When you execute commands and the output becomes large, save the logs to files for easier review.
110
+ - When running commands, avoid setting a timeout yourself. Let them run as long as needed.
111
+ - When running sudo commands, especially package installations, run them in the background to avoid timeout issues.
112
+ - When CI is failing, download the logs locally and investigate them carefully.
113
+ - When a code or log file has more than 1500 lines, read it in chunks of 1500 lines.
114
+ - When facing a complex problem, do as much tracing as possible and turn on all verbose modes.
115
+ ${getExperimentsExamplesSubPrompt(argv)}
116
+ - When you face something extremely hard, use divide and conquer.
117
+ ${workspaceInstructions}
118
+ Initial research.
119
+ - When you start, create a detailed plan for yourself and follow your todo list step by step. Add as many relevant points from these guidelines to the todo list as practical so you can track the work clearly.
120
+ - When you read the issue, read all details and comments thoroughly.
121
+ - When you see screenshots or images in issue descriptions, pull request descriptions, comments, or discussions, download the image to a local file first, then use the Read tool to view and analyze it. Before reading downloaded images with the Read tool, verify that the file is a valid image rather than HTML by using a CLI tool such as the 'file' command. When the file command shows "HTML", "text", or "ASCII text", the download failed, so do not call Read on that file. When images are from GitHub issues or PRs, such as URLs containing "github.com/user-attachments", use: curl -L -H "Authorization: token $(gh auth token)" -o <filename> "<url>"
122
+ - When you need issue details, use gh issue view https://github.com/${owner}/${repo}/issues/${issueNumber}.
123
+ - When you need related code, use gh search code --owner ${owner} [keywords].
124
+ - When you need repo context, read files in your working directory.${
125
+ argv?.promptCheckSiblingPullRequests !== false
126
+ ? `
127
+ - When you study related work, study the most recent related pull requests.`
128
+ : ''
129
+ }
130
+ - When the issue is not defined clearly enough, write a comment with clarifying questions.
131
+ - When accessing GitHub Gists, use gh gist view command instead of direct URL fetching.
132
+ - When you are fixing a bug, find the actual root cause first and run as many experiments as needed.
133
+ - When you are fixing a bug and the code does not have enough tracing or logs, add them and keep them in the code with the default state switched off.
134
+ - When you need comments on a pull request, note that GitHub has three different comment types with different API endpoints:
135
+ 1. PR review comments (inline code comments): gh api repos/${owner}/${repo}/pulls/${prNumber}/comments --paginate
136
+ 2. PR conversation comments (general discussion): gh api repos/${owner}/${repo}/issues/${prNumber}/comments --paginate
137
+ 3. PR reviews (approve/request changes): gh api repos/${owner}/${repo}/pulls/${prNumber}/reviews --paginate
138
+ Note: The command "gh pr view --json comments" only returns conversation comments and misses review comments.
139
+ - When you need the latest comments on the issue, use gh api repos/${owner}/${repo}/issues/${issueNumber}/comments --paginate.
140
+
141
+ Solution development and testing.
142
+ - When issue is solvable, first create a test that reproduces the problem, then implement the fix.
143
+ - When implementing features, search for similar existing implementations in the codebase and use them as examples instead of implementing everything from scratch.
144
+ - When coding, commit each atomic step that is useful on its own to the pull request branch so interrupted work remains preserved in the pull request.
145
+ - When you test:
146
+ start from testing of small functions using separate scripts;
147
+ write unit tests with mocks for easy and quick start.
148
+ - When you test integrations, use existing framework.
149
+ - When you test solution draft, include automated checks in pr.
150
+ - When you write or modify tests, consider setting reasonable timeouts at test, suite, and CI job levels so failures surface quickly instead of hanging.
151
+ - When you see repeated test timeout patterns in CI, investigate the root cause rather than increasing timeouts.
152
+ - When the issue is unclear, write a comment on the issue with questions.
153
+ - When you encounter any problems that you are unable to solve yourself, write a comment to the pull request asking for help.
154
+ - When you need human help, use gh pr comment ${prNumber} --body "your message" to comment on existing PR.
155
+
156
+ Reproducible testing.
157
+ - When fixing a bug, create a test that reproduces the problem before implementing the fix. When you cannot reproduce the problem, you cannot verify the fix.
158
+ - When encountering logic bugs, write an automated test that fails due to the bug, then implement the fix to make it pass.
159
+ - When encountering UI bugs, capture a screenshot showing the problem state, then create a visual regression test or manual verification screenshot after the fix.
160
+ - When creating tests, prefer minimum reproducible examples, meaning the simplest test case that demonstrates the issue.
161
+ - When submitting a fix, include in the PR description: (1) how to reproduce the issue, (2) the automated test that verifies the fix, (3) before/after screenshots for UI issues.
162
+ - When a bug fix does not have a reproducing test, treat the fix as incomplete because regressions can occur later without notice.
163
+
164
+ Preparing pull request.
165
+ - When you code, follow contributing guidelines.
166
+ - When you commit, write clear message.
167
+ - When you need examples of style, use gh pr list --repo ${owner}/${repo} --state merged --search [keywords].
168
+ - When you open pr, describe solution draft and include tests.
169
+ - When there is a package with version and GitHub Actions workflows for automatic release, update the version in your pull request to prepare for next release.
170
+ - When you update existing pr ${prNumber}, use gh pr edit to modify title and description.
171
+ - When you finalize the pull request:
172
+ check that the pull request title and description are updated (the PR may start with a [WIP] prefix and a placeholder description that should be replaced with the actual title and description of the changes),
173
+ follow style from merged prs for code, title, and description,
174
+ check that no uncommitted changes corresponding to the original requirements are left behind,
175
+ check that the default branch is merged into the pull request branch,
176
+ check that all CI checks are passing if they exist before you finish,
177
+ double-check that all changes in the pull request address the original requirements of the issue,
178
+ check for newly introduced bugs in the pull request by carefully reading gh pr diff,
179
+ check that no previously existing features were removed without an explicit request in the issue description, issue comments, or pull request comments.
180
+ - When you finish implementation, use gh pr ready ${prNumber}.
181
+
182
+ Workflow and collaboration.
183
+ - When you check branch, verify with git branch --show-current.
184
+ - When you push, push only to branch ${branchName}.
185
+ - When you finish, create a pull request from branch ${branchName}.
186
+ - When pr ${prNumber} already exists for this branch, update it instead of creating new one.
187
+ - When you organize workflow, use pull requests instead of direct merges to default branch (main or master).
188
+ - When you manage commits, preserve commit history for later analysis.
189
+ - When you contribute, keep repository history forward-moving with regular commits, pushes, and reverts if needed.
190
+ - When you face conflict that you cannot resolve yourself, ask for help.
191
+ - When you collaborate, respect branch protections by working only on ${branchName}.
192
+ - When you mention a result, include the pull request URL or comment URL.
193
+ - When you need to create pr, remember pr ${prNumber} already exists for this branch.
194
+
195
+ Self review.
196
+ - When you check your solution draft, run all tests locally.
197
+ - When you compare with repo style, use gh pr diff [number].
198
+ - When you finalize, confirm code, tests, and description are consistent.${
199
+ argv && argv.promptEnsureAllRequirementsAreMet
200
+ ? `
201
+ - When no explicit feedback or requirements are provided, ensure all changes are correct, consistent, validated, tested, logged, and aligned with all discussed requirements by checking the issue description and all comments on the issue and pull request. Check that all CI or CD checks are passing.`
202
+ : ''
203
+ }
204
+
205
+ GitHub CLI command patterns.
206
+ - When fetching lists from GitHub API, use the --paginate flag to ensure all results are returned (GitHub returns max 30 per page by default).
207
+ - When listing PR review comments (inline code comments), use gh api repos/OWNER/REPO/pulls/NUMBER/comments --paginate.
208
+ - When listing PR conversation comments, use gh api repos/OWNER/REPO/issues/NUMBER/comments --paginate.
209
+ - When listing PR reviews, use gh api repos/OWNER/REPO/pulls/NUMBER/reviews --paginate.
210
+ - When listing issue comments, use gh api repos/OWNER/REPO/issues/NUMBER/comments --paginate.
211
+ - When adding PR comment, use gh pr comment NUMBER --body "text" --repo OWNER/REPO.
212
+ - When adding issue comment, use gh issue comment NUMBER --body "text" --repo OWNER/REPO.
213
+ - When viewing PR details, use gh pr view NUMBER --repo OWNER/REPO.
214
+ - When filtering with jq, use gh api repos/${owner}/${repo}/pulls/${prNumber}/comments --paginate --jq 'reverse | .[0:5]'.${
215
+ argv && argv.promptPlaywrightMcp
216
+ ? `
217
+
218
+ Playwright MCP usage (browser automation via MCP tools).
219
+ - 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.
220
+ - 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.
221
+ - When WebSearch tool fails or returns insufficient results, use Playwright MCP tools (browser_navigate, browser_snapshot) as a fallback for internet search.
222
+ - When you need to interact with dynamic web pages that require JavaScript execution, use Playwright MCP tools.
223
+ - When you need to visually verify how a web page looks or take screenshots, use browser_take_screenshot from Playwright MCP.
224
+ - 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).
225
+ - When you need to test responsive design or different viewport sizes, use browser_resize from Playwright MCP.
226
+ - When you finish using the browser, close it with browser_close to free resources.
227
+ - When reproducing UI bugs, use browser_take_screenshot to capture the problem state before implementing any fix.
228
+ - When fixing UI bugs, take before/after screenshots to provide visual evidence of the fix for human verification.
229
+ - When creating UI tests, save baseline screenshots to the repository for visual regression testing.
230
+ - When verifying UI fixes, compare screenshots to ensure the fix does not introduce unintended visual changes.`
231
+ : ''
232
+ }${
233
+ modelSupportsVision
234
+ ? `
235
+
236
+ Visual UI work and screenshots.
237
+ - When you work on visual UI changes (frontend, CSS, HTML, design), include a render or screenshot of the final result in the pull request description.
238
+ - When you need to show visual results, take a screenshot and save it to the repository (e.g., in a docs/screenshots/ or assets/ folder).
239
+ - When you save screenshots to the repository, use permanent links in the pull request description markdown (e.g., https://github.com/${screenshotRepoPath}/blob/${branchName}/docs/screenshots/result.png?raw=true).
240
+ - When uploading images, commit them to the branch first, then reference them using the GitHub blob URL format with ?raw=true suffix (works for both public and private repositories).
241
+ - When the visual result is important for review, mention it explicitly in the pull request description with the embedded image.
242
+ - When fixing UI bugs, capture both the "before" (problem) and "after" (fixed) screenshots as evidence for human verification of the fix.
243
+ - When reporting UI bugs, include a screenshot of the problem state to enable visual verification of the fix.
244
+ - When the fix is visual, include side-by-side or sequential comparison of before/after states in the PR description.
245
+ - When possible, create automated visual regression tests to prevent the UI bug from recurring.`
246
+ : ''
247
+ }${ciExamples}${getArchitectureCareSubPrompt(argv)}`;
248
+ };
249
+
250
+ export default {
251
+ buildUserPrompt,
252
+ buildSystemPrompt,
253
+ };
@@ -327,7 +327,7 @@ export const SOLVE_OPTION_DEFINITIONS = {
327
327
  tool: {
328
328
  type: 'string',
329
329
  description: 'AI tool to use for solving issues',
330
- choices: ['claude', 'opencode', 'codex', 'agent'],
330
+ choices: ['claude', 'opencode', 'codex', 'agent', 'qwen'],
331
331
  default: 'claude',
332
332
  },
333
333
  plan: {
@@ -352,7 +352,7 @@ export const SOLVE_OPTION_DEFINITIONS = {
352
352
  },
353
353
  'enable-workspaces': {
354
354
  type: 'boolean',
355
- description: 'Use separate workspace directory structure with repository/ and tmp/ folders. Works with all tools (claude, opencode, codex, agent). Experimental feature.',
355
+ description: 'Use separate workspace directory structure with repository/ and tmp/ folders. Works with all tools (claude, opencode, codex, agent, qwen). Experimental feature.',
356
356
  default: false,
357
357
  },
358
358
  'interactive-mode': {
@@ -428,7 +428,7 @@ export const SOLVE_OPTION_DEFINITIONS = {
428
428
  },
429
429
  'prompt-playwright-mcp': {
430
430
  type: 'boolean',
431
- 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.',
431
+ 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, --tool agent, and --tool qwen.',
432
432
  default: true,
433
433
  },
434
434
  'prompt-check-sibling-pull-requests': {
@@ -448,7 +448,7 @@ export const SOLVE_OPTION_DEFINITIONS = {
448
448
  },
449
449
  'playwright-mcp': {
450
450
  type: 'boolean',
451
- 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.',
451
+ 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, --tool agent, and --tool qwen.',
452
452
  default: true,
453
453
  },
454
454
  'playwright-mcp-auto-cleanup': {
@@ -468,7 +468,7 @@ export const SOLVE_OPTION_DEFINITIONS = {
468
468
  },
469
469
  'prompt-subagents-via-agent-commander': {
470
470
  type: 'boolean',
471
- description: 'Guide AI to use agent-commander CLI (start-agent) instead of native tool-specific delegation for subagent work. Allows using any supported agent type (claude, opencode, codex, agent) with a unified API. Supported for --tool claude and --tool codex and requires agent-commander to be installed.',
471
+ description: 'Guide AI to use agent-commander CLI (start-agent) instead of native tool-specific delegation for subagent work. Allows using any supported agent type (claude, opencode, codex, agent, qwen) with a unified API. Supported for --tool claude and --tool codex and requires agent-commander to be installed.',
472
472
  default: false,
473
473
  },
474
474
  'auto-init-repository': {
package/src/solve.mjs CHANGED
@@ -99,6 +99,9 @@ if (argv.tool === 'opencode') {
99
99
  } else if (argv.tool === 'agent') {
100
100
  const agentLib = await import('./agent.lib.mjs');
101
101
  checkForUncommittedChanges = agentLib.checkForUncommittedChanges;
102
+ } else if (argv.tool === 'qwen') {
103
+ const qwenLib = await import('./qwen.lib.mjs');
104
+ checkForUncommittedChanges = qwenLib.checkForUncommittedChanges;
102
105
  } else {
103
106
  checkForUncommittedChanges = claudeLib.checkForUncommittedChanges;
104
107
  }
@@ -751,6 +754,36 @@ try {
751
754
  agentPath,
752
755
  $,
753
756
  });
757
+ } else if (argv.tool === 'qwen') {
758
+ const qwenLib = await import('./qwen.lib.mjs');
759
+ const { executeQwen, checkPlaywrightMcpAvailability: checkQwenPlaywrightMcp } = qwenLib;
760
+ const qwenPath = process.env.QWEN_PATH || 'qwen';
761
+ await resolvePlaywrightMcp(checkQwenPlaywrightMcp);
762
+
763
+ toolResult = await executeQwen({
764
+ issueUrl,
765
+ issueNumber,
766
+ prNumber,
767
+ prUrl,
768
+ branchName,
769
+ tempDir,
770
+ workspaceTmpDir,
771
+ isContinueMode,
772
+ mergeStateStatus,
773
+ forkedRepo,
774
+ feedbackLines,
775
+ forkActionsUrl,
776
+ owner,
777
+ repo,
778
+ argv,
779
+ log,
780
+ setLogFile,
781
+ getLogFile,
782
+ formatAligned,
783
+ getResourceSnapshot,
784
+ qwenPath,
785
+ $,
786
+ });
754
787
  } else {
755
788
  // Default to Claude
756
789
  if (argv.tool === 'claude' || !argv.tool) {
@@ -1150,6 +1183,7 @@ try {
1150
1183
  prNumber,
1151
1184
  branchName,
1152
1185
  tempDir,
1186
+ workspaceTmpDir,
1153
1187
  mergeStateStatus,
1154
1188
  feedbackLines: hintLines,
1155
1189
  argv: {
@@ -167,13 +167,13 @@ export const getUncommittedChangesDetails = async tempDir => {
167
167
  };
168
168
 
169
169
  /**
170
- * Execute the AI tool (Claude, OpenCode, Codex, Agent) for a restart iteration
170
+ * Execute the AI tool (Claude, OpenCode, Codex, Agent, Qwen) for a restart iteration
171
171
  * This is the shared tool execution logic used by both watch mode and auto-restart-until-mergeable mode
172
172
  * @param {Object} params - Execution parameters
173
173
  * @returns {Promise<Object>} - Tool execution result
174
174
  */
175
175
  export const executeToolIteration = async params => {
176
- const { issueUrl, owner, repo, issueNumber, prNumber, branchName, tempDir, mergeStateStatus, feedbackLines, argv } = params;
176
+ const { issueUrl, owner, repo, issueNumber, prNumber, branchName, tempDir, workspaceTmpDir, mergeStateStatus, feedbackLines, argv } = params;
177
177
 
178
178
  // Import necessary modules for tool execution
179
179
  const memoryCheck = await import('./memory-check.mjs');
@@ -301,6 +301,48 @@ export const executeToolIteration = async params => {
301
301
  agentPath,
302
302
  $,
303
303
  });
304
+ } else if (argv.tool === 'qwen') {
305
+ // Use Qwen Code
306
+ const qwenExecLib = await import('./qwen.lib.mjs');
307
+ const { executeQwen, checkPlaywrightMcpAvailability } = qwenExecLib;
308
+ const qwenPath = argv.qwenPath || 'qwen';
309
+
310
+ if (argv.promptPlaywrightMcp) {
311
+ const playwrightMcpAvailable = await checkPlaywrightMcpAvailability();
312
+ if (playwrightMcpAvailable) {
313
+ await log('🎭 Playwright MCP detected - enabling browser automation hints', { verbose: true });
314
+ } else {
315
+ await log('ℹ️ Playwright MCP not detected - browser automation hints will be disabled', { verbose: true });
316
+ argv.promptPlaywrightMcp = false;
317
+ }
318
+ } else {
319
+ await log('ℹ️ Playwright MCP explicitly disabled via --no-prompt-playwright-mcp', { verbose: true });
320
+ }
321
+
322
+ toolResult = await executeQwen({
323
+ issueUrl,
324
+ issueNumber,
325
+ prNumber,
326
+ prUrl: `https://github.com/${owner}/${repo}/pull/${prNumber}`,
327
+ branchName,
328
+ tempDir,
329
+ workspaceTmpDir,
330
+ isContinueMode: true,
331
+ mergeStateStatus,
332
+ forkedRepo: argv.fork,
333
+ feedbackLines,
334
+ forkActionsUrl: null,
335
+ owner,
336
+ repo,
337
+ argv,
338
+ log,
339
+ setLogFile: () => {},
340
+ getLogFile: () => '',
341
+ formatAligned,
342
+ getResourceSnapshot,
343
+ qwenPath,
344
+ $,
345
+ });
304
346
  } else {
305
347
  // Use Claude (default)
306
348
  const claudeExecLib = await import('./claude.lib.mjs');
@@ -319,6 +319,14 @@ export const performSystemChecks = async (minDiskSpace = 2048, skipToolConnectio
319
319
  await log('❌ Cannot proceed without Agent connection', { level: 'error' });
320
320
  return false;
321
321
  }
322
+ } else if (argv.tool === 'qwen') {
323
+ // Validate Qwen Code connection
324
+ const qwenLib = await import('./qwen.lib.mjs');
325
+ isToolConnected = await qwenLib.validateQwenConnection(model);
326
+ if (!isToolConnected) {
327
+ await log('❌ Cannot proceed without Qwen Code connection', { level: 'error' });
328
+ return false;
329
+ }
322
330
  } else {
323
331
  // Validate Claude CLI connection (default)
324
332
  const isClaudeConnected = await validateClaudeConnection(model);
@@ -0,0 +1,203 @@
1
+ import os from 'os';
2
+ import path from 'path';
3
+ import { spawn } from 'child_process';
4
+ import { promises as fs } from 'fs';
5
+ import { parseGitHubUrl } from './github.lib.mjs';
6
+
7
+ export const TASK_ISSUE_TITLE_MAX_LENGTH = 256;
8
+
9
+ function normalizeNewlines(value) {
10
+ return String(value || '').replace(/\r\n?/g, '\n');
11
+ }
12
+
13
+ function cleanRepositoryCandidate(value) {
14
+ return String(value || '')
15
+ .trim()
16
+ .replace(/^[<([{]+/, '')
17
+ .replace(/[>\])}.,;:]+$/, '');
18
+ }
19
+
20
+ export function stripTaskCommandPrefix(text) {
21
+ const value = normalizeNewlines(text).trimStart();
22
+ return value.replace(/^\/(?:task|split)(?:@\S+)?(?:[ \t]+|\n|$)/i, '').trim();
23
+ }
24
+
25
+ export function resolveTaskIssueCreationInput({ commandText = '', replyText = '' } = {}) {
26
+ const inlineText = stripTaskCommandPrefix(commandText);
27
+ if (inlineText) return inlineText;
28
+ return normalizeNewlines(replyText).trim();
29
+ }
30
+
31
+ export function parseTaskRepository(value) {
32
+ const candidate = cleanRepositoryCandidate(value);
33
+ const parsed = parseGitHubUrl(candidate);
34
+ if (!parsed.valid || parsed.type !== 'repo') return null;
35
+ return {
36
+ owner: parsed.owner,
37
+ repo: parsed.repo,
38
+ fullName: `${parsed.owner}/${parsed.repo}`,
39
+ url: `https://github.com/${parsed.owner}/${parsed.repo}`,
40
+ };
41
+ }
42
+
43
+ function parseRepositoryDirective(line) {
44
+ const trimmed = line.trim();
45
+ if (!trimmed.startsWith('--repository')) return { matched: false };
46
+
47
+ const match = trimmed.match(/^--repository(?:=(\S+)|\s+(\S+))$/);
48
+ if (!match) {
49
+ return {
50
+ matched: true,
51
+ error: 'Invalid --repository syntax. Use --repository <github-repository-url>.',
52
+ };
53
+ }
54
+
55
+ const repository = parseTaskRepository(match[1] || match[2]);
56
+ if (!repository) {
57
+ return {
58
+ matched: true,
59
+ error: '--repository must point to a GitHub repository URL.',
60
+ };
61
+ }
62
+
63
+ return { matched: true, repository };
64
+ }
65
+
66
+ function parseRepositoryLine(line) {
67
+ const trimmed = line.trim();
68
+ if (!trimmed || /\s/.test(trimmed)) return null;
69
+ return parseTaskRepository(trimmed);
70
+ }
71
+
72
+ function setRepository(currentRepository, nextRepository) {
73
+ if (!nextRepository) return { repository: currentRepository };
74
+ if (currentRepository) {
75
+ return {
76
+ repository: currentRepository,
77
+ error: 'Only one GitHub repository may be provided.',
78
+ };
79
+ }
80
+ return { repository: nextRepository };
81
+ }
82
+
83
+ export function buildTaskIssueTitle(issueText, maxLength = TASK_ISSUE_TITLE_MAX_LENGTH) {
84
+ const firstLine = normalizeNewlines(issueText).trim().split('\n')[0]?.trim() || 'New task';
85
+ if (firstLine.length <= maxLength) return firstLine;
86
+ return `${firstLine.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
87
+ }
88
+
89
+ export function parseTaskIssueCreationInput(input) {
90
+ const normalized = normalizeNewlines(input).trim();
91
+ if (!normalized) {
92
+ return { valid: false, error: 'Missing repository and issue text.' };
93
+ }
94
+
95
+ const lines = normalized.split('\n');
96
+ let repository = null;
97
+ let bodyLines = [];
98
+
99
+ for (const line of lines) {
100
+ const directive = parseRepositoryDirective(line);
101
+ if (!directive.matched) {
102
+ bodyLines.push(line);
103
+ continue;
104
+ }
105
+ if (directive.error) return { valid: false, error: directive.error };
106
+ const next = setRepository(repository, directive.repository);
107
+ if (next.error) return { valid: false, error: next.error };
108
+ repository = next.repository;
109
+ }
110
+
111
+ if (!repository) {
112
+ bodyLines = [];
113
+ for (const line of lines) {
114
+ const lineRepository = parseRepositoryLine(line);
115
+ if (!lineRepository) {
116
+ bodyLines.push(line);
117
+ continue;
118
+ }
119
+ const next = setRepository(repository, lineRepository);
120
+ if (next.error) return { valid: false, error: next.error };
121
+ repository = next.repository;
122
+ }
123
+ }
124
+
125
+ if (!repository) {
126
+ return {
127
+ valid: false,
128
+ error: 'Missing GitHub repository URL. Provide it on its own line or with --repository <github-repository-url>.',
129
+ };
130
+ }
131
+
132
+ const issueText = bodyLines.join('\n').trim();
133
+ if (!issueText) {
134
+ return { valid: false, error: 'Missing issue text.' };
135
+ }
136
+
137
+ return {
138
+ valid: true,
139
+ repository,
140
+ issueText,
141
+ title: buildTaskIssueTitle(issueText),
142
+ };
143
+ }
144
+
145
+ function runCommand(command, args, options = {}) {
146
+ return new Promise(resolve => {
147
+ const child = spawn(command, args, {
148
+ stdio: ['ignore', 'pipe', 'pipe'],
149
+ env: process.env,
150
+ ...options,
151
+ });
152
+
153
+ let stdout = '';
154
+ let stderr = '';
155
+ child.stdout.on('data', data => {
156
+ stdout += data.toString();
157
+ });
158
+ child.stderr.on('data', data => {
159
+ stderr += data.toString();
160
+ });
161
+ child.on('error', error => {
162
+ resolve({ code: 1, stdout, stderr: stderr || error.message });
163
+ });
164
+ child.on('close', code => {
165
+ resolve({ code, stdout, stderr });
166
+ });
167
+ });
168
+ }
169
+
170
+ export function parseCreatedTaskIssueOutput(output) {
171
+ const tokens = String(output || '')
172
+ .split(/\s+/)
173
+ .filter(Boolean);
174
+ for (const token of tokens) {
175
+ const parsed = parseGitHubUrl(cleanRepositoryCandidate(token));
176
+ if (parsed.valid && parsed.type === 'issue') {
177
+ return {
178
+ owner: parsed.owner,
179
+ repo: parsed.repo,
180
+ number: parsed.number,
181
+ url: parsed.normalized,
182
+ };
183
+ }
184
+ }
185
+ throw new Error(`Could not parse created issue URL from gh output: ${String(output || '').trim()}`);
186
+ }
187
+
188
+ export async function createTaskIssue({ repository, title, body, run = runCommand }) {
189
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'hive-mind-task-issue-'));
190
+ const bodyFile = path.join(tempDir, 'body.md');
191
+
192
+ try {
193
+ await fs.writeFile(bodyFile, body);
194
+ const result = await run('gh', ['issue', 'create', '--repo', repository.fullName, '--title', title, '--body-file', bodyFile]);
195
+ if (result.code !== 0) {
196
+ const output = `${result.stderr || ''}${result.stdout || ''}`.trim();
197
+ throw new Error(output || `gh issue create exited with code ${result.code}`);
198
+ }
199
+ return parseCreatedTaskIssueOutput(result.stdout);
200
+ } finally {
201
+ await fs.rm(tempDir, { recursive: true, force: true }).catch(() => {});
202
+ }
203
+ }
package/src/task.mjs CHANGED
@@ -33,7 +33,7 @@ if (earlyArgs.length === 0 || earlyArgs.includes('--help') || earlyArgs.includes
33
33
  console.log(' --only-decompose Only run decomposition mode');
34
34
  console.log(' --split Split a GitHub issue into smaller issues');
35
35
  console.log(' --split-count Number of issues to split into [default: 2]');
36
- console.log(' --tool AI tool for agent-commander read-only mode (claude, codex, opencode, agent) [default: claude]');
36
+ console.log(' --tool AI tool for agent-commander read-only mode (claude, codex, opencode, agent, qwen) [default: claude]');
37
37
  console.log(' --model, -m Model to use');
38
38
  console.log(' --isolation agent-commander isolation mode [default: screen]');
39
39
  console.log(' --dry-run Print split output without creating GitHub issues');