@grinev/opencode-telegram-bot 0.22.0 → 0.22.1
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/dist/app/services/file-download-service.js +51 -2
- package/dist/bot/callbacks/scheduled-task-callback-handler.js +20 -1
- package/dist/bot/handlers/document-handler.js +1 -1
- package/dist/bot/handlers/media-group-handler.js +1 -1
- package/dist/bot/streaming/compact-progress-streamer.js +17 -11
- package/dist/opencode/process.js +40 -3
- package/package.json +1 -1
|
@@ -79,12 +79,61 @@ const APPLICATION_TEXT_MIME_TYPES = new Set([
|
|
|
79
79
|
"application/x-yaml",
|
|
80
80
|
"application/sql",
|
|
81
81
|
]);
|
|
82
|
-
|
|
82
|
+
const TEXT_FILE_EXTENSIONS = new Set([
|
|
83
|
+
"svelte",
|
|
84
|
+
"vue",
|
|
85
|
+
"ts",
|
|
86
|
+
"tsx",
|
|
87
|
+
"jsx",
|
|
88
|
+
"mjs",
|
|
89
|
+
"cjs",
|
|
90
|
+
"go",
|
|
91
|
+
"rs",
|
|
92
|
+
"rb",
|
|
93
|
+
"py",
|
|
94
|
+
"java",
|
|
95
|
+
"c",
|
|
96
|
+
"cpp",
|
|
97
|
+
"h",
|
|
98
|
+
"hpp",
|
|
99
|
+
"cs",
|
|
100
|
+
"swift",
|
|
101
|
+
"kt",
|
|
102
|
+
"kts",
|
|
103
|
+
"sh",
|
|
104
|
+
"bash",
|
|
105
|
+
"yaml",
|
|
106
|
+
"yml",
|
|
107
|
+
"toml",
|
|
108
|
+
"ini",
|
|
109
|
+
"cfg",
|
|
110
|
+
"md",
|
|
111
|
+
"mdx",
|
|
112
|
+
"css",
|
|
113
|
+
"scss",
|
|
114
|
+
"less",
|
|
115
|
+
"html",
|
|
116
|
+
"htm",
|
|
117
|
+
"graphql",
|
|
118
|
+
"gql",
|
|
119
|
+
"proto",
|
|
120
|
+
"gradle",
|
|
121
|
+
]);
|
|
122
|
+
export function isTextMimeType(mimeType, filename) {
|
|
83
123
|
if (!mimeType) {
|
|
84
124
|
return false;
|
|
85
125
|
}
|
|
86
126
|
if (mimeType.startsWith("text/")) {
|
|
87
127
|
return true;
|
|
88
128
|
}
|
|
89
|
-
|
|
129
|
+
if (APPLICATION_TEXT_MIME_TYPES.has(mimeType)) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
if (filename) {
|
|
133
|
+
const ext = filename.split(".").pop()?.toLowerCase();
|
|
134
|
+
if (ext && TEXT_FILE_EXTENSIONS.has(ext)) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return false;
|
|
90
139
|
}
|
|
@@ -100,12 +100,31 @@ function formatDateTime(dateIso, timezone) {
|
|
|
100
100
|
return dateIso;
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
+
const TASK_DETAIL_PROMPT_BYTE_BUDGET = 3400;
|
|
104
|
+
function truncatePromptForDetails(prompt) {
|
|
105
|
+
if (Buffer.byteLength(prompt, "utf-8") <= TASK_DETAIL_PROMPT_BYTE_BUDGET) {
|
|
106
|
+
return prompt;
|
|
107
|
+
}
|
|
108
|
+
const budget = TASK_DETAIL_PROMPT_BYTE_BUDGET - 3;
|
|
109
|
+
let lo = 0;
|
|
110
|
+
let hi = prompt.length;
|
|
111
|
+
while (lo < hi) {
|
|
112
|
+
const mid = (lo + hi + 1) >>> 1;
|
|
113
|
+
if (Buffer.byteLength(prompt.slice(0, mid), "utf-8") <= budget) {
|
|
114
|
+
lo = mid;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
hi = mid - 1;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return `${prompt.slice(0, lo)}...`;
|
|
121
|
+
}
|
|
103
122
|
function formatTaskDetails(task) {
|
|
104
123
|
const variant = task.model.variant ? ` (${task.model.variant})` : "";
|
|
105
124
|
const model = `${task.model.providerID}/${task.model.modelID}${variant}`;
|
|
106
125
|
const cronLine = task.kind === "cron" ? `${t("tasklist.details.cron", { cron: task.cron })}\n` : "";
|
|
107
126
|
return t("tasklist.details", {
|
|
108
|
-
prompt: task.prompt,
|
|
127
|
+
prompt: truncatePromptForDetails(task.prompt),
|
|
109
128
|
project: `${task.projectWorktree}\n${t("status.line.model", { model })}`,
|
|
110
129
|
schedule: task.scheduleSummary,
|
|
111
130
|
cronLine,
|
|
@@ -18,7 +18,7 @@ export async function handleDocumentMessage(ctx, deps) {
|
|
|
18
18
|
const mimeType = doc.mime_type || "";
|
|
19
19
|
const filename = doc.file_name || "document";
|
|
20
20
|
try {
|
|
21
|
-
if (isTextMimeType(mimeType)) {
|
|
21
|
+
if (isTextMimeType(mimeType, filename)) {
|
|
22
22
|
if (!isFileSizeAllowed(doc.file_size, config.files.maxFileSizeKb)) {
|
|
23
23
|
logger.warn(`[Document] Text file too large: ${filename} (${doc.file_size} bytes > ${config.files.maxFileSizeKb}KB)`);
|
|
24
24
|
await ctx.reply(t("bot.text_file_too_large", { maxSizeKb: String(config.files.maxFileSizeKb) }));
|
|
@@ -137,7 +137,7 @@ export class MediaGroupAttachmentHandler {
|
|
|
137
137
|
const document = item.document;
|
|
138
138
|
const mimeType = document.mime_type || "";
|
|
139
139
|
const filename = document.file_name || "document";
|
|
140
|
-
if (isTextMimeType(mimeType)) {
|
|
140
|
+
if (isTextMimeType(mimeType, filename)) {
|
|
141
141
|
if (!isFileSizeAllowed(document.file_size, config.files.maxFileSizeKb)) {
|
|
142
142
|
return { reason: "text_file_too_large" };
|
|
143
143
|
}
|
|
@@ -26,19 +26,10 @@ export class CompactProgressStreamer {
|
|
|
26
26
|
this.editText = editText;
|
|
27
27
|
}
|
|
28
28
|
updateActivity(sessionId, activity) {
|
|
29
|
-
|
|
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);
|
|
29
|
+
this.updateActivityState(sessionId, activity, true);
|
|
39
30
|
}
|
|
40
31
|
updateThinking(sessionId) {
|
|
41
|
-
this.
|
|
32
|
+
this.updateActivityState(sessionId, t("progress.compact.thinking"), false);
|
|
42
33
|
}
|
|
43
34
|
updateResponding(sessionId) {
|
|
44
35
|
this.updateActivity(sessionId, t("progress.compact.responding"));
|
|
@@ -105,6 +96,21 @@ export class CompactProgressStreamer {
|
|
|
105
96
|
this.states.set(sessionId, state);
|
|
106
97
|
return state;
|
|
107
98
|
}
|
|
99
|
+
updateActivityState(sessionId, activity, createIfMissing) {
|
|
100
|
+
const normalizedActivity = activity.trim();
|
|
101
|
+
if (!sessionId || !normalizedActivity) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const state = createIfMissing ? this.getOrCreateState(sessionId) : this.states.get(sessionId);
|
|
105
|
+
if (!state) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
state.latestText = t("progress.compact.activity", {
|
|
109
|
+
header: t("progress.compact.working_header"),
|
|
110
|
+
activity: normalizedActivity,
|
|
111
|
+
});
|
|
112
|
+
this.ensureTimer(state);
|
|
113
|
+
}
|
|
108
114
|
ensureTimer(state) {
|
|
109
115
|
if (state.cancelled || state.timer) {
|
|
110
116
|
return;
|
package/dist/opencode/process.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { exec, spawn } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
2
4
|
import { promisify } from "node:util";
|
|
3
5
|
const execAsync = promisify(exec);
|
|
4
6
|
const DEFAULT_OPENCODE_PORT = 4096;
|
|
@@ -25,13 +27,48 @@ export function resolveLocalOpencodeTarget(apiUrl) {
|
|
|
25
27
|
return null;
|
|
26
28
|
}
|
|
27
29
|
}
|
|
30
|
+
function resolveWindowsOpencodeExe() {
|
|
31
|
+
// npm on Windows usually puts opencode.cmd on PATH (not opencode.exe).
|
|
32
|
+
// We locate the shim and derive the real exe path from its directory.
|
|
33
|
+
const pathEnv = process.env.PATH ?? "";
|
|
34
|
+
const pathEntries = pathEnv.split(path.delimiter).filter(Boolean);
|
|
35
|
+
for (const entry of pathEntries) {
|
|
36
|
+
const opencodeCmd = path.join(entry, "opencode.cmd");
|
|
37
|
+
if (!existsSync(opencodeCmd)) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const candidateExe = path.join(entry, "node_modules", "opencode-ai", "bin", "opencode.exe");
|
|
41
|
+
if (existsSync(candidateExe)) {
|
|
42
|
+
return candidateExe;
|
|
43
|
+
}
|
|
44
|
+
// Found the shim but not the exe where it usually lives. Stop searching.
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
return "";
|
|
48
|
+
}
|
|
28
49
|
export function createOpencodeServeSpawnCommand(target) {
|
|
29
50
|
const isWindows = process.platform === "win32";
|
|
30
51
|
const port = target.port.toString();
|
|
52
|
+
if (isWindows) {
|
|
53
|
+
const resolvedExe = resolveWindowsOpencodeExe();
|
|
54
|
+
if (resolvedExe) {
|
|
55
|
+
return {
|
|
56
|
+
command: resolvedExe,
|
|
57
|
+
args: ["serve", "--port", port],
|
|
58
|
+
windowsHide: true,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// Safe fallback: works with default npm installs where only opencode.cmd is on PATH.
|
|
62
|
+
return {
|
|
63
|
+
command: "cmd.exe",
|
|
64
|
+
args: ["/c", "opencode", "serve", "--port", port],
|
|
65
|
+
windowsHide: true,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
31
68
|
return {
|
|
32
|
-
command:
|
|
33
|
-
args:
|
|
34
|
-
windowsHide:
|
|
69
|
+
command: "opencode",
|
|
70
|
+
args: ["serve", "--port", port],
|
|
71
|
+
windowsHide: false,
|
|
35
72
|
};
|
|
36
73
|
}
|
|
37
74
|
export function startLocalOpencodeServer(target) {
|