e860b8e4e4e232a503cee28c715cbedbfb1de0f0
40 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5b49051276 | fix(desktop): keep profile switches on the selected gateway | ||
|
|
f6306d1920 |
feat(contracts): TypeScript consumes the generated contract; hand-typed wire shapes deleted
apps/shared/src/gateway-events.ts is now a thin layer over gateway-contract.generated.ts (client-local synthetic events + the GatewayEvent envelope); gateway-events.json, its two rendezvous tests and the duplicated BillingBlock / SessionInfo / ProjectInfo hand copies are gone. Desktop, TUI, web and shared typecheck against the generated RpcMethods / ServerRequestMap / BackendGatewayEventMap. What tsc found once the types were honest: three phantom fields the backend never sent (tool.start.todos, error.reason, voice.transcript.voice_stopped) - the TUI todo tests were driving the list through the phantom and are retargeted to tool.complete, where the wire actually carries it; nullable fields (`None` on the wire) were typed as plain optionals in eight places and now coerce at the boundary; SessionResumeResult had a stale generic. Contract fixes from the consumer pass: TranscriptMessage is the gateway projection (text/row_id/context/args), not the stored row; SkinPayload matches HermesSkin (empty-string defaults, never null); SessionLiveInfo model/tools/skills are required (always emitted); BillingBlock.billing_url is required-nullable (dataclass asdict). tui_gateway/AGENTS.md documents the declare -> regenerate -> tsc loop. |
||
|
|
61304d5923 |
fmt(js): npm run fix on merge (#110132)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
bab5cece78 |
refactor(ts): one reconnect backoff in apps/shared; web events feed rides the shared client and survives reconnects
Four backoff formulas (ui-tui 1000/30s, desktop 300/15s jittered, web events
1000/30s, web PTY inline 250/3s cap 5 — untested) collapse into
apps/shared/src/reconnect-backoff.ts::reconnectBackoffDelayMs(attempt,
{baseDelayMs, capMs, jitter}). Every caller keeps its own parameters
(table in the PR body); the PTY ladder gains a test.
web/src/components/ChatSidebar.tsx hand-rolled a third WebSocket frame
dispatcher (`new WebSocket` + JSON.parse + `frame.method === "event"` switch
+ a private RpcEnvelope re-declaring shared JsonRpcFrame) for /api/events.
That socket now goes through EventsFeedClient, a notification-only subclass
of the shared JsonRpcGatewayClient (replay off, heartbeat off, connect
timeout covering ticket minting); the effect keeps only the retry ladder and
the banner. Both sidebar clients are now created once per component instead
of per `version` bump, so the shared client's seq watermarks survive a drop
and its `session.events.since` gap replay can actually fire for web
(previously the client was rebuilt on every reconnect and replay never ran).
Behavior change: web sidecar reconnects reuse the same JsonRpcGatewayClient
(gap replay now runs); the events feed's handshake `error`+`close` pair is one
`closed` transition (one retry timer, as before); no parameter of any
backoff ladder changed.
|
||
|
|
36773e0d78 |
refactor(ts): one GatewayEventMap in apps/shared typed from tui_gateway emitters; drop never-emitted tool.progress
Three TypeScript clients each declared their own copy of the tui_gateway wire
types and had drifted apart: apps/shared had a partial GatewayEventName union
with a `(string & {})` escape hatch, ui-tui/gatewayTypes.ts a 150-line
discriminated union, and apps/desktop an `RpcEvent<T>` that was field-for-field
the shared GatewayEvent with `type: string`. None matched the emitter:
message.complete lacked warning/status/error/recoverable/error_surface,
tool.start/tool.complete lacked args/result, SessionResumeResponse lacked
session_key/messages_omitted/hydrating/auto_continue/todo_state, three
different ModelOptionProvider shapes disagreed on fields, and all three unions
handled a `tool.progress` event that no Python emitter has ever produced.
Now:
* `apps/shared/src/gateway-events.ts` is the single home: payload interfaces
typed from the Python emitters (file::symbol cited per interface),
`BackendGatewayEventMap` (89 backend names) + `ClientLocalGatewayEventMap`
(5 TUI-synthetic transport events, clearly marked, excluded from the
contract) merged into `GatewayEventMap`; `GatewayEvent<K>` is discriminated
on `type` with `seq` typed. RPC shapes shared by 2+ surfaces live beside it
(ModelOptionProvider = union of every field hermes_cli/inventory.py sets,
incl. pricing_pending/free_tier_pending; SessionResumeResponse<Info>;
SessionListItem with resolved_id; Usage).
* `JsonRpcGatewayClient.on<K>` is keyed by event name; the gateway.ready
heartbeat/replay_epoch and per-frame `seq` reads are typed instead of cast.
* ui-tui and apps/desktop import the shared names; their local duplicates are
deleted (no re-export shims — importers are repointed; the desktop plugin
SDK barrel keeps its public `RpcEvent` name as an alias of GatewayEvent).
web/src repoints ModelOptionProvider/ModelOptionsResponse.
* `tool.progress` handling is removed from the TUI handler/turnController,
desktop event sets/tools handler, shared union, tests, and two docs
(`grep '"tool.progress"' tui_gateway/` = 0 hits; the `display.tool_progress`
config mode is unrelated and untouched).
* `message.complete.warning` (history-commit note from
prompt_turn.py::_complete_turn_payload) is typed and surfaced on both
surfaces through their existing notice paths (TUI pushActivity 'warn',
desktop notify kind 'warning').
Contract: `apps/shared/src/gateway-events.json` is the sorted list of
backend-emitted names. `tests/tui_gateway/test_gateway_event_contract.py`
collects names from the Python emitter side (emit-helper literals, the
`.request → .expire` table, change-watcher table, child delta mirror,
subagent relay, desktop_ui tool emitters, gateway.ready/setup.ready/
browser-controller frames) and asserts emitted == JSON in both directions.
`apps/shared/src/gateway-events.test.ts` asserts BACKEND_EVENT_NAMES (which
the map type is `satisfies`-checked against) == JSON. Sabotage-verified: a
fake JSON name fails both tests; a fake TS name fails tsc + vitest; a fake
Python `_emit("...")` fails pytest.
|
||
|
|
d1dbb0ac9e |
feat(gateway): multiplexer hot-serves profiles created while it runs, unroutes deleted ones
A `gateway.multiplex_profiles` gateway enumerated `profiles/` once at boot, so a profile created afterwards (CLI, dashboard, Desktop, TUI) was never served until `hermes gateway restart`; Desktop and the dashboard gave no reminder, so a new profile's bot simply never connected. The served set is now reconciled at runtime (`gateway/run_profile_reconcile.py`): - `hermes_cli/profiles.py` create/delete ping the multiplexer over its control socket (new `rescan-profiles` verb); a supervised watcher rescans every 30s as the safety net. - A new profile gets its adapters under its own runtime scope from its config/.env (`_start_one_profile_adapters`, same duplicate-credential guard as boot, now seeded with the LIVE secondaries' claims), `served_profiles` in gateway_state.json is updated, MCP discovery + log routing run for it. Other profiles' adapters are never touched. - A served profile whose config.yaml/.env changed is re-scanned so a token added after create builds the adapter; already-live/queued platforms are skipped (no second poller). - A deleted profile (tombstone) has its reconnects cancelled, adapters torn down, pairing/busy bookkeeping and cached agents dropped, and this process's SQLite / memory-store handles released so the deleter's rmtree succeeds. - The in-process cron ticker takes a live enumerator so new profiles' jobs fire. - PUT /api/messaging/platforms/<id>?profile=X returns `hot_served` when a live multiplexer rebuilt X's adapters; Desktop/dashboard skip the restart banner then. - `hermes profile create` confirms hot-serve; the restart reminder stays for a gateway that did not pick the profile up (older build / signal failed). |
||
|
|
0dcadf6f41 |
revert: remove Collective Wisdom V1 (#94266)
Reverts the in-tree org skill-marketplace: hermes_wisdom package, three model tools, CLI/gateway/desktop/dashboard/Telegram/Slack surfaces. Later non-Wisdom work on shared files (guest onboarding i18n, dashboard startup schema, Slack adapter, tui_gateway) is kept; Wisdom-only call sites and config were stripped from those files. |
||
|
|
8c74118c4a |
fmt(js): npm run fix on merge (#108127)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
0e927c914d |
Guided first launch behind HERMES_GUEST_ONBOARDING: intro, guided chat, first task in default (NS-848, PR B1) (#107958)
* feat(desktop): port guided onboarding substrate Add seeded session creation, transcript directives, profile routing, and the shared window and pane primitives needed by the guided flow. Keep later-step mounts deferred and exclude provider selection and retry machinery. * refactor(desktop): anti-slop cleanup for substrate Assemble seed parameters in the existing create helper and use the owning transcript attribute type. Read the guaranteed gateway and connection contracts directly to remove runtime type probes and unchecked assertions. * test(desktop): create-overrides invariants Verify that reasoning and title overrides do not select a provider or model. Empty overrides and seeds add no parameters. * feat(desktop): port first-run cinematic window Play the cinematic behind the guest onboarding launch flag using bundled Collapse and JetBrains Mono. Give the native window its own controller and restore the app on skip, renderer deadman or native watchdog. Drop the perf scenario because it depends on the removed replay hook. Guided chat kickoff and app-shell gate wiring remain with their later steps. * refactor(desktop): anti-slop cleanup for cinematic Preserve audio and canvas behavior through named types and inferred results. Split the viewport node and frame drawing to keep control flow bounded. Cut comments that only repeat the code. * feat(desktop): add onboarding gate and answers stores Track cinematic, guided chat, handoff and completion in one phase record. Queue the guide after the intro and share pending kickoff work between callers. Keep existing saved answers while dropping retired preferences. Leave intro seen-state ownership with the cinematic store. * feat(desktop): port guided onboarding chat Add guided setup cards, runbooks, machine context, and onboarding presence. Connect transcript rendering and first-build progress to the desktop behind the onboarding flag. Leave session kickoff and handoff execution for the next step. * refactor(desktop): anti-slop cleanup for guided chat Keep directive and layout lookups typed. Remove unsafe test casts and isolate onboarding transcript calculations without changing the flow. * feat(desktop): connect guided onboarding to durable first-build handoff Start the guide only after its profile backend confirms bootstrap readiness. Seed or adopt the welcome chat, then transfer the first build to default with a durable receipt and explicit retry. Wire cinematic completion, screen stand-down, layout growth and progress check-ins. Save agreed preferences before creating the build and release prompt slots after storage refusal. * refactor(desktop): anti-slop cleanup for onboarding handoff Reuse the gateway request and error contracts. Isolate guide adoption and snapshot validation while preserving receipt recovery and reasoning overrides. Validate persisted receipt fields at the JSON boundary without coercion. Keep corrupt identities rejected and retain only the permitted test mocks. * fix(desktop): guided chat review fixes Wire the native machine probe so guided setup can suggest a name and offer the right first task. Restore the comments that explain the flow boundaries. The directive registration uses the launch flag to preserve ordinary chat. Ruling 6 folds active.ts into assembly to keep activity ownership together and removes the second greeting source so the seeded and visible greetings agree. * fix(desktop): handoff review fixes Probe the guide backend before switching profiles so a readiness refusal keeps classic onboarding on the current backend. Restore list-valued personalization coverage and routing rationale. Remove the obsolete setup status fixture. * chore(desktop): onboarding script cull and rehearsal recipe Document a temporary-state rehearsal using the existing onboarding flag and optional portal stand-in. Keep the main scripts unchanged and retain window growth for the guided chat. * fix(connectors): reject incomplete catalog responses * feat(gateway): scope connector controls to the owning session * feat(desktop): connect apps through native session-owned controls * feat(desktop): gate connector cards and enable free-tier access Use the launch flag before mounting connector controls so classic transcripts add no status requests. Allow existing free-tier identities through the read-only tool gateway gate and test the owning-profile RPC path with A’s launch gate. Keep authorization links out of previews. * style(desktop): format connector translations Apply Prettier to the connector copy blocks while preserving upstream translations and free-tier wording. * refactor(desktop): anti-slop cleanup for connector card Use the transcript JSON contract and concrete RPC parameters. Preserve malformed-value filtering at one string boundary and make the fixture and row types explicit. Keep connector execution and cancellation behavior unchanged. * feat(desktop): detect initial language from the OS Use the native machine locale when no supported language is saved. Preserve explicit choices and leave inferred languages out of config. * refactor(desktop): anti-slop cleanup for initial locale detection Keep unvalidated config values at the existing validation boundary. Pass no saved choice after that boundary has ruled it out, preserving locale precedence. * test(desktop): onboarding port test set Make native window tests reject duplicate IPC handlers and isolate disabled onboarding. Assert the active gate mock when onboarding re-enables. Keep the test set limited to behavior carried by the port. * fix(desktop): recover failed guide kickoff and reveal once The review found that a failed guide create stranded the solo shell and draft profile, and solo boot faded an already visible window a second time. Restore the prior route and layout, release onboarding through its existing phase record, and surface create failures. Let the film own the reveal while solo boot animates the visible resize. * fix(desktop): preserve transcript ownership across cards and handoff The review reproduced answers submitted to the focused chat, repeated questions disabled across sessions, handoff recovery using foreground identity, and mount-dependent progress history. Target each card’s own composer, scope settlement to its message and session, carry the issuing guide through handoff, and derive progress from its transcript with streaming activity. Reuse the existing owner ladder for exact and profile-only routes. * fix(gateway): preserve connector ownership with profile routing The review found that shared-primary profile metadata was rejected before connector dispatch, while desktop controls treated a missing registry id as missing ownership. Accept profile only as routing metadata and keep the live transport as authorization. Resolve card ownership through the existing exact/profile ladder, retaining ambient routing only for the single-backend case. * fix(desktop): resolve plugin roots and gate the Basic layout The review found that the first plugin build was seeded with a different installation’s fixed path, and the director ruled that flag-off layouts must match main. Resolve the running desktop’s plugin root before seeding a plugin build and register Basic only when onboarding is enabled. Keep the runbook wording and the ordinary four layout presets intact. * fix(desktop): clear review-fix slop findings The slop gate flagged an undocumented layout-data assertion and unknown-return types in the new test selectors. Record the layout registry invariant and preserve each selector’s return type. The only remaining production finding is the accepted connector-tools baseline. * fix(desktop): detect the OS language on a fresh install The review found that the merged English config default prevented the desktop from probing the OS language on a fresh install. Add an opt-in saved-values read so an absent choice remains distinct from saved English. Preserve default-valued English only for explicit language saves; unrelated settings saves must not turn a merged default into a language choice. Older backends ignore the new query options and keep returning merged English, preserving their existing desktop behavior. * test(desktop): make the flag-off layout registry test deterministic The flag-off test awaited the full controller import, pulling in the UI graph and installing application watchers just to read layout presets. That import took 9.5 seconds locally and timed out in the director's run. Move the existing trees and registration into a small layout-presets module. Production and the synchronous test use the same flag-gated registration, without starting the controller in the test. Keep the real registry invariant and dispose the test's contributions after completion. * fix(desktop): keep the transcript parser and ::ask behind the onboarding flag Register the guided chat's question card only with onboarding enabled. Restore main's whole-paragraph parser and contribution rendering when the flag is off, including its streaming prose behavior. Keep segmentation for the guided flow until B4 decides the parser's wider use. Restore main's two parser test files so its existing product and plugin contracts remain the flag-off check. * test: drop the onboarding and connector tests pending a later ticket Apply the director's ruling to remove B1's added test files and restore main's existing suites. Keep only the gateway route-reader mock contract that main's profile tests need against the shipped activation behavior; their cases and assertions stay intact. The flow's shape is not settled and B3/B4 rewrite it. The connector layer will also be reworked. The live CDP run is the flow check until a follow-up ticket brings tests back. --------- Co-authored-by: brooklyn! <brooklyn.bb.nicholson@gmail.com> |
||
|
|
1827a8584e |
refactor(desktop): name the in-memory tail wipe by what it clears
Polish after #107993: `clearAllTranscriptTails` sat next to the cache's `clearTranscriptTails` differing by one word that did not encode which store each empties; rename it `clearTranscriptTailPaging` and keep the WHY at the one call site instead of repeating it in the JSDoc. The gateway-switch test resets paging state in afterEach through the helper (covers the LRU order too) rather than an inline atom reset that a failing assertion would skip. Drop a duplicated "capture before await" sentence in getLatestSessionMessages. |
||
|
|
a6ee31f55a |
feat(wisdom): add Hermes Collective Wisdom Agent V1 (#94266)
* feat(wisdom): add trusted publish and install foundation
* feat(wisdom): add private contribution loop
* feat(wisdom): add managed consumption workflows
* fix(wisdom): close cross-repository safety gaps
* fix(wisdom): align local package and lifecycle policy
* fix(wisdom): require explicit profile setup
* docs(wisdom): repin reconciled gateway head
* fix(wisdom): fence content downloads and approval receipts
* docs(wisdom): record generation-fenced downloads
* docs(wisdom): record unified delivery PR
* fix(ci): stop passing invalid classifier inputs
* docs(wisdom): remove internal requirements ledger
* feat(wisdom): localize dashboard and desktop copy
* feat(wisdom): complete local contribution and consumption UX
* style(wisdom): satisfy desktop lint
* chore(wisdom): refresh requirements pin
* test(dashboard): allow formatted profile copy
* test(wisdom): stabilize desktop interaction coverage
* fix(wisdom): surface dashboard action failures
* fix(wisdom): add repeatable Portal demo login
* feat(wisdom): add actionable skill notifications
* feat(wisdom): add notification install and update actions
* fix(wisdom): make Telegram skill alerts actionable
* fix(wisdom): always refresh demo Agent login
* feat(wisdom): embed Telegram notification actions
* fix(wisdom): preserve Telegram notifications after actions
* fix(wisdom): keep Telegram notification cards readable
* feat(wisdom): add Telegram candidate approval flow
* feat(wisdom): explain Telegram qualification reasons
* fix(wisdom): reconcile cross-surface candidate actions
* feat(telegram): add Collective Wisdom management command
* chore(wisdom): refresh Gateway contract pin
* chore(wisdom): advance Gateway contract pin
* feat(wisdom): align command UX across clients
* feat(slack): add Collective Wisdom management parity
* feat(wisdom): add security and professionalism reviews
* feat(wisdom): add first-time qualification guidance
* feat(wisdom): simplify qualification sharing choices
* feat(skills): add optional editorial metadata
* feat(wisdom): enrich legacy skill presentation
* fix(wisdom): harden review and update boundaries
* fix(wisdom): emit canonical review timestamps
* fix(wisdom): align with merged gateway and main
* wisdom: add agent-led sharing core (policy, evidence, schemas, templates, delivery, weekly job, share/install flows)
- hermes_wisdom/agent_led/: policy resolution (server > local > defaults),
7-day evidence builder that excludes bundled/hub/managed skills and
dismissed/handled/recently-suggested content hashes, strict pydantic
schemas for agent output with repair-or-reject, fixed copy templates
(Share / Teammate / Published / Update / Mute), idempotent retried
delivery ledger with stale-action resolution, weekly review job,
resumable Share and Install flows.
- prompts/: candidate review, recipient recommendation, share packaging.
- tests/wisdom/test_agent_led.py: 30 tests.
* wisdom: agent-led renderers and button action dispatcher
- render.py: Telegram HTML, Slack blocks, Desktop payload; editorial name
is the emphasized line, product label stays separate.
- actions.py: resolve opaque wa:<action>:<dedup> targets via the delivery
ledger; Not now -> dismissal, Mute -> fixed options, Share -> resumable
packaging flow, Install/Update -> plan command. Never publishes/installs.
* wisdom: CLI verbs, agent_led config default, conversational catalog skill
- hermes wisdom browse/review-week/act/share/dismiss/mute (all --json).
- wisdom.agent_led config block, default enabled.
- SKILL.md rewritten so natural-language catalog questions map to the CLI
verbs, share/install flows and fixed notification templates.
* wisdom: wire agent-led weekly review into gateway tick and Telegram buttons
- gateway housekeeping tick calls maybe_run_weekly_review with a home
channel sender when a Telegram adapter is available.
- Telegram: wa: callbacks resolved through the ledger (stale-safe), mute
duration keyboard, send_wisdom_agent_recommendation rich card + fallback.
* fix(wisdom): integrate local mediation and harden model and setup boundaries
* fix(wisdom): honor authoritative recommendation policy and defer on failure
* fix(wisdom): synchronize opaque suppression and recheck delivery preferences
* feat(wisdom): route weekly selection through the session-owned assessment queue
* fix(wisdom): prepare and submit the reviewed generated share package
* feat(wisdom): separate native Share preparation from publication consent
* feat(wisdom): sync native mute choices through a leased preference outbox
* feat(wisdom): bind native mute controls to durable preference choices
* feat(wisdom): add scoped desktop and dashboard notification settings
* fix(wisdom): revalidate feed recommendations before assessment and delivery
* fix(wisdom): persist validated delivery receipts before completing notices
* feat(wisdom): add private notification claim and receipt client
* Persist Wisdom send reservations and recover delivery acknowledgements
* Route legacy Wisdom controls through current native review
* Add typed private Wisdom operation outcome client
* fix(wisdom): make agent-led advice usable in the local demo
* fix(wisdom): keep requested consent outside proactive limits
* fix(wisdom): distinguish unavailable assessments and preserve digest text
* fix(wisdom): assess ongoing usefulness beyond the current task
* fix(wisdom): restore immediate qualification sharing controls
* fix(wisdom): separate qualification review from installation advice
* fix(wisdom): collapse review checklists and simplify sharing copy
* fix(wisdom): show compact sharing progress and publication receipts
* fix(wisdom): require credential prefixes rather than matching skill names
* fix(wisdom): finish package checks before presenting sharing consent
* fix(wisdom): scan local skills before qualification cards
* fix(wisdom): update moderation results on existing sharing cards
* fix(wisdom): keep sharing review accessible from receipt cards
* fix(wisdom): align mediated review cards and collapsible checks
* fix(wisdom): clarify clean security summary wording
* fix(wisdom): normalize consent plans and add explicit recheck
* fix(wisdom): keep install and update receipts concise
* fix(wisdom): collapse assessments and deduplicate operation cards
* fix(wisdom): restore private Portal review from native cards
* fix(wisdom): sync Portal publication to original consent card
* fix(wisdom): show local skill version on sharing cards
* fix(wisdom): skip agent recommendations for self-published versions
* fix(wisdom): simplify candidate notices and local-edit recovery copy
* feat(wisdom): submit locally reviewed packages with one confirmation
* feat(wisdom): expose safe receipt and outcome sync recovery
* wisdom: onboarding notice says detect and share, names the user's own skill
Copy review from the product owner on the first and returning
qualification notices (fixed delivery mode):
- the feature blurb now says the org enabled detection *and sharing*
- both notices say the detected skill is one the user created
- both close with an exclamation mark
Applied identically to hermes_wisdom.notice, the desktop and web i18n
strings, and the tests that assert the sentences.
* wisdom: one opener, no approval line, ask to share after the skill is shown
Product owner review of the candidate card.
- The Hermes written card now opens with the same sentence as the fixed card
("Your organisation has enabled Collective Wisdom, a feature designed to
automatically detect and share useful skills across all team members.")
instead of its own blurb, so there is one first time message.
- "Nothing is shared without your approval." removed from Telegram, Slack
and Desktop. The buttons already make the permission explicit.
- "Would you like to share?" no longer appears before the skill is named.
It is now the last line, after the skill name, description, why suggested
and the checks, and reads "Would you like to share it?" (matching the
agent led template wording).
Tests updated for the new order; proposalNotice removed from all desktop locales.
* wisdom: American spelling, organization
Product owner decision: user facing copy uses American spelling.
Changes "Your organisation" to "Your organization" in the chat notice,
the Hermes written card opener, the desktop and web strings, and the
tests that assert them. Identifiers such as nas_organisation:* and the
German and French locales are untouched.
* wisdom: candidate card copy round 4 (owner review)
Apply the product owner's round 4 copy decisions to the Hermes Collective
Wisdom candidate card on Telegram, Slack, Desktop and the shared views:
1. Hermes-written cards are titled "Hermes Collective Wisdom" instead of
the bare "Collective Wisdom".
2. The "Reusable skill ready to review" line is gone from the candidate
card (Telegram rich card and plain fallback, legacy agent-led share
template).
3. The skill name and description are labelled: "Skill name: <name>" and
"What it does: <description>" (Telegram, Slack, Desktop).
4. "Why suggested:" is now "Why others might benefit:".
5. A passing professionalism review reads "Safe to share at work ✓ (no
inappropriate content found)" with no per-check bullets and no "Pass";
a failed review reads "Needs a look before sharing at work (possible
inappropriate content)" and lists only the checks that flagged
something. Pending/unavailable wording is unchanged.
6. Telegram button toasts: "Will ask later...", "Preparing more
details...", "Sharing...".
7. Qualification reasons: "You used this skill consistently across many
days." and "You've really refined this skill."
8. prompts/wisdom_candidate_review.md asks for a compelling
editorial_name, a simple one_line_description and a compelling
why_coworkers_benefit under 300 characters; "Be concise and
convincing." becomes "Be concise and compelling: the goal is that the
user wants to share it."
Tests updated for the new strings; review_text() gains direct coverage.
* wisdom: re-apply owner copy after rebase
- Native share cards (advice_view/interaction_view): drop the approval line, ask "Would you like to share it?" as the last line after the checks
- Hermes-written completion card titled "Hermes Collective Wisdom"
- Qualification reasons use the owner wording (consistently across many days / really refined)
- American spelling (organization) in remaining English copy
- Desktop test asserts the current Share button; web test matches the returning notice
* fix(wisdom): pin reconciled Gateway and verify Unicode hash vectors
Pin Gateway 60cd2d6b613ae3cd4a6e65155d1142006d907e78 and byte-identical producer artifacts. Verify every content-order case and package-manifest binding. Validation: 186 focused Python tests, Ruff and contract verifier.
* fix(wisdom): reconcile optional SDK tests and frontend lint
* fix(wisdom): default to agent-written notification summaries
* fix(wisdom): restore deferred install review and browse controls
* feat(wisdom): inspect installed setup with exact package provenance
* feat(wisdom): run native-approved installed setup steps with durable evidence
* fix(wisdom): recover interrupted setup with explicit native consent
* feat(wisdom): hand native installs into guided setup review
* fix(wisdom): continue requested setup with fixed notification copy
* fix(wisdom): preserve setup while waiting for a session model
* fix(wisdom): expose canonical setup review controls on desktop
* fix(wisdom): resume setup after recorded automatic updates
* fix(wisdom): make missing setup prerequisites recheckable
* chore(wisdom): align Agent with verified Gateway contract
* fix(wisdom): stop guessing team slugs in portal links
* fix(wisdom): retire pending advice on account sign-out
* fix(wisdom): cancel advice after terminal account revocation
* fix(wisdom): fence feed responses across account sign-out
* fix(wisdom): checkpoint signed-out feed before reactivation
* fix(wisdom): link proactive advice to scoped notification settings
* fix(wisdom): coalesce queued publication recommendations by version
* fix(wisdom): keep package review navigation local and deferable
* fix(wisdom): reflect installed state in discovery controls
* fix(wisdom): show exact checks before command confirmation
* chore(wisdom): pin bounded analytics privacy contract
* chore(wisdom): pin retired legacy notification contract
* feat(wisdom): review publisher usage with exact sharing copy
* fix(wisdom): align discovery and review check summaries
* fix(wisdom): show expired consent before confirmation
* fix(wisdom): require fresh review for legacy install controls
* fix(wisdom): preserve review expiry across check toggles
* fix(wisdom): retain update policy in native install reviews
* fix(wisdom): surface failed native card edits
* fix(wisdom): persist local command approval reviews
* fix(wisdom): use saved approvals for messaging commands
* test(wisdom): provide scan result in setup handoff fixture
* test(wisdom): exercise Telegram approvals with saved review state
* fix(wisdom): retain suppression policy for offline deferral
* fix(wisdom): reconsider candidates after deferred suppression expires
* fix(wisdom): bind review checks and report verified readiness separately
* fix(wisdom): persist accepted publication intent and recover exact outcomes
* fix(sync): pin UTF-8 tree ordering across writers
* chore(wisdom): pin organisation-scoped Gateway authorization
* fix(wisdom): restrict consent delivery to user-facing sessions
* chore(wisdom): refresh reviewed Gateway contract pin
* fix(wisdom): preserve kept tools in Blank Slate exclusions
* test(auth): reset anonymous fixture with a profile-scoped cache
* fix(wisdom): gate local surfaces and work on current profile entitlement
* fix(wisdom): invalidate quiet tool cache on entitlement changes
* test(wisdom): authorize local consent gateway fixtures
* fix(wisdom): keep entitlement decoding free of native crypto imports
* test(wisdom): provide local entitlement to demo CLI subprocess
* ci: leave upstream workflow unchanged in Wisdom PR
* fix(wisdom): ship package and contracts in Nix wheels
---------
Co-authored-by: hbizi <36184542+hbizi@users.noreply.github.com>
|
||
|
|
c2d01f1012 |
fix(desktop): key the page under the stamp its rows already carry
A custom HERMES_HOME (outside the profiles tree) reported `profile: null`, so the tail entry landed under profile '' while the session's owner hint — built from list rows that _serving_profile() stamps "default" — looked up 'default'. Two identities for one backend; the exact-key lookup missed and "Show earlier" stayed hidden for the Desktop's own sandboxed and HERMES_HOME-overridden installs (electron/main.ts resolveHermesHome). The messages endpoint now returns the same _serving_profile() stamp the list rows carry, so the Desktop keys a page under the owner it already routes the session by; the inline resolver and its function-body import go away. On the renderer, the ambient profile used for backends that predate the field is captured at request time alongside the connection, so a profile switch mid-read cannot re-key the page. |
||
|
|
fd9c52ae1e |
refactor(desktop): name the tail entry's route and owner
TranscriptTailState.profile was documented as the owning profile but now holds the request ROUTE (the owner is the map key). Rename the recordTranscriptTail parameters to route/owner, fix the field doc, say why a bare-string scope still defaults to 'default' while an object scope does not, and correct transcript-tail-cache's claim that it mirrors the in-memory key (it keys by request scope and the two stores never cross-read). |
||
|
|
a0977b0a3c |
fix(desktop): coalesce tail spellings against backends without the profile field
A backend that predates the `profile` response field left the owner key's
profile empty, while the resume path records the same session under
{connectionId, profile: 'default'}. Two entries per backend meant the
owner-hint lookup only ever saw the resume entry and the no-hint fallback
returned nothing — "Show earlier" stayed hidden for exactly the mixed
Desktop/backend versions the PR body says remain readable.
An absent field now falls back to the ambient request profile ('default'
when none is active): that IS the profile an untagged read lands on. A
`null` field (custom HERMES_HOME) still stays distinct from 'default'.
|
||
|
|
1affcb9386 |
refactor(desktop): resolve the ambient tail owner through the api/client seam
api/sessions.ts imported $connection from @/store/session to learn whether an untagged read lands on the local pool. That closes a real module cycle (api/sessions -> store/session -> session-unread-remote -> @/hermes -> api/sessions), inert only because the read sat inside a function body, and it breaks the "no store import" contract client.ts documents for exactly this reason. Publish the fact through the seam that already exists: store/session's setConnection pushes local/remote into client.ts (setApiRequestLocalMode, next to setApiRequestProfile / setApiRequestConnection), and ambientOwnerConnectionId() answers "which backend serves an untagged request" for the owner key. A null descriptor (reconnect blip) keeps the last mode, matching rescopeConnectionScopedStores. |
||
|
|
e52c97b8e7 |
fix(tests): align desktop fixtures with the jsdom window contract
Pairing tests stub only the Electron preload bridge so the real jsdom window and its event APIs survive; paging fixtures use nonzero deterministic timestamps so the conversion-time Date.now() ID fallback cannot produce a mismatch across separate conversions. (Second commit of #107839, minus the test_anon_auth_core.py change that main already carries as 0c0983ab21.) |
||
|
|
d45842d206 | fix(desktop): preserve older history pagination across scopes | ||
|
|
128e411023 |
fix(desktop): resolve passive reads inside the backend resolvers
The salvaged commit answered a passive read with its own pool lookup in the
two REST dispatchers. That lookup recomputed the pool key and got it wrong
for every route that is not the plain v1 pool: forced-local registry children
live at `conn:local::<profile>`, and primary-routed profiles (the active
profile, global-remote, registry-primary reuse) are served by startHermes()
and are never in backendPool. Bot tiles always carry an ownerRoute with a
connectionId, so their reconcile threw "no warm backend" on every tick for
a warm backend, and the bare catch in reconcileTileTranscripts hid it --
open bot chats silently stopped receiving background deliveries.
Thread `passive` into ensureBackend / ensureRegistryBackend instead and
decide at the one place each route already does its `backendPool.get`:
existing entry -> serve it without refreshing lastActiveAt; no entry ->
throw before evictLruPoolBackends/spawn. Primary routes never reach a
spawn site, so they stay served. The remote-descriptor revalidation stays
on the passive path.
A passive read never dials, so the registry dispatcher keeps it OUT of the
backendDialClaims coalescing: an interactive open joining an in-flight
passive read would otherwise inherit its "no warm backend" rejection
instead of spawning.
getLatestSessionMessages takes `{ passive }` (matching getSessionMessages)
instead of a positional boolean; the reconcile tests assert the passive
intent.
|
||
|
|
66a56cc337 |
fix(desktop): prevent background tile reconcile from starving pool slots (#103375)
Passive tile reconciles (reconcileTileTranscripts on every sessions.changed tick) were cold-starting pooled backends for every open bot tile, including hidden ones, and holding each spawned slot for its lifetime via the 10s touch loop. With 15-20+ profiles this permanently saturated the Warm Bot Backends pool so interactive opens timed out. Add a passive read intent: getLatestSessionMessages now accepts a passive flag forwarded as HermesApiRequest.passive. The Electron main process honors it by serving only already-warm pool entries and failing fast otherwise, without spawning or bumping lastActiveAt. reconcileTileTranscripts uses passive reads so background refresh never consumes a pool slot; interactive opens keep the normal spawn path. |
||
|
|
52dea2c7d9 |
feat(desktop): Telegram QR quick setup + persistent restart banner on Messaging
Port the dashboard Channels flow into the Desktop Messaging page: - Telegram "Quick setup": the Nous pairing service mints a bot; the page shows the QR / deep link, polls until Telegram confirms, pre-fills the detected owner id into the allowlist, and applies. The backend writes the token + allowlist into the scoped profile and restarts the gateway; the page watches the restart child's exit so a failed restart surfaces instead of a silent "gateway stopped". - Restart-needed banner: every credential save / clear / enable toggle keeps a banner with "Restart now" up until a restart actually completes. Toasts vanish; the credential still only loads on the next gateway start. - runGatewayRestart now resolves a boolean (never rejected before, so the Webhooks page's catch path was dead and its banner cleared on failure). - New API helpers carry the page's profile scope on every pairing call so start/status/apply hit the same backend that holds the pairing in memory. - i18n keys in all six locales; `qrcode` added to the desktop deps (already pinned identically in web/). |
||
|
|
1f92ab1ddf |
fmt(js): npm run fix on merge (#105106)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
0b3391322c |
fix: keep desktop provider setup within its selected profile
Remount scope-owned credential state on Applies-to changes and bind onboarding requests to their initiating route. Cancel polling and invalidate late results when setup closes or reopens, without undoing writes already sent. Co-authored-by: By JTT <29462570+jordan-thirkle@users.noreply.github.com> |
||
|
|
e3ba651b6d | fix(desktop): relay MCP OAuth through client-local callbacks | ||
|
|
6840bb02e8 |
fmt(js): npm run fix on merge (#101102)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
4e3feb8bbb |
feat(tts): speech toggles warm up and unload local TTS engines (#100881)
Desktop "Read replies aloud" / voice conversation, TUI and CLI /voice tts now hold a lease on the TTS engine. Acquiring pre-loads the configured provider (piper/kittentts model into the same LRU slot synthesis reads; lazily-installed cloud SDKs), so the first spoken reply no longer pays the model load as dead air. Releasing the last lease across surfaces unloads resident local models. - tools/tts_tool.py: warm_tts_provider / release_tts_provider / acquire_tts_lease / release_tts_lease over a _LOCAL_TTS_MODEL_CACHES registry; piper/kittentts loaders extracted so warm-up and synthesis share one resolution path. - web_server: POST /api/audio/tts-lease (profile-scoped, off-loop, failures reported in body never as HTTP errors). - tui_gateway voice.toggle + cli.py /voice tts|on|off wire the lease. - desktop: lib/tts-lease.ts (dedupe, per-lease serialization, latest intent wins) driven from useComposerVoice; setTtsLease API client. - docs: features/tts.md section. Live (real piper, isolated HERMES_HOME): first synthesis 988ms cold → 92ms after the toggle warmed the engine; release drops the model. |
||
|
|
3d81650c2f |
fix(desktop): a failed sidebar scan keeps the rows it could not re-read
The sidebar reports a profile it could not scan as HTTP 200 with an empty
page and errors=[{profile}]. The renderer merges that page keeping only
working, pinned, and selected rows, so every idle Yesterday / This-week
session disappears until a later scan succeeds — and the 5s coalescing cache
then serves the same empty payload back for the rest of its TTL.
Carry the previous rows forward for exactly the profiles named in errors[],
keyed by profile::id so a twin id in another profile is never stitched in.
Profiles that scanned cleanly are still authoritative, so a genuinely empty
page with no errors still clears the list. Per-profile usage and truncation
flags follow the same rule rather than zeroing under a list that was kept.
The legacy per-slice fallback stamps errors on the slice that actually
failed, so a cron read failure can no longer blank recents.
Part of #73847
Part of #88528
Co-authored-by: AKAZIK-py <AKAZIK-py@users.noreply.github.com>
|
||
|
|
43e67d872f |
feat: local models — managed llama.cpp runtime with one-click desktop setup
Run models locally as a first-class provider. The CLI grows a managed llama.cpp runtime (engine install, model download, server supervision); the desktop app grows the full setup and management story on top of it. GUI surfaces ship behind the desktop --local launch flag (hermes desktop --local, or the flag on the packaged app); backend routes and the CLI are always live. Runtime (hermes_cli/local_runtime/): - curated GGUF catalog with per-machine variant selection: hardware probe (VRAM/RAM/UMA), fit planning with spill accounting, quant choice by context window - derived recommendation: quality-ranked picks gated by a predicted decode-speed floor, bandwidth-aware on unified memory; the decision table is pinned as a test (pick AND reason per memory class), and the Recommended badge explains its pick in a tooltip fed by the resolver's actual branch - engine install + model download with resumable split parts, cumulative plan-level progress, and staged-model integrity (a split GGUF counts only when every part is present) - server supervision: spawn/adopt/stop, router mode with per-model load progress relayed over SSE, abandoned-request cleanup Desktop: - Settings -> Providers -> Local models: one-click quickstart (install engine, download the recommended model, boot) plus per-model download/ activate/eject, fit-ranked catalog with context pills - model pickers (composer dropdown + Cmd+K) show staged local models, in-flight downloads as live progress rows, and load-into-memory bars - local-setup campaign tip for eligible hardware; System resources statusbar widget (GPU/VRAM/RAM); in-chat load progress during sends - friendly dead-server errors, and failed agent builds retry on the next send instead of wedging the session Co-developed with NVIDIA field feedback on RTX 5090 and DGX Spark. |
||
|
|
a1c25d393a |
feat(desktop): built-in optional-skills catalog in Capabilities → Skills with one-click install
The Skills tab now lists the entire official optional-skills catalog (optional-skills/ shipped with the repo) below the installed skills. Each catalog row has an Install button that routes through the standard hub action pipeline; once the install finishes the row flips into the installed list with the normal enabled/disabled toggle. - backend: GET /api/skills/hub/official — OptionalSkillSource.list_local() scan (no network) + per-profile installed flags from the hub lock - desktop: catalog section in SkillsView with search/scope integration, install-state spinners off $hubActions, and an OfficialSkillDetail pane (hub preview: frontmatter + full SKILL.md + Install) - CapRow gains an optional action slot (button instead of the Switch) - electron: route the new endpoint with the skills family (primary backend) - i18n: officialCatalog/officialPill keys across en/ja/zh/zh-hant |
||
|
|
18f429a89b |
fix(desktop): scope every session mutation to its owning profile
Session mutations from the desktop sidebar silently no-op against the wrong
profile's state.db and reappear on the next refresh, unless the mutated
session happens to belong to the serving (primary) profile. Most visible in
the "All Profiles" view and on a remote-primary desktop (a registered remote
gateway as primary, every profile served from it): deleting a session flashes
away optimistically, then comes back after a profile switch.
Root cause: the mutation helpers passed the owning profile ONLY as
request.profile, which the Electron main process consumes for backend ROUTING
but which does not scope the request the backend actually receives. The
backend selects its target DB from ?profile= (DELETE) or body.profile (PATCH).
Reads (getSession, getSessionMessages) and renameSession already scope
correctly; the other mutations regressed / never did:
- deleteSession -> no ?profile= in the URL
- setSessionArchived -> no profile in the PATCH body
- setSessionPinnedRemote -> no profile in the PATCH body
- setSessionUnreadRemote -> no profile in the PATCH body
On a remote gateway whose connection has no remoteProfile alias, the main
process leaves such requests unscoped, so the backend opens its own (default)
state.db, cannot find another profile's row, and returns
{ok:true, already_absent:true} (DELETE) or no-ops (PATCH) — a fake success the
UI treats as done. deleteSession lost its ?profile= scope in the api/ module
split (it was present via PR #44138 / the pre-split hermes.ts path).
Fix: scope all four mutations the same way the working endpoints do —
deleteSession appends sessionScopeQuery(profile) to the URL; setSessionArchived
/ setSessionPinnedRemote / setSessionUnreadRemote include profile in the PATCH
body (mirroring renameSession). request.profile stays for per-profile
remote-override and global-remote routing. Single-profile / selected-profile
users are unaffected (the serving profile already matched).
Verified against a live remote-primary desktop: the DELETE now goes out as
/api/sessions/<id>?profile=<owner> and the row is actually removed from the
owning profile's state.db (confirmed server-side) instead of returning
already_absent.
Adds api/sessions.test.ts coverage: delete scopes ?profile= in the URL for
object and bare-string owners and omits it when no owner is known; archive /
pin / unread carry body.profile when owned and omit it otherwise.
Fixes #78836
|
||
|
|
6cb6aeb168 |
feat(desktop): real-profile browsing toggle in Capabilities → Tools → Browser
Users reported no GUI switch for browser.use_real_profile — the only desktop home was the generic Settings → Config editor, which nobody found. The Browser toolset detail pane now renders a 'Use My Real Browser Profile' ToggleRow above the backend/provider matrix. - new BrowserRealProfilePanel: reads the shared profile-scoped config record cache, optimistic write-through, rollback on failure - saveHermesConfigRecord: capability-scoped PUT /api/config counterpart of getHermesConfigRecord, so the Capabilities scope selector writes the profile it points at (possibly another gateway) - i18n: en/ja/zh/zh-hant keys (ar inherits en via defineLocale) - docs: browser.md desktop pointer corrected to the real location Live E2E on the built app over CDP: clicking the switch flipped browser.use_real_profile true→false→true in the sandbox HERMES_HOME config.yaml, GET reflected it, no layout glitches (screenshots in PR). |
||
|
|
fe576ba48e |
fix(desktop): a 'This device' Capabilities pick reaches the local machine again under a remote registry primary
Since
|
||
|
|
9faa685385 |
feat(desktop): read-only stored-transcript resume + legacy owner-backfill trigger (#94724)
The fail-closed owner ladder (#95407) is correct for new sessions, but legacy unowned rows on registry-topology installs dead-ended in SessionOwnerResolutionError (reporter's Error B) with their transcripts fully intact in state.db. - resolveLegacyOwnerBackfillScope: pick the single-match store for the server-side owner backfill at enumeration time (serving registered connection / primary pool); fail closed on multi-candidate topologies. - maybeBackfillLegacySessionOwners: one-shot per scope per renderer, fire-and-forget from the #95407 stamp path, logs the stamped count. - Read-only stored-transcript resume: when session.resume fails closed, fetch the transcript over id-only REST (ambient first, then registered backends, read-only probes only) and open the session as a read-only transcript instead of dead-ending; sends are refused with a notice and a later successful live resume clears the latch. Wired into the main pane resume recovery and the session-tile delegate (which now runs the same fail-closed owner gate as the RPC dispatcher). Refs #94724 |
||
|
|
d81b801fb4 |
fix(desktop): bound getConnection()/resolveGatewayWsUrl() on every remaining route
20s withTimeout() on the boot() and soft-switch paths (use-gateway-boot.ts), since these are IPC round-trips into the main process with no timeout of their own — a wedged main-process round-trip hangs the awaiting caller forever instead of surfacing a failure. Every other production call site of the same IPC pair was still unbounded: - store/gateway.ts's openSecondary() and sharedPrimaryRoute() — the actual connection-establishment underneath requestGatewayForProfile/Agent, ensureGatewayForProfile/Agent, and every other exported routing entry point that opens a non-primary profile's socket. - use-gateway-request.ts's on-demand reconnect (the primary gateway's "not connected" retry path hit by every RPC). - voice-playback.ts's resolveSpeakStreamUrl(). - api/plugins.ts's activeConnection() (pluginSocket's connect()). Extracted RECONNECT_ATTEMPT_TIMEOUT_MS into the shared lib/with-timeout.ts (previously local to use-gateway-boot.ts) so every call site uses the same budget instead of duplicating the constant. Regression tests mirror the existing use-gateway-boot.test.tsx hang-repro pattern: wedge getConnection()/getConnectionFor() with a never-resolving promise, advance fake timers past the 20s bound, assert the caller settles instead of hanging. Mutation-verified: reverted the production fix (kept tests) and confirmed the 6 new tests fail — 4 by genuinely timing out at the vitest level, 2 by TypeError on the not-yet-exported activeConnection — restored the fix and confirmed all 70 tests across the gateway/voice/boot/ plugins suites pass, with tsc -p . --noEmit clean throughout. |
||
|
|
fd565c80e9 |
feat(desktop): fleet profile rail — every registered gateway's agents on one strip
With several gateways registered, the Sessions profile rail only ever showed the active gateway's profiles; reaching a bot on another machine meant a gateway switch first, then a click on the rail that appeared afterwards. Bot Mode (#91134) and Capabilities already read the union agent roster; the rail is now its third consumer. - Every registered gateway's profiles sit on the one strip, in registry order (This device first, then by label), each group headed by that gateway's kind glyph. The active gateway's squares are unchanged; the others are "at rest" (dimmed) with tooltips/accessible names qualified by machine (`inbox · Homelab`), so same-named profiles never read alike. - Clicking an at-rest square performs the same dial → commit → re-home as the statusbar switcher, landing on that exact (gateway, profile): `selectConnection(id, { profile })`. The spinner sits on the clicked square; the previous source stays painted until the target answers. Groups keep their slots whichever gateway is active, so a square never moves under the pointer that clicked it. - Right-click on an at-rest square: Switch to / Color / Rename / Edit SOUL.md / Delete, executed on the owning gateway (renameProfile, getProfileSoul and updateProfileSoul accept the same scope deleteProfile already had); the delete confirmation names the machine. The legacy per-profile "Connect to a remote host…" item is hidden on multi-gateway setups, where the rail shows machines directly. - Unreachable gateways keep their squares with an amber dot on the glyph; two registrations of one backend collapse to one group; past thirteen squares across the fleet the strip condenses into a menu sectioned by gateway. Roster is fetched on mount / focus / registry change only — no periodic fleet polling. - Single-gateway Desktops render exactly as before: no roster fetch, same DOM. Also fixes a boot race the e2e surfaced: initializeConnectionsRegistry() "restored" the launch-mode source over a switch the user had already made while boot was settling (same class as #91047). The restore now yields when a switch is pending or already landed. Tests: pure grouping (fleet-rail.test.ts), rail component fleet mode (profile-rail-fleet.test.tsx), store (explicit profile pick; restore yields), and a Playwright e2e (fleet-profile-rail.spec.ts) that boots Desktop with two REAL backends — the local one plus a second `hermes serve` registered as a remote URL connection — and verifies layout, a real re-home, gateway-scoped actions, and order stability. Docs: multi-connection-desktop.md describes the fleet rail. Refs #89304, #92384, #91047, #94724 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
2947272233 |
refactor(desktop): one canonical write shape for connection_id row stamping
Reconcile #94901's API-layer row stamping with #94656's durable-owner persistence: extract lib/session-owner-stamp.ts as THE canonical stamp-untagged-rows write path (never clobbers an explicit owner, never stamps `local`) and re-express api/sessions' stampActiveConnectionOwner through it. #94656's writers (optimistic row from the captured owner route, mergeSessionPage carry, cache patch) are exact-owner writers and stay as-is; the helper's contract documents why it must not overwrite them. Credit: row-stamping concept from PR #94901 (joe-rodgers) and PR #95007 (weismanfamily); persistence shape from PR #94656 (Zeus-Deus). Co-authored-by: joe-rodgers <25499388+joe-rodgers@users.noreply.github.com> |
||
|
|
fb393ee08b |
fix(desktop): stamp remote list rows with their owning connection; retry one transient projects.tree loss
Partial cherry-pick of PR #94901 (joe-rodgers). Surviving scope: - api/sessions: stampActiveConnectionOwner — rows returned by the active non-local gateway are stamped with its registry connection_id (explicit owners from multi-source responses preserved), so a later resume cannot fall back to a same-named local profile. - store/projects: one-shot projects.tree retry when a remote source switch leaves the first read RPC on a newly-opened socket without a response (request timed out / gateway connection closed), only while the same gateway/profile is still foreground. Component fix for the live-confirmed #92352 sidebar-never-paints gap. Dropped scope (superseded on main / by the #94656 anchor landed just below): knownSessionOwner+SessionOwnerScope rewiring in session.ts, session-states.ts, wiring.tsx (main and #94656 carry richer variants), and the $connection-derived optimistic-row stamp in use-session-actions/utils.ts (#94656 stamps the optimistic row from the captured exact owner route instead of ambient state). Original-PR: #94901 Dropped-scope: routing half of 2cb5bdbf1 (session.ts, session-states.ts, wiring.tsx, use-session-actions/utils.ts hunks) |
||
|
|
02c7ae956e |
fix(desktop): route session list REST through the active profile
Sidebar and legacy session-list helpers tagged the registry connection but not the active profile, so Electron routed those reads to the wrong backend after a profile or remote switch. Keep hermesApi connection-only: stamp profileScoped on the list helpers instead of every REST call. Co-authored-by: noah <loahnisk@gmail.com> |
||
|
|
f377140e3d |
fmt(js): npm run fix on merge (#92815)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
336b3216d5 |
Merge current upstream into fix/remote-bot-routing
Preserve the connection-bound Bot and session routing implementation while
adopting upstream's modular Desktop API split and all changes through
|
||
|
|
aa20dbe73e |
refactor(desktop): split src/hermes.ts into src/api/ domain modules
2,248 lines of gateway REST client become twelve modules by domain, with hermes.ts left as a barrel so all 144 importers stay put. The import graph is a star — every domain module imports only ./client, and client imports nothing back — so there are no cycles. The barrel names client's public exports rather than re-exporting it wholesale. Splitting a module forces its private helpers into exports so siblings can reach them, and export * would then republish them: profileScoped, connectionScoped and capabilityScoped were private to hermes.ts and have to stay that way, or a call site can assemble its own request scope and drift from the api layer. |