@llblab/pi-kit 0.27.6 → 0.28.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 (182) hide show
  1. package/BACKLOG.md +2 -5
  2. package/CHANGELOG.md +5 -0
  3. package/README.md +2 -2
  4. package/node_modules/@llblab/pi-telegram/AGENTS.md +10 -8
  5. package/node_modules/@llblab/pi-telegram/BACKLOG.md +1 -9
  6. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +13 -0
  7. package/node_modules/@llblab/pi-telegram/LICENSE +21 -0
  8. package/node_modules/@llblab/pi-telegram/README.md +15 -5
  9. package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.d.ts +1 -5
  10. package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.js +5 -7
  11. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +5 -1
  12. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +6 -1
  13. package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +4 -2
  14. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +54 -56
  15. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +206 -139
  16. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +21 -2
  17. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +181 -25
  18. package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.d.ts +4 -1
  19. package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.js +28 -4
  20. package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +52 -56
  21. package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +78 -246
  22. package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.d.ts +1 -3
  23. package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.js +23 -26
  24. package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.d.ts +0 -5
  25. package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.js +5 -5
  26. package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +0 -23
  27. package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +15 -15
  28. package/node_modules/@llblab/pi-telegram/dist/lib/config.d.ts +0 -17
  29. package/node_modules/@llblab/pi-telegram/dist/lib/config.js +12 -14
  30. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.d.ts +0 -4
  31. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.js +2 -5
  32. package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +255 -61
  33. package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.d.ts +0 -19
  34. package/node_modules/@llblab/pi-telegram/dist/lib/inbound.d.ts +0 -2
  35. package/node_modules/@llblab/pi-telegram/dist/lib/inbound.js +2 -2
  36. package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +177 -16
  37. package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +1115 -249
  38. package/node_modules/@llblab/pi-telegram/dist/lib/keyboard.d.ts +0 -2
  39. package/node_modules/@llblab/pi-telegram/dist/lib/keyboard.js +2 -2
  40. package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +1 -1
  41. package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +100 -12
  42. package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +380 -14
  43. package/node_modules/@llblab/pi-telegram/dist/lib/logging.d.ts +30 -12
  44. package/node_modules/@llblab/pi-telegram/dist/lib/logging.js +129 -72
  45. package/node_modules/@llblab/pi-telegram/dist/lib/media.d.ts +28 -5
  46. package/node_modules/@llblab/pi-telegram/dist/lib/media.js +26 -8
  47. package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.d.ts +0 -11
  48. package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.js +8 -8
  49. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.d.ts +0 -18
  50. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.js +18 -18
  51. package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.d.ts +4 -4
  52. package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.js +12 -7
  53. package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.d.ts +1 -1
  54. package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.js +1 -1
  55. package/node_modules/@llblab/pi-telegram/dist/lib/menu.d.ts +1 -0
  56. package/node_modules/@llblab/pi-telegram/dist/lib/menu.js +4 -3
  57. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.d.ts +0 -1
  58. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.js +1 -1
  59. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-buttons.js +1 -8
  60. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.d.ts +1 -6
  61. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.js +4 -21
  62. package/node_modules/@llblab/pi-telegram/dist/lib/outbound.d.ts +0 -8
  63. package/node_modules/@llblab/pi-telegram/dist/lib/outbound.js +5 -5
  64. package/node_modules/@llblab/pi-telegram/dist/lib/paths.d.ts +50 -7
  65. package/node_modules/@llblab/pi-telegram/dist/lib/paths.js +155 -17
  66. package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +1 -0
  67. package/node_modules/@llblab/pi-telegram/dist/lib/pi.js +2 -1
  68. package/node_modules/@llblab/pi-telegram/dist/lib/polling.d.ts +0 -5
  69. package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +5 -5
  70. package/node_modules/@llblab/pi-telegram/dist/lib/preview.d.ts +0 -3
  71. package/node_modules/@llblab/pi-telegram/dist/lib/preview.js +3 -3
  72. package/node_modules/@llblab/pi-telegram/dist/lib/prompt-templates.d.ts +0 -1
  73. package/node_modules/@llblab/pi-telegram/dist/lib/prompt-templates.js +1 -1
  74. package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +3 -15
  75. package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +69 -8
  76. package/node_modules/@llblab/pi-telegram/dist/lib/recovery.d.ts +29 -9
  77. package/node_modules/@llblab/pi-telegram/dist/lib/recovery.js +104 -33
  78. package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +0 -4
  79. package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +1 -1
  80. package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +73 -8
  81. package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +2206 -307
  82. package/node_modules/@llblab/pi-telegram/dist/lib/sections.d.ts +0 -2
  83. package/node_modules/@llblab/pi-telegram/dist/lib/sections.js +1 -1
  84. package/node_modules/@llblab/pi-telegram/dist/lib/status.d.ts +44 -9
  85. package/node_modules/@llblab/pi-telegram/dist/lib/status.js +149 -27
  86. package/node_modules/@llblab/pi-telegram/dist/lib/sync.d.ts +0 -5
  87. package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +5 -4
  88. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +11 -3
  89. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +34 -23
  90. package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.d.ts +1 -0
  91. package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.js +18 -21
  92. package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.d.ts +32 -5
  93. package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.js +191 -7
  94. package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.d.ts +2 -0
  95. package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.js +1 -1
  96. package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +291 -59
  97. package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +1647 -369
  98. package/node_modules/@llblab/pi-telegram/dist/lib/turns.d.ts +0 -1
  99. package/node_modules/@llblab/pi-telegram/dist/lib/turns.js +1 -1
  100. package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +184 -28
  101. package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +1032 -114
  102. package/node_modules/@llblab/pi-telegram/dist/lib/wire.d.ts +12 -0
  103. package/node_modules/@llblab/pi-telegram/dist/lib/wire.js +21 -0
  104. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.d.ts +25 -17
  105. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.js +95 -20
  106. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-identity.d.ts +20 -0
  107. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-identity.js +103 -0
  108. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +43 -8
  109. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +148 -52
  110. package/node_modules/@llblab/pi-telegram/dist/package.json +6 -6
  111. package/node_modules/@llblab/pi-telegram/dist/skills/generated-control-surface/SKILL.md +3 -3
  112. package/node_modules/@llblab/pi-telegram/dist/skills/telegram-bridge/references/diagnosis.md +3 -3
  113. package/node_modules/@llblab/pi-telegram/docs/README.md +1 -1
  114. package/node_modules/@llblab/pi-telegram/docs/activity.md +1 -1
  115. package/node_modules/@llblab/pi-telegram/docs/architecture.md +152 -34
  116. package/node_modules/@llblab/pi-telegram/docs/callback-namespaces.md +1 -1
  117. package/node_modules/@llblab/pi-telegram/docs/delivery.md +1 -1
  118. package/node_modules/@llblab/pi-telegram/docs/generative-apps.md +2 -2
  119. package/node_modules/@llblab/pi-telegram/docs/inbound.md +1 -1
  120. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +125 -19
  121. package/node_modules/@llblab/pi-telegram/docs/public-api.md +3 -1
  122. package/node_modules/@llblab/pi-telegram/docs/ui-style.md +22 -8
  123. package/node_modules/@llblab/pi-telegram/docs/updates.md +1 -1
  124. package/node_modules/@llblab/pi-telegram/lib/activity-verbosity.ts +5 -9
  125. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +6 -0
  126. package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +5 -2
  127. package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +243 -224
  128. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +178 -31
  129. package/node_modules/@llblab/pi-telegram/lib/bus-transport.ts +30 -8
  130. package/node_modules/@llblab/pi-telegram/lib/bus.ts +110 -334
  131. package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +29 -29
  132. package/node_modules/@llblab/pi-telegram/lib/command-templates.ts +5 -5
  133. package/node_modules/@llblab/pi-telegram/lib/commands.ts +15 -15
  134. package/node_modules/@llblab/pi-telegram/lib/config.ts +12 -17
  135. package/node_modules/@llblab/pi-telegram/lib/delivery.ts +2 -8
  136. package/node_modules/@llblab/pi-telegram/lib/extension.ts +257 -72
  137. package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +0 -22
  138. package/node_modules/@llblab/pi-telegram/lib/inbound.ts +2 -2
  139. package/node_modules/@llblab/pi-telegram/lib/journal.ts +1182 -326
  140. package/node_modules/@llblab/pi-telegram/lib/keyboard.ts +2 -2
  141. package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +1 -1
  142. package/node_modules/@llblab/pi-telegram/lib/locks.ts +402 -23
  143. package/node_modules/@llblab/pi-telegram/lib/logging.ts +151 -101
  144. package/node_modules/@llblab/pi-telegram/lib/media.ts +51 -9
  145. package/node_modules/@llblab/pi-telegram/lib/menu-model.ts +8 -8
  146. package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +18 -18
  147. package/node_modules/@llblab/pi-telegram/lib/menu-status.ts +11 -0
  148. package/node_modules/@llblab/pi-telegram/lib/menu-thinking.ts +2 -2
  149. package/node_modules/@llblab/pi-telegram/lib/menu.ts +5 -1
  150. package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +1 -1
  151. package/node_modules/@llblab/pi-telegram/lib/outbound-buttons.ts +1 -10
  152. package/node_modules/@llblab/pi-telegram/lib/outbound-markup.ts +5 -24
  153. package/node_modules/@llblab/pi-telegram/lib/outbound.ts +5 -5
  154. package/node_modules/@llblab/pi-telegram/lib/paths.ts +177 -17
  155. package/node_modules/@llblab/pi-telegram/lib/pi.ts +5 -2
  156. package/node_modules/@llblab/pi-telegram/lib/polling.ts +5 -5
  157. package/node_modules/@llblab/pi-telegram/lib/preview.ts +3 -3
  158. package/node_modules/@llblab/pi-telegram/lib/prompt-templates.ts +1 -1
  159. package/node_modules/@llblab/pi-telegram/lib/queue.ts +67 -9
  160. package/node_modules/@llblab/pi-telegram/lib/recovery.ts +104 -44
  161. package/node_modules/@llblab/pi-telegram/lib/replies.ts +1 -1
  162. package/node_modules/@llblab/pi-telegram/lib/routing.ts +1955 -375
  163. package/node_modules/@llblab/pi-telegram/lib/sections.ts +1 -1
  164. package/node_modules/@llblab/pi-telegram/lib/status.ts +156 -36
  165. package/node_modules/@llblab/pi-telegram/lib/sync.ts +4 -4
  166. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +48 -33
  167. package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +24 -21
  168. package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +267 -9
  169. package/node_modules/@llblab/pi-telegram/lib/thread-reconciler.ts +3 -1
  170. package/node_modules/@llblab/pi-telegram/lib/threads.ts +1697 -488
  171. package/node_modules/@llblab/pi-telegram/lib/turns.ts +1 -1
  172. package/node_modules/@llblab/pi-telegram/lib/updates.ts +1072 -151
  173. package/node_modules/@llblab/pi-telegram/lib/wire.ts +28 -0
  174. package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +97 -47
  175. package/node_modules/@llblab/pi-telegram/lib/workspace-identity.ts +147 -0
  176. package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +188 -89
  177. package/node_modules/@llblab/pi-telegram/package.json +6 -6
  178. package/node_modules/@llblab/pi-telegram/scripts/audit-exports.mjs +100 -0
  179. package/node_modules/@llblab/pi-telegram/scripts/check-downgrade.mjs +80 -43
  180. package/node_modules/@llblab/pi-telegram/skills/generated-control-surface/SKILL.md +3 -3
  181. package/node_modules/@llblab/pi-telegram/skills/telegram-bridge/references/diagnosis.md +3 -3
  182. package/package.json +2 -2
