@llblab/pi-kit 0.18.1 → 0.19.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 (139) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +4 -2
  3. package/node_modules/@llblab/pi-state-flow/AGENTS.md +35 -37
  4. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +5 -3
  5. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +19 -1
  6. package/node_modules/@llblab/pi-state-flow/README.md +102 -48
  7. package/node_modules/@llblab/pi-state-flow/dist/index.d.ts +6 -9
  8. package/node_modules/@llblab/pi-state-flow/dist/index.js +6 -9
  9. package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.d.ts +2 -2
  10. package/node_modules/@llblab/pi-state-flow/dist/lib/acquisition.js +21 -8
  11. package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.d.ts +40 -24
  12. package/node_modules/@llblab/pi-state-flow/dist/lib/artifact.js +93 -63
  13. package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.d.ts +4 -3
  14. package/node_modules/@llblab/pi-state-flow/dist/lib/compaction.js +9 -9
  15. package/node_modules/@llblab/pi-state-flow/dist/lib/config.d.ts +1 -1
  16. package/node_modules/@llblab/pi-state-flow/dist/lib/config.js +6 -5
  17. package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +8 -7
  18. package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +56 -58
  19. package/node_modules/@llblab/pi-state-flow/dist/lib/continuation.js +12 -24
  20. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.d.ts +7 -18
  21. package/node_modules/@llblab/pi-state-flow/dist/lib/durable.js +47 -78
  22. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.d.ts +4 -6
  23. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +241 -437
  24. package/node_modules/@llblab/pi-state-flow/dist/lib/git.d.ts +2 -72
  25. package/node_modules/@llblab/pi-state-flow/dist/lib/git.js +120 -499
  26. package/node_modules/@llblab/pi-state-flow/dist/lib/history.d.ts +2 -1
  27. package/node_modules/@llblab/pi-state-flow/dist/lib/history.js +4 -3
  28. package/node_modules/@llblab/pi-state-flow/dist/lib/json.d.ts +3 -0
  29. package/node_modules/@llblab/pi-state-flow/dist/lib/json.js +43 -22
  30. package/node_modules/@llblab/pi-state-flow/dist/lib/logging.d.ts +1 -5
  31. package/node_modules/@llblab/pi-state-flow/dist/lib/logging.js +0 -2
  32. package/node_modules/@llblab/pi-state-flow/dist/lib/memory.d.ts +1 -14
  33. package/node_modules/@llblab/pi-state-flow/dist/lib/memory.js +5 -37
  34. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +1 -2
  35. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +13 -32
  36. package/node_modules/@llblab/pi-state-flow/dist/lib/query.d.ts +6 -6
  37. package/node_modules/@llblab/pi-state-flow/dist/lib/query.js +25 -21
  38. package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.d.ts +3 -3
  39. package/node_modules/@llblab/pi-state-flow/dist/lib/recovery.js +15 -12
  40. package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.d.ts +2 -4
  41. package/node_modules/@llblab/pi-state-flow/dist/lib/rehydration.js +5 -1
  42. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.d.ts +26 -88
  43. package/node_modules/@llblab/pi-state-flow/dist/lib/runtime.js +159 -255
  44. package/node_modules/@llblab/pi-state-flow/dist/lib/skills.d.ts +0 -3
  45. package/node_modules/@llblab/pi-state-flow/dist/lib/skills.js +1 -47
  46. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.d.ts +16 -33
  47. package/node_modules/@llblab/pi-state-flow/dist/lib/snapshot.js +48 -137
  48. package/node_modules/@llblab/pi-state-flow/dist/lib/state.d.ts +16 -11
  49. package/node_modules/@llblab/pi-state-flow/dist/lib/state.js +13 -14
  50. package/node_modules/@llblab/pi-state-flow/dist/lib/status.d.ts +1 -9
  51. package/node_modules/@llblab/pi-state-flow/dist/lib/status.js +15 -43
  52. package/node_modules/@llblab/pi-state-flow/dist/lib/storage.d.ts +5 -9
  53. package/node_modules/@llblab/pi-state-flow/dist/lib/storage.js +13 -45
  54. package/node_modules/@llblab/pi-state-flow/dist/lib/telegram.js +1 -1
  55. package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.d.ts +15 -7
  56. package/node_modules/@llblab/pi-state-flow/dist/lib/temporal.js +104 -24
  57. package/node_modules/@llblab/pi-state-flow/dist/lib/transition.d.ts +0 -2
  58. package/node_modules/@llblab/pi-state-flow/dist/lib/transition.js +11 -14
  59. package/node_modules/@llblab/pi-state-flow/dist/package.json +9 -6
  60. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-guide/SKILL.md +11 -17
  61. package/node_modules/@llblab/pi-state-flow/dist/skills/state-flow-memory/SKILL.md +8 -8
  62. package/node_modules/@llblab/pi-state-flow/docs/README.md +2 -2
  63. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +64 -67
  64. package/node_modules/@llblab/pi-state-flow/docs/compatibility.md +24 -6
  65. package/node_modules/@llblab/pi-state-flow/docs/filesystem-recovery.md +12 -15
  66. package/node_modules/@llblab/pi-state-flow/docs/fork-contract.md +22 -27
  67. package/node_modules/@llblab/pi-state-flow/docs/lazy-state.md +25 -41
  68. package/node_modules/@llblab/pi-state-flow/docs/performance.md +38 -421
  69. package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +33 -46
  70. package/node_modules/@llblab/pi-state-flow/docs/usage.md +37 -61
  71. package/node_modules/@llblab/pi-state-flow/index.ts +7 -71
  72. package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +26 -11
  73. package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +116 -88
  74. package/node_modules/@llblab/pi-state-flow/lib/compaction.ts +12 -10
  75. package/node_modules/@llblab/pi-state-flow/lib/config.ts +6 -6
  76. package/node_modules/@llblab/pi-state-flow/lib/context.ts +52 -59
  77. package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +11 -20
  78. package/node_modules/@llblab/pi-state-flow/lib/durable.ts +44 -86
  79. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +237 -460
  80. package/node_modules/@llblab/pi-state-flow/lib/git.ts +110 -552
  81. package/node_modules/@llblab/pi-state-flow/lib/history.ts +4 -3
  82. package/node_modules/@llblab/pi-state-flow/lib/json.ts +39 -23
  83. package/node_modules/@llblab/pi-state-flow/lib/logging.ts +1 -7
  84. package/node_modules/@llblab/pi-state-flow/lib/memory.ts +5 -44
  85. package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +14 -25
  86. package/node_modules/@llblab/pi-state-flow/lib/query.ts +26 -22
  87. package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +17 -11
  88. package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +7 -5
  89. package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +154 -254
  90. package/node_modules/@llblab/pi-state-flow/lib/skills.ts +1 -49
  91. package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +58 -142
  92. package/node_modules/@llblab/pi-state-flow/lib/state.ts +27 -19
  93. package/node_modules/@llblab/pi-state-flow/lib/status.ts +16 -54
  94. package/node_modules/@llblab/pi-state-flow/lib/storage.ts +12 -40
  95. package/node_modules/@llblab/pi-state-flow/lib/telegram.ts +1 -1
  96. package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +104 -22
  97. package/node_modules/@llblab/pi-state-flow/lib/transition.ts +16 -26
  98. package/node_modules/@llblab/pi-state-flow/package.json +9 -6
  99. package/node_modules/@llblab/pi-state-flow/skills/state-flow-guide/SKILL.md +11 -17
  100. package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +8 -8
  101. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +7 -0
  102. package/node_modules/@llblab/pi-telegram/README.md +1 -2
  103. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +1 -3
  104. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +1 -6
  105. package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +1 -4
  106. package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +13 -33
  107. package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +1 -4
  108. package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.d.ts +1 -0
  109. package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.js +72 -1
  110. package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +7 -0
  111. package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +27 -0
  112. package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +3 -0
  113. package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +17 -0
  114. package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +8 -1
  115. package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
  116. package/node_modules/@llblab/pi-telegram/docs/architecture.md +2 -0
  117. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +1 -1
  118. package/node_modules/@llblab/pi-telegram/docs/public-api.md +1 -1
  119. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +0 -10
  120. package/node_modules/@llblab/pi-telegram/lib/commands.ts +18 -41
  121. package/node_modules/@llblab/pi-telegram/lib/extension.ts +1 -8
  122. package/node_modules/@llblab/pi-telegram/lib/menu-queue.ts +126 -1
  123. package/node_modules/@llblab/pi-telegram/lib/queue.ts +46 -0
  124. package/node_modules/@llblab/pi-telegram/lib/replies.ts +19 -0
  125. package/node_modules/@llblab/pi-telegram/lib/sync.ts +8 -1
  126. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  127. package/package.json +3 -3
  128. package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.d.ts +0 -21
  129. package/node_modules/@llblab/pi-state-flow/dist/lib/discovery.js +0 -125
  130. package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.d.ts +0 -36
  131. package/node_modules/@llblab/pi-state-flow/dist/lib/maintenance.js +0 -98
  132. package/node_modules/@llblab/pi-state-flow/dist/lib/migration.d.ts +0 -13
  133. package/node_modules/@llblab/pi-state-flow/dist/lib/migration.js +0 -167
  134. package/node_modules/@llblab/pi-state-flow/dist/lib/publication.d.ts +0 -86
  135. package/node_modules/@llblab/pi-state-flow/dist/lib/publication.js +0 -437
  136. package/node_modules/@llblab/pi-state-flow/lib/discovery.ts +0 -133
  137. package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +0 -147
  138. package/node_modules/@llblab/pi-state-flow/lib/migration.ts +0 -171
  139. package/node_modules/@llblab/pi-state-flow/lib/publication.ts +0 -458
