@link-assistant/hive-mind 2.16.0 → 2.18.0
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 +125 -0
- package/README.hi.md +12 -0
- package/README.md +15 -0
- package/README.ru.md +15 -0
- package/README.zh.md +24 -12
- package/package.json +24 -17
- package/src/agent-snapshot-store.lib.mjs +252 -0
- package/src/agent.lib.mjs +25 -25
- package/src/agent.version-gates.lib.mjs +73 -0
- package/src/bot-lifecycle.lib.mjs +55 -4
- package/src/cleanup.mjs +57 -3
- package/src/disk-guard.lib.mjs +21 -1
- package/src/formal-ai-version.lib.mjs +10 -6
- package/src/github-url-parser.lib.mjs +80 -23
- package/src/github-url-recovery.lib.mjs +514 -0
- package/src/hive.mjs +10 -0
- package/src/instrument.mjs +12 -14
- package/src/instrument.sanitize.lib.mjs +52 -0
- package/src/isolation-runner.lib.mjs +56 -33
- package/src/isolation-runner.parsers.lib.mjs +29 -3
- package/src/isolation-runner.resume.lib.mjs +263 -0
- package/src/locales/en.lino +7 -0
- package/src/locales/hi.lino +7 -0
- package/src/locales/ru.lino +7 -0
- package/src/locales/zh.lino +7 -0
- package/src/pull-request-changes.lib.mjs +1 -1
- package/src/session-kill-diagnostics.lib.mjs +47 -5
- package/src/session-kill-resume.in-place.lib.mjs +136 -0
- package/src/session-kill-resume.lib.mjs +43 -16
- package/src/session-monitor.kill-sections.lib.mjs +8 -0
- package/src/session-store.lib.mjs +1 -1
- package/src/solve.clone-errors.lib.mjs +86 -0
- package/src/solve.repository.lib.mjs +36 -63
- package/src/solve.resource-diagnostics.lib.mjs +34 -1
- package/src/solve.validation.lib.mjs +16 -0
- package/src/start-command-cli.lib.mjs +60 -0
- package/src/telegram-bot.mjs +51 -95
- package/src/telegram-overrides-validation.lib.mjs +73 -0
- package/src/working-session-summary.lib.mjs +1 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared access to the `$` CLI (start-command, link-foundation/start).
|
|
3
|
+
*
|
|
4
|
+
* Extracted from src/isolation-runner.lib.mjs (issue #2189) so the resume/attach
|
|
5
|
+
* wrappers added for `start-command@0.33.0` can reach the same lazily-loaded
|
|
6
|
+
* `command-stream` `$` and the same PATH lookup without importing the runner —
|
|
7
|
+
* which would create a cycle — and without duplicating either.
|
|
8
|
+
*
|
|
9
|
+
* @see https://github.com/link-foundation/start
|
|
10
|
+
* @see https://github.com/link-assistant/hive-mind/issues/2189
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { ensureUseM } from './use-m-bootstrap.lib.mjs';
|
|
14
|
+
|
|
15
|
+
/** The message every wrapper reports when `$` is not installed. */
|
|
16
|
+
export const START_COMMAND_MISSING_ERROR = '`$` (start-command) binary not found on PATH. Install link-foundation/start.';
|
|
17
|
+
|
|
18
|
+
let commandStreamDollarPromise = null;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Lazily load `command-stream`'s `$` template tag.
|
|
22
|
+
*
|
|
23
|
+
* Cached across calls; a failed load clears the cache so a transient failure
|
|
24
|
+
* (a cold `use-m` fetch, say) does not poison every later call.
|
|
25
|
+
*
|
|
26
|
+
* @returns {Promise<Function>} The `$` template tag
|
|
27
|
+
*/
|
|
28
|
+
export async function getCommandStreamDollar() {
|
|
29
|
+
if (!commandStreamDollarPromise) {
|
|
30
|
+
commandStreamDollarPromise = (async () => {
|
|
31
|
+
if (typeof globalThis.use === 'undefined') {
|
|
32
|
+
await ensureUseM();
|
|
33
|
+
}
|
|
34
|
+
const { $ } = await globalThis.use('command-stream');
|
|
35
|
+
return $;
|
|
36
|
+
})();
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
return await commandStreamDollarPromise;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
commandStreamDollarPromise = null;
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Find the `$` CLI binary path.
|
|
48
|
+
*
|
|
49
|
+
* @returns {Promise<string|null>} Path to the `$` binary, or null when absent
|
|
50
|
+
*/
|
|
51
|
+
export async function findStartCommandBinary() {
|
|
52
|
+
try {
|
|
53
|
+
const $ = await getCommandStreamDollar();
|
|
54
|
+
const result = await $({ mirror: false })`which $`;
|
|
55
|
+
const resolved = result.stdout?.toString().trim() || '';
|
|
56
|
+
return resolved || null;
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
package/src/telegram-bot.mjs
CHANGED
|
@@ -27,10 +27,10 @@ const yargs = getLinoYargsFactory();
|
|
|
27
27
|
const { createYargsConfig: createTelegramYargsConfig } = await import('./telegram.config.lib.mjs');
|
|
28
28
|
const { createYargsConfig: createSolveYargsConfig, detectMalformedFlags } = await import('./solve.config.lib.mjs');
|
|
29
29
|
const { createYargsConfig: createHiveYargsConfig } = await import('./hive.config.lib.mjs');
|
|
30
|
-
const { enhanceUnknownArgumentError } = await import('./option-suggestions.lib.mjs');
|
|
31
30
|
const { validateBranchInArgs } = await import('./solve.branch.lib.mjs');
|
|
32
31
|
const { extractIsolationFromArgs, isValidPerCommandIsolation } = await import('./telegram-isolation.lib.mjs');
|
|
33
32
|
const { mergeArgsWithOverrides } = await import('./args-overrides.lib.mjs'); // issue #2085
|
|
33
|
+
const { validateCommandOverrides } = await import('./telegram-overrides-validation.lib.mjs'); // issue #2198
|
|
34
34
|
const config = createTelegramYargsConfig(yargs(hideBin(process.argv))).parse();
|
|
35
35
|
|
|
36
36
|
// Configuration priority: CLI option > --configuration LINO > .lenv > .env
|
|
@@ -109,96 +109,26 @@ if (ISOLATION_BACKEND) {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
// Validate solve overrides early using
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
stderrBuffer.push(chunk);
|
|
128
|
-
return true;
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
try {
|
|
132
|
-
// Use .parse() instead of yargs(args).parseSync() to ensure .strict() mode works
|
|
133
|
-
const testYargs = createSolveYargsConfig(yargs());
|
|
134
|
-
// Suppress yargs error output - we'll handle errors ourselves
|
|
135
|
-
testYargs
|
|
136
|
-
.exitProcess(false)
|
|
137
|
-
.showHelpOnFail(false)
|
|
138
|
-
.fail((msg, err) => {
|
|
139
|
-
if (err) throw err;
|
|
140
|
-
throw new Error(msg);
|
|
141
|
-
});
|
|
142
|
-
await testYargs.parse(testArgs);
|
|
143
|
-
// Issue #1482: Validate --base-branch in overrides early
|
|
144
|
-
const overrideBranchError = validateBranchInArgs(solveOverridesForValidation);
|
|
145
|
-
if (overrideBranchError) throw new Error(overrideBranchError);
|
|
146
|
-
console.log('✅ Solve overrides validated successfully');
|
|
147
|
-
} finally {
|
|
148
|
-
// Restore stderr
|
|
149
|
-
process.stderr.write = originalStderrWrite;
|
|
150
|
-
}
|
|
151
|
-
} catch (error) {
|
|
152
|
-
const enhancedError = enhanceUnknownArgumentError(error, createSolveYargsConfig(yargs()));
|
|
153
|
-
console.error(`❌ Invalid solve-overrides: ${enhancedError.message || String(enhancedError)}`);
|
|
154
|
-
console.error(` Overrides: ${solveOverrides.join(' ')}`);
|
|
155
|
-
process.exit(1);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
// Validate hive overrides early using hive's yargs config
|
|
159
|
-
// Only validate if hive command is enabled
|
|
160
|
-
if (hiveEnabled && hiveOverrides.length > 0) {
|
|
161
|
-
console.log('Validating hive overrides...');
|
|
162
|
-
try {
|
|
163
|
-
const { backend: hiveOverrideIsolation, filteredArgs: hiveOverridesForValidation } = extractIsolationFromArgs(hiveOverrides);
|
|
164
|
-
if (hiveOverrideIsolation && !isValidPerCommandIsolation(hiveOverrideIsolation)) {
|
|
165
|
-
throw new Error(`Invalid --isolation value '${hiveOverrideIsolation}'. Must be: screen, tmux, or docker`);
|
|
166
|
-
}
|
|
167
|
-
// Add a dummy URL as the first argument (required positional for hive)
|
|
168
|
-
const testArgs = ['https://github.com/test/test', ...hiveOverridesForValidation];
|
|
169
|
-
|
|
170
|
-
// Temporarily suppress stderr to avoid yargs error output during validation
|
|
171
|
-
const originalStderrWrite = process.stderr.write;
|
|
172
|
-
const stderrBuffer = [];
|
|
173
|
-
process.stderr.write = chunk => {
|
|
174
|
-
stderrBuffer.push(chunk);
|
|
175
|
-
return true;
|
|
176
|
-
};
|
|
177
|
-
try {
|
|
178
|
-
// Use .parse() instead of yargs(args).parseSync() to ensure .strict() mode works
|
|
179
|
-
const testYargs = createHiveYargsConfig(yargs());
|
|
180
|
-
// Suppress yargs error output - we'll handle errors ourselves
|
|
181
|
-
testYargs
|
|
182
|
-
.exitProcess(false)
|
|
183
|
-
.showHelpOnFail(false)
|
|
184
|
-
.fail((msg, err) => {
|
|
185
|
-
if (err) throw err;
|
|
186
|
-
throw new Error(msg);
|
|
187
|
-
});
|
|
188
|
-
await testYargs.parse(testArgs);
|
|
189
|
-
const overrideBranchError = validateBranchInArgs(hiveOverridesForValidation); // Issue #1482
|
|
190
|
-
if (overrideBranchError) throw new Error(overrideBranchError);
|
|
191
|
-
console.log('✅ Hive overrides validated successfully');
|
|
192
|
-
} finally {
|
|
193
|
-
// Restore stderr
|
|
194
|
-
process.stderr.write = originalStderrWrite;
|
|
195
|
-
}
|
|
196
|
-
} catch (error) {
|
|
197
|
-
const enhancedError = enhanceUnknownArgumentError(error, createHiveYargsConfig(yargs()));
|
|
198
|
-
console.error(`❌ Invalid hive-overrides: ${enhancedError.message || String(enhancedError)}`);
|
|
199
|
-
console.error(` Overrides: ${hiveOverrides.join(' ')}`);
|
|
200
|
-
process.exit(1);
|
|
112
|
+
// Validate solve/hive overrides early, using each command's own yargs config, so
|
|
113
|
+
// a bad flag is rejected at startup instead of when the first session spawns
|
|
114
|
+
// (issue #1209). The shared implementation lives in
|
|
115
|
+
// telegram-overrides-validation.lib.mjs (issue #2198).
|
|
116
|
+
for (const { enabled, label, flag, overrides, createYargsConfig, dummyUrl } of [
|
|
117
|
+
{ enabled: solveEnabled, label: 'Solve', flag: 'solve-overrides', overrides: solveOverrides, createYargsConfig: createSolveYargsConfig, dummyUrl: 'https://github.com/test/test/issues/1' },
|
|
118
|
+
{ enabled: hiveEnabled, label: 'Hive', flag: 'hive-overrides', overrides: hiveOverrides, createYargsConfig: createHiveYargsConfig, dummyUrl: 'https://github.com/test/test' },
|
|
119
|
+
]) {
|
|
120
|
+
if (!enabled || overrides.length === 0) continue;
|
|
121
|
+
|
|
122
|
+
console.log(`Validating ${label.toLowerCase()} overrides...`);
|
|
123
|
+
const result = await validateCommandOverrides({ overrides, createYargsConfig, yargs, dummyUrl });
|
|
124
|
+
if (result.ok) {
|
|
125
|
+
console.log(`✅ ${label} overrides validated successfully`);
|
|
126
|
+
continue;
|
|
201
127
|
}
|
|
128
|
+
|
|
129
|
+
console.error(`❌ Invalid ${flag}: ${result.message}`);
|
|
130
|
+
console.error(` Overrides: ${overrides.join(' ')}`);
|
|
131
|
+
process.exit(1);
|
|
202
132
|
}
|
|
203
133
|
|
|
204
134
|
// Handle dry-run mode - exit after validation WITHOUT loading heavy dependencies
|
|
@@ -239,6 +169,8 @@ const { formatUsageMessage, formatCodexLimitsSection, getAllCachedLimits } = lim
|
|
|
239
169
|
const { handleShowLimitsFlag, captureStartSnapshotAndAppend } = await import('./telegram-show-limits.lib.mjs'); // #594
|
|
240
170
|
const { getVersionInfo, formatVersionMessage } = await import('./version-info.lib.mjs');
|
|
241
171
|
const { escapeMarkdown, escapeMarkdownV2, cleanNonPrintableChars, makeSpecialCharsVisible } = await import('./telegram-markdown.lib.mjs');
|
|
172
|
+
const { formatUrlRepairs, hasNotableRepair, namesGitHubHost, revealHiddenCharacters } = await import('./github-url-recovery.lib.mjs'); // #2194
|
|
173
|
+
|
|
242
174
|
const { getSolveQueue, createQueueExecuteCallback } = await import('./telegram-solve-queue.lib.mjs');
|
|
243
175
|
const { applySolveToolAlias, getFirstParsedPositionalArg, getSolveCommandNameFromText, getSolveToolAliasFromText, moveArgumentToFront, parseArgsWithYargs, parseCommandArgs, SOLVE_COMMAND_NAMES } = await import('./telegram-solve-command.lib.mjs');
|
|
244
176
|
const { executeStartScreen: executeStartScreenCommand, buildExecuteAndUpdateMessage } = await import('./telegram-command-execution.lib.mjs');
|
|
@@ -385,22 +317,28 @@ async function validateGitHubUrl(args, options = {}) {
|
|
|
385
317
|
if (!rawUrl) return { valid: false, error: t('telegram.missing_github_url', { commandName }, { locale }) };
|
|
386
318
|
// Issue #1102: Clean non-printable chars (Zero-Width Space, BOM, etc.) from URLs
|
|
387
319
|
const url = cleanNonPrintableChars(rawUrl);
|
|
388
|
-
|
|
320
|
+
// Issue #2194: the host may be typed in any case (GITHUB.COM) or hidden behind
|
|
321
|
+
// look-alike punctuation, and "github.com" in the path of another host is not a
|
|
322
|
+
// GitHub URL at all — so the recovery layer's host check makes the call, not a
|
|
323
|
+
// substring test that both misses the first case and accepts the second.
|
|
324
|
+
if (!namesGitHubHost(url)) return { valid: false, error: t('telegram.first_arg_must_be_github_url', {}, { locale }) };
|
|
389
325
|
const parsed = parseGitHubUrl(url);
|
|
390
326
|
if (!parsed.valid) return { valid: false, error: parsed.error || 'Invalid GitHub URL', suggestion: parsed.suggestion };
|
|
327
|
+
// Issue #2194: tell the user which URL we actually understood when we had to repair theirs.
|
|
328
|
+
const recoveryNotice = hasNotableRepair(parsed.repairs) ? t('telegram.url_recovered', { original: escapeMarkdown(makeSpecialCharsVisible(rawUrl)), used: escapeMarkdown(parsed.canonical), repairs: escapeMarkdown(formatUrlRepairs(parsed.repairs, { notableOnly: true })) }, { locale }) : null;
|
|
391
329
|
if (!allowedTypes.includes(parsed.type)) {
|
|
392
330
|
const allowedTypesStr = allowedTypes.map(t => (t === 'pull' ? 'pull request' : t)).join(', ');
|
|
393
331
|
const baseUrl = `https://github.com/${parsed.owner}/${parsed.repo}`;
|
|
394
332
|
const escapedUrl = escapeMarkdown(url),
|
|
395
333
|
escapedBaseUrl = escapeMarkdown(baseUrl); // Issue #1102: escape for Markdown
|
|
396
334
|
let error;
|
|
397
|
-
if (parsed.type === 'issues_list') error = t('telegram.url_issues_list_error', { url:
|
|
398
|
-
else if (parsed.type === 'pulls_list') error = t('telegram.url_pulls_list_error', { url:
|
|
335
|
+
if (parsed.type === 'issues_list') error = t('telegram.url_issues_list_error', { url: escapedBaseUrl, example: `${escapedBaseUrl}/issues/1` }, { locale });
|
|
336
|
+
else if (parsed.type === 'pulls_list') error = t('telegram.url_pulls_list_error', { url: escapedBaseUrl, example: `${escapedBaseUrl}/pull/1` }, { locale });
|
|
399
337
|
else if (parsed.type === 'repo') error = t('telegram.url_repo_error', { allowedTypes: allowedTypesStr, url: escapedUrl, example: `${escapedBaseUrl}/issues/1` }, { locale });
|
|
400
338
|
else error = t('telegram.url_must_be_type', { allowedTypes: allowedTypesStr, type: parsed.type.replace('_', ' ') }, { locale });
|
|
401
339
|
return { valid: false, error };
|
|
402
340
|
}
|
|
403
|
-
return { valid: true, parsed, normalizedUrl: url };
|
|
341
|
+
return { valid: true, parsed, normalizedUrl: url, recoveryNotice };
|
|
404
342
|
}
|
|
405
343
|
|
|
406
344
|
const executeAndUpdateMessage = buildExecuteAndUpdateMessage({ resolveIsolation, ISOLATION_BACKEND, isolationRunner, VERBOSE, executeStartScreen, trackSession, untrackSession, AUTO_WATCH_MESSAGE, startAutoTerminalWatchForSession, bot, formatExecutingWorkSessionMessage, formatStartingWorkSessionMessage });
|
|
@@ -609,6 +547,10 @@ async function handleSolveCommand(ctx) {
|
|
|
609
547
|
}
|
|
610
548
|
|
|
611
549
|
VERBOSE && console.log(`[VERBOSE] ${solveCommandDisplay} passed all checks, executing...`);
|
|
550
|
+
// Issue #2194: the incoming text is the only record of what the user actually
|
|
551
|
+
// typed, and the log for that issue did not contain it — so an invisible
|
|
552
|
+
// character in the URL was invisible in the log too. Reveal it here.
|
|
553
|
+
VERBOSE && console.log(`[VERBOSE] ${solveCommandDisplay} raw text: ${revealHiddenCharacters(ctx.message.text)}`);
|
|
612
554
|
const solveToolAlias = getSolveToolAliasFromText(ctx.message.text);
|
|
613
555
|
let userArgs = parseCommandArgs(ctx.message.text);
|
|
614
556
|
|
|
@@ -677,6 +619,8 @@ async function handleSolveCommand(ctx) {
|
|
|
677
619
|
await safeReply(ctx, errorMsg, { reply_to_message_id: ctx.message.message_id });
|
|
678
620
|
return;
|
|
679
621
|
}
|
|
622
|
+
// Issue #2194: the link needed repair, so show what we understood before we act on it.
|
|
623
|
+
if (validation.recoveryNotice) await safeReply(ctx, validation.recoveryNotice, { reply_to_message_id: ctx.message.message_id });
|
|
680
624
|
userArgs = moveArgumentToFront(userArgs, validation.normalizedUrl, cleanNonPrintableChars);
|
|
681
625
|
// Issue #2166: hand the spawned session the same canonical URL that is shown
|
|
682
626
|
// in the chat, so the echo and the actual work can never disagree.
|
|
@@ -896,6 +840,8 @@ async function handleHiveCommand(ctx) {
|
|
|
896
840
|
await safeReply(ctx, errorMsg, { reply_to_message_id: ctx.message.message_id });
|
|
897
841
|
return;
|
|
898
842
|
}
|
|
843
|
+
// Issue #2194: the link needed repair, so show what we understood before we act on it.
|
|
844
|
+
if (validation.recoveryNotice) await safeReply(ctx, validation.recoveryNotice, { reply_to_message_id: ctx.message.message_id });
|
|
899
845
|
// Normalize issues_list/pulls_list to base repo URL, or use cleaned URL
|
|
900
846
|
let normalizedArgs = moveArgumentToFront(userArgs, validation.normalizedUrl, cleanNonPrintableChars);
|
|
901
847
|
const p = validation.parsed;
|
|
@@ -1252,7 +1198,17 @@ async function onBotLaunched() {
|
|
|
1252
1198
|
// the monitor resumes watching — and finally reports any that were killed while
|
|
1253
1199
|
// the bot was down. Done before starting the monitor so the first tick already
|
|
1254
1200
|
// sees the resumed sessions.
|
|
1255
|
-
|
|
1201
|
+
// Issue #2189: before replaying the durable store, let `$ --resume-all`
|
|
1202
|
+
// settle the isolation backend's own record — a restart orphans the detached
|
|
1203
|
+
// completion watchers, so an execution that ended while the bot was down has
|
|
1204
|
+
// nobody left to write its exit.
|
|
1205
|
+
await resumeSessionsOnLaunch({
|
|
1206
|
+
resumeTrackedSessions,
|
|
1207
|
+
reconcileIsolationSessions: typeof isolationRunner?.resumeAllIsolationSessions === 'function' ? ({ verbose }) => isolationRunner.resumeAllIsolationSessions({ verbose }) : null,
|
|
1208
|
+
botStartTime: BOT_START_TIME,
|
|
1209
|
+
verbose: VERBOSE,
|
|
1210
|
+
logger: botLogger,
|
|
1211
|
+
});
|
|
1256
1212
|
|
|
1257
1213
|
startSessionMonitoringOnce();
|
|
1258
1214
|
startFormalAiMaintenanceOnce();
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Early validation of --solve-overrides / --hive-overrides for the Telegram bot.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from src/telegram-bot.mjs, which had grown past the 1350-line
|
|
5
|
+
* warning threshold that scripts/check-file-line-limits.sh enforces (issue
|
|
6
|
+
* #1593, surfaced again by issue #2198). The solve and hive blocks were
|
|
7
|
+
* near-identical copies of each other, so folding them into one parameterised
|
|
8
|
+
* function removes the duplication as well as the lines.
|
|
9
|
+
*
|
|
10
|
+
* Behaviour is unchanged and pinned by tests/test-telegram-bot-dry-run.mjs and
|
|
11
|
+
* tests/test-telegram-bot-configuration-isolation-links-notation.mjs, which
|
|
12
|
+
* assert on the exact console output the caller prints.
|
|
13
|
+
*
|
|
14
|
+
* The function reports rather than exits: the caller owns the messages and the
|
|
15
|
+
* exit code, which keeps this module testable in-process.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { enhanceUnknownArgumentError } from './option-suggestions.lib.mjs';
|
|
19
|
+
import { validateBranchInArgs } from './solve.branch.lib.mjs';
|
|
20
|
+
import { extractIsolationFromArgs, isValidPerCommandIsolation } from './telegram-isolation.lib.mjs';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Parses `overrides` through the real yargs config of the target command, so an
|
|
24
|
+
* unknown or malformed flag is rejected at bot startup rather than when the
|
|
25
|
+
* first command spawns a session (issue #1209).
|
|
26
|
+
*
|
|
27
|
+
* @param {object} options
|
|
28
|
+
* @param {string[]} options.overrides Override arguments to validate.
|
|
29
|
+
* @param {Function} options.createYargsConfig The command's yargs config factory.
|
|
30
|
+
* @param {Function} options.yargs A yargs factory (bound to Links Notation).
|
|
31
|
+
* @param {string} options.dummyUrl Stand-in for the command's required positional.
|
|
32
|
+
* @returns {Promise<{ok: true} | {ok: false, message: string}>}
|
|
33
|
+
*/
|
|
34
|
+
export const validateCommandOverrides = async ({ overrides, createYargsConfig, yargs, dummyUrl }) => {
|
|
35
|
+
try {
|
|
36
|
+
const { backend: overrideIsolation, filteredArgs } = extractIsolationFromArgs(overrides);
|
|
37
|
+
if (overrideIsolation && !isValidPerCommandIsolation(overrideIsolation)) {
|
|
38
|
+
throw new Error(`Invalid --isolation value '${overrideIsolation}'. Must be: screen, tmux, or docker`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const testArgs = [dummyUrl, ...filteredArgs];
|
|
42
|
+
|
|
43
|
+
// yargs writes its own diagnostics to stderr before the .fail() handler
|
|
44
|
+
// runs; suppress them so the caller's single message is what the operator
|
|
45
|
+
// sees.
|
|
46
|
+
const originalStderrWrite = process.stderr.write;
|
|
47
|
+
process.stderr.write = () => true;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
// .parse() rather than parseSync() so .strict() mode is honoured.
|
|
51
|
+
const testYargs = createYargsConfig(yargs());
|
|
52
|
+
testYargs
|
|
53
|
+
.exitProcess(false)
|
|
54
|
+
.showHelpOnFail(false)
|
|
55
|
+
.fail((msg, err) => {
|
|
56
|
+
if (err) throw err;
|
|
57
|
+
throw new Error(msg);
|
|
58
|
+
});
|
|
59
|
+
await testYargs.parse(testArgs);
|
|
60
|
+
|
|
61
|
+
// Issue #1482: --base-branch inside the overrides is validated too.
|
|
62
|
+
const overrideBranchError = validateBranchInArgs(filteredArgs);
|
|
63
|
+
if (overrideBranchError) throw new Error(overrideBranchError);
|
|
64
|
+
} finally {
|
|
65
|
+
process.stderr.write = originalStderrWrite;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { ok: true };
|
|
69
|
+
} catch (error) {
|
|
70
|
+
const enhancedError = enhanceUnknownArgumentError(error, createYargsConfig(yargs()));
|
|
71
|
+
return { ok: false, message: enhancedError.message || String(enhancedError) };
|
|
72
|
+
}
|
|
73
|
+
};
|
|
@@ -64,7 +64,7 @@ export const formatWorkingSessionSummaryMarkdown = text => {
|
|
|
64
64
|
let inFence = false;
|
|
65
65
|
let previousNonEmpty = '';
|
|
66
66
|
|
|
67
|
-
for (let index = 0; index < lines.length;
|
|
67
|
+
for (let index = 0; index < lines.length;) {
|
|
68
68
|
const line = lines[index];
|
|
69
69
|
if (/^\s*(```|~~~)/.test(line)) {
|
|
70
70
|
inFence = !inFence;
|