@llblab/pi-kit 0.6.0 → 0.7.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 (123) hide show
  1. package/CHANGELOG.md +4 -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 +13 -8
  73. package/node_modules/@llblab/pi-telegram/BACKLOG.md +20 -3
  74. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +13 -0
  75. package/node_modules/@llblab/pi-telegram/README.md +12 -8
  76. package/node_modules/@llblab/pi-telegram/docs/README.md +1 -0
  77. package/node_modules/@llblab/pi-telegram/docs/architecture.md +220 -18
  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 +12 -5
  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 +28 -1
  86. package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +600 -135
  87. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +962 -55
  88. package/node_modules/@llblab/pi-telegram/lib/bus.ts +350 -26
  89. package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +544 -0
  90. package/node_modules/@llblab/pi-telegram/lib/commands.ts +234 -11
  91. package/node_modules/@llblab/pi-telegram/lib/config.ts +177 -24
  92. package/node_modules/@llblab/pi-telegram/lib/extension.ts +1792 -0
  93. package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +20 -2
  94. package/node_modules/@llblab/pi-telegram/lib/journal.ts +2184 -126
  95. package/node_modules/@llblab/pi-telegram/lib/locks.ts +38 -1
  96. package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +152 -13
  97. package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +51 -10
  98. package/node_modules/@llblab/pi-telegram/lib/paths.ts +29 -0
  99. package/node_modules/@llblab/pi-telegram/lib/polling.ts +85 -17
  100. package/node_modules/@llblab/pi-telegram/lib/prompts.ts +5 -2
  101. package/node_modules/@llblab/pi-telegram/lib/queue.ts +52 -18
  102. package/node_modules/@llblab/pi-telegram/lib/replies.ts +2 -2
  103. package/node_modules/@llblab/pi-telegram/lib/routing.ts +305 -112
  104. package/node_modules/@llblab/pi-telegram/lib/status.ts +10 -0
  105. package/node_modules/@llblab/pi-telegram/lib/sync.ts +308 -39
  106. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +279 -4
  107. package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +664 -0
  108. package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +226 -0
  109. package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +118 -0
  110. package/node_modules/@llblab/pi-telegram/lib/threads.ts +1686 -129
  111. package/node_modules/@llblab/pi-telegram/lib/updates.ts +1319 -97
  112. package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +1643 -0
  113. package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +968 -0
  114. package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +84 -0
  115. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  116. package/node_modules/@llblab/pi-telegram/screenshot.png +0 -0
  117. package/node_modules/@llblab/pi-telegram/scripts/measure-bus.mjs +83 -0
  118. package/node_modules/@llblab/pi-telegram/scripts/measure-workspace.mjs +101 -0
  119. package/node_modules/@llblab/{skills → pi-telegram/skills}/show-me/SKILL.md +28 -6
  120. package/node_modules/@llblab/pi-telegram/skills/show-me/references/telegram-surfaces.md +43 -0
  121. package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/delivery-and-threads.md +1 -1
  122. package/node_modules/@llblab/skills/package.json +2 -3
  123. package/package.json +6 -5
