opencode-telegram-group-topics-bot 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/.env.example +74 -0
  2. package/LICENSE +21 -0
  3. package/README.md +305 -0
  4. package/dist/agent/manager.js +60 -0
  5. package/dist/agent/types.js +26 -0
  6. package/dist/app/start-bot-app.js +47 -0
  7. package/dist/bot/commands/abort.js +116 -0
  8. package/dist/bot/commands/commands.js +389 -0
  9. package/dist/bot/commands/constants.js +20 -0
  10. package/dist/bot/commands/definitions.js +25 -0
  11. package/dist/bot/commands/help.js +27 -0
  12. package/dist/bot/commands/models.js +38 -0
  13. package/dist/bot/commands/new.js +247 -0
  14. package/dist/bot/commands/opencode-start.js +85 -0
  15. package/dist/bot/commands/opencode-stop.js +44 -0
  16. package/dist/bot/commands/projects.js +304 -0
  17. package/dist/bot/commands/rename.js +173 -0
  18. package/dist/bot/commands/sessions.js +491 -0
  19. package/dist/bot/commands/start.js +67 -0
  20. package/dist/bot/commands/status.js +138 -0
  21. package/dist/bot/constants.js +49 -0
  22. package/dist/bot/handlers/agent.js +127 -0
  23. package/dist/bot/handlers/context.js +125 -0
  24. package/dist/bot/handlers/document.js +65 -0
  25. package/dist/bot/handlers/inline-menu.js +124 -0
  26. package/dist/bot/handlers/model.js +152 -0
  27. package/dist/bot/handlers/permission.js +281 -0
  28. package/dist/bot/handlers/prompt.js +263 -0
  29. package/dist/bot/handlers/question.js +285 -0
  30. package/dist/bot/handlers/variant.js +147 -0
  31. package/dist/bot/handlers/voice.js +173 -0
  32. package/dist/bot/index.js +945 -0
  33. package/dist/bot/message-patterns.js +4 -0
  34. package/dist/bot/middleware/auth.js +30 -0
  35. package/dist/bot/middleware/interaction-guard.js +80 -0
  36. package/dist/bot/middleware/unknown-command.js +22 -0
  37. package/dist/bot/scope.js +222 -0
  38. package/dist/bot/telegram-constants.js +3 -0
  39. package/dist/bot/telegram-rate-limiter.js +263 -0
  40. package/dist/bot/utils/commands.js +21 -0
  41. package/dist/bot/utils/file-download.js +91 -0
  42. package/dist/bot/utils/keyboard.js +85 -0
  43. package/dist/bot/utils/send-with-markdown-fallback.js +57 -0
  44. package/dist/bot/utils/session-error-filter.js +34 -0
  45. package/dist/bot/utils/topic-link.js +29 -0
  46. package/dist/cli/args.js +98 -0
  47. package/dist/cli.js +80 -0
  48. package/dist/config.js +103 -0
  49. package/dist/i18n/de.js +330 -0
  50. package/dist/i18n/en.js +330 -0
  51. package/dist/i18n/es.js +330 -0
  52. package/dist/i18n/index.js +102 -0
  53. package/dist/i18n/ru.js +330 -0
  54. package/dist/i18n/zh.js +330 -0
  55. package/dist/index.js +28 -0
  56. package/dist/interaction/cleanup.js +24 -0
  57. package/dist/interaction/constants.js +25 -0
  58. package/dist/interaction/guard.js +100 -0
  59. package/dist/interaction/manager.js +113 -0
  60. package/dist/interaction/types.js +1 -0
  61. package/dist/keyboard/manager.js +115 -0
  62. package/dist/keyboard/types.js +1 -0
  63. package/dist/model/capabilities.js +62 -0
  64. package/dist/model/manager.js +257 -0
  65. package/dist/model/types.js +24 -0
  66. package/dist/opencode/client.js +13 -0
  67. package/dist/opencode/events.js +159 -0
  68. package/dist/opencode/prompt-submit-error.js +101 -0
  69. package/dist/permission/manager.js +92 -0
  70. package/dist/permission/types.js +1 -0
  71. package/dist/pinned/manager.js +405 -0
  72. package/dist/pinned/types.js +1 -0
  73. package/dist/process/manager.js +273 -0
  74. package/dist/process/types.js +1 -0
  75. package/dist/project/manager.js +88 -0
  76. package/dist/question/manager.js +186 -0
  77. package/dist/question/types.js +1 -0
  78. package/dist/rename/manager.js +64 -0
  79. package/dist/runtime/bootstrap.js +350 -0
  80. package/dist/runtime/mode.js +74 -0
  81. package/dist/runtime/paths.js +37 -0
  82. package/dist/runtime/process-error-handlers.js +24 -0
  83. package/dist/session/cache-manager.js +455 -0
  84. package/dist/session/manager.js +87 -0
  85. package/dist/settings/manager.js +283 -0
  86. package/dist/stt/client.js +64 -0
  87. package/dist/summary/aggregator.js +625 -0
  88. package/dist/summary/formatter.js +417 -0
  89. package/dist/summary/tool-message-batcher.js +277 -0
  90. package/dist/topic/colors.js +8 -0
  91. package/dist/topic/constants.js +10 -0
  92. package/dist/topic/manager.js +161 -0
  93. package/dist/topic/title-constants.js +2 -0
  94. package/dist/topic/title-format.js +10 -0
  95. package/dist/topic/title-sync.js +17 -0
  96. package/dist/utils/error-format.js +29 -0
  97. package/dist/utils/logger.js +175 -0
  98. package/dist/utils/safe-background-task.js +33 -0
  99. package/dist/variant/manager.js +103 -0
  100. package/dist/variant/types.js +1 -0
  101. package/package.json +76 -0
