opencode-telegram-bot 1.0.0 → 1.0.2
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 +13 -0
- package/dist/index.js +279 -47
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -103,8 +103,21 @@ These are handled directly by the Telegram bot:
|
|
|
103
103
|
| `/switch <id>` | Switch to a different session (prefix match supported) |
|
|
104
104
|
| `/title <text>` | Rename the current session |
|
|
105
105
|
| `/delete <id>` | Delete a session from the server |
|
|
106
|
+
| `/export` | Export the current session as a markdown file |
|
|
107
|
+
| `/export full` | Export with all details (thinking, costs, steps) |
|
|
106
108
|
| `/help` | Show available commands |
|
|
107
109
|
|
|
110
|
+
### Session Export
|
|
111
|
+
|
|
112
|
+
The `/export` command builds a markdown file from the current session and saves it to the directory where OpenCode is running. The file is also sent back to you as a Telegram document.
|
|
113
|
+
|
|
114
|
+
Two modes are available:
|
|
115
|
+
|
|
116
|
+
- **`/export`** -- Default. Includes user messages, assistant text, and tool calls (name, input, output).
|
|
117
|
+
- **`/export full`** (also accepts `detailed` or `all`) -- Includes everything from the default mode plus reasoning/thinking blocks, step boundaries with token counts, costs, subtasks, retries, and compaction markers.
|
|
118
|
+
|
|
119
|
+
The exported file is named `session-<id>.md` (or `session-<id>-detailed.md` for the full export).
|
|
120
|
+
|
|
108
121
|
### OpenCode Commands
|
|
109
122
|
|
|
110
123
|
Any `/` command that isn't a bot command is automatically forwarded to the OpenCode server via its command system. The bot fetches the list of available commands on startup and validates them.
|
package/dist/index.js
CHANGED
|
@@ -18608,7 +18608,7 @@ async function startTelegram(options) {
|
|
|
18608
18608
|
}
|
|
18609
18609
|
// Telegram-only commands that should not be forwarded to OpenCode
|
|
18610
18610
|
const telegramCommands = new Set([
|
|
18611
|
-
"start", "help", "new", "sessions", "switch", "title", "delete",
|
|
18611
|
+
"start", "help", "new", "sessions", "switch", "title", "delete", "export",
|
|
18612
18612
|
]);
|
|
18613
18613
|
// Map of chatId -> sessionId for the active session per chat
|
|
18614
18614
|
const chatSessions = new Map();
|
|
@@ -18842,9 +18842,11 @@ async function startTelegram(options) {
|
|
|
18842
18842
|
"Bot commands:\n" +
|
|
18843
18843
|
"/new - Start a new conversation\n" +
|
|
18844
18844
|
"/sessions - List your sessions\n" +
|
|
18845
|
-
"/switch <
|
|
18845
|
+
"/switch <number> - Switch to a different session\n" +
|
|
18846
18846
|
"/title <text> - Rename the current session\n" +
|
|
18847
|
-
"/delete <
|
|
18847
|
+
"/delete <number> - Delete a session\n" +
|
|
18848
|
+
"/export - Export the current session as a markdown file\n" +
|
|
18849
|
+
"/export full - Export with all details (thinking, costs, steps)\n" +
|
|
18848
18850
|
"/help - Show this help message\n";
|
|
18849
18851
|
if (opencodeCommands.size > 0) {
|
|
18850
18852
|
msg +=
|
|
@@ -18859,9 +18861,11 @@ async function startTelegram(options) {
|
|
|
18859
18861
|
"Bot commands:\n" +
|
|
18860
18862
|
"/new - Start a new conversation\n" +
|
|
18861
18863
|
"/sessions - List your sessions\n" +
|
|
18862
|
-
"/switch <
|
|
18864
|
+
"/switch <number> - Switch to a different session\n" +
|
|
18863
18865
|
"/title <text> - Rename the current session\n" +
|
|
18864
|
-
"/delete <
|
|
18866
|
+
"/delete <number> - Delete a session\n" +
|
|
18867
|
+
"/export - Export the current session as a markdown file\n" +
|
|
18868
|
+
"/export full - Export with all details (thinking, costs, steps)\n" +
|
|
18865
18869
|
"/help - Show this help message\n";
|
|
18866
18870
|
if (opencodeCommands.size > 0) {
|
|
18867
18871
|
msg +=
|
|
@@ -18878,34 +18882,49 @@ async function startTelegram(options) {
|
|
|
18878
18882
|
console.log(`[Telegram] Session cleared for chat ${chatId}`);
|
|
18879
18883
|
ctx.reply("Conversation reset. Your next message will start a new session.");
|
|
18880
18884
|
});
|
|
18885
|
+
/**
|
|
18886
|
+
* Get the list of known sessions, sorted by most recently updated.
|
|
18887
|
+
*/
|
|
18888
|
+
async function getKnownSessions() {
|
|
18889
|
+
const list = await client.session.list();
|
|
18890
|
+
if (!list.data || list.data.length === 0)
|
|
18891
|
+
return [];
|
|
18892
|
+
return list.data
|
|
18893
|
+
.filter((s) => knownSessionIds.has(s.id))
|
|
18894
|
+
.sort((a, b) => b.time.updated - a.time.updated);
|
|
18895
|
+
}
|
|
18896
|
+
/**
|
|
18897
|
+
* Resolve a user argument to a session. Accepts either a numeric index
|
|
18898
|
+
* (1-based, as shown by /sessions) or a session ID / prefix.
|
|
18899
|
+
*/
|
|
18900
|
+
function resolveSession(sessions, arg) {
|
|
18901
|
+
// Try numeric index first
|
|
18902
|
+
const num = parseInt(arg, 10);
|
|
18903
|
+
if (!isNaN(num) && String(num) === arg && num >= 1 && num <= sessions.length) {
|
|
18904
|
+
return sessions[num - 1];
|
|
18905
|
+
}
|
|
18906
|
+
// Fall back to ID / prefix match
|
|
18907
|
+
return sessions.find((s) => s.id === arg || s.id.startsWith(arg));
|
|
18908
|
+
}
|
|
18881
18909
|
// Handle /sessions command - list Telegram bot sessions only
|
|
18882
18910
|
bot.command("sessions", async (ctx) => {
|
|
18883
18911
|
const chatId = ctx.chat.id.toString();
|
|
18884
18912
|
const activeSessionId = chatSessions.get(chatId);
|
|
18885
18913
|
try {
|
|
18886
|
-
const
|
|
18887
|
-
if (!list.data || list.data.length === 0) {
|
|
18888
|
-
await ctx.reply("No sessions found.");
|
|
18889
|
-
return;
|
|
18890
|
-
}
|
|
18891
|
-
// Filter to only sessions created by this bot
|
|
18892
|
-
const sessions = list.data
|
|
18893
|
-
.filter((s) => knownSessionIds.has(s.id))
|
|
18894
|
-
.sort((a, b) => b.time.updated - a.time.updated);
|
|
18914
|
+
const sessions = await getKnownSessions();
|
|
18895
18915
|
if (sessions.length === 0) {
|
|
18896
18916
|
await ctx.reply("No sessions found.");
|
|
18897
18917
|
return;
|
|
18898
18918
|
}
|
|
18899
18919
|
let msg = "Your sessions:\n\n";
|
|
18900
|
-
|
|
18920
|
+
sessions.forEach((session, i) => {
|
|
18901
18921
|
const isActive = session.id === activeSessionId;
|
|
18902
18922
|
const age = formatAge(session.time.updated);
|
|
18903
|
-
const
|
|
18904
|
-
|
|
18905
|
-
|
|
18906
|
-
|
|
18907
|
-
msg += `\nUse /
|
|
18908
|
-
msg += `\nUse /delete <id> to delete a session.`;
|
|
18923
|
+
const marker = isActive ? " [active]" : "";
|
|
18924
|
+
msg += `${i + 1}. ${session.title} - ${age}${marker}\n`;
|
|
18925
|
+
});
|
|
18926
|
+
msg += `\nUse /switch <number> to switch sessions.`;
|
|
18927
|
+
msg += `\nUse /delete <number> to delete a session.`;
|
|
18909
18928
|
await ctx.reply(msg);
|
|
18910
18929
|
}
|
|
18911
18930
|
catch (err) {
|
|
@@ -18913,24 +18932,17 @@ async function startTelegram(options) {
|
|
|
18913
18932
|
await ctx.reply("Failed to list sessions.");
|
|
18914
18933
|
}
|
|
18915
18934
|
});
|
|
18916
|
-
// Handle /switch <
|
|
18935
|
+
// Handle /switch <number> command - switch to a different session
|
|
18917
18936
|
bot.command("switch", async (ctx) => {
|
|
18918
18937
|
const chatId = ctx.chat.id.toString();
|
|
18919
18938
|
const args = ctx.message.text.replace(/^\/switch\s*/, "").trim();
|
|
18920
18939
|
if (!args) {
|
|
18921
|
-
await ctx.reply("Usage: /switch <
|
|
18940
|
+
await ctx.reply("Usage: /switch <number>\n\nUse /sessions to see available sessions.");
|
|
18922
18941
|
return;
|
|
18923
18942
|
}
|
|
18924
18943
|
try {
|
|
18925
|
-
|
|
18926
|
-
const
|
|
18927
|
-
if (!list.data) {
|
|
18928
|
-
await ctx.reply("Failed to fetch sessions from the server.");
|
|
18929
|
-
return;
|
|
18930
|
-
}
|
|
18931
|
-
const match = list.data
|
|
18932
|
-
.filter((s) => knownSessionIds.has(s.id))
|
|
18933
|
-
.find((s) => s.id === args || s.id.startsWith(args));
|
|
18944
|
+
const sessions = await getKnownSessions();
|
|
18945
|
+
const match = resolveSession(sessions, args);
|
|
18934
18946
|
if (!match) {
|
|
18935
18947
|
await ctx.reply(`No session found matching "${args}".\n\nUse /sessions to see available sessions.`);
|
|
18936
18948
|
return;
|
|
@@ -18938,8 +18950,7 @@ async function startTelegram(options) {
|
|
|
18938
18950
|
chatSessions.set(chatId, match.id);
|
|
18939
18951
|
saveSessions();
|
|
18940
18952
|
console.log(`[Telegram] Switched chat ${chatId} to session ${match.id}`);
|
|
18941
|
-
|
|
18942
|
-
await ctx.reply(`Switched to session: ${match.title} (${shortId})`);
|
|
18953
|
+
await ctx.reply(`Switched to session: ${match.title}`);
|
|
18943
18954
|
}
|
|
18944
18955
|
catch (err) {
|
|
18945
18956
|
console.error("[Telegram] Error switching session:", err);
|
|
@@ -18975,24 +18986,17 @@ async function startTelegram(options) {
|
|
|
18975
18986
|
await ctx.reply("Failed to rename session.");
|
|
18976
18987
|
}
|
|
18977
18988
|
});
|
|
18978
|
-
// Handle /delete <
|
|
18989
|
+
// Handle /delete <number> command - delete a session
|
|
18979
18990
|
bot.command("delete", async (ctx) => {
|
|
18980
18991
|
const chatId = ctx.chat.id.toString();
|
|
18981
18992
|
const args = ctx.message.text.replace(/^\/delete\s*/, "").trim();
|
|
18982
18993
|
if (!args) {
|
|
18983
|
-
await ctx.reply("Usage: /delete <
|
|
18994
|
+
await ctx.reply("Usage: /delete <number>\n\nUse /sessions to see available sessions.");
|
|
18984
18995
|
return;
|
|
18985
18996
|
}
|
|
18986
18997
|
try {
|
|
18987
|
-
|
|
18988
|
-
const
|
|
18989
|
-
if (!list.data) {
|
|
18990
|
-
await ctx.reply("Failed to fetch sessions from the server.");
|
|
18991
|
-
return;
|
|
18992
|
-
}
|
|
18993
|
-
const match = list.data
|
|
18994
|
-
.filter((s) => knownSessionIds.has(s.id))
|
|
18995
|
-
.find((s) => s.id === args || s.id.startsWith(args));
|
|
18998
|
+
const sessions = await getKnownSessions();
|
|
18999
|
+
const match = resolveSession(sessions, args);
|
|
18996
19000
|
if (!match) {
|
|
18997
19001
|
await ctx.reply(`No session found matching "${args}".\n\nUse /sessions to see available sessions.`);
|
|
18998
19002
|
return;
|
|
@@ -19013,15 +19017,242 @@ async function startTelegram(options) {
|
|
|
19013
19017
|
// Remove from known sessions
|
|
19014
19018
|
knownSessionIds.delete(match.id);
|
|
19015
19019
|
saveSessions();
|
|
19016
|
-
const shortId = match.id.substring(0, 8);
|
|
19017
19020
|
console.log(`[Telegram] Deleted session ${match.id}`);
|
|
19018
|
-
await ctx.reply(`Deleted session: ${match.title}
|
|
19021
|
+
await ctx.reply(`Deleted session: ${match.title}`);
|
|
19019
19022
|
}
|
|
19020
19023
|
catch (err) {
|
|
19021
19024
|
console.error("[Telegram] Error deleting session:", err);
|
|
19022
19025
|
await ctx.reply("Failed to delete session.");
|
|
19023
19026
|
}
|
|
19024
19027
|
});
|
|
19028
|
+
/**
|
|
19029
|
+
* Render a message part to markdown.
|
|
19030
|
+
* In default mode: only text and tool calls (name + input/output).
|
|
19031
|
+
* In detailed mode: also includes reasoning, step info, subtasks, costs, etc.
|
|
19032
|
+
*/
|
|
19033
|
+
function renderPart(part, detailed) {
|
|
19034
|
+
let md = "";
|
|
19035
|
+
switch (part.type) {
|
|
19036
|
+
case "text": {
|
|
19037
|
+
const text = part.text;
|
|
19038
|
+
if (text) {
|
|
19039
|
+
md += `${text}\n\n`;
|
|
19040
|
+
}
|
|
19041
|
+
break;
|
|
19042
|
+
}
|
|
19043
|
+
case "tool": {
|
|
19044
|
+
const tool = part.tool;
|
|
19045
|
+
const state = part.state;
|
|
19046
|
+
md += `**Tool: ${tool}**\n\n`;
|
|
19047
|
+
if (state.input) {
|
|
19048
|
+
md += `**Input:**\n\`\`\`json\n${JSON.stringify(state.input, null, 2)}\n\`\`\`\n\n`;
|
|
19049
|
+
}
|
|
19050
|
+
if (state.output) {
|
|
19051
|
+
md += `**Output:**\n\`\`\`\n${state.output}\n\`\`\`\n\n`;
|
|
19052
|
+
}
|
|
19053
|
+
if (state.error) {
|
|
19054
|
+
md += `**Error:**\n\`\`\`\n${state.error}\n\`\`\`\n\n`;
|
|
19055
|
+
}
|
|
19056
|
+
if (detailed && state.time) {
|
|
19057
|
+
const duration = ((state.time.end - state.time.start) / 1000).toFixed(1);
|
|
19058
|
+
md += `*Status: ${state.status} (${duration}s)*\n\n`;
|
|
19059
|
+
}
|
|
19060
|
+
break;
|
|
19061
|
+
}
|
|
19062
|
+
case "reasoning": {
|
|
19063
|
+
if (!detailed)
|
|
19064
|
+
break;
|
|
19065
|
+
const text = part.text;
|
|
19066
|
+
if (text) {
|
|
19067
|
+
md += `<details>\n<summary>Thinking</summary>\n\n${text}\n\n</details>\n\n`;
|
|
19068
|
+
}
|
|
19069
|
+
break;
|
|
19070
|
+
}
|
|
19071
|
+
case "step-start": {
|
|
19072
|
+
if (!detailed)
|
|
19073
|
+
break;
|
|
19074
|
+
md += `---\n*Step started*\n\n`;
|
|
19075
|
+
break;
|
|
19076
|
+
}
|
|
19077
|
+
case "step-finish": {
|
|
19078
|
+
if (!detailed)
|
|
19079
|
+
break;
|
|
19080
|
+
const reason = part.reason;
|
|
19081
|
+
const cost = part.cost;
|
|
19082
|
+
const tokens = part.tokens;
|
|
19083
|
+
let info = `*Step finished`;
|
|
19084
|
+
if (reason)
|
|
19085
|
+
info += ` (${reason})`;
|
|
19086
|
+
info += `*`;
|
|
19087
|
+
if (tokens) {
|
|
19088
|
+
info += `\n*Tokens: ${tokens.input} in / ${tokens.output} out`;
|
|
19089
|
+
if (tokens.reasoning > 0)
|
|
19090
|
+
info += ` / ${tokens.reasoning} reasoning`;
|
|
19091
|
+
if (tokens.cache.read > 0 || tokens.cache.write > 0) {
|
|
19092
|
+
info += ` (cache: ${tokens.cache.read} read, ${tokens.cache.write} write)`;
|
|
19093
|
+
}
|
|
19094
|
+
info += `*`;
|
|
19095
|
+
}
|
|
19096
|
+
if (cost !== undefined && cost > 0) {
|
|
19097
|
+
info += `\n*Cost: $${cost.toFixed(4)}*`;
|
|
19098
|
+
}
|
|
19099
|
+
md += `${info}\n\n---\n\n`;
|
|
19100
|
+
break;
|
|
19101
|
+
}
|
|
19102
|
+
case "subtask": {
|
|
19103
|
+
if (!detailed)
|
|
19104
|
+
break;
|
|
19105
|
+
const description = part.description;
|
|
19106
|
+
const agent = part.agent;
|
|
19107
|
+
const prompt = part.prompt;
|
|
19108
|
+
md += `**Subtask${agent ? ` (${agent})` : ""}**`;
|
|
19109
|
+
if (description)
|
|
19110
|
+
md += `: ${description}`;
|
|
19111
|
+
md += `\n\n`;
|
|
19112
|
+
if (prompt) {
|
|
19113
|
+
md += `> ${prompt.replace(/\n/g, "\n> ")}\n\n`;
|
|
19114
|
+
}
|
|
19115
|
+
break;
|
|
19116
|
+
}
|
|
19117
|
+
case "agent": {
|
|
19118
|
+
if (!detailed)
|
|
19119
|
+
break;
|
|
19120
|
+
const name = part.name;
|
|
19121
|
+
if (name) {
|
|
19122
|
+
md += `*Agent: ${name}*\n\n`;
|
|
19123
|
+
}
|
|
19124
|
+
break;
|
|
19125
|
+
}
|
|
19126
|
+
case "retry": {
|
|
19127
|
+
if (!detailed)
|
|
19128
|
+
break;
|
|
19129
|
+
const attempt = part.attempt;
|
|
19130
|
+
const error = part.error;
|
|
19131
|
+
md += `**Retry (attempt ${attempt || "?"})**`;
|
|
19132
|
+
if (error?.data?.message) {
|
|
19133
|
+
md += `\n\`\`\`\n${error.data.message}\n\`\`\``;
|
|
19134
|
+
}
|
|
19135
|
+
md += `\n\n`;
|
|
19136
|
+
break;
|
|
19137
|
+
}
|
|
19138
|
+
case "compaction": {
|
|
19139
|
+
if (!detailed)
|
|
19140
|
+
break;
|
|
19141
|
+
const auto = part.auto;
|
|
19142
|
+
md += `*Context compacted${auto ? " (auto)" : ""}*\n\n`;
|
|
19143
|
+
break;
|
|
19144
|
+
}
|
|
19145
|
+
default:
|
|
19146
|
+
// snapshot, patch, file, etc. - skip in both modes
|
|
19147
|
+
break;
|
|
19148
|
+
}
|
|
19149
|
+
return md;
|
|
19150
|
+
}
|
|
19151
|
+
// Handle /export command - export the current session to the opencode project directory
|
|
19152
|
+
// Usage: /export - default (text + tool calls only)
|
|
19153
|
+
// /export full - detailed (includes thinking, steps, costs, subtasks, etc.)
|
|
19154
|
+
bot.command("export", async (ctx) => {
|
|
19155
|
+
const chatId = ctx.chat.id.toString();
|
|
19156
|
+
const sessionId = chatSessions.get(chatId);
|
|
19157
|
+
if (!sessionId) {
|
|
19158
|
+
await ctx.reply("No active session. Send a message first to create one.");
|
|
19159
|
+
return;
|
|
19160
|
+
}
|
|
19161
|
+
const args = ctx.message.text.replace(/^\/export\s*/, "").trim().toLowerCase();
|
|
19162
|
+
const detailed = args === "full" || args === "detailed" || args === "all";
|
|
19163
|
+
try {
|
|
19164
|
+
const modeLabel = detailed ? "detailed" : "summary";
|
|
19165
|
+
const processingMsg = await ctx.reply(`Exporting session (${modeLabel})...`);
|
|
19166
|
+
// Fetch session info and messages in parallel
|
|
19167
|
+
const [sessionResult, messagesResult, pathResult] = await Promise.all([
|
|
19168
|
+
client.session.get({ path: { id: sessionId } }),
|
|
19169
|
+
client.session.messages({ path: { id: sessionId } }),
|
|
19170
|
+
client.path.get(),
|
|
19171
|
+
]);
|
|
19172
|
+
if (sessionResult.error || !sessionResult.data) {
|
|
19173
|
+
throw new Error(`Failed to get session: ${JSON.stringify(sessionResult.error)}`);
|
|
19174
|
+
}
|
|
19175
|
+
if (messagesResult.error || !messagesResult.data) {
|
|
19176
|
+
throw new Error(`Failed to get messages: ${JSON.stringify(messagesResult.error)}`);
|
|
19177
|
+
}
|
|
19178
|
+
const session = sessionResult.data;
|
|
19179
|
+
const messages = messagesResult.data;
|
|
19180
|
+
// Build the markdown export
|
|
19181
|
+
let md = `# ${session.title}\n\n`;
|
|
19182
|
+
md += `**Session ID:** ${session.id}\n`;
|
|
19183
|
+
md += `**Created:** ${new Date(session.time.created * 1000).toLocaleString()}\n`;
|
|
19184
|
+
md += `**Updated:** ${new Date(session.time.updated * 1000).toLocaleString()}\n`;
|
|
19185
|
+
if (detailed) {
|
|
19186
|
+
md += `**Export mode:** Detailed\n`;
|
|
19187
|
+
}
|
|
19188
|
+
md += `\n---\n\n`;
|
|
19189
|
+
for (const msg of messages) {
|
|
19190
|
+
const info = msg.info;
|
|
19191
|
+
const parts = msg.parts || [];
|
|
19192
|
+
if (info.role === "user") {
|
|
19193
|
+
md += `## User\n\n`;
|
|
19194
|
+
for (const part of parts) {
|
|
19195
|
+
md += renderPart(part, detailed);
|
|
19196
|
+
}
|
|
19197
|
+
md += `---\n\n`;
|
|
19198
|
+
}
|
|
19199
|
+
else if (info.role === "assistant") {
|
|
19200
|
+
const assistant = info;
|
|
19201
|
+
const duration = assistant.time.completed
|
|
19202
|
+
? ((assistant.time.completed - assistant.time.created) / 1000).toFixed(1) + "s"
|
|
19203
|
+
: "";
|
|
19204
|
+
const modelLabel = assistant.modelID || "unknown";
|
|
19205
|
+
const modeLabel = assistant.mode || "";
|
|
19206
|
+
const header = [modeLabel, modelLabel, duration].filter(Boolean).join(" · ");
|
|
19207
|
+
md += `## Assistant (${header})\n\n`;
|
|
19208
|
+
if (detailed && assistant.tokens) {
|
|
19209
|
+
const t = assistant.tokens;
|
|
19210
|
+
md += `*Tokens: ${t.input} in / ${t.output} out`;
|
|
19211
|
+
if (t.reasoning > 0)
|
|
19212
|
+
md += ` / ${t.reasoning} reasoning`;
|
|
19213
|
+
if (t.cache.read > 0 || t.cache.write > 0) {
|
|
19214
|
+
md += ` (cache: ${t.cache.read} read, ${t.cache.write} write)`;
|
|
19215
|
+
}
|
|
19216
|
+
md += `*\n`;
|
|
19217
|
+
if (assistant.cost && assistant.cost > 0) {
|
|
19218
|
+
md += `*Cost: $${assistant.cost.toFixed(4)}*\n`;
|
|
19219
|
+
}
|
|
19220
|
+
md += `\n`;
|
|
19221
|
+
}
|
|
19222
|
+
for (const part of parts) {
|
|
19223
|
+
md += renderPart(part, detailed);
|
|
19224
|
+
}
|
|
19225
|
+
md += `---\n\n`;
|
|
19226
|
+
}
|
|
19227
|
+
}
|
|
19228
|
+
// Write to the project directory
|
|
19229
|
+
const projectDir = pathResult.data?.directory;
|
|
19230
|
+
const idPrefix = sessionId.substring(0, 8);
|
|
19231
|
+
const suffix = detailed ? "-detailed" : "";
|
|
19232
|
+
const filename = `session-${idPrefix}${suffix}.md`;
|
|
19233
|
+
const exportPath = (0,external_node_path_.resolve)(projectDir || ".", filename);
|
|
19234
|
+
(0,external_node_fs_.writeFileSync)(exportPath, md, "utf-8");
|
|
19235
|
+
console.log(`[Telegram] Exported session ${sessionId} to ${exportPath} (${detailed ? "detailed" : "summary"})`);
|
|
19236
|
+
// Delete the processing message
|
|
19237
|
+
try {
|
|
19238
|
+
await bot.telegram.deleteMessage(ctx.chat.id, processingMsg.message_id);
|
|
19239
|
+
}
|
|
19240
|
+
catch {
|
|
19241
|
+
// Ignore if we can't delete it
|
|
19242
|
+
}
|
|
19243
|
+
// Send the file to the user
|
|
19244
|
+
await bot.telegram.sendDocument(ctx.chat.id, {
|
|
19245
|
+
source: Buffer.from(md, "utf-8"),
|
|
19246
|
+
filename,
|
|
19247
|
+
});
|
|
19248
|
+
await ctx.reply(`Session exported to: ${exportPath}`);
|
|
19249
|
+
}
|
|
19250
|
+
catch (err) {
|
|
19251
|
+
console.error("[Telegram] Error exporting session:", err);
|
|
19252
|
+
await handleSessionError(chatId);
|
|
19253
|
+
await ctx.reply("Sorry, there was an error exporting the session. Try again or use /new to start a fresh session.");
|
|
19254
|
+
}
|
|
19255
|
+
});
|
|
19025
19256
|
// Catch-all for unregistered / commands - forward to OpenCode
|
|
19026
19257
|
bot.on("text", async (ctx) => {
|
|
19027
19258
|
const userText = ctx.message.text;
|
|
@@ -19053,6 +19284,7 @@ async function startTelegram(options) {
|
|
|
19053
19284
|
body: {
|
|
19054
19285
|
command: commandName,
|
|
19055
19286
|
arguments: commandArgs,
|
|
19287
|
+
agent: "default",
|
|
19056
19288
|
},
|
|
19057
19289
|
});
|
|
19058
19290
|
if (result.error) {
|