@markusylisiurunen/tau 0.3.35 → 0.3.37
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/README.md +62 -15
- package/dist/core/auth/cli.js +10 -0
- package/dist/core/auth/cli.js.map +1 -1
- package/dist/core/auth/providers/openai_codex.js +36 -16
- package/dist/core/auth/providers/openai_codex.js.map +1 -1
- package/dist/core/cli.js +7 -0
- package/dist/core/cli.js.map +1 -1
- package/dist/core/config/builtin_themes.js +0 -93
- package/dist/core/config/builtin_themes.js.map +1 -1
- package/dist/core/config/client_tools.js +109 -0
- package/dist/core/config/client_tools.js.map +1 -0
- package/dist/core/config/index.js.map +1 -1
- package/dist/core/config/schema.js +33 -1
- package/dist/core/config/schema.js.map +1 -1
- package/dist/core/debug.js +16 -30
- package/dist/core/debug.js.map +1 -1
- package/dist/core/runtime/runtime_bootstrap.js +68 -25
- package/dist/core/runtime/runtime_bootstrap.js.map +1 -1
- package/dist/core/telegram/adapter.js +319 -67
- package/dist/core/telegram/adapter.js.map +1 -1
- package/dist/core/telegram/config.js +1 -1
- package/dist/core/telegram/runtime.js +20 -1
- package/dist/core/telegram/runtime.js.map +1 -1
- package/dist/core/telegram/session_manager.js +12 -0
- package/dist/core/telegram/session_manager.js.map +1 -1
- package/dist/core/tools/tool_names.js +1 -0
- package/dist/core/tools/tool_names.js.map +1 -1
- package/dist/core/tools/view_image.js +8 -5
- package/dist/core/tools/view_image.js.map +1 -1
- package/dist/core/utils/context.js +0 -1
- package/dist/core/utils/context.js.map +1 -1
- package/dist/core/utils/context_builder.js +2 -3
- package/dist/core/utils/context_builder.js.map +1 -1
- package/dist/core/utils/spawn_capture.js +58 -22
- package/dist/core/utils/spawn_capture.js.map +1 -1
- package/dist/core/utils/user_metadata.js +14 -0
- package/dist/core/utils/user_metadata.js.map +1 -1
- package/dist/core/version.js +1 -1
- package/dist/main.js +91 -31
- package/dist/main.js.map +1 -1
- package/dist/protocol/session_protocol.d.ts +1 -1
- package/dist/protocol/session_protocol.js +1 -1
- package/dist/sdk/client.js +7 -108
- package/dist/sdk/client.js.map +1 -1
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/local_client.d.ts +3 -0
- package/dist/sdk/local_client.js +106 -0
- package/dist/sdk/local_client.js.map +1 -0
- package/dist/sdk/session.js +22 -3
- package/dist/sdk/session.js.map +1 -1
- package/dist/store/session_snapshot_migrations.js +5 -1
- package/dist/store/session_snapshot_migrations.js.map +1 -1
- package/dist/transport/in_process_session_transport.js +3 -0
- package/dist/transport/in_process_session_transport.js.map +1 -1
- package/dist/transport/index.d.ts +1 -1
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/session_protocol_transport_helpers.js +10 -0
- package/dist/transport/session_protocol_transport_helpers.js.map +1 -1
- package/dist/transport/session_transport.d.ts +2 -0
- package/dist/transport/stdio_session_transport.d.ts +3 -1
- package/dist/transport/stdio_session_transport.js +13 -6
- package/dist/transport/stdio_session_transport.js.map +1 -1
- package/dist/transport/websocket_session_transport.d.ts +3 -1
- package/dist/transport/websocket_session_transport.js +11 -2
- package/dist/transport/websocket_session_transport.js.map +1 -1
- package/dist/tui/chat_controller/diff_review_service.js +12 -11
- package/dist/tui/chat_controller/diff_review_service.js.map +1 -1
- package/dist/tui/chat_controller/diff_review_user_message.js +1 -0
- package/dist/tui/chat_controller/diff_review_user_message.js.map +1 -1
- package/dist/tui/chat_controller/history_message_model.js +4 -1
- package/dist/tui/chat_controller/history_message_model.js.map +1 -1
- package/dist/tui/chat_view.js +0 -11
- package/dist/tui/chat_view.js.map +1 -1
- package/dist/tui/command_client_tools.js +80 -0
- package/dist/tui/command_client_tools.js.map +1 -0
- package/dist/tui/session_chat_app.js +72 -5
- package/dist/tui/session_chat_app.js.map +1 -1
- package/dist/tui/session_chat_controller.js +30 -56
- package/dist/tui/session_chat_controller.js.map +1 -1
- package/dist/tui/ui/theme/palette.js +0 -3
- package/dist/tui/ui/theme/palette.js.map +1 -1
- package/dist/tui/ui/theme/theme.js.map +1 -1
- package/dist/tui/ui/user_message.js +4 -10
- package/dist/tui/ui/user_message.js.map +1 -1
- package/package.json +1 -1
- package/dist/core/utils/agents_files.js +0 -282
- package/dist/core/utils/agents_files.js.map +0 -1
- package/dist/tui/memory_mode.js +0 -24
- package/dist/tui/memory_mode.js.map +0 -1
|
@@ -27,14 +27,23 @@ const TELEGRAM_MESSAGE_BYTE_BUFFER_RATIO = 0.95;
|
|
|
27
27
|
const TELEGRAM_SAFE_MESSAGE_BYTES = Math.floor(TELEGRAM_MAX_MESSAGE_BYTES * TELEGRAM_MESSAGE_BYTE_BUFFER_RATIO);
|
|
28
28
|
const TELEGRAM_RICH_MESSAGE_SAFE_BYTES = Math.floor(TELEGRAM_MAX_RICH_MESSAGE_BYTES * TELEGRAM_MESSAGE_BYTE_BUFFER_RATIO);
|
|
29
29
|
const TELEGRAM_MESSAGE_SPLIT_DELAY_MS = 1000;
|
|
30
|
+
const TELEGRAM_DELIVERY_ATTEMPT_TIMEOUT_MS = 30_000;
|
|
31
|
+
const TELEGRAM_DELIVERY_RETRY_DELAYS_MS = [1000, 5000];
|
|
30
32
|
const TELEGRAM_TYPING_REFRESH_MS = 4000;
|
|
31
33
|
const ABORTED = Symbol("aborted");
|
|
34
|
+
const TIMED_OUT = Symbol("timed-out");
|
|
32
35
|
const CALLBACK_ACTION_PREFIX = "tau:action:";
|
|
33
36
|
const MAX_TELEGRAM_ATTACHMENTS_PER_TURN = 10;
|
|
34
37
|
const MAX_TELEGRAM_ATTACHMENT_FILE_BYTES = 20 * 1024 * 1024;
|
|
35
38
|
const MAX_TELEGRAM_ATTACHMENT_TOTAL_BYTES = 50 * 1024 * 1024;
|
|
36
39
|
const MAX_TELEGRAM_GROUP_PENDING_MESSAGES = 50;
|
|
37
40
|
const TELEGRAM_ATTACHMENT_TEMP_DIR_PREFIX = "tau-telegram-attachments-";
|
|
41
|
+
const TELEGRAM_REASONING_EFFORTS = [
|
|
42
|
+
"low",
|
|
43
|
+
"medium",
|
|
44
|
+
"high",
|
|
45
|
+
"xhigh",
|
|
46
|
+
];
|
|
38
47
|
const NO_ACTIVE_SESSION_MESSAGE = "no active session. use /new.";
|
|
39
48
|
const SUPPORTED_TEXT_ATTACHMENT_EXTENSIONS = new Set([
|
|
40
49
|
".txt",
|
|
@@ -350,29 +359,41 @@ function formatTelegramSender(user) {
|
|
|
350
359
|
}
|
|
351
360
|
return `id ${user.id}`;
|
|
352
361
|
}
|
|
353
|
-
function formatTelegramSessionName(
|
|
354
|
-
return `${
|
|
362
|
+
function formatTelegramSessionName(sessionId, projectLabel) {
|
|
363
|
+
return `${projectLabel} session ${sessionId}`;
|
|
355
364
|
}
|
|
356
365
|
function formatProjectLabel(projectId, project) {
|
|
357
366
|
return "projectIds" in project ? `${projectId} (${project.projectIds.join(", ")})` : projectId;
|
|
358
367
|
}
|
|
359
368
|
function formatSessionStatus(session, project, snapshot, preferredProjectId) {
|
|
360
|
-
const
|
|
369
|
+
const sessionName = formatTelegramSessionName(session.id, formatProjectLabel(session.projectId, project));
|
|
361
370
|
const preferenceStatus = preferredProjectId && preferredProjectId !== session.projectId
|
|
362
|
-
? ` new chats will use
|
|
371
|
+
? ` new chats will use ${preferredProjectId}.`
|
|
363
372
|
: "";
|
|
364
|
-
if (session.state === "failed"
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
373
|
+
if (session.state === "failed") {
|
|
374
|
+
const errorStatus = session.error ? ` ${ensureTerminalPunctuation(session.error)}` : "";
|
|
375
|
+
return `your ${sessionName} failed.${errorStatus}${preferenceStatus}`;
|
|
376
|
+
}
|
|
377
|
+
const state = {
|
|
378
|
+
queued: "queued",
|
|
379
|
+
"preparing-workspace": "preparing its workspace",
|
|
380
|
+
running: "running",
|
|
381
|
+
"waiting-input": "waiting for input",
|
|
382
|
+
}[session.state];
|
|
383
|
+
const status = `your ${sessionName} is ${state}.`;
|
|
368
384
|
if (!snapshot) {
|
|
369
385
|
return `${status}${preferenceStatus}`;
|
|
370
386
|
}
|
|
387
|
+
const goalStatus = snapshot.goal
|
|
388
|
+
? snapshot.goal.status === "active"
|
|
389
|
+
? " it is pursuing a goal."
|
|
390
|
+
: " its goal is blocked."
|
|
391
|
+
: "";
|
|
371
392
|
const model = snapshot.bootstrap.model.name || snapshot.bootstrap.model.id;
|
|
372
393
|
const reasoning = snapshot.settings.reasoning ?? "none";
|
|
373
394
|
const context = formatTelegramContextUsage(snapshot);
|
|
374
395
|
const cost = formatTelegramSessionCost(getTelegramSessionCostTotal(snapshot));
|
|
375
|
-
return `${status} it is using ${model} with ${reasoning} reasoning. context usage is ${context}. cumulative cost is ${cost}.${preferenceStatus}`;
|
|
396
|
+
return `${status}${goalStatus} it is using ${model} with ${reasoning} reasoning. context usage is ${context}. cumulative cost is ${cost}.${preferenceStatus}`;
|
|
376
397
|
}
|
|
377
398
|
function ensureTerminalPunctuation(text) {
|
|
378
399
|
const trimmed = text.trim();
|
|
@@ -391,6 +412,17 @@ function formatTelegramContextUsage(snapshot) {
|
|
|
391
412
|
function getTelegramSessionCostTotal(snapshot) {
|
|
392
413
|
return snapshot.costTotal;
|
|
393
414
|
}
|
|
415
|
+
function supportsReasoningEffort(snapshot, reasoning) {
|
|
416
|
+
if (!snapshot.bootstrap.model.reasoning) {
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
const persona = snapshot.catalog.personas.find((candidate) => candidate.id === snapshot.settings.personaId);
|
|
420
|
+
if (!persona) {
|
|
421
|
+
return false;
|
|
422
|
+
}
|
|
423
|
+
const allowed = persona.allowedReasoningLevels;
|
|
424
|
+
return !allowed || allowed.length === 0 || allowed.includes(reasoning);
|
|
425
|
+
}
|
|
394
426
|
function formatTelegramSessionCost(total) {
|
|
395
427
|
return `$${formatAdaptiveNumber(total, 2, 5)}`;
|
|
396
428
|
}
|
|
@@ -443,17 +475,23 @@ function parseOrThrow(schema, raw, message) {
|
|
|
443
475
|
}
|
|
444
476
|
return parsed.data;
|
|
445
477
|
}
|
|
478
|
+
const TelegramFailureEnvelopeSchema = z.object({
|
|
479
|
+
ok: z.literal(false),
|
|
480
|
+
error_code: z.number().int(),
|
|
481
|
+
description: z.string(),
|
|
482
|
+
parameters: z
|
|
483
|
+
.object({
|
|
484
|
+
retry_after: z.number().int().nonnegative().optional(),
|
|
485
|
+
})
|
|
486
|
+
.optional(),
|
|
487
|
+
});
|
|
446
488
|
const TelegramEnvelopeSchema = z.discriminatedUnion("ok", [
|
|
447
489
|
z.object({
|
|
448
490
|
ok: z.literal(true),
|
|
449
491
|
description: z.string().optional(),
|
|
450
492
|
result: z.unknown(),
|
|
451
493
|
}),
|
|
452
|
-
|
|
453
|
-
ok: z.literal(false),
|
|
454
|
-
description: z.string().optional(),
|
|
455
|
-
result: z.unknown().optional(),
|
|
456
|
-
}),
|
|
494
|
+
TelegramFailureEnvelopeSchema,
|
|
457
495
|
]);
|
|
458
496
|
const TelegramChatSchema = telegramObject({ id: z.number(), type: z.string() });
|
|
459
497
|
const TelegramUserSchema = telegramPartialObject({
|
|
@@ -508,6 +546,26 @@ const TelegramGetFileResultSchema = z.object({ file_path: z.string() });
|
|
|
508
546
|
const TelegramGetMeResultSchema = telegramObject({ username: z.string() });
|
|
509
547
|
const TelegramAckResultSchema = z.literal(true);
|
|
510
548
|
const MAX_TELEGRAM_ERROR_DETAIL_LENGTH = 500;
|
|
549
|
+
export class TelegramRequestError extends Error {
|
|
550
|
+
retryable;
|
|
551
|
+
retryAfterMs;
|
|
552
|
+
constructor(message, options) {
|
|
553
|
+
super(message, { cause: options.cause });
|
|
554
|
+
this.name = "TelegramRequestError";
|
|
555
|
+
this.retryable = options.retryable;
|
|
556
|
+
this.retryAfterMs = options.retryAfterMs;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
class TelegramDeliveryError extends Error {
|
|
560
|
+
attempts;
|
|
561
|
+
retryable;
|
|
562
|
+
constructor(cause, attempts, retryable) {
|
|
563
|
+
super(cause instanceof Error ? cause.message : String(cause), { cause });
|
|
564
|
+
this.name = "TelegramDeliveryError";
|
|
565
|
+
this.attempts = attempts;
|
|
566
|
+
this.retryable = retryable;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
511
569
|
function getErrorCode(error) {
|
|
512
570
|
if (typeof error !== "object" || error === null || !("code" in error)) {
|
|
513
571
|
return undefined;
|
|
@@ -528,23 +586,38 @@ function truncateTelegramErrorDetail(detail) {
|
|
|
528
586
|
}
|
|
529
587
|
return `${detail.slice(0, MAX_TELEGRAM_ERROR_DETAIL_LENGTH)}…`;
|
|
530
588
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
589
|
+
function isRetryableTelegramErrorCode(errorCode) {
|
|
590
|
+
return errorCode === 408 || errorCode === 429 || (errorCode >= 500 && errorCode <= 599);
|
|
591
|
+
}
|
|
592
|
+
function getTelegramRetryAfterMs(envelope) {
|
|
593
|
+
const retryAfterSeconds = envelope.parameters?.retry_after;
|
|
594
|
+
return retryAfterSeconds === undefined ? undefined : retryAfterSeconds * 1000;
|
|
595
|
+
}
|
|
596
|
+
function parseTelegramHttpError(responseText) {
|
|
597
|
+
const trimmedResponseText = responseText.trim();
|
|
598
|
+
if (!trimmedResponseText) {
|
|
599
|
+
return { detail: "" };
|
|
535
600
|
}
|
|
536
601
|
try {
|
|
537
|
-
const parsed =
|
|
538
|
-
if (parsed.success
|
|
539
|
-
return
|
|
602
|
+
const parsed = TelegramFailureEnvelopeSchema.safeParse(JSON.parse(trimmedResponseText));
|
|
603
|
+
if (parsed.success) {
|
|
604
|
+
return {
|
|
605
|
+
detail: truncateTelegramErrorDetail(parsed.data.description.trim()),
|
|
606
|
+
errorCode: parsed.data.error_code,
|
|
607
|
+
retryAfterMs: getTelegramRetryAfterMs(parsed.data),
|
|
608
|
+
};
|
|
540
609
|
}
|
|
541
610
|
}
|
|
542
611
|
catch { }
|
|
543
|
-
return truncateTelegramErrorDetail(
|
|
612
|
+
return { detail: truncateTelegramErrorDetail(trimmedResponseText) };
|
|
613
|
+
}
|
|
614
|
+
function asPermanentTelegramRequestError(error) {
|
|
615
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
616
|
+
return new TelegramRequestError(message, { retryable: false, cause: error });
|
|
544
617
|
}
|
|
545
618
|
function createTelegramApi(botToken) {
|
|
546
619
|
const apiUrl = `https://api.telegram.org/bot${botToken}`;
|
|
547
|
-
async function callTelegramMethod(method, payload, resultSchema) {
|
|
620
|
+
async function callTelegramMethod(method, payload, resultSchema, signal) {
|
|
548
621
|
let response;
|
|
549
622
|
try {
|
|
550
623
|
response = await fetch(`${apiUrl}/${method}`, {
|
|
@@ -553,28 +626,68 @@ function createTelegramApi(botToken) {
|
|
|
553
626
|
"content-type": "application/json",
|
|
554
627
|
},
|
|
555
628
|
body: JSON.stringify(payload),
|
|
629
|
+
signal,
|
|
556
630
|
});
|
|
557
631
|
}
|
|
558
632
|
catch (error) {
|
|
559
|
-
throw new
|
|
633
|
+
throw new TelegramRequestError(`telegram ${method} network request failed: ${formatTelegramNetworkError(error)}`, { retryable: true, cause: error });
|
|
560
634
|
}
|
|
561
635
|
if (!response.ok) {
|
|
562
|
-
|
|
563
|
-
|
|
636
|
+
let responseText;
|
|
637
|
+
try {
|
|
638
|
+
responseText = await response.text();
|
|
639
|
+
}
|
|
640
|
+
catch (error) {
|
|
641
|
+
throw new TelegramRequestError(`telegram ${method} failed: HTTP ${response.status}: response body read failed: ${formatTelegramNetworkError(error)}`, {
|
|
642
|
+
retryable: isRetryableTelegramErrorCode(response.status),
|
|
643
|
+
cause: error,
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
const { detail, errorCode, retryAfterMs } = parseTelegramHttpError(responseText);
|
|
647
|
+
throw new TelegramRequestError(`telegram ${method} failed: HTTP ${response.status}${detail ? `: ${detail}` : ""}`, {
|
|
648
|
+
retryable: isRetryableTelegramErrorCode(response.status) ||
|
|
649
|
+
(errorCode !== undefined && isRetryableTelegramErrorCode(errorCode)),
|
|
650
|
+
...(retryAfterMs === undefined ? {} : { retryAfterMs }),
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
let responseText;
|
|
654
|
+
try {
|
|
655
|
+
responseText = await response.text();
|
|
656
|
+
}
|
|
657
|
+
catch (error) {
|
|
658
|
+
throw new TelegramRequestError(`telegram ${method} response body read failed: ${formatTelegramNetworkError(error)}`, { retryable: true, cause: error });
|
|
564
659
|
}
|
|
565
660
|
let raw;
|
|
566
661
|
try {
|
|
567
|
-
raw =
|
|
662
|
+
raw = JSON.parse(responseText);
|
|
568
663
|
}
|
|
569
|
-
catch {
|
|
570
|
-
throw new
|
|
664
|
+
catch (error) {
|
|
665
|
+
throw new TelegramRequestError(`telegram ${method} returned invalid JSON`, {
|
|
666
|
+
retryable: false,
|
|
667
|
+
cause: error,
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
let envelope;
|
|
671
|
+
try {
|
|
672
|
+
envelope = parseOrThrow(TelegramEnvelopeSchema, raw, `telegram ${method} returned an invalid response payload`);
|
|
673
|
+
}
|
|
674
|
+
catch (error) {
|
|
675
|
+
throw asPermanentTelegramRequestError(error);
|
|
571
676
|
}
|
|
572
|
-
const envelope = parseOrThrow(TelegramEnvelopeSchema, raw, `telegram ${method} returned an invalid response payload`);
|
|
573
677
|
if (!envelope.ok) {
|
|
574
|
-
const detail = envelope.description
|
|
575
|
-
|
|
678
|
+
const detail = envelope.description.trim();
|
|
679
|
+
const retryAfterMs = getTelegramRetryAfterMs(envelope);
|
|
680
|
+
throw new TelegramRequestError(`telegram ${method} request failed${detail ? `: ${detail}` : ""}`, {
|
|
681
|
+
retryable: isRetryableTelegramErrorCode(envelope.error_code),
|
|
682
|
+
...(retryAfterMs === undefined ? {} : { retryAfterMs }),
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
try {
|
|
686
|
+
return parseOrThrow(resultSchema, envelope.result, `telegram ${method} returned an invalid result`);
|
|
687
|
+
}
|
|
688
|
+
catch (error) {
|
|
689
|
+
throw asPermanentTelegramRequestError(error);
|
|
576
690
|
}
|
|
577
|
-
return parseOrThrow(resultSchema, envelope.result, `telegram ${method} returned an invalid result`);
|
|
578
691
|
}
|
|
579
692
|
return {
|
|
580
693
|
async getMe() {
|
|
@@ -592,7 +705,7 @@ function createTelegramApi(botToken) {
|
|
|
592
705
|
chat_id: chatId,
|
|
593
706
|
text,
|
|
594
707
|
...(options?.replyMarkup ? { reply_markup: options.replyMarkup } : {}),
|
|
595
|
-
}, z.unknown());
|
|
708
|
+
}, z.unknown(), options.signal);
|
|
596
709
|
},
|
|
597
710
|
async sendRichMessage(chatId, markdown, options) {
|
|
598
711
|
await callTelegramMethod("sendRichMessage", {
|
|
@@ -601,7 +714,7 @@ function createTelegramApi(botToken) {
|
|
|
601
714
|
markdown,
|
|
602
715
|
},
|
|
603
716
|
...(options?.replyMarkup ? { reply_markup: options.replyMarkup } : {}),
|
|
604
|
-
}, z.unknown());
|
|
717
|
+
}, z.unknown(), options.signal);
|
|
605
718
|
},
|
|
606
719
|
async sendChatAction(chatId, action) {
|
|
607
720
|
await callTelegramMethod("sendChatAction", {
|
|
@@ -681,6 +794,8 @@ class TelegramAdapterImpl {
|
|
|
681
794
|
attachmentTempDirsBySession = new Map();
|
|
682
795
|
updateQueueTailByKey = new Map();
|
|
683
796
|
inFlightUpdateTasks = new Set();
|
|
797
|
+
notificationQueueTailByChat = new Map();
|
|
798
|
+
inFlightNotificationTasks = new Set();
|
|
684
799
|
unsubscribeSessionEvents;
|
|
685
800
|
loopPromise;
|
|
686
801
|
nextUpdateOffset = 0;
|
|
@@ -750,6 +865,7 @@ class TelegramAdapterImpl {
|
|
|
750
865
|
try {
|
|
751
866
|
await this.loopPromise;
|
|
752
867
|
await this.waitForInFlightUpdateTasks();
|
|
868
|
+
await Promise.allSettled(Array.from(this.inFlightNotificationTasks));
|
|
753
869
|
for (const sessionId of Array.from(this.chatsBySession.keys())) {
|
|
754
870
|
this.stopTypingIndicators(sessionId);
|
|
755
871
|
}
|
|
@@ -771,36 +887,38 @@ class TelegramAdapterImpl {
|
|
|
771
887
|
{
|
|
772
888
|
command: "/new",
|
|
773
889
|
description: "start a new session",
|
|
774
|
-
usage: "/new",
|
|
775
890
|
callbackAction: "new",
|
|
776
891
|
handler: async (chatId, args, sourceMessageId) => this.handleNew(chatId, args, sourceMessageId),
|
|
777
892
|
},
|
|
778
893
|
{
|
|
779
894
|
command: "/status",
|
|
780
895
|
description: "show active session status",
|
|
781
|
-
usage: "/status",
|
|
782
896
|
callbackAction: "status",
|
|
783
897
|
handler: async (chatId) => this.handleStatus(chatId),
|
|
784
898
|
},
|
|
785
899
|
{
|
|
786
900
|
command: "/compact",
|
|
787
901
|
description: "compact session context",
|
|
788
|
-
usage: "/compact",
|
|
789
902
|
handler: async (chatId, args) => this.handleCompact(chatId, args),
|
|
790
903
|
},
|
|
791
904
|
{
|
|
792
905
|
command: "/interrupt",
|
|
793
906
|
description: "interrupt active run",
|
|
794
|
-
usage: "/interrupt",
|
|
795
907
|
callbackAction: "interrupt",
|
|
796
908
|
handler: async (chatId) => this.handleInterrupt(chatId),
|
|
797
909
|
},
|
|
798
910
|
];
|
|
911
|
+
for (const reasoning of TELEGRAM_REASONING_EFFORTS) {
|
|
912
|
+
definitions.push({
|
|
913
|
+
command: `/effort_${reasoning}`,
|
|
914
|
+
description: `set reasoning effort to ${reasoning}`,
|
|
915
|
+
handler: async (chatId, args) => this.handleSetReasoning(chatId, reasoning, args),
|
|
916
|
+
});
|
|
917
|
+
}
|
|
799
918
|
for (const projectId of this.allowedProjectIds) {
|
|
800
919
|
definitions.push({
|
|
801
920
|
command: `/use_${projectId}`,
|
|
802
921
|
description: `use ${projectId} for new sessions`,
|
|
803
|
-
usage: `/use_${projectId}`,
|
|
804
922
|
handler: async (chatId, args) => this.handleUseProject(chatId, projectId, args),
|
|
805
923
|
});
|
|
806
924
|
}
|
|
@@ -901,14 +1019,14 @@ class TelegramAdapterImpl {
|
|
|
901
1019
|
timeoutSeconds: this.requestTimeoutSeconds,
|
|
902
1020
|
allowedUpdates: ["message", "callback_query"],
|
|
903
1021
|
}));
|
|
904
|
-
if (updates ===
|
|
1022
|
+
if (updates === ABORTED) {
|
|
905
1023
|
return [];
|
|
906
1024
|
}
|
|
907
1025
|
return updates;
|
|
908
1026
|
}
|
|
909
1027
|
async raceWithAbort(promise) {
|
|
910
1028
|
if (this.abortController.signal.aborted) {
|
|
911
|
-
return
|
|
1029
|
+
return ABORTED;
|
|
912
1030
|
}
|
|
913
1031
|
let abortListener;
|
|
914
1032
|
const abortPromise = new Promise((resolve) => {
|
|
@@ -921,7 +1039,7 @@ class TelegramAdapterImpl {
|
|
|
921
1039
|
this.abortController.signal.removeEventListener("abort", abortListener);
|
|
922
1040
|
}
|
|
923
1041
|
if (raceResult === ABORTED) {
|
|
924
|
-
return
|
|
1042
|
+
return ABORTED;
|
|
925
1043
|
}
|
|
926
1044
|
if (!raceResult.ok) {
|
|
927
1045
|
throw raceResult.error;
|
|
@@ -1467,6 +1585,32 @@ class TelegramAdapterImpl {
|
|
|
1467
1585
|
await this.reply(chatId, `failed to save project preference: ${error instanceof Error ? error.message : String(error)}`);
|
|
1468
1586
|
}
|
|
1469
1587
|
}
|
|
1588
|
+
async handleSetReasoning(chatId, reasoning, args) {
|
|
1589
|
+
if (args.length > 0) {
|
|
1590
|
+
await this.reply(chatId, `usage: /effort_${reasoning}`);
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
const session = await this.requireActiveSession(chatId);
|
|
1594
|
+
if (!session) {
|
|
1595
|
+
return;
|
|
1596
|
+
}
|
|
1597
|
+
try {
|
|
1598
|
+
const sessionManager = this.getSessionManagerForChat(chatId);
|
|
1599
|
+
const snapshot = await sessionManager.getSessionSnapshot(session.id);
|
|
1600
|
+
if (!snapshot) {
|
|
1601
|
+
throw new TelegramSessionManagerError("not_ready", "session is still preparing");
|
|
1602
|
+
}
|
|
1603
|
+
if (!supportsReasoningEffort(snapshot, reasoning)) {
|
|
1604
|
+
await this.reply(chatId, `reasoning effort ${reasoning} is not supported by this session.`);
|
|
1605
|
+
return;
|
|
1606
|
+
}
|
|
1607
|
+
await sessionManager.setReasoning(session.id, reasoning);
|
|
1608
|
+
await this.reply(chatId, `reasoning effort set to ${reasoning}.`);
|
|
1609
|
+
}
|
|
1610
|
+
catch (error) {
|
|
1611
|
+
await this.reply(chatId, this.formatManagerError(error));
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1470
1614
|
async handleNew(chatId, args, sourceMessageId) {
|
|
1471
1615
|
const parsed = this.resolveNewCommand(chatId, args);
|
|
1472
1616
|
if ("error" in parsed) {
|
|
@@ -1495,7 +1639,7 @@ class TelegramAdapterImpl {
|
|
|
1495
1639
|
const session = this.getActiveSession(chatId);
|
|
1496
1640
|
if (!session) {
|
|
1497
1641
|
await this.reply(chatId, preferredProjectId
|
|
1498
|
-
? `new chats will use
|
|
1642
|
+
? `new chats will use ${preferredProjectId}.`
|
|
1499
1643
|
: "no active session. select a project with /use_<project>.");
|
|
1500
1644
|
return;
|
|
1501
1645
|
}
|
|
@@ -1544,10 +1688,10 @@ class TelegramAdapterImpl {
|
|
|
1544
1688
|
const sessionManager = this.getSessionManagerForChat(chatId);
|
|
1545
1689
|
const result = await sessionManager.interruptSession(session.id);
|
|
1546
1690
|
if (!result.interrupted) {
|
|
1547
|
-
await this.reply(chatId, `nothing is running in ${formatTelegramSessionName(result.session)}.`);
|
|
1691
|
+
await this.reply(chatId, `nothing is running in ${formatTelegramSessionName(result.session.id, result.session.projectId)}.`);
|
|
1548
1692
|
return;
|
|
1549
1693
|
}
|
|
1550
|
-
await this.reply(chatId, `stopping ${formatTelegramSessionName(result.session)}.`);
|
|
1694
|
+
await this.reply(chatId, `stopping ${formatTelegramSessionName(result.session.id, result.session.projectId)}.`);
|
|
1551
1695
|
}
|
|
1552
1696
|
catch (error) {
|
|
1553
1697
|
await this.reply(chatId, this.formatManagerError(error));
|
|
@@ -1891,8 +2035,7 @@ class TelegramAdapterImpl {
|
|
|
1891
2035
|
return;
|
|
1892
2036
|
}
|
|
1893
2037
|
this.notifySession(event.sessionId, [
|
|
1894
|
-
|
|
1895
|
-
`project: ${event.targetProjectId}`,
|
|
2038
|
+
`provisioning ${event.targetProjectId} failed in your ${formatTelegramSessionName(event.sessionId, event.projectId)}.`,
|
|
1896
2039
|
truncateText(event.diagnostic, MAX_PROVISION_DIAGNOSTIC_CHARS),
|
|
1897
2040
|
"the session remains available.",
|
|
1898
2041
|
].join("\n"));
|
|
@@ -1975,19 +2118,19 @@ class TelegramAdapterImpl {
|
|
|
1975
2118
|
}
|
|
1976
2119
|
this.notifySession(event.sessionId, isVerbose
|
|
1977
2120
|
? [formatSessionHeadline(event.sessionId, "assistant message"), event.progress.text].join("\n")
|
|
1978
|
-
: event.progress.text, { rich: true });
|
|
2121
|
+
: event.progress.text, { rich: true, messageId: event.progress.messageId });
|
|
1979
2122
|
}
|
|
1980
2123
|
notifyLifecycle(sessionId, projectId, state) {
|
|
1981
|
-
const
|
|
1982
|
-
started: "run started",
|
|
1983
|
-
finished: "run finished",
|
|
1984
|
-
failed: "run failed",
|
|
1985
|
-
}[state];
|
|
2124
|
+
const sessionName = formatTelegramSessionName(sessionId, projectId);
|
|
1986
2125
|
if (state === "failed") {
|
|
1987
|
-
this.notifySession(sessionId, `something went wrong in
|
|
2126
|
+
this.notifySession(sessionId, `something went wrong in your ${sessionName}.`);
|
|
1988
2127
|
return;
|
|
1989
2128
|
}
|
|
1990
|
-
|
|
2129
|
+
const stateLabel = {
|
|
2130
|
+
started: "started a run",
|
|
2131
|
+
finished: "finished its run",
|
|
2132
|
+
}[state];
|
|
2133
|
+
this.notifySession(sessionId, `your ${sessionName} ${stateLabel}.`);
|
|
1991
2134
|
}
|
|
1992
2135
|
notifySession(sessionId, text, options = {}) {
|
|
1993
2136
|
const chatIds = this.chatsBySession.get(sessionId);
|
|
@@ -1995,14 +2138,44 @@ class TelegramAdapterImpl {
|
|
|
1995
2138
|
return;
|
|
1996
2139
|
}
|
|
1997
2140
|
for (const chatId of chatIds) {
|
|
1998
|
-
|
|
1999
|
-
this.log("warn", "failed to send telegram notification", {
|
|
2000
|
-
chatId,
|
|
2001
|
-
cause: error instanceof Error ? error.message : String(error),
|
|
2002
|
-
});
|
|
2003
|
-
});
|
|
2141
|
+
this.enqueueNotification(sessionId, chatId, text, options);
|
|
2004
2142
|
}
|
|
2005
2143
|
}
|
|
2144
|
+
enqueueNotification(sessionId, chatId, text, options) {
|
|
2145
|
+
const previousTask = this.notificationQueueTailByChat.get(chatId) ?? Promise.resolve();
|
|
2146
|
+
const queuedTask = previousTask
|
|
2147
|
+
.then(async () => {
|
|
2148
|
+
if (this.abortController.signal.aborted) {
|
|
2149
|
+
return;
|
|
2150
|
+
}
|
|
2151
|
+
await this.reply(chatId, text, { rich: options.rich });
|
|
2152
|
+
})
|
|
2153
|
+
.catch((error) => {
|
|
2154
|
+
if (this.abortController.signal.aborted) {
|
|
2155
|
+
return;
|
|
2156
|
+
}
|
|
2157
|
+
const deliveryError = error instanceof TelegramDeliveryError
|
|
2158
|
+
? error
|
|
2159
|
+
: new TelegramDeliveryError(error, 1, false);
|
|
2160
|
+
this.log("error", deliveryError.retryable
|
|
2161
|
+
? "telegram notification delivery retries exhausted"
|
|
2162
|
+
: "failed to send telegram notification", {
|
|
2163
|
+
sessionId,
|
|
2164
|
+
chatId,
|
|
2165
|
+
...(options.rich === true ? { messageId: options.messageId } : {}),
|
|
2166
|
+
attempts: deliveryError.attempts,
|
|
2167
|
+
cause: deliveryError.message,
|
|
2168
|
+
});
|
|
2169
|
+
});
|
|
2170
|
+
const trackedTask = queuedTask.finally(() => {
|
|
2171
|
+
this.inFlightNotificationTasks.delete(trackedTask);
|
|
2172
|
+
if (this.notificationQueueTailByChat.get(chatId) === trackedTask) {
|
|
2173
|
+
this.notificationQueueTailByChat.delete(chatId);
|
|
2174
|
+
}
|
|
2175
|
+
});
|
|
2176
|
+
this.notificationQueueTailByChat.set(chatId, trackedTask);
|
|
2177
|
+
this.inFlightNotificationTasks.add(trackedTask);
|
|
2178
|
+
}
|
|
2006
2179
|
startTypingIndicators(sessionId) {
|
|
2007
2180
|
const chatIds = this.chatsBySession.get(sessionId);
|
|
2008
2181
|
if (!chatIds) {
|
|
@@ -2079,7 +2252,7 @@ class TelegramAdapterImpl {
|
|
|
2079
2252
|
}
|
|
2080
2253
|
}
|
|
2081
2254
|
formatSessionReady(sessionId, projectId) {
|
|
2082
|
-
return `all set, your ${projectId}
|
|
2255
|
+
return `all set, your ${formatTelegramSessionName(sessionId, projectId)} is ready.`;
|
|
2083
2256
|
}
|
|
2084
2257
|
formatMessageQueued(sessionId) {
|
|
2085
2258
|
return formatSessionHeadline(sessionId, "message queued");
|
|
@@ -2115,9 +2288,13 @@ class TelegramAdapterImpl {
|
|
|
2115
2288
|
async replyWithRichMessage(chatId, text, replyMarkup) {
|
|
2116
2289
|
const chunks = splitTelegramRichMessage(text);
|
|
2117
2290
|
for (const [index, chunk] of chunks.entries()) {
|
|
2118
|
-
await this.api.sendRichMessage(chatId, chunk, {
|
|
2291
|
+
const result = await this.sendWithRetry((signal) => this.api.sendRichMessage(chatId, chunk, {
|
|
2119
2292
|
replyMarkup: index === chunks.length - 1 ? replyMarkup : undefined,
|
|
2120
|
-
|
|
2293
|
+
signal,
|
|
2294
|
+
}));
|
|
2295
|
+
if (result === ABORTED) {
|
|
2296
|
+
return;
|
|
2297
|
+
}
|
|
2121
2298
|
if (index < chunks.length - 1) {
|
|
2122
2299
|
await this.wait(TELEGRAM_MESSAGE_SPLIT_DELAY_MS);
|
|
2123
2300
|
}
|
|
@@ -2126,14 +2303,89 @@ class TelegramAdapterImpl {
|
|
|
2126
2303
|
async replyWithPlainMessage(chatId, text, replyMarkup) {
|
|
2127
2304
|
const chunks = splitTelegramMessage(text);
|
|
2128
2305
|
for (const [index, chunk] of chunks.entries()) {
|
|
2129
|
-
await this.api.sendMessage(chatId, chunk, {
|
|
2306
|
+
const result = await this.sendWithRetry((signal) => this.api.sendMessage(chatId, chunk, {
|
|
2130
2307
|
replyMarkup: index === chunks.length - 1 ? replyMarkup : undefined,
|
|
2131
|
-
|
|
2308
|
+
signal,
|
|
2309
|
+
}));
|
|
2310
|
+
if (result === ABORTED) {
|
|
2311
|
+
return;
|
|
2312
|
+
}
|
|
2132
2313
|
if (index < chunks.length - 1) {
|
|
2133
2314
|
await this.wait(TELEGRAM_MESSAGE_SPLIT_DELAY_MS);
|
|
2134
2315
|
}
|
|
2135
2316
|
}
|
|
2136
2317
|
}
|
|
2318
|
+
async sendWithRetry(send) {
|
|
2319
|
+
for (let attempt = 1;; attempt += 1) {
|
|
2320
|
+
try {
|
|
2321
|
+
const result = await this.runDeliveryAttempt(send);
|
|
2322
|
+
if (result === ABORTED) {
|
|
2323
|
+
return ABORTED;
|
|
2324
|
+
}
|
|
2325
|
+
return;
|
|
2326
|
+
}
|
|
2327
|
+
catch (error) {
|
|
2328
|
+
if (this.abortController.signal.aborted) {
|
|
2329
|
+
return ABORTED;
|
|
2330
|
+
}
|
|
2331
|
+
const retryDelayMs = TELEGRAM_DELIVERY_RETRY_DELAYS_MS[attempt - 1];
|
|
2332
|
+
const retryable = error instanceof TelegramRequestError && error.retryable;
|
|
2333
|
+
if (!retryable || retryDelayMs === undefined) {
|
|
2334
|
+
throw new TelegramDeliveryError(error, attempt, retryable);
|
|
2335
|
+
}
|
|
2336
|
+
await this.wait(Math.max(retryDelayMs, error.retryAfterMs ?? 0));
|
|
2337
|
+
if (this.abortController.signal.aborted) {
|
|
2338
|
+
return ABORTED;
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
async runDeliveryAttempt(send) {
|
|
2344
|
+
if (this.abortController.signal.aborted) {
|
|
2345
|
+
return ABORTED;
|
|
2346
|
+
}
|
|
2347
|
+
const attemptController = new AbortController();
|
|
2348
|
+
let abortListener;
|
|
2349
|
+
const abortPromise = new Promise((resolve) => {
|
|
2350
|
+
abortListener = () => {
|
|
2351
|
+
resolve(ABORTED);
|
|
2352
|
+
attemptController.abort();
|
|
2353
|
+
};
|
|
2354
|
+
this.abortController.signal.addEventListener("abort", abortListener, { once: true });
|
|
2355
|
+
});
|
|
2356
|
+
let timeout;
|
|
2357
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
2358
|
+
timeout = setTimeout(() => {
|
|
2359
|
+
resolve(TIMED_OUT);
|
|
2360
|
+
attemptController.abort();
|
|
2361
|
+
}, TELEGRAM_DELIVERY_ATTEMPT_TIMEOUT_MS);
|
|
2362
|
+
timeout.unref?.();
|
|
2363
|
+
});
|
|
2364
|
+
let sendPromise;
|
|
2365
|
+
try {
|
|
2366
|
+
sendPromise = send(attemptController.signal);
|
|
2367
|
+
}
|
|
2368
|
+
catch (error) {
|
|
2369
|
+
sendPromise = Promise.reject(error);
|
|
2370
|
+
}
|
|
2371
|
+
const settled = sendPromise.then(() => ({ ok: true }), (error) => ({ ok: false, error }));
|
|
2372
|
+
const raceResult = await Promise.race([settled, abortPromise, timeoutPromise]);
|
|
2373
|
+
if (timeout) {
|
|
2374
|
+
clearTimeout(timeout);
|
|
2375
|
+
}
|
|
2376
|
+
if (abortListener) {
|
|
2377
|
+
this.abortController.signal.removeEventListener("abort", abortListener);
|
|
2378
|
+
}
|
|
2379
|
+
if (raceResult === ABORTED) {
|
|
2380
|
+
return ABORTED;
|
|
2381
|
+
}
|
|
2382
|
+
if (raceResult === TIMED_OUT) {
|
|
2383
|
+
throw new TelegramRequestError(`telegram delivery attempt timed out after ${TELEGRAM_DELIVERY_ATTEMPT_TIMEOUT_MS / 1000} seconds`, { retryable: true });
|
|
2384
|
+
}
|
|
2385
|
+
if (!raceResult.ok) {
|
|
2386
|
+
throw raceResult.error;
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2137
2389
|
async wait(durationMs) {
|
|
2138
2390
|
if (durationMs <= 0 || this.abortController.signal.aborted) {
|
|
2139
2391
|
return;
|