@link-assistant/hive-mind 1.72.0 → 1.72.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.72.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 055a1a0: Fix `--auto-attach-solution-summary` so Codex-authored comments that use the
8
+ visible "Working session summary" heading are counted as AI comments instead of
9
+ being mistaken for hive-mind's automated summary comment.
10
+
11
+ ## 1.72.1
12
+
13
+ ### Patch Changes
14
+
15
+ - 249646e: Fix: Move Claude CLI resume command from GitHub comment to logs
16
+
17
+ When usage limit is reached, the GitHub comment now only mentions the
18
+ `--auto-continue-on-limit-reset` option instead of showing bash commands.
19
+ This is more user-friendly for Telegram bot users who don't use CLI commands directly.
20
+
21
+ The Claude CLI resume command is still available in the logs (in the collapsed
22
+ block or gist link), allowing advanced users to resume manually if needed:
23
+
24
+ ```bash
25
+ (cd "/tmp/gh-issue-solver-..." && claude --resume session-id)
26
+ ```
27
+
28
+ Changes:
29
+ - GitHub comments now only suggest using the `--auto-continue-on-limit-reset` option
30
+ - Resume commands are kept in logs only (not in the visible comment)
31
+ - Session ID is still shown for reference
32
+
33
+ Fixes #942
34
+
3
35
  ## 1.72.0
4
36
 
5
37
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.72.0",
3
+ "version": "1.72.2",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -52,6 +52,43 @@ export const buildClaudeResumeCommand = ({ tempDir, sessionId, claudePath = 'cla
52
52
  return `(cd "${tempDir}" && ${claudePath} ${args})`;
53
53
  };
54
54
 
55
+ /**
56
+ * Build the Claude CLI autonomous resume command
57
+ *
58
+ * This generates a fully autonomous command that includes all flags needed to run
59
+ * without user interaction. The user can copy-paste this command and it will
60
+ * continue the session autonomously.
61
+ *
62
+ * The command includes:
63
+ * - --resume <sessionId>: Resume from the specified session
64
+ * - --output-format stream-json: For streaming output
65
+ * - --dangerously-skip-permissions: Skip interactive permission prompts
66
+ * - --model <model>: Use the same model as the original session
67
+ * - -p "Continue.": Simple prompt to continue the work
68
+ *
69
+ * Note: This function is specifically designed for Claude CLI (--tool claude)
70
+ * and should only be used when the tool is 'claude' or undefined (defaults to claude).
71
+ *
72
+ * @param {Object} options - Options for building the command
73
+ * @param {string} options.tempDir - The working directory (e.g., /tmp/gh-issue-solver-...)
74
+ * @param {string} options.sessionId - The session ID to resume
75
+ * @param {string} options.claudePath - Path to the claude CLI binary (defaults to 'claude')
76
+ * @param {string} [options.model] - The model to use (e.g., 'sonnet', 'opus', 'claude-sonnet-4-20250514')
77
+ * @returns {string} - The full autonomous resume command
78
+ */
79
+ export const buildClaudeAutonomousResumeCommand = ({ tempDir, sessionId, claudePath = 'claude', model }) => {
80
+ let args = `--resume ${sessionId} --output-format stream-json --dangerously-skip-permissions`;
81
+
82
+ if (model) {
83
+ args += ` --model ${model}`;
84
+ }
85
+
86
+ // Add a simple "Continue." prompt to continue the autonomous work
87
+ args += ` -p "Continue."`;
88
+
89
+ return `(cd "${tempDir}" && ${claudePath} ${args})`;
90
+ };
91
+
55
92
  /**
56
93
  * Build the Claude CLI initial command with the (cd ... && claude ...) pattern
57
94
  *
@@ -85,5 +122,6 @@ export const buildClaudeInitialCommand = ({ tempDir, claudePath = 'claude', mode
85
122
  // Export default object for compatibility
86
123
  export default {
87
124
  buildClaudeResumeCommand,
125
+ buildClaudeAutonomousResumeCommand,
88
126
  buildClaudeInitialCommand,
89
127
  };
@@ -16,7 +16,8 @@ import { initProgressMonitoring } from './solve.progress-monitoring.lib.mjs';
16
16
  import { sanitizeObjectStrings } from './unicode-sanitization.lib.mjs';
17
17
  import Decimal from 'decimal.js-light';
18
18
  import { displayBudgetStats, createEmptySubSessionUsage, accumulateModelUsage, displayModelUsage, displayCostComparison, mergeResultModelUsage, createSubAgentCallEntry, accumulateSubAgentUsage, getRawRequestInputTokens } from './claude.budget-stats.lib.mjs';
19
- import { buildClaudeResumeCommand } from './claude.command-builder.lib.mjs';
19
+ import { buildClaudeResumeCommand, buildClaudeAutonomousResumeCommand } from './claude.command-builder.lib.mjs';
20
+ import { buildSolveResumeCommand } from './solve.resume-command.lib.mjs'; // Issue #942
20
21
  import { SESSION_FORCE_KILLED_MARKER, postTrackedComment } from './tool-comments.lib.mjs'; // Issue #1625
21
22
  import { handleClaudeRuntimeSwitch } from './claude.runtime-switch.lib.mjs'; // see issue #1141
22
23
  import { CLAUDE_MODELS as availableModels } from './models/index.mjs'; // Issue #1221
@@ -27,13 +28,14 @@ import { fetchModelInfo } from './model-info.lib.mjs';
27
28
  import { classifyRetryableError, maybeSwitchToFallbackModel } from './tool-retry.lib.mjs';
28
29
  import { resolveSubSessionSize } from './sub-session-size.lib.mjs'; // Issue #1706
29
30
  import { withAgentsMdAsClaudeMd } from './agents-md-claude-support.lib.mjs';
30
- export { availableModels }; // Re-export for backward compatibility
31
- export { fetchModelInfo };
32
- const showResumeCommand = async (sessionId, tempDir, claudePath, model, log) => {
31
+ export { availableModels, fetchModelInfo }; // Re-export for backward compatibility
32
+ const showResumeCommand = async (sessionId, tempDir, claudePath, model, log, argv = null) => {
33
33
  if (!sessionId || !tempDir) return;
34
- const cmd = buildClaudeResumeCommand({ tempDir, sessionId, claudePath, model });
35
- await log('\n💡 To continue this session in Claude Code interactive mode:\n');
36
- await log(` ${cmd}\n`);
34
+ await log(`\n💡 To continue this session:\n`);
35
+ await log(` Interactive mode: ${buildClaudeResumeCommand({ tempDir, sessionId, claudePath, model })}\n`);
36
+ await log(` Autonomous mode: ${buildClaudeAutonomousResumeCommand({ tempDir, sessionId, claudePath, model })}\n`);
37
+ // Issue #942: 3rd option - restart the entire /solve flow, not just the claude session.
38
+ if (argv && argv.url) await log(` Solve resume mode: ${buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool: argv.tool || 'claude', model: argv.model, fallbackModel: argv.fallbackModel, tempDir })}\n`);
37
39
  };
38
40
  /** Format numbers with spaces as thousands separator (no commas) */
39
41
  export const formatNumber = num => {
@@ -1238,25 +1240,24 @@ export const executeClaudeCommand = async params => {
1238
1240
  limitReached = true;
1239
1241
  limitResetTime = limitInfo.resetTime;
1240
1242
  limitTimezone = limitInfo.timezone;
1241
-
1242
- // Format and display user-friendly message
1243
+ const hasSession = tempDir && sessionId;
1244
+ // Issue #942: include all 3 resume options (interactive/autonomous/solve).
1243
1245
  const messageLines = formatUsageLimitMessage({
1244
1246
  tool: 'Anthropic Claude Code',
1245
1247
  resetTime: limitInfo.resetTime,
1246
1248
  sessionId,
1247
- resumeCommand: argv.url ? `${process.argv[0]} ${process.argv[1]} --auto-continue ${argv.url}` : null,
1249
+ interactiveResumeCommand: hasSession ? buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model }) : null,
1250
+ autonomousResumeCommand: hasSession ? buildClaudeAutonomousResumeCommand({ tempDir, sessionId, model: argv.model }) : null,
1251
+ solveResumeCommand: hasSession && argv?.url ? buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool: argv.tool || 'claude', model: argv.model, fallbackModel: argv.fallbackModel, tempDir }) : null,
1248
1252
  });
