@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.
Files changed (193) hide show
  1. package/.env.example +10 -15
  2. package/README.md +33 -13
  3. package/dist/app/{start-bot-app.js → bootstrap/start-bot-app.js} +16 -15
  4. package/dist/{bot/utils/assistant-run-footer.js → app/formatters/assistant-run-footer-formatter.js} +1 -1
  5. package/dist/{summary → app/formatters}/subagent-formatter.js +8 -3
  6. package/dist/{summary/formatter.js → app/formatters/summary-formatter.js} +20 -132
  7. package/dist/{summary → app/formatters}/tool-message-batcher.js +1 -1
  8. package/dist/{bot/assistant-run-state.js → app/managers/assistant-run-state-manager.js} +1 -1
  9. package/dist/{attach/manager.js → app/managers/attach-manager.js} +1 -1
  10. package/dist/{background-session/tracker.js → app/managers/background-session-manager.js} +2 -2
  11. package/dist/{scheduled-task/foreground-state.js → app/managers/foreground-session-state-manager.js} +1 -1
  12. package/dist/{interaction/manager.js → app/managers/interaction-manager.js} +31 -1
  13. package/dist/{permission/manager.js → app/managers/permission-manager.js} +1 -1
  14. package/dist/{question/manager.js → app/managers/question-manager.js} +1 -1
  15. package/dist/{rename/manager.js → app/managers/rename-manager.js} +1 -1
  16. package/dist/{scheduled-task/creation-manager.js → app/managers/scheduled-task-creation-manager.js} +2 -2
  17. package/dist/{summary/aggregator.js → app/managers/summary-aggregation-manager.js} +177 -13
  18. package/dist/{agent/manager.js → app/services/agent-selection-service.js} +4 -5
  19. package/dist/{attach/service.js → app/services/attach-service.js} +23 -44
  20. package/dist/{bot/utils/busy-reconciliation.js → app/services/busy-reconciliation-service.js} +12 -7
  21. package/dist/app/services/command-catalog-service.js +21 -0
  22. package/dist/{bot/utils/external-user-input.js → app/services/external-user-input-service.js} +4 -19
  23. package/dist/app/services/file-browser-service.js +265 -0
  24. package/dist/{bot/utils/file-download.js → app/services/file-download-service.js} +14 -28
  25. package/dist/app/services/mcp-catalog-service.js +79 -0
  26. package/dist/app/services/message-history-service.js +81 -0
  27. package/dist/{model/capabilities.js → app/services/model-capabilities-service.js} +2 -2
  28. package/dist/{model/context-limit.js → app/services/model-context-limit-service.js} +4 -4
  29. package/dist/{model/manager.js → app/services/model-selection-service.js} +4 -4
  30. package/dist/{project/manager.js → app/services/project-service.js} +3 -3
  31. package/dist/app/services/project-switch-service.js +39 -0
  32. package/dist/{bot/utils/busy-guard.js → app/services/run-control-service.js} +3 -14
  33. package/dist/{scheduled-task/executor.js → app/services/scheduled-task-executor-service.js} +27 -12
  34. package/dist/{scheduled-task/runtime.js → app/services/scheduled-task-runtime-service.js} +17 -56
  35. package/dist/{scheduled-task/schedule-parser.js → app/services/scheduled-task-schedule-parser-service.js} +3 -3
  36. package/dist/{scheduled-task/session-ignore.js → app/services/scheduled-task-session-ignore-service.js} +1 -1
  37. package/dist/{session/cache-manager.js → app/services/session-cache-service.js} +3 -3
  38. package/dist/{session/manager.js → app/services/session-service.js} +1 -1
  39. package/dist/app/services/skills-catalog-service.js +21 -0
  40. package/dist/{stt/client.js → app/services/stt-service.js} +2 -2
  41. package/dist/{tts/client.js → app/services/tts-service.js} +68 -5
  42. package/dist/{variant/manager.js → app/services/variant-selection-service.js} +3 -3
  43. package/dist/{scheduled-task/store.js → app/stores/scheduled-task-store.js} +3 -3
  44. package/dist/{settings/manager.js → app/stores/settings-store.js} +49 -7
  45. package/dist/app/types/worktree.js +1 -0
  46. package/dist/bot/{handlers/agent.js → callbacks/agent-selection-callback-handler.js} +8 -57
  47. package/dist/bot/callbacks/callback-router.js +113 -0
  48. package/dist/bot/{commands/commands.js → callbacks/command-catalog-callback-handler.js} +17 -197
  49. package/dist/bot/{handlers/context.js → callbacks/context-control-callback-handler.js} +3 -32
  50. package/dist/bot/callbacks/file-browser-callback-handler.js +219 -0
  51. package/dist/bot/callbacks/inline-menu-cancel-callback-handler.js +32 -0
  52. package/dist/bot/{commands/mcps.js → callbacks/mcp-catalog-callback-handler.js} +7 -172
  53. package/dist/bot/{commands/messages.js → callbacks/message-history-callback-handler.js} +17 -248
  54. package/dist/bot/{handlers/model.js → callbacks/model-selection-callback-handler.js} +10 -71
  55. package/dist/bot/callbacks/permission-callback-handler.js +114 -0
  56. package/dist/bot/callbacks/project-callback-handler.js +79 -0
  57. package/dist/bot/callbacks/question-callback-handler.js +159 -0
  58. package/dist/bot/{commands/rename.js → callbacks/rename-callback-handler.js} +6 -34
  59. package/dist/bot/{commands/tasklist.js → callbacks/scheduled-task-callback-handler.js} +83 -68
  60. package/dist/bot/{commands/sessions.js → callbacks/session-callback-handler.js} +19 -158
  61. package/dist/bot/callbacks/settings-callback-handler.js +105 -0
  62. package/dist/bot/{commands/skills.js → callbacks/skills-catalog-callback-handler.js} +8 -188
  63. package/dist/bot/{handlers/variant.js → callbacks/variant-selection-callback-handler.js} +7 -69
  64. package/dist/bot/callbacks/worktree-callback-handler.js +97 -0
  65. package/dist/bot/commands/{abort.js → abort-command.js} +6 -6
  66. package/dist/bot/commands/command-catalog-command.js +42 -0
  67. package/dist/bot/commands/definitions.js +1 -1
  68. package/dist/bot/commands/{detach.js → detach-command.js} +8 -8
  69. package/dist/bot/commands/ls-command.js +48 -0
  70. package/dist/bot/commands/mcp-catalog-command.js +39 -0
  71. package/dist/bot/commands/messages-command.js +59 -0
  72. package/dist/bot/commands/{new.js → new-command.js} +12 -11
  73. package/dist/bot/commands/open-command.js +45 -0
  74. package/dist/bot/commands/{opencode-start.js → opencode-start-command.js} +1 -1
  75. package/dist/bot/commands/{opencode-stop.js → opencode-stop-command.js} +1 -1
  76. package/dist/bot/commands/projects-command.js +32 -0
  77. package/dist/bot/commands/rename-command.js +33 -0
  78. package/dist/bot/commands/sessions-command.js +42 -0
  79. package/dist/bot/commands/settings-command.js +10 -0
  80. package/dist/bot/commands/skills-catalog-command.js +42 -0
  81. package/dist/bot/commands/{start.js → start-command.js} +12 -12
  82. package/dist/bot/commands/{status.js → status-command.js} +15 -10
  83. package/dist/bot/commands/{task.js → task-command.js} +9 -81
  84. package/dist/bot/commands/tasklist-command.js +40 -0
  85. package/dist/bot/commands/worktree-command.js +47 -0
  86. package/dist/bot/handlers/{document.js → document-handler.js} +3 -3
  87. package/dist/bot/handlers/{media-group.js → media-group-handler.js} +3 -3
  88. package/dist/bot/handlers/photo-handler.js +44 -0
  89. package/dist/bot/handlers/prompt.js +17 -17
  90. package/dist/bot/handlers/text-message-handler.js +61 -0
  91. package/dist/bot/handlers/tts-response-handler.js +29 -0
  92. package/dist/bot/handlers/{voice.js → voice-handler.js} +6 -3
  93. package/dist/bot/index.js +27 -1179
  94. package/dist/{keyboard/manager.js → bot/keyboards/keyboard-manager.js} +6 -6
  95. package/dist/bot/keyboards/keyboard-types.js +1 -0
  96. package/dist/bot/{utils/keyboard.js → keyboards/main-reply-keyboard.js} +2 -2
  97. package/dist/bot/menus/agent-selection-menu.js +54 -0
  98. package/dist/bot/menus/command-catalog-menu.js +101 -0
  99. package/dist/bot/menus/context-control-menu.js +33 -0
  100. package/dist/bot/menus/file-browser-menu.js +265 -0
  101. package/dist/bot/{handlers → menus}/inline-menu.js +7 -34
  102. package/dist/bot/menus/mcp-catalog-menu.js +97 -0
  103. package/dist/bot/menus/message-history-menu.js +107 -0
  104. package/dist/bot/menus/model-selection-menu.js +66 -0
  105. package/dist/bot/menus/permission-menu.js +116 -0
  106. package/dist/bot/{commands/projects.js → menus/project-selection-menu.js} +5 -105
  107. package/dist/bot/{handlers/question.js → menus/question-menu.js} +10 -166
  108. package/dist/bot/menus/rename-menu.js +6 -0
  109. package/dist/bot/menus/scheduled-task-menu.js +42 -0
  110. package/dist/bot/menus/session-selection-menu.js +115 -0
  111. package/dist/bot/menus/settings-menu.js +52 -0
  112. package/dist/bot/menus/skills-catalog-menu.js +99 -0
  113. package/dist/bot/menus/variant-selection-menu.js +67 -0
  114. package/dist/bot/menus/worktree-selection-menu.js +76 -0
  115. package/dist/bot/{utils → messages}/assistant-rendering.js +2 -2
  116. package/dist/bot/messages/busy-blocked-renderer.js +11 -0
  117. package/dist/bot/messages/external-user-input-notification.js +22 -0
  118. package/dist/bot/messages/scheduled-task-delivery.js +63 -0
  119. package/dist/bot/{utils → messages}/send-downloaded-file.js +1 -1
  120. package/dist/bot/messages/summary-message-formatter.js +130 -0
  121. package/dist/bot/{utils → messages}/thinking-message.js +1 -4
  122. package/dist/bot/messages/thinking-rendering.js +66 -0
  123. package/dist/{interaction/guard.js → bot/middleware/interaction-guard-decision.js} +11 -4
  124. package/dist/bot/middleware/interaction-guard.js +2 -2
  125. package/dist/bot/middleware/unknown-command.js +1 -1
  126. package/dist/{pinned/format.js → bot/pinned/pinned-message-format.js} +1 -2
  127. package/dist/{pinned/manager.js → bot/pinned/pinned-message-manager.js} +10 -10
  128. package/dist/bot/pinned/pinned-message-types.js +1 -0
  129. package/dist/{summary → bot/render}/markdown-to-telegram-v2.js +2 -2
  130. package/dist/bot/render/types.js +1 -0
  131. package/dist/bot/routers/command-router.js +73 -0
  132. package/dist/bot/routers/message-router.js +150 -0
  133. package/dist/bot/services/attach-presentation.js +40 -0
  134. package/dist/bot/services/event-subscription-service.js +1116 -0
  135. package/dist/bot/services/project-switch-presentation.js +26 -0
  136. package/dist/bot/streaming/compact-progress-streamer.js +162 -0
  137. package/dist/bot/{utils → streaming}/finalize-assistant-response.js +9 -3
  138. package/dist/bot/streaming/response-streamer.js +16 -1
  139. package/dist/cli.js +4 -4
  140. package/dist/config.js +9 -18
  141. package/dist/i18n/ar.js +31 -6
  142. package/dist/i18n/de.js +32 -7
  143. package/dist/i18n/en.js +32 -7
  144. package/dist/i18n/es.js +32 -7
  145. package/dist/i18n/fr.js +32 -7
  146. package/dist/i18n/ru.js +32 -7
  147. package/dist/i18n/zh.js +32 -7
  148. package/dist/index.js +1 -1
  149. package/dist/opencode/ready-refresh.js +2 -2
  150. package/dist/{service → runtime/service}/manager.js +2 -2
  151. package/dist/runtime/service/types.js +1 -0
  152. package/package.json +1 -1
  153. package/dist/bot/commands/ls.js +0 -445
  154. package/dist/bot/commands/open.js +0 -319
  155. package/dist/bot/commands/tts.js +0 -13
  156. package/dist/bot/commands/worktree.js +0 -201
  157. package/dist/bot/handlers/permission.js +0 -235
  158. package/dist/bot/utils/browser-roots.js +0 -140
  159. package/dist/bot/utils/file-tree.js +0 -92
  160. package/dist/bot/utils/send-tts-response.js +0 -37
  161. package/dist/bot/utils/switch-project.js +0 -45
  162. package/dist/bot/utils/telegram-file-url.js +0 -11
  163. package/dist/interaction/busy.js +0 -8
  164. package/dist/interaction/cleanup.js +0 -32
  165. /package/dist/{scheduled-task/display.js → app/formatters/scheduled-task-display-formatter.js} +0 -0
  166. /package/dist/{bot/utils/abort-error-suppression.js → app/managers/abort-suppression-manager.js} +0 -0
  167. /package/dist/{external-input/suppression.js → app/managers/external-input-suppression-manager.js} +0 -0
  168. /package/dist/{scheduled-task/next-run.js → app/services/scheduled-task-next-run-service.js} +0 -0
  169. /package/dist/{git/worktree.js → app/services/worktree-service.js} +0 -0
  170. /package/dist/{agent/types.js → app/types/agent.js} +0 -0
  171. /package/dist/{interaction/types.js → app/types/interaction.js} +0 -0
  172. /package/dist/{model/types.js → app/types/model.js} +0 -0
  173. /package/dist/{keyboard/types.js → app/types/permission.js} +0 -0
  174. /package/dist/{permission/types.js → app/types/project.js} +0 -0
  175. /package/dist/{pinned/types.js → app/types/question.js} +0 -0
  176. /package/dist/{scheduled-task/types.js → app/types/scheduled-task.js} +0 -0
  177. /package/dist/{question/types.js → app/types/session.js} +0 -0
  178. /package/dist/{service/types.js → app/types/settings.js} +0 -0
  179. /package/dist/{telegram/render/types.js → app/types/summary.js} +0 -0
  180. /package/dist/{variant/types.js → app/types/variant.js} +0 -0
  181. /package/dist/bot/commands/{help.js → help-command.js} +0 -0
  182. /package/dist/bot/{utils → messages}/send-with-markdown-fallback.js +0 -0
  183. /package/dist/bot/{utils → messages}/telegram-text.js +0 -0
  184. /package/dist/{telegram → bot}/render/block-fallback.js +0 -0
  185. /package/dist/{telegram → bot}/render/block-parser.js +0 -0
  186. /package/dist/{telegram → bot}/render/block-renderer.js +0 -0
  187. /package/dist/{telegram → bot}/render/chunker.js +0 -0
  188. /package/dist/{telegram → bot}/render/inline-renderer.js +0 -0
  189. /package/dist/{telegram → bot}/render/markdown-normalizer.js +0 -0
  190. /package/dist/{telegram → bot}/render/pipeline.js +0 -0
  191. /package/dist/{telegram → bot}/render/validator.js +0 -0
  192. /package/dist/bot/{utils/commands.js → routers/command-utils.js} +0 -0
  193. /package/dist/{service/runtime.js → runtime/service/env.js} +0 -0
