arisa 5.1.49 → 5.1.60
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/AGENTS.md +0 -2
- package/package.json +1 -1
- package/src/core/agent/agent-manager.js +39 -489
- package/src/core/agent/agent-session-lifecycle.js +181 -0
- package/src/core/agent/pi-capability-tools.js +183 -0
- package/src/core/artifacts/artifact-store.js +73 -17
- package/src/core/capabilities/capability-service.js +340 -0
- package/src/core/tasks/task-routing.js +7 -0
- package/src/core/tasks/task-runner.js +53 -0
- package/src/core/tasks/task-store.js +316 -92
- package/src/core/tools/tool-output-materializer.js +5 -5
- package/src/official-tools.lock.json +7 -4
- package/src/runtime/arisa-capabilities.js +51 -242
- package/src/runtime/create-app.js +10 -1
- package/src/runtime/create-headless-app.js +5 -2
- package/src/transport/telegram/bot.js +112 -368
- package/src/transport/telegram/chat-queue.js +72 -6
- package/src/transport/telegram/prompt-builders.js +9 -0
- package/src/transport/telegram/task-dispatcher.js +73 -36
- package/src/transport/telegram/telegram-auth-controller.js +180 -0
- package/src/transport/telegram/telegram-session-bridge.js +170 -0
- package/src/transport/telegram/telegram-tools-command.js +28 -0
- package/src/transport/telegram/telegram-workspace-controller.js +66 -0
- package/test/agent-session-lifecycle.test.js +58 -0
- package/test/artifact-store.test.js +38 -2
- package/test/capabilities-security.test.js +58 -0
- package/test/context-and-task-bounds.test.js +76 -1
- package/test/device-code-message.test.js +9 -0
- package/test/media-caption.test.js +1 -1
- package/test/pi-capability-tools.test.js +65 -0
- package/test/session-start-operational-notes.test.js +1 -1
- package/test/task-idempotency.test.js +40 -0
- package/test/task-routing.test.js +62 -0
- package/test/task-store.test.js +178 -6
- package/test/telegram-task-dispatcher.test.js +99 -23
- package/test/telegram-text-artifact.test.js +13 -2
- package/test/telegram-tools-command.test.js +47 -0
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
import { Bot
|
|
2
|
-
import path from "node:path";
|
|
1
|
+
import { Bot } from "grammy";
|
|
3
2
|
import { authorizeChat } from "./auth.js";
|
|
4
3
|
import { captureIncomingArtifact } from "./media.js";
|
|
5
|
-
import { buildDeviceCodeTelegramMessage } from "./device-code-message.js";
|
|
6
4
|
import { renderTelegramHtml } from "./text-format.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { buildPiAuthTelegramMessage, getErrorMessage, getPiAuthIssue } from "../../core/agent/auth-flow.js";
|
|
6
|
+
import { createTelegramAuthController } from "./telegram-auth-controller.js";
|
|
9
7
|
import { resolveChatSpeed } from "../../core/agent/model-selection.js";
|
|
10
8
|
import { SessionSeedStore } from "../../core/conversation/session-seed-store.js";
|
|
11
9
|
import { formatDoctorReport } from "../../runtime/doctor.js";
|
|
12
|
-
import { formatToolUsageReport } from "../../runtime/tool-usage-report.js";
|
|
13
10
|
import { ToolResourceNoteStore } from "../../core/tools/tool-resource-note-store.js";
|
|
14
11
|
import { formatUpdateReport } from "../../runtime/update-manager.js";
|
|
15
12
|
import { cancelRestartReceipt, deliverRestartReceipt, prepareRestartReceipt } from "../../runtime/restart-receipt.js";
|
|
@@ -17,7 +14,10 @@ import { buildUpdatePicker, createTelegramUpdateCallbackHandler } from "./update
|
|
|
17
14
|
import { createTelegramModelControls } from "./model-controls.js";
|
|
18
15
|
import { createTelegramModelCallbackHandler } from "./model-callback.js";
|
|
19
16
|
import { createTelegramTaskDispatcher } from "./task-dispatcher.js";
|
|
20
|
-
import {
|
|
17
|
+
import { createTelegramSessionBridgeController } from "./telegram-session-bridge.js";
|
|
18
|
+
import { createTelegramToolsCommandHandler } from "./telegram-tools-command.js";
|
|
19
|
+
import { createTelegramWorkspaceController } from "./telegram-workspace-controller.js";
|
|
20
|
+
import { resolveTelegramWorkspaceRoute } from "./workspace-group.js";
|
|
21
21
|
import {
|
|
22
22
|
buildNewSessionPrompt,
|
|
23
23
|
buildPrompt,
|
|
@@ -30,17 +30,18 @@ import {
|
|
|
30
30
|
isSilentReply,
|
|
31
31
|
normalizeIncomingArtifact,
|
|
32
32
|
sanitizeSessionHandoff,
|
|
33
|
+
scheduledPromptSpeedOptions,
|
|
33
34
|
shouldIncludeArtifactReference,
|
|
34
35
|
withPromptSpeed
|
|
35
36
|
} from "./prompt-builders.js";
|
|
36
37
|
import {
|
|
37
38
|
createChatStateStore,
|
|
39
|
+
createPromptExecutionReceipt,
|
|
38
40
|
drainChatPromptQueue,
|
|
39
41
|
queueChatPrompt,
|
|
40
42
|
resolveTelegramBusyMessageMode,
|
|
41
43
|
routeBusyPrompt
|
|
42
44
|
} from "./chat-queue.js";
|
|
43
|
-
|
|
44
45
|
export {
|
|
45
46
|
createChatStateStore,
|
|
46
47
|
drainChatPromptQueue,
|
|
@@ -56,6 +57,7 @@ export {
|
|
|
56
57
|
collectText,
|
|
57
58
|
isScheduledTaskPrompt,
|
|
58
59
|
isSilentReply,
|
|
60
|
+
scheduledPromptSpeedOptions,
|
|
59
61
|
shouldIncludeArtifactReference,
|
|
60
62
|
withPromptSpeed
|
|
61
63
|
} from "./prompt-builders.js";
|
|
@@ -73,6 +75,12 @@ export function isProcessableTelegramMessage(message = {}) {
|
|
|
73
75
|
);
|
|
74
76
|
}
|
|
75
77
|
|
|
78
|
+
export function resolveIncomingBusyMessageMode({ config, route, message }) {
|
|
79
|
+
if (route?.workspace) return "queue";
|
|
80
|
+
if (typeof message?.text !== "string") return "queue";
|
|
81
|
+
return resolveTelegramBusyMessageMode(config, route?.sessionId);
|
|
82
|
+
}
|
|
83
|
+
|
|
76
84
|
export function buildTopicInitializationHandoff({ name, context }) {
|
|
77
85
|
return [
|
|
78
86
|
`Telegram topic: ${String(name || "").trim()}`,
|
|
@@ -167,11 +175,12 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
167
175
|
const perChatState = createChatStateStore();
|
|
168
176
|
const sessionSeeds = new SessionSeedStore();
|
|
169
177
|
const notifiedPromptErrors = new WeakSet();
|
|
170
|
-
const authRenewals = new Map();
|
|
171
|
-
const workspaceRoutes = new WeakMap();
|
|
172
|
-
const workspaceGateStates = new Map();
|
|
173
|
-
let piAuthIssue = null;
|
|
174
178
|
let taskTimer = null;
|
|
179
|
+
const { authorizeContext, contextRoute, registerRoute } = createTelegramWorkspaceController({
|
|
180
|
+
config,
|
|
181
|
+
api: bot.api,
|
|
182
|
+
saveConfig
|
|
183
|
+
});
|
|
175
184
|
|
|
176
185
|
const requestRestartWithReceipt = async (ctx, reason = "Telegram restart") => {
|
|
177
186
|
const route = contextRoute(ctx);
|
|
@@ -211,152 +220,13 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
211
220
|
if (error instanceof Error) notifiedPromptErrors.add(error);
|
|
212
221
|
}
|
|
213
222
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const issue = rememberPiAuthIssue(error);
|
|
222
|
-
if (!issue) return false;
|
|
223
|
-
|
|
224
|
-
try {
|
|
225
|
-
await bot.api.sendMessage(chatId, buildPiAuthTelegramMessage({ config, chatId, issue }));
|
|
226
|
-
markPromptErrorNotified(error);
|
|
227
|
-
return true;
|
|
228
|
-
} catch (notifyError) {
|
|
229
|
-
logger?.error("telegram", `auth issue notice failed for chat ${chatId}: ${getErrorMessage(notifyError)}`);
|
|
230
|
-
return false;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function selectTelegramLoginOption(options = []) {
|
|
235
|
-
return options.find((option) => /device/i.test(`${option.id} ${option.label}`))
|
|
236
|
-
|| options.find((option) => /browser|oauth|web/i.test(`${option.id} ${option.label}`))
|
|
237
|
-
|| options[0]
|
|
238
|
-
|| null;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
async function finishAuthRenewal(chatId, renewal) {
|
|
242
|
-
try {
|
|
243
|
-
await renewal.promise;
|
|
244
|
-
await agentManager.validateAgent();
|
|
245
|
-
agentManager.clearSessionCache(chatId);
|
|
246
|
-
piAuthIssue = null;
|
|
247
|
-
logger?.log("telegram", `Pi auth renewal completed for chat ${chatId}`);
|
|
248
|
-
await bot.api.sendMessage(chatId, buildPiAuthTelegramMessage({ config, chatId, verified: true }));
|
|
249
|
-
} catch (error) {
|
|
250
|
-
const issue = rememberPiAuthIssue(error) || { kind: "validation-failed", message: getErrorMessage(error) };
|
|
251
|
-
piAuthIssue = issue;
|
|
252
|
-
logger?.error("telegram", `Pi auth renewal failed for chat ${chatId}: ${getErrorMessage(error)}`);
|
|
253
|
-
await bot.api.sendMessage(chatId, buildPiAuthTelegramMessage({ config, chatId, issue })).catch((notifyError) => {
|
|
254
|
-
logger?.error("telegram", `auth renewal failure notice failed for chat ${chatId}: ${getErrorMessage(notifyError)}`);
|
|
255
|
-
});
|
|
256
|
-
} finally {
|
|
257
|
-
authRenewals.delete(chatKey(chatId));
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
async function startAuthRenewal(chatId) {
|
|
262
|
-
const key = chatKey(chatId);
|
|
263
|
-
const existing = authRenewals.get(key);
|
|
264
|
-
if (existing) {
|
|
265
|
-
return { started: false, renewal: existing };
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
const renewal = createPiOAuthLogin({
|
|
269
|
-
provider: config.pi.provider,
|
|
270
|
-
onSelect: async ({ message, options }) => {
|
|
271
|
-
const selected = selectTelegramLoginOption(options);
|
|
272
|
-
if (!selected) return undefined;
|
|
273
|
-
logger?.log("telegram", `Pi auth option for chat ${chatId}: ${selected.id}`);
|
|
274
|
-
await bot.api.sendMessage(chatId, `${message}\nUsing: ${selected.label || selected.id}`);
|
|
275
|
-
return selected.id;
|
|
276
|
-
},
|
|
277
|
-
onAuth: async ({ url, instructions }) => {
|
|
278
|
-
await bot.api.sendMessage(chatId, [
|
|
279
|
-
instructions || "Open this URL to continue Pi authentication:",
|
|
280
|
-
url,
|
|
281
|
-
"After login, paste the full redirect URL back here."
|
|
282
|
-
].join("\n"));
|
|
283
|
-
},
|
|
284
|
-
onDeviceCode: async ({ userCode, verificationUri, expiresInSeconds }) => {
|
|
285
|
-
const payload = buildDeviceCodeTelegramMessage({ userCode, verificationUri, expiresInSeconds });
|
|
286
|
-
const { text, ...options } = payload;
|
|
287
|
-
await bot.api.sendMessage(chatId, text, options);
|
|
288
|
-
},
|
|
289
|
-
onPrompt: async ({ message, controller }) => {
|
|
290
|
-
await bot.api.sendMessage(chatId, `${message}\nReply here with the value.`);
|
|
291
|
-
return controller.waitForManualCode();
|
|
292
|
-
},
|
|
293
|
-
onProgress: (message) => {
|
|
294
|
-
if (message) logger?.log("telegram", `Pi auth progress for chat ${chatId}: ${message}`);
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
authRenewals.set(key, renewal);
|
|
299
|
-
finishAuthRenewal(chatId, renewal);
|
|
300
|
-
return { started: true, renewal };
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
async function submitAuthRenewalInput(ctx) {
|
|
304
|
-
const renewal = authRenewals.get(chatKey(ctx.chat.id));
|
|
305
|
-
const text = getIncomingMessageText(ctx.message).trim();
|
|
306
|
-
if (!renewal || !renewal.manualInputRequested || !text) return false;
|
|
307
|
-
|
|
308
|
-
if (!renewal.submitManualCode(text)) return false;
|
|
309
|
-
await ctx.reply("Got it. Finishing Pi login now...");
|
|
310
|
-
return true;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
function getIncomingChatMeta(ctx) {
|
|
314
|
-
return {
|
|
315
|
-
languageCode: ctx.from?.language_code || "",
|
|
316
|
-
username: ctx.from?.username || "",
|
|
317
|
-
firstName: ctx.from?.first_name || "",
|
|
318
|
-
lastName: ctx.from?.last_name || ""
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
|
|
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;
|
|
328
|
-
}
|
|
329
|
-
|
|
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(() => {});
|
|
336
|
-
}
|
|
337
|
-
return { ok: false, reason: route.reason || "workspace-locked" };
|
|
338
|
-
}
|
|
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(() => {});
|
|
346
|
-
}
|
|
347
|
-
return { ok: true, firstTime: false, workspace: true };
|
|
348
|
-
}
|
|
349
|
-
|
|
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
|
-
};
|
|
359
|
-
}
|
|
223
|
+
const authController = createTelegramAuthController({
|
|
224
|
+
config,
|
|
225
|
+
api: bot.api,
|
|
226
|
+
agentManager,
|
|
227
|
+
logger,
|
|
228
|
+
markPromptErrorNotified
|
|
229
|
+
});
|
|
360
230
|
|
|
361
231
|
function getChatState(chatId) {
|
|
362
232
|
return perChatState.get(chatId);
|
|
@@ -409,142 +279,24 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
409
279
|
return buildPrompt({ ctx, artifact, transcript, toolResult });
|
|
410
280
|
}
|
|
411
281
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
kind: "document",
|
|
427
|
-
mimeType: "text/markdown",
|
|
428
|
-
source: { type: "assistant", chatId },
|
|
429
|
-
metadata: { delivery: "telegram-document" }
|
|
430
|
-
});
|
|
431
|
-
await sendDocument(new InputFile(artifact.path, path.basename(artifact.path)), {
|
|
432
|
-
caption: "Response attached as Markdown."
|
|
433
|
-
});
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
logger?.log("telegram", `sending text reply for chat ${chatId}`);
|
|
438
|
-
const sent = await sendText(renderTelegramHtml(text), { parse_mode: "HTML" });
|
|
439
|
-
if (sent?.message_id) {
|
|
440
|
-
const messages = getChatState(chatId).assistantMessages;
|
|
441
|
-
messages.set(sent.message_id, text);
|
|
442
|
-
while (messages.size > 50) messages.delete(messages.keys().next().value);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
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
|
-
};
|
|
491
|
-
return {
|
|
492
|
-
sendMedia: async (filePath, { method = "audio", caption, filename } = {}) => {
|
|
493
|
-
logger?.log("telegram", `sending ${method} reply for chat ${route.transportChatId}`);
|
|
494
|
-
const input = new InputFile(filePath, filename || undefined);
|
|
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
|
|
522
|
-
};
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
agentManager.setArtifactDeliveryHandler?.(async ({ chatId, artifact, caption, method }) => {
|
|
526
|
-
const resolvedMethod = method
|
|
527
|
-
|| artifact.metadata?.delivery?.method
|
|
528
|
-
|| (artifact.kind === "audio" || artifact.mimeType?.startsWith("audio/") ? "audio"
|
|
529
|
-
: artifact.kind === "image" || artifact.mimeType?.startsWith("image/") ? "photo"
|
|
530
|
-
: artifact.kind === "video" || artifact.mimeType?.startsWith("video/") ? "video"
|
|
531
|
-
: "document");
|
|
532
|
-
const safeCaption = caption && !/(^|\s)(\/[^\s]|[A-Za-z]:[\\/])/.test(caption) ? caption : undefined;
|
|
533
|
-
await createTelegramSessionBridge({
|
|
534
|
-
workspace: false,
|
|
535
|
-
sessionId: String(chatId),
|
|
536
|
-
scopeChatId: chatId,
|
|
537
|
-
transportChatId: chatId,
|
|
538
|
-
threadId: null
|
|
539
|
-
}).sendMedia(artifact.path, {
|
|
540
|
-
method: resolvedMethod,
|
|
541
|
-
caption: safeCaption,
|
|
542
|
-
filename: path.basename(artifact.path)
|
|
543
|
-
});
|
|
544
|
-
return { ok: true, artifactId: artifact.id, method: resolvedMethod };
|
|
282
|
+
const {
|
|
283
|
+
createSessionBridge: createTelegramSessionBridge,
|
|
284
|
+
createWorkspaceAccessGuard,
|
|
285
|
+
installArtifactDeliveryHandler,
|
|
286
|
+
sendTextReply
|
|
287
|
+
} = createTelegramSessionBridgeController({
|
|
288
|
+
config,
|
|
289
|
+
api: bot.api,
|
|
290
|
+
agentManager,
|
|
291
|
+
artifactStore,
|
|
292
|
+
sessionSeeds,
|
|
293
|
+
getChatState,
|
|
294
|
+
buildTopicInitializationHandoff,
|
|
295
|
+
logger
|
|
545
296
|
});
|
|
297
|
+
installArtifactDeliveryHandler();
|
|
546
298
|
|
|
547
|
-
async function processPromptForChat({ chatId, prompt, ctx = null }) {
|
|
299
|
+
async function processPromptForChat({ chatId, prompt, ctx = null, executionReceipt = null }) {
|
|
548
300
|
const route = ctx ? contextRoute(ctx) : {
|
|
549
301
|
workspace: false,
|
|
550
302
|
sessionId: String(chatId),
|
|
@@ -575,11 +327,12 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
575
327
|
chatState.activeSession = session;
|
|
576
328
|
chatState.activeRoute = route;
|
|
577
329
|
try {
|
|
578
|
-
text = await withPromptSpeed({
|
|
330
|
+
text = await withPromptSpeed(scheduledPromptSpeedOptions({
|
|
331
|
+
prompt,
|
|
332
|
+
session,
|
|
579
333
|
speedController,
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
}, () => collectText(session, prompt, {
|
|
334
|
+
configuredSpeed: resolveChatSpeed(config, sessionId)
|
|
335
|
+
}), () => collectText(session, prompt, {
|
|
583
336
|
logger,
|
|
584
337
|
chatId: sessionId,
|
|
585
338
|
onSlowPrompt: () => bot.api.sendMessage(
|
|
@@ -590,11 +343,16 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
590
343
|
}));
|
|
591
344
|
} catch (error) {
|
|
592
345
|
agentManager.resetSession(sessionId);
|
|
346
|
+
if (error && typeof error === "object") {
|
|
347
|
+
error.retryable = false;
|
|
348
|
+
error.outcomeUncertain = true;
|
|
349
|
+
}
|
|
593
350
|
throw error;
|
|
594
351
|
} finally {
|
|
595
352
|
if (chatState.activeSession === session) chatState.activeSession = null;
|
|
596
353
|
chatState.activeRoute = null;
|
|
597
354
|
}
|
|
355
|
+
executionReceipt?.resolve({ status: "executed" });
|
|
598
356
|
if (text) {
|
|
599
357
|
await createWorkspaceAccessGuard(route)();
|
|
600
358
|
await sendTextReply({
|
|
@@ -611,8 +369,18 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
611
369
|
return work();
|
|
612
370
|
}
|
|
613
371
|
|
|
614
|
-
async function enqueuePrompt({
|
|
372
|
+
async function enqueuePrompt({
|
|
373
|
+
chatId,
|
|
374
|
+
prompt,
|
|
375
|
+
label,
|
|
376
|
+
ctx = null,
|
|
377
|
+
replaceQueued = false,
|
|
378
|
+
busyMessageMode = "queue",
|
|
379
|
+
waitForExecution = false,
|
|
380
|
+
coalesceQueued = false
|
|
381
|
+
}) {
|
|
615
382
|
const chatState = getChatState(chatId);
|
|
383
|
+
const receipt = waitForExecution ? createPromptExecutionReceipt() : null;
|
|
616
384
|
|
|
617
385
|
if (chatState.processing) {
|
|
618
386
|
const incomingRoute = ctx ? contextRoute(ctx) : null;
|
|
@@ -626,10 +394,14 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
626
394
|
prompt,
|
|
627
395
|
mode: sameDelivery ? busyMessageMode : "queue",
|
|
628
396
|
replaceQueued,
|
|
629
|
-
ctx
|
|
397
|
+
ctx,
|
|
398
|
+
receipt,
|
|
399
|
+
coalesceQueued
|
|
630
400
|
});
|
|
631
401
|
if (routed.disposition === "steered") {
|
|
632
402
|
logger?.log("telegram", `chat ${chatId} busy, steering ${label}`);
|
|
403
|
+
} else if (routed.disposition === "coalesced") {
|
|
404
|
+
logger?.log("telegram", `chat ${chatId} busy, coalescing ${label} into pending direct turn`);
|
|
633
405
|
} else {
|
|
634
406
|
logger?.log("telegram", `chat ${chatId} busy, queueing ${label}`);
|
|
635
407
|
if (routed.steerError) {
|
|
@@ -637,24 +409,28 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
637
409
|
}
|
|
638
410
|
}
|
|
639
411
|
if (replaceQueued) chatState.continueAfterClose = true;
|
|
640
|
-
return;
|
|
412
|
+
return receipt ? receipt.promise : undefined;
|
|
641
413
|
}
|
|
642
414
|
|
|
643
415
|
chatState.processing = true;
|
|
644
416
|
logger?.log("telegram", `processing ${label} in chat ${chatId}`);
|
|
645
|
-
|
|
417
|
+
const draining = processChatPromptQueue({ chatId, prompt, label, ctx, initialReceipt: receipt });
|
|
418
|
+
if (!receipt) return draining;
|
|
419
|
+
draining.catch(() => {});
|
|
420
|
+
return receipt.promise;
|
|
646
421
|
}
|
|
647
422
|
|
|
648
|
-
function processChatPromptQueue({ chatId, prompt, label, ctx = null, beforeInitialPrompt }) {
|
|
423
|
+
function processChatPromptQueue({ chatId, prompt, label, ctx = null, beforeInitialPrompt, initialReceipt = null }) {
|
|
649
424
|
const chatState = getChatState(chatId);
|
|
650
425
|
return drainChatPromptQueue({
|
|
651
426
|
chatState,
|
|
652
427
|
initialPrompt: prompt,
|
|
653
428
|
initialCtx: ctx,
|
|
429
|
+
initialReceipt,
|
|
654
430
|
beforeInitialPrompt,
|
|
655
|
-
processPrompt: ({ prompt: currentPrompt, ctx: currentCtx }) => {
|
|
431
|
+
processPrompt: ({ prompt: currentPrompt, ctx: currentCtx, receipt }) => {
|
|
656
432
|
logger?.log("telegram", `prompt dispatch for chat ${chatId}`);
|
|
657
|
-
return processPromptForChat({ chatId, prompt: currentPrompt, ctx: currentCtx });
|
|
433
|
+
return processPromptForChat({ chatId, prompt: currentPrompt, ctx: currentCtx, executionReceipt: receipt });
|
|
658
434
|
},
|
|
659
435
|
onPromptInterrupted: (error) => {
|
|
660
436
|
logger?.log("telegram", `${label} interrupted by queued /new for chat ${chatId}: ${getErrorMessage(error)}`);
|
|
@@ -662,7 +438,7 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
662
438
|
onPromptFailure: async (error) => {
|
|
663
439
|
const message = getErrorMessage(error);
|
|
664
440
|
logger?.error("telegram", `${label} failed for chat ${chatId}: ${message}`);
|
|
665
|
-
await
|
|
441
|
+
await authController.notifyIssueIfNeeded(chatId, error);
|
|
666
442
|
}
|
|
667
443
|
});
|
|
668
444
|
}
|
|
@@ -674,14 +450,17 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
674
450
|
if (chatState.processing) {
|
|
675
451
|
await ensureQueuedTelegramTyping(chatState, ctx);
|
|
676
452
|
const incomingPrompt = await buildIncomingPrompt(ctx, route);
|
|
677
|
-
const busyMessageMode =
|
|
678
|
-
|
|
679
|
-
|
|
453
|
+
const busyMessageMode = resolveIncomingBusyMessageMode({
|
|
454
|
+
config,
|
|
455
|
+
route,
|
|
456
|
+
message: ctx.message
|
|
457
|
+
});
|
|
680
458
|
return enqueuePrompt({
|
|
681
459
|
chatId: route.sessionId,
|
|
682
460
|
prompt: incomingPrompt,
|
|
683
461
|
label: `message ${ctx.msg.message_id}`,
|
|
684
462
|
busyMessageMode,
|
|
463
|
+
coalesceQueued: busyMessageMode === "steer" && typeof ctx.message?.text === "string",
|
|
685
464
|
ctx
|
|
686
465
|
});
|
|
687
466
|
}
|
|
@@ -726,28 +505,29 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
726
505
|
timer.unref?.();
|
|
727
506
|
}
|
|
728
507
|
|
|
729
|
-
async function enqueueAsyncPrompt({ chatId, prompt, label,
|
|
508
|
+
async function enqueueAsyncPrompt({ chatId, prompt, label, route: taskRoute }) {
|
|
730
509
|
let ctx = { chat: { id: chatId }, api: bot.api };
|
|
731
|
-
|
|
510
|
+
const destination = taskRoute?.transport === "telegram" ? taskRoute.destination : null;
|
|
511
|
+
if (destination?.chatId && destination?.threadId) {
|
|
732
512
|
ctx = {
|
|
733
|
-
chat: { id:
|
|
513
|
+
chat: { id: destination.chatId, type: "supergroup", is_forum: true },
|
|
734
514
|
from: { id: chatId },
|
|
735
|
-
message: { message_thread_id:
|
|
515
|
+
message: { message_thread_id: destination.threadId },
|
|
736
516
|
api: bot.api
|
|
737
517
|
};
|
|
738
518
|
const route = await resolveTelegramWorkspaceRoute({ config, api: bot.api, ctx });
|
|
739
519
|
if (!route.ok) throw new Error("Scheduled owner-workspace destination is unavailable.");
|
|
740
|
-
|
|
520
|
+
registerRoute(ctx, route);
|
|
741
521
|
}
|
|
742
522
|
const route = contextRoute(ctx);
|
|
743
523
|
const chatState = getChatState(route.sessionId);
|
|
744
524
|
if (chatState.processing) await ensureQueuedTelegramTyping(chatState, ctx);
|
|
745
|
-
return enqueuePrompt({ chatId: route.sessionId, prompt, label, ctx });
|
|
525
|
+
return enqueuePrompt({ chatId: route.sessionId, prompt, label, ctx, waitForExecution: true });
|
|
746
526
|
}
|
|
747
527
|
|
|
748
528
|
const { dispatchDueTasks } = createTelegramTaskDispatcher({
|
|
749
529
|
taskStore,
|
|
750
|
-
sendMessage: (chatId, text) => bot.api.sendMessage(chatId, text),
|
|
530
|
+
sendMessage: (chatId, text, options) => bot.api.sendMessage(chatId, text, options),
|
|
751
531
|
enqueueAsyncPrompt,
|
|
752
532
|
artifactStore,
|
|
753
533
|
toolRegistry,
|
|
@@ -840,13 +620,8 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
840
620
|
bot.command("new", async (ctx) => {
|
|
841
621
|
const auth = await authorizeContext(ctx);
|
|
842
622
|
if (!auth.ok) return;
|
|
843
|
-
if (
|
|
844
|
-
await ctx.reply(
|
|
845
|
-
config,
|
|
846
|
-
chatId: ctx.chat.id,
|
|
847
|
-
issue: piAuthIssue,
|
|
848
|
-
renewalActive: authRenewals.has(chatKey(ctx.chat.id))
|
|
849
|
-
}));
|
|
623
|
+
if (authController.getIssue()) {
|
|
624
|
+
await ctx.reply(authController.buildBlockedMessage(ctx.chat.id));
|
|
850
625
|
return;
|
|
851
626
|
}
|
|
852
627
|
await handleNewCommand(ctx);
|
|
@@ -890,11 +665,13 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
890
665
|
}
|
|
891
666
|
});
|
|
892
667
|
|
|
893
|
-
bot.command("tools",
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
668
|
+
bot.command("tools", createTelegramToolsCommandHandler({
|
|
669
|
+
authorize: authorizeContext,
|
|
670
|
+
contextRoute,
|
|
671
|
+
toolRegistry,
|
|
672
|
+
withTyping,
|
|
673
|
+
logger
|
|
674
|
+
}));
|
|
898
675
|
|
|
899
676
|
bot.command("model", async (ctx) => {
|
|
900
677
|
const auth = await authorizeContext(ctx);
|
|
@@ -914,38 +691,10 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
914
691
|
await showSpeedPicker(ctx);
|
|
915
692
|
});
|
|
916
693
|
|
|
917
|
-
bot.command("auth",
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
const status = getPiAuthStatus(config, ctx.chat.id);
|
|
922
|
-
if (status.hasApiKey || !status.supportsOAuth) {
|
|
923
|
-
await withTyping(ctx, async () => {
|
|
924
|
-
try {
|
|
925
|
-
await agentManager.validateAgent();
|
|
926
|
-
agentManager.clearSessionCache(ctx.chat.id);
|
|
927
|
-
piAuthIssue = null;
|
|
928
|
-
await ctx.reply(buildPiAuthTelegramMessage({ config, chatId: ctx.chat.id, verified: true }));
|
|
929
|
-
} catch (error) {
|
|
930
|
-
const issue = rememberPiAuthIssue(error) || { kind: "validation-failed", message: getErrorMessage(error) };
|
|
931
|
-
piAuthIssue = issue;
|
|
932
|
-
await ctx.reply(buildPiAuthTelegramMessage({ config, chatId: ctx.chat.id, issue }));
|
|
933
|
-
}
|
|
934
|
-
});
|
|
935
|
-
return;
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
try {
|
|
939
|
-
const { started } = await startAuthRenewal(ctx.chat.id);
|
|
940
|
-
await ctx.reply(started
|
|
941
|
-
? "Starting Pi login from Telegram..."
|
|
942
|
-
: "Pi login is already in progress. Paste the redirect URL or code here when you have it.");
|
|
943
|
-
} catch (error) {
|
|
944
|
-
const issue = rememberPiAuthIssue(error) || { kind: "validation-failed", message: getErrorMessage(error) };
|
|
945
|
-
piAuthIssue = issue;
|
|
946
|
-
await ctx.reply(buildPiAuthTelegramMessage({ config, chatId: ctx.chat.id, issue }));
|
|
947
|
-
}
|
|
948
|
-
});
|
|
694
|
+
bot.command("auth", (ctx) => authController.handleCommand(ctx, {
|
|
695
|
+
authorize: authorizeContext,
|
|
696
|
+
withTyping
|
|
697
|
+
}));
|
|
949
698
|
|
|
950
699
|
const handleModelCallback = createTelegramModelCallbackHandler({
|
|
951
700
|
config,
|
|
@@ -970,7 +719,7 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
970
719
|
const reaction = ctx.messageReaction;
|
|
971
720
|
const chatId = reaction.chat.id;
|
|
972
721
|
const auth = await authorizeChat({ config, chatId, saveConfig });
|
|
973
|
-
if (!auth.ok ||
|
|
722
|
+
if (!auth.ok || authController.getIssue()) return;
|
|
974
723
|
|
|
975
724
|
const reactedMessageText = getChatState(chatId).assistantMessages.get(reaction.message_id) || "";
|
|
976
725
|
const prompt = buildReactionPrompt({ reaction, reactedMessageText });
|
|
@@ -993,15 +742,10 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
993
742
|
const command = getTelegramCommand(ctx);
|
|
994
743
|
if (command) return;
|
|
995
744
|
|
|
996
|
-
if (await
|
|
745
|
+
if (await authController.submitRenewalInput(ctx)) return;
|
|
997
746
|
|
|
998
|
-
if (
|
|
999
|
-
await ctx.reply(
|
|
1000
|
-
config,
|
|
1001
|
-
chatId: ctx.chat.id,
|
|
1002
|
-
issue: piAuthIssue,
|
|
1003
|
-
renewalActive: authRenewals.has(chatKey(ctx.chat.id))
|
|
1004
|
-
}));
|
|
747
|
+
if (authController.getIssue()) {
|
|
748
|
+
await ctx.reply(authController.buildBlockedMessage(ctx.chat.id));
|
|
1005
749
|
return;
|
|
1006
750
|
}
|
|
1007
751
|
|
|
@@ -1047,7 +791,7 @@ export async function createTelegramBot({ config, artifactStore, toolRegistry, t
|
|
|
1047
791
|
async notifyPiAuthIssue(error) {
|
|
1048
792
|
let notified = false;
|
|
1049
793
|
for (const chatId of config.telegram.authorizedChatIds || []) {
|
|
1050
|
-
notified = await
|
|
794
|
+
notified = await authController.notifyIssueIfNeeded(chatId, error) || notified;
|
|
1051
795
|
}
|
|
1052
796
|
return notified;
|
|
1053
797
|
}
|