@llblab/pi-telegram 0.48.2 → 0.49.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 (50) hide show
  1. package/AGENTS.md +1 -1
  2. package/BACKLOG.md +1 -0
  3. package/CHANGELOG.md +12 -0
  4. package/README.md +1 -0
  5. package/dist/lib/activity.d.ts +2 -0
  6. package/dist/lib/activity.js +12 -0
  7. package/dist/lib/bindings.d.ts +3 -2
  8. package/dist/lib/bindings.js +7 -2
  9. package/dist/lib/commands.d.ts +134 -2
  10. package/dist/lib/commands.js +297 -16
  11. package/dist/lib/delivery.d.ts +2 -0
  12. package/dist/lib/delivery.js +11 -0
  13. package/dist/lib/extension.js +68 -8
  14. package/dist/lib/menu-settings.d.ts +4 -3
  15. package/dist/lib/menu-settings.js +17 -13
  16. package/dist/lib/pi.d.ts +2 -1
  17. package/dist/lib/pi.js +1 -0
  18. package/dist/lib/queue.d.ts +2 -0
  19. package/dist/lib/queue.js +5 -1
  20. package/dist/lib/replies.d.ts +1 -0
  21. package/dist/lib/replies.js +1 -0
  22. package/dist/lib/routing.d.ts +1 -0
  23. package/dist/lib/routing.js +59 -7
  24. package/dist/lib/thread-display.d.ts +23 -0
  25. package/dist/lib/thread-display.js +20 -0
  26. package/dist/lib/threads.d.ts +18 -0
  27. package/dist/lib/threads.js +152 -6
  28. package/dist/lib/updates.d.ts +1 -0
  29. package/dist/lib/updates.js +5 -3
  30. package/dist/package.json +1 -1
  31. package/docs/architecture.md +3 -3
  32. package/docs/callback-namespaces.md +1 -1
  33. package/docs/public-api.md +4 -3
  34. package/docs/sections.md +2 -2
  35. package/docs/ui-style.md +2 -1
  36. package/docs/updates.md +1 -1
  37. package/lib/activity.ts +14 -0
  38. package/lib/bindings.ts +10 -2
  39. package/lib/commands.ts +466 -21
  40. package/lib/delivery.ts +15 -0
  41. package/lib/extension.ts +77 -9
  42. package/lib/menu-settings.ts +25 -10
  43. package/lib/pi.ts +3 -0
  44. package/lib/queue.ts +7 -1
  45. package/lib/replies.ts +2 -0
  46. package/lib/routing.ts +90 -19
  47. package/lib/thread-display.ts +30 -0
  48. package/lib/threads.ts +199 -6
  49. package/lib/updates.ts +5 -2
  50. package/package.json +1 -1
