dsh-tg-bridge 1.2.9 → 1.2.10

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/bridge.mjs CHANGED
@@ -23,6 +23,7 @@ import net from 'node:net';
23
23
  dns.setDefaultResultOrder('ipv4first');
24
24
  net.setDefaultAutoSelectFamily(false);
25
25
  import { noticeIsObsolete } from './queue-notice.mjs';
26
+ import { immediateKind } from './immediate.mjs';
26
27
  import { handleControl, CONTROL_COMMANDS } from './control.mjs';
27
28
  import { renderPrices } from './prices.mjs';
28
29
  import { t, resolveLang } from './i18n.mjs';
@@ -474,33 +475,13 @@ function commandOf(text) {
474
475
  return `/${match[1].toLowerCase().replace(/[.-]+/g, '')}`;
475
476
  }
476
477
 
477
- /**
478
- * Commands that must run *now*, not behind the turn they are about.
479
- *
480
- * Everything in a topic used to be queued, so `/stop` waited for the very turn
481
- * it was supposed to kill — on 2026-10-24 a 26-minute turn swallowed `/stop`
482
- * and the person got "ход уже идёт" instead of an interruption. These few
483
- * commands touch no turn state, so they can run concurrently with one.
484
- */
485
- const IMMEDIATE_COMMANDS = new Set([
486
- 'stop',
487
- 'logs',
488
- 'status',
489
- 'help',
490
- 'doctor',
491
- 'restart',
492
- ]);
493
-
494
478
  /** Canonical immediate command name for a message, or '' when it must queue. */
495
479
  function immediateCommandOf(text) {
496
480
  const trimmed = String(text ?? '').trim();
497
481
  if (!trimmed.startsWith('/')) return '';
498
- const head = trimmed.split(/\s+/)[0];
482
+ const [head, ...rest] = trimmed.split(/\s+/);
499
483
  const collapsed = commandOf(head).replace(/^\//, '');
500
- if (collapsed === 'apprestart' || collapsed === 'apprestartconfirm' || collapsed === 'restartconfirm') {
501
- return 'restart';
502
- }
503
- return IMMEDIATE_COMMANDS.has(collapsed) ? collapsed : '';
484
+ return immediateKind(collapsed, rest);
504
485
  }
505
486
 
506
487
  /**
package/immediate.mjs ADDED
@@ -0,0 +1,56 @@
1
+ // Which control commands may run while a turn is still working?
2
+ //
3
+ // Everything in a topic used to be queued, so `/stop` waited for the very turn
4
+ // it was supposed to kill. The rule here is not "important commands", it is
5
+ // "commands that neither read nor write the state of the running turn".
6
+ //
7
+ // Groups:
8
+ // * always immediate — read-only reports and turn control;
9
+ // * immediate only without arguments — the bare form reports, the form with
10
+ // an argument changes state (`/soul <text>`, `/lang en`);
11
+ // * `/plugins` without an action — the list; with add/remove/disable/enable
12
+ // it installs plugins, which must queue like any other mutation;
13
+ // * everything else queues, because it changes the session the turn is using
14
+ // (`/model`, `/new`, `/resume`, `/cd`, `/send`, `/seed`, `/plugin`).
15
+
16
+ const PLUGIN_ACTIONS = new Set(['add', 'remove', 'disable', 'enable']);
17
+
18
+ const ALWAYS = new Set([
19
+ // Turn control.
20
+ 'stop',
21
+ 'restart',
22
+ // Reports: they read profiles, sessions, skills and the price table, but
23
+ // touch neither the queue nor the session of the running turn.
24
+ 'status',
25
+ 'logs',
26
+ 'help',
27
+ 'doctor',
28
+ 'models',
29
+ 'sessions',
30
+ 'skills',
31
+ 'prices',
32
+ ]);
33
+
34
+ const BARE_ONLY = new Set([
35
+ 'soul', // `/soul` reads AGENTS.md, `/soul <text>` rewrites it
36
+ 'lang', // `/lang` reports the current language, `/lang en` switches it
37
+ ]);
38
+
39
+ /**
40
+ * Canonical immediate command name, or '' when the message must queue.
41
+ * `name` is the command without the slash and without a bot mention,
42
+ * `args` is everything after it, already split.
43
+ */
44
+ export function immediateKind(name, args = []) {
45
+ const command = String(name ?? '').toLowerCase();
46
+ if (command === 'apprestart' || command === 'apprestartconfirm' || command === 'restartconfirm') {
47
+ return 'restart';
48
+ }
49
+ if (ALWAYS.has(command)) return command;
50
+ if (command === 'plugins') {
51
+ const [action = ''] = args;
52
+ return PLUGIN_ACTIONS.has(String(action).toLowerCase()) ? '' : 'plugins';
53
+ }
54
+ if (BARE_ONLY.has(command) && args.length === 0) return command;
55
+ return '';
56
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-tg-bridge",
3
- "version": "1.2.9",
3
+ "version": "1.2.10",
4
4
  "description": "Telegram bridge for DeepSeek Harness: text, voice, photos and project-per-topic sessions without opening the desktop app",
5
5
  "type": "module",
6
6
  "main": "bridge.mjs",
@@ -45,6 +45,7 @@
45
45
  "LICENSE",
46
46
  "config.example.json",
47
47
  "patches/",
48
- "queue-notice.mjs"
48
+ "queue-notice.mjs",
49
+ "immediate.mjs"
49
50
  ]
50
51
  }