@llblab/pi-kit 0.1.11 → 0.1.13

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 CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  All notable changes to `@llblab/pi-kit` are documented here.
4
4
 
5
+ ## 0.1.13 - 2026-08-30
6
+
7
+ - `Telegram Minor Release`: Advances `@llblab/pi-telegram` to `0.40.0`, requiring Pi 0.84.4 and adopting native compaction-failure, mid-run compaction, terminal-message, and local UI-prompt lifecycle contracts while increasing bounded inbound text output to 24,000 characters.
8
+
9
+ ## 0.1.12 - 2026-08-28
10
+
11
+ - `Codex Usage Release`: Advances `@llblab/pi-codex-usage` to `0.9.4`, reducing automatic OpenAI quota polling by increasing the refresh interval from 30 to 60 seconds while preserving the local countdown responsiveness.
12
+ - `Telegram Hotfix`: Advances `@llblab/pi-telegram` to `0.39.5`, preserving terminal-equivalent chronology by delivering an active turn's final Telegram answer before automatic-compaction start and completion notices.
13
+
5
14
  ## 0.1.11 - 2026-08-28
6
15
 
7
16
  - `Telegram Hotfix`: Advances `@llblab/pi-telegram` to `0.39.4`, preventing repeated extension errors when print, RPC, ACP, or other headless hosts refresh lifecycle status without an initialized theme while preserving interactive status rendering.
package/README.md CHANGED
@@ -7,9 +7,9 @@
7
7
  | Package | Version | Resources |
8
8
  | --- | ---: | --- |
9
9
  | `@llblab/pi-actors` | `0.50.0` | Extension and Skills |
10
- | `@llblab/pi-codex-usage` | `0.9.3` | Extension |
10
+ | `@llblab/pi-codex-usage` | `0.9.4` | Extension |
11
11
  | `@llblab/pi-grow-loop` | `0.7.3` | Extension and Skills |
12
- | `@llblab/pi-telegram` | `0.39.4` | Extension and Skills |
12
+ | `@llblab/pi-telegram` | `0.40.0` | Extension and Skills |
13
13
 
14
14
  Versions are exact by design. Updating an extension does not change an installed kit until this repository explicitly advances that dependency and publishes a new kit version.
15
15
 
@@ -1,8 +1,3 @@
1
1
  # Backlog
2
2
 
3
- ## Pending Release Verification
4
-
5
- - [x] Fix stale extension context usage after session replacement or reload. Source: GitHub issue #1. Timers must not keep using captured stale `ctx` after Pi session lifecycle changes.
6
- - [x] Make usage refresh cache/in-flight state model-aware for Codex vs Spark. A regular Codex cache or request must not temporarily render Spark as `n/a` after model switching.
7
- - [x] Preserve the active `codex`/`spark` label for failure states. Spark failures should render `spark n/a` or `spark error`, not `codex ...`.
8
- - [x] Replace the visually full startup/request placeholder with counter-moving upper/lower in-bar loading markers. Keep the last usable active-bucket bar visible during ordinary refreshes, and hold provisional zero-usage reports behind the loader until confirmed stable.
3
+ No open work.
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.4: Responsive Quota Polling
4
+
5
+ - Increased the automatic Codex quota refresh interval from 30 to 60 seconds. Impact: status values remain responsive down to the locally rendered seconds countdown while reducing repeated OpenAI requests, especially when several Pi instances are open.
6
+
3
7
  ## 0.9.3: Trusted Release Automation
4
8
 
5
9
  - Added tag-gated release automation with exact package/lock/tag identity checks, full package validation, npm Trusted Publisher provenance, public registry verification, and workflow-owned GitHub Release creation through `.github/workflows/release.yml`.