package/AGENTS.md CHANGED
@@ -121,7 +121,7 @@ The detailed map is canonical in [`docs/architecture.md`](./docs/architecture.md
121
121
  - Command templates remain compact and shell-free. Use string leaves or ordered `template` arrays; shell operators are not an execution contract. Examples use portable executable placeholders, never machine-local paths.
122
122
  - `telegram_attach` is the canonical file path and `telegram_message` the direct Markdown text/buttons path. Both require current direct or registered-follower authority and must not replace the normal active-turn reply.
123
123
  - Inbound handlers transform text/media before queueing; outbound handlers precede programmatic/provider fallbacks. Public contracts and ordering live in `docs/inbound.md`, `docs/outbound.md`, and `docs/public-api.md`.
124
- - Pi integration uses public hooks and APIs. A Telegram `/new` or equivalent session replacement requires a public Pi API that executes the real terminal path.
124
+ - Pi integration uses public hooks and APIs. Telegram `/new` is scheduled against the exact durable update, dispatched only after that update is removed from the journal, and then routed through an internal Pi command via `pi.sendUserMessage(..., { expandPromptTemplates: true })`; the command handler receives the real `ExtensionCommandContext` and calls `ctx.newSession()`. Before replacement, CAS-publish one exact expiring handoff in the profile target snapshot. `workspace-thread` successors re-key the matching Workspace binding; `classic-chat` successors preserve Profile/CWD/session/chat continuity without creating a binding or invoking topic APIs. Both atomically claim the intent before one terminal result, and the old `withSession` path never publishes the same success. Never replace the session while inbound authority is unsettled, store stale command contexts, accept an expired or mismatched handoff, inject terminal input, spawn a shadow Pi process, or mutate session files.
125
125
 
126
126
  ## 7. Engineering Conventions
127
127
 
package/BACKLOG.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  _This file owns unresolved project work only. Completed behavior belongs in `CHANGELOG.md`; durable contracts belong in `AGENTS.md` and `/docs`._
4
4
 
5
+ - [ ] `Unified Telegram /new continuity` (`local-actionable`, current priority; closes [#81](https://github.com/llblab/pi-telegram/issues/81)): The existing one-shot intent now has strict `workspace-thread | classic-chat` continuity. Classic preparation persists exact Profile/CWD/session/chat identity without requiring or fabricating a Workspace binding; its codec round-trip and claim path leave Workspace bindings untouched. Threaded preparation and successor re-key retain the existing slot/name path. Local implementation and docs are complete. Classic preparation-to-successor settlement passes with one terminal send and no Workspace mutation against both the same store instance and a freshly reopened process store; duplicate settlement sends nothing. Threaded re-key, mismatch, expiry, failed-claim, full-suite, invariants, and Domain DAG regressions pass under the discriminated codec. Final `dist` is rebuilt; Classic Settings also hides both Thread-specific controls rather than exposing only cleanup. Remaining: operator-authorized Classic and Threaded live smoke. Do not close or post to #81 before release authority. Both modes delete confirmation before replacement, CAS-claim before terminal send, and preserve at-most-once cross-process result semantics; do not close or post to #81 before release authority.
5
6
  - [ ] `Prompt enqueue hotfix` (`optional`, `operator-gated`): Optional nonblocking operator-authorized disposable-follower smoke: overlap delayed voice processing with turn completion and confirm one-time ordered consumption and truthful counts against the [queue contract](./docs/architecture.md#queue-and-dispatch-safety). Separately authorized supported recovery investigation remains open: prevention does not repair an already-wedged in-memory queue; establish the exact recovery path and preservation/discard consequences before mutation, otherwise report the blocker. No journal/ownership edits, replay of settled input, implicit queue clearing, or restart; live activation requires separate operator authorization.
6
7
  - [ ] `Channel multimedia posts` (`0.45.1`, live-acceptance-gated): `telegram_message` channel delivery accepts one local `.jpg`/`.jpeg`/`.png`/`.webp` photo or `.mp4` video, uploads it through the multipart transport as `sendPhoto`/`sendVideo` with `text` as the HTML caption, validates kind and size (photo ≤ 10 MiB, video ≤ 50 MiB) plus ≤ 1024 visible caption characters before issuance, and rejects unsupported types and albums instead of downgrading them to links. The channel-post journal binds kind/file name/byte size/SHA-256 and caption, so duplicate requests and lost acknowledgements never re-upload; media-post edits replace the caption through `editMessageCaption`, and Markdown spoilers render as `<tg-spoiler>`. Live image publication passed on `@llb_log`. Regressions cover confirmed publication, duplicate requests, lost ACK, pre-issuance rejection, caption edits, and reconnect replacement. Remaining: operator-authorized disposable-channel acceptance of rejected upload, duplicate request, and caption edit.
7
8
  - [ ] `Manual Thread naming` (`gated-but-preparable`, release priority): Local bot-owned `/name Name` and bare `/name` flows avoid model dispatch. One expiring exact-target input dialog immediately accepts the next valid name, always offers cancel, and offers **Reset to automatic** only while a manual override exists; duplicate/stale callbacks cannot repeat mutation. Durable manual override supersedes every automatic display mode, reset is leader/follower generation- and target-fenced, and Letters remains the default without rewriting recovery identity. Local review findings are remediated, including Bot-API-wait target-replacement regressions for leader/follower rename and reset. Remaining: disposable live acceptance for command-menu ordering, dialog, invalid input, duplicate callbacks, leader/follower rename and reset.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.49.0: Unified fresh-session continuity
8
+
9
+ - `/new`: Classic and Threaded Mode now share one confirmed fresh-session flow. The settled callback deletes its dialog and publishes an expiring exact-target intent; a same- or cross-process successor preserves the classic chat or re-keys the Thread/slot/name, atomically claims once, then sends one terminal result. Identity mismatches fail closed.
10
+ - `Command menus`: Both Telegram command menus now keep the primary sequence `/start`, `/compact`, `/new`, `/continue`, `/next`; the niche `/name` command remains available but moves out of menus into the Thread display Settings guidance.
11
+ - `Classic Settings`: Hides both Thread display and Thread cleanup controls when Threaded Mode is unavailable, rather than exposing only half of the Thread-specific surface.
12
+ - `Thread display`: A current Thread with a manual `/name` override now appears as `custom` on the Settings row and detail heading; choosing any automatic mode clears the current Thread's override, reapplies that projection, and selects it again. Successful interactive rename/reset now settles its source before asynchronously publishing the result, so immediate `/new` cannot replay the consumed name after replacement; cancellation reports only its completed result.
13
+
14
+ ## 0.48.3: Assistant publication identity hotfix
15
+
16
+ - `Thread restore display identity`: Restoring or reclaiming a Thread now renames the destination from its current Workspace display title when available, rather than leaking the internal baked slot name such as `Moss` into directory-title mode.
17
+ - `Assistant publication identity`: An empty terminal assistant message may recover an earlier preserved answer, but it no longer republishes that text when the same Telegram intermediate output was already admitted; the pre-send turn fence also prevents lost-acknowledgement retries while allowing later turns.
18
+
7
19
  ## 0.48.2: Follower promotion recovery
8
20
 
9
21
  - `Follower promotion recovery`: A failed follower-to-leader promotion, including unavailable Thread slot authority, is now contained as a retryable bus recovery event instead of escaping a detached heartbeat recovery promise and terminating Pi.
package/README.md CHANGED
@@ -164,6 +164,7 @@ Use these in the bot DM.
164
164
  | --- | --- |
165
165
  | `/start` | Pair when needed and open the main operator menu |
166
166
  | `/name [Name]` | Set a manual Thread title immediately, or open rename/reset controls when Name is omitted |
167
+ | `/new` | Start a new Pi session in the current classic chat or Thread after confirming the bridge is idle |
167
168
  | `/compact` | Confirm and run session compaction when safe |
168
169
  | `/next` | Dispatch the next queued turn, aborting first if needed |
169
170
  | `/continue` | Enqueue a priority continuation prompt |
@@ -201,7 +201,9 @@ export interface TelegramActivityPublicationRuntime {
201
201
  export declare function createTelegramActivityPublicationRuntime(): TelegramActivityPublicationRuntime;
202
202
  export interface TelegramAssistantOutputRuntime {
203
203
  start: () => void;
204
+ beginTurn: () => void;
204
205
  accept: (event: TelegramAssistantSegmentEvent) => void;
206
+ hasAdmittedTelegramIntermediate: (text: string) => boolean;
205
207
  waitForIdle: () => Promise<void>;
206
208
  stop: () => void;
207
209
  }
@@ -520,6 +520,7 @@ export function createTelegramAssistantOutputRuntime(deps) {
520
520
  let running = false;
521
521
  let tail = Promise.resolve();
522
522
  const admitted = new Set();
523
+ const admittedTelegramIntermediateText = new Set();
523
524
  const isEligibleEvent = (event) => (event.source === "telegram" && event.placement === "intermediate") ||
524
525
  event.source === "local" ||
525
526
  event.source === "autonomous" ||
@@ -529,8 +530,12 @@ export function createTelegramAssistantOutputRuntime(deps) {
529
530
  generation += 1;
530
531
  running = true;
531
532
  admitted.clear();
533
+ admittedTelegramIntermediateText.clear();
532
534
  tail = Promise.resolve();
533
535
  },
536
+ beginTurn() {
537
+ admittedTelegramIntermediateText.clear();
538
+ },
534
539
  accept(event) {
535
540
  if (!running || !isEligibleEvent(event) || !event.text.trim())
536
541
  return;
@@ -538,6 +543,9 @@ export function createTelegramAssistantOutputRuntime(deps) {
538
543
  if (admitted.has(key))
539
544
  return;
540
545
  admitted.add(key);
546
+ if (event.source === "telegram" && event.placement === "intermediate") {
547
+ admittedTelegramIntermediateText.add(event.text.trim());
548
+ }
541
549
  const admittedGeneration = generation;
542
550
  const admittedAuthority = deps.captureAuthority?.();
543
551
  const preparation = deps.prepareSend?.(event);
@@ -562,6 +570,9 @@ export function createTelegramAssistantOutputRuntime(deps) {
562
570
  }
563
571
  }).finally(() => preparation?.settle());
564
572
  },
573
+ hasAdmittedTelegramIntermediate(text) {
574
+ return admittedTelegramIntermediateText.has(text.trim());
575
+ },
565
576
  waitForIdle() {
566
577
  return tail;
567
578
  },
@@ -569,6 +580,7 @@ export function createTelegramAssistantOutputRuntime(deps) {
569
580
  generation += 1;
570
581
  running = false;
571
582
  admitted.clear();
583
+ admittedTelegramIntermediateText.clear();
572
584
  },
573
585
  };
574
586
  }
@@ -202,7 +202,7 @@ interface TelegramLifecycleBindingDeps {
202
202
  publicationRuntime: TelegramBridgePublicationRuntime;
203
203
  activityRuntime: Activity.TelegramActivityRuntime;
204
204
  activityVerbosityRuntime?: ActivityVerbosity.TelegramActivityVerbosityRuntime;
205
- assistantOutputRuntime: Pick<Activity.TelegramAssistantOutputRuntime, "start" | "waitForIdle" | "stop">;
205
+ assistantOutputRuntime: Pick<Activity.TelegramAssistantOutputRuntime, "start" | "beginTurn" | "hasAdmittedTelegramIntermediate" | "waitForIdle" | "stop">;
206
206
  sessionLifecycleRuntime: Pick<Lifecycle.TelegramLifecycleRegistrationDeps, "onSessionStart" | "onSessionShutdown" | "onModelSelect">;
207
207
  configStore: Pick<Config.TelegramConfigStore, "get" | "getOutboundHandlers" | "hasBotToken" | "load">;
208
208
  abort: Runtime.TelegramRuntimeAbortPort;
@@ -216,6 +216,7 @@ interface TelegramLifecycleBindingDeps {
216
216
  deferredQueueDispatchRuntime: Queue.TelegramDeferredQueueDispatchRuntime<Pi.ExtensionContext>;
217
217
  modelContextAvailabilityRuntime: Prompts.TelegramModelContextAvailabilityRuntime;
218
218
  disconnectOnQuit?: () => Promise<unknown>;
219
+ onSessionStarted?: (event: Pi.SessionStartEvent, ctx: Pi.ExtensionContext) => void;
219
220
  shutdownGenerativeAppLiveSurfaces?: () => void;
220
221
  resolveAutomaticThreadCleanupEnabled?: () => boolean | Promise<boolean>;
221
222
  buttonActionStore: OutboundHandlers.TelegramButtonActionStore;
@@ -246,5 +247,5 @@ interface TelegramLifecycleBindingDeps {
246
247
  updateStatus: TelegramBridgeStatusUpdater;
247
248
  recordRuntimeEvent: TelegramRuntimeEventRecorder;
248
249
  }
249
- export declare function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime, activityRuntime, activityVerbosityRuntime, assistantOutputRuntime, sessionLifecycleRuntime, configStore, abort, typing, lifecycle, activeTurnRuntime, telegramQueueStore, modelSwitchController, previewRuntime, promptDispatchRuntime, deferredQueueDispatchRuntime, modelContextAvailabilityRuntime, disconnectOnQuit, shutdownGenerativeAppLiveSurfaces, resolveAutomaticThreadCleanupEnabled, buttonActionStore, callMultipart, sendChatAction, sendRecordVoiceAction, sendMarkdownReply, sendTextReply, dispatchNextQueuedTelegramTurn, onPromptHandedOff, answerGuestQuery, deleteMessage, sendGuestReply, editGuestReply, stopGuestPlaceholder, preparePreviewDelivery, finalizeMarkdownPreview, proactivePushTargetGetter, getAssistantRenderingMode, recordMessageOwnership, canSendAgentActivity, isSessionContextActive, isTurnTransportActive, updateStatus, recordRuntimeEvent, }: TelegramLifecycleBindingDeps): void;
250
+ export declare function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime, activityRuntime, activityVerbosityRuntime, assistantOutputRuntime, sessionLifecycleRuntime, configStore, abort, typing, lifecycle, activeTurnRuntime, telegramQueueStore, modelSwitchController, previewRuntime, promptDispatchRuntime, deferredQueueDispatchRuntime, modelContextAvailabilityRuntime, disconnectOnQuit, onSessionStarted, shutdownGenerativeAppLiveSurfaces, resolveAutomaticThreadCleanupEnabled, buttonActionStore, callMultipart, sendChatAction, sendRecordVoiceAction, sendMarkdownReply, sendTextReply, dispatchNextQueuedTelegramTurn, onPromptHandedOff, answerGuestQuery, deleteMessage, sendGuestReply, editGuestReply, stopGuestPlaceholder, preparePreviewDelivery, finalizeMarkdownPreview, proactivePushTargetGetter, getAssistantRenderingMode, recordMessageOwnership, canSendAgentActivity, isSessionContextActive, isTurnTransportActive, updateStatus, recordRuntimeEvent, }: TelegramLifecycleBindingDeps): void;
250
251
  export {};
@@ -499,7 +499,7 @@ export function registerTelegramCommandsAndTools({ pi, agentDir, configStore, pe
499
499
  },
500
500
  });
