@link-assistant/hive-mind 1.50.7 → 1.50.9
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 +17 -0
- package/README.md +6 -0
- package/package.json +1 -1
- package/src/agent.prompts.lib.mjs +25 -37
- package/src/architecture-care.prompts.lib.mjs +11 -11
- package/src/claude.prompts.lib.mjs +31 -46
- package/src/codex.lib.mjs +481 -100
- package/src/codex.options.lib.mjs +52 -0
- package/src/codex.prompts.lib.mjs +84 -39
- package/src/experiments-examples.prompts.lib.mjs +7 -7
- package/src/github-merge-repo-actions.lib.mjs +66 -2
- package/src/github-merge.lib.mjs +3 -3
- package/src/hive.bootstrap.lib.mjs +32 -0
- package/src/hive.config.lib.mjs +3 -3
- package/src/hive.mjs +13 -20
- package/src/interactive-mode.lib.mjs +200 -265
- package/src/interactive-mode.shared.lib.mjs +133 -0
- package/src/limits.lib.mjs +339 -2
- package/src/models/index.mjs +21 -12
- package/src/opencode.prompts.lib.mjs +26 -38
- package/src/queue-config.lib.mjs +6 -0
- package/src/solve.auto-continue.lib.mjs +1 -0
- package/src/solve.auto-merge.lib.mjs +8 -4
- package/src/solve.bootstrap.lib.mjs +39 -0
- package/src/solve.config.lib.mjs +18 -13
- package/src/solve.mjs +35 -40
- package/src/solve.progress-monitoring.lib.mjs +10 -2
- package/src/solve.restart-shared.lib.mjs +13 -1
- package/src/solve.results.lib.mjs +43 -5
- package/src/solve.validation.lib.mjs +1 -1
- package/src/telegram-bot.mjs +4 -2
- package/src/telegram-solve-queue.helpers.lib.mjs +151 -0
- package/src/telegram-solve-queue.lib.mjs +82 -181
- package/src/version-info.lib.mjs +8 -5
|
@@ -69,8 +69,9 @@ export const watchUntilMergeable = async params => {
|
|
|
69
69
|
const MIN_CI_CHECK_INTERVAL_SECONDS = 120;
|
|
70
70
|
const watchInterval = Math.max(rawWatchInterval, MIN_CI_CHECK_INTERVAL_SECONDS);
|
|
71
71
|
const isAutoMerge = argv.autoMerge || false;
|
|
72
|
-
// Issue #1503: --wait-for-all-actions-in-repository-before-
|
|
73
|
-
|
|
72
|
+
// Issue #1503/#1573: --wait-for-all-actions-in-repository-before-mergeable
|
|
73
|
+
// When enabled (default: true), blocks merge if ANY CI/CD run in the repo is active — ensures safety when pipelines interact.
|
|
74
|
+
const waitForAllRepoActionsFlag = argv.waitForAllActionsInRepositoryBeforeMergeable ?? argv['wait-for-all-actions-in-repository-before-mergeable'] ?? argv.waitForAllActionsInRepositoryBeforeMergable ?? argv['wait-for-all-actions-in-repository-before-mergable'] ?? false;
|
|
74
75
|
|
|
75
76
|
// Track latest session data across all iterations for accurate pricing
|
|
76
77
|
let latestSessionId = null;
|
|
@@ -219,7 +220,9 @@ export const watchUntilMergeable = async params => {
|
|
|
219
220
|
|
|
220
221
|
if (!consensus.allAgree) {
|
|
221
222
|
const m = consensus.mechanisms;
|
|
222
|
-
|
|
223
|
+
const repoLabel = m.repoActions.skipped ? 'skipped' : `${m.repoActions.count} active`;
|
|
224
|
+
const commitsLabel = m.allCommitsCI.skipped ? 'skipped' : `${m.allCommitsCI.pendingCommits.length} pending of ${m.allCommitsCI.totalCommits}`;
|
|
225
|
+
await log(formatAligned('🔄', 'CI mechanisms DISAGREE:', `CheckRuns=${m.checkRunsAPI.status}, WorkflowRuns=${m.workflowRunsAPI.inProgress} in-progress, AllCommits=${commitsLabel}, RepoActions=${repoLabel}`, 2));
|
|
223
226
|
await log(formatAligned('⏳', 'Continuing to monitor...', 'Mechanisms must agree before declaring mergeable', 2));
|
|
224
227
|
consecutiveNoRunsChecks = 0;
|
|
225
228
|
lastCheckTime = currentTime;
|
|
@@ -229,7 +232,8 @@ export const watchUntilMergeable = async params => {
|
|
|
229
232
|
await interruptibleSleep(actualWaitSeconds * 1000);
|
|
230
233
|
continue;
|
|
231
234
|
}
|
|
232
|
-
|
|
235
|
+
const acLabel = consensus.mechanisms.allCommitsCI.skipped ? '' : `, AllCommits=complete(${consensus.mechanisms.allCommitsCI.totalCommits})`;
|
|
236
|
+
await log(formatAligned('✅', 'All CI mechanisms agree:', `CheckRuns=${consensus.mechanisms.checkRunsAPI.status}, WorkflowRuns=complete(${consensus.mechanisms.workflowRunsAPI.total})${acLabel}, RepoActions=${consensus.mechanisms.repoActions.skipped ? 'skipped' : 'clear'}`, 2));
|
|
233
237
|
} else if (waitForAllRepoActionsFlag) {
|
|
234
238
|
// Even with no CI configured, check repo-wide actions for absolute safety
|
|
235
239
|
const repoRuns = await getAllActiveRepoRuns(owner, repo, argv.verbose);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Handle lightweight early-exit paths before solve loads its full dependency graph.
|
|
5
|
+
*
|
|
6
|
+
* @param {string[]} earlyArgs - Raw CLI args without the node/script prefix
|
|
7
|
+
* @returns {Promise<void>}
|
|
8
|
+
*/
|
|
9
|
+
export async function handleSolveEarlyExit(earlyArgs) {
|
|
10
|
+
if (earlyArgs.includes('--version')) {
|
|
11
|
+
const { getVersion } = await import('./version.lib.mjs');
|
|
12
|
+
try {
|
|
13
|
+
console.log(await getVersion());
|
|
14
|
+
} catch {
|
|
15
|
+
console.error('Error: Unable to determine version');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (earlyArgs.includes('--help') || earlyArgs.includes('-h')) {
|
|
22
|
+
// Load minimal modules needed for help output.
|
|
23
|
+
const { use } = eval(await (await fetch('https://unpkg.com/use-m/use.js')).text());
|
|
24
|
+
globalThis.use = use;
|
|
25
|
+
const { initializeConfig, createYargsConfig } = await import('./solve.config.lib.mjs');
|
|
26
|
+
const { yargs, hideBin } = await initializeConfig(use);
|
|
27
|
+
const rawArgs = hideBin(process.argv);
|
|
28
|
+
const argsWithoutHelp = rawArgs.filter(arg => arg !== '--help' && arg !== '-h');
|
|
29
|
+
createYargsConfig(yargs(argsWithoutHelp)).showHelp();
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (earlyArgs.length === 0) {
|
|
34
|
+
console.error('Usage: solve.mjs <issue-url> [options]');
|
|
35
|
+
console.error('\nError: Missing required github issue or pull request URL');
|
|
36
|
+
console.error('\nRun "solve.mjs --help" for more information');
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/solve.config.lib.mjs
CHANGED
|
@@ -186,11 +186,16 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
186
186
|
description: 'Auto-restart until PR becomes mergeable (no iteration limit). Restarts on new comments from non-bot users, CI failures, merge conflicts, or other issues. Does NOT auto-merge.',
|
|
187
187
|
default: true,
|
|
188
188
|
},
|
|
189
|
-
'wait-for-all-actions-in-repository-before-
|
|
189
|
+
'wait-for-all-actions-in-repository-before-mergeable': {
|
|
190
190
|
type: 'boolean',
|
|
191
|
-
description: 'Wait for ALL active GitHub Actions workflow runs in the entire repository to complete before declaring PR mergeable.
|
|
191
|
+
description: 'Wait for ALL active GitHub Actions workflow runs in the entire repository to complete before declaring PR mergeable. When enabled, blocks merge if ANY CI/CD run in the repository is active, regardless of branch — this ensures safety when CI/CD pipelines interact or depend on each other. Enabled by default.',
|
|
192
192
|
default: true,
|
|
193
193
|
},
|
|
194
|
+
'wait-for-all-actions-in-repository-before-mergable': {
|
|
195
|
+
type: 'boolean',
|
|
196
|
+
description: 'Deprecated alias for --wait-for-all-actions-in-repository-before-mergeable (fixes typo).',
|
|
197
|
+
hidden: true,
|
|
198
|
+
},
|
|
194
199
|
'auto-restart-on-non-updated-pull-request-description': {
|
|
195
200
|
type: 'boolean',
|
|
196
201
|
description: 'Automatically restart if PR title or description still contains auto-generated placeholder text after agent execution. Restarts with a hint about what was not updated.',
|
|
@@ -224,13 +229,13 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
224
229
|
},
|
|
225
230
|
think: {
|
|
226
231
|
type: 'string',
|
|
227
|
-
description: 'Thinking level
|
|
232
|
+
description: 'Thinking level hint. For Claude, translated to --thinking-budget for Claude Code >= 2.1.12 (off=0, low=~8000, medium=~16000, high=~24000, max=31999). For Codex, mapped to reasoning effort (off=none, low=low, medium=medium, high=high, max=xhigh).',
|
|
228
233
|
choices: ['off', 'low', 'medium', 'high', 'max'],
|
|
229
234
|
default: undefined,
|
|
230
235
|
},
|
|
231
236
|
'thinking-budget': {
|
|
232
237
|
type: 'number',
|
|
233
|
-
description: 'Thinking token budget
|
|
238
|
+
description: 'Thinking token budget. For Claude Code, controls MAX_THINKING_TOKENS (0-31999 by default). For Codex, enables finer reasoning-effort mapping including minimal/low/medium/high/xhigh.',
|
|
234
239
|
default: undefined,
|
|
235
240
|
},
|
|
236
241
|
'thinking-budget-claude-minimum-version': {
|
|
@@ -245,7 +250,7 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
245
250
|
},
|
|
246
251
|
'prompt-plan-sub-agent': {
|
|
247
252
|
type: 'boolean',
|
|
248
|
-
description: 'Encourage AI to use
|
|
253
|
+
description: 'Encourage AI to use a planning sub-agent or planning workflow for initial planning. Supported for --tool claude and --tool codex.',
|
|
249
254
|
default: false,
|
|
250
255
|
},
|
|
251
256
|
'base-branch': {
|
|
@@ -321,27 +326,27 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
321
326
|
},
|
|
322
327
|
'interactive-mode': {
|
|
323
328
|
type: 'boolean',
|
|
324
|
-
description: '[EXPERIMENTAL] Post
|
|
329
|
+
description: '[EXPERIMENTAL] Post tool output as PR comments in real-time. Supported for --tool claude and --tool codex.',
|
|
325
330
|
default: false,
|
|
326
331
|
},
|
|
327
332
|
'prompt-explore-sub-agent': {
|
|
328
333
|
type: 'boolean',
|
|
329
|
-
description: 'Encourage
|
|
334
|
+
description: 'Encourage AI to use Explore-style sub-agent workflow for codebase exploration. Supported for --tool claude and --tool codex.',
|
|
330
335
|
default: false,
|
|
331
336
|
},
|
|
332
337
|
'prompt-general-purpose-sub-agent': {
|
|
333
338
|
type: 'boolean',
|
|
334
|
-
description: 'Prompt AI to use general-purpose sub agents for processing large tasks with multiple files/folders.
|
|
339
|
+
description: 'Prompt AI to use general-purpose sub agents for processing large tasks with multiple files/folders. Supported for --tool claude and --tool codex.',
|
|
335
340
|
default: false,
|
|
336
341
|
},
|
|
337
342
|
'tokens-budget-stats': {
|
|
338
343
|
type: 'boolean',
|
|
339
|
-
description: '[EXPERIMENTAL] Show detailed token budget statistics including context window usage and ratios.
|
|
344
|
+
description: '[EXPERIMENTAL] Show detailed token budget statistics including context window usage and ratios. Supported for --tool claude, --tool codex, and any tool that returns detailed token usage.',
|
|
340
345
|
default: false,
|
|
341
346
|
},
|
|
342
347
|
'prompt-issue-reporting': {
|
|
343
348
|
type: 'boolean',
|
|
344
|
-
description: 'Enable automatic issue creation for spotted bugs/errors not related to main task. Issues will include reproducible examples, workarounds, and fix suggestions. Works for both current and third-party repositories.
|
|
349
|
+
description: 'Enable automatic issue creation for spotted bugs/errors not related to main task. Issues will include reproducible examples, workarounds, and fix suggestions. Works for both current and third-party repositories. Supported for --tool claude and --tool codex.',
|
|
345
350
|
default: false,
|
|
346
351
|
},
|
|
347
352
|
'prompt-architecture-care': {
|
|
@@ -351,12 +356,12 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
351
356
|
},
|
|
352
357
|
'prompt-case-studies': {
|
|
353
358
|
type: 'boolean',
|
|
354
|
-
description: 'Create comprehensive case study documentation for the issue including logs, analysis, timeline, root cause investigation, and proposed solutions. Organizes findings into ./docs/case-studies/issue-{id}/ directory.
|
|
359
|
+
description: 'Create comprehensive case study documentation for the issue including logs, analysis, timeline, root cause investigation, and proposed solutions. Organizes findings into ./docs/case-studies/issue-{id}/ directory. Supported for --tool claude and --tool codex.',
|
|
355
360
|
default: false,
|
|
356
361
|
},
|
|
357
362
|
'prompt-playwright-mcp': {
|
|
358
363
|
type: 'boolean',
|
|
359
|
-
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.
|
|
364
|
+
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 codex.',
|
|
360
365
|
default: true,
|
|
361
366
|
},
|
|
362
367
|
'prompt-check-sibling-pull-requests': {
|
|
@@ -386,7 +391,7 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
386
391
|
},
|
|
387
392
|
'prompt-subagents-via-agent-commander': {
|
|
388
393
|
type: 'boolean',
|
|
389
|
-
description: 'Guide
|
|
394
|
+
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.',
|
|
390
395
|
default: false,
|
|
391
396
|
},
|
|
392
397
|
'auto-init-repository': {
|
package/src/solve.mjs
CHANGED
|
@@ -1,39 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Import Sentry instrumentation first (must be before other imports)
|
|
3
3
|
import './instrument.mjs';
|
|
4
|
-
// Early exit paths - handle these before loading all modules to speed up testing
|
|
5
4
|
const earlyArgs = process.argv.slice(2);
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const version = await getVersion();
|
|
10
|
-
console.log(version);
|
|
11
|
-
} catch {
|
|
12
|
-
console.error('Error: Unable to determine version');
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}
|
|
15
|
-
process.exit(0);
|
|
16
|
-
}
|
|
17
|
-
if (earlyArgs.includes('--help') || earlyArgs.includes('-h')) {
|
|
18
|
-
// Load minimal modules needed for help
|
|
19
|
-
const { use } = eval(await (await fetch('https://unpkg.com/use-m/use.js')).text());
|
|
20
|
-
globalThis.use = use;
|
|
21
|
-
const config = await import('./solve.config.lib.mjs');
|
|
22
|
-
const { initializeConfig, createYargsConfig } = config;
|
|
23
|
-
const { yargs, hideBin } = await initializeConfig(use);
|
|
24
|
-
const rawArgs = hideBin(process.argv);
|
|
25
|
-
// Filter out help flags to avoid duplicate display
|
|
26
|
-
const argsWithoutHelp = rawArgs.filter(arg => arg !== '--help' && arg !== '-h');
|
|
27
|
-
createYargsConfig(yargs(argsWithoutHelp)).showHelp();
|
|
28
|
-
process.exit(0);
|
|
29
|
-
}
|
|
30
|
-
if (earlyArgs.length === 0) {
|
|
31
|
-
console.error('Usage: solve.mjs <issue-url> [options]');
|
|
32
|
-
console.error('\nError: Missing required github issue or pull request URL');
|
|
33
|
-
console.error('\nRun "solve.mjs --help" for more information');
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
// Now load all modules for normal operation
|
|
5
|
+
const { handleSolveEarlyExit } = await import('./solve.bootstrap.lib.mjs');
|
|
6
|
+
await handleSolveEarlyExit(earlyArgs);
|
|
7
|
+
|
|
37
8
|
const { use } = eval(await (await fetch('https://unpkg.com/use-m/use.js')).text());
|
|
38
9
|
globalThis.use = use;
|
|
39
10
|
const { $ } = await use('command-stream');
|
|
@@ -58,9 +29,9 @@ const { processAutoContinueForIssue } = autoContinue;
|
|
|
58
29
|
const repository = await import('./solve.repository.lib.mjs');
|
|
59
30
|
const { setupTempDirectory, cleanupTempDirectory } = repository;
|
|
60
31
|
const results = await import('./solve.results.lib.mjs');
|
|
61
|
-
const { cleanupClaudeFile, showSessionSummary, verifyResults, buildClaudeResumeCommand, checkForAiCreatedComments, attachSolutionSummary } = results;
|
|
32
|
+
const { cleanupClaudeFile, showSessionSummary, verifyResults, buildClaudeResumeCommand, buildSolveResumeCommand, checkForAiCreatedComments, attachSolutionSummary } = results;
|
|
62
33
|
const claudeLib = await import('./claude.lib.mjs');
|
|
63
|
-
const { executeClaude } = claudeLib;
|
|
34
|
+
const { executeClaude, checkPlaywrightMcpAvailability } = claudeLib;
|
|
64
35
|
|
|
65
36
|
const githubLinking = await import('./github-linking.lib.mjs');
|
|
66
37
|
const { extractLinkedIssueNumber } = githubLinking;
|
|
@@ -769,9 +740,21 @@ try {
|
|
|
769
740
|
});
|
|
770
741
|
} else if (argv.tool === 'codex') {
|
|
771
742
|
const codexLib = await import('./codex.lib.mjs');
|
|
772
|
-
const { executeCodex } = codexLib;
|
|
743
|
+
const { executeCodex, checkPlaywrightMcpAvailability } = codexLib;
|
|
773
744
|
const codexPath = process.env.CODEX_PATH || 'codex';
|
|
774
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
|
+
}
|
|
757
|
+
|
|
775
758
|
toolResult = await executeCodex({
|
|
776
759
|
issueUrl,
|
|
777
760
|
issueNumber,
|
|
@@ -831,7 +814,6 @@ try {
|
|
|
831
814
|
if (argv.tool === 'claude' || !argv.tool) {
|
|
832
815
|
// If flag is true (default), check if Playwright MCP is actually available
|
|
833
816
|
if (argv.promptPlaywrightMcp) {
|
|
834
|
-
const { checkPlaywrightMcpAvailability } = claudeLib;
|
|
835
817
|
const playwrightMcpAvailable = await checkPlaywrightMcpAvailability();
|
|
836
818
|
if (playwrightMcpAvailable) {
|
|
837
819
|
await log('🎭 Playwright MCP detected - enabling browser automation hints', { verbose: true });
|
|
@@ -927,6 +909,12 @@ try {
|
|
|
927
909
|
await log('');
|
|
928
910
|
await log(` ${claudeResumeCmd}`);
|
|
929
911
|
await log('');
|
|
912
|
+
} else if (argv.url) {
|
|
913
|
+
const solveResumeCmd = buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool: toolForResume, model: argv.model, tempDir });
|
|
914
|
+
await log(`💡 To continue this ${toolForResume} session with solve:`);
|
|
915
|
+
await log('');
|
|
916
|
+
await log(` ${solveResumeCmd}`);
|
|
917
|
+
await log('');
|
|
930
918
|
}
|
|
931
919
|
}
|
|
932
920
|
|
|
@@ -936,7 +924,7 @@ try {
|
|
|
936
924
|
try {
|
|
937
925
|
// Build Claude CLI resume command
|
|
938
926
|
const tool = argv.tool || 'claude';
|
|
939
|
-
const resumeCommand = tool === 'claude' ? buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model }) : null;
|
|
927
|
+
const resumeCommand = tool === 'claude' ? buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model }) : sessionId ? buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool, model: argv.model, tempDir }) : null;
|
|
940
928
|
const logUploadSuccess = await attachLogToGitHub({
|
|
941
929
|
logFile: getLogFile(),
|
|
942
930
|
targetType: 'pr',
|
|
@@ -974,7 +962,7 @@ try {
|
|
|
974
962
|
const resetTime = global.limitResetTime;
|
|
975
963
|
// Build Claude CLI resume command
|
|
976
964
|
const tool = argv.tool || 'claude';
|
|
977
|
-
const resumeCmd = tool === 'claude' ? buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model }) : null;
|
|
965
|
+
const resumeCmd = tool === 'claude' ? buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model }) : sessionId ? buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool, model: argv.model, tempDir }) : null;
|
|
978
966
|
const resumeSection = resumeCmd ? `To resume after the limit resets, use:\n\`\`\`bash\n${resumeCmd}\n\`\`\`` : `Session ID: \`${sessionId}\``;
|
|
979
967
|
// Format the reset time with relative time and UTC conversion if available
|
|
980
968
|
const timezone = global.limitTimezone || null;
|
|
@@ -1002,7 +990,7 @@ try {
|
|
|
1002
990
|
try {
|
|
1003
991
|
// Build Claude CLI resume command (only for logging, not shown to users when auto-resume is enabled)
|
|
1004
992
|
const tool = argv.tool || 'claude';
|
|
1005
|
-
const resumeCommand = tool === 'claude' ? buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model }) : null;
|
|
993
|
+
const resumeCommand = tool === 'claude' ? buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model }) : sessionId ? buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool, model: argv.model, tempDir }) : null;
|
|
1006
994
|
const logUploadSuccess = await attachLogToGitHub({
|
|
1007
995
|
logFile: getLogFile(),
|
|
1008
996
|
targetType: 'pr',
|
|
@@ -1090,6 +1078,13 @@ try {
|
|
|
1090
1078
|
await log('');
|
|
1091
1079
|
await log(` ${claudeResumeCmd}`);
|
|
1092
1080
|
await log('');
|
|
1081
|
+
} else if (sessionId && argv.url) {
|
|
1082
|
+
const solveResumeCmd = buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool: toolForFailure, model: argv.model, tempDir });
|
|
1083
|
+
await log('');
|
|
1084
|
+
await log(`💡 To continue this ${toolForFailure} session with solve:`);
|
|
1085
|
+
await log('');
|
|
1086
|
+
await log(` ${solveResumeCmd}`);
|
|
1087
|
+
await log('');
|
|
1093
1088
|
}
|
|
1094
1089
|
|
|
1095
1090
|
// Attach failure logs before exiting (Issues #1212, #1462: fall back to issue if no PR)
|
|
@@ -1104,7 +1099,7 @@ try {
|
|
|
1104
1099
|
try {
|
|
1105
1100
|
// Build Claude CLI resume command
|
|
1106
1101
|
const tool = argv.tool || 'claude';
|
|
1107
|
-
const resumeCommand = sessionId
|
|
1102
|
+
const resumeCommand = sessionId ? (tool === 'claude' ? buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model }) : buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool, model: argv.model, tempDir })) : null;
|
|
1108
1103
|
const logUploadSuccess = await attachLogToGitHub({
|
|
1109
1104
|
logFile: getLogFile(),
|
|
1110
1105
|
targetType: logTargetType,
|
|
@@ -358,8 +358,8 @@ export const createProgressMonitor = ({ owner, repo, prNumber, $, log, verbose =
|
|
|
358
358
|
};
|
|
359
359
|
|
|
360
360
|
/**
|
|
361
|
-
* Process a
|
|
362
|
-
* and updating progress automatically.
|
|
361
|
+
* Process a tool stream event, detecting Claude TodoWrite or Codex todo_list
|
|
362
|
+
* updates and updating progress automatically.
|
|
363
363
|
*
|
|
364
364
|
* @param {Object} data - Parsed JSON event from Claude CLI stream
|
|
365
365
|
* @param {boolean} force - Force update even if within rate limit interval
|
|
@@ -381,6 +381,14 @@ export const createProgressMonitor = ({ owner, repo, prNumber, $, log, verbose =
|
|
|
381
381
|
if (data.type === 'user' && data.tool_use_result?.newTodos) {
|
|
382
382
|
updated = await updateProgress(data.tool_use_result.newTodos, force);
|
|
383
383
|
}
|
|
384
|
+
// Pattern 3: Codex item event with todo_list payload
|
|
385
|
+
if ((data.type === 'item.started' || data.type === 'item.updated' || data.type === 'item.completed') && data.item?.type === 'todo_list' && Array.isArray(data.item.items)) {
|
|
386
|
+
const todos = data.item.items.map(todo => ({
|
|
387
|
+
status: todo?.completed ? 'completed' : 'pending',
|
|
388
|
+
content: todo?.text || '',
|
|
389
|
+
}));
|
|
390
|
+
updated = await updateProgress(todos, force);
|
|
391
|
+
}
|
|
384
392
|
return updated;
|
|
385
393
|
};
|
|
386
394
|
|
|
@@ -208,9 +208,21 @@ export const executeToolIteration = async params => {
|
|
|
208
208
|
} else if (argv.tool === 'codex') {
|
|
209
209
|
// Use Codex
|
|
210
210
|
const codexExecLib = await import('./codex.lib.mjs');
|
|
211
|
-
const { executeCodex } = codexExecLib;
|
|
211
|
+
const { executeCodex, checkPlaywrightMcpAvailability } = codexExecLib;
|
|
212
212
|
const codexPath = argv.codexPath || 'codex';
|
|
213
213
|
|
|
214
|
+
if (argv.promptPlaywrightMcp) {
|
|
215
|
+
const playwrightMcpAvailable = await checkPlaywrightMcpAvailability();
|
|
216
|
+
if (playwrightMcpAvailable) {
|
|
217
|
+
await log('🎭 Playwright MCP detected - enabling browser automation hints', { verbose: true });
|
|
218
|
+
} else {
|
|
219
|
+
await log('ℹ️ Playwright MCP not detected - browser automation hints will be disabled', { verbose: true });
|
|
220
|
+
argv.promptPlaywrightMcp = false;
|
|
221
|
+
}
|
|
222
|
+
} else {
|
|
223
|
+
await log('ℹ️ Playwright MCP explicitly disabled via --no-prompt-playwright-mcp', { verbose: true });
|
|
224
|
+
}
|
|
225
|
+
|
|
214
226
|
toolResult = await executeCodex({
|
|
215
227
|
issueUrl,
|
|
216
228
|
issueNumber,
|
|
@@ -37,6 +37,41 @@ const { autoContinueWhenLimitResets } = autoContinue;
|
|
|
37
37
|
const claudeCommandBuilder = await import('./claude.command-builder.lib.mjs');
|
|
38
38
|
export const { buildClaudeResumeCommand, buildClaudeInitialCommand } = claudeCommandBuilder;
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Build a solve.mjs resume command for tools that do not have a first-party interactive
|
|
42
|
+
* resume CLI flow like Claude Code. This keeps the invocation within hive-mind so the
|
|
43
|
+
* original tool selection and working directory can be preserved.
|
|
44
|
+
*
|
|
45
|
+
* @param {Object} options
|
|
46
|
+
* @param {string} options.issueUrl - The issue URL passed to solve.mjs
|
|
47
|
+
* @param {string} options.sessionId - The session ID to resume
|
|
48
|
+
* @param {string|null} [options.tool] - Tool name (codex, opencode, agent)
|
|
49
|
+
* @param {string|null} [options.model] - Model name to preserve
|
|
50
|
+
* @param {string|null} [options.tempDir] - Working directory to preserve
|
|
51
|
+
* @param {string} [options.nodePath] - Node binary path
|
|
52
|
+
* @param {string} [options.scriptPath] - solve.mjs path
|
|
53
|
+
* @returns {string}
|
|
54
|
+
*/
|
|
55
|
+
export const buildSolveResumeCommand = ({ issueUrl, sessionId, tool = null, model = null, tempDir = null, nodePath = process.argv[0], scriptPath = process.argv[1] }) => {
|
|
56
|
+
const shellQuote = value => `"${String(value).replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`;
|
|
57
|
+
|
|
58
|
+
const args = [shellQuote(scriptPath), shellQuote(issueUrl), '--resume', shellQuote(sessionId)];
|
|
59
|
+
|
|
60
|
+
if (tool && tool !== 'claude') {
|
|
61
|
+
args.push('--tool', shellQuote(tool));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (model) {
|
|
65
|
+
args.push('--model', shellQuote(model));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (tempDir) {
|
|
69
|
+
args.push('--working-directory', shellQuote(tempDir));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return `${shellQuote(nodePath)} ${args.join(' ')}`;
|
|
73
|
+
};
|
|
74
|
+
|
|
40
75
|
// Import error handling functions
|
|
41
76
|
// const errorHandlers = await import('./solve.error-handlers.lib.mjs'); // Not currently used
|
|
42
77
|
// Import Sentry integration
|
|
@@ -444,12 +479,8 @@ export const showSessionSummary = async (sessionId, limitReached, argv, issueUrl
|
|
|
444
479
|
const absoluteLogPath = path.resolve(getLogFile());
|
|
445
480
|
await log(`✅ Complete log file: ${absoluteLogPath}`);
|
|
446
481
|
|
|
447
|
-
// Show claude resume command only for --tool claude (or default)
|
|
448
|
-
// This allows users to investigate, resume, see context, and more
|
|
449
|
-
// Uses the (cd ... && claude --resume ...) pattern for a fully copyable, executable command
|
|
450
482
|
const tool = argv.tool || 'claude';
|
|
451
483
|
if (tool === 'claude') {
|
|
452
|
-
// Build the Claude CLI resume command using the command builder
|
|
453
484
|
const claudeResumeCmd = buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model });
|
|
454
485
|
|
|
455
486
|
await log('');
|
|
@@ -457,6 +488,13 @@ export const showSessionSummary = async (sessionId, limitReached, argv, issueUrl
|
|
|
457
488
|
await log('');
|
|
458
489
|
await log(` ${claudeResumeCmd}`);
|
|
459
490
|
await log('');
|
|
491
|
+
} else if (issueUrl) {
|
|
492
|
+
const solveResumeCmd = buildSolveResumeCommand({ issueUrl, sessionId, tool, model: argv.model, tempDir });
|
|
493
|
+
await log('');
|
|
494
|
+
await log(`💡 To continue this ${tool} session with solve:`);
|
|
495
|
+
await log('');
|
|
496
|
+
await log(` ${solveResumeCmd}`);
|
|
497
|
+
await log('');
|
|
460
498
|
}
|
|
461
499
|
|
|
462
500
|
if (limitReached) {
|
|
@@ -472,7 +510,7 @@ export const showSessionSummary = async (sessionId, limitReached, argv, issueUrl
|
|
|
472
510
|
await log(`\n⏰ Limit resets at: ${global.limitResetTime}`);
|
|
473
511
|
}
|
|
474
512
|
|
|
475
|
-
await log('\n💡 After the limit resets, resume using the
|
|
513
|
+
await log('\n💡 After the limit resets, resume using the command above.');
|
|
476
514
|
|
|
477
515
|
if (argv.autoCleanup !== false) {
|
|
478
516
|
await log('');
|
|
@@ -306,7 +306,7 @@ export const performSystemChecks = async (minDiskSpace = 2048, skipToolConnectio
|
|
|
306
306
|
} else if (argv.tool === 'codex') {
|
|
307
307
|
// Validate Codex connection
|
|
308
308
|
const codexLib = await import('./codex.lib.mjs');
|
|
309
|
-
isToolConnected = await codexLib.validateCodexConnection(model);
|
|
309
|
+
isToolConnected = await codexLib.validateCodexConnection(model, argv.verbose);
|
|
310
310
|
if (!isToolConnected) {
|
|
311
311
|
await log('❌ Cannot proceed without Codex connection', { level: 'error' });
|
|
312
312
|
return false;
|
package/src/telegram-bot.mjs
CHANGED
|
@@ -41,7 +41,7 @@ const { parseGitHubUrl, validateGitHubEntityExistence } = await import('./github
|
|
|
41
41
|
const { validateModelName, buildModelOptionDescription } = await import('./models/index.mjs');
|
|
42
42
|
const { validateBranchInArgs } = await import('./solve.branch.lib.mjs');
|
|
43
43
|
const { extractIsolationFromArgs, isValidPerCommandIsolation, resolveIsolation, createIsolationAwareQueueCallback } = await import('./telegram-isolation.lib.mjs');
|
|
44
|
-
const { formatUsageMessage, getAllCachedLimits } = await import('./limits.lib.mjs');
|
|
44
|
+
const { formatUsageMessage, formatCodexLimitsSection, getAllCachedLimits } = await import('./limits.lib.mjs');
|
|
45
45
|
const { getVersionInfo, formatVersionMessage } = await import('./version-info.lib.mjs');
|
|
46
46
|
const { escapeMarkdown, escapeMarkdownV2, cleanNonPrintableChars, makeSpecialCharsVisible } = await import('./telegram-markdown.lib.mjs');
|
|
47
47
|
const { getSolveQueue, createQueueExecuteCallback } = await import('./telegram-solve-queue.lib.mjs');
|
|
@@ -758,9 +758,11 @@ bot.command('limits', async ctx => {
|
|
|
758
758
|
|
|
759
759
|
// Format message with usage limits and queue status (issues #1343, #1267)
|
|
760
760
|
const claudeError = limits.claude.success ? null : limits.claude.error;
|
|
761
|
+
const codexError = limits.codex.success ? null : limits.codex.error;
|
|
761
762
|
const solveQueue = getSolveQueue({ verbose: VERBOSE });
|
|
762
763
|
const queueStatus = await solveQueue.formatStatus();
|
|
763
|
-
const
|
|
764
|
+
const codexSection = formatCodexLimitsSection(limits.codex.success ? limits.codex : null, codexError);
|
|
765
|
+
const message = '📊 *Usage Limits*\n\n' + formatUsageMessage(limits.claude.success ? limits.claude.usage : null, limits.disk.success ? limits.disk.diskSpace : null, limits.github.success ? limits.github.githubRateLimit : null, limits.cpu.success ? limits.cpu.cpuLoad : null, limits.memory.success ? limits.memory.memory : null, claudeError, [codexSection, queueStatus]);
|
|
764
766
|
await ctx.telegram.editMessageText(fetchingMessage.chat.id, fetchingMessage.message_id, undefined, message, { parse_mode: 'Markdown' });
|
|
765
767
|
});
|
|
766
768
|
bot.command('version', async ctx => {
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { exec } from 'node:child_process';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
|
|
6
|
+
const execAsync = promisify(exec);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Count running processes by name.
|
|
10
|
+
* @param {string} processName - Process name to search for (e.g., 'claude', 'agent')
|
|
11
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
12
|
+
* @returns {Promise<{count: number, processes: string[]}>}
|
|
13
|
+
*/
|
|
14
|
+
export async function getRunningProcesses(processName, verbose = false) {
|
|
15
|
+
try {
|
|
16
|
+
const { stdout } = await execAsync(`pgrep -l -x ${processName} 2>/dev/null || true`);
|
|
17
|
+
const lines = stdout
|
|
18
|
+
.trim()
|
|
19
|
+
.split('\n')
|
|
20
|
+
.filter(line => line.trim());
|
|
21
|
+
|
|
22
|
+
const processes = lines
|
|
23
|
+
.map(line => {
|
|
24
|
+
const parts = line.trim().split(/\s+/);
|
|
25
|
+
return {
|
|
26
|
+
pid: parts[0],
|
|
27
|
+
name: parts.slice(1).join(' ') || processName,
|
|
28
|
+
};
|
|
29
|
+
})
|
|
30
|
+
.filter(p => p.pid);
|
|
31
|
+
|
|
32
|
+
if (verbose) {
|
|
33
|
+
console.log(`[VERBOSE] /solve_queue found ${processes.length} running ${processName} processes`);
|
|
34
|
+
if (processes.length > 0) {
|
|
35
|
+
console.log(`[VERBOSE] /solve_queue processes: ${JSON.stringify(processes)}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
count: processes.length,
|
|
41
|
+
processes: processes.map(p => `${p.pid}:${p.name}`),
|
|
42
|
+
};
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (verbose) {
|
|
45
|
+
console.error(`[VERBOSE] /solve_queue error counting ${processName} processes:`, error.message);
|
|
46
|
+
}
|
|
47
|
+
return { count: 0, processes: [] };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Count running claude processes.
|
|
53
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
54
|
+
* @returns {Promise<{count: number, processes: string[]}>}
|
|
55
|
+
*/
|
|
56
|
+
export async function getRunningClaudeProcesses(verbose = false) {
|
|
57
|
+
return getRunningProcesses('claude', verbose);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Count running agent processes.
|
|
62
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
63
|
+
* @returns {Promise<{count: number, processes: string[]}>}
|
|
64
|
+
*/
|
|
65
|
+
export async function getRunningAgentProcesses(verbose = false) {
|
|
66
|
+
return getRunningProcesses('agent', verbose);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Count running codex processes.
|
|
71
|
+
* @param {boolean} verbose - Whether to log verbose output
|
|
72
|
+
* @returns {Promise<{count: number, processes: string[]}>}
|
|
73
|
+
*/
|
|
74
|
+
export async function getRunningCodexProcesses(verbose = false) {
|
|
75
|
+
return getRunningProcesses('codex', verbose);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Format a threshold as percentage for display.
|
|
80
|
+
* @param {number} ratio - Ratio (0.0 - 1.0)
|
|
81
|
+
* @returns {string} Formatted percentage
|
|
82
|
+
*/
|
|
83
|
+
export function formatThresholdPercent(ratio) {
|
|
84
|
+
return `${Math.round(ratio * 100)}%`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Format milliseconds into human-readable duration.
|
|
89
|
+
* Shows days, hours, minutes, and seconds as appropriate.
|
|
90
|
+
* Examples: "5h 43m 23s", "2m 15s", "45s", "1d 3h 12m 5s"
|
|
91
|
+
*
|
|
92
|
+
* @param {number} ms - Duration in milliseconds
|
|
93
|
+
* @returns {string} Human-readable duration
|
|
94
|
+
* @see https://github.com/link-assistant/hive-mind/issues/1267
|
|
95
|
+
*/
|
|
96
|
+
export function formatDuration(ms) {
|
|
97
|
+
if (ms < 0) ms = 0;
|
|
98
|
+
|
|
99
|
+
const totalSeconds = Math.floor(ms / 1000);
|
|
100
|
+
const days = Math.floor(totalSeconds / 86400);
|
|
101
|
+
const hours = Math.floor((totalSeconds % 86400) / 3600);
|
|
102
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
103
|
+
const seconds = totalSeconds % 60;
|
|
104
|
+
|
|
105
|
+
const parts = [];
|
|
106
|
+
if (days > 0) parts.push(`${days}d`);
|
|
107
|
+
if (hours > 0) parts.push(`${hours}h`);
|
|
108
|
+
if (minutes > 0) parts.push(`${minutes}m`);
|
|
109
|
+
if (seconds > 0 || parts.length === 0) parts.push(`${seconds}s`);
|
|
110
|
+
|
|
111
|
+
return parts.join(' ');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Generate human-readable waiting reason based on threshold violation.
|
|
116
|
+
* @param {string} metric - The metric name (ram, cpu, disk, etc.)
|
|
117
|
+
* @param {number} currentValue - Current value (as percentage 0-100)
|
|
118
|
+
* @param {number} threshold - Threshold ratio (0.0 - 1.0)
|
|
119
|
+
* @returns {string} Human-readable reason
|
|
120
|
+
*/
|
|
121
|
+
export function formatWaitingReason(metric, currentValue, threshold) {
|
|
122
|
+
const thresholdPercent = formatThresholdPercent(threshold);
|
|
123
|
+
const currentPercent = Math.round(currentValue);
|
|
124
|
+
|
|
125
|
+
switch (metric) {
|
|
126
|
+
case 'ram':
|
|
127
|
+
return `RAM usage is ${currentPercent}% (threshold: ${thresholdPercent})`;
|
|
128
|
+
case 'cpu':
|
|
129
|
+
return `CPU usage is ${currentPercent}% (threshold: ${thresholdPercent})`;
|
|
130
|
+
case 'disk':
|
|
131
|
+
return `Disk usage is ${currentPercent}% (threshold: ${thresholdPercent})`;
|
|
132
|
+
case 'claude_5_hour_session':
|
|
133
|
+
return `Claude 5 hour session limit is ${currentPercent}% (threshold: ${thresholdPercent})`;
|
|
134
|
+
case 'claude_weekly':
|
|
135
|
+
return `Claude weekly limit is ${currentPercent}% (threshold: ${thresholdPercent})`;
|
|
136
|
+
case 'codex_5_hour_session':
|
|
137
|
+
return `Codex 5 hour session limit is ${currentPercent}% (threshold: ${thresholdPercent})`;
|
|
138
|
+
case 'codex_weekly':
|
|
139
|
+
return `Codex weekly limit is ${currentPercent}% (threshold: ${thresholdPercent})`;
|
|
140
|
+
case 'github':
|
|
141
|
+
return `GitHub API usage is ${currentPercent}% (threshold: ${thresholdPercent})`;
|
|
142
|
+
case 'min_interval':
|
|
143
|
+
return 'Minimum interval between commands not reached';
|
|
144
|
+
case 'claude_running':
|
|
145
|
+
return 'Claude process is already running';
|
|
146
|
+
case 'codex_running':
|
|
147
|
+
return 'Codex process is already running';
|
|
148
|
+
default:
|
|
149
|
+
return `${metric} threshold exceeded`;
|
|
150
|
+
}
|
|
151
|
+
}
|