@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.
Files changed (129) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +4 -4
  3. package/node_modules/@llblab/pi-actors/AGENTS.md +1 -1
  4. package/node_modules/@llblab/pi-actors/CHANGELOG.md +6 -0
  5. package/node_modules/@llblab/pi-actors/README.md +1 -1
  6. package/node_modules/@llblab/pi-actors/dist/lib/async-runs.d.ts +3 -0
  7. package/node_modules/@llblab/pi-actors/dist/lib/async-runs.js +14 -1
  8. package/node_modules/@llblab/pi-actors/dist/lib/command-templates.js +45 -3
  9. package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.js +1 -1
  10. package/node_modules/@llblab/pi-actors/dist/lib/observability.d.ts +16 -3
  11. package/node_modules/@llblab/pi-actors/dist/lib/observability.js +92 -7
  12. package/node_modules/@llblab/pi-actors/dist/lib/pi.d.ts +0 -1
  13. package/node_modules/@llblab/pi-actors/dist/lib/pi.js +15 -24
  14. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.d.ts +17 -0
  15. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.js +44 -0
  16. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.d.ts +4 -0
  17. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.js +102 -4
  18. package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.js +58 -39
  19. package/node_modules/@llblab/pi-actors/dist/lib/runtime.js +14 -6
  20. package/node_modules/@llblab/pi-actors/dist/skills/actors/SKILL.md +1 -1
  21. package/node_modules/@llblab/pi-actors/docs/async-runs.md +1 -1
  22. package/node_modules/@llblab/pi-actors/docs/coordinator-delivery.md +18 -23
  23. package/node_modules/@llblab/pi-actors/lib/async-runs.ts +18 -1
  24. package/node_modules/@llblab/pi-actors/lib/command-templates.ts +41 -3
  25. package/node_modules/@llblab/pi-actors/lib/extension-runtime.ts +1 -1
  26. package/node_modules/@llblab/pi-actors/lib/observability.ts +119 -5
  27. package/node_modules/@llblab/pi-actors/lib/pi.ts +15 -28
  28. package/node_modules/@llblab/pi-actors/lib/run-delivery-lineage.ts +68 -0
  29. package/node_modules/@llblab/pi-actors/lib/run-delivery.ts +120 -4
  30. package/node_modules/@llblab/pi-actors/lib/run-ui-runtime.ts +69 -44
  31. package/node_modules/@llblab/pi-actors/lib/runtime.ts +17 -6
  32. package/node_modules/@llblab/pi-actors/package.json +1 -1
  33. package/node_modules/@llblab/pi-actors/skills/actors/SKILL.md +1 -1
  34. package/node_modules/@llblab/pi-state-flow/AGENTS.md +33 -12
  35. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +125 -2
  36. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +87 -45
  37. package/node_modules/@llblab/pi-state-flow/README.md +203 -107
  38. package/node_modules/@llblab/pi-state-flow/docs/README.md +4 -0
  39. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +188 -0
  40. package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +42 -0
  41. package/node_modules/@llblab/pi-state-flow/index.ts +164 -2
  42. package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +138 -0
  43. package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +273 -0
  44. package/node_modules/@llblab/pi-state-flow/lib/config.ts +48 -0
  45. package/node_modules/@llblab/pi-state-flow/lib/context.ts +18 -5
  46. package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +268 -0
  47. package/node_modules/@llblab/pi-state-flow/lib/discovery.ts +117 -0
  48. package/node_modules/@llblab/pi-state-flow/lib/durable.ts +562 -0
  49. package/node_modules/@llblab/pi-state-flow/lib/episode.ts +24 -12
  50. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +606 -70
  51. package/node_modules/@llblab/pi-state-flow/lib/git.ts +666 -0
  52. package/node_modules/@llblab/pi-state-flow/lib/history.ts +95 -0
  53. package/node_modules/@llblab/pi-state-flow/lib/json.ts +24 -0
  54. package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +141 -0
  55. package/node_modules/@llblab/pi-state-flow/lib/memory.ts +52 -0
  56. package/node_modules/@llblab/pi-state-flow/lib/migration.ts +88 -0
  57. package/node_modules/@llblab/pi-state-flow/lib/publication.ts +296 -0
  58. package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +23 -7
  59. package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +79 -0
  60. package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +264 -0
  61. package/node_modules/@llblab/pi-state-flow/lib/session.ts +6 -0
  62. package/node_modules/@llblab/pi-state-flow/lib/skills.ts +99 -7
  63. package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +305 -48
  64. package/node_modules/@llblab/pi-state-flow/lib/state.ts +74 -7
  65. package/node_modules/@llblab/pi-state-flow/lib/status.ts +125 -6
  66. package/node_modules/@llblab/pi-state-flow/lib/storage.ts +196 -0
  67. package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +233 -0
  68. package/node_modules/@llblab/pi-state-flow/lib/terminal.ts +70 -24
  69. package/node_modules/@llblab/pi-state-flow/lib/transition.ts +254 -29
  70. package/node_modules/@llblab/pi-state-flow/package.json +8 -2
  71. package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +128 -0
  72. package/node_modules/@llblab/pi-telegram/AGENTS.md +14 -9
  73. package/node_modules/@llblab/pi-telegram/BACKLOG.md +23 -5
  74. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +18 -0
  75. package/node_modules/@llblab/pi-telegram/README.md +13 -9
  76. package/node_modules/@llblab/pi-telegram/docs/README.md +1 -0
  77. package/node_modules/@llblab/pi-telegram/docs/architecture.md +227 -23
  78. package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +1 -1
  79. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +70 -19
  80. package/node_modules/@llblab/pi-telegram/docs/outbound.md +6 -6
  81. package/node_modules/@llblab/pi-telegram/docs/public-api.md +13 -6
  82. package/node_modules/@llblab/pi-telegram/docs/ui-style.md +3 -1
  83. package/node_modules/@llblab/pi-telegram/index.ts +4 -1418
  84. package/node_modules/@llblab/pi-telegram/lib/agent-messages.ts +6 -3
  85. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +46 -1
  86. package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +32 -19
  87. package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +600 -135
  88. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +962 -55
  89. package/node_modules/@llblab/pi-telegram/lib/bus.ts +355 -26
  90. package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +718 -0
  91. package/node_modules/@llblab/pi-telegram/lib/commands.ts +237 -11
  92. package/node_modules/@llblab/pi-telegram/lib/config.ts +242 -26
  93. package/node_modules/@llblab/pi-telegram/lib/extension.ts +1851 -0
  94. package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +20 -2
  95. package/node_modules/@llblab/pi-telegram/lib/journal.ts +2184 -126
  96. package/node_modules/@llblab/pi-telegram/lib/locks.ts +44 -2
  97. package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +152 -13
  98. package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +97 -10
  99. package/node_modules/@llblab/pi-telegram/lib/paths.ts +29 -0
  100. package/node_modules/@llblab/pi-telegram/lib/polling.ts +85 -17
  101. package/node_modules/@llblab/pi-telegram/lib/preview.ts +17 -0
  102. package/node_modules/@llblab/pi-telegram/lib/prompts.ts +6 -2
  103. package/node_modules/@llblab/pi-telegram/lib/queue.ts +118 -26
  104. package/node_modules/@llblab/pi-telegram/lib/rendering.ts +4 -1
  105. package/node_modules/@llblab/pi-telegram/lib/replies.ts +21 -2
  106. package/node_modules/@llblab/pi-telegram/lib/routing.ts +344 -112
  107. package/node_modules/@llblab/pi-telegram/lib/setup.ts +44 -4
  108. package/node_modules/@llblab/pi-telegram/lib/status.ts +51 -4
  109. package/node_modules/@llblab/pi-telegram/lib/sync.ts +308 -39
  110. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +353 -22
  111. package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +664 -0
  112. package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +226 -0
  113. package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +118 -0
  114. package/node_modules/@llblab/pi-telegram/lib/threads.ts +1686 -129
  115. package/node_modules/@llblab/pi-telegram/lib/turns.ts +7 -0
  116. package/node_modules/@llblab/pi-telegram/lib/updates.ts +1319 -97
  117. package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +1643 -0
  118. package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +968 -0
  119. package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +84 -0
  120. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  121. package/node_modules/@llblab/pi-telegram/screenshot.png +0 -0
  122. package/node_modules/@llblab/pi-telegram/scripts/measure-bus.mjs +83 -0
  123. package/node_modules/@llblab/pi-telegram/scripts/measure-workspace.mjs +101 -0
  124. package/node_modules/@llblab/{skills → pi-telegram/skills}/show-me/SKILL.md +28 -6
  125. package/node_modules/@llblab/pi-telegram/skills/show-me/references/telegram-surfaces.md +43 -0
  126. package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/delivery-and-threads.md +1 -1
  127. package/node_modules/@llblab/skills/package.json +2 -3
  128. package/package.json +6 -5
  129. /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, skip_entity_detection: true },
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: createTelegramApiClient(deps.getBotToken, {
1452
- recordRuntimeEvent: deps.recordRuntimeEvent,
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 body: Record<string, unknown> = { guest_query_id: guestQueryId };
1751
- if (options?.result) {
1752
- body.result = options.result;
1753
- } else if (text !== undefined || options?.richMessage) {
1754
- const inputContent: Record<string, unknown> = options?.richMessage
1755
- ? { rich_message: options.richMessage }
1756
- : { message_text: text };
1757
- if (!options?.richMessage && options?.parseMode) {
1758
- inputContent.parse_mode = options.parseMode;
1759
- }
1760
- body.result = {
1761
- type: "article",
1762
- id: "1",
1763
- title: "Response",
1764
- input_message_content: inputContent,
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),