@llblab/pi-telegram 0.5.0 → 0.5.1
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/README.md +1 -1
- package/docs/architecture.md +3 -3
- package/lib/commands.ts +28 -10
- package/lib/queue.ts +12 -0
- package/lib/routing.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -82,7 +82,7 @@ Use these inside the Telegram DM with your bot:
|
|
|
82
82
|
- **`/status`**: View session stats, cost, and use inline buttons to change models.
|
|
83
83
|
- **`/model`**: Open the interactive model selector.
|
|
84
84
|
- **`/compact`**: Start session compaction (only works when the session is idle).
|
|
85
|
-
- **`/stop`**: Abort the active run.
|
|
85
|
+
- **`/stop`**: Abort the active run and clear all waiting Telegram queue items.
|
|
86
86
|
|
|
87
87
|
Telegram command admission is explicit: `/compact`, `/stop`, `/help`, `/start`, `/status`, and `/model` execute immediately. Synthetic model-switch continuation turns still enter the high-priority control lane so they can resume before normal queued prompts when pi becomes safe to dispatch.
|
|
88
88
|
|
package/docs/architecture.md
CHANGED
|
@@ -92,7 +92,7 @@ Admission contract:
|
|
|
92
92
|
|
|
93
93
|
| Admission | Examples | Queue shape | Dispatch rank |
|
|
94
94
|
| --------------------- | ---------------------------------------------------- | -------------------------------------------------------------------- | ------------- |
|
|
95
|
-
| Immediate execution | `/compact`, `/stop`, `/help`, `/start` | Does not enter the Telegram queue
|
|
95
|
+
| Immediate execution | `/compact`, `/stop`, `/help`, `/start` | Does not enter the Telegram queue; `/stop` also clears queued items | N/A |
|
|
96
96
|
| Control queue | Model-switch continuation turns and future deferred controls | `queueLane: control`; accepts control items and continuation prompts | 0 |
|
|
97
97
|
| Priority prompt queue | A waiting prompt promoted by `👍` | `kind: prompt`, `queueLane: priority` | 1 |
|
|
98
98
|
| Default prompt queue | Normal Telegram text/media turns | `kind: prompt`, `queueLane: default` | 2 |
|
|
@@ -113,7 +113,7 @@ This prevents queue races around rapid follow-ups, `/compact`, and mixed local p
|
|
|
113
113
|
|
|
114
114
|
### Abort Behavior
|
|
115
115
|
|
|
116
|
-
When `/stop`
|
|
116
|
+
When `/stop` runs from Telegram, it clears pending model-switch state, clears every waiting Telegram queue item, resets aborted-turn history preservation, and then aborts the active Telegram turn when an abort handler exists. This intentionally favors recovery over preservation: priority/default/control queue items are dropped so the next Telegram message can enter a clean queue and dispatch like a fresh TUI prompt after an interrupted run.
|
|
117
117
|
|
|
118
118
|
## Rendering Model
|
|
119
119
|
|
|
@@ -162,7 +162,7 @@ Current operator controls include:
|
|
|
162
162
|
- Inline status buttons for model and thinking adjustments, applying idle selections immediately while still respecting busy-run restart rules; model-menu inputs are cached briefly and stored inline-menu states are pruned by TTL/LRU so old keyboards expire predictably
|
|
163
163
|
- `/model` for interactive model selection, executed immediately from Telegram and supporting in-flight restart of the active Telegram-owned run on a newly selected model
|
|
164
164
|
- `/compact` for Telegram-triggered pi session compaction when the bridge is idle
|
|
165
|
-
- `/stop` for aborting the active Telegram-owned run
|
|
165
|
+
- `/stop` for aborting the active Telegram-owned run and clearing waiting Telegram queue items
|
|
166
166
|
- `/telegram-status` for pi-side diagnostics as grouped line-by-line sections separated by blank lines: connection, polling, execution, queue, and the recent redacted runtime/API event ring. These sections include polling state, last update id, active turn source ids, pending dispatch, compaction state, active tool count, pending model-switch state, total queue depth, and queue-lane counts. The event ring records transport/API, polling/update, prompt-dispatch, control-action, typing, compaction, setup, session-lifecycle, and attachment queue/delivery failures; benign unchanged edit responses and unsupported empty draft-clear attempts are filtered out so expected preview transport noise does not obscure real failures
|
|
167
167
|
- Queue reactions using `👍` and `👎` apply to waiting text, voice, file, image, and media-group turns by matching the turn's source Telegram message ids; `👎` acts as the canonical queue-removal path because ordinary Telegram DM message deletions are not exposed through the Bot API polling path this bridge uses
|
|
168
168
|
|
package/lib/commands.ts
CHANGED
|
@@ -32,7 +32,10 @@ export const TELEGRAM_BOT_COMMANDS: readonly TelegramBotCommandDefinition[] = [
|
|
|
32
32
|
},
|
|
33
33
|
{ command: "model", description: "Open the interactive model selector" },
|
|
34
34
|
{ command: "compact", description: "Compact the current pi session" },
|
|
35
|
-
{
|
|
35
|
+
{
|
|
36
|
+
command: "stop",
|
|
37
|
+
description: "Abort the current pi task and clear queued turns",
|
|
38
|
+
},
|
|
36
39
|
];
|
|
37
40
|
|
|
38
41
|
export interface TelegramBotCommandRegistrationDeps {
|
|
@@ -178,7 +181,7 @@ export interface TelegramCommandActionDeps<TMessage, TContext> {
|
|
|
178
181
|
export interface TelegramStopCommandDeps {
|
|
179
182
|
hasAbortHandler: () => boolean;
|
|
180
183
|
clearPendingModelSwitch: () => void;
|
|
181
|
-
|
|
184
|
+
clearQueuedTelegramItems: () => number;
|
|
182
185
|
setPreserveQueuedTurnsAsHistory: (preserve: boolean) => void;
|
|
183
186
|
abortCurrentTurn: () => void;
|
|
184
187
|
updateStatus: () => void;
|
|
@@ -408,6 +411,7 @@ export interface TelegramCommandRuntimeDeps<
|
|
|
408
411
|
hasAbortHandler: () => boolean;
|
|
409
412
|
clearPendingModelSwitch: () => void;
|
|
410
413
|
hasQueuedTelegramItems: () => boolean;
|
|
414
|
+
clearQueuedTelegramItems: (ctx: TContext) => number;
|
|
411
415
|
setPreserveQueuedTurnsAsHistory: (preserve: boolean) => void;
|
|
412
416
|
abortCurrentTurn: () => void;
|
|
413
417
|
isIdle: (ctx: TContext) => boolean;
|
|
@@ -439,7 +443,7 @@ export interface TelegramCommandRuntimeDeps<
|
|
|
439
443
|
}
|
|
440
444
|
|
|
441
445
|
export const TELEGRAM_HELP_TEXT =
|
|
442
|
-
"Send me a message and I will forward it to pi. Commands: /status, /model, /compact, /stop.";
|
|
446
|
+
"Send me a message and I will forward it to pi. Commands: /status, /model, /compact, /stop. /stop aborts the current run and clears queued Telegram turns.";
|
|
443
447
|
|
|
444
448
|
function getTelegramCommandErrorMessage(error: unknown): string {
|
|
445
449
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -482,20 +486,32 @@ export function getTelegramCommandExecutionMode(
|
|
|
482
486
|
return action.executionMode;
|
|
483
487
|
}
|
|
484
488
|
|
|
489
|
+
function formatTelegramQueuedTurnCount(count: number): string {
|
|
490
|
+
return count === 1 ? "1 queued turn" : `${count} queued turns`;
|
|
491
|
+
}
|
|
492
|
+
|
|
485
493
|
export async function handleTelegramStopCommand(
|
|
486
494
|
deps: TelegramStopCommandDeps,
|
|
487
495
|
): Promise<void> {
|
|
496
|
+
deps.clearPendingModelSwitch();
|
|
497
|
+
const clearedCount = deps.clearQueuedTelegramItems();
|
|
498
|
+
deps.setPreserveQueuedTurnsAsHistory(false);
|
|
488
499
|
if (!deps.hasAbortHandler()) {
|
|
489
|
-
|
|
500
|
+
const clearedSuffix =
|
|
501
|
+
clearedCount > 0
|
|
502
|
+
? ` Cleared ${formatTelegramQueuedTurnCount(clearedCount)}.`
|
|
503
|
+
: "";
|
|
504
|
+
if (clearedCount > 0) deps.updateStatus();
|
|
505
|
+
await deps.sendTextReply(`No active turn.${clearedSuffix}`);
|
|
490
506
|
return;
|
|
491
507
|
}
|
|
492
|
-
deps.clearPendingModelSwitch();
|
|
493
|
-
if (deps.hasQueuedTelegramItems()) {
|
|
494
|
-
deps.setPreserveQueuedTurnsAsHistory(true);
|
|
495
|
-
}
|
|
496
508
|
deps.abortCurrentTurn();
|
|
497
509
|
deps.updateStatus();
|
|
498
|
-
|
|
510
|
+
const clearedSuffix =
|
|
511
|
+
clearedCount > 0
|
|
512
|
+
? ` Cleared ${formatTelegramQueuedTurnCount(clearedCount)}.`
|
|
513
|
+
: "";
|
|
514
|
+
await deps.sendTextReply(`Aborted current turn.${clearedSuffix}`);
|
|
499
515
|
}
|
|
500
516
|
|
|
501
517
|
export async function handleTelegramCompactCommand(
|
|
@@ -656,6 +672,7 @@ export function createTelegramCommandHandlerTargetRuntime<
|
|
|
656
672
|
hasAbortHandler: deps.hasAbortHandler,
|
|
657
673
|
clearPendingModelSwitch: deps.clearPendingModelSwitch,
|
|
658
674
|
hasQueuedTelegramItems: deps.hasQueuedTelegramItems,
|
|
675
|
+
clearQueuedTelegramItems: deps.clearQueuedTelegramItems,
|
|
659
676
|
setPreserveQueuedTurnsAsHistory: deps.setPreserveQueuedTurnsAsHistory,
|
|
660
677
|
abortCurrentTurn: deps.abortCurrentTurn,
|
|
661
678
|
isIdle: deps.isIdle,
|
|
@@ -736,7 +753,8 @@ async function handleTelegramCommandRuntime<
|
|
|
736
753
|
await handleTelegramStopCommand({
|
|
737
754
|
hasAbortHandler: deps.hasAbortHandler,
|
|
738
755
|
clearPendingModelSwitch: deps.clearPendingModelSwitch,
|
|
739
|
-
|
|
756
|
+
clearQueuedTelegramItems: () =>
|
|
757
|
+
deps.clearQueuedTelegramItems(commandCtx),
|
|
740
758
|
setPreserveQueuedTurnsAsHistory: deps.setPreserveQueuedTurnsAsHistory,
|
|
741
759
|
abortCurrentTurn: deps.abortCurrentTurn,
|
|
742
760
|
updateStatus: updateStatusFor(commandCtx),
|
package/lib/queue.ts
CHANGED
|
@@ -1061,6 +1061,7 @@ export interface TelegramQueueMutationControllerDeps<
|
|
|
1061
1061
|
export interface TelegramQueueMutationController<TContext> {
|
|
1062
1062
|
append: (item: TelegramQueueItem<TContext>, ctx: TContext) => void;
|
|
1063
1063
|
reorder: (ctx: TContext) => void;
|
|
1064
|
+
clear: (ctx: TContext) => number;
|
|
1064
1065
|
removeByMessageIds: (messageIds: number[], ctx: TContext) => number;
|
|
1065
1066
|
clearPriorityByMessageId: (messageId: number, ctx: TContext) => boolean;
|
|
1066
1067
|
prioritizeByMessageId: (messageId: number, ctx: TContext) => boolean;
|
|
@@ -1262,6 +1263,7 @@ export function createTelegramQueueMutationController<TContext>(
|
|
|
1262
1263
|
append: (item, ctx) =>
|
|
1263
1264
|
appendTelegramQueueItemRuntime(item, buildRuntimeDeps(ctx)),
|
|
1264
1265
|
reorder: (ctx) => reorderTelegramQueueItemsRuntime(buildRuntimeDeps(ctx)),
|
|
1266
|
+
clear: (ctx) => clearTelegramQueueItemsRuntime(buildRuntimeDeps(ctx)),
|
|
1265
1267
|
removeByMessageIds: (messageIds, ctx) =>
|
|
1266
1268
|
removeTelegramQueueItemsByMessageIdsRuntime(
|
|
1267
1269
|
messageIds,
|
|
@@ -1291,6 +1293,16 @@ export function reorderTelegramQueueItemsRuntime<TContext>(
|
|
|
1291
1293
|
deps.updateStatus(deps.ctx);
|
|
1292
1294
|
}
|
|
1293
1295
|
|
|
1296
|
+
export function clearTelegramQueueItemsRuntime<TContext>(
|
|
1297
|
+
deps: TelegramQueueMutationRuntimeDeps<TContext>,
|
|
1298
|
+
): number {
|
|
1299
|
+
const removedCount = deps.getQueuedItems().length;
|
|
1300
|
+
if (removedCount === 0) return 0;
|
|
1301
|
+
deps.setQueuedItems([]);
|
|
1302
|
+
deps.updateStatus(deps.ctx);
|
|
1303
|
+
return removedCount;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1294
1306
|
export function removeTelegramQueueItemsByMessageIdsRuntime<TContext>(
|
|
1295
1307
|
messageIds: number[],
|
|
1296
1308
|
deps: TelegramQueueMutationRuntimeDeps<TContext>,
|
package/lib/routing.ts
CHANGED
|
@@ -131,6 +131,7 @@ export function createTelegramInboundRouteRuntime<
|
|
|
131
131
|
hasAbortHandler: deps.bridgeRuntime.abort.hasHandler,
|
|
132
132
|
clearPendingModelSwitch: deps.modelSwitchController.clearPendingSwitch,
|
|
133
133
|
hasQueuedTelegramItems: deps.telegramQueueStore.hasQueuedItems,
|
|
134
|
+
clearQueuedTelegramItems: deps.queueMutationRuntime.clear,
|
|
134
135
|
setPreserveQueuedTurnsAsHistory:
|
|
135
136
|
deps.bridgeRuntime.lifecycle.setPreserveQueuedTurnsAsHistory,
|
|
136
137
|
abortCurrentTurn: deps.bridgeRuntime.abort.abortTurn,
|