@llblab/pi-telegram 0.8.1 → 0.8.2
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 +7 -0
- package/index.ts +4 -0
- package/lib/inbound-handlers.ts +15 -2
- package/lib/locks.ts +16 -0
- package/lib/menu-model.ts +1 -1
- package/lib/menu-thinking.ts +1 -1
- package/lib/preview.ts +15 -0
- package/lib/queue.ts +9 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.2: Lock-Safe Delivery
|
|
4
|
+
|
|
5
|
+
- `[Lock Safety]` Active Telegram turns now re-check singleton ownership before preview flushes and final agent-end delivery. Impact: an old π instance stays silent after another instance takes the Telegram bridge lock, even if the old instance finishes a long-running prompt later.
|
|
6
|
+
- `[Inbound Handlers]` The first step of an inbound composition now receives the full configured handler timeout before elapsed-time accounting starts on later steps. Impact: composition timeout behavior is deterministic and avoids one-millisecond test/runtime drift at pipeline start.
|
|
7
|
+
- `[Menu UI]` Model and Thinking submenu headers now include their matching command icons (`🤖` and `🧠`). Impact: submenu headings match the Queue menu's icon-led style.
|
|
8
|
+
- `[Package]` Bumped package metadata to `0.8.2` and kept the lockfile in sync.
|
|
9
|
+
|
|
3
10
|
## 0.8.1: Outbound Voice Translation Hotfix
|
|
4
11
|
|
|
5
12
|
- `[Outbound Voice]` Composed voice handlers now pass the original `telegram_voice` text to the first pipeline step through stdin, then continue piping each step's stdout into the next step. Impact: translate-from-stdin voice pipelines can translate hidden voice text before TTS instead of failing with an empty first-step input.
|
package/index.ts
CHANGED
|
@@ -49,6 +49,8 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
49
49
|
const { abort, lifecycle, queue, setup, typing } = bridgeRuntime;
|
|
50
50
|
const configStore = Config.createTelegramConfigStore();
|
|
51
51
|
const lockRuntime = Locks.createTelegramLockRuntime<Pi.ExtensionContext>();
|
|
52
|
+
const lockOwnershipGuard =
|
|
53
|
+
Locks.createTelegramLockOwnershipGuard(lockRuntime);
|
|
52
54
|
const activeTurnRuntime = Queue.createTelegramActiveTurnStore();
|
|
53
55
|
const buttonActionStore = OutboundHandlers.createTelegramButtonActionStore();
|
|
54
56
|
const pendingModelSwitchStore =
|
|
@@ -198,6 +200,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
198
200
|
sendDraft: sendMessageDraft,
|
|
199
201
|
sendMessage,
|
|
200
202
|
editMessageText: editTelegramMessageText,
|
|
203
|
+
canSend: lockOwnershipGuard.ownsCurrentProcess,
|
|
201
204
|
...replyTransport,
|
|
202
205
|
});
|
|
203
206
|
const { finalizeMarkdownPreview } =
|
|
@@ -459,6 +462,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
459
462
|
sendQueuedAttachments: queuedAttachmentSender,
|
|
460
463
|
planOutboundReply: outboundReplyPlanner,
|
|
461
464
|
sendOutboundReplyArtifacts: outboundReplyArtifactSender,
|
|
465
|
+
isCurrentOwner: lockOwnershipGuard.ownsContext,
|
|
462
466
|
getActiveToolExecutions: lifecycle.getActiveToolExecutions,
|
|
463
467
|
setActiveToolExecutions: lifecycle.setActiveToolExecutions,
|
|
464
468
|
triggerPendingModelSwitchAbort: modelSwitchController.triggerPendingAbort,
|
package/lib/inbound-handlers.ts
CHANGED
|
@@ -258,6 +258,15 @@ function getRemainingTelegramInboundTimeout(
|
|
|
258
258
|
return Math.max(1, timeout - (Date.now() - startedAt));
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
+
function getTelegramInboundInitialCompositionStepTimeout(
|
|
262
|
+
handler: TelegramInboundHandlerConfig,
|
|
263
|
+
step: TelegramInboundCommandTemplateConfig,
|
|
264
|
+
): number {
|
|
265
|
+
const timeout = getTelegramInboundHandlerTimeout(handler);
|
|
266
|
+
const stepTimeout = getTelegramInboundHandlerConfiguredTimeout(step);
|
|
267
|
+
return stepTimeout === undefined ? timeout : Math.min(stepTimeout, timeout);
|
|
268
|
+
}
|
|
269
|
+
|
|
261
270
|
function getTelegramInboundCompositionStepTimeout(
|
|
262
271
|
handler: TelegramInboundHandlerConfig,
|
|
263
272
|
step: TelegramInboundCommandTemplateConfig,
|
|
@@ -421,7 +430,9 @@ async function executeTelegramTextHandler(
|
|
|
421
430
|
output,
|
|
422
431
|
cwd,
|
|
423
432
|
deps,
|
|
424
|
-
|
|
433
|
+
index === 0
|
|
434
|
+
? getTelegramInboundInitialCompositionStepTimeout(handler, step)
|
|
435
|
+
: getTelegramInboundCompositionStepTimeout(handler, step, startedAt),
|
|
425
436
|
);
|
|
426
437
|
} catch (error) {
|
|
427
438
|
if (typeof step === "object" && step.critical) throw error;
|
|
@@ -498,7 +509,9 @@ async function executeTelegramInboundHandler(
|
|
|
498
509
|
cwd,
|
|
499
510
|
deps,
|
|
500
511
|
false,
|
|
501
|
-
|
|
512
|
+
index === 0
|
|
513
|
+
? getTelegramInboundInitialCompositionStepTimeout(handler, step)
|
|
514
|
+
: getTelegramInboundCompositionStepTimeout(handler, step, startedAt),
|
|
502
515
|
index === 0 ? undefined : output,
|
|
503
516
|
);
|
|
504
517
|
} catch (error) {
|
package/lib/locks.ts
CHANGED
|
@@ -61,6 +61,11 @@ export interface TelegramLockRuntime<TContext extends TelegramLockContext> {
|
|
|
61
61
|
owns: (ctx?: TelegramLockContext) => boolean;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
export interface TelegramLockOwnershipGuard<TContext extends TelegramLockContext> {
|
|
65
|
+
ownsCurrentProcess: () => boolean;
|
|
66
|
+
ownsContext: (ctx: TContext) => boolean;
|
|
67
|
+
}
|
|
68
|
+
|
|
64
69
|
export interface TelegramLockRuntimeOptions {
|
|
65
70
|
key?: string;
|
|
66
71
|
locksPath?: string;
|
|
@@ -234,6 +239,17 @@ export function createTelegramLockRuntime<TContext extends TelegramLockContext>(
|
|
|
234
239
|
};
|
|
235
240
|
}
|
|
236
241
|
|
|
242
|
+
export function createTelegramLockOwnershipGuard<
|
|
243
|
+
TContext extends TelegramLockContext,
|
|
244
|
+
>(
|
|
245
|
+
lock: TelegramLockRuntime<TContext>,
|
|
246
|
+
): TelegramLockOwnershipGuard<TContext> {
|
|
247
|
+
return {
|
|
248
|
+
ownsCurrentProcess: () => lock.owns(),
|
|
249
|
+
ownsContext: (ctx) => lock.owns(ctx),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
237
253
|
export function createTelegramLockedPollingRuntime<
|
|
238
254
|
TContext extends TelegramLockContext,
|
|
239
255
|
>(
|
package/lib/menu-model.ts
CHANGED
|
@@ -236,7 +236,7 @@ export interface TelegramModelMenuRuntime<
|
|
|
236
236
|
|
|
237
237
|
export const TELEGRAM_MODEL_PAGE_SIZE = 6;
|
|
238
238
|
const TELEGRAM_MODEL_PAGE_PICKER_ROW_SIZE = 4;
|
|
239
|
-
export const MODEL_MENU_TITLE = "<b
|
|
239
|
+
export const MODEL_MENU_TITLE = "<b>🤖 Choose a model:</b>";
|
|
240
240
|
export const MODEL_PAGE_MENU_TITLE = "<b>Choose a page:</b>";
|
|
241
241
|
|
|
242
242
|
function truncateTelegramButtonLabel(label: string, maxLength = 56): string {
|
package/lib/menu-thinking.ts
CHANGED
|
@@ -109,7 +109,7 @@ export async function handleTelegramThinkingMenuCallbackAction(
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
export function buildThinkingMenuText(): string {
|
|
112
|
-
return "<b
|
|
112
|
+
return "<b>🧠 Choose a thinking level:</b>";
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
export function buildThinkingMenuReplyMarkup(
|
package/lib/preview.ts
CHANGED
|
@@ -89,6 +89,7 @@ export interface TelegramPreviewRuntimeDeps<
|
|
|
89
89
|
chunks: TelegramRenderedChunk[],
|
|
90
90
|
options?: { replyMarkup?: TReplyMarkup },
|
|
91
91
|
) => Promise<number | undefined>;
|
|
92
|
+
canSend?: () => boolean;
|
|
92
93
|
}
|
|
93
94
|
|
|
94
95
|
export interface TelegramPreviewActiveTurn {
|
|
@@ -182,6 +183,7 @@ export interface TelegramPreviewControllerDeps<
|
|
|
182
183
|
chunks: TelegramRenderedChunk[],
|
|
183
184
|
options?: { replyMarkup?: TReplyMarkup },
|
|
184
185
|
) => Promise<number | undefined>;
|
|
186
|
+
canSend?: () => boolean;
|
|
185
187
|
throttleMs?: number;
|
|
186
188
|
maxDraftId?: number;
|
|
187
189
|
setTimer?: (
|
|
@@ -418,6 +420,7 @@ export function createTelegramPreviewController<
|
|
|
418
420
|
options,
|
|
419
421
|
),
|
|
420
422
|
editRenderedMessage: deps.editRenderedMessage,
|
|
423
|
+
canSend: deps.canSend,
|
|
421
424
|
});
|
|
422
425
|
return {
|
|
423
426
|
getState: () => state,
|
|
@@ -574,6 +577,10 @@ async function performTelegramPreviewFlush<
|
|
|
574
577
|
state: TelegramPreviewRuntimeState,
|
|
575
578
|
deps: TelegramPreviewRuntimeDeps<TReplyMarkup>,
|
|
576
579
|
): Promise<void> {
|
|
580
|
+
if (deps.canSend && !deps.canSend()) {
|
|
581
|
+
await clearTelegramPreview(chatId, deps);
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
577
584
|
const snapshot = buildTelegramPreviewSnapshot({
|
|
578
585
|
state,
|
|
579
586
|
maxMessageLength: deps.maxMessageLength,
|
|
@@ -658,6 +665,10 @@ export async function finalizeTelegramPreview<
|
|
|
658
665
|
): Promise<boolean> {
|
|
659
666
|
const state = deps.getState();
|
|
660
667
|
if (!state) return false;
|
|
668
|
+
if (deps.canSend && !deps.canSend()) {
|
|
669
|
+
await clearTelegramPreview(chatId, deps);
|
|
670
|
+
return false;
|
|
671
|
+
}
|
|
661
672
|
await flushTelegramPreview(chatId, deps);
|
|
662
673
|
const finalText = buildTelegramPreviewFinalText(state);
|
|
663
674
|
if (!finalText) {
|
|
@@ -683,6 +694,10 @@ export async function finalizeTelegramMarkdownPreview<
|
|
|
683
694
|
): Promise<boolean> {
|
|
684
695
|
const state = deps.getState();
|
|
685
696
|
if (!state) return false;
|
|
697
|
+
if (deps.canSend && !deps.canSend()) {
|
|
698
|
+
await clearTelegramPreview(chatId, deps);
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
686
701
|
await flushTelegramPreview(chatId, deps);
|
|
687
702
|
const chunks = deps.renderTelegramMessage(markdown, { mode: "markdown" });
|
|
688
703
|
if (chunks.length === 0) {
|
package/lib/queue.ts
CHANGED
|
@@ -771,6 +771,7 @@ export interface TelegramAgentEndRuntimeDeps<
|
|
|
771
771
|
preserveQueuedTurnsAsHistory: boolean;
|
|
772
772
|
resetRuntimeState: () => void;
|
|
773
773
|
updateStatus: () => void;
|
|
774
|
+
isCurrentOwner?: () => boolean;
|
|
774
775
|
dispatchNextQueuedTelegramTurn: () => void;
|
|
775
776
|
clearPreview: (chatId: number) => Promise<void>;
|
|
776
777
|
setPreviewPendingText: (text: string) => void;
|
|
@@ -815,6 +816,7 @@ export interface TelegramAgentEndHookRuntimeDeps<
|
|
|
815
816
|
getPreserveQueuedTurnsAsHistory: () => boolean;
|
|
816
817
|
resetRuntimeState: () => void;
|
|
817
818
|
updateStatus: (ctx: TContext) => void;
|
|
819
|
+
isCurrentOwner?: (ctx: TContext) => boolean;
|
|
818
820
|
dispatchNextQueuedTelegramTurn: (ctx: TContext) => void;
|
|
819
821
|
requestDeferredDispatchNextQueuedTelegramTurn: (
|
|
820
822
|
dispatch: (ctx: TContext) => void,
|
|
@@ -932,6 +934,9 @@ export function createTelegramAgentEndHook<
|
|
|
932
934
|
preserveQueuedTurnsAsHistory: deps.getPreserveQueuedTurnsAsHistory(),
|
|
933
935
|
resetRuntimeState: deps.resetRuntimeState,
|
|
934
936
|
updateStatus: () => deps.updateStatus(ctx),
|
|
937
|
+
isCurrentOwner: deps.isCurrentOwner
|
|
938
|
+
? () => deps.isCurrentOwner?.(ctx) ?? false
|
|
939
|
+
: undefined,
|
|
935
940
|
dispatchNextQueuedTelegramTurn: () => {
|
|
936
941
|
deps.requestDeferredDispatchNextQueuedTelegramTurn(
|
|
937
942
|
deps.dispatchNextQueuedTelegramTurn,
|
|
@@ -964,6 +969,10 @@ export async function handleTelegramAgentEndRuntime<
|
|
|
964
969
|
const replyMarkup = outboundReply?.replyMarkup;
|
|
965
970
|
deps.resetRuntimeState();
|
|
966
971
|
deps.updateStatus();
|
|
972
|
+
if (turn && deps.isCurrentOwner && !deps.isCurrentOwner()) {
|
|
973
|
+
await deps.clearPreview(turn.chatId);
|
|
974
|
+
return;
|
|
975
|
+
}
|
|
967
976
|
const endPlan = buildTelegramAgentEndPlan({
|
|
968
977
|
hasTurn: !!turn,
|
|
969
978
|
stopReason: assistant.stopReason,
|