@link-assistant/hive-mind 2.11.5 → 2.11.7
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 +21 -0
- package/package.json +1 -1
- package/src/agent.lib.mjs +13 -10
- package/src/bidirectional-interactive.lib.mjs +2 -1
- package/src/budget-stats-policy.lib.mjs +31 -0
- package/src/claude.lib.mjs +13 -14
- package/src/codex.lib.mjs +29 -70
- package/src/codex.run-diagnostics.lib.mjs +117 -0
- package/src/formal-ai-runtime.lib.mjs +439 -0
- package/src/formal-ai.lib.mjs +143 -41
- package/src/gemini.lib.mjs +6 -8
- package/src/git.lib.mjs +10 -1
- package/src/github-error-reporter.lib.mjs +2 -1
- package/src/github-rate-limit.lib.mjs +13 -0
- package/src/github-terminal-state.lib.mjs +7 -1
- package/src/github.lib.mjs +16 -8
- package/src/opencode.lib.mjs +11 -10
- package/src/post-finish-sanitization-sweep.lib.mjs +4 -3
- package/src/quiet-probe.lib.mjs +72 -0
- package/src/qwen.lib.mjs +8 -7
- package/src/solve.auto-continue.lib.mjs +2 -1
- package/src/solve.auto-merge.lib.mjs +14 -15
- package/src/solve.auto-pr.lib.mjs +11 -5
- package/src/solve.branch-errors.lib.mjs +2 -1
- package/src/solve.config.lib.mjs +2 -2
- package/src/solve.execution.lib.mjs +2 -1
- package/src/solve.feedback.lib.mjs +18 -13
- package/src/solve.fork-sync.lib.mjs +2 -1
- package/src/solve.mjs +0 -2
- package/src/solve.repo-setup.lib.mjs +40 -4
- package/src/solve.repository.lib.mjs +4 -3
- package/src/solve.results.lib.mjs +32 -26
- package/src/solve.validation.lib.mjs +7 -0
- package/src/solve.watch.lib.mjs +15 -15
- package/src/token-sanitization.lib.mjs +7 -2
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { reportError } from './sentry.lib.mjs';
|
|
8
8
|
|
|
9
9
|
import { wrapDollarWithGhRetry as _wrapDollarWithGhRetry } from './github-rate-limit.lib.mjs'; // rate-limit marker (#1726): gh API calls flow through $ wrapped by caller
|
|
10
|
+
import { QUIET_PROBE, quietProbe } from './quiet-probe.lib.mjs'; // issue #2130: keep read-only probe payloads out of the attached log
|
|
10
11
|
// Issue #1827: tool-generated comments (markers + in-memory tracked IDs) must
|
|
11
12
|
// not count as feedback in watch/continue mode, mirroring checkForNonBotComments.
|
|
12
13
|
import { isToolGeneratedComment, isToolTrackedCommentId } from './tool-comments.lib.mjs';
|
|
@@ -22,7 +23,7 @@ export const detectAndCountFeedback = async params => {
|
|
|
22
23
|
|
|
23
24
|
// Get current GitHub user to filter out own comments
|
|
24
25
|
try {
|
|
25
|
-
const userResult = await
|
|
26
|
+
const userResult = await quietProbe($)`gh api user --jq .login`;
|
|
26
27
|
if (userResult.code === 0) {
|
|
27
28
|
currentUser = userResult.stdout.toString().trim();
|
|
28
29
|
await log(formatAligned('👤', 'Current user:', currentUser, 2));
|
|
@@ -63,7 +64,11 @@ export const detectAndCountFeedback = async params => {
|
|
|
63
64
|
|
|
64
65
|
// Get the last commit timestamp from the PR branch
|
|
65
66
|
let lastCommitTime = null;
|
|
66
|
-
|
|
67
|
+
// Issue #2130: quiet. These probes answer "when was the last commit?", and
|
|
68
|
+
// the answer is logged in words below; mirroring them put bare ISO dates
|
|
69
|
+
// and `git log`'s "unknown revision" complaint - the expected outcome for
|
|
70
|
+
// a branch that has never been pushed - into the attached log.
|
|
71
|
+
const git$ = repositoryPath ? $({ cwd: repositoryPath, ...QUIET_PROBE }) : quietProbe($);
|
|
67
72
|
let lastCommitResult = await git$`git log -1 --format="%aI" origin/${branchName}`;
|
|
68
73
|
if (lastCommitResult.code !== 0) {
|
|
69
74
|
// Fallback to local branch if remote doesn't exist
|
|
@@ -76,7 +81,7 @@ export const detectAndCountFeedback = async params => {
|
|
|
76
81
|
} else {
|
|
77
82
|
// Fallback: Get last commit time from GitHub API
|
|
78
83
|
try {
|
|
79
|
-
const prCommitsResult = await
|
|
84
|
+
const prCommitsResult = await quietProbe($)`gh api repos/${owner}/${repo}/pulls/${prNumber}/commits --paginate --jq 'last.commit.author.date'`;
|
|
80
85
|
if (prCommitsResult.code === 0 && prCommitsResult.stdout) {
|
|
81
86
|
lastCommitTime = new Date(prCommitsResult.stdout.toString().trim());
|
|
82
87
|
await log(formatAligned('📅', 'Last commit time (from API):', lastCommitTime.toISOString(), 2));
|
|
@@ -109,14 +114,14 @@ export const detectAndCountFeedback = async params => {
|
|
|
109
114
|
let prConversationComments = [];
|
|
110
115
|
|
|
111
116
|
// Get PR code review comments (use --paginate to get all comments, not just first page)
|
|
112
|
-
const prReviewCommentsResult = await
|
|
117
|
+
const prReviewCommentsResult = await quietProbe($)`gh api repos/${owner}/${repo}/pulls/${prNumber}/comments --paginate`;
|
|
113
118
|
if (prReviewCommentsResult.code === 0) {
|
|
114
119
|
prReviewComments = JSON.parse(prReviewCommentsResult.stdout.toString());
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
// Get PR conversation comments (PR is also an issue)
|
|
118
123
|
// Use --paginate to get all comments - GitHub API returns max 30 per page by default
|
|
119
|
-
const prConversationCommentsResult = await
|
|
124
|
+
const prConversationCommentsResult = await quietProbe($)`gh api repos/${owner}/${repo}/issues/${prNumber}/comments --paginate`;
|
|
120
125
|
if (prConversationCommentsResult.code === 0) {
|
|
121
126
|
prConversationComments = JSON.parse(prConversationCommentsResult.stdout.toString());
|
|
122
127
|
}
|
|
@@ -156,7 +161,7 @@ export const detectAndCountFeedback = async params => {
|
|
|
156
161
|
|
|
157
162
|
// Count new issue comments after last commit
|
|
158
163
|
// Use --paginate to get all comments - GitHub API returns max 30 per page by default
|
|
159
|
-
const issueCommentsResult = await
|
|
164
|
+
const issueCommentsResult = await quietProbe($)`gh api repos/${owner}/${repo}/issues/${issueNumber}/comments --paginate`;
|
|
160
165
|
if (issueCommentsResult.code === 0) {
|
|
161
166
|
const issueComments = JSON.parse(issueCommentsResult.stdout.toString());
|
|
162
167
|
const filteredIssueComments = issueComments.filter(comment => {
|
|
@@ -242,7 +247,7 @@ export const detectAndCountFeedback = async params => {
|
|
|
242
247
|
// started) should be considered feedback.
|
|
243
248
|
try {
|
|
244
249
|
// Check PR description edit time
|
|
245
|
-
const prDetailsResult = await
|
|
250
|
+
const prDetailsResult = await quietProbe($)`gh api repos/${owner}/${repo}/pulls/${prNumber}`;
|
|
246
251
|
if (prDetailsResult.code === 0) {
|
|
247
252
|
const prDetails = JSON.parse(prDetailsResult.stdout.toString());
|
|
248
253
|
const prUpdatedAt = new Date(prDetails.updated_at);
|
|
@@ -265,7 +270,7 @@ export const detectAndCountFeedback = async params => {
|
|
|
265
270
|
|
|
266
271
|
// Check issue description edit time if we have an issue
|
|
267
272
|
if (issueNumber) {
|
|
268
|
-
const issueDetailsResult = await
|
|
273
|
+
const issueDetailsResult = await quietProbe($)`gh api repos/${owner}/${repo}/issues/${issueNumber}`;
|
|
269
274
|
if (issueDetailsResult.code === 0) {
|
|
270
275
|
const issueDetails = JSON.parse(issueDetailsResult.stdout.toString());
|
|
271
276
|
const issueUpdatedAt = new Date(issueDetails.updated_at);
|
|
@@ -300,12 +305,12 @@ export const detectAndCountFeedback = async params => {
|
|
|
300
305
|
|
|
301
306
|
// 3. Check for new commits on default branch
|
|
302
307
|
try {
|
|
303
|
-
const defaultBranchResult = await
|
|
308
|
+
const defaultBranchResult = await quietProbe($)`gh api repos/${owner}/${repo}`;
|
|
304
309
|
if (defaultBranchResult.code === 0) {
|
|
305
310
|
const repoData = JSON.parse(defaultBranchResult.stdout.toString());
|
|
306
311
|
const defaultBranch = repoData.default_branch;
|
|
307
312
|
|
|
308
|
-
const commitsResult = await
|
|
313
|
+
const commitsResult = await quietProbe($)`gh api repos/${owner}/${repo}/commits --paginate --field sha=${defaultBranch} --field since=${lastCommitTime.toISOString()}`;
|
|
309
314
|
if (commitsResult.code === 0) {
|
|
310
315
|
const commits = JSON.parse(commitsResult.stdout.toString());
|
|
311
316
|
if (commits.length > 0) {
|
|
@@ -353,10 +358,10 @@ export const detectAndCountFeedback = async params => {
|
|
|
353
358
|
|
|
354
359
|
// 6. Check for failed PR checks
|
|
355
360
|
try {
|
|
356
|
-
const prHeadResult = await
|
|
361
|
+
const prHeadResult = await quietProbe($)`gh api repos/${owner}/${repo}/pulls/${prNumber} --jq '.head.sha'`;
|
|
357
362
|
if (prHeadResult.code === 0) {
|
|
358
363
|
const prHeadSha = prHeadResult.stdout.toString().trim();
|
|
359
|
-
const checksResult = await
|
|
364
|
+
const checksResult = await quietProbe($)`gh api repos/${owner}/${repo}/commits/${prHeadSha}/check-runs --paginate --slurp`;
|
|
360
365
|
const checkRuns = checksResult.code === 0 ? JSON.parse(checksResult.stdout.toString() || '[]').flatMap(page => page.check_runs || []) : [];
|
|
361
366
|
const failedChecks = checkRuns.filter(check => check.conclusion === 'failure' && new Date(check.completed_at) > lastCommitTime);
|
|
362
367
|
|
|
@@ -379,7 +384,7 @@ export const detectAndCountFeedback = async params => {
|
|
|
379
384
|
|
|
380
385
|
// 7. Check for review requests with changes requested
|
|
381
386
|
try {
|
|
382
|
-
const reviewsResult = await
|
|
387
|
+
const reviewsResult = await quietProbe($)`gh api repos/${owner}/${repo}/pulls/${prNumber}/reviews --paginate`;
|
|
383
388
|
if (reviewsResult.code === 0) {
|
|
384
389
|
const reviews = JSON.parse(reviewsResult.stdout.toString());
|
|
385
390
|
const changesRequestedReviews = reviews.filter(review => review.state === 'CHANGES_REQUESTED' && new Date(review.submitted_at) > lastCommitTime);
|
|
@@ -14,6 +14,7 @@ const use = globalThis.use;
|
|
|
14
14
|
|
|
15
15
|
// Use command-stream for consistent $ behavior; wrap with rate-limit retry (#1726)
|
|
16
16
|
const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
|
|
17
|
+
const { QUIET_PROBE } = await import('./quiet-probe.lib.mjs'); // issue #2130: keep read-only probe payloads out of the attached log
|
|
17
18
|
const $ = wrapDollarWithGhRetry((await use('command-stream')).$);
|
|
18
19
|
|
|
19
20
|
// Import shared library functions
|
|
@@ -179,7 +180,7 @@ export const setupUpstreamAndSync = async (tempDir, forkedRepo, upstreamRemote,
|
|
|
179
180
|
// branch. Attempting the push there is guaranteed to be rejected
|
|
180
181
|
// with "permission denied" and is unnecessary, so we skip it and
|
|
181
182
|
// keep working on the PR branch.
|
|
182
|
-
const currentUserResult = await
|
|
183
|
+
const currentUserResult = await $(QUIET_PROBE)`gh api user --jq .login`;
|
|
183
184
|
const currentUser = currentUserResult.code === 0 ? currentUserResult.stdout.toString().trim() : null;
|
|
184
185
|
const pushDecision = shouldPushDefaultBranchToFork({ currentUser, forkedRepo });
|
|
185
186
|
|
package/src/solve.mjs
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// Issue #1625: centralized markers + tracked posting for the "empty repo"
|
|
7
7
|
// issue comment so it's excluded from --auto-attach-solution-summary's check.
|
|
8
8
|
import { REPOSITORY_INITIALIZATION_REQUIRED_MARKER, postTrackedComment } from './tool-comments.lib.mjs';
|
|
9
|
+
import { QUIET_PROBE } from './quiet-probe.lib.mjs'; // issue #2130: keep read-only probe payloads out of the attached log
|
|
9
10
|
|
|
10
11
|
export async function setupRepositoryAndClone({ argv, owner, repo, forkOwner, forkRepoName, tempDir, isContinueMode, issueUrl, log, $, needsClone = true }) {
|
|
11
12
|
// Set up repository and handle forking
|
|
@@ -28,14 +29,49 @@ export async function setupRepositoryAndClone({ argv, owner, repo, forkOwner, fo
|
|
|
28
29
|
const prForkRemote = await setupPrForkRemote(tempDir, argv, prForkOwner, repo, isContinueMode, owner);
|
|
29
30
|
|
|
30
31
|
// Set up git authentication using gh
|
|
31
|
-
|
|
32
|
-
if (authSetupResult.code !== 0) {
|
|
33
|
-
await log('Note: gh auth setup-git had issues, continuing anyway\n');
|
|
34
|
-
}
|
|
32
|
+
await setupGitCredentialHelper({ tempDir, log, $ });
|
|
35
33
|
|
|
36
34
|
return { repoToClone, forkedRepo, upstreamRemote, prForkRemote, prForkOwner };
|
|
37
35
|
}
|
|
38
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Point git at `gh` for GitHub credentials.
|
|
39
|
+
*
|
|
40
|
+
* Issue #2130: `gh auth setup-git` only ever writes the *global* gitconfig. In
|
|
41
|
+
* a container where `~/.gitconfig` is bind-mounted, git cannot replace it and
|
|
42
|
+
* gh reports `failed to set up git credential helper: failed to run git: error:
|
|
43
|
+
* could not write config file /home/box/.gitconfig: Device or resource busy`.
|
|
44
|
+
* Hive Mind used to mirror that line raw and continue with no credential helper
|
|
45
|
+
* at all, so the real failure only surfaced later as a push error.
|
|
46
|
+
*
|
|
47
|
+
* The clone-local config is equivalent for our purposes and is never
|
|
48
|
+
* bind-mounted, so it is used as the fallback. The empty `helper` entry first
|
|
49
|
+
* clears any inherited helper, exactly as `gh auth setup-git` does.
|
|
50
|
+
*/
|
|
51
|
+
export async function setupGitCredentialHelper({ tempDir, log, $, hosts = ['github.com'] }) {
|
|
52
|
+
const authSetupResult = await $({ cwd: tempDir, ...QUIET_PROBE })`gh auth setup-git 2>&1`;
|
|
53
|
+
if (authSetupResult.code === 0) return { scope: 'global', hosts };
|
|
54
|
+
|
|
55
|
+
const reason = (authSetupResult.stdout?.toString() || authSetupResult.stderr?.toString() || '').trim();
|
|
56
|
+
await log(`ℹ️ gh auth setup-git could not write the global gitconfig${reason ? `: ${reason.split('\n')[0]}` : ''}`, { verbose: true });
|
|
57
|
+
|
|
58
|
+
const failures = [];
|
|
59
|
+
for (const host of hosts) {
|
|
60
|
+
const key = `credential.https://${host}.helper`;
|
|
61
|
+
const cleared = await $({ cwd: tempDir, ...QUIET_PROBE })`git config --local --replace-all ${key} ""`;
|
|
62
|
+
const configured = await $({ cwd: tempDir, ...QUIET_PROBE })`git config --local --add ${key} ${'!gh auth git-credential'}`;
|
|
63
|
+
if (cleared.code !== 0 || configured.code !== 0) failures.push(host);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (failures.length) {
|
|
67
|
+
await log(`⚠️ Could not configure a git credential helper for ${failures.join(', ')} - pushes may require credentials in the remote URL`, { level: 'warning' });
|
|
68
|
+
return { scope: 'none', hosts: failures };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
await log('🔑 Configured the gh credential helper for this clone (the global gitconfig is not writable)', { verbose: true });
|
|
72
|
+
return { scope: 'local', hosts };
|
|
73
|
+
}
|
|
74
|
+
|
|
39
75
|
async function setupRepository(argv, owner, repo, forkOwner, issueUrl, forkRepoName) {
|
|
40
76
|
const repository = await import('./solve.repository.lib.mjs');
|
|
41
77
|
const { setupRepository: setupRepoFn } = repository;
|
|
@@ -14,6 +14,7 @@ const use = globalThis.use;
|
|
|
14
14
|
|
|
15
15
|
// Use command-stream for consistent $ behavior; wrap with rate-limit retry (#1726)
|
|
16
16
|
const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
|
|
17
|
+
const { QUIET_PROBE } = await import('./quiet-probe.lib.mjs'); // issue #2130: keep read-only probe payloads out of the attached log
|
|
17
18
|
const $ = wrapDollarWithGhRetry((await use('command-stream')).$);
|
|
18
19
|
const os = (await use('os')).default;
|
|
19
20
|
const path = (await use('path')).default;
|
|
@@ -57,7 +58,7 @@ export const getRootRepository = async (owner, repo) => {
|
|
|
57
58
|
// Check if current user has a fork of the given root repository
|
|
58
59
|
export const checkExistingForkOfRoot = async rootRepo => {
|
|
59
60
|
try {
|
|
60
|
-
const userResult = await lib.ghCmdRetry(() =>
|
|
61
|
+
const userResult = await lib.ghCmdRetry(() => $(QUIET_PROBE)`gh api user --jq .login`, { label: 'get user (fork check)' });
|
|
61
62
|
if (userResult.code !== 0) return null;
|
|
62
63
|
const currentUser = userResult.stdout.toString().trim();
|
|
63
64
|
// Issue #2119: build the jq expression in JS. Its double quotes belong to jq,
|
|
@@ -378,7 +379,7 @@ export const setupRepository = async (argv, owner, repo, forkOwner = null, issue
|
|
|
378
379
|
await log(`${formatAligned('', 'Checking fork status...', '')}\n`);
|
|
379
380
|
|
|
380
381
|
// Get current user (issue #1536: retry on transient network errors)
|
|
381
|
-
const userResult = await lib.ghCmdRetry(() =>
|
|
382
|
+
const userResult = await lib.ghCmdRetry(() => $(QUIET_PROBE)`gh api user --jq .login`, { label: 'get current user' });
|
|
382
383
|
if (userResult.code !== 0) {
|
|
383
384
|
await log(`${formatAligned('❌', 'Error:', 'Failed to get current user')}`);
|
|
384
385
|
await safeExit(1, 'Repository setup failed');
|
|
@@ -1191,7 +1192,7 @@ export const setupPrForkRemote = async (tempDir, argv, prForkOwner, repo, isCont
|
|
|
1191
1192
|
|
|
1192
1193
|
// Get current user to check if it's someone else's fork
|
|
1193
1194
|
await log(`\n${formatAligned('🔍', 'Checking PR fork:', 'Determining if branch is in another fork...')}`);
|
|
1194
|
-
const userResult = await
|
|
1195
|
+
const userResult = await $(QUIET_PROBE)`gh api user --jq .login`;
|
|
1195
1196
|
if (userResult.code !== 0) {
|
|
1196
1197
|
await log(`${formatAligned('⚠️', 'Warning:', 'Failed to get current user, cannot set up pr-fork remote')}`);
|
|
1197
1198
|
return null;
|
|
@@ -15,6 +15,7 @@ const use = globalThis.use;
|
|
|
15
15
|
// Use command-stream for consistent $ behavior across runtimes
|
|
16
16
|
const { $: __rawDollar$ } = await use('command-stream');
|
|
17
17
|
const { wrapDollarWithGhRetry } = await import('./github-rate-limit.lib.mjs');
|
|
18
|
+
const { QUIET_PROBE } = await import('./quiet-probe.lib.mjs'); // issue #2130: keep read-only probe payloads out of the attached log
|
|
18
19
|
const $ = wrapDollarWithGhRetry(__rawDollar$);
|
|
19
20
|
const path = (await use('path')).default;
|
|
20
21
|
|
|
@@ -28,8 +29,6 @@ import { safeExit } from './exit-handler.lib.mjs';
|
|
|
28
29
|
// Import GitHub-related functions
|
|
29
30
|
const githubLib = await import('./github.lib.mjs');
|
|
30
31
|
const { sanitizeLogContent, attachLogToGitHub } = githubLib;
|
|
31
|
-
const { buildCostInfoString } = await import('./github-cost-info.lib.mjs');
|
|
32
|
-
const { buildBudgetStatsString } = await import('./claude.budget-stats.lib.mjs');
|
|
33
32
|
|
|
34
33
|
// Issue #1745: process-wide sanitization counters used to print a one-line
|
|
35
34
|
// "we masked N secrets" summary at the end of each run.
|
|
@@ -673,7 +672,18 @@ export const showSessionSummary = async (sessionId, limitReached, argv, issueUrl
|
|
|
673
672
|
// same observed facts (working-session summary and attached log alike).
|
|
674
673
|
export const buildSessionBudgetStatsData = async ({ argv, sessionId = null, tempDir = null, resultModelUsage = null, streamTokenUsage = null, subAgentCalls = null, pricingInfo = null }) => {
|
|
675
674
|
let budgetStatsData = null;
|
|
676
|
-
|
|
675
|
+
// Issue #2132: budget stats are a property of the working session **log**.
|
|
676
|
+
// With `--attach-logs` disabled there is no log comment, so they must not be
|
|
677
|
+
// computed or published anywhere.
|
|
678
|
+
const { shouldPublishBudgetStats, isAttachLogsEnabled, isTokensBudgetStatsEnabled } = await import('./budget-stats-policy.lib.mjs');
|
|
679
|
+
if (!shouldPublishBudgetStats(argv)) {
|
|
680
|
+
if (argv?.verbose) {
|
|
681
|
+
const reason = !isTokensBudgetStatsEnabled(argv) ? '--no-tokens-budget-stats' : !isAttachLogsEnabled(argv) ? '--attach-logs is disabled' : 'unknown';
|
|
682
|
+
await log(` ℹ️ Skipping context/cost budget stats publication (${reason})`, { verbose: true });
|
|
683
|
+
}
|
|
684
|
+
return null;
|
|
685
|
+
}
|
|
686
|
+
if (sessionId && tempDir) {
|
|
677
687
|
try {
|
|
678
688
|
const { calculateSessionTokens } = await import('./claude.lib.mjs');
|
|
679
689
|
const tokenUsage = await calculateSessionTokens(sessionId, tempDir, resultModelUsage);
|
|
@@ -685,7 +695,7 @@ export const buildSessionBudgetStatsData = async ({ argv, sessionId = null, temp
|
|
|
685
695
|
}
|
|
686
696
|
}
|
|
687
697
|
// Issue #1526: Build budget stats from Agent CLI token/context data when no JSONL session available
|
|
688
|
-
if (!budgetStatsData &&
|
|
698
|
+
if (!budgetStatsData && pricingInfo?.tokenUsage) {
|
|
689
699
|
try {
|
|
690
700
|
const { buildAgentBudgetStats } = await import('./claude.budget-stats.lib.mjs');
|
|
691
701
|
const agentBudgetData = buildAgentBudgetStats(pricingInfo.tokenUsage, pricingInfo);
|
|
@@ -719,7 +729,7 @@ export const verifyResults = async (owner, repo, branchName, issueNumber, prNumb
|
|
|
719
729
|
|
|
720
730
|
try {
|
|
721
731
|
// Get the current user's GitHub username
|
|
722
|
-
const userResult = await
|
|
732
|
+
const userResult = await $(QUIET_PROBE)`gh api user --jq .login`;
|
|
723
733
|
|
|
724
734
|
if (userResult.code !== 0) {
|
|
725
735
|
throw new Error(`Failed to get current user: ${userResult.stderr ? userResult.stderr.toString() : 'Unknown error'}`);
|
|
@@ -1139,7 +1149,7 @@ export const { TOOL_GENERATED_COMMENT_MARKERS, isToolGeneratedComment, trackTool
|
|
|
1139
1149
|
export const checkForAiCreatedComments = async (sessionStartTime, owner, repo, prNumber, issueNumber) => {
|
|
1140
1150
|
try {
|
|
1141
1151
|
// Get the current user's GitHub username
|
|
1142
|
-
const userResult = await
|
|
1152
|
+
const userResult = await $(QUIET_PROBE)`gh api user --jq .login`;
|
|
1143
1153
|
if (userResult.code !== 0) {
|
|
1144
1154
|
return false; // Cannot determine, default to not attaching
|
|
1145
1155
|
}
|
|
@@ -1263,15 +1273,19 @@ export const checkForAiCreatedComments = async (sessionStartTime, owner, repo, p
|
|
|
1263
1273
|
* @param {string} options.repo - Repository name
|
|
1264
1274
|
* @returns {Promise<boolean>} - True if comment was posted successfully
|
|
1265
1275
|
*/
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1276
|
+
/**
|
|
1277
|
+
* Issue #2132: the working session summary must describe *what the AI did* and
|
|
1278
|
+
* nothing else. Cost estimation and context/token budget statistics belong to
|
|
1279
|
+
* the working session log comment (`--attach-logs`), where they are already
|
|
1280
|
+
* published once per working session. Rendering them in the summary as well
|
|
1281
|
+
* duplicated the very same block in two consecutive comments.
|
|
1282
|
+
*
|
|
1283
|
+
* Kept as an exported function returning an empty string so the invariant is
|
|
1284
|
+
* directly testable and any future caller cannot silently re-add the block.
|
|
1285
|
+
*/
|
|
1286
|
+
export const buildWorkingSessionSummaryDetails = () => '';
|
|
1273
1287
|
|
|
1274
|
-
export const attachSolutionSummary = async ({ resultSummary, prNumber, issueNumber, owner, repo,
|
|
1288
|
+
export const attachSolutionSummary = async ({ resultSummary, prNumber, issueNumber, owner, repo, changeStats = null }) => {
|
|
1275
1289
|
if (!resultSummary || typeof resultSummary !== 'string') {
|
|
1276
1290
|
await log('⚠️ No working session summary available to attach', { verbose: true });
|
|
1277
1291
|
return false;
|
|
@@ -1286,12 +1300,6 @@ export const attachSolutionSummary = async ({ resultSummary, prNumber, issueNumb
|
|
|
1286
1300
|
}
|
|
1287
1301
|
|
|
1288
1302
|
try {
|
|
1289
|
-
const usageDetails = buildWorkingSessionSummaryDetails({
|
|
1290
|
-
publicPricingEstimate,
|
|
1291
|
-
anthropicTotalCostUSD,
|
|
1292
|
-
pricingInfo,
|
|
1293
|
-
budgetStatsData,
|
|
1294
|
-
});
|
|
1295
1303
|
// Issue #2119: publish what the session actually produced. The reported
|
|
1296
1304
|
// summary said "The `pwd` command completed" and printed the solver's own
|
|
1297
1305
|
// /tmp workspace, on a pull request that was still empty.
|
|
@@ -1301,7 +1309,7 @@ export const attachSolutionSummary = async ({ resultSummary, prNumber, issueNumb
|
|
|
1301
1309
|
const comment = `${toolComments.WORKING_SESSION_SUMMARY_AUTOMATION_MARKER}
|
|
1302
1310
|
## ${toolComments.WORKING_SESSION_SUMMARY_MARKER}
|
|
1303
1311
|
|
|
1304
|
-
${summaryBody}${noChangesNotice ? `\n\n${noChangesNotice}` : ''}
|
|
1312
|
+
${summaryBody}${noChangesNotice ? `\n\n${noChangesNotice}` : ''}
|
|
1305
1313
|
|
|
1306
1314
|
---
|
|
1307
1315
|
*${toolComments.WORKING_SESSION_SUMMARY_AUTOMATED_FOOTER}*`;
|
|
@@ -1358,7 +1366,7 @@ ${summaryBody}${noChangesNotice ? `\n\n${noChangesNotice}` : ''}${usageDetails ?
|
|
|
1358
1366
|
* @param {boolean} [options.success=true] - skip attachment for failed iterations
|
|
1359
1367
|
* @returns {Promise<{attached: boolean, reason: string}>}
|
|
1360
1368
|
*/
|
|
1361
|
-
export const maybeAttachWorkingSessionSummary = async ({ argv, resultSummary, workStartTime, owner, repo, prNumber, issueNumber, success = true,
|
|
1369
|
+
export const maybeAttachWorkingSessionSummary = async ({ argv, resultSummary, workStartTime, owner, repo, prNumber, issueNumber, success = true, pricingInfo = null, budgetStatsData = null, sessionUsage = null }) => {
|
|
1362
1370
|
if (!success) {
|
|
1363
1371
|
return { attached: false, reason: 'iteration_failed' };
|
|
1364
1372
|
}
|
|
@@ -1407,16 +1415,14 @@ export const maybeAttachWorkingSessionSummary = async ({ argv, resultSummary, wo
|
|
|
1407
1415
|
// say so, instead of reading as a report of completed work.
|
|
1408
1416
|
const changeStats = prNumber ? await getPullRequestChangeStats({ owner, repo, prNumber, $ }) : null;
|
|
1409
1417
|
|
|
1418
|
+
// Issue #2132: the summary carries no cost/budget block. `resolvedBudgetStatsData`
|
|
1419
|
+
// is computed only so the caller can reuse it for this session's log comment.
|
|
1410
1420
|
const ok = await attachSolutionSummary({
|
|
1411
1421
|
resultSummary,
|
|
1412
1422
|
prNumber,
|
|
1413
1423
|
issueNumber,
|
|
1414
1424
|
owner,
|
|
1415
1425
|
repo,
|
|
1416
|
-
publicPricingEstimate,
|
|
1417
|
-
anthropicTotalCostUSD,
|
|
1418
|
-
pricingInfo,
|
|
1419
|
-
budgetStatsData: resolvedBudgetStatsData,
|
|
1420
1426
|
changeStats,
|
|
1421
1427
|
});
|
|
1422
1428
|
return { attached: !!ok, reason: ok ? 'attached' : 'post_failed', budgetStatsData: resolvedBudgetStatsData };
|
|
@@ -308,13 +308,20 @@ export const performSystemChecks = async (minDiskSpace = 10240, skipToolConnecti
|
|
|
308
308
|
const { validateFormalAiToolConnection } = await import('./formal-ai.lib.mjs');
|
|
309
309
|
const formalAiValidation = await validateFormalAiToolConnection(argv.tool || 'claude');
|
|
310
310
|
isToolConnected = formalAiValidation.valid;
|
|
311
|
+
// Record the wrapper version on both paths: the wrapper builds the tool's
|
|
312
|
+
// argv, so its version is the first thing needed to explain a failure
|
|
313
|
+
// (issue #2130's logs recorded neither, which left the round-2 claude
|
|
314
|
+
// failure unattributable).
|
|
315
|
+
const formalAiVersionLine = `📦 Formal AI wrapper version: ${formalAiValidation.formalAiVersion || 'unknown'}`;
|
|
311
316
|
if (isToolConnected) {
|
|
312
317
|
await log(`✅ Formal AI wrapper and ${argv.tool || 'claude'} CLI are available`);
|
|
318
|
+
await log(formalAiVersionLine);
|
|
313
319
|
if (formalAiValidation.version) {
|
|
314
320
|
await log(`📦 ${argv.tool || 'claude'} CLI version: ${formalAiValidation.version}`);
|
|
315
321
|
}
|
|
316
322
|
} else {
|
|
317
323
|
await log(`❌ Formal AI dispatch validation failed: ${formalAiValidation.error}`, { level: 'error' });
|
|
324
|
+
await log(` ${formalAiVersionLine}`, { level: 'error' });
|
|
318
325
|
await log(' Install or update the wrapper with: cargo install formal-ai', { level: 'error' });
|
|
319
326
|
return false;
|
|
320
327
|
}
|
package/src/solve.watch.lib.mjs
CHANGED
|
@@ -567,19 +567,21 @@ export const watchForFeedback = async params => {
|
|
|
567
567
|
latestResultModelUsage = toolResult.resultModelUsage;
|
|
568
568
|
}
|
|
569
569
|
|
|
570
|
-
// Issue #1508: Compute budget stats for auto-restart log comment
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
570
|
+
// Issue #1508: Compute budget stats for auto-restart log comment.
|
|
571
|
+
// Issue #2132: shared with the top-level run and the
|
|
572
|
+
// auto-restart-until-mergeable loop via buildSessionBudgetStatsData,
|
|
573
|
+
// so every working session derives its own stats the same way (and
|
|
574
|
+
// skips them entirely when `--attach-logs` is disabled).
|
|
575
|
+
const { buildSessionBudgetStatsData } = await import('./solve.results.lib.mjs');
|
|
576
|
+
const autoRestartBudgetStatsData = await buildSessionBudgetStatsData({
|
|
577
|
+
argv,
|
|
578
|
+
sessionId: latestSessionId,
|
|
579
|
+
tempDir,
|
|
580
|
+
resultModelUsage: toolResult.resultModelUsage,
|
|
581
|
+
streamTokenUsage: toolResult.streamTokenUsage || null,
|
|
582
|
+
subAgentCalls: toolResult.subAgentCalls || null,
|
|
583
|
+
pricingInfo: toolResult.pricingInfo || null,
|
|
584
|
+
});
|
|
583
585
|
|
|
584
586
|
// Issue #1761: Post the working session **summary** BEFORE uploading
|
|
585
587
|
// the working session **log** so the summary always appears above
|
|
@@ -604,8 +606,6 @@ export const watchForFeedback = async params => {
|
|
|
604
606
|
prNumber,
|
|
605
607
|
issueNumber,
|
|
606
608
|
success: true,
|
|
607
|
-
publicPricingEstimate: toolResult.publicPricingEstimate,
|
|
608
|
-
anthropicTotalCostUSD: latestAnthropicCost,
|
|
609
609
|
pricingInfo: toolResult.pricingInfo,
|
|
610
610
|
budgetStatsData: autoRestartBudgetStatsData,
|
|
611
611
|
});
|
|
@@ -24,6 +24,7 @@ import { reportError } from './sentry.lib.mjs';
|
|
|
24
24
|
export { createCredentialStreamSanitizer };
|
|
25
25
|
|
|
26
26
|
import { wrapDollarWithGhRetry as _wrapDollarWithGhRetry } from './github-rate-limit.lib.mjs'; // rate-limit marker (#1726): gh API calls flow through $ wrapped by caller
|
|
27
|
+
import { QUIET_PROBE } from './quiet-probe.lib.mjs'; // issue #2130: never mirror the call that discovers which secrets must be masked
|
|
27
28
|
// Dynamic imports for runtime dependencies
|
|
28
29
|
const getOsModule = async () => (await import('os')).default;
|
|
29
30
|
const getPathModule = async () => (await import('path')).default;
|
|
@@ -255,8 +256,12 @@ export const getGitHubTokensFromCommand = async () => {
|
|
|
255
256
|
const tokens = [];
|
|
256
257
|
|
|
257
258
|
try {
|
|
258
|
-
// Run gh auth status to get token info
|
|
259
|
-
|
|
259
|
+
// Run gh auth status to get token info.
|
|
260
|
+
// Issue #2130: never mirror this. The whole point of the call is to learn
|
|
261
|
+
// which secrets have to be masked, so its output is the one thing that is
|
|
262
|
+
// guaranteed to be unmasked at this moment - and it was being echoed into
|
|
263
|
+
// the log that later gets attached to the pull request.
|
|
264
|
+
const authResult = await $(QUIET_PROBE)`gh auth status 2>&1`.catch(() => ({ stdout: '', stderr: '' }));
|
|
260
265
|
const authOutput = authResult.stdout?.toString() + authResult.stderr?.toString() || '';
|
|
261
266
|
|
|
262
267
|
// Look for token patterns in the output
|