@llblab/pi-telegram 0.47.0 → 0.48.1
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 +5 -3
- package/BACKLOG.md +0 -5
- package/CHANGELOG.md +11 -1
- package/README.md +5 -2
- package/dist/lib/bus.js +1 -1
- package/dist/lib/config.js +1 -1
- package/dist/lib/extension.js +0 -4
- package/dist/lib/menu-settings.d.ts +1 -2
- package/dist/lib/menu-settings.js +17 -17
- package/dist/lib/status.js +8 -14
- package/dist/lib/thread-display.js +2 -2
- package/dist/package.json +1 -1
- package/dist/skills/show-me/SKILL.md +1 -1
- package/dist/skills/show-me/references/telegram-surfaces.md +1 -1
- package/docs/README.md +2 -0
- package/docs/architecture.md +3 -1
- package/docs/multi-instance-bus.md +5 -5
- package/docs/public-api.md +2 -2
- package/lib/bus.ts +1 -1
- package/lib/config.ts +1 -1
- package/lib/extension.ts +0 -12
- package/lib/menu-settings.ts +17 -19
- package/lib/status.ts +9 -15
- package/lib/thread-display.ts +2 -2
- package/package.json +1 -1
- package/skills/show-me/SKILL.md +1 -1
- package/skills/show-me/references/telegram-surfaces.md +1 -1
package/AGENTS.md
CHANGED
|
@@ -38,7 +38,7 @@ Keep each fact in one authoritative layer:
|
|
|
38
38
|
|
|
39
39
|
## 3. Repository Topology And Local Skills
|
|
40
40
|
|
|
41
|
-
- `/index.ts`: Thin
|
|
41
|
+
- `/index.ts`: Thin source entrypoint re-exporting the default extension. The installed/runtime entrypoint is generated under `/dist/pi-telegram`; after every project change, run `npm run build` before reload, restart, or live verification so Pi does not execute stale compiled output.
|
|
42
42
|
- `/lib/extension.ts`: Sole extension composition root.
|
|
43
43
|
- `/api/*.ts`: Stable public package membranes documented in `docs/public-api.md`.
|
|
44
44
|
- `/lib/*.ts`: Flat, cohesive runtime domains; package-private unless re-exported through `/api`.
|
|
@@ -92,14 +92,14 @@ Use the relevant local skill before non-trivial work in its domain. Keep skill o
|
|
|
92
92
|
|
|
93
93
|
### 4.4 Queue, Delivery, And User Surfaces
|
|
94
94
|
|
|
95
|
-
- 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. Each prompt is one object with one active lane and no reserved return slot. Normal and Priority are separate FIFO lanes: crossing lanes removes it from the source and appends it at the destination tail, while Keep/Skip and same-category emoji changes preserve lane position. Complete reaction sets independently derive Priority from recognized positive emoji and Skip from recognized negative emoji; both may coexist, suppressed turns retain durable receipts while waiting, and Skip settles them only when the prompt reaches dispatch before dropping it without inference. Suppressed turns remain visible at a struck-through physical ordinal without contributing to executable queue counters, while graceful session shutdown discards all remaining queue authority before clearing memory.
|
|
95
|
+
- 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. The terminal `+N` suffix is a yellow count of executable prompts still waiting, excludes the dispatched head immediately, and never counts current agent work from any source. Each prompt is one object with one active lane and no reserved return slot. Normal and Priority are separate FIFO lanes: crossing lanes removes it from the source and appends it at the destination tail, while Keep/Skip and same-category emoji changes preserve lane position. Complete reaction sets independently derive Priority from recognized positive emoji and Skip from recognized negative emoji; both may coexist, suppressed turns retain durable receipts while waiting, and Skip settles them only when the prompt reaches dispatch before dropping it without inference. Suppressed turns remain visible at a struck-through physical ordinal without contributing to executable queue counters, while graceful session shutdown discards all remaining queue authority before clearing memory.
|
|
96
96
|
- `/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.
|
|
97
97
|
- 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.
|
|
98
98
|
- 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. Before Telegram preview or final delivery, strip every assistant-authored HTML comment regardless of Markdown position while keeping action activation top-level-only; a comment-only result sends no text message. Preserve literal code outside comments and structurally safe chunking; never split invalid markup.
|
|
99
99
|
- `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. Turns that already answer as one atomic reply (voice replies, Guest Mode queries) never stream previews.
|
|
100
100
|
- Native `sendChatAction(typing)` is the automatic activity signal for unsettled agent and compaction work while Telegram transport is authorized. Extension-owned blocking UI prompts pause it and completion resumes it while either work owner remains active. Do not invent extra in-chat work indicators or emit activity for startup/connect/reload/recovery alone.
|
|
101
101
|
- Public activity handlers and connected companion delivery are asynchronous, target-bound, generation-fenced surfaces. Connected companion projection has no independent opt-out: disconnect or authority loss is its boundary. Token deltas, hidden reasoning, unknown sources, and stale authority never enter public projection.
|
|
102
|
-
- Thread display defaults to the profile-scoped Letters strategy, with Names and
|
|
102
|
+
- Thread display defaults to the profile-scoped Letters strategy, with Names, Directory Snake, and Directory Title as the other automatic choices; Names projects the generated dictionary name for the slot. Unsupported retained display keys resolve to Letters without rewriting persisted configuration. A durable manual Thread display name retained on its Workspace binding overrides any automatic projection until exact reset; keep generated/recovery identity separate from manual and acknowledged display fields. UI labels, emoji semantics, navigation, settings controls, callback namespaces, voice behavior, command templates, and assistant markup follow the linked `/docs` contracts. Generated human-readable prompt-button labels use `emoji + space + text`; emoji-free text is only a reasoned no-semantic-marker fallback. Non-spatial generated controls default to top-level vertical cells, with nested rows reserved for unmistakably compact peers. Do not restate other evolving UI details here.
|
|
103
103
|
|
|
104
104
|
## 5. Domain Ownership Index
|
|
105
105
|
|
|
@@ -148,12 +148,14 @@ Before non-trivial work:
|
|
|
148
148
|
While working:
|
|
149
149
|
|
|
150
150
|
- Keep changes inside this repository; updating an installed Pi checkout is a separate operator action.
|
|
151
|
+
- Rebuild the package with `npm run build` after edits. Pi loads `dist/pi-telegram/index.js`, so source-only changes are not live and `/reload` or process restart alone will reload stale compiled output.
|
|
151
152
|
- Read large artifacts search-first and range-bounded. For `CHANGELOG.md`, inspect only the current release section unless older history is relevant.
|
|
152
153
|
- Keep successful validation output compact; inspect focused failure tails. Prefer focused tests/typecheck during iteration and broad validation at a stable gate.
|
|
153
154
|
- Preserve unrelated work and do not commit, publish, tag, deploy, or perform external actions without explicit authorization.
|
|
154
155
|
|
|
155
156
|
Before completion:
|
|
156
157
|
|
|
158
|
+
- Run `npm run build` after the final edit and before any `/reload`, restart, live check, or handoff; never report a source change as live while `/dist` is stale.
|
|
157
159
|
- 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.
|
|
158
160
|
- For Domain DAG changes, run `SKILL_DIR=.agents/skills/domain-dag bash .agents/skills/domain-dag/scripts/validate-domain-dag.sh --root .`.
|
|
159
161
|
- 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.
|
package/BACKLOG.md
CHANGED
|
@@ -2,11 +2,6 @@
|
|
|
2
2
|
|
|
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
|
-
- [ ] `Session-aware Thread binding` (`next minor`, primary outcome): Replace profile-scoped exact-`cwd` reuse as the sole Workspace identity with a durable composite of normalized exact `cwd` and Pi's stable public session identity. `/telegram-connect` must persist that session-to-Thread binding so reopening, resuming, or continuing the same Pi session automatically restores the same Telegram Thread and letter slot when the retained binding has not been validly displaced. Different sessions in one directory must receive distinct live slots, while the same session must not allocate a duplicate after process restart. Session number/name are bounded lookup or presentation aliases only, never routing authority. Preserve authenticated live-owner, accepted-work, displacement, retirement, and slot-pressure fences. This is the principal deliverable of the next minor release.
|
|
6
|
-
- [x] `Identity and migration contract`: [`docs/multi-instance-bus.md`](./docs/multi-instance-bus.md#approved-next-contract-session-aware-workspace-identity) now fixes the public session source, composite authority, binding-key derivation, lifecycle semantics, exact legacy `{ cwd, ∅ }` coexistence, fail-closed malformed present identity behavior, mixed-version fence, and non-ownership boundaries.
|
|
7
|
-
- [x] `Store and allocation foundation`: Session-qualified identity, persistence, independent same-cwd claims/slots, strict digest validation, cleanup and retirement snapshots, and exact legacy cwd-only coexistence are locally implemented. The former staged-adoption claim state and commit exceptions have been removed, so no latent mutation path can consume a cwd-only binding. Absence of session identity remains its own key across reload and stale status persistence; same-session restore retains its letter while another session cannot reuse it.
|
|
8
|
-
- [x] `Leader/follower lifecycle wiring`: Leader assembly now reads public `ctx.sessionManager.getSessionId()`, passes it through sync provisioning, and restores the matching session-qualified Workspace without creating a duplicate Thread; cwd-only legacy state remains an independent `{ cwd, ∅ }` identity and is never exposed through a session lookup. Authenticated follower registration now carries a strict bounded session ID over the bus, preserves it in the live registry, and passes it into leader-owned provisioning; fresh same-cwd follower sessions therefore commit distinct session-qualified bindings. Production follower assembly sources the value from the current Pi context, independently of process instance and session generation. Follower-to-leader promotion now resolves the exact current session binding and preserves its target, slot, generated name, and acknowledged display title through leader handoff. Startup restore and `/telegram-connect` share the session-bearing registration envelope and leader provisioner. Protocol v2 makes session identity mandatory for every cwd-scoped participant and rejects protocol-v1 0.45.x peers before provisioning; the supported upgrade path stops all instances, updates them together, then starts a fresh session-native leader. Session-aware stale-target replacement is now regression-covered: replacing session A updates only A's binding and leaves same-cwd session B untouched. Capability rejection is covered independently from the older durable-admission gate. Startup restore eligibility now checks the current session exactly and never treats a legacy cwd-only binding as that session, so neither a sibling nor `{ cwd, ∅ }` can make a fresh session auto-connect. Explicit `/telegram-connect`, restore-only registration, and reconnect handoff all use the same session-bearing registration runtime; focused tests cover restore-only and handoff transmission. The production integration harness now supplies a distinct stable `sessionManager.getSessionId()` per mocked context, and the lifecycle/cleanup integration regression passes with session-aware leader provisioning. Legacy `{ cwd, ∅ }` records remain readable but inert and independent: they are never adopted, migrated, or used for a v2 session. Full-suite closure is green: the bundled Show Me wording is compressed within its disclosure budget, integration contexts preserve one session manager across idle/active phases, and the complete test command passes.
|
|
9
|
-
- [ ] `Compatibility and acceptance` (`local-actionable` plus live gate, current): Exact lookup now treats both presence and absence of session ID symmetrically: `{ cwd, ∅ }` cannot match a session binding, and `{ cwd, session }` cannot match legacy. Focused store, follower, leader, and polling suites pass across coexistence, restore-only, same-session process replacement, follower reload/handoff, distinct same-cwd session allocation (the `/fork`/`/new` identity effect), promotion, session-isolated rename, stale-target replacement, malformed identity, and rolling-upgrade capability behavior. Full slot-pressure retirement now selects the oldest eligible exact binding across legacy and session-qualified identities and persists the victim's session component; protection and exact-intent fences remain unchanged. The Pi adapter now has explicit evidence that restart/reload/resume/continue-recent aliases do not influence authority: only the public stable session ID is read, while a forked ID remains distinct. Local acceptance is complete: generation-fenced stale ownership cannot mutate a replacement; target displacement is exact-session isolated; strict legacy and session-qualified snapshots coexist without aliasing or stale resurrection; protocol-v1 peers are explicitly rejected, while all protocol-v2 cwd participants use session-qualified identity. Update the current-contract docs only with locally validated behavior. Remaining gate: a separately operator-authorized live smoke for same-session restoration and two concurrent sessions in one directory.
|
|
10
5
|
- [ ] `Prompt enqueue hotfix` (`optional`, `operator-gated`): Optional nonblocking operator-authorized disposable-follower smoke: overlap delayed voice processing with turn completion and confirm one-time ordered consumption and truthful counts against the [queue contract](./docs/architecture.md#queue-and-dispatch-safety). Separately authorized supported recovery investigation remains open: prevention does not repair an already-wedged in-memory queue; establish the exact recovery path and preservation/discard consequences before mutation, otherwise report the blocker. No journal/ownership edits, replay of settled input, implicit queue clearing, or restart; live activation requires separate operator authorization.
|
|
11
6
|
- [ ] `Channel multimedia posts` (`0.45.1`, live-acceptance-gated): `telegram_message` channel delivery accepts one local `.jpg`/`.jpeg`/`.png`/`.webp` photo or `.mp4` video, uploads it through the multipart transport as `sendPhoto`/`sendVideo` with `text` as the HTML caption, validates kind and size (photo ≤ 10 MiB, video ≤ 50 MiB) plus ≤ 1024 visible caption characters before issuance, and rejects unsupported types and albums instead of downgrading them to links. The channel-post journal binds kind/file name/byte size/SHA-256 and caption, so duplicate requests and lost acknowledgements never re-upload; media-post edits replace the caption through `editMessageCaption`, and Markdown spoilers render as `<tg-spoiler>`. Live image publication passed on `@llb_log`. Regressions cover confirmed publication, duplicate requests, lost ACK, pre-issuance rejection, caption edits, and reconnect replacement. Remaining: operator-authorized disposable-channel acceptance of rejected upload, duplicate request, and caption edit.
|
|
12
7
|
- [ ] `Manual Thread naming` (`gated-but-preparable`, release priority): Local bot-owned `/name Name` and bare `/name` flows avoid model dispatch. One expiring exact-target input dialog immediately accepts the next valid name, always offers cancel, and offers **Reset to automatic** only while a manual override exists; duplicate/stale callbacks cannot repeat mutation. Durable manual override supersedes every automatic display mode, reset is leader/follower generation- and target-fenced, and Letters remains the default without rewriting recovery identity. Local review findings are remediated, including Bot-API-wait target-replacement regressions for leader/follower rename and reset. Remaining: disposable live acceptance for command-menu ordering, dialog, invalid input, duplicate callbacks, leader/follower rename and reset.
|
package/CHANGELOG.md
CHANGED
|
@@ -4,13 +4,23 @@
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.48.1: Truthful terminal queue count hotfix
|
|
8
|
+
|
|
9
|
+
- `Terminal queue status`: The yellow `+N` suffix now counts only executable prompts still waiting in the Telegram queue. The current agent run never contributes—whether it started from Telegram, the terminal, or autonomous continuation—and a dispatched Telegram prompt leaves the visible count before its run settles.
|
|
10
|
+
|
|
11
|
+
## 0.48.0: Unified activity and Thread display
|
|
12
|
+
|
|
13
|
+
- `Unified activity`: The terminal keeps its stable `connected`, `leader`, or `follower` identity while work runs. Active work uses the same green Queue count as queued items, while Telegram typing projects every agent run—including local or autonomous work—into the assigned Thread and aggregate `All` target.
|
|
14
|
+
- `Thread display`: Settings now present the exact profile configuration values `letters`, `names`, `directory-title`, and `directory-snake`, place title case first, and omit the redundant live-preview block. Concurrent `directory-title` slot suffixes use one plain space instead of a middle-dot separator.
|
|
15
|
+
- `Session-aware Thread binding`: Operator live smoke confirmed same-session restoration and distinct concurrent sessions in one directory without duplicate Threads or letter slots, completing the remaining acceptance gate.
|
|
16
|
+
|
|
7
17
|
## 0.47.0: Built runtime and live surfaces
|
|
8
18
|
|
|
9
19
|
- `Startup`: npm installs now load a compiled JavaScript extension from a nested `dist/pi-telegram` entrypoint while source checkouts retain `index.ts`. Stable public subpath exports ship compiled JavaScript plus declarations, packaged Skills follow the distributive root, and validation checks the built extension import.
|
|
10
20
|
- `Button authoring`: Native `telegram_button` fences now accept adjacent top-level JSON/CML objects without an outer array or commas as vertical singleton rows. Explicit outer matrices remain equivalent, nested arrays still own horizontal layout, and malformed sequences activate no controls.
|
|
11
21
|
- `Activity`: Native typing remains continuously re-armed during agent/message work, pauses only for local UI prompts, resumes for unsettled agent or compaction work, and retargets queued turns without leaking keepalive ticks across Threads; transport and session fences still suppress stale actions.
|
|
12
22
|
- `Live views`: Validated `refreshAfterMs` drives non-overlapping timers after hinted `telegram_bind` delivery. Refreshes and current-surface actions edit one generation-fenced Delivery handle through the Markdown/button planner across classic, leader, and follower targets; hints reschedule and omission closes. Retryable failures retain one frame; unavailable messages invalidate with a bounded diagnostic. Take, shutdown, and replacement fence late refreshes and cancel memory-only surfaces.
|
|
13
|
-
- `Thread display`: Adds
|
|
23
|
+
- `Thread display`: Adds `directory-snake` and `directory-title` with Unicode-aware path tokenization, uppercase-abbreviation preservation, live previews, title projection, and peer capability fencing. Slot suffixes now follow authenticated concurrent same-directory ownership; dormant bindings and sticky legacy metadata are ignored. The retired `directories` key resolves to Letters and is omitted from Settings, leaving exactly Letters, Names, Directory Snake, and Directory Title.
|
|
14
24
|
|
|
15
25
|
## 0.46.0: Session-aware Workspace Threads
|
|
16
26
|
|
package/README.md
CHANGED
|
@@ -200,7 +200,7 @@ Messages sent while Pi is busy become queued turns. Queue controls let you inspe
|
|
|
200
200
|
|
|
201
201
|
Queue policy:
|
|
202
202
|
|
|
203
|
-
- One prompt is one queue object with exactly one current lane and one current position; it never reserves a shadow place in the other lane.
|
|
203
|
+
- One prompt is one queue object with exactly one current lane and one current position; it never reserves a shadow place in the other lane. The terminal's yellow `+N` suffix counts only executable prompts still waiting, never the current run; a dispatched Telegram prompt stops contributing before that run settles.
|
|
204
204
|
- Priority and Normal are separate FIFO lanes; Priority dispatches first.
|
|
205
205
|
- Moving `Normal → Priority` removes the prompt from Normal and places it at the Priority tail. Moving `Priority → Normal` removes it from Priority and places it at the Normal tail; no former position is restored.
|
|
206
206
|
- Keep/Skip never changes lane position. Skip preserves durable authority while waiting so Keep remains reversible, then settles that authority and drops the prompt without a model turn when dispatch reaches it. Skipped prompts stay visible at their physical queue position with a struck-through ordinal, but are excluded immediately from the executable queue count shown in both the Pi status bar and Telegram main menu. Graceful session shutdown discards all remaining queued authority, so a new session starts empty.
|
|
@@ -240,7 +240,7 @@ Classic private DM mode is the base product mode. When Telegram private-chat Thr
|
|
|
240
240
|
- Unknown threads are preserved and offered explicit reroute/restore choices.
|
|
241
241
|
- Telegram never launches hidden Pi processes.
|
|
242
242
|
|
|
243
|
-
In Threaded Mode, open Settings → **🧵 Thread display** to choose
|
|
243
|
+
In Threaded Mode, open Settings → **🧵 Thread display** to choose `letters` (default), `names`, `directory-title`, or `directory-snake` for this bot profile; a retained **directories** value remains available as an unchanged legacy presentation. Fresh tabs are created with the active mode's title instead of being visibly renamed afterward. Telegram tab titles, Pi terminal status, live Thread choosers/notices, prompt attribution, and named `telegram_message` targeting use the same acknowledged display name; target IDs and live registrations still own routing. Names shows the generated dictionary name chosen for the slot, such as `Anchor` for slot `A`; the directory formats produce `api_tools` or `Api Tools` and append `_a` or ` A` only while two or more authenticated live instances share that exact directory. Dormant retained bindings do not keep those suffixes visible. `/name` sets a manual Thread display name; **Reset to automatic** restores the selected automatic projection. Switching preserves Thread IDs, slots, generated recovery identity, and queue ownership. Partial application reports an error and can be retried without recreating Threads.
|
|
244
244
|
|
|
245
245
|
| Mode | Best for | Runtime shape |
|
|
246
246
|
| --- | --- | --- |
|
|
@@ -316,7 +316,10 @@ The docs index lives at [docs/README.md](./docs/README.md).
|
|
|
316
316
|
|
|
317
317
|
## Development
|
|
318
318
|
|
|
319
|
+
Pi loads the compiled `dist/pi-telegram/index.js` entrypoint. After every project change, run `npm run build` before `/reload`, restart, or live verification; reloading source without rebuilding can leave the running extension on stale compiled code.
|
|
320
|
+
|
|
319
321
|
```bash
|
|
322
|
+
npm run build
|
|
320
323
|
npm run typecheck
|
|
321
324
|
npm test
|
|
322
325
|
npm run audit
|
package/dist/lib/bus.js
CHANGED
|
@@ -479,7 +479,7 @@ export function parseTelegramBusEnvelope(line) {
|
|
|
479
479
|
case "follower.setThreadDisplayMode":
|
|
480
480
|
if (typeof value.instanceId === "string" &&
|
|
481
481
|
typeof value.registrationGeneration === "string" &&
|
|
482
|
-
(value.mode === "letters" || value.mode === "names" ||
|
|
482
|
+
(value.mode === "letters" || value.mode === "names" ||
|
|
483
483
|
value.mode === "directory-snake" || value.mode === "directory-title")) {
|
|
484
484
|
envelope = { kind, requestId, instanceId: value.instanceId,
|
|
485
485
|
registrationGeneration: value.registrationGeneration, mode: value.mode };
|
package/dist/lib/config.js
CHANGED
|
@@ -80,7 +80,7 @@ export function getTelegramBotTokenDiagnostic(value, env = process.env) {
|
|
|
80
80
|
return `Telegram bot token environment variable ${reference.variable} is not set.`;
|
|
81
81
|
}
|
|
82
82
|
const TELEGRAM_THREAD_DISPLAY_MODES = [
|
|
83
|
-
"letters", "names", "
|
|
83
|
+
"letters", "names", "directory-snake", "directory-title",
|
|
84
84
|
];
|
|
85
85
|
export function resolveTelegramThreadDisplayMode(config) {
|
|
86
86
|
return TELEGRAM_THREAD_DISPLAY_MODES.includes(config.threadDisplayMode)
|
package/dist/lib/extension.js
CHANGED
|
@@ -687,10 +687,6 @@ export default function (pi) {
|
|
|
687
687
|
return threadStore.getBotState().threadMode === "enabled"
|
|
688
688
|
? Config.resolveTelegramThreadDisplayMode(configStore.get()) : undefined;
|
|
689
689
|
},
|
|
690
|
-
getThreadDisplayPreview(mode) {
|
|
691
|
-
const bindings = threadStore.listWorkspaceBindings();
|
|
692
|
-
return [...ThreadDisplay.resolveTelegramWorkspaceDisplayNames(bindings, mode, ThreadDisplay.resolveTelegramLiveWorkspaceBindingKeys(bindings, threadStore.getActiveByInstanceId(telegramInstanceId)?.target, telegramBusFollowerRegistry.list())).values()];
|
|
693
|
-
},
|
|
694
690
|
async setThreadDisplayMode(mode) {
|
|
695
691
|
try {
|
|
696
692
|
await ThreadDisplay.applyTelegramThreadDisplaySetting(mode, {
|
|
@@ -12,7 +12,6 @@ import type { TelegramVoiceReplyMode } from "./voice.ts";
|
|
|
12
12
|
export type TelegramSettingsMenuReplyMarkup = TelegramInlineKeyboardMarkup;
|
|
13
13
|
export interface TelegramSettingsStateDeps {
|
|
14
14
|
getThreadDisplayMode?: () => TelegramThreadDisplayMode | undefined;
|
|
15
|
-
getThreadDisplayPreview?: (mode: TelegramThreadDisplayMode) => readonly string[];
|
|
16
15
|
areDraftPreviewsEnabled: () => boolean;
|
|
17
16
|
getAssistantRenderingMode: () => TelegramAssistantRenderingMode;
|
|
18
17
|
getActivityVerbosity: () => TelegramActivityVerbosity;
|
|
@@ -87,7 +86,7 @@ export declare const TIME_INJECTION_MODE_SETTINGS_TITLE = "<b>\uD83D\uDD52 Time
|
|
|
87
86
|
export declare const VOICE_REPLY_MODE_SETTINGS_TITLE = "<b>\uD83D\uDC44 Voice reply mode:</b>";
|
|
88
87
|
export declare const THREAD_DISPLAY_SETTINGS_TITLE = "<b>\uD83E\uDDF5 Thread display:</b>";
|
|
89
88
|
export declare function buildTelegramSettingsMenuText(): string;
|
|
90
|
-
export declare function buildThreadDisplaySettingsText(mode: TelegramThreadDisplayMode
|
|
89
|
+
export declare function buildThreadDisplaySettingsText(mode: TelegramThreadDisplayMode): string;
|
|
91
90
|
export declare function buildAutomaticThreadCleanupSettingsText(enabled: boolean): string;
|
|
92
91
|
export declare function buildInactiveThreadReviewText(count: number): string;
|
|
93
92
|
export declare function buildInactiveThreadReviewReplyMarkup(operationId?: string, canCleanInactiveThreads?: boolean): TelegramSettingsMenuReplyMarkup;
|
|
@@ -25,22 +25,16 @@ function getVoiceReplyModeSetting(mode, configured) {
|
|
|
25
25
|
export function buildTelegramSettingsMenuText() {
|
|
26
26
|
return SETTINGS_MENU_TITLE;
|
|
27
27
|
}
|
|
28
|
-
function
|
|
29
|
-
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
30
|
-
}
|
|
31
|
-
export function buildThreadDisplaySettingsText(mode, preview = []) {
|
|
32
|
-
const visiblePreview = preview.slice(0, 5).map(escapeThreadDisplayPreview);
|
|
28
|
+
export function buildThreadDisplaySettingsText(mode) {
|
|
33
29
|
return [
|
|
34
|
-
`${THREAD_DISPLAY_SETTINGS_TITLE} <code>${mode}</code>`,
|
|
30
|
+
`${THREAD_DISPLAY_SETTINGS_TITLE} <code>${threadDisplayModeLabel(mode)}</code>`,
|
|
35
31
|
"",
|
|
36
32
|
"Choose how this bot profile labels Telegram tabs and Pi terminal status. Each slot is unique across this bot profile.",
|
|
37
33
|
"",
|
|
38
34
|
"<code>-</code> <code>letters</code> (default): show the unique slot, such as <b><i>A</i></b> or <b><i>B</i></b>.",
|
|
39
35
|
"<code>-</code> <code>names</code>: show the generated dictionary name for the slot, such as <b><i>Anchor</i></b> or <b><i>Briar</i></b>.",
|
|
40
|
-
"<code>-</code> <code>
|
|
41
|
-
"<code>-</code> <code>directory-snake</code>: normalize the distinguishing directory path, such as <b><i>api_tools</i></b> or <b><i>frontend_extensions_a</i></b>.",
|
|
42
|
-
"<code>-</code> <code>directory-title</code>: humanize the distinguishing directory path, such as <b><i>Api Tools</i></b> or <b><i>Frontend / Extensions · A</i></b>.",
|
|
43
|
-
...(visiblePreview.length ? ["", `<b>Live preview:</b> ${visiblePreview.map((label) => `<code>${label}</code>`).join(", ")}${preview.length > visiblePreview.length ? `, +${preview.length - visiblePreview.length} more` : ""}`] : []),
|
|
36
|
+
"<code>-</code> <code>directory-title</code>: humanize the distinguishing directory path, such as <b><i>Api Tools</i></b> or <b><i>Frontend / Extensions A</i></b>.",
|
|
37
|
+
"<code>-</code> <code>directory-snake</code>: normalize the distinguishing directory path to lowercase, such as <b><i>api_tools</i></b> or <b><i>frontend_extensions_a</i></b>.",
|
|
44
38
|
].join("\n");
|
|
45
39
|
}
|
|
46
40
|
export function buildAutomaticThreadCleanupSettingsText(enabled) {
|
|
@@ -199,11 +193,20 @@ export async function openTelegramSettingsMenu(deps, sectionRegistry) {
|
|
|
199
193
|
state.mode = "settings";
|
|
200
194
|
deps.storeModelMenuState(state);
|
|
201
195
|
}
|
|
196
|
+
function threadDisplayModeLabel(mode) {
|
|
197
|
+
switch (mode) {
|
|
198
|
+
case "letters": return "letters";
|
|
199
|
+
case "names": return "names";
|
|
200
|
+
case "directory-title": return "directory-title";
|
|
201
|
+
case "directory-snake": return "directory-snake";
|
|
202
|
+
default: return "letters";
|
|
203
|
+
}
|
|
204
|
+
}
|
|
202
205
|
export function buildThreadDisplaySettingsReplyMarkup(mode) {
|
|
203
206
|
return { inline_keyboard: [
|
|
204
207
|
[{ text: "⬆️ Back", callback_data: "settings:list" }],
|
|
205
|
-
...["letters", "names", "
|
|
206
|
-
text: `${mode === value ? "🟢 " : ""}${value}`,
|
|
208
|
+
...["letters", "names", "directory-title", "directory-snake"].map((value) => [{
|
|
209
|
+
text: `${mode === value ? "🟢 " : ""}${threadDisplayModeLabel(value)}`,
|
|
207
210
|
callback_data: `settings:set:thread-display:${value}`,
|
|
208
211
|
}]),
|
|
209
212
|
] };
|
|
@@ -341,7 +344,7 @@ export async function handleTelegramSettingsMenuCallbackAction(callbackQueryId,
|
|
|
341
344
|
}
|
|
342
345
|
if (data.startsWith("settings:set:thread-display:")) {
|
|
343
346
|
const mode = data.slice("settings:set:thread-display:".length);
|
|
344
|
-
if (mode !== "letters" && mode !== "names" &&
|
|
347
|
+
if (mode !== "letters" && mode !== "names" &&
|
|
345
348
|
mode !== "directory-snake" && mode !== "directory-title") {
|
|
346
349
|
await deps.answerCallbackQuery(callbackQueryId, "Unknown Thread display mode.");
|
|
347
350
|
return true;
|
|
@@ -359,7 +362,7 @@ export async function handleTelegramSettingsMenuCallbackAction(callbackQueryId,
|
|
|
359
362
|
await deps.answerCallbackQuery(callbackQueryId, "Threaded Mode is no longer available.");
|
|
360
363
|
return true;
|
|
361
364
|
}
|
|
362
|
-
await deps.updateSettingsMessage(buildThreadDisplaySettingsText(mode
|
|
365
|
+
await deps.updateSettingsMessage(buildThreadDisplaySettingsText(mode), buildThreadDisplaySettingsReplyMarkup(mode));
|
|
363
366
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
364
367
|
return true;
|
|
365
368
|
}
|
|
@@ -530,7 +533,6 @@ export function createTelegramSettingsMenuRuntime(deps, sectionRegistry) {
|
|
|
530
533
|
getTimeInjectionMode: deps.getTimeInjectionMode,
|
|
531
534
|
isAutomaticThreadCleanupEnabled: deps.isAutomaticThreadCleanupEnabled,
|
|
532
535
|
getThreadDisplayMode: deps.getThreadDisplayMode,
|
|
533
|
-
getThreadDisplayPreview: deps.getThreadDisplayPreview,
|
|
534
536
|
sendSettingsMenu: (state, text, replyMarkup) => deps.sendInteractiveMessage(state.chatId, text, "html", replyMarkup),
|
|
535
537
|
storeModelMenuState: deps.storeModelMenuState,
|
|
536
538
|
}, sectionRegistry);
|
|
@@ -546,7 +548,6 @@ export function createTelegramSettingsMenuRuntime(deps, sectionRegistry) {
|
|
|
546
548
|
getTimeInjectionMode: deps.getTimeInjectionMode,
|
|
547
549
|
isAutomaticThreadCleanupEnabled: deps.isAutomaticThreadCleanupEnabled,
|
|
548
550
|
getThreadDisplayMode: deps.getThreadDisplayMode,
|
|
549
|
-
getThreadDisplayPreview: deps.getThreadDisplayPreview,
|
|
550
551
|
updateSettingsMessage: (text, replyMarkup) => deps.editInteractiveMessage(state.chatId, state.messageId, text, "html", replyMarkup),
|
|
551
552
|
}, sectionRegistry);
|
|
552
553
|
},
|
|
@@ -576,7 +577,6 @@ export function createTelegramSettingsMenuRuntime(deps, sectionRegistry) {
|
|
|
576
577
|
getTimeInjectionMode: deps.getTimeInjectionMode,
|
|
577
578
|
isAutomaticThreadCleanupEnabled: deps.isAutomaticThreadCleanupEnabled,
|
|
578
579
|
getThreadDisplayMode: deps.getThreadDisplayMode,
|
|
579
|
-
getThreadDisplayPreview: deps.getThreadDisplayPreview,
|
|
580
580
|
setThreadDisplayMode: deps.setThreadDisplayMode,
|
|
581
581
|
setDraftPreviewsEnabled: deps.setDraftPreviewsEnabled,
|
|
582
582
|
setAssistantRenderingMode: deps.setAssistantRenderingMode,
|
package/dist/lib/status.js
CHANGED
|
@@ -215,9 +215,10 @@ export function createTelegramBridgeStatusRuntime(deps) {
|
|
|
215
215
|
getStatusBarState: (_ctx, error) => {
|
|
216
216
|
const config = deps.getConfig();
|
|
217
217
|
const queuedItems = deps.getQueuedItems();
|
|
218
|
-
const queuedItemCount = deps.getQueuedItemCount?.(queuedItems) ?? queuedItems.length;
|
|
219
|
-
const hasActiveTurn = deps.hasActiveTurn();
|
|
220
218
|
const hasPendingDispatch = deps.hasDispatchPending();
|
|
219
|
+
const waitingItems = hasPendingDispatch ? queuedItems.slice(1) : queuedItems;
|
|
220
|
+
const queuedItemCount = deps.getQueuedItemCount?.(waitingItems) ?? waitingItems.length;
|
|
221
|
+
const hasActiveTurn = deps.hasActiveTurn();
|
|
221
222
|
const hasPendingModelSwitch = deps.hasPendingModelSwitch();
|
|
222
223
|
const activeToolExecutions = deps.getActiveToolExecutions();
|
|
223
224
|
const compactionInProgress = deps.isCompactionInProgress();
|
|
@@ -244,7 +245,7 @@ export function createTelegramBridgeStatusRuntime(deps) {
|
|
|
244
245
|
activeToolExecutions,
|
|
245
246
|
queuedItems: queuedItemCount,
|
|
246
247
|
}),
|
|
247
|
-
queuedStatus:
|
|
248
|
+
queuedStatus: queuedItemCount > 0 ? ` +${queuedItemCount}` : "",
|
|
248
249
|
pollingStopReason: deps.getPollingState?.().stopReason,
|
|
249
250
|
error,
|
|
250
251
|
};
|
|
@@ -405,7 +406,7 @@ function getTelegramStatusBarLabel(state) {
|
|
|
405
406
|
export function buildTelegramStatusBarText(theme, state) {
|
|
406
407
|
const label = theme.fg("accent", getTelegramStatusBarLabel(state));
|
|
407
408
|
const queued = state.queuedStatus
|
|
408
|
-
? theme.fg("
|
|
409
|
+
? theme.fg("warning", state.queuedStatus)
|
|
409
410
|
: "";
|
|
410
411
|
if (!state.hasBotToken)
|
|
411
412
|
return `${label} ${theme.fg("muted", "not configured")}${queued}`;
|
|
@@ -418,23 +419,16 @@ export function buildTelegramStatusBarText(theme, state) {
|
|
|
418
419
|
if (!state.pollingActive && state.busRole !== "follower")
|
|
419
420
|
return `${theme.fg("accent", "telegram")} ${theme.fg("dim", "disconnected")}${queued}`;
|
|
420
421
|
if (state.error) {
|
|
421
|
-
return `${label} ${theme.fg("error", "error")}`;
|
|
422
|
+
return `${label} ${theme.fg("error", "error")}${queued}`;
|
|
422
423
|
}
|
|
423
424
|
if (state.busRole === "follower" && state.followerRegistered === false) {
|
|
424
425
|
return `${label} ${theme.fg("warning", "reconnecting")}${queued}`;
|
|
425
426
|
}
|
|
426
|
-
if (state.processing) {
|
|
427
|
-
const processingStatus = state.queuedStatus
|
|
428
|
-
? "active"
|
|
429
|
-
: (state.processingStatus ?? "processing");
|
|
430
|
-
const processingToken = processingStatus === "active" ? "warning" : "accent";
|
|
431
|
-
return `${label} ${theme.fg(processingToken, processingStatus)}${queued}`;
|
|
432
|
-
}
|
|
433
427
|
if (state.busRole === "follower")
|
|
434
428
|
return `${label} ${theme.fg("success", "follower")}${queued}`;
|
|
435
429
|
if (state.busRole === "leader")
|
|
436
|
-
return `${label} ${theme.fg("success", "leader")}`;
|
|
437
|
-
return `${label} ${theme.fg("success", "connected")}`;
|
|
430
|
+
return `${label} ${theme.fg("success", "leader")}${queued}`;
|
|
431
|
+
return `${label} ${theme.fg("success", "connected")}${queued}`;
|
|
438
432
|
}
|
|
439
433
|
function formatTelegramBridgeBotStatus(state) {
|
|
440
434
|
if (state.botUsername)
|
|
@@ -116,7 +116,7 @@ export function resolveTelegramWorkspaceDisplayNames(bindings, mode, liveBinding
|
|
|
116
116
|
if (showSuffix && !slot)
|
|
117
117
|
continue;
|
|
118
118
|
const suffix = !showSuffix ? "" : mode === "directory-title"
|
|
119
|
-
? `
|
|
119
|
+
? ` ${slot}`
|
|
120
120
|
: `_${slot.toLowerCase()}`;
|
|
121
121
|
labels.set(binding.bindingKey, boundedLabel(base, suffix));
|
|
122
122
|
}
|
|
@@ -146,7 +146,7 @@ export function resolveTelegramWorkspaceDisplayNames(bindings, mode, liveBinding
|
|
|
146
146
|
labels.delete(binding.bindingKey);
|
|
147
147
|
continue;
|
|
148
148
|
}
|
|
149
|
-
labels.set(binding.bindingKey, boundedLabel(bases.get(binding.bindingKey), mode === "directory-title" ? `
|
|
149
|
+
labels.set(binding.bindingKey, boundedLabel(bases.get(binding.bindingKey), mode === "directory-title" ? ` ${binding.slot}` : `_${binding.slot.toLowerCase()}`));
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
const counts = new Map();
|
package/dist/package.json
CHANGED
|
@@ -29,7 +29,7 @@ Help the user understand the current topic. Skip the preamble and keep prose bri
|
|
|
29
29
|
A compact state line is enough when provenance matters:
|
|
30
30
|
|
|
31
31
|
```text
|
|
32
|
-
State: locally implemented
|
|
32
|
+
State: locally implemented | validated | not released | not live
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
### Visual Forms
|
|
@@ -26,7 +26,7 @@ Use this reference only when Show Me is responding through Telegram or preparing
|
|
|
26
26
|
- Build mobile-first for roughly phone-width reading, then let the same document expand cleanly in a system or desktop browser. Text must wrap; diagrams must scroll or reflow without clipping.
|
|
27
27
|
- Use semantic headings, sufficient contrast, non-color status meaning, comfortable touch targets, and no hover-only information.
|
|
28
28
|
- Preserve real labels, values, ordering, and uncertainty. An attractive reconstruction must not invent runtime state or imply that a proposed interaction exists.
|
|
29
|
-
- Include a compact provenance line when state matters, such as `Local patch
|
|
29
|
+
- Include a compact provenance line when state matters, such as `Local patch - validated - not released - not live`.
|
|
30
30
|
- When rendering tools are available, inspect at least one narrow viewport and one wider viewport. Report what was inspected; static source review is not visual proof.
|
|
31
31
|
- Deliver the file through the active Telegram attachment mechanism. Do not expose local paths as if the user could open them remotely.
|
|
32
32
|
|
package/docs/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Living index of project documentation in `/docs`.
|
|
4
4
|
|
|
5
|
+
> Developer note: Pi executes the generated `dist/pi-telegram/index.js` entrypoint. After any project edit, run `npm run build` before `/reload`, restart, or live verification; otherwise the runtime can continue using stale compiled output.
|
|
6
|
+
|
|
5
7
|
`public-api.md` is the canonical entrypoint for stable extension surfaces. Focused documents exist only when a surface has enough runtime rules to need a dedicated standard.
|
|
6
8
|
|
|
7
9
|
## Documents
|
package/docs/architecture.md
CHANGED
|
@@ -452,7 +452,9 @@ Long-text split recovery remains conservative: only human text at or above the n
|
|
|
452
452
|
|
|
453
453
|
### Queue And Dispatch Safety
|
|
454
454
|
|
|
455
|
-
The bridge keeps its own Telegram queue.
|
|
455
|
+
The bridge keeps its own Telegram queue. The Pi status bar's yellow `+N` suffix projects only executable prompts still waiting: current agent work from Telegram, terminal, or autonomous sources never contributes, and the dispatched Telegram head is subtracted while it remains retained pending `agent_start` consumption.
|
|
456
|
+
|
|
457
|
+
Queue items have two explicit dimensions:
|
|
456
458
|
|
|
457
459
|
- `kind`: `prompt` or `control`.
|
|
458
460
|
- `queueLane`: `control`, `priority`, or `default`.
|
|
@@ -167,10 +167,10 @@ Production retirement requires a durable profile-scoped reader/writer ledger own
|
|
|
167
167
|
- Retirement intent preparation/adoption/execution owns its exact gate-and-ledger protocol but remains absent from production composition. Status projection and polling/routing bot-mode writes change only diagnostic or capability metadata; they neither create nor remove Thread/Workspace authority and serialize through the store's local persistence queue.
|
|
168
168
|
- Production callers of reservation, provision/cleanup intent, target-record, Workspace-binding, and display mutators are contained by the owners above. The generic store remains policy-free for isolated tests and domain composition; calling a primitive directly is not production retirement authority.
|
|
169
169
|
- Workspace identity remains the selected bot profile plus normalized exact full `cwd`; directory basenames are presentation, never routing keys. Each concurrent binding receives one profile-wide unique lowercase slot from `a` through `z`, persisted on the wire/store as its uppercase equivalent, independent of directory and leader/follower role. This replaces the two competing displayed allocation identities; immutable legacy `instanceSlot` and `bindingKey` remain recovery keys, not another displayed pool.
|
|
170
|
-
- Automatic display mode is a bot-profile setting shared by Telegram Thread titles and Pi TUI status. The selector offers `letters`, `names`, `directory-snake`, then `directory-title`; absent or invalid values resolve to `letters`. Letters show `A`, `B`, `C`; Names shows the generated dictionary name for the slot, such as `Anchor` for `A`. The
|
|
170
|
+
- Automatic display mode is a bot-profile setting shared by Telegram Thread titles and Pi TUI status. The selector offers `letters`, `names`, `directory-snake`, then `directory-title`; absent or invalid values resolve to `letters`. Letters show `A`, `B`, `C`; Names shows the generated dictionary name for the slot, such as `Anchor` for `A`. The retired `directories` value is unsupported: it resolves to `letters`, is omitted from Settings, and is not rewritten automatically. A durable per-Workspace `manualThreadName`, set through Telegram `/name`, overrides any automatic projection until explicitly reset. Bare `/name` immediately enters exact-target rename input: cancel is always available, while reset is shown only when a manual override exists; no intermediate action-selection step exists. Store the automatic preference at `profiles.<name>.threadDisplayMode`, not as a process-local choice or a setting shared by unrelated bots.
|
|
171
171
|
- Directory formatting operates on the shortest path-segment suffix that distinguishes the normalized exact cwd from every other displayed cwd, while routing and identity continue to use the unmodified full cwd. Each segment is tokenized deterministically at runs of non-letter/non-number characters, lowercase-or-number to uppercase transitions, and the acronym boundary before an uppercase-plus-lowercase word (`apiPRDServer` becomes `api`, `PRD`, `Server`). Empty tokens are discarded. `directory-snake` lowercases tokens and joins them with `_`; segment qualifiers also join with `_`, so `/work/API tools` previews as `api_tools`. `directory-title` joins words with spaces and qualified segments with ` / `; an all-uppercase token containing a letter is preserved (`PRD` remains `PRD`), while every other token is lowercased and capitalized (`api_tools` becomes `Api Tools`). Unicode letters and numbers participate in tokenization and locale-independent Unicode case conversion; punctuation is only a separator. Root and token-empty segments retain the existing safe fallback instead of fabricating an empty title.
|
|
172
|
-
- Settings previews are computed by the same pure projector used for initial creation and reconciliation, against the current binding set rather than illustrative hard-coded text. Slot suffixes are applied after base formatting (`api_tools_a` and `Api Tools
|
|
173
|
-
- The
|
|
172
|
+
- Settings previews are computed by the same pure projector used for initial creation and reconciliation, against the current binding set rather than illustrative hard-coded text. Slot suffixes are applied after base formatting (`api_tools_a` and `Api Tools A`) and remain outside abbreviation/case normalization; title-case suffixes use one plain space and never a middle-dot separator. Manual names are never normalized. Case-insensitive or 128-character-bounded collisions remain fail-closed under the existing ambiguity contract. New persisted modes and follower requests require a new negotiated display-format capability; an older peer may continue operating only while the effective profile mode remains Letters or Names, and cannot silently reinterpret either new value.
|
|
173
|
+
- The unreachable legacy `directories` projector remains only for decoding old internal snapshots; effective configuration falls back to Letters. The `directory-snake` and `directory-title` modes derive suffix visibility exclusively from the leader's current authenticated live-owner snapshot: one live binding for a normalized exact cwd has no suffix; two or more live bindings for that cwd all expose their globally assigned slots; dormant retained bindings never affect the count. `showSlotSuffix` remains readable for legacy presentation but is ignored by both new projectors and is never written merely because live concurrency changed. The leader captures exact profile, epoch, binding target, and follower registration generation before projection; a provisioning candidate counts only after its authenticated exact owner is admitted to the same serialized Workspace mutation, while an owner losing authority is excluded before reconciliation. Registration/provision completion, authenticated disconnect or confirmed-dead prune, target replacement, and promotion schedule one serialized reconciliation of every affected live same-cwd binding. A failed or partial Telegram rename retains acknowledged per-binding progress and retries only under a fresh live snapshot; dormant tabs are not renamed until they regain authenticated ownership. Equal basenames from different paths still require deterministic parent-path qualification independently from same-cwd suffixing. Preserve the existing `threadName` as generated/recovery identity while automatic modes are selected. New manual names live only in `manualThreadName`; do not guess manual provenance from a legacy name or palette membership. Telegram `/name Name` changes the manual override and displayed title only for its exact originating target. Leader and follower requests carry that target through final generation/binding checks, so replacement cannot redirect a stale dialog mutation. Reset uses the same negotiated `workspace-thread-rename-v1` capability and exact follower generation; the leader computes the current automatic projection from the latest live snapshot, edits the exact target, clears only `manualThreadName`, and persists before acknowledging. Follower metadata refresh preserves an acknowledged display title only while target and registration generation stay unchanged. Named-profile setup preserves the latest saved automatic preference even if another instance changes it while the token form is open.
|
|
174
174
|
- Fresh provisioning projects the candidate together with retained bindings and sends the active mode's title in `createForumTopic`. The exact targeted provision retains creation-title evidence until the Workspace commit publishes the binding and consumes that evidence together. Recovery preserves it even when a starting record already exists; an untargeted or unknown creation never authorizes a title commit. Proven deletion removes exact-target pending creation evidence, including when no current record was committed. Older contradictory pending/deleted snapshots settle that evidence durably before replacement; closed targets and pending cleanup block recovery until reconciliation, rather than becoming active again. The same exact-target check protects follower reconnect/carried-target shortcuts and the final Workspace commit, before creation evidence can be consumed. A matching carried pending target resumes through the provisioner that owns its reserved slot and acknowledged title instead of allocating that slot again. The shared provision-commit helper first commits the claim, then applies the acknowledged title with exact-binding comparison, preserving generic stale-title rejection on target replacement. Switching display mode changes projection only: preserve Thread ID, binding identity, slot, queue ownership, and routing. `displayTitle` records a successful Telegram edit independently of `threadName`, survives same-target registration updates, and is cleared on target replacement. The title reconciler captures profile, mode, leader epoch, and exact live-binding authority before each edit, rechecks after ACK and persistence, and skips dormant bindings. Failed persistence retains acknowledged dirty metadata for a later persist without repeating that API edit; a late or unknown ACK never commits a title to a replacement binding. Keep the stable palette/manual name separate from the current display title so switching back does not generate a different name. The leader owns Telegram title edits and acknowledged follower/TUI convergence, with generation/profile fencing and truthful partial-failure recovery. Successful registration ACKs optionally carry the acknowledged `displayTitle` with the exact target and registration generation, making it available before the initial status refresh. Heartbeats carry later title changes; stale generations cannot update display state. Connected notices use acknowledged titles while runtime `threadName` remains the stable restoration identity. Live bot chooser/notice labels, prompt attribution, and cross-instance agent-target name selection use the same acknowledged projection, but candidate liveness and the captured numeric `{chatId, threadId}` remain authoritative. Ambiguous projected names fail closed. Older peers can ignore the optional field, and no separate polling connection or follower snapshot-read loop is needed; do not expose a setting control that merely stores a preference without updating its promised surfaces.
|
|
175
175
|
- Reopening a retained inactive binding restores its slot and name without taking leadership. Explicit connection from the same directory may allocate a second binding. Startup restore remains restore-only: it must not evict another Workspace or allocate a fresh binding merely because all remembered bindings are owned. Explicit connection already skips a live peer's migrated binding instead of attempting to adopt it; that admission rule is independent of the display redesign.
|
|
176
176
|
- Prefer free letters in deterministic order. Only under full slot exhaustion may retirement choose the eligible binding with the oldest proven inactivity start, not necessarily slot `a` after `z`. Time alone never retires a binding. If every slot is protected or its ownership is unverifiable, reject new allocation with a truthful capacity explanation; never evict a live owner or silently expand into `aa`.
|
|
@@ -388,7 +388,7 @@ Threaded Mode should make follower threads behave like normal Telegram instance
|
|
|
388
388
|
| Replies/finals | Final replies land in the same thread | Follower finals go through leader transport into follower thread | Outbound calls carry target and inject `message_thread_id` | Reply delivery and bus API tests |
|
|
389
389
|
| Previews/Rich Drafts | Draft previews use the active thread target | Follower previews use the same native draft lifecycle through the leader | Preview transport preserves target and draft id | Preview thread-target tests |
|
|
390
390
|
| Attachments/voice | Files and voice upload in the instance thread | Follower uploads route through leader multipart transport | Multipart calls are target-scoped and follower-authorized | Bus allowlist and outbound delivery tests |
|
|
391
|
-
| Native activity status | `sendChatAction(typing)` renders Telegram's native `…typing` indicator in the assigned thread and mirrors aggregate `All` for every agent run, including local and autonomous work; terminal
|
|
391
|
+
| Native activity status | `sendChatAction(typing)` renders Telegram's native `…typing` indicator in the assigned thread and mirrors aggregate `All` for every agent run, including local and autonomous work; terminal identity remains `connected`, with active work included in the green Queue count | Followers route one thread action and one aggregate action through leader transport for any agent run while retaining their stable terminal `follower` identity and green activity count | Agent lifecycle targets the active Telegram turn when present and otherwise the instance binding; one keyed loop avoids duplicate aggregate sends/rate-limit pressure | Agent-start binding, typing-loop, target-routing, and terminal-status regressions |
|
|
392
392
|
| Leader election / promotion | Current leader keeps its thread across reload | A promoted follower keeps its existing thread, slot, and name when elected and after later reload | Promotion converts the current follower binding into the leader profile before forced lock acquisition, so leader startup reuses it instead of provisioning a new thread | Follower heartbeat recovery passes binding snapshot into promotion; own-topic provisioner reuses promoted bindings |
|
|
393
393
|
| `/start` command/menu bootstrap | Registers visible bot commands and opens the menu | Follower `/start` can refresh the bot command menu through the leader and open its local menu without warnings | Bot command registration is a validated global Bot API call allowed through trusted follower bus transport | Bus allowlist regression for `setMyCommands` |
|
|
394
394
|
| Follower reconnect | Existing leader binding is reused only when still usable | Same-process `/new` or `/reload` suspends the old follower socket/context and automatically re-registers the new session to the exact prior target; explicit reconnect to a genuinely closed/stale Telegram tab still recreates a visible thread before success | A short-lived handoff carries the assigned target across session replacement, and the leader transfers that binding to the new runtime instance id by stable manual-follower identity; stale Bot API errors remain the proof for fresh provisioning | Session handoff/refresh, leader binding-transfer, persisted leader-reload reuse, and stale-target replacement regressions |
|
|
@@ -430,7 +430,7 @@ Named bots use sibling `profiles.<name>` entries. Shared bridge settings remain
|
|
|
430
430
|
Rules:
|
|
431
431
|
|
|
432
432
|
- Classic mode is selected by Telegram capability: when private-chat threads are unavailable or disabled, the polling owner uses ordinary single-DM behavior and blocked instances do not register as followers. During a live downgrade from Threaded Mode, the current bus leader becomes the classic polling owner after two 2.5-second capability-monitor probes and followers disconnect; if classic polling restore fails transiently, later monitor ticks retry the restore instead of allowing a follower takeover. Followers must not turn the downgrade into a takeover while active thread bindings prove the singleton owner was already established by the bus leader.
|
|
433
|
-
- Telegram private-chat Threaded Mode enables local leader/follower behavior automatically. The leader owns `getUpdates`; registered followers route Telegram API work through the leader. `/telegram-connect` registers as follower when a live leader exists and does not offer manual takeover in that state. The TUI status bar
|
|
433
|
+
- Telegram private-chat Threaded Mode enables local leader/follower behavior automatically. The leader owns `getUpdates`; registered followers route Telegram API work through the leader. `/telegram-connect` registers as follower when a live leader exists and does not offer manual takeover in that state. The TUI status bar keeps `telegram connected`, `telegram leader`, or `telegram follower` as stable transport identity during work; active work and queued items share the green Queue count instead of replacing that identity with an `active` processing label. Follower registration is unique by live profile/target: a reload or session replacement must replace stale registry entries rather than leaving multiple routable ids for one Telegram thread, and fallback target ownership must not classify leader records as followers.
|
|
434
434
|
- The thread chat is the owner's private bot DM (`allowedUserId`); no `topics.chatId` config is needed. Thread names are assigned by the bridge from a baked compact per-slot palette. There is no agent-facing `telegram_rename_thread` tool and no separate user-facing slash command for manual thread renames.
|
|
435
435
|
- Thread reuse is extension-owned through profile-scoped Workspace bindings; there is no separate `topics` config surface in the active private-chat thread model. Leader/follower record keys remain live role projections, while normalized exact-`cwd` bindings preserve identity and deterministic same-directory process suffixes across role and process changes.
|
|
436
436
|
- Thread cleanup remains conservative and centralized: destructive close/delete actions are planned and applied through `thread-reconciler` with proof-before-delete checks, leader-epoch fencing, and retry-preserving failure semantics.
|
package/docs/public-api.md
CHANGED
|
@@ -90,7 +90,7 @@ interface TelegramBotProfile {
|
|
|
90
90
|
botUsername?: string; // runtime-managed
|
|
91
91
|
botId?: number; // runtime-managed
|
|
92
92
|
allowedUserId?: number;
|
|
93
|
-
threadDisplayMode?: "letters" | "names" | "
|
|
93
|
+
threadDisplayMode?: "letters" | "names" | "directory-snake" | "directory-title";
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
interface TelegramConfig {
|
|
@@ -120,7 +120,7 @@ Bot/session identity always persists under `profiles.<name>`. The ordinary setup
|
|
|
120
120
|
|
|
121
121
|
The file is global across Pi instances and contains configuration only. The per-profile polling/admission cursor is `acceptedThroughUpdateId` in that profile's private durable update journal; it is not a config key. On first connection after this cut, a legacy config cursor is transferred directly into the journal before polling and then removed from config. Journal publication failure preserves the legacy source; config publication failure leaves the journal authoritative so retry is idempotent. Cooperating instances serialize recursive config delta merges through `telegram.json.transaction` and preserve unrelated global/profile changes from newer disk snapshots. A semantically unchanged merge adopts the latest disk state in memory without replacing the file; later commits win when two deltas intentionally change the same leaf. Same-parent temp-file replacement retries bounded transient `EPERM`, `EACCES`, and `EBUSY` destination contention without deleting the live config or leaving transaction serialization. For manual edits, stop or idle the connected instances, publish a complete valid file atomically, and let them reload. A non-transactional editor racing Pi persistence has no same-leaf conflict guarantee.
|
|
122
122
|
|
|
123
|
-
Threaded Mode Settings exposes **Thread display** as Letters (default), Names, `directory-snake`, or `directory-title`; retained `directories`
|
|
123
|
+
Threaded Mode Settings exposes **Thread display** as Letters (default), Names, `directory-snake`, or `directory-title`; retained `directories` is unsupported, resolves to Letters, and is never rewritten automatically. `profiles.<name>.threadDisplayMode` is profile-scoped; absent and invalid values resolve to `letters`. Settings previews the current binding set through the same projector used for fresh titles and reconciliation. Names projects the generated dictionary name for the slot; the two directory modes tokenize Unicode path segments deterministically and render snake case or humanized title case, with `_a` or ` A` slot suffixes when required. The leader serializes preference persistence and title reconciliation, while a follower sends an authenticated `follower.setThreadDisplayMode` request gated by `thread-display-mode-v1`, the additional `directory-display-format-v1` capability for either new directory mode, and its exact registration generation. Config writes check the originating authority inside the config transaction; mode changes preserve target IDs, slots, generated recovery names, manual overrides, and queue ownership. `/name` mutations carry their originating target through final binding validation. The caller confirms only after application succeeds. A partial failure may leave the preference saved and some titles updated; Settings reports that state and permits retry. Acknowledged follower titles arrive through heartbeat rather than a new read loop.
|
|
124
124
|
|
|
125
125
|
Hidden/default semantics are represented by absence:
|
|
126
126
|
|
package/lib/bus.ts
CHANGED
|
@@ -1029,7 +1029,7 @@ export function parseTelegramBusEnvelope(
|
|
|
1029
1029
|
case "follower.setThreadDisplayMode":
|
|
1030
1030
|
if (typeof value.instanceId === "string" &&
|
|
1031
1031
|
typeof value.registrationGeneration === "string" &&
|
|
1032
|
-
(value.mode === "letters" || value.mode === "names" ||
|
|
1032
|
+
(value.mode === "letters" || value.mode === "names" ||
|
|
1033
1033
|
value.mode === "directory-snake" || value.mode === "directory-title")) {
|
|
1034
1034
|
envelope = { kind, requestId, instanceId: value.instanceId,
|
|
1035
1035
|
registrationGeneration: value.registrationGeneration, mode: value.mode };
|
package/lib/config.ts
CHANGED
|
@@ -144,7 +144,7 @@ export type TelegramThreadDisplayMode =
|
|
|
144
144
|
| "directory-title";
|
|
145
145
|
|
|
146
146
|
const TELEGRAM_THREAD_DISPLAY_MODES: readonly TelegramThreadDisplayMode[] = [
|
|
147
|
-
"letters", "names", "
|
|
147
|
+
"letters", "names", "directory-snake", "directory-title",
|
|
148
148
|
];
|
|
149
149
|
|
|
150
150
|
export function resolveTelegramThreadDisplayMode(
|
package/lib/extension.ts
CHANGED
|
@@ -866,18 +866,6 @@ export default function (pi: Pi.ExtensionAPI) {
|
|
|
866
866
|
return threadStore.getBotState().threadMode === "enabled"
|
|
867
867
|
? Config.resolveTelegramThreadDisplayMode(configStore.get()) : undefined;
|
|
868
868
|
},
|
|
869
|
-
getThreadDisplayPreview(mode) {
|
|
870
|
-
const bindings = threadStore.listWorkspaceBindings();
|
|
871
|
-
return [...ThreadDisplay.resolveTelegramWorkspaceDisplayNames(
|
|
872
|
-
bindings,
|
|
873
|
-
mode,
|
|
874
|
-
ThreadDisplay.resolveTelegramLiveWorkspaceBindingKeys(
|
|
875
|
-
bindings,
|
|
876
|
-
threadStore.getActiveByInstanceId(telegramInstanceId)?.target,
|
|
877
|
-
telegramBusFollowerRegistry.list(),
|
|
878
|
-
),
|
|
879
|
-
).values()];
|
|
880
|
-
},
|
|
881
869
|
async setThreadDisplayMode(mode) {
|
|
882
870
|
try {
|
|
883
871
|
await ThreadDisplay.applyTelegramThreadDisplaySetting(mode, {
|
package/lib/menu-settings.ts
CHANGED
|
@@ -23,7 +23,6 @@ export type TelegramSettingsMenuReplyMarkup = TelegramInlineKeyboardMarkup;
|
|
|
23
23
|
|
|
24
24
|
export interface TelegramSettingsStateDeps {
|
|
25
25
|
getThreadDisplayMode?: () => TelegramThreadDisplayMode | undefined;
|
|
26
|
-
getThreadDisplayPreview?: (mode: TelegramThreadDisplayMode) => readonly string[];
|
|
27
26
|
areDraftPreviewsEnabled: () => boolean;
|
|
28
27
|
getAssistantRenderingMode: () => TelegramAssistantRenderingMode;
|
|
29
28
|
getActivityVerbosity: () => TelegramActivityVerbosity;
|
|
@@ -176,26 +175,18 @@ export function buildTelegramSettingsMenuText(): string {
|
|
|
176
175
|
return SETTINGS_MENU_TITLE;
|
|
177
176
|
}
|
|
178
177
|
|
|
179
|
-
function escapeThreadDisplayPreview(text: string): string {
|
|
180
|
-
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
181
|
-
}
|
|
182
|
-
|
|
183
178
|
export function buildThreadDisplaySettingsText(
|
|
184
179
|
mode: TelegramThreadDisplayMode,
|
|
185
|
-
preview: readonly string[] = [],
|
|
186
180
|
): string {
|
|
187
|
-
const visiblePreview = preview.slice(0, 5).map(escapeThreadDisplayPreview);
|
|
188
181
|
return [
|
|
189
|
-
`${THREAD_DISPLAY_SETTINGS_TITLE} <code>${mode}</code>`,
|
|
182
|
+
`${THREAD_DISPLAY_SETTINGS_TITLE} <code>${threadDisplayModeLabel(mode)}</code>`,
|
|
190
183
|
"",
|
|
191
184
|
"Choose how this bot profile labels Telegram tabs and Pi terminal status. Each slot is unique across this bot profile.",
|
|
192
185
|
"",
|
|
193
186
|
"<code>-</code> <code>letters</code> (default): show the unique slot, such as <b><i>A</i></b> or <b><i>B</i></b>.",
|
|
194
187
|
"<code>-</code> <code>names</code>: show the generated dictionary name for the slot, such as <b><i>Anchor</i></b> or <b><i>Briar</i></b>.",
|
|
195
|
-
"<code>-</code> <code>
|
|
196
|
-
"<code>-</code> <code>directory-snake</code>: normalize the distinguishing directory path, such as <b><i>api_tools</i></b> or <b><i>frontend_extensions_a</i></b>.",
|
|
197
|
-
"<code>-</code> <code>directory-title</code>: humanize the distinguishing directory path, such as <b><i>Api Tools</i></b> or <b><i>Frontend / Extensions · A</i></b>.",
|
|
198
|
-
...(visiblePreview.length ? ["", `<b>Live preview:</b> ${visiblePreview.map((label) => `<code>${label}</code>`).join(", ")}${preview.length > visiblePreview.length ? `, +${preview.length - visiblePreview.length} more` : ""}`] : []),
|
|
188
|
+
"<code>-</code> <code>directory-title</code>: humanize the distinguishing directory path, such as <b><i>Api Tools</i></b> or <b><i>Frontend / Extensions A</i></b>.",
|
|
189
|
+
"<code>-</code> <code>directory-snake</code>: normalize the distinguishing directory path to lowercase, such as <b><i>api_tools</i></b> or <b><i>frontend_extensions_a</i></b>.",
|
|
199
190
|
].join("\n");
|
|
200
191
|
}
|
|
201
192
|
|
|
@@ -423,11 +414,21 @@ export async function openTelegramSettingsMenu<
|
|
|
423
414
|
deps.storeModelMenuState(state);
|
|
424
415
|
}
|
|
425
416
|
|
|
417
|
+
function threadDisplayModeLabel(mode: TelegramThreadDisplayMode): string {
|
|
418
|
+
switch (mode) {
|
|
419
|
+
case "letters": return "letters";
|
|
420
|
+
case "names": return "names";
|
|
421
|
+
case "directory-title": return "directory-title";
|
|
422
|
+
case "directory-snake": return "directory-snake";
|
|
423
|
+
default: return "letters";
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
426
427
|
export function buildThreadDisplaySettingsReplyMarkup(mode: TelegramThreadDisplayMode): TelegramSettingsMenuReplyMarkup {
|
|
427
428
|
return { inline_keyboard: [
|
|
428
429
|
[{ text: "⬆️ Back", callback_data: "settings:list" }],
|
|
429
|
-
...(["letters", "names", "
|
|
430
|
-
text: `${mode === value ? "🟢 " : ""}${value}`,
|
|
430
|
+
...(["letters", "names", "directory-title", "directory-snake"] as const).map((value) => [{
|
|
431
|
+
text: `${mode === value ? "🟢 " : ""}${threadDisplayModeLabel(value)}`,
|
|
431
432
|
callback_data: `settings:set:thread-display:${value}`,
|
|
432
433
|
}]),
|
|
433
434
|
] };
|
|
@@ -642,7 +643,7 @@ export async function handleTelegramSettingsMenuCallbackAction(
|
|
|
642
643
|
}
|
|
643
644
|
if (data.startsWith("settings:set:thread-display:")) {
|
|
644
645
|
const mode = data.slice("settings:set:thread-display:".length);
|
|
645
|
-
if (mode !== "letters" && mode !== "names" &&
|
|
646
|
+
if (mode !== "letters" && mode !== "names" &&
|
|
646
647
|
mode !== "directory-snake" && mode !== "directory-title") {
|
|
647
648
|
await deps.answerCallbackQuery(callbackQueryId, "Unknown Thread display mode.");
|
|
648
649
|
return true;
|
|
@@ -660,7 +661,7 @@ export async function handleTelegramSettingsMenuCallbackAction(
|
|
|
660
661
|
return true;
|
|
661
662
|
}
|
|
662
663
|
await deps.updateSettingsMessage(
|
|
663
|
-
buildThreadDisplaySettingsText(mode
|
|
664
|
+
buildThreadDisplaySettingsText(mode),
|
|
664
665
|
buildThreadDisplaySettingsReplyMarkup(mode),
|
|
665
666
|
);
|
|
666
667
|
await deps.answerCallbackQuery(callbackQueryId);
|
|
@@ -876,7 +877,6 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
876
877
|
getTimeInjectionMode: deps.getTimeInjectionMode,
|
|
877
878
|
isAutomaticThreadCleanupEnabled: deps.isAutomaticThreadCleanupEnabled,
|
|
878
879
|
getThreadDisplayMode: deps.getThreadDisplayMode,
|
|
879
|
-
getThreadDisplayPreview: deps.getThreadDisplayPreview,
|
|
880
880
|
sendSettingsMenu: (state, text, replyMarkup) =>
|
|
881
881
|
deps.sendInteractiveMessage(
|
|
882
882
|
state.chatId,
|
|
@@ -901,7 +901,6 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
901
901
|
getTimeInjectionMode: deps.getTimeInjectionMode,
|
|
902
902
|
isAutomaticThreadCleanupEnabled: deps.isAutomaticThreadCleanupEnabled,
|
|
903
903
|
getThreadDisplayMode: deps.getThreadDisplayMode,
|
|
904
|
-
getThreadDisplayPreview: deps.getThreadDisplayPreview,
|
|
905
904
|
updateSettingsMessage: (text, replyMarkup) =>
|
|
906
905
|
deps.editInteractiveMessage(
|
|
907
906
|
state.chatId,
|
|
@@ -946,7 +945,6 @@ export function createTelegramSettingsMenuRuntime<
|
|
|
946
945
|
getTimeInjectionMode: deps.getTimeInjectionMode,
|
|
947
946
|
isAutomaticThreadCleanupEnabled: deps.isAutomaticThreadCleanupEnabled,
|
|
948
947
|
getThreadDisplayMode: deps.getThreadDisplayMode,
|
|
949
|
-
getThreadDisplayPreview: deps.getThreadDisplayPreview,
|
|
950
948
|
setThreadDisplayMode: deps.setThreadDisplayMode,
|
|
951
949
|
setDraftPreviewsEnabled: deps.setDraftPreviewsEnabled,
|
|
952
950
|
setAssistantRenderingMode: deps.setAssistantRenderingMode,
|
package/lib/status.ts
CHANGED
|
@@ -705,9 +705,11 @@ export function createTelegramBridgeStatusRuntime<
|
|
|
705
705
|
getStatusBarState: (_ctx, error) => {
|
|
706
706
|
const config = deps.getConfig();
|
|
707
707
|
const queuedItems = deps.getQueuedItems();
|
|
708
|
-
const queuedItemCount = deps.getQueuedItemCount?.(queuedItems) ?? queuedItems.length;
|
|
709
|
-
const hasActiveTurn = deps.hasActiveTurn();
|
|
710
708
|
const hasPendingDispatch = deps.hasDispatchPending();
|
|
709
|
+
const waitingItems = hasPendingDispatch ? queuedItems.slice(1) : queuedItems;
|
|
710
|
+
const queuedItemCount =
|
|
711
|
+
deps.getQueuedItemCount?.(waitingItems) ?? waitingItems.length;
|
|
712
|
+
const hasActiveTurn = deps.hasActiveTurn();
|
|
711
713
|
const hasPendingModelSwitch = deps.hasPendingModelSwitch();
|
|
712
714
|
const activeToolExecutions = deps.getActiveToolExecutions();
|
|
713
715
|
const compactionInProgress = deps.isCompactionInProgress();
|
|
@@ -735,7 +737,7 @@ export function createTelegramBridgeStatusRuntime<
|
|
|
735
737
|
activeToolExecutions,
|
|
736
738
|
queuedItems: queuedItemCount,
|
|
737
739
|
}),
|
|
738
|
-
queuedStatus:
|
|
740
|
+
queuedStatus: queuedItemCount > 0 ? ` +${queuedItemCount}` : "",
|
|
739
741
|
pollingStopReason: deps.getPollingState?.().stopReason,
|
|
740
742
|
error,
|
|
741
743
|
};
|
|
@@ -924,7 +926,7 @@ export function buildTelegramStatusBarText(
|
|
|
924
926
|
): string {
|
|
925
927
|
const label = theme.fg("accent", getTelegramStatusBarLabel(state));
|
|
926
928
|
const queued = state.queuedStatus
|
|
927
|
-
? theme.fg("
|
|
929
|
+
? theme.fg("warning", state.queuedStatus)
|
|
928
930
|
: "";
|
|
929
931
|
if (!state.hasBotToken)
|
|
930
932
|
return `${label} ${theme.fg("muted", "not configured")}${queued}`;
|
|
@@ -937,24 +939,16 @@ export function buildTelegramStatusBarText(
|
|
|
937
939
|
if (!state.pollingActive && state.busRole !== "follower")
|
|
938
940
|
return `${theme.fg("accent", "telegram")} ${theme.fg("dim", "disconnected")}${queued}`;
|
|
939
941
|
if (state.error) {
|
|
940
|
-
return `${label} ${theme.fg("error", "error")}`;
|
|
942
|
+
return `${label} ${theme.fg("error", "error")}${queued}`;
|
|
941
943
|
}
|
|
942
944
|
if (state.busRole === "follower" && state.followerRegistered === false) {
|
|
943
945
|
return `${label} ${theme.fg("warning", "reconnecting")}${queued}`;
|
|
944
946
|
}
|
|
945
|
-
if (state.processing) {
|
|
946
|
-
const processingStatus = state.queuedStatus
|
|
947
|
-
? "active"
|
|
948
|
-
: (state.processingStatus ?? "processing");
|
|
949
|
-
const processingToken =
|
|
950
|
-
processingStatus === "active" ? "warning" : "accent";
|
|
951
|
-
return `${label} ${theme.fg(processingToken, processingStatus)}${queued}`;
|
|
952
|
-
}
|
|
953
947
|
if (state.busRole === "follower")
|
|
954
948
|
return `${label} ${theme.fg("success", "follower")}${queued}`;
|
|
955
949
|
if (state.busRole === "leader")
|
|
956
|
-
return `${label} ${theme.fg("success", "leader")}`;
|
|
957
|
-
return `${label} ${theme.fg("success", "connected")}`;
|
|
950
|
+
return `${label} ${theme.fg("success", "leader")}${queued}`;
|
|
951
|
+
return `${label} ${theme.fg("success", "connected")}${queued}`;
|
|
958
952
|
}
|
|
959
953
|
|
|
960
954
|
function formatTelegramBridgeBotStatus(
|
package/lib/thread-display.ts
CHANGED
|
@@ -132,7 +132,7 @@ export function resolveTelegramWorkspaceDisplayNames(
|
|
|
132
132
|
: (liveDirectoryCounts.get(binding.cwd) ?? 0) > 1;
|
|
133
133
|
if (showSuffix && !slot) continue;
|
|
134
134
|
const suffix = !showSuffix ? "" : mode === "directory-title"
|
|
135
|
-
? `
|
|
135
|
+
? ` ${slot}`
|
|
136
136
|
: `_${slot!.toLowerCase()}`;
|
|
137
137
|
labels.set(binding.bindingKey, boundedLabel(base, suffix));
|
|
138
138
|
}
|
|
@@ -161,7 +161,7 @@ export function resolveTelegramWorkspaceDisplayNames(
|
|
|
161
161
|
continue;
|
|
162
162
|
}
|
|
163
163
|
labels.set(binding.bindingKey, boundedLabel(bases.get(binding.bindingKey)!,
|
|
164
|
-
mode === "directory-title" ? `
|
|
164
|
+
mode === "directory-title" ? ` ${binding.slot}` : `_${binding.slot.toLowerCase()}`));
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
const counts = new Map<string, number>();
|
package/package.json
CHANGED
package/skills/show-me/SKILL.md
CHANGED
|
@@ -29,7 +29,7 @@ Help the user understand the current topic. Skip the preamble and keep prose bri
|
|
|
29
29
|
A compact state line is enough when provenance matters:
|
|
30
30
|
|
|
31
31
|
```text
|
|
32
|
-
State: locally implemented
|
|
32
|
+
State: locally implemented | validated | not released | not live
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
### Visual Forms
|
|
@@ -26,7 +26,7 @@ Use this reference only when Show Me is responding through Telegram or preparing
|
|
|
26
26
|
- Build mobile-first for roughly phone-width reading, then let the same document expand cleanly in a system or desktop browser. Text must wrap; diagrams must scroll or reflow without clipping.
|
|
27
27
|
- Use semantic headings, sufficient contrast, non-color status meaning, comfortable touch targets, and no hover-only information.
|
|
28
28
|
- Preserve real labels, values, ordering, and uncertainty. An attractive reconstruction must not invent runtime state or imply that a proposed interaction exists.
|
|
29
|
-
- Include a compact provenance line when state matters, such as `Local patch
|
|
29
|
+
- Include a compact provenance line when state matters, such as `Local patch - validated - not released - not live`.
|
|
30
30
|
- When rendering tools are available, inspect at least one narrow viewport and one wider viewport. Report what was inspected; static source review is not visual proof.
|
|
31
31
|
- Deliver the file through the active Telegram attachment mechanism. Do not expose local paths as if the user could open them remotely.
|
|
32
32
|
|