@link-assistant/hive-mind 1.71.1 → 1.72.1
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 +30 -0
- package/package.json +1 -1
- package/src/claude.command-builder.lib.mjs +38 -0
- package/src/claude.lib.mjs +20 -19
- package/src/claude.prompts.lib.mjs +9 -0
- package/src/codex.lib.mjs +8 -2
- package/src/gemini.lib.mjs +5 -1
- package/src/solve.config.lib.mjs +5 -0
- package/src/solve.minimal-restart-prompt.lib.mjs +90 -0
- package/src/solve.mjs +33 -27
- package/src/solve.results.lib.mjs +21 -53
- package/src/solve.resume-command.lib.mjs +31 -0
- package/src/solve.watch.lib.mjs +38 -2
- package/src/usage-limit.lib.mjs +26 -5
- package/CLAUDE.md +0 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.72.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 249646e: Fix: Move Claude CLI resume command from GitHub comment to logs
|
|
8
|
+
|
|
9
|
+
When usage limit is reached, the GitHub comment now only mentions the
|
|
10
|
+
`--auto-continue-on-limit-reset` option instead of showing bash commands.
|
|
11
|
+
This is more user-friendly for Telegram bot users who don't use CLI commands directly.
|
|
12
|
+
|
|
13
|
+
The Claude CLI resume command is still available in the logs (in the collapsed
|
|
14
|
+
block or gist link), allowing advanced users to resume manually if needed:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
(cd "/tmp/gh-issue-solver-..." && claude --resume session-id)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Changes:
|
|
21
|
+
- GitHub comments now only suggest using the `--auto-continue-on-limit-reset` option
|
|
22
|
+
- Resume commands are kept in logs only (not in the visible comment)
|
|
23
|
+
- Session ID is still shown for reference
|
|
24
|
+
|
|
25
|
+
Fixes #942
|
|
26
|
+
|
|
27
|
+
## 1.72.0
|
|
28
|
+
|
|
29
|
+
### Minor Changes
|
|
30
|
+
|
|
31
|
+
- fffdfbf: Add experimental `--resume-on-auto-restart` support for resuming Claude auto-restart sessions with a minimal uncommitted-change prompt.
|
|
32
|
+
|
|
3
33
|
## 1.71.1
|
|
4
34
|
|
|
5
35
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -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
|
};
|
package/src/claude.lib.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
35
|
-
await log(
|
|
36
|
-
await log(` ${
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
|
1258
|
-
await
|
|
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,
|
|
@@ -17,6 +17,11 @@ import { buildWorkLanguageDirective } from './work-language.prompts.lib.mjs';
|
|
|
17
17
|
export const buildUserPrompt = params => {
|
|
18
18
|
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, owner, repo, argv, contributingGuidelines, claudeVersion } = params;
|
|
19
19
|
|
|
20
|
+
if (argv?.minimalRestartContext && argv.resume) {
|
|
21
|
+
const lines = feedbackLines && feedbackLines.length > 0 ? feedbackLines : ['Continue the auto-restart from the previous resumed session.'];
|
|
22
|
+
return `${lines.join('\n')}\n`;
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
const promptLines = [];
|
|
21
26
|
|
|
22
27
|
// Issue or PR reference
|
|
@@ -87,6 +92,10 @@ export const buildUserPrompt = params => {
|
|
|
87
92
|
export const buildSystemPrompt = params => {
|
|
88
93
|
const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv, modelSupportsVision, forkedRepo } = params;
|
|
89
94
|
|
|
95
|
+
if (argv?.minimalRestartContext && argv.resume) {
|
|
96
|
+
return '';
|
|
97
|
+
}
|
|
98
|
+
|
|
90
99
|
// When in fork mode, screenshots are pushed to the fork, not the original repo
|
|
91
100
|
const screenshotRepoPath = argv?.fork && forkedRepo ? forkedRepo : `${owner}/${repo}`;
|
|
92
101
|
|
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
|
-
|
|
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
|
-
|
|
1109
|
+
solveResumeCommand: solveResumeCmd,
|
|
1104
1110
|
});
|
|
1105
1111
|
|
|
1106
1112
|
for (const line of messageLines) {
|
package/src/gemini.lib.mjs
CHANGED
|
@@ -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
|
-
|
|
550
|
+
solveResumeCommand: solveResumeCmd,
|
|
547
551
|
});
|
|
548
552
|
|
|
549
553
|
for (const line of messageLines) {
|
package/src/solve.config.lib.mjs
CHANGED
|
@@ -187,6 +187,11 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
187
187
|
description: 'Maximum number of auto-restart iterations before stopping (default: 5, 0 = unlimited)',
|
|
188
188
|
default: 5,
|
|
189
189
|
},
|
|
190
|
+
'resume-on-auto-restart': {
|
|
191
|
+
type: 'boolean',
|
|
192
|
+
description: '[EXPERIMENTAL] Resume the previous Claude session on uncommitted-change auto-restart and send only a minimal restart prompt. Disabled by default.',
|
|
193
|
+
default: false,
|
|
194
|
+
},
|
|
190
195
|
'auto-resume-max-iterations': {
|
|
191
196
|
type: 'number',
|
|
192
197
|
description: 'Maximum number of automatic resume/restart continuations after usage-limit resets (default: 5, 0 = unlimited)',
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generate minimal prompt for auto-restart with session resume
|
|
5
|
+
* This module provides functions to create lightweight prompts for auto-restart
|
|
6
|
+
* that assume the AI has full context from the previous session
|
|
7
|
+
*
|
|
8
|
+
* Part of the cost optimization feature for issue #661
|
|
9
|
+
* @see case-studies/issue-661-session-resume-cost-optimization/
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Note: This module does not import $ directly
|
|
13
|
+
// Functions receive $ as a parameter from the calling module
|
|
14
|
+
// This ensures consistent command executor usage across the codebase
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generate minimal prompt for auto-restart with session resume
|
|
18
|
+
* This prompt assumes the AI has full context from the previous session
|
|
19
|
+
* Target: ~500 tokens (compared to 50k-200k in full context)
|
|
20
|
+
*
|
|
21
|
+
* @param {string} tempDir - Working directory
|
|
22
|
+
* @param {object} $ - Command executor
|
|
23
|
+
* @returns {Promise<string>} Minimal restart prompt
|
|
24
|
+
*/
|
|
25
|
+
export const generateMinimalRestartPrompt = async (tempDir, $) => {
|
|
26
|
+
// Get uncommitted changes
|
|
27
|
+
const gitStatus = await $({ cwd: tempDir })`git status --porcelain`;
|
|
28
|
+
const uncommittedFiles = gitStatus.stdout.toString().trim();
|
|
29
|
+
|
|
30
|
+
// Get brief diff summaries (not full diffs to keep the prompt minimal)
|
|
31
|
+
const gitDiffStat = await $({ cwd: tempDir })`git diff --stat`;
|
|
32
|
+
const unstagedDiffSummary = gitDiffStat.stdout.toString().trim();
|
|
33
|
+
const gitCachedDiffStat = await $({ cwd: tempDir })`git diff --cached --stat`;
|
|
34
|
+
const stagedDiffSummary = gitCachedDiffStat.stdout.toString().trim();
|
|
35
|
+
const summarySections = [];
|
|
36
|
+
if (unstagedDiffSummary) summarySections.push(`Unstaged changes:\n${unstagedDiffSummary}`);
|
|
37
|
+
if (stagedDiffSummary) summarySections.push(`Staged changes:\n${stagedDiffSummary}`);
|
|
38
|
+
const diffSummary = summarySections.join('\n\n') || 'No tracked-file diff summary available.';
|
|
39
|
+
|
|
40
|
+
// Count changes
|
|
41
|
+
const fileCount = uncommittedFiles.split('\n').filter(line => line.trim()).length;
|
|
42
|
+
|
|
43
|
+
return `🔄 Auto-restart: resume the previous session and handle its uncommitted changes.
|
|
44
|
+
|
|
45
|
+
Uncommitted files (${fileCount}):
|
|
46
|
+
${uncommittedFiles}
|
|
47
|
+
|
|
48
|
+
Changes summary:
|
|
49
|
+
${diffSummary}
|
|
50
|
+
|
|
51
|
+
Please review these changes and commit them with an appropriate commit message.
|
|
52
|
+
Follow the repository's commit message conventions from previous commits.`;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Generate full context prompt (fallback when resume fails or not enabled)
|
|
57
|
+
* This is used when session resume is not available or failed
|
|
58
|
+
*
|
|
59
|
+
* @param {string} issueUrl - Issue URL
|
|
60
|
+
* @param {string} issueBody - Issue description
|
|
61
|
+
* @param {number} prNumber - PR number
|
|
62
|
+
* @param {Array<string>} feedbackLines - Feedback from reviewers
|
|
63
|
+
* @param {string} tempDir - Working directory
|
|
64
|
+
* @param {object} $ - Command executor
|
|
65
|
+
* @returns {Promise<string>} Full restart prompt
|
|
66
|
+
*/
|
|
67
|
+
export const generateFullRestartPrompt = async (issueUrl, issueBody, prNumber, feedbackLines, tempDir, $) => {
|
|
68
|
+
// Get uncommitted changes with full diff
|
|
69
|
+
const gitStatus = await $({ cwd: tempDir })`git status --porcelain`;
|
|
70
|
+
const uncommittedFiles = gitStatus.stdout.toString().trim();
|
|
71
|
+
|
|
72
|
+
const gitDiff = await $({ cwd: tempDir })`git diff`;
|
|
73
|
+
const fullDiff = gitDiff.stdout.toString();
|
|
74
|
+
|
|
75
|
+
let prompt = `
|
|
76
|
+
Continuing work on issue: ${issueUrl}
|
|
77
|
+
|
|
78
|
+
Previous session completed but left uncommitted changes.
|
|
79
|
+
`.trim();
|
|
80
|
+
|
|
81
|
+
if (feedbackLines && feedbackLines.length > 0) {
|
|
82
|
+
prompt += `\n\nFeedback from reviewers:\n${feedbackLines.join('\n')}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
prompt += `\n\nUncommitted changes:\n${uncommittedFiles}\n\nFull diff:\n${fullDiff}`;
|
|
86
|
+
|
|
87
|
+
prompt += '\n\nPlease review these changes and commit them appropriately.';
|
|
88
|
+
|
|
89
|
+
return prompt;
|
|
90
|
+
};
|
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');
|
|
@@ -826,6 +826,13 @@ try {
|
|
|
826
826
|
limitReached = toolResult.limitReached;
|
|
827
827
|
cleanupContext.limitReached = limitReached;
|
|
828
828
|
|
|
829
|
+
if (sessionId && (argv.resumeOnAutoRestart || argv['resume-on-auto-restart'])) {
|
|
830
|
+
global.previousSessionId = sessionId;
|
|
831
|
+
if (argv.verbose) {
|
|
832
|
+
await log(`Session ID stored for auto-restart resume: ${sessionId}`, { verbose: true });
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
829
836
|
// Capture limit reset time and timezone globally for downstream handlers (auto-continue, cleanup decisions)
|
|
830
837
|
if (toolResult && toolResult.limitResetTime) {
|
|
831
838
|
global.limitResetTime = toolResult.limitResetTime;
|
|
@@ -861,16 +868,14 @@ try {
|
|
|
861
868
|
await log(`⏰ Limit resets at: ${formattedResetTime}`);
|
|
862
869
|
}
|
|
863
870
|
await log('');
|
|
864
|
-
// Show
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
await log('💡 To continue this session in Claude Code interactive mode:');
|
|
870
|
-
await log('');
|
|
871
|
-
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 })}`);
|
|
872
876
|
await log('');
|
|
873
877
|
} else if (argv.url) {
|
|
878
|
+
const toolForResume = argv.tool || 'claude';
|
|
874
879
|
const solveResumeCmd = buildSolveResumeCommand({ issueUrl: argv.url, sessionId, tool: toolForResume, model: argv.model, fallbackModel: argv.fallbackModel, tempDir });
|
|
875
880
|
await log(`💡 To continue this ${toolForResume} session with solve:`);
|
|
876
881
|
await log('');
|
|
@@ -919,13 +924,13 @@ try {
|
|
|
919
924
|
await log(` ⚠️ Error uploading logs: ${uploadError.message}`);
|
|
920
925
|
}
|
|
921
926
|
} else if (prNumber) {
|
|
922
|
-
// Fallback: Post simple failure comment
|
|
927
|
+
// Fallback: Post simple failure comment (no CLI commands in GitHub comments, only mention option)
|
|
923
928
|
try {
|
|
924
929
|
const resetTime = global.limitResetTime;
|
|
925
|
-
//
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
const resumeSection =
|
|
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.';
|
|
929
934
|
// Format the reset time with relative time and UTC conversion if available
|
|
930
935
|
const timezone = global.limitTimezone || null;
|
|
931
936
|
const formattedResetTime = resetTime ? formatResetTimeWithRelative(resetTime, timezone) : null;
|
|
@@ -1032,21 +1037,22 @@ try {
|
|
|
1032
1037
|
// Skip failure exit if limit reached with auto-resume (continues to showSessionSummary/autoContinueWhenLimitResets)
|
|
1033
1038
|
const shouldSkipFailureExitForAutoLimitContinue = limitReached && argv.autoResumeOnLimitReset;
|
|
1034
1039
|
if (!success && !shouldSkipFailureExitForAutoLimitContinue) {
|
|
1035
|
-
//
|
|
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
|
|
1036
1044
|
const toolForFailure = argv.tool || 'claude';
|
|
1037
|
-
if (sessionId
|
|
1038
|
-
const claudeResumeCmd = buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model });
|
|
1045
|
+
if (sessionId) {
|
|
1039
1046
|
await log('');
|
|
1040
|
-
await log('💡 To continue this session
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
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
|
+
}
|
|
1050
1056
|
await log('');
|
|
1051
1057
|
}
|
|
1052
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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!');
|
|
@@ -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
|
+
};
|
package/src/solve.watch.lib.mjs
CHANGED
|
@@ -290,6 +290,38 @@ export const watchForFeedback = async params => {
|
|
|
290
290
|
// to comments posted during *this* iteration only, not across the whole watch loop.
|
|
291
291
|
const iterationStartTime = new Date();
|
|
292
292
|
|
|
293
|
+
let restartFeedbackLines = feedbackLines;
|
|
294
|
+
let restartArgv = argv;
|
|
295
|
+
const shouldUseSessionResume = Boolean(isTemporaryWatch && (firstIterationInTemporaryMode || hasUncommittedInTempMode) && (argv.resumeOnAutoRestart || argv['resume-on-auto-restart']) && (argv.tool === 'claude' || !argv.tool) && global.previousSessionId);
|
|
296
|
+
|
|
297
|
+
if (shouldUseSessionResume) {
|
|
298
|
+
await log(formatAligned('', 'Experimental session resume: using minimal auto-restart prompt', '', 2));
|
|
299
|
+
await log(formatAligned('', `Resuming session: ${global.previousSessionId}`, '', 2));
|
|
300
|
+
|
|
301
|
+
if (argv.verbose) {
|
|
302
|
+
try {
|
|
303
|
+
const { calculateSessionTokens } = await import('./claude.lib.mjs');
|
|
304
|
+
const tokenUsage = await calculateSessionTokens(global.previousSessionId, tempDir);
|
|
305
|
+
if (tokenUsage?.totalTokens) {
|
|
306
|
+
await log(formatAligned('', `Previous session tokens: ${tokenUsage.totalTokens.toLocaleString()}`, '', 2));
|
|
307
|
+
}
|
|
308
|
+
} catch {
|
|
309
|
+
await log(formatAligned('', 'Could not read previous session token usage', '', 2));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const { generateMinimalRestartPrompt } = await import('./solve.minimal-restart-prompt.lib.mjs');
|
|
314
|
+
const minimalPrompt = await generateMinimalRestartPrompt(tempDir, $);
|
|
315
|
+
restartFeedbackLines = [minimalPrompt];
|
|
316
|
+
restartArgv = {
|
|
317
|
+
...argv,
|
|
318
|
+
resume: global.previousSessionId,
|
|
319
|
+
minimalRestartContext: true,
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
await log(formatAligned('', `Minimal restart prompt size: ${minimalPrompt.length} characters`, '', 2));
|
|
323
|
+
}
|
|
324
|
+
|
|
293
325
|
// Execute tool using shared utility
|
|
294
326
|
const toolResult = await executeToolIteration({
|
|
295
327
|
issueUrl,
|
|
@@ -300,10 +332,14 @@ export const watchForFeedback = async params => {
|
|
|
300
332
|
branchName: prBranch || branchName,
|
|
301
333
|
tempDir,
|
|
302
334
|
mergeStateStatus,
|
|
303
|
-
feedbackLines,
|
|
304
|
-
argv,
|
|
335
|
+
feedbackLines: restartFeedbackLines,
|
|
336
|
+
argv: restartArgv,
|
|
305
337
|
});
|
|
306
338
|
|
|
339
|
+
if (toolResult.sessionId && (argv.resumeOnAutoRestart || argv['resume-on-auto-restart'])) {
|
|
340
|
+
global.previousSessionId = toolResult.sessionId;
|
|
341
|
+
}
|
|
342
|
+
|
|
307
343
|
if (!toolResult.success) {
|
|
308
344
|
// Check if this is an API error using shared utility
|
|
309
345
|
if (isApiError(toolResult)) {
|
package/src/usage-limit.lib.mjs
CHANGED
|
@@ -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 -
|
|
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
|
-
|
|
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
|
|
414
|
-
|
|
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('');
|