@llblab/pi-kit 0.6.0 → 0.7.1
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/CHANGELOG.md +8 -0
- package/README.md +4 -4
- package/node_modules/@llblab/pi-actors/AGENTS.md +1 -1
- package/node_modules/@llblab/pi-actors/CHANGELOG.md +6 -0
- package/node_modules/@llblab/pi-actors/README.md +1 -1
- package/node_modules/@llblab/pi-actors/dist/lib/async-runs.d.ts +3 -0
- package/node_modules/@llblab/pi-actors/dist/lib/async-runs.js +14 -1
- package/node_modules/@llblab/pi-actors/dist/lib/command-templates.js +45 -3
- package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.js +1 -1
- package/node_modules/@llblab/pi-actors/dist/lib/observability.d.ts +16 -3
- package/node_modules/@llblab/pi-actors/dist/lib/observability.js +92 -7
- package/node_modules/@llblab/pi-actors/dist/lib/pi.d.ts +0 -1
- package/node_modules/@llblab/pi-actors/dist/lib/pi.js +15 -24
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.d.ts +17 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.js +44 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.d.ts +4 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.js +102 -4
- package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.js +58 -39
- package/node_modules/@llblab/pi-actors/dist/lib/runtime.js +14 -6
- package/node_modules/@llblab/pi-actors/dist/skills/actors/SKILL.md +1 -1
- package/node_modules/@llblab/pi-actors/docs/async-runs.md +1 -1
- package/node_modules/@llblab/pi-actors/docs/coordinator-delivery.md +18 -23
- package/node_modules/@llblab/pi-actors/lib/async-runs.ts +18 -1
- package/node_modules/@llblab/pi-actors/lib/command-templates.ts +41 -3
- package/node_modules/@llblab/pi-actors/lib/extension-runtime.ts +1 -1
- package/node_modules/@llblab/pi-actors/lib/observability.ts +119 -5
- package/node_modules/@llblab/pi-actors/lib/pi.ts +15 -28
- package/node_modules/@llblab/pi-actors/lib/run-delivery-lineage.ts +68 -0
- package/node_modules/@llblab/pi-actors/lib/run-delivery.ts +120 -4
- package/node_modules/@llblab/pi-actors/lib/run-ui-runtime.ts +69 -44
- package/node_modules/@llblab/pi-actors/lib/runtime.ts +17 -6
- package/node_modules/@llblab/pi-actors/package.json +1 -1
- package/node_modules/@llblab/pi-actors/skills/actors/SKILL.md +1 -1
- package/node_modules/@llblab/pi-state-flow/AGENTS.md +33 -12
- package/node_modules/@llblab/pi-state-flow/BACKLOG.md +125 -2
- package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +87 -45
- package/node_modules/@llblab/pi-state-flow/README.md +203 -107
- package/node_modules/@llblab/pi-state-flow/docs/README.md +4 -0
- package/node_modules/@llblab/pi-state-flow/docs/architecture.md +188 -0
- package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +42 -0
- package/node_modules/@llblab/pi-state-flow/index.ts +164 -2
- package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +138 -0
- package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +273 -0
- package/node_modules/@llblab/pi-state-flow/lib/config.ts +48 -0
- package/node_modules/@llblab/pi-state-flow/lib/context.ts +18 -5
- package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +268 -0
- package/node_modules/@llblab/pi-state-flow/lib/discovery.ts +117 -0
- package/node_modules/@llblab/pi-state-flow/lib/durable.ts +562 -0
- package/node_modules/@llblab/pi-state-flow/lib/episode.ts +24 -12
- package/node_modules/@llblab/pi-state-flow/lib/extension.ts +606 -70
- package/node_modules/@llblab/pi-state-flow/lib/git.ts +666 -0
- package/node_modules/@llblab/pi-state-flow/lib/history.ts +95 -0
- package/node_modules/@llblab/pi-state-flow/lib/json.ts +24 -0
- package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +141 -0
- package/node_modules/@llblab/pi-state-flow/lib/memory.ts +52 -0
- package/node_modules/@llblab/pi-state-flow/lib/migration.ts +88 -0
- package/node_modules/@llblab/pi-state-flow/lib/publication.ts +296 -0
- package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +23 -7
- package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +79 -0
- package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +264 -0
- package/node_modules/@llblab/pi-state-flow/lib/session.ts +6 -0
- package/node_modules/@llblab/pi-state-flow/lib/skills.ts +99 -7
- package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +305 -48
- package/node_modules/@llblab/pi-state-flow/lib/state.ts +74 -7
- package/node_modules/@llblab/pi-state-flow/lib/status.ts +125 -6
- package/node_modules/@llblab/pi-state-flow/lib/storage.ts +196 -0
- package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +233 -0
- package/node_modules/@llblab/pi-state-flow/lib/terminal.ts +70 -24
- package/node_modules/@llblab/pi-state-flow/lib/transition.ts +254 -29
- package/node_modules/@llblab/pi-state-flow/package.json +8 -2
- package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +128 -0
- package/node_modules/@llblab/pi-telegram/AGENTS.md +14 -9
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +23 -5
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +18 -0
- package/node_modules/@llblab/pi-telegram/README.md +13 -9
- package/node_modules/@llblab/pi-telegram/docs/README.md +1 -0
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +227 -23
- package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +70 -19
- package/node_modules/@llblab/pi-telegram/docs/outbound.md +6 -6
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +13 -6
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +3 -1
- package/node_modules/@llblab/pi-telegram/index.ts +4 -1418
- package/node_modules/@llblab/pi-telegram/lib/agent-messages.ts +6 -3
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +46 -1
- package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +32 -19
- package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +600 -135
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +962 -55
- package/node_modules/@llblab/pi-telegram/lib/bus.ts +355 -26
- package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +718 -0
- package/node_modules/@llblab/pi-telegram/lib/commands.ts +237 -11
- package/node_modules/@llblab/pi-telegram/lib/config.ts +242 -26
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +1851 -0
- package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +20 -2
- package/node_modules/@llblab/pi-telegram/lib/journal.ts +2184 -126
- package/node_modules/@llblab/pi-telegram/lib/locks.ts +44 -2
- package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +152 -13
- package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +97 -10
- package/node_modules/@llblab/pi-telegram/lib/paths.ts +29 -0
- package/node_modules/@llblab/pi-telegram/lib/polling.ts +85 -17
- package/node_modules/@llblab/pi-telegram/lib/preview.ts +17 -0
- package/node_modules/@llblab/pi-telegram/lib/prompts.ts +6 -2
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +118 -26
- package/node_modules/@llblab/pi-telegram/lib/rendering.ts +4 -1
- package/node_modules/@llblab/pi-telegram/lib/replies.ts +21 -2
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +344 -112
- package/node_modules/@llblab/pi-telegram/lib/setup.ts +44 -4
- package/node_modules/@llblab/pi-telegram/lib/status.ts +51 -4
- package/node_modules/@llblab/pi-telegram/lib/sync.ts +308 -39
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +353 -22
- package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +664 -0
- package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +226 -0
- package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +118 -0
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +1686 -129
- package/node_modules/@llblab/pi-telegram/lib/turns.ts +7 -0
- package/node_modules/@llblab/pi-telegram/lib/updates.ts +1319 -97
- package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +1643 -0
- package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +968 -0
- package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +84 -0
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/screenshot.png +0 -0
- package/node_modules/@llblab/pi-telegram/scripts/measure-bus.mjs +83 -0
- package/node_modules/@llblab/pi-telegram/scripts/measure-workspace.mjs +101 -0
- package/node_modules/@llblab/{skills → pi-telegram/skills}/show-me/SKILL.md +28 -6
- package/node_modules/@llblab/pi-telegram/skills/show-me/references/telegram-surfaces.md +43 -0
- package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/delivery-and-threads.md +1 -1
- package/node_modules/@llblab/skills/package.json +2 -3
- package/package.json +6 -5
- /package/node_modules/@llblab/pi-telegram/lib/{logs.ts → logging.ts} +0 -0
|
@@ -37,6 +37,7 @@ function getTelegramApiTempDir(): string {
|
|
|
37
37
|
return resolveTelegramTempDir();
|
|
38
38
|
}
|
|
39
39
|
const TELEGRAM_TEMP_FILE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
40
|
+
const activeTelegramApiWorkspaceAdmissionOperationIds = new Set<string>();
|
|
40
41
|
const TELEGRAM_TEMP_SCRATCH_FILE_PATTERN =
|
|
41
42
|
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}-/u;
|
|
42
43
|
const TELEGRAM_INBOUND_FILE_MAX_BYTES = getTelegramInboundFileByteLimitFromEnv(
|
|
@@ -199,6 +200,10 @@ export interface TelegramSentMessage {
|
|
|
199
200
|
message_id: number;
|
|
200
201
|
}
|
|
201
202
|
|
|
203
|
+
export interface TelegramSentGuestMessage {
|
|
204
|
+
inline_message_id?: string;
|
|
205
|
+
}
|
|
206
|
+
|
|
202
207
|
export interface TelegramReplyParameters {
|
|
203
208
|
message_id: number;
|
|
204
209
|
allow_sending_without_reply?: boolean;
|
|
@@ -426,6 +431,11 @@ export interface TelegramAnswerGuestQueryOptions {
|
|
|
426
431
|
result?: TelegramGuestCachedMediaResult;
|
|
427
432
|
}
|
|
428
433
|
|
|
434
|
+
export interface TelegramEditGuestInlineMessageContent {
|
|
435
|
+
text?: string;
|
|
436
|
+
richMessage?: TelegramInputRichMessage;
|
|
437
|
+
}
|
|
438
|
+
|
|
429
439
|
export interface TelegramAnswerCallbackQueryOptions {
|
|
430
440
|
recordRuntimeEvent?: (
|
|
431
441
|
kind: "api",
|
|
@@ -465,6 +475,263 @@ export interface TelegramApiClient {
|
|
|
465
475
|
) => Promise<void>;
|
|
466
476
|
}
|
|
467
477
|
|
|
478
|
+
export interface TelegramApiTargetActivityRuntime {
|
|
479
|
+
begin: (
|
|
480
|
+
method: string,
|
|
481
|
+
body: Record<string, unknown>,
|
|
482
|
+
) => () => void;
|
|
483
|
+
hasPendingTarget: (target: { chatId: number; threadId?: number }) => boolean;
|
|
484
|
+
listPendingTargets: () => { chatId: number; threadId: number }[];
|
|
485
|
+
listPendingChats: () => number[];
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function parseTelegramApiTargetInteger(value: unknown): number | undefined {
|
|
489
|
+
const parsed = typeof value === "number" ? value :
|
|
490
|
+
typeof value === "string" && /^-?\d+$/u.test(value) ? Number(value) : undefined;
|
|
491
|
+
return parsed !== undefined && Number.isSafeInteger(parsed) ? parsed : undefined;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export function createTelegramApiTargetActivityRuntime(): TelegramApiTargetActivityRuntime {
|
|
495
|
+
const pending = new Map<string, {
|
|
496
|
+
target: { chatId: number; threadId: number };
|
|
497
|
+
count: number;
|
|
498
|
+
}>();
|
|
499
|
+
const pendingChats = new Map<number, number>();
|
|
500
|
+
const messageScopedMethods = new Set([
|
|
501
|
+
"deleteMessage",
|
|
502
|
+
"editMessageCaption",
|
|
503
|
+
"editMessageReplyMarkup",
|
|
504
|
+
"editMessageText",
|
|
505
|
+
]);
|
|
506
|
+
return {
|
|
507
|
+
begin(method, body) {
|
|
508
|
+
const chatId = parseTelegramApiTargetInteger(body.chat_id);
|
|
509
|
+
const threadId = parseTelegramApiTargetInteger(body.message_thread_id);
|
|
510
|
+
const messageId = parseTelegramApiTargetInteger(body.message_id);
|
|
511
|
+
const chatScoped = chatId !== undefined && threadId === undefined &&
|
|
512
|
+
messageId !== undefined && messageScopedMethods.has(method);
|
|
513
|
+
if (chatId === undefined || (threadId === undefined && !chatScoped)) {
|
|
514
|
+
return () => undefined;
|
|
515
|
+
}
|
|
516
|
+
const key = threadId === undefined ? undefined : `${chatId}:${threadId}`;
|
|
517
|
+
if (key) {
|
|
518
|
+
const existing = pending.get(key);
|
|
519
|
+
if (existing) existing.count += 1;
|
|
520
|
+
else pending.set(key, { target: { chatId, threadId: threadId! }, count: 1 });
|
|
521
|
+
} else {
|
|
522
|
+
pendingChats.set(chatId, (pendingChats.get(chatId) ?? 0) + 1);
|
|
523
|
+
}
|
|
524
|
+
let completed = false;
|
|
525
|
+
return () => {
|
|
526
|
+
if (completed) return;
|
|
527
|
+
completed = true;
|
|
528
|
+
if (!key) {
|
|
529
|
+
const count = pendingChats.get(chatId);
|
|
530
|
+
if (!count || count <= 1) pendingChats.delete(chatId);
|
|
531
|
+
else pendingChats.set(chatId, count - 1);
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
const current = pending.get(key);
|
|
535
|
+
if (!current || current.count <= 1) pending.delete(key);
|
|
536
|
+
else current.count -= 1;
|
|
537
|
+
};
|
|
538
|
+
},
|
|
539
|
+
hasPendingTarget(target) {
|
|
540
|
+
return pendingChats.has(target.chatId) ||
|
|
541
|
+
(target.threadId !== undefined && pending.has(`${target.chatId}:${target.threadId}`));
|
|
542
|
+
},
|
|
543
|
+
listPendingTargets() {
|
|
544
|
+
return Array.from(pending.values(), ({ target }) => ({ ...target }));
|
|
545
|
+
},
|
|
546
|
+
listPendingChats() {
|
|
547
|
+
return Array.from(pendingChats.keys());
|
|
548
|
+
},
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
export function createTelegramApiTargetTrackingClient(
|
|
553
|
+
client: TelegramApiClient,
|
|
554
|
+
activity: TelegramApiTargetActivityRuntime,
|
|
555
|
+
): TelegramApiClient {
|
|
556
|
+
const track = async <T>(
|
|
557
|
+
method: string,
|
|
558
|
+
body: Record<string, unknown>,
|
|
559
|
+
operation: () => Promise<T>,
|
|
560
|
+
): Promise<T> => {
|
|
561
|
+
const end = activity.begin(method, body);
|
|
562
|
+
try {
|
|
563
|
+
return await operation();
|
|
564
|
+
} finally {
|
|
565
|
+
end();
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
return {
|
|
569
|
+
call: (method, body, options) => track(
|
|
570
|
+
method,
|
|
571
|
+
body,
|
|
572
|
+
() => client.call(method, body, options),
|
|
573
|
+
),
|
|
574
|
+
callMultipart: (method, fields, fileField, filePath, fileName, options) =>
|
|
575
|
+
track(method, fields, () => client.callMultipart(
|
|
576
|
+
method, fields, fileField, filePath, fileName, options,
|
|
577
|
+
)),
|
|
578
|
+
downloadFile: client.downloadFile,
|
|
579
|
+
answerCallbackQuery: client.answerCallbackQuery,
|
|
580
|
+
...(client.answerGuestQuery
|
|
581
|
+
? { answerGuestQuery: client.answerGuestQuery }
|
|
582
|
+
: {}),
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export type TelegramApiWorkspaceAdmissionScopeLike =
|
|
587
|
+
| { kind: "target"; target: { chatId: number; threadId: number } }
|
|
588
|
+
| { kind: "chat"; chatId: number }
|
|
589
|
+
| { kind: "profile" };
|
|
590
|
+
|
|
591
|
+
export interface TelegramApiWorkspaceAdmissionLeaseLike {
|
|
592
|
+
operationId: string;
|
|
593
|
+
operationKind: string;
|
|
594
|
+
profileKey: string;
|
|
595
|
+
scope: TelegramApiWorkspaceAdmissionScopeLike;
|
|
596
|
+
owner: { processId: number; processBirthId: string };
|
|
597
|
+
acquiredAtMs: number;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
export interface TelegramApiWorkspaceAdmissionPort {
|
|
601
|
+
acquireAdmission: (input: {
|
|
602
|
+
operationId: string;
|
|
603
|
+
operationKind: string;
|
|
604
|
+
scope: TelegramApiWorkspaceAdmissionScopeLike;
|
|
605
|
+
}) =>
|
|
606
|
+
| {
|
|
607
|
+
kind: "acquired";
|
|
608
|
+
lease: TelegramApiWorkspaceAdmissionLeaseLike;
|
|
609
|
+
resumed: boolean;
|
|
610
|
+
}
|
|
611
|
+
| { kind: "blocked"; reason: "retirement-fenced" };
|
|
612
|
+
releaseAdmission: (expected: TelegramApiWorkspaceAdmissionLeaseLike) => boolean;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
export class TelegramApiWorkspaceAdmissionError extends Error {
|
|
616
|
+
readonly code: "blocked" | "unavailable" | "release-lost" | "duplicate-operation";
|
|
617
|
+
|
|
618
|
+
constructor(code: TelegramApiWorkspaceAdmissionError["code"], message: string) {
|
|
619
|
+
super(message);
|
|
620
|
+
this.name = "TelegramApiWorkspaceAdmissionError";
|
|
621
|
+
this.code = code;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
export function getTelegramApiWorkspaceAdmissionScope(
|
|
626
|
+
body: Record<string, unknown>,
|
|
627
|
+
): TelegramApiWorkspaceAdmissionScopeLike | undefined {
|
|
628
|
+
if (!("chat_id" in body)) return undefined;
|
|
629
|
+
const chatId = parseTelegramApiTargetInteger(body.chat_id);
|
|
630
|
+
if (chatId === undefined || chatId === 0) return { kind: "profile" };
|
|
631
|
+
if (!("message_thread_id" in body) || body.message_thread_id === undefined) {
|
|
632
|
+
return { kind: "chat", chatId };
|
|
633
|
+
}
|
|
634
|
+
const threadId = parseTelegramApiTargetInteger(body.message_thread_id);
|
|
635
|
+
if (threadId === undefined || threadId <= 0) return { kind: "profile" };
|
|
636
|
+
return { kind: "target", target: { chatId, threadId } };
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
export function createTelegramApiWorkspaceAdmissionClient(
|
|
640
|
+
client: TelegramApiClient,
|
|
641
|
+
admission:
|
|
642
|
+
| TelegramApiWorkspaceAdmissionPort
|
|
643
|
+
| (() => TelegramApiWorkspaceAdmissionPort | undefined),
|
|
644
|
+
options: {
|
|
645
|
+
onReleaseError?: (error: unknown, method: string) => void;
|
|
646
|
+
createOperationId?: () => string;
|
|
647
|
+
} = {},
|
|
648
|
+
): TelegramApiClient {
|
|
649
|
+
const admit = async <T>(
|
|
650
|
+
method: string,
|
|
651
|
+
body: Record<string, unknown>,
|
|
652
|
+
operation: () => Promise<T>,
|
|
653
|
+
): Promise<T> => {
|
|
654
|
+
const scope = getTelegramApiWorkspaceAdmissionScope(body);
|
|
655
|
+
if (!scope) return operation();
|
|
656
|
+
const currentAdmission =
|
|
657
|
+
typeof admission === "function" ? admission() : admission;
|
|
658
|
+
if (!currentAdmission) {
|
|
659
|
+
throw new TelegramApiWorkspaceAdmissionError(
|
|
660
|
+
"unavailable",
|
|
661
|
+
"Telegram API Workspace admission authority is unavailable.",
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
const operationId = `api:${(options.createOperationId ?? randomUUID)()}`;
|
|
665
|
+
if (activeTelegramApiWorkspaceAdmissionOperationIds.has(operationId)) {
|
|
666
|
+
throw new TelegramApiWorkspaceAdmissionError(
|
|
667
|
+
"duplicate-operation",
|
|
668
|
+
"Telegram API Workspace admission operation is already active.",
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
activeTelegramApiWorkspaceAdmissionOperationIds.add(operationId);
|
|
672
|
+
try {
|
|
673
|
+
const acquired = currentAdmission.acquireAdmission({
|
|
674
|
+
operationId,
|
|
675
|
+
operationKind: `api.${method}`,
|
|
676
|
+
scope,
|
|
677
|
+
});
|
|
678
|
+
if (acquired.kind === "blocked") {
|
|
679
|
+
throw new TelegramApiWorkspaceAdmissionError(
|
|
680
|
+
"blocked",
|
|
681
|
+
"Telegram API target is temporarily unavailable during Workspace retirement.",
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
try {
|
|
685
|
+
return await operation();
|
|
686
|
+
} finally {
|
|
687
|
+
try {
|
|
688
|
+
if (!currentAdmission.releaseAdmission(acquired.lease)) {
|
|
689
|
+
throw new TelegramApiWorkspaceAdmissionError(
|
|
690
|
+
"release-lost",
|
|
691
|
+
"Telegram API Workspace admission lease disappeared before release.",
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
} catch (error) {
|
|
695
|
+
try {
|
|
696
|
+
options.onReleaseError?.(error, method);
|
|
697
|
+
} catch {
|
|
698
|
+
// Diagnostics cannot convert an already-settled API request into replay.
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
} finally {
|
|
703
|
+
activeTelegramApiWorkspaceAdmissionOperationIds.delete(operationId);
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
return {
|
|
707
|
+
call: (method, body, callOptions) =>
|
|
708
|
+
admit(method, body, () => client.call(method, body, callOptions)),
|
|
709
|
+
callMultipart: (
|
|
710
|
+
method,
|
|
711
|
+
fields,
|
|
712
|
+
fileField,
|
|
713
|
+
filePath,
|
|
714
|
+
fileName,
|
|
715
|
+
callOptions,
|
|
716
|
+
) =>
|
|
717
|
+
admit(method, fields, () =>
|
|
718
|
+
client.callMultipart(
|
|
719
|
+
method,
|
|
720
|
+
fields,
|
|
721
|
+
fileField,
|
|
722
|
+
filePath,
|
|
723
|
+
fileName,
|
|
724
|
+
callOptions,
|
|
725
|
+
),
|
|
726
|
+
),
|
|
727
|
+
downloadFile: client.downloadFile,
|
|
728
|
+
answerCallbackQuery: client.answerCallbackQuery,
|
|
729
|
+
...(client.answerGuestQuery
|
|
730
|
+
? { answerGuestQuery: client.answerGuestQuery }
|
|
731
|
+
: {}),
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
|
|
468
735
|
export interface TelegramBridgeApiRuntimeDeps {
|
|
469
736
|
captureRequestErrorHandler?: (body: Record<string, unknown>) =>
|
|
470
737
|
((error: unknown) => Promise<void>) | undefined;
|
|
@@ -552,6 +819,21 @@ export interface TelegramBridgeApiRuntime {
|
|
|
552
819
|
text?: string,
|
|
553
820
|
options?: TelegramAnswerGuestQueryOptions,
|
|
554
821
|
) => Promise<void>;
|
|
822
|
+
/**
|
|
823
|
+
* Temporary Guest Mode ACK experiment: answers the guest query and returns
|
|
824
|
+
* the sent inline message id so the final answer can edit that early reply.
|
|
825
|
+
* Requires direct transport ownership; Telegram does not document editing
|
|
826
|
+
* guest answers, so this exists only to falsify that behavior live.
|
|
827
|
+
*/
|
|
828
|
+
answerGuestQueryForInlineMessage: (
|
|
829
|
+
guestQueryId: string,
|
|
830
|
+
text?: string,
|
|
831
|
+
options?: TelegramAnswerGuestQueryOptions,
|
|
832
|
+
) => Promise<string | undefined>;
|
|
833
|
+
editGuestInlineMessage: (
|
|
834
|
+
inlineMessageId: string,
|
|
835
|
+
content: TelegramEditGuestInlineMessageContent,
|
|
836
|
+
) => Promise<void>;
|
|
555
837
|
deleteMessage: (chatId: number, messageId: number) => Promise<void>;
|
|
556
838
|
prepareTempDir: () => Promise<number>;
|
|
557
839
|
}
|
|
@@ -1411,7 +1693,7 @@ export function createTelegramNativeMarkdownDraftSender(deps: {
|
|
|
1411
1693
|
return deps.sendRichMessageDraft({
|
|
1412
1694
|
chat_id: chatId,
|
|
1413
1695
|
draft_id: draftId,
|
|
1414
|
-
rich_message: { markdown: text
|
|
1696
|
+
rich_message: { markdown: text },
|
|
1415
1697
|
...(options?.message_thread_id !== undefined
|
|
1416
1698
|
? { message_thread_id: options.message_thread_id }
|
|
1417
1699
|
: {}),
|
|
@@ -1442,15 +1724,57 @@ export function createTelegramAssistantDraftSender(deps: {
|
|
|
1442
1724
|
};
|
|
1443
1725
|
}
|
|
1444
1726
|
|
|
1727
|
+
export function buildTelegramAnswerGuestQueryBody(
|
|
1728
|
+
guestQueryId: string,
|
|
1729
|
+
text?: string,
|
|
1730
|
+
options?: TelegramAnswerGuestQueryOptions,
|
|
1731
|
+
): Record<string, unknown> {
|
|
1732
|
+
const body: Record<string, unknown> = { guest_query_id: guestQueryId };
|
|
1733
|
+
if (options?.result) {
|
|
1734
|
+
body.result = options.result;
|
|
1735
|
+
} else if (text !== undefined || options?.richMessage) {
|
|
1736
|
+
const inputContent: Record<string, unknown> = options?.richMessage
|
|
1737
|
+
? { rich_message: options.richMessage }
|
|
1738
|
+
: { message_text: text };
|
|
1739
|
+
if (!options?.richMessage && options?.parseMode) {
|
|
1740
|
+
inputContent.parse_mode = options.parseMode;
|
|
1741
|
+
}
|
|
1742
|
+
body.result = {
|
|
1743
|
+
type: "article",
|
|
1744
|
+
id: "1",
|
|
1745
|
+
title: "Response",
|
|
1746
|
+
input_message_content: inputContent,
|
|
1747
|
+
};
|
|
1748
|
+
}
|
|
1749
|
+
return body;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1445
1752
|
export function createDefaultTelegramBridgeApiRuntime(deps: {
|
|
1446
1753
|
getBotToken: () => string | undefined;
|
|
1447
1754
|
recordRuntimeEvent: TelegramBridgeApiRuntimeDeps["recordRuntimeEvent"];
|
|
1448
1755
|
captureRequestErrorHandler?: TelegramBridgeApiRuntimeDeps["captureRequestErrorHandler"];
|
|
1756
|
+
targetActivity?: TelegramApiTargetActivityRuntime;
|
|
1757
|
+
workspaceAdmission?:
|
|
1758
|
+
| TelegramApiWorkspaceAdmissionPort
|
|
1759
|
+
| (() => TelegramApiWorkspaceAdmissionPort | undefined);
|
|
1449
1760
|
}): TelegramBridgeApiRuntime {
|
|
1761
|
+
const client = createTelegramApiClient(deps.getBotToken, {
|
|
1762
|
+
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
1763
|
+
});
|
|
1764
|
+
const admittedClient = deps.workspaceAdmission
|
|
1765
|
+
? createTelegramApiWorkspaceAdmissionClient(client, deps.workspaceAdmission, {
|
|
1766
|
+
onReleaseError(error, method) {
|
|
1767
|
+
deps.recordRuntimeEvent("api", error, {
|
|
1768
|
+
phase: "workspace-admission-release",
|
|
1769
|
+
method,
|
|
1770
|
+
});
|
|
1771
|
+
},
|
|
1772
|
+
})
|
|
1773
|
+
: client;
|
|
1450
1774
|
return createTelegramBridgeApiRuntime({
|
|
1451
|
-
client:
|
|
1452
|
-
|
|
1453
|
-
|
|
1775
|
+
client: deps.targetActivity
|
|
1776
|
+
? createTelegramApiTargetTrackingClient(admittedClient, deps.targetActivity)
|
|
1777
|
+
: admittedClient,
|
|
1454
1778
|
tempDir: getTelegramApiTempDir(),
|
|
1455
1779
|
maxFileSizeBytes: TELEGRAM_INBOUND_FILE_MAX_BYTES,
|
|
1456
1780
|
tempFileMaxAgeMs: TELEGRAM_TEMP_FILE_MAX_AGE_MS,
|
|
@@ -1746,25 +2070,32 @@ export function createTelegramBridgeApiRuntime(
|
|
|
1746
2070
|
guestQueryId: string,
|
|
1747
2071
|
text: string | undefined,
|
|
1748
2072
|
options: TelegramAnswerGuestQueryOptions | undefined,
|
|
2073
|
+
) =>
|
|
2074
|
+
callRecorded<void>(
|
|
2075
|
+
"answerGuestQuery",
|
|
2076
|
+
buildTelegramAnswerGuestQueryBody(guestQueryId, text, options),
|
|
2077
|
+
),
|
|
2078
|
+
answerGuestQueryForInlineMessage: async (
|
|
2079
|
+
guestQueryId: string,
|
|
2080
|
+
text: string | undefined,
|
|
2081
|
+
options: TelegramAnswerGuestQueryOptions | undefined,
|
|
1749
2082
|
) => {
|
|
1750
|
-
const
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
}
|
|
1767
|
-
return callRecorded<void>("answerGuestQuery", body);
|
|
2083
|
+
const sent = await callRecorded<TelegramSentGuestMessage | undefined>(
|
|
2084
|
+
"answerGuestQuery",
|
|
2085
|
+
buildTelegramAnswerGuestQueryBody(guestQueryId, text, options),
|
|
2086
|
+
);
|
|
2087
|
+
return sent?.inline_message_id;
|
|
2088
|
+
},
|
|
2089
|
+
editGuestInlineMessage: async (
|
|
2090
|
+
inlineMessageId: string,
|
|
2091
|
+
content: TelegramEditGuestInlineMessageContent,
|
|
2092
|
+
) => {
|
|
2093
|
+
await callRecorded("editMessageText", {
|
|
2094
|
+
inline_message_id: inlineMessageId,
|
|
2095
|
+
...(content.richMessage
|
|
2096
|
+
? { rich_message: content.richMessage }
|
|
2097
|
+
: { text: content.text }),
|
|
2098
|
+
});
|
|
1768
2099
|
},
|
|
1769
2100
|
prepareTempDir: () =>
|
|
1770
2101
|
prepareTelegramTempDir(deps.tempDir, deps.tempFileMaxAgeMs),
|