@llblab/pi-telegram 0.27.11 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +150 -258
- package/BACKLOG.md +1 -169
- package/CHANGELOG.md +396 -441
- package/README.md +9 -6
- package/api/updates.ts +5 -0
- package/docs/architecture.md +71 -26
- package/docs/multi-instance-bus.md +23 -5
- package/docs/public-api.md +7 -6
- package/docs/ui-style.md +6 -6
- package/docs/updates.md +29 -11
- package/index.ts +356 -246
- package/lib/activity-verbosity.ts +26 -0
- package/lib/bindings.ts +240 -2
- package/lib/bus-follower.ts +436 -238
- package/lib/bus-leader.ts +395 -42
- package/lib/bus.ts +994 -153
- package/lib/commands.ts +184 -30
- package/lib/config.ts +23 -2
- package/lib/journal.ts +3140 -0
- package/lib/lifecycle.ts +4 -0
- package/lib/locks.ts +21 -21
- package/lib/media.ts +71 -32
- package/lib/menu-queue.ts +31 -17
- package/lib/menu.ts +5 -3
- package/lib/model.ts +51 -24
- package/lib/ownership.ts +42 -7
- package/lib/paths.ts +35 -0
- package/lib/polling.ts +591 -106
- package/lib/prompts.ts +17 -0
- package/lib/queue.ts +732 -143
- package/lib/routing.ts +291 -64
- package/lib/runtime.ts +26 -10
- package/lib/status.ts +257 -18
- package/lib/sync.ts +131 -5
- package/lib/telegram-api.ts +41 -11
- package/lib/text-groups.ts +75 -35
- package/lib/threads.ts +112 -4
- package/lib/turns.ts +79 -14
- package/lib/updates.ts +3771 -223
- package/package.json +3 -3
- package/scripts/check-downgrade.mjs +435 -0
package/AGENTS.md
CHANGED
|
@@ -2,261 +2,153 @@
|
|
|
2
2
|
|
|
3
3
|
## 0. Meta-Protocol Principles
|
|
4
4
|
|
|
5
|
-
- `
|
|
6
|
-
- `
|
|
7
|
-
- `
|
|
8
|
-
- `
|
|
9
|
-
- `
|
|
10
|
-
- `
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- `
|
|
23
|
-
- `
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
- `/
|
|
42
|
-
- `/
|
|
43
|
-
- `/
|
|
44
|
-
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
- `
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
-
|
|
55
|
-
- `
|
|
56
|
-
-
|
|
57
|
-
- `
|
|
58
|
-
- `
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
- `
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
-
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
-
|
|
91
|
-
-
|
|
92
|
-
-
|
|
93
|
-
-
|
|
94
|
-
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
-
|
|
101
|
-
-
|
|
102
|
-
-
|
|
103
|
-
-
|
|
104
|
-
-
|
|
105
|
-
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
-
|
|
110
|
-
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
-
|
|
115
|
-
-
|
|
116
|
-
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
-
|
|
123
|
-
-
|
|
124
|
-
-
|
|
125
|
-
-
|
|
126
|
-
-
|
|
127
|
-
-
|
|
128
|
-
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
-
|
|
154
|
-
-
|
|
155
|
-
- Keep push/PR CI on minimum Node `22.19.0` across Ubuntu, macOS, and Windows with typecheck, tests, and package dry-run on every OS; run the package-owned dependency audit once on Ubuntu, require the native Windows named-pipe roundtrip to execute, and keep Unix-only skips explicit by protected boundary. The release audit omits peer dependencies because Pi is the host-selected runtime and its shrinkwrapped graph is not shipped by this extension; keep a separate full installed-host audit command for visibility, and never use peer omission to hide a dependency owned or bundled by `pi-telegram`.
|
|
156
|
-
- Route important runtime failures through the recent runtime event recorder so `/telegram-status` remains useful for post-mortem debugging. Keep the compact TUI status bar bounded to the generic `error` state; detailed error messages belong only in diagnostics/logs, never inline beside the status label.
|
|
157
|
-
- Treat remaining Markdown-to-HTML rendering as Telegram UI/compat output work, not generic Markdown rendering or assistant reply delivery
|
|
158
|
-
- Preserve literal code content in Telegram rendering
|
|
159
|
-
- Avoid HTML chunk splits that break tags
|
|
160
|
-
- Prefer width-efficient monospace table and list formatting for narrow clients, with table padding based on grapheme/display width rather than raw UTF-16 length where possible
|
|
161
|
-
- Flatten nested Markdown quotes into indented single-blockquote output because Telegram does not render nested blockquotes reliably
|
|
162
|
-
|
|
163
|
-
## 6.4 File And Naming Style
|
|
164
|
-
|
|
165
|
-
- Keep comments and user-facing docs in English unless the surrounding file already follows another convention
|
|
166
|
-
- Each project `.ts` file should start with a short multi-line responsibility header comment that explains the file boundary to future maintainers; source-module headers must include `Zones:` tags for cross-cutting responsibility areas
|
|
167
|
-
- Name extracted `/lib` modules and mirrored `/tests` suites by bare domain when the repository already supplies the Telegram scope; prefer `queue.ts`, `updates.ts`, and `queue.test.ts` over redundant `telegram-*` filename prefixes. Exception: the concrete Bot API transport domain is named `telegram-api.ts` / `telegram-api.test.ts` to avoid ambiguity with the public `/api/*.ts` package membranes
|
|
168
|
-
- Keep test helpers with the mirrored domain suite by default because test files mirror module-domain boundaries; introduce shared `tests/fixtures` only when multiple domain suites truly reuse the same setup. A `tests/<domain>.test.ts` file should primarily protect `lib/<domain>.ts`; this includes `tests/runtime.test.ts`, which should protect `lib/runtime.ts` rather than act as a general integration bucket. Cross-domain runtime flows belong in the single shared `tests/integration.test.ts` suite. Do not create narrow `*.integration.test.ts` files unless a future repeated setup boundary becomes large enough to justify splitting the one shared integration suite
|
|
169
|
-
- Prefer targeted edits, keeping `index.ts` as the orchestration layer and moving reusable logic into flat `/lib` domain modules when a subsystem becomes large enough to earn extraction
|
|
170
|
-
- Do not atomize cohesive domains just because a file is large. A 1k+ line module can remain healthy when it owns one coherent mechanism and its subparts change together; extract only when there is real pressure to isolate, substitute, independently test, independently mutate, break cycles, or reduce repeated cross-domain coupling.
|
|
171
|
-
- Keep composition wiring DRY with small local adapters or owning-domain contracts when repetition appears, but do not hide live mutable session state behind broad facades just to reduce repeated closures
|
|
172
|
-
- Keep interface contracts consistent for the same runtime entity: prefer the owning domain's exported contract when multiple modules mean the same entity, and use local structural `*Like`/view contracts only for deliberate narrow projections that avoid real coupling without duplicating source-of-truth shapes
|
|
173
|
-
|
|
174
|
-
## 6.5 Current Domain Ownership Snapshot
|
|
175
|
-
|
|
176
|
-
The canonical detailed ownership map lives in [`docs/architecture.md`](./docs/architecture.md). Keep this section as a compact agent-facing index, not a second copy of the full map.
|
|
177
|
-
|
|
178
|
-
- Scheduling and lifecycle: `queue`, `runtime`, `lifecycle`, `locks`
|
|
179
|
-
- Telegram transport, polling, bus, sync, and inbound flow: `api`, `polling`, `bus`, `bus-api`, `bus-leader`, `bus-follower`, `ownership`, `target`, `sync`, `thread-reconciler`, `threads`, `updates`, `routing`, `media`, `turns`, `inbound`, `config`, `setup`
|
|
180
|
-
- Response surfaces: `preview`, `replies`, `rendering`, `keyboard`, `delivery`, `activity`, `outbound-markup`, `outbound-attachments`, `outbound`, `voice`, `status`
|
|
181
|
-
- Controls and application menu UI: `commands`, `menu`, `menu-model`, `menu-thinking`, `menu-status`, `menu-queue`, `model`, `prompts`
|
|
182
|
-
- Extension platform: `sections` owns section registry, token mapping, callback dispatch, context building, and its globalThis bridge; `delivery` owns target-aware extension views, logical handles, target policy, ordering, lifecycle fencing, and its globalThis runtime membrane; `activity` owns normalized lifecycle registration, activity/source identity, non-blocking dispatch, delivery contexts, and its globalThis runtime membrane; `voice` owns the voice-provider registry and its globalThis bridge
|
|
183
|
-
- Pi SDK boundary: `pi` owns direct pi imports and bound extension API ports; `bindings` owns pi-facing command/tool/lifecycle registration wiring extracted from the entrypoint
|
|
184
|
-
|
|
185
|
-
## 6.6 Entrypoint And Import Boundaries
|
|
186
|
-
|
|
187
|
-
- Keep the preview domain as a thin streaming lifecycle controller only: draft ids, safe-prefix selection for `sendRichMessageDraft`, voice suppression, serialized flushes, diagnostics, and finalization state. Do not reintroduce assistant preview rendering there; keep `rendering.ts` scoped to bridge-owned UI/compat regular-message rendering rather than assistant or guest Markdown delivery. Drafts must only send structurally closed Markdown prefixes; draft failures are not proof that drafts are globally unsupported, so record the failure and skip that preview frame. Do not add raw plain-message fallback previews for assistant Markdown
|
|
188
|
-
- Preview/final delivery ordering is release-critical: finalization waits for active preview flushes, persisted final delivery should not be followed by a post-final draft-clear call that creates transient draft UI, and regressions should cover in-flight draft flush serialization plus final reply ordering.
|
|
189
|
-
- Live Rich Draft observation: `sendMessageDraft(..., undefined)` after a persisted final Rich Message can appear in Telegram clients as a separate animated three-dot draft block before dissolving. Do not use post-final draft-clear or pre-final draft-clear for assistant finalization; let the persisted final `sendRichMessage` replace/complete the user-visible lifecycle and reset local preview state only.
|
|
190
|
-
- Remove unused code immediately when discovered. Do not keep dormant config knobs, disabled policy branches, speculative fallback paths, or "maybe later" implementation hooks in the product path; reintroduce them only with an active caller, tests, and live evidence.
|
|
191
|
-
- Keep Telegram prompt guidance compact and operational. Do not add format-specific steering for native Rich Markdown features unless the model needs a real bridge-specific rule such as formula delimiters or hidden outbound action syntax.
|
|
192
|
-
- Keep direct `node:*` file-operation dependencies out of `index.ts` when an owning domain exists; the entrypoint should compose ports while domains own local filesystem details such as temp-dir preparation, attachment stats, and turn image reads
|
|
193
|
-
- In `index.ts`, prefer namespace imports for local bridge domains so orchestration reads as domain-scoped calls such as `Queue.*`, `Turns.*`, and `Rendering.*` instead of long flat import lists
|
|
194
|
-
- Keep the local `index.ts` plus `/lib/*.ts` import graph acyclic; `tests/invariants.test.ts` guards this boundary plus shared-bucket bans, empty interface-extension shell regressions, pi SDK centralization, source-only entrypoint Node-runtime/local-adapter/process/direct-pi access avoidance, runtime-domain isolation, structural leaf-domain import isolation, menu/model boundary drift, Telegram API/config default coupling, structural update/media coupling to Telegram API transport shapes, and attachment coupling to queue/inbound media/Telegram API helpers as domains keep evolving
|
|
195
|
-
- Do not reintroduce shared bucket domains such as `lib/constants.ts`, `lib/types.ts`, `lib/globals.ts`, or broad global-augmentation files; constants, registry keys, state interfaces, and concrete transport shapes should stay in their owning domains, and `index.ts` should not grow new shared magic constants
|
|
196
|
-
- Keep remaining `index.ts` code focused on cross-domain adapter wiring that needs live extension state, pi callbacks, Telegram API ports, or status updates; do not extract one-off closures solely to reduce line count
|
|
197
|
-
- Treat every meaningful `index.ts` change as a composition-pressure checkpoint: if the touched cluster owns domain-specific mutable state, identity, lifecycle, fallback policy, or a testable port boundary, move that cluster into its owning domain in the same release when safe. Keep inline wiring only when extraction would merely relocate one-off adapters or hide cross-domain state behind a broad dependency bag
|
|
198
|
-
- Domain-specific queue planning, preview transport/controller behavior, UI/compat rendering, Telegram API transport, menu state, and command behavior should stay in their owning domains instead of moving to `/lib/runtime.ts` solely to shrink `index.ts`
|
|
199
|
-
- Prefer narrow structural runtime ports in domains that only store or route pi-compatible values; direct pi SDK/model imports should stay centralized in `/lib/pi.ts`, while domains that actively register pi hooks/tools/commands should consume those concrete contracts through the adapter
|
|
200
|
-
|
|
201
|
-
## 7. Operational Conventions
|
|
202
|
-
|
|
203
|
-
- When Telegram-visible behavior changes, sync `README.md` and the relevant `/docs` entry in the same pass
|
|
204
|
-
- Dead-code source of truth is real reachability from composition roots, package exports, tests, registered commands/tools/hooks, and documented public APIs — not whether code is implemented or recently useful. Proactively remove unused mechanisms when their only evidence is historical intent. Treat `tsc --noUnusedLocals --noUnusedParameters` as the diagnostic lead source, then whitelist only deliberate exported public contracts or test fixtures with documented reachability.
|
|
205
|
-
- When durable runtime constraints or repeat bug patterns emerge, record them here instead of burying them in changelog prose
|
|
206
|
-
- When fork identity changes, keep `README.md`, package metadata, and docs aligned so the published package does not point back at stale upstream coordinates
|
|
207
|
-
- README positioning should emphasize `/start` as the primary Telegram operator menu and keep reaction shortcuts secondary. Reactions are useful queue affordances, but menu controls are the core CLI-to-Telegram bridge.
|
|
208
|
-
- Document configuration knobs without UI in the root README when they affect bootstrap, networking, transport limits, or prompt context; currently this includes token env bootstrap, Node env proxy mode, Telegram network-family policy, inbound/outbound size limits, and `time`.
|
|
209
|
-
- Keep extension-local standards self-contained: shared patterns such as command templates may evolve independently in multiple extensions, but pi-telegram docs/changelog should describe the standard without naming sibling extension implementations as dependencies or authorities.
|
|
210
|
-
- Work only inside this repository during development tasks; updating the installed Pi extension checkout is a separate manual operator step, not part of normal in-repo implementation work
|
|
211
|
-
|
|
212
|
-
## 8. Integration Protocols
|
|
213
|
-
|
|
214
|
-
- Telegram API methods currently used include polling, message editing, draft streaming, callback queries, reactions, file download, and media upload endpoints
|
|
215
|
-
- Pi integration depends on lifecycle hooks such as `before_agent_start`, `agent_start`, `message_start`, `message_update`, and `agent_end`
|
|
216
|
-
- `ctx.ui.input()` provides placeholder text rather than an editable prefilled value; when a real default must appear already filled in, prefer `ctx.ui.editor()`
|
|
217
|
-
- For `/telegram-setup`, prefer the locally saved bot token over environment variables on repeat setup runs; env vars are the bootstrap path when no local token exists, and persisted `telegram.json` writes must remain atomic plus private because status/setup/polling paths may read it concurrently; keep same-parent temp-file replacement and retry only bounded transient destination-contention errors (`EPERM`, `EACCES`, `EBUSY`) rather than deleting the live config or weakening transaction serialization
|
|
218
|
-
- Settings value descriptions must follow their visible chooser order (rows top-to-bottom, shared-row values left-to-right); `(default)` annotates the actual default in place and never reorders the list. Command help plus prompt-template commands and status/model/thinking/queue controls are driven through `/start`'s Telegram inline application menu and callback queries; the Queue button shows the queued-item count, model-menu scope/pagination controls stay at the top under Main menu, the model pagination indicator opens a compact page picker, and thinking-menu text stays a compact heading because the current level is marked by button state; `/status`, `/model`, `/thinking`, and `/queue` are hidden compatibility shortcuts
|
|
219
|
-
- Shared inline-keyboard structure belongs to `keyboard`; application-control button labels, callback data, and callback behavior stay in `menu`/`menu-model`/`menu-thinking`/`menu-status`/`menu-queue` while core queue mechanics stay in `queue`
|
|
220
|
-
- Telegram `/settings` options should open nested detail submenus by default: boolean options show a description plus Back, `on`, and `off`; list options show Back plus selectable values. One-shot actions such as syncing may run directly without a submenu when there is no meaningful choice or description step.
|
|
221
|
-
- Inline UI labels and dialogs follow [`docs/ui-style.md`](./docs/ui-style.md): action buttons use emoji plus capitalized action text, state & navigation buttons show state and lead to a submenu, first-level submenus start with `⬆️ Main menu` while deeper submenus start with `⬆️ Back`, boolean toggles use Capitalized horizontal `On`/`Off` with green active `On`, yellow active `Off`, and black inactive dots, tabs use capitalized labels with purple default-state and yellow elevated-state active dots and black inactive dots, vertical option lists mark only the current value green, accepted generated prompt buttons apply Telegram's green `success` style to the exact selected choice without changing its label, and confirmation dialogs use a single bold text-only question with emoji on buttons only.
|
|
222
|
-
- Inbound text/media may be transformed through configured `inboundHandlers` before queueing; legacy `attachmentHandlers` are deprecated compatibility aliases appended after `inboundHandlers`; active-turn outbound files must flow through `telegram_attach`, while explicit local/TUI Telegram sends use `telegram_attach` for files or `telegram_message` for text/buttons
|
|
223
|
-
- Long Telegram text split recovery belongs to `text-groups`: keep it conservative, short-debounced, same chat/user/message-id contiguous, and gated by near-limit human text so normal rapid follow-ups and slash commands stay separate
|
|
224
|
-
- Public API boundaries live in [`docs/public-api.md`](./docs/public-api.md): companion extensions must use public API domain subpaths such as `@llblab/pi-telegram/sections`, `/delivery`, `/voice`, `/inbound`, `/outbound`, and `/updates`; `0.12.0` intentionally removes the published `@llblab/pi-telegram/lib/*.ts` compatibility wildcard
|
|
225
|
-
- `Companion` names the extension consumer/product relationship, not a core domain responsibility. Source headers, Zones, module names, public API names, and domain docs should use the owned capability (`delivery`, `activity`, `sections`, `voice`, or extension interop); reserve companion terminology for the external extension that composes those APIs.
|
|
226
|
-
- Public handler API matrix: low-level buses use `registerTelegramUpdateHandler(handler)`, `registerTelegramInboundHandler(kind, handler)`, and `registerTelegramOutboundHandler(kind, handler)` without ids; high-level surfaces use stable identity (`registerTelegramSection({ id, ... })`, `registerTelegramVoiceTranscriptionProvider(provider, { id })`, and `registerTelegramVoiceSynthesisProvider(provider, { id })`). Imperative `/delivery` operations resolve the current runtime on every call and use generation-bound logical handles rather than registration ids or captured Pi contexts. Inbound handlers and command-backed outbound handlers use command templates as the standard config contract; built-in outbound buttons use inline keyboards plus callback routing because no polling command execution is needed
|
|
227
|
-
- Telegram prompt-template commands are discovered from Pi slash commands with `source: "prompt"`; Pi template names are mapped to Bot API-compatible aliases (`fix-tests` → `/fix_tests`), aliases that conflict with built-in bridge commands or hidden shortcuts are not displayed, prompt-template aliases stay out of the Telegram bot command menu, and the bridge expands template files before queueing because extension-originated `sendUserMessage()` bypasses Pi's interactive template expansion
|
|
228
|
-
- Target-aware companion delivery follows [`docs/delivery.md`](./docs/delivery.md): `active-turn`, `instance`, `aggregate`, and explicit target scopes resolve through current profile/role state; followers may target only their assigned thread or aggregate surface, leaders may target current live bindings, logical handles reconcile chunked send/edit/delete, and reload/session replacement invalidates old generations
|
|
229
|
-
- Unknown callback data not owned by pi-telegram prefixes (`compact:`, `tgbtn:`, `menu:`, `model:`, `thinking:`, `status:`, `queue:`, `section:`, `settings:`) may be forwarded as `[callback] <data>` after built-in handlers decline it; update-handler extensions should follow `docs/callback-namespaces.md` and must not poll the same bot independently
|
|
230
|
-
- Command templates stay compact and shell-free: no `command` field, no shell execution, inline defaults are allowed as `{name=default}`, `template` may be a string or an ordered composition array, only `args`/`defaults` inherit into leaves, top-level `timeout` wraps composed sequences, stdout pipes to the next step's stdin by default, and multi-step work should use `template: [...]` rather than provider-specific fields; `pipe` is only a legacy local alias
|
|
231
|
-
- Command-template documentation examples should use portable executable placeholders such as `/path/to/stt` and `/path/to/tts`, not host-local skill paths or machine-specific install locations
|
|
232
|
-
- Abstract pi-telegram docs and README examples must not leak real companion-extension identities into generic provider ids, runtime-event categories, or sample code. Use neutral ids such as `@scope/voice-provider/tts` and reserve real companion names for explicit companion-extension lists or case-study references.
|
|
233
|
-
|
|
234
|
-
## 9. Extension Sections Conventions
|
|
235
|
-
|
|
236
|
-
- `Section identity`: use the same identity-key rules as the Extension Locks Standard (`package.json/name` → canonical id); no separate `owner` field
|
|
237
|
-
- `Token mapping`: Telegram's 64-byte `callback_data` limit forces compact numeric tokens (`section:0:action:payload`). Section authors never hand-roll `section:` strings — use `ctx.callbackData(action, payload?)`
|
|
238
|
-
- `Navigation hierarchy`: Back buttons are auto-prepended by `ctx.edit()` only. Root views use `⬆️ Main menu` → `menu:back`. Nested views from `handleCallback` use `⬆️ Back` → `section:<token>:open`. Settings views use `⬆️ Back` → `settings:list`. `ctx.open()` sends standalone chat messages without auto-navigation
|
|
239
|
-
- `Context ports`: sections receive `TelegramSectionContext` / `TelegramSectionCallbackContext` with `answerCallback`, `edit`, `open`, `enqueuePrompt`, and `callbackData`. Section views default to `parseMode: "html"`; use explicit `"markdown"` when a section naturally owns Markdown content, or `"plain"` for text. No filesystem access, no raw bot clients, no second polling loop
|
|
240
|
-
- `Settings indicators`: use `settings.getLabel()` for dynamic status rows in the Settings submenu (e.g., `🟢`/`⚫️` based on internal state). Called on every Settings list render
|
|
241
|
-
- `Handler fallback`: `section.handleCallback` runs first; if it returns `"pass"` and `settings.handleCallback` exists, the settings handler runs with a fresh context carrying `backCallback="settings:list"`
|
|
242
|
-
- `Stale tokens`: unknown or unregistered tokens answer the callback with a short popup. Section errors are caught and surfaced as popup text — no unhandled exceptions leak to polling
|
|
243
|
-
- `Load order`: `pi-telegram` must load first (sets `globalThis.__piTelegramSectionRegistry__`). Consumer extensions load second. The typed import is the preferred path; the `globalThis` bridge exists for load-order tolerance
|
|
244
|
-
- `Shutdown`: call `pi.on("shutdown", () => unregister())` in the extension's default export
|
|
245
|
-
- `Section separators`: extension-injected main-menu rows appear before the **⚙️ Settings** row. Inside Settings, keep `⬆️ Main menu` first and group built-ins by operator meaning rather than alphabet: message presentation (`Draft previews`, `Rendering`, `Voice reply`), technical/public activity (`Activity`, `Proactive push`), prompt context (`Time injection`), then lifecycle (`Thread cleanup`). Extension settings follow the built-in groups using their explicit `settings.order`, then section id; detail-submenu choices keep their own semantic order.
|
|
246
|
-
- `Model button format`: use `provider/ModelId` format (e.g., `anthropic/claude-sonnet-4-5`) across model menu buttons and status row. The compact `provider/id` form is canonical
|
|
247
|
-
- `Section domain ownership`: `lib/sections.ts` owns the registry, token mapping, callback dispatch, and context building. `lib/menu.ts` dispatches `section:` callbacks before built-in handling. `lib/menu-status.ts` injects section rows. `lib/menu-settings.ts` injects settings rows and passes `sectionRegistry` through callback deps
|
|
248
|
-
- `Callback routing order`: button actions → compact confirmations → queue menu → settings menu → section callbacks → built-in menu handling → `[callback]` fallback. Settings menu callbacks always pass `sectionRegistry` to `updateTelegramSettingsMenuMessage` and `handleTelegramSettingsMenuCallbackAction`
|
|
249
|
-
|
|
250
|
-
## 10. Pre-Task Preparation Protocol
|
|
251
|
-
|
|
252
|
-
- Read `README.md` for current user-facing behavior and fork positioning
|
|
253
|
-
- Read `BACKLOG.md` before changing runtime behavior or documentation so open work stays truthful
|
|
254
|
-
- Read `/docs/architecture.md` before restructuring queue, preview, rendering, or command-handling logic
|
|
255
|
-
- Inspect the relevant `index.ts` section before editing because most bridge behavior is stateful and cross-linked
|
|
256
|
-
|
|
257
|
-
## 11. Task Completion Protocol
|
|
258
|
-
|
|
259
|
-
- Run the smallest meaningful validation for the touched area; `npm test` is the default regression suite once rendering or queue logic changes
|
|
260
|
-
- For rendering changes, ensure regressions still cover nested lists, code blocks, underscore-heavy text, and long-message chunking
|
|
261
|
-
- For queue/dispatch changes, validate abort, compaction, pending-dispatch, and Pi pending-message guard behavior
|
|
262
|
-
- Sync `README.md`, `CHANGELOG.md`, `BACKLOG.md`, and `/docs` whenever user-visible behavior or real open-work state changes
|
|
5
|
+
- `Mobile companion boundary`: Telegram extends a running Pi session; it is not a remote terminal, PTY supervisor, process launcher, session browser, or replacement TUI. Never emulate Pi navigation through private internals, ANSI/TTY injection, or a shadow `pi` process.
|
|
6
|
+
- `Runtime safety`: Prefer explicit, fenced, recoverable behavior over shortcuts that can desynchronize Telegram transport, durable admission, local queue state, or Pi lifecycle state.
|
|
7
|
+
- `Pi-native extensibility`: Add capabilities through stable Pi and pi-telegram contracts. Do not fork polling, transport, menu ownership, or package-private runtime internals.
|
|
8
|
+
- `Bidirectional binding`: Treat Pi instance ↔ Telegram thread and bot ↔ client state as two-way relationships. Create, observe, repair, and reflect bindings on both surfaces.
|
|
9
|
+
- `Progressive enhancement`: Use richer Telegram/Pi capability when proven available and retain a useful fail-closed fallback when it is not.
|
|
10
|
+
- `Boundary clarity`: Keep Telegram transport, Pi integration, rendering/delivery, durable admission, extension APIs, and release/context state under distinct owners.
|
|
11
|
+
|
|
12
|
+
## 1. Product Contract
|
|
13
|
+
|
|
14
|
+
`pi-telegram` is a session-local Telegram runtime adapter for Pi: a private-DM operator surface for prompts, streaming previews, queue controls, settings, files, voice/buttons, and companion-extension interop. Its core loop is mobile continuation of a live Pi session.
|
|
15
|
+
|
|
16
|
+
Canonical terms:
|
|
17
|
+
|
|
18
|
+
- `Telegram turn`: One Telegram input unit processed by Pi, including a coalesced media group.
|
|
19
|
+
- `Queued` / `active Telegram turn`: Accepted-but-not-running / currently bound Pi work.
|
|
20
|
+
- `TelegramTarget`: `{ chatId, threadId? }`; classic private chats omit `threadId`.
|
|
21
|
+
- `Thread`: Product term for Telegram's tabbed private-chat surface. Use `topic` only for Bot API primitives.
|
|
22
|
+
- `Leader` / `follower`: The process owning `getUpdates` and direct Bot API transport / a registered process routing through that leader.
|
|
23
|
+
- `Instance slot`: Extension-owned `A`–`Z` ordering metadata, not the normal visible thread title. Naming and allocation details live in [`docs/multi-instance-bus.md`](./docs/multi-instance-bus.md).
|
|
24
|
+
|
|
25
|
+
## 2. Context Ownership
|
|
26
|
+
|
|
27
|
+
Keep each fact in one authoritative layer:
|
|
28
|
+
|
|
29
|
+
- [`README.md`](./README.md): Public product entrypoint. Preserve the flow identity → install/connect → examples → product model → compact capabilities → controls/safety → docs. Balance strong positioning with a practical catalogue; neither hide capabilities nor duplicate implementation docs.
|
|
30
|
+
- `AGENTS.md`: Stable engineering boundaries, recurring runtime invariants, and work protocol. Link to evolving subsystem contracts instead of copying them here.
|
|
31
|
+
- [`BACKLOG.md`](./BACKLOG.md): Canonical unresolved work. Keep only open top-level outcomes with nested decomposition and done criteria. Remove completed outcomes rather than retaining checked history.
|
|
32
|
+
- [`CHANGELOG.md`](./CHANGELOG.md): Completed user/operator/developer impact. A release has at most eight outcome bullets of at most 512 characters, each beginning with an inline-code domain label and colon. Exclude personal names and real user/chat/message/thread identifiers. Consolidate the current pre-release section before release; do not rewrite historical sections without an explicit retrospective request and evidence pass.
|
|
33
|
+
- [`docs/README.md`](./docs/README.md): Technical documentation index.
|
|
34
|
+
- [`docs/architecture.md`](./docs/architecture.md): Canonical runtime, domain-ownership, queue, journal, delivery, and lifecycle contract.
|
|
35
|
+
- [`docs/public-api.md`](./docs/public-api.md): Canonical public commands, config, markup, package entrypoints, and compatibility contract.
|
|
36
|
+
- [`docs/multi-instance-bus.md`](./docs/multi-instance-bus.md): Canonical Threaded Mode, leader/follower, binding, election, and transport protocol.
|
|
37
|
+
- Other `/docs` files own their named subsystem contracts; keep them reachable from `docs/README.md`.
|
|
38
|
+
|
|
39
|
+
## 3. Repository Topology And Local Skills
|
|
40
|
+
|
|
41
|
+
- `/index.ts`: Sole extension entrypoint and composition root.
|
|
42
|
+
- `/api/*.ts`: Stable public package membranes documented in `docs/public-api.md`.
|
|
43
|
+
- `/lib/*.ts`: Flat, cohesive runtime domains; package-private unless re-exported through `/api`.
|
|
44
|
+
- `/tests/*.test.ts`: Domain-mirrored suites; `tests/integration.test.ts` owns cross-domain runtime flows.
|
|
45
|
+
- `/.agents/skills/telegram-bot`: Bot API lookup guidance and vendored `api.md`; keep the reference intact.
|
|
46
|
+
- `/.agents/skills/domain-dag`: Repository architecture guidance and validator.
|
|
47
|
+
|
|
48
|
+
Use the relevant local skill before non-trivial work in its domain. Keep skill operating guidance in its `SKILL.md`, not duplicated here.
|
|
49
|
+
|
|
50
|
+
## 4. Architecture And Runtime Invariants
|
|
51
|
+
|
|
52
|
+
### 4.1 Flat Domain DAG
|
|
53
|
+
|
|
54
|
+
- Cohesive domains live as flat `/lib/*.ts` modules whose local import graph is acyclic.
|
|
55
|
+
- `index.ts` constructs high-level runtimes and wires live ports. Domain policy, mutable state, sequencing, identity, retries, normalization, and lifecycle recovery belong to the owning `/lib` module.
|
|
56
|
+
- Extract only when ownership, substitution, independent testing/mutation, cycle pressure, or repeated coupling earns a boundary. Do not atomize a cohesive module or create one-use wrappers merely to shrink `index.ts`.
|
|
57
|
+
- `bindings` owns Pi-facing registration and narrow cross-domain assembly; it may connect established ports but must not absorb routing, rendering, transport, or mutable policy.
|
|
58
|
+
- `pi` owns direct Pi SDK imports and concrete adapter contracts. Other domains use narrow ports; domains that register Pi hooks/tools/commands consume contracts through that adapter.
|
|
59
|
+
- Do not introduce shared buckets such as `lib/constants.ts`, `lib/types.ts`, `lib/globals.ts`, or broad global-augmentation modules. Keep state, constants, registry keys, and concrete transport shapes with their domain owner.
|
|
60
|
+
- Every source `.ts` file starts with a brief responsibility header containing `Zones:` tags such as `telegram`, `pi agent`, `tui`, or `shared utils`.
|
|
61
|
+
- Use namespace imports for local domains in `index.ts` (`Queue.*`, `Turns.*`) and keep direct `node:*`, filesystem, process, and local-adapter mechanics in owning domains when one exists.
|
|
62
|
+
|
|
63
|
+
### 4.2 Ownership, Sessions, And Trust
|
|
64
|
+
|
|
65
|
+
- The bridge is session-local and paired to one allowed Telegram user. Preserve `{ chatId, threadId? }` through every inbound, queue, callback, reaction, media, preview, reply, menu, voice, attachment, and direct-delivery path.
|
|
66
|
+
- Telegram transport ownership is not semantic queue ownership. Losing the exact transport lock must not erase accepted local queue work or stop valid local Pi dispatch; direct Bot API mutations fail closed until exact direct or follower authority exists.
|
|
67
|
+
- `tmp/telegram/owners.json` is the sole transport-owner authority. Cross-process read/check/write operations serialize transactionally and acquisition, refresh, release, takeover, and irreversible leader work fence the exact owner/epoch. `state.json` and `logs.jsonl` are diagnostics, never routing authority.
|
|
68
|
+
- Threaded Mode has exactly one live leader per bot profile. Followers are real operator-started Pi processes and must authenticate/register over local IPC; Telegram never spawns hidden Pi processes. A live but unreachable owner does not authorize split-brain polling.
|
|
69
|
+
- Local IPC is a trust boundary, not merely a private socket. Unknown, stale, mismatched-generation, or unauthorized requests must not inject prompts, callbacks, API sends, artifacts, liveness, or bindings.
|
|
70
|
+
- Protocol compatibility is independent from package version. Registration negotiates protocol version, runtime build, and canonical capabilities before target provisioning or live publication. `durable-follower-admission-v1` gates source forwarding; `queue-handoff-v1` independently gates semantic queue transfer for every participant and is advertised only with exact source/recipient journal-binding composition. `follower.register` is the sole bootstrap request; other requests require exact live-registry generation authority, and `bus.ack` is response-only.
|
|
71
|
+
- Long-lived timers, pollers, watchers, receivers, heartbeats, background delivery, and deferred dispatch are session-bound. Replacement stops stale activity and makes late work inert; same-process handoff may preserve exact profile/target identity but never stale Pi context or cross-profile authority. Aborting a durable update generation does not release that `update_id`: replacement replay waits for its actual handler settlement, and effectful handlers use the shared execution fence immediately before commit and after awaited delegation. Internal clones explicitly carry the hidden fence; reroute forwarding, thread-store mutation, cleanup, and Bot API boundaries retain the originating generation.
|
|
72
|
+
- Runtime state is event-driven reconciliation of local assumptions against Telegram signals, not a complete bot read-model and not permission to query Telegram on every action. Destructive thread cleanup goes through `thread-reconciler` with current proof and leader fencing.
|
|
73
|
+
|
|
74
|
+
### 4.3 Durable Admission And Settlement
|
|
75
|
+
|
|
76
|
+
- Admission is journal-first: validate and persist the complete `getUpdates` response before one monotonic offset commit, then signal an independent worker without awaiting semantic execution. Missing cursor with a non-empty journal, malformed/foreign authority, or capacity exhaustion fails closed. “Durable” means process-crash recovery after atomic rename, not unflushed host/kernel/filesystem/device/power-loss survival.
|
|
77
|
+
- Foreign forwarding settles as `accepted`, `retryable`, or `terminal-rejected`. Only an authenticated acknowledgement carrying the expected `deliveryId` and `sourceUpdateId` releases leader journal authority. Negative, missing, stale, mismatched, or capacity-failed settlement remains durable; callback error answers are side effects only.
|
|
78
|
+
- A forwarding delivery id is stable across registration replacement and derives from envelope kind, source `update_id`, and stable recipient binding. Runtime instance and registration generation remain separate attempt fences. Persisted message ownership carries the stable binding so replay can rebind only to its current authenticated registration.
|
|
79
|
+
- A queued receipt persists its acquiring runtime instance, OS pid/process-birth identity, session generation, acquisition id, and acquisition time. Only exact authority may settle or discard it. Same-process session replacement may reconstruct the claim and the original process may settle after transport ownership moves; a foreign process may neither replay nor settle it through generic removal or a copied acquisition id.
|
|
80
|
+
- Startup and elapsed time are not owner-death proof; queued authority has no time lease. Dead-owner recovery groups the complete receipt and transactionally rechecks pid liveness plus process-birth identity: only an absent PID or mismatched stable Linux/macOS birth proof returns all sources to `pending`; a matching proof is `alive`, while Windows or inaccessible birth metadata is `unverifiable`, and both non-dead outcomes keep authority queued. The live-transfer contract is authenticated offer → exact-generation bounded payload staging → recipient CAS acceptance → exact receipt-and-owner ACK → donor removal → recipient readiness. The offer freezes donor settlement/recovery; controls rebuild local closures; negative/mismatched pre-acceptance ACK cancels only an unaccepted offer and retains donor work; a lost post-acceptance ACK cannot cancel recipient authority and leaves donor memory frozen for explicit reconciliation.
|
|
81
|
+
- Execution failures persist bounded diagnostics and attempt state as `retry-wait`. Automatic retry continues indefinitely with exponential `1s → 2s → 4s → 8s → 16s → 32s → 60s` delay capped at 60 seconds; later independent updates continue draining, durable authority is never silently discarded, and legacy `failed` entries resume automatically at startup. Snapshot-plus-segment journals compact only after 256 unapplied revisions or 4 MiB; snapshot-first cleanup tolerates redundant segments, and empty authority may atomically rebind bot/profile identity.
|
|
82
|
+
- An unresolved reaction delays only the exact governed queue item identified by chat/message sources, not unrelated queue work. Queue receipt publication follows in-memory append and precedes dispatch request; receipt-bearing turns remain queued until every exact source commits.
|
|
83
|
+
- The detailed implementation and release gates live in [`docs/architecture.md`](./docs/architecture.md), [`docs/multi-instance-bus.md`](./docs/multi-instance-bus.md), and [`BACKLOG.md`](./BACKLOG.md).
|
|
84
|
+
|
|
85
|
+
### 4.4 Queue, Delivery, And User Surfaces
|
|
86
|
+
|
|
87
|
+
- Queue lane/kind admission is explicit. Dispatch waits for active-turn, pending-dispatch, control, compaction, `ctx.isIdle()`, and Pi pending-message guards; a dispatched prompt stays queued until `agent_start` consumes it. Waiting-turn reactions reconcile from the complete current set: removal suppresses without discard and outranks priority, priority promotes otherwise, and no recognized reaction restores default. Suppressed turns stay visible and cannot block unrelated dispatch.
|
|
88
|
+
- `/stop`, `/abort`, `/next`, and `/continue` respectively reset+abort, abort while preserving queue, force the next turn, and enqueue a control-lane continuation. Abort-history folding applies only to Telegram-owned active turns.
|
|
89
|
+
- Telegram extension side effects must not hold Pi's core lifecycle hostage after semantic completion. Preserve ordering in extension-owned background work, record failures, and fence target/profile/transport/session authority.
|
|
90
|
+
- Complete assistant/guest model answers use Telegram-native Rich Markdown. Harness-owned menus, status, diagnostics, thinking, and tool evidence remain explicit HTML/plain or their documented native surface. Preserve literal code and structurally safe chunking; never split invalid markup.
|
|
91
|
+
- `preview` owns streaming lifecycle only, not assistant rendering. Finalization waits for active preview flushes and must not issue pre/post-final draft-clear calls that create transient Telegram draft UI.
|
|
92
|
+
- Native `sendChatAction(typing)` is the automatic activity signal. Do not invent extra in-chat work indicators or emit activity for startup/connect/reload/recovery alone.
|
|
93
|
+
- Public activity handlers and companion delivery are asynchronous, target-bound, generation-fenced surfaces. Token deltas, hidden reasoning, unknown sources, and stale authority never enter public projection.
|
|
94
|
+
- UI labels, emoji semantics, navigation, settings controls, callback namespaces, voice behavior, command templates, and assistant markup follow the linked `/docs` contracts; do not restate those evolving details here.
|
|
95
|
+
|
|
96
|
+
## 5. Domain Ownership Index
|
|
97
|
+
|
|
98
|
+
The detailed map is canonical in [`docs/architecture.md`](./docs/architecture.md). This index is only for routing work:
|
|
99
|
+
|
|
100
|
+
- `queue`, `runtime`, `lifecycle`, `locks`: Scheduling, session coordination, lifecycle, and locking.
|
|
101
|
+
- `api`, `polling`, `bus*`, `ownership`, `target`, `sync`, `thread-reconciler`, `threads`, `updates`, `routing`, `media`, `turns`, `inbound`, `config`, `setup`: Telegram transport, profiles, durable admission, routing, and inbound flow.
|
|
102
|
+
- `preview`, `replies`, `rendering`, `keyboard`, `delivery`, `activity`, `outbound*`, `voice`, `status`: Response and delivery surfaces.
|
|
103
|
+
- `commands`, `menu*`, `model`, `prompts`: Controls and application-menu UI; core queue mechanics remain in `queue`.
|
|
104
|
+
- `sections`, `delivery`, `activity`, `voice`: Extension registries/runtime membranes for their named capabilities. `Companion` describes consumers, not a source-domain owner.
|
|
105
|
+
- `pi`, `bindings`: Pi SDK boundary and Pi-facing registration/composition.
|
|
106
|
+
|
|
107
|
+
## 6. Public And Integration Boundaries
|
|
108
|
+
|
|
109
|
+
- Companion extensions use documented package subpaths such as `@llblab/pi-telegram/sections`, `/delivery`, `/voice`, `/inbound`, `/outbound`, and `/updates`; never import `lib/*.ts`.
|
|
110
|
+
- Low-level handler buses have no caller-supplied ids; high-level registries use stable identities. Imperative delivery resolves the current runtime on every call and returns generation-bound logical handles rather than captured Pi contexts.
|
|
111
|
+
- Extension sections receive only documented context ports. They do not access raw bot clients/filesystems or run a second polling loop; unregister on shutdown.
|
|
112
|
+
- Unknown callback data may reach extension handlers only after built-in namespaces decline it. Follow [`docs/callback-namespaces.md`](./docs/callback-namespaces.md).
|
|
113
|
+
- Command templates remain compact and shell-free. Use string leaves or ordered `template` arrays; shell operators are not an execution contract. Examples use portable executable placeholders, never machine-local paths.
|
|
114
|
+
- `telegram_attach` is the canonical file path and `telegram_message` the direct Markdown text/buttons path. Both require current direct or registered-follower authority and must not replace the normal active-turn reply.
|
|
115
|
+
- Inbound handlers transform text/media before queueing; outbound handlers precede programmatic/provider fallbacks. Public contracts and ordering live in `docs/inbound.md`, `docs/outbound.md`, and `docs/public-api.md`.
|
|
116
|
+
- Pi integration uses public hooks and APIs. A Telegram `/new` or equivalent session replacement requires a public Pi API that executes the real terminal path.
|
|
117
|
+
|
|
118
|
+
## 7. Engineering Conventions
|
|
119
|
+
|
|
120
|
+
- Keep comments and user-facing docs in English. Comment non-obvious rationale/contracts, not names or standard idioms.
|
|
121
|
+
- Name flat modules by bare domain (`queue.ts`, `queue.test.ts`); `telegram-api.ts` is the intentional transport exception. Tests primarily protect their mirrored module; shared fixtures require real cross-suite reuse.
|
|
122
|
+
- Keep interfaces consistent with their owning exported contract. Use local structural `*Like`/view types only for deliberate narrow projections, not duplicate source-of-truth models.
|
|
123
|
+
- Remove dead code immediately. Reachability from composition roots, public exports, tests, registered surfaces, and documented APIs—not recent usefulness—determines whether code is live.
|
|
124
|
+
- Treat every meaningful `index.ts` edit as a composition-pressure check, but keep one-off live adapter wiring there when extraction would only hide cross-domain state.
|
|
125
|
+
- Follow [`docs/ui-style.md`](./docs/ui-style.md) for interface copy, emoji, buttons, menus, and dialogs. Update the registry before assigning a new UI emoji meaning.
|
|
126
|
+
- Markdown tables use compact source formatting with `---` separator cells and one surrounding space per cell. Preserve vendored references unchanged.
|
|
127
|
+
- Treat Windows filesystem, named-pipe, lock, heartbeat, and atomic-rename reports as high-signal evidence; reduce them to regressions or explicit platform caveats.
|
|
128
|
+
- Route significant runtime failures through the redacted recent-event recorder. Keep the compact TUI status at generic `error`; details belong in diagnostics.
|
|
129
|
+
|
|
130
|
+
## 8. Work Protocol
|
|
131
|
+
|
|
132
|
+
Before non-trivial work:
|
|
133
|
+
|
|
134
|
+
1. Read `README.md` for current product behavior and positioning.
|
|
135
|
+
2. Read `BACKLOG.md` before runtime or documentation changes.
|
|
136
|
+
3. Read the relevant indexed docs; read `docs/architecture.md` before architecture, queue, preview, rendering, lifecycle, or command restructuring.
|
|
137
|
+
4. Inspect the owning module, its callers, mirrored tests, and the relevant `index.ts` wiring before editing.
|
|
138
|
+
5. Run an `AGENTS.md` compliance pass for implementation, release, and architecture work; update an obsolete rule instead of silently working around it.
|
|
139
|
+
|
|
140
|
+
While working:
|
|
141
|
+
|
|
142
|
+
- Keep changes inside this repository; updating an installed Pi checkout is a separate operator action.
|
|
143
|
+
- Read large artifacts search-first and range-bounded. For `CHANGELOG.md`, inspect only the current release section unless older history is relevant.
|
|
144
|
+
- Keep successful validation output compact; inspect focused failure tails. Prefer focused tests/typecheck during iteration and broad validation at a stable gate.
|
|
145
|
+
- Preserve unrelated work and do not commit, publish, tag, deploy, or perform external actions without explicit authorization.
|
|
146
|
+
|
|
147
|
+
Before completion:
|
|
148
|
+
|
|
149
|
+
- Run the smallest decisive validation for the affected closure. Queue/rendering/lifecycle changes normally require `npm run typecheck` and `npm test` at the stable gate.
|
|
150
|
+
- For Domain DAG changes, run `SKILL_DIR=.agents/skills/domain-dag bash .agents/skills/domain-dag/scripts/validate-domain-dag.sh --root .`.
|
|
151
|
+
- Keep strict unused-local/parameter checking. Validate queue dispatch around abort, compaction, pending dispatch, and Pi pending-message guards; validate rendering around literal code, nesting, and long-message chunks.
|
|
152
|
+
- When context files change, run the ABCd context validator and review warnings rather than relying on exit status alone.
|
|
153
|
+
- Sync `README.md`, `CHANGELOG.md`, `BACKLOG.md`, and relevant `/docs` only when behavior, shipped impact, open-work truth, or durable contracts actually changed.
|
|
154
|
+
- Do not call a release ready until its canonical backlog gates and required platform/live evidence are complete.
|