@llblab/pi-kit 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +4 -4
  3. package/node_modules/@llblab/pi-actors/AGENTS.md +1 -1
  4. package/node_modules/@llblab/pi-actors/CHANGELOG.md +6 -0
  5. package/node_modules/@llblab/pi-actors/README.md +1 -1
  6. package/node_modules/@llblab/pi-actors/dist/lib/async-runs.d.ts +3 -0
  7. package/node_modules/@llblab/pi-actors/dist/lib/async-runs.js +14 -1
  8. package/node_modules/@llblab/pi-actors/dist/lib/command-templates.js +45 -3
  9. package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.js +1 -1
  10. package/node_modules/@llblab/pi-actors/dist/lib/observability.d.ts +16 -3
  11. package/node_modules/@llblab/pi-actors/dist/lib/observability.js +92 -7
  12. package/node_modules/@llblab/pi-actors/dist/lib/pi.d.ts +0 -1
  13. package/node_modules/@llblab/pi-actors/dist/lib/pi.js +15 -24
  14. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.d.ts +17 -0
  15. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery-lineage.js +44 -0
  16. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.d.ts +4 -0
  17. package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.js +102 -4
  18. package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.js +58 -39
  19. package/node_modules/@llblab/pi-actors/dist/lib/runtime.js +14 -6
  20. package/node_modules/@llblab/pi-actors/dist/skills/actors/SKILL.md +1 -1
  21. package/node_modules/@llblab/pi-actors/docs/async-runs.md +1 -1
  22. package/node_modules/@llblab/pi-actors/docs/coordinator-delivery.md +18 -23
  23. package/node_modules/@llblab/pi-actors/lib/async-runs.ts +18 -1
  24. package/node_modules/@llblab/pi-actors/lib/command-templates.ts +41 -3
  25. package/node_modules/@llblab/pi-actors/lib/extension-runtime.ts +1 -1
  26. package/node_modules/@llblab/pi-actors/lib/observability.ts +119 -5
  27. package/node_modules/@llblab/pi-actors/lib/pi.ts +15 -28
  28. package/node_modules/@llblab/pi-actors/lib/run-delivery-lineage.ts +68 -0
  29. package/node_modules/@llblab/pi-actors/lib/run-delivery.ts +120 -4
  30. package/node_modules/@llblab/pi-actors/lib/run-ui-runtime.ts +69 -44
  31. package/node_modules/@llblab/pi-actors/lib/runtime.ts +17 -6
  32. package/node_modules/@llblab/pi-actors/package.json +1 -1
  33. package/node_modules/@llblab/pi-actors/skills/actors/SKILL.md +1 -1
  34. package/node_modules/@llblab/pi-state-flow/AGENTS.md +33 -12
  35. package/node_modules/@llblab/pi-state-flow/BACKLOG.md +125 -2
  36. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +87 -45
  37. package/node_modules/@llblab/pi-state-flow/README.md +203 -107
  38. package/node_modules/@llblab/pi-state-flow/docs/README.md +4 -0
  39. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +188 -0
  40. package/node_modules/@llblab/pi-state-flow/docs/temporal-acceptance.md +42 -0
  41. package/node_modules/@llblab/pi-state-flow/index.ts +164 -2
  42. package/node_modules/@llblab/pi-state-flow/lib/acquisition.ts +138 -0
  43. package/node_modules/@llblab/pi-state-flow/lib/artifact.ts +273 -0
  44. package/node_modules/@llblab/pi-state-flow/lib/config.ts +48 -0
  45. package/node_modules/@llblab/pi-state-flow/lib/context.ts +18 -5
  46. package/node_modules/@llblab/pi-state-flow/lib/continuation.ts +268 -0
  47. package/node_modules/@llblab/pi-state-flow/lib/discovery.ts +117 -0
  48. package/node_modules/@llblab/pi-state-flow/lib/durable.ts +562 -0
  49. package/node_modules/@llblab/pi-state-flow/lib/episode.ts +24 -12
  50. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +606 -70
  51. package/node_modules/@llblab/pi-state-flow/lib/git.ts +666 -0
  52. package/node_modules/@llblab/pi-state-flow/lib/history.ts +95 -0
  53. package/node_modules/@llblab/pi-state-flow/lib/json.ts +24 -0
  54. package/node_modules/@llblab/pi-state-flow/lib/maintenance.ts +141 -0
  55. package/node_modules/@llblab/pi-state-flow/lib/memory.ts +52 -0
  56. package/node_modules/@llblab/pi-state-flow/lib/migration.ts +88 -0
  57. package/node_modules/@llblab/pi-state-flow/lib/publication.ts +296 -0
  58. package/node_modules/@llblab/pi-state-flow/lib/recovery.ts +23 -7
  59. package/node_modules/@llblab/pi-state-flow/lib/rehydration.ts +79 -0
  60. package/node_modules/@llblab/pi-state-flow/lib/runtime.ts +264 -0
  61. package/node_modules/@llblab/pi-state-flow/lib/session.ts +6 -0
  62. package/node_modules/@llblab/pi-state-flow/lib/skills.ts +99 -7
  63. package/node_modules/@llblab/pi-state-flow/lib/snapshot.ts +305 -48
  64. package/node_modules/@llblab/pi-state-flow/lib/state.ts +74 -7
  65. package/node_modules/@llblab/pi-state-flow/lib/status.ts +125 -6
  66. package/node_modules/@llblab/pi-state-flow/lib/storage.ts +196 -0
  67. package/node_modules/@llblab/pi-state-flow/lib/temporal.ts +233 -0
  68. package/node_modules/@llblab/pi-state-flow/lib/terminal.ts +70 -24
  69. package/node_modules/@llblab/pi-state-flow/lib/transition.ts +254 -29
  70. package/node_modules/@llblab/pi-state-flow/package.json +8 -2
  71. package/node_modules/@llblab/pi-state-flow/skills/state-flow-memory/SKILL.md +128 -0
  72. package/node_modules/@llblab/pi-telegram/AGENTS.md +14 -9
  73. package/node_modules/@llblab/pi-telegram/BACKLOG.md +23 -5
  74. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +18 -0
  75. package/node_modules/@llblab/pi-telegram/README.md +13 -9
  76. package/node_modules/@llblab/pi-telegram/docs/README.md +1 -0
  77. package/node_modules/@llblab/pi-telegram/docs/architecture.md +227 -23
  78. package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +1 -1
  79. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +70 -19
  80. package/node_modules/@llblab/pi-telegram/docs/outbound.md +6 -6
  81. package/node_modules/@llblab/pi-telegram/docs/public-api.md +13 -6
  82. package/node_modules/@llblab/pi-telegram/docs/ui-style.md +3 -1
  83. package/node_modules/@llblab/pi-telegram/index.ts +4 -1418
  84. package/node_modules/@llblab/pi-telegram/lib/agent-messages.ts +6 -3
  85. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +46 -1
  86. package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +32 -19
  87. package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +600 -135
  88. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +962 -55
  89. package/node_modules/@llblab/pi-telegram/lib/bus.ts +355 -26
  90. package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +718 -0
  91. package/node_modules/@llblab/pi-telegram/lib/commands.ts +237 -11
  92. package/node_modules/@llblab/pi-telegram/lib/config.ts +242 -26
  93. package/node_modules/@llblab/pi-telegram/lib/extension.ts +1851 -0
  94. package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +20 -2
  95. package/node_modules/@llblab/pi-telegram/lib/journal.ts +2184 -126
  96. package/node_modules/@llblab/pi-telegram/lib/locks.ts +44 -2
  97. package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +152 -13
  98. package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +97 -10
  99. package/node_modules/@llblab/pi-telegram/lib/paths.ts +29 -0
  100. package/node_modules/@llblab/pi-telegram/lib/polling.ts +85 -17
  101. package/node_modules/@llblab/pi-telegram/lib/preview.ts +17 -0
  102. package/node_modules/@llblab/pi-telegram/lib/prompts.ts +6 -2
  103. package/node_modules/@llblab/pi-telegram/lib/queue.ts +118 -26
  104. package/node_modules/@llblab/pi-telegram/lib/rendering.ts +4 -1
  105. package/node_modules/@llblab/pi-telegram/lib/replies.ts +21 -2
  106. package/node_modules/@llblab/pi-telegram/lib/routing.ts +344 -112
  107. package/node_modules/@llblab/pi-telegram/lib/setup.ts +44 -4
  108. package/node_modules/@llblab/pi-telegram/lib/status.ts +51 -4
  109. package/node_modules/@llblab/pi-telegram/lib/sync.ts +308 -39
  110. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +353 -22
  111. package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +664 -0
  112. package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +226 -0
  113. package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +118 -0
  114. package/node_modules/@llblab/pi-telegram/lib/threads.ts +1686 -129
  115. package/node_modules/@llblab/pi-telegram/lib/turns.ts +7 -0
  116. package/node_modules/@llblab/pi-telegram/lib/updates.ts +1319 -97
  117. package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +1643 -0
  118. package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +968 -0
  119. package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +84 -0
  120. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  121. package/node_modules/@llblab/pi-telegram/screenshot.png +0 -0
  122. package/node_modules/@llblab/pi-telegram/scripts/measure-bus.mjs +83 -0
  123. package/node_modules/@llblab/pi-telegram/scripts/measure-workspace.mjs +101 -0
  124. package/node_modules/@llblab/{skills → pi-telegram/skills}/show-me/SKILL.md +28 -6
  125. package/node_modules/@llblab/pi-telegram/skills/show-me/references/telegram-surfaces.md +43 -0
  126. package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/delivery-and-threads.md +1 -1
  127. package/node_modules/@llblab/skills/package.json +2 -3
  128. package/package.json +6 -5
  129. /package/node_modules/@llblab/pi-telegram/lib/{logs.ts → logging.ts} +0 -0
