grok-telegram-bot 2.4.0 → 2.6.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 (81) hide show
  1. package/.env.example +38 -2
  2. package/CHANGELOG.md +190 -1
  3. package/README.md +60 -15
  4. package/docs/GROUP.md +260 -0
  5. package/docs/INSTALL.md +3 -0
  6. package/package.json +4 -4
  7. package/src/app/lifetime-flag.ts +20 -0
  8. package/src/app/settings-store.ts +47 -8
  9. package/src/app/types.ts +38 -1
  10. package/src/app/updater.ts +24 -3
  11. package/src/bot/auth.ts +100 -15
  12. package/src/bot/bot.ts +193 -17
  13. package/src/bot/chat-controller.ts +181 -18
  14. package/src/bot/commands.ts +69 -29
  15. package/src/bot/deps.ts +3 -0
  16. package/src/bot/group-memory.ts +339 -0
  17. package/src/bot/handlers/accounts.ts +7 -0
  18. package/src/bot/handlers/control.ts +85 -32
  19. package/src/bot/handlers/document.ts +31 -4
  20. package/src/bot/handlers/forum.ts +217 -0
  21. package/src/bot/handlers/menu.ts +86 -24
  22. package/src/bot/handlers/message.ts +247 -27
  23. package/src/bot/handlers/photo.ts +126 -16
  24. package/src/bot/handlers/running.ts +150 -24
  25. package/src/bot/handlers/session-card.ts +13 -5
  26. package/src/bot/handlers/sessions.ts +68 -18
  27. package/src/bot/handlers/voice.ts +52 -7
  28. package/src/bot/image-return.ts +11 -5
  29. package/src/bot/manager-context.ts +208 -0
  30. package/src/bot/manager-jobs.ts +142 -0
  31. package/src/bot/menu/ephemeral.ts +16 -3
  32. package/src/bot/menu/keyboard.ts +53 -14
  33. package/src/bot/menu/refresh.ts +3 -1
  34. package/src/bot/menu/status-panel.ts +12 -6
  35. package/src/bot/permission-service.ts +19 -0
  36. package/src/bot/prompt-anchor.ts +299 -0
  37. package/src/bot/prompt-content.ts +8 -0
  38. package/src/bot/registry.ts +94 -1
  39. package/src/bot/scope.ts +95 -0
  40. package/src/bot/session-runtime.ts +1280 -183
  41. package/src/bot/suggestions.ts +91 -31
  42. package/src/bot/telegram-actions.ts +1130 -0
  43. package/src/bot/telegram-bots.ts +496 -0
  44. package/src/bot/telegram-io.ts +97 -10
  45. package/src/cli.ts +2 -0
  46. package/src/config.ts +201 -2
  47. package/src/forum/bind-path.ts +146 -0
  48. package/src/forum/manager.ts +652 -0
  49. package/src/forum/project-icon.ts +142 -0
  50. package/src/forum/thread.ts +49 -0
  51. package/src/forum/topic-store.ts +114 -0
  52. package/src/forum/types.ts +29 -0
  53. package/src/grok/client.ts +130 -28
  54. package/src/index.ts +205 -75
  55. package/src/projects/manager.ts +16 -3
  56. package/src/render/chunk.ts +17 -10
  57. package/src/render/hashtags.ts +5 -1
  58. package/src/render/manager-directive.ts +137 -0
  59. package/src/render/session-comment.ts +74 -7
  60. package/src/render/telegram-bridge.ts +464 -0
  61. package/src/render/tool-call.ts +56 -37
  62. package/src/service/platform.ts +44 -7
  63. package/src/service/windows.ts +16 -4
  64. package/src/sessions/history.ts +68 -9
  65. package/src/sessions/process.ts +7 -0
  66. package/src/sessions/types.ts +2 -2
  67. package/src/stream/streamer.ts +62 -15
  68. package/scripts/analyze-jsonl.ts +0 -33
  69. package/scripts/delayed-restart.ps1 +0 -29
  70. package/scripts/probe-exit-response-shape.py +0 -77
  71. package/scripts/probe-plan-exit.py +0 -60
  72. package/scripts/probe-plan-exit2.py +0 -48
  73. package/scripts/probe-plan-fields.py +0 -41
  74. package/scripts/probe-plan-fields2.py +0 -58
  75. package/scripts/probe-plan-response-path.py +0 -48
  76. package/scripts/sample-claude-tooluse.ts +0 -21
  77. package/scripts/sample-kiro-events.ts +0 -31
  78. package/scripts/smoke-exit-plan.ts +0 -274
  79. package/scripts/smoke-exit-shapes.ts +0 -252
  80. package/scripts/smoke-import.mjs +0 -82
  81. package/scripts/smoke-import.ts +0 -73
