@link-assistant/hive-mind 2.1.1 → 2.1.3
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 +12 -0
- package/package.json +1 -1
- package/src/agent.lib.mjs +132 -32
- package/src/bidirectional-interactive.lib.mjs +169 -71
- package/src/exit-handler.lib.mjs +4 -2
- package/src/github.lib.mjs +2 -4
- package/src/live-input-capabilities.lib.mjs +221 -0
- package/src/solve.auto-merge-helpers.lib.mjs +66 -0
- package/src/solve.auto-merge.lib.mjs +34 -5
- package/src/solve.branch-divergence.lib.mjs +233 -3
- package/src/solve.config.lib.mjs +10 -10
- package/src/solve.fork-sync.lib.mjs +94 -38
- package/src/solve.mjs +1 -1
- package/src/solve.pre-pr-failure-notifier.lib.mjs +25 -30
- package/src/solve.validation.lib.mjs +3 -1
package/src/solve.config.lib.mjs
CHANGED
|
@@ -242,13 +242,13 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
242
242
|
description: 'Auto-restart until PR becomes mergeable (no iteration limit). Restarts on new comments from non-bot users, CI failures, merge conflicts, or other issues. Does NOT auto-merge.',
|
|
243
243
|
default: true,
|
|
244
244
|
},
|
|
245
|
-
// Issue #1708:
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
//
|
|
245
|
+
// Issue #1708/#2007: streaming-first feedback into the running tool session.
|
|
246
|
+
// Claude and Agent are wired through bidirectional stream-json stdin pipes;
|
|
247
|
+
// other tools retain restart/resume fallback behavior until a verified
|
|
248
|
+
// mid-session input protocol is wired into their solve runners.
|
|
249
249
|
'auto-input-until-mergeable': {
|
|
250
250
|
type: 'boolean',
|
|
251
|
-
description: '[EXPERIMENTAL]
|
|
251
|
+
description: '[EXPERIMENTAL] Keep feeding new issue/PR events (uncommitted changes, CI/CD failures, PR/issue comments, issue title/description edits) into the running AI session, in all ways possible. For --tool claude and --tool agent this streams the events directly into the live process via stream-json stdin (implies --accept-incomming-comments-as-input and --queue-comments-to-input by default, deferring comments until the AI finishes the current step). For codex, opencode, gemini, qwen, and unknown tools, it uses the universal restart/resume fallback: wait for the current turn to finish in the JSON output, stop the process, then resume/restart the AI session with the new events via --auto-restart-until-mergeable. Codex live streaming should be wired in a future runner through Codex app-server turn/steer. Disabled by default.',
|
|
252
252
|
default: false,
|
|
253
253
|
},
|
|
254
254
|
'wait-for-all-actions-in-repository-before-mergeable': {
|
|
@@ -436,7 +436,7 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
436
436
|
// Issue #817: Bidirectional interactive options
|
|
437
437
|
'accept-incomming-comments-as-input': {
|
|
438
438
|
type: 'boolean',
|
|
439
|
-
description: '[EXPERIMENTAL] Accept new PR/issue comments as input for
|
|
439
|
+
description: '[EXPERIMENTAL] Accept new PR/issue comments as input for the running stream-json tool during execution (excludes outgoing comments generated by solve itself). Does not require --interactive-mode; disabled by default. Only supported for --tool claude and --tool agent.',
|
|
440
440
|
default: false,
|
|
441
441
|
},
|
|
442
442
|
'exclude-all-own-incomming-comments-from-input': {
|
|
@@ -446,13 +446,13 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
446
446
|
},
|
|
447
447
|
'bidirectional-interactive-mode': {
|
|
448
448
|
type: 'boolean',
|
|
449
|
-
description: '[EXPERIMENTAL] Convenience flag that enables --interactive-mode, --accept-incomming-comments-as-input and --exclude-all-own-incomming-comments-from-input together. Only supported for --tool claude.',
|
|
449
|
+
description: '[EXPERIMENTAL] Convenience flag that enables --interactive-mode, --accept-incomming-comments-as-input and --exclude-all-own-incomming-comments-from-input together. Only supported for --tool claude and --tool agent.',
|
|
450
450
|
default: false,
|
|
451
451
|
},
|
|
452
452
|
// Issue #1708: Comment delivery mode for --accept-incomming-comments-as-input.
|
|
453
453
|
// --stream-comments-to-input: forward comments immediately as they arrive
|
|
454
454
|
// (the default for --accept-incomming-comments-as-input on its own; matches
|
|
455
|
-
// the existing #817 behavior of pushing comments to
|
|
455
|
+
// the existing #817 behavior of pushing comments to the stream-json tool as soon as
|
|
456
456
|
// pollIncomingComments sees them).
|
|
457
457
|
// --queue-comments-to-input: hold comments until the AI signals it is idle
|
|
458
458
|
// (waiting for input), then flush the queue. Used by
|
|
@@ -461,12 +461,12 @@ export const SOLVE_OPTION_DEFINITIONS = {
|
|
|
461
461
|
// The two flags are mutually exclusive; if both are set, queue mode wins.
|
|
462
462
|
'stream-comments-to-input': {
|
|
463
463
|
type: 'boolean',
|
|
464
|
-
description: '[EXPERIMENTAL] When --accept-incomming-comments-as-input is enabled, forward each new PR/issue comment to the AI immediately as it arrives (real-time streaming). This is the default behavior for --accept-incomming-comments-as-input on its own. Mutually exclusive with --queue-comments-to-input; queue mode wins if both are set. Only supported for --tool claude.',
|
|
464
|
+
description: '[EXPERIMENTAL] When --accept-incomming-comments-as-input is enabled, forward each new PR/issue comment to the AI immediately as it arrives (real-time streaming). This is the default behavior for --accept-incomming-comments-as-input on its own. Mutually exclusive with --queue-comments-to-input; queue mode wins if both are set. Only supported for --tool claude and --tool agent.',
|
|
465
465
|
default: false,
|
|
466
466
|
},
|
|
467
467
|
'queue-comments-to-input': {
|
|
468
468
|
type: 'boolean',
|
|
469
|
-
description: '[EXPERIMENTAL] When --accept-incomming-comments-as-input is enabled, queue new PR/issue comments and only flush them once the AI signals it is idle (waiting for input). This is the default mode implied by --auto-input-until-mergeable so the AI completes the current step before being interrupted with new instructions. Mutually exclusive with --stream-comments-to-input; queue mode wins if both are set. Only supported for --tool claude.',
|
|
469
|
+
description: '[EXPERIMENTAL] When --accept-incomming-comments-as-input is enabled, queue new PR/issue comments and only flush them once the AI signals it is idle (waiting for input). This is the default mode implied by --auto-input-until-mergeable so the AI completes the current step before being interrupted with new instructions. Mutually exclusive with --stream-comments-to-input; queue mode wins if both are set. Only supported for --tool claude and --tool agent.',
|
|
470
470
|
default: false,
|
|
471
471
|
},
|
|
472
472
|
'prompt-explore-sub-agent': {
|
|
@@ -26,7 +26,80 @@ import { safeExit } from './exit-handler.lib.mjs';
|
|
|
26
26
|
// Issue #1893: helpers that decide whether the fork's default branch may be
|
|
27
27
|
// pushed and that distinguish a permission-denied rejection from a genuine
|
|
28
28
|
// fork divergence.
|
|
29
|
-
const { isPermissionDeniedPushError, shouldPushDefaultBranchToFork } = await import('./solve.branch-divergence.lib.mjs');
|
|
29
|
+
const { buildForkDivergenceBlockedReason, buildForkDivergenceFailureActionSection, getForkDefaultBranchDivergenceSnapshot, isPermissionDeniedPushError, shouldPushDefaultBranchToFork } = await import('./solve.branch-divergence.lib.mjs');
|
|
30
|
+
|
|
31
|
+
const firstNonEmptyLine = value =>
|
|
32
|
+
String(value || '')
|
|
33
|
+
.split('\n')
|
|
34
|
+
.map(line => line.trim())
|
|
35
|
+
.find(Boolean) || '';
|
|
36
|
+
|
|
37
|
+
const resolveSolveCommand = argv => {
|
|
38
|
+
const issueUrl = argv.url || argv['issue-url'] || argv._?.[0] || '<issue-url>';
|
|
39
|
+
return `solve ${issueUrl}`;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const extractGitHubUrlNumber = argv => {
|
|
43
|
+
const url = String(argv.url || argv['issue-url'] || argv._?.[0] || '');
|
|
44
|
+
const match = url.match(/github\.com\/[^/]+\/[^/]+\/(issues|pull)\/(\d+)/i);
|
|
45
|
+
return match ? { type: match[1] === 'pull' ? 'pulls' : 'issues', number: match[2] } : null;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const resolveTaskRequester = async ({ $, owner, repo, argv }) => {
|
|
49
|
+
const target = extractGitHubUrlNumber(argv);
|
|
50
|
+
if (!target) return null;
|
|
51
|
+
const result = await $({ silent: true })`gh api repos/${owner}/${repo}/${target.type}/${target.number} --jq .user.login 2>&1`;
|
|
52
|
+
return result.code === 0 ? firstNonEmptyLine(result.stdout) : null;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const logForkDivergenceDetails = async ({ snapshot, currentUser, taskRequester, solveCommand, includeAction = true }) => {
|
|
56
|
+
await log('');
|
|
57
|
+
await log(' 🔍 What happened:');
|
|
58
|
+
await log(` Git rejected updating ${snapshot.forkedRepo}:${snapshot.branchName} after Hive Mind synced the local branch to ${snapshot.upstreamRepo}:${snapshot.branchName}.`);
|
|
59
|
+
await log('');
|
|
60
|
+
await log(' 📦 Current state:');
|
|
61
|
+
await log(` Fork: ${snapshot.forkedRepo}`);
|
|
62
|
+
await log(` Upstream: ${snapshot.upstreamRepo}`);
|
|
63
|
+
await log(` Branch: ${snapshot.branchName}`);
|
|
64
|
+
if (currentUser) await log(` Authenticated user: ${currentUser}`);
|
|
65
|
+
if (taskRequester) await log(` Task requester: ${taskRequester}`);
|
|
66
|
+
if (snapshot.compareUrl) await log(` Compare: ${snapshot.compareUrl}`);
|
|
67
|
+
|
|
68
|
+
if (snapshot.fetchError || snapshot.inspectError) {
|
|
69
|
+
await log('');
|
|
70
|
+
await log(' ⚠️ Safety check incomplete:');
|
|
71
|
+
await log(` ${snapshot.fetchError || snapshot.inspectError}`);
|
|
72
|
+
await log(' Hive Mind cannot prove whether force-with-lease would overwrite fork-only commits.');
|
|
73
|
+
} else {
|
|
74
|
+
await log(` Fork-only commits: ${snapshot.forkUniqueCount ?? 'unknown'}`);
|
|
75
|
+
await log(` Upstream-only commits missing from fork: ${snapshot.upstreamUniqueCount ?? 'unknown'}`);
|
|
76
|
+
if ((snapshot.forkUniqueCount ?? 0) > 0) {
|
|
77
|
+
await log('');
|
|
78
|
+
await log(' ⚠️ Commits that would be lost from the fork default branch:');
|
|
79
|
+
for (const commit of snapshot.uniqueCommits) {
|
|
80
|
+
await log(` - ${commit.shortSha || commit.sha} ${commit.author || 'unknown author'} ${commit.subject || 'no subject'}`);
|
|
81
|
+
}
|
|
82
|
+
if (snapshot.uniqueCommits.length < snapshot.forkUniqueCount) {
|
|
83
|
+
await log(` - ... ${snapshot.forkUniqueCount - snapshot.uniqueCommits.length} more commit(s) not shown`);
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
await log('');
|
|
87
|
+
await log(' ✅ Safety check:');
|
|
88
|
+
await log(` No commits unique to ${snapshot.forkRef} were found.`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const actionSection = buildForkDivergenceFailureActionSection({ snapshot, currentUser, taskRequester, solveCommand });
|
|
93
|
+
if (includeAction) {
|
|
94
|
+
await log('');
|
|
95
|
+
await log(' 🔧 GitHub comment guidance:');
|
|
96
|
+
for (const line of actionSection.split('\n')) {
|
|
97
|
+
await log(` ${line}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
await log('');
|
|
101
|
+
return actionSection;
|
|
102
|
+
};
|
|
30
103
|
|
|
31
104
|
// Set up upstream remote and sync fork
|
|
32
105
|
export const setupUpstreamAndSync = async (tempDir, forkedRepo, upstreamRemote, owner, repo, argv) => {
|
|
@@ -170,16 +243,23 @@ export const setupUpstreamAndSync = async (tempDir, forkedRepo, upstreamRemote,
|
|
|
170
243
|
// Fork has diverged from upstream
|
|
171
244
|
await log('');
|
|
172
245
|
await log(`${formatAligned('⚠️', 'FORK DIVERGENCE DETECTED', '')}`, { level: 'warn' });
|
|
173
|
-
await
|
|
174
|
-
|
|
175
|
-
await
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
await
|
|
246
|
+
const taskRequester = await resolveTaskRequester({ $, owner, repo, argv });
|
|
247
|
+
const solveCommand = resolveSolveCommand(argv);
|
|
248
|
+
const divergenceSnapshot = await getForkDefaultBranchDivergenceSnapshot({
|
|
249
|
+
$,
|
|
250
|
+
tempDir,
|
|
251
|
+
branchName: upstreamDefaultBranch,
|
|
252
|
+
forkedRepo,
|
|
253
|
+
upstreamRepo: `${owner}/${repo}`,
|
|
254
|
+
});
|
|
255
|
+
const failureActionSection = await logForkDivergenceDetails({
|
|
256
|
+
snapshot: divergenceSnapshot,
|
|
257
|
+
currentUser,
|
|
258
|
+
taskRequester,
|
|
259
|
+
solveCommand,
|
|
260
|
+
includeAction: !argv.allowForkDivergenceResolutionUsingForcePushWithLease,
|
|
261
|
+
});
|
|
262
|
+
const blockedReason = buildForkDivergenceBlockedReason({ snapshot: divergenceSnapshot });
|
|
183
263
|
|
|
184
264
|
// Check if user has enabled automatic force push
|
|
185
265
|
if (argv.allowForkDivergenceResolutionUsingForcePushWithLease) {
|
|
@@ -225,36 +305,12 @@ export const setupUpstreamAndSync = async (tempDir, forkedRepo, upstreamRemote,
|
|
|
225
305
|
await log(' 3. Manually sync fork with upstream:');
|
|
226
306
|
await log(' git fetch upstream');
|
|
227
307
|
await log(` git reset --hard upstream/${upstreamDefaultBranch}`);
|
|
228
|
-
await log(` git push --force origin ${upstreamDefaultBranch}`);
|
|
308
|
+
await log(` git push --force-with-lease origin ${upstreamDefaultBranch}`);
|
|
229
309
|
await log('');
|
|
230
|
-
await safeExit(1, 'Repository setup failed - fork sync failed');
|
|
310
|
+
await safeExit(1, 'Repository setup failed - fork sync failed', { failureActionSection });
|
|
231
311
|
}
|
|
232
312
|
} else {
|
|
233
|
-
|
|
234
|
-
await log(' 💡 Your options:');
|
|
235
|
-
await log('');
|
|
236
|
-
await log(' Option 1: Delete your fork and recreate it (SIMPLEST)');
|
|
237
|
-
await log(` gh repo delete ${forkedRepo}`);
|
|
238
|
-
await log(' Then run the solve command again - the fork will be recreated automatically');
|
|
239
|
-
await log(' ⚠️ Only use this if your fork has no unique commits you need to preserve');
|
|
240
|
-
await log('');
|
|
241
|
-
await log(' Option 2: Enable automatic force-push (DANGEROUS)');
|
|
242
|
-
await log(' Add --allow-fork-divergence-resolution-using-force-push-with-lease flag to your command');
|
|
243
|
-
await log(' This will automatically sync your fork with upstream using force-with-lease');
|
|
244
|
-
await log(' ⚠️ Overwrites fork history - any unique commits will be LOST');
|
|
245
|
-
await log('');
|
|
246
|
-
await log(' Option 3: Manually resolve the divergence');
|
|
247
|
-
await log(' 1. Decide if you need any commits unique to your fork');
|
|
248
|
-
await log(' 2. If yes, cherry-pick them after syncing');
|
|
249
|
-
await log(' 3. If no, manually force-push:');
|
|
250
|
-
await log(' git fetch upstream');
|
|
251
|
-
await log(` git reset --hard upstream/${upstreamDefaultBranch}`);
|
|
252
|
-
await log(` git push --force origin ${upstreamDefaultBranch}`);
|
|
253
|
-
await log('');
|
|
254
|
-
await log(' 🔧 To proceed with auto-resolution, restart with:');
|
|
255
|
-
await log(` solve ${argv.url || argv['issue-url'] || argv._[0] || '<issue-url>'} --allow-fork-divergence-resolution-using-force-push-with-lease`);
|
|
256
|
-
await log('');
|
|
257
|
-
await safeExit(1, 'Repository setup halted - fork divergence requires user decision');
|
|
313
|
+
await safeExit(1, blockedReason, { failureActionSection });
|
|
258
314
|
}
|
|
259
315
|
} else {
|
|
260
316
|
// Some other push error (not divergence-related)
|
package/src/solve.mjs
CHANGED
|
@@ -163,7 +163,7 @@ const cleanupWrapper = async () => {
|
|
|
163
163
|
}
|
|
164
164
|
};
|
|
165
165
|
const interruptWrapper = createInterruptWrapper({ cleanupContext, checkForUncommittedChanges, shouldAttachLogs, attachLogToGitHub, getLogFile, sanitizeLogContent, $, log });
|
|
166
|
-
initializeExitHandler(getAbsoluteLogPath, log, cleanupWrapper, interruptWrapper, ({ code, reason }) => notifyIssueAboutPrePullRequestFailure({ code, reason, argv, globalState: global, $, log, getLogFile, shouldAttachLogs, attachLogToGitHub, sanitizeLogContent, rawCommand }));
|
|
166
|
+
initializeExitHandler(getAbsoluteLogPath, log, cleanupWrapper, interruptWrapper, ({ code, reason, failureActionSection }) => notifyIssueAboutPrePullRequestFailure({ code, reason, failureActionSection, argv, globalState: global, $, log, getLogFile, shouldAttachLogs, attachLogToGitHub, sanitizeLogContent, rawCommand }));
|
|
167
167
|
installGlobalExitHandlers();
|
|
168
168
|
// Issue #1823: Configure the working-session guard. When the experimental
|
|
169
169
|
// --do-not-shutdown-in-the-middle-of-working-session flag is set (hive passes it to every
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { getTrackedToolCommentIds, postTrackedComment, SOLUTION_DRAFT_FAILED_MARKER } from './tool-comments.lib.mjs';
|
|
2
2
|
import { extractForkReplacementBlockedDetails, isForkReplacementBlockedReason } from './solve.repository-recovery-message.lib.mjs';
|
|
3
|
+
import { FORK_DIVERGENCE_RESOLUTION_OPTION, buildForkDivergenceFailureActionSection } from './solve.branch-divergence.lib.mjs';
|
|
3
4
|
|
|
4
|
-
export
|
|
5
|
+
export { FORK_DIVERGENCE_RESOLUTION_OPTION };
|
|
5
6
|
|
|
6
7
|
const truncate = (value, maxLength = 2000) => {
|
|
7
8
|
const text = value === null || value === undefined ? '' : String(value);
|
|
@@ -40,20 +41,17 @@ export function buildPrePullRequestFailureActionSection(reason = '') {
|
|
|
40
41
|
- It could not prove whether the existing repository has unique commits, so it did not delete it automatically.
|
|
41
42
|
|
|
42
43
|
### What you can do
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
- Use \`--allow-force-non-fork-repository-deletion\` only after confirming that deleting \`${repository}\` is acceptable and any unique commits can be lost
|
|
46
|
-
|
|
47
|
-
Administrator-only CLI details, if any, are printed in the solver terminal log rather than in this GitHub comment.`;
|
|
44
|
+
- Repository owner path: back up any needed work, then delete, rename, archive, or repair \`${repository}\` in GitHub and rerun the solver.
|
|
45
|
+
- External-owner path: ask the repository owner or a Hive Mind administrator to clean it up and rerun the solver.
|
|
46
|
+
- Use \`--allow-force-non-fork-repository-deletion\` only after confirming that deleting \`${repository}\` is acceptable and any unique commits can be lost.`;
|
|
48
47
|
}
|
|
49
48
|
|
|
50
49
|
if (isForkDivergenceFailure(reason)) {
|
|
51
|
-
return
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
Administrator-only CLI details, if any, are printed in the solver terminal log rather than in this GitHub comment.`;
|
|
50
|
+
return buildForkDivergenceFailureActionSection({
|
|
51
|
+
snapshot: {
|
|
52
|
+
fetchError: 'No fork divergence inspection data was attached to this failure reason.',
|
|
53
|
+
},
|
|
54
|
+
});
|
|
57
55
|
}
|
|
58
56
|
|
|
59
57
|
if (isPushRejectionFailure(reason)) {
|
|
@@ -62,26 +60,20 @@ Administrator-only CLI details, if any, are printed in the solver terminal log r
|
|
|
62
60
|
return `### What you can do
|
|
63
61
|
- Inspect the remote branch${branchUrl ? `: ${branchUrl}` : ' named in the failure'}.
|
|
64
62
|
- Compare the base and head histories${compareUrl ? `: ${compareUrl}` : ' using the compare link named in the failure'}.
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
|
|
68
|
-
Administrator-only CLI details, if any, are printed in the solver terminal log rather than in this GitHub comment.`;
|
|
63
|
+
- Branch owner path: merge the remote branch state or choose a new branch name, then rerun the solver.
|
|
64
|
+
- Manual force-push remains blocked until a human confirms that overwriting the remote branch is safe.`;
|
|
69
65
|
}
|
|
70
66
|
|
|
71
67
|
if (isForkOrRecoveryFailure) {
|
|
72
68
|
return `### What you can do
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
- Repository deletion can require a separate GitHub account or token with repository deletion permission; Hive Mind does not rely on that permission by default
|
|
76
|
-
|
|
77
|
-
Administrator-only CLI details, if any, are printed in the solver terminal log rather than in this issue comment.`;
|
|
69
|
+
- Repository owner path: remove, rename, archive, initialize, or otherwise repair the affected fork or repository in GitHub, then rerun the solver.
|
|
70
|
+
- External-owner path: ask a Hive Mind administrator to handle manual recreation or fix of the repository and rerun the solver.
|
|
71
|
+
- Repository deletion can require a separate GitHub account or token with repository deletion permission; Hive Mind does not rely on that permission by default.`;
|
|
78
72
|
}
|
|
79
73
|
|
|
80
74
|
return `### What you can do
|
|
81
75
|
- Resolve the repository, account, permissions, or environment problem described above, then rerun the solver.
|
|
82
|
-
-
|
|
83
|
-
|
|
84
|
-
Administrator-only CLI details, if any, are printed in the solver terminal log rather than in this issue comment.`;
|
|
76
|
+
- Repository owner or Hive Mind administrator path: handle manual recreation or fix of the repository when the required action is outside the requester access.`;
|
|
85
77
|
}
|
|
86
78
|
|
|
87
79
|
export function shouldNotifyIssueAboutPrePullRequestFailure({ code, globalState }) {
|
|
@@ -128,11 +120,11 @@ export function resolvePreExitFailureNotificationTarget({ code, globalState }) {
|
|
|
128
120
|
};
|
|
129
121
|
}
|
|
130
122
|
|
|
131
|
-
export function buildPrePullRequestFailureComment({ reason, owner, repo, issueNumber, argv = {}, logAttachmentAttempted = false }) {
|
|
123
|
+
export function buildPrePullRequestFailureComment({ reason, owner, repo, issueNumber, argv = {}, logAttachmentAttempted = false, failureActionSection = null }) {
|
|
132
124
|
const tool = argv.tool || 'claude';
|
|
133
125
|
const modelLine = argv.model ? `\n- **Requested model**: \`${argv.model}\`` : '';
|
|
134
126
|
const logLine = logAttachmentAttempted ? 'Log attachment was attempted but failed. Check the solver terminal log for the complete failure output.' : 'Logs were not attached because `--attach-logs` was not enabled.';
|
|
135
|
-
const actionSection = buildPrePullRequestFailureActionSection(reason);
|
|
127
|
+
const actionSection = failureActionSection || buildPrePullRequestFailureActionSection(reason);
|
|
136
128
|
|
|
137
129
|
return `## 🚨 ${SOLUTION_DRAFT_FAILED_MARKER}
|
|
138
130
|
|
|
@@ -154,12 +146,12 @@ ${logLine}
|
|
|
154
146
|
`;
|
|
155
147
|
}
|
|
156
148
|
|
|
157
|
-
export function buildExistingPullRequestFailureComment({ reason, owner, repo, prNumber, issueNumber = null, argv = {}, logAttachmentAttempted = false }) {
|
|
149
|
+
export function buildExistingPullRequestFailureComment({ reason, owner, repo, prNumber, issueNumber = null, argv = {}, logAttachmentAttempted = false, failureActionSection = null }) {
|
|
158
150
|
const tool = argv.tool || 'claude';
|
|
159
151
|
const modelLine = argv.model ? `\n- **Requested model**: \`${argv.model}\`` : '';
|
|
160
152
|
const issueLine = issueNumber ? `\n- **Linked issue**: #${issueNumber}` : '';
|
|
161
153
|
const logLine = logAttachmentAttempted ? 'Log attachment was attempted but failed. Check the solver terminal log for the complete failure output.' : 'Logs were not attached because `--attach-logs` was not enabled.';
|
|
162
|
-
const actionSection = buildPrePullRequestFailureActionSection(reason);
|
|
154
|
+
const actionSection = failureActionSection || buildPrePullRequestFailureActionSection(reason);
|
|
163
155
|
|
|
164
156
|
return `## 🚨 ${SOLUTION_DRAFT_FAILED_MARKER}
|
|
165
157
|
|
|
@@ -191,7 +183,7 @@ const markNotificationPosted = ({ globalState, targetType }) => {
|
|
|
191
183
|
};
|
|
192
184
|
|
|
193
185
|
export async function notifyIssueAboutPrePullRequestFailure(options) {
|
|
194
|
-
const { code, reason, argv = {}, globalState = globalThis, $, log = async () => {}, getLogFile, shouldAttachLogs = false, attachLogToGitHub, sanitizeLogContent, rawCommand = null, postComment = postTrackedComment } = options;
|
|
186
|
+
const { code, reason, failureActionSection = null, argv = {}, globalState = globalThis, $, log = async () => {}, getLogFile, shouldAttachLogs = false, attachLogToGitHub, sanitizeLogContent, rawCommand = null, postComment = postTrackedComment } = options;
|
|
195
187
|
|
|
196
188
|
const target = resolvePreExitFailureNotificationTarget({ code, globalState });
|
|
197
189
|
if (!target) {
|
|
@@ -200,6 +192,7 @@ export async function notifyIssueAboutPrePullRequestFailure(options) {
|
|
|
200
192
|
|
|
201
193
|
const { owner, repo, issueNumber, prNumber, targetType, targetNumber } = target;
|
|
202
194
|
const targetLabel = targetType === 'pr' ? `pull request #${targetNumber}` : `issue #${targetNumber}`;
|
|
195
|
+
const actionSection = failureActionSection || buildPrePullRequestFailureActionSection(reason);
|
|
203
196
|
globalState.preExitFailureNotificationInProgress = true;
|
|
204
197
|
if (targetType === 'pr') {
|
|
205
198
|
globalState.pullRequestFailureNotificationInProgress = true;
|
|
@@ -223,7 +216,7 @@ export async function notifyIssueAboutPrePullRequestFailure(options) {
|
|
|
223
216
|
sanitizeLogContent,
|
|
224
217
|
verbose: argv.verbose,
|
|
225
218
|
errorMessage: `${errorPrefix}\n\nReason: ${reason || 'Unknown error'}`,
|
|
226
|
-
failureActionSection:
|
|
219
|
+
failureActionSection: actionSection,
|
|
227
220
|
argv,
|
|
228
221
|
requestedModel: argv.originalModel || argv.model,
|
|
229
222
|
tool: argv.tool || 'claude',
|
|
@@ -250,6 +243,7 @@ export async function notifyIssueAboutPrePullRequestFailure(options) {
|
|
|
250
243
|
argv,
|
|
251
244
|
rawCommand,
|
|
252
245
|
logAttachmentAttempted: shouldAttachLogs,
|
|
246
|
+
failureActionSection: actionSection,
|
|
253
247
|
})
|
|
254
248
|
: buildPrePullRequestFailureComment({
|
|
255
249
|
reason,
|
|
@@ -259,6 +253,7 @@ export async function notifyIssueAboutPrePullRequestFailure(options) {
|
|
|
259
253
|
argv,
|
|
260
254
|
rawCommand,
|
|
261
255
|
logAttachmentAttempted: shouldAttachLogs,
|
|
256
|
+
failureActionSection: actionSection,
|
|
262
257
|
});
|
|
263
258
|
const posted = await postComment({ $, owner, repo, targetNumber, body });
|
|
264
259
|
if (posted.ok) {
|
|
@@ -341,7 +341,9 @@ export const performSystemChecks = async (minDiskSpace = 10240, skipToolConnecti
|
|
|
341
341
|
} else if (argv.tool === 'agent') {
|
|
342
342
|
// Validate Agent connection
|
|
343
343
|
const agentLib = await import('./agent.lib.mjs');
|
|
344
|
-
isToolConnected = await agentLib.validateAgentConnection(model
|
|
344
|
+
isToolConnected = await agentLib.validateAgentConnection(model, {
|
|
345
|
+
requireLiveInput: !!(argv.autoInputUntilMergeable || argv.acceptIncommingCommentsAsInput),
|
|
346
|
+
});
|
|
345
347
|
if (!isToolConnected) {
|
|
346
348
|
await log('❌ Cannot proceed without Agent connection', { level: 'error' });
|
|
347
349
|
return false;
|