merge: upstream/main v0.20 sync — unlocks desktop remote auth (DAN-2485) - #148
Merged
Conversation
… effect CI-caught: cron-jobs-section had an extra blank line between sorted imports; use-message-stream's visibility-flush effect assigns flushHandleRef.current=null inside a useEffect (legitimate timer-clear, not an atom mirror) — eslint-disable-next-line per the rule's documented convention.
Retarget NousResearch#73639 onto the SessionDB mixin split (hermes_state_common / hermes_state_schema). Fresh installs create UPDATE OF content/tool_* triggers; existing broad AFTER UPDATE triggers are inspected and replaced under schema init without an FTS rebuild (WHEN clauses already guarded content correctness; OF skips non-content status writes that saturated disk I/O on large state.db). Tests: tests/test_fts_update_of_narrowing.py (4)
_ensure_fts_cjk_schema never raises on OperationalError; post-condition after dropping messages_fts_cjk_update now requires a narrowed UPDATE trigger or durable fts_cjk_stale + unavailable. Covers the production soft-fail path the raise-only handler missed.
Simplify-pass fold: to_drop names come from the literal update_names\nallowlist via IN binding, so the [A-Za-z0-9_]+ fullmatch could never\nfail — and if it somehow did, its `continue` would miscount (the\nskipped trigger stayed in len(to_drop)/the log while CREATE TRIGGER\nIF NOT EXISTS silently kept the broad variant). Delete the guard and\nits function-local re import; keep the invariant as a comment.
## Summary - Normalize OpenViking endpoints through `is_always_blocked_url` and fall back to the default local endpoint when poisoned. - Keep intentional loopback / LAN self-host working. - Add focused unit tests. ## Salvage / credit Memory-provider endpoint floor sibling of RetainDB/Supermemory always-blocked hardening (avoids over-broad NousResearch#4984-style private-IP bans). (cherry picked from commit 8fa607d)
(cherry picked from commit 389a90b)
`_start_local_openviking_server()` spawned `openviking-server` unconditionally. Both callers — `initialize()` and the runtime unreachable handler — reach it from a health probe, and that probe can time out client-side while the server is up and serving. The spawned process then loses the data-directory lock and exits immediately with `DataDirectoryLocked`; because the probe keeps timing out, the cycle repeats every cooldown window (~5 min observed). The existing 30s `_failed_refresh` cooldown paces the loop but cannot stop it, since it expires while the underlying condition persists. Probe the target host:port before spawning and treat an occupied port as already-started. This guards both call sites at their single convergence point. The probe deliberately tests only that a listener owns the port — enough to know a second server would lose the lock — and says nothing about that listener's health. The parse/probe now precedes the PATH lookup, so a reachable server is reported as running even when `openviking-server` is not on PATH. Fixes NousResearch#74846 (cherry picked from commit b49427d)
The provider used to disable OpenViking permanently when the server was unreachable. That was fixed: `_ensure_client()` now reconnects lazily, with a 30s cooldown gate in `_ensure_client_locked`. Only one of the seven user-facing warnings was updated to match. The other six still told the user memory was "disabled for this Hermes run", which is no longer true — every one of those paths is retried on the next access. A user who reads the old message has no reason to retry, which is very likely how NousResearch#5721 ("never recovers") came to be filed against behaviour that already recovers. All six sites were traced to confirm none is terminal for the run: the `initialize()`-time and waiter-thread failures never arm `_failed_refresh` (only line 2439 does), so they retry on the very next access with no cooldown at all. The replacement wording deliberately omits the "(after cooldown)" parenthetical used at the already-correct site — that detail is only accurate where `_failed_refresh` was just armed. The neutral phrasing is true at all six. Also promotes two clause separators to periods to avoid "…; …disabled;" collisions. (cherry picked from commit 8346403)
`_committed_session_ids` is a permanent per-sid latch, and `_session_needs_commit` checks it before the turn counter by design — a racing sync_turn can re-increment `_turn_count` after commit+reset, so the guard must win to stop a double-commit. That is correct for a session being left behind. It is wrong for one that keeps its id. `compress_context()` commits before rewriting the transcript in both modes, and with `compression.in_place: true` (the default) `on_session_switch` receives the same id and does not rotate. The latch then rejects every later commit for a still-live session — the next compression, /new, normal session end, startup recovery — so every post-compression turn is silently never extracted. Rotation mode is unaffected because a fresh child id is minted and starts clean, which is what confirms the latch's intent was only ever to dedupe the departing id. Clear the latch when compression completes without rotation. Turns arriving after that point are genuinely new, and this is a defined moment rather than a race. The rotation path is untouched, so the old id stays latched and its _finalize_session_async still dedupes against the compression commit. Fixes NousResearch#74695 (cherry picked from commit d1e5c3d)
Review feedback: the previous test called _mark_session_committed directly, so it verified the guard's behavior but not the wiring that sets it — a future break in the commit_memory_session -> same-id compression-boundary path would not be caught. Add a lifecycle regression that drives the real sequence: on_session_end commits through the actual path, on_session_switch(same id, reason="compression") crosses the boundary, sync_turn records a genuinely new turn, and a second on_session_end must produce a second commit POST. Without the fix it fails showing exactly one commit call, which is the reported data loss: every turn after the first compression is dropped. The rotation and /undo tests stay as scope guards. (cherry picked from commit 0ca5a33)
…nViking and RetainDB OpenViking is_available() only consulted env vars and use_ovcli_config, so an endpoint saved to config.yaml (e.g. by the Dashboard) reported needs_config; _resolve_connection_settings() likewise never folded config.yaml's non-secret fields into its chain. RetainDB initialize() read base_url/project from the environment only, ignoring the values the Dashboard writes to config.yaml. Both now resolve non-secret fields as env -> (ovcli ->) config.yaml -> default; secrets still come from the environment. Adds regression tests for both. Fixes NousResearch#68209 (cherry picked from commit dca57915b97b5705b30927a062e1d0f2f23d3841)
…s as fallback _recall_config() previously read all settings (recall_limit, score_threshold, recall_resources, etc.) exclusively from environment variables. This forced users to store behavioural configuration in .env, violating the Hermes convention that .env is for secrets only. The infrastructure to load config.yaml -> memory.openviking was already in place via _load_hermes_openviking_config(), but _recall_config() never called it. Fix: call _load_hermes_openviking_config() and pass its values as the default parameter to _env_int/_env_float/_env_bool. Env vars still override config.yaml values, preserving backward compatibility. Closes NousResearch#62540 (cherry picked from commit 6aadf12)
…HOME tests Add three tests to TestOpenVikingConfigSchema: 1. test_recall_config_reads_from_config_yaml — writes memory.openviking settings in config.yaml and verifies _recall_config() consumes them. 2. test_recall_config_env_overrides_config_yaml — writes both config.yaml and OPENVIKING_RECALL_* env vars, verifies env takes precedence. 3. test_recall_config_partial_config_yaml — partially populated config.yaml falls back to defaults for omitted keys. All 46 openviking_plugin tests pass (43 existing + 3 new). (cherry picked from commit b8d7834)
Review follow-up for salvaged PR NousResearch#76782. Three setup-wizard validation functions called _normalize_openviking_url outside their try/except blocks. Since _normalize_openviking_url now raises _OpenVikingEndpointError for blocked or malformed endpoints, an invalid endpoint would crash the wizard instead of returning a friendly (False, message) tuple. - _validate_openviking_auth: move _normalize_openviking_url inside try - _validate_openviking_root_access: same - _validate_openviking_setup_values: catch _OpenVikingEndpointError explicitly - Remove dead ternary in _normalize_openviking_url safety check (candidate always has http/https scheme by that point) - Replace redundant float('-inf') < x < float('inf') with math.isfinite() in _setting_float; drop the redundant infinity check from _setting_int (is_integer() already rejects inf/nan)
…-cicav chore: contributor email mapping for cicav (legacy noreply form)
…ckup/vendor dirs SubdirectoryHintTracker re-injected identical context files whenever the same AGENTS.md was reachable through more than one path. Symlinked shared workspaces, hardlinks, and timestamped backup copies all alias a single file, so a normal session could ship the same 8KB of instructions two or three times. Nothing deduped it and nothing excluded directories that only ever hold copies. Two changes: * Track a sha256 of every injected hint body. Repeat content is skipped, and the working directory's own context file is seeded at construction so the copy prompt_builder already loaded at startup is never sent again. * Skip directories that hold copies rather than authoritative context (backups, node_modules, venv, site-packages, .git, .Trash, vendor, caches). Screening is relative to working_dir, so a project that legitimately lives under vendor/ keeps discovering its own subdirectory hints. Measured on a real session that touched a symlinked shared workspace: 3 injections / ~24,000 chars before, 1 injection / 8,112 chars after. 14 new tests cover symlink aliasing, byte-identical copies, working-dir seeding, distinct content still being injected, each excluded directory name, excluded ancestors, and the working-dir-inside-excluded-name case.
Re-derivation of NousResearch#23254 (@devsart95) on today's flush loop. The turn flush in _flush_messages_to_session_db wrote one BEGIN IMMEDIATE transaction per message row; a typical agent turn (user + assistant + tool results) paid 3-8 transactions -- and, off WAL (the default on macOS while the WAL-reset guard is active), 3-8 fsyncs -- per turn. Adds SessionDB.append_messages_batch: same row shape as append_message (shared _prepare_message_row serializer + _MESSAGE_INSERT_SQL column list, so the two writers cannot drift), same compression-lock and compression-closed guards, one aggregated session-counter UPDATE, one transaction for the whole batch. Row serialization stays outside the write lock. The flush loop now collects the turn's new rows and writes them in one call. All-or-nothing pairs exactly with the persisted-marker stamping: on failure no rows landed and no markers were stamped, so the next flush re-writes the whole tail (same recovery contract as before, minus the partial-prefix case that could double-count). Measured (same harness, 5-message turn, journal_mode=DELETE, synchronous=FULL): 2.32ms -> 0.83ms median per turn flush (64% faster, 5 fsyncs -> 1). On WAL the win is smaller but the atomicity fix holds.
Sibling sites of the per-message flush pattern: both branch-seed paths (session.branch in methods_session.py and the lazy seed persist in server.py) copied the parent history row-by-row -- one transaction per row, and a branch seed can be hundreds of rows. Route both through SessionDB.append_messages_batch. The server.py path also gains real atomicity: _branch_seed_persisted assumed every row landed, which the per-row loop could not guarantee.
…rites The flush now goes through append_messages_batch; MagicMock-based assertions and barrier fakes that hooked append_message observed nothing (the flush's try/except swallowed the AttributeError). Assert on the batch payload instead.
… share guards, chunk seeds Simplify-pass folds on the NousResearch#23254 salvage: - REUSE (HIGH): append_messages_batch now delegates row serialization to the pre-existing _insert_message_rows helper (already shared by replace_messages / archive_and_compact / portability import) instead of adding a third serialization path (_prepare_message_row + _MESSAGE_INSERT_SQL are gone). One row-writer for every multi-row path; the row-ID return was consumed by no production caller, so the batch returns the inserted count. - QUALITY (HIGH): the compression-lock + compression-closed admission guards are extracted into _check_transcript_write_guards, shared by append_message and append_messages_batch (previously duplicated 23 lines that had already needed targeted fixes, NousResearch#74478). The role-gated reasoning filtering is no longer duplicated in run_agent.py — it lives at its one site inside _insert_message_rows. - EFFICIENCY (MEDIUM, measured): unbounded seed copies hold one BEGIN IMMEDIATE for seconds (10k rows ~= 2.4s; FTS triggers dominate) and monopolize the in-process write lock. append_messages_batch grows a chunk_rows param; all seed/copy call sites use chunk_rows=500. Same recovery semantics as the old per-row loops, bounded lock holds. - REUSE (MEDIUM): the two remaining per-row branch-copy loops found by the pass (gateway/slash_commands.py /branch, hermes_cli cli_commands_mixin.py branch) are converted to chunked batches too (AsyncSessionDB's generic to_thread forwarder covers the async site). Turn-flush benchmark unchanged after the refactor: 2.43 -> 0.87 ms median per 5-message flush (64% faster).
…pend_messages_batch CI-caught: test_verification_stop_caching and test_tui_gateway_server::test_native_vision_turn_persists_a_renderable_image_ref both assert on append_message.call_args, but the flush loop now calls append_messages_batch. Same class of test-fake fallout fixed in 5 other files — these two were missed.
…#38491) Re-derivation of NousResearch#38491 by @stremtec onto current main (the original is 10,119 commits behind; the hook moved into ui-tui/src/app/). The hook returned a fresh object literal every render, defeating memoization in useMainApp's consumers; useMemo over the (all-useCallback-stable) handles makes the return referentially stable. Dep array covers ALL nine returned handles incl. trimTail (the re-derivation initially omitted it - stale-closure class).
A plain click on a composer file row in remote mode handed the backend's file:// URL to the local browser bridge, which cannot resolve a path that only exists on the gateway host. Route remote non-HTML file targets to the gateway-backed in-app preview pane instead; local files, ordinary URLs, and remote HTML (staged locally by openPreviewTargetInBrowser) keep their existing browser path. Supersedes NousResearch#70296 and NousResearch#57878. Co-authored-by: lesterlxt <153183032+lesterlxt@users.noreply.github.com> Co-authored-by: cj52973 <cjenkins@scacpa.org>
- perfectionist/sort-imports in store/wake-word.ts - contributors/emails mapping for drew@kainotomic.com -> appletechie
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…ch#79494) A plain click on a composer file row in remote mode handed the backend's file:// URL to the local browser bridge, which cannot resolve a path that only exists on the gateway host. Route remote non-HTML file targets to the gateway-backed in-app preview pane instead; local files, ordinary URLs, and remote HTML (staged locally by openPreviewTargetInBrowser) keep their existing browser path. Supersedes NousResearch#70296 and NousResearch#57878. Co-authored-by: lesterlxt <153183032+lesterlxt@users.noreply.github.com> Co-authored-by: cj52973 <cjenkins@scacpa.org>
…7705) * feat(desktop): shared pane-strip primitives — one bar, one glyph, one close menu The zone header hand-rolled its tab bar, its close-verb context menu, and the bare-glyph "+" inline; the preview rail kept a second copy of all three. Extract PaneTabStrip (the bar), PaneStripGlyph/PaneStripTool (glyph buttons as data, titlebar-tool style), and paneTabCloseItems (the four close verbs) into the pane-tab primitives, and render the zone header through them. Panes contribute strip glyphs via PaneChrome.stripTools; $stripToolsRevision tells the strip to re-read. * refactor(desktop): preview tabs are layout-tree tiles like session and page tiles The in-app browser / preview rail carried its own tab strip beside the zone's own — a second bar at a different height with its own close menu, label casing, ⌘W rung, and welded to the file browser's zone so ⌘J toggled it away. It predated the layout tree. $previewTabs now mirrors into pane contributions through the same paneMirror session and route tiles use, so a preview tab IS a zone tab: one strip, drag/ stack/split, the shared close verbs, plain ⌘W, its own zone docked beside main. URL tabs are titled Browser (the tab names the surface, not the page); files keep their filename and a file-type lead glyph. Deleted with the rail: the preview pane contribution + PREVIEW_PANE_ID + its visibility binding, the 'preview' placements in the default tree and presets, the ⌘W rail rung, the reveal listener, and the preview.close* i18n keys (copies of zones.*). lone-header now keys on "closeable placement:main" instead of the session-tile: id prefix, so any tile dragged into its own zone keeps its tab. * fix(desktop): preview console/DevTools live on the strip, and DevTools tells the truth The two toggles were titlebar tools — far from the preview they act on and one ambiguous global pair once two previews were open. They're strip glyphs now, contributed per-tab as PaneStripTool data with real tooltips: the console store is cached by tab id so the glyph and the panel read the same logs, and the pane registers a DevTools handle for its tab. DevTools active state was also a lie: it tracked our click handler, so closing the DevTools window itself left the glyph stuck on. The webview's devtools-opened/closed events drive it now. * fix(desktop): ⌘W and ⌃Tab work over preview and page zones The generic tab verbs keyed zone eligibility on the CHAT strip (workspace / session-tile: ids), so a zone holding only a Browser or page tile was invisible to them: ⌃Tab skipped it, and ⌘W fell through the chat rung and emptied the MAIN chat while you were looking at a preview. ⌘1…⌘9 already worked — the verbs disagreed about what counts as a tab strip. New isMainStripPane (any placement:'main' tenant — sessions, pages, previews) drives ⌘W and ⌃Tab; isSessionStripPane keeps gating what it should: where a session may dock (⌘T's anchor, the strip's +). * fix(desktop): preview tab selection follows the tree, not just the reverse openPreview drove tree reveals, but clicking a preview TAB only activated its pane in the tree — $rightRailActiveTabId kept naming the previous tab, so $previewTarget (⌘L quote labels, the titlebar's has-preview state) reported a tab that wasn't on screen. The mirror now also listens tree→store: when the interacted zone's active pane is a preview tile, the store selection follows. Both directions converge on the same id, so no ping-pong. * fix(desktop): session drags land in preview and page zones tileZoneHost replaces chatZonePane: a zone hosting any main tile (a Browser tile, a page) accepts stack and split drops — the known asymmetry where you could drag a preview tab out but never drag a session in. Only a CHAT zone's center is the link-to-composer drop; a preview zone's center stacks, since there's no composer to link to. * chore(desktop): drop the rail's dead multi-close verbs closeActiveRightRailTab / closeOtherRightRailTabs / closeRightRailTabsToRight lost their last callers when ⌘W and the close menu moved to the zone strip's shared rungs; the tests now exercise closeRightRailTab's own fallback behavior directly. * fix(desktop): open_preview lands whenever its session is on screen The preview.open handler honored the event only when its session was the FOCUSED one — but the turn that runs open_preview is usually a tile's session, and by the time the tool fires the user's last click has often parked focus on main (or anywhere else). The tool reported success, the store never wrote, and nothing appeared: an explicit 'open reddit' silently vanished. On-screen is the right bar: honor the open when the session is the primary chat or any open tile, which keeps truly invisible background sessions from yanking the pane (offer, don't hijack) without eating opens the user asked for. * fix(desktop): one Browser — a second URL navigates it, not a second tab Tabs were keyed url:<address>, so every distinct page the agent opened stacked another BROWSER tab — three opens, three Browsers, each titled identically because the tab deliberately names the surface, not the page. The title already said singleton; the key disagreed. URL targets now share one url:browser id: openPreview re-fronts the tab and swaps its target, and the pane rebuilds its webview against the new address. Files and artifacts keep per-identity tabs. Restored storage rekeys old per-address rows and keeps only the most recent.
The GUI now passes client_capture: true on wake.start, wake.status, and the post-voice re-arm; update the store and slash-handler tests to the new param shape. 123/123 pass locally.
PDFs were classified as generic binary/text previews, rendering raw %PDF bytes locally and failing entirely for remote-only files. Classify PDFs as their own preview kind, load bytes through the existing local/remote filesystem bridge, convert them to revocable Blob URLs for Chromium's embedded viewer, migrate persisted pre-PDF tabs at restore, and retry restored previews when the active filesystem connection changes. Salvaged from NousResearch#76008-era base onto current main: PDF classification now composes with the remote-HTML enrichment branch, and the persisted-tab migration runs before the One-Browser URL rekey in decodePreviewTabs. Supersedes NousResearch#76565. Co-authored-by: Brooklyn Nicholson <brooklyn@brooklyn.sh>
…reaming (NousResearch#79491) * feat(wake): client-capture wake word for remote desktop Remote headless backends have no PortAudio mic, so "hey hermes" fails even when openWakeWord is installed. Let the desktop stream 16 kHz int16 PCM via wake.feed while detection stays server-side. - wake_word.capture: auto|local|client (+ GUI client_capture prefer) - WakeWordDetector external_audio queue + feed_audio API - wake.feed RPC; wake.start/status report capture + frame_length - Desktop getUserMedia feeder; stop on wake.detected, re-arm after voice - Docs + unit tests (26 pass in tests/tools/test_wake_word.py) * fix(wake): address review on client-capture re-arm and feed queue - wake.status reports effective capture from the armed detector (client vs local), plus frame_length/sample_rate; GUI status probes prefer client - Gateway test doubles accept external_audio on start_listening - Desktop PCM feeder uses a bounded ordered queue instead of dropping frames while a wake.feed RPC is in flight - /wake on and status/re-arm paths pass client_capture so remote reattach works * fix(wake): auto capture keeps the backend mic when one exists With capture:auto the desktop always preferred client streaming, so a local desktop with a working backend mic silently switched from PortAudio to getUserMedia default-device — dropping wake_word.input_device selection (NousResearch#74363). A ready backend input now wins under auto; client capture is the fallback for a preferring surface on a mic-less backend, and capture:client still forces streaming. Also removes the dead auto branch (both arms returned local) and lets the client-feed test skip cleanly when numpy is absent. * perf(desktop): coalesce wake.feed frames Sending one 80 ms frame per RPC is ~12.5 gateway calls/s for as long as the ear is armed. Drain up to 4 queued frames into a single wake.feed payload (backend feed() already splits long buffers into engine frames) — ~3 RPCs/s steady-state. Fix the wake.feed size-cap comment (64000 bytes = 2 s, not 0.5 s). * docs(config): document wake_word.capture in cli-config.yaml.example --------- Co-authored-by: Andrew <drew@kainotomic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
The dashboard now mints an action_id per backend update, hands it to the spawned `hermes update` via HERMES_ACTION_ID, and reuses an in-flight update action instead of spawning a duplicate. The updater prints a bounded `=== hermes-update completed <id> ===` receipt on every success path — normal, zip, dependency-repair, and the no-op "Already up to date!" path that previously ended with no terminal marker at all (NousResearch#58764) — so the Desktop can prove completion across the dashboard restart boundary instead of guessing from stale log text. Co-authored-by: Vitor Cepeda Lopes <vitor@vitorcepedalopes.com> Co-authored-by: doncazper <caztronics@yahoo.com>
Remote backend updates failed with "Backend update failed." on nearly every run: applyBackendUpdate() polled for only 30×1.5s ≈ 45s, then read exit_code null off the still-running action and called it a failure. Real updates (backup + uv sync + npm install + vite build) routinely run longer, and the no-op "Already up to date" path never restarted the gateway so the old return-check timed out too. A still-running, reachable action is now never converted into failure by an elapsed budget — only a nonzero exit is. The apply loop keeps one in-flight promise, tolerates reconnects during the dashboard restart without extending the fixed six-minute deadline forever, and confirms success by the action-specific receipt that survives the restart, falling back to proving the requested commit / up-to-date check for older backends without action_id support. Inconclusive completion fails closed. Fixes NousResearch#47359 Fixes NousResearch#58764 Co-authored-by: Vitor Cepeda Lopes <vitor@vitorcepedalopes.com> Co-authored-by: Mark Vlcek <markvlcek@gmail.com> Co-authored-by: doncazper <caztronics@yahoo.com>
…-preview fix(desktop): render remote PDFs in preview rail
* feat(agent): read_preview — the desktop-gated tool that reads the in-app browser The agent could open the preview pane (open_preview) and read the embedded terminal (read_terminal), but the browser it had just opened was a black box — 'what does this page say?' had no answer. read_preview mirrors read_terminal end to end: HERMES_DESKTOP-gated via check_fn (zero schema footprint outside the GUI), dispatched through the same agent callback pattern, windowed with start/count so a long page pages instead of flooding context. * feat(gateway): preview.read blocking bridge Same lifecycle as terminal.read: the tool blocks on preview.read.request, the renderer answers preview.read.respond (allow_expired — a slow page extraction losing the 45s race must not surface a raw 4009), and a timeout emits preview.read.expire so late answers resolve quietly. * feat(desktop): the renderer serializes the active preview tab for the agent preview-reader.ts is the preview analog of the terminal's buffer registry: the URL pane registers a page reader (webview executeJavaScript → title + visible innerText) keyed by tab id; readActivePreview resolves the ACTIVE tab, windows the text (24k cap per read), and answers file/artifact tabs with identity plus a note pointing at the tool that reads that content directly. The gateway event handler answers preview.read.request beside terminal.read.request.
…tes (NousResearch#79513) * feat(update): emit an action-scoped terminal receipt from hermes update The dashboard now mints an action_id per backend update, hands it to the spawned `hermes update` via HERMES_ACTION_ID, and reuses an in-flight update action instead of spawning a duplicate. The updater prints a bounded `=== hermes-update completed <id> ===` receipt on every success path — normal, zip, dependency-repair, and the no-op "Already up to date!" path that previously ended with no terminal marker at all (NousResearch#58764) — so the Desktop can prove completion across the dashboard restart boundary instead of guessing from stale log text. Co-authored-by: Vitor Cepeda Lopes <vitor@vitorcepedalopes.com> Co-authored-by: doncazper <caztronics@yahoo.com> * fix(desktop): make remote backend updates terminal-state driven Remote backend updates failed with "Backend update failed." on nearly every run: applyBackendUpdate() polled for only 30×1.5s ≈ 45s, then read exit_code null off the still-running action and called it a failure. Real updates (backup + uv sync + npm install + vite build) routinely run longer, and the no-op "Already up to date" path never restarted the gateway so the old return-check timed out too. A still-running, reachable action is now never converted into failure by an elapsed budget — only a nonzero exit is. The apply loop keeps one in-flight promise, tolerates reconnects during the dashboard restart without extending the fixed six-minute deadline forever, and confirms success by the action-specific receipt that survives the restart, falling back to proving the requested commit / up-to-date check for older backends without action_id support. Inconclusive completion fails closed. Fixes NousResearch#47359 Fixes NousResearch#58764 Co-authored-by: Vitor Cepeda Lopes <vitor@vitorcepedalopes.com> Co-authored-by: Mark Vlcek <markvlcek@gmail.com> Co-authored-by: doncazper <caztronics@yahoo.com> --------- Co-authored-by: Vitor Cepeda Lopes <vitor@vitorcepedalopes.com> Co-authored-by: doncazper <caztronics@yahoo.com> Co-authored-by: Mark Vlcek <markvlcek@gmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
…lay-skill-metrics feat(observability): aggregate bounded skill metrics
…lay-client-dimensions feat(observability): add Relay client resource metrics
…lay-install-activation-metrics feat(observability): add Relay active install metrics
Strategy: -X theirs (upstream wins all conflicts). Dan's custom fork commits (memgw provider, slack wiring, email HTML, cbm hooks, CI workflows, security redactions) cherry-picked back in follow-ups. Deps bumps left to upstream. # Conflicts: # acp_registry/agent.json # gateway/platforms/slack.py # gateway/platforms/telegram.py # tests/cron/test_cron_profile.py # ui-tui/package-lock.json # ui-tui/packages/hermes-ink/package-lock.json # web/package-lock.json
…2485) Co-Authored-By: Claude <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
16 conflicts resolved by taking sync-branch (v0.20) version: - 8x shared_metrics/relay_shared_metrics observability (add/add) - uv.lock (clean v0.20 lockfile; main's had duplicate-key bug from #146) - package-lock.json x2, ui-tui/package.json, apps/desktop/package.json - 3x test files + 1 smoke script uv lock --check passes (252 packages, 0 duplicate keys). Co-Authored-By: Claude <noreply@anthropic.com>
This was referenced Aug 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Deploy plan (after merge)
Test plan
Refs DAN-2485
🤖 Generated with Claude Code