1249
- for (const line of messageLines) {
1250
- await log(line, { level: 'warning' });
1251
- }
1253
+ for (const line of messageLines) await log(line, { level: 'warning' });
1252
1254
  } else if (lastMessage.includes('context_length_exceeded')) {
1253
1255
  await log('\n\n❌ Context length exceeded. Try with a smaller issue or split the work.', { level: 'error' });
1254
1256
  } else {
1255
1257
  await log(`\n\n❌ Claude command failed with exit code ${exitCode}`, { level: 'error' });
1256
- if (sessionId && !argv.resume) {
1257
- await log(`📌 Session ID for resuming: ${sessionId}`);
1258
- await log('\nTo resume this session, run:');
1259
- await log(` ${process.argv[0]} ${process.argv[1]} ${argv.url} --resume ${sessionId}`);
1258
+ if (sessionId && !argv.resume && tempDir) {
1259
+ await log(`📌 Session ID: ${sessionId}`);
1260
+ await showResumeCommand(sessionId, tempDir, claudePath, argv.model, log, argv);
1260
1261
  }
1261
1262
  }
1262
1263
  }
@@ -1275,7 +1276,7 @@ export const executeClaudeCommand = async params => {
1275
1276
  await log('\n📈 System resources after execution:', { verbose: true });
1276
1277
  await log(` Memory: ${resourcesAfter.memory.split('\n')[1]}`, { verbose: true });
1277
1278
  await log(` Load: ${resourcesAfter.load}`, { verbose: true });
1278
- await showResumeCommand(sessionId, tempDir, claudePath, argv.model, log);
1279
+ await showResumeCommand(sessionId, tempDir, claudePath, argv.model, log, argv);
1279
1280
  return {
1280
1281
  success: false,
1281
1282
  sessionId,
@@ -1361,7 +1362,7 @@ export const executeClaudeCommand = async params => {
1361
1362
  await log(` ⚠️ Could not calculate token usage: ${tokenError.message}`, { verbose: true });
1362
1363
  }
1363
1364
  }
1364
- await showResumeCommand(sessionId, tempDir, claudePath, argv.model, log);
1365
+ await showResumeCommand(sessionId, tempDir, claudePath, argv.model, log, argv);
1365
1366
  return {
1366
1367
  success: true,
1367
1368
  sessionId,
package/src/codex.lib.mjs CHANGED
@@ -17,6 +17,8 @@ import { log } from './lib.mjs';
17
17
  import { reportError } from './sentry.lib.mjs';
18
18
  import { timeouts, retryLimits } from './config.lib.mjs';
19
19
  import { detectUsageLimit, formatUsageLimitMessage } from './usage-limit.lib.mjs';
20
+ import { buildSolveResumeCommand } from './solve.resume-command.lib.mjs'; // Issue #942
21
+ const __codexBuildSolveResumeCmd = (argv, sessionId, tempDir) => (sessionId && argv?.url ? buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool: 'codex', model: argv.model, fallbackModel: argv.fallbackModel, tempDir }) : null);
20
22
  import { sanitizeObjectStrings } from './unicode-sanitization.lib.mjs';
