arisa 5.1.24 → 5.1.49
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/package.json +2 -3
- package/src/core/agent/agent-manager.js +132 -70
- package/src/core/agent/pi-runtime.js +0 -8
- package/src/core/agent/system-shell-tool.js +13 -2
- package/src/core/artifacts/artifact-store.js +17 -18
- package/src/core/config/config-defaults.js +2 -2
- package/src/core/conversation/session-seed-store.js +85 -0
- package/src/core/tools/ipc-client.js +0 -2
- package/src/core/tools/tool-output-materializer.js +41 -0
- package/src/core/tools/tool-registry.js +96 -23
- package/src/official-tools.lock.json +145 -93
- package/src/runtime/doctor.js +1 -4
- package/src/runtime/headless-tool-executor.js +2 -32
- package/src/runtime/paths.js +7 -1
- package/src/runtime/restart-receipt.js +90 -0
- package/src/transport/telegram/bot.js +390 -1034
- package/src/transport/telegram/chat-queue.js +132 -0
- package/src/transport/telegram/media.js +2 -2
- package/src/transport/telegram/model-callback.js +211 -0
- package/src/transport/telegram/model-controls.js +164 -0
- package/src/transport/telegram/prompt-builders.js +372 -0
- package/src/transport/telegram/task-dispatcher.js +94 -0
- package/src/transport/telegram/update-command.js +1 -1
- package/src/transport/telegram/workspace-group.js +83 -0
- package/test/agent-tool-policy.test.js +7 -1
- package/test/context-and-task-bounds.test.js +9 -7
- package/test/doctor.test.js +2 -4
- package/test/model-selection.test.js +47 -1
- package/test/official-tool-dependencies.test.js +2 -0
- package/test/paths.test.js +4 -4
- package/test/restart-receipt.test.js +39 -0
- package/test/session-start-operational-notes.test.js +47 -0
- package/test/telegram-prompt-builders.test.js +33 -0
- package/test/telegram-task-dispatcher.test.js +102 -0
- package/test/telegram-workspace-group.test.js +76 -0
- package/test/tool-registry-run.test.js +62 -0
- package/test/topic-initialization.test.js +66 -0
- package/src/core/conversation/conversation-history-store.js +0 -142
|
@@ -1,25 +1,86 @@
|
|
|
1
1
|
import { Bot, InputFile } from "grammy";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { authorizeChat } from "./auth.js";
|
|
4
|
-
import { captureIncomingArtifact
|
|
4
|
+
import { captureIncomingArtifact } from "./media.js";
|
|
5
5
|
import { buildDeviceCodeTelegramMessage } from "./device-code-message.js";
|
|
6
|
-
import { buildEffortPicker, buildModelPicker, buildSpeedPicker, parseEffortPickerAction, parseModelPickerAction, parseSpeedPickerAction, reverseModelOrder } from "./model-picker.js";
|
|
7
6
|
import { renderTelegramHtml } from "./text-format.js";
|
|
8
7
|
import { buildPiAuthRecoveryBlockedMessage, buildPiAuthTelegramMessage, getErrorMessage, getPiAuthIssue, getPiAuthStatus } from "../../core/agent/auth-flow.js";
|
|
9
8
|
import { createPiOAuthLogin } from "../../core/agent/pi-auth-login.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { clampModelSpeed, MODEL_SPEEDS, modelSupportsSpeed } from "../../core/agent/model-speed.js";
|
|
13
|
-
import { normalizeArtifactForReasoning, shouldNormalizeArtifactToText } from "../../core/artifacts/normalize-for-reasoning.js";
|
|
14
|
-
import { formatPortableSessionHistory } from "../../core/agent/agent-manager.js";
|
|
15
|
-
import { ConversationHistoryStore } from "../../core/conversation/conversation-history-store.js";
|
|
9
|
+
import { resolveChatSpeed } from "../../core/agent/model-selection.js";
|
|
10
|
+
import { SessionSeedStore } from "../../core/conversation/session-seed-store.js";
|
|
16
11
|
import { formatDoctorReport } from "../../runtime/doctor.js";
|
|
17
12
|
import { formatToolUsageReport } from "../../runtime/tool-usage-report.js";
|
|
18
13
|
import { ToolResourceNoteStore } from "../../core/tools/tool-resource-note-store.js";
|
|
19
14
|
import { formatUpdateReport } from "../../runtime/update-manager.js";
|
|
15
|
+
import { cancelRestartReceipt, deliverRestartReceipt, prepareRestartReceipt } from "../../runtime/restart-receipt.js";
|
|
20
16
|
import { buildUpdatePicker, createTelegramUpdateCallbackHandler } from "./update-command.js";
|
|
17
|
+
import { createTelegramModelControls } from "./model-controls.js";
|
|
18
|
+
import { createTelegramModelCallbackHandler } from "./model-callback.js";
|
|
19
|
+
import { createTelegramTaskDispatcher } from "./task-dispatcher.js";
|
|
20
|
+
import { resolveTelegramWorkspaceRoute, topicSessionId } from "./workspace-group.js";
|
|
21
|
+
import {
|
|
22
|
+
buildNewSessionPrompt,
|
|
23
|
+
buildPrompt,
|
|
24
|
+
buildReactionPrompt,
|
|
25
|
+
buildSessionHandoffPrompt,
|
|
26
|
+
buildStartupMessage,
|
|
27
|
+
collectText,
|
|
28
|
+
getIncomingMessageText,
|
|
29
|
+
isScheduledTaskPrompt,
|
|
30
|
+
isSilentReply,
|
|
31
|
+
normalizeIncomingArtifact,
|
|
32
|
+
sanitizeSessionHandoff,
|
|
33
|
+
shouldIncludeArtifactReference,
|
|
34
|
+
withPromptSpeed
|
|
35
|
+
} from "./prompt-builders.js";
|
|
36
|
+
import {
|
|
37
|
+
createChatStateStore,
|
|
38
|
+
drainChatPromptQueue,
|
|
39
|
+
queueChatPrompt,
|
|
40
|
+
resolveTelegramBusyMessageMode,
|
|
41
|
+
routeBusyPrompt
|
|
42
|
+
} from "./chat-queue.js";
|
|
43
|
+
|
|
44
|
+
export {
|
|
45
|
+
createChatStateStore,
|
|
46
|
+
drainChatPromptQueue,
|
|
47
|
+
queueChatPrompt,
|
|
48
|
+
resolveTelegramBusyMessageMode,
|
|
49
|
+
routeBusyPrompt
|
|
50
|
+
} from "./chat-queue.js";
|
|
51
|
+
|
|
52
|
+
export {
|
|
53
|
+
buildAsyncTaskPrompt,
|
|
54
|
+
buildPrompt,
|
|
55
|
+
buildReactionPrompt,
|
|
56
|
+
collectText,
|
|
57
|
+
isScheduledTaskPrompt,
|
|
58
|
+
isSilentReply,
|
|
59
|
+
shouldIncludeArtifactReference,
|
|
60
|
+
withPromptSpeed
|
|
61
|
+
} from "./prompt-builders.js";
|
|
62
|
+
|
|
63
|
+
export function isProcessableTelegramMessage(message = {}) {
|
|
64
|
+
return Boolean(
|
|
65
|
+
String(message.text || "").trim()
|
|
66
|
+
|| message.voice
|
|
67
|
+
|| message.audio
|
|
68
|
+
|| message.video
|
|
69
|
+
|| message.document
|
|
70
|
+
|| message.photo?.length
|
|
71
|
+
|| message.location
|
|
72
|
+
|| message.venue
|
|
73
|
+
);
|
|
74
|
+
}
|
|
21
75
|
|
|
22
|
-
|
|
76
|
+
export function buildTopicInitializationHandoff({ name, context }) {
|
|
77
|
+
return [
|
|
78
|
+
`Telegram topic: ${String(name || "").trim()}`,
|
|
79
|
+
"This topic has an isolated conversation session.",
|
|
80
|
+
"Use the following as background context. Do not repeat it unless the user asks.",
|
|
81
|
+
String(context || "").trim()
|
|
82
|
+
].filter(Boolean).join("\n\n");
|
|
83
|
+
}
|
|
23
84
|
|
|
24
85
|
export const telegramCommands = Object.freeze([
|
|
25
86
|
{ command: "new", description: "New chat context" },
|
|
@@ -51,7 +112,7 @@ export function createTelegramRestartHandler({ authorize, requestRestart, logger
|
|
|
51
112
|
restartRequested = true;
|
|
52
113
|
try {
|
|
53
114
|
await ctx.reply("Arisa is restarting. I'll be back shortly.");
|
|
54
|
-
const handoff = await requestRestart();
|
|
115
|
+
const handoff = await requestRestart(ctx);
|
|
55
116
|
logger?.log("telegram", `restart handed off to process ${handoff.pid}`);
|
|
56
117
|
} catch (error) {
|
|
57
118
|
restartRequested = false;
|
|
@@ -61,35 +122,6 @@ export function createTelegramRestartHandler({ authorize, requestRestart, logger
|
|
|
61
122
|
};
|
|
62
123
|
}
|
|
63
124
|
|
|
64
|
-
function quotedMessageSummary(message) {
|
|
65
|
-
if (!message) return [];
|
|
66
|
-
|
|
67
|
-
const fromName = message.from?.username
|
|
68
|
-
? `@${message.from.username}`
|
|
69
|
-
: [message.from?.first_name, message.from?.last_name].filter(Boolean).join(" ") || "unknown";
|
|
70
|
-
|
|
71
|
-
const parts = [
|
|
72
|
-
`quotedMessageId: ${message.message_id}`,
|
|
73
|
-
`quotedFrom: ${fromName}`
|
|
74
|
-
];
|
|
75
|
-
|
|
76
|
-
if (message.text) parts.push(`quotedText: ${message.text}`);
|
|
77
|
-
if (message.caption) parts.push(`quotedCaption: ${message.caption}`);
|
|
78
|
-
if (message.voice) parts.push(`quotedKind: voice`);
|
|
79
|
-
if (message.audio) parts.push(`quotedKind: audio`);
|
|
80
|
-
if (message.photo?.length) parts.push(`quotedKind: image`);
|
|
81
|
-
if (message.document) parts.push(`quotedKind: document`);
|
|
82
|
-
if (message.video) parts.push(`quotedKind: video`);
|
|
83
|
-
if (message.sticker) parts.push(`quotedKind: sticker`);
|
|
84
|
-
if (message.location) parts.push(`quotedKind: location`, `quotedLocation: ${formatLocationText(message)}`);
|
|
85
|
-
|
|
86
|
-
if (!message.text && !message.caption) {
|
|
87
|
-
parts.push(`Important: this message replies to a Telegram message with no textual body available in the update. Use the quoted kind and metadata as context.`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return parts;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
125
|
function getTelegramCommand(ctx) {
|
|
94
126
|
const text = ctx.message?.text || "";
|
|
95
127
|
const entity = ctx.message?.entities?.[0];
|
|
@@ -97,348 +129,13 @@ function getTelegramCommand(ctx) {
|
|
|
97
129
|
return text.slice(1, entity.length).split("@")[0].trim().toLowerCase();
|
|
98
130
|
}
|
|
99
131
|
|
|
100
|
-
function getIncomingMessageText(message) {
|
|
101
|
-
return message?.text || message?.caption || formatLocationText(message) || "";
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function telegramDisplayName(entity = {}) {
|
|
105
|
-
if (entity.username) return `@${entity.username}`;
|
|
106
|
-
return [entity.first_name, entity.last_name].filter(Boolean).join(" ") || entity.title || "unknown";
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function forwardedMessageSummary(message) {
|
|
110
|
-
const origin = message?.forward_origin;
|
|
111
|
-
if (!origin) return [];
|
|
112
|
-
|
|
113
|
-
const parts = ["forwarded: true", `forwardedOriginType: ${origin.type}`];
|
|
114
|
-
if (origin.type === "user") parts.push(`forwardedFrom: ${telegramDisplayName(origin.sender_user)}`);
|
|
115
|
-
if (origin.type === "hidden_user") parts.push(`forwardedFrom: ${origin.sender_user_name}`);
|
|
116
|
-
if (origin.type === "chat" || origin.type === "channel") {
|
|
117
|
-
parts.push(`forwardedFrom: ${telegramDisplayName(origin.chat)}`);
|
|
118
|
-
}
|
|
119
|
-
if (origin.type === "channel" && origin.message_id) parts.push(`forwardedMessageId: ${origin.message_id}`);
|
|
120
|
-
if (origin.author_signature) parts.push(`forwardedAuthorSignature: ${origin.author_signature}`);
|
|
121
|
-
if (origin.date) parts.push(`forwardedAt: ${new Date(origin.date * 1000).toISOString()}`);
|
|
122
|
-
return parts;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function reactionLabel(reaction = {}) {
|
|
126
|
-
if (reaction.type === "emoji") return reaction.emoji || "emoji";
|
|
127
|
-
if (reaction.type === "custom_emoji") return `custom:${reaction.custom_emoji_id || "unknown"}`;
|
|
128
|
-
if (reaction.type === "paid") return "paid";
|
|
129
|
-
return reaction.type || "unknown";
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function reactionDifference(left = [], right = []) {
|
|
133
|
-
const remaining = right.map(reactionLabel);
|
|
134
|
-
return left.map(reactionLabel).filter((label) => {
|
|
135
|
-
const index = remaining.indexOf(label);
|
|
136
|
-
if (index < 0) return true;
|
|
137
|
-
remaining.splice(index, 1);
|
|
138
|
-
return false;
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export function buildReactionPrompt({ reaction, reactedMessageText = "" }) {
|
|
143
|
-
const oldReactions = reaction.old_reaction || [];
|
|
144
|
-
const newReactions = reaction.new_reaction || [];
|
|
145
|
-
const added = reactionDifference(newReactions, oldReactions);
|
|
146
|
-
const removed = reactionDifference(oldReactions, newReactions);
|
|
147
|
-
const actor = reaction.user || reaction.actor_chat || {};
|
|
148
|
-
const actorId = reaction.user?.id || reaction.actor_chat?.id || "unknown";
|
|
149
|
-
|
|
150
|
-
return [
|
|
151
|
-
"Incoming Telegram reaction.",
|
|
152
|
-
`chatId: ${reaction.chat.id}`,
|
|
153
|
-
`userId: ${actorId}`,
|
|
154
|
-
`username: ${reaction.user?.username || "(no username)"}`,
|
|
155
|
-
`reactedMessageId: ${reaction.message_id}`,
|
|
156
|
-
reactedMessageText ? `reactedMessageText: ${reactedMessageText}` : null,
|
|
157
|
-
added.length ? `addedReactions: ${added.join(" ")}` : null,
|
|
158
|
-
removed.length ? `removedReactions: ${removed.join(" ")}` : null,
|
|
159
|
-
`currentReactions: ${newReactions.map(reactionLabel).join(" ") || "none"}`,
|
|
160
|
-
`actor: ${telegramDisplayName(actor)}`,
|
|
161
|
-
"Treat this as lightweight feedback on the referenced message. Respond only if the reaction clearly requests action; otherwise stay silent."
|
|
162
|
-
].filter(Boolean).join("\n");
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function baseMimeType(mimeType = "") {
|
|
166
|
-
return mimeType.split(";")[0].trim().toLowerCase();
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function isInlineTextArtifact(artifact, messageText) {
|
|
170
|
-
return artifact?.kind === "text"
|
|
171
|
-
&& baseMimeType(artifact.mimeType) === "text/plain"
|
|
172
|
-
&& typeof artifact.text === "string"
|
|
173
|
-
&& artifact.text === messageText;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export function shouldIncludeArtifactReference({ artifact, messageText = "" } = {}) {
|
|
177
|
-
if (!artifact) return false;
|
|
178
|
-
return !isInlineTextArtifact(artifact, messageText);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export function buildPrompt({ ctx, artifact, transcript, toolResult }) {
|
|
182
|
-
const parts = [
|
|
183
|
-
`Incoming Telegram message.`,
|
|
184
|
-
`chatId: ${ctx.chat.id}`,
|
|
185
|
-
`userId: ${ctx.from.id}`,
|
|
186
|
-
`username: ${ctx.from.username || "(no username)"}`,
|
|
187
|
-
`messageId: ${ctx.msg.message_id}`
|
|
188
|
-
];
|
|
189
|
-
|
|
190
|
-
const messageText = getIncomingMessageText(ctx.message);
|
|
191
|
-
if (messageText) parts.push(`text: ${messageText}`);
|
|
192
|
-
parts.push(...forwardedMessageSummary(ctx.message));
|
|
193
|
-
parts.push(...quotedMessageSummary(ctx.message?.reply_to_message));
|
|
194
|
-
if (shouldIncludeArtifactReference({ artifact, messageText })) {
|
|
195
|
-
if (artifact?.path) parts.push(`artifactPath: ${artifact.path}`);
|
|
196
|
-
if (artifact?.id) parts.push(`artifactId: ${artifact.id}`);
|
|
197
|
-
if (artifact?.mimeType) parts.push(`mimeType: ${artifact.mimeType}`);
|
|
198
|
-
if (artifact?.kind) parts.push(`kind: ${artifact.kind}`);
|
|
199
|
-
}
|
|
200
|
-
if (transcript) {
|
|
201
|
-
parts.push(`transcriptArtifactId: ${transcript.id}`);
|
|
202
|
-
parts.push(`transcriptText: ${transcript.text}`);
|
|
203
|
-
parts.push(`Important: the incoming media has already been transcribed. Use the transcript as the user message content. Do not answer with a raw transcription unless the user explicitly asked for one.`);
|
|
204
|
-
}
|
|
205
|
-
if (shouldNormalizeArtifactToText(artifact) && !transcript && toolResult) {
|
|
206
|
-
parts.push(`mediaNormalizationResult: ${JSON.stringify(toolResult)}`);
|
|
207
|
-
parts.push(`Important: pre-reasoning media normalization could not be completed, so you do not have a transcript for this audio/video message.`);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
parts.push(`Use read/write/edit for file work in the active workspace, bash for bash-compatible commands, and system_shell for native system commands such as PowerShell on Windows.`);
|
|
211
|
-
parts.push(`If you need an Arisa modular CLI tool, use list_tools/tool_help/run_tool.`);
|
|
212
|
-
parts.push(`If a tool config is missing, ask the user naturally and then use set_tool_config.`);
|
|
213
|
-
parts.push(`To deliver a file to the chat: run_tool with deliver:true to generate and send in one step, or send_artifact with an existing artifactId (e.g. an inbound file).`);
|
|
214
|
-
return parts.join("\n");
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function buildNewSessionPrompt(ctx) {
|
|
218
|
-
return [
|
|
219
|
-
"System event: /new requested.",
|
|
220
|
-
"Session was reset.",
|
|
221
|
-
`preferredTelegramLanguageCode: ${ctx.from?.language_code || "unknown"}`,
|
|
222
|
-
"Reply with a brief, warm confirmation in the user's language."
|
|
223
|
-
].join("\n");
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
export function isScheduledTaskPrompt(prompt) {
|
|
227
|
-
return String(prompt || "").startsWith("Scheduled task fired.\n");
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
export async function withPromptSpeed({ speedController, speed, restoreSpeed }, work) {
|
|
231
|
-
if (!speedController || speed === undefined) return work();
|
|
232
|
-
speedController.setSpeed(speed);
|
|
233
|
-
try {
|
|
234
|
-
return await work();
|
|
235
|
-
} finally {
|
|
236
|
-
speedController.setSpeed(restoreSpeed());
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
export async function buildAsyncTaskPrompt({ task, artifactStore, toolRegistry, resourceNotes, logger }) {
|
|
241
|
-
const taskText = task.payload.prompt || "";
|
|
242
|
-
const resourceId = String(task.source?.resourceId || task.payload?.resourceId || "").trim();
|
|
243
|
-
const resourceNote = resourceId && task.source?.toolName
|
|
244
|
-
? await resourceNotes.get(task.payload.chatId, task.source.toolName, resourceId)
|
|
245
|
-
: "";
|
|
246
|
-
const parts = [
|
|
247
|
-
"Scheduled task fired.",
|
|
248
|
-
`taskId: ${task.id}`,
|
|
249
|
-
`chatId: ${task.payload.chatId}`,
|
|
250
|
-
resourceNote ? `resourceNote: ${resourceNote}` : null,
|
|
251
|
-
taskText ? `text: ${taskText}` : null
|
|
252
|
-
];
|
|
253
|
-
|
|
254
|
-
if (task.payload.artifactId) {
|
|
255
|
-
const chatArtifactStore = artifactStore.forChat(task.payload.chatId);
|
|
256
|
-
const artifact = await chatArtifactStore.get(task.payload.artifactId);
|
|
257
|
-
if (artifact) {
|
|
258
|
-
if (shouldIncludeArtifactReference({ artifact, messageText: taskText })) {
|
|
259
|
-
parts.push(`artifactPath: ${artifact.path || ""}`);
|
|
260
|
-
parts.push(`artifactId: ${artifact.id}`);
|
|
261
|
-
parts.push(`mimeType: ${artifact.mimeType}`);
|
|
262
|
-
parts.push(`kind: ${artifact.kind}`);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const { normalizedArtifact, toolResult } = await normalizeArtifactForReasoning({
|
|
266
|
-
artifact,
|
|
267
|
-
desiredMimeType: "text/plain",
|
|
268
|
-
toolRegistry,
|
|
269
|
-
chatArtifactStore,
|
|
270
|
-
chatId: task.payload.chatId
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
if (normalizedArtifact) {
|
|
274
|
-
logger?.log("tasks", `artifact ${artifact.id} normalized to ${normalizedArtifact.id}`);
|
|
275
|
-
parts.push(`transcriptArtifactId: ${normalizedArtifact.id}`);
|
|
276
|
-
parts.push(`transcriptText: ${normalizedArtifact.text}`);
|
|
277
|
-
parts.push("Important: the attached media artifact has already been normalized for reasoning. Use the transcript as the message content.");
|
|
278
|
-
} else if (shouldNormalizeArtifactToText(artifact) && toolResult) {
|
|
279
|
-
parts.push(`mediaNormalizationResult: ${JSON.stringify(toolResult)}`);
|
|
280
|
-
parts.push("Important: pre-reasoning media normalization could not be completed, so you do not have a transcript for this audio/video artifact.");
|
|
281
|
-
}
|
|
282
|
-
} else {
|
|
283
|
-
parts.push(`artifactId: ${task.payload.artifactId}`);
|
|
284
|
-
parts.push("Important: referenced artifact was not found.");
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
parts.push("Treat this as a new request for the chat and fulfill it now.");
|
|
289
|
-
parts.push("If needed, use read/write/edit, bash, system_shell, or Arisa modular tools via run_tool.");
|
|
290
|
-
return parts.filter(Boolean).join("\n");
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
async function buildAsyncEventPrompt(task, resourceNotes) {
|
|
294
|
-
const resourceId = String(task.source?.resourceId || task.payload?.resourceId || "").trim();
|
|
295
|
-
const resourceNote = resourceId && task.source?.toolName
|
|
296
|
-
? await resourceNotes.get(task.payload.chatId, task.source.toolName, resourceId)
|
|
297
|
-
: "";
|
|
298
|
-
return [
|
|
299
|
-
"External event arrived.",
|
|
300
|
-
`taskId: ${task.id}`,
|
|
301
|
-
`chatId: ${task.payload.chatId}`,
|
|
302
|
-
resourceNote ? `resourceNote: ${resourceNote}` : null,
|
|
303
|
-
task.payload.prompt ? `event: ${task.payload.prompt}` : null,
|
|
304
|
-
"A polling checker detected this external event. Evaluate it and decide the next action.",
|
|
305
|
-
"If it warrants no action, you may stay silent.",
|
|
306
|
-
"If needed, use read/write/edit, bash, system_shell, or Arisa modular tools via run_tool."
|
|
307
|
-
].filter(Boolean).join("\n");
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
async function normalizeIncomingArtifact({ artifact, toolRegistry, chatArtifactStore, chatId }) {
|
|
311
|
-
if (!artifact) return { transcript: null, toolResult: null };
|
|
312
|
-
const { normalizedArtifact, toolResult } = await normalizeArtifactForReasoning({
|
|
313
|
-
artifact,
|
|
314
|
-
desiredMimeType: "text/plain",
|
|
315
|
-
toolRegistry,
|
|
316
|
-
chatArtifactStore,
|
|
317
|
-
chatId
|
|
318
|
-
});
|
|
319
|
-
return { transcript: normalizedArtifact, toolResult };
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function sessionEventLogMessage(event) {
|
|
323
|
-
if (event.type === "tool_execution_start") {
|
|
324
|
-
return `tool ${event.toolName} started`;
|
|
325
|
-
}
|
|
326
|
-
if (event.type === "tool_execution_end") {
|
|
327
|
-
return `tool ${event.toolName} ${event.isError ? "failed" : "finished"}`;
|
|
328
|
-
}
|
|
329
|
-
if (event.type === "auto_retry_start") {
|
|
330
|
-
return `auto retry ${event.attempt}/${event.maxAttempts} in ${event.delayMs}ms: ${event.errorMessage}`;
|
|
331
|
-
}
|
|
332
|
-
if (event.type === "auto_retry_end") {
|
|
333
|
-
return event.success
|
|
334
|
-
? `auto retry succeeded after ${event.attempt} attempt(s)`
|
|
335
|
-
: `auto retry failed after ${event.attempt} attempt(s): ${event.finalError || "unknown error"}`;
|
|
336
|
-
}
|
|
337
|
-
if (event.type === "compaction_start") {
|
|
338
|
-
return `compaction started (${event.reason})`;
|
|
339
|
-
}
|
|
340
|
-
if (event.type === "compaction_end") {
|
|
341
|
-
return `compaction ${event.aborted ? "aborted" : "finished"} (${event.reason})`;
|
|
342
|
-
}
|
|
343
|
-
if (event.type === "message_end" && event.message?.stopReason === "error") {
|
|
344
|
-
return `assistant message ended with error: ${event.message.errorMessage || "unknown error"}`;
|
|
345
|
-
}
|
|
346
|
-
return "";
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
function buildStartupMessage(chatMeta = {}) {
|
|
350
|
-
const languageCode = String(chatMeta.languageCode || "").toLowerCase();
|
|
351
|
-
if (languageCode.startsWith("es")) return "Arisa esta en linea de nuevo.";
|
|
352
|
-
if (languageCode.startsWith("pt")) return "Arisa esta online de novo.";
|
|
353
|
-
return "Arisa is back online.";
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
export async function collectText(session, prompt, { logger, chatId, onSlowPrompt } = {}) {
|
|
357
|
-
const assistantMessages = [];
|
|
358
|
-
let assistantMessage = "";
|
|
359
|
-
let assistantErrorMessage = "";
|
|
360
|
-
let slowPromptTimer = null;
|
|
361
|
-
const finishAssistantMessage = () => {
|
|
362
|
-
if (assistantMessage && !isSilentReply(assistantMessage)) {
|
|
363
|
-
assistantMessages.push(assistantMessage);
|
|
364
|
-
}
|
|
365
|
-
assistantMessage = "";
|
|
366
|
-
};
|
|
367
|
-
const unsubscribe = session.subscribe((event) => {
|
|
368
|
-
if (event.arisaPromptScoped === false) return;
|
|
369
|
-
if (event.type === "message_start" && event.message.role === "assistant") {
|
|
370
|
-
finishAssistantMessage();
|
|
371
|
-
}
|
|
372
|
-
if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") {
|
|
373
|
-
assistantMessage += event.assistantMessageEvent.delta;
|
|
374
|
-
}
|
|
375
|
-
if (event.type === "message_end" && event.message?.role === "assistant") {
|
|
376
|
-
if (event.message.stopReason === "error") {
|
|
377
|
-
assistantErrorMessage = event.message.errorMessage || "assistant message ended with error";
|
|
378
|
-
} else if (event.message.stopReason !== "aborted") {
|
|
379
|
-
// Auto-compaction and retry can emit a transient error before a successful continuation.
|
|
380
|
-
assistantErrorMessage = "";
|
|
381
|
-
}
|
|
382
|
-
finishAssistantMessage();
|
|
383
|
-
}
|
|
384
|
-
const logMessage = sessionEventLogMessage(event);
|
|
385
|
-
if (logMessage) logger?.log("agent", `chat ${chatId} ${logMessage}`);
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
if (onSlowPrompt) {
|
|
389
|
-
slowPromptTimer = setTimeout(() => {
|
|
390
|
-
logger?.log("telegram", `prompt for chat ${chatId} is still running after ${slowPromptNoticeMs}ms`);
|
|
391
|
-
onSlowPrompt().catch((error) => {
|
|
392
|
-
logger?.error("telegram", `slow prompt notice failed for chat ${chatId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
393
|
-
});
|
|
394
|
-
}, slowPromptNoticeMs);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
try {
|
|
398
|
-
await session.prompt(prompt);
|
|
399
|
-
} finally {
|
|
400
|
-
if (slowPromptTimer) clearTimeout(slowPromptTimer);
|
|
401
|
-
unsubscribe();
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
if (assistantErrorMessage) {
|
|
405
|
-
throw new Error(assistantErrorMessage);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
finishAssistantMessage();
|
|
409
|
-
return assistantMessages.join("\n\n").trim();
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
export function isSilentReply(text) {
|
|
413
|
-
return /^(?:NO_REPLY|No reply needed\.|No action needed\.)(?:\s+(?:NO_REPLY|No reply needed\.|No action needed\.))*$/.test(String(text || "").trim());
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
function buildSessionHandoffPrompt() {
|
|
417
|
-
return [
|
|
418
|
-
"Prepare a concise handoff for the next Arisa session.",
|
|
419
|
-
"Review the entire active session, including any previous compaction summaries and the latest messages.",
|
|
420
|
-
"Keep only durable context: current goals or projects, decisions, user preferences, unresolved tasks, and important facts needed to continue.",
|
|
421
|
-
"Use at most 8 short bullets and at most 1600 characters.",
|
|
422
|
-
"Exclude secrets, tokens, passwords, cookies, API keys, private file paths, full transcripts, and stale chatter.",
|
|
423
|
-
"Do not take actions, call tools, send messages, or explain the process.",
|
|
424
|
-
"Return only the handoff."
|
|
425
|
-
].join("\n");
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
function sanitizeSessionHandoff(text) {
|
|
429
|
-
const sanitized = String(text || "")
|
|
430
|
-
.replace(/-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/gi, "[redacted private key]")
|
|
431
|
-
.replace(/\b(?:sk-[A-Za-z0-9_-]{12,}|gh[opsu]_[A-Za-z0-9_-]{12,}|Bearer\s+[A-Za-z0-9._-]+)\b/gi, "[redacted credential]")
|
|
432
|
-
.replace(/(?:api[_ -]?key|access[_ -]?token|refresh[_ -]?token|client[_ -]?secret|password|cookie|secret)\s*[:=]\s*[^\s,;]+/gi, "[redacted credential]")
|
|
433
|
-
.trim();
|
|
434
|
-
if (sanitized.length <= 4000) return sanitized;
|
|
435
|
-
return `${sanitized.slice(0, 3997).trim()}...`;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
132
|
export async function startTelegramTyping(ctx) {
|
|
439
|
-
|
|
133
|
+
const options = ctx.message?.message_thread_id
|
|
134
|
+
? { message_thread_id: ctx.message.message_thread_id }
|
|
135
|
+
: undefined;
|
|
136
|
+
await ctx.api.sendChatAction(ctx.chat.id, "typing", options).catch(() => {});
|
|
440
137
|
const timer = setInterval(() => {
|
|
441
|
-
ctx.api.sendChatAction(ctx.chat.id, "typing").catch(() => {});
|
|
138
|
+
ctx.api.sendChatAction(ctx.chat.id, "typing", options).catch(() => {});
|
|
442
139
|
}, 4000);
|
|
443
140
|
return () => clearInterval(timer);
|
|
444
141
|
}
|
|
@@ -462,164 +159,43 @@ async function withTyping(ctx, work) {
|
|
|
462
159
|
}
|
|
463
160
|
}
|
|
464
161
|
|
|
465
|
-
export
|
|
466
|
-
const states = new Map();
|
|
467
|
-
|
|
468
|
-
function reset(chatId) {
|
|
469
|
-
const state = {
|
|
470
|
-
processing: false,
|
|
471
|
-
pendingPrompts: [],
|
|
472
|
-
continueAfterClose: false,
|
|
473
|
-
historyRevision: 0,
|
|
474
|
-
beforeNextPrompt: null,
|
|
475
|
-
activeSession: null,
|
|
476
|
-
activeSteers: [],
|
|
477
|
-
assistantMessages: new Map(),
|
|
478
|
-
stopQueuedTyping: null
|
|
479
|
-
};
|
|
480
|
-
states.set(String(chatId), state);
|
|
481
|
-
return state;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
return {
|
|
485
|
-
get(chatId) {
|
|
486
|
-
const key = String(chatId);
|
|
487
|
-
return states.get(key) || reset(key);
|
|
488
|
-
},
|
|
489
|
-
reset,
|
|
490
|
-
anyProcessing() {
|
|
491
|
-
return [...states.values()].some((state) => state.processing);
|
|
492
|
-
}
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
export function queueChatPrompt(chatState, prompt, { replace = false } = {}) {
|
|
497
|
-
if (replace) chatState.pendingPrompts = [];
|
|
498
|
-
chatState.pendingPrompts.push(prompt);
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
function takeQueuedPrompt(chatState) {
|
|
502
|
-
return chatState.pendingPrompts.shift() || "";
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
export function resolveTelegramBusyMessageMode(config, chatId) {
|
|
506
|
-
const chatMode = config.telegram?.chatMeta?.[String(chatId)]?.busyMessageMode;
|
|
507
|
-
const mode = chatMode || config.telegram?.busyMessageMode;
|
|
508
|
-
return mode === "steer" ? "steer" : "queue";
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
export async function routeBusyPrompt({ chatState, prompt, mode = "queue", replaceQueued = false }) {
|
|
512
|
-
const session = chatState.activeSession;
|
|
513
|
-
if (
|
|
514
|
-
mode === "steer"
|
|
515
|
-
&& !replaceQueued
|
|
516
|
-
&& !chatState.continueAfterClose
|
|
517
|
-
&& !chatState.beforeNextPrompt
|
|
518
|
-
&& session?.isStreaming
|
|
519
|
-
&& typeof session.steer === "function"
|
|
520
|
-
) {
|
|
521
|
-
try {
|
|
522
|
-
await session.steer(prompt);
|
|
523
|
-
chatState.activeSteers.push(prompt);
|
|
524
|
-
return { disposition: "steered" };
|
|
525
|
-
} catch (error) {
|
|
526
|
-
queueChatPrompt(chatState, prompt);
|
|
527
|
-
return { disposition: "queued", steerError: error };
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
queueChatPrompt(chatState, prompt, { replace: replaceQueued });
|
|
532
|
-
return { disposition: "queued" };
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
export async function drainChatPromptQueue({
|
|
536
|
-
chatState,
|
|
537
|
-
initialPrompt,
|
|
538
|
-
initialCtx = null,
|
|
539
|
-
processPrompt,
|
|
540
|
-
onPromptFailure,
|
|
541
|
-
onPromptInterrupted,
|
|
542
|
-
beforeInitialPrompt
|
|
543
|
-
}) {
|
|
544
|
-
let currentPrompt = initialPrompt;
|
|
545
|
-
let currentCtx = initialCtx;
|
|
546
|
-
|
|
547
|
-
try {
|
|
548
|
-
await beforeInitialPrompt?.();
|
|
549
|
-
while (currentPrompt) {
|
|
550
|
-
while (chatState.beforeNextPrompt) {
|
|
551
|
-
const gate = chatState.beforeNextPrompt;
|
|
552
|
-
await gate;
|
|
553
|
-
if (chatState.beforeNextPrompt === gate) chatState.beforeNextPrompt = null;
|
|
554
|
-
}
|
|
555
|
-
if (chatState.continueAfterClose && chatState.pendingPrompts.length) {
|
|
556
|
-
currentPrompt = takeQueuedPrompt(chatState);
|
|
557
|
-
chatState.continueAfterClose = false;
|
|
558
|
-
currentCtx = null;
|
|
559
|
-
}
|
|
560
|
-
try {
|
|
561
|
-
await processPrompt({ prompt: currentPrompt, ctx: currentCtx });
|
|
562
|
-
} catch (error) {
|
|
563
|
-
if (chatState.continueAfterClose && chatState.pendingPrompts.length) {
|
|
564
|
-
await onPromptInterrupted?.(error);
|
|
565
|
-
} else {
|
|
566
|
-
await onPromptFailure?.(error);
|
|
567
|
-
throw error;
|
|
568
|
-
}
|
|
569
|
-
} finally {
|
|
570
|
-
currentCtx = null;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
currentPrompt = takeQueuedPrompt(chatState);
|
|
574
|
-
chatState.continueAfterClose = false;
|
|
575
|
-
}
|
|
576
|
-
} finally {
|
|
577
|
-
stopQueuedTelegramTyping(chatState);
|
|
578
|
-
chatState.processing = false;
|
|
579
|
-
chatState.activeSession = null;
|
|
580
|
-
chatState.activeSteers = [];
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
export async function closeModelPicker(ctx, { messageText, callbackText }) {
|
|
585
|
-
await ctx.api.editMessageText(
|
|
586
|
-
ctx.chat.id,
|
|
587
|
-
ctx.callbackQuery.message.message_id,
|
|
588
|
-
messageText
|
|
589
|
-
);
|
|
590
|
-
await ctx.answerCallbackQuery({ text: callbackText });
|
|
591
|
-
}
|
|
162
|
+
export { closeModelPicker } from "./model-callback.js";
|
|
592
163
|
|
|
593
164
|
export async function createTelegramBot({ config, artifactStore, toolRegistry, taskStore, agentManager, saveConfig, updateConfig, doctor, checkUpdates, updateCore, updateTools, requestRestart, logger }) {
|
|
594
165
|
const resourceNotes = new ToolResourceNoteStore();
|
|
595
166
|
const bot = new Bot(config.telegram.token);
|
|
596
167
|
const perChatState = createChatStateStore();
|
|
597
|
-
const
|
|
168
|
+
const sessionSeeds = new SessionSeedStore();
|
|
598
169
|
const notifiedPromptErrors = new WeakSet();
|
|
599
170
|
const authRenewals = new Map();
|
|
171
|
+
const workspaceRoutes = new WeakMap();
|
|
172
|
+
const workspaceGateStates = new Map();
|
|
600
173
|
let piAuthIssue = null;
|
|
601
174
|
let taskTimer = null;
|
|
602
175
|
|
|
176
|
+
const requestRestartWithReceipt = async (ctx, reason = "Telegram restart") => {
|
|
177
|
+
const route = contextRoute(ctx);
|
|
178
|
+
const receipt = await prepareRestartReceipt({
|
|
179
|
+
transportChatId: route.transportChatId,
|
|
180
|
+
threadId: route.threadId
|
|
181
|
+
}, { reason });
|
|
182
|
+
try {
|
|
183
|
+
return await requestRestart();
|
|
184
|
+
} catch (error) {
|
|
185
|
+
await cancelRestartReceipt(receipt.id).catch(() => {});
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
603
189
|
const handleRestartCommand = createTelegramRestartHandler({
|
|
604
|
-
authorize:
|
|
605
|
-
|
|
606
|
-
chatId: ctx.chat.id,
|
|
607
|
-
saveConfig,
|
|
608
|
-
chatMeta: getIncomingChatMeta(ctx)
|
|
609
|
-
}),
|
|
610
|
-
requestRestart,
|
|
190
|
+
authorize: authorizeContext,
|
|
191
|
+
requestRestart: (ctx) => requestRestartWithReceipt(ctx, "Telegram /restart"),
|
|
611
192
|
logger
|
|
612
193
|
});
|
|
613
194
|
const handleUpdateCallback = createTelegramUpdateCallbackHandler({
|
|
614
|
-
authorize:
|
|
615
|
-
config,
|
|
616
|
-
chatId: ctx.chat.id,
|
|
617
|
-
saveConfig,
|
|
618
|
-
chatMeta: getIncomingChatMeta(ctx)
|
|
619
|
-
}),
|
|
195
|
+
authorize: authorizeContext,
|
|
620
196
|
updateCore,
|
|
621
197
|
updateTools,
|
|
622
|
-
requestRestart,
|
|
198
|
+
requestRestart: (ctx) => requestRestartWithReceipt(ctx, "Telegram update restart"),
|
|
623
199
|
logger
|
|
624
200
|
});
|
|
625
201
|
|
|
@@ -743,174 +319,97 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
743
319
|
};
|
|
744
320
|
}
|
|
745
321
|
|
|
746
|
-
function
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
provider: config.pi.provider,
|
|
753
|
-
apiKey: config.pi.apiKey
|
|
754
|
-
});
|
|
755
|
-
return reverseModelOrder(listProviderModels(config.pi.provider, runtime));
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
async function showModelPicker(ctx, page = 0) {
|
|
759
|
-
const agentConfig = getAgentConfig(config);
|
|
760
|
-
const picker = buildModelPicker({
|
|
761
|
-
provider: agentConfig.provider,
|
|
762
|
-
models: await getProviderModels(ctx.chat.id),
|
|
763
|
-
selectedModelId: resolveChatModel(config, ctx.chat.id),
|
|
764
|
-
selectedThinkingLevel: resolveChatThinkingLevel(config, ctx.chat.id),
|
|
765
|
-
selectedSpeed: resolveChatSpeed(config, ctx.chat.id),
|
|
766
|
-
page,
|
|
767
|
-
pageSize: config.telegram.modelPickerPageSize
|
|
768
|
-
});
|
|
769
|
-
const extra = { reply_markup: picker.replyMarkup };
|
|
770
|
-
const messageId = ctx.callbackQuery?.message?.message_id;
|
|
771
|
-
if (messageId) {
|
|
772
|
-
return ctx.api.editMessageText(ctx.chat.id, messageId, picker.text, extra);
|
|
322
|
+
async function authorizeContext(ctx) {
|
|
323
|
+
const route = await resolveTelegramWorkspaceRoute({ config, api: ctx.api, ctx });
|
|
324
|
+
if (!route.workspace) {
|
|
325
|
+
const auth = await authorizeChat({ config, chatId: ctx.chat.id, saveConfig, chatMeta: getIncomingChatMeta(ctx) });
|
|
326
|
+
if (auth.ok) workspaceRoutes.set(ctx, route);
|
|
327
|
+
return auth;
|
|
773
328
|
}
|
|
774
|
-
return ctx.reply(picker.text, extra);
|
|
775
|
-
}
|
|
776
329
|
|
|
777
|
-
|
|
778
|
-
const
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
}
|
|
784
|
-
if (!modelSupportsThinking(resolvedModel)) {
|
|
785
|
-
const text = `${resolvedModel.provider}/${resolvedModel.id} does not support effort levels.`;
|
|
786
|
-
if (ctx.callbackQuery?.message?.message_id) {
|
|
787
|
-
return ctx.api.editMessageText(ctx.chat.id, ctx.callbackQuery.message.message_id, text);
|
|
330
|
+
const gateKey = String(ctx.chat.id);
|
|
331
|
+
const previous = workspaceGateStates.get(gateKey);
|
|
332
|
+
if (!route.ok) {
|
|
333
|
+
workspaceGateStates.set(gateKey, route.reason || "locked");
|
|
334
|
+
if (previous !== (route.reason || "locked")) {
|
|
335
|
+
await ctx.reply("Private workspace access is paused because this forum is no longer owner-only.").catch(() => {});
|
|
788
336
|
}
|
|
789
|
-
return
|
|
337
|
+
return { ok: false, reason: route.reason || "workspace-locked" };
|
|
790
338
|
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
modelIndex
|
|
799
|
-
});
|
|
800
|
-
const extra = { reply_markup: picker.replyMarkup };
|
|
801
|
-
const messageId = ctx.callbackQuery?.message?.message_id;
|
|
802
|
-
if (messageId) {
|
|
803
|
-
return ctx.api.editMessageText(ctx.chat.id, messageId, picker.text, extra);
|
|
339
|
+
if (!(config.telegram.authorizedChatIds || []).includes(route.ownerChatId)) {
|
|
340
|
+
return { ok: false, reason: "owner-not-authorized" };
|
|
341
|
+
}
|
|
342
|
+
workspaceRoutes.set(ctx, route);
|
|
343
|
+
workspaceGateStates.set(gateKey, "ready");
|
|
344
|
+
if (previous && previous !== "ready") {
|
|
345
|
+
await ctx.reply("Private workspace access restored.").catch(() => {});
|
|
804
346
|
}
|
|
805
|
-
return
|
|
347
|
+
return { ok: true, firstTime: false, workspace: true };
|
|
806
348
|
}
|
|
807
349
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
}
|
|
818
|
-
return ctx.reply(text);
|
|
819
|
-
}
|
|
820
|
-
const picker = buildSpeedPicker({
|
|
821
|
-
provider: model.provider,
|
|
822
|
-
modelId: model.id,
|
|
823
|
-
speeds: MODEL_SPEEDS,
|
|
824
|
-
selectedSpeed: resolveChatSpeed(config, ctx.chat.id)
|
|
825
|
-
});
|
|
826
|
-
const extra = { reply_markup: picker.replyMarkup };
|
|
827
|
-
const messageId = ctx.callbackQuery?.message?.message_id;
|
|
828
|
-
if (messageId) return ctx.api.editMessageText(ctx.chat.id, messageId, picker.text, extra);
|
|
829
|
-
return ctx.reply(picker.text, extra);
|
|
350
|
+
function contextRoute(ctx) {
|
|
351
|
+
return workspaceRoutes.get(ctx) || {
|
|
352
|
+
ok: true,
|
|
353
|
+
workspace: false,
|
|
354
|
+
sessionId: String(ctx.chat.id),
|
|
355
|
+
scopeChatId: ctx.chat.id,
|
|
356
|
+
transportChatId: ctx.chat.id,
|
|
357
|
+
threadId: null
|
|
358
|
+
};
|
|
830
359
|
}
|
|
831
360
|
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
const key = chatKey(chatId);
|
|
835
|
-
const hadSelections = Boolean(agentConfig.chatModels);
|
|
836
|
-
const previousSelection = agentConfig.chatModels?.[key];
|
|
837
|
-
const level = clampModelThinkingLevel(model, thinkingLevel ?? resolveChatThinkingLevel(config, chatId));
|
|
838
|
-
const speed = clampModelSpeed(model, resolveChatSpeed(config, chatId));
|
|
839
|
-
selectChatModel(config, chatId, model, { thinkingLevel: level, speed });
|
|
840
|
-
try {
|
|
841
|
-
await saveConfig(config);
|
|
842
|
-
} catch (error) {
|
|
843
|
-
if (previousSelection) {
|
|
844
|
-
agentConfig.chatModels[key] = previousSelection;
|
|
845
|
-
} else {
|
|
846
|
-
delete agentConfig.chatModels[key];
|
|
847
|
-
if (!hadSelections) delete agentConfig.chatModels;
|
|
848
|
-
}
|
|
849
|
-
throw error;
|
|
850
|
-
}
|
|
851
|
-
agentManager.resetSession(chatId);
|
|
852
|
-
return level;
|
|
361
|
+
function getChatState(chatId) {
|
|
362
|
+
return perChatState.get(chatId);
|
|
853
363
|
}
|
|
854
364
|
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
delete agentConfig.chatModels[key];
|
|
869
|
-
if (!hadSelections) delete agentConfig.chatModels;
|
|
870
|
-
}
|
|
871
|
-
throw error;
|
|
872
|
-
}
|
|
873
|
-
return level;
|
|
365
|
+
const {
|
|
366
|
+
getProviderModels,
|
|
367
|
+
showModelPicker,
|
|
368
|
+
showEffortPicker,
|
|
369
|
+
showSpeedPicker,
|
|
370
|
+
persistChatModel,
|
|
371
|
+
persistChatEffort,
|
|
372
|
+
persistChatSpeed
|
|
373
|
+
} = createTelegramModelControls({ config, saveConfig, agentManager, contextRoute });
|
|
374
|
+
|
|
375
|
+
function modelSelectionFor(chatId) {
|
|
376
|
+
const selection = config.pi.chatModels?.[chatKey(chatId)];
|
|
377
|
+
return selection?.provider === config.pi.provider ? selection : null;
|
|
874
378
|
}
|
|
875
379
|
|
|
876
|
-
async function
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
const
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
agentConfig.chatModels[key] = previousSelection;
|
|
889
|
-
} else {
|
|
890
|
-
delete agentConfig.chatModels[key];
|
|
891
|
-
if (!hadSelections) delete agentConfig.chatModels;
|
|
892
|
-
}
|
|
893
|
-
agentManager.clearSessionCache(chatId);
|
|
894
|
-
throw error;
|
|
895
|
-
}
|
|
896
|
-
return level;
|
|
380
|
+
async function ensureWorkspaceTopicModelSelection(route) {
|
|
381
|
+
if (!route.workspace || chatKey(route.sessionId) === chatKey(route.ownerChatId)) return;
|
|
382
|
+
if (modelSelectionFor(route.sessionId)) return;
|
|
383
|
+
const inherited = modelSelectionFor(route.scopeChatId) || modelSelectionFor(route.transportChatId);
|
|
384
|
+
if (!inherited) return;
|
|
385
|
+
config.pi.chatModels ||= {};
|
|
386
|
+
config.pi.chatModels[chatKey(route.sessionId)] = {
|
|
387
|
+
...inherited,
|
|
388
|
+
sessionRevision: 0
|
|
389
|
+
};
|
|
390
|
+
await saveConfig(config);
|
|
391
|
+
logger?.log("telegram", `inherited model ${inherited.model} for workspace topic session ${route.sessionId}`);
|
|
897
392
|
}
|
|
898
393
|
|
|
899
|
-
async function buildIncomingPrompt(ctx) {
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
const
|
|
903
|
-
const artifact = await captureIncomingArtifact(ctx, artifactStore);
|
|
394
|
+
async function buildIncomingPrompt(ctx, route = contextRoute(ctx)) {
|
|
395
|
+
logger?.log("telegram", `message ${ctx.msg.message_id} in chat ${route.transportChatId} session ${route.sessionId}`);
|
|
396
|
+
const chatArtifactStore = artifactStore.forChat(route.scopeChatId);
|
|
397
|
+
const artifact = await captureIncomingArtifact(ctx, artifactStore, { storageChatId: route.scopeChatId });
|
|
904
398
|
if (artifact) logger?.log("telegram", `captured artifact ${artifact.kind}${artifact.id ? ` ${artifact.id}` : ""}`);
|
|
905
|
-
const { transcript, toolResult } = await normalizeIncomingArtifact({
|
|
399
|
+
const { transcript, toolResult, normalizationRequired } = await normalizeIncomingArtifact({
|
|
400
|
+
artifact,
|
|
401
|
+
toolRegistry,
|
|
402
|
+
chatArtifactStore,
|
|
403
|
+
chatId: route.scopeChatId
|
|
404
|
+
});
|
|
906
405
|
if (transcript) logger?.log("telegram", `media transcribed to artifact ${transcript.id}`);
|
|
907
|
-
if (
|
|
908
|
-
logger?.log("telegram", `media normalization unavailable for chat ${
|
|
406
|
+
if (normalizationRequired && !transcript) {
|
|
407
|
+
logger?.log("telegram", `media normalization unavailable for chat ${route.transportChatId}: ${toolResult?.error || toolResult?.missingConfig?.join(", ") || "unknown error"}`);
|
|
909
408
|
}
|
|
910
409
|
return buildPrompt({ ctx, artifact, transcript, toolResult });
|
|
911
410
|
}
|
|
912
411
|
|
|
913
|
-
async function sendTextReply({ sendText, sendDocument, chatId, text }) {
|
|
412
|
+
async function sendTextReply({ sendText, sendDocument, chatId, artifactChatId = chatId, text }) {
|
|
914
413
|
const maxInlineReplyLength = 3500;
|
|
915
414
|
|
|
916
415
|
if (isSilentReply(text)) {
|
|
@@ -920,7 +419,7 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
920
419
|
|
|
921
420
|
if (text.length > maxInlineReplyLength) {
|
|
922
421
|
logger?.log("telegram", `sending long reply as markdown attachment for chat ${chatId}`);
|
|
923
|
-
const chatArtifactStore = artifactStore.forChat(
|
|
422
|
+
const chatArtifactStore = artifactStore.forChat(artifactChatId);
|
|
924
423
|
const artifact = await chatArtifactStore.createGeneratedFile({
|
|
925
424
|
fileName: `reply-${Date.now()}.md`,
|
|
926
425
|
content: text,
|
|
@@ -944,17 +443,82 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
944
443
|
}
|
|
945
444
|
}
|
|
946
445
|
|
|
947
|
-
function
|
|
446
|
+
function createWorkspaceAccessGuard(route) {
|
|
447
|
+
return async () => {
|
|
448
|
+
if (!route.workspace) return;
|
|
449
|
+
const current = await resolveTelegramWorkspaceRoute({
|
|
450
|
+
config,
|
|
451
|
+
api: bot.api,
|
|
452
|
+
ctx: {
|
|
453
|
+
chat: { id: route.transportChatId, type: "supergroup", is_forum: true },
|
|
454
|
+
from: { id: route.ownerChatId },
|
|
455
|
+
message: { message_thread_id: route.threadId }
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
if (!current.ok) throw new Error("Owner-only workspace access is paused.");
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function createTelegramSessionBridge(route) {
|
|
463
|
+
const messageOptions = (extra = {}) => route.workspace && route.threadId
|
|
464
|
+
? { ...extra, message_thread_id: route.threadId }
|
|
465
|
+
: extra;
|
|
466
|
+
const initializeForumTopic = async ({ messageThreadId, name, context }) => {
|
|
467
|
+
if (!route.workspace) throw new Error("Telegram topic initialization is only available from the owner workspace forum.");
|
|
468
|
+
await createWorkspaceAccessGuard(route)();
|
|
469
|
+
const initializedSessionId = topicSessionId({
|
|
470
|
+
ownerChatId: route.ownerChatId,
|
|
471
|
+
groupChatId: route.transportChatId,
|
|
472
|
+
threadId: messageThreadId,
|
|
473
|
+
generalTopicId: route.generalTopicId
|
|
474
|
+
});
|
|
475
|
+
if (initializedSessionId === String(route.ownerChatId)) {
|
|
476
|
+
throw new Error("The General topic already uses the owner's private session and cannot be reinitialized here.");
|
|
477
|
+
}
|
|
478
|
+
const handoff = buildTopicInitializationHandoff({ name, context });
|
|
479
|
+
await sessionSeeds.set(initializedSessionId, handoff);
|
|
480
|
+
agentManager.resetSession(initializedSessionId, { handoff });
|
|
481
|
+
await agentManager.waitForSessionClose(initializedSessionId);
|
|
482
|
+
return {
|
|
483
|
+
ok: true,
|
|
484
|
+
chatId: route.transportChatId,
|
|
485
|
+
messageThreadId,
|
|
486
|
+
sessionId: initializedSessionId,
|
|
487
|
+
name,
|
|
488
|
+
initialized: true
|
|
489
|
+
};
|
|
490
|
+
};
|
|
948
491
|
return {
|
|
949
492
|
sendMedia: async (filePath, { method = "audio", caption, filename } = {}) => {
|
|
950
|
-
logger?.log("telegram", `sending ${method} reply for chat ${
|
|
493
|
+
logger?.log("telegram", `sending ${method} reply for chat ${route.transportChatId}`);
|
|
951
494
|
const input = new InputFile(filePath, filename || undefined);
|
|
952
|
-
|
|
953
|
-
if (method === "
|
|
954
|
-
if (method === "
|
|
955
|
-
if (method === "
|
|
956
|
-
return bot.api.
|
|
957
|
-
|
|
495
|
+
const options = messageOptions({ caption });
|
|
496
|
+
if (method === "voice") return bot.api.sendVoice(route.transportChatId, input, options);
|
|
497
|
+
if (method === "document") return bot.api.sendDocument(route.transportChatId, input, options);
|
|
498
|
+
if (method === "photo" || method === "image") return bot.api.sendPhoto(route.transportChatId, input, options);
|
|
499
|
+
if (method === "video") return bot.api.sendVideo(route.transportChatId, input, options);
|
|
500
|
+
return bot.api.sendAudio(route.transportChatId, input, options);
|
|
501
|
+
},
|
|
502
|
+
createForumTopic: async (name, context) => {
|
|
503
|
+
if (!route.workspace) throw new Error("Telegram topic creation is only available from the owner workspace forum.");
|
|
504
|
+
await createWorkspaceAccessGuard(route)();
|
|
505
|
+
const topic = await bot.api.createForumTopic(route.transportChatId, name);
|
|
506
|
+
return initializeForumTopic({
|
|
507
|
+
messageThreadId: topic.message_thread_id,
|
|
508
|
+
name: topic.name,
|
|
509
|
+
context
|
|
510
|
+
});
|
|
511
|
+
},
|
|
512
|
+
initializeForumTopic,
|
|
513
|
+
prepareRestartReceipt: (summary) => prepareRestartReceipt({
|
|
514
|
+
transportChatId: route.transportChatId,
|
|
515
|
+
threadId: route.threadId
|
|
516
|
+
}, { reason: String(summary || "Agent-requested restart").trim() }),
|
|
517
|
+
cancelRestartReceipt,
|
|
518
|
+
getTaskContext: () => route.workspace ? {
|
|
519
|
+
transportChatId: route.transportChatId,
|
|
520
|
+
messageThreadId: route.topicThreadId
|
|
521
|
+
} : null
|
|
958
522
|
};
|
|
959
523
|
}
|
|
960
524
|
|
|
@@ -966,7 +530,13 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
966
530
|
: artifact.kind === "video" || artifact.mimeType?.startsWith("video/") ? "video"
|
|
967
531
|
: "document");
|
|
968
532
|
const safeCaption = caption && !/(^|\s)(\/[^\s]|[A-Za-z]:[\\/])/.test(caption) ? caption : undefined;
|
|
969
|
-
await createTelegramSessionBridge(
|
|
533
|
+
await createTelegramSessionBridge({
|
|
534
|
+
workspace: false,
|
|
535
|
+
sessionId: String(chatId),
|
|
536
|
+
scopeChatId: chatId,
|
|
537
|
+
transportChatId: chatId,
|
|
538
|
+
threadId: null
|
|
539
|
+
}).sendMedia(artifact.path, {
|
|
970
540
|
method: resolvedMethod,
|
|
971
541
|
caption: safeCaption,
|
|
972
542
|
filename: path.basename(artifact.path)
|
|
@@ -975,54 +545,63 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
975
545
|
});
|
|
976
546
|
|
|
977
547
|
async function processPromptForChat({ chatId, prompt, ctx = null }) {
|
|
548
|
+
const route = ctx ? contextRoute(ctx) : {
|
|
549
|
+
workspace: false,
|
|
550
|
+
sessionId: String(chatId),
|
|
551
|
+
scopeChatId: chatId,
|
|
552
|
+
transportChatId: chatId,
|
|
553
|
+
threadId: null
|
|
554
|
+
};
|
|
555
|
+
const sessionId = route.sessionId;
|
|
556
|
+
const bridge = createTelegramSessionBridge(route);
|
|
557
|
+
const messageOptions = (extra = {}) => route.workspace && route.threadId
|
|
558
|
+
? { ...extra, message_thread_id: route.threadId }
|
|
559
|
+
: extra;
|
|
978
560
|
const work = async () => {
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
561
|
+
await ensureWorkspaceTopicModelSelection(route);
|
|
562
|
+
if (route.workspace && route.threadId) {
|
|
563
|
+
const handoff = await sessionSeeds.consume(sessionId);
|
|
564
|
+
if (handoff) {
|
|
565
|
+
agentManager.resetSession(sessionId, { handoff });
|
|
566
|
+
await agentManager.waitForSessionClose(sessionId);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
const { session, speedController } = await agentManager.getSessionContext(sessionId, bridge, {
|
|
570
|
+
scopeChatId: route.scopeChatId,
|
|
571
|
+
accessGuard: createWorkspaceAccessGuard(route)
|
|
984
572
|
});
|
|
985
573
|
let text = "";
|
|
986
|
-
|
|
987
|
-
const chatState = getChatState(chatId);
|
|
574
|
+
const chatState = getChatState(sessionId);
|
|
988
575
|
chatState.activeSession = session;
|
|
989
|
-
chatState.
|
|
576
|
+
chatState.activeRoute = route;
|
|
990
577
|
try {
|
|
991
578
|
text = await withPromptSpeed({
|
|
992
579
|
speedController,
|
|
993
580
|
speed: isScheduledTaskPrompt(prompt) ? 1 : undefined,
|
|
994
|
-
restoreSpeed: () => clampModelSpeed(session.model, resolveChatSpeed(config,
|
|
581
|
+
restoreSpeed: () => clampModelSpeed(session.model, resolveChatSpeed(config, sessionId))
|
|
995
582
|
}, () => collectText(session, prompt, {
|
|
996
583
|
logger,
|
|
997
|
-
chatId,
|
|
584
|
+
chatId: sessionId,
|
|
998
585
|
onSlowPrompt: () => bot.api.sendMessage(
|
|
999
|
-
|
|
1000
|
-
"This is taking longer than 5 minutes, so I will keep the current session running instead of starting over. Send /new if you want to abandon it and start fresh."
|
|
586
|
+
route.transportChatId,
|
|
587
|
+
"This is taking longer than 5 minutes, so I will keep the current session running instead of starting over. Send /new if you want to abandon it and start fresh.",
|
|
588
|
+
messageOptions()
|
|
1001
589
|
)
|
|
1002
590
|
}));
|
|
1003
591
|
} catch (error) {
|
|
1004
|
-
agentManager.resetSession(
|
|
592
|
+
agentManager.resetSession(sessionId);
|
|
1005
593
|
throw error;
|
|
1006
594
|
} finally {
|
|
1007
|
-
steeredPrompts = [...chatState.activeSteers];
|
|
1008
595
|
if (chatState.activeSession === session) chatState.activeSession = null;
|
|
1009
|
-
chatState.
|
|
1010
|
-
}
|
|
1011
|
-
if (getChatState(chatId).historyRevision === historyRevision) {
|
|
1012
|
-
const historyPrompt = steeredPrompts.length
|
|
1013
|
-
? [prompt, ...steeredPrompts.map((message) => `[Steering message]\n${message}`)].join("\n\n")
|
|
1014
|
-
: prompt;
|
|
1015
|
-
await conversationHistory.appendTurn(chatId, {
|
|
1016
|
-
runtime: "pi",
|
|
1017
|
-
prompt: historyPrompt,
|
|
1018
|
-
response: text
|
|
1019
|
-
});
|
|
596
|
+
chatState.activeRoute = null;
|
|
1020
597
|
}
|
|
1021
598
|
if (text) {
|
|
599
|
+
await createWorkspaceAccessGuard(route)();
|
|
1022
600
|
await sendTextReply({
|
|
1023
|
-
sendText: (message, extra) => bot.api.sendMessage(
|
|
1024
|
-
sendDocument: (file, extra) => bot.api.sendDocument(
|
|
1025
|
-
chatId,
|
|
601
|
+
sendText: (message, extra) => bot.api.sendMessage(route.transportChatId, message, messageOptions(extra)),
|
|
602
|
+
sendDocument: (file, extra) => bot.api.sendDocument(route.transportChatId, file, messageOptions(extra)),
|
|
603
|
+
chatId: sessionId,
|
|
604
|
+
artifactChatId: route.scopeChatId,
|
|
1026
605
|
text
|
|
1027
606
|
});
|
|
1028
607
|
}
|
|
@@ -1036,11 +615,18 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
1036
615
|
const chatState = getChatState(chatId);
|
|
1037
616
|
|
|
1038
617
|
if (chatState.processing) {
|
|
618
|
+
const incomingRoute = ctx ? contextRoute(ctx) : null;
|
|
619
|
+
const activeRoute = chatState.activeRoute;
|
|
620
|
+
const sameDelivery = !incomingRoute || !activeRoute || (
|
|
621
|
+
incomingRoute.transportChatId === activeRoute.transportChatId
|
|
622
|
+
&& incomingRoute.threadId === activeRoute.threadId
|
|
623
|
+
);
|
|
1039
624
|
const routed = await routeBusyPrompt({
|
|
1040
625
|
chatState,
|
|
1041
626
|
prompt,
|
|
1042
|
-
mode: busyMessageMode,
|
|
1043
|
-
replaceQueued
|
|
627
|
+
mode: sameDelivery ? busyMessageMode : "queue",
|
|
628
|
+
replaceQueued,
|
|
629
|
+
ctx
|
|
1044
630
|
});
|
|
1045
631
|
if (routed.disposition === "steered") {
|
|
1046
632
|
logger?.log("telegram", `chat ${chatId} busy, steering ${label}`);
|
|
@@ -1082,25 +668,27 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
1082
668
|
}
|
|
1083
669
|
|
|
1084
670
|
async function enqueueOrProcess(ctx) {
|
|
1085
|
-
const
|
|
671
|
+
const route = contextRoute(ctx);
|
|
672
|
+
const chatState = getChatState(route.sessionId);
|
|
1086
673
|
|
|
1087
674
|
if (chatState.processing) {
|
|
1088
675
|
await ensureQueuedTelegramTyping(chatState, ctx);
|
|
1089
|
-
const incomingPrompt = await buildIncomingPrompt(ctx);
|
|
676
|
+
const incomingPrompt = await buildIncomingPrompt(ctx, route);
|
|
1090
677
|
const busyMessageMode = typeof ctx.message?.text === "string"
|
|
1091
|
-
? resolveTelegramBusyMessageMode(config,
|
|
678
|
+
? resolveTelegramBusyMessageMode(config, route.sessionId)
|
|
1092
679
|
: "queue";
|
|
1093
680
|
return enqueuePrompt({
|
|
1094
|
-
chatId:
|
|
681
|
+
chatId: route.sessionId,
|
|
1095
682
|
prompt: incomingPrompt,
|
|
1096
683
|
label: `message ${ctx.msg.message_id}`,
|
|
1097
|
-
busyMessageMode
|
|
684
|
+
busyMessageMode,
|
|
685
|
+
ctx
|
|
1098
686
|
});
|
|
1099
687
|
}
|
|
1100
688
|
|
|
1101
|
-
const incomingPrompt = await buildIncomingPrompt(ctx);
|
|
689
|
+
const incomingPrompt = await buildIncomingPrompt(ctx, route);
|
|
1102
690
|
return enqueuePrompt({
|
|
1103
|
-
chatId:
|
|
691
|
+
chatId: route.sessionId,
|
|
1104
692
|
prompt: incomingPrompt,
|
|
1105
693
|
label: `message ${ctx.msg.message_id}`,
|
|
1106
694
|
ctx
|
|
@@ -1117,6 +705,12 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
1117
705
|
logger?.log("telegram", `startup message failed for chat ${chatId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1118
706
|
}
|
|
1119
707
|
}
|
|
708
|
+
try {
|
|
709
|
+
const result = await deliverRestartReceipt((chatId, text, options) => bot.api.sendMessage(chatId, text, options));
|
|
710
|
+
if (result) logger?.log("telegram", `delivered restart receipt ${result.receipt.id}`);
|
|
711
|
+
} catch (error) {
|
|
712
|
+
logger?.log("telegram", `restart receipt delivery failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
713
|
+
}
|
|
1120
714
|
}
|
|
1121
715
|
|
|
1122
716
|
function scheduleStartupMessages({ skipAgentStartupPrompts = false } = {}) {
|
|
@@ -1132,91 +726,48 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
1132
726
|
timer.unref?.();
|
|
1133
727
|
}
|
|
1134
728
|
|
|
1135
|
-
async function enqueueAsyncPrompt({ chatId, prompt, label }) {
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
if (task.kind === "agent_task") {
|
|
1150
|
-
if (!task.payload.prompt) {
|
|
1151
|
-
await taskStore.fail(task.id, "agent_task missing prompt");
|
|
1152
|
-
return;
|
|
1153
|
-
}
|
|
1154
|
-
logger?.log("tasks", `running task ${task.id} for chat ${chatId}`);
|
|
1155
|
-
await enqueueAsyncPrompt({
|
|
1156
|
-
chatId,
|
|
1157
|
-
prompt: await buildAsyncTaskPrompt({ task, artifactStore, toolRegistry, resourceNotes, logger }),
|
|
1158
|
-
label: `scheduled task ${task.id}`
|
|
1159
|
-
});
|
|
1160
|
-
await taskStore.complete(task.id);
|
|
1161
|
-
return;
|
|
729
|
+
async function enqueueAsyncPrompt({ chatId, prompt, label, telegramContext }) {
|
|
730
|
+
let ctx = { chat: { id: chatId }, api: bot.api };
|
|
731
|
+
if (telegramContext?.transportChatId && telegramContext?.messageThreadId) {
|
|
732
|
+
ctx = {
|
|
733
|
+
chat: { id: telegramContext.transportChatId, type: "supergroup", is_forum: true },
|
|
734
|
+
from: { id: chatId },
|
|
735
|
+
message: { message_thread_id: telegramContext.messageThreadId },
|
|
736
|
+
api: bot.api
|
|
737
|
+
};
|
|
738
|
+
const route = await resolveTelegramWorkspaceRoute({ config, api: bot.api, ctx });
|
|
739
|
+
if (!route.ok) throw new Error("Scheduled owner-workspace destination is unavailable.");
|
|
740
|
+
workspaceRoutes.set(ctx, route);
|
|
1162
741
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
if (acknowledgement) {
|
|
1168
|
-
try {
|
|
1169
|
-
await bot.api.sendMessage(chatId, acknowledgement);
|
|
1170
|
-
} catch (error) {
|
|
1171
|
-
logger?.log("telegram", `agent event acknowledgement failed for chat ${chatId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
await enqueueAsyncPrompt({
|
|
1175
|
-
chatId,
|
|
1176
|
-
prompt: await buildAsyncEventPrompt(task, resourceNotes),
|
|
1177
|
-
label: `agent event ${task.id}`
|
|
1178
|
-
});
|
|
1179
|
-
await taskStore.complete(task.id);
|
|
1180
|
-
return;
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
if (task.kind === "poll_tool") {
|
|
1184
|
-
const toolName = task.payload?.toolName;
|
|
1185
|
-
if (!toolName) {
|
|
1186
|
-
await taskStore.fail(task.id, "poll_tool missing toolName");
|
|
1187
|
-
return;
|
|
1188
|
-
}
|
|
1189
|
-
logger?.log("tasks", `polling tool ${toolName} (task ${task.id}) for chat ${chatId}`);
|
|
1190
|
-
try {
|
|
1191
|
-
await agentManager.runTool({
|
|
1192
|
-
name: toolName,
|
|
1193
|
-
request: { args: task.payload.args || {} },
|
|
1194
|
-
chatId
|
|
1195
|
-
});
|
|
1196
|
-
} catch (error) {
|
|
1197
|
-
logger?.log("tasks", `poll_tool ${toolName} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1198
|
-
}
|
|
1199
|
-
await taskStore.complete(task.id);
|
|
1200
|
-
return;
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
|
-
await taskStore.fail(task.id, `Unsupported task: ${task.kind}`);
|
|
742
|
+
const route = contextRoute(ctx);
|
|
743
|
+
const chatState = getChatState(route.sessionId);
|
|
744
|
+
if (chatState.processing) await ensureQueuedTelegramTyping(chatState, ctx);
|
|
745
|
+
return enqueuePrompt({ chatId: route.sessionId, prompt, label, ctx });
|
|
1204
746
|
}
|
|
1205
747
|
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
}
|
|
748
|
+
const { dispatchDueTasks } = createTelegramTaskDispatcher({
|
|
749
|
+
taskStore,
|
|
750
|
+
sendMessage: (chatId, text) => bot.api.sendMessage(chatId, text),
|
|
751
|
+
enqueueAsyncPrompt,
|
|
752
|
+
artifactStore,
|
|
753
|
+
toolRegistry,
|
|
754
|
+
resourceNotes,
|
|
755
|
+
agentManager,
|
|
756
|
+
logger
|
|
757
|
+
});
|
|
1216
758
|
|
|
1217
|
-
async function summarizeSessionBeforeReset(chatId
|
|
759
|
+
async function summarizeSessionBeforeReset(chatId, route = {
|
|
760
|
+
workspace: false,
|
|
761
|
+
sessionId: String(chatId),
|
|
762
|
+
scopeChatId: chatId,
|
|
763
|
+
transportChatId: chatId,
|
|
764
|
+
threadId: null
|
|
765
|
+
}) {
|
|
1218
766
|
try {
|
|
1219
|
-
const context = await agentManager.getSessionContext(chatId, createTelegramSessionBridge(
|
|
767
|
+
const context = await agentManager.getSessionContext(chatId, createTelegramSessionBridge(route), {
|
|
768
|
+
scopeChatId: route.scopeChatId,
|
|
769
|
+
accessGuard: createWorkspaceAccessGuard(route)
|
|
770
|
+
});
|
|
1220
771
|
const parentSession = context.session.sessionFile || "";
|
|
1221
772
|
if (!context.session.messages.length) return { handoff: "", parentSession: "" };
|
|
1222
773
|
|
|
@@ -1229,19 +780,21 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
1229
780
|
}
|
|
1230
781
|
|
|
1231
782
|
async function handleNewCommand(ctx) {
|
|
1232
|
-
const
|
|
783
|
+
const route = contextRoute(ctx);
|
|
784
|
+
const sessionId = route.sessionId;
|
|
785
|
+
const chatState = getChatState(sessionId);
|
|
1233
786
|
const wasProcessing = chatState.processing;
|
|
1234
787
|
chatState.historyRevision += 1;
|
|
1235
788
|
const commandRevision = chatState.historyRevision;
|
|
1236
789
|
const prompt = buildNewSessionPrompt(ctx);
|
|
1237
790
|
|
|
1238
791
|
if (wasProcessing) {
|
|
1239
|
-
logger?.log("telegram", `chat ${
|
|
1240
|
-
queueChatPrompt(chatState, prompt, { replace: true });
|
|
792
|
+
logger?.log("telegram", `chat ${sessionId} busy, queueing new-session command`);
|
|
793
|
+
queueChatPrompt(chatState, prompt, { replace: true, ctx });
|
|
1241
794
|
chatState.continueAfterClose = true;
|
|
1242
795
|
const reset = (async () => {
|
|
1243
|
-
await
|
|
1244
|
-
agentManager.resetSession(
|
|
796
|
+
await sessionSeeds.clear(sessionId);
|
|
797
|
+
agentManager.resetSession(sessionId);
|
|
1245
798
|
})();
|
|
1246
799
|
chatState.beforeNextPrompt = reset;
|
|
1247
800
|
try {
|
|
@@ -1253,21 +806,22 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
1253
806
|
}
|
|
1254
807
|
|
|
1255
808
|
chatState.processing = true;
|
|
1256
|
-
logger?.log("telegram", `processing new-session command in chat ${
|
|
809
|
+
logger?.log("telegram", `processing new-session command in chat ${sessionId}`);
|
|
1257
810
|
await processChatPromptQueue({
|
|
1258
|
-
chatId:
|
|
811
|
+
chatId: sessionId,
|
|
1259
812
|
prompt,
|
|
1260
813
|
label: "new-session command",
|
|
1261
814
|
ctx,
|
|
1262
815
|
beforeInitialPrompt: async () => {
|
|
1263
|
-
const handoff = await withTyping(ctx, () => summarizeSessionBeforeReset(
|
|
816
|
+
const handoff = await withTyping(ctx, () => summarizeSessionBeforeReset(sessionId, route));
|
|
1264
817
|
if (chatState.historyRevision !== commandRevision) return;
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
818
|
+
if (route.workspace && route.threadId) {
|
|
819
|
+
await sessionSeeds.set(sessionId, handoff.handoff);
|
|
820
|
+
} else {
|
|
821
|
+
await sessionSeeds.clear(sessionId);
|
|
822
|
+
}
|
|
1269
823
|
if (chatState.historyRevision !== commandRevision) return;
|
|
1270
|
-
agentManager.resetSession(
|
|
824
|
+
agentManager.resetSession(sessionId, handoff);
|
|
1271
825
|
}
|
|
1272
826
|
});
|
|
1273
827
|
}
|
|
@@ -1278,13 +832,13 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
1278
832
|
});
|
|
1279
833
|
|
|
1280
834
|
bot.command("start", async (ctx) => {
|
|
1281
|
-
const auth = await
|
|
835
|
+
const auth = await authorizeContext(ctx);
|
|
1282
836
|
if (!auth.ok) return;
|
|
1283
837
|
return ctx.reply(auth.firstTime ? "This chat is now authorized for Arisa." : "Arisa is ready.");
|
|
1284
838
|
});
|
|
1285
839
|
|
|
1286
840
|
bot.command("new", async (ctx) => {
|
|
1287
|
-
const auth = await
|
|
841
|
+
const auth = await authorizeContext(ctx);
|
|
1288
842
|
if (!auth.ok) return;
|
|
1289
843
|
if (piAuthIssue) {
|
|
1290
844
|
await ctx.reply(buildPiAuthRecoveryBlockedMessage({
|
|
@@ -1301,7 +855,7 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
1301
855
|
bot.command("restart", handleRestartCommand);
|
|
1302
856
|
|
|
1303
857
|
bot.command("doctor", async (ctx) => {
|
|
1304
|
-
const auth = await
|
|
858
|
+
const auth = await authorizeContext(ctx);
|
|
1305
859
|
if (!auth.ok) return;
|
|
1306
860
|
const pending = await ctx.reply(renderTelegramHtml("```text\nRunning Arisa Doctor…\n```"), { parse_mode: "HTML" });
|
|
1307
861
|
try {
|
|
@@ -1318,11 +872,11 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
1318
872
|
});
|
|
1319
873
|
|
|
1320
874
|
bot.command("update", async (ctx) => {
|
|
1321
|
-
const auth = await
|
|
875
|
+
const auth = await authorizeContext(ctx);
|
|
1322
876
|
if (!auth.ok) return;
|
|
1323
877
|
const pending = await ctx.reply(renderTelegramHtml("```text\nChecking Arisa and official tool updates…\n```"), { parse_mode: "HTML" });
|
|
1324
878
|
try {
|
|
1325
|
-
const report = await checkUpdates(ctx.
|
|
879
|
+
const report = await checkUpdates(contextRoute(ctx).scopeChatId);
|
|
1326
880
|
const picker = buildUpdatePicker(report);
|
|
1327
881
|
await ctx.api.editMessageText(
|
|
1328
882
|
ctx.chat.id,
|
|
@@ -1337,31 +891,31 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
1337
891
|
});
|
|
1338
892
|
|
|
1339
893
|
bot.command("tools", async (ctx) => {
|
|
1340
|
-
const auth = await
|
|
894
|
+
const auth = await authorizeContext(ctx);
|
|
1341
895
|
if (!auth.ok) return;
|
|
1342
|
-
await ctx.reply(renderTelegramHtml(formatToolUsageReport(await toolRegistry.usage(ctx.
|
|
896
|
+
await ctx.reply(renderTelegramHtml(formatToolUsageReport(await toolRegistry.usage(contextRoute(ctx).scopeChatId))), { parse_mode: "HTML" });
|
|
1343
897
|
});
|
|
1344
898
|
|
|
1345
899
|
bot.command("model", async (ctx) => {
|
|
1346
|
-
const auth = await
|
|
900
|
+
const auth = await authorizeContext(ctx);
|
|
1347
901
|
if (!auth.ok) return;
|
|
1348
902
|
await showModelPicker(ctx);
|
|
1349
903
|
});
|
|
1350
904
|
|
|
1351
905
|
bot.command("effort", async (ctx) => {
|
|
1352
|
-
const auth = await
|
|
906
|
+
const auth = await authorizeContext(ctx);
|
|
1353
907
|
if (!auth.ok) return;
|
|
1354
908
|
await showEffortPicker(ctx);
|
|
1355
909
|
});
|
|
1356
910
|
|
|
1357
911
|
bot.command("speed", async (ctx) => {
|
|
1358
|
-
const auth = await
|
|
912
|
+
const auth = await authorizeContext(ctx);
|
|
1359
913
|
if (!auth.ok) return;
|
|
1360
914
|
await showSpeedPicker(ctx);
|
|
1361
915
|
});
|
|
1362
916
|
|
|
1363
917
|
bot.command("auth", async (ctx) => {
|
|
1364
|
-
const auth = await
|
|
918
|
+
const auth = await authorizeContext(ctx);
|
|
1365
919
|
if (!auth.ok) return;
|
|
1366
920
|
|
|
1367
921
|
const status = getPiAuthStatus(config, ctx.chat.id);
|
|
@@ -1393,222 +947,23 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
1393
947
|
}
|
|
1394
948
|
});
|
|
1395
949
|
|
|
950
|
+
const handleModelCallback = createTelegramModelCallbackHandler({
|
|
951
|
+
config,
|
|
952
|
+
authorizeContext,
|
|
953
|
+
contextRoute,
|
|
954
|
+
getChatState,
|
|
955
|
+
getProviderModels,
|
|
956
|
+
showModelPicker,
|
|
957
|
+
showEffortPicker,
|
|
958
|
+
persistChatModel,
|
|
959
|
+
persistChatEffort,
|
|
960
|
+
persistChatSpeed,
|
|
961
|
+
logger
|
|
962
|
+
});
|
|
963
|
+
|
|
1396
964
|
bot.on("callback_query:data", async (ctx, next) => {
|
|
1397
965
|
if (await handleUpdateCallback(ctx)) return;
|
|
1398
|
-
|
|
1399
|
-
const effortAction = modelAction ? null : parseEffortPickerAction(ctx.callbackQuery.data);
|
|
1400
|
-
const speedAction = modelAction || effortAction ? null : parseSpeedPickerAction(ctx.callbackQuery.data);
|
|
1401
|
-
const action = modelAction || effortAction || speedAction;
|
|
1402
|
-
if (!action) return next();
|
|
1403
|
-
if (action.type === "noop") {
|
|
1404
|
-
await ctx.answerCallbackQuery();
|
|
1405
|
-
return;
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
const auth = await authorizeChat({ config, chatId: ctx.chat.id, saveConfig });
|
|
1409
|
-
if (!auth.ok) {
|
|
1410
|
-
await ctx.answerCallbackQuery({ text: "This chat is not authorized.", show_alert: true });
|
|
1411
|
-
return;
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1414
|
-
try {
|
|
1415
|
-
if (action.type === "page") {
|
|
1416
|
-
await showModelPicker(ctx, action.value);
|
|
1417
|
-
await ctx.answerCallbackQuery();
|
|
1418
|
-
return;
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
|
-
const models = await getProviderModels(ctx.chat.id);
|
|
1422
|
-
const chatBusy = getChatState(ctx.chat.id).processing;
|
|
1423
|
-
|
|
1424
|
-
if (action.type === "select") {
|
|
1425
|
-
const model = models[action.value];
|
|
1426
|
-
if (!model) {
|
|
1427
|
-
await ctx.answerCallbackQuery({
|
|
1428
|
-
text: "This model list is no longer current. Run /model again.",
|
|
1429
|
-
show_alert: true
|
|
1430
|
-
});
|
|
1431
|
-
return;
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
// Reasoning models open the effort picker only — no session reset yet.
|
|
1435
|
-
if (modelSupportsThinking(model)) {
|
|
1436
|
-
await showEffortPicker(ctx, {
|
|
1437
|
-
model,
|
|
1438
|
-
modelIndex: action.value,
|
|
1439
|
-
selectedThinkingLevel: clampModelThinkingLevel(model, resolveChatThinkingLevel(config, ctx.chat.id))
|
|
1440
|
-
});
|
|
1441
|
-
await ctx.answerCallbackQuery({ text: `Choose effort for ${model.id}.` });
|
|
1442
|
-
return;
|
|
1443
|
-
}
|
|
1444
|
-
|
|
1445
|
-
if (chatBusy) {
|
|
1446
|
-
await ctx.answerCallbackQuery({
|
|
1447
|
-
text: "Wait for the current response before changing models.",
|
|
1448
|
-
show_alert: true
|
|
1449
|
-
});
|
|
1450
|
-
return;
|
|
1451
|
-
}
|
|
1452
|
-
|
|
1453
|
-
const currentModelId = resolveChatModel(config, ctx.chat.id);
|
|
1454
|
-
const currentEffort = resolveChatThinkingLevel(config, ctx.chat.id);
|
|
1455
|
-
if (model.id === currentModelId && currentEffort === "off") {
|
|
1456
|
-
await closeModelPicker(ctx, {
|
|
1457
|
-
messageText: `Already using ${model.provider}/${model.id}.`,
|
|
1458
|
-
callbackText: `Already using ${model.id}.`
|
|
1459
|
-
});
|
|
1460
|
-
return;
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
|
-
await persistChatModel(ctx.chat.id, model, "off");
|
|
1464
|
-
await ctx.api.editMessageText(
|
|
1465
|
-
ctx.chat.id,
|
|
1466
|
-
ctx.callbackQuery.message.message_id,
|
|
1467
|
-
`Model changed to ${model.provider}/${model.id}.\nA new chat context will start with your next message.`
|
|
1468
|
-
);
|
|
1469
|
-
await ctx.answerCallbackQuery({ text: `Using ${model.id}.` });
|
|
1470
|
-
return;
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
if (action.type === "model-effort") {
|
|
1474
|
-
const model = models[action.modelIndex];
|
|
1475
|
-
if (!model) {
|
|
1476
|
-
await ctx.answerCallbackQuery({
|
|
1477
|
-
text: "This model list is no longer current. Run /model again.",
|
|
1478
|
-
show_alert: true
|
|
1479
|
-
});
|
|
1480
|
-
return;
|
|
1481
|
-
}
|
|
1482
|
-
const levels = listModelThinkingLevels(model);
|
|
1483
|
-
if (!levels.includes(action.level)) {
|
|
1484
|
-
await ctx.answerCallbackQuery({
|
|
1485
|
-
text: "That effort level is not available for this model.",
|
|
1486
|
-
show_alert: true
|
|
1487
|
-
});
|
|
1488
|
-
return;
|
|
1489
|
-
}
|
|
1490
|
-
|
|
1491
|
-
const currentModelId = resolveChatModel(config, ctx.chat.id);
|
|
1492
|
-
const currentEffort = resolveChatThinkingLevel(config, ctx.chat.id);
|
|
1493
|
-
if (model.id === currentModelId && action.level === currentEffort) {
|
|
1494
|
-
await closeModelPicker(ctx, {
|
|
1495
|
-
messageText: `Already using ${model.provider}/${model.id} (effort: ${action.level}).`,
|
|
1496
|
-
callbackText: `Already using ${model.id} at ${action.level}.`
|
|
1497
|
-
});
|
|
1498
|
-
return;
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
// Effort-only updates do not reset the session, so they are safe while busy.
|
|
1502
|
-
if (model.id === currentModelId) {
|
|
1503
|
-
await persistChatEffort(ctx.chat.id, model, action.level);
|
|
1504
|
-
await ctx.api.editMessageText(
|
|
1505
|
-
ctx.chat.id,
|
|
1506
|
-
ctx.callbackQuery.message.message_id,
|
|
1507
|
-
`Effort set to ${action.level} for ${model.provider}/${model.id}.`
|
|
1508
|
-
);
|
|
1509
|
-
await ctx.answerCallbackQuery({ text: `Effort: ${action.level}.` });
|
|
1510
|
-
return;
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
if (chatBusy) {
|
|
1514
|
-
await ctx.answerCallbackQuery({
|
|
1515
|
-
text: "Wait for the current response before changing models.",
|
|
1516
|
-
show_alert: true
|
|
1517
|
-
});
|
|
1518
|
-
return;
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
await persistChatModel(ctx.chat.id, model, action.level);
|
|
1522
|
-
await ctx.api.editMessageText(
|
|
1523
|
-
ctx.chat.id,
|
|
1524
|
-
ctx.callbackQuery.message.message_id,
|
|
1525
|
-
`Model changed to ${model.provider}/${model.id} (effort: ${action.level}).\nA new chat context will start with your next message.`
|
|
1526
|
-
);
|
|
1527
|
-
await ctx.answerCallbackQuery({ text: `Using ${model.id} / ${action.level}.` });
|
|
1528
|
-
return;
|
|
1529
|
-
}
|
|
1530
|
-
|
|
1531
|
-
if (action.type === "effort") {
|
|
1532
|
-
const model = models.find((item) => item.id === resolveChatModel(config, ctx.chat.id));
|
|
1533
|
-
if (!model) {
|
|
1534
|
-
await ctx.answerCallbackQuery({
|
|
1535
|
-
text: "Current model is unavailable. Run /model again.",
|
|
1536
|
-
show_alert: true
|
|
1537
|
-
});
|
|
1538
|
-
return;
|
|
1539
|
-
}
|
|
1540
|
-
if (!modelSupportsThinking(model)) {
|
|
1541
|
-
await ctx.answerCallbackQuery({
|
|
1542
|
-
text: "This model does not support effort levels.",
|
|
1543
|
-
show_alert: true
|
|
1544
|
-
});
|
|
1545
|
-
return;
|
|
1546
|
-
}
|
|
1547
|
-
const levels = listModelThinkingLevels(model);
|
|
1548
|
-
if (!levels.includes(action.level)) {
|
|
1549
|
-
await ctx.answerCallbackQuery({
|
|
1550
|
-
text: "That effort level is not available for this model.",
|
|
1551
|
-
show_alert: true
|
|
1552
|
-
});
|
|
1553
|
-
return;
|
|
1554
|
-
}
|
|
1555
|
-
const currentEffort = resolveChatThinkingLevel(config, ctx.chat.id);
|
|
1556
|
-
if (action.level === currentEffort) {
|
|
1557
|
-
await closeModelPicker(ctx, {
|
|
1558
|
-
messageText: `Already using effort ${action.level} for ${model.provider}/${model.id}.`,
|
|
1559
|
-
callbackText: `Already using effort ${action.level}.`
|
|
1560
|
-
});
|
|
1561
|
-
return;
|
|
1562
|
-
}
|
|
1563
|
-
await persistChatEffort(ctx.chat.id, model, action.level);
|
|
1564
|
-
await ctx.api.editMessageText(
|
|
1565
|
-
ctx.chat.id,
|
|
1566
|
-
ctx.callbackQuery.message.message_id,
|
|
1567
|
-
`Effort set to ${action.level} for ${model.provider}/${model.id}.`
|
|
1568
|
-
);
|
|
1569
|
-
await ctx.answerCallbackQuery({ text: `Effort: ${action.level}.` });
|
|
1570
|
-
return;
|
|
1571
|
-
}
|
|
1572
|
-
|
|
1573
|
-
if (action.type === "speed") {
|
|
1574
|
-
const model = models.find((item) => item.id === resolveChatModel(config, ctx.chat.id));
|
|
1575
|
-
if (!model) {
|
|
1576
|
-
await ctx.answerCallbackQuery({
|
|
1577
|
-
text: "Current model is unavailable. Run /model again.",
|
|
1578
|
-
show_alert: true
|
|
1579
|
-
});
|
|
1580
|
-
return;
|
|
1581
|
-
}
|
|
1582
|
-
if (!modelSupportsSpeed(model)) {
|
|
1583
|
-
await ctx.answerCallbackQuery({
|
|
1584
|
-
text: "This model does not support speed 1.5x.",
|
|
1585
|
-
show_alert: true
|
|
1586
|
-
});
|
|
1587
|
-
return;
|
|
1588
|
-
}
|
|
1589
|
-
const currentSpeed = resolveChatSpeed(config, ctx.chat.id);
|
|
1590
|
-
if (action.speed === currentSpeed) {
|
|
1591
|
-
await closeModelPicker(ctx, {
|
|
1592
|
-
messageText: `Already using speed ${action.speed.toFixed(1)}x for ${model.provider}/${model.id}.`,
|
|
1593
|
-
callbackText: `Already using speed ${action.speed.toFixed(1)}x.`
|
|
1594
|
-
});
|
|
1595
|
-
return;
|
|
1596
|
-
}
|
|
1597
|
-
await persistChatSpeed(ctx.chat.id, model, action.speed);
|
|
1598
|
-
await ctx.api.editMessageText(
|
|
1599
|
-
ctx.chat.id,
|
|
1600
|
-
ctx.callbackQuery.message.message_id,
|
|
1601
|
-
`Speed set to ${action.speed.toFixed(1)}x for ${model.provider}/${model.id}.`
|
|
1602
|
-
);
|
|
1603
|
-
await ctx.answerCallbackQuery({ text: `Speed: ${action.speed.toFixed(1)}x.` });
|
|
1604
|
-
}
|
|
1605
|
-
} catch (error) {
|
|
1606
|
-
logger?.error("telegram", `model selection failed for chat ${ctx.chat.id}: ${getErrorMessage(error)}`);
|
|
1607
|
-
await ctx.answerCallbackQuery({
|
|
1608
|
-
text: "Could not change the model, effort, or speed.",
|
|
1609
|
-
show_alert: true
|
|
1610
|
-
}).catch(() => {});
|
|
1611
|
-
}
|
|
966
|
+
return handleModelCallback(ctx, next);
|
|
1612
967
|
});
|
|
1613
968
|
|
|
1614
969
|
bot.on("message_reaction", async (ctx) => {
|
|
@@ -1631,8 +986,9 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
1631
986
|
});
|
|
1632
987
|
|
|
1633
988
|
bot.on("message", async (ctx) => {
|
|
1634
|
-
const auth = await
|
|
989
|
+
const auth = await authorizeContext(ctx);
|
|
1635
990
|
if (!auth.ok) return;
|
|
991
|
+
if (!isProcessableTelegramMessage(ctx.message)) return;
|
|
1636
992
|
|
|
1637
993
|
const command = getTelegramCommand(ctx);
|
|
1638
994
|
if (command) return;
|