Merge upstream v2026.6.5 into main - #19
Conversation
Remove the outer card chrome (border/bg/shadow/rounded) wrapping each appearance section so they're flat headings + option grids instead of boxes nested inside boxes, matching the other settings pages.
… swatches Color Mode and Tool Call Display become flat radio-style rows (no tile border/fill, no inner icon box, no filled check badge — just a subtle active bg and a check). Theme drops its outer card wrapper so only the preview swatch shows, with a primary ring marking the active palette.
…lists Add a base-layer rule giving every interactive control (button, select, menu item, switch, tab, summary) cursor:pointer, and strip the now-redundant hardcoded cursor-pointer from those elements (plain clickable divs/labels keep theirs). Remove the divide-y separators from settings list sections so they breathe.
…ontrols Replace the vertical option-row lists with a compact SegmentedControl (grouped pill buttons on a single track), dropping the per-option descriptions since the section subtitle already covers the context.
…itches The switch already communicates state, so the text label was noise.
…ight Add an xs size variant to the Switch primitive and use it for the provider edit submenu toggles. In appearance settings, drop the redundant selection Pills (the UI already shows the active choice), move the Color Mode and Tool Call Display segmented controls into the section header's right side (responsive: stacks under the heading on narrow widths), and shrink the segmented control.
… noise (NousResearch#38665) `test_tty_passthrough_to_container` asserted `int(numeric_lines[0]) > 0` where `numeric_lines` was every `.isdigit()` token in the FULL PTY stream — but the container's s6 boot output (cont-init diagnostics, the preinit `uid=0 ... egid=0` line, skills-sync summaries like `Done: 90 new, 0 updated, 0 unchanged. 90 total bundled.`) is written to the same PTY before the `tput cols` probe runs. So the test was really asserting on "the first number anywhere in the boot log", which passed only by luck on whatever that first digit happened to be. Any PR that shifts boot output flips the first digit to a stray `0` and breaks the test with `assert 0 > 0` — even when TTY passthrough is working perfectly (`tput cols` returns the right value). This is a latent landmine for every Docker PR that changes boot output (e.g. adding a bundled dependency changes the skills-sync counts). Fix: emit the probe result behind a unique marker (`HERMES_TTY_COLS=<cols>` / `HERMES_TTY_COLS=NO_TTY`) and parse only the marked value, ignoring all boot-log noise. The test's real intent — verify `docker run -t` delivers a real TTY with a positive column count — is preserved (NO_TTY and non-numeric values still fail). Verified against a real build, adversarially: - Built an image with extra boot output (the markdown core-dep change from NousResearch#38649, which is what surfaced this) so the OLD logic grabs a stray `0` -> reproduced `assert 0 > 0` locally. - The hardened test PASSES against that same image, and against a clean image. `tput cols` correctly returns 123 in both.
…ling (NousResearch#38593) Both POST /api/model/set and the profile-model writer hand-rolled the same provider/default/base_url/context_length reconciliation. Extract it into _apply_main_model_assignment so the custom-vs-hosted base_url logic lives in one place — removing the future-drift risk where one site learns about custom base_url persistence and the other forgets. Behavior unchanged; pinned with a direct helper unit test.
Adds a top-left swap button (replacing the search icon) that mirrors the layout: sessions sidebar ↔ file browser + preview rail. Persisted via $panesFlipped. The left/right sidebar toggles, content inset, and pane borders all follow the active side so the buttons stay accurate after a flip.
…earch#37928) (NousResearch#38655) Salvage of NousResearch#37928 (@sarvesh1327), reduced to the still-needed delta. `/opt/hermes/gateway` is a runtime-writable Python package: on first import the supervised gateway writes `__pycache__` beneath it, and the image does not set PYTHONDONTWRITEBYTECODE. When HERMES_UID/PUID is remapped at boot (e.g. Unraid 99), `usermod -u` only re-chowns the hermes home dir; the build trees under /opt/hermes keep the build-time UID (10000). main already chowns `.venv`, `ui-tui`, and `node_modules` on remap (NousResearch#38556) but missed `gateway`, so the remapped gateway hits EACCES writing `__pycache__` (NousResearch#27221). Add `/opt/hermes/gateway` to both chown sites — the Dockerfile build-time `chown -R hermes:hermes` line and the stage2-hook build-tree repair — so it tracks the remapped UID like the sibling trees. Differs from NousResearch#37928 as submitted: dropped the `uid_gid_remapped` flag and the `|| [ "$uid_gid_remapped" = true ]` chown gate. main's NousResearch#38556 already solved that half, and more correctly — it probes the actual tree ownership (`venv_owner != actual_hermes_uid`) rather than tracking same-boot remaps, which also catches pre-existing ownership drift and stays idempotent. Keeping NousResearch#37928's flag would regress that. The salvage is the `gateway`-tree addition only. Verified end-to-end against a real image build: on baseline main a remap to UID 99 leaves `gateway` owned by 10000 and a write as uid 99 fails EACCES; with this change `gateway` is chowned to 99:100 and the write succeeds, while the default-uid (no-remap) path is unchanged. Fixes NousResearch#27221. Co-authored-by: Sarvesh <sarveshagl1327@gmail.com>
…earch#5954) (NousResearch#38668) `_install_dependencies` (hermes memory setup) hard-aborted with "uv not found — cannot install dependencies" whenever `uv` was not on PATH, even when a perfectly good `pip` was available. Slim container images and some CI environments don't ship uv, so memory-provider dependency installation dead-ended there for no good reason. Now: use `uv pip install` when uv is present, otherwise fall back to `<python> -m pip install` when pip3/pip is available, and only abort (with the uv install hint) when neither is found. The "Run manually:" hints reflect whichever installer was selected. Salvages NousResearch#5954 by @MustafaKara7. Their patch added redundant local `import subprocess` / `import sys` (both are already in scope — module -level `sys`, function-top `subprocess`); this salvage drops those and adds a regression test (TestInstallDependenciesRunner) covering all three paths (uv / pip-fallback / abort). Verified adversarially: the pip-fallback test fails against origin/main's unfixed code with the exact dead-end symptom and passes with the fix. Closes NousResearch#5954. Co-authored-by: MustafaKara7 <186085093+MustafaKara7@users.noreply.github.com>
…lm (NousResearch#33685) Closes NousResearch#25495 (matrix/synapse broken in the official docker image). `tools/lazy_deps.py` routes `platform.matrix` to `mautrix[encryption]==0.21.0`, which transitively depends on `python-olm`. `python-olm` is a Cython extension that links against `libolm`; without `libolm-dev` in the image's apt set the lazy-install build fails. Add `libolm-dev` to the runtime apt install line so the in-container source build succeeds on first matrix use. Salvages NousResearch#27795 by @konsisumer. Their PR targeted a pre-rework Dockerfile (still had `build-essential nodejs npm` in the apt list, no `ca-certificates`); cherry-pick conflicts on incidental apt-list churn, so this re-applies the same one-word insert against the current apt line plus the matching pyproject.toml comment update. Co-authored-by: konsisumer <11262660+konsisumer@users.noreply.github.com>
…n resets (salvage NousResearch#38502) (NousResearch#38677) * fix(desktop): critical fixes — attachments, IME composition, scroll, fetchJson DC2: Pass attachments to onSubmit() on direct Enter submit and call clearComposerAttachments(). Previously attachments were silently dropped — only text was sent while attachment pills remained visible. DH1: Add 'open' to ThinkingDisclosure ResizeObserver effect deps. When the disclosure toggles, refs point to new DOM but the observer wasn't reattached, breaking live-scroll preview after expand/collapse and leaking detached DOM nodes. DH3+DH4: Add composition tracking via composingRef (set by compositionstart/compositionend). Guards handleEditorInput (skip preedit state writes), handleEditorKeyDown (prefer composingRef over unreliable isComposing), and form onSubmit (prevent IME Enter from triggering submission). Fixes IME Enter message splitting and preedit text leaking into app state on CJK input. DH6: Add res.on('error', reject) to fetchJson response stream. Without this, a TCP reset mid-transfer left the promise hanging forever, freezing the desktop UI. All TypeScript compiles cleanly. * chore: add copii.list@gmail.com to AUTHOR_MAP (stremtec) * fix(desktop): prevent scroll snap-back during streaming, atomic config writes DH2: Defer pinToBottom() in useLayoutEffect to rAF so that browser scroll/wheel events from the current frame are processed first. Previously an immediate pinToBottom() could snap the viewport back to bottom against the user's trackpad scroll-up intent during streaming — the wheel event hadn't fired yet so stickyBottomRef was still true. DH7: Add writeFileAtomic() helper (write to .tmp then rename) and use it in writeDesktopConnectionConfig, writeDesktopUpdateConfig, and writeBootstrapMarker. Prevents partial writes on crash/power loss that would corrupt JSON config files, requiring manual repair. * fix(desktop): guard nativeTheme listener from duplicates, invalidate connection config cache DM9: Guard nativeTheme.on('updated') with a one-shot flag so that multiple createWindow() calls (e.g. macOS activate after all windows closed) don't accumulate duplicate listeners on the process-wide singleton. DM3: Add mtime-based cache invalidation to readDesktopConnectionConfig. Previously the cache was populated once and never invalidated — if an external tool modified connection.json, the desktop ignored the change until restart. Now re-reads when the file's mtime differs. * fix(desktop): widen fetchJson res.on('error') to sibling fetch + sort JSX props Follow-up to salvaged NousResearch#38502: - resourceBufferFromUrl had the same mid-stream-reset hang class as fetchJson (req.on('error') present, res.on('error') missing). Add the response-stream error handler so a TCP reset during body read rejects instead of leaving the promise unsettled. - Sort the new onComposition* JSX props to satisfy perfectionist/sort-jsx-props (was an introduced eslint error in the composer). --------- Co-authored-by: asill-livestream <copii.list@gmail.com>
Builds on the clarify/needs-input work with a cross-cutting pass to make the desktop surfaces feel like one app. - Global Cmd+K command palette (cmdk): nav, settings deep-links, async API-key / MCP-server / archived-session groups, reusable theme sub-page (light/dark groups, stays open on pick), loop nav, fuzzy match. Replaces per-page settings search. - Shared SearchField: borderless, underline-on-focus, `field-sizing` auto-width. Unifies sessions sidebar, pages, overlays, command center, cron; drops bespoke OverlaySearchInput. - Cron & Profiles converted to OverlayView; flat token-driven panels (no card-in-card / divider borders) matching command center. - `r` refresh hotkey via useRefreshHotkey; drop the visible refresh buttons. - Button text/textStrong link variants applied across settings & views; shared PAGE_INSET_X content gutters. - Math/ascii loaders replace "Loading…" text placeholders; x-icon close over text "Close"; cursor-pointer at the dropdown/select primitive level.
Reload window → text link, Open logs pushed right (ml-auto), and the error message box drops the oversized rounded-2xl for rounded-md.
The full-width `text` New-profile button drew an underline under the + glyph on hover (text-decoration spans the icon). Replace with a proper "PROFILES" section header + ghost add-icon button, matching the chat sidebar's header/new-item pattern.
Tab/focus showed Tailwind's `focus-visible:ring-*` (a box-shadow) plus the native outline. Drop both via an unlayered reset that nulls --tw-ring-*; the composer / input soft-glow is untouched (those use direct box-shadows).
Add a proper shadcn-style Badge (CVA tones, app radius — not a full pill) and use it for the Default/.env tags instead of bespoke rounded-full spans. Drop the oversized text-sm metadata values to text-xs.
…ebar - Sidebar toggles in the titlebar no longer carry an active highlight — they're plain show/hide affordances now. - Replace every bespoke rounded-full status pill (cron, messaging, settings, skills) with the shared Badge (adds a `warn` tone). App radius, one component. - Cron row actions use Codicons (play/debug-pause/zap/edit/trash) to match the rest of the chrome instead of stray lucide glyphs.
Mute/haptics state reads from the icon glyph (and aria-pressed) — no background highlight on any titlebar action.
gap-4 → gap-2.5 between Try again / Reload window.
Empty datasets no longer render a search field. Adds a `searchHidden` prop to PageSearchShell (artifacts/skills/messaging) and gates cron + command center sessions search on a non-empty list. The chat sidebar already did this via showSessionSections.
Long unbroken input ran off horizontally and the stacked layout flipped on a char-count guess (too early). Add wrap rules to the contentEditable and drive expansion off the editor's actual rendered height via the resize observer, so it stacks exactly when the text wraps to a 2nd line.
- Composer single-line row centers (was bottom-aligned); placeholder randomizes per session (starter vs follow-up) without mid-stream flip. - Drop chat header on brand-new sessions (dead label + border). - ⌘N flashes its sidebar hint; ⌘. toggles the command center. - Intro wordmark fills width (drop 8rem fit cap). - Unify error states on a shared ErrorState component (boundary + updates).
- Shared useDeepLinkHighlight hook collapses 3 near-identical settings deep-link effects (keys/mcp); config kept inline (distinct bail-clear). - command-center: table-driven SECTION_ICONS + single errorText helper. - clarify-tool: OPTION_ROW_CLASS + RadioDot extracted from option rows. - desktop-controller: merge Cmd+K / Cmd+. into one keydown handler. - statusbar-controls: hoist shared action class. - Misc: drop redundant cn()/cursor-pointer/dead fields; tidy switch.
Add active sessions to the palette (fuzzy jump-to-chat), remove the low-value per-API-key entries, and move the lazy palette sources (config/sessions/archived) to react-query instead of hand-rolled useState + effect fetching. Hoist the shared nav helper.
…d+K palette & UI consistency pass (NousResearch#38631) * fix(desktop): surface background-session clarify prompts instead of hanging clarify.request is a one-shot blocking event: the gateway turn blocks on clarify.respond. The desktop handler dropped it for any non-focused session (`if (!isActiveEvent) return`) and stored at most one request in a single global atom, so a background session that asked a clarifying question hung forever and re-focusing it could never recover (the event was already gone). - store/clarify.ts: key pending requests by runtime session id; expose the active session's request via a focus-scoped computed view (ClarifyTool is unchanged). clearClarifyRequest takes an optional session id for targeted clears, with a request-id fallback. - use-message-stream.ts: park every session's clarify (drop the isActiveEvent early return); toast when one lands for a background session since the row otherwise just keeps spinning like normal work. - clarify-tool.tsx: clear by session id so answering one chat can't wipe another's pending request. - store/clarify.test.ts: concurrent independence, focus-scoped view, targeted/stale/fallback clears. * feat(desktop): persistent needs-input indicator + icon button consolidation Replace the background-clarify toast (expired on alt-tab, easy to miss) with a persistent, glowing amber "needs input" dot on the session's sidebar row, driven off a new ClientSessionState.needsInput flag mirrored into a $attentionSessionIds store. The flag is set on clarify.request and cleared the moment the turn resumes (tool.complete) or ends. Also: redesign the clarify tool UI (borderless choices, pseudo-radio dots, right-aligned checkmark, arc border, tighter padding), make Button the single source of icon-button styling (4px radius, new icon-titlebar variant, titlebar buttons rendered polymorphically via asChild, Codicons throughout), put the file-tree refresh action first, and .trim() pasted composer text. * style(desktop): padding-driven, square non-icon buttons Default button sizing was vanilla-shadcn chunky (fixed h-9, 16px padding) and inconsistent with the icon-button radius pass. Size text variants by padding + line-height instead of fixed heights so they stay snug and scale with content, and drop the radius on non-icon buttons (icon buttons keep the shared 4px). Move the update-overlay CTAs off a hardcoded h-10 onto the padding-based lg variant. Composer and the inline approval strip are untouched. * style(desktop): shrink button scale, flush overlay sidebar, variant-ize stray buttons - Buttons: smaller default font (14px -> 13px) and tighter padding-driven sizes across every variant; the chunky shadcn scale read as oversized in a dense desktop UI. - Overlay split layout (settings / command center): the shared OverlayView top padding left the card surface showing as a gap above the sidebar. Move the titlebar clearance into each column so the sidebar background runs flush to the card's top edge. - Consolidate buttons that hardcoded size/radius/font onto the proper size variants (tooltip-icon-button, overlay close, cron IconAction, SidebarTrigger, gateway system button, session-row actions radius, title chip radius, release notes link) so styling flows from variant props, not per-call overrides. Composer and the inline approval strip are intentionally left as-is. * style(desktop): 12px button text, drop sparkle decoration + redundant settings titles - Button base font down to 12px (text-xs) for the dense desktop scale. - Remove the decorative Sparkles glyph from the model "Apply" button (keep the spinner while applying). - Drop the page-level section titles that just restate the left nav ("Main model", "Appearance", "MCP servers") — the sidebar already labels the pane. Sub-section headings (Auxiliary models, LLM providers, etc.) stay. * feat(desktop): add boxless `text` button variant; use for aux-model actions New reusable `text` variant renders a button as inline label text (no bg/border, muted -> foreground, underline-on-hover affordance). Emphasize the actionable word by adding `font-semibold`/`underline` at the call site. Applied to the auxiliary-model "Set to main" (plain), "Change" and "Reset all to main" (bold + underlined) actions, replacing the boxed ghost/outline buttons. * style(desktop): nudge button scale up + 2.5px radius on non-icon buttons Bump default/sm vertical padding a step (the 12px pass read too small) and give non-icon buttons a subtle 2.5px radius instead of square corners. Icon buttons keep their 4px. * style(desktop): unify Input/Textarea/SelectTrigger on shared controlVariants Mirror the buttonVariants exercise for non-composer form controls: add a single controlVariants source of truth (2.5px radius, 12px text, padding-driven sizing, chrome via desktop-input-chrome) and consume it from Input, Textarea, and SelectTrigger. Drop per-call radius/height/font overrides that fought the shared look. * style(desktop): flatten appearance settings — drop card-in-card sections Remove the outer card chrome (border/bg/shadow/rounded) wrapping each appearance section so they're flat headings + option grids instead of boxes nested inside boxes, matching the other settings pages. * style(desktop): de-box appearance options into flat rows + bare theme swatches Color Mode and Tool Call Display become flat radio-style rows (no tile border/fill, no inner icon box, no filled check badge — just a subtle active bg and a check). Theme drops its outer card wrapper so only the preview swatch shows, with a primary ring marking the active palette. * style(desktop): primitive-level pointer cursor + borderless settings lists Add a base-layer rule giving every interactive control (button, select, menu item, switch, tab, summary) cursor:pointer, and strip the now-redundant hardcoded cursor-pointer from those elements (plain clickable divs/labels keep theirs). Remove the divide-y separators from settings list sections so they breathe. * style(desktop): Color Mode + Tool Call Display as one-row segmented controls Replace the vertical option-row lists with a compact SegmentedControl (grouped pill buttons on a single track), dropping the per-option descriptions since the section subtitle already covers the context. * style(desktop): drop redundant On/Off label next to boolean config switches The switch already communicates state, so the text label was noise. * style(desktop): add Switch xs size; move appearance controls inline-right Add an xs size variant to the Switch primitive and use it for the provider edit submenu toggles. In appearance settings, drop the redundant selection Pills (the UI already shows the active choice), move the Color Mode and Tool Call Display segmented controls into the section header's right side (responsive: stacks under the heading on narrow widths), and shrink the segmented control. * feat(desktop): titlebar toggle to flip sidebar sides Adds a top-left swap button (replacing the search icon) that mirrors the layout: sessions sidebar ↔ file browser + preview rail. Persisted via $panesFlipped. The left/right sidebar toggles, content inset, and pane borders all follow the active side so the buttons stay accurate after a flip. * feat(desktop): global Cmd+K palette + UI consistency overhaul Builds on the clarify/needs-input work with a cross-cutting pass to make the desktop surfaces feel like one app. - Global Cmd+K command palette (cmdk): nav, settings deep-links, async API-key / MCP-server / archived-session groups, reusable theme sub-page (light/dark groups, stays open on pick), loop nav, fuzzy match. Replaces per-page settings search. - Shared SearchField: borderless, underline-on-focus, `field-sizing` auto-width. Unifies sessions sidebar, pages, overlays, command center, cron; drops bespoke OverlaySearchInput. - Cron & Profiles converted to OverlayView; flat token-driven panels (no card-in-card / divider borders) matching command center. - `r` refresh hotkey via useRefreshHotkey; drop the visible refresh buttons. - Button text/textStrong link variants applied across settings & views; shared PAGE_INSET_X content gutters. - Math/ascii loaders replace "Loading…" text placeholders; x-icon close over text "Close"; cursor-pointer at the dropdown/select primitive level. * style(desktop): tidy root error-boundary actions Reload window → text link, Open logs pushed right (ml-auto), and the error message box drops the oversized rounded-2xl for rounded-md. * style(desktop): fix profiles sidebar — header + add-icon, drop text-link The full-width `text` New-profile button drew an underline under the + glyph on hover (text-decoration spans the icon). Replace with a proper "PROFILES" section header + ghost add-icon button, matching the chat sidebar's header/new-item pattern. * style(desktop): kill focus rings globally Tab/focus showed Tailwind's `focus-visible:ring-*` (a box-shadow) plus the native outline. Drop both via an unlayered reset that nulls --tw-ring-*; the composer / input soft-glow is untouched (those use direct box-shadows). * style(desktop): shared Badge component; tidy profile metadata Add a proper shadcn-style Badge (CVA tones, app radius — not a full pill) and use it for the Default/.env tags instead of bespoke rounded-full spans. Drop the oversized text-sm metadata values to text-xs. * style(desktop): migrate bespoke pills to shared Badge; tidy cron/titlebar - Sidebar toggles in the titlebar no longer carry an active highlight — they're plain show/hide affordances now. - Replace every bespoke rounded-full status pill (cron, messaging, settings, skills) with the shared Badge (adds a `warn` tone). App radius, one component. - Cron row actions use Codicons (play/debug-pause/zap/edit/trash) to match the rest of the chrome instead of stray lucide glyphs. * style(desktop): drop active background on titlebar actions Mute/haptics state reads from the icon glyph (and aria-pressed) — no background highlight on any titlebar action. * style(desktop): tighten error-boundary action gap gap-4 → gap-2.5 between Try again / Reload window. * style(desktop): hide search when there's nothing to search Empty datasets no longer render a search field. Adds a `searchHidden` prop to PageSearchShell (artifacts/skills/messaging) and gates cron + command center sessions search on a non-empty list. The chat sidebar already did this via showSessionSections. * fix(desktop): composer wraps long text & expands at the real wrap point Long unbroken input ran off horizontally and the stacked layout flipped on a char-count guess (too early). Add wrap rules to the contentEditable and drive expansion off the editor's actual rendered height via the resize observer, so it stacks exactly when the text wraps to a 2nd line. * feat(desktop): composer/intro polish + shared ErrorState - Composer single-line row centers (was bottom-aligned); placeholder randomizes per session (starter vs follow-up) without mid-stream flip. - Drop chat header on brand-new sessions (dead label + border). - ⌘N flashes its sidebar hint; ⌘. toggles the command center. - Intro wordmark fills width (drop 8rem fit cap). - Unify error states on a shared ErrorState component (boundary + updates). * style(desktop): satisfy lint across PR-touched files * refactor(desktop): DRY/elegance pass over PR-touched files - Shared useDeepLinkHighlight hook collapses 3 near-identical settings deep-link effects (keys/mcp); config kept inline (distinct bail-clear). - command-center: table-driven SECTION_ICONS + single errorText helper. - clarify-tool: OPTION_ROW_CLASS + RadioDot extracted from option rows. - desktop-controller: merge Cmd+K / Cmd+. into one keydown handler. - statusbar-controls: hoist shared action class. - Misc: drop redundant cn()/cursor-pointer/dead fields; tidy switch. * feat(desktop): Cmd+K jumps to sessions; drop API-key entries Add active sessions to the palette (fuzzy jump-to-chat), remove the low-value per-API-key entries, and move the lazy palette sources (config/sessions/archived) to react-query instead of hand-rolled useState + effect fetching. Hoist the shared nav helper.
… from NousResearch#32487) (NousResearch#38756) * refactor(supermemory): session-level conversation ingest + kebab tool aliases Salvaged from NousResearch#32487 (by @MaheshtheDev), rebased onto current main. - sync_turn now buffers cleaned turns; the full session is ingested once at session end / switch / shutdown via the conversations endpoint - ingest_conversation() accepts and forwards functional document metadata (type, session_id, message_count, partial) - register kebab-case tool aliases (supermemory-save/search/forget/profile) alongside the snake_case names - README + docs (EN/zh-Hans) updated for the simplified session model Source/vendor-attribution removed per project policy (no telemetry): dropped x-sm-source header, sm_source metadata, and sm_capture_mode tags. Preserved the post-branch atomic_json_write(mode=0o600) hardening that the PR's stale base had reverted. Updated provider tests for the new behavior and added maheshthedev@gmail.com to release.py AUTHOR_MAP. Co-authored-by: alt-glitch <balyan.sid@gmail.com> * feat(supermemory): restore x-sm-source for Spaces routing Reinstates x-sm-source: hermes (SDK default_headers + conversations POST) and sm_source: hermes document metadata. Per @Dhravya (Supermemory), this is a functional routing key, not telemetry: it groups Hermes writes into a dedicated "Hermes" Space in the Supermemory app so users can filter and bulk-manage memories per source agent. sm_capture_mode remains dropped (appears analytics-only; Spaces are routed by sm_source) pending confirmation. Adds README note + a unit test covering _merge_metadata sm_source stamping and legacy source->type migration. --------- Co-authored-by: Mahesh Sanikommu <maheshthedev@gmail.com>
…esearch#39727) * fix: respect disabled auto-compaction on context overflow Port from anomalyco/opencode#30749. When compression.enabled is false, NO automatic compaction trigger may fire. The proactive token-threshold paths (preflight + post-response should_compress gate) already honoured the setting, but the three provider-overflow recovery paths in the agent loop — long-context-tier 429, 413 payload-too-large, and context-overflow — called _compress_context() unconditionally, silently compressing and rotating the session against the user's explicit choice. Add a single guard at the top of the overflow-recovery dispatch: when compression is disabled and the error is one of those three overflow classes, surface a terminal error (compaction_disabled: True) telling the user to /compress manually, /new, switch to a larger-context model, or reduce attachments. Manual /compress (force=True) is unaffected — it never enters this loop. Tests: new TestOverflowWithCompactionDisabled (413 + 400 overflow don't compress when disabled; control case still compresses when enabled). Existing overflow-recovery tests updated to enable compaction explicitly (they verify the recovery fires); fixture defaults flipped to True to match production (compression.enabled defaults to True). * fix(completion): remove /model <arg> autocomplete from CLI/TUI The TUI frontend already suppressed /model argument completion in favor of the two-step ModelPicker (useCompletion.ts), but the CLI prompt_toolkit completer and the gateway-backed complete.slash RPC (TUI + desktop) still emitted model aliases and probed LM Studio on every keystroke. Drops the /model branch in SlashCommandCompleter.get_completions, the _model_completions method, and the LM Studio probe/cache helper that only fed it. Command-name completion (/mod -> model) and sibling arg completers (/skin, /personality) are untouched. Removes the now-dead TestModelTabCompletion tests.
* docs: remove --include-desktop install instructions Drop the --include-desktop curl one-liner from the desktop app docs. The flag remains in scripts/install.sh; these docs now point to the desktop installer / website and the 'hermes desktop' path instead. * docs: remove --include-desktop from install docs Drop the redundant 'Hermes Desktop installer on Linux' block (which used --include-desktop) from quickstart, installation, and index docs. The website installer covers macOS/Windows desktop; the CLI-only path covers Linux. Removes the flag from all user-facing docs.
…sResearch#39847) * fix: respect disabled auto-compaction on context overflow Port from anomalyco/opencode#30749. When compression.enabled is false, NO automatic compaction trigger may fire. The proactive token-threshold paths (preflight + post-response should_compress gate) already honoured the setting, but the three provider-overflow recovery paths in the agent loop — long-context-tier 429, 413 payload-too-large, and context-overflow — called _compress_context() unconditionally, silently compressing and rotating the session against the user's explicit choice. Add a single guard at the top of the overflow-recovery dispatch: when compression is disabled and the error is one of those three overflow classes, surface a terminal error (compaction_disabled: True) telling the user to /compress manually, /new, switch to a larger-context model, or reduce attachments. Manual /compress (force=True) is unaffected — it never enters this loop. Tests: new TestOverflowWithCompactionDisabled (413 + 400 overflow don't compress when disabled; control case still compresses when enabled). Existing overflow-recovery tests updated to enable compaction explicitly (they verify the recovery fires); fixture defaults flipped to True to match production (compression.enabled defaults to True). * perf(/model): prewarm picker provider-models cache in background The no-args /model picker calls list_authenticated_providers(), which fetches each authenticated provider's live /v1/models list serially. On a cold or stale (>1h TTL) cache that blocks ~1.5s on the user's critical path the first time /model is opened in a session. Warm that exact path off-thread during the idle window right after the CLI banner is shown: a once-per-process daemon thread runs list_authenticated_providers() to populate provider_models_cache.json for every authed provider. By the time the user types /model, the picker hits the warm disk cache (~136ms vs ~1500ms). Process-level Event guard (mirrors run_agent's _openrouter_prewarm_done) ensures at most one thread per process; fully exception-isolated so an offline/no-creds provider can never affect the session.
… backend Per-profile remote hosts (NousResearch#39778) wired the chat/resume socket to a profile's remote backend, but session list + transcript reads still assumed every profile's state.db is a local file the primary can open. For a remote profile the local file is absent or stale, so the IDs the sidebar shows 404 the moment resume runs against the remote -- the "session not found -> new session" bug. Intercept the three session-read GETs in the hermes:api handler and route them to the owning remote backend (which serves its own state.db natively): GET /api/profiles/sessions -> splice each remote profile's real rows in GET /api/sessions/{id}[/messages] -> read from the remote for remote profiles No remote profiles configured -> untouched local fast path. A dead remote contributes nothing rather than breaking the sidebar. Verified end-to-end against a live remote backend: a remote-profile session resumes from remote history and continues on the remote across turns (history grows in place, no new session spawned).
…st pagination
Follow-up to the read-routing fix: make remote-profile sessions fully
first-class, not just resumable.
Mutations (rename/archive/delete) went through the same hermes:api handler but
never carried the owning profile, so they hit the local primary's state.db --
which has no row for a remote session. Deleting/archiving/renaming a remote
session silently no-op'd or 404'd, and the row reappeared on next refresh.
- hermes.ts: setSessionArchived/deleteSession/renameSession take the owning
profile and pass it as request.profile so Electron routes to that profile's
backend (matching the read path). Callers now forward session.profile.
- main.cjs: generalize the intercept (read -> request) to also reroute
DELETE/PATCH on /api/sessions/{id} for remote profiles, stripping the profile
param (the remote serves its own state.db; no cross-profile semantics there).
- web_server.py: DELETE /api/sessions/{id} gains a profile param for parity with
GET/PATCH (local cross-profile delete).
Also fix the unified-list merge: it concatenated each remote's page onto the
primary's without re-windowing, so a limit=N request could return up to
N*(1+remotes) rows and report the primary's (stale) total. Now it over-fetches
limit+offset from each remote (from offset 0), re-sorts by recency, re-windows
to the page, and recomputes total/profile_totals from the remote counts.
Verified live against a remote backend: rename/archive/delete mutate the remote
db; page 1 windows to limit, profile_totals reflect remote counts, page 2 has no
overlap with page 1. tsc -b clean; connection-config tests pass.
… rename/archive/delete) (NousResearch#39894) * fix(desktop): route remote-profile session reads to the owning remote backend Per-profile remote hosts (NousResearch#39778) wired the chat/resume socket to a profile's remote backend, but session list + transcript reads still assumed every profile's state.db is a local file the primary can open. For a remote profile the local file is absent or stale, so the IDs the sidebar shows 404 the moment resume runs against the remote -- the "session not found -> new session" bug. Intercept the three session-read GETs in the hermes:api handler and route them to the owning remote backend (which serves its own state.db natively): GET /api/profiles/sessions -> splice each remote profile's real rows in GET /api/sessions/{id}[/messages] -> read from the remote for remote profiles No remote profiles configured -> untouched local fast path. A dead remote contributes nothing rather than breaking the sidebar. Verified end-to-end against a live remote backend: a remote-profile session resumes from remote history and continues on the remote across turns (history grows in place, no new session spawned). * fix(desktop): route remote-profile session mutations + fix unified-list pagination Follow-up to the read-routing fix: make remote-profile sessions fully first-class, not just resumable. Mutations (rename/archive/delete) went through the same hermes:api handler but never carried the owning profile, so they hit the local primary's state.db -- which has no row for a remote session. Deleting/archiving/renaming a remote session silently no-op'd or 404'd, and the row reappeared on next refresh. - hermes.ts: setSessionArchived/deleteSession/renameSession take the owning profile and pass it as request.profile so Electron routes to that profile's backend (matching the read path). Callers now forward session.profile. - main.cjs: generalize the intercept (read -> request) to also reroute DELETE/PATCH on /api/sessions/{id} for remote profiles, stripping the profile param (the remote serves its own state.db; no cross-profile semantics there). - web_server.py: DELETE /api/sessions/{id} gains a profile param for parity with GET/PATCH (local cross-profile delete). Also fix the unified-list merge: it concatenated each remote's page onto the primary's without re-windowing, so a limit=N request could return up to N*(1+remotes) rows and report the primary's (stale) total. Now it over-fetches limit+offset from each remote (from offset 0), re-sorts by recency, re-windows to the page, and recomputes total/profile_totals from the remote counts. Verified live against a remote backend: rename/archive/delete mutate the remote db; page 1 windows to limit, profile_totals reflect remote counts, page 2 has no overlap with page 1. tsc -b clean; connection-config tests pass.
max_tokens set under model: in config.yaml was silently ignored. The value was never read from config, never passed through _resolve_runtime_agent_kwargs(), _resolve_turn_agent_config(), or the session override path. Added it to all three code paths so custom/Ollama endpoints receive the correct output cap. Closes NousResearch#20741
…r override Previous commit only covered the gateway runtime path. This adds: - CLI __init__: read max_tokens from model config with HERMES_MAX_TOKENS env override - CLI AIAgent() calls (interactive + background): pass max_tokens - Gateway _resolve_runtime_agent_kwargs: add HERMES_MAX_TOKENS env override All three code paths (CLI, gateway runtime, session override) now consistently propagate max_tokens to AIAgent.
Widens ViewWay's NousResearch#20741 fix to the sibling config surface: a custom_providers entry can pin its own output cap via max_output_tokens (or max_tokens). _get_named_custom_provider now lifts it onto the resolved runtime at all three return sites, and the gateway uses it as a fallback only when the documented global model.max_tokens isn't set, so the global key always wins. Precedence: HERMES_MAX_TOKENS > model.max_tokens > provider max_output_tokens > None. Closes the same NousResearch#20741 truncation for users who configure the cap per-provider rather than globally. Picks up the intent of NousResearch#19782 (alexcam1901), reimplemented to feed ViewWay's max_tokens pipeline.
…te dashboard (NousResearch#39921) * fix(desktop): cross-profile session history in app-global remote mode NousResearch#39894 made remote-profile sessions first-class for PER-PROFILE remote overrides. But the common setup — Settings → Gateway → "All profiles" → Remote — writes app-GLOBAL remote mode (connection.json top-level mode:'remote', empty profiles map), which the intercept didn't recognize. Switching to a non-launch profile then 404'd every session read, so no history showed for it. In global remote mode a SINGLE backend serves every profile via ?profile= (it reads each profile's state.db off the remote host's own disk — verified: one dashboard returns /api/profiles and /api/profiles/sessions?profile=all across all profiles). The fix: when no per-profile override matches but global remote mode is active, route per-session reads/mutations to that one backend and KEEP the ?profile= param so it opens the right state.db (instead of bailing to the local path and dropping the profile scope). - new globalRemoteActive() — true for connection.json mode:'remote' or the HERMES_DESKTOP_REMOTE_URL env override. - per-session branch: per-profile override → route sans profile (own db); global mode → route to the single backend WITH ?profile= preserved. - unified list is unchanged in global mode: it already passes through to the one backend, which aggregates all profiles natively. Verified live against a one-dashboard / multi-profile remote (Austin's topology): cross-profile transcript reads load (was 404), rename/delete route to the right profile, unified list spans both profiles. Known limitation (architectural, not fixed here): LIVE chat as a non-launch profile still needs a per-profile dashboard on the remote — the dashboard binds HERMES_HOME once at process start, so one global backend can't run an agent turn as another profile. Session history/read/mutate now work regardless. * fix(gateway): resume + chat any profile over one global-remote dashboard The REST half of this branch made cross-profile session history visible in app-global remote mode, but resume + chat still went over the WebSocket gateway, which was hard-bound to the dashboard's launch profile. Resuming a non-launch profile's session 404'd ("session not found") and sending spawned a new session — because session.resume/prompt.submit had no profile concept and the live agent + state.db were process-global to the launch profile's HERMES_HOME. Make the WS gateway per-session profile-aware so ONE dashboard can serve every local profile on its host (the app-global remote topology): - session.resume accepts an optional `profile`. _profile_home() resolves that profile's home on this host; resume opens THAT profile's state.db, binds its HERMES_HOME (ContextVar override) while building the agent so config/skills/ model resolve to it, and passes the profile db to the agent so turns persist to the right state.db. The owning profile_home is stored on the session. - prompt.submit re-binds the stored profile_home for the turn thread (mid-turn home reads — memory, skills — resolve to the resumed profile), reset in finally. - _make_agent gains an optional session_db param (defaults to _get_db()). - _load_cfg honors the home override (falls back to _hermes_home) so a resumed profile loads its own config; cache keyed on resolved path. - desktop: session.resume now sends the owning profile. Omitted/launch profile → unchanged (single-profile and per-profile-remote setups are byte-for-byte the same path). Verified live against a one-dashboard / multi-profile remote: resuming a non-launch profile's session loads its history, runs a real turn against THAT profile's home/env, and persists to its state.db. tests/tui_gateway/test_protocol.py: _make_agent mocks updated for the new param.
Introduce a lightweight React context-based i18n layer for the desktop app and translate the UI into Simplified Chinese. - New apps/desktop/src/i18n module: typed Translations interface, en + zh locale tables, I18nProvider/useI18n, localStorage-persisted locale (defaults to English), and language endonym metadata for the picker. - Wire I18nProvider at the app root in main.tsx. - Refactor 24 desktop screens/components to read strings from the `t` object instead of hard-coded English. - Add a unit test for the i18n context.
…usResearch#39993) Follow-up to NousResearch#39921. That PR scoped session.resume + prompt.submit to a session's profile, but a BRAND-NEW chat (session.create) under a non-launch profile was still built and persisted against the dashboard's launch profile. Two visible symptoms in app-global remote mode (one dashboard, many profiles): 1. "who are you" in profile S replied as the launch (default) profile/agent — the agent was built with the launch HERMES_HOME, so config/SOUL/identity came from the wrong profile. 2. "session not found" on later resume — _ensure_session_db_row persisted the row into the launch profile's state.db via _get_db(), so the session lived in the wrong db, the unified list mis-tagged it (it showed up under BOTH profiles), and resume routed to the wrong one. Fix — carry the owning profile through the create path too: - session.create accepts an optional `profile`; resolves its home and stores `profile_home` on the session (alongside what resume already set). - _start_agent_build binds that profile's HERMES_HOME while building the agent (config/skills/model/identity resolve to it) and hands the agent the profile's state.db so turns persist there. - _ensure_session_db_row writes the row into the profile's state.db, not the launch db — fixing the duplicate row + mis-tag + resume 404. - desktop sends the new-chat profile on session.create. None/launch profile → unchanged (single-profile and per-profile-remote setups take the same path). Verified live against a one-dashboard / multi-profile remote: a new chat under `work` builds as work's agent (correct SOUL identity), persists ONLY to work's state.db (launch db stays empty), the unified list tags it `work` exactly once, and it resumes cleanly. tests/test_tui_gateway_server.py: _make_agent mocks updated for the session_db param added in NousResearch#39921's build path.
Section 3 (user `providers:`) already honors `discover_models: false` to
skip live /models discovery and keep the explicit `models:` list. Section 4
(`custom_providers:` list) did not — `should_probe` ignored the field, so any
grouped custom provider with an api_key always had its configured subset
replaced by the full live /models catalog.
This adds the same `discover_models` support to section 4:
- Default True — no behaviour change for existing configs.
- `discover_models: false` keeps the explicit `models:` list even when an
api_key is present.
- String values ("false"/"no"/"0") are normalised to False, matching
section 3.
- If any entry in a grouped endpoint opts out, the whole group opts out.
Use case: endpoints that expose a full aggregator catalog via /models but
only serve a configured subset.
Salvaged from NousResearch#29810 — rebased onto current main. The PR's other change
(`key_env` resolution in section 4) landed independently in commit aa283d1
(custom provider picker credential isolation), so only the discover_models
portion is carried here.
Co-authored-by: ohMyJason <42903577+ohMyJason@users.noreply.github.com>
…tom flow The terminal `hermes model` wizard (_model_flow_named_custom) always live-probed a custom provider's /models endpoint, ignoring the configured `models:` list. For plans whose endpoint exposes a large catalog (e.g. Baidu Qianfan Coding Plan returns 100+ models for a 2-3 model plan) the picker flooded with models the user can't use. This wires `discover_models` (and the `models:` list) through _named_custom_provider_map into the flow and honors `discover_models: false` the same way the slash-command picker (model_switch.py sections 3 & 4) does: - Default stays True — live probe, no behaviour change. - discover_models: false → use the configured `models:` list verbatim, skip the probe (string 'false'/'no'/'0' normalised to False). - If the probe is on but returns empty, fall back to the configured list instead of forcing manual entry. Closes NousResearch#18726
…over-models-section4-29810 feat(model_switch): honor discover_models in custom_providers section 4 (salvage NousResearch#29810)
…ustom-discover-models-18726 feat(model): honor discover_models in terminal hermes model named-custom flow (closes NousResearch#18726)
…-error-on-session-resume fix(cli): use Rich [dim] tag instead of ANSI escape in session resume messages
… partial matches (NousResearch#39858) The error guard in _search_with_rg/_search_with_grep was unreachable and, if it had fired, would have discarded valid results. Two root causes: 1. Unreachable. Both methods pipe the search through `| head` with no pipefail, so the pipeline reported head's exit code (0), masking rg/grep's error code (2). The guard never fired. Worse, because _exec merges stderr into stdout (stderr=subprocess.STDOUT), the error text was then parsed as bogus match lines instead of being surfaced — the user got garbage matches with no indication the search failed. 2. Latent results-dropping. The original `not result.stdout.strip()` check was always False on error (error text lives in stdout), and the `hasattr(result, 'stderr')` branch was dead code (ExecuteResult has no stderr field). A naive broadening to `exit_code == 2` would have nuked real matches whenever rg/grep also hit a non-fatal error (e.g. one unreadable file in a tree that otherwise matched), which both tools signal with exit 2. Fix: - Prefix the piped command with `set -o pipefail` so rg/grep's real exit status propagates. rg exits 0 on a truncating head; grep exits 141 (SIGPIPE), so the strict `== 2` guard ignores truncated-success. - Add _split_tool_diagnostics() to separate tool diagnostics from match output by tool prefix and output shape. Diagnostics never become matches; on a hard error they are the message to surface. - Only surface an error when exit==2 AND no usable match payload remains, so partial errors keep their real matches. Tests: tests/tools/test_search_error_guard.py drives both methods through the real local backend (hard error surfaced, partial error keeps matches, truncation no false error, files_only/count exclude diagnostics) plus unit coverage for the splitter. Supersedes NousResearch#39710.
The Surface Release — native desktop app, browser admin panel, remote-gateway connect, Simplified Chinese desktop UI, leaner default skill set, NVIDIA/skills trusted tap, fuzzy model picker, /undo. 874 commits · 542 PRs · 170 contributors · 399 issues closed.
…10) PR #10 ('Merge upstream v2026.5.29 into main') was squash-merged, so the upstream commit range up to v2026.5.29 is present in the tree but absent from main's ancestry. This synthetic -s ours merge restores the ancestry link (two parents, tree identical to prior main) so subsequent upstream merges diff only the genuine post-v2026.5.29 delta instead of re-applying it.
Merges NousResearch/hermes-agent v2026.6.5 (v0.16.0) into the AmbulnzLLC fork. Preceded by a synthetic '-s ours' graft of v2026.5.29 (commit 9e66724) that restores the upstream ancestry flattened when PR #10 was squash-merged. That graft dropped the conflict count from 246 to 8 by moving the merge-base up to v2026.5.29, so this merge only reconciles the genuine v2026.5.29..v2026.6.5 delta. Conflict resolutions (8 files): - Dockerfile: drop audio (build-essential/libportaudio2) and web/ui-tui dashboard installs (took upstream); uv extras = web+pty+hindsight (dropped voice). - pyproject.toml: version 0.16.0; add **/README.md package-data. - MANIFEST.in: add 'graft locales' (upstream). - acp_registry/agent.json, hermes_cli/__init__.py: version/date -> 0.16.0 / 2026.6.5. - tests/test_packaging_metadata.py: take upstream Starlette-CVE + locale tests. - docker/stage2-hook.sh: union -- keep fork GitHub-App-PEM + taps-seed hooks AND upstream gateway_state bootstrap. - gateway/stream_consumer.py: true merge -- keep fork _message_id-None split branch + pending_barrier clarify-card guard (PR #16), adopt upstream finalize/ is_turn_final refinements (NousResearch#29346).
🔎 Lint report:
|
| Rule | Count |
|---|---|
unresolved-import |
95 |
unresolved-attribute |
68 |
invalid-argument-type |
60 |
invalid-assignment |
36 |
unsupported-operator |
15 |
no-matching-overload |
7 |
not-subscriptable |
5 |
call-non-callable |
2 |
invalid-method-override |
1 |
unsupported-base |
1 |
unused-type-ignore-comment |
1 |
unresolved-reference |
1 |
deprecated |
1 |
invalid-return-type |
1 |
First entries
tests/tools/test_mcp_preflight_content_type.py:20: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/tools/test_managed_media_gateways.py:514: [unresolved-attribute] unresolved-attribute: Attribute `exec_module` is not defined on `None` in union `Loader | None`
tests/hermes_cli/test_aux_config.py:37: [unsupported-operator] unsupported-operator: Operator `in` is not supported between objects of type `Literal["title_generation"]` and `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 30 union elements`
tests/gateway/test_stream_consumer_fresh_final.py:335: [invalid-argument-type] invalid-argument-type: Argument to bound method `GatewayStreamConsumer.on_delta` is incorrect: Expected `str`, found `None`
tests/hermes_cli/test_dashboard_auth_password_login.py:393: [unresolved-attribute] unresolved-attribute: Attribute `status_code` is not defined on `None` in union `None | Unknown`
tests/tools/test_web_providers.py:384: [invalid-method-override] invalid-method-override: Invalid override of method `extract`: Definition is incompatible with `WebSearchProvider.extract`
tests/plugins/test_kanban_attachments.py:19: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/hermes_cli/test_aux_config.py:47: [unsupported-operator] unsupported-operator: Operator `not in` is not supported between objects of type `Literal["session_search"]` and `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 30 union elements`
cli.py:2264: [invalid-argument-type] invalid-argument-type: Argument to bound method `dict.get` is incorrect: Expected `Never`, found `Literal["type"]`
tests/gateway/test_feishu_meeting_invite.py:108: [unresolved-attribute] unresolved-attribute: Attribute `user_id` is not defined on `None` in union `MeetingInviteUser | None`
tests/gateway/test_per_platform_streaming_defaults.py:16: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `Overload[(i: SupportsIndex, /) -> Unknown, (s: slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> list[Unknown]]` cannot be called with key of type `Literal["platforms"]` on object of type `list[Unknown]`
tests/hermes_cli/test_tools_config.py:745: [invalid-argument-type] invalid-argument-type: Argument to function `_detect_active_provider_index` is incorrect: Expected `list[Unknown]`, found `str | list[dict[str, str | list[Unknown]] | dict[str, str | list[Unknown] | bool | list[str]] | dict[str, str | list[dict[str, str]]]] | list[dict[str, str | list[Unknown] | bool | list[str]] | dict[str, str | list[dict[str, str]]]] | ... omitted 4 union elements`
optional-skills/creative/pixel-art/scripts/pixel_art.py:21: [unresolved-import] unresolved-import: Cannot resolve imported module `palettes`
tests/tools/test_mcp_preflight_content_type.py:212: [unresolved-import] unresolved-import: Cannot resolve imported module `httpx`
hermes_cli/config.py:4602: [unresolved-attribute] unresolved-attribute: Attribute `get` is not defined on `str`, `list[Unknown]`, `list[str]`, `int` in union `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 30 union elements`
hermes_cli/tools_config.py:2875: [unresolved-attribute] unresolved-attribute: Attribute `get` is not defined on `str` in union `str | dict[str, str | list[Unknown]] | dict[str, str | list[Unknown] | bool | list[str]] | dict[str, str | list[dict[str, str]]]`
tests/gateway/test_slack.py:524: [unresolved-attribute] unresolved-attribute: Attribute `done` is not defined on `None` in union `Task[Unknown] | None`
tests/plugins/test_plugin_dashboard_auth_contract.py:28: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/tools/test_docker_environment.py:638: [invalid-argument-type] invalid-argument-type: Argument to function `_sanitize_label_value` is incorrect: Expected `str`, found `None`
tests/test_packaging_metadata.py:4: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/gateway/test_per_platform_streaming_defaults.py:18: [invalid-argument-type] invalid-argument-type: Method `__getitem__` of type `Overload[(i: SupportsIndex, /) -> str, (s: slice[SupportsIndex | None, SupportsIndex | None, SupportsIndex | None], /) -> list[str]]` cannot be called with key of type `Literal["streaming"]` on object of type `list[str]`
tests/gateway/test_gateway_shutdown.py:147: [unresolved-attribute] unresolved-attribute: Object of type `bound method GatewayRunner._launch_systemd_restart_shortcut() -> None` has no attribute `assert_called_once_with`
tests/gateway/test_config_driven_access_policy.py:100: [invalid-argument-type] invalid-argument-type: Argument to function `BasePlatformAdapter.enforces_own_access_policy` is incorrect: Expected `BasePlatformAdapter`, found `object`
hermes_cli/main.py:10208: [no-matching-overload] no-matching-overload: No overload of function `run` matches arguments
tests/acp/test_session_db_private_access.py:16: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
... and 269 more
✅ Fixed issues (243):
| Rule | Count |
|---|---|
unresolved-import |
177 |
invalid-argument-type |
18 |
unsupported-operator |
15 |
unresolved-attribute |
14 |
invalid-assignment |
10 |
unresolved-reference |
2 |
invalid-return-type |
2 |
invalid-parameter-default |
2 |
call-non-callable |
1 |
no-matching-overload |
1 |
not-subscriptable |
1 |
First entries
tests/run_agent/test_1630_context_overflow_loop.py:11: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/agent/test_anthropic_mcp_prefix_strip.py:15: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/gateway/test_kanban_notifier.py:4: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/agent/test_title_generator.py:6: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/hermes_cli/test_mcp_reload_confirm_gate.py:34: [unresolved-attribute] unresolved-attribute: Attribute `get` is not defined on `str`, `list[Unknown]`, `list[str]`, `int` in union `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 28 union elements`
tests/hermes_cli/test_kanban_core_functionality.py:3376: [unresolved-attribute] unresolved-attribute: Attribute `get` is not defined on `str`, `list[Unknown]`, `list[str]`, `int` in union `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 28 union elements`
tests/tools/test_skill_provenance.py:5: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/hermes_cli/test_session_recap.py:6: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tools/environments/base.py:528: [unresolved-attribute] unresolved-attribute: Attribute `fileno` is not defined on `None` in union `IO[str] | None`
tests/tools/test_config_null_guard.py:9: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/honcho_plugin/test_async_memory.py:19: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/run_agent/test_image_shrink_recovery.py:23: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
skills/creative/pixel-art/scripts/pixel_art.py:16: [unresolved-import] unresolved-import: Cannot resolve imported module `PIL`
tools/file_operations.py:1938: [unresolved-attribute] unresolved-attribute: Object of type `~AlwaysFalsy` has no attribute `strip`
tests/hermes_cli/test_setup_irc.py:9: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/tools/test_web_providers.py:219: [unsupported-operator] unsupported-operator: Operator `in` is not supported between objects of type `Literal["extract_backend"]` and `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 28 union elements`
tests/honcho_plugin/test_pin_peer_name.py:21: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/run_agent/test_streaming_tool_call_repair.py:15: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/tools/test_resolve_path.py:7: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/tools/test_browser_camofox.py:7: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/acp/test_approval_isolation.py:20: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/tools/test_video_analyze.py:10: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/hermes_cli/test_tools_config.py:1241: [invalid-argument-type] invalid-argument-type: Argument to function `_reconfigure_provider` is incorrect: Expected `dict[Unknown, Unknown]`, found `str | dict[str, str | list[Unknown] | bool | list[str]] | dict[str, str | list[Unknown]] | dict[str, str | list[dict[str, str]]] | Unknown`
tests/cli/test_resume_display.py:717: [unsupported-operator] unsupported-operator: Operator `in` is not supported between objects of type `Literal["resume_display"]` and `str | dict[Unknown, Unknown] | list[Unknown] | ... omitted 28 union elements`
tests/agent/test_auxiliary_main_first.py:18: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
... and 218 more
Unchanged: 4872 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
Local test resultsRan the canonical runner (
Coverage rationale: The full ~17k-test suite was not run locally; CI covers the remainder. |
Wiz Scan Summary
|
| Scanner | Findings |
|---|---|
| 2 |
|
| 1 |
|
| - | |
| - | |
| 45 |
|
| - | |
| Total | 2 |
To detect these findings earlier in the dev lifecycle, try using Wiz Code VS Code Extension.
There was a problem hiding this comment.
PII
More Details
| Attribute | Value |
|---|---|
| Data Classifier | PII/Name |
| Data Classifier ID | BUILTIN-125 |
Sampled Examples
| Key | Value |
|---|---|
| author | Al** ***n |
| author | Al** ***n |
| author | De*** C****g |
| author | Gio*g |
| author | Ju***n *****e |
Rule ID: BUILTIN-125
What
Merges upstream NousResearch/hermes-agent v2026.6.5 (v0.16.0) into the AmbulnzLLC fork's
main.Why this was a "shitshow" and how it's fixed
PR #10 ("Merge upstream v2026.5.29 into main") was squash-merged, which flattened the upstream history — the v2026.5.29 code was present in the tree but not in
main's ancestry. As a result git re-diffed a month of already-applied upstream work, producing 246 conflicts.This PR is preceded by a synthetic
git merge -s oursgraft of v2026.5.29 (commit9e66724) that records v2026.5.29 as an already-applied ancestor (tree unchanged, ancestry restored). That moved the merge-base up to v2026.5.29, dropping the conflict count from 246 → 8 so this merge only reconciles the genuinev2026.5.29..v2026.6.5delta.The merge commit has two parents (graft + v2026.6.5), so the flattening problem does not recur for future upstream syncs.
Conflict resolutions (8 files)
build-essential/libportaudio2) and theweb/ui-tuidashboard installs (took upstream);uvextras =web pty hindsight(droppedvoice).0.16.0; added**/README.mdpackage-data.graft locales(upstream).0.16.0/2026.6.5.gateway_statebootstrap._message_id is Nonesplit branch +pending_barrierclarify-card guard (PR fix(teams): order clarify card after its preamble text via bounded stream-drain barrier #16), and adopted upstreamfinalize/is_turn_finalrefinements (Discord: tool-using responses (api_calls≥2) silently dropped — noSending responselog afterresponse readyNousResearch/hermes-agent#29346).Test status
Local test run kicked off after PR creation — results posted as a comment below.
Review notes
gateway/stream_consumer.pywas a non-trivial hand-reconciled merge — worth a focused look.