fix: recover empty completions and export long responses - #207
fix: recover empty completions and export long responses#207vijaymick67-rgb wants to merge 4 commits into
Conversation
- Aggregate zero-work evidence across every assistant turn of an attempt, so an earlier tool or reasoning call or any token/cost usage blocks the automatic replay instead of judging only the final empty message. - Replace the loose retryIdlePending flag with retryDispatched + idleGuard lifecycle state: exactly one retry, stale or duplicate session.idle events are consumed while the retry is in flight, and the retry completion always clears the state so its own idle produces the normal footer. - Invalidate the retry state on abort, detach, session error, bot-context loss, session mismatch, runtime cleanup, original/retry API failure, and replacement by a newer prompt; retry API failure restores the idle state and resumes the prompt queue. - Store only the terminal successfully delivered response for /lastfile and the automatic Markdown export, committing at session idle; failed, empty, or intermediate responses never replace the previous last good one. - Bind retry and export ownership to the originating Telegram chat.
- Add an assistant-message-started signal to the aggregator and only treat a completed message as a terminal final-response candidate when it survived with no upstream error, performed no tool activity, and did not end on a known non-terminal finish reason. Intermediate commentary and truncated or aborted output are still streamed to the user but never committed. - Invalidate a pending final-response candidate whenever a newer assistant message starts, so commentary followed by more tool work never becomes /lastfile, an automatic Markdown export, or evidence of a successful task. - Gate the assistant-run footer on an actually committed terminal response instead of any completed message, so a run that ends without a deliverable is never announced as complete. - Harden stale-idle ordering across the automatic retry with an explicit response-delivered phase and a settle timer: every session.idle, including a stale duplicate of the original attempt's idle, is consumed while the retry state is alive, and the retry is finalized exactly once by its own settle rather than by the first idle that happens to arrive. - Keep the retry idle guard off until the retry is actually dispatched so a registered-but-unreplayed attempt never swallows a normal run's idle. - Bind the committed /lastfile, automatic Markdown export, success footer, and TTS reply to the originating Telegram chat preserved for the prompt instead of the mutable service-wide chat context.
Reliability amendment: terminality, intermediate invalidation, stale-idle hardening, chat ownership1. Previous SHA
2. New SHA
3. Exact lifecycle fixes
4. How terminal success is now establishedAt session idle the pending final-response candidate is committed only when it (a) completed, (b) is non-empty, (c) has no upstream error, (d) performed no tool activity, (e) was not superseded by a newer assistant message, and (f) the run's 5. How intermediate candidates are invalidatedAny new assistant message start invalidates the pending candidate of a different messageId, and an errored/tool-bearing completion never establishes a candidate. The invalidation is serialized through 6. How stale original idle is distinguished from retry idleThe original attempt's idle that triggered the retry is consumed by the guard; while the retry state is alive all idles are consumed. Once the retry delivers its terminal response, 7. Chat ownership behaviorOrigin chat preserved per prompt ( 8. Tests added/updated
9. Validation
10. Files changed
11. Remaining uncertainty
12. Explicit confirmationNo merge performed. No Telegram bot or OpenCode server restarted. No |
- Terminality now fails closed: only the OpenCode FinishReason value "stop" (the sole successful terminal value in the @opencode-ai/llm literal schema) qualifies a non-empty assistant message as a final response. Missing, unknown, or arbitrary finish reasons are never treated as terminal success, and the per-step step-finish reason is no longer substituted for the authoritative message-level info.finish. - Remove the 300ms retry settle timer heuristic. A retry is finalized only when a session.idle is confirmed against the authoritative OpenCode session status: missing/"idle" means genuinely finished, while "busy"/"retry" (or a failed or ambiguous lookup) treats the idle as stale and consumes it without finalizing anything. Exactly one automatic retry remains the maximum.
Surgical fix: explicit terminal finish allowlist + authoritative retry idle1. Previous SHA
2. New SHA
3. Successful terminal finish reasons now accepted, and where they came fromOnly 4. How ambiguous finish reasons now fail closed
5. What replaced the 300ms timerThe 6. How stale original idle is distinguished from genuine retry completionOpenCode keeps a session in its active status map while busy and deletes it when it goes idle (publishing 7. Behavior when authoritative status lookup failsA failed or ambiguous lookup ( 8. Tests added/changed
9. Validation
10. Files changed
11. Remaining uncertainty
12. Explicit confirmationNo merge performed. No Telegram bot or OpenCode server restarted. No |
Summary
Validation
pm run build
pm run lint
pm run typecheck
pm test -- --pool=forks --maxWorkers=1 (145 files, 1558 tests)
Deployment
The currently running Telegram bot and OpenCode server were not restarted. Manual restart/deployment is required after merge.
This PR is intentionally draft and must not be auto-merged.