@llblab/pi-telegram 0.56.1 → 0.56.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/AGENTS.md CHANGED
@@ -99,9 +99,9 @@ Use the relevant local skill before non-trivial work in its domain. Keep skill o
99
99
 
100
100
  ### 4.4 Queue, Delivery, And User Surfaces
101
101
 
102
- - Queue lane/kind admission is explicit. Ordinary new-task dispatch waits for active-turn, pending-dispatch, control, compaction, `ctx.isIdle()`, and Pi pending-message guards; a dispatched prompt stays queued until `agent_start` consumes it. The terminal shows one warning-yellow `+N` suffix for executable waiting work across all lanes (Normal/Priority/Steer/Control), omitted when zero. It excludes the dispatched head, frozen attempts and Skip, and never counts current agent work from any source. Each prompt is one object with one active lane and no reserved return slot. Control/Steer/Priority/Normal have explicit ranks. For ordinary prompts, crossing lanes removes it from the source and appends it at the destination tail; Keep/Skip and unchanged effective intent preserve lane position. Complete reaction sets derive Steer from fire, otherwise Priority from thumbs-up/heart/dove, and independent Skip from negative emoji. Fire dominates weaker positives; lightning is a Control marker, never a command classifier or Priority alias. Decorative changes cannot reset a menu lane choice; positive intent and Skip may coexist, suppressed turns retain durable receipts while waiting, and Skip settles them only when the prompt reaches dispatch before dropping it without inference. Suppressed turns remain visible at a struck-through physical ordinal without contributing to executable queue counters, while graceful session shutdown discards all remaining queue authority before clearing memory.
102
+ - Queue lane/kind admission is explicit. Ordinary new-task dispatch waits for active-turn, pending-dispatch, control, compaction, `ctx.isIdle()`, and Pi pending-message guards; a dispatched prompt stays queued until `agent_start` consumes it. The terminal shows one warning-yellow `+N` suffix for executable waiting work across all lanes (Normal/Priority/Steer/Control), omitted when zero. It excludes the dispatched head, frozen attempts and Skip, and never counts current agent work from any source. Each prompt is one object with one active lane and no reserved return slot. Control/Steer/Priority/Normal have explicit ranks. For ordinary prompts, crossing lanes removes it from the source and appends it at the destination tail; Keep/Skip and unchanged effective intent preserve lane position. Only fire, thumbs-up and thumbs-down have queue reaction semantics. Complete reaction sets derive Steer from fire, otherwise Priority from thumbs-up, and independent Skip from thumbs-down. All other reactions are decorative and cannot flush or mutate queued work. Fire dominates thumbs-up; lightning is a Control marker, never a command classifier or Priority alias. Decorative changes cannot reset a menu lane choice; positive intent and Skip may coexist, suppressed turns retain durable receipts while waiting, and Skip settles them only when the prompt reaches dispatch before dropping it without inference. Suppressed turns remain visible at a struck-through physical ordinal without contributing to executable queue counters, while graceful session shutdown discards all remaining queue authority before clearing memory.
103
103
  - Experimental Steer stays in the existing Queue/admission owner until one protected whole-packet attempt. Active insertion uses public `turn_end` after one response/full tool batch without resetting current Telegram reply/preview ownership; idle uses ordinary `sendUserMessage`. Recheck exact context/generation/source/native/compaction guards, freeze original objects before whole-receipt journal disposition, and never treat it as SDK/model-use proof. Partial/refused/unknown attempts remain non-executable and immutable without replay. Idle originals stay behind one pinned pending head until `agent_start` projects one active reply/voice/attachment owner. Keep identity fencing separate from same-session readiness refusal so old failures cannot clear replacement flags and unissued packets cannot contaminate native agent starts. Queue transfer allows unspent Steer only with `queue-steer-v1` plus baseline handoff support at donor/leader/recipient; missing capability refuses before offer, and frozen/pending work never transfers as unspent input.