21
23
  import { mapModelToId, resolveCodexReasoningEffort } from './codex.options.lib.mjs';
22
24
  import { createInteractiveHandler } from './interactive-mode.lib.mjs';
@@ -1011,11 +1013,13 @@ export const executeCodexCommand = async params => {
1011
1013
  limitReached = true;
1012
1014
  limitResetTime = limitInfo.resetTime;
1013
1015
 
1016
+ // Issue #942: build proper solve resume command (preserves tool/model/dir).
1017
+ const solveResumeCmd = __codexBuildSolveResumeCmd(argv, sessionId, tempDir);
1014
1018
  const messageLines = formatUsageLimitMessage({
1015
1019
  tool: 'OpenAI Codex',
1016
1020
  resetTime: limitInfo.resetTime,
1017
1021
  sessionId,
1018
- resumeCommand: sessionId ? `${process.argv[0]} ${process.argv[1]} ${argv.url} --resume ${sessionId}` : null,
1022
+ solveResumeCommand: solveResumeCmd,
1019
1023
  });
1020
1024
 
1021
1025
  for (const line of messageLines) {
@@ -1096,11 +1100,13 @@ export const executeCodexCommand = async params => {
1096
1100
  limitResetTime = limitInfo.resetTime;
1097
1101
 
1098
1102
  // Format and display user-friendly message
1103
+ // Issue #942: build proper solve resume command (preserves tool/model/dir).
1104
+ const solveResumeCmd = __codexBuildSolveResumeCmd(argv, sessionId, tempDir);
1099
1105
  const messageLines = formatUsageLimitMessage({
1100
1106
  tool: 'OpenAI Codex',
1101
1107
  resetTime: limitInfo.resetTime,
1102
1108
  sessionId,
1103
- resumeCommand: sessionId ? `${process.argv[0]} ${process.argv[1]} ${argv.url} --resume ${sessionId}` : null,
1109
+ solveResumeCommand: solveResumeCmd,
1104
1110
  });
1105
1111
 
1106
1112
  for (const line of messageLines) {
@@ -13,6 +13,8 @@ import { log } from './lib.mjs';
13
13
  import { reportError } from './sentry.lib.mjs';
14
14
  import { timeouts, retryLimits } from './config.lib.mjs';
15
15
  import { detectUsageLimit, formatUsageLimitMessage } from './usage-limit.lib.mjs';
16
+ import { buildSolveResumeCommand } from './solve.resume-command.lib.mjs'; // Issue #942
17
+ const __geminiBuildSolveResumeCmd = (argv, sessionId, tempDir) => (sessionId && argv?.url ? buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool: 'gemini', model: argv.model, fallbackModel: argv.fallbackModel, tempDir }) : null);
16
18
  import { sanitizeObjectStrings } from './unicode-sanitization.lib.mjs';
17
19
  import { defaultModels, geminiModels } from './models/index.mjs';
18
20
  import { checkPlaywrightMcpPackageAvailability } from './playwright-mcp.lib.mjs';
@@ -539,11 +541,13 @@ export const executeGeminiCommand = async params => {
539
541
  limitReached = true;
540
542
  limitResetTime = limitInfo.resetTime;
541
543
 
544
+ // Issue #942: build proper solve resume command (preserves tool/model/dir).
545
+ const solveResumeCmd = __geminiBuildSolveResumeCmd(argv, sessionId, tempDir);
542
546
  const messageLines = formatUsageLimitMessage({
543
547
  tool: 'Gemini CLI',
544
548
  resetTime: limitInfo.resetTime,
545
549
  sessionId,
546
- resumeCommand: sessionId ? `${process.argv[0]} ${process.argv[1]} ${argv.url} --resume ${sessionId} --tool gemini` : null,
550
+ solveResumeCommand: solveResumeCmd,
547
551
  });
548
552
 
549
553
  for (const line of messageLines) {
package/src/solve.mjs CHANGED
@@ -31,7 +31,7 @@ const { processAutoContinueForIssue } = autoContinue;
31
31
  const repository = await import('./solve.repository.lib.mjs');
32
32
  const { setupTempDirectory, cleanupTempDirectory } = repository;
33
33
  const results = await import('./solve.results.lib.mjs');
34
- const { cleanupClaudeFile, showSessionSummary, verifyResults, buildClaudeResumeCommand, buildSolveResumeCommand, maybeAttachWorkingSessionSummary, verifyPullRequestIssueLinkAfterAutoRestart } = results;
34
+ const { cleanupClaudeFile, showSessionSummary, verifyResults, buildClaudeResumeCommand, buildClaudeAutonomousResumeCommand, buildSolveResumeCommand, maybeAttachWorkingSessionSummary, verifyPullRequestIssueLinkAfterAutoRestart } = results;
35
35
  const claudeLib = await import('./claude.lib.mjs');
36
36
  const { executeClaude, checkPlaywrightMcpAvailability } = claudeLib;
37
37
  const githubLinking = await import('./github-linking.lib.mjs');
@@ -868,16 +868,14 @@ try {
868
868
  await log(`⏰ Limit resets at: ${formattedResetTime}`);
869
869
  }
870
870
  await log('');
871
- // Show claude resume command only for --tool claude (or default)
872
- // Uses the (cd ... && claude --resume ...) pattern for a fully copyable, executable command
873
- const toolForResume = argv.tool || 'claude';
874
- if (toolForResume === 'claude') {
875
- const claudeResumeCmd = buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model });
876
- await log('💡 To continue this session in Claude Code interactive mode:');
877
- await log('');
878
- await log(` ${claudeResumeCmd}`);
871
+ // Show dual resume commands (interactive + autonomous) only for --tool claude
872
+ if ((argv.tool || 'claude') === 'claude') {
873
+ await log('💡 To continue this session:');
874
+ await log(` Interactive mode: ${buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model })}`);
875
+ await log(` Autonomous mode: ${buildClaudeAutonomousResumeCommand({ tempDir, sessionId, model: argv.model })}`);
879
876
  await log('');
880
877
  } else if (argv.url) {
878
+ const toolForResume = argv.tool || 'claude';
881
879
  const solveResumeCmd = buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool: toolForResume, model: argv.model, fallbackModel: argv.fallbackModel, tempDir });
882
880
  await log(`💡 To continue this ${toolForResume} session with solve:`);
883
881
  await log('');
@@ -926,13 +924,13 @@ try {
926
924
  await log(` ⚠️ Error uploading logs: ${uploadError.message}`);
927
925
  }
928
926
  } else if (prNumber) {
929
- // Fallback: Post simple failure comment if logs are not attached
927
+ // Fallback: Post simple failure comment (no CLI commands in GitHub comments, only mention option)
930
928
  try {
931
929
  const resetTime = global.limitResetTime;
932
- // Build Claude CLI resume command
933
- const tool = argv.tool || 'claude';
934
- const resumeCmd = tool === 'claude' ? buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model }) : sessionId ? buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool, model: argv.model, fallbackModel: argv.fallbackModel, tempDir }) : null;
935
- const resumeSection = resumeCmd ? `To resume after the limit resets, use:\n\`\`\`bash\n${resumeCmd}\n\`\`\`` : `Session ID: \`${sessionId}\``;
930
+ // Issue #942: do not embed CLI commands in GitHub comments. Users
931
+ // interact via the Telegram bot, not the CLI. The full resume
932
+ // commands (interactive/autonomous/solve) live in the attached logs.
933
+ const resumeSection = sessionId ? `Session ID: \`${sessionId}\`\n\nUse the \`--auto-resume-on-limit-reset\` or \`--auto-restart-on-limit-reset\` option to automatically resume when the limit resets.` : 'Use the `--auto-resume-on-limit-reset` or `--auto-restart-on-limit-reset` option to automatically resume when the limit resets.';
936
934
  // Format the reset time with relative time and UTC conversion if available
