@markusylisiurunen/tau 0.2.63 → 0.2.65
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/dist/core/async/cli.js +68 -104
- package/dist/core/async/cli.js.map +1 -1
- package/dist/core/async/http_protocol.js +119 -8
- package/dist/core/async/http_protocol.js.map +1 -1
- package/dist/core/async/http_server.js +120 -240
- package/dist/core/async/http_server.js.map +1 -1
- package/dist/core/async/index.js +1 -1
- package/dist/core/async/index.js.map +1 -1
- package/dist/core/async/server_config.js +161 -356
- package/dist/core/async/server_config.js.map +1 -1
- package/dist/core/async/session_manager.js +5 -17
- package/dist/core/async/session_manager.js.map +1 -1
- package/dist/core/async/telegram.js +268 -171
- package/dist/core/async/telegram.js.map +1 -1
- package/dist/core/auth/providers/openai_codex.js +4 -9
- package/dist/core/auth/providers/openai_codex.js.map +1 -1
- package/dist/core/config/content_loader.js +57 -205
- package/dist/core/config/content_loader.js.map +1 -1
- package/dist/core/config/markdown_frontmatter.js +34 -0
- package/dist/core/config/markdown_frontmatter.js.map +1 -0
- package/dist/core/config/schema.js +266 -332
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/config/skill_parser.js +8 -32
- package/dist/core/config/skill_parser.js.map +1 -1
- package/dist/core/config/skills_loader.js +32 -18
- package/dist/core/config/skills_loader.js.map +1 -1
- package/dist/core/events/index.js +1 -0
- package/dist/core/events/index.js.map +1 -1
- package/dist/core/events/parser.js +115 -0
- package/dist/core/events/parser.js.map +1 -0
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/modes/rpc_protocol.js +249 -189
- package/dist/core/modes/rpc_protocol.js.map +1 -1
- package/dist/core/session/session_engine.js +1 -3
- package/dist/core/session/session_engine.js.map +1 -1
- package/dist/core/subagents/control_plane.js +13 -2
- package/dist/core/subagents/control_plane.js.map +1 -1
- package/dist/core/subagents/subagent_engine.js +3 -3
- package/dist/core/subagents/subagent_engine.js.map +1 -1
- package/dist/core/tools/emit_output.js +3 -2
- package/dist/core/tools/emit_output.js.map +1 -1
- package/dist/core/tools/registry.js +6 -0
- package/dist/core/tools/registry.js.map +1 -1
- package/dist/core/tools/send_input_to_agent.js +12 -16
- package/dist/core/tools/send_input_to_agent.js.map +1 -1
- package/dist/core/tools/spawn_agent.js +25 -30
- package/dist/core/tools/spawn_agent.js.map +1 -1
- package/dist/core/tools/terminate_agent.js +10 -14
- package/dist/core/tools/terminate_agent.js.map +1 -1
- package/dist/core/tools/wait_for_agent.js +10 -14
- package/dist/core/tools/wait_for_agent.js.map +1 -1
- package/dist/core/utils/mistral_transcription.js +13 -21
- package/dist/core/utils/mistral_transcription.js.map +1 -1
- package/dist/core/utils/parallel_api.js +15 -19
- package/dist/core/utils/parallel_api.js.map +1 -1
- package/dist/core/utils/zod.js +7 -0
- package/dist/core/utils/zod.js.map +1 -1
- package/dist/core/version.js +1 -1
- package/dist/tui/chat_controller/session_maintenance_service.js +98 -171
- package/dist/tui/chat_controller/session_maintenance_service.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { basename, extname, join } from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
4
5
|
import { transcribeMistralAudio } from "../utils/mistral_transcription.js";
|
|
5
|
-
import {
|
|
6
|
+
import { formatZodError } from "../utils/zod.js";
|
|
6
7
|
import { AsyncSessionManagerError, createScopedAsyncSessionManager, } from "./session_manager.js";
|
|
7
8
|
const DEFAULT_POLL_INTERVAL_MS = 1000;
|
|
8
9
|
const DEFAULT_REQUEST_TIMEOUT_SECONDS = 30;
|
|
@@ -23,6 +24,7 @@ const MAX_TELEGRAM_ATTACHMENTS_PER_TURN = 10;
|
|
|
23
24
|
const MAX_TELEGRAM_ATTACHMENT_FILE_BYTES = 20 * 1024 * 1024;
|
|
24
25
|
const MAX_TELEGRAM_ATTACHMENT_TOTAL_BYTES = 50 * 1024 * 1024;
|
|
25
26
|
const TELEGRAM_ATTACHMENT_TEMP_DIR_PREFIX = "tau-telegram-attachments-";
|
|
27
|
+
const NO_ACTIVE_SESSION_MESSAGE = "no active session. use /new or /sessions";
|
|
26
28
|
const SUPPORTED_TEXT_ATTACHMENT_EXTENSIONS = new Set([
|
|
27
29
|
".txt",
|
|
28
30
|
".md",
|
|
@@ -91,17 +93,10 @@ async function sweepStaleTelegramAttachmentTempDirs() {
|
|
|
91
93
|
return;
|
|
92
94
|
}
|
|
93
95
|
}
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
{ command: "use", description: "switch active session" },
|
|
99
|
-
{ command: "sessions", description: "list sessions" },
|
|
100
|
-
{ command: "status", description: "show active session status" },
|
|
101
|
-
{ command: "interrupt", description: "interrupt active run" },
|
|
102
|
-
{ command: "close", description: "close session(s)" },
|
|
103
|
-
{ command: "verbose", description: "stream progress updates" },
|
|
104
|
-
{ command: "quiet", description: "only send final assistant message" },
|
|
96
|
+
const QUICK_ACTION_ROWS = [
|
|
97
|
+
["new", "sessions", "status"],
|
|
98
|
+
["interrupt", "close"],
|
|
99
|
+
["quiet", "verbose"],
|
|
105
100
|
];
|
|
106
101
|
function splitCommandText(text) {
|
|
107
102
|
return text
|
|
@@ -251,9 +246,76 @@ function describeSession(session, details = {}) {
|
|
|
251
246
|
function formatSessionHeadline(sessionId, label) {
|
|
252
247
|
return `(${sessionId}) ${label}`;
|
|
253
248
|
}
|
|
249
|
+
const telegramObject = (shape) => z.object(shape).passthrough();
|
|
250
|
+
const telegramPartialObject = (shape) => telegramObject(shape).partial();
|
|
251
|
+
function parseOrThrow(schema, raw, message) {
|
|
252
|
+
const parsed = schema.safeParse(raw);
|
|
253
|
+
if (!parsed.success) {
|
|
254
|
+
throw new Error(`${message}: ${formatZodError(parsed.error)}`);
|
|
255
|
+
}
|
|
256
|
+
return parsed.data;
|
|
257
|
+
}
|
|
258
|
+
const TelegramEnvelopeSchema = z.discriminatedUnion("ok", [
|
|
259
|
+
z.object({
|
|
260
|
+
ok: z.literal(true),
|
|
261
|
+
description: z.string().optional(),
|
|
262
|
+
result: z.unknown(),
|
|
263
|
+
}),
|
|
264
|
+
z.object({
|
|
265
|
+
ok: z.literal(false),
|
|
266
|
+
description: z.string().optional(),
|
|
267
|
+
result: z.unknown().optional(),
|
|
268
|
+
}),
|
|
269
|
+
]);
|
|
270
|
+
const TelegramChatSchema = telegramObject({ id: z.number(), type: z.string() });
|
|
271
|
+
const TelegramUserSchema = telegramObject({ id: z.number() });
|
|
272
|
+
const TELEGRAM_FILE_SHAPE = {
|
|
273
|
+
file_id: z.string(),
|
|
274
|
+
file_name: z.string(),
|
|
275
|
+
mime_type: z.string(),
|
|
276
|
+
file_size: z.number(),
|
|
277
|
+
};
|
|
278
|
+
const TelegramFileSchema = telegramPartialObject(TELEGRAM_FILE_SHAPE);
|
|
279
|
+
const TelegramPhotoVariantSchema = telegramPartialObject({
|
|
280
|
+
file_id: z.string(),
|
|
281
|
+
file_size: z.number(),
|
|
282
|
+
width: z.number(),
|
|
283
|
+
height: z.number(),
|
|
284
|
+
});
|
|
285
|
+
const TelegramVoiceSchema = TelegramFileSchema.pick({ file_id: true, mime_type: true });
|
|
286
|
+
const TelegramAudioSchema = TelegramFileSchema.pick({
|
|
287
|
+
file_id: true,
|
|
288
|
+
mime_type: true,
|
|
289
|
+
file_name: true,
|
|
290
|
+
});
|
|
291
|
+
const TelegramMessageSchema = telegramPartialObject({
|
|
292
|
+
message_id: z.number(),
|
|
293
|
+
chat: TelegramChatSchema,
|
|
294
|
+
from: TelegramUserSchema,
|
|
295
|
+
text: z.string(),
|
|
296
|
+
caption: z.string(),
|
|
297
|
+
photo: z.array(TelegramPhotoVariantSchema),
|
|
298
|
+
document: TelegramFileSchema,
|
|
299
|
+
voice: TelegramVoiceSchema,
|
|
300
|
+
audio: TelegramAudioSchema,
|
|
301
|
+
});
|
|
302
|
+
const TelegramCallbackQuerySchema = telegramPartialObject({
|
|
303
|
+
id: z.string(),
|
|
304
|
+
from: TelegramUserSchema,
|
|
305
|
+
data: z.string(),
|
|
306
|
+
message: telegramPartialObject({ chat: TelegramChatSchema }),
|
|
307
|
+
});
|
|
308
|
+
const TelegramUpdateSchema = telegramPartialObject({
|
|
309
|
+
update_id: z.number(),
|
|
310
|
+
message: TelegramMessageSchema,
|
|
311
|
+
callback_query: TelegramCallbackQuerySchema,
|
|
312
|
+
});
|
|
313
|
+
const TelegramGetUpdatesResultSchema = z.array(TelegramUpdateSchema);
|
|
314
|
+
const TelegramGetFileResultSchema = z.object({ file_path: z.string() });
|
|
315
|
+
const TelegramAckResultSchema = z.literal(true);
|
|
254
316
|
function createTelegramApi(botToken) {
|
|
255
317
|
const apiUrl = `https://api.telegram.org/bot${botToken}`;
|
|
256
|
-
async function callTelegramMethod(method, payload) {
|
|
318
|
+
async function callTelegramMethod(method, payload, resultSchema) {
|
|
257
319
|
const response = await fetch(`${apiUrl}/${method}`, {
|
|
258
320
|
method: "POST",
|
|
259
321
|
headers: {
|
|
@@ -265,43 +327,40 @@ function createTelegramApi(botToken) {
|
|
|
265
327
|
const detail = (await response.text()).trim();
|
|
266
328
|
throw new Error(detail || `telegram ${method} failed: HTTP ${response.status}`);
|
|
267
329
|
}
|
|
268
|
-
let
|
|
330
|
+
let raw;
|
|
269
331
|
try {
|
|
270
|
-
|
|
332
|
+
raw = await response.json();
|
|
271
333
|
}
|
|
272
334
|
catch {
|
|
273
335
|
throw new Error(`telegram ${method} returned invalid JSON`);
|
|
274
336
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
if (data.ok !== true) {
|
|
279
|
-
const detail = typeof data.description === "string" ? data.description.trim() : "";
|
|
337
|
+
const envelope = parseOrThrow(TelegramEnvelopeSchema, raw, `telegram ${method} returned an invalid response payload`);
|
|
338
|
+
if (!envelope.ok) {
|
|
339
|
+
const detail = envelope.description?.trim() ?? "";
|
|
280
340
|
throw new Error(detail || `telegram ${method} request failed`);
|
|
281
341
|
}
|
|
282
|
-
return
|
|
342
|
+
return parseOrThrow(resultSchema, envelope.result, `telegram ${method} returned an invalid result`);
|
|
283
343
|
}
|
|
284
344
|
return {
|
|
285
345
|
async getUpdates(args) {
|
|
286
|
-
|
|
346
|
+
return callTelegramMethod("getUpdates", {
|
|
287
347
|
offset: args.offset,
|
|
288
348
|
timeout: args.timeoutSeconds,
|
|
289
349
|
allowed_updates: args.allowedUpdates,
|
|
290
|
-
});
|
|
291
|
-
return updates;
|
|
350
|
+
}, TelegramGetUpdatesResultSchema);
|
|
292
351
|
},
|
|
293
352
|
async sendMessage(chatId, text, options) {
|
|
294
353
|
await callTelegramMethod("sendMessage", {
|
|
295
354
|
chat_id: chatId,
|
|
296
355
|
text,
|
|
297
356
|
...(options?.replyMarkup ? { reply_markup: options.replyMarkup } : {}),
|
|
298
|
-
});
|
|
357
|
+
}, z.unknown());
|
|
299
358
|
},
|
|
300
359
|
async downloadFile(fileId) {
|
|
301
|
-
const
|
|
360
|
+
const parsed = await callTelegramMethod("getFile", {
|
|
302
361
|
file_id: fileId,
|
|
303
|
-
});
|
|
304
|
-
const filePath =
|
|
362
|
+
}, TelegramGetFileResultSchema);
|
|
363
|
+
const filePath = parsed.file_path?.trim();
|
|
305
364
|
if (!filePath) {
|
|
306
365
|
throw new Error("telegram file path is missing");
|
|
307
366
|
}
|
|
@@ -316,20 +375,20 @@ function createTelegramApi(botToken) {
|
|
|
316
375
|
async setCommands(commands) {
|
|
317
376
|
await callTelegramMethod("setMyCommands", {
|
|
318
377
|
commands,
|
|
319
|
-
});
|
|
378
|
+
}, TelegramAckResultSchema);
|
|
320
379
|
},
|
|
321
380
|
async setMessageReaction(chatId, messageId) {
|
|
322
381
|
await callTelegramMethod("setMessageReaction", {
|
|
323
382
|
chat_id: chatId,
|
|
324
383
|
message_id: messageId,
|
|
325
384
|
reaction: [{ type: "emoji", emoji: MESSAGE_QUEUED_REACTION_EMOJI }],
|
|
326
|
-
});
|
|
385
|
+
}, TelegramAckResultSchema);
|
|
327
386
|
},
|
|
328
387
|
async answerCallbackQuery(callbackQueryId, text) {
|
|
329
388
|
await callTelegramMethod("answerCallbackQuery", {
|
|
330
389
|
callback_query_id: callbackQueryId,
|
|
331
390
|
...(text ? { text } : {}),
|
|
332
|
-
});
|
|
391
|
+
}, TelegramAckResultSchema);
|
|
333
392
|
},
|
|
334
393
|
};
|
|
335
394
|
}
|
|
@@ -349,6 +408,9 @@ class AsyncTelegramAdapterImpl {
|
|
|
349
408
|
api;
|
|
350
409
|
fetchImpl;
|
|
351
410
|
onLog;
|
|
411
|
+
commandDefinitions;
|
|
412
|
+
commandHandlers;
|
|
413
|
+
callbackActionHandlers;
|
|
352
414
|
abortController = new AbortController();
|
|
353
415
|
activeSessionsByChat = new Map();
|
|
354
416
|
sessionsByChat = new Map();
|
|
@@ -390,6 +452,15 @@ class AsyncTelegramAdapterImpl {
|
|
|
390
452
|
this.api = options.api ?? createTelegramApi(options.botToken);
|
|
391
453
|
this.fetchImpl = options.fetchImpl;
|
|
392
454
|
this.onLog = options.onLog;
|
|
455
|
+
this.commandDefinitions = this.createCommandDefinitions();
|
|
456
|
+
this.commandHandlers = new Map();
|
|
457
|
+
this.callbackActionHandlers = new Map();
|
|
458
|
+
for (const definition of this.commandDefinitions) {
|
|
459
|
+
this.commandHandlers.set(definition.command, definition.handler);
|
|
460
|
+
if (definition.callbackAction) {
|
|
461
|
+
this.callbackActionHandlers.set(definition.callbackAction, definition.handler);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
393
464
|
this.unsubscribeSessionEvents = this.sessionManager.onEvent((event) => {
|
|
394
465
|
this.onSessionEvent(event);
|
|
395
466
|
});
|
|
@@ -422,13 +493,88 @@ class AsyncTelegramAdapterImpl {
|
|
|
422
493
|
log(level, message, data) {
|
|
423
494
|
this.onLog?.({ level, message, ...(data === undefined ? {} : { data }) });
|
|
424
495
|
}
|
|
496
|
+
createCommandDefinitions() {
|
|
497
|
+
return [
|
|
498
|
+
{
|
|
499
|
+
command: "/help",
|
|
500
|
+
description: "show supported commands",
|
|
501
|
+
usage: "/help",
|
|
502
|
+
handler: async (chatId) => this.handleHelp(chatId),
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
command: "/new",
|
|
506
|
+
description: "start a new session",
|
|
507
|
+
usage: "/new [projectId]",
|
|
508
|
+
callbackAction: "new",
|
|
509
|
+
handler: async (chatId, args) => this.handleNew(chatId, args),
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
command: "/projects",
|
|
513
|
+
description: "list configured projects",
|
|
514
|
+
usage: "/projects",
|
|
515
|
+
handler: async (chatId) => this.handleProjects(chatId),
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
command: "/use",
|
|
519
|
+
description: "switch active session",
|
|
520
|
+
usage: "/use <sessionId|prefix|index>",
|
|
521
|
+
handler: async (chatId, args) => this.handleUse(chatId, args),
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
command: "/sessions",
|
|
525
|
+
description: "list sessions",
|
|
526
|
+
usage: "/sessions",
|
|
527
|
+
callbackAction: "sessions",
|
|
528
|
+
handler: async (chatId) => this.handleSessions(chatId),
|
|
529
|
+
},
|
|
530
|
+
{
|
|
531
|
+
command: "/status",
|
|
532
|
+
description: "show active session status",
|
|
533
|
+
usage: "/status",
|
|
534
|
+
callbackAction: "status",
|
|
535
|
+
handler: async (chatId) => this.handleStatus(chatId),
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
command: "/interrupt",
|
|
539
|
+
description: "interrupt active run",
|
|
540
|
+
usage: "/interrupt",
|
|
541
|
+
callbackAction: "interrupt",
|
|
542
|
+
handler: async (chatId) => this.handleInterrupt(chatId),
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
command: "/close",
|
|
546
|
+
description: "close session(s)",
|
|
547
|
+
usage: "/close [<sessionId>|all]",
|
|
548
|
+
callbackAction: "close",
|
|
549
|
+
handler: async (chatId, args) => this.handleClose(chatId, args),
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
command: "/verbose",
|
|
553
|
+
description: "stream progress updates",
|
|
554
|
+
usage: "/verbose",
|
|
555
|
+
callbackAction: "verbose",
|
|
556
|
+
handler: async (chatId) => this.handleVerbosityCommand(chatId, "verbose"),
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
command: "/quiet",
|
|
560
|
+
description: "only send final assistant message",
|
|
561
|
+
usage: "/quiet",
|
|
562
|
+
callbackAction: "quiet",
|
|
563
|
+
handler: async (chatId) => this.handleVerbosityCommand(chatId, "quiet"),
|
|
564
|
+
},
|
|
565
|
+
];
|
|
566
|
+
}
|
|
425
567
|
async syncCommands() {
|
|
426
568
|
if (!this.api.setCommands) {
|
|
427
569
|
return;
|
|
428
570
|
}
|
|
571
|
+
const commands = this.commandDefinitions.map((definition) => ({
|
|
572
|
+
command: definition.command.slice(1),
|
|
573
|
+
description: definition.description,
|
|
574
|
+
}));
|
|
429
575
|
try {
|
|
430
|
-
await this.api.setCommands(
|
|
431
|
-
this.log("info", "telegram commands synced", { count:
|
|
576
|
+
await this.api.setCommands(commands);
|
|
577
|
+
this.log("info", "telegram commands synced", { count: commands.length });
|
|
432
578
|
}
|
|
433
579
|
catch (error) {
|
|
434
580
|
this.log("warn", "failed to sync telegram commands", {
|
|
@@ -471,7 +617,7 @@ class AsyncTelegramAdapterImpl {
|
|
|
471
617
|
if (updates === undefined) {
|
|
472
618
|
return [];
|
|
473
619
|
}
|
|
474
|
-
return updates
|
|
620
|
+
return updates;
|
|
475
621
|
}
|
|
476
622
|
async raceWithAbort(promise) {
|
|
477
623
|
if (this.abortController.signal.aborted) {
|
|
@@ -624,9 +770,8 @@ class AsyncTelegramAdapterImpl {
|
|
|
624
770
|
if (parsedAttachments.length === 0) {
|
|
625
771
|
return;
|
|
626
772
|
}
|
|
627
|
-
const session = this.
|
|
773
|
+
const session = await this.requireActiveOrSingleSession(chatId);
|
|
628
774
|
if (!session) {
|
|
629
|
-
await this.reply(chatId, "no active session. use /new or /sessions");
|
|
630
775
|
return;
|
|
631
776
|
}
|
|
632
777
|
const pending = this.pendingAttachmentsBySession.get(session.id) ?? [];
|
|
@@ -636,17 +781,21 @@ class AsyncTelegramAdapterImpl {
|
|
|
636
781
|
for (const attachment of parsedAttachments) {
|
|
637
782
|
const attachmentLabel = describeAttachment(attachment.fileName, attachment.mimeType);
|
|
638
783
|
if (pending.length >= MAX_TELEGRAM_ATTACHMENTS_PER_TURN) {
|
|
639
|
-
await this.
|
|
784
|
+
await this.replySkippedAttachment(chatId, attachmentLabel, `exceeds attachment limit (${MAX_TELEGRAM_ATTACHMENTS_PER_TURN} files per turn)`);
|
|
640
785
|
continue;
|
|
641
786
|
}
|
|
642
|
-
|
|
643
|
-
attachment.sizeBytes
|
|
644
|
-
|
|
787
|
+
const declaredFileLimitReason = typeof attachment.sizeBytes === "number"
|
|
788
|
+
? this.getAttachmentPerFileLimitReason(attachment.sizeBytes)
|
|
789
|
+
: undefined;
|
|
790
|
+
if (declaredFileLimitReason) {
|
|
791
|
+
await this.replySkippedAttachment(chatId, attachmentLabel, declaredFileLimitReason);
|
|
645
792
|
continue;
|
|
646
793
|
}
|
|
647
|
-
|
|
648
|
-
totalSizeBytes
|
|
649
|
-
|
|
794
|
+
const declaredTotalLimitReason = typeof attachment.sizeBytes === "number"
|
|
795
|
+
? this.getAttachmentTotalLimitReason(totalSizeBytes, attachment.sizeBytes)
|
|
796
|
+
: undefined;
|
|
797
|
+
if (declaredTotalLimitReason) {
|
|
798
|
+
await this.replySkippedAttachment(chatId, attachmentLabel, declaredTotalLimitReason);
|
|
650
799
|
continue;
|
|
651
800
|
}
|
|
652
801
|
let bytes;
|
|
@@ -658,12 +807,14 @@ class AsyncTelegramAdapterImpl {
|
|
|
658
807
|
continue;
|
|
659
808
|
}
|
|
660
809
|
const sizeBytes = bytes.byteLength;
|
|
661
|
-
|
|
662
|
-
|
|
810
|
+
const fileLimitReason = this.getAttachmentPerFileLimitReason(sizeBytes);
|
|
811
|
+
if (fileLimitReason) {
|
|
812
|
+
await this.replySkippedAttachment(chatId, attachmentLabel, fileLimitReason);
|
|
663
813
|
continue;
|
|
664
814
|
}
|
|
665
|
-
|
|
666
|
-
|
|
815
|
+
const totalLimitReason = this.getAttachmentTotalLimitReason(totalSizeBytes, sizeBytes);
|
|
816
|
+
if (totalLimitReason) {
|
|
817
|
+
await this.replySkippedAttachment(chatId, attachmentLabel, totalLimitReason);
|
|
667
818
|
continue;
|
|
668
819
|
}
|
|
669
820
|
const tempDirPath = await this.getOrCreatePendingAttachmentTempDir(session.id);
|
|
@@ -727,12 +878,14 @@ class AsyncTelegramAdapterImpl {
|
|
|
727
878
|
await this.reply(chatId, `skipped attachment ${attachmentLabel}: local temp file is missing`);
|
|
728
879
|
continue;
|
|
729
880
|
}
|
|
730
|
-
|
|
731
|
-
|
|
881
|
+
const fileLimitReason = this.getAttachmentPerFileLimitReason(materialized.sizeBytes);
|
|
882
|
+
if (fileLimitReason) {
|
|
883
|
+
await this.replySkippedAttachment(chatId, attachmentLabel, fileLimitReason);
|
|
732
884
|
continue;
|
|
733
885
|
}
|
|
734
|
-
|
|
735
|
-
|
|
886
|
+
const totalLimitReason = this.getAttachmentTotalLimitReason(totalSizeBytes, materialized.sizeBytes);
|
|
887
|
+
if (totalLimitReason) {
|
|
888
|
+
await this.replySkippedAttachment(chatId, attachmentLabel, totalLimitReason);
|
|
736
889
|
continue;
|
|
737
890
|
}
|
|
738
891
|
totalSizeBytes += materialized.sizeBytes;
|
|
@@ -826,47 +979,12 @@ class AsyncTelegramAdapterImpl {
|
|
|
826
979
|
const parts = splitCommandText(text);
|
|
827
980
|
const command = stripCommandMention(parts[0] ?? "");
|
|
828
981
|
const args = parts.slice(1);
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
}
|
|
833
|
-
if (command === "/new") {
|
|
834
|
-
await this.handleNew(chatId, args);
|
|
835
|
-
return;
|
|
836
|
-
}
|
|
837
|
-
if (command === "/projects") {
|
|
838
|
-
await this.handleProjects(chatId);
|
|
839
|
-
return;
|
|
840
|
-
}
|
|
841
|
-
if (command === "/use") {
|
|
842
|
-
await this.handleUse(chatId, args);
|
|
843
|
-
return;
|
|
844
|
-
}
|
|
845
|
-
if (command === "/sessions") {
|
|
846
|
-
await this.handleSessions(chatId);
|
|
847
|
-
return;
|
|
848
|
-
}
|
|
849
|
-
if (command === "/status") {
|
|
850
|
-
await this.handleStatus(chatId);
|
|
851
|
-
return;
|
|
852
|
-
}
|
|
853
|
-
if (command === "/interrupt") {
|
|
854
|
-
await this.handleInterrupt(chatId);
|
|
855
|
-
return;
|
|
856
|
-
}
|
|
857
|
-
if (command === "/close") {
|
|
858
|
-
await this.handleClose(chatId, args);
|
|
859
|
-
return;
|
|
860
|
-
}
|
|
861
|
-
if (command === "/verbose") {
|
|
862
|
-
await this.handleVerbosityCommand(chatId, "verbose");
|
|
982
|
+
const handler = this.commandHandlers.get(command);
|
|
983
|
+
if (!handler) {
|
|
984
|
+
await this.reply(chatId, "unsupported command. use /help");
|
|
863
985
|
return;
|
|
864
986
|
}
|
|
865
|
-
|
|
866
|
-
await this.handleVerbosityCommand(chatId, "quiet");
|
|
867
|
-
return;
|
|
868
|
-
}
|
|
869
|
-
await this.reply(chatId, "unsupported command. use /help");
|
|
987
|
+
await handler(chatId, args);
|
|
870
988
|
}
|
|
871
989
|
async handleCallback(chatId, callbackData) {
|
|
872
990
|
if (callbackData.startsWith(CALLBACK_USE_PREFIX)) {
|
|
@@ -880,50 +998,18 @@ class AsyncTelegramAdapterImpl {
|
|
|
880
998
|
if (!callbackData.startsWith(CALLBACK_ACTION_PREFIX)) {
|
|
881
999
|
return false;
|
|
882
1000
|
}
|
|
883
|
-
const action = callbackData.slice(CALLBACK_ACTION_PREFIX.length);
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
return
|
|
887
|
-
}
|
|
888
|
-
if (action === "sessions") {
|
|
889
|
-
await this.handleSessions(chatId);
|
|
890
|
-
return true;
|
|
891
|
-
}
|
|
892
|
-
if (action === "status") {
|
|
893
|
-
await this.handleStatus(chatId);
|
|
894
|
-
return true;
|
|
895
|
-
}
|
|
896
|
-
if (action === "interrupt") {
|
|
897
|
-
await this.handleInterrupt(chatId);
|
|
898
|
-
return true;
|
|
899
|
-
}
|
|
900
|
-
if (action === "close") {
|
|
901
|
-
await this.handleClose(chatId, []);
|
|
902
|
-
return true;
|
|
903
|
-
}
|
|
904
|
-
if (action === "quiet") {
|
|
905
|
-
await this.handleVerbosityCommand(chatId, "quiet");
|
|
906
|
-
return true;
|
|
907
|
-
}
|
|
908
|
-
if (action === "verbose") {
|
|
909
|
-
await this.handleVerbosityCommand(chatId, "verbose");
|
|
910
|
-
return true;
|
|
1001
|
+
const action = callbackData.slice(CALLBACK_ACTION_PREFIX.length).trim();
|
|
1002
|
+
const handler = this.callbackActionHandlers.get(action);
|
|
1003
|
+
if (!handler) {
|
|
1004
|
+
return false;
|
|
911
1005
|
}
|
|
912
|
-
|
|
1006
|
+
await handler(chatId, []);
|
|
1007
|
+
return true;
|
|
913
1008
|
}
|
|
914
1009
|
async handleHelp(chatId) {
|
|
915
1010
|
const lines = [
|
|
916
1011
|
"commands:",
|
|
917
|
-
|
|
918
|
-
"/new [projectId]",
|
|
919
|
-
"/projects",
|
|
920
|
-
"/sessions",
|
|
921
|
-
"/use <sessionId|prefix|index>",
|
|
922
|
-
"/status",
|
|
923
|
-
"/interrupt",
|
|
924
|
-
"/close [<sessionId>|all]",
|
|
925
|
-
"/verbose",
|
|
926
|
-
"/quiet",
|
|
1012
|
+
...this.commandDefinitions.map((definition) => definition.usage),
|
|
927
1013
|
"",
|
|
928
1014
|
"tip: use /sessions and tap a session button to switch quickly",
|
|
929
1015
|
];
|
|
@@ -1113,27 +1199,15 @@ class AsyncTelegramAdapterImpl {
|
|
|
1113
1199
|
}
|
|
1114
1200
|
buildQuickActionsKeyboard() {
|
|
1115
1201
|
return {
|
|
1116
|
-
inline_keyboard:
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
{ text: "/status", callback_data: `${CALLBACK_ACTION_PREFIX}status` },
|
|
1121
|
-
],
|
|
1122
|
-
[
|
|
1123
|
-
{ text: "/interrupt", callback_data: `${CALLBACK_ACTION_PREFIX}interrupt` },
|
|
1124
|
-
{ text: "/close", callback_data: `${CALLBACK_ACTION_PREFIX}close` },
|
|
1125
|
-
],
|
|
1126
|
-
[
|
|
1127
|
-
{ text: "/quiet", callback_data: `${CALLBACK_ACTION_PREFIX}quiet` },
|
|
1128
|
-
{ text: "/verbose", callback_data: `${CALLBACK_ACTION_PREFIX}verbose` },
|
|
1129
|
-
],
|
|
1130
|
-
],
|
|
1202
|
+
inline_keyboard: QUICK_ACTION_ROWS.map((row) => row.map((action) => ({
|
|
1203
|
+
text: `/${action}`,
|
|
1204
|
+
callback_data: `${CALLBACK_ACTION_PREFIX}${action}`,
|
|
1205
|
+
}))),
|
|
1131
1206
|
};
|
|
1132
1207
|
}
|
|
1133
1208
|
async handleStatus(chatId) {
|
|
1134
|
-
const session = this.
|
|
1209
|
+
const session = await this.requireActiveSession(chatId);
|
|
1135
1210
|
if (!session) {
|
|
1136
|
-
await this.reply(chatId, "no active session. use /new or /sessions");
|
|
1137
1211
|
return;
|
|
1138
1212
|
}
|
|
1139
1213
|
await this.reply(chatId, describeSession(session, {
|
|
@@ -1145,9 +1219,8 @@ class AsyncTelegramAdapterImpl {
|
|
|
1145
1219
|
});
|
|
1146
1220
|
}
|
|
1147
1221
|
async handleInterrupt(chatId) {
|
|
1148
|
-
const session = this.
|
|
1222
|
+
const session = await this.requireActiveSession(chatId);
|
|
1149
1223
|
if (!session) {
|
|
1150
|
-
await this.reply(chatId, "no active session. use /new or /sessions");
|
|
1151
1224
|
return;
|
|
1152
1225
|
}
|
|
1153
1226
|
try {
|
|
@@ -1190,7 +1263,7 @@ class AsyncTelegramAdapterImpl {
|
|
|
1190
1263
|
}
|
|
1191
1264
|
const sessionId = target ?? this.getActiveSession(chatId)?.id;
|
|
1192
1265
|
if (!sessionId) {
|
|
1193
|
-
await this.reply(chatId,
|
|
1266
|
+
await this.reply(chatId, NO_ACTIVE_SESSION_MESSAGE);
|
|
1194
1267
|
return;
|
|
1195
1268
|
}
|
|
1196
1269
|
try {
|
|
@@ -1204,38 +1277,28 @@ class AsyncTelegramAdapterImpl {
|
|
|
1204
1277
|
}
|
|
1205
1278
|
}
|
|
1206
1279
|
async handleVerbosityCommand(chatId, verbosity) {
|
|
1207
|
-
const session = this.
|
|
1280
|
+
const session = await this.requireActiveSession(chatId);
|
|
1208
1281
|
if (!session) {
|
|
1209
|
-
await this.reply(chatId, "no active session. use /new or /sessions");
|
|
1210
1282
|
return;
|
|
1211
1283
|
}
|
|
1212
1284
|
this.sessionVerbosityBySession.set(session.id, verbosity);
|
|
1213
1285
|
await this.reply(chatId, formatSessionHeadline(session.id, `verbosity set to ${verbosity}`));
|
|
1214
1286
|
}
|
|
1215
1287
|
async handleMessage(chatId, text, sourceMessageId) {
|
|
1216
|
-
const session = this.
|
|
1288
|
+
const session = await this.requireActiveOrSingleSession(chatId);
|
|
1217
1289
|
if (!session) {
|
|
1218
|
-
await this.reply(chatId, "no active session. use /new or /sessions");
|
|
1219
1290
|
return;
|
|
1220
1291
|
}
|
|
1221
1292
|
try {
|
|
1222
|
-
|
|
1223
|
-
const sessionManager = this.getSessionManagerForChat(chatId);
|
|
1224
|
-
await sessionManager.sendMessage(session.id, textWithAttachments, this.systemMessage ? { additionalSystemMessage: this.systemMessage } : undefined);
|
|
1225
|
-
this.resetPendingAttachmentQueue(session.id);
|
|
1226
|
-
await this.reactToQueuedMessage(chatId, sourceMessageId);
|
|
1227
|
-
if (this.isVerboseSession(session.id)) {
|
|
1228
|
-
await this.reply(chatId, this.formatMessageQueued(session.id));
|
|
1229
|
-
}
|
|
1293
|
+
await this.submitSessionMessage(chatId, session.id, text, sourceMessageId);
|
|
1230
1294
|
}
|
|
1231
1295
|
catch (error) {
|
|
1232
1296
|
await this.reply(chatId, this.formatManagerError(error));
|
|
1233
1297
|
}
|
|
1234
1298
|
}
|
|
1235
1299
|
async handleAudioMessage(chatId, message, sourceMessageId) {
|
|
1236
|
-
const session = this.
|
|
1300
|
+
const session = await this.requireActiveOrSingleSession(chatId);
|
|
1237
1301
|
if (!session) {
|
|
1238
|
-
await this.reply(chatId, "no active session. use /new or /sessions");
|
|
1239
1302
|
return;
|
|
1240
1303
|
}
|
|
1241
1304
|
if (!this.mistralApiKey) {
|
|
@@ -1262,19 +1325,53 @@ class AsyncTelegramAdapterImpl {
|
|
|
1262
1325
|
return;
|
|
1263
1326
|
}
|
|
1264
1327
|
try {
|
|
1265
|
-
|
|
1266
|
-
const sessionManager = this.getSessionManagerForChat(chatId);
|
|
1267
|
-
await sessionManager.sendMessage(session.id, textWithAttachments, this.systemMessage ? { additionalSystemMessage: this.systemMessage } : undefined);
|
|
1268
|
-
this.resetPendingAttachmentQueue(session.id);
|
|
1269
|
-
await this.reactToQueuedMessage(chatId, sourceMessageId);
|
|
1270
|
-
if (this.isVerboseSession(session.id)) {
|
|
1271
|
-
await this.reply(chatId, this.formatMessageQueued(session.id));
|
|
1272
|
-
}
|
|
1328
|
+
await this.submitSessionMessage(chatId, session.id, transcript, sourceMessageId);
|
|
1273
1329
|
}
|
|
1274
1330
|
catch (error) {
|
|
1275
1331
|
await this.reply(chatId, this.formatManagerError(error));
|
|
1276
1332
|
}
|
|
1277
1333
|
}
|
|
1334
|
+
async requireActiveSession(chatId) {
|
|
1335
|
+
const session = this.getActiveSession(chatId);
|
|
1336
|
+
if (!session) {
|
|
1337
|
+
await this.reply(chatId, NO_ACTIVE_SESSION_MESSAGE);
|
|
1338
|
+
return undefined;
|
|
1339
|
+
}
|
|
1340
|
+
return session;
|
|
1341
|
+
}
|
|
1342
|
+
async requireActiveOrSingleSession(chatId) {
|
|
1343
|
+
const session = this.getActiveOrSingleSession(chatId);
|
|
1344
|
+
if (!session) {
|
|
1345
|
+
await this.reply(chatId, NO_ACTIVE_SESSION_MESSAGE);
|
|
1346
|
+
return undefined;
|
|
1347
|
+
}
|
|
1348
|
+
return session;
|
|
1349
|
+
}
|
|
1350
|
+
async submitSessionMessage(chatId, sessionId, text, sourceMessageId) {
|
|
1351
|
+
const textWithAttachments = await this.buildMessageTextWithAttachments(sessionId, text, chatId);
|
|
1352
|
+
const sessionManager = this.getSessionManagerForChat(chatId);
|
|
1353
|
+
await sessionManager.sendMessage(sessionId, textWithAttachments, this.systemMessage ? { additionalSystemMessage: this.systemMessage } : undefined);
|
|
1354
|
+
this.resetPendingAttachmentQueue(sessionId);
|
|
1355
|
+
await this.reactToQueuedMessage(chatId, sourceMessageId);
|
|
1356
|
+
if (this.isVerboseSession(sessionId)) {
|
|
1357
|
+
await this.reply(chatId, this.formatMessageQueued(sessionId));
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
getAttachmentPerFileLimitReason(sizeBytes) {
|
|
1361
|
+
if (sizeBytes <= MAX_TELEGRAM_ATTACHMENT_FILE_BYTES) {
|
|
1362
|
+
return undefined;
|
|
1363
|
+
}
|
|
1364
|
+
return `exceeds per-file limit (${describeAttachmentLimitBytes(MAX_TELEGRAM_ATTACHMENT_FILE_BYTES)})`;
|
|
1365
|
+
}
|
|
1366
|
+
getAttachmentTotalLimitReason(totalSizeBytes, nextSizeBytes) {
|
|
1367
|
+
if (totalSizeBytes + nextSizeBytes <= MAX_TELEGRAM_ATTACHMENT_TOTAL_BYTES) {
|
|
1368
|
+
return undefined;
|
|
1369
|
+
}
|
|
1370
|
+
return `exceeds per-turn total limit (${describeAttachmentLimitBytes(MAX_TELEGRAM_ATTACHMENT_TOTAL_BYTES)})`;
|
|
1371
|
+
}
|
|
1372
|
+
async replySkippedAttachment(chatId, attachmentLabel, reason) {
|
|
1373
|
+
await this.reply(chatId, `skipped attachment ${attachmentLabel}: ${reason}`);
|
|
1374
|
+
}
|
|
1278
1375
|
getActiveSession(chatId) {
|
|
1279
1376
|
const sessionId = this.activeSessionsByChat.get(chatId);
|
|
1280
1377
|
if (!sessionId) {
|