@carlitose86/pi-telegram-reactive 0.1.0-next.0 → 0.1.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -1
- package/README.md +1 -1
- package/dist/lib/queue.js +44 -4
- package/dist/package.json +1 -1
- package/docs/specs/telegram-silent-reply-drop.md +59 -0
- package/docs/tickets/telegram-silent-reply-drop/01-record-dropped-final-replies.md +42 -0
- package/docs/tickets/telegram-silent-reply-drop/02-reproduce-steered-run-drop.md +43 -0
- package/lib/queue.ts +74 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
-
No additional changes beyond the preview
|
|
7
|
+
No additional changes beyond the preview candidates below.
|
|
8
|
+
|
|
9
|
+
## 0.1.0-next.1: Dropped-reply diagnostics
|
|
10
|
+
|
|
11
|
+
- `Dropped final replies`: When the bridge deliberately does not deliver a final reply that had text, attachments or an error notice, because the session was replaced, the transport changed or the active turn moved, it now records one redacted `delivery` event with a `final-reply-*` phase instead of leaving no trace. Runs with nothing to publish stay silent. A regression covers final replies after mid-run steers.
|
|
8
12
|
|
|
9
13
|
## 0.1.0-next.0: Pi Telegram Reactive preview candidate
|
|
10
14
|
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ Pi Telegram Reactive is the distinct `@carlitose86/pi-telegram-reactive` preview
|
|
|
17
17
|
From npm:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
pi install npm:@carlitose86/pi-telegram-reactive@0.1.0-next.
|
|
20
|
+
pi install npm:@carlitose86/pi-telegram-reactive@0.1.0-next.1
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
From git:
|
package/dist/lib/queue.js
CHANGED
|
@@ -875,10 +875,25 @@ export function buildTelegramAgentEndPlan(options) {
|
|
|
875
875
|
shouldSendAttachmentNotice: false,
|
|
876
876
|
};
|
|
877
877
|
}
|
|
878
|
+
/** Records a deliberately undelivered final reply without its content. */
|
|
879
|
+
function recordTelegramDroppedFinalReply(recordRuntimeEvent, phase, turn, content) {
|
|
880
|
+
recordRuntimeEvent?.("delivery", new Error("Telegram final reply was not delivered."), {
|
|
881
|
+
phase,
|
|
882
|
+
hasTurn: !!turn,
|
|
883
|
+
hasThread: turn?.target?.threadId !== undefined,
|
|
884
|
+
textLength: content.text?.length ?? 0,
|
|
885
|
+
attachmentCount: content.attachmentCount ?? 0,
|
|
886
|
+
});
|
|
887
|
+
}
|
|
878
888
|
export function createTelegramAgentEndHook(deps) {
|
|
879
889
|
return async (event, ctx, assistantOverride) => {
|
|
880
|
-
if (deps.isSessionActive?.(ctx) === false)
|
|
890
|
+
if (deps.isSessionActive?.(ctx) === false) {
|
|
891
|
+
const activeTurn = deps.getActiveTurn();
|
|
892
|
+
if (activeTurn) {
|
|
893
|
+
recordTelegramDroppedFinalReply(deps.recordRuntimeEvent, "final-reply-session-inactive", activeTurn, { attachmentCount: activeTurn.queuedAttachments.length });
|
|
894
|
+
}
|
|
881
895
|
return;
|
|
896
|
+
}
|
|
882
897
|
const turn = deps.getActiveTurn();
|
|
883
898
|
const extractedAssistant = assistantOverride ?? (turn ? deps.extractAssistant(event.messages) : {});
|
|
884
899
|
const assistant = deps.isAssistantAlreadyPublished?.(extractedAssistant)
|
|
@@ -889,8 +904,15 @@ export function createTelegramAgentEndHook(deps) {
|
|
|
889
904
|
const scheduleDelivery = reservation?.schedule ?? deps.scheduleActiveTurnDelivery;
|
|
890
905
|
try {
|
|
891
906
|
await deps.loadConfig?.();
|
|
892
|
-
|
|
907
|
+
const sessionInactive = deps.isSessionActive?.(ctx) === false;
|
|
908
|
+
if (sessionInactive || deps.getActiveTurn() !== turn) {
|
|
909
|
+
if (hasPublication) {
|
|
910
|
+
recordTelegramDroppedFinalReply(deps.recordRuntimeEvent, sessionInactive
|
|
911
|
+
? "final-reply-session-inactive"
|
|
912
|
+
: "final-reply-turn-replaced", turn, { text: assistant.text, attachmentCount: turn?.queuedAttachments.length });
|
|
913
|
+
}
|
|
893
914
|
return;
|
|
915
|
+
}
|
|
894
916
|
await handleTelegramAgentEndRuntime({
|
|
895
917
|
turn,
|
|
896
918
|
assistant,
|
|
@@ -964,8 +986,26 @@ export async function handleTelegramAgentEndRuntime(deps) {
|
|
|
964
986
|
const finalText = outboundReply ? outboundReply.markdown : rawFinalText;
|
|
965
987
|
const hasOutboundArtifacts = !!outboundReply?.voiceText || !!outboundReply?.voiceReplies?.length;
|
|
966
988
|
const replyMarkup = outboundReply?.replyMarkup;
|
|
967
|
-
const
|
|
968
|
-
|
|
989
|
+
const hasPublishableContent = !!finalText ||
|
|
990
|
+
hasOutboundArtifacts ||
|
|
991
|
+
(turn?.queuedAttachments.length ?? 0) > 0 ||
|
|
992
|
+
assistant.stopReason === "error";
|
|
993
|
+
let dropRecorded = false;
|
|
994
|
+
// The first failed fence that would lose content leaves one diagnostic event;
|
|
995
|
+
// without it a dropped final reply is indistinguishable from a sent one.
|
|
996
|
+
const isDeliveryActive = () => {
|
|
997
|
+
const sessionActive = deps.isSessionActive?.() !== false;
|
|
998
|
+
const transportActive = !turn || deps.isTurnTransportActive?.(turn) !== false;
|
|
999
|
+
if (sessionActive && transportActive)
|
|
1000
|
+
return true;
|
|
1001
|
+
if (!dropRecorded && hasPublishableContent) {
|
|
1002
|
+
dropRecorded = true;
|
|
1003
|
+
recordTelegramDroppedFinalReply(deps.recordRuntimeEvent, sessionActive
|
|
1004
|
+
? "final-reply-transport-inactive"
|
|
1005
|
+
: "final-reply-session-inactive", turn, { text: finalText, attachmentCount: turn?.queuedAttachments.length });
|
|
1006
|
+
}
|
|
1007
|
+
return false;
|
|
1008
|
+
};
|
|
969
1009
|
const preview = turn && !turn.guestQueryId ? deps.preparePreviewDelivery?.(isDeliveryActive) : undefined;
|
|
970
1010
|
const setPreviewPendingText = preview?.setPreviewPendingText ?? deps.setPreviewPendingText;
|
|
971
1011
|
const finalizeMarkdownPreview = preview?.finalizeMarkdownPreview ?? deps.finalizeMarkdownPreview;
|
package/dist/package.json
CHANGED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Telegram final replies dropped without a trace
|
|
2
|
+
|
|
3
|
+
## Artifact Graph
|
|
4
|
+
- Artifact ID: `artifact:telegram-silent-reply-drop-spec`
|
|
5
|
+
- Role: `spec`
|
|
6
|
+
- Standalone: true
|
|
7
|
+
### Children
|
|
8
|
+
- [01 Record every dropped final reply](../tickets/telegram-silent-reply-drop/01-record-dropped-final-replies.md)
|
|
9
|
+
- [02 Reproduce and fix the steered-run drop](../tickets/telegram-silent-reply-drop/02-reproduce-steered-run-drop.md)
|
|
10
|
+
|
|
11
|
+
## Type and evidence
|
|
12
|
+
Bug analysis. On 2026-10-07 a registered follower session (installed `@carlitose86/pi-telegram-reactive` `0.1.0-next.0`, built from `release/reactive-next-0`) produced two final assistant replies that never reached its Telegram Thread. Its session file shows the exact sequence (UTC):
|
|
13
|
+
|
|
14
|
+
| Time | Event |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| 13:39:39 | Telegram prompt (from a prompt button) starts a run |
|
|
17
|
+
| 13:39:46 | assistant `toolUse` |
|
|
18
|
+
| 13:40:09 | Telegram prompt "Chi sarebbe il collega?" enters the same run |
|
|
19
|
+
| 13:40:27 | assistant `stop`, answering both prompts |
|
|
20
|
+
| 13:40:27 | Telegram prompt "Poi spiegami…" enters at that last `turn_end`, so the run continues |
|
|
21
|
+
| 13:40:32–34 | assistant `toolUse` ×2 |
|
|
22
|
+
| 13:40:59 | assistant `stop`; run settles |
|
|
23
|
+
|
|
24
|
+
Neither reply was delivered, and `tmp/telegram/logs.jsonl` has no event after 13:33:28. Earlier replies of the same session, without mid-run prompts, arrived and left voice-fallback events. Inbound and leader polling stayed healthy.
|
|
25
|
+
|
|
26
|
+
The final-reply path ends silently in several places:
|
|
27
|
+
- `createTelegramAgentEndHook` returns when the session context is no longer current, or when the active turn changed while config loaded.
|
|
28
|
+
- `handleTelegramAgentEndRuntime` returns when `isDeliveryActive()` is false (session inactive, or the turn's transport stamp is no longer current) and when there is no active turn at all.
|
|
29
|
+
- An assistant text already admitted as an intermediate publication is reduced to its stop reason.
|
|
30
|
+
|
|
31
|
+
None of these exits records a runtime event, so the logs cannot say which one fired. The transport stamp changes only with profile or bot-token changes, so a stale stamp is unlikely here; a missing active turn or a replaced turn during a run with mid-run steers (`createTelegramMidRunSteerRuntime`, added with the busy-run steering feature) is the leading hypothesis. It is not proven.
|
|
32
|
+
|
|
33
|
+
## Goal
|
|
34
|
+
A final reply produced for a Telegram turn reaches its exact Telegram target, and when the bridge deliberately does not deliver one it leaves a redacted diagnostic event naming the reason.
|
|
35
|
+
|
|
36
|
+
## Non-goals
|
|
37
|
+
- No change to voice fallback, prompt buttons or the leader/follower bus protocol unless the reproduction proves they cause the drop.
|
|
38
|
+
- No replay of an uncertain send and no delivery to a different target, profile or session.
|
|
39
|
+
- No mutation of journals, owners, locks or state files to recover lost replies.
|
|
40
|
+
|
|
41
|
+
## Target behavior and invariants
|
|
42
|
+
1. Every non-delivery exit of the final-reply path that discards assistant text, attachments or an error notice records one `delivery` runtime event with a stable `phase` per reason: `final-reply-session-inactive`, `final-reply-transport-inactive` or `final-reply-turn-replaced`. Event details hold only bounded, redacted facts: reason, turn and Thread presence, text length, attachment count; never the reply text.
|
|
43
|
+
2. Exits that discard nothing (no text, no attachments, no error) stay silent. A run with no active Telegram turn is an ordinary local run whose output belongs to companion projection, and an answer already admitted as an intermediate publication is intentional deduplication; neither records an event, so routine runs do not flood the log. Slice 02 decides whether the observed drop went through the no-active-turn exit.
|
|
44
|
+
3. A run started by a Telegram prompt that absorbs one or more mid-run Telegram steers, including a steer queued at the final `turn_end`, delivers its final reply to the original turn's target exactly once.
|
|
45
|
+
4. Existing fences keep their meaning: a truly replaced session, profile or bot token still prevents delivery, now with an event.
|
|
46
|
+
|
|
47
|
+
## Implementation slices
|
|
48
|
+
- **01:** Add the diagnostic events of invariants 1–2 with unit tests for each reason. This alone makes any recurrence attributable.
|
|
49
|
+
- **02:** Reproduce the observed sequence (prompt-button turn, mid-run steer after a tool turn, second steer at the final `turn_end`) in a lifecycle-level test with real bridge wiring and fake Pi events. If it reproduces, fix the root cause and keep the test. If it does not, record which paths were exercised, keep the test as a regression, and leave the ticket open for live evidence from slice 01's events.
|
|
50
|
+
|
|
51
|
+
## Verification
|
|
52
|
+
Unit tests for each event reason; a lifecycle/integration test for invariant 3; `npm run build`, `npm run typecheck`, `npm test`, `npm run build:check`. Live confirmation needs a paired Telegram Thread and is a manual step after installation; no live behavior is claimed from tests.
|
|
53
|
+
|
|
54
|
+
## Gates
|
|
55
|
+
- **Attempt:** one pull request in `carlitose/pi-telegram` targeting `release/reactive-next-0`.
|
|
56
|
+
- **Budget and time per attempt:** at most 3 hours; no external spend. **Maximum attempts:** 2.
|
|
57
|
+
- **Approvals:** the agent may merge the pull request once its required checks are green. Publishing `0.1.0-next.1` to npm is done by the user (`npm publish`, browser 2FA). Updating the pin in `pi-personal-config` and installing happen only after the running `dbh-crew` benchmark lot ends.
|
|
58
|
+
- **Exact version:** none.
|
|
59
|
+
- **Existing blocks found:** the `dbh-crew` lot is running (blocks only installation); open pull request #7 `release/reactive-next-0 → main` (does not block this branch). Searched: open pull requests, `package.json` version, repository workflows `validate.yml` and `release.yml`.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
ticket_schema: 1
|
|
3
|
+
ticket_id: "01"
|
|
4
|
+
execution_mode: AFK
|
|
5
|
+
blocked_by: []
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Record every dropped final reply
|
|
9
|
+
|
|
10
|
+
## Artifact Graph
|
|
11
|
+
- Artifact ID: `artifact:telegram-silent-reply-drop-01`
|
|
12
|
+
- Role: `ticket`
|
|
13
|
+
- Parent: [telegram-silent-reply-drop.md](../../specs/telegram-silent-reply-drop.md)
|
|
14
|
+
|
|
15
|
+
## Parent Spec
|
|
16
|
+
[Telegram final replies dropped without a trace](../../specs/telegram-silent-reply-drop.md), target behavior 1, 2 and 4; slice 01.
|
|
17
|
+
|
|
18
|
+
## What to Build
|
|
19
|
+
Make each non-delivery exit of the final-reply path (`createTelegramAgentEndHook` and `handleTelegramAgentEndRuntime` in `lib/queue.ts`) that discards assistant text, queued attachments or an error notice record one redacted `delivery` runtime event with a stable `final-reply-*` phase naming the reason. Exits that discard nothing stay silent. Delivery behavior and fences do not change.
|
|
20
|
+
|
|
21
|
+
## Acceptance Criteria
|
|
22
|
+
- [ ] Session-inactive, transport-inactive, turn-replaced, no-active-turn and already-published exits each record exactly one event with their own phase when content would be lost.
|
|
23
|
+
- [ ] Event details contain no reply text; only reason, text presence/length, attachment count and target presence.
|
|
24
|
+
- [ ] A run with nothing to publish records no event.
|
|
25
|
+
- [ ] Existing queue/lifecycle tests stay green; `dist/` is rebuilt.
|
|
26
|
+
|
|
27
|
+
## Frontier
|
|
28
|
+
Done: merged in #8 (`298f291`), CI green on Linux, macOS and Windows. Live events appear only after the package is published and installed.
|
|
29
|
+
|
|
30
|
+
## Gates
|
|
31
|
+
As the parent spec: one PR to `release/reactive-next-0`, 3 hours per attempt, at most 2 attempts; the agent may merge on green checks; npm publication by the user; installation only after the `dbh-crew` lot.
|
|
32
|
+
|
|
33
|
+
## Step-by-Step Implementation Plan
|
|
34
|
+
1. Add a failing unit test per reason in `tests/queue.test.ts`.
|
|
35
|
+
2. Record the events at each exit through the existing `recordRuntimeEvent` port.
|
|
36
|
+
3. `npm run build`, typecheck, focused then full tests.
|
|
37
|
+
|
|
38
|
+
## Testing Plan
|
|
39
|
+
Unit tests in `tests/queue.test.ts`; `npm run typecheck`; `npm test`; `npm run build:check`.
|
|
40
|
+
|
|
41
|
+
## Out of Scope
|
|
42
|
+
- Finding or fixing the root cause (ticket 02).
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
ticket_schema: 1
|
|
3
|
+
ticket_id: "02"
|
|
4
|
+
execution_mode: AFK
|
|
5
|
+
blocked_by:
|
|
6
|
+
- "01"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Reproduce and fix the steered-run drop
|
|
10
|
+
|
|
11
|
+
## Artifact Graph
|
|
12
|
+
- Artifact ID: `artifact:telegram-silent-reply-drop-02`
|
|
13
|
+
- Role: `ticket`
|
|
14
|
+
- Parent: [telegram-silent-reply-drop.md](../../specs/telegram-silent-reply-drop.md)
|
|
15
|
+
|
|
16
|
+
## Parent Spec
|
|
17
|
+
[Telegram final replies dropped without a trace](../../specs/telegram-silent-reply-drop.md), target behavior 3 and 4; slice 02.
|
|
18
|
+
|
|
19
|
+
## What to Build
|
|
20
|
+
A lifecycle-level test with real bridge wiring and fake Pi events that replays the observed sequence: a Telegram prompt (from a prompt button) starts a run, one mid-run steer is injected after a tool turn, a second steer is injected at the final `turn_end` so the run continues, then the run settles. The final reply must reach the original turn's target exactly once. If the test reproduces the drop, fix the root cause. If it does not, keep it as a regression, document the exercised paths here, and leave the ticket open for live evidence from ticket 01's events.
|
|
21
|
+
|
|
22
|
+
## Acceptance Criteria
|
|
23
|
+
- [ ] The test covers both steers, including the one queued at the final `turn_end`.
|
|
24
|
+
- [ ] With the fix, the final reply is sent once to the original target; no duplicate and no other target.
|
|
25
|
+
- [ ] Replaced session, profile or token still blocks delivery and now records ticket 01's event.
|
|
26
|
+
- [ ] `npm run typecheck`, `npm test` and `npm run build:check` pass.
|
|
27
|
+
|
|
28
|
+
## Frontier
|
|
29
|
+
Investigated, not reproduced. In the classic single-process runtime the final reply reaches the turn target once with 0, 1 and 2 mid-run steers, including one queued at the final `turn_end` (regression in `tests/integration.test.ts`). Not exercised: registered-follower delivery through the leader and a prompt-button origin, both present in the incident. Open until a live recurrence yields ticket 01's `final-reply-*` event, which names the failing fence.
|
|
30
|
+
|
|
31
|
+
## Gates
|
|
32
|
+
As the parent spec: one PR to `release/reactive-next-0`, 3 hours per attempt, at most 2 attempts; the agent may merge on green checks; npm publication by the user; installation only after the `dbh-crew` lot.
|
|
33
|
+
|
|
34
|
+
## Step-by-Step Implementation Plan
|
|
35
|
+
1. Locate the lifecycle test harness that drives `agent_start`/`turn_end`/`message_start`/`agent_end` with the mid-run steer runtime.
|
|
36
|
+
2. Write the failing test for the observed sequence.
|
|
37
|
+
3. Fix the smallest owning module; rebuild `dist/`.
|
|
38
|
+
|
|
39
|
+
## Testing Plan
|
|
40
|
+
Lifecycle/integration test in the mirrored suite; full `npm test`; live check in a paired Thread after installation (manual, not claimed).
|
|
41
|
+
|
|
42
|
+
## Out of Scope
|
|
43
|
+
- Voice fallback and leader/follower protocol changes unless the reproduction proves them causal.
|
package/lib/queue.ts
CHANGED
|
@@ -1759,6 +1759,28 @@ export function buildTelegramAgentEndPlan(options: {
|
|
|
1759
1759
|
};
|
|
1760
1760
|
}
|
|
1761
1761
|
|
|
1762
|
+
/** Records a deliberately undelivered final reply without its content. */
|
|
1763
|
+
function recordTelegramDroppedFinalReply(
|
|
1764
|
+
recordRuntimeEvent:
|
|
1765
|
+
| ((category: string, error: unknown, details?: Record<string, unknown>) => void)
|
|
1766
|
+
| undefined,
|
|
1767
|
+
phase: string,
|
|
1768
|
+
turn: PendingTelegramTurn | undefined,
|
|
1769
|
+
content: { text?: string; attachmentCount?: number },
|
|
1770
|
+
): void {
|
|
1771
|
+
recordRuntimeEvent?.(
|
|
1772
|
+
"delivery",
|
|
1773
|
+
new Error("Telegram final reply was not delivered."),
|
|
1774
|
+
{
|
|
1775
|
+
phase,
|
|
1776
|
+
hasTurn: !!turn,
|
|
1777
|
+
hasThread: turn?.target?.threadId !== undefined,
|
|
1778
|
+
textLength: content.text?.length ?? 0,
|
|
1779
|
+
attachmentCount: content.attachmentCount ?? 0,
|
|
1780
|
+
},
|
|
1781
|
+
);
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1762
1784
|
export function createTelegramAgentEndHook<
|
|
1763
1785
|
TTurn extends PendingTelegramTurn,
|
|
1764
1786
|
TContext,
|
|
@@ -1777,7 +1799,18 @@ export function createTelegramAgentEndHook<
|
|
|
1777
1799
|
ctx: TContext,
|
|
1778
1800
|
assistantOverride?: TelegramAgentEndAssistantResult,
|
|
1779
1801
|
): Promise<void> => {
|
|
1780
|
-
if (deps.isSessionActive?.(ctx) === false)
|
|
1802
|
+
if (deps.isSessionActive?.(ctx) === false) {
|
|
1803
|
+
const activeTurn = deps.getActiveTurn();
|
|
1804
|
+
if (activeTurn) {
|
|
1805
|
+
recordTelegramDroppedFinalReply(
|
|
1806
|
+
deps.recordRuntimeEvent,
|
|
1807
|
+
"final-reply-session-inactive",
|
|
1808
|
+
activeTurn,
|
|
1809
|
+
{ attachmentCount: activeTurn.queuedAttachments.length },
|
|
1810
|
+
);
|
|
1811
|
+
}
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1781
1814
|
const turn = deps.getActiveTurn();
|
|
1782
1815
|
const extractedAssistant = assistantOverride ?? (turn ? deps.extractAssistant(event.messages) : {});
|
|
1783
1816
|
const assistant = deps.isAssistantAlreadyPublished?.(extractedAssistant)
|
|
@@ -1788,7 +1821,20 @@ export function createTelegramAgentEndHook<
|
|
|
1788
1821
|
const scheduleDelivery = reservation?.schedule ?? deps.scheduleActiveTurnDelivery;
|
|
1789
1822
|
try {
|
|
1790
1823
|
await deps.loadConfig?.();
|
|
1791
|
-
|
|
1824
|
+
const sessionInactive = deps.isSessionActive?.(ctx) === false;
|
|
1825
|
+
if (sessionInactive || deps.getActiveTurn() !== turn) {
|
|
1826
|
+
if (hasPublication) {
|
|
1827
|
+
recordTelegramDroppedFinalReply(
|
|
1828
|
+
deps.recordRuntimeEvent,
|
|
1829
|
+
sessionInactive
|
|
1830
|
+
? "final-reply-session-inactive"
|
|
1831
|
+
: "final-reply-turn-replaced",
|
|
1832
|
+
turn,
|
|
1833
|
+
{ text: assistant.text, attachmentCount: turn?.queuedAttachments.length },
|
|
1834
|
+
);
|
|
1835
|
+
}
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1792
1838
|
await handleTelegramAgentEndRuntime({
|
|
1793
1839
|
turn,
|
|
1794
1840
|
assistant,
|
|
@@ -1872,9 +1918,32 @@ export async function handleTelegramAgentEndRuntime<
|
|
|
1872
1918
|
const hasOutboundArtifacts =
|
|
1873
1919
|
!!outboundReply?.voiceText || !!outboundReply?.voiceReplies?.length;
|
|
1874
1920
|
const replyMarkup = outboundReply?.replyMarkup;
|
|
1875
|
-
const
|
|
1876
|
-
|
|
1877
|
-
|
|
1921
|
+
const hasPublishableContent =
|
|
1922
|
+
!!finalText ||
|
|
1923
|
+
hasOutboundArtifacts ||
|
|
1924
|
+
(turn?.queuedAttachments.length ?? 0) > 0 ||
|
|
1925
|
+
assistant.stopReason === "error";
|
|
1926
|
+
let dropRecorded = false;
|
|
1927
|
+
// The first failed fence that would lose content leaves one diagnostic event;
|
|
1928
|
+
// without it a dropped final reply is indistinguishable from a sent one.
|
|
1929
|
+
const isDeliveryActive = (): boolean => {
|
|
1930
|
+
const sessionActive = deps.isSessionActive?.() !== false;
|
|
1931
|
+
const transportActive =
|
|
1932
|
+
!turn || deps.isTurnTransportActive?.(turn) !== false;
|
|
1933
|
+
if (sessionActive && transportActive) return true;
|
|
1934
|
+
if (!dropRecorded && hasPublishableContent) {
|
|
1935
|
+
dropRecorded = true;
|
|
1936
|
+
recordTelegramDroppedFinalReply(
|
|
1937
|
+
deps.recordRuntimeEvent,
|
|
1938
|
+
sessionActive
|
|
1939
|
+
? "final-reply-transport-inactive"
|
|
1940
|
+
: "final-reply-session-inactive",
|
|
1941
|
+
turn,
|
|
1942
|
+
{ text: finalText, attachmentCount: turn?.queuedAttachments.length },
|
|
1943
|
+
);
|
|
1944
|
+
}
|
|
1945
|
+
return false;
|
|
1946
|
+
};
|
|
1878
1947
|
const preview = turn && !turn.guestQueryId ? deps.preparePreviewDelivery?.(isDeliveryActive) : undefined;
|
|
1879
1948
|
const setPreviewPendingText = preview?.setPreviewPendingText ?? deps.setPreviewPendingText;
|
|
1880
1949
|
const finalizeMarkdownPreview = preview?.finalizeMarkdownPreview ?? deps.finalizeMarkdownPreview;
|