dsh-tg-bridge 1.5.0 → 1.5.1

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.
Files changed (4) hide show
  1. package/README.md +14 -0
  2. package/bridge.mjs +35 -1
  3. package/package.json +1 -1
  4. package/say.mjs +24 -5
package/README.md CHANGED
@@ -206,6 +206,20 @@ and a line in `~/.dsh/profiles/headless/cordis.patch.yml`:
206
206
  The bridge checks the profile itself: if the line is missing, it does not write the task
207
207
  (it would just expire) and behaves as before, queueing the message.
208
208
 
209
+ ### Live thoughts
210
+
211
+ The agent can leave a short decision trail inside the progress message instead of sending
212
+ extra chat messages:
213
+
214
+ ```bash
215
+ node ~/.dsh/tg-bridge/say.mjs --thought "checking why the watchdog fired twice"
216
+ ```
217
+
218
+ The note is appended to `~/.dsh/tg-bridge/thoughts/<chat>_<thread>.jsonl`; the progress
219
+ message shows the last two as `💭` lines. Inside a turn the chat and thread come from
220
+ `DSH_TG_CHAT` / `DSH_TG_THREAD`, so the command needs no ids. `--thought` never sends a
221
+ Telegram message on its own.
222
+
209
223
  ### Goals and tasks
210
224
 
211
225
  A topic carries its own goal and checklist, so the aim survives `/new`, a
package/bridge.mjs CHANGED
@@ -44,6 +44,7 @@ const LOG = join(DIR, 'bridge.log');
44
44
  const CRED = join(DSH_HOME, '.credentials.yaml');
45
45
  const PENDING_DIR = join(DIR, 'pending');
46
46
  const STEER_DIR = join(DIR, 'steer');
47
+ const THOUGHT_DIR = join(DIR, 'thoughts');
47
48
  // The helper the agent calls to confirm an addition in the chat while the turn
48
49
  // is still running. Lives next to the bridge so an installed copy works too.
49
50
  const SAY = join(DIR, 'say.mjs');
