@link-assistant/hive-mind 1.52.1 → 1.53.1
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 +16 -0
- package/package.json +1 -1
- package/src/agent.lib.mjs +18 -11
- package/src/agent.prompts.lib.mjs +18 -0
- package/src/claude.lib.mjs +16 -5
- package/src/claude.prompts.lib.mjs +1 -0
- package/src/codex.lib.mjs +5 -0
- package/src/codex.prompts.lib.mjs +1 -0
- package/src/github.lib.mjs +59 -43
- package/src/interactive-mode.lib.mjs +12 -2
- package/src/opencode.lib.mjs +15 -0
- package/src/opencode.prompts.lib.mjs +18 -0
- package/src/playwright-mcp.lib.mjs +298 -0
- package/src/solve.auto-merge-helpers.lib.mjs +9 -6
- package/src/solve.auto-merge.lib.mjs +28 -20
- package/src/solve.config.lib.mjs +6 -1
- package/src/solve.mjs +36 -35
- package/src/solve.progress-monitoring.lib.mjs +16 -11
- package/src/solve.repo-setup.lib.mjs +8 -4
- package/src/solve.repository.lib.mjs +6 -4
- package/src/solve.restart-shared.lib.mjs +29 -2
- package/src/solve.results.lib.mjs +64 -3
- package/src/solve.session.lib.mjs +25 -13
- package/src/solve.watch.lib.mjs +7 -2
- package/src/tool-comments.lib.mjs +271 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Centralized definitions for GitHub comments posted by solve.mjs itself
|
|
5
|
+
* (session bookkeeping, log uploads, auto-restart notices, etc.) — as
|
|
6
|
+
* opposed to comments posted by the AI agent via its own tool calls.
|
|
7
|
+
*
|
|
8
|
+
* Issue #1625: --auto-attach-solution-summary was broken because the tool's
|
|
9
|
+
* own "AI Work Session Started" / "Solution Draft Log" / "Ready to merge"
|
|
10
|
+
* comments counted as AI-authored comments, so the summary was always
|
|
11
|
+
* suppressed even when the AI session produced zero comments of its own.
|
|
12
|
+
*
|
|
13
|
+
* This module is the single source of truth for the marker strings embedded
|
|
14
|
+
* in those comments. Posting sites use these constants to *build* comment
|
|
15
|
+
* bodies; the summary filter uses the same constants to *detect* them. If a
|
|
16
|
+
* marker needs to change, changing it here updates both sides — no more
|
|
17
|
+
* duplicate literals drifting apart.
|
|
18
|
+
*
|
|
19
|
+
* It also provides in-memory tracking: any comment posted by solve.mjs can
|
|
20
|
+
* be registered by its numeric GitHub comment ID, and checkForAiCreatedComments
|
|
21
|
+
* uses that set as the *primary* filter (marker matching is the fallback for
|
|
22
|
+
* comments whose IDs were not captured, e.g. when `gh pr comment` didn't
|
|
23
|
+
* return JSON).
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
// ----------------------------------------------------------------------------
|
|
27
|
+
// Marker constants — single source of truth for comment header/keyphrase text.
|
|
28
|
+
// Each constant is the exact substring that both (a) appears in the posted
|
|
29
|
+
// comment body and (b) is searched for when filtering out tool-generated
|
|
30
|
+
// comments. Do NOT duplicate these literals elsewhere.
|
|
31
|
+
// ----------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
// solve.session.lib.mjs — startWorkSession() / endWorkSession()
|
|
34
|
+
export const AI_WORK_SESSION_STARTED_MARKER = 'AI Work Session Started';
|
|
35
|
+
export const AI_WORK_SESSION_COMPLETED_MARKER = 'AI Work Session Completed';
|
|
36
|
+
export const AI_WORK_SESSION_RESUMED_MARKER = 'AI Work Session Resumed';
|
|
37
|
+
|
|
38
|
+
// solve.session.lib.mjs — auto-resume / auto-restart on limit reset
|
|
39
|
+
export const AUTO_RESUME_ON_LIMIT_RESET_MARKER = 'Auto Resume (on limit reset)';
|
|
40
|
+
export const AUTO_RESTART_ON_LIMIT_RESET_MARKER = 'Auto Restart (on limit reset)';
|
|
41
|
+
|
|
42
|
+
// github.lib.mjs — attachLogToGitHub() success / resumed / truncated log comments
|
|
43
|
+
export const SOLUTION_DRAFT_LOG_MARKER = 'Solution Draft Log';
|
|
44
|
+
|
|
45
|
+
// solve.watch.lib.mjs / solve.auto-merge.lib.mjs — auto-restart notifications
|
|
46
|
+
export const AUTO_RESTART_MARKER = 'Auto-restart';
|
|
47
|
+
export const AUTO_RESTART_UNTIL_MERGEABLE_LOG_MARKER = 'Auto-restart-until-mergeable Log';
|
|
48
|
+
|
|
49
|
+
// solve.auto-merge.lib.mjs — "ready to merge" status comments
|
|
50
|
+
export const READY_TO_MERGE_MARKER = 'Ready to merge';
|
|
51
|
+
|
|
52
|
+
// solve.auto-merge.lib.mjs — "auto-merged successfully" status comments
|
|
53
|
+
export const AUTO_MERGED_MARKER = 'Auto-merged';
|
|
54
|
+
|
|
55
|
+
// solve.auto-merge.lib.mjs — billing-limit notification (spending cap / free tier)
|
|
56
|
+
export const BILLING_LIMIT_MARKER = 'GitHub Actions Billing Limit';
|
|
57
|
+
|
|
58
|
+
// github.lib.mjs — fork contributor "Allow edits by maintainers" request
|
|
59
|
+
export const MAINTAINER_ACCESS_REQUEST_MARKER = 'Allow edits by maintainers';
|
|
60
|
+
|
|
61
|
+
// solve.progress-monitoring.lib.mjs — live-progress comment section markers.
|
|
62
|
+
// These are HTML comments so they don't render in the GitHub UI; they exist
|
|
63
|
+
// specifically to let the tool find its own comment later.
|
|
64
|
+
export const LIVE_PROGRESS_SECTION_START_MARKER = '<!-- LIVE-PROGRESS-START -->';
|
|
65
|
+
export const LIVE_PROGRESS_SECTION_END_MARKER = '<!-- LIVE-PROGRESS-END -->';
|
|
66
|
+
|
|
67
|
+
// claude.lib.mjs — "session force-killed due to stream timeout" notifications
|
|
68
|
+
export const SESSION_FORCE_KILLED_MARKER = 'Session Force-Killed';
|
|
69
|
+
|
|
70
|
+
// solve.repo-setup.lib.mjs / solve.repository.lib.mjs — issue comments posted
|
|
71
|
+
// when the target repository is empty / uninitialized so solving can't start.
|
|
72
|
+
export const REPOSITORY_INITIALIZATION_REQUIRED_MARKER = 'Repository Initialization Required';
|
|
73
|
+
|
|
74
|
+
// interactive-mode.lib.mjs — interactive mode session comments
|
|
75
|
+
export const INTERACTIVE_SESSION_STARTED_MARKER = 'Interactive session started';
|
|
76
|
+
export const INTERACTIVE_SESSION_ENDED_MARKER = 'Interactive session ended';
|
|
77
|
+
|
|
78
|
+
// github.lib.mjs — closing footer present in every log upload comment variant
|
|
79
|
+
export const NOW_WORKING_SESSION_IS_ENDED_MARKER = 'Now working session is ended';
|
|
80
|
+
|
|
81
|
+
// Failure-path markers (github.lib.mjs error paths)
|
|
82
|
+
export const SOLUTION_DRAFT_FAILED_MARKER = 'Solution Draft Failed';
|
|
83
|
+
export const SOLUTION_DRAFT_FINISHED_WITH_ERRORS_MARKER = 'Solution Draft Finished with Errors';
|
|
84
|
+
export const USAGE_LIMIT_REACHED_MARKER = 'Usage Limit Reached';
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Every marker that identifies a tool-posted comment. Derived from the
|
|
88
|
+
* named constants above so that adding a new marker only requires adding
|
|
89
|
+
* the constant and appending it here.
|
|
90
|
+
*/
|
|
91
|
+
export const TOOL_GENERATED_COMMENT_MARKERS = [AI_WORK_SESSION_STARTED_MARKER, AI_WORK_SESSION_COMPLETED_MARKER, AI_WORK_SESSION_RESUMED_MARKER, AUTO_RESUME_ON_LIMIT_RESET_MARKER, AUTO_RESTART_ON_LIMIT_RESET_MARKER, SOLUTION_DRAFT_LOG_MARKER, AUTO_RESTART_MARKER, AUTO_RESTART_UNTIL_MERGEABLE_LOG_MARKER, READY_TO_MERGE_MARKER, AUTO_MERGED_MARKER, BILLING_LIMIT_MARKER, MAINTAINER_ACCESS_REQUEST_MARKER, LIVE_PROGRESS_SECTION_START_MARKER, SESSION_FORCE_KILLED_MARKER, REPOSITORY_INITIALIZATION_REQUIRED_MARKER, INTERACTIVE_SESSION_STARTED_MARKER, INTERACTIVE_SESSION_ENDED_MARKER, NOW_WORKING_SESSION_IS_ENDED_MARKER, SOLUTION_DRAFT_FAILED_MARKER, SOLUTION_DRAFT_FINISHED_WITH_ERRORS_MARKER, USAGE_LIMIT_REACHED_MARKER];
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Markers that indicate the end of a working session. Used by
|
|
95
|
+
* solve.auto-merge-helpers.checkForExistingComment to scope the
|
|
96
|
+
* duplicate-search window to the current session only (Issue #1584).
|
|
97
|
+
*/
|
|
98
|
+
export const SESSION_ENDING_MARKERS = [NOW_WORKING_SESSION_IS_ENDED_MARKER, AI_WORK_SESSION_COMPLETED_MARKER];
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Determine whether a GitHub comment body matches any known tool-generated
|
|
102
|
+
* marker. Used as a fallback when a comment's ID was not captured by
|
|
103
|
+
* in-memory tracking (see below).
|
|
104
|
+
*
|
|
105
|
+
* @param {string} body - The comment body
|
|
106
|
+
* @returns {boolean} - True if the body contains a tool-generated marker
|
|
107
|
+
*/
|
|
108
|
+
export const isToolGeneratedComment = body => {
|
|
109
|
+
if (!body || typeof body !== 'string') return false;
|
|
110
|
+
return TOOL_GENERATED_COMMENT_MARKERS.some(marker => body.includes(marker));
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// ----------------------------------------------------------------------------
|
|
114
|
+
// In-memory tracking of comments posted by solve.mjs during this session.
|
|
115
|
+
//
|
|
116
|
+
// Every tool-initiated comment-post helper should register its comment ID
|
|
117
|
+
// via trackToolCommentId(). checkForAiCreatedComments() then uses the set
|
|
118
|
+
// as the primary filter, falling back to marker-based detection for any
|
|
119
|
+
// comment whose ID was not captured.
|
|
120
|
+
//
|
|
121
|
+
// IDs are GitHub numeric comment IDs (from issue/PR/review comment APIs),
|
|
122
|
+
// coerced to strings for consistent Set membership. Review (inline) comments
|
|
123
|
+
// and conversation comments share the same ID namespace at the API layer,
|
|
124
|
+
// but we never mix them since solve.mjs only posts to conversation + issue
|
|
125
|
+
// endpoints — review comments are AI-only.
|
|
126
|
+
// ----------------------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
const trackedToolCommentIds = new Set();
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Register a comment ID as tool-generated. Safe to call with null/undefined
|
|
132
|
+
* (e.g., when comment posting failed or the ID couldn't be extracted).
|
|
133
|
+
* @param {string|number|null|undefined} commentId
|
|
134
|
+
*/
|
|
135
|
+
export const trackToolCommentId = commentId => {
|
|
136
|
+
if (commentId === null || commentId === undefined) return;
|
|
137
|
+
trackedToolCommentIds.add(String(commentId));
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Returns whether a given comment ID was posted by solve.mjs itself during
|
|
142
|
+
* this session.
|
|
143
|
+
* @param {string|number|null|undefined} commentId
|
|
144
|
+
* @returns {boolean}
|
|
145
|
+
*/
|
|
146
|
+
export const isToolTrackedCommentId = commentId => {
|
|
147
|
+
if (commentId === null || commentId === undefined) return false;
|
|
148
|
+
return trackedToolCommentIds.has(String(commentId));
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Returns the set of tracked comment IDs (read-only snapshot).
|
|
153
|
+
* Primarily for tests and diagnostics.
|
|
154
|
+
* @returns {Set<string>}
|
|
155
|
+
*/
|
|
156
|
+
export const getTrackedToolCommentIds = () => new Set(trackedToolCommentIds);
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Reset tracking state. Primarily for tests; solve.mjs does not need to
|
|
160
|
+
* call this between real sessions because each invocation is a fresh
|
|
161
|
+
* process.
|
|
162
|
+
*/
|
|
163
|
+
export const resetTrackedToolCommentIds = () => {
|
|
164
|
+
trackedToolCommentIds.clear();
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Post a GitHub comment on a PR or issue via `gh api` and return the
|
|
169
|
+
* numeric comment ID (as string). The ID is also automatically tracked in
|
|
170
|
+
* the in-memory set above.
|
|
171
|
+
*
|
|
172
|
+
* This is the preferred path for all tool-posted comments because `gh pr
|
|
173
|
+
* comment` / `gh issue comment` only print the comment URL to stdout, and
|
|
174
|
+
* extracting the numeric ID from a URL is brittle. `gh api POST` returns
|
|
175
|
+
* full JSON, from which the ID is trivial to extract.
|
|
176
|
+
*
|
|
177
|
+
* Falls back to best-effort URL parsing if JSON parsing fails, so a single
|
|
178
|
+
* API change cannot break the code path.
|
|
179
|
+
*
|
|
180
|
+
* @param {Object} options
|
|
181
|
+
* @param {Function} options.$ - command-stream tagged template (required — we
|
|
182
|
+
* accept it as a parameter so this module has no top-level dependency on
|
|
183
|
+
* `command-stream`, keeping it cheap to import from tests)
|
|
184
|
+
* @param {string} options.owner
|
|
185
|
+
* @param {string} options.repo
|
|
186
|
+
* @param {number|string} options.targetNumber - PR or issue number
|
|
187
|
+
* @param {string} options.body
|
|
188
|
+
* @returns {Promise<{ok: boolean, commentId: string|null, stderr?: string}>}
|
|
189
|
+
*/
|
|
190
|
+
export const postTrackedComment = async ({ $, owner, repo, targetNumber, body }) => {
|
|
191
|
+
if (!$) {
|
|
192
|
+
throw new Error('postTrackedComment requires a command-stream $ helper');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Use `gh api` with stdin to avoid shell-quoting problems on multi-line
|
|
196
|
+
// bodies and to get JSON back so we can extract the comment ID.
|
|
197
|
+
// We use the /issues/<n>/comments endpoint because it works identically
|
|
198
|
+
// for both PRs and issues (a PR is an issue at this endpoint).
|
|
199
|
+
const apiPath = `repos/${owner}/${repo}/issues/${targetNumber}/comments`;
|
|
200
|
+
const payload = JSON.stringify({ body });
|
|
201
|
+
|
|
202
|
+
// `gh api --input -` reads from stdin. command-stream supports .stdin(...)
|
|
203
|
+
// on the returned process handle (same API used in interactive-mode.lib.mjs
|
|
204
|
+
// via execFileAsync). We build the invocation through $ so callers can
|
|
205
|
+
// inject a mock.
|
|
206
|
+
let result;
|
|
207
|
+
try {
|
|
208
|
+
result = await $({ input: payload })`gh api ${apiPath} -X POST --input -`;
|
|
209
|
+
} catch (err) {
|
|
210
|
+
return { ok: false, commentId: null, stderr: err && err.message ? err.message : String(err) };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (result.code !== 0) {
|
|
214
|
+
const stderr = result.stderr ? result.stderr.toString() : '';
|
|
215
|
+
return { ok: false, commentId: null, stderr };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const stdout = result.stdout ? result.stdout.toString() : '';
|
|
219
|
+
let commentId = null;
|
|
220
|
+
try {
|
|
221
|
+
const parsed = JSON.parse(stdout);
|
|
222
|
+
if (parsed && parsed.id !== undefined && parsed.id !== null) {
|
|
223
|
+
commentId = String(parsed.id);
|
|
224
|
+
}
|
|
225
|
+
} catch {
|
|
226
|
+
// Fallback: match numeric id in the JSON text, or the issuecomment-<n>
|
|
227
|
+
// fragment in the html_url, whichever shows up first.
|
|
228
|
+
const match = stdout.match(/"id"\s*:\s*(\d+)|issuecomment-(\d+)/);
|
|
229
|
+
if (match) commentId = match[1] || match[2] || null;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
trackToolCommentId(commentId);
|
|
233
|
+
|
|
234
|
+
return { ok: true, commentId };
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Post a GitHub comment whose body is already written to a file on disk.
|
|
239
|
+
* Used by attachLogToGitHub() where the comment body can be tens of KB
|
|
240
|
+
* (entire execution log embedded in a <details>) — too large for inline
|
|
241
|
+
* shell arguments and awkward to pipe as stdin JSON.
|
|
242
|
+
*
|
|
243
|
+
* Reads the file and posts via postTrackedComment() so the returned comment
|
|
244
|
+
* ID is tracked exactly like any other tool-posted comment. Kept separate
|
|
245
|
+
* from postTrackedComment so callers that already have a body string don't
|
|
246
|
+
* pay for a tempfile round-trip.
|
|
247
|
+
*
|
|
248
|
+
* @param {Object} options
|
|
249
|
+
* @param {Function} options.$ - command-stream tagged template
|
|
250
|
+
* @param {string} options.owner
|
|
251
|
+
* @param {string} options.repo
|
|
252
|
+
* @param {number|string} options.targetNumber
|
|
253
|
+
* @param {string} options.bodyFile - absolute path to the comment body file
|
|
254
|
+
* @returns {Promise<{ok: boolean, commentId: string|null, stderr?: string}>}
|
|
255
|
+
*/
|
|
256
|
+
export const postTrackedCommentFromFile = async ({ $, owner, repo, targetNumber, bodyFile }) => {
|
|
257
|
+
if (!$) {
|
|
258
|
+
throw new Error('postTrackedCommentFromFile requires a command-stream $ helper');
|
|
259
|
+
}
|
|
260
|
+
if (typeof globalThis.use === 'undefined') {
|
|
261
|
+
globalThis.use = (await eval(await (await fetch('https://unpkg.com/use-m/use.js')).text())).use;
|
|
262
|
+
}
|
|
263
|
+
const fs = (await globalThis.use('fs')).promises;
|
|
264
|
+
let body;
|
|
265
|
+
try {
|
|
266
|
+
body = await fs.readFile(bodyFile, 'utf8');
|
|
267
|
+
} catch (err) {
|
|
268
|
+
return { ok: false, commentId: null, stderr: err && err.message ? err.message : String(err) };
|
|
269
|
+
}
|
|
270
|
+
return postTrackedComment({ $, owner, repo, targetNumber, body });
|
|
271
|
+
};
|