@@ -0,0 +1,26 @@
1
+ import { keyboardManager } from "../keyboards/keyboard-manager.js";
2
+ import { createMainKeyboard } from "../keyboards/main-reply-keyboard.js";
3
+ import { pinnedMessageManager } from "../pinned/pinned-message-manager.js";
4
+ export function createProjectSwitchPresentation() {
5
+ return {
6
+ async clearPinnedMessage() {
7
+ await pinnedMessageManager.clear();
8
+ },
9
+ initializeKeyboard(ctx) {
10
+ if (ctx.chat) {
11
+ keyboardManager.initialize(ctx.api, ctx.chat.id);
12
+ }
13
+ },
14
+ async refreshContextLimit() {
15
+ await pinnedMessageManager.refreshContextLimit();
16
+ return pinnedMessageManager.getContextLimit();
17
+ },
18
+ updateKeyboardContext(contextInfo) {
19
+ keyboardManager.updateContext(contextInfo.tokensUsed, contextInfo.tokensLimit);
20
+ },
21
+ updateKeyboardAgent(agent) {
22
+ keyboardManager.updateAgent(agent);
23
+ },
24
+ createMainKeyboard,
25
+ };
26
+ }
@@ -0,0 +1,162 @@
1
+ import { t } from "../../i18n/index.js";
2
+ import { logger } from "../../utils/logger.js";
3
+ function getErrorMessage(error) {
4
+ return error instanceof Error ? error.message : String(error);
5
+ }
6
+ function createInitialState(sessionId) {
7
+ return {
8
+ sessionId,
9
+ messageId: null,
10
+ latestText: "",
11
+ toolCallIds: new Set(),
12
+ filePaths: new Set(),
13
+ timer: null,
14
+ task: Promise.resolve(true),
15
+ cancelled: false,
16
+ };
17
+ }
18
+ export class CompactProgressStreamer {
19
+ states = new Map();
20
+ throttleMs;
21
+ sendText;
22
+ editText;
23
+ constructor({ throttleMs, sendText, editText }) {
24
+ this.throttleMs = throttleMs;
25
+ this.sendText = sendText;
26
+ this.editText = editText;
27
+ }
28
+ updateActivity(sessionId, activity) {
29
+ const normalizedActivity = activity.trim();
30
+ if (!sessionId || !normalizedActivity) {
31
+ return;
32
+ }
33
+ const state = this.getOrCreateState(sessionId);
34
+ state.latestText = t("progress.compact.activity", {
35
+ header: t("progress.compact.working_header"),
36
+ activity: normalizedActivity,
37
+ });
38
+ this.ensureTimer(state);
39
+ }
40
+ updateThinking(sessionId) {
41
+ this.updateActivity(sessionId, t("progress.compact.thinking"));
42
+ }
43
+ updateResponding(sessionId) {
44
+ this.updateActivity(sessionId, t("progress.compact.responding"));
45
+ }
46
+ updateWaitingForQuestion(sessionId) {
47
+ this.updateActivity(sessionId, t("progress.compact.waiting_question"));
48
+ }
49
+ updateWaitingForPermission(sessionId) {
50
+ this.updateActivity(sessionId, t("progress.compact.waiting_permission"));
51
+ }
52
+ addToolCall(sessionId, callId) {
53
+ if (!sessionId || !callId) {
54
+ return;
55
+ }
56
+ this.states.get(sessionId)?.toolCallIds.add(callId);
57
+ }
58
+ addFileChange(sessionId, filePath) {
59
+ const normalizedPath = filePath.trim();
60
+ if (!sessionId || !normalizedPath) {
61
+ return;
62
+ }
63
+ this.states.get(sessionId)?.filePaths.add(normalizedPath);
64
+ }
65
+ async finalize(sessionId) {
66
+ const state = this.states.get(sessionId);
67
+ if (!state) {
68
+ return;
69
+ }
70
+ state.latestText = t("progress.compact.done", {
71
+ header: t("progress.compact.finished_header"),
72
+ tools: state.toolCallIds.size,
73
+ files: state.filePaths.size,
74
+ });
75
+ this.clearTimer(state);
76
+ await state.task.catch(() => false);
77
+ await this.syncState(state, "finalize");
78
+ this.cancelState(state);
79
+ this.states.delete(sessionId);
80
+ }
81
+ clearSession(sessionId, reason) {
82
+ const state = this.states.get(sessionId);
83
+ if (!state) {
84
+ return;
85
+ }
86
+ this.clearTimer(state);
87
+ this.cancelState(state);
88
+ this.states.delete(sessionId);
89
+ logger.debug(`[CompactProgress] Cleared session: session=${sessionId}, reason=${reason}`);
90
+ }
91
+ clearAll(reason) {
92
+ for (const state of this.states.values()) {
93
+ this.clearTimer(state);
94
+ this.cancelState(state);
95
+ }
96
+ this.states.clear();
97
+ logger.debug(`[CompactProgress] Cleared all sessions: reason=${reason}`);
98
+ }
99
+ getOrCreateState(sessionId) {
100
+ const existing = this.states.get(sessionId);
101
+ if (existing) {
102
+ return existing;
103
+ }
104
+ const state = createInitialState(sessionId);
105
+ this.states.set(sessionId, state);
106
+ return state;
107
+ }
108
+ ensureTimer(state) {
109
+ if (state.cancelled || state.timer) {
110
+ return;
111
+ }
112
+ if (this.throttleMs <= 0) {
113
+ state.task = this.enqueueTask(state, () => this.syncState(state, "immediate"));
114
+ return;
115
+ }
116
+ state.timer = setTimeout(() => {
117
+ state.timer = null;
118
+ state.task = this.enqueueTask(state, () => this.syncState(state, "throttle"));
119
+ }, this.throttleMs);
120
+ }
121
+ enqueueTask(state, task) {
122
+ const nextTask = state.task
123
+ .catch(() => false)
124
+ .then(async () => {
125
+ if (state.cancelled) {
126
+ return false;
127
+ }
128
+ return task();
129
+ });
130
+ state.task = nextTask;
131
+ return nextTask;
132
+ }
133
+ async syncState(state, reason) {
134
+ const text = state.latestText.trim();
135
+ if (!text || state.cancelled) {
136
+ return false;
137
+ }
138
+ try {
139
+ if (state.messageId === null) {
140
+ state.messageId = await this.sendText(state.sessionId, text);
141
+ }
142
+ else {
143
+ await this.editText(state.sessionId, state.messageId, text);
144
+ }
145
+ logger.debug(`[CompactProgress] Synced progress message: session=${state.sessionId}, reason=${reason}`);
146
+ return true;
147
+ }
148
+ catch (error) {
149
+ logger.error(`[CompactProgress] Failed to sync progress message: session=${state.sessionId}, reason=${reason}, error=${getErrorMessage(error)}`, error);
150
+ return false;
151
+ }
152
+ }
153
+ clearTimer(state) {
154
+ if (state.timer) {
155
+ clearTimeout(state.timer);
156
+ state.timer = null;
157
+ }
158
+ }
159
+ cancelState(state) {
160
+ state.cancelled = true;
161
+ }
162
+ }
@@ -1,5 +1,5 @@
1
1
  import { logger } from "../../utils/logger.js";