@@ -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 {
@@ -358,15 +403,76 @@ export interface TelegramBridgeCommandRegistrationDeps {
358
403
  ctx: ExtensionCommandContext,
359
404
  profileName: string,
360
405
  ) => Promise<boolean>;
406
+ validateThreadName?: (threadName: string) => string | undefined;
407
+ }
408
+
409
+ export type TelegramThreadDisplayNameRenamePort = (
410
+ target: { chatId: number; threadId?: number },
411
+ threadName: string,
412
+ ) => Promise<{ ok: boolean; threadName?: string; message?: string }>;
413
+
414
+ export type TelegramThreadDisplayNameResetPort = (
415
+ target: { chatId: number; threadId?: number },
416
+ ) => Promise<{
417
+ ok: boolean;
418
+ threadName?: string;
419
+ message?: string;
420
+ }>;
421
+
422
+ export function createTelegramThreadDisplayNameResetBinding(): {
423
+ bind: (reset: TelegramThreadDisplayNameResetPort) => void;
424
+ reset: TelegramThreadDisplayNameResetPort;
425
+ } {
426
+ let current: TelegramThreadDisplayNameResetPort | undefined;
427
+ return {
428
+ bind(reset) { current = reset; },
429
+ async reset(target) {
430
+ return current
431
+ ? current(target)
432
+ : { ok: false, message: "Thread display name reset is unavailable." };
433
+ },
434
+ };
435
+ }
436
+
437
+ export function createTelegramThreadDisplayNameRenameBinding(): {
438
+ bind: (rename: TelegramThreadDisplayNameRenamePort) => void;
439
+ rename: TelegramThreadDisplayNameRenamePort;
440
+ } {
441
+ let current: TelegramThreadDisplayNameRenamePort | undefined;
442
+ return {
443
+ bind(rename) {
444
+ current = rename;
445
+ },
446
+ async rename(target, threadName) {
447
+ if (!current) {
448
+ return {
449
+ ok: false,
450
+ message: "Thread display naming is unavailable.",
451
+ };
452
+ }
453
+ return current(target, threadName);
454
+ },
455
+ };
361
456
  }
362
457
 
363
458
  function parseTelegramProfileArg(args: string): string | undefined {
364
459
  const word = args.trim().split(/\s+/)[0];
365
460
  if (!word || word.length === 0) return undefined;
366
- if (word.startsWith("-")) return undefined;
461
+ if (word.startsWith("-") || /^as=/i.test(word)) return undefined;
367
462
  return word === TELEGRAM_DEFAULT_PROFILE_NAME ? undefined : word;
368
463
  }
369
464
 
465
+ export function parseTelegramRequestedThreadName(
466
+ args: string,
467
+ ): string | undefined {
468
+ const token = args
469
+ .trim()
470
+ .split(/\s+/)
471
+ .find((word) => /^as=/i.test(word));
472
+ const value = token?.slice(3).trim();
473
+ return value || undefined;
474
+ }
475
+
370
476
  function formatTelegramTakeoverTitle(ctx: ExtensionCommandContext): string {
371
477
  return ctx.ui.theme.fg("accent", "pi-telegram");
372
478
  }
@@ -403,9 +509,28 @@ export function registerTelegramBridgeCommands(
403
509
  },
404
510
  });
