@llblab/pi-telegram 0.51.5 → 0.52.0

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 (181) hide show
  1. package/AGENTS.md +10 -8
  2. package/BACKLOG.md +1 -8
  3. package/CHANGELOG.md +23 -0
  4. package/LICENSE +21 -0
  5. package/README.md +16 -6
  6. package/dist/lib/activity-verbosity.d.ts +1 -5
  7. package/dist/lib/activity-verbosity.js +5 -7
  8. package/dist/lib/bindings.d.ts +9 -2
  9. package/dist/lib/bindings.js +22 -13
  10. package/dist/lib/bus-api.js +4 -2
  11. package/dist/lib/bus-follower.d.ts +55 -57
  12. package/dist/lib/bus-follower.js +213 -140
  13. package/dist/lib/bus-leader.d.ts +21 -2
  14. package/dist/lib/bus-leader.js +181 -25
  15. package/dist/lib/bus-transport.d.ts +4 -1
  16. package/dist/lib/bus-transport.js +28 -4
  17. package/dist/lib/bus.d.ts +52 -56
  18. package/dist/lib/bus.js +78 -246
  19. package/dist/lib/channel-posts.d.ts +1 -3
  20. package/dist/lib/channel-posts.js +23 -26
  21. package/dist/lib/command-templates.d.ts +0 -5
  22. package/dist/lib/command-templates.js +5 -5
  23. package/dist/lib/commands.d.ts +12 -26
  24. package/dist/lib/commands.js +152 -98
  25. package/dist/lib/config.d.ts +0 -17
  26. package/dist/lib/config.js +12 -14
  27. package/dist/lib/delivery.d.ts +0 -4
  28. package/dist/lib/delivery.js +2 -5
  29. package/dist/lib/extension.js +281 -61
  30. package/dist/lib/generative-apps.d.ts +0 -19
  31. package/dist/lib/inbound.d.ts +0 -2
  32. package/dist/lib/inbound.js +2 -2
  33. package/dist/lib/journal.d.ts +177 -16
  34. package/dist/lib/journal.js +1115 -249
  35. package/dist/lib/keyboard.d.ts +0 -2
  36. package/dist/lib/keyboard.js +2 -2
  37. package/dist/lib/lifecycle.d.ts +57 -2
  38. package/dist/lib/lifecycle.js +110 -5
  39. package/dist/lib/locks.d.ts +100 -12
  40. package/dist/lib/locks.js +380 -14
  41. package/dist/lib/logging.d.ts +30 -12
  42. package/dist/lib/logging.js +129 -72
  43. package/dist/lib/media.d.ts +28 -5
  44. package/dist/lib/media.js +26 -8
  45. package/dist/lib/menu-model.d.ts +0 -11
  46. package/dist/lib/menu-model.js +8 -8
  47. package/dist/lib/menu-settings.d.ts +0 -18
  48. package/dist/lib/menu-settings.js +18 -18
  49. package/dist/lib/menu-status.d.ts +4 -4
  50. package/dist/lib/menu-status.js +12 -7
  51. package/dist/lib/menu-thinking.d.ts +1 -1
  52. package/dist/lib/menu-thinking.js +1 -1
  53. package/dist/lib/menu.d.ts +1 -0
  54. package/dist/lib/menu.js +4 -3
  55. package/dist/lib/model.js +2 -4
  56. package/dist/lib/outbound-attachments.d.ts +0 -1
  57. package/dist/lib/outbound-attachments.js +1 -1
  58. package/dist/lib/outbound-buttons.js +1 -8
  59. package/dist/lib/outbound-markup.d.ts +1 -6
  60. package/dist/lib/outbound-markup.js +4 -21
  61. package/dist/lib/outbound.d.ts +0 -8
  62. package/dist/lib/outbound.js +5 -5
  63. package/dist/lib/paths.d.ts +50 -7
  64. package/dist/lib/paths.js +155 -17
  65. package/dist/lib/pi.d.ts +1 -0
  66. package/dist/lib/pi.js +2 -1
  67. package/dist/lib/polling.d.ts +0 -5
  68. package/dist/lib/polling.js +5 -5
  69. package/dist/lib/preview.d.ts +0 -3
  70. package/dist/lib/preview.js +3 -3
  71. package/dist/lib/prompt-templates.d.ts +0 -1
  72. package/dist/lib/prompt-templates.js +1 -1
  73. package/dist/lib/queue.d.ts +3 -15
  74. package/dist/lib/queue.js +69 -8
  75. package/dist/lib/recovery.d.ts +29 -9
  76. package/dist/lib/recovery.js +104 -33
  77. package/dist/lib/replies.d.ts +0 -4
  78. package/dist/lib/replies.js +1 -1
  79. package/dist/lib/routing.d.ts +73 -8
  80. package/dist/lib/routing.js +2206 -307
  81. package/dist/lib/sections.d.ts +0 -2
  82. package/dist/lib/sections.js +1 -1
  83. package/dist/lib/status.d.ts +46 -9
  84. package/dist/lib/status.js +179 -27
  85. package/dist/lib/sync.d.ts +0 -5
  86. package/dist/lib/sync.js +9 -8
  87. package/dist/lib/telegram-api.d.ts +11 -3
  88. package/dist/lib/telegram-api.js +34 -23
  89. package/dist/lib/thread-cleanup-manager.d.ts +1 -0
  90. package/dist/lib/thread-cleanup-manager.js +18 -21
  91. package/dist/lib/thread-naming.d.ts +32 -5
  92. package/dist/lib/thread-naming.js +191 -7
  93. package/dist/lib/thread-reconciler.d.ts +2 -0
  94. package/dist/lib/thread-reconciler.js +1 -1
  95. package/dist/lib/threads.d.ts +292 -59
  96. package/dist/lib/threads.js +1664 -373
  97. package/dist/lib/turns.d.ts +0 -1
  98. package/dist/lib/turns.js +1 -1
  99. package/dist/lib/updates.d.ts +184 -28
  100. package/dist/lib/updates.js +1032 -114
  101. package/dist/lib/wire.d.ts +12 -0
  102. package/dist/lib/wire.js +21 -0
  103. package/dist/lib/workspace-admission.d.ts +25 -17
  104. package/dist/lib/workspace-admission.js +95 -20
  105. package/dist/lib/workspace-identity.d.ts +20 -0
  106. package/dist/lib/workspace-identity.js +103 -0
  107. package/dist/lib/workspace-retirement.d.ts +45 -10
  108. package/dist/lib/workspace-retirement.js +196 -64
  109. package/dist/package.json +6 -6
  110. package/dist/skills/generated-control-surface/SKILL.md +3 -3
  111. package/dist/skills/telegram-bridge/references/diagnosis.md +3 -3
  112. package/docs/README.md +1 -1
  113. package/docs/activity.md +1 -1
  114. package/docs/architecture.md +158 -39
  115. package/docs/callback-namespaces.md +1 -1
  116. package/docs/delivery.md +1 -1
  117. package/docs/generative-apps.md +2 -2
  118. package/docs/inbound.md +1 -1
  119. package/docs/multi-instance-bus.md +127 -19
  120. package/docs/public-api.md +5 -3
  121. package/docs/ui-style.md +26 -8
  122. package/docs/updates.md +1 -1
  123. package/lib/activity-verbosity.ts +5 -9
  124. package/lib/bindings.ts +23 -10
  125. package/lib/bus-api.ts +5 -2
  126. package/lib/bus-follower.ts +251 -226
  127. package/lib/bus-leader.ts +178 -31
  128. package/lib/bus-transport.ts +30 -8
  129. package/lib/bus.ts +110 -334
  130. package/lib/channel-posts.ts +29 -29
  131. package/lib/command-templates.ts +5 -5
  132. package/lib/commands.ts +150 -112
  133. package/lib/config.ts +12 -17
  134. package/lib/delivery.ts +2 -8
  135. package/lib/extension.ts +282 -72
  136. package/lib/generative-apps.ts +0 -22
  137. package/lib/inbound.ts +2 -2
  138. package/lib/journal.ts +1182 -326
  139. package/lib/keyboard.ts +2 -2
  140. package/lib/lifecycle.ts +141 -5
  141. package/lib/locks.ts +402 -23
  142. package/lib/logging.ts +151 -101
  143. package/lib/media.ts +51 -9
  144. package/lib/menu-model.ts +8 -8
  145. package/lib/menu-settings.ts +18 -18
  146. package/lib/menu-status.ts +11 -0
  147. package/lib/menu-thinking.ts +2 -2
  148. package/lib/menu.ts +5 -1
  149. package/lib/model.ts +2 -4
  150. package/lib/outbound-attachments.ts +1 -1
  151. package/lib/outbound-buttons.ts +1 -10
  152. package/lib/outbound-markup.ts +5 -24
  153. package/lib/outbound.ts +5 -5
  154. package/lib/paths.ts +177 -17
  155. package/lib/pi.ts +5 -2
  156. package/lib/polling.ts +5 -5
  157. package/lib/preview.ts +3 -3
  158. package/lib/prompt-templates.ts +1 -1
  159. package/lib/queue.ts +67 -9
  160. package/lib/recovery.ts +104 -44
  161. package/lib/replies.ts +1 -1
  162. package/lib/routing.ts +1955 -375
  163. package/lib/sections.ts +1 -1
  164. package/lib/status.ts +185 -36
  165. package/lib/sync.ts +8 -8
  166. package/lib/telegram-api.ts +48 -33
  167. package/lib/thread-cleanup-manager.ts +24 -21
  168. package/lib/thread-naming.ts +267 -9
  169. package/lib/thread-reconciler.ts +3 -1
  170. package/lib/threads.ts +1718 -491
  171. package/lib/turns.ts +1 -1
  172. package/lib/updates.ts +1072 -151
  173. package/lib/wire.ts +28 -0
  174. package/lib/workspace-admission.ts +97 -47
  175. package/lib/workspace-identity.ts +147 -0
  176. package/lib/workspace-retirement.ts +234 -102
  177. package/package.json +6 -6
  178. package/scripts/audit-exports.mjs +100 -0
  179. package/scripts/check-downgrade.mjs +80 -43
  180. package/skills/generated-control-surface/SKILL.md +3 -3
  181. package/skills/telegram-bridge/references/diagnosis.md +3 -3
