@grinev/opencode-telegram-bot 0.21.1 → 0.22.0
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/.env.example +10 -15
- package/README.md +33 -13
- package/dist/app/{start-bot-app.js → bootstrap/start-bot-app.js} +16 -15
- package/dist/{bot/utils/assistant-run-footer.js → app/formatters/assistant-run-footer-formatter.js} +1 -1
- package/dist/{summary → app/formatters}/subagent-formatter.js +8 -3
- package/dist/{summary/formatter.js → app/formatters/summary-formatter.js} +20 -132
- package/dist/{summary → app/formatters}/tool-message-batcher.js +1 -1
- package/dist/{bot/assistant-run-state.js → app/managers/assistant-run-state-manager.js} +1 -1
- package/dist/{attach/manager.js → app/managers/attach-manager.js} +1 -1
- package/dist/{background-session/tracker.js → app/managers/background-session-manager.js} +2 -2
- package/dist/{scheduled-task/foreground-state.js → app/managers/foreground-session-state-manager.js} +1 -1
- package/dist/{interaction/manager.js → app/managers/interaction-manager.js} +31 -1
- package/dist/{permission/manager.js → app/managers/permission-manager.js} +1 -1
- package/dist/{question/manager.js → app/managers/question-manager.js} +1 -1
- package/dist/{rename/manager.js → app/managers/rename-manager.js} +1 -1
- package/dist/{scheduled-task/creation-manager.js → app/managers/scheduled-task-creation-manager.js} +2 -2
- package/dist/{summary/aggregator.js → app/managers/summary-aggregation-manager.js} +177 -13
- package/dist/{agent/manager.js → app/services/agent-selection-service.js} +4 -5
- package/dist/{attach/service.js → app/services/attach-service.js} +23 -44
- package/dist/{bot/utils/busy-reconciliation.js → app/services/busy-reconciliation-service.js} +12 -7
- package/dist/app/services/command-catalog-service.js +21 -0
- package/dist/{bot/utils/external-user-input.js → app/services/external-user-input-service.js} +4 -19
- package/dist/app/services/file-browser-service.js +265 -0
- package/dist/{bot/utils/file-download.js → app/services/file-download-service.js} +14 -28
- package/dist/app/services/mcp-catalog-service.js +79 -0
- package/dist/app/services/message-history-service.js +81 -0
- package/dist/{model/capabilities.js → app/services/model-capabilities-service.js} +2 -2
- package/dist/{model/context-limit.js → app/services/model-context-limit-service.js} +4 -4
- package/dist/{model/manager.js → app/services/model-selection-service.js} +4 -4
- package/dist/{project/manager.js → app/services/project-service.js} +3 -3
- package/dist/app/services/project-switch-service.js +39 -0
- package/dist/{bot/utils/busy-guard.js → app/services/run-control-service.js} +3 -14
- package/dist/{scheduled-task/executor.js → app/services/scheduled-task-executor-service.js} +27 -12
- package/dist/{scheduled-task/runtime.js → app/services/scheduled-task-runtime-service.js} +17 -56
- package/dist/{scheduled-task/schedule-parser.js → app/services/scheduled-task-schedule-parser-service.js} +3 -3
- package/dist/{scheduled-task/session-ignore.js → app/services/scheduled-task-session-ignore-service.js} +1 -1
- package/dist/{session/cache-manager.js → app/services/session-cache-service.js} +3 -3
- package/dist/{session/manager.js → app/services/session-service.js} +1 -1
- package/dist/app/services/skills-catalog-service.js +21 -0
- package/dist/{stt/client.js → app/services/stt-service.js} +2 -2
- package/dist/{tts/client.js → app/services/tts-service.js} +68 -5
- package/dist/{variant/manager.js → app/services/variant-selection-service.js} +3 -3
- package/dist/{scheduled-task/store.js → app/stores/scheduled-task-store.js} +3 -3
- package/dist/{settings/manager.js → app/stores/settings-store.js} +49 -7
- package/dist/app/types/worktree.js +1 -0
- package/dist/bot/{handlers/agent.js → callbacks/agent-selection-callback-handler.js} +8 -57
- package/dist/bot/callbacks/callback-router.js +113 -0
- package/dist/bot/{commands/commands.js → callbacks/command-catalog-callback-handler.js} +17 -197
- package/dist/bot/{handlers/context.js → callbacks/context-control-callback-handler.js} +3 -32
- package/dist/bot/callbacks/file-browser-callback-handler.js +219 -0
- package/dist/bot/callbacks/inline-menu-cancel-callback-handler.js +32 -0
- package/dist/bot/{commands/mcps.js → callbacks/mcp-catalog-callback-handler.js} +7 -172
- package/dist/bot/{commands/messages.js → callbacks/message-history-callback-handler.js} +17 -248
- package/dist/bot/{handlers/model.js → callbacks/model-selection-callback-handler.js} +10 -71
- package/dist/bot/callbacks/permission-callback-handler.js +114 -0
- package/dist/bot/callbacks/project-callback-handler.js +79 -0
- package/dist/bot/callbacks/question-callback-handler.js +159 -0
- package/dist/bot/{commands/rename.js → callbacks/rename-callback-handler.js} +6 -34
- package/dist/bot/{commands/tasklist.js → callbacks/scheduled-task-callback-handler.js} +83 -68
- package/dist/bot/{commands/sessions.js → callbacks/session-callback-handler.js} +19 -158
- package/dist/bot/callbacks/settings-callback-handler.js +105 -0
- package/dist/bot/{commands/skills.js → callbacks/skills-catalog-callback-handler.js} +8 -188
- package/dist/bot/{handlers/variant.js → callbacks/variant-selection-callback-handler.js} +7 -69
- package/dist/bot/callbacks/worktree-callback-handler.js +97 -0
- package/dist/bot/commands/{abort.js → abort-command.js} +6 -6
- package/dist/bot/commands/command-catalog-command.js +42 -0
- package/dist/bot/commands/definitions.js +1 -1
- package/dist/bot/commands/{detach.js → detach-command.js} +8 -8
- package/dist/bot/commands/ls-command.js +48 -0
- package/dist/bot/commands/mcp-catalog-command.js +39 -0
- package/dist/bot/commands/messages-command.js +59 -0
- package/dist/bot/commands/{new.js → new-command.js} +12 -11
- package/dist/bot/commands/open-command.js +45 -0
- package/dist/bot/commands/{opencode-start.js → opencode-start-command.js} +1 -1
- package/dist/bot/commands/{opencode-stop.js → opencode-stop-command.js} +1 -1
- package/dist/bot/commands/projects-command.js +32 -0
- package/dist/bot/commands/rename-command.js +33 -0
- package/dist/bot/commands/sessions-command.js +42 -0
- package/dist/bot/commands/settings-command.js +10 -0
- package/dist/bot/commands/skills-catalog-command.js +42 -0
- package/dist/bot/commands/{start.js → start-command.js} +12 -12
- package/dist/bot/commands/{status.js → status-command.js} +15 -10
- package/dist/bot/commands/{task.js → task-command.js} +9 -81
- package/dist/bot/commands/tasklist-command.js +40 -0
- package/dist/bot/commands/worktree-command.js +47 -0
- package/dist/bot/handlers/{document.js → document-handler.js} +3 -3
- package/dist/bot/handlers/{media-group.js → media-group-handler.js} +3 -3
- package/dist/bot/handlers/photo-handler.js +44 -0
- package/dist/bot/handlers/prompt.js +17 -17
- package/dist/bot/handlers/text-message-handler.js +61 -0
- package/dist/bot/handlers/tts-response-handler.js +29 -0
- package/dist/bot/handlers/{voice.js → voice-handler.js} +6 -3
- package/dist/bot/index.js +27 -1179
- package/dist/{keyboard/manager.js → bot/keyboards/keyboard-manager.js} +6 -6
- package/dist/bot/keyboards/keyboard-types.js +1 -0
- package/dist/bot/{utils/keyboard.js → keyboards/main-reply-keyboard.js} +2 -2
- package/dist/bot/menus/agent-selection-menu.js +54 -0
- package/dist/bot/menus/command-catalog-menu.js +101 -0
- package/dist/bot/menus/context-control-menu.js +33 -0
- package/dist/bot/menus/file-browser-menu.js +265 -0
- package/dist/bot/{handlers → menus}/inline-menu.js +7 -34
- package/dist/bot/menus/mcp-catalog-menu.js +97 -0
- package/dist/bot/menus/message-history-menu.js +107 -0
- package/dist/bot/menus/model-selection-menu.js +66 -0
- package/dist/bot/menus/permission-menu.js +116 -0
- package/dist/bot/{commands/projects.js → menus/project-selection-menu.js} +5 -105
- package/dist/bot/{handlers/question.js → menus/question-menu.js} +10 -166
- package/dist/bot/menus/rename-menu.js +6 -0
- package/dist/bot/menus/scheduled-task-menu.js +42 -0
- package/dist/bot/menus/session-selection-menu.js +115 -0
- package/dist/bot/menus/settings-menu.js +52 -0
- package/dist/bot/menus/skills-catalog-menu.js +99 -0
- package/dist/bot/menus/variant-selection-menu.js +67 -0
- package/dist/bot/menus/worktree-selection-menu.js +76 -0
- package/dist/bot/{utils → messages}/assistant-rendering.js +2 -2
- package/dist/bot/messages/busy-blocked-renderer.js +11 -0
- package/dist/bot/messages/external-user-input-notification.js +22 -0
- package/dist/bot/messages/scheduled-task-delivery.js +63 -0
- package/dist/bot/{utils → messages}/send-downloaded-file.js +1 -1
- package/dist/bot/messages/summary-message-formatter.js +130 -0
- package/dist/bot/{utils → messages}/thinking-message.js +1 -4
- package/dist/bot/messages/thinking-rendering.js +66 -0
- package/dist/{interaction/guard.js → bot/middleware/interaction-guard-decision.js} +11 -4
- package/dist/bot/middleware/interaction-guard.js +2 -2
- package/dist/bot/middleware/unknown-command.js +1 -1
- package/dist/{pinned/format.js → bot/pinned/pinned-message-format.js} +1 -2
- package/dist/{pinned/manager.js → bot/pinned/pinned-message-manager.js} +10 -10
- package/dist/bot/pinned/pinned-message-types.js +1 -0
- package/dist/{summary → bot/render}/markdown-to-telegram-v2.js +2 -2
- package/dist/bot/render/types.js +1 -0
- package/dist/bot/routers/command-router.js +73 -0
- package/dist/bot/routers/message-router.js +150 -0
- package/dist/bot/services/attach-presentation.js +40 -0
- package/dist/bot/services/event-subscription-service.js +1116 -0
- package/dist/bot/services/project-switch-presentation.js +26 -0
- package/dist/bot/streaming/compact-progress-streamer.js +162 -0
- package/dist/bot/{utils → streaming}/finalize-assistant-response.js +9 -3
- package/dist/bot/streaming/response-streamer.js +16 -1
- package/dist/cli.js +4 -4
- package/dist/config.js +9 -18
- package/dist/i18n/ar.js +31 -6
- package/dist/i18n/de.js +32 -7
- package/dist/i18n/en.js +32 -7
- package/dist/i18n/es.js +32 -7
- package/dist/i18n/fr.js +32 -7
- package/dist/i18n/ru.js +32 -7
- package/dist/i18n/zh.js +32 -7
- package/dist/index.js +1 -1
- package/dist/opencode/ready-refresh.js +2 -2
- package/dist/{service → runtime/service}/manager.js +2 -2
- package/dist/runtime/service/types.js +1 -0
- package/package.json +1 -1
- package/dist/bot/commands/ls.js +0 -445
- package/dist/bot/commands/open.js +0 -319
- package/dist/bot/commands/tts.js +0 -13
- package/dist/bot/commands/worktree.js +0 -201
- package/dist/bot/handlers/permission.js +0 -235
- package/dist/bot/utils/browser-roots.js +0 -140
- package/dist/bot/utils/file-tree.js +0 -92
- package/dist/bot/utils/send-tts-response.js +0 -37
- package/dist/bot/utils/switch-project.js +0 -45
- package/dist/bot/utils/telegram-file-url.js +0 -11
- package/dist/interaction/busy.js +0 -8
- package/dist/interaction/cleanup.js +0 -32
- /package/dist/{scheduled-task/display.js → app/formatters/scheduled-task-display-formatter.js} +0 -0
- /package/dist/{bot/utils/abort-error-suppression.js → app/managers/abort-suppression-manager.js} +0 -0
- /package/dist/{external-input/suppression.js → app/managers/external-input-suppression-manager.js} +0 -0
- /package/dist/{scheduled-task/next-run.js → app/services/scheduled-task-next-run-service.js} +0 -0
- /package/dist/{git/worktree.js → app/services/worktree-service.js} +0 -0
- /package/dist/{agent/types.js → app/types/agent.js} +0 -0
- /package/dist/{interaction/types.js → app/types/interaction.js} +0 -0
- /package/dist/{model/types.js → app/types/model.js} +0 -0
- /package/dist/{keyboard/types.js → app/types/permission.js} +0 -0
- /package/dist/{permission/types.js → app/types/project.js} +0 -0
- /package/dist/{pinned/types.js → app/types/question.js} +0 -0
- /package/dist/{scheduled-task/types.js → app/types/scheduled-task.js} +0 -0
- /package/dist/{question/types.js → app/types/session.js} +0 -0
- /package/dist/{service/types.js → app/types/settings.js} +0 -0
- /package/dist/{telegram/render/types.js → app/types/summary.js} +0 -0
- /package/dist/{variant/types.js → app/types/variant.js} +0 -0
- /package/dist/bot/commands/{help.js → help-command.js} +0 -0
- /package/dist/bot/{utils → messages}/send-with-markdown-fallback.js +0 -0
- /package/dist/bot/{utils → messages}/telegram-text.js +0 -0
- /package/dist/{telegram → bot}/render/block-fallback.js +0 -0
- /package/dist/{telegram → bot}/render/block-parser.js +0 -0
- /package/dist/{telegram → bot}/render/block-renderer.js +0 -0
- /package/dist/{telegram → bot}/render/chunker.js +0 -0
- /package/dist/{telegram → bot}/render/inline-renderer.js +0 -0
- /package/dist/{telegram → bot}/render/markdown-normalizer.js +0 -0
- /package/dist/{telegram → bot}/render/pipeline.js +0 -0
- /package/dist/{telegram → bot}/render/validator.js +0 -0
- /package/dist/bot/{utils/commands.js → routers/command-utils.js} +0 -0
- /package/dist/{service/runtime.js → runtime/service/env.js} +0 -0
package/.env.example
CHANGED
|
@@ -80,11 +80,8 @@ OPENCODE_MODEL_ID=big-pickle
|
|
|
80
80
|
# If exceeded, the bot stops waiting for the result and marks the run as failed.
|
|
81
81
|
# SCHEDULED_TASK_EXECUTION_TIMEOUT_MINUTES=120
|
|
82
82
|
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
# draft = uses sendMessageDraft (Bot API 9.5+) for smooth animated draft previews,
|
|
86
|
-
# then finalizes with sendMessage. Only works in private chats.
|
|
87
|
-
# RESPONSE_STREAMING_MODE=edit
|
|
83
|
+
# Send scheduled task result/error messages without Telegram push notifications (default: false)
|
|
84
|
+
# SCHEDULED_TASK_DISABLE_NOTIFICATION=false
|
|
88
85
|
|
|
89
86
|
# Stream update throttle in milliseconds for assistant/tool message edits (default: 1000)
|
|
90
87
|
# Higher value = fewer Telegram edit requests, lower value = more real-time updates
|
|
@@ -98,15 +95,6 @@ OPENCODE_MODEL_ID=big-pickle
|
|
|
98
95
|
# Supported locales: en, ar, de, es, fr, ru, zh
|
|
99
96
|
# BOT_LOCALE=en
|
|
100
97
|
|
|
101
|
-
# Hide thinking indicator messages (default: false)
|
|
102
|
-
# HIDE_THINKING_MESSAGES=false
|
|
103
|
-
|
|
104
|
-
# Hide tool call service messages (default: false)
|
|
105
|
-
# HIDE_TOOL_CALL_MESSAGES=false
|
|
106
|
-
|
|
107
|
-
# Hide tool file edit documents sent as .txt attachments (default: false)
|
|
108
|
-
# HIDE_TOOL_FILE_MESSAGES=false
|
|
109
|
-
|
|
110
98
|
# Track background sessions in the currently selected project/worktree (default: true)
|
|
111
99
|
# Sends short notifications when detached or non-current sessions in the current directory reply,
|
|
112
100
|
# ask questions, or request permissions. Other projects/worktrees are not tracked.
|
|
@@ -141,7 +129,7 @@ OPENCODE_MODEL_ID=big-pickle
|
|
|
141
129
|
|
|
142
130
|
# Text-to-Speech credentials (optional)
|
|
143
131
|
# TTS reply behavior is controlled globally with /tts and persisted in settings.json.
|
|
144
|
-
# Provider: "openai" (default) or "google".
|
|
132
|
+
# Provider: "openai" (default), "elevenlabs", or "google".
|
|
145
133
|
#
|
|
146
134
|
# --- OpenAI-compatible (default) ---
|
|
147
135
|
# Set TTS_API_URL and TTS_API_KEY to any OpenAI-compatible TTS endpoint.
|
|
@@ -150,6 +138,13 @@ OPENCODE_MODEL_ID=big-pickle
|
|
|
150
138
|
# TTS_MODEL=gpt-4o-mini-tts
|
|
151
139
|
# TTS_VOICE=alloy
|
|
152
140
|
#
|
|
141
|
+
# --- ElevenLabs ---
|
|
142
|
+
# TTS_PROVIDER=elevenlabs
|
|
143
|
+
# TTS_API_URL=https://api.elevenlabs.io/v1
|
|
144
|
+
# TTS_API_KEY=
|
|
145
|
+
# TTS_MODEL=eleven_flash_v2_5
|
|
146
|
+
# TTS_VOICE=21m00Tcm4TlvDq8ikWAM
|
|
147
|
+
#
|
|
153
148
|
# --- Google Cloud TTS ---
|
|
154
149
|
# 1. Create a project at https://console.cloud.google.com
|
|
155
150
|
# 2. Enable "Cloud Text-to-Speech API"
|
package/README.md
CHANGED
|
@@ -23,6 +23,8 @@ Languages: English (`en`), العربية (`ar`), Deutsch (`de`), Español (`es`
|
|
|
23
23
|
<img src="assets/screencast.gif" width="45%" alt="OpenCode Telegram Bot screencast" />
|
|
24
24
|
</p>
|
|
25
25
|
|
|
26
|
+
> I use [boardown](https://github.com/grinev/boardown), my open-source Markdown-based task board, to plan and track this project. It stores tasks in plain `.md` files and can be used as a VS Code extension or a desktop app.
|
|
27
|
+
|
|
26
28
|
## Features
|
|
27
29
|
|
|
28
30
|
- **Remote coding** — send prompts to OpenCode from anywhere, receive complete results with code sent as files
|
|
@@ -36,7 +38,8 @@ Languages: English (`en`), العربية (`ar`), Deutsch (`de`), Español (`es`
|
|
|
36
38
|
- **Custom Commands** — run OpenCode custom commands (and built-ins like `init`/`review`) from an inline menu with confirmation
|
|
37
39
|
- **Skills Catalog** — browse OpenCode skills from an inline menu and run them immediately or with arguments in the next message
|
|
38
40
|
- **Interactive Q&A** — answer agent questions and approve permissions via inline buttons
|
|
39
|
-
- **
|
|
41
|
+
- **Runtime settings** — use `/settings` to change runtime preferences; see [Runtime Settings](#runtime-settings)
|
|
42
|
+
- **Voice prompts** — send voice/audio messages, transcribe them via a Whisper-compatible API, and optionally enable spoken replies in `/settings`
|
|
40
43
|
- **File attachments** — send images, PDF documents, and text-based files to OpenCode, including multiple files in one Telegram album
|
|
41
44
|
- **Scheduled tasks** — schedule prompts to run later or on a recurring interval; see [Scheduled Tasks](#scheduled-tasks)
|
|
42
45
|
- **Context control** — compact context when it gets too large, right from the chat
|
|
@@ -139,7 +142,7 @@ opencode-telegram config
|
|
|
139
142
|
| `/worktree` | Switch between existing git worktrees |
|
|
140
143
|
| `/open` | Add a project by browsing directories |
|
|
141
144
|
| `/ls` | List directory contents, then tap to open or download |
|
|
142
|
-
| `/
|
|
145
|
+
| `/settings` | Change bot settings |
|
|
143
146
|
| `/rename` | Rename the current session |
|
|
144
147
|
| `/commands` | Browse and run custom commands |
|
|
145
148
|
| `/skills` | Browse and run OpenCode skills |
|
|
@@ -225,13 +228,10 @@ When installed via npm, the configuration wizard handles the initial setup. The
|
|
|
225
228
|
| `COMMANDS_LIST_LIMIT` | Items per page in `/commands` and `/skills` | No | `10` |
|
|
226
229
|
| `TASK_LIMIT` | Maximum number of scheduled tasks that can exist at once | No | `10` |
|
|
227
230
|
| `SCHEDULED_TASK_EXECUTION_TIMEOUT_MINUTES` | Maximum time the bot waits for one scheduled task run before marking it failed | No | `120` |
|
|
231
|
+
| `SCHEDULED_TASK_DISABLE_NOTIFICATION` | Send scheduled task result/error messages without Telegram push notifications | No | `false` |
|
|
228
232
|
| `BASH_TOOL_DISPLAY_MAX_LENGTH` | Maximum displayed length for `bash` tool commands in Telegram summaries; longer commands are truncated | No | `128` |
|
|
229
|
-
| `SERVICE_MESSAGES_INTERVAL_SEC` | Service messages interval (thinking + tool calls); keep `>=2` to avoid Telegram rate limits, `0` = immediate | No | `5` |
|
|
230
|
-
| `HIDE_THINKING_MESSAGES` | Hide `💭 Thinking...` service messages | No | `false` |
|
|
231
|
-
| `HIDE_TOOL_CALL_MESSAGES` | Hide tool-call service messages (`💻 bash ...`, `📖 read ...`, etc.) | No | `false` |
|
|
232
|
-
| `HIDE_TOOL_FILE_MESSAGES` | Hide file edit documents sent as `.txt` attachments (`edit_*.txt`, `write_*.txt`) | No | `false` |
|
|
233
233
|
| `TRACK_BACKGROUND_SESSIONS` | Track detached/non-current sessions in the current selected project/worktree and send short notifications | No | `true` |
|
|
234
|
-
| `
|
|
234
|
+
| `RESPONSE_STREAM_THROTTLE_MS` | Stream update throttle in milliseconds for assistant, thinking, and tool message edits | No | `1000` |
|
|
235
235
|
| `MESSAGE_FORMAT_MODE` | Assistant reply formatting mode: `markdown` (Telegram MarkdownV2) or `raw` | No | `markdown` |
|
|
236
236
|
| `CODE_FILE_MAX_SIZE_KB` | Max file size (KB) to send as document | No | `100` |
|
|
237
237
|
| `STT_API_URL` | Whisper-compatible API base URL (enables voice/audio transcription) | No | — |
|
|
@@ -239,11 +239,11 @@ When installed via npm, the configuration wizard handles the initial setup. The
|
|
|
239
239
|
| `STT_MODEL` | STT model name passed to `/audio/transcriptions` | No | `whisper-large-v3-turbo` |
|
|
240
240
|
| `STT_LANGUAGE` | Optional language hint (empty = provider auto-detect) | No | — |
|
|
241
241
|
| `STT_NOTE_PROMPT` | Optional note prepended to the LLM prompt as `[Note: ...]` for voice transcriptions; empty / `false` / `0` disable it | No | — |
|
|
242
|
-
| `TTS_PROVIDER` | TTS provider: `openai` for OpenAI-compatible APIs or `google` for Google Cloud TTS
|
|
243
|
-
| `TTS_API_URL` |
|
|
244
|
-
| `TTS_API_KEY` | OpenAI-compatible
|
|
245
|
-
| `TTS_MODEL` |
|
|
246
|
-
| `TTS_VOICE` | TTS voice name. Defaults to `alloy`
|
|
242
|
+
| `TTS_PROVIDER` | TTS provider: `openai` for OpenAI-compatible APIs, `elevenlabs` for ElevenLabs, or `google` for Google Cloud TTS | No | `openai` |
|
|
243
|
+
| `TTS_API_URL` | TTS API base URL for OpenAI-compatible APIs or ElevenLabs | No | — |
|
|
244
|
+
| `TTS_API_KEY` | TTS API key for OpenAI-compatible APIs or ElevenLabs | No | — |
|
|
245
|
+
| `TTS_MODEL` | TTS model name. Passed as `model` for OpenAI-compatible APIs and `model_id` for ElevenLabs | No | `gpt-4o-mini-tts` |
|
|
246
|
+
| `TTS_VOICE` | TTS voice name or ElevenLabs voice ID. Defaults to `alloy`, `21m00Tcm4TlvDq8ikWAM`, or `en-US-Studio-O` by provider | No | provider-specific |
|
|
247
247
|
| `GOOGLE_APPLICATION_CREDENTIALS` | Path to a Google Cloud service account JSON key file for `TTS_PROVIDER=google` | No | — |
|
|
248
248
|
| `LOG_LEVEL` | Log level (`debug`, `info`, `warn`, `error`) | No | `info` |
|
|
249
249
|
| `LOG_RETENTION` | Number of log files to keep: launch files in `sources`, daily files in `installed` | No | `10` |
|
|
@@ -252,6 +252,16 @@ When installed via npm, the configuration wizard handles the initial setup. The
|
|
|
252
252
|
|
|
253
253
|
Logs are written to `./logs` when running from sources and to the runtime config directory `logs/` folder in `installed` mode. Log rotation depends on runtime mode: `sources` creates one file per bot launch, while `installed` appends to one file per day. Old log files are removed according to `LOG_RETENTION`.
|
|
254
254
|
|
|
255
|
+
### Runtime Settings
|
|
256
|
+
|
|
257
|
+
Runtime preferences are changed from `/settings` and stored in `settings.json`:
|
|
258
|
+
|
|
259
|
+
- Compact output mode
|
|
260
|
+
- Thinking content display
|
|
261
|
+
- Diff file attachments
|
|
262
|
+
- Response streaming mode: `edit` or `draft (experimental)`; applies only to final assistant replies, not thinking messages
|
|
263
|
+
- Audio replies: `off`, `all`, or `auto` when TTS is configured
|
|
264
|
+
|
|
255
265
|
### Reverse Proxy (Optional)
|
|
256
266
|
|
|
257
267
|
For environments that block `api.telegram.org` but allow your own HTTPS endpoint (corporate networks, restricted regions), you can route Bot API traffic through a reverse proxy you control. This is an alternative to the SOCKS/HTTP forward proxy configured with `TELEGRAM_PROXY_URL`.
|
|
@@ -311,7 +321,7 @@ If `STT_API_URL` and `STT_API_KEY` are set, the bot will:
|
|
|
311
321
|
|
|
312
322
|
If `STT_NOTE_PROMPT` is set to a non-empty value other than `false` or `0`, the bot prepends `[Note: ...]` to the transcription before sending it to the LLM. The recognized text shown in Telegram stays unchanged.
|
|
313
323
|
|
|
314
|
-
If TTS credentials are configured, you can
|
|
324
|
+
If TTS credentials are configured, you can choose spoken reply behavior in `/settings`: `off` disables audio replies, `all` sends audio for every assistant reply, and `auto` sends audio only after voice/audio prompts. The preference is stored in `settings.json` and persists across restarts.
|
|
315
325
|
|
|
316
326
|
OpenAI-compatible TTS configuration example:
|
|
317
327
|
|
|
@@ -323,6 +333,16 @@ TTS_MODEL=gpt-4o-mini-tts
|
|
|
323
333
|
TTS_VOICE=alloy
|
|
324
334
|
```
|
|
325
335
|
|
|
336
|
+
ElevenLabs TTS configuration example:
|
|
337
|
+
|
|
338
|
+
```env
|
|
339
|
+
TTS_PROVIDER=elevenlabs
|
|
340
|
+
TTS_API_URL=https://api.elevenlabs.io/v1
|
|
341
|
+
TTS_API_KEY=your-elevenlabs-api-key
|
|
342
|
+
TTS_MODEL=eleven_flash_v2_5
|
|
343
|
+
TTS_VOICE=21m00Tcm4TlvDq8ikWAM
|
|
344
|
+
```
|
|
345
|
+
|
|
326
346
|
Google Cloud TTS configuration example:
|
|
327
347
|
|
|
328
348
|
```env
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
|
-
import { cleanupBotRuntime, createBot } from "
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
3
|
+
import { cleanupBotRuntime, createBot } from "../../bot/index.js";
|
|
4
|
+
import { createScheduledTaskDeliverySender } from "../../bot/messages/scheduled-task-delivery.js";
|
|
5
|
+
import { config } from "../../config.js";
|
|
6
|
+
import { opencodeAutoRestartService } from "../../opencode/auto-restart.js";
|
|
7
|
+
import { notifyOpencodeReadyIfHealthy, registerOpenCodeReadyRefreshHandler, } from "../../opencode/ready-refresh.js";
|
|
8
|
+
import { loadSettings } from "../stores/settings-store.js";
|
|
9
|
+
import { scheduledTaskRuntime } from "../services/scheduled-task-runtime-service.js";
|
|
10
|
+
import { reconcileStoredModelSelection } from "../services/model-selection-service.js";
|
|
11
|
+
import { getRuntimeMode } from "../../runtime/mode.js";
|
|
12
|
+
import { getRuntimePaths } from "../../runtime/paths.js";
|
|
13
|
+
import { clearServiceStateFile } from "../../runtime/service/manager.js";
|
|
14
|
+
import { getServiceStateFilePathFromEnv, isServiceChildProcess } from "../../runtime/service/env.js";
|
|
15
|
+
import { getLogFilePath, initializeLogger, logger } from "../../utils/logger.js";
|
|
16
|
+
import { safeBackgroundTask } from "../../utils/safe-background-task.js";
|
|
16
17
|
const SHUTDOWN_TIMEOUT_MS = 5000;
|
|
17
18
|
async function getBotVersion() {
|
|
18
19
|
try {
|
|
19
|
-
const packageJsonPath = new URL("
|
|
20
|
+
const packageJsonPath = new URL("../../../package.json", import.meta.url);
|
|
20
21
|
const packageJsonContent = await readFile(packageJsonPath, "utf-8");
|
|
21
22
|
const packageJson = JSON.parse(packageJsonContent);
|
|
22
23
|
return packageJson.version ?? "unknown";
|
|
@@ -43,7 +44,7 @@ export async function startBotApp() {
|
|
|
43
44
|
await reconcileStoredModelSelection();
|
|
44
45
|
registerOpenCodeReadyRefreshHandler();
|
|
45
46
|
const bot = createBot();
|
|
46
|
-
await scheduledTaskRuntime.initialize(bot);
|
|
47
|
+
await scheduledTaskRuntime.initialize(bot, createScheduledTaskDeliverySender(bot.api, config.telegram.allowedUserId));
|
|
47
48
|
safeBackgroundTask({
|
|
48
49
|
taskName: "app.opencodeStartup",
|
|
49
50
|
task: async () => {
|
package/dist/{bot/utils/assistant-run-footer.js → app/formatters/assistant-run-footer-formatter.js}
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getAgentDisplayName } from "
|
|
1
|
+
import { getAgentDisplayName } from "../types/agent.js";
|
|
2
2
|
function formatDuration(elapsedMs) {
|
|
3
3
|
const safeElapsedMs = Math.max(0, Math.round(elapsedMs));
|
|
4
4
|
const totalSeconds = Math.floor(safeElapsedMs / 1000);
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { t } from "../../i18n/index.js";
|
|
2
|
+
import { formatCompactToolInfo } from "./summary-formatter.js";
|
|
3
|
+
function formatModelDisplayName(providerID, modelID) {
|
|
4
|
+
if (providerID && modelID) {
|
|
5
|
+
return `${providerID}/${modelID}`;
|
|
6
|
+
}
|
|
7
|
+
return t("pinned.unknown");
|
|
8
|
+
}
|
|
4
9
|
function shouldPreferInputDetails(tool, input) {
|
|
5
10
|
if (!input) {
|
|
6
11
|
return false;
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import * as path from "path";
|
|
2
|
-
import { config } from "
|
|
3
|
-
import { logger } from "
|
|
4
|
-
import { t } from "
|
|
5
|
-
import { getCurrentProject } from "../settings
|
|
6
|
-
import { convertToTelegramMarkdownV2 } from "./markdown-to-telegram-v2.js";
|
|
7
|
-
import { normalizeMarkdownForTelegramRendering } from "../telegram/render/markdown-normalizer.js";
|
|
8
|
-
const TELEGRAM_MESSAGE_LIMIT = 4096;
|
|
9
|
-
const MARKDOWN_V2_RESERVED_CHARS = /([_\*\[\]\(\)~`>#+\-=|{}.!\\])/g;
|
|
2
|
+
import { config } from "../../config.js";
|
|
3
|
+
import { logger } from "../../utils/logger.js";
|
|
4
|
+
import { t } from "../../i18n/index.js";
|
|
5
|
+
import { getCurrentProject } from "../stores/settings-store.js";
|
|
10
6
|
function truncateWithEllipsis(text, maxLength) {
|
|
11
7
|
if (text.length <= maxLength) {
|
|
12
8
|
return text;
|
|
@@ -16,50 +12,6 @@ function truncateWithEllipsis(text, maxLength) {
|
|
|
16
12
|
}
|
|
17
13
|
return `${text.slice(0, maxLength - 3).trimEnd()}...`;
|
|
18
14
|
}
|
|
19
|
-
function endsWithOddTrailingBackslashes(text, start, end) {
|
|
20
|
-
let backslashCount = 0;
|
|
21
|
-
for (let index = end - 1; index >= start; index--) {
|
|
22
|
-
if (text[index] !== "\\") {
|
|
23
|
-
break;
|
|
24
|
-
}
|
|
25
|
-
backslashCount += 1;
|
|
26
|
-
}
|
|
27
|
-
return backslashCount % 2 === 1;
|
|
28
|
-
}
|
|
29
|
-
function resolveSplitEndIndex(text, currentIndex, maxLength, options) {
|
|
30
|
-
const hardLimit = Math.min(text.length, currentIndex + maxLength);
|
|
31
|
-
if (hardLimit >= text.length) {
|
|
32
|
-
return text.length;
|
|
33
|
-
}
|
|
34
|
-
let endIndex = hardLimit;
|
|
35
|
-
const breakPoint = text.lastIndexOf("\n", endIndex);
|
|
36
|
-
if (breakPoint > currentIndex) {
|
|
37
|
-
endIndex = breakPoint + 1;
|
|
38
|
-
}
|
|
39
|
-
if (!options?.avoidTrailingMarkdownEscape) {
|
|
40
|
-
return endIndex;
|
|
41
|
-
}
|
|
42
|
-
while (endIndex > currentIndex && endsWithOddTrailingBackslashes(text, currentIndex, endIndex)) {
|
|
43
|
-
endIndex -= 1;
|
|
44
|
-
}
|
|
45
|
-
return endIndex > currentIndex ? endIndex : hardLimit;
|
|
46
|
-
}
|
|
47
|
-
function splitText(text, maxLength, options) {
|
|
48
|
-
const parts = [];
|
|
49
|
-
let currentIndex = 0;
|
|
50
|
-
while (currentIndex < text.length) {
|
|
51
|
-
const endIndex = resolveSplitEndIndex(text, currentIndex, maxLength, options);
|
|
52
|
-
if (endIndex <= currentIndex) {
|
|
53
|
-
const fallbackEnd = Math.min(text.length, currentIndex + 1);
|
|
54
|
-
parts.push(text.slice(currentIndex, fallbackEnd));
|
|
55
|
-
currentIndex = fallbackEnd;
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
parts.push(text.slice(currentIndex, endIndex));
|
|
59
|
-
currentIndex = endIndex;
|
|
60
|
-
}
|
|
61
|
-
return parts;
|
|
62
|
-
}
|
|
63
15
|
export function normalizePathForDisplay(filePath) {
|
|
64
16
|
const normalizedPath = filePath.replace(/\\/g, "/");
|
|
65
17
|
const project = getCurrentProject();
|
|
@@ -81,86 +33,6 @@ export function normalizePathForDisplay(filePath) {
|
|
|
81
33
|
}
|
|
82
34
|
return normalizedPath;
|
|
83
35
|
}
|
|
84
|
-
export function formatSummary(text) {
|
|
85
|
-
return formatSummaryWithMode(text, config.bot.messageFormatMode);
|
|
86
|
-
}
|
|
87
|
-
export function escapePlainTextForTelegramMarkdownV2(text) {
|
|
88
|
-
return text.replace(MARKDOWN_V2_RESERVED_CHARS, "\\$1");
|
|
89
|
-
}
|
|
90
|
-
function formatMarkdownForTelegram(text) {
|
|
91
|
-
try {
|
|
92
|
-
const preprocessed = normalizeMarkdownForTelegramRendering(text);
|
|
93
|
-
return escapeMarkdownV2PipesOutsideCode(convertToTelegramMarkdownV2(preprocessed));
|
|
94
|
-
}
|
|
95
|
-
catch (error) {
|
|
96
|
-
logger.warn("[Formatter] Failed to convert markdown summary, falling back to raw text", error);
|
|
97
|
-
return text;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
function escapeMarkdownV2PipesOutsideCode(text) {
|
|
101
|
-
let result = "";
|
|
102
|
-
let index = 0;
|
|
103
|
-
let inInlineCode = false;
|
|
104
|
-
let inCodeFence = false;
|
|
105
|
-
while (index < text.length) {
|
|
106
|
-
if (text.startsWith("```", index)) {
|
|
107
|
-
result += "```";
|
|
108
|
-
index += 3;
|
|
109
|
-
inCodeFence = !inCodeFence;
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
const char = text[index];
|
|
113
|
-
if (!inCodeFence && char === "`") {
|
|
114
|
-
inInlineCode = !inInlineCode;
|
|
115
|
-
result += char;
|
|
116
|
-
index += 1;
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
if (!inCodeFence && !inInlineCode && char === "|" && text[index - 1] !== "\\") {
|
|
120
|
-
result += "\\|";
|
|
121
|
-
index += 1;
|
|
122
|
-
continue;
|
|
123
|
-
}
|
|
124
|
-
result += char;
|
|
125
|
-
index += 1;
|
|
126
|
-
}
|
|
127
|
-
return result;
|
|
128
|
-
}
|
|
129
|
-
export function formatSummaryWithMode(text, mode, maxLength = TELEGRAM_MESSAGE_LIMIT) {
|
|
130
|
-
if (!text || text.trim().length === 0) {
|
|
131
|
-
return [];
|
|
132
|
-
}
|
|
133
|
-
const normalizedMaxLength = Math.max(1, Math.floor(maxLength));
|
|
134
|
-
const rawTextLimit = mode === "raw" ? Math.max(1, normalizedMaxLength - "```\n\n```".length) : normalizedMaxLength;
|
|
135
|
-
const parts = splitText(text, rawTextLimit);
|
|
136
|
-
const formattedParts = [];
|
|
137
|
-
for (const part of parts) {
|
|
138
|
-
const trimmed = part.trim();
|
|
139
|
-
if (!trimmed) {
|
|
140
|
-
continue;
|
|
141
|
-
}
|
|
142
|
-
if (mode === "markdown") {
|
|
143
|
-
const converted = formatMarkdownForTelegram(trimmed);
|
|
144
|
-
const convertedParts = splitText(converted, normalizedMaxLength, {
|
|
145
|
-
avoidTrailingMarkdownEscape: true,
|
|
146
|
-
});
|
|
147
|
-
for (const convertedPart of convertedParts) {
|
|
148
|
-
const normalizedPart = convertedPart.trim();
|
|
149
|
-
if (normalizedPart) {
|
|
150
|
-
formattedParts.push(normalizedPart);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
if (parts.length > 1) {
|
|
156
|
-
formattedParts.push(`\`\`\`\n${trimmed}\n\`\`\``);
|
|
157
|
-
}
|
|
158
|
-
else {
|
|
159
|
-
formattedParts.push(trimmed);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
return formattedParts;
|
|
163
|
-
}
|
|
164
36
|
function getToolDetails(tool, input) {
|
|
165
37
|
if (!input) {
|
|
166
38
|
return "";
|
|
@@ -355,6 +227,22 @@ export function formatCompactToolInfo(toolInfo, maxLength = 64, fallback = "-")
|
|
|
355
227
|
}
|
|
356
228
|
return `${normalized.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
|
|
357
229
|
}
|
|
230
|
+
export function formatCompactToolActivity(toolInfo, maxLength = 64) {
|
|
231
|
+
if (!hasCompactToolDetails(toolInfo)) {
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
const formatted = formatCompactToolInfo(toolInfo, maxLength, "").trim();
|
|
235
|
+
return formatted || null;
|
|
236
|
+
}
|
|
237
|
+
function hasCompactToolDetails(toolInfo) {
|
|
238
|
+
if (typeof toolInfo.title === "string" && toolInfo.title.trim().length > 0) {
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
if (toolInfo.tool === "todowrite" && Array.isArray(toolInfo.metadata?.todos)) {
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
return getToolDetails(toolInfo.tool, toolInfo.input).trim().length > 0;
|
|
245
|
+
}
|
|
358
246
|
function countLines(text) {
|
|
359
247
|
return text.split("\n").length;
|
|
360
248
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { isScheduledTaskSessionIgnored } from "../scheduled-task
|
|
2
|
-
import { logger } from "
|
|
1
|
+
import { isScheduledTaskSessionIgnored } from "../services/scheduled-task-session-ignore-service.js";
|
|
2
|
+
import { logger } from "../../utils/logger.js";
|
|
3
3
|
class BackgroundSessionTracker {
|
|
4
4
|
directory = null;
|
|
5
5
|
onNotification = null;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { permissionManager } from "./permission-manager.js";
|
|
2
|
+
import { questionManager } from "./question-manager.js";
|
|
3
|
+
import { renameManager } from "./rename-manager.js";
|
|
4
|
+
import { taskCreationManager } from "./scheduled-task-creation-manager.js";
|
|
5
|
+
import { logger } from "../../utils/logger.js";
|
|
2
6
|
export const DEFAULT_ALLOWED_INTERACTION_COMMANDS = ["/help", "/status", "/abort", "/detach"];
|
|
3
7
|
function normalizeCommand(command) {
|
|
4
8
|
const trimmed = command.trim().toLowerCase();
|
|
@@ -104,3 +108,29 @@ class InteractionManager {
|
|
|
104
108
|
}
|
|
105
109
|
}
|
|
106
110
|
export const interactionManager = new InteractionManager();
|
|
111
|
+
export function clearAllInteractionState(reason) {
|
|
112
|
+
const questionActive = questionManager.isActive();
|
|
113
|
+
const permissionActive = permissionManager.isActive();
|
|
114
|
+
const renameActive = renameManager.isWaitingForName();
|
|
115
|
+
const taskCreationActive = taskCreationManager.isActive();
|
|
116
|
+
const interactionSnapshot = interactionManager.getSnapshot();
|
|
117
|
+
questionManager.clear();
|
|
118
|
+
permissionManager.clear();
|
|
119
|
+
renameManager.clear();
|
|
120
|
+
taskCreationManager.clear();
|
|
121
|
+
interactionManager.clear(reason);
|
|
122
|
+
const hasAnyActiveState = questionActive ||
|
|
123
|
+
permissionActive ||
|
|
124
|
+
renameActive ||
|
|
125
|
+
taskCreationActive ||
|
|
126
|
+
interactionSnapshot !== null;
|
|
127
|
+
const message = `[InteractionCleanup] Cleared state: reason=${reason}, ` +
|
|
128
|
+
`questionActive=${questionActive}, permissionActive=${permissionActive}, ` +
|
|
129
|
+
`renameActive=${renameActive}, taskCreationActive=${taskCreationActive}, ` +
|
|
130
|
+
`interactionKind=${interactionSnapshot?.kind || "none"}`;
|
|
131
|
+
if (hasAnyActiveState) {
|
|
132
|
+
logger.info(message);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
logger.debug(message);
|
|
136
|
+
}
|
package/dist/{scheduled-task/creation-manager.js → app/managers/scheduled-task-creation-manager.js}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { cloneParsedTaskSchedule, cloneScheduledTaskModel } from "
|
|
2
|
-
import { logger } from "
|
|
1
|
+
import { cloneParsedTaskSchedule, cloneScheduledTaskModel } from "../types/scheduled-task.js";
|
|
2
|
+
import { logger } from "../../utils/logger.js";
|
|
3
3
|
function cloneState(state) {
|
|
4
4
|
return {
|
|
5
5
|
...state,
|