@@ -0,0 +1,330 @@
1
+ export const es = {
2
+ "cmd.description.status": "Estado del servidor y de la sesión",
3
+ "cmd.description.new": "Crear una sesión nueva",
4
+ "cmd.description.abort": "Abortar la acción actual",
5
+ "cmd.description.stop": "Detener la acción actual",
6
+ "cmd.description.sessions": "Listar sesiones",
7
+ "cmd.description.projects": "Listar proyectos",
8
+ "cmd.description.commands": "Comandos personalizados",
9
+ "cmd.description.model": "Seleccionar modelo",
10
+ "cmd.description.agent": "Seleccionar modo de agente",
11
+ "cmd.description.cleanup": "Cerrar temas obsoletos",
12
+ "cmd.description.opencode_start": "Iniciar servidor OpenCode",
13
+ "cmd.description.opencode_stop": "Detener servidor OpenCode",
14
+ "cmd.description.help": "Ayuda",
15
+ "callback.unknown_command": "Comando desconocido",
16
+ "callback.processing_error": "Error de procesamiento",
17
+ "error.load_agents": "❌ No se pudo cargar la lista de agentes",
18
+ "error.load_models": "❌ No se pudo cargar la lista de modelos",
19
+ "error.load_variants": "❌ No se pudo cargar la lista de variantes",
20
+ "error.context_button": "❌ No se pudo procesar el botón de contexto",
21
+ "error.generic": "🔴 Algo salió mal.",
22
+ "interaction.blocked.expired": "⚠️ Esta interacción ha expirado. Por favor, iníciala de nuevo.",
23
+ "interaction.blocked.expected_callback": "⚠️ Para este paso, usa los botones en línea o toca Cancelar.",
24
+ "interaction.blocked.expected_text": "⚠️ Para este paso, envía un mensaje de texto.",
25
+ "interaction.blocked.expected_command": "⚠️ Para este paso, envía un comando.",
26
+ "interaction.blocked.command_not_allowed": "⚠️ Este comando no está disponible en el paso actual.",
27
+ "interaction.blocked.finish_current": "⚠️ Termina primero la interacción actual (responde o cancela) y después abre otro menú.",
28
+ "inline.blocked.expected_choice": "⚠️ Elige una opción usando los botones en línea o toca Cancelar.",
29
+ "inline.blocked.command_not_allowed": "⚠️ Este comando no está disponible mientras el menú en línea está activo.",
30
+ "question.blocked.expected_answer": "⚠️ Responde la pregunta actual usando botones, Respuesta personalizada o Cancelar.",
31
+ "question.blocked.command_not_allowed": "⚠️ Este comando no está disponible hasta que se complete el flujo de la pregunta actual.",
32
+ "inline.button.cancel": "❌ Cancelar",
33
+ "inline.inactive_callback": "Este menú está inactivo",
34
+ "inline.cancelled_callback": "Cancelado",
35
+ "common.unknown": "desconocido",
36
+ "common.unknown_error": "error desconocido",
37
+ "start.welcome": "👋 ¡Bienvenido a OpenCode Telegram Group Topics Bot!\n\nUsa los comandos:\n/projects — seleccionar proyecto\n/sessions — lista de sesiones\n/new — sesión nueva\n/status — estado\n/help — ayuda\n\nUsa los botones inferiores para elegir modo, modelo y variante.",
38
+ "help.keyboard_hint": "💡 Usa los botones inferiores para modo del agente, modelo, variante y acciones de contexto.",
39
+ "help.text": "📖 **Ayuda**\n\n/status - Ver estado del servidor\n/sessions - Lista de sesiones\n/new - Crear una sesión nueva\n/help - Ayuda",
40
+ "bot.thinking": "💭 Pensando...",
41
+ "bot.project_not_selected": "🏗 No hay un proyecto seleccionado.\n\nPrimero selecciona un proyecto con /projects.",
42
+ "bot.creating_session": "🔄 Creando una sesión nueva...",
43
+ "bot.create_session_error": "🔴 No se pudo crear la sesión. Prueba /new o revisa el estado del servidor con /status.",
44
+ "bot.session_created": "✅ Sesión creada: {title}",
45
+ "bot.session_busy": "⏳ Tu solicitud anterior todavía está en ejecución, por eso esta nueva no se inició.\n\nPor qué pasó: OpenCode acepta solo una ejecución activa por sesión.\nQué hacer: espera la respuesta actual, o usa /abort si parece bloqueada, y luego envía el mensaje de nuevo.",
46
+ "bot.session_reset_project_mismatch": "⚠️ La sesión activa no coincide con el proyecto seleccionado, así que se reinició. Usa /sessions para elegir una o /new para crear una nueva.",
47
+ "bot.prompt_send_error": "⚠️ No pude entregar este mensaje a OpenCode.\n\nCausa probable: un problema temporal de conexión entre el bot y el servidor OpenCode.\nQué hacer: envía el mensaje otra vez. Si sigue pasando, ejecuta /status y verifica que OpenCode esté accesible.",
48
+ "bot.prompt_send_error_session_not_found": "⚠️ No pude entregar este mensaje porque la sesión activa ya no está disponible.\n\nPor qué pasó: la sesión pudo haberse reiniciado, cambiado o eliminado.\nQué hacer: selecciona una sesión con /sessions o crea una nueva con /new, y vuelve a enviar el mensaje.",
49
+ "bot.session_error": "🔴 OpenCode devolvió un error: {message}",
50
+ "bot.session_retry": "🔁 {message}\n\nEl proveedor devuelve el mismo error en intentos repetidos. Usa /abort para detenerlo.",
51
+ "bot.unknown_command": "⚠️ Comando desconocido: {command}. Usa /help para ver los comandos disponibles.",
52
+ "bot.photo_downloading": "⏳ Descargando foto...",
53
+ "bot.photo_too_large": "⚠️ La foto es demasiado grande (max {maxSizeMb}MB)",
54
+ "bot.photo_model_no_image": "⚠️ El modelo actual no admite entrada de imagen. Enviaré solo texto.",
55
+ "bot.photo_download_error": "🔴 No se pudo descargar la foto",
56
+ "bot.photo_no_caption": "💡 Consejo: agrega un pie de foto para describir que quieres hacer con esta foto.",
57
+ "bot.file_downloading": "⏳ Descargando archivo...",
58
+ "bot.file_too_large": "⚠️ El archivo es demasiado grande (max {maxSizeMb}MB)",
59
+ "bot.file_download_error": "🔴 No se pudo descargar el archivo",
60
+ "bot.model_no_pdf": "⚠️ El modelo actual no admite entrada PDF. Enviaré solo texto.",
61
+ "bot.text_file_too_large": "⚠️ El archivo de texto es demasiado grande (max {maxSizeKb}KB)",
62
+ "status.header_running": "🟢 **OpenCode Server está en ejecución**",
63
+ "status.health.healthy": "Saludable",
64
+ "status.health.unhealthy": "No saludable",
65
+ "status.line.health": "Estado: {health}",
66
+ "status.line.version": "Versión: {version}",
67
+ "status.line.managed_yes": "Administrado por el bot: Sí",
68
+ "status.line.managed_no": "Administrado por el bot: No",
69
+ "status.line.pid": "PID: {pid}",
70
+ "status.line.uptime_sec": "Tiempo activo: {seconds} s",
71
+ "status.line.mode": "Modo: {mode}",
72
+ "status.line.model": "Modelo: {model}",
73
+ "status.agent_not_set": "no configurado",
74
+ "status.project_selected": "🏗 Proyecto: {project}",
75
+ "status.project_not_selected": "🏗 Proyecto: no seleccionado",
76
+ "status.project_hint": "Usa /projects para seleccionar un proyecto",
77
+ "status.session_selected": "📋 Sesión actual: {title}",
78
+ "status.session_not_selected": "📋 Sesión actual: no seleccionada",
79
+ "status.session_hint": "Usa /sessions para elegir una o /new para crear una",
80
+ "status.server_unavailable": "🔴 OpenCode Server no está disponible\n\nUsa /opencode_start para iniciar el servidor.",
81
+ "projects.empty": "📭 No se encontraron proyectos.\n\nAbre un directorio en OpenCode y crea al menos una sesión; entonces aparecerá aquí.",
82
+ "projects.select": "Selecciona un proyecto:",
83
+ "projects.select_with_current": "Selecciona un proyecto:\n\nActual: 🏗 {project}",
84
+ "projects.page_indicator": "Página {current}/{total}",
85
+ "projects.prev_page": "⬅️ Anterior",
86
+ "projects.next_page": "Siguiente ➡️",
87
+ "projects.fetch_error": "🔴 OpenCode Server no está disponible u ocurrió un error al cargar los proyectos.",
88
+ "projects.page_load_error": "No se puede cargar esta página. Inténtalo de nuevo.",
89
+ "projects.selected": "✅ Proyecto seleccionado: {project}\n\n📋 La sesión se reinició. Usa /sessions o /new para este proyecto.",
90
+ "projects.select_error": "🔴 No se pudo seleccionar el proyecto.",
91
+ "projects.locked.topic_scope": "⚠️ Este tema está vinculado a su propio ámbito de proyecto/sesión. Cambia de proyecto solo desde General antes de crear temas.",
92
+ "projects.locked.group_project": "⚠️ Este grupo ya está configurado para el proyecto: {project}. Crea un grupo nuevo si quieres trabajar en otro repositorio.",
93
+ "projects.locked.callback": "El cambio de proyecto está bloqueado para este grupo.",
94
+ "sessions.project_not_selected": "🏗 No hay un proyecto seleccionado.\n\nPrimero selecciona un proyecto con /projects.",
95
+ "sessions.empty": "📭 No se encontraron sesiones.\n\nCrea una sesión nueva con /new.",
96
+ "sessions.select": "Selecciona una sesión:",
97
+ "sessions.select_page": "Selecciona una sesión (página {page}):",
98
+ "sessions.fetch_error": "🔴 OpenCode Server no está disponible u ocurrió un error al cargar las sesiones.",
99
+ "sessions.select_project_first": "🔴 No hay un proyecto seleccionado. Usa /projects.",
100
+ "sessions.page_empty_callback": "No hay sesiones en esta página",
101
+ "sessions.page_load_error_callback": "No se puede cargar esta página. Inténtalo de nuevo.",
102
+ "sessions.button.prev_page": "⬅️ Anterior",
103
+ "sessions.button.next_page": "Siguiente ➡️",
104
+ "sessions.topic_locked": "⚠️ Este tema está vinculado a su sesión actual. Usa /new en General para crear otro tema.",
105
+ "sessions.general_overview": "Resumen de sesiones por tema:",
106
+ "sessions.general_item": "• {topic} (hilo #{thread}) - {status}",
107
+ "sessions.general_empty": "Aún no hay temas de sesión. Usa /new para crear uno.",
108
+ "sessions.bound_topic_link": "🔗 Tema de esta sesión: {url}",
109
+ "sessions.created_topic_link": "✅ Tema creado para esta sesión: {url}",
110
+ "sessions.loading_context": "⏳ Cargando contexto y los últimos mensajes...",
111
+ "sessions.selected": "✅ Sesión seleccionada: {title}",
112
+ "sessions.select_error": "🔴 No se pudo seleccionar la sesión.",
113
+ "sessions.preview.empty": "No hay mensajes recientes.",
114
+ "sessions.preview.title": "Mensajes recientes:",
115
+ "sessions.preview.you": "Tú:",
116
+ "sessions.preview.agent": "Agente:",
117
+ "new.project_not_selected": "🏗 No hay un proyecto seleccionado.\n\nPrimero selecciona un proyecto con /projects.",
118
+ "new.created": "✅ Sesión nueva creada: {title}",
119
+ "new.topic_only_in_general": "⚠️ Ejecuta /new desde el tema General para crear un tema de sesión dedicado.",
120
+ "new.requires_forum_general": "⚠️ /new requiere el tema General en un supergrupo con foros habilitados.",
121
+ "new.topic_created": "✅ El tema de sesión está listo: {title}",
122
+ "new.general_created": "✅ Se creó una nueva sesión de OpenCode y un tema de grupo.",
123
+ "new.topic_create_error": "🔴 No se pudo crear el tema de sesión. Verifica permisos de foro e inténtalo de nuevo.",
124
+ "new.topic_create_no_rights": "🔴 No puedo crear temas del foro en este grupo. Otorga al bot el permiso de gestionar temas (Manage Topics) y vuelve a ejecutar /new.",
125
+ "new.general_open_link": "🔗 Abrir tema: {url}",
126
+ "new.create_error": "🔴 OpenCode Server no está disponible u ocurrió un error al crear la sesión.",
127
+ "cleanup.topic_use_general": "⚠️ Ejecuta /cleanup desde el tema General.",
128
+ "cleanup.requires_forum_general": "⚠️ /cleanup solo está disponible en el tema General de un supergrupo con foros habilitados.",
129
+ "cleanup.no_topics": "✅ No hay temas de sesión para limpiar.",
130
+ "cleanup.result": "🧹 Limpieza completa. Revisados: {inspected}, cerrados: {closed}, omitidos: {skipped}, fallidos: {failed}.",
131
+ "stop.no_active_session": "🛑 El agente no se inició\n\nCrea una sesión con /new o selecciona una con /sessions.",
132
+ "stop.in_progress": "🛑 Flujo de eventos detenido; enviando señal de aborto...\n\nEsperando a que el agente se detenga.",
133
+ "stop.warn_unconfirmed": "⚠️ Flujo de eventos detenido, pero el servidor no confirmó el aborto.\n\nRevisa /status y vuelve a intentar /abort en unos segundos.",
134
+ "stop.warn_maybe_finished": "⚠️ Flujo de eventos detenido, pero el agente podría haber terminado ya.",
135
+ "stop.success": "✅ Acción del agente interrumpida. No se enviarán más mensajes de esta ejecución.",
136
+ "stop.warn_still_busy": "⚠️ Señal enviada, pero el agente sigue ocupado.\n\nEl flujo de eventos ya está deshabilitado, así que no se enviarán mensajes intermedios.",
137
+ "stop.warn_timeout": "⚠️ Tiempo de espera agotado al solicitar el aborto.\n\nEl flujo de eventos ya está deshabilitado; vuelve a intentar /abort en unos segundos.",
138
+ "stop.warn_local_only": "⚠️ Flujo de eventos detenido localmente, pero el aborto en el servidor falló.",
139
+ "stop.error": "🔴 No se pudo detener la acción.\n\nEl flujo de eventos está detenido; prueba /abort otra vez.",
140
+ "opencode_start.already_running_managed": "⚠️ OpenCode Server ya está en ejecución\n\nPID: {pid}\nTiempo activo: {seconds} segundos",
141
+ "opencode_start.already_running_external": "✅ OpenCode Server ya está en ejecución como un proceso externo\n\nVersión: {version}\n\nEste servidor no fue iniciado por el bot, por lo que /opencode-stop no puede detenerlo.",
142
+ "opencode_start.starting": "🔄 Iniciando OpenCode Server...",
143
+ "opencode_start.start_error": "🔴 No se pudo iniciar OpenCode Server\n\nError: {error}\n\nRevisa que OpenCode CLI esté instalado y disponible en PATH:\n`opencode --version`\n`npm install -g @opencode-ai/cli`",
144
+ "opencode_start.started_not_ready": "⚠️ OpenCode Server se inició, pero no responde\n\nPID: {pid}\n\nEl servidor puede estar iniciando. Prueba /status en unos segundos.",
145
+ "opencode_start.success": "✅ OpenCode Server iniciado correctamente\n\nPID: {pid}\nVersión: {version}",
146
+ "opencode_start.error": "🔴 Ocurrió un error al iniciar el servidor.\n\nRevisa los logs de la aplicación para más detalles.",
147
+ "opencode_stop.external_running": "⚠️ OpenCode Server está en ejecución como un proceso externo\n\nEste servidor no fue iniciado con /opencode-start.\nDeténlo manualmente o usa /status para revisar el estado.",
148
+ "opencode_stop.not_running": "⚠️ OpenCode Server no está en ejecución",
149
+ "opencode_stop.stopping": "🛑 Deteniendo OpenCode Server...\n\nPID: {pid}",
150
+ "opencode_stop.stop_error": "🔴 No se pudo detener OpenCode Server\n\nError: {error}",
151
+ "opencode_stop.success": "✅ OpenCode Server detenido correctamente",
152
+ "opencode_stop.error": "🔴 Ocurrió un error al detener el servidor.\n\nRevisa los logs de la aplicación para más detalles.",
153
+ "agent.changed_callback": "Modo cambiado: {name}",
154
+ "agent.changed_message": "✅ Modo cambiado a: {name}",
155
+ "agent.change_error_callback": "No se pudo cambiar el modo",
156
+ "agent.menu.current": "Modo actual: {name}\n\nSelecciona el modo:",
157
+ "agent.menu.select": "Selecciona el modo de trabajo:",
158
+ "agent.menu.empty": "⚠️ No hay agentes disponibles",
159
+ "agent.menu.error": "🔴 No se pudo obtener la lista de agentes",
160
+ "model.changed_callback": "Modelo cambiado: {name}",
161
+ "model.changed_message": "✅ Modelo cambiado a: {name}",
162
+ "model.change_error_callback": "No se pudo cambiar el modelo",
163
+ "model.menu.empty": "⚠️ No hay modelos disponibles",
164
+ "model.menu.select": "Selecciona el modelo:",
165
+ "model.menu.current": "Modelo actual: {name}\n\nSelecciona el modelo:",
166
+ "model.menu.favorites_title": "⭐ Favoritos (Agrega modelos a favoritos en OpenCode CLI)",
167
+ "model.menu.favorites_empty": "— Vacío.",
168
+ "model.menu.recent_title": "🕘 Recientes",
169
+ "model.menu.recent_empty": "— Vacío.",
170
+ "model.menu.favorites_hint": "ℹ️ Agrega modelos a favoritos en OpenCode CLI para mantenerlos arriba de la lista.",
171
+ "model.menu.error": "🔴 No se pudo obtener la lista de modelos",
172
+ "variant.model_not_selected_callback": "Error: no hay un modelo seleccionado",
173
+ "variant.changed_callback": "Variante cambiada: {name}",
174
+ "variant.changed_message": "✅ Variante cambiada a: {name}",
175
+ "variant.change_error_callback": "No se pudo cambiar la variante",
176
+ "variant.select_model_first": "⚠️ Selecciona un modelo primero",
177
+ "variant.menu.empty": "⚠️ No hay variantes disponibles",
178
+ "variant.menu.current": "Variante actual: {name}\n\nSelecciona la variante:",
179
+ "variant.menu.error": "🔴 No se pudo obtener la lista de variantes",
180
+ "context.button.confirm": "✅ Sí, compactar contexto",
181
+ "context.no_active_session": "⚠️ No hay una sesión activa. Crea una sesión con /new",
182
+ "context.confirm_text": '📊 Compactación de contexto para la sesión "{title}"\n\nEsto reducirá el uso de contexto eliminando mensajes antiguos del historial. La tarea actual no se interrumpirá.\n\n¿Continuar?',
183
+ "context.general_not_available": "⚠️ La compactación de contexto solo está disponible dentro de un tema de sesión, no en General.",
184
+ "context.general_not_available_callback": "Primero abre un tema de sesión.",
185
+ "context.callback_session_not_found": "Sesión no encontrada",
186
+ "context.callback_compacting": "Compactando contexto...",
187
+ "context.progress": "⏳ Compactando contexto...",
188
+ "context.error": "❌ La compactación de contexto falló",
189
+ "context.success": "✅ Contexto compactado correctamente",
190
+ "permission.inactive_callback": "La solicitud de permisos está inactiva",
191
+ "permission.processing_error_callback": "Error de procesamiento",
192
+ "permission.no_active_request_callback": "Error: no hay una solicitud activa",
193
+ "permission.reply.once": "Permitido una vez",
194
+ "permission.reply.always": "Siempre permitido",
195
+ "permission.reply.reject": "Rechazado",
196
+ "permission.send_reply_error": "❌ No se pudo enviar la respuesta de permisos",
197
+ "permission.blocked.expected_reply": "⚠️ Primero responde a la solicitud de permisos usando los botones de arriba.",
198
+ "permission.blocked.command_not_allowed": "⚠️ Este comando no está disponible hasta que respondas a la solicitud de permisos.",
199
+ "permission.header": "{emoji} **Solicitud de permisos: {name}**\n\n",
200
+ "permission.button.allow": "✅ Permitir una vez",
201
+ "permission.button.always": "🔓 Permitir siempre",
202
+ "permission.button.reject": "❌ Rechazar",
203
+ "permission.name.bash": "Bash",
204
+ "permission.name.edit": "Editar",
205
+ "permission.name.write": "Escribir",
206
+ "permission.name.read": "Leer",
207
+ "permission.name.webfetch": "Obtener web",
208
+ "permission.name.websearch": "Buscar en la web",
209
+ "permission.name.glob": "Buscar archivos",
210
+ "permission.name.grep": "Buscar contenido",
211
+ "permission.name.list": "Listar directorio",
212
+ "permission.name.task": "Tarea",
213
+ "permission.name.lsp": "LSP",
214
+ "question.inactive_callback": "La encuesta está inactiva",
215
+ "question.processing_error_callback": "Error de procesamiento",
216
+ "question.select_one_required_callback": "Selecciona al menos una opción",
217
+ "question.enter_custom_callback": "Envía tu respuesta personalizada como mensaje",
218
+ "question.cancelled": "❌ Encuesta cancelada",
219
+ "question.answer_already_received": "Respuesta ya recibida, espera...",
220
+ "question.completed_no_answers": "✅ Encuesta completada (sin respuestas)",
221
+ "question.no_active_project": "❌ No hay un proyecto activo",
222
+ "question.no_active_request": "❌ No hay una solicitud activa",
223
+ "question.send_answers_error": "❌ No se pudieron enviar las respuestas al agente",
224
+ "question.multi_hint": "\n*Puedes seleccionar varias opciones*",
225
+ "question.button.submit": "✅ Listo",
226
+ "question.button.custom": "🔤 Respuesta personalizada",
227
+ "question.button.cancel": "❌ Cancelar",
228
+ "question.use_custom_button_first": '⚠️ Para enviar texto, primero toca "Respuesta personalizada" para la pregunta actual.',
229
+ "question.summary.title": "✅ ¡Encuesta completada!\n\n",
230
+ "question.summary.question": "Pregunta {index}:\n{question}\n\n",
231
+ "question.summary.answer": "Respuesta:\n{answer}\n\n",
232
+ "keyboard.agent_mode": "{emoji} Modo {name}",
233
+ "keyboard.context": "📊 {used} / {limit} ({percent}%)",
234
+ "keyboard.context_empty": "📊 Controles",
235
+ "keyboard.general_defaults": "Valores por defecto de sesión:",
236
+ "keyboard.general_defaults_info": "Estos valores se aplican a las sesiones nuevas creadas en este grupo:\n• Agente\n• Modelo\n• Variante",
237
+ "keyboard.variant": "💭 {name}",
238
+ "keyboard.variant_default": "💡 Predeterminado",
239
+ "keyboard.updated": "⌨️ Teclado actualizado",
240
+ "keyboard.dm.status": "/status",
241
+ "keyboard.dm.help": "/help",
242
+ "keyboard.dm.opencode_start": "/opencode_start",
243
+ "keyboard.dm.opencode_stop": "/opencode_stop",
244
+ "pinned.default_session_title": "sesión nueva",
245
+ "pinned.unknown": "Desconocido",
246
+ "pinned.line.project": "Proyecto: {project}",
247
+ "pinned.line.model": "Modelo: {model}",
248
+ "pinned.line.context": "Contexto: {used} / {limit} ({percent}%)",
249
+ "pinned.files.title": "Archivos ({count}):",
250
+ "pinned.files.item": " {path}{diff}",
251
+ "pinned.files.more": " ... y {count} más",
252
+ "tool.todo.overflow": "*({count} tareas más)*",
253
+ "tool.file_header.write": "Escribir archivo/ruta: {path}\n============================================================\n\n",
254
+ "tool.file_header.edit": "Editar archivo/ruta: {path}\n============================================================\n\n",
255
+ "runtime.wizard.ask_token": "Introduce el token del bot de Telegram (obtenlo de @BotFather).\n> ",
256
+ "runtime.wizard.ask_language": "Selecciona el idioma de la interfaz.\nIntroduce el número del idioma de la lista o el código de locale.\nPulsa Enter para mantener el idioma por defecto: {defaultLocale}\n{options}\n> ",
257
+ "runtime.wizard.language_invalid": "Introduce un número de idioma de la lista o un código de locale compatible.\n",
258
+ "runtime.wizard.language_selected": "Idioma seleccionado: {language}\n",
259
+ "runtime.wizard.token_required": "El token es obligatorio. Inténtalo de nuevo.\n",
260
+ "runtime.wizard.token_invalid": "El token parece inválido (se espera el formato <id>:<secret>). Inténtalo de nuevo.\n",
261
+ "runtime.wizard.ask_user_id": "Introduce tu Telegram User ID (puedes obtenerlo de @userinfobot).\n> ",
262
+ "runtime.wizard.user_id_invalid": "Introduce un entero positivo (> 0).\n",
263
+ "runtime.wizard.ask_api_url": "Introduce la URL de la API de OpenCode (opcional).\nPulsa Enter para usar el valor por defecto: {defaultUrl}\n> ",
264
+ "runtime.wizard.ask_server_username": "Introduce el nombre de usuario del servidor de OpenCode (opcional).\nPulsa Enter para usar el valor por defecto: {defaultUsername}\n> ",
265
+ "runtime.wizard.ask_server_password": "Introduce la contraseña del servidor de OpenCode (opcional, entrada oculta).\nPulsa Enter para omitirla.\n> ",
266
+ "runtime.wizard.api_url_invalid": "Introduce una URL válida (http/https) o pulsa Enter para usar el valor por defecto.\n",
267
+ "runtime.wizard.start": "Configuración de OpenCode Telegram Group Topics Bot.\n",
268
+ "runtime.wizard.saved": "Configuración guardada:\n- {envPath}\n- {settingsPath}\n",
269
+ "runtime.wizard.not_configured_starting": "La aplicación aún no está configurada. Iniciando el asistente...\n",
270
+ "runtime.wizard.tty_required": "El asistente interactivo requiere un terminal TTY. Ejecuta `opencode-telegram-group-topics-bot config` en una shell interactiva.",
271
+ "rename.no_session": "⚠️ No hay una sesión activa. Crea o selecciona una sesión primero.",
272
+ "rename.prompt": "📝 Introduce un nuevo título para la sesión:\n\nActual: {title}",
273
+ "rename.empty_title": "⚠️ El título no puede estar vacío.",
274
+ "rename.success": "✅ Sesión renombrada a: {title}",
275
+ "rename.error": "🔴 No se pudo renombrar la sesión.",
276
+ "rename.cancelled": "❌ Cambio de nombre cancelado.",
277
+ "rename.inactive_callback": "La solicitud de cambio de nombre está inactiva",
278
+ "rename.inactive": "⚠️ La solicitud de cambio de nombre no está activa. Ejecuta /rename otra vez.",
279
+ "rename.blocked.expected_name": "⚠️ Introduce el nuevo nombre de la sesión como texto o toca Cancelar en el mensaje de cambio de nombre.",
280
+ "rename.blocked.command_not_allowed": "⚠️ Este comando no está disponible mientras el cambio de nombre espera un nuevo nombre.",
281
+ "rename.button.cancel": "❌ Cancelar",
282
+ "commands.select": "Elige un comando de OpenCode:",
283
+ "commands.empty": "📭 No hay comandos de OpenCode disponibles para este proyecto.",
284
+ "commands.fetch_error": "🔴 No se pudieron cargar los comandos de OpenCode.",
285
+ "commands.no_description": "Sin descripción",
286
+ "commands.button.execute": "✅ Ejecutar",
287
+ "commands.button.cancel": "❌ Cancelar",
288
+ "commands.confirm": "Confirma la ejecución del comando {command}. Para ejecutarlo con argumentos, envía los argumentos como mensaje.",
289
+ "commands.inactive_callback": "Este menú de comandos está inactivo",
290
+ "commands.cancelled_callback": "Cancelado",
291
+ "commands.execute_callback": "Ejecutando comando...",
292
+ "commands.executing_prefix": "⚡ Ejecutando comando:",
293
+ "commands.arguments_empty": "⚠️ Los argumentos no pueden estar vacíos. Envía texto o toca Ejecutar.",
294
+ "commands.execute_error": "🔴 No se pudo ejecutar el comando de OpenCode.",
295
+ "cmd.description.rename": "Renombrar la sesión actual",
296
+ "cli.usage": "Uso:\n opencode-telegram-group-topics-bot [start] [--mode sources|installed]\n opencode-telegram-group-topics-bot status\n opencode-telegram-group-topics-bot stop\n opencode-telegram-group-topics-bot config [--mode sources|installed]\n\nNotas:\n - Sin comando, el valor por defecto es `start`\n - `config` usa el modo `installed` por defecto salvo que se indique `--mode sources`",
297
+ "cli.placeholder.status": "El comando `status` es actualmente un marcador de posición. Las comprobaciones reales de estado se agregarán en la capa de servicio (Fase 5).",
298
+ "cli.placeholder.stop": "El comando `stop` es actualmente un marcador de posición. La detención real del proceso en segundo plano se agregará en la capa de servicio (Fase 5).",
299
+ "cli.placeholder.unavailable": "El comando no esta disponible.",
300
+ "cli.error.prefix": "Error de CLI: {message}",
301
+ "cli.args.unknown_command": "Comando desconocido: {value}",
302
+ "cli.args.mode_requires_value": "La opción --mode requiere un valor: sources|installed",
303
+ "cli.args.invalid_mode": "Valor de --mode inválido: {value}. Se espera sources|installed",
304
+ "cli.args.unknown_option": "Opción desconocida: {value}",
305
+ "cli.args.mode_only_start": "La opción --mode solo se admite para los comandos start y config",
306
+ "legacy.models.fetch_error": "🔴 No se pudo obtener la lista de modelos. Revisa el estado del servidor con /status.",
307
+ "legacy.models.empty": "📋 No hay modelos disponibles. Configura los proveedores en OpenCode.",
308
+ "legacy.models.header": "📋 **Modelos disponibles:**\n\n",
309
+ "legacy.models.no_provider_models": " ⚠️ No hay modelos disponibles\n",
310
+ "legacy.models.env_hint": "💡 Para usar el modelo en .env:\n",
311
+ "legacy.models.error": "🔴 Ocurrió un error al cargar la lista de modelos.",
312
+ "stt.recognizing": "🎤 Reconociendo audio...",
313
+ "stt.recognized": "🎤 Reconocido:\n{text}",
314
+ "stt.not_configured": "🎤 El reconocimiento de voz no está configurado.\n\nConfigura STT_API_URL y STT_API_KEY en .env para habilitarlo.",
315
+ "stt.error": "🔴 No se pudo reconocer el audio: {error}",
316
+ "stt.empty_result": "🎤 No se detectó voz en el mensaje de audio.",
317
+ "start.welcome_dm": "👋 El modo de DM está limitado a comandos de estado/control del bot y servidor.\n\nUsa un hilo de tema en grupo para trabajar con proyectos y sesiones.",
318
+ "status.global_overview": "📈 Resumen global",
319
+ "status.global_projects": "Proyectos: {count}",
320
+ "status.global_sessions": "Sesiones: {count}",
321
+ "dm.restricted.command": "⚠️ Los comandos de control de sesión están deshabilitados en DM. Usa un hilo de tema en grupo para trabajar con proyectos/sesiones.",
322
+ "dm.restricted.prompt": "⚠️ Los prompts están deshabilitados en DM. Usa un hilo de tema en grupo para ejecutar tareas de OpenCode.",
323
+ "help.dm.title": "Comandos de control en DM",
324
+ "help.dm.command_start": "mostrar guía del modo DM",
325
+ "help.dm.hint": "Usa hilos de tema en grupo para trabajo de proyecto/sesión.",
326
+ "status.dm.title": "Resumen de estado en DM",
327
+ "status.dm.hint": "Usa hilos de tema en grupo para ejecutar sesiones de OpenCode.",
328
+ "group.general.prompts_disabled": "⚠️ Los prompts están deshabilitados en el tema General. Usa /new para crear un tema de sesión dedicado.",
329
+ "topic.unbound": "⚠️ Este tema no está vinculado a ninguna sesión. Ve al tema General y ejecuta /new.",
330
+ };
@@ -0,0 +1,102 @@
1
+ import { en } from "./en.js";
2
+ import { de } from "./de.js";
3
+ import { es } from "./es.js";
4
+ import { ru } from "./ru.js";
5
+ import { zh } from "./zh.js";
6
+ const LOCALE_DEFINITIONS = [
7
+ {
8
+ code: "en",
9
+ label: "English",
10
+ dateLocale: "en-US",
11
+ dictionary: en,
12
+ },
13
+ {
14
+ code: "de",
15
+ label: "Deutsch",
16
+ dateLocale: "de-DE",
17
+ dictionary: de,
18
+ },
19
+ {
20
+ code: "es",
21
+ label: "Español",
22
+ dateLocale: "es-ES",
23
+ dictionary: es,
24
+ },
25
+ {
26
+ code: "ru",
27
+ label: "Русский",
28
+ dateLocale: "ru-RU",
29
+ dictionary: ru,
30
+ },
31
+ {
32
+ code: "zh",
33
+ label: "简体中文",
34
+ dateLocale: "zh-CN",
35
+ dictionary: zh,
36
+ },
37
+ ];
38
+ const DEFAULT_LOCALE = "en";
39
+ export const SUPPORTED_LOCALES = LOCALE_DEFINITIONS.map((definition) => definition.code);
40
+ const localeDefinitionByCode = Object.fromEntries(LOCALE_DEFINITIONS.map((definition) => [definition.code, definition]));
41
+ let runtimeLocaleOverride = null;
42
+ export function resolveSupportedLocale(locale) {
43
+ const normalized = locale?.trim().toLowerCase();
44
+ if (!normalized) {
45
+ return null;
46
+ }
47
+ if (Object.hasOwn(localeDefinitionByCode, normalized)) {
48
+ return normalized;
49
+ }
50
+ const baseLocale = normalized.split("-")[0];
51
+ if (Object.hasOwn(localeDefinitionByCode, baseLocale)) {
52
+ return baseLocale;
53
+ }
54
+ return null;
55
+ }
56
+ export function normalizeLocale(locale, fallback = DEFAULT_LOCALE) {
57
+ return resolveSupportedLocale(locale) ?? fallback;
58
+ }
59
+ export function isSupportedLocale(locale) {
60
+ return resolveSupportedLocale(locale) !== null;
61
+ }
62
+ export function getLocaleOptions() {
63
+ return LOCALE_DEFINITIONS.map(({ code, label }) => ({ code, label }));
64
+ }
65
+ export function getDateLocale(locale) {
66
+ const activeLocale = locale ?? getLocale();
67
+ return localeDefinitionByCode[activeLocale].dateLocale;
68
+ }
69
+ function interpolate(template, params) {
70
+ if (!params) {
71
+ return template;
72
+ }
73
+ return template.replace(/\{([a-zA-Z0-9_]+)\}/g, (fullMatch, key) => {
74
+ const value = params[key];
75
+ if (value === undefined || value === null) {
76
+ return fullMatch;
77
+ }
78
+ return String(value);
79
+ });
80
+ }
81
+ export function getLocale() {
82
+ if (runtimeLocaleOverride) {
83
+ return runtimeLocaleOverride;
84
+ }
85
+ const localeFromEnv = process.env.BOT_LOCALE;
86
+ return normalizeLocale(localeFromEnv, DEFAULT_LOCALE);
87
+ }
88
+ export function setRuntimeLocale(locale) {
89
+ runtimeLocaleOverride = locale;
90
+ }
91
+ export function resetRuntimeLocale() {
92
+ runtimeLocaleOverride = null;
93
+ }
94
+ export function t(key, params, locale) {
95
+ const activeLocale = locale ?? getLocale();
96
+ const dictionary = localeDefinitionByCode[activeLocale].dictionary;
97
+ const template = dictionary[key] ?? en[key];
98
+ if (!template) {
99
+ return key;
100
+ }
101
+ return interpolate(template, params);
102
+ }