@@ -1,33 +0,0 @@
1
- import { createReadStream } from "node:fs";
2
- import { createInterface } from "node:readline";
3
-
4
- const path = process.argv[2];
5
- if (!path) {
6
- console.error("usage: analyze-jsonl <path>");
7
- process.exit(1);
8
- }
9
-
10
- const kinds = new Map<string, number>();
11
- let lines = 0;
12
- let bad = 0;
13
-
14
- const rl = createInterface({ input: createReadStream(path, { encoding: "utf8" }) });
15
- for await (const line of rl) {
16
- if (!line.trim()) continue;
17
- lines++;
18
- try {
19
- const o = JSON.parse(line) as { kind?: string; type?: string };
20
- const k = o.kind || o.type || "(none)";
21
- kinds.set(k, (kinds.get(k) || 0) + 1);
22
- } catch {
23
- bad++;
24
- }
25
- }
26
-
27
- console.log(JSON.stringify({ path, lines, bad }, null, 2));
28
- console.log(
29
- [...kinds.entries()]
30
- .sort((a, b) => b[1] - a[1])
31
- .map(([k, n]) => `${n}\t${k}`)
32
- .join("\n"),
33
- );
@@ -1,29 +0,0 @@
1
- Set-Location "H:\Lucru\Domains\grok-telegram-bot"
2
- $log = "H:\Lucru\Domains\grok-telegram-bot\logs\restart-verify.log"
3
- function W($m) { $line = "$(Get-Date -Format o) $m"; Add-Content -Path $log -Value $line; Write-Output $line }
4
-
5
- W "waiting 20s for idle/flush..."
6
- Start-Sleep -Seconds 20
7
-
8
- $old = Get-CimInstance Win32_Process -Filter "name='node.exe'" | Where-Object { $_.CommandLine -match 'grok-telegram-bot\\src\\index' } | Select-Object -ExpandProperty ProcessId
9
- W "old pid(s): $($old -join ',')"
10
-
11
- W "running: npx tsx src/cli.ts restart"
12
- $out = npx tsx src/cli.ts restart 2>&1 | Out-String
13
- W $out
14
-
15
- Start-Sleep -Seconds 8
16
- $new = Get-CimInstance Win32_Process -Filter "name='node.exe'" | Where-Object { $_.CommandLine -match 'grok-telegram-bot\\src\\index' } | Select-Object ProcessId,CreationDate,CommandLine
17
- W "new process:"
18
- W ($new | Format-List | Out-String)
19
-
20
- # Confirm loaded code path mentions outcome approved by checking process start after our file mtime
21
- $fixMtime = (Get-Item "H:\Lucru\Domains\grok-telegram-bot\src\grok\plan-approval.ts").LastWriteTime
22
- W "plan-approval.ts mtime: $fixMtime"
23
- if ($new) {
24
- W "status:"
25
- W (npx tsx src/cli.ts status 2>&1 | Out-String)
26
- W "RESTART_OK"
27
- } else {
28
- W "RESTART_FAILED no process"
29
- }
@@ -1,77 +0,0 @@
1
- """Deep probe of grok.exe for ExitPlanMode reverse-request/response shapes."""
2
- from __future__ import annotations
3
-
4
- import re
5
- from pathlib import Path
6
-
7
- data = Path(r"C:\Users\artic\.grok\bin\grok.exe").read_bytes()
8
-
9
-
10
- def nearby(label: bytes, n: int = 3, before: int = 120, after: int = 300) -> None:
11
- start = 0
12
- for i in range(n):
13
- j = data.find(label, start)
14
- if j < 0:
15
- print(f"NOT FOUND: {label!r}")
16
- return
17
- chunk = data[max(0, j - before) : j + after]
18
- text = "".join(chr(b) if 32 <= b < 127 else "|" for b in chunk)
19
- print(f"\n=== {label!r} @{j} ===\n{text}")
20
- start = j + len(label)
21
-
22
-
23
- # Key messages around reverse-request
24
- for lab in [
25
- b"sending ext_method to client",
26
- b"client disconnected mid-approval",
27
- b"no client wired",
28
- b"ExitPlanModeExtRequest",
29
- b"ExitPlanModeExtResponse",
30
- b"Failed to parse ExitPlanMode",
31
- b"x.ai/exit_plan_mode",
32
- b"agent.ext_method",
33
- b"ext_method",
34
- b"session/request_permission",
35
- ]:
36
- nearby(lab, n=2)
37
-
38
- print("\n\n==== null-terminated strings containing ExitPlan or approved near ExtResponse ====")
39
- idx = data.find(b"ExitPlanModeExtResponse serialization")
40
- region = data[max(0, idx - 4000) : idx + 2000]
41
- cur = bytearray()
42
- strs: list[str] = []
43
- for b in region:
44
- if 32 <= b < 127:
45
- cur.append(b)
46
- else:
47
- if 3 <= len(cur) <= 60:
48
- s = cur.decode()
49
- if any(
50
- k in s.lower()
51
- for k in (
52
- "approv",
53
- "abandon",
54
- "feedback",
55
- "decision",
56
- "action",
57
- "outcome",
58
- "plan",
59
- "comment",
60
- "exit",
61
- "request",
62
- "session",
63
- "content",
64
- )
65
- ):
66
- strs.append(s)
67
- cur.clear()
68
- for s in dict.fromkeys(strs):
69
- print(repr(s))
70
-
71
- print("\n\n==== method name candidates ====")
72
- for m in sorted(set(re.findall(rb"x\.ai/[a-zA-Z0-9_/\-]{3,50}", data))):
73
- s = m.decode("ascii", "ignore")
74
- if any(k in s.lower() for k in ("plan", "exit", "ask", "approv", "question")):
75
- # only print clean-looking
76
- if re.fullmatch(r"x\.ai/[a-z0-9_/\-]+", s):
77
- print(s)
@@ -1,60 +0,0 @@
1
- """Probe grok.exe for ExitPlanMode ACP extension request/response shape."""
2
- from __future__ import annotations
3
-
4
- import re
5
- from pathlib import Path
6
-
7
- path = Path(r"C:\Users\artic\.grok\bin\grok.exe")
8
- data = path.read_bytes()
9
-
10
-
11
- def ctx(idx: int, before: int = 80, after: int = 200) -> str:
12
- chunk = data[max(0, idx - before) : idx + after]
13
- return "".join(chr(b) if 32 <= b < 127 else "|" for b in chunk)
14
-
15
-
16
- needles = [
17
- b"ExitPlanModeExtRequest",
18
- b"ExitPlanModeExtResponse",
19
- b"Failed to parse ExitPlanModeExtRequest",
20
- b"x.ai/exit_plan_mode",
21
- b"[exit_plan_mode] user approved",
22
- b"[exit_plan_mode] user abandoned",
23
- b"request_changes",
24
- b"requestChanges",
25
- b"additionalFeedback",
26
- b"additional_feedback",
27
- b"planContent",
28
- b"plan_content",
29
- b"emptyPlan",
30
- b"EmptyPlan",
31
- b"PlanReady",
32
- ]
33
-
34
- for n in needles:
35
- i = 0
36
- c = 0
37
- while c < 3:
38
- j = data.find(n, i)
39
- if j < 0:
40
- break
41
- print(f"\n=== {n!r} @ {j} ===")
42
- print(ctx(j, 40, 160))
43
- i = j + len(n)
44
- c += 1
45
-
46
- print("\n=== combos approved/abandoned ===")
47
- for m in re.finditer(rb"approved.{0,30}abandoned|abandoned.{0,30}approved", data, re.I):
48
- print(ctx(m.start(), 10, 80))
49
-
50
- print("\n=== identifiers near first ExtResponse ===")
51
- idx = data.find(b"ExitPlanModeExtResponse")
52
- if idx >= 0:
53
- region = data[idx : idx + 1200]
54
- print(re.findall(rb"[A-Za-z_][A-Za-z0-9_]{2,40}", region)[:60])
55
-
56
- print("\n=== identifiers near struct ExitPlanModeExtRequest with 3 ===")
57
- idx = data.find(b"struct ExitPlanModeExtRequest with 3 elements")
58
- if idx >= 0:
59
- region = data[idx - 400 : idx + 400]
60
- print([s.decode() for s in re.findall(rb"[A-Za-z_][A-Za-z0-9_]{2,40}", region)])
@@ -1,48 +0,0 @@
1
- from pathlib import Path
2
- import re
3
-
4
- data = Path(r"C:\Users\artic\.grok\bin\grok.exe").read_bytes()
5
-
6
- # Pull null-terminated strings of length 3-40 around key anchors
7
- anchors = [
8
- data.find(b"ExitPlanModeExtRequest serialization"),
9
- data.find(b"ExitPlanModeExtResponse serialization"),
10
- data.find(b"struct ExitPlanModeExtRequest with 3"),
11
- data.find(b"struct ExitPlanModeExtResponse with 2"),
12
- data.find(b"approvedabandonedAdditional feedback"),
13
- data.find(b"[exit_plan_mode] intercepted"),
14
- data.find(b"Plan approval requested"),
15
- ]
16
-
17
- for a in anchors:
18
- if a < 0:
19
- continue
20
- region = data[max(0, a - 800) : a + 800]
21
- strs = []
22
- cur = bytearray()
23
- for b in region:
24
- if 32 <= b < 127:
25
- cur.append(b)
26
- else:
27
- if 3 <= len(cur) <= 48:
28
- strs.append(cur.decode("ascii"))
29
- cur.clear()
30
- if 3 <= len(cur) <= 48:
31
- strs.append(cur.decode("ascii"))
32
- # unique preserve order
33
- seen = set()
34
- out = []
35
- for s in strs:
36
- if s not in seen:
37
- seen.add(s)
38
- out.append(s)
39
- print("\n==== ANCHOR", a, "====")
40
- for s in out:
41
- print(s)
42
-
43
- # Also list method names that look like reverse requests
44
- print("\n==== x.ai methods clean ====")
45
- for m in sorted(set(re.findall(rb"x\.ai/[a-z0-9_/\-]{3,60}", data))):
46
- s = m.decode()
47
- if any(k in s for k in ["plan", "ask", "question", "mode", "approv"]):
48
- print(s)
@@ -1,41 +0,0 @@
1
- from pathlib import Path
2
- import re
3
-
4
- data = Path(r"C:\Users\artic\.grok\bin\grok.exe").read_bytes()
5
-
6
- for n in [
7
- b"additional_feedback",
8
- b"additionalFeedback",
9
- b"AdditionalFeedback",
10
- b"review_comments",
11
- b"reviewComments",
12
- ]:
13
- print(n, "count", data.count(n), "at", data.find(n))
14
-
15
- # Pull short identifiers within 2KB of ExitPlanModeExtResponse serialization
16
- idx = data.find(b"ExitPlanModeExtResponse serialization")
17
- region = data[idx - 2000 : idx + 500]
18
- idents = re.findall(rb"[A-Za-z_][A-Za-z0-9_]{2,40}", region)
19
- # filter boring
20
- skip = {"struct", "with", "elements", "serialization", "should", "not", "fail", "crates", "codegen"}
21
- seen = []
22
- for s in idents:
23
- t = s.decode()
24
- if t in skip or t in seen:
25
- continue
26
- seen.append(t)
27
- print("idents near serialization:", seen[:80])
28
-
29
- # Look for schemars / json schema around ExitPlanModeExt
30
- for pat in [rb'"decision"', rb'"action"', rb'"outcome"', rb'"approved"', rb'"abandoned"']:
31
- c = 0
32
- start = 0
33
- while c < 5:
34
- j = data.find(pat, start)
35
- if j < 0:
36
- break
37
- window = data[j : j + 80]
38
- if b"plan" in window.lower() or b"approv" in window or b"abandon" in window:
39
- print(pat, j, window[:80])
40
- c += 1
41
- start = j + 1
@@ -1,58 +0,0 @@
1
- from pathlib import Path
2
- import re
3
-
4
- data = Path(r"C:\Users\artic\.grok\bin\grok.exe").read_bytes()
5
-
6
- # Find null-separated sequences containing approved and abandoned as siblings
7
- needle = b"approved\x00abandoned"
8
- idx = data.find(needle)
9
- print("approved\\0abandoned", idx)
10
- if idx >= 0:
11
- print(data[idx - 40 : idx + 80])
12
-
13
- needle2 = b"abandoned\x00approved"
14
- print("abandoned\\0approved", data.find(needle2))
15
-
16
- # Also try without null (concatenated variants for Display)
17
- print("concat", data.find(b"approvedabandoned"))
18
-
19
- # Search for field name candidates near concat
20
- idx = data.find(b"approvedabandoned")
21
- region = data[idx - 100 : idx + 120]
22
- # extract C strings
23
- cur = bytearray()
24
- strs = []
25
- for b in region:
26
- if 32 <= b < 127:
27
- cur.append(b)
28
- else:
29
- if cur:
30
- strs.append(cur.decode())
31
- cur.clear()
32
- print("near concat:", strs)
33
-
34
- # Look at ExitPlanModeExtRequest 3 elements - field names for request
35
- for label in [b"plan_content", b"plan_file_path", b"session_id", b"sessionId", b"tool_call_id", b"toolCallId", b"empty"]:
36
- print(label, data.count(label))
37
-
38
- # Find bytes: decision\0 or action\0 near approved
39
- for field in [b"decision\x00", b"action\x00", b"outcome\x00", b"result\x00", b"choice\x00", b"feedback\x00", b"comments\x00", b"message\x00"]:
40
- start = 0
41
- hits = 0
42
- while hits < 3:
43
- j = data.find(field, start)
44
- if j < 0:
45
- break
46
- win = data[j : j + 60]
47
- if b"approv" in win or b"abandon" in win or b"plan" in win.lower():
48
- print("FIELD", field, j, win)
49
- hits += 1
50
- start = j + 1
51
-
52
- # Extract all strings of form [a-z_]+ that appear within 200 bytes after "ExitPlanModeExtResponse with 2"
53
- idx = data.find(b"struct ExitPlanModeExtResponse with 2 elements")
54
- print("struct at", idx)
55
- if idx >= 0:
56
- region = data[idx : idx + 300]
57
- print(region)
58
- print(re.findall(rb"[a-z_]{3,30}", region))
@@ -1,48 +0,0 @@
1
- from pathlib import Path
2
-
3
- data = Path(r"C:\Users\artic\.grok\bin\grok.exe").read_bytes()
4
-
5
-
6
- def show(label: bytes, n: int = 3, before: int = 100, after: int = 250) -> None:
7
- start = 0
8
- for _ in range(n):
9
- j = data.find(label, start)
10
- if j < 0:
11
- print(label, "not found")
12
- return
13
- chunk = data[max(0, j - before) : j + after]
14
- text = "".join(chr(b) if 32 <= b < 127 else "|" for b in chunk)
15
- print(f"\n=== {label!r} @ {j} ===")
16
- print(text)
17
- start = j + len(label)
18
-
19
-
20
- for lab in [
21
- b"[exit_plan_mode] user approved",
22
- b"[exit_plan_mode] user abandoned",
23
- b"Opened plan approval view from ext_method",
24
- b"Failed to parse ExitPlanModeExtResponse",
25
- b"ExitPlanModeExtResponse serialization should not fail",
26
- b"resume exit_plan_mode reverse-request failed",
27
- b"Plan approval requested",
28
- ]:
29
- show(lab, n=2)
30
-
31
- # Print unique printable strings of length 4-24 in a 1KB window after
32
- # "Opened plan approval view from ext_method"
33
- idx = data.find(b"Opened plan approval view from ext_method")
34
- region = data[idx : idx + 1500]
35
- cur = bytearray()
36
- seen = []
37
- for b in region:
38
- if 32 <= b < 127:
39
- cur.append(b)
40
- else:
41
- if 4 <= len(cur) <= 32:
42
- s = cur.decode()
43
- if s not in seen:
44
- seen.append(s)
45
- cur.clear()
46
- print("\nstrings after open plan approval:")
47
- for s in seen:
48
- print(" ", s)
@@ -1,21 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
-
3
- const path =
4
- "H:\\Lucru\\Domains\\claude-telegram-bot\\data\\sessions\\019f47bc-67bc-7043-a40a-f487a1d5af8b.jsonl";
5
- const lines = readFileSync(path, "utf8").split(/\n/).filter(Boolean);
6
-
7
- for (const line of lines) {
8
- const o = JSON.parse(line) as { kind?: string; data?: Record<string, unknown> };
9
- if (o.kind === "ToolUse") {
10
- console.log("keys", Object.keys(o.data || {}));
11
- console.log(JSON.stringify(o).slice(0, 1500));
12
- break;
13
- }
14
- }
15
- for (const line of lines) {
16
- const o = JSON.parse(line) as { kind?: string };
17
- if (o.kind === "AssistantMessage") {
18
- console.log("ASSIST", JSON.stringify(o).slice(0, 800));
19
- break;
20
- }
21
- }
@@ -1,31 +0,0 @@
1
- import { readFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
-
5
- const path = join(homedir(), ".kiro", "sessions", "cli", "8d37ff05-4658-4e7c-be14-3693e1c82d00.jsonl");
6
- const lines = readFileSync(path, "utf8").split(/\n/).filter(Boolean);
7
-
8
- for (const line of lines) {
9
- const o = JSON.parse(line) as { kind?: string; data?: { content?: unknown; results?: unknown } };
10
- if (o.kind === "ToolResults") {
11
- console.log("TOOLRESULTS data keys", o.data ? Object.keys(o.data) : []);
12
- console.log(JSON.stringify(o.data).slice(0, 1200));
13
- break;
14
- }
15
- }
16
- for (const line of lines) {
17
- const o = JSON.parse(line) as { kind?: string; data?: { content?: unknown } };
18
- if (o.kind === "AssistantMessage" && JSON.stringify(o).includes("toolUse")) {
19
- console.log("ASSIST toolUse content");
20
- console.log(JSON.stringify(o.data?.content).slice(0, 1200));
21
- break;
22
- }
23
- }
24
- for (const line of lines) {
25
- const o = JSON.parse(line) as { kind?: string; data?: { content?: unknown } };
26
- if (o.kind === "AssistantMessage" && JSON.stringify(o).includes("thinking")) {
27
- console.log("ASSIST thinking content");
28
- console.log(JSON.stringify(o.data?.content).slice(0, 800));
29
- break;
30
- }
31
- }