@@ -735,6 +736,9 @@ function runTurn(task, sessionId, key = '', onEvent = null, extraArgs = [], cwd
735
736
  cwd: cwd && existsSync(cwd) ? cwd : config.cwd,
736
737
  env: {
737
738
  ...process.env,
739
+ DSH_TG_KEY: key,
740
+ DSH_TG_CHAT: String(key).split(':')[0] || '',
741
+ DSH_TG_THREAD: String(key).split(':')[1] || '0',
738
742
  ELECTRON_RUN_AS_NODE: '1',
739
743
  UNOROUTER_API_KEY: readSecret('UNOROUTER_API_KEY'),
740
744
  // Free fallback providers: their keys live next to the main one and are
@@ -871,6 +875,34 @@ async function startProgress(chatId, threadId, lang = 'ru') {
871
875
  let dirty = false;
872
876
  let timer = null;
873
877
 
878
+ // Short "💭" notes the agent appends with `say.mjs --thought`. The progress
879
+ // message shows the last two, so the person sees the decision trail without
880
+ // a second stream of chat messages.
881
+ const thoughtFile = join(THOUGHT_DIR, `${chatId}_${threadId || 0}.jsonl`);
882
+ let thoughts = [];
883
+ try {
884
+ mkdirSync(THOUGHT_DIR, { recursive: true });
885
+ writeFileSync(thoughtFile, '');
886
+ } catch {}
887
+ const thoughtTimer = setInterval(() => {
888
+ let next = [];
889
+ try {
890
+ next = readFileSync(thoughtFile, 'utf8')
891
+ .split('\n')
892
+ .filter(Boolean)
893
+ .slice(-2)
894
+ .map((line) => {
895
+ try { return String(JSON.parse(line).text || '').slice(0, 140); } catch { return ''; }
896
+ })
897
+ .filter(Boolean);
898
+ } catch {}
899
+ if (next.join('\n') !== thoughts.join('\n')) {
900
+ thoughts = next;
901
+ schedule();
902
+ }
903
+ }, 3000);
904
+ thoughtTimer.unref?.();
905
+
874
906
  const body = (final = false) => {
875
907
  const seconds = Math.round((Date.now() - started) / 1000);
876
908
  const head = final
@@ -880,6 +912,7 @@ async function startProgress(chatId, threadId, lang = 'ru') {
880
912
  // shell commands read as noise on a phone.
881
913
  const tail = steps.slice(-3).map((line) => `• ${line}`).join('\n');
882
914
  const more = steps.length > 3 ? `\n${t(lang, 'status.more', { n: steps.length - 3 })}` : '';
915
+ const mind = thoughts.length ? `\n${thoughts.map((line) => `💭 ${line}`).join('\n')}` : '';
883
916
  // The cache line belongs to the outcome, not to every intermediate edit.
884
917
  const cache = final && usage.cacheReadTokens
885
918
  ? `\n${t(lang, 'status.cacheLine', {
@@ -887,7 +920,7 @@ async function startProgress(chatId, threadId, lang = 'ru') {
887
920
  k: Math.round(usage.cacheReadTokens / 1000),
888
921
  })}`
889
922
  : '';
890
- return (tail ? `${head}\n${tail}${more}${cache}` : `${head}${cache}`);
923
+ return (tail ? `${head}${mind}\n${tail}${more}${cache}` : `${head}${mind}${cache}`);
891
924
  };
892
925
 
893
926
  try {
@@ -969,6 +1002,7 @@ async function startProgress(chatId, threadId, lang = 'ru') {
969
1002
  },
970
1003
  async finish(result) {
971
1004
  clearTimeout(timer);
1005
+ clearInterval(thoughtTimer);
972
1006
  if (result?.error) {
973
1007
  // A failed turn leaves the status message as the error report and swaps
974
1008
  // «Стоп» for «Повторить»: the person's next move is a retry, not a stop.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-tg-bridge",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
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": "plugin-bridge.mjs",
package/say.mjs CHANGED
@@ -7,16 +7,21 @@
7
7
  //
8
8
  // node say.mjs --steer <job-id> "text"
9
9
  // node say.mjs --chat <chat-id> [--thread <id>] "text"
10
+ // node say.mjs --thought "short note" (chat/thread come from DSH_TG_CHAT/DSH_TG_THREAD)
11
+ //
12
+ // `--thought` does not send a message: it appends to the turn's thought file,
13
+ // and the live progress message shows the last two notes as 💭 lines.
10
14
  //
11
15
  // With `--steer` the chat/thread come from the job file and an ack marker is
12
16
  // written, which suppresses the bridge's fallback line.
13
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
17
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
14
18
  import { homedir } from 'node:os';
15
19
  import { join } from 'node:path';
16
20
 
17
21
  const DSH_HOME = process.env.DSH_HOME || join(homedir(), '.dsh');
18
22
  const STEER = join(DSH_HOME, 'tg-bridge', 'steer');
19
23
  const ACKS = join(STEER, 'acks');
24
+ const THOUGHTS = join(DSH_HOME, 'tg-bridge', 'thoughts');
20
25
 
21
26
  function findToken() {
22
27
  const env = (process.env.DSH_TELEGRAM_BOT_TOKEN || '').trim();
@@ -39,18 +44,19 @@ function jobOf(id) {
39
44
  }
40
45
 
41
46
  function usage() {
42
- console.error('usage: node say.mjs --steer <id> "text" | --chat <id> [--thread <id>] "text"');
47
+ console.error('usage: node say.mjs --steer <id> "text" | --chat <id> [--thread <id>] "text" | --thought "text"');
43
48
  process.exit(2);
44
49
  }
45
50
 
46
51
  function parse(argv) {
47
- const out = { chatId: 0, threadId: 0, steer: '', text: '' };
52
+ const out = { chatId: 0, threadId: 0, steer: '', thought: false, text: '' };
48
53
  const rest = [];
49
54
  for (let i = 0; i < argv.length; i += 1) {
50
55
  const arg = argv[i];
51
56
  if (arg === '--steer') out.steer = argv[++i] || '';
52
57
  else if (arg === '--chat') out.chatId = Number(argv[++i] || 0);
53
58
  else if (arg === '--thread') out.threadId = Number(argv[++i] || 0);
59
+ else if (arg === '--thought') out.thought = true;
54
60
  else rest.push(arg);
55
61
  }
56
62
  out.text = rest.join(' ').trim();
@@ -61,8 +67,8 @@ async function main() {
61
67
  const args = parse(process.argv.slice(2));
62
68
  if (!args.text) usage();
63
69
 
64
- let chatId = args.chatId;
65
- let threadId = args.threadId;
70
+ let chatId = args.chatId || Number(process.env.DSH_TG_CHAT || 0);
71
+ let threadId = args.threadId || Number(process.env.DSH_TG_THREAD || 0);
66
72
  let jobPath = '';
67
73
  if (args.steer) {
68
74
  const found = jobOf(args.steer);
@@ -76,6 +82,19 @@ async function main() {
76
82
  }
77
83
  if (!chatId) usage();
78
84
 
85
+ if (args.thought) {
86
+ try {
87
+ mkdirSync(THOUGHTS, { recursive: true });
88
+ const file = join(THOUGHTS, `${chatId}_${threadId || 0}.jsonl`);
89
+ appendFileSync(file, `${JSON.stringify({ at: Date.now(), text: args.text })}\n`, 'utf8');
90
+ console.log(file);
91
+ } catch (error) {
92
+ console.error(String(error?.message ?? error));
93
+ process.exit(6);
94
+ }
95
+ return;
96
+ }
97
+
79
98
  const token = findToken();
80
99
  if (!token) {
81
100
  console.error('bot token not found (DSH_TELEGRAM_BOT_TOKEN or ~/.dsh/.credentials.yaml)');