@link-assistant/hive-mind 1.78.1 → 1.78.2

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.78.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 70db26f: Ensure Telegram work-session completion messages recover pull request links from completed solve logs when linked-issue lookup does not return a PR.
8
+
3
9
  ## 1.78.1
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.78.1",
3
+ "version": "1.78.2",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -15,8 +15,9 @@
15
15
  * @see https://github.com/link-assistant/hive-mind/issues/380
16
16
  */
17
17
 
18
- import { promisify } from 'util';
19
18
  import { exec as execCallback } from 'child_process';
19
+ import fs from 'fs/promises';
20
+ import { promisify } from 'util';
20
21
  import { formatSessionCompletionMessage, getSessionCompletionExitCode } from './work-session-formatting.lib.mjs';
21
22
  import { notifySubscribers, getSubscriberCount } from './telegram-subscribers.lib.mjs';
22
23
 
@@ -157,6 +158,45 @@ function normalizeSessionUrl(url) {
157
158
  return url.replace(/#.*$/, '').replace(/\/+$/, '').toLowerCase();
158
159
  }
159
160
 
161
+ const GITHUB_PULL_REQUEST_URL_RE = /https:\/\/github\.com\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)\/pull\/([0-9]+)/g;
162
+
163
+ export function extractPullRequestUrlFromText(text, { owner = null, repo = null } = {}) {
164
+ if (!text) return null;
165
+
166
+ const expectedOwner = owner ? String(owner).toLowerCase() : null;
167
+ const expectedRepo = repo ? String(repo).toLowerCase() : null;
168
+ const value = String(text);
169
+ GITHUB_PULL_REQUEST_URL_RE.lastIndex = 0;
170
+
171
+ let match;
172
+ while ((match = GITHUB_PULL_REQUEST_URL_RE.exec(value)) !== null) {
173
+ const [, matchOwner, matchRepo, pullNumber] = match;
174
+ if (expectedOwner && matchOwner.toLowerCase() !== expectedOwner) continue;
175
+ if (expectedRepo && matchRepo.toLowerCase() !== expectedRepo) continue;
176
+ return `https://github.com/${matchOwner}/${matchRepo}/pull/${pullNumber}`;
177
+ }
178
+
179
+ return null;
180
+ }
181
+
182
+ async function resolvePullRequestUrlFromSessionLog(logPath, ctx, { verbose = false, readFile = fs.readFile } = {}) {
183
+ if (!logPath) return null;
184
+
185
+ try {
186
+ const logText = await readFile(logPath, 'utf8');
187
+ const pullRequestUrl = extractPullRequestUrlFromText(logText, { owner: ctx.owner, repo: ctx.repo });
188
+ if (pullRequestUrl && verbose) {
189
+ console.log(`[VERBOSE] Found PR ${pullRequestUrl} in completed session log ${logPath}`);
190
+ }
191
+ return pullRequestUrl;
192
+ } catch (error) {
193
+ if (verbose) {
194
+ console.log(`[VERBOSE] Could not inspect session log ${logPath} for PR URL: ${error?.message || error}`);
195
+ }
196
+ return null;
197
+ }
198
+ }
199
+
160
200
  function isNonIsolationSessionActive(sessionName, sessionInfo, verbose = false) {
161
201
  const startTime = sessionInfo.startTime instanceof Date ? sessionInfo.startTime : new Date(sessionInfo.startTime);
162
202
  const elapsed = Date.now() - startTime.getTime();
@@ -272,13 +312,19 @@ export async function monitorSessions(bot, verbose = false, options = {}) {
272
312
  try {
273
313
  const finalExitCode = getSessionCompletionExitCode({ exitCode, statusResult });
274
314
 
275
- // Issue #1688: When the original /solve URL was an issue, look up the
276
- // linked PR so the completion message can include both an `Issue:` and
277
- // a `Pull request:` line. Failures are logged and ignored — the
278
- // notification still goes out without the PR line.
315
+ // Issue #1688/#1905: When the original /solve URL was an issue, look up
316
+ // the created PR so the completion message can include both an
317
+ // `Issue:` and a `Pull request:` line. The linked-issue API can lag
318
+ // behind the solver's own verification log, so we also inspect the
319
+ // completed session log before giving up.
279
320
  let pullRequestUrl = null;
280
321
  try {
281
- pullRequestUrl = await resolvePullRequestUrlForSession(sessionInfo, { verbose, lookupLinkedPullRequest: options.lookupLinkedPullRequest });
322
+ pullRequestUrl = await resolvePullRequestUrlForSession(sessionInfo, {
323
+ verbose,
324
+ lookupLinkedPullRequest: options.lookupLinkedPullRequest,
325
+ statusResult,
326
+ readFile: options.readFile,
327
+ });
282
328
  } catch (lookupError) {
283
329
  if (verbose) {
284
330
  console.log(`[VERBOSE] Pull request lookup failed for ${sessionName}: ${lookupError?.message || lookupError}`);
@@ -395,36 +441,50 @@ export async function monitorSessions(bot, verbose = false, options = {}) {
395
441
  * @param {Object} [options]
396
442
  * @param {boolean} [options.verbose]
397
443
  * @param {Function} [options.lookupLinkedPullRequest] - Optional override `(ctx) => Promise<string|null>`
444
+ * @param {Object} [options.statusResult] - Completed start-command status payload, including logPath
445
+ * @param {Function} [options.readFile] - Optional test override for reading session logs
398
446
  * @returns {Promise<string|null>} PR URL or null
399
447
  *
400
448
  * @see https://github.com/link-assistant/hive-mind/issues/1688
449
+ * @see https://github.com/link-assistant/hive-mind/issues/1905
401
450
  */
402
- async function resolvePullRequestUrlForSession(sessionInfo, { verbose = false, lookupLinkedPullRequest = null } = {}) {
451
+ async function resolvePullRequestUrlForSession(sessionInfo, { verbose = false, lookupLinkedPullRequest = null, statusResult = null, readFile = fs.readFile } = {}) {
403
452
  const ctx = sessionInfo?.urlContext;
404
453
  if (!ctx || ctx.type !== 'issue' || !ctx.owner || !ctx.repo || !ctx.number) {
405
454
  return null;
406
455
  }
407
456
 
408
457
  if (typeof lookupLinkedPullRequest === 'function') {
409
- return await lookupLinkedPullRequest(ctx);
410
- }
411
-
412
- try {
413
- const { batchCheckPullRequestsForIssues } = await import('./github.lib.mjs');
414
- const result = await batchCheckPullRequestsForIssues(ctx.owner, ctx.repo, [ctx.number]);
415
- const linkedPRs = result?.[ctx.number]?.linkedPRs || [];
416
- if (linkedPRs.length > 0 && linkedPRs[0].url) {
458
+ const linkedPullRequestUrl = await lookupLinkedPullRequest(ctx);
459
+ if (linkedPullRequestUrl) return linkedPullRequestUrl;
460
+ } else {
461
+ try {
462
+ const { batchCheckPullRequestsForIssues } = await import('./github.lib.mjs');
463
+ const result = await batchCheckPullRequestsForIssues(ctx.owner, ctx.repo, [ctx.number]);
464
+ const linkedPRs = result?.[ctx.number]?.linkedPRs || [];
465
+ if (linkedPRs.length > 0 && linkedPRs[0].url) {
466
+ if (verbose) {
467
+ console.log(`[VERBOSE] Found linked PR ${linkedPRs[0].url} for issue ${ctx.owner}/${ctx.repo}#${ctx.number}`);
468
+ }
469
+ return linkedPRs[0].url;
470
+ }
471
+ } catch (error) {
417
472
  if (verbose) {
418
- console.log(`[VERBOSE] Found linked PR ${linkedPRs[0].url} for issue ${ctx.owner}/${ctx.repo}#${ctx.number}`);
473
+ console.log(`[VERBOSE] batchCheckPullRequestsForIssues failed for ${ctx.owner}/${ctx.repo}#${ctx.number}: ${error?.message || error}`);
419
474
  }
420
- return linkedPRs[0].url;
421
- }
422
- } catch (error) {
423
- if (verbose) {
424
- console.log(`[VERBOSE] batchCheckPullRequestsForIssues failed for ${ctx.owner}/${ctx.repo}#${ctx.number}: ${error?.message || error}`);
425
475
  }
426
- throw error;
427
476
  }
477
+
478
+ const logPath = statusResult?.logPath || sessionInfo?.logPath || null;
479
+ const pullRequestUrlFromLog = await resolvePullRequestUrlFromSessionLog(logPath, ctx, { verbose, readFile });
480
+ if (pullRequestUrlFromLog) return pullRequestUrlFromLog;
481
+
482
+ if (verbose && logPath) {
483
+ console.log(`[VERBOSE] No PR URL found for issue ${ctx.owner}/${ctx.repo}#${ctx.number} in session log ${logPath}`);
484
+ } else if (verbose) {
485
+ console.log(`[VERBOSE] No session log path available for PR URL fallback for issue ${ctx.owner}/${ctx.repo}#${ctx.number}`);
486
+ }
487
+
428
488
  return null;
429
489
  }
430
490