937
935
  const timezone = global.limitTimezone || null;
938
936
  const formattedResetTime = resetTime ? formatResetTimeWithRelative(resetTime, timezone) : null;
@@ -1039,21 +1037,22 @@ try {
1039
1037
  // Skip failure exit if limit reached with auto-resume (continues to showSessionSummary/autoContinueWhenLimitResets)
1040
1038
  const shouldSkipFailureExitForAutoLimitContinue = limitReached && argv.autoResumeOnLimitReset;
1041
1039
  if (!success && !shouldSkipFailureExitForAutoLimitContinue) {
1042
- // Show claude resume command only for --tool claude (or default) on failure
1040
+ // Issue #942: show all three resume options on failure for richer guidance.
1041
+ // 1. Interactive claude - opens Claude Code interactively (claude only)
1042
+ // 2. Autonomous claude - one-shot claude --resume w/ --dangerously-skip-permissions -p (claude only)
1043
+ // 3. Solve resume - re-enters solve.mjs with --resume, preserving tool/model/dir
1043
1044
  const toolForFailure = argv.tool || 'claude';
1044
- if (sessionId && toolForFailure === 'claude') {
1045
- const claudeResumeCmd = buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model });
1046
- await log('');
1047
- await log('💡 To continue this session in Claude Code interactive mode:');
1048
- await log('');
1049
- await log(` ${claudeResumeCmd}`);
1045
+ if (sessionId) {
1050
1046
  await log('');
1051
- } else if (sessionId && argv.url) {
1052
- const solveResumeCmd = buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool: toolForFailure, model: argv.model, fallbackModel: argv.fallbackModel, tempDir });
1053
- await log('');
1054
- await log(`💡 To continue this ${toolForFailure} session with solve:`);
1055
- await log('');
1056
- await log(` ${solveResumeCmd}`);
1047
+ await log('💡 To continue this session:');
1048
+ if (toolForFailure === 'claude') {
1049
+ await log(` Interactive mode: ${buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model })}`);
1050
+ await log(` Autonomous mode: ${buildClaudeAutonomousResumeCommand({ tempDir, sessionId, model: argv.model })}`);
1051
+ }
1052
+ if (argv.url) {
1053
+ const solveResumeCmd = buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool: toolForFailure, model: argv.model, fallbackModel: argv.fallbackModel, tempDir });
1054
+ await log(` Solve resume mode: ${solveResumeCmd}`);
1055
+ }
1057
1056
  await log('');
