@llblab/pi-telegram 0.21.0 → 0.22.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 +6 -4
- package/CHANGELOG.md +477 -447
- package/README.md +9 -7
- package/docs/architecture.md +26 -14
- package/docs/delivery.md +2 -1
- package/docs/locks.md +9 -5
- package/docs/multi-instance-bus.md +5 -5
- package/docs/public-api.md +2 -2
- package/index.ts +311 -633
- package/lib/bindings.ts +59 -21
- package/lib/bus-api.ts +12 -2
- package/lib/bus-follower.ts +327 -83
- package/lib/bus-leader.ts +201 -121
- package/lib/bus.ts +345 -37
- package/lib/config.ts +168 -28
- package/lib/delivery.ts +148 -61
- package/lib/lifecycle.ts +199 -8
- package/lib/locks.ts +569 -56
- package/lib/logs.ts +178 -19
- package/lib/media.ts +79 -24
- package/lib/model.ts +5 -10
- package/lib/ownership.ts +150 -6
- package/lib/polling.ts +156 -7
- package/lib/preview.ts +15 -3
- package/lib/queue.ts +167 -20
- package/lib/routing.ts +68 -12
- package/lib/sync.ts +105 -32
- package/lib/telegram-api.ts +86 -10
- package/lib/text-groups.ts +71 -15
- package/lib/thread-reconciler.ts +49 -36
- package/lib/threads.ts +505 -118
- package/package.json +1 -1
package/lib/lifecycle.ts
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
* Binds prepared Telegram lifecycle runtimes to pi extension lifecycle events
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import * as BusFollower from "./bus-follower.ts";
|
|
8
|
+
import * as Queue from "./queue.ts";
|
|
7
9
|
import type {
|
|
8
10
|
AgentEndEvent,
|
|
9
11
|
AgentSettledEvent,
|
|
@@ -50,6 +52,7 @@ type TelegramLifecycleModel = ExtensionContext["model"];
|
|
|
50
52
|
type TelegramLifecycleMessage = AgentEndEvent["messages"][number];
|
|
51
53
|
|
|
52
54
|
export interface TelegramLifecycleRegistrationDeps {
|
|
55
|
+
isSessionActive?: (ctx: ExtensionContext) => boolean;
|
|
53
56
|
onInput?: (event: InputEvent, ctx: ExtensionContext) => Promise<void> | void;
|
|
54
57
|
onSessionStart: (
|
|
55
58
|
event: SessionStartEvent,
|
|
@@ -122,21 +125,73 @@ export interface TelegramSessionLifecycleHooks {
|
|
|
122
125
|
|
|
123
126
|
export interface TelegramSessionContextStore<TContext> {
|
|
124
127
|
get: () => TContext | undefined;
|
|
125
|
-
|
|
126
|
-
|
|
128
|
+
getGeneration: () => number;
|
|
129
|
+
isCurrent: (ctx: TContext, generation?: number) => boolean;
|
|
130
|
+
set: (ctx: TContext) => number;
|
|
131
|
+
clear: (ctx?: TContext) => boolean;
|
|
127
132
|
}
|
|
128
133
|
|
|
129
|
-
export function createTelegramSessionContextStore<
|
|
130
|
-
|
|
131
|
-
|
|
134
|
+
export function createTelegramSessionContextStore<TContext>(
|
|
135
|
+
options: {
|
|
136
|
+
getIdentity?: (ctx: TContext) => unknown;
|
|
137
|
+
} = {},
|
|
138
|
+
): TelegramSessionContextStore<TContext> {
|
|
132
139
|
let currentContext: TContext | undefined;
|
|
140
|
+
let currentIdentity: unknown;
|
|
141
|
+
let generation = 0;
|
|
142
|
+
const objectIdentityGenerations = new WeakMap<object, number>();
|
|
143
|
+
const primitiveIdentityGenerations = new Map<unknown, number>();
|
|
144
|
+
const resolveIdentity = (ctx: TContext): unknown =>
|
|
145
|
+
options.getIdentity?.(ctx) ?? ctx;
|
|
146
|
+
const getIdentityGeneration = (identity: unknown): number | undefined =>
|
|
147
|
+
(typeof identity === "object" && identity !== null) ||
|
|
148
|
+
typeof identity === "function"
|
|
149
|
+
? objectIdentityGenerations.get(identity as object)
|
|
150
|
+
: primitiveIdentityGenerations.get(identity);
|
|
151
|
+
const setIdentityGeneration = (identity: unknown, value: number): void => {
|
|
152
|
+
if (
|
|
153
|
+
(typeof identity === "object" && identity !== null) ||
|
|
154
|
+
typeof identity === "function"
|
|
155
|
+
) {
|
|
156
|
+
objectIdentityGenerations.set(identity as object, value);
|
|
157
|
+
} else {
|
|
158
|
+
primitiveIdentityGenerations.set(identity, value);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
133
161
|
return {
|
|
134
162
|
get: () => currentContext,
|
|
163
|
+
getGeneration: () => generation,
|
|
164
|
+
isCurrent: (ctx, expectedGeneration) => {
|
|
165
|
+
if (currentContext === undefined) return false;
|
|
166
|
+
const identity = resolveIdentity(ctx);
|
|
167
|
+
return (
|
|
168
|
+
identity === currentIdentity &&
|
|
169
|
+
getIdentityGeneration(identity) === generation &&
|
|
170
|
+
(expectedGeneration === undefined || generation === expectedGeneration)
|
|
171
|
+
);
|
|
172
|
+
},
|
|
135
173
|
set: (ctx) => {
|
|
136
174
|
currentContext = ctx;
|
|
175
|
+
currentIdentity = resolveIdentity(ctx);
|
|
176
|
+
generation += 1;
|
|
177
|
+
setIdentityGeneration(currentIdentity, generation);
|
|
178
|
+
return generation;
|
|
137
179
|
},
|
|
138
|
-
clear: () => {
|
|
180
|
+
clear: (ctx) => {
|
|
181
|
+
if (ctx !== undefined) {
|
|
182
|
+
const identity = resolveIdentity(ctx);
|
|
183
|
+
if (
|
|
184
|
+
identity !== currentIdentity ||
|
|
185
|
+
getIdentityGeneration(identity) !== generation
|
|
186
|
+
) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (currentContext === undefined) return false;
|
|
139
191
|
currentContext = undefined;
|
|
192
|
+
currentIdentity = undefined;
|
|
193
|
+
generation += 1;
|
|
194
|
+
return true;
|
|
140
195
|
},
|
|
141
196
|
};
|
|
142
197
|
}
|
|
@@ -148,12 +203,127 @@ export function createTelegramSessionContextTracker(
|
|
|
148
203
|
onSessionStart: async (_event, ctx) => {
|
|
149
204
|
store.set(ctx);
|
|
150
205
|
},
|
|
151
|
-
onSessionShutdown: async () => {
|
|
152
|
-
store.clear();
|
|
206
|
+
onSessionShutdown: async (_event, ctx) => {
|
|
207
|
+
store.clear(ctx);
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function createTelegramSessionGenerationFence(
|
|
213
|
+
store: TelegramSessionContextStore<ExtensionContext>,
|
|
214
|
+
hooks: TelegramSessionLifecycleHooks,
|
|
215
|
+
): TelegramSessionLifecycleHooks {
|
|
216
|
+
return {
|
|
217
|
+
async onSessionStart(event, ctx) {
|
|
218
|
+
const generation = store.set(ctx);
|
|
219
|
+
await hooks.onSessionStart(event, ctx);
|
|
220
|
+
if (!store.isCurrent(ctx, generation)) return;
|
|
221
|
+
},
|
|
222
|
+
async onSessionShutdown(event, ctx) {
|
|
223
|
+
const generation = store.getGeneration();
|
|
224
|
+
if (!store.isCurrent(ctx, generation)) return;
|
|
225
|
+
await hooks.onSessionShutdown(event, ctx);
|
|
226
|
+
store.clear(ctx);
|
|
153
227
|
},
|
|
154
228
|
};
|
|
155
229
|
}
|
|
156
230
|
|
|
231
|
+
export interface TelegramBridgeSessionServiceRuntime {
|
|
232
|
+
resumeGroupedInput(ctx: ExtensionContext): void;
|
|
233
|
+
suspendGroupedInput(): void;
|
|
234
|
+
delivery: {
|
|
235
|
+
onSessionStart(): Promise<void>;
|
|
236
|
+
onSessionShutdown(): Promise<void>;
|
|
237
|
+
};
|
|
238
|
+
polling: {
|
|
239
|
+
onSessionStart(
|
|
240
|
+
event: SessionStartEvent,
|
|
241
|
+
ctx: ExtensionContext,
|
|
242
|
+
): Promise<void>;
|
|
243
|
+
};
|
|
244
|
+
capabilityMonitor: { start(ctx: ExtensionContext): void; stop(): void };
|
|
245
|
+
queueWatchdog: { start(ctx: ExtensionContext): void; stop(): void };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export interface TelegramBridgeSessionLifecycleAssemblyDeps<
|
|
249
|
+
TQueueItem,
|
|
250
|
+
TModel = unknown,
|
|
251
|
+
> {
|
|
252
|
+
contextStore: TelegramSessionContextStore<ExtensionContext>;
|
|
253
|
+
queue: Omit<
|
|
254
|
+
Queue.TelegramSessionLifecycleRuntimeDeps<
|
|
255
|
+
ExtensionContext,
|
|
256
|
+
TQueueItem,
|
|
257
|
+
TModel
|
|
258
|
+
>,
|
|
259
|
+
"isSessionActive" | "stopPolling" | "clearPendingMediaGroups"
|
|
260
|
+
>;
|
|
261
|
+
follower: Omit<
|
|
262
|
+
BusFollower.TelegramBusFollowerSessionRefreshHookDeps<ExtensionContext>,
|
|
263
|
+
"isSessionActive"
|
|
264
|
+
> &
|
|
265
|
+
BusFollower.TelegramBusFollowerSessionReplacementSuspenderDeps;
|
|
266
|
+
services: TelegramBridgeSessionServiceRuntime;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function createTelegramBridgeSessionLifecycleAssembly<
|
|
270
|
+
TQueueItem,
|
|
271
|
+
TModel = unknown,
|
|
272
|
+
>(
|
|
273
|
+
deps: TelegramBridgeSessionLifecycleAssemblyDeps<TQueueItem, TModel>,
|
|
274
|
+
): TelegramSessionLifecycleHooks {
|
|
275
|
+
const isSessionActive = deps.contextStore.isCurrent;
|
|
276
|
+
const suspendForReplacement =
|
|
277
|
+
BusFollower.createTelegramBusFollowerSessionReplacementSuspender({
|
|
278
|
+
registrationState: deps.follower.registrationState,
|
|
279
|
+
instanceId: deps.follower.instanceId,
|
|
280
|
+
suspendPolling: deps.follower.suspendPolling,
|
|
281
|
+
recordRuntimeEvent: deps.follower.recordRuntimeEvent,
|
|
282
|
+
});
|
|
283
|
+
const queueLifecycle = Queue.createTelegramSessionLifecycleRuntime({
|
|
284
|
+
...deps.queue,
|
|
285
|
+
isSessionActive,
|
|
286
|
+
stopPolling: suspendForReplacement,
|
|
287
|
+
clearPendingMediaGroups: deps.services.suspendGroupedInput,
|
|
288
|
+
});
|
|
289
|
+
const servicesLifecycle = appendTelegramLifecycleHooks(
|
|
290
|
+
queueLifecycle,
|
|
291
|
+
{
|
|
292
|
+
async onSessionStart(event, ctx) {
|
|
293
|
+
deps.services.resumeGroupedInput(ctx);
|
|
294
|
+
await deps.services.delivery.onSessionStart();
|
|
295
|
+
await deps.services.polling.onSessionStart(event, ctx);
|
|
296
|
+
deps.services.capabilityMonitor.start(ctx);
|
|
297
|
+
deps.services.queueWatchdog.start(ctx);
|
|
298
|
+
},
|
|
299
|
+
async onSessionShutdown() {
|
|
300
|
+
await deps.services.delivery.onSessionShutdown();
|
|
301
|
+
deps.services.queueWatchdog.stop();
|
|
302
|
+
deps.services.capabilityMonitor.stop();
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
isSessionActive,
|
|
306
|
+
);
|
|
307
|
+
const followerLifecycle = appendTelegramLifecycleHooks(
|
|
308
|
+
servicesLifecycle,
|
|
309
|
+
{
|
|
310
|
+
onSessionStart: BusFollower.createTelegramBusFollowerSessionRefreshHook({
|
|
311
|
+
registrationState: deps.follower.registrationState,
|
|
312
|
+
registrationRuntime: deps.follower.registrationRuntime,
|
|
313
|
+
getLeaderState: deps.follower.getLeaderState,
|
|
314
|
+
isSessionActive,
|
|
315
|
+
updateStatus: deps.follower.updateStatus,
|
|
316
|
+
recordRuntimeEvent: deps.follower.recordRuntimeEvent,
|
|
317
|
+
}),
|
|
318
|
+
},
|
|
319
|
+
isSessionActive,
|
|
320
|
+
);
|
|
321
|
+
return createTelegramSessionGenerationFence(
|
|
322
|
+
deps.contextStore,
|
|
323
|
+
followerLifecycle,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
157
327
|
type TelegramLifecycleTimer = number | ReturnType<typeof setTimeout>;
|
|
158
328
|
|
|
159
329
|
function unrefTelegramLifecycleTimer(timer: TelegramLifecycleTimer): void {
|
|
@@ -162,6 +332,7 @@ function unrefTelegramLifecycleTimer(timer: TelegramLifecycleTimer): void {
|
|
|
162
332
|
}
|
|
163
333
|
|
|
164
334
|
export interface TelegramCompactionObserverRuntimeDeps<TContext> {
|
|
335
|
+
isContextActive?: (ctx: TContext) => boolean;
|
|
165
336
|
setCompactionInProgress: (inProgress: boolean) => void;
|
|
166
337
|
updateStatus: (ctx: TContext) => void;
|
|
167
338
|
startTypingLoop?: (ctx: TContext) => boolean | void;
|
|
@@ -206,6 +377,7 @@ export function createTelegramCompactionObserverRuntime<TContext>(
|
|
|
206
377
|
};
|
|
207
378
|
return {
|
|
208
379
|
onSessionBeforeCompact: (_event, ctx) => {
|
|
380
|
+
if (deps.isContextActive && !deps.isContextActive(ctx)) return;
|
|
209
381
|
deps.setCompactionInProgress(true);
|
|
210
382
|
const typingStartResult = deps.startTypingLoop?.(ctx);
|
|
211
383
|
typingStartedByObserver =
|
|
@@ -214,6 +386,7 @@ export function createTelegramCompactionObserverRuntime<TContext>(
|
|
|
214
386
|
clearFallbackTimer();
|
|
215
387
|
fallbackTimer = setTimer(() => {
|
|
216
388
|
fallbackTimer = undefined;
|
|
389
|
+
if (deps.isContextActive && !deps.isContextActive(ctx)) return;
|
|
217
390
|
deps.setCompactionInProgress(false);
|
|
218
391
|
if (typingStartedByObserver) deps.stopTypingLoop?.();
|
|
219
392
|
typingStartedByObserver = false;
|
|
@@ -229,6 +402,7 @@ export function createTelegramCompactionObserverRuntime<TContext>(
|
|
|
229
402
|
},
|
|
230
403
|
onSessionCompact: (_event, ctx) => {
|
|
231
404
|
clearFallbackTimer();
|
|
405
|
+
if (deps.isContextActive && !deps.isContextActive(ctx)) return;
|
|
232
406
|
deps.setCompactionInProgress(false);
|
|
233
407
|
if (typingStartedByObserver) deps.stopTypingLoop?.();
|
|
234
408
|
typingStartedByObserver = false;
|
|
@@ -314,14 +488,17 @@ export interface TelegramExtraLifecycleHooks {
|
|
|
314
488
|
export function appendTelegramLifecycleHooks(
|
|
315
489
|
base: TelegramSessionLifecycleHooks,
|
|
316
490
|
extra: TelegramExtraLifecycleHooks,
|
|
491
|
+
isSessionActive?: (ctx: ExtensionContext) => boolean,
|
|
317
492
|
): TelegramSessionLifecycleHooks {
|
|
318
493
|
return {
|
|
319
494
|
onSessionStart: async (event, ctx) => {
|
|
320
495
|
await base.onSessionStart(event, ctx);
|
|
496
|
+
if (isSessionActive?.(ctx) === false) return;
|
|
321
497
|
await extra.onSessionStart?.(event, ctx);
|
|
322
498
|
},
|
|
323
499
|
onSessionShutdown: async (event, ctx) => {
|
|
324
500
|
await base.onSessionShutdown(event, ctx);
|
|
501
|
+
if (isSessionActive?.(ctx) === false) return;
|
|
325
502
|
await extra.onSessionShutdown?.(event, ctx);
|
|
326
503
|
},
|
|
327
504
|
};
|
|
@@ -331,7 +508,10 @@ export function registerTelegramLifecycleHooks(
|
|
|
331
508
|
pi: ExtensionAPI,
|
|
332
509
|
deps: TelegramLifecycleRegistrationDeps,
|
|
333
510
|
): void {
|
|
511
|
+
const isActive = (ctx: ExtensionContext): boolean =>
|
|
512
|
+
deps.isSessionActive?.(ctx) !== false;
|
|
334
513
|
pi.on("input", async (event, ctx) => {
|
|
514
|
+
if (!isActive(ctx)) return;
|
|
335
515
|
await deps.onInput?.(event, ctx);
|
|
336
516
|
});
|
|
337
517
|
pi.on("session_start", async (event, ctx) => {
|
|
@@ -341,39 +521,50 @@ export function registerTelegramLifecycleHooks(
|
|
|
341
521
|
await deps.onSessionShutdown(event, ctx);
|
|
342
522
|
});
|
|
343
523
|
pi.on("session_before_compact", async (event, ctx) => {
|
|
524
|
+
if (!isActive(ctx)) return;
|
|
344
525
|
await deps.onSessionBeforeCompact?.(event, ctx);
|
|
345
526
|
});
|
|
346
527
|
pi.on("session_compact", async (event, ctx) => {
|
|
528
|
+
if (!isActive(ctx)) return;
|
|
347
529
|
await deps.onSessionCompact?.(event, ctx);
|
|
348
530
|
});
|
|
349
531
|
pi.on("before_agent_start", async (event, ctx) => {
|
|
350
532
|
return deps.onBeforeAgentStart(event, ctx);
|
|
351
533
|
});
|
|
352
534
|
pi.on("model_select", async (event, ctx) => {
|
|
535
|
+
if (!isActive(ctx)) return;
|
|
353
536
|
await deps.onModelSelect(event, ctx);
|
|
354
537
|
});
|
|
355
538
|
pi.on("agent_start", async (event, ctx) => {
|
|
539
|
+
if (!isActive(ctx)) return;
|
|
356
540
|
await deps.onAgentStart(event, ctx);
|
|
357
541
|
});
|
|
358
542
|
pi.on("tool_execution_start", async (event, ctx) => {
|
|
543
|
+
if (!isActive(ctx)) return;
|
|
359
544
|
await deps.onToolExecutionStart(event, ctx);
|
|
360
545
|
});
|
|
361
546
|
pi.on("tool_execution_update", async (event, ctx) => {
|
|
547
|
+
if (!isActive(ctx)) return;
|
|
362
548
|
await deps.onToolExecutionUpdate?.(event, ctx);
|
|
363
549
|
});
|
|
364
550
|
pi.on("tool_execution_end", async (event, ctx) => {
|
|
551
|
+
if (!isActive(ctx)) return;
|
|
365
552
|
await deps.onToolExecutionEnd(event, ctx);
|
|
366
553
|
});
|
|
367
554
|
pi.on("message_start", async (event, ctx) => {
|
|
555
|
+
if (!isActive(ctx)) return;
|
|
368
556
|
await deps.onMessageStart(event, ctx);
|
|
369
557
|
});
|
|
370
558
|
pi.on("message_update", async (event, ctx) => {
|
|
559
|
+
if (!isActive(ctx)) return;
|
|
371
560
|
await deps.onMessageUpdate(event, ctx);
|
|
372
561
|
});
|
|
373
562
|
pi.on("agent_end", async (event, ctx) => {
|
|
563
|
+
if (!isActive(ctx)) return;
|
|
374
564
|
await deps.onAgentEnd(event, ctx);
|
|
375
565
|
});
|
|
376
566
|
pi.on("agent_settled", async (event, ctx) => {
|
|
567
|
+
if (!isActive(ctx)) return;
|
|
377
568
|
await deps.onAgentSettled?.(event, ctx);
|
|
378
569
|
});
|
|
379
570
|
}
|