@llblab/pi-telegram 0.35.1 → 0.36.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 +2 -1
- package/BACKLOG.md +11 -0
- package/CHANGELOG.md +18 -0
- package/README.md +36 -7
- package/docs/README.md +2 -1
- package/docs/architecture.md +29 -2
- package/docs/compact-matrix-literal.md +23 -13
- package/docs/generative-apps.md +310 -0
- package/docs/multi-instance-bus.md +1 -1
- package/docs/outbound.md +2 -2
- package/docs/public-api.md +3 -2
- package/docs/ui-style.md +14 -7
- package/index.ts +14 -0
- package/lib/bindings.ts +118 -8
- package/lib/generative-app-worker.mjs +103 -0
- package/lib/generative-apps.ts +953 -0
- package/lib/menu-queue.ts +105 -112
- package/lib/outbound-buttons.ts +51 -2
- package/lib/outbound-markup.ts +18 -14
- package/lib/outbound.ts +5 -1
- package/lib/prompts.ts +1 -0
- package/lib/queue.ts +98 -42
- package/lib/routing.ts +15 -0
- package/lib/runtime.ts +0 -23
- package/lib/updates.ts +49 -18
- package/package.json +1 -1
- package/skills/generated-control-surface/SKILL.md +13 -5
- package/skills/generative-apps/SKILL.md +110 -0
- package/skills/telegram-bridge/SKILL.md +5 -1
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# Generative Apps Runtime For Telegram
|
|
2
|
+
|
|
3
|
+
_Status: incremental implementation. Canonical installation and explicit transactional replacement, agent-side method invocation, state/history commits, partial-tail recovery, cross-process transition locking with dead-owner recovery, installation-generation plus revision rejection for direct app-output controls, lifecycle-cancelled worker-isolated methods, the bounded non-shell process port, strict bound-action parsing, pre-model-queue `tgbtn` dispatch, new-message default views, and opt-in in-place bound-action edits with explicit-action send fallback are implemented locally. Agent-mediated initial-surface revision capture, process-birth lock proof, voice delivery, automatic refresh scheduling, removal, and complete lifecycle diagnostics remain open in the backlog._
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
This document specifies the concrete Generative App runtime implemented by `pi-telegram`. The transport-independent concept, vocabulary, application shapes, hybrid action model, and agent operating workflow belong to the bundled [`generative-apps` Skill](../skills/generative-apps/SKILL.md).
|
|
8
|
+
|
|
9
|
+
The Telegram implementation provides managed installation, method execution, persistence, button binding, callback routing, and message delivery:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
Telegram control → bound method → state/capability owner
|
|
13
|
+
Telegram view ← rendered output ← fresh result
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
This runtime coexists with ordinary prompt buttons, companion-extension callbacks, Sections, and the Delivery API. It ships no application catalog or `examples/` tree; reusable domain scripts remain with their capability owners.
|
|
17
|
+
|
|
18
|
+
## Ownership Split
|
|
19
|
+
|
|
20
|
+
- The bundled [`generative-apps` Skill](../skills/generative-apps/SKILL.md) owns the general concept and agent operation: category definition, `generated` versus `generative`, application shapes, hybrid method/prompt surfaces, selection, authorship, review, workflow, safety, and validation judgment.
|
|
21
|
+
- [`architecture.md`](./architecture.md#generative-apps) owns this runtime's place inside the Telegram bridge and its domain boundaries.
|
|
22
|
+
- This document owns only `pi-telegram` implementation contracts: canonical managed identity, executable ABI, Telegram wire syntax, state timeline, installation/replacement, bounded ports, callback routing, delivery, lifecycle, and current limitations.
|
|
23
|
+
- [`generated-control-surface`](../skills/generated-control-surface/SKILL.md) owns the separate ephemeral control-surface operating protocol.
|
|
24
|
+
|
|
25
|
+
Keep conceptual guidance out of this document and Telegram runtime mechanics out of the Generative Apps Skill.
|
|
26
|
+
|
|
27
|
+
## Canonical Layout And Identity
|
|
28
|
+
|
|
29
|
+
Generative Apps live under the active Pi agent directory, never in package installation files or temporary storage:
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
<agent-dir>/genapps/
|
|
33
|
+
└── poker/
|
|
34
|
+
├── poker.mjs
|
|
35
|
+
├── state.json
|
|
36
|
+
└── states.jsonl
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Identity is structural:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
app = directory name = module stem
|
|
43
|
+
poker = poker = poker.mjs
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
No app manifest, per-app `package.json`, duplicated `name`, class registration, or default export is required. The `.mjs` extension supplies ESM semantics directly.
|
|
47
|
+
|
|
48
|
+
An app name is a unique lowercase ASCII identifier accepted by the runtime's path-safe validation. It must not contain path separators, `..`, `::`, or a native callback namespace delimiter.
|
|
49
|
+
|
|
50
|
+
## Inference-Bypass Syntax
|
|
51
|
+
|
|
52
|
+
Compact Matrix Literal and full JSON buttons keep their existing `label + prompt` contract. A bound action is encoded entirely in the prompt string:
|
|
53
|
+
|
|
54
|
+
```ebnf
|
|
55
|
+
bound-action = app "::" method [ "(" json-value ")" ]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Examples:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
poker::fold
|
|
62
|
+
poker::call(18)
|
|
63
|
+
poker::init({"seed":"abc"})
|
|
64
|
+
media::seek("+30s")
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
No argument means no decorative empty parentheses. One optional argument is a strict JSON value; the runtime never evaluates JavaScript source from the argument.
|
|
68
|
+
|
|
69
|
+
CML:
|
|
70
|
+
|
|
71
|
+
```text
|
|
72
|
+
[{Fold|poker::fold}{Call 18|poker::call(18)}]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Equivalent JSON:
|
|
76
|
+
|
|
77
|
+
```json
|
|
78
|
+
[
|
|
79
|
+
[
|
|
80
|
+
{ "label": "Fold", "prompt": "poker::fold" },
|
|
81
|
+
{ "label": "Call 18", "prompt": "poker::call(18)" }
|
|
82
|
+
]
|
|
83
|
+
]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`app` is not a button property. Both representations normalize to the same prompt string, and routing happens afterward.
|
|
87
|
+
|
|
88
|
+
The double colon is the inference-bypass operator: it routes a generated prompt control to a registered deterministic owner before Pi queue admission. Native extension callbacks retain their existing single-colon grammar:
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
myext:action:payload native callback_data namespace
|
|
92
|
+
poker::call(18) generated prompt routed to a Generative App
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
These routes do not conflict. Native callbacks are direct by construction. `::` exists only because an ordinary generated button prompt would otherwise enter the model queue.
|
|
96
|
+
|
|
97
|
+
An absent, stale, or invalid bound app fails closed and never degrades into an accidental model prompt.
|
|
98
|
+
|
|
99
|
+
## `telegram_bind` Tool
|
|
100
|
+
|
|
101
|
+
One agent Tool owns installation and deliberate invocation through two mutually exclusive shapes.
|
|
102
|
+
|
|
103
|
+
Install an external self-contained module and initialize it:
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
telegram_bind({
|
|
107
|
+
app: "poker",
|
|
108
|
+
script: "/path/to/poker.mjs",
|
|
109
|
+
argument: { seed: "abc" }
|
|
110
|
+
})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The runtime copies the module to `<agent-dir>/genapps/poker/poker.mjs`, validates the canonical identity and required exports, transactionally invokes `init(argument)`, and initializes state. Existing installation is never overwritten without explicit replacement authority. Installed canonical modules are discovered directly when a bound action resolves; there is no separate app registry.
|
|
114
|
+
|
|
115
|
+
Explicitly replace an installed app after editing its script:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
telegram_bind({
|
|
119
|
+
app: "poker",
|
|
120
|
+
script: "/path/to/poker.mjs",
|
|
121
|
+
replace: true,
|
|
122
|
+
argument: { seed: "abc" }
|
|
123
|
+
})
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Replacement validates and initializes a complete staging app before publishing it under the existing app name. A failed `init` preserves the installed module, state, and timeline. Omitting `replace: true` keeps duplicate installation fail-closed, while setting it for an absent app also fails instead of silently changing replacement into installation.
|
|
127
|
+
|
|
128
|
+
Discover or reuse an app already written at its canonical path and invoke a named method:
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
telegram_bind({
|
|
132
|
+
app: "poker",
|
|
133
|
+
method: "init",
|
|
134
|
+
argument: { seed: "abc" }
|
|
135
|
+
})
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Agent-side diagnostic invocation uses the same shape:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
telegram_bind({ app: "poker", method: "inspect" })
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`script` and `method` are mutually exclusive, and `replace` is valid only with `script`. Script installation or replacement implicitly invokes mandatory `init`; existing-app invocation names its method explicitly. Folder presence supplies durable discoverability across runtime replacement without introducing a manifest.
|
|
145
|
+
|
|
146
|
+
During an active Telegram turn, `telegram_bind` displays successful app output directly through the current outbound planner and exact turn target by default, including initial `init` output; its Tool result tells the agent not to repeat or reformat the delivered view. Set `display: false` for agent-only diagnosis. Outside an active Telegram turn, the Tool returns bounded output for exact caller-owned presentation rather than choosing a Telegram target implicitly. The same method invoked through `app::method(argument)` routes its rendered output directly to the owning Telegram surface.
|
|
147
|
+
|
|
148
|
+
## Module Contract
|
|
149
|
+
|
|
150
|
+
A Generative App exports plain named async or synchronous functions. `init` is mandatory. Classes and default exports are outside the contract.
|
|
151
|
+
|
|
152
|
+
```js
|
|
153
|
+
export async function init({ argument, run, signal }) {
|
|
154
|
+
const seed = argument?.seed ?? "default";
|
|
155
|
+
return {
|
|
156
|
+
state: { seed, turn: 0 },
|
|
157
|
+
output: "**Ready**\n\n<!-- telegram_button {Start|poker::start} -->"
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function start({ state }) {
|
|
162
|
+
const nextState = { ...state, turn: state.turn + 1 };
|
|
163
|
+
return {
|
|
164
|
+
state: nextState,
|
|
165
|
+
output: `**Turn:** \`${nextState.turn}\``
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export async function inspect({ state }) {
|
|
170
|
+
return { output: JSON.stringify(state) };
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The runtime context may contain only bounded capabilities required by the contract:
|
|
175
|
+
|
|
176
|
+
- Current immutable app state, absent for first initialization.
|
|
177
|
+
- Parsed optional JSON argument.
|
|
178
|
+
- Cancellation signal and current app revision.
|
|
179
|
+
- A bounded non-shell process port for coherent CLI adapters.
|
|
180
|
+
- Redacted app/target metadata needed for diagnostics and rendering ownership.
|
|
181
|
+
|
|
182
|
+
The runtime does not pass a raw Telegram client, bot token, Pi extension context, arbitrary transport operation, or mutable queue/session state.
|
|
183
|
+
|
|
184
|
+
A method result contains:
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
interface GenerativeAppResult {
|
|
188
|
+
state?: JsonValue;
|
|
189
|
+
output: string;
|
|
190
|
+
viewMode?: "new" | "edit";
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`output` is ordinary assistant Markdown plus existing top-level voice/button markup. It passes through the established outbound planner rather than defining a second rendering language. Omitted `viewMode` defaults to `"new"`: the result arrives as a fresh message and the clicked button remains visibly selected on its prior surface. `viewMode: "edit"` opts one result into replacing the callback message and keyboard in place when Telegram permits it; edit failure after that explicit action may fall back to one new message.
|
|
195
|
+
|
|
196
|
+
Returning `state` requests a committed transition. Omitting `state` makes the method output-only, which supports inspection and live refresh without appending duplicate history. Invalid, oversized, non-serializable, or malformed results fail before state or Telegram effects commit.
|
|
197
|
+
|
|
198
|
+
## Current State And State Timeline
|
|
199
|
+
|
|
200
|
+
`state.json` is the compact current projection read by the runtime and, when useful, by the agent:
|
|
201
|
+
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"seed": "abc",
|
|
205
|
+
"turn": 2
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`states.jsonl` is the committed-state timeline. Its first line is the successful initial state; each later state-changing method appends one complete snapshot envelope:
|
|
210
|
+
|
|
211
|
+
```jsonl
|
|
212
|
+
{"revision":0,"method":"init","argument":{"seed":"abc"},"state":{"seed":"abc","turn":0}}
|
|
213
|
+
{"revision":1,"method":"start","state":{"seed":"abc","turn":1}}
|
|
214
|
+
{"revision":2,"method":"start","state":{"seed":"abc","turn":2}}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
The state in `state.json` equals the state in the latest complete journal line. Runtime-owned locking, revision checks, complete-line append, atomic replacement, and recovery preserve that relation across concurrent clicks and interruption. A partial final JSONL line is never treated as committed state.
|
|
218
|
+
|
|
219
|
+
A successful `init` is a hard new-run boundary. It transactionally clears current state and prior history, writes the new initial snapshot as revision zero, and publishes the initial output. Initialization failure preserves the previous working state and journal unchanged.
|
|
220
|
+
|
|
221
|
+
Application state is the app's complete persistent checkpoint: it includes interaction/configuration state plus the latest normalized external projection needed to render, diagnose, or reconstruct the current view. An agent reading `state.json` should be able to identify material app reality such as selected track, playback state, queue position, backend, and last observation without executing the app first. This projection is explicitly a last-observed cache, not the external domain authority: before a mutation, explicit status, or refresh, a CLI-backed app re-reads the actual owner, then commits a new complete snapshot only when retained app state materially changes.
|
|
222
|
+
|
|
223
|
+
## CLI Capability Adapters
|
|
224
|
+
|
|
225
|
+
A Generative App may compose several existing CLI tools when they belong to one coherent domain or user journey:
|
|
226
|
+
|
|
227
|
+
```text
|
|
228
|
+
media Generative App → playerctl + mpv + local media library
|
|
229
|
+
git Generative App → git + gh
|
|
230
|
+
actors Generative App → documented Actor runtime capabilities
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
A bounded process port uses executable plus argument arrays, an explicit working directory, output limits, timeout, cancellation, and redacted evidence. Shell interpolation is not the default contract.
|
|
234
|
+
|
|
235
|
+
```js
|
|
236
|
+
const result = await run({
|
|
237
|
+
command: "playerctl",
|
|
238
|
+
args: ["metadata", "--format", "{{artist}} — {{title}}"],
|
|
239
|
+
timeoutMs: 10_000
|
|
240
|
+
});
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
A generic `exec(arbitrary-shell-command)` Generative App is forbidden. It would turn Telegram into a remote terminal, bypass bounded capability ownership, and violate the mobile companion boundary. A direct button click authorizes only the installed app method and its validated argument, never arbitrary process execution.
|
|
244
|
+
|
|
245
|
+
## Live Views
|
|
246
|
+
|
|
247
|
+
A Generative App sends a new message after a successful bound user action by default. This simple mode preserves prior surfaces and their visibly selected buttons, is robust across ordinary Telegram constraints, and remains a first-class behavior rather than a fallback to eliminate. A method may opt into `viewMode: "edit"` to replace the callback message and keyboard in place; if that explicit action cannot edit a deleted or otherwise unavailable message, it may send one fresh view because the click itself supplies recreation authority.
|
|
248
|
+
|
|
249
|
+
Automatic refresh is not implemented in the current runtime. The intended future contract uses an exported `refresh` method and a bounded scheduling hint; applications must not return or rely on that hint until the backlog item is complete:
|
|
250
|
+
|
|
251
|
+
```js
|
|
252
|
+
export async function refresh({ state, run }) {
|
|
253
|
+
return {
|
|
254
|
+
output: await renderPlayer(state.player, run),
|
|
255
|
+
refreshAfterMs: 5000
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
The runtime contract is:
|
|
261
|
+
|
|
262
|
+
- Missing `refreshAfterMs` stops automatic refresh.
|
|
263
|
+
- Values below two seconds clamp to two seconds.
|
|
264
|
+
- The next interval starts only after the prior refresh and Telegram edit settle; calls never overlap or accumulate.
|
|
265
|
+
- One refresh schedule exists per app, profile, target, and logical surface.
|
|
266
|
+
- An unchanged normalized frame digest causes no Telegram edit.
|
|
267
|
+
- Telegram `retry_after`, bounded backoff, lifecycle cancellation, target authority, and execution generation remain authoritative.
|
|
268
|
+
- Refresh is session-bound and does not silently resume after process replacement until the surface is opened again.
|
|
269
|
+
- Output-only refresh does not change `state.json` or append `states.jsonl`.
|
|
270
|
+
|
|
271
|
+
The runtime retains the latest `TelegramDeliveryHandle` in memory for each live app surface. The first frame sends a logical view; later app actions and refreshes edit that same view rather than creating message traffic.
|
|
272
|
+
|
|
273
|
+
Telegram does not reliably report deletion of every ordinary private bot message. When a supported deletion update identifies the handle, the runtime invalidates it immediately. When edit returns a known message-not-found result, the runtime forgets the handle and stops refresh. It never recreates a user-deleted view automatically; the next explicit user action or app opening may create a fresh view.
|
|
274
|
+
|
|
275
|
+
## Lifecycle And Safety
|
|
276
|
+
|
|
277
|
+
Generative Apps are trusted local code and therefore an explicit capability grant, not a sandbox promise. The runtime still narrows accidental authority and operational failure:
|
|
278
|
+
|
|
279
|
+
- Installation validates canonical paths and rejects traversal, symlinks outside the managed root, identity mismatch, and silent replacement; explicit replacement stages and initializes the new app before swapping it under the same app.
|
|
280
|
+
- App execution cannot own Telegram polling, credentials, raw transport, Pi queue state, or another app's files through the provided contract.
|
|
281
|
+
- Per-app transitions serialize and compare immutable installation generation plus state revision so stale buttons cannot cross replacement or mutate newer state.
|
|
282
|
+
- State commit and Telegram effect ordering are explicit; ambiguous non-idempotent transport outcomes never replay blindly.
|
|
283
|
+
- Time, output, state-size, refresh-rate, and process bounds prevent one app from monopolizing the extension.
|
|
284
|
+
- Session/profile/target generation replacement makes old scheduled work inert.
|
|
285
|
+
- Diagnostics redact secrets and preserve app name, method, revision, failure class, and bounded stderr/result evidence.
|
|
286
|
+
- Removal cancels refresh, invalidates the live binding, and keeps destructive state deletion as a separate explicit operation.
|
|
287
|
+
|
|
288
|
+
## Application Roles
|
|
289
|
+
|
|
290
|
+
The runtime supports two ownership roles without shipping application templates:
|
|
291
|
+
|
|
292
|
+
- A standalone deterministic app owns its complete application state and transition rules. Poker-like games are the reference shape for app-owned state, not a bundled catalog entry.
|
|
293
|
+
- A view/controller adapter owns only validated adapter configuration and a last-observed projection. A music-player remote is the reference shape: the Actor remains authoritative, while the Generative App samples structured status, invokes bounded controls, and renders the next view.
|
|
294
|
+
|
|
295
|
+
Both roles use the same module, state, bound-action, and safety contracts. Selection and authoring procedure belong to the bundled `generative-apps` Skill.
|
|
296
|
+
|
|
297
|
+
## Validation Contract
|
|
298
|
+
|
|
299
|
+
Implementation is not complete until evidence covers:
|
|
300
|
+
|
|
301
|
+
- Canonical path identity, direct app discovery, copy/install, explicit replacement with failure preservation, removal, and traversal rejection.
|
|
302
|
+
- Mandatory `init`, named method dispatch, no-argument and strict-JSON argument parsing, missing exports, and result validation.
|
|
303
|
+
- Transactional initialization, state/history equality, concurrent/stale actions, partial journal recovery, and output-only methods.
|
|
304
|
+
- CML and full JSON button equivalence, inference bypass before Pi queue admission, absent-owner failure, and unchanged native callback routing.
|
|
305
|
+
- Direct classic, leader, and follower target delivery with generation fencing and no model turn.
|
|
306
|
+
- CLI process timeout, cancellation, output bounds, stderr diagnostics, and arbitrary-shell rejection.
|
|
307
|
+
- Live-view handle retention, unchanged-frame suppression, two-second minimum, non-overlap, coalescing, Telegram backoff, deletion invalidation, message-not-found handling, and lifecycle cancellation.
|
|
308
|
+
- Poker-style internal state and media-style external-state reference applications.
|
|
309
|
+
|
|
310
|
+
The canonical open implementation work remains in [`../BACKLOG.md`](../BACKLOG.md). This document owns the proposed subsystem contract and its architectural boundaries.
|
|
@@ -316,7 +316,7 @@ Threaded Mode should make follower threads behave like normal Telegram instance
|
|
|
316
316
|
| Surface | Leader behavior | Follower requirement | Routing/ownership invariant | Regression evidence |
|
|
317
317
|
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
318
318
|
| 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 |
|
|
319
|
-
| Queued-message removal reactions | 👎/👻/💔/💩/🗑
|
|
319
|
+
| 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 |
|
|
320
320
|
| 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 |
|
|
321
321
|
| 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 |
|
|
322
322
|
| 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 |
|
package/docs/outbound.md
CHANGED
|
@@ -164,11 +164,11 @@ I can continue.
|
|
|
164
164
|
|
|
165
165
|
Rules:
|
|
166
166
|
|
|
167
|
-
- `telegram_button` accepts a JSON object, JSON matrix, [Compact Matrix Literal](./compact-matrix-literal.md), or double-quoted HTML-like attributes; `telegram_buttons` is an exact plural alias. CML uses `{value}` or `{label|prompt}`, trims
|
|
167
|
+
- `telegram_button` accepts a JSON object, JSON matrix, [Compact Matrix Literal](./compact-matrix-literal.md), or double-quoted HTML-like attributes; `telegram_buttons` is an exact plural alias. CML uses `{value}`, `{label|prompt}`, or `{label|prompt|selected_style}`; the optional third atom requires a prompt and accepts only `primary`, `success`, or `danger`. It trims every atom, preserves non-structural printable text, and decodes only `\|`, `\}`, and `\\`. Shorthand, body, paired-comment, unquoted-attribute, and single-quoted-attribute forms are rejected.
|
|
168
168
|
- A colon after either button marker is rejected so every payload form shares one unambiguous action marker.
|
|
169
169
|
- Use `label` plus `prompt`, or the compact `value` key when both strings are identical. Explicit `label` or `prompt` takes precedence over its `value` fallback. Use JSON with `\n` escapes for multiline prompts.
|
|
170
170
|
- The opening marker must start at column zero on a top-level line outside fenced code, quotes, lists, and indented examples; otherwise it remains literal Markdown.
|
|
171
|
-
- Prefer one matrix comment for multiple buttons. Each top-level JSON object or CML cell becomes one full-width inline-keyboard row in source order; a nested row groups one or more buttons horizontally. The parser imposes no artificial per-row width cap; empty rows, malformed cells, unknown/trailing CML escapes, a
|
|
171
|
+
- Prefer one matrix comment for multiple buttons. Each top-level JSON object or CML cell becomes one full-width inline-keyboard row in source order; a nested row groups one or more buttons horizontally. The parser imposes no artificial per-row width cap; empty rows, malformed cells, unknown/trailing CML escapes, a third unescaped CML separator, empty atoms, unknown selected styles, and deeper nesting are rejected atomically. Generated surfaces default to five columns and expand to six through eight only for short position-bearing labels. Repeated singular comments remain valid.
|
|
172
172
|
- Button actions are stored in memory with short `callback_data`; Telegram never sees the full prompt in the button payload.
|
|
173
173
|
- After Telegram accepts a generated button callback as a queued prompt, the bridge changes that exact button to its configured selection style without changing agent-authored text or emoji. Set `selected_style` to `primary` (blue), `success` (green), or `danger` (red); omitted or invalid values fall back to `primary`. The style never suppresses queue admission. Other choices stay visually unchanged and remain available; the callback acknowledgement remains the fallback on clients that do not render button styles.
|
|
174
174
|
- When generated button markup is the entire assistant reply, the bridge supplies the standard `☑️ **Choose an option:**` heading as visible message text so Telegram has a message to which it can attach the inline keyboard.
|
package/docs/public-api.md
CHANGED
|
@@ -61,11 +61,12 @@ This command surface is a mobile companion subset, not a raw terminal-command br
|
|
|
61
61
|
|
|
62
62
|
### Tools and assistant-authored actions
|
|
63
63
|
|
|
64
|
+
- `telegram_bind({ app, script, argument? } | { app, method, argument? })` installs and initializes one canonical managed Generative App module under `<agent-dir>/genapps/<app>/<app>.mjs`, or invokes one named method on an installed app. Installation rejects silent replacement and noncanonical/symlink sources. Methods receive immutable JSON state, one optional JSON argument, cancellation, revision, and a bounded non-shell process port; successful state changes commit to `state.json` plus `states.jsonl`, while output-only methods leave history unchanged. After one-shot `tgbtn` resolution, a complete `app::method` or `app::method(<strict JSON>)` prompt invokes the installed app before Pi queue admission and sends its planned Markdown/buttons directly; malformed or failed bound actions never fall back to a model prompt. Direct app-output buttons retain hidden source revisions and stale actions fail before method execution; sibling processes serialize transitions and recover dead lock owners. Bound actions send a fresh message by default and retain the clicked button's selected state on its prior surface. A result may opt into `viewMode: "edit"` to replace the callback message and keyboard in place, with one fresh-send fallback only for that explicit action. Agent-mediated initial-surface revisions, process-birth lock proof, automatic refresh, and voice output remain open.
|
|
64
65
|
- `telegram_attach(paths, chat_id?, thread_id?, caption?)` is the stable artifact delivery tool for generated files. During Telegram turns it queues files for the active reply; with `assistant.rendering: "rich"`, exactly one PNG/JPEG, MP4, or MP3 artifact plus non-empty final Markdown can become one reply-anchored Rich Message. HTML mode, multiple/unsupported files, Guest Mode, and voice outputs retain their established paths. Outside Telegram turns the tool sends files directly to the paired/default chat, the registered follower's assigned thread, or an explicit `chat_id` plus optional `thread_id` when this Pi instance owns `/telegram-connect` or is registered with the multi-instance bus.
|
|
65
66
|
- `telegram_message(text, chat_id?, thread_id?)` sends a direct Telegram Markdown message when this Pi instance owns `/telegram-connect` or is registered with the multi-instance bus. During an active Telegram turn, omitted targeting and an explicit target equal to that turn are rejected so the ordinary final-reply path remains the sole current-target response; an explicit different chat/thread target remains allowed for requested cross-target delivery. Outside active turns, paired/default local/TUI delivery remains unchanged. Top-level `telegram_button` comments inside `text` are parsed with the same planner used for normal replies and attached to that message; buttons are never standalone Telegram messages.
|
|
66
|
-
- The bundled `telegram-bridge` Skill owns action syntax, target routing, Threaded Mode, formatting, and profile-specific debugging guidance. The regular prompt routes applicable turns to that Skill. `telegram_attach` and `telegram_message` remain registered but are model-active only while this instance owns direct transport or holds a live follower registration; disconnect/loss suppresses their schemas and prompt metadata, and recovery restores only the operator's previously active pi-telegram subset.
|
|
67
|
+
- The bundled `telegram-bridge` Skill owns action syntax, target routing, Threaded Mode, formatting, Generative App operation, and profile-specific debugging guidance. The regular prompt routes applicable turns to that Skill. `telegram_attach`, `telegram_bind`, and `telegram_message` remain registered but are model-active only while this instance owns direct transport or holds a live follower registration; disconnect/loss suppresses their schemas and prompt metadata, and recovery restores only the operator's previously active pi-telegram subset.
|
|
67
68
|
- `telegram_voice` hidden comments request Telegram-native voice delivery through either a JSON object or double-quoted attributes. Equivalent `text` or `value` supplies the spoken payload; explicit `text` takes precedence.
|
|
68
|
-
- `telegram_button` hidden comments create inline buttons whose taps enqueue prompts. One marker accepts a JSON object, JSON matrix, [Compact Matrix Literal](./compact-matrix-literal.md), or double-quoted attributes; `telegram_buttons` is an exact plural alias. Top-level matrix cells become full-width rows, while nested rows group one or more buttons horizontally without an artificial parser-width cap. CML uses `{value}` or `{label|prompt}`, trims atom boundaries
|
|
69
|
+
- `telegram_button` hidden comments create inline buttons whose taps enqueue prompts. One marker accepts a JSON object, JSON matrix, [Compact Matrix Literal](./compact-matrix-literal.md), or double-quoted attributes; `telegram_buttons` is an exact plural alias. Top-level matrix cells become full-width rows, while nested rows group one or more buttons horizontally without an artificial parser-width cap. CML uses `{value}`, `{label|prompt}`, or `{label|prompt|selected_style}`; the optional third atom requires a prompt and accepts only `primary`, `success`, or `danger`. It trims atom boundaries and supports only the minimal escapes `\|`, `\}`, and `\\`. Prefer one matrix comment for multiple buttons. Use JSON `label` plus `prompt`, or `value` when both strings are identical. Action markers are colon-free; colon-prefixed payloads are rejected. Use top-level column-zero comments outside code, quotes, lists, and indented examples; do not emit standalone button actions.
|
|
69
70
|
|
|
70
71
|
Prompt guidance is context-aware: local/TUI prompts see only explicit direct-delivery guidance, while Telegram-originated turns receive the full action-comment syntax and phone-width output contract.
|
|
71
72
|
|
package/docs/ui-style.md
CHANGED
|
@@ -54,7 +54,7 @@ Use emoji as stable semantic markers, not decoration. Emoji carry transportable
|
|
|
54
54
|
| `➡️` | Choose replacement target | Thread replace/restore target buttons that select which Pi instance should move to the current thread | Use inside the second replace/restore chooser, not for ordinary reroutes. |
|
|
55
55
|
| `☑️` | Activate / choose this item | Model detail activation action, generated button-only choice heading | Positive selection cue; use `🟢 Active` for already-current state. |
|
|
56
56
|
| `❌` | No / cancel | Confirmation cancel buttons | Use for safe cancellation, not destructive removal. |
|
|
57
|
-
| `🗑` | Delete /
|
|
57
|
+
| `🗑` | Delete / defer removal | Destructive confirmations and removal reaction | In the queue menu, reversible Keep/Skip selectors replace immediate deletion. |
|
|
58
58
|
|
|
59
59
|
### State Indicators And Button Grammars
|
|
60
60
|
|
|
@@ -68,7 +68,14 @@ Use emoji as stable semantic markers, not decoration. Emoji carry transportable
|
|
|
68
68
|
|
|
69
69
|
### Queue Reaction Shortcuts
|
|
70
70
|
|
|
71
|
-
Queue reactions are shortcut controls for waiting turns. Preserve their semantics across Telegram reactions, queue-menu rows, status previews, and tests.
|
|
71
|
+
Queue reactions are shortcut controls for waiting turns. Preserve their semantics across Telegram reactions, queue-menu rows, status previews, and tests. Positive emoji control the Priority/Normal lane; negative emoji control Keep/Skip. These categories are independent, may coexist, and mutate only their own dimension. Crossing lanes appends the prompt at the destination FIFO tail; changing Keep/Skip or changing emoji within one category preserves lane position. Skip wins only when dispatch reaches the prompt.
|
|
72
|
+
|
|
73
|
+
Queue item detail renders two independent selector rows:
|
|
74
|
+
|
|
75
|
+
- `🟡 Priority` / `⚫️ Normal` or `⚫️ Priority` / `🟣 Normal` selects the lane.
|
|
76
|
+
- `🟢 Keep` / `⚫️ Skip` or `⚫️ Keep` / `🟡 Skip` selects dispatch disposition.
|
|
77
|
+
|
|
78
|
+
The menu may clear internal Skip but cannot remove a reaction created by the user through Telegram's Bot API.
|
|
72
79
|
|
|
73
80
|
| Emoji | Meaning | Canonical surfaces | Notes |
|
|
74
81
|
| --- | --- | --- | --- |
|
|
@@ -77,11 +84,11 @@ Queue reactions are shortcut controls for waiting turns. Preserve their semantic
|
|
|
77
84
|
| `❤` / `❤️` | Promote to priority | Queue reaction shortcut | Normalize display consistently where code normalizes reactions. |
|
|
78
85
|
| `🕊` / `🕊️` | Promote to priority | Queue reaction shortcut | Soft/peaceful promotion gesture. |
|
|
79
86
|
| `🔥` | Promote to priority | Queue reaction shortcut | Urgent/hot promotion gesture. |
|
|
80
|
-
| `👎` |
|
|
81
|
-
| `👻` |
|
|
82
|
-
| `💔` |
|
|
83
|
-
| `💩` |
|
|
84
|
-
| `🗑` |
|
|
87
|
+
| `👎` | 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. |
|
|
88
|
+
| `👻` | Defer removal of waiting turn | Queue reaction shortcut and queue emoji marker | Disappear/remove metaphor. |
|
|
89
|
+
| `💔` | Defer removal of waiting turn | Queue reaction shortcut and queue emoji marker | Reversible cancel metaphor. |
|
|
90
|
+
| `💩` | Defer removal of waiting turn | Queue reaction shortcut and queue emoji marker | Reversible reject metaphor. |
|
|
91
|
+
| `🗑` | Defer removal | Queue reaction shortcut and queue emoji marker | Like Skip, the reaction remains reversible until dispatch reaches the marked turn. |
|
|
85
92
|
|
|
86
93
|
### Decorative Or Local-Example Emoji
|
|
87
94
|
|
package/index.ts
CHANGED
|
@@ -228,6 +228,8 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
228
228
|
const proactivePushChatIdGetter =
|
|
229
229
|
Config.createTelegramProactivePushChatIdGetter(proactivePushTargetGetter);
|
|
230
230
|
const buttonActionStore = Outbound.createTelegramButtonActionStore();
|
|
231
|
+
const planGenerativeAppOutput =
|
|
232
|
+
Outbound.createTelegramOutboundReplyPlanner(buttonActionStore);
|
|
231
233
|
const pendingModelSwitchStore =
|
|
232
234
|
Model.createPendingModelSwitchStore<
|
|
233
235
|
Model.ScopedTelegramModel<ActivePiModel>
|
|
@@ -502,6 +504,16 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
502
504
|
getHandlers: configStore.getOutboundHandlers,
|
|
503
505
|
recordRuntimeEvent,
|
|
504
506
|
});
|
|
507
|
+
const invokeGenerativeAppBoundButtonAction =
|
|
508
|
+
Bindings.createTelegramGenerativeAppBoundButtonActionInvoker({
|
|
509
|
+
agentDir: Paths.resolveAgentDir(),
|
|
510
|
+
assertExecutionCurrent: Updates.assertTelegramUpdateExecutionCurrent,
|
|
511
|
+
getExecutionFence: Updates.getTelegramUpdateExecutionFence,
|
|
512
|
+
planOutput: planGenerativeAppOutput,
|
|
513
|
+
sendMarkdownReply,
|
|
514
|
+
editInteractiveMessage,
|
|
515
|
+
recordRuntimeEvent,
|
|
516
|
+
});
|
|
505
517
|
const {
|
|
506
518
|
activityRuntime,
|
|
507
519
|
activityVerbosityRuntime,
|
|
@@ -760,6 +772,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
760
772
|
settingsMenuCallbackHandler: settingsMenuRuntime.handleCallbackQuery,
|
|
761
773
|
sectionRegistry,
|
|
762
774
|
buttonActionStore,
|
|
775
|
+
invokeBoundButtonAction: invokeGenerativeAppBoundButtonAction,
|
|
763
776
|
inboundHandlerRuntime,
|
|
764
777
|
threadStore,
|
|
765
778
|
updateStatus,
|
|
@@ -1219,6 +1232,7 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
1219
1232
|
|
|
1220
1233
|
Bindings.registerTelegramCommandsAndTools({
|
|
1221
1234
|
pi,
|
|
1235
|
+
agentDir: Paths.resolveAgentDir(),
|
|
1222
1236
|
configStore,
|
|
1223
1237
|
persistConfig: persistTelegramConfigWithSync,
|
|
1224
1238
|
setup,
|
package/lib/bindings.ts
CHANGED
|
@@ -26,6 +26,7 @@ import * as Setup from "./setup.ts";
|
|
|
26
26
|
import * as Status from "./status.ts";
|
|
27
27
|
import * as TelegramApi from "./telegram-api.ts";
|
|
28
28
|
import type { TelegramTarget } from "./target.ts";
|
|
29
|
+
import * as GenerativeApps from "./generative-apps.ts";
|
|
29
30
|
|
|
30
31
|
type ActivePiModel = NonNullable<Pi.ExtensionContext["model"]>;
|
|
31
32
|
|
|
@@ -53,10 +54,7 @@ export interface TelegramQueueBindingRuntime<TContext> {
|
|
|
53
54
|
|
|
54
55
|
export function createTelegramQueueBindingRuntime<TContext>(deps: {
|
|
55
56
|
store: Queue.TelegramQueueStateStore<TContext>;
|
|
56
|
-
queue: Pick<
|
|
57
|
-
Runtime.TelegramBridgeRuntime["queue"],
|
|
58
|
-
"getNextPriorityReactionOrder" | "incrementNextPriorityReactionOrder"
|
|
59
|
-
>;
|
|
57
|
+
queue: Pick<Runtime.TelegramBridgeRuntime["queue"], "allocateItemOrder">;
|
|
60
58
|
lifecycle: Pick<
|
|
61
59
|
Runtime.TelegramBridgeRuntime["lifecycle"],
|
|
62
60
|
"isCompactionInProgress" | "hasDispatchPending"
|
|
@@ -95,9 +93,7 @@ export function createTelegramQueueBindingRuntime<TContext>(deps: {
|
|
|
95
93
|
}): TelegramQueueBindingRuntime<TContext> {
|
|
96
94
|
const mutation = Queue.createTelegramQueueMutationController({
|
|
97
95
|
...deps.store,
|
|
98
|
-
|
|
99
|
-
incrementNextPriorityReactionOrder:
|
|
100
|
-
deps.queue.incrementNextPriorityReactionOrder,
|
|
96
|
+
allocateLaneOrder: deps.queue.allocateItemOrder,
|
|
101
97
|
onItemsDiscarded(items, ctx) {
|
|
102
98
|
deps.admission.getSettlement()?.onItemsDiscarded(items, ctx);
|
|
103
99
|
},
|
|
@@ -146,6 +142,108 @@ export function createTelegramQueueBindingRuntime<TContext>(deps: {
|
|
|
146
142
|
};
|
|
147
143
|
}
|
|
148
144
|
|
|
145
|
+
export function createTelegramGenerativeAppBoundButtonActionInvoker<
|
|
146
|
+
TQuery extends {
|
|
147
|
+
message?: { chat?: { id?: number }; message_id?: number };
|
|
148
|
+
},
|
|
149
|
+
>(deps: {
|
|
150
|
+
agentDir: string;
|
|
151
|
+
assertExecutionCurrent: (query: TQuery) => void;
|
|
152
|
+
getExecutionFence: (query: TQuery) => GenerativeApps.GenerativeAppExecutionFence | undefined;
|
|
153
|
+
planOutput: ReturnType<typeof OutboundHandlers.createTelegramOutboundReplyPlanner>;
|
|
154
|
+
sendMarkdownReply: (
|
|
155
|
+
chatId: number,
|
|
156
|
+
replyToMessageId: number,
|
|
157
|
+
markdown: string,
|
|
158
|
+
options?: { replyMarkup?: OutboundHandlers.TelegramOutboundButtonMarkup },
|
|
159
|
+
) => Promise<unknown>;
|
|
160
|
+
editInteractiveMessage?: (
|
|
161
|
+
chatId: number,
|
|
162
|
+
messageId: number,
|
|
163
|
+
markdown: string,
|
|
164
|
+
mode: "markdown",
|
|
165
|
+
replyMarkup: OutboundHandlers.TelegramOutboundButtonMarkup,
|
|
166
|
+
) => Promise<void>;
|
|
167
|
+
recordRuntimeEvent: TelegramRuntimeEventRecorder;
|
|
168
|
+
}): (
|
|
169
|
+
action: OutboundHandlers.TelegramOutboundButtonAction,
|
|
170
|
+
query: TQuery,
|
|
171
|
+
) => Promise<false | "new" | "edit"> {
|
|
172
|
+
return async (action, query) => {
|
|
173
|
+
let boundAction: GenerativeApps.GenerativeAppBoundAction | undefined;
|
|
174
|
+
try {
|
|
175
|
+
boundAction = GenerativeApps.parseGenerativeAppBoundAction(action.prompt);
|
|
176
|
+
if (!boundAction) return false;
|
|
177
|
+
deps.assertExecutionCurrent(query);
|
|
178
|
+
const result = await GenerativeApps.invokeGenerativeApp({
|
|
179
|
+
agentDir: deps.agentDir,
|
|
180
|
+
...(deps.getExecutionFence(query)
|
|
181
|
+
? { execution: deps.getExecutionFence(query) }
|
|
182
|
+
: {}),
|
|
183
|
+
...(boundAction.argument !== undefined
|
|
184
|
+
? { argument: boundAction.argument }
|
|
185
|
+
: {}),
|
|
186
|
+
...(action.binding?.app === boundAction.app
|
|
187
|
+
? {
|
|
188
|
+
expectedGeneration: action.binding.generation,
|
|
189
|
+
expectedRevision: action.binding.revision,
|
|
190
|
+
}
|
|
191
|
+
: {}),
|
|
192
|
+
method: boundAction.method,
|
|
193
|
+
app: boundAction.app,
|
|
194
|
+
});
|
|
195
|
+
deps.assertExecutionCurrent(query);
|
|
196
|
+
const chatId = query.message?.chat?.id;
|
|
197
|
+
const messageId = query.message?.message_id;
|
|
198
|
+
if (typeof chatId !== "number" || typeof messageId !== "number") {
|
|
199
|
+
throw new Error("Generative App callback target is unavailable.");
|
|
200
|
+
}
|
|
201
|
+
const reply = deps.planOutput(result.output, {
|
|
202
|
+
binding: {
|
|
203
|
+
generation: result.generation,
|
|
204
|
+
app: result.app,
|
|
205
|
+
revision: result.revision,
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
if (result.viewMode === "edit" && deps.editInteractiveMessage) {
|
|
209
|
+
let editFailed = false;
|
|
210
|
+
try {
|
|
211
|
+
await deps.editInteractiveMessage(
|
|
212
|
+
chatId,
|
|
213
|
+
messageId,
|
|
214
|
+
reply.markdown,
|
|
215
|
+
"markdown",
|
|
216
|
+
reply.replyMarkup ?? { inline_keyboard: [] },
|
|
217
|
+
);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
editFailed = true;
|
|
220
|
+
deps.recordRuntimeEvent("generative-app", error, {
|
|
221
|
+
phase: "bound-action-edit-fallback",
|
|
222
|
+
app: boundAction.app,
|
|
223
|
+
method: boundAction.method,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
deps.assertExecutionCurrent(query);
|
|
227
|
+
if (!editFailed) return "edit";
|
|
228
|
+
}
|
|
229
|
+
deps.assertExecutionCurrent(query);
|
|
230
|
+
await deps.sendMarkdownReply(chatId, messageId, reply.markdown, {
|
|
231
|
+
replyMarkup: reply.replyMarkup,
|
|
232
|
+
});
|
|
233
|
+
deps.assertExecutionCurrent(query);
|
|
234
|
+
return "new";
|
|
235
|
+
} catch (error) {
|
|
236
|
+
deps.recordRuntimeEvent("generative-app", error, {
|
|
237
|
+
phase: "bound-action",
|
|
238
|
+
...(boundAction
|
|
239
|
+
? { app: boundAction.app, method: boundAction.method }
|
|
240
|
+
: {}),
|
|
241
|
+
});
|
|
242
|
+
throw error;
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
149
247
|
export interface TelegramAgentMessageToolRoutingRuntime {
|
|
150
248
|
resolveAgentTarget: TelegramAgentTargetResolver;
|
|
151
249
|
routeAgentMessage: TelegramAgentMessageRouter;
|
|
@@ -331,6 +429,7 @@ export function createTelegramActivityBindingRuntime<TTransportStamp>(deps: {
|
|
|
331
429
|
|
|
332
430
|
interface TelegramCommandsAndToolsBindingDeps {
|
|
333
431
|
pi: Pi.ExtensionAPI;
|
|
432
|
+
agentDir: string;
|
|
334
433
|
configStore: Config.TelegramConfigStore;
|
|
335
434
|
persistConfig: (config?: Config.TelegramConfig) => Promise<void>;
|
|
336
435
|
setup: Setup.TelegramSetupGuard;
|
|
@@ -348,7 +447,10 @@ interface TelegramCommandsAndToolsBindingDeps {
|
|
|
348
447
|
chatId: number,
|
|
349
448
|
replyToMessageId: number | undefined,
|
|
350
449
|
markdown: string,
|
|
351
|
-
options?: {
|
|
450
|
+
options?: {
|
|
451
|
+
replyMarkup?: unknown;
|
|
452
|
+
target?: { chatId: number; threadId?: number };
|
|
453
|
+
},
|
|
352
454
|
) => Promise<number | undefined>;
|
|
353
455
|
callMultipart: OutboundHandlers.TelegramVoiceReplySenderDeps["sendMultipart"];
|
|
354
456
|
getDefaultChatId: () => number | undefined;
|
|
@@ -362,6 +464,7 @@ interface TelegramCommandsAndToolsBindingDeps {
|
|
|
362
464
|
|
|
363
465
|
export function registerTelegramCommandsAndTools({
|
|
364
466
|
pi,
|
|
467
|
+
agentDir,
|
|
365
468
|
configStore,
|
|
366
469
|
persistConfig,
|
|
367
470
|
setup,
|
|
@@ -383,6 +486,13 @@ export function registerTelegramCommandsAndTools({
|
|
|
383
486
|
recordRuntimeEvent,
|
|
384
487
|
updateStatus,
|
|
385
488
|
}: TelegramCommandsAndToolsBindingDeps): void {
|
|
489
|
+
GenerativeApps.registerTelegramBindTool(pi, {
|
|
490
|
+
agentDir,
|
|
491
|
+
getActiveTurn: activeTurnRuntime.get,
|
|
492
|
+
planOutput: OutboundHandlers.createTelegramOutboundReplyPlanner(buttonActionStore),
|
|
493
|
+
sendMarkdownReply,
|
|
494
|
+
recordRuntimeEvent,
|
|
495
|
+
});
|
|
386
496
|
OutboundAttachments.registerTelegramOutboundAttachmentTool(pi, {
|
|
387
497
|
getActiveTurn: activeTurnRuntime.get,
|
|
388
498
|
getDefaultChatId,
|