dsh-tg-bridge 1.3.2 → 1.3.3
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/LICENSE +1 -1
- package/README.md +120 -122
- package/bot-menu.mjs +3 -3
- package/bridge.mjs +2 -2
- package/cli.mjs +1 -1
- package/config.example.json +1 -1
- package/control.mjs +2 -2
- package/package.json +1 -1
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,179 +1,179 @@
|
|
|
1
1
|
# dsh-tg-bridge
|
|
2
2
|
|
|
3
|
-
Telegram
|
|
4
|
-
|
|
3
|
+
A Telegram bridge for [DeepSeek Harness](https://deepseek.com): drive the agent from a
|
|
4
|
+
bot with text, voice and photos, without opening the desktop app.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
Each forum topic is a separate project with its own session and its own working
|
|
7
|
+
directory. Replies arrive with progress, a "Stop" button and copyable code
|
|
8
|
+
blocks.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Features
|
|
11
11
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
- **Fail-fast
|
|
19
|
-
- **`dsh-tg check
|
|
20
|
-
-
|
|
21
|
-
-
|
|
12
|
+
- **Text**: a regular task goes to the agent, the answer comes back in the same topic.
|
|
13
|
+
- **Voice**: local faster-whisper, the transcript is shown before the answer.
|
|
14
|
+
- **Photos**: sent to a vision-capable model; text and UI are recognized.
|
|
15
|
+
- **Project per topic**: `/cd my-app`, and all turns run from that directory.
|
|
16
|
+
- **Live progress**: "⏳ Working..." with steps, a timer and the cache hit rate at the end.
|
|
17
|
+
- **No lost messages**: a journal with acknowledgement and replay.
|
|
18
|
+
- **Fail-fast**: when the quota runs out it switches to a free model right away.
|
|
19
|
+
- **`dsh-tg check`**: diagnostics in one command, no changes to the system.
|
|
20
|
+
- **Languages**: menus and service messages in `en`, `ru`, `zh`; `/lang` switches the topic language, a new user gets theirs from the Telegram `language_code` (anything outside ru/zh → `en`).
|
|
21
|
+
- **"Stop"/"Retry" buttons, tool confirmations**: right in the message, no commands to type.
|
|
22
22
|
|
|
23
|
-
##
|
|
23
|
+
## Requirements
|
|
24
24
|
|
|
25
25
|
- Node.js ≥ 20
|
|
26
|
-
-
|
|
27
|
-
-
|
|
26
|
+
- DeepSeek Harness installed (`~/.dsh/profiles/headless`)
|
|
27
|
+
- A Telegram bot token from [@BotFather](https://t.me/BotFather)
|
|
28
28
|
|
|
29
|
-
##
|
|
29
|
+
## Languages
|
|
30
30
|
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
- Menus (`/` in chat): three sets, `ru` by default, plus `en` and `zh`.
|
|
32
|
+
- Topic language: `/lang en|ru|zh`; stored in `state.json` and survives a restart.
|
|
33
|
+
- A new user gets the language from the Telegram `language_code`; anything that is not `ru`/`zh` → `en`.
|
|
34
|
+
- Translated so far: menus, the start message, turn status, errors, `/cd`, `/new`, `/stop`, `/lang`,
|
|
35
|
+
the reply to an unknown command. Long reports (`/status`, `/sessions`, `/models` and similar)
|
|
36
|
+
are still in Russian, that is the next pass.
|
|
37
37
|
|
|
38
|
-
##
|
|
38
|
+
## Installation
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
One command: your bot token and your numeric id. Nothing to edit by hand.
|
|
41
41
|
|
|
42
42
|
**Windows (PowerShell)**
|
|
43
43
|
|
|
44
44
|
```powershell
|
|
45
|
-
npx dsh-tg-bridge@latest init --token 123456789:AA... --user
|
|
45
|
+
npx dsh-tg-bridge@latest init --token 123456789:AA... --user 123456789
|
|
46
46
|
npx dsh-tg-bridge@latest setup
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
**macOS / Linux**
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
|
-
DSH_TG_TOKEN=123456789:AA... DSH_TG_USER=
|
|
52
|
+
DSH_TG_TOKEN=123456789:AA... DSH_TG_USER=123456789 \
|
|
53
53
|
bash -c "$(curl -fsSL https://raw.githubusercontent.com/aleksvega/dsh-tg-bridge/main/install.sh)"
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
**Windows
|
|
56
|
+
**Windows, one line** (same thing, installs and starts):
|
|
57
57
|
|
|
58
58
|
```powershell
|
|
59
|
-
$env:DSH_TG_TOKEN='123456789:AA...'; $env:DSH_TG_USER='
|
|
59
|
+
$env:DSH_TG_TOKEN='123456789:AA...'; $env:DSH_TG_USER='123456789'
|
|
60
60
|
irm https://raw.githubusercontent.com/aleksvega/dsh-tg-bridge/main/install.ps1 | iex
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
What `init` does:
|
|
64
64
|
|
|
65
|
-
|
|
|
65
|
+
| What | Where |
|
|
66
66
|
|---|---|
|
|
67
|
-
|
|
|
68
|
-
|
|
|
69
|
-
|
|
|
67
|
+
| bot token | `~/.dsh/.credentials.yaml` → `refs.DSH_TELEGRAM_BOT_TOKEN`; other keys are left alone |
|
|
68
|
+
| your id and working directory | `~/.dsh/tg-bridge/config.json`; an existing file is not overwritten |
|
|
69
|
+
| `headless` profile | checked, with a hint on how to create it if missing |
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
`init` flags: `--cwd <path>` (default `~/Projects`), `--voice` (enable
|
|
72
|
+
voice transcription), `--dry-run` (show what would be written, change nothing).
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
Get your id from [@userinfobot](https://t.me/userinfobot), the token from
|
|
75
75
|
[@BotFather](https://t.me/BotFather).
|
|
76
76
|
|
|
77
|
-
###
|
|
77
|
+
### Bot settings in Telegram
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
Three things without which the bot will stay silent in a group or work only halfway:
|
|
80
80
|
|
|
81
|
-
1. **Privacy mode → Disable** (`/setprivacy`
|
|
82
|
-
|
|
83
|
-
2.
|
|
84
|
-
|
|
85
|
-
3.
|
|
86
|
-
|
|
81
|
+
1. **Privacy mode → Disable** (`/setprivacy` in BotFather). Otherwise the bot sees
|
|
82
|
+
only commands, not regular text in a topic.
|
|
83
|
+
2. **Topics enabled** in the group settings (Edit → Topics). Without them all work
|
|
84
|
+
goes to a single chat and per-topic sessions are not created.
|
|
85
|
+
3. **Pin Messages** permission for the bot. Only needed to pin the first
|
|
86
|
+
message of a topic; without it the bridge keeps working but will not pin.
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
You do not need to set up the command menu by hand: the bridge installs it on start,
|
|
89
|
+
in Russian, English and Chinese at once. The full settings table, permissions, checks and
|
|
90
|
+
common errors are in [docs/TELEGRAM-SETUP.md](docs/TELEGRAM-SETUP.md).
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
Linux.
|
|
94
|
-
`DSH-Bridge-Kick
|
|
92
|
+
**Autostart** is installed by the setup: `launchd` on macOS, `systemd --user` on
|
|
93
|
+
Linux. On Windows `setup` registers the `DSH-TG-Bridge` task and a one-minute watchdog
|
|
94
|
+
`DSH-Bridge-Kick` that brings the bridge back after a crash.
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
**Manual install** (if you want full control):
|
|
97
97
|
|
|
98
98
|
```bash
|
|
99
99
|
git clone https://github.com/aleksvega/dsh-tg-bridge.git ~/.dsh/tg-bridge
|
|
100
100
|
cd ~/.dsh/tg-bridge
|
|
101
|
-
node cli.mjs init --token 123456789:AA... --user
|
|
102
|
-
node cli.mjs check #
|
|
103
|
-
node cli.mjs setup #
|
|
101
|
+
node cli.mjs init --token 123456789:AA... --user 123456789
|
|
102
|
+
node cli.mjs check # report only, changes nothing
|
|
103
|
+
node cli.mjs setup # stop the old bridge and start the new one
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
106
|
+
If the bridge is already installed, `setup` restarts it on the new code and reinstalls
|
|
107
|
+
the command menu.
|
|
108
108
|
|
|
109
|
-
##
|
|
109
|
+
## Troubleshooting
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
Error replies end with a code, which tells you what to fix:
|
|
112
112
|
|
|
113
|
-
|
|
|
113
|
+
| Code | Meaning |
|
|
114
114
|
|---|---|
|
|
115
|
-
| `INSUFFICIENT_QUOTA` |
|
|
116
|
-
| `TG_CONFLICT` |
|
|
117
|
-
| `MISSING_CREDENTIAL` |
|
|
118
|
-
| `NO_DSH_CLI` |
|
|
119
|
-
| `NETWORK` |
|
|
120
|
-
| `SANDBOX_DENY` | DSH
|
|
121
|
-
| `UNKNOWN_MODEL` |
|
|
122
|
-
| `EMPTY_RESPONSE` |
|
|
123
|
-
| `TURN_FAILED` |
|
|
115
|
+
| `INSUFFICIENT_QUOTA` | provider balance is empty: top up or stay on free models (`/prices`, `/models`) |
|
|
116
|
+
| `TG_CONFLICT` | a second process is polling the same bot: `/doctor`, then `/restart` |
|
|
117
|
+
| `MISSING_CREDENTIAL` | no provider key in the profile: add it in the DSH app |
|
|
118
|
+
| `NO_DSH_CLI` | DeepSeek Harness CLI not found: check the app installation |
|
|
119
|
+
| `NETWORK` | no network to the provider: retry, check VPN/DNS |
|
|
120
|
+
| `SANDBOX_DENY` | DSH denied access to a file or directory: check the project path and permissions |
|
|
121
|
+
| `UNKNOWN_MODEL` | the model from the config is unavailable: `/models` lists the working ones |
|
|
122
|
+
| `EMPTY_RESPONSE` | the model returned nothing: retry or switch model (`/model`) |
|
|
123
|
+
| `TURN_FAILED` | cause not recognized: `/doctor` and `/logs errors` |
|
|
124
124
|
|
|
125
|
-
##
|
|
125
|
+
## Checks
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
Send the bot:
|
|
128
128
|
|
|
129
|
-
|
|
|
129
|
+
| Command | Expected |
|
|
130
130
|
|---|---|
|
|
131
|
-
| `/doctor` |
|
|
132
|
-
| `/help` |
|
|
133
|
-
| `/cd
|
|
134
|
-
| `/prices` |
|
|
131
|
+
| `/doctor` | report: pid, Telegram, disk, directory, session |
|
|
132
|
+
| `/help` | commands grouped by category |
|
|
133
|
+
| `/cd my-app` | topic bound to the project |
|
|
134
|
+
| `/prices` | model prices and the cost of the current one |
|
|
135
135
|
|
|
136
|
-
##
|
|
136
|
+
## Commands
|
|
137
137
|
|
|
138
138
|
```
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
139
|
+
Work: /cd · /new · /stop · /send · /seed
|
|
140
|
+
Sessions: /status · /sessions · /resume
|
|
141
|
+
Models: /models · /model · /prices
|
|
142
|
+
Extensions: /skills · /plugins · /plugin · /soul
|
|
143
|
+
Diagnostics: /doctor · /logs [errors|N|text]
|
|
144
|
+
Service: /app · /restart · /help
|
|
145
145
|
```
|
|
146
146
|
|
|
147
|
-
##
|
|
147
|
+
## How it works
|
|
148
148
|
|
|
149
149
|
```
|
|
150
|
-
Telegram → bridge.mjs → dsh --profile headless --json →
|
|
150
|
+
Telegram → bridge.mjs → dsh --profile headless --json → reply
|
|
151
151
|
│
|
|
152
|
-
├─
|
|
153
|
-
├─
|
|
154
|
-
├─
|
|
155
|
-
└─ fail-fast
|
|
152
|
+
├─ pending/ journal + ack (messages are not lost)
|
|
153
|
+
├─ per-topic queue (turns in topics run in parallel)
|
|
154
|
+
├─ progress + buttons (you can see the work is going)
|
|
155
|
+
└─ fail-fast on quota (free models as a fallback)
|
|
156
156
|
```
|
|
157
157
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
158
|
+
The bridge is a single process with long polling. Each turn spawns a separate
|
|
159
|
+
CLI process that exits after the answer, so "no DSH process" between
|
|
160
|
+
turns is normal, not a failure.
|
|
161
161
|
|
|
162
|
-
###
|
|
162
|
+
### Steering during a turn
|
|
163
163
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
90
|
|
164
|
+
If you write in a topic while a turn is still running, the message does not queue
|
|
165
|
+
behind it: the bridge drops the task into `~/.dsh/tg-bridge/steer/`, the `dsh-steer-inbox`
|
|
166
|
+
plugin hands it to the live agent on its next step, and the agent immediately replies with a short
|
|
167
|
+
line in the chat: what it is doing now and where it put the addition. If the agent stays silent
|
|
168
|
+
for 90 seconds, the bridge writes one neutral line itself so there is no silence.
|
|
169
169
|
|
|
170
|
-
|
|
170
|
+
This needs the plugin from this repository:
|
|
171
171
|
|
|
172
172
|
```bash
|
|
173
|
-
dsh plugin --profile headless add link
|
|
173
|
+
dsh plugin --profile headless add link:<path-to-repository>/plugin/steer-inbox
|
|
174
174
|
```
|
|
175
175
|
|
|
176
|
-
|
|
176
|
+
and a line in `~/.dsh/profiles/headless/cordis.patch.yml`:
|
|
177
177
|
|
|
178
178
|
```yaml
|
|
179
179
|
- insert:
|
|
@@ -181,27 +181,25 @@ dsh plugin --profile headless add link:<путь-к-репозиторию>/plug
|
|
|
181
181
|
name: 'dsh-steer-inbox'
|
|
182
182
|
```
|
|
183
183
|
|
|
184
|
-
|
|
185
|
-
|
|
184
|
+
The bridge checks the profile itself: if the line is missing, it does not write the task
|
|
185
|
+
(it would just expire) and behaves as before, queueing the message.
|
|
186
186
|
|
|
187
|
-
##
|
|
187
|
+
## Security
|
|
188
188
|
|
|
189
|
-
- `config.json`, `state.json`, `pending/`,
|
|
190
|
-
|
|
191
|
-
-
|
|
192
|
-
-
|
|
189
|
+
- `config.json`, `state.json`, `pending/`, logs are in `.gitignore` and never
|
|
190
|
+
end up in the repository.
|
|
191
|
+
- The token is stored in the DSH credentials store, not in code or config.
|
|
192
|
+
- The bridge only replies to ids in `allowedUserIds`.
|
|
193
193
|
|
|
194
|
-
##
|
|
194
|
+
## Documentation
|
|
195
195
|
|
|
196
|
-
- [docs/TELEGRAM-SETUP.md](docs/TELEGRAM-SETUP.md)
|
|
197
|
-
- [docs/
|
|
198
|
-
- [docs/
|
|
199
|
-
- [docs/
|
|
200
|
-
- [docs/
|
|
201
|
-
- [docs/
|
|
202
|
-
- [docs/CHANGELOG.md](docs/CHANGELOG.md) — история изменений
|
|
203
|
-
- [docs/RELEASE-CRITERIA.md](docs/RELEASE-CRITERIA.md) — критерии готовности релиза
|
|
196
|
+
- [docs/TELEGRAM-SETUP.md](docs/TELEGRAM-SETUP.md): BotFather settings, group and bot permissions
|
|
197
|
+
- [docs/ROADMAP.md](docs/ROADMAP.md): what comes next
|
|
198
|
+
- [docs/UX-ANALYSIS.md](docs/UX-ANALYSIS.md): interactive UX review
|
|
199
|
+
- [docs/COST.md](docs/COST.md): what one turn costs and how to reduce it
|
|
200
|
+
- [docs/CHANGELOG.md](docs/CHANGELOG.md): change history
|
|
201
|
+
- [docs/RELEASE-CRITERIA.md](docs/RELEASE-CRITERIA.md): release readiness criteria
|
|
204
202
|
|
|
205
|
-
##
|
|
203
|
+
## License
|
|
206
204
|
|
|
207
|
-
MIT
|
|
205
|
+
MIT, see [LICENSE](LICENSE).
|
package/bot-menu.mjs
CHANGED
|
@@ -30,7 +30,7 @@ const token = (() => {
|
|
|
30
30
|
/** The real, implemented surface of dsh-tg-bridge — nothing aspirational. */
|
|
31
31
|
const COMMANDS = [
|
|
32
32
|
{ command: 'new', description: 'Новая сессия в этой теме' },
|
|
33
|
-
{ command: 'cd', description: 'Привязать тему к проекту: /cd
|
|
33
|
+
{ command: 'cd', description: 'Привязать тему к проекту: /cd my-app' },
|
|
34
34
|
{ command: 'status', description: 'Сессии, модель, активные ходы' },
|
|
35
35
|
{ command: 'stop', description: 'Остановить текущий ход' },
|
|
36
36
|
{ command: 'prices', description: 'Цены моделей и что стоит текущая' },
|
|
@@ -54,7 +54,7 @@ const COMMANDS = [
|
|
|
54
54
|
/** English menu (language_code: en). Same commands, translated descriptions. */
|
|
55
55
|
const COMMANDS_EN = [
|
|
56
56
|
{ command: 'new', description: 'New session in this topic' },
|
|
57
|
-
{ command: 'cd', description: 'Bind this topic to a project: /cd
|
|
57
|
+
{ command: 'cd', description: 'Bind this topic to a project: /cd my-app' },
|
|
58
58
|
{ command: 'status', description: 'Sessions, model, active turns' },
|
|
59
59
|
{ command: 'stop', description: 'Stop the current turn' },
|
|
60
60
|
{ command: 'prices', description: 'Model prices and the cost of the current one' },
|
|
@@ -78,7 +78,7 @@ const COMMANDS_EN = [
|
|
|
78
78
|
/** Chinese menu (language_code: zh-hans). */
|
|
79
79
|
const COMMANDS_ZH = [
|
|
80
80
|
{ command: 'new', description: '在本话题开始新会话' },
|
|
81
|
-
{ command: 'cd', description: '将本话题绑定到项目:/cd
|
|
81
|
+
{ command: 'cd', description: '将本话题绑定到项目:/cd my-app' },
|
|
82
82
|
{ command: 'status', description: '会话、模型、进行中的任务' },
|
|
83
83
|
{ command: 'stop', description: '停止当前任务' },
|
|
84
84
|
{ command: 'prices', description: '模型价格与当前模型花费' },
|
package/bridge.mjs
CHANGED
|
@@ -95,7 +95,7 @@ process.on('exit', (code) => {
|
|
|
95
95
|
});
|
|
96
96
|
|
|
97
97
|
const config = {
|
|
98
|
-
allowedUserIds: [
|
|
98
|
+
allowedUserIds: [],
|
|
99
99
|
profile: 'headless',
|
|
100
100
|
cwd: join(HOME, 'Documents', 'deepseek-harness', 'default-workspace'),
|
|
101
101
|
turnTimeoutMs: 20 * 60 * 1000,
|
|
@@ -709,7 +709,7 @@ function runTurn(task, sessionId, key = '', onEvent = null, extraArgs = [], cwd
|
|
|
709
709
|
args.push(task);
|
|
710
710
|
const child = spawn(EXE, args, {
|
|
711
711
|
// Per-topic project directory: a topic can be bound to one repository so
|
|
712
|
-
// "
|
|
712
|
+
// "work in my-app" stops meaning "cd in every command".
|
|
713
713
|
cwd: cwd && existsSync(cwd) ? cwd : config.cwd,
|
|
714
714
|
env: {
|
|
715
715
|
...process.env,
|
package/cli.mjs
CHANGED
|
@@ -87,7 +87,7 @@ init:
|
|
|
87
87
|
--voice включить голос (нужен python + faster-whisper)
|
|
88
88
|
--dry-run показать, что будет записано, ничего не менять
|
|
89
89
|
|
|
90
|
-
Docs:
|
|
90
|
+
Docs: README.md, docs/TELEGRAM-SETUP.md, docs/ROADMAP.md.
|
|
91
91
|
`;
|
|
92
92
|
|
|
93
93
|
/**
|
package/config.example.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_comment": "
|
|
2
|
+
"_comment": "Copy to config.json and fill in. Get your chat id from @userinfobot; without it the bridge answers nobody.",
|
|
3
3
|
"allowedUserIds": [],
|
|
4
4
|
"profile": "headless",
|
|
5
5
|
"cwd": "",
|
package/control.mjs
CHANGED
|
@@ -622,7 +622,7 @@ export async function handleControl(ctx) {
|
|
|
622
622
|
|
|
623
623
|
case '/cd': {
|
|
624
624
|
// Bind THIS topic to a project directory. Every later turn in the topic
|
|
625
|
-
// spawns the CLI with that cwd, so "
|
|
625
|
+
// spawns the CLI with that cwd, so "work in my-app" stops
|
|
626
626
|
// meaning "cd ... in every single command".
|
|
627
627
|
const wanted = (arg || '').trim().replace(/^"|"$/g, '');
|
|
628
628
|
state.cwd = state.cwd || {};
|
|
@@ -630,7 +630,7 @@ export async function handleControl(ctx) {
|
|
|
630
630
|
const current = state.cwd[key] || config.cwd;
|
|
631
631
|
const lines = ['Текущий каталог темы:', current, '', 'Привязать: /cd <путь>', 'Сбросить: /cd reset'];
|
|
632
632
|
try {
|
|
633
|
-
const dirs = readdirSync('
|
|
633
|
+
const dirs = readdirSync(config.projectsDir || join(HOME, 'Projects'), { withFileTypes: true })
|
|
634
634
|
.filter((entry) => entry.isDirectory())
|
|
635
635
|
.map((entry) => entry.name);
|
|
636
636
|
if (dirs.length > 0) lines.push('', 'Проекты:', dirs.slice(0, 20).join(' · '));
|
package/package.json
CHANGED