@llblab/pi-kit 0.14.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/README.md +2 -2
- package/node_modules/@llblab/pi-codex-usage/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-codex-usage/README.md +6 -0
- package/node_modules/@llblab/pi-codex-usage/index.ts +81 -7
- package/node_modules/@llblab/pi-codex-usage/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +5 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/activity.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/activity.js +12 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +1 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +5 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/queue.js +5 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.d.ts +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/replies.js +1 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +4 -2
- package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/lib/activity.ts +14 -0
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +7 -2
- package/node_modules/@llblab/pi-telegram/lib/queue.ts +7 -1
- package/node_modules/@llblab/pi-telegram/lib/replies.ts +2 -0
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +6 -2
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@llblab/pi-kit` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.15.0 - 2026-09-16
|
|
6
|
+
|
|
7
|
+
- `Business Credit Usage`: Advances the exact Codex Usage pin to `0.10.0`, adding Business-account credit usage with normalized display, precise reset countdown updates, and cache reuse.
|
|
8
|
+
- `State Flow Recovery Delivery`: Advances the exact Telegram pin to `0.48.3`, suppressing duplicate preserved-answer recovery only when matching intermediate text was admitted in the same Telegram turn while preserving equal answers in later turns. Thread restore and reclaim also use the current Workspace display title.
|
|
9
|
+
|
|
5
10
|
## 0.14.1 - 2026-09-16
|
|
6
11
|
|
|
7
12
|
- `State Flow Telegram Discovery Hotfix`: Advances the exact State Flow pin to `0.13.3`, restoring its Telegram main-menu control when Pi loads the compiled sibling extension layout while retaining package-export discovery and optional fail-open behavior. The package set and explicit load order remain unchanged.
|
package/README.md
CHANGED
|
@@ -12,10 +12,10 @@ Package links lead to the owning repositories for usage, documentation, issues,
|
|
|
12
12
|
| --- | ---: | --- |
|
|
13
13
|
| [`@llblab/pi-actors`](https://github.com/llblab/pi-actors) | `0.53.0` | Inspectable local Runs, reusable Recipes, persistent tools, and delegation Skills |
|
|
14
14
|
| [`@llblab/pi-clean-room`](https://github.com/llblab/pi-clean-room) | `0.1.1` | Isolated nested Pi TUI with explicitly selected extensions |
|
|
15
|
-
| [`@llblab/pi-codex-usage`](https://github.com/llblab/pi-codex-usage) | `0.
|
|
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.13.3` | Incremental scoped state, context, and memory with semantic storage, compiled runtime delivery, safe compaction, exact publication, and reliable Telegram section discovery |
|
|
18
|
-
| [`@llblab/pi-telegram`](https://github.com/llblab/pi-telegram) | `0.48.
|
|
18
|
+
| [`@llblab/pi-telegram`](https://github.com/llblab/pi-telegram) | `0.48.3` | Telegram companion with built runtime delivery, State Flow recovery deduplication, adaptive Thread display, exact queues, files, voice, controls, and Generative Apps guidance |
|
|
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.
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.0: Business Credit Usage
|
|
4
|
+
|
|
5
|
+
- Added Business-account credit usage support for responses that do not expose Codex rate-limit windows. Impact: the statusline now shows rounded remaining credit percentage and an optional reset countdown while preserving existing rate-limit displays when both forms are available.
|
|
6
|
+
- Scheduled credit reset countdown redraws at their exact display boundaries and retained credits-only reports in the active cache. Impact: credit status remains locally responsive between API refreshes without displacing model-specific quota windows.
|
|
7
|
+
|
|
3
8
|
## 0.9.4: Responsive Quota Polling
|
|
4
9
|
|
|
5
10
|
- 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.
|
|
@@ -68,6 +68,12 @@ Before the first usable report for the active quota bucket arrives, two half-hei
|
|
|
68
68
|
|
|
69
69
|
When the weekly reset time is available, it follows either the single-window percentage or the dual-window bar. More than a day remains is shown in 144-minute day-tenth steps such as `7d`, `6.9d`, `6.6d`, `5.1d`, `5d`, `3.7d`, `3d`, `2d`, `1.9d`, `1.5d`, and `1.1d`, rounded upward to the next tenth. At 24 hours and below it switches to upward-rounded 6-minute hour-tenth steps such as `24h`, `23.7h`, `20.1h`, `20h`, `19.9h`, `1.4h`, `1.3h`, `1.2h`, `1.1h`, and `1h`. Under an hour it switches to floored minutes, and under a minute to seconds. After the reset timestamp passes, `0s` is held until the next successful quota refresh reports the new weekly window.
|
|
70
70
|
|
|
71
|
+
Business accounts that return credits instead of Codex rate-limit windows show the rounded remaining percentage. When the response includes a credit reset cycle, its countdown is shown after the percentage:
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
codex 81% 2.8d
|
|
75
|
+
```
|
|
76
|
+
|
|
71
77
|
When the 5-hour window is exhausted and exposes its own reset time, the statusline adds the 5-hour reset before the weekly reset:
|
|
72
78
|
|
|
73
79
|
```text
|
|
@@ -90,6 +90,12 @@ export type UsageQueryError = {
|
|
|
90
90
|
|
|
91
91
|
export type CodexUsageReport = {
|
|
92
92
|
snapshots: NormalizedRateLimitSnapshot[];
|
|
93
|
+
credits?: NormalizedCreditUsage;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export type NormalizedCreditUsage = {
|
|
97
|
+
remainingPercent: number;
|
|
98
|
+
resetAt?: number;
|
|
93
99
|
};
|
|
94
100
|
|
|
95
101
|
export type NormalizedRateLimitSnapshot = {
|
|
@@ -106,6 +112,8 @@ export type NormalizedRateLimitWindow = {
|
|
|
106
112
|
type RateLimitStatusPayload = {
|
|
107
113
|
rate_limit?: unknown;
|
|
108
114
|
additional_rate_limits?: unknown;
|
|
115
|
+
credits?: unknown;
|
|
116
|
+
spend_control?: unknown;
|
|
109
117
|
};
|
|
110
118
|
|
|
111
119
|
type BackendAdditionalRateLimit = {
|
|
@@ -593,7 +601,10 @@ async function queryUsage(
|
|
|
593
601
|
source === "pi-auth"
|
|
594
602
|
? await queryViaPiAuth(ctx, options.timeoutMs)
|
|
595
603
|
: await queryViaCodexAppServer(options.timeoutMs);
|
|
596
|
-
if (
|
|
604
|
+
if (
|
|
605
|
+
selectUsageSnapshot(report, activeUsageLimitId(model)) ||
|
|
606
|
+
report.credits
|
|
607
|
+
) {
|
|
597
608
|
return { ok: true, report };
|
|
598
609
|
}
|
|
599
610
|
errors.push({
|
|
@@ -920,12 +931,56 @@ export function normalizeBackendPayload(
|
|
|
920
931
|
}
|
|
921
932
|
}
|
|
922
933
|
|
|
923
|
-
|
|
934
|
+
const credits = normalizeBackendCredits(payload, _capturedAt);
|
|
935
|
+
if (snapshots.length === 0 && !credits) {
|
|
924
936
|
throw new Error(
|
|
925
|
-
"Codex usage endpoint returned no displayable rate-limit windows.",
|
|
937
|
+
"Codex usage endpoint returned no displayable rate-limit windows or credits.",
|
|
926
938
|
);
|
|
927
939
|
}
|
|
928
|
-
return { snapshots };
|
|
940
|
+
return credits ? { snapshots, credits } : { snapshots };
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
function normalizeBackendCredits(
|
|
944
|
+
payload: RateLimitStatusPayload,
|
|
945
|
+
capturedAt: number,
|
|
946
|
+
): NormalizedCreditUsage | undefined {
|
|
947
|
+
const spendControl = payload.spend_control as
|
|
948
|
+
| Record<string, unknown>
|
|
949
|
+
| undefined;
|
|
950
|
+
const individualLimit = spendControl?.individual_limit as
|
|
951
|
+
| Record<string, unknown>
|
|
952
|
+
| undefined;
|
|
953
|
+
const credits = payload.credits as Record<string, unknown> | undefined;
|
|
954
|
+
const limit = asNumber(individualLimit?.limit);
|
|
955
|
+
const used = asNumber(individualLimit?.used);
|
|
956
|
+
const remaining = asNumber(individualLimit?.remaining);
|
|
957
|
+
const resetAt = asResetTime(
|
|
958
|
+
[
|
|
959
|
+
individualLimit?.reset_at,
|
|
960
|
+
individualLimit?.resets_at,
|
|
961
|
+
individualLimit?.reset_time,
|
|
962
|
+
spendControl?.reset_at,
|
|
963
|
+
spendControl?.resets_at,
|
|
964
|
+
credits?.reset_at,
|
|
965
|
+
credits?.resets_at,
|
|
966
|
+
],
|
|
967
|
+
individualLimit?.reset_after_seconds ?? spendControl?.reset_after_seconds,
|
|
968
|
+
capturedAt,
|
|
969
|
+
);
|
|
970
|
+
if (
|
|
971
|
+
limit === undefined ||
|
|
972
|
+
used === undefined ||
|
|
973
|
+
remaining === undefined ||
|
|
974
|
+
limit <= 0
|
|
975
|
+
)
|
|
976
|
+
return undefined;
|
|
977
|
+
const remainingPercent = Math.min(
|
|
978
|
+
100,
|
|
979
|
+
Math.max(0, (remaining / limit) * 100),
|
|
980
|
+
);
|
|
981
|
+
return resetAt === undefined
|
|
982
|
+
? { remainingPercent }
|
|
983
|
+
: { remainingPercent, resetAt };
|
|
929
984
|
}
|
|
930
985
|
|
|
931
986
|
function backendAdditionalLimitId(limit: BackendAdditionalRateLimit): string {
|
|
@@ -1103,7 +1158,9 @@ export function formatCodexUsageStatusValue(
|
|
|
1103
1158
|
const model = typeof modelOrNow === "number" ? undefined : modelOrNow;
|
|
1104
1159
|
const capturedNow = typeof modelOrNow === "number" ? modelOrNow : now;
|
|
1105
1160
|
const snapshot = selectActiveUsageSnapshot(report, model);
|
|
1106
|
-
if (!snapshot || (!snapshot.primary && !snapshot.secondary))
|
|
1161
|
+
if (!snapshot || (!snapshot.primary && !snapshot.secondary)) {
|
|
1162
|
+
return formatCreditUsage(report.credits, capturedNow);
|
|
1163
|
+
}
|
|
1107
1164
|
if (!snapshot.primary || !snapshot.secondary) {
|
|
1108
1165
|
const window = snapshot.primary ?? snapshot.secondary;
|
|
1109
1166
|
if (!window) return undefined;
|
|
@@ -1130,6 +1187,17 @@ export function formatCodexUsageStatusValue(
|
|
|
1130
1187
|
return countdown ? `${bar} ${countdown}` : bar;
|
|
1131
1188
|
}
|
|
1132
1189
|
|
|
1190
|
+
function formatCreditUsage(
|
|
1191
|
+
credits: NormalizedCreditUsage | undefined,
|
|
1192
|
+
now: number,
|
|
1193
|
+
): string | undefined {
|
|
1194
|
+
if (!credits) return undefined;
|
|
1195
|
+
const percentage = `${Math.round(credits.remainingPercent)}%`;
|
|
1196
|
+
return credits.resetAt
|
|
1197
|
+
? `${percentage} ${formatResetCountdown(credits.resetAt, now)}`
|
|
1198
|
+
: percentage;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1133
1201
|
export function formatWeeklyResetCountdown(
|
|
1134
1202
|
report: CodexUsageReport,
|
|
1135
1203
|
modelOrNow?: CodexUsageModel | number,
|
|
@@ -1167,7 +1235,10 @@ export function nextResetCountdownDelayMs(
|
|
|
1167
1235
|
model?: CodexUsageModel,
|
|
1168
1236
|
): number | undefined {
|
|
1169
1237
|
const snapshot = selectActiveUsageSnapshot(report, model);
|
|
1170
|
-
const
|
|
1238
|
+
const hasRateLimitWindow = Boolean(snapshot?.primary || snapshot?.secondary);
|
|
1239
|
+
const resetTimes = [
|
|
1240
|
+
hasRateLimitWindow ? weeklyWindow(snapshot)?.resetAt : report.credits?.resetAt,
|
|
1241
|
+
];
|
|
1171
1242
|
if (snapshot?.secondary && isQuotaWindowExhausted(snapshot.primary)) {
|
|
1172
1243
|
resetTimes.push(snapshot.primary?.resetAt);
|
|
1173
1244
|
}
|
|
@@ -1391,7 +1462,10 @@ export function canReuseCachedReport(
|
|
|
1391
1462
|
report: CodexUsageReport,
|
|
1392
1463
|
model: CodexUsageModel | undefined,
|
|
1393
1464
|
): boolean {
|
|
1394
|
-
return
|
|
1465
|
+
return (
|
|
1466
|
+
selectActiveUsageSnapshot(report, model) !== undefined ||
|
|
1467
|
+
report.credits !== undefined
|
|
1468
|
+
);
|
|
1395
1469
|
}
|
|
1396
1470
|
|
|
1397
1471
|
export function isFullyAvailableReport(
|
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.48.3: Assistant publication identity hotfix
|
|
8
|
+
|
|
9
|
+
- `Thread restore display identity`: Restoring or reclaiming a Thread now renames the destination from its current Workspace display title when available, rather than leaking the internal baked slot name such as `Moss` into directory-title mode.
|
|
10
|
+
- `Assistant publication identity`: An empty terminal assistant message may recover an earlier preserved answer, but it no longer republishes that text when the same Telegram intermediate output was already admitted; the pre-send turn fence also prevents lost-acknowledgement retries while allowing later turns.
|
|
11
|
+
|
|
7
12
|
## 0.48.2: Follower promotion recovery
|
|
8
13
|
|
|
9
14
|
- `Follower promotion recovery`: A failed follower-to-leader promotion, including unavailable Thread slot authority, is now contained as a retryable bus recovery event instead of escaping a detached heartbeat recovery promise and terminating Pi.
|
|
@@ -201,7 +201,9 @@ export interface TelegramActivityPublicationRuntime {
|
|
|
201
201
|
export declare function createTelegramActivityPublicationRuntime(): TelegramActivityPublicationRuntime;
|
|
202
202
|
export interface TelegramAssistantOutputRuntime {
|
|
203
203
|
start: () => void;
|
|
204
|
+
beginTurn: () => void;
|
|
204
205
|
accept: (event: TelegramAssistantSegmentEvent) => void;
|
|
206
|
+
hasAdmittedTelegramIntermediate: (text: string) => boolean;
|
|
205
207
|
waitForIdle: () => Promise<void>;
|
|
206
208
|
stop: () => void;
|
|
207
209
|
}
|
|
@@ -520,6 +520,7 @@ export function createTelegramAssistantOutputRuntime(deps) {
|
|
|
520
520
|
let running = false;
|
|
521
521
|
let tail = Promise.resolve();
|
|
522
522
|
const admitted = new Set();
|
|
523
|
+
const admittedTelegramIntermediateText = new Set();
|
|
523
524
|
const isEligibleEvent = (event) => (event.source === "telegram" && event.placement === "intermediate") ||
|
|
524
525
|
event.source === "local" ||
|
|
525
526
|
event.source === "autonomous" ||
|
|
@@ -529,8 +530,12 @@ export function createTelegramAssistantOutputRuntime(deps) {
|
|
|
529
530
|
generation += 1;
|
|
530
531
|
running = true;
|
|
531
532
|
admitted.clear();
|
|
533
|
+
admittedTelegramIntermediateText.clear();
|
|
532
534
|
tail = Promise.resolve();
|
|
533
535
|
},
|
|
536
|
+
beginTurn() {
|
|
537
|
+
admittedTelegramIntermediateText.clear();
|
|
538
|
+
},
|
|
534
539
|
accept(event) {
|
|
535
540
|
if (!running || !isEligibleEvent(event) || !event.text.trim())
|
|
536
541
|
return;
|
|
@@ -538,6 +543,9 @@ export function createTelegramAssistantOutputRuntime(deps) {
|
|
|
538
543
|
if (admitted.has(key))
|
|
539
544
|
return;
|
|
540
545
|
admitted.add(key);
|
|
546
|
+
if (event.source === "telegram" && event.placement === "intermediate") {
|
|
547
|
+
admittedTelegramIntermediateText.add(event.text.trim());
|
|
548
|
+
}
|
|
541
549
|
const admittedGeneration = generation;
|
|
542
550
|
const admittedAuthority = deps.captureAuthority?.();
|
|
543
551
|
const preparation = deps.prepareSend?.(event);
|
|
@@ -562,6 +570,9 @@ export function createTelegramAssistantOutputRuntime(deps) {
|
|
|
562
570
|
}
|
|
563
571
|
}).finally(() => preparation?.settle());
|
|
564
572
|
},
|
|
573
|
+
hasAdmittedTelegramIntermediate(text) {
|
|
574
|
+
return admittedTelegramIntermediateText.has(text.trim());
|
|
575
|
+
},
|
|
565
576
|
waitForIdle() {
|
|
566
577
|
return tail;
|
|
567
578
|
},
|
|
@@ -569,6 +580,7 @@ export function createTelegramAssistantOutputRuntime(deps) {
|
|
|
569
580
|
generation += 1;
|
|
570
581
|
running = false;
|
|
571
582
|
admitted.clear();
|
|
583
|
+
admittedTelegramIntermediateText.clear();
|
|
572
584
|
},
|
|
573
585
|
};
|
|
574
586
|
}
|
|
@@ -202,7 +202,7 @@ interface TelegramLifecycleBindingDeps {
|
|
|
202
202
|
publicationRuntime: TelegramBridgePublicationRuntime;
|
|
203
203
|
activityRuntime: Activity.TelegramActivityRuntime;
|
|
204
204
|
activityVerbosityRuntime?: ActivityVerbosity.TelegramActivityVerbosityRuntime;
|
|
205
|
-
assistantOutputRuntime: Pick<Activity.TelegramAssistantOutputRuntime, "start" | "waitForIdle" | "stop">;
|
|
205
|
+
assistantOutputRuntime: Pick<Activity.TelegramAssistantOutputRuntime, "start" | "beginTurn" | "hasAdmittedTelegramIntermediate" | "waitForIdle" | "stop">;
|
|
206
206
|
sessionLifecycleRuntime: Pick<Lifecycle.TelegramLifecycleRegistrationDeps, "onSessionStart" | "onSessionShutdown" | "onModelSelect">;
|
|
207
207
|
configStore: Pick<Config.TelegramConfigStore, "get" | "getOutboundHandlers" | "hasBotToken" | "load">;
|
|
208
208
|
abort: Runtime.TelegramRuntimeAbortPort;
|
|
@@ -615,7 +615,10 @@ export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime,
|
|
|
615
615
|
clearDispatchPending: lifecycle.clearDispatchPending,
|
|
616
616
|
setFoldQueuedPromptsIntoHistory: lifecycle.setFoldQueuedPromptsIntoHistory,
|
|
617
617
|
setActiveTurn: activeTurnRuntime.set,
|
|
618
|
-
onPromptHandedOff,
|
|
618
|
+
onPromptHandedOff: (turn, ctx) => {
|
|
619
|
+
assistantOutputRuntime.beginTurn();
|
|
620
|
+
onPromptHandedOff?.(turn, ctx);
|
|
621
|
+
},
|
|
619
622
|
createPreviewState: previewRuntime.resetState,
|
|
620
623
|
startTypingLoop: (ctx) => {
|
|
621
624
|
const turn = activeTurnRuntime.get();
|
|
@@ -627,6 +630,7 @@ export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime,
|
|
|
627
630
|
getActiveTurn: activeTurnRuntime.get,
|
|
628
631
|
loadConfig: configStore.load,
|
|
629
632
|
extractAssistant: Replies.extractRunAssistantMessage,
|
|
633
|
+
isRecoveredAssistantAlreadyPublished: (assistant) => !!assistant.text && assistantOutputRuntime.hasAdmittedTelegramIntermediate(assistant.text),
|
|
630
634
|
getFoldQueuedPromptsIntoHistory: lifecycle.shouldFoldQueuedPromptsIntoHistory,
|
|
631
635
|
resetRuntimeState: agentEndResetter,
|
|
632
636
|
isSessionActive: isSessionContextActive,
|
|
@@ -393,6 +393,7 @@ export interface TelegramAgentEndAssistantResult {
|
|
|
393
393
|
text?: string;
|
|
394
394
|
stopReason?: string;
|
|
395
395
|
errorMessage?: string;
|
|
396
|
+
recoveredFromEarlier?: boolean;
|
|
396
397
|
}
|
|
397
398
|
export interface TelegramAgentEndOutboundVoiceReply {
|
|
398
399
|
text: string;
|
|
@@ -468,6 +469,7 @@ export interface TelegramAgentEndHookRuntimeDeps<TTurn extends PendingTelegramTu
|
|
|
468
469
|
getActiveTurn: () => TTurn | undefined;
|
|
469
470
|
loadConfig?: () => Promise<void>;
|
|
470
471
|
extractAssistant: (messages: readonly TMessage[]) => TelegramAgentEndAssistantResult;
|
|
472
|
+
isRecoveredAssistantAlreadyPublished?: (assistant: TelegramAgentEndAssistantResult) => boolean;
|
|
471
473
|
getFoldQueuedPromptsIntoHistory: () => boolean;
|
|
472
474
|
resetRuntimeState: () => void;
|
|
473
475
|
isSessionActive?: (ctx: TContext) => boolean;
|
|
@@ -853,7 +853,11 @@ export function createTelegramAgentEndHook(deps) {
|
|
|
853
853
|
if (deps.isSessionActive?.(ctx) === false)
|
|
854
854
|
return;
|
|
855
855
|
const turn = deps.getActiveTurn();
|
|
856
|
-
const
|
|
856
|
+
const extractedAssistant = assistantOverride ?? (turn ? deps.extractAssistant(event.messages) : {});
|
|
857
|
+
const assistant = extractedAssistant.recoveredFromEarlier &&
|
|
858
|
+
deps.isRecoveredAssistantAlreadyPublished?.(extractedAssistant)
|
|
859
|
+
? { stopReason: extractedAssistant.stopReason }
|
|
860
|
+
: extractedAssistant;
|
|
857
861
|
const hasPublication = !!assistant.text || assistant.stopReason === "error" || !!turn?.queuedAttachments.length;
|
|
858
862
|
const reservation = turn && !turn.guestQueryId && hasPublication ? deps.reserveActiveTurnDelivery?.() : undefined;
|
|
859
863
|
const scheduleDelivery = reservation?.schedule ?? deps.scheduleActiveTurnDelivery;
|
|
@@ -44,6 +44,7 @@ export declare function extractRunAssistantMessage(messages: readonly unknown[])
|
|
|
44
44
|
text?: string;
|
|
45
45
|
stopReason?: string;
|
|
46
46
|
errorMessage?: string;
|
|
47
|
+
recoveredFromEarlier?: boolean;
|
|
47
48
|
};
|
|
48
49
|
export interface TelegramReplyOwnershipRecorder {
|
|
49
50
|
record: (input: {
|
|
@@ -142,6 +142,7 @@ export function extractRunAssistantMessage(messages) {
|
|
|
142
142
|
text,
|
|
143
143
|
stopReason: typeof rawStopReason === "string" ? rawStopReason : undefined,
|
|
144
144
|
errorMessage: typeof rawErrorMessage === "string" ? rawErrorMessage : undefined,
|
|
145
|
+
recoveredFromEarlier: true,
|
|
145
146
|
};
|
|
146
147
|
}
|
|
147
148
|
return latest;
|
|
@@ -923,7 +923,8 @@ export function createTelegramInboundRouteRuntime(deps) {
|
|
|
923
923
|
await deps.callApi("editForumTopic", {
|
|
924
924
|
chat_id: sourceTarget.chatId,
|
|
925
925
|
message_thread_id: sourceTarget.threadId,
|
|
926
|
-
name:
|
|
926
|
+
name: deps.getDisplayTitle?.(sourceTarget) ??
|
|
927
|
+
Threads.getTelegramTopicTitleForThreadName(threadName, slot),
|
|
927
928
|
});
|
|
928
929
|
assertExecutionCurrent();
|
|
929
930
|
}
|
|
@@ -1024,7 +1025,8 @@ export function createTelegramInboundRouteRuntime(deps) {
|
|
|
1024
1025
|
await deps.callApi("editForumTopic", {
|
|
1025
1026
|
chat_id: sourceTarget.chatId,
|
|
1026
1027
|
message_thread_id: sourceTarget.threadId,
|
|
1027
|
-
name:
|
|
1028
|
+
name: deps.getDisplayTitle?.(sourceTarget) ??
|
|
1029
|
+
Threads.getTelegramTopicTitleForThreadName(threadName, slot),
|
|
1028
1030
|
});
|
|
1029
1031
|
assertExecutionCurrent();
|
|
1030
1032
|
}
|
|
@@ -804,7 +804,9 @@ export function createTelegramActivityPublicationRuntime(): TelegramActivityPubl
|
|
|
804
804
|
|
|
805
805
|
export interface TelegramAssistantOutputRuntime {
|
|
806
806
|
start: () => void;
|
|
807
|
+
beginTurn: () => void;
|
|
807
808
|
accept: (event: TelegramAssistantSegmentEvent) => void;
|
|
809
|
+
hasAdmittedTelegramIntermediate: (text: string) => boolean;
|
|
808
810
|
waitForIdle: () => Promise<void>;
|
|
809
811
|
stop: () => void;
|
|
810
812
|
}
|
|
@@ -834,6 +836,7 @@ export function createTelegramAssistantOutputRuntime<TAuthority = undefined>(dep
|
|
|
834
836
|
let running = false;
|
|
835
837
|
let tail: Promise<void> = Promise.resolve();
|
|
836
838
|
const admitted = new Set<string>();
|
|
839
|
+
const admittedTelegramIntermediateText = new Set<string>();
|
|
837
840
|
const isEligibleEvent = (event: TelegramAssistantSegmentEvent): boolean =>
|
|
838
841
|
(event.source === "telegram" && event.placement === "intermediate") ||
|
|
839
842
|
event.source === "local" ||
|
|
@@ -845,13 +848,20 @@ export function createTelegramAssistantOutputRuntime<TAuthority = undefined>(dep
|
|
|
845
848
|
generation += 1;
|
|
846
849
|
running = true;
|
|
847
850
|
admitted.clear();
|
|
851
|
+
admittedTelegramIntermediateText.clear();
|
|
848
852
|
tail = Promise.resolve();
|
|
849
853
|
},
|
|
854
|
+
beginTurn() {
|
|
855
|
+
admittedTelegramIntermediateText.clear();
|
|
856
|
+
},
|
|
850
857
|
accept(event) {
|
|
851
858
|
if (!running || !isEligibleEvent(event) || !event.text.trim()) return;
|
|
852
859
|
const key = `${event.activityId}:${event.sequence}`;
|
|
853
860
|
if (admitted.has(key)) return;
|
|
854
861
|
admitted.add(key);
|
|
862
|
+
if (event.source === "telegram" && event.placement === "intermediate") {
|
|
863
|
+
admittedTelegramIntermediateText.add(event.text.trim());
|
|
864
|
+
}
|
|
855
865
|
const admittedGeneration = generation;
|
|
856
866
|
const admittedAuthority = deps.captureAuthority?.();
|
|
857
867
|
const preparation = deps.prepareSend?.(event);
|
|
@@ -877,6 +887,9 @@ export function createTelegramAssistantOutputRuntime<TAuthority = undefined>(dep
|
|
|
877
887
|
}
|
|
878
888
|
}).finally(() => preparation?.settle());
|
|
879
889
|
},
|
|
890
|
+
hasAdmittedTelegramIntermediate(text) {
|
|
891
|
+
return admittedTelegramIntermediateText.has(text.trim());
|
|
892
|
+
},
|
|
880
893
|
waitForIdle() {
|
|
881
894
|
return tail;
|
|
882
895
|
},
|
|
@@ -884,6 +897,7 @@ export function createTelegramAssistantOutputRuntime<TAuthority = undefined>(dep
|
|
|
884
897
|
generation += 1;
|
|
885
898
|
running = false;
|
|
886
899
|
admitted.clear();
|
|
900
|
+
admittedTelegramIntermediateText.clear();
|
|
887
901
|
},
|
|
888
902
|
};
|
|
889
903
|
}
|
|
@@ -835,7 +835,7 @@ interface TelegramLifecycleBindingDeps {
|
|
|
835
835
|
activityVerbosityRuntime?: ActivityVerbosity.TelegramActivityVerbosityRuntime;
|
|
836
836
|
assistantOutputRuntime: Pick<
|
|
837
837
|
Activity.TelegramAssistantOutputRuntime,
|
|
838
|
-
"start" | "waitForIdle" | "stop"
|
|
838
|
+
"start" | "beginTurn" | "hasAdmittedTelegramIntermediate" | "waitForIdle" | "stop"
|
|
839
839
|
>;
|
|
840
840
|
sessionLifecycleRuntime: Pick<
|
|
841
841
|
Lifecycle.TelegramLifecycleRegistrationDeps,
|
|
@@ -1136,7 +1136,10 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
1136
1136
|
clearDispatchPending: lifecycle.clearDispatchPending,
|
|
1137
1137
|
setFoldQueuedPromptsIntoHistory: lifecycle.setFoldQueuedPromptsIntoHistory,
|
|
1138
1138
|
setActiveTurn: activeTurnRuntime.set,
|
|
1139
|
-
onPromptHandedOff,
|
|
1139
|
+
onPromptHandedOff: (turn, ctx) => {
|
|
1140
|
+
assistantOutputRuntime.beginTurn();
|
|
1141
|
+
onPromptHandedOff?.(turn, ctx);
|
|
1142
|
+
},
|
|
1140
1143
|
createPreviewState: previewRuntime.resetState,
|
|
1141
1144
|
startTypingLoop: (ctx) => {
|
|
1142
1145
|
const turn = activeTurnRuntime.get();
|
|
@@ -1148,6 +1151,8 @@ export function registerTelegramLifecycleRuntimeHooks({
|
|
|
1148
1151
|
getActiveTurn: activeTurnRuntime.get,
|
|
1149
1152
|
loadConfig: configStore.load,
|
|
1150
1153
|
extractAssistant: Replies.extractRunAssistantMessage,
|
|
1154
|
+
isRecoveredAssistantAlreadyPublished: (assistant) =>
|
|
1155
|
+
!!assistant.text && assistantOutputRuntime.hasAdmittedTelegramIntermediate(assistant.text),
|
|
1151
1156
|
getFoldQueuedPromptsIntoHistory:
|
|
1152
1157
|
lifecycle.shouldFoldQueuedPromptsIntoHistory,
|
|
1153
1158
|
resetRuntimeState: agentEndResetter,
|
|
@@ -1474,6 +1474,7 @@ export interface TelegramAgentEndAssistantResult {
|
|
|
1474
1474
|
text?: string;
|
|
1475
1475
|
stopReason?: string;
|
|
1476
1476
|
errorMessage?: string;
|
|
1477
|
+
recoveredFromEarlier?: boolean;
|
|
1477
1478
|
}
|
|
1478
1479
|
|
|
1479
1480
|
export interface TelegramAgentEndOutboundVoiceReply {
|
|
@@ -1592,6 +1593,7 @@ export interface TelegramAgentEndHookRuntimeDeps<
|
|
|
1592
1593
|
extractAssistant: (
|
|
1593
1594
|
messages: readonly TMessage[],
|
|
1594
1595
|
) => TelegramAgentEndAssistantResult;
|
|
1596
|
+
isRecoveredAssistantAlreadyPublished?: (assistant: TelegramAgentEndAssistantResult) => boolean;
|
|
1595
1597
|
getFoldQueuedPromptsIntoHistory: () => boolean;
|
|
1596
1598
|
resetRuntimeState: () => void;
|
|
1597
1599
|
isSessionActive?: (ctx: TContext) => boolean;
|
|
@@ -1736,7 +1738,11 @@ export function createTelegramAgentEndHook<
|
|
|
1736
1738
|
): Promise<void> => {
|
|
1737
1739
|
if (deps.isSessionActive?.(ctx) === false) return;
|
|
1738
1740
|
const turn = deps.getActiveTurn();
|
|
1739
|
-
const
|
|
1741
|
+
const extractedAssistant = assistantOverride ?? (turn ? deps.extractAssistant(event.messages) : {});
|
|
1742
|
+
const assistant = extractedAssistant.recoveredFromEarlier &&
|
|
1743
|
+
deps.isRecoveredAssistantAlreadyPublished?.(extractedAssistant)
|
|
1744
|
+
? { stopReason: extractedAssistant.stopReason }
|
|
1745
|
+
: extractedAssistant;
|
|
1740
1746
|
const hasPublication = !!assistant.text || assistant.stopReason === "error" || !!turn?.queuedAttachments.length;
|
|
1741
1747
|
const reservation = turn && !turn.guestQueryId && hasPublication ? deps.reserveActiveTurnDelivery?.() : undefined;
|
|
1742
1748
|
const scheduleDelivery = reservation?.schedule ?? deps.scheduleActiveTurnDelivery;
|
|
@@ -191,6 +191,7 @@ export function extractRunAssistantMessage(
|
|
|
191
191
|
text?: string;
|
|
192
192
|
stopReason?: string;
|
|
193
193
|
errorMessage?: string;
|
|
194
|
+
recoveredFromEarlier?: boolean;
|
|
194
195
|
} {
|
|
195
196
|
const latest = extractLatestAssistantMessageText(messages);
|
|
196
197
|
if (latest.text || latest.stopReason !== "stop") return latest;
|
|
@@ -213,6 +214,7 @@ export function extractRunAssistantMessage(
|
|
|
213
214
|
stopReason: typeof rawStopReason === "string" ? rawStopReason : undefined,
|
|
214
215
|
errorMessage:
|
|
215
216
|
typeof rawErrorMessage === "string" ? rawErrorMessage : undefined,
|
|
217
|
+
recoveredFromEarlier: true,
|
|
216
218
|
};
|
|
217
219
|
}
|
|
218
220
|
return latest;
|
|
@@ -1560,7 +1560,9 @@ export function createTelegramInboundRouteRuntime<
|
|
|
1560
1560
|
await deps.callApi("editForumTopic", {
|
|
1561
1561
|
chat_id: sourceTarget.chatId,
|
|
1562
1562
|
message_thread_id: sourceTarget.threadId,
|
|
1563
|
-
name:
|
|
1563
|
+
name:
|
|
1564
|
+
deps.getDisplayTitle?.(sourceTarget) ??
|
|
1565
|
+
Threads.getTelegramTopicTitleForThreadName(threadName, slot),
|
|
1564
1566
|
});
|
|
1565
1567
|
assertExecutionCurrent();
|
|
1566
1568
|
} catch (renameError) {
|
|
@@ -1717,7 +1719,9 @@ export function createTelegramInboundRouteRuntime<
|
|
|
1717
1719
|
await deps.callApi("editForumTopic", {
|
|
1718
1720
|
chat_id: sourceTarget.chatId,
|
|
1719
1721
|
message_thread_id: sourceTarget.threadId,
|
|
1720
|
-
name:
|
|
1722
|
+
name:
|
|
1723
|
+
deps.getDisplayTitle?.(sourceTarget) ??
|
|
1724
|
+
Threads.getTelegramTopicTitleForThreadName(threadName, slot),
|
|
1721
1725
|
});
|
|
1722
1726
|
assertExecutionCurrent();
|
|
1723
1727
|
} catch (renameError) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@llblab/pi-actors": "0.53.0",
|
|
44
44
|
"@llblab/pi-clean-room": "0.1.1",
|
|
45
|
-
"@llblab/pi-codex-usage": "0.
|
|
45
|
+
"@llblab/pi-codex-usage": "0.10.0",
|
|
46
46
|
"@llblab/pi-grow-loop": "0.8.1",
|
|
47
47
|
"@llblab/pi-state-flow": "0.13.3",
|
|
48
|
-
"@llblab/pi-telegram": "0.48.
|
|
48
|
+
"@llblab/pi-telegram": "0.48.3",
|
|
49
49
|
"@llblab/skills": "1.15.0"
|
|
50
50
|
},
|
|
51
51
|
"bundledDependencies": [
|