@@ -7,6 +7,8 @@ import { chmodSync, closeSync, constants, fstatSync, lstatSync, mkdirSync, openS
7
7
  import { dirname } from "node:path";
8
8
  import { createHash, randomUUID } from "node:crypto";
9
9
  import { renameTelegramPathWithRetry, withTelegramFileTransaction } from "./locks.js";
10
+ import { getTelegramJournalPublicationPaths } from "./paths.js";
11
+ import { isWireRecord as isObject, hasOnlyWireKeys as onlyKeys, isNonNegativeWireInteger as safeTime, } from "./wire.js";
10
12
  function targetKey(target) {
11
13
  return `${target.chatId}:${target.threadId}`;
12
14
  }
@@ -134,16 +136,6 @@ export function createTelegramInactiveThreadCleanupReviewRuntime(deps) {
134
136
  },
135
137
  };
136
138
  }
137
- function isObject(value) {
138
- return !!value && typeof value === "object" && !Array.isArray(value);
139
- }
140
- function onlyKeys(value, keys) {
141
- const allowed = new Set(keys);
142
- return Object.keys(value).every(key => allowed.has(key));
143
- }
144
- function safeTime(value) {
145
- return Number.isSafeInteger(value) && value >= 0;
146
- }
147
139
  function validateWorkSet(value, profileName) {
148
140
  if (!isObject(value) || !onlyKeys(value, ["operationId", "createdAtMs", "entries"]) ||
149
141
  typeof value.operationId !== "string" || !value.operationId || !safeTime(value.createdAtMs) ||
@@ -199,7 +191,18 @@ function validateWorkSet(value, profileName) {
199
191
  function sameCandidates(left, right) {
200
192
  return JSON.stringify(left) === JSON.stringify(right);
201
193
  }
194
+ function parseTelegramThreadCleanupWorkFile(value, profileName, tokenSha256) {
195
+ if (!isObject(value) || !onlyKeys(value, ["version", "profileName", "tokenSha256", "workSets"]) ||
196
+ value.version !== 1 || value.profileName !== profileName || value.tokenSha256 !== tokenSha256 || !Array.isArray(value.workSets))
197
+ throw new Error("Telegram Thread cleanup store identity or schema does not match.");
198
+ const workSets = value.workSets.map(workSet => validateWorkSet(workSet, profileName));
199
+ if (new Set(workSets.map(workSet => workSet.operationId)).size !== workSets.length)
200
+ throw new Error("Telegram Thread cleanup operation identity is ambiguous.");
201
+ return { version: 1, profileName, tokenSha256, workSets };
202
+ }
202
203
  export function createTelegramThreadCleanupWorkStore(options) {
204
+ options = { ...options };
205
+ const publicationPaths = getTelegramJournalPublicationPaths(options.path, options.runtimeDir);
203
206
  const maxWorkSets = options.maxWorkSets ?? 32;
204
207
  const maxBytes = options.maxBytes ?? 1024 * 1024;
205
208
  const now = options.getNowMs ?? Date.now;
@@ -236,14 +239,7 @@ export function createTelegramThreadCleanupWorkStore(options) {
236
239
  finally {
237
240
  closeSync(fd);
238
241
  }
239
- if (!isObject(value) || !onlyKeys(value, ["version", "profileName", "tokenSha256", "workSets"]) ||
240
- value.version !== 1 || value.profileName !== options.profileName ||
241
- value.tokenSha256 !== options.tokenSha256 || !Array.isArray(value.workSets))
242
- throw new Error("Telegram Thread cleanup store identity or schema does not match.");
243
- const workSets = value.workSets.map(workSet => validateWorkSet(workSet, options.profileName));
244
- if (new Set(workSets.map(workSet => workSet.operationId)).size !== workSets.length)
245
- throw new Error("Telegram Thread cleanup operation identity is ambiguous.");
246
- return { version: 1, profileName: options.profileName, tokenSha256: options.tokenSha256, workSets };
242
+ return parseTelegramThreadCleanupWorkFile(value, options.profileName, options.tokenSha256);
247
243
  };
248
244
  const publish = (file) => {
249
245
  if (file.workSets.length > maxWorkSets)
@@ -251,8 +247,9 @@ export function createTelegramThreadCleanupWorkStore(options) {
251
247
  const serialized = `${JSON.stringify(file, null, 2)}\n`;
252
248
  if (Buffer.byteLength(serialized) > maxBytes)
253
249
  throw new Error("Telegram Thread cleanup byte capacity reached.");
254
- const temporaryPath = `${options.path}.${process.pid}.${randomUUID()}.tmp`;
250
+ const temporaryPath = `${publicationPaths.temporaryBasePath}.${process.pid}.${randomUUID()}.tmp`;
255
251
  mkdirSync(dirname(options.path), { recursive: true, mode: 0o700 });
252
+ mkdirSync(dirname(temporaryPath), { recursive: true, mode: 0o700 });
256
253
  try {
257
254
  writeFileSync(temporaryPath, serialized, { mode: 0o600 });
258
255
  chmodSync(temporaryPath, 0o600);
@@ -269,7 +266,7 @@ export function createTelegramThreadCleanupWorkStore(options) {
269
266
  catch { /* rename consumed it */ }
270
267
  }
271
268
  };
272
- const mutate = (operation) => withTelegramFileTransaction(`${options.path}.transaction`, () => operation(read()));
269
+ const mutate = (operation) => withTelegramFileTransaction(publicationPaths.transactionPath, () => operation(read()));
273
270
  return {
274
271
  prepare(operationId, candidates) {
275
272
  if (!operationId || candidates.length === 0 || candidates.some(candidate => candidate.profileName !== options.profileName))
@@ -343,7 +340,7 @@ export function createTelegramThreadCleanupWorkStore(options) {
343
340
  });
344
341
  },
345
342
  list() {
346
- return structuredClone(withTelegramFileTransaction(`${options.path}.transaction`, () => read().workSets));
343
+ return structuredClone(withTelegramFileTransaction(publicationPaths.transactionPath, () => read().workSets));
347
344
  },
348
345
  };
349
346
  }
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Telegram Thread manual-name dialog state
3
- * Zones: telegram, thread identity, runtime controls
4
- * Owns one expiring exact-target interaction per session scope.
5
- * Excludes Telegram transport, durable Workspace mutation, and name validation.
2
+ * Telegram Thread naming rules and manual-name interaction
3
+ * Zones: thread identity names, palettes, title formatting, runtime controls
4
+ * Owns name/title value policy and one expiring exact-target dialog per session scope.
5
+ * Excludes occupancy/slot allocation, display-mode projection, transport and API/store effects.
6
6
  */
7
- import type { TelegramTarget } from "./target.ts";
7
+ import { type TelegramTarget } from "./target.ts";
8
8
  export declare const TELEGRAM_THREAD_NAME_DIALOG_TTL_MS: number;
9
9
  export type TelegramThreadNameDialogAction = "reset" | "cancel";
10
10
  export interface TelegramThreadNameDialogCandidate {
@@ -44,3 +44,30 @@ export declare function createTelegramThreadNameDialogRuntime(options?: {
44
44
  clearScope(scope: string): void;
45
45
  inspect(target: TelegramTarget): TelegramThreadNameDialogCandidate | undefined;
46
46
  };
47
+ export interface TelegramThreadTitleInput {
48
+ instanceId: string;
49
+ profileKey: string;
50
+ threadName?: string;
51
+ }
52
+ export interface TelegramThreadNameInput {
53
+ seed: string;
54
+ cwd?: string;
55
+ role?: "leader" | "follower";
56
+ peers?: readonly string[];
57
+ slot?: string;
58
+ }
59
+ export declare function createTelegramThreadName(input: TelegramThreadNameInput): string;
60
+ export declare function normalizeTelegramTopicTargetThreadName(threadName: string): string;
61
+ export declare function getTelegramTopicIdentityName(threadName: string): string;
62
+ export declare function chooseTelegramThreadName(input: {
63
+ slot: string | undefined;
64
+ entropy?: number | string;
65
+ getRandom?: () => number;
66
+ occupied?: readonly string[];
67
+ }): string | undefined;
68
+ export declare function getTelegramThreadNameLeadingSlot(threadName: string | undefined): string | undefined;
69
+ export declare function getTelegramTopicThreadNameValidationError(threadName: string, _slot: string | undefined): string | undefined;
70
+ export declare function getTelegramManualThreadDisplayNameValidationError(threadName: string): string | undefined;
71
+ export declare function isTelegramTopicThreadNameValidForSlot(threadName: string, slot: string | undefined): boolean;
72
+ export declare function getTelegramTopicName(request: TelegramThreadTitleInput, template?: string, slot?: string): string;
73
+ export declare function getTelegramTopicTitleForThreadName(threadName: string, slot: string, template?: string): string;
@@ -1,16 +1,14 @@
1
1
  /**
2
- * Telegram Thread manual-name dialog state
3
- * Zones: telegram, thread identity, runtime controls
4
- * Owns one expiring exact-target interaction per session scope.
5
- * Excludes Telegram transport, durable Workspace mutation, and name validation.
2
+ * Telegram Thread naming rules and manual-name interaction
3
+ * Zones: thread identity names, palettes, title formatting, runtime controls
4
+ * Owns name/title value policy and one expiring exact-target dialog per session scope.
5
+ * Excludes occupancy/slot allocation, display-mode projection, transport and API/store effects.
6
6
  */
7
+ import { areTelegramTargetsEqual as sameTarget } from "./target.js";
7
8
  export const TELEGRAM_THREAD_NAME_DIALOG_TTL_MS = 5 * 60_000;
8
9
  function targetKey(target) {
9
10
  return `${target.chatId}:${target.threadId ?? "chat"}`;
10
11
  }
11
- function sameTarget(left, right) {
12
- return left.chatId === right.chatId && left.threadId === right.threadId;
13
- }
14
12
  function cloneCandidate(candidate) {
15
13
  return { ...candidate, target: { ...candidate.target } };
16
14
  }
@@ -76,3 +74,189 @@ export function createTelegramThreadNameDialogRuntime(options) {
76
74
  },
77
75
  };
78
76
  }
77
+ function hashString(value) {
78
+ let hash = 2166136261;
79
+ for (let index = 0; index < value.length; index += 1) {
80
+ hash ^= value.charCodeAt(index);
81
+ hash = Math.imul(hash, 16777619);
82
+ }
83
+ return hash >>> 0;
84
+ }
85
+ function getWorkspaceHint(cwd) {
86
+ if (!cwd)
87
+ return undefined;
88
+ const parts = cwd.split("/").filter(Boolean);
89
+ const last = parts.at(-1)?.trim();
90
+ if (!last)
91
+ return undefined;
92
+ return (last
93
+ .replace(/[^\p{L}\p{N}._-]+/gu, " ")
94
+ .trim()
95
+ .slice(0, 32) || undefined);
96
+ }
97
+ export function createTelegramThreadName(input) {
98
+ const workspace = getWorkspaceHint(input.cwd);
99
+ const roleMark = input.role === "leader"
100
+ ? "Leader"
101
+ : input.role === "follower"
102
+ ? "Follower"
103
+ : undefined;
104
+ const slot = input.slot ? `Thread ${input.slot}` : undefined;
105
+ const peerSalt = input.peers?.slice().sort().join("|") ?? "";
106
+ const fallback = `Instance ${hashString(`${input.seed}|${input.cwd ?? ""}|${input.role ?? ""}|${peerSalt}|${input.slot ?? ""}`)
107
+ .toString(36)
108
+ .slice(0, 4)}`;
109
+ return ([slot, workspace, roleMark].filter(Boolean).join(" ").slice(0, 96) ||
110
+ fallback);
111
+ }
112
+ export function normalizeTelegramTopicTargetThreadName(threadName) {
113
+ return threadName.replace(/\s+/g, " ").trim().slice(0, 96);
114
+ }
115
+ function getGraphemeSegments(value) {
116
+ const segmenter = Intl.Segmenter;
117
+ if (!segmenter)
118
+ return Array.from(value);
119
+ return Array.from(new segmenter(undefined, { granularity: "grapheme" }).segment(value), (part) => part.segment);
120
+ }
121
+ export function getTelegramTopicIdentityName(threadName) {
122
+ return getGraphemeSegments(normalizeTelegramTopicTargetThreadName(threadName))
123
+ .join("")
124
+ .trim();
125
+ }
126
+ const TELEGRAM_THREAD_NAME_PALETTE = {
127
+ A: ["Atlas", "Aster", "Aurora", "Anchor", "Ashen"],
128
+ B: ["Beacon", "Briar", "Boreal", "Birch", "Bison"],
129
+ C: ["Cedar", "Comet", "Cipher", "Coral", "Cinder"],
130
+ D: ["Delta", "Dawn", "Drift", "Dune", "Dagger"],
131
+ E: ["Ember", "Echo", "Eagle", "Eden", "Elder"],
132
+ F: ["Falcon", "Fjord", "Flint", "Forest", "Fable"],
133
+ G: ["Grove", "Glade", "Glyph", "Garnet", "Gale"],
134
+ H: ["Harbor", "Hawk", "Hazel", "Helix", "Haven"],
135
+ I: ["Iris", "Ivory", "Iron", "Isle", "Idea"],
136
+ J: ["Jade", "Juno", "Jolt", "Jewel", "Jasper"],
137
+ K: ["Kite", "Karma", "Kernel", "Kodiak", "Kelp"],
138
+ L: ["Lumen", "Laurel", "Lynx", "Lotus", "Lagoon"],
139
+ M: ["Maple", "Meteor", "Meadow", "Marble", "Moss"],
140
+ N: ["Nimbus", "Nova", "Nectar", "North", "Noble"],
141
+ O: ["Orion", "Onyx", "Opal", "Orbit", "Olive"],
142
+ P: ["Pine", "Pulse", "Praxis", "Pebble", "Prism"],
143
+ Q: ["Quartz", "Quill", "Quasar", "Quest", "Quiver"],
144
+ R: ["River", "Raven", "Rune", "Reef", "Ridge"],
145
+ S: ["Spruce", "Solar", "Signal", "Stone", "Sable"],
146
+ T: ["Timber", "Talon", "Terra", "Torch", "Tide"],
147
+ U: ["Umber", "Unity", "Ursa", "Uplink", "Ulmus"],
148
+ V: ["Violet", "Vector", "Vista", "Vale", "Vortex"],
149
+ W: ["Willow", "Warden", "Wave", "Winter", "Wisp"],
150
+ X: ["Xenon", "Xylem", "Xavier", "Xylo", "Xerus"],
151
+ Y: ["Yarrow", "Yonder", "Yukon", "Yale", "Yogi"],
152
+ Z: ["Zenith", "Zephyr", "Zircon", "Zebra", "Zion"],
153
+ };
154
+ export function chooseTelegramThreadName(input) {
155
+ if (!input.slot || !/^[A-Z]$/.test(input.slot))
156
+ return undefined;
157
+ const names = TELEGRAM_THREAD_NAME_PALETTE[input.slot];
158
+ if (!names || names.length === 0)
159
+ return undefined;
160
+ const occupied = new Set((input.occupied ?? []).map((name) => getTelegramTopicIdentityName(name)));
161
+ const start = input.getRandom
162
+ ? Math.max(0, Math.min(names.length - 1, Math.floor(input.getRandom() * names.length)))
163
+ : getTelegramThreadNameEntropyIndex(input.entropy, names.length);
164
+ for (let offset = 0; offset < names.length; offset += 1) {
165
+ const name = names[(start + offset) % names.length];
166
+ if (!occupied.has(getTelegramTopicIdentityName(name)))
167
+ return name;
168
+ }
169
+ for (const paletteSlot of "ABCDEFGHIJKLMNOPQRSTUVWXYZ") {
170
+ for (const name of TELEGRAM_THREAD_NAME_PALETTE[paletteSlot] ?? []) {
171
+ if (!occupied.has(getTelegramTopicIdentityName(name)))
172
+ return name;
173
+ }
174
+ }
175
+ return undefined;
176
+ }
177
+ export function getTelegramThreadNameLeadingSlot(threadName) {
178
+ if (!threadName)
179
+ return undefined;
180
+ const first = getTelegramTopicIdentityName(threadName)[0];
181
+ return first && /^[A-Z]$/.test(first) ? first : undefined;
182
+ }
183
+ function getTelegramThreadNameEntropyIndex(entropy, length) {
184
+ if (length <= 1)
185
+ return 0;
186
+ if (typeof entropy === "number" && entropy < 1_000_000_000_000)
187
+ return 0;
188
+ const value = entropy === undefined ? "0" : String(entropy);
189
+ let hash = 2166136261;
190
+ for (const char of value) {
191
+ hash ^= char.codePointAt(0) ?? 0;
192
+ hash = Math.imul(hash, 16777619) >>> 0;
193
+ }
194
+ return hash % length;
195
+ }
196
+ export function getTelegramTopicThreadNameValidationError(threadName, _slot) {
197
+ const identity = getTelegramTopicIdentityName(threadName);
198
+ const reasons = [];
199
+ if (!identity)
200
+ reasons.push("it is empty after trimming");
201
+ if (/\s/.test(identity))
202
+ reasons.push("it contains spaces");
203
+ if (/[^A-Za-z]/.test(identity)) {
204
+ reasons.push("it contains characters outside Latin A-Z letters");
205
+ }
206
+ if (!/^[A-Z]/.test(identity)) {
207
+ reasons.push("it does not start with an uppercase Latin letter");
208
+ }
209
+ const genericLabels = new Set(["telegram", "leader", "follower"]);
210
+ if (genericLabels.has(identity.toLowerCase())) {
211
+ reasons.push("it is a generic role label");
212
+ }
213
+ if (/^[A-Z]$/.test(identity))
214
+ reasons.push("it is only a bare slot letter");
215
+ if (reasons.length === 0)
216
+ return undefined;
217
+ return `Invalid Telegram instance name: ${reasons.join("; ")}. Use exactly one capitalized Latin word with no spaces, punctuation, emoji, non-Latin letters, or digits; it must not be a generic role label or only a bare slot letter.`;
218
+ }
219
+ export function getTelegramManualThreadDisplayNameValidationError(threadName) {
220
+ const trimmed = threadName.trim();
221
+ const normalized = trimmed.replace(/\s+/g, " ");
222
+ const reasons = [];
223
+ if (!trimmed)
224
+ reasons.push("it is empty after trimming");
225
+ if (trimmed && /[^\x20-\x7E]/.test(trimmed)) {
226
+ reasons.push("it contains characters outside printable ASCII");
227
+ }
228
+ if (normalized.length > 96)
229
+ reasons.push("it is longer than 96 characters");
230
+ if (/^[A-Z]$/.test(normalized)) {
231
+ reasons.push("a bare slot letter is reserved for reset to automatic");
232
+ }
233
+ if (reasons.length === 0)
234
+ return undefined;
235
+ return `Invalid Telegram Thread display name: ${reasons.join("; ")}. Use 1–96 printable ASCII characters.`;
236
+ }
237
+ export function isTelegramTopicThreadNameValidForSlot(threadName, slot) {
238
+ return !getTelegramTopicThreadNameValidationError(threadName, slot);
239
+ }
240
+ function applyTopicNameTemplate(template, request, slot) {
241
+ const threadName = request.threadName?.replace(/\s+/g, " ").trim() || request.profileKey;
242
+ let result = template
243
+ .replaceAll("{threadName}", threadName)
244
+ .replaceAll("{profileKey}", request.profileKey)
245
+ .replaceAll("{instanceId}", request.instanceId);
246
+ if (slot)
247
+ result = result.replaceAll("{slot}", slot);
248
+ return result;
249
+ }
250
+ export function getTelegramTopicName(request, template = "{slot}", slot) {
251
+ const name = applyTopicNameTemplate(template, request, slot)
252
+ .replace(/\s+/g, " ")
253
+ .trim();
254
+ return (name || slot || "Pi").slice(0, 128);
255
+ }
256
+ export function getTelegramTopicTitleForThreadName(threadName, slot, template = "{threadName}") {
257
+ return getTelegramTopicName({
258
+ instanceId: "",
259
+ profileKey: normalizeTelegramTopicTargetThreadName(threadName) || "Pi",
260
+ threadName,
261
+ }, template, slot);
262
+ }
@@ -192,6 +192,8 @@ export interface ThreadReconciliationApplyResult {
192
192
  incompleteActions?: ThreadReconciliationAction[];
193
193
  }
194
194
  export interface ThreadReconciliationApplyPorts {
195
+ /** Private-chat temporary tabs support deletion, not forum closing. */
196
+ skipCloseBeforeDelete?: boolean;
195
197
  isCleanupTargetProtected?: (target: ThreadTarget, action: ThreadReconciliationAction) => boolean;
196
198
  callApi?: <TResponse>(method: string, body: Record<string, unknown>) => Promise<TResponse>;
197
199
  markActiveByTarget?: (target: ThreadTarget) => boolean;
@@ -295,7 +295,7 @@ export async function applyThreadReconciliationPlan(plan, ports) {
295
295
  }
296
296
  let deleteConfirmed = false;
297
297
  let superseded = false;
298
- for (const method of ["closeForumTopic", "deleteForumTopic"]) {
298
+ for (const method of ports.skipCloseBeforeDelete ? ["deleteForumTopic"] : ["closeForumTopic", "deleteForumTopic"]) {
299
299
  if (ports.isCleanupTargetProtected?.(action.target, action)) {
300
300
  superseded = true;
301
301
  break;