104
- - `/stop`, `/abort`, `/next`, and `/continue` respectively reset+abort, abort while preserving queue, force the next turn, and enqueue a control-lane continuation. A negative reaction to an original standalone `/continue` settles and removes only its exact waiting source-addressed continuation; positive reactions do not change its control lane. Protect the Pi-owned pending head and already-started work, preserve other queue positions, and never address synthetic model continuations by their reply anchor. Settlement failure retains the continuation. Abort-history folding applies only to Telegram-owned active turns. A busy `/next` marks only its exact active Telegram turn: terminal abort settlement attempts one explicit abort notice before the exact selected queued prompt receives its dispatch notice. Notice failure is diagnostic and cannot block dispatch; a later `/abort` or `/stop` cancels both pending transition notices before taking ownership. Target command adapters must forward these transition ports. A successful dispatch notice keeps the queued prompt's one reply-header claim across agent start, so later messages in that turn never repeat it.
104
+ - `/stop`, `/abort`, `/next`, and `/continue` respectively reset+abort, abort while preserving queue, force the next turn, and enqueue a control-lane continuation. A thumbs-down reaction to an original standalone `/continue` settles and removes only its exact waiting source-addressed continuation; positive reactions do not change its control lane. Protect the Pi-owned pending head and already-started work, preserve other queue positions, and never address synthetic model continuations by their reply anchor. Settlement failure retains the continuation. Abort-history folding applies only to Telegram-owned active turns. A busy `/next` marks only its exact active Telegram turn: terminal abort settlement attempts one explicit abort notice before the exact selected queued prompt receives its dispatch notice. Notice failure is diagnostic and cannot block dispatch; a later `/abort` or `/stop` cancels both pending transition notices before taking ownership. Target command adapters must forward these transition ports. A successful dispatch notice keeps the queued prompt's one reply-header claim across agent start, so later messages in that turn never repeat it.
105
105
  - A model selection from an authorized Telegram target may stop and continue any interruptible active agent run in that same Pi session, including local/TUI work. Queue the synthetic control-lane continuation before aborting; when no Telegram prompt owns the run, bind continuation and reply ownership to the exact model-menu chat/Thread/message. Delay abort until every active tool execution settles, and clear both selection and fallback target on cancellation, agent start, settlement, or session replacement.
106
106
  - Telegram extension side effects must not hold Pi's core lifecycle hostage after semantic completion. Preserve ordering in extension-owned background work, record failures, and fence target/profile/transport/session authority.
107
107
  - In Threaded Mode, All is service/input-only. New output, previews, typing and routing UI require a concrete Thread; neither implicit fallback nor explicit aggregate delivery may write to All. Unavailable routing authority retains input without an All notice or silent settlement. Classic DM and explicit channel/business destinations remain separate; historical All output is not auto-cleaned.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,11 @@
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.56.2: Canonical Queue Reactions
8
+
9
+ - `Queue reactions`: Only thumbs-up selects Priority, thumbs-down selects Skip, and fire selects Steer. Heart, dove, ghost, broken-heart, poop and wastebasket aliases are no longer controls. Unsupported reactions neither flush waiting input nor change queue intent; removing a supported reaction does not fall back to a retired alias. Only thumbs-down cancels a waiting standalone Continue.
10
+ - `Queue display`: Priority and Skip badges use thumbs-up and thumbs-down even for retained older metadata. Existing lane, disposition, source and receipt authority remain unchanged; Control keeps its lightning label, not a reaction shortcut.
11
+
7
12
  ## 0.56.1: Recover interrupted connections
8
13
 
9
14
  - `Connection recovery`: Explicit `/telegram-connect` can abandon matching unknown Thread-creation attempts and connect afresh without deleting tabs or clearing accepted work. Known pending targets keep their Workspace key and slot; unique creation IDs prevent late replies from attaching to a new attempt. Follower recovery requires `explicit-connect-recovery-v1` on both peers. A lost API reply may leave an extra tab; automatic startup and Restore-only registration never reset it.
package/README.md CHANGED
@@ -208,7 +208,7 @@ In Threaded Mode, `All` is input-only: messages enter temporary routing tabs, an
208
208
 
209
209
  Messages sent while Pi is busy become queued turns. Queue controls let you inspect, prioritize, keep or skip, and dispatch work without touching the terminal.
210
210
 
211
- Use reactions or the queue menu to control waiting prompts.
211
+ Use reactions or the queue menu to control waiting prompts. Only `👍`, `👎`, and `🔥` have queue semantics; all other reactions are ignored.
212
212
 
213
213
  Priority — when the prompt runs:
214
214
  - **Normal — no dedicated reaction emoji.** The default lane for ordinary queued work.
@@ -227,7 +227,7 @@ Queue policy:
227
227
  - One yellow terminal counter shows waiting work across all lanes, excluding current or already-dispatched work, Skip and frozen attempts.
