opencode-telegram-bot 1.0.7 → 1.0.8
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/README.md +12 -1
- package/dist/index.js +171 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
A Telegram bot that forwards messages to an [OpenCode](https://opencode.ai) agent and returns the responses. Each chat gets a persistent session, so the agent remembers conversation context across messages.
|
|
4
4
|
|
|
5
|
+
## 🎥 Demo
|
|
6
|
+
|
|
7
|
+
https://github.com/user-attachments/assets/e071f536-7036-4e9c-a5fb-c77414626825
|
|
8
|
+
|
|
5
9
|
## Prerequisites
|
|
6
10
|
|
|
7
11
|
- [Node.js](https://nodejs.org/) 18+
|
|
@@ -118,7 +122,9 @@ The bot also registers these commands in Telegram's command menu (the `/` button
|
|
|
118
122
|
By default, the bot only shows the assistant's final text response. Use `/verbose` to toggle verbose mode (or `/verbose on|off` to set it explicitly), which also displays:
|
|
119
123
|
|
|
120
124
|
- **Thinking/reasoning** -- shown as plain text with a 🧠 prefix, truncated to 500 characters
|
|
121
|
-
- **Tool calls** -- shown as a compact one-line summary (e.g.
|
|
125
|
+
- **Tool calls** -- shown as a compact one-line summary (e.g. `⚙️ read -- src/app.ts`)
|
|
126
|
+
- **Delegations** -- shown when work is delegated to a subagent (e.g. `🧩 Delegated: Find relevant docs`)
|
|
127
|
+
- **Subagent details** -- reasoning/tool calls from subagents when available, plus a short `ℹ️ Subagent responded` notice
|
|
122
128
|
|
|
123
129
|
Verbose mode is per-chat and persists across bot restarts. Use `/verbose` again to turn it off.
|
|
124
130
|
|
|
@@ -130,6 +136,11 @@ Example with verbose mode on:
|
|
|
130
136
|
⚙️ grep -- pattern: "authenticate" in src/
|
|
131
137
|
⚙️ read -- src/auth/handler.ts
|
|
132
138
|
|
|
139
|
+
🧩 Delegated: Search auth docs
|
|
140
|
+
🧠 Thinking (agent: subagent): Scanning docs for auth references...
|
|
141
|
+
⚙️ read -- README.md (agent: subagent)
|
|
142
|
+
ℹ️ Subagent responded
|
|
143
|
+
|
|
133
144
|
Here's what I found in the auth module...
|
|
134
145
|
```
|
|
135
146
|
|
package/dist/index.js
CHANGED
|
@@ -18974,6 +18974,63 @@ async function startTelegram(options) {
|
|
|
18974
18974
|
let processingMsgDeleted = false;
|
|
18975
18975
|
const sentToolIds = new Set();
|
|
18976
18976
|
const sentReasoningIds = new Set();
|
|
18977
|
+
const sentSubtaskIds = new Set();
|
|
18978
|
+
let sawDelegation = false;
|
|
18979
|
+
let sentSubagentDetails = false;
|
|
18980
|
+
let sentSubagentResponseNotice = false;
|
|
18981
|
+
function extractTaskId(outputText) {
|
|
18982
|
+
const match = outputText.match(/task[_-]?id:\s*([\w-]+)/i);
|
|
18983
|
+
return match ? match[1] : null;
|
|
18984
|
+
}
|
|
18985
|
+
async function emitSubagentMessages(taskSessionId) {
|
|
18986
|
+
try {
|
|
18987
|
+
const messagesResult = await client.session.messages({
|
|
18988
|
+
path: { id: taskSessionId },
|
|
18989
|
+
});
|
|
18990
|
+
if (messagesResult.error || !messagesResult.data) {
|
|
18991
|
+
console.warn(`[Telegram] Failed to fetch subagent messages for ${taskSessionId}: ${JSON.stringify(messagesResult.error)}`);
|
|
18992
|
+
return;
|
|
18993
|
+
}
|
|
18994
|
+
for (const msg of messagesResult.data) {
|
|
18995
|
+
const info = msg.info;
|
|
18996
|
+
if (info?.role !== "assistant")
|
|
18997
|
+
continue;
|
|
18998
|
+
const parts = msg.parts || [];
|
|
18999
|
+
for (const part of parts) {
|
|
19000
|
+
if (part.type === "reasoning") {
|
|
19001
|
+
const text = part.text;
|
|
19002
|
+
if (!text)
|
|
19003
|
+
continue;
|
|
19004
|
+
const truncated = truncate(text.replace(/\n/g, " "), 500);
|
|
19005
|
+
await sendTelegramMessage(chatId, `\u{1F9E0} Thinking (agent: subagent): ${truncated}`, true);
|
|
19006
|
+
sentSubagentDetails = true;
|
|
19007
|
+
}
|
|
19008
|
+
if (part.type === "tool") {
|
|
19009
|
+
const tool = part.tool;
|
|
19010
|
+
const state = part.state;
|
|
19011
|
+
if (!state)
|
|
19012
|
+
continue;
|
|
19013
|
+
if (state.status !== "completed" && state.status !== "error")
|
|
19014
|
+
continue;
|
|
19015
|
+
const summary = summarizeTool(tool, state.input);
|
|
19016
|
+
let line = `\u{2699}\u{FE0F} ${summary} (agent: subagent)`;
|
|
19017
|
+
if (state.status === "error") {
|
|
19018
|
+
line += ` \u{274C}`;
|
|
19019
|
+
}
|
|
19020
|
+
await sendTelegramMessage(chatId, line, true);
|
|
19021
|
+
sentSubagentDetails = true;
|
|
19022
|
+
}
|
|
19023
|
+
}
|
|
19024
|
+
}
|
|
19025
|
+
if (!sentSubagentResponseNotice) {
|
|
19026
|
+
await sendTelegramMessage(chatId, "ℹ️ Subagent responded", true);
|
|
19027
|
+
sentSubagentResponseNotice = true;
|
|
19028
|
+
}
|
|
19029
|
+
}
|
|
19030
|
+
catch (err) {
|
|
19031
|
+
console.warn("[Telegram] Error fetching subagent messages:", err);
|
|
19032
|
+
}
|
|
19033
|
+
}
|
|
18977
19034
|
// Buffer the latest reasoning text -- we send it when a non-reasoning part arrives
|
|
18978
19035
|
// so we get the complete thinking block rather than a partial one
|
|
18979
19036
|
let pendingReasoning = null;
|
|
@@ -18999,7 +19056,10 @@ async function startTelegram(options) {
|
|
|
18999
19056
|
sentReasoningIds.add(pendingReasoning.id);
|
|
19000
19057
|
await deleteProcessingMsg();
|
|
19001
19058
|
const truncated = truncate(pendingReasoning.text.replace(/\n/g, " "), 500);
|
|
19002
|
-
|
|
19059
|
+
const agentLabel = pendingReasoning.agent
|
|
19060
|
+
? ` (agent: ${pendingReasoning.agent})`
|
|
19061
|
+
: "";
|
|
19062
|
+
await sendTelegramMessage(chatId, `\u{1F9E0} Thinking${agentLabel}: ${truncated}`, true);
|
|
19003
19063
|
}
|
|
19004
19064
|
pendingReasoning = null;
|
|
19005
19065
|
}
|
|
@@ -19008,14 +19068,26 @@ async function startTelegram(options) {
|
|
|
19008
19068
|
const ev = event;
|
|
19009
19069
|
if (ev.type === "message.part.updated" && ev.properties) {
|
|
19010
19070
|
const part = ev.properties.part;
|
|
19011
|
-
|
|
19012
|
-
|
|
19071
|
+
const partSessionId = part.sessionID;
|
|
19072
|
+
const parentSessionId = part.parentSessionID ||
|
|
19073
|
+
part.parentSessionId;
|
|
19074
|
+
const rootSessionId = part.rootSessionID ||
|
|
19075
|
+
part.rootSessionId;
|
|
19076
|
+
const isPrimarySession = partSessionId === sessionId;
|
|
19077
|
+
const isChildSession = parentSessionId === sessionId || rootSessionId === sessionId;
|
|
19078
|
+
// Only process events for our session (or subagent sessions that link back)
|
|
19079
|
+
if (!isPrimarySession && !isChildSession)
|
|
19013
19080
|
continue;
|
|
19081
|
+
if (!isPrimarySession && isChildSession && verbose) {
|
|
19082
|
+
// Subagent events are allowed to stream in verbose mode
|
|
19083
|
+
}
|
|
19014
19084
|
const partText = part.text;
|
|
19015
19085
|
if (part.type === "reasoning" && part.id) {
|
|
19016
19086
|
// Buffer reasoning -- keep updating with the latest full text
|
|
19017
19087
|
if (partText) {
|
|
19018
|
-
|
|
19088
|
+
const agent = part.agent ||
|
|
19089
|
+
(!isPrimarySession ? "subagent" : undefined);
|
|
19090
|
+
pendingReasoning = { id: part.id, text: partText, agent };
|
|
19019
19091
|
}
|
|
19020
19092
|
}
|
|
19021
19093
|
else if (part.type === "tool" && part.id) {
|
|
@@ -19030,12 +19102,74 @@ async function startTelegram(options) {
|
|
|
19030
19102
|
sentToolIds.add(part.id);
|
|
19031
19103
|
await deleteProcessingMsg();
|
|
19032
19104
|
const tool = part.tool;
|
|
19033
|
-
const
|
|
19034
|
-
|
|
19035
|
-
if (
|
|
19036
|
-
|
|
19105
|
+
const agent = part.agent ||
|
|
19106
|
+
(!isPrimarySession ? "subagent" : undefined);
|
|
19107
|
+
if (tool === "task") {
|
|
19108
|
+
const input = state.input;
|
|
19109
|
+
const description = input?.description || input?.prompt || summarizeTool(tool, state.input);
|
|
19110
|
+
let line = `\u{1F9E9} Delegated`;
|
|
19111
|
+
if (description) {
|
|
19112
|
+
line += `: ${truncate(description, 120)}`;
|
|
19113
|
+
}
|
|
19114
|
+
if (agent) {
|
|
19115
|
+
line += ` (agent: ${agent})`;
|
|
19116
|
+
}
|
|
19117
|
+
if (state.status === "error") {
|
|
19118
|
+
line += ` \u{274C}`;
|
|
19119
|
+
}
|
|
19120
|
+
await sendTelegramMessage(chatId, line, true);
|
|
19121
|
+
sawDelegation = true;
|
|
19122
|
+
if (state.output && verbose) {
|
|
19123
|
+
const rawOutput = typeof state.output === "string"
|
|
19124
|
+
? state.output
|
|
19125
|
+
: JSON.stringify(state.output, null, 2);
|
|
19126
|
+
const outputText = rawOutput.trim();
|
|
19127
|
+
if (outputText) {
|
|
19128
|
+
const taskSessionId = extractTaskId(outputText);
|
|
19129
|
+
if (taskSessionId) {
|
|
19130
|
+
await emitSubagentMessages(taskSessionId);
|
|
19131
|
+
}
|
|
19132
|
+
if (!sentSubagentDetails && !sentSubagentResponseNotice) {
|
|
19133
|
+
await sendTelegramMessage(chatId, "ℹ️ Subagent responded (no thought/tool details available)", true);
|
|
19134
|
+
sentSubagentResponseNotice = true;
|
|
19135
|
+
}
|
|
19136
|
+
}
|
|
19137
|
+
}
|
|
19138
|
+
}
|
|
19139
|
+
else {
|
|
19140
|
+
const summary = summarizeTool(tool, state.input);
|
|
19141
|
+
let line = `\u{2699}\u{FE0F} ${summary}`;
|
|
19142
|
+
if (agent) {
|
|
19143
|
+
line += ` (agent: ${agent})`;
|
|
19144
|
+
}
|
|
19145
|
+
if (state.status === "error") {
|
|
19146
|
+
line += ` \u{274C}`;
|
|
19147
|
+
}
|
|
19148
|
+
await sendTelegramMessage(chatId, line, true);
|
|
19149
|
+
}
|
|
19150
|
+
}
|
|
19151
|
+
}
|
|
19152
|
+
}
|
|
19153
|
+
else if (part.type === "subtask") {
|
|
19154
|
+
await flushReasoning();
|
|
19155
|
+
if (verbose) {
|
|
19156
|
+
const id = part.id;
|
|
19157
|
+
if (!id || !sentSubtaskIds.has(id)) {
|
|
19158
|
+
if (id)
|
|
19159
|
+
sentSubtaskIds.add(id);
|
|
19160
|
+
await deleteProcessingMsg();
|
|
19161
|
+
const description = part.description;
|
|
19162
|
+
const agent = part.agent ||
|
|
19163
|
+
(!isPrimarySession ? "subagent" : undefined);
|
|
19164
|
+
let line = "\u{1F9E9} Delegated";
|
|
19165
|
+
if (description) {
|
|
19166
|
+
line += `: ${truncate(description, 120)}`;
|
|
19167
|
+
}
|
|
19168
|
+
if (agent) {
|
|
19169
|
+
line += ` (agent: ${agent})`;
|
|
19037
19170
|
}
|
|
19038
19171
|
await sendTelegramMessage(chatId, line, true);
|
|
19172
|
+
sawDelegation = true;
|
|
19039
19173
|
}
|
|
19040
19174
|
}
|
|
19041
19175
|
}
|
|
@@ -19076,6 +19210,35 @@ async function startTelegram(options) {
|
|
|
19076
19210
|
if (!finalText) {
|
|
19077
19211
|
finalText = "The agent returned an empty response.";
|
|
19078
19212
|
}
|
|
19213
|
+
if (verbose && sawDelegation) {
|
|
19214
|
+
const lines = finalText.split("\n");
|
|
19215
|
+
let inTaskResult = false;
|
|
19216
|
+
const filtered = lines.filter((line) => {
|
|
19217
|
+
const trimmed = line.trim();
|
|
19218
|
+
if (/^<task_result>$/i.test(trimmed)) {
|
|
19219
|
+
inTaskResult = true;
|
|
19220
|
+
return false;
|
|
19221
|
+
}
|
|
19222
|
+
if (/^<\/task_result>$/i.test(trimmed)) {
|
|
19223
|
+
inTaskResult = false;
|
|
19224
|
+
return false;
|
|
19225
|
+
}
|
|
19226
|
+
if (inTaskResult)
|
|
19227
|
+
return false;
|
|
19228
|
+
if (/^Subagent response:/i.test(trimmed))
|
|
19229
|
+
return false;
|
|
19230
|
+
if (/^Subagent (ran|returned|reported)/i.test(trimmed))
|
|
19231
|
+
return false;
|
|
19232
|
+
if (/^Ran the subagent/i.test(trimmed))
|
|
19233
|
+
return false;
|
|
19234
|
+
return true;
|
|
19235
|
+
});
|
|
19236
|
+
const cleaned = filtered.join("\n").trim();
|
|
19237
|
+
if (!cleaned) {
|
|
19238
|
+
return;
|
|
19239
|
+
}
|
|
19240
|
+
finalText = cleaned;
|
|
19241
|
+
}
|
|
19079
19242
|
const chunks = splitMessage(finalText, 4096);
|
|
19080
19243
|
for (const chunk of chunks) {
|
|
19081
19244
|
await sendTelegramMessage(chatId, chunk);
|