@llblab/pi-telegram 0.52.1 → 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 +10 -8
- package/BACKLOG.md +1 -16
- package/CHANGELOG.md +17 -0
- package/README.md +8 -10
- 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 +640 -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 +34 -3
- package/dist/lib/locks.js +318 -119
- 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 +23 -37
- package/dist/lib/polling.js +71 -52
- 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 +6 -3
- package/dist/lib/sync.js +112 -45
- 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 +165 -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 +36 -30
- 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 +1207 -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 +703 -212
- 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 +165 -149
- 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 +488 -359
- package/lib/target.ts +22 -3
- package/lib/telegram-api.ts +322 -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/dist/lib/sync.js
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
* Zones: Telegram bot reality mirror, demand-driven reconciliation, status diagnostics
|
|
4
4
|
* Owns pure contracts for deciding when local Telegram mirror state should be refreshed without querying Telegram on every action
|
|
5
5
|
*/
|
|
6
|
-
import { getTelegramApiErrorRequestTarget, isTelegramStaleTargetHttpError } from "./telegram-api.js";
|
|
7
6
|
import { getTelegramTargetKey } from "./target.js";
|
|
7
|
+
import { getTelegramApiErrorRequestTarget, isTelegramStaleTargetHttpError, } from "./telegram-api.js";
|
|
8
8
|
import * as ThreadReconciler from "./thread-reconciler.js";
|
|
9
|
-
import {
|
|
10
|
-
import { normalizeTelegramWorkspacePath } from "./workspace-identity.js";
|
|
9
|
+
import { commitTelegramWorkspaceProvisionBinding, createTelegramCleanupTargetProtection, getTelegramTargetFromApiBody, getTelegramThreadOwnerKey, isSameTelegramProcessInstance, isTelegramTopicTargetStaleError, provisionOwnBusTopic, } from "./threads.js";
|
|
11
10
|
import { createTelegramWorkspaceAdmissionOperationId, runWithTelegramWorkspaceAdmissionsAsync, } from "./workspace-admission.js";
|
|
12
|
-
import {
|
|
11
|
+
import { normalizeTelegramWorkspacePath } from "./workspace-identity.js";
|
|
12
|
+
import { TelegramWorkspaceSlotUnavailableError } from "./workspace-slots.js";
|
|
13
13
|
function markTelegramConfigSyncChange(state, action, options) {
|
|
14
14
|
const nowMs = options?.nowMs ?? Date.now();
|
|
15
15
|
let nextState = markTelegramSyncSliceFresh(state, "pairing", {
|
|
@@ -32,10 +32,13 @@ export function createTelegramPreservedLeaderQuitHandler(deps) {
|
|
|
32
32
|
const profile = deps.getProfileName();
|
|
33
33
|
if (epoch === undefined || !isSessionCurrent())
|
|
34
34
|
return undefined;
|
|
35
|
-
const isCurrent = () => isSessionCurrent() &&
|
|
36
|
-
deps.
|
|
35
|
+
const isCurrent = () => isSessionCurrent() &&
|
|
36
|
+
deps.getCurrentLeaderEpoch() === epoch &&
|
|
37
|
+
deps.getProfileName() === profile &&
|
|
38
|
+
deps.isPollingSuspended();
|
|
37
39
|
return async () => {
|
|
38
|
-
if (!isCurrent() ||
|
|
40
|
+
if (!isCurrent() ||
|
|
41
|
+
(await deps.resolveAutomaticThreadCleanupEnabled()) !== false)
|
|
39
42
|
return;
|
|
40
43
|
if (!isCurrent())
|
|
41
44
|
return;
|
|
@@ -49,13 +52,20 @@ export function createTelegramPreservedLeaderQuitHandler(deps) {
|
|
|
49
52
|
await deps.topicTargetStore.load();
|
|
50
53
|
if (!isCurrent())
|
|
51
54
|
return;
|
|
52
|
-
const records = deps.topicTargetStore
|
|
55
|
+
const records = deps.topicTargetStore
|
|
56
|
+
.list()
|
|
57
|
+
.filter((record) => record.instanceId === deps.instanceId &&
|
|
58
|
+
(record.status === "active" || record.status === "starting"));
|
|
53
59
|
if (records.length !== 1)
|
|
54
60
|
return;
|
|
55
61
|
const record = records[0];
|
|
56
|
-
if (deps.topicTargetStore
|
|
62
|
+
if (deps.topicTargetStore
|
|
63
|
+
.listPendingCleanups()
|
|
64
|
+
.some((intent) => intent.target.chatId === record.target.chatId &&
|
|
65
|
+
intent.target.threadId === record.target.threadId))
|
|
57
66
|
return;
|
|
58
|
-
if (!await deps.topicTargetStore.detachTargetOwner(record, isCurrent) &&
|
|
67
|
+
if (!(await deps.topicTargetStore.detachTargetOwner(record, isCurrent)) &&
|
|
68
|
+
isCurrent()) {
|
|
59
69
|
throw new Error("Telegram preserved leader detachment was not committed.");
|
|
60
70
|
}
|
|
61
71
|
});
|
|
@@ -74,11 +84,47 @@ function createTelegramSessionRestartThreadCleanupHandler(deps) {
|
|
|
74
84
|
}
|
|
75
85
|
export function createTelegramThreadDisconnectAssembly(deps) {
|
|
76
86
|
return {
|
|
77
|
-
disconnect
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
87
|
+
async disconnect() {
|
|
88
|
+
const captured = deps.captureStopPolling?.();
|
|
89
|
+
const stop = captured?.stop ?? deps.stopPolling;
|
|
90
|
+
let stopAttempted = false;
|
|
91
|
+
const disconnect = createTelegramManualThreadDisconnectHandler({
|
|
92
|
+
...deps,
|
|
93
|
+
workspaceOperationKind: "workspace.disconnect-thread",
|
|
94
|
+
isDisconnectCurrent: captured?.isCurrent ?? deps.isDisconnectCurrent,
|
|
95
|
+
stopPolling() {
|
|
96
|
+
stopAttempted = true;
|
|
97
|
+
return stop();
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
try {
|
|
101
|
+
return await disconnect();
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
// Never retry a stop whose teardown/release outcome may already have committed.
|
|
105
|
+
if (stopAttempted) {
|
|
106
|
+
deps.recordRuntimeEvent("connection", error, {
|
|
107
|
+
phase: "disconnect-stop",
|
|
108
|
+
});
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
deps.recordRuntimeEvent("connection", error, {
|
|
112
|
+
phase: "disconnect-cleanup",
|
|
113
|
+
});
|
|
114
|
+
let stopped;
|
|
115
|
+
try {
|
|
116
|
+
stopped = await stop();
|
|
117
|
+
}
|
|
118
|
+
catch (stopError) {
|
|
119
|
+
deps.recordRuntimeEvent("connection", stopError, {
|
|
120
|
+
phase: "disconnect-stop",
|
|
121
|
+
});
|
|
122
|
+
throw stopError;
|
|
123
|
+
}
|
|
124
|
+
const separator = /[.!?]$/u.test(stopped) ? " " : ". ";
|
|
125
|
+
return `${stopped}${separator}Thread cleanup was not confirmed. Check /telegram-status --debug.`;
|
|
126
|
+
}
|
|
127
|
+
},
|
|
82
128
|
cleanupForSessionRestart: createTelegramSessionRestartThreadCleanupHandler({
|
|
83
129
|
...deps,
|
|
84
130
|
suspendPolling: deps.suspendPolling,
|
|
@@ -86,7 +132,12 @@ export function createTelegramThreadDisconnectAssembly(deps) {
|
|
|
86
132
|
};
|
|
87
133
|
}
|
|
88
134
|
export function createTelegramManualThreadDisconnectHandler(deps) {
|
|
135
|
+
const assertCurrent = () => {
|
|
136
|
+
if (deps.isDisconnectCurrent?.() === false)
|
|
137
|
+
throw new Error("Telegram disconnect was superseded by a new connection.");
|
|
138
|
+
};
|
|
89
139
|
const operation = async () => {
|
|
140
|
+
assertCurrent();
|
|
90
141
|
const currentRecord = deps.getCurrentThreadRecord();
|
|
91
142
|
let cleanupPending = false;
|
|
92
143
|
if (currentRecord?.target.threadId) {
|
|
@@ -98,6 +149,7 @@ export function createTelegramManualThreadDisconnectHandler(deps) {
|
|
|
98
149
|
if (isManualFollower && !ownsLeader) {
|
|
99
150
|
if (deps.disconnectFollowerThread) {
|
|
100
151
|
const disconnected = await deps.disconnectFollowerThread();
|
|
152
|
+
assertCurrent();
|
|
101
153
|
if (!disconnected) {
|
|
102
154
|
throw new Error("Telegram follower thread deletion requires a live leader registration.");
|
|
103
155
|
}
|
|
@@ -117,11 +169,15 @@ export function createTelegramManualThreadDisconnectHandler(deps) {
|
|
|
117
169
|
target,
|
|
118
170
|
requestedAtMs: (deps.getNowMs ?? Date.now)(),
|
|
119
171
|
};
|
|
120
|
-
const departingRecord = deps.topicTargetStore
|
|
121
|
-
|
|
172
|
+
const departingRecord = deps.topicTargetStore
|
|
173
|
+
.list()
|
|
174
|
+
.find((record) => record.instanceId === currentRecord.instanceId &&
|
|
175
|
+
record.target.chatId === target.chatId &&
|
|
176
|
+
record.target.threadId === target.threadId);
|
|
122
177
|
const isCleanupTargetProtected = createTelegramCleanupTargetProtection(deps.topicTargetStore, departingRecord);
|
|
123
178
|
deps.topicTargetStore.upsertPendingCleanup(intent);
|
|
124
179
|
await deps.topicTargetStore.persist();
|
|
180
|
+
assertCurrent();
|
|
125
181
|
const cleanupPlan = ThreadReconciler.planThreadReconciliation({
|
|
126
182
|
nowMs: (deps.getNowMs ?? Date.now)(),
|
|
127
183
|
currentLeaderEpoch: leaderEpoch,
|
|
@@ -131,20 +187,25 @@ export function createTelegramManualThreadDisconnectHandler(deps) {
|
|
|
131
187
|
const cleanup = await ThreadReconciler.applyThreadReconciliationPlan(cleanupPlan, {
|
|
132
188
|
isCleanupTargetProtected,
|
|
133
189
|
callApi(method, body) {
|
|
190
|
+
assertCurrent();
|
|
134
191
|
return deps.callApi(method, body);
|
|
135
192
|
},
|
|
136
193
|
markStaleByTarget(targetToMark, syncStatus, lastSyncError) {
|
|
194
|
+
assertCurrent();
|
|
137
195
|
return deps.topicTargetStore.markStaleByTarget(targetToMark, syncStatus, lastSyncError);
|
|
138
196
|
},
|
|
139
197
|
removeCleanupIntentById(id) {
|
|
198
|
+
assertCurrent();
|
|
140
199
|
return deps.topicTargetStore.removePendingCleanup(id);
|
|
141
200
|
},
|
|
142
201
|
persist() {
|
|
202
|
+
assertCurrent();
|
|
143
203
|
return deps.topicTargetStore.persist();
|
|
144
204
|
},
|
|
145
205
|
getCurrentLeaderEpoch: deps.getCurrentLeaderEpoch,
|
|
146
206
|
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
147
207
|
});
|
|
208
|
+
assertCurrent();
|
|
148
209
|
if (cleanupPlan.actions.some((action) => isCleanupTargetProtected(action.target, action))) {
|
|
149
210
|
return "Thread disconnect superseded by a new binding.";
|
|
150
211
|
}
|
|
@@ -160,6 +221,7 @@ export function createTelegramManualThreadDisconnectHandler(deps) {
|
|
|
160
221
|
action: "manual-disconnect",
|
|
161
222
|
}));
|
|
162
223
|
}
|
|
224
|
+
assertCurrent();
|
|
163
225
|
const stopped = await deps.stopPolling();
|
|
164
226
|
return cleanupPending
|
|
165
227
|
? `${stopped} Telegram thread cleanup remains pending for the next leader.`
|
|
@@ -269,23 +331,32 @@ export function captureTelegramStaleTargetRequestRecovery(body, deps) {
|
|
|
269
331
|
if (!target || epoch === undefined)
|
|
270
332
|
return undefined;
|
|
271
333
|
const key = getTelegramTargetKey(target);
|
|
272
|
-
const record = deps.topicTargetStore
|
|
334
|
+
const record = deps.topicTargetStore
|
|
335
|
+
.list()
|
|
336
|
+
.find((candidate) => getTelegramTargetKey(candidate.target) === key);
|
|
273
337
|
if (!record)
|
|
274
338
|
return undefined;
|
|
275
339
|
const generation = deps.getSessionGeneration();
|
|
276
340
|
const profile = deps.getProfileName();
|
|
277
341
|
const isAuthorityCurrent = () => deps.getCurrentLeaderEpoch() === epoch &&
|
|
278
|
-
deps.getSessionGeneration() === generation &&
|
|
342
|
+
deps.getSessionGeneration() === generation &&
|
|
343
|
+
deps.getProfileName() === profile;
|
|
279
344
|
const isCurrent = () => {
|
|
280
|
-
const current = deps.topicTargetStore
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
345
|
+
const current = deps.topicTargetStore
|
|
346
|
+
.list()
|
|
347
|
+
.find((candidate) => getTelegramTargetKey(candidate.target) === key);
|
|
348
|
+
return (isAuthorityCurrent() &&
|
|
349
|
+
current?.instanceId === record.instanceId &&
|
|
350
|
+
current?.profileKey === record.profileKey &&
|
|
351
|
+
current?.updatedAtMs === record.updatedAtMs &&
|
|
352
|
+
current?.createdAtMs === record.createdAtMs);
|
|
284
353
|
};
|
|
285
354
|
return async (error) => {
|
|
286
355
|
const requestTarget = getTelegramApiErrorRequestTarget(error);
|
|
287
|
-
if (!isTelegramStaleTargetHttpError(error) ||
|
|
288
|
-
|
|
356
|
+
if (!isTelegramStaleTargetHttpError(error) ||
|
|
357
|
+
!requestTarget ||
|
|
358
|
+
getTelegramTargetKey(requestTarget) !== key ||
|
|
359
|
+
!isCurrent())
|
|
289
360
|
return;
|
|
290
361
|
if (await recoverStaleTelegramTopicApiError({ chat_id: target.chatId, message_thread_id: target.threadId }, error, { ...deps, isCurrent, isAuthorityCurrent })) {
|
|
291
362
|
deps.onRecovered();
|
|
@@ -311,7 +382,7 @@ export async function recoverStaleTelegramTopicApiError(apiBody, error, deps) {
|
|
|
311
382
|
const recover = async () => {
|
|
312
383
|
if (deps.isCurrent) {
|
|
313
384
|
if (!deps.topicTargetStore.invalidateTarget ||
|
|
314
|
-
!await deps.topicTargetStore.invalidateTarget(target, deps.isCurrent, String(error)))
|
|
385
|
+
!(await deps.topicTargetStore.invalidateTarget(target, deps.isCurrent, String(error))))
|
|
315
386
|
return false;
|
|
316
387
|
if (deps.isAuthorityCurrent?.() === false)
|
|
317
388
|
return false;
|
|
@@ -393,7 +464,12 @@ export async function ensureTelegramLeaderThreadBinding(deps) {
|
|
|
393
464
|
const legacyLeaderRecord = deps.topicTargetStore.getByProfileKey(leaderProfileKey);
|
|
394
465
|
let capacityUnavailable = false;
|
|
395
466
|
const workspaceIdentity = normalizedLeaderCwd
|
|
396
|
-
? deps.topicTargetStore.claimWorkspaceIdentity(normalizedLeaderCwd, deps.instanceId, legacyLeaderRecord?.instanceId, {
|
|
467
|
+
? deps.topicTargetStore.claimWorkspaceIdentity(normalizedLeaderCwd, deps.instanceId, legacyLeaderRecord?.instanceId, {
|
|
468
|
+
sessionId: deps.sessionId,
|
|
469
|
+
onCapacityUnavailable() {
|
|
470
|
+
capacityUnavailable = true;
|
|
471
|
+
},
|
|
472
|
+
})
|
|
397
473
|
: undefined;
|
|
398
474
|
if (deps.cwd && !workspaceIdentity) {
|
|
399
475
|
if (capacityUnavailable)
|
|
@@ -428,7 +504,9 @@ export async function ensureTelegramLeaderThreadBinding(deps) {
|
|
|
428
504
|
target: { ...committed.target },
|
|
429
505
|
slot: committed.slot ?? result.slot,
|
|
430
506
|
...(committed.threadName ? { threadName: committed.threadName } : {}),
|
|
431
|
-
...(committed.displayTitle
|
|
507
|
+
...(committed.displayTitle
|
|
508
|
+
? { displayTitle: committed.displayTitle }
|
|
509
|
+
: {}),
|
|
432
510
|
};
|
|
433
511
|
};
|
|
434
512
|
try {
|
|
@@ -469,7 +547,9 @@ export async function ensureTelegramLeaderThreadBinding(deps) {
|
|
|
469
547
|
...(legacyLeaderRecord.threadName
|
|
470
548
|
? { threadName: legacyLeaderRecord.threadName }
|
|
471
549
|
: {}),
|
|
472
|
-
...(legacyLeaderRecord.slot
|
|
550
|
+
...(legacyLeaderRecord.slot
|
|
551
|
+
? { slot: legacyLeaderRecord.slot }
|
|
552
|
+
: {}),
|
|
473
553
|
updatedAtMs: legacyLeaderRecord.updatedAtMs,
|
|
474
554
|
}
|
|
475
555
|
: undefined;
|
|
@@ -557,7 +637,9 @@ export async function ensureTelegramLeaderThreadBinding(deps) {
|
|
|
557
637
|
});
|
|
558
638
|
// Legacy callers without session identity may reuse the active instance target.
|
|
559
639
|
// Session-aware callers must resolve their exact Workspace binding instead.
|
|
560
|
-
if (deps.sessionId === undefined &&
|
|
640
|
+
if (deps.sessionId === undefined &&
|
|
641
|
+
!deps.forceFreshUnnamed &&
|
|
642
|
+
priorTargets.length > 0) {
|
|
561
643
|
const record = priorTargets[0];
|
|
562
644
|
deps.recordEvent("telegram", "Leader thread preserved after session lifecycle change", {
|
|
563
645
|
phase: "leader-thread-reused",
|
|
@@ -704,21 +786,6 @@ export function createTelegramProvisioningActivityRuntime() {
|
|
|
704
786
|
},
|
|
705
787
|
};
|
|
706
788
|
}
|
|
707
|
-
const RECONCILE_TRIGGERS = new Set([
|
|
708
|
-
"startup",
|
|
709
|
-
"reload",
|
|
710
|
-
"topic-lifecycle",
|
|
711
|
-
"stale-api-error",
|
|
712
|
-
"setup-change",
|
|
713
|
-
"pairing-change",
|
|
714
|
-
"follower-register",
|
|
715
|
-
"follower-prune",
|
|
716
|
-
"status-request",
|
|
717
|
-
"leader-health-tick",
|
|
718
|
-
]);
|
|
719
|
-
export function shouldReconcileTelegramSync(trigger) {
|
|
720
|
-
return RECONCILE_TRIGGERS.has(trigger);
|
|
721
|
-
}
|
|
722
789
|
export function markTelegramSyncSliceSuspect(state, slice, input) {
|
|
723
790
|
return {
|
|
724
791
|
...state,
|
package/dist/lib/target.d.ts
CHANGED
|
@@ -18,3 +18,7 @@ export declare function areTelegramTargetsEqual(left: TelegramTarget, right: Tel
|
|
|
18
18
|
export declare function getTelegramTargetThreadParams(target: TelegramTarget): {
|
|
19
19
|
message_thread_id?: number;
|
|
20
20
|
};
|
|
21
|
+
/** Pure wire parser for `{ chatId, threadId? }`; unknown fields are dropped and malformed shapes refuse. */
|
|
22
|
+
export declare function parseTelegramTarget(value: unknown): TelegramTarget | undefined;
|
|
23
|
+
/** Pure Bot API integer id parser accepting integer numbers or non-blank integer strings. */
|
|
24
|
+
export declare function parseTelegramIntegerId(value: unknown): number | undefined;
|
package/dist/lib/target.js
CHANGED
|
@@ -25,3 +25,21 @@ export function getTelegramTargetThreadParams(target) {
|
|
|
25
25
|
? { message_thread_id: target.threadId }
|
|
26
26
|
: {};
|
|
27
27
|
}
|
|
28
|
+
/** Pure wire parser for `{ chatId, threadId? }`; unknown fields are dropped and malformed shapes refuse. */
|
|
29
|
+
export function parseTelegramTarget(value) {
|
|
30
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
31
|
+
return undefined;
|
|
32
|
+
const { chatId, threadId } = value;
|
|
33
|
+
if (typeof chatId !== "number")
|
|
34
|
+
return undefined;
|
|
35
|
+
return typeof threadId === "number" ? { chatId, threadId } : { chatId };
|
|
36
|
+
}
|
|
37
|
+
/** Pure Bot API integer id parser accepting integer numbers or non-blank integer strings. */
|
|
38
|
+
export function parseTelegramIntegerId(value) {
|
|
39
|
+
if (typeof value === "number" && Number.isInteger(value))
|
|
40
|
+
return value;
|
|
41
|
+
if (typeof value !== "string" || value.trim() === "")
|
|
42
|
+
return undefined;
|
|
43
|
+
const parsed = Number(value);
|
|
44
|
+
return Number.isInteger(parsed) ? parsed : undefined;
|
|
45
|
+
}
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import type { TelegramAttachmentSource } from "./media.ts";
|
|
9
9
|
export declare const TELEGRAM_FILE_MAX_BYTES: number;
|
|
10
|
-
|
|
10
|
+
/** First positive safe-integer byte limit among ordered env names, else the caller's default. */
|
|
11
|
+
export declare function getTelegramByteLimitFromEnv(env: NodeJS.ProcessEnv, names: readonly string[], defaultValue: number): number;
|
|
11
12
|
export type TelegramNetworkFamilyPolicy = "auto" | "ipv4" | "ipv6" | "ipv4-fallback";
|
|
12
13
|
type TelegramNetworkFamily = 4 | 6;
|
|
13
14
|
export interface TelegramUser {
|
|
@@ -288,6 +289,8 @@ export interface TelegramApiRetryWait {
|
|
|
288
289
|
retryAfterSeconds?: number;
|
|
289
290
|
}
|
|
290
291
|
export interface TelegramApiCallOptions {
|
|
292
|
+
/** Local per-call effect authority; independent of source completion and transport ownership. */
|
|
293
|
+
assertAuthority?: () => void;
|
|
291
294
|
signal?: AbortSignal;
|
|
292
295
|
maxAttempts?: number;
|
|
293
296
|
retryRateLimit?: boolean;
|
|
@@ -297,6 +300,12 @@ export interface TelegramApiCallOptions {
|
|
|
297
300
|
/** Observability hook fired before a 429 retry wait, never for 5xx waits. */
|
|
298
301
|
onRetryWait?: (wait: TelegramApiRetryWait) => void;
|
|
299
302
|
}
|
|
303
|
+
export declare class TelegramApiAuthorityError extends Error {
|
|
304
|
+
readonly requestIssued: boolean;
|
|
305
|
+
constructor(requestIssued: boolean);
|
|
306
|
+
}
|
|
307
|
+
/** Recheck caller authority, classifying refusal by whether a Bot API request was already issued. */
|
|
308
|
+
export declare function assertTelegramApiCallAuthority(assertAuthority: (() => void) | undefined, requestIssued: boolean): void;
|
|
300
309
|
export interface TelegramFileDownloadOptions {
|
|
301
310
|
signal?: AbortSignal;
|
|
302
311
|
maxFileSizeBytes?: number;
|
|
@@ -338,14 +347,14 @@ export interface TelegramEditGuestInlineMessageContent {
|
|
|
338
347
|
richMessage?: TelegramInputRichMessage;
|
|
339
348
|
parseMode?: "HTML";
|
|
340
349
|
}
|
|
341
|
-
export interface TelegramAnswerCallbackQueryOptions {
|
|
350
|
+
export interface TelegramAnswerCallbackQueryOptions extends Pick<TelegramApiCallOptions, "assertAuthority"> {
|
|
342
351
|
recordRuntimeEvent?: (kind: "api", error: unknown, details?: Record<string, unknown>) => void;
|
|
343
352
|
}
|
|
344
353
|
export interface TelegramApiClient {
|
|
345
354
|
call: <TResponse>(method: string, body: Record<string, unknown>, options?: TelegramApiCallOptions) => Promise<TResponse>;
|
|
346
355
|
callMultipart: <TResponse>(method: string, fields: Record<string, string>, fileField: string, filePath: string, fileName: string, options?: TelegramApiCallOptions) => Promise<TResponse>;
|
|
347
356
|
downloadFile: (fileId: string, suggestedName: string, tempDir: string, options?: TelegramFileDownloadOptions) => Promise<string>;
|
|
348
|
-
answerCallbackQuery: (callbackQueryId: string, text?: string) => Promise<void>;
|
|
357
|
+
answerCallbackQuery: (callbackQueryId: string, text?: string, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<void>;
|
|
349
358
|
answerGuestQuery?: (guestQueryId: string, text?: string, options?: TelegramAnswerGuestQueryOptions) => Promise<void>;
|
|
350
359
|
}
|
|
351
360
|
export interface TelegramApiTargetActivityRuntime {
|
|
@@ -422,16 +431,14 @@ export interface TelegramBridgeApiRuntimeDeps {
|
|
|
422
431
|
chatActionMinIntervalMs?: number;
|
|
423
432
|
chatActionMaxGates?: number;
|
|
424
433
|
}
|
|
425
|
-
export interface TelegramBridgeApiRuntime {
|
|
426
|
-
call: <TResponse>(method: string, body: Record<string, unknown>, options?: TelegramApiCallOptions) => Promise<TResponse>;
|
|
427
|
-
callMultipart: <TResponse>(method: string, fields: Record<string, string>, fileField: string, filePath: string, fileName: string, options?: TelegramApiCallOptions) => Promise<TResponse>;
|
|
434
|
+
export interface TelegramBridgeApiRuntime extends Pick<TelegramApiClient, "call" | "callMultipart"> {
|
|
428
435
|
downloadFile: (fileId: string, suggestedName: string, source?: TelegramAttachmentSource) => Promise<string>;
|
|
429
436
|
deleteWebhook: (signal?: AbortSignal) => Promise<boolean>;
|
|
430
437
|
getUpdates: (body: Record<string, unknown>, signal?: AbortSignal) => Promise<TelegramUpdate[]>;
|
|
431
438
|
setMyCommands: (commands: readonly {
|
|
432
439
|
command: string;
|
|
433
440
|
description: string;
|
|
434
|
-
}[]) => Promise<boolean>;
|
|
441
|
+
}[], options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<boolean>;
|
|
435
442
|
sendChatAction: (chatId: number, action: string, options?: {
|
|
436
443
|
message_thread_id?: number;
|
|
437
444
|
}) => Promise<boolean>;
|
|
@@ -446,12 +453,12 @@ export interface TelegramBridgeApiRuntime {
|
|
|
446
453
|
entities?: unknown[];
|
|
447
454
|
message_thread_id?: number;
|
|
448
455
|
}) => Promise<boolean>;
|
|
449
|
-
sendMessage: (body: TelegramSendMessageBody) => Promise<TelegramSentMessage>;
|
|
450
|
-
sendRichMessage: (body: TelegramSendRichMessageBody) => Promise<TelegramSentMessage>;
|
|
456
|
+
sendMessage: (body: TelegramSendMessageBody, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<TelegramSentMessage>;
|
|
457
|
+
sendRichMessage: (body: TelegramSendRichMessageBody, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<TelegramSentMessage>;
|
|
451
458
|
sendRichMessageDraft: (body: TelegramSendRichMessageDraftBody) => Promise<boolean>;
|
|
452
|
-
editMessageText: (body: TelegramEditMessageTextBody) => Promise<"edited" | "unchanged">;
|
|
459
|
+
editMessageText: (body: TelegramEditMessageTextBody, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<"edited" | "unchanged">;
|
|
453
460
|
editMessageReplyMarkup: (chatId: number, messageId: number, replyMarkup: unknown) => Promise<void>;
|
|
454
|
-
answerCallbackQuery: (callbackQueryId: string, text?: string) => Promise<void>;
|
|
461
|
+
answerCallbackQuery: (callbackQueryId: string, text?: string, options?: Pick<TelegramApiCallOptions, "assertAuthority">) => Promise<void>;
|
|
455
462
|
answerGuestQuery: (guestQueryId: string, text?: string, options?: TelegramAnswerGuestQueryOptions) => Promise<void>;
|
|
456
463
|
/**
|
|
457
464
|
* Temporary Guest Mode ACK experiment: answers the guest query and returns
|
|
@@ -514,8 +521,6 @@ export declare function fetchTelegramBotIdentity(botToken: string, fetchImpl?: t
|
|
|
514
521
|
*/
|
|
515
522
|
export declare function callTelegramMultipart<TResponse>(botToken: string | undefined, method: string, fields: Record<string, string>, fileField: string, filePath: string, fileName: string, options?: TelegramApiCallOptions): Promise<TResponse>;
|
|
516
523
|
export declare function downloadTelegramFile(botToken: string | undefined, fileId: string, suggestedName: string, tempDir: string, options?: TelegramFileDownloadOptions): Promise<string>;
|
|
517
|
-
/** Tooltip-like callback answers omit a terminal sentence period; ellipses and other punctuation stay literal. */
|
|
518
|
-
export declare function formatTelegramCallbackAnswerText(text: string | undefined): string | undefined;
|
|
519
524
|
export declare function answerTelegramCallbackQuery(botToken: string | undefined, callbackQueryId: string, text?: string, options?: TelegramAnswerCallbackQueryOptions): Promise<void>;
|
|
520
525
|
export declare function createTelegramChatActionSender<TAction extends string>(sendChatAction: (chatId: number, action: TAction, options?: {
|
|
521
526
|
message_thread_id?: number;
|