1058
1057
  }
1059
1058
 
@@ -43,48 +43,16 @@ const { autoContinueWhenLimitResets } = autoContinue;
43
43
  // Import Claude-specific command builders
44
44
  // These are used to generate copy-pasteable Claude CLI resume commands for users
45
45
  // Pattern: (cd "/tmp/gh-issue-solver-..." && claude --resume <session-id>)
46
+ // Two types of resume commands are supported:
47
+ // 1. Interactive resume: Short command that opens interactive mode
48
+ // 2. Autonomous resume: Full command with all flags to run autonomously
46
49
  const claudeCommandBuilder = await import('./claude.command-builder.lib.mjs');
47
- export const { buildClaudeResumeCommand, buildClaudeInitialCommand } = claudeCommandBuilder;
50
+ export const { buildClaudeResumeCommand, buildClaudeAutonomousResumeCommand, buildClaudeInitialCommand } = claudeCommandBuilder;
48
51
 
49
- /**
50
- * Build a solve.mjs resume command for tools that do not have a first-party interactive
51
- * resume CLI flow like Claude Code. This keeps the invocation within hive-mind so the
52
- * original tool selection and working directory can be preserved.
53
- *
54
- * @param {Object} options
55
- * @param {string} options.issueUrl - The issue URL passed to solve.mjs
56
- * @param {string} options.sessionId - The session ID to resume
57
- * @param {string|null} [options.tool] - Tool name (codex, opencode, agent, gemini)
58
- * @param {string|null} [options.model] - Model name to preserve
59
- * @param {string|null} [options.fallbackModel] - Explicit fallback model to preserve
60
- * @param {string|null} [options.tempDir] - Working directory to preserve
61
- * @param {string} [options.nodePath] - Node binary path
62
- * @param {string} [options.scriptPath] - solve.mjs path
63
- * @returns {string}
64
- */
65
- export const buildSolveResumeCommand = ({ issueUrl, sessionId, tool = null, model = null, fallbackModel = null, tempDir = null, nodePath = process.argv[0], scriptPath = process.argv[1] }) => {
66
- const shellQuote = value => `"${String(value).replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`;
67
-
68
- const args = [shellQuote(scriptPath), shellQuote(issueUrl), '--resume', shellQuote(sessionId)];
69
-
70
- if (tool && tool !== 'claude') {
71
- args.push('--tool', shellQuote(tool));
72
- }
73
-
74
- if (model) {
75
- args.push('--model', shellQuote(model));
76
- }
77
-
78
- if (fallbackModel) {
79
- args.push('--fallback-model', shellQuote(fallbackModel));
80
- }
81
-
82
- if (tempDir) {
83
- args.push('--working-directory', shellQuote(tempDir));
84
- }
85
-
86
- return `${shellQuote(nodePath)} ${args.join(' ')}`;
87
- };
52
+ // Issue #942: buildSolveResumeCommand lives in its own module so it can be safely
53
+ // imported from tool libraries (claude/codex/gemini) without circular imports.
54
+ import { buildSolveResumeCommand } from './solve.resume-command.lib.mjs';
55
+ export { buildSolveResumeCommand };
88
56
 
