@llblab/pi-kit 0.29.2 → 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 (208) hide show
  1. package/BACKLOG.md +2 -1
  2. package/CHANGELOG.md +5 -0
  3. package/README.md +2 -2
  4. package/node_modules/@llblab/pi-telegram/AGENTS.md +9 -7
  5. package/node_modules/@llblab/pi-telegram/BACKLOG.md +1 -18
  6. package/node_modules/@llblab/pi-telegram/CHANGELOG.md +11 -0
  7. package/node_modules/@llblab/pi-telegram/README.md +7 -11
  8. package/node_modules/@llblab/pi-telegram/api/commands.ts +3 -0
  9. package/node_modules/@llblab/pi-telegram/dist/api/commands.d.ts +1 -1
  10. package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.d.ts +1 -5
  11. package/node_modules/@llblab/pi-telegram/dist/lib/activity-verbosity.js +15 -42
  12. package/node_modules/@llblab/pi-telegram/dist/lib/activity.d.ts +9 -0
  13. package/node_modules/@llblab/pi-telegram/dist/lib/activity.js +39 -10
  14. package/node_modules/@llblab/pi-telegram/dist/lib/agent-messages.js +7 -3
  15. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +43 -5
  16. package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +163 -20
  17. package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.d.ts +12 -0
  18. package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +182 -79
  19. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.d.ts +111 -31
  20. package/node_modules/@llblab/pi-telegram/dist/lib/bus-follower.js +1324 -359
  21. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.d.ts +41 -91
  22. package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +969 -637
  23. package/node_modules/@llblab/pi-telegram/dist/lib/bus-transport.js +16 -9
  24. package/node_modules/@llblab/pi-telegram/dist/lib/bus.d.ts +215 -18
  25. package/node_modules/@llblab/pi-telegram/dist/lib/bus.js +1048 -327
  26. package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.d.ts +5 -0
  27. package/node_modules/@llblab/pi-telegram/dist/lib/channel-posts.js +434 -261
  28. package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.d.ts +26 -5
  29. package/node_modules/@llblab/pi-telegram/dist/lib/command-templates.js +92 -176
  30. package/node_modules/@llblab/pi-telegram/dist/lib/commands.d.ts +168 -82
  31. package/node_modules/@llblab/pi-telegram/dist/lib/commands.js +1409 -291
  32. package/node_modules/@llblab/pi-telegram/dist/lib/config.d.ts +1 -8
  33. package/node_modules/@llblab/pi-telegram/dist/lib/config.js +34 -76
  34. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.d.ts +2 -2
  35. package/node_modules/@llblab/pi-telegram/dist/lib/delivery.js +16 -16
  36. package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +639 -171
  37. package/node_modules/@llblab/pi-telegram/dist/lib/generative-app-worker.mjs +19 -7
  38. package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.d.ts +0 -5
  39. package/node_modules/@llblab/pi-telegram/dist/lib/generative-apps.js +61 -49
  40. package/node_modules/@llblab/pi-telegram/dist/lib/inbound.d.ts +5 -0
  41. package/node_modules/@llblab/pi-telegram/dist/lib/inbound.js +35 -59
  42. package/node_modules/@llblab/pi-telegram/dist/lib/journal.d.ts +33 -3
  43. package/node_modules/@llblab/pi-telegram/dist/lib/journal.js +2063 -782
  44. package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.d.ts +2 -6
  45. package/node_modules/@llblab/pi-telegram/dist/lib/lifecycle.js +75 -58
  46. package/node_modules/@llblab/pi-telegram/dist/lib/locks.d.ts +29 -3
  47. package/node_modules/@llblab/pi-telegram/dist/lib/locks.js +289 -108
  48. package/node_modules/@llblab/pi-telegram/dist/lib/logging.js +50 -14
  49. package/node_modules/@llblab/pi-telegram/dist/lib/media.d.ts +22 -1
  50. package/node_modules/@llblab/pi-telegram/dist/lib/media.js +46 -28
  51. package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.d.ts +19 -5
  52. package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.js +53 -11
  53. package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.d.ts +5 -4
  54. package/node_modules/@llblab/pi-telegram/dist/lib/menu-queue.js +45 -63
  55. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.d.ts +5 -4
  56. package/node_modules/@llblab/pi-telegram/dist/lib/menu-settings.js +114 -78
  57. package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.d.ts +1 -1
  58. package/node_modules/@llblab/pi-telegram/dist/lib/menu-status.js +8 -25
  59. package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.d.ts +6 -1
  60. package/node_modules/@llblab/pi-telegram/dist/lib/menu-thinking.js +14 -21
  61. package/node_modules/@llblab/pi-telegram/dist/lib/menu.d.ts +8 -61
  62. package/node_modules/@llblab/pi-telegram/dist/lib/menu.js +92 -164
  63. package/node_modules/@llblab/pi-telegram/dist/lib/model.d.ts +1 -1
  64. package/node_modules/@llblab/pi-telegram/dist/lib/model.js +2 -16
  65. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.d.ts +6 -27
  66. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-attachments.js +45 -38
  67. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-buttons.js +22 -16
  68. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.d.ts +0 -2
  69. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-markup.js +51 -64
  70. package/node_modules/@llblab/pi-telegram/dist/lib/outbound-voice.js +6 -3
  71. package/node_modules/@llblab/pi-telegram/dist/lib/outbound.d.ts +6 -6
  72. package/node_modules/@llblab/pi-telegram/dist/lib/outbound.js +61 -51
  73. package/node_modules/@llblab/pi-telegram/dist/lib/ownership.js +6 -2
  74. package/node_modules/@llblab/pi-telegram/dist/lib/paths.d.ts +5 -0
  75. package/node_modules/@llblab/pi-telegram/dist/lib/paths.js +37 -21
  76. package/node_modules/@llblab/pi-telegram/dist/lib/pi.d.ts +2 -0
  77. package/node_modules/@llblab/pi-telegram/dist/lib/pi.js +8 -1
  78. package/node_modules/@llblab/pi-telegram/dist/lib/polling.d.ts +21 -37
  79. package/node_modules/@llblab/pi-telegram/dist/lib/polling.js +59 -50
  80. package/node_modules/@llblab/pi-telegram/dist/lib/preview.js +39 -19
  81. package/node_modules/@llblab/pi-telegram/dist/lib/process-identity.d.ts +24 -0
  82. package/node_modules/@llblab/pi-telegram/dist/lib/process-identity.js +133 -0
  83. package/node_modules/@llblab/pi-telegram/dist/lib/prompts.js +2 -7
  84. package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +14 -16
  85. package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +249 -126
  86. package/node_modules/@llblab/pi-telegram/dist/lib/recovery.d.ts +2 -83
  87. package/node_modules/@llblab/pi-telegram/dist/lib/recovery.js +20 -274
  88. package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +16 -24
  89. package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +99 -116
  90. package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +120 -7
  91. package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +4667 -1152
  92. package/node_modules/@llblab/pi-telegram/dist/lib/runtime.d.ts +1 -11
  93. package/node_modules/@llblab/pi-telegram/dist/lib/sections.d.ts +4 -29
  94. package/node_modules/@llblab/pi-telegram/dist/lib/sections.js +7 -20
  95. package/node_modules/@llblab/pi-telegram/dist/lib/skills.js +4 -3
  96. package/node_modules/@llblab/pi-telegram/dist/lib/status.js +86 -57
  97. package/node_modules/@llblab/pi-telegram/dist/lib/sync.d.ts +1 -3
  98. package/node_modules/@llblab/pi-telegram/dist/lib/sync.js +67 -43
  99. package/node_modules/@llblab/pi-telegram/dist/lib/target.d.ts +4 -0
  100. package/node_modules/@llblab/pi-telegram/dist/lib/target.js +18 -0
  101. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.d.ts +18 -13
  102. package/node_modules/@llblab/pi-telegram/dist/lib/telegram-api.js +148 -53
  103. package/node_modules/@llblab/pi-telegram/dist/lib/text-groups.js +4 -39
  104. package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.d.ts +6 -27
  105. package/node_modules/@llblab/pi-telegram/dist/lib/thread-cleanup-manager.js +369 -186
  106. package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.d.ts +1 -2
  107. package/node_modules/@llblab/pi-telegram/dist/lib/thread-display.js +58 -38
  108. package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.d.ts +28 -8
  109. package/node_modules/@llblab/pi-telegram/dist/lib/thread-naming.js +175 -66
  110. package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.d.ts +42 -16
  111. package/node_modules/@llblab/pi-telegram/dist/lib/thread-reconciler.js +90 -50
  112. package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +82 -20
  113. package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +2247 -855
  114. package/node_modules/@llblab/pi-telegram/dist/lib/time-injection.js +1 -2
  115. package/node_modules/@llblab/pi-telegram/dist/lib/turns.js +1 -19
  116. package/node_modules/@llblab/pi-telegram/dist/lib/updates.d.ts +169 -80
  117. package/node_modules/@llblab/pi-telegram/dist/lib/updates.js +2418 -641
  118. package/node_modules/@llblab/pi-telegram/dist/lib/voice.d.ts +7 -22
  119. package/node_modules/@llblab/pi-telegram/dist/lib/voice.js +7 -22
  120. package/node_modules/@llblab/pi-telegram/dist/lib/wire.js +1 -1
  121. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.d.ts +3 -1
  122. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-admission.js +425 -211
  123. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-identity.js +8 -13
  124. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.d.ts +1 -1
  125. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-retirement.js +328 -140
  126. package/node_modules/@llblab/pi-telegram/dist/lib/workspace-slots.js +8 -4
  127. package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
  128. package/node_modules/@llblab/pi-telegram/docs/architecture.md +31 -29
  129. package/node_modules/@llblab/pi-telegram/docs/inbound.md +1 -1
  130. package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +15 -3
  131. package/node_modules/@llblab/pi-telegram/docs/outbound.md +1 -1
  132. package/node_modules/@llblab/pi-telegram/docs/public-api.md +29 -1
  133. package/node_modules/@llblab/pi-telegram/docs/ui-style.md +25 -8
  134. package/node_modules/@llblab/pi-telegram/lib/activity-verbosity.ts +59 -82
  135. package/node_modules/@llblab/pi-telegram/lib/activity.ts +146 -102
  136. package/node_modules/@llblab/pi-telegram/lib/agent-messages.ts +12 -5
  137. package/node_modules/@llblab/pi-telegram/lib/bindings.ts +389 -82
  138. package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +263 -102
  139. package/node_modules/@llblab/pi-telegram/lib/bus-follower.ts +2440 -601
  140. package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +2209 -1290
  141. package/node_modules/@llblab/pi-telegram/lib/bus-transport.ts +46 -12
  142. package/node_modules/@llblab/pi-telegram/lib/bus.ts +1673 -444
  143. package/node_modules/@llblab/pi-telegram/lib/channel-posts.ts +848 -327
  144. package/node_modules/@llblab/pi-telegram/lib/command-templates.ts +152 -265
  145. package/node_modules/@llblab/pi-telegram/lib/commands.ts +2398 -555
  146. package/node_modules/@llblab/pi-telegram/lib/config.ts +177 -170
  147. package/node_modules/@llblab/pi-telegram/lib/delivery.ts +41 -29
  148. package/node_modules/@llblab/pi-telegram/lib/extension.ts +1206 -433
  149. package/node_modules/@llblab/pi-telegram/lib/generative-app-worker.mjs +19 -7
  150. package/node_modules/@llblab/pi-telegram/lib/generative-apps.ts +324 -211
  151. package/node_modules/@llblab/pi-telegram/lib/inbound.ts +95 -97
  152. package/node_modules/@llblab/pi-telegram/lib/journal.ts +5311 -1763
  153. package/node_modules/@llblab/pi-telegram/lib/keyboard.ts +1 -3
  154. package/node_modules/@llblab/pi-telegram/lib/lifecycle.ts +128 -78
  155. package/node_modules/@llblab/pi-telegram/lib/locks.ts +677 -203
  156. package/node_modules/@llblab/pi-telegram/lib/logging.ts +147 -58
  157. package/node_modules/@llblab/pi-telegram/lib/media.ts +82 -43
  158. package/node_modules/@llblab/pi-telegram/lib/menu-model.ts +85 -18
  159. package/node_modules/@llblab/pi-telegram/lib/menu-queue.ts +88 -129
  160. package/node_modules/@llblab/pi-telegram/lib/menu-settings.ts +260 -155
  161. package/node_modules/@llblab/pi-telegram/lib/menu-status.ts +20 -60
  162. package/node_modules/@llblab/pi-telegram/lib/menu-thinking.ts +41 -56
  163. package/node_modules/@llblab/pi-telegram/lib/menu.ts +112 -285
  164. package/node_modules/@llblab/pi-telegram/lib/model.ts +8 -22
  165. package/node_modules/@llblab/pi-telegram/lib/outbound-attachments.ts +156 -125
  166. package/node_modules/@llblab/pi-telegram/lib/outbound-buttons.ts +76 -49
  167. package/node_modules/@llblab/pi-telegram/lib/outbound-markup.ts +96 -94
  168. package/node_modules/@llblab/pi-telegram/lib/outbound-voice.ts +33 -27
  169. package/node_modules/@llblab/pi-telegram/lib/outbound.ts +125 -125
  170. package/node_modules/@llblab/pi-telegram/lib/ownership.ts +19 -10
  171. package/node_modules/@llblab/pi-telegram/lib/paths.ts +148 -49
  172. package/node_modules/@llblab/pi-telegram/lib/pi.ts +35 -15
  173. package/node_modules/@llblab/pi-telegram/lib/polling.ts +144 -147
  174. package/node_modules/@llblab/pi-telegram/lib/preview.ts +93 -40
  175. package/node_modules/@llblab/pi-telegram/lib/process-identity.ts +164 -0
  176. package/node_modules/@llblab/pi-telegram/lib/prompts.ts +4 -11
  177. package/node_modules/@llblab/pi-telegram/lib/queue.ts +511 -300
  178. package/node_modules/@llblab/pi-telegram/lib/recovery.ts +50 -454
  179. package/node_modules/@llblab/pi-telegram/lib/rendering.ts +1 -3
  180. package/node_modules/@llblab/pi-telegram/lib/replies.ts +248 -209
  181. package/node_modules/@llblab/pi-telegram/lib/routing.ts +8509 -2186
  182. package/node_modules/@llblab/pi-telegram/lib/runtime.ts +1 -12
  183. package/node_modules/@llblab/pi-telegram/lib/sections.ts +24 -89
  184. package/node_modules/@llblab/pi-telegram/lib/setup.ts +1 -3
  185. package/node_modules/@llblab/pi-telegram/lib/skills.ts +9 -4
  186. package/node_modules/@llblab/pi-telegram/lib/status.ts +215 -110
  187. package/node_modules/@llblab/pi-telegram/lib/sync.ts +448 -361
  188. package/node_modules/@llblab/pi-telegram/lib/target.ts +22 -3
  189. package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +302 -127
  190. package/node_modules/@llblab/pi-telegram/lib/text-groups.ts +5 -39
  191. package/node_modules/@llblab/pi-telegram/lib/thread-cleanup-manager.ts +833 -331
  192. package/node_modules/@llblab/pi-telegram/lib/thread-display.ts +213 -97
  193. package/node_modules/@llblab/pi-telegram/lib/thread-naming.ts +232 -87
  194. package/node_modules/@llblab/pi-telegram/lib/thread-reconciler.ts +155 -72
  195. package/node_modules/@llblab/pi-telegram/lib/threads.ts +5318 -1845
  196. package/node_modules/@llblab/pi-telegram/lib/time-injection.ts +1 -4
  197. package/node_modules/@llblab/pi-telegram/lib/turns.ts +15 -28
  198. package/node_modules/@llblab/pi-telegram/lib/updates.ts +5184 -1612
  199. package/node_modules/@llblab/pi-telegram/lib/voice.ts +7 -35
  200. package/node_modules/@llblab/pi-telegram/lib/wire.ts +1 -1
  201. package/node_modules/@llblab/pi-telegram/lib/workspace-admission.ts +996 -416
  202. package/node_modules/@llblab/pi-telegram/lib/workspace-identity.ts +14 -17
  203. package/node_modules/@llblab/pi-telegram/lib/workspace-retirement.ts +878 -376
  204. package/node_modules/@llblab/pi-telegram/lib/workspace-slots.ts +22 -13
  205. package/node_modules/@llblab/pi-telegram/package.json +1 -1
  206. package/package.json +2 -2
  207. package/node_modules/@llblab/pi-telegram/scripts/check-downgrade.mjs +0 -472
  208. 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 {