@llblab/pi-telegram 0.36.0 → 0.36.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/BACKLOG.md +0 -1
- package/CHANGELOG.md +15 -1
- package/README.md +2 -2
- package/docs/README.md +1 -1
- package/docs/architecture.md +1 -1
- package/docs/compact-matrix-literal.md +104 -127
- package/docs/outbound.md +1 -1
- package/docs/public-api.md +1 -1
- package/docs/ui-style.md +9 -7
- package/lib/generative-app-worker.mjs +1 -0
- package/lib/generative-apps.ts +16 -18
- package/lib/menu-queue.ts +1 -1
- package/lib/menu-thinking.ts +7 -6
- package/lib/outbound-buttons.ts +14 -15
- package/lib/outbound-markup.ts +63 -8
- package/lib/updates.ts +30 -8
- package/package.json +1 -1
- package/skills/generated-control-surface/SKILL.md +5 -3
- package/skills/generative-apps/SKILL.md +5 -1
- package/skills/telegram-bridge/SKILL.md +4 -2
package/BACKLOG.md
CHANGED
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
_This file owns unresolved project work only. Completed behavior belongs in `CHANGELOG.md`; durable contracts belong in `AGENTS.md` and `/docs`._
|
|
4
4
|
|
|
5
5
|
- [ ] `Operator-confirmed pairing`: Replace first-contact automatic ownership with an explicit pending request that the trusted Pi interface can allow or reject before `allowedUserId` is persisted or any Telegram prompt executes; retain manual preconfiguration, bound pending-request lifetime and multiplicity, fail closed across session/transport replacement, and cover unauthorized-first-contact races.
|
|
6
|
-
- [ ] `Unauthorized denial consistency`: Align unauthorized Guest Mode answers and direct private-message replies so both begin with the same denial emoji and apply equivalent explicit bold emphasis to their denial copy through each surface's supported renderer; centralize the copy/style owner and protect both paths with focused regressions without pretending callback popups support rich text.
|
|
7
6
|
- [ ] `Environment-backed bot tokens`: Let each profile store an exact Pi-native `$ENV_VAR` or `${ENV_VAR}` reference in `telegram.json` instead of copying the resolved secret; preserve the originating alias when `/telegram-setup` prefills from a supported environment variable, resolve only at validation/activation boundaries, fail closed with a redacted named-variable diagnostic when unresolved, retain literal-token compatibility, and cover named profiles, reload, persistence, status, and secret-redaction paths.
|
|
8
7
|
- [ ] [`Inference bypass Generative Apps`](./docs/generative-apps.md): Let the agent install and bind one managed JavaScript app owner for a generated-prompt prefix so deterministic controls can bypass model inference without creating another button grammar.
|
|
9
8
|
- [ ] Harden the implemented `telegram_bind` installation/invocation kernel with removal operations, stronger cross-process replacement recovery, and bounded diagnostics while preserving lifecycle cancellation, worker-isolated methods, explicit staged replacement, canonical `<agent-dir>/genapps/<app>/<app>.mjs` identity, mandatory named `init`, no manifest/package metadata, non-symlink roots/sources, and fail-closed silent replacement.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,7 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
> Each release keeps at most 8 outcome records of at most 512 characters.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 0.36.2: Provider-Compatible Controls
|
|
6
|
+
|
|
7
|
+
- `Provider-Compatible Bind Schema`: Exposes `telegram_bind` through one top-level JSON object schema while retaining runtime enforcement of mutually exclusive install and invocation forms, avoiding providers that reject top-level union schemas before any tool call can run.
|
|
8
|
+
- `Reaction Shortcut Naming`: Removes obsolete reaction-action labels from setup guidance now that queue reaction semantics use their current naming.
|
|
9
|
+
- `Compact Thinking Picker`: Keeps `off` on a distinct full-width row and groups the six ordered reasoning intensities into two three-button rows, reducing menu height without changing thinking-level values or callbacks.
|
|
10
|
+
|
|
11
|
+
## 0.36.1: Adaptive Control Surfaces
|
|
12
|
+
|
|
13
|
+
- `Adaptive Button Literal`: Replaces binary JSON-or-CML parsing with one strict-JSON-first matrix grammar over a shared button AST. Named JSON objects and positional CML cells may coexist in one matrix or row, commas are optional only between completed elements, every compression step preserves semantics, and malformed comments reject atomically without registering partial callbacks.
|
|
14
|
+
- `Trusted Release Publication`: Makes tag releases depend on reusable full validation, exact tag/package/lock/changelog identity, npm Trusted Publisher provenance, idempotent existing-version checks, public package and tarball verification, and one workflow-owned GitHub Release path without npm tokens.
|
|
15
|
+
- `Consistent Access Denial`: Centralizes unauthorized copy so Guest Mode and private messages render `🚫 Access denied.` with equivalent explicit bold emphasis through HTML-capable surfaces, while callback popups use the same emoji and plain copy without pretending to support rich text.
|
|
16
|
+
- `Destructive Skip State`: Changes the active queue Skip selector from yellow to red and records the indicator in the UI registry, making deferred prompt removal visually distinct from Priority, Scoped, and other elevated but non-destructive states.
|
|
17
|
+
- `Worker Cancellation Fence`: Makes Generative App workers acknowledge child-process abort handling before parent termination, with a bounded forced fallback for synchronously blocked methods, preventing Linux CI load from orphaning a cancelled process long enough to commit its delayed side effect.
|
|
18
|
+
- `Capability App Routing`: Telegram interaction now prefers an already-advertised capability-owned Generative App for repeated controls before synthesizing one-shot prompt buttons, while the bridge remains generic and neither discovers capability-specific apps nor owns their state or identities.
|
|
19
|
+
- `Hybrid Surface Breadcrumbs`: Generated Control Surface and Generative Apps now cross-route over one logical button matrix and shared JSON/CML runtime notation: ephemeral agent composition stays separate from reusable scripts, while one app view may combine deterministic bound methods with ordinary model-mediated prompts and no third button Skill is introduced.
|
|
6
20
|
|
|
7
21
|
## 0.36.0: Generative Apps
|
|
8
22
|
|
package/README.md
CHANGED
|
@@ -86,7 +86,7 @@ Enable the optional capabilities the bridge needs in the [@BotFather](https://t.
|
|
|
86
86
|
|
|
87
87
|
1. Enable guest mode so the bot can answer mentions and replies in chats where it is not a member.
|
|
88
88
|
2. Enable private-chat Threaded Mode; when it is available, one live instance becomes the profile's leader and later visible Pi instances register as followers. Without it, the bridge stays in classic single-owner DM mode.
|
|
89
|
-
3. Make the bot an administrator in any chat where the queue reaction shortcuts
|
|
89
|
+
3. Make the bot an administrator in any chat where the queue reaction shortcuts should work. Reaction updates require admin rights, so the shortcuts silently do nothing in non-admin chats; private chats deliver reactions without admin rights.
|
|
90
90
|
|
|
91
91
|
## What It Feels Like
|
|
92
92
|
|
|
@@ -220,7 +220,7 @@ Voice notes, audio, images, PDFs, and other media can pass through configured in
|
|
|
220
220
|
|
|
221
221
|
### Buttons And Callbacks
|
|
222
222
|
|
|
223
|
-
Assistant replies can include top-level hidden `telegram_button` comments using a JSON object, JSON matrix, Compact Matrix Literal (CML), or compact double-quoted attributes; `telegram_buttons` is a plural alias.
|
|
223
|
+
Assistant replies can include top-level hidden `telegram_button` comments using a JSON object, adaptive JSON/CML matrix, positional Compact Matrix Literal (CML), or compact double-quoted attributes; `telegram_buttons` is a plural alias. One adaptive matrix may mix named JSON objects with positional CML cells, and commas between completed matrix or row elements are optional while JSON object internals remain strict. Top-level cells become full-width rows while nested rows group one or more buttons horizontally without an artificial parser-level width cap; generated surfaces default to five columns and use six to eight only for short position-bearing labels. CML uses `{value}`, `{label|prompt}`, or `{label|prompt|selected_style}` with `primary`, `success`, or `danger`; the optional style requires an explicit prompt. It trims atom boundaries, preserves non-structural text literally, and decodes only `\|`, `\}`, and `\\`. Prefer one matrix comment for multiple buttons. Buttons use `label` plus `prompt`, or the compact `value` key when both are identical. The action marker is colon-free for every payload form. The bridge strips the comments from visible text, renders inline buttons, and routes callbacks back into Pi as queued prompts or extension-owned callback actions. Button-only replies receive the standard `☑️ **Choose an option:**` heading as automatic visible fallback text. Once a generated prompt button is accepted, only that exact button switches to its optional `selected_style` (`primary` blue by default, `success` green, or `danger` red) without altering its agent-authored label or emoji; every style still queues the selected prompt.
|
|
224
224
|
|
|
225
225
|
### Threaded Mode And Multi-Instance Bus
|
|
226
226
|
|
package/docs/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Living index of project documentation in `/docs`.
|
|
|
15
15
|
- [command-templates.md](./command-templates.md) — Portable command-template standard core
|
|
16
16
|
- [inbound.md](./inbound.md) — Local `pi-telegram` inbound text/media handler bus, programmatic inbound handlers, registered STT provider fallbacks, legacy `attachmentHandlers` compatibility, placeholders, and fallbacks
|
|
17
17
|
- [outbound.md](./outbound.md) — Local `pi-telegram` outbound-handler config, text/voice/button behavior, single-artifact Rich results, voice synthesis provider fallback priority, artifact outputs, and callback routing
|
|
18
|
-
- [compact-matrix-literal.md](./compact-matrix-literal.md) —
|
|
18
|
+
- [compact-matrix-literal.md](./compact-matrix-literal.md) — Adaptive Button Literal / CML v3 standard for strict JSON objects, positional cells, optional element commas, mixed bounded-depth matrices, atomic parsing, and renderer-owned width policy
|
|
19
19
|
- [generative-apps.md](./generative-apps.md) — Generative Apps runtime and wire contract for managed `.mjs` identity, inference-bypass bindings, persistent state timelines, bounded adapters, replacement, and lifecycle; agent operation lives in the bundled `generative-apps` Skill
|
|
20
20
|
- [callback-namespaces.md](./callback-namespaces.md) — Shared Telegram `callback_data` namespace standard for layered extensions
|
|
21
21
|
- [updates.md](./updates.md) — Update classification and runtime handler registry that lets layered extensions observe and consume Telegram updates without owning their own polling connection
|
package/docs/architecture.md
CHANGED
|
@@ -344,7 +344,7 @@ Outbound files staged during an active Telegram turn are delivered after that tu
|
|
|
344
344
|
Assistant-authored final-message actions use hidden top-level comments with colon-free markers; colon-prefixed payloads are rejected:
|
|
345
345
|
|
|
346
346
|
- `telegram_voice` accepts a JSON object or double-quoted HTML-like attributes and creates voice reply artifacts through configured outbound handlers, programmatic voice handlers, or registered synthesis providers.
|
|
347
|
-
- `telegram_button` accepts a JSON object, JSON matrix, Compact Matrix Literal, or double-quoted attributes; `telegram_buttons` aliases the same parser. Each top-level
|
|
347
|
+
- `telegram_button` accepts a JSON object, adaptive JSON/CML matrix, positional Compact Matrix Literal, or double-quoted attributes; `telegram_buttons` aliases the same parser. Named JSON objects and positional cells may coexist, with commas optional only between completed matrix or row elements. Each top-level cell creates one full-width row, while a nested row groups buttons horizontally without an artificial parser-width cap; every callback enqueues its configured prompt text as a normal Telegram prompt turn. The JSON-first grammar, positional trim/escape rules, atomic rejection, and renderer-owned width policy are specified in [Adaptive Button Literal](./compact-matrix-literal.md).
|
|
348
348
|
|
|
349
349
|
Preview delivery strips top-level action comments before streaming draft Markdown. Comments inside code fences, quotes, lists, or indented examples stay literal.
|
|
350
350
|
|
|
@@ -1,94 +1,101 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Adaptive Button Literal
|
|
2
2
|
|
|
3
|
-
> Status: Portable
|
|
3
|
+
> Status: Portable CML v3 standard implemented by the unreleased `pi-telegram` control-surface parser.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Adaptive Button Literal is one bounded-depth matrix grammar over a shared button AST. It accepts strict JSON button objects, positional Compact Matrix Literal (CML) cells, or both in the same matrix and row. Commas between completed matrix or row elements are optional, so producers can progressively compress representation without changing runtime meaning.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
```text
|
|
8
|
+
full named JSON → comma-optional adjacency → mixed named/positional cells → compact CML
|
|
9
|
+
```
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
- Preserve ordered singleton and compact rows.
|
|
13
|
-
- Preserve non-structural Unicode text literally.
|
|
14
|
-
- Admit deterministic linear-time parsing without evaluation or recovery.
|
|
15
|
-
- Remain deterministic beside an existing JSON form.
|
|
16
|
-
- Fail closed on malformed or deeper structures.
|
|
11
|
+
The compact form is not JSON: `{label|prompt|variant}` assigns meaning by position. The formats share semantics and topology, not syntax.
|
|
17
12
|
|
|
18
|
-
##
|
|
13
|
+
## Goals
|
|
19
14
|
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
15
|
+
- Preserve one ordered button matrix across multiple representation densities.
|
|
16
|
+
- Let producers compress individual cells without converting the whole surface.
|
|
17
|
+
- Preserve valid strict JSON behavior unchanged.
|
|
18
|
+
- Admit deterministic linear-time parsing without evaluation or partial recovery.
|
|
19
|
+
- Keep rendering, callback ownership, and application state outside the notation.
|
|
24
20
|
|
|
25
21
|
## Data Model
|
|
26
22
|
|
|
27
|
-
|
|
23
|
+
Every accepted payload normalizes to a non-empty ordered list of non-empty rows:
|
|
28
24
|
|
|
29
25
|
```text
|
|
30
|
-
Cell = {
|
|
26
|
+
Cell = { label?: string, prompt?: string, value?: string, selected_style?: string }
|
|
31
27
|
Rows = Cell[][]
|
|
32
28
|
```
|
|
33
29
|
|
|
34
|
-
A top-level cell
|
|
30
|
+
A top-level cell becomes a singleton row. A nested row preserves horizontal grouping.
|
|
35
31
|
|
|
36
|
-
|
|
32
|
+
These representations are semantically equivalent:
|
|
37
33
|
|
|
38
34
|
```text
|
|
39
|
-
{
|
|
35
|
+
[[{"label":"Pause","prompt":"music::pause"},{"value":"Next"}],{"value":"Status"}]
|
|
36
|
+
[[{"label":"Pause","prompt":"music::pause"}{"value":"Next"}]{"value":"Status"}]
|
|
37
|
+
[[{"label":"Pause","prompt":"music::pause"},{Next}],{Status}]
|
|
38
|
+
[[{Pause|music::pause}{Next}]{Status}]
|
|
40
39
|
```
|
|
41
40
|
|
|
42
|
-
|
|
41
|
+
Named JSON objects and positional cells may coexist within the same horizontal row:
|
|
43
42
|
|
|
44
43
|
```text
|
|
45
|
-
{
|
|
44
|
+
[[{"label":"Open","prompt":"/tmp"}{Back|/}]]
|
|
46
45
|
```
|
|
47
46
|
|
|
48
|
-
|
|
47
|
+
## Positional Cells
|
|
48
|
+
|
|
49
|
+
A one-atom cell copies its value into label and prompt through the existing button contract:
|
|
49
50
|
|
|
50
51
|
```text
|
|
51
|
-
{
|
|
52
|
-
== { key: "Stop", value: "music-player::stop", variant: "danger" }
|
|
52
|
+
{Next}
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
A two-atom cell separates label and prompt:
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
{Pause|music::pause}
|
|
59
|
+
```
|
|
56
60
|
|
|
57
|
-
|
|
61
|
+
A three-atom cell adds the selected style:
|
|
58
62
|
|
|
59
63
|
```text
|
|
60
|
-
|
|
61
|
-
matrix := "[" ws element (ws element)* ws "]"
|
|
62
|
-
element := cell | row
|
|
63
|
-
row := "[" ws cell (ws cell)* ws "]"
|
|
64
|
-
cell := "{" atom "}"
|
|
65
|
-
| "{" atom "|" atom "}"
|
|
66
|
-
| "{" atom "|" atom "|" atom "}"
|
|
67
|
-
atom := atom-unit+
|
|
68
|
-
atom-unit := ordinary | "\\|" | "\\}" | "\\\\"
|
|
69
|
-
ws := *(SP | HTAB | CR | LF)
|
|
64
|
+
{Stop|music::stop|danger}
|
|
70
65
|
```
|
|
71
66
|
|
|
72
|
-
|
|
67
|
+
The style atom is accepted only as `primary`, `success`, or `danger`.
|
|
73
68
|
|
|
74
|
-
|
|
69
|
+
## Adaptive Grammar
|
|
70
|
+
|
|
71
|
+
The structural grammar is:
|
|
75
72
|
|
|
76
73
|
```text
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
[
|
|
81
|
-
|
|
82
|
-
|
|
74
|
+
payload := json-object | matrix | positional-cell
|
|
75
|
+
matrix := "[" ws element (boundary element)* ws "]"
|
|
76
|
+
element := cell | row
|
|
77
|
+
row := "[" ws cell (boundary cell)* ws "]"
|
|
78
|
+
cell := json-object | positional-cell
|
|
79
|
+
boundary := ws [","] ws
|
|
80
|
+
positional-cell := "{" atom "}"
|
|
81
|
+
| "{" atom "|" atom "}"
|
|
82
|
+
| "{" atom "|" atom "|" atom "}"
|
|
83
|
+
atom := atom-unit+
|
|
84
|
+
atom-unit := ordinary | "\|" | "\}" | "\\"
|
|
85
|
+
ws := *(SP | HTAB | CR | LF)
|
|
83
86
|
```
|
|
84
87
|
|
|
85
|
-
|
|
88
|
+
`boundary` occurs only after one complete element and before another. It may contain one comma or no comma. Element delimiters make empty adjacency unambiguous. Leading, repeated, and trailing commas are invalid.
|
|
89
|
+
|
|
90
|
+
A `json-object` is one complete strict JSON object. Its property commas, strings, escaping, nested values, and other internals remain ordinary strict JSON; comma optionality applies only between matrix or row elements.
|
|
91
|
+
|
|
92
|
+
Rows cannot contain rows. The grammar never recurses beyond one row inside the top-level matrix.
|
|
86
93
|
|
|
87
94
|
## Atoms, Whitespace, And Escapes
|
|
88
95
|
|
|
89
|
-
Leading and trailing whitespace in
|
|
96
|
+
Leading and trailing whitespace in positional atoms is trimmed. Internal ordinary spaces are preserved. CR, LF, HTAB, C0 controls, DEL, and C1 controls remaining inside an atom after trimming are invalid.
|
|
90
97
|
|
|
91
|
-
Only three
|
|
98
|
+
Only three positional-cell escapes exist:
|
|
92
99
|
|
|
93
100
|
```text
|
|
94
101
|
\| → literal |
|
|
@@ -96,104 +103,74 @@ Only three escape sequences exist:
|
|
|
96
103
|
\\ → literal \
|
|
97
104
|
```
|
|
98
105
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
Every other printable character is literal inside a cell, including:
|
|
102
|
-
|
|
103
|
-
```text
|
|
104
|
-
{ [ ] " : , / emoji and ordinary spaces
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
An opening `{` has no structural meaning after a cell has begun. Square brackets are structural only outside a cell. One or two unescaped vertical bars select the two- or three-atom form; a third is invalid. Every selected atom must remain non-empty after trimming. When multiline text or non-positional metadata is needed, the producer uses the embedding's JSON or other full-fidelity form.
|
|
108
|
-
|
|
109
|
-
## Width Policy
|
|
110
|
-
|
|
111
|
-
CML Core does not impose a visual row-width maximum. Width is a renderer and interaction-policy concern, not a property of the key-value matrix wire format.
|
|
112
|
-
|
|
113
|
-
An embedding may enforce a documented host limit. The `pi-telegram` parser does not add an artificial per-row width cap because Telegram Bot API does not document one and existing top-level matrices already admit host-bounded action counts. Its bundled Generated Control Surface Skill owns UX policy: five columns are the proven default for short position-bearing labels, six to eight may be used only when labels remain compact and readable, and wider surfaces should normally be regrouped.
|
|
106
|
+
Every other printable character is literal inside a positional cell, including commas, colons, quotes, square brackets, emoji, and ordinary spaces. A comma inside `{label|prompt}` is data; a comma after the closing `}` is an optional element separator.
|
|
114
107
|
|
|
115
|
-
## Parsing
|
|
108
|
+
## Deterministic Parsing
|
|
116
109
|
|
|
117
110
|
A conforming parser:
|
|
118
111
|
|
|
119
|
-
1.
|
|
120
|
-
2.
|
|
121
|
-
3.
|
|
122
|
-
4.
|
|
123
|
-
5.
|
|
124
|
-
6.
|
|
125
|
-
7.
|
|
126
|
-
8.
|
|
127
|
-
9.
|
|
112
|
+
1. Attempts strict JSON first for sources beginning with `{` or `[`. Successful JSON is validated only against the existing button matrix schema and never reinterpreted.
|
|
113
|
+
2. If strict JSON parsing fails, parses the original source with the adaptive grammar.
|
|
114
|
+
3. Tries one complete strict JSON object at each cell boundary before positional interpretation.
|
|
115
|
+
4. Accepts at most one optional comma between completed matrix or row elements.
|
|
116
|
+
5. Rejects leading, repeated, trailing, or property-level omitted commas.
|
|
117
|
+
6. Rejects empty atoms, matrices, rows, and nesting deeper than one row.
|
|
118
|
+
7. Decodes only `\|`, `\}`, and `\\` in positional cells.
|
|
119
|
+
8. Consumes exactly one complete payload and rejects trailing content.
|
|
120
|
+
9. Returns no partial rows or cells after any failure.
|
|
121
|
+
10. Runs in linear time over a host-bounded payload with fixed grammar depth.
|
|
128
122
|
|
|
129
|
-
|
|
123
|
+
Malformed JSON-looking input receives no generic recovery. It is accepted only if it independently forms a complete valid adaptive literal.
|
|
130
124
|
|
|
131
|
-
##
|
|
125
|
+
## Telegram Profile
|
|
132
126
|
|
|
133
|
-
|
|
127
|
+
For `telegram_button` and the exact `telegram_buttons` alias:
|
|
134
128
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
129
|
+
- JSON `value` keeps its existing label/prompt fallback semantics.
|
|
130
|
+
- Positional `{value}` is equivalent to JSON `{"value":"value"}`.
|
|
131
|
+
- Positional `{label|prompt}` is equivalent to JSON `{"label":"label","prompt":"prompt"}`.
|
|
132
|
+
- Positional `{label|prompt|selected_style}` is equivalent to the corresponding three-field JSON object.
|
|
133
|
+
- Top-level cells become full-width rows.
|
|
134
|
+
- Nested rows become horizontal keyboard rows.
|
|
135
|
+
- Invalid payloads are stripped with their recognized action comment and register no callbacks.
|
|
139
136
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
## `pi-telegram` Profile
|
|
143
|
-
|
|
144
|
-
For `telegram_button` and its exact `telegram_buttons` alias:
|
|
145
|
-
|
|
146
|
-
- `Cell.key` becomes the visible button label.
|
|
147
|
-
- `Cell.value` becomes the queued prompt.
|
|
148
|
-
- A top-level cell becomes one full-width inline-keyboard row.
|
|
149
|
-
- A nested row becomes one horizontal row.
|
|
150
|
-
- `{value}` is equivalent to JSON `{"value":"value"}`.
|
|
151
|
-
- `{label|prompt}` is equivalent to JSON `{"label":"label","prompt":"prompt"}`.
|
|
152
|
-
- `{label|prompt|selected_style}` is equivalent to JSON `{"label":"label","prompt":"prompt","selected_style":"selected_style"}`.
|
|
153
|
-
- The third atom is accepted only when the prompt atom is present and its exact value is `primary`, `success`, or `danger`.
|
|
154
|
-
- JSON and double-quoted attributes remain the full-fidelity forms for Generative App script output, multiline values, and other metadata.
|
|
155
|
-
- Invalid CML is stripped with its enclosing recognized action comment and registers no callbacks, matching existing fail-closed action behavior.
|
|
156
|
-
|
|
157
|
-
Example embedding:
|
|
137
|
+
Example:
|
|
158
138
|
|
|
159
139
|
```html
|
|
160
|
-
<!-- telegram_button [{⬆️ Up|/}[{⬅️|page-1}{➡️|page-3}]{📁 etc
|
|
140
|
+
<!-- telegram_button [{⬆️ Up|/},[{⬅️|page-1}{➡️|page-3}],{"label":"📁 etc","prompt":"/etc"}] -->
|
|
161
141
|
```
|
|
162
142
|
|
|
163
|
-
The enclosing HTML-comment transport
|
|
143
|
+
The enclosing HTML-comment transport owns its own delimiter boundary. Content containing the comment terminator must use another supported representation.
|
|
164
144
|
|
|
165
|
-
##
|
|
145
|
+
## Width Policy
|
|
146
|
+
|
|
147
|
+
The grammar imposes no visual row-width maximum. Renderer and interaction policy own width. The bundled Generated Control Surface Skill defaults to at most five short position-bearing controls per row, permits six to eight only when labels remain compact, and treats eight as the phone-width UX maximum.
|
|
166
148
|
|
|
167
|
-
|
|
149
|
+
## Conformance
|
|
168
150
|
|
|
169
|
-
|
|
151
|
+
Accepted classes include:
|
|
170
152
|
|
|
171
|
-
-
|
|
172
|
-
-
|
|
173
|
-
-
|
|
174
|
-
-
|
|
175
|
-
-
|
|
176
|
-
-
|
|
177
|
-
-
|
|
178
|
-
- Structural whitespace between tokens.
|
|
179
|
-
- Semantic equivalence with supported JSON cell forms.
|
|
153
|
+
- Strict JSON objects and matrices.
|
|
154
|
+
- Positional singleton, two-atom, and styled cells.
|
|
155
|
+
- Matrices and rows with commas, without commas, or a mixture of boundaries.
|
|
156
|
+
- Named JSON and positional cells mixed in one matrix or row.
|
|
157
|
+
- Literal commas inside positional atoms and strict JSON strings.
|
|
158
|
+
- Unicode, defined escapes, structural whitespace, and rows at supported renderer widths.
|
|
159
|
+
- Semantic equivalence across every progressive-compression step.
|
|
180
160
|
|
|
181
|
-
|
|
161
|
+
Rejected classes include:
|
|
182
162
|
|
|
183
|
-
- Empty
|
|
184
|
-
-
|
|
185
|
-
- Missing
|
|
186
|
-
-
|
|
187
|
-
-
|
|
188
|
-
-
|
|
189
|
-
-
|
|
190
|
-
-
|
|
191
|
-
- JSON that parses but fails the JSON action schema.
|
|
163
|
+
- Empty payloads, matrices, rows, labels, prompts, or style atoms.
|
|
164
|
+
- Leading, repeated, or trailing element commas.
|
|
165
|
+
- Missing commas between properties inside a JSON object.
|
|
166
|
+
- Deeper row nesting.
|
|
167
|
+
- Missing, crossed, or mismatched delimiters.
|
|
168
|
+
- A third positional separator, unknown style, unknown escape, or trailing backslash.
|
|
169
|
+
- Internal control characters and trailing garbage.
|
|
170
|
+
- Valid JSON that fails the existing JSON action schema.
|
|
192
171
|
|
|
193
172
|
Every rejected case proves zero callback registration.
|
|
194
173
|
|
|
195
174
|
## Versioning
|
|
196
175
|
|
|
197
|
-
This document defines CML
|
|
198
|
-
|
|
199
|
-
Future versions must not assign new meaning to input rejected by a security or ownership boundary without an explicit syntactic discriminator. Non-positional metadata and deeper structures require a revised standard rather than permissive recovery.
|
|
176
|
+
This document defines CML v3. V3 extends the v2 positional grammar with strict JSON object cells, mixed representation, and optional element-boundary commas. It does not make JSON object internals permissive and does not add deeper structures. Future versions must preserve strict-JSON-first routing, bounded depth, atomic rejection, and an explicit discriminator for any new meaning at a security or ownership boundary.
|
package/docs/outbound.md
CHANGED
|
@@ -164,7 +164,7 @@ 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}`, `{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.
|
|
167
|
+
- `telegram_button` accepts a JSON object, adaptive JSON/CML matrix, positional [Compact Matrix Literal](./compact-matrix-literal.md), or double-quoted HTML-like attributes; `telegram_buttons` is an exact plural alias. Named JSON objects and positional cells may coexist in one matrix or row, and commas are optional only between completed elements while JSON object internals remain strict. 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.
|
package/docs/public-api.md
CHANGED
|
@@ -66,7 +66,7 @@ This command surface is a mobile companion subset, not a raw terminal-command br
|
|
|
66
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.
|
|
67
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.
|
|
68
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.
|
|
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
|
|
69
|
+
- `telegram_button` hidden comments create inline buttons whose taps enqueue prompts. One marker accepts a JSON object, adaptive JSON/CML matrix, positional [Compact Matrix Literal](./compact-matrix-literal.md), or double-quoted attributes; `telegram_buttons` is an exact plural alias. Named JSON objects and positional cells may coexist in one matrix or row, and commas are optional only between completed elements. Top-level 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.
|
|
70
70
|
|
|
71
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.
|
|
72
72
|
|
package/docs/ui-style.md
CHANGED
|
@@ -62,6 +62,7 @@ Use emoji as stable semantic markers, not decoration. Emoji carry transportable
|
|
|
62
62
|
| --- | --- | --- | --- |
|
|
63
63
|
| `🟢` | Current/active/enabled `On` | Current option in vertical lists, active state rows, active `On` toggle | One strong current marker per option list. |
|
|
64
64
|
| `🟡` | Active `Off` or elevated/filter state | Active `Off` toggle, Priority/Scoped active tab | Yellow means intentionally not-normal or off/default-caution, not error. |
|
|
65
|
+
| `🔴` | Active destructive/deferred disposition | Active queue `Skip` selector | Red distinguishes a prompt that will be discarded at dispatch from reversible neutral or elevated state. |
|
|
65
66
|
| `🟣` | Normal/default active tab | Normal priority tab, All/default scope tab, active page picker | Use for neutral active tabs. |
|
|
66
67
|
| `⚫️` | Inactive placeholder | Inactive toggle values and inactive tabs | Keeps row width stable. |
|
|
67
68
|
| `⬆️` | Navigate upward | `⬆️ Main menu`, `⬆️ Back` | Always first row in submenus. |
|
|
@@ -73,7 +74,7 @@ Queue reactions are shortcut controls for waiting turns. Preserve their semantic
|
|
|
73
74
|
Queue item detail renders two independent selector rows:
|
|
74
75
|
|
|
75
76
|
- `🟡 Priority` / `⚫️ Normal` or `⚫️ Priority` / `🟣 Normal` selects the lane.
|
|
76
|
-
- `🟢 Keep` / `⚫️ Skip` or `⚫️ Keep` /
|
|
77
|
+
- `🟢 Keep` / `⚫️ Skip` or `⚫️ Keep` / `🔴 Skip` selects dispatch disposition.
|
|
77
78
|
|
|
78
79
|
The menu may clear internal Skip but cannot remove a reaction created by the user through Telegram's Bot API.
|
|
79
80
|
|
|
@@ -167,22 +168,23 @@ Examples:
|
|
|
167
168
|
- `⚫️ Priority` / `🟣 Normal`
|
|
168
169
|
- `1` / `🟣 2` / `3`
|
|
169
170
|
|
|
170
|
-
##
|
|
171
|
+
## Option Lists
|
|
171
172
|
|
|
172
|
-
|
|
173
|
+
Option lists choose one value from a fixed set, for example model selection, thinking level, voice reply mode, or time injection mode.
|
|
173
174
|
|
|
174
175
|
Rules:
|
|
175
176
|
|
|
176
|
-
- Put each option on its own row.
|
|
177
|
+
- Put each option on its own row when labels are long, the set may grow, or scanning benefits from full width.
|
|
178
|
+
- A fixed set of short, ordered peer values may use compact rows of up to three buttons.
|
|
179
|
+
- Keep a semantically distinct value such as thinking `off` on its own full-width row before grouped intensity values.
|
|
177
180
|
- Mark only the current value with `🟢`.
|
|
178
181
|
- Leave non-current values without emoji.
|
|
179
182
|
- Use lowercase labels when the option is a value.
|
|
180
183
|
|
|
181
184
|
Examples:
|
|
182
185
|
|
|
183
|
-
- `hidden`
|
|
184
|
-
- `🟢
|
|
185
|
-
- `always`
|
|
186
|
+
- Vertical: `hidden`, `🟢 mirror`, `always`.
|
|
187
|
+
- Thinking: full-width `off`, then `minimal` / `low` / `🟢 medium`, then `high` / `xhigh` / `max`.
|
|
186
188
|
|
|
187
189
|
## Generated Prompt Buttons
|
|
188
190
|
|
package/lib/generative-apps.ts
CHANGED
|
@@ -490,7 +490,7 @@ async function executeGenerativeAppWorker(options: {
|
|
|
490
490
|
if (settled || terminationError) return;
|
|
491
491
|
terminationError = error;
|
|
492
492
|
worker.postMessage({ type: "abort" });
|
|
493
|
-
const forcedTermination = setTimeout(terminateAndFinish,
|
|
493
|
+
const forcedTermination = setTimeout(terminateAndFinish, 250);
|
|
494
494
|
forcedTermination.unref?.();
|
|
495
495
|
};
|
|
496
496
|
const abort = (): void => {
|
|
@@ -507,9 +507,14 @@ async function executeGenerativeAppWorker(options: {
|
|
|
507
507
|
}, options.methodTimeoutMs);
|
|
508
508
|
timeout.unref?.();
|
|
509
509
|
options.execution?.signal.addEventListener("abort", abort, { once: true });
|
|
510
|
-
worker.
|
|
510
|
+
worker.on("message", (message: {
|
|
511
|
+
error?: string;
|
|
512
|
+
ok?: boolean;
|
|
513
|
+
result?: unknown;
|
|
514
|
+
type?: string;
|
|
515
|
+
}) => {
|
|
511
516
|
if (terminationError) {
|
|
512
|
-
terminateAndFinish();
|
|
517
|
+
if (message?.type === "abort-ack") terminateAndFinish();
|
|
513
518
|
} else if (message?.ok === true) {
|
|
514
519
|
finish(undefined, message.result);
|
|
515
520
|
void worker.terminate();
|
|
@@ -866,21 +871,14 @@ export function registerTelegramBindTool(
|
|
|
866
871
|
label: "Telegram Bind",
|
|
867
872
|
description:
|
|
868
873
|
"Install, explicitly replace, or invoke one named method on a managed Generative App; successful output displays directly in the active Telegram turn unless display is false.",
|
|
869
|
-
parameters: Type.
|
|
870
|
-
Type.
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
Type.Object({
|
|
878
|
-
app: Type.String(),
|
|
879
|
-
method: Type.String(),
|
|
880
|
-
display: Type.Optional(Type.Boolean()),
|
|
881
|
-
argument: Type.Optional(Type.Unknown()),
|
|
882
|
-
}, { additionalProperties: false }),
|
|
883
|
-
]),
|
|
874
|
+
parameters: Type.Object({
|
|
875
|
+
app: Type.String(),
|
|
876
|
+
script: Type.Optional(Type.String()),
|
|
877
|
+
method: Type.Optional(Type.String()),
|
|
878
|
+
replace: Type.Optional(Type.Boolean()),
|
|
879
|
+
display: Type.Optional(Type.Boolean()),
|
|
880
|
+
argument: Type.Optional(Type.Unknown()),
|
|
881
|
+
}, { additionalProperties: false }),
|
|
884
882
|
async execute(_toolCallId, params) {
|
|
885
883
|
try {
|
|
886
884
|
const result = await bindGenerativeApp({
|
package/lib/menu-queue.ts
CHANGED
|
@@ -192,7 +192,7 @@ function buildTelegramQueueItemSubmenuReplyMarkup(
|
|
|
192
192
|
callback_data: `queue:skip-set:${chatId}:${replyToMessageId}:keep`,
|
|
193
193
|
},
|
|
194
194
|
{
|
|
195
|
-
text: isSkipped ? "
|
|
195
|
+
text: isSkipped ? "🔴 Skip" : "⚫️ Skip",
|
|
196
196
|
callback_data: `queue:skip-set:${chatId}:${replyToMessageId}:skip`,
|
|
197
197
|
},
|
|
198
198
|
],
|
package/lib/menu-thinking.ts
CHANGED
|
@@ -128,13 +128,14 @@ export function buildThinkingMenuReplyMarkup(
|
|
|
128
128
|
currentThinkingLevel: ThinkingLevel,
|
|
129
129
|
): TelegramReplyMarkup {
|
|
130
130
|
const rows = [[{ text: "⬆️ Main menu", callback_data: "menu:back" }]];
|
|
131
|
+
const levelButtons = THINKING_LEVELS.map((level) => ({
|
|
132
|
+
text: level === currentThinkingLevel ? `🟢 ${level}` : level,
|
|
133
|
+
callback_data: `thinking:set:${level}`,
|
|
134
|
+
}));
|
|
131
135
|
rows.push(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
callback_data: `thinking:set:${level}`,
|
|
136
|
-
},
|
|
137
|
-
]),
|
|
136
|
+
levelButtons.slice(0, 1),
|
|
137
|
+
levelButtons.slice(1, 4),
|
|
138
|
+
levelButtons.slice(4, 7),
|
|
138
139
|
);
|
|
139
140
|
return { inline_keyboard: rows };
|
|
140
141
|
}
|
package/lib/outbound-buttons.ts
CHANGED
|
@@ -183,21 +183,20 @@ export function planTelegramButtonReply(
|
|
|
183
183
|
parseTopLevelTelegramComment(comment, candidate),
|
|
184
184
|
);
|
|
185
185
|
if (!command) return comment.raw;
|
|
186
|
-
const payloadRows = parseTelegramActionPayloadRows(comment, command)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
});
|
|
200
|
-
if (row.length > 0) keyboard.push(row);
|
|
186
|
+
const payloadRows = parseTelegramActionPayloadRows(comment, command);
|
|
187
|
+
if (!payloadRows) return "";
|
|
188
|
+
const actionRows = payloadRows.map((payloadRow) =>
|
|
189
|
+
payloadRow.map(parseTelegramButtonAction),
|
|
190
|
+
);
|
|
191
|
+
if (actionRows.some((row) => row.some((action) => !action))) return "";
|
|
192
|
+
for (const actionRow of actionRows) {
|
|
193
|
+
keyboard.push(actionRow.map((action) => ({
|
|
194
|
+
text: action!.text,
|
|
195
|
+
callback_data: deps.registerAction({
|
|
196
|
+
...action!,
|
|
197
|
+
...(deps.binding ? { binding: deps.binding } : {}),
|
|
198
|
+
}),
|
|
199
|
+
})));
|
|
201
200
|
}
|
|
202
201
|
return "";
|
|
203
202
|
});
|
package/lib/outbound-markup.ts
CHANGED
|
@@ -207,7 +207,7 @@ export function parseTelegramActionPayload(
|
|
|
207
207
|
|
|
208
208
|
const TELEGRAM_COMPACT_ACTION_CONTROL_PATTERN = /[\u0000-\u001f\u007f-\u009f]/u;
|
|
209
209
|
|
|
210
|
-
function
|
|
210
|
+
function parseTelegramAdaptiveActionPayloadRows(
|
|
211
211
|
source: string,
|
|
212
212
|
): Record<string, unknown>[][] | undefined {
|
|
213
213
|
let offset = 0;
|
|
@@ -219,13 +219,20 @@ function parseTelegramCompactActionPayloadRows(
|
|
|
219
219
|
const skipWhitespace = (): void => {
|
|
220
220
|
while (isStructuralWhitespace(source[offset])) offset += 1;
|
|
221
221
|
};
|
|
222
|
+
const consumeOptionalSeparator = (): boolean => {
|
|
223
|
+
skipWhitespace();
|
|
224
|
+
if (source[offset] !== ",") return true;
|
|
225
|
+
offset += 1;
|
|
226
|
+
skipWhitespace();
|
|
227
|
+
return source[offset] !== "," && source[offset] !== "]";
|
|
228
|
+
};
|
|
222
229
|
const normalizeAtom = (value: string): string | undefined => {
|
|
223
230
|
const normalized = value.trim();
|
|
224
231
|
return normalized && !TELEGRAM_COMPACT_ACTION_CONTROL_PATTERN.test(normalized)
|
|
225
232
|
? normalized
|
|
226
233
|
: undefined;
|
|
227
234
|
};
|
|
228
|
-
const
|
|
235
|
+
const parseCompactCell = (): Record<string, unknown> | undefined => {
|
|
229
236
|
if (source[offset] !== "{") return undefined;
|
|
230
237
|
offset += 1;
|
|
231
238
|
const atomSources: string[][] = [[]];
|
|
@@ -267,6 +274,54 @@ function parseTelegramCompactActionPayloadRows(
|
|
|
267
274
|
}
|
|
268
275
|
return undefined;
|
|
269
276
|
};
|
|
277
|
+
const parseJsonObjectCell = (): Record<string, unknown> | undefined => {
|
|
278
|
+
if (source[offset] !== "{") return undefined;
|
|
279
|
+
const start = offset;
|
|
280
|
+
const stack: string[] = [];
|
|
281
|
+
let inString = false;
|
|
282
|
+
let escaped = false;
|
|
283
|
+
for (let index = start; index < source.length; index += 1) {
|
|
284
|
+
const character = source[index]!;
|
|
285
|
+
if (inString) {
|
|
286
|
+
if (escaped) escaped = false;
|
|
287
|
+
else if (character === "\\") escaped = true;
|
|
288
|
+
else if (character === '"') inString = false;
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (character === '"') {
|
|
292
|
+
inString = true;
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (character === "{" || character === "[") {
|
|
296
|
+
stack.push(character);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (character !== "}" && character !== "]") continue;
|
|
300
|
+
const opening = stack.pop();
|
|
301
|
+
if (
|
|
302
|
+
(character === "}" && opening !== "{") ||
|
|
303
|
+
(character === "]" && opening !== "[")
|
|
304
|
+
) return undefined;
|
|
305
|
+
if (stack.length > 0) continue;
|
|
306
|
+
const candidate = source.slice(start, index + 1);
|
|
307
|
+
try {
|
|
308
|
+
const value: unknown = JSON.parse(candidate);
|
|
309
|
+
if (!isTelegramActionPayload(value)) return undefined;
|
|
310
|
+
offset = index + 1;
|
|
311
|
+
return value;
|
|
312
|
+
} catch {
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return undefined;
|
|
317
|
+
};
|
|
318
|
+
const parseCell = (): Record<string, unknown> | undefined => {
|
|
319
|
+
const start = offset;
|
|
320
|
+
const jsonCell = parseJsonObjectCell();
|
|
321
|
+
if (jsonCell) return jsonCell;
|
|
322
|
+
offset = start;
|
|
323
|
+
return parseCompactCell();
|
|
324
|
+
};
|
|
270
325
|
const parseRow = (): Record<string, unknown>[] | undefined => {
|
|
271
326
|
if (source[offset] !== "[") return undefined;
|
|
272
327
|
offset += 1;
|
|
@@ -281,6 +336,7 @@ function parseTelegramCompactActionPayloadRows(
|
|
|
281
336
|
const cell = parseCell();
|
|
282
337
|
if (!cell) return undefined;
|
|
283
338
|
row.push(cell);
|
|
339
|
+
if (!consumeOptionalSeparator()) return undefined;
|
|
284
340
|
}
|
|
285
341
|
return undefined;
|
|
286
342
|
};
|
|
@@ -299,15 +355,14 @@ function parseTelegramCompactActionPayloadRows(
|
|
|
299
355
|
const cell = parseCell();
|
|
300
356
|
if (!cell) return undefined;
|
|
301
357
|
rows.push([cell]);
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
-
if (character === "[") {
|
|
358
|
+
} else if (character === "[") {
|
|
305
359
|
const row = parseRow();
|
|
306
360
|
if (!row) return undefined;
|
|
307
361
|
rows.push(row);
|
|
308
|
-
|
|
362
|
+
} else {
|
|
363
|
+
return undefined;
|
|
309
364
|
}
|
|
310
|
-
return undefined;
|
|
365
|
+
if (!consumeOptionalSeparator()) return undefined;
|
|
311
366
|
}
|
|
312
367
|
return undefined;
|
|
313
368
|
};
|
|
@@ -353,7 +408,7 @@ export function parseTelegramActionPayloadRows(
|
|
|
353
408
|
}
|
|
354
409
|
return rows;
|
|
355
410
|
} catch {
|
|
356
|
-
return
|
|
411
|
+
return parseTelegramAdaptiveActionPayloadRows(payload.source);
|
|
357
412
|
}
|
|
358
413
|
}
|
|
359
414
|
if (payload.hasBody) return undefined;
|
package/lib/updates.ts
CHANGED
|
@@ -886,6 +886,19 @@ export type TelegramMessageOwnershipRecorder = (
|
|
|
886
886
|
input: TelegramMessageOwnershipRecorderInput,
|
|
887
887
|
) => void;
|
|
888
888
|
|
|
889
|
+
interface TelegramUnauthorizedReplyOptions {
|
|
890
|
+
parseMode?: "HTML";
|
|
891
|
+
target?: { chatId: number; threadId?: number };
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
const TELEGRAM_UNAUTHORIZED_DENIAL_COPY = "Access denied.";
|
|
895
|
+
|
|
896
|
+
function formatTelegramUnauthorizedDenial(format: "plain" | "html"): string {
|
|
897
|
+
return format === "html"
|
|
898
|
+
? `🚫 <b>${TELEGRAM_UNAUTHORIZED_DENIAL_COPY}</b>`
|
|
899
|
+
: `🚫 ${TELEGRAM_UNAUTHORIZED_DENIAL_COPY}`;
|
|
900
|
+
}
|
|
901
|
+
|
|
889
902
|
export interface TelegramUpdateRuntimeDeps<
|
|
890
903
|
TContext = unknown,
|
|
891
904
|
TReactionUpdate extends TelegramMessageReactionUpdated =
|
|
@@ -927,7 +940,11 @@ export interface TelegramUpdateRuntimeDeps<
|
|
|
927
940
|
callbackQueryId: string,
|
|
928
941
|
text?: string,
|
|
929
942
|
) => Promise<void>;
|
|
930
|
-
answerGuestQuery: (
|
|
943
|
+
answerGuestQuery: (
|
|
944
|
+
guestQueryId: string,
|
|
945
|
+
text?: string,
|
|
946
|
+
options?: Pick<TelegramUnauthorizedReplyOptions, "parseMode">,
|
|
947
|
+
) => Promise<void>;
|
|
931
948
|
handleAuthorizedTelegramCallbackQuery: (
|
|
932
949
|
query: TCallbackQuery,
|
|
933
950
|
ctx: TContext,
|
|
@@ -936,7 +953,7 @@ export interface TelegramUpdateRuntimeDeps<
|
|
|
936
953
|
chatId: number,
|
|
937
954
|
replyToMessageId: number,
|
|
938
955
|
text: string,
|
|
939
|
-
options?:
|
|
956
|
+
options?: TelegramUnauthorizedReplyOptions,
|
|
940
957
|
) => Promise<number | undefined>;
|
|
941
958
|
handleAuthorizedTelegramMessage: (
|
|
942
959
|
message: TMessage,
|
|
@@ -996,7 +1013,11 @@ export interface TelegramUpdateRuntimeControllerDeps<
|
|
|
996
1013
|
callbackQueryId: string,
|
|
997
1014
|
text?: string,
|
|
998
1015
|
) => Promise<void>;
|
|
999
|
-
answerGuestQuery: (
|
|
1016
|
+
answerGuestQuery: (
|
|
1017
|
+
guestQueryId: string,
|
|
1018
|
+
text?: string,
|
|
1019
|
+
options?: Pick<TelegramUnauthorizedReplyOptions, "parseMode">,
|
|
1020
|
+
) => Promise<void>;
|
|
1000
1021
|
handleAuthorizedTelegramCallbackQuery: (
|
|
1001
1022
|
query: TCallbackQuery,
|
|
1002
1023
|
ctx: TContext,
|
|
@@ -1005,7 +1026,7 @@ export interface TelegramUpdateRuntimeControllerDeps<
|
|
|
1005
1026
|
chatId: number,
|
|
1006
1027
|
replyToMessageId: number,
|
|
1007
1028
|
text: string,
|
|
1008
|
-
options?:
|
|
1029
|
+
options?: TelegramUnauthorizedReplyOptions,
|
|
1009
1030
|
) => Promise<number | undefined>;
|
|
1010
1031
|
handleAuthorizedTelegramMessage: (
|
|
1011
1032
|
message: TMessage,
|
|
@@ -1449,7 +1470,7 @@ export async function executeTelegramUpdatePlan<
|
|
|
1449
1470
|
assertExecutionCurrent();
|
|
1450
1471
|
await deps.answerCallbackQuery(
|
|
1451
1472
|
callbackQueryId,
|
|
1452
|
-
"
|
|
1473
|
+
formatTelegramUnauthorizedDenial("plain"),
|
|
1453
1474
|
);
|
|
1454
1475
|
}
|
|
1455
1476
|
return;
|
|
@@ -1464,7 +1485,8 @@ export async function executeTelegramUpdatePlan<
|
|
|
1464
1485
|
assertExecutionCurrent();
|
|
1465
1486
|
await deps.answerGuestQuery(
|
|
1466
1487
|
plan.guestMessage.guest_query_id,
|
|
1467
|
-
"
|
|
1488
|
+
formatTelegramUnauthorizedDenial("html"),
|
|
1489
|
+
{ parseMode: "HTML" },
|
|
1468
1490
|
);
|
|
1469
1491
|
return;
|
|
1470
1492
|
}
|
|
@@ -1584,8 +1606,8 @@ export async function executeTelegramUpdatePlan<
|
|
|
1584
1606
|
await deps.sendTextReply(
|
|
1585
1607
|
replyTarget.chatId,
|
|
1586
1608
|
replyTarget.messageId,
|
|
1587
|
-
"
|
|
1588
|
-
{ target: replyTarget },
|
|
1609
|
+
formatTelegramUnauthorizedDenial("html"),
|
|
1610
|
+
{ parseMode: "HTML", target: replyTarget },
|
|
1589
1611
|
);
|
|
1590
1612
|
}
|
|
1591
1613
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: generated-control-surface
|
|
3
|
-
description: Proactively compiles current state, available capabilities, and user intent into contextual, evidence-backed, ephemeral prompt-button interfaces. Use on Telegram turns and other prompt-button transports whenever controls materially shorten likely feedback, without requiring an explicit user request,
|
|
3
|
+
description: Proactively compiles current state, available capabilities, and user intent into contextual, evidence-backed, ephemeral prompt-button interfaces. Use on Telegram turns and other prompt-button transports whenever controls materially shorten likely feedback, without requiring an explicit user request; route reusable deterministic loops toward Generative Apps, omit decorative UI, and preserve domain ownership while fixed transport menus and callbacks remain with their runtime owners.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Generated Control Surface
|
|
@@ -25,7 +25,9 @@ The primitive belongs to the Surface plane: it projects State, exposes Agency ca
|
|
|
25
25
|
|
|
26
26
|
Use this Skill only to synthesize a state-derived prompt-button surface. Operating or modifying an existing Telegram bridge menu, callback interface, fixed frontend, runtime-owned control, or installed Generative App stays with that subsystem and does not route here merely because it contains buttons.
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
This Skill and `generative-apps` share one logical button-matrix and `label + prompt` interaction model while retaining different execution owners. On Telegram, the bridge runtime owns the full JSON/CML action notation: this Skill emits an ephemeral surface from current context, while a Generative App returns reusable views through its script ABI. Shared rendering does not justify a third button Skill or move transport grammar into either interaction Skill.
|
|
29
|
+
|
|
30
|
+
When a generated surface reveals repeated stable interaction with bounded state and deterministic transitions, load the complementary `generative-apps` Skill and consider compiling that loop. A Generative App may retain ordinary model-mediated prompt buttons beside deterministic bound methods, so only the stable actions need bypass inference. Conversely, keep one-off, interpretive, changing, and context-heavy interaction here when a reusable script would add no concrete latency, token, cost, reliability, or UX value.
|
|
29
31
|
|
|
30
32
|
On Telegram turns, evaluate this Skill proactively rather than waiting for the user to ask for buttons. Load and apply it when a likely next decision, approval, navigation step, inspection, or bounded action can be made materially easier through controls; its correct output may still contain zero buttons when the admission test fails.
|
|
31
33
|
|
|
@@ -122,7 +124,7 @@ Re-check mutable targets immediately before execution. Access denial never autho
|
|
|
122
124
|
|
|
123
125
|
## Prompt Buttons
|
|
124
126
|
|
|
125
|
-
Use the transport's canonical prompt-button syntax. For pi-telegram, one top-level `telegram_button` comment accepts one JSON object, double-quoted attributes,
|
|
127
|
+
Use the transport's canonical prompt-button syntax. For pi-telegram, one top-level `telegram_button` comment accepts one JSON object, double-quoted attributes, an adaptive JSON/CML matrix, or positional Compact Matrix Literal (CML). One matrix or row may mix named JSON objects with positional cells, and commas are optional only between completed elements while JSON object internals remain strict. CML uses `{value}`, `{label|prompt}`, or `{label|prompt|selected_style}`; the optional third atom requires an explicit prompt and accepts only `primary`, `success`, or `danger`. It trims atom boundaries, preserves other printable text literally, and decodes only `\|`, `\}`, and `\\`. Prefer CML whenever the model authors the control and it can express the required surface; fall back to expanded JSON only for multiline prompts, non-positional metadata, or a concrete CML parse/render failure, never merely from implementation habit. Deterministic Generative App scripts may return ordinary JSON because their source payload does not consume model-output tokens; author and operate those adapters through the `generative-apps` Skill rather than growing a parallel app workflow here. A top-level cell becomes one full-width row, while a nested row groups one or more controls horizontally without a parser-level width cap. Prefer one layout comment for multiple controls instead of repeating the marker; `telegram_buttons` is a plural alias, not a different format.
|
|
126
128
|
|
|
127
129
|
### Semantic Row Composition
|
|
128
130
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: generative-apps
|
|
3
|
-
description: Design, author, review, install, replace, invoke, or debug reusable Generative Apps that combine generated button interfaces, deterministic bound methods, and optional model-mediated prompts. Use for standalone applications and bounded view/controller adapters rendered through pi-telegram.
|
|
3
|
+
description: Design, author, review, install, replace, invoke, or debug reusable Generative Apps that combine generated button interfaces, deterministic bound methods, and optional model-mediated prompts. Use for standalone applications and bounded view/controller adapters rendered through pi-telegram; prefer Generated Control Surface when no reusable deterministic program is earned.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Generative Apps
|
|
@@ -29,6 +29,10 @@ Generated Control Surface → current context → model → one ephemeral surfac
|
|
|
29
29
|
Generative App → model → reusable program → many evolving surfaces
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
Both Skills use the same logical button matrix and `label + prompt` interaction model. The Telegram runtime owns its full JSON/CML notation and callback routing; this Skill owns reusable program judgment, while `generated-control-surface` owns ephemeral agent-authored composition. Shared rendering needs no third button Skill and does not collapse those responsibilities.
|
|
33
|
+
|
|
34
|
+
An app may mix deterministic `app::method` controls and ordinary prompts in one view. Compile only the stable transitions that benefit from inference bypass; keep explanation, interpretation, teaching, and adaptation on the model-mediated plane. When no reusable state or deterministic loop earns a script, load and use `generated-control-surface` instead.
|
|
35
|
+
|
|
32
36
|
The `generated` / `generative` distinction is intentional. Do not rename `generated-control-surface` to a competing generative term.
|
|
33
37
|
|
|
34
38
|
## Ownership
|
|
@@ -46,7 +46,9 @@ Use `telegram_attach` outside Telegram turns only when the user explicitly reque
|
|
|
46
46
|
|
|
47
47
|
## Assistant-Authored Actions
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Before compiling assistant-authored controls, inspect already-loaded capability guidance for an advertised maintained Generative App or view/controller adapter. When that owner-provided view exists and the current intent concerns repeated interaction, load and follow the bundled `generative-apps` Skill and prefer binding or invoking the existing app over synthesizing one-shot prompt buttons. This is a routing breadcrumb, not permission for the bridge to discover capability-specific apps, own their state, or hard-code their identities.
|
|
50
|
+
|
|
51
|
+
Otherwise, on Telegram turns, proactively load `generated-control-surface` when a likely next decision or action may benefit from prompt buttons; do not wait for an explicit button request, and accept zero controls when its admission rules reject decorative or low-value UI.
|
|
50
52
|
|
|
51
53
|
`telegram_button` and `telegram_voice` are hidden top-level HTML comments, not tools. Emit them at column zero, outside lists, quotes, code blocks, and indentation.
|
|
52
54
|
|
|
@@ -60,7 +62,7 @@ Button forms:
|
|
|
60
62
|
<!-- telegram_button [{⬆️ Up|/}[{⬅️|page-1}{➡️|page-3}]{📁 etc|/etc}] -->
|
|
61
63
|
```
|
|
62
64
|
|
|
63
|
-
- `telegram_button` accepts one JSON object,
|
|
65
|
+
- `telegram_button` accepts one JSON object, an adaptive JSON/CML matrix, positional Compact Matrix Literal (CML), or double-quoted attributes; `telegram_buttons` is an exact plural alias. One matrix or row may mix named JSON objects with positional cells, and commas are optional only between completed elements while JSON object internals remain strict. CML uses `{value}`, `{label|prompt}`, or `{label|prompt|selected_style}`; the optional third atom requires an explicit prompt and accepts only `primary`, `success`, or `danger`. It trims atom boundaries, preserves other printable text literally, and decodes only `\|`, `\}`, and `\\`. Prefer CML for model-authored controls whenever it can express the required surface; use expanded JSON only for multiline prompts, non-positional metadata, or a concrete parse/render failure fallback, never merely from implementation habit. Deterministic Generative App scripts may use ordinary JSON because their output does not spend model tokens. In a matrix, each top-level cell becomes a full-width row and each nested row groups one or more buttons horizontally without a parser-level width cap. Prefer one matrix comment for multiple buttons, normally keep generated rows at five columns or fewer, and use six through eight only for short position-bearing labels. Keep the complete action in one top-level comment and encode multiline content with JSON `\n`.
|
|
64
66
|
- Use `label` plus a self-contained `prompt`, or non-empty `value` when both are identical.
|
|
65
67
|
- Optional `selected_style` is `primary` (default), `success`, or `danger`; style never suppresses prompt admission.
|
|
66
68
|
- If button comments form the whole reply, the bridge supplies the standard choice heading.
|