@llblab/pi-kit 0.10.7 → 0.10.8

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 CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  All notable changes to `@llblab/pi-kit` are documented here.
4
4
 
5
+ ## 0.10.8 - 2026-09-13
6
+
7
+ - `Follower Forwarding`: Advances the exact Telegram pin to `0.45.8`, so cached-ownership forwarding uses current registration protocol evidence for voice/message and edit retries, message-only callbacks, and reactions. Exact generation, binding, protocol, and durable-receipt checks remain enforced. The package set, resource inventory, and load order remain unchanged.
8
+
5
9
  ## 0.10.7 - 2026-09-12
6
10
 
7
11
  - `Queue Enqueue`: Advances the exact Telegram pin to `0.45.7`, preventing asynchronous voice/file preparation from restoring consumed prompts or overwriting newer queue changes. Surviving history and receipts follow current queue state; an already-wedged in-memory queue still requires separately authorized recovery. The package set, resource inventory, and load order remain unchanged.
package/README.md CHANGED
@@ -15,7 +15,7 @@ Package links lead to the owning repositories for usage, documentation, issues,
15
15
  | [`@llblab/pi-codex-usage`](https://github.com/llblab/pi-codex-usage) | `0.9.4` | Compact Codex/Spark subscription-limit status |
16
16
  | [`@llblab/pi-grow-loop`](https://github.com/llblab/pi-grow-loop) | `0.8.1` | Visible continuation scheduling and bounded worker Skills |
17
17
  | [`@llblab/pi-state-flow`](https://github.com/llblab/pi-state-flow) | `0.9.6` | Atomic scoped state cohorts with final-eligibility resolution |
18
- | [`@llblab/pi-telegram`](https://github.com/llblab/pi-telegram) | `0.45.7` | Telegram companion, queues, files, voice, controls, and Generative Apps guidance |
18
+ | [`@llblab/pi-telegram`](https://github.com/llblab/pi-telegram) | `0.45.8` | Telegram companion, queues, files, voice, controls, and Generative Apps guidance |
19
19
  | [`@llblab/skills`](https://github.com/llblab/skills) | `1.15.0` | Portable workflows for engineering, review, design, context maintenance, and other focused tasks |
20
20
 
21
21
  Versions are exact by design. An upstream release does not change an installed kit until this repository explicitly advances the dependency and publishes a new kit version. Runtime defects and package-specific feature requests belong in the linked repository; package selection and kit installation issues belong here.
@@ -4,6 +4,10 @@
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## 0.45.8: Follower Forwarding Hotfix
8
+
9
+ - `Follower Forwarding`: Message-ownership lookups now project the matching live registration's protocol identity before forwarding. Voice/message and edit retries, message-only callbacks, and reactions no longer stall on an incomplete cached owner after a rejection or re-registration. Exact generation, binding, protocol, and durable-receipt checks remain enforced.
10
+
7
11
  ## 0.45.7: Queue Enqueue Race Hotfix
8
12
 
9
13
  - `Queue Enqueue`: Asynchronous voice/file preparation no longer restores consumed prompts or overwrites newer queue changes, preventing a settled phantom head from blocking accepted work. Final assembly uses current queue state and allocates order at commit; abort-history folding retains only surviving intended prompts and receipts while preserving a handed-off head until `agent_start`.
@@ -429,7 +429,7 @@ The initial `offset: -1` cursor bootstrap is allowed only when both cursor and j
429
429
 
430
430
  Threaded Mode forwarding is a two-journal handoff. Only peers that mutually advertise protocol v1 and `durable-follower-admission-v1` may route or become election-eligible. The follower validates its exact binding and registration generation, durably appends the source-bound delivery, and only then returns the exact receipt. The leader classifies each attempt as `accepted`, `retryable`, or `terminal-rejected` with its delivery identity and failure class; only `accepted` with the expected `deliveryId` and `sourceUpdateId` may complete leader journal authority. Missing, negative, stale-generation, or mismatched-receipt acknowledgements remain durable, and a callback error answer is only an operator-facing side effect.
431
431
 
432
- Delivery ids derive only from envelope kind, source `update_id`, and stable recipient binding. Live registration generation remains a separate attempt fence, while callback/reaction message ownership carries that stable binding and rebinds to its current authenticated follower registration after replacement. Lost acknowledgements therefore replay idempotently into the same follower journal identity without changing the delivery id. Package build skew is allowed only while protocol version and capabilities remain compatible.
432
+ Delivery ids derive only from envelope kind, source `update_id`, and stable recipient binding. Live registration generation remains a separate attempt fence. Message ownership carries the stable binding and rebinds to its current authenticated follower registration after replacement. `ownership.getForwardOwnership()` projects the protocol identity from the exact matching live instance, registration generation, and binding on every lookup; protocol is not cached with message history. The same port serves messages, edits, callbacks, and reactions, including callbacks without a Thread ID. If a cache lookup returns a foreign record but no matching durable-capable protocol can be projected, that record remains foreign and fails forwarding validation rather than falling through to local execution. The final bus validator independently rechecks generation, binding, and complete protocol identity before transport. Lost acknowledgements therefore retain the same delivery identity; only the exact durable receipt completes the source. Package build skew is allowed only while protocol version and capabilities remain compatible.
433
433
 
434
434
  Worker execution ownership is per `update_id` across same-runtime generations. Aborting a generation ends its authority but does not prove its handler settled; replacement replay remains blocked on that exact settlement. Late success and failure are both diagnostic events. Public and built-in handlers receive the same optional execution fence (`signal`, generation/update identity, and pre-effect assertion); the runtime binds it non-enumerably to every internal update carrier and checks it before routing-plan effects. Prompt construction rechecks after downloads and inbound handlers before queue mutation, pairing rechecks around persistence, command/menu and extension-command delegation retain the source fence across detached effects, lifecycle sync rechecks after store load before reconciliation, and reroute clones carry the source fence through forwarding, thread replacement, cleanup, persistence, and Bot API rename boundaries. Legacy handlers remain source-compatible but must not commit unfenced late effects.
435
435
 
@@ -934,7 +934,7 @@ export default function (pi: Pi.ExtensionAPI) {
934
934
  },
935
935
  getAdmissionScope: getTelegramUpdateAdmissionScope,
936
936
  getAdmissionJournalBinding: getTelegramQueueJournalBinding,
937
- getMessageOwnership: messageOwnershipRuntime.store.get,
937
+ getMessageOwnership: messageOwnershipRuntime.getForwardOwnership,
938
938
  recordMessageOwnership: messageOwnershipRuntime.recordRouted,
939
939
  ...inboundBusProjectionRuntime,
940
940
  getDisplayTitle: getThreadDisplayTitle,
@@ -1,9 +1,14 @@
1
1
  /**
2
2
  * Telegram message ownership helpers
3
3
  * Zones: telegram routing, multi-instance bus, in-memory coordination
4
- * Owns live message-id ownership lookup so callbacks/reactions can resolve a Telegram UI surface back to the instance/target that produced it
4
+ * Owns message-id ownership and live forwarding projections so inbound updates resolve back to the instance/target that owns them
5
5
  */
6
6
 
7
+ import {
8
+ TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION,
9
+ type TelegramBusFollowerView,
10
+ type TelegramBusForwardOwnership,
11
+ } from "./bus.ts";
7
12
  import { getTelegramTargetKey, type TelegramTarget } from "./target.ts";
8
13
 
9
14
  export interface TelegramMessageOwnershipRecord {
@@ -44,15 +49,17 @@ export interface TelegramMessageOwnershipStore {
44
49
  clear: () => void;
45
50
  }
46
51
 
47
- export interface TelegramFollowerOwnershipView {
48
- instanceId: string;
49
- connectedAtMs: number;
50
- profileKey?: string;
51
- registrationGeneration?: string;
52
- }
52
+ export type TelegramFollowerOwnershipView = Pick<
53
+ TelegramBusFollowerView,
54
+ "instanceId" | "connectedAtMs" | "profileKey" | "registrationGeneration" | "protocol"
55
+ >;
53
56
 
54
57
  export interface TelegramBusMessageOwnershipRuntime {
55
58
  store: TelegramMessageOwnershipStore;
59
+ getForwardOwnership(
60
+ chatId: number,
61
+ messageId: number,
62
+ ): TelegramBusForwardOwnership | undefined;
56
63
  recordLocal(input: {
57
64
  chatId: number;
58
65
  messageId: number;
@@ -137,6 +144,20 @@ export function createTelegramBusMessageOwnershipRuntime(deps: {
137
144
  };
138
145
  return {
139
146
  store,
147
+ getForwardOwnership(chatId, messageId) {
148
+ const record = store.get(chatId, messageId);
149
+ if (!record?.ownerGeneration || !record.recipientBindingKey) return record;
150
+ const follower = deps.listFollowers().find((candidate) =>
151
+ candidate.instanceId === record.instanceId &&
152
+ candidate.registrationGeneration === record.ownerGeneration &&
153
+ candidate.profileKey === record.recipientBindingKey,
154
+ );
155
+ // Protocol is live registration authority, not message-cache history.
156
+ // Keep an incomplete known owner foreign so the forwarder fails closed.
157
+ return follower?.protocol?.capabilities.includes(
158
+ TELEGRAM_BUS_CAPABILITY_DURABLE_FOLLOWER_ADMISSION,
159
+ ) ? { ...record, protocolIdentity: follower.protocol } : record;
160
+ },
140
161
  recordLocal(input) {
141
162
  return store.record({ ...input, instanceId: deps.instanceId });
142
163
  },
@@ -14,6 +14,7 @@ import {
14
14
  import type {
15
15
  TelegramBusEnvelope,
16
16
  TelegramBusFollowerView,
17
+ TelegramBusForwardOwnership,
17
18
  TelegramBusForeignUpdateSettlement,
18
19
  TelegramProcessLiveness,
19
20
  } from "./bus.ts";
@@ -345,22 +346,14 @@ export function getAuthorizedTelegramGuestMessage(
345
346
 
346
347
  // --- Flow ---
347
348
 
348
- export interface TelegramMessageOwnershipView {
349
- instanceId: string;
350
- ownerGeneration?: string;
351
- recipientBindingKey?: string;
352
- }
349
+ export type TelegramMessageOwnershipView = TelegramBusForwardOwnership;
353
350
 
354
351
  export type TelegramMessageOwnershipLookup = (
355
352
  chatId: number,
356
353
  messageId: number,
357
354
  ) => TelegramMessageOwnershipView | undefined;
358
355
 
359
- export interface TelegramTargetOwnershipView {
360
- instanceId: string;
361
- ownerGeneration?: string;
362
- recipientBindingKey?: string;
363
- }
356
+ export type TelegramTargetOwnershipView = TelegramBusForwardOwnership;
364
357
 
365
358
  export type TelegramTargetOwnershipLookup = (
366
359
  target: TelegramTarget,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.45.7",
3
+ "version": "0.45.8",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-kit",
3
- "version": "0.10.7",
3
+ "version": "0.10.8",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -45,7 +45,7 @@
45
45
  "@llblab/pi-codex-usage": "0.9.4",
46
46
  "@llblab/pi-grow-loop": "0.8.1",
47
47
  "@llblab/pi-state-flow": "0.9.6",
48
- "@llblab/pi-telegram": "0.45.7",
48
+ "@llblab/pi-telegram": "0.45.8",
49
49
  "@llblab/skills": "1.15.0"
50
50
  },
51
51
  "bundledDependencies": [