@llblab/pi-telegram 0.54.3 → 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 +21 -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 +5 -1
- package/dist/lib/bindings.js +9 -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 +56 -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 +14 -2
- package/dist/lib/routing.js +546 -207
- 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 +20 -5
- package/dist/lib/threads.js +111 -32
- 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 +16 -15
- 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 +8 -6
- package/lib/activity.ts +29 -5
- package/lib/bindings.ts +13 -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 +59 -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 +644 -283
- 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 +130 -38
- package/lib/turns.ts +8 -1
- package/lib/updates.ts +58 -21
- package/package.json +1 -1
package/dist/lib/model.d.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Zones: pi agent model control, telegram controls, queue continuation
|
|
4
4
|
* Owns model identity, thinking levels, scoped resolution, current-model state, and in-flight model switching
|
|
5
5
|
*/
|
|
6
|
+
import * as Machine from "./machine.ts";
|
|
6
7
|
import { type PendingTelegramTurn } from "./queue.ts";
|
|
7
8
|
export interface MenuModel {
|
|
8
9
|
provider: string;
|
|
@@ -44,17 +45,10 @@ export declare function parseTelegramScopedModelPatternList(value: string): stri
|
|
|
44
45
|
export declare function parseTelegramCliScopedModelPatterns(args: string[]): string[] | undefined;
|
|
45
46
|
export declare function resolveScopedModelPatterns<TModel extends MenuModel = MenuModel>(patterns: string[], availableModels: TModel[]): ScopedTelegramModel<TModel>[];
|
|
46
47
|
export declare function sortScopedModels<TModel extends MenuModel = MenuModel>(models: ScopedTelegramModel<TModel>[], currentModel: TModel | undefined): ScopedTelegramModel<TModel>[];
|
|
47
|
-
export interface PendingModelSwitchStore<TSelection> {
|
|
48
|
-
get: () => TSelection | undefined;
|
|
49
|
-
set: (selection: TSelection | undefined) => void;
|
|
50
|
-
clear: () => void;
|
|
51
|
-
has: () => boolean;
|
|
52
|
-
}
|
|
53
48
|
export interface TelegramInFlightModelSwitchState {
|
|
54
49
|
isIdle: boolean;
|
|
55
50
|
hasAbortHandler: boolean;
|
|
56
51
|
}
|
|
57
|
-
export declare function createPendingModelSwitchStore<TSelection>(): PendingModelSwitchStore<TSelection>;
|
|
58
52
|
export declare function canRestartAgentRunForTelegramModelSwitch(state: TelegramInFlightModelSwitchState): boolean;
|
|
59
53
|
export declare function shouldTriggerPendingTelegramModelSwitchAbort(state: {
|
|
60
54
|
hasPendingModelSwitch: boolean;
|
|
@@ -62,12 +56,6 @@ export declare function shouldTriggerPendingTelegramModelSwitchAbort(state: {
|
|
|
62
56
|
hasAbortHandler: boolean;
|
|
63
57
|
activeToolExecutions: number;
|
|
64
58
|
}): boolean;
|
|
65
|
-
export declare function restartTelegramModelSwitchContinuation<TTurn, TSelection>(state: {
|
|
66
|
-
activeTurn: TTurn | undefined;
|
|
67
|
-
abort: (() => void) | undefined;
|
|
68
|
-
selection: TSelection;
|
|
69
|
-
queueContinuation: (turn: TTurn, selection: TSelection) => void;
|
|
70
|
-
}): boolean;
|
|
71
59
|
export declare function buildTelegramModelSwitchContinuationText<TModel extends MenuModel>(telegramPrefix: string, model: TModel, thinkingLevel?: ScopedTelegramModel<TModel>["thinkingLevel"]): string;
|
|
72
60
|
export type TelegramModelSwitchContinuationSource = Pick<PendingTelegramTurn, "chatId" | "replyToMessageId" | "target">;
|
|
73
61
|
export declare function buildTelegramModelSwitchContinuationTurn<TModel extends MenuModel>(options: {
|
|
@@ -100,8 +88,6 @@ export declare function createTelegramModelSwitchContinuationQueueRuntime<TConte
|
|
|
100
88
|
}): (turn: TelegramModelSwitchContinuationSource, selection: TSelection, ctx: TContext) => void;
|
|
101
89
|
export interface TelegramModelSwitchControllerDeps<TContext, TSelection> {
|
|
102
90
|
isIdle: (ctx: TContext) => boolean;
|
|
103
|
-
getPendingModelSwitch: () => TSelection | undefined;
|
|
104
|
-
setPendingModelSwitch: (selection: TSelection | undefined) => void;
|
|
105
91
|
getActiveTurn: () => PendingTelegramTurn | undefined;
|
|
106
92
|
getAbortHandler: () => (() => void) | undefined;
|
|
107
93
|
hasAbortHandler: () => boolean;
|
|
@@ -111,6 +97,8 @@ export interface TelegramModelSwitchControllerDeps<TContext, TSelection> {
|
|
|
111
97
|
}
|
|
112
98
|
export interface TelegramModelSwitchController<TContext, TSelection> {
|
|
113
99
|
canOfferInFlightSwitch: (ctx: TContext) => boolean;
|
|
100
|
+
/** A staged switch waits for its abort; queue dispatch and live-work observation treat it as pending control. */
|
|
101
|
+
hasPendingSwitch: () => boolean;
|
|
114
102
|
stagePendingSwitch: (selection: TSelection, ctx: TContext, continuationTurn?: TelegramModelSwitchContinuationSource) => void;
|
|
115
103
|
clearPendingSwitch: () => void;
|
|
116
104
|
queueContinuation: (turn: TelegramModelSwitchContinuationSource, selection: TSelection, ctx: TContext) => void;
|
|
@@ -124,4 +112,55 @@ export interface TelegramModelSwitchControllerRuntimeDeps<TContext, TSelection e
|
|
|
124
112
|
appendQueuedItem: (item: PendingTelegramTurn, ctx: TContext) => void;
|
|
125
113
|
}
|
|
126
114
|
export declare function createTelegramModelSwitchControllerRuntime<TContext, TSelection extends ScopedTelegramModel>(deps: TelegramModelSwitchControllerRuntimeDeps<TContext, TSelection>): TelegramModelSwitchController<TContext, TSelection>;
|
|
115
|
+
/** The whole model-switch state; only `transitionTelegramModelSwitch` produces a new one. */
|
|
116
|
+
export interface TelegramModelSwitchMachineState<TSelection> {
|
|
117
|
+
/** One staged switch: the selection and the turn its continuation answers move and clear together. */
|
|
118
|
+
readonly pending: {
|
|
119
|
+
readonly selection: TSelection;
|
|
120
|
+
/** The active Telegram turn at staging, or the model menu's own target for a local run. */
|
|
121
|
+
readonly turn?: TelegramModelSwitchContinuationSource;
|
|
122
|
+
} | null;
|
|
123
|
+
}
|
|
124
|
+
/** Facts owned by other domains, sampled by the shell when the event is dispatched. */
|
|
125
|
+
interface TelegramModelSwitchRunFacts {
|
|
126
|
+
activeTurn?: TelegramModelSwitchContinuationSource;
|
|
127
|
+
hasAbortHandler: boolean;
|
|
128
|
+
activeToolExecutions: number;
|
|
129
|
+
}
|
|
130
|
+
export type TelegramModelSwitchEvent<TSelection> = {
|
|
131
|
+
type: "staged";
|
|
132
|
+
selection: TSelection;
|
|
133
|
+
turn?: TelegramModelSwitchContinuationSource;
|
|
134
|
+
} | ({
|
|
135
|
+
type: "abortRequested";
|
|
136
|
+
} & TelegramModelSwitchRunFacts) | {
|
|
137
|
+
type: "restartRequested";
|
|
138
|
+
selection: TSelection;
|
|
139
|
+
turn?: TelegramModelSwitchContinuationSource;
|
|
140
|
+
hasAbortHandler: boolean;
|
|
141
|
+
} | {
|
|
142
|
+
type: "cleared";
|
|
143
|
+
};
|
|
144
|
+
export type TelegramModelSwitchEffect<TSelection> = {
|
|
145
|
+
type: "queueContinuation";
|
|
146
|
+
turn: TelegramModelSwitchContinuationSource;
|
|
147
|
+
selection: TSelection;
|
|
148
|
+
} | {
|
|
149
|
+
type: "abort";
|
|
150
|
+
};
|
|
151
|
+
export interface TelegramModelSwitchStep<TSelection> extends Machine.TelegramMachineStep<TelegramModelSwitchMachineState<TSelection>, TelegramModelSwitchEffect<TSelection>> {
|
|
152
|
+
/** `abortRequested` / `restartRequested`: the run was stopped for a continuation. */
|
|
153
|
+
result: boolean;
|
|
154
|
+
}
|
|
155
|
+
export declare function createTelegramModelSwitchMachineState<TSelection>(): TelegramModelSwitchMachineState<TSelection>;
|
|
156
|
+
/**
|
|
157
|
+
* Pure model-switch transition. A staged switch fires only once no tool runs and an abort handler exists; any restart
|
|
158
|
+
* or clear consumes the staged switch too, so a stale selection can never abort the continuation run it started.
|
|
159
|
+
*/
|
|
160
|
+
export declare function transitionTelegramModelSwitch<TSelection>(state: TelegramModelSwitchMachineState<TSelection>, event: TelegramModelSwitchEvent<TSelection>): TelegramModelSwitchStep<TSelection>;
|
|
161
|
+
/**
|
|
162
|
+
* The shell around `transitionTelegramModelSwitch`: it samples the active turn, abort handler and tool count into
|
|
163
|
+
* events, and runs the continuation and abort effects with the caller's context and the handler it sampled.
|
|
164
|
+
*/
|
|
127
165
|
export declare function createTelegramModelSwitchController<TContext, TSelection>(deps: TelegramModelSwitchControllerDeps<TContext, TSelection>): TelegramModelSwitchController<TContext, TSelection>;
|
|
166
|
+
export {};
|
package/dist/lib/model.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Zones: pi agent model control, telegram controls, queue continuation
|
|
4
4
|
* Owns model identity, thinking levels, scoped resolution, current-model state, and in-flight model switching
|
|
5
5
|
*/
|
|
6
|
+
import * as Machine from "./machine.js";
|
|
6
7
|
import { truncateTelegramQueueSummary, } from "./queue.js";
|
|
7
8
|
import { TELEGRAM_PREFIX } from "./turns.js";
|
|
8
9
|
export const THINKING_LEVELS = [
|
|
@@ -249,19 +250,6 @@ export function sortScopedModels(models, currentModel) {
|
|
|
249
250
|
});
|
|
250
251
|
return sorted;
|
|
251
252
|
}
|
|
252
|
-
export function createPendingModelSwitchStore() {
|
|
253
|
-
let selection;
|
|
254
|
-
return {
|
|
255
|
-
get: () => selection,
|
|
256
|
-
set: (nextSelection) => {
|
|
257
|
-
selection = nextSelection;
|
|
258
|
-
},
|
|
259
|
-
clear: () => {
|
|
260
|
-
selection = undefined;
|
|
261
|
-
},
|
|
262
|
-
has: () => selection !== undefined,
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
253
|
export function canRestartAgentRunForTelegramModelSwitch(state) {
|
|
266
254
|
return !state.isIdle && state.hasAbortHandler;
|
|
267
255
|
}
|
|
@@ -271,13 +259,6 @@ export function shouldTriggerPendingTelegramModelSwitchAbort(state) {
|
|
|
271
259
|
state.hasAbortHandler &&
|
|
272
260
|
state.activeToolExecutions === 0);
|
|
273
261
|
}
|
|
274
|
-
export function restartTelegramModelSwitchContinuation(state) {
|
|
275
|
-
if (!state.activeTurn || !state.abort)
|
|
276
|
-
return false;
|
|
277
|
-
state.queueContinuation(state.activeTurn, state.selection);
|
|
278
|
-
state.abort();
|
|
279
|
-
return true;
|
|
280
|
-
}
|
|
281
262
|
export function buildTelegramModelSwitchContinuationText(telegramPrefix, model, thinkingLevel) {
|
|
282
263
|
const modelLabel = `${model.provider}/${model.id}`;
|
|
283
264
|
const thinkingSuffix = thinkingLevel ? `; thinking: ${thinkingLevel}` : "";
|
|
@@ -332,8 +313,6 @@ export function createTelegramModelSwitchContinuationQueueRuntime(deps) {
|
|
|
332
313
|
export function createTelegramModelSwitchControllerRuntime(deps) {
|
|
333
314
|
return createTelegramModelSwitchController({
|
|
334
315
|
isIdle: deps.isIdle,
|
|
335
|
-
getPendingModelSwitch: deps.getPendingModelSwitch,
|
|
336
|
-
setPendingModelSwitch: deps.setPendingModelSwitch,
|
|
337
316
|
getActiveTurn: deps.getActiveTurn,
|
|
338
317
|
getAbortHandler: deps.getAbortHandler,
|
|
339
318
|
hasAbortHandler: deps.hasAbortHandler,
|
|
@@ -347,36 +326,106 @@ export function createTelegramModelSwitchControllerRuntime(deps) {
|
|
|
347
326
|
updateStatus: deps.updateStatus,
|
|
348
327
|
});
|
|
349
328
|
}
|
|
329
|
+
export function createTelegramModelSwitchMachineState() {
|
|
330
|
+
return { pending: null };
|
|
331
|
+
}
|
|
332
|
+
/** The continuation is queued before the abort, so the stopped run always has its successor waiting. */
|
|
333
|
+
const restartFor = (turn, selection) => ({
|
|
334
|
+
state: { pending: null },
|
|
335
|
+
effects: [{ type: "queueContinuation", turn, selection }, { type: "abort" }],
|
|
336
|
+
result: true,
|
|
337
|
+
});
|
|
338
|
+
/**
|
|
339
|
+
* Pure model-switch transition. A staged switch fires only once no tool runs and an abort handler exists; any restart
|
|
340
|
+
* or clear consumes the staged switch too, so a stale selection can never abort the continuation run it started.
|
|
341
|
+
*/
|
|
342
|
+
export function transitionTelegramModelSwitch(state, event) {
|
|
343
|
+
const unchanged = { state, effects: [], result: false };
|
|
344
|
+
switch (event.type) {
|
|
345
|
+
case "staged":
|
|
346
|
+
return {
|
|
347
|
+
state: {
|
|
348
|
+
pending: {
|
|
349
|
+
selection: event.selection,
|
|
350
|
+
...(event.turn ? { turn: event.turn } : {}),
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
effects: [],
|
|
354
|
+
result: false,
|
|
355
|
+
};
|
|
356
|
+
case "abortRequested": {
|
|
357
|
+
const pending = state.pending;
|
|
358
|
+
const turn = pending?.turn ?? event.activeTurn;
|
|
359
|
+
if (!pending ||
|
|
360
|
+
!turn ||
|
|
361
|
+
!shouldTriggerPendingTelegramModelSwitchAbort({
|
|
362
|
+
hasPendingModelSwitch: true,
|
|
363
|
+
hasContinuationTurn: true,
|
|
364
|
+
hasAbortHandler: event.hasAbortHandler,
|
|
365
|
+
activeToolExecutions: event.activeToolExecutions,
|
|
366
|
+
}))
|
|
367
|
+
return unchanged;
|
|
368
|
+
return restartFor(turn, pending.selection);
|
|
369
|
+
}
|
|
370
|
+
case "restartRequested":
|
|
371
|
+
return event.turn && event.hasAbortHandler
|
|
372
|
+
? restartFor(event.turn, event.selection)
|
|
373
|
+
: unchanged;
|
|
374
|
+
case "cleared":
|
|
375
|
+
return { state: { pending: null }, effects: [], result: false };
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* The shell around `transitionTelegramModelSwitch`: it samples the active turn, abort handler and tool count into
|
|
380
|
+
* events, and runs the continuation and abort effects with the caller's context and the handler it sampled.
|
|
381
|
+
*/
|
|
350
382
|
export function createTelegramModelSwitchController(deps) {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
383
|
+
// The context and abort handler of the call being dispatched; effects run synchronously inside that dispatch.
|
|
384
|
+
let call;
|
|
385
|
+
const machine = Machine.createTelegramMachineRunner({
|
|
386
|
+
initial: createTelegramModelSwitchMachineState(),
|
|
387
|
+
transition: transitionTelegramModelSwitch,
|
|
388
|
+
run(effect) {
|
|
389
|
+
if (!call)
|
|
390
|
+
return;
|
|
391
|
+
if (effect.type === "queueContinuation")
|
|
392
|
+
deps.queueContinuation(effect.turn, effect.selection, call.ctx);
|
|
393
|
+
else
|
|
394
|
+
call.abort?.();
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
const dispatchFor = (ctx, abort, event) => {
|
|
398
|
+
const preceding = call;
|
|
399
|
+
call = { ctx, abort };
|
|
400
|
+
try {
|
|
401
|
+
return machine.dispatch(event).result;
|
|
402
|
+
}
|
|
403
|
+
finally {
|
|
404
|
+
call = preceding;
|
|
361
405
|
}
|
|
362
|
-
|
|
406
|
+
};
|
|
407
|
+
const triggerPendingAbort = (ctx) => {
|
|
363
408
|
const abort = deps.getAbortHandler();
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
return true;
|
|
409
|
+
return dispatchFor(ctx, abort, {
|
|
410
|
+
type: "abortRequested",
|
|
411
|
+
activeTurn: deps.getActiveTurn(),
|
|
412
|
+
hasAbortHandler: deps.hasAbortHandler() && !!abort,
|
|
413
|
+
activeToolExecutions: deps.getActiveToolExecutions(),
|
|
414
|
+
});
|
|
371
415
|
};
|
|
372
416
|
return {
|
|
373
417
|
canOfferInFlightSwitch: (ctx) => canRestartAgentRunForTelegramModelSwitch({
|
|
374
418
|
isIdle: deps.isIdle(ctx),
|
|
375
419
|
hasAbortHandler: deps.hasAbortHandler(),
|
|
376
420
|
}),
|
|
421
|
+
hasPendingSwitch: () => machine.state.pending !== null,
|
|
377
422
|
stagePendingSwitch: (selection, ctx, continuationTurn) => {
|
|
378
|
-
|
|
379
|
-
|
|
423
|
+
const turn = deps.getActiveTurn() ?? continuationTurn;
|
|
424
|
+
machine.dispatch({
|
|
425
|
+
type: "staged",
|
|
426
|
+
selection,
|
|
427
|
+
...(turn ? { turn } : {}),
|
|
428
|
+
});
|
|
380
429
|
try {
|
|
381
430
|
deps.updateStatus(ctx);
|
|
382
431
|
}
|
|
@@ -385,23 +434,19 @@ export function createTelegramModelSwitchController(deps) {
|
|
|
385
434
|
}
|
|
386
435
|
},
|
|
387
436
|
clearPendingSwitch: () => {
|
|
388
|
-
|
|
389
|
-
deps.setPendingModelSwitch(undefined);
|
|
437
|
+
machine.dispatch({ type: "cleared" });
|
|
390
438
|
},
|
|
391
439
|
queueContinuation: deps.queueContinuation,
|
|
392
440
|
triggerPendingAbort,
|
|
393
441
|
restartInterruptedTurn: (selection, ctx, continuationTurn) => {
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
442
|
+
const abort = deps.getAbortHandler();
|
|
443
|
+
const turn = deps.getActiveTurn() ?? continuationTurn;
|
|
444
|
+
return dispatchFor(ctx, abort, {
|
|
445
|
+
type: "restartRequested",
|
|
397
446
|
selection,
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
},
|
|
447
|
+
...(turn ? { turn } : {}),
|
|
448
|
+
hasAbortHandler: !!abort,
|
|
401
449
|
});
|
|
402
|
-
if (restarted)
|
|
403
|
-
pendingContinuationTurn = undefined;
|
|
404
|
-
return restarted;
|
|
405
450
|
},
|
|
406
451
|
};
|
|
407
452
|
}
|
package/dist/lib/polling.js
CHANGED
|
@@ -893,6 +893,8 @@ export function createTelegramThreadCapabilityMonitor(deps) {
|
|
|
893
893
|
return {
|
|
894
894
|
start(ctx) {
|
|
895
895
|
stop();
|
|
896
|
+
// Disabled-mode confirmation counts consecutive probes of one run, never observations from before a restart.
|
|
897
|
+
consecutiveDisabledProbes = 0;
|
|
896
898
|
const expectedGeneration = generation;
|
|
897
899
|
interval = setInterval(() => {
|
|
898
900
|
if (generation !== expectedGeneration)
|
package/dist/lib/preview.d.ts
CHANGED
|
@@ -2,42 +2,28 @@
|
|
|
2
2
|
* Telegram preview streaming helpers
|
|
3
3
|
* Zones: telegram outbound, native rich markdown drafts
|
|
4
4
|
* Owns safe draft preview selection, runtime updates, and preview finalization
|
|
5
|
+
* The streaming state machine is a pure transition function driven by one imperative shell per runtime.
|
|
5
6
|
*/
|
|
6
7
|
import type { TelegramAssistantOutputPreparation } from "./activity.ts";
|
|
7
8
|
import { type TelegramTarget } from "./target.ts";
|
|
8
|
-
export type TelegramDraftSupport = "unknown" | "supported";
|
|
9
9
|
export interface TelegramPreviewState {
|
|
10
10
|
mode: "draft";
|
|
11
11
|
draftId?: number;
|
|
12
12
|
pendingText: string;
|
|
13
13
|
lastSentText: string;
|
|
14
14
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
publicationPromise?: Promise<void>;
|
|
20
|
-
sealed?: boolean;
|
|
21
|
-
nextDraftAt?: number;
|
|
22
|
-
flushTimer?: ReturnType<typeof setTimeout>;
|
|
15
|
+
/** One immutable preview segment; a replacement segment gets a new id, so late results for an old one are dropped. */
|
|
16
|
+
export interface TelegramPreviewSegment extends Readonly<TelegramPreviewState> {
|
|
17
|
+
readonly id: number;
|
|
18
|
+
readonly sealed: boolean;
|
|
23
19
|
}
|
|
24
20
|
export type TelegramPreviewReplyMarkup = unknown;
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
setDraftSupport: (support: TelegramDraftSupport) => void;
|
|
32
|
-
allocateDraftId: () => number;
|
|
33
|
-
sendDraft: (chatId: number, draftId: number, text?: string, options?: {
|
|
34
|
-
parse_mode?: string;
|
|
35
|
-
entities?: unknown[];
|
|
36
|
-
message_thread_id?: number;
|
|
37
|
-
}) => Promise<unknown>;
|
|
38
|
-
canSend?: () => boolean;
|
|
39
|
-
recordRuntimeEvent?: (category: string, error: unknown, details?: Record<string, unknown>) => void;
|
|
40
|
-
}
|
|
21
|
+
type TelegramDraftSender = (chatId: number, draftId: number, text?: string, options?: {
|
|
22
|
+
parse_mode?: string;
|
|
23
|
+
entities?: unknown[];
|
|
24
|
+
message_thread_id?: number;
|
|
25
|
+
}) => Promise<unknown>;
|
|
26
|
+
type TelegramPreviewEventRecorder = (category: string, error: unknown, details?: Record<string, unknown>) => void;
|
|
41
27
|
export interface TelegramPreviewActiveTurn {
|
|
42
28
|
chatId: number;
|
|
43
29
|
replyToMessageId?: number;
|
|
@@ -46,28 +32,6 @@ export interface TelegramPreviewActiveTurn {
|
|
|
46
32
|
voiceReplyRequired?: boolean;
|
|
47
33
|
guestQueryId?: string;
|
|
48
34
|
}
|
|
49
|
-
export interface TelegramAssistantMessagePreviewStartDeps<TMessage> {
|
|
50
|
-
getActiveTurn: () => TelegramPreviewActiveTurn | undefined;
|
|
51
|
-
isAssistantMessage: (message: TMessage) => boolean;
|
|
52
|
-
getState: () => TelegramPreviewRuntimeState | undefined;
|
|
53
|
-
setState: (state: TelegramPreviewRuntimeState | undefined) => void;
|
|
54
|
-
createPreviewState: () => TelegramPreviewRuntimeState;
|
|
55
|
-
canSend?: () => boolean;
|
|
56
|
-
}
|
|
57
|
-
export interface TelegramAssistantMessagePreviewUpdateDeps<TMessage> {
|
|
58
|
-
getActiveTurn: () => TelegramPreviewActiveTurn | undefined;
|
|
59
|
-
isAssistantMessage: (message: TMessage) => boolean;
|
|
60
|
-
getState: () => TelegramPreviewRuntimeState | undefined;
|
|
61
|
-
setState: (state: TelegramPreviewRuntimeState | undefined) => void;
|
|
62
|
-
createPreviewState: () => TelegramPreviewRuntimeState;
|
|
63
|
-
canSend?: () => boolean;
|
|
64
|
-
getMessageText: (message: TMessage) => string;
|
|
65
|
-
minDraftIntervalMs?: number;
|
|
66
|
-
schedulePreviewFlush: (chatId: number, options?: {
|
|
67
|
-
target?: TelegramTarget;
|
|
68
|
-
}) => void;
|
|
69
|
-
}
|
|
70
|
-
export type TelegramAssistantMessagePreviewHookDeps<TMessage> = TelegramAssistantMessagePreviewStartDeps<TMessage> & TelegramAssistantMessagePreviewUpdateDeps<TMessage>;
|
|
71
35
|
export interface TelegramAssistantMessagePreviewHookEvent<TMessage> {
|
|
72
36
|
message: TMessage;
|
|
73
37
|
}
|
|
@@ -78,46 +42,37 @@ export interface TelegramAssistantMessagePreviewHooks<TMessage> {
|
|
|
78
42
|
export interface TelegramPreviewControllerDeps {
|
|
79
43
|
getDefaultReplyToMessageId?: () => number | undefined;
|
|
80
44
|
maxMessageLength?: number;
|
|
81
|
-
|
|
82
|
-
sendDraft: (chatId: number, draftId: number, text?: string, options?: {
|
|
83
|
-
parse_mode?: string;
|
|
84
|
-
entities?: unknown[];
|
|
85
|
-
message_thread_id?: number;
|
|
86
|
-
}) => Promise<unknown>;
|
|
45
|
+
sendDraft: TelegramDraftSender;
|
|
87
46
|
canSend?: () => boolean;
|
|
88
47
|
maxDraftId?: number;
|
|
89
|
-
|
|
48
|
+
/** Throttle between draft frames; defaults to the native draft cadence. */
|
|
49
|
+
minDraftIntervalMs?: number;
|
|
50
|
+
recordRuntimeEvent?: TelegramPreviewEventRecorder;
|
|
90
51
|
}
|
|
52
|
+
type TelegramPreviewTargetOptions = {
|
|
53
|
+
target?: TelegramTarget;
|
|
54
|
+
};
|
|
91
55
|
export interface TelegramPreviewController {
|
|
56
|
+
/** Read-only view of the current segment; it never grants mutation. */
|
|
57
|
+
getState: () => TelegramPreviewSegment | undefined;
|
|
58
|
+
/** True while a draft request is in flight or a pending request waits behind a publication. */
|
|
59
|
+
isFlushing: () => boolean;
|
|
92
60
|
seal: () => void;
|
|
93
61
|
preparePublication: () => TelegramAssistantOutputPreparation | undefined;
|
|
94
|
-
prepareClear: (chatId: number, options?: {
|
|
95
|
-
target?: TelegramTarget;
|
|
62
|
+
prepareClear: (chatId: number, options?: TelegramPreviewTargetOptions & {
|
|
96
63
|
isDeliveryActive?: () => boolean;
|
|
97
64
|
}) => () => Promise<void>;
|
|
98
|
-
getState: () => TelegramPreviewRuntimeState | undefined;
|
|
99
|
-
setState: (state: TelegramPreviewRuntimeState | undefined) => void;
|
|
100
65
|
setPendingText: (text: string) => void;
|
|
101
|
-
createState: () => TelegramPreviewRuntimeState;
|
|
102
66
|
resetState: () => void;
|
|
103
67
|
invalidate: () => void;
|
|
104
|
-
clear: (chatId: number, options?: {
|
|
68
|
+
clear: (chatId: number, options?: TelegramPreviewTargetOptions & {
|
|
105
69
|
awaitFlush?: boolean;
|
|
106
|
-
target?: TelegramTarget;
|
|
107
|
-
}) => Promise<void>;
|
|
108
|
-
flush: (chatId: number, options?: {
|
|
109
|
-
target?: TelegramTarget;
|
|
110
70
|
}) => Promise<void>;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
finalize: (chatId: number, replyToMessageId?: number, options?: {
|
|
115
|
-
target?: TelegramTarget;
|
|
116
|
-
}) => Promise<boolean>;
|
|
71
|
+
flush: (chatId: number, options?: TelegramPreviewTargetOptions) => Promise<void>;
|
|
72
|
+
scheduleFlush: (chatId: number, options?: TelegramPreviewTargetOptions) => void;
|
|
73
|
+
finalize: (chatId: number, replyToMessageId?: number, options?: TelegramPreviewTargetOptions) => Promise<boolean>;
|
|
117
74
|
}
|
|
118
|
-
export
|
|
119
|
-
export declare function createTelegramPreviewControllerRuntime(deps: TelegramPreviewControllerRuntimeDeps): TelegramPreviewController;
|
|
120
|
-
export interface TelegramAssistantPreviewRuntimeDeps<TMessage, TReplyMarkup = TelegramPreviewReplyMarkup> extends TelegramPreviewControllerRuntimeDeps {
|
|
75
|
+
export interface TelegramAssistantPreviewRuntimeDeps<TMessage, TReplyMarkup = TelegramPreviewReplyMarkup> extends TelegramPreviewControllerDeps {
|
|
121
76
|
getActiveTurn: () => TelegramPreviewActiveTurn | undefined;
|
|
122
77
|
isAssistantMessage: (message: TMessage) => boolean;
|
|
123
78
|
getMessageText: (message: TMessage) => string;
|
|
@@ -126,35 +81,113 @@ export interface TelegramAssistantPreviewRuntimeDeps<TMessage, TReplyMarkup = Te
|
|
|
126
81
|
target?: TelegramTarget;
|
|
127
82
|
}) => Promise<number | undefined>;
|
|
128
83
|
}
|
|
84
|
+
type TelegramMarkdownFinalizer<TReplyMarkup> = (chatId: number, markdown: string, replyToMessageId?: number, options?: {
|
|
85
|
+
replyMarkup?: TReplyMarkup;
|
|
86
|
+
target?: TelegramTarget;
|
|
87
|
+
}) => Promise<boolean>;
|
|
129
88
|
export interface TelegramPreparedPreviewDelivery<TReplyMarkup = unknown> {
|
|
130
89
|
clearPreview: TelegramPreviewController["clear"];
|
|
131
90
|
setPreviewPendingText: TelegramPreviewController["setPendingText"];
|
|
132
|
-
finalizeMarkdownPreview:
|
|
91
|
+
finalizeMarkdownPreview: TelegramMarkdownFinalizer<TReplyMarkup>;
|
|
133
92
|
}
|
|
134
93
|
export type TelegramAssistantPreviewRuntime<TMessage, TReplyMarkup = TelegramPreviewReplyMarkup> = TelegramPreviewController & TelegramAssistantMessagePreviewHooks<TMessage> & {
|
|
135
94
|
prepareDelivery: (isDeliveryActive: () => boolean) => TelegramPreparedPreviewDelivery<TReplyMarkup>;
|
|
136
|
-
finalizeMarkdown:
|
|
137
|
-
replyMarkup?: TReplyMarkup;
|
|
138
|
-
target?: TelegramTarget;
|
|
139
|
-
}) => Promise<boolean>;
|
|
95
|
+
finalizeMarkdown: TelegramMarkdownFinalizer<TReplyMarkup>;
|
|
140
96
|
};
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
clear: (chatId: number, options?: {
|
|
144
|
-
awaitFlush?: boolean;
|
|
145
|
-
target?: TelegramTarget;
|
|
146
|
-
}) => Promise<void>;
|
|
147
|
-
discard?: () => void;
|
|
148
|
-
isDeliveryActive?: () => boolean;
|
|
149
|
-
sendMarkdownReply: (chatId: number, replyToMessageId: number | undefined, markdown: string, options?: {
|
|
150
|
-
replyMarkup?: TReplyMarkup;
|
|
151
|
-
target?: TelegramTarget;
|
|
152
|
-
}) => Promise<number | undefined>;
|
|
153
|
-
}): (chatId: number, markdown: string, replyToMessageId?: number, options?: {
|
|
154
|
-
replyMarkup?: TReplyMarkup;
|
|
97
|
+
interface TelegramPreviewFlushRequest {
|
|
98
|
+
chatId: number;
|
|
155
99
|
target?: TelegramTarget;
|
|
156
|
-
}
|
|
157
|
-
|
|
100
|
+
}
|
|
101
|
+
interface TelegramPreviewMachineConfig {
|
|
102
|
+
maxDraftId: number;
|
|
103
|
+
maxMessageLength: number;
|
|
104
|
+
minDraftIntervalMs: number;
|
|
105
|
+
}
|
|
106
|
+
/** The whole preview state; only `transitionTelegramPreview` produces a new one. */
|
|
107
|
+
export interface TelegramPreviewMachineState {
|
|
108
|
+
readonly segment: TelegramPreviewSegment | null;
|
|
109
|
+
/** Bumped by reset and invalidation; a result issued under an older generation is inert. */
|
|
110
|
+
readonly generation: number;
|
|
111
|
+
readonly nextSegmentId: number;
|
|
112
|
+
readonly nextDraftId: number;
|
|
113
|
+
readonly nextDraftAt: number;
|
|
114
|
+
/** Segment that owns the current generation's issued draft; old-generation requests do not block it. */
|
|
115
|
+
readonly inFlight: number | null;
|
|
116
|
+
/** The one coalesced flush request waiting behind an in-flight draft or an open publication. */
|
|
117
|
+
readonly pending: TelegramPreviewFlushRequest | null;
|
|
118
|
+
readonly publications: readonly number[];
|
|
119
|
+
/** The segment the throttle timer is armed for; the timer belongs to that segment alone. */
|
|
120
|
+
readonly timerSegmentId: number | null;
|
|
121
|
+
}
|
|
122
|
+
/** Impure inputs the runner samples at dispatch time, so transitions stay pure. */
|
|
123
|
+
interface TelegramPreviewClock {
|
|
124
|
+
now: number;
|
|
125
|
+
canSend: boolean;
|
|
126
|
+
}
|
|
127
|
+
export type TelegramPreviewEvent = {
|
|
128
|
+
type: "reset";
|
|
129
|
+
} | {
|
|
130
|
+
type: "invalidated";
|
|
131
|
+
} | {
|
|
132
|
+
type: "segmentStarted";
|
|
133
|
+
} | {
|
|
134
|
+
type: "segmentEnsured";
|
|
135
|
+
} | {
|
|
136
|
+
type: "sealed";
|
|
137
|
+
} | {
|
|
138
|
+
type: "discarded";
|
|
139
|
+
} | {
|
|
140
|
+
type: "textSet";
|
|
141
|
+
text: string;
|
|
142
|
+
} | {
|
|
143
|
+
type: "draftWindowOpened";
|
|
144
|
+
at: number;
|
|
145
|
+
} | ({
|
|
146
|
+
type: "flushRequested";
|
|
147
|
+
request: TelegramPreviewFlushRequest;
|
|
148
|
+
} & TelegramPreviewClock) | ({
|
|
149
|
+
type: "timerFired";
|
|
150
|
+
request: TelegramPreviewFlushRequest;
|
|
151
|
+
segmentId: number;
|
|
152
|
+
} & TelegramPreviewClock) | ({
|
|
153
|
+
type: "draftSettled";
|
|
154
|
+
segmentId: number;
|
|
155
|
+
generation: number;
|
|
156
|
+
/** Present only when Telegram accepted the draft. */
|
|
157
|
+
text?: string;
|
|
158
|
+
} & TelegramPreviewClock) | {
|
|
159
|
+
type: "publicationOpened";
|
|
160
|
+
id: number;
|
|
161
|
+
} | ({
|
|
162
|
+
type: "publicationSettled";
|
|
163
|
+
id: number;
|
|
164
|
+
} & TelegramPreviewClock);
|
|
165
|
+
export type TelegramPreviewEffect = {
|
|
166
|
+
type: "sendDraft";
|
|
167
|
+
request: TelegramPreviewFlushRequest;
|
|
168
|
+
draftId: number;
|
|
169
|
+
text: string;
|
|
170
|
+
segmentId: number;
|
|
171
|
+
generation: number;
|
|
172
|
+
} | {
|
|
173
|
+
type: "armTimer";
|
|
174
|
+
delay: number;
|
|
175
|
+
request: TelegramPreviewFlushRequest;
|
|
176
|
+
segmentId: number;
|
|
177
|
+
} | {
|
|
178
|
+
type: "cancelTimer";
|
|
179
|
+
} | {
|
|
180
|
+
type: "clearClosedGate";
|
|
181
|
+
request: TelegramPreviewFlushRequest;
|
|
182
|
+
};
|
|
183
|
+
export interface TelegramPreviewStep {
|
|
184
|
+
state: TelegramPreviewMachineState;
|
|
185
|
+
effects: readonly TelegramPreviewEffect[];
|
|
186
|
+
}
|
|
187
|
+
export declare function createTelegramPreviewMachineState(): TelegramPreviewMachineState;
|
|
188
|
+
export declare function transitionTelegramPreview(state: TelegramPreviewMachineState, event: TelegramPreviewEvent, config: TelegramPreviewMachineConfig): TelegramPreviewStep;
|
|
189
|
+
/** True while a draft request is in flight, or, when publications block, a pending request waits behind one. */
|
|
190
|
+
export declare function isTelegramPreviewBusy(state: TelegramPreviewMachineState, blockedByPublication?: boolean): boolean;
|
|
158
191
|
export declare function createTelegramPreviewController(deps: TelegramPreviewControllerDeps): TelegramPreviewController;
|
|
159
192
|
/**
|
|
160
193
|
* Returns true when the active turn is a Telegram Guest Mode query. A guest
|
|
@@ -164,26 +197,8 @@ export declare function createTelegramPreviewController(deps: TelegramPreviewCon
|
|
|
164
197
|
export declare function shouldSuppressPreviewForGuestTurn(turn: {
|
|
165
198
|
guestQueryId?: string;
|
|
166
199
|
} | null | undefined): boolean;
|
|
200
|
+
export declare function createTelegramAssistantPreviewRuntime<TMessage, TReplyMarkup = TelegramPreviewReplyMarkup>(deps: TelegramAssistantPreviewRuntimeDeps<TMessage, TReplyMarkup>): TelegramAssistantPreviewRuntime<TMessage, TReplyMarkup>;
|
|
167
201
|
export declare function buildTelegramPreviewFinalText(state: TelegramPreviewState): string | undefined;
|
|
168
|
-
export declare function createTelegramPreviewRuntimeState(): TelegramPreviewRuntimeState;
|
|
169
202
|
export declare function allocateTelegramDraftId(currentDraftId: number, maxDraftId: number): number;
|
|
170
|
-
interface TelegramNativeMarkdownPreviewSnapshot {
|
|
171
|
-
text: string;
|
|
172
|
-
}
|
|
173
|
-
export declare function shouldUseTelegramDraftPreview(_options: {
|
|
174
|
-
draftSupport: TelegramDraftSupport;
|
|
175
|
-
snapshot?: TelegramNativeMarkdownPreviewSnapshot;
|
|
176
|
-
}): boolean;
|
|
177
|
-
export declare function clearTelegramPreview(chatId: number, deps: TelegramPreviewRuntimeDeps, options?: {
|
|
178
|
-
awaitFlush?: boolean;
|
|
179
|
-
target?: TelegramTarget;
|
|
180
|
-
isDeliveryActive?: () => boolean;
|
|
181
|
-
}): Promise<void>;
|
|
182
203
|
export declare function getSafeTelegramRichMarkdownDraftPrefix(markdown: string, maxMessageLength: number): string | undefined;
|
|
183
|
-
export declare function flushTelegramPreview(chatId: number, deps: TelegramPreviewRuntimeDeps, options?: {
|
|
184
|
-
target?: TelegramTarget;
|
|
185
|
-
}): Promise<void>;
|
|
186
|
-
export declare function finalizeTelegramPreview(chatId: number, deps: TelegramPreviewRuntimeDeps, options?: {
|
|
187
|
-
target?: TelegramTarget;
|
|
188
|
-
}): Promise<boolean>;
|
|
189
204
|
export {};
|