89
57
  // Import error handling functions
90
58
  // const errorHandlers = await import('./solve.error-handlers.lib.mjs'); // Not currently used
@@ -605,23 +573,23 @@ export const showSessionSummary = async (sessionId, limitReached, argv, issueUrl
605
573
  const absoluteLogPath = path.resolve(getLogFile());
606
574
  await log(`✅ Complete log file: ${absoluteLogPath}`);
607
575
 
576
+ // Show three resume options:
577
+ // 1. Interactive claude - opens Claude Code interactively (claude only)
578
+ // 2. Autonomous claude - one-shot claude --resume w/ --dangerously-skip-permissions -p (claude only)
579
+ // 3. Solve resume - re-enters solve.mjs with --resume so the full flow continues
580
+ // (works for every supported tool, including codex/gemini/etc.)
608
581
  const tool = argv.tool || 'claude';
582
+ await log('');
583
+ await log('💡 To continue this session:');
609
584
  if (tool === 'claude') {
610
- const claudeResumeCmd = buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model });
611
-
612
- await log('');
613
- await log('💡 To continue this session in Claude Code interactive mode:');
614
- await log('');
615
- await log(` ${claudeResumeCmd}`);
616
- await log('');
617
- } else if (issueUrl) {
585
+ await log(` Interactive mode: ${buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model })}`);
586
+ await log(` Autonomous mode: ${buildClaudeAutonomousResumeCommand({ tempDir, sessionId, model: argv.model })}`);
587
+ }
588
+ if (issueUrl) {
618
589
  const solveResumeCmd = buildSolveResumeCommand({ issueUrl, sessionId, tool, model: argv.model, fallbackModel: argv.fallbackModel, tempDir });
619
- await log('');
620
- await log(`💡 To continue this ${tool} session with solve:`);
621
- await log('');
622
- await log(` ${solveResumeCmd}`);
623
- await log('');
590
+ await log(` Solve resume mode: ${solveResumeCmd}`);
624
591
  }
592
+ await log('');
625
593
 