@@ -4,6 +4,13 @@
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.50.1: Queue, lifecycle, and Workspace hotfixes
8
+
9
+ - `Workspace slot recovery`: Uses the canonical binding returned by target-deduplicating Workspace commit for the live leader record and connection result, preventing a provisional recovery slot from consuming a second letter and falsely blocking follower registration at A–Z capacity.
10
+ - `Guest queue Skip`: Lets the leader discard a waiting Guest Mode prompt directly from the queue menu while other work is active. Guest callbacks use stable queue identity, settle durable admission before removal, stop placeholder rotation, and visually clear the inline globe placeholder without starting a model turn.
11
+ - `Internal lifecycle safety`: Binds each armed `/telegram-internal` lifecycle action to an unpredictable one-use token, so manual, mismatched, and replayed calls cannot consume or execute it.
12
+ - `Command surface`: Uses Pi-style argument-first autocomplete hints, keeps descriptions concise and punctuation-consistent, uses `profile` consistently, describes `/telegram-internal` as a manual-use warning, and removes Workspace Thread naming from `/telegram-connect`; Thread names remain configurable from Telegram.
13
+
7
14
  ## 0.50.0: Internal lifecycle gateway and thinking cadence
8
15
 
9
16
  - `Internal lifecycle gateway`: Replaces the session-specific technical command with one `/telegram-internal` gateway for runtime-armed typed actions. Manual invocation performs no action and explains that the command cannot be run manually; settled `/new` replacement retains the same lifecycle and terminal-result guarantees.
@@ -55,7 +55,7 @@ Paste the bot token. If `~/.pi/agent/telegram.json` already contains a saved tok
55
55
  /telegram-connect
