@llblab/pi-kit 0.22.1 → 0.22.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +1 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +2 -1
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-telegram/README.md +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +22 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/skills.d.ts +2 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/skills.js +9 -2
- package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +1 -1
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +1 -1
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +27 -3
- package/node_modules/@llblab/pi-telegram/lib/skills.ts +10 -2
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@llblab/pi-kit` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.22.2 - 2026-09-24
|
|
6
|
+
|
|
7
|
+
- `Follower Registration Recovery`: Advances the exact Telegram pin to `0.51.3`. Followers whose retained target record contains a stale Workspace slot now reconcile to the authenticated canonical claim before binding commit, durably repairing their record without disturbing an unrelated binding that owns the old letter or making unnecessary Bot API calls.
|
|
8
|
+
- `Filterable Packaged Skills`: Installed npm/git packages now leave Telegram Skill discovery to the manifest so Pi resource filters are honored. Raw TypeScript checkouts under Pi's extensions directory retain adjacent source-Skill discovery without creating a duplicate packaged discovery path.
|
|
9
|
+
- `Package Cohort`: Keeps every other bundled package at its current exact version. Package membership, resource paths, load order, Pi minimum, and bundled Skill inventory remain unchanged.
|
|
10
|
+
|
|
5
11
|
## 0.22.1 - 2026-09-24
|
|
6
12
|
|
|
7
13
|
- `In-Flight Model Switching`: Advances the exact Telegram pin to `0.51.2`. Telegram model selection can stop, switch, and continue any interruptible run in the current Pi session, including local/TUI work, while preserving the authorized chat, Thread, and reply target and deferring abort until active tools settle.
|
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Package links lead to the owning repositories for usage, documentation, issues,
|
|
|
15
15
|
| [`@llblab/pi-codex-usage`](https://github.com/llblab/pi-codex-usage) | `0.10.0` | Compact Codex/Spark subscription-limit and Business credit-usage status |
|
|
16
16
|
| [`@llblab/pi-grow-loop`](https://github.com/llblab/pi-grow-loop) | `0.8.1` | Visible continuation scheduling and bounded worker Skills |
|
|
17
17
|
| [`@llblab/pi-state-flow`](https://github.com/llblab/pi-state-flow) | `0.18.1` | Incremental scoped context/memory compiler with independent scope revisions, canonical file persistence, bounded and diagnosable Git backup replication, native working context within each run, and targeted historical reads |
|
|
18
|
-
| [`@llblab/pi-telegram`](https://github.com/llblab/pi-telegram) | `0.51.
|
|
18
|
+
| [`@llblab/pi-telegram`](https://github.com/llblab/pi-telegram) | `0.51.3` | Telegram companion with self-healing follower registration, filterable packaged Skills, in-flight model switching, continuous exact-Thread typing, pressure-safe Workspace rotation, files, voice, and controls |
|
|
19
19
|
| [`@llblab/skills`](https://github.com/llblab/skills) | `1.15.0` | Portable workflows for engineering, review, design, context maintenance, and other focused tasks |
|
|
20
20
|
|
|
21
21
|
Versions are exact by design. An upstream release does not change an installed kit until this repository explicitly advances the dependency and publishes a new kit version. Runtime defects and package-specific feature requests belong in the linked repository; package selection and kit installation issues belong here.
|
|
@@ -47,6 +47,7 @@ Keep each fact in one authoritative layer:
|
|
|
47
47
|
- `/skills/generated-control-surface`: Optional state-derived, late-bound interface over truthful domain evidence, capabilities, workflows, and choices; it remains renderer-neutral, independent from the bridge skill, and owns no parallel state.
|
|
48
48
|
- `/skills/generative-apps`: Agent operating contract for compiling stable repeated Telegram interaction into deterministic standalone applications or bounded view/controller adapters whose buttons bypass model inference.
|
|
49
49
|
- `/skills/show-me`: Portable visual-explanation protocol with Telegram-aware phone-width Markdown and self-contained browser artifact guidance; it owns explanation shape and evidence honesty, not bridge transport.
|
|
50
|
+
- `Skill discovery`: Compiled npm/git packages expose bundled Skills only through `pi.skills`, preserving Pi package filters. A raw TypeScript extension checkout may contribute the source Skill root through `resources_discover`; compiled runtime and source runtime must never both own discovery.
|
|
50
51
|
- `/.agents/skills/telegram-bot`: Bot API lookup guidance and vendored `api.md`; keep the reference intact.
|
|
51
52
|
- `/.agents/skills/domain-dag`: Repository architecture guidance and validator.
|
|
52
53
|
|
|
@@ -82,7 +83,7 @@ Use the relevant local skill before non-trivial work in its domain. Keep skill o
|
|
|
82
83
|
- 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.
|
|
83
84
|
- Storage cutovers must reconcile actual consumer locations before correcting path adapters. Never normalize a relative historical reference into new authority or treat equal reference strings / empty canonical storage as source completeness. Exact-path preflight is lexical only; physical identity, historical coverage, writer closure and migration remain separate proofs.
|
|
84
85
|
- Workspace mutations acquire cross-process admission before their shared process-local gate and hold it through asynchronous API work and durable settlement. Topic lifecycle, complete unbound/reroute target handling, manual disconnect, and session-restart cleanup use profile-wide scope; either retained retirement-fence phase rejects them before state access. Cleanup scope spans intent publication, target mutation, persistence, and transport release. Detached reconciliation that mutates Thread state must reacquire fresh profile admission through the same gate; it cannot inherit a caller lease that ended before its timer runs. A live operation ID has one process-local caller: concurrent reuse is rejected before lease acquisition, while retry after the caller exits may resume exact durable authority.
|
|
85
|
-
- Workspace retirement is capacity-pressure-only. Elapsed time and heartbeat silence never trigger deletion; only complete `A`–`Z` exhaustion may propose the oldest continuously proven inactive, fully unprotected binding. Exact deletion, durable retirement, and fence completion precede slot reuse. Authorized demand-driven rotation retries one failed fresh allocation after retirement; restore-only follower startup never evicts. Release ordinary registration/provisioning leases before acquiring the destructive fence, retain the shared mutation gate across retirement, and validate the exact permit immediately before one non-retried deletion. Persist an exact method/target-matched rejection before withdrawing its intent; release that fence only after durable withdrawal, retaining the binding. A later attempt requires fresh operation authority. Protection reads must never repair, quarantine, or reset journals. Non-destructive owner detachment must atomically retain one exact Workspace binding and its letter while removing only its uniquely matched owner record and stamping first inactivity; it never manufactures Thread-deletion evidence or clears accepted work. Retained prune observations are bounded, non-routing and registration/profile/epoch/runtime-fenced. One unfinished preservation operation retains its admission identity across fresh-PID-proof retries; it can never become deletion authority. Leader quit requires completed delivery/polling/worker teardown under the captured session generation, profile and epoch; reload/new/resume/fork never establish inactivity. Unknown deletion outcomes retain their fence; only confirmed durable completion permits reuse.
|
|
86
|
+
- Workspace retirement is capacity-pressure-only. Elapsed time and heartbeat silence never trigger deletion; only complete `A`–`Z` exhaustion may propose the oldest continuously proven inactive, fully unprotected binding. Exact deletion, durable retirement, and fence completion precede slot reuse. Authorized demand-driven rotation retries one failed fresh allocation after retirement; restore-only follower startup never evicts. Release ordinary registration/provisioning leases before acquiring the destructive fence, retain the shared mutation gate across retirement, and validate the exact permit immediately before one non-retried deletion. Persist an exact method/target-matched rejection before withdrawing its intent; release that fence only after durable withdrawal, retaining the binding. A later attempt requires fresh operation authority. Protection reads must never repair, quarantine, or reset journals. Non-destructive owner detachment must atomically retain one exact Workspace binding and its letter while removing only its uniquely matched owner record and stamping first inactivity; it never manufactures Thread-deletion evidence or clears accepted work. During authenticated follower registration, the exact Workspace claim owns the letter: a mismatched retained target record is repaired to that claim before binding commit, while any unrelated binding that owns the stale letter remains untouched. Retained prune observations are bounded, non-routing and registration/profile/epoch/runtime-fenced. One unfinished preservation operation retains its admission identity across fresh-PID-proof retries; it can never become deletion authority. Leader quit requires completed delivery/polling/worker teardown under the captured session generation, profile and epoch; reload/new/resume/fork never establish inactivity. Unknown deletion outcomes retain their fence; only confirmed durable completion permits reuse.
|
|
86
87
|
- 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.
|
|
87
88
|
- 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.
|
|
88
89
|
- 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. Cached presence is not current execution proof: prepared custody must revalidate the exact queued owner/group without recovery and refuse offers or uncertain reads. Completion requires an exact removal acknowledgement, never merely `!ready`; a retained acknowledgement permits local cleanup only, not replay. 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.
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.51.3: Follower recovery and filterable Skills
|
|
8
|
+
|
|
9
|
+
- `Follower slot reconciliation`: Re-registration now treats the exact Workspace claim as canonical when a retained follower record carries a different slot. It repairs the record before binding commit, preserves the unrelated binding that owns the stale letter, and makes repeated registration idempotent instead of returning `Telegram Workspace binding claim changed.` forever.
|
|
10
|
+
- `Filterable packaged Skills`: Compiled npm/git installations now leave bundled Skill discovery to the `pi.skills` manifest, so Pi package filters can select or disable individual Skills. A raw TypeScript checkout under Pi's `extensions` directory still contributes its source Skill root through `resources_discover`, preserving local-repository development without double-owning installed resources.
|
|
11
|
+
|
|
7
12
|
## 0.51.2: Model switching and typing continuity
|
|
8
13
|
|
|
9
14
|
- `In-flight model switching`: Telegram model selection can again stop, switch, and continue any interruptible agent run in the current Pi session, including local/TUI work without an active Telegram prompt. The exact model-menu chat/Thread/message supplies fallback continuation ownership, active tools defer abort until settlement, and cancellation/session boundaries clear both selection and target state instead of returning a false busy response.
|
|
@@ -26,6 +26,8 @@ From git:
|
|
|
26
26
|
pi install git:github.com/llblab/pi-telegram
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
Installed npm/git packages expose bundled Skills through their `pi.skills` manifest, so Pi package filters can select individual Skills. A raw TypeScript checkout placed directly under Pi's `extensions` directory instead contributes its adjacent source Skills at runtime; the two discovery paths are mutually exclusive.
|
|
30
|
+
|
|
29
31
|
The extension requires Pi `0.84.4` or newer, matching the package's peer dependencies. Its Activity API uses the public `agent_settled` lifecycle event to keep retries/continuations under one activity identity and release that identity only after the run fully settles.
|
|
30
32
|
|
|
31
33
|
Pi is the primary and only officially supported host. Narrow host-neutral adapters preserve ordered prompt blocks and normalize synchronous or asynchronous legacy/generic settings services for Pi-compatible hosts, but this is best-effort compatibility rather than an OMP support guarantee. Alternate-host shims must still reproduce required Pi lifecycle semantics—especially `agent_settled`—and their maintainers own ongoing validation.
|
|
@@ -722,12 +722,30 @@ export function createTelegramBusFollowerTargetProvisioner(deps) {
|
|
|
722
722
|
: recoverableTarget && !pendingTargetRecovery
|
|
723
723
|
? await recoverRequestedTarget()
|
|
724
724
|
: await provisionTarget();
|
|
725
|
+
const alignResultWithWorkspaceSlot = () => {
|
|
726
|
+
if (!workspaceIdentity ||
|
|
727
|
+
result.record.slot === workspaceIdentity.slot)
|
|
728
|
+
return;
|
|
729
|
+
deps.recordRuntimeEvent("bus", "Telegram follower record slot reconciled to its Workspace claim", {
|
|
730
|
+
phase: "follower-register-slot-reconcile",
|
|
731
|
+
instanceId: registration.instanceId,
|
|
732
|
+
chatId: result.target.chatId,
|
|
733
|
+
threadId: result.target.threadId,
|
|
734
|
+
previousSlot: result.record.slot,
|
|
735
|
+
slot: workspaceIdentity.slot,
|
|
736
|
+
});
|
|
737
|
+
result = {
|
|
738
|
+
...result,
|
|
739
|
+
record: { ...result.record, slot: workspaceIdentity.slot },
|
|
740
|
+
};
|
|
741
|
+
};
|
|
742
|
+
alignResultWithWorkspaceSlot();
|
|
725
743
|
const crossSessionReuse = !!reconnectRecord &&
|
|
726
744
|
reconnectRecord.instanceId !== registration.instanceId;
|
|
727
745
|
if (reconnectRecord && !crossSessionReuse) {
|
|
728
746
|
const nowMs = getNowMs();
|
|
729
747
|
const refreshedRecord = deps.topicTargetStore.upsert({
|
|
730
|
-
...
|
|
748
|
+
...result.record,
|
|
731
749
|
instanceId: registration.instanceId,
|
|
732
750
|
updatedAtMs: nowMs,
|
|
733
751
|
lastSyncObservedAtMs: nowMs,
|
|
@@ -804,7 +822,7 @@ export function createTelegramBusFollowerTargetProvisioner(deps) {
|
|
|
804
822
|
else if (crossSessionReuse && reconnectRecord) {
|
|
805
823
|
const nowMs = getNowMs();
|
|
806
824
|
const transferredRecord = deps.topicTargetStore.upsert({
|
|
807
|
-
...
|
|
825
|
+
...result.record,
|
|
808
826
|
profileKey: followerProfileKey,
|
|
809
827
|
owner: followerOwner.kind === "manual-follower"
|
|
810
828
|
? followerOwner
|
|
@@ -861,6 +879,7 @@ export function createTelegramBusFollowerTargetProvisioner(deps) {
|
|
|
861
879
|
}
|
|
862
880
|
}
|
|
863
881
|
if (workspaceIdentity) {
|
|
882
|
+
alignResultWithWorkspaceSlot();
|
|
864
883
|
const workspaceCommit = Threads.commitTelegramWorkspaceProvisionBinding({
|
|
865
884
|
store: deps.topicTargetStore,
|
|
866
885
|
instanceId: registration.instanceId,
|
|
@@ -872,7 +891,7 @@ export function createTelegramBusFollowerTargetProvisioner(deps) {
|
|
|
872
891
|
...(result.record.threadName
|
|
873
892
|
? { threadName: result.record.threadName }
|
|
874
893
|
: {}),
|
|
875
|
-
|
|
894
|
+
slot: workspaceIdentity.slot,
|
|
876
895
|
journalBindingKeys: [followerProfileKey],
|
|
877
896
|
journalBindingsComplete: true,
|
|
878
897
|
updatedAtMs: getNowMs(),
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bundled Telegram skill discovery
|
|
3
3
|
* Zones: pi agent, telegram guidance
|
|
4
|
-
* Owns source-checkout
|
|
4
|
+
* Owns source-checkout skill contribution; installed packages use their manifest
|
|
5
5
|
*/
|
|
6
6
|
import type { ExtensionAPI } from "./pi.ts";
|
|
7
7
|
export declare const TELEGRAM_SKILLS_PATH: string;
|
|
8
|
-
export declare function registerTelegramSkillDiscovery(pi: Pick<ExtensionAPI, "on"
|
|
8
|
+
export declare function registerTelegramSkillDiscovery(pi: Pick<ExtensionAPI, "on">, modulePath?: string): boolean;
|
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bundled Telegram skill discovery
|
|
3
3
|
* Zones: pi agent, telegram guidance
|
|
4
|
-
* Owns source-checkout
|
|
4
|
+
* Owns source-checkout skill contribution; installed packages use their manifest
|
|
5
5
|
*/
|
|
6
|
+
import { extname } from "node:path";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
+
const TELEGRAM_SKILLS_MODULE_PATH = fileURLToPath(import.meta.url);
|
|
7
9
|
export const TELEGRAM_SKILLS_PATH = fileURLToPath(new URL("../skills", import.meta.url));
|
|
8
|
-
export function registerTelegramSkillDiscovery(pi) {
|
|
10
|
+
export function registerTelegramSkillDiscovery(pi, modulePath = TELEGRAM_SKILLS_MODULE_PATH) {
|
|
11
|
+
// A raw source extension has no package manifest owner. Compiled npm/git
|
|
12
|
+
// packages do, so contributing again would bypass their resource filters.
|
|
13
|
+
if (extname(modulePath) !== ".ts")
|
|
14
|
+
return false;
|
|
9
15
|
pi.on("resources_discover", () => ({
|
|
10
16
|
skillPaths: [TELEGRAM_SKILLS_PATH],
|
|
11
17
|
}));
|
|
18
|
+
return true;
|
|
12
19
|
}
|
|
@@ -151,7 +151,7 @@ A registered instance exposes:
|
|
|
151
151
|
|
|
152
152
|
## Approved Next Contract: Directory Names And Reclaimable Slots
|
|
153
153
|
|
|
154
|
-
Status: approved design with a locally tested pure selection policy in `lib/workspace-slots.ts` and profile-isolated display preference persistence/default resolution in `lib/config.ts`. Workspace claims now reserve global letters before provisioning and preserve legacy binding keys. An exact claim assigns the first free letter to a missing-slot binding or the selected member of a duplicate-slot set, but persistence waits for successful target recovery; unresolved duplicates block unrelated fresh allocation. Sticky suffix metadata and acknowledged `displayTitle` persist in Workspace bindings. `lib/thread-display.ts` provides the three-mode projection plus serialized title reconciliation wired into leader startup and follower registration. Heartbeat ACKs carry acknowledged display titles to followers and the current-thread/TUI projection uses them without changing restoration identity. Settings now exposes Letters (default), Names, and Directories; follower changes use the capability-gated leader-owned setting path. Live bot chooser/notice labels and cross-instance agent-target resolution use acknowledged titles without granting routing authority. The Thread store now persists the first proven `inactiveSinceMs` transition with exact confirmed target absence or fenced non-destructive detachment of a confirmed-dead follower or quiescent quitting leader whose tab is preserved; successful active provisioning clears it. Pressure selection, intents, mocked execution, and recovery are implemented. A 2/2 same-model independent post-fix quorum cleared the admission-composition blocker at 0.96 confidence per reviewer, and the operator has since authorized demand-driven rotation. Fresh allocation now invokes the existing retirement lifecycle under full capacity; `BACKLOG.md` owns remaining disposable-client acceptance.
|
|
154
|
+
Status: approved design with a locally tested pure selection policy in `lib/workspace-slots.ts` and profile-isolated display preference persistence/default resolution in `lib/config.ts`. Workspace claims now reserve global letters before provisioning and preserve legacy binding keys. An exact claim assigns the first free letter to a missing-slot binding or the selected member of a duplicate-slot set, but persistence waits for successful target recovery; unresolved duplicates block unrelated fresh allocation. If authenticated follower re-registration finds that its retained target record carries another letter, the exact claim is canonical: registration repairs that record before binding commit and leaves the unrelated binding that owns the stale letter intact. Sticky suffix metadata and acknowledged `displayTitle` persist in Workspace bindings. `lib/thread-display.ts` provides the three-mode projection plus serialized title reconciliation wired into leader startup and follower registration. Heartbeat ACKs carry acknowledged display titles to followers and the current-thread/TUI projection uses them without changing restoration identity. Settings now exposes Letters (default), Names, and Directories; follower changes use the capability-gated leader-owned setting path. Live bot chooser/notice labels and cross-instance agent-target resolution use acknowledged titles without granting routing authority. The Thread store now persists the first proven `inactiveSinceMs` transition with exact confirmed target absence or fenced non-destructive detachment of a confirmed-dead follower or quiescent quitting leader whose tab is preserved; successful active provisioning clears it. Pressure selection, intents, mocked execution, and recovery are implemented. A 2/2 same-model independent post-fix quorum cleared the admission-composition blocker at 0.96 confidence per reviewer, and the operator has since authorized demand-driven rotation. Fresh allocation now invokes the existing retirement lifecycle under full capacity; `BACKLOG.md` owns remaining disposable-client acceptance.
|
|
155
155
|
|
|
156
156
|
The pure policy distinguishes a free letter, a proposed pressure-reclamation victim, and protected/invalid capacity. Its caller must supply a validated profile-wide snapshot, reservations, proven inactivity start, and explicit protection classification; duplicate legacy letters block selection. The policy performs no filesystem or Telegram operations and does not establish liveness or deletion authority. It proposes a victim only when every profile-wide letter is occupied or reserved; elapsed time alone never triggers retirement.
|
|
157
157
|
|
|
@@ -71,7 +71,7 @@ Every assistant-authored HTML comment is transport-private on Telegram: previews
|
|
|
71
71
|
- `telegram_channel_post(action, operation_id, markdown?)` edits or deletes one exact `published` record returned by `telegram_channel_posts`. Edit requires Markdown and delete forbids it. A media-post edit replaces the caption through `editMessageCaption`, while a text post uses `editMessageText`; both render Markdown formatting, including spoilers, as Telegram HTML. The direct leader fences the tool call as outcome-unknown before the mutation call, so ambiguous failures are never replayed automatically.
|
|
72
72
|
- `telegram_channel_posts(chat_id?, limit?)` lists newest bounded records from the active profile's agent-owned post journal. It returns publication, edit/delete outcome-unknown, confirmed, and deleted local records only, including retained media kind/file name/size/SHA-256 identity; it never reads or claims completeness for Telegram channel history. This explicit successful listing is the only tool response that exposes retained authored Markdown; channel tool failures use fixed redacted messages, while pre-issuance media/caption validation errors stay actionable.
|
|
73
73
|
- `telegram_message(text, chat_id?, media?, channel?, thread_id?, thread?)` sends a direct Telegram Markdown message when this Pi instance owns `/telegram-connect` or is registered with the multi-instance bus. A public `@username`, or an exact negative numeric channel ID with `channel: true`, is passed as `chat_id` without a local registry; channel delivery requires the direct leader, and Telegram enforces whether the bot has channel posting permission. Channel delivery accepts `media` as one local `.jpg`/`.jpeg`/`.png`/`.webp` photo or `.mp4` video (photo ≤ 10 MiB, video ≤ 50 MiB), uploaded through multipart `sendPhoto`/`sendVideo` with `text` as its HTML caption (≤ 1024 visible characters); unsupported media types and albums are rejected before issuance, and the durable channel-post journal binds media identity and caption so duplicate or lost-acknowledgement retries never re-upload. `thread` accepts a live numeric Thread id or its current acknowledged display title; name matching is case-insensitive and fails closed when absent or ambiguous, while delivery captures the numeric target. During an active Telegram turn, omitted targeting and an explicit target equal to that turn are rejected so the ordinary final-reply path remains the sole current-target response; an explicit different chat/thread target remains allowed. Outside active turns, paired/default local/TUI delivery remains unchanged. Top-level `telegram_button` comments inside `text` are parsed with the same planner used for normal replies and attached to that message; buttons are never standalone Telegram messages.
|
|
74
|
-
- The bundled `telegram-bridge` Skill owns action syntax, target routing, Threaded Mode, formatting, Generative App operation, and profile-specific debugging guidance. The bundled `show-me` Skill owns portable evidence-honest explanations and adapts them to phone-width Markdown or self-contained HTML artifacts when Telegram is the active surface. The regular prompt routes applicable turns to these and the other bundled Skills. `telegram_attach`, `telegram_bind`, and `telegram_message` remain registered but are model-active only while this instance owns direct transport or holds a live follower registration; disconnect/loss suppresses their schemas and prompt metadata, and recovery restores only the operator's previously active pi-telegram subset.
|
|
74
|
+
- The bundled `telegram-bridge` Skill owns action syntax, target routing, Threaded Mode, formatting, Generative App operation, and profile-specific debugging guidance. The bundled `show-me` Skill owns portable evidence-honest explanations and adapts them to phone-width Markdown or self-contained HTML artifacts when Telegram is the active surface. The regular prompt routes applicable turns to these and the other bundled Skills. Compiled npm/git installations expose them through the package `pi.skills` manifest so Pi resource filters remain authoritative; a raw TypeScript checkout under Pi's `extensions` directory contributes its source Skill root at runtime instead. `telegram_attach`, `telegram_bind`, and `telegram_message` remain registered but are model-active only while this instance owns direct transport or holds a live follower registration; disconnect/loss suppresses their schemas and prompt metadata, and recovery restores only the operator's previously active pi-telegram subset.
|
|
75
75
|
- `telegram_voice` hidden comments request Telegram-native voice delivery through `{text}`, `{text|lang}`, `{text|lang|rate}`, or a JSON object. JSON is the fallback for multiline content, named fields, or escaping; equivalent `text` or `value` supplies the spoken payload, with explicit `text` taking precedence.
|
|
76
76
|
- `telegram_button` hidden comments create footer buttons; standalone column-zero triple-backtick `telegram_button` blocks create button rows between paragraphs in Native Rich Markdown. Both accept the same singleton or mixed JSON/CML matrix and share prompt/app routing; fenced blocks also accept adjacent top-level JSON/CML objects without an outer array or commas as vertical singleton rows. Native rows allow at most eight buttons and must fit one Rich Message chunk; invalid or incomplete blocks register nothing. Drafts hide action fences. HTML compatibility projects fenced controls into the footer. In-body clicks acknowledge without recoloring the Rich body; selected-style highlighting remains footer-only. One marker accepts a JSON object, adaptive JSON/CML matrix, or positional [Compact Matrix Literal](./compact-matrix-literal.md). Named JSON objects and positional cells may coexist in one matrix or row; separators are optional and one trailing comma is tolerated at matrix, row, and JSON-object boundaries. Top-level cells become full-width rows, while nested rows group one or more buttons horizontally without an artificial parser-width cap. CML uses `{value}`, `{label|prompt}`, prompt-only `{|prompt}`, or the corresponding three-atom form with `selected_style`; an omitted label uses the existing prompt-as-label fallback, and the optional third atom requires a non-empty prompt and accepts only `primary`, `success`, or `danger`. A fourth atom accepts `1` or `true` (disabled), and `0` or `false` (enabled), with exact lowercase spelling; an omitted fourth position stays enabled, and the third atom may be empty in this form (`{|Next||1}`). JSON uses boolean `disabled`. Disabled cells need no prompt or selected style: `{Next|||1}` is label-only and `{|||1}` is blank (JSON `{"label":"Next","disabled":true}` and `{"disabled":true}`). The Telegram renderer supplies a non-breaking space only when the label is empty. Disabled cells stay visible but carry `disabled: {}` instead of callback data and register no prompt or bound action; invalid disabled values reject the candidate matrix. It trims atom boundaries and supports only the minimal escapes `\|`, `\}`, and `\\`. Prefer one matrix comment for multiple buttons. Use JSON `label` plus `prompt`, or `value` when both strings are identical. Action markers are colon-free; colon-prefixed payloads are rejected. Use top-level column-zero action wrappers, outside quotes, lists, or enclosing code examples. Ordinary code fences and larger outer fences preserve literal examples; bare JSON/CML in prose never activates.
|
|
77
77
|
|
|
@@ -1210,13 +1210,36 @@ export function createTelegramBusFollowerTargetProvisioner(
|
|
|
1210
1210
|
: recoverableTarget && !pendingTargetRecovery
|
|
1211
1211
|
? await recoverRequestedTarget()
|
|
1212
1212
|
: await provisionTarget();
|
|
1213
|
+
const alignResultWithWorkspaceSlot = (): void => {
|
|
1214
|
+
if (
|
|
1215
|
+
!workspaceIdentity ||
|
|
1216
|
+
result.record.slot === workspaceIdentity.slot
|
|
1217
|
+
) return;
|
|
1218
|
+
deps.recordRuntimeEvent(
|
|
1219
|
+
"bus",
|
|
1220
|
+
"Telegram follower record slot reconciled to its Workspace claim",
|
|
1221
|
+
{
|
|
1222
|
+
phase: "follower-register-slot-reconcile",
|
|
1223
|
+
instanceId: registration.instanceId,
|
|
1224
|
+
chatId: result.target.chatId,
|
|
1225
|
+
threadId: result.target.threadId,
|
|
1226
|
+
previousSlot: result.record.slot,
|
|
1227
|
+
slot: workspaceIdentity.slot,
|
|
1228
|
+
},
|
|
1229
|
+
);
|
|
1230
|
+
result = {
|
|
1231
|
+
...result,
|
|
1232
|
+
record: { ...result.record, slot: workspaceIdentity.slot },
|
|
1233
|
+
};
|
|
1234
|
+
};
|
|
1235
|
+
alignResultWithWorkspaceSlot();
|
|
1213
1236
|
const crossSessionReuse =
|
|
1214
1237
|
!!reconnectRecord &&
|
|
1215
1238
|
reconnectRecord.instanceId !== registration.instanceId;
|
|
1216
1239
|
if (reconnectRecord && !crossSessionReuse) {
|
|
1217
1240
|
const nowMs = getNowMs();
|
|
1218
1241
|
const refreshedRecord = deps.topicTargetStore.upsert({
|
|
1219
|
-
...
|
|
1242
|
+
...result.record,
|
|
1220
1243
|
instanceId: registration.instanceId,
|
|
1221
1244
|
updatedAtMs: nowMs,
|
|
1222
1245
|
lastSyncObservedAtMs: nowMs,
|
|
@@ -1300,7 +1323,7 @@ export function createTelegramBusFollowerTargetProvisioner(
|
|
|
1300
1323
|
} else if (crossSessionReuse && reconnectRecord) {
|
|
1301
1324
|
const nowMs = getNowMs();
|
|
1302
1325
|
const transferredRecord = deps.topicTargetStore.upsert({
|
|
1303
|
-
...
|
|
1326
|
+
...result.record,
|
|
1304
1327
|
profileKey: followerProfileKey,
|
|
1305
1328
|
owner:
|
|
1306
1329
|
followerOwner.kind === "manual-follower"
|
|
@@ -1361,6 +1384,7 @@ export function createTelegramBusFollowerTargetProvisioner(
|
|
|
1361
1384
|
}
|
|
1362
1385
|
}
|
|
1363
1386
|
if (workspaceIdentity) {
|
|
1387
|
+
alignResultWithWorkspaceSlot();
|
|
1364
1388
|
const workspaceCommit =
|
|
1365
1389
|
Threads.commitTelegramWorkspaceProvisionBinding({
|
|
1366
1390
|
store: deps.topicTargetStore,
|
|
@@ -1373,7 +1397,7 @@ export function createTelegramBusFollowerTargetProvisioner(
|
|
|
1373
1397
|
...(result.record.threadName
|
|
1374
1398
|
? { threadName: result.record.threadName }
|
|
1375
1399
|
: {}),
|
|
1376
|
-
|
|
1400
|
+
slot: workspaceIdentity.slot,
|
|
1377
1401
|
journalBindingKeys: [followerProfileKey],
|
|
1378
1402
|
journalBindingsComplete: true,
|
|
1379
1403
|
updatedAtMs: getNowMs(),
|
|
@@ -1,21 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Bundled Telegram skill discovery
|
|
3
3
|
* Zones: pi agent, telegram guidance
|
|
4
|
-
* Owns source-checkout
|
|
4
|
+
* Owns source-checkout skill contribution; installed packages use their manifest
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { extname } from "node:path";
|
|
7
8
|
import { fileURLToPath } from "node:url";
|
|
8
9
|
|
|
9
10
|
import type { ExtensionAPI } from "./pi.ts";
|
|
10
11
|
|
|
12
|
+
const TELEGRAM_SKILLS_MODULE_PATH = fileURLToPath(import.meta.url);
|
|
13
|
+
|
|
11
14
|
export const TELEGRAM_SKILLS_PATH = fileURLToPath(
|
|
12
15
|
new URL("../skills", import.meta.url),
|
|
13
16
|
);
|
|
14
17
|
|
|
15
18
|
export function registerTelegramSkillDiscovery(
|
|
16
19
|
pi: Pick<ExtensionAPI, "on">,
|
|
17
|
-
|
|
20
|
+
modulePath = TELEGRAM_SKILLS_MODULE_PATH,
|
|
21
|
+
): boolean {
|
|
22
|
+
// A raw source extension has no package manifest owner. Compiled npm/git
|
|
23
|
+
// packages do, so contributing again would bypass their resource filters.
|
|
24
|
+
if (extname(modulePath) !== ".ts") return false;
|
|
18
25
|
pi.on("resources_discover", () => ({
|
|
19
26
|
skillPaths: [TELEGRAM_SKILLS_PATH],
|
|
20
27
|
}));
|
|
28
|
+
return true;
|
|
21
29
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-kit",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@llblab/pi-codex-usage": "0.10.0",
|
|
46
46
|
"@llblab/pi-grow-loop": "0.8.1",
|
|
47
47
|
"@llblab/pi-state-flow": "0.18.1",
|
|
48
|
-
"@llblab/pi-telegram": "0.51.
|
|
48
|
+
"@llblab/pi-telegram": "0.51.3",
|
|
49
49
|
"@llblab/skills": "1.15.0"
|
|
50
50
|
},
|
|
51
51
|
"bundledDependencies": [
|