626
594
  if (limitReached) {
627
595
  await log('⏰ LIMIT REACHED DETECTED!');
@@ -1290,12 +1258,13 @@ export const attachSolutionSummary = async ({ resultSummary, prNumber, issueNumb
1290
1258
  }
1291
1259
 
1292
1260
  try {
1293
- const comment = `## Working session summary
1261
+ const comment = `${toolComments.WORKING_SESSION_SUMMARY_AUTOMATION_MARKER}
1262
+ ## ${toolComments.WORKING_SESSION_SUMMARY_MARKER}
1294
1263
 
1295
1264
  ${resultSummary}
1296
1265
 
1297
1266
  ---
1298
- *This summary was automatically extracted from the AI working session output.*`;
1267
+ *${toolComments.WORKING_SESSION_SUMMARY_AUTOMATED_FOOTER}*`;
1299
1268
 
1300
1269
  const { ok, commentId, stderr } = await postTrackedComment({ $, owner, repo, targetNumber, body: comment });
1301
1270
 
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Build a solve.mjs resume command for tools that do not have a first-party interactive
5
+ * resume CLI flow like Claude Code. This keeps the invocation within hive-mind so the
6
+ * original tool selection and working directory can be preserved.
7
+ *
8
+ * Lives in its own module (not solve.results.lib.mjs) so it can be imported from
9
+ * claude.lib.mjs / codex.lib.mjs / gemini.lib.mjs without creating a circular import.
10
+ * See issue #942.
11
+ *
12
+ * @param {Object} options
13
+ * @param {string} options.issueUrl - The issue URL passed to solve.mjs
14
+ * @param {string} options.sessionId - The session ID to resume
15
+ * @param {string|null} [options.tool] - Tool name (codex, opencode, agent, gemini)
16
+ * @param {string|null} [options.model] - Model name to preserve
17
+ * @param {string|null} [options.fallbackModel] - Explicit fallback model to preserve
18
+ * @param {string|null} [options.tempDir] - Working directory to preserve
19
+ * @param {string} [options.nodePath] - Node binary path
20
+ * @param {string} [options.scriptPath] - solve.mjs path
21
+ * @returns {string}
22
+ */
23
+ export const buildSolveResumeCommand = ({ issueUrl, sessionId, tool = null, model = null, fallbackModel = null, tempDir = null, nodePath = process.argv[0], scriptPath = process.argv[1] }) => {
24
+ const shellQuote = value => `"${String(value).replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`;
25
+ const args = [shellQuote(scriptPath), shellQuote(issueUrl), '--resume', shellQuote(sessionId)];
26
+ if (tool && tool !== 'claude') args.push('--tool', shellQuote(tool));
27
+ if (model) args.push('--model', shellQuote(model));
28
+ if (fallbackModel) args.push('--fallback-model', shellQuote(fallbackModel));
29
+ if (tempDir) args.push('--working-directory', shellQuote(tempDir));
30
+ return `${shellQuote(nodePath)} ${args.join(' ')}`;
31
+ };
@@ -64,10 +64,15 @@ export const CANCELLED_CI_REVIEW_MARKER = 'Cancelled CI/CD Requires Review';
64
64
  // iteration, or watch-mode iteration). Issue #1728: Renamed from
65
65
  // "Solution summary" because not every working session is a solution draft —
66
66
  // many are continuation/restart iterations that are part of an in-progress
67
- // solution. Tracking it as a tool-generated marker prevents the next
68
- // iteration's --auto-attach-solution-summary check from mistaking a
69
- // previous iteration's summary for an AI-authored comment.
67
+ // solution. Automation evidence is tracked separately so the visible heading
68
+ // can still be used by real AI-authored comments.
70
69
  export const WORKING_SESSION_SUMMARY_MARKER = 'Working session summary';
70
+ // Issue #1813: the visible "Working session summary" heading is natural text
71
+ // that Codex can write in its own PR comment. Do not treat the heading alone as
72
+ // a tool-generated marker. New automated summaries include this hidden marker;
73
+ // legacy automated summaries are still recognized by the footer text below.
74
+ export const WORKING_SESSION_SUMMARY_AUTOMATION_MARKER = '<!-- hive-mind:working-session-summary -->';
75
+ export const WORKING_SESSION_SUMMARY_AUTOMATED_FOOTER = 'This summary was automatically extracted from the AI working session output.';
71
76
 
72
77
  // github.lib.mjs — fork contributor "Allow edits by maintainers" request
73
78
  export const MAINTAINER_ACCESS_REQUEST_MARKER = 'Allow edits by maintainers';
@@ -102,7 +107,7 @@ export const USAGE_LIMIT_REACHED_MARKER = 'Usage Limit Reached';
102
107
  * named constants above so that adding a new marker only requires adding
103
108
  * the constant and appending it here.
104
109
  */
105
- export const TOOL_GENERATED_COMMENT_MARKERS = [AI_WORK_SESSION_STARTED_MARKER, AI_WORK_SESSION_COMPLETED_MARKER, AI_WORK_SESSION_RESUMED_MARKER, AUTO_RESUME_ON_LIMIT_RESET_MARKER, AUTO_RESTART_ON_LIMIT_RESET_MARKER, SOLUTION_DRAFT_LOG_MARKER, AUTO_RESTART_MARKER, AUTO_RESTART_UNTIL_MERGEABLE_LOG_MARKER, READY_TO_MERGE_MARKER, AUTO_MERGED_MARKER, BILLING_LIMIT_MARKER, CANCELLED_CI_REVIEW_MARKER, MAINTAINER_ACCESS_REQUEST_MARKER, LIVE_PROGRESS_SECTION_START_MARKER, SESSION_FORCE_KILLED_MARKER, REPOSITORY_INITIALIZATION_REQUIRED_MARKER, INTERACTIVE_SESSION_STARTED_MARKER, INTERACTIVE_SESSION_ENDED_MARKER, NOW_WORKING_SESSION_IS_ENDED_MARKER, SOLUTION_DRAFT_FAILED_MARKER, SOLUTION_DRAFT_FINISHED_WITH_ERRORS_MARKER, USAGE_LIMIT_REACHED_MARKER, WORKING_SESSION_SUMMARY_MARKER];
110
+ export const TOOL_GENERATED_COMMENT_MARKERS = [AI_WORK_SESSION_STARTED_MARKER, AI_WORK_SESSION_COMPLETED_MARKER, AI_WORK_SESSION_RESUMED_MARKER, AUTO_RESUME_ON_LIMIT_RESET_MARKER, AUTO_RESTART_ON_LIMIT_RESET_MARKER, SOLUTION_DRAFT_LOG_MARKER, AUTO_RESTART_MARKER, AUTO_RESTART_UNTIL_MERGEABLE_LOG_MARKER, READY_TO_MERGE_MARKER, AUTO_MERGED_MARKER, BILLING_LIMIT_MARKER, CANCELLED_CI_REVIEW_MARKER, MAINTAINER_ACCESS_REQUEST_MARKER, LIVE_PROGRESS_SECTION_START_MARKER, SESSION_FORCE_KILLED_MARKER, REPOSITORY_INITIALIZATION_REQUIRED_MARKER, INTERACTIVE_SESSION_STARTED_MARKER, INTERACTIVE_SESSION_ENDED_MARKER, NOW_WORKING_SESSION_IS_ENDED_MARKER, SOLUTION_DRAFT_FAILED_MARKER, SOLUTION_DRAFT_FINISHED_WITH_ERRORS_MARKER, USAGE_LIMIT_REACHED_MARKER, WORKING_SESSION_SUMMARY_AUTOMATION_MARKER];
106
111
 
107
112
  /**
108
113
  * Markers that indicate the end of a working session. Used by
@@ -119,9 +124,14 @@ export const SESSION_ENDING_MARKERS = [NOW_WORKING_SESSION_IS_ENDED_MARKER, AI_W
119
124
  * @param {string} body - The comment body
120
125
  * @returns {boolean} - True if the body contains a tool-generated marker
121
126
  */
127
+ export const isAutomatedWorkingSessionSummaryComment = body => {
128
+ if (!body || typeof body !== 'string') return false;
129
+ return body.includes(WORKING_SESSION_SUMMARY_AUTOMATION_MARKER) || (body.includes(`## ${WORKING_SESSION_SUMMARY_MARKER}`) && body.includes(WORKING_SESSION_SUMMARY_AUTOMATED_FOOTER));
130
+ };
131
+
122
132
  export const isToolGeneratedComment = body => {
123
133
  if (!body || typeof body !== 'string') return false;
124
- return TOOL_GENERATED_COMMENT_MARKERS.some(marker => body.includes(marker));
134
+ return isAutomatedWorkingSessionSummaryComment(body) || TOOL_GENERATED_COMMENT_MARKERS.some(marker => body.includes(marker));
125
135
  };
126
136
 
127
137
  // ----------------------------------------------------------------------------
@@ -394,10 +394,14 @@ export function formatResetTimeWithRelative(resetTime, timezone = null) {
394
394
  * @param {string} options.tool - Tool name (claude, codex, opencode, agent, gemini)
395
395
  * @param {string|null} options.resetTime - Time when limit resets
396
396
  * @param {string|null} options.sessionId - Session ID for resuming
397
- * @param {string|null} options.resumeCommand - Command to resume session
397
+ * @param {string|null} options.resumeCommand - Interactive resume command (legacy, for backward compatibility)
398
+ * @param {string|null} options.interactiveResumeCommand - Command to resume in interactive mode (claude)
399
+ * @param {string|null} options.autonomousResumeCommand - Command to resume in autonomous mode (claude)
400
+ * @param {string|null} options.solveResumeCommand - Command to resume the full solve.mjs flow
401
+ * (works for every supported tool, including codex/gemini/etc.)
398
402
  * @returns {string[]} - Array of formatted message lines
399
403
  */
400
- export function formatUsageLimitMessage({ tool, resetTime, sessionId, resumeCommand }) {
404
+ export function formatUsageLimitMessage({ tool, resetTime, sessionId, resumeCommand, interactiveResumeCommand, autonomousResumeCommand, solveResumeCommand }) {
401
405
  const lines = ['', '⏳ Usage Limit Reached!', '', `Your ${tool || 'AI tool'} usage limit has been reached.`];
402
406
 
403
407
  if (resetTime) {
@@ -406,12 +410,29 @@ export function formatUsageLimitMessage({ tool, resetTime, sessionId, resumeComm
406
410
  lines.push('Please wait for the limit to reset.');
407
411
  }
408
412
 
409
- if (sessionId && resumeCommand) {
413
+ // Support both new (dual command) and legacy (single command) formats
414
+ const interactiveCmd = interactiveResumeCommand || resumeCommand;
415
+
416
+ if (sessionId && (interactiveCmd || autonomousResumeCommand || solveResumeCommand)) {
410
417
  lines.push('');
411
418
  lines.push(`📌 Session ID: ${sessionId}`);
412
419
  lines.push('');
413
- lines.push('To resume this session after the limit resets, run:');
414
- lines.push(` ${resumeCommand}`);
420
+ lines.push('To resume this session after the limit resets:');
421
+ if (interactiveCmd) {
422
+ lines.push('');
423
+ lines.push(' Interactive mode (opens Claude Code for user interaction):');
424
+ lines.push(` ${interactiveCmd}`);
425
+ }
426
+ if (autonomousResumeCommand) {
427
+ lines.push('');
428
+ lines.push(' Autonomous mode (continues work without user interaction):');
429
+ lines.push(` ${autonomousResumeCommand}`);
430
+ }
431
+ if (solveResumeCommand) {
432
+ lines.push('');
433
+ lines.push(' Solve resume mode (re-enters the full solve flow, preserves tool/model/dir):');
434
+ lines.push(` ${solveResumeCommand}`);
435
+ }
415
436
  }
416
437
 
417
438
  lines.push('');
package/CLAUDE.md DELETED
@@ -1,5 +0,0 @@
1
- Issue to solve: undefined
2
- Your prepared branch: issue-644-cf3b8243
3
- Your prepared working directory: /tmp/gh-issue-solver-1762010397765
4
-
5
- Proceed.