Commit Graph

12 Commits

Author SHA1 Message Date
Teknium
a5d1fe0864 refactor(hermes_cli/web_models): EnvVarReveal inherits EnvVarDelete, fold validator bool branch, compact field rationale 2026-09-02 23:54:30 -07:00
Teknium
2c77cbdcb7 refactor(hermes_cli/web_models): compact field comments and class spacing; JSON schemas byte-identical 2026-09-02 21:09:29 -07:00
Teknium
15aaa359bf refactor(web): strip web_models provenance banners, compact field comments (schema byte-identical) 2026-09-02 13:35:24 -07:00
Teknium
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.
2026-09-01 21:43:59 -07:00
Teknium
01f7ce5b76 feat(sessions): one-shot single-match owner backfill for legacy NULL-profile rows (#94724)
POST /api/sessions/owner-backfill stamps a store's own serving-profile
identity onto its pre-#95407 'profile_name = NULL' session rows. Single
match by construction (each profile's state.db belongs to exactly one
profile), idempotent, one-shot-per-row, never overwrites a non-NULL
owner, and reports the stamped count for logging.

Refs #94724
2026-08-27 02:17:56 -07:00
fangliquanflq
66186dc58f fix(desktop): keep bot reconciliation off inactive backends 2026-08-26 00:21:29 -07:00
Nicolas Formenton
c75d835555 feat(desktop): mark a session as unread/read with a persisted watermark 2026-08-15 01:21:40 -07:00
Brooklyn Nicholson
21aaa8b4f8 feat(desktop): resolve a session's pull request
A session row can say whether its work is open, merged or closed, and link
to it. The join is the session's own repo + branch, asked of GitHub in one
batched GraphQL request per repo (branch aliases, not a `gh pr list` page
that a busy repo crowds ours out of), through the remote-aware git facade so
a desktop on a remote gateway asks the backend's `gh`.

Two ways a session's branch can't answer, both covered:

- It ran on trunk. Fork PRs share our branch namespace, so asking about
  `main` badges a stranger's PR onto it — trunk is never asked about, and
  cross-repository PRs are dropped server-side either way.
- It worked in a worktree, so the branch it recorded at start isn't where
  the PR came from. Creating a PR from the review pane binds the session to
  the branch it actually used, and for sessions that predate that, the PR is
  recovered from the transcript: `gh pr create` prints a bare PR url and
  nothing else, so a tool result whose whole output is one is a claim rather
  than a mention. Scanned read-only across profiles, once per session ever.
2026-08-09 06:21:04 -05:00
Brooklyn Nicholson
d1196750c0 feat(profiles): REST export/import + extra_files overlay hook
export_profile() accepts extra_files (root-relative filename -> text) so a
caller can stage companion files into the archive; the desktop uses it for
desktop.json, its appearance/interface overlay, now part of the default
profile's export allow-list.

New routes wrapping the existing hermes profile export/import machinery:
- POST /api/profiles/{name}/export  (extra_files + optional output path)
- POST /api/profiles/import         (returns the bundled desktop overlay)
- GET  /api/profiles/{name}/desktop-overlay

Paths cross the API, not bytes - the desktop's native dialogs and its
local/pooled backends share a filesystem.
2026-08-04 12:07:29 -06:00
Brooklyn Nicholson
f174c0b6bb fix(pairing): scope the approve/revoke endpoints to a profile
The gateway keeps one PairingStore per served profile, but every
`/api/pairing` endpoint built the global one. An operator managing a named
profile saw the wrong pending list, and approving wrote a grant into a
whitelist their running gateway never consults — the user stays locked out
while the UI shows them as approved.

`_pairing_store(profile)` now resolves per profile and validates the name
(400/404 on an unknown one). No `_profile_scope` needed: PairingStore
resolves the profile's home itself, so nothing process-global is swapped
across an await.

Both GUIs had to change to match. The listing rides the query param — for
the dashboard that meant deleting `pairing` from the "machine-global, must
NOT be rewritten" exclusion list, a comment this change makes false. The
mutating endpoints read the profile off the BODY, which no query-param
rewrite reaches, so approve/revoke send it explicitly on both surfaces.
2026-07-29 18:43:50 -05:00
Flownium
774d92fbfa fix: approve listed pairing requests 2026-07-29 17:49:33 -05:00
teknium1
9179fb72ea refactor: extract web_server Pydantic models to web_models.py (pure schema move) 2026-07-29 10:15:07 -07:00