@llblab/pi-kit 0.29.1 → 0.30.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 (222) hide show
  1. package/BACKLOG.md +3 -1
  2. package/CHANGELOG.md +10 -0
  3. package/README.md +4 -4
  4. package/node_modules/@llblab/pi-state-flow/CHANGELOG.md +12 -0
  5. package/node_modules/@llblab/pi-state-flow/README.md +2 -2
  6. package/node_modules/@llblab/pi-state-flow/dist/lib/context.d.ts +2 -2
  7. package/node_modules/@llblab/pi-state-flow/dist/lib/context.js +17 -8
  8. package/node_modules/@llblab/pi-state-flow/dist/lib/extension.js +7 -3
  9. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.d.ts +2 -1
  10. package/node_modules/@llblab/pi-state-flow/dist/lib/protocol.js +3 -2
  11. package/node_modules/@llblab/pi-state-flow/dist/package.json +1 -1
  12. package/node_modules/@llblab/pi-state-flow/docs/architecture.md +3 -3
  13. package/node_modules/@llblab/pi-state-flow/docs/usage.md +3 -2
  14. package/node_modules/@llblab/pi-state-flow/lib/context.ts +17 -8
  15. package/node_modules/@llblab/pi-state-flow/lib/extension.ts +6 -3
  16. package/node_modules/@llblab/pi-state-flow/lib/protocol.ts +4 -2
  17. package/node_modules/@llblab/pi-state-flow/package.json +1 -1
  18. package/node_modules/@llblab/pi-telegram/AGENTS.md +13 -10
  19. package/node_modules/@llblab/pi-telegram/BACKLOG.md +1 -1
  20. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +23 -0
  21. package/node_modules/@llblab/pi-telegram/README.md +10 -11
  22. package/node_modules/@llblab/pi-telegram/api/commands.ts +3 -0
  23. package/node_modules/@llblab/pi-telegram/dist/api/commands.d.ts +1 -1
  24. package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.d.ts +1 -5
  25. package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.js +15 -42
  26. package/node_modules/@llblab/pi-telegram/dist/lib/activity.d.ts +9 -0
  27. package/node_modules/@llblab/pi-telegram/dist/lib/activity.js +39 -10
  28. package/node_modules/@llblab/pi-telegram/dist/lib/agent-messages.js +7 -3
  29. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +43 -5
  30. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +164 -20
  31. package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.d.ts +12 -0
  32. package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +181 -77
  33. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +111 -31
  34. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +1324 -359
  35. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +41 -91
  36. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +969 -637
  37. package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.js +16 -9
  38. package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +215 -18
  39. package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +1048 -327
  40. package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.d.ts +5 -0
  41. package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.js +434 -261
  42. package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.d.ts +26 -5
  43. package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.js +92 -176
  44. package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +168 -82
  45. package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +1409 -291
  46. package/node_modules/@llblab/pi-telegram/dist/lib/config.d.ts +1 -8
  47. package/node_modules/@llblab/pi-telegram/dist/lib/config.js +34 -76
  48. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.d.ts +2 -2
  49. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.js +16 -16
  50. package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +642 -170
  51. package/node_modules/@llblab/pi-telegram/dist/lib/generative-app-worker.mjs +19 -7
  52. package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.d.ts +0 -5
  53. package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.js +61 -49
  54. package/node_modules/@llblab/pi-telegram/dist/lib/inbound.d.ts +5 -0
  55. package/node_modules/@llblab/pi-telegram/dist/lib/inbound.js +35 -59
  56. package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +40 -4
  57. package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +2102 -760
  58. package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.d.ts +2 -6
  59. package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +75 -58
  60. package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +34 -3
  61. package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +318 -119
  62. package/node_modules/@llblab/pi-telegram/dist/lib/logging.js +50 -14
  63. package/node_modules/@llblab/pi-telegram/dist/lib/media.d.ts +22 -1
  64. package/node_modules/@llblab/pi-telegram/dist/lib/media.js +46 -28
  65. package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.d.ts +19 -5
  66. package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.js +53 -11
  67. package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.d.ts +5 -4
  68. package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.js +45 -63
  69. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.d.ts +5 -4
  70. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.js +114 -78
  71. package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.d.ts +1 -1
  72. package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.js +8 -25
  73. package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.d.ts +6 -1
  74. package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.js +14 -21
  75. package/node_modules/@llblab/pi-telegram/dist/lib/menu.d.ts +8 -61
  76. package/node_modules/@llblab/pi-telegram/dist/lib/menu.js +92 -164
  77. package/node_modules/@llblab/pi-telegram/dist/lib/model.d.ts +1 -1
  78. package/node_modules/@llblab/pi-telegram/dist/lib/model.js +2 -16
  79. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.d.ts +6 -27
  80. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.js +45 -38
  81. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-buttons.js +22 -16
  82. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.d.ts +0 -2
  83. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.js +51 -64
  84. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-voice.js +6 -3
  85. package/node_modules/@llblab/pi-telegram/dist/lib/outbound.d.ts +6 -6
  86. package/node_modules/@llblab/pi-telegram/dist/lib/outbound.js +61 -51
  87. package/node_modules/@llblab/pi-telegram/dist/lib/ownership.js +6 -2
  88. package/node_modules/@llblab/pi-telegram/dist/lib/paths.d.ts +5 -0
  89. package/node_modules/@llblab/pi-telegram/dist/lib/paths.js +37 -21
  90. package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +2 -0
  91. package/node_modules/@llblab/pi-telegram/dist/lib/pi.js +8 -1
  92. package/node_modules/@llblab/pi-telegram/dist/lib/polling.d.ts +23 -37
  93. package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +71 -52
  94. package/node_modules/@llblab/pi-telegram/dist/lib/preview.js +39 -19
  95. package/node_modules/@llblab/pi-telegram/dist/lib/process-identity.d.ts +24 -0
  96. package/node_modules/@llblab/pi-telegram/dist/lib/process-identity.js +133 -0
  97. package/node_modules/@llblab/pi-telegram/dist/lib/prompts.js +2 -7
  98. package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +16 -16
  99. package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +261 -116
  100. package/node_modules/@llblab/pi-telegram/dist/lib/recovery.d.ts +2 -83
  101. package/node_modules/@llblab/pi-telegram/dist/lib/recovery.js +20 -274
  102. package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +16 -24
  103. package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +99 -116
  104. package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +121 -7
  105. package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +4750 -1161
  106. package/node_modules/@llblab/pi-telegram/dist/lib/runtime.d.ts +1 -11
  107. package/node_modules/@llblab/pi-telegram/dist/lib/sections.d.ts +4 -29
  108. package/node_modules/@llblab/pi-telegram/dist/lib/sections.js +7 -20
  109. package/node_modules/@llblab/pi-telegram/dist/lib/skills.js +4 -3
  110. package/node_modules/@llblab/pi-telegram/dist/lib/status.js +86 -57
  111. package/node_modules/@llblab/pi-telegram/dist/lib/sync.d.ts +6 -3
  112. package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +112 -45
  113. package/node_modules/@llblab/pi-telegram/dist/lib/target.d.ts +4 -0
  114. package/node_modules/@llblab/pi-telegram/dist/lib/target.js +18 -0
  115. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +18 -11
  116. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +165 -49
  117. package/node_modules/@llblab/pi-telegram/dist/lib/text-groups.js +4 -39
  118. package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.d.ts +6 -27
  119. package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.js +369 -186
  120. package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.d.ts +1 -2
  121. package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.js +58 -38
  122. package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.d.ts +28 -8
  123. package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.js +175 -66
  124. package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.d.ts +42 -16
  125. package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.js +90 -50
  126. package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +88 -24
  127. package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +2276 -842
  128. package/node_modules/@llblab/pi-telegram/dist/lib/time-injection.js +1 -2
  129. package/node_modules/@llblab/pi-telegram/dist/lib/turns.js +1 -19
  130. package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +178 -82
  131. package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +2438 -645
  132. package/node_modules/@llblab/pi-telegram/dist/lib/voice.d.ts +7 -22
  133. package/node_modules/@llblab/pi-telegram/dist/lib/voice.js +7 -22
  134. package/node_modules/@llblab/pi-telegram/dist/lib/wire.js +1 -1
  135. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.d.ts +3 -1
  136. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.js +425 -211
  137. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-identity.js +8 -13
  138. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +1 -1
  139. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +328 -140
  140. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-slots.js +8 -4
  141. package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
  142. package/node_modules/@llblab/pi-telegram/docs/architecture.md +43 -35
  143. package/node_modules/@llblab/pi-telegram/docs/inbound.md +1 -1
  144. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +15 -3
  145. package/node_modules/@llblab/pi-telegram/docs/outbound.md +1 -1
  146. package/node_modules/@llblab/pi-telegram/docs/public-api.md +29 -1
  147. package/node_modules/@llblab/pi-telegram/docs/ui-style.md +25 -8
  148. package/node_modules/@llblab/pi-telegram/lib/activity-verbosity.ts +59 -82
  149. package/node_modules/@llblab/pi-telegram/lib/activity.ts +146 -102
  150. package/node_modules/@llblab/pi-telegram/lib/agent-messages.ts +12 -5
  151. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +390 -82
  152. package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +262 -99
  153. package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +2440 -601
  154. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +2209 -1290
  155. package/node_modules/@llblab/pi-telegram/lib/bus-transport.ts +46 -12
  156. package/node_modules/@llblab/pi-telegram/lib/bus.ts +1673 -444
  157. package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +848 -327
  158. package/node_modules/@llblab/pi-telegram/lib/command-templates.ts +152 -265
  159. package/node_modules/@llblab/pi-telegram/lib/commands.ts +2398 -555
  160. package/node_modules/@llblab/pi-telegram/lib/config.ts +177 -170
  161. package/node_modules/@llblab/pi-telegram/lib/delivery.ts +41 -29
  162. package/node_modules/@llblab/pi-telegram/lib/extension.ts +1209 -431
  163. package/node_modules/@llblab/pi-telegram/lib/generative-app-worker.mjs +19 -7
  164. package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +324 -211
  165. package/node_modules/@llblab/pi-telegram/lib/inbound.ts +95 -97
  166. package/node_modules/@llblab/pi-telegram/lib/journal.ts +5329 -1712
  167. package/node_modules/@llblab/pi-telegram/lib/keyboard.ts +1 -3
  168. package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +128 -78
  169. package/node_modules/@llblab/pi-telegram/lib/locks.ts +703 -212
  170. package/node_modules/@llblab/pi-telegram/lib/logging.ts +147 -58
  171. package/node_modules/@llblab/pi-telegram/lib/media.ts +82 -43
  172. package/node_modules/@llblab/pi-telegram/lib/menu-model.ts +85 -18
  173. package/node_modules/@llblab/pi-telegram/lib/menu-queue.ts +88 -129
  174. package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +260 -155
  175. package/node_modules/@llblab/pi-telegram/lib/menu-status.ts +20 -60
  176. package/node_modules/@llblab/pi-telegram/lib/menu-thinking.ts +41 -56
  177. package/node_modules/@llblab/pi-telegram/lib/menu.ts +112 -285
  178. package/node_modules/@llblab/pi-telegram/lib/model.ts +8 -22
  179. package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +156 -125
  180. package/node_modules/@llblab/pi-telegram/lib/outbound-buttons.ts +76 -49
  181. package/node_modules/@llblab/pi-telegram/lib/outbound-markup.ts +96 -94
  182. package/node_modules/@llblab/pi-telegram/lib/outbound-voice.ts +33 -27
  183. package/node_modules/@llblab/pi-telegram/lib/outbound.ts +125 -125
  184. package/node_modules/@llblab/pi-telegram/lib/ownership.ts +19 -10
  185. package/node_modules/@llblab/pi-telegram/lib/paths.ts +148 -49
  186. package/node_modules/@llblab/pi-telegram/lib/pi.ts +35 -15
  187. package/node_modules/@llblab/pi-telegram/lib/polling.ts +165 -149
  188. package/node_modules/@llblab/pi-telegram/lib/preview.ts +93 -40
  189. package/node_modules/@llblab/pi-telegram/lib/process-identity.ts +164 -0
  190. package/node_modules/@llblab/pi-telegram/lib/prompts.ts +4 -11
  191. package/node_modules/@llblab/pi-telegram/lib/queue.ts +523 -289
  192. package/node_modules/@llblab/pi-telegram/lib/recovery.ts +50 -454
  193. package/node_modules/@llblab/pi-telegram/lib/rendering.ts +1 -3
  194. package/node_modules/@llblab/pi-telegram/lib/replies.ts +248 -209
  195. package/node_modules/@llblab/pi-telegram/lib/routing.ts +8527 -2149
  196. package/node_modules/@llblab/pi-telegram/lib/runtime.ts +1 -12
  197. package/node_modules/@llblab/pi-telegram/lib/sections.ts +24 -89
  198. package/node_modules/@llblab/pi-telegram/lib/setup.ts +1 -3
  199. package/node_modules/@llblab/pi-telegram/lib/skills.ts +9 -4
  200. package/node_modules/@llblab/pi-telegram/lib/status.ts +215 -110
  201. package/node_modules/@llblab/pi-telegram/lib/sync.ts +488 -359
  202. package/node_modules/@llblab/pi-telegram/lib/target.ts +22 -3
  203. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +322 -122
  204. package/node_modules/@llblab/pi-telegram/lib/text-groups.ts +5 -39
  205. package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +833 -331
  206. package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +213 -97
  207. package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +232 -87
  208. package/node_modules/@llblab/pi-telegram/lib/thread-reconciler.ts +155 -72
  209. package/node_modules/@llblab/pi-telegram/lib/threads.ts +5329 -1817
  210. package/node_modules/@llblab/pi-telegram/lib/time-injection.ts +1 -4
  211. package/node_modules/@llblab/pi-telegram/lib/turns.ts +15 -28
  212. package/node_modules/@llblab/pi-telegram/lib/updates.ts +5194 -1600
  213. package/node_modules/@llblab/pi-telegram/lib/voice.ts +7 -35
  214. package/node_modules/@llblab/pi-telegram/lib/wire.ts +1 -1
  215. package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +996 -416
  216. package/node_modules/@llblab/pi-telegram/lib/workspace-identity.ts +14 -17
  217. package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +878 -376
  218. package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +22 -13
  219. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  220. package/package.json +3 -3
  221. package/node_modules/@llblab/pi-telegram/scripts/check-downgrade.mjs +0 -472
  222. package/node_modules/@llblab/pi-telegram/scripts/measure-workspace.mjs +0 -108