2
- export async function finalizeAssistantResponse({ sessionId, messageId, messageText, responseStreamer, flushPendingServiceMessages, prepareStreamingPayload, renderFinalParts, getReplyKeyboard, sendRenderedPart, }) {
2
+ export async function finalizeAssistantResponse({ sessionId, messageId, messageText, responseStreamer, flushPendingServiceMessages, prepareStreamingPayload, renderFinalParts, getReplyKeyboard, notifyFirstFinalPart = false, sendRenderedPart, }) {
3
3
  logger.debug(`[FinalizeResponse] Final assistant raw text received: session=${sessionId}, message=${messageId}`, messageText);
4
4
  const keyboard = getReplyKeyboard();
5
5
  const replyOptions = keyboard ? { reply_markup: keyboard } : undefined;
@@ -15,15 +15,21 @@ export async function finalizeAssistantResponse({ sessionId, messageId, messageT
15
15
  preparedStreamPayload.sendOptions = streamSendOptions;
16
16
  preparedStreamPayload.editOptions = undefined;
17
17
  }
18
- const result = await responseStreamer.complete(sessionId, messageId, preparedStreamPayload ?? undefined);
18
+ const result = notifyFirstFinalPart
19
+ ? await responseStreamer.complete(sessionId, messageId, preparedStreamPayload ?? undefined, {
20
+ notifyFirstCompletePart: true,
21
+ })
22
+ : await responseStreamer.complete(sessionId, messageId, preparedStreamPayload ?? undefined);
19
23
  await flushPendingServiceMessages();
20
24
  if (result.streamed) {
21
25
  logger.debug(`[FinalizeResponse] Finalized streamed assistant message in place: session=${sessionId}, message=${messageId}, telegramMessages=${result.telegramMessageIds.length}`);
22
26
  return true;
23
27
  }
24
28
  const parts = renderFinalParts(messageText);
29
+ let notifyNextPart = notifyFirstFinalPart;
25
30
  for (const part of parts) {
26
- await sendRenderedPart(part, silentReplyOptions);
31
+ await sendRenderedPart(part, notifyNextPart ? replyOptions : silentReplyOptions);
32
+ notifyNextPart = false;
27
33
  }
28
34
  return false;
29
35
  }
@@ -51,6 +51,16 @@ function delay(ms) {
51
51
  setTimeout(resolve, ms);
52
52
  });
53
53
  }
