@llblab/pi-telegram 0.27.11 → 0.28.0
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 +150 -258
- package/BACKLOG.md +1 -169
- package/CHANGELOG.md +396 -441
- package/README.md +9 -6
- package/api/updates.ts +5 -0
- package/docs/architecture.md +71 -26
- package/docs/multi-instance-bus.md +23 -5
- package/docs/public-api.md +7 -6
- package/docs/ui-style.md +6 -6
- package/docs/updates.md +29 -11
- package/index.ts +356 -246
- package/lib/activity-verbosity.ts +26 -0
- package/lib/bindings.ts +240 -2
- package/lib/bus-follower.ts +436 -238
- package/lib/bus-leader.ts +395 -42
- package/lib/bus.ts +994 -153
- package/lib/commands.ts +184 -30
- package/lib/config.ts +23 -2
- package/lib/journal.ts +3140 -0
- package/lib/lifecycle.ts +4 -0
- package/lib/locks.ts +21 -21
- package/lib/media.ts +71 -32
- package/lib/menu-queue.ts +31 -17
- package/lib/menu.ts +5 -3
- package/lib/model.ts +51 -24
- package/lib/ownership.ts +42 -7
- package/lib/paths.ts +35 -0
- package/lib/polling.ts +591 -106
- package/lib/prompts.ts +17 -0
- package/lib/queue.ts +732 -143
- package/lib/routing.ts +291 -64
- package/lib/runtime.ts +26 -10
- package/lib/status.ts +257 -18
- package/lib/sync.ts +131 -5
- package/lib/telegram-api.ts +41 -11
- package/lib/text-groups.ts +75 -35
- package/lib/threads.ts +112 -4
- package/lib/turns.ts +79 -14
- package/lib/updates.ts +3771 -223
- package/package.json +3 -3
- package/scripts/check-downgrade.mjs +435 -0
package/docs/updates.md
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
This document describes the registry that lets layered pi extensions running in the same pi process hook into `pi-telegram`'s polling loop and react to inbound Telegram updates **before** `pi-telegram`'s default routing fires.
|
|
8
8
|
|
|
9
|
+
The `0.28.0` transport/execution split is specified in [Durable Admission And Recovery](./architecture.md#durable-admission-and-recovery). Polling durably admits and advances batches before the independent worker invokes this registry. The worker binds source ids only after public interception passes, supplies public and built-in handlers with the same optional execution fence, settles late grouping under its exact generation, and publishes durable queue readiness. This document describes the executable public registry contract.
|
|
10
|
+
|
|
9
11
|
It is the runtime counterpart to [Callback Namespaces](./callback-namespaces.md): callback namespaces define how to share `callback_data` cleanly; update handlers define how to observe and optionally short-circuit the dispatch of those updates.
|
|
10
12
|
|
|
11
13
|
## When to use it
|
|
@@ -22,9 +24,10 @@ If the extension needs a durable top-level Telegram menu section with managed re
|
|
|
22
24
|
|
|
23
25
|
## Constraints
|
|
24
26
|
|
|
25
|
-
- One bot
|
|
26
|
-
- Handlers run in the
|
|
27
|
+
- One bot profile has one leader-owned `getUpdates` loop. This process-local registry neither creates another transport nor replaces the Threaded Mode leader/follower bus.
|
|
28
|
+
- Handlers run before built-in routing in the independent semantic worker. They must return promptly because a long await delays later handler/routing execution for that source order, but it does not delay `getUpdates` admission or offset persistence. Replacement generations wait for the unsettled handler rather than executing the same update concurrently.
|
|
27
29
|
- Handler errors are caught and logged silently so polling never breaks. If you need durable error reporting, do it inside your handler.
|
|
30
|
+
- Typed handlers receive an optional second `execution` argument with `signal`, `updateId`, `generation`, `isCurrent()`, and `assertCurrent()`. Pass `signal` into cancellable work and call `assertCurrent()` immediately before irreversible effects. The runtime also rejects a verdict returned after cancellation. Legacy one-argument and zero-coupling handlers remain compatible, but any effect they commit while still awaiting is their responsibility; replacement replay still waits for that handler's actual settlement.
|
|
28
31
|
- The registry lives on `globalThis`. Module instance identity is not required, so layered extensions can reach it without importing `@llblab/pi-telegram`.
|
|
29
32
|
|
|
30
33
|
## Verdicts
|
|
@@ -45,11 +48,12 @@ Two equivalent paths.
|
|
|
45
48
|
```ts
|
|
46
49
|
import { registerTelegramUpdateHandler } from "@llblab/pi-telegram/updates";
|
|
47
50
|
|
|
48
|
-
const off = registerTelegramUpdateHandler(async (update) => {
|
|
51
|
+
const off = registerTelegramUpdateHandler(async (update, execution) => {
|
|
49
52
|
const cb = (update as { callback_query?: { id?: string; data?: string } })
|
|
50
53
|
.callback_query;
|
|
51
54
|
if (!cb?.data?.startsWith("myext:")) return "pass";
|
|
52
|
-
await resolveMyApproval(cb);
|
|
55
|
+
await resolveMyApproval(cb, execution?.signal);
|
|
56
|
+
execution?.assertCurrent();
|
|
53
57
|
return "consume";
|
|
54
58
|
});
|
|
55
59
|
|
|
@@ -69,13 +73,26 @@ type PiTelegramVerdict =
|
|
|
69
73
|
| "pass"
|
|
70
74
|
| void
|
|
71
75
|
| Promise<"consume" | "pass" | void>;
|
|
72
|
-
type
|
|
76
|
+
type PiTelegramUpdateExecutionFence = {
|
|
77
|
+
readonly generation: number;
|
|
78
|
+
readonly updateId: number;
|
|
79
|
+
readonly signal: AbortSignal;
|
|
80
|
+
isCurrent: () => boolean;
|
|
81
|
+
assertCurrent: () => void;
|
|
82
|
+
};
|
|
83
|
+
type PiTelegramUpdateHandler = (
|
|
84
|
+
update: unknown,
|
|
85
|
+
execution?: PiTelegramUpdateExecutionFence,
|
|
86
|
+
) => PiTelegramVerdict;
|
|
73
87
|
|
|
74
88
|
interface PiTelegramUpdateHandlerRegistry {
|
|
75
89
|
readonly version: 1;
|
|
76
90
|
add: (handler: PiTelegramUpdateHandler) => () => void;
|
|
77
91
|
// Required: pi-telegram's polling loop calls this on every update.
|
|
78
|
-
dispatch: (
|
|
92
|
+
dispatch: (
|
|
93
|
+
update: unknown,
|
|
94
|
+
execution?: PiTelegramUpdateExecutionFence,
|
|
95
|
+
) => Promise<"consume" | "pass">;
|
|
79
96
|
}
|
|
80
97
|
|
|
81
98
|
const REGISTRY_KEY = "__piTelegramUpdateHandlerRegistry__";
|
|
@@ -100,10 +117,11 @@ function getOrCreateRegistry(): PiTelegramUpdateHandlerRegistry {
|
|
|
100
117
|
handlers.add(handler);
|
|
101
118
|
return () => handlers.delete(handler);
|
|
102
119
|
},
|
|
103
|
-
async dispatch(update) {
|
|
120
|
+
async dispatch(update, execution) {
|
|
104
121
|
for (const handler of handlers) {
|
|
122
|
+
execution?.assertCurrent();
|
|
105
123
|
try {
|
|
106
|
-
const result = await handler(update);
|
|
124
|
+
const result = await handler(update, execution);
|
|
107
125
|
if (result === "consume") return "consume";
|
|
108
126
|
} catch {
|
|
109
127
|
// Never break polling because of a handler error.
|
|
@@ -122,7 +140,7 @@ const off = getOrCreateRegistry().add((update) => {
|
|
|
122
140
|
});
|
|
123
141
|
```
|
|
124
142
|
|
|
125
|
-
The registry object on `globalThis.__piTelegramUpdateHandlerRegistry__` is versioned (`version: 1`) and stable across pi-telegram releases;
|
|
143
|
+
The registry object on `globalThis.__piTelegramUpdateHandlerRegistry__` is versioned (`version: 1`) and stable across pi-telegram releases; the optional second arguments preserve existing v1 callers and implementations. Internal adapters can capture one stable check with `createTelegramUpdateExecutionFenceGuard(update)` or transfer the hidden binding across an unavoidable clone with `carryTelegramUpdateExecutionFence(source, target)`, while `getTelegramUpdateExecutionFence` and `assertTelegramUpdateExecutionCurrent` serve carriers that retain it. Future breaking changes will use a new schema version and a new key.
|
|
126
144
|
|
|
127
145
|
## Interaction with built-in routing
|
|
128
146
|
|
|
@@ -140,9 +158,9 @@ The handler registry is ownership-agnostic and does not interact with the extens
|
|
|
140
158
|
|
|
141
159
|
If a layered extension needs to react to ownership changes, it should observe `pi-telegram` lifecycle events through the standard pi extension hooks rather than through the handler registry.
|
|
142
160
|
|
|
143
|
-
## Not a multiplexer
|
|
161
|
+
## Not a transport multiplexer
|
|
144
162
|
|
|
145
|
-
This registry does not
|
|
163
|
+
This registry does not bypass Telegram's single-polling-connection-per-bot constraint and does not route updates between processes. Threaded Mode provides the separate profile-scoped leader/follower bus: the leader owns `getUpdates`, applies its process-local update registry, and then routes retained built-in traffic to the owning instance. The registry remains an in-process extension interception surface only.
|
|
146
164
|
|
|
147
165
|
## Relationship to extension sections
|
|
148
166
|
|