@link-assistant/hive-mind 2.8.6 → 2.8.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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 2.8.7
4
+
5
+ ### Patch Changes
6
+
7
+ - f8abac0: fix(telegram): apply operator `TELEGRAM_SOLVE_OVERRIDES` to the `/solve` started by `/fix` (#2085)
8
+
9
+ `/fix --ci-cd` genuinely spawns the real `solve.mjs`, but the Telegram bot's
10
+ operator solve overrides (e.g. `--attach-logs`) were only merged into `/solve`
11
+ and `/hive` — never `/fix`. As a result the solve launched by `/fix` ran
12
+ without the operator's defaults. The `mergeArgsWithOverrides` helper is now
13
+ extracted into a shared `src/args-overrides.lib.mjs` module and the `/fix`
14
+ handler applies `solveOverrides` (including an optional `--isolation` override)
15
+ exactly like `/solve` does, restoring the missing defaults.
16
+
3
17
  ## 2.8.6
4
18
 
5
19
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "2.8.6",
3
+ "version": "2.8.7",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Shared CLI-argument override merging (issue #2085).
3
+ *
4
+ * The Telegram bot lets operators configure "override" options that are always
5
+ * applied to a command regardless of what the requester typed — e.g.
6
+ * `TELEGRAM_SOLVE_OVERRIDES="(\n --attach-logs\n --auto-continue\n)"`. These
7
+ * overrides encode the operator's defaults for `/solve`.
8
+ *
9
+ * `mergeArgsWithOverrides` used to live inside `telegram-bot.mjs`, where only
10
+ * the `/solve` and `/hive` handlers could reach it. `/fix` hands its generated
11
+ * issue off to `/solve`, so it must apply the very same solve overrides —
12
+ * otherwise the solve started by `/fix` silently runs without the operator's
13
+ * defaults (issue #2085: "`--attach-logs` were not applied"). Extracting the
14
+ * helper here lets `telegram-fix-command.lib.mjs` reuse the exact same merge
15
+ * semantics without importing the bot entry point (which would be circular).
16
+ */
17
+
18
+ /**
19
+ * Merge operator override options into a user-supplied argument list.
20
+ *
21
+ * Override flags win: any user flag that also appears in `overrides` (together
22
+ * with its value, if any) is dropped, then every override is appended. Boolean
23
+ * flags and `--flag value` pairs are both handled. The relative order of the
24
+ * surviving user args (including positionals like the issue/repository URL) is
25
+ * preserved, and the overrides are appended at the end so they take precedence
26
+ * in last-wins CLI parsers.
27
+ *
28
+ * @param {string[]} userArgs - Arguments the requester supplied.
29
+ * @param {string[]} overrides - Operator override options (already tokenized).
30
+ * @returns {string[]} The merged argument list.
31
+ */
32
+ export function mergeArgsWithOverrides(userArgs, overrides) {
33
+ if (!overrides || overrides.length === 0) {
34
+ return Array.isArray(userArgs) ? userArgs : [];
35
+ }
36
+ const safeUserArgs = Array.isArray(userArgs) ? userArgs : [];
37
+
38
+ // Parse overrides to identify flags and their values
39
+ const overrideFlags = new Map(); // Map of flag -> value (or null for boolean flags)
40
+
41
+ for (let i = 0; i < overrides.length; i++) {
42
+ const arg = overrides[i];
43
+ if (arg.startsWith('--')) {
44
+ // Check if next item is a value (doesn't start with --)
45
+ if (i + 1 < overrides.length && !overrides[i + 1].startsWith('--')) {
46
+ overrideFlags.set(arg, overrides[i + 1]);
47
+ i++; // Skip the value in next iteration
48
+ } else {
49
+ overrideFlags.set(arg, null); // Boolean flag
50
+ }
51
+ }
52
+ }
53
+
54
+ // Filter user args to remove any that conflict with overrides
55
+ const filteredArgs = [];
56
+ for (let i = 0; i < safeUserArgs.length; i++) {
57
+ const arg = safeUserArgs[i];
58
+ if (arg.startsWith('--')) {
59
+ // If this flag exists in overrides, skip it and its value
60
+ if (overrideFlags.has(arg)) {
61
+ // Skip the flag
62
+ // Also skip next arg if it's a value (doesn't start with --)
63
+ if (i + 1 < safeUserArgs.length && !safeUserArgs[i + 1].startsWith('--')) {
64
+ i++; // Skip the value too
65
+ }
66
+ continue;
67
+ }
68
+ }
69
+ filteredArgs.push(arg);
70
+ }
71
+
72
+ // Merge: filtered user args + overrides
73
+ return [...filteredArgs, ...overrides];
74
+ }
@@ -29,6 +29,7 @@ const { createYargsConfig: createHiveYargsConfig } = await import('./hive.config
29
29
  const { enhanceUnknownArgumentError } = await import('./option-suggestions.lib.mjs');
30
30
  const { validateBranchInArgs } = await import('./solve.branch.lib.mjs');
31
31
  const { extractIsolationFromArgs, isValidPerCommandIsolation } = await import('./telegram-isolation.lib.mjs');
32
+ const { mergeArgsWithOverrides } = await import('./args-overrides.lib.mjs'); // issue #2085
32
33
 
33
34
  const config = createTelegramYargsConfig(yargs(hideBin(process.argv))).parse();
34
35
 
@@ -367,49 +368,6 @@ function validateModelInArgs(args, tool = 'claude') {
367
368
  return null;
368
369
  }
369
370
 
370
- function mergeArgsWithOverrides(userArgs, overrides) {
371
- if (!overrides || overrides.length === 0) {
372
- return userArgs;
373
- }
374
-
375
- // Parse overrides to identify flags and their values
376
- const overrideFlags = new Map(); // Map of flag -> value (or null for boolean flags)
377
-
378
- for (let i = 0; i < overrides.length; i++) {
379
- const arg = overrides[i];
380
- if (arg.startsWith('--')) {
381
- // Check if next item is a value (doesn't start with --)
382
- if (i + 1 < overrides.length && !overrides[i + 1].startsWith('--')) {
383
- overrideFlags.set(arg, overrides[i + 1]);
384
- i++; // Skip the value in next iteration
385
- } else {
386
- overrideFlags.set(arg, null); // Boolean flag
387
- }
388
- }
389
- }
390
-
391
- // Filter user args to remove any that conflict with overrides
392
- const filteredArgs = [];
393
- for (let i = 0; i < userArgs.length; i++) {
394
- const arg = userArgs[i];
395
- if (arg.startsWith('--')) {
396
- // If this flag exists in overrides, skip it and its value
397
- if (overrideFlags.has(arg)) {
398
- // Skip the flag
399
- // Also skip next arg if it's a value (doesn't start with --)
400
- if (i + 1 < userArgs.length && !userArgs[i + 1].startsWith('--')) {
401
- i++; // Skip the value too
402
- }
403
- continue;
404
- }
405
- }
406
- filteredArgs.push(arg);
407
- }
408
-
409
- // Merge: filtered user args + overrides
410
- return [...filteredArgs, ...overrides];
411
- }
412
-
413
371
  // Inject --language LOCALE into spawn args if no language flag is already present.
414
372
  // Issue #378: telegram bot resolves the user's effective locale and propagates
415
373
  // it to spawned solve/hive sessions so the AI tool replies in the same language.
@@ -595,7 +553,7 @@ registerSubscribeCommands(bot, sharedCommandOpts);
595
553
  const { registerTaskCommands } = await import('./telegram-task-command.lib.mjs');
596
554
  const { handleTaskCommand, TASK_COMMAND_NAMES } = registerTaskCommands(bot, { ...sharedCommandOpts, taskEnabled, safeReply, executeAndUpdateMessage, resolveLocale: resolveLocaleFromTelegramCtx });
597
555
  const { registerFixCommand } = await import('./telegram-fix-command.lib.mjs');
598
- const { handleFixCommand, FIX_COMMAND_NAMES } = registerFixCommand(bot, { ...sharedCommandOpts, fixEnabled, safeReply, executeAndUpdateMessage, resolveLocale: resolveLocaleFromTelegramCtx });
556
+ const { handleFixCommand, FIX_COMMAND_NAMES } = registerFixCommand(bot, { ...sharedCommandOpts, fixEnabled, safeReply, executeAndUpdateMessage, resolveLocale: resolveLocaleFromTelegramCtx, solveOverrides });
599
557
  const { registerAuthCommand } = await import('./telegram-auth-command.lib.mjs');
600
558
  const { handleAuthCommand } = registerAuthCommand(bot, { ...sharedCommandOpts, allowedChats, authEnabled, safeReply });
601
559
 
@@ -12,6 +12,7 @@ import { validateModelName } from './models/index.mjs';
12
12
  import { parseFixRepository } from './fix.ci-cd.lib.mjs';
13
13
  import { escapeMarkdown } from './telegram-markdown.lib.mjs';
14
14
  import { extractIsolationFromArgs, isValidPerCommandIsolation } from './telegram-isolation.lib.mjs';
15
+ import { mergeArgsWithOverrides } from './args-overrides.lib.mjs';
15
16
  import { moveArgumentToFront, parseCommandArgs } from './telegram-solve-command.lib.mjs';
16
17
  import { formatStartingWorkSessionMessage } from './work-session-formatting.lib.mjs';
17
18
 
@@ -87,7 +88,7 @@ function injectLanguageIfMissing(args, locale) {
87
88
  }
88
89
 
89
90
  export function registerFixCommand(bot, options) {
90
- const { VERBOSE, fixEnabled, addBreadcrumb, isOldMessage, isForwardedOrReply, isGroupChat, isTopicAuthorized, buildAuthErrorMessage, isChatStopped, getStoppedChatRejectMessage, safeReply, executeAndUpdateMessage, resolveLocale = null } = options;
91
+ const { VERBOSE, fixEnabled, addBreadcrumb, isOldMessage, isForwardedOrReply, isGroupChat, isTopicAuthorized, buildAuthErrorMessage, isChatStopped, getStoppedChatRejectMessage, safeReply, executeAndUpdateMessage, resolveLocale = null, solveOverrides = [] } = options;
91
92
 
92
93
  async function handleFixCommand(ctx) {
93
94
  const commandDisplay = '/fix';
@@ -137,7 +138,22 @@ export function registerFixCommand(bot, options) {
137
138
  return;
138
139
  }
139
140
 
140
- const modelError = validateFixModel(filteredArgs);
141
+ // Issue #2085: /fix hands the generated issue off to /solve, so it must
142
+ // apply the operator's solve overrides (TELEGRAM_SOLVE_OVERRIDES) exactly
143
+ // like the /solve handler does — otherwise the solve started by /fix runs
144
+ // without the operator's defaults (e.g. --attach-logs). The overrides are
145
+ // forwarded to /solve because /fix passes every option it does not consume
146
+ // through to solve.mjs. An --isolation override applies to the /fix work
147
+ // session itself (which contains the nested /solve), mirroring /solve.
148
+ const { backend: overrideIsolation, filteredArgs: solveOverridesWithoutIsolation } = extractIsolationFromArgs(solveOverrides);
149
+ if (overrideIsolation && !isValidPerCommandIsolation(overrideIsolation)) {
150
+ await safeReply(ctx, `❌ Invalid --isolation value '${escapeMarkdown(overrideIsolation)}' in solve overrides. Must be: screen, tmux, or docker`, { reply_to_message_id: ctx.message.message_id });
151
+ return;
152
+ }
153
+ const effectiveIsolation = overrideIsolation || perCommandIsolation;
154
+ const mergedArgs = mergeArgsWithOverrides(filteredArgs, solveOverridesWithoutIsolation);
155
+
156
+ const modelError = validateFixModel(mergedArgs);
141
157
  if (modelError) {
142
158
  await safeReply(ctx, `❌ ${escapeMarkdown(modelError)}`, { reply_to_message_id: ctx.message.message_id });
143
159
  return;
@@ -147,12 +163,13 @@ export function registerFixCommand(bot, options) {
147
163
  const userOptionsRaw = built.args.slice(1).join(' ');
148
164
  let infoBlock = `Requested by: ${requester}\nRepository: ${escapeMarkdown(built.repository.url)}`;
149
165
  if (userOptionsRaw) infoBlock += `\n\n🛠 Options: ${escapeMarkdown(userOptionsRaw)}`;
166
+ if (solveOverrides.length > 0) infoBlock += `\n\n🔒 Solve overrides: ${escapeMarkdown(solveOverrides.join(' '))}`;
150
167
 
151
168
  const fixUrlContext = { owner: built.repository.owner, repo: built.repository.repo, normalized: built.repository.url };
152
169
  const startingMessage = await safeReply(ctx, formatStartingWorkSessionMessage({ infoBlock }), { reply_to_message_id: ctx.message.message_id });
153
170
  const fixLocale = resolveLocale ? resolveLocale(ctx) : null;
154
- const argsForExec = injectLanguageIfMissing(filteredArgs, fixLocale);
155
- await executeAndUpdateMessage(ctx, startingMessage, 'fix', argsForExec, infoBlock, perCommandIsolation || null, getFixToolFromArgs(argsForExec), fixUrlContext);
171
+ const argsForExec = injectLanguageIfMissing(mergedArgs, fixLocale);
172
+ await executeAndUpdateMessage(ctx, startingMessage, 'fix', argsForExec, infoBlock, effectiveIsolation || null, getFixToolFromArgs(argsForExec), fixUrlContext);
156
173
  }
157
174
 
158
175
  bot.command(