Drop two PrimaryProfilePin cases that only restate the constructor
defaults and blank-string normalisation, and the wiring-routing test that
froze POOL_LIMITS_SETTINGS_ROUTE to a literal string — a snapshot of the
constant, not a behaviour contract. The two kept pin tests cover the bug
(a live primary keeps answering for its booted profile after the stored
preference moves; teardown releases the pin), and the notifications tests
cover the toast action end-to-end.
`primaryProfileKey()` re-read active-profile.json on every call. The rail's
live workspace switch rewrites that file via `hermes:profile:remember`
WITHOUT re-homing the primary, so after a switch the routing table disagreed
with the running process: a request for the profile the primary actually
booted as (e.g. "default") no longer matched `primaryProfile` in
`resolveProfileBackendRoute`, fell through to the pool, and spawned a second
backend for the same HERMES_HOME.
The duplicate was keepalive-fresh so LRU eviction spared it, it burned a pool
slot, and with the default cap of 3 every further profile queued and failed
with `Local backend start for "<profile>" timed out while waiting for a free
slot` (repro in desktop.log: "default" spawned as a pool backend while the
primary "default" was still running; coder/qwen then timed out for 20+ min).
Snapshot the launch profile in `startHermes()` (PrimaryProfilePin.pin) and
release it in `resetHermesConnection()` so the next start follows the stored
preference again. The pin is a tiny pure module with tests; main.ts only owns
the file read and the two call sites.
Switching the pooled dispatch probe to /api/health (salvaged from #97914)
would 404 on every dispatch against a remote older than 0.19, retire the
tunnel and reconnect forever - the same storm #107997 describes, moved to
old backends. Fall back to /api/status on an explicit 404 exactly the way
the boot readiness probe already does (backend-health.ts). The legacy
fallback idea and its test are taken from #101976 (@edosulai); the rest of
that PR (timeout-tolerance streak, ServerAlive SSH options) is not adopted.
Co-authored-by: Edo Sulaiman <edosulai@icloud.com>
Cold /api/status through a Windows no-mux SSH forward routinely exceeds
the 2.5s dispatch budget, so Desktop retires a live tunnel and respawns.
Use the cheap /api/health route (5s, same as DEFAULT_HEALTH_PROBE_TIMEOUT_MS).
Background liveness still probes /api/status at 10s.
A 2500ms dispatch probe is shorter than quiet-box hermes serve cold-start (~6-8s), so a just-woken pooled backend always fails and reconnects. Reuse REMOTE_LIVENESS_TIMEOUT_MS (10s) for that probe.
Co-authored-by: Cursor <cursoragent@cursor.com>
useRoster repaints every 5s and hands pullServerAvatars the active-source
rows. Since the multi-source merge (ed20a6f01a) every such row is
sourceScoped, so the avatar sync branch chose requestForBot and dialed each
bot's OWN backend to read a profile-directory PNG: a fresh WebSocket with
one JSON-RPC message, torn down at refcount 0, per bot per tick (#99336's
"ws accepted / ws closed messages=1" every ~5.2s on background profiles),
and for every bot with no running backend a pool spawn that waits out
POOL_SLOT_WAIT_MS (30s) and is re-queued by the next paint, forever, once
the pool is full (#102913's per-bot "waiting for a free local slot ...
timed out" cadence). The loop was self-sustaining because the plugin's own
160px face raster is deliberately not parked in $botMeta, so the empty
image slot re-fetched it on every tick.
Assets are files under the profile directory; the gateway that just
answered profiles.list reads them for any of its profiles. Route the three
avatar RPCs through host.request like the roster query itself, and remember
face-only answers so a row is fetched once, not once per tick.
Not changed: relay.ts (its loops dedupe to one route per registered
connection and return early below two connections, so a single-connection
desktop never issues a relay RPC), and useRoster's own profiles.list, which
already rides the active socket via requestForBot({name}).
The first Bot Mode roster paint after launch ran pullServerAvatars over every
row, and for a source-scoped row (every row on a local-primary desktop once
host.agents annotates the roster) each profiles.get_asset / set_asset went
through requestForBot -> host.requestProfile -> requestGatewayForAgent, i.e. a
(connectionId, profile) secondary that spawns that profile's pooled backend.
With ~60 registered profiles and 3 warm slots this queued 56 background spawns
at boot; each queued dial then rode reconnectSecondary's backoff until the
stall budget parked it (#107969), so the pool never drained and desktop.log
filled with "waiting for a free local slot" (#102978).
The active gateway's own profiles.list already produced these rows by reading
every local profile directory; get_asset/set_asset are the same directory
reads addressed by name. Route both through host.request on the active socket
with the row's backend profile name (route.targetProfile, so managed aliases
still resolve). No secondary socket, no pool slot, no spawn.
Cross-connection (remoteSource) rows never reached this path: pullServerAvatars
is fed activeSourceRoster, which filters them out.
host.warmAgent — the (connection, profile) sibling of warmProfile that
bot-row.tsx fires on pointerEnter for multi-source roster rows — still
dialed openGatewayForAgent directly, so a pointer sweep across a mixed
roster kept spawning at pointer speed past maxBackends on the registry
path even after warmProfile was guarded. Same bug class as #103631,
different door.
prewarmProfileBackend now takes an optional connectionId: the
active-profile no-op, the 60s throttle (keyed by the pool scope key) and
the pool-saturation skip apply unchanged, and the dial picks
openGatewayForAgent for a scoped source. One resolver owns every
speculative warm in the app; the real click still spawns on demand.
Plugin rosters warm profile backends on pointerEnter with no dwell of
their own. warmProfile dialed openGatewayForProfile directly, bypassing
the pool-saturation guard, hover dwell, and per-profile throttle that
prewarmProfileBackend enforces for the built-in rail — so a pointer
sweep across a roster could spawn past maxBackends and leave the next
profile's real spawn queued until the 30s slot timeout, surfacing as a
profile surface that hangs forever while every other profile renders.
Delegate to prewarmProfileBackend so every speculative warm shares one
resolver and one policy, as the design guide requires. The real click
still spawns on demand; only the speculative head start is gated.
Settings, Layout, and HUD default to the right so tabs keep the left
titlebar. Appearance has a Left/Right control for people who want the
previous left cluster.
(cherry picked from commit 7fe3175e475eb0ea81198bb69a0250662f940b17)
Keep panel tabs in the titlebar moved those app actions next to the
sidebar toggle, which ate the tab strip. Put them back on the right
edge. Sidebar toggle stays left. Fixes#107351.
(cherry picked from commit 7f4460a7f6028cf384506733a5bfa52273792d64)
`revealDesktopPane` drove files/review/sessions/terminal through their
store setters only. Those are same-value no-ops when the pane's `$open`
already reads true while the user minimized its zone from the header
chevron, so the `focus_pane` tool reported success over an invisible
pane (#106009; class noted by @worryfreeaa). Route every tree-backed
revealer through `revealTreePane` after its own setter, which clears
`minimized` and fronts the pane.
(cherry picked from commit 690a1a75108ec63ce5cb1a638543969b0877dbaa)
* fix(desktop): centralize guide handoff receipt reads
Resolve the guide receipt key and value together in setup-profile. Use the helper at all four read sites so connection scoping follows one implementation.
* fix(desktop): recover from unreadable handoff receipts
Memoize receipt reads and show Retry only for the error phase. Quarantine corrupt data before retrying, and resolve the guide identity when the failed request did not retain it so a fresh build can start.
Cover preservation of corrupt data and removal from the active receipt key with an invariant test.
* fix(desktop): validate persisted onboarding phases from one list
Derive OnboardingPhase and persisted-value validation from the same phase list so future phases survive relaunch. Verify every persisted phase reloads and an unknown value falls back to idle.
* fix(desktop): share window centering arithmetic
Extract centeredBounds and use it for onboarding boot and window growth. Keep the existing work-area clamps and coordinate rounding unchanged.
* fix(desktop): compute progress steps inline
Remove the ineffective ProgressCard memo because streaming flushes replace the messages array. Keep the same transcript scan and rendered steps.
* fix(desktop): center the free-tier status chip detail
Wrap the model label and sign-in badge in an inline flex span with a shared gap. This centers the badge beside the model text without changing other status-bar details.
* fix(desktop): derive the guide receipt key in one place
The Retry path spelled the key derivation out again because the read helper throws on a corrupt receipt before it can return the key. A separate guideHandoffReceiptKey serves both the reader and the quarantine, so the derivation has one home again.
* fix(desktop): keep the free-tier badge at its intended leading
Badge declares leading-none, but the class merger drops it behind the size variant's font-size class, so the badge inherits a 1.5 leading and renders 16px tall next to an 11px label. That height, not the inline alignment, is what read as a detached badge. Restating leading-none on the chip's badge brings it to 11.6px, inside the label's cap height. The Badge component itself is left alone; every other badge in the app has the same dropped leading and that is a separate decision.
- Multi-range requests (any comma) now fall back to a full 200 instead of silently serving only
the first part (RFC 7233 permits ignoring Range); documented + pinned by a test.
- Open the file first and fstat that handle, then stream from the same FileHandle, so
Content-Length and the bytes delivered come from one open file (no stat/stream race).
Handing the FileHandle (not the raw fd) to createReadStream avoids a double close.
- ENOENT/ENOTDIR return a 404 Response instead of rejecting; covered by a test.
The custom setPermissionCheckHandler only allowed media/audioCapture/
videoCapture, which made Electron deny the 'automatic-fullscreen'
permission consulted during HTML5 video requestFullscreen(). The
request handler's isMediaCapturePermission() also returned false for
'fullscreen'. Result: the native fullscreen button on <video controls>
in chat silently did nothing.
Allow 'fullscreen' + 'automatic-fullscreen' in both handlers.
Verified with a minimal Electron repro using Hermes' exact handlers:
requestFullscreen() failed with 'TypeError: Permissions check failed'
before; works after. User-verified in the packaged desktop app.
Let settled remote sessions continue their first quit. Fence late local starts and join existing local and SSH drains without cancelling managed update recovery.
Co-authored-by: Gille <4317663+helix4u@users.noreply.github.com>
Co-authored-by: ChanPark03 <parkchan0302@gmail.com>
Direct chat already uploads PDFs into the session workspace. Group turns
called pdf.attach instead, which needs pdftoppm and swallowed failures, so
bots saw the filename and no file. Stage PDFs the same way as other files,
and name a failed attach in that member's prompt.
Group PDFs currently only hit pdf.attach, so a member prompt can name the
file while the session workspace never receives it. These tests require the
same file.attach + @file: ref path 1:1 chat uses, and a named failure when
that staging throws.
Queue a forced follow-up when Test overlaps an older enumeration; retain bounded caching for incidental focus events.
Co-authored-by: FalconOrtiz <falcon.ortiz11@gmail.com>
Keep a runtime turn descriptor, feed its roster key into row mood and activity filtering, and release only the completing invocation’s presence. Stop uses the captured owner rather than a name lookup.
Co-authored-by: Tuna Dev <tuancookiez@gmail.com>
Preserve drafts and attachments through the existing nothing-sent contract. Port the command-shaped guard to the shared send boundary with behavioral coverage and localized feedback.
Co-authored-by: ClintonEmok <54935030+ClintonEmok@users.noreply.github.com>
#107239 hid the app's fixed titlebar clusters on every contributed full
page, so a plugin route that mounts no titleBar.* content got a bare
strip: sidebar toggle, settings gear, layout editor, HUD, flip and
right-sidebar controls all unmounted, and titleBar.tools items were
dropped with no opt-out on RouteContribution.
The band now yields only while the page actually projects chrome into
it. titleBar.* contributions are mount-scoped, so the presence check
follows the page; a chrome-owning page also keeps its titleBar.tools
items in the band.
useComposerMetrics dedupes its --composer-measured-height writes against
a "last published bucket" ref. The unmount cleanup cleared the surface
vars but left that ref alone, so after a non-final unmount (StrictMode
replay, Suspense hide) the re-mount measured the same dock, saw an
unchanged bucket, and never wrote the var back. The thread then read
the :root estimate (~62px) under a dock that could be 200px tall, and
the status stack covered the last turn until a real resize fired.
Reset the bucket refs in the same cleanup that clears the vars. Adds a
StrictMode regression test that fails on the old code.
* refactor(desktop): compress intro reveal
Remove the unused inline cinematic fallback and its skip callback plumbing now that the native window owns playback. Keep native timing and exit behavior unchanged, colocate the spinner with text effects, and document the current launch and handoff contract.
Area delta against B1: 47 additions, 53 deletions, net -6 lines across six files. Most fork verdicts were already applied in B1.
* refactor(desktop): compress guided chat surface
Remove random greeting variants and retain one existing opener per locale,
while preserving the banked greeting and machine-name suggestion. Trim
assembly commentary while keeping the reasons for its layout invariants.
Move solo-boot and window-growth IPC into a topical Electron sibling so
onboarding handlers no longer grow main.ts. Preserve sender gating,
reveal ordering and window geometry.
* refactor(desktop): compress onboarding handoff
Split welcome-chat kickoff from durable handoff effects and wire each
hook directly. Remove duplicated option types, a redundant readiness
comparison, nullable receipt-key state and stale prose while preserving
B1 routing and recovery.
* refactor(desktop): compress guided chat back half
Remove the duplicate handoff completion key and its reader/writer helpers.
Use the onboarding phase record for replay guards and settled cards while
keeping accepted receipts as the completion boundary.
Preserve the signpost and plugin plan under ruling 5.
* refactor(desktop): compress stores and transcript integration
Remove unused machine reset and untargeted host composer submission. Trim machine and presence commentary while preserving their live consumers. Wire reasoning through the existing scratchpad surface and memoise progress history without mutating it.
Keep parser and connector rendering under rulings 4 and 5. Area delta: 36 insertions, 101 deletions; net -65 lines.
* fix(desktop): keep skipped onboarding apart from a completed handoff
Make skipGuide() persist skipped and let beginOnboardingHandoff accept
guided or skipped. requestSetupHandoff and HandoffCard derive completion
from the accepted receipt.
The latch merge conflated skipping the guide with starting the first
build. A later handoff therefore claimed "was started" without creating
a session. Preserve skipping as its own terminal phase so a later
handoff can create the build and reach done only after acceptance.
* refactor(desktop): B2 review notes
Correct the layout-growth comment in assembly.ts: growing to preserve
the chat size balloons the window. Restore the WHY clauses in
onboarding-handoff.ts and onboarding-kickoff.ts for pending title metadata
on older backends and the caller's requestGateway reading the create pin.
Move guideSourceConnectionId beside $setupSession in setup-profile.ts
and derive each hook's option types from useSessionActions, so kickoff
no longer imports the heavier handoff leg.
Move $handoffError and retrySetupHandoff beside $setupHandoff in
setup-profile.ts and update the card and handoff hook importers.
This removes the setup-profile/handoff-receipt cycle and leaves receipt
persistence dependent only on storage and its receipt type.
* fix(desktop): derive the first-build receipt key one way
Use guideSourceConnectionId(guide.storedId) for the save and request
receipt keys, matching resume and HandoffCard. Keep guide.connectionId
for RPC routing and preserve the receipt key's string format.
At boot the resume path only knows the guide's stored id. When no owner
hint exists but an active gateway connection does, keying writes by the
resolver's ambient route hides the accepted receipt from resume and
leaves the card on Opening. One derivation lets every path find the same
receipt without changing where the build request is sent.
* feat(desktop): intro type at 150% for legibility
Set the intro window's root font size to 150%. Every measure in the intro
is in rem, so the chat card, its rows, bubbles and gaps scale together.
The brand close uses viewport units; its wordmark and tagline are scaled
by hand to match (6.8 to 10.2 vmin, 1.35 to 2 vmin). The hero card's
width cap rises from 900 to 1350 px so lines keep their length on a
large display; its minimum width is unchanged so the three-column stage
still fits a laptop.
The intro fills a display the user sits back from, and at the app's 16 px
root its text read too small on a large monitor (director ruling).
* fix(desktop): status bar keeps one fill under glass; free-tier chip reads Nous, model, Sign in
Under the glass appearance in sidebar scope, the body paints a hard stop
at the rail's edge (glass mix left, opaque chrome right) and the status
bar was transparent, so the seam ran through the bar and cut whichever
item sat on it: in the 886 px guided window, the free-tier chip. The bar
now belongs to the opaque content column across the full width, the way
Finder's does; window scope has no seam and keeps the transparent bar.
The chip itself read "Nous · free tier · nous/welcome" with the Sign in
badge touching the label. It now reads "Nous", the model id small and
monospace, then a solid Sign in badge set off by a gap; the full
"Nous · model" string moves to the tooltip. "Free tier" is no longer
said in the bar (director ruling).
* 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>
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.
A GitHub outage, a rate limit, a corporate proxy intercepting TLS and a DNS
failure all rendered as the same generic line, so #105855 read as a Hermes
bug during a run of GitHub incidents. The main process now classifies the
failure (HTTP status incl. 403/429 rate-limit and 5xx outage wording, DNS,
timeout, connection refused/reset, TLS) into one actionable sentence; the
overlay shows it under the title and About appends it to the status line.
* 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>
wipeSessionListsForGatewaySwitch cleared the persisted transcript-tail
cache but never the in-memory $transcriptTailBySessionId atom. Before this
stack both an ambient local read and an ambient remote read of a recycled
stored id landed under the bare-id key, so the newer backend simply
overwrote the older. Owner-keyed entries coexist instead, and the
unique-match lookup that gates "Show earlier" sees two candidates and
fails closed for that session until eviction. Wipe the atom alongside the
cache.