56
56
  ```
57
57
 
58
- The connected Pi instance owns Telegram polling. Use `/telegram-connect <profile>` to activate a named profile, and optionally append `as=Name` to name a fresh Workspace Thread. Each profile is a parallel bot runtime with isolated polling, diagnostics, Threaded Mode state, and local bus transport; the `default` profile keeps unsuffixed runtime paths. In classic mode each profile uses a singleton lock. When Telegram private-chat Threaded Mode is available, one live instance becomes the profile's leader and later visible Pi instances register as followers. Reopening or resuming the same Pi session restores its remembered Thread at session startup; a distinct session in the same directory has its own binding and still requires explicit `/telegram-connect` when no remembered binding exists.
58
+ The connected Pi instance owns Telegram polling. Use `/telegram-connect <profile>` to activate a named profile. Each profile is a parallel bot runtime with isolated polling, diagnostics, Threaded Mode state, and local bus transport; the `default` profile keeps unsuffixed runtime paths. In classic mode each profile uses a singleton lock. When Telegram private-chat Threaded Mode is available, one live instance becomes the profile's leader and later visible Pi instances register as followers. Reopening or resuming the same Pi session restores its remembered Thread at session startup; a distinct session in the same directory has its own binding and still requires explicit `/telegram-connect` when no remembered binding exists.
59
59
 
60
60
  After an unclean computer shutdown, `/telegram-connect` detects truncated or structurally invalid temporary ownership/routing files, quarantines only the damaged files under `tmp/telegram/recovery/`, and retries once. A journal snapshot removed by older broad temp cleanup is rebuilt when its complete segment history proves an empty result, while a revisionless snapshot is repaired from the first surviving segment's exact predecessor when the reconstructed tail validates. Otherwise the snapshot and segments are quarantined as recovery evidence, a fresh journal is published, and startup continues with an informational diagnostic instead of requiring manual JSON repair. Unsupported journal versions block recovery without rewriting or quarantining the retained files; use a compatible runtime rather than deleting journals. Saved `telegram.json` configuration and runtime diagnostics remain intact. Recovery never replaces a verifiable live owner; if safe automatic recovery cannot complete, the command gives one explicit Pi-restart instruction instead of requiring deletion of the whole `tmp/` directory.
61
61
 
@@ -183,7 +183,6 @@ Run these inside Pi.
183
183
  | `/telegram-setup <profile>` | Save or update a named-profile bot token |
184
184
  | `/telegram-connect` / `/telegram-connect default` | Activate `profiles.default` and acquire its transport ownership |
185
185
  | `/telegram-connect <profile>` | Activate a named profile and acquire its transport ownership |
186
- | `/telegram-connect [profile] as=Name` | Give a fresh Workspace Thread one unique capitalized Latin-word identity while connecting |
187
186
  | `/telegram-disconnect` | Confirm, then stop polling, release ownership, and delete this instance's Threaded Mode tab; graceful Pi quit always preserves restart ownership and independently deletes the tab only when automatic cleanup is enabled |
188
187
  | `/telegram-status` | Inspect connection, mode, separate polling/worker progress, journal depth, queue, transport, automatic retry state, and recent diagnostics |
189
188
 
@@ -165,8 +165,6 @@ interface TelegramCommandsAndToolsBindingDeps {
165
165
  stopPolling?: () => Promise<void | string>;
166
166
  recoverPollingStart?: Commands.TelegramBridgeCommandRegistrationDeps["recoverPollingStart"];
167
167
  getDisconnectThreadName?: () => string | undefined;
168
- setRequestedThreadNameForPollingStart?: (threadName: string | undefined) => void;
169
- validateThreadName?: Commands.TelegramBridgeCommandRegistrationDeps["validateThreadName"];
170
168
  onTransportChanged?: () => Promise<void> | void;
171
169
  getStatusLines: (options?: Status.TelegramBridgeStatusLineOptions) => string[];
172
170
  buttonActionStore: OutboundHandlers.TelegramButtonActionStore;
@@ -196,7 +194,7 @@ interface TelegramCommandsAndToolsBindingDeps {
196
194
  updateStatus: TelegramBridgeStatusUpdater;
197
195
  recordRuntimeEvent: TelegramRuntimeEventRecorder;
198
196
  }
199
- export declare function registerTelegramCommandsAndTools({ pi, agentDir, configStore, persistConfig, setup, activeTurnRuntime, lockedPollingRuntime, stopPolling, recoverPollingStart, getDisconnectThreadName, setRequestedThreadNameForPollingStart, validateThreadName, onTransportChanged, getStatusLines, buttonActionStore, sendMarkdownReply, sendChannelMarkdownMessage, sendChannelMediaMessage, listChannelPosts, mutateChannelPost, callMultipart, getDefaultChatId, getDefaultTarget, resolveAgentTarget, routeAgentMessage, canSendDirect, setGenerativeAppLiveSurfaceRuntime, recordRuntimeEvent, updateStatus, }: TelegramCommandsAndToolsBindingDeps): void;
197
+ export declare function registerTelegramCommandsAndTools({ pi, agentDir, configStore, persistConfig, setup, activeTurnRuntime, lockedPollingRuntime, stopPolling, recoverPollingStart, getDisconnectThreadName, onTransportChanged, getStatusLines, buttonActionStore, sendMarkdownReply, sendChannelMarkdownMessage, sendChannelMediaMessage, listChannelPosts, mutateChannelPost, callMultipart, getDefaultChatId, getDefaultTarget, resolveAgentTarget, routeAgentMessage, canSendDirect, setGenerativeAppLiveSurfaceRuntime, recordRuntimeEvent, updateStatus, }: TelegramCommandsAndToolsBindingDeps): void;
200
198
  interface TelegramLifecycleBindingDeps {
201
199
  pi: Pi.ExtensionAPI;
202
200
  publicationRuntime: TelegramBridgePublicationRuntime;
@@ -323,7 +323,7 @@ export function createTelegramActivityBindingRuntime(deps) {
323
323
  },
324
324
  };
325
325
  }
326
- export function registerTelegramCommandsAndTools({ pi, agentDir, configStore, persistConfig, setup, activeTurnRuntime, lockedPollingRuntime, stopPolling, recoverPollingStart, getDisconnectThreadName, setRequestedThreadNameForPollingStart, validateThreadName, onTransportChanged, getStatusLines, buttonActionStore, sendMarkdownReply, sendChannelMarkdownMessage, sendChannelMediaMessage, listChannelPosts, mutateChannelPost, callMultipart, getDefaultChatId, getDefaultTarget, resolveAgentTarget, routeAgentMessage, canSendDirect, setGenerativeAppLiveSurfaceRuntime, recordRuntimeEvent, updateStatus, }) {
326
+ export function registerTelegramCommandsAndTools({ pi, agentDir, configStore, persistConfig, setup, activeTurnRuntime, lockedPollingRuntime, stopPolling, recoverPollingStart, getDisconnectThreadName, onTransportChanged, getStatusLines, buttonActionStore, sendMarkdownReply, sendChannelMarkdownMessage, sendChannelMediaMessage, listChannelPosts, mutateChannelPost, callMultipart, getDefaultChatId, getDefaultTarget, resolveAgentTarget, routeAgentMessage, canSendDirect, setGenerativeAppLiveSurfaceRuntime, recordRuntimeEvent, updateStatus, }) {
327
327
  GenerativeApps.registerTelegramBindTool(pi, {
328
328
  agentDir,
329
329
  getActiveProfileName: configStore.getActiveProfileName,
@@ -453,7 +453,6 @@ export function registerTelegramCommandsAndTools({ pi, agentDir, configStore, pe
453
453
  hasBotToken: configStore.hasBotToken,
454
454
  getBotTokenDiagnostic: configStore.getBotTokenDiagnostic,
455
455
  startPolling: async (ctx, options) => {
456
- setRequestedThreadNameForPollingStart?.(options?.requestedThreadName);
457
456
  try {
458
457
  return await lockedPollingRuntime.start(ctx, options);
459
458
  }
@@ -461,14 +460,10 @@ export function registerTelegramCommandsAndTools({ pi, agentDir, configStore, pe
461
460
  recordRuntimeEvent("recovery", error, { phase: "polling-start" });
462
461
  throw error;
463
462
  }
464
- finally {
465
- setRequestedThreadNameForPollingStart?.(undefined);
466
- }
467
463
  },
468
464
  stopPolling: stopPolling ?? lockedPollingRuntime.stop,
469
465
  recoverPollingStart,
470
466
  getDisconnectThreadName,
471
- validateThreadName,
472
467
  queueAgentConnectionContext,
473
468
  updateStatus,
474
469
  getProfileNames: () => Config.getTelegramProfileNames(configStore.getStoredConfig()),
@@ -87,7 +87,6 @@ export declare function createTelegramBotCommandRegistrar(deps: TelegramBotComma
87
87
  export interface TelegramBridgeCommandStartPollingOptions {
88
88
  force?: boolean;
89
89
  forceFreshLeaderThread?: boolean;
90
- requestedThreadName?: string;
91
90
  }
92
91
  export interface TelegramBridgeCommandStartPollingResult {
93
92
  ok: boolean;
@@ -119,7 +118,6 @@ export interface TelegramBridgeCommandRegistrationDeps {
119
118
  getProfileNames?: () => string[];
120
119
  activateDefaultProfileConfig?: (ctx: ExtensionCommandContext) => Promise<void>;
121
120
  activateProfileConfig?: (ctx: ExtensionCommandContext, profileName: string) => Promise<boolean>;
122
- validateThreadName?: (threadName: string) => string | undefined;
123
121
  }
124
122
  export type TelegramThreadDisplayNameRenamePort = (target: {
125
123
  chatId: number;
@@ -145,7 +143,6 @@ export declare function createTelegramThreadDisplayNameRenameBinding(): {
145
143
  bind: (rename: TelegramThreadDisplayNameRenamePort) => void;
146
144
  rename: TelegramThreadDisplayNameRenamePort;
147
145
  };
148
- export declare function parseTelegramRequestedThreadName(args: string): string | undefined;
149
146
  export declare function registerTelegramBridgeCommands(pi: ExtensionAPI, deps: TelegramBridgeCommandRegistrationDeps): void;
150
147
  export declare const TELEGRAM_RESERVED_COMMAND_NAMES: readonly ["stop", "name", "new", "abort", "next", "continue", "status", "queue", "compact", "model", "thinking", "settings", "help", "start"];
151
148
  export type TelegramReservedCommandName = (typeof TELEGRAM_RESERVED_COMMAND_NAMES)[number];
@@ -575,7 +572,7 @@ export declare function createTelegramCommandOrPromptRuntime<TMessage, TContext>
575
572
  dispatchMessages: (messages: TMessage[], ctx: TContext) => Promise<void>;
576
573
  };
577
574
  export declare const TELEGRAM_INTERNAL_COMMAND_NAME = "telegram-internal";
578
- export declare const TELEGRAM_INTERNAL_COMMAND_DESCRIPTION = "(internal) dispatch one settled Telegram lifecycle action";
575
+ export declare const TELEGRAM_INTERNAL_COMMAND_DESCRIPTION = "Internal Telegram command cannot be run manually";
579
576
  export declare const TELEGRAM_INTERNAL_MANUAL_USE_MESSAGE = "This internal Telegram command cannot be run manually.";
580
577
  export declare function delayTelegramSessionAction(delayMs: number): Promise<void>;
581
578
  export interface TelegramSessionActionRuntimeDeps {
@@ -3,6 +3,7 @@
3
3
  * Zones: telegram controls, pi agent commands, queue controls
4
4
  * Owns Telegram slash-command normalization, bot command metadata, pi-side command registration, and command-initiated session replacement orchestration behind runtime ports
5
5
  */
6
+ import { randomUUID } from "node:crypto";
6
7
  import { pairTelegramUserIfNeeded, TELEGRAM_DEFAULT_PROFILE_NAME, } from "./config.js";
7
8
  import { escapeHtml } from "./rendering.js";
8
9
  import { createTelegramControlItemBuilder, createTelegramControlQueueController, createTelegramQueueAdmissionReceipt, } from "./queue.js";
@@ -241,14 +242,6 @@ function parseTelegramProfileArg(args) {
241
242
  return undefined;
242
243
  return word === TELEGRAM_DEFAULT_PROFILE_NAME ? undefined : word;
243
244
  }
244
- export function parseTelegramRequestedThreadName(args) {
245
- const token = args
246
- .trim()
247
- .split(/\s+/)
248
- .find((word) => /^as=/i.test(word));
249
- const value = token?.slice(3).trim();
250
- return value || undefined;
251
- }
252
245
  function formatTelegramTakeoverTitle(ctx) {
253
246
  return ctx.ui.theme.fg("accent", "pi-telegram");
254
247
  }
@@ -262,7 +255,7 @@ function formatTelegramTakeoverPrompt(ctx, owner) {
262
255
  }
263
256
  export function registerTelegramBridgeCommands(pi, deps) {
264
257
  pi.registerCommand("telegram-setup", {
265
- description: "Configure Telegram bot token. Use /telegram-setup <name> for named profiles.",
258
+ description: "<profile> — Configure Telegram bot token",
266
259
  handler: async (args, ctx) => {
267
260
  await deps.promptForConfig(ctx, parseTelegramProfileArg(args));
268
261
  },
@@ -275,26 +268,14 @@ export function registerTelegramBridgeCommands(pi, deps) {
275
268
  },
276
269
  });
277
270
  pi.registerCommand("telegram-connect", {
278
- description: "Start the Telegram bridge. Use /telegram-connect <profile> and optional as=Name for a fresh Workspace Thread.",
271
+ description: "<profile> — Start Telegram bridge",
279
272
  handler: async (args, ctx) => {
280
- const profileName = parseTelegramProfileArg(args);
281
- const requestedNameTokens = args
282
- .trim()
283
- .split(/\s+/)
284
- .filter((word) => /^as=/i.test(word));
285
- const requestedThreadName = parseTelegramRequestedThreadName(args);
286
- const requestedNameError = requestedNameTokens.length > 1
287
- ? "Specify at most one as=Name Workspace Thread name."
288
- : requestedNameTokens.length === 1 && !requestedThreadName
289
- ? "Usage: /telegram-connect [profile] as=Flightprice"
290
- : requestedThreadName
291
- ? deps.validateThreadName?.(requestedThreadName)
292
- : undefined;
293
- if (requestedNameError) {
294
- ctx.ui.notify(requestedNameError, "warning");
273
+ if (args.trim().split(/\s+/).some((word) => /^as=/i.test(word))) {
274
+ ctx.ui.notify("Thread names are configured from Telegram, not from Pi commands.", "warning");
295
275
  deps.updateStatus(ctx);
296
276
  return;
297
277
  }
278
+ const profileName = parseTelegramProfileArg(args);
298
279
  if (profileName && deps.activateProfileConfig) {
299
280
  const ok = await deps.activateProfileConfig(ctx, profileName);
300
281
  if (!ok) {
@@ -357,7 +338,6 @@ export function registerTelegramBridgeCommands(pi, deps) {
357
338
  };
358
339
  let result = await startWithRecovery({
359
340
  forceFreshLeaderThread: true,
360
- ...(requestedThreadName ? { requestedThreadName } : {}),
361
341
  });
362
342
  if (result && !result.ok && result.canTakeover) {
363
343
  const confirmed = await ctx.ui.confirm(formatTelegramTakeoverTitle(ctx), formatTelegramTakeoverPrompt(ctx, result.owner));
@@ -369,7 +349,6 @@ export function registerTelegramBridgeCommands(pi, deps) {
369
349
  result = await startWithRecovery({
370
350
  force: true,
371
351
  forceFreshLeaderThread: true,
372
- ...(requestedThreadName ? { requestedThreadName } : {}),
373
352
  });
374
353
  }
375
354
  if (result?.message) {
@@ -382,7 +361,7 @@ export function registerTelegramBridgeCommands(pi, deps) {
382
361
  },
383
362
  });
384
363
  pi.registerCommand("telegram-disconnect", {
385
- description: "Stop Telegram; in Threaded Mode, delete this instance's current thread",
364
+ description: "Stop Telegram and delete current thread in Threaded Mode",
386
365
  handler: async (_args, ctx) => {
387
366
  const threadName = deps.getDisconnectThreadName?.();
388
367
  if (threadName) {
@@ -1233,7 +1212,7 @@ async function handleTelegramCommandRuntime(commandName, message, ctx, deps, com
1233
1212
  }, commandArgs);
1234
1213
  }
1235
1214
  export const TELEGRAM_INTERNAL_COMMAND_NAME = "telegram-internal";
1236
- export const TELEGRAM_INTERNAL_COMMAND_DESCRIPTION = "(internal) dispatch one settled Telegram lifecycle action";
1215
+ export const TELEGRAM_INTERNAL_COMMAND_DESCRIPTION = "Internal Telegram command cannot be run manually";
1237
1216
  export const TELEGRAM_INTERNAL_MANUAL_USE_MESSAGE = "This internal Telegram command cannot be run manually.";
1238
1217
  export function delayTelegramSessionAction(delayMs) {
1239
1218
  return new Promise((resolve) => setTimeout(resolve, delayMs));
@@ -1382,9 +1361,9 @@ export function createTelegramSessionActionRuntime(deps) {
1382
1361
  registered = true;
1383
1362
  deps.registerCommand(TELEGRAM_INTERNAL_COMMAND_NAME, {
1384
1363
  description: TELEGRAM_INTERNAL_COMMAND_DESCRIPTION,
1385
- handler: async (_args, ctx) => {
1364
+ handler: async (args, ctx) => {
1386
1365
  const action = pendingAction;
1387
- if (!action) {
1366
+ if (!action || args.trim() !== action.token) {
1388
1367
  ctx.ui.notify(TELEGRAM_INTERNAL_MANUAL_USE_MESSAGE, "warning");
1389
1368
  return;
1390
1369
  }
@@ -1421,9 +1400,10 @@ export function createTelegramSessionActionRuntime(deps) {
1421
1400
  pendingTarget = undefined;
1422
1401
  if (!target)
1423
1402
  return;
1424
- pendingAction = { kind: "replace-session", updateId, target };
1403
+ const token = randomUUID();
1404
+ pendingAction = { kind: "replace-session", token, updateId, target };
1425
1405
  void Promise.resolve()
1426
- .then(() => deps.sendUserMessage(`/${TELEGRAM_INTERNAL_COMMAND_NAME}`, {
1406
+ .then(() => deps.sendUserMessage(`/${TELEGRAM_INTERNAL_COMMAND_NAME} ${token}`, {
1427
1407
  expandPromptTemplates: true,
1428
1408
  }))
1429
1409
  .catch((error) => {
@@ -689,6 +689,7 @@ export default function (pi) {
689
689
  storeModelMenuState: modelMenuRuntime.storeState,
690
690
  updateStatusMessage: menuActions.updateStatusMessage,
691
691
  updateStatus,
692
+ dismissGuestPlaceholder: guestPlaceholderRuntime.dismiss,
692
693
  });
693
694
  const threadDisplaySettingsRuntime = ThreadDisplay.createTelegramThreadDisplaySettingsRuntime({
694
695
  getTarget() {
@@ -1449,10 +1450,6 @@ export default function (pi) {
1449
1450
  return undefined;
1450
1451
  return record.threadName ?? "current Telegram thread";
1451
1452
  },
1452
- setRequestedThreadNameForPollingStart: telegramThreadCapabilityState.setRequestedThreadName,
1453
- validateThreadName(threadName) {
1454
- return Threads.getTelegramTopicThreadNameValidationError(threadName, undefined);
1455
- },
1456
1453
  onTransportChanged() {
1457
1454
  deliveryLifecycleRuntime.onSessionStart();
1458
1455
  activityVerbosityRuntime.reset();
@@ -33,5 +33,6 @@ export declare function createTelegramQueueMenuRuntime<Context, TModel extends M
33
33
  storeModelMenuState: (state: TelegramModelMenuState<TModel>) => void;
34
34
  updateStatusMessage: (state: TelegramModelMenuState<TModel>, ctx: Context) => Promise<void>;
35
35
  updateStatus: (ctx: Context) => void;
36
+ dismissGuestPlaceholder?: (inlineMessageId: string) => Promise<void>;
36
37
  }): TelegramQueueMenuRuntime<Context>;
37
38
  export {};
@@ -34,6 +34,8 @@ function toTelegramQueueMenuItems(items) {
34
34
  return {
35
35
  chatId: item.chatId,
36
36
  replyToMessageId: item.replyToMessageId,
37
+ queueOrder: item.queueOrder,
38
+ isGuest: item.kind === "prompt" && item.guestQueryId !== undefined,
37
39
  queuePosition: index + 1,
38
40
  isPriority: item.queueLane === "priority",
39
41
  priorityEmoji: item.kind === "prompt" ? item.priorityEmoji : undefined,
@@ -72,7 +74,9 @@ function buildTelegramQueueMenuReplyMarkup(items, emptyRefreshIndex = 0) {
72
74
  return [
73
75
  {
74
76
  text: label,
75
- callback_data: `queue:pick:${item.chatId}:${item.replyToMessageId}`,
77
+ callback_data: item.isGuest
78
+ ? `queue:guest-pick:${item.queueOrder}`
79
+ : `queue:pick:${item.chatId}:${item.replyToMessageId}`,
76
80
  },
77
81
  ];
78
82
  });
@@ -128,6 +132,19 @@ function getTelegramQueueMenuItemText(item) {
128
132
  const preview = `<pre>${escapeTelegramQueueMenuHtmlPreview(item.promptText)}</pre>`;
129
133
  return `${heading}\n${preview}`;
130
134
  }
135
+ function buildTelegramGuestQueueItemSubmenuReplyMarkup(queueOrder) {
136
+ return {
137
+ inline_keyboard: [
138
+ [{ text: "⬆️ Back", callback_data: "queue:list" }],
139
+ [
140
+ {
141
+ text: "🔴 Skip",
142
+ callback_data: `queue:guest-skip:${queueOrder}`,
143
+ },
144
+ ],
145
+ ],
146
+ };
147
+ }
131
148
  function buildTelegramQueueItemSubmenuReplyMarkup(chatId, replyToMessageId, isPriority, isSkipped) {
132
149
  return {
133
150
  inline_keyboard: [
@@ -171,6 +188,21 @@ async function handleTelegramQueueMenuCallback(callbackQueryId, data, replyChatI
171
188
  await updateTelegramQueueMenuList(callbackQueryId, replyChatId, replyMessageId, deps, undefined, refreshMatch[1] === undefined ? 0 : Number(refreshMatch[1]));
172
189
  return true;
173
190
  }
191
+ const guestPickMatch = data.match(/^queue:guest-pick:(\d+)$/);
192
+ if (guestPickMatch) {
193
+ await handleTelegramGuestQueueMenuPick(callbackQueryId, replyChatId, replyMessageId, Number(guestPickMatch[1]), deps);
194
+ return true;
195
+ }
196
+ const guestSkipMatch = data.match(/^queue:guest-skip:(\d+)$/);
197
+ if (guestSkipMatch) {
198
+ const skipped = await deps.skipGuest(Number(guestSkipMatch[1]), ctx);
199
+ if (!skipped) {
200
+ await refreshStaleTelegramQueueMenuItem(callbackQueryId, replyChatId, replyMessageId, deps);
201
+ return true;
202
+ }
203
+ await updateTelegramQueueMenuList(callbackQueryId, replyChatId, replyMessageId, deps, "Guest prompt skipped.");
204
+ return true;
205
+ }
174
206
  const pickMatch = data.match(/^queue:pick:(\d+):(\d+)$/);
175
207
  if (pickMatch) {
176
208
  await handleTelegramQueueMenuPick(callbackQueryId, replyChatId, replyMessageId, Number(pickMatch[1]), Number(pickMatch[2]), deps);
@@ -208,6 +240,14 @@ async function updateTelegramQueueMenuList(callbackQueryId, replyChatId, replyMe
208
240
  async function refreshStaleTelegramQueueMenuItem(callbackQueryId, replyChatId, replyMessageId, deps) {
209
241
  await updateTelegramQueueMenuList(callbackQueryId, replyChatId, replyMessageId, deps, "Item no longer in queue.");
210
242
  }
243
+ async function handleTelegramGuestQueueMenuPick(callbackQueryId, replyChatId, replyMessageId, queueOrder, deps) {
244
+ const item = deps.findGuestItem(queueOrder);
245
+ if (!item) {
246
+ return refreshStaleTelegramQueueMenuItem(callbackQueryId, replyChatId, replyMessageId, deps);
247
+ }
248
+ await deps.updateQueueMessage(replyChatId, replyMessageId, getTelegramQueueMenuItemText(item), buildTelegramGuestQueueItemSubmenuReplyMarkup(queueOrder));
249
+ await deps.answerCallbackQuery(callbackQueryId);
250
+ }
211
251
  async function handleTelegramQueueMenuPick(callbackQueryId, replyChatId, replyMessageId, chatId, msgId, deps) {
212
252
  const item = deps.findItem(chatId, msgId);
213
253
  if (!item) {
@@ -272,6 +312,7 @@ export function createTelegramQueueMenuRuntime(deps) {
272
312
  updateStatusMessage: deps.updateStatusMessage,
273
313
  answerCallbackQuery: deps.answerCallbackQuery,
274
314
  updateStatus: deps.updateStatus,
315
+ dismissGuestPlaceholder: deps.dismissGuestPlaceholder,
275
316
  }),
276
317
  };
277
318
  }
@@ -318,9 +359,22 @@ function createQueueMenuCallbackHandler(deps) {
318
359
  const findItem = (cId, rId) => {
319
360
  return findTelegramQueueMenuItem(toMenuItems(), cId, rId);
320
361
  };
362
+ const findGuestItem = (queueOrder) => {
363
+ return toMenuItems().find((item) => {
364
+ return item.isGuest && item.queueOrder === queueOrder;
365
+ });
366
+ };
321
367
  return handleTelegramQueueMenuCallback(query.id, data, chatId, messageId, ctx, {
322
368
  getQueuedItems: toMenuItems,
323
369
  findItem,
370
+ findGuestItem,
371
+ skipGuest: (queueOrder, callbackContext) => {
372
+ return skipQueuedTelegramGuestPrompt(queueOrder, callbackContext, {
373
+ getQueueSnapshot,
374
+ queueMutationRuntime: deps.queueMutationRuntime,
375
+ dismissGuestPlaceholder: deps.dismissGuestPlaceholder,
376
+ });
377
+ },
324
378
  togglePriority: (cId, rId) => {
325
379
  return toggleQueuedTelegramPromptPriority(cId, rId, ctx, {
326
380
  getQueueSnapshot,
@@ -345,6 +399,23 @@ function createQueueMenuCallbackHandler(deps) {
345
399
  });
346
400
  };
347
401
  }
402
+ async function skipQueuedTelegramGuestPrompt(queueOrder, ctx, deps) {
403
+ const item = deps.getQueueSnapshot().find((candidate) => {
404
+ return candidate.kind === "prompt" &&
405
+ candidate.guestQueryId !== undefined &&
406
+ candidate.queueOrder === queueOrder;
407
+ });
408
+ if (!item || !deps.queueMutationRuntime.removeGuestPromptByQueueOrder) {
409
+ return false;
410
+ }
411
+ const removed = deps.queueMutationRuntime.removeGuestPromptByQueueOrder(queueOrder, ctx);
412
+ if (!removed)
413
+ return false;
414
+ if (item.guestInlineMessageId && deps.dismissGuestPlaceholder) {
415
+ await deps.dismissGuestPlaceholder(item.guestInlineMessageId);
416
+ }
417
+ return true;
418
+ }
348
419
  function getQueueMenuReactionDisposition(item, priority, skipped) {
349
420
  if (priority && skipped) {
350
421
  return {
@@ -242,6 +242,11 @@ export declare function removeTelegramQueueItemsByMessageIds<TContext = unknown>
242
242
  removedItems: TelegramQueueItem<TContext>[];
243
243
  removedCount: number;
244
244
  };
245
+ export declare function removeTelegramQueuedGuestPromptByOrder<TContext = unknown>(items: TelegramQueueItem<TContext>[], queueOrder: number): {
246
+ items: TelegramQueueItem<TContext>[];
247
+ removedItems: PendingTelegramTurn[];
248
+ removedCount: number;
249
+ };
245
250
  export declare function applyTelegramQueuePromptReactionDisposition<TContext = unknown>(items: TelegramQueueItem<TContext>[], messageId: number, disposition: TelegramQueueReactionDisposition, destinationLaneOrder?: number, scope?: TelegramQueueMessageScope): {
246
251
  items: TelegramQueueItem<TContext>[];
247
252
  changed: boolean;
@@ -624,6 +629,7 @@ export interface TelegramQueueMutationController<TContext> {
624
629
  reorder: (ctx: TContext) => void;
625
630
  clear: (ctx: TContext) => number;
626
631
  removeByMessageIds: (messageIds: number[], ctx: TContext, scope?: TelegramQueueMessageScope) => number;
632
+ removeGuestPromptByQueueOrder?: (queueOrder: number, ctx: TContext) => boolean;
627
633
  applyReactionByMessageId: (messageId: number, disposition: TelegramQueueReactionDisposition, ctx: TContext, scope?: TelegramQueueMessageScope) => boolean;
628
634
  }
629
635
  export interface TelegramControlQueueControllerDeps<TContext> {
@@ -673,6 +679,7 @@ export declare function createTelegramQueueMutationController<TContext>(deps: Te
673
679
  export declare function reorderTelegramQueueItemsRuntime<TContext>(deps: TelegramQueueMutationRuntimeDeps<TContext>): void;
674
680
  export declare function clearTelegramQueueItemsRuntime<TContext>(deps: TelegramQueueMutationRuntimeDeps<TContext>): number;
675
681
  export declare function removeTelegramQueueItemsByMessageIdsRuntime<TContext>(messageIds: number[], deps: TelegramQueueMutationRuntimeDeps<TContext>, scope?: TelegramQueueMessageScope): number;
682
+ export declare function removeTelegramQueuedGuestPromptByOrderRuntime<TContext>(queueOrder: number, deps: TelegramQueueMutationRuntimeDeps<TContext>): boolean;
676
683
  export declare function applyTelegramQueuePromptReactionDispositionRuntime<TContext>(messageId: number, disposition: TelegramQueueReactionDisposition, deps: TelegramQueueMutationRuntimeDeps<TContext>, scope?: TelegramQueueMessageScope): boolean;
677
684
  export declare function enqueueTelegramPromptTurnRuntime<TMessage, TContext = unknown>(messages: TMessage[], deps: TelegramPromptEnqueueRuntimeDeps<TMessage, TContext>): Promise<PendingTelegramTurn>;
678
685
  export declare function createTelegramPromptEnqueueController<TMessage, TContext = unknown>(deps: TelegramPromptEnqueueControllerDeps<TMessage, TContext>): TelegramPromptEnqueueController<TMessage, TContext>;
@@ -491,6 +491,21 @@ export function removeTelegramQueueItemsByMessageIds(items, messageIds, scope) {
491
491
  removedCount: removedItems.length,
492
492
  };
493
493
  }
494
+ export function removeTelegramQueuedGuestPromptByOrder(items, queueOrder) {
495
+ const index = items.findIndex((item) => {
496
+ return isPendingTelegramTurn(item) &&
497
+ item.guestQueryId !== undefined &&
498
+ item.queueOrder === queueOrder;
499
+ });
500
+ if (index < 0)
501
+ return { items, removedItems: [], removedCount: 0 };
502
+ const removedItem = items[index];
503
+ return {
504
+ items: [...items.slice(0, index), ...items.slice(index + 1)],
505
+ removedItems: [removedItem],
506
+ removedCount: 1,
507
+ };
508
+ }
494
509
  export function applyTelegramQueuePromptReactionDisposition(items, messageId, disposition, destinationLaneOrder, scope) {
495
510
  let nextItems = items;
496
511
  for (const [index, item] of items.entries()) {
@@ -1440,6 +1455,7 @@ export function createTelegramQueueMutationController(deps) {
1440
1455
  reorder: (ctx) => reorderTelegramQueueItemsRuntime(buildRuntimeDeps(ctx)),
1441
1456
  clear: (ctx) => clearTelegramQueueItemsRuntime(buildRuntimeDeps(ctx)),
1442
1457
  removeByMessageIds: (messageIds, ctx, scope) => removeTelegramQueueItemsByMessageIdsRuntime(messageIds, buildRuntimeDeps(ctx), scope),
1458
+ removeGuestPromptByQueueOrder: (queueOrder, ctx) => removeTelegramQueuedGuestPromptByOrderRuntime(queueOrder, buildRuntimeDeps(ctx)),
1443
1459
  applyReactionByMessageId: (messageId, disposition, ctx, scope) => applyTelegramQueuePromptReactionDispositionRuntime(messageId, disposition, buildRuntimeDeps(ctx), scope),
1444
1460
  };
1445
1461
  }
@@ -1492,6 +1508,17 @@ export function removeTelegramQueueItemsByMessageIdsRuntime(messageIds, deps, sc
1492
1508
  updateTelegramQueueStatusRuntime(deps);
1493
1509
  return removedCount;
1494
1510
  }
1511
+ export function removeTelegramQueuedGuestPromptByOrderRuntime(queueOrder, deps) {
1512
+ const { items, removedItems, removedCount } = removeTelegramQueuedGuestPromptByOrder(deps.getQueuedItems(), queueOrder);
1513
+ if (removedCount === 0)
1514
+ return false;
1515
+ // A menu Skip means permanent discard, so settle durable admission before
1516
+ // removing the live item. A failed settlement keeps the prompt retryable.
1517
+ deps.onItemsDiscarded?.(removedItems, deps.ctx);
1518
+ deps.setQueuedItems(items);
1519
+ updateTelegramQueueStatusRuntime(deps);
1520
+ return true;
1521
+ }
1495
1522
  export function applyTelegramQueuePromptReactionDispositionRuntime(messageId, disposition, deps, scope) {
1496
1523
  const queuedItems = deps.getQueuedItems();
1497
1524
  const changesLane = queuedItems.some((item) => {
@@ -190,6 +190,7 @@ export declare const TELEGRAM_GUEST_PLACEHOLDER_FRAME_MS = 1000;
190
190
  export declare const TELEGRAM_GUEST_PLACEHOLDER_MIN_MS = 20000;
191
191
  export declare const TELEGRAM_GUEST_PLACEHOLDER_MAX_MS = 26000;
192
192
  export declare const TELEGRAM_GUEST_PLACEHOLDER_FRAMES: readonly ["<b>🌎 Working on it.</b>", "<b>🌍 Working on it.</b>", "<b>🌏 Working on it..</b>", "<b>🌎 Working on it..</b>", "<b>🌍 Working on it...</b>", "<b>🌏 Working on it...</b>"];
193
+ export declare const TELEGRAM_DISMISSED_GUEST_PLACEHOLDER_TEXT = "\u2063";
193
194
  export declare function buildTelegramGuestPlaceholderFrame(step: number): string;
194
195
  export interface TelegramGuestPlaceholderRuntimeDeps {
195
196
  editGuestInlineMessage: (inlineMessageId: string, content: {
@@ -211,5 +212,7 @@ export interface TelegramGuestPlaceholderRuntime {
211
212
  stop: (inlineMessageId: string) => Promise<void>;
212
213
  /** Cancels every loop without waiting for in-flight edits (session shutdown). */
213
214
  stopAll: () => void;
215
+ /** Stops rotation and visually clears the inline placeholder. */
216
+ dismiss: (inlineMessageId: string) => Promise<void>;
214
217
  }
215
218
  export declare function createTelegramGuestPlaceholderRuntime(deps: TelegramGuestPlaceholderRuntimeDeps): TelegramGuestPlaceholderRuntime;
@@ -612,6 +612,9 @@ export const TELEGRAM_GUEST_PLACEHOLDER_FRAMES = [
612
612
  "<b>🌍 Working on it...</b>",
613
613
  "<b>🌏 Working on it...</b>",
614
614
  ];
615
+ // Telegram does not expose deletion for inline messages. This non-printing,
616
+ // non-empty edit visually clears a guest placeholder after its query is skipped.
617
+ export const TELEGRAM_DISMISSED_GUEST_PLACEHOLDER_TEXT = "\u2063";
615
618
  export function buildTelegramGuestPlaceholderFrame(step) {
616
619
  const frames = TELEGRAM_GUEST_PLACEHOLDER_FRAMES;
617
620
  const index = ((step % frames.length) + frames.length) % frames.length;
@@ -734,5 +737,19 @@ export function createTelegramGuestPlaceholderRuntime(deps) {
734
737
  }
735
738
  sessions.clear();
736
739
  },
740
+ async dismiss(inlineMessageId) {
741
+ await stop(inlineMessageId);
742
+ try {
743
+ await deps.editGuestInlineMessage(inlineMessageId, {
744
+ text: TELEGRAM_DISMISSED_GUEST_PLACEHOLDER_TEXT,
745
+ parseMode: "HTML",
746
+ });
747
+ }
748
+ catch (error) {
749
+ deps.recordRuntimeEvent?.("guest", error, {
750
+ phase: "guest-placeholder-dismiss",
751
+ });
752
+ }
753
+ },
737
754
  };
738
755
  }
@@ -381,7 +381,14 @@ export async function ensureTelegramLeaderThreadBinding(deps) {
381
381
  assertLeaderEpoch("before-workspace-persist");
382
382
  await deps.topicTargetStore.persist();
383
383
  assertLeaderEpoch("after-workspace-persist");
384
- return { ...result, ...(committed.displayTitle ? { displayTitle: committed.displayTitle } : {}) };
384
+ const { target: _target, slot: _slot, threadName: _threadName, displayTitle: _displayTitle, ...rest } = result;
385
+ return {
386
+ ...rest,
387
+ target: { ...committed.target },
388
+ slot: committed.slot ?? result.slot,
389
+ ...(committed.threadName ? { threadName: committed.threadName } : {}),
390
+ ...(committed.displayTitle ? { displayTitle: committed.displayTitle } : {}),
391
+ };
385
392
  };
386
393
  try {
387
394
  const unavailableTargetKeys = new Set([
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.50.0",
3
+ "version": "0.50.1",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -480,6 +480,8 @@ Priority and Skip may coexist, including `👍 + 💩`. The prompt remains at it
480
480
 
481
481
  Menu and reaction controls share the same canonical queue state. A menu Keep can clear internal Skip without changing Priority or queue position, but Telegram's Bot API cannot remove a reaction created by the user; the visible user reaction can therefore remain until that user removes it. Once Pi has consumed or dropped a prompt, later reactions cannot retract or restore it.
482
482
 
483
+ Guest Mode is the deliberate exception to deferred Keep/Skip semantics. A guest prompt has no source message reaction surface and may share zero-valued chat/message placeholders with other guest prompts, so queue-menu callbacks address it by immutable `queueOrder` and expose an immediate `Skip`. The leader settles its durable admission, removes it while other agent work may continue, stops placeholder animation, and replaces the inline globe placeholder with non-printing content. Bot API inline messages have no delete method, so this visual clear is the supported deletion equivalent. The guest prompt never reaches a model turn.
484
+
483
485
  Admission and planning validate lane contracts. Invalid lane/kind pairings fail predictably instead of being silently coerced.
484
486
 
485
487
  Prompt preparation completes downloads, inbound handlers, and binary image reads before synchronous final assembly and queue commit. Enqueue captures only intended abort-history identities before preparation; afterward it reads the current queue, folds surviving selected prompts using their current text and receipts, allocates the new turn's order, and appends without another asynchronous boundary. The handed-off head remains in place until `agent_start` consumes it, even if preparation finishes first. Concurrent arrivals, removals, and lane/reaction edits remain intact; preparation failure or stale-generation completion leaves intervening queue changes untouched.
@@ -186,7 +186,7 @@ Status: locally implemented and validated for the next minor release; final comp
186
186
 
187
187
  A durable Workspace binding is owned by the selected bot profile plus `{ cwd, sessionId? }`. `cwd` is the normalized exact full path. When present, `sessionId` is the exact non-empty bounded value returned by the public `ctx.sessionManager.getSessionId()` API. Session display names, selector positions, session file paths, process instance IDs, and lifecycle generations are metadata or transient fences, never substitutes for the stable session ID. Absence is a real identity value `{ cwd, ∅ }`, not a wildcard; malformed present values still fail closed.
188
188
 
189
- The persisted binding stores the exact session ID and derives its new binding key from the existing collision-verified Workspace directory key plus a full SHA-256 digest of the session ID. Exact persisted values remain the collision check; the digest is an index component, not independent authority. Reopening, `/resume`, `/continue`/continue-recent, or process replacement with the same `{ cwd, sessionId }` reclaims the same retained target and profile-wide letter when current ownership, accepted-work, admission, and retirement evidence allow it. `/new` and `/fork` produce distinct session IDs and therefore distinct bindings even in the same directory. `/reload` changes neither component.
189
+ The persisted binding stores the exact session ID and derives its new binding key from the existing collision-verified Workspace directory key plus a full SHA-256 digest of the session ID. Exact persisted values remain the collision check; the digest is an index component, not independent authority. Reopening, `/resume`, `/continue`/continue-recent, or process replacement with the same `{ cwd, sessionId }` reclaims the same retained target and profile-wide letter when current ownership, accepted-work, admission, and retirement evidence allow it. When target deduplication resolves a provisional claim to an already retained binding, the committed binding's canonical target, slot, and generated name must drive the live record and reported result; provisional metadata must not consume a second letter. `/new` and `/fork` produce distinct session IDs and therefore distinct bindings even in the same directory. `/reload` changes neither component.
190
190
 
191
191
  Legacy cwd-only bindings remain exact compatibility identities whose absent session component is itself a unique key. They are neither wildcard session bindings nor adoption candidates: session-aware claims never stamp, migrate, consume, or restore them, and instead allocate their own `{ cwd, sessionId }` binding subject to the same global slot and protection rules. `{ cwd, ∅ }`, `{ cwd, session-a }`, and `{ cwd, session-b }` may coexist as majestic degradation for older state and peers. The 0.46 runtime reads strict legacy records as inert independent `{ cwd, ∅ }` bindings but never migrates, adopts, or routes a session through them. Protocol v2 is session-native and intentionally incompatible with protocol-v1 0.45.x peers. Upgrade requires stopping all Telegram instances first, updating them together, and then starting them again; the first v2 process becomes leader with its own session-qualified binding.
192
192
 
@@ -40,7 +40,7 @@ import {
40
40
  Stable commands inside Pi:
41
41
 
42
42
  - `/telegram-setup` — configure/update the bot token.
43
- - `/telegram-connect` — start polling here and acquire external Telegram control ownership. `/telegram-connect [profile] as=Name` assigns an optional unique capitalized Latin-word identity only when provisioning a fresh Workspace Thread. Accepted queue/reply state stays local if ownership later moves elsewhere. A successful command queues a hidden connection-state note for delivery with the agent's next turn without triggering one; it says Telegram is connected and that connectivity alone is not user intent.
43
+ - `/telegram-connect` — start polling here and acquire external Telegram control ownership, optionally for a named profile. Thread names are configured from Telegram rather than Pi commands. Accepted queue/reply state stays local if ownership later moves elsewhere. A successful command queues a hidden connection-state note for delivery with the agent's next turn without triggering one; it says Telegram is connected and that connectivity alone is not user intent.
44
44
  - `/telegram-disconnect` — after destructive confirmation, stop polling and release ownership without deleting or silencing accepted local queue state. A successful command queues the corresponding hidden, non-triggering disconnected context note; cancelled or failed disconnects do not publish a false state transition. In Threaded Mode it deletes this instance's current Telegram thread; a follower waits for its active leader to confirm generation-fenced cleanup before stopping. Graceful Pi `quit` performs the same teardown without prompting, while `reload`, `new`, `resume`, and `fork` preserve same-process handoff.
45
45
  - `/telegram-status` — show connection, polling, execution, queue, and recent event diagnostics; debug output separates poller and worker progress, durable automatic-retry state, exact foreign queued-owner identity, and negotiated protocol/build/capabilities.
46
46
 
@@ -563,10 +563,6 @@ interface TelegramCommandsAndToolsBindingDeps {
563
563
  stopPolling?: () => Promise<void | string>;
564
564
  recoverPollingStart?: Commands.TelegramBridgeCommandRegistrationDeps["recoverPollingStart"];
565
565
  getDisconnectThreadName?: () => string | undefined;
566
- setRequestedThreadNameForPollingStart?: (
567
- threadName: string | undefined,
568
- ) => void;
569
- validateThreadName?: Commands.TelegramBridgeCommandRegistrationDeps["validateThreadName"];
570
566
  onTransportChanged?: () => Promise<void> | void;
571
567
  getStatusLines: (
572
568
  options?: Status.TelegramBridgeStatusLineOptions,
@@ -615,8 +611,6 @@ export function registerTelegramCommandsAndTools({
615
611
  stopPolling,
616
612
  recoverPollingStart,
617
613
  getDisconnectThreadName,
618
- setRequestedThreadNameForPollingStart,
619
- validateThreadName,
620
614
  onTransportChanged,
621
615
  getStatusLines,
622
616
  buttonActionStore,
@@ -785,20 +779,16 @@ export function registerTelegramCommandsAndTools({
785
779
  hasBotToken: configStore.hasBotToken,
786
780
  getBotTokenDiagnostic: configStore.getBotTokenDiagnostic,
787
781
  startPolling: async (ctx, options) => {
788
- setRequestedThreadNameForPollingStart?.(options?.requestedThreadName);
789
782
  try {
790
783
  return await lockedPollingRuntime.start(ctx, options);
791
784
  } catch (error) {
792
785
  recordRuntimeEvent("recovery", error, { phase: "polling-start" });
793
786
  throw error;
794
- } finally {
795
- setRequestedThreadNameForPollingStart?.(undefined);
796
787
  }
797
788
  },
798
789
  stopPolling: stopPolling ?? lockedPollingRuntime.stop,
799
790
  recoverPollingStart,
800
791
  getDisconnectThreadName,
801
- validateThreadName,
802
792
  queueAgentConnectionContext,
803
793
  updateStatus,
804
794
  getProfileNames: () =>