Skip to content

Commit 03e9e69

Browse files
authored
Merge pull request #11 from grinev/release/0.6.1
Release/0.6.1
2 parents 1afc49d + 5f30287 commit 03e9e69

11 files changed

Lines changed: 677 additions & 232 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@grinev/opencode-telegram-bot",
3-
"version": "0.6.0",
3+
"version": "0.6.1",
44
"description": "Telegram bot client for OpenCode to run and monitor coding tasks from chat.",
55
"type": "module",
66
"main": "./dist/index.js",

src/bot/index.ts

Lines changed: 86 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,27 @@ let botInstance: Bot<Context> | null = null;
5959
let chatIdInstance: number | null = null;
6060
let commandsInitialized = false;
6161

62+
const TELEGRAM_DOCUMENT_CAPTION_MAX_LENGTH = 1024;
63+
const __filename = fileURLToPath(import.meta.url);
64+
const __dirname = path.dirname(__filename);
65+
const TEMP_DIR = path.join(__dirname, "..", ".tmp");
66+
67+
function prepareDocumentCaption(caption: string): string {
68+
const normalizedCaption = caption.trim();
69+
if (!normalizedCaption) {
70+
return "";
71+
}
72+
73+
if (normalizedCaption.length <= TELEGRAM_DOCUMENT_CAPTION_MAX_LENGTH) {
74+
return normalizedCaption;
75+
}
76+
77+
return `${normalizedCaption.slice(0, TELEGRAM_DOCUMENT_CAPTION_MAX_LENGTH - 3)}...`;
78+
}
79+
6280
const toolMessageBatcher = new ToolMessageBatcher({
6381
intervalSeconds: 5,
64-
sendMessage: async (sessionId, text) => {
82+
sendText: async (sessionId, text) => {
6583
if (!botInstance || !chatIdInstance) {
6684
return;
6785
}
@@ -75,6 +93,34 @@ const toolMessageBatcher = new ToolMessageBatcher({
7593
disable_notification: true,
7694
});
7795
},
96+
sendFile: async (sessionId, fileData) => {
97+
if (!botInstance || !chatIdInstance) {
98+
return;
99+
}
100+
101+
const currentSession = getCurrentSession();
102+
if (!currentSession || currentSession.id !== sessionId) {
103+
return;
104+
}
105+
106+
const tempFilePath = path.join(TEMP_DIR, fileData.filename);
107+
108+
try {
109+
logger.debug(
110+
`[Bot] Sending code file: ${fileData.filename} (${fileData.buffer.length} bytes, session=${sessionId})`,
111+
);
112+
113+
await fs.mkdir(TEMP_DIR, { recursive: true });
114+
await fs.writeFile(tempFilePath, fileData.buffer);
115+
116+
await botInstance.api.sendDocument(chatIdInstance, new InputFile(tempFilePath), {
117+
caption: fileData.caption,
118+
disable_notification: true,
119+
});
120+
} finally {
121+
await fs.unlink(tempFilePath).catch(() => {});
122+
}
123+
},
78124
});
79125

80126
async function ensureCommandsInitialized(ctx: Context, next: NextFunction): Promise<void> {
@@ -161,10 +207,6 @@ async function ensureEventSubscription(directory: string): Promise<void> {
161207
});
162208

163209
summaryAggregator.setOnTool(async (toolInfo) => {
164-
if (config.bot.hideToolCallMessages) {
165-
return;
166-
}
167-
168210
if (!botInstance || !chatIdInstance) {
169211
logger.error("Bot or chat ID not available for sending tool notification");
170212
return;
@@ -175,6 +217,14 @@ async function ensureEventSubscription(directory: string): Promise<void> {
175217
return;
176218
}
177219

220+
const shouldIncludeToolInfoInFileCaption =
221+
toolInfo.hasFileAttachment &&
222+
(toolInfo.tool === "write" || toolInfo.tool === "edit" || toolInfo.tool === "apply_patch");
223+
224+
if (config.bot.hideToolCallMessages || shouldIncludeToolInfoInFileCaption) {
225+
return;
226+
}
227+
178228
try {
179229
const message = formatToolInfo(toolInfo);
180230
if (message) {
@@ -185,38 +235,25 @@ async function ensureEventSubscription(directory: string): Promise<void> {
185235
}
186236
});
187237

188-
summaryAggregator.setOnToolFile(async (fileData) => {
238+
summaryAggregator.setOnToolFile(async (fileInfo) => {
189239
if (!botInstance || !chatIdInstance) {
190240
logger.error("Bot or chat ID not available for sending file");
191241
return;
192242
}
193243

194244
const currentSession = getCurrentSession();
195-
if (!currentSession) {
245+
if (!currentSession || currentSession.id !== fileInfo.sessionId) {
196246
return;
197247
}
198248

199-
const __filename = fileURLToPath(import.meta.url);
200-
const __dirname = path.dirname(__filename);
201-
const tempDir = path.join(__dirname, "..", ".tmp");
202-
203249
try {
204-
logger.debug(
205-
`[Bot] Sending code file: ${fileData.filename} (${fileData.buffer.length} bytes)`,
206-
);
207-
208-
await fs.mkdir(tempDir, { recursive: true });
250+
const toolMessage = formatToolInfo(fileInfo);
251+
const caption = prepareDocumentCaption(toolMessage || fileInfo.fileData.caption);
209252

210-
const tempFilePath = path.join(tempDir, fileData.filename);
211-
await fs.writeFile(tempFilePath, fileData.buffer);
212-
213-
await botInstance.api.sendDocument(chatIdInstance, new InputFile(tempFilePath), {
214-
caption: fileData.caption,
215-
disable_notification: true,
253+
toolMessageBatcher.enqueueFile(fileInfo.sessionId, {
254+
...fileInfo.fileData,
255+
caption,
216256
});
217-
218-
await fs.unlink(tempFilePath);
219-
logger.debug(`[Bot] Temporary file deleted: ${fileData.filename}`);
220257
} catch (err) {
221258
logger.error("Failed to send file to Telegram:", err);
222259
}
@@ -813,6 +850,16 @@ export function createBot(): Bot<Context> {
813850
}
814851
}
815852

853+
const promptErrorLogContext = {
854+
sessionId: currentSession.id,
855+
directory: currentSession.directory,
856+
agent: currentAgent || "default",
857+
modelProvider: storedModel.providerID || "default",
858+
modelId: storedModel.modelID || "default",
859+
variant: storedModel.variant || "default",
860+
promptLength: text.length,
861+
};
862+
816863
logger.info(`[Bot] Calling session.prompt (fire-and-forget) with agent=${currentAgent}...`);
817864

818865
// CRITICAL: DO NOT wait for session.prompt to complete.
@@ -824,24 +871,26 @@ export function createBot(): Bot<Context> {
824871
task: () => opencodeClient.session.prompt(promptOptions),
825872
onSuccess: ({ error }) => {
826873
if (error) {
827-
const details = formatErrorDetails(error);
828-
logger.error("OpenCode API error:", error);
829-
// Send the error via API directly because ctx is no longer available
830-
void bot.api
831-
.sendMessage(
832-
ctx.chat.id,
833-
t("bot.prompt_send_error_detailed", {
834-
details,
835-
}),
836-
)
837-
.catch(() => {});
874+
const details = formatErrorDetails(error, 6000);
875+
logger.error(
876+
"[Bot] OpenCode API returned an error for session.prompt",
877+
promptErrorLogContext,
878+
);
879+
logger.error("[Bot] session.prompt error details:", details);
880+
logger.error("[Bot] session.prompt raw API error object:", error);
881+
882+
// Send user-friendly error via API directly because ctx is no longer available
883+
void bot.api.sendMessage(ctx.chat.id, t("bot.prompt_send_error")).catch(() => {});
838884
return;
839885
}
840886

841887
logger.info("[Bot] session.prompt completed");
842888
},
843889
onError: (error) => {
844-
logger.error("[Bot] session.prompt background task failed:", error);
890+
const details = formatErrorDetails(error, 6000);
891+
logger.error("[Bot] session.prompt background task failed", promptErrorLogContext);
892+
logger.error("[Bot] session.prompt background failure details:", details);
893+
logger.error("[Bot] session.prompt raw background error object:", error);
845894
void bot.api.sendMessage(ctx.chat.id, t("bot.prompt_send_error")).catch(() => {});
846895
},
847896
});

src/i18n/en.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,7 @@ export const en = {
6161
"⏳ Agent is already running a task. Wait for completion or use /stop to interrupt current run.",
6262
"bot.session_reset_project_mismatch":
6363
"⚠️ Active session does not match the selected project, so it was reset. Use /sessions to pick one or /new to create a new session.",
64-
"bot.prompt_send_error_detailed": "🔴 Failed to send request.\n\nDetails: {details}",
65-
"bot.prompt_send_error": "🔴 An error occurred while sending request to OpenCode.",
64+
"bot.prompt_send_error": "Failed to send request to OpenCode.",
6665
"bot.unknown_command": "⚠️ Unknown command: {command}. Use /help to see available commands.",
6766

6867
"status.header_running": "🟢 **OpenCode Server is running**",

src/i18n/ru.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,7 @@ export const ru: I18nDictionary = {
6060
"⏳ Агент уже выполняет задачу. Дождитесь завершения или используйте /stop, чтобы прервать текущий запуск.",
6161
"bot.session_reset_project_mismatch":
6262
"⚠️ Активная сессия не соответствует выбранному проекту, поэтому была сброшена. Используйте /sessions для выбора или /new для создания новой сессии.",
63-
"bot.prompt_send_error_detailed": "🔴 Ошибка при отправке запроса.\n\nДетали: {details}",
64-
"bot.prompt_send_error": "🔴 Произошла ошибка при отправке запроса в OpenCode.",
63+
"bot.prompt_send_error": "Не удалось отправить запрос в OpenCode.",
6564
"bot.unknown_command": "⚠️ Неизвестная команда: {command}. Используйте /help для списка команд.",
6665

6766
"status.header_running": "🟢 **OpenCode Server запущен**",

0 commit comments

Comments
 (0)