@@ -5,7 +5,10 @@
5
5
  * Excludes occupancy/slot allocation, display-mode projection, transport and API/store effects.
6
6
  */
7
7
 
8
- import { areTelegramTargetsEqual as sameTarget, type TelegramTarget } from "./target.ts";
8
+ import {
9
+ areTelegramTargetsEqual as sameTarget,
10
+ type TelegramTarget,
11
+ } from "./target.ts";
9
12
 
10
13
  export const TELEGRAM_THREAD_NAME_DIALOG_TTL_MS = 5 * 60_000;
11
14
 
@@ -29,10 +32,39 @@ function cloneCandidate(
29
32
  return { ...candidate, target: { ...candidate.target } };
30
33
  }
31
34
 
35
+ export interface TelegramThreadNameDialogLifetime {
36
+ /** Independent recipient effect authority; never the input handle's deadline or ownership. */
37
+ readonly assertAuthority?: () => void;
38
+ isCurrent(): boolean;
39
+ publish(
40
+ dialogMessageId: number,
41
+ assertPublicationCurrent?: () => void,
42
+ ): TelegramThreadNameDialogCandidate | undefined;
43
+ select(action: TelegramThreadNameDialogAction): {
44
+ kind: "reset" | "cancel" | "expired";
45
+ };
46
+ consumeName(
47
+ text: string,
48
+ ): { kind: "name"; name: string } | { kind: "none" | "empty" };
49
+ reopen(): TelegramThreadNameDialogCandidate | undefined;
50
+ finish(): void;
51
+ }
52
+
32
53
  export function createTelegramThreadNameDialogRuntime(options?: {
33
54
  ttlMs?: number;
34
55
  nowMs?: () => number;
35
56
  }): {
57
+ prepare(input: {
58
+ scope: string;
59
+ target: TelegramTarget;
60
+ isCurrent?: () => boolean;
61
+ assertAuthority?: () => void;
62
+ }): TelegramThreadNameDialogLifetime | undefined;
63
+ capture(input: {
64
+ scope: string;
65
+ target: TelegramTarget;
66
+ dialogMessageId: number;
67
+ }): TelegramThreadNameDialogLifetime | undefined;
36
68
  open(input: {
37
69
  scope: string;
38
70
  target: TelegramTarget;
@@ -50,65 +82,231 @@ export function createTelegramThreadNameDialogRuntime(options?: {
50
82
  text: string;
51
83
  }): { kind: "name"; name: string } | { kind: "none" | "empty" };
52
84
  clearScope(scope: string): void;
53
- inspect(target: TelegramTarget): TelegramThreadNameDialogCandidate | undefined;
85
+ inspect(
86
+ target: TelegramTarget,
87
+ ): TelegramThreadNameDialogCandidate | undefined;
54
88
  } {
55
89
  const ttlMs = options?.ttlMs ?? TELEGRAM_THREAD_NAME_DIALOG_TTL_MS;
56
90
  const nowMs = options?.nowMs ?? Date.now;
57
- const candidates = new Map<string, TelegramThreadNameDialogCandidate>();
91
+ interface Entry {
92
+ scope: string;
93
+ target: TelegramTarget;
94
+ dialogMessageId?: number;
95
+ phase: "delivery" | "input" | "consumed";
96
+ expiresAtMs: number;
97
+ isCurrent?: () => boolean;
98
+ assertAuthority?: () => void;
99
+ consumedBy?: object;
100
+ }
101
+ const candidates = new Map<string, Entry>();
102
+ const recipientCurrent = (entry: Entry): boolean => {
103
+ try {
104
+ return entry.isCurrent ? entry.isCurrent() === true : true;
105
+ } catch {
106
+ return false;
107
+ }
108
+ };
109
+ const view = (entry: Entry): TelegramThreadNameDialogCandidate =>
110
+ cloneCandidate({
111
+ scope: entry.scope,
112
+ target: entry.target,
113
+ dialogMessageId: entry.dialogMessageId!,
114
+ phase: "input",
115
+ expiresAtMs: entry.expiresAtMs,
116
+ });
117
+ const createLifetime = (entry: Entry): TelegramThreadNameDialogLifetime => {
118
+ const key = targetKey(entry.target);
119
+ const claim = {};
120
+ let ended = false;
121
+ const finish = () => {
122
+ ended = true;
123
+ if (
124
+ candidates.get(key) === entry &&
125
+ (!entry.consumedBy || entry.consumedBy === claim)
126
+ )
127
+ candidates.delete(key);
128
+ };
129
+ const isCurrent = () => {
130
+ if (
131
+ ended ||
132
+ candidates.get(key) !== entry ||
133
+ (entry.consumedBy && entry.consumedBy !== claim)
134
+ ) {
135
+ ended = true;
136
+ return false;
137
+ }
138
+ const current = entry.expiresAtMs > nowMs() && recipientCurrent(entry);
139
+ // Supplied authority/clock observations may synchronously replace this dialog.
140
+ if (
141
+ !current ||
142
+ entry.expiresAtMs <= nowMs() ||
143
+ candidates.get(key) !== entry ||
144
+ (entry.consumedBy && entry.consumedBy !== claim)
145
+ ) {
146
+ finish();
147
+ return false;
148
+ }
149
+ return true;
150
+ };
151
+ return {
152
+ assertAuthority: entry.assertAuthority,
153
+ isCurrent,
154
+ publish(dialogMessageId, assertPublicationCurrent) {
155
+ if (!isCurrent() || entry.phase !== "delivery") return undefined;
156
+ // Source authority is needed for this publication only, never retained by future input/effects.
157
+ if (assertPublicationCurrent) {
158
+ assertPublicationCurrent();
159
+ if (entry.expiresAtMs <= nowMs()) return undefined;
160
+ assertPublicationCurrent();
161
+ if (
162
+ ended ||
163
+ candidates.get(key) !== entry ||
164
+ entry.phase !== "delivery"
165
+ )
166
+ return undefined;
167
+ }
168
+ if (!Number.isSafeInteger(dialogMessageId) || dialogMessageId <= 0) {
169
+ finish();
170
+ return undefined;
171
+ }
172
+ entry.dialogMessageId = dialogMessageId;
173
+ entry.phase = "input";
174
+ return view(entry);
175
+ },
176
+ select(action) {
177
+ if (!isCurrent() || entry.phase !== "input") return { kind: "expired" };
178
+ if (action === "cancel") finish();
179
+ else {
180
+ entry.phase = "consumed";
181
+ entry.consumedBy = claim;
182
+ }
183
+ return { kind: action };
184
+ },
185
+ consumeName(text) {
186
+ if (!isCurrent() || entry.phase !== "input") return { kind: "none" };
187
+ const name = text.trim();
188
+ if (!name) return { kind: "empty" };
189
+ entry.phase = "consumed";
190
+ entry.consumedBy = claim;
191
+ return { kind: "name", name };
192
+ },
193
+ reopen() {
194
+ if (
195
+ !isCurrent() ||
196
+ entry.phase !== "consumed" ||
197
+ entry.consumedBy !== claim
198
+ ) {
199
+ return undefined;
200
+ }
201
+ // A failed effect may restore input, never renew its deadline or issued handle.
202
+ const replacement: Entry = {
203
+ ...entry,
204
+ phase: "input",
205
+ consumedBy: undefined,
206
+ };
207
+ candidates.set(key, replacement);
208
+ ended = true;
209
+ return view(replacement);
210
+ },
211
+ finish,
212
+ };
213
+ };
58
214
  const current = (
59
215
  scope: string,
60
216
  target: TelegramTarget,
61
- ): TelegramThreadNameDialogCandidate | undefined => {
62
- const key = targetKey(target);
63
- const candidate = candidates.get(key);
64
- if (!candidate || candidate.scope !== scope ||
65
- !sameTarget(candidate.target, target)) return undefined;
66
- if (candidate.expiresAtMs <= nowMs()) {
67
- candidates.delete(key);
217
+ ): Entry | undefined => {
218
+ const entry = candidates.get(targetKey(target));
219
+ if (
220
+ !entry ||
221
+ entry.scope !== scope ||
222
+ entry.phase !== "input" ||
223
+ !sameTarget(entry.target, target) ||
224
+ !createLifetime(entry).isCurrent()
225
+ )
68
226
  return undefined;
69
- }
70
- return candidate;
227
+ return entry;
228
+ };
229
+ const capture = (input: {
230
+ scope: string;
231
+ target: TelegramTarget;
232
+ dialogMessageId: number;
233
+ }): TelegramThreadNameDialogLifetime | undefined => {
234
+ const { scope, dialogMessageId } = input;
235
+ const target = { ...input.target };
236
+ const entry = current(scope, target);
237
+ if (!entry || entry.dialogMessageId !== dialogMessageId) return undefined;
238
+ return createLifetime(entry);
71
239
  };
72
240
  return {
241
+ prepare(input) {
242
+ const scope = input.scope;
243
+ const target = { ...input.target };
244
+ const isCurrent = input.isCurrent,
245
+ assertAuthority = input.assertAuthority;
246
+ const key = targetKey(target);
247
+ const previous = candidates.get(key);
248
+ const entry: Entry = {
249
+ scope,
250
+ target,
251
+ phase: "delivery",
252
+ expiresAtMs: nowMs() + ttlMs,
253
+ isCurrent,
254
+ assertAuthority,
255
+ };
256
+ if (
257
+ !recipientCurrent(entry) ||
258
+ entry.expiresAtMs <= nowMs() ||
259
+ candidates.get(key) !== previous
260
+ )
261
+ return undefined;
262
+ candidates.set(key, entry);
263
+ return createLifetime(entry);
264
+ },
265
+ capture,
73
266
  open(input) {
74
- const candidate: TelegramThreadNameDialogCandidate = {
267
+ const entry: Entry = {
75
268
  scope: input.scope,
76
269
  target: { ...input.target },
77
- dialogMessageId: input.dialogMessageId,
78
270
  phase: "input",
271
+ dialogMessageId: input.dialogMessageId,
79
272
  expiresAtMs: nowMs() + ttlMs,
80
273
  };
81
- candidates.set(targetKey(input.target), candidate);
82
- return cloneCandidate(candidate);
274
+ candidates.set(targetKey(entry.target), entry);
275
+ return view(entry);
83
276
  },
84
277
  select(input) {
85
- const candidate = current(input.scope, input.target);
86
- if (!candidate || candidate.dialogMessageId !== input.dialogMessageId) {
87
- return { kind: "expired" };
88
- }
89
- candidates.delete(targetKey(input.target));
90
- return { kind: input.action };
278
+ const action = input.action;
279
+ const lifetime = capture(input);
280
+ if (!lifetime) return { kind: "expired" };
281
+ const result = lifetime.select(action);
282
+ lifetime.finish();
283
+ return result;
91
284
  },
92
285
  consumeName(input) {
93
- const candidate = current(input.scope, input.target);
94
- if (!candidate || candidate.phase !== "input") return { kind: "none" };
95
- const name = input.text.trim();
96
- if (!name) return { kind: "empty" };
97
- candidates.delete(targetKey(input.target));
98
- return { kind: "name", name };
286
+ const text = input.text;
287
+ const entry = current(input.scope, input.target);
288
+ if (!entry) return { kind: "none" };
289
+ const lifetime = createLifetime(entry);
290
+ const result = lifetime.consumeName(text);
291
+ if (result.kind === "name") lifetime.finish();
292
+ return result;
99
293
  },
100
294
  clearScope(scope) {
101
- for (const [key, candidate] of candidates) {
102
- if (candidate.scope === scope) candidates.delete(key);
295
+ for (const [key, entry] of candidates) {
296
+ if (entry.scope === scope) candidates.delete(key);
103
297
  }
104
298
  },
105
299
  inspect(target) {
106
- const candidate = candidates.get(targetKey(target));
107
- if (!candidate || candidate.expiresAtMs <= nowMs()) {
108
- candidates.delete(targetKey(target));
300
+ const key = targetKey(target);
301
+ const entry = candidates.get(key);
302
+ if (!entry) return undefined;
303
+ if (entry.expiresAtMs <= nowMs()) {
304
+ if (candidates.get(key) === entry) candidates.delete(key);
109
305
  return undefined;
110
306
  }
111
- return cloneCandidate(candidate);
307
+ if (entry.phase !== "input" || !createLifetime(entry).isCurrent())
308
+ return undefined;
309
+ return view(entry);
112
310
  },
113
311
  };
114
312
  }
@@ -119,59 +317,6 @@ export interface TelegramThreadTitleInput {
119
317
  threadName?: string;
120
318
  }
121
319
 
122
- export interface TelegramThreadNameInput {
123
- seed: string;
124
- cwd?: string;
125
- role?: "leader" | "follower";
126
- peers?: readonly string[];
127
- slot?: string;
128
- }
129
-
130
- function hashString(value: string): number {
131
- let hash = 2166136261;
132
- for (let index = 0; index < value.length; index += 1) {
133
- hash ^= value.charCodeAt(index);
134
- hash = Math.imul(hash, 16777619);
135
- }
136
- return hash >>> 0;
137
- }
138
-
139
- function getWorkspaceHint(cwd: string | undefined): string | undefined {
140
- if (!cwd) return undefined;
141
- const parts = cwd.split("/").filter(Boolean);
142
- const last = parts.at(-1)?.trim();
143
- if (!last) return undefined;
144
- return (
145
- last
146
- .replace(/[^\p{L}\p{N}._-]+/gu, " ")
147
- .trim()
148
- .slice(0, 32) || undefined
149
- );
150
- }
151
-
152
- export function createTelegramThreadName(
153
- input: TelegramThreadNameInput,
154
- ): string {
155
- const workspace = getWorkspaceHint(input.cwd);
156
- const roleMark =
157
- input.role === "leader"
158
- ? "Leader"
159
- : input.role === "follower"
160
- ? "Follower"
161
- : undefined;
162
- const slot = input.slot ? `Thread ${input.slot}` : undefined;
163
- const peerSalt = input.peers?.slice().sort().join("|") ?? "";
164
- const fallback = `Instance ${hashString(
165
- `${input.seed}|${input.cwd ?? ""}|${input.role ?? ""}|${peerSalt}|${input.slot ?? ""}`,
166
- )
167
- .toString(36)
168
- .slice(0, 4)}`;
169
- return (
170
- [slot, workspace, roleMark].filter(Boolean).join(" ").slice(0, 96) ||
171
- fallback
172
- );
173
- }
174
-
175
320
  export function normalizeTelegramTopicTargetThreadName(
176
321
  threadName: string,
177
322
  ): string {