@llblab/pi-telegram 0.13.0 → 0.13.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/AGENTS.md CHANGED
@@ -60,7 +60,7 @@
60
60
  - Queue admission is explicit and validated: immediate commands, control lane, priority lane, and default lane must preserve allowed kind/lane pairings
61
61
  - Dispatch is gated by active turns, pending dispatch, unsettled control work, compaction, `ctx.isIdle()`, and π pending messages; dispatched prompts remain queued until `agent_start` consumes them
62
62
  - Telegram `/compact` owns a native `typing` keepalive for the compaction window so phone clients show activity between the started/completed notices; stop it on both completion and failure
63
- - `/stop`, `/abort`, `/next`, and `/continue` have distinct contracts: reset queue and abort; abort while preserving queue; force next queued turn; enqueue a priority `continue` prompt
63
+ - `/stop`, `/abort`, `/next`, and `/continue` have distinct contracts: reset queue and abort; abort while preserving queue; force next queued turn; enqueue a control-lane `continue` resume prompt without folding queued prompts into history
64
64
  - `/start`, `/help`, and `/status` open the unified command-help/status-row/control menu; `/model`, `/thinking`, and `/queue` jump to sections directly; visible bot commands are `/start`, `/compact`, `/next`, `/continue`, `/abort`, `/stop`
65
65
  - Command/menu emoji are fixed UI adornments owned by the `commands` map; do not add a persisted emoji toggle or Settings menu until there is a real setting to own
66
66
  - Telegram `reply_to_message` context is prompt-only and must not affect slash-command parsing
package/BACKLOG.md CHANGED
@@ -1,3 +1,28 @@
1
1
  # Project Backlog
2
2
 
3
- No open work.
3
+ ## Bound inbound handler output
4
+
5
+ `Task`: Bound inbound handler stdout, stderr, and recorded failure text before they enter prompts or runtime status.
6
+
7
+ `Why`: Large OCR, PDF, STT, or failing command output can inflate prompt context, memory use, and `/telegram-status`.
8
+
9
+ `Exit criteria`:
10
+
11
+ - Handler stdout added to `[outputs]` is truncated or externalized behind a bounded artifact reference.
12
+ - Handler stderr and stdout included in failure messages are bounded.
13
+ - Runtime event messages and details are bounded before storage and rendering.
14
+ - Regression tests cover large handler stdout and large failure output.
15
+
16
+ ## Recover from invalid config JSON
17
+
18
+ `Task`: Make `telegram.json` load failures recoverable without bricking pi-telegram session startup.
19
+
20
+ `Why`: A hand-edited or partially written invalid config currently bubbles `JSON.parse` failure through session start, which can block the normal repair path.
21
+
22
+ `Exit criteria`:
23
+
24
+ - Invalid config JSON is reported through a runtime event or clear status diagnostic.
25
+ - Session startup continues with safe empty config defaults.
26
+ - The invalid file is preserved or renamed for operator recovery.
27
+ - `/telegram-setup` remains usable after an invalid config is detected.
28
+ - Regression tests cover invalid config startup behavior.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,14 @@
1
1
  # Changelog
2
2
 
3
- ## 0.13.0: Command Template Standard, Voice Hardening, And Domain Cleanup (2026-05-22)
3
+ ## Unreleased
4
+
5
+ ## 0.13.1: Rendering, Typing, And Continue Queue Hotfix
6
+
7
+ - `[Rendering]` Fixed Telegram HTML rendering for Markdown bold/italic spans that cross soft line breaks, so assistant replies like `**first line\nsecond line**` render as bold text instead of showing raw asterisks. Added a regression for the guest-mode-style multiline bold reply shape.
8
+ - `[Typing Status]` Hardened assistant message activity hooks so transient preview/provider transport failures are recorded but do not break the native Telegram `typing` keepalive while an active turn continues.
9
+ - `[Continue Queue]` `/continue` now enqueues as a control-lane resume prompt and explicitly clears preserved-abort history mode, so queued Telegram prompts stay separate and the continuation runs ahead of queued prompt work after abort or compaction recovery.
10
+
11
+ ## 0.13.0: Command Template Standard, Voice Hardening, And Domain Cleanup
4
12
 
5
13
  - `[Architecture]` Extracted outbound assistant-action markup parsing into `lib/outbound-markup.ts` and removed the temporary Voice/Outbound/Queue import-cycle allowance. Impact: project source imports are fully acyclic again while preserving existing voice and outbound helper exports.
6
14
  - `[Tests]` Updated the Pi SDK centralization invariant to guard the current `@earendil-works/*` package scope as well as the legacy scope. Impact: new direct SDK imports outside `lib/pi.ts` are caught again.
package/lib/bindings.ts CHANGED
@@ -279,6 +279,7 @@ export function registerTelegramLifecycleRuntimeHooks({
279
279
  startTypingLoop: promptDispatchRuntime.startTypingLoop,
280
280
  onMessageStart: previewRuntime.onMessageStart,
281
281
  onMessageUpdate: previewRuntime.onMessageUpdate,
282
+ recordRuntimeEvent,
282
283
  });
