@link-assistant/hive-mind 2.10.2 ā 2.10.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 +12 -0
- package/package.json +1 -1
- package/src/solve.auto-merge.lib.mjs +4 -0
- package/src/solve.mjs +7 -8
- package/src/solve.results.lib.mjs +63 -10
- package/src/solve.watch.lib.mjs +4 -0
- package/src/use-m-bootstrap.lib.mjs +8 -1
- package/src/use-with-retry.lib.mjs +37 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 2.10.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f1d5026: Recover use-m dependency imports when an installed package is missing an internal module or alias cleanup hits a transient filesystem race by removing the incomplete alias with bounded retries and reloading it. The CDN bootstrap fallback is also repinned from use-m 8.13.8 to 8.14.4 so a CDN outage no longer downgrades dependency loading to a use-m without corrupt-alias recovery.
|
|
8
|
+
|
|
9
|
+
## 2.10.3
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 3cfd6c2: Show cost and token/context budget facts in automated working-session summaries.
|
|
14
|
+
|
|
3
15
|
## 2.10.2
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1068,6 +1068,10 @@ No further AI sessions will be started automatically for this run. Please review
|
|
|
1068
1068
|
prNumber,
|
|
1069
1069
|
issueNumber,
|
|
1070
1070
|
success: true,
|
|
1071
|
+
publicPricingEstimate: toolResult.publicPricingEstimate,
|
|
1072
|
+
anthropicTotalCostUSD: latestAnthropicCost,
|
|
1073
|
+
pricingInfo: toolResult.pricingInfo,
|
|
1074
|
+
budgetStatsData: autoMergeBudgetStatsData,
|
|
1071
1075
|
});
|
|
1072
1076
|
} catch (summaryError) {
|
|
1073
1077
|
reportError(summaryError, {
|
package/src/solve.mjs
CHANGED
|
@@ -1228,13 +1228,8 @@ try {
|
|
|
1228
1228
|
|
|
1229
1229
|
// 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
1230
|
await finalizeDevelopmentLog();
|
|
1231
|
-
// Issue #1263 / #1728:
|
|
1232
|
-
|
|
1233
|
-
// top-level solve, auto-restart-until-mergeable, and watch-mode iterations
|
|
1234
|
-
// all use identical attach logic. The helper internally honours
|
|
1235
|
-
// --attach-solution-summary (always attach) and --auto-attach-solution-summary
|
|
1236
|
-
// (attach only if no AI comment was posted during the session).
|
|
1237
|
-
await maybeAttachWorkingSessionSummary({
|
|
1231
|
+
// Issue #1263 / #1728 / #2115: shared summary attachment and usage display.
|
|
1232
|
+
const { budgetStatsData: workingSessionBudgetStatsData = null } = await maybeAttachWorkingSessionSummary({
|
|
1238
1233
|
argv,
|
|
1239
1234
|
resultSummary,
|
|
1240
1235
|
workStartTime,
|
|
@@ -1243,10 +1238,14 @@ try {
|
|
|
1243
1238
|
prNumber,
|
|
1244
1239
|
issueNumber,
|
|
1245
1240
|
success,
|
|
1241
|
+
publicPricingEstimate,
|
|
1242
|
+
anthropicTotalCostUSD,
|
|
1243
|
+
pricingInfo,
|
|
1244
|
+
sessionUsage: { sessionId, tempDir, resultModelUsage, streamTokenUsage, subAgentCalls },
|
|
1246
1245
|
});
|
|
1247
1246
|
|
|
1248
1247
|
// Search for newly created pull requests and comments
|
|
1249
|
-
const verifyResult = await verifyResults(owner, repo, branchName, issueNumber, prNumber, prUrl, referenceTime, argv, shouldAttachLogs, shouldRestart, sessionId, tempDir, anthropicTotalCostUSD, publicPricingEstimate, pricingInfo, errorDuringExecution, sessionType, resultModelUsage, streamTokenUsage, subAgentCalls);
|
|
1248
|
+
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);
|
|
1250
1249
|
const logsAlreadyUploaded = verifyResult?.logUploadSuccess || false;
|
|
1251
1250
|
|
|
1252
1251
|
// Issue #1162: Auto-restart when PR title/description still has placeholder content
|
|
@@ -28,6 +28,8 @@ import { safeExit } from './exit-handler.lib.mjs';
|
|
|
28
28
|
// Import GitHub-related functions
|
|
29
29
|
const githubLib = await import('./github.lib.mjs');
|
|
30
30
|
const { sanitizeLogContent, attachLogToGitHub } = githubLib;
|
|
31
|
+
const { buildCostInfoString } = await import('./github-cost-info.lib.mjs');
|
|
32
|
+
const { buildBudgetStatsString } = await import('./claude.budget-stats.lib.mjs');
|
|
31
33
|
|
|
32
34
|
// Issue #1745: process-wide sanitization counters used to print a one-line
|
|
33
35
|
// "we masked N secrets" summary at the end of each run.
|
|
@@ -663,11 +665,9 @@ export const showSessionSummary = async (sessionId, limitReached, argv, issueUrl
|
|
|
663
665
|
}
|
|
664
666
|
};
|
|
665
667
|
|
|
666
|
-
//
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
// Issue #1491, #1526: Build budget stats data for GitHub comment (computed once, used in both PR and issue paths)
|
|
668
|
+
// Build token/context data once so every end-of-session publication can use the
|
|
669
|
+
// same observed facts (working-session summary and attached log alike).
|
|
670
|
+
export const buildSessionBudgetStatsData = async ({ argv, sessionId = null, tempDir = null, resultModelUsage = null, streamTokenUsage = null, subAgentCalls = null, pricingInfo = null }) => {
|
|
671
671
|
let budgetStatsData = null;
|
|
672
672
|
if (argv.tokensBudgetStats && sessionId && tempDir) {
|
|
673
673
|
try {
|
|
@@ -692,6 +692,26 @@ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumb
|
|
|
692
692
|
if (argv.verbose) await log(` ā ļø Could not build agent budget stats: ${agentBudgetError.message}`, { verbose: true });
|
|
693
693
|
}
|
|
694
694
|
}
|
|
695
|
+
return budgetStatsData;
|
|
696
|
+
};
|
|
697
|
+
|
|
698
|
+
// Verify results by searching for new PRs and comments
|
|
699
|
+
export const verifyResults = async (owner, repo, branchName, issueNumber, prNumber, prUrl, referenceTime, argv, shouldAttachLogs, shouldRestart = false, sessionId = null, tempDir = null, anthropicTotalCostUSD = null, publicPricingEstimate = null, pricingInfo = null, errorDuringExecution = false, sessionType = 'new', resultModelUsage = null, streamTokenUsage = null, subAgentCalls = null, precomputedBudgetStatsData = null) => {
|
|
700
|
+
await log('\nš Searching for created pull requests or comments...');
|
|
701
|
+
|
|
702
|
+
// Issue #1491, #1526, #2115: reuse data already calculated for the working
|
|
703
|
+
// session summary; retain the fallback for callers that do not precompute it.
|
|
704
|
+
const budgetStatsData =
|
|
705
|
+
precomputedBudgetStatsData ??
|
|
706
|
+
(await buildSessionBudgetStatsData({
|
|
707
|
+
argv,
|
|
708
|
+
sessionId,
|
|
709
|
+
tempDir,
|
|
710
|
+
resultModelUsage,
|
|
711
|
+
streamTokenUsage,
|
|
712
|
+
subAgentCalls,
|
|
713
|
+
pricingInfo,
|
|
714
|
+
}));
|
|
695
715
|
|
|
696
716
|
try {
|
|
697
717
|
// Get the current user's GitHub username
|
|
@@ -1241,7 +1261,15 @@ export const checkForAiCreatedComments = async (sessionStartTime, owner, repo, p
|
|
|
1241
1261
|
* @param {string} options.repo - Repository name
|
|
1242
1262
|
* @returns {Promise<boolean>} - True if comment was posted successfully
|
|
1243
1263
|
*/
|
|
1244
|
-
export const
|
|
1264
|
+
export const buildWorkingSessionSummaryDetails = ({ publicPricingEstimate = null, anthropicTotalCostUSD = null, pricingInfo = null, budgetStatsData = null } = {}) => {
|
|
1265
|
+
const costInfo = buildCostInfoString(publicPricingEstimate, anthropicTotalCostUSD, pricingInfo, {
|
|
1266
|
+
includeTokenUsage: false,
|
|
1267
|
+
});
|
|
1268
|
+
const budgetStats = budgetStatsData ? buildBudgetStatsString(budgetStatsData.tokenUsage, budgetStatsData.subAgentCalls) : '';
|
|
1269
|
+
return `${costInfo}${budgetStats}`.trim();
|
|
1270
|
+
};
|
|
1271
|
+
|
|
1272
|
+
export const attachSolutionSummary = async ({ resultSummary, prNumber, issueNumber, owner, repo, publicPricingEstimate = null, anthropicTotalCostUSD = null, pricingInfo = null, budgetStatsData = null }) => {
|
|
1245
1273
|
if (!resultSummary || typeof resultSummary !== 'string') {
|
|
1246
1274
|
await log('ā ļø No working session summary available to attach', { verbose: true });
|
|
1247
1275
|
return false;
|
|
@@ -1256,10 +1284,16 @@ export const attachSolutionSummary = async ({ resultSummary, prNumber, issueNumb
|
|
|
1256
1284
|
}
|
|
1257
1285
|
|
|
1258
1286
|
try {
|
|
1287
|
+
const usageDetails = buildWorkingSessionSummaryDetails({
|
|
1288
|
+
publicPricingEstimate,
|
|
1289
|
+
anthropicTotalCostUSD,
|
|
1290
|
+
pricingInfo,
|
|
1291
|
+
budgetStatsData,
|
|
1292
|
+
});
|
|
1259
1293
|
const comment = `${toolComments.WORKING_SESSION_SUMMARY_AUTOMATION_MARKER}
|
|
1260
1294
|
## ${toolComments.WORKING_SESSION_SUMMARY_MARKER}
|
|
1261
1295
|
|
|
1262
|
-
${resultSummary}
|
|
1296
|
+
${resultSummary}${usageDetails ? `\n\n${usageDetails}` : ''}
|
|
1263
1297
|
|
|
1264
1298
|
---
|
|
1265
1299
|
*${toolComments.WORKING_SESSION_SUMMARY_AUTOMATED_FOOTER}*`;
|
|
@@ -1316,7 +1350,7 @@ ${resultSummary}
|
|
|
1316
1350
|
* @param {boolean} [options.success=true] - skip attachment for failed iterations
|
|
1317
1351
|
* @returns {Promise<{attached: boolean, reason: string}>}
|
|
1318
1352
|
*/
|
|
1319
|
-
export const maybeAttachWorkingSessionSummary = async ({ argv, resultSummary, workStartTime, owner, repo, prNumber, issueNumber, success = true }) => {
|
|
1353
|
+
export const maybeAttachWorkingSessionSummary = async ({ argv, resultSummary, workStartTime, owner, repo, prNumber, issueNumber, success = true, publicPricingEstimate = null, anthropicTotalCostUSD = null, pricingInfo = null, budgetStatsData = null, sessionUsage = null }) => {
|
|
1320
1354
|
if (!success) {
|
|
1321
1355
|
return { attached: false, reason: 'iteration_failed' };
|
|
1322
1356
|
}
|
|
@@ -1352,6 +1386,25 @@ export const maybeAttachWorkingSessionSummary = async ({ argv, resultSummary, wo
|
|
|
1352
1386
|
return { attached: false, reason: 'no_attach_decision' };
|
|
1353
1387
|
}
|
|
1354
1388
|
|
|
1355
|
-
const
|
|
1356
|
-
|
|
1389
|
+
const resolvedBudgetStatsData =
|
|
1390
|
+
budgetStatsData ??
|
|
1391
|
+
(sessionUsage
|
|
1392
|
+
? await buildSessionBudgetStatsData({
|
|
1393
|
+
argv,
|
|
1394
|
+
pricingInfo,
|
|
1395
|
+
...sessionUsage,
|
|
1396
|
+
})
|
|
1397
|
+
: null);
|
|
1398
|
+
const ok = await attachSolutionSummary({
|
|
1399
|
+
resultSummary,
|
|
1400
|
+
prNumber,
|
|
1401
|
+
issueNumber,
|
|
1402
|
+
owner,
|
|
1403
|
+
repo,
|
|
1404
|
+
publicPricingEstimate,
|
|
1405
|
+
anthropicTotalCostUSD,
|
|
1406
|
+
pricingInfo,
|
|
1407
|
+
budgetStatsData: resolvedBudgetStatsData,
|
|
1408
|
+
});
|
|
1409
|
+
return { attached: !!ok, reason: ok ? 'attached' : 'post_failed', budgetStatsData: resolvedBudgetStatsData };
|
|
1357
1410
|
};
|
package/src/solve.watch.lib.mjs
CHANGED
|
@@ -580,6 +580,10 @@ export const watchForFeedback = async params => {
|
|
|
580
580
|
prNumber,
|
|
581
581
|
issueNumber,
|
|
582
582
|
success: true,
|
|
583
|
+
publicPricingEstimate: toolResult.publicPricingEstimate,
|
|
584
|
+
anthropicTotalCostUSD: latestAnthropicCost,
|
|
585
|
+
pricingInfo: toolResult.pricingInfo,
|
|
586
|
+
budgetStatsData: autoRestartBudgetStatsData,
|
|
583
587
|
});
|
|
584
588
|
} catch (summaryError) {
|
|
585
589
|
reportError(summaryError, {
|
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
import { wrapUseWithRetry } from './use-with-retry.lib.mjs';
|
|
4
4
|
|
|
5
5
|
export const USE_M_BOOTSTRAP_URL = 'https://unpkg.com/use-m/use.js';
|
|
6
|
-
|
|
6
|
+
// Issue #2113: the fallback is only reached when unpkg cannot serve the `latest`
|
|
7
|
+
// bundle, but until now it pinned 8.13.8 ā the last release *without* any
|
|
8
|
+
// corrupt-alias self-healing. A CDN hiccup therefore silently downgraded every
|
|
9
|
+
// dependency import to the least resilient loader available. 8.14.4 is the first
|
|
10
|
+
// release that both repairs corrupt aliases (8.14.3, use-m #66/#67) and removes
|
|
11
|
+
// them with a retry budget (8.14.4, use-m #68), so the degraded path now keeps
|
|
12
|
+
// upstream recovery instead of losing it.
|
|
13
|
+
export const USE_M_BOOTSTRAP_FALLBACK_URL = 'https://unpkg.com/use-m@8.14.4/use.js';
|
|
7
14
|
|
|
8
15
|
const isMissingUseMBundle = code => /^Not found: \/use-m@[^/]+\/use\.js\s*$/.test(code.trim());
|
|
9
16
|
|
|
@@ -16,13 +16,21 @@
|
|
|
16
16
|
* 3. Node throws `Invalid package config <dir>/package.json.` with
|
|
17
17
|
* `code: 'ERR_INVALID_PACKAGE_CONFIG'` ā the package.json itself
|
|
18
18
|
* is corrupt/truncated and cannot even be parsed (issue #1712).
|
|
19
|
+
* 4. Node throws `ERR_MODULE_NOT_FOUND` for a file imported by the package
|
|
20
|
+
* entry point ā npm left an incomplete package tree (issue #2113).
|
|
21
|
+
* 5. use-m's self-heal throws `Failed to remove corrupt npm alias ...`
|
|
22
|
+
* because a concurrent filesystem mutation made recursive removal fail
|
|
23
|
+
* with a retryable code such as `ENOTEMPTY` (issue #2113).
|
|
19
24
|
*
|
|
20
|
-
* The recovery is identical for all
|
|
25
|
+
* The recovery is identical for all five: delete the broken alias install
|
|
21
26
|
* directory and ask use-m to re-fetch. A clean reinstall almost always
|
|
22
27
|
* succeeds. This helper centralises that retry so every call site picks
|
|
23
28
|
* it up.
|
|
24
29
|
*/
|
|
25
30
|
|
|
31
|
+
const ALIAS_CLEANUP_ERROR = /^Failed to remove (?:corrupt|incomplete) npm alias '([^']+)'\.$/;
|
|
32
|
+
const RETRYABLE_RM_CODES = new Set(['EBUSY', 'EMFILE', 'ENFILE', 'ENOTEMPTY', 'EPERM']);
|
|
33
|
+
|
|
26
34
|
/**
|
|
27
35
|
* @param {(specifier: string) => Promise<unknown>} use - the use-m loader.
|
|
28
36
|
* @param {string} specifier - the npm specifier to load (e.g. `'getenv'`).
|
|
@@ -94,8 +102,9 @@ export const useWithRetry = async (use, specifier, options = {}) => {
|
|
|
94
102
|
// Remember the file so the next attempt can bypass Node's poisoned
|
|
95
103
|
// module cache if use-m hands us the same path again.
|
|
96
104
|
cleanedImportPath = /Failed to import module from '/.test(error?.message ?? '') ? corruptedPath : null;
|
|
97
|
-
} catch {
|
|
105
|
+
} catch (cleanupError) {
|
|
98
106
|
// Best-effort cleanup; fall through to retry regardless.
|
|
107
|
+
log(`cleanup of ${resolveAliasDir(corruptedPath)} failed: ${cleanupError?.message}`);
|
|
99
108
|
}
|
|
100
109
|
}
|
|
101
110
|
}
|
|
@@ -128,9 +137,23 @@ export const isCorruptInstallError = error => {
|
|
|
128
137
|
// resolve/import logic gets a chance to run.
|
|
129
138
|
if (error?.code === 'ERR_INVALID_PACKAGE_CONFIG') return true;
|
|
130
139
|
if (cause?.code === 'ERR_INVALID_PACKAGE_CONFIG') return true;
|
|
140
|
+
// Mode 4 (issue #2113): the installed entry point exists, but one of the
|
|
141
|
+
// files it imports does not. Restrict this to use-m's import wrapper so an
|
|
142
|
+
// unrelated application-level ERR_MODULE_NOT_FOUND is not retried.
|
|
143
|
+
const message = typeof error?.message === 'string' ? error.message : '';
|
|
144
|
+
if (/^Failed to import module from '/.test(message) && cause?.code === 'ERR_MODULE_NOT_FOUND') return true;
|
|
145
|
+
// Mode 5 (issue #2113): use-m@8.14.3 added its own alias self-heal, but its
|
|
146
|
+
// recursive rm used Node's default maxRetries=0, so a concurrent mutation
|
|
147
|
+
// escaped as ENOTEMPTY (or another rm-retry code). use-m@8.14.4 fixed this
|
|
148
|
+
// upstream (use-m #68) by giving removePackageAlias its own retry budget.
|
|
149
|
+
// The downstream guard is deliberately kept: the bootstrap can still land on
|
|
150
|
+
// an older use-m (a pinned CDN fallback, a preinstalled global, or a cached
|
|
151
|
+
// bundle), and even 8.14.4 rethrows this exact wrapper once its own budget is
|
|
152
|
+
// exhausted ā in which case a fresh removal plus reinstall is still the right
|
|
153
|
+
// recovery.
|
|
154
|
+
if (ALIAS_CLEANUP_ERROR.test(message) && RETRYABLE_RM_CODES.has(cause?.code)) return true;
|
|
131
155
|
// Mode 2 (also seen on hosted CI): npm install completes but the package
|
|
132
156
|
// tree is incomplete, so use-m can't resolve the entry point.
|
|
133
|
-
const message = typeof error?.message === 'string' ? error.message : '';
|
|
134
157
|
if (/^Failed to resolve the path to /.test(message)) return true;
|
|
135
158
|
// Fallback string match for ERR_INVALID_PACKAGE_CONFIG (in case the error
|
|
136
159
|
// bubbles through use-m without preserving the `code` property).
|
|
@@ -150,7 +173,10 @@ export const extractCorruptedFilePath = error => {
|
|
|
150
173
|
// extract the package.json path so the caller's cleanup() walks up to
|
|
151
174
|
// the alias dir.
|
|
152
175
|
const invalidConfigMatch = message.match(/Invalid package config (\S+?package\.json)/);
|
|
153
|
-
|
|
176
|
+
if (invalidConfigMatch) return invalidConfigMatch[1];
|
|
177
|
+
// Mode 5 (issue #2113): use-m already reports the whole alias directory.
|
|
178
|
+
const cleanupMatch = message.match(ALIAS_CLEANUP_ERROR);
|
|
179
|
+
return cleanupMatch ? cleanupMatch[1] : null;
|
|
154
180
|
};
|
|
155
181
|
|
|
156
182
|
/**
|
|
@@ -176,11 +202,15 @@ export const resolveAliasDir = corruptedPath => {
|
|
|
176
202
|
return segments.slice(0, -1).join('/') || corruptedPath;
|
|
177
203
|
};
|
|
178
204
|
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
205
|
+
export const removeAliasWithRetry = async (path, options = {}) => {
|
|
206
|
+
const remove = options.rm ?? (await import('node:fs/promises')).rm;
|
|
207
|
+
const maxRetries = options.maxRetries ?? 5;
|
|
208
|
+
const retryDelay = options.retryDelay ?? 100;
|
|
209
|
+
await remove(path, { recursive: true, force: true, maxRetries, retryDelay });
|
|
182
210
|
};
|
|
183
211
|
|
|
212
|
+
const defaultCleanup = removeAliasWithRetry;
|
|
213
|
+
|
|
184
214
|
// Cache-busting import: a query string makes Node treat the URL as a distinct
|
|
185
215
|
// module, so the freshly reinstalled file is evaluated instead of the cached
|
|
186
216
|
// SyntaxError from the corrupt one.
|