@llblab/pi-telegram 0.17.3 → 0.17.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/BACKLOG.md +5 -0
- package/CHANGELOG.md +9 -0
- package/docs/architecture.md +1 -1
- package/docs/public-api.md +1 -1
- package/lib/replies.ts +70 -2
- package/package.json +1 -1
- package/screenshot.png +0 -0
package/BACKLOG.md
CHANGED
|
@@ -5,6 +5,11 @@
|
|
|
5
5
|
- Done when: each confirmed sequence has a conservative normalization or safe-degradation rule covered by tests.
|
|
6
6
|
- Done when: unconfirmed speculative rewrites remain out of the delivery path.
|
|
7
7
|
|
|
8
|
+
- [ ] Evaluate Rich Draft placeholder heartbeat for long unsafe preview gaps.
|
|
9
|
+
- Done when: live/client evidence confirms whether `RichBlockThinking` / `<tg-thinking>` prevents Telegram draft expiry while assistant output is still streaming but no safe Markdown prefix is available.
|
|
10
|
+
- Done when: the behavior is compared against the existing typing keepalive lifecycle so Telegram chat action and Rich Draft placeholder states do not fight each other.
|
|
11
|
+
- Done when: abort/clear behavior is tested so placeholder drafts do not create the transient confusing block observed after abort.
|
|
12
|
+
|
|
8
13
|
- [ ] Track a public Pi session-replacement hook before adding Telegram `/new`.
|
|
9
14
|
- [ ] Wait for a safe public Pi API.
|
|
10
15
|
- Done when: Pi exposes an API that invokes the same session-replacement path as terminal `/new`.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.17.5: Screenshot Refresh
|
|
4
|
+
|
|
5
|
+
- `[Docs]` Refreshed the package screenshot.
|
|
6
|
+
|
|
7
|
+
## 0.17.4: Native Rich Markdown Splitter Hotfix
|
|
8
|
+
|
|
9
|
+
- `[Rich Markdown]` Rewrap oversized fenced code, display-math, and fully wrapped inline-formatting blocks when splitting native Rich Markdown at Telegram transport limits. Impact: very long structured Markdown blocks no longer produce invalid partial Rich Markdown chunks, so final assistant replies can stay native without losing long code/math/formatted output.
|
|
10
|
+
- `[Tests]` Added regressions for oversized fenced code, display math, and inline formatting split behavior. Impact: future native splitter changes must preserve structurally valid chunks beyond Telegram's single-message size limit.
|
|
11
|
+
|
|
3
12
|
## 0.17.3: Native Draft Preview Hotfix
|
|
4
13
|
|
|
5
14
|
- `[Rich Markdown]` Normalize multiline display-math blocks written as `$$` / content / `$$` into Telegram-supported `math` code fences before native Rich Markdown delivery, while preserving literal delimiters inside code fences. Impact: assistant replies following the Telegram prompt guidance for block formulas no longer risk making the whole Rich Markdown message render as raw Markdown.
|
package/docs/architecture.md
CHANGED
|
@@ -198,7 +198,7 @@ Assistant delivery guarantees:
|
|
|
198
198
|
- Model-authored Markdown is the source of truth; the bridge does not pre-render assistant Markdown to HTML.
|
|
199
199
|
- Before native Rich Markdown delivery, the bridge normalizes known Bot-API-fragile source forms without changing visible meaning, including space-after-marker blockquotes and dollar-prefixed ticker atoms that Telegram may otherwise treat as unterminated math.
|
|
200
200
|
- Quoted rich replies use Telegram `rich_message` blocks as the prompt-context source when available, so `[reply]` context receives rendered plain text instead of raw `InputRichMessage.markdown` fallback text.
|
|
201
|
-
- Long native Markdown replies are split only at Telegram Rich Message transport limits.
|
|
201
|
+
- Long native Markdown replies are split only at Telegram Rich Message transport limits; oversized fenced code, display-math, and fully wrapped inline-formatting blocks are rewrapped per chunk so persisted Rich Markdown chunks remain structurally valid.
|
|
202
202
|
- Streaming previews pass structurally closed assistant Markdown prefixes through to `sendRichMessageDraft` with ownership checks, voice suppression, and serialized flushes. Unclosed inline spans, links, fenced code, comments, and display-math blocks are held back until a safe boundary exists. Draft failures are recorded and the failing frame is skipped instead of degrading to raw plain-message previews, because partial Markdown can be invalid while the final message remains valid.
|
|
203
203
|
- Preview flushes are serialized so older edits cannot race newer drafts; final delivery waits for active draft flushes and does not perform a post-final draft-clear call.
|
|
204
204
|
|
package/docs/public-api.md
CHANGED
|
@@ -99,7 +99,7 @@ Hidden/default semantics are represented by absence:
|
|
|
99
99
|
- Voice Reply `hidden`: no `voice.replyMode` key is persisted.
|
|
100
100
|
- Time Injection `hidden`: no `time.injectionMode` key is persisted; if `time` becomes empty, the whole `time` object may be omitted.
|
|
101
101
|
|
|
102
|
-
Assistant Markdown delivery is native: final replies are sent as `InputRichMessage.markdown` via `sendRichMessage`, and draft previews use `sendRichMessageDraft` when a structurally closed preview frame is available. Draft-frame failures are recorded and skipped rather than converted into raw plain preview messages, because partial Markdown can be temporarily invalid while the final answer remains valid. Guest replies use `InputRichMessageContent` in `answerGuestQuery` results. Bridge-owned UI surfaces such as menus, status, queue controls, commands, and sections keep explicit Telegram HTML/plain rendering by default because those texts are authored by the bridge or companion extensions for Telegram UI. Companion extension sections may explicitly request `"markdown"`, `"html"`, or `"plain"` per view. There is no `telegram.json` rendering toggle for assistant delivery. The bridge sets `skip_entity_detection: true` for assistant and guest Markdown so technical text such as `/commands`, hashtags, URLs, phone numbers, and card-like numbers does not gain unintended automatic entities; explicit Markdown links still belong in the Markdown source.
|
|
102
|
+
Assistant Markdown delivery is native: final replies are sent as `InputRichMessage.markdown` via `sendRichMessage`, and draft previews use `sendRichMessageDraft` when a structurally closed preview frame is available. Draft-frame failures are recorded and skipped rather than converted into raw plain preview messages, because partial Markdown can be temporarily invalid while the final answer remains valid. Long native replies are split at Telegram Rich Message transport limits, with oversized fenced code, display-math, and fully wrapped inline-formatting blocks rewrapped per chunk so persisted chunks remain structurally valid. Guest replies use `InputRichMessageContent` in `answerGuestQuery` results. Bridge-owned UI surfaces such as menus, status, queue controls, commands, and sections keep explicit Telegram HTML/plain rendering by default because those texts are authored by the bridge or companion extensions for Telegram UI. Companion extension sections may explicitly request `"markdown"`, `"html"`, or `"plain"` per view. There is no `telegram.json` rendering toggle for assistant delivery. The bridge sets `skip_entity_detection: true` for assistant and guest Markdown so technical text such as `/commands`, hashtags, URLs, phone numbers, and card-like numbers does not gain unintended automatic entities; explicit Markdown links still belong in the Markdown source.
|
|
103
103
|
|
|
104
104
|
Environment variables are stable only where documented in the README: bot-token bootstrap, proxy behavior, agent root, and inbound/outbound file size limits.
|
|
105
105
|
|
package/lib/replies.ts
CHANGED
|
@@ -456,6 +456,12 @@ function countTelegramNativeMarkdownBlocks(block: string): number {
|
|
|
456
456
|
}
|
|
457
457
|
|
|
458
458
|
function splitTelegramNativeMarkdownLongBlock(block: string): string[] {
|
|
459
|
+
return splitTelegramNativeMarkdownLongFenceBlock(block) ??
|
|
460
|
+
splitTelegramNativeMarkdownLongWrappedInlineBlock(block) ??
|
|
461
|
+
splitTelegramNativeMarkdownLongPlainBlock(block);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function splitTelegramNativeMarkdownLongPlainBlock(block: string): string[] {
|
|
459
465
|
const chunks: string[] = [];
|
|
460
466
|
let remaining = block;
|
|
461
467
|
while (remaining.length > TELEGRAM_RICH_MESSAGE_MAX_CHARS) {
|
|
@@ -468,8 +474,70 @@ function splitTelegramNativeMarkdownLongBlock(block: string): string[] {
|
|
|
468
474
|
return chunks;
|
|
469
475
|
}
|
|
470
476
|
|
|
471
|
-
function
|
|
472
|
-
const
|
|
477
|
+
function splitTelegramNativeMarkdownLongFenceBlock(block: string): string[] | undefined {
|
|
478
|
+
const lines = block.split("\n");
|
|
479
|
+
const opening = lines[0] ?? "";
|
|
480
|
+
const closing = lines[lines.length - 1] ?? "";
|
|
481
|
+
const openingMatch = opening?.match(/^ {0,3}(`{3,}|~{3,})/);
|
|
482
|
+
if (!openingMatch || !closing || lines.length < 2) return undefined;
|
|
483
|
+
const markerText = openingMatch[1] ?? "```";
|
|
484
|
+
const marker = markerText[0] as "`" | "~";
|
|
485
|
+
if (!new RegExp(`^ {0,3}${marker}{${markerText.length},}\\s*$`).test(closing)) {
|
|
486
|
+
return undefined;
|
|
487
|
+
}
|
|
488
|
+
const maxContentLength = TELEGRAM_RICH_MESSAGE_MAX_CHARS -
|
|
489
|
+
opening.length -
|
|
490
|
+
closing.length -
|
|
491
|
+
2;
|
|
492
|
+
if (maxContentLength <= 0) return undefined;
|
|
493
|
+
const content = lines.slice(1, -1).join("\n");
|
|
494
|
+
return splitTelegramNativeMarkdownWrappedContent(
|
|
495
|
+
content,
|
|
496
|
+
maxContentLength,
|
|
497
|
+
(chunk) => `${opening}\n${chunk}${chunk.endsWith("\n") ? "" : "\n"}${closing}`,
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function splitTelegramNativeMarkdownLongWrappedInlineBlock(
|
|
502
|
+
block: string,
|
|
503
|
+
): string[] | undefined {
|
|
504
|
+
const delimiter = ["**", "__", "~~", "`", "*", "_"]
|
|
505
|
+
.find((candidate) =>
|
|
506
|
+
block.startsWith(candidate) &&
|
|
507
|
+
block.endsWith(candidate) &&
|
|
508
|
+
block.length > candidate.length * 2
|
|
509
|
+
);
|
|
510
|
+
if (!delimiter) return undefined;
|
|
511
|
+
const maxContentLength = TELEGRAM_RICH_MESSAGE_MAX_CHARS - delimiter.length * 2;
|
|
512
|
+
if (maxContentLength <= 0) return undefined;
|
|
513
|
+
return splitTelegramNativeMarkdownWrappedContent(
|
|
514
|
+
block.slice(delimiter.length, -delimiter.length),
|
|
515
|
+
maxContentLength,
|
|
516
|
+
(chunk) => `${delimiter}${chunk}${delimiter}`,
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function splitTelegramNativeMarkdownWrappedContent(
|
|
521
|
+
content: string,
|
|
522
|
+
maxContentLength: number,
|
|
523
|
+
wrap: (chunk: string) => string,
|
|
524
|
+
): string[] {
|
|
525
|
+
const chunks: string[] = [];
|
|
526
|
+
let remaining = content;
|
|
527
|
+
while (remaining.length > maxContentLength) {
|
|
528
|
+
const window = remaining.slice(0, maxContentLength + 1);
|
|
529
|
+
const splitIndex = findTelegramNativeMarkdownSplitIndex(window, maxContentLength);
|
|
530
|
+
chunks.push(wrap(remaining.slice(0, splitIndex)));
|
|
531
|
+
remaining = remaining.slice(splitIndex);
|
|
532
|
+
}
|
|
533
|
+
if (remaining.length > 0) chunks.push(wrap(remaining));
|
|
534
|
+
return chunks;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function findTelegramNativeMarkdownSplitIndex(
|
|
538
|
+
text: string,
|
|
539
|
+
hardLimit = TELEGRAM_RICH_MESSAGE_MAX_CHARS,
|
|
540
|
+
): number {
|
|
473
541
|
const paragraphIndex = text.lastIndexOf("\n\n", hardLimit);
|
|
474
542
|
if (paragraphIndex > 0) return paragraphIndex + 2;
|
|
475
543
|
const lineIndex = text.lastIndexOf("\n", hardLimit);
|
package/package.json
CHANGED
package/screenshot.png
CHANGED
|
Binary file
|