@llblab/pi-kit 0.22.0 → 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 +12 -0
- package/README.md +1 -1
- package/node_modules/@llblab/pi-telegram/AGENTS.md +4 -2
- package/node_modules/@llblab/pi-telegram/BACKLOG.md +3 -8
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +11 -0
- package/node_modules/@llblab/pi-telegram/README.md +3 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +1 -4
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.d.ts +0 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-api.js +0 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/bus-leader.js +22 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +1 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.d.ts +3 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/menu-model.js +17 -3
- package/node_modules/@llblab/pi-telegram/dist/lib/menu.d.ts +5 -7
- package/node_modules/@llblab/pi-telegram/dist/lib/menu.js +4 -5
- package/node_modules/@llblab/pi-telegram/dist/lib/model.d.ts +12 -12
- package/node_modules/@llblab/pi-telegram/dist/lib/model.js +49 -35
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +0 -2
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +1 -8
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +0 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/runtime.d.ts +3 -6
- package/node_modules/@llblab/pi-telegram/dist/lib/runtime.js +4 -55
- 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/lib/telegram-api.js +36 -10
- package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +8 -8
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +3 -3
- package/node_modules/@llblab/pi-telegram/docs/public-api.md +1 -1
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +7 -4
- package/node_modules/@llblab/pi-telegram/lib/bus-api.ts +0 -7
- package/node_modules/@llblab/pi-telegram/lib/bus-leader.ts +27 -3
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +1 -3
- package/node_modules/@llblab/pi-telegram/lib/menu-model.ts +25 -4
- package/node_modules/@llblab/pi-telegram/lib/menu.ts +14 -9
- package/node_modules/@llblab/pi-telegram/lib/model.ts +77 -44
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +1 -8
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +0 -1
- package/node_modules/@llblab/pi-telegram/lib/runtime.ts +7 -58
- package/node_modules/@llblab/pi-telegram/lib/skills.ts +10 -2
- package/node_modules/@llblab/pi-telegram/lib/telegram-api.ts +43 -17
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
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
|
+
|
|
11
|
+
## 0.22.1 - 2026-09-24
|
|
12
|
+
|
|
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.
|
|
14
|
+
- `Typing Continuity`: Native presence refreshes only the assigned Thread, coalesces concurrent chat actions, shares Telegram cooldown across Thread keys, and preserves an existing agent typing loop through compaction. Best-effort typing failures no longer replace healthy leader/follower status with an error.
|
|
15
|
+
- `Package Cohort`: Keeps every other bundled package at its current exact version. Package membership, resource paths, load order, Pi minimum, and bundled Skill ownership remain unchanged.
|
|
16
|
+
|
|
5
17
|
## 0.22.0 - 2026-09-23
|
|
6
18
|
|
|
7
19
|
- `Named npm Extensions`: Advances the exact Clean Room pin to `0.2.0`. `/clean-room <package-name>` now loads installed npm Pi extensions by name, including scoped packages, while preserving explicit resource isolation and project-local precedence.
|
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.
|
|
@@ -96,10 +97,11 @@ Use the relevant local skill before non-trivial work in its domain. Keep skill o
|
|
|
96
97
|
|
|
97
98
|
- 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.
|
|
98
99
|
- `/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. A busy `/next` marks only its exact active Telegram turn: terminal abort settlement attempts one explicit abort notice before the exact selected queued prompt receives its dispatch notice. Notice failure is diagnostic and cannot block dispatch; a later `/abort` or `/stop` cancels both pending transition notices before taking ownership. Target command adapters must forward these transition ports. A successful dispatch notice keeps the queued prompt's one reply-header claim across agent start, so later messages in that turn never repeat it.
|
|
100
|
+
- A model selection from an authorized Telegram target may stop and continue any interruptible active agent run in that same Pi session, including local/TUI work. Queue the synthetic control-lane continuation before aborting; when no Telegram prompt owns the run, bind continuation and reply ownership to the exact model-menu chat/Thread/message. Delay abort until every active tool execution settles, and clear both selection and fallback target on cancellation, agent start, settlement, or session replacement.
|
|
99
101
|
- 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.
|
|
100
102
|
- 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.
|
|
101
103
|
- `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.
|
|
102
|
-
- 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.
|
|
104
|
+
- 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. Compaction may stop only a typing loop it actually started; it must preserve a pre-existing agent-owned loop. Refresh only the assigned Thread on a conservative three-second cadence; do not mirror typing to aggregate `All` and multiply shared-chat flood pressure. Typing is best-effort presence only: a failed exact-target action remains a structured diagnostic and must not replace a healthy connected/leader/follower status with `error`. Do not invent extra in-chat work indicators or emit activity for startup/connect/reload/recovery alone.
|
|
103
105
|
- 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.
|
|
104
106
|
- 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. After leader startup, automatic display contraction waits one follower-staleness window so election and follower re-registration cannot briefly remove and restore an acknowledged same-cwd suffix; stop/start generation cancels stale reconciliation. 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.
|
|
105
107
|
|
|
@@ -2,16 +2,11 @@
|
|
|
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
|
-
- [ ] `
|
|
6
|
-
- [ ] `Manual Thread naming` (`live-acceptance-gated`): Exercise command ordering, dialog/cancel, invalid input, duplicate callbacks, leader/follower rename, manual override, and reset against disposable Threads. Local generation/target fencing and Bot-API-wait replacement regressions are complete.
|
|
7
|
-
- [ ] [`Workspace Thread recovery`](./docs/multi-instance-bus.md) (`operator-/environment-gated`): Finish live and platform evidence without weakening exact owner, admission, journal, or deletion fences.
|
|
8
|
-
- [ ] Confirm the retired K Thread/history is absent after the successful full-A–Z pressure rotation, and repeat the rebuilt leader-election smoke to verify a returning same-directory follower causes no temporary suffix contraction.
|
|
9
|
-
- [ ] Complete follower Restore, inaccessible-callback, already-absent chooser, stale-target, Singleton↔Threaded, and native-Windows acceptance with disposable targets; preserve accepted work and current ownership.
|
|
5
|
+
- [ ] [`Workspace Thread recovery`](./docs/multi-instance-bus.md) (`environment-gated`): Complete native-Windows coverage for follower restore, inaccessible callbacks, stale targets, and Singleton↔Threaded transitions while preserving accepted work and current ownership.
|
|
10
6
|
- [ ] Select an approved exact-absence observation before implementing ambiguous `deletion-issued` recovery. Never replay an unknown deletion or infer absence from cache, heartbeat silence, empty `editForumTopic`, or `sendChatAction` success.
|
|
11
7
|
- [ ] [`Automatic pairing and follower input custody`](./docs/architecture.md#automatic-pairing-confirmation-design) (`activation-gated`): Keep production custody disabled until historical sources and physical references are reconciled, all legacy writers/consumers are retired or excluded under operator authority, migration is complete, and every participating peer supports the final protocol. Preserve outcome-unknown running work, exact receipt/group authority, authenticated handoff, and rollback/downgrade safety. Then compose the prepared v3 lifecycle/bus ports, add bounded operator disposition for retained legacy retry state, and run real-process plus native-Windows acceptance.
|
|
12
|
-
- [ ] `Thread Cleanup Manager` (`activation-gated`): Production currently exposes non-destructive review only. Before enabling deletion, compose the exact cleanup fence/permit coordinator
|
|
13
|
-
- [ ] `Channel operations` (`live-acceptance-gated`): On a disposable channel with real sender rights, verify media rejection, duplicate-send suppression, caption edit, and the durable authored-post create/list/edit/delete flow. Preserve direct-leader authority and outcome-unknown no-replay semantics.
|
|
8
|
+
- [ ] `Thread Cleanup Manager` (`activation-gated`): Production currently exposes non-destructive review only. Before enabling deletion, compose the exact cleanup fence/permit coordinator and prove successor recovery for `commit-ready` without transport replay. Unknown outcomes remain retained.
|
|
14
9
|
- [ ] [`Inference-bypass Generative Apps`](./docs/generative-apps.md) (`hardening`): Complete removal/replacement recovery, process-birth lock recovery, follower routing, refresh/backoff, stale revision rejection, redacted failure diagnostics, CML/JSON parity, and no-model-turn acceptance. Extend the Music Player adapter evidence across volume, toggle, previous, status, restart, unavailable backend, and bounded process failures.
|
|
15
10
|
- [ ] `External compatibility acceptance` (`environment-gated`): Confirm one `telegram_bind` request through the reporter's OMP + llama-server build ([#267](https://github.com/llblab/pi-telegram/issues/267)); validate environment-backed bot-token setup/status for default and named profiles; and extend native in-body controls to a second client, follower routing, disabled cells, and app-method revision rejection.
|
|
16
|
-
- [ ] `Queue recovery evidence` (`operator-gated`): Establish a supported preservation/discard path for any already-wedged in-memory queue before mutation.
|
|
11
|
+
- [ ] `Queue recovery evidence` (`operator-gated`): Establish a supported preservation/discard path for any already-wedged in-memory queue before mutation. Never clear journals, replay settled input, or treat restart as repair.
|
|
17
12
|
- [ ] `Show Me ownership transfer` (`release-coordination`): After a pi-telegram release containing the bundled `show-me` Skill is available, remove the former copy from `@llblab/skills` under that package's release policy, then update Pi Kit. The coordinated final install must expose exactly one Skill identity.
|
|
@@ -4,6 +4,17 @@
|
|
|
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
|
+
|
|
12
|
+
## 0.51.2: Model switching and typing continuity
|
|
13
|
+
|
|
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.
|
|
15
|
+
- `Typing continuity`: Native presence now refreshes only the assigned Thread every three seconds instead of duplicating each action into aggregate `All`. The leader admits at most one concurrent action per chat and shares Telegram 429 cooldown across that chat's Thread keys. Compaction preserves a pre-existing agent typing loop instead of claiming and stopping it, addressing both multi-instance flicker and lifecycle gaps.
|
|
16
|
+
- `Typing status truthfulness`: Failed `sendChatAction(typing)` calls remain bounded structured diagnostics but no longer replace a healthy connected/leader/follower status with `error`. Optional presence failures therefore cannot misreport transport health after the actual turn and delivery continue successfully.
|
|
17
|
+
|
|
7
18
|
## 0.51.1: Queue transitions and follower heartbeat stability
|
|
8
19
|
|
|
9
20
|
- `/next lifecycle notices`: Target command composition now forwards request, exact-turn marker, and cancellation ports. Busy `/next` orders abort then selected-prompt dispatch notices; failures cannot block dispatch, and `/abort`/`/stop` cancel stale notices. A successful dispatch notice keeps first-reply ownership through agent start, so later answer messages do not repeat the queued-prompt reply. Rapid ordinary messages remain distinct turns.
|
|
@@ -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.
|
|
@@ -123,7 +125,7 @@ Enable the optional capabilities the bridge needs in the [@BotFather](https://t.
|
|
|
123
125
|
| Queue control | Inspect waiting turns, keep or skip stale work, promote important prompts, continue, abort, stop, or force the next queued item. | Long Pi tasks keep running while new mobile prompts stay visible and controllable instead of interrupting or disappearing. |
|
|
124
126
|
| Operator menu | Use `/start` for status, prompt templates, model, thinking, settings, queue, extension sections, and diagnostics. | The bot is an operator panel, not a command cheat sheet. |
|
|
125
127
|
| Prompt templates | Run Pi prompt templates as Telegram-safe commands such as `/fix_tests`. | Reusable local workflows become phone-accessible without exposing arbitrary terminal commands. |
|
|
126
|
-
| Model and thinking | Switch model or thinking level from Telegram through safe continuation flows. | Mobile control can
|
|
128
|
+
| Model and thinking | Switch model or thinking level from Telegram through safe continuation flows, including an active local/TUI agent run. | Mobile control can stop, switch, and resume in the same session context instead of returning a false busy dead end. |
|
|
127
129
|
| Compaction | Confirm `/compact`, show native active status during compaction, and preserve Telegram-owned turn semantics. | Context maintenance is visible and safe from the phone. |
|
|
128
130
|
| Draft previews | Show Telegram's native `…typing` indicator whenever the connected instance is doing agent work, or enable Rich Draft previews for streamed answer text. | Local prompts, Telegram turns, and autonomous continuations remain visibly active while draft visibility stays independent from final rendering. |
|
|
129
131
|
| Activity | Keep the default `verbose` technical surface, show only `thinking`, show only `tools`, or select `quiet` for answer-only delivery. Every instance reloads this shared file-backed choice before a new agent run; thinking accumulates for two seconds and then updates at most every two seconds in a headerless expandable quote, while each tool uses one iconless closed root row containing nested evidence details. | Persistent collapsed technical activity minimizes chat height and stays bounded, redacted, target-fenced, free of URL previews, and visually separate from semantic assistant answers. |
|
|
@@ -676,10 +676,7 @@ export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime,
|
|
|
676
676
|
return false;
|
|
677
677
|
const turn = activeTurnRuntime.get();
|
|
678
678
|
const target = turn?.target ?? proactivePushTargetGetter();
|
|
679
|
-
promptDispatchRuntime.startTypingLoop(ctx, turn?.chatId ?? target?.chatId, {
|
|
680
|
-
target,
|
|
681
|
-
});
|
|
682
|
-
return true;
|
|
679
|
+
return (promptDispatchRuntime.startTypingLoop(ctx, turn?.chatId ?? target?.chatId, { target }) !== false);
|
|
683
680
|
};
|
|
684
681
|
const startActiveTurnTypingLoop = (ctx) => {
|
|
685
682
|
if (uiPromptActive)
|
|
@@ -14,5 +14,4 @@ export interface TelegramBusAwareApiRuntimeDeps {
|
|
|
14
14
|
threadId?: number;
|
|
15
15
|
} | undefined;
|
|
16
16
|
}
|
|
17
|
-
export declare function createTelegramAggregateTypingActionSender(runtime: Pick<TelegramBridgeApiRuntime, "call">): (chatId: number) => Promise<unknown>;
|
|
18
17
|
export declare function createTelegramBusAwareApiRuntime(deps: TelegramBusAwareApiRuntimeDeps): TelegramBridgeApiRuntime;
|
|
@@ -37,9 +37,6 @@ function markFollowerCrossTargetDelivery(body, defaultTarget) {
|
|
|
37
37
|
function rejectTelegramDirectOwnership(method) {
|
|
38
38
|
return Promise.reject(new Error(`Telegram ${method} requires direct transport ownership.`));
|
|
39
39
|
}
|
|
40
|
-
export function createTelegramAggregateTypingActionSender(runtime) {
|
|
41
|
-
return (chatId) => runtime.call("sendChatAction", { chat_id: chatId, action: "typing" });
|
|
42
|
-
}
|
|
43
40
|
export function createTelegramBusAwareApiRuntime(deps) {
|
|
44
41
|
return {
|
|
45
42
|
call(method, body, options) {
|
|
@@ -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(),
|
|
@@ -514,7 +514,6 @@ export default function (pi) {
|
|
|
514
514
|
typing,
|
|
515
515
|
getDefaultChatId: proactivePushChatIdGetter,
|
|
516
516
|
sendTypingAction,
|
|
517
|
-
sendAggregateTypingAction: BusApi.createTelegramAggregateTypingActionSender(telegramApiRuntime),
|
|
518
517
|
updateStatus,
|
|
519
518
|
isContextActive: telegramSessionContextStore.isCurrent,
|
|
520
519
|
getTransportAuthority() {
|
|
@@ -1353,7 +1352,7 @@ export default function (pi) {
|
|
|
1353
1352
|
loadConfig: configStore.load,
|
|
1354
1353
|
setQueuedItems: telegramQueueStore.setQueuedItems,
|
|
1355
1354
|
setCurrentModel: currentModelRuntime.set,
|
|
1356
|
-
setPendingModelSwitch:
|
|
1355
|
+
setPendingModelSwitch: modelSwitchController.clearPendingSwitch,
|
|
1357
1356
|
syncCounters: queue.syncCounters,
|
|
1358
1357
|
syncFlags: lifecycle.syncFlags,
|
|
1359
1358
|
bindDeferredDispatchContext: deferredQueueDispatchRuntime.bind,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Owns model-menu state, scoped model pages, model callback planning, and model-menu message rendering
|
|
5
5
|
*/
|
|
6
6
|
import type { TelegramInlineKeyboardMarkup } from "./keyboard.ts";
|
|
7
|
-
import { type MenuModel, type ScopedTelegramModel, type ThinkingLevel } from "./model.ts";
|
|
7
|
+
import { type MenuModel, type ScopedTelegramModel, type TelegramModelSwitchContinuationSource, type ThinkingLevel } from "./model.ts";
|
|
8
8
|
export type TelegramModelScope = "all" | "scoped";
|
|
9
9
|
export interface TelegramModelMenuState<TModel extends MenuModel = MenuModel> {
|
|
10
10
|
chatId: number;
|
|
@@ -93,8 +93,8 @@ export type TelegramModelMenuCallbackDeps<TModel extends MenuModel = MenuModel>
|
|
|
93
93
|
setModel: (model: TModel) => Promise<boolean>;
|
|
94
94
|
setCurrentModel: (model: TModel) => void;
|
|
95
95
|
setThinkingLevel: (level: ThinkingLevel) => void;
|
|
96
|
-
stagePendingModelSwitch: (selection: ScopedTelegramModel<TModel
|
|
97
|
-
restartInterruptedTelegramTurn: (selection: ScopedTelegramModel<TModel
|
|
96
|
+
stagePendingModelSwitch: (selection: ScopedTelegramModel<TModel>, continuationTurn: TelegramModelSwitchContinuationSource) => void;
|
|
97
|
+
restartInterruptedTelegramTurn: (selection: ScopedTelegramModel<TModel>, continuationTurn: TelegramModelSwitchContinuationSource) => Promise<boolean> | boolean;
|
|
98
98
|
};
|
|
99
99
|
export interface TelegramModelMenuOpenDeps<TModel extends MenuModel = MenuModel> {
|
|
100
100
|
isIdle: () => boolean;
|
|
@@ -506,19 +506,33 @@ export async function handleTelegramModelMenuCallbackAction(callbackQueryId, par
|
|
|
506
506
|
if (plan.selection.thinkingLevel) {
|
|
507
507
|
deps.setThinkingLevel(plan.selection.thinkingLevel);
|
|
508
508
|
}
|
|
509
|
-
|
|
509
|
+
const continuationTurn = {
|
|
510
|
+
chatId: params.state.chatId,
|
|
511
|
+
replyToMessageId: params.state.messageId,
|
|
512
|
+
target: {
|
|
513
|
+
chatId: params.state.chatId,
|
|
514
|
+
...(params.state.threadId === undefined
|
|
515
|
+
? {}
|
|
516
|
+
: { threadId: params.state.threadId }),
|
|
517
|
+
},
|
|
518
|
+
};
|
|
510
519
|
if (plan.mode === "restart-after-tool") {
|
|
511
|
-
deps.stagePendingModelSwitch(plan.selection);
|
|
520
|
+
deps.stagePendingModelSwitch(plan.selection, continuationTurn);
|
|
521
|
+
await deps.updateModelMenuMessage();
|
|
512
522
|
await deps.answerCallbackQuery(callbackQueryId, plan.callbackText);
|
|
513
523
|
return true;
|
|
514
524
|
}
|
|
515
525
|
if (plan.mode === "restart-now") {
|
|
516
|
-
const restarted = await deps.restartInterruptedTelegramTurn(plan.selection);
|
|
526
|
+
const restarted = await deps.restartInterruptedTelegramTurn(plan.selection, continuationTurn);
|
|
527
|
+
await deps.updateModelMenuMessage();
|
|
517
528
|
if (!restarted) {
|
|
518
529
|
await deps.answerCallbackQuery(callbackQueryId, "Pi is busy. Send /abort, /next, or /stop.");
|
|
519
530
|
return true;
|
|
520
531
|
}
|
|
521
532
|
}
|
|
533
|
+
else {
|
|
534
|
+
await deps.updateModelMenuMessage();
|
|
535
|
+
}
|
|
522
536
|
await deps.answerCallbackQuery(callbackQueryId, plan.callbackText);
|
|
523
537
|
return true;
|
|
524
538
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Owns app-menu/status state, inline UI text, and callback composition while model/thinking/queue menu details live in dedicated domains
|
|
5
5
|
*/
|
|
6
6
|
import { type TelegramMenuMessageRuntimeDeps, type TelegramModelMenuState, type TelegramModelMenuStateBuilderContext, type TelegramModelMenuStateBuilderDeps, type TelegramReplyMarkup } from "./menu-model.ts";
|
|
7
|
-
import { type MenuModel, type ScopedTelegramModel, type ThinkingLevel } from "./model.ts";
|
|
7
|
+
import { type MenuModel, type ScopedTelegramModel, type TelegramModelSwitchContinuationSource, type ThinkingLevel } from "./model.ts";
|
|
8
8
|
import type { TelegramInputRichMessage } from "./telegram-api.ts";
|
|
9
9
|
import { type TelegramSectionRegistry } from "./sections.ts";
|
|
10
10
|
export { applyTelegramModelPageSelection, applyTelegramModelScopeSelection, buildModelMenuReplyMarkup, buildModelPageMenuReplyMarkup, buildTelegramModelCallbackPlan, buildTelegramModelMenuRenderPayload, buildTelegramModelMenuState, buildTelegramModelMenuStateRuntime, buildTelegramModelPageMenuRenderPayload, createTelegramModelMenuRuntime, createTelegramModelMenuStateBuilder, formatScopedModelButtonText, getModelMenuItems, getStoredTelegramModelMenuState, getTelegramModelMenuPage, getTelegramModelSelection, handleTelegramModelMenuCallbackAction, MODEL_MENU_TITLE, MODEL_PAGE_MENU_TITLE, openTelegramModelMenu, pruneStoredTelegramModelMenus, resolveCachedTelegramModelMenuInputs, sendTelegramModelMenuMessage, storeTelegramModelMenuState, TELEGRAM_MODEL_PAGE_SIZE, updateTelegramModelMenuMessage, } from "./menu-model.ts";
|
|
@@ -49,14 +49,13 @@ export interface TelegramMenuCallbackRuntimeDeps<TContext, TModel extends MenuMo
|
|
|
49
49
|
updateSettingsMenuMessage?: (state: TelegramModelMenuState<TModel>, ctx: TContext) => Promise<void>;
|
|
50
50
|
answerCallbackQuery: (callbackQueryId: string, text?: string) => Promise<void>;
|
|
51
51
|
isIdle: (ctx: TContext) => boolean;
|
|
52
|
-
hasActiveTelegramTurn: () => boolean;
|
|
53
52
|
hasAbortHandler: () => boolean;
|
|
54
53
|
hasActiveToolExecutions: () => boolean;
|
|
55
54
|
persistScopedModelPatterns?: (patterns: string[], ctx: TContext) => Promise<void>;
|
|
56
55
|
setModel: (model: TModel) => Promise<boolean>;
|
|
57
56
|
setCurrentModel: (model: TModel, ctx: TContext) => void;
|
|
58
|
-
stagePendingModelSwitch: (selection: ScopedTelegramModel<TModel>, ctx: TContext) => void;
|
|
59
|
-
restartInterruptedTelegramTurn: (selection: ScopedTelegramModel<TModel>, ctx: TContext) => Promise<boolean> | boolean;
|
|
57
|
+
stagePendingModelSwitch: (selection: ScopedTelegramModel<TModel>, ctx: TContext, continuationTurn: TelegramModelSwitchContinuationSource) => void;
|
|
58
|
+
restartInterruptedTelegramTurn: (selection: ScopedTelegramModel<TModel>, ctx: TContext, continuationTurn: TelegramModelSwitchContinuationSource) => Promise<boolean> | boolean;
|
|
60
59
|
sectionRegistry?: TelegramSectionRegistry;
|
|
61
60
|
editInteractiveMessage?: (chatId: number, messageId: number, text: string, mode: "markdown" | "html" | "plain", replyMarkup: TelegramReplyMarkup) => Promise<void>;
|
|
62
61
|
sendInteractiveMessage?: (chatId: number, text: string, mode: "markdown" | "html" | "plain", replyMarkup: TelegramReplyMarkup, options?: {
|
|
@@ -133,14 +132,13 @@ export interface TelegramMenuCallbackRuntimeAdapterDeps<TContext, TModel extends
|
|
|
133
132
|
updateSettingsMenuMessage?: (state: TelegramModelMenuState<TModel>, ctx: TContext) => Promise<void>;
|
|
134
133
|
answerCallbackQuery: (callbackQueryId: string, text?: string) => Promise<void>;
|
|
135
134
|
isIdle: (ctx: TContext) => boolean;
|
|
136
|
-
hasActiveTelegramTurn: () => boolean;
|
|
137
135
|
hasAbortHandler: () => boolean;
|
|
138
136
|
getActiveToolExecutions: () => number;
|
|
139
137
|
persistScopedModelPatterns?: (patterns: string[], ctx: TContext) => Promise<void>;
|
|
140
138
|
setModel: (model: TModel) => Promise<boolean>;
|
|
141
139
|
setCurrentModel: (model: TModel, ctx: TContext) => void;
|
|
142
|
-
stagePendingModelSwitch: (selection: ScopedTelegramModel<TModel>, ctx: TContext) => void;
|
|
143
|
-
restartInterruptedTelegramTurn: (selection: ScopedTelegramModel<TModel>, ctx: TContext) => Promise<boolean> | boolean;
|
|
140
|
+
stagePendingModelSwitch: (selection: ScopedTelegramModel<TModel>, ctx: TContext, continuationTurn: TelegramModelSwitchContinuationSource) => void;
|
|
141
|
+
restartInterruptedTelegramTurn: (selection: ScopedTelegramModel<TModel>, ctx: TContext, continuationTurn: TelegramModelSwitchContinuationSource) => Promise<boolean> | boolean;
|
|
144
142
|
sectionRegistry?: TelegramSectionRegistry;
|
|
145
143
|
editInteractiveMessage?: (chatId: number, messageId: number, text: string, mode: "markdown" | "html" | "plain", replyMarkup: TelegramReplyMarkup) => Promise<void>;
|
|
146
144
|
sendInteractiveMessage?: (chatId: number, text: string, mode: "markdown" | "html" | "plain", replyMarkup: TelegramReplyMarkup, options?: {
|
|
@@ -99,7 +99,6 @@ export function createTelegramMenuCallbackHandlerForContext(deps) {
|
|
|
99
99
|
updateSettingsMenuMessage: deps.updateSettingsMenuMessage,
|
|
100
100
|
answerCallbackQuery: deps.answerCallbackQuery,
|
|
101
101
|
isIdle: deps.isIdle,
|
|
102
|
-
hasActiveTelegramTurn: deps.hasActiveTelegramTurn,
|
|
103
102
|
hasAbortHandler: deps.hasAbortHandler,
|
|
104
103
|
hasActiveToolExecutions: () => deps.getActiveToolExecutions() > 0,
|
|
105
104
|
persistScopedModelPatterns: deps.persistScopedModelPatterns,
|
|
@@ -228,7 +227,7 @@ export async function handleTelegramMenuCallbackRuntime(query, ctx, deps) {
|
|
|
228
227
|
activeModel: deps.getActiveModel(ctx),
|
|
229
228
|
currentThinkingLevel: deps.getThinkingLevel(),
|
|
230
229
|
isIdle: deps.isIdle(ctx),
|
|
231
|
-
canRestartBusyRun: deps.
|
|
230
|
+
canRestartBusyRun: deps.hasAbortHandler(),
|
|
232
231
|
hasActiveToolExecutions: deps.hasActiveToolExecutions(),
|
|
233
232
|
}, {
|
|
234
233
|
updateModelMenuMessage: () => deps.updateModelMenuMessage(state, ctx),
|
|
@@ -243,10 +242,10 @@ export async function handleTelegramMenuCallbackRuntime(query, ctx, deps) {
|
|
|
243
242
|
deps.setThinkingLevel(level);
|
|
244
243
|
deps.updateStatus(ctx);
|
|
245
244
|
},
|
|
246
|
-
stagePendingModelSwitch: (selection) => {
|
|
247
|
-
deps.stagePendingModelSwitch(selection, ctx);
|
|
245
|
+
stagePendingModelSwitch: (selection, continuationTurn) => {
|
|
246
|
+
deps.stagePendingModelSwitch(selection, ctx, continuationTurn);
|
|
248
247
|
},
|
|
249
|
-
restartInterruptedTelegramTurn: (selection) => deps.restartInterruptedTelegramTurn(selection, ctx),
|
|
248
|
+
restartInterruptedTelegramTurn: (selection, continuationTurn) => deps.restartInterruptedTelegramTurn(selection, ctx, continuationTurn),
|
|
250
249
|
});
|
|
251
250
|
}
|
|
252
251
|
catch (error) {
|
|
@@ -52,14 +52,13 @@ export interface PendingModelSwitchStore<TSelection> {
|
|
|
52
52
|
}
|
|
53
53
|
export interface TelegramInFlightModelSwitchState {
|
|
54
54
|
isIdle: boolean;
|
|
55
|
-
hasActiveTelegramTurn: boolean;
|
|
56
55
|
hasAbortHandler: boolean;
|
|
57
56
|
}
|
|
58
57
|
export declare function createPendingModelSwitchStore<TSelection>(): PendingModelSwitchStore<TSelection>;
|
|
59
|
-
export declare function
|
|
58
|
+
export declare function canRestartAgentRunForTelegramModelSwitch(state: TelegramInFlightModelSwitchState): boolean;
|
|
60
59
|
export declare function shouldTriggerPendingTelegramModelSwitchAbort(state: {
|
|
61
60
|
hasPendingModelSwitch: boolean;
|
|
62
|
-
|
|
61
|
+
hasContinuationTurn: boolean;
|
|
63
62
|
hasAbortHandler: boolean;
|
|
64
63
|
activeToolExecutions: number;
|
|
65
64
|
}): boolean;
|
|
@@ -70,8 +69,9 @@ export declare function restartTelegramModelSwitchContinuation<TTurn, TSelection
|
|
|
70
69
|
queueContinuation: (turn: TTurn, selection: TSelection) => void;
|
|
71
70
|
}): boolean;
|
|
72
71
|
export declare function buildTelegramModelSwitchContinuationText<TModel extends MenuModel>(telegramPrefix: string, model: TModel, thinkingLevel?: ScopedTelegramModel<TModel>["thinkingLevel"]): string;
|
|
72
|
+
export type TelegramModelSwitchContinuationSource = Pick<PendingTelegramTurn, "chatId" | "replyToMessageId" | "target">;
|
|
73
73
|
export declare function buildTelegramModelSwitchContinuationTurn<TModel extends MenuModel>(options: {
|
|
74
|
-
turn:
|
|
74
|
+
turn: TelegramModelSwitchContinuationSource;
|
|
75
75
|
selection: ScopedTelegramModel<TModel>;
|
|
76
76
|
telegramPrefix?: string;
|
|
77
77
|
queueOrder: number;
|
|
@@ -82,22 +82,22 @@ export declare function createTelegramModelSwitchContinuationTurnBuilder<TModel
|
|
|
82
82
|
allocateItemOrder: () => number;
|
|
83
83
|
allocateControlOrder: () => number;
|
|
84
84
|
}): (options: {
|
|
85
|
-
turn:
|
|
85
|
+
turn: TelegramModelSwitchContinuationSource;
|
|
86
86
|
selection: ScopedTelegramModel<TModel>;
|
|
87
87
|
}) => PendingTelegramTurn;
|
|
88
88
|
export declare function createTelegramModelSwitchContinuationQueue<TContext, TSelection extends ScopedTelegramModel>(deps: {
|
|
89
89
|
createContinuationTurn: (options: {
|
|
90
|
-
turn:
|
|
90
|
+
turn: TelegramModelSwitchContinuationSource;
|
|
91
91
|
selection: TSelection;
|
|
92
92
|
}) => PendingTelegramTurn;
|
|
93
93
|
appendQueuedItem: (item: PendingTelegramTurn, ctx: TContext) => void;
|
|
94
|
-
}): (turn:
|
|
94
|
+
}): (turn: TelegramModelSwitchContinuationSource, selection: TSelection, ctx: TContext) => void;
|
|
95
95
|
export declare function createTelegramModelSwitchContinuationQueueRuntime<TContext, TSelection extends ScopedTelegramModel>(deps: {
|
|
96
96
|
telegramPrefix?: string;
|
|
97
97
|
allocateItemOrder: () => number;
|
|
98
98
|
allocateControlOrder: () => number;
|
|
99
99
|
appendQueuedItem: (item: PendingTelegramTurn, ctx: TContext) => void;
|
|
100
|
-
}): (turn:
|
|
100
|
+
}): (turn: TelegramModelSwitchContinuationSource, selection: TSelection, ctx: TContext) => void;
|
|
101
101
|
export interface TelegramModelSwitchControllerDeps<TContext, TSelection> {
|
|
102
102
|
isIdle: (ctx: TContext) => boolean;
|
|
103
103
|
getPendingModelSwitch: () => TSelection | undefined;
|
|
@@ -106,16 +106,16 @@ export interface TelegramModelSwitchControllerDeps<TContext, TSelection> {
|
|
|
106
106
|
getAbortHandler: () => (() => void) | undefined;
|
|
107
107
|
hasAbortHandler: () => boolean;
|
|
108
108
|
getActiveToolExecutions: () => number;
|
|
109
|
-
queueContinuation: (turn:
|
|
109
|
+
queueContinuation: (turn: TelegramModelSwitchContinuationSource, selection: TSelection, ctx: TContext) => void;
|
|
110
110
|
updateStatus: (ctx: TContext) => void;
|
|
111
111
|
}
|
|
112
112
|
export interface TelegramModelSwitchController<TContext, TSelection> {
|
|
113
113
|
canOfferInFlightSwitch: (ctx: TContext) => boolean;
|
|
114
|
-
stagePendingSwitch: (selection: TSelection, ctx: TContext) => void;
|
|
114
|
+
stagePendingSwitch: (selection: TSelection, ctx: TContext, continuationTurn?: TelegramModelSwitchContinuationSource) => void;
|
|
115
115
|
clearPendingSwitch: () => void;
|
|
116
|
-
queueContinuation: (turn:
|
|
116
|
+
queueContinuation: (turn: TelegramModelSwitchContinuationSource, selection: TSelection, ctx: TContext) => void;
|
|
117
117
|
triggerPendingAbort: (ctx: TContext) => boolean;
|
|
118
|
-
restartInterruptedTurn: (selection: TSelection, ctx: TContext) => boolean;
|
|
118
|
+
restartInterruptedTurn: (selection: TSelection, ctx: TContext, continuationTurn?: TelegramModelSwitchContinuationSource) => boolean;
|
|
119
119
|
}
|
|
120
120
|
export interface TelegramModelSwitchControllerRuntimeDeps<TContext, TSelection extends ScopedTelegramModel> extends Omit<TelegramModelSwitchControllerDeps<TContext, TSelection>, "queueContinuation"> {
|
|
121
121
|
telegramPrefix?: string;
|
|
@@ -261,12 +261,12 @@ export function createPendingModelSwitchStore() {
|
|
|
261
261
|
has: () => selection !== undefined,
|
|
262
262
|
};
|
|
263
263
|
}
|
|
264
|
-
export function
|
|
265
|
-
return !state.isIdle && state.
|
|
264
|
+
export function canRestartAgentRunForTelegramModelSwitch(state) {
|
|
265
|
+
return !state.isIdle && state.hasAbortHandler;
|
|
266
266
|
}
|
|
267
267
|
export function shouldTriggerPendingTelegramModelSwitchAbort(state) {
|
|
268
268
|
return (state.hasPendingModelSwitch &&
|
|
269
|
-
state.
|
|
269
|
+
state.hasContinuationTurn &&
|
|
270
270
|
state.hasAbortHandler &&
|
|
271
271
|
state.activeToolExecutions === 0);
|
|
272
272
|
}
|
|
@@ -297,7 +297,7 @@ export function buildTelegramModelSwitchContinuationText(telegramPrefix, model,
|
|
|
297
297
|
const thinkingSuffix = thinkingLevel
|
|
298
298
|
? ` Keep the selected thinking level (${thinkingLevel}) if it still applies.`
|
|
299
299
|
: "";
|
|
300
|
-
return `${telegramPrefix} Continue the interrupted previous
|
|
300
|
+
return `${telegramPrefix} Continue the interrupted previous request using the newly selected model (${modelLabel}). Resume from the last unfinished step instead of restarting from scratch unless necessary.${thinkingSuffix}`;
|
|
301
301
|
}
|
|
302
302
|
export function buildTelegramModelSwitchContinuationTurn(options) {
|
|
303
303
|
const modelLabel = `${options.selection.model.provider}/${options.selection.model.id}`;
|
|
@@ -318,7 +318,7 @@ export function buildTelegramModelSwitchContinuationTurn(options) {
|
|
|
318
318
|
text: buildTelegramModelSwitchContinuationText(options.telegramPrefix ?? TELEGRAM_PREFIX, options.selection.model, options.selection.thinkingLevel),
|
|
319
319
|
},
|
|
320
320
|
],
|
|
321
|
-
historyText: `Continue interrupted
|
|
321
|
+
historyText: `Continue interrupted request on ${modelLabel}`,
|
|
322
322
|
statusSummary: `↻ ${statusLabel || "continue"}`,
|
|
323
323
|
};
|
|
324
324
|
}
|
|
@@ -364,46 +364,60 @@ export function createTelegramModelSwitchControllerRuntime(deps) {
|
|
|
364
364
|
});
|
|
365
365
|
}
|
|
366
366
|
export function createTelegramModelSwitchController(deps) {
|
|
367
|
+
let pendingContinuationTurn;
|
|
368
|
+
const triggerPendingAbort = (ctx) => {
|
|
369
|
+
const turn = pendingContinuationTurn ?? deps.getActiveTurn();
|
|
370
|
+
if (!shouldTriggerPendingTelegramModelSwitchAbort({
|
|
371
|
+
hasPendingModelSwitch: !!deps.getPendingModelSwitch(),
|
|
372
|
+
hasContinuationTurn: !!turn,
|
|
373
|
+
hasAbortHandler: deps.hasAbortHandler(),
|
|
374
|
+
activeToolExecutions: deps.getActiveToolExecutions(),
|
|
375
|
+
})) {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
const selection = deps.getPendingModelSwitch();
|
|
379
|
+
const abort = deps.getAbortHandler();
|
|
380
|
+
if (!selection || !turn || !abort)
|
|
381
|
+
return false;
|
|
382
|
+
pendingContinuationTurn = undefined;
|
|
383
|
+
deps.setPendingModelSwitch(undefined);
|
|
384
|
+
deps.queueContinuation(turn, selection, ctx);
|
|
385
|
+
abort();
|
|
386
|
+
return true;
|
|
387
|
+
};
|
|
367
388
|
return {
|
|
368
|
-
canOfferInFlightSwitch: (ctx) =>
|
|
389
|
+
canOfferInFlightSwitch: (ctx) => canRestartAgentRunForTelegramModelSwitch({
|
|
369
390
|
isIdle: deps.isIdle(ctx),
|
|
370
|
-
hasActiveTelegramTurn: !!deps.getActiveTurn(),
|
|
371
391
|
hasAbortHandler: deps.hasAbortHandler(),
|
|
372
392
|
}),
|
|
373
|
-
stagePendingSwitch: (selection, ctx) => {
|
|
393
|
+
stagePendingSwitch: (selection, ctx, continuationTurn) => {
|
|
394
|
+
pendingContinuationTurn = deps.getActiveTurn() ?? continuationTurn;
|
|
374
395
|
deps.setPendingModelSwitch(selection);
|
|
375
|
-
|
|
396
|
+
try {
|
|
397
|
+
deps.updateStatus(ctx);
|
|
398
|
+
}
|
|
399
|
+
finally {
|
|
400
|
+
triggerPendingAbort(ctx);
|
|
401
|
+
}
|
|
376
402
|
},
|
|
377
403
|
clearPendingSwitch: () => {
|
|
404
|
+
pendingContinuationTurn = undefined;
|
|
378
405
|
deps.setPendingModelSwitch(undefined);
|
|
379
406
|
},
|
|
380
407
|
queueContinuation: deps.queueContinuation,
|
|
381
|
-
triggerPendingAbort
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
return false;
|
|
395
|
-
deps.setPendingModelSwitch(undefined);
|
|
396
|
-
deps.queueContinuation(turn, selection, ctx);
|
|
397
|
-
abort();
|
|
398
|
-
return true;
|
|
408
|
+
triggerPendingAbort,
|
|
409
|
+
restartInterruptedTurn: (selection, ctx, continuationTurn) => {
|
|
410
|
+
const restarted = restartTelegramModelSwitchContinuation({
|
|
411
|
+
activeTurn: deps.getActiveTurn() ?? continuationTurn,
|
|
412
|
+
abort: deps.getAbortHandler(),
|
|
413
|
+
selection,
|
|
414
|
+
queueContinuation: (turn, nextSelection) => {
|
|
415
|
+
deps.queueContinuation(turn, nextSelection, ctx);
|
|
416
|
+
},
|
|
417
|
+
});
|
|
418
|
+
if (restarted)
|
|
419
|
+
pendingContinuationTurn = undefined;
|
|
420
|
+
return restarted;
|
|
399
421
|
},
|
|
400
|
-
restartInterruptedTurn: (selection, ctx) => restartTelegramModelSwitchContinuation({
|
|
401
|
-
activeTurn: deps.getActiveTurn(),
|
|
402
|
-
abort: deps.getAbortHandler(),
|
|
403
|
-
selection,
|
|
404
|
-
queueContinuation: (turn, nextSelection) => {
|
|
405
|
-
deps.queueContinuation(turn, nextSelection, ctx);
|
|
406
|
-
},
|
|
407
|
-
}),
|
|
408
422
|
};
|
|
409
423
|
}
|