54
+ function enableNotificationForOptions(options) {
55
+ if (!options) {
56
+ return options;
57
+ }
58
+ const nextOptions = {
59
+ ...options,
60
+ };
61
+ delete nextOptions.disable_notification;
62
+ return nextOptions;
63
+ }
54
64
  export class ResponseStreamer {
55
65
  throttleMs;
56
66
  sendPart;
@@ -111,11 +121,16 @@ export class ResponseStreamer {
111
121
  if (synced && this.completePart && state.latestPayload) {
112
122
  try {
113
123
  const realMessageIds = [];
124
+ let notifyNextCompletePart = options?.notifyFirstCompletePart === true;
114
125
  for (const part of state.latestPayload.parts) {
115
126
  if (!part.text) {
116
127
  continue;
117
128
  }
118
- const result = await this.completePart(part, state.latestPayload.sendOptions);
129
+ const completeOptions = notifyNextCompletePart
130
+ ? enableNotificationForOptions(state.latestPayload.sendOptions)
131
+ : state.latestPayload.sendOptions;
132
+ notifyNextCompletePart = false;
133
+ const result = await this.completePart(part, completeOptions);
119
134
  realMessageIds.push(result.messageId);
120
135
  }
121
136
  state.telegramMessageIds = realMessageIds;
package/dist/cli.js CHANGED
@@ -81,7 +81,7 @@ async function runStartCommand(mode, daemon) {
81
81
  const { ensureRuntimeConfigForStart } = await import("./runtime/bootstrap.js");
82
82
  await ensureRuntimeConfigForStart();
83
83
  if (daemon) {
84
- const { startBotDaemon } = await import("./service/manager.js");
84
+ const { startBotDaemon } = await import("./runtime/service/manager.js");
85
85
  const result = await startBotDaemon(modeResult.mode);
86
86
  const cleanupMessage = formatServiceCleanupMessage(result.cleanupReason);
87
87
  const runtimePaths = getRuntimePaths();
@@ -115,7 +115,7 @@ async function runStartCommand(mode, daemon) {
115
115
  }
116
116
  const { initializeLogger } = await import("./utils/logger.js");
117
117
  await initializeLogger();
118
- const { startBotApp } = await import("./app/start-bot-app.js");
118
+ const { startBotApp } = await import("./app/bootstrap/start-bot-app.js");
119
119
  await startBotApp();
120
120
  return EXIT_SUCCESS;
121
121
  }
@@ -129,7 +129,7 @@ async function runConfigCommand() {
129
129
  }
130
130
  async function runStatusCommand() {
131
131
  setRuntimeMode("installed");
132
- const { getBotServiceStatus } = await import("./service/manager.js");
132
+ const { getBotServiceStatus } = await import("./runtime/service/manager.js");
133
133
  const runtimePaths = getRuntimePaths();
134
134
  const status = await getBotServiceStatus();
135
135
  writeOptionalLine(formatServiceCleanupMessage(status.cleanupReason));
@@ -150,7 +150,7 @@ async function runStatusCommand() {
150
150
  }
151
151
  async function runStopCommand() {
152
152
  setRuntimeMode("installed");
153
- const { stopBotDaemon } = await import("./service/manager.js");
153
+ const { stopBotDaemon } = await import("./runtime/service/manager.js");
154
154
  const result = await stopBotDaemon();
155
155
  writeOptionalLine(formatServiceCleanupMessage(result.cleanupReason));
156
156
  if (!result.success) {
package/dist/config.js CHANGED
@@ -39,17 +39,6 @@ function getOptionalBooleanEnvVar(key, defaultValue) {
39
39
  }
40
40
  return defaultValue;
41
41
  }
42
- function getOptionalStreamingModeEnvVar(key, defaultValue) {
43
- const value = getEnvVar(key, false);
44
- if (!value) {
45
- return defaultValue;
46
- }
47
- const normalized = value.trim().toLowerCase();
48
- if (normalized === "edit" || normalized === "draft") {
49
- return normalized;
50
- }
51
- return defaultValue;
52
- }
53
42
  function getOptionalMessageFormatModeEnvVar(key, defaultValue) {
54
43
  const value = getEnvVar(key, false);
55
44
  if (!value) {
@@ -61,7 +50,7 @@ function getOptionalMessageFormatModeEnvVar(key, defaultValue) {
61
50
  }
62
51
  return defaultValue;
63
52
  }
64
- const VALID_TTS_PROVIDERS = ["openai", "google"];
53
+ const VALID_TTS_PROVIDERS = ["openai", "google", "elevenlabs"];
65
54
  function getOptionalTtsProviderEnvVar(key, defaultValue) {
66
55
  const value = getEnvVar(key, false);
67
56
  if (!value) {
@@ -121,13 +110,10 @@ export const config = {
121
110
  commandsListLimit: getOptionalPositiveIntEnvVar("COMMANDS_LIST_LIMIT", 10),
122
111
  taskLimit: getOptionalPositiveIntEnvVar("TASK_LIMIT", 10),
123
112
  scheduledTaskExecutionTimeoutMinutes: getOptionalPositiveIntEnvVar("SCHEDULED_TASK_EXECUTION_TIMEOUT_MINUTES", 120),
113
+ scheduledTaskNotificationsSilent: getOptionalBooleanEnvVar("SCHEDULED_TASK_DISABLE_NOTIFICATION", false),
124
114
  responseStreamThrottleMs: getOptionalPositiveIntEnvVar("RESPONSE_STREAM_THROTTLE_MS", 1000),
125
- responseStreamingMode: getOptionalStreamingModeEnvVar("RESPONSE_STREAMING_MODE", "edit"),
126
115
  bashToolDisplayMaxLength: getOptionalPositiveIntEnvVar("BASH_TOOL_DISPLAY_MAX_LENGTH", 128),
127
116
  locale: getOptionalLocaleEnvVar("BOT_LOCALE", "en"),
128
- hideThinkingMessages: getOptionalBooleanEnvVar("HIDE_THINKING_MESSAGES", false),
129
- hideToolCallMessages: getOptionalBooleanEnvVar("HIDE_TOOL_CALL_MESSAGES", false),
130
- hideToolFileMessages: getOptionalBooleanEnvVar("HIDE_TOOL_FILE_MESSAGES", false),
131
117
  trackBackgroundSessions: getOptionalBooleanEnvVar("TRACK_BACKGROUND_SESSIONS", true),
132
118
  messageFormatMode: getOptionalMessageFormatModeEnvVar("MESSAGE_FORMAT_MODE", "markdown"),
133
119
  },
@@ -146,12 +132,17 @@ export const config = {
146
132
  },
147
133
  tts: (() => {
148
134
  const provider = getOptionalTtsProviderEnvVar("TTS_PROVIDER", "openai");
149
- const defaultVoice = provider === "google" ? "en-US-Studio-O" : "alloy";
135
+ const defaultVoice = provider === "google"
136
+ ? "en-US-Studio-O"
137
+ : provider === "elevenlabs"
138
+ ? "21m00Tcm4TlvDq8ikWAM"
139
+ : "alloy";
140
+ const defaultModel = provider === "elevenlabs" ? "eleven_flash_v2_5" : "gpt-4o-mini-tts";
150
141
  return {
151
142
  apiUrl: getEnvVar("TTS_API_URL", false),
152
143
  apiKey: getEnvVar("TTS_API_KEY", false),
153
144
  provider,
154
- model: getEnvVar("TTS_MODEL", false) || "gpt-4o-mini-tts",
145
+ model: getEnvVar("TTS_MODEL", false) || defaultModel,
155
146
  voice: getEnvVar("TTS_VOICE", false) || defaultVoice,
156
147
  };
157
148
  })(),
package/dist/i18n/ar.js CHANGED
@@ -12,7 +12,7 @@ export const ar = {
12
12
  "cmd.description.detach": "الخروج من الجلسة دون إيقافها",
13
13
  "cmd.description.sessions": "عرض الجلسات السابقة",
14
14
  "cmd.description.messages": "استعراض رسائل الجلسة",
15
- "cmd.description.tts": "تشغيل أو إيقاف الردود الصوتية",
15
+ "cmd.description.settings": "تغيير إعدادات البوت",
16
16
  "cmd.description.projects": "عرض المشاريع",
17
17
  "cmd.description.worktree": "التبديل بين نسخ العمل في Git",
18
18
  "cmd.description.task": "إنشاء مهمة مجدولة",
@@ -44,6 +44,7 @@ export const ar = {
44
44
  "question.blocked.expected_answer": "⚠️ أجب عن السؤال الحالي باستخدام الأزرار، أو اختر إجابة مخصصة، أو اضغط إلغاء.",
45
45
  "question.blocked.command_not_allowed": "⚠️ لا يمكن استخدام هذا الأمر قبل إنهاء السؤال الحالي.",
46
46
  "inline.button.cancel": "❌ إلغاء",
47
+ "inline.button.close": "❌ إغلاق",
47
48
  "inline.inactive_callback": "انتهت صلاحية هذه القائمة",
48
49
  "inline.cancelled_callback": "تم الإلغاء",
49
50
  "common.unknown": "غير معروف",
@@ -52,6 +53,16 @@ export const ar = {
52
53
  "help.keyboard_hint": "💡 استخدم الأزرار السفلية للتبديل بين الوكيل والنموذج وخيارات التشغيل وإدارة السياق.",
53
54
  "help.text": "📖 **المساعدة**\n\n/status - عرض حالة الخادم والجلسة\n/sessions - عرض الجلسات\n/new - بدء جلسة جديدة\n/help - المساعدة",
54
55
  "bot.thinking": "💭 جارٍ التفكير...",
56
+ "progress.compact.activity": "{header}\n{activity}",
57
+ "progress.compact.working_header": "⏳ جارٍ العمل",
58
+ "progress.compact.finished_header": "✅ اكتمل العمل",
59
+ "progress.compact.thinking": "💭 جارٍ التفكير...",
60
+ "progress.compact.responding": "✍️ جارٍ كتابة الرد...",
61
+ "progress.compact.waiting_question": "❓ في انتظار إجابتك...",
62
+ "progress.compact.waiting_permission": "🔐 في انتظار الإذن...",
63
+ "progress.compact.retrying": "🔁 جارٍ إعادة المحاولة...",
64
+ "progress.compact.task": "🤖 المهمة قيد التشغيل",
65
+ "progress.compact.done": "{header}\nاستدعاءات الأدوات: {tools} · الملفات المعدلة: {files}",
55
66
  "bot.project_not_selected": "🏗 لم تحدد مشروعًا بعد.\n\nاختر مشروعًا أولًا باستخدام /projects.",
56
67
  "bot.creating_session": "🔄 جارٍ بدء جلسة جديدة...",
57
68
  "bot.create_session_error": "🔴 تعذر بدء جلسة جديدة. جرّب /new أو افحص حالة الخادم باستخدام /status.",
@@ -94,8 +105,9 @@ export const ar = {
94
105
  "status.line.mode": "الوكيل: {mode}",
95
106
  "status.line.model": "النموذج: {model}",
96
107
  "status.line.tts": "الردود الصوتية: {tts}",
97
- "status.tts.on": "مفعّلة",
98
108
  "status.tts.off": "معطّلة",
109
+ "status.tts.all": "الكل",
110
+ "status.tts.auto": "تلقائي",
99
111
  "status.agent_not_set": "غير محدد",
100
112
  "status.project_selected": "المشروع: {project}",
101
113
  "status.worktree_selected": "نسخة العمل: {worktree}",
@@ -105,10 +117,23 @@ export const ar = {
105
117
  "status.session_not_selected": "الجلسة الحالية: غير محددة",
106
118
  "status.session_hint": "استخدم /sessions لاختيار جلسة أو /new لبدء جلسة جديدة",
107
119
  "status.server_unavailable": "🔴 خادم OpenCode غير متاح\n\nاستخدم /opencode_start لتشغيل الخادم.",
108
- "tts.enabled": "🔊 تم تفعيل الردود الصوتية.",
120
+ "tts.off": "🔇 تم تعطيل الردود الصوتية.",
121
+ "tts.all": "🔊 تم تفعيل الردود الصوتية لجميع الرسائل.",
122
+ "tts.auto": "🎤 تم تفعيل الردود الصوتية للرسائل الصوتية فقط.",
109
123
  "tts.not_configured": "⚠️ الردود الصوتية غير متاحة حاليًا. اضبط `TTS_API_URL` و`TTS_API_KEY` أولًا.",
110
- "tts.disabled": "🔇 تم تعطيل الردود الصوتية.",
111
124
  "tts.failed": "⚠️ تعذر إنشاء الرد الصوتي.",
125
+ "settings.menu.title": "⚙️ إعدادات البوت\nاضغط على إعداد لتبديل قيمته:",
126
+ "settings.compact_output.label": "وضع الإخراج المختصر",
127
+ "settings.thinking_content.label": "محتوى التفكير",
128
+ "settings.response_streaming.label": "بث الرد",
129
+ "settings.response_streaming.edit": "edit",
130
+ "settings.response_streaming.draft": "draft (experimental)",
131
+ "settings.diff_files.label": "ملفات diff",
132
+ "settings.assistant_footer.label": "تذييل الرد",
133
+ "settings.tts.label": "الردود الصوتية",
134
+ "settings.value.on": "مفعّل",
135
+ "settings.value.off": "معطّل",
136
+ "settings.saved": "✅ تم حفظ الإعداد.",
112
137
  "projects.empty": "📭 لم يتم العثور على مشاريع.\n\nافتح مجلدًا في OpenCode وأنشئ جلسة واحدة على الأقل، ثم سيظهر المشروع هنا.",
113
138
  "projects.select": "اختر مشروعًا:",
114
139
  "projects.select_with_current": "اختر مشروعًا:\n\nالمشروع الحالي: 🏗 {project}",
@@ -222,8 +247,8 @@ export const ar = {
222
247
  "model.menu.error": "🔴 تعذر تحميل قائمة النماذج",
223
248
  "model.search.button": "🔍 بحث",
224
249
  "model.search.prompt": "🔍 اكتب اسم النموذج للبحث عنه:",
225
- "model.search.results_title": "نتائج البحث عن \"{query}\":",
226
- "model.search.no_results": "لم يتم العثور على نماذج مطابقة لـ \"{query}\"",
250
+ "model.search.results_title": 'نتائج البحث عن "{query}":',
251
+ "model.search.no_results": 'لم يتم العثور على نماذج مطابقة لـ "{query}"',
227
252
  "model.search.search_again": "↩ البحث مرة أخرى",
228
253
  "model.search.error": "تعذر البحث عن النماذج",
229
254
  "variant.model_not_selected_callback": "خطأ: لم يتم تحديد النموذج",
package/dist/i18n/de.js CHANGED
@@ -5,7 +5,7 @@ export const de = {
5
5
  "cmd.description.detach": "Von aktueller Sitzung trennen",
6
6
  "cmd.description.sessions": "Sitzungen auflisten",
7
7
  "cmd.description.messages": "Sitzungsnachrichten durchsuchen",
8
- "cmd.description.tts": "Audioantworten umschalten",
8
+ "cmd.description.settings": "Bot-Einstellungen ändern",
9
9
  "cmd.description.projects": "Projekte auflisten",
10
10
  "cmd.description.worktree": "Git-Worktrees wechseln",
11
11
  "cmd.description.task": "Geplante Aufgabe erstellen",
@@ -35,6 +35,7 @@ export const de = {
35
35
  "question.blocked.expected_answer": "⚠️ Beantworte die aktuelle Frage über Buttons, Eigene Antwort oder Abbrechen.",
36
36
  "question.blocked.command_not_allowed": "⚠️ Dieser Befehl ist erst verfügbar, wenn der aktuelle Frage-Flow abgeschlossen ist.",
37
37
  "inline.button.cancel": "❌ Abbrechen",
38
+ "inline.button.close": "❌ Schließen",
38
39
  "inline.inactive_callback": "Dieses Menü ist inaktiv",
39
40
  "inline.cancelled_callback": "Abgebrochen",
40
41
  "common.unknown": "unbekannt",
@@ -43,6 +44,16 @@ export const de = {
43
44
  "help.keyboard_hint": "💡 Nutze die unteren Buttons für Agent, Modell, Variante und Kontextaktionen.",
44
45
  "help.text": "📖 **Hilfe**\n\n/status - Serverstatus prüfen\n/sessions - Sitzungsliste\n/new - Neue Sitzung erstellen\n/help - Hilfe",
45
46
  "bot.thinking": "💭 Denke...",
47
+ "progress.compact.activity": "{header}\n{activity}",
48
+ "progress.compact.working_header": "⏳ Arbeite",
49
+ "progress.compact.finished_header": "✅ Arbeit abgeschlossen",
50
+ "progress.compact.thinking": "💭 Denke...",
51
+ "progress.compact.responding": "✍️ Schreibe Antwort...",
52
+ "progress.compact.waiting_question": "❓ Warte auf deine Antwort...",
53
+ "progress.compact.waiting_permission": "🔐 Warte auf Berechtigung...",
54
+ "progress.compact.retrying": "🔁 Wiederhole...",
55
+ "progress.compact.task": "🤖 Aufgabe läuft",
56
+ "progress.compact.done": "{header}\nTool-Aufrufe: {tools} · geänderte Dateien: {files}",
46
57
  "bot.project_not_selected": "🏗 Projekt ist nicht ausgewählt.\n\nWähle zuerst ein Projekt mit /projects.",
47
58
  "bot.creating_session": "🔄 Erstelle eine neue Sitzung...",
48
59
  "bot.create_session_error": "🔴 Sitzung konnte nicht erstellt werden. Versuche /new oder prüfe den Serverstatus mit /status.",
@@ -84,9 +95,10 @@ export const de = {
84
95
  "status.line.uptime_sec": "Betriebszeit: {seconds} s",
85
96
  "status.line.mode": "Agent: {mode}",
86
97
  "status.line.model": "Modell: {model}",
87
- "status.line.tts": "TTS-Antworten: {tts}",
88
- "status.tts.on": "Ein",
98
+ "status.line.tts": "Audioantworten: {tts}",
89
99
  "status.tts.off": "Aus",
100
+ "status.tts.all": "Alle",
101
+ "status.tts.auto": "Auto",
90
102
  "status.agent_not_set": "nicht gesetzt",
91
103
  "status.project_selected": "Projekt: {project}",
92
104
  "status.worktree_selected": "Worktree: {worktree}",
@@ -96,10 +108,23 @@ export const de = {
96
108
  "status.session_not_selected": "Aktuelle Sitzung: nicht ausgewählt",
97
109
  "status.session_hint": "Nutze /sessions zur Auswahl oder /new zum Erstellen",
98
110
  "status.server_unavailable": "🔴 OpenCode-Server ist nicht verfügbar\n\nNutze /opencode_start, um den Server zu starten.",
99
- "tts.enabled": "🔊 Audioantworten global aktiviert.",
111
+ "tts.off": "🔇 Audioantworten deaktiviert.",
112
+ "tts.all": "🔊 Audioantworten für alle Nachrichten aktiviert.",
113
+ "tts.auto": "🎤 Audioantworten nur für Sprachnachrichten aktiviert.",
100
114
  "tts.not_configured": "⚠️ Audioantworten sind nicht verfugbar. Setze zuerst `TTS_API_URL` und `TTS_API_KEY`.",
101
- "tts.disabled": "🔇 Audioantworten global deaktiviert.",
102
115
  "tts.failed": "⚠️ Audioreply konnte nicht erzeugt werden.",
116
+ "settings.menu.title": "⚙️ Bot-Einstellungen\nTippen Sie auf eine Einstellung, um ihren Wert umzuschalten:",
117
+ "settings.compact_output.label": "Kompakte Ausgabe",
118
+ "settings.thinking_content.label": "Thinking-Inhalt",
119
+ "settings.response_streaming.label": "Antwort-Streaming",
120
+ "settings.response_streaming.edit": "edit",
121
+ "settings.response_streaming.draft": "draft (experimental)",
122
+ "settings.diff_files.label": "Diff-Dateien",
123
+ "settings.assistant_footer.label": "Antwort-Footer",
124
+ "settings.tts.label": "Audioantworten",
125
+ "settings.value.on": "Ein",
126
+ "settings.value.off": "Aus",
127
+ "settings.saved": "✅ Einstellung gespeichert.",
103
128
  "projects.empty": "📭 Keine Projekte gefunden.\n\nÖffne ein Verzeichnis in OpenCode und erstelle mindestens eine Sitzung, dann erscheint es hier.",
104
129
  "projects.select": "Projekt auswählen:",
105
130
  "projects.select_with_current": "Projekt auswählen:\n\nAktuell: 🏗 {project}",
@@ -213,8 +238,8 @@ export const de = {
213
238
  "model.menu.error": "🔴 Modellliste konnte nicht geladen werden",
214
239
  "model.search.button": "🔍 Suche",
215
240
  "model.search.prompt": "🔍 Modellnamen zum Suchen eingeben:",
216
- "model.search.results_title": "Suchergebnisse für \"{query}\":",
217
- "model.search.no_results": "Keine Modelle gefunden für \"{query}\"",
241
+ "model.search.results_title": 'Suchergebnisse für "{query}":',
242
+ "model.search.no_results": 'Keine Modelle gefunden für "{query}"',
218
243
  "model.search.search_again": "↩ Erneut suchen",
219
244
  "model.search.error": "Suche fehlgeschlagen",
220
245
  "variant.model_not_selected_callback": "Fehler: Modell ist nicht ausgewählt",
package/dist/i18n/en.js CHANGED
@@ -5,7 +5,7 @@ export const en = {
5
5
  "cmd.description.detach": "Detach from current session",
6
6
  "cmd.description.sessions": "List sessions",
7
7
  "cmd.description.messages": "Browse session messages",
8
- "cmd.description.tts": "Toggle audio replies",
8
+ "cmd.description.settings": "Change bot settings",
9
9
  "cmd.description.projects": "List projects",
10
10
  "cmd.description.worktree": "Switch git worktrees",
11
11
  "cmd.description.task": "Create a scheduled task",
@@ -35,6 +35,7 @@ export const en = {
35
35
  "question.blocked.expected_answer": "⚠️ Answer the current question using buttons, Custom answer, or Cancel.",
36
36
  "question.blocked.command_not_allowed": "⚠️ This command is not available until current question flow is completed.",
37
37
  "inline.button.cancel": "❌ Cancel",
38
+ "inline.button.close": "❌ Close",
38
39
  "inline.inactive_callback": "This menu is inactive",
39
40
  "inline.cancelled_callback": "Cancelled",
40
41
  "common.unknown": "unknown",
@@ -43,6 +44,16 @@ export const en = {
43
44
  "help.keyboard_hint": "💡 Use the bottom keyboard buttons for the agent, model, variant, and context actions.",
44
45
  "help.text": "📖 **Help**\n\n/status - Check server status\n/sessions - Session list\n/new - Create new session\n/help - Help",
45
46
  "bot.thinking": "💭 Thinking...",
47
+ "progress.compact.activity": "{header}\n{activity}",
48
+ "progress.compact.working_header": "⏳ Working",
49
+ "progress.compact.finished_header": "✅ Finished Work",
50
+ "progress.compact.thinking": "💭 Thinking...",
51
+ "progress.compact.responding": "✍️ Writing answer...",
52
+ "progress.compact.waiting_question": "❓ Waiting for your answer...",
53
+ "progress.compact.waiting_permission": "🔐 Waiting for permission...",
54
+ "progress.compact.retrying": "🔁 Retrying...",
55
+ "progress.compact.task": "🤖 Running Task",
56
+ "progress.compact.done": "{header}\ntool calls: {tools} · changed files: {files}",
46
57
  "bot.project_not_selected": "🏗 Project is not selected.\n\nFirst select a project with /projects.",
47
58
  "bot.creating_session": "🔄 Creating a new session...",
48
59
  "bot.create_session_error": "🔴 Failed to create session. Try /new or check server status with /status.",
@@ -84,9 +95,10 @@ export const en = {
84
95
  "status.line.uptime_sec": "Uptime: {seconds} sec",
85
96
  "status.line.mode": "Agent: {mode}",
86
97
  "status.line.model": "Model: {model}",
87
- "status.line.tts": "TTS replies: {tts}",
88
- "status.tts.on": "On",
98
+ "status.line.tts": "Audio replies: {tts}",
89
99
  "status.tts.off": "Off",
100
+ "status.tts.all": "All",
101
+ "status.tts.auto": "Auto",
90
102
  "status.agent_not_set": "not set",
91
103
  "status.project_selected": "Project: {project}",
92
104
  "status.worktree_selected": "Worktree: {worktree}",
@@ -96,10 +108,23 @@ export const en = {
96
108
  "status.session_not_selected": "Current session: not selected",
97
109
  "status.session_hint": "Use /sessions to select one or /new to create one",
98
110
  "status.server_unavailable": "🔴 OpenCode Server is unavailable\n\nUse /opencode_start to start the server.",
99
- "tts.enabled": "🔊 Audio replies enabled globally.",
111
+ "tts.off": "🔇 Audio replies disabled.",
112
+ "tts.all": "🔊 Audio replies enabled for all messages.",
113
+ "tts.auto": "🎤 Audio replies enabled for voice/audio messages only.",
100
114
  "tts.not_configured": "⚠️ Audio replies are unavailable. Set `TTS_API_URL` and `TTS_API_KEY` first.",
101
- "tts.disabled": "🔇 Audio replies disabled globally.",
102
115
  "tts.failed": "⚠️ Failed to generate audio reply.",
116
+ "settings.menu.title": "⚙️ Bot settings\nTap a setting to toggle its value:",
117
+ "settings.compact_output.label": "Compact output mode",
118
+ "settings.thinking_content.label": "Thinking content",
119
+ "settings.response_streaming.label": "Response streaming",
120
+ "settings.response_streaming.edit": "edit",
121
+ "settings.response_streaming.draft": "draft (experimental)",
122
+ "settings.diff_files.label": "Diff files",
123
+ "settings.assistant_footer.label": "Assistant footer",
124
+ "settings.tts.label": "Audio replies",
125
+ "settings.value.on": "On",
126
+ "settings.value.off": "Off",
127
+ "settings.saved": "✅ Setting saved.",
103
128
  "projects.empty": "📭 No projects found.\n\nOpen a directory in OpenCode and create at least one session, then it will appear here.",
104
129
  "projects.select": "Select a project:",
105
130
  "projects.select_with_current": "Select a project:\n\nCurrent: 🏗 {project}",
@@ -213,8 +238,8 @@ export const en = {
213
238
  "model.menu.error": "🔴 Failed to get models list",
214
239
  "model.search.button": "🔍 Search",
215
240
  "model.search.prompt": "🔍 Enter model name to search:",
216
- "model.search.results_title": "Search results for \"{query}\":",
217
- "model.search.no_results": "No models found for \"{query}\"",
241
+ "model.search.results_title": 'Search results for "{query}":',
242
+ "model.search.no_results": 'No models found for "{query}"',
218
243
  "model.search.search_again": "↩ Search again",
219
244
  "model.search.error": "Search failed",
220
245
  "variant.model_not_selected_callback": "Error: model is not selected",