@@ -6,7 +6,7 @@
6
6
  import { type TelegramConfigStore } from "./config.ts";
7
7
  import type * as Pi from "./pi.ts";
8
8
  import type { ExtensionAPI, ExtensionCommandContext } from "./pi.ts";
9
- import type { TelegramBridgeStatusLineOptions } from "./status.ts";
9
+ import { type TelegramBridgeStatusLineOptions } from "./status.ts";
10
10
  import type { TelegramSessionReplacementIntent } from "./threads.ts";
11
11
  import { type PendingTelegramControlItem, type TelegramQueueAdmissionReceipt } from "./queue.ts";
12
12
  export interface ParsedTelegramCommand {
@@ -43,10 +43,7 @@ interface RegisteredTelegramExtensionCommand {
43
43
  emoji?: string;
44
44
  handler: TelegramExtensionCommandRegistration["handler"];
45
45
  }
46
- export declare function normalizeTelegramExtensionCommandName(name: string): string;
47
- export declare function isTelegramExtensionCommandName(name: string): boolean;
48
46
  export declare function registerTelegramCommand(registration: TelegramExtensionCommandRegistration): () => void;
49
- export declare function getTelegramExtensionCommands(): RegisteredTelegramExtensionCommand[];
50
47
  export declare function findTelegramExtensionCommand(name: string | undefined): RegisteredTelegramExtensionCommand | undefined;
51
48
  export declare function clearTelegramExtensionCommands(): void;
52
49
  export declare const TELEGRAM_COMMAND_EMOJI: {
@@ -65,18 +62,14 @@ export declare const TELEGRAM_COMMAND_EMOJI: {
65
62
  readonly new: "🆕";
66
63
  };
67
64
  export type TelegramCommandEmojiName = keyof typeof TELEGRAM_COMMAND_EMOJI;
68
- export declare function getTelegramCommandEmoji(command: TelegramCommandEmojiName): string;
69
65
  export declare function formatTelegramCommandEmojiPrefix(command: TelegramCommandEmojiName): string;
70
66
  export declare function formatTelegramPiCommandHtml(command: string): string;
71
67
  export declare function formatTelegramInformationHeading(emoji: string, text: string): string;
72
68
  export declare function formatTelegramInvalidInstanceName(validationError: string): string;
73
69
  export declare function formatTelegramThreadDisplayNameSavedHeading(name: string): string;
74
70
  export declare function formatTelegramAutomaticThreadDisplayNameRestoredHeading(name: string): string;
75
- export declare const TELEGRAM_COMPACTION_STARTED_TEXT: string;
76
- export declare const TELEGRAM_COMPACTION_COMPLETED_TEXT: string;
77
71
  export declare const TELEGRAM_COMPACTION_STARTED_MARKDOWN: string;
78
72
  export declare const TELEGRAM_COMPACTION_COMPLETED_MARKDOWN = "**\u2705 Compaction completed.**";
79
- export declare const TELEGRAM_BUILTIN_BOT_COMMANDS: readonly TelegramBotCommandDefinition[];
80
73
  export declare const TELEGRAM_BOT_COMMANDS: readonly TelegramBotCommandDefinition[];
81
74
  export declare function getTelegramReservedCommandNames(): string[];
82
75
  export interface TelegramBotCommandRegistrationDeps {
@@ -112,12 +105,21 @@ export interface TelegramBridgeCommandRegistrationDeps {
112
105
  startPolling: (ctx: ExtensionCommandContext, options?: TelegramBridgeCommandStartPollingOptions) => void | Promise<void | TelegramBridgeCommandStartPollingResult> | TelegramBridgeCommandStartPollingResult;
113
106
  stopPolling: () => Promise<void | string>;
114
107
  recoverPollingStart?: (error: unknown) => Promise<TelegramPollingStartRecoveryResult>;
108
+ recordConnectionEvent?: (error: unknown, phase: string) => void;
115
109
  getDisconnectThreadName?: () => string | undefined;
116
110
  queueAgentConnectionContext?: (connected: boolean) => void;
117
111
  updateStatus: (ctx: ExtensionCommandContext) => void;
112
+ isContextCurrent?: (ctx: ExtensionCommandContext) => boolean;
113
+ getSessionGeneration?: () => number;
114
+ connectionIntent?: {
115
+ begin(cwd: string, profileName?: string): string;
116
+ finish(id: string): void;
117
+ isActive(id: string): boolean;
118
+ cancel(): void;
119
+ };
118
120
  getProfileNames?: () => string[];
119
- activateDefaultProfileConfig?: (ctx: ExtensionCommandContext) => Promise<void>;
120
- activateProfileConfig?: (ctx: ExtensionCommandContext, profileName: string) => Promise<boolean>;
121
+ activateDefaultProfileConfig?: (ctx: ExtensionCommandContext, isCurrent: () => boolean) => Promise<void>;
122
+ activateProfileConfig?: (ctx: ExtensionCommandContext, profileName: string, isCurrent: () => boolean) => Promise<boolean>;
121
123
  }
122
124
  export type TelegramThreadDisplayNameRenamePort = (target: {
123
125
  chatId: number;
@@ -533,16 +535,8 @@ export declare function handleTelegramNextCommand(deps: {
533
535
  parseMode?: "HTML";
534
536
  }) => Promise<void>) | undefined;
535
537
  }): Promise<void>;
536
- export declare function handleTelegramContinueCommand<TMessage, TContext>(message: TMessage, ctx: TContext, deps: {
537
- enqueueContinueTurn: (message: TMessage, ctx: TContext) => Promise<void>;
538
- }): Promise<void>;
539
- export declare function buildTelegramNewConfirmationReplyMarkup(): TelegramCompactConfirmationReplyMarkup;
540
- export declare function getTelegramNewConfirmationHtml(): string;
541
538
  export declare function openTelegramNewConfirmation(target: TelegramCommandMessageTarget, deps: TelegramCompactConfirmationDeps): Promise<void>;
542
539
  export declare function handleTelegramNewConfirmationCallback<TContext>(query: TelegramCompactConfirmationCallbackQuery, deps: TelegramNewConfirmationCallbackDeps<TContext>): Promise<boolean>;
543
- export declare function buildTelegramCompactConfirmationReplyMarkup(): TelegramCompactConfirmationReplyMarkup;
544
- export declare function getTelegramCompactConfirmationHtml(): string;
545
- export declare function openTelegramCompactConfirmation(target: TelegramCommandMessageTarget, deps: TelegramCompactConfirmationDeps): Promise<void>;
546
540
  export declare function handleTelegramCompactConfirmationCallback<TContext>(query: TelegramCompactConfirmationCallbackQuery, deps: TelegramCompactConfirmationCallbackDeps<TContext>): Promise<boolean>;
547
541
  export interface TelegramNewCommandDeps extends TelegramRuntimeEventRecorderPort {
548
542
  isIdle: () => boolean;
@@ -579,7 +573,6 @@ export declare function createTelegramCommandOrPromptRuntime<TMessage, TContext>
579
573
  export declare const TELEGRAM_INTERNAL_COMMAND_NAME = "telegram-internal";
580
574
  export declare const TELEGRAM_INTERNAL_COMMAND_DESCRIPTION = "Internal Telegram command cannot be run manually";
581
575
  export declare const TELEGRAM_INTERNAL_MANUAL_USE_MESSAGE = "This internal Telegram command cannot be run manually.";
582
- export declare function delayTelegramSessionAction(delayMs: number): Promise<void>;
583
576
  export interface TelegramSessionActionRuntimeDeps {
584
577
  registerCommand: Pi.ExtensionAPI["registerCommand"];
585
578
  sendUserMessage: Pi.ExtensionAPI["sendUserMessage"];
@@ -612,13 +605,6 @@ export interface TelegramSessionReplacementSettlementDeps {
612
605
  isCurrent?: () => boolean;
613
606
  }
614
607
  export declare function settleTelegramSessionReplacement(deps: TelegramSessionReplacementSettlementDeps): Promise<"none" | "settled" | "expired" | "failed" | "stale">;
615
- export declare function createTelegramSessionReplacementSettlementRuntime<TContext>(deps: {
616
- resolve: (ctx: TContext) => TelegramSessionReplacementSettlementDeps | undefined;
617
- onResult?: (result: "none" | "settled" | "expired" | "failed" | "stale") => void;
618
- onError?: (error: unknown) => void;
619
- }): {
620
- onSessionStart: (ctx: TContext) => void;
621
- };
622
608
  export interface TelegramSessionActionAssemblyDeps {
623
609
  registerCommand: Pi.ExtensionAPI["registerCommand"];
624
610
  sendUserMessage: Pi.ExtensionAPI["sendUserMessage"];
@@ -6,6 +6,7 @@
6
6
  import { randomUUID } from "node:crypto";
7
7
  import { pairTelegramUserIfNeeded, TELEGRAM_DEFAULT_PROFILE_NAME, } from "./config.js";
8
8
  import { escapeHtml } from "./rendering.js";
9
+ import { formatTelegramConnectionFailure } from "./status.js";
9
10
  import { createTelegramControlItemBuilder, createTelegramControlQueueController, createTelegramQueueAdmissionReceipt, } from "./queue.js";
10
11
  const TELEGRAM_EXTENSION_COMMAND_REGISTRY_KEY = "__piTelegramCommandRegistry__";
11
12
  const TELEGRAM_BOT_COMMAND_NAME_PATTERN = /^[a-z0-9_]{1,32}$/;
@@ -22,10 +23,10 @@ function getOrCreateTelegramCommandRegistry() {
22
23
  globalThis[TELEGRAM_EXTENSION_COMMAND_REGISTRY_KEY] = registry;
23
24
  return registry;
24
25
  }
25
- export function normalizeTelegramExtensionCommandName(name) {
26
+ function normalizeTelegramExtensionCommandName(name) {
26
27
  return name.trim().replace(/^\/+/, "").toLowerCase();
27
28
  }
28
- export function isTelegramExtensionCommandName(name) {
29
+ function isTelegramExtensionCommandName(name) {
29
30
  return TELEGRAM_BOT_COMMAND_NAME_PATTERN.test(name);
30
31
  }
31
32
  function normalizeTelegramExtensionCommandEmoji(emoji) {
@@ -66,7 +67,7 @@ export function registerTelegramCommand(registration) {
66
67
  registry.commands.delete(name);
67
68
  };
68
69
  }
69
- export function getTelegramExtensionCommands() {
70
+ function getTelegramExtensionCommands() {
70
71
  return Array.from(getOrCreateTelegramCommandRegistry().commands.values()).sort((a, b) => a.order - b.order || a.name.localeCompare(b.name));
71
72
  }
72
73
  export function findTelegramExtensionCommand(name) {
@@ -92,7 +93,7 @@ export const TELEGRAM_COMMAND_EMOJI = {
92
93
  name: "🏷️",
93
94
  new: "🆕",
94
95
  };
95
- export function getTelegramCommandEmoji(command) {
96
+ function getTelegramCommandEmoji(command) {
96
97
  return TELEGRAM_COMMAND_EMOJI[command];
97
98
  }
98
99
  export function formatTelegramCommandEmojiPrefix(command) {
@@ -123,14 +124,14 @@ export function formatTelegramThreadDisplayNameSavedHeading(name) {
123
124
  export function formatTelegramAutomaticThreadDisplayNameRestoredHeading(name) {
124
125
  return `<b>✅ Automatic Thread display name restored as <i>${escapeHtml(name)}</i>.</b>`;
125
126
  }
126
- export const TELEGRAM_COMPACTION_STARTED_TEXT = formatTelegramInformationHeading(getTelegramCommandEmoji("compact"), "Compaction started.");
127
- export const TELEGRAM_COMPACTION_COMPLETED_TEXT = formatTelegramInformationHeading("✅", "Compaction completed.");
127
+ const TELEGRAM_COMPACTION_STARTED_TEXT = formatTelegramInformationHeading(getTelegramCommandEmoji("compact"), "Compaction started.");
128
+ const TELEGRAM_COMPACTION_COMPLETED_TEXT = formatTelegramInformationHeading("✅", "Compaction completed.");
128
129
  export const TELEGRAM_COMPACTION_STARTED_MARKDOWN = `**${formatTelegramCommandEmojiPrefix("compact")}Compaction started.**`;
129
130
  export const TELEGRAM_COMPACTION_COMPLETED_MARKDOWN = "**✅ Compaction completed.**";
130
131
  function formatTelegramBotCommandDescription(command, description) {
131
132
  return `${formatTelegramCommandEmojiPrefix(command)}${description}`;
132
133
  }
133
- export const TELEGRAM_BUILTIN_BOT_COMMANDS = [
134
+ const TELEGRAM_BUILTIN_BOT_COMMANDS = [
134
135
  {
135
136
  command: "start",
136
137
  description: formatTelegramBotCommandDescription("start", "Open menu / Pair bridge"),
@@ -270,121 +271,174 @@ export function registerTelegramBridgeCommands(pi, deps) {
270
271
  pi.registerCommand("telegram-connect", {
271
272
  description: "<profile> — Start Telegram bridge",
272
273
  handler: async (args, ctx) => {
274
+ const sessionGeneration = deps.getSessionGeneration?.();
275
+ let intentId;
276
+ // Pi context getters throw after replacement; check plain intent/generation first.
277
+ const isCurrent = () => (!intentId || deps.connectionIntent?.isActive(intentId) !== false) &&
278
+ (sessionGeneration === undefined || deps.getSessionGeneration?.() === sessionGeneration) &&
279
+ deps.isContextCurrent?.(ctx) !== false;
280
+ if (!isCurrent())
281
+ return;
273
282
  if (args.trim().split(/\s+/).some((word) => /^as=/i.test(word))) {
274
283
  ctx.ui.notify("Thread names are configured from Telegram, not from Pi commands.", "warning");
275
284
  deps.updateStatus(ctx);
276
285
  return;
277
286
  }
278
287
  const profileName = parseTelegramProfileArg(args);
279
- if (profileName && deps.activateProfileConfig) {
280
- const ok = await deps.activateProfileConfig(ctx, profileName);
281
- if (!ok) {
282
- ctx.ui.notify(`Profile "${profileName}" not found.`, "error");
283
- deps.updateStatus(ctx);
284
- return;
285
- }
286
- ctx.ui.notify(`Activated profile "${profileName}".`, "info");
287
- }
288
- else {
289
- await (deps.activateDefaultProfileConfig?.(ctx) ?? deps.reloadConfig());
290
- }
291
- if (!deps.hasBotToken()) {
292
- const botTokenDiagnostic = deps.getBotTokenDiagnostic?.();
293
- if (botTokenDiagnostic)
294
- ctx.ui.notify(botTokenDiagnostic, "error");
295
- const profileNames = deps.getProfileNames?.() ?? [];
296
- if (!profileName && profileNames.length > 0) {
297
- ctx.ui.notify(`No default Telegram profile configured. Available profiles: ${profileNames.join(", ")}. Use /telegram-connect <profileName> or /telegram-setup to create a default profile.`, "info");
298
- deps.updateStatus(ctx);
299
- return;
288
+ intentId = deps.connectionIntent?.begin(ctx.cwd, profileName);
289
+ try {
290
+ if (profileName && deps.activateProfileConfig) {
291
+ const ok = await deps.activateProfileConfig(ctx, profileName, isCurrent);
292
+ if (!isCurrent())
293
+ return;
294
+ if (!ok) {
295
+ ctx.ui.notify(`Profile "${profileName}" not found.`, "error");
296
+ deps.updateStatus(ctx);
297
+ return;
298
+ }
299
+ ctx.ui.notify(`Activated profile "${profileName}".`, "info");
300
300
  }
301
- await deps.promptForConfig(ctx, profileName);
302
- return;
303
- }
304
- let recoveryUsed = false;
305
- const startWithRecovery = async (options) => {
306
- try {
307
- return await deps.startPolling(ctx, options);
301
+ else {
302
+ await (deps.activateDefaultProfileConfig?.(ctx, isCurrent) ?? deps.reloadConfig());
303
+ if (!isCurrent())
304
+ return;
308
305
  }
309
- catch (error) {
310
- if (!deps.recoverPollingStart || recoveryUsed)
311
- throw error;
312
- const recovery = await deps.recoverPollingStart(error);
313
- if (recovery.kind === "unhandled")
314
- throw error;
315
- if (recovery.kind === "blocked") {
316
- return { ok: false, message: recovery.message };
306
+ if (!deps.hasBotToken()) {
307
+ const botTokenDiagnostic = deps.getBotTokenDiagnostic?.();
308
+ if (botTokenDiagnostic)
309
+ ctx.ui.notify(botTokenDiagnostic, "error");
310
+ const profileNames = deps.getProfileNames?.() ?? [];
311
+ if (!profileName && profileNames.length > 0) {
312
+ ctx.ui.notify(`No default Telegram profile configured. Available profiles: ${profileNames.join(", ")}. Use /telegram-connect <profileName> or /telegram-setup to create a default profile.`, "info");
313
+ deps.updateStatus(ctx);
314
+ return;
317
315
  }
318
- recoveryUsed = true;
316
+ await deps.promptForConfig(ctx, profileName);
317
+ return;
318
+ }
319
+ let recoveryUsed = false;
320
+ const startWithRecovery = async (options) => {
319
321
  try {
320
- const retry = await deps.startPolling(ctx, options);
321
- if (!retry) {
322
- return { ok: true, message: recovery.message };
322
+ return await deps.startPolling(ctx, options);
323
+ }
324
+ catch (error) {
325
+ if (!isCurrent())
326
+ return;
327
+ if (!deps.recoverPollingStart || recoveryUsed)
328
+ throw error;
329
+ deps.recordConnectionEvent?.(error, "polling-start");
330
+ const recovery = await deps.recoverPollingStart(error);
331
+ if (!isCurrent())
332
+ return;
333
+ if (recovery.kind === "unhandled")
334
+ throw error;
335
+ if (recovery.kind === "blocked") {
336
+ return { ok: false, message: recovery.message,
337
+ notice: "Telegram recovery blocked. Check /telegram-status --debug." };
338
+ }
339
+ recoveryUsed = true;
340
+ deps.recordConnectionEvent?.(recovery.message, "recovery");
341
+ try {
342
+ const retry = await deps.startPolling(ctx, options);
343
+ if (!isCurrent())
344
+ return;
345
+ if (!retry)
346
+ return { ok: true, message: "Telegram bridge connected; temporary state recovered." };
347
+ return {
348
+ ...retry,
349
+ message: retry.ok
350
+ ? "Telegram bridge connected; temporary state recovered."
351
+ : retry.message,
352
+ };
353
+ }
354
+ catch (error) {
355
+ if (!isCurrent())
356
+ return;
357
+ deps.recordConnectionEvent?.(error, "recovery-retry");
358
+ return {
359
+ ok: false,
360
+ notice: "Telegram recovery failed. Restart this Pi instance.",
361
+ };
323
362
  }
324
- return {
325
- ...retry,
326
- message: retry.ok
327
- ? `${recovery.message} ${retry.message ?? "Telegram bridge connected."}`
328
- : retry.message,
329
- };
330
363
  }
331
- catch {
332
- return {
333
- ok: false,
334
- message: "Telegram temporary state was recovered, but the bridge could not restart. Restart this Pi instance and run /telegram-connect again.",
335
- };
364
+ };
365
+ let result = await startWithRecovery({ forceFreshLeaderThread: true });
366
+ if (!isCurrent())
367
+ return;
368
+ if (result && !result.ok && result.canTakeover) {
369
+ const confirmed = await ctx.ui.confirm(formatTelegramTakeoverTitle(ctx), formatTelegramTakeoverPrompt(ctx, result.owner));
370
+ if (!isCurrent())
371
+ return;
372
+ if (!confirmed) {
373
+ ctx.ui.notify("Telegram bridge takeover cancelled.", "info");
374
+ deps.updateStatus(ctx);
375
+ return;
336
376
  }
377
+ result = await startWithRecovery({ force: true, forceFreshLeaderThread: true });
378
+ if (!isCurrent())
379
+ return;
337
380
  }
338
- };
339
- let result = await startWithRecovery({
340
- forceFreshLeaderThread: true,
341
- });
342
- if (result && !result.ok && result.canTakeover) {
343
- const confirmed = await ctx.ui.confirm(formatTelegramTakeoverTitle(ctx), formatTelegramTakeoverPrompt(ctx, result.owner));
344
- if (!confirmed) {
345
- ctx.ui.notify("Telegram bridge takeover cancelled.", "info");
346
- deps.updateStatus(ctx);
347
- return;
381
+ if (result && !result.ok) {
382
+ if (result.message)
383
+ deps.recordConnectionEvent?.(result.message, "connect-refused");
384
+ ctx.ui.notify(result.notice ?? formatTelegramConnectionFailure(result.message), "warning");
348
385
  }
349
- result = await startWithRecovery({
350
- force: true,
351
- forceFreshLeaderThread: true,
352
- });
386
+ else if (result?.message) {
387
+ ctx.ui.notify(result.message, "info");
388
+ }
389
+ if (!result || result.ok)
390
+ deps.queueAgentConnectionContext?.(true);
391
+ deps.updateStatus(ctx);
353
392
  }
354
- if (result?.message) {
355
- ctx.ui.notify(result.message, result.ok ? "info" : "warning");
393
+ catch (error) {
394
+ if (!isCurrent())
395
+ return;
396
+ deps.recordConnectionEvent?.(error, "connect");
397
+ ctx.ui.notify(formatTelegramConnectionFailure(error), "warning");
398
+ deps.updateStatus(ctx);
356
399
  }
357
- if (!result || result.ok) {
358
- deps.queueAgentConnectionContext?.(true);
400
+ finally {
401
+ if (intentId)
402
+ deps.connectionIntent?.finish(intentId);
359
403
  }
360
- deps.updateStatus(ctx);
361
404
  },
362
405
  });
363
406
  pi.registerCommand("telegram-disconnect", {
364
407
  description: "Stop Telegram and delete current thread in Threaded Mode",
365
408
  handler: async (_args, ctx) => {
366
- const threadName = deps.getDisconnectThreadName?.();
367
- if (threadName) {
368
- const confirmed = await ctx.ui.confirm(ctx.ui.theme.fg("accent", "pi-telegram"), `Delete Telegram thread ${ctx.ui.theme.fg("warning", threadName)} and disconnect this Pi session?`);
369
- if (!confirmed) {
370
- ctx.ui.notify("Telegram disconnect cancelled.", "info");
371
- deps.updateStatus(ctx);
372
- return;
373
- }
374
- }
409
+ const generation = deps.getSessionGeneration?.();
410
+ const isCurrent = () => (generation === undefined || deps.getSessionGeneration?.() === generation) &&
411
+ deps.isContextCurrent?.(ctx) !== false;
412
+ if (!isCurrent())
413
+ return;
414
+ deps.connectionIntent?.cancel();
375
415
  try {
416
+ const threadName = deps.getDisconnectThreadName?.();
417
+ if (threadName) {
418
+ const confirmed = await ctx.ui.confirm(ctx.ui.theme.fg("accent", "pi-telegram"), `Delete Telegram thread ${ctx.ui.theme.fg("warning", threadName)} and disconnect this Pi session?`);
419
+ if (!isCurrent())
420
+ return;
421
+ if (!confirmed) {
422
+ ctx.ui.notify("Telegram disconnect cancelled.", "info");
423
+ return;
424
+ }
425
+ }
376
426
  const message = await deps.stopPolling();
427
+ if (!isCurrent())
428
+ return;
377
429
  if (message)
378
430
  ctx.ui.notify(message, "info");
379
431
  deps.queueAgentConnectionContext?.(false);
380
432
  }
381
433
  catch (error) {
382
- const detail = error instanceof Error ? error.message : String(error);
383
- ctx.ui.notify(`Telegram disconnect did not complete: ${detail} Keep this Pi session open, restore leader connectivity, inspect /telegram-status --debug, and retry /telegram-disconnect.`, "warning");
384
- throw error;
434
+ deps.recordConnectionEvent?.(error, "disconnect");
435
+ if (!isCurrent())
436
+ return;
437
+ ctx.ui.notify("Telegram disconnect incomplete; keep Pi open. Check /telegram-status --debug.", "warning");
385
438
  }
386
439
  finally {
387
- deps.updateStatus(ctx);
440
+ if (isCurrent())
441
+ deps.updateStatus(ctx);
388
442
  }
389
443
  },
390
444
  });
@@ -684,7 +738,7 @@ export async function handleTelegramNextCommand(deps) {
684
738
  deps.dispatchNextQueuedTurn();
685
739
  deps.updateStatus();
686
740
  }
687
- export async function handleTelegramContinueCommand(message, ctx, deps) {
741
+ async function handleTelegramContinueCommand(message, ctx, deps) {
688
742
  await deps.enqueueContinueTurn(message, ctx);
689
743
  }
690
744
  function dispatchNextQueuedTelegramTurnAfterCompact(deps) {
@@ -694,7 +748,7 @@ function dispatchNextQueuedTelegramTurnAfterCompact(deps) {
694
748
  }
695
749
  deps.dispatchNextQueuedTelegramTurn();
696
750
  }
697
- export function buildTelegramNewConfirmationReplyMarkup() {
751
+ function buildTelegramNewConfirmationReplyMarkup() {
698
752
  return {
699
753
  inline_keyboard: [
700
754
  [
@@ -704,7 +758,7 @@ export function buildTelegramNewConfirmationReplyMarkup() {
704
758
  ],
705
759
  };
706
760
  }
707
- export function getTelegramNewConfirmationHtml() {
761
+ function getTelegramNewConfirmationHtml() {
708
762
  return "<b>Start a new session?</b>";
709
763
  }
710
764
  export async function openTelegramNewConfirmation(target, deps) {
@@ -731,7 +785,7 @@ export async function handleTelegramNewConfirmationCallback(query, deps) {
731
785
  await deps.runNew(deps.ctx);
732
786
  return true;
733
787
  }
734
- export function buildTelegramCompactConfirmationReplyMarkup() {
788
+ function buildTelegramCompactConfirmationReplyMarkup() {
735
789
  return {
736
790
  inline_keyboard: [
737
791
  [
@@ -741,10 +795,10 @@ export function buildTelegramCompactConfirmationReplyMarkup() {
741
795
  ],
742
796
  };
743
797
  }
744
- export function getTelegramCompactConfirmationHtml() {
798
+ function getTelegramCompactConfirmationHtml() {
745
799
  return "<b>Compact session?</b>";
746
800
  }
747
- export async function openTelegramCompactConfirmation(target, deps) {
801
+ async function openTelegramCompactConfirmation(target, deps) {
748
802
  await deps.sendInteractiveMessage(target.chatId, getTelegramCompactConfirmationHtml(), "html", buildTelegramCompactConfirmationReplyMarkup(), target.threadId !== undefined
749
803
  ? { target: { chatId: target.chatId, threadId: target.threadId } }
750
804
  : undefined);
@@ -1223,7 +1277,7 @@ async function handleTelegramCommandRuntime(commandName, message, ctx, deps, com
1223
1277
  export const TELEGRAM_INTERNAL_COMMAND_NAME = "telegram-internal";
1224
1278
  export const TELEGRAM_INTERNAL_COMMAND_DESCRIPTION = "Internal Telegram command cannot be run manually";
1225
1279
  export const TELEGRAM_INTERNAL_MANUAL_USE_MESSAGE = "This internal Telegram command cannot be run manually.";
1226
- export function delayTelegramSessionAction(delayMs) {
1280
+ function delayTelegramSessionAction(delayMs) {
1227
1281
  return new Promise((resolve) => setTimeout(resolve, delayMs));
1228
1282
  }
1229
1283
  export async function settleTelegramSessionReplacement(deps) {
@@ -1255,7 +1309,7 @@ export async function settleTelegramSessionReplacement(deps) {
1255
1309
  }
1256
1310
  return "stale";
1257
1311
  }
1258
- export function createTelegramSessionReplacementSettlementRuntime(deps) {
1312
+ function createTelegramSessionReplacementSettlementRuntime(deps) {
1259
1313
  let generation = 0;
1260
1314
  return {
1261
1315
  onSessionStart(ctx) {
@@ -16,17 +16,10 @@ export type TelegramBotTokenReference = {
16
16
  } | {
17
17
  kind: "malformed";
18
18
  };
19
- /**
20
- * Parse a persisted bot-token value. `$NAME` and `${NAME}` are exact
21
- * environment-variable references. Any other `$`-prefixed value is malformed
22
- * rather than a literal secret so a broken reference fails closed.
23
- */
24
- export declare function getTelegramBotTokenReference(value: string | undefined): TelegramBotTokenReference | undefined;
25
19
  /** Resolve a persisted token at a validation/activation boundary. */
26
20
  export declare function resolveTelegramBotToken(value: string | undefined, env?: NodeJS.ProcessEnv): string | undefined;
27
21
  /** Redacted diagnostic for an unresolved or malformed token reference. */
28
22
  export declare function getTelegramBotTokenDiagnostic(value: string | undefined, env?: NodeJS.ProcessEnv): string | undefined;
29
- export type TelegramOutboundCommandTemplateConfig = string | CommandTemplateObjectConfig;
30
23
  export interface TelegramOutboundHandlerConfig extends CommandTemplateObjectConfig {
31
24
  type?: string;
32
25
  match?: string | string[];
@@ -179,13 +172,6 @@ export declare function normalizeTelegramDefaultProfileConfig(config: TelegramCo
179
172
  changed: boolean;
180
173
  };
181
174
  export declare function createTelegramConfigStore(options?: TelegramConfigStoreOptions): TelegramConfigStore;
182
- export declare function createTelegramDraftPreviewsChecker(configStore: Pick<TelegramConfigStore, "get">): () => boolean;
183
- export declare function createTelegramDraftPreviewsSetter(configStore: TelegramMutableConfigStore): (enabled: boolean) => Promise<void>;
184
- export declare function createTelegramAssistantRenderingModeGetter(configStore: Pick<TelegramConfigStore, "get">): () => TelegramAssistantRenderingMode;
185
- export declare function createTelegramAssistantRenderingModeSetter(configStore: TelegramMutableConfigStore): (mode: TelegramAssistantRenderingMode) => Promise<void>;
186
- export declare function createTelegramActivityVerbosityGetter(configStore: Pick<TelegramConfigStore, "get">): () => TelegramActivityVerbosity;
187
- export declare function createTelegramActivityVerbosityRefresher(configStore: TelegramMutableConfigStore): () => Promise<void>;
188
- export declare function createTelegramActivityVerbositySetter(configStore: TelegramMutableConfigStore): (verbosity: TelegramActivityVerbosity) => Promise<void>;
189
175
  export declare function createTelegramVoiceReplyModeGetter(configStore: Pick<TelegramConfigStore, "get">): () => "manual" | "mirror" | "always";
190
176
  export declare function createTelegramVoiceReplyModeConfiguredChecker(configStore: Pick<TelegramConfigStore, "get">): () => boolean;
191
177
  export declare function createTelegramVoiceReplyModeSetter(configStore: TelegramMutableConfigStore): (replyMode: "manual" | "hidden" | "mirror" | "always" | undefined) => Promise<void>;
@@ -203,9 +189,6 @@ export declare function createTelegramProactivePushTargetGetter(deps: {
203
189
  getAssignedTarget: () => TelegramProactivePushTarget | undefined;
204
190
  getAllowedUserId: () => number | undefined;
205
191
  }): () => TelegramProactivePushTarget | undefined;
206
- export declare function createTelegramAutomaticThreadCleanupChecker(configStore: Pick<TelegramConfigStore, "get">): () => boolean;
207
- export declare function createTelegramAutomaticThreadCleanupResolver(configStore: TelegramMutableConfigStore): () => Promise<boolean>;
208
- export declare function createTelegramAutomaticThreadCleanupSetter(configStore: TelegramMutableConfigStore): (enabled: boolean) => Promise<void>;
209
192
  export declare function createTelegramConfigControls(configStore: TelegramMutableConfigStore): {
210
193
  areDraftPreviewsEnabled: () => boolean;
211
194
  setDraftPreviewsEnabled: (enabled: boolean) => Promise<void>;
@@ -8,6 +8,7 @@ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, u
8
8
  import { chmod, mkdir, rename, writeFile } from "node:fs/promises";
9
9
  import { resolveAgentDir, resolveTelegramConfigPath, TELEGRAM_DEFAULT_PROFILE_NAME, } from "./paths.js";
10
10
  export { TELEGRAM_DEFAULT_PROFILE_NAME } from "./paths.js";
11
+ import { isWireRecord as isPlainConfigRecord } from "./wire.js";
11
12
  import { withTelegramFileTransaction } from "./locks.js";
12
13
  const CONFIG_RUNTIME_KEY = "__piTelegramConfigRuntime__";
13
14
  const CONFIG_REPLACE_RETRY_ATTEMPTS = 5;
@@ -45,7 +46,7 @@ const TELEGRAM_BOT_TOKEN_ENV_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
45
46
  * environment-variable references. Any other `$`-prefixed value is malformed
46
47
  * rather than a literal secret so a broken reference fails closed.
47
48
  */
48
- export function getTelegramBotTokenReference(value) {
49
+ function getTelegramBotTokenReference(value) {
49
50
  const trimmed = value?.trim();
50
51
  if (!trimmed)
51
52
  return undefined;
@@ -211,9 +212,6 @@ export async function writeTelegramConfig(agentDir, configPath, config) {
211
212
  await rename(tempConfigPath, configPath);
212
213
  await chmod(configPath, 0o600);
213
214
  }
214
- function isPlainConfigRecord(value) {
215
- return !!value && typeof value === "object" && !Array.isArray(value);
216
- }
217
215
  function cloneTelegramConfig(value) {
218
216
  return structuredClone(value);
219
217
  }
@@ -628,7 +626,7 @@ export function createTelegramConfigStore(options = {}) {
628
626
  },
629
627
  };
630
628
  }
631
- export function createTelegramDraftPreviewsChecker(configStore) {
629
+ function createTelegramDraftPreviewsChecker(configStore) {
632
630
  return () => {
633
631
  const config = configStore.get();
634
632
  return (config.assistant?.draftPreviews ??
@@ -637,7 +635,7 @@ export function createTelegramDraftPreviewsChecker(configStore) {
637
635
  true);
638
636
  };
639
637
  }
640
- export function createTelegramDraftPreviewsSetter(configStore) {
638
+ function createTelegramDraftPreviewsSetter(configStore) {
641
639
  return async (enabled) => {
642
640
  await loadLatestTelegramConfig(configStore);
643
641
  const { draftPreviews: _legacyDraftPreviews, richDraftPreviews: _legacyRichDraftPreviews, ...current } = configStore.get();
@@ -649,14 +647,14 @@ export function createTelegramDraftPreviewsSetter(configStore) {
649
647
  await configStore.persist(config);
650
648
  };
651
649
  }
652
- export function createTelegramAssistantRenderingModeGetter(configStore) {
650
+ function createTelegramAssistantRenderingModeGetter(configStore) {
653
651
  return () => {
654
652
  const config = configStore.get();
655
653
  const mode = config.assistant?.rendering ?? config.assistantRendering;
656
654
  return mode === "html" ? "html" : "rich";
657
655
  };
658
656
  }
659
- export function createTelegramAssistantRenderingModeSetter(configStore) {
657
+ function createTelegramAssistantRenderingModeSetter(configStore) {
660
658
  return async (mode) => {
661
659
  await loadLatestTelegramConfig(configStore);
662
660
  const { assistantRendering: _legacyAssistantRendering, ...current } = configStore.get();
@@ -668,7 +666,7 @@ export function createTelegramAssistantRenderingModeSetter(configStore) {
668
666
  await configStore.persist(config);
669
667
  };
670
668
  }
671
- export function createTelegramActivityVerbosityGetter(configStore) {
669
+ function createTelegramActivityVerbosityGetter(configStore) {
672
670
  return () => {
673
671
  const assistant = configStore.get().assistant;
674
672
  if (assistant?.activity !== undefined) {
@@ -685,10 +683,10 @@ export function createTelegramActivityVerbosityGetter(configStore) {
685
683
  return "verbose";
686
684
  };
687
685
  }
688
- export function createTelegramActivityVerbosityRefresher(configStore) {
686
+ function createTelegramActivityVerbosityRefresher(configStore) {
689
687
  return () => loadLatestTelegramConfig(configStore);
690
688
  }
691
- export function createTelegramActivityVerbositySetter(configStore) {
689
+ function createTelegramActivityVerbositySetter(configStore) {
692
690
  return async (verbosity) => {
693
691
  await loadLatestTelegramConfig(configStore);
694
692
  const current = configStore.get();
@@ -801,10 +799,10 @@ export function createTelegramProactivePushTargetGetter(deps) {
801
799
  return typeof chatId === "number" ? { chatId } : undefined;
802
800
  };
803
801
  }
804
- export function createTelegramAutomaticThreadCleanupChecker(configStore) {
802
+ function createTelegramAutomaticThreadCleanupChecker(configStore) {
805
803
  return () => configStore.get().threads?.automaticCleanup ?? true;
806
804
  }
807
- export function createTelegramAutomaticThreadCleanupResolver(configStore) {
805
+ function createTelegramAutomaticThreadCleanupResolver(configStore) {
808
806
  return async () => {
809
807
  await loadLatestTelegramConfig(configStore);
810
808
  if (configStore.didLastLoadRecoverInvalidConfig?.()) {
@@ -813,7 +811,7 @@ export function createTelegramAutomaticThreadCleanupResolver(configStore) {
813
811
  return createTelegramAutomaticThreadCleanupChecker(configStore)();
814
812
  };
815
813
  }
816
- export function createTelegramAutomaticThreadCleanupSetter(configStore) {
814
+ function createTelegramAutomaticThreadCleanupSetter(configStore) {
817
815
  return async (enabled) => {
818
816
  await loadLatestTelegramConfig(configStore);
819
817
  const current = configStore.get();
@@ -108,10 +108,6 @@ export declare function createTelegramBridgeDeliveryLifecycleHooks<TTransportSta
108
108
  getTransportStamp?: () => TTransportStamp;
109
109
  isTransportStampActive?: (stamp: TTransportStamp) => boolean;
110
110
  }): ReturnType<typeof createTelegramDeliveryLifecycleHooks>;
111
- export declare function classifyTelegramDeliveryTransportError(error: unknown): {
112
- reason: Extract<TelegramDeliveryFailureReason, "commit-unknown" | "message-unavailable" | "rate-limited" | "transport-retryable" | "transport-failed">;
113
- retryAfterMs?: number;
114
- };
115
111
  /** @internal */
116
112
  export interface TelegramDeliveryTargetPolicyView {
117
113
  canDeliver: boolean;