405
511
  pi.registerCommand("telegram-connect", {
406
- description: "Start the Telegram bridge. Use /telegram-connect <name> for named profiles.",
512
+ description:
513
+ "Start the Telegram bridge. Use /telegram-connect <profile> and optional as=Name for a fresh Workspace Thread.",
407
514
  handler: async (args, ctx) => {
408
515
  const profileName = parseTelegramProfileArg(args);
516
+ const requestedNameTokens = args
517
+ .trim()
518
+ .split(/\s+/)
519
+ .filter((word) => /^as=/i.test(word));
520
+ const requestedThreadName = parseTelegramRequestedThreadName(args);
521
+ const requestedNameError =
522
+ requestedNameTokens.length > 1
523
+ ? "Specify at most one as=Name Workspace Thread name."
524
+ : requestedNameTokens.length === 1 && !requestedThreadName
525
+ ? "Usage: /telegram-connect [profile] as=Flightprice"
526
+ : requestedThreadName
527
+ ? deps.validateThreadName?.(requestedThreadName)
528
+ : undefined;
529
+ if (requestedNameError) {
530
+ ctx.ui.notify(requestedNameError, "warning");
531
+ deps.updateStatus(ctx);
532
+ return;
533
+ }
409
534
  if (profileName && deps.activateProfileConfig) {
410
535
  const ok = await deps.activateProfileConfig(ctx, profileName);
411
536
  if (!ok) {
@@ -466,6 +591,7 @@ export function registerTelegramBridgeCommands(
466
591
  };
467
592
  let result = await startWithRecovery({
468
593
  forceFreshLeaderThread: true,
594
+ ...(requestedThreadName ? { requestedThreadName } : {}),
469
595
  });
470
596
  if (result && !result.ok && result.canTakeover) {
471
597
  const confirmed = await ctx.ui.confirm(
@@ -480,6 +606,7 @@ export function registerTelegramBridgeCommands(
480
606
  result = await startWithRecovery({
481
607
  force: true,
482
608
  forceFreshLeaderThread: true,
609
+ ...(requestedThreadName ? { requestedThreadName } : {}),
483
610
  });
484
611
  }
485
612
  if (result?.message) {
@@ -527,6 +654,7 @@ export function registerTelegramBridgeCommands(
527
654
 
528
655
  export const TELEGRAM_RESERVED_COMMAND_NAMES = [
529
656
  "stop",
657
+ "name",
530
658
  "abort",
531
659
  "next",
532
660
  "continue",
@@ -559,6 +687,7 @@ export function isTelegramReservedCommandName(
559
687
  export type TelegramCommandAction =
560
688
  | { kind: "ignore"; executionMode: "ignored" }
561
689
  | { kind: "stop"; executionMode: "immediate" }
690
+ | { kind: "name"; executionMode: "immediate" }
562
691
  | { kind: "abort"; executionMode: "immediate" }
563
692
  | { kind: "next"; executionMode: "immediate" }
564
693
  | { kind: "continue"; executionMode: "immediate" }
@@ -578,6 +707,7 @@ export type TelegramCommandExecutionMode = "ignored" | "immediate";
578
707
 
579
708
  export interface TelegramCommandActionDeps<TMessage, TContext> {
580
709
  handleStop: (message: TMessage, ctx: TContext) => Promise<void>;
710
+ handleName: (message: TMessage, ctx: TContext, name: string) => Promise<void>;
581
711
  handleAbort: (message: TMessage, ctx: TContext) => Promise<void>;
582
712
  handleNext: (message: TMessage, ctx: TContext) => Promise<void>;
583
713
  handleContinue: (message: TMessage, ctx: TContext) => Promise<void>;
@@ -971,10 +1101,15 @@ export function createTelegramCommandTargetRuntime<
971
1101
  export interface TelegramCommandOrPromptRuntimeDeps<TMessage, TContext> {
972
1102
  extractRawText: (messages: TMessage[]) => string;
973
1103
  shouldIgnoreMessages?: (messages: TMessage[]) => boolean;
1104
+ consumeThreadNameInput?: (
1105
+ messages: TMessage[],
1106
+ ctx: TContext,
1107
+ ) => Promise<boolean>;
974
1108
  handleCommand: (
975
1109
  commandName: string | undefined,
976
1110
  message: TMessage,
977
1111
  ctx: TContext,
1112
+ commandArgs?: string,
978
1113
  ) => Promise<boolean>;
979
1114
  executeExtensionCommand?: (
980
1115
  command: ParsedTelegramCommand,
@@ -1039,11 +1174,17 @@ export interface TelegramCommandRuntimeDeps<
1039
1174
  openThinkingMenu: (message: TMessage, ctx: TContext) => Promise<void>;
1040
1175
  openQueueMenu: (message: TMessage, ctx: TContext) => Promise<void>;
1041
1176
  openSettingsMenu?: (message: TMessage, ctx: TContext) => Promise<void>;
1177
+ validateThreadName?: (threadName: string) => string | undefined;
1178
+ renameCurrentThread?: TelegramThreadDisplayNameRenamePort;
1179
+ resetCurrentThreadName?: TelegramThreadDisplayNameResetPort;
1180
+ openThreadNameDialog?: (
1181
+ message: TMessage,
1182
+ ctx: TContext,
1183
+ ) => Promise<void>;
1042
1184
  getAllowedUserId: () => number | undefined;
1043
- setAllowedUserId: (userId: number) => void;
1185
+ persistAllowedUserId: TelegramConfigStore["persistAllowedUserId"];
1044
1186
  registerBotCommands: () => Promise<void>;
1045
1187
  getPromptTemplateCommands?: () => readonly TelegramPromptTemplateMenuCommand[];
1046
- persistConfig: () => Promise<void>;
1047
1188
  sendTextReply: (
1048
1189
  message: TMessage,
1049
1190
  text: string,
@@ -1060,6 +1201,7 @@ export const TELEGRAM_APP_MENU_INTRO_HTML = [
1060
1201
  "<b>Pi Telegram</b>",
1061
1202
  "",
1062
1203
  `${formatTelegramCommandEmojiPrefix("start")}/start — Open menu / Pair bridge`,
1204
+ `${formatTelegramCommandEmojiPrefix("name")}/name Name — Rename this thread`,
1063
1205
  `${formatTelegramCommandEmojiPrefix("compact")}/compact — Compact current session`,
1064
1206
  `${formatTelegramCommandEmojiPrefix("next")}/next — Force next turn`,
1065
1207
  `${formatTelegramCommandEmojiPrefix("continue")}/continue — Queue continue prompt`,
@@ -1100,6 +1242,7 @@ function buildTelegramAppMenuIntroHtml(): string {
1100
1242
  "<b>Pi Telegram</b>",
1101
1243
  "",
1102
1244
  `${formatTelegramCommandEmojiPrefix("start")}/start — Open menu / Pair bridge`,
1245
+ `${formatTelegramCommandEmojiPrefix("name")}/name Name — Rename this thread`,
1103
1246
  `${formatTelegramCommandEmojiPrefix("compact")}/compact — Compact current session`,
1104
1247
  ...extensionLines,
1105
1248
  `${formatTelegramCommandEmojiPrefix("next")}/next — Force next turn`,
@@ -1168,6 +1311,7 @@ export function parseTelegramCommand(
1168
1311
 
1169
1312
  export const TELEGRAM_COMMAND_ACTIONS = {
1170
1313
  stop: { kind: "stop", executionMode: "immediate" },
1314
+ name: { kind: "name", executionMode: "immediate" },
1171
1315
  abort: { kind: "abort", executionMode: "immediate" },
1172
1316
  next: { kind: "next", executionMode: "immediate" },
1173
1317
  continue: { kind: "continue", executionMode: "immediate" },
@@ -1294,7 +1438,7 @@ export async function handleTelegramNextCommand(deps: {
1294
1438
  deps.updateStatus();
1295
1439
  const notice = formatTelegramInformationHeading(
1296
1440
  "⏩",
1297
- "Operation aborted. Dispatching next queued turn.",
1441
+ "Dispatching next queued turn.",
1298
1442
  );
1299
1443
  if (activeTurnReply) {
1300
1444
  await activeTurnReply(notice, { parseMode: "HTML" });
@@ -1525,6 +1669,7 @@ export async function executeTelegramCommandAction<TMessage, TContext>(
1525
1669
  message: TMessage,
1526
1670
  ctx: TContext,
1527
1671
  deps: TelegramCommandActionDeps<TMessage, TContext>,
1672
+ commandArgs = "",
1528
1673
  ): Promise<boolean> {
1529
1674
  switch (action.kind) {
1530
1675
  case "ignore":
@@ -1532,6 +1677,9 @@ export async function executeTelegramCommandAction<TMessage, TContext>(
1532
1677
  case "stop":
1533
1678
  await deps.handleStop(message, ctx);
1534
1679
  return true;
1680
+ case "name":
1681
+ await deps.handleName(message, ctx, commandArgs);
1682
+ return true;
1535
1683
  case "abort":
1536
1684
  await deps.handleAbort(message, ctx);
1537
1685
  return true;
@@ -1595,6 +1743,7 @@ export function createTelegramCommandHandlerTargetRuntime<
1595
1743
  commandName: string | undefined,
1596
1744
  message: TMessage,
1597
1745
  ctx: TContext,
1746
+ commandArgs?: string,
1598
1747
  ) => Promise<boolean> {
1599
1748
  const commandTargetRuntime = createTelegramCommandTargetQueueRuntime<
1600
1749
  TMessage,
@@ -1643,11 +1792,14 @@ export function createTelegramCommandHandlerTargetRuntime<
1643
1792
  openSettingsMenu: commandTargetRuntime.openSettingsMenu,
1644
1793
  handleForumBootstrap: deps.handleForumBootstrap,
1645
1794
  getAllowedUserId: deps.getAllowedUserId,
1646
- setAllowedUserId: deps.setAllowedUserId,
1795
+ persistAllowedUserId: deps.persistAllowedUserId,
1647
1796
  registerBotCommands: createTelegramBotCommandRegistrar({
1648
1797
  setMyCommands: deps.setMyCommands,
1649
1798
  }),
1650
- persistConfig: deps.persistConfig,
1799
+ validateThreadName: deps.validateThreadName,
1800
+ renameCurrentThread: deps.renameCurrentThread,
1801
+ resetCurrentThreadName: deps.resetCurrentThreadName,
1802
+ openThreadNameDialog: deps.openThreadNameDialog,
1651
1803
  sendTextReply: commandTargetRuntime.sendTextReply,
1652
1804
  recordRuntimeEvent: deps.recordRuntimeEvent,
1653
1805
  });
@@ -1661,8 +1813,9 @@ export function createTelegramCommandHandler<
1661
1813
  commandName: string | undefined,
1662
1814
  message: TMessage,
1663
1815
  ctx: TContext,
1816
+ commandArgs?: string,
1664
1817
  ): Promise<boolean> => {
1665
- return handleTelegramCommandRuntime(commandName, message, ctx, deps);
1818
+ return handleTelegramCommandRuntime(commandName, message, ctx, deps, commandArgs);
1666
1819
  };
1667
1820
  }
1668
1821
 
@@ -1678,11 +1831,16 @@ export function createTelegramCommandOrPromptRuntime<TMessage, TContext>(
1678
1831
  if (!firstMessage) return;
1679
1832
  if (deps.shouldIgnoreMessages?.(messages)) return;
1680
1833
  deps.assertExecutionCurrent?.(firstMessage);
1834
+ if (await deps.consumeThreadNameInput?.(messages, ctx)) {
1835
+ deps.assertExecutionCurrent?.(firstMessage);
1836
+ return;
1837
+ }
1681
1838
  const command = parseTelegramCommand(deps.extractRawText(messages));
1682
1839
  const handled = await deps.handleCommand(
1683
1840
  command?.name,
1684
1841
  firstMessage,
1685
1842
  ctx,
1843
+ command?.args,
1686
1844
  );
1687
1845
  deps.assertExecutionCurrent?.(firstMessage);
1688
1846
  if (handled) return;
@@ -1755,6 +1913,7 @@ async function handleTelegramCommandRuntime<
1755
1913
  message: TMessage,
1756
1914
  ctx: TContext,
1757
1915
  deps: TelegramCommandRuntimeDeps<TMessage, TContext>,
1916
+ commandArgs = "",
1758
1917
  ): Promise<boolean> {
1759
1918
  const assertExecutionCurrentFor = (nextMessage: TMessage) => (): void =>
1760
1919
  deps.assertExecutionCurrent?.(nextMessage);
@@ -1784,6 +1943,69 @@ async function handleTelegramCommandRuntime<
1784
1943
  sendTextReply: sendReplyFor(nextMessage),
1785
1944
  });
1786
1945
  },
1946
+ handleName: async (nextMessage, _commandCtx, requestedName) => {
1947
+ const threadName = requestedName.trim();
1948
+ if (!threadName) {
1949
+ if (deps.openThreadNameDialog) {
1950
+ await deps.openThreadNameDialog(nextMessage, _commandCtx);
1951
+ } else {
1952
+ await sendReplyFor(nextMessage)(
1953
+ formatTelegramInformationHeading("🏷️", "Usage: /name Navigator"),
1954
+ { parseMode: "HTML" },
1955
+ );
1956
+ }
1957
+ return;
1958
+ }
1959
+ if (/^[A-Z]$/.test(threadName) && deps.resetCurrentThreadName) {
1960
+ const result = await deps.resetCurrentThreadName(
1961
+ getTelegramCommandMessageTarget(nextMessage),
1962
+ );
1963
+ await sendReplyFor(nextMessage)(
1964
+ result.ok && !result.message
1965
+ ? formatTelegramAutomaticThreadDisplayNameRestoredHeading(
1966
+ result.threadName ?? threadName,
1967
+ )
1968
+ : formatTelegramInformationHeading(
1969
+ result.ok ? "✅" : "⚠️",
1970
+ result.message ?? "Thread display name reset failed.",
1971
+ ),
1972
+ { parseMode: "HTML" },
1973
+ );
1974
+ return;
1975
+ }
1976
+ const validationError = deps.validateThreadName?.(threadName);
1977
+ if (validationError) {
1978
+ await sendReplyFor(nextMessage)(
1979
+ formatTelegramInvalidInstanceName(validationError),
1980
+ { parseMode: "HTML" },
1981
+ );
1982
+ return;
1983
+ }
1984
+ if (!deps.renameCurrentThread) {
1985
+ await sendReplyFor(nextMessage)(
1986
+ formatTelegramInformationHeading("🚫", "Thread display naming is unavailable."),
1987
+ { parseMode: "HTML" },
1988
+ );
1989
+ return;
1990
+ }
1991
+ deps.assertExecutionCurrent?.(nextMessage);
1992
+ const result = await deps.renameCurrentThread(
1993
+ getTelegramCommandMessageTarget(nextMessage),
1994
+ threadName,
1995
+ );
1996
+ deps.assertExecutionCurrent?.(nextMessage);
1997
+ await sendReplyFor(nextMessage)(
1998
+ result.ok && !result.message
1999
+ ? formatTelegramThreadDisplayNameSavedHeading(
2000
+ result.threadName ?? threadName,
2001
+ )
2002
+ : formatTelegramInformationHeading(
2003
+ result.ok ? "✅" : "⚠️",
2004
+ result.message ?? "Thread display name update failed.",
2005
+ ),
2006
+ { parseMode: "HTML" },
2007
+ );
2008
+ },
1787
2009
  handleAbort: async (nextMessage, commandCtx) => {
1788
2010
  await handleTelegramAbortCommand({
1789
2011
  hasAbortHandler: deps.hasAbortHandler,
@@ -1916,14 +2138,14 @@ async function handleTelegramCommandRuntime<
1916
2138
  nextMessage.from?.id !== undefined &&
1917
2139
  canPairTelegramUserFromCommandMessage(nextMessage)
1918
2140
  ) {
1919
- await pairTelegramUserIfNeeded(nextMessage.from.id, {
2141
+ const allowed = await pairTelegramUserIfNeeded(nextMessage.from.id, {
1920
2142
  allowedUserId: deps.getAllowedUserId(),
1921
2143
  ctx: undefined,
1922
- setAllowedUserId: deps.setAllowedUserId,
1923
- persistConfig: deps.persistConfig,
2144
+ persistAllowedUserId: deps.persistAllowedUserId,
1924
2145
  updateStatus: updateStatusFor(commandCtx),
1925
2146
  assertExecutionCurrent: assertExecutionCurrentFor(nextMessage),
1926
2147
  });
2148
+ if (!allowed) return;
1927
2149
  }
1928
2150
  const isContextActive = () =>
1929
2151
  deps.isContextActive?.(commandCtx) !== false;
@@ -1959,5 +2181,6 @@ async function handleTelegramCommandRuntime<
1959
2181
  );
1960
2182
  },
1961
2183
  },
2184
+ commandArgs,
1962
2185
  );
1963
2186
  }