@link-assistant/hive-mind 1.69.5 → 1.69.6

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,11 @@
1
1
  # @link-assistant/hive-mind
2
2
 
3
+ ## 1.69.6
4
+
5
+ ### Patch Changes
6
+
7
+ - c2c51fa: Allow Telegram `/terminal_watch` and `/watch` to be used by the user who started a tracked session while preserving chat-owner access and private-repository DM routing.
8
+
3
9
  ## 1.69.5
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/hive-mind",
3
- "version": "1.69.5",
3
+ "version": "1.69.6",
4
4
  "description": "AI-powered issue solver and hive mind for collaborative problem solving",
5
5
  "main": "src/hive.mjs",
6
6
  "type": "module",
@@ -175,10 +175,10 @@ en
175
175
  telegram.help_stop_start "*/stop* / */start* - Stop or resume accepting new tasks (owner only)"
176
176
  telegram.help_stop_uuid "*/stop* `<uuid>` - Send CTRL+C to an isolated solve/hive session (owner only). Also works as a reply to a message containing the UUID."
177
177
  telegram.help_log "*/log* - Fetch isolation session log (owner only). Usage: `/log <uuid>` or reply with `/log`"
178
- telegram.help_terminal_watch "*/terminal\\_watch* - Live-update an isolation session log (owner only). Usage: `/terminal_watch <uuid>` or reply with `/terminal_watch`"
178
+ telegram.help_terminal_watch "*/terminal\\_watch* (alias: */watch*) - Live-update an isolation session log (chat owner or session requester). Usage: `/terminal_watch <uuid>`, `/watch <uuid>`, or reply with either command"
179
179
  telegram.help_notifications "🔔 *Session Notifications:* Completion notifications are automatic; use /subscribe for private DM forwards."
180
180
  telegram.help_isolation_mode "🔒 *Isolation Mode:* `{{isolationBackend}}` (experimental)"
181
- telegram.help_group_note "⚠️ *Note:* /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /hive, /solve\\_queue, /limits, /version, /accept\\_invites, /merge, /stop and /start commands only work in group chats. /terminal\\_watch, /subscribe and /unsubscribe work in private and group chats."
181
+ telegram.help_group_note "⚠️ *Note:* /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /hive, /solve\\_queue, /limits, /version, /accept\\_invites, /merge, /stop and /start commands only work in group chats. /terminal\\_watch, /watch, /subscribe and /unsubscribe work in private and group chats."
182
182
  telegram.help_common_options "🔧 *Common Options:*"
183
183
  telegram.help_model_option "• `--model <model>` or `-m` - {{modelDescription}}"
184
184
  telegram.help_base_branch_option "• `--base-branch <branch>` or `-b` - Target branch for PR (default: repo default branch)"
@@ -175,10 +175,10 @@ hi
175
175
  telegram.help_stop_start "*/stop* / */start* - नए tasks स्वीकार करना रोकें या फिर शुरू करें (केवल owner)"
176
176
  telegram.help_stop_uuid "*/stop* `<uuid>` - isolated solve/hive session को CTRL+C भेजें (केवल owner)। UUID वाले message पर reply के रूप में भी काम करता है।"
177
177
  telegram.help_log "*/log* - isolated session log लाएँ (केवल owner)। उपयोग: `/log <uuid>` या `/log` से reply करें"
178
- telegram.help_terminal_watch "*/terminal\\_watch* - isolated session log को live-update करें (केवल owner)। उपयोग: `/terminal_watch <uuid>` या `/terminal_watch` से reply करें"
178
+ telegram.help_terminal_watch "*/terminal\\_watch* (alias: */watch*) - isolated session log को live-update करें (chat owner या session requester)। उपयोग: `/terminal_watch <uuid>`, `/watch <uuid>`, या इनमें से किसी command से reply करें"
179
179
  telegram.help_notifications "🔔 *Session Notifications:* completion notifications automatic हैं; private DM forwards के लिए /subscribe उपयोग करें।"
180
180
  telegram.help_isolation_mode "🔒 *Isolation Mode:* `{{isolationBackend}}` (experimental)"
181
- telegram.help_group_note "⚠️ *नोट:* /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /hive, /solve\\_queue, /limits, /version, /accept\\_invites, /merge, /stop और /start commands केवल group chats में काम करती हैं। /terminal\\_watch, /subscribe और /unsubscribe private और group chats में काम करती हैं।"
181
+ telegram.help_group_note "⚠️ *नोट:* /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /hive, /solve\\_queue, /limits, /version, /accept\\_invites, /merge, /stop और /start commands केवल group chats में काम करती हैं। /terminal\\_watch, /watch, /subscribe और /unsubscribe private और group chats में काम करती हैं।"
182
182
  telegram.help_common_options "🔧 *Common Options:*"
