@llblab/pi-telegram 0.33.0 → 0.33.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 +10 -0
- package/docs/architecture.md +2 -2
- package/lib/bindings.ts +31 -3
- package/lib/commands.ts +35 -14
- package/lib/status.ts +1 -0
- package/package.json +1 -1
- package/skills/generated-control-surface/SKILL.md +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
> Each release keeps at most 8 outcome records of at most 512 characters.
|
|
4
4
|
|
|
5
|
+
## 0.33.2: Structured Filesystem Surfaces
|
|
6
|
+
|
|
7
|
+
- `Filesystem Ordering`: Sorts generated directory surfaces by visible directories, hidden directories, visible files, then hidden files, with alphabetical ordering inside each category before fixed ten-entry pagination.
|
|
8
|
+
- `Surface Metadata`: Reuses compact status-style key-value rows for filesystem path and range metadata, with bold labels and inline-code values instead of middle-dot section separators or duplicated entry listings.
|
|
9
|
+
|
|
10
|
+
## 0.33.1: Rendered Command Replies And Visible Compaction
|
|
11
|
+
|
|
12
|
+
- `Command Rendering`: Restored the `/next` empty-queue emphasis by preserving its HTML source and forwarding `parseMode: "HTML"` through the command reply adapter, so command helpers select the renderer explicitly instead of leaking HTML or Markdown syntax as plain text.
|
|
13
|
+
- `Compaction Visibility`: Reports `compacting` ahead of generic active or pending status and sends the same start/completion notices for observed automatic compaction as for manually requested compaction, while retaining queue blocking and deferred dispatch.
|
|
14
|
+
|
|
5
15
|
## 0.33.0: Matrix Controls And Pinned Filesystem Navigation
|
|
6
16
|
|
|
7
17
|
- `Button Matrix`: Extended `telegram_button` and its plural alias from flat arrays to JSON matrices: top-level objects remain full-width rows, while nested arrays intentionally group one to three peer controls horizontally; empty, oversized, or deeper rows fail closed without changing existing object, attribute, or flat-array behavior.
|
package/docs/architecture.md
CHANGED
|
@@ -352,7 +352,7 @@ The bridge does not mirror arbitrary `ctx.ui.confirm/input/select/custom` prompt
|
|
|
352
352
|
|
|
353
353
|
## Diagnostics And Operational Behavior
|
|
354
354
|
|
|
355
|
-
Status rendering distinguishes connected, active, dispatching, queued, tool-running, model-switching, and compacting states. If a queue mutation removes the last waiting item while Telegram-owned work still has running tools, status remains active instead of degrading to connected.
|
|
355
|
+
Status rendering distinguishes connected, active, dispatching, queued, tool-running, model-switching, and compacting states; the Telegram status menu gives compaction precedence over generic active or pending work. Observed automatic compaction sends the same start and completion notices as the manual command without duplicating notices for command-owned compaction. If a queue mutation removes the last waiting item while Telegram-owned work still has running tools, status remains active instead of degrading to connected.
|
|
356
356
|
|
|
357
357
|
Queue reactions are reversible shortcut controls for waiting turns. The runtime reconciles each complete `MessageReactionUpdated.new_reaction` set: any removal reaction (`👎`, `👻`, `💔`, `💩`, `🗑`) suppresses the governed prompt without discarding its queue authority; otherwise any promotion reaction (`👍`, `⚡️`, `❤️`, `🕊`, `🔥`) moves it to priority; otherwise it returns to the default lane. Suppressed prompts remain visible in the queue menu, survive authenticated queue handoff, and do not block unrelated dispatch. Reaction changes first flush a matching delayed text or media group so the governed turn exists before mutation. Once Pi has consumed a prompt, reactions cannot retract it.
|
|
358
358
|
|
|
@@ -362,7 +362,7 @@ Complete intermediate assistant text blocks from Telegram-originated activity ar
|
|
|
362
362
|
|
|
363
363
|
`assistant.activity` is an independent bridge-owned projection over normalized Activity events. Each process reloads the shared file-backed setting at `agent-start` before activity admission, so multi-instance mode cannot continue projecting a stale broader process-local selection. Omitted values resolve to `verbose`, while invalid values fail closed to `quiet`; `thinking` and `tools` select one technical class, while `verbose` enables both. Provider-exposed thinking uses persistent ordinary HTML containing only a standard expandable blockquote with a bounded redacted latest-text window and inline Markdown rendered as Telegram HTML. Completed executed tools use native Rich Messages: each closed `<Tool>: <status>` root details node renders snake-case names as title words while preserving an uppercase two- or three-letter repeated prefix per word, then the native disclosure chevron reveals an open-by-default `arguments` child plus closed retained `update N` and `result`/`error` child details with lowercase monospaced, marker-free summaries and JSON pre blocks; known-safe Rich rejections fall back to the previous HTML disclosure. The projection captures the exact target and transport stamp at activity admission, serializes updates, preserves tool-start order, closes coalescing across assistant/thinking boundaries, bounds retained text/update memory plus edit frames and message/tool size, disables previews and HTTP(S) auto-link recognition inside technical evidence, and never replays a possibly committed send. Session generations own independent queues, so replacement drops queued old work without waiting on an old call. Both proactive prose and active-turn final delivery wait for the admitted activity queue inside their extension-owned delivery tasks, preserving technical-before-semantic ordering without delaying Pi lifecycle completion. Settlement, replacement, disconnect, failure, or stale authority clears only local ownership; already-sent activity messages remain in chat.
|
|
364
364
|
|
|
365
|
-
Telegram prompt guidance is context- and authority-aware. The package and source-checkout extension both contribute `telegram-bridge` plus the optional `generated-control-surface` Skill through Pi resource discovery. The latter treats `interface = f(state, capabilities, intent)` as a renderer-neutral primitive, compiling transient evidence-backed controls over domain-owned workflows, systems, navigation, supervision, and decisions without creating parallel application state. Its filesystem adapter reserves the first full-width row for parent traversal outside root, places available Previous/Next controls together in one compact row immediately afterward,
|
|
365
|
+
Telegram prompt guidance is context- and authority-aware. The package and source-checkout extension both contribute `telegram-bridge` plus the optional `generated-control-surface` Skill through Pi resource discovery. The latter treats `interface = f(state, capabilities, intent)` as a renderer-neutral primitive, compiling transient evidence-backed controls over domain-owned workflows, systems, navigation, supervision, and decisions without creating parallel application state. Its filesystem adapter reserves the first full-width row for parent traversal outside root, places available Previous/Next controls together in one compact row immediately afterward, orders visible directories, hidden directories, visible files, and hidden files alphabetically within each category before fixed ten-entry pagination, renders path/range metadata as stacked status-style key-value rows instead of middle-dot prose, emits the complete Telegram control set through one JSON-matrix action, suppresses duplicate plain/monospaced listings and default Refresh unless user preference overrides presentation, and retains an ordinary numbered fallback when buttons are unavailable. Only an exact direct owner or live registered follower exposes the two pi-telegram delivery tools, their active-tool metadata, and the compact routing suffix. Disconnect or authority loss removes those tool surfaces for subsequent requests without touching foreign tools; reconnect/recovery restores only the pi-telegram subset that was active before suspension, including across same-process reload. Telegram-originated turns route to the stable Skill contract and retain dynamic blocks such as `[voice] delivery: automatic voice`; the Skill and public documentation own syntax, target routing, Threaded Mode behavior, and diagnostics.
|
|
366
366
|
|
|
367
367
|
## In-Flight Model Switching
|
|
368
368
|
|
package/lib/bindings.ts
CHANGED
|
@@ -860,6 +860,21 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
860
860
|
target: turn?.target,
|
|
861
861
|
});
|
|
862
862
|
};
|
|
863
|
+
let observedAutomaticCompaction = false;
|
|
864
|
+
const sendCompactionNotice = async (text: string): Promise<void> => {
|
|
865
|
+
const turn = activeTurnRuntime.get();
|
|
866
|
+
const target = turn?.target ?? proactivePushTargetGetter?.();
|
|
867
|
+
if (!target) return;
|
|
868
|
+
try {
|
|
869
|
+
await sendMarkdownReply(target.chatId, turn?.replyToMessageId, text, {
|
|
870
|
+
target,
|
|
871
|
+
});
|
|
872
|
+
} catch (error) {
|
|
873
|
+
recordRuntimeEvent("delivery", error, {
|
|
874
|
+
phase: "compaction-notice",
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
};
|
|
863
878
|
const compactionObserver = Lifecycle.createTelegramCompactionObserverRuntime({
|
|
864
879
|
isContextActive: isSessionContextActive,
|
|
865
880
|
setCompactionInProgress: lifecycle.setCompactionInProgress,
|
|
@@ -870,7 +885,10 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
870
885
|
deferredQueueDispatchRuntime.request,
|
|
871
886
|
dispatchNextQueuedTelegramTurn,
|
|
872
887
|
recordRuntimeEvent,
|
|
873
|
-
onCompactionAbandoned:
|
|
888
|
+
onCompactionAbandoned: () => {
|
|
889
|
+
observedAutomaticCompaction = false;
|
|
890
|
+
activityRuntime.onCompactionAbandoned();
|
|
891
|
+
},
|
|
874
892
|
});
|
|
875
893
|
const messageActivityTypingHooks =
|
|
876
894
|
Lifecycle.createTelegramMessageActivityTypingHooks({
|
|
@@ -902,6 +920,7 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
902
920
|
activityRuntime.onSessionShutdown();
|
|
903
921
|
activityVerbosityRuntime?.reset();
|
|
904
922
|
assistantOutputRuntime.stop();
|
|
923
|
+
observedAutomaticCompaction = false;
|
|
905
924
|
compactionObserver.onSessionShutdown();
|
|
906
925
|
if (event.reason === "quit" && disconnectOnQuit) {
|
|
907
926
|
try {
|
|
@@ -916,15 +935,24 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
916
935
|
}
|
|
917
936
|
await sessionLifecycleRuntime.onSessionShutdown(event, ctx);
|
|
918
937
|
},
|
|
919
|
-
onSessionBeforeCompact(event, ctx) {
|
|
938
|
+
async onSessionBeforeCompact(event, ctx) {
|
|
920
939
|
if (!isSessionContextActive(ctx)) return;
|
|
940
|
+
const shouldNotify = !(lifecycle.isCompactionInProgress?.() ?? false);
|
|
941
|
+
if (shouldNotify) observedAutomaticCompaction = true;
|
|
921
942
|
activityRuntime.onCompactionStart(Pi.getSessionCompactionReason(event));
|
|
922
943
|
compactionObserver.onSessionBeforeCompact(event, ctx);
|
|
944
|
+
if (shouldNotify) {
|
|
945
|
+
await sendCompactionNotice(Commands.TELEGRAM_COMPACTION_STARTED_TEXT);
|
|
946
|
+
}
|
|
923
947
|
},
|
|
924
|
-
onSessionCompact(event, ctx) {
|
|
948
|
+
async onSessionCompact(event, ctx) {
|
|
925
949
|
if (!isSessionContextActive(ctx)) return;
|
|
926
950
|
activityRuntime.onCompactionEnd(Pi.getSessionCompactionReason(event));
|
|
927
951
|
compactionObserver.onSessionCompact(event, ctx);
|
|
952
|
+
if (observedAutomaticCompaction) {
|
|
953
|
+
observedAutomaticCompaction = false;
|
|
954
|
+
await sendCompactionNotice(Commands.TELEGRAM_COMPACTION_COMPLETED_TEXT);
|
|
955
|
+
}
|
|
928
956
|
},
|
|
929
957
|
async onAgentStart(event, ctx) {
|
|
930
958
|
if (!isSessionContextActive(ctx)) return;
|
package/lib/commands.ts
CHANGED
|
@@ -185,6 +185,10 @@ export function formatTelegramCommandEmojiPrefix(
|
|
|
185
185
|
return `${getTelegramCommandEmoji(command)} `;
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
export const TELEGRAM_COMPACTION_STARTED_TEXT =
|
|
189
|
+
`${formatTelegramCommandEmojiPrefix("compact")}Compaction started.`;
|
|
190
|
+
export const TELEGRAM_COMPACTION_COMPLETED_TEXT = "✅ Compaction completed.";
|
|
191
|
+
|
|
188
192
|
function formatTelegramBotCommandDescription(
|
|
189
193
|
command: TelegramCommandEmojiName,
|
|
190
194
|
description: string,
|
|
@@ -716,7 +720,10 @@ export interface TelegramCommandTargetRuntimeDeps<TContext> {
|
|
|
716
720
|
chatId: number,
|
|
717
721
|
replyToMessageId: number,
|
|
718
722
|
text: string,
|
|
719
|
-
options?: {
|
|
723
|
+
options?: {
|
|
724
|
+
parseMode?: "HTML";
|
|
725
|
+
target?: { chatId: number; threadId?: number };
|
|
726
|
+
},
|
|
720
727
|
) => Promise<unknown>;
|
|
721
728
|
}
|
|
722
729
|
|
|
@@ -734,7 +741,11 @@ export interface TelegramCommandTargetRuntime<
|
|
|
734
741
|
showStatus: (message: TMessage, ctx: TContext) => Promise<void>;
|
|
735
742
|
openModelMenu: (message: TMessage, ctx: TContext) => Promise<void>;
|
|
736
743
|
openSettingsMenu: (message: TMessage, ctx: TContext) => Promise<void>;
|
|
737
|
-
sendTextReply: (
|
|
744
|
+
sendTextReply: (
|
|
745
|
+
message: TMessage,
|
|
746
|
+
text: string,
|
|
747
|
+
options?: { parseMode?: "HTML" },
|
|
748
|
+
) => Promise<void>;
|
|
738
749
|
}
|
|
739
750
|
|
|
740
751
|
export function getTelegramCommandMessageTarget(
|
|
@@ -917,9 +928,10 @@ export function createTelegramCommandTargetRuntime<
|
|
|
917
928
|
target.threadId,
|
|
918
929
|
);
|
|
919
930
|
},
|
|
920
|
-
sendTextReply: async (message, text) => {
|
|
931
|
+
sendTextReply: async (message, text, options) => {
|
|
921
932
|
const target = getTelegramCommandMessageTarget(message);
|
|
922
933
|
await deps.sendTextReply(target.chatId, target.replyToMessageId, text, {
|
|
934
|
+
...options,
|
|
923
935
|
target,
|
|
924
936
|
});
|
|
925
937
|
},
|
|
@@ -1002,7 +1014,11 @@ export interface TelegramCommandRuntimeDeps<
|
|
|
1002
1014
|
registerBotCommands: () => Promise<void>;
|
|
1003
1015
|
getPromptTemplateCommands?: () => readonly TelegramPromptTemplateMenuCommand[];
|
|
1004
1016
|
persistConfig: () => Promise<void>;
|
|
1005
|
-
sendTextReply: (
|
|
1017
|
+
sendTextReply: (
|
|
1018
|
+
message: TMessage,
|
|
1019
|
+
text: string,
|
|
1020
|
+
options?: { parseMode?: "HTML" },
|
|
1021
|
+
) => Promise<void>;
|
|
1006
1022
|
sendInteractiveMessage?: TelegramCompactConfirmationDeps["sendInteractiveMessage"];
|
|
1007
1023
|
assertExecutionCurrent?: (message: TMessage) => void;
|
|
1008
1024
|
}
|
|
@@ -1185,11 +1201,14 @@ export async function handleTelegramNextCommand(deps: {
|
|
|
1185
1201
|
dispatchNextQueuedTurn: () => void;
|
|
1186
1202
|
clearFoldForDispatch: () => void;
|
|
1187
1203
|
updateStatus: () => void;
|
|
1188
|
-
sendTextReply: (
|
|
1204
|
+
sendTextReply: (
|
|
1205
|
+
text: string,
|
|
1206
|
+
options?: { parseMode?: "HTML" },
|
|
1207
|
+
) => Promise<void>;
|
|
1189
1208
|
}): Promise<void> {
|
|
1190
1209
|
deps.clearPendingModelSwitch();
|
|
1191
1210
|
if (!deps.hasQueuedItems()) {
|
|
1192
|
-
await deps.sendTextReply("
|
|
1211
|
+
await deps.sendTextReply("<b>Queue is empty.</b>", { parseMode: "HTML" });
|
|
1193
1212
|
return;
|
|
1194
1213
|
}
|
|
1195
1214
|
if (!deps.isIdle() && deps.hasAbortHandler()) {
|
|
@@ -1294,7 +1313,7 @@ export async function handleTelegramCompactConfirmationCallback<TContext>(
|
|
|
1294
1313
|
await deps.editInteractiveMessage(
|
|
1295
1314
|
chatId,
|
|
1296
1315
|
messageId,
|
|
1297
|
-
|
|
1316
|
+
TELEGRAM_COMPACTION_STARTED_TEXT,
|
|
1298
1317
|
"plain",
|
|
1299
1318
|
{ inline_keyboard: [] },
|
|
1300
1319
|
);
|
|
@@ -1335,7 +1354,7 @@ export async function handleTelegramCompactCommand(
|
|
|
1335
1354
|
deps.setCompactionInProgress(false);
|
|
1336
1355
|
deps.updateStatus();
|
|
1337
1356
|
dispatchNextQueuedTelegramTurnAfterCompact(deps);
|
|
1338
|
-
void deps.sendTextReply(
|
|
1357
|
+
void deps.sendTextReply(TELEGRAM_COMPACTION_COMPLETED_TEXT);
|
|
1339
1358
|
},
|
|
1340
1359
|
onError: (error) => {
|
|
1341
1360
|
deps.stopTypingLoop?.();
|
|
@@ -1358,7 +1377,7 @@ export async function handleTelegramCompactCommand(
|
|
|
1358
1377
|
}
|
|
1359
1378
|
if (!deps.suppressStartNotice) {
|
|
1360
1379
|
await deps.sendTextReply(
|
|
1361
|
-
|
|
1380
|
+
TELEGRAM_COMPACTION_STARTED_TEXT,
|
|
1362
1381
|
);
|
|
1363
1382
|
}
|
|
1364
1383
|
}
|
|
@@ -1631,11 +1650,13 @@ async function handleTelegramCommandRuntime<
|
|
|
1631
1650
|
): Promise<boolean> {
|
|
1632
1651
|
const assertExecutionCurrentFor = (nextMessage: TMessage) => (): void =>
|
|
1633
1652
|
deps.assertExecutionCurrent?.(nextMessage);
|
|
1634
|
-
const sendReplyFor =
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1653
|
+
const sendReplyFor =
|
|
1654
|
+
(nextMessage: TMessage) =>
|
|
1655
|
+
async (text: string, options?: { parseMode?: "HTML" }) => {
|
|
1656
|
+
deps.assertExecutionCurrent?.(nextMessage);
|
|
1657
|
+
await deps.sendTextReply(nextMessage, text, options);
|
|
1658
|
+
deps.assertExecutionCurrent?.(nextMessage);
|
|
1659
|
+
};
|
|
1639
1660
|
const updateStatusFor = (commandCtx: TContext) => () =>
|
|
1640
1661
|
deps.updateStatus(commandCtx);
|
|
1641
1662
|
return executeTelegramCommandAction(
|
package/lib/status.ts
CHANGED
|
@@ -1505,6 +1505,7 @@ function buildContextSummary(
|
|
|
1505
1505
|
}
|
|
1506
1506
|
|
|
1507
1507
|
function buildStatusSummary(ctx: TelegramStatusContext): string {
|
|
1508
|
+
if (ctx.isCompactionInProgress?.()) return "compacting";
|
|
1508
1509
|
if (ctx.hasPendingMessages?.()) return "pending";
|
|
1509
1510
|
if (ctx.isIdle?.() === false) return "active";
|
|
1510
1511
|
if (ctx.isIdle?.() === true) return "idle";
|
package/package.json
CHANGED
|
@@ -80,6 +80,8 @@ A surface normally contains:
|
|
|
80
80
|
|
|
81
81
|
Prefer 2–6 controls for feedback and decisions; navigation collections may use up to 12 when the additional entries remain scannable. Split larger sets by category or page instead of building a button wall. Do not add navigation controls when the surface is a one-step decision.
|
|
82
82
|
|
|
83
|
+
Present compact metadata as stacked key-value rows that reuse status-surface grammar: a short bold label, a colon, and an inline-code value when the value is path-like, numeric, an identifier, or machine state. Prefer ``**Path:** `/home/llb` `` and ``**Entries:** `1–10 of 52` `` on separate lines over prose fragments joined by a middle dot or other decorative section separator.
|
|
84
|
+
|
|
83
85
|
## Truth Modes
|
|
84
86
|
|
|
85
87
|
Name the basis of the surface when ambiguity matters:
|
|
@@ -146,8 +148,8 @@ Treat a user prompt that is exactly a plausible filesystem path—including `/`
|
|
|
146
148
|
|
|
147
149
|
1. Pin `⬆️ Up` as the first full-width row whenever the current path is not filesystem root; its entire prompt is the exact parent path. Omit Up at `/`.
|
|
148
150
|
2. When page traversal exists, place `⬅️ Previous` and `➡️ Next` together in one compact row immediately after Up, omitting either unavailable direction. Page traversal re-inspects the directory and preserves a fixed 10-entry page size; moving Up opens the parent at page one.
|
|
149
|
-
3. Render at most 10
|
|
150
|
-
4. Keep visible text to
|
|
151
|
+
3. Sort entries by semantic category before pagination: visible directories, hidden directories, visible files, then hidden files; sort names alphabetically within each category. Render at most 10 resulting entry buttons as full-width rows after structural navigation. Each label uses the entry name plus a semantic folder/file emoji, and its entire prompt may be the exact target path because this Skill defines path-only prompts as navigation intent.
|
|
152
|
+
4. Keep visible text to two compact status-style rows such as ``**Path:** `/home/llb` `` and ``**Entries:** `1–10 of 52` ``; do not join metadata with a middle dot and do not duplicate entry names as a plain or monospaced directory listing. Omit Refresh by default because resubmitting the current path already requests fresh rendering.
|
|
151
153
|
|
|
152
154
|
For pi-telegram, emit the complete filesystem control set—Up, compact page traversal, then current-page entries—in one `telegram_button` JSON matrix rather than repeating one hidden comment per button. If prompt buttons are unavailable or fail to render, preserve the same ordering and pagination as an ordinary numbered text fallback, not a monospaced inventory, so free-form path entry remains sufficient. Show a plain or monospaced directory listing instead only when the user explicitly requests it or durable user Knowledge establishes that presentation preference. Never preview credential stores, private keys, browser profiles, cookies, tokens, wallets, or other secret-bearing files, and never raise privileges merely to enumerate a path.
|
|
153
155
|
|