@openacp/cli 0.6.5 → 0.6.7

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.
Files changed (68) hide show
  1. package/dist/{adapter-YSEIZJBA.js → adapter-7GY3N4ZH.js} +9 -9
  2. package/dist/{admin-SCP25TN2.js → admin-2HAFXQBG.js} +6 -4
  3. package/dist/{chunk-WVMSP4AF.js → chunk-2J2RBYWN.js} +2 -2
  4. package/dist/{chunk-ZKTIZME6.js → chunk-47B7GNOE.js} +2 -2
  5. package/dist/{chunk-XVL6AGMG.js → chunk-5OVPEDUB.js} +2 -2
  6. package/dist/{chunk-FW6HM4VU.js → chunk-5SXG7X5D.js} +862 -303
  7. package/dist/chunk-5SXG7X5D.js.map +1 -0
  8. package/dist/{chunk-F3AICYO4.js → chunk-JHYXKVV2.js} +19 -1
  9. package/dist/chunk-JHYXKVV2.js.map +1 -0
  10. package/dist/{chunk-FCLGYYTY.js → chunk-JUYDFUSN.js} +224 -2
  11. package/dist/chunk-JUYDFUSN.js.map +1 -0
  12. package/dist/{chunk-774Y4RAK.js → chunk-KIRH7TUJ.js} +94 -24
  13. package/dist/chunk-KIRH7TUJ.js.map +1 -0
  14. package/dist/{chunk-4GQ3I65A.js → chunk-LBIKITQT.js} +1 -2
  15. package/dist/{chunk-4GQ3I65A.js.map → chunk-LBIKITQT.js.map} +1 -1
  16. package/dist/{chunk-FEWSQT3U.js → chunk-LO4Y5WQ7.js} +1014 -83
  17. package/dist/chunk-LO4Y5WQ7.js.map +1 -0
  18. package/dist/{chunk-3IRAWHMC.js → chunk-MZXWCDBU.js} +3 -3
  19. package/dist/{chunk-YQRF3IOR.js → chunk-O7CPGUAI.js} +2 -2
  20. package/dist/{chunk-7KZI2236.js → chunk-RHE2JSYE.js} +2 -2
  21. package/dist/{chunk-3ZO3MHZN.js → chunk-SHHMBGB3.js} +4 -3
  22. package/dist/chunk-SHHMBGB3.js.map +1 -0
  23. package/dist/{chunk-JV6XQRAE.js → chunk-XANPHG7W.js} +2 -2
  24. package/dist/{chunk-PJVKOZTR.js → chunk-YEOY2PBJ.js} +2 -2
  25. package/dist/cli.js +21 -21
  26. package/dist/{config-B26J3XXN.js → config-CQAS6YHR.js} +2 -2
  27. package/dist/{config-editor-QTGUK3CD.js → config-editor-37BM56WF.js} +4 -4
  28. package/dist/{config-registry-7I6GGDOY.js → config-registry-HDXFES2D.js} +2 -2
  29. package/dist/{daemon-5DS5BQXJ.js → daemon-K33ZPSEZ.js} +3 -3
  30. package/dist/{discord-QKT3JMRW.js → discord-VOHXRTCH.js} +113 -131
  31. package/dist/discord-VOHXRTCH.js.map +1 -0
  32. package/dist/{doctor-QQ3YZEYV.js → doctor-HASEBMUD.js} +4 -4
  33. package/dist/doctor-W4VGLDVM.js +9 -0
  34. package/dist/index.d.ts +105 -10
  35. package/dist/index.js +15 -11
  36. package/dist/{main-TSZR4HPP.js → main-DUXVFTDD.js} +19 -19
  37. package/dist/{new-session-K6UCWYOP.js → new-session-NHK7TOEW.js} +3 -3
  38. package/dist/{settings-RRF77IC4.js → settings-6TF4WIGJ.js} +3 -3
  39. package/dist/{setup-5ZKSUR26.js → setup-RJCEB6FS.js} +3 -3
  40. package/package.json +1 -1
  41. package/dist/chunk-3ZO3MHZN.js.map +0 -1
  42. package/dist/chunk-774Y4RAK.js.map +0 -1
  43. package/dist/chunk-F3AICYO4.js.map +0 -1
  44. package/dist/chunk-FCLGYYTY.js.map +0 -1
  45. package/dist/chunk-FEWSQT3U.js.map +0 -1
  46. package/dist/chunk-FW6HM4VU.js.map +0 -1
  47. package/dist/discord-QKT3JMRW.js.map +0 -1
  48. package/dist/doctor-6SUCVUZB.js +0 -9
  49. /package/dist/{adapter-YSEIZJBA.js.map → adapter-7GY3N4ZH.js.map} +0 -0
  50. /package/dist/{admin-SCP25TN2.js.map → admin-2HAFXQBG.js.map} +0 -0
  51. /package/dist/{chunk-WVMSP4AF.js.map → chunk-2J2RBYWN.js.map} +0 -0
  52. /package/dist/{chunk-ZKTIZME6.js.map → chunk-47B7GNOE.js.map} +0 -0
  53. /package/dist/{chunk-XVL6AGMG.js.map → chunk-5OVPEDUB.js.map} +0 -0
  54. /package/dist/{chunk-3IRAWHMC.js.map → chunk-MZXWCDBU.js.map} +0 -0
  55. /package/dist/{chunk-YQRF3IOR.js.map → chunk-O7CPGUAI.js.map} +0 -0
  56. /package/dist/{chunk-7KZI2236.js.map → chunk-RHE2JSYE.js.map} +0 -0
  57. /package/dist/{chunk-JV6XQRAE.js.map → chunk-XANPHG7W.js.map} +0 -0
  58. /package/dist/{chunk-PJVKOZTR.js.map → chunk-YEOY2PBJ.js.map} +0 -0
  59. /package/dist/{config-B26J3XXN.js.map → config-CQAS6YHR.js.map} +0 -0
  60. /package/dist/{config-editor-QTGUK3CD.js.map → config-editor-37BM56WF.js.map} +0 -0
  61. /package/dist/{config-registry-7I6GGDOY.js.map → config-registry-HDXFES2D.js.map} +0 -0
  62. /package/dist/{daemon-5DS5BQXJ.js.map → daemon-K33ZPSEZ.js.map} +0 -0
  63. /package/dist/{doctor-6SUCVUZB.js.map → doctor-HASEBMUD.js.map} +0 -0
  64. /package/dist/{doctor-QQ3YZEYV.js.map → doctor-W4VGLDVM.js.map} +0 -0
  65. /package/dist/{main-TSZR4HPP.js.map → main-DUXVFTDD.js.map} +0 -0
  66. /package/dist/{new-session-K6UCWYOP.js.map → new-session-NHK7TOEW.js.map} +0 -0
  67. /package/dist/{settings-RRF77IC4.js.map → settings-6TF4WIGJ.js.map} +0 -0
  68. /package/dist/{setup-5ZKSUR26.js.map → setup-RJCEB6FS.js.map} +0 -0
@@ -1,10 +1,20 @@
1
1
  import {
2
2
  PRODUCT_GUIDE,
3
- dispatchMessage
4
- } from "./chunk-FCLGYYTY.js";
3
+ STATUS_ICONS,
4
+ dispatchMessage,
5
+ evaluateNoise,
6
+ extractContentText,
7
+ formatTokens,
8
+ formatToolSummary,
9
+ formatToolTitle,
10
+ progressBar,
11
+ splitMessage,
12
+ stripCodeFences,
13
+ truncateContent
14
+ } from "./chunk-JUYDFUSN.js";
5
15
  import {
6
16
  DoctorEngine
7
- } from "./chunk-PJVKOZTR.js";
17
+ } from "./chunk-YEOY2PBJ.js";
8
18
  import {
9
19
  buildMenuKeyboard,
10
20
  buildSkillMessages,
@@ -12,15 +22,19 @@ import {
12
22
  handleHelp,
13
23
  handleMenu
14
24
  } from "./chunk-7QJS2XBD.js";
25
+ import {
26
+ CheckpointReader,
27
+ DEFAULT_MAX_TOKENS
28
+ } from "./chunk-LO4Y5WQ7.js";
15
29
  import {
16
30
  ChannelAdapter
17
- } from "./chunk-4GQ3I65A.js";
31
+ } from "./chunk-LBIKITQT.js";
18
32
  import {
19
33
  getConfigValue,
20
34
  getSafeFields,
21
35
  isHotReloadable,
22
36
  resolveOptions
23
- } from "./chunk-F3AICYO4.js";
37
+ } from "./chunk-JHYXKVV2.js";
24
38
  import {
25
39
  createChildLogger
26
40
  } from "./chunk-GAK6PIBW.js";
@@ -76,13 +90,16 @@ function escapeHtml(text) {
76
90
  function markdownToTelegramHtml(md) {
77
91
  const codeBlocks = [];
78
92
  const inlineCodes = [];
79
- let text = md.replace(/```(\w*)\n?([\s\S]*?)```/g, (_match, lang, code) => {
80
- const index = codeBlocks.length;
81
- const escapedCode = escapeHtml(code);
82
- const langAttr = lang ? ` class="language-${escapeHtml(lang)}"` : "";
83
- codeBlocks.push(`<pre><code${langAttr}>${escapedCode}</code></pre>`);
84
- return `\0CODE_BLOCK_${index}\0`;
85
- });
93
+ let text = md.replace(
94
+ /```(\w*)\n?([\s\S]*?)```/g,
95
+ (_match, lang, code) => {
96
+ const index = codeBlocks.length;
97
+ const escapedCode = escapeHtml(code);
98
+ const langAttr = lang ? ` class="language-${escapeHtml(lang)}"` : "";
99
+ codeBlocks.push(`<pre><code${langAttr}>${escapedCode}</code></pre>`);
100
+ return `\0CODE_BLOCK_${index}\0`;
101
+ }
102
+ );
86
103
  text = text.replace(/`([^`]+)`/g, (_match, code) => {
87
104
  const index = inlineCodes.length;
88
105
  inlineCodes.push(`<code>${escapeHtml(code)}</code>`);
@@ -100,72 +117,32 @@ function markdownToTelegramHtml(md) {
100
117
  });
101
118
  return text;
102
119
  }
103
- var STATUS_ICON = {
104
- pending: "\u23F3",
105
- in_progress: "\u{1F504}",
106
- completed: "\u2705",
107
- failed: "\u274C"
108
- };
109
- var KIND_ICON = {
110
- read: "\u{1F4D6}",
111
- edit: "\u270F\uFE0F",
112
- delete: "\u{1F5D1}\uFE0F",
113
- execute: "\u25B6\uFE0F",
114
- search: "\u{1F50D}",
115
- fetch: "\u{1F310}",
116
- think: "\u{1F9E0}",
117
- move: "\u{1F4E6}",
118
- other: "\u{1F6E0}\uFE0F"
119
- };
120
- function extractContentText(content, depth = 0) {
121
- if (!content || depth > 5) return "";
122
- if (typeof content === "string") return content;
123
- if (Array.isArray(content)) {
124
- return content.map((c) => extractContentText(c, depth + 1)).filter(Boolean).join("\n");
125
- }
126
- if (typeof content === "object" && content !== null) {
127
- const c = content;
128
- if (c.type === "text" && typeof c.text === "string") return c.text;
129
- if (typeof c.text === "string") return c.text;
130
- if (typeof c.content === "string") return c.content;
131
- if (c.content && typeof c.content === "object") return extractContentText(c.content, depth + 1);
132
- if (c.input) return extractContentText(c.input, depth + 1);
133
- if (c.output) return extractContentText(c.output, depth + 1);
134
- const keys = Object.keys(c).filter((k) => k !== "type");
135
- if (keys.length === 0) return "";
136
- return JSON.stringify(c, null, 2);
137
- }
138
- return String(content);
139
- }
140
- function truncateContent(text, maxLen = 3800) {
141
- if (text.length <= maxLen) return text;
142
- return text.slice(0, maxLen) + "\n\u2026 (truncated)";
143
- }
144
- function formatToolCall(tool) {
145
- const si = STATUS_ICON[tool.status || ""] || "\u{1F527}";
146
- const ki = KIND_ICON[tool.kind || ""] || "\u{1F6E0}\uFE0F";
147
- let text = `${si} ${ki} <b>${escapeHtml(tool.name || "Tool")}</b>`;
120
+ function formatToolCall(tool, verbosity = "medium") {
121
+ const si = STATUS_ICONS[tool.status || ""] || "\u{1F527}";
122
+ const name = tool.name || "Tool";
123
+ const label = verbosity === "low" ? formatToolTitle(name, tool.rawInput) : formatToolSummary(name, tool.rawInput);
124
+ let text = `${si} <b>${escapeHtml(label)}</b>`;
148
125
  text += formatViewerLinks(tool.viewerLinks, tool.viewerFilePath);
149
- if (!tool.viewerLinks) {
150
- const details = extractContentText(tool.content);
126
+ if (verbosity === "high" || verbosity === "medium" && !tool.viewerLinks) {
127
+ const details = stripCodeFences(extractContentText(tool.content));
151
128
  if (details) {
152
129
  text += `
153
- <pre>${escapeHtml(truncateContent(details))}</pre>`;
130
+ <pre>${escapeHtml(truncateContent(details, 3800))}</pre>`;
154
131
  }
155
132
  }
156
133
  return text;
157
134
  }
