@link-assistant/hive-mind 1.54.3 → 1.54.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.54.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 2ac0a14: Notify the source issue when solve exits with a known issue but no pull request, including failure logs when `--attach-logs` is enabled.
8
+
3
9
  ## 1.54.3
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.54.3",
3
+ "version": "1.54.4",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -13,7 +13,7 @@
13
13
  "hive-telegram-bot": "./src/telegram-bot.mjs"
14
14
  },
15
15
  "scripts": {
16
- "test": "node tests/solve-queue.test.mjs && node tests/limits-display.test.mjs && node tests/test-usage-limit.mjs && node tests/test-codex-support.mjs && node tests/test-build-cost-info-string.mjs && node tests/test-claude-code-install-method.mjs && node tests/test-issue-1616-pr-issue-link-preservation.mjs && node tests/test-telegram-message-filters.mjs && node tests/test-telegram-bot-command-aliases.mjs && node tests/test-solve-queue-command.mjs && node tests/test-queue-display-1267.mjs && node tests/test-telegram-bot-launcher.mjs",
16
+ "test": "node tests/solve-queue.test.mjs && node tests/limits-display.test.mjs && node tests/test-usage-limit.mjs && node tests/test-codex-support.mjs && node tests/test-build-cost-info-string.mjs && node tests/test-claude-code-install-method.mjs && node tests/test-issue-1616-pr-issue-link-preservation.mjs && node tests/test-pre-pr-failure-notifier-1640.mjs && node tests/test-telegram-message-filters.mjs && node tests/test-telegram-bot-command-aliases.mjs && node tests/test-solve-queue-command.mjs && node tests/test-queue-display-1267.mjs && node tests/test-telegram-bot-launcher.mjs",
17
17
  "test:queue": "node tests/solve-queue.test.mjs",
18
18
  "test:limits-display": "node tests/limits-display.test.mjs",
19
19
  "test:usage-limit": "node tests/test-usage-limit.mjs",
@@ -27,6 +27,8 @@ let logFunction = null;
27
27
  let cleanupFunction = null;
28
28
  let interruptFunction = null;
29
29
  let interruptHandlerRan = false;
30
+ let preExitFunction = null;
31
+ let preExitHandlerRan = false;
30
32
 
31
33
  /**
32
34
  * Initialize the exit handler with required dependencies
@@ -36,11 +38,16 @@ let interruptHandlerRan = false;
36
38
  * @param {Function} interrupt - Optional interrupt function to call on SIGINT/SIGTERM before cleanup
37
39
  * (e.g., auto-commit uncommitted changes, upload logs)
38
40
  */
39
- export const initializeExitHandler = (getLogPath, log, cleanup = null, interrupt = null) => {
41
+ export const initializeExitHandler = (getLogPath, log, cleanup = null, interrupt = null, preExit = null) => {
40
42
  getLogPathFunction = getLogPath;
41
43
  logFunction = log;
42
44
  cleanupFunction = cleanup;
43
45
  interruptFunction = interrupt;
46
+ preExitFunction = preExit;
47
+ };
48
+
49
+ export const setPreExitHandler = preExit => {
50
+ preExitFunction = preExit;
44
51
  };
45
52
 
46
53
  /**
@@ -200,6 +207,20 @@ export const logActiveHandles = async (log = null) => {
200
207
  export const safeExit = async (code = 0, reason = 'Process completed') => {
201
208
  await showExitMessage(reason, code);
202
209
 
210
+ if (code !== 0 && preExitFunction && !preExitHandlerRan) {
211
+ preExitHandlerRan = true;
212
+ try {
213
+ await preExitFunction({ code, reason });
214
+ } catch (error) {
215
+ const message = error && error.message ? error.message : String(error);
216
+ if (logFunction) {
217
+ await logFunction(`⚠️ Pre-exit handler failed: ${message}`, { level: 'warning' });
218
+ } else {
219
+ console.warn(`⚠️ Pre-exit handler failed: ${message}`);
220
+ }
221
+ }
222
+ }
223
+
203
224
  // Issue #1431: Drain/unref active handles so the event loop exits naturally.
204
225
  // This resolves the root causes of dangling ReadStream (stdin), Socket (undici),
205
226
  // ChildProcess (command-stream), and WriteStream (stdout/stderr) handles.
@@ -70,6 +70,7 @@ export const handleFailure = async options => {
70
70
  });
71
71
  if (logUploadSuccess) {
72
72
  await log(`📎 Failure log attached to ${targetLabel}`);
73
+ if (!hasPR && hasIssue) global.prePullRequestFailureNotificationPosted = true;
73
74
  }
74
75
  } catch (attachError) {
75
76
  reportError(attachError, {
package/src/solve.mjs CHANGED
@@ -41,6 +41,7 @@ const { formatResetTimeWithRelative } = usageLimitLib;
41
41
 
42
42
  const errorHandlers = await import('./solve.error-handlers.lib.mjs');
43
43
  const { createUncaughtExceptionHandler, createUnhandledRejectionHandler, handleMainExecutionError, handleNoPrAvailableError } = errorHandlers;
44
+ const { notifyIssueAboutPrePullRequestFailure } = await import('./solve.pre-pr-failure-notifier.lib.mjs');
44
45
 
45
46
  const watchLib = await import('./solve.watch.lib.mjs');
46
47
  const { startWatchMode } = watchLib;
@@ -132,7 +133,7 @@ const cleanupWrapper = async () => {
132
133
  }
133
134
  };
134
135
  const interruptWrapper = createInterruptWrapper({ cleanupContext, checkForUncommittedChanges, shouldAttachLogs, attachLogToGitHub, getLogFile, sanitizeLogContent, $, log });
135
- initializeExitHandler(getAbsoluteLogPath, log, cleanupWrapper, interruptWrapper);
136
+ initializeExitHandler(getAbsoluteLogPath, log, cleanupWrapper, interruptWrapper, ({ code, reason }) => notifyIssueAboutPrePullRequestFailure({ code, reason, argv, globalState: global, $, log, getLogFile, shouldAttachLogs, attachLogToGitHub, sanitizeLogContent, rawCommand }));
136
137
  installGlobalExitHandlers();
137
138
 
138
139
  // Now handle argument validation that was moved from early checks
@@ -0,0 +1,107 @@
1
+ import { getTrackedToolCommentIds, postTrackedComment, SOLUTION_DRAFT_FAILED_MARKER } from './tool-comments.lib.mjs';
2
+
3
+ const truncate = (value, maxLength = 2000) => {
4
+ const text = value === null || value === undefined ? '' : String(value);
5
+ if (text.length <= maxLength) return text;
6
+ return `${text.slice(0, maxLength - 22)}\n... truncated ...`;
7
+ };
8
+
9
+ const fence = value => truncate(value || 'Unknown error').replaceAll('```', '` ` `');
10
+
11
+ export function shouldNotifyIssueAboutPrePullRequestFailure({ code, globalState }) {
12
+ if (code === 0) return false;
13
+ if (!globalState?.issueNumber || !globalState?.owner || !globalState?.repo) return false;
14
+ if (globalState?.createdPR?.number) return false;
15
+ if (globalState.prePullRequestFailureNotificationPosted || globalState.prePullRequestFailureNotificationInProgress) return false;
16
+ return getTrackedToolCommentIds().size === 0;
17
+ }
18
+
19
+ export function buildPrePullRequestFailureComment({ reason, owner, repo, issueNumber, argv = {}, rawCommand = null, logAttachmentAttempted = false }) {
20
+ const tool = argv.tool || 'claude';
21
+ const modelLine = argv.model ? `\n- **Requested model**: \`${argv.model}\`` : '';
22
+ const commandBlock = rawCommand
23
+ ? `
24
+
25
+ ### Command
26
+ \`\`\`bash
27
+ ${fence(rawCommand)}
28
+ \`\`\``
29
+ : '';
30
+ 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.';
31
+
32
+ return `## 🚨 ${SOLUTION_DRAFT_FAILED_MARKER}
33
+
34
+ The automated solver stopped before creating a pull request, so no PR was opened for this issue.
35
+
36
+ ### Failure
37
+ - **Repository**: \`${owner}/${repo}\`
38
+ - **Issue**: #${issueNumber}
39
+ - **Tool**: \`${tool}\`${modelLine}
40
+
41
+ **Reason**
42
+ \`\`\`text
43
+ ${fence(reason)}
44
+ \`\`\`${commandBlock}
45
+
46
+ ${logLine}
47
+
48
+ Please resolve the reported problem and rerun the solve command.`;
49
+ }
50
+
51
+ export async function notifyIssueAboutPrePullRequestFailure(options) {
52
+ const { code, reason, argv = {}, globalState = globalThis, $, log = async () => {}, getLogFile, shouldAttachLogs = false, attachLogToGitHub, sanitizeLogContent, rawCommand = null, postComment = postTrackedComment } = options;
53
+
54
+ if (!shouldNotifyIssueAboutPrePullRequestFailure({ code, globalState })) {
55
+ return { notified: false, skipped: true };
56
+ }
57
+
58
+ const owner = globalState.owner;
59
+ const repo = globalState.repo;
60
+ const issueNumber = globalState.issueNumber;
61
+ globalState.prePullRequestFailureNotificationInProgress = true;
62
+
63
+ try {
64
+ if (shouldAttachLogs && getLogFile && attachLogToGitHub && sanitizeLogContent) {
65
+ await log(`\n📄 Notifying issue #${issueNumber} about pre-PR failure with logs...`);
66
+ const uploaded = await attachLogToGitHub({
67
+ logFile: getLogFile(),
68
+ targetType: 'issue',
69
+ targetNumber: issueNumber,
70
+ owner,
71
+ repo,
72
+ $,
73
+ log,
74
+ sanitizeLogContent,
75
+ verbose: argv.verbose,
76
+ errorMessage: `The solver stopped before creating a pull request.\n\nReason: ${reason || 'Unknown error'}`,
77
+ requestedModel: argv.model,
78
+ tool: argv.tool || 'claude',
79
+ });
80
+ if (uploaded) {
81
+ globalState.prePullRequestFailureNotificationPosted = true;
82
+ return { notified: true, method: 'log-upload' };
83
+ }
84
+ }
85
+
86
+ await log(`\n💬 Notifying issue #${issueNumber} about pre-PR failure...`);
87
+ const body = buildPrePullRequestFailureComment({
88
+ reason,
89
+ owner,
90
+ repo,
91
+ issueNumber,
92
+ argv,
93
+ rawCommand,
94
+ logAttachmentAttempted: shouldAttachLogs,
95
+ });
96
+ const posted = await postComment({ $, owner, repo, targetNumber: issueNumber, body });
97
+ if (posted.ok) {
98
+ globalState.prePullRequestFailureNotificationPosted = true;
99
+ await log(` ✅ Pre-PR failure comment posted to issue #${issueNumber}${posted.commentId ? ` (id=${posted.commentId})` : ''}`);
100
+ return { notified: true, method: 'comment', commentId: posted.commentId || null };
101
+ }
102
+ await log(` ⚠️ Could not post pre-PR failure comment: ${posted.stderr || 'unknown error'}`, { level: 'warning' });
103
+ return { notified: false, error: posted.stderr || 'unknown error' };
104
+ } finally {
105
+ globalState.prePullRequestFailureNotificationInProgress = false;
106
+ }
107
+ }