grok-telegram-bot 2.3.0 → 2.4.0

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/.env.example +26 -0
  2. package/CHANGELOG.md +55 -0
  3. package/package.json +1 -1
  4. package/scripts/analyze-jsonl.ts +33 -0
  5. package/scripts/delayed-restart.ps1 +29 -0
  6. package/scripts/probe-exit-response-shape.py +77 -0
  7. package/scripts/probe-plan-exit.py +60 -0
  8. package/scripts/probe-plan-exit2.py +48 -0
  9. package/scripts/probe-plan-fields.py +41 -0
  10. package/scripts/probe-plan-fields2.py +58 -0
  11. package/scripts/probe-plan-response-path.py +48 -0
  12. package/scripts/sample-claude-tooluse.ts +21 -0
  13. package/scripts/sample-kiro-events.ts +31 -0
  14. package/scripts/smoke-exit-plan.ts +274 -0
  15. package/scripts/smoke-exit-shapes.ts +252 -0
  16. package/scripts/smoke-import.mjs +82 -0
  17. package/scripts/smoke-import.ts +73 -0
  18. package/src/app/accounts.ts +84 -0
  19. package/src/app/instance-lock.ts +6 -0
  20. package/src/app/types.ts +19 -2
  21. package/src/app/updater.ts +17 -6
  22. package/src/app/usage.ts +204 -7
  23. package/src/bot/account-rotator.ts +71 -2
  24. package/src/bot/bot.ts +36 -0
  25. package/src/bot/chat-controller.ts +35 -0
  26. package/src/bot/commands.ts +2 -0
  27. package/src/bot/complexity-gate.ts +69 -0
  28. package/src/bot/deps.ts +19 -0
  29. package/src/bot/handlers/accounts.ts +55 -5
  30. package/src/bot/handlers/import-session.ts +290 -0
  31. package/src/bot/handlers/menu.ts +17 -38
  32. package/src/bot/handlers/message.ts +1 -0
  33. package/src/bot/handlers/running.ts +35 -5
  34. package/src/bot/handlers/session-card.ts +12 -0
  35. package/src/bot/handlers/sessions.ts +14 -3
  36. package/src/bot/handlers/usage.ts +118 -16
  37. package/src/bot/menu/keyboard.ts +5 -4
  38. package/src/bot/menu/status-panel.ts +19 -6
  39. package/src/bot/prompt-content.ts +4 -0
  40. package/src/bot/reauth-controller.ts +2 -2
  41. package/src/bot/session-fork.ts +11 -0
  42. package/src/bot/session-runtime.ts +831 -64
  43. package/src/bot/suggestions.ts +429 -0
  44. package/src/config.ts +41 -0
  45. package/src/grok/client.ts +106 -20
  46. package/src/grok/plan-approval.ts +72 -0
  47. package/src/grok/session-log.ts +16 -0
  48. package/src/grok/types.ts +21 -2
  49. package/src/import/build-import.ts +132 -0
  50. package/src/import/history-readers.ts +681 -0
  51. package/src/import/list-running.ts +100 -0
  52. package/src/import/sources.ts +78 -0
  53. package/src/index.ts +179 -24
  54. package/src/render/diff.ts +11 -2
  55. package/src/render/file-summary.ts +31 -1
  56. package/src/render/markdown.ts +293 -35
  57. package/src/render/plan.ts +127 -0
  58. package/src/render/session-comment.ts +261 -0
  59. package/src/render/tool-call-detail.ts +400 -19
  60. package/src/render/tool-call-merge.ts +115 -0
  61. package/src/render/tool-call.ts +405 -142
  62. package/src/render/truncate.ts +85 -0
  63. package/src/service/windows.ts +14 -2
  64. package/src/sessions/history.ts +57 -0
  65. package/src/sessions/store.ts +3 -0
  66. package/src/sessions/types.ts +5 -0
  67. package/src/stream/streamer.ts +73 -9
  68. package/src/tasks/runner.ts +4 -3
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Session "comment" shown on Running / Sessions cards:
3
+ * • while busy — current step (tool / thinking / working on …)
4
+ * • when idle — what the last turn solved (assistant result + files)
5
+ *
6
+ * Built only from data already in the turn — no extra agent prompt (those
7
+ * leaked into chat/history). Display-only; never truncates agent context.
8
+ */
9
+ import { basename } from "node:path";
10
+ import type { SessionUpdate } from "../grok/types.js";
11
+ import { extractProgress } from "./progress.js";
12
+ import {
13
+ extractCommand,
14
+ extractPath,
15
+ extractSearchQuery,
16
+ extractUrl,
17
+ resolveToolIdentity,
18
+ } from "./tool-call-detail.js";
19
+ import type { FileOp } from "./file-summary.js";
20
+
21
+ /** Max length of a card comment line (room for a real one-sentence outcome). */
22
+ export const COMMENT_MAX = 200;
23
+
24
+ /**
25
+ * Legacy marker for the removed silent AI card-summary prompt. Kept only so
26
+ * history / previews can strip it if an old session log still contains it.
27
+ */
28
+ export const COMMENT_SUMMARY_PROMPT_PREFIX = "Session status update (meta only).";
29
+
30
+ /** Collapse whitespace and clamp to card width. */
31
+ export function cleanCommentLine(raw: string, max = COMMENT_MAX): string {
32
+ // Drop leaked meta-summary prompts (should never be a card comment).
33
+ if (raw.includes(COMMENT_SUMMARY_PROMPT_PREFIX) || /^Session status update\b/i.test(raw.trim())) {
34
+ return "";
35
+ }
36
+ let t = raw
37
+ .replace(/\{[\s]*progress[\s]*:[\s]*\d{1,3}\s*%?[\s]*\}/gi, "")
38
+ .replace(/\r\n/g, "\n")
39
+ .replace(/\s+/g, " ")
40
+ .trim();
41
+ // Prefer the first non-empty line if multi-line junk remains.
42
+ const first = t.split("\n").map((l) => l.trim()).find((l) => l.length > 0);
43
+ t = (first ?? t).replace(/^["'`]+|["'`]+$/g, "").trim();
44
+ if (!t) return "";
45
+ if (t.length <= max) return t;
46
+ return t.slice(0, max - 1) + "\u2026";
47
+ }
48
+
49
+ /**
50
+ * Strip bot-injected wrappers (complexity directive, reply markers) so recheck
51
+ * prompts and card previews see the user's real request text.
52
+ */
53
+ export function stripDirectiveWrappers(raw: string): string {
54
+ let t = raw.trim().replace(/^\([^)]*\)\s*/, "");
55
+ const marker = "User's new message:";
56
+ const i = t.lastIndexOf(marker);
57
+ if (i !== -1) t = t.slice(i + marker.length);
58
+ t = t.replace(/^TASK COMPLEXITY:[\s\S]*?User task:\s*/i, "");
59
+ t = t.replace(/^COMPLEXITY \(decide yourself[\s\S]*?User task:\s*/i, "");
60
+ return t.trim();
61
+ }
62
+
63
+ /** Strip bot directives so a user prompt is card-friendly. */
64
+ export function cleanUserPreview(raw: string, max = 80): string {
65
+ let t = stripDirectiveWrappers(raw);
66
+ // Import confirm prompts are noise on cards.
67
+ if (/session import complete/i.test(t)) return "";
68
+ // Self-recheck / quiet meta prompts should not appear as user "Working:" text.
69
+ if (/^SELF-RECHECK \(automatic quality pass/i.test(t)) return "Self-recheck";
70
+ if (/^SELF-RECHECK DECISION \(meta only\)/i.test(t)) return "";
71
+ if (/^FOLLOW-UP SUGGESTIONS \(meta only\)/i.test(t)) return "";
72
+ return cleanCommentLine(t, max);
73
+ }
74
+
75
+ /**
76
+ * Pull a human-useful outcome snippet from the assistant's streamed reply.
77
+ * Prefers the closing sentences (where conclusions land) over mid-turn "I'll…".
78
+ */
79
+ export function extractResultSnippet(assistantText: string | undefined, max = 160): string {
80
+ if (!assistantText?.trim()) return "";
81
+ let t = extractProgress(assistantText).cleaned;
82
+ // Drop fenced code / diffs / tool-looking blocks — keep prose.
83
+ t = t.replace(/```[\s\S]*?```/g, " ");
84
+ t = t.replace(/^>\s?.*$/gm, " "); // quoted thinking
85
+ // Drop lines that look like tool-call headers (emoji + bold title).
86
+ t = t.replace(/^[^\nA-Za-z0-9]*\*\*[^*\n]+\*\*[^\n]*$/gm, " ");
87
+ t = t.replace(/\*\*([^*]+)\*\*/g, "$1");
88
+ t = t.replace(/`([^`]+)`/g, "$1");
89
+ t = t.replace(/\s+/g, " ").trim();
90
+ if (!t) return "";
91
+ // Filter pure meta / plumbing lines.
92
+ if (/^Session status update\b/i.test(t)) return "";
93
+ if (/^COMPLEXITY \(decide yourself/i.test(t)) return "";
94
+
95
+ // Prefer last 1–2 substantial sentences (what was solved).
96
+ const sentences = t
97
+ .split(/(?<=[.!?])\s+/)
98
+ .map((s) => s.trim())
99
+ .filter((s) => s.length >= 24 && !isWeakOpener(s));
100
+ if (sentences.length >= 2) {
101
+ const tail = sentences.slice(-2).join(" ");
102
+ return cleanCommentLine(tail, max);
103
+ }
104
+ if (sentences.length === 1) return cleanCommentLine(sentences[0]!, max);
105
+
106
+ // Fallback: whole cleaned text, clamped.
107
+ return cleanCommentLine(t, max);
108
+ }
109
+
110
+ /** Mid-turn openers that are not useful as a "what was solved" line. */
111
+ function isWeakOpener(s: string): boolean {
112
+ return /^(i'?ll |i will |let me |looking |investigat|tracing |checking |reading |searching )/i.test(
113
+ s,
114
+ );
115
+ }
116
+
117
+ /** Compact file-change phrase: "~3: tool-call.ts, running.ts +1". */
118
+ export function formatFilesPhrase(fileOps: Map<string, FileOp>, maxNames = 3): string {
119
+ const n = fileOps.size;
120
+ if (n === 0) return "";
121
+ const names = [...fileOps.keys()]
122
+ .slice(0, maxNames)
123
+ .map((p) => basename(p.replace(/\\/g, "/")));
124
+ const more = n > maxNames ? ` +${n - maxNames}` : "";
125
+ const counts = countsShort(fileOps);
126
+ return `${counts} ${names.join(", ")}${more}`.trim();
127
+ }
128
+
129
+ function countsShort(ops: Map<string, FileOp>): string {
130
+ let c = 0,
131
+ e = 0,
132
+ d = 0,
133
+ m = 0;
134
+ for (const op of ops.values()) {
135
+ if (op === "created") c++;
136
+ else if (op === "edited") e++;
137
+ else if (op === "deleted") d++;
138
+ else if (op === "moved") m++;
139
+ }
140
+ const parts: string[] = [];
141
+ if (c) parts.push(`+${c}`);
142
+ if (e) parts.push(`~${e}`);
143
+ if (d) parts.push(`\u2212${d}`);
144
+ if (m) parts.push(`\u2192${m}`);
145
+ return parts.length ? parts.join("") : `${ops.size} files`;
146
+ }
147
+
148
+ /**
149
+ * Card summary for a finished turn: what was solved (assistant result) + files.
150
+ * Never uses a follow-up model call.
151
+ */
152
+ export function buildLastTurnSummary(opts: {
153
+ userText?: string;
154
+ assistantText?: string;
155
+ fileOps: Map<string, FileOp>;
156
+ stopReason?: string;
157
+ cancelled?: boolean;
158
+ error?: string;
159
+ max?: number;
160
+ }): string {
161
+ const max = opts.max ?? COMMENT_MAX;
162
+ if (opts.cancelled) return "Stopped by user";
163
+ if (opts.error) return cleanCommentLine(`Error: ${opts.error}`, max);
164
+
165
+ const result = extractResultSnippet(opts.assistantText, Math.min(160, max - 20));
166
+ const files = formatFilesPhrase(opts.fileOps);
167
+ const intent = cleanUserPreview(opts.userText || "", 55);
168
+
169
+ // Prefer outcome prose (what was solved) over the user's ask.
170
+ if (result && files) {
171
+ const combined = `${result} \u00B7 ${files}`;
172
+ return cleanCommentLine(combined, max);
173
+ }
174
+ if (result) return cleanCommentLine(result, max);
175
+ if (files && intent) return cleanCommentLine(`${intent} \u2192 ${files}`, max);
176
+ if (files) return cleanCommentLine(`Changed ${files}`, max);
177
+ if (intent) return cleanCommentLine(intent, max);
178
+
179
+ if (opts.stopReason && opts.stopReason !== "end_turn" && opts.stopReason !== "cancelled") {
180
+ return cleanCommentLine(`Done (${opts.stopReason})`, max);
181
+ }
182
+ return "Turn complete";
183
+ }
184
+
185
+ /** @deprecated Use {@link buildLastTurnSummary}. */
186
+ export function buildLocalTurnComment(opts: {
187
+ userText?: string;
188
+ assistantText?: string;
189
+ fileOps: Map<string, FileOp>;
190
+ stopReason?: string;
191
+ cancelled?: boolean;
192
+ error?: string;
193
+ }): string {
194
+ return buildLastTurnSummary(opts);
195
+ }
196
+
197
+ /** Derive a live "current step" line from an ACP tool update. */
198
+ export function stepFromToolUpdate(u: SessionUpdate): string | undefined {
199
+ const raw = { ...((u.rawInput || {}) as Record<string, unknown>) };
200
+ const id = resolveToolIdentity(u, raw);
201
+ const kind = id.kind;
202
+ const path = extractPath(raw);
203
+ const short = path ? basename(path.replace(/\\/g, "/")) : "";
204
+ const status = (u.status || "").toLowerCase();
205
+ const done = status === "completed" || status === "failed";
206
+ const fail = status === "failed" ? " failed" : done ? " done" : "";
207
+
208
+ switch (kind) {
209
+ case "execute": {
210
+ const cmd = extractCommand(raw);
211
+ if (cmd) return cleanCommentLine(`Run: ${cmd}${fail}`, COMMENT_MAX);
212
+ break;
213
+ }
214
+ case "edit":
215
+ return cleanCommentLine(`Edit ${short || path || "file"}${fail}`, COMMENT_MAX);
216
+ case "write":
217
+ case "create":
218
+ return cleanCommentLine(`${kind === "create" ? "Create" : "Write"} ${short || path || "file"}${fail}`, COMMENT_MAX);
219
+ case "read":
220
+ return cleanCommentLine(`Read ${short || path || id.toolName || "file"}${fail}`, COMMENT_MAX);
221
+ case "list":
222
+ return cleanCommentLine(`List ${short || path || "."}${fail}`, COMMENT_MAX);
223
+ case "search": {
224
+ const q = extractSearchQuery(raw);
225
+ return cleanCommentLine(`Search${q ? `: ${q}` : ""}${fail}`, COMMENT_MAX);
226
+ }
227
+ case "delete":
228
+ return cleanCommentLine(`Delete ${short || path || "file"}${fail}`, COMMENT_MAX);
229
+ case "move":
230
+ case "rename":
231
+ return cleanCommentLine(`${kind === "rename" ? "Rename" : "Move"} ${short || path || "file"}${fail}`, COMMENT_MAX);
232
+ case "fetch":
233
+ case "web_fetch": {
234
+ const url = extractUrl(raw);
235
+ return cleanCommentLine(`Fetch ${url || "URL"}${fail}`, COMMENT_MAX);
236
+ }
237
+ case "web_search": {
238
+ const q = extractSearchQuery(raw) || extractUrl(raw);
239
+ return cleanCommentLine(`Web search${q ? `: ${q}` : ""}${fail}`, COMMENT_MAX);
240
+ }
241
+ case "mcp":
242
+ return cleanCommentLine(
243
+ `MCP ${id.mcpServer ? id.mcpServer + ": " : ""}${id.mcpMethod || id.toolName}${fail}`,
244
+ COMMENT_MAX,
245
+ );
246
+ default:
247
+ break;
248
+ }
249
+ if (id.toolName) return cleanCommentLine(`${id.toolName}${fail}`, COMMENT_MAX);
250
+ if (u.title?.trim() && !/^other$/i.test(u.title.trim())) {
251
+ return cleanCommentLine(u.title.trim() + fail, COMMENT_MAX);
252
+ }
253
+ return undefined;
254
+ }
255
+
256
+ /** Thinking step line from a thought chunk (display only). */
257
+ export function stepFromThought(text: string): string {
258
+ const t = text.replace(/\s+/g, " ").trim();
259
+ if (!t) return "Thinking\u2026";
260
+ return cleanCommentLine(`Thinking: ${t}`, COMMENT_MAX);
261
+ }