501
501
  }
502
- export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime, activityRuntime, activityVerbosityRuntime, assistantOutputRuntime, sessionLifecycleRuntime, configStore, abort, typing, lifecycle, activeTurnRuntime, telegramQueueStore, modelSwitchController, previewRuntime, promptDispatchRuntime, deferredQueueDispatchRuntime, modelContextAvailabilityRuntime, disconnectOnQuit, shutdownGenerativeAppLiveSurfaces, resolveAutomaticThreadCleanupEnabled, buttonActionStore, callMultipart, sendChatAction, sendRecordVoiceAction, sendMarkdownReply, sendTextReply, dispatchNextQueuedTelegramTurn, onPromptHandedOff, answerGuestQuery, deleteMessage, sendGuestReply, editGuestReply, stopGuestPlaceholder, preparePreviewDelivery, finalizeMarkdownPreview, proactivePushTargetGetter, getAssistantRenderingMode, recordMessageOwnership, canSendAgentActivity, isSessionContextActive = () => true, isTurnTransportActive, updateStatus, recordRuntimeEvent, }) {
502
+ export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime, activityRuntime, activityVerbosityRuntime, assistantOutputRuntime, sessionLifecycleRuntime, configStore, abort, typing, lifecycle, activeTurnRuntime, telegramQueueStore, modelSwitchController, previewRuntime, promptDispatchRuntime, deferredQueueDispatchRuntime, modelContextAvailabilityRuntime, disconnectOnQuit, onSessionStarted, shutdownGenerativeAppLiveSurfaces, resolveAutomaticThreadCleanupEnabled, buttonActionStore, callMultipart, sendChatAction, sendRecordVoiceAction, sendMarkdownReply, sendTextReply, dispatchNextQueuedTelegramTurn, onPromptHandedOff, answerGuestQuery, deleteMessage, sendGuestReply, editGuestReply, stopGuestPlaceholder, preparePreviewDelivery, finalizeMarkdownPreview, proactivePushTargetGetter, getAssistantRenderingMode, recordMessageOwnership, canSendAgentActivity, isSessionContextActive = () => true, isTurnTransportActive, updateStatus, recordRuntimeEvent, }) {
503
503
  const agentEndResetter = Runtime.createTelegramAgentEndResetter({
504
504
  abort,
505
505
  typing,
@@ -615,7 +615,10 @@ export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime,
615
615
  clearDispatchPending: lifecycle.clearDispatchPending,
616
616
  setFoldQueuedPromptsIntoHistory: lifecycle.setFoldQueuedPromptsIntoHistory,
617
617
  setActiveTurn: activeTurnRuntime.set,
618
- onPromptHandedOff,
618
+ onPromptHandedOff: (turn, ctx) => {
619
+ assistantOutputRuntime.beginTurn();
620
+ onPromptHandedOff?.(turn, ctx);
621
+ },
619
622
  createPreviewState: previewRuntime.resetState,
620
623
  startTypingLoop: (ctx) => {
621
624
  const turn = activeTurnRuntime.get();
@@ -627,6 +630,7 @@ export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime,
627
630
  getActiveTurn: activeTurnRuntime.get,
628
631
  loadConfig: configStore.load,
629
632
  extractAssistant: Replies.extractRunAssistantMessage,
633
+ isRecoveredAssistantAlreadyPublished: (assistant) => !!assistant.text && assistantOutputRuntime.hasAdmittedTelegramIntermediate(assistant.text),
630
634
  getFoldQueuedPromptsIntoHistory: lifecycle.shouldFoldQueuedPromptsIntoHistory,
631
635
  resetRuntimeState: agentEndResetter,
632
636
  isSessionActive: isSessionContextActive,
@@ -751,6 +755,7 @@ export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime,
751
755
  activityVerbosityRuntime?.reset();
752
756
  modelContextAvailabilityRuntime.reconcile();
753
757
  await sessionLifecycleRuntime.onSessionStart(event, ctx);
758
+ onSessionStarted?.(event, ctx);
754
759
  },
755
760
  async onSessionShutdown(event, ctx) {
756
761
  if (!isSessionContextActive(ctx))
@@ -1,11 +1,13 @@
1
1
  /**
2
2
  * Telegram command routing helpers
3
3
  * Zones: telegram controls, pi agent commands, queue controls
4
- * Owns Telegram slash-command normalization, bot command metadata, and pi-side command registration behind runtime ports
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
6
  import { type TelegramConfigStore } from "./config.ts";
7
+ import type * as Pi from "./pi.ts";
7
8
  import type { ExtensionAPI, ExtensionCommandContext } from "./pi.ts";
8
9
  import type { TelegramBridgeStatusLineOptions } from "./status.ts";
10
+ import type { TelegramSessionReplacementIntent } from "./threads.ts";
9
11
  import { type PendingTelegramControlItem, type TelegramQueueAdmissionReceipt } from "./queue.ts";
10
12
  export interface ParsedTelegramCommand {
11
13
  name: string;
@@ -60,6 +62,7 @@ export declare const TELEGRAM_COMMAND_EMOJI: {
60
62
  readonly abort: "⏹️";
61
63
  readonly stop: "🟥";
62
64
  readonly name: "🏷️";
65
+ readonly new: "🆕";
63
66
  };
64
67
  export type TelegramCommandEmojiName = keyof typeof TELEGRAM_COMMAND_EMOJI;
65
68
  export declare function getTelegramCommandEmoji(command: TelegramCommandEmojiName): string;
@@ -144,7 +147,7 @@ export declare function createTelegramThreadDisplayNameRenameBinding(): {
144
147
  };
145
148
  export declare function parseTelegramRequestedThreadName(args: string): string | undefined;
146
149
  export declare function registerTelegramBridgeCommands(pi: ExtensionAPI, deps: TelegramBridgeCommandRegistrationDeps): void;
147
- export declare const TELEGRAM_RESERVED_COMMAND_NAMES: readonly ["stop", "name", "abort", "next", "continue", "status", "queue", "compact", "model", "thinking", "settings", "help", "start"];
150
+ export declare const TELEGRAM_RESERVED_COMMAND_NAMES: readonly ["stop", "name", "new", "abort", "next", "continue", "status", "queue", "compact", "model", "thinking", "settings", "help", "start"];
148
151
  export type TelegramReservedCommandName = (typeof TELEGRAM_RESERVED_COMMAND_NAMES)[number];
149
152
  export declare function isTelegramReservedCommandName(commandName: string | undefined): commandName is TelegramReservedCommandName;
150
153
  export type TelegramCommandAction = {
@@ -156,6 +159,9 @@ export type TelegramCommandAction = {
156
159
  } | {
157
160
  kind: "name";
158
161
  executionMode: "immediate";
162
+ } | {
163
+ kind: "new";
164
+ executionMode: "immediate";
159
165
  } | {
160
166
  kind: "abort";
161
167
  executionMode: "immediate";
@@ -192,6 +198,7 @@ export type TelegramCommandExecutionMode = "ignored" | "immediate";
192
198
  export interface TelegramCommandActionDeps<TMessage, TContext> {
193
199
  handleStop: (message: TMessage, ctx: TContext) => Promise<void>;
194
200
  handleName: (message: TMessage, ctx: TContext, name: string) => Promise<void>;
201
+ handleNew: (message: TMessage, ctx: TContext) => Promise<void>;
195
202
  handleAbort: (message: TMessage, ctx: TContext) => Promise<void>;
196
203
  handleNext: (message: TMessage, ctx: TContext) => Promise<void>;
197
204
  handleContinue: (message: TMessage, ctx: TContext) => Promise<void>;
@@ -264,6 +271,13 @@ export interface TelegramCompactConfirmationCallbackQuery {
264
271
  message_thread_id?: number;
265
272
  };
266
273
  }
274
+ export interface TelegramNewConfirmationCallbackDeps<TContext> {
275
+ ctx: TContext;
276
+ answerCallbackQuery: (callbackQueryId: string, text?: string) => Promise<void>;
277
+ editInteractiveMessage: (chatId: number, messageId: number, text: string, mode: "markdown" | "html" | "plain", replyMarkup: TelegramCompactConfirmationReplyMarkup) => Promise<void>;
278
+ deleteMessage: (chatId: number, messageId: number) => Promise<void>;
279
+ runNew: (ctx: TContext) => Promise<void>;
280
+ }
267
281
  export interface TelegramCompactConfirmationCallbackDeps<TContext> {
268
282
  ctx: TContext;
269
283
  answerCallbackQuery: (callbackQueryId: string, text?: string) => Promise<void>;
@@ -390,6 +404,7 @@ export interface TelegramCommandRuntimeDeps<TMessage extends TelegramCommandRunt
390
404
  }) => void;
391
405
  stopTypingLoop?: () => void;
392
406
  enqueueContinueTurn: (message: TMessage, ctx: TContext) => Promise<void>;
407
+ requestNewSession?: (message: TMessage) => void;
393
408
  compact: (ctx: TContext, callbacks: {
394
409
  onComplete: () => void;
395
410
  onError: (error: unknown) => void;
@@ -434,6 +449,10 @@ export declare const TELEGRAM_COMMAND_ACTIONS: {
434
449
  readonly kind: "name";
435
450
  readonly executionMode: "immediate";
436
451
  };
452
+ readonly new: {
453
+ readonly kind: "new";
454
+ readonly executionMode: "immediate";
455
+ };
437
456
  readonly abort: {
438
457
  readonly kind: "abort";
439
458
  readonly executionMode: "immediate";
@@ -515,10 +534,27 @@ export declare function handleTelegramNextCommand(deps: {
515
534
  export declare function handleTelegramContinueCommand<TMessage, TContext>(message: TMessage, ctx: TContext, deps: {
516
535
  enqueueContinueTurn: (message: TMessage, ctx: TContext) => Promise<void>;
517
536
  }): Promise<void>;
537
+ export declare function buildTelegramNewConfirmationReplyMarkup(): TelegramCompactConfirmationReplyMarkup;
538
+ export declare function getTelegramNewConfirmationHtml(): string;
539
+ export declare function openTelegramNewConfirmation(target: TelegramCommandMessageTarget, deps: TelegramCompactConfirmationDeps): Promise<void>;
540
+ export declare function handleTelegramNewConfirmationCallback<TContext>(query: TelegramCompactConfirmationCallbackQuery, deps: TelegramNewConfirmationCallbackDeps<TContext>): Promise<boolean>;
518
541
  export declare function buildTelegramCompactConfirmationReplyMarkup(): TelegramCompactConfirmationReplyMarkup;
519
542
  export declare function getTelegramCompactConfirmationHtml(): string;
520
543
  export declare function openTelegramCompactConfirmation(target: TelegramCommandMessageTarget, deps: TelegramCompactConfirmationDeps): Promise<void>;
521
544
  export declare function handleTelegramCompactConfirmationCallback<TContext>(query: TelegramCompactConfirmationCallbackQuery, deps: TelegramCompactConfirmationCallbackDeps<TContext>): Promise<boolean>;
545
+ export interface TelegramNewCommandDeps extends TelegramRuntimeEventRecorderPort {
546
+ isIdle: () => boolean;
547
+ hasPendingMessages: () => boolean;
548
+ hasActiveTelegramTurn: () => boolean;
549
+ hasDispatchPending: () => boolean;
550
+ hasQueuedTelegramItems: () => boolean;
551
+ isCompactionInProgress: () => boolean;
552
+ requestNewSession?: () => void;
553
+ sendTextReply: (text: string, options?: {
554
+ parseMode?: "HTML";
555
+ }) => Promise<void>;
556
+ }
557
+ export declare function handleTelegramNewCommand(deps: TelegramNewCommandDeps): Promise<void>;
522
558
  export declare function handleTelegramCompactCommand(deps: TelegramCompactCommandDeps): Promise<void>;
523
559
  export declare function handleTelegramStatusCommand<TContext>(deps: {
524
560
  ctx: TContext;
@@ -538,4 +574,100 @@ export declare function createTelegramCommandHandler<TMessage extends TelegramCo
538
574
  export declare function createTelegramCommandOrPromptRuntime<TMessage, TContext>(deps: TelegramCommandOrPromptRuntimeDeps<TMessage, TContext>): {
539
575
  dispatchMessages: (messages: TMessage[], ctx: TContext) => Promise<void>;
540
576
  };
577
+ export declare const TELEGRAM_SESSION_ACTION_COMMAND_NAME = "telegram-session-action";
578
+ export declare const TELEGRAM_SESSION_ACTION_COMMAND_DESCRIPTION = "(internal) replace the current Pi session after Telegram settlement";
579
+ export declare function delayTelegramSessionAction(delayMs: number): Promise<void>;
580
+ export interface TelegramSessionActionRuntimeDeps {
581
+ registerCommand: Pi.ExtensionAPI["registerCommand"];
582
+ sendUserMessage: Pi.ExtensionAPI["sendUserMessage"];
583
+ notifyResult: (target: {
584
+ chatId: number;
585
+ threadId?: number;
586
+ messageId: number;
587
+ }, result: "success" | "cancelled" | "failure") => Promise<void>;
588
+ prepareReplacement?: (ctx: Pi.ExtensionCommandContext, updateId: number, target: {
589
+ chatId: number;
590
+ threadId?: number;
591
+ messageId: number;
592
+ }) => Promise<void>;
593
+ recordRuntimeEvent?: (category: string, error: unknown) => void;
594
+ }
595
+ export interface TelegramSessionReplacementSettlementDeps {
596
+ getIntent: () => Promise<TelegramSessionReplacementIntent | undefined>;
597
+ hasSuccessorContinuity: (intent: TelegramSessionReplacementIntent) => boolean;
598
+ editSuccess: (intent: TelegramSessionReplacementIntent) => Promise<{
599
+ ok: boolean;
600
+ retryable?: boolean;
601
+ message?: string;
602
+ }>;
603
+ clearIntent: (intent: TelegramSessionReplacementIntent) => Promise<boolean>;
604
+ profileName: string | undefined;
605
+ cwd: string;
606
+ sessionId: string;
607
+ now?: () => number;
608
+ sleep?: (delayMs: number) => Promise<void>;
609
+ isCurrent?: () => boolean;
610
+ }
611
+ export declare function settleTelegramSessionReplacement(deps: TelegramSessionReplacementSettlementDeps): Promise<"none" | "settled" | "expired" | "failed" | "stale">;
612
+ export declare function createTelegramSessionReplacementSettlementRuntime<TContext>(deps: {
613
+ resolve: (ctx: TContext) => TelegramSessionReplacementSettlementDeps | undefined;
614
+ onResult?: (result: "none" | "settled" | "expired" | "failed" | "stale") => void;
615
+ onError?: (error: unknown) => void;
616
+ }): {
617
+ onSessionStart: (ctx: TContext) => void;
618
+ };
619
+ export interface TelegramSessionActionAssemblyDeps {
620
+ registerCommand: Pi.ExtensionAPI["registerCommand"];
621
+ sendUserMessage: Pi.ExtensionAPI["sendUserMessage"];
622
+ store: {
623
+ load: () => Promise<void>;
624
+ refresh?: () => Promise<void>;
625
+ getWorkspaceBindingByTarget: (target: {
626
+ chatId: number;
627
+ threadId?: number;
628
+ }, sessionId?: string) => {
629
+ cwd: string;
630
+ sessionId?: string;
631
+ slot?: string;
632
+ threadName?: string;
633
+ manualThreadName?: string;
634
+ target: {
635
+ chatId: number;
636
+ threadId: number;
637
+ };
638
+ } | undefined;
639
+ getSessionReplacementIntent: () => TelegramSessionReplacementIntent | undefined;
640
+ commitSessionReplacementIntent: (intent: TelegramSessionReplacementIntent, isCurrent: () => boolean) => Promise<boolean>;
641
+ removeSessionReplacementIntent: (intent: TelegramSessionReplacementIntent, isCurrent: () => boolean) => Promise<boolean>;
642
+ };
643
+ getProfileName: () => string | undefined;
644
+ ownsPersistence: () => boolean;
645
+ sendResult: (target: {
646
+ chatId: number;
647
+ threadId?: number;
648
+ }, html: string) => Promise<{
649
+ ok: boolean;
650
+ retryable?: boolean;
651
+ }>;
652
+ handoffTtlMs: number;
653
+ now?: () => number;
654
+ recordRuntimeEvent?: (category: string, error: unknown) => void;
655
+ }
656
+ export declare function createTelegramSessionActionAssembly(deps: TelegramSessionActionAssemblyDeps): {
657
+ action: TelegramSessionActionRuntime;
658
+ settlement: {
659
+ onSessionStart: (ctx: Pi.ExtensionContext) => void;
660
+ };
661
+ };
662
+ export interface TelegramSessionActionRuntime {
663
+ register: () => void;
664
+ scheduleAfterUpdate: (updateId: number, target: {
665
+ chatId: number;
666
+ threadId?: number;
667
+ messageId: number;
668
+ }) => boolean;
669
+ onUpdateCompleted: (updateId: number) => void;
670
+ hasPending: () => boolean;
671
+ }
672
+ export declare function createTelegramSessionActionRuntime(deps: TelegramSessionActionRuntimeDeps): TelegramSessionActionRuntime;
541
673
  export {};