183
183
  telegram.help_model_option "• `--model <model>` या `-m` - {{modelDescription}}"
184
184
  telegram.help_base_branch_option "• `--base-branch <branch>` या `-b` - PR के लिए target branch (default: repo default branch)"
@@ -175,10 +175,10 @@ ru
175
175
  telegram.help_stop_start "*/stop* / */start* - Остановить или возобновить прием новых задач (только владелец)"
176
176
  telegram.help_stop_uuid "*/stop* `<uuid>` - Отправить CTRL+C в изолированный сеанс solve/hive (только владелец). Также работает ответом на сообщение с UUID."
177
177
  telegram.help_log "*/log* - Получить лог изолированного сеанса (только владелец). Использование: `/log <uuid>` или ответ `/log`"
178
- telegram.help_terminal_watch "*/terminal\\_watch* - Обновлять лог изолированного сеанса вживую (только владелец). Использование: `/terminal_watch <uuid>` или ответ `/terminal_watch`"
178
+ telegram.help_terminal_watch "*/terminal\\_watch* (псевдоним: */watch*) - Обновлять лог изолированного сеанса вживую (владелец чата или пользователь, запустивший сеанс). Использование: `/terminal_watch <uuid>`, `/watch <uuid>` или ответ одной из этих команд"
179
179
  telegram.help_notifications "🔔 *Уведомления о сеансах:* Уведомления о завершении автоматические; используйте /subscribe для личных пересылок."
180
180
  telegram.help_isolation_mode "🔒 *Режим изоляции:* `{{isolationBackend}}` (экспериментально)"
181
- telegram.help_group_note "⚠️ *Замечание:* команды /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /hive, /solve\\_queue, /limits, /version, /accept\\_invites, /merge, /stop и /start работают только в групповых чатах. /terminal\\_watch, /subscribe и /unsubscribe работают в личных и групповых чатах."
181
+ telegram.help_group_note "⚠️ *Замечание:* команды /solve, /do, /continue, /claude, /codex, /opencode, /agent, /gemini, /qwen, /task, /split, /hive, /solve\\_queue, /limits, /version, /accept\\_invites, /merge, /stop и /start работают только в групповых чатах. /terminal\\_watch, /watch, /subscribe и /unsubscribe работают в личных и групповых чатах."
182
182
  telegram.help_common_options "🔧 *Общие опции:*"
183
183
  telegram.help_model_option "• `--model <model>` или `-m` - {{modelDescription}}"
184
184
  telegram.help_base_branch_option "• `--base-branch <branch>` или `-b` - Целевая ветка для PR (по умолчанию ветка репозитория)"
@@ -175,10 +175,10 @@ zh
175
175
  telegram.help_stop_start "*/stop* / */start* - 停止或恢复接收新任务(仅所有者)"
176
176
  telegram.help_stop_uuid "*/stop* `<uuid>` - 向隔离的 solve/hive 会话发送 CTRL+C(仅所有者)。也可回复包含 UUID 的消息。"
177
177
  telegram.help_log "*/log* - 获取隔离会话日志(仅所有者)。用法:`/log <uuid>` 或回复 `/log`"
178
- telegram.help_terminal_watch "*/terminal\\_watch* - 实时更新隔离会话日志(仅所有者)。用法:`/terminal_watch <uuid>` 或回复 `/terminal_watch`"
178
+ telegram.help_terminal_watch "*/terminal\\_watch*(别名:*/watch*)- 实时更新隔离会话日志(聊天所有者或会话请求者)。用法:`/terminal_watch <uuid>`、`/watch <uuid>`,或用任一命令回复"
179
179
  telegram.help_notifications "🔔 *会话通知:* 完成通知会自动发送;使用 /subscribe 获取私聊转发。"
180
180
  telegram.help_isolation_mode "🔒 *隔离模式:* `{{isolationBackend}}`(实验性)"