283
284
  Lifecycle.registerTelegramLifecycleHooks(pi, {
284
285
  ...sessionLifecycleRuntime,
package/lib/lifecycle.ts CHANGED
@@ -182,6 +182,11 @@ export interface TelegramMessageActivityTypingDeps<TContext> {
182
182
  startTypingLoop: (ctx: TContext) => void;
183
183
  onMessageStart: TelegramLifecycleRegistrationDeps["onMessageStart"];
184
184
  onMessageUpdate: TelegramLifecycleRegistrationDeps["onMessageUpdate"];
185
+ recordRuntimeEvent?: (
186
+ category: string,
187
+ error: unknown,
188
+ details?: Record<string, unknown>,
189
+ ) => void;
185
190
  }
186
191
 
187
192
  export function createTelegramMessageActivityTypingHooks<
@@ -195,15 +200,27 @@ export function createTelegramMessageActivityTypingHooks<
195
200
  const ensureTyping = (ctx: TContext): void => {
196
201
  if (deps.hasActiveTurn()) deps.startTypingLoop(ctx);
197
202
  };
203
+ const handleMessageActivity = async (
204
+ phase: "start" | "update",
205
+ event: Parameters<TelegramLifecycleRegistrationDeps["onMessageStart"]>[0],
206
+ ctx: ExtensionContext,
207
+ inner: TelegramLifecycleRegistrationDeps["onMessageStart"],
208
+ ): Promise<void> => {
209
+ const typedCtx = ctx as TContext;
210
+ ensureTyping(typedCtx);
211
+ try {
212
+ await inner(event, ctx);
213
+ } catch (error) {
214
+ deps.recordRuntimeEvent?.("message-activity", error, { phase });
215
+ } finally {
216
+ ensureTyping(typedCtx);
217
+ }
218
+ };
198
219
  return {
199
- onMessageStart: async (event, ctx) => {
200
- ensureTyping(ctx as TContext);
201
- await deps.onMessageStart(event, ctx);
202
- },
203
- onMessageUpdate: async (event, ctx) => {
204
- ensureTyping(ctx as TContext);
205
- await deps.onMessageUpdate(event, ctx);
206
- },
220
+ onMessageStart: (event, ctx) =>
221
+ handleMessageActivity("start", event, ctx, deps.onMessageStart),
222
+ onMessageUpdate: (event, ctx) =>
223
+ handleMessageActivity("update", event, ctx, deps.onMessageUpdate),
207
224
  };
208
225
  }
209
226
 
package/lib/preview.ts CHANGED
@@ -345,6 +345,7 @@ export function createTelegramPreviewControllerRuntime<
345
345
  maxDraftId: deps.maxDraftId,
346
346
  setTimer: deps.setTimer,
347
347
  clearTimer: deps.clearTimer,
348
+ recordRuntimeEvent: deps.recordRuntimeEvent,
348
349
  });
349
350
  }
350
351
 
package/lib/rendering.ts CHANGED
@@ -843,7 +843,7 @@ function renderDelimitedInlineStyle(
843
843
  ): string {
844
844
  const escapedDelimiter = delimiter.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
845
845
  const pattern = new RegExp(
846
- `(^|[^\\p{L}\\p{N}\\\\])(${escapedDelimiter})(?=\\S)(.+?)(?<=\\S)\\2(?=[^\\p{L}\\p{N}]|$)`,
846
+ `(^|[^\\p{L}\\p{N}\\\\])(${escapedDelimiter})(?=\\S)([\\s\\S]+?)(?<=\\S)\\2(?=[^\\p{L}\\p{N}]|$)`,
847
847
  "gu",
848
848
  );
849
849
  return text.replace(
@@ -1004,9 +1004,28 @@ function renderMarkdownTextPiece(piece: string): string {
1004
1004
  return renderInlineMarkdown(piece);
1005
1005
  }
1006
1006
 
1007
+ function isPlainInlineMarkdownLine(line: string): boolean {
1008
+ if (line.trim().length === 0) return false;
1009
+ return (
1010
+ matchMarkdownHeadingLine(line) === null &&
1011
+ !/^(\s*)([-*+]|\d+\.)\s+\[([ xX])\]\s+(.+)$/.test(line) &&
1012
+ !/^(\s*)[-*+]\s+(.+)$/.test(line) &&
1013
+ !/^(\s*)(\d+)\.\s+(.+)$/.test(line) &&
1014
+ !/^>\s?(.+)$/.test(line) &&
1015
+ !/^([-*_]\s*){3,}$/.test(line.trim())
1016
+ );
1017
+ }
1018
+
1007
1019
  function renderMarkdownTextLines(block: string): string[] {
1008
1020
  const rendered: string[] = [];
1009
1021
  const lines = block.split("\n");
1022
+ const nonBlankLines = lines.filter((line) => line.trim().length > 0);
1023
+ if (
1024
+ nonBlankLines.length > 1 &&
1025
+ nonBlankLines.every(isPlainInlineMarkdownLine)
1026
+ ) {
1027
+ return renderInlineMarkdown(nonBlankLines.join("\n")).split("\n");
1028
+ }
1010
1029
  for (const line of lines) {
1011
1030
  if (line.trim().length === 0) continue;
1012
1031
  for (const piece of splitPlainMarkdownLine(line)) {
package/lib/routing.ts CHANGED
@@ -351,8 +351,8 @@ export function createTelegramInboundRouteRuntime<
351
351
  const turn = await promptTurnBuilder([continueMessage], [], ctx);
352
352
  const continueTurn = {
353
353
  ...turn,
354
- queueLane: "priority" as const,
355
- laneOrder: Number.MIN_SAFE_INTEGER + turn.queueOrder,
354
+ queueLane: "control" as const,
355
+ laneOrder: deps.bridgeRuntime.queue.allocateControlOrder(),
356
356
  statusSummary: "continue",
357
357
  };
358
358
  deps.queueMutationRuntime.append(continueTurn, ctx);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.13.0",
3
+ "version": "0.13.1",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"