228
228
  - Dispatched or frozen prompts are inspection-only. An uncertain Steer attempt is not automatically retried.
229
229
  - The bot cannot remove your Telegram reactions; changing a menu choice may leave the original reaction visible.
230
- - A negative reaction to your waiting standalone `/continue` cancels it immediately rather than marking it for reversible Skip. Already-running work is unaffected.
230
+ - A `👎` reaction to your waiting standalone `/continue` cancels it immediately rather than marking it for reversible Skip. Already-running work is unaffected.
231
231
  - The queue is session-local. Let it drain before reload or shutdown, or accept losing waiting work.
232
232
 
233
233
  See [Queue Behavior and Safety](./docs/architecture.md#priority-reactions-keep-and-skip) for reaction precedence, dispatch rules and recovery details.
@@ -71,7 +71,7 @@ function buildTelegramQueueMenuReplyMarkup(items, emptyRefreshIndex = 0) {
71
71
  return { inline_keyboard: [backRow, refreshRow] };
72
72
  const rows = items.map((item) => {
73
73
  const badge = getTelegramQueueMenuLaneBadge(item);
74
- const prefix = item.reactionSuppressionEmoji ? `${item.reactionSuppressionEmoji} ` :
74
+ const prefix = item.reactionSuppressionEmoji ? "👎 " :
75
75
  badge ? `${badge} ` : item.hasAttachments ? "📎 " : "";
76
76
  const position = item.reactionSuppressionEmoji
77
77
  ? formatSkippedTelegramQueuePosition(item.queuePosition)
@@ -130,11 +130,10 @@ function getTelegramQueueMenuLaneBadge(item) {
130
130
  return "🔥";
131
131
  if (item.queueLane !== "priority")
132
132
  return "";
133
- const emoji = item.priorityEmoji?.replace(/\uFE0F/g, "");
134
- return emoji === "❤" || emoji === "🕊" ? emoji : "👍";
133
+ return "👍";
135
134
  }
136
135
  function getTelegramQueueMenuItemText(item) {
137
- const marker = item.reactionSuppressionEmoji ?? getTelegramQueueMenuLaneBadge(item);
136
+ const marker = item.reactionSuppressionEmoji ? "👎" : getTelegramQueueMenuLaneBadge(item);
138
137
  const badge = marker ? ` ${marker}` : "";
139
138
  const position = item.reactionSuppressionEmoji
140
139
  ? `<s>${item.queuePosition}</s>.`
@@ -441,11 +440,10 @@ function applyQueuedTelegramPromptMenuFlags(chatId, replyToMessageId, ctx, deps,
441
440
  const disposition = { kind: "reaction-transition" };
442
441
  if (flags.lane !== undefined) {
443
442
  disposition.promptLane = flags.lane;
444
- disposition.priorityEmoji = flags.lane === "steer" ? "🔥" : flags.lane === "priority" ?
445
- getTelegramQueueMenuLaneBadge({ queueLane: "priority", priorityEmoji: item.queueLane === "priority" ? item.priorityEmoji : undefined }) : null;
443
+ disposition.priorityEmoji = flags.lane === "steer" ? "🔥" : flags.lane === "priority" ? "👍" : null;
446
444
  }
447
445
  if (flags.skipped !== undefined)
448
- disposition.suppressionEmoji = flags.skipped ? (item.reactionSuppressionEmoji ?? "👎") : null;
446
+ disposition.suppressionEmoji = flags.skipped ? "👎" : null;
449
447
  return deps.queueMutationRuntime.applyReactionByMessageId(replyToMessageId, disposition, ctx) ||
450
448
  ((flags.lane === undefined || flags.lane === item.queueLane) &&
451
449
  (flags.skipped === undefined || flags.skipped === (item.reactionSuppressionEmoji !== undefined)));
@@ -22,14 +22,6 @@ export declare const TELEGRAM_PRIORITY_REACTIONS: readonly [{
22
22
  readonly id: 10;
23
23
  readonly name: "like";
24
24
  readonly emoji: "👍";
25
- }, {
26
- readonly id: 12;
27
- readonly name: "heart";
28
- readonly emoji: "❤";
29
- }, {
30
- readonly id: 13;
31
- readonly name: "dove";
32
- readonly emoji: "🕊";
33
25
  }];
34
26
  export declare const TELEGRAM_STEER_REACTIONS: readonly [{
35
27
  readonly id: 14;
@@ -40,25 +32,9 @@ export declare const TELEGRAM_REMOVAL_REACTIONS: readonly [{
40
32
  readonly id: 20;
41
33
  readonly name: "dislike";
42
34
  readonly emoji: "👎";
43
- }, {
44
- readonly id: 21;
45
- readonly name: "ghost";
46
- readonly emoji: "👻";
47
- }, {
48
- readonly id: 22;
49
- readonly name: "broken-heart";
50
- readonly emoji: "💔";
51
- }, {
52
- readonly id: 23;
53
- readonly name: "poop";
54
- readonly emoji: "💩";
55
- }, {
56
- readonly id: 24;
57
- readonly name: "wastebasket";
58
- readonly emoji: "🗑";
59
35
  }];
60
- export declare const TELEGRAM_PRIORITY_REACTION_EMOJIS: ("👍" | "❤" | "🕊")[];
61
- export declare const TELEGRAM_REMOVAL_REACTION_EMOJIS: ("👎" | "👻" | "💔" | "💩" | "🗑")[];
36
+ export declare const TELEGRAM_PRIORITY_REACTION_EMOJIS: "👍"[];
37
+ export declare const TELEGRAM_REMOVAL_REACTION_EMOJIS: "👎"[];
62
38
  export interface TelegramUpdateDeletion {
63
39
  deleted_business_messages?: {
64
40
  message_ids?: unknown;
@@ -14,16 +14,10 @@ import { createTelegramPrivateTarget, createTelegramThreadTarget, } from "./targ
14
14
  import { isWireRecord as isTelegramUpdateAdmissionRecord, isNonEmptyWireString as isTelegramUpdateAdmissionString, } from "./wire.js";
15
15
  export const TELEGRAM_PRIORITY_REACTIONS = [
16
16
  { id: 10, name: "like", emoji: "👍" },
17
- { id: 12, name: "heart", emoji: "❤" },
18
- { id: 13, name: "dove", emoji: "🕊" },
19
17
  ];
20
18
  export const TELEGRAM_STEER_REACTIONS = [{ id: 14, name: "fire", emoji: "🔥" }];
21
19
  export const TELEGRAM_REMOVAL_REACTIONS = [
22
20
  { id: 20, name: "dislike", emoji: "👎" },
23
- { id: 21, name: "ghost", emoji: "👻" },
24
- { id: 22, name: "broken-heart", emoji: "💔" },
25
- { id: 23, name: "poop", emoji: "💩" },
26
- { id: 24, name: "wastebasket", emoji: "🗑" },
27
21
  ];
28
22
  export const TELEGRAM_PRIORITY_REACTION_EMOJIS = TELEGRAM_PRIORITY_REACTIONS.map((reaction) => reaction.emoji);
29
23
  export const TELEGRAM_REMOVAL_REACTION_EMOJIS = TELEGRAM_REMOVAL_REACTIONS.map((reaction) => reaction.emoji);
@@ -46,7 +40,7 @@ function getTelegramReactionEmoji(emojis, candidates) {
46
40
  function getTelegramQueueReactionTransition(oldReactions, newReactions) {
47
41
  const oldEmojis = collectTelegramReactionEmojis(oldReactions);
48
42
  const newEmojis = collectTelegramReactionEmojis(newReactions);
49
- // Fire is effective Steer intent; weaker positive aliases under it do not reorder the lane.
43
+ // Fire selects Steer; thumbs-up beneath it does not reorder the lane. Other emoji carry no queue intent.
50
44
  const steerEmoji = TELEGRAM_STEER_REACTIONS[0].emoji;
51
45
  const oldPriorityEmoji = oldEmojis.has(steerEmoji) ? steerEmoji : getTelegramReactionEmoji(oldEmojis, TELEGRAM_PRIORITY_REACTION_EMOJIS);
52
46
  const newPriorityEmoji = newEmojis.has(steerEmoji) ? steerEmoji : getTelegramReactionEmoji(newEmojis, TELEGRAM_PRIORITY_REACTION_EMOJIS);
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.56.1",
3
+ "version": "0.56.2",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -610,7 +610,7 @@ Bridge-owned Steer uses the existing public Pi SDK. Busy-run operator smoke cove
610
610
 
611
611
  A prompt is one queue object with exactly one active lane membership and one current position. It has no duplicate, shadow entry, or reserved return slot in the other lane. Each prompt admitted directly to a lane joins that lane's tail. A `Normal → Priority` transition removes it from Normal and appends that same object to the Priority tail; a later `Priority → Normal` transition removes it from Priority and appends it to the current Normal tail rather than restoring any historical position. The immutable `queueOrder` records original admission identity only and is never a return address; `laneOrder` records the current destination-lane position. Keep/Skip changes and emoji changes within the same reaction category preserve both lane and lane position exactly.
612
612
 
613
- Telegram reactions are shortcut controls over those dimensions. Fire selects Steer and dominates other positives; thumbs-up/heart/dove select Priority and recognized negatives control Skip. Lightning is now a Control marker only, not a reaction shortcut; the emoji sets live in the [queue reaction registry](./ui-style.md#queue-reaction-shortcuts). The runtime compares effective intent from complete old and new reaction sets and mutates only dimensions that changed. Weaker positives under fire and decorative changes cannot reset a menu lane choice; so adding or removing a negative reaction cannot silently change Priority, and changing a positive reaction cannot silently change Skip. The registry order selects the retained display emoji when several recognized emoji from one category coexist; it does not let one category override the other.
613
+ Telegram reactions are shortcut controls over those dimensions. Only fire selects Steer, thumbs-up selects Priority and thumbs-down selects Skip. Fire dominates thumbs-up; all other reactions are ignored, including former heart/dove and removal aliases. Lightning is now a Control marker only, not a reaction shortcut; the emoji sets live in the [queue reaction registry](./ui-style.md#queue-reaction-shortcuts). The runtime compares effective intent from complete old and new reaction sets and mutates only dimensions that changed. Weaker positives under fire and decorative changes cannot reset a menu lane choice; so adding or removing a negative reaction cannot silently change Priority, and changing a positive reaction cannot silently change Skip. Ignored emoji cannot retain a lane or Skip when its supported reaction is removed. Existing explicit queue state and receipts are not rewritten during upgrade; menu badges project that state as thumbs-up, fire or thumbs-down rather than reviving retired aliases.
614
614
 
615
615
  Steer or Priority and Skip may coexist when a prompt carries both positive and negative reactions. The prompt remains at its current lane position while waiting, and its durable journal receipts remain intact so Keep stays reversible and exact live handoff remains possible. Skip wins when dispatch reaches the prompt: the dispatcher first settles those receipts durably, then drops it without a model turn and continues; settlement failure retains the skipped head instead of allowing replay ambiguity. The prompt stays visible with only its negative emoji, is excluded immediately from the executable queue count shared by the Pi status bar and Telegram main menu, and keeps a struck-through physical ordinal. Returning it to Keep restores its contribution to the count without moving it. Graceful session shutdown discards all remaining queue receipts before clearing session-local memory, and startup discards receipts only after proving their former process dead, so a new or unrelated session never inherits queued work. Queue item detail exposes horizontal Normal/Priority/Steer and Keep/Skip selectors instead of irreversible Delete. Legacy published Priority callbacks retain explicit lane intent. Dispatched/frozen items expose inspection only; an oversized idle Steer item stays editable with a Priority/Normal escape and never splits its source group.
616
616
 
@@ -618,7 +618,7 @@ Menu and reaction controls share the same canonical queue state. A menu Keep can
618
618
 
619
619
  Guest Mode is the deliberate exception to deferred Keep/Skip semantics. A guest prompt has no source message reaction surface and may share zero-valued chat/message placeholders with other guest prompts, so queue-menu callbacks address it by immutable `queueOrder` and expose an immediate `Skip`. The leader settles its durable admission, removes it while other agent work may continue, stops placeholder animation, and replaces the inline globe placeholder with non-printing content. Bot API inline messages have no delete method, so this visual clear is the supported deletion equivalent. A successfully skipped waiting guest prompt never reaches a model turn. Once dispatched, its retained head is inspection-only until `agent_start` consumes that exact reply owner; stale Guest Skip callbacks refuse without receipt disposal or placeholder dismissal. Later waiting guests remain independently skippable.
620
620
 
621
- A standalone `/continue` is a source-addressed control-lane prompt, not a Normal/Priority prompt. An authorized negative reaction to its original chat/message immediately settles the exact waiting continuation's receipts before removing it, even while unrelated work is active. Failed or unknown settlement retains it for the existing reaction retry path; duplicate reactions cannot settle twice. Positive reactions and removal of the negative reaction neither change its control lane nor reconstruct cancelled work. The dispatched head awaiting `agent_start` and already-started work are never cancelled; appends and reorders preserve that Pi-owned head. Synthetic model-switch continuations have no source message IDs and are not reaction-addressable by their fallback reply anchor. Reaction authorization and chat/message namespace isolation remain unchanged.
621
+ A standalone `/continue` is a source-addressed control-lane prompt, not a Normal/Priority prompt. An authorized thumbs-down reaction to its original chat/message immediately settles the exact waiting continuation's receipts before removing it, even while unrelated work is active. Failed or unknown settlement retains it for the existing reaction retry path; duplicate reactions cannot settle twice. Positive reactions and removal of the negative reaction neither change its control lane nor reconstruct cancelled work. The dispatched head awaiting `agent_start` and already-started work are never cancelled; appends and reorders preserve that Pi-owned head. Synthetic model-switch continuations have no source message IDs and are not reaction-addressable by their fallback reply anchor. Reaction authorization and chat/message namespace isolation remain unchanged.
622
622
 
623
623
  Admission and planning validate lane contracts. Invalid lane/kind pairings fail predictably instead of being silently coerced.
624
624
 
@@ -468,8 +468,8 @@ Threaded Mode should make follower threads behave like normal Telegram instance
468
468
  | Surface | Leader behavior | Follower requirement | Routing/ownership invariant | Regression evidence |
469
469
  | --- | --- | --- | --- | --- |
470
470
  | Prompt intake | Thread prompt queues locally | Thread prompt is forwarded and queued by the owning follower | Target ownership routes by `{ chatId, threadId }` before local handling | Routing tests for foreign target message forwarding |
471
- | Queued-message removal reactions | 👎/👻/💔/💩/🗑 marks a pending prompt/media turn for deletion when it reaches dispatch | Same reaction on a queued follower prompt marks that follower's pending turn for deletion before model dispatch | When the leader forwards a prompt to a follower, it records `chatId/messageId -> follower instance` because Bot API reaction updates expose chat/message but not thread id | Update runtime regression records forwarded message ownership and forwards the later reaction |
472
- | Queue priority reactions | 👍/⚡/❤/🕊/🔥 prioritizes queued prompts | Same reactions prioritize follower queued prompts | Reaction forwarding uses stored message ownership, then follower mutates its local queue | Reaction mutation tests plus forwarded-reaction coverage |
471
+ | Queued-message removal reactions | Only 👎 selects Skip for a pending prompt/media turn; disposition occurs at dispatch | Same reaction on a queued follower prompt marks that follower's pending turn for deletion before model dispatch | When the leader forwards a prompt to a follower, it records `chatId/messageId -> follower instance` because Bot API reaction updates expose chat/message but not thread id | Update runtime regression records forwarded message ownership and forwards the later reaction |
472
+ | Queue priority reactions | 👍 selects Priority; 🔥 selects Steer; all other positive reactions are decorative | Same reactions prioritize follower queued prompts | Reaction forwarding uses stored message ownership, then follower mutates its local queue | Reaction mutation tests plus forwarded-reaction coverage |
473
473
  | Message edits | Edits update matching queued prompt text | Edits in a follower thread update that follower's queued prompt | Message target ownership forwards edits to the owning instance; stored message ownership is the fallback when Telegram edit payloads omit thread id | Update routing tests for foreign target and message-owned edited-message forwarding |
474
474
  | Callbacks/buttons/menus | Callback handled by the owning instance/menu state | Follower callbacks are forwarded to the owning follower; follower menu sends/edits/deletes route through leader transport | Leader records ownership for follower-sent Bot API messages so callbacks can route by message id even when Telegram omits thread id; Bot API edit/delete lacks thread id, so follower bus allows validated same-chat message operations | Callback forwarding, generated-button target, bus follower-sent ownership, and bus edit/delete allowlist tests |
475
475
  | Replies/finals | Final replies land in the same thread | Follower finals go through leader transport into follower thread | Outbound calls carry target and inject `message_thread_id` | Reply delivery and bus API tests |
package/docs/ui-style.md CHANGED
@@ -56,7 +56,7 @@ Use emoji as stable semantic markers, not decoration. Emoji carry transportable
56
56
  | `☑️` | Activate / choose this item | Model detail activation action, generated button-only choice heading | Positive selection cue; use `🟢 Active` for already-current state. |
57
57
  | `⛔️` | Cancel source routing | `⛔️ Cancel routing` chooser action and the `Routing cancelled` toast | Not an abort of active Pi work. Private retention remains mandatory; disposable-tab removal stays bound to exact eligible target authority and follows only once every input in the tab is resolved. |
58
58
  | `❌` | No / cancel / terminal failure | Confirmation cancel buttons and terminal failure notices | Do not use for a recoverable operation failure that leaves session state intact. |
59
- | `🗑` | Delete / defer removal | Destructive confirmations and removal reaction | In the queue menu, reversible Keep/Skip selectors replace immediate deletion. |
59
+ | `🗑` | Delete | Destructive confirmations | Not a queue reaction shortcut; queue menus use reversible Keep/Skip selectors. |
60
60
 
61
61
  ### Informational Feedback
62
62
 
@@ -96,14 +96,10 @@ The menu may clear internal Skip but cannot remove a reaction created by the use
96
96
  | --- | --- | --- | --- |
97
97
  | `👍` | Promote to priority | Queue reaction shortcut | Normalized from variants like `👍️`. |
98
98
  | `⚡` | Producer-owned Control marker | Control queue rows | Not a positive-reaction alias or command classifier; old stored Priority badges project `👍` without changing lane authority. |
99
- | `❤` / `❤️` | Promote to priority | Queue reaction shortcut | Normalize display consistently where code normalizes reactions. |
100
- | `🕊` / `🕊️` | Promote to priority | Queue reaction shortcut | Soft/peaceful promotion gesture. |
101
- | `🔥` | Select Steer | Queue reaction shortcut and Steer badge | Dominates `👍`, heart and dove; not immediate issuance or model-use proof. |
99
+ | `🔥` | Select Steer | Queue reaction shortcut and Steer badge | Dominates `👍`; not immediate issuance or model-use proof. |
102
100
  | `👎` | Defer removal of waiting turn | Queue reaction shortcut and queue emoji marker | Reversible until the marked turn reaches dispatch; not negative feedback to the agent. |
103
- | `👻` | Defer removal of waiting turn | Queue reaction shortcut and queue emoji marker | Disappear/remove metaphor. |
104
- | `💔` | Defer removal of waiting turn | Queue reaction shortcut and queue emoji marker | Reversible cancel metaphor. |
105
- | `💩` | Defer removal of waiting turn | Queue reaction shortcut and queue emoji marker | Reversible reject metaphor. |
106
- | `🗑` | Defer removal | Queue reaction shortcut and queue emoji marker | Like Skip, the reaction remains reversible until dispatch reaches the marked turn. |
101
+
102
+ Only `👍`, `👎`, and `🔥` are queue reaction shortcuts. Every other reaction is decorative, including the retired aliases. Retained queue state uses canonical badges without changing lane, disposition or receipt authority.
107
103
 
108
104
  ### Decorative Or Local-Example Emoji
109
105
 
package/lib/menu-queue.ts CHANGED
@@ -115,7 +115,7 @@ function buildTelegramQueueMenuReplyMarkup(
115
115
  if (items.length === 0) return { inline_keyboard: [backRow, refreshRow] };
116
116
  const rows = items.map((item) => {
117
117
  const badge = getTelegramQueueMenuLaneBadge(item);
118
- const prefix = item.reactionSuppressionEmoji ? `${item.reactionSuppressionEmoji} ` :
118
+ const prefix = item.reactionSuppressionEmoji ? "👎 " :
119
119
  badge ? `${badge} ` : item.hasAttachments ? "📎 " : "";
120
120
  const position = item.reactionSuppressionEmoji
121
121
  ? formatSkippedTelegramQueuePosition(item.queuePosition)
@@ -179,16 +179,15 @@ function escapeTelegramQueueMenuHtmlPreview(text: string): string {
179
179
  return truncated ? escaped + suffix : escaped;
180
180
  }
181
181
 
182
- function getTelegramQueueMenuLaneBadge(item: Pick<TelegramQueueMenuItem, "queueLane" | "priorityEmoji">): string {
182
+ function getTelegramQueueMenuLaneBadge(item: Pick<TelegramQueueMenuItem, "queueLane">): string {
183
183
  if (item.queueLane === "control") return "⚡";
184
184
  if (item.queueLane === "steer") return "🔥";
185
185
  if (item.queueLane !== "priority") return "";
186
- const emoji = item.priorityEmoji?.replace(/\uFE0F/g, "");
187
- return emoji === "❤" || emoji === "🕊" ? emoji : "👍";
186
+ return "👍";
188
187
  }
189
188
 
190
189
  function getTelegramQueueMenuItemText(item: TelegramQueueMenuItem): string {
191
- const marker = item.reactionSuppressionEmoji ?? getTelegramQueueMenuLaneBadge(item);
190
+ const marker = item.reactionSuppressionEmoji ? "👎" : getTelegramQueueMenuLaneBadge(item);
192
191
  const badge = marker ? ` ${marker}` : "";
193
192
  const position = item.reactionSuppressionEmoji
194
193
  ? `<s>${item.queuePosition}</s>.`
@@ -920,10 +919,9 @@ function applyQueuedTelegramPromptMenuFlags<Context>(
920
919
  const disposition: Extract<Queue.TelegramQueueReactionDisposition, { kind: "reaction-transition" }> = { kind: "reaction-transition" };
921
920
  if (flags.lane !== undefined) {
922
921
  disposition.promptLane = flags.lane;
923
- disposition.priorityEmoji = flags.lane === "steer" ? "🔥" : flags.lane === "priority" ?
924
- getTelegramQueueMenuLaneBadge({ queueLane: "priority", priorityEmoji: item.queueLane === "priority" ? item.priorityEmoji : undefined }) : null;
922
+ disposition.priorityEmoji = flags.lane === "steer" ? "🔥" : flags.lane === "priority" ? "👍" : null;
925
923
  }
926
- if (flags.skipped !== undefined) disposition.suppressionEmoji = flags.skipped ? (item.reactionSuppressionEmoji ?? "👎") : null;
924
+ if (flags.skipped !== undefined) disposition.suppressionEmoji = flags.skipped ? "👎" : null;
927
925
  return deps.queueMutationRuntime.applyReactionByMessageId(replyToMessageId, disposition, ctx) ||
928
926
  ((flags.lane === undefined || flags.lane === item.queueLane) &&
929
927
  (flags.skipped === undefined || flags.skipped === (item.reactionSuppressionEmoji !== undefined)));
package/lib/updates.ts CHANGED
@@ -108,16 +108,10 @@ export type TelegramReactionType =
108
108
 
109
109
  export const TELEGRAM_PRIORITY_REACTIONS = [
110
110
  { id: 10, name: "like", emoji: "👍" },
111
- { id: 12, name: "heart", emoji: "❤" },
112
- { id: 13, name: "dove", emoji: "🕊" },
113
111
  ] as const;
114
112
  export const TELEGRAM_STEER_REACTIONS = [{ id: 14, name: "fire", emoji: "🔥" }] as const;
115
113
  export const TELEGRAM_REMOVAL_REACTIONS = [
116
114
  { id: 20, name: "dislike", emoji: "👎" },
117
- { id: 21, name: "ghost", emoji: "👻" },
118
- { id: 22, name: "broken-heart", emoji: "💔" },
119
- { id: 23, name: "poop", emoji: "💩" },
120
- { id: 24, name: "wastebasket", emoji: "🗑" },
121
115
  ] as const;
122
116
  export const TELEGRAM_PRIORITY_REACTION_EMOJIS =
123
117
  TELEGRAM_PRIORITY_REACTIONS.map((reaction) => reaction.emoji);
@@ -159,7 +153,7 @@ function getTelegramQueueReactionTransition(
159
153
  ): TelegramQueueReactionDisposition | undefined {
160
154
  const oldEmojis = collectTelegramReactionEmojis(oldReactions);
161
155
  const newEmojis = collectTelegramReactionEmojis(newReactions);
162
- // Fire is effective Steer intent; weaker positive aliases under it do not reorder the lane.
156
+ // Fire selects Steer; thumbs-up beneath it does not reorder the lane. Other emoji carry no queue intent.
163
157
  const steerEmoji = TELEGRAM_STEER_REACTIONS[0].emoji;
164
158
  const oldPriorityEmoji = oldEmojis.has(steerEmoji) ? steerEmoji : getTelegramReactionEmoji(oldEmojis, TELEGRAM_PRIORITY_REACTION_EMOJIS);
165
159
  const newPriorityEmoji = newEmojis.has(steerEmoji) ? steerEmoji : getTelegramReactionEmoji(newEmojis, TELEGRAM_PRIORITY_REACTION_EMOJIS);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.56.1",
3
+ "version": "0.56.2",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"