@grinev/opencode-telegram-bot 0.17.0 â 0.19.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 +22 -1
- package/README.md +35 -6
- package/dist/app/start-bot-app.js +4 -0
- package/dist/attach/manager.js +66 -0
- package/dist/attach/service.js +171 -0
- package/dist/bot/commands/abort.js +0 -4
- package/dist/bot/commands/commands.js +13 -3
- package/dist/bot/commands/definitions.js +1 -0
- package/dist/bot/commands/mcps.js +394 -0
- package/dist/bot/commands/new.js +10 -18
- package/dist/bot/commands/opencode-start.js +2 -10
- package/dist/bot/commands/sessions.js +19 -20
- package/dist/bot/commands/start.js +2 -0
- package/dist/bot/handlers/prompt.js +26 -31
- package/dist/bot/index.js +82 -10
- package/dist/bot/middleware/interaction-guard.js +1 -1
- package/dist/bot/utils/busy-guard.js +3 -2
- package/dist/bot/utils/external-user-input.js +46 -0
- package/dist/bot/utils/switch-project.js +2 -0
- package/dist/config.js +25 -6
- package/dist/external-input/suppression.js +54 -0
- package/dist/i18n/de.js +34 -0
- package/dist/i18n/en.js +34 -0
- package/dist/i18n/es.js +34 -0
- package/dist/i18n/fr.js +34 -0
- package/dist/i18n/ru.js +34 -0
- package/dist/i18n/zh.js +34 -0
- package/dist/interaction/guard.js +2 -1
- package/dist/opencode/auto-restart.js +92 -0
- package/dist/opencode/events.js +13 -1
- package/dist/opencode/process.js +18 -1
- package/dist/pinned/manager.js +39 -0
- package/dist/summary/aggregator.js +89 -9
- package/dist/summary/formatter.js +2 -2
- package/dist/summary/markdown-to-telegram-v2.js +99 -0
- package/dist/summary/subagent-formatter.js +23 -2
- package/dist/telegram/render/inline-renderer.js +18 -0
- package/dist/tts/client.js +94 -19
- package/package.json +2 -2
package/dist/i18n/en.js
CHANGED
|
@@ -10,6 +10,7 @@ export const en = {
|
|
|
10
10
|
"cmd.description.tasklist": "List scheduled tasks",
|
|
11
11
|
"cmd.description.commands": "Custom commands",
|
|
12
12
|
"cmd.description.skills": "Skills catalog",
|
|
13
|
+
"cmd.description.mcps": "MCP servers",
|
|
13
14
|
"cmd.description.opencode_start": "Start OpenCode server",
|
|
14
15
|
"cmd.description.opencode_stop": "Stop OpenCode server",
|
|
15
16
|
"cmd.description.help": "Help",
|
|
@@ -48,6 +49,7 @@ export const en = {
|
|
|
48
49
|
"bot.prompt_send_error": "Failed to send request to OpenCode.",
|
|
49
50
|
"bot.session_error": "đ´ OpenCode returned an error: {message}",
|
|
50
51
|
"bot.session_retry": "đ {message}\n\nProvider keeps returning the same error on repeated retries. Use /abort to abort.",
|
|
52
|
+
"bot.external_user_input": "External user input",
|
|
51
53
|
"bot.unknown_command": "â ī¸ Unknown command: {command}. Use /help to see available commands.",
|
|
52
54
|
"bot.photo_downloading": "âŗ Downloading photo...",
|
|
53
55
|
"bot.photo_too_large": "â ī¸ Photo is too large (max {maxSizeMb}MB)",
|
|
@@ -113,6 +115,17 @@ export const en = {
|
|
|
113
115
|
"sessions.preview.title": "Recent messages:",
|
|
114
116
|
"sessions.preview.you": "You:",
|
|
115
117
|
"sessions.preview.agent": "Agent:",
|
|
118
|
+
"attach.project_not_selected": "đ Project is not selected.\n\nFirst select a project with /projects.",
|
|
119
|
+
"attach.session_not_selected": "đŦ Session is not selected.\n\nFirst choose a session with /sessions.",
|
|
120
|
+
"attach.session_project_mismatch": "â ī¸ The selected session does not match the current project. Choose the session again via /sessions.",
|
|
121
|
+
"attach.connected": "â
Connected to session: {title}",
|
|
122
|
+
"attach.already_connected": "âšī¸ Already connected to session: {title}",
|
|
123
|
+
"attach.status.idle_message": "Status: idle. Waiting for new events.",
|
|
124
|
+
"attach.status.busy_message": "Status: busy. New prompts are temporarily blocked.",
|
|
125
|
+
"attach.restored_question": "Recovered a pending question for this session.",
|
|
126
|
+
"attach.restored_permissions": "Recovered pending permission requests: {count}.",
|
|
127
|
+
"attach.disconnect_hint": "To disconnect, switch to another session or project.",
|
|
128
|
+
"attach.error": "đ´ Failed to attach to the current session.",
|
|
116
129
|
"new.project_not_selected": "đ Project is not selected.\n\nFirst select a project with /projects.",
|
|
117
130
|
"new.created": "â
New session created: {title}",
|
|
118
131
|
"new.create_error": "đ´ OpenCode Server is unavailable or an error occurred while creating session.",
|
|
@@ -232,6 +245,9 @@ export const en = {
|
|
|
232
245
|
"pinned.line.project": "Project: {project}",
|
|
233
246
|
"pinned.line.worktree": "Worktree: {worktree}",
|
|
234
247
|
"pinned.line.model": "Model: {model}",
|
|
248
|
+
"pinned.line.attach": "Tracking: {status}",
|
|
249
|
+
"pinned.attach.status.idle": "active, idle",
|
|
250
|
+
"pinned.attach.status.busy": "active, busy",
|
|
235
251
|
"pinned.line.context": "Context: {used} / {limit} ({percent}%)",
|
|
236
252
|
"pinned.line.cost": "Cost: {cost} spent",
|
|
237
253
|
"subagent.header": "Subagent {agent}: {description}",
|
|
@@ -349,6 +365,24 @@ export const en = {
|
|
|
349
365
|
"skills.button.next_page": "Next âĄī¸",
|
|
350
366
|
"skills.page_empty_callback": "No skills on this page",
|
|
351
367
|
"skills.page_load_error_callback": "Cannot load this page. Please try again.",
|
|
368
|
+
"mcps.select": "MCP servers:",
|
|
369
|
+
"mcps.empty": "đ No MCP servers configured.",
|
|
370
|
+
"mcps.fetch_error": "đ´ Failed to load MCP servers.",
|
|
371
|
+
"mcps.toggle_error": "đ´ Failed to toggle MCP server.",
|
|
372
|
+
"mcps.enabling": "Enabling...",
|
|
373
|
+
"mcps.disabling": "Disabling...",
|
|
374
|
+
"mcps.status.connected": "đĸ Connected",
|
|
375
|
+
"mcps.status.disabled": "đ´ Disabled",
|
|
376
|
+
"mcps.status.failed": "â ī¸ Failed",
|
|
377
|
+
"mcps.status.needs_auth": "đ Needs auth",
|
|
378
|
+
"mcps.status.needs_client_registration": "đ Needs registration",
|
|
379
|
+
"mcps.detail.title": "Server: {name}",
|
|
380
|
+
"mcps.detail.status": "Status: {status}",
|
|
381
|
+
"mcps.detail.error": "Error: {error}",
|
|
382
|
+
"mcps.button.enable": "đĸ Enable",
|
|
383
|
+
"mcps.button.disable": "đ´ Disable",
|
|
384
|
+
"mcps.button.back": "âŦ
ī¸ Back",
|
|
385
|
+
"mcps.auth_required": "This server requires authorization and cannot be enabled from the bot.",
|
|
352
386
|
"cmd.description.rename": "Rename current session",
|
|
353
387
|
"legacy.models.fetch_error": "đ´ Failed to get models list. Check server status with /status.",
|
|
354
388
|
"legacy.models.empty": "đ No available models. Configure providers in OpenCode.",
|
package/dist/i18n/es.js
CHANGED
|
@@ -10,6 +10,7 @@ export const es = {
|
|
|
10
10
|
"cmd.description.tasklist": "Ver tareas programadas",
|
|
11
11
|
"cmd.description.commands": "Comandos personalizados",
|
|
12
12
|
"cmd.description.skills": "CatÃĄlogo de skills",
|
|
13
|
+
"cmd.description.mcps": "MCP servers",
|
|
13
14
|
"cmd.description.opencode_start": "Iniciar servidor OpenCode",
|
|
14
15
|
"cmd.description.opencode_stop": "Detener servidor OpenCode",
|
|
15
16
|
"cmd.description.help": "Ayuda",
|
|
@@ -48,6 +49,7 @@ export const es = {
|
|
|
48
49
|
"bot.prompt_send_error": "No se pudo enviar la solicitud a OpenCode.",
|
|
49
50
|
"bot.session_error": "đ´ OpenCode devolviÃŗ un error: {message}",
|
|
50
51
|
"bot.session_retry": "đ {message}\n\nEl proveedor devuelve el mismo error en intentos repetidos. Usa /abort para detenerlo.",
|
|
52
|
+
"bot.external_user_input": "Entrada externa del usuario",
|
|
51
53
|
"bot.unknown_command": "â ī¸ Comando desconocido: {command}. Usa /help para ver los comandos disponibles.",
|
|
52
54
|
"bot.photo_downloading": "âŗ Descargando foto...",
|
|
53
55
|
"bot.photo_too_large": "â ī¸ La foto es demasiado grande (max {maxSizeMb}MB)",
|
|
@@ -113,6 +115,17 @@ export const es = {
|
|
|
113
115
|
"sessions.preview.title": "Mensajes recientes:",
|
|
114
116
|
"sessions.preview.you": "TÃē:",
|
|
115
117
|
"sessions.preview.agent": "Agente:",
|
|
118
|
+
"attach.project_not_selected": "đ No hay un proyecto seleccionado.\n\nPrimero selecciona un proyecto con /projects.",
|
|
119
|
+
"attach.session_not_selected": "đŦ No hay una sesiÃŗn seleccionada.\n\nPrimero selecciona una sesiÃŗn con /sessions.",
|
|
120
|
+
"attach.session_project_mismatch": "â ī¸ La sesiÃŗn seleccionada no coincide con el proyecto actual. Vuelve a seleccionarla con /sessions.",
|
|
121
|
+
"attach.connected": "â
Conectado a la sesiÃŗn: {title}",
|
|
122
|
+
"attach.already_connected": "âšī¸ Ya estÃĄs conectado a la sesiÃŗn: {title}",
|
|
123
|
+
"attach.status.idle_message": "Estado: idle. Esperando nuevos eventos.",
|
|
124
|
+
"attach.status.busy_message": "Estado: busy. Los nuevos prompts estÃĄn bloqueados temporalmente.",
|
|
125
|
+
"attach.restored_question": "Se restaurÃŗ una pregunta pendiente para esta sesiÃŗn.",
|
|
126
|
+
"attach.restored_permissions": "Se restauraron solicitudes de permiso pendientes: {count}.",
|
|
127
|
+
"attach.disconnect_hint": "Para desconectarte, cambia a otra sesiÃŗn o proyecto.",
|
|
128
|
+
"attach.error": "đ´ No se pudo conectar a la sesiÃŗn actual.",
|
|
116
129
|
"new.project_not_selected": "đ No hay un proyecto seleccionado.\n\nPrimero selecciona un proyecto con /projects.",
|
|
117
130
|
"new.created": "â
SesiÃŗn nueva creada: {title}",
|
|
118
131
|
"new.create_error": "đ´ OpenCode Server no estÃĄ disponible u ocurriÃŗ un error al crear la sesiÃŗn.",
|
|
@@ -232,6 +245,9 @@ export const es = {
|
|
|
232
245
|
"pinned.line.project": "Proyecto: {project}",
|
|
233
246
|
"pinned.line.worktree": "Worktree: {worktree}",
|
|
234
247
|
"pinned.line.model": "Modelo: {model}",
|
|
248
|
+
"pinned.line.attach": "Tracking: {status}",
|
|
249
|
+
"pinned.attach.status.idle": "activo, idle",
|
|
250
|
+
"pinned.attach.status.busy": "activo, busy",
|
|
235
251
|
"pinned.line.context": "Contexto: {used} / {limit} ({percent}%)",
|
|
236
252
|
"pinned.line.cost": "Costo: {cost} gastado",
|
|
237
253
|
"subagent.header": "Subagente {agent}: {description}",
|
|
@@ -349,6 +365,24 @@ export const es = {
|
|
|
349
365
|
"skills.button.next_page": "Siguiente âĄī¸",
|
|
350
366
|
"skills.page_empty_callback": "No hay skills en esta pÃĄgina",
|
|
351
367
|
"skills.page_load_error_callback": "No se pudo cargar esta pÃĄgina. Por favor, intÊntalo de nuevo.",
|
|
368
|
+
"mcps.select": "MCP servers:",
|
|
369
|
+
"mcps.empty": "đ No MCP servers configured.",
|
|
370
|
+
"mcps.fetch_error": "đ´ Failed to load MCP servers.",
|
|
371
|
+
"mcps.toggle_error": "đ´ Failed to toggle MCP server.",
|
|
372
|
+
"mcps.enabling": "Enabling...",
|
|
373
|
+
"mcps.disabling": "Disabling...",
|
|
374
|
+
"mcps.status.connected": "đĸ Connected",
|
|
375
|
+
"mcps.status.disabled": "đ´ Disabled",
|
|
376
|
+
"mcps.status.failed": "â ī¸ Failed",
|
|
377
|
+
"mcps.status.needs_auth": "đ Needs auth",
|
|
378
|
+
"mcps.status.needs_client_registration": "đ Needs registration",
|
|
379
|
+
"mcps.detail.title": "Server: {name}",
|
|
380
|
+
"mcps.detail.status": "Status: {status}",
|
|
381
|
+
"mcps.detail.error": "Error: {error}",
|
|
382
|
+
"mcps.button.enable": "đĸ Enable",
|
|
383
|
+
"mcps.button.disable": "đ´ Disable",
|
|
384
|
+
"mcps.button.back": "âŦ
ī¸ Back",
|
|
385
|
+
"mcps.auth_required": "This server requires authorization and cannot be enabled from the bot.",
|
|
352
386
|
"cmd.description.rename": "Renombrar la sesiÃŗn actual",
|
|
353
387
|
"legacy.models.fetch_error": "đ´ No se pudo obtener la lista de modelos. Revisa el estado del servidor con /status.",
|
|
354
388
|
"legacy.models.empty": "đ No hay modelos disponibles. Configura los proveedores en OpenCode.",
|
package/dist/i18n/fr.js
CHANGED
|
@@ -10,6 +10,7 @@ export const fr = {
|
|
|
10
10
|
"cmd.description.tasklist": "Afficher les tÃĸches planifiÊes",
|
|
11
11
|
"cmd.description.commands": "Commandes personnalisÊes",
|
|
12
12
|
"cmd.description.skills": "Catalogue de skills",
|
|
13
|
+
"cmd.description.mcps": "MCP servers",
|
|
13
14
|
"cmd.description.opencode_start": "DÊmarrer le serveur OpenCode",
|
|
14
15
|
"cmd.description.opencode_stop": "ArrÃĒter le serveur OpenCode",
|
|
15
16
|
"cmd.description.help": "Aide",
|
|
@@ -48,6 +49,7 @@ export const fr = {
|
|
|
48
49
|
"bot.prompt_send_error": "Impossible d'envoyer la requÃĒte à OpenCode.",
|
|
49
50
|
"bot.session_error": "đ´ OpenCode a renvoyÊ une erreur : {message}",
|
|
50
51
|
"bot.session_retry": "đ {message}\n\nLe fournisseur renvoie la mÃĒme erreur à chaque nouvelle tentative. Utilisez /abort pour arrÃĒter.",
|
|
52
|
+
"bot.external_user_input": "EntrÊe utilisateur externe",
|
|
51
53
|
"bot.unknown_command": "â ī¸ Commande inconnue : {command}. Utilisez /help pour voir les commandes disponibles.",
|
|
52
54
|
"bot.photo_downloading": "âŗ TÊlÊchargement de la photo...",
|
|
53
55
|
"bot.photo_too_large": "â ī¸ La photo est trop volumineuse (max {maxSizeMb}MB)",
|
|
@@ -113,6 +115,17 @@ export const fr = {
|
|
|
113
115
|
"sessions.preview.title": "Messages rÊcents :",
|
|
114
116
|
"sessions.preview.you": "Vous :",
|
|
115
117
|
"sessions.preview.agent": "Agent :",
|
|
118
|
+
"attach.project_not_selected": "đ Aucun projet sÊlectionnÊ.\n\nSÊlectionnez d'abord un projet avec /projects.",
|
|
119
|
+
"attach.session_not_selected": "đŦ Aucune session sÊlectionnÊe.\n\nSÊlectionnez d'abord une session avec /sessions.",
|
|
120
|
+
"attach.session_project_mismatch": "â ī¸ La session sÊlectionnÊe ne correspond pas au projet actuel. SÊlectionnez-la de nouveau via /sessions.",
|
|
121
|
+
"attach.connected": "â
ConnectÊ à la session : {title}",
|
|
122
|
+
"attach.already_connected": "âšī¸ DÊjà connectÊ à la session : {title}",
|
|
123
|
+
"attach.status.idle_message": "Statut : idle. En attente de nouveaux ÊvÊnements.",
|
|
124
|
+
"attach.status.busy_message": "Statut : busy. Les nouveaux prompts sont temporairement bloquÊs.",
|
|
125
|
+
"attach.restored_question": "Une question en attente a ÊtÊ restaurÊe pour cette session.",
|
|
126
|
+
"attach.restored_permissions": "Demandes de permission en attente restaurÊes : {count}.",
|
|
127
|
+
"attach.disconnect_hint": "Pour vous dÊconnecter, passez simplement à une autre session ou à un autre projet.",
|
|
128
|
+
"attach.error": "đ´ Impossible de se connecter à la session actuelle.",
|
|
116
129
|
"new.project_not_selected": "đ Aucun projet n'est sÊlectionnÊ.\n\nSÊlectionnez d'abord un projet avec /projects.",
|
|
117
130
|
"new.created": "â
Nouvelle session crÊÊe : {title}",
|
|
118
131
|
"new.create_error": "đ´ Le serveur OpenCode est indisponible ou une erreur s'est produite lors de la crÊation de la session.",
|
|
@@ -232,6 +245,9 @@ export const fr = {
|
|
|
232
245
|
"pinned.line.project": "Projet : {project}",
|
|
233
246
|
"pinned.line.worktree": "Worktree : {worktree}",
|
|
234
247
|
"pinned.line.model": "Modèle : {model}",
|
|
248
|
+
"pinned.line.attach": "Tracking : {status}",
|
|
249
|
+
"pinned.attach.status.idle": "actif, idle",
|
|
250
|
+
"pinned.attach.status.busy": "actif, busy",
|
|
235
251
|
"pinned.line.context": "Contexte : {used} / {limit} ({percent}%)",
|
|
236
252
|
"pinned.line.cost": "CoÃģt : {cost} dÊpensÊ",
|
|
237
253
|
"subagent.header": "Sous-agent {agent} : {description}",
|
|
@@ -349,6 +365,24 @@ export const fr = {
|
|
|
349
365
|
"skills.button.next_page": "Suivant âĄī¸",
|
|
350
366
|
"skills.page_empty_callback": "Aucun skill sur cette page",
|
|
351
367
|
"skills.page_load_error_callback": "Impossible de charger cette page. Veuillez rÊessayer.",
|
|
368
|
+
"mcps.select": "MCP servers:",
|
|
369
|
+
"mcps.empty": "đ No MCP servers configured.",
|
|
370
|
+
"mcps.fetch_error": "đ´ Failed to load MCP servers.",
|
|
371
|
+
"mcps.toggle_error": "đ´ Failed to toggle MCP server.",
|
|
372
|
+
"mcps.enabling": "Enabling...",
|
|
373
|
+
"mcps.disabling": "Disabling...",
|
|
374
|
+
"mcps.status.connected": "đĸ Connected",
|
|
375
|
+
"mcps.status.disabled": "đ´ Disabled",
|
|
376
|
+
"mcps.status.failed": "â ī¸ Failed",
|
|
377
|
+
"mcps.status.needs_auth": "đ Needs auth",
|
|
378
|
+
"mcps.status.needs_client_registration": "đ Needs registration",
|
|
379
|
+
"mcps.detail.title": "Server: {name}",
|
|
380
|
+
"mcps.detail.status": "Status: {status}",
|
|
381
|
+
"mcps.detail.error": "Error: {error}",
|
|
382
|
+
"mcps.button.enable": "đĸ Enable",
|
|
383
|
+
"mcps.button.disable": "đ´ Disable",
|
|
384
|
+
"mcps.button.back": "âŦ
ī¸ Back",
|
|
385
|
+
"mcps.auth_required": "This server requires authorization and cannot be enabled from the bot.",
|
|
352
386
|
"cmd.description.rename": "Renommer la session actuelle",
|
|
353
387
|
"legacy.models.fetch_error": "đ´ Impossible de rÊcupÊrer la liste des modèles. VÊrifiez l'Êtat du serveur avec /status.",
|
|
354
388
|
"legacy.models.empty": "đ Aucun modèle disponible. Configurez les fournisseurs dans OpenCode.",
|
package/dist/i18n/ru.js
CHANGED
|
@@ -10,6 +10,7 @@ export const ru = {
|
|
|
10
10
|
"cmd.description.tasklist": "ĐĄĐŋиŅĐžĐē ĐˇĐ°Đ´Đ°Ņ ĐŋĐž ŅаŅĐŋиŅаĐŊиŅ",
|
|
11
11
|
"cmd.description.commands": "ĐĐžĐģŅСОваŅĐĩĐģŅŅĐēиĐĩ ĐēĐžĐŧаĐŊĐ´Ņ",
|
|
12
12
|
"cmd.description.skills": "ĐаŅаĐģĐžĐŗ ŅĐēиĐģĐģОв",
|
|
13
|
+
"cmd.description.mcps": "MCP ŅĐĩŅвĐĩŅŅ",
|
|
13
14
|
"cmd.description.opencode_start": "ĐаĐŋŅŅŅиŅŅ OpenCode ŅĐĩŅвĐĩŅ",
|
|
14
15
|
"cmd.description.opencode_stop": "ĐŅŅаĐŊОвиŅŅ OpenCode ŅĐĩŅвĐĩŅ",
|
|
15
16
|
"cmd.description.help": "ĐĄĐŋŅавĐēа",
|
|
@@ -48,6 +49,7 @@ export const ru = {
|
|
|
48
49
|
"bot.prompt_send_error": "ĐĐĩ ŅдаĐģĐžŅŅ ĐžŅĐŋŅавиŅŅ ĐˇĐ°ĐŋŅĐžŅ Đ˛ OpenCode.",
|
|
49
50
|
"bot.session_error": "đ´ OpenCode вĐĩŅĐŊŅĐģ ĐžŅийĐēŅ: {message}",
|
|
50
51
|
"bot.session_retry": "đ {message}\n\nĐŅОваКдĐĩŅ Đ˛ĐžĐˇĐ˛ŅаŅаĐĩŅ ĐžĐ´ĐŊŅ Đ¸ ŅŅ ĐļĐĩ ĐžŅийĐēŅ ĐŋŅи ĐŋОвŅĐžŅĐŊŅŅ
СаĐŋŅĐžŅаŅ
. ĐŅĐŋĐžĐģŅСŅĐšŅĐĩ /abort Đ´ĐģŅ ĐžŅŅаĐŊОвĐēи.",
|
|
52
|
+
"bot.external_user_input": "ĐĐŊĐĩŅĐŊиК ввОд ĐŋĐžĐģŅСОваŅĐĩĐģŅ",
|
|
51
53
|
"bot.unknown_command": "â ī¸ ĐĐĩиСвĐĩŅŅĐŊĐ°Ņ ĐēĐžĐŧаĐŊда: {command}. ĐŅĐŋĐžĐģŅСŅĐšŅĐĩ /help Đ´ĐģŅ ŅĐŋиŅĐēа ĐēĐžĐŧаĐŊĐ´.",
|
|
52
54
|
"bot.photo_downloading": "âŗ ĐĄĐēаŅĐ¸Đ˛Đ°Ņ ŅĐžŅĐž...",
|
|
53
55
|
"bot.photo_too_large": "â ī¸ Đ¤ĐžŅĐž ŅĐģиŅĐēĐžĐŧ йОĐģŅŅĐžĐĩ (ĐŧаĐēŅ. {maxSizeMb}ĐĐ)",
|
|
@@ -113,6 +115,17 @@ export const ru = {
|
|
|
113
115
|
"sessions.preview.title": "ĐĐžŅĐģĐĩĐ´ĐŊиĐĩ ŅООйŅĐĩĐŊиŅ:",
|
|
114
116
|
"sessions.preview.you": "ĐŅ:",
|
|
115
117
|
"sessions.preview.agent": "ĐĐŗĐĩĐŊŅ:",
|
|
118
|
+
"attach.project_not_selected": "đ ĐŅĐžĐĩĐēŅ ĐŊĐĩ вŅĐąŅаĐŊ.\n\nĐĄĐŊаŅаĐģа вŅĐąĐĩŅиŅĐĩ ĐŋŅĐžĐĩĐēŅ ĐēĐžĐŧаĐŊдОК /projects.",
|
|
119
|
+
"attach.session_not_selected": "đŦ ĐĄĐĩŅŅĐ¸Ņ ĐŊĐĩ вŅĐąŅаĐŊа.\n\nĐĄĐŊаŅаĐģа вŅĐąĐĩŅиŅĐĩ ŅĐĩŅŅĐ¸Ņ ŅĐĩŅĐĩС /sessions.",
|
|
120
|
+
"attach.session_project_mismatch": "â ī¸ ĐŅĐąŅаĐŊĐŊĐ°Ņ ŅĐĩŅŅĐ¸Ņ ĐŊĐĩ ŅООŅвĐĩŅŅŅвŅĐĩŅ ŅĐĩĐēŅŅĐĩĐŧŅ ĐŋŅĐžĐĩĐēŅŅ. ĐОвŅĐžŅĐŊĐž вŅĐąĐĩŅиŅĐĩ ŅĐĩŅŅĐ¸Ņ ŅĐĩŅĐĩС /sessions.",
|
|
121
|
+
"attach.connected": "â
ĐОдĐēĐģŅŅиĐģŅŅ Đē ŅĐĩŅŅии: {title}",
|
|
122
|
+
"attach.already_connected": "âšī¸ ĐŖĐļĐĩ ĐŋОдĐēĐģŅŅĐĩĐŊ Đē ŅĐĩŅŅии: {title}",
|
|
123
|
+
"attach.status.idle_message": "ĐĄŅаŅŅŅ: idle. ĐĐ´Ņ ĐŊОвŅĐĩ ŅОйŅŅиŅ.",
|
|
124
|
+
"attach.status.busy_message": "ĐĄŅаŅŅŅ: busy. ĐОвŅĐĩ ĐŋŅĐžĐŧŅŅ Đ˛ŅĐĩĐŧĐĩĐŊĐŊĐž СайĐģĐžĐēиŅОваĐŊŅ.",
|
|
125
|
+
"attach.restored_question": "ĐĐžŅŅŅаĐŊОвиĐģ ĐžĐļидаŅŅиК вОĐŋŅĐžŅ Đ´ĐģŅ ŅŅОК ŅĐĩŅŅии.",
|
|
126
|
+
"attach.restored_permissions": "ĐĐžŅŅŅаĐŊОвиĐģ ĐžĐļидаŅŅиĐĩ СаĐŋŅĐžŅŅ ŅаСŅĐĩŅĐĩĐŊиК: {count}.",
|
|
127
|
+
"attach.disconnect_hint": "ЧŅĐžĐąŅ ĐžŅĐēĐģŅŅиŅŅŅŅ, ĐŋĐĩŅĐĩĐēĐģŅŅиŅĐĩŅŅ ĐŊа Đ´ŅŅĐŗŅŅ ŅĐĩŅŅĐ¸Ņ Đ¸Đģи ĐŋŅĐžĐĩĐēŅ.",
|
|
128
|
+
"attach.error": "đ´ ĐĐĩ ŅдаĐģĐžŅŅ ĐŋОдĐēĐģŅŅиŅŅŅŅ Đē ŅĐĩĐēŅŅĐĩĐš ŅĐĩŅŅии.",
|
|
116
129
|
"new.project_not_selected": "đ ĐŅĐžĐĩĐēŅ ĐŊĐĩ вŅĐąŅаĐŊ.\n\nĐĄĐŊаŅаĐģа вŅĐąĐĩŅиŅĐĩ ĐŋŅĐžĐĩĐēŅ ĐēĐžĐŧаĐŊдОК /projects.",
|
|
117
130
|
"new.created": "â
ХОСдаĐŊа ĐŊĐžĐ˛Đ°Ņ ŅĐĩŅŅиŅ: {title}",
|
|
118
131
|
"new.create_error": "đ´ OpenCode Server ĐŊĐĩĐ´ĐžŅŅŅĐŋĐĩĐŊ иĐģи ĐŋŅОиСОŅĐģа ĐžŅийĐēа ĐŋŅи ŅОСдаĐŊии ŅĐĩŅŅии.",
|
|
@@ -232,6 +245,9 @@ export const ru = {
|
|
|
232
245
|
"pinned.line.project": "ĐŅĐžĐĩĐēŅ: {project}",
|
|
233
246
|
"pinned.line.worktree": "Worktree: {worktree}",
|
|
234
247
|
"pinned.line.model": "ĐОдĐĩĐģŅ: {model}",
|
|
248
|
+
"pinned.line.attach": "Tracking: {status}",
|
|
249
|
+
"pinned.attach.status.idle": "аĐēŅивĐĩĐŊ, idle",
|
|
250
|
+
"pinned.attach.status.busy": "аĐēŅивĐĩĐŊ, busy",
|
|
235
251
|
"pinned.line.context": "ĐĐžĐŊŅĐĩĐēŅŅ: {used} / {limit} ({percent}%)",
|
|
236
252
|
"pinned.line.cost": "ĐĄŅОиĐŧĐžŅŅŅ: {cost} ĐŋĐžŅŅаŅĐĩĐŊĐž",
|
|
237
253
|
"subagent.header": "ĐĄĐ°ĐąĐ°ĐŗĐĩĐŊŅ {agent}: {description}",
|
|
@@ -349,6 +365,24 @@ export const ru = {
|
|
|
349
365
|
"skills.button.next_page": "ĐĐŋĐĩŅŅĐ´ âĄī¸",
|
|
350
366
|
"skills.page_empty_callback": "Đа ŅŅОК ŅŅŅаĐŊиŅĐĩ ĐŊĐĩŅ ŅĐēиĐģĐģОв",
|
|
351
367
|
"skills.page_load_error_callback": "ĐĐĩ ŅдаĐģĐžŅŅ ĐˇĐ°ĐŗŅŅСиŅŅ ŅŅŅ ŅŅŅаĐŊиŅŅ. ĐĐžĐļаĐģŅĐšŅŅа, ĐŋĐžĐŋŅОйŅĐšŅĐĩ ŅĐŊОва.",
|
|
368
|
+
"mcps.select": "MCP ŅĐĩŅвĐĩŅŅ:",
|
|
369
|
+
"mcps.empty": "đ MCP ŅĐĩŅвĐĩŅŅ ĐŊĐĩ ĐŊаŅŅŅĐžĐĩĐŊŅ.",
|
|
370
|
+
"mcps.fetch_error": "đ´ ĐĐĩ ŅдаĐģĐžŅŅ ĐˇĐ°ĐŗŅŅСиŅŅ MCP ŅĐĩŅвĐĩŅŅ.",
|
|
371
|
+
"mcps.toggle_error": "đ´ ĐĐĩ ŅдаĐģĐžŅŅ ĐŋĐĩŅĐĩĐēĐģŅŅиŅŅ MCP ŅĐĩŅвĐĩŅ.",
|
|
372
|
+
"mcps.enabling": "ĐĐēĐģŅŅаŅ...",
|
|
373
|
+
"mcps.disabling": "ĐŅĐēĐģŅŅаŅ...",
|
|
374
|
+
"mcps.status.connected": "đĸ ĐОдĐēĐģŅŅĐĩĐŊ",
|
|
375
|
+
"mcps.status.disabled": "đ´ ĐŅĐēĐģŅŅĐĩĐŊ",
|
|
376
|
+
"mcps.status.failed": "â ī¸ ĐŅийĐēа",
|
|
377
|
+
"mcps.status.needs_auth": "đ ĐĸŅĐĩĐąŅĐĩŅŅŅ Đ°Đ˛ŅĐžŅиСаŅиŅ",
|
|
378
|
+
"mcps.status.needs_client_registration": "đ ĐĸŅĐĩĐąŅĐĩŅŅŅ ŅĐĩĐŗĐ¸ŅŅŅаŅиŅ",
|
|
379
|
+
"mcps.detail.title": "ĐĄĐĩŅвĐĩŅ: {name}",
|
|
380
|
+
"mcps.detail.status": "ĐĄŅаŅŅŅ: {status}",
|
|
381
|
+
"mcps.detail.error": "ĐŅийĐēа: {error}",
|
|
382
|
+
"mcps.button.enable": "đĸ ĐĐēĐģŅŅиŅŅ",
|
|
383
|
+
"mcps.button.disable": "đ´ ĐŅĐēĐģŅŅиŅŅ",
|
|
384
|
+
"mcps.button.back": "âŦ
ī¸ ĐаСад",
|
|
385
|
+
"mcps.auth_required": "ĐŅĐžŅ ŅĐĩŅвĐĩŅ ŅŅĐĩĐąŅĐĩŅ Đ°Đ˛ŅĐžŅиСаŅии и ĐŊĐĩ ĐŧĐžĐļĐĩŅ ĐąŅŅŅ Đ˛ĐēĐģŅŅĐĩĐŊ иС йОŅа.",
|
|
352
386
|
"cmd.description.rename": "ĐĐĩŅĐĩиĐŧĐĩĐŊОваŅŅ ŅĐĩĐēŅŅŅŅ ŅĐĩŅŅиŅ",
|
|
353
387
|
"legacy.models.fetch_error": "đ´ ĐĐĩ ŅдаĐģĐžŅŅ ĐŋĐžĐģŅŅиŅŅ ŅĐŋиŅĐžĐē ĐŧОдĐĩĐģĐĩĐš. ĐŅОвĐĩŅŅŅĐĩ ŅŅаŅŅŅ ŅĐĩŅвĐĩŅа /status.",
|
|
354
388
|
"legacy.models.empty": "đ ĐĐĩŅ Đ´ĐžŅŅŅĐŋĐŊŅŅ
ĐŧОдĐĩĐģĐĩĐš. ĐаŅŅŅОКŅĐĩ ĐŋŅОваКдĐĩŅŅ ŅĐĩŅĐĩС OpenCode.",
|
package/dist/i18n/zh.js
CHANGED
|
@@ -10,6 +10,7 @@ export const zh = {
|
|
|
10
10
|
"cmd.description.tasklist": "æĨįåŽæļäģģåĄ",
|
|
11
11
|
"cmd.description.commands": "čĒåŽäšåŊäģ¤",
|
|
12
12
|
"cmd.description.skills": "æčŊįŽåŊ",
|
|
13
|
+
"cmd.description.mcps": "MCP servers",
|
|
13
14
|
"cmd.description.opencode_start": "å¯å¨ OpenCode æåĄå¨",
|
|
14
15
|
"cmd.description.opencode_stop": "åæĸ OpenCode æåĄå¨",
|
|
15
16
|
"cmd.description.help": "帎åŠ",
|
|
@@ -48,6 +49,7 @@ export const zh = {
|
|
|
48
49
|
"bot.prompt_send_error": "å OpenCode åé蝎æąå¤ąč´Ĩã",
|
|
49
50
|
"bot.session_error": "đ´ OpenCode čŋåé蝝īŧ{message}",
|
|
50
51
|
"bot.session_retry": "đ {message}\n\næäžæšå¨éå¤é蝿ļæįģčŋååä¸é蝝ãäŊŋ፠/abort å¯åæĸã",
|
|
52
|
+
"bot.external_user_input": "å¤é¨į¨æˇčžå
Ĩ",
|
|
51
53
|
"bot.unknown_command": "â ī¸ æĒįĨåŊäģ¤īŧ{command}ãäŊŋ፠/help æĨįå¯į¨åŊäģ¤ã",
|
|
52
54
|
"bot.photo_downloading": "âŗ æŖå¨ä¸čŊŊį
§į...",
|
|
53
55
|
"bot.photo_too_large": "â ī¸ į
§įčŋ大īŧæå¤§ {maxSizeMb}MBīŧ",
|
|
@@ -113,6 +115,17 @@ export const zh = {
|
|
|
113
115
|
"sessions.preview.title": "æčŋæļæ¯īŧ",
|
|
114
116
|
"sessions.preview.you": "äŊ īŧ",
|
|
115
117
|
"sessions.preview.agent": "äģŖįīŧ",
|
|
118
|
+
"attach.project_not_selected": "đ æĒéæŠéĄšįŽã\n\n蝎å
äŊŋ፠/projects éæŠä¸ä¸Ē饚įŽã",
|
|
119
|
+
"attach.session_not_selected": "đŦ æĒéæŠäŧč¯ã\n\n蝎å
äŊŋ፠/sessions éæŠä¸ä¸Ēäŧč¯ã",
|
|
120
|
+
"attach.session_project_mismatch": "â ī¸ éä¸įäŧč¯ä¸åŊå饚įŽä¸åšé
ã蝎éčŋ /sessions éæ°éæŠã",
|
|
121
|
+
"attach.connected": "â
厞čŋæĨå°äŧč¯īŧ{title}",
|
|
122
|
+
"attach.already_connected": "âšī¸ 厞įģčŋæĨå°äŧč¯īŧ{title}",
|
|
123
|
+
"attach.status.idle_message": "įļæīŧidleãæŖå¨įåž
æ°äēäģļã",
|
|
124
|
+
"attach.status.busy_message": "įļæīŧbusyãæ°įæį¤ē蝿æļčĸĢéģæĸã",
|
|
125
|
+
"attach.restored_question": "厞æĸå¤č¯Ĩäŧč¯įåž
å¤įéŽéĸã",
|
|
126
|
+
"attach.restored_permissions": "厞æĸå¤åž
å¤įæé蝎æąīŧ{count}ã",
|
|
127
|
+
"attach.disconnect_hint": "åĻéæåŧčŋæĨīŧåĒéåæĸå°å
ļäģäŧč¯æéĄšįŽã",
|
|
128
|
+
"attach.error": "đ´ æ æŗčŋæĨå°åŊåäŧč¯ã",
|
|
116
129
|
"new.project_not_selected": "đ æĒéæŠéĄšįŽã\n\n蝎å
äŊŋ፠/projects éæŠä¸ä¸Ē饚įŽã",
|
|
117
130
|
"new.created": "â
æ°äŧč¯åˇ˛ååģēīŧ{title}",
|
|
118
131
|
"new.create_error": "đ´ OpenCode æåĄå¨ä¸å¯į¨īŧæååģēäŧ蝿ļåįé蝝ã",
|
|
@@ -232,6 +245,9 @@ export const zh = {
|
|
|
232
245
|
"pinned.line.project": "饚įŽ: {project}",
|
|
233
246
|
"pinned.line.worktree": "Worktree: {worktree}",
|
|
234
247
|
"pinned.line.model": "æ¨Ąå: {model}",
|
|
248
|
+
"pinned.line.attach": "Tracking: {status}",
|
|
249
|
+
"pinned.attach.status.idle": "active, idle",
|
|
250
|
+
"pinned.attach.status.busy": "active, busy",
|
|
235
251
|
"pinned.line.context": "ä¸ä¸æ: {used} / {limit} ({percent}%)",
|
|
236
252
|
"pinned.line.cost": "č´šį¨: {cost}",
|
|
237
253
|
"subagent.header": "åäģŖį {agent}: {description}",
|
|
@@ -349,6 +365,24 @@ export const zh = {
|
|
|
349
365
|
"skills.button.next_page": "ä¸ä¸éĄĩ âĄī¸",
|
|
350
366
|
"skills.page_empty_callback": "čŋä¸éĄĩæ˛ĄææčŊ",
|
|
351
367
|
"skills.page_load_error_callback": "æ æŗå čŊŊæ¤éĄĩéĸã蝎éč¯ã",
|
|
368
|
+
"mcps.select": "MCP servers:",
|
|
369
|
+
"mcps.empty": "đ No MCP servers configured.",
|
|
370
|
+
"mcps.fetch_error": "đ´ Failed to load MCP servers.",
|
|
371
|
+
"mcps.toggle_error": "đ´ Failed to toggle MCP server.",
|
|
372
|
+
"mcps.enabling": "Enabling...",
|
|
373
|
+
"mcps.disabling": "Disabling...",
|
|
374
|
+
"mcps.status.connected": "đĸ Connected",
|
|
375
|
+
"mcps.status.disabled": "đ´ Disabled",
|
|
376
|
+
"mcps.status.failed": "â ī¸ Failed",
|
|
377
|
+
"mcps.status.needs_auth": "đ Needs auth",
|
|
378
|
+
"mcps.status.needs_client_registration": "đ Needs registration",
|
|
379
|
+
"mcps.detail.title": "Server: {name}",
|
|
380
|
+
"mcps.detail.status": "Status: {status}",
|
|
381
|
+
"mcps.detail.error": "Error: {error}",
|
|
382
|
+
"mcps.button.enable": "đĸ Enable",
|
|
383
|
+
"mcps.button.disable": "đ´ Disable",
|
|
384
|
+
"mcps.button.back": "âŦ
ī¸ Back",
|
|
385
|
+
"mcps.auth_required": "This server requires authorization and cannot be enabled from the bot.",
|
|
352
386
|
"cmd.description.rename": "éåŊååŊåäŧč¯",
|
|
353
387
|
"legacy.models.fetch_error": "đ´ čˇåæ¨ĄååčĄ¨å¤ąč´Ĩã蝎äŊŋ፠/status æŖæĨæåĄå¨įļæã",
|
|
354
388
|
"legacy.models.empty": "đ æ˛Ąæå¯į¨æ¨Ąåãč¯ˇå¨ OpenCode ä¸é
įŊŽ providersã",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { interactionManager } from "./manager.js";
|
|
2
2
|
import { allowsBusyInteraction, isBusyAllowedCommand } from "./busy.js";
|
|
3
3
|
import { foregroundSessionState } from "../scheduled-task/foreground-state.js";
|
|
4
|
+
import { attachManager } from "../attach/manager.js";
|
|
4
5
|
function normalizeIncomingCommand(text) {
|
|
5
6
|
const trimmed = text.trim();
|
|
6
7
|
if (!trimmed.startsWith("/")) {
|
|
@@ -83,7 +84,7 @@ function isAllowedTaskCallback(ctx, state) {
|
|
|
83
84
|
export function resolveInteractionGuardDecision(ctx) {
|
|
84
85
|
const state = interactionManager.getSnapshot();
|
|
85
86
|
const { inputType, command } = classifyIncomingInput(ctx);
|
|
86
|
-
const isBusy = foregroundSessionState.isBusy();
|
|
87
|
+
const isBusy = foregroundSessionState.isBusy() || attachManager.isBusy();
|
|
87
88
|
if (state && interactionManager.isExpired()) {
|
|
88
89
|
interactionManager.clear("expired");
|
|
89
90
|
return createBlockDecision(inputType, state, "expired", command, isBusy);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { config } from "../config.js";
|
|
2
|
+
import { logger } from "../utils/logger.js";
|
|
3
|
+
import { opencodeClient } from "./client.js";
|
|
4
|
+
import { resolveLocalOpencodeTarget, startLocalOpencodeServer, } from "./process.js";
|
|
5
|
+
const SERVER_READY_TIMEOUT_MS = 10000;
|
|
6
|
+
const SERVER_READY_POLL_INTERVAL_MS = 500;
|
|
7
|
+
function sleep(ms) {
|
|
8
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
|
+
}
|
|
10
|
+
async function isOpencodeServerHealthy() {
|
|
11
|
+
try {
|
|
12
|
+
const { data, error } = await opencodeClient.global.health();
|
|
13
|
+
return !error && data?.healthy === true;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function waitForOpencodeServerReady(timeoutMs) {
|
|
20
|
+
const startedAt = Date.now();
|
|
21
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
22
|
+
if (await isOpencodeServerHealthy()) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
await sleep(SERVER_READY_POLL_INTERVAL_MS);
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
export class OpencodeAutoRestartService {
|
|
30
|
+
timer = null;
|
|
31
|
+
localTarget = null;
|
|
32
|
+
started = false;
|
|
33
|
+
checkInProgress = false;
|
|
34
|
+
async start() {
|
|
35
|
+
if (this.started || !config.opencode.autoRestartEnabled) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const localTarget = resolveLocalOpencodeTarget(config.opencode.apiUrl);
|
|
39
|
+
if (!localTarget) {
|
|
40
|
+
logger.warn(`[OpenCodeAutoRestart] Disabled because OPENCODE_API_URL is not local: ${config.opencode.apiUrl}`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
this.started = true;
|
|
44
|
+
this.localTarget = localTarget;
|
|
45
|
+
logger.info(`[OpenCodeAutoRestart] Enabled: port=${localTarget.port}, intervalSec=${config.opencode.monitorIntervalSec}`);
|
|
46
|
+
await this.checkAndRestart("startup");
|
|
47
|
+
this.timer = setInterval(() => {
|
|
48
|
+
void this.checkAndRestart("interval");
|
|
49
|
+
}, config.opencode.monitorIntervalSec * 1000);
|
|
50
|
+
this.timer.unref?.();
|
|
51
|
+
}
|
|
52
|
+
stop() {
|
|
53
|
+
if (this.timer) {
|
|
54
|
+
clearInterval(this.timer);
|
|
55
|
+
this.timer = null;
|
|
56
|
+
}
|
|
57
|
+
this.started = false;
|
|
58
|
+
this.localTarget = null;
|
|
59
|
+
}
|
|
60
|
+
async checkAndRestart(reason) {
|
|
61
|
+
if (this.checkInProgress || !this.localTarget) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.checkInProgress = true;
|
|
65
|
+
try {
|
|
66
|
+
if (await isOpencodeServerHealthy()) {
|
|
67
|
+
logger.debug(`[OpenCodeAutoRestart] Health-check succeeded: reason=${reason}`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
logger.warn(`[OpenCodeAutoRestart] OpenCode server is unavailable, starting local server: reason=${reason}, port=${this.localTarget.port}`);
|
|
71
|
+
const childProcess = startLocalOpencodeServer(this.localTarget);
|
|
72
|
+
childProcess.once("error", (error) => {
|
|
73
|
+
logger.error("[OpenCodeAutoRestart] OpenCode server process failed to start", error);
|
|
74
|
+
});
|
|
75
|
+
const pid = childProcess.pid;
|
|
76
|
+
childProcess.unref();
|
|
77
|
+
const ready = await waitForOpencodeServerReady(SERVER_READY_TIMEOUT_MS);
|
|
78
|
+
if (!ready) {
|
|
79
|
+
logger.warn(`[OpenCodeAutoRestart] OpenCode server was started but did not become ready: pid=${pid ?? "unknown"}, port=${this.localTarget.port}`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
logger.info(`[OpenCodeAutoRestart] OpenCode server recovered: pid=${pid ?? "unknown"}, port=${this.localTarget.port}`);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
logger.error("[OpenCodeAutoRestart] Failed to check or restart OpenCode server", error);
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
this.checkInProgress = false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export const opencodeAutoRestartService = new OpencodeAutoRestartService();
|
package/dist/opencode/events.js
CHANGED
|
@@ -8,6 +8,7 @@ let eventCallback = null;
|
|
|
8
8
|
let isListening = false;
|
|
9
9
|
let activeDirectory = null;
|
|
10
10
|
let streamAbortController = null;
|
|
11
|
+
let listenerGeneration = 0;
|
|
11
12
|
function getReconnectDelayMs(attempt) {
|
|
12
13
|
const exponentialDelay = RECONNECT_BASE_DELAY_MS * Math.pow(2, Math.max(0, attempt - 1));
|
|
13
14
|
return Math.min(exponentialDelay, RECONNECT_MAX_DELAY_MS);
|
|
@@ -44,6 +45,7 @@ export async function subscribeToEvents(directory, callback) {
|
|
|
44
45
|
activeDirectory = null;
|
|
45
46
|
}
|
|
46
47
|
const controller = new AbortController();
|
|
48
|
+
const generation = ++listenerGeneration;
|
|
47
49
|
activeDirectory = directory;
|
|
48
50
|
eventCallback = callback;
|
|
49
51
|
isListening = true;
|
|
@@ -70,7 +72,16 @@ export async function subscribeToEvents(directory, callback) {
|
|
|
70
72
|
// Use setImmediate to avoid blocking the event loop
|
|
71
73
|
// and let grammY process incoming Telegram updates
|
|
72
74
|
const callbackSnapshot = eventCallback;
|
|
73
|
-
setImmediate(() =>
|
|
75
|
+
setImmediate(() => {
|
|
76
|
+
if (streamAbortController !== controller ||
|
|
77
|
+
controller.signal.aborted ||
|
|
78
|
+
!isListening ||
|
|
79
|
+
activeDirectory !== directory ||
|
|
80
|
+
listenerGeneration !== generation) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
callbackSnapshot(event);
|
|
84
|
+
});
|
|
74
85
|
}
|
|
75
86
|
}
|
|
76
87
|
eventStream = null;
|
|
@@ -130,6 +141,7 @@ export async function subscribeToEvents(directory, callback) {
|
|
|
130
141
|
}
|
|
131
142
|
}
|
|
132
143
|
export function stopEventListening() {
|
|
144
|
+
listenerGeneration++;
|
|
133
145
|
streamAbortController?.abort();
|
|
134
146
|
streamAbortController = null;
|
|
135
147
|
isListening = false;
|
package/dist/opencode/process.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { exec } from "node:child_process";
|
|
1
|
+
import { exec, spawn } from "node:child_process";
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
3
|
const execAsync = promisify(exec);
|
|
4
4
|
const DEFAULT_OPENCODE_PORT = 4096;
|
|
@@ -25,6 +25,23 @@ export function resolveLocalOpencodeTarget(apiUrl) {
|
|
|
25
25
|
return null;
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
+
export function createOpencodeServeSpawnCommand(target) {
|
|
29
|
+
const isWindows = process.platform === "win32";
|
|
30
|
+
const port = target.port.toString();
|
|
31
|
+
return {
|
|
32
|
+
command: isWindows ? "cmd.exe" : "opencode",
|
|
33
|
+
args: isWindows ? ["/c", "opencode", "serve", "--port", port] : ["serve", "--port", port],
|
|
34
|
+
windowsHide: isWindows,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function startLocalOpencodeServer(target) {
|
|
38
|
+
const spawnCommand = createOpencodeServeSpawnCommand(target);
|
|
39
|
+
return spawn(spawnCommand.command, spawnCommand.args, {
|
|
40
|
+
detached: true,
|
|
41
|
+
stdio: "ignore",
|
|
42
|
+
windowsHide: spawnCommand.windowsHide,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
28
45
|
function parsePid(value) {
|
|
29
46
|
const pid = Number.parseInt(value.trim(), 10);
|
|
30
47
|
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
package/dist/pinned/manager.js
CHANGED
|
@@ -15,6 +15,8 @@ class PinnedMessageManager {
|
|
|
15
15
|
chatId: null,
|
|
16
16
|
sessionId: null,
|
|
17
17
|
sessionTitle: t("pinned.default_session_title"),
|
|
18
|
+
attachActive: false,
|
|
19
|
+
attachBusy: false,
|
|
18
20
|
projectPath: "",
|
|
19
21
|
projectBranch: null,
|
|
20
22
|
projectWorktreePath: null,
|
|
@@ -55,6 +57,8 @@ class PinnedMessageManager {
|
|
|
55
57
|
// Update state
|
|
56
58
|
this.state.sessionId = sessionId;
|
|
57
59
|
this.state.sessionTitle = sessionTitle || t("pinned.default_session_title");
|
|
60
|
+
this.state.attachActive = false;
|
|
61
|
+
this.state.attachBusy = false;
|
|
58
62
|
await this.refreshProjectMetadata();
|
|
59
63
|
// Fetch context limit for current model
|
|
60
64
|
await this.fetchContextLimit();
|
|
@@ -73,6 +77,27 @@ class PinnedMessageManager {
|
|
|
73
77
|
// Load existing diffs from API (for session restoration)
|
|
74
78
|
await this.loadDiffsFromApi(sessionId);
|
|
75
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Restore in-memory state for a persisted pinned message without creating a new Telegram message.
|
|
82
|
+
*/
|
|
83
|
+
async restoreExistingSession(sessionId, sessionTitle) {
|
|
84
|
+
logger.info(`[PinnedManager] Restoring existing pinned message for session: ${sessionId}`);
|
|
85
|
+
this.state.sessionId = sessionId;
|
|
86
|
+
this.state.sessionTitle = sessionTitle || t("pinned.default_session_title");
|
|
87
|
+
this.state.attachActive = false;
|
|
88
|
+
this.state.attachBusy = false;
|
|
89
|
+
this.state.changedFiles = [];
|
|
90
|
+
this.lastRenderedMessageText = null;
|
|
91
|
+
this.pendingUpdate = false;
|
|
92
|
+
this.pendingForceUpdate = false;
|
|
93
|
+
await this.refreshProjectMetadata();
|
|
94
|
+
await this.fetchContextLimit();
|
|
95
|
+
if (this.onKeyboardUpdateCallback && this.state.tokensLimit > 0) {
|
|
96
|
+
this.onKeyboardUpdateCallback(this.state.tokensUsed, this.state.tokensLimit);
|
|
97
|
+
}
|
|
98
|
+
await this.updatePinnedMessage(true);
|
|
99
|
+
await this.loadDiffsFromApi(sessionId);
|
|
100
|
+
}
|
|
76
101
|
/**
|
|
77
102
|
* Called when session title is updated (after first message)
|
|
78
103
|
*/
|
|
@@ -83,6 +108,15 @@ class PinnedMessageManager {
|
|
|
83
108
|
await this.updatePinnedMessage();
|
|
84
109
|
}
|
|
85
110
|
}
|
|
111
|
+
async setAttachState(active, busy) {
|
|
112
|
+
const nextBusy = active ? busy : false;
|
|
113
|
+
if (this.state.attachActive === active && this.state.attachBusy === nextBusy) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
this.state.attachActive = active;
|
|
117
|
+
this.state.attachBusy = nextBusy;
|
|
118
|
+
await this.updatePinnedMessage();
|
|
119
|
+
}
|
|
86
120
|
/**
|
|
87
121
|
* Load context token usage from session history
|
|
88
122
|
*/
|
|
@@ -672,6 +706,9 @@ class PinnedMessageManager {
|
|
|
672
706
|
// Just reset state if not initialized
|
|
673
707
|
this.state.messageId = null;
|
|
674
708
|
this.state.sessionId = null;
|
|
709
|
+
this.state.sessionTitle = t("pinned.default_session_title");
|
|
710
|
+
this.state.attachActive = false;
|
|
711
|
+
this.state.attachBusy = false;
|
|
675
712
|
this.state.tokensUsed = 0;
|
|
676
713
|
this.state.tokensLimit = 0;
|
|
677
714
|
this.state.projectPath = "";
|
|
@@ -691,6 +728,8 @@ class PinnedMessageManager {
|
|
|
691
728
|
this.state.messageId = null;
|
|
692
729
|
this.state.sessionId = null;
|
|
693
730
|
this.state.sessionTitle = t("pinned.default_session_title");
|
|
731
|
+
this.state.attachActive = false;
|
|
732
|
+
this.state.attachBusy = false;
|
|
694
733
|
this.state.projectPath = "";
|
|
695
734
|
this.state.projectBranch = null;
|
|
696
735
|
this.state.projectWorktreePath = null;
|