@llblab/pi-telegram 0.54.4 → 0.55.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 +4 -2
- package/BACKLOG.md +1 -1
- package/CHANGELOG.md +11 -0
- package/README.md +5 -3
- package/dist/lib/activity.d.ts +5 -1
- package/dist/lib/activity.js +31 -2
- package/dist/lib/bindings.d.ts +3 -1
- package/dist/lib/bindings.js +8 -2
- package/dist/lib/bus-api.d.ts +2 -2
- package/dist/lib/bus-api.js +7 -2
- package/dist/lib/bus-follower.d.ts +49 -3
- package/dist/lib/bus-follower.js +313 -146
- package/dist/lib/bus-leader.d.ts +3 -0
- package/dist/lib/bus-leader.js +99 -18
- package/dist/lib/bus.d.ts +5 -3
- package/dist/lib/bus.js +13 -10
- package/dist/lib/delivery.d.ts +4 -0
- package/dist/lib/delivery.js +48 -15
- package/dist/lib/extension.js +48 -6
- package/dist/lib/lifecycle.d.ts +68 -0
- package/dist/lib/lifecycle.js +178 -58
- package/dist/lib/machine.d.ts +23 -0
- package/dist/lib/machine.js +37 -0
- package/dist/lib/media.d.ts +114 -28
- package/dist/lib/media.js +292 -119
- package/dist/lib/model.d.ts +54 -15
- package/dist/lib/model.js +98 -53
- package/dist/lib/polling.js +2 -0
- package/dist/lib/preview.d.ts +128 -113
- package/dist/lib/preview.js +448 -366
- package/dist/lib/queue.d.ts +2 -2
- package/dist/lib/queue.js +3 -3
- package/dist/lib/routing.d.ts +6 -1
- package/dist/lib/routing.js +334 -210
- package/dist/lib/runtime.d.ts +77 -11
- package/dist/lib/runtime.js +174 -92
- package/dist/lib/sections.js +0 -1
- package/dist/lib/telegram-api.d.ts +11 -9
- package/dist/lib/telegram-api.js +53 -4
- package/dist/lib/text-groups.d.ts +0 -26
- package/dist/lib/text-groups.js +51 -98
- package/dist/lib/thread-display.js +14 -6
- package/dist/lib/threads.d.ts +18 -5
- package/dist/lib/threads.js +107 -30
- package/dist/lib/turns.d.ts +2 -0
- package/dist/lib/turns.js +6 -1
- package/dist/lib/updates.d.ts +11 -4
- package/dist/lib/updates.js +54 -19
- package/dist/package.json +1 -1
- package/docs/activity.md +1 -1
- package/docs/architecture.md +12 -11
- package/docs/delivery.md +7 -5
- package/docs/multi-instance-bus.md +18 -8
- package/docs/public-api.md +2 -2
- package/docs/ui-style.md +2 -2
- package/lib/activity.ts +29 -5
- package/lib/bindings.ts +9 -1
- package/lib/bus-api.ts +11 -3
- package/lib/bus-follower.ts +417 -203
- package/lib/bus-leader.ts +88 -14
- package/lib/bus.ts +17 -15
- package/lib/delivery.ts +47 -19
- package/lib/extension.ts +51 -9
- package/lib/lifecycle.ts +229 -55
- package/lib/machine.ts +60 -0
- package/lib/media.ts +438 -172
- package/lib/model.ts +169 -74
- package/lib/polling.ts +2 -0
- package/lib/preview.ts +715 -566
- package/lib/queue.ts +5 -5
- package/lib/routing.ts +325 -238
- package/lib/runtime.ts +260 -104
- package/lib/sections.ts +0 -2
- package/lib/telegram-api.ts +61 -8
- package/lib/text-groups.ts +75 -136
- package/lib/thread-display.ts +15 -4
- package/lib/threads.ts +120 -36
- package/lib/turns.ts +8 -1
- package/lib/updates.ts +58 -21
- package/package.json +1 -1
package/lib/activity.ts
CHANGED
|
@@ -243,6 +243,7 @@ function createTelegramActivityContext(
|
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
interface TelegramActivityHandlerQueue {
|
|
246
|
+
notifyDrained?: () => void;
|
|
246
247
|
registration: RegisteredTelegramActivityHandler;
|
|
247
248
|
events: TelegramActivityEvent[];
|
|
248
249
|
running: boolean;
|
|
@@ -287,6 +288,19 @@ function coalesceActivityEvents(
|
|
|
287
288
|
return next;
|
|
288
289
|
}
|
|
289
290
|
|
|
291
|
+
/** Optional observation must not change queue/transport settlement. */
|
|
292
|
+
function captureActivityDrainObserver(capture?: () => (() => void) | undefined): () => void {
|
|
293
|
+
try {
|
|
294
|
+
const notify = capture?.();
|
|
295
|
+
return () => {
|
|
296
|
+
try {
|
|
297
|
+
const emitted: unknown = notify?.();
|
|
298
|
+
if (emitted) void Promise.resolve(emitted).catch(() => undefined);
|
|
299
|
+
} catch { /* Observer failure is inert. */ }
|
|
300
|
+
};
|
|
301
|
+
} catch { return () => {}; }
|
|
302
|
+
}
|
|
303
|
+
|
|
290
304
|
/** @internal */
|
|
291
305
|
export interface TelegramActivityDispatcher {
|
|
292
306
|
hasPending?: () => boolean;
|
|
@@ -297,6 +311,7 @@ export interface TelegramActivityDispatcher {
|
|
|
297
311
|
/** @internal */
|
|
298
312
|
export function createTelegramActivityDispatcher(
|
|
299
313
|
deps: {
|
|
314
|
+
captureDrain?: () => (() => void) | undefined;
|
|
300
315
|
recordFailure?: (
|
|
301
316
|
handlerId: string,
|
|
302
317
|
event: TelegramActivityEvent,
|
|
@@ -306,6 +321,7 @@ export function createTelegramActivityDispatcher(
|
|
|
306
321
|
): TelegramActivityDispatcher {
|
|
307
322
|
const queues = new Map<string, TelegramActivityHandlerQueue>();
|
|
308
323
|
let stopped = false;
|
|
324
|
+
const hasPending = () => Array.from(queues.values()).some(queue => queue.active && (queue.running || queue.events.length > 0));
|
|
309
325
|
const drain = async (queue: TelegramActivityHandlerQueue): Promise<void> => {
|
|
310
326
|
if (queue.running || !queue.active) return;
|
|
311
327
|
queue.running = true;
|
|
@@ -341,13 +357,11 @@ export function createTelegramActivityDispatcher(
|
|
|
341
357
|
}
|
|
342
358
|
} finally {
|
|
343
359
|
queue.running = false;
|
|
360
|
+
if (!stopped && !hasPending()) queue.notifyDrained?.();
|
|
344
361
|
}
|
|
345
362
|
};
|
|
346
363
|
return {
|
|
347
|
-
hasPending
|
|
348
|
-
Array.from(queues.values()).some(
|
|
349
|
-
(queue) => queue.active && (queue.running || queue.events.length > 0),
|
|
350
|
-
),
|
|
364
|
+
hasPending,
|
|
351
365
|
dispatch(event) {
|
|
352
366
|
if (stopped) return;
|
|
353
367
|
for (const registration of getTelegramActivityHandlers()) {
|
|
@@ -361,6 +375,8 @@ export function createTelegramActivityDispatcher(
|
|
|
361
375
|
};
|
|
362
376
|
queues.set(registration.id, queue);
|
|
363
377
|
}
|
|
378
|
+
if (!queue.running && queue.events.length === 0)
|
|
379
|
+
queue.notifyDrained = captureActivityDrainObserver(deps.captureDrain);
|
|
364
380
|
const previous = queue.events.at(-1);
|
|
365
381
|
if (previous && canCoalesceActivityEvents(previous, event)) {
|
|
366
382
|
queue.events[queue.events.length - 1] = coalesceActivityEvents(
|
|
@@ -389,6 +405,7 @@ export function createTelegramActivityDispatcher(
|
|
|
389
405
|
/** @internal */
|
|
390
406
|
export function createTelegramActivityBridgeRuntime(deps: {
|
|
391
407
|
generation: string;
|
|
408
|
+
captureDrain?: () => (() => void) | undefined;
|
|
392
409
|
observeEvent?: (event: TelegramActivityEvent) => void;
|
|
393
410
|
recordFailure?: (
|
|
394
411
|
handlerId: string,
|
|
@@ -407,6 +424,7 @@ export function createTelegramActivityBridgeRuntime(deps: {
|
|
|
407
424
|
runtime = createTelegramActivityRuntime({
|
|
408
425
|
generation: `${deps.generation}:${++generationSequence}`,
|
|
409
426
|
dispatcher: createTelegramActivityDispatcher({
|
|
427
|
+
captureDrain: deps.captureDrain,
|
|
410
428
|
recordFailure: deps.recordFailure,
|
|
411
429
|
}),
|
|
412
430
|
observeEvent: deps.observeEvent,
|
|
@@ -766,7 +784,9 @@ export interface TelegramActivityPublicationRuntime {
|
|
|
766
784
|
reset: () => void;
|
|
767
785
|
}
|
|
768
786
|
|
|
769
|
-
export function createTelegramActivityPublicationRuntime(
|
|
787
|
+
export function createTelegramActivityPublicationRuntime(deps: {
|
|
788
|
+
captureDrain?: () => (() => void) | undefined;
|
|
789
|
+
} = {}): TelegramActivityPublicationRuntime {
|
|
770
790
|
let generation = 0;
|
|
771
791
|
let tail = Promise.resolve();
|
|
772
792
|
let outstanding = 0,
|
|
@@ -774,6 +794,7 @@ export function createTelegramActivityPublicationRuntime(): TelegramActivityPubl
|
|
|
774
794
|
const pending = new Set<() => void>();
|
|
775
795
|
const reserve = (): TelegramActivityPublicationReservation => {
|
|
776
796
|
const admittedGeneration = generation;
|
|
797
|
+
const notifyDrained = captureActivityDrainObserver(deps.captureDrain);
|
|
777
798
|
let state: "pending" | "published" | "cancelled" = "pending";
|
|
778
799
|
let resolve!: (task: (() => Promise<void>) | undefined) => void;
|
|
779
800
|
const ready = new Promise<(() => Promise<void>) | undefined>((accept) => {
|
|
@@ -794,6 +815,7 @@ export function createTelegramActivityPublicationRuntime(): TelegramActivityPubl
|
|
|
794
815
|
})
|
|
795
816
|
.finally(() => {
|
|
796
817
|
outstanding -= 1;
|
|
818
|
+
if (admittedGeneration === generation && outstanding === 0 && !unconfirmed) notifyDrained();
|
|
797
819
|
});
|
|
798
820
|
tail = result.catch(() => {});
|
|
799
821
|
return {
|
|
@@ -818,6 +840,7 @@ export function createTelegramActivityPublicationRuntime(): TelegramActivityPubl
|
|
|
818
840
|
hasUnconfirmed: () => unconfirmed,
|
|
819
841
|
beginWork() {
|
|
820
842
|
const admittedGeneration = generation;
|
|
843
|
+
const notifyDrained = captureActivityDrainObserver(deps.captureDrain);
|
|
821
844
|
let settled = false;
|
|
822
845
|
outstanding += 1;
|
|
823
846
|
return {
|
|
@@ -827,6 +850,7 @@ export function createTelegramActivityPublicationRuntime(): TelegramActivityPubl
|
|
|
827
850
|
outstanding -= 1;
|
|
828
851
|
if (outcome === "unconfirmed" && admittedGeneration === generation)
|
|
829
852
|
unconfirmed = true;
|
|
853
|
+
if (admittedGeneration === generation && outstanding === 0 && !unconfirmed) notifyDrained();
|
|
830
854
|
},
|
|
831
855
|
};
|
|
832
856
|
},
|
package/lib/bindings.ts
CHANGED
|
@@ -654,6 +654,7 @@ export interface TelegramActivityBindingRuntime {
|
|
|
654
654
|
/** Compose public activity fanout, verbosity, and assistant output ordering. */
|
|
655
655
|
export function createTelegramActivityBindingRuntime<TTransportStamp>(deps: {
|
|
656
656
|
generation: string;
|
|
657
|
+
captureDrain?: () => (() => void) | undefined;
|
|
657
658
|
assistantOutput: Omit<
|
|
658
659
|
Parameters<
|
|
659
660
|
typeof createTelegramAssistantOutputBindingRuntime<TTransportStamp>
|
|
@@ -669,7 +670,7 @@ export function createTelegramActivityBindingRuntime<TTransportStamp>(deps: {
|
|
|
669
670
|
"captureAuthority" | "isAuthorityActive" | "recordFailure" | "enqueue"
|
|
670
671
|
>;
|
|
671
672
|
}): TelegramActivityBindingRuntime {
|
|
672
|
-
const publication = Activity.createTelegramActivityPublicationRuntime();
|
|
673
|
+
const publication = Activity.createTelegramActivityPublicationRuntime({ captureDrain: deps.captureDrain });
|
|
673
674
|
const assistantOutputBinding = createTelegramAssistantOutputBindingRuntime({
|
|
674
675
|
...deps.assistantOutput,
|
|
675
676
|
enqueue: publication.enqueue,
|
|
@@ -690,6 +691,7 @@ export function createTelegramActivityBindingRuntime<TTransportStamp>(deps: {
|
|
|
690
691
|
});
|
|
691
692
|
const activityRuntime = Activity.createTelegramActivityBridgeRuntime({
|
|
692
693
|
generation: deps.generation,
|
|
694
|
+
captureDrain: deps.captureDrain,
|
|
693
695
|
observeEvent(event) {
|
|
694
696
|
assistantOutputBinding.observeEvent(event);
|
|
695
697
|
activityVerbosityRuntime.accept(event);
|
|
@@ -1196,6 +1198,7 @@ interface TelegramLifecycleBindingDeps {
|
|
|
1196
1198
|
}) => void;
|
|
1197
1199
|
canSendAgentActivity: (ctx: Pi.ExtensionContext) => boolean;
|
|
1198
1200
|
isSessionContextActive: (ctx: Pi.ExtensionContext) => boolean;
|
|
1201
|
+
getSessionGeneration?: () => number;
|
|
1199
1202
|
isTurnTransportActive?: (turn: Queue.PendingTelegramTurn) => boolean;
|
|
1200
1203
|
updateStatus: TelegramBridgeStatusUpdater;
|
|
1201
1204
|
recordRuntimeEvent: TelegramRuntimeEventRecorder;
|
|
@@ -1246,6 +1249,7 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
1246
1249
|
recordMessageOwnership,
|
|
1247
1250
|
canSendAgentActivity,
|
|
1248
1251
|
isSessionContextActive = () => true,
|
|
1252
|
+
getSessionGeneration,
|
|
1249
1253
|
isTurnTransportActive,
|
|
1250
1254
|
updateStatus,
|
|
1251
1255
|
recordRuntimeEvent,
|
|
@@ -1550,6 +1554,8 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
1550
1554
|
const messageActivityTypingHooks =
|
|
1551
1555
|
Lifecycle.createTelegramMessageActivityTypingHooks({
|
|
1552
1556
|
hasActiveTurn: activeTurnRuntime.has,
|
|
1557
|
+
isContextActive: isSessionContextActive,
|
|
1558
|
+
getSessionGeneration,
|
|
1553
1559
|
startTypingLoop: startActiveTurnTypingLoop,
|
|
1554
1560
|
onMessageStart: previewRuntime.onMessageStart,
|
|
1555
1561
|
onMessageUpdate: previewRuntime.onMessageUpdate,
|
|
@@ -1638,9 +1644,11 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
1638
1644
|
},
|
|
1639
1645
|
async onAgentStart(event, ctx) {
|
|
1640
1646
|
if (!isSessionContextActive(ctx)) return;
|
|
1647
|
+
const generation = getSessionGeneration?.();
|
|
1641
1648
|
agentWorkActive = true;
|
|
1642
1649
|
cancelPendingFinalPublication();
|
|
1643
1650
|
await agentStartWithDedupReset(event, ctx);
|
|
1651
|
+
if (!isSessionContextActive(ctx) || (getSessionGeneration && !Object.is(getSessionGeneration(), generation))) return;
|
|
1644
1652
|
const turn = activeTurnRuntime.get();
|
|
1645
1653
|
activityRuntime.onAgentStart(turn?.target, turn?.replyToMessageId);
|
|
1646
1654
|
startAgentActivityTypingLoop(ctx);
|
package/lib/bus-api.ts
CHANGED
|
@@ -23,10 +23,12 @@ import type {
|
|
|
23
23
|
TelegramSendRichMessageBody,
|
|
24
24
|
TelegramSendRichMessageDraftBody,
|
|
25
25
|
TelegramSentMessage,
|
|
26
|
+
TelegramTypingActionOptions,
|
|
26
27
|
TelegramUpdate,
|
|
27
28
|
} from "./telegram-api.ts";
|
|
28
29
|
import {
|
|
29
30
|
buildTelegramAnswerGuestQueryBody,
|
|
31
|
+
assertTelegramApiCallAuthority,
|
|
30
32
|
isTelegramApiCommitUnknownError,
|
|
31
33
|
isTelegramMessageNotModifiedError,
|
|
32
34
|
TelegramApiAuthorityError,
|
|
@@ -36,6 +38,7 @@ import {
|
|
|
36
38
|
export type TelegramBusApiCall = (
|
|
37
39
|
method: string,
|
|
38
40
|
args: unknown[],
|
|
41
|
+
localOptions?: Pick<TelegramApiCallOptions, "assertAuthority">,
|
|
39
42
|
) => Promise<unknown>;
|
|
40
43
|
|
|
41
44
|
export interface TelegramBusAwareApiRuntimeDeps {
|
|
@@ -235,8 +238,9 @@ function sendTelegramFollowerChatAction(
|
|
|
235
238
|
>,
|
|
236
239
|
chatId: number,
|
|
237
240
|
action: string,
|
|
238
|
-
options?:
|
|
241
|
+
options?: TelegramTypingActionOptions,
|
|
239
242
|
): Promise<boolean> {
|
|
243
|
+
assertTelegramApiCallAuthority(options?.assertAuthority, false);
|
|
240
244
|
const body = withDefaultThreadTarget(
|
|
241
245
|
{
|
|
242
246
|
chat_id: chatId,
|
|
@@ -247,7 +251,11 @@ function sendTelegramFollowerChatAction(
|
|
|
247
251
|
},
|
|
248
252
|
deps.getDefaultTarget?.(),
|
|
249
253
|
);
|
|
250
|
-
|
|
254
|
+
// Only plain single-attempt policy crosses IPC; the captured assertion is a local issuance fence.
|
|
255
|
+
const args = ["sendChatAction", body, ...(action === "typing"
|
|
256
|
+
? [{ maxAttempts: 1, retrySafety: "non-idempotent" }] : [])];
|
|
257
|
+
return deps.callFollowerApi("call", args, options?.assertAuthority
|
|
258
|
+
? { assertAuthority: options.assertAuthority } : undefined).then(asBoolean);
|
|
251
259
|
}
|
|
252
260
|
|
|
253
261
|
export function createTelegramBusAwareApiRuntime(
|
|
@@ -345,7 +353,7 @@ export function createTelegramBusAwareApiRuntime(
|
|
|
345
353
|
},
|
|
346
354
|
sendTypingAction(
|
|
347
355
|
chatId: number,
|
|
348
|
-
options?:
|
|
356
|
+
options?: TelegramTypingActionOptions,
|
|
349
357
|
): Promise<unknown> {
|
|
350
358
|
return deps.ownsDirect()
|
|
351
359
|
? deps.directRuntime.sendTypingAction(chatId, options)
|