@link-assistant/hive-mind 1.59.3 → 1.59.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 +82 -0
- package/package.json +1 -1
- package/src/hive.mjs +5 -2
- package/src/telegram-terminal-watch-command.lib.mjs +3 -33
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,87 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.59.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- b2e0d12: Fix `/terminal_watch` uploading the full session log file when the watch
|
|
8
|
+
completes — addresses issue
|
|
9
|
+
[#1720](https://github.com/link-assistant/hive-mind/issues/1720).
|
|
10
|
+
|
|
11
|
+
Before this fix, `/terminal_watch` finished by calling
|
|
12
|
+
`bot.telegram.sendDocument(chatId, ...)` to attach the `<uuid>.log` file. That
|
|
13
|
+
had two unwanted effects:
|
|
14
|
+
- It duplicated work that the dedicated `/log` command already does.
|
|
15
|
+
- The bare `bot.telegram.sendDocument(chatId, ...)` call did not carry
|
|
16
|
+
`message_thread_id`, so in forum-enabled supergroups the document landed in
|
|
17
|
+
the **General** topic instead of the topic where `/terminal_watch` was
|
|
18
|
+
invoked, and it was not threaded as a reply.
|
|
19
|
+
|
|
20
|
+
`/terminal_watch` now only updates the live "✅ Terminal watch complete"
|
|
21
|
+
message at the end of the session. To download the log, use
|
|
22
|
+
`/log <uuid>` — it correctly replies in the originating topic via
|
|
23
|
+
`ctx.replyWithDocument`, which Telegraf annotates with `message_thread_id`
|
|
24
|
+
automatically.
|
|
25
|
+
|
|
26
|
+
A new regression test (`tests/test-issue-1720-terminal-watch-no-log.mjs`)
|
|
27
|
+
guards both behaviours, and `tests/test-issue-467-terminal-watch.mjs` was
|
|
28
|
+
updated to assert that no document is uploaded by the watcher.
|
|
29
|
+
|
|
30
|
+
- 5c87a38: Fix `hive` to (a) stop forwarding `false` for solve options whose `type` is
|
|
31
|
+
`'string'` but whose `default` is `false`, and (b) exit non-zero when any
|
|
32
|
+
worker fails — issue #1718.
|
|
33
|
+
|
|
34
|
+
Previously, when a user ran `/hive` against several issues, every spawned
|
|
35
|
+
`solve` worker crashed with:
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
Invalid --working-session-live-progress value: "false". Expected "comment" or "pr".
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
…and `hive` itself still exited with code `0`, so the Telegram bot rendered a
|
|
42
|
+
green "Work session finished successfully" envelope even though zero PRs had
|
|
43
|
+
been created.
|
|
44
|
+
|
|
45
|
+
Two independent root causes:
|
|
46
|
+
1. **Auto-forwarder leaked `false` as a string.** In
|
|
47
|
+
[`src/hive.mjs`](./src/hive.mjs), the auto-forward block read:
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
} else if ((def.type === 'string' || def.type === 'number') && value !== undefined) {
|
|
51
|
+
args.push(`--${optionName}`, String(value));
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
For `working-session-live-progress`, `solve.config.lib.mjs` declares
|
|
56
|
+
`type: 'string', default: false`. yargs preserves the boolean `false`
|
|
57
|
+
verbatim, so hive forwarded `--working-session-live-progress false`,
|
|
58
|
+
which `solve` rejects. The fix adds `&& value !== false` to the
|
|
59
|
+
predicate. Other `type:'string'` options whose `default` is `false`
|
|
60
|
+
are now also protected by a single defense-in-depth check.
|
|
61
|
+
|
|
62
|
+
2. **No non-zero exit on worker failures.** After `monitorWithSentry()`
|
|
63
|
+
resolved, hive returned without consulting `issueQueue.getStats()`. The
|
|
64
|
+
fix queries `finalStats = issueQueue.getStats()` and calls
|
|
65
|
+
`safeExit(1, …)` when `finalStats.failed > 0`, mirroring the exit
|
|
66
|
+
semantics solve already uses. Wrappers like `start-command`, the Telegram
|
|
67
|
+
bot, and CI now correctly observe the failure.
|
|
68
|
+
|
|
69
|
+
`--isolation screen` (R3 of the issue) was already wired through correctly;
|
|
70
|
+
no change required there. The verbose forwarder dump
|
|
71
|
+
(`📋 Command: ${solveCommand} ${args.join(' ')}`) — which is what allowed us
|
|
72
|
+
to diagnose this run in the first place — is preserved.
|
|
73
|
+
|
|
74
|
+
Tests: [`tests/test-issue-1718-hive-passthrough-false.mjs`](./tests/test-issue-1718-hive-passthrough-false.mjs)
|
|
75
|
+
locks the option shape, asserts both fixes are present in `src/hive.mjs`,
|
|
76
|
+
replays the forwarder logic on synthetic argv, and adds a defense-in-depth
|
|
77
|
+
sweep that no `type:'string'` / `default:false` option ever produces
|
|
78
|
+
`--<flag> false`.
|
|
79
|
+
|
|
80
|
+
Documentation: [`docs/case-studies/issue-1718/`](./docs/case-studies/issue-1718/README.md)
|
|
81
|
+
contains the timeline reconstructed from the user's `screen` log, the
|
|
82
|
+
distilled facts, the per-symptom root-cause analysis, the solution plan, and
|
|
83
|
+
notes confirming no upstream report (yargs / start-command) is required.
|
|
84
|
+
|
|
3
85
|
## 1.59.3
|
|
4
86
|
|
|
5
87
|
### Patch Changes
|
package/package.json
CHANGED
package/src/hive.mjs
CHANGED
|
@@ -798,8 +798,8 @@ if (isRunningDirectly) {
|
|
|
798
798
|
for (const entry of value) {
|
|
799
799
|
args.push(`--${optionName}`, String(entry));
|
|
800
800
|
}
|
|
801
|
-
} else if ((def.type === 'string' || def.type === 'number') && value !== undefined) {
|
|
802
|
-
args.push(`--${optionName}`, String(value));
|
|
801
|
+
} else if ((def.type === 'string' || def.type === 'number') && value !== undefined && value !== false) {
|
|
802
|
+
args.push(`--${optionName}`, String(value)); // Issue #1718: skip false (some string options have default:false)
|
|
803
803
|
}
|
|
804
804
|
}
|
|
805
805
|
// Log the actual command being executed so users can investigate/reproduce
|
|
@@ -1483,6 +1483,9 @@ if (isRunningDirectly) {
|
|
|
1483
1483
|
await log(` 📁 Full log file: ${absoluteLogPath}`, { level: 'error' });
|
|
1484
1484
|
await safeExit(1, 'Error occurred');
|
|
1485
1485
|
}
|
|
1486
|
+
|
|
1487
|
+
const finalStats = issueQueue.getStats(); // Issue #1718: surface worker failures via exit code
|
|
1488
|
+
if (finalStats.failed > 0) await safeExit(1, `${finalStats.failed} task(s) failed (completed: ${finalStats.completed})`);
|
|
1486
1489
|
} catch (fatalError) {
|
|
1487
1490
|
// Handle fatal errors during initialization or execution
|
|
1488
1491
|
console.error('\n❌ Fatal error occurred during hive initialization or execution');
|
|
@@ -6,15 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import fs from 'fs/promises';
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import { constants as fsConstants } from 'fs';
|
|
11
9
|
import { extractSessionIdFromText, decideLogDestination, resolveLogPath } from './telegram-log-command.lib.mjs';
|
|
12
10
|
|
|
13
11
|
const DEFAULT_WIDTH = 120;
|
|
14
12
|
const DEFAULT_HEIGHT = 25;
|
|
15
13
|
const DEFAULT_INTERVAL_MS = 2500;
|
|
16
14
|
const DEFAULT_MAX_CHARS = 3400;
|
|
17
|
-
const TELEGRAM_DOCUMENT_MAX_BYTES = 50 * 1024 * 1024;
|
|
18
15
|
const GITHUB_URL_RE = /https:\/\/github\.com\/[^\s"'`<>]+/i;
|
|
19
16
|
const activeWatches = new Map();
|
|
20
17
|
|
|
@@ -135,23 +132,6 @@ export function formatTerminalWatchMessage({ sessionId, statusResult = null, log
|
|
|
135
132
|
return lines.join('\n');
|
|
136
133
|
}
|
|
137
134
|
|
|
138
|
-
async function fileExists(filePath) {
|
|
139
|
-
try {
|
|
140
|
-
await fs.access(filePath, fsConstants.R_OK);
|
|
141
|
-
return true;
|
|
142
|
-
} catch {
|
|
143
|
-
return false;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
async function fileSize(filePath) {
|
|
148
|
-
try {
|
|
149
|
-
return (await fs.stat(filePath)).size;
|
|
150
|
-
} catch {
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
135
|
async function readLogFile(logPath) {
|
|
156
136
|
try {
|
|
157
137
|
return await fs.readFile(logPath, 'utf8');
|
|
@@ -182,16 +162,6 @@ export async function resolveTerminalWatchRepository({ sessionInfo = null, statu
|
|
|
182
162
|
}
|
|
183
163
|
}
|
|
184
164
|
|
|
185
|
-
async function sendLogDocument({ bot, chatId, logPath, sessionId, statusResult }) {
|
|
186
|
-
if (!(await fileExists(logPath))) return;
|
|
187
|
-
const size = await fileSize(logPath);
|
|
188
|
-
if (size !== null && size > TELEGRAM_DOCUMENT_MAX_BYTES) {
|
|
189
|
-
await bot.telegram.sendMessage(chatId, `⚠️ Full log for \`${sessionId}\` is ${(size / (1024 * 1024)).toFixed(1)} MB, above Telegram's 50 MB upload limit.`, { parse_mode: 'Markdown' });
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
await bot.telegram.sendDocument(chatId, { source: logPath, filename: path.basename(logPath) }, { caption: `📄 Full log for session \`${sessionId}\`${statusResult?.status ? `\nStatus: \`${statusResult.status}\`` : ''}`, parse_mode: 'Markdown' });
|
|
193
|
-
}
|
|
194
|
-
|
|
195
165
|
async function querySessionStatusWithRetry(querySessionStatus, sessionId, verbose, attempts = 3) {
|
|
196
166
|
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
197
167
|
const statusResult = await querySessionStatus(sessionId, verbose);
|
|
@@ -201,7 +171,9 @@ async function querySessionStatusWithRetry(querySessionStatus, sessionId, verbos
|
|
|
201
171
|
return null;
|
|
202
172
|
}
|
|
203
173
|
|
|
204
|
-
|
|
174
|
+
// Note: /terminal_watch never uploads the full session log itself (issue #1720).
|
|
175
|
+
// Use /log <uuid> if you want the log file delivered as a document.
|
|
176
|
+
export function watchTerminalLogSession({ bot, chatId, messageId, sessionId, logPath, querySessionStatus, isTerminalSessionStatus, options = {}, repoDescription = null, verbose = false }) {
|
|
205
177
|
const key = `${chatId}:${messageId}:${sessionId}`;
|
|
206
178
|
activeWatches.get(key)?.stop();
|
|
207
179
|
|
|
@@ -225,7 +197,6 @@ export function watchTerminalLogSession({ bot, chatId, messageId, sessionId, log
|
|
|
225
197
|
if (completed) {
|
|
226
198
|
stopped = true;
|
|
227
199
|
activeWatches.delete(key);
|
|
228
|
-
if (attachLogOnComplete) await sendLogDocument({ bot, chatId, logPath, sessionId, statusResult });
|
|
229
200
|
return;
|
|
230
201
|
}
|
|
231
202
|
} catch (error) {
|
|
@@ -409,6 +380,5 @@ export const __INTERNAL_FOR_TESTS__ = {
|
|
|
409
380
|
DEFAULT_HEIGHT,
|
|
410
381
|
DEFAULT_INTERVAL_MS,
|
|
411
382
|
DEFAULT_MAX_CHARS,
|
|
412
|
-
TELEGRAM_DOCUMENT_MAX_BYTES,
|
|
413
383
|
GITHUB_URL_RE,
|
|
414
384
|
};
|