@link-assistant/hive-mind 2.11.13 → 2.12.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 +18 -0
- package/package.json +4 -1
- package/src/agent-command.lib.mjs +74 -0
- package/src/agent.lib.mjs +59 -34
- package/src/agentic-cli-updater.lib.mjs +241 -0
- package/src/claude.connection.lib.mjs +209 -0
- package/src/claude.lib.mjs +6 -202
- package/src/codex.lib.mjs +0 -128
- package/src/formal-ai-isolation.lib.mjs +62 -0
- package/src/formal-ai-maintenance.lib.mjs +106 -0
- package/src/formal-ai-model.lib.mjs +25 -0
- package/src/formal-ai-runtime.lib.mjs +10 -0
- package/src/formal-ai-sidecar.lib.mjs +565 -0
- package/src/formal-ai-updater.lib.mjs +294 -0
- package/src/formal-ai-version.lib.mjs +100 -0
- package/src/formal-ai.lib.mjs +11 -16
- package/src/github-rate-limit.lib.mjs +3 -0
- package/src/github-url-parser.lib.mjs +255 -0
- package/src/github.lib.mjs +22 -343
- package/src/hive.mjs +0 -152
- package/src/interactive-mode.lib.mjs +0 -43
- package/src/isolation-runner.lib.mjs +44 -173
- package/src/limits.lib.mjs +0 -89
- package/src/model-args.lib.mjs +32 -0
- package/src/models/index.mjs +5 -19
- package/src/session-monitor.lib.mjs +14 -172
- package/src/solve.auto-merge.lib.mjs +70 -164
- package/src/solve.mjs +31 -193
- package/src/solve.repository.lib.mjs +0 -83
- package/src/solve.results.lib.mjs +2 -92
- package/src/solve.session.lib.mjs +52 -19
- package/src/solve.tool-uncommitted.lib.mjs +22 -0
- package/src/state-lock.lib.mjs +82 -0
- package/src/telegram-bot.mjs +17 -65
- package/src/telegram-fix-command.lib.mjs +1 -8
- package/src/telegram-merge-queue.lib.mjs +3 -155
- package/src/telegram-solve-queue.lib.mjs +9 -168
- package/src/telegram-task-command.lib.mjs +1 -8
- package/src/use-m-bootstrap.lib.mjs +6 -5
- package/src/use-with-retry.lib.mjs +128 -2
- package/src/working-session-summary.lib.mjs +47 -1
package/src/solve.mjs
CHANGED
|
@@ -5,7 +5,6 @@ import { ensureUseM } from './use-m-bootstrap.lib.mjs';
|
|
|
5
5
|
const earlyArgs = process.argv.slice(2);
|
|
6
6
|
const { handleSolveEarlyExit } = await import('./solve.bootstrap.lib.mjs');
|
|
7
7
|
await handleSolveEarlyExit(earlyArgs);
|
|
8
|
-
|
|
9
8
|
const use = (globalThis.use = await ensureUseM());
|
|
10
9
|
const { $: __rawDollar$ } = await use('command-stream');
|
|
11
10
|
const { configureGitHubRateLimitLogging, wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
|
|
@@ -63,18 +62,16 @@ const { startWorkSession, endWorkSession, SESSION_TYPES } = await import('./solv
|
|
|
63
62
|
const { attachFinalLogIfMissing } = await import('./attach-logs-guarantee.lib.mjs'); // Issue #1952
|
|
64
63
|
const { collectAndCommitDevelopmentLogArtifacts, fetchIssueType, isDevelopmentLogEnabled, isIssueTypeAwarePromptEnabled } = await import('./development-log.lib.mjs');
|
|
65
64
|
const { createDevelopmentLogFinalizer } = await import('./development-log.finalize.lib.mjs');
|
|
66
|
-
// Issue #1625: centralized markers + tracked comment posting for solve.mjs's
|
|
67
|
-
// own usage-limit notifications (so they're excluded from the
|
|
68
|
-
// "did the AI post anything?" check in --auto-attach-solution-summary).
|
|
65
|
+
// Issue #1625: centralized markers + tracked comment posting for solve.mjs's own usage-limit notifications (so they're excluded from the "did the AI post anything?" check in --auto-attach-solution-summary).
|
|
69
66
|
const { postTrackedComment, USAGE_LIMIT_REACHED_MARKER } = await import('./tool-comments.lib.mjs');
|
|
70
67
|
const { prepareFeedbackAndTimestamps, checkUncommittedChanges, checkForkActions } = await import('./solve.preparation.lib.mjs');
|
|
71
68
|
const { validateAndExitOnInvalidClaudeSubAgentModel, validateAndExitOnInvalidModel } = await import('./models/index.mjs');
|
|
72
69
|
const { autoAcceptInviteForRepo } = await import('./solve.accept-invite.lib.mjs');
|
|
73
70
|
const { handleAutoForkOption, handleMaintainerForkAccess } = await import('./solve.fork-detection.lib.mjs');
|
|
71
|
+
const { resolveUncommittedChangesTool } = await import('./solve.tool-uncommitted.lib.mjs');
|
|
74
72
|
const logFile = await initializeLogFile(null);
|
|
75
73
|
const versionInfo = await getVersionInfo();
|
|
76
74
|
const rawCommand = await logSolveStartup(versionInfo);
|
|
77
|
-
|
|
78
75
|
let finalResourceSnapshotRecorded = false;
|
|
79
76
|
const safeExit = async (code = 0, reason = 'Process completed', options = {}) => {
|
|
80
77
|
if (!finalResourceSnapshotRecorded) {
|
|
@@ -83,7 +80,6 @@ const safeExit = async (code = 0, reason = 'Process completed', options = {}) =>
|
|
|
83
80
|
}
|
|
84
81
|
return await baseSafeExit(code, reason, options);
|
|
85
82
|
};
|
|
86
|
-
|
|
87
83
|
let argv;
|
|
88
84
|
try {
|
|
89
85
|
argv = await parseArguments(yargs, hideBin);
|
|
@@ -95,7 +91,6 @@ try {
|
|
|
95
91
|
await safeExit(1, 'Invalid command-line arguments');
|
|
96
92
|
}
|
|
97
93
|
global.verboseMode = argv.verbose;
|
|
98
|
-
|
|
99
94
|
setupVerboseLogInterceptor(); // Issue #1466: capture [VERBOSE] output in log files
|
|
100
95
|
setupStdioLogInterceptor(); // Issue #1549: capture ALL terminal output in log file
|
|
101
96
|
configureGitHubRateLimitLogging({
|
|
@@ -103,33 +98,8 @@ configureGitHubRateLimitLogging({
|
|
|
103
98
|
log,
|
|
104
99
|
});
|
|
105
100
|
await recordResourceSnapshot({ phase: RESOURCE_PHASE_SOLVE_START, log, diskPath: '/', label: 'solve start', logExecutionContext: true }); // #2001: detect+report container context
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
// Conditionally import tool-specific functions after argv is parsed
|
|
109
|
-
// If --use-agent-commander is enabled, use agent-commander's checkForUncommittedChanges
|
|
110
|
-
let checkForUncommittedChanges;
|
|
111
|
-
let agentCommanderLib = null;
|
|
112
|
-
if (argv.useAgentCommander) {
|
|
113
|
-
agentCommanderLib = await import('./agent-commander.lib.mjs');
|
|
114
|
-
checkForUncommittedChanges = agentCommanderLib.checkForUncommittedChanges;
|
|
115
|
-
} else if (argv.tool === 'opencode') {
|
|
116
|
-
const opencodeLib = await import('./opencode.lib.mjs');
|
|
117
|
-
checkForUncommittedChanges = opencodeLib.checkForUncommittedChanges;
|
|
118
|
-
} else if (argv.tool === 'gemini') {
|
|
119
|
-
const geminiLib = await import('./gemini.lib.mjs');
|
|
120
|
-
checkForUncommittedChanges = geminiLib.checkForUncommittedChanges;
|
|
121
|
-
} else if (argv.tool === 'codex') {
|
|
122
|
-
const codexLib = await import('./codex.lib.mjs');
|
|
123
|
-
checkForUncommittedChanges = codexLib.checkForUncommittedChanges;
|
|
124
|
-
} else if (argv.tool === 'agent') {
|
|
125
|
-
const agentLib = await import('./agent.lib.mjs');
|
|
126
|
-
checkForUncommittedChanges = agentLib.checkForUncommittedChanges;
|
|
127
|
-
} else if (argv.tool === 'qwen') {
|
|
128
|
-
const qwenLib = await import('./qwen.lib.mjs');
|
|
129
|
-
checkForUncommittedChanges = qwenLib.checkForUncommittedChanges;
|
|
130
|
-
} else {
|
|
131
|
-
checkForUncommittedChanges = claudeLib.checkForUncommittedChanges;
|
|
132
|
-
}
|
|
101
|
+
// Early logs go to cwd; custom log dir takes effect after argv is parsed.
|
|
102
|
+
let { checkForUncommittedChanges, agentCommanderLib } = await resolveUncommittedChangesTool({ argv, claudeLib });
|
|
133
103
|
const shouldAttachLogs = argv.attachLogs || argv['attach-logs'];
|
|
134
104
|
await showAttachLogsWarning(shouldAttachLogs);
|
|
135
105
|
const absoluteLogPath = path.resolve(logFile);
|
|
@@ -161,10 +131,7 @@ const cleanupWrapper = async () => {
|
|
|
161
131
|
const interruptWrapper = createInterruptWrapper({ cleanupContext, checkForUncommittedChanges, shouldAttachLogs, attachLogToGitHub, getLogFile, sanitizeLogContent, $, log });
|
|
162
132
|
initializeExitHandler(getAbsoluteLogPath, log, cleanupWrapper, interruptWrapper, ({ code, reason, failureActionSection }) => notifyIssueAboutPrePullRequestFailure({ code, reason, failureActionSection, argv, globalState: global, $, log, getLogFile, shouldAttachLogs, attachLogToGitHub, sanitizeLogContent, rawCommand }));
|
|
163
133
|
installGlobalExitHandlers({ handleProcessErrors: false }); // #2117: solve's richer process-error handlers below must not race a duplicate pair.
|
|
164
|
-
// Issue #1823: Configure the working-session guard. When the experimental
|
|
165
|
-
// --do-not-shutdown-in-the-middle-of-working-session flag is set (hive passes it to every
|
|
166
|
-
// worker), an interrupt received during an AI working session is deferred: solve lets the AI
|
|
167
|
-
// finish, auto-commits, then shuts down gracefully instead of aborting the AI tool mid-run.
|
|
134
|
+
// Issue #1823: Configure the working-session guard. When the experimental --do-not-shutdown-in-the-middle-of-working-session flag is set (hive passes it to every worker), an interrupt received during an AI working session is deferred: solve lets the AI finish, auto-commits, then shuts down gracefully instead of aborting the AI tool mid-run.
|
|
168
135
|
configureWorkingSession({ enabled: argv['do-not-shutdown-in-the-middle-of-working-session'] === true, log });
|
|
169
136
|
const markFailureNotificationPosted = targetType => {
|
|
170
137
|
global.preExitFailureNotificationPosted = true;
|
|
@@ -174,7 +141,6 @@ const markFailureNotificationPosted = targetType => {
|
|
|
174
141
|
global.prePullRequestFailureNotificationPosted = true;
|
|
175
142
|
}
|
|
176
143
|
};
|
|
177
|
-
|
|
178
144
|
// Now handle argument validation that was moved from early checks
|
|
179
145
|
let issueUrl = argv['issue-url'] || argv._[0];
|
|
180
146
|
if (!issueUrl) {
|
|
@@ -192,8 +158,7 @@ const { isIssueUrl, isPrUrl, normalizedUrl, owner, repo, number: urlNumber } = u
|
|
|
192
158
|
issueUrl = normalizedUrl || issueUrl;
|
|
193
159
|
global.owner = owner;
|
|
194
160
|
global.repo = repo;
|
|
195
|
-
// Issue #1752: record the source issue as soon as the URL is validated so the pre-exit
|
|
196
|
-
// notifier can still comment on it if a check fails before normal issue-mode setup below.
|
|
161
|
+
// Issue #1752: record the source issue as soon as the URL is validated so the pre-exit notifier can still comment on it if a check fails before normal issue-mode setup below.
|
|
197
162
|
if (isIssueUrl) {
|
|
198
163
|
global.issueNumber = urlNumber;
|
|
199
164
|
}
|
|
@@ -235,13 +200,11 @@ if (!(await validateUrlRequirement(issueUrl))) {
|
|
|
235
200
|
if (!(await validateContinueOnlyOnFeedback(argv, isPrUrl, isIssueUrl))) {
|
|
236
201
|
await safeExit(1, 'Feedback validation failed');
|
|
237
202
|
}
|
|
238
|
-
|
|
239
203
|
// Validate model name EARLY - always runs regardless of --skip-tool-connection-check
|
|
240
204
|
const tool = argv.tool || 'claude';
|
|
241
205
|
await validateAndExitOnInvalidModel(argv.model, tool, safeExit);
|
|
242
206
|
if (argv.fallbackModel) await validateAndExitOnInvalidModel(argv.fallbackModel, tool, safeExit);
|
|
243
207
|
argv.originalModel ||= argv.model;
|
|
244
|
-
|
|
245
208
|
// Validate --plan-model if provided (Issue #1223)
|
|
246
209
|
if (argv.planModel) {
|
|
247
210
|
if (tool !== 'claude') {
|
|
@@ -251,7 +214,6 @@ if (argv.planModel) {
|
|
|
251
214
|
await validateAndExitOnInvalidModel(argv.planModel, tool, safeExit);
|
|
252
215
|
}
|
|
253
216
|
if (argv.subAgentModel) await validateAndExitOnInvalidClaudeSubAgentModel(argv.subAgentModel, tool, safeExit);
|
|
254
|
-
|
|
255
217
|
// Perform all system checks (skip tool connection check in dry-run or when --skip-tool-connection-check; model validation always runs)
|
|
256
218
|
const prepareOnly = argv.dryRun || argv.onlyPrepareCommand;
|
|
257
219
|
const skipToolConnectionCheck = prepareOnly || argv.skipToolConnectionCheck || argv.toolConnectionCheck === false;
|
|
@@ -277,8 +239,7 @@ if (argv.verbose) {
|
|
|
277
239
|
await log(` Is PR URL: ${!!isPrUrl}`, { verbose: true });
|
|
278
240
|
}
|
|
279
241
|
const claudePath = argv.executeToolWithBun ? 'bunx claude' : process.env.CLAUDE_PATH || 'claude';
|
|
280
|
-
// Note: owner, repo, and urlNumber are extracted from validateGitHubUrl() above
|
|
281
|
-
// Accept pending invitation BEFORE any access checks (auto-fork, permissions, entity validation)
|
|
242
|
+
// Note: owner, repo, and urlNumber are extracted from validateGitHubUrl() above Accept pending invitation BEFORE any access checks (auto-fork, permissions, entity validation)
|
|
282
243
|
if (argv.autoAcceptInvite) {
|
|
283
244
|
await autoAcceptInviteForRepo(owner, repo, log, argv.verbose);
|
|
284
245
|
}
|
|
@@ -290,27 +251,22 @@ const hasWriteAccess = await checkRepositoryWritePermission(owner, repo, {
|
|
|
290
251
|
useFork: argv.fork,
|
|
291
252
|
issueUrl: issueUrl,
|
|
292
253
|
});
|
|
293
|
-
|
|
294
254
|
if (!hasWriteAccess) {
|
|
295
255
|
await log('');
|
|
296
256
|
await log('❌ Cannot proceed without repository write access or --fork option', { level: 'error' });
|
|
297
257
|
await safeExit(1, 'Permission check failed');
|
|
298
258
|
}
|
|
299
|
-
|
|
300
259
|
// Issue #1552: Validate entity existence AFTER permissions (cascade: user/org → repo → issue/PR)
|
|
301
260
|
const entityCheck = await (await import('./github-entity-validation.lib.mjs')).validateGitHubEntityExistence({ owner, repo, number: urlNumber, type: isIssueUrl ? 'issue' : isPrUrl ? 'pull' : undefined, baseBranch: argv.baseBranch, verbose: argv.verbose, autoAcceptInvite: !!argv.autoAcceptInvite });
|
|
302
261
|
if (!entityCheck.valid) {
|
|
303
262
|
await log(`\n❌ ${entityCheck.error}\n`, { level: 'error' });
|
|
304
263
|
await safeExit(1, `GitHub entity not found (${entityCheck.level})`);
|
|
305
264
|
}
|
|
306
|
-
|
|
307
|
-
// Detect repository visibility once and reuse for downstream decisions
|
|
308
|
-
// (auto-cleanup default + Issue #1716 private-repo fork bypass)
|
|
265
|
+
// Detect repository visibility once and reuse for downstream decisions (auto-cleanup default + Issue #1716 private-repo fork bypass)
|
|
309
266
|
const { detectRepositoryVisibility } = githubLib;
|
|
310
267
|
const { isPublic: isRepoPublic } = await detectRepositoryVisibility(owner, repo);
|
|
311
268
|
if (argv.autoCleanup === undefined) {
|
|
312
|
-
// For public repos: keep temp directories (default false)
|
|
313
|
-
// For private repos: clean up temp directories (default true)
|
|
269
|
+
// For public repos: keep temp directories (default false) For private repos: clean up temp directories (default true)
|
|
314
270
|
argv.autoCleanup = !isRepoPublic;
|
|
315
271
|
if (argv.verbose) {
|
|
316
272
|
await log(` Auto-cleanup default: ${argv.autoCleanup} (repository is ${isRepoPublic ? 'public' : 'private'})`, {
|
|
@@ -318,11 +274,7 @@ if (argv.autoCleanup === undefined) {
|
|
|
318
274
|
});
|
|
319
275
|
}
|
|
320
276
|
}
|
|
321
|
-
// Issue #1716: When the upstream repository is private and the user has direct
|
|
322
|
-
// write access, fork-based workflows should be skipped — even if the existing
|
|
323
|
-
// PR was originally created from a fork. Forks of private repositories often
|
|
324
|
-
// become inaccessible (renamed, deleted, parent re-private'd) and there's no
|
|
325
|
-
// reason to use them when we can push branches and PRs to the upstream repo.
|
|
277
|
+
// Issue #1716: When the upstream repository is private and the user has direct write access, fork-based workflows should be skipped — even if the existing PR was originally created from a fork. Forks of private repositories often become inaccessible (renamed, deleted, parent re-private'd) and there's no reason to use them when we can push branches and PRs to the upstream repo.
|
|
326
278
|
const skipForkForPrivateUpstream = !isRepoPublic && !argv.fork && hasWriteAccess;
|
|
327
279
|
// Determine mode and get issue details
|
|
328
280
|
let issueNumber;
|
|
@@ -376,7 +328,6 @@ if (autoContinueResult.isContinueMode) {
|
|
|
376
328
|
await log(' Will clone fork repository for continue mode', { verbose: true });
|
|
377
329
|
}
|
|
378
330
|
}
|
|
379
|
-
|
|
380
331
|
// Check if maintainer can push to the fork when --allow-to-push-to-contributors-pull-requests-as-maintainer is enabled
|
|
381
332
|
if (forkOwner && argv.allowToPushToContributorsPullRequestsAsMaintainer && argv.autoFork) {
|
|
382
333
|
await handleMaintainerForkAccess({ owner, repo, prNumber });
|
|
@@ -451,7 +402,6 @@ if (isPrUrl) {
|
|
|
451
402
|
await log(' Will clone fork repository for continue mode', { verbose: true });
|
|
452
403
|
}
|
|
453
404
|
}
|
|
454
|
-
|
|
455
405
|
// Check if maintainer can push to the fork when --allow-to-push-to-contributors-pull-requests-as-maintainer is enabled
|
|
456
406
|
if (forkOwner && argv.allowToPushToContributorsPullRequestsAsMaintainer && argv.autoFork) {
|
|
457
407
|
await handleMaintainerForkAccess({ owner, repo, prNumber });
|
|
@@ -503,8 +453,7 @@ const finalizeDevelopmentLog = createDevelopmentLogFinalizer({
|
|
|
503
453
|
getParams: () => ({ enabled: isDevelopmentLogEnabled(argv), repositoryPath: tempDir, logFile: getLogFile(), issueNumber, prNumber, tool: argv.tool || 'claude', sessionId, branchName, rawCommand, $, log }), // prettier-ignore
|
|
504
454
|
});
|
|
505
455
|
try {
|
|
506
|
-
// Set up repository and clone using the new module
|
|
507
|
-
// If --working-directory points to existing repo, needsClone is false and we skip cloning
|
|
456
|
+
// Set up repository and clone using the new module If --working-directory points to existing repo, needsClone is false and we skip cloning
|
|
508
457
|
const { forkedRepo } = await setupRepositoryAndClone({
|
|
509
458
|
argv,
|
|
510
459
|
owner,
|
|
@@ -519,12 +468,9 @@ try {
|
|
|
519
468
|
$,
|
|
520
469
|
needsClone,
|
|
521
470
|
});
|
|
522
|
-
|
|
523
471
|
cleanupContext.diskDiagnostics = { beforeBytes: await recordAfterCloneSize({ tempDir, log }) };
|
|
524
472
|
await recordResourceSnapshot({ phase: RESOURCE_PHASE_AFTER_CLONE, log, diskPath: '/', label: 'after repository clone' });
|
|
525
|
-
|
|
526
|
-
// Verify default branch and status using the new module
|
|
527
|
-
// Pass argv, owner, repo, issueUrl for empty repository auto-initialization (--auto-init-repository)
|
|
473
|
+
// Verify default branch and status using the new module Pass argv, owner, repo, issueUrl for empty repository auto-initialization (--auto-init-repository)
|
|
528
474
|
const defaultBranch = await verifyDefaultBranchAndStatus({
|
|
529
475
|
tempDir,
|
|
530
476
|
log,
|
|
@@ -552,7 +498,6 @@ try {
|
|
|
552
498
|
prNumber,
|
|
553
499
|
});
|
|
554
500
|
cleanupContext.branchName = branchName;
|
|
555
|
-
|
|
556
501
|
// Auto-merge default branch to pull request branch if enabled
|
|
557
502
|
let autoMergeFeedbackLines = [];
|
|
558
503
|
if (isContinueMode && argv['auto-merge-default-branch-to-pull-request-branch']) {
|
|
@@ -586,16 +531,13 @@ try {
|
|
|
586
531
|
autoMergeFeedbackLines.push('');
|
|
587
532
|
}
|
|
588
533
|
}
|
|
589
|
-
|
|
590
534
|
// Initialize PR variables early
|
|
591
535
|
let prUrl = null;
|
|
592
|
-
|
|
593
536
|
// In continue mode, we already have the PR details
|
|
594
537
|
if (isContinueMode) {
|
|
595
538
|
prUrl = issueUrl; // The input URL is the PR URL
|
|
596
539
|
// prNumber is already set from earlier when we parsed the PR
|
|
597
540
|
}
|
|
598
|
-
|
|
599
541
|
// Handle auto PR creation using the new module
|
|
600
542
|
const autoPrResult = await handleAutoPrCreation({
|
|
601
543
|
argv,
|
|
@@ -615,7 +557,6 @@ try {
|
|
|
615
557
|
path,
|
|
616
558
|
fs,
|
|
617
559
|
});
|
|
618
|
-
|
|
619
560
|
let claudeCommitHash = null;
|
|
620
561
|
if (autoPrResult) {
|
|
621
562
|
prUrl = autoPrResult.prUrl;
|
|
@@ -627,17 +568,12 @@ try {
|
|
|
627
568
|
}
|
|
628
569
|
}
|
|
629
570
|
if (prNumber) cleanupContext.prNumber = prNumber;
|
|
630
|
-
|
|
631
|
-
// CRITICAL: Validate that we have a PR number when required
|
|
632
|
-
// This prevents continuing without a PR when one was supposed to be created
|
|
571
|
+
// CRITICAL: Validate that we have a PR number when required This prevents continuing without a PR when one was supposed to be created
|
|
633
572
|
if ((isContinueMode || argv.autoPullRequestCreation) && !prNumber) {
|
|
634
573
|
await handleNoPrAvailableError({ isContinueMode, tempDir, issueNumber, issueUrl, owner, repo, log, formatAligned });
|
|
635
574
|
}
|
|
636
|
-
|
|
637
575
|
const enforceRequestedBaseBranch = () => ensurePullRequestBaseBranch({ owner, repo, prNumber, argv, log, formatAligned, $ });
|
|
638
|
-
|
|
639
576
|
await enforceRequestedBaseBranch();
|
|
640
|
-
|
|
641
577
|
if (isContinueMode) {
|
|
642
578
|
await log(`\n${formatAligned('🔄', 'Continue mode:', 'ACTIVE')}`);
|
|
643
579
|
await log(formatAligned('', 'Using existing PR:', `#${prNumber}`, 2));
|
|
@@ -646,10 +582,7 @@ try {
|
|
|
646
582
|
await log(`\n${formatAligned('⏭️', 'Auto PR creation:', 'DISABLED')}`);
|
|
647
583
|
await log(formatAligned('', 'Workflow:', 'AI will create the PR', 2));
|
|
648
584
|
}
|
|
649
|
-
|
|
650
|
-
// Start work session using the new module
|
|
651
|
-
// Determine session type based on command line flags
|
|
652
|
-
// See: https://github.com/link-assistant/hive-mind/issues/1152
|
|
585
|
+
// Start work session using the new module Determine session type based on command line flags See: https://github.com/link-assistant/hive-mind/issues/1152
|
|
653
586
|
let sessionType = SESSION_TYPES.NEW;
|
|
654
587
|
if (argv.sessionType) {
|
|
655
588
|
// Session type was explicitly set (e.g., by auto-resume/auto-restart spawning a new process)
|
|
@@ -667,7 +600,6 @@ try {
|
|
|
667
600
|
$,
|
|
668
601
|
sessionType,
|
|
669
602
|
});
|
|
670
|
-
|
|
671
603
|
// Prepare feedback and timestamps using the new module
|
|
672
604
|
const { feedbackLines: preparedFeedbackLines, referenceTime } = await prepareFeedbackAndTimestamps({
|
|
673
605
|
prNumber,
|
|
@@ -685,10 +617,8 @@ try {
|
|
|
685
617
|
tempDir,
|
|
686
618
|
$,
|
|
687
619
|
});
|
|
688
|
-
|
|
689
620
|
// Initialize feedback lines
|
|
690
621
|
let feedbackLines = null;
|
|
691
|
-
|
|
692
622
|
// Add auto-merge feedback lines if any
|
|
693
623
|
if (autoMergeFeedbackLines && autoMergeFeedbackLines.length > 0) {
|
|
694
624
|
if (!feedbackLines) {
|
|
@@ -696,7 +626,6 @@ try {
|
|
|
696
626
|
}
|
|
697
627
|
feedbackLines.push(...autoMergeFeedbackLines);
|
|
698
628
|
}
|
|
699
|
-
|
|
700
629
|
// Merge feedback lines
|
|
701
630
|
if (preparedFeedbackLines && preparedFeedbackLines.length > 0) {
|
|
702
631
|
if (!feedbackLines) {
|
|
@@ -704,7 +633,6 @@ try {
|
|
|
704
633
|
}
|
|
705
634
|
feedbackLines.push(...preparedFeedbackLines);
|
|
706
635
|
}
|
|
707
|
-
|
|
708
636
|
// Check for uncommitted changes and merge with feedback
|
|
709
637
|
const uncommittedFeedbackLines = await checkUncommittedChanges({
|
|
710
638
|
tempDir,
|
|
@@ -718,7 +646,6 @@ try {
|
|
|
718
646
|
}
|
|
719
647
|
feedbackLines.push(...uncommittedFeedbackLines);
|
|
720
648
|
}
|
|
721
|
-
|
|
722
649
|
// Check for fork actions
|
|
723
650
|
const forkActionsUrl = await checkForkActions({
|
|
724
651
|
argv,
|
|
@@ -728,22 +655,16 @@ try {
|
|
|
728
655
|
formatAligned,
|
|
729
656
|
$,
|
|
730
657
|
});
|
|
731
|
-
|
|
732
658
|
// Execute tool command with all prompts and settings
|
|
733
659
|
let toolResult;
|
|
734
|
-
|
|
735
|
-
// Issue #1823: Mark the start of the AI working session. While this is active and the
|
|
736
|
-
// --do-not-shutdown-in-the-middle-of-working-session flag is set, an interrupt (CTRL+C/SIGTERM)
|
|
737
|
-
// is deferred until the AI tool finishes its turn (see exit-handler.lib.mjs + working-session.lib.mjs).
|
|
660
|
+
// Issue #1823: Mark the start of the AI working session. While this is active and the --do-not-shutdown-in-the-middle-of-working-session flag is set, an interrupt (CTRL+C/SIGTERM) is deferred until the AI tool finishes its turn (see exit-handler.lib.mjs + working-session.lib.mjs).
|
|
738
661
|
beginWorkingSession();
|
|
739
|
-
|
|
740
662
|
// If --use-agent-commander is enabled, use agent-commander for all tools
|
|
741
663
|
if (argv.useAgentCommander) {
|
|
742
664
|
// Ensure agent-commander is available
|
|
743
665
|
if (!agentCommanderLib) {
|
|
744
666
|
agentCommanderLib = await import('./agent-commander.lib.mjs');
|
|
745
667
|
}
|
|
746
|
-
|
|
747
668
|
const isAvailable = await agentCommanderLib.isAgentCommanderAvailable();
|
|
748
669
|
if (!isAvailable) {
|
|
749
670
|
await log('\n[agent-commander] agent-commander is not installed.', { level: 'error' });
|
|
@@ -751,9 +672,7 @@ try {
|
|
|
751
672
|
await log(' Or remove the --use-agent-commander flag to use embedded tool logic.', { level: 'error' });
|
|
752
673
|
await safeExit(1, 'agent-commander not available');
|
|
753
674
|
}
|
|
754
|
-
|
|
755
675
|
await log(`\n[agent-commander] Using agent-commander for ${argv.tool || 'claude'} execution`);
|
|
756
|
-
|
|
757
676
|
toolResult = await agentCommanderLib.executeWithAgentCommander({
|
|
758
677
|
issueUrl,
|
|
759
678
|
issueNumber,
|
|
@@ -786,7 +705,6 @@ try {
|
|
|
786
705
|
qwen: { lib: './qwen.lib.mjs', execFn: 'executeQwen', envVar: 'QWEN_PATH', defaultBin: 'qwen', pathKey: 'qwenPath' },
|
|
787
706
|
}[argv.tool];
|
|
788
707
|
const toolLib = await import(toolDispatch.lib);
|
|
789
|
-
|
|
790
708
|
toolResult = await toolLib[toolDispatch.execFn]({
|
|
791
709
|
issueUrl,
|
|
792
710
|
issueNumber,
|
|
@@ -839,17 +757,13 @@ try {
|
|
|
839
757
|
});
|
|
840
758
|
toolResult = claudeResult;
|
|
841
759
|
}
|
|
842
|
-
|
|
843
760
|
try {
|
|
844
761
|
await recordAfterAgentSize({ tempDir, beforeBytes: cleanupContext.diskDiagnostics?.beforeBytes ?? null, log });
|
|
845
762
|
} catch (diskError) {
|
|
846
763
|
await log(`⚠️ Disk-size measurement failed: ${cleanErrorMessage(diskError)}`, { level: 'warning', verbose: true });
|
|
847
764
|
}
|
|
848
765
|
await recordResourceSnapshot({ phase: RESOURCE_PHASE_AFTER_AGENT, log, diskPath: '/', label: 'after AI execution' });
|
|
849
|
-
|
|
850
|
-
// Issue #1823: Mark the end of the AI working session. If a graceful-shutdown interrupt arrived
|
|
851
|
-
// during the session (deferred by the working-session guard), honor it now: auto-commit any
|
|
852
|
-
// uncommitted changes and exit gracefully — only AFTER the AI tool has fully finished its turn.
|
|
766
|
+
// Issue #1823: Mark the end of the AI working session. If a graceful-shutdown interrupt arrived during the session (deferred by the working-session guard), honor it now: auto-commit any uncommitted changes and exit gracefully — only AFTER the AI tool has fully finished its turn.
|
|
853
767
|
const workingSessionState = endWorkingSession();
|
|
854
768
|
if (workingSessionState.shutdownRequested) {
|
|
855
769
|
const shutdownExitCode = workingSessionState.shutdownSignal === 'SIGINT' ? 130 : 143;
|
|
@@ -860,11 +774,9 @@ try {
|
|
|
860
774
|
} catch (interruptError) {
|
|
861
775
|
await log(`⚠️ Auto-commit on graceful shutdown failed: ${cleanErrorMessage(interruptError)}`, { level: 'warning' });
|
|
862
776
|
}
|
|
863
|
-
// Graceful shutdown is NOT a failure: skip the pre-exit failure notifier so no spurious
|
|
864
|
-
// "solver failed" comment is posted (issue #1823: no errors on graceful shutdown).
|
|
777
|
+
// Graceful shutdown is NOT a failure: skip the pre-exit failure notifier so no spurious "solver failed" comment is posted (issue #1823: no errors on graceful shutdown).
|
|
865
778
|
await safeExit(shutdownExitCode, 'Graceful shutdown after AI working session', { skipPreExit: true });
|
|
866
779
|
}
|
|
867
|
-
|
|
868
780
|
if (toolResult.preparedOnly) await safeExit(0, 'Command prepared', { skipPreExit: true });
|
|
869
781
|
const { success } = toolResult;
|
|
870
782
|
sessionId = toolResult.sessionId;
|
|
@@ -876,7 +788,6 @@ try {
|
|
|
876
788
|
let resultModelUsage = toolResult.resultModelUsage || null;
|
|
877
789
|
let streamTokenUsage = toolResult.streamTokenUsage || null;
|
|
878
790
|
let subAgentCalls = toolResult.subAgentCalls || null; // Issue #1590
|
|
879
|
-
|
|
880
791
|
const applyRestartResult = result => {
|
|
881
792
|
if (!result) return;
|
|
882
793
|
sessionId = result.sessionId || sessionId;
|
|
@@ -886,14 +797,12 @@ try {
|
|
|
886
797
|
};
|
|
887
798
|
limitReached = toolResult.limitReached;
|
|
888
799
|
cleanupContext.limitReached = limitReached;
|
|
889
|
-
|
|
890
800
|
if (sessionId && (argv.resumeOnAutoRestart || argv['resume-on-auto-restart'])) {
|
|
891
801
|
global.previousSessionId = sessionId;
|
|
892
802
|
if (argv.verbose) {
|
|
893
803
|
await log(`Session ID stored for auto-restart resume: ${sessionId}`, { verbose: true });
|
|
894
804
|
}
|
|
895
805
|
}
|
|
896
|
-
|
|
897
806
|
// Capture limit reset time and timezone globally for downstream handlers (auto-continue, cleanup decisions)
|
|
898
807
|
if (toolResult && toolResult.limitResetTime) {
|
|
899
808
|
global.limitResetTime = toolResult.limitResetTime;
|
|
@@ -901,20 +810,16 @@ try {
|
|
|
901
810
|
if (toolResult && toolResult.limitTimezone) {
|
|
902
811
|
global.limitTimezone = toolResult.limitTimezone;
|
|
903
812
|
}
|
|
904
|
-
|
|
905
813
|
// Handle limit reached scenario
|
|
906
814
|
if (limitReached) {
|
|
907
|
-
// Check for both auto-resume (maintains context) and auto-restart (fresh start)
|
|
908
|
-
// See: https://github.com/link-assistant/hive-mind/issues/1152
|
|
815
|
+
// Check for both auto-resume (maintains context) and auto-restart (fresh start) See: https://github.com/link-assistant/hive-mind/issues/1152
|
|
909
816
|
const shouldAutoResumeOnReset = argv.autoResumeOnLimitReset;
|
|
910
817
|
const shouldAutoRestartOnReset = argv.autoRestartOnLimitReset;
|
|
911
818
|
const shouldAutoContinueOnReset = shouldAutoResumeOnReset || shouldAutoRestartOnReset;
|
|
912
|
-
|
|
913
819
|
// If limit was reached but neither auto-resume nor auto-restart is enabled, fail immediately
|
|
914
820
|
if (!shouldAutoContinueOnReset) {
|
|
915
821
|
await log('\n❌ USAGE LIMIT REACHED!');
|
|
916
822
|
await log(' The AI tool has reached its usage limit.');
|
|
917
|
-
|
|
918
823
|
// Always show manual resume command in console so users can resume after limit resets
|
|
919
824
|
if (sessionId) {
|
|
920
825
|
const resetTime = global.limitResetTime;
|
|
@@ -923,8 +828,7 @@ try {
|
|
|
923
828
|
await log(`📁 Working directory: ${tempDir}`);
|
|
924
829
|
await log(`📌 Session ID: ${sessionId}`);
|
|
925
830
|
if (resetTime) {
|
|
926
|
-
// Format reset time with relative time and UTC for better user understanding
|
|
927
|
-
// See: https://github.com/link-assistant/hive-mind/issues/1152
|
|
831
|
+
// Format reset time with relative time and UTC for better user understanding See: https://github.com/link-assistant/hive-mind/issues/1152
|
|
928
832
|
const formattedResetTime = formatResetTimeWithRelative(resetTime, timezone);
|
|
929
833
|
await log(`⏰ Limit resets at: ${formattedResetTime}`);
|
|
930
834
|
}
|
|
@@ -944,7 +848,6 @@ try {
|
|
|
944
848
|
await log('');
|
|
945
849
|
}
|
|
946
850
|
}
|
|
947
|
-
|
|
948
851
|
// If --attach-logs is enabled and we have a PR, attach logs with usage limit details
|
|
949
852
|
if (shouldAttachLogs && sessionId && prNumber) {
|
|
950
853
|
await log('\n📄 Attaching logs to Pull Request...');
|
|
@@ -972,7 +875,6 @@ try {
|
|
|
972
875
|
tool: argv.tool || 'claude',
|
|
973
876
|
resultModelUsage, // Issue #1454: accurate multi-model display
|
|
974
877
|
});
|
|
975
|
-
|
|
976
878
|
if (logUploadSuccess) {
|
|
977
879
|
markFailureNotificationPosted('pr');
|
|
978
880
|
await log(' ✅ Logs uploaded successfully');
|
|
@@ -988,15 +890,12 @@ try {
|
|
|
988
890
|
// Fallback: Post simple failure comment (no CLI commands in GitHub comments, only mention option)
|
|
989
891
|
try {
|
|
990
892
|
const resetTime = global.limitResetTime;
|
|
991
|
-
// Issue #942: do not embed CLI commands in GitHub comments. Users
|
|
992
|
-
// interact via the Telegram bot, not the CLI. The full resume
|
|
993
|
-
// commands (interactive/autonomous/solve) live in the attached logs.
|
|
893
|
+
// Issue #942: do not embed CLI commands in GitHub comments. Users interact via the Telegram bot, not the CLI. The full resume commands (interactive/autonomous/solve) live in the attached logs.
|
|
994
894
|
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.';
|
|
995
895
|
// Format the reset time with relative time and UTC conversion if available
|
|
996
896
|
const timezone = global.limitTimezone || null;
|
|
997
897
|
const formattedResetTime = resetTime ? formatResetTimeWithRelative(resetTime, timezone) : null;
|
|
998
898
|
const failureComment = formattedResetTime ? `❌ **${USAGE_LIMIT_REACHED_MARKER}**\n\nThe AI tool has reached its usage limit. The limit will reset at: **${formattedResetTime}**\n\n${resumeSection}` : `❌ **${USAGE_LIMIT_REACHED_MARKER}**\n\nThe AI tool has reached its usage limit. Please wait for the limit to reset.\n\n${resumeSection}`;
|
|
999
|
-
|
|
1000
899
|
const posted = await postTrackedComment({ $, owner, repo, targetNumber: prNumber, body: failureComment });
|
|
1001
900
|
if (posted.ok) {
|
|
1002
901
|
markFailureNotificationPosted('pr');
|
|
@@ -1006,11 +905,9 @@ try {
|
|
|
1006
905
|
await log(` Warning: Could not post failure comment: ${cleanErrorMessage(error)}`, { verbose: true });
|
|
1007
906
|
}
|
|
1008
907
|
}
|
|
1009
|
-
|
|
1010
908
|
await safeExit(1, 'Usage limit reached - use --auto-resume-on-limit-reset or --auto-restart-on-limit-reset to wait for reset');
|
|
1011
909
|
} else {
|
|
1012
|
-
// auto-resume-on-limit-reset or auto-restart-on-limit-reset is enabled - attach logs and/or post waiting comment
|
|
1013
|
-
// Determine the mode type for comment formatting
|
|
910
|
+
// auto-resume-on-limit-reset or auto-restart-on-limit-reset is enabled - attach logs and/or post waiting comment Determine the mode type for comment formatting
|
|
1014
911
|
const limitContinueMode = shouldAutoRestartOnReset ? 'restart' : 'resume';
|
|
1015
912
|
if (prNumber && global.limitResetTime) {
|
|
1016
913
|
// If --attach-logs is enabled, upload logs with usage limit details
|
|
@@ -1035,8 +932,7 @@ try {
|
|
|
1035
932
|
toolName: getToolDisplayName(argv.tool),
|
|
1036
933
|
resumeCommand,
|
|
1037
934
|
sessionId,
|
|
1038
|
-
// Tell attachLogToGitHub that auto-resume is enabled to suppress CLI commands in the comment
|
|
1039
|
-
// See: https://github.com/link-assistant/hive-mind/issues/1152
|
|
935
|
+
// Tell attachLogToGitHub that auto-resume is enabled to suppress CLI commands in the comment See: https://github.com/link-assistant/hive-mind/issues/1152
|
|
1040
936
|
isAutoResumeEnabled: true,
|
|
1041
937
|
autoResumeMode: limitContinueMode,
|
|
1042
938
|
argv,
|
|
@@ -1044,7 +940,6 @@ try {
|
|
|
1044
940
|
tool: argv.tool || 'claude',
|
|
1045
941
|
resultModelUsage, // Issue #1454: accurate multi-model display
|
|
1046
942
|
});
|
|
1047
|
-
|
|
1048
943
|
if (logUploadSuccess) {
|
|
1049
944
|
await log(' ✅ Logs uploaded successfully');
|
|
1050
945
|
} else {
|
|
@@ -1062,7 +957,6 @@ try {
|
|
|
1062
957
|
const validation = await import('./solve.validation.lib.mjs');
|
|
1063
958
|
const { calculateWaitTime } = validation;
|
|
1064
959
|
const waitMs = calculateWaitTime(global.limitResetTime, global.limitTimezone || null);
|
|
1065
|
-
|
|
1066
960
|
const formatWaitTime = ms => {
|
|
1067
961
|
const seconds = Math.floor(ms / 1000);
|
|
1068
962
|
const minutes = Math.floor(seconds / 60);
|
|
@@ -1073,16 +967,12 @@ try {
|
|
|
1073
967
|
const h = hours % 24;
|
|
1074
968
|
return `${days}:${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
|
|
1075
969
|
};
|
|
1076
|
-
|
|
1077
|
-
// For waiting comments, don't show CLI commands since auto-continue will handle it automatically
|
|
1078
|
-
// See: https://github.com/link-assistant/hive-mind/issues/1152
|
|
970
|
+
// For waiting comments, don't show CLI commands since auto-continue will handle it automatically See: https://github.com/link-assistant/hive-mind/issues/1152
|
|
1079
971
|
const continueModeName = limitContinueMode === 'restart' ? 'auto-restart' : 'auto-resume';
|
|
1080
972
|
const continueDescription = limitContinueMode === 'restart' ? 'The session will automatically restart (fresh start) when the limit resets.' : 'The session will automatically resume (with context preserved) when the limit resets.';
|
|
1081
|
-
// Format reset time with relative time and UTC for better user understanding
|
|
1082
|
-
// See: https://github.com/link-assistant/hive-mind/issues/1236
|
|
973
|
+
// Format reset time with relative time and UTC for better user understanding See: https://github.com/link-assistant/hive-mind/issues/1236
|
|
1083
974
|
const waitingResetTimeFormatted = formatResetTimeWithRelative(global.limitResetTime, global.limitTimezone || null) || global.limitResetTime;
|
|
1084
975
|
const waitingComment = `⏳ **${USAGE_LIMIT_REACHED_MARKER} - Waiting to ${limitContinueMode === 'restart' ? 'Restart' : 'Continue'}**\n\nThe AI tool has reached its usage limit. ${continueModeName} is enabled.\n\n**Reset time:** ${waitingResetTimeFormatted}\n**Wait time:** ${formatWaitTime(waitMs)} (days:hours:minutes:seconds)\n\n${continueDescription}\n\nSession ID: \`${sessionId}\``;
|
|
1085
|
-
|
|
1086
976
|
const posted = await postTrackedComment({ $, owner, repo, targetNumber: prNumber, body: waitingComment });
|
|
1087
977
|
if (posted.ok) {
|
|
1088
978
|
await log(` Posted waiting comment to PR${posted.commentId ? ` (id=${posted.commentId})` : ''}`);
|
|
@@ -1094,14 +984,10 @@ try {
|
|
|
1094
984
|
}
|
|
1095
985
|
}
|
|
1096
986
|
}
|
|
1097
|
-
|
|
1098
987
|
// Skip failure exit if limit reached with auto-resume (continues to showSessionSummary/autoContinueWhenLimitResets)
|
|
1099
988
|
const shouldSkipFailureExitForAutoLimitContinue = limitReached && argv.autoResumeOnLimitReset;
|
|
1100
989
|
if (!success && !shouldSkipFailureExitForAutoLimitContinue) {
|
|
1101
|
-
// Issue #942: show all three resume options on failure for richer guidance.
|
|
1102
|
-
// 1. Interactive claude - opens Claude Code interactively (claude only)
|
|
1103
|
-
// 2. Autonomous claude - one-shot claude --resume w/ --dangerously-skip-permissions -p (claude only)
|
|
1104
|
-
// 3. Solve resume - re-enters solve.mjs with --resume, preserving tool/model/dir
|
|
990
|
+
// Issue #942: show all three resume options on failure for richer guidance. 1. Interactive claude - opens Claude Code interactively (claude only) 2. Autonomous claude - one-shot claude --resume w/ --dangerously-skip-permissions -p (claude only) 3. Solve resume - re-enters solve.mjs with --resume, preserving tool/model/dir
|
|
1105
991
|
const toolForFailure = argv.tool || 'claude';
|
|
1106
992
|
// Issue #1845: surface the core error instead of just "<TOOL> execution failed" (terminal + comment).
|
|
1107
993
|
const toolFailureMessage = formatToolExecutionFailure({ tool: toolForFailure, toolResult });
|
|
@@ -1118,7 +1004,6 @@ try {
|
|
|
1118
1004
|
}
|
|
1119
1005
|
await log('');
|
|
1120
1006
|
}
|
|
1121
|
-
|
|
1122
1007
|
// Preserve work before remote diagnostics; issue #2101 ended during log upload.
|
|
1123
1008
|
try {
|
|
1124
1009
|
const { criticalErrorRecovery } = await import('./config.lib.mjs');
|
|
@@ -1129,14 +1014,12 @@ try {
|
|
|
1129
1014
|
} catch (preserveError) {
|
|
1130
1015
|
await log(` ⚠️ Could not auto-commit before failure exit: ${preserveError.message}`, { verbose: true });
|
|
1131
1016
|
}
|
|
1132
|
-
|
|
1133
1017
|
// Attach failure logs before exiting (Issues #1212, #1462: fall back to issue if no PR)
|
|
1134
1018
|
const hasPR = global.createdPR && global.createdPR.number;
|
|
1135
1019
|
const hasIssue = global.issueNumber;
|
|
1136
1020
|
const logTargetType = hasPR ? 'pr' : hasIssue ? 'issue' : null;
|
|
1137
1021
|
const logTargetNumber = hasPR ? global.createdPR.number : hasIssue ? global.issueNumber : null;
|
|
1138
1022
|
const logTargetLabel = hasPR ? 'Pull Request' : `original issue #${logTargetNumber}`;
|
|
1139
|
-
|
|
1140
1023
|
if (shouldAttachLogs && logTargetType && logTargetNumber) {
|
|
1141
1024
|
await log(`\n📄 Attaching failure logs to ${logTargetLabel}...`);
|
|
1142
1025
|
try {
|
|
@@ -1166,7 +1049,6 @@ try {
|
|
|
1166
1049
|
tool: argv.tool || 'claude',
|
|
1167
1050
|
resultModelUsage, // Issue #1454: accurate multi-model display
|
|
1168
1051
|
});
|
|
1169
|
-
|
|
1170
1052
|
if (logUploadSuccess) {
|
|
1171
1053
|
markFailureNotificationPosted(logTargetType);
|
|
1172
1054
|
await log(` 📎 Failure logs posted to ${logTargetLabel}`);
|
|
@@ -1179,10 +1061,8 @@ try {
|
|
|
1179
1061
|
await log(` ⚠️ Error uploading failure logs: ${uploadError.message}`);
|
|
1180
1062
|
}
|
|
1181
1063
|
}
|
|
1182
|
-
|
|
1183
1064
|
await safeExit(1, toolFailureMessage);
|
|
1184
1065
|
}
|
|
1185
|
-
|
|
1186
1066
|
// Clean up .playwright-mcp/ to prevent browser artifacts from triggering auto-restart (Issue #1124)
|
|
1187
1067
|
if (argv.playwrightMcpAutoCleanup !== false) {
|
|
1188
1068
|
const playwrightMcpDir = path.join(tempDir, '.playwright-mcp');
|
|
@@ -1202,30 +1082,19 @@ try {
|
|
|
1202
1082
|
} else {
|
|
1203
1083
|
await log('ℹ️ Playwright MCP auto-cleanup disabled via --no-playwright-mcp-auto-cleanup', { verbose: true });
|
|
1204
1084
|
}
|
|
1205
|
-
|
|
1206
|
-
// When limit is reached, force auto-commit of any uncommitted changes to preserve work.
|
|
1207
|
-
// Issue #1834 (PR #1835 feedback): "on all critical errors we auto commit uncommitted changes by
|
|
1208
|
-
// default." A failed/errored session is a critical error, so auto-commit (and push) to preserve any
|
|
1209
|
-
// work the agent left on disk. On by default; disable via HIVE_MIND_AUTO_COMMIT_ON_CRITICAL_ERROR=false.
|
|
1085
|
+
// When limit is reached, force auto-commit of any uncommitted changes to preserve work. Issue #1834 (PR #1835 feedback): "on all critical errors we auto commit uncommitted changes by default." A failed/errored session is a critical error, so auto-commit (and push) to preserve any work the agent left on disk. On by default; disable via HIVE_MIND_AUTO_COMMIT_ON_CRITICAL_ERROR=false.
|
|
1210
1086
|
const { criticalErrorRecovery } = await import('./config.lib.mjs');
|
|
1211
1087
|
const criticalError = success === false || errorDuringExecution === true;
|
|
1212
1088
|
const shouldAutoCommit = argv['auto-commit-uncommitted-changes'] || limitReached || (criticalError && criticalErrorRecovery.autoCommitUncommittedChanges);
|
|
1213
1089
|
const autoRestartEnabled = argv['autoRestartOnUncommittedChanges'] !== false;
|
|
1214
1090
|
const shouldRestart = await checkForUncommittedChanges(tempDir, owner, repo, branchName, $, log, shouldAutoCommit, autoRestartEnabled);
|
|
1215
|
-
|
|
1216
|
-
// Issue #1516: cleanupClaudeFile() moved to after completion signals (before endWorkSession)
|
|
1217
|
-
|
|
1218
|
-
// Show summary of session and log file
|
|
1091
|
+
// Issue #1516: cleanupClaudeFile() moved to after completion signals (before endWorkSession) Show summary of session and log file
|
|
1219
1092
|
await showSessionSummary(sessionId, limitReached, argv, issueUrl, tempDir, shouldAttachLogs);
|
|
1220
|
-
|
|
1221
|
-
// Issue #1571: Defense-in-depth guard — skip post-processing if auto-continue is handling it
|
|
1222
|
-
// (prevents "Solution Draft Log" / "Ready to merge" comments before "Auto Resume")
|
|
1093
|
+
// Issue #1571: Defense-in-depth guard — skip post-processing if auto-continue is handling it (prevents "Solution Draft Log" / "Ready to merge" comments before "Auto Resume")
|
|
1223
1094
|
if (limitReached && (argv.autoResumeOnLimitReset || argv.autoRestartOnLimitReset) && global.limitResetTime) {
|
|
1224
1095
|
await safeExit(0, 'Auto-continue child process will handle post-processing');
|
|
1225
1096
|
}
|
|
1226
|
-
|
|
1227
1097
|
await enforceRequestedBaseBranch();
|
|
1228
|
-
|
|
1229
1098
|
// Issue #2048: commit+push dev log BEFORE any PR readiness signal so its CI gates readiness (was last, breaking CI post-signal; PR #2046, docs/case-studies/issue-2048). Idempotent: trailing call is a no-op. prettier-ignore
|
|
1230
1099
|
await finalizeDevelopmentLog();
|
|
1231
1100
|
// Issue #1263 / #1728 / #2115: shared summary attachment and usage display.
|
|
@@ -1241,25 +1110,20 @@ try {
|
|
|
1241
1110
|
pricingInfo,
|
|
1242
1111
|
sessionUsage: { sessionId, tempDir, resultModelUsage, streamTokenUsage, subAgentCalls },
|
|
1243
1112
|
});
|
|
1244
|
-
|
|
1245
1113
|
// Search for newly created pull requests and comments
|
|
1246
1114
|
const verifyResult = await verifyResults(owner, repo, branchName, issueNumber, prNumber, prUrl, referenceTime, argv, shouldAttachLogs, shouldRestart, sessionId, tempDir, anthropicTotalCostUSD, publicPricingEstimate, pricingInfo, errorDuringExecution, sessionType, resultModelUsage, streamTokenUsage, subAgentCalls, workingSessionBudgetStatsData);
|
|
1247
1115
|
const logsAlreadyUploaded = verifyResult?.logUploadSuccess || false;
|
|
1248
|
-
|
|
1249
1116
|
// Issue #1162: Auto-restart when PR title/description still has placeholder content
|
|
1250
1117
|
if (argv.autoRestartOnNonUpdatedPullRequestDescription && (verifyResult?.prTitleHasPlaceholder || verifyResult?.prBodyHasPlaceholder)) {
|
|
1251
1118
|
const { buildPRNotUpdatedHint } = results;
|
|
1252
1119
|
const hintLines = buildPRNotUpdatedHint(verifyResult.prTitleHasPlaceholder, verifyResult.prBodyHasPlaceholder);
|
|
1253
|
-
|
|
1254
1120
|
await log('');
|
|
1255
1121
|
await log('🔄 AUTO-RESTART: PR title/description not updated by agent');
|
|
1256
1122
|
hintLines.forEach(async line => await log(` ${line}`));
|
|
1257
1123
|
await log(' Restarting tool to give agent another chance to update...');
|
|
1258
1124
|
await log('');
|
|
1259
|
-
|
|
1260
1125
|
// Import executeToolIteration for re-execution
|
|
1261
1126
|
const { executeToolIteration } = await import('./solve.restart-shared.lib.mjs');
|
|
1262
|
-
|
|
1263
1127
|
// Re-execute tool with hint as feedback lines
|
|
1264
1128
|
const restartResult = await executeToolIteration({
|
|
1265
1129
|
issueUrl,
|
|
@@ -1278,16 +1142,12 @@ try {
|
|
|
1278
1142
|
autoRestartOnNonUpdatedPullRequestDescription: false,
|
|
1279
1143
|
},
|
|
1280
1144
|
});
|
|
1281
|
-
|
|
1282
1145
|
// Update session data from restart
|
|
1283
1146
|
applyRestartResult(restartResult);
|
|
1284
|
-
|
|
1285
1147
|
// Clean up CLAUDE.md/.gitkeep again after restart
|
|
1286
1148
|
await cleanupClaudeFile(tempDir, branchName, null, argv);
|
|
1287
|
-
|
|
1288
1149
|
// Re-verify results after restart (without auto-restart flag to prevent recursion)
|
|
1289
1150
|
const reVerifyResult = await verifyResults(owner, repo, branchName, issueNumber, prNumber, prUrl, referenceTime, { ...argv, autoRestartOnNonUpdatedPullRequestDescription: false }, shouldAttachLogs, false, sessionId, tempDir, anthropicTotalCostUSD, publicPricingEstimate, pricingInfo, errorDuringExecution, sessionType, resultModelUsage, streamTokenUsage, subAgentCalls);
|
|
1290
|
-
|
|
1291
1151
|
if (reVerifyResult?.prTitleHasPlaceholder || reVerifyResult?.prBodyHasPlaceholder) {
|
|
1292
1152
|
await log('⚠️ PR title/description still not updated after restart');
|
|
1293
1153
|
}
|
|
@@ -1296,7 +1156,6 @@ try {
|
|
|
1296
1156
|
applyRestartResult(await runEscalation({ issueUrl, owner, repo, issueNumber, prNumber, branchName, tempDir, workspaceTmpDir, argv, cleanupClaudeFile, resultSummary }));
|
|
1297
1157
|
applyRestartResult(await runAutoEnsureRequirements({ issueUrl, owner, repo, issueNumber, prNumber, branchName, tempDir, argv, cleanupClaudeFile }));
|
|
1298
1158
|
applyRestartResult(await runKeepWorkingUntilDone({ issueUrl, owner, repo, issueNumber, prNumber, branchName, tempDir, workspaceTmpDir, argv, cleanupClaudeFile, resultSummary }));
|
|
1299
|
-
|
|
1300
1159
|
// Start watch mode if enabled OR if we need to handle uncommitted changes
|
|
1301
1160
|
if (argv.verbose) {
|
|
1302
1161
|
await log('');
|
|
@@ -1309,7 +1168,6 @@ try {
|
|
|
1309
1168
|
await log(` branchName: ${branchName}`, { verbose: true });
|
|
1310
1169
|
await log(` isContinueMode: ${isContinueMode}`, { verbose: true });
|
|
1311
1170
|
}
|
|
1312
|
-
|
|
1313
1171
|
// If uncommitted changes detected and auto-commit is disabled, enter temporary watch mode
|
|
1314
1172
|
const temporaryWatchMode = shouldRestart && !argv.watch;
|
|
1315
1173
|
if (temporaryWatchMode) {
|
|
@@ -1320,7 +1178,6 @@ try {
|
|
|
1320
1178
|
await log(' Will exit automatically after changes are committed or discarded');
|
|
1321
1179
|
await log('');
|
|
1322
1180
|
}
|
|
1323
|
-
|
|
1324
1181
|
const watchResult = await startWatchMode({
|
|
1325
1182
|
issueUrl,
|
|
1326
1183
|
owner,
|
|
@@ -1338,7 +1195,6 @@ try {
|
|
|
1338
1195
|
temporaryWatch: temporaryWatchMode, // Flag to indicate temporary watch mode
|
|
1339
1196
|
},
|
|
1340
1197
|
});
|
|
1341
|
-
|
|
1342
1198
|
if (watchResult && watchResult.latestSessionId) {
|
|
1343
1199
|
sessionId = watchResult.latestSessionId;
|
|
1344
1200
|
anthropicTotalCostUSD = watchResult.latestAnthropicCost;
|
|
@@ -1351,19 +1207,14 @@ try {
|
|
|
1351
1207
|
}
|
|
1352
1208
|
}
|
|
1353
1209
|
}
|
|
1354
|
-
|
|
1355
1210
|
await enforceRequestedBaseBranch();
|
|
1356
|
-
|
|
1357
1211
|
// Track whether logs were successfully attached (used by endWorkSession)
|
|
1358
1212
|
let logsAttached = false;
|
|
1359
|
-
|
|
1360
|
-
// After watch mode completes (either user watch or temporary)
|
|
1361
|
-
// Push any committed changes if this was a temporary watch mode
|
|
1213
|
+
// After watch mode completes (either user watch or temporary) Push any committed changes if this was a temporary watch mode
|
|
1362
1214
|
if (temporaryWatchMode) {
|
|
1363
1215
|
await log('');
|
|
1364
1216
|
await log('📤 Pushing committed changes to GitHub...');
|
|
1365
1217
|
await log('');
|
|
1366
|
-
|
|
1367
1218
|
try {
|
|
1368
1219
|
const pushResult = await $({ cwd: tempDir })`git push origin ${branchName} 2>&1`;
|
|
1369
1220
|
if (pushResult.code === 0) {
|
|
@@ -1383,13 +1234,8 @@ try {
|
|
|
1383
1234
|
await log(' Please push manually:', { level: 'error' });
|
|
1384
1235
|
await log(` cd ${tempDir} && git push origin ${branchName}`, { level: 'error' });
|
|
1385
1236
|
}
|
|
1386
|
-
|
|
1387
1237
|
await verifyPullRequestIssueLinkAfterAutoRestart({ prNumber, issueNumber, owner, repo, argv, cleanErrorMessage });
|
|
1388
|
-
|
|
1389
|
-
// Attach updated logs to PR after auto-restart completes
|
|
1390
|
-
// Issue #1154: Skip if logs were already uploaded by verifyResults() to prevent duplicates
|
|
1391
|
-
// Issue #1290: Always upload if auto-restart ran but last iteration's logs weren't uploaded
|
|
1392
|
-
// This ensures final logs are uploaded even when the last iteration failed
|
|
1238
|
+
// Attach updated logs to PR after auto-restart completes Issue #1154: Skip if logs were already uploaded by verifyResults() to prevent duplicates Issue #1290: Always upload if auto-restart ran but last iteration's logs weren't uploaded This ensures final logs are uploaded even when the last iteration failed
|
|
1393
1239
|
const autoRestartRanButNotUploaded = watchResult?.autoRestartIterationsRan && !watchResult?.lastIterationLogUploaded;
|
|
1394
1240
|
if (shouldAttachLogs && prNumber && (!logsAlreadyUploaded || autoRestartRanButNotUploaded)) {
|
|
1395
1241
|
await log('📎 Uploading working session logs to Pull Request...');
|
|
@@ -1412,7 +1258,6 @@ try {
|
|
|
1412
1258
|
tool: argv.tool || 'claude',
|
|
1413
1259
|
resultModelUsage, // Issue #1454: accurate multi-model display
|
|
1414
1260
|
});
|
|
1415
|
-
|
|
1416
1261
|
if (logUploadSuccess) {
|
|
1417
1262
|
await log('✅ Working session logs uploaded successfully');
|
|
1418
1263
|
logsAttached = true;
|
|
@@ -1427,10 +1272,7 @@ try {
|
|
|
1427
1272
|
logsAttached = true;
|
|
1428
1273
|
}
|
|
1429
1274
|
}
|
|
1430
|
-
|
|
1431
|
-
// Start auto-restart-until-mergeable mode if enabled
|
|
1432
|
-
// This runs after the normal watch mode completes (if any)
|
|
1433
|
-
// --auto-merge implies --auto-restart-until-mergeable
|
|
1275
|
+
// Start auto-restart-until-mergeable mode if enabled This runs after the normal watch mode completes (if any) --auto-merge implies --auto-restart-until-mergeable
|
|
1434
1276
|
if (argv.autoMerge || argv.autoRestartUntilMergeable) {
|
|
1435
1277
|
const autoMergeResult = await startAutoRestartUntilMergeable({
|
|
1436
1278
|
issueUrl,
|
|
@@ -1443,7 +1285,6 @@ try {
|
|
|
1443
1285
|
tempDir,
|
|
1444
1286
|
argv,
|
|
1445
1287
|
});
|
|
1446
|
-
|
|
1447
1288
|
// Update session data with latest from auto-merge mode for accurate pricing
|
|
1448
1289
|
if (autoMergeResult && autoMergeResult.latestSessionId) {
|
|
1449
1290
|
sessionId = autoMergeResult.latestSessionId;
|
|
@@ -1458,13 +1299,10 @@ try {
|
|
|
1458
1299
|
}
|
|
1459
1300
|
}
|
|
1460
1301
|
}
|
|
1461
|
-
|
|
1462
1302
|
// Issue #1952: Final --attach-logs safety net + logsAttached reconciliation. See attach-logs-guarantee.lib.mjs.
|
|
1463
1303
|
logsAttached = (await attachFinalLogIfMissing({ shouldAttachLogs, prNumber, owner, repo, $, log, sanitizeLogContent, getLogFile, attachLogToGitHub, argv, sessionId, tempDir, anthropicTotalCostUSD, resultModelUsage })) || logsAttached;
|
|
1464
|
-
|
|
1465
1304
|
// Issue #1516: Cleanup after all signals (was before verifyResults, caused premature commits)
|
|
1466
1305
|
await cleanupClaudeFile(tempDir, branchName, claudeCommitHash, argv);
|
|
1467
|
-
|
|
1468
1306
|
await finalizeDevelopmentLog(); // Issue #1596/#2048: idempotent no-op on the success path (already committed before readiness signal); still preserves late/error work.
|
|
1469
1307
|
await endWorkSession({ isContinueMode, prNumber, argv, log, formatAligned, $, logsAttached });
|
|
1470
1308
|
} catch (error) {
|