@llblab/pi-telegram 0.10.3 → 0.10.5
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/CHANGELOG.md +12 -0
- package/README.md +5 -3
- package/index.ts +2 -0
- package/lib/commands.ts +31 -2
- package/lib/polling.ts +32 -5
- package/lib/routing.ts +5 -0
- package/lib/runtime.ts +36 -3
- package/lib/text-groups.ts +3 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.5: Queue Continuity And Input Resilience Hotfix
|
|
4
|
+
|
|
5
|
+
- `[Compaction]` `/compact` completion and failure callbacks now request deferred queue dispatch instead of dispatching immediately. Impact: queued Telegram turns resume after compaction state and π idle/pending-message state have a chance to settle.
|
|
6
|
+
- `[Text Groups]` Long-text split recovery is more aggressive where Telegram chunking actually drifts: the debounce is rounded to 1s, the conservative 3600-character start threshold is preserved, and continuation messages can span a much wider message-id gap while staying scoped to the same chat/user and non-command text. Impact: very large pasted prompts are more likely to arrive as one agent turn instead of several fragmented turns.
|
|
7
|
+
- `[Runtime Status]` Typing-loop and prompt-dispatch status updates are now best-effort and record stale-context failures as structured runtime events. Impact: status/Running indicators remain resilient after error paths without hiding diagnostics.
|
|
8
|
+
- `[Tests]` Added regressions for deferred compact dispatch, stale status failures in typing/dispatch paths, and many-part split-text grouping.
|
|
9
|
+
|
|
10
|
+
## 0.10.4: Polling Status Resilience Hotfix
|
|
11
|
+
|
|
12
|
+
- `[Polling]` Status-bar updates from the polling loop are now best-effort and no longer crash the extension when a captured session context becomes stale after session reload. Failures are recorded as structured polling runtime events with `phase: "status-update"`. Impact: polling cleanup and retry status updates stay resilient without changing the Telegram API, config, or operator workflow.
|
|
13
|
+
- `[Tests]` Added stale-context polling regressions for startup, cleanup, and retry status updates. Impact: the external PR #43 fix is now covered by maintainer-side tests and kept aligned with local style.
|
|
14
|
+
|
|
3
15
|
## 0.10.3: Dependency Audit Hotfix
|
|
4
16
|
|
|
5
17
|
- `[Dependencies]` Refreshed the lockfile transitive dependency set to resolve current `protobufjs` / `@protobufjs/utf8` npm audit advisories inherited through development peer installs. Impact: `npm run validate` is green again without changing runtime API or bridge behavior.
|
package/README.md
CHANGED
|
@@ -240,9 +240,11 @@ Import from `@llblab/pi-telegram`, call `registerTelegramSection()`, and return
|
|
|
240
240
|
|
|
241
241
|
Third-party extensions that integrate with `pi-telegram`:
|
|
242
242
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
243
|
+
- [`pi-telegram-tool-status`](https://github.com/Timur00Kh/pi-telegram-tool-status) — Live-updating service messages that list tools used by the agent. It keeps one message per Telegram prompt and edits it in place as tools execute.
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
pi install npm:pi-telegram-tool-status
|
|
247
|
+
```
|
|
246
248
|
|
|
247
249
|
## License
|
|
248
250
|
|
package/index.ts
CHANGED
|
@@ -352,6 +352,8 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
352
352
|
inboundHandlerRuntime,
|
|
353
353
|
updateStatus,
|
|
354
354
|
dispatchNextQueuedTelegramTurn,
|
|
355
|
+
requestDeferredDispatchNextQueuedTelegramTurn:
|
|
356
|
+
deferredQueueDispatchRuntime.request,
|
|
355
357
|
answerCallbackQuery,
|
|
356
358
|
editInteractiveMessage,
|
|
357
359
|
sendInteractiveMessage,
|
package/lib/commands.ts
CHANGED
|
@@ -317,6 +317,9 @@ export interface TelegramCompactCommandDeps extends TelegramRuntimeEventRecorder
|
|
|
317
317
|
setCompactionInProgress: (inProgress: boolean) => void;
|
|
318
318
|
updateStatus: () => void;
|
|
319
319
|
dispatchNextQueuedTelegramTurn: () => void;
|
|
320
|
+
requestDeferredDispatchNextQueuedTelegramTurn?: (
|
|
321
|
+
dispatch: () => void,
|
|
322
|
+
) => void;
|
|
320
323
|
compact: (callbacks: {
|
|
321
324
|
onComplete: () => void;
|
|
322
325
|
onError: (error: unknown) => void;
|
|
@@ -547,6 +550,9 @@ export interface TelegramCommandRuntimeDeps<
|
|
|
547
550
|
setCompactionInProgress: (inProgress: boolean) => void;
|
|
548
551
|
updateStatus: (ctx: TContext) => void;
|
|
549
552
|
dispatchNextQueuedTelegramTurn: (ctx: TContext) => void;
|
|
553
|
+
requestDeferredDispatchNextQueuedTelegramTurn?: (
|
|
554
|
+
dispatch: (ctx: TContext) => void,
|
|
555
|
+
) => void;
|
|
550
556
|
enqueueContinueTurn: (message: TMessage, ctx: TContext) => Promise<void>;
|
|
551
557
|
compact: (
|
|
552
558
|
ctx: TContext,
|
|
@@ -758,6 +764,22 @@ export async function handleTelegramContinueCommand<TMessage, TContext>(
|
|
|
758
764
|
await deps.enqueueContinueTurn(message, ctx);
|
|
759
765
|
}
|
|
760
766
|
|
|
767
|
+
function dispatchNextQueuedTelegramTurnAfterCompact(
|
|
768
|
+
deps: Pick<
|
|
769
|
+
TelegramCompactCommandDeps,
|
|
770
|
+
| "dispatchNextQueuedTelegramTurn"
|
|
771
|
+
| "requestDeferredDispatchNextQueuedTelegramTurn"
|
|
772
|
+
>,
|
|
773
|
+
): void {
|
|
774
|
+
if (deps.requestDeferredDispatchNextQueuedTelegramTurn) {
|
|
775
|
+
deps.requestDeferredDispatchNextQueuedTelegramTurn(
|
|
776
|
+
deps.dispatchNextQueuedTelegramTurn,
|
|
777
|
+
);
|
|
778
|
+
return;
|
|
779
|
+
}
|
|
780
|
+
deps.dispatchNextQueuedTelegramTurn();
|
|
781
|
+
}
|
|
782
|
+
|
|
761
783
|
export async function handleTelegramCompactCommand(
|
|
762
784
|
deps: TelegramCompactCommandDeps,
|
|
763
785
|
): Promise<void> {
|
|
@@ -781,13 +803,13 @@ export async function handleTelegramCompactCommand(
|
|
|
781
803
|
onComplete: () => {
|
|
782
804
|
deps.setCompactionInProgress(false);
|
|
783
805
|
deps.updateStatus();
|
|
784
|
-
deps
|
|
806
|
+
dispatchNextQueuedTelegramTurnAfterCompact(deps);
|
|
785
807
|
void deps.sendTextReply("Compaction completed.");
|
|
786
808
|
},
|
|
787
809
|
onError: (error) => {
|
|
788
810
|
deps.setCompactionInProgress(false);
|
|
789
811
|
deps.updateStatus();
|
|
790
|
-
deps
|
|
812
|
+
dispatchNextQueuedTelegramTurnAfterCompact(deps);
|
|
791
813
|
deps.recordRuntimeEvent?.("compact", error);
|
|
792
814
|
const errorMessage = getTelegramCommandErrorMessage(error);
|
|
793
815
|
void deps.sendTextReply(`Compaction failed: ${errorMessage}`);
|
|
@@ -1069,6 +1091,13 @@ async function handleTelegramCommandRuntime<
|
|
|
1069
1091
|
updateStatus: updateStatusFor(commandCtx),
|
|
1070
1092
|
dispatchNextQueuedTelegramTurn: () =>
|
|
1071
1093
|
deps.dispatchNextQueuedTelegramTurn(commandCtx),
|
|
1094
|
+
requestDeferredDispatchNextQueuedTelegramTurn:
|
|
1095
|
+
deps.requestDeferredDispatchNextQueuedTelegramTurn
|
|
1096
|
+
? (dispatch) =>
|
|
1097
|
+
deps.requestDeferredDispatchNextQueuedTelegramTurn?.(() =>
|
|
1098
|
+
dispatch(),
|
|
1099
|
+
)
|
|
1100
|
+
: undefined,
|
|
1072
1101
|
compact: (callbacks) => deps.compact(commandCtx, callbacks),
|
|
1073
1102
|
sendTextReply: sendReplyFor(nextMessage),
|
|
1074
1103
|
recordRuntimeEvent: deps.recordRuntimeEvent,
|
package/lib/polling.ts
CHANGED
|
@@ -91,7 +91,8 @@ export function createTelegramPollingActivityReader(
|
|
|
91
91
|
return () => isTelegramPollingControllerActive(state);
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
export interface TelegramPollingRuntimeDeps<TContext>
|
|
94
|
+
export interface TelegramPollingRuntimeDeps<TContext>
|
|
95
|
+
extends TelegramRuntimeEventRecorderPort {
|
|
95
96
|
hasBotToken: () => boolean;
|
|
96
97
|
getPollingPromise: () => Promise<void> | undefined;
|
|
97
98
|
setPollingPromise: (promise: Promise<void> | undefined) => void;
|
|
@@ -150,6 +151,7 @@ export function createTelegramPollingControllerRuntime<
|
|
|
150
151
|
}),
|
|
151
152
|
updateStatus: deps.updateStatus,
|
|
152
153
|
createAbortController: deps.createAbortController,
|
|
154
|
+
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
153
155
|
});
|
|
154
156
|
}
|
|
155
157
|
|
|
@@ -191,6 +193,22 @@ export async function stopTelegramPollingRuntime<TContext>(
|
|
|
191
193
|
deps.setPollingPromise(undefined);
|
|
192
194
|
}
|
|
193
195
|
|
|
196
|
+
function updateTelegramPollingStatusSafely<TContext>(
|
|
197
|
+
updateStatus: (ctx: TContext, message?: string) => void,
|
|
198
|
+
ctx: TContext,
|
|
199
|
+
options: {
|
|
200
|
+
message?: string;
|
|
201
|
+
recordRuntimeEvent?: TelegramRuntimeEventRecorderPort["recordRuntimeEvent"];
|
|
202
|
+
} = {},
|
|
203
|
+
): void {
|
|
204
|
+
try {
|
|
205
|
+
updateStatus(ctx, options.message);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
// The polling loop can outlive the session context it captured.
|
|
208
|
+
options.recordRuntimeEvent?.("polling", error, { phase: "status-update" });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
194
212
|
export function startTelegramPollingRuntime<TContext>(
|
|
195
213
|
ctx: TContext,
|
|
196
214
|
deps: TelegramPollingRuntimeDeps<TContext>,
|
|
@@ -208,10 +226,14 @@ export function startTelegramPollingRuntime<TContext>(
|
|
|
208
226
|
const promise = deps.runPollLoop(ctx, controller.signal).finally(() => {
|
|
209
227
|
deps.setPollingPromise(undefined);
|
|
210
228
|
deps.setPollingController(undefined);
|
|
211
|
-
deps.updateStatus
|
|
229
|
+
updateTelegramPollingStatusSafely(deps.updateStatus, ctx, {
|
|
230
|
+
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
231
|
+
});
|
|
212
232
|
});
|
|
213
233
|
deps.setPollingPromise(promise);
|
|
214
|
-
deps.updateStatus
|
|
234
|
+
updateTelegramPollingStatusSafely(deps.updateStatus, ctx, {
|
|
235
|
+
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
236
|
+
});
|
|
215
237
|
}
|
|
216
238
|
|
|
217
239
|
export interface TelegramRuntimeEventRecorderPort {
|
|
@@ -281,10 +303,15 @@ export function createTelegramPollLoopRunner<
|
|
|
281
303
|
persistConfig: deps.persistConfig,
|
|
282
304
|
handleUpdate: deps.handleUpdate,
|
|
283
305
|
onErrorStatus: (message) => {
|
|
284
|
-
deps.updateStatus
|
|
306
|
+
updateTelegramPollingStatusSafely(deps.updateStatus, ctx, {
|
|
307
|
+
message,
|
|
308
|
+
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
309
|
+
});
|
|
285
310
|
},
|
|
286
311
|
onStatusReset: () => {
|
|
287
|
-
deps.updateStatus
|
|
312
|
+
updateTelegramPollingStatusSafely(deps.updateStatus, ctx, {
|
|
313
|
+
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
314
|
+
});
|
|
288
315
|
},
|
|
289
316
|
sleep,
|
|
290
317
|
maxUpdateFailures: deps.maxUpdateFailures,
|
package/lib/routing.ts
CHANGED
|
@@ -79,6 +79,9 @@ export interface TelegramInboundRouteRuntimeDeps<
|
|
|
79
79
|
inboundHandlerRuntime: TelegramInboundHandlerRuntime<TContext>;
|
|
80
80
|
updateStatus: (ctx: TContext, error?: string) => void;
|
|
81
81
|
dispatchNextQueuedTelegramTurn: (ctx: TContext) => void;
|
|
82
|
+
requestDeferredDispatchNextQueuedTelegramTurn?: (
|
|
83
|
+
dispatch: (ctx: TContext) => void,
|
|
84
|
+
) => void;
|
|
82
85
|
answerCallbackQuery: (
|
|
83
86
|
callbackQueryId: string,
|
|
84
87
|
text?: string,
|
|
@@ -339,6 +342,8 @@ export function createTelegramInboundRouteRuntime<
|
|
|
339
342
|
deps.bridgeRuntime.lifecycle.setCompactionInProgress,
|
|
340
343
|
updateStatus: deps.updateStatus,
|
|
341
344
|
dispatchNextQueuedTelegramTurn: deps.dispatchNextQueuedTelegramTurn,
|
|
345
|
+
requestDeferredDispatchNextQueuedTelegramTurn:
|
|
346
|
+
deps.requestDeferredDispatchNextQueuedTelegramTurn,
|
|
342
347
|
enqueueContinueTurn,
|
|
343
348
|
compact: deps.compact,
|
|
344
349
|
allocateItemOrder: deps.bridgeRuntime.queue.allocateItemOrder,
|
package/lib/runtime.ts
CHANGED
|
@@ -328,6 +328,25 @@ export interface TelegramRuntimeEventRecorderPort {
|
|
|
328
328
|
) => void;
|
|
329
329
|
}
|
|
330
330
|
|
|
331
|
+
function updateTelegramRuntimeStatusSafely<TContext>(
|
|
332
|
+
updateStatus: (ctx: TContext, error?: string) => void,
|
|
333
|
+
ctx: TContext,
|
|
334
|
+
options: {
|
|
335
|
+
error?: string;
|
|
336
|
+
category: string;
|
|
337
|
+
phase: string;
|
|
338
|
+
recordRuntimeEvent?: TelegramRuntimeEventRecorderPort["recordRuntimeEvent"];
|
|
339
|
+
},
|
|
340
|
+
): void {
|
|
341
|
+
try {
|
|
342
|
+
updateStatus(ctx, options.error);
|
|
343
|
+
} catch (statusError) {
|
|
344
|
+
options.recordRuntimeEvent?.(options.category, statusError, {
|
|
345
|
+
phase: options.phase,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
331
350
|
export interface TelegramTypingLoopStarterDeps<
|
|
332
351
|
TContext,
|
|
333
352
|
> extends TelegramRuntimeEventRecorderPort {
|
|
@@ -351,7 +370,12 @@ export function createTelegramTypingLoopStarter<TContext>(
|
|
|
351
370
|
} catch (error) {
|
|
352
371
|
const message =
|
|
353
372
|
error instanceof Error ? error.message : String(error);
|
|
354
|
-
deps.updateStatus
|
|
373
|
+
updateTelegramRuntimeStatusSafely(deps.updateStatus, ctx, {
|
|
374
|
+
error: message,
|
|
375
|
+
category: "typing",
|
|
376
|
+
phase: "status-update",
|
|
377
|
+
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
378
|
+
});
|
|
355
379
|
deps.recordRuntimeEvent?.("typing", error, {
|
|
356
380
|
chatId: targetChatId,
|
|
357
381
|
});
|
|
@@ -468,13 +492,22 @@ export function createTelegramPromptDispatchLifecycle<TContext>(
|
|
|
468
492
|
onPromptDispatchStart: (ctx: TContext, chatId?: number): void => {
|
|
469
493
|
deps.lifecycle.setDispatchPending(true);
|
|
470
494
|
deps.startTypingLoop(ctx, chatId);
|
|
471
|
-
deps.updateStatus
|
|
495
|
+
updateTelegramRuntimeStatusSafely(deps.updateStatus, ctx, {
|
|
496
|
+
category: "dispatch",
|
|
497
|
+
phase: "status-update",
|
|
498
|
+
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
499
|
+
});
|
|
472
500
|
},
|
|
473
501
|
onPromptDispatchFailure: (ctx: TContext, message: string): void => {
|
|
474
502
|
deps.lifecycle.clearDispatchPending();
|
|
475
503
|
deps.typing.stop();
|
|
476
504
|
deps.recordRuntimeEvent?.("dispatch", new Error(message));
|
|
477
|
-
deps.updateStatus
|
|
505
|
+
updateTelegramRuntimeStatusSafely(deps.updateStatus, ctx, {
|
|
506
|
+
error: `dispatch failed: ${message}`,
|
|
507
|
+
category: "dispatch",
|
|
508
|
+
phase: "status-update",
|
|
509
|
+
recordRuntimeEvent: deps.recordRuntimeEvent,
|
|
510
|
+
});
|
|
478
511
|
},
|
|
479
512
|
};
|
|
480
513
|
}
|
package/lib/text-groups.ts
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
* Owns conservative delayed grouping for Telegram text messages that look like automatic long-message splits
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
const TELEGRAM_TEXT_GROUP_DEBOUNCE_MS =
|
|
7
|
+
const TELEGRAM_TEXT_GROUP_DEBOUNCE_MS = 1000;
|
|
8
8
|
const TELEGRAM_TEXT_GROUP_MIN_SPLIT_LENGTH = 3600;
|
|
9
|
+
const TELEGRAM_TEXT_GROUP_MAX_MESSAGE_ID_GAP = 10;
|
|
9
10
|
|
|
10
11
|
export interface TelegramTextGroupMessage {
|
|
11
12
|
message_id: number;
|
|
@@ -91,7 +92,7 @@ function canAppendTelegramTextGroupMessage<
|
|
|
91
92
|
return (
|
|
92
93
|
!!previous &&
|
|
93
94
|
message.message_id > previous.message_id &&
|
|
94
|
-
message.message_id <= previous.message_id +
|
|
95
|
+
message.message_id <= previous.message_id + TELEGRAM_TEXT_GROUP_MAX_MESSAGE_ID_GAP &&
|
|
95
96
|
text.length > 0 &&
|
|
96
97
|
!isTelegramTextGroupCommand(text)
|
|
97
98
|
);
|