Commit graph

8190 commits

Author SHA1 Message Date
brooklyn!
e99a0f6a97
Merge pull request #67283 from NousResearch/bb/salvage-60980-apiserver-offload
fix(api_server): offload synchronous SessionDB calls off the event loop (supersedes #60980)
2026-07-18 23:47:03 -04:00
kshitijk4poor
9ef66ea8c1 test: expect restored human anchor after user-role summary compaction
_is_real_user_message no longer accepts a user-role compaction summary as
the human anchor, so _compress_context restores the original user turn
after the summary; update the lifecycle-status test's expectation.
2026-07-19 08:55:08 +05:30
kshitijk4poor
c03c247e7c fix(compression): keep anchor restoration alternation-safe and grounding scaffolding-proof
Follow-up hardening on top of the salvaged #66637 commits:

- _insert_real_user_anchor could place the restored human turn directly
  next to user-role scaffolding (index-0 insert before a leading synthetic
  user turn, or a scaffolding-only transcript), breaking the strict
  alternation contract (#55677). Restoration now merges into trailing
  scaffolding (anchor text leads, synthetic flags cleared) and appends
  after a user-role compaction summary instead of inserting adjacent.
- _is_real_user_message now also rejects user-role compaction summaries
  (the compressor pins the summary to role=user when the tail opens with
  an assistant turn), so a summary can no longer satisfy the human-anchor
  check and skip restoration.
- _latest_user_task_snapshot reuses the same real-user predicate, so the
  deterministic task snapshot can no longer anchor on todo snapshots,
  truncation notices, or background-process reports.
- The Historical Task Snapshot rewrite keeps the section terminated with
  a blank line; the previous replacement consumed the boundary newlines,
  gluing the next '## ' heading mid-line and deleting all later sections
  on the next iterative compaction.
- Drop _length_continuation_synthetic (no producer anywhere).
- AUTHOR_MAP entry for enzo-adami.
2026-07-19 08:55:08 +05:30
Enzo Adami
d511ce0602 test: assert durable compression rotation 2026-07-19 08:55:08 +05:30
Enzo Adami
2f824ec5d2 test: align string summaries with grounded task snapshot 2026-07-19 08:55:08 +05:30
Enzo Adami
761a0b124e fix: ground compression task snapshot 2026-07-19 08:55:08 +05:30
Enzo Adami
960abf73a0 fix(compression): preserve human intent and durable handoffs 2026-07-19 08:55:08 +05:30
Dusk1e
228d8de19c fix(cli): restore session cwd on mid-chat /resume and /sessions 2026-07-18 23:20:19 -04:00
Brooklyn Nicholson
d2cb318509 fix(api_server): reconcile SessionDB offload with per-profile DB routing
Rebased onto current main, where _ensure_session_db grew a per-profile
cache (get_hermes_home()-keyed) for /p/<profile>/ multiplex — after this
PR's base. The PR's async rewrite assumed the old single-self._session_db
model, so on current main `session_db=self._session_db` in _create_agent
would pass None in production (the real DB lives in the per-home cache).

Split the concern: keep a SYNC _ensure_session_db (per-profile, used by
_create_agent + the many sync-patching create_agent tests) and add an
async _ensure_session_db_async that captures the profile home on the loop
thread then offloads only the SQLite open via to_thread (single-flight).
Both share _open_and_cache_session_db. Request handlers use the async
variant; _create_agent reverts to the sync call. Updated the first-request
test's FakeDB to accept db_path to match main's SessionDB(db_path=...).

Co-authored-by: necoweb3 <sswdarius@gmail.com>
2026-07-18 23:14:57 -04:00
Brooklyn Nicholson
66a7825ebb feat: unify active-project identity in chat status (supersedes #64721)
Surface the session's first-class Project in both chat surfaces: the
Desktop status bar (project name as the workspace label, full cwd in the
tooltip) and the TUI status label + /status output.

One source of truth. The per-profile projects.db is the authority, read
in tui_gateway via _project_info_for_cwd (backed by
projects_db.project_for_path) and threaded through every session.info
emission path the TUI consumes. The Desktop already caches that truth in
$projectTree, so it DERIVES the label from it (projectNameForCwd) instead
of carrying a second per-session $currentProject atom fed from
session.info.

That drops the parallel state #64721 introduced and the entire
reset/reconciliation surface it required (resume, agentless cwd.set,
gateway-switch, fresh-draft): the label is purely derived, so it stays
correct whenever the cwd or the project tree changes. Only explicit,
named projects resolve on both surfaces, so an auto-discovered repo root
keeps the cwd-leaf label everywhere.

Excludes the unrelated markdown shell-fence change bundled in #64721.
2026-07-18 23:08:13 -04:00
dsad
5529175084 fix: TOCTOU race in session create + offload SessionDB init
- Make create sequence (check + insert + title) atomic via single
  _execute_write call with BEGIN IMMEDIATE, closing the TOCTOU window
  where two concurrent same-ID creates could both return 201.

- Offload _ensure_session_db() to asyncio.to_thread with single-flight
  lock so first-request SQLite init doesn't block the event loop.

- Add concurrent same-ID create test (one 201, one 409) and
  first-request path test covering the initialization.
2026-07-18 23:07:29 -04:00
dsad
7ba944d054 fix(api_server): offload synchronous SessionDB calls off the event loop 2026-07-18 23:06:17 -04:00
Soju06
7b3dcee928 feat(cache): persist the exact bytes sent to the API in an api_content sidecar
The first LLM call of every gateway turn gets ~0% provider prompt-cache
hit rate (in-turn calls: 97-99%) because the bytes sent for a turn's
user message are not the bytes replayed next turn: memory-prefetch and
pre_llm_call context are injected into the API copy only, the #48677
persist override writes cleaned content to the DB row, and
get_messages_as_conversation sanitize/strips user and assistant content
on load. Any of these diverges the request prefix at that message and
re-prefills everything after it — measured 27.9s for the first call vs
2.4-5.8s cached at a median ~156k-token context.

Persist what you send: a nullable messages.api_content column stores the
exact content string sent to the API when it differs from the clean
stored content, and replay substitutes it verbatim (no sanitize, no
strip). The injection composition lives in one helper
(turn_context.compose_user_api_content); the turn prologue stamps its
output onto the live user message, the api_messages build sends the
stamped bytes, and every outgoing copy pops the field so it never
reaches a provider. The crash-resilience user-turn persist moves after
prefetch/pre_llm_call so the user row is written once with its final
sidecar; _ensure_db_session stays before preflight compression (session
rotation needs the parent row under PRAGMA foreign_keys=ON). The
current-turn index trackers are re-anchored after compaction rebuilds
the message list, in-place preflight compaction backfills the stamp onto
the already-inserted row, and gateway replay forwards the sidecar only
when the replay pipeline did not rewrite the content. Rewrite paths that
would leave stale bytes (historical image strip, merge-summary-into-
tail, consecutive-user repair merge, stale-confirmation redaction) drop
the sidecar; the chat-completions transport and the max-iterations
summary path strip it defensively. codex_app_server and MoA turns are
excluded from stamping because their wire bytes differ from the
composition. A missing or dropped sidecar degrades to today's behavior
(one cache-boundary miss), never to wrong content.
2026-07-19 08:25:35 +05:30
brooklyn!
e53f87fe69
Merge pull request #67238 from NousResearch/bb/anthropic-owner-thread-abort
fix(agent): request-local Anthropic clients so the stale/interrupt watchdog never corrupts SQLite (#67142, supersedes #51688)
2026-07-18 22:37:40 -04:00
brooklyn!
8e9c176117
Merge pull request #67245 from NousResearch/bb/desktop-sidebar-batch
perf(desktop): batch sidebar session slices into one profile-DB pass
2026-07-18 22:21:12 -04:00
brooklyn!
ea1cc1dd4b
Merge pull request #67247 from NousResearch/bb/desktop-resume-single-read
perf(gateway): serve session.resume model + display history from one SELECT
2026-07-18 22:18:39 -04:00
yu-xin-c
6a135142cc test(gateway): use compression helper in voice regression 2026-07-19 07:27:29 +05:30
yu-xin-c
7b330b1d22 fix(gateway): preserve pending voice media semantics 2026-07-19 07:27:29 +05:30
yu-xin-c
f5d493aebf fix(gateway): dedupe pending voice transcript echoes 2026-07-19 07:27:29 +05:30
kshitijk4poor
71157cbf66 refactor(turn_finalizer): extract _is_pure_tool_call_tail, fix SQLite durability
Extract the inline pure-tool-call tail check to a named helper using
flatten_message_text (canonical content extraction). Fix a SQLite
durability regression: the incremental tool-call persist
(conversation_loop.py:4990) stamps _DB_PERSISTED_MARKER on the assistant
row, so the next _persist_session flush skips it — the filled content
reaches the in-memory transcript but NOT the durable store, and /resume
reloads content="". Pop the marker so the next flush re-writes the row.

Tests pass, ruff clean.
2026-07-19 07:27:10 +05:30
Frowtek
56ac96976b fix(agent): persist the delivered response when the turn tail is a tool-call row
`finalize_turn` guarantees the invariant "delivered final_response =>
assistant row in transcript" (#43849 / #44100) for recovery paths that
return a response without appending a closing assistant message. It
enforces it by checking only the tail's ROLE:

    if _tail_role != "assistant":
        messages.append({"role": "assistant", "content": final_response})

A tail that is a *pure tool-call turn* — `assistant(tool_calls=[...])`
with no text of its own — satisfies that check while carrying none of the
delivered answer. The append is skipped and the response is never
persisted, so the durable transcript ends at an assistant row the user
never saw as the reply. On the next turn the model replays the user
backlog and re-answers it: exactly the symptom the block was added to
prevent, just reached through a different tail shape.

Observed with the real finalizer: with messages ending
`user -> assistant(content="", tool_calls=[t1])` and
`final_response="Here is your answer."`, the persisted transcript keeps
`content=""` and the answer is absent.

Fill that row's empty content instead of appending. This keeps the
invariant without disturbing the tool-call structure and without creating
an assistant->assistant pair. A tail tool-call row that already carries
model text is left untouched, so no model output is ever overwritten.

Adds regression tests for both: the empty tool-call tail is filled, and a
tool-call tail with existing text is not clobbered.
2026-07-19 07:27:10 +05:30
Brooklyn Nicholson
206953fb66 perf(gateway): serve session.resume's model + display history from one SELECT
session.resume built two projections of the same lineage with two separate
get_messages_as_conversation calls — the model-fed copy (tip rows, alternation-
repaired) and the display copy (full lineage, verbatim). The display fetch
already reads a superset of the model fetch (the tip rows are part of the
lineage), so the second call re-scanned the same messages.

Add SessionDB.get_resume_conversations(): one lineage SELECT, split into the tip
(model) and full (display) projections in memory via the extracted
_rows_to_conversation helper. Byte-identical to the two separate reads
(test_get_resume_conversations_matches_separate_reads covers a lineage with a
dangling tool-call tail so repair diverges the two lengths, a single session,
and replayed-user dedup). Both session.resume build paths (deferred + eager) now
use it; the subagent single-read path is unchanged.

From the Desktop performance audit (P1: "Remove repeated resume transcript
work") — the gateway half. The renderer's concurrent REST prefetch is left as-is
(the audit flags dropping it as measure-first).
2026-07-18 21:50:26 -04:00
Brooklyn Nicholson
40160e2a04 perf(desktop): batch sidebar session slices into one profile-DB pass
The sidebar refresh fired three /api/profiles/sessions calls (recents, cron,
messaging), and each one reopened every selected profile's state.db and re-ran
list_sessions_rich + session_count — ~3N DB opens/counts per refresh, on every
turn/broadcast/reconnect.

Add GET /api/profiles/sessions/sidebar: one pass that opens each profile DB
once and runs the three source-scoped queries together (recents scoped to the
active profile; cron + messaging cross-profile), returning the three windows in
one payload. Same read-only projection, 300s active heuristic, and caller-
supplied source taxonomy (recents_exclude / messaging_exclude / source=cron) as
the per-slice endpoint.

Renderer refreshSessions now makes one listSidebarSessions call and distributes
recents/cron/messaging to their stores (cron *jobs* stay a separate getCronJobs
API). Electron splices remote profiles per slice via fetchProfilesSessionSlice
(reusing the proven per-slice merge) so remote correctness is preserved; the
no-remote common case gets the single-open fast path.

From the Desktop performance audit (P1: "Batch sidebar session slices").
2026-07-18 21:45:18 -04:00
Brooklyn Nicholson
c2cb37532c fix(telegram): add cause-agnostic wedged-recovery watchdog so the reconnect ladder can't freeze silently (#66377)
The Telegram gateway could go silently deaf for hours: the reconnect ladder
stalled mid-way (e.g. "attempt 4/10, reconnecting in 40s" then nothing) while
the process stayed active(running), so Restart=always never fired.

Root class: every recovery path — the ladder's re-entry
(_schedule_polling_recovery), the pending-update probe (_probe_pending_updates),
and PTB's error callback — gates new recovery on _polling_error_task.done(). If
that single task wedges on any hung await, all recovery returns early forever
and nothing retries.

The heartbeat loop is a separate task, so make it an independent, cause-agnostic
watchdog: if the same recovery task stays in-flight past
_POLLING_ERROR_TASK_STUCK_TIMEOUT (300s — well beyond a healthy ladder attempt's
bounded stop+drain+start+backoff), force a retryable-fatal so the background
reconnector rebuilds the adapter instead of relying on the frozen ladder. This
guarantees progress regardless of *where* the stall is (issue direction #1),
tracked locally so no task-assignment site needs to change.

Also salvages @koduri-mahesh-bhushan-chowdary's #66492 (drain-await timeout),
which closes the one concrete wedge vector documented in the incident
(_drain_polling_connections' unbounded shutdown()/initialize() on a wedged
CLOSE-WAIT pool). The watchdog covers the rest of the class.

Co-authored-by: Koduri Mahesh Bhushan Chowdary <mkoduri73@gmail.com>
2026-07-18 21:31:15 -04:00
Koduri Mahesh Bhushan Chowdary
3391e639f6 fix(telegram): bound polling drain so wedged pool close can't stall reconnect ladder (#66377)
_drain_polling_connections() awaited polling_req.shutdown() and
.initialize() without a timeout. When the getUpdates httpx connection is
wedged on a stale CLOSE-WAIT socket, that close can block forever, hanging
_handle_polling_network_error (the tracked _polling_error_task). The task
never completes, so every escalation path — _schedule_polling_recovery,
_probe_pending_updates, the heartbeat verifier — stays gated behind its
in-flight guard, the ladder freezes mid-way, _set_fatal_error is never
reached, and Restart=always never fires: the gateway is alive but silently
dead.

Wrap both drain awaits in asyncio.wait_for with a new module-level
_DRAIN_TIMEOUT (15.0s, matching _UPDATER_STOP_TIMEOUT), mirroring the
existing bounded stop()/start_polling() sites. On timeout the drain logs and
continues, so the handler task completes and the ladder always advances
toward the fatal-restart escalation.

Adds test_reconnect_continues_if_drain_hangs, which wedges the drain and
asserts the handler still reaches start_polling within a hard bound.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 21:23:11 -04:00
Brooklyn Nicholson
42c240f580 fix(agent): request-local Anthropic clients so the stale/interrupt watchdog never corrupts SQLite (#67142)
Direct-Anthropic requests used a single shared _anthropic_client, and the
stale/interrupt watchdog closed + rebuilt it from the poll (stranger) thread
at four sites (non-streaming stale/interrupt, streaming stale/interrupt).
Closing a client whose TLS socket a worker thread was still reading released
the FD from a stranger thread; the kernel recycled it under a live SSL BIO,
which then wrote a 24-byte TLS record into an unrelated SQLite header
(cron/executions.db), bricking every cron on the profile. Same shape as the
OpenAI-only #29507 fix, but the Anthropic path never got the owner-thread
contract.

Extend the #29507 ownership contract to Anthropic: build a per-request client
(_create_request_anthropic_client), register it with the request-client
holder tagged by kind, and route _close_request_client_once by kind — a
stranger thread only shuts the request client's sockets down
(_abort_request_anthropic_client), while the owning worker performs the SDK
close (_close_request_anthropic_client). The shared _anthropic_client is now
never closed from inside a request (streaming or non-streaming), including the
worker retry-cleanup sites, since each attempt builds a fresh request client.
The #28161 no-hang guarantee is preserved: the poll-thread socket abort
unblocks the worker immediately.

Salvages the approach from #51688 (@raymondyan-zhijie), reimplemented onto
current main (non-streaming dispatch was refactored into
_dispatch_nonstreaming_api_request; streaming grew _cancel_current_stream_attempt
and worker retry-cleanup sites). Tests updated to the request-local mechanism
(incl. replacing a banned source-reading test with a behavior test) plus new
regression coverage proving the watchdog aborts the request client and never
touches the shared client.

Co-authored-by: raymondyan-zhijie <32435458+raymondyan-zhijie@users.noreply.github.com>
2026-07-18 21:20:48 -04:00
Paulo Nascimento
90d3ba5be9 fix(cli): warn once per path for UTF-32 .env refuse-to-mangle
Hot-reload and multi-entry load_hermes_dotenv can hit the same UTF-32
file repeatedly; gate the refuse-to-mangle warning on a module-level
seen-set (house style: _WARNED_KEYS sibling) so logs are not spammed.
2026-07-18 19:01:10 -04:00
Paulo Nascimento
7d597cc5d4 fix(cli): sanitize UTF-16 .env without corrupting the first key
Notepad "Unicode" saves write UTF-16 with a BOM. The sanitizer decoded
those bytes as utf-8-sig with errors=replace, glued U+FFFD onto the first
key name, stripped NULs, and rewrote the mangled content permanently.

Sniff leading BOMs before any text decode (UTF-32 before UTF-16, because
UTF-32-LE's BOM starts with UTF-16-LE's FF FE). Decode UTF-16 correctly
and rewrite as clean UTF-8. Refuse UTF-32 (leave untouched + warning).
After errors=replace, do not persist a first line that starts with U+FFFD.

Does not touch _load_dotenv_with_fallback (#65124's surface).
2026-07-18 19:01:10 -04:00
Paulo Nascimento
4441e11c77 fix(cli): quote .env values with internal whitespace in save_env_value
_quote_env_value previously left internal spaces unquoted (only #/"/'
and leading/trailing whitespace triggered). Spaced macOS paths written
via hermes setup SSH / Google Chat SA path / hermes config set produced
lines that python-dotenv still parsed but shell `set -a; . file` word-split.

Extend needs_quoting with any(c.isspace()); escaping dialect unchanged.
2026-07-18 19:01:10 -04:00
brooklyn!
ed957aeb26
Merge pull request #67182 from NousResearch/bb/p0-salvage
fix(install): keep install.ps1 pure ASCII so Windows PowerShell 5.1 doesn't misparse it (#66994/#67000)
2026-07-18 18:58:31 -04:00
Brooklyn Nicholson
1cec5c69d3 test(mcp): e2e integration coverage for #63892 poll-loop OOM spin
The salvaged unit tests hand-construct completed futures to lock the
_run_on_mcp_loop contract. Add a live-loop test that reproduces the actual
field trigger: an inner asyncio.wait_for expiry stores a real TimeoutError
on a real future scheduled on the MCP loop. Asserts the fixed loop surfaces
it once, promptly -- not spinning to the outer deadline (which both leaked
memory and masked the real error behind the generic wrapper message).
2026-07-18 18:49:56 -04:00
PRATHAMESH75
97249cfc8a fix(install): keep install.ps1 pure ASCII so Windows PowerShell 5.1 doesn't misparse it
A commit added a bullet and an em-dash inside two Write-Host/Write-Info string
literals in scripts/install.ps1. The file has no UTF-8 BOM, so Windows
PowerShell 5.1 (which the bootstrap runs the cached script under) reads it in
the system ANSI code page (CP1252), not UTF-8. The em-dash's UTF-8 tail byte
decodes to a smart close-quote (U+201D) that the tokenizer treats as a string
delimiter, prematurely closing the string and desyncing the parser -- surfacing
as the reported cascade of syntax errors at lines 1619/1770 and aborting the
Windows GUI installer before it does anything.

Non-ASCII bytes in '#' comments are harmless (skipped to end-of-line), so the
file carried em-dashes in comments for months; only the two chars in code
broke it. Convert all non-ASCII to ASCII equivalents (em-dash -> '--', already
this file's own comment convention; bullet -> '-') and add a source-level test
locking the pure-ASCII invariant, since Linux CI cannot run the PS installer.

Fixes #66994
Fixes #67000
2026-07-18 18:40:59 -04:00
Jupiter363
3df8bd3478 fix(mcp): propagate stored timeouts from completed futures 2026-07-18 18:40:34 -04:00
Teknium
7a43ab042f
fix(computer_use): reconnect a dead cua-driver session instead of hanging (#67138)
Bug 1 of #55048: when the MCP connection dropped (driver crash / restart),
_lifecycle_coro exited but left _started=True, so the next list_apps/capture
passed _require_started() and then operated on a None session — hanging
forever instead of reconnecting.

- _lifecycle_coro's finally now resets _started=False on ANY exit, so a dead
  session is re-enterable (idempotent no-op on the normal stop() path; atomic
  bool write, safe from the bridge-loop thread without the lock stop() holds).
- call_tool() re-enters start() when the session isn't active, rebuilding it
  before the call. The start_session/end_session handshake (driven by start()/
  stop() themselves) is exempted so bootstrap doesn't recurse.

Tests: two cases in test_computer_use_delivery_ladder.py — finally resets
_started, and call_tool restarts a dead session exactly once. Full
computer_use suite green (233).

Refs #55048 (Bug 1). Bug 2 (expose foreground dispatch) is covered by the
delivery_mode work in #67123.
2026-07-18 15:07:04 -07:00
Austin Pickett
ca0703feae
fix(cli): try bundled TUI before requiring ui-tui workspace (#67116)
_make_tui_argv() called _ensure_tui_workspace(tui_dir) unconditionally
before checking for a prebuilt bundle. That function sys.exit(1)s when
ui-tui/ doesn't exist, which it never does on a pip/pipx install — the
wheel ships hermes_cli/tui_dist/entry.js but never ships ui-tui/ at all
(that directory only exists in a git checkout).

Every dashboard Chat tab connection on a pip/pipx install therefore
hard-exited before ever reaching _find_bundled_tui(), surfacing as the
unhelpful "Chat unavailable: 1" banner despite having a fully valid
bundled entry.js on disk.

Move the bundled-wheel/HERMES_TUI_DIR shortcut ahead of the workspace
check. --dev is unaffected (it never uses the bundled path and still
requires the workspace), and the checkout-without-bundle path is
unaffected (bundled lookup returns None, falls through to the existing
git-restore/npm-install/build flow).

Adds a contributors/emails mapping for the original author.

Fixes #56665

Co-authored-by: lucaskvasirr <lucaskvasir@duck.com>
2026-07-18 17:17:25 -04:00
teknium1
11cb9e571f fix: harden /model --once against persistence and config-sync leaks
Fixes the two review defects that kept PR #29923 open, plus docs:

- gateway: exclude --once from the session-store write-through. The
  once-override lived only in memory before, but the write-through
  persisted it, so a gateway restart before the finally-restore
  rehydrated a supposedly one-turn model permanently.
- TUI: skip _sync_agent_model_with_config while a one-turn restore is
  pending. The once-model is deliberately not pinned as a session
  model_override, so the config sync saw a model mismatch and clobbered
  the once-override back to the config model before the turn ran.
- tests: real _handle_model_command drive asserting --once never
  touches set_model_override while --session still does; restore-pop
  idempotency.
- docs: /model --once in configuring-models.md with an honest
  prompt-cache cost note (one-shot switch breaks the cached prefix
  twice; wins for short sessions and cheap-to-expensive escalation).
2026-07-18 14:01:56 -07:00
deusyu
3f84b7a163 feat: add /model --once one-turn model override (#29914)
Adds --once to /model across CLI, TUI, and gateway: switch model for the
next turn only, restoring the previous model in a finally block so
success, exception, and interrupt all revert. Parsing extends
parse_model_flags_detailed(); resolve_persist_behavior() treats --once
as a persistence opt-out; --global + --once is rejected.

Salvaged from PR #29923 (image-generation lane split to #59815 per
review; conflict resolution against current main by the maintainers).
2026-07-18 14:01:56 -07:00
Teknium
38b39b87ef fix(discord): keep recovery ledger I/O off event loop
Offload scan bookkeeping and final-delivery writes so SQLite contention cannot stall Discord heartbeats or message delivery.
2026-07-18 14:01:33 -07:00
Teknium
2b2203e3a7 fix(discord): advance cursors only after final delivery
Round-robin configured Discord histories under the global scan cap, but move each channel/thread cursor only when its source message reaches successful final delivery.
2026-07-18 14:01:33 -07:00
Teknium
bc0e5adb1d fix(discord): persist per-channel recovery cursors
Resume each configured Discord channel/thread after its last scanned message so busy sources cannot permanently starve later history windows.
2026-07-18 14:01:33 -07:00
Teknium
92a7145297 test(streaming): include original reply anchor metadata 2026-07-18 14:01:33 -07:00
Teknium
9412f2dd84 fix(discord): persist streamed final delivery
Carry the original reply anchor through stream metadata so a successful final Discord edit marks the recovered source message complete.
2026-07-18 14:01:33 -07:00
Teknium
d5b9c1ee37 fix(discord): guard recovery claims and ledger failures
Honor configured bot senders at shared ingress, fail closed when durable state is unavailable, and suppress duplicate reconnect work while a fresh queued/processing claim is active.
2026-07-18 14:01:33 -07:00
Teknium
da955a643e fix(discord): preserve recovery message identity
Admit recovered events without consuming live dedup first, bypass split-message debounce, report actual dispatch admission, and require explicit reply correlation before suppressing a missed request.
2026-07-18 14:01:33 -07:00
Teknium
fad6cbaed3 fix(discord): make reconnect recovery lifecycle-safe
Preserve monotonic final-delivery completion, isolate recovery config and storage per adapter/profile, coalesce reconnect scans, release cancelled claims, bypass split-message debounce for historical events, and move bounded ledger setup into a short-timeout state module.
2026-07-18 14:01:33 -07:00
Teknium
95ce3344c4 test(discord): assert global recovery scan cap
Keep the scan-cap regression focused on the invariant instead of depending on per-channel ordering.
2026-07-18 14:01:33 -07:00
Teknium
80744bc2bc fix(discord): close reconnect recovery edge cases
Include allowed mention-gated channels in default recovery scope, keep the newest bounded history window, narrow outage-message detection, avoid disabled-path ledger I/O, and persist forum replies.
2026-07-18 14:01:33 -07:00
Teknium
2278f2cb7e fix(discord): harden reconnect message recovery
Route recovered messages through the live Discord ingress policy, preserve dedup and completion invariants, bound and retain the recovery ledger, and expose the opt-in config with docs and backup coverage.
2026-07-18 14:01:33 -07:00
emo-eth
867037bced test: update Discord backfill import for plugin adapter 2026-07-18 14:01:33 -07:00
James
a52041b2e0 fix: avoid masking missed Discord parent messages
Preserve startup missed-message backfill behavior while avoiding false address classifications from unrelated parent-channel messages.
2026-07-18 14:01:33 -07:00