dsh-tg-bridge 1.3.0 → 1.3.2

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
@@ -1469,18 +1469,53 @@ function steerRunningTurn(msg, text, sessionId, thread) {
1469
1469
  say: SAY,
1470
1470
  });
1471
1471
  log(`steer ${job.id} -> ${sessionId} (${text.length} chars)`);
1472
- const timer = setTimeout(async () => {
1472
+ watchSteer(job, msg, text, thread, lang);
1473
+ return job;
1474
+ }
1475
+
1476
+ /**
1477
+ * Follow one steer job until it is either acknowledged by the model or taken by
1478
+ * the plugin.
1479
+ *
1480
+ * Two failure modes need cover: a model that stays silent (one neutral line
1481
+ * after 90 s), and a job that never reached the agent because the turn ended
1482
+ * first — that addition must become its own turn instead of expiring unseen in
1483
+ * the directory.
1484
+ */
1485
+ function watchSteer(job, msg, text, thread, lang) {
1486
+ const key = sessionKey(msg.chat.id, thread);
1487
+ const acked = () => existsSync(join(STEER_DIR, 'acks', `${job.id}.json`));
1488
+ const consumed = () => existsSync(join(STEER_DIR, 'done', `${job.id}.json`));
1489
+ const check = async (attempt) => {
1473
1490
  try {
1474
- if (existsSync(join(STEER_DIR, 'acks', `${job.id}.json`))) return;
1475
- if (!activeTurns.has(key)) return; // turn ended; its answer is the reply
1476
- log(`steer ${job.id} unacknowledged after 90s; sending fallback`);
1477
- await send(msg.chat.id, t(lang, 'steer.ackFallback'), thread);
1491
+ if (acked() || consumed()) return;
1492
+ if (activeTurns.has(key)) {
1493
+ if (attempt === 0) {
1494
+ log(`steer ${job.id} unacknowledged after 90s; sending fallback`);
1495
+ await send(msg.chat.id, t(lang, 'steer.ackFallback'), thread);
1496
+ }
1497
+ setTimeout(() => check(attempt + 1), 60_000).unref?.();
1498
+ return;
1499
+ }
1500
+ // The turn is over and the plugin never picked the job up: deliver it as
1501
+ // a turn of its own, otherwise the person's addition is silently lost.
1502
+ const pending = join(STEER_DIR, `${job.id}.json`);
1503
+ if (!existsSync(pending)) return;
1504
+ try { unlinkSync(pending); } catch {}
1505
+ log(`steer ${job.id} not consumed by the turn; re-queued as a new turn`);
1506
+ enqueue(key, () => handleMessage({
1507
+ chat: msg.chat,
1508
+ from: msg.from,
1509
+ message_id: 0,
1510
+ message_thread_id: thread || undefined,
1511
+ is_topic_message: thread > 0,
1512
+ text,
1513
+ }));
1478
1514
  } catch (error) {
1479
- log(`steer fallback failed: ${error.message}`);
1515
+ log(`steer watch failed for ${job.id}: ${error.message}`);
1480
1516
  }
1481
- }, 90_000);
1482
- timer.unref?.();
1483
- return job;
1517
+ };
1518
+ setTimeout(() => check(0), 90_000).unref?.();
1484
1519
  }
1485
1520
 
1486
1521
  function enqueue(key, task, onBusy) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-tg-bridge",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
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",
@@ -6,6 +6,7 @@
6
6
  // `steer()` — next-step input, not a queued next turn — which is what makes
7
7
  // "добавь ещё вот это" land inside the current work instead of after it.
8
8
  import { mkdirSync, readFileSync, readdirSync, renameSync, statSync, unlinkSync, appendFileSync } from 'node:fs';
9
+ import { randomUUID } from 'node:crypto';
9
10
  import { homedir } from 'node:os';
10
11
  import { join } from 'node:path';
11
12
 
@@ -83,6 +84,12 @@ function apply(ctx, config = {}) {
83
84
  'Then continue the work and include this addition. Do not repeat that line in the final answer.',
84
85
  ].join('\n');
85
86
  agent.steer({
87
+ // DSH stores the steered message verbatim and validates the session
88
+ // log on load: an event whose message has no `id`/`role` makes every
89
+ // later turn fail with "event at seq N lacks an identified message",
90
+ // and the whole topic dies until the log is repaired by hand.
91
+ id: randomUUID(),
92
+ role: 'user',
86
93
  content: [{ type: 'text', text: `${String(job.text ?? '')}${line}` }],
87
94
  source: { kind: 'tg-bridge' },
88
95
  });