@@ -6,6 +6,7 @@
6
6
 
7
7
  import {
8
8
  pairTelegramUserIfNeeded,
9
+ type TelegramConfigStore,
9
10
  TELEGRAM_DEFAULT_PROFILE_NAME,
10
11
  } from "./config.ts";
11
12
  import type { ExtensionAPI, ExtensionCommandContext } from "./pi.ts";
@@ -170,6 +171,7 @@ export const TELEGRAM_COMMAND_EMOJI = {
170
171
  continue: "▶️",
171
172
  abort: "⏹️",
172
173
  stop: "🟥",
174
+ name: "🏷️",
173
175
  } as const;
174
176
 
175
177
  export type TelegramCommandEmojiName = keyof typeof TELEGRAM_COMMAND_EMOJI;
@@ -186,6 +188,10 @@ export function formatTelegramCommandEmojiPrefix(
186
188
  return `${getTelegramCommandEmoji(command)} `;
187
189
  }
188
190
 
191
+ export function formatTelegramPiCommandHtml(command: string): string {
192
+ return `<code>${escapeHtml(command)}</code>`;
193
+ }
194
+
189
195
  export function formatTelegramInformationHeading(
190
196
  emoji: string,
191
197
  text: string,
@@ -193,6 +199,37 @@ export function formatTelegramInformationHeading(
193
199
  return `<b>${escapeHtml(emoji)} ${escapeHtml(text)}</b>`;
194
200
  }
195
201
 
202
+ export function formatTelegramInvalidInstanceName(
203
+ validationError: string,
204
+ ): string {
205
+ const details = validationError.replace(
206
+ /^Invalid Telegram (?:instance name|Thread display name):\s*/,
207
+ "",
208
+ );
209
+ const items = details
210
+ .split(/;\s+|(?<=\.)\s+(?=[A-Z])/)
211
+ .map((item) => item.trim())
212
+ .filter(Boolean)
213
+ .map((item) => /[.!?]$/.test(item) ? item : `${item}.`)
214
+ .map((item) => item[0]!.toUpperCase() + item.slice(1));
215
+ return [
216
+ "<b>⚠️ Invalid Thread Display Name:</b>\n",
217
+ ...items.map((item) => `• ${escapeHtml(item)}`),
218
+ ].join("\n");
219
+ }
220
+
221
+ export function formatTelegramThreadDisplayNameSavedHeading(
222
+ name: string,
223
+ ): string {
224
+ return `<b>✅ Thread display name saved as <i>${escapeHtml(name)}</i>.</b>`;
225
+ }
226
+
227
+ export function formatTelegramAutomaticThreadDisplayNameRestoredHeading(
228
+ name: string,
229
+ ): string {
230
+ return `<b>✅ Automatic Thread display name restored as <i>${escapeHtml(name)}</i>.</b>`;
231
+ }
232
+
196
233
  export const TELEGRAM_COMPACTION_STARTED_TEXT =
197
234
  formatTelegramInformationHeading(
198
235
  getTelegramCommandEmoji("compact"),
@@ -221,6 +258,13 @@ export const TELEGRAM_BUILTIN_BOT_COMMANDS: readonly TelegramBotCommandDefinitio
221
258
  "Open menu / Pair bridge",
222
259
  ),
223
260
  },
