@link-assistant/hive-mind 2.11.13 → 2.12.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 +18 -0
- package/package.json +4 -1
- package/src/agent-command.lib.mjs +74 -0
- package/src/agent.lib.mjs +59 -34
- package/src/agentic-cli-updater.lib.mjs +241 -0
- package/src/claude.connection.lib.mjs +209 -0
- package/src/claude.lib.mjs +6 -202
- package/src/codex.lib.mjs +0 -128
- package/src/formal-ai-isolation.lib.mjs +62 -0
- package/src/formal-ai-maintenance.lib.mjs +106 -0
- package/src/formal-ai-model.lib.mjs +25 -0
- package/src/formal-ai-runtime.lib.mjs +10 -0
- package/src/formal-ai-sidecar.lib.mjs +565 -0
- package/src/formal-ai-updater.lib.mjs +294 -0
- package/src/formal-ai-version.lib.mjs +100 -0
- package/src/formal-ai.lib.mjs +11 -16
- package/src/github-rate-limit.lib.mjs +3 -0
- package/src/github-url-parser.lib.mjs +255 -0
- package/src/github.lib.mjs +22 -343
- package/src/hive.mjs +0 -152
- package/src/interactive-mode.lib.mjs +0 -43
- package/src/isolation-runner.lib.mjs +44 -173
- package/src/limits.lib.mjs +0 -89
- package/src/model-args.lib.mjs +32 -0
- package/src/models/index.mjs +5 -19
- package/src/session-monitor.lib.mjs +14 -172
- package/src/solve.auto-merge.lib.mjs +70 -164
- package/src/solve.mjs +31 -193
- package/src/solve.repository.lib.mjs +0 -83
- package/src/solve.results.lib.mjs +2 -92
- package/src/solve.session.lib.mjs +52 -19
- package/src/solve.tool-uncommitted.lib.mjs +22 -0
- package/src/state-lock.lib.mjs +82 -0
- package/src/telegram-bot.mjs +17 -65
- package/src/telegram-fix-command.lib.mjs +1 -8
- package/src/telegram-merge-queue.lib.mjs +3 -155
- package/src/telegram-solve-queue.lib.mjs +9 -168
- package/src/telegram-task-command.lib.mjs +1 -8
- package/src/use-m-bootstrap.lib.mjs +6 -5
- package/src/use-with-retry.lib.mjs +128 -2
- package/src/working-session-summary.lib.mjs +47 -1
|
@@ -62,6 +62,48 @@ function getSessionCommentContent(sessionType, timestamp) {
|
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Post the tracked comment that marks a work-session start.
|
|
67
|
+
*
|
|
68
|
+
* This is separate from startWorkSession because an in-process continuation
|
|
69
|
+
* after a usage-limit wait is already inside a work session: the PR is already
|
|
70
|
+
* draft, but reviewers still need an explicit boundary for the resumed tool
|
|
71
|
+
* execution.
|
|
72
|
+
*
|
|
73
|
+
* @param {Object} options
|
|
74
|
+
* @param {string} options.owner
|
|
75
|
+
* @param {string} options.repo
|
|
76
|
+
* @param {number} options.prNumber
|
|
77
|
+
* @param {Function} options.$
|
|
78
|
+
* @param {Function} options.log
|
|
79
|
+
* @param {Function} options.formatAligned
|
|
80
|
+
* @param {string} options.sessionType
|
|
81
|
+
* @param {Date} [options.timestamp]
|
|
82
|
+
*/
|
|
83
|
+
export async function postWorkSessionStartComment({ owner, repo, prNumber, $, log, formatAligned, sessionType, timestamp = new Date() }) {
|
|
84
|
+
try {
|
|
85
|
+
const { emoji, header, description } = getSessionCommentContent(sessionType, timestamp);
|
|
86
|
+
const startComment = `${emoji} **${header}**\n\n${description}`;
|
|
87
|
+
const { ok, commentId, stderr } = await postTrackedComment({ $, owner, repo, targetNumber: prNumber, body: startComment });
|
|
88
|
+
if (ok) {
|
|
89
|
+
await log(formatAligned('💬', 'Posted:', `${header} comment${commentId ? ` (id=${commentId})` : ''}`, 2));
|
|
90
|
+
} else {
|
|
91
|
+
await log(`Warning: Could not post work start comment: ${stderr || 'unknown error'}`, { level: 'warning' });
|
|
92
|
+
}
|
|
93
|
+
} catch (error) {
|
|
94
|
+
const sentryLib = await import('./sentry.lib.mjs');
|
|
95
|
+
const { reportError } = sentryLib;
|
|
96
|
+
reportError(error, {
|
|
97
|
+
context: 'post_start_comment',
|
|
98
|
+
owner,
|
|
99
|
+
repo,
|
|
100
|
+
prNumber,
|
|
101
|
+
operation: 'create_pr_comment',
|
|
102
|
+
});
|
|
103
|
+
await log('Warning: Could not post work start comment', { level: 'warning' });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
65
107
|
/**
|
|
66
108
|
* Start a work session and post appropriate comment
|
|
67
109
|
* @param {Object} options - Session options
|
|
@@ -101,25 +143,16 @@ export async function startWorkSession({ isContinueMode, prNumber, argv, log, fo
|
|
|
101
143
|
// Post a comment marking the start of work session with appropriate header based on session type.
|
|
102
144
|
// Issue #1625: Use postTrackedComment so the comment ID is registered in-memory and can be
|
|
103
145
|
// excluded from the "did the AI post anything?" check in checkForAiCreatedComments().
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
const sentryLib = await import('./sentry.lib.mjs');
|
|
115
|
-
const { reportError } = sentryLib;
|
|
116
|
-
reportError(error, {
|
|
117
|
-
context: 'post_start_comment',
|
|
118
|
-
prNumber,
|
|
119
|
-
operation: 'create_pr_comment',
|
|
120
|
-
});
|
|
121
|
-
await log('Warning: Could not post work start comment', { level: 'warning' });
|
|
122
|
-
}
|
|
146
|
+
await postWorkSessionStartComment({
|
|
147
|
+
owner: global.owner,
|
|
148
|
+
repo: global.repo,
|
|
149
|
+
prNumber,
|
|
150
|
+
$,
|
|
151
|
+
log,
|
|
152
|
+
formatAligned,
|
|
153
|
+
sessionType,
|
|
154
|
+
timestamp: workStartTime,
|
|
155
|
+
});
|
|
123
156
|
}
|
|
124
157
|
|
|
125
158
|
return workStartTime;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const TOOL_MODULES = {
|
|
2
|
+
agent: './agent.lib.mjs',
|
|
3
|
+
codex: './codex.lib.mjs',
|
|
4
|
+
gemini: './gemini.lib.mjs',
|
|
5
|
+
opencode: './opencode.lib.mjs',
|
|
6
|
+
qwen: './qwen.lib.mjs',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export async function resolveUncommittedChangesTool({ argv, claudeLib, importModule = specifier => import(specifier) }) {
|
|
10
|
+
if (argv.useAgentCommander) {
|
|
11
|
+
const agentCommanderLib = await importModule('./agent-commander.lib.mjs');
|
|
12
|
+
return { agentCommanderLib, checkForUncommittedChanges: agentCommanderLib.checkForUncommittedChanges };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const toolModule = TOOL_MODULES[argv.tool];
|
|
16
|
+
if (toolModule) {
|
|
17
|
+
const module = await importModule(toolModule);
|
|
18
|
+
return { agentCommanderLib: null, checkForUncommittedChanges: module.checkForUncommittedChanges };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return { agentCommanderLib: null, checkForUncommittedChanges: claudeLib.checkForUncommittedChanges };
|
|
22
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-process exclusive locks over the durable bot state directory.
|
|
3
|
+
*
|
|
4
|
+
* Issue #2146 introduced two background maintainers — the Formal AI sidecar
|
|
5
|
+
* lifecycle and the agentic-CLI refresh — that must never interleave with the
|
|
6
|
+
* work they maintain. Both need the same primitive, so it lives here once.
|
|
7
|
+
*
|
|
8
|
+
* `mkdir` is the lock: it is atomic on every filesystem the bot runs on, unlike
|
|
9
|
+
* "check then create" with a regular file. A lock older than `staleMs` is
|
|
10
|
+
* broken so a killed process cannot deadlock the bot forever.
|
|
11
|
+
*
|
|
12
|
+
* @see https://github.com/link-assistant/hive-mind/issues/2146
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import fs from 'node:fs';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
|
|
18
|
+
import { resolveBotStateDir } from './session-store.lib.mjs';
|
|
19
|
+
|
|
20
|
+
export const DEFAULT_STATE_LOCK_TIMEOUT_MS = 10 * 60 * 1000;
|
|
21
|
+
export const DEFAULT_STATE_LOCK_STALE_MS = 15 * 60 * 1000;
|
|
22
|
+
export const DEFAULT_STATE_LOCK_POLL_MS = 250;
|
|
23
|
+
|
|
24
|
+
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
25
|
+
|
|
26
|
+
/** Absolute path of the lock directory backing `name`. */
|
|
27
|
+
export const resolveStateLockPath = (name, env = process.env) => path.join(resolveBotStateDir(env), `${name}.lock`);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Run `fn` while holding the named state lock.
|
|
31
|
+
*
|
|
32
|
+
* @param {string} name - Lock name, e.g. `formal-ai-sidecar`.
|
|
33
|
+
* @param {() => Promise<any>} fn - Critical section.
|
|
34
|
+
* @returns {Promise<any>} Whatever `fn` resolves to.
|
|
35
|
+
*/
|
|
36
|
+
export const withStateLock = async (name, fn, { env = process.env, fsImpl = fs, now = () => Date.now(), timeoutMs = DEFAULT_STATE_LOCK_TIMEOUT_MS, staleMs = DEFAULT_STATE_LOCK_STALE_MS, pollMs = DEFAULT_STATE_LOCK_POLL_MS, sleepImpl = sleep, log = null } = {}) => {
|
|
37
|
+
const lockPath = resolveStateLockPath(name, env);
|
|
38
|
+
fsImpl.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
39
|
+
|
|
40
|
+
const deadline = now() + timeoutMs;
|
|
41
|
+
for (;;) {
|
|
42
|
+
try {
|
|
43
|
+
fsImpl.mkdirSync(lockPath);
|
|
44
|
+
break;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (error?.code !== 'EEXIST') throw error;
|
|
47
|
+
|
|
48
|
+
let age;
|
|
49
|
+
try {
|
|
50
|
+
age = now() - fsImpl.statSync(lockPath).mtimeMs;
|
|
51
|
+
} catch {
|
|
52
|
+
// The holder released it between mkdir and stat; retry immediately.
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (age > staleMs) {
|
|
57
|
+
if (log) await log(`⚠️ ${name} lock held for ${Math.round(age / 1000)}s with no owner; breaking it`);
|
|
58
|
+
try {
|
|
59
|
+
fsImpl.rmSync(lockPath, { recursive: true, force: true });
|
|
60
|
+
} catch {
|
|
61
|
+
// Another waiter won the race; retry normally.
|
|
62
|
+
}
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (now() >= deadline) throw new Error(`Timed out after ${Math.round(timeoutMs / 1000)}s waiting for the ${name} lock at ${lockPath}`, { cause: error });
|
|
67
|
+
await sleepImpl(pollMs);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
return await fn();
|
|
73
|
+
} finally {
|
|
74
|
+
try {
|
|
75
|
+
fsImpl.rmSync(lockPath, { recursive: true, force: true });
|
|
76
|
+
} catch {
|
|
77
|
+
// Losing the release is recoverable through the stale-lock path above.
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export default { DEFAULT_STATE_LOCK_POLL_MS, DEFAULT_STATE_LOCK_STALE_MS, DEFAULT_STATE_LOCK_TIMEOUT_MS, resolveStateLockPath, withStateLock };
|