grok-telegram-bot 2.4.0 → 2.5.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.
- package/.env.example +38 -2
- package/CHANGELOG.md +119 -1
- package/README.md +58 -15
- package/docs/GROUP.md +225 -0
- package/docs/INSTALL.md +3 -0
- package/package.json +1 -1
- package/src/app/lifetime-flag.ts +20 -0
- package/src/app/settings-store.ts +47 -8
- package/src/app/types.ts +12 -1
- package/src/app/updater.ts +24 -3
- package/src/bot/auth.ts +96 -15
- package/src/bot/bot.ts +122 -15
- package/src/bot/chat-controller.ts +52 -18
- package/src/bot/commands.ts +69 -29
- package/src/bot/deps.ts +3 -0
- package/src/bot/group-memory.ts +159 -0
- package/src/bot/handlers/accounts.ts +7 -0
- package/src/bot/handlers/control.ts +85 -32
- package/src/bot/handlers/document.ts +31 -4
- package/src/bot/handlers/forum.ts +207 -0
- package/src/bot/handlers/menu.ts +86 -24
- package/src/bot/handlers/message.ts +101 -21
- package/src/bot/handlers/photo.ts +123 -16
- package/src/bot/handlers/running.ts +150 -24
- package/src/bot/handlers/session-card.ts +13 -5
- package/src/bot/handlers/sessions.ts +68 -18
- package/src/bot/handlers/voice.ts +52 -7
- package/src/bot/image-return.ts +8 -5
- package/src/bot/menu/ephemeral.ts +13 -3
- package/src/bot/menu/keyboard.ts +53 -14
- package/src/bot/menu/refresh.ts +3 -1
- package/src/bot/menu/status-panel.ts +12 -6
- package/src/bot/permission-service.ts +19 -0
- package/src/bot/prompt-anchor.ts +300 -0
- package/src/bot/prompt-content.ts +3 -0
- package/src/bot/registry.ts +94 -1
- package/src/bot/scope.ts +94 -0
- package/src/bot/session-runtime.ts +647 -158
- package/src/bot/suggestions.ts +91 -31
- package/src/bot/telegram-actions.ts +440 -0
- package/src/bot/telegram-bots.ts +495 -0
- package/src/bot/telegram-io.ts +94 -10
- package/src/cli.ts +2 -0
- package/src/config.ts +201 -2
- package/src/forum/bind-path.ts +146 -0
- package/src/forum/manager.ts +651 -0
- package/src/forum/project-icon.ts +142 -0
- package/src/forum/thread.ts +16 -0
- package/src/forum/topic-store.ts +114 -0
- package/src/forum/types.ts +29 -0
- package/src/grok/client.ts +130 -28
- package/src/index.ts +205 -75
- package/src/projects/manager.ts +16 -3
- package/src/render/chunk.ts +17 -10
- package/src/render/hashtags.ts +5 -1
- package/src/render/session-comment.ts +64 -7
- package/src/render/telegram-bridge.ts +360 -0
- package/src/render/tool-call.ts +56 -37
- package/src/service/platform.ts +44 -7
- package/src/service/windows.ts +16 -4
- package/src/sessions/history.ts +50 -9
- package/src/sessions/process.ts +7 -0
- package/src/sessions/types.ts +2 -2
- package/src/stream/streamer.ts +17 -6
- package/scripts/analyze-jsonl.ts +0 -33
- package/scripts/delayed-restart.ps1 +0 -29
- package/scripts/probe-exit-response-shape.py +0 -77
- package/scripts/probe-plan-exit.py +0 -60
- package/scripts/probe-plan-exit2.py +0 -48
- package/scripts/probe-plan-fields.py +0 -41
- package/scripts/probe-plan-fields2.py +0 -58
- package/scripts/probe-plan-response-path.py +0 -48
- package/scripts/sample-claude-tooluse.ts +0 -21
- package/scripts/sample-kiro-events.ts +0 -31
- package/scripts/smoke-exit-plan.ts +0 -274
- package/scripts/smoke-exit-shapes.ts +0 -252
- package/scripts/smoke-import.mjs +0 -82
- package/scripts/smoke-import.ts +0 -73
package/src/bot/registry.ts
CHANGED
|
@@ -18,7 +18,7 @@ import type { AppConfig } from "../config.js";
|
|
|
18
18
|
import { subagentSummary } from "../render/subagent.js";
|
|
19
19
|
import type { SessionStore } from "../sessions/store.js";
|
|
20
20
|
import type { AccountRotator } from "./account-rotator.js";
|
|
21
|
-
import { ChatController } from "./chat-controller.js";
|
|
21
|
+
import { ChatController, type ChatBridgeServices } from "./chat-controller.js";
|
|
22
22
|
import type { SessionRuntime } from "./session-runtime.js";
|
|
23
23
|
|
|
24
24
|
export interface SessionDescription {
|
|
@@ -34,8 +34,14 @@ export interface SessionDescription {
|
|
|
34
34
|
|
|
35
35
|
export class RuntimeRegistry {
|
|
36
36
|
private readonly controllers = new Map<number, ChatController>();
|
|
37
|
+
/**
|
|
38
|
+
* Forum topic controllers keyed by `chatId:threadId`.
|
|
39
|
+
* Each topic has its own multi-session controller + settings (model/reasoning).
|
|
40
|
+
*/
|
|
41
|
+
private readonly forumControllers = new Map<string, ChatController>();
|
|
37
42
|
private refresher: ((chatId: number) => void) | undefined;
|
|
38
43
|
private rotator: AccountRotator | undefined;
|
|
44
|
+
private bridge: ChatBridgeServices | undefined;
|
|
39
45
|
/** Chat ids with a running turn, most-recently-started last. */
|
|
40
46
|
private readonly activeChats: number[] = [];
|
|
41
47
|
/** Subagent sessionId -> owner chat id. */
|
|
@@ -60,6 +66,13 @@ export class RuntimeRegistry {
|
|
|
60
66
|
this.rotator = rotator;
|
|
61
67
|
}
|
|
62
68
|
|
|
69
|
+
/** Telegram bridge services (forum, session store, sibling bots). */
|
|
70
|
+
setBridge(bridge: ChatBridgeServices): void {
|
|
71
|
+
this.bridge = bridge;
|
|
72
|
+
for (const c of this.controllers.values()) c.bridge = bridge;
|
|
73
|
+
for (const c of this.forumControllers.values()) c.bridge = bridge;
|
|
74
|
+
}
|
|
75
|
+
|
|
63
76
|
controller(chatId: number): ChatController {
|
|
64
77
|
let c = this.controllers.get(chatId);
|
|
65
78
|
if (!c) {
|
|
@@ -74,6 +87,7 @@ export class RuntimeRegistry {
|
|
|
74
87
|
(busy) => this.noteActivity(chatId, busy),
|
|
75
88
|
() => this.rotator,
|
|
76
89
|
);
|
|
90
|
+
c.bridge = this.bridge;
|
|
77
91
|
this.controllers.set(chatId, c);
|
|
78
92
|
}
|
|
79
93
|
return c;
|
|
@@ -84,9 +98,78 @@ export class RuntimeRegistry {
|
|
|
84
98
|
return this.controller(chatId).foreground();
|
|
85
99
|
}
|
|
86
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Multi-session controller for a forum topic (fixed project path).
|
|
103
|
+
* Settings key: `{chatId}:t{threadId}` — own model / reasoning / sessions.
|
|
104
|
+
*/
|
|
105
|
+
forumController(
|
|
106
|
+
chatId: number,
|
|
107
|
+
threadId: number,
|
|
108
|
+
cwd: string,
|
|
109
|
+
projectName?: string,
|
|
110
|
+
): ChatController {
|
|
111
|
+
const key = `${chatId}:${threadId}`;
|
|
112
|
+
let c = this.forumControllers.get(key);
|
|
113
|
+
if (c && c.fixedCwd && normPath(c.fixedCwd) !== normPath(cwd)) {
|
|
114
|
+
// Path re-bind — dispose and recreate.
|
|
115
|
+
c.dispose();
|
|
116
|
+
this.forumControllers.delete(key);
|
|
117
|
+
c = undefined;
|
|
118
|
+
}
|
|
119
|
+
if (!c) {
|
|
120
|
+
c = new ChatController(
|
|
121
|
+
this.api,
|
|
122
|
+
chatId,
|
|
123
|
+
this.acp,
|
|
124
|
+
this.cfg,
|
|
125
|
+
this.settings,
|
|
126
|
+
this.store,
|
|
127
|
+
(id) => this.refresher?.(id),
|
|
128
|
+
(busy) => this.noteActivity(chatId, busy),
|
|
129
|
+
() => this.rotator,
|
|
130
|
+
{
|
|
131
|
+
messageThreadId: threadId,
|
|
132
|
+
settingsKey: `${chatId}:t${threadId}`,
|
|
133
|
+
fixedCwd: cwd,
|
|
134
|
+
fixedProjectName: projectName,
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
c.bridge = this.bridge;
|
|
138
|
+
this.forumControllers.set(key, c);
|
|
139
|
+
}
|
|
140
|
+
return c;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Foreground runtime for a forum topic. Creates the topic controller lazily.
|
|
145
|
+
*/
|
|
146
|
+
getForumTopic(
|
|
147
|
+
chatId: number,
|
|
148
|
+
threadId: number,
|
|
149
|
+
cwd: string,
|
|
150
|
+
projectName?: string,
|
|
151
|
+
): SessionRuntime {
|
|
152
|
+
return this.forumController(chatId, threadId, cwd, projectName).foreground();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** All forum topic controllers (for bidirectional session listing). */
|
|
156
|
+
allForumControllers(): ChatController[] {
|
|
157
|
+
return [...this.forumControllers.values()];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Forum controller that currently owns a session id, if any. */
|
|
161
|
+
forumControllerForSession(sessionId: string): ChatController | undefined {
|
|
162
|
+
for (const c of this.forumControllers.values()) {
|
|
163
|
+
if (c.findBySession(sessionId)) return c;
|
|
164
|
+
}
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
|
|
87
168
|
disposeAll(): void {
|
|
88
169
|
for (const c of this.controllers.values()) c.dispose();
|
|
89
170
|
this.controllers.clear();
|
|
171
|
+
for (const c of this.forumControllers.values()) c.dispose();
|
|
172
|
+
this.forumControllers.clear();
|
|
90
173
|
}
|
|
91
174
|
|
|
92
175
|
/** Find the chat that currently controls a given session id. */
|
|
@@ -94,6 +177,12 @@ export class RuntimeRegistry {
|
|
|
94
177
|
for (const [chatId, c] of this.controllers) {
|
|
95
178
|
if (c.findBySession(sessionId)) return chatId;
|
|
96
179
|
}
|
|
180
|
+
for (const [key, c] of this.forumControllers) {
|
|
181
|
+
if (c.findBySession(sessionId)) {
|
|
182
|
+
const chatId = Number(key.split(":")[0]);
|
|
183
|
+
return Number.isFinite(chatId) ? chatId : undefined;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
97
186
|
return undefined;
|
|
98
187
|
}
|
|
99
188
|
|
|
@@ -184,3 +273,7 @@ export class RuntimeRegistry {
|
|
|
184
273
|
}
|
|
185
274
|
}
|
|
186
275
|
}
|
|
276
|
+
|
|
277
|
+
function normPath(p: string): string {
|
|
278
|
+
return p.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
279
|
+
}
|
package/src/bot/scope.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve private-chat vs forum-topic scope for handlers (menu, running, sessions).
|
|
3
|
+
* Forum topics use a dedicated ChatController with per-topic settings + sessions.
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from "grammy";
|
|
6
|
+
import type { BotDeps } from "./deps.js";
|
|
7
|
+
import type { ChatController } from "./chat-controller.js";
|
|
8
|
+
import type { SessionRuntime } from "./session-runtime.js";
|
|
9
|
+
import { forumThreadId } from "../forum/thread.js";
|
|
10
|
+
import { FORUM_GENERAL_THREAD_ID } from "../forum/thread.js";
|
|
11
|
+
|
|
12
|
+
export interface HandlerScope {
|
|
13
|
+
chatId: number;
|
|
14
|
+
/** Forum message_thread_id when in the configured topic group. */
|
|
15
|
+
threadId?: number;
|
|
16
|
+
isForum: boolean;
|
|
17
|
+
/** Settings key for this scope (chat or chat:t{thread}). */
|
|
18
|
+
settingsKey: string;
|
|
19
|
+
controller: ChatController;
|
|
20
|
+
/** Foreground runtime for this scope. */
|
|
21
|
+
rt: SessionRuntime;
|
|
22
|
+
/** Extra fields so replies land in the same topic. */
|
|
23
|
+
threadExtra: { message_thread_id?: number };
|
|
24
|
+
/** Project path when forum topic is bound. */
|
|
25
|
+
projectPath?: string;
|
|
26
|
+
projectName?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Build settings storage key. */
|
|
30
|
+
export function settingsKeyFor(chatId: number, threadId?: number): string {
|
|
31
|
+
if (threadId === undefined) return String(chatId);
|
|
32
|
+
return `${chatId}:t${threadId}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Extract thread id from message or callback message. */
|
|
36
|
+
export function threadIdFromContext(ctx: Context): number | undefined {
|
|
37
|
+
const msg = ctx.message ?? ctx.callbackQuery?.message;
|
|
38
|
+
if (!msg || !("message_thread_id" in msg)) return undefined;
|
|
39
|
+
const tid = (msg as { message_thread_id?: number }).message_thread_id;
|
|
40
|
+
return typeof tid === "number" ? tid : undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Resolve handler scope. For the forum group, ensures topic binding (General →
|
|
45
|
+
* workspace) and returns the topic's ChatController + foreground runtime.
|
|
46
|
+
*/
|
|
47
|
+
export function resolveScope(ctx: Context, deps: BotDeps): HandlerScope {
|
|
48
|
+
const chatId = ctx.chat!.id;
|
|
49
|
+
const rawThread = threadIdFromContext(ctx);
|
|
50
|
+
const isForum = Boolean(deps.forum?.isActiveForumChat(chatId));
|
|
51
|
+
const threadExtra =
|
|
52
|
+
isForum || rawThread !== undefined
|
|
53
|
+
? { message_thread_id: isForum ? forumThreadId(rawThread) : rawThread }
|
|
54
|
+
: {};
|
|
55
|
+
|
|
56
|
+
if (!isForum || !deps.forum) {
|
|
57
|
+
const controller = deps.registry.controller(chatId);
|
|
58
|
+
return {
|
|
59
|
+
chatId,
|
|
60
|
+
isForum: false,
|
|
61
|
+
settingsKey: settingsKeyFor(chatId),
|
|
62
|
+
controller,
|
|
63
|
+
rt: controller.foreground(),
|
|
64
|
+
threadExtra: rawThread !== undefined ? { message_thread_id: rawThread } : {},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const tid = forumThreadId(rawThread);
|
|
69
|
+
// Ensure General / AI paths are bound before opening menus.
|
|
70
|
+
if (tid === FORUM_GENERAL_THREAD_ID) {
|
|
71
|
+
if (!deps.forum.store.get(tid)?.projectPath) {
|
|
72
|
+
deps.forum.store.bindProject(tid, deps.cfg.workspace, "General", "general");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const resolved = deps.forum.resolveCwd(tid);
|
|
76
|
+
const cwd = resolved?.cwd ?? deps.cfg.workspace;
|
|
77
|
+
const projectName = resolved?.projectName ?? (tid === FORUM_GENERAL_THREAD_ID ? "General" : `Topic ${tid}`);
|
|
78
|
+
if (!resolved && tid !== FORUM_GENERAL_THREAD_ID) {
|
|
79
|
+
// Unbound topic — still allow menu with workspace fallback for prefs; path
|
|
80
|
+
// bind happens on next text message via resolveForumRuntime.
|
|
81
|
+
}
|
|
82
|
+
const controller = deps.registry.forumController(chatId, tid, cwd, projectName);
|
|
83
|
+
return {
|
|
84
|
+
chatId,
|
|
85
|
+
threadId: tid,
|
|
86
|
+
isForum: true,
|
|
87
|
+
settingsKey: settingsKeyFor(chatId, tid),
|
|
88
|
+
controller,
|
|
89
|
+
rt: controller.foreground(),
|
|
90
|
+
threadExtra: { message_thread_id: tid },
|
|
91
|
+
projectPath: cwd,
|
|
92
|
+
projectName,
|
|
93
|
+
};
|
|
94
|
+
}
|