261
+ {
262
+ command: "name",
263
+ description: formatTelegramBotCommandDescription(
264
+ "name",
265
+ "Rename this thread",
266
+ ),
267
+ },
224
268
  {
225
269
  command: "compact",
226
270
  description: formatTelegramBotCommandDescription(
@@ -319,6 +363,7 @@ export function createTelegramBotCommandRegistrar(
319
363
  export interface TelegramBridgeCommandStartPollingOptions {
320
364
  force?: boolean;
321
365
  forceFreshLeaderThread?: boolean;
366
+ requestedThreadName?: string;
322
367
  }
323
368
 
324
369
  export interface TelegramBridgeCommandStartPollingResult {
@@ -338,6 +383,7 @@ export interface TelegramBridgeCommandRegistrationDeps {
338
383
  getStatusLines: (options?: TelegramBridgeStatusLineOptions) => string[];
339
384
  reloadConfig: () => Promise<void>;
340
385
  hasBotToken: () => boolean;
386
+ getBotTokenDiagnostic?: () => string | undefined;
341
387
  startPolling: (
342
388
  ctx: ExtensionCommandContext,
343
389
  options?: TelegramBridgeCommandStartPollingOptions,
@@ -358,15 +404,76 @@ export interface TelegramBridgeCommandRegistrationDeps {
358
404
  ctx: ExtensionCommandContext,
359
405
  profileName: string,
360
406
  ) => Promise<boolean>;
407
+ validateThreadName?: (threadName: string) => string | undefined;
408
+ }
409
+
410
+ export type TelegramThreadDisplayNameRenamePort = (
411
+ target: { chatId: number; threadId?: number },
412
+ threadName: string,
413
+ ) => Promise<{ ok: boolean; threadName?: string; message?: string }>;
414
+
415
+ export type TelegramThreadDisplayNameResetPort = (
416
+ target: { chatId: number; threadId?: number },
417
+ ) => Promise<{
418
+ ok: boolean;
419
+ threadName?: string;
420
+ message?: string;
421
+ }>;
422
+
423
+ export function createTelegramThreadDisplayNameResetBinding(): {
424
+ bind: (reset: TelegramThreadDisplayNameResetPort) => void;
425
+ reset: TelegramThreadDisplayNameResetPort;
426
+ } {
427
+ let current: TelegramThreadDisplayNameResetPort | undefined;
428
+ return {
429
+ bind(reset) { current = reset; },
430
+ async reset(target) {
431
+ return current
432
+ ? current(target)
433
+ : { ok: false, message: "Thread display name reset is unavailable." };
434
+ },
435
+ };
436
+ }
437
+
438
+ export function createTelegramThreadDisplayNameRenameBinding(): {
439
+ bind: (rename: TelegramThreadDisplayNameRenamePort) => void;
440
+ rename: TelegramThreadDisplayNameRenamePort;
441
+ } {
442
+ let current: TelegramThreadDisplayNameRenamePort | undefined;
443
+ return {
444
+ bind(rename) {
445
+ current = rename;
446
+ },
447
+ async rename(target, threadName) {
448
+ if (!current) {
449
+ return {
450
+ ok: false,
451
+ message: "Thread display naming is unavailable.",
452
+ };
453
+ }
454
+ return current(target, threadName);
455
+ },
456
+ };
361
457
  }
362
458
 
363
459
  function parseTelegramProfileArg(args: string): string | undefined {
364
460
  const word = args.trim().split(/\s+/)[0];
365
461
  if (!word || word.length === 0) return undefined;
366
- if (word.startsWith("-")) return undefined;
462
+ if (word.startsWith("-") || /^as=/i.test(word)) return undefined;
367
463
  return word === TELEGRAM_DEFAULT_PROFILE_NAME ? undefined : word;
368
464
  }
369
465
 
466
+ export function parseTelegramRequestedThreadName(
467
+ args: string,
468
+ ): string | undefined {
469
+ const token = args
470
+ .trim()
471
+ .split(/\s+/)
472
+ .find((word) => /^as=/i.test(word));
473
+ const value = token?.slice(3).trim();
474
+ return value || undefined;
475
+ }
476
+
370
477
  function formatTelegramTakeoverTitle(ctx: ExtensionCommandContext): string {
371
478
  return ctx.ui.theme.fg("accent", "pi-telegram");
372
479
  }
@@ -403,9 +510,28 @@ export function registerTelegramBridgeCommands(
403
510
  },
404
511
  });
405
512
  pi.registerCommand("telegram-connect", {
406
- description: "Start the Telegram bridge. Use /telegram-connect <name> for named profiles.",
513
+ description:
514
+ "Start the Telegram bridge. Use /telegram-connect <profile> and optional as=Name for a fresh Workspace Thread.",
407
515
  handler: async (args, ctx) => {
408
516
  const profileName = parseTelegramProfileArg(args);
517
+ const requestedNameTokens = args
518
+ .trim()
519
+ .split(/\s+/)
520
+ .filter((word) => /^as=/i.test(word));
521
+ const requestedThreadName = parseTelegramRequestedThreadName(args);
522
+ const requestedNameError =
523
+ requestedNameTokens.length > 1
524
+ ? "Specify at most one as=Name Workspace Thread name."
525
+ : requestedNameTokens.length === 1 && !requestedThreadName
526
+ ? "Usage: /telegram-connect [profile] as=Flightprice"
527
+ : requestedThreadName
528
+ ? deps.validateThreadName?.(requestedThreadName)
529
+ : undefined;
530
+ if (requestedNameError) {
531
+ ctx.ui.notify(requestedNameError, "warning");
532
+ deps.updateStatus(ctx);
533
+ return;
534
+ }
409
535
  if (profileName && deps.activateProfileConfig) {
410
536
  const ok = await deps.activateProfileConfig(ctx, profileName);
411
537
  if (!ok) {
@@ -418,6 +544,8 @@ export function registerTelegramBridgeCommands(
418
544
  await (deps.activateDefaultProfileConfig?.(ctx) ?? deps.reloadConfig());
419
545
  }
420
546
  if (!deps.hasBotToken()) {
547
+ const botTokenDiagnostic = deps.getBotTokenDiagnostic?.();
548
+ if (botTokenDiagnostic) ctx.ui.notify(botTokenDiagnostic, "error");
421
549
  const profileNames = deps.getProfileNames?.() ?? [];
422
550
  if (!profileName && profileNames.length > 0) {
423
551
  ctx.ui.notify(
@@ -466,6 +594,7 @@ export function registerTelegramBridgeCommands(
466
594
  };
467
595
  let result = await startWithRecovery({
468
596
  forceFreshLeaderThread: true,
597
+ ...(requestedThreadName ? { requestedThreadName } : {}),
469
598
  });
470
599
  if (result && !result.ok && result.canTakeover) {
471
600
  const confirmed = await ctx.ui.confirm(
@@ -480,6 +609,7 @@ export function registerTelegramBridgeCommands(
480
609
  result = await startWithRecovery({
481
610
  force: true,
482
611
  forceFreshLeaderThread: true,
612
+ ...(requestedThreadName ? { requestedThreadName } : {}),
483
613
  });
484
614
  }
485
615
  if (result?.message) {
@@ -527,6 +657,7 @@ export function registerTelegramBridgeCommands(
527
657
 
528
658
  export const TELEGRAM_RESERVED_COMMAND_NAMES = [
529
659
  "stop",
660
+ "name",
530
661
  "abort",
531
662
  "next",
532
663
  "continue",
@@ -559,6 +690,7 @@ export function isTelegramReservedCommandName(
559
690
  export type TelegramCommandAction =
560
691
  | { kind: "ignore"; executionMode: "ignored" }
561
692
  | { kind: "stop"; executionMode: "immediate" }
693
+ | { kind: "name"; executionMode: "immediate" }
562
694
  | { kind: "abort"; executionMode: "immediate" }
563
695
  | { kind: "next"; executionMode: "immediate" }
564
696
  | { kind: "continue"; executionMode: "immediate" }
@@ -578,6 +710,7 @@ export type TelegramCommandExecutionMode = "ignored" | "immediate";
578
710
 
579
711
  export interface TelegramCommandActionDeps<TMessage, TContext> {
580
712
  handleStop: (message: TMessage, ctx: TContext) => Promise<void>;
713
+ handleName: (message: TMessage, ctx: TContext, name: string) => Promise<void>;
581
714
  handleAbort: (message: TMessage, ctx: TContext) => Promise<void>;
582
715
  handleNext: (message: TMessage, ctx: TContext) => Promise<void>;
583
716
  handleContinue: (message: TMessage, ctx: TContext) => Promise<void>;
@@ -971,10 +1104,15 @@ export function createTelegramCommandTargetRuntime<
971
1104
  export interface TelegramCommandOrPromptRuntimeDeps<TMessage, TContext> {
972
1105
  extractRawText: (messages: TMessage[]) => string;
973
1106
  shouldIgnoreMessages?: (messages: TMessage[]) => boolean;
1107
+ consumeThreadNameInput?: (
1108
+ messages: TMessage[],
1109
+ ctx: TContext,
1110
+ ) => Promise<boolean>;
974
1111
  handleCommand: (
975
1112
  commandName: string | undefined,
976
1113
  message: TMessage,
977
1114
  ctx: TContext,
1115
+ commandArgs?: string,
978
1116
  ) => Promise<boolean>;
979
1117
  executeExtensionCommand?: (
980
1118
  command: ParsedTelegramCommand,
@@ -1039,11 +1177,17 @@ export interface TelegramCommandRuntimeDeps<
1039
1177
  openThinkingMenu: (message: TMessage, ctx: TContext) => Promise<void>;
1040
1178
  openQueueMenu: (message: TMessage, ctx: TContext) => Promise<void>;
1041
1179
  openSettingsMenu?: (message: TMessage, ctx: TContext) => Promise<void>;
1180
+ validateThreadName?: (threadName: string) => string | undefined;
1181
+ renameCurrentThread?: TelegramThreadDisplayNameRenamePort;
1182
+ resetCurrentThreadName?: TelegramThreadDisplayNameResetPort;
1183
+ openThreadNameDialog?: (
1184
+ message: TMessage,
1185
+ ctx: TContext,
1186
+ ) => Promise<void>;
1042
1187
  getAllowedUserId: () => number | undefined;
1043
- setAllowedUserId: (userId: number) => void;
1188
+ persistAllowedUserId: TelegramConfigStore["persistAllowedUserId"];
1044
1189
  registerBotCommands: () => Promise<void>;
1045
1190
  getPromptTemplateCommands?: () => readonly TelegramPromptTemplateMenuCommand[];
1046
- persistConfig: () => Promise<void>;
1047
1191
  sendTextReply: (
1048
1192
  message: TMessage,
1049
1193
  text: string,
@@ -1060,6 +1204,7 @@ export const TELEGRAM_APP_MENU_INTRO_HTML = [
1060
1204
  "<b>Pi Telegram</b>",
1061
1205
  "",
1062
1206
  `${formatTelegramCommandEmojiPrefix("start")}/start — Open menu / Pair bridge`,
1207
+ `${formatTelegramCommandEmojiPrefix("name")}/name Name — Rename this thread`,
1063
1208
  `${formatTelegramCommandEmojiPrefix("compact")}/compact — Compact current session`,
1064
1209
  `${formatTelegramCommandEmojiPrefix("next")}/next — Force next turn`,
1065
1210
  `${formatTelegramCommandEmojiPrefix("continue")}/continue — Queue continue prompt`,
@@ -1100,6 +1245,7 @@ function buildTelegramAppMenuIntroHtml(): string {
1100
1245
  "<b>Pi Telegram</b>",
1101
1246
  "",
1102
1247
  `${formatTelegramCommandEmojiPrefix("start")}/start — Open menu / Pair bridge`,
1248
+ `${formatTelegramCommandEmojiPrefix("name")}/name Name — Rename this thread`,
1103
1249
  `${formatTelegramCommandEmojiPrefix("compact")}/compact — Compact current session`,
1104
1250
  ...extensionLines,
1105
1251
  `${formatTelegramCommandEmojiPrefix("next")}/next — Force next turn`,
@@ -1168,6 +1314,7 @@ export function parseTelegramCommand(
1168
1314
 
1169
1315
  export const TELEGRAM_COMMAND_ACTIONS = {
1170
1316
  stop: { kind: "stop", executionMode: "immediate" },
1317
+ name: { kind: "name", executionMode: "immediate" },
1171
1318
  abort: { kind: "abort", executionMode: "immediate" },
1172
1319
  next: { kind: "next", executionMode: "immediate" },
1173
1320
  continue: { kind: "continue", executionMode: "immediate" },
@@ -1294,7 +1441,7 @@ export async function handleTelegramNextCommand(deps: {
1294
1441
  deps.updateStatus();
1295
1442
  const notice = formatTelegramInformationHeading(
1296
1443
  "⏩",
1297
- "Operation aborted. Dispatching next queued turn.",
1444
+ "Dispatching next queued turn.",
1298
1445
  );
1299
1446
  if (activeTurnReply) {
1300
1447
  await activeTurnReply(notice, { parseMode: "HTML" });
@@ -1525,6 +1672,7 @@ export async function executeTelegramCommandAction<TMessage, TContext>(
1525
1672
  message: TMessage,
1526
1673
  ctx: TContext,
1527
1674
  deps: TelegramCommandActionDeps<TMessage, TContext>,
1675
+ commandArgs = "",
1528
1676
  ): Promise<boolean> {
1529
1677
  switch (action.kind) {
1530
1678
  case "ignore":
@@ -1532,6 +1680,9 @@ export async function executeTelegramCommandAction<TMessage, TContext>(
1532
1680
  case "stop":
1533
1681
  await deps.handleStop(message, ctx);
1534
1682
  return true;
1683
+ case "name":
1684
+ await deps.handleName(message, ctx, commandArgs);
1685
+ return true;
1535
1686
  case "abort":
1536
1687
  await deps.handleAbort(message, ctx);
1537
1688
  return true;
@@ -1595,6 +1746,7 @@ export function createTelegramCommandHandlerTargetRuntime<
1595
1746
  commandName: string | undefined,
1596
1747
  message: TMessage,
1597
1748
  ctx: TContext,
1749
+ commandArgs?: string,
1598
1750
  ) => Promise<boolean> {
1599
1751
  const commandTargetRuntime = createTelegramCommandTargetQueueRuntime<
1600
1752
  TMessage,
@@ -1643,11 +1795,14 @@ export function createTelegramCommandHandlerTargetRuntime<
1643
1795
  openSettingsMenu: commandTargetRuntime.openSettingsMenu,
1644
1796
  handleForumBootstrap: deps.handleForumBootstrap,
1645
1797
  getAllowedUserId: deps.getAllowedUserId,
1646
- setAllowedUserId: deps.setAllowedUserId,
1798
+ persistAllowedUserId: deps.persistAllowedUserId,
1647
1799
  registerBotCommands: createTelegramBotCommandRegistrar({
1648
1800
  setMyCommands: deps.setMyCommands,
1649
1801
  }),
1650
- persistConfig: deps.persistConfig,
1802
+ validateThreadName: deps.validateThreadName,
1803
+ renameCurrentThread: deps.renameCurrentThread,
1804
+ resetCurrentThreadName: deps.resetCurrentThreadName,
1805
+ openThreadNameDialog: deps.openThreadNameDialog,
1651
1806
  sendTextReply: commandTargetRuntime.sendTextReply,
1652
1807
  recordRuntimeEvent: deps.recordRuntimeEvent,
1653
1808
  });
@@ -1661,8 +1816,9 @@ export function createTelegramCommandHandler<
1661
1816
  commandName: string | undefined,
1662
1817
  message: TMessage,
1663
1818
  ctx: TContext,
1819
+ commandArgs?: string,
1664
1820
  ): Promise<boolean> => {
1665
- return handleTelegramCommandRuntime(commandName, message, ctx, deps);
1821
+ return handleTelegramCommandRuntime(commandName, message, ctx, deps, commandArgs);
1666
1822
  };
1667
1823
  }
1668
1824
 
@@ -1678,11 +1834,16 @@ export function createTelegramCommandOrPromptRuntime<TMessage, TContext>(
1678
1834
  if (!firstMessage) return;
1679
1835
  if (deps.shouldIgnoreMessages?.(messages)) return;
1680
1836
  deps.assertExecutionCurrent?.(firstMessage);
1837
+ if (await deps.consumeThreadNameInput?.(messages, ctx)) {
1838
+ deps.assertExecutionCurrent?.(firstMessage);
1839
+ return;
1840
+ }
1681
1841
  const command = parseTelegramCommand(deps.extractRawText(messages));
1682
1842
  const handled = await deps.handleCommand(
1683
1843
  command?.name,
1684
1844
  firstMessage,
1685
1845
  ctx,
1846
+ command?.args,
1686
1847
  );
1687
1848
  deps.assertExecutionCurrent?.(firstMessage);
1688
1849
  if (handled) return;
@@ -1755,6 +1916,7 @@ async function handleTelegramCommandRuntime<
1755
1916
  message: TMessage,
1756
1917
  ctx: TContext,
1757
1918
  deps: TelegramCommandRuntimeDeps<TMessage, TContext>,
1919
+ commandArgs = "",
1758
1920
  ): Promise<boolean> {
1759
1921
  const assertExecutionCurrentFor = (nextMessage: TMessage) => (): void =>
1760
1922
  deps.assertExecutionCurrent?.(nextMessage);
@@ -1784,6 +1946,69 @@ async function handleTelegramCommandRuntime<
1784
1946
  sendTextReply: sendReplyFor(nextMessage),
1785
1947
  });
1786
1948
  },
1949
+ handleName: async (nextMessage, _commandCtx, requestedName) => {
1950
+ const threadName = requestedName.trim();
1951
+ if (!threadName) {
1952
+ if (deps.openThreadNameDialog) {
1953
+ await deps.openThreadNameDialog(nextMessage, _commandCtx);
1954
+ } else {
1955
+ await sendReplyFor(nextMessage)(
1956
+ formatTelegramInformationHeading("🏷️", "Usage: /name Navigator"),
1957
+ { parseMode: "HTML" },
1958
+ );
1959
+ }
1960
+ return;
1961
+ }
1962
+ if (/^[A-Z]$/.test(threadName) && deps.resetCurrentThreadName) {
1963
+ const result = await deps.resetCurrentThreadName(
1964
+ getTelegramCommandMessageTarget(nextMessage),
1965
+ );
1966
+ await sendReplyFor(nextMessage)(
1967
+ result.ok && !result.message
1968
+ ? formatTelegramAutomaticThreadDisplayNameRestoredHeading(
1969
+ result.threadName ?? threadName,
1970
+ )
1971
+ : formatTelegramInformationHeading(
1972
+ result.ok ? "✅" : "⚠️",
1973
+ result.message ?? "Thread display name reset failed.",
1974
+ ),
1975
+ { parseMode: "HTML" },
1976
+ );
1977
+ return;
1978
+ }
1979
+ const validationError = deps.validateThreadName?.(threadName);
1980
+ if (validationError) {
1981
+ await sendReplyFor(nextMessage)(
1982
+ formatTelegramInvalidInstanceName(validationError),
1983
+ { parseMode: "HTML" },
1984
+ );
1985
+ return;
1986
+ }
1987
+ if (!deps.renameCurrentThread) {
1988
+ await sendReplyFor(nextMessage)(
1989
+ formatTelegramInformationHeading("🚫", "Thread display naming is unavailable."),
1990
+ { parseMode: "HTML" },
1991
+ );
1992
+ return;
1993
+ }
1994
+ deps.assertExecutionCurrent?.(nextMessage);
1995
+ const result = await deps.renameCurrentThread(
1996
+ getTelegramCommandMessageTarget(nextMessage),
1997
+ threadName,
1998
+ );
1999
+ deps.assertExecutionCurrent?.(nextMessage);
2000
+ await sendReplyFor(nextMessage)(
2001
+ result.ok && !result.message
2002
+ ? formatTelegramThreadDisplayNameSavedHeading(
2003
+ result.threadName ?? threadName,
2004
+ )
2005
+ : formatTelegramInformationHeading(
2006
+ result.ok ? "✅" : "⚠️",
2007
+ result.message ?? "Thread display name update failed.",
2008
+ ),
2009
+ { parseMode: "HTML" },
2010
+ );
2011
+ },
1787
2012
  handleAbort: async (nextMessage, commandCtx) => {
1788
2013
  await handleTelegramAbortCommand({
1789
2014
  hasAbortHandler: deps.hasAbortHandler,
@@ -1916,14 +2141,14 @@ async function handleTelegramCommandRuntime<
1916
2141
  nextMessage.from?.id !== undefined &&
1917
2142
  canPairTelegramUserFromCommandMessage(nextMessage)
1918
2143
  ) {
1919
- await pairTelegramUserIfNeeded(nextMessage.from.id, {
2144
+ const allowed = await pairTelegramUserIfNeeded(nextMessage.from.id, {
1920
2145
  allowedUserId: deps.getAllowedUserId(),
1921
2146
  ctx: undefined,
1922
- setAllowedUserId: deps.setAllowedUserId,
1923
- persistConfig: deps.persistConfig,
2147
+ persistAllowedUserId: deps.persistAllowedUserId,
1924
2148
  updateStatus: updateStatusFor(commandCtx),
1925
2149
  assertExecutionCurrent: assertExecutionCurrentFor(nextMessage),
1926
2150
  });
2151
+ if (!allowed) return;
1927
2152
  }
1928
2153
  const isContextActive = () =>
1929
2154
  deps.isContextActive?.(commandCtx) !== false;
@@ -1959,5 +2184,6 @@ async function handleTelegramCommandRuntime<
1959
2184
  );
1960
2185
  },
1961
2186
  },
2187
+ commandArgs,
1962
2188
  );
1963
2189
  }