@@ -14,7 +14,7 @@ This repository is a minimal fork of [`narumiruna/pi-extensions/extensions/pi-co
14
14
 
15
15
  ## Features
16
16
 
17
- - Shows two counter-moving half-height markers in the statusline bar while the active Codex/Spark quota bucket is loading, then refreshes every 30 seconds
17
+ - Shows two counter-moving half-height markers in the statusline bar while the active Codex/Spark quota bucket is loading, then refreshes every 60 seconds
18
18
  - Keeps the last usable active-bucket bar visible during ordinary refreshes instead of replacing known quota with a loading state
19
19
  - Statusline output adapts to the response: weekly-only limits show an explicit remaining percentage and reset countdown, while dual-window limits keep the compact themed bar
20
20
  - Regular Codex subscription models show the primary `codex` quota bucket
@@ -16,7 +16,7 @@ const HOUR_MS = 60 * MINUTE_MS;
16
16
  const HOUR_TENTH_MS = 6 * MINUTE_MS;
17
17
  const DAY_MS = 24 * HOUR_MS;
18
18
  const DAY_TENTH_MS = 144 * MINUTE_MS;
19
- const REFRESH_INTERVAL_MS = 30 * SECOND_MS;
19
+ const REFRESH_INTERVAL_MS = 60 * SECOND_MS;
20
20
  const PROVISIONAL_RETRY_MS = SECOND_MS;
21
21
  const FULL_AVAILABILITY_CONFIRMATION_MS = 15 * SECOND_MS;
22
22
  const LOADING_FRAME_MS = 30;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-codex-usage",
3
- "version": "0.9.3",
3
+ "version": "0.9.4",
4
4
  "private": false,
5
5
  "description": "Minimal Pi extension that shows primary Codex ChatGPT subscription usage limits",
6
6
  "keywords": [
@@ -2,6 +2,18 @@
2
2
 
3
3
  > Each release keeps at most 8 outcome records of at most 512 characters.
4
4
 
5
+ ## 0.40.0: Pi 0.84.4 Lifecycle Alignment
6
+
7
+ - `Pi Compatibility`: Requires `@earendil-works/pi-coding-agent`, `pi-agent-core`, and `pi-ai` 0.84.4 or newer, aligning the bridge and its validation runtime with native compaction-failure, mid-run compaction, message-finalization, and UI-prompt lifecycle contracts.
8
+ - `Compaction Failure`: Handles `session_compact_failed` immediately by clearing compacting state, stopping observer-owned typing, releasing deferred queue work, abandoning stale activity, and reporting automatic failure or cancellation without duplicating manual `/compact` errors.
9
+ - `Compaction Chronology`: Distinguishes terminal assistant output awaiting Telegram delivery from mid-run tool continuation. Post-answer notices remain ordered after the final reply, while tool-result threshold compaction is reported in place before the next assistant response.
10
+ - `Local UI Waiting`: Pauses Telegram typing while Pi waits on extension-owned local select, confirm, input, editor, or custom prompts and emits coalesced `ui-prompt-start` / `ui-prompt-end` Activity boundaries before resuming active-turn typing.
11
+ - `Inbound Context`: Raises successful inbound handler and built-in text attachment output from 12,000 to 24,000 characters while retaining the 1 MB built-in file admission ceiling and explicit truncation evidence.
12
+
13
+ ## 0.39.5: Auto-Compaction Notice Chronology Hotfix
14
+
15
+ - `Turn Chronology`: Defers observed automatic-compaction notices while a Telegram turn is still active, preserving the same causal order shown by the terminal: final answer, compaction started, then compaction completed. Abandoned compactions and session shutdown discard deferred notices instead of leaking them into a later turn.
16
+
5
17
  ## 0.39.4: Headless Status Bar Hotfix
6
18
 
7
19
  - `Headless Hosts`: Skips status-bar rendering when a print, RPC, ACP, or other non-interactive host has not initialized its theme, preventing repeated extension errors during lifecycle status refreshes while preserving normal interactive status updates and error propagation.
@@ -96,7 +96,7 @@ Source classification follows evidence, not guesses:
96
96
 
97
97
  Automatic retries, overflow compaction retries, and tool continuations inherit the current activity identity/source until `agent_settled`. A new unrelated `agent_start` after settlement allocates a new activity id.
98
98
 
99
- A standalone compaction owns a temporary activity only until `session_compact`. Pi does not expose a sibling-extension cancellation/failure callback after `session_before_compact`, so pi-telegram abandons that temporary identity at the first provable fallback boundary: the next `agent_start`, a replacement compaction start, session shutdown, or the existing five-minute compaction-observer timeout. A late `session_compact` after abandonment is ignored and cannot attach to the next run. Compaction inside an existing agent activity never clears that agent's identity.
99
+ A standalone compaction owns a temporary activity until `session_compact` or `session_compact_failed`. Native failure or cancellation immediately abandons that identity, clears compacting state, releases deferred queue work, and stops observer-owned typing; the five-minute observer timeout remains a compatibility fallback for a host that never emits a terminal compaction event. A late `session_compact` after abandonment is ignored and cannot attach to the next run. Compaction inside an existing agent activity never clears that agent's identity.
100
100
 
101
101
  ## Event Contract
102
102
 
@@ -151,6 +151,12 @@ export type TelegramActivityEvent = TelegramActivityEnvelope & (
151
151
  type: "compaction-end";
152
152
  reason: "manual" | "threshold" | "overflow" | "unknown";
153
153
  }
154
+ | {
155
+ type: "ui-prompt-start";
156
+ kind: "select" | "confirm" | "input" | "editor" | "custom";
157
+ title?: string;
158
+ }
159
+ | { type: "ui-prompt-end" }
154
160
  | { type: "agent-end" }
155
161
  | { type: "agent-settled" }
156
162
  );
@@ -250,7 +256,8 @@ The bridge maps Pi hooks as follows:
250
256
  - `agent_start`: allocate or reuse activity identity and emit `agent-start` after Telegram queue consumption establishes active-turn ownership.
251
257
  - `message_update.assistantMessageEvent`: normalize text/reasoning/provider boundaries.
252
258
  - `tool_execution_start/update/end`: emit executed tool events.
253
- - `session_before_compact` / `session_compact`: emit compaction boundaries and preserve activity identity across retry compaction; abandon an unterminated standalone compaction at the next lifecycle boundary or observer timeout rather than merging it into another run. A missing or unrecognized reason maps to `unknown` rather than guessing.
259
+ - `session_before_compact` / `session_compact` / `session_compact_failed`: emit successful compaction boundaries, abandon failed or cancelled work immediately, and preserve activity identity across retry compaction. Mid-run threshold compaction stays between tool results and the next assistant response, while terminal assistant output awaiting transport keeps later notices behind its final reply. A missing or unrecognized reason maps to `unknown` rather than guessing.
260
+ - `ui_prompt_start` / `ui_prompt_end`: emit one coalesced waiting span for extension-owned local UI, pause Telegram typing while Pi waits for the operator, and resume active-turn typing when the prompt closes.
254
261
  - `agent_end`: emit low-level run completion but keep identity alive for retry/follow-up work.
255
262
  - `agent_settled`: emit terminal settlement, flush pending terminal segments, and release activity identity.
256
263
  - `session_shutdown`: stop dispatch, clear pending normalization state, and invalidate delivery generation through the existing delivery lifecycle.
@@ -315,14 +315,15 @@ Queue and menu mutations are reachable through Telegram updates handled by the c
315
315
 
316
316
  ### Compaction And Typing Status
317
317
 
318
- Manual `/compact` requires inline confirmation because accidental taps are disruptive. Confirmed manual compaction and auto-compaction both set the bridge compaction flag, block queued prompt dispatch, retain that flag in explicit diagnostics, and clear it on compact completion, timeout fallback, or session shutdown. Pi owns its terminal compaction lifecycle; pi-telegram keeps `Active` scoped to Telegram-owned work and otherwise preserves the stable connected/leader/follower role.
318
+ Manual `/compact` requires inline confirmation because accidental taps are disruptive. Confirmed manual compaction and auto-compaction both set the bridge compaction flag, block queued prompt dispatch, retain that flag in explicit diagnostics, and clear it on native compact completion or failure, timeout fallback, or session shutdown. Pi owns its terminal compaction lifecycle; pi-telegram keeps `Active` scoped to Telegram-owned work and otherwise preserves the stable connected/leader/follower role. Mid-run threshold compaction reports notices in place between tool output and the next assistant response; compaction observed after terminal assistant output waits for final Telegram delivery so transport chronology matches the terminal.
319
319
 
320
320
  Native typing during compaction follows connected-instance activity rather than terminal status:
321
321
 
322
322
  - Confirmed manual `/compact` starts a native `typing` keepalive in the command target and stops it on completion/failure.
323
323
  - Automatic/session compaction with an active Telegram turn reuses that turn's target.
324
324
  - Automatic/session compaction without an active Telegram turn uses the connected instance's assigned target; an unconnected instance sends nothing.
325
- - Thread-targeted typing is sent to the concrete thread and mirrored to `All` as the aggregate activity surface; completion, timeout, and shutdown stop the keyed loop.
325
+ - Thread-targeted typing is sent to the concrete thread and mirrored to `All` as the aggregate activity surface; completion, native failure, timeout, and shutdown stop the keyed loop.
326
+ - Pi `ui_prompt_start` pauses active-turn typing while an extension-owned local prompt waits for the operator; `ui_prompt_end` emits the matching Activity boundary and resumes typing.
326
327
 
327
328
  At every connected instance `agent_start`, the lifecycle binding starts Telegram's native `…typing` indicator in that instance's assigned target, whether the run came from Telegram, the local TUI, or an autonomous continuation such as Grow Loop. Terminal `Active` remains Telegram-turn-specific; the native indicator answers the separate question of whether the instance is doing agent work. Each loop keeps one action in flight, while the leader API runtime coalesces identical chat/thread/action calls across local and follower traffic for two seconds; expired gates prune opportunistically and at most 256 currently active keys are retained. A Telegram 429 response opens the exact action's shared `retry_after` suppression window without scheduling delayed retries or projecting expected activity throttling as a terminal status error. Assistant message start/update hooks still re-arm it during Telegram-owned turns so transient provider/model errors do not leave a continuing run without activity feedback, and agent/session completion stops it.
328
329
 
@@ -85,6 +85,12 @@ export type TelegramActivityPayload =
85
85
  type: "compaction-end";
86
86
  reason: "manual" | "threshold" | "overflow" | "unknown";
87
87
  }
88
+ | {
89
+ type: "ui-prompt-start";
90
+ kind: "select" | "confirm" | "input" | "editor" | "custom";
91
+ title?: string;
92
+ }
93
+ | { type: "ui-prompt-end" }
88
94
  | { type: "agent-end" }
89
95
  | { type: "agent-settled" };
90
96
 
@@ -440,6 +446,12 @@ export function createTelegramActivityBridgeRuntime(deps: {
440
446
  onCompactionAbandoned() {
441
447
  getRuntime()?.onCompactionAbandoned();
442
448
  },
449
+ onUiPromptStart(kind, title) {
450
+ getRuntime()?.onUiPromptStart(kind, title);
451
+ },
452
+ onUiPromptEnd() {
453
+ getRuntime()?.onUiPromptEnd();
454
+ },
443
455
  onAgentEnd() {
444
456
  getRuntime()?.onAgentEnd();
445
457
  },
@@ -502,6 +514,11 @@ export interface TelegramActivityRuntime {
502
514
  reason: "manual" | "threshold" | "overflow" | "unknown",
503
515
  ) => void;
504
516
  onCompactionAbandoned: () => void;
517
+ onUiPromptStart: (
518
+ kind: "select" | "confirm" | "input" | "editor" | "custom",
519
+ title?: string,
520
+ ) => void;
521
+ onUiPromptEnd: () => void;
505
522
  onAgentEnd: () => void;
506
523
  onAgentSettled: () => void;
507
524
  onSessionShutdown: () => void;
@@ -533,6 +550,7 @@ export function createTelegramActivityRuntime(deps: {
533
550
  let pendingAssistantSegment: PendingAssistantSegment | undefined;
534
551
  let compactionInProgress = false;
535
552
  let compactionOwnedActivity = false;
553
+ let uiPromptInProgress = false;
536
554
  const ensureActivity = (
537
555
  activeTelegramTarget?: TelegramActivityTarget,
538
556
  ): string => {
@@ -592,6 +610,7 @@ export function createTelegramActivityRuntime(deps: {
592
610
  pendingAssistantSegment = undefined;
593
611
  compactionInProgress = false;
594
612
  compactionOwnedActivity = false;
613
+ uiPromptInProgress = false;
595
614
  };
596
615
  const abandonCompaction = (): void => {
597
616
  if (!compactionInProgress) return;
@@ -685,6 +704,16 @@ export function createTelegramActivityRuntime(deps: {
685
704
  onCompactionAbandoned() {
686
705
  abandonCompaction();
687
706
  },
707
+ onUiPromptStart(kind, title) {
708
+ if (!activityId || uiPromptInProgress) return;
709
+ uiPromptInProgress = true;
710
+ emit({ type: "ui-prompt-start", kind, title });
711
+ },
712
+ onUiPromptEnd() {
713
+ if (!activityId || !uiPromptInProgress) return;
714
+ uiPromptInProgress = false;
715
+ emit({ type: "ui-prompt-end" });
716
+ },
688
717
  onAgentEnd() {
689
718
  if (activityId) emit({ type: "agent-end" });
690
719
  },
@@ -1012,8 +1012,9 @@ export function registerTelegramLifecycleRuntimeHooks({
1012
1012
  const agentStartWithDedupReset = Lifecycle.createAgentStartDedupHook(
1013
1013
  agentLifecycleHooks.onAgentStart,
1014
1014
  );
1015
+ let uiPromptActive = false;
1015
1016
  const startAgentActivityTypingLoop = (ctx: Pi.ExtensionContext): boolean => {
1016
- if (!canSendAgentActivity(ctx)) return false;
1017
+ if (uiPromptActive || !canSendAgentActivity(ctx)) return false;
1017
1018
  const turn = activeTurnRuntime.get();
1018
1019
  const target = turn?.target ?? proactivePushTargetGetter();
1019
1020
  promptDispatchRuntime.startTypingLoop(ctx, turn?.chatId ?? target?.chatId, {
@@ -1022,12 +1023,16 @@ export function registerTelegramLifecycleRuntimeHooks({
1022
1023
  return true;
1023
1024
  };
1024
1025
  const startActiveTurnTypingLoop = (ctx: Pi.ExtensionContext): void => {
1026
+ if (uiPromptActive) return;
1025
1027
  const turn = activeTurnRuntime.get();
1026
1028
  promptDispatchRuntime.startTypingLoop(ctx, turn?.chatId, {
1027
1029
  target: turn?.target,
1028
1030
  });
1029
1031
  };
1030
1032
  let observedAutomaticCompaction = false;
1033
+ let agentRunActive = false;
1034
+ let terminalAssistantMessagePendingDelivery = false;
1035
+ const deferredAutomaticCompactionNotices: string[] = [];
1031
1036
  const sendCompactionNotice = async (text: string): Promise<void> => {
1032
1037
  const turn = activeTurnRuntime.get();
1033
1038
  const target = turn?.target ?? proactivePushTargetGetter?.();
@@ -1042,6 +1047,10 @@ export function registerTelegramLifecycleRuntimeHooks({
1042
1047
  });
1043
1048
  }
1044
1049
  };
1050
+ const flushDeferredAutomaticCompactionNotices = async (): Promise<void> => {
1051
+ const notices = deferredAutomaticCompactionNotices.splice(0);
1052
+ for (const notice of notices) await sendCompactionNotice(notice);
1053
+ };
1045
1054
  const compactionObserver = Lifecycle.createTelegramCompactionObserverRuntime({
1046
1055
  isContextActive: isSessionContextActive,
1047
1056
  setCompactionInProgress: lifecycle.setCompactionInProgress,
@@ -1054,6 +1063,7 @@ export function registerTelegramLifecycleRuntimeHooks({
1054
1063
  recordRuntimeEvent,
1055
1064
  onCompactionAbandoned: () => {
1056
1065
  observedAutomaticCompaction = false;
1066
+ deferredAutomaticCompactionNotices.length = 0;
1057
1067
  activityRuntime.onCompactionAbandoned();
1058
1068
  },
1059
1069
  });
@@ -1088,6 +1098,10 @@ export function registerTelegramLifecycleRuntimeHooks({
1088
1098
  activityVerbosityRuntime?.reset();
1089
1099
  assistantOutputRuntime.stop();
1090
1100
  observedAutomaticCompaction = false;
1101
+ agentRunActive = false;
1102
+ terminalAssistantMessagePendingDelivery = false;
1103
+ uiPromptActive = false;
1104
+ deferredAutomaticCompactionNotices.length = 0;
1091
1105
  compactionObserver.onSessionShutdown();
1092
1106
  if (event.reason === "quit" && disconnectOnQuit) {
1093
1107
  try {
@@ -1109,11 +1123,20 @@ export function registerTelegramLifecycleRuntimeHooks({
1109
1123
  activityRuntime.onCompactionStart(Pi.getSessionCompactionReason(event));
1110
1124
  compactionObserver.onSessionBeforeCompact(event, ctx);
1111
1125
  if (shouldNotify) {
1112
- await waitForActiveTurnDelivery();
1113
- if (!isSessionContextActive(ctx)) return;
1114
- await sendCompactionNotice(
1115
- Commands.TELEGRAM_COMPACTION_STARTED_MARKDOWN,
1116
- );
1126
+ if (
1127
+ activeTurnRuntime.has() &&
1128
+ terminalAssistantMessagePendingDelivery
1129
+ ) {
1130
+ deferredAutomaticCompactionNotices.push(
1131
+ Commands.TELEGRAM_COMPACTION_STARTED_MARKDOWN,
1132
+ );
1133
+ } else {
1134
+ await waitForActiveTurnDelivery();
1135
+ if (!isSessionContextActive(ctx)) return;
1136
+ await sendCompactionNotice(
1137
+ Commands.TELEGRAM_COMPACTION_STARTED_MARKDOWN,
1138
+ );
1139
+ }
1117
1140
  }
1118
1141
  },
1119
1142
  async onSessionCompact(event, ctx) {
@@ -1122,13 +1145,39 @@ export function registerTelegramLifecycleRuntimeHooks({
1122
1145
  compactionObserver.onSessionCompact(event, ctx);
1123
1146
  if (observedAutomaticCompaction) {
1124
1147
  observedAutomaticCompaction = false;
1125
- await sendCompactionNotice(
1126
- Commands.TELEGRAM_COMPACTION_COMPLETED_MARKDOWN,
1127
- );
1148
+ if (deferredAutomaticCompactionNotices.length > 0) {
1149
+ deferredAutomaticCompactionNotices.push(
1150
+ Commands.TELEGRAM_COMPACTION_COMPLETED_MARKDOWN,
1151
+ );
1152
+ } else {
1153
+ await sendCompactionNotice(
1154
+ Commands.TELEGRAM_COMPACTION_COMPLETED_MARKDOWN,
1155
+ );
1156
+ }
1157
+ }
1158
+ },
1159
+ async onSessionCompactFailed(event, ctx) {
1160
+ if (!isSessionContextActive(ctx)) return;
1161
+ const shouldNotify = observedAutomaticCompaction;
1162
+ const deferredNotices = deferredAutomaticCompactionNotices.splice(0);
1163
+ const shouldDefer =
1164
+ deferredNotices.length > 0 ||
1165
+ (activeTurnRuntime.has() && terminalAssistantMessagePendingDelivery);
1166
+ compactionObserver.onSessionCompactFailed(event, ctx);
1167
+ if (!shouldNotify) return;
1168
+ const notice = event.aborted
1169
+ ? "**⚠️ Compaction cancelled.**"
1170
+ : "**⚠️ Compaction failed.**";
1171
+ if (shouldDefer) {
1172
+ deferredAutomaticCompactionNotices.push(...deferredNotices, notice);
1173
+ } else {
1174
+ await sendCompactionNotice(notice);
1128
1175
  }
1129
1176
  },
1130
1177
  async onAgentStart(event, ctx) {
1131
1178
  if (!isSessionContextActive(ctx)) return;
1179
+ agentRunActive = true;
1180
+ terminalAssistantMessagePendingDelivery = false;
1132
1181
  await agentStartWithDedupReset(event, ctx);
1133
1182
  activityRuntime.onAgentStart(activeTurnRuntime.get()?.target);
1134
1183
  startAgentActivityTypingLoop(ctx);
@@ -1173,10 +1222,39 @@ export function registerTelegramLifecycleRuntimeHooks({
1173
1222
  }
1174
1223
  await messageActivityHooks.onMessageUpdate(event, ctx);
1175
1224
  },
1225
+ onMessageEnd(event, ctx) {
1226
+ if (!isSessionContextActive(ctx)) return;
1227
+ terminalAssistantMessagePendingDelivery =
1228
+ event.message.role === "assistant" &&
1229
+ event.message.stopReason !== "toolUse" &&
1230
+ event.message.stopReason !== "error" &&
1231
+ event.message.stopReason !== "aborted";
1232
+ },
1233
+ onUiPromptStart(event, ctx) {
1234
+ if (!isSessionContextActive(ctx)) return;
1235
+ uiPromptActive = true;
1236
+ typing.stop();
1237
+ activityRuntime.onUiPromptStart(event.kind, event.title);
1238
+ updateStatus(ctx);
1239
+ },
1240
+ onUiPromptEnd(_event, ctx) {
1241
+ if (!isSessionContextActive(ctx)) return;
1242
+ uiPromptActive = false;
1243
+ activityRuntime.onUiPromptEnd();
1244
+ if (agentRunActive) startAgentActivityTypingLoop(ctx);
1245
+ updateStatus(ctx);
1246
+ },
1176
1247
  async onAgentEnd(event, ctx) {
1177
1248
  if (!isSessionContextActive(ctx)) return;
1249
+ agentRunActive = false;
1178
1250
  activityRuntime.onAgentEnd();
1179
1251
  await agentLifecycleHooks.onAgentEnd(event, ctx);
1252
+ if (deferredAutomaticCompactionNotices.length > 0) {
1253
+ await waitForActiveTurnDelivery();
1254
+ if (!isSessionContextActive(ctx)) return;
1255
+ await flushDeferredAutomaticCompactionNotices();
1256
+ }
1257
+ terminalAssistantMessagePendingDelivery = false;
1180
1258
  },
1181
1259
  async onAgentSettled(event, ctx) {
1182
1260
  if (!isSessionContextActive(ctx)) return;
@@ -19,7 +19,7 @@ import { getTelegramVoiceTranscriptionProviders } from "./voice.ts";
19
19
 
20
20
  const DEFAULT_INBOUND_HANDLER_TIMEOUT_MS = 120_000;
21
21
  const INBOUND_HANDLER_REGISTRY_KEY = "__piTelegramInboundHandlers__";
22
- const MAX_INBOUND_HANDLER_OUTPUT_LENGTH = 12_000;
22
+ const MAX_INBOUND_HANDLER_OUTPUT_LENGTH = 24_000;
23
23
  const MAX_INBOUND_HANDLER_FAILURE_STREAM_LENGTH = 4_000;
24
24
 
25
25
  type TelegramInboundCommandTemplateConfig =
@@ -16,13 +16,17 @@ import type {
16
16
  ExtensionAPI,
17
17
  ExtensionContext,
18
18
  InputEvent,
19
+ MessageEndEvent,
19
20
  SessionBeforeCompactEvent,
20
21
  SessionCompactEvent,
22
+ SessionCompactFailedEvent,
21
23
  SessionShutdownEvent,
22
24
  SessionStartEvent,
23
25
  ToolExecutionEndEvent,
24
26
  ToolExecutionStartEvent,
25
27
  ToolExecutionUpdateEvent,
28
+ UIPromptEndEvent,
29
+ UIPromptStartEvent,
26
30
  } from "./pi.ts";
27
31
 
28
32
  let resetTransportReplyDedupFn: (() => void) | undefined;
@@ -78,6 +82,10 @@ export interface TelegramLifecycleRegistrationDeps {
78
82
  event: SessionCompactEvent,
79
83
  ctx: ExtensionContext,
80
84
  ) => Promise<void> | void;
85
+ onSessionCompactFailed?: (
86
+ event: SessionCompactFailedEvent,
87
+ ctx: ExtensionContext,
88
+ ) => Promise<void> | void;
81
89
  onBeforeAgentStart: (
82
90
  event: TelegramBeforeAgentStartEvent,
83
91
  ctx: ExtensionContext,
@@ -113,6 +121,18 @@ export interface TelegramLifecycleRegistrationDeps {
113
121
  },
114
122
  ctx: ExtensionContext,
115
123
  ) => Promise<void>;
124
+ onMessageEnd?: (
125
+ event: MessageEndEvent,
126
+ ctx: ExtensionContext,
127
+ ) => Promise<void> | void;
128
+ onUiPromptStart?: (
129
+ event: UIPromptStartEvent,
130
+ ctx: ExtensionContext,
131
+ ) => Promise<void> | void;
132
+ onUiPromptEnd?: (
133
+ event: UIPromptEndEvent,
134
+ ctx: ExtensionContext,
135
+ ) => Promise<void> | void;
116
136
  onAgentEnd: (event: AgentEndEvent, ctx: ExtensionContext) => Promise<void>;
117
137
  onAgentSettled?: (
118
138
  event: AgentSettledEvent,
@@ -416,6 +436,10 @@ export interface TelegramCompactionObserverRuntime<TContext> {
416
436
  ctx: TContext,
417
437
  ) => void;
418
438
  onSessionCompact: (event: SessionCompactEvent, ctx: TContext) => void;
439
+ onSessionCompactFailed: (
440
+ event: SessionCompactFailedEvent,
441
+ ctx: TContext,
442
+ ) => void;
419
443
  onSessionShutdown: () => void;
420
444
  }
421
445
 
@@ -471,6 +495,16 @@ export function createTelegramCompactionObserverRuntime<TContext>(
471
495
  deps.updateStatus(ctx);
472
496
  requestDispatch();
473
497
  },
498
+ onSessionCompactFailed: (_event, ctx) => {
499
+ clearFallbackTimer();
500
+ if (deps.isContextActive && !deps.isContextActive(ctx)) return;
501
+ deps.setCompactionInProgress(false);
502
+ if (typingStartedByObserver) deps.stopTypingLoop?.();
503
+ typingStartedByObserver = false;
504
+ deps.updateStatus(ctx);
505
+ deps.onCompactionAbandoned?.();
506
+ requestDispatch();
507
+ },
474
508
  onSessionShutdown: () => {
475
509
  clearFallbackTimer();
476
510
  if (typingStartedByObserver) deps.stopTypingLoop?.();
@@ -590,6 +624,10 @@ export function registerTelegramLifecycleHooks(
590
624
  if (!isActive(ctx)) return;
591
625
  await deps.onSessionCompact?.(event, ctx);
592
626
  });
627
+ pi.on("session_compact_failed", async (event, ctx) => {
628
+ if (!isActive(ctx)) return;
629
+ await deps.onSessionCompactFailed?.(event, ctx);
630
+ });
593
631
  // The Pi SDK still types this result as a string; compatible runtimes may
594
632
  // preserve ordered system prompt blocks through the same public hook.
595
633
  const registerBeforeAgentStart = pi.on.bind(pi) as unknown as (
@@ -630,6 +668,18 @@ export function registerTelegramLifecycleHooks(
630
668
  if (!isActive(ctx)) return;
631
669
  await deps.onMessageUpdate(event, ctx);
632
670
  });
671
+ pi.on("message_end", async (event, ctx) => {
672
+ if (!isActive(ctx)) return;
673
+ await deps.onMessageEnd?.(event, ctx);
674
+ });
675
+ pi.on("ui_prompt_start", async (event, ctx) => {
676
+ if (!isActive(ctx)) return;
677
+ await deps.onUiPromptStart?.(event, ctx);
678
+ });
679
+ pi.on("ui_prompt_end", async (event, ctx) => {
680
+ if (!isActive(ctx)) return;
681
+ await deps.onUiPromptEnd?.(event, ctx);
682
+ });
633
683
  pi.on("agent_end", async (event, ctx) => {
634
684
  if (!isActive(ctx)) return;
635
685
  await deps.onAgentEnd(event, ctx);
@@ -14,11 +14,14 @@ import {
14
14
  type ExtensionCommandContext,
15
15
  type ExtensionContext,
16
16
  type InputEvent,
17
+ type MessageEndEvent,
17
18
  type SessionBeforeCompactEvent,
18
19
  type SessionCompactEvent,
19
20
  type SessionShutdownEvent,
20
21
  type SessionStartEvent,
21
22
  type SlashCommandInfo,
23
+ type UIPromptEndEvent,
24
+ type UIPromptStartEvent,
22
25
  SettingsManager,
23
26
  } from "@earendil-works/pi-coding-agent";
24
27
 
@@ -32,13 +35,25 @@ export type {
32
35
  ExtensionCommandContext,
33
36
  ExtensionContext,
34
37
  InputEvent,
38
+ MessageEndEvent,
35
39
  SessionBeforeCompactEvent,
36
40
  SessionCompactEvent,
37
41
  SessionShutdownEvent,
38
42
  SessionStartEvent,
39
43
  SlashCommandInfo,
44
+ UIPromptEndEvent,
45
+ UIPromptStartEvent,
40
46
  };
41
47
 
48
+ export interface SessionCompactFailedEvent {
49
+ type: "session_compact_failed";
50
+ reason: "manual" | "threshold" | "overflow";
51
+ errorMessage?: string;
52
+ aborted: boolean;
53
+ willRetry: boolean;
54
+ fromExtension: boolean;
55
+ }
56
+
42
57
  export interface ToolExecutionStartEvent {
43
58
  type: "tool_execution_start";
44
59
  toolCallId: string;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-telegram",
3
- "version": "0.39.4",
3
+ "version": "0.40.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -71,9 +71,9 @@
71
71
  "image": "https://raw.githubusercontent.com/llblab/pi-telegram/main/screenshot.png"
72
72
  },
73
73
  "peerDependencies": {
74
- "@earendil-works/pi-agent-core": ">=0.80.6",
75
- "@earendil-works/pi-ai": ">=0.80.6",
76
- "@earendil-works/pi-coding-agent": ">=0.80.6",
74
+ "@earendil-works/pi-agent-core": ">=0.84.4",
75
+ "@earendil-works/pi-ai": ">=0.84.4",
76
+ "@earendil-works/pi-coding-agent": ">=0.84.4",
77
77
  "@sinclair/typebox": "*"
78
78
  },
79
79
  "devDependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-kit",
3
- "version": "0.1.11",
3
+ "version": "0.1.13",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -41,9 +41,9 @@
41
41
  ],
42
42
  "dependencies": {
43
43
  "@llblab/pi-actors": "0.50.0",
44
- "@llblab/pi-codex-usage": "0.9.3",
44
+ "@llblab/pi-codex-usage": "0.9.4",
45
45
  "@llblab/pi-grow-loop": "0.7.3",
46
- "@llblab/pi-telegram": "0.39.4"
46
+ "@llblab/pi-telegram": "0.40.0"
47
47
  },
48
48
  "bundledDependencies": [
49
49
  "@llblab/pi-actors",