* [AI] fix(agents): retain durable transcript-repair backlog for delivered assistant finals
A delivered assistant reply was silently lost when the canonical transcript
append failed after a successful send: the finalizer logged the failure and
continued delivery, and clearing the transport-replay marker removed the only
durable copy of the final (P1 data loss).
- finalizeEmbeddedAgentCommand now records a durable pendingTranscriptRepair
backlog on the session entry whenever the transcript writer throws, while
pendingFinalDelivery cleanup stays unchanged.
- The backlog is ordered and appended per turn (deduplicated by per-run
lifecycle generation, never by reply text), so consecutive failures and
identical reply texts each keep their own recoverable record.
- The next CLI/embedded turn best-effort re-appends each missing assistant
turn through the real SQLite writer (loaded via the existing lazy
attempt-execution runtime boundary) and clears recovered entries; failures
keep the backlog and never block the turn.
- Replays use exact per-turn idempotency (a per-run idempotency key with
scan-assistant lookup) instead of tail-text gap-fill, so a distinct failed
turn is re-appended even when an earlier persisted assistant message has
identical text, and repeated replays stay idempotent.
- Finals owned by another transcript writer (assistantTranscriptOwned) are
never queued, avoiding duplicate assistant messages after a user-turn-only
writer failure.
- Session-entry shape normalization round-trips the backlog and upgrades a
legacy single record into the array; slot-key registry updated.
- Regression tests: delivery-success + failed write keeps the repair record,
next-turn recovery, consecutive failures (distinct and identical texts),
recovery after an equal-text earlier assistant message, runtime-owned-final
exclusion, and shape normalization; the scoped max-lines exception for the
compaction-rotation suite is mirrored in oxlint-config.test.ts.
Fixes#117793
Co-Authored-By: Codex <noreply@anthropic.com>
* [AI] fix(agents): recover transcript repairs across session rotation; split repair tests
assistant-transcript-repair now migrates a pending repair record to the
rotated successor session instead of retaining a predecessor-scoped
record that can never be written: rotation keeps the same session key
while replacing the entry's session id, so the guard previously skipped
the record on every later pass and the delivered turn stayed missing
from durable history. The recovered turn is appended to the successor
transcript under a tested ownership rule.
The transcript-repair scenarios moved out of the oversized
compaction-rotation suite into a focused test module
(assistant-transcript-repair.test.ts), and the new max-lines override
plus its oxlint-config assertion were removed per repo policy. An
end-to-end regression covers failed predecessor persistence followed by
a rotated successor and a successful repair.
Co-Authored-By: Codex <noreply@anthropic.com>
* refactor(sessions): keep transcript repair array-only
* fix(agents): repair transcript before next turn
---------
Co-authored-by: Codex <noreply@anthropic.com>
Co-authored-by: Dallin Romney <dallinromney@gmail.com>
Co-authored-by: sallyom <somalley@redhat.com>
Diagnostics exporters are plugin services that only ever start inside the
Gateway process, so embedded `openclaw agent --local` turns emitted
diagnostic events in the CLI process with no subscriber: zero spans,
metrics, or logs reached the OTLP collector while identical Gateway-
dispatched turns exported fine.
The CLI now starts the `diagnostics-otel` service around embedded agent
runs via a scoped, non-activating plugin load that honors the same
plugin-enablement config as the Gateway, and flushes before exit: drain
the async diagnostic-event queue, then shut the SDK down (force flush).
Each step is bounded separately (5s drain, 10s flush) so an unreachable
collector cannot hold the CLI open, and a stalled drain cannot consume
the flush window and discard telemetry that was already buffered.
When OTel is configured but the plugin is absent, the run now says so
instead of exporting nothing with nothing explaining why.
`diagnostics-prometheus` stays Gateway-only: it is a pull-based scrape
server that is useless in a short-lived process and would race the
Gateway for its port. No new config surface; configs without OTel enabled
short-circuit before any plugin load.
Co-authored-by: Alex Knight <15041791+amknight@users.noreply.github.com>
* refactor(runtime): own ambient state lifecycle cleanup
* chore(plugin-sdk): refresh additive API baseline
* fix(runtime): restore agent wait listener lifecycle
Keep agent-job data process-shared while preserving module-local event subscription ownership. Ratchet the public wildcard budget down after replacing text-runtime's wildcard singleton export with named exports.