158
- function formatToolUpdate(update) {
159
- const si = STATUS_ICON[update.status] || "\u{1F527}";
160
- const ki = KIND_ICON[update.kind || ""] || "\u{1F6E0}\uFE0F";
135
+ function formatToolUpdate(update, verbosity = "medium") {
136
+ const si = STATUS_ICONS[update.status] || "\u{1F527}";
161
137
  const name = update.name || "Tool";
162
- let text = `${si} ${ki} <b>${escapeHtml(name)}</b>`;
138
+ const label = verbosity === "low" ? formatToolTitle(name, update.rawInput) : formatToolSummary(name, update.rawInput);
139
+ let text = `${si} <b>${escapeHtml(label)}</b>`;
163
140
  text += formatViewerLinks(update.viewerLinks, update.viewerFilePath);
164
- if (!update.viewerLinks) {
165
- const details = extractContentText(update.content);
141
+ if (verbosity === "high" || verbosity === "medium" && !update.viewerLinks) {
142
+ const details = stripCodeFences(extractContentText(update.content));
166
143
  if (details) {
167
144
  text += `
168
- <pre>${escapeHtml(truncateContent(details))}</pre>`;
145
+ <pre>${escapeHtml(truncateContent(details, 3800))}</pre>`;
169
146
  }
170
147
  }
171
148
  return text;
@@ -174,19 +151,14 @@ function formatViewerLinks(links, filePath) {
174
151
  if (!links) return "";
175
152
  const fileName = filePath ? filePath.split("/").pop() || filePath : "";
176
153
  let text = "\n";
177
- if (links.file) text += `
154
+ if (links.file)
155
+ text += `
178
156
  \u{1F4C4} <a href="${escapeHtml(links.file)}">View ${escapeHtml(fileName || "file")}</a>`;
179
- if (links.diff) text += `
157
+ if (links.diff)
158
+ text += `
180
159
  \u{1F4DD} <a href="${escapeHtml(links.diff)}">View diff${fileName ? ` \u2014 ${escapeHtml(fileName)}` : ""}</a>`;
181
160
  return text;
182
161
  }
183
- function formatTokens(n) {
184
- return n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
185
- }
186
- function progressBar(ratio) {
187
- const filled = Math.round(Math.min(ratio, 1) * 10);
188
- return "\u2593".repeat(filled) + "\u2591".repeat(10 - filled);
189
- }
190
162
  function formatUsage(usage) {
191
163
  const { tokensUsed, contextSize } = usage;
192
164
  if (tokensUsed == null) return "\u{1F4CA} Usage data unavailable";
@@ -213,49 +185,30 @@ function formatUsageReport(summaries, budgetStatus) {
213
185
  const lines = ["\u{1F4CA} <b>Usage Report</b>"];
214
186
  for (const summary of summaries) {
215
187
  lines.push("");
216
- lines.push(`\u2500\u2500 <b>${PERIOD_LABEL[summary.period] ?? summary.period}</b> \u2500\u2500`);
188
+ lines.push(
189
+ `\u2500\u2500 <b>${PERIOD_LABEL[summary.period] ?? summary.period}</b> \u2500\u2500`
190
+ );
217
191
  lines.push(
218
192
  `\u{1F4B0} ${formatCost(summary.totalCost)} \xB7 \u{1F524} ${formatTokens(summary.totalTokens)} tokens \xB7 \u{1F4CB} ${summary.sessionCount} sessions`
219
193
  );
220
194
  if (summary.period === "month" && budgetStatus.budget > 0) {
221
195
  const bar = progressBar(budgetStatus.used / budgetStatus.budget);
222
- lines.push(`Budget: ${formatCost(budgetStatus.used)} / ${formatCost(budgetStatus.budget)} (${budgetStatus.percent}%)`);
196
+ lines.push(
197
+ `Budget: ${formatCost(budgetStatus.used)} / ${formatCost(budgetStatus.budget)} (${budgetStatus.percent}%)`
198
+ );
223
199
  lines.push(`${bar} ${budgetStatus.percent}%`);
224
200
  }
225
201
  }
226
202
  return lines.join("\n");
227
203
  }
228
- function splitMessage(text, maxLength = 3800) {
229
- if (text.length <= maxLength) return [text];
230
- const chunks = [];
231
- let remaining = text;
232
- while (remaining.length > 0) {
233
- if (remaining.length <= maxLength) {
234
- chunks.push(remaining);
235
- break;
236
- }
237
- const wouldLeaveSmall = remaining.length < maxLength * 1.3;
238
- const searchLimit = wouldLeaveSmall ? Math.floor(remaining.length / 2) + 300 : maxLength;
239
- let splitAt = remaining.lastIndexOf("\n\n", searchLimit);
240
- if (splitAt === -1 || splitAt < searchLimit * 0.2) {
241
- splitAt = remaining.lastIndexOf("\n", searchLimit);
242
- }
243
- if (splitAt === -1 || splitAt < searchLimit * 0.2) {
244
- splitAt = searchLimit;
245
- }
246
- const candidate = remaining.slice(0, splitAt);
247
- const fences = candidate.match(/```/g);
248
- if (fences && fences.length % 2 !== 0) {
249
- const closingFence = remaining.indexOf("```", splitAt);
250
- if (closingFence !== -1) {
251
- const afterFence = remaining.indexOf("\n", closingFence + 3);
252
- splitAt = afterFence !== -1 ? afterFence + 1 : closingFence + 3;
253
- }
254
- }
255
- chunks.push(remaining.slice(0, splitAt));
256
- remaining = remaining.slice(splitAt).replace(/^\n+/, "");
257
- }
258
- return chunks;
204
+ function formatSummary(summary, sessionName) {
205
+ const header = sessionName ? `\u{1F4CB} <b>Summary \u2014 ${escapeHtml(sessionName)}</b>` : "\u{1F4CB} <b>Session Summary</b>";
206
+ return `${header}
207
+
208
+ ${escapeHtml(summary)}`;
209
+ }
210
+ function splitMessage2(text, maxLength = 3800) {
211
+ return splitMessage(text, maxLength);
259
212
  }
260
213
 
261
214
  // src/adapters/telegram/commands/admin.ts
@@ -267,7 +220,10 @@ function setupDangerousModeCallbacks(bot, core) {
267
220
  const session = core.sessionManager.getSession(sessionId);
268
221
  if (session) {
269
222
  session.dangerousMode = !session.dangerousMode;
270
- log.info({ sessionId, dangerousMode: session.dangerousMode }, "Dangerous mode toggled via button");
223
+ log.info(
224
+ { sessionId, dangerousMode: session.dangerousMode },
225
+ "Dangerous mode toggled via button"
226
+ );
271
227
  core.sessionManager.patchRecord(sessionId, { dangerousMode: session.dangerousMode }).catch(() => {
272
228
  });
273
229
  const toastText2 = session.dangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
@@ -277,7 +233,11 @@ function setupDangerousModeCallbacks(bot, core) {
277
233
  }
278
234
  try {
279
235
  await ctx.editMessageReplyMarkup({
280
- reply_markup: buildSessionControlKeyboard(sessionId, session.dangerousMode, session.voiceMode === "on")
236
+ reply_markup: buildSessionControlKeyboard(
237
+ sessionId,
238
+ session.dangerousMode,
239
+ session.voiceMode === "on"
240
+ )
281
241
  });
282
242
  } catch {
283
243
  }
@@ -286,7 +246,9 @@ function setupDangerousModeCallbacks(bot, core) {
286
246
  const record = core.sessionManager.getSessionRecord(sessionId);
287
247
  if (!record || record.status === "cancelled" || record.status === "error") {
288
248
  try {
289
- await ctx.answerCallbackQuery({ text: "\u26A0\uFE0F Session not found or already ended." });
249
+ await ctx.answerCallbackQuery({
250
+ text: "\u26A0\uFE0F Session not found or already ended."
251
+ });
290
252
  } catch {
291
253
  }
292
254
  return;
@@ -294,7 +256,10 @@ function setupDangerousModeCallbacks(bot, core) {
294
256
  const newDangerousMode = !(record.dangerousMode ?? false);
295
257
  core.sessionManager.patchRecord(sessionId, { dangerousMode: newDangerousMode }).catch(() => {
296
258
  });
297
- log.info({ sessionId, dangerousMode: newDangerousMode }, "Dangerous mode toggled via button (store-only, session not in memory)");
259
+ log.info(
260
+ { sessionId, dangerousMode: newDangerousMode },
261
+ "Dangerous mode toggled via button (store-only, session not in memory)"
262
+ );
298
263
  const toastText = newDangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
299
264
  try {
300
265
  await ctx.answerCallbackQuery({ text: toastText });
@@ -302,7 +267,11 @@ function setupDangerousModeCallbacks(bot, core) {
302
267
  }
303
268
  try {
304
269
  await ctx.editMessageReplyMarkup({
305
- reply_markup: buildSessionControlKeyboard(sessionId, newDangerousMode, false)
270
+ reply_markup: buildSessionControlKeyboard(
271
+ sessionId,
272
+ newDangerousMode,
273
+ false
274
+ )
306
275
  });
307
276
  } catch {
308
277
  }
@@ -311,26 +280,40 @@ function setupDangerousModeCallbacks(bot, core) {
311
280
  async function handleEnableDangerous(ctx, core) {
312
281
  const threadId = ctx.message?.message_thread_id;
313
282
  if (!threadId) {
314
- await ctx.reply("\u26A0\uFE0F This command only works inside a session topic.", { parse_mode: "HTML" });
283
+ await ctx.reply("\u26A0\uFE0F This command only works inside a session topic.", {
284
+ parse_mode: "HTML"
285
+ });
315
286
  return;
316
287
  }
317
- const session = core.sessionManager.getSessionByThread("telegram", String(threadId));
288
+ const session = core.sessionManager.getSessionByThread(
289
+ "telegram",
290
+ String(threadId)
291
+ );
318
292
  if (session) {
319
293
  if (session.dangerousMode) {
320
- await ctx.reply("\u2620\uFE0F Dangerous mode is already enabled.", { parse_mode: "HTML" });
294
+ await ctx.reply("\u2620\uFE0F Dangerous mode is already enabled.", {
295
+ parse_mode: "HTML"
296
+ });
321
297
  return;
322
298
  }
323
299
  session.dangerousMode = true;
324
300
  core.sessionManager.patchRecord(session.id, { dangerousMode: true }).catch(() => {
325
301
  });
326
302
  } else {
327
- const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
303
+ const record = core.sessionManager.getRecordByThread(
304
+ "telegram",
305
+ String(threadId)
306
+ );
328
307
  if (!record || record.status === "cancelled" || record.status === "error") {
329
- await ctx.reply("\u26A0\uFE0F No active session in this topic.", { parse_mode: "HTML" });
308
+ await ctx.reply("\u26A0\uFE0F No active session in this topic.", {
309
+ parse_mode: "HTML"
310
+ });
330
311
  return;
331
312
  }
332
313
  if (record.dangerousMode) {
333
- await ctx.reply("\u2620\uFE0F Dangerous mode is already enabled.", { parse_mode: "HTML" });
314
+ await ctx.reply("\u2620\uFE0F Dangerous mode is already enabled.", {
315
+ parse_mode: "HTML"
316
+ });
334
317
  return;
335
318
  }
336
319
  core.sessionManager.patchRecord(record.sessionId, { dangerousMode: true }).catch(() => {
@@ -348,32 +331,49 @@ Use /disable_dangerous to restore normal behaviour.`,
348
331
  async function handleDisableDangerous(ctx, core) {
349
332
  const threadId = ctx.message?.message_thread_id;
350
333
  if (!threadId) {
351
- await ctx.reply("\u26A0\uFE0F This command only works inside a session topic.", { parse_mode: "HTML" });
334
+ await ctx.reply("\u26A0\uFE0F This command only works inside a session topic.", {
335
+ parse_mode: "HTML"
336
+ });
352
337
  return;
353
338
  }
354
- const session = core.sessionManager.getSessionByThread("telegram", String(threadId));
339
+ const session = core.sessionManager.getSessionByThread(
340
+ "telegram",
341
+ String(threadId)
342
+ );
355
343
  if (session) {
356
344
  if (!session.dangerousMode) {
357
- await ctx.reply("\u{1F510} Dangerous mode is already disabled.", { parse_mode: "HTML" });
345
+ await ctx.reply("\u{1F510} Dangerous mode is already disabled.", {
346
+ parse_mode: "HTML"
347
+ });
358
348
  return;
359
349
  }
360
350
  session.dangerousMode = false;
361
351
  core.sessionManager.patchRecord(session.id, { dangerousMode: false }).catch(() => {
362
352
  });
363
353
  } else {
364
- const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
354
+ const record = core.sessionManager.getRecordByThread(
355
+ "telegram",
356
+ String(threadId)
357
+ );
365
358
  if (!record || record.status === "cancelled" || record.status === "error") {
366
- await ctx.reply("\u26A0\uFE0F No active session in this topic.", { parse_mode: "HTML" });
359
+ await ctx.reply("\u26A0\uFE0F No active session in this topic.", {
360
+ parse_mode: "HTML"
361
+ });
367
362
  return;
368
363
  }
369
364
  if (!record.dangerousMode) {
370
- await ctx.reply("\u{1F510} Dangerous mode is already disabled.", { parse_mode: "HTML" });
365
+ await ctx.reply("\u{1F510} Dangerous mode is already disabled.", {
366
+ parse_mode: "HTML"
367
+ });
371
368
  return;
372
369
  }
373
370
  core.sessionManager.patchRecord(record.sessionId, { dangerousMode: false }).catch(() => {
374
371
  });
375
372
  }
376
- await ctx.reply("\u{1F510} <b>Dangerous mode disabled</b>\n\nPermission requests will be shown normally.", { parse_mode: "HTML" });
373
+ await ctx.reply(
374
+ "\u{1F510} <b>Dangerous mode disabled</b>\n\nPermission requests will be shown normally.",
375
+ { parse_mode: "HTML" }
376
+ );
377
377
  }
378
378
  function buildSessionControlKeyboard(sessionId, dangerousMode, voiceMode) {
379
379
  return new InlineKeyboard().text(
@@ -390,7 +390,9 @@ function setupTTSCallbacks(bot, core) {
390
390
  const session = core.sessionManager.getSession(sessionId);
391
391
  if (!session) {
392
392
  try {
393
- await ctx.answerCallbackQuery({ text: "\u26A0\uFE0F Session not found or not active." });
393
+ await ctx.answerCallbackQuery({
394
+ text: "\u26A0\uFE0F Session not found or not active."
395
+ });
394
396
  } catch {
395
397
  }
396
398
  return;
@@ -404,7 +406,11 @@ function setupTTSCallbacks(bot, core) {
404
406
  }
405
407
  try {
406
408
  await ctx.editMessageReplyMarkup({
407
- reply_markup: buildSessionControlKeyboard(sessionId, session.dangerousMode, newMode === "on")
409
+ reply_markup: buildSessionControlKeyboard(
410
+ sessionId,
411
+ session.dangerousMode,
412
+ newMode === "on"
413
+ )
408
414
  });
409
415
  } catch {
410
416
  }
@@ -413,42 +419,113 @@ function setupTTSCallbacks(bot, core) {
413
419
  async function handleTTS(ctx, core) {
414
420
  const threadId = ctx.message?.message_thread_id;
415
421
  if (!threadId) {
416
- await ctx.reply("\u26A0\uFE0F This command only works inside a session topic.", { parse_mode: "HTML" });
422
+ await ctx.reply("\u26A0\uFE0F This command only works inside a session topic.", {
423
+ parse_mode: "HTML"
424
+ });
417
425
  return;
418
426
  }
419
427
  const session = await core.getOrResumeSession("telegram", String(threadId));
420
428
  if (!session) {
421
- await ctx.reply("\u26A0\uFE0F No active session in this topic.", { parse_mode: "HTML" });
429
+ await ctx.reply("\u26A0\uFE0F No active session in this topic.", {
430
+ parse_mode: "HTML"
431
+ });
422
432
  return;
423
433
  }
424
434
  const args = ctx.message?.text?.split(/\s+/).slice(1) ?? [];
425
435
  const arg = args[0]?.toLowerCase();
426
436
  if (arg === "on") {
427
437
  session.setVoiceMode("on");
428
- await ctx.reply("\u{1F50A} Text to Speech enabled for this session.", { parse_mode: "HTML" });
438
+ await ctx.reply("\u{1F50A} Text to Speech enabled for this session.", {
439
+ parse_mode: "HTML"
440
+ });
429
441
  } else if (arg === "off") {
430
442
  session.setVoiceMode("off");
431
443
  await ctx.reply("\u{1F507} Text to Speech disabled.", { parse_mode: "HTML" });
432
444
  } else {
433
445
  session.setVoiceMode("next");
434
- await ctx.reply("\u{1F50A} Text to Speech enabled for the next message.", { parse_mode: "HTML" });
446
+ await ctx.reply("\u{1F50A} Text to Speech enabled for the next message.", {
447
+ parse_mode: "HTML"
448
+ });
449
+ }
450
+ }
451
+ var VERBOSITY_LABELS = {
452
+ low: "\u{1F507} Low",
453
+ medium: "\u{1F4CA} Medium",
454
+ high: "\u{1F4D6} High"
455
+ };
456
+ async function handleVerbosity(ctx, core) {
457
+ const args = ctx.message?.text?.split(/\s+/).slice(1) ?? [];
458
+ const arg = args[0]?.toLowerCase();
459
+ if (arg === "low" || arg === "medium" || arg === "high") {
460
+ await core.configManager.save(
461
+ { channels: { telegram: { displayVerbosity: arg } } },
462
+ "channels.telegram.displayVerbosity"
463
+ );
464
+ await ctx.reply(
465
+ `${VERBOSITY_LABELS[arg]} Display verbosity set to <b>${arg}</b>.`,
466
+ { parse_mode: "HTML" }
467
+ );
468
+ } else {
469
+ const current = core.configManager.get().channels?.telegram?.displayVerbosity ?? "medium";
470
+ await ctx.reply(
471
+ `\u{1F4CA} Current verbosity: <b>${current}</b>
472
+
473
+ Usage: <code>/verbosity low|medium|high</code>
474
+
475
+ \u2022 <b>low</b> \u2014 minimal output, title only
476
+ \u2022 <b>medium</b> \u2014 balanced (default)
477
+ \u2022 <b>high</b> \u2014 full detail with content`,
478
+ { parse_mode: "HTML" }
479
+ );
435
480
  }
436
481
  }
482
+ function setupVerbosityCallbacks(bot, core) {
483
+ bot.callbackQuery(/^vb:/, async (ctx) => {
484
+ const level = ctx.callbackQuery.data.slice(3);
485
+ if (level !== "low" && level !== "medium" && level !== "high") return;
486
+ await core.configManager.save(
487
+ { channels: { telegram: { displayVerbosity: level } } },
488
+ "channels.telegram.displayVerbosity"
489
+ );
490
+ try {
491
+ await ctx.answerCallbackQuery({
492
+ text: `${VERBOSITY_LABELS[level]} Verbosity: ${level}`
493
+ });
494
+ } catch {
495
+ }
496
+ });
497
+ }
437
498
  async function handleUpdate(ctx, core) {
438
499
  if (!core.requestRestart) {
439
- await ctx.reply("\u26A0\uFE0F Update is not available (no restart handler registered).", { parse_mode: "HTML" });
500
+ await ctx.reply(
501
+ "\u26A0\uFE0F Update is not available (no restart handler registered).",
502
+ { parse_mode: "HTML" }
503
+ );
440
504
  return;
441
505
  }
442
506
  const { getCurrentVersion, getLatestVersion, compareVersions, runUpdate } = await import("./version-AXXV6IV2.js");
443
507
  const current = getCurrentVersion();
444
- const statusMsg = await ctx.reply(`\u{1F50D} Checking for updates... (current: v${escapeHtml(current)})`, { parse_mode: "HTML" });
508
+ const statusMsg = await ctx.reply(
509
+ `\u{1F50D} Checking for updates... (current: v${escapeHtml(current)})`,
510
+ { parse_mode: "HTML" }
511
+ );
445
512
  const latest = await getLatestVersion();
446
513
  if (!latest) {
447
- await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, "\u274C Could not check for updates.", { parse_mode: "HTML" });
514
+ await ctx.api.editMessageText(
515
+ ctx.chat.id,
516
+ statusMsg.message_id,
517
+ "\u274C Could not check for updates.",
518
+ { parse_mode: "HTML" }
519
+ );
448
520
  return;
449
521
  }
450
522
  if (compareVersions(current, latest) >= 0) {
451
- await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, `\u2705 Already up to date (v${escapeHtml(current)}).`, { parse_mode: "HTML" });
523
+ await ctx.api.editMessageText(
524
+ ctx.chat.id,
525
+ statusMsg.message_id,
526
+ `\u2705 Already up to date (v${escapeHtml(current)}).`,
527
+ { parse_mode: "HTML" }
528
+ );
452
529
  return;
453
530
  }
454
531
  await ctx.api.editMessageText(
@@ -459,7 +536,12 @@ async function handleUpdate(ctx, core) {
459
536
  );
460
537
  const ok = await runUpdate();
461
538
  if (!ok) {
462
- await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, "\u274C Update failed. Try manually: <code>npm install -g @openacp/cli@latest</code>", { parse_mode: "HTML" });
539
+ await ctx.api.editMessageText(
540
+ ctx.chat.id,
541
+ statusMsg.message_id,
542
+ "\u274C Update failed. Try manually: <code>npm install -g @openacp/cli@latest</code>",
543
+ { parse_mode: "HTML" }
544
+ );
463
545
  return;
464
546
  }
465
547
  await ctx.api.editMessageText(
@@ -473,10 +555,16 @@ async function handleUpdate(ctx, core) {
473
555
  }
474
556
  async function handleRestart(ctx, core) {
475
557
  if (!core.requestRestart) {
476
- await ctx.reply("\u26A0\uFE0F Restart is not available (no restart handler registered).", { parse_mode: "HTML" });
558
+ await ctx.reply(
559
+ "\u26A0\uFE0F Restart is not available (no restart handler registered).",
560
+ { parse_mode: "HTML" }
561
+ );
477
562
  return;
478
563
  }
479
- await ctx.reply("\u{1F504} <b>Restarting OpenACP...</b>\nRebuilding and restarting. Be back shortly.", { parse_mode: "HTML" });
564
+ await ctx.reply(
565
+ "\u{1F504} <b>Restarting OpenACP...</b>\nRebuilding and restarting. Be back shortly.",
566
+ { parse_mode: "HTML" }
567
+ );
480
568
  await new Promise((r) => setTimeout(r, 500));
481
569
  await core.requestRestart();
482
570
  }
@@ -1179,26 +1267,13 @@ async function handleArchive(ctx, core) {
1179
1267
  const threadId = ctx.message?.message_thread_id;
1180
1268
  if (!threadId) return;
1181
1269
  const session = core.sessionManager.getSessionByThread("telegram", String(threadId));
1182
- if (!session) {
1183
- await ctx.reply(
1184
- "\u2139\uFE0F <b>/archive</b> works in session topics \u2014 it recreates the topic with a clean chat view while keeping your agent session alive.\n\nGo to the session topic you want to archive and type /archive there.",
1185
- { parse_mode: "HTML" }
1186
- );
1187
- return;
1188
- }
1189
- if (session.status === "initializing") {
1190
- await ctx.reply("\u23F3 Please wait for session to be ready.", { parse_mode: "HTML" });
1191
- return;
1192
- }
1193
- if (session.status !== "active") {
1194
- await ctx.reply(`\u26A0\uFE0F Cannot archive \u2014 session is ${session.status}.`, { parse_mode: "HTML" });
1195
- return;
1196
- }
1270
+ const record = !session ? core.sessionManager.getRecordByThread("telegram", String(threadId)) : void 0;
1271
+ const identifier = session?.id ?? record?.sessionId ?? `topic:${threadId}`;
1197
1272
  await ctx.reply(
1198
- "\u26A0\uFE0F <b>Archive this session topic?</b>\n\nThis will permanently delete all messages in this topic and create a fresh one.\nYour agent session will continue \u2014 only the chat view is reset.\n\n<i>Note: links to messages in this topic will stop working.</i>",
1273
+ "\u26A0\uFE0F <b>Archive this session?</b>\n\nThis will:\n\u2022 Delete this topic and all messages\n\u2022 Stop the agent session (if running)\n\u2022 Remove the session record\n\n<i>This action cannot be undone.</i>",
1199
1274
  {
1200
1275
  parse_mode: "HTML",
1201
- reply_markup: new InlineKeyboard3().text("\u{1F5D1} Yes, archive", `ar:yes:${session.id}`).text("\u274C Cancel", `ar:no:${session.id}`)
1276
+ reply_markup: new InlineKeyboard3().text("\u{1F5D1} Yes, archive", `ar:yes:${identifier}`).text("\u274C Cancel", `ar:no:${identifier}`)
1202
1277
  }
1203
1278
  );
1204
1279
  }
@@ -1209,31 +1284,103 @@ async function handleArchiveConfirm(ctx, core, chatId) {
1209
1284
  await ctx.answerCallbackQuery();
1210
1285
  } catch {
1211
1286
  }
1212
- const [, action, sessionId] = data.split(":");
1287
+ const [, action, ...rest] = data.split(":");
1288
+ const identifier = rest.join(":");
1213
1289
  if (action === "no") {
1214
1290
  await ctx.editMessageText("Archive cancelled.", { parse_mode: "HTML" });
1215
1291
  return;
1216
1292
  }
1217
- await ctx.editMessageText("\u{1F504} Archiving topic...", { parse_mode: "HTML" });
1218
- const result = await core.archiveSession(sessionId);
1293
+ await ctx.editMessageText("\u{1F504} Archiving...", { parse_mode: "HTML" });
1294
+ if (identifier.startsWith("topic:")) {
1295
+ const topicId = Number(identifier.slice("topic:".length));
1296
+ try {
1297
+ await ctx.api.deleteForumTopic(chatId, topicId);
1298
+ core.notificationManager.notifyAll({
1299
+ sessionId: "system",
1300
+ sessionName: `Orphan topic #${topicId}`,
1301
+ type: "completed",
1302
+ summary: `Orphan topic #${topicId} archived and deleted.`
1303
+ });
1304
+ } catch (err) {
1305
+ core.notificationManager.notifyAll({
1306
+ sessionId: "system",
1307
+ sessionName: `Orphan topic #${topicId}`,
1308
+ type: "error",
1309
+ summary: `Failed to delete orphan topic #${topicId}: ${err.message}`
1310
+ });
1311
+ }
1312
+ return;
1313
+ }
1314
+ const result = await core.archiveSession(identifier);
1219
1315
  if (result.ok) {
1220
- const newTopicId = Number(result.newThreadId);
1221
- await ctx.api.sendMessage(chatId, "\u2705 Topic archived. Session continues.", {
1222
- message_thread_id: newTopicId,
1223
- parse_mode: "HTML"
1316
+ core.notificationManager.notifyAll({
1317
+ sessionId: identifier,
1318
+ type: "completed",
1319
+ summary: `Session archived and deleted.`
1224
1320
  });
1225
1321
  } else {
1226
1322
  try {
1227
1323
  await ctx.editMessageText(`\u274C Failed to archive: <code>${escapeHtml(result.error)}</code>`, { parse_mode: "HTML" });
1228
1324
  } catch {
1229
1325
  core.notificationManager.notifyAll({
1230
- sessionId,
1326
+ sessionId: identifier,
1231
1327
  type: "error",
1232
- summary: `Failed to recreate topic for session "${sessionId}": ${result.error}`
1328
+ summary: `Failed to archive session "${identifier}": ${result.error}`
1233
1329
  });
1234
1330
  }
1235
1331
  }
1236
1332
  }
1333
+ async function handleSummary(ctx, core) {
1334
+ const threadId = ctx.message?.message_thread_id;
1335
+ if (!threadId) return;
1336
+ const session = core.sessionManager.getSessionByThread("telegram", String(threadId));
1337
+ const record = !session ? core.sessionManager.getRecordByThread("telegram", String(threadId)) : void 0;
1338
+ const sessionId = session?.id ?? record?.sessionId;
1339
+ if (!sessionId) {
1340
+ await ctx.reply(
1341
+ "\u2139\uFE0F <b>/summary</b> works in session topics \u2014 it asks the agent to summarize the session.\n\nGo to a session topic and type /summary there.",
1342
+ { parse_mode: "HTML" }
1343
+ );
1344
+ return;
1345
+ }
1346
+ await ctx.replyWithChatAction("typing");
1347
+ const result = await core.summarizeSession(sessionId);
1348
+ if (result.ok) {
1349
+ await ctx.reply(formatSummary(result.summary, session?.name ?? record?.name), { parse_mode: "HTML" });
1350
+ } else {
1351
+ await ctx.reply(`\u26A0\uFE0F ${escapeHtml(result.error)}`, { parse_mode: "HTML" });
1352
+ }
1353
+ }
1354
+ async function handleSummaryCallback(ctx, core, chatId) {
1355
+ const data = ctx.callbackQuery?.data;
1356
+ if (!data) return;
1357
+ const sessionId = data.replace("sm:summary:", "");
1358
+ try {
1359
+ await ctx.answerCallbackQuery();
1360
+ } catch {
1361
+ }
1362
+ const session = core.sessionManager.getSession(sessionId);
1363
+ const record = !session ? core.sessionManager.getSessionRecord(sessionId) : void 0;
1364
+ const threadId = session ? Number(session.threadId) : record?.platform?.topicId ?? 0;
1365
+ if (!threadId) return;
1366
+ await ctx.api.sendMessage(chatId, "\u{1F4CB} Generating summary...", {
1367
+ message_thread_id: threadId,
1368
+ parse_mode: "HTML"
1369
+ });
1370
+ const result = await core.summarizeSession(sessionId);
1371
+ const sessionName = session?.name ?? record?.name;
1372
+ if (result.ok) {
1373
+ await ctx.api.sendMessage(chatId, formatSummary(result.summary, sessionName), {
1374
+ message_thread_id: threadId,
1375
+ parse_mode: "HTML"
1376
+ });
1377
+ } else {
1378
+ await ctx.api.sendMessage(chatId, `\u26A0\uFE0F ${escapeHtml(result.error)}`, {
1379
+ message_thread_id: threadId,
1380
+ parse_mode: "HTML"
1381
+ });
1382
+ }
1383
+ }
1237
1384
 
1238
1385
  // src/adapters/telegram/commands/agents.ts
1239
1386
  import { InlineKeyboard as InlineKeyboard4 } from "grammy";
@@ -1622,13 +1769,290 @@ ${resultText}`,
1622
1769
  });
1623
1770
  }
1624
1771
 
1625
- // src/adapters/telegram/commands/settings.ts
1772
+ // src/adapters/telegram/commands/resume.ts
1773
+ import * as fs from "fs";
1774
+ import * as path from "path";
1775
+ import * as os from "os";
1626
1776
  import { InlineKeyboard as InlineKeyboard6 } from "grammy";
1627
- var log4 = createChildLogger({ module: "telegram-settings" });
1777
+ var log4 = createChildLogger({ module: "telegram-cmd-resume" });
1778
+ var PENDING_TIMEOUT_MS2 = 5 * 60 * 1e3;
1779
+ function botFromCtx2(ctx) {
1780
+ return { api: ctx.api };
1781
+ }
1782
+ var pendingResumes = /* @__PURE__ */ new Map();
1783
+ function cleanupPending2(userId) {
1784
+ const pending = pendingResumes.get(userId);
1785
+ if (pending) {
1786
+ clearTimeout(pending.timer);
1787
+ pendingResumes.delete(userId);
1788
+ }
1789
+ }
1790
+ function parseResumeArgs(matchStr) {
1791
+ const args = matchStr.split(" ").filter(Boolean);
1792
+ if (args.length === 0) return { query: { type: "latest", value: "5" } };
1793
+ const first = args[0];
1794
+ if (first === "pr") return args[1] ? { query: { type: "pr", value: args[1] } } : null;
1795
+ if (first === "branch") return args[1] ? { query: { type: "branch", value: args[1] } } : null;
1796
+ if (first === "commit") return args[1] ? { query: { type: "commit", value: args[1] } } : null;
1797
+ if (CheckpointReader.isCheckpointId(first)) return { query: { type: "checkpoint", value: first } };
1798
+ if (CheckpointReader.isSessionId(first)) return { query: { type: "session", value: first } };
1799
+ if (first.includes("/pull/")) {
1800
+ const prMatch = first.match(/\/pull\/(\d+)/);
1801
+ return prMatch ? { query: { type: "pr", value: prMatch[1] } } : null;
1802
+ }
1803
+ const ghCommitMatch = first.match(/github\.com\/[^/]+\/[^/]+\/commit\/([0-9a-f]+)/);
1804
+ if (ghCommitMatch) return { query: { type: "commit", value: ghCommitMatch[1] } };
1805
+ const ghBranchMatch = first.match(/github\.com\/[^/]+\/[^/]+\/tree\/(.+?)(?:\?|#|$)/);
1806
+ if (ghBranchMatch) return { query: { type: "branch", value: ghBranchMatch[1] } };
1807
+ const ghCompareMatch = first.match(/github\.com\/[^/]+\/[^/]+\/compare\/(?:[^.]+\.{2,3})(.+?)(?:\?|#|$)/);
1808
+ if (ghCompareMatch) return { query: { type: "branch", value: ghCompareMatch[1] } };
1809
+ if (first.match(/github\.com\/[^/]+\/[^/]+\/?$/) && !first.includes("/tree/") && !first.includes("/pull/") && !first.includes("/commit/") && !first.includes("/compare/")) {
1810
+ return { query: { type: "latest", value: "5" } };
1811
+ }
1812
+ const entireCheckpointMatch = first.match(/entire\.io\/gh\/[^/]+\/[^/]+\/checkpoints\/[^/]+\/([0-9a-f]{12})/);
1813
+ if (entireCheckpointMatch) return { query: { type: "checkpoint", value: entireCheckpointMatch[1] } };
1814
+ const entireCommitMatch = first.match(/entire\.io\/gh\/[^/]+\/[^/]+\/commit\/([0-9a-f]+)/);
1815
+ if (entireCommitMatch) return { query: { type: "commit", value: entireCommitMatch[1] } };
1816
+ return { query: { type: "latest", value: "5" } };
1817
+ }
1818
+ function looksLikePath(text) {
1819
+ return text.startsWith("/") || text.startsWith("~") || text.startsWith(".");
1820
+ }
1821
+ function listWorkspaceDirs(baseDir, maxItems = 10) {
1822
+ const resolved = baseDir.replace(/^~/, os.homedir());
1823
+ try {
1824
+ if (!fs.existsSync(resolved)) return [];
1825
+ return fs.readdirSync(resolved, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith(".")).map((d) => d.name).sort().slice(0, maxItems);
1826
+ } catch {
1827
+ return [];
1828
+ }
1829
+ }
1830
+ async function showWorkspacePicker(ctx, core, chatId, userId, query) {
1831
+ const config = core.configManager.get();
1832
+ const baseDir = config.workspace.baseDir;
1833
+ const resolvedBase = baseDir.replace(/^~/, os.homedir());
1834
+ const subdirs = listWorkspaceDirs(baseDir);
1835
+ const keyboard = new InlineKeyboard6();
1836
+ for (const dir of subdirs) {
1837
+ const fullPath = path.join(resolvedBase, dir);
1838
+ keyboard.text(`\u{1F4C1} ${dir}`, `m:resume:ws:${dir}`).row();
1839
+ }
1840
+ keyboard.text(`\u{1F4C1} Use ${baseDir}`, "m:resume:ws:default").row();
1841
+ keyboard.text("\u270F\uFE0F Enter project path", "m:resume:ws:custom");
1842
+ const queryLabel = query.type === "latest" ? "latest sessions" : `${query.type}: ${query.value}`;
1843
+ const text = `\u{1F4C1} <b>Select project directory for resume</b>
1844
+
1845
+ Query: <code>${escapeHtml(queryLabel)}</code>
1846
+
1847
+ Choose the repo that has Entire checkpoints enabled:`;
1848
+ const msg = await ctx.reply(text, { parse_mode: "HTML", reply_markup: keyboard });
1849
+ cleanupPending2(userId);
1850
+ pendingResumes.set(userId, {
1851
+ query,
1852
+ step: "workspace",
1853
+ messageId: msg.message_id,
1854
+ threadId: ctx.message?.message_thread_id,
1855
+ timer: setTimeout(() => pendingResumes.delete(userId), PENDING_TIMEOUT_MS2)
1856
+ });
1857
+ }
1858
+ async function executeResume(ctx, core, chatId, query, repoPath) {
1859
+ const provider = await core.contextManager.getProvider(repoPath);
1860
+ if (!provider) {
1861
+ await ctx.reply(
1862
+ `\u26A0\uFE0F <b>Entire not enabled in <code>${escapeHtml(repoPath)}</code></b>
1863
+
1864
+ To enable conversation history tracking:
1865
+ <code>cd ${escapeHtml(repoPath)} && npx entire enable</code>
1866
+
1867
+ Learn more: https://docs.entire.io/getting-started`,
1868
+ { parse_mode: "HTML" }
1869
+ );
1870
+ return;
1871
+ }
1872
+ const fullQuery = { ...query, repoPath };
1873
+ await ctx.reply(`\u{1F50D} Scanning ${query.type === "latest" ? "latest sessions" : `${query.type}: ${escapeHtml(query.value)}`}...`, { parse_mode: "HTML" });
1874
+ const listResult = await core.contextManager.listSessions(fullQuery);
1875
+ if (!listResult || listResult.sessions.length === 0) {
1876
+ await ctx.reply(
1877
+ `\u{1F50D} <b>No sessions found</b>
1878
+
1879
+ Query: <code>${escapeHtml(query.type)}: ${escapeHtml(query.value)}</code>
1880
+ Repo: <code>${escapeHtml(repoPath)}</code>`,
1881
+ { parse_mode: "HTML" }
1882
+ );
1883
+ return;
1884
+ }
1885
+ const config = core.configManager.get();
1886
+ const agentName = config.defaultAgent;
1887
+ let threadId;
1888
+ try {
1889
+ const queryLabel = query.type === "latest" ? "latest" : `${query.type}: ${query.value.slice(0, 20)}`;
1890
+ const topicName = `\u{1F4DC} Resume \u2014 ${queryLabel}`;
1891
+ threadId = await createSessionTopic(botFromCtx2(ctx), chatId, topicName);
1892
+ await ctx.api.sendMessage(chatId, `\u23F3 Loading context and starting session...`, {
1893
+ message_thread_id: threadId,
1894
+ parse_mode: "HTML"
1895
+ });
1896
+ const { session, contextResult } = await core.createSessionWithContext({
1897
+ channelId: "telegram",
1898
+ agentName,
1899
+ workingDirectory: repoPath,
1900
+ contextQuery: fullQuery,
1901
+ contextOptions: { maxTokens: DEFAULT_MAX_TOKENS }
1902
+ });
1903
+ session.threadId = String(threadId);
1904
+ await core.sessionManager.patchRecord(session.id, { platform: { topicId: threadId } });
1905
+ const sessionCount = contextResult?.sessionCount ?? listResult.sessions.length;
1906
+ const mode = contextResult?.mode ?? "full";
1907
+ const tokens = contextResult?.tokenEstimate ?? listResult.estimatedTokens;
1908
+ const topicLink = buildDeepLink(chatId, threadId);
1909
+ const replyTarget = ctx.message?.message_thread_id;
1910
+ if (replyTarget !== threadId) {
1911
+ await ctx.reply(
1912
+ `\u2705 Session resumed \u2192 <a href="${topicLink}">Open topic</a>`,
1913
+ { parse_mode: "HTML" }
1914
+ );
1915
+ }
1916
+ await ctx.api.sendMessage(
1917
+ chatId,
1918
+ `\u2705 <b>Session resumed with context</b>
1919
+ <b>Agent:</b> ${escapeHtml(session.agentName)}
1920
+ <b>Workspace:</b> <code>${escapeHtml(session.workingDirectory)}</code>
1921
+ <b>Sessions loaded:</b> ${sessionCount}
1922
+ <b>Mode:</b> ${escapeHtml(mode)}
1923
+ <b>~Tokens:</b> ${tokens.toLocaleString()}
1924
+
1925
+ Context is ready \u2014 chat here to continue working with the agent.`,
1926
+ {
1927
+ message_thread_id: threadId,
1928
+ parse_mode: "HTML",
1929
+ reply_markup: buildSessionControlKeyboard(session.id, false, false)
1930
+ }
1931
+ );
1932
+ session.warmup().catch((err) => log4.error({ err }, "Warm-up error"));
1933
+ } catch (err) {
1934
+ log4.error({ err }, "Resume session creation failed");
1935
+ if (threadId) {
1936
+ try {
1937
+ await ctx.api.deleteForumTopic(chatId, threadId);
1938
+ } catch {
1939
+ }
1940
+ }
1941
+ const message = err instanceof Error ? err.message : typeof err === "object" ? JSON.stringify(err) : String(err);
1942
+ await ctx.reply(`\u274C ${escapeHtml(message)}`, { parse_mode: "HTML" });
1943
+ }
1944
+ }
1945
+ async function handleResume(ctx, core, chatId, assistant) {
1946
+ const rawMatch = ctx.match;
1947
+ const matchStr = typeof rawMatch === "string" ? rawMatch : "";
1948
+ const parsed = parseResumeArgs(matchStr);
1949
+ if (!parsed) {
1950
+ await ctx.reply(
1951
+ `\u274C <b>Invalid arguments.</b>
1952
+
1953
+ Usage examples:
1954
+ \u2022 <code>/resume</code> \u2014 latest 5 sessions
1955
+ \u2022 <code>/resume pr 19</code>
1956
+ \u2022 <code>/resume branch main</code>
1957
+ \u2022 <code>/resume commit e0dd2fa4</code>
1958
+ \u2022 <code>/resume f634acf05138</code> \u2014 checkpoint ID
1959
+ \u2022 <code>/resume https://entire.io/gh/.../checkpoints/.../2e884e2c402a</code>
1960
+ \u2022 <code>/resume https://entire.io/gh/.../commit/e0dd2fa4...</code>`,
1961
+ { parse_mode: "HTML" }
1962
+ );
1963
+ return;
1964
+ }
1965
+ const { query } = parsed;
1966
+ const userId = ctx.from?.id;
1967
+ if (!userId) return;
1968
+ await showWorkspacePicker(ctx, core, chatId, userId, query);
1969
+ }
1970
+ async function handlePendingResumeInput(ctx, core, chatId, assistantTopicId) {
1971
+ const userId = ctx.from?.id;
1972
+ if (!userId) return false;
1973
+ const pending = pendingResumes.get(userId);
1974
+ if (!pending || !ctx.message?.text) return false;
1975
+ if (pending.step !== "workspace_input" && pending.step !== "workspace") return false;
1976
+ const threadId = ctx.message.message_thread_id;
1977
+ if (threadId && threadId !== assistantTopicId) return false;
1978
+ if (pending.step === "workspace" && !looksLikePath(ctx.message.text.trim())) return false;
1979
+ let workspace = ctx.message.text.trim();
1980
+ if (!workspace) {
1981
+ await ctx.reply("\u26A0\uFE0F Please enter a valid directory path.", { parse_mode: "HTML" });
1982
+ return true;
1983
+ }
1984
+ if (!workspace.startsWith("/") && !workspace.startsWith("~")) {
1985
+ const baseDir = core.configManager.get().workspace.baseDir;
1986
+ workspace = `${baseDir.replace(/\/$/, "")}/${workspace}`;
1987
+ }
1988
+ const resolved = core.configManager.resolveWorkspace(workspace);
1989
+ cleanupPending2(userId);
1990
+ await executeResume(ctx, core, chatId, pending.query, resolved);
1991
+ return true;
1992
+ }
1993
+ function setupResumeCallbacks(bot, core, chatId) {
1994
+ bot.callbackQuery(/^m:resume:/, async (ctx) => {
1995
+ const data = ctx.callbackQuery.data;
1996
+ const userId = ctx.from?.id;
1997
+ if (!userId) return;
1998
+ try {
1999
+ await ctx.answerCallbackQuery();
2000
+ } catch {
2001
+ }
2002
+ const pending = pendingResumes.get(userId);
2003
+ if (!pending) return;
2004
+ if (data === "m:resume:ws:default") {
2005
+ const baseDir = core.configManager.get().workspace.baseDir;
2006
+ const resolved = core.configManager.resolveWorkspace(baseDir);
2007
+ cleanupPending2(userId);
2008
+ try {
2009
+ await ctx.api.editMessageText(chatId, pending.messageId, `\u23F3 Using <code>${escapeHtml(resolved)}</code>...`, { parse_mode: "HTML" });
2010
+ } catch {
2011
+ }
2012
+ await executeResume(ctx, core, chatId, pending.query, resolved);
2013
+ return;
2014
+ }
2015
+ if (data === "m:resume:ws:custom") {
2016
+ try {
2017
+ await ctx.api.editMessageText(
2018
+ chatId,
2019
+ pending.messageId,
2020
+ `\u270F\uFE0F <b>Enter project path:</b>
2021
+
2022
+ Full path like <code>~/code/my-project</code>
2023
+ Or just the folder name (will use workspace baseDir)`,
2024
+ { parse_mode: "HTML" }
2025
+ );
2026
+ } catch {
2027
+ await ctx.reply(`\u270F\uFE0F <b>Enter project path:</b>`, { parse_mode: "HTML" });
2028
+ }
2029
+ clearTimeout(pending.timer);
2030
+ pending.step = "workspace_input";
2031
+ pending.timer = setTimeout(() => pendingResumes.delete(userId), PENDING_TIMEOUT_MS2);
2032
+ return;
2033
+ }
2034
+ if (data.startsWith("m:resume:ws:")) {
2035
+ const dirName = data.replace("m:resume:ws:", "");
2036
+ const baseDir = core.configManager.get().workspace.baseDir;
2037
+ const resolved = core.configManager.resolveWorkspace(path.join(baseDir.replace(/^~/, os.homedir()), dirName));
2038
+ cleanupPending2(userId);
2039
+ try {
2040
+ await ctx.api.editMessageText(chatId, pending.messageId, `\u23F3 Using <code>${escapeHtml(resolved)}</code>...`, { parse_mode: "HTML" });
2041
+ } catch {
2042
+ }
2043
+ await executeResume(ctx, core, chatId, pending.query, resolved);
2044
+ return;
2045
+ }
2046
+ });
2047
+ }
2048
+
2049
+ // src/adapters/telegram/commands/settings.ts
2050
+ import { InlineKeyboard as InlineKeyboard7 } from "grammy";
2051
+ var log5 = createChildLogger({ module: "telegram-settings" });
1628
2052
  function buildSettingsKeyboard(core) {
1629
2053
  const config = core.configManager.get();
1630
2054
  const fields = getSafeFields();
1631
- const kb = new InlineKeyboard6();
2055
+ const kb = new InlineKeyboard7();
1632
2056
  for (const field of fields) {
1633
2057
  const value = getConfigValue(config, field.path);
1634
2058
  const label = formatFieldLabel(field, value);
@@ -1687,7 +2111,7 @@ function setupSettingsCallbacks(bot, core, getAssistantSession) {
1687
2111
  } catch {
1688
2112
  }
1689
2113
  } catch (err) {
1690
- log4.error({ err, fieldPath }, "Failed to toggle config");
2114
+ log5.error({ err, fieldPath }, "Failed to toggle config");
1691
2115
  try {
1692
2116
  await ctx.answerCallbackQuery({ text: "\u274C Failed to update" });
1693
2117
  } catch {
@@ -1701,7 +2125,7 @@ function setupSettingsCallbacks(bot, core, getAssistantSession) {
1701
2125
  if (!fieldDef) return;
1702
2126
  const options = resolveOptions(fieldDef, config) ?? [];
1703
2127
  const currentValue = getConfigValue(config, fieldPath);
1704
- const kb = new InlineKeyboard6();
2128
+ const kb = new InlineKeyboard7();
1705
2129
  for (const opt of options) {
1706
2130
  const marker = opt === String(currentValue) ? " \u2713" : "";
1707
2131
  kb.text(`${opt}${marker}`, `s:pick:${fieldPath}:${opt}`).row();
@@ -1761,7 +2185,7 @@ Tap to change:`, {
1761
2185
  } catch {
1762
2186
  }
1763
2187
  } catch (err) {
1764
- log4.error({ err, fieldPath }, "Failed to set config");
2188
+ log5.error({ err, fieldPath }, "Failed to set config");
1765
2189
  try {
1766
2190
  await ctx.answerCallbackQuery({ text: "\u274C Failed to update" });
1767
2191
  } catch {
@@ -1832,8 +2256,8 @@ function buildNestedUpdate(dotPath, value) {
1832
2256
  }
1833
2257
 
1834
2258
  // src/adapters/telegram/commands/doctor.ts
1835
- import { InlineKeyboard as InlineKeyboard7 } from "grammy";
1836
- var log5 = createChildLogger({ module: "telegram-cmd-doctor" });
2259
+ import { InlineKeyboard as InlineKeyboard8 } from "grammy";
2260
+ var log6 = createChildLogger({ module: "telegram-cmd-doctor" });
1837
2261
  var pendingFixesStore = /* @__PURE__ */ new Map();
1838
2262
  function renderReport(report) {
1839
2263
  const icons = { pass: "\u2705", warn: "\u26A0\uFE0F", fail: "\u274C" };
@@ -1850,7 +2274,7 @@ function renderReport(report) {
1850
2274
  lines.push(`<b>Result:</b> ${passed} passed, ${warnings} warnings, ${failed} failed${fixedStr}`);
1851
2275
  let keyboard;
1852
2276
  if (report.pendingFixes.length > 0) {
1853
- keyboard = new InlineKeyboard7();
2277
+ keyboard = new InlineKeyboard8();
1854
2278
  for (let i = 0; i < report.pendingFixes.length; i++) {
1855
2279
  const label = `\u{1F527} Fix: ${report.pendingFixes[i].message.slice(0, 30)}`;
1856
2280
  keyboard.text(label, `m:doctor:fix:${i}`).row();
@@ -1876,7 +2300,7 @@ async function handleDoctor(ctx) {
1876
2300
  reply_markup: keyboard
1877
2301
  });
1878
2302
  } catch (err) {
1879
- log5.error({ err }, "Doctor command failed");
2303
+ log6.error({ err }, "Doctor command failed");
1880
2304
  await ctx.api.editMessageText(
1881
2305
  ctx.chat.id,
1882
2306
  statusMsg.message_id,
@@ -1925,7 +2349,7 @@ function setupDoctorCallbacks(bot) {
1925
2349
  }
1926
2350
  }
1927
2351
  } catch (err) {
1928
- log5.error({ err, index }, "Doctor fix callback failed");
2352
+ log6.error({ err, index }, "Doctor fix callback failed");
1929
2353
  }
1930
2354
  });
1931
2355
  bot.callbackQuery("m:doctor", async (ctx) => {
@@ -1938,8 +2362,8 @@ function setupDoctorCallbacks(bot) {
1938
2362
  }
1939
2363
 
1940
2364
  // src/adapters/telegram/commands/tunnel.ts
1941
- import { InlineKeyboard as InlineKeyboard8 } from "grammy";
1942
- var log6 = createChildLogger({ module: "telegram-cmd-tunnel" });
2365
+ import { InlineKeyboard as InlineKeyboard9 } from "grammy";
2366
+ var log7 = createChildLogger({ module: "telegram-cmd-tunnel" });
1943
2367
  async function handleTunnel(ctx, core) {
1944
2368
  if (!core.tunnelService) {
1945
2369
  await ctx.reply("\u274C Tunnel service is not enabled.", { parse_mode: "HTML" });
@@ -2026,7 +2450,7 @@ async function handleTunnels(ctx, core) {
2026
2450
  \u2192 <a href="${escapeHtml(e.publicUrl)}">${escapeHtml(e.publicUrl)}</a>` : "";
2027
2451
  return `${status} Port <b>${e.port}</b>${label}${url}`;
2028
2452
  });
2029
- const keyboard = new InlineKeyboard8();
2453
+ const keyboard = new InlineKeyboard9();
2030
2454
  for (const e of entries) {
2031
2455
  keyboard.text(`\u{1F50C} Stop ${e.port}${e.label ? ` (${e.label})` : ""}`, `tn:stop:${e.port}`).row();
2032
2456
  }
@@ -2066,7 +2490,7 @@ function setupTunnelCallbacks(bot, core) {
2066
2490
  if (remaining.length === 0) {
2067
2491
  await ctx.editMessageText("\u{1F50C} All tunnels stopped.", { parse_mode: "HTML" });
2068
2492
  } else {
2069
- const kb = new InlineKeyboard8();
2493
+ const kb = new InlineKeyboard9();
2070
2494
  for (const e of remaining) {
2071
2495
  kb.text(`\u{1F50C} Stop ${e.port}${e.label ? ` (${e.label})` : ""}`, `tn:stop:${e.port}`).row();
2072
2496
  }
@@ -2115,10 +2539,14 @@ function setupCommands(bot, core, chatId, assistant) {
2115
2539
  bot.command("tunnel", (ctx) => handleTunnel(ctx, core));
2116
2540
  bot.command("tunnels", (ctx) => handleTunnels(ctx, core));
2117
2541
  bot.command("archive", (ctx) => handleArchive(ctx, core));
2542
+ bot.command("summary", (ctx) => handleSummary(ctx, core));
2118
2543
  bot.command("text_to_speech", (ctx) => handleTTS(ctx, core));
2544
+ bot.command("verbosity", (ctx) => handleVerbosity(ctx, core));
2545
+ bot.command("resume", (ctx) => handleResume(ctx, core, chatId, assistant));
2119
2546
  }
2120
2547
  function setupAllCallbacks(bot, core, chatId, systemTopicIds, getAssistantSession) {
2121
2548
  setupNewSessionCallbacks(bot, core, chatId);
2549
+ setupResumeCallbacks(bot, core, chatId);
2122
2550
  setupSessionCallbacks(bot, core, chatId, systemTopicIds);
2123
2551
  setupSettingsCallbacks(bot, core, getAssistantSession ?? (() => void 0));
2124
2552
  setupDoctorCallbacks(bot);
@@ -2127,9 +2555,16 @@ function setupAllCallbacks(bot, core, chatId, systemTopicIds, getAssistantSessio
2127
2555
  bot.callbackQuery(/^na:/, async (ctx) => {
2128
2556
  const agentKey = ctx.callbackQuery.data.replace("na:", "");
2129
2557
  await ctx.answerCallbackQuery();
2130
- await createSessionDirect(ctx, core, chatId, agentKey, core.configManager.get().workspace.baseDir);
2558
+ await createSessionDirect(
2559
+ ctx,
2560
+ core,
2561
+ chatId,
2562
+ agentKey,
2563
+ core.configManager.get().workspace.baseDir
2564
+ );
2131
2565
  });
2132
2566
  bot.callbackQuery(/^ar:/, (ctx) => handleArchiveConfirm(ctx, core, chatId));
2567
+ bot.callbackQuery(/^sm:/, (ctx) => handleSummaryCallback(ctx, core, chatId));
2133
2568
  bot.callbackQuery(/^m:/, async (ctx) => {
2134
2569
  const data = ctx.callbackQuery.data;
2135
2570
  try {
@@ -2177,8 +2612,14 @@ var STATIC_COMMANDS = [
2177
2612
  { command: "install", description: "Install a new agent" },
2178
2613
  { command: "help", description: "Help" },
2179
2614
  { command: "menu", description: "Show menu" },
2180
- { command: "enable_dangerous", description: "Auto-approve all permission requests (session only)" },
2181
- { command: "disable_dangerous", description: "Restore normal permission prompts (session only)" },
2615
+ {
2616
+ command: "enable_dangerous",
2617
+ description: "Auto-approve all permission requests (session only)"
2618
+ },
2619
+ {
2620
+ command: "disable_dangerous",
2621
+ description: "Restore normal permission prompts (session only)"
2622
+ },
2182
2623
  { command: "integrate", description: "Manage agent integrations" },
2183
2624
  { command: "handoff", description: "Continue this session in your terminal" },
2184
2625
  { command: "clear", description: "Clear assistant history" },
@@ -2189,13 +2630,16 @@ var STATIC_COMMANDS = [
2189
2630
  { command: "tunnel", description: "Create/stop tunnel for a local port" },
2190
2631
  { command: "tunnels", description: "List active tunnels" },
2191
2632
  { command: "archive", description: "Archive session topic (recreate with clean history)" },
2192
- { command: "text_to_speech", description: "Toggle Text to Speech (/text_to_speech on, /text_to_speech off)" }
2633
+ { command: "summary", description: "Get AI summary of current session" },
2634
+ { command: "text_to_speech", description: "Toggle Text to Speech (/text_to_speech on, /text_to_speech off)" },
2635
+ { command: "verbosity", description: "Set display verbosity (/verbosity low|medium|high)" },
2636
+ { command: "resume", description: "Resume with conversation history from Entire checkpoints" }
2193
2637
  ];
2194
2638
 
2195
2639
  // src/adapters/telegram/permissions.ts
2196
- import { InlineKeyboard as InlineKeyboard9 } from "grammy";
2640
+ import { InlineKeyboard as InlineKeyboard10 } from "grammy";
2197
2641
  import { nanoid } from "nanoid";
2198
- var log7 = createChildLogger({ module: "telegram-permissions" });
2642
+ var log8 = createChildLogger({ module: "telegram-permissions" });
2199
2643
  var PermissionHandler = class {
2200
2644
  constructor(bot, chatId, getSession, sendNotification) {
2201
2645
  this.bot = bot;
@@ -2212,7 +2656,7 @@ var PermissionHandler = class {
2212
2656
  requestId: request.id,
2213
2657
  options: request.options.map((o) => ({ id: o.id, isAllow: o.isAllow }))
2214
2658
  });
2215
- const keyboard = new InlineKeyboard9();
2659
+ const keyboard = new InlineKeyboard10();
2216
2660
  for (const option of request.options) {
2217
2661
  const emoji = option.isAllow ? "\u2705" : "\u274C";
2218
2662
  keyboard.text(`${emoji} ${option.label}`, `p:${callbackKey}:${option.id}`);
@@ -2255,7 +2699,7 @@ ${escapeHtml(request.description)}`,
2255
2699
  }
2256
2700
  const session = this.getSession(pending.sessionId);
2257
2701
  const isAllow = pending.options.find((o) => o.id === optionId)?.isAllow ?? false;
2258
- log7.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
2702
+ log8.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
2259
2703
  if (session?.permissionGate.requestId === pending.requestId) {
2260
2704
  session.permissionGate.resolve(optionId);
2261
2705
  }
@@ -2273,10 +2717,10 @@ ${escapeHtml(request.description)}`,
2273
2717
  };
2274
2718
 
2275
2719
  // src/adapters/telegram/assistant.ts
2276
- var log8 = createChildLogger({ module: "telegram-assistant" });
2720
+ var log9 = createChildLogger({ module: "telegram-assistant" });
2277
2721
  async function spawnAssistant(core, adapter, assistantTopicId) {
2278
2722
  const config = core.configManager.get();
2279
- log8.info({ agent: config.defaultAgent }, "Creating assistant session...");
2723
+ log9.info({ agent: config.defaultAgent }, "Creating assistant session...");
2280
2724
  const session = await core.createSession({
2281
2725
  channelId: "telegram",
2282
2726
  agentName: config.defaultAgent,
@@ -2285,7 +2729,7 @@ async function spawnAssistant(core, adapter, assistantTopicId) {
2285
2729
  // Prevent auto-naming from triggering after system prompt
2286
2730
  });
2287
2731
  session.threadId = String(assistantTopicId);
2288
- log8.info({ sessionId: session.id }, "Assistant agent spawned");
2732
+ log9.info({ sessionId: session.id }, "Assistant agent spawned");
2289
2733
  const allRecords = core.sessionManager.listRecords();
2290
2734
  const activeCount = allRecords.filter((r) => r.status === "active" || r.status === "initializing").length;
2291
2735
  const statusCounts = /* @__PURE__ */ new Map();
@@ -2306,9 +2750,9 @@ async function spawnAssistant(core, adapter, assistantTopicId) {
2306
2750
  };
2307
2751
  const systemPrompt = buildAssistantSystemPrompt(ctx);
2308
2752
  const ready = session.enqueuePrompt(systemPrompt).then(() => {
2309
- log8.info({ sessionId: session.id }, "Assistant system prompt completed");
2753
+ log9.info({ sessionId: session.id }, "Assistant system prompt completed");
2310
2754
  }).catch((err) => {
2311
- log8.warn({ err }, "Assistant system prompt failed");
2755
+ log9.warn({ err }, "Assistant system prompt failed");
2312
2756
  });
2313
2757
  return { session, ready };
2314
2758
  }
@@ -2474,7 +2918,7 @@ function redirectToAssistant(chatId, assistantTopicId) {
2474
2918
  }
2475
2919
 
2476
2920
  // src/adapters/telegram/activity.ts
2477
- var log9 = createChildLogger({ module: "telegram:activity" });
2921
+ var log10 = createChildLogger({ module: "telegram:activity" });
2478
2922
  var THINKING_REFRESH_MS = 15e3;
2479
2923
  var THINKING_MAX_MS = 3 * 60 * 1e3;
2480
2924
  var ThinkingIndicator = class {
@@ -2506,7 +2950,7 @@ var ThinkingIndicator = class {
2506
2950
  this.startRefreshTimer();
2507
2951
  }
2508
2952
  } catch (err) {
2509
- log9.warn({ err }, "ThinkingIndicator.show() failed");
2953
+ log10.warn({ err }, "ThinkingIndicator.show() failed");
2510
2954
  } finally {
2511
2955
  this.sending = false;
2512
2956
  }
@@ -2579,7 +3023,7 @@ var UsageMessage = class {
2579
3023
  if (result) this.msgId = result.message_id;
2580
3024
  }
2581
3025
  } catch (err) {
2582
- log9.warn({ err }, "UsageMessage.send() failed");
3026
+ log10.warn({ err }, "UsageMessage.send() failed");
2583
3027
  }
2584
3028
  }
2585
3029
  getMsgId() {
@@ -2592,7 +3036,7 @@ var UsageMessage = class {
2592
3036
  try {
2593
3037
  await this.sendQueue.enqueue(() => this.api.deleteMessage(this.chatId, id));
2594
3038
  } catch (err) {
2595
- log9.warn({ err }, "UsageMessage.delete() failed");
3039
+ log10.warn({ err }, "UsageMessage.delete() failed");
2596
3040
  }
2597
3041
  }
2598
3042
  };
@@ -2678,7 +3122,7 @@ var PlanCard = class {
2678
3122
  if (result) this.msgId = result.message_id;
2679
3123
  }
2680
3124
  } catch (err) {
2681
- log9.warn({ err }, "PlanCard flush failed");
3125
+ log10.warn({ err }, "PlanCard flush failed");
2682
3126
  }
2683
3127
  }
2684
3128
  };
@@ -2741,7 +3185,7 @@ var ActivityTracker = class {
2741
3185
  })
2742
3186
  );
2743
3187
  } catch (err) {
2744
- log9.warn({ err }, "ActivityTracker.onComplete() Done send failed");
3188
+ log10.warn({ err }, "ActivityTracker.onComplete() Done send failed");
2745
3189
  }
2746
3190
  }
2747
3191
  }
@@ -2824,7 +3268,7 @@ var TelegramSendQueue = class {
2824
3268
 
2825
3269
  // src/adapters/telegram/action-detect.ts
2826
3270
  import { nanoid as nanoid2 } from "nanoid";
2827
- import { InlineKeyboard as InlineKeyboard10 } from "grammy";
3271
+ import { InlineKeyboard as InlineKeyboard11 } from "grammy";
2828
3272
  var CMD_NEW_RE = /\/new(?:\s+([^\s\u0080-\uFFFF]+)(?:\s+([^\s\u0080-\uFFFF]+))?)?/;
2829
3273
  var CMD_CANCEL_RE = /\/cancel\b/;
2830
3274
  var KW_NEW_RE = /(?:create|new)\s+session/i;
@@ -2871,7 +3315,7 @@ function removeAction(id) {
2871
3315
  actionMap.delete(id);
2872
3316
  }
2873
3317
  function buildActionKeyboard(actionId, action) {
2874
- const keyboard = new InlineKeyboard10();
3318
+ const keyboard = new InlineKeyboard11();
2875
3319
  if (action.action === "new_session") {
2876
3320
  keyboard.text("\u2705 Create session", `a:${actionId}`);
2877
3321
  keyboard.text("\u274C Cancel", `a:dismiss:${actionId}`);
@@ -2980,7 +3424,7 @@ function setupActionCallbacks(bot, core, chatId, getAssistantSessionId) {
2980
3424
  }
2981
3425
 
2982
3426
  // src/adapters/telegram/tool-call-tracker.ts
2983
- var log10 = createChildLogger({ module: "tool-call-tracker" });
3427
+ var log11 = createChildLogger({ module: "tool-call-tracker" });
2984
3428
  var ToolCallTracker = class {
2985
3429
  constructor(bot, chatId, sendQueue) {
2986
3430
  this.bot = bot;
@@ -2988,7 +3432,7 @@ var ToolCallTracker = class {
2988
3432
  this.sendQueue = sendQueue;
2989
3433
  }
2990
3434
  sessions = /* @__PURE__ */ new Map();
2991
- async trackNewCall(sessionId, threadId, meta) {
3435
+ async trackNewCall(sessionId, threadId, meta, verbosity = "medium") {
2992
3436
  if (!this.sessions.has(sessionId)) {
2993
3437
  this.sessions.set(sessionId, /* @__PURE__ */ new Map());
2994
3438
  }
@@ -3000,31 +3444,34 @@ var ToolCallTracker = class {
3000
3444
  msgId: 0,
3001
3445
  name: meta.name,
3002
3446
  kind: meta.kind,
3447
+ rawInput: meta.rawInput,
3003
3448
  viewerLinks: meta.viewerLinks,
3004
3449
  viewerFilePath: meta.viewerFilePath,
3005
3450
  ready
3006
3451
  });
3007
- const msg = await this.sendQueue.enqueue(
3008
- () => this.bot.api.sendMessage(
3009
- this.chatId,
3010
- formatToolCall(meta),
3011
- {
3452
+ try {
3453
+ const msg = await this.sendQueue.enqueue(
3454
+ () => this.bot.api.sendMessage(this.chatId, formatToolCall(meta, verbosity), {
3012
3455
  message_thread_id: threadId,
3013
3456
  parse_mode: "HTML",
3014
3457
  disable_notification: true
3015
- }
3016
- )
3017
- );
3018
- const toolEntry = this.sessions.get(sessionId).get(meta.id);
3019
- toolEntry.msgId = msg.message_id;
3020
- resolveReady();
3458
+ })
3459
+ );
3460
+ const toolEntry = this.sessions.get(sessionId).get(meta.id);
3461
+ toolEntry.msgId = msg.message_id;
3462
+ } finally {
3463
+ resolveReady();
3464
+ }
3021
3465
  }
3022
- async updateCall(sessionId, meta) {
3466
+ async updateCall(sessionId, meta, verbosity = "medium") {
3023
3467
  const toolState = this.sessions.get(sessionId)?.get(meta.id);
3024
3468
  if (!toolState) return;
3025
3469
  if (meta.viewerLinks) {
3026
3470
  toolState.viewerLinks = meta.viewerLinks;
3027
- log10.debug({ toolId: meta.id, viewerLinks: meta.viewerLinks }, "Accumulated viewerLinks");
3471
+ log11.debug(
3472
+ { toolId: meta.id, viewerLinks: meta.viewerLinks },
3473
+ "Accumulated viewerLinks"
3474
+ );
3028
3475
  }
3029
3476
  if (meta.viewerFilePath) toolState.viewerFilePath = meta.viewerFilePath;
3030
3477
  if (meta.name) toolState.name = meta.name;
@@ -3032,7 +3479,7 @@ var ToolCallTracker = class {
3032
3479
  const isTerminal = meta.status === "completed" || meta.status === "failed";
3033
3480
  if (!isTerminal) return;
3034
3481
  await toolState.ready;
3035
- log10.debug(
3482
+ log11.debug(
3036
3483
  {
3037
3484
  toolId: meta.id,
3038
3485
  status: meta.status,
@@ -3047,10 +3494,11 @@ var ToolCallTracker = class {
3047
3494
  ...meta,
3048
3495
  name: toolState.name,
3049
3496
  kind: toolState.kind,
3497
+ rawInput: toolState.rawInput,
3050
3498
  viewerLinks: toolState.viewerLinks,
3051
3499
  viewerFilePath: toolState.viewerFilePath
3052
3500
  };
3053
- const formattedText = formatToolUpdate(merged);
3501
+ const formattedText = formatToolUpdate(merged, verbosity);
3054
3502
  try {
3055
3503
  await this.sendQueue.enqueue(
3056
3504
  () => this.bot.api.editMessageText(
@@ -3061,7 +3509,7 @@ var ToolCallTracker = class {
3061
3509
  )
3062
3510
  );
3063
3511
  } catch (err) {
3064
- log10.warn(
3512
+ log11.warn(
3065
3513
  {
3066
3514
  err,
3067
3515
  msgId: toolState.msgId,
@@ -3204,7 +3652,7 @@ var MessageDraft = class {
3204
3652
  } catch {
3205
3653
  }
3206
3654
  }
3207
- const mdChunks = splitMessage(this.buffer);
3655
+ const mdChunks = splitMessage2(this.buffer);
3208
3656
  const chunkPromises = [];
3209
3657
  for (let i = 0; i < mdChunks.length; i++) {
3210
3658
  const html = markdownToTelegramHtml(mdChunks[i]);
@@ -3332,7 +3780,7 @@ var DraftManager = class {
3332
3780
  };
3333
3781
 
3334
3782
  // src/adapters/telegram/skill-command-manager.ts
3335
- var log11 = createChildLogger({ module: "skill-commands" });
3783
+ var log12 = createChildLogger({ module: "skill-commands" });
3336
3784
  var SkillCommandManager = class {
3337
3785
  // sessionId → pinned msgId
3338
3786
  constructor(bot, chatId, sendQueue, sessionManager) {
@@ -3398,7 +3846,7 @@ var SkillCommandManager = class {
3398
3846
  disable_notification: true
3399
3847
  });
3400
3848
  } catch (err) {
3401
- log11.error({ err, sessionId }, "Failed to send skill commands");
3849
+ log12.error({ err, sessionId }, "Failed to send skill commands");
3402
3850
  }
3403
3851
  }
3404
3852
  async cleanup(sessionId) {
@@ -3427,7 +3875,7 @@ var SkillCommandManager = class {
3427
3875
  };
3428
3876
 
3429
3877
  // src/adapters/telegram/adapter.ts
3430
- var log12 = createChildLogger({ module: "telegram" });
3878
+ var log13 = createChildLogger({ module: "telegram" });
3431
3879
  function patchedFetch(input, init) {
3432
3880
  if (init?.signal && !(init.signal instanceof AbortSignal)) {
3433
3881
  const nativeController = new AbortController();
@@ -3456,6 +3904,12 @@ var TelegramAdapter = class extends ChannelAdapter {
3456
3904
  skillManager;
3457
3905
  fileService;
3458
3906
  sessionTrackers = /* @__PURE__ */ new Map();
3907
+ get verbosity() {
3908
+ const live = this.core.configManager.get().channels?.telegram;
3909
+ const v = live?.displayVerbosity ?? this.telegramConfig.displayVerbosity;
3910
+ if (v === "low" || v === "high") return v;
3911
+ return "medium";
3912
+ }
3459
3913
  getOrCreateTracker(sessionId, threadId) {
3460
3914
  let tracker = this.sessionTrackers.get(sessionId);
3461
3915
  if (!tracker) {
@@ -3481,8 +3935,16 @@ var TelegramAdapter = class extends ChannelAdapter {
3481
3935
  }
3482
3936
  });
3483
3937
  this.fileService = this.core.fileService;
3484
- this.toolTracker = new ToolCallTracker(this.bot, this.telegramConfig.chatId, this.sendQueue);
3485
- this.draftManager = new DraftManager(this.bot, this.telegramConfig.chatId, this.sendQueue);
3938
+ this.toolTracker = new ToolCallTracker(
3939
+ this.bot,
3940
+ this.telegramConfig.chatId,
3941
+ this.sendQueue
3942
+ );
3943
+ this.draftManager = new DraftManager(
3944
+ this.bot,
3945
+ this.telegramConfig.chatId,
3946
+ this.sendQueue
3947
+ );
3486
3948
  this.skillManager = new SkillCommandManager(
3487
3949
  this.bot,
3488
3950
  this.telegramConfig.chatId,
@@ -3491,7 +3953,7 @@ var TelegramAdapter = class extends ChannelAdapter {
3491
3953
  );
3492
3954
  this.bot.catch((err) => {
3493
3955
  const rootCause = err.error instanceof Error ? err.error : err;
3494
- log12.error({ err: rootCause }, "Telegram bot error");
3956
+ log13.error({ err: rootCause }, "Telegram bot error");
3495
3957
  });
3496
3958
  this.bot.api.config.use(async (prev, method, payload, signal) => {
3497
3959
  const maxRetries = 3;
@@ -3501,11 +3963,15 @@ var TelegramAdapter = class extends ChannelAdapter {
3501
3963
  return result;
3502
3964
  }
3503
3965
  const retryAfter = (result.parameters?.retry_after ?? 5) + 1;
3504
- const rateLimitedMethods = ["sendMessage", "editMessageText", "editMessageReplyMarkup"];
3966
+ const rateLimitedMethods = [
3967
+ "sendMessage",
3968
+ "editMessageText",
3969
+ "editMessageReplyMarkup"
3970
+ ];
3505
3971
  if (rateLimitedMethods.includes(method)) {
3506
3972
  this.sendQueue.onRateLimited();
3507
3973
  }
3508
- log12.warn(
3974
+ log13.warn(
3509
3975
  { method, retryAfter, attempt: attempt + 1 },
3510
3976
  "Rate limited by Telegram, retrying"
3511
3977
  );
@@ -3551,6 +4017,7 @@ var TelegramAdapter = class extends ChannelAdapter {
3551
4017
  );
3552
4018
  setupDangerousModeCallbacks(this.bot, this.core);
3553
4019
  setupTTSCallbacks(this.bot, this.core);
4020
+ setupVerbosityCallbacks(this.bot, this.core);
3554
4021
  setupActionCallbacks(
3555
4022
  this.bot,
3556
4023
  this.core,
@@ -3562,7 +4029,10 @@ var TelegramAdapter = class extends ChannelAdapter {
3562
4029
  this.bot,
3563
4030
  this.core,
3564
4031
  this.telegramConfig.chatId,
3565
- { notificationTopicId: this.notificationTopicId, assistantTopicId: this.assistantTopicId },
4032
+ {
4033
+ notificationTopicId: this.notificationTopicId,
4034
+ assistantTopicId: this.assistantTopicId
4035
+ },
3566
4036
  () => {
3567
4037
  if (!this.assistantSession) return void 0;
3568
4038
  return {
@@ -3606,8 +4076,14 @@ var TelegramAdapter = class extends ChannelAdapter {
3606
4076
  });
3607
4077
  return;
3608
4078
  }
3609
- const session = this.core.sessionManager.getSessionByThread("telegram", String(threadId));
3610
- const record = session ? void 0 : this.core.sessionManager.getRecordByThread("telegram", String(threadId));
4079
+ const session = this.core.sessionManager.getSessionByThread(
4080
+ "telegram",
4081
+ String(threadId)
4082
+ );
4083
+ const record = session ? void 0 : this.core.sessionManager.getRecordByThread(
4084
+ "telegram",
4085
+ String(threadId)
4086
+ );
3611
4087
  const agentName = session?.agentName ?? record?.agentName;
3612
4088
  const agentSessionId = session?.agentSessionId ?? record?.agentSessionId;
3613
4089
  if (!agentName || !agentSessionId) {
@@ -3638,7 +4114,7 @@ var TelegramAdapter = class extends ChannelAdapter {
3638
4114
  this.setupRoutes();
3639
4115
  this.bot.start({
3640
4116
  allowed_updates: ["message", "callback_query"],
3641
- onStart: () => log12.info(
4117
+ onStart: () => log13.info(
3642
4118
  { chatId: this.telegramConfig.chatId },
3643
4119
  "Telegram bot started"
3644
4120
  )
@@ -3648,7 +4124,9 @@ var TelegramAdapter = class extends ChannelAdapter {
3648
4124
  const agents = this.core.agentManager.getAvailableAgents();
3649
4125
  const allRecords = this.core.sessionManager.listRecords();
3650
4126
  const welcomeText = buildWelcomeMessage({
3651
- activeCount: allRecords.filter((r) => r.status === "active" || r.status === "initializing").length,
4127
+ activeCount: allRecords.filter(
4128
+ (r) => r.status === "active" || r.status === "initializing"
4129
+ ).length,
3652
4130
  errorCount: allRecords.filter((r) => r.status === "error").length,
3653
4131
  totalCount: allRecords.length,
3654
4132
  agents: agents.map((a) => a.name),
@@ -3660,10 +4138,10 @@ var TelegramAdapter = class extends ChannelAdapter {
3660
4138
  reply_markup: buildMenuKeyboard()
3661
4139
  });
3662
4140
  } catch (err) {
3663
- log12.warn({ err }, "Failed to send welcome message");
4141
+ log13.warn({ err }, "Failed to send welcome message");
3664
4142
  }
3665
4143
  try {
3666
- log12.info("Spawning assistant session...");
4144
+ log13.info("Spawning assistant session...");
3667
4145
  const { session, ready } = await spawnAssistant(
3668
4146
  this.core,
3669
4147
  this,
@@ -3671,13 +4149,19 @@ var TelegramAdapter = class extends ChannelAdapter {
3671
4149
  );
3672
4150
  this.assistantSession = session;
3673
4151
  this.assistantInitializing = true;
3674
- log12.info({ sessionId: session.id }, "Assistant session ready, system prompt running in background");
4152
+ log13.info(
4153
+ { sessionId: session.id },
4154
+ "Assistant session ready, system prompt running in background"
4155
+ );
3675
4156
  ready.then(() => {
3676
4157
  this.assistantInitializing = false;
3677
- log12.info({ sessionId: session.id }, "Assistant ready for user messages");
4158
+ log13.info(
4159
+ { sessionId: session.id },
4160
+ "Assistant ready for user messages"
4161
+ );
3678
4162
  });
3679
4163
  } catch (err) {
3680
- log12.error({ err }, "Failed to spawn assistant");
4164
+ log13.error({ err }, "Failed to spawn assistant");
3681
4165
  this.bot.api.sendMessage(
3682
4166
  this.telegramConfig.chatId,
3683
4167
  `\u26A0\uFE0F <b>Failed to start assistant session.</b>
@@ -3693,13 +4177,26 @@ var TelegramAdapter = class extends ChannelAdapter {
3693
4177
  await this.assistantSession.destroy();
3694
4178
  }
3695
4179
  await this.bot.stop();
3696
- log12.info("Telegram bot stopped");
4180
+ log13.info("Telegram bot stopped");
3697
4181
  }
3698
4182
  setupRoutes() {
3699
4183
  this.bot.on("message:text", async (ctx) => {
3700
4184
  const threadId = ctx.message.message_thread_id;
3701
4185
  const text = ctx.message.text;
3702
- if (await handlePendingWorkspaceInput(ctx, this.core, this.telegramConfig.chatId, this.assistantTopicId)) {
4186
+ if (await handlePendingWorkspaceInput(
4187
+ ctx,
4188
+ this.core,
4189
+ this.telegramConfig.chatId,
4190
+ this.assistantTopicId
4191
+ )) {
4192
+ return;
4193
+ }
4194
+ if (await handlePendingResumeInput(
4195
+ ctx,
4196
+ this.core,
4197
+ this.telegramConfig.chatId,
4198
+ this.assistantTopicId
4199
+ )) {
3703
4200
  return;
3704
4201
  }
3705
4202
  if (!threadId) {
@@ -3714,19 +4211,29 @@ var TelegramAdapter = class extends ChannelAdapter {
3714
4211
  const forwardText = text.startsWith("/") ? text.slice(1) : text;
3715
4212
  if (threadId === this.assistantTopicId) {
3716
4213
  if (!this.assistantSession) {
3717
- await ctx.reply("\u26A0\uFE0F Assistant is not available yet. Please try again shortly.", { parse_mode: "HTML" });
4214
+ await ctx.reply(
4215
+ "\u26A0\uFE0F Assistant is not available yet. Please try again shortly.",
4216
+ { parse_mode: "HTML" }
4217
+ );
3718
4218
  return;
3719
4219
  }
3720
- await this.draftManager.finalize(this.assistantSession.id, this.assistantSession.id);
4220
+ await this.draftManager.finalize(
4221
+ this.assistantSession.id,
4222
+ this.assistantSession.id
4223
+ );
3721
4224
  ctx.replyWithChatAction("typing").catch(() => {
3722
4225
  });
3723
4226
  handleAssistantMessage(this.assistantSession, forwardText).catch(
3724
- (err) => log12.error({ err }, "Assistant error")
4227
+ (err) => log13.error({ err }, "Assistant error")
3725
4228
  );
3726
4229
  return;
3727
4230
  }
3728
- const sessionId = this.core.sessionManager.getSessionByThread("telegram", String(threadId))?.id;
3729
- if (sessionId) await this.draftManager.finalize(sessionId, this.assistantSession?.id);
4231
+ const sessionId = this.core.sessionManager.getSessionByThread(
4232
+ "telegram",
4233
+ String(threadId)
4234
+ )?.id;
4235
+ if (sessionId)
4236
+ await this.draftManager.finalize(sessionId, this.assistantSession?.id);
3730
4237
  if (sessionId) {
3731
4238
  const tracker = this.sessionTrackers.get(sessionId);
3732
4239
  if (tracker) await tracker.onNewPrompt();
@@ -3738,7 +4245,7 @@ var TelegramAdapter = class extends ChannelAdapter {
3738
4245
  threadId: String(threadId),
3739
4246
  userId: String(ctx.from.id),
3740
4247
  text: forwardText
3741
- }).catch((err) => log12.error({ err }, "handleMessage error"));
4248
+ }).catch((err) => log13.error({ err }, "handleMessage error"));
3742
4249
  });
3743
4250
  this.bot.on("message:photo", async (ctx) => {
3744
4251
  const threadId = ctx.message.message_thread_id;
@@ -3825,25 +4332,57 @@ var TelegramAdapter = class extends ChannelAdapter {
3825
4332
  this.draftManager.appendText(ctx.sessionId, content.text);
3826
4333
  },
3827
4334
  onToolCall: async (ctx, content) => {
4335
+ const meta = content.metadata ?? {};
4336
+ const toolName = meta.name ?? content.text ?? "Tool";
4337
+ const toolKind = String(meta.kind ?? "other");
4338
+ const noiseAction = evaluateNoise(toolName, toolKind, meta.rawInput);
4339
+ if (noiseAction === "hide" && this.verbosity !== "high") return;
4340
+ if (noiseAction === "collapse" && this.verbosity === "low") return;
3828
4341
  const tracker = this.getOrCreateTracker(ctx.sessionId, ctx.threadId);
3829
4342
  await tracker.onToolCall();
3830
- await this.draftManager.finalize(ctx.sessionId, this.assistantSession?.id);
3831
- const meta = content.metadata;
3832
- await this.toolTracker.trackNewCall(ctx.sessionId, ctx.threadId, {
3833
- ...meta
3834
- });
4343
+ await this.draftManager.finalize(
4344
+ ctx.sessionId,
4345
+ this.assistantSession?.id
4346
+ );
4347
+ await this.toolTracker.trackNewCall(
4348
+ ctx.sessionId,
4349
+ ctx.threadId,
4350
+ {
4351
+ id: meta.id ?? "",
4352
+ name: meta.name ?? content.text ?? "Tool",
4353
+ kind: meta.kind,
4354
+ status: meta.status,
4355
+ content: meta.content,
4356
+ rawInput: meta.rawInput,
4357
+ viewerLinks: meta.viewerLinks,
4358
+ viewerFilePath: meta.viewerFilePath
4359
+ },
4360
+ this.verbosity
4361
+ );
3835
4362
  },
3836
4363
  onToolUpdate: async (ctx, content) => {
3837
- const meta = content.metadata;
3838
- await this.toolTracker.updateCall(ctx.sessionId, {
3839
- ...meta
3840
- });
4364
+ const meta = content.metadata ?? {};
4365
+ await this.toolTracker.updateCall(
4366
+ ctx.sessionId,
4367
+ {
4368
+ id: meta.id ?? "",
4369
+ name: meta.name ?? content.text ?? "",
4370
+ kind: meta.kind,
4371
+ status: meta.status ?? "completed",
4372
+ content: meta.content,
4373
+ rawInput: meta.rawInput,
4374
+ viewerLinks: meta.viewerLinks,
4375
+ viewerFilePath: meta.viewerFilePath
4376
+ },
4377
+ this.verbosity
4378
+ );
3841
4379
  },
3842
4380
  onPlan: async (ctx, content) => {
3843
- const meta = content.metadata;
4381
+ const meta = content.metadata ?? {};
4382
+ const entries = meta.entries ?? [];
3844
4383
  const tracker = this.getOrCreateTracker(ctx.sessionId, ctx.threadId);
3845
4384
  await tracker.onPlan(
3846
- meta.entries.map((e) => ({
4385
+ entries.map((e) => ({
3847
4386
  content: e.content,
3848
4387
  status: e.status,
3849
4388
  priority: e.priority ?? "medium"
@@ -3852,7 +4391,10 @@ var TelegramAdapter = class extends ChannelAdapter {
3852
4391
  },
3853
4392
  onUsage: async (ctx, content) => {
3854
4393
  const meta = content.metadata;
3855
- await this.draftManager.finalize(ctx.sessionId, this.assistantSession?.id);
4394
+ await this.draftManager.finalize(
4395
+ ctx.sessionId,
4396
+ this.assistantSession?.id
4397
+ );
3856
4398
  const tracker = this.getOrCreateTracker(ctx.sessionId, ctx.threadId);
3857
4399
  await tracker.sendUsage(meta ?? {});
3858
4400
  if (this.notificationTopicId && ctx.sessionId !== this.assistantSession?.id) {
@@ -3880,7 +4422,14 @@ Task completed.
3880
4422
  if (!content.attachment) return;
3881
4423
  const { attachment } = content;
3882
4424
  if (attachment.size > 50 * 1024 * 1024) {
3883
- log12.warn({ sessionId: ctx.sessionId, fileName: attachment.fileName, size: attachment.size }, "File too large for Telegram (>50MB)");
4425
+ log13.warn(
4426
+ {
4427
+ sessionId: ctx.sessionId,
4428
+ fileName: attachment.fileName,
4429
+ size: attachment.size
4430
+ },
4431
+ "File too large for Telegram (>50MB)"
4432
+ );
3884
4433
  await this.sendQueue.enqueue(
3885
4434
  () => this.bot.api.sendMessage(
3886
4435
  this.telegramConfig.chatId,
@@ -3917,11 +4466,17 @@ Task completed.
3917
4466
  );
3918
4467
  }
3919
4468
  } catch (err) {
3920
- log12.error({ err, sessionId: ctx.sessionId, fileName: attachment.fileName }, "Failed to send attachment");
4469
+ log13.error(
4470
+ { err, sessionId: ctx.sessionId, fileName: attachment.fileName },
4471
+ "Failed to send attachment"
4472
+ );
3921
4473
  }
3922
4474
  },
3923
4475
  onSessionEnd: async (ctx, _content) => {
3924
- await this.draftManager.finalize(ctx.sessionId, this.assistantSession?.id);
4476
+ await this.draftManager.finalize(
4477
+ ctx.sessionId,
4478
+ this.assistantSession?.id
4479
+ );
3925
4480
  this.draftManager.cleanup(ctx.sessionId);
3926
4481
  this.toolTracker.cleanup(ctx.sessionId);
3927
4482
  await this.skillManager.cleanup(ctx.sessionId);
@@ -3945,7 +4500,10 @@ Task completed.
3945
4500
  }
3946
4501
  },
3947
4502
  onError: async (ctx, content) => {
3948
- await this.draftManager.finalize(ctx.sessionId, this.assistantSession?.id);
4503
+ await this.draftManager.finalize(
4504
+ ctx.sessionId,
4505
+ this.assistantSession?.id
4506
+ );
3949
4507
  const tracker = this.sessionTrackers.get(ctx.sessionId);
3950
4508
  if (tracker) {
3951
4509
  tracker.destroy();
@@ -3979,20 +4537,24 @@ Task completed.
3979
4537
  };
3980
4538
  // --- ChannelAdapter implementations ---
3981
4539
  async sendMessage(sessionId, content) {
3982
- if (this.assistantInitializing && sessionId === this.assistantSession?.id) return;
4540
+ if (this.assistantInitializing && sessionId === this.assistantSession?.id)
4541
+ return;
3983
4542
  const session = this.core.sessionManager.getSession(sessionId);
3984
4543
  if (!session) return;
3985
4544
  if (session.archiving) return;
3986
4545
  const threadId = Number(session.threadId);
3987
4546
  if (!threadId || isNaN(threadId)) {
3988
- log12.warn({ sessionId, threadId: session.threadId }, "Session has no valid threadId, skipping message");
4547
+ log13.warn(
4548
+ { sessionId, threadId: session.threadId },
4549
+ "Session has no valid threadId, skipping message"
4550
+ );
3989
4551
  return;
3990
4552
  }
3991
4553
  const ctx = { sessionId, threadId };
3992
- await dispatchMessage(this.messageHandlers, ctx, content);
4554
+ await dispatchMessage(this.messageHandlers, ctx, content, this.verbosity);
3993
4555
  }
3994
4556
  async sendPermissionRequest(sessionId, request) {
3995
- log12.info({ sessionId, requestId: request.id }, "Permission request sent");
4557
+ log13.info({ sessionId, requestId: request.id }, "Permission request sent");
3996
4558
  const session = this.core.sessionManager.getSession(sessionId);
3997
4559
  if (!session) return;
3998
4560
  await this.sendQueue.enqueue(
@@ -4001,7 +4563,7 @@ Task completed.
4001
4563
  }
4002
4564
  async sendNotification(notification) {
4003
4565
  if (notification.sessionId === this.assistantSession?.id) return;
4004
- log12.info(
4566
+ log13.info(
4005
4567
  { sessionId: notification.sessionId, type: notification.type },
4006
4568
  "Notification sent"
4007
4569
  );
@@ -4016,7 +4578,9 @@ Task completed.
4016
4578
  `;
4017
4579
  text += escapeHtml(notification.summary);
4018
4580
  const deepLink = notification.deepLink ?? (() => {
4019
- const session = this.core.sessionManager.getSession(notification.sessionId);
4581
+ const session = this.core.sessionManager.getSession(
4582
+ notification.sessionId
4583
+ );
4020
4584
  const threadId = session?.threadId;
4021
4585
  if (!threadId) return void 0;
4022
4586
  const chatIdStr = String(this.telegramConfig.chatId);
@@ -4028,16 +4592,18 @@ Task completed.
4028
4592
 
4029
4593
  <a href="${deepLink}">\u2192 Go to topic</a>`;
4030
4594
  }
4595
+ const replyMarkup = notification.type === "completed" ? { inline_keyboard: [[{ text: "\u{1F4CB} Summary", callback_data: `sm:summary:${notification.sessionId}` }]] } : void 0;
4031
4596
  await this.sendQueue.enqueue(
4032
4597
  () => this.bot.api.sendMessage(this.telegramConfig.chatId, text, {
4033
4598
  message_thread_id: this.notificationTopicId,
4034
4599
  parse_mode: "HTML",
4035
- disable_notification: false
4600
+ disable_notification: false,
4601
+ reply_markup: replyMarkup
4036
4602
  })
4037
4603
  );
4038
4604
  }
4039
4605
  async createSessionThread(sessionId, name) {
4040
- log12.info({ sessionId, name }, "Session topic created");
4606
+ log13.info({ sessionId, name }, "Session topic created");
4041
4607
  return String(
4042
4608
  await createSessionTopic(this.bot, this.telegramConfig.chatId, name)
4043
4609
  );
@@ -4061,7 +4627,10 @@ Task completed.
4061
4627
  try {
4062
4628
  await this.bot.api.deleteForumTopic(this.telegramConfig.chatId, topicId);
4063
4629
  } catch (err) {
4064
- log12.warn({ err, sessionId, topicId }, "Failed to delete forum topic (may already be deleted)");
4630
+ log13.warn(
4631
+ { err, sessionId, topicId },
4632
+ "Failed to delete forum topic (may already be deleted)"
4633
+ );
4065
4634
  }
4066
4635
  }
4067
4636
  async sendSkillCommands(sessionId, commands) {
@@ -4073,7 +4642,10 @@ Task completed.
4073
4642
  await this.skillManager.send(sessionId, threadId, commands);
4074
4643
  }
4075
4644
  resolveSessionId(threadId) {
4076
- return this.core.sessionManager.getSessionByThread("telegram", String(threadId))?.id;
4645
+ return this.core.sessionManager.getSessionByThread(
4646
+ "telegram",
4647
+ String(threadId)
4648
+ )?.id;
4077
4649
  }
4078
4650
  async downloadTelegramFile(fileId) {
4079
4651
  try {
@@ -4085,7 +4657,7 @@ Task completed.
4085
4657
  const buffer = Buffer.from(await response.arrayBuffer());
4086
4658
  return { buffer, filePath: file.file_path };
4087
4659
  } catch (err) {
4088
- log12.error({ err }, "Failed to download file from Telegram");
4660
+ log13.error({ err }, "Failed to download file from Telegram");
4089
4661
  return null;
4090
4662
  }
4091
4663
  }
@@ -4096,18 +4668,28 @@ Task completed.
4096
4668
  let originalFilePath;
4097
4669
  const sessionId = this.resolveSessionId(threadId) || "unknown";
4098
4670
  if (convertOggToWav) {
4099
- const oggAtt = await this.fileService.saveFile(sessionId, "voice.ogg", downloaded.buffer, "audio/ogg");
4671
+ const oggAtt = await this.fileService.saveFile(
4672
+ sessionId,
4673
+ "voice.ogg",
4674
+ downloaded.buffer,
4675
+ "audio/ogg"
4676
+ );
4100
4677
  originalFilePath = oggAtt.filePath;
4101
4678
  try {
4102
4679
  buffer = await this.fileService.convertOggToWav(buffer);
4103
4680
  } catch (err) {
4104
- log12.warn({ err }, "OGG\u2192WAV conversion failed, saving original OGG");
4681
+ log13.warn({ err }, "OGG\u2192WAV conversion failed, saving original OGG");
4105
4682
  fileName = "voice.ogg";
4106
4683
  mimeType = "audio/ogg";
4107
4684
  originalFilePath = void 0;
4108
4685
  }
4109
4686
  }
4110
- const att = await this.fileService.saveFile(sessionId, fileName, buffer, mimeType);
4687
+ const att = await this.fileService.saveFile(
4688
+ sessionId,
4689
+ fileName,
4690
+ buffer,
4691
+ mimeType
4692
+ );
4111
4693
  if (originalFilePath) {
4112
4694
  att.originalFilePath = originalFilePath;
4113
4695
  }
@@ -4127,7 +4709,7 @@ Task completed.
4127
4709
  userId: String(userId),
4128
4710
  text,
4129
4711
  attachments: [att]
4130
- }).catch((err) => log12.error({ err }, "handleMessage error"));
4712
+ }).catch((err) => log13.error({ err }, "handleMessage error"));
4131
4713
  }
4132
4714
  async cleanupSkillCommands(sessionId) {
4133
4715
  await this.skillManager.cleanup(sessionId);
@@ -4135,10 +4717,9 @@ Task completed.
4135
4717
  async archiveSessionTopic(sessionId) {
4136
4718
  const core = this.core;
4137
4719
  const session = core.sessionManager.getSession(sessionId);
4138
- if (!session) return null;
4720
+ if (!session) return;
4139
4721
  const chatId = this.telegramConfig.chatId;
4140
4722
  const oldTopicId = Number(session.threadId);
4141
- const rawName = (session.name || `Session ${session.id.slice(0, 6)}`).replace(/^🔄\s*/, "");
4142
4723
  session.archiving = true;
4143
4724
  await this.draftManager.finalize(session.id, this.assistantSession?.id);
4144
4725
  this.draftManager.cleanup(session.id);
@@ -4150,32 +4731,10 @@ Task completed.
4150
4731
  this.sessionTrackers.delete(session.id);
4151
4732
  }
4152
4733
  await deleteSessionTopic(this.bot, chatId, oldTopicId);
4153
- let newTopicId;
4154
- try {
4155
- newTopicId = await createSessionTopic(this.bot, chatId, `\u{1F504} ${rawName}`);
4156
- } catch (createErr) {
4157
- session.archiving = false;
4158
- core.notificationManager.notifyAll({
4159
- sessionId: session.id,
4160
- sessionName: session.name,
4161
- type: "error",
4162
- summary: `Topic recreation failed for session "${rawName}". Session is orphaned. Error: ${createErr.message}`
4163
- });
4164
- throw createErr;
4165
- }
4166
- session.threadId = String(newTopicId);
4167
- const existingRecord = core.sessionManager.getSessionRecord(session.id);
4168
- const existingPlatform = { ...existingRecord?.platform ?? {} };
4169
- delete existingPlatform.skillMsgId;
4170
- await core.sessionManager.patchRecord(session.id, {
4171
- platform: { ...existingPlatform, topicId: newTopicId }
4172
- });
4173
- session.archiving = false;
4174
- return { newThreadId: String(newTopicId) };
4175
4734
  }
4176
4735
  };
4177
4736
 
4178
4737
  export {
4179
4738
  TelegramAdapter
4180
4739
  };
4181
- //# sourceMappingURL=chunk-FW6HM4VU.js.map
4740
+ //# sourceMappingURL=chunk-5SXG7X5D.js.map