181
- telegram.help_group_note "⚠️ *注意:* /solve、/do、/continue、/claude、/codex、/opencode、/agent、/gemini、/qwen、/task、/split、/hive、/solve\\_queue、/limits、/version、/accept\\_invites、/merge、/stop 和 /start 仅在群聊中有效。/terminal\\_watch、/subscribe 和 /unsubscribe 在私聊和群聊中有效。"
181
+ telegram.help_group_note "⚠️ *注意:* /solve、/do、/continue、/claude、/codex、/opencode、/agent、/gemini、/qwen、/task、/split、/hive、/solve\\_queue、/limits、/version、/accept\\_invites、/merge、/stop 和 /start 仅在群聊中有效。/terminal\\_watch、/watch、/subscribe 和 /unsubscribe 在私聊和群聊中有效。"
182
182
  telegram.help_common_options "🔧 *常用选项:*"
183
183
  telegram.help_model_option "• `--model <model>` 或 `-m` - {{modelDescription}}"
184
184
  telegram.help_base_branch_option "• `--base-branch <branch>` 或 `-b` - PR 目标分支(默认:仓库默认分支)"
@@ -17,7 +17,7 @@ const activeWatches = new Map();
17
17
 
18
18
  function splitCommandArgs(text) {
19
19
  const body = String(text || '')
20
- .replace(/^\/terminal_watch(?:@\w+)?\b/i, '')
20
+ .replace(/^\/(?:terminal_watch|watch)(?:@\w+)?\b/i, '')
21
21
  .trim();
22
22
  return body.match(/"[^"]*"|'[^']*'|\S+/g)?.map(token => token.replace(/^(['"])(.*)\1$/, '$2')) || [];
23
23
  }
@@ -171,6 +171,12 @@ async function querySessionStatusWithRetry(querySessionStatus, sessionId, verbos
171
171
  return null;
172
172
  }
173
173
 
174
+ export function isTerminalWatchSessionRequester({ sessionInfo = null, userId = null } = {}) {
175
+ if (userId === null || userId === undefined) return false;
176
+ if (sessionInfo?.requesterUserId === null || sessionInfo?.requesterUserId === undefined) return false;
177
+ return String(sessionInfo.requesterUserId) === String(userId);
178
+ }
179
+
174
180
  // Note: /terminal_watch never uploads the full session log itself (issue #1720).
175
181
  // Use /log <uuid> if you want the log file delivered as a document.
176
182
  function getDisplayedTerminalSnapshot(logText, options) {
@@ -229,7 +235,7 @@ export function watchTerminalLogSession({ bot, chatId, messageId, sessionId, log
229
235
  }
230
236
 
231
237
  function buildUsage() {
232
- return 'Usage:\n• `/terminal_watch <UUID>`\n• Reply to a session message with `/terminal_watch`\n\nOptions: `--size 120x25`, `--width 120`, `--height 25`, `--interval-ms 2500`, `--max-chars 3400`';
238
+ return 'Usage:\n• `/terminal_watch <UUID>` (alias: `/watch <UUID>`)\n• Reply to a session message with `/terminal_watch` or `/watch`\n\nOptions: `--size 120x25`, `--width 120`, `--height 25`, `--interval-ms 2500`, `--max-chars 3400`';
233
239
  }
234
240
 
235
241
  async function createWatchMessage({ ctx, targetChatId, replyToMessageId, text }) {
@@ -302,12 +308,14 @@ export async function startAutoTerminalWatchForSession({ bot, ctx, sessionId, se
302
308
 
303
309
  export async function registerTerminalWatchCommand(bot, options) {
304
310
  const { VERBOSE = false, isOldMessage, isChatAuthorized, isTopicAuthorized, buildAuthErrorMessage } = options;
305
- const runner = await import('./isolation-runner.lib.mjs');
311
+ const runner = !options.querySessionStatus || !options.isTerminalSessionStatus ? await import('./isolation-runner.lib.mjs') : null;
312
+ const querySessionStatus = options.querySessionStatus || runner.querySessionStatus;
313
+ const isTerminalSessionStatus = options.isTerminalSessionStatus || runner.isTerminalSessionStatus;
306
314
  const getTrackedSessionInfo = options.getTrackedSessionInfo || (await import('./session-monitor.lib.mjs')).getTrackedSessionInfo;
307
315
  const detectRepositoryVisibility = options.detectRepositoryVisibility || (await import('./github.lib.mjs')).detectRepositoryVisibility;
308
316
  const parseGitHubUrl = options.parseGitHubUrl || (await import('./github.lib.mjs')).parseGitHubUrl;
309
317
 
310
- bot.command('terminal_watch', async ctx => {
318
+ const handleTerminalWatchCommand = async ctx => {
311
319
  VERBOSE && console.log('[VERBOSE] /terminal_watch command received');
312
320
  if (isOldMessage && isOldMessage(ctx)) return;
313
321
 
@@ -327,17 +335,23 @@ export async function registerTerminalWatchCommand(bot, options) {
327
335
  return;
328
336
  }
329
337
 
338
+ const sessionInfo = getTrackedSessionInfo ? getTrackedSessionInfo(sessionId) : null;
339
+
330
340
  if (chat.type !== 'private') {
331
- try {
332
- const member = await ctx.telegram.getChatMember(chat.id, ctx.from.id);
333
- if (!member || member.status !== 'creator') {
334
- await ctx.reply('❌ /terminal_watch is only available to the chat owner.', { reply_to_message_id: message.message_id });
341
+ if (isTerminalWatchSessionRequester({ sessionInfo, userId: ctx.from?.id })) {
342
+ VERBOSE && console.log(`[VERBOSE] /terminal_watch allowed for session requester ${ctx.from?.id} on ${sessionId}`);
343
+ } else {
344
+ try {
345
+ const member = await ctx.telegram.getChatMember(chat.id, ctx.from.id);
346
+ if (!member || member.status !== 'creator') {
347
+ await ctx.reply('❌ /terminal_watch is only available to the chat owner or the user who started this session.', { reply_to_message_id: message.message_id });
348
+ return;
349
+ }
350
+ } catch (error) {
351
+ console.error('[ERROR] /terminal_watch: getChatMember failed:', error);
352
+ await ctx.reply('❌ Failed to verify permissions for /terminal_watch.', { reply_to_message_id: message.message_id });
335
353
  return;
336
354
  }
337
- } catch (error) {
338
- console.error('[ERROR] /terminal_watch: getChatMember failed:', error);
339
- await ctx.reply('❌ Failed to verify permissions for /terminal_watch.', { reply_to_message_id: message.message_id });
340
- return;
341
355
  }
342
356
  }
343
357
 
@@ -349,7 +363,7 @@ export async function registerTerminalWatchCommand(bot, options) {
349
363
 
350
364
  let statusResult;
351
365
  try {
352
- statusResult = await runner.querySessionStatus(sessionId, VERBOSE);
366
+ statusResult = await querySessionStatus(sessionId, VERBOSE);
353
367
  } catch (error) {
354
368
  console.error('[ERROR] /terminal_watch: querySessionStatus failed:', error);
355
369
  await ctx.reply(`❌ Failed to query session status: ${error.message || String(error)}`, { reply_to_message_id: message.message_id });
@@ -361,7 +375,6 @@ export async function registerTerminalWatchCommand(bot, options) {
361
375
  return;
362
376
  }
363
377
 
364
- const sessionInfo = getTrackedSessionInfo ? getTrackedSessionInfo(sessionId) : null;
365
378
  const { repoVisibility, repoDescription } = await resolveTerminalWatchRepository({ sessionInfo, statusResult, parseGitHubUrl, detectRepositoryVisibility });
366
379
  const decision = decideLogDestination({ statusResult, sessionInfo, repoVisibility, chatType: chat.type });
367
380
  if (decision.destination === 'reject') {
@@ -378,13 +391,16 @@ export async function registerTerminalWatchCommand(bot, options) {
378
391
  }
379
392
 
380
393
  try {
381
- await startWatchFromResolvedSession({ bot, ctx, sessionId, statusResult, sessionInfo, decision, logPath, watchOptions: parsedArgs.options, querySessionStatus: runner.querySessionStatus, isTerminalSessionStatus: runner.isTerminalSessionStatus, repoDescription, verbose: VERBOSE });
394
+ await startWatchFromResolvedSession({ bot, ctx, sessionId, statusResult, sessionInfo, decision, logPath, watchOptions: parsedArgs.options, querySessionStatus, isTerminalSessionStatus, repoDescription, verbose: VERBOSE });
382
395
  } catch (error) {
383
396
  console.error('[ERROR] /terminal_watch: failed to start watch:', error);
384
397
  const friendly = error?.code === 403 || /chat not found|bot can't initiate conversation/i.test(error?.message || '') ? 'I could not send you a DM. Please open a private chat with me and send /start, then try again.' : `Failed to start terminal watch: ${error.message || String(error)}`;
385
398
  await ctx.reply(`❌ ${friendly}`, { reply_to_message_id: message.message_id });
386
399
  }
387
- });
400
+ };
401
+
402
+ bot.command('terminal_watch', handleTerminalWatchCommand);
403
+ bot.command('watch', handleTerminalWatchCommand);
388
404
  }
389
405
 
390
406
  export const __INTERNAL_FOR_TESTS__ = {