@llblab/pi-telegram 0.52.2 → 0.53.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.
- package/AGENTS.md +9 -7
- package/BACKLOG.md +1 -18
- package/CHANGELOG.md +11 -0
- package/README.md +7 -11
- package/api/commands.ts +3 -0
- package/dist/api/commands.d.ts +1 -1
- package/dist/lib/activity-verbosity.d.ts +1 -5
- package/dist/lib/activity-verbosity.js +15 -42
- package/dist/lib/activity.d.ts +9 -0
- package/dist/lib/activity.js +39 -10
- package/dist/lib/agent-messages.js +7 -3
- package/dist/lib/bindings.d.ts +43 -5
- package/dist/lib/bindings.js +163 -20
- package/dist/lib/bus-api.d.ts +12 -0
- package/dist/lib/bus-api.js +182 -79
- package/dist/lib/bus-follower.d.ts +111 -31
- package/dist/lib/bus-follower.js +1324 -359
- package/dist/lib/bus-leader.d.ts +41 -91
- package/dist/lib/bus-leader.js +969 -637
- package/dist/lib/bus-transport.js +16 -9
- package/dist/lib/bus.d.ts +215 -18
- package/dist/lib/bus.js +1048 -327
- package/dist/lib/channel-posts.d.ts +5 -0
- package/dist/lib/channel-posts.js +434 -261
- package/dist/lib/command-templates.d.ts +26 -5
- package/dist/lib/command-templates.js +92 -176
- package/dist/lib/commands.d.ts +168 -82
- package/dist/lib/commands.js +1409 -291
- package/dist/lib/config.d.ts +1 -8
- package/dist/lib/config.js +34 -76
- package/dist/lib/delivery.d.ts +2 -2
- package/dist/lib/delivery.js +16 -16
- package/dist/lib/extension.js +639 -171
- package/dist/lib/generative-app-worker.mjs +19 -7
- package/dist/lib/generative-apps.d.ts +0 -5
- package/dist/lib/generative-apps.js +61 -49
- package/dist/lib/inbound.d.ts +5 -0
- package/dist/lib/inbound.js +35 -59
- package/dist/lib/journal.d.ts +33 -3
- package/dist/lib/journal.js +2063 -782
- package/dist/lib/lifecycle.d.ts +2 -6
- package/dist/lib/lifecycle.js +75 -58
- package/dist/lib/locks.d.ts +29 -3
- package/dist/lib/locks.js +289 -108
- package/dist/lib/logging.js +50 -14
- package/dist/lib/media.d.ts +22 -1
- package/dist/lib/media.js +46 -28
- package/dist/lib/menu-model.d.ts +19 -5
- package/dist/lib/menu-model.js +53 -11
- package/dist/lib/menu-queue.d.ts +5 -4
- package/dist/lib/menu-queue.js +45 -63
- package/dist/lib/menu-settings.d.ts +5 -4
- package/dist/lib/menu-settings.js +114 -78
- package/dist/lib/menu-status.d.ts +1 -1
- package/dist/lib/menu-status.js +8 -25
- package/dist/lib/menu-thinking.d.ts +6 -1
- package/dist/lib/menu-thinking.js +14 -21
- package/dist/lib/menu.d.ts +8 -61
- package/dist/lib/menu.js +92 -164
- package/dist/lib/model.d.ts +1 -1
- package/dist/lib/model.js +2 -16
- package/dist/lib/outbound-attachments.d.ts +6 -27
- package/dist/lib/outbound-attachments.js +45 -38
- package/dist/lib/outbound-buttons.js +22 -16
- package/dist/lib/outbound-markup.d.ts +0 -2
- package/dist/lib/outbound-markup.js +51 -64
- package/dist/lib/outbound-voice.js +6 -3
- package/dist/lib/outbound.d.ts +6 -6
- package/dist/lib/outbound.js +61 -51
- package/dist/lib/ownership.js +6 -2
- package/dist/lib/paths.d.ts +5 -0
- package/dist/lib/paths.js +37 -21
- package/dist/lib/pi.d.ts +2 -0
- package/dist/lib/pi.js +8 -1
- package/dist/lib/polling.d.ts +21 -37
- package/dist/lib/polling.js +59 -50
- package/dist/lib/preview.js +39 -19
- package/dist/lib/process-identity.d.ts +24 -0
- package/dist/lib/process-identity.js +133 -0
- package/dist/lib/prompts.js +2 -7
- package/dist/lib/queue.d.ts +14 -16
- package/dist/lib/queue.js +249 -126
- package/dist/lib/recovery.d.ts +2 -83
- package/dist/lib/recovery.js +20 -274
- package/dist/lib/replies.d.ts +16 -24
- package/dist/lib/replies.js +99 -116
- package/dist/lib/routing.d.ts +120 -7
- package/dist/lib/routing.js +4667 -1152
- package/dist/lib/runtime.d.ts +1 -11
- package/dist/lib/sections.d.ts +4 -29
- package/dist/lib/sections.js +7 -20
- package/dist/lib/skills.js +4 -3
- package/dist/lib/status.js +86 -57
- package/dist/lib/sync.d.ts +1 -3
- package/dist/lib/sync.js +67 -43
- package/dist/lib/target.d.ts +4 -0
- package/dist/lib/target.js +18 -0
- package/dist/lib/telegram-api.d.ts +18 -13
- package/dist/lib/telegram-api.js +148 -53
- package/dist/lib/text-groups.js +4 -39
- package/dist/lib/thread-cleanup-manager.d.ts +6 -27
- package/dist/lib/thread-cleanup-manager.js +369 -186
- package/dist/lib/thread-display.d.ts +1 -2
- package/dist/lib/thread-display.js +58 -38
- package/dist/lib/thread-naming.d.ts +28 -8
- package/dist/lib/thread-naming.js +175 -66
- package/dist/lib/thread-reconciler.d.ts +42 -16
- package/dist/lib/thread-reconciler.js +90 -50
- package/dist/lib/threads.d.ts +82 -20
- package/dist/lib/threads.js +2247 -855
- package/dist/lib/time-injection.js +1 -2
- package/dist/lib/turns.js +1 -19
- package/dist/lib/updates.d.ts +169 -80
- package/dist/lib/updates.js +2418 -641
- package/dist/lib/voice.d.ts +7 -22
- package/dist/lib/voice.js +7 -22
- package/dist/lib/wire.js +1 -1
- package/dist/lib/workspace-admission.d.ts +3 -1
- package/dist/lib/workspace-admission.js +425 -211
- package/dist/lib/workspace-identity.js +8 -13
- package/dist/lib/workspace-retirement.d.ts +1 -1
- package/dist/lib/workspace-retirement.js +328 -140
- package/dist/lib/workspace-slots.js +8 -4
- package/dist/package.json +1 -1
- package/docs/architecture.md +31 -29
- package/docs/inbound.md +1 -1
- package/docs/multi-instance-bus.md +15 -3
- package/docs/outbound.md +1 -1
- package/docs/public-api.md +29 -1
- package/docs/ui-style.md +25 -8
- package/lib/activity-verbosity.ts +59 -82
- package/lib/activity.ts +146 -102
- package/lib/agent-messages.ts +12 -5
- package/lib/bindings.ts +389 -82
- package/lib/bus-api.ts +263 -102
- package/lib/bus-follower.ts +2440 -601
- package/lib/bus-leader.ts +2209 -1290
- package/lib/bus-transport.ts +46 -12
- package/lib/bus.ts +1673 -444
- package/lib/channel-posts.ts +848 -327
- package/lib/command-templates.ts +152 -265
- package/lib/commands.ts +2398 -555
- package/lib/config.ts +177 -170
- package/lib/delivery.ts +41 -29
- package/lib/extension.ts +1206 -433
- package/lib/generative-app-worker.mjs +19 -7
- package/lib/generative-apps.ts +324 -211
- package/lib/inbound.ts +95 -97
- package/lib/journal.ts +5311 -1763
- package/lib/keyboard.ts +1 -3
- package/lib/lifecycle.ts +128 -78
- package/lib/locks.ts +677 -203
- package/lib/logging.ts +147 -58
- package/lib/media.ts +82 -43
- package/lib/menu-model.ts +85 -18
- package/lib/menu-queue.ts +88 -129
- package/lib/menu-settings.ts +260 -155
- package/lib/menu-status.ts +20 -60
- package/lib/menu-thinking.ts +41 -56
- package/lib/menu.ts +112 -285
- package/lib/model.ts +8 -22
- package/lib/outbound-attachments.ts +156 -125
- package/lib/outbound-buttons.ts +76 -49
- package/lib/outbound-markup.ts +96 -94
- package/lib/outbound-voice.ts +33 -27
- package/lib/outbound.ts +125 -125
- package/lib/ownership.ts +19 -10
- package/lib/paths.ts +148 -49
- package/lib/pi.ts +35 -15
- package/lib/polling.ts +144 -147
- package/lib/preview.ts +93 -40
- package/lib/process-identity.ts +164 -0
- package/lib/prompts.ts +4 -11
- package/lib/queue.ts +511 -300
- package/lib/recovery.ts +50 -454
- package/lib/rendering.ts +1 -3
- package/lib/replies.ts +248 -209
- package/lib/routing.ts +8509 -2186
- package/lib/runtime.ts +1 -12
- package/lib/sections.ts +24 -89
- package/lib/setup.ts +1 -3
- package/lib/skills.ts +9 -4
- package/lib/status.ts +215 -110
- package/lib/sync.ts +448 -361
- package/lib/target.ts +22 -3
- package/lib/telegram-api.ts +302 -127
- package/lib/text-groups.ts +5 -39
- package/lib/thread-cleanup-manager.ts +833 -331
- package/lib/thread-display.ts +213 -97
- package/lib/thread-naming.ts +232 -87
- package/lib/thread-reconciler.ts +155 -72
- package/lib/threads.ts +5318 -1845
- package/lib/time-injection.ts +1 -4
- package/lib/turns.ts +15 -28
- package/lib/updates.ts +5184 -1612
- package/lib/voice.ts +7 -35
- package/lib/wire.ts +1 -1
- package/lib/workspace-admission.ts +996 -416
- package/lib/workspace-identity.ts +14 -17
- package/lib/workspace-retirement.ts +878 -376
- package/lib/workspace-slots.ts +22 -13
- package/package.json +1 -1
- package/scripts/check-downgrade.mjs +0 -472
- package/scripts/measure-workspace.mjs +0 -108
package/lib/thread-display.ts
CHANGED
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
* Excludes routing, name allocation, profile mutation, and live-owner discovery.
|
|
6
6
|
*/
|
|
7
7
|
import { isDeepStrictEqual } from "node:util";
|
|
8
|
+
|
|
8
9
|
import type { TelegramThreadDisplayMode } from "./config.ts";
|
|
10
|
+
import type { TelegramApiCallOptions } from "./telegram-api.ts";
|
|
9
11
|
import type {
|
|
10
12
|
TelegramTopicTargetStore,
|
|
11
13
|
TelegramWorkspaceDisplayBinding,
|
|
12
14
|
TelegramWorkspaceThreadBinding,
|
|
13
15
|
} from "./threads.ts";
|
|
14
|
-
import type { TelegramApiCallOptions } from "./telegram-api.ts";
|
|
15
16
|
|
|
16
17
|
function labelText(value: string): string {
|
|
17
18
|
return value.replace(/\s+/gu, " ").trim();
|
|
@@ -30,14 +31,20 @@ function directorySegments(cwd: string): string[] {
|
|
|
30
31
|
return cwd.split("/").filter(Boolean);
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
function distinguishingDirectorySegments(
|
|
34
|
+
function distinguishingDirectorySegments(
|
|
35
|
+
cwd: string,
|
|
36
|
+
directories: readonly string[],
|
|
37
|
+
): string[] {
|
|
34
38
|
const parts = directorySegments(cwd);
|
|
35
39
|
if (!parts.length) return [];
|
|
36
40
|
for (let depth = 1; depth <= parts.length; depth++) {
|
|
37
41
|
const candidate = labelText(parts.slice(-depth).join("/"));
|
|
38
|
-
const collides = directories.some(
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
const collides = directories.some(
|
|
43
|
+
(other) =>
|
|
44
|
+
other !== cwd &&
|
|
45
|
+
labelText(
|
|
46
|
+
directorySegments(other).slice(-depth).join("/"),
|
|
47
|
+
).toLowerCase() === candidate.toLowerCase(),
|
|
41
48
|
);
|
|
42
49
|
if (!collides) return parts.slice(-depth);
|
|
43
50
|
}
|
|
@@ -65,14 +72,21 @@ function formatDirectoryLabel(
|
|
|
65
72
|
): string {
|
|
66
73
|
const segments = distinguishingDirectorySegments(cwd, directories);
|
|
67
74
|
if (!segments.length) return "/";
|
|
68
|
-
const formatted = segments
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
const formatted = segments
|
|
76
|
+
.map((segment) => {
|
|
77
|
+
const tokens = tokenizeTelegramDirectorySegment(segment);
|
|
78
|
+
if (!tokens.length) return "";
|
|
79
|
+
if (mode === "directory-snake")
|
|
80
|
+
return tokens.map((token) => token.toLowerCase()).join("_");
|
|
81
|
+
return tokens
|
|
82
|
+
.map((token) =>
|
|
83
|
+
/\p{L}/u.test(token) && token === token.toUpperCase()
|
|
84
|
+
? token
|
|
85
|
+
: `${token.slice(0, 1).toUpperCase()}${token.slice(1).toLowerCase()}`,
|
|
86
|
+
)
|
|
87
|
+
.join(" ");
|
|
88
|
+
})
|
|
89
|
+
.filter(Boolean);
|
|
76
90
|
if (!formatted.length) return directoryLabel(cwd, directories);
|
|
77
91
|
return formatted.join(mode === "directory-snake" ? "_" : " / ");
|
|
78
92
|
}
|
|
@@ -91,27 +105,59 @@ export function resolveTelegramLiveWorkspaceBindingKeys(
|
|
|
91
105
|
};
|
|
92
106
|
add(leaderTarget);
|
|
93
107
|
for (const follower of followers) add(follower.target);
|
|
94
|
-
return new Set(
|
|
95
|
-
|
|
96
|
-
|
|
108
|
+
return new Set(
|
|
109
|
+
bindings
|
|
110
|
+
.filter((binding) =>
|
|
111
|
+
targets.has(`${binding.target.chatId}:${binding.target.threadId}`),
|
|
112
|
+
)
|
|
113
|
+
.map((binding) => binding.bindingKey),
|
|
114
|
+
);
|
|
97
115
|
}
|
|
98
116
|
|
|
99
117
|
/** Missing or ambiguous metadata yields no label rather than inventing identity. */
|
|
118
|
+
/** Case-insensitive label occurrences; directory modes count only live bindings. */
|
|
119
|
+
function countTelegramDisplayLabels(
|
|
120
|
+
bindings: readonly TelegramWorkspaceDisplayBinding[],
|
|
121
|
+
labels: ReadonlyMap<string, string>,
|
|
122
|
+
mode: TelegramThreadDisplayMode,
|
|
123
|
+
liveBindingKeys: ReadonlySet<string>,
|
|
124
|
+
): Map<string, number> {
|
|
125
|
+
const counts = new Map<string, number>();
|
|
126
|
+
for (const binding of bindings) {
|
|
127
|
+
const label = labels.get(binding.bindingKey);
|
|
128
|
+
if (
|
|
129
|
+
!label ||
|
|
130
|
+
((mode === "directory-snake" || mode === "directory-title") &&
|
|
131
|
+
!liveBindingKeys.has(binding.bindingKey))
|
|
132
|
+
)
|
|
133
|
+
continue;
|
|
134
|
+
const key = label.toLowerCase();
|
|
135
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
136
|
+
}
|
|
137
|
+
return counts;
|
|
138
|
+
}
|
|
139
|
+
|
|
100
140
|
export function resolveTelegramWorkspaceDisplayNames(
|
|
101
141
|
bindings: readonly TelegramWorkspaceDisplayBinding[],
|
|
102
142
|
mode: TelegramThreadDisplayMode,
|
|
103
143
|
liveBindingKeys: ReadonlySet<string> = new Set(),
|
|
104
144
|
): Map<string, string> {
|
|
105
145
|
const labels = new Map<string, string>();
|
|
106
|
-
const directories = Array.from(
|
|
146
|
+
const directories = Array.from(
|
|
147
|
+
new Set(bindings.map((binding) => binding.cwd)),
|
|
148
|
+
);
|
|
107
149
|
const liveDirectoryCounts = new Map<string, number>();
|
|
108
150
|
for (const binding of bindings) {
|
|
109
151
|
if (!liveBindingKeys.has(binding.bindingKey)) continue;
|
|
110
|
-
liveDirectoryCounts.set(
|
|
152
|
+
liveDirectoryCounts.set(
|
|
153
|
+
binding.cwd,
|
|
154
|
+
(liveDirectoryCounts.get(binding.cwd) ?? 0) + 1,
|
|
155
|
+
);
|
|
111
156
|
}
|
|
112
157
|
const bases = new Map<string, string>();
|
|
113
158
|
for (const binding of bindings) {
|
|
114
|
-
const slot =
|
|
159
|
+
const slot =
|
|
160
|
+
binding.slot && /^[A-Z]$/u.test(binding.slot) ? binding.slot : undefined;
|
|
115
161
|
const manualName = binding.manualThreadName
|
|
116
162
|
? labelText(binding.manualThreadName)
|
|
117
163
|
: undefined;
|
|
@@ -123,36 +169,50 @@ export function resolveTelegramWorkspaceDisplayNames(
|
|
|
123
169
|
const name = binding.threadName ? labelText(binding.threadName) : slot;
|
|
124
170
|
if (name) labels.set(binding.bindingKey, boundedLabel(name));
|
|
125
171
|
} else {
|
|
126
|
-
const base =
|
|
127
|
-
|
|
128
|
-
|
|
172
|
+
const base =
|
|
173
|
+
mode === "directories"
|
|
174
|
+
? directoryLabel(binding.cwd, directories)
|
|
175
|
+
: formatDirectoryLabel(binding.cwd, directories, mode);
|
|
129
176
|
bases.set(binding.bindingKey, base);
|
|
130
|
-
const showSuffix =
|
|
131
|
-
|
|
132
|
-
|
|
177
|
+
const showSuffix =
|
|
178
|
+
mode === "directories"
|
|
179
|
+
? binding.showSlotSuffix ||
|
|
180
|
+
bindings.filter((candidate) => candidate.cwd === binding.cwd)
|
|
181
|
+
.length > 1
|
|
182
|
+
: (liveDirectoryCounts.get(binding.cwd) ?? 0) > 1;
|
|
133
183
|
if (showSuffix && !slot) continue;
|
|
134
|
-
const suffix = !showSuffix
|
|
135
|
-
?
|
|
136
|
-
:
|
|
184
|
+
const suffix = !showSuffix
|
|
185
|
+
? ""
|
|
186
|
+
: mode === "directory-title"
|
|
187
|
+
? ` ${slot}`
|
|
188
|
+
: `_${slot!.toLowerCase()}`;
|
|
137
189
|
labels.set(binding.bindingKey, boundedLabel(base, suffix));
|
|
138
190
|
}
|
|
139
191
|
}
|
|
140
192
|
// Long or whitespace-normalized paths can collide even after qualification.
|
|
141
|
-
if (
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
193
|
+
if (
|
|
194
|
+
mode === "directories" ||
|
|
195
|
+
mode === "directory-snake" ||
|
|
196
|
+
mode === "directory-title"
|
|
197
|
+
) {
|
|
198
|
+
const counts = countTelegramDisplayLabels(
|
|
199
|
+
bindings,
|
|
200
|
+
labels,
|
|
201
|
+
mode,
|
|
202
|
+
liveBindingKeys,
|
|
203
|
+
);
|
|
150
204
|
for (const binding of bindings) {
|
|
151
205
|
const label = labels.get(binding.bindingKey);
|
|
152
|
-
if (
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
206
|
+
if (
|
|
207
|
+
binding.manualThreadName ||
|
|
208
|
+
!label ||
|
|
209
|
+
(counts.get(label.toLowerCase()) ?? 0) < 2
|
|
210
|
+
)
|
|
211
|
+
continue;
|
|
212
|
+
if (
|
|
213
|
+
(mode === "directory-snake" || mode === "directory-title") &&
|
|
214
|
+
(liveDirectoryCounts.get(binding.cwd) ?? 0) < 2
|
|
215
|
+
) {
|
|
156
216
|
labels.delete(binding.bindingKey);
|
|
157
217
|
continue;
|
|
158
218
|
}
|
|
@@ -160,18 +220,23 @@ export function resolveTelegramWorkspaceDisplayNames(
|
|
|
160
220
|
labels.delete(binding.bindingKey);
|
|
161
221
|
continue;
|
|
162
222
|
}
|
|
163
|
-
labels.set(
|
|
164
|
-
|
|
223
|
+
labels.set(
|
|
224
|
+
binding.bindingKey,
|
|
225
|
+
boundedLabel(
|
|
226
|
+
bases.get(binding.bindingKey)!,
|
|
227
|
+
mode === "directory-title"
|
|
228
|
+
? ` ${binding.slot}`
|
|
229
|
+
: `_${binding.slot.toLowerCase()}`,
|
|
230
|
+
),
|
|
231
|
+
);
|
|
165
232
|
}
|
|
166
233
|
}
|
|
167
|
-
const counts =
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
174
|
-
}
|
|
234
|
+
const counts = countTelegramDisplayLabels(
|
|
235
|
+
bindings,
|
|
236
|
+
labels,
|
|
237
|
+
mode,
|
|
238
|
+
liveBindingKeys,
|
|
239
|
+
);
|
|
175
240
|
for (const [key, label] of labels) {
|
|
176
241
|
if ((counts.get(label.toLowerCase()) ?? 0) > 1) labels.delete(key);
|
|
177
242
|
}
|
|
@@ -185,24 +250,29 @@ export function resolveTelegramInitialWorkspaceDisplayName(input: {
|
|
|
185
250
|
preserveRetainedManualName?: boolean;
|
|
186
251
|
liveBindingKeys?: ReadonlySet<string>;
|
|
187
252
|
}): string | undefined {
|
|
188
|
-
const retained = input.bindings.find(
|
|
189
|
-
binding.bindingKey === input.binding.bindingKey,
|
|
253
|
+
const retained = input.bindings.find(
|
|
254
|
+
(binding) => binding.bindingKey === input.binding.bindingKey,
|
|
190
255
|
);
|
|
191
256
|
const binding = retained
|
|
192
257
|
? {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
258
|
+
...input.binding,
|
|
259
|
+
...(retained.showSlotSuffix ? { showSlotSuffix: true as const } : {}),
|
|
260
|
+
...(input.preserveRetainedManualName !== false &&
|
|
261
|
+
retained.manualThreadName
|
|
262
|
+
? { manualThreadName: retained.manualThreadName }
|
|
263
|
+
: {}),
|
|
264
|
+
}
|
|
199
265
|
: input.binding;
|
|
200
|
-
return resolveTelegramWorkspaceDisplayNames(
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
266
|
+
return resolveTelegramWorkspaceDisplayNames(
|
|
267
|
+
[
|
|
268
|
+
...input.bindings.filter(
|
|
269
|
+
(candidate) => candidate.bindingKey !== binding.bindingKey,
|
|
270
|
+
),
|
|
271
|
+
binding,
|
|
272
|
+
],
|
|
273
|
+
input.mode,
|
|
274
|
+
new Set([...(input.liveBindingKeys ?? []), binding.bindingKey]),
|
|
275
|
+
).get(binding.bindingKey);
|
|
206
276
|
}
|
|
207
277
|
|
|
208
278
|
export async function applyTelegramThreadDisplaySetting(
|
|
@@ -210,28 +280,43 @@ export async function applyTelegramThreadDisplaySetting(
|
|
|
210
280
|
deps: {
|
|
211
281
|
getProfileKey(): string | undefined;
|
|
212
282
|
ownsLeader(): boolean;
|
|
213
|
-
getLeaderSetter():
|
|
214
|
-
|
|
283
|
+
getLeaderSetter():
|
|
284
|
+
((mode: TelegramThreadDisplayMode) => Promise<void>) | undefined;
|
|
285
|
+
getFollowerSetter():
|
|
286
|
+
((mode: TelegramThreadDisplayMode) => Promise<void>) | undefined;
|
|
215
287
|
reloadConfig(): Promise<void>;
|
|
216
288
|
},
|
|
217
289
|
): Promise<void> {
|
|
218
290
|
const profile = deps.getProfileKey();
|
|
219
|
-
const setter = deps.ownsLeader()
|
|
220
|
-
|
|
291
|
+
const setter = deps.ownsLeader()
|
|
292
|
+
? deps.getLeaderSetter()
|
|
293
|
+
: deps.getFollowerSetter();
|
|
294
|
+
if (!setter)
|
|
295
|
+
throw new Error(
|
|
296
|
+
"Thread display settings require a connected compatible instance.",
|
|
297
|
+
);
|
|
221
298
|
await setter(mode);
|
|
222
|
-
if (deps.getProfileKey() !== profile)
|
|
299
|
+
if (deps.getProfileKey() !== profile)
|
|
300
|
+
throw new Error("Telegram Thread display setting changed profile.");
|
|
223
301
|
await deps.reloadConfig();
|
|
224
|
-
if (deps.getProfileKey() !== profile)
|
|
302
|
+
if (deps.getProfileKey() !== profile)
|
|
303
|
+
throw new Error("Telegram Thread display setting changed profile.");
|
|
225
304
|
}
|
|
226
305
|
|
|
227
306
|
export interface TelegramThreadDisplayReconcilerDeps {
|
|
228
|
-
store: Pick<
|
|
229
|
-
|
|
307
|
+
store: Pick<
|
|
308
|
+
TelegramTopicTargetStore,
|
|
309
|
+
"listWorkspaceBindings" | "setWorkspaceDisplayTitle" | "persist"
|
|
310
|
+
>;
|
|
230
311
|
getMode(): TelegramThreadDisplayMode;
|
|
231
312
|
getProfileKey(): string;
|
|
232
313
|
getLeaderEpoch(): string | number | undefined;
|
|
233
|
-
captureBindingAuthority(
|
|
234
|
-
|
|
314
|
+
captureBindingAuthority(
|
|
315
|
+
binding: TelegramWorkspaceThreadBinding,
|
|
316
|
+
): (() => boolean) | undefined;
|
|
317
|
+
captureLiveBindingKeys(
|
|
318
|
+
bindings: readonly TelegramWorkspaceThreadBinding[],
|
|
319
|
+
): ReadonlySet<string>;
|
|
235
320
|
callApi<TResponse>(
|
|
236
321
|
method: string,
|
|
237
322
|
body: Record<string, unknown>,
|
|
@@ -241,11 +326,15 @@ export interface TelegramThreadDisplayReconcilerDeps {
|
|
|
241
326
|
|
|
242
327
|
export function createTelegramThreadDisplaySettingsRuntime(deps: {
|
|
243
328
|
getTarget(): { chatId: number; threadId?: number } | undefined;
|
|
244
|
-
getBinding(target: {
|
|
245
|
-
|
|
329
|
+
getBinding(target: {
|
|
330
|
+
chatId: number;
|
|
331
|
+
threadId?: number;
|
|
332
|
+
}): { manualThreadName?: string } | undefined;
|
|
246
333
|
apply(mode: TelegramThreadDisplayMode): Promise<void>;
|
|
247
|
-
reset(target: {
|
|
248
|
-
|
|
334
|
+
reset(target: {
|
|
335
|
+
chatId: number;
|
|
336
|
+
threadId?: number;
|
|
337
|
+
}): Promise<{ ok: boolean; message?: string }>;
|
|
249
338
|
}): {
|
|
250
339
|
isCustom(): boolean;
|
|
251
340
|
setMode(mode: TelegramThreadDisplayMode): Promise<void>;
|
|
@@ -253,17 +342,23 @@ export function createTelegramThreadDisplaySettingsRuntime(deps: {
|
|
|
253
342
|
return {
|
|
254
343
|
isCustom() {
|
|
255
344
|
const target = deps.getTarget();
|
|
256
|
-
return
|
|
345
|
+
return (
|
|
346
|
+
!!target &&
|
|
347
|
+
typeof deps.getBinding(target)?.manualThreadName === "string"
|
|
348
|
+
);
|
|
257
349
|
},
|
|
258
350
|
async setMode(mode) {
|
|
259
351
|
const target = deps.getTarget();
|
|
260
|
-
const hadManualName =
|
|
352
|
+
const hadManualName =
|
|
353
|
+
!!target &&
|
|
261
354
|
typeof deps.getBinding(target)?.manualThreadName === "string";
|
|
262
355
|
await deps.apply(mode);
|
|
263
356
|
if (!hadManualName || !target) return;
|
|
264
357
|
const reset = await deps.reset(target);
|
|
265
358
|
if (!reset.ok) {
|
|
266
|
-
throw new Error(
|
|
359
|
+
throw new Error(
|
|
360
|
+
reset.message ?? "Telegram Thread display override reset failed.",
|
|
361
|
+
);
|
|
267
362
|
}
|
|
268
363
|
},
|
|
269
364
|
};
|
|
@@ -279,40 +374,61 @@ export function createTelegramThreadDisplayReconciler(
|
|
|
279
374
|
const profile = deps.getProfileKey();
|
|
280
375
|
const mode = deps.getMode();
|
|
281
376
|
const assertAuthority = (): void => {
|
|
282
|
-
if (
|
|
283
|
-
|
|
284
|
-
|
|
377
|
+
if (
|
|
378
|
+
epoch === undefined ||
|
|
379
|
+
deps.getLeaderEpoch() !== epoch ||
|
|
380
|
+
deps.getProfileKey() !== profile ||
|
|
381
|
+
deps.getMode() !== mode
|
|
382
|
+
) {
|
|
383
|
+
throw new Error(
|
|
384
|
+
"Telegram Thread display update lost profile, mode, or leader authority.",
|
|
385
|
+
);
|
|
285
386
|
}
|
|
286
387
|
};
|
|
287
388
|
assertAuthority();
|
|
288
389
|
const bindings = deps.store.listWorkspaceBindings();
|
|
289
390
|
const liveBindingKeys = deps.captureLiveBindingKeys(bindings);
|
|
290
|
-
const titles = resolveTelegramWorkspaceDisplayNames(
|
|
391
|
+
const titles = resolveTelegramWorkspaceDisplayNames(
|
|
392
|
+
bindings,
|
|
393
|
+
mode,
|
|
394
|
+
liveBindingKeys,
|
|
395
|
+
);
|
|
291
396
|
let changed = 0;
|
|
292
397
|
for (const binding of bindings) {
|
|
293
398
|
const isBindingCurrent = deps.captureBindingAuthority(binding);
|
|
294
399
|
if (!isBindingCurrent) continue;
|
|
295
400
|
const title = titles.get(binding.bindingKey);
|
|
296
|
-
if (!title)
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const current = deps.store.listWorkspaceBindings().find((candidate) =>
|
|
300
|
-
candidate.bindingKey === expected.bindingKey,
|
|
401
|
+
if (!title)
|
|
402
|
+
throw new Error(
|
|
403
|
+
"Telegram Thread display identity is missing or ambiguous.",
|
|
301
404
|
);
|
|
405
|
+
const assertBinding = (
|
|
406
|
+
expected: TelegramWorkspaceThreadBinding,
|
|
407
|
+
): void => {
|
|
408
|
+
assertAuthority();
|
|
409
|
+
const current = deps.store
|
|
410
|
+
.listWorkspaceBindings()
|
|
411
|
+
.find((candidate) => candidate.bindingKey === expected.bindingKey);
|
|
302
412
|
if (!isDeepStrictEqual(current, expected) || !isBindingCurrent()) {
|
|
303
413
|
throw new Error("Telegram Thread display binding changed.");
|
|
304
414
|
}
|
|
305
415
|
};
|
|
306
416
|
assertBinding(binding);
|
|
307
417
|
if ((binding.displayTitle ?? binding.threadName) === title) continue;
|
|
308
|
-
await deps.callApi(
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
418
|
+
await deps.callApi(
|
|
419
|
+
"editForumTopic",
|
|
420
|
+
{
|
|
421
|
+
chat_id: binding.target.chatId,
|
|
422
|
+
message_thread_id: binding.target.threadId,
|
|
423
|
+
name: title,
|
|
424
|
+
},
|
|
425
|
+
{ maxAttempts: 1 },
|
|
426
|
+
);
|
|
313
427
|
assertBinding(binding);
|
|
314
428
|
if (!deps.store.setWorkspaceDisplayTitle(binding, title)) {
|
|
315
|
-
throw new Error(
|
|
429
|
+
throw new Error(
|
|
430
|
+
"Telegram Thread display binding changed before title commit.",
|
|
431
|
+
);
|
|
316
432
|
}
|
|
317
433
|
await deps.store.persist();
|
|
318
434
|
assertBinding({ ...binding, displayTitle: title });
|