@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.
@@ -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: Stage 1 introduces this flag inert — it parses, appears in
246
- // --help, and is read by validateAutoInputUntilMergeable below, but does not
247
- // change the runtime loop yet. Stages 2-6 will wire it into watchUntilMergeable
248
- // and the bidirectional NDJSON pipe (see docs/case-studies/issue-1708/).
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] Extend a single AI tool session as long as possible by streaming new input (uncommitted changes, CI/CD failures, PR/issue comments, issue title/body updates) directly into the running session, instead of restarting it. Implies --accept-incomming-comments-as-input and --queue-comments-to-input by default (comments are deferred until the AI finishes the current step and is waiting for input). Existing auto-restart/auto-resume loops remain enabled as a fallback, but the goal is to keep them dormant. The full streaming-aware watchUntilMergeable replacement and per-tool wiring is staged in subsequent PRs (see docs/case-studies/issue-1708/). Falls back gracefully on non-Claude tools and on streaming errors. Disabled by default.',
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 Claude during execution (excludes outgoing comments generated by solve itself). Does not require --interactive-mode; disabled by default. Only supported for --tool claude.',
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 Claude as soon as
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 log('');
174
- await log(' 🔍 What happened:');
175
- await log(` Your fork's ${upstreamDefaultBranch} branch has different commits than upstream`);
176
- await log(' This typically occurs when upstream had a force push (e.g., git reset --hard)');
177
- await log('');
178
- await log(' 📦 Current state:');
179
- await log(` • Fork: ${forkedRepo}`);
180
- await log(` • Upstream: ${owner}/${repo}`);
181
- await log(` • Branch: ${upstreamDefaultBranch}`);
182
- await log('');
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
- // Flag is not enabled - provide guidance
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 const FORK_DIVERGENCE_RESOLUTION_OPTION = '--allow-fork-divergence-resolution-using-force-push-with-lease';
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
- - If you control \`${repository}\`, back up any needed work, then delete, rename, archive, or repair it in GitHub and rerun the solver.
44
- - If you do not control \`${repository}\`, ask the repository owner or a Hive Mind administrator to clean it up and rerun the solver.
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 `### What you can do
52
- - If the fork's default branch can be overwritten safely, rerun with \`${FORK_DIVERGENCE_RESOLUTION_OPTION}\` to allow a guarded force-with-lease sync.
53
- - If the fork has commits you need to preserve, resolve the divergence manually, then rerun the solver.
54
- - If this requires elevated Hive Mind access, ask a Hive Mind administrator to handle the affected fork or repository.
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
- - If the branch belongs to you, merge the remote branch state or choose a new branch name, then rerun the solver.
66
- - Do not force-push unless you have manually confirmed that overwriting the remote branch is safe.
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
- - If the affected fork or repository belongs to you, remove, rename, archive, initialize, or otherwise repair it in GitHub, then rerun the solver.
74
- - If the action requires elevated Hive Mind access, ask a Hive Mind administrator to handle the affected fork or repository and rerun the solver.
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
- - If this requires elevated Hive Mind access, ask a Hive Mind administrator to handle the specific failure described above.
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: buildPrePullRequestFailureActionSection(reason),
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;