diff --git a/src/bot/events/telegram-event-delivery.ts b/src/bot/events/telegram-event-delivery.ts index bf14b4f73..88f93ec4b 100644 --- a/src/bot/events/telegram-event-delivery.ts +++ b/src/bot/events/telegram-event-delivery.ts @@ -1,6 +1,6 @@ import { promises as fs } from "fs"; +import * as os from "os"; import * as path from "path"; -import { fileURLToPath } from "url"; import { InputFile, type Bot, type Context } from "grammy"; import type { CodeFileData } from "../../app/formatters/summary-formatter.js"; import { logger } from "../../utils/logger.js"; @@ -14,9 +14,10 @@ import { } from "../messages/telegram-text.js"; import type { TelegramRenderedPart } from "../render/types.js"; -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const TEMP_DIR = path.join(__dirname, "..", "..", ".tmp"); +// The package directory can be read-only (a global npm install is owned by +// root), so outgoing files belong under the OS temp directory, not next to the +// compiled code. A failed mkdir there broke every tool-output upload. +const TEMP_DIR = path.join(os.tmpdir(), "opencode-telegram-bot"); type TelegramApi = Bot["api"]; type SendMessageOptions = Parameters[2]; diff --git a/tests/bot/events/telegram-event-delivery.test.ts b/tests/bot/events/telegram-event-delivery.test.ts index 0479edf3d..b34e3005d 100644 --- a/tests/bot/events/telegram-event-delivery.test.ts +++ b/tests/bot/events/telegram-event-delivery.test.ts @@ -1,3 +1,6 @@ +import { promises as fs } from "fs"; +import * as os from "os"; +import * as path from "path"; import { describe, expect, it, vi } from "vitest"; import { TelegramEventDelivery, @@ -10,6 +13,7 @@ function createDestination() { sendMessageDraft: vi.fn().mockResolvedValue(true), editMessageText: vi.fn().mockResolvedValue(undefined), deleteMessage: vi.fn().mockResolvedValue(true), + sendDocument: vi.fn().mockResolvedValue({ message_id: 11 }), }; return { api, destination: { api, chatId: 42 } as unknown as TelegramDestination }; @@ -91,4 +95,28 @@ describe("bot/events/telegram-event-delivery", () => { expect(api.sendMessage).toHaveBeenNthCalledWith(2, 42, "draft reply", agentPreview); expect(api.sendMessage).toHaveBeenNthCalledWith(3, 42, "🌐 webfetch https://example.com/"); }); + + it("stages outgoing documents in the OS temp directory, not beside the installed package", async () => { + const { destination } = createDestination(); + const writeFile = vi.spyOn(fs, "writeFile").mockResolvedValue(undefined); + + let stagedPath = ""; + writeFile.mockImplementation((filePath) => { + stagedPath = String(filePath); + return Promise.resolve(); + }); + + try { + await new TelegramEventDelivery().sendDocument(destination, { + buffer: Buffer.from("diff"), + filename: "write_app.ts.txt", + caption: "", + }); + } finally { + writeFile.mockRestore(); + } + + expect(stagedPath).not.toBe(""); + expect(path.dirname(stagedPath)).toBe(path.join(os.